IdEst |
|
ID3 Editing and Scripting Tool |
Sergey Poznyakoff |
Idest
offers a scripting facility, which makes it possible
to extend its functionality beyond the basic operations, described in
previous chapters. Scripts must be written in Scheme, using
‘Guile’, the
GNU’s Ubiquitous Intelligent Language for Extensions. For
information about the language, refer to Revised(5)
Report on the Algorithmic Language Scheme. For a detailed
description of Guile and its features, see
Overview in The Guile Reference Manual.
The scripting mode is enabled when the option
--script (-S) is given in the command line. This
option stops further option processing, so any other idest
command line options must be given before it. The argument
to this option specifies the name of the script file:
$ idest --script list.scm *.mp3
You can omit the ‘.scm’ suffix, as idest
will try it
automatically (see below).
When this option is given, the following operations are performed:
This step is omitted if the program is given the --no-init-files (-N) option.
When a startup file is loaded, the list of files which were to be tried after it is passed to it as arguments. This allows for chain-loading all files in the list using the following code:
(let load-loop ((name-list (cdr (command-line)))) (if (not (null? name-list)) (let ((name (car name-list))) (load-loop (cdr name-list)) (if (file-exists? name) (primitive-load name)))))
%load-path
.
The default load path is formed as follows:
version-site-dir . package-site-dir guile-site-dir %load-path
where the components are as follows:
The standard Guile load path (see Build Config in The Guile Reference Manual).
This directory is selected at compile time using the rules below.
Its value is returned by the (%idest-guile-site-dir)
primitive:
The reason for using this directory is described in http://www.gnu.org.ua/software/gint/#guile-site-dir.
If guile-site-dir
coincides with the standard %site-dir
,
this part is omitted, because the latter is always present in the
%load-path
.
This is the directory for installing version-independent
idest
files. It is formed as follows:
guile-site-dir
/idest
This value is returned by the (%idest-package-site-dir)
primitive.
This is the directory for installing version-dependent
idest
files. It is formed as follows:
package-site-dir
/2.1
This value is returned by the (%idest-version-site-dir)
primitive.
The load path can be modified using the --load-path (-P) and --prepend-load-path (-p) command line options. Both options take as argument a list of directory names, separated by colons. The --load-path option adds these directories to the tail of the load path list. The --prepend-load-path option adds them to the head of the load path list.
The script is loaded via primitive-load-path
(see primitive-load-path in The Guile Reference Manual), so
idest
will consult the %load-extensions
list and
try suffixes from that list as described in
%load-extensions in The Guile Reference Manual).
The script can access command line arguments via the usual
command-line
function (see command-line in The Guile Reference Manual). It
can also modify the argument list (e.g. by removing its command
line options). It must not, however, modify ‘argv[0]’. Any
changes it does to the argument list become visible to
idest
. The only requirement is that the modified argument
list consist of the script name (as argv[0]) and input file names.
The main function must be declared as:
It takes two arguments. The file argument supplies the name of
the file being processed. The frames argument is a list of ID3
frames read from that file. Each element of frames is a pair,
with the frame name in its car
and an association list of
frame properties in its cdr
.
The properties are identified by property names, which are Scheme symbols. The following property names are defined:
Value of this frame, as a string.
Frame description. It is a string, verbosely describing the frame. For example, the description of ‘TRCK’ frames is ‘Track number/position in set’.
These are the same descriptions that are output with the --describe option (see describe).
Unsupported or partially-supported frames contain only this property.
Its value is a list of frame fields. Each field is represented by a
triplet ‘(ord type value)’, where ord is the
ordinal number of that field in frame, type is its type (integer) and
value is its value. If type is one of numeric types,
value is the numeric value converted to string (as per
number->string
). If type is a string type, value
contains the string in the appropriate encoding. Otherwise,
value holds the field value as a binary string. Each byte in
such a string is represented by two hexadecimal digits. For example,
‘AB\n’ is represented as ‘41420A’.
More properties are defined at a per-frame basis to represent
frame qualifiers. They are named after corresponding qualifiers
as listed in --list-frames
output (see describe). For
example, for ‘comment’ (‘COMM’) frames:
A three-letter code of the language in which the text is written.
Content descriptor.
The mode in which input files are open is controlled by the
idest-readonly
variable:
This is a boolean variable that indicates whether idest-main
can modify tag frames. If its value is #t
, the function return
value will be ignored, and input files will be opened in read-only
mode. This is the default.
If idest-readonly
is ‘#f’ the idest-main
function
should return the new list of frames. If it returns an empty list,
all existing frames will be deleted. If the function chooses not
to modify any frames, it must return #f
.
The two following sections show how to write script files. The
sample scripts they discuss can be found in subdirectory
examples of the idest
distribution.
This section illustrates how to use the scripting facility for listing the contents of ID3 tags.
The simplest way to list all frames using a Guile script is:
;; list1.scm -- lists all frames. (define (idest-main name frames) (display name) (newline) (for-each (lambda (frame) (display frame) (newline)) frames))
Here is a sample output:
$ idest --script list1.scm track01.scm track01.mp3 (TIT2 (descr . Title/songname/content description) (text . Cor i arbre)) (TRCK (descr . Track number/position in set) (text . 1)) (COMM (descr . Comments) (condesc . Bit_Rate) (lang . eng) (text . 320)) (TENC (descr . Encoded by) (text . Myencoder 1.0)) (COMM (descr . Comments) (condesc . Sample_Rate) (lang . eng) (text . 44100))
As mentioned above, a script can access the command-line arguments. To illustrate this, let’s modify the list1.scm to display only a subset of frames, given as a comma-separated list in the first argument. To do so, we will need a list of requested frames:
(define frame-list '())
The main function consults this list to see whether to display a frame:
(define (idest-main name frames) (display name) (newline) (for-each (lambda (frame) (if (member (car frame) frame-list) (begin (display frame) (newline)))) frames))
Finally, the following code initializes frame-list
from the
first argument and removes that argument from the list seen by
idest
. Note that the 0th argument is the name of the
script itself, and it should not be modified.
(let ((cmd (command-line))) (cond ((< (length cmd) 3) (error "usage: idest -S list2 FRAME-LIST FILE...") (exit 1)) (else (set! frame-list (string-split (list-ref cmd 1) #\,)) (set-program-arguments (cons (car cmd) (list-tail cmd 2))))))
The full script text is then:
;; list2.scm -- lists only requested frames. (define frame-list '()) (define (idest-main name frames) (display name) (newline) (for-each (lambda (frame) (if (member (car frame) frame-list) (begin (display frame) (newline)))) frames)) (let ((cmd (command-line))) (cond ((< (length cmd) 3) (error "usage: idest -S list2 FRAME-LIST FILE...") (exit 1)) (else (set! frame-list (string-split (list-ref cmd 1) #\,)) (set-program-arguments (cons (car cmd) (list-tail cmd 2))))))
Sample usage:
$ idest --script list2 TIT2,TENC track01.scm (TIT2 (descr . Title/songname/content description) (text . Cor i arbre)) (TENC (descr . Encoded by) (text . Myencoder 1.0))
A more elaborate example will print, for each input file, its name, followed by the title, artist name and year, as shown in this sample output:
$ idest -S shortlist *.mp3 dnr.mp3: Diamonds & Rust by Joan Baez, 1975 ams.mp3: Amsterdam, by Jacques Brel, 1968
To implement this, we would need a function that returns the value of
a given frame from the frame list. Remember, that the latter is a
list of pairs, so the task is achieved easily by using the
assoc-ref
function:
(define (get-frame code frames) (or (assoc-ref (or (assoc-ref frames code) '()) 'text) "unknown"))
The inner assoc-ref
selects a requested frame. An
empty list is returned if such a frame is not found. The outer
assoc-ref
selects the ‘text’ property.
Now, we define the main function:
(define (idest-main name frames) (format #t "~A: ~A by ~A, ~A~%" name (get-frame "TIT2" frames) ; Title (get-frame "TPE1" frames) ; Artist (get-frame "TDRC" frames))) ; Year
This section illustrates how to write scripts that modify ID3 tags.
We will write a script which creates a new value for the ‘title’
(TIT2
) frame from the name of the input file. The title is
created using the following algorithm:
Here is the implementation:
;; settitle.scm - set title (TIT2) frame based on ;; the file name. (use-modules (ice-9 regex) (srfi srfi-13)) (define (idest-main file frames) (cond ((string-match "(.*)\\.mp3" file) => (lambda (match) (cons (cons "TIT2" (list (cons 'text (string-map (lambda (c) (if (char=? c #\_) #\space c)) (match:substring match 1))))) ;; (filter (lambda (elt) (not (string=? (car elt) "TIT2"))) frames)))) (else #f))) (set! idest-readonly #f)
An example of using this script on all files in the current directory:
$ idest --script settitle *.mp3
Formats are advanced scripting feature which allows for extending
idest
output by writing an appropriate script in Scheme. A
format is invoked using the --format (-H) command
line option. The format name is given as argument to that option.
Similarly to the --source option, the --format option stops
further argument processing and passes the rest of arguments to the
format module, which is supposed to remove its option arguments and
leave only input file names. For example:
$ idest --format=framelist -Q -l *.mp3
This example invokes idest
with the ‘framelist’ format
(see framelist). The -Q and -l flags are format
options.
The source for format module name must be saved in the file named name.scm located in the subdirectory idest/format somewhere in the Guile load path. It must begin with the following clause:
(define-module (idest format name))
The module must define and export the ‘idest-main’ function,
whose calling convention and return type is the same as that in
the usual idest
scripts (see idest-main). For example,
the following is a simplified version of the ‘framelist’ module
(see framelist):
(define-module (idest format framelist)) (define frame-list '()) (define-public (idest-main name frames) (for-each (lambda (elt) (cond ((member (car elt) frame-list) (display (car elt)) (newline)))) frames))
If the module needs to process command line arguments, it may not do
so in the main code, as the usual idest
modules do.
Instead, it should export a special function, ‘idest-init’,
defined as:
(define-public (idest-init) ...)
This function analyzes the command line, removes the consumed modules options and returns. For example:
(define-public (idest-init) (let ((cmd (command-line))) (cond ((< (length cmd) 3) (error "usage: idest --format=framelist FRAME-LIST FILE...") (exit 1)) (else (set! frame-list (string-split (list-ref cmd 1) #\,)) (set-program-arguments (cons (car cmd) (list-tail cmd 2)))))))
The module should also export the symbol ‘description’, which should contain a string with a concise description of the module. This description will be shown in the --format=help output (see help format). For example:
(define-public description "display a list of frames defined in each file")
Idest
is shipped with a set of predefined formats. These
formats are found in the scheme/idest/format subdirectory of
the source tree. They are installed into the
‘version-site-dir’/format directory (see version-site-dir).
The ‘help’ format searches the load path for available format modules and lists them. For each module its name and short description are shown on a separate line. The output is sorted alphabetically by the format name:
$ idest --format=help framelist: display a list of frames defined in each file lyrics: display lyrics (the USLT content), if present pic: show attached picture (APIC frame) or save it on disk shortlist: display title, artist name and year
If ‘help’ is used with the --which (-w) option, the format includes the directory where the module is found:
$ idest --format=help --which framelist (/usr/share/idest/format): display a list of frames defined in each file ...
The ‘framelist’ format displays a list of ID3 frames present in each input file, e.g.:
$ idest --format=framelist file.mp3 TIT2 TRCK COMM TENC COMM
The following command line options are understood:
Display all qualifiers. For example:
$ idest --format=framelist --full file.mp3 TIT2 descr="Title/songname/content description" TRCK descr="Track number/position in set" COMM descr="Comments" lang="eng" condesc="" TENC descr="Encoded by" COMM descr="Comments" lang="cat" condesc=""
Display only frames from flist, which is a list of frame names, separated by commas.
Display frames in qualified form:
$ idest --format=framelist --qualified file.mp3 TIT2 TRCK COMM:eng: TENC COMM:cat:
Fit output on single-line, e.g.:
$ idest --format=framelist --single-line file.mp3 TIT2,TRCK,COMM,TENC,COMM
Show a short help summary
The ‘lyrics’ format displays the lyrics (as found in the ‘USLT’ frame). The text is preceded by the song title from the ‘TIT2’ frame, e.g.:
$ idest --format lyrics file.mp3 How doth the little How doth the little crocodile Improve his shining tail, And pour the waters of the Nile On every golden scale! How cheerfully he seems to grin, How neatly spreads his claws, And welcomse little fishes in With gently smiling jaws!
If the environment variable PAGER
is set, its value is used to
paginate the output.
This module supports the following command line options:
Select ‘USLT’ frames with name as the value of their ‘lang’ qualifier.
Select ‘USLT’ frames with text as the value of their ‘condesc’ qualifier.
Show a short help summary
The ‘pic’ format displays or stores on disk attached pictures. It supports the following options:
Use prog to view images (default: xv
).
Look for pictures with this descriptive text.
Look for pictures with this MIME type.
Store pictures on disk, instead of displaying them. The picture names are created by expanding the file name template, given with the following option:
Set the template for output file names (implies –store). The template can contain the following meta-characters:
Char | Expands to |
---|---|
~D | Input file directory part |
~N | Input file base name |
~C | Content description |
~T | Mime type without the ‘image/’ prefix |
~P | Picture type |
~I | PID of the idest process |
The default template is ‘/tmp/~I-~N.~T’.
Show a short help summary
The ‘shortlist’ format module is similar to the ‘shortlist.scm’ example program, discussed in shortlist example. It does not take any command line options – everything after the format name is treated as file names:
$ idest --format=shortlist *.mp3 dnr.mp3: Diamonds & Rust by Joan Baez, 1975 ams.mp3: Amsterdam, by Jacques Brel, 1968
Batch modules or batches are idest
module files
located in a set of predefined directories which apply a set of
modifications to the argument files. In other words, batches are
file-modifying counterpart of formats. A batch is invoked using the
--batch (-B) command line option. The batch name
is given as argument to that option. Similarly to the --source
and --format options, the --batch option stops
further argument processing and passes the rest of arguments to the
batch module, which is supposed to remove its option arguments and
leave only the input file names. For example:
$ idest --batch=setpic -f cover.png file.mp3
In this example, ‘setpic’ is the batch module name, ‘-f cover.png’ are its arguments (see setpic), and ‘file.mp3’ is the argument file.
The rules for writing batch modules are similar to those for formats (see format modules) with only few differences.
The source for format module name must be saved in the file named name.scm located in the subdirectory idest/batch somewhere in the Guile load path. It must begin with the following clause:
(define-module (idest batch name))
The module must define and export the ‘idest-main’ function,
whose calling convention is the same as that in
the usual idest
scripts (see idest-main). This function
must return the new list of frames. If it returns an empty list,
all existing frames will be deleted. If the function chooses not
to modify any frames, it must return #f
.
If the module needs to process command line arguments, it should do so in the function ‘idest-init’, defined as:
(define-public (idest-init) ...)
Finally, the module should export the symbol ‘description’ with a concise description of the module. This description will be shown in the --batch=help output (see help batch).
To illustrate this, here is the code for module ‘delfrm’, which removes the requested frames from all argument files:
(define-module (idest batch delfrm)) (define-public description "remove requested frames from the input files") (define frame-list '()) (define-public (idest-main) (filter (lambda (frame) (not (member (car frame) frame-list))) frames)) (define-public (idest-init) (let ((cmd (command-line))) (cond ((< (length cmd) 3) (error "usage: idest --batch=delfrm FRAME-LIST FILE...") (exit 1)) (else (set! frame-list (string-split (list-ref cmd 1) #\,)) (set-program-arguments (cons (car cmd) (list-tail cmd 2)))))))
Idest
is shipped with a set of predefined batch modules. These
modules are found in the scheme/idest/batch subdirectory of
the source tree. They are installed into the
‘version-site-dir’/batch directory (see version-site-dir).
The ‘help’ batch searches the load path for available batch modules and lists them. For each module its name and short description are shown on a separate line. The output is sorted alphabetically by the format name:
$ idest --format=help setlyrics: set song lyrics (USLT frame) from a file setpic: set attached picture from a file
If ‘help’ is used with the --which (-w) option, the format includes the directory where the module is found:
$ idest --format=help --which setlyrics (/usr/share/idest/format): set song lyrics (USLT frame) from a file ...
The ‘setlyrics’ batch reads the text from the specified file (or standard input, if no file is given) and stores it in the ‘USLT’ frame. It supports the following command line options:
Read text from file (default: stdin).
Set language in which the lyrics is written, i.e. the value of the ‘lang’ qualifier (default: ‘eng’).
Set content description.
Show a short help summary
The ‘setpic’ module reads a picture from a supplied file and attaches it to the argument files. It supports the following options:
Read picture from file. This option is required.
Set the value of ‘condesc’ qualifier.
Set MIME type. By default it is deduced from the picture file suffix.
Set picture type (a decimal number). Default is ‘0’.
Show a short help summary
For example:
$ idest --batch setpic --file cover.png \ --description='Album Cover' file.mp3
When writing a script which modifies tags, it is good idea to test
it before applying it to your data. Idest
provides a
special option for that: --dry-run (-n, e.g.:
$ idest --dry-run --script settitle *.mp3
This will run your script as usual, but instead of applying the
changes to the input files, idest
will verbosely print
results of each invocation of ‘idest-main’. When
--dry-run is used, input files are opened in read-only mode.
This option works with batch files as well, e.g.:
$ idest --dry-run --batch delfrm *.mp3
Here is an example of the dry-run output, obtained from the command above:
dry-run: loading ../examples/settitle.scm ... dry-run: loading /usr/share/guile/1.8/ice-9/regex.scm ... dry-run: loading /usr/share/guile/1.8/srfi/srfi-13.scm ... File Tinc_un_clavell_per_a_tu.mp3 (TIT2 (text . Tinc un clavell per a tu)) (TALB (descr . Album/movie/show title) (text . Maremar)) ...
The first frame shown (‘TIT2’) was produced by settitle.scm (see the previous chapter). Rest of frames come from the input file itself.
Notice the diagnostics lines which start with ‘dry-run’. In
dry-run mode idest
verbosely reports the full file names of
all files it loads. In this particular case, the line
dry-run: loading ../examples/settitle.scm ...
shows the full path of the script file itself, whereas the two lines
dry-run: loading /usr/share/guile/1.8/ice-9/regex.scm ... dry-run: loading /usr/share/guile/1.8/srfi/srfi-13.scm ...
reflect the use-modules
clause at the beginning of
settitle.scm (see settitle.scm).
The ‘dry-run’ mode is actually implemented as a usual
idest
Guile script, named dry-run.scm. The
script is installed to the package script directory. Its
source can be found in the subdirectory scheme of the
idest
distribution.
This document was generated on March 11, 2017 using makeinfo.
Verbatim copying and distribution of this entire article is permitted in any medium, provided this notice is preserved.