0
# Container Formats
1
2
This document covers Mutagen's support for audio container formats including MP4/M4A (iTunes), ASF/WMA (Windows Media), and other formats that embed audio streams with rich metadata capabilities.
3
4
## Imports
5
6
```python { .api }
7
# MP4/M4A/M4B/M4P format
8
from mutagen.mp4 import MP4, MP4Tags, MP4Cover, MP4FreeForm, AtomDataType
9
from mutagen.mp4 import Open, delete
10
11
# ASF/WMA format
12
from mutagen.asf import ASF, ASFInfo, ASFTags, ASFValue
13
from mutagen.asf import Open as ASFOpen
14
15
# AAC format (Advanced Audio Coding)
16
from mutagen.aac import AAC, AACInfo
17
18
# AC-3 format (Dolby Digital)
19
from mutagen.ac3 import AC3, AC3Info
20
21
# Musepack format
22
from mutagen.musepack import Musepack, MusepackInfo
23
24
# Easy interfaces
25
from mutagen.easymp4 import EasyMP4, EasyMP4Tags
26
```
27
28
## MP4/M4A Format
29
30
### MP4 Class
31
32
```python { .api }
33
class MP4:
34
"""MPEG-4 audio container (M4A, M4B, M4P, etc.).
35
36
Supports iTunes-style metadata atoms with rich data types including
37
text, numbers, covers, and custom freeform data.
38
39
Attributes:
40
info: MP4 stream information
41
tags: MP4Tags container with iTunes-compatible metadata
42
filename: Path to the MP4 file
43
"""
44
45
def __init__(self, filename: str) -> None:
46
"""Load MP4 file and parse metadata atoms.
47
48
Args:
49
filename: Path to MP4 file (.m4a, .m4b, .m4p, .mp4, etc.)
50
51
Raises:
52
MutagenError: If file is not valid MP4 or corrupted
53
"""
54
55
def add_tags(self) -> None:
56
"""Add empty MP4 metadata if none exists."""
57
58
def save(self) -> None:
59
"""Save MP4 metadata back to file."""
60
61
# Function aliases
62
Open = MP4
63
64
def delete(filename: str) -> None:
65
"""Remove MP4 metadata while preserving audio.
66
67
Args:
68
filename: Path to MP4 file
69
"""
70
71
# Usage examples
72
from mutagen.mp4 import MP4
73
74
# Load MP4 file
75
mp4_file = MP4("song.m4a")
76
77
# Access stream info
78
print(f"Duration: {mp4_file.info.length} seconds")
79
print(f"Bitrate: {mp4_file.info.bitrate} bps")
80
81
# Access iTunes metadata
82
print(mp4_file["©nam"]) # Title
83
print(mp4_file["©ART"]) # Artist
84
print(mp4_file["©alb"]) # Album
85
86
# Modify metadata
87
mp4_file["©nam"] = ["New Title"]
88
mp4_file["©ART"] = ["New Artist"]
89
mp4_file.save()
90
```
91
92
### MP4 Tag Atoms
93
94
MP4 uses iTunes-compatible metadata atoms with specific naming conventions:
95
96
```python { .api }
97
class MP4Tags:
98
"""iTunes-style MP4 metadata container.
99
100
Uses 4-character atom names (e.g., ©nam, ©ART) for standard metadata
101
and freeform atoms for custom data.
102
"""
103
104
# Standard iTunes atoms
105
STANDARD_ATOMS = {
106
# Text metadata
107
"©nam": "Title",
108
"©ART": "Artist",
109
"©alb": "Album",
110
"©day": "Date",
111
"©gen": "Genre",
112
"©wrt": "Composer",
113
"©too": "Encoder",
114
"©cmt": "Comment",
115
"©lyr": "Lyrics",
116
"aART": "Album Artist",
117
"©grp": "Grouping",
118
119
# Numeric metadata
120
"trkn": "Track Number (current/total)",
121
"disk": "Disc Number (current/total)",
122
"tmpo": "BPM",
123
"©day": "Year",
124
125
# Flags/ratings
126
"rtng": "Rating",
127
"pcst": "Podcast flag",
128
"cpil": "Compilation flag",
129
"hdvd": "HD Video flag",
130
131
# Cover art
132
"covr": "Cover Art",
133
134
# Sorting
135
"sonm": "Sort Title",
136
"soar": "Sort Artist",
137
"soal": "Sort Album",
138
"soaa": "Sort Album Artist",
139
140
# Purchase info
141
"apID": "Purchase Account",
142
"cnID": "Catalog ID",
143
}
144
145
# Usage examples
146
mp4_file = MP4("song.m4a")
147
148
# Text metadata
149
mp4_file["©nam"] = ["Song Title"]
150
mp4_file["©ART"] = ["Artist Name"]
151
mp4_file["©alb"] = ["Album Name"]
152
mp4_file["©day"] = ["2023"]
153
mp4_file["©gen"] = ["Rock"]
154
155
# Track/disc numbers (as tuples)
156
mp4_file["trkn"] = [(1, 12)] # Track 1 of 12
157
mp4_file["disk"] = [(1, 2)] # Disc 1 of 2
158
159
# BPM (as list of integers)
160
mp4_file["tmpo"] = [120]
161
162
# Boolean flags
163
mp4_file["cpil"] = [True] # Compilation
164
mp4_file["pcst"] = [True] # Podcast
165
166
mp4_file.save()
167
```
168
169
### Cover Art
170
171
```python { .api }
172
class MP4Cover:
173
"""MP4 cover art container.
174
175
Attributes:
176
data: Image data as bytes
177
imageformat: Format constant (FORMAT_JPEG or FORMAT_PNG)
178
"""
179
180
FORMAT_JPEG = 0x0D
181
FORMAT_PNG = 0x0E
182
183
def __init__(self, data: bytes, imageformat: int = None) -> None:
184
"""Create MP4 cover art.
185
186
Args:
187
data: Image data bytes
188
imageformat: Image format (auto-detected if None)
189
"""
190
191
# Usage examples
192
from mutagen.mp4 import MP4, MP4Cover
193
194
mp4_file = MP4("song.m4a")
195
196
# Add cover art
197
with open("cover.jpg", "rb") as f:
198
cover_data = f.read()
199
200
# Create cover art object
201
cover = MP4Cover(cover_data, MP4Cover.FORMAT_JPEG)
202
203
# Add to file (supports multiple covers)
204
mp4_file["covr"] = [cover]
205
206
# Multiple covers
207
with open("back.png", "rb") as f:
208
back_data = f.read()
209
210
back_cover = MP4Cover(back_data, MP4Cover.FORMAT_PNG)
211
mp4_file["covr"] = [cover, back_cover]
212
213
mp4_file.save()
214
215
# Access existing covers
216
covers = mp4_file.get("covr", [])
217
for i, cover in enumerate(covers):
218
with open(f"extracted_cover_{i}.jpg", "wb") as f:
219
f.write(cover)
220
```
221
222
### Freeform Metadata
223
224
```python { .api }
225
class MP4FreeForm:
226
"""Freeform MP4 metadata atom.
227
228
Allows custom metadata with arbitrary names and data types.
229
Uses reverse DNS naming convention.
230
231
Attributes:
232
data: Raw bytes data
233
dataformat: AtomDataType enum value
234
"""
235
236
class AtomDataType:
237
"""MP4 atom data type enumeration."""
238
IMPLICIT = 0
239
UTF8 = 1
240
UTF16 = 2
241
SJIS = 3
242
HTML = 6
243
XML = 7
244
UUID = 8
245
ISRC = 9
246
MI3P = 10
247
GIF = 12
248
JPEG = 13
249
PNG = 14
250
URL = 15
251
DURATION = 16
252
DATETIME = 17
253
GENRES = 18
254
INTEGER = 21
255
RIAA_PA = 24
256
257
# Usage examples
258
from mutagen.mp4 import MP4, MP4FreeForm, AtomDataType
259
260
mp4_file = MP4("song.m4a")
261
262
# Custom text metadata
263
custom_text = MP4FreeForm(b"Custom Value", AtomDataType.UTF8)
264
mp4_file["----:com.apple.iTunes:CUSTOM_FIELD"] = [custom_text]
265
266
# Custom binary data
267
binary_data = MP4FreeForm(b"\x00\x01\x02\x03", AtomDataType.IMPLICIT)
268
mp4_file["----:com.apple.iTunes:BINARY_DATA"] = [binary_data]
269
270
# Custom integers
271
integer_data = MP4FreeForm(b"\x00\x00\x00\x7B", AtomDataType.INTEGER) # 123
272
mp4_file["----:com.apple.iTunes:CUSTOM_NUMBER"] = [integer_data]
273
274
mp4_file.save()
275
276
# Read freeform data
277
custom_field = mp4_file.get("----:com.apple.iTunes:CUSTOM_FIELD")
278
if custom_field:
279
print(f"Custom field: {custom_field[0].decode('utf-8')}")
280
```
281
282
## ASF/WMA Format
283
284
### ASF Class
285
286
```python { .api }
287
class ASF:
288
"""Advanced Systems Format / Windows Media Audio file.
289
290
Microsoft's container format supporting WMA audio and rich metadata
291
through ASF attribute objects.
292
293
Attributes:
294
info: ASFInfo with stream information
295
tags: ASFTags container with metadata attributes
296
filename: Path to ASF file
297
"""
298
299
def __init__(self, filename: str) -> None:
300
"""Load ASF file and parse metadata.
301
302
Args:
303
filename: Path to ASF file (.wma, .wmv, .asf)
304
305
Raises:
306
MutagenError: If file is not valid ASF or corrupted
307
"""
308
309
def add_tags(self) -> None:
310
"""Add empty ASF metadata if none exists."""
311
312
def save(self) -> None:
313
"""Save ASF metadata back to file."""
314
315
class ASFInfo:
316
"""ASF stream information.
317
318
Attributes:
319
length: Duration in seconds
320
bitrate: Bitrate in bits per second
321
sample_rate: Audio sample rate in Hz (if audio stream)
322
channels: Number of audio channels (if audio stream)
323
codec_name: Codec name string
324
codec_description: Detailed codec description
325
"""
326
327
# Function alias
328
Open = ASF
329
330
# Usage examples
331
from mutagen.asf import ASF
332
333
# Load ASF file
334
asf_file = ASF("song.wma")
335
336
# Access stream info
337
info = asf_file.info
338
print(f"Codec: {info.codec_name}")
339
print(f"Duration: {info.length} seconds")
340
341
# Access metadata
342
print(asf_file["Title"])
343
print(asf_file["Author"]) # Artist
344
print(asf_file["AlbumTitle"])
345
346
# Modify metadata
347
asf_file["Title"] = ["New Title"]
348
asf_file["Author"] = ["New Artist"]
349
asf_file.save()
350
```
351
352
### ASF Metadata
353
354
```python { .api }
355
class ASFTags:
356
"""ASF metadata container.
357
358
Dictionary-like interface for ASF attributes. Supports multiple
359
data types including strings, integers, booleans, and binary data.
360
"""
361
362
class ASFValue:
363
"""ASF attribute value wrapper.
364
365
Handles type conversion and encoding for ASF metadata values.
366
367
Attributes:
368
value: The actual value (various types)
369
type: ASF data type constant
370
"""
371
372
# Standard ASF attributes
373
STANDARD_ATTRIBUTES = {
374
"Title": "Title",
375
"Author": "Artist/Author",
376
"AlbumTitle": "Album",
377
"AlbumArtist": "Album Artist",
378
"Genre": "Genre",
379
"Year": "Year",
380
"Track": "Track Number",
381
"TrackNumber": "Track Number",
382
"PartOfSet": "Disc Number",
383
"Copyright": "Copyright",
384
"Description": "Comment/Description",
385
"Rating": "Rating",
386
"IsVBR": "Variable Bitrate Flag",
387
}
388
389
# Usage examples
390
from mutagen.asf import ASF, ASFValue
391
392
asf_file = ASF("song.wma")
393
394
# Text metadata
395
asf_file["Title"] = ["Song Title"]
396
asf_file["Author"] = ["Artist Name"]
397
asf_file["AlbumTitle"] = ["Album Name"]
398
asf_file["Genre"] = ["Rock"]
399
400
# Numeric metadata
401
asf_file["Track"] = [1]
402
asf_file["Year"] = [2023]
403
404
# Boolean flags
405
asf_file["IsVBR"] = [True]
406
407
# Custom attributes
408
asf_file["CustomField"] = ["Custom Value"]
409
410
asf_file.save()
411
412
# Access with type information
413
title_attr = asf_file.get("Title")
414
if title_attr:
415
for value in title_attr:
416
print(f"Title: {value} (type: {type(value)})")
417
```
418
419
## Other Compressed Formats
420
421
### AAC (Advanced Audio Coding)
422
423
```python { .api }
424
class AAC:
425
"""Advanced Audio Coding file.
426
427
Raw AAC stream without container. Limited metadata support.
428
429
Attributes:
430
info: AACInfo with stream details
431
filename: Path to AAC file
432
"""
433
434
class AACInfo:
435
"""AAC stream information.
436
437
Attributes:
438
length: Duration in seconds
439
bitrate: Bitrate in bits per second
440
sample_rate: Sample rate in Hz
441
channels: Number of channels
442
"""
443
444
# Usage examples
445
from mutagen.aac import AAC
446
447
aac_file = AAC("song.aac")
448
print(f"Sample rate: {aac_file.info.sample_rate}")
449
print(f"Channels: {aac_file.info.channels}")
450
```
451
452
### AC-3 (Dolby Digital)
453
454
```python { .api }
455
class AC3:
456
"""Dolby Digital AC-3 audio file.
457
458
Surround sound audio format with limited metadata.
459
460
Attributes:
461
info: AC3Info with stream details
462
filename: Path to AC-3 file
463
"""
464
465
class AC3Info:
466
"""AC-3 stream information.
467
468
Attributes:
469
length: Duration in seconds
470
bitrate: Bitrate in bits per second
471
sample_rate: Sample rate in Hz
472
channels: Number of channels
473
frame_size: Frame size in bytes
474
"""
475
476
# Usage examples
477
from mutagen.ac3 import AC3
478
479
ac3_file = AC3("audio.ac3")
480
print(f"Channels: {ac3_file.info.channels}")
481
print(f"Bitrate: {ac3_file.info.bitrate}")
482
```
483
484
### Musepack
485
486
```python { .api }
487
class Musepack:
488
"""Musepack audio file.
489
490
High-quality lossy compression with APEv2 tags.
491
492
Attributes:
493
info: MusepackInfo with stream details
494
tags: APEv2 tag container
495
filename: Path to Musepack file
496
"""
497
498
class MusepackInfo:
499
"""Musepack stream information.
500
501
Attributes:
502
length: Duration in seconds
503
bitrate: Bitrate in bits per second
504
sample_rate: Sample rate in Hz
505
channels: Number of channels
506
version: Musepack version
507
profile: Quality profile name
508
"""
509
510
# Usage examples
511
from mutagen.musepack import Musepack
512
513
mpc_file = Musepack("song.mpc")
514
print(f"Profile: {mpc_file.info.profile}")
515
516
# APEv2 tags
517
mpc_file["Title"] = "Song Title"
518
mpc_file["Artist"] = "Artist Name"
519
mpc_file.save()
520
```
521
522
## Easy MP4 Interface
523
524
```python { .api }
525
class EasyMP4(MP4):
526
"""MP4 with easy dictionary-like interface.
527
528
Provides simplified tag names instead of iTunes atom names.
529
"""
530
531
class EasyMP4Tags:
532
"""Easy MP4 tags container with normalized field names.
533
534
Maps simple tag names to iTunes atoms:
535
- title -> ©nam
536
- artist -> ©ART
537
- album -> ©alb
538
- date -> ©day
539
- etc.
540
"""
541
542
# Usage examples
543
from mutagen.easymp4 import EasyMP4
544
545
# Load with easy interface
546
easy_mp4 = EasyMP4("song.m4a")
547
548
# Simple tag names
549
easy_mp4["title"] = ["Song Title"]
550
easy_mp4["artist"] = ["Artist Name"]
551
easy_mp4["album"] = ["Album Name"]
552
easy_mp4["date"] = ["2023"]
553
easy_mp4["tracknumber"] = ["1/12"]
554
easy_mp4["discnumber"] = ["1/2"]
555
556
# Multi-value support
557
easy_mp4["genre"] = ["Rock", "Alternative"]
558
559
easy_mp4.save()
560
561
# Available keys
562
print("Available keys:", list(EasyMP4.valid_keys.keys()))
563
```
564
565
## Practical Examples
566
567
### Complete MP4 Tagging
568
569
```python
570
from mutagen.mp4 import MP4, MP4Cover
571
572
def setup_mp4_complete(filename, metadata, cover_path=None):
573
"""Set up MP4 file with complete iTunes metadata."""
574
mp4_file = MP4(filename)
575
576
# Basic text metadata
577
mp4_file["©nam"] = [metadata["title"]]
578
mp4_file["©ART"] = [metadata["artist"]]
579
mp4_file["©alb"] = [metadata["album"]]
580
mp4_file["©day"] = [str(metadata["year"])]
581
582
# Track/disc numbers
583
if "track" in metadata:
584
track_total = metadata.get("track_total")
585
if track_total:
586
mp4_file["trkn"] = [(metadata["track"], track_total)]
587
else:
588
mp4_file["trkn"] = [(metadata["track"], 0)]
589
590
if "disc" in metadata:
591
disc_total = metadata.get("disc_total", 0)
592
mp4_file["disk"] = [(metadata["disc"], disc_total)]
593
594
# Optional fields
595
if "albumartist" in metadata:
596
mp4_file["aART"] = [metadata["albumartist"]]
597
if "genre" in metadata:
598
mp4_file["©gen"] = [metadata["genre"]]
599
if "composer" in metadata:
600
mp4_file["©wrt"] = [metadata["composer"]]
601
if "bpm" in metadata:
602
mp4_file["tmpo"] = [metadata["bpm"]]
603
604
# Flags
605
if metadata.get("compilation"):
606
mp4_file["cpil"] = [True]
607
if metadata.get("podcast"):
608
mp4_file["pcst"] = [True]
609
610
# Cover art
611
if cover_path:
612
with open(cover_path, "rb") as f:
613
cover_data = f.read()
614
615
# Detect format
616
if cover_path.lower().endswith('.png'):
617
format_type = MP4Cover.FORMAT_PNG
618
else:
619
format_type = MP4Cover.FORMAT_JPEG
620
621
cover = MP4Cover(cover_data, format_type)
622
mp4_file["covr"] = [cover]
623
624
# Custom metadata
625
if "custom_fields" in metadata:
626
for key, value in metadata["custom_fields"].items():
627
atom_name = f"----:com.apple.iTunes:{key}"
628
mp4_file[atom_name] = [MP4FreeForm(value.encode('utf-8'),
629
AtomDataType.UTF8)]
630
631
mp4_file.save()
632
print(f"MP4 file tagged: {filename}")
633
634
# Usage
635
metadata = {
636
"title": "Song Title",
637
"artist": "Artist Name",
638
"album": "Album Name",
639
"year": 2023,
640
"track": 1,
641
"track_total": 12,
642
"disc": 1,
643
"albumartist": "Album Artist",
644
"genre": "Rock",
645
"bpm": 120,
646
"compilation": False,
647
"custom_fields": {
648
"ENCODER": "Custom Encoder",
649
"MOOD": "Happy"
650
}
651
}
652
653
setup_mp4_complete("song.m4a", metadata, "cover.jpg")
654
```
655
656
### Cross-Container Format Conversion
657
658
```python
659
def convert_metadata(source_file, target_file):
660
"""Convert metadata between container formats."""
661
import mutagen
662
663
source = mutagen.File(source_file)
664
target = mutagen.File(target_file)
665
666
if not source or not target:
667
return False
668
669
source_format = source.__class__.__name__
670
target_format = target.__class__.__name__
671
672
# Common metadata mapping
673
metadata_map = {}
674
675
if source_format == "MP4":
676
metadata_map = {
677
"title": source.get("©nam", [""])[0],
678
"artist": source.get("©ART", [""])[0],
679
"album": source.get("©alb", [""])[0],
680
"date": source.get("©day", [""])[0]
681
}
682
elif source_format == "ASF":
683
metadata_map = {
684
"title": source.get("Title", [""])[0],
685
"artist": source.get("Author", [""])[0],
686
"album": source.get("AlbumTitle", [""])[0],
687
"date": str(source.get("Year", [0])[0])
688
}
689
690
# Apply to target
691
if target_format == "MP4":
692
target["©nam"] = [metadata_map["title"]]
693
target["©ART"] = [metadata_map["artist"]]
694
target["©alb"] = [metadata_map["album"]]
695
target["©day"] = [metadata_map["date"]]
696
elif target_format == "ASF":
697
target["Title"] = [metadata_map["title"]]
698
target["Author"] = [metadata_map["artist"]]
699
target["AlbumTitle"] = [metadata_map["album"]]
700
try:
701
target["Year"] = [int(metadata_map["date"])]
702
except ValueError:
703
pass
704
705
target.save()
706
return True
707
708
# Usage
709
convert_metadata("song.m4a", "song.wma")
710
```
711
712
## See Also
713
714
- [Mutagen](./index.md) - Main documentation and overview
715
- [Easy Interfaces](./easy-interfaces.md) - Simplified cross-format tag access
716
- [MP3 and ID3 Tags](./mp3-id3.md) - MP3 format and ID3 tagging
717
- [Lossless Audio Formats](./lossless-formats.md) - FLAC and other lossless formats