audiometa-python 0.11.1
A comprehensive Python library for reading and writing audio metadata across multiple formats
License: Apache-2.0
Requires Python: >=3.12
Keywords: audio, metadata, mp3, flac, wav, id3, vorbis, riff

<div align="center">
  <img src="https://raw.githubusercontent.com/BehindTheMusicTree/audiometa/main/assets/logo.png" alt="AudioMeta Logo" width="200"/>
</div>

# AudioMeta Python

[![CI](https://github.com/BehindTheMusicTree/audiometa/actions/workflows/ci.yml/badge.svg)](https://github.com/BehindTheMusicTree/audiometa/actions/workflows/ci.yml)
[![Python](https://img.shields.io/badge/python-3.12%20%7C%203.13%20%7C%203.14-blue)](https://www.python.org/)
[![License](https://img.shields.io/badge/license-Apache%202.0-green.svg)](LICENSE)
[![PyPI version](https://img.shields.io/pypi/v/audiometa-python)](https://pypi.org/project/audiometa-python/)
[![Downloads](https://img.shields.io/pepy/dt/audiometa-python)](https://pepy.tech/project/audiometa-python)
[![GitHub stars](https://img.shields.io/github/stars/BehindTheMusicTree/audiometa?style=social)](https://github.com/BehindTheMusicTree/audiometa/stargazers)

A powerful, unified Python library for reading and writing audio metadata across multiple formats. AudioMeta supports MP3, FLAC, and WAV audio files, working seamlessly with ID3v1, ID3v2, Vorbis, and RIFF metadata formats through a single, consistent API.

**Author**: [Andreas Garcia](https://github.com/BehindTheMusicTree)

## ⭐ Show Your Support

If you find AudioMeta Python useful, please consider:

- ⭐ **Starring this repository** - It helps others discover the project
- 🐛 **Reporting bugs** - Help improve the library by [opening an issue](https://github.com/BehindTheMusicTree/audiometa/issues)
- 💡 **Suggesting features** - Share your ideas via [GitHub Discussions](https://github.com/BehindTheMusicTree/audiometa/discussions) or [feature requests](https://github.com/BehindTheMusicTree/audiometa/issues)
- 🤝 **Contributing** - See [CONTRIBUTING.md](CONTRIBUTING.md) for ways to help
- 📢 **Sharing** - Tell others about AudioMeta Python

Your support helps make this project better for everyone! 🎵

## Table of Contents

- [⭐ Show Your Support](#-show-your-support)
- [✨ Features](#-features)
- [📁 Supported Formats](#-supported-formats)
  - [Supported Audio Formats Per Metadata Format](#supported-audio-formats-per-metadata-format)
  - [Supported Metadata Formats per Audio Format](#supported-metadata-formats-per-audio-format)
  - [Format Capabilities](#format-capabilities)
- [📊 Supported Fields And Audio Technical Info](#-supported-fields-and-audio-technical-info)
- [📦 Installation](#-installation)
  - [System Requirements](#system-requirements)
  - [Installing Required Tools](#installing-required-tools)
  - [Verifying Installation](#verifying-installation)
  - [External Tools Usage](#external-tools-usage)
- [🚀 Getting Started](#-getting-started)
  - [What You Need](#what-you-need)
  - [Your First Steps](#your-first-steps)
  - [Common Use Cases](#common-use-cases)
- [⚡ Quick Start](#-quick-start)
  - [Reading Metadata](#reading-metadata)
  - [Validate Metadata Before Update](#validate-metadata-before-update)
  - [Writing Metadata](#writing-metadata)
  - [Deleting Metadata](#deleting-metadata)
- [📚 Core API Reference](#-core-api-reference)
  - [Reading Metadata (API Reference)](#reading-metadata-api-reference)
  - [Pre-Update Validation (API Reference)](#pre-update-validation-api-reference)
  - [Writing Metadata (API Reference)](#writing-metadata-api-reference)
  - [Deleting Metadata (API Reference)](#deleting-metadata-api-reference)
  - [Error Handling (API Reference)](#error-handling-api-reference)
- [📖 Metadata Guide](#-metadata-guide)
  - [Metadata Field Guide: Support and Handling](#metadata-field-guide-support-and-handling)
  - [Audio Technical Info Guide](#audio-technical-info-guide)
  - [Unsupported Metadata Handling](#unsupported-metadata-handling)
- [💻 Command Line Interface](#-command-line-interface)
  - [Installation](#cli-installation)
  - [Basic Usage](#basic-usage)
  - [Advanced Options](#advanced-options)
  - [Output Formats](#output-formats)
  - [Examples](#examples)
- [📝 Changelog](#-changelog)
- [🤝 Contributing](#-contributing)
- [Code of Conduct](CODE_OF_CONDUCT.md)
- [Security Policy](https://github.com/BehindTheMusicTree/audiometa/security/policy)
- [License](#license)

## ✨ Features

- **Unified API**: A single, consistent API for reading and writing metadata across all supported formats. Use the same functions (`get_unified_metadata()`, `update_metadata()`, etc.) regardless of whether you're working with MP3, FLAC, or WAV files. The library automatically handles format-specific differences, normalizes field names, and intelligently merges metadata from multiple formats when reading.

- **Multi-format Support**: ID3v1, ID3v2, Vorbis (FLAC), and RIFF (WAV) metadata formats. Many audio files can contain multiple metadata formats simultaneously (e.g., MP3 files with both ID3v1 and ID3v2 tags, FLAC files with ID3v1, ID3v2, and Vorbis comments). AudioMeta intelligently handles these scenarios with automatic format detection and priority-based reading.

- **Format Control**: Force specific metadata formats when reading or writing for precise control. Read only ID3v1 tags from an MP3 file that contains both ID3v1 and ID3v2, or write metadata exclusively to the Vorbis format in a FLAC file. Essential for format-specific operations, migration tasks, or working with legacy metadata formats.

- **Technical Information**: Access to technical information about audio files, including duration, bitrate, sample rate, channels, and file size. This technical data is extracted directly from audio file headers, so you can get comprehensive file analysis even when no metadata tags are present.

- **Core Metadata Fields**: Support for 15+ metadata fields including title, artist, album, rating, BPM, and more. More fields are planned to be supported soon.

- **Read/Write Operations**: Full read and write support for most formats

- **Rating Support**: Normalized rating handling across different formats

- **Complete File Analysis**: Get full metadata including headers and technical details even when no metadata is present

## 📁 Supported Formats

### Supported Audio Formats Per Metadata Format

| Format | Audio Format   |
| ------ | -------------- |
| ID3v1  | MP3, FLAC, WAV |
| ID3v2  | MP3, FLAC, WAV |
| Vorbis | FLAC           |
| RIFF   | WAV            |

### Supported Metadata Formats per Audio Format

| Audio Format | Supported Metadata Formats |
| ------------ | -------------------------- |
| MP3          | ID3v1, ID3v2               |
| FLAC         | ID3v1, ID3v2, Vorbis       |
| WAV          | ID3v1, ID3v2, RIFF         |

### Format Capabilities

For comprehensive information about each metadata format (history, structure, advantages, disadvantages, use cases), see the **[Metadata Formats Guide](docs/METADATA_FORMATS.md)**.

#### ID3v1 Metadata Format

- **Primary Support**: MP3 files (native format)
- **Extended Support**: FLAC and WAV files with ID3v1 tags
- **Limitations**: 30-character field limits, no album artist support
- **Operations**: Full read/write support with direct file manipulation
- **Note**: ID3v1.1 is supported (track number supported in comment field)

#### ID3v2 Metadata Format

- **Supported Formats**: MP3, WAV, FLAC
- **Features**: All metadata fields, multiple artists, cover art, extended metadata
- **Versions**: Supports ID3v2.3 and ID3v2.4
- **Note**: Most versatile format, works across multiple file types

#### Vorbis Metadata Format

- **Primary Support**: FLAC files (native Vorbis comments)
- **Features**: Most metadata fields, multiple artists, cover art
- **Limitations**: Some fields not supported (lyrics, etc.)
- **Note**: Standard metadata format for FLAC files

**Vorbis Comment Key Handling**
Vorbis comment field names are case-insensitive, as defined by the Xiph.org Vorbis Comment specification.
To ensure consistent and predictable behavior, this library normalizes all field names internally and follows modern interoperability conventions.

**_Reading_**
When reading Vorbis comments, the library treats field names in a case-insensitive manner. For example, "TITLE", "title", and "Title" are considered equivalent.

**_Writing_**
When writing Vorbis comments, the library standardizes field names to uppercase to maintain consistency and compatibility with common practices in audio metadata management. It thus writes "TITLE" removing eventual existing variations in casing.

#### RIFF Metadata Format

- **Strict Support**: WAV files only
- **Features**: Most metadata fields including album artist, language, comments
- **Limitations**: Some fields not supported (BPM, lyrics, etc.)
- **Note**: Native metadata format for WAV files

## 📊 Supported Fields And Audio Technical Info

AudioMeta supports comprehensive audio information across all formats. For technical audio information (duration, bitrate, sample rate, channels, file size, format info, MD5 checksum validation and repair), see:

**[Audio Technical Info Guide](docs/AUDIO_TECHNICAL_INFO_GUIDE.md)**

For metadata fields (title, artist, album, genres, ratings, etc.), see:

**[Metadata Field Guide: Support and Handling](docs/METADATA_FIELD_GUIDE.md)**

## 📦 Installation

```bash
pip install audiometa-python
```

### System Requirements

- **Python**: 3.12, 3.13, or 3.14
- **Operating Systems**: Windows, macOS, Linux
- **Dependencies**: Automatically installed with the package
- **Required Tools**: ffprobe (for WAV file processing), flac (for FLAC MD5 validation)

### Installing Required Tools

The library requires several external tools for full functionality. **Use the automated installation scripts** to ensure you have the correct pinned versions that match CI.

#### Automated Setup {#automated-setup-recommended}

To ensure your local environment matches CI exactly, use the automated installation scripts:

```bash
# Ubuntu/Linux
./scripts/install-system-dependencies-ubuntu.sh

# macOS
./scripts/install-system-dependencies-macos.sh

# Windows
.\scripts\install-system-dependencies-windows.ps1
```

These scripts install all required tools with pinned versions that match CI:

**Production tools (required for library functionality):**

- **ffmpeg** / **ffprobe** - For WAV file processing and technical info (all platforms)
- **flac** / **metaflac** - For FLAC MD5 validation and metadata writing (all platforms)
- **id3v2** - For ID3v2 tag writing on FLAC files (Ubuntu/macOS only; Windows requires WSL)
- **bwfmetaedit** - For BWF metadata (Ubuntu/macOS/Windows)
- **libsndfile** - For audio file I/O (Ubuntu/macOS only)

**Dev/testing tools (only needed for running tests locally):**

- **mediainfo** - Only for integration test verification
- **exiftool** - Only for integration test verification

**Pinned versions:** All tool versions are pinned in separate configuration files (the single source of truth):

- [`system-dependencies-prod.toml`](system-dependencies-prod.toml) - Production dependencies (ffmpeg, flac, id3v2)
- [`system-dependencies-test-only.toml`](system-dependencies-test-only.toml) - Test-only dependencies (mediainfo, exiftool, bwfmetaedit, libsndfile) - supplementary to prod dependencies
- [`system-dependencies-lint.toml`](system-dependencies-lint.toml) - Lint dependencies (PowerShell)

The scripts verify installed versions match these pinned versions. See the configuration files for complete details and OS-specific version information.

**Note for Windows users:**

- The `id3v2` tool is not available as a native Windows binary. The installation script attempts to use **WSL (Windows Subsystem for Linux)** to install `id3v2` via Ubuntu's package manager, but WSL installation complexity (requiring system restarts, DISM configuration, and Ubuntu distribution setup) has prevented successful full installation in practice. This is why Windows CI only runs e2e tests (which don't require `id3v2`). For local development, the script will attempt WSL installation, but manual WSL setup may be required.
- **Windows CI differences:** Windows CI only runs e2e tests (not unit/integration tests), so some tools are skipped in CI but still installed locally for full test coverage: `mediainfo` and `exiftool` are required for integration tests and are installed by the script for local development, but skipped in Windows CI since integration tests don't run there.

#### Verifying Installation

After installation, verify the tools are available:

```bash
ffprobe -version
flac --version
```

The installation scripts automatically verify installed versions match pinned versions from `system-dependencies-prod.toml`, `system-dependencies-test-only.toml`, and `system-dependencies-lint.toml`.

#### External Tools Usage

AudioMeta uses a combination of Python libraries and external command-line tools depending on the operation and audio format. This section provides a comprehensive overview of when external tools are required versus when pure Python libraries are used.

| Format     | Read Metadata    | Write Metadata                             | Technical Info (Duration/Bitrate/etc.) | Validation           |
| ---------- | ---------------- | ------------------------------------------ | -------------------------------------- | -------------------- |
| **ID3v1**  | Custom (Python)  | Custom (Python)                            | mutagen (Python)                       | N/A                  |
| **ID3v2**  | mutagen (Python) | mutagen (Python) / id3v2/mid3v2 (external) | mutagen (Python)                       | N/A                  |
| **Vorbis** | Custom (Python)  | metaflac (external)                        | mutagen (Python)                       | flac (external tool) |
| **RIFF**   | mutagen (Python) | Custom (Python)                            | ffprobe (external tool)                | N/A                  |

**Notes:**

- **ID3v2**: Uses external tools (`id3v2` or `mid3v2`) for writing to FLAC files to prevent file corruption
- **Vorbis**: Uses `metaflac` external tool for writing to preserve proper uppercase key casing and avoid file corruption
- **External tools required**: `metaflac`, `id3v2`/`mid3v2` (for FLAC files), `ffprobe`, `flac`

## 🚀 Getting Started

### What You Need

- Python 3.12, 3.13, or 3.14
- Audio files (MP3, FLAC, WAV)
- Basic Python knowledge

### Your First Steps

1. **Install the library** using pip
2. **Try reading metadata** from an existing audio file
3. **Update some metadata** to see how writing works
4. **Explore advanced features** like format-specific operations

### Common Use Cases

- **Music library management**: Organize and clean up metadata
- **Metadata cleanup**: Remove unwanted or duplicate information
- **Format conversion**: Migrate metadata between formats
- **Batch processing**: Update multiple files at once
- **Privacy protection**: Remove personal information from files

## ⚡ Quick Start

### Reading Metadata

When reading metadata, there are three functions to use: `get_unified_metadata` and `get_unified_metadata_field`, and `get_full_metadata`.

- `get_unified_metadata`: Reads all metadata from a file and returns a unified dictionary.
- `get_unified_metadata_field`: Reads a specific metadata field from a file.
- `get_full_metadata`: Reads all metadata from a file and returns a dictionary including headers and technical info.

#### Reading from a specific metadata format

The library supports reading metadata from specific formats (ID3v1, ID3v2.3, ID3v2.4, Vorbis, RIFF). This is useful when you know the format of the file you are working with and you want to read only from that format.

```python
from audiometa import get_unified_metadata, UnifiedMetadataKey
from audiometa.utils.MetadataFormat import MetadataFormat

metadata = get_unified_metadata("path/to/your/audio.mp3", metadata_format=MetadataFormat.ID3V2)
print(f"Title: {metadata.get(UnifiedMetadataKey.TITLE, 'Unknown')}")
```

When specifying a metadata format not supported by the audio format of the file, raises a MetadataFormatNotSupportedByAudioFormatError.

```python
from audiometa import get_unified_metadata, UnifiedMetadataKey
from audiometa.utils.MetadataFormat import MetadataFormat
from audiometa.exceptions import MetadataFormatNotSupportedByAudioFormatError

try:
    metadata = get_unified_metadata("path/to/your/audio.mp3", metadata_format=MetadataFormat.RIFF)
except MetadataFormatNotSupportedByAudioFormatError as e:
    print(f"Error: {e}")
```

#### Reading All Metadata

**`get_unified_metadata(file_path, metadata_format=None)`**

Reads all metadata from a file and returns a unified dictionary.
If `metadata_format` is specified, reads only from that format.
If not specified, uses priority order across all formats.

**Note:** `file_path` can be a string or `pathlib.Path` object.

```python
from audiometa import get_unified_metadata

metadata = get_unified_metadata("path/to/your/audio.mp3")
print(f"Title: {metadata.get(UnifiedMetadataKey.TITLE, 'Unknown')}")
print(f"Artist: {metadata.get(UnifiedMetadataKey.ARTISTS, ['Unknown'])}")
print(f"Album: {metadata.get(UnifiedMetadataKey.ALBUM, 'Unknown')}")
```

#### Reading Specific Metadata Fields (Quick Start)

**`get_unified_metadata_field(file_path, field, metadata_format=None)`**

Reads a specific metadata field. If no metadata format is specified, uses priority order across all formats.

**Note:** `file_path` can be a string or `pathlib.Path` object.

**Note:** The `field` parameter can be a `UnifiedMetadataKey` enum instance or a string matching an enum value (e.g., `"title"`). Invalid values will raise `MetadataFieldNotSupportedByLibError`.

```python
from audiometa import get_unified_metadata_field, UnifiedMetadataKey

# Get title using priority order (all formats)
title = get_unified_metadata_field("song.mp3", UnifiedMetadataKey.TITLE)
```

If `metadata_format` is specified, reads only from that format.

```python
from audiometa import get_unified_metadata_field, UnifiedMetadataKey
from audiometa.utils.MetadataFormat import MetadataFormat

# Get raw rating from specific format only
id3v2_rating = get_unified_metadata_field("song.mp3", UnifiedMetadataKey.RATING, metadata_format=MetadataFormat.ID3V2, id3v2_version=(2, 4, 0))
```

If `metadata_format` is specified and the field is not supported by that format, raises a MetadataFieldNotSupportedError.

```python
from audiometa import get_unified_metadata_field, UnifiedMetadataKey
from audiometa.utils.MetadataFormat import MetadataFormat
from audiometa.exceptions import MetadataFieldNotSupportedByMetadataFormatError

# Attempt to get unsupported field from specific format

try:
    riff_bpm = get_unified_metadata_field("song.wav", UnifiedMetadataKey.BPM, metadata_format=MetadataFormat.RIFF)
except MetadataFieldNotSupportedByMetadataFormatError as e:
    print(f"Error: {e}")
```

#### Reading Full Metadata From All Formats Including Headers and Technical Info

**`get_full_metadata(file_path, include_headers=True, include_technical=True)`**

Gets comprehensive metadata including all available information from a file, including headers and technical details even when no metadata is present.

**Note:** `file_path` can be a string or `pathlib.Path` object.

```python
from audiometa import get_full_metadata

full_metadata = get_full_metadata("song.mp3")
```

### Validate Metadata Before Update

Before updating metadata in a file, it's recommended to validate your metadata to catch errors early:

```python
from audiometa import validate_metadata_for_update, UnifiedMetadataKey

# Validate metadata before updating
metadata = {
    UnifiedMetadataKey.TITLE: 'New Song Title',
    UnifiedMetadataKey.ARTISTS: ['Artist Name'],
    UnifiedMetadataKey.ALBUM: 'Album Name',
    UnifiedMetadataKey.RATING: 85,
}

try:
    validate_metadata_for_update(metadata)
    print("Metadata is valid!")
except Exception as e:
    print(f"Metadata validation failed: {e}")
```

This validation checks for:

- **Type correctness**: Ensures values match expected types (strings for title, lists for artists, etc.)
- **Format rules**: Validates field formats (e.g., release dates must be in ISO 8601 format)
- **Value ranges**: Checks ratings, track numbers, and other numeric values are within valid ranges
- **Empty values**: Verifies at least one field is provided

See [Pre-Update Validation Function](#pre-update-validation-function) for detailed validation rules and examples.

### Writing Metadata

```python
from audiometa import update_metadata

# Update metadata (use UnifiedMetadataKey for explicit typing)
from audiometa import UnifiedMetadataKey

new_metadata = {
    UnifiedMetadataKey.TITLE: 'New Song Title',
    UnifiedMetadataKey.ARTISTS: ['Artist Name'],
    UnifiedMetadataKey.ALBUM: 'Album Name',
    UnifiedMetadataKey.RATING: 85,
}
update_metadata("path/to/your/audio.mp3", new_metadata)
```

**Format-specific Writing**

```python
from audiometa.utils.MetadataFormat import MetadataFormat
update_metadata("song.wav", new_metadata, metadata_format=MetadataFormat.RIFF)
```

**For comprehensive documentation** on writing strategies, format handling, and unsupported field management, see the **[Writing Metadata Guide](docs/WRITING_METADATA.md)**.

### Deleting Metadata

There are two ways to remove metadata from audio files:

#### Delete All Metadata (Complete Removal)

```python
from audiometa import delete_all_metadata

# Delete ALL metadata from ALL supported formats (removes metadata headers entirely)
success = delete_all_metadata("path/to/your/audio.mp3")
print(f"All metadata deleted: {success}")

# Delete metadata from specific format only
from audiometa.utils.MetadataFormat import MetadataFormat
success = delete_all_metadata("song.wav", metadata_format=MetadataFormat.ID3V2)
# This removes only ID3v2 tags, keeps RIFF metadata
```

**Important**: This function removes the metadata headers/containers entirely from the file, not just the content. This means:

- ID3v2 tag structure is completely removed
- Vorbis comment blocks are completely removed
- RIFF INFO chunks are completely removed
- File size is significantly reduced

#### Remove Specific Fields (Selective Removal)

```python
from audiometa import update_metadata, UnifiedMetadataKey

# Remove only specific fields by setting them to None
update_metadata("path/to/your/audio.mp3", {
    UnifiedMetadataKey.TITLE: None,        # Remove title field
    UnifiedMetadataKey.ARTISTS: None # Remove artist field
    # Other fields remain unchanged
})

# This removes only the specified fields while keeping:
# - Other metadata fields intact
# - Metadata headers/containers in place
# - File size mostly unchanged
```

**When to use each approach:**

- **`delete_all_metadata()`**: When you want to completely strip all metadata from a file
- **Setting fields to `None`**: When you want to clean up specific fields while preserving others

#### Comparison Table

| Aspect               | `delete_all_metadata()`   | Setting fields to `None`      |
| -------------------- | ------------------------- | ----------------------------- |
| **Scope**            | Removes ALL metadata      | Removes only specified fields |
| **Metadata headers** | **Completely removed**    | **Preserved**                 |
| **File size**        | Significantly reduced     | Minimal change                |
| **Other fields**     | All removed               | Unchanged                     |
| **Use case**         | Complete cleanup          | Selective cleanup             |
| **Performance**      | Faster (single operation) | Slower (field-by-field)       |

#### Example Scenarios

**Scenario 1: Complete Privacy Cleanup**

```python
# Remove ALL metadata for privacy
delete_all_metadata("personal_recording.mp3")
# Result: File has no metadata headers at all (ID3v2 tags completely removed)
```

**Scenario 2: Clean Up Specific Information**

```python
# Remove only personal info, keep technical metadata
update_metadata("song.mp3", {
    UnifiedMetadataKey.TITLE: None,           # Remove title
    UnifiedMetadataKey.ARTISTS: None,   # Remove artist
    # Keep album, genre, year, etc.
})
# Result: File keeps metadata headers but removes specific fields
```

### Getting Technical Information

The library provides functional APIs for getting technical information about audio files:

```python
from audiometa import get_duration_in_sec, get_bitrate, get_sample_rate, get_channels, get_file_size, is_audio_file

# Check if a file is a valid audio file before processing
if is_audio_file("path/to/your/audio.flac"):
    # Get technical information using functional API (recommended)
    duration = get_duration_in_sec("path/to/your/audio.flac")
    bitrate = get_bitrate("path/to/your/audio.flac")
    sample_rate = get_sample_rate("path/to/your/audio.flac")
    channels = get_channels("path/to/your/audio.flac")
    file_size = get_file_size("path/to/your/audio.flac")

    print(f"Duration: {duration} seconds")
    print(f"Bitrate: {bitrate} bps ({bitrate // 1000} kbps)")
    print(f"Sample Rate: {sample_rate} Hz")
    print(f"Channels: {channels}")
    print(f"File Size: {file_size} bytes")
else:
    print("File is not a valid audio file")
```

## 📚 Core API Reference

### Reading Metadata (API Reference)

#### Reading Priorities (Tag Precedence)

When the same metadata tag exists in multiple formats within the same file, the library follows file-specific precedence orders for reading:

#### FLAC Files Reading Priorities

1. **Vorbis** (highest precedence)
2. **ID3v2**
3. **ID3v1** (lowest precedence, legacy format)

#### MP3 Files Reading Priorities

1. **ID3v2** (highest precedence)
2. **ID3v1** (lowest precedence, legacy format)

#### WAV Files Reading Priorities

1. **RIFF** (highest precedence)
2. **ID3v2**
3. **ID3v1** (lowest precedence, legacy format)

**Examples**:

- For MP3 files: If a title exists in both ID3v1 and ID3v2, the ID3v2 title will be returned.
- For WAV files: If a title exists in both RIFF and ID3v2, the RIFF title will be returned.
- For FLAC files: If a title exists in both Vorbis and ID3v2, the Vorbis title will be returned.

#### Reading All Metadata From All Metadata Formats Including Priority Logic

**`get_unified_metadata(file_path, metadata_format=None)`**

Reads all metadata from a file and returns a unified dictionary.
If `metadata_format` is specified, reads only from that format.
If not specified, uses priority order across all formats.

**Note:** `file_path` can be a string or `pathlib.Path` object.

```python
from audiometa import get_unified_metadata

# Read all metadata (unified across all formats)
metadata = get_unified_metadata("song.mp3")
print(metadata[UnifiedMetadataKey.TITLE])  # Song title
print(metadata[UnifiedMetadataKey.ARTISTS])  # List of artists
```

#### Reading All Metadata From A Specific Format

**`get_unified_metadata(file_path, metadata_format=MetadataFormat.ID3V2)`**

```python

# Read only ID3v2 metadata
from audiometa.utils.MetadataFormat import MetadataFormat
id3v2_metadata = get_unified_metadata("song.mp3", metadata_format=MetadataFormat.ID3V2)

# Read only Vorbis metadata
vorbis_metadata = get_unified_metadata("song.flac", metadata_format=MetadataFormat.VORBIS)
```

#### Reading All Metadata From A ID3v2 Format With Version

**`get_unified_metadata(file_path, metadata_format=MetadataFormat.ID3V2), id3v2_version=(2, 3, 0))`**

```python

# Read only ID3v2.3 metadata
from audiometa.utils.MetadataFormat import MetadataFormat
id3v2_3_metadata = get_unified_metadata("song.mp3", metadata_format=MetadataFormat.ID3V2, id3v2_version=(2, 3, 0))

# Read only ID3v2.4 metadata
id3v2_4_metadata = get_unified_metadata("song.mp3", metadata_format=MetadataFormat.ID3V2, id3v2_version=(2, 4, 0))
```

#### Reading Specific Metadata Fields

**`get_unified_metadata_field(file_path, field, metadata_format=None)`**

Reads a specific metadata field. If `metadata_format` is specified, reads only from that format; otherwise uses priority order across all formats.

**Note:** `file_path` can be a string or `pathlib.Path` object.

```python
from audiometa import get_unified_metadata_field, UnifiedMetadataKey
from audiometa.utils.MetadataFormat import MetadataFormat

# Get title using priority order (all formats)
title = get_unified_metadata_field("song.mp3", UnifiedMetadataKey.TITLE)

# Get raw rating from specific format only
id3v2_rating = get_unified_metadata_field("song.mp3", UnifiedMetadataKey.RATING, metadata_format=MetadataFormat.ID3V2)
```

#### Reading Full Metadata From All Formats Including Headers and Technical Info

**`get_full_metadata(file_path, include_headers=True, include_technical=True)`**

Gets comprehensive metadata including all available information from a file, including headers and technical details even when no metadata is present.

**Note:** `file_path` can be a string or `pathlib.Path` object.

This function provides the most complete view of an audio file by combining:

- All metadata from all supported formats (ID3v1, ID3v2, Vorbis, RIFF)
- Technical information (duration, bitrate, sample rate, channels, file size)
- Format-specific headers and structure information
- Raw metadata details from each format

```python
from audiometa import get_full_metadata, UnifiedMetadataKey

# Get complete metadata including headers and technical info
full_metadata = get_full_metadata("song.mp3")

# Access unified metadata (same as get_unified_metadata)
print(f"Title: {full_metadata['unified_metadata'][UnifiedMetadataKey.TITLE]}")
print(f"Artists: {full_metadata['unified_metadata'][UnifiedMetadataKey.ARTISTS]}")

# Access technical information
print(f"Duration: {full_metadata['technical_info']['duration_seconds']} seconds")
print(f"Bitrate: {full_metadata['technical_info']['bitrate_bps']} bps ({full_metadata['technical_info']['bitrate_bps'] // 1000} kbps)")
print(f"Sample Rate: {full_metadata['technical_info']['sample_rate_hz']} Hz")
print(f"Channels: {full_metadata['technical_info']['channels']}")
print(f"File Size: {full_metadata['technical_info']['file_size_bytes']} bytes")

# Access format-specific metadata
print(f"ID3v2 Title: {full_metadata['metadata_format']['id3v2']['title']}")
print(f"Vorbis Title: {full_metadata['metadata_format']['vorbis']['title']}")

# Access header information
print(f"ID3v2 Version: {full_metadata['headers']['id3v2']['version']}")
print(f"ID3v2 Header Size: {full_metadata['headers']['id3v2']['header_size_bytes']}")
print(f"Has ID3v1 Header: {full_metadata['headers']['id3v1']['present']}")
print(f"RIFF Chunk Info: {full_metadata['headers']['riff']['chunk_info']}")

# Access raw metadata details
print(f"Raw ID3v2 Frames: {full_metadata['raw_metadata']['id3v2']['frames']}")
print(f"Raw Vorbis Comments: {full_metadata['raw_metadata']['vorbis']['comments']}")
```

**Parameters:**

- `file_path`: Path to the audio file (str or Path)
- `include_headers`: Whether to include format-specific header information (default: True)
- `include_technical`: Whether to include technical audio information (default: True)

**Returns:**
A comprehensive dictionary containing:

```python
{
    'unified_metadata': {
        # Same as get_unified_metadata() result
        'title': 'Song Title',
        'artists': ['Artist 1', 'Artist 2'],
        'album_name': 'Album Name',
        # ... all other metadata fields
    },
    'technical_info': {
        'duration_seconds': 180.5,
        'bitrate_bps': 320000,
        'sample_rate_hz': 44100,
        'channels': 2,
        'file_size_bytes': 7234567,
        'file_extension': '.mp3',
        'audio_format_name': 'MP3',
        'is_flac_md5_valid': None,  # Only for FLAC files
    },
    'metadata_format': {
        'id3v1': {
            # ID3v1 specific metadata (if present)
            'title': 'Song Title',
            'artist': 'Artist Name',
            # ... other ID3v1 fields
        },
        'id3v2': {
            # ID3v2 specific metadata (if present)
            'title': 'Song Title',
            'artists': ['Artist 1', 'Artist 2'],
            # ... other ID3v2 fields
        },
        'vorbis': {
            # Vorbis specific metadata (if present)
            'title': 'Song Title',
            'artists': ['Artist 1', 'Artist 2'],
            # ... other Vorbis fields
        },
        'riff': {
            # RIFF specific metadata (if present)
            'title': 'Song Title',
            'artist': 'Artist Name',
            # ... other RIFF fields
        }
    },
    'headers': {
        'id3v1': {
            'present': True,
            'position': 'end_of_file',
            'size_bytes': 128,
            'version': '1.1',
            'has_track_number': True
        },
        'id3v2': {
            'present': True,
            'version': '2.3.0',
            'header_size_bytes': 2048,
            'flags': {...},
            'extended_header': {...}
        },
        'vorbis': {
            'present': True,
            'vendor_string': 'reference libFLAC 1.3.2',
            'comment_count': 15,
            'block_size': 4096
        },
        'riff': {
            'present': True,
            'chunk_info': {
                'riff_chunk_size': 7234000,
                'info_chunk_size': 1024,
                'audio_format': 'PCM',
                'subchunk_size': 7232000
            }
        }
    },
    'raw_metadata': {
        'id3v1': {
            'raw_data': b'...',  # Raw 128-byte ID3v1 tag
            'parsed_fields': {...}
        },
        'id3v2': {
            'frames': {...},  # Raw ID3v2 frames
            'raw_header': b'...'
        },
        'vorbis': {
            'comments': {...},  # Raw Vorbis comment blocks
            'vendor_string': '...'
        },
        'riff': {
            'info_chunk': {...},  # Raw RIFF INFO chunk data
            'chunk_structure': {...}
        }
    },
    'format_priorities': {
        'file_extension': '.mp3',
        'reading_order': ['id3v2', 'id3v1'],
        'writing_format': 'id3v2'
    }
}
```

**Use Cases:**

- **Complete file analysis**: Get everything about an audio file in one call
- **Debugging metadata issues**: Inspect raw headers and format-specific data
- **Format migration**: Understand what metadata exists in each format before converting
- **File validation**: Check header integrity and format compliance
- **Metadata forensics**: Analyze metadata structure and detect anomalies
- **Batch processing**: Get comprehensive information for multiple files efficiently

**Examples:**

```python
# Basic usage - get everything
full_info = get_full_metadata("song.mp3")

# Get only metadata without technical details
metadata_only = get_full_metadata("song.mp3", include_technical=False)

# Get only technical info without headers
tech_only = get_full_metadata("song.mp3", include_headers=False)

# Check if file has specific format headers
if full_info['headers']['id3v2']['present']:
    print("File has ID3v2 tags")
    print(f"ID3v2 version: {full_info['headers']['id3v2']['version']}")

# Compare metadata across formats
id3v2_title = full_info['metadata_format']['id3v2'].get('title')
vorbis_title = full_info['metadata_format']['vorbis'].get('title')
if id3v2_title != vorbis_title:
    print("Title differs between ID3v2 and Vorbis")

# Analyze file structure
print(f"File size: {full_info['technical_info']['file_size_bytes']} bytes")
print(f"Metadata overhead: {full_info['headers']['id3v2']['header_size_bytes']} bytes")
print(f"Audio data ratio: {(full_info['technical_info']['file_size_bytes'] - full_info['headers']['id3v2']['header_size_bytes']) / full_info['technical_info']['file_size_bytes'] * 100:.1f}%")
```

### Pre-Update Validation (API Reference)

Before updating metadata, the library provides validation to ensure your data is correct:

**Validation Rules**

The library validates metadata value types and formats when keys are provided as `UnifiedMetadataKey` instances:

- `None` values are allowed and indicate field removal.
- For fields whose expected type is `list[...]` (for example `ARTISTS` or `GENRES_NAMES`) the validator accepts only lists. Each list element is checked against the expected inner type (e.g., `str` for `ARTISTS`).
- For plain types (`str`, `int`, etc.) the value must be an instance of that type.
- On type mismatch the library raises `InvalidMetadataFieldTypeError`.
- **Rating Validation**: See [Rating Validation Rules](#rating-validation-rules) for detailed rules on rating values.
- **Release Date Validation**: See [Release Date Validation Rules](#release-date-validation-rules) for detailed rules on release date formats.

Note: The validator uses the `UnifiedMetadataKey` enum to determine expected types. String keys that match `UnifiedMetadataKey` enum values (e.g., `"title"`, `"artists"`) are automatically converted to enum instances and validated. You can use either string keys or `UnifiedMetadataKey` enum instances - both are validated the same way. Using `UnifiedMetadataKey` enum instances provides better IDE support and type checking.

**`validate_metadata_for_update(unified_metadata, normalized_rating_max_value=None)`**

Validates unified metadata values before updating metadata in a file. Validates that a metadata dictionary contains at least one field and validates types, formats, and values (rating, release date, track number) if present. For detailed validation rules, see [Rating Validation Rules](#rating-validation-rules) and [Release Date Validation Rules](#release-date-validation-rules).

```python
from audiometa import validate_metadata_for_update, UnifiedMetadataKey

# Valid metadata
validate_metadata_for_update({UnifiedMetadataKey.TITLE: "Song Title"})

# Valid: empty string is allowed (represents setting field to empty)
validate_metadata_for_update({UnifiedMetadataKey.TITLE: ""})

# Valid: None value is allowed (represents field removal)
validate_metadata_for_update({UnifiedMetadataKey.TITLE: None})

# Valid: empty list is allowed
validate_metadata_for_update({UnifiedMetadataKey.ARTISTS: []})

# Valid: list with None values is allowed (None values will be filtered during writing)
validate_metadata_for_update({UnifiedMetadataKey.ARTISTS: [None, None]})

# Valid: rating with normalization (see Rating Validation Rules for details)
validate_metadata_for_update({UnifiedMetadataKey.RATING: 50}, normalized_rating_max_value=100)

# Invalid: negative rating
validate_metadata_for_update({UnifiedMetadataKey.RATING: -1})
# Raises: InvalidRatingValueError

# Valid: release date
validate_metadata_for_update({UnifiedMetadataKey.RELEASE_DATE: "2024-01-01"})

# Invalid: invalid release date format
validate_metadata_for_update({UnifiedMetadataKey.RELEASE_DATE: "2024/01/01"})
# Raises: InvalidMetadataFieldFormatError
```

### Writing Metadata (API Reference)

**For comprehensive writing metadata documentation**, including writing strategies, format handling, unsupported field management, and advanced examples, see the dedicated guide:

**[Writing Metadata Guide](docs/WRITING_METADATA.md)**

For validation before writing, see [Pre-Update Validation (API Reference)](#pre-update-validation-api-reference).

#### Metadata Dictionary Structure

When writing, metadata should be provided as a dictionary with keys corresponding to unified metadata fields defined in `UnifiedMetadataKey`.

```python
metadata = {
    UnifiedMetadataKey.TITLE: 'Song Title',
    UnifiedMetadataKey.ARTISTS: ['Artist 1', 'Artist 2'],
    UnifiedMetadataKey.ALBUM: 'Album Name',
    UnifiedMetadataKey.YEAR: 2024,
    UnifiedMetadataKey.GENRES_NAMES: ['Rock'],
    UnifiedMetadataKey.RATING: 85,
    UnifiedMetadataKey.BPM: 120,
    UnifiedMetadataKey.COMMENT: 'Some comments here',
}
```

**`update_metadata(file_path, metadata, **options)`\*\*

Updates metadata in a file. The function automatically calls pre-update validation on the metadata before writing (see [Pre-Update Validation](#pre-update-validation) for validation rules).

**Note:** `file_path` can be a string or `pathlib.Path` object.

```python
from audiometa import update_metadata

# Basic wr