taglib documentation.
This commit is contained in:
@@ -62,8 +62,8 @@ file extension. Pass @racket['opus] or @racket['flac] to force a backend.
|
||||
When @racket[copy-tags?] is true, common textual tags and an embedded picture
|
||||
are copied from the source file to the destination file. Opus comments and
|
||||
cover art are written before encoding starts through @tt{libopusenc}. FLAC
|
||||
metadata is copied after the encoded file has been written, using the TagLib
|
||||
wrapper.
|
||||
metadata is copied after the encoded file has been written, using the
|
||||
read-write API from @racketmodname[racket-audio/taglib].
|
||||
|
||||
When @racket[progress-callback] is a procedure, it is called with a progress
|
||||
hash during encoding. Progress is based on the number of input frames read from
|
||||
|
||||
+200
-80
@@ -9,48 +9,72 @@
|
||||
@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.
|
||||
API used by the audio package. It wraps the lower level TagLib C FFI module and
|
||||
presents a Racket API for common tags, generic properties, audio 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 module can be used in two modes. The default mode is read-only and returns
|
||||
a snapshot of the metadata. A handle opened with @racket[#:mode 'read-write]
|
||||
keeps the native TagLib file open and can be modified with the setter
|
||||
procedures documented below. Changes are written to the media file by calling
|
||||
@racket[tags-save!].
|
||||
|
||||
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.
|
||||
The name @racket[id3-tags] is historical. The implementation uses TagLib, so
|
||||
the usable file types are the file types supported by the TagLib library
|
||||
available at run time.
|
||||
|
||||
@section{Reading metadata}
|
||||
@section{Opening and closing tag handles}
|
||||
|
||||
@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.
|
||||
@defproc[(id3-tags [file path-string?]
|
||||
[#:mode mode (or/c 'read 'read-only 'read-write 'write) 'read])
|
||||
any/c]{
|
||||
Opens @racket[file] through TagLib and returns an opaque tag handle. In the
|
||||
default read-only mode, the module copies the values needed on the Racket side,
|
||||
frees the native TagLib objects, and returns a snapshot handle.
|
||||
|
||||
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].}
|
||||
In read-write mode, the native TagLib file remains open. Setter procedures may
|
||||
then be used to modify fields, properties, and pictures. Call
|
||||
@racket[tags-save!] to write changes and @racket[tags-close!] to close the
|
||||
native handle.
|
||||
|
||||
@defproc[(tags-valid? [tags any/c]) boolean?]{
|
||||
Returns @racket[#t] when @racket[id3-tags] successfully opened the file and
|
||||
TagLib reported it as valid.}
|
||||
On Windows, the implementation retries with the wide-character TagLib open
|
||||
function when the normal open function does not produce a valid TagLib file.}
|
||||
|
||||
@defproc[(call-with-id3-tags [file path-string?]
|
||||
[proc procedure?]
|
||||
[#:mode mode (or/c 'read 'read-only 'read-write 'write) 'read])
|
||||
any/c]{
|
||||
Opens @racket[file], calls @racket[proc] with the tag handle, and closes the
|
||||
handle afterwards with @racket[tags-close!]. This is most useful for
|
||||
read-write code because it avoids leaking the native TagLib file handle.}
|
||||
|
||||
@deftogether[
|
||||
(@defproc[(tags-valid? [tags any/c]) boolean?]
|
||||
@defproc[(tags-read-write? [tags any/c]) boolean?]
|
||||
@defproc[(tags-closed? [tags any/c]) boolean?])]{
|
||||
Return handle state. @racket[tags-valid?] reports whether TagLib opened the
|
||||
file successfully. @racket[tags-read-write?] reports whether the handle was
|
||||
opened in read-write mode. @racket[tags-closed?] reports whether the native
|
||||
TagLib file handle has been closed.}
|
||||
|
||||
@deftogether[
|
||||
(@defproc[(tags-save! [tags any/c]) boolean?]
|
||||
@defproc[(tags-close! [tags any/c]) void?])]{
|
||||
@racket[tags-save!] writes pending changes for a read-write handle to the media
|
||||
file. @racket[tags-close!] closes the native TagLib file handle. Closing a
|
||||
read-only snapshot is harmless.
|
||||
}
|
||||
|
||||
@racketblock[
|
||||
(define tags (id3-tags "song.mp3"))
|
||||
|
||||
(when (tags-valid? tags)
|
||||
(printf "~a - ~a\n" (tags-artist tags) (tags-title tags)))]
|
||||
(call-with-id3-tags "track.flac"
|
||||
(lambda (tags)
|
||||
(when (tags-valid? tags)
|
||||
(tags-title! tags "New title")
|
||||
(tags-save! tags)))
|
||||
#:mode 'read-write)]
|
||||
|
||||
@section{Common tag fields}
|
||||
|
||||
@@ -59,32 +83,52 @@ TagLib reported it as valid.}
|
||||
@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?])]
|
||||
@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.
|
||||
are returned as the empty string.}
|
||||
|
||||
@deftogether[
|
||||
(@defproc[(tags-year [tags any/c]) integer?]
|
||||
@defproc[(tags-track [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].
|
||||
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].
|
||||
(@defproc[(tags-title! [tags any/c] [value (or/c string? 'clear)]) void?]
|
||||
@defproc[(tags-album! [tags any/c] [value (or/c string? 'clear)]) void?]
|
||||
@defproc[(tags-artist! [tags any/c] [value (or/c string? 'clear)]) void?]
|
||||
@defproc[(tags-comment! [tags any/c] [value (or/c string? 'clear)]) void?]
|
||||
@defproc[(tags-genre! [tags any/c] [value (or/c string? 'clear)]) void?])]{
|
||||
Set common textual fields on a read-write handle. Passing @racket['clear]
|
||||
clears the field. Call @racket[tags-save!] to persist the change.}
|
||||
|
||||
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.
|
||||
@deftogether[
|
||||
(@defproc[(tags-year! [tags any/c] [value (or/c exact-nonnegative-integer? 'clear)]) void?]
|
||||
@defproc[(tags-track! [tags any/c] [value (or/c exact-nonnegative-integer? 'clear)]) void?])]{
|
||||
Set numeric common fields on a read-write handle. Passing @racket['clear]
|
||||
writes zero through the TagLib C API and updates the Racket-side cache to
|
||||
@racket[-1].}
|
||||
|
||||
@section{Selected generic fields}
|
||||
|
||||
@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 @racket['composer] key, the album artist from
|
||||
@racket['albumartist], and the disc number from @racket['discnumber]. Use
|
||||
@racket[tags-keys] and @racket[tags-ref] for direct access to the complete
|
||||
generic property store.}
|
||||
|
||||
@deftogether[
|
||||
(@defproc[(tags-composer! [tags any/c] [value (or/c string? 'clear)]) void?]
|
||||
@defproc[(tags-album-artist! [tags any/c] [value (or/c string? 'clear)]) void?]
|
||||
@defproc[(tags-disc-number! [tags any/c]
|
||||
[value (or/c exact-nonnegative-integer? string? 'clear)])
|
||||
void?])]{
|
||||
Set selected generic properties on a read-write handle. The disc number may be
|
||||
provided as a number or as the exact string that should be written.}
|
||||
|
||||
@section{Audio properties}
|
||||
|
||||
@@ -92,10 +136,11 @@ parsed as a number, the result may be @racket[#f]. Use @racket[tags-keys] and
|
||||
(@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?])]
|
||||
@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].
|
||||
Hz, bit rate in kbit/s, and number of channels. These values are read-only
|
||||
properties of the media stream, not editable tags. Missing values are returned
|
||||
as @racket[-1].}
|
||||
|
||||
@section{Generic properties}
|
||||
|
||||
@@ -109,9 +154,39 @@ 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].}
|
||||
|
||||
@defproc[(tags-set! [tags any/c]
|
||||
[key (or/c symbol? string?)]
|
||||
[value (or/c string? 'clear)])
|
||||
void?]{
|
||||
Sets a generic property on a read-write handle. Symbol keys are converted to
|
||||
upper-case TagLib property names; string keys are passed as supplied. Passing
|
||||
@racket['clear] clears the property.}
|
||||
|
||||
@defproc[(tags-set-values! [tags any/c]
|
||||
[key (or/c symbol? string?)]
|
||||
[values (or/c (listof string?) 'clear)])
|
||||
void?]{
|
||||
Replaces a generic property with zero or more values. Passing @racket['clear]
|
||||
removes the property.}
|
||||
|
||||
@defproc[(tags-append! [tags any/c]
|
||||
[key (or/c symbol? string?)]
|
||||
[value string?])
|
||||
void?]{
|
||||
Appends a value to a generic property on a read-write handle.}
|
||||
|
||||
@defproc[(tags-clear! [tags any/c]
|
||||
[key (or/c symbol? string?)])
|
||||
void?]{
|
||||
Clears a generic property on a read-write handle.}
|
||||
|
||||
@racketblock[
|
||||
(for ([key (in-list (tags-keys tags))])
|
||||
(printf "~a: ~s\n" key (tags-ref tags key)))]
|
||||
(call-with-id3-tags "track.flac"
|
||||
(lambda (tags)
|
||||
(tags-set-values! tags 'composer '("Johann Sebastian Bach"))
|
||||
(tags-set! tags 'discnumber "1")
|
||||
(tags-save! tags))
|
||||
#:mode 'read-write)]
|
||||
|
||||
Generic properties may contain multiple values for a single key. The API keeps
|
||||
those values as lists instead of joining them into one string.
|
||||
@@ -120,8 +195,8 @@ those values as lists instead of joining them into one string.
|
||||
|
||||
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].
|
||||
the picture procedures documented below. It can also be written to another
|
||||
file with @racket[tags-picture!] or @racket[tags-append-picture!].
|
||||
|
||||
@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
|
||||
@@ -130,14 +205,15 @@ 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->description [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)])]
|
||||
@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.
|
||||
recognized.}
|
||||
|
||||
@defproc[(tags-picture->bitmap [tags any/c])
|
||||
(or/c (is-a?/c bitmap%) #f)]{
|
||||
@@ -150,41 +226,85 @@ Reads the embedded picture bytes with @racket[read-bitmap] and returns a
|
||||
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.}
|
||||
@racket[#f] when the tag handle has no picture.}
|
||||
|
||||
@defproc[(make-tags-picture [mimetype string?]
|
||||
[kind integer?]
|
||||
[data (or/c bytes? (is-a?/c bitmap%))]
|
||||
[#:description description string? ""])
|
||||
id3-picture?]{
|
||||
Creates a picture value from encoded image bytes or from a @racket[bitmap%].
|
||||
The MIME type should normally be @racket["image/jpeg"] or @racket["image/png"].}
|
||||
|
||||
@defproc[(make-tags-picture-from-bitmap [bitmap (is-a?/c bitmap%)]
|
||||
[kind integer?]
|
||||
[#:mimetype mimetype string? "image/png"]
|
||||
[#:description description string? ""])
|
||||
id3-picture?]{
|
||||
Creates a picture value by encoding @racket[bitmap] as PNG or JPEG.}
|
||||
|
||||
@deftogether[
|
||||
(@defproc[(tags-picture! [tags any/c]
|
||||
[picture (or/c id3-picture? 'clear)])
|
||||
void?]
|
||||
@defproc[(tags-append-picture! [tags any/c]
|
||||
[picture id3-picture?])
|
||||
void?]
|
||||
@defproc[(tags-clear-picture! [tags any/c]) void?])]{
|
||||
Set, append, or clear embedded artwork on a read-write handle. The procedures
|
||||
use TagLib complex properties underneath. Call @racket[tags-save!] to persist
|
||||
the change.}
|
||||
|
||||
@racketblock[
|
||||
(define ext (tags-picture->ext tags))
|
||||
(define cover
|
||||
(make-tags-picture "image/jpeg" 3 (file->bytes "cover.jpg")
|
||||
#:description "Cover"))
|
||||
|
||||
(when ext
|
||||
(tags-picture->file tags
|
||||
(format "cover.~a" ext)))]
|
||||
(call-with-id3-tags "track.flac"
|
||||
(lambda (tags)
|
||||
(tags-picture! tags cover)
|
||||
(tags-save! tags))
|
||||
#:mode 'read-write)]
|
||||
|
||||
@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.
|
||||
(@defproc[(id3-picture? [v any/c]) boolean?]
|
||||
@defproc[(id3-picture-mimetype [picture id3-picture?]) string?]
|
||||
@defproc[(id3-picture-kind [picture id3-picture?]) integer?]
|
||||
@defproc[(id3-picture-size [picture id3-picture?]) integer?]
|
||||
@defproc[(id3-picture-bytes [picture id3-picture?]) bytes?]
|
||||
@defproc[(id3-picture-description [picture id3-picture?]) string?])]{
|
||||
Access the fields of a picture value. These procedures are useful when the
|
||||
caller wants to process the image bytes directly or pass a picture to another
|
||||
component.}
|
||||
|
||||
@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 contains the keys @racket['valid?], @racket['read-write?],
|
||||
@racket['closed?], @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{Copying tags and pictures}
|
||||
|
||||
The encoder pipeline uses this module for metadata transfer. For FLAC output,
|
||||
@racket[audio-encode] first writes the audio stream and then opens the resulting
|
||||
file with @racket[id3-tags] in read-write mode to copy tags and pictures through
|
||||
TagLib. For Opus output, comments and pictures are supplied to
|
||||
@tt{libopusenc} before encoding starts, because OpusTags are written at the
|
||||
start of the Ogg Opus stream.
|
||||
|
||||
Applications that need explicit metadata editing should use the read-write API
|
||||
directly, as in the examples above.
|
||||
|
||||
@section{Example}
|
||||
|
||||
@racketblock[
|
||||
@@ -209,10 +329,10 @@ 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.
|
||||
A read-only tag handle is a Racket-side snapshot. A read-write tag handle keeps
|
||||
the native TagLib file open until @racket[tags-close!] is called. Setter
|
||||
procedures update both the native file and the Racket-side cache; the changes
|
||||
are persisted only after @racket[tags-save!] succeeds.
|
||||
|
||||
The implementation normalizes generic property names by lower-casing TagLib
|
||||
property keys and converting them to symbols. Values remain lists of strings
|
||||
|
||||
Reference in New Issue
Block a user