0
# Easy Interfaces
1
2
This document covers Mutagen's easy interfaces that provide simplified, normalized tag access across different audio formats. Easy interfaces abstract away format-specific tag names and provide dictionary-like access with common field names.
3
4
## Imports
5
6
```python { .api }
7
# Easy ID3 interface for MP3 files
8
from mutagen.easyid3 import EasyID3
9
10
# Easy MP4 interface for M4A files
11
from mutagen.easymp4 import EasyMP4, EasyMP4Tags
12
13
# Easy TrueAudio interface
14
from mutagen.trueaudio import EasyTrueAudio
15
16
# Easy MP3 class (combines MP3 with EasyID3)
17
from mutagen.mp3 import EasyMP3
18
19
# Base easy functionality
20
from mutagen._util import DictMixin
21
```
22
23
## Overview
24
25
Easy interfaces solve the problem of format-specific tag names by providing normalized field names that work consistently across different audio formats:
26
27
**Problem**: Different formats use different tag names
28
- MP3 (ID3): `TIT2` for title, `TPE1` for artist
29
- FLAC (Vorbis): `TITLE` for title, `ARTIST` for artist
30
- MP4 (iTunes): `©nam` for title, `©ART` for artist
31
32
**Solution**: Easy interfaces use common names
33
- `title` for title across all formats
34
- `artist` for artist across all formats
35
- Automatic conversion to/from format-specific names
36
37
## EasyID3 Interface
38
39
### EasyID3 Class
40
41
```python { .api }
42
class EasyID3:
43
"""Easy dictionary-like interface for ID3 tags.
44
45
Provides simplified tag names instead of ID3 frame IDs.
46
Automatically handles encoding and multi-value conversion.
47
48
Attributes:
49
valid_keys: Dict mapping easy names to ID3 frame names
50
filename: Path to audio file
51
"""
52
53
def __init__(self, filename: str = None) -> None:
54
"""Create EasyID3 instance.
55
56
Args:
57
filename: Path to MP3 file (optional)
58
59
Raises:
60
MutagenError: If file is not valid MP3 or has no ID3 tags
61
"""
62
63
def save(self, **kwargs) -> None:
64
"""Save ID3 tags back to file."""
65
66
def delete(self) -> None:
67
"""Remove all ID3 tags from file."""
68
69
def add_tags(self) -> None:
70
"""Add empty ID3 tags if none exist."""
71
72
# Standard easy field mappings for ID3 (alphabetical order)
73
EASY_ID3_FIELDS = {
74
'album': 'TALB', # Album title
75
'albumartist': 'TPE2', # Album artist
76
'albumartistsort': 'TSO2', # Album artist sort order
77
'albumsort': 'TSOA', # Album sort order
78
'arranger': 'TPE4', # Arranger
79
'artist': 'TPE1', # Lead artist
80
'artistsort': 'TSOP', # Artist sort order
81
'author': 'TOLY', # Original lyricist
82
'bpm': 'TBPM', # Beats per minute
83
'compilation': 'TCMP', # iTunes compilation flag
84
'composer': 'TCOM', # Composer
85
'composersort': 'TSOC', # Composer sort order
86
'conductor': 'TPE3', # Conductor
87
'copyright': 'TCOP', # Copyright message
88
'date': 'TDRC', # Recording date (special handling)
89
'discnumber': 'TPOS', # Disc number
90
'discsubtitle': 'TSST', # Set subtitle
91
'encodedby': 'TENC', # Encoded by
92
'genre': 'TCON', # Genre (special handling)
93
'grouping': 'TIT1', # Content group description
94
'isrc': 'TSRC', # ISRC code
95
'language': 'TLAN', # Language
96
'length': 'TLEN', # Length in milliseconds
97
'lyricist': 'TEXT', # Lyricist
98
'media': 'TMED', # Media type
99
'mood': 'TMOO', # Mood
100
'organization': 'TPUB', # Publisher
101
'originaldate': 'TDOR', # Original release date (special handling)
102
'title': 'TIT2', # Title
103
'titlesort': 'TSOT', # Title sort order
104
'tracknumber': 'TRCK', # Track number
105
'version': 'TIT3', # Subtitle/description refinement
106
}
107
108
# Usage examples
109
from mutagen.easyid3 import EasyID3
110
111
# Load MP3 with easy interface
112
easy_tags = EasyID3("song.mp3")
113
114
# Simple tag access with normalized names
115
easy_tags["title"] = ["Song Title"]
116
easy_tags["artist"] = ["Artist Name"]
117
easy_tags["album"] = ["Album Name"]
118
easy_tags["date"] = ["2023"]
119
easy_tags["tracknumber"] = ["1/12"] # Track 1 of 12
120
easy_tags["discnumber"] = ["1/2"] # Disc 1 of 2
121
122
# Multi-value fields
123
easy_tags["genre"] = ["Rock", "Alternative"]
124
easy_tags["artist"] = ["Primary Artist", "Featured Artist"]
125
126
# Additional metadata
127
easy_tags["albumartist"] = ["Album Artist"]
128
easy_tags["composer"] = ["Composer Name"]
129
easy_tags["bpm"] = ["120"]
130
easy_tags["comment"] = ["This is a comment"]
131
132
# Save changes
133
easy_tags.save()
134
135
# Read tags
136
print(f"Title: {easy_tags['title'][0]}")
137
print(f"Artists: {', '.join(easy_tags['artist'])}")
138
139
# List available keys
140
print("Available keys:", list(EasyID3.valid_keys.keys()))
141
142
# Check if key exists
143
if "title" in easy_tags:
144
print(f"Title: {easy_tags['title'][0]}")
145
```
146
147
### EasyMP3 Class
148
149
```python { .api }
150
class EasyMP3:
151
"""MP3 file with EasyID3 interface built-in.
152
153
Combines MP3 format support with EasyID3 tag interface.
154
Acts like MP3 class but uses easy tag names.
155
"""
156
157
def __init__(self, filename: str) -> None:
158
"""Load MP3 file with easy tag interface."""
159
160
# Usage examples
161
from mutagen.mp3 import EasyMP3
162
163
# Load MP3 with built-in easy interface
164
mp3_file = EasyMP3("song.mp3")
165
166
# Access stream info like regular MP3
167
print(f"Duration: {mp3_file.info.length} seconds")
168
print(f"Bitrate: {mp3_file.info.bitrate} bps")
169
170
# Use easy tag names
171
mp3_file["title"] = ["Song Title"]
172
mp3_file["artist"] = ["Artist Name"]
173
mp3_file.save()
174
175
# Auto-detection with easy interface
176
import mutagen
177
easy_mp3 = mutagen.File("song.mp3", easy=True) # Returns EasyMP3
178
```
179
180
## EasyMP4 Interface
181
182
### EasyMP4 Class
183
184
```python { .api }
185
class EasyMP4:
186
"""Easy dictionary-like interface for MP4 metadata.
187
188
Provides simplified tag names instead of iTunes atom names.
189
Automatically handles data type conversion and encoding.
190
"""
191
192
def __init__(self, filename: str) -> None:
193
"""Create EasyMP4 instance.
194
195
Args:
196
filename: Path to MP4 file (.m4a, .m4b, .m4p, etc.)
197
"""
198
199
class EasyMP4Tags:
200
"""Easy MP4 tags container with normalized field names."""
201
202
# Standard easy field mappings for MP4
203
EASY_MP4_FIELDS = {
204
# Basic metadata
205
'title': '©nam', # Title
206
'artist': '©ART', # Artist
207
'album': '©alb', # Album
208
'albumartist': 'aART', # Album artist
209
'date': '©day', # Date
210
211
# Track/disc information
212
'tracknumber': 'trkn', # Track number (special handling)
213
'discnumber': 'disk', # Disc number (special handling)
214
215
# Classification
216
'genre': '©gen', # Genre
217
'grouping': '©grp', # Grouping
218
219
# Credits
220
'composer': '©wrt', # Writer/Composer
221
'comment': '©cmt', # Comment
222
'lyrics': '©lyr', # Lyrics
223
224
# Technical
225
'encodedby': '©too', # Encoded by
226
'copyright': '©cpy', # Copyright
227
'bpm': 'tmpo', # Beats per minute (special handling)
228
229
# Sorting (iTunes)
230
'titlesort': 'sonm', # Title sort order
231
'artistsort': 'soar', # Artist sort order
232
'albumsort': 'soal', # Album sort order
233
'albumartistsort': 'soaa', # Album artist sort order
234
}
235
236
# Usage examples
237
from mutagen.easymp4 import EasyMP4
238
239
# Load MP4 with easy interface
240
easy_mp4 = EasyMP4("song.m4a")
241
242
# Simple tag access
243
easy_mp4["title"] = ["Song Title"]
244
easy_mp4["artist"] = ["Artist Name"]
245
easy_mp4["album"] = ["Album Name"]
246
easy_mp4["date"] = ["2023"]
247
248
# Track/disc numbers (simplified)
249
easy_mp4["tracknumber"] = ["1/12"] # Automatically converts to tuple
250
easy_mp4["discnumber"] = ["1/2"] # Automatically converts to tuple
251
252
# Numeric fields
253
easy_mp4["bpm"] = ["120"] # Automatically converts to integer
254
255
# Multi-value support
256
easy_mp4["genre"] = ["Rock", "Alternative"]
257
258
# Sorting fields for iTunes
259
easy_mp4["titlesort"] = ["Title, The"] # Sort ignoring "The"
260
easy_mp4["artistsort"] = ["Smith, John"] # Last, First format
261
262
easy_mp4.save()
263
264
# Read tags
265
print(f"Title: {easy_mp4['title'][0]}")
266
print(f"Track: {easy_mp4['tracknumber'][0]}") # Returns "1/12" format
267
```
268
269
## EasyTrueAudio Interface
270
271
```python { .api }
272
class EasyTrueAudio:
273
"""TrueAudio file with EasyID3 interface.
274
275
Since TrueAudio uses ID3v2 tags, it can use the same easy
276
interface as MP3 files.
277
"""
278
279
# Usage examples
280
from mutagen.trueaudio import EasyTrueAudio
281
282
# Load TrueAudio with easy interface
283
tta_file = EasyTrueAudio("song.tta")
284
285
# Same easy field names as EasyID3
286
tta_file["title"] = ["Song Title"]
287
tta_file["artist"] = ["Artist Name"]
288
tta_file.save()
289
```
290
291
## Cross-Format Easy Usage
292
293
### Unified Tag Access
294
295
```python
296
def get_easy_tags(filename):
297
"""Get tags using easy interface regardless of format."""
298
import mutagen
299
from mutagen.easyid3 import EasyID3
300
from mutagen.easymp4 import EasyMP4
301
from mutagen.trueaudio import EasyTrueAudio
302
303
# Try auto-detection with easy interface
304
audio_file = mutagen.File(filename, easy=True)
305
306
if audio_file:
307
return audio_file
308
309
# Fallback to format-specific easy interfaces
310
try:
311
if filename.lower().endswith('.mp3'):
312
return EasyID3(filename)
313
elif filename.lower().endswith(('.m4a', '.m4b', '.m4p', '.mp4')):
314
return EasyMP4(filename)
315
elif filename.lower().endswith('.tta'):
316
return EasyTrueAudio(filename)
317
except:
318
pass
319
320
return None
321
322
def set_basic_tags(filename, metadata):
323
"""Set basic tags using easy interface."""
324
easy_file = get_easy_tags(filename)
325
326
if not easy_file:
327
print(f"No easy interface available for {filename}")
328
return False
329
330
# Standard fields that work across easy interfaces
331
field_mapping = {
332
'title': 'title',
333
'artist': 'artist',
334
'album': 'album',
335
'date': 'date',
336
'genre': 'genre',
337
'tracknumber': 'tracknumber',
338
'albumartist': 'albumartist'
339
}
340
341
for source_key, easy_key in field_mapping.items():
342
if source_key in metadata and easy_key in easy_file.valid_keys:
343
value = metadata[source_key]
344
easy_file[easy_key] = [str(value)] if not isinstance(value, list) else value
345
346
easy_file.save()
347
return True
348
349
# Usage
350
metadata = {
351
'title': 'Song Title',
352
'artist': 'Artist Name',
353
'album': 'Album Name',
354
'date': '2023',
355
'genre': 'Rock',
356
'tracknumber': '1'
357
}
358
359
# Works with MP3, MP4, TrueAudio
360
set_basic_tags("song.mp3", metadata)
361
set_basic_tags("song.m4a", metadata)
362
set_basic_tags("song.tta", metadata)
363
```
364
365
### Tag Migration Between Formats
366
367
```python
368
def migrate_tags_easy(source_file, target_file):
369
"""Migrate tags between formats using easy interfaces."""
370
371
source_easy = get_easy_tags(source_file)
372
target_easy = get_easy_tags(target_file)
373
374
if not source_easy or not target_easy:
375
return False
376
377
# Get intersection of supported fields
378
source_keys = set(source_easy.valid_keys.keys())
379
target_keys = set(target_easy.valid_keys.keys())
380
common_keys = source_keys & target_keys
381
382
# Copy common fields
383
for key in common_keys:
384
if key in source_easy:
385
target_easy[key] = source_easy[key]
386
387
target_easy.save()
388
print(f"Migrated {len(common_keys)} tag fields")
389
return True
390
391
# Example: Copy tags from MP3 to M4A
392
migrate_tags_easy("song.mp3", "song.m4a")
393
```
394
395
## Advanced Easy Interface Usage
396
397
### Custom Field Mappings
398
399
```python
400
# Extend EasyID3 with custom mappings
401
from mutagen.easyid3 import EasyID3
402
403
def add_custom_easy_mapping(easy_key, frame_id, getter=None, setter=None):
404
"""Add custom field mapping to EasyID3."""
405
406
if getter is None:
407
def getter(id3, key):
408
return [frame.text[0] for frame in id3.getall(frame_id)]
409
410
if setter is None:
411
def setter(id3, key, value):
412
from mutagen.id3 import TextFrame
413
frame_class = getattr(mutagen.id3, frame_id)
414
id3[frame_id] = frame_class(encoding=3, text=value)
415
416
EasyID3.RegisterKey(easy_key, getter, setter)
417
418
# Add custom field for mood
419
def mood_get(id3, key):
420
"""Get mood from TMOO frame."""
421
frames = id3.getall('TMOO')
422
return [frame.text[0] for frame in frames] if frames else []
423
424
def mood_set(id3, key, value):
425
"""Set mood in TMOO frame."""
426
from mutagen.id3 import TMOO
427
id3['TMOO'] = TMOO(encoding=3, text=value)
428
429
add_custom_easy_mapping('mood', 'TMOO', mood_get, mood_set)
430
431
# Now use custom field
432
easy_tags = EasyID3("song.mp3")
433
easy_tags['mood'] = ['Happy']
434
easy_tags.save()
435
```
436
437
### Validation and Normalization
438
439
```python
440
def validate_easy_tags(easy_file, required_fields=None):
441
"""Validate easy tags and normalize values."""
442
443
if required_fields is None:
444
required_fields = ['title', 'artist', 'album']
445
446
issues = []
447
448
# Check required fields
449
for field in required_fields:
450
if field not in easy_file or not easy_file[field]:
451
issues.append(f"Missing required field: {field}")
452
453
# Normalize track numbers
454
if 'tracknumber' in easy_file:
455
track_values = easy_file['tracknumber']
456
normalized_tracks = []
457
458
for track in track_values:
459
# Ensure track number format
460
if '/' not in track:
461
normalized_tracks.append(track)
462
else:
463
# Validate track/total format
464
try:
465
current, total = track.split('/', 1)
466
int(current) # Validate numeric
467
int(total) # Validate numeric
468
normalized_tracks.append(track)
469
except ValueError:
470
issues.append(f"Invalid track number format: {track}")
471
472
if normalized_tracks != track_values:
473
easy_file['tracknumber'] = normalized_tracks
474
475
# Normalize dates to YYYY format
476
if 'date' in easy_file:
477
date_values = easy_file['date']
478
normalized_dates = []
479
480
for date in date_values:
481
# Extract year from various date formats
482
import re
483
year_match = re.search(r'(\d{4})', date)
484
if year_match:
485
normalized_dates.append(year_match.group(1))
486
else:
487
issues.append(f"Invalid date format: {date}")
488
489
if normalized_dates != date_values:
490
easy_file['date'] = normalized_dates
491
492
return issues
493
494
# Usage
495
easy_tags = EasyID3("song.mp3")
496
issues = validate_easy_tags(easy_tags)
497
498
if issues:
499
print("Tag validation issues:")
500
for issue in issues:
501
print(f" - {issue}")
502
else:
503
print("All tags valid")
504
easy_tags.save()
505
```
506
507
### Batch Easy Processing
508
509
```python
510
import os
511
import mutagen
512
513
def batch_easy_tag(directory, tag_updates):
514
"""Batch update tags using easy interfaces."""
515
516
results = {'success': [], 'failed': [], 'skipped': []}
517
518
for filename in os.listdir(directory):
519
filepath = os.path.join(directory, filename)
520
521
if not os.path.isfile(filepath):
522
continue
523
524
try:
525
# Try easy interface first
526
audio_file = mutagen.File(filepath, easy=True)
527
528
if audio_file and hasattr(audio_file, 'valid_keys'):
529
# Apply updates
530
for key, value in tag_updates.items():
531
if key in audio_file.valid_keys:
532
audio_file[key] = [value] if not isinstance(value, list) else value
533
534
audio_file.save()
535
results['success'].append(filename)
536
537
else:
538
results['skipped'].append(f"{filename} (no easy interface)")
539
540
except Exception as e:
541
results['failed'].append(f"{filename} ({str(e)})")
542
543
return results
544
545
# Usage
546
tag_updates = {
547
'albumartist': 'Various Artists',
548
'date': '2023',
549
'genre': 'Compilation'
550
}
551
552
results = batch_easy_tag("/path/to/music", tag_updates)
553
554
print(f"Successfully updated: {len(results['success'])} files")
555
print(f"Failed: {len(results['failed'])} files")
556
print(f"Skipped: {len(results['skipped'])} files")
557
```
558
559
## Available Easy Fields
560
561
### Common Fields (Most Formats)
562
563
```python
564
COMMON_EASY_FIELDS = [
565
'title', # Track title
566
'artist', # Primary artist
567
'album', # Album name
568
'albumartist', # Album artist
569
'date', # Release date
570
'genre', # Musical genre
571
'tracknumber', # Track number (format: "1" or "1/12")
572
'discnumber', # Disc number (format: "1" or "1/2")
573
]
574
```
575
576
### Extended Fields (Format Dependent)
577
578
```python
579
EXTENDED_EASY_FIELDS = [
580
'composer', # Composer name
581
'conductor', # Conductor name
582
'lyricist', # Lyricist name
583
'performer', # Performer credits
584
'grouping', # Content grouping
585
'comment', # Comment text
586
'lyrics', # Song lyrics
587
'bpm', # Beats per minute
588
'encodedby', # Encoded by
589
'copyright', # Copyright notice
590
'website', # Official website
591
'isrc', # International Standard Recording Code
592
'language', # Language code
593
'originaldate', # Original release date
594
'titlesort', # Title sort order
595
'artistsort', # Artist sort order
596
'albumsort', # Album sort order
597
]
598
```
599
600
## See Also
601
602
- [Mutagen](./index.md) - Main documentation and overview
603
- [MP3 and ID3 Tags](./mp3-id3.md) - Native ID3 tag access
604
- [Container Formats](./container-formats.md) - Native MP4 metadata access
605
- [Core Functionality](./core-functionality.md) - Base classes and format detection