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

(major-version u1)

(revision u1)

(flags u1)

(size id3-tag-size)))

If you have some MP3 files lying around, you can test this much of the code and also see what version of ID3 tags your MP3s contain. First you can write a function that reads an id3-tag, as just defined, from the beginning of a file. Be aware, however, that ID3 tags aren't required to appear at the beginning of a file, though these days they almost always do. To find an ID3 tag elsewhere in a file, you can scan the file looking for the sequence of bytes 73, 68, 51 (in other words, the string 'ID3').[275] For now you can probably get away with assuming the tags are the first thing in the file.

(defun read-id3 (file)

(with-open-file (in file :element-type '(unsigned-byte 8))

(read-value 'id3-tag in)))

On top of this function you can build a function that takes a filename and prints the information in the tag header along with the name of the file.

(defun show-tag-header (file)

(with-slots (identifier major-version revision flags size) (read-id3 file)

(format t '~a ~d.~d ~8,'0b ~d bytes — ~a~%'

identifier major-version revision flags size (enough-namestring file))))

It prints output that looks like this:

ID3V2> (show-tag-header '/usr2/mp3/Kitka/Wintersongs/02 Byla Cesta.mp3')

ID3 2.0 00000000 2165 bytes — Kitka/Wintersongs/02 Byla Cesta.mp3

NIL

Of course, to determine what versions of ID3 are most common in your MP3 library, it'd be handier to have a function that returns a summary of all the MP3 files under a given directory. You can write one easily enough using the walk-directory function defined in Chapter 15. First define a helper function that tests whether a given filename has an mp3 extension.

(defun mp3-p (file)

(and

(not (directory-pathname-p file))

(string-equal 'mp3' (pathname-type file))))

Then you can combine show-tag-header and mp3-p with walk- directory to print a summary of the ID3 header in each file under a given directory.

(defun show-tag-headers (dir)

(walk-directory dir #'show-tag-header :test #'mp3-p))

However, if you have a lot of MP3s, you may just want a count of how many ID3 tags of each version you have in your MP3 collection. To get that information, you might write a function like this:

(defun count-versions (dir)

(let ((versions (mapcar #'(lambda (x) (cons x 0)) '(2 3 4))))

(flet ((count-version (file)

(incf (cdr (assoc (major-version (read-id3 file)) versions)))))

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

versions))

Another function you'll need in Chapter 29 is one that tests whether a file actually starts with an ID3 tag, which you can define like this:

(defun id3-p (file)

(with-open-file (in file :element-type '(unsigned-byte 8))

(string= 'ID3' (read-value 'iso-8859-1-string in :length 3))))

ID3 Frames

As I discussed earlier, the bulk of an ID3 tag is divided into frames. Each frame has a structure similar to that of the tag as a whole. Each frame starts with a header indicating what kind of frame it is and the size of the frame in bytes. The structure of the frame header changed slightly between version 2.2 and version 2.3 of the ID3 format, and eventually you'll have to deal with both forms. To start, you can focus on parsing version 2.2 frames.

The header of a 2.2 frame consists of three bytes that encode a three-character ISO 8859-1 string followed by a three-byte unsigned integer, which specifies the size of the frame in bytes, excluding the six-byte header. The string identifies what type of frame it is, which determines how you parse the data following the size. This is exactly the kind of situation for which you defined the define-tagged-binary-class macro. You can define a tagged class that reads the frame header and then dispatches to the appropriate concrete class using a function that maps IDs to a class names.

(define-tagged-binary-class id3-frame ()

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

(size u3))

(:dispatch (find-frame-class id)))

Now you're ready to start implementing concrete frame classes. However, the specification defines quite a few—63 in version 2.2 and even more in later specs. Even considering frame types that share a common structure to be equivalent, you'll still find 24 unique frame types in version 2.2. But only a few of these are used 'in the wild.' So rather than immediately setting to work defining classes for each of the frame types, you can start by writing a generic frame class that lets you read the frames in a tag without parsing the data within the frames themselves. This will give you a way to find out what frames are actually present in the MP3s you want to process. You'll need this class eventually anyway because the specification allows for experimental frames that you'll need to be able to read without parsing.

Since the size field of the frame header tells you exactly how many bytes long the frame is, you can define a generic-frame class that extends id3-frame and adds a single field, data, that will hold an array of bytes.

(define-binary-class generic-frame (id3-frame)

((data (raw-bytes :size size))))

The type of the data field, raw-bytes, just needs to hold an array of bytes. You can define it like this:

(define-binary-type raw-bytes (size)

(:reader (in)

(let ((buf (make-array size :element-type '(unsigned-byte 8))))

(read-sequence buf in)

buf))

(:writer (out buf)

(write-sequence buf out)))

For the time being, you'll want all frames to be read as generic-frames, so you can define the find-frame-class function used in id3-frame's :dispatch expression to always return generic-frame, regardless of the frame's id.

(defun find-frame-class (id)

(declare (ignore id))

'generic-frame)

Now you need to modify id3-tag so it'll read frames after the header fields. There's only one tricky bit to reading the frame data: although the tag header tells you how many bytes long the tag is, that number

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

0

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

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