#x72dac08a>

#<GENERIC-FRAME-V2.2 @ #x72dac16a> #<GENERIC-FRAME-V2.2 @ #x72dac24a>

#<GENERIC-FRAME-V2.2 @ #x72dac32a> #<GENERIC-FRAME-V2.2 @ #x72dac40a>

#<GENERIC-FRAME-V2.2 @ #x72dac4f2> #<GENERIC-FRAME-V2.2 @ #x72dac632>

#<GENERIC-FRAME-V2.2 @ #x72dac7b2>)

Okay, that's not too informative. What you really want to know are what kinds of frames are in there. In other words, you want to know the ids of those frames, which you can get with a simple MAPCAR like this:

ID3V2> (mapcar #'id (frames *id3*))

('TT2' 'TP1' 'TAL' 'TRK' 'TPA' 'TYE' 'TCO' 'TEN' 'COM' 'COM' 'COM')

If you look up these identifiers in the ID3v2.2 spec, you'll discover that all the frames with identifiers starting with T are text information frames and have a similar structure. And COM is the identifier for comment frames, which have a structure similar to that of text information frames. The particular text information frames identified here turn out to be the frames for representing the song title, artist, album, track, part of set, year, genre, and encoding program.

Of course, this is just one MP3 file. Maybe other frames are used in other files. It's easy enough to discover. First define a function that combines the previous MAPCAR expression with a call to read-id3 and wraps the whole thing in a DELETE- DUPLICATES to keep things tidy. You'll have to use a :test argument of #'string= to DELETE-DUPLICATES to specify that you want two elements considered the same if they're the same string.

(defun frame-types (file)

(delete-duplicates (mapcar #'id (frames (read-id3 file))) :test #'string=))

This should give the same answer except with only one of each identifier when passed the same filename.

ID3V2> (frame-types '/usr2/mp3/Kitka/Wintersongs/02 Byla Cesta.mp3')

('TT2' 'TP1' 'TAL' 'TRK' 'TPA' 'TYE' 'TCO' 'TEN' 'COM')

Then you can use Chapter 15's walk-directory function along with mp3-p to find every MP3 file under a directory and combine the results of calling frame-types on each file. Recall that NUNION is the recycling version of the UNION function; since frame-types makes a new list for each file, this is safe.

(defun frame-types-in-dir (dir)

(let ((ids ()))

(flet ((collect (file)

(setf ids (nunion ids (frame-types file) :test #'string=))))

(walk-directory dir #'collect :test #'mp3-p))

ids))

Now pass it the name of a directory, and it'll tell you the set of identifiers used in all the MP3 files under that directory. It may take a few seconds depending how many MP3 files you have, but you'll probably get something similar to this:

ID3V2> (frame-types-in-dir '/usr2/mp3/')

('TCON' 'COMM' 'TRCK' 'TIT2' 'TPE1' 'TALB' 'TCP' 'TT2' 'TP1' 'TCM'

'TAL' 'TRK' 'TPA' 'TYE' 'TCO' 'TEN' 'COM')

The four-letter identifiers are the version 2.3 equivalents of the version 2.2 identifiers I discussed previously. Since the information stored in those frames is exactly the information you'll need in Chapter 27, it makes sense to implement classes only for the frames actually used, namely, text information and comment frames, which you'll do in the next two sections. If you decide later that you want to support other frame types, it's mostly a matter of translating the ID3 specifications into the appropriate binary class definitions.

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

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

0

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

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