Skip to content

sentriz/go-taglib

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

61 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

go-taglib

This project is a Go library for reading and writing audio metadata tags. By packaging an embedded Wasm binary, the library needs no external dependencies or CGo. Meaning easy static builds and cross compilation.

Current bundled TagLib version is v2.1.1.

To reproduce or verify the bundled binary, see the attestations.

godoc

Features

  • Read and write metadata tags for audio files, including support for multi-valued tags.
  • Read and write embedded images (album artwork) from audio files.
  • Retrieve audio properties such as length, bitrate, sample rate, and channels.
  • Supports multiple audio formats including MP3, FLAC, M4A, WAV, OGG, WMA, and more.
  • Safe for concurrent use
  • Reasonably fast

Usage

Add the library to your project with go get go.senan.xyz/taglib@latest

Reading metadata

func main() {
    tags, err := taglib.ReadTags("path/to/audiofile.mp3")
    // check(err)

    fmt.Printf("tags: %v\n", tags) // map[string][]string

    fmt.Printf("AlbumArtist: %q\n", tags[taglib.AlbumArtist])
    fmt.Printf("Album: %q\n", tags[taglib.Album])
    fmt.Printf("TrackNumber: %q\n", tags[taglib.TrackNumber])
}

Writing metadata

func main() {
    err := taglib.WriteTags("path/to/audiofile.mp3", map[string][]string{
        // Multi-valued tags allowed
        taglib.AlbumArtist:   {"David Byrne", "Brian Eno"},
        taglib.Album:         {"My Life in the Bush of Ghosts"},
        taglib.TrackNumber:   {"1"},

        // Non-standard allowed too
        "ALBUMARTIST_CREDIT": {"Brian Eno & David Byrne"},
    }, 0)
    // check(err)
}

Options for writing

The behaviour of writing can be configured with some bitset flags

The options are

  • Clear which indicates that all existing tags not present in the new map should be removed

The options can be combined the with the bitwise OR operator (|)

    taglib.WriteTags(path, tags, taglib.Clear)
    taglib.WriteTags(path, tags, 0)

Reading properties

func main() {
    properties, err := taglib.ReadProperties("path/to/audiofile.mp3")
    // check(err)

    fmt.Printf("Length: %v\n", properties.Length)
    fmt.Printf("Bitrate: %d\n", properties.Bitrate)
    fmt.Printf("SampleRate: %d\n", properties.SampleRate)
    fmt.Printf("Channels: %d\n", properties.Channels)

    // Image metadata (without reading actual image data)
    for i, img := range properties.Images {
        fmt.Printf("Image %d - Type: %s, Description: %s, MIME type: %s\n",
            i, img.Type, img.Description, img.MIMEType)
    }
}

Reading embedded images

import (
    "bytes"
    "image"

    _ "image/gif"
    _ "image/jpeg"
    _ "image/png"

    // ...
)

func main() {
    // Read first image (index 0)
    imageBytes, err := taglib.ReadImage("path/to/audiofile.mp3")
    // check(err)

    if imageBytes == nil {
        fmt.Printf("File contains no image")
        return
    }

    img, format, err := image.Decode(bytes.NewReader(imageBytes))
    // check(err)

    fmt.Printf("format: %q\n", format)

    bounds := img.Bounds()
    fmt.Printf("width: %d\n", bounds.Dx())
    fmt.Printf("height: %d\n", bounds.Dy())

    // Read a specific image by index
    backCover, err := taglib.ReadImageOptions("path/to/audiofile.mp3", 1)
    // check(err)
}

Writing embedded images

func main() {
    var imageBytes []byte // Some image data, from somewhere

    // Write as front cover with auto-detected MIME type
    err = taglib.WriteImage("path/to/audiofile.mp3", imageBytes)
    // check(err)

    // Write with custom options
    err = taglib.WriteImageOptions(
        "path/to/audiofile.mp3",
        imageBytes,
        0,                  // replaces image at index; use higher index to append
        "Back Cover",       // picture type
        "Back artwork",     // description
        "image/jpeg",       // MIME type
    )
    // check(err)
}

Manually Building and Using the Wasm Binary

The binary is already included in the package. However if you want to manually build and override it, you can with WASI SDK and Go build flags

  1. Clone this repository and Git submodules

    $ git clone "https://github.com/sentriz/go-taglib.git" --recursive
    $ cd go-taglib

Note

Make sure to use the --recursive flag, without it there will be no TagLib submodule to build with

  1. Generate the Wasm binary:

    $ ./build/build-docker.sh
    $ # taglib.wasm created

    Or, to build without Docker install WASI SDK and Binaryen, then

    $ ./build/build.sh /path/to/wasi-sdk # eg /opt/wasi-sdk
    $ # taglib.wasm created
  2. Use the new binary in your project

    $ CGO_ENABLED=0 go build -ldflags="-X 'go.senan.xyz/taglib.binaryPath=/path/to/taglib.wasm'" ./your/project/...

Performance

In this example, tracks are read on average in 0.3 ms, and written in 1.85 ms

goos: linux
goarch: amd64
pkg: go.senan.xyz/taglib
cpu: AMD Ryzen 7 7840U w/ Radeon  780M Graphics
BenchmarkWrite-16         608   1847873 ns/op
BenchmarkRead-16         3802    299247 ns/op

License

This project is licensed under the GNU Lesser General Public License v2.1. See the LICENSE file for details.

Contributing

Contributions are welcome! Please open an issue or submit a pull request.

Acknowledgments

  • TagLib for the audio metadata library.
  • Wazero for the WebAssembly runtime in Go.

About

portable Go audio metadata read/write via TagLib compiled to Wasm

Topics

Resources

License

Stars

Watchers

Forks

Sponsor this project

 

Contributors 3

  •  
  •  
  •