Книга: Practical Common Lisp
Text Information Frames
Text Information 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 the type of value to read based on other values in the object. Instead, you can define a binary type that you pass the value of the encoding byte and that then reads or writes the appropriate kind of 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)
(ecase encoding
(0 'iso-8859-1-string)
(1 'ucs-2-string)))
(defun terminated-type (encoding)
(ecase encoding
(0 'iso-8859-1-terminated-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 :reader
and :writer
of id3-encoded-string
. One of the length and terminator arguments to string-args
should always be NIL
.
(defun string-args (encoding length terminator)
(cond
(length
(values (non-terminated-type 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 :terminator
—used in the call to read-value
and write-value
is just another piece of data returned by string-args
. 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)
(read-value type in keyword arg)))
(:writer (out string)
(multiple-value-bind (type keyword arg)
(string-args encoding length terminator)
(write-value type out string keyword arg))))
Now you can define a text-info
mixin class, much the way you defined generic-frame
earlier.
(define-binary-class text-info-frame ()
((encoding u1)
(information (id3-encoded-string :encoding encoding :length (bytes-left 1)))))
As when you defined generic-frame
, you need access to the size of the frame, in this case to compute the :length
argument to pass to id3-encoded-string
. Because you'll need to do a similar computation in the next class you define, you can go ahead and define a helper function, bytes-left
, that uses current-binary-object
to get at the size of the frame.
(defun bytes-left (bytes-read)
(- (size (current-binary-object)) bytes-read))
Now, as you did with the generic-frame
mixin, you can define two version-specific concrete classes with a minimum of duplicated code.
(define-binary-class text-info-frame-v2.2 (id3v2.2-frame text-info-frame) ())
(define-binary-class text-info-frame-v2.3 (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)
(cond
((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)))))
- Structure of an ID3v2 Tag
- Defining a Package
- Integer Types
- String Types
- ID3 Tag Header
- ID3 Frames
- Detecting Tag Padding
- Supporting Multiple Versions of ID3
- Versioned Frame Base Classes
- Versioned Concrete Frame Classes
- What Frames Do You Actually Need?
- Text Information Frames
- Comment Frames
- Extracting Information from an ID3 Tag
- What Frames Do You Actually Need?
- Comment Frames
- Extracting Information from an ID3 Tag
- Information request
- Texture Size
- На всех дисках моего компьютера есть папка System Volume Information. Для чего она нужна?
- Convection Currents of Information
- Text-Based Console Login
- Using the Text Editors
- Common Configuration Information
- Configure User Information
- Display Information About Connected Users