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

easy-interfaces.mddocs/

Easy Interfaces

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.

Imports

# Easy ID3 interface for MP3 files
from mutagen.easyid3 import EasyID3

# Easy MP4 interface for M4A files  
from mutagen.easymp4 import EasyMP4, EasyMP4Tags

# Easy TrueAudio interface
from mutagen.trueaudio import EasyTrueAudio

# Easy MP3 class (combines MP3 with EasyID3)
from mutagen.mp3 import EasyMP3

# Base easy functionality
from mutagen._util import DictMixin

Overview

Easy interfaces solve the problem of format-specific tag names by providing normalized field names that work consistently across different audio formats:

Problem: Different formats use different tag names

  • MP3 (ID3): TIT2 for title, TPE1 for artist
  • FLAC (Vorbis): TITLE for title, ARTIST for artist
  • MP4 (iTunes): ©nam for title, ©ART for artist

Solution: Easy interfaces use common names

  • title for title across all formats
  • artist for artist across all formats
  • Automatic conversion to/from format-specific names

EasyID3 Interface

EasyID3 Class

class EasyID3:
    """Easy dictionary-like interface for ID3 tags.
    
    Provides simplified tag names instead of ID3 frame IDs.
    Automatically handles encoding and multi-value conversion.
    
    Attributes:
        valid_keys: Dict mapping easy names to ID3 frame names
        filename: Path to audio file
    """
    
    def __init__(self, filename: str = None) -> None:
        """Create EasyID3 instance.
        
        Args:
            filename: Path to MP3 file (optional)
            
        Raises:
            MutagenError: If file is not valid MP3 or has no ID3 tags
        """
    
    def save(self, **kwargs) -> None:
        """Save ID3 tags back to file."""
    
    def delete(self) -> None:
        """Remove all ID3 tags from file."""
    
    def add_tags(self) -> None:
        """Add empty ID3 tags if none exist."""

# Standard easy field mappings for ID3 (alphabetical order)
EASY_ID3_FIELDS = {
    'album': 'TALB',           # Album title
    'albumartist': 'TPE2',     # Album artist
    'albumartistsort': 'TSO2', # Album artist sort order
    'albumsort': 'TSOA',       # Album sort order
    'arranger': 'TPE4',        # Arranger
    'artist': 'TPE1',          # Lead artist
    'artistsort': 'TSOP',      # Artist sort order
    'author': 'TOLY',          # Original lyricist
    'bpm': 'TBPM',             # Beats per minute
    'compilation': 'TCMP',     # iTunes compilation flag
    'composer': 'TCOM',        # Composer
    'composersort': 'TSOC',    # Composer sort order
    'conductor': 'TPE3',       # Conductor
    'copyright': 'TCOP',       # Copyright message
    'date': 'TDRC',            # Recording date (special handling)
    'discnumber': 'TPOS',      # Disc number
    'discsubtitle': 'TSST',    # Set subtitle
    'encodedby': 'TENC',       # Encoded by
    'genre': 'TCON',           # Genre (special handling)
    'grouping': 'TIT1',        # Content group description
    'isrc': 'TSRC',            # ISRC code
    'language': 'TLAN',        # Language
    'length': 'TLEN',          # Length in milliseconds
    'lyricist': 'TEXT',        # Lyricist
    'media': 'TMED',           # Media type
    'mood': 'TMOO',            # Mood
    'organization': 'TPUB',    # Publisher
    'originaldate': 'TDOR',    # Original release date (special handling)
    'title': 'TIT2',           # Title
    'titlesort': 'TSOT',       # Title sort order
    'tracknumber': 'TRCK',     # Track number
    'version': 'TIT3',         # Subtitle/description refinement
}

# Usage examples
from mutagen.easyid3 import EasyID3

# Load MP3 with easy interface
easy_tags = EasyID3("song.mp3")

# Simple tag access with normalized names
easy_tags["title"] = ["Song Title"]
easy_tags["artist"] = ["Artist Name"]
easy_tags["album"] = ["Album Name"]
easy_tags["date"] = ["2023"]
easy_tags["tracknumber"] = ["1/12"]  # Track 1 of 12
easy_tags["discnumber"] = ["1/2"]    # Disc 1 of 2

# Multi-value fields
easy_tags["genre"] = ["Rock", "Alternative"]
easy_tags["artist"] = ["Primary Artist", "Featured Artist"]

# Additional metadata
easy_tags["albumartist"] = ["Album Artist"]
easy_tags["composer"] = ["Composer Name"]
easy_tags["bpm"] = ["120"]
easy_tags["comment"] = ["This is a comment"]

# Save changes
easy_tags.save()

# Read tags
print(f"Title: {easy_tags['title'][0]}")
print(f"Artists: {', '.join(easy_tags['artist'])}")

# List available keys
print("Available keys:", list(EasyID3.valid_keys.keys()))

# Check if key exists
if "title" in easy_tags:
    print(f"Title: {easy_tags['title'][0]}")

EasyMP3 Class

class EasyMP3:
    """MP3 file with EasyID3 interface built-in.
    
    Combines MP3 format support with EasyID3 tag interface.
    Acts like MP3 class but uses easy tag names.
    """
    
    def __init__(self, filename: str) -> None:
        """Load MP3 file with easy tag interface."""

# Usage examples
from mutagen.mp3 import EasyMP3

# Load MP3 with built-in easy interface
mp3_file = EasyMP3("song.mp3")

# Access stream info like regular MP3
print(f"Duration: {mp3_file.info.length} seconds")
print(f"Bitrate: {mp3_file.info.bitrate} bps")

# Use easy tag names
mp3_file["title"] = ["Song Title"]
mp3_file["artist"] = ["Artist Name"]
mp3_file.save()

# Auto-detection with easy interface
import mutagen
easy_mp3 = mutagen.File("song.mp3", easy=True)  # Returns EasyMP3

EasyMP4 Interface

EasyMP4 Class

class EasyMP4:
    """Easy dictionary-like interface for MP4 metadata.
    
    Provides simplified tag names instead of iTunes atom names.
    Automatically handles data type conversion and encoding.
    """
    
    def __init__(self, filename: str) -> None:
        """Create EasyMP4 instance.
        
        Args:
            filename: Path to MP4 file (.m4a, .m4b, .m4p, etc.)
        """

class EasyMP4Tags:
    """Easy MP4 tags container with normalized field names."""

# Standard easy field mappings for MP4
EASY_MP4_FIELDS = {
    # Basic metadata  
    'title': '©nam',           # Title
    'artist': '©ART',          # Artist
    'album': '©alb',           # Album
    'albumartist': 'aART',     # Album artist
    'date': '©day',            # Date
    
    # Track/disc information
    'tracknumber': 'trkn',     # Track number (special handling)
    'discnumber': 'disk',      # Disc number (special handling)
    
    # Classification
    'genre': '©gen',           # Genre
    'grouping': '©grp',        # Grouping
    
    # Credits
    'composer': '©wrt',        # Writer/Composer
    'comment': '©cmt',         # Comment
    'lyrics': '©lyr',          # Lyrics
    
    # Technical
    'encodedby': '©too',       # Encoded by
    'copyright': '©cpy',       # Copyright
    'bpm': 'tmpo',            # Beats per minute (special handling)
    
    # Sorting (iTunes)
    'titlesort': 'sonm',       # Title sort order
    'artistsort': 'soar',      # Artist sort order
    'albumsort': 'soal',       # Album sort order
    'albumartistsort': 'soaa', # Album artist sort order
}

# Usage examples
from mutagen.easymp4 import EasyMP4

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

# Simple tag access
easy_mp4["title"] = ["Song Title"]
easy_mp4["artist"] = ["Artist Name"]
easy_mp4["album"] = ["Album Name"] 
easy_mp4["date"] = ["2023"]

# Track/disc numbers (simplified)
easy_mp4["tracknumber"] = ["1/12"]   # Automatically converts to tuple
easy_mp4["discnumber"] = ["1/2"]     # Automatically converts to tuple

# Numeric fields
easy_mp4["bpm"] = ["120"]            # Automatically converts to integer

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

# Sorting fields for iTunes
easy_mp4["titlesort"] = ["Title, The"]  # Sort ignoring "The"
easy_mp4["artistsort"] = ["Smith, John"]  # Last, First format

easy_mp4.save()

# Read tags
print(f"Title: {easy_mp4['title'][0]}")
print(f"Track: {easy_mp4['tracknumber'][0]}")  # Returns "1/12" format

EasyTrueAudio Interface

class EasyTrueAudio:
    """TrueAudio file with EasyID3 interface.
    
    Since TrueAudio uses ID3v2 tags, it can use the same easy
    interface as MP3 files.
    """

# Usage examples  
from mutagen.trueaudio import EasyTrueAudio

# Load TrueAudio with easy interface
tta_file = EasyTrueAudio("song.tta")

# Same easy field names as EasyID3
tta_file["title"] = ["Song Title"]
tta_file["artist"] = ["Artist Name"] 
tta_file.save()

Cross-Format Easy Usage

Unified Tag Access

def get_easy_tags(filename):
    """Get tags using easy interface regardless of format."""
    import mutagen
    from mutagen.easyid3 import EasyID3
    from mutagen.easymp4 import EasyMP4
    from mutagen.trueaudio import EasyTrueAudio
    
    # Try auto-detection with easy interface
    audio_file = mutagen.File(filename, easy=True)
    
    if audio_file:
        return audio_file
    
    # Fallback to format-specific easy interfaces
    try:
        if filename.lower().endswith('.mp3'):
            return EasyID3(filename)
        elif filename.lower().endswith(('.m4a', '.m4b', '.m4p', '.mp4')):
            return EasyMP4(filename)
        elif filename.lower().endswith('.tta'):
            return EasyTrueAudio(filename)
    except:
        pass
    
    return None

def set_basic_tags(filename, metadata):
    """Set basic tags using easy interface."""
    easy_file = get_easy_tags(filename)
    
    if not easy_file:
        print(f"No easy interface available for {filename}")
        return False
    
    # Standard fields that work across easy interfaces
    field_mapping = {
        'title': 'title',
        'artist': 'artist', 
        'album': 'album',
        'date': 'date',
        'genre': 'genre',
        'tracknumber': 'tracknumber',
        'albumartist': 'albumartist'
    }
    
    for source_key, easy_key in field_mapping.items():
        if source_key in metadata and easy_key in easy_file.valid_keys:
            value = metadata[source_key]
            easy_file[easy_key] = [str(value)] if not isinstance(value, list) else value
    
    easy_file.save()
    return True

# Usage
metadata = {
    'title': 'Song Title',
    'artist': 'Artist Name',
    'album': 'Album Name',
    'date': '2023',
    'genre': 'Rock',
    'tracknumber': '1'
}

# Works with MP3, MP4, TrueAudio
set_basic_tags("song.mp3", metadata)
set_basic_tags("song.m4a", metadata) 
set_basic_tags("song.tta", metadata)

Tag Migration Between Formats

def migrate_tags_easy(source_file, target_file):
    """Migrate tags between formats using easy interfaces."""
    
    source_easy = get_easy_tags(source_file)
    target_easy = get_easy_tags(target_file)
    
    if not source_easy or not target_easy:
        return False
    
    # Get intersection of supported fields
    source_keys = set(source_easy.valid_keys.keys())
    target_keys = set(target_easy.valid_keys.keys()) 
    common_keys = source_keys & target_keys
    
    # Copy common fields
    for key in common_keys:
        if key in source_easy:
            target_easy[key] = source_easy[key]
    
    target_easy.save()
    print(f"Migrated {len(common_keys)} tag fields")
    return True

# Example: Copy tags from MP3 to M4A
migrate_tags_easy("song.mp3", "song.m4a")

Advanced Easy Interface Usage

Custom Field Mappings

# Extend EasyID3 with custom mappings
from mutagen.easyid3 import EasyID3

def add_custom_easy_mapping(easy_key, frame_id, getter=None, setter=None):
    """Add custom field mapping to EasyID3."""
    
    if getter is None:
        def getter(id3, key):
            return [frame.text[0] for frame in id3.getall(frame_id)]
    
    if setter is None:
        def setter(id3, key, value):
            from mutagen.id3 import TextFrame
            frame_class = getattr(mutagen.id3, frame_id)
            id3[frame_id] = frame_class(encoding=3, text=value)
    
    EasyID3.RegisterKey(easy_key, getter, setter)

# Add custom field for mood
def mood_get(id3, key):
    """Get mood from TMOO frame."""
    frames = id3.getall('TMOO')
    return [frame.text[0] for frame in frames] if frames else []

def mood_set(id3, key, value):
    """Set mood in TMOO frame."""
    from mutagen.id3 import TMOO
    id3['TMOO'] = TMOO(encoding=3, text=value)

add_custom_easy_mapping('mood', 'TMOO', mood_get, mood_set)

# Now use custom field
easy_tags = EasyID3("song.mp3")
easy_tags['mood'] = ['Happy']
easy_tags.save()

Validation and Normalization

def validate_easy_tags(easy_file, required_fields=None):
    """Validate easy tags and normalize values."""
    
    if required_fields is None:
        required_fields = ['title', 'artist', 'album']
    
    issues = []
    
    # Check required fields
    for field in required_fields:
        if field not in easy_file or not easy_file[field]:
            issues.append(f"Missing required field: {field}")
    
    # Normalize track numbers
    if 'tracknumber' in easy_file:
        track_values = easy_file['tracknumber']
        normalized_tracks = []
        
        for track in track_values:
            # Ensure track number format
            if '/' not in track:
                normalized_tracks.append(track)
            else:
                # Validate track/total format
                try:
                    current, total = track.split('/', 1)
                    int(current)  # Validate numeric
                    int(total)    # Validate numeric
                    normalized_tracks.append(track)
                except ValueError:
                    issues.append(f"Invalid track number format: {track}")
        
        if normalized_tracks != track_values:
            easy_file['tracknumber'] = normalized_tracks
    
    # Normalize dates to YYYY format
    if 'date' in easy_file:
        date_values = easy_file['date']
        normalized_dates = []
        
        for date in date_values:
            # Extract year from various date formats
            import re
            year_match = re.search(r'(\d{4})', date)
            if year_match:
                normalized_dates.append(year_match.group(1))
            else:
                issues.append(f"Invalid date format: {date}")
        
        if normalized_dates != date_values:
            easy_file['date'] = normalized_dates
    
    return issues

# Usage
easy_tags = EasyID3("song.mp3")
issues = validate_easy_tags(easy_tags)

if issues:
    print("Tag validation issues:")
    for issue in issues:
        print(f"  - {issue}")
else:
    print("All tags valid")
    easy_tags.save()

Batch Easy Processing

import os
import mutagen

def batch_easy_tag(directory, tag_updates):
    """Batch update tags using easy interfaces."""
    
    results = {'success': [], 'failed': [], 'skipped': []}
    
    for filename in os.listdir(directory):
        filepath = os.path.join(directory, filename)
        
        if not os.path.isfile(filepath):
            continue
        
        try:
            # Try easy interface first
            audio_file = mutagen.File(filepath, easy=True)
            
            if audio_file and hasattr(audio_file, 'valid_keys'):
                # Apply updates
                for key, value in tag_updates.items():
                    if key in audio_file.valid_keys:
                        audio_file[key] = [value] if not isinstance(value, list) else value
                
                audio_file.save()
                results['success'].append(filename)
                
            else:
                results['skipped'].append(f"{filename} (no easy interface)")
                
        except Exception as e:
            results['failed'].append(f"{filename} ({str(e)})")
    
    return results

# Usage
tag_updates = {
    'albumartist': 'Various Artists',
    'date': '2023',
    'genre': 'Compilation'
}

results = batch_easy_tag("/path/to/music", tag_updates)

print(f"Successfully updated: {len(results['success'])} files")
print(f"Failed: {len(results['failed'])} files") 
print(f"Skipped: {len(results['skipped'])} files")

Available Easy Fields

Common Fields (Most Formats)

COMMON_EASY_FIELDS = [
    'title',        # Track title
    'artist',       # Primary artist
    'album',        # Album name
    'albumartist',  # Album artist
    'date',         # Release date
    'genre',        # Musical genre
    'tracknumber',  # Track number (format: "1" or "1/12")
    'discnumber',   # Disc number (format: "1" or "1/2")
]

Extended Fields (Format Dependent)

EXTENDED_EASY_FIELDS = [
    'composer',     # Composer name
    'conductor',    # Conductor name
    'lyricist',     # Lyricist name
    'performer',    # Performer credits
    'grouping',     # Content grouping
    'comment',      # Comment text
    'lyrics',       # Song lyrics
    'bpm',          # Beats per minute
    'encodedby',    # Encoded by
    'copyright',    # Copyright notice
    'website',      # Official website
    'isrc',         # International Standard Recording Code
    'language',     # Language code
    'originaldate', # Original release date
    'titlesort',    # Title sort order
    'artistsort',   # Artist sort order  
    'albumsort',    # Album sort order
]

See Also

  • Mutagen - Main documentation and overview
  • MP3 and ID3 Tags - Native ID3 tag access
  • Container Formats - Native MP4 metadata access
  • Core Functionality - Base classes and format detection

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