Read audio file metadata
npx @tessl/cli install tessl/pypi-tinytag@2.1.0TinyTag is a pure Python library for reading metadata from various audio file formats including MP3, M4A, WAVE, OGG, FLAC, WMA, and AIFF. It provides a unified API across all supported formats, allowing developers to extract essential metadata such as title, artist, album, track numbers, duration, bitrate, and embedded images without requiring external dependencies.
pip install tinytagfrom tinytag import TinyTagComplete imports for all functionality:
from tinytag import (
TinyTag, Image, Images, OtherFields, OtherImages,
TinyTagException, ParseError, UnsupportedFormatError,
__version__
)For type annotations (when needed):
from typing import BinaryIO, Any
from os import PathLikefrom tinytag import TinyTag
# Read basic metadata
tag = TinyTag.get('/path/to/audio/file.mp3')
print(f'Title: {tag.title}')
print(f'Artist: {tag.artist}')
print(f'Album: {tag.album}')
print(f'Duration: {tag.duration:.2f} seconds')
print(f'Bitrate: {tag.bitrate} kbps')
# Read metadata with images
tag = TinyTag.get('/path/to/audio/file.mp3', image=True)
if tag.images.front_cover:
cover_data = tag.images.front_cover.data
with open('cover.jpg', 'wb') as f:
f.write(cover_data)
# Check if file is supported
if TinyTag.is_supported('/path/to/file.mp3'):
tag = TinyTag.get('/path/to/file.mp3')Parse audio files and extract metadata using a unified API across all supported formats.
class TinyTag:
@classmethod
def get(cls,
filename: bytes | str | PathLike[Any] | None = None,
file_obj: BinaryIO | None = None,
tags: bool = True,
duration: bool = True,
image: bool = False,
encoding: str | None = None,
ignore_errors: bool | None = None) -> TinyTag:
"""
Return a TinyTag instance for an audio file.
Args:
filename: Path to audio file
file_obj: Binary file object (alternative to filename)
tags: Whether to parse metadata tags
duration: Whether to calculate audio duration
image: Whether to load embedded images
encoding: Text encoding override for some formats
ignore_errors: Deprecated parameter
Returns:
TinyTag instance with parsed metadata
Raises:
ParseError: If parsing fails
ValueError: If neither filename nor file_obj provided
"""
@classmethod
def is_supported(cls, filename: bytes | str | PathLike[Any]) -> bool:
"""
Check if a file is supported based on its extension.
Args:
filename: Path to file to check
Returns:
True if file extension is supported
"""
SUPPORTED_FILE_EXTENSIONS: tuple[str, ...] = (
'.mp1', '.mp2', '.mp3',
'.oga', '.ogg', '.opus', '.spx',
'.wav', '.flac', '.wma',
'.m4b', '.m4a', '.m4r', '.m4v', '.mp4', '.aax', '.aaxc',
'.aiff', '.aifc', '.aif', '.afc'
)Access standard metadata fields and file properties through instance attributes.
# Audio Properties
filename: str | None # Original filename
filesize: int # File size in bytes
duration: float | None # Duration in seconds
channels: int | None # Number of audio channels
bitrate: float | None # Bitrate in kBits/s
bitdepth: int | None # Bit depth (for lossless)
samplerate: int | None # Sample rate in Hz
# Metadata Fields
artist: str | None # Primary artist
albumartist: str | None # Album artist
composer: str | None # Composer
album: str | None # Album title
disc: int | None # Disc number
disc_total: int | None # Total discs
title: str | None # Track title
track: int | None # Track number
track_total: int | None # Total tracks
genre: str | None # Genre
year: str | None # Year or date
comment: str | None # Comment field
# Additional Data
images: Images # Embedded images
other: OtherFields # Additional metadataConvert metadata to dictionary format for serialization or processing.
def as_dict(self) -> dict[str, str | float | list[str]]:
"""
Return flat dictionary representation of metadata.
Returns:
Dictionary with metadata fields, lists for multiple values
"""
# Deprecated Methods (will be removed in future versions)
def get_image(self) -> bytes | None:
"""
DEPRECATED: Use images.any instead.
Return bytes of any embedded image.
Returns:
Image data bytes or None if no image available
"""
@property
def extra(self) -> dict[str, str]:
"""
DEPRECATED: Use 'other' attribute instead.
Legacy access to additional metadata fields.
Returns:
Dictionary of extra metadata fields
"""
@property
def audio_offset(self) -> None:
"""
OBSOLETE: Always returns None.
This property is no longer used and will be removed.
Returns:
None
"""Access embedded images like album artwork with full metadata support.
class Images:
"""Container for embedded images in audio files."""
front_cover: Image | None # Front cover image
back_cover: Image | None # Back cover image
media: Image | None # Media/CD label image
other: OtherImages # Additional images
@property
def any(self) -> Image | None:
"""
Return any available image, prioritizing front cover.
Returns:
First available image or None
"""
def as_dict(self) -> dict[str, list[Image]]:
"""
Return flat dictionary of all images.
Returns:
Dictionary mapping image types to lists of Image objects
"""
class Image:
"""Represents an embedded image in an audio file."""
def __init__(self, name: str, data: bytes, mime_type: str | None = None) -> None:
"""
Create Image instance.
Args:
name: Image type/name
data: Binary image data
mime_type: MIME type (e.g., 'image/jpeg')
"""
name: str # Image type/name
data: bytes # Binary image data
mime_type: str | None # MIME type
description: str | None # Image descriptionAccess format-specific and extended metadata through standardized field names.
class OtherFields(dict[str, list[str]]):
"""Dictionary containing additional metadata fields."""
# Standardized field names (when available):
# barcode, bpm, catalog_number, conductor, copyright, director
# encoded_by, encoder_settings, initial_key, isrc, language, license
# lyricist, lyrics, media, publisher, set_subtitle, url
class OtherImages(dict[str, list[Image]]):
"""Dictionary containing additional embedded images."""
# Standardized image types (when available):
# generic, icon, alt_icon, front_cover, back_cover, media, leaflet
# lead_artist, artist, conductor, band, composer, lyricist
# recording_location, during_recording, during_performance, screen_capture
# bright_colored_fish, illustration, band_logo, publisher_logo, unknownHandle errors during audio file parsing with specific exception types.
class TinyTagException(Exception):
"""Base exception for all TinyTag errors."""
class ParseError(TinyTagException):
"""Raised when parsing an audio file fails."""
class UnsupportedFormatError(TinyTagException):
"""Raised when file format is not supported."""Access the library version for compatibility checks.
__version__: str # Current library version (e.g., "2.1.2")Use tinytag from the command line to extract metadata and save images.
# Basic usage
python -m tinytag /path/to/audio.mp3
# Save cover image
python -m tinytag -i cover.jpg /path/to/audio.mp3
# Different output formats
python -m tinytag -f csv /path/to/audio.mp3
python -m tinytag -f json /path/to/audio.mp3
# Skip unsupported files
python -m tinytag -s /path/to/files/*# Command line options:
# -h, --help Display help
# -i, --save-image <path> Save cover art to file
# -f, --format json|csv|tsv|tabularcsv Output format
# -s, --skip-unsupported Skip unsupported filesfrom tinytag import TinyTag
# Parse audio file
tag = TinyTag.get('music.mp3')
# Access common metadata
print(f'Artist: {tag.artist}')
print(f'Title: {tag.title}')
print(f'Album: {tag.album}')
print(f'Duration: {tag.duration:.1f}s')
print(f'Bitrate: {tag.bitrate}kbps')
# Check audio properties
if tag.channels:
print(f'Channels: {tag.channels}')
if tag.samplerate:
print(f'Sample rate: {tag.samplerate}Hz')from tinytag import TinyTag, Image
# Load file with images
tag = TinyTag.get('album.flac', image=True)
# Access front cover
if tag.images.front_cover:
cover = tag.images.front_cover
print(f'Cover: {cover.name} ({cover.mime_type})')
print(f'Size: {len(cover.data)} bytes')
# Save cover image
with open('cover.jpg', 'wb') as f:
f.write(cover.data)
# Get any available image
any_image = tag.images.any
if any_image:
print(f'Found image: {any_image.name}')
# Legacy approach (deprecated - use images.any instead)
# image_data = tag.get_image() # DEPRECATED
# if image_data:
# with open('cover_legacy.jpg', 'wb') as f:
# f.write(image_data)
# Access all images
all_images = tag.images.as_dict()
for image_type, images in all_images.items():
print(f'{image_type}: {len(images)} images')from tinytag import TinyTag
tag = TinyTag.get('detailed.mp3')
# Access additional fields
bpm_values = tag.other.get('bpm')
if bpm_values:
print(f'BPM: {bpm_values[0]}')
lyrics = tag.other.get('lyrics')
if lyrics:
print(f'Lyrics: {lyrics[0][:100]}...')
# Handle multiple artists
primary_artist = tag.artist
additional_artists = tag.other.get('artist', [])
all_artists = [primary_artist] + additional_artists if primary_artist else additional_artists
print(f'All artists: {", ".join(all_artists)}')
# Legacy metadata access (deprecated - use 'other' instead)
# extra_data = tag.extra # DEPRECATED: returns dict[str, str]
# Use tag.other instead which returns dict[str, list[str]]
# Export all metadata
metadata = tag.as_dict()
for field, value in metadata.items():
print(f'{field}: {value}')from tinytag import TinyTag, ParseError, UnsupportedFormatError
try:
# Check support first
if not TinyTag.is_supported('file.unknown'):
print('File format not supported')
exit(1)
# Parse file
tag = TinyTag.get('file.mp3')
print(f'Successfully parsed: {tag.title}')
except ParseError as e:
print(f'Failed to parse file: {e}')
except UnsupportedFormatError as e:
print(f'Unsupported format: {e}')
except Exception as e:
print(f'Unexpected error: {e}')from tinytag import TinyTag
from io import BytesIO
# Using file object instead of filename
with open('audio.mp3', 'rb') as f:
tag = TinyTag.get(file_obj=f)
print(f'Title: {tag.title}')
# Using BytesIO
with open('audio.mp3', 'rb') as f:
data = f.read()
file_obj = BytesIO(data)
tag = TinyTag.get(file_obj=file_obj)
print(f'Duration: {tag.duration}')from tinytag import __version__
# Check library version
print(f'TinyTag version: {__version__}')
# Version-dependent behavior
from packaging import version
if version.parse(__version__) >= version.parse('2.0.0'):
# Use v2.x API features
tag = TinyTag.get('file.mp3')
if tag.track is not None: # v2.x: integers
print(f'Track: {tag.track}')
else:
# Handle v1.x compatibility
passTinyTag supports the following audio formats:
All formats use the same unified API, ensuring consistent behavior across different audio file types.