Documentation added
This commit is contained in:
@@ -0,0 +1,219 @@
|
||||
#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.
|
||||
Reference in New Issue
Block a user