haudiotagger 1.2.6 copy "haudiotagger: ^1.2.6" to clipboard
haudiotagger: ^1.2.6 copied to clipboard

Read and write audio metadata in Flutter. Supports MP3, FLAC, OGG, MP4, WAV, AIFF, and more. Powered by Rust for fast, reliable performance.

hAudiotagger

hAudiotagger

Rust-powered audio metadata for Flutter

pub.dev CI MIT License


Read, write, and edit audio metadata across Android, iOS, Linux, macOS, Windows, and Web. Built on lofty via flutter_rust_bridge.

hAudiotagger

Features #

Feature Platforms
Read / write metadata (title, artist, album, art, lyrics...) All
Partial updates — change one field without touching others All
Batch operations with progress callbacks All
Custom tags (TXXX, Vorbis) All
ID3v2 version control (v2.3 / v2.4) All
Strip ID3v1 tags All
Audio properties (duration, bitrate, codec...) All
Tag format detection All
Validate metadata (missing fields, invalid values) All
Normalize metadata (trim, Unicode, whitespace) All
Copy metadata between files All
Merge tags with configurable strategy All
ReplayGain support (track/album gain/peak) All

Note

On the web, browsers cannot access arbitrary local files via file paths due to security sandboxing. Use the *FromBytes variants (e.g. readFromBytes, writeToBytes) which accept and return raw byte arrays. On native platforms (Android, iOS, Linux, macOS, Windows), both file path and bytes APIs are available. Also see Web Setup.

Install #

dependencies:
  haudiotagger: ^1.2.6

Quick Start #

import 'package:haudiotagger/haudiotagger.dart';

// Read
final tag = await Haudiotagger.read('/path/to/song.mp3');
print(tag?.title);

// Write
await Haudiotagger.write('/path/to/song.mp3', Tag(
  title: 'My Song',
  artist: 'Artist',
  album: 'Album',
));

// Update (preserves other fields)
await Haudiotagger.update('/path/to/song.mp3', TagChanges(
  album: 'New Album',
));

// Batch
final result = await Haudiotagger.batchWrite(paths, tag);

Supported Formats #

Format Tags
MP3 ID3v2, ID3v1, APE
FLAC Vorbis Comments, ID3v2*
MP4 / M4A iTunes ilst
Ogg Vorbis Vorbis Comments
Opus Vorbis Comments
AAC ID3v2, ID3v1
WAV ID3v2, RIFF INFO
AIFF ID3v2, Text Chunks
APE APE, ID3v2*, ID3v1
WavPack APE, ID3v1

* The tag will be read only, due to lack of official support


Usage #

Read Metadata #

// From file path (native)
final tag = await Haudiotagger.read('/path/to/song.mp3');

// From bytes (web + native)
final tag = await Haudiotagger.readFromBytes(fileBytes);

Write Metadata #

// To file path (native)
await Haudiotagger.write('/path/to/song.mp3', Tag(
  title: 'Song Title',
  trackArtist: 'Artist',
  album: 'Album',
  year: 2024,
));

// To bytes (web + native)
final modified = await Haudiotagger.writeToBytes(fileBytes, tag);

Update Metadata #

Only the fields you pass are changed — everything else stays intact.

await Haudiotagger.update('/path/to/song.mp3', TagChanges(
  title: 'New Title',
  genre: 'Jazz',
));

// Bytes variant
final modified = await Haudiotagger.updateFromBytes(fileBytes, changes);

Batch Operations #

// Write same tag to multiple files
final result = await Haudiotagger.batchWrite(paths, tag);

// Apply same changes to multiple files
await Haudiotagger.batchUpdateChanges(paths, TagChanges(album: 'New Album'));

// Per-file callback with progress
await Haudiotagger.batchUpdate(
  paths,
  onProgress: (p) => print('${(p.percent * 100).round()}%'),
  (path, current) => current.copyWith(trackNumber: paths.indexOf(path) + 1),
);

// Web/bytes variants available
await Haudiotagger.batchWriteFromBytes(byteArrays, tag);

Custom Tags #

Read, write, and remove format-specific custom tags (ID3v2 TXXX frames, Vorbis non-standard keys).

// Read
final custom = await Haudiotagger.getCustomTags('/path/to/song.mp3');
// {'MY_FIELD': 'some value'}

// Write
await Haudiotagger.setCustomTag('/path/to/song.mp3', 'MY_FIELD', 'some value');

// Remove
await Haudiotagger.removeCustomTag('/path/to/song.mp3', 'MY_FIELD');

// Bytes variants: getCustomTagsFromBytes, setCustomTagFromBytes, removeCustomTagFromBytes

ID3v2 Version Control #

// Detect version
final version = await Haudiotagger.getId3v2Version('/path/to/song.mp3');
// Id3v2Version.v3 or Id3v2Version.v4

// Convert to ID3v2.3 (widely compatible)
await Haudiotagger.convertId3v2('/path/to/song.mp3', Id3v2Version.v3);

// Convert to ID3v2.4 (latest spec)
await Haudiotagger.convertId3v2('/path/to/song.mp3', Id3v2Version.v4);

// Bytes variants: getId3v2VersionFromBytes, convertId3v2FromBytes

Remove ID3v1 #

await Haudiotagger.removeId3v1('/path/to/song.mp3');
final cleaned = await Haudiotagger.removeId3v1FromBytes(bytes);

Audio Properties #

final props = await Haudiotagger.readProperties('/path/to/song.mp3');
// props.duration, props.bitrate, props.sampleRate, props.codec, ...

// Bytes variant
await Haudiotagger.readPropertiesFromBytes(bytes);

Diff Tags #

Compare two tags to see exactly what changed — useful for confirmation dialogs and undo previews.

final oldTag = await Haudiotagger.read('/path/to/song.mp3');
final newTag = oldTag?.copyWith(title: 'New Title', year: 2025);

final diff = Haudiotagger.diff(oldTag!, newTag!);

print(diff.length);     // 2
print(diff.changes[0]); // title: Old Title → New Title

for (final change in diff.changes) {
  switch (change.type) {
    case ChangeType.added:
      print('Added ${change.field.name}');
    case ChangeType.updated:
      print('Updated ${change.field.name}');
    case ChangeType.removed:
      print('Removed ${change.field.name}');
  }
}

Detect Tag Formats #

final formats = await Haudiotagger.getTagFormats('/path/to/song.mp3');
// ['ID3v2', 'ID3v1']

Inspect File #

One call to get everything: format, tag format, properties, metadata, pictures, and file size.

final info = await Haudiotagger.inspect('/path/to/song.mp3');

print(info.format);      // 'MP3'
print(info.tagFormat);   // 'ID3v2'
print(info.size);        // 4812345
print(info.metadata?.title);
print(info.properties.duration);
print(info.pictures.length);

// Bytes variant
final info = await Haudiotagger.inspectFromBytes(bytes);

Validate Metadata #

Detect issues before publishing: missing fields, invalid track/disc numbers, bad BPM or year values.

final result = await Haudiotagger.validate('/path/to/song.mp3');

print(result.isValid);  // false if any errors

for (final issue in result.issues) {
  print('${issue.severity.name}: ${issue.field} — ${issue.message}');
}
// Error: track_number — Track number (5) exceeds total (3)
// Warning: pictures — Missing artwork

// Bytes variant
final result = await Haudiotagger.validateFromBytes(bytes);

// Validate a Tag directly
final result = await Haudiotagger.validateTag(tag);

Normalize Metadata #

Clean up whitespace, normalize Unicode, and remove empty values.

// Default options (trim, normalize whitespace, NFKC, remove empty)
final tag = await Haudiotagger.normalize('/path/to/song.mp3');
await Haudiotagger.write('/path/to/song.mp3', tag);

// Custom options
final tag = await Haudiotagger.normalizeTag(
  currentTag,
  options: NormalizeOptions(
    trimValues: true,
    normalizeWhitespace: true,
    normalizeUnicode: false,  // keep original Unicode
    removeEmptyValues: true,
  ),
);

// Bytes variant
final bytes = await Haudiotagger.normalizeBytes(fileBytes);

Copy Metadata #

Copy metadata between files with fine-grained control.

// Copy all metadata from FLAC to MP3
await Haudiotagger.copyMetadata('album.flac', 'album.mp3');

// Copy without artwork
await Haudiotagger.copyMetadata(
  'source.mp3',
  'dest.mp3',
  includeArtwork: false,
);

// Copy without lyrics or custom tags
await Haudiotagger.copyMetadata(
  'source.flac',
  'dest.mp3',
  includeLyrics: false,
  includeCustomTags: false,
);

// Bytes variant (web + native)
final result = await Haudiotagger.copyMetadataFromBytes(
  sourceBytes,
  destBytes,
  includeArtwork: true,
);

Merge Tags #

Combine two tags with configurable priority strategy.

final tagA = await Haudiotagger.read('song_a.mp3');
final tagB = await Haudiotagger.read('song_b.mp3');

// Default: preferFirstNonEmpty (tagA wins unless empty)
final merged = Haudiotagger.mergeTags(tagA, tagB);

// Explicit strategy
final merged = Haudiotagger.mergeTags(
  tagA,
  tagB,
  strategy: MergeStrategy.preferSecond,
);

await Haudiotagger.write('merged.mp3', merged);

Strategies: preferFirst, preferSecond, preferFirstNonEmpty, preferSecondNonEmpty

ReplayGain #

Read and write ReplayGain tags for volume normalization.

final tag = await Haudiotagger.read('song.mp3');

// Read
print(tag.replayGainTrackGain);  // "-6.43"
print(tag.replayGainAlbumPeak);  // "0.981201"

// Write
await Haudiotagger.write('song.mp3', tag.copyWith(
  replayGainTrackGain: '-6.43',
  replayGainTrackPeak: '0.981201',
  replayGainAlbumGain: '-7.12',
  replayGainAlbumPeak: '0.995000',
));

// Partial update
await Haudiotagger.update('song.mp3', TagChanges(
  replayGainTrackGain: '-6.43',
));

⚡ Performance Benchmarks #

Benchmarks were performed using hAudiotagger 1.2.6 on a Linux system with 500 MP3 files (~8 MB each).

Benchmark Environment #

Property Value
hAudiotagger 1.2.6
OS Linux (Arch)
CPU AMD Ryzen 5 4500U
RAM 16 GB DDR4
Storage 256 GB NVMe SSD
Dart v3.13.2
Rust v1.98.0
lofty v0.25.1
flutter_rust_bridge v2.13.0

Bytes API (Web + Native) #

Operation 1 File 10 Files 50 Files 100 Files
Read 59 f/s 63 f/s 78 f/s 76 f/s
Update 28 f/s 22 f/s 20 f/s 20 f/s
Batch Write 19 f/s 26 f/s 30 f/s 30 f/s
Custom Tag 24 f/s 18 f/s 22 f/s 22 f/s

File-Path API (Native, Rayon Parallel) #

Operation 1 File 10 Files 50 Files 100 Files
Read 500 f/s 1,000 f/s 1,064 f/s 1,282 f/s
Update 26 f/s 30 f/s 63 f/s 71 f/s
Batch Write 29 f/s 36 f/s 198 f/s 197 f/s
Batch Update 25 f/s 36 f/s 202 f/s 197 f/s

Note

All values in files per second (f/s). File-path APIs are significantly faster because Rust reads files directly without FFI data transfer. Bytes APIs require serializing/deserializing all data across the Dart↔Rust boundary, which becomes the bottleneck at scale. Use file-path APIs on native for maximum throughput; bytes APIs are designed for web where file paths are unavailable.


Web Setup #

The host page must be cross-origin isolated for WASM shared memory. Add these headers when serving:

Cross-Origin-Opener-Policy: same-origin
Cross-Origin-Embedder-Policy: require-corp

For local development:

flutter run -d chrome \
  --web-header=Cross-Origin-Opener-Policy=same-origin \
  --web-header=Cross-Origin-Embedder-Policy=require-corp

API Reference #

All methods
Method Returns Platform
read(path) Tag? native
readFromBytes(bytes) Tag? all
write(path, tag) void native
writeToBytes(bytes, tag) Uint8List all
update(path, changes) void native
updateFromBytes(bytes, changes) Uint8List all
remove(path, fields) void native
removeFromBytes(bytes, fields) Uint8List all
clear(path) void native
clearFromBytes(bytes) Uint8List all
readProperties(path) AudioProperties native
readPropertiesFromBytes(bytes) AudioProperties all
getTagFormats(path) List<String> native
getTagFormatsFromBytes(bytes) List<String> all
inspect(path) AudioFileInfo native
inspectFromBytes(bytes) AudioFileInfo all
getCustomTags(path) Map<String, String> native
getCustomTagsFromBytes(bytes) Map<String, String> all
setCustomTag(path, key, value) void native
setCustomTagFromBytes(bytes, key, value) Uint8List all
removeCustomTag(path, key) void native
removeCustomTagFromBytes(bytes, key) Uint8List all
getId3v2Version(path) Id3v2Version? all
getId3v2VersionFromBytes(bytes) Id3v2Version? all
convertId3v2(path, version) void all
convertId3v2FromBytes(bytes, version) Uint8List all
removeId3v1(path) void all
removeId3v1FromBytes(bytes) Uint8List all
validate(path) ValidationResult native
validateFromBytes(bytes) ValidationResult all
validateTag(tag) ValidationResult all
normalize(path) Tag native
normalizeBytes(bytes) Uint8List all
normalizeTag(tag, {options}) Tag all
copyMetadata(src, dst, {includeArtwork, includeLyrics, includeCustomTags}) void native
copyMetadataFromBytes(srcBytes, dstBytes, {includeArtwork, includeLyrics, includeCustomTags}) Uint8List all
mergeTags(tagA, tagB, {strategy}) Tag all
batchWrite(paths, tag) BatchResult native
batchUpdateChanges(paths, changes) BatchResult native
batchUpdate(paths, updater, {onProgress}) BatchResult native
batchWriteFromBytes(bytes, tag) BatchBytesResult all
batchUpdateChangesFromBytes(bytes, changes) BatchBytesResult all
batchUpdateFromBytes(bytes, updater, {onProgress}) BatchBytesResult all
Data types

Tag #

Field Type Notes
title String?
trackArtist String?
album String?
albumArtist String?
year int?
genre String?
trackNumber int?
trackTotal int?
discNumber int?
discTotal int?
lyrics String?
comment String?
bpm double?
duration int? Read-only
pictures List<Picture>
replayGainTrackGain String? e.g. "-6.43"
replayGainTrackPeak String? e.g. "0.981201"
replayGainAlbumGain String? e.g. "-7.12"
replayGainAlbumPeak String? e.g. "0.995000"

TagChanges #

Same fields as Tag, all optional. Only set fields are applied.

Picture #

Field Type
pictureType PictureType
mimeType MimeType?
bytes Uint8List

AudioFileInfo #

Field Type Notes
format String e.g. MP3, FLAC
tagFormat String e.g. ID3v2, VorbisComments
properties AudioProperties Technical details
metadata Tag? All metadata fields
pictures List<Picture> Embedded artwork
size BigInt File size in bytes

AudioProperties #

Field Type
duration Duration?
durationMicros int?
bitrate int?
sampleRate int?
channels int?
bitsPerSample int?
codec String
containerFormat String
lossless bool
bitrateMode BitrateMode
fileSize BigInt?

Id3v2Version #

v2 (not supported for writing), v3, v4

BatchResult #

Field Type
successes int
failures int
errors List<(String, String)>

BatchBytesResult #

Field Type
results List<Uint8List>
failures int
errors List<(int, String)>

BatchProgress #

Field Type
completed int
total int
percent double

MetadataDiff #

Field Type
changes List<MetadataChange>
length int
isEmpty bool

MetadataChange<T> #

Field Type
field TagField
oldValue T?
newValue T?
type ChangeType

ChangeType #

added, updated, removed

ValidationResult #

Field Type
issues List<ValidationIssue>
isValid bool (getter)

ValidationIssue #

Field Type
field String
message String
severity ValidationSeverity

ValidationSeverity #

error, warning

NormalizeOptions #

Field Type Default
trimValues bool true
normalizeWhitespace bool true
normalizeUnicode bool true
removeEmptyValues bool true

MergeStrategy #

Value Behavior
preferFirst tagA wins for all fields
preferSecond tagB wins for all fields
preferFirstNonEmpty tagA wins unless empty, then tagB
preferSecondNonEmpty tagB wins unless empty, then tagA

Requirements #

  • Flutter >= 3.0.0
  • Dart SDK >= 3.6.0

License #

hAudiotagger is open-source software licensed under the MIT License.

See the LICENSE file for more information.

❤️ Support #

If hAudiotagger helps you build something cool, consider:

  • ⭐ Starring the repository
  • 🐛 Reporting bugs
  • 💡 Suggesting improvements
  • 🤝 Contributing code
  • 📦 Sharing the package with other Flutter developers

Every bit of support helps keep the project moving forward.

Made with ❤️ and 🦀

12
likes
160
points
909
downloads

Documentation

API reference

Publisher

verified publisherhirdaya-shrestha.com.np

Weekly Downloads

Read and write audio metadata in Flutter. Supports MP3, FLAC, OGG, MP4, WAV, AIFF, and more. Powered by Rust for fast, reliable performance.

Repository (GitHub)
View/report issues
Contributing

License

MIT (license)

Dependencies

ffi, flutter, flutter_rust_bridge, freezed_annotation, plugin_platform_interface

More

Packages that depend on haudiotagger

Packages that implement haudiotagger