CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/pypi-mutagen

Python library for handling audio metadata across multiple formats including MP3, FLAC, MP4, OGG and many others

Pending
Overview
Eval results
Files

container-formats.mddocs/

Container Formats

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.

Imports

# MP4/M4A/M4B/M4P format
from mutagen.mp4 import MP4, MP4Tags, MP4Cover, MP4FreeForm, AtomDataType
from mutagen.mp4 import Open, delete

# ASF/WMA format  
from mutagen.asf import ASF, ASFInfo, ASFTags, ASFValue
from mutagen.asf import Open as ASFOpen

# AAC format (Advanced Audio Coding)
from mutagen.aac import AAC, AACInfo

# AC-3 format (Dolby Digital)
from mutagen.ac3 import AC3, AC3Info

# Musepack format
from mutagen.musepack import Musepack, MusepackInfo

# Easy interfaces
from mutagen.easymp4 import EasyMP4, EasyMP4Tags

MP4/M4A Format

MP4 Class

class MP4:
    """MPEG-4 audio container (M4A, M4B, M4P, etc.).
    
    Supports iTunes-style metadata atoms with rich data types including
    text, numbers, covers, and custom freeform data.
    
    Attributes:
        info: MP4 stream information
        tags: MP4Tags container with iTunes-compatible metadata
        filename: Path to the MP4 file
    """
    
    def __init__(self, filename: str) -> None:
        """Load MP4 file and parse metadata atoms.
        
        Args:
            filename: Path to MP4 file (.m4a, .m4b, .m4p, .mp4, etc.)
            
        Raises:
            MutagenError: If file is not valid MP4 or corrupted
        """
    
    def add_tags(self) -> None:
        """Add empty MP4 metadata if none exists."""
    
    def save(self) -> None:
        """Save MP4 metadata back to file."""

# Function aliases
Open = MP4

def delete(filename: str) -> None:
    """Remove MP4 metadata while preserving audio.
    
    Args:
        filename: Path to MP4 file
    """

# Usage examples
from mutagen.mp4 import MP4

# Load MP4 file
mp4_file = MP4("song.m4a")

# Access stream info
print(f"Duration: {mp4_file.info.length} seconds") 
print(f"Bitrate: {mp4_file.info.bitrate} bps")

# Access iTunes metadata
print(mp4_file["©nam"])  # Title
print(mp4_file["©ART"])  # Artist
print(mp4_file["©alb"])  # Album

# Modify metadata
mp4_file["©nam"] = ["New Title"]
mp4_file["©ART"] = ["New Artist"]
mp4_file.save()

MP4 Tag Atoms

MP4 uses iTunes-compatible metadata atoms with specific naming conventions:

class MP4Tags:
    """iTunes-style MP4 metadata container.
    
    Uses 4-character atom names (e.g., ©nam, ©ART) for standard metadata
    and freeform atoms for custom data.
    """

# Standard iTunes atoms
STANDARD_ATOMS = {
    # Text metadata
    "©nam": "Title",
    "©ART": "Artist", 
    "©alb": "Album",
    "©day": "Date",
    "©gen": "Genre",
    "©wrt": "Composer",
    "©too": "Encoder",
    "©cmt": "Comment",
    "©lyr": "Lyrics",
    "aART": "Album Artist",
    "©grp": "Grouping",
    
    # Numeric metadata
    "trkn": "Track Number (current/total)",
    "disk": "Disc Number (current/total)", 
    "tmpo": "BPM",
    "©day": "Year",
    
    # Flags/ratings
    "rtng": "Rating",
    "pcst": "Podcast flag",
    "cpil": "Compilation flag",
    "hdvd": "HD Video flag",
    
    # Cover art
    "covr": "Cover Art",
    
    # Sorting
    "sonm": "Sort Title",
    "soar": "Sort Artist",
    "soal": "Sort Album",
    "soaa": "Sort Album Artist",
    
    # Purchase info
    "apID": "Purchase Account",
    "cnID": "Catalog ID",
}

# Usage examples
mp4_file = MP4("song.m4a")

# Text metadata
mp4_file["©nam"] = ["Song Title"]
mp4_file["©ART"] = ["Artist Name"]
mp4_file["©alb"] = ["Album Name"] 
mp4_file["©day"] = ["2023"]
mp4_file["©gen"] = ["Rock"]

# Track/disc numbers (as tuples)
mp4_file["trkn"] = [(1, 12)]  # Track 1 of 12
mp4_file["disk"] = [(1, 2)]   # Disc 1 of 2

# BPM (as list of integers)
mp4_file["tmpo"] = [120]

# Boolean flags
mp4_file["cpil"] = [True]  # Compilation
mp4_file["pcst"] = [True]  # Podcast

mp4_file.save()

Cover Art

class MP4Cover:
    """MP4 cover art container.
    
    Attributes:
        data: Image data as bytes
        imageformat: Format constant (FORMAT_JPEG or FORMAT_PNG)
    """
    
    FORMAT_JPEG = 0x0D
    FORMAT_PNG = 0x0E
    
    def __init__(self, data: bytes, imageformat: int = None) -> None:
        """Create MP4 cover art.
        
        Args:
            data: Image data bytes
            imageformat: Image format (auto-detected if None)
        """

# Usage examples
from mutagen.mp4 import MP4, MP4Cover

mp4_file = MP4("song.m4a")

# Add cover art
with open("cover.jpg", "rb") as f:
    cover_data = f.read()

# Create cover art object
cover = MP4Cover(cover_data, MP4Cover.FORMAT_JPEG)

# Add to file (supports multiple covers)
mp4_file["covr"] = [cover]

# Multiple covers
with open("back.png", "rb") as f:
    back_data = f.read()
    
back_cover = MP4Cover(back_data, MP4Cover.FORMAT_PNG)
mp4_file["covr"] = [cover, back_cover]

mp4_file.save()

# Access existing covers
covers = mp4_file.get("covr", [])
for i, cover in enumerate(covers):
    with open(f"extracted_cover_{i}.jpg", "wb") as f:
        f.write(cover)

Freeform Metadata

class MP4FreeForm:
    """Freeform MP4 metadata atom.
    
    Allows custom metadata with arbitrary names and data types.
    Uses reverse DNS naming convention.
    
    Attributes:
        data: Raw bytes data
        dataformat: AtomDataType enum value
    """

class AtomDataType:
    """MP4 atom data type enumeration."""
    IMPLICIT = 0
    UTF8 = 1
    UTF16 = 2
    SJIS = 3
    HTML = 6
    XML = 7
    UUID = 8
    ISRC = 9
    MI3P = 10
    GIF = 12
    JPEG = 13
    PNG = 14
    URL = 15
    DURATION = 16
    DATETIME = 17
    GENRES = 18
    INTEGER = 21
    RIAA_PA = 24

# Usage examples
from mutagen.mp4 import MP4, MP4FreeForm, AtomDataType

mp4_file = MP4("song.m4a")

# Custom text metadata
custom_text = MP4FreeForm(b"Custom Value", AtomDataType.UTF8)
mp4_file["----:com.apple.iTunes:CUSTOM_FIELD"] = [custom_text]

# Custom binary data
binary_data = MP4FreeForm(b"\x00\x01\x02\x03", AtomDataType.IMPLICIT)
mp4_file["----:com.apple.iTunes:BINARY_DATA"] = [binary_data]

# Custom integers
integer_data = MP4FreeForm(b"\x00\x00\x00\x7B", AtomDataType.INTEGER) # 123
mp4_file["----:com.apple.iTunes:CUSTOM_NUMBER"] = [integer_data]

mp4_file.save()

# Read freeform data
custom_field = mp4_file.get("----:com.apple.iTunes:CUSTOM_FIELD")
if custom_field:
    print(f"Custom field: {custom_field[0].decode('utf-8')}")

ASF/WMA Format

ASF Class

class ASF:
    """Advanced Systems Format / Windows Media Audio file.
    
    Microsoft's container format supporting WMA audio and rich metadata
    through ASF attribute objects.
    
    Attributes:
        info: ASFInfo with stream information
        tags: ASFTags container with metadata attributes
        filename: Path to ASF file
    """
    
    def __init__(self, filename: str) -> None:
        """Load ASF file and parse metadata.
        
        Args:
            filename: Path to ASF file (.wma, .wmv, .asf)
            
        Raises:
            MutagenError: If file is not valid ASF or corrupted
        """
    
    def add_tags(self) -> None:
        """Add empty ASF metadata if none exists."""
    
    def save(self) -> None:
        """Save ASF metadata back to file."""

class ASFInfo:
    """ASF stream information.
    
    Attributes:
        length: Duration in seconds
        bitrate: Bitrate in bits per second
        sample_rate: Audio sample rate in Hz (if audio stream)
        channels: Number of audio channels (if audio stream)
        codec_name: Codec name string
        codec_description: Detailed codec description
    """

# Function alias
Open = ASF

# Usage examples
from mutagen.asf import ASF

# Load ASF file
asf_file = ASF("song.wma")

# Access stream info
info = asf_file.info
print(f"Codec: {info.codec_name}")
print(f"Duration: {info.length} seconds")

# Access metadata
print(asf_file["Title"])
print(asf_file["Author"])  # Artist
print(asf_file["AlbumTitle"])

# Modify metadata
asf_file["Title"] = ["New Title"]
asf_file["Author"] = ["New Artist"]
asf_file.save()

ASF Metadata

class ASFTags:
    """ASF metadata container.
    
    Dictionary-like interface for ASF attributes. Supports multiple
    data types including strings, integers, booleans, and binary data.
    """

class ASFValue:
    """ASF attribute value wrapper.
    
    Handles type conversion and encoding for ASF metadata values.
    
    Attributes:
        value: The actual value (various types)
        type: ASF data type constant
    """

# Standard ASF attributes
STANDARD_ATTRIBUTES = {
    "Title": "Title",
    "Author": "Artist/Author", 
    "AlbumTitle": "Album",
    "AlbumArtist": "Album Artist",
    "Genre": "Genre", 
    "Year": "Year",
    "Track": "Track Number",
    "TrackNumber": "Track Number",
    "PartOfSet": "Disc Number",
    "Copyright": "Copyright",
    "Description": "Comment/Description",
    "Rating": "Rating",
    "IsVBR": "Variable Bitrate Flag",
}

# Usage examples
from mutagen.asf import ASF, ASFValue

asf_file = ASF("song.wma")

# Text metadata
asf_file["Title"] = ["Song Title"]
asf_file["Author"] = ["Artist Name"]
asf_file["AlbumTitle"] = ["Album Name"]
asf_file["Genre"] = ["Rock"]

# Numeric metadata
asf_file["Track"] = [1]
asf_file["Year"] = [2023]

# Boolean flags
asf_file["IsVBR"] = [True]

# Custom attributes
asf_file["CustomField"] = ["Custom Value"]

asf_file.save()

# Access with type information
title_attr = asf_file.get("Title")
if title_attr:
    for value in title_attr:
        print(f"Title: {value} (type: {type(value)})")

Other Compressed Formats

AAC (Advanced Audio Coding)

class AAC:
    """Advanced Audio Coding file.
    
    Raw AAC stream without container. Limited metadata support.
    
    Attributes:
        info: AACInfo with stream details
        filename: Path to AAC file
    """

class AACInfo:
    """AAC stream information.
    
    Attributes:
        length: Duration in seconds
        bitrate: Bitrate in bits per second
        sample_rate: Sample rate in Hz
        channels: Number of channels
    """

# Usage examples
from mutagen.aac import AAC

aac_file = AAC("song.aac")
print(f"Sample rate: {aac_file.info.sample_rate}")
print(f"Channels: {aac_file.info.channels}")

AC-3 (Dolby Digital)

class AC3:
    """Dolby Digital AC-3 audio file.
    
    Surround sound audio format with limited metadata.
    
    Attributes:
        info: AC3Info with stream details
        filename: Path to AC-3 file
    """

class AC3Info:
    """AC-3 stream information.
    
    Attributes:
        length: Duration in seconds
        bitrate: Bitrate in bits per second
        sample_rate: Sample rate in Hz
        channels: Number of channels
        frame_size: Frame size in bytes
    """

# Usage examples
from mutagen.ac3 import AC3

ac3_file = AC3("audio.ac3")
print(f"Channels: {ac3_file.info.channels}")
print(f"Bitrate: {ac3_file.info.bitrate}")

Musepack

class Musepack:
    """Musepack audio file.
    
    High-quality lossy compression with APEv2 tags.
    
    Attributes:
        info: MusepackInfo with stream details
        tags: APEv2 tag container
        filename: Path to Musepack file
    """

class MusepackInfo:
    """Musepack stream information.
    
    Attributes:
        length: Duration in seconds
        bitrate: Bitrate in bits per second
        sample_rate: Sample rate in Hz
        channels: Number of channels
        version: Musepack version
        profile: Quality profile name
    """

# Usage examples
from mutagen.musepack import Musepack

mpc_file = Musepack("song.mpc")
print(f"Profile: {mpc_file.info.profile}")

# APEv2 tags
mpc_file["Title"] = "Song Title"
mpc_file["Artist"] = "Artist Name"
mpc_file.save()

Easy MP4 Interface

class EasyMP4(MP4):
    """MP4 with easy dictionary-like interface.
    
    Provides simplified tag names instead of iTunes atom names.
    """

class EasyMP4Tags:
    """Easy MP4 tags container with normalized field names.
    
    Maps simple tag names to iTunes atoms:
    - title -> ©nam
    - artist -> ©ART  
    - album -> ©alb
    - date -> ©day
    - etc.
    """

# Usage examples
from mutagen.easymp4 import EasyMP4

# Load with easy interface
easy_mp4 = EasyMP4("song.m4a")

# Simple tag names
easy_mp4["title"] = ["Song Title"]
easy_mp4["artist"] = ["Artist Name"]
easy_mp4["album"] = ["Album Name"]
easy_mp4["date"] = ["2023"]
easy_mp4["tracknumber"] = ["1/12"]
easy_mp4["discnumber"] = ["1/2"]

# Multi-value support
easy_mp4["genre"] = ["Rock", "Alternative"]

easy_mp4.save()

# Available keys
print("Available keys:", list(EasyMP4.valid_keys.keys()))

Practical Examples

Complete MP4 Tagging

from mutagen.mp4 import MP4, MP4Cover

def setup_mp4_complete(filename, metadata, cover_path=None):
    """Set up MP4 file with complete iTunes metadata."""
    mp4_file = MP4(filename)
    
    # Basic text metadata
    mp4_file["©nam"] = [metadata["title"]]
    mp4_file["©ART"] = [metadata["artist"]] 
    mp4_file["©alb"] = [metadata["album"]]
    mp4_file["©day"] = [str(metadata["year"])]
    
    # Track/disc numbers
    if "track" in metadata:
        track_total = metadata.get("track_total")
        if track_total:
            mp4_file["trkn"] = [(metadata["track"], track_total)]
        else:
            mp4_file["trkn"] = [(metadata["track"], 0)]
    
    if "disc" in metadata:
        disc_total = metadata.get("disc_total", 0)
        mp4_file["disk"] = [(metadata["disc"], disc_total)]
    
    # Optional fields
    if "albumartist" in metadata:
        mp4_file["aART"] = [metadata["albumartist"]]
    if "genre" in metadata:
        mp4_file["©gen"] = [metadata["genre"]]
    if "composer" in metadata:
        mp4_file["©wrt"] = [metadata["composer"]]
    if "bpm" in metadata:
        mp4_file["tmpo"] = [metadata["bpm"]]
        
    # Flags
    if metadata.get("compilation"):
        mp4_file["cpil"] = [True]
    if metadata.get("podcast"):
        mp4_file["pcst"] = [True]
    
    # Cover art
    if cover_path:
        with open(cover_path, "rb") as f:
            cover_data = f.read()
        
        # Detect format
        if cover_path.lower().endswith('.png'):
            format_type = MP4Cover.FORMAT_PNG
        else:
            format_type = MP4Cover.FORMAT_JPEG
            
        cover = MP4Cover(cover_data, format_type)
        mp4_file["covr"] = [cover]
    
    # Custom metadata
    if "custom_fields" in metadata:
        for key, value in metadata["custom_fields"].items():
            atom_name = f"----:com.apple.iTunes:{key}"
            mp4_file[atom_name] = [MP4FreeForm(value.encode('utf-8'), 
                                              AtomDataType.UTF8)]
    
    mp4_file.save()
    print(f"MP4 file tagged: {filename}")

# Usage
metadata = {
    "title": "Song Title",
    "artist": "Artist Name",
    "album": "Album Name",
    "year": 2023,
    "track": 1,
    "track_total": 12,
    "disc": 1,
    "albumartist": "Album Artist",
    "genre": "Rock",
    "bpm": 120,
    "compilation": False,
    "custom_fields": {
        "ENCODER": "Custom Encoder",
        "MOOD": "Happy"
    }
}

setup_mp4_complete("song.m4a", metadata, "cover.jpg")

Cross-Container Format Conversion

def convert_metadata(source_file, target_file):
    """Convert metadata between container formats."""
    import mutagen
    
    source = mutagen.File(source_file)
    target = mutagen.File(target_file) 
    
    if not source or not target:
        return False
    
    source_format = source.__class__.__name__
    target_format = target.__class__.__name__
    
    # Common metadata mapping
    metadata_map = {}
    
    if source_format == "MP4":
        metadata_map = {
            "title": source.get("©nam", [""])[0],
            "artist": source.get("©ART", [""])[0],
            "album": source.get("©alb", [""])[0],
            "date": source.get("©day", [""])[0]
        }
    elif source_format == "ASF":
        metadata_map = {
            "title": source.get("Title", [""])[0],
            "artist": source.get("Author", [""])[0],
            "album": source.get("AlbumTitle", [""])[0],
            "date": str(source.get("Year", [0])[0])
        }
    
    # Apply to target
    if target_format == "MP4":
        target["©nam"] = [metadata_map["title"]]
        target["©ART"] = [metadata_map["artist"]]
        target["©alb"] = [metadata_map["album"]] 
        target["©day"] = [metadata_map["date"]]
    elif target_format == "ASF":
        target["Title"] = [metadata_map["title"]]
        target["Author"] = [metadata_map["artist"]]
        target["AlbumTitle"] = [metadata_map["album"]]
        try:
            target["Year"] = [int(metadata_map["date"])]
        except ValueError:
            pass
    
    target.save()
    return True

# Usage
convert_metadata("song.m4a", "song.wma")

See Also

  • Mutagen - Main documentation and overview
  • Easy Interfaces - Simplified cross-format tag access
  • MP3 and ID3 Tags - MP3 format and ID3 tagging
  • Lossless Audio Formats - FLAC and other lossless formats

Install with Tessl CLI

npx tessl i tessl/pypi-mutagen

docs

container-formats.md

core-functionality.md

easy-interfaces.md

index.md

lossless-formats.md

mp3-id3.md

ogg-formats.md

tile.json