flutter_taglib 1.1.1 copy "flutter_taglib: ^1.1.1" to clipboard
flutter_taglib: ^1.1.1 copied to clipboard

A high-performance Flutter plugin wrapping TagLib using Dart FFI and Native Assets to read/write audio metadata and properties.

flutter_taglib #

pub package License

A high-performance, feature-rich Flutter plugin wrapping TagLib using Dart FFI and Native Assets. It allows you to read and write audio metadata (including album cover art) and extract technical audio properties across various platforms.

Note

This package uses Flutter's modern Native Assets feature. The native TagLib C++ code is compiled directly during your application build process, ensuring seamless compilation and optimization without requiring precompiled binaries.


Features #

  • Wide Format Support: Read and write tags for MP3, FLAC, M4A (AAC/ALAC), WAV, OGG (Vorbis), and other formats supported by TagLib.
  • Full Tag Editing: Read and modify standard tag fields: Title, Artist, Album, Genre, Year, Track Number, and Comment.
  • Album Art (Cover) Management:
    • Check if a file contains cover art (hasCover).
    • Retrieve cover art bytes (coverData) and its MIME type (coverMimeType).
    • Set or update cover art, or remove it entirely.
  • Audio Technical Properties: Extract read-only properties:
    • Duration (as Dart Duration)
    • Bitrate (kbps) and Bitrate Mode (CBR, VBR, or Unknown)
    • Sample Rate (Hz)
    • Channels (Mono, Stereo, etc.)
    • Get a structured AudioInfo object containing all detailed audio properties.
  • Scoped Storage & SAF Support (Android):
    • Open files using Unix File Descriptors (openFd) to bypass Scoped Storage restrictions.
    • Automatically request write permissions using openAsync or requestWriteAccess().
  • Selectable Platform Support: Avoid compilation conflicts by selectively enabling or disabling platform builds using a simple YAML configuration file.

Installation #

Add flutter_taglib to your pubspec.yaml:

dependencies:
  flutter_taglib: ^1.0.0

If you are developing against the local repository, you can keep using a path dependency during development:

dependencies:
  flutter_taglib:
    path: /path/to/flutter_taglib

Usage Guide #

1. Basic Reading #

Open an audio file and retrieve its metadata and technical properties:

import 'package:flutter_taglib/flutter_taglib.dart';

void readMetadata(String filePath) {
  // Check if the native library is loaded and supported on this platform
  if (!TagLibFile.isSupported) {
    print('TagLib is not supported on this platform.');
    return;
  }

  // Open the file
  final file = TagLibFile.open(filePath);
  if (file == null) {
    print('Failed to open file: $filePath');
    return;
  }

  try {
    // Read tag fields
    print('Title:       ${file.title}');
    print('Artist:      ${file.artist}');
    print('Album:       ${file.album}');
    print('Genre:       ${file.genre}');
    print('Year:        ${file.year}');
    print('Track:       ${file.track}');
    print('Comment:     ${file.comment}');

    // Read audio properties
    print('Duration:     ${file.duration}');
    print('Bitrate:      ${file.bitrate} kbps');
    print('Bitrate Mode: ${file.bitrateMode}'); // 'CBR', 'VBR', or 'Unknown'
    print('Sample Rate:  ${file.sampleRate} Hz');
    print('Channels:     ${file.channels}');

    // Or retrieve all detailed audio properties as a structured object
    final audioInfo = file.audioInfo;
    print('Audio Info:   $audioInfo');
  } finally {
    // Always close the file to release native resources!
    file.close();
  }
}

2. Modifying Metadata #

To update metadata, modify the fields and call save().

Important

On Android, modifying files in Scoped Storage may require write permission. You must request write access before making changes.

import 'package:flutter_taglib/flutter_taglib.dart';

Future<void> updateMetadata(String filePath) async {
  // Use openAsync with writeAccess: true to automatically request permissions on Android
  final file = await TagLibFile.openAsync(filePath, writeAccess: true);
  if (file == null) {
    print('Failed to open file or permission denied.');
    return;
  }

  try {
    // Edit tag fields
    file.title = 'My New Song Title';
    file.artist = 'Famous Artist';
    file.album = 'New Album';
    file.year = 2026;
    file.track = 3;

    // Save changes back to the file
    final success = file.save();
    if (success) {
      print('Metadata saved successfully!');
    } else {
      print('Failed to save metadata.');
    }
  } finally {
    file.close();
  }
}

3. Cover Art (Album Art) #

Extract, update, or remove cover art:

import 'dart:typed_data';
import 'package:flutter_taglib/flutter_taglib.dart';

void handleCoverArt(TagLibFile file, Uint8List? newCoverBytes) {
  // 1. Read Cover Art
  if (file.hasCover) {
    final Uint8List? coverBytes = file.coverData;
    final String? mimeType = file.coverMimeType;
    print('Cover found! MIME type: $mimeType, Size: ${coverBytes?.length} bytes');
  } else {
    print('No cover art found.');
  }

  // 2. Set or Update Cover Art
  if (newCoverBytes != null) {
    file.setCover(data: newCoverBytes, mimeType: 'image/jpeg');
    file.save();
  }

  // 3. Remove Cover Art
  file.setCover(data: null); // Pass null to delete the cover art
  file.save();
}

4. iOS File and Directory Access #

On iOS, file and directory access uses Apple's security-scoped resource model. The plugin wraps that lifecycle in typed helpers so you do not need to work with raw MethodChannel maps or manage most of the native details yourself.

There are two separate flows:

  1. Audio file editing uses a writable working copy.
  2. Directory access uses security-scoped bookmarks that can be restored later.

Audio File Editing Flow

When you pick an audio file with TagLibFile.pickAudioFileForEditing(), the plugin:

  • Presents an iOS document picker.
  • Calls startAccessingSecurityScopedResource() for the selected file.
  • Creates a writable working copy in temporary storage.
  • Returns a PickedAudioFile with both the working copy path and the original file path.

After you edit metadata and call save(), call PickedAudioFile.commit() to copy the working copy back to the original file.

final picked = await TagLibFile.pickAudioFileForEditing();
if (picked != null) {
  final file = await TagLibFile.openAsync(picked.path);
  if (file != null) {
    try {
      file.title = 'Updated Title';
      file.save();
      await picked.commit();
    } finally {
      file.close();
    }
  }
}

Directory Authorization Flow

When you pick a directory with TagLibFile.pickAuthorizedDirectory(), the plugin:

  • Presents an iOS folder picker.
  • Calls startAccessingSecurityScopedResource() for the selected directory.
  • Creates a security-scoped bookmark.
  • Stores that bookmark in UserDefaults under the key flutter_taglib.directoryBookmarks.

That means access can be restored later, even after the app restarts, as long as iOS still accepts the bookmark.

Use the returned AuthorizedDirectory as a disposable handle:

final directory = await TagLibFile.pickAuthorizedDirectory();
if (directory != null) {
  try {
    print('Authorized directory: ${directory.path}');
  } finally {
    await directory.dispose();
  }
}

Restoring Access

If you need to restore a previously authorized directory later, call TagLibFile.restoreAuthorizedDirectory(path).

The plugin will:

  • Look up the stored bookmark for the path or one of its ancestor directories.
  • Resolve the bookmark with URL(resolvingBookmarkData:).
  • Call startAccessingSecurityScopedResource() again.
  • Refresh the bookmark if iOS reports it is stale.
final restored = await TagLibFile.restoreAuthorizedDirectory(directoryPath);
if (restored != null) {
  try {
    print('Restored access to: ${restored.path}');
  } finally {
    await restored.dispose();
  }
}

Notes

  • pickAudioFileForEditing() is for editing individual files, not for persistent directory access.
  • pickAuthorizedDirectory() is the entry point for reusable folder access.
  • Always call dispose() on AuthorizedDirectory when you are done.
  • The bookmark data is persisted on-device via UserDefaults, not in your Dart code.

5. Android Scoped Storage & File Descriptors #

Android 10+ enforces Scoped Storage. Directly opening a filepath (like /storage/emulated/0/...) in C++ write mode will fail unless permissions are handled. flutter_taglib offers two ways to handle this:

Option A: Automatic Permission Requests (openAsync & requestWriteAccess)

Use openAsync with writeAccess: true to trigger the system prompt when necessary. If you already have a TagLibFile open in read-only mode, you can request write access before saving:

final file = TagLibFile.open(path);
// ... do some read operations ...

// Reopens the file natively with write permissions
final hasWriteAccess = await file.requestWriteAccess();
if (hasWriteAccess) {
  file.title = 'New Title';
  file.save();
}
file.close();

Option B: Opening File Descriptors (openFd)

If you obtain a file descriptor through the Android Storage Access Framework (SAF) or MediaStore, you can pass the file descriptor directly to TagLibFile.openFd:

// E.g., obtained via MethodChannel or a document picker in Android
int fd = androidFileDescriptor; 
final file = TagLibFile.openFd(fd, path: filePath);

if (file != null) {
  try {
    print(file.title);
    file.title = 'Updated Title via FD';
    file.save();
  } finally {
    file.close();
  }
}

6. Advanced Metadata Properties (Generic Properties Map) #

For advanced metadata management, TagLib supports a generic properties map (Map<String, List<String>>) representing key-value pairs of tags. This allows you to read and write tags that are not exposed via standard high-level properties, or handle tags with multiple values (e.g., multiple artists or genres).

Standard keys are defined as constants in the TagProperties class (e.g., TagProperties.albumArtist, TagProperties.lyrics, TagProperties.bpm, etc.).

Reading Generic Properties

final file = TagLibFile.open(filePath);
if (file != null) {
  try {
    // Get all properties as a Map<String, List<String>>
    final Map<String, List<String>> props = file.properties;

    // Read standard tags using TagProperties constants
    final artists = props[TagProperties.artist]; // List of artist names
    final albumArtist = props[TagProperties.albumArtist]?.firstOrNull;
    final lyrics = props[TagProperties.lyrics]?.firstOrNull;

    print('Artists: $artists');
    print('Lyrics: $lyrics');
    
    // Print all available properties in the file
    props.forEach((key, values) {
      print('$key: $values');
    });
  } finally {
    file.close();
  }
}

Writing Generic Properties

final file = await TagLibFile.openAsync(filePath, writeAccess: true);
if (file != null) {
  try {
    // 1. Prepare properties to set
    final Map<String, List<String>> newProps = {
      TagProperties.artist: ['First Artist', 'Second Artist'], // Multiple values
      TagProperties.albumArtist: ['Various Artists'],
      TagProperties.lyrics: ['Line 1...\nLine 2...\nLine 3...'],
      'CUSTOM_TAG': ['Custom Value'], // You can also use custom tags
    };

    // 2. Set the properties in memory
    // It returns any properties not supported by this file format
    final unsupported = file.setProperties(newProps);
    if (unsupported.isNotEmpty) {
      print('Warning: Some properties are unsupported by the file format: $unsupported');
    }

    // 3. Save to write changes to disk
    final success = file.save();
    if (success) {
      print('Generic properties saved successfully!');
    }
  } finally {
    file.close();
  }
}

Configuration: Selectable Platform Support #

If your project only targets specific platforms or you want to avoid native compilation conflicts with other libraries, you can selectively enable or disable platforms by adding a configuration file named flutter_taglib.yaml in your host project's root directory.

Create a flutter_taglib.yaml file:

# flutter_taglib.yaml
platforms:
  android: true
  ios: false
  macos: true
  windows: true
  linux: true
  • true (Default): Compilation is enabled for the platform.
  • false: Native compilation is skipped for the platform, and calling TagLibFile methods on that platform will throw an UnsupportedError (or you can verify via TagLibFile.isSupported which returns false).

Native Assets Compilation Requirements #

Because this plugin compiles TagLib from source using Native Assets:

  • Android: Requires NDK configured in your local environment.
  • iOS/macOS: Requires Xcode.
  • Windows: Requires Visual Studio with C++ build tools.
  • Linux: Requires build tools and development libraries. On Debian/Ubuntu, install the required packages with:
    sudo apt update
    sudo apt install -y clang cmake ninja-build pkg-config libgtk-3-dev liblzma-dev
    
    To run or build the application:
    # Run in development mode
    flutter run -d linux
    
    # Build release version
    flutter build linux
    

License #

This project is licensed under the Apache 2.0 License. TagLib itself is licensed under LGPL/MPL.

1
likes
0
points
256
downloads

Publisher

unverified uploader

Weekly Downloads

A high-performance Flutter plugin wrapping TagLib using Dart FFI and Native Assets to read/write audio metadata and properties.

Repository (GitHub)
View/report issues

Topics

#audio #metadata #ffi #native-assets #taglib

License

unknown (license)

Dependencies

code_assets, ffi, flutter, hooks, logging, native_toolchain_c

More

Packages that depend on flutter_taglib

Packages that implement flutter_taglib