Skip to content

thebytearray/TagLib-spm

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

8 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

TagLibSPM

Repository: github.com/thebytearray/TagLib-spm

Swift Package Manager library that exposes TagLib on iOS 15+ and macOS 12+ for reading and writing audio metadata (tags, embedded pictures, and basic audio properties) on local files.

Every tagged release ships a prebuilt TagLib.xcframework attached to its GitHub release. When you add the package with a version rule (.package(from: "1.0.0")), SwiftPM resolves a release tag whose Package.swift is a .binaryTarget pointing at that tag's xcframework zip, so consumers never need to clone submodules or compile any C++.


Table of contents

  1. Requirements
  2. Adding the package to your app
  3. Examples
  4. Public Swift API
  5. File URLs and threading
  6. Repository layout
  7. How releases work
  8. Building the xcframework locally (contributors only)
  9. License

Requirements

  • Consumers: Xcode with Swift 5.9 or newer. No submodules needed when resolving from a tag.
  • Contributors: additionally Git with submodule support.
  • zlib is linked into the xcframework at build time; consumers pick it up via load commands in the dylib.

Adding the package to your app

Package URL: https://github.com/thebytearray/TagLib-spm

In Xcode: File → Add Package Dependencies…, paste the URL, choose a version, and add the TagLib product to your target.

In another Package.swift:

dependencies: [
    .package(url: "https://github.com/thebytearray/TagLib-spm.git", from: "1.0.0"),
],
.target(
    name: "YourTarget",
    dependencies: [.product(name: "TagLib", package: "TagLib-spm")]
)
import TagLib

Always pin by version / tag, not branch — the binary manifest only exists at tag commits (see How releases work).


Examples: reading and editing metadata

Use URL file URLs and call TagLib off the main thread (see File URLs and threading).

Read title, artist, and album

import TagLib

func printCommonTags(fileURL: URL) {
    guard let meta = TagLib.getMetadata(from: fileURL, readPictures: false) else {
        print("Could not read file or unsupported format")
        return
    }
    let map = meta.propertyMap
    let title = map["TITLE"]?.first
    let artist = map["ARTIST"]?.first
    let album = map["ALBUM"]?.first
    print("Title:", title ?? "(none)", "Artist:", artist ?? "(none)", "Album:", album ?? "(none)")
}

TagLib uses uppercase keys such as TITLE, ARTIST, ALBUM. Values are arrays (formats may allow multiple values per key).

Read a single property

let titles = TagLib.getMetadataPropertyValues(from: fileURL, propertyName: "TITLE")
// Missing keys return [].

Change title and artist, then save

func applyTitleAndArtist(fileURL: URL, title: String, artist: String) -> Bool {
    var map = TagLib.getMetadata(from: fileURL, readPictures: false)?.propertyMap ?? [:]
    map["TITLE"] = [title]
    map["ARTIST"] = [artist]
    return TagLib.savePropertyMap(to: fileURL, propertyMap: map)
}

Saving replaces the property map for that file type. Start from getMetadata and merge changes so you do not wipe other tags.

Merge edits (recommended)

func updateTags(fileURL: URL, edits: [String: [String]]) -> Bool {
    guard var map = TagLib.getMetadata(from: fileURL, readPictures: false)?.propertyMap else {
        return false
    }
    for (key, values) in edits {
        map[key] = values
    }
    return TagLib.savePropertyMap(to: fileURL, propertyMap: map)
}

Embedded cover art

if let cover = TagLib.getFrontCover(from: fileURL) {
    let imageData = cover.data
    let mime = cover.mimeType
}
func setFrontCoverJPEG(fileURL: URL, jpegData: Data) -> Bool {
    let picture = Picture(
        data: jpegData,
        description: "",
        pictureType: "Front Cover",
        mimeType: "image/jpeg"
    )
    return TagLib.savePictures(to: fileURL, pictures: [picture])
}

Audio properties (no tags)

if let audio = TagLib.getAudioProperties(from: fileURL, readStyle: .average) {
    let seconds = audio.length / 1000
    print("Duration ~ \(seconds)s, \(audio.bitrate) kbps, \(audio.sampleRate) Hz, \(audio.channels) ch")
}

Public Swift API

Entry points are static methods on the TagLib enum. URL must be a file URL (url.isFileURL == true).

Method Purpose
getAudioProperties(from:readStyle:) Bitrate, duration, sample rate, channels.
getMetadata(from:readPictures:) Property map and optional pictures.
getMetadataPropertyValues(from:propertyName:) Values for one key; [] if missing.
getPictures(from:) All embedded pictures.
getFrontCover(from:) "Front Cover" or first picture.
savePropertyMap(to:propertyMap:) Writes tags from [String: [String]].
savePictures(to:pictures:) Writes embedded pictures.

Types: AudioPropertiesReadStyle (fast, average, accurate), AudioProperties, Metadata, Picture, PropertyMap ([String: [String]]). Property keys match TagLib's map (e.g. TITLE, ARTIST). Picture types follow ID3v2 (e.g. "Front Cover").


File URLs and threading

  • Use normal file **URL**s (and security-scoped access on iOS when needed).
  • TagLib does blocking I/O; call from a background queue or Task.detached, not the main thread.

Repository layout

  • Package.swift (on main) — source manifest. Compiles TagLib from the vendor/taglib submodule into a dynamic TagLib.framework. Used by contributors, CI, and Scripts/create-xcframework.sh.
  • Scripts/binary-package.swift.tmpl — template for the binary Package.swift the release workflow materializes at tag time (__RELEASE__ and __CHECKSUM__ placeholders are filled in by sed).
  • Scripts/create-xcframework.sh — archives TagLibSPM for macOS, iOS, and iOS Simulator, bundles them into TagLib.xcframework, ditto-zips it, and writes TagLib.xcframework.zip.sha256.
  • Sources/ — Swift wrapper (TagLib), C++ bridge (CTagLib), and SPM-specific config headers for TagLibCore.
  • vendor/taglib — upstream TagLib C++ sources as a git submodule.
  • Tests/TagLibTests — tests exercising the source build.
  • .github/workflows/ci.yml — builds + tests the source manifest on every push / PR.
  • .github/workflows/release.yml — the release pipeline (see below).

How releases work

SwiftPM doesn't allow target paths to escape the package root, so we can't keep two Package.swift files side by side. Instead, main always carries the source manifest (so swift build / swift test Just Work), and the release workflow generates the binary manifest on-the-fly and commits it only at the release tag.

main:          commit A ─ commit B ─ commit C   ← source Package.swift (compiles from vendor/)
                                    │
tag v1.0.0:                         └── commit C' ← binary Package.swift (points at v1.0.0/*.zip)

commit C' is reachable only from the tag ref on origin; main is never advanced onto it. SPM consumers resolving from: "1.0.0" fetch commit C' and see the .binaryTarget.

Cutting a release

Trigger Actions → Release → Run workflow with a new tag (e.g. v1.0.1). The workflow:

  1. Checks out main (or the chosen branch) with submodules.
  2. Fails fast if the tag already exists on origin.
  3. Runs swift test against the source manifest.
  4. Runs Scripts/create-xcframework.sh, producing TagLib.xcframework.zip + its SHA-256.
  5. Substitutes __RELEASE__ and __CHECKSUM__ in Scripts/binary-package.swift.tmpl and writes the result over Package.swift in the working tree.
  6. Commits the rewritten Package.swift, annotates the tag on that commit.
  7. Pushes only the tag (git push origin refs/tags/...). The tag commit becomes reachable on origin; main stays untouched.
  8. softprops/action-gh-release@v2 uploads TagLib.xcframework.zip to the release named after the tag.

If you ever need to verify what SPM consumers see: git show <tag>:Package.swift.


Building the xcframework locally (contributors only)

git clone --recurse-submodules https://github.com/thebytearray/TagLib-spm.git
cd TagLib-spm
# If you already cloned without submodules:
git submodule update --init --recursive

mkdir -p vendor/taglib/taglib/spm_public_headers
# Opening Package.swift in Xcode once makes the `TagLibSPM` scheme available.
./Scripts/create-xcframework.sh /path/to/out

Outputs inside /path/to/out:

  • TagLib.xcframework
  • TagLib.xcframework.zip
  • TagLib.xcframework.zip.sha256

Run tests locally with swift test.

API docs (DocC):

swift package --disable-sandbox preview-documentation --target TagLib
# or
swift package generate-documentation --target TagLib

License

Packaging and Swift/C code in this repository are under LGPL 2.1; see LICENSE. TagLib in vendor/taglib follows upstream licensing (LGPL / MPL); see that tree's COPYING files and taglib.org.

About

No description, website, or topics provided.

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors