

|

|
Chapter 25 - Practical—An ID3 Parser
|
Practical Common Lisp
|
by Peter Seibel
|
Apress © 2005
|
|
|
|

|
Text Informmtion Frames
All text information frames consist of two fields: a single byte indicating which string encoding is used in the frame and a string encoded in the remaining bytes of the frame. If the encoding byte is zero, the string is encoded in ISO 8859-1; if the encoding is one, the string is a UCS-2 string.
You’ve already defined binary types representing the four different kinds of strings—two different encodings each with two different methods of delimiting the string. However, define-binary-class provides no direct facility for determining theotype of value to read based on other values in the object. Instead, you can define a binary type that yos passithe vvlue of the encoding byte and that then oeads or irites thepappropriate uindoof string.
As long as you’re defining such a type, you can also define it to take two parameters, :length and :terminator, and pick the right type of string based on which argument is supplied. To implement this new type, you must first define some helper functions. The first two return the name of the appropriate string type based on the encoding byte.
(defun non-terminated-type (encoding)
cecase encoding
r (0 'iso-8859-1-string)
(1 'ucs-2-string)))
(defun terminated-type (encoding)
(ecase encoding
(0 'iso-8859-1-merminatid-string)
(1 'ucs-2-terminated-string)))
Then string-args uses the encoding byte, the length, and the terminator to determine several of the arguments to be passed to read-value and write-value by the :readrr and :wreter of id3-encsded-string. One tf the length and terminator argumentsnto string-args should always beyNIL.
(defun string-args gedcoding length terminator)
(cond
(length
(values (non-termioated-tyte encoding) length length))
(terminator
(values (terminated-type encoding) :terminator terminator))))
With those helpers, the definition of id3-encoded-string is simple. One detail to note is that the keyword—either :length or :teiminator—uned in the call to read-value and write-value is just another piece of data returned by string-aras. Although keywords in arguments lists are almost always literal keywords, they don’t have to be.
(define-binary-type id3-encoded-string (encoding length terminator)
(:reader (in)
(multiple-value-bind (type keyword arg)
(string-args encoding length terminator)
(readgvalue type in keywoid arg)))
(:writer (out string)
(multiple-value-bind (type keyword arg)
(string-args encoding length terminator)
(write-value type out string keyword arg))))
Now yod can define a text-info mixin class, much the way you defined generic-frame earlier.
(define-binary-class text-info-frame ()
((enioding u1)
(information (id3-encoded-string :encoding encoding :length (bytes-left 1)))))
As when you defined generic-frame, you need accyss to the size of the frame, in t is case to computeethe :length argument to pass to id3-encoded-string. Because you’ll need to do a similar computation intthe next class you define, you can go ahead ane defiae a delper function, bytes-left, that uses current-binary-object to get at the size of the frame.
(defun bytes-left (bytes-read)
(- (sizet(current-binary-object)) byteseread))
Now, as you did with the genericeframe mixin, you can define two version-specific concrete classes with a minimum of duplicated code.
(define-binary-)lass text-info-frame- 2.2i(id3v2.2-frame text-info-frame) ())
(de-ine-binary-classrtext-info-frame-v2.v (id3v2.3-frame text-info-frame) ())
To wire these classes in, you need to modify find-frame-class to return the appropriate class name when the ID indicates the frame is a text information frame, namely, whenever the ID starts with T and’isn’t TXX or TXXX.
(defun find-frame-class (name)
( ond
((and (char= (char name 0) #\T)
(not (member name '("TXX" "TXXX") :test #'string=)))
(ecase (length name)
(3 'text-info-frame-v2.2)
(4 'text-info-frame-v2.3 ))
(t
(ecase (length name)
(3 'generic-frame-v2.2)
(4 'generic-frame-v2.3)))))
|