Skip to content

jeonghi/TagLibSwift

Repository files navigation

TagLibSwift

Read and write audio metadata in Swift, powered by TagLib 2.3.1.

Swift Platforms SPM TagLib License

TagLibSwift wraps the TagLib C++ library as a Swift package for iOS and macOS. TagLib's source is compiled from vendored source by SwiftPM β€” no prebuilt binary, no CMake step, and full iOS device and simulator support out of the box.

The public API mirrors TagLib's own model β€” open a file, then reach its tag, audioProperties, properties, and pictures, and save() β€” layered over TagLib's C++ types via Swift/C++ interop.


Features

  • πŸ“– Read & write tags for MP3/ID3v2, FLAC, MP4/M4A, Ogg Vorbis, WAV/AIFF, APE, WavPack, and more.
  • 🧱 TagLib-faithful API β€” a single AudioFile handle exposing tag, audioProperties, properties, pictures, and save(), mirroring TagLib::FileRef.
  • πŸ—‚οΈ Universal metadata β€” the format-independent PropertyMap exposes every text tag (ALBUMARTIST, COMPOSER, DISCNUMBER, BPM, …) as a plain [String: [String]].
  • πŸ–ΌοΈ Cover art β€” read and write embedded pictures as Data.
  • 🧬 Generic complex properties β€” read/write any complex-property key (PICTURE, GENERALOBJECT, …) as typed ComplexValue maps.
  • 🎚️ Audio properties β€” bitrate, length, sample rate, channels, plus bitsPerSample and MPEG version/layer where the format provides them.
  • πŸ’Ύ In-memory I/O β€” open from and serialize back to Data, no file path required.
  • 🧩 Escape hatch β€” drop down to the raw TagLib.FileRef for format-specific work when you need it.
  • βœ… Tested β€” 34 tests across MP3/FLAC/M4A/Ogg (path and in-memory) with 100% line coverage of the Swift API.

Requirements

Minimum
Swift 5.9
Xcode 15
iOS 13.0
macOS 10.15

Installation

Swift Package Manager

Add the dependency to your Package.swift:

dependencies: [
    .package(url: "https://github.com/jeonghi/TagLibSwift.git", from: "0.1.0")
]

Then add the product to your target. TagLibSwift uses Swift/C++ interoperability, so any target that depends on it must enable C++ interop:

.target(
    name: "MyApp",
    dependencies: [.product(name: "TagLibSwift", package: "TagLibSwift")],
    swiftSettings: [.interoperabilityMode(.Cxx)]
)

In Xcode, add the package via File β–Έ Add Package Dependencies…, then set Build Settings β–Έ C++ and Objective-C Interoperability to C++/Objective-C++ on the consuming target.

Because Swift/C++ interop is not yet ABI-stable, treat adopting TagLibSwift as a deliberate, toolchain-pinned choice. The API rides the value-type interop path (no imported reference types), which preserves the iOS 13 / macOS 10.15 floor.

Quick Start

import TagLibSwift
import Foundation

guard let file = AudioFile(path: "/path/to/song.flac") else { return }

// Read common tags
print(file.tag.title, "β€”", file.tag.artist)

// Read audio properties
if let audio = file.audioProperties {
    print(audio.bitrate, "kbps,", audio.lengthInSeconds, "s")
}

// Write common tags
file.tag.title = "New Title"
file.tag.year = 2026

// Universal, format-independent text tags
var props = file.properties
props["COMPOSER"] = ["Jane Doe"]
let unsupported = file.setProperties(props)   // returns any keys this format rejected

// Cover art
file.setPictures([
    Picture(data: try Data(contentsOf: coverURL),
            mimeType: "image/png",
            description: "cover",
            pictureType: .frontCover)
])

try file.save()

setProperties(_:) returns the keys the format could not store (empty means everything was written) β€” so partial writes are never silent.

In-memory files

Open audio straight from bytes (e.g. a download) and serialize the edited bytes back out β€” no temp file needed:

guard let file = AudioFile(data: mp3Data, fileExtension: "mp3") else { return }
file.tag.title = "New Title"
try file.save()                 // writes back into the in-memory stream
let updated: Data = try file.serialized()   // reopen with AudioFile(data:fileExtension:)

serialized() throws AudioFileError.notMemoryBacked on a path-backed file.

Generic complex properties

Beyond cover art, any complex-property key is reachable as typed ComplexValue maps:

for key in file.complexPropertyKeys {            // e.g. "PICTURE", "GENERALOBJECT"
    let maps = file.complexProperties(key)       // [[String: ComplexValue]]
    // ComplexValue: .string / .int / .bool / .data / .stringList / .unsupported
}
file.setComplexProperties("PICTURE", newMaps)    // then save()

pictures / setPictures(_:) are a thin, typed layer over the PICTURE key.

API at a glance

Member TagLib equivalent Notes
AudioFile(path:) FileRef(path) Returns nil if the file could not be parsed.
AudioFile(data:fileExtension:) FileRef(IOStream*) Opens from an in-memory Data buffer via a ByteVectorStream.
try file.serialized() ByteVectorStream::data() In-memory bytes after save(); throws for path-backed files.
file.isValid / file.isNull FileRef::isNull Validity of the parsed file.
file.tag FileRef::tag() Live view: title, artist, album, comment, genre, year, track, isEmpty. Setters persist on save().
file.audioProperties FileRef::audioProperties() Optional; lengthInSeconds, lengthInMilliseconds, bitrate, sampleRate, channels, bitsPerSample?, mpegVersion?, mpegLayer?.
file.properties / setProperties(_:) FileRef::properties() / setProperties() PropertyMap = [String: [String]]. setProperties returns rejected keys.
file.complexPropertyKeys / complexProperties(_:) / setComplexProperties(_:_:) FileRef::complexProperty* Any complex-property key as [[String: ComplexValue]].
file.pictures / setPictures(_:) complex property "PICTURE" Cover art as Picture values (typed layer over the PICTURE key).
try file.save() FileRef::save() Throws AudioFileError.saveFailed.

Escape hatch

When the typed API doesn't cover a format-specific need (e.g. individual ID3v2 frames), reach the underlying TagLib type:

let ref = file.fileRef   // TagLib.FileRef β€” use TagLib's C++ API directly

Feature coverage

How far the Swift surface covers TagLib. Because the whole library is compiled in and reachable through file.fileRef, everything TagLib can do is accessible β€” the checklist distinguishes what's wrapped in an idiomatic Swift API from what you reach through the raw escape hatch.

Legend: βœ… high-level Swift API Β· πŸ”Ά reachable via raw file.fileRef (C++ interop), not pre-wrapped Β· ❌ not available

TagLib capability Status Swift API
Basic tags β€” title, artist, album, comment, genre, year, track βœ… file.tag
Universal text tags β€” PropertyMap (ALBUMARTIST, COMPOSER, DISCNUMBER, BPM, LYRICS, …, any key) βœ… file.properties / setProperties(_:)
Rejected-key reporting on write βœ… setProperties(_:) return value
Cover art / pictures β€” complex PICTURE property βœ… file.pictures / setPictures(_:)
Generic complex properties β€” any key as typed ComplexValue maps βœ… file.complexPropertyKeys / complexProperties(_:) / setComplexProperties(_:_:)
Audio properties β€” length, bitrate, sample rate, channels βœ… file.audioProperties
Extended audio props β€” bitsPerSample (FLAC/WAV/AIFF/MP4/APE/WavPack/TrueAudio/DSF/DSDIFF), MPEG version/layer βœ… file.audioProperties.bitsPerSample / .mpegVersion / .mpegLayer
In-memory / IOStream / ByteVector I/O (no file path) βœ… AudioFile(data:fileExtension:) / try file.serialized()
Save to disk βœ… try file.save()
File validity β€” isNull / isValid βœ… file.isValid / file.isNull
All TagLib file formats (auto-detected) β€” the API above works on every supported format βœ… AudioFile(path:)
Format-specific tag content β€” ID3v2 / MP4 / Xiph / APE / ASF metadata βœ… file.properties (text) Β· complexProperties(_:) (binary/structured)
Format-specific tag classes β€” raw ID3v2 frames (chapters, USLT lyrics, POPM rating, TXXX…), MP4 atoms, Xiph/APE/ASF objects πŸ”Ά file.fileRef
Per-version tag strip/remove (ID3v1 vs ID3v2, etc.) πŸ”Ά file.fileRef

The high-level API now covers TagLib's portable metadata model end to end β€” base tags, the universal PropertyMap (every text tag), generic complex properties (cover art and any other key), extended audio properties, and in-memory I/O β€” across all formats. The remaining πŸ”Ά rows are, by design, thin: the content of format-specific tags is already reachable through properties (text) and complexProperties (binary/structured), so wrapping each raw frame/atom class (hundreds of format-specific APIs) and per-version tag stripping stay reachable through the raw file.fileRef escape hatch rather than being pre-wrapped.

Example app

A multiplatform (iOS + macOS) SwiftUI demo lives in Examples/TagLibSwiftDemo/. Open Examples/TagLibSwiftDemo/TagLibSwiftDemo.xcodeproj in Xcode and run β€” it exercises the full API: opening an AudioFile, editing tag.*, reading audioProperties, editing the PropertyMap (showing rejected keys on save), viewing/setting cover art (pictures / setPictures), and save(). It ships a tiny tagged sample MP3 that it copies to a writable temp location before opening (the bundle is read-only), plus a .fileImporter to open other files.

The demo target enables C++ interop via the build setting SWIFT_OBJC_INTEROP_MODE = objcxx β€” required for import TagLibSwift to compile. The project is generated from Project.swift with tuist (tuist generate), but the committed .xcodeproj is standalone and opens/builds without tuist.

Supported formats

MP3 (ID3v1/ID3v2), FLAC, Ogg (Vorbis/Opus/Speex/FLAC), MP4/M4A, WAV, AIFF, APE, WavPack, Musepack, TrueAudio, ASF/WMA, Matroska, DSF/DSDIFF, and tracker formats (MOD/IT/S3M/XM) β€” everything TagLib 2.3.1 supports.

Versioning

TagLibSwift vendors TagLib 2.3.1. The upstream source lives in-repo, so consumers need no submodules to build. Maintainers bumping the vendored TagLib version should follow UPDATING.md.

Testing

swift test

The base tests use a committed MP3 fixture (regenerate with Tests/TagLibSwiftTests/Resources/create_test_mp3.sh, requires ffmpeg). The multi-format tests generate their FLAC/M4A/Ogg fixtures automatically at runtime, skipping cleanly if ffmpeg or a required encoder is unavailable.

License

TagLibSwift is released under the MIT License. TagLib is licensed under LGPL v2.1.

About

A Swift Package wrapping TagLib (C++) with a Swift/C++ interop API for iOS and macOS.

Resources

Stars

Watchers

Forks

Releases

Packages

Used by

Contributors

Languages