(: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)))))

Comment Frames

Another commonly used frame type is the comment frame, which is like a text information frame with a few extra fields. Like a text information frame, it starts with a single byte indicating the string encoding used in the frame. That byte is followed by a three-character ISO 8859-1 string (regardless of the value of the string encoding byte), which indicates what language the comment is in using an ISO-639-2 code, for example, 'eng' for English or 'jpn' for Japanese. That field is followed by two strings encoded as indicated by the first byte. The first is a null-terminated string containing a description of the comment. The second, which takes up the remainder of the frame, is the comment text itself.

(define-binary-class comment-frame ()

((encoding u1)

(language (iso-8859-1-string :length 3))

(description (id3-encoded-string :encoding encoding :terminator +null+))

(text (id3-encoded-string

:encoding encoding

:length (bytes-left

(+ 1 ; encoding

3 ; language

(encoded-string-length description encoding t)))))))

As in the definition of the text-info mixin, you can use bytes-left to compute the size of the final string. However, since the description field is a variable-length string, the number of bytes read prior to the start of text isn't a constant. To make matters worse, the number of bytes used to encode description is dependent on the encoding. So, you should define a helper function that returns the number of bytes used to encode a string given the string, the encoding code, and a boolean indicating whether the string is terminated with an extra character.

(defun encoded-string-length (string encoding terminated)

(let ((characters (+ (length string) (if terminated 1 0))))

(* characters (ecase encoding (0 1) (1 2)))))

And, as before, you can define the concrete version-specific comment frame classes and wire them into find-frame-class.

(define-binary-class comment-frame-v2.2 (id3v2.2-frame comment-frame) ())

(define-binary-class comment-frame-v2.3 (id3v2.3-frame comment-frame) ())

(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)))

((string= name 'COM') 'comment-frame-v2.2)

((string= name 'COMM') 'comment-frame-v2.3)

(t

(ecase (length name)

(3 'generic-frame-v2.2)

(4 'generic-frame-v2.3)))))

Extracting Information from an ID3 Tag

Now that you have the basic ability to read and write ID3 tags, you have a lot of directions you could take this code. If you want to develop a complete ID3 tag editor, you'll need to implement specific classes for all the frame types. You'd also need to define methods for manipulating the tag and frame objects in a consistent way (for instance, if you change the value of a string in a text-info-frame, you'll likely need to adjust the size); as the code stands, there's nothing to make sure that happens.[279]

Or, if you just need to extract certain pieces of information about an MP3 file from its ID3 tag—as you will when you develop a streaming MP3 server in Chapters 27, 28, and 29—you'll need to write functions that find the appropriate frames and extract the information you want.

Finally, to make this production-quality code, you'd have to pore over the ID3 specs and deal with the details I

Вы читаете Practical Common Lisp
Добавить отзыв
ВСЕ ОТЗЫВЫ О КНИГЕ В ИЗБРАННОЕ

0

Вы можете отметить интересные вам фрагменты текста, которые будут доступны по уникальной ссылке в адресной строке браузера.

Отметить Добавить цитату