Skip to content

ketanchoyal/metadata_audio

Repository files navigation

metadata_audio Pub

Buy Me A Coffee

A Dart-native audio metadata parser library that provides comprehensive metadata extraction for various audio formats. This package is a port of the TypeScript music-metadata library, maintaining architecture parity with a Dart-first TDD approach.

Features

  • Multi-format support: MP3, FLAC, Ogg Vorbis, MP4, WAV, AIFF, APE, ASF, Matroska, and more
  • Comprehensive metadata: ID3, Vorbis comments, iTunes tags, and other metadata standards
  • Chapter/Track boundaries: Extract embedded chapter markers, cue points, and track boundaries (MP4, FLAC, Ogg, WAV, Matroska)
  • Live metadata observation: Receive incremental format, common, and chapter updates while parsing is still in progress
  • Smart URL parsing: Automatically selects optimal download strategy for remote files
  • Streaming support: Parse metadata without loading entire files into memory
  • Type-safe: Full Dart type safety with comprehensive error handling
  • Well-tested: Extensive test suite with TDD principles
  • TypeScript parity: Ported from music-metadata with exact output compatibility
  • Chapter downloading: Extract individual audiobook chapters as standalone playable AAC files with parallel HTTP downloads

Screenshots

Local File Parsing Remote URL Parsing
Local file parsing with chapter byte offsets Remote URL parsing with chapter downloads

Getting started

Install

Add this to your pubspec.yaml:

dependencies:
  metadata_audio: any

Then run:

dart pub get

Prerequisites

  • Dart SDK 3.10.7 or higher

Usage

Initialization

The library auto-initializes with all format loaders on first use. No manual setup required.

import 'package:metadata_audio/metadata_audio.dart';

// Just use it - auto-initializes on first call
final metadata = await parseFile('/path/to/audio.mp3');

Custom Initialization (Optional)

For custom configurations, you can initialize manually:

import 'package:metadata_audio/metadata_audio.dart';

// Create a custom registry with only specific formats
final registry = ParserRegistry()
  ..register(MpegLoader())
  ..register(FlacLoader());

initializeParserFactory(ParserFactory(registry));

// Now parsing will only use registered formats
final metadata = await parseFile('/path/to/audio.mp3');

Parse Local Files

import 'package:metadata_audio/metadata_audio.dart';

// Parse a file
final metadata = await parseFile('/path/to/audio.mp3');

// Access common metadata
print('Title: ${metadata.common.title}');
print('Artist: ${metadata.common.artist}');
print('Album: ${metadata.common.album}');
print('Duration: ${metadata.format.duration}');

Parse from URL (Smart)

The parseUrl() function automatically selects the best strategy based on file size and server capabilities:

// Small files (< 5MB): Full download
// Medium files (5-50MB): Header-only download (~256KB)
// Large files (> 50MB): Random access with on-demand fetching

final metadata = await parseUrl('https://example.com/audio.m4a');

// With callback to see selected strategy
final metadata = await parseUrl(
  'https://example.com/large-audio.m4a',
  onStrategySelected: (strategy, reason) {
    print('Using $strategy because $reason');
  },
);

Parse Bytes

final bytes = await File('/path/to/audio.flac').readAsBytes();
final metadata = await parseBytes(
  bytes,
  fileInfo: FileInfo(mimeType: 'audio/flac'),
);

Chapter Extraction

final metadata = await parseFile(
  '/path/to/audiobook.m4a',
  options: const ParseOptions(includeChapters: true),
);

if (metadata.format.chapters != null) {
  for (final chapter in metadata.format.chapters!) {
    print('${chapter.title}: ${chapter.start}ms - ${chapter.end}ms');
  }
}

Observe Metadata While Parsing

Use ParseOptions.observer to receive incremental updates while parsing is still running.

final metadata = await parseFile(
  '/path/to/audiobook.m4b',
  options: ParseOptions(
    includeChapters: true,
    observer: (event) {
      final tag = event.tag;
      if (tag == null) return;

      if (tag.id == MetadataFormatId.container) {
        print('Container discovered: ${tag.value}');
      }

      if (tag.id == MetadataCommonId.title) {
        print('Title discovered: ${tag.value}');
      }

      if (tag.id == MetadataFormatId.chapters) {
        final chapters = tag.value as List<Chapter>?;
        print('Chapter count so far: ${chapters?.length ?? 0}');
      }
    },
  ),
);

Observer events include a snapshot of the accumulated metadata at that point:

ParseOptions(
  observer: (event) {
    final snapshot = event.metadata;
    print('Current title: ${snapshot?.common.title}');
    print('Current duration: ${snapshot?.format.duration}');
  },
);

IDs are extension types, so you can use built-in constants for known fields or create your own dynamically for unknown upstream keys:

const titleId = MetadataCommonId.title;
const customId = MetadataCommonId<Object?>('my-custom-tag');

print(titleId == const MetadataCommonId<Object?>('title')); // true
print(customId.path); // my-custom-tag

Supported Formats

Format Status Notes
MP3 ✅ Complete ID3v1, ID3v2.2/2.3/2.4, MPEG audio, Lyrics3, ID3v2 chapters (CHAP/CTOC)
FLAC ✅ Complete Vorbis comments, picture metadata, CUESHEET block → chapters
OGG ✅ Complete Vorbis, Opus, Speex, FLAC-in-Ogg, Vorbis chapter tags (CHAPTER###)
MP4/M4A ✅ Complete iTunes atoms, chapter tracks (chap/tref), QuickTime chapters
WAV ✅ Complete RIFF, LIST-INFO, BWF, cue points + adtl labels, ltxt chunks
AIFF ✅ Complete AIFF-C, ID3, chunks
APE ✅ Complete APEv2 tags, Monkey's Audio header
ASF/WMA ✅ Complete Windows Media metadata
Matroska ✅ Complete MKV, WebM tags, EditionEntry chapters
Musepack ✅ Complete SV7, SV8
WavPack ✅ Complete APEv2 tags
DSD ✅ Complete DSF, DSDIFF

URL Parsing Strategies

When parsing from URLs, the library automatically selects the most efficient strategy:

Strategy File Size Method Use Case
fullDownload ≤ 5MB Download entire file Small files, any server
headerOnly 5-50MB Download first 256KB Medium files with Range support
randomAccess > 50MB On-demand chunk fetching Large files with Range support

Manual Strategy Selection

import 'package:metadata_audio/metadata_audio.dart';

// Force specific strategy
final metadata = await parseUrl(
  url,
  strategy: ParseStrategy.headerOnly, // or fullDownload, randomAccess
);

// Detect strategy without parsing
final info = await detectStrategy(url);
print('Strategy: ${info.strategy}');
print('File size: ${info.fileSize}');
print('Range support: ${info.supportsRange}');

HTTP Tokenizers

For advanced use cases, you can use the underlying tokenizers directly:

// Full download - works with any server
final tokenizer = await HttpTokenizer.fromUrl(url);

// Header-only - requires Range support
final tokenizer = await RangeTokenizer.fromUrl(url);

// Random access - on-demand fetching
final tokenizer = await RandomAccessTokenizer.fromUrl(url);

Chapter/Boundary Extraction

The library supports extracting embedded track/disk boundaries and chapter markers from various audio formats. This is particularly useful for audiobooks, podcasts, DJ mixes, and live recordings.

Supported Chapter Sources

Format Source Description
MP3 ID3v2 CHAP/CTOC ID3v2.3/2.4 chapter frames
MP4/M4A Chapter track QuickTime chap track reference with sample tables
MP4/M4A iTunes chapters Text track chapters in M4B audiobooks
FLAC CUESHEET FLAC native CUESHEET metadata block
FLAC Vorbis comments CHAPTER### and CHAPTER###NAME tags
Ogg Vorbis comments CHAPTER### timestamp tags
WAV RIFF cue + adtl cue chunk with labl/ltxt in LIST/adtl
Matroska EditionEntry MKV chapter atoms (EditionEntry)

Chapter Model

class Chapter {
  final String? id;
  final String title;
  final int start;          // Start time in milliseconds
  final int? end;           // End time in milliseconds
  final int? byteOffset;    // Start byte offset in file
  final int? endByteOffset; // End byte offset in file
  final int? timeScale;     // Time scale (units per second)
}

Chapter Download

Extract individual chapters from audiobooks as standalone playable AAC files. Supports both local files and remote URLs with parallel HTTP downloads for maximum throughput.

import 'package:metadata_audio/metadata_audio.dart';

final result = await ChapterDownloader.downloadChapter(
  originalUrl: 'https://example.com/audiobook.m4b',
  chapterStartMs: chapter.start,
  chapterEndMs: chapter.end!,
  outputPath: '/path/to/chapter_1.aac',
  parallelChunks: 4, // Number of parallel HTTP connections
  onPhase: (phase) {
    // ChapterDownloadPhase: connecting, analyzing,
    // resolvingSamples, downloading, writing
    print('Phase: $phase');
  },
  onProgress: (progress) {
    print('Download: ${(progress * 100).toStringAsFixed(1)}%');
  },
);

if (result.isSuccess) {
  print('Saved to: ${result.outputPath}');
} else {
  print('Failed: ${result.error}');
}

The ChapterDownloadResult provides structured success/failure information:

Field Type Description
isSuccess bool Whether the download completed successfully
outputPath String? Output file path on success
error String? Error message on failure

Development

For information on contributing to this package, see CONTRIBUTING.md.

Development Setup

# Get dependencies
dart pub get

# Run tests
dart test

# Run analysis
dart analyze

# Format code
dart format lib/ test/

License

MIT - See LICENSE file for details

Upstream Compatibility

This package is a Dart port of the TypeScript music-metadata library, maintaining exact output compatibility. The goal is 1:1 parity with the upstream library for all supported formats.

TypeScript Parity Status

Feature Status Notes
Core metadata extraction ✅ Complete All formats match upstream output
Chapter extraction ✅ Complete All chapter sources supported
Metadata observer events ✅ Complete Incremental format/common/chapter updates during parsing
Tag mapping ✅ Complete Common tag normalization
Duration calculation ✅ Complete MPEG Xing/Info, format-specific logic
Bitrate calculation ✅ Complete Float precision matches upstream
Picture extraction ✅ Complete Cover art from all formats
URL parsing ✅ Complete Smart strategy selection

To verify parity, run the comparison tool:

dart tool/compare_metadata.dart > comparison/output_dart.json
cd comparison && npm run compare

Additional Information


For detailed architecture and design decisions, see the project documentation in /docs.

About

A Dart-native audio metadata parser library that provides comprehensive metadata extraction for various audio formats including MP3, FLAC, Ogg, MP4, WAV, AIFF, APE, ASF, Matroska, and more. Ported from music-metadata with architecture parity and TDD approach.

Resources

License

Contributing

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages