htaglib

Bindings to TagLib, audio meta-data library

https://github.com/mrkkrp/htaglib

Version on this page:1.2.0@rev:1
LTS Haskell 23.0:1.2.1@rev:1
Stackage Nightly 2024-12-12:1.2.1@rev:1
Latest on Hackage:1.2.1@rev:1

See all snapshots htaglib appears in

BSD-3-Clause licensed and maintained by Mark Karpov
This version can be pinned in stack with:htaglib-1.2.0@sha256:5bd2491b20b90b4be9e84b916e8cc6cdee41d6640deb79f188aa2c5dbef87a27,2755

Module documentation for 1.2.0

HTagLib

License BSD3 Hackage Stackage Nightly Stackage LTS Build Status

This is Haskell bindings to TagLib, library for reading and editing meta-data of several popular audio formats.

It works with the following formats:

  • MP3
  • FLAC
  • MPC
  • Speex
  • WavPack TrueAudio
  • WAV
  • AIFF
  • MP4
  • ASF

This happens in an abstract, uniform way, so you don’t need to handle any low-level details. As a consequence, it’s currently not possible to work with format-specific functionality.

Alternatives

There is at least two Haskell bindings doing “the same” thing:

Both are very low level, without any protection or higher-level abstractions, not really type-safe. I personally don’t want to use them, so I wrote this.

A note for FLAC users

If you want to work with FLAC, there is a complete Haskell binding to libFLAC—reference FLAC implementation. It allows to work with all FLAC metadata (read and write) and also provides Haskell API to stream encoder and stream decoder. Please prefer that package if you don’t need to work with other audio formats.

Quick start

First, since this is bindings to C-interface of the library, you’ll need to install the library itself. If you’re on a Unix-like system, chances are you’ll have it in the official repositories of your distro.

After installation of the library, install htaglib package using Cabal or Stack (recommended):

$ stack install htaglib

Reading meta data

Now to the hacking. It’s recommended that you define a record representing meta-data of audio track in your program, like this:

module Main (main) where

import Data.Monoid
import Sound.HTagLib
import System.Environment (getArgs)

data AudioTrack = AudioTrack
  { atTitle   :: Title
  , atArtist  :: Artist
  , atAlbum   :: Album
  , atComment :: Comment
  , atGenre   :: Genre
  , atYear    :: Maybe Year
  , atTrack   :: Maybe TrackNumber }
  deriving Show

A couple of notes here. We use unique types for every component of meta data, so it’s more difficult to use track title in lieu of track artist, for example. Meta data that is represented by strings also has smart constructors, they replace zero bytes with spaces, this is necessary to avoid troubles when your Haskell strings go to the C-level (well, zero-bytes in strings is rather edge case, but it should be mentioned). Of course, Title, Artist, Album, Comment, and Genre all are instances of IsString, so just turn on OverloadedStrings and you can use normal string literals to create data of these types.

Year and TrackNumber may be not set or missing, in this case you get Nothing. This is possible with string-based fields too, but in that case you just get empty strings. Year and TrackNumber have smart constructors that make sure that the values are positive (i.e. zero is not allowed).

OK, it’s time to read some info. There is TagGetter type which is an applicative functor. You first construct TagGetter which will retrieve entire AudioTrack for you using applicative style:

audioTrackGetter :: TagGetter AudioTrack
audioTrackGetter = AudioTrack
  <$> titleGetter
  <*> artistGetter
  <*> albumGetter
  <*> commentGetter
  <*> genreGetter
  <*> yearGetter
  <*> trackNumberGetter

Perfect, now use getTags to read entire record:

main :: IO ()
main = do
  path  <- head <$> getArgs
  track <- getTags path audioTrackGetter
  print track

For example (alignment is added):

$ ./example "/home/mark/music/David Bowie/1977, Low/01 Speed of Life.flac"
AudioTrack
  { atTitle   = Title   "Speed of Life"
  , atArtist  = Artist  "David Bowie"
  , atAlbum   = Album   "Low"
  , atComment = Comment ""
  , atGenre   = Genre   ""
  , atYear    = Just    (Year 1977)
  , atTrack   = Just    (TrackNumber 1)
  }

Success! It’s also possible to extract audio properties like sample rate, etc. but it’s not shown here for simplicity, consult Haddocks for more information.

N.B. If you need to extract duration of tracks, TagLib only returns number of seconds as an integer. This means that if you want to calculate total duration, you’ll have slightly incorrect result. Proper solution is to extract duration as floating-point number, for that we recommend bindings to libsndfilehsndfile (or the above-mentioned flac package for Haskell if you work with FLAC).

Writing meta data

We cannot use applicative interface to set tags. There are several reasons:

  • Applicative interface in general is better for extracting or parsing (or rather assembling complex parsers from more basic ones).

  • Some fields like sample rate or length can only be read, not set.

  • We may wish to set one or two fields selectively, not everything.

Solution: use monoids. TagSetter is an instance of Monoid. This means that we can set title and artist of audio track like this:

main :: IO ()
main = do
  (path : title : artist : _) <- getArgs
  setTags path Nothing $
    titleSetter (mkTitle title) <>
    artistSetter (mkArtist artist)
  track <- getTags path audioTrackGetter
  print track

This code loads file and changes “title” and “artist” meta data fields.

Conclusion

With the interface provided by getTags and setTags it’s not possible to forget to close file or free some resource. You can read all meta data at once directly into your data structure in type-safe manner. Writing meta data should be trivial too. Have fun!

License

Copyright © 2015–2018 Mark Karpov

Distributed under BSD 3 clause license.

Changes

HTagLib 1.2.0

  • Compiles with GHC 8.4.1.

  • Added Semigroup instance for TagSetter.

  • All definitions are re-exported in the Sound.HTagLib module, so other modules have been hidden.

HTagLib 1.1.1

  • Improved/updated documentation and metadata.

HTagLib 1.1.0

  • Made getTags, getTags', setTags, and setTags' callable from any instance of MonadIO, not only IO.

HTagLib 1.0.4

  • Re-wrote the test suite with Hspec.

  • Drop support for GHC 7.6.

HTagLib 1.0.3

  • Export functions instead of record selectors for tag newtype wrappers. This is to prevent changes (using record syntax) to values created with smart constructors.

HTagLib 1.0.2

  • Modify test suite so it passes with newer versions of TagLib as well.

HTagLib 1.0.1

  • Rewritten setters (without changing the API), so at most one writing operation is performed for every settable value. When combining setters that happen to set the same tags to different values, value on the left side of mappend wins.

HTagLib 1.0.0

  • Make the module Sound.HTagLib.Internal hidden for end users.

  • Make Text underlying type for wrappers around textual data (breaking change, previously it was String).

  • Rename functions like getTitle to unTitle (breaking change).

  • Fix bug when wrong data is read from files when current locale specifies encoding other than UTF-8.

HTagLib 0.1.1

  • Missing audio samples used for testing are included in distribution.

HTagLib 0.1.0

  • Initial release.