#lang scribble/manual @(require (for-label racket/base racket/contract racket/path racket/draw "../taglib.rkt")) @title{TagLib Metadata} @author[@author+email["Hans Dijkema" "hans@dijkewijk.nl"]] @defmodule[racket-audio/taglib] The @racketmodname[racket-audio/taglib] module provides the high level metadata reader used by the audio package. It wraps the lower level TagLib FFI module and presents a small, read-only Racket API for common tags, audio properties, generic properties, and embedded cover art. Calling @racket[id3-tags] opens the file through TagLib, copies the values that are needed on the Racket side, reads the optional embedded picture, frees the native TagLib objects, and returns an opaque tag handle. The handle is therefore a snapshot of the metadata at the time it was read. It does not keep the media file or the native TagLib handle open. The name @racket[id3-tags] is historical. The module uses TagLib to open the file, so the usable file types are the file types supported by the TagLib library available at run time. This module is not a tag editor; it only reads metadata. @section{Reading metadata} @defproc[(id3-tags [file path-string?]) any/c]{ Reads metadata from @racket[file] and returns an opaque tag handle. The argument may be a path or a string. On Windows, the implementation retries with the wide-character TagLib open function when the normal open function does not produce a valid TagLib file. The returned handle is passed to the other procedures in this module. If the file cannot be opened, @racket[id3-tags] still returns a handle, but @racket[tags-valid?] returns @racket[#f]. Other accessors then return their default values, such as @racket[""], @racket[-1], @racket['()], or @racket[#f].} @defproc[(tags-valid? [tags any/c]) boolean?]{ Returns @racket[#t] when @racket[id3-tags] successfully opened the file and TagLib reported it as valid.} @racketblock[ (define tags (id3-tags "song.mp3")) (when (tags-valid? tags) (printf "~a - ~a\n" (tags-artist tags) (tags-title tags)))] @section{Common tag fields} @deftogether[ (@defproc[(tags-title [tags any/c]) string?] @defproc[(tags-album [tags any/c]) string?] @defproc[(tags-artist [tags any/c]) string?] @defproc[(tags-comment [tags any/c]) string?] @defproc[(tags-genre [tags any/c]) string?])] Return the common textual fields from the TagLib tag interface. Missing fields are returned as the empty string. @deftogether[ (@defproc[(tags-year [tags any/c]) integer?] @defproc[(tags-track [tags any/c]) integer?])] Return the year and track number from the common TagLib tag interface. Missing numeric values are returned as @racket[-1]. @deftogether[ (@defproc[(tags-composer [tags any/c]) (or/c string? (listof string?))] @defproc[(tags-album-artist [tags any/c]) (or/c string? (listof string?))] @defproc[(tags-disc-number [tags any/c]) (or/c number? #f)])] Return selected values from the generic TagLib property store. The composer is read from the lower-case @racket['composer] key, the album artist from @racket['albumartist], and the disc number from @racket['discnumber]. Composer and album artist return a list of strings when the property is present and the empty string when it is missing. The disc number is parsed from the first property value and defaults to @racket[-1]. If the stored value cannot be parsed as a number, the result may be @racket[#f]. Use @racket[tags-keys] and @racket[tags-ref] for direct access to the complete generic property store. @section{Audio properties} @deftogether[ (@defproc[(tags-length [tags any/c]) integer?] @defproc[(tags-sample-rate [tags any/c]) integer?] @defproc[(tags-bit-rate [tags any/c]) integer?] @defproc[(tags-channels [tags any/c]) integer?])] Return audio properties reported by TagLib: length in seconds, sample rate in Hz, bit rate in kbit/s, and number of channels. Missing values are returned as @racket[-1]. @section{Generic properties} @defproc[(tags-keys [tags any/c]) (listof symbol?)]{ Returns the generic TagLib property keys found in the file. Keys are lower-cased and converted to symbols.} @defproc[(tags-ref [tags any/c] [key symbol?]) (or/c (listof string?) #f)]{ Returns the list of values associated with @racket[key], or @racket[#f] when the property was not found. Use lower-case symbol keys, matching the values returned by @racket[tags-keys].} @racketblock[ (for ([key (in-list (tags-keys tags))]) (printf "~a: ~s\n" key (tags-ref tags key)))] Generic properties may contain multiple values for a single key. The API keeps those values as lists instead of joining them into one string. @section{Embedded pictures} The module represents embedded artwork as an opaque @deftech{picture value}. The picture value is returned by @racket[tags-picture] and can be inspected with the picture procedures documented below. When no picture is available, the picture-related procedures return @racket[#f]. @defproc[(tags-picture [tags any/c]) (or/c any/c #f)]{ Returns the embedded picture value, or @racket[#f] when the file has no picture that the underlying FFI layer could read.} @deftogether[ (@defproc[(tags-picture->kind [tags any/c]) (or/c integer? #f)] @defproc[(tags-picture->mimetype [tags any/c]) (or/c string? #f)] @defproc[(tags-picture->size [tags any/c]) (or/c integer? #f)] @defproc[(tags-picture->ext [tags any/c]) (or/c symbol? #f)])] Return selected information about the embedded picture. The kind is the numeric picture type reported by the FFI layer. The MIME type is the stored MIME type, such as @racket["image/jpeg"] or @racket["image/png"]. The size is the number of bytes in the embedded image. The extension helper returns @racket['jpg], @racket['png], or @racket[#f] when the MIME type is not recognized. @defproc[(tags-picture->bitmap [tags any/c]) (or/c (is-a?/c bitmap%) #f)]{ Reads the embedded picture bytes with @racket[read-bitmap] and returns a @racket[bitmap%] object. If there is no embedded picture, the result is @racket[#f].} @defproc[(tags-picture->file [tags any/c] [path path-string?]) boolean?]{ Writes the embedded picture bytes to @racket[path] in binary mode, replacing an existing file. The procedure returns @racket[#t] when a picture was written and @racket[#f] when the tag handle has no picture. The file name is not adjusted automatically; use @racket[tags-picture->ext] when the caller wants to choose an extension from the MIME type.} @racketblock[ (define ext (tags-picture->ext tags)) (when ext (tags-picture->file tags (format "cover.~a" ext)))] @section{Picture values} @deftogether[ (@defproc[(id3-picture-mimetype [picture any/c]) string?] @defproc[(id3-picture-kind [picture any/c]) integer?] @defproc[(id3-picture-size [picture any/c]) integer?] @defproc[(id3-picture-bytes [picture any/c]) bytes?])] Access the fields of a picture value returned by @racket[tags-picture]. These procedures are useful when the caller wants to process the image bytes directly instead of converting them to a bitmap or writing them to a file. @section{Converting to a hash} @defproc[(tags->hash [tags any/c]) hash?]{ Returns a mutable hash containing the core values copied from the tag handle. The hash contains the keys @racket['valid?], @racket['title], @racket['album], @racket['artist], @racket['comment], @racket['composer], @racket['genre], @racket['year], @racket['track], @racket['length], @racket['sample-rate], @racket['bit-rate], @racket['channels], @racket['picture], and @racket['keys]. The hash is intended as a convenient snapshot for application code. Generic property values are not expanded into the hash; use @racket[tags-ref] for those values.} @section{Example} @racketblock[ (define tags (id3-tags "track.flac")) (cond [(not (tags-valid? tags)) (printf "No readable tags\n")] [else (printf "Title: ~a\n" (tags-title tags)) (printf "Artist: ~a\n" (tags-artist tags)) (printf "Album: ~a\n" (tags-album tags)) (printf "Length: ~a seconds\n" (tags-length tags)) (when (tags-picture tags) (define ext (or (tags-picture->ext tags) 'bin)) (tags-picture->file tags (format "cover.~a" ext)))])] @section{Implementation notes} This chapter documents the public @racketmodname["taglib.rkt"] layer. The native TagLib calls are delegated to @racketmodname["taglib-ffi.rkt"], but callers normally should not use that lower level module directly. The tag handle is implemented as a small Racket object with a private dispatch procedure. The native TagLib file is not stored in the handle. This keeps the public API simple and prevents native resources from leaking into application code. The implementation normalizes generic property names by lower-casing TagLib property keys and converting them to symbols. Values remain lists of strings because TagLib properties may contain multiple values for one key.