(decompressed-size (optional :type 'u4 :if (frame-compressed-p flags)))

(encryption-scheme (optional :type 'u1 :if (frame-encrypted-p flags)))

(grouping-identity (optional :type 'u1 :if (frame-grouped-p flags))))

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

(defun frame-compressed-p (flags) (logbitp 7 flags))

(defun frame-encrypted-p (flags) (logbitp 6 flags))

(defun frame-grouped-p (flags) (logbitp 5 flags))

With these two classes defined, you can now implement the methods on the generic function frame- header-size.

(defmethod frame-header-size ((frame id3v2.2-frame)) 6)

(defmethod frame-header-size ((frame id3v2.3-frame)) 10)

The optional fields in a version 2.3 frame aren't counted as part of the header for this computation since they're already included in the value of the frame's size.

Versioned Concrete Frame Classes

In the original definition, generic-frame subclassed id3-frame. But now id3-frame has been replaced with the two version-specific base classes, id3v2.2- frame and id3v2.3-frame. So, you need to define two new versions of generic- frame, one for each base class. One way to define this classes would be like this:

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

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

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

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

However, it's a bit annoying that these two classes are the same except for their superclass. It's not too bad in this case since there's only one additional field. But if you take this approach for other concrete frame classes, ones that have a more complex internal structure that's identical between the two ID3 versions, the duplication will be more irksome.

Another approach, and the one you should actually use, is to define a class generic-frame as a mixin: a class intended to be used as a superclass along with one of the version- specific base classes to produce a concrete, version-specific frame class. The only tricky bit about this approach is that if generic-frame doesn't extend either of the frame base classes, then you can't refer to the size slot in its definition. Instead, you must use the current-binary-object function I discussed at the end of the previous chapter to access the object you're in the midst of reading or writing and pass it to size. And you need to account for the difference in the number of bytes of the total frame size that will be left over, in the case of a version 2.3 frame, if any of the optional fields are included in the frame. So, you should define a generic function data-bytes with methods that do the right thing for both version 2.2 and version 2.3 frames.

(define-binary-class generic-frame ()

((data (raw-bytes :size (data-bytes (current-binary-object))))))

(defgeneric data-bytes (frame))

(defmethod data-bytes ((frame id3v2.2-frame))

(size frame))

(defmethod data-bytes ((frame id3v2.3-frame))

(let ((flags (flags frame)))

(- (size frame)

(if (frame-compressed-p flags) 4 0)

(if (frame-encrypted-p flags) 1 0)

(if (frame-grouped-p flags) 1 0))))

Then you can define concrete classes that extend one of the version-specific base classes and generic-frame to define version-specific generic frame classes.

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

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

With these classes defined, you can redefine the find-frame-class function to return the right versioned class based on the length of the identifier.

(defun find-frame-class (id)

(ecase (length id)

(3 'generic-frame-v2.2)

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

What Frames Do You Actually Need?

With the ability to read both version 2.2 and version 2.3 tags using generic frames, you're ready to start implementing classes to represent the specific frames you care about. However, before you dive in, you should take a breather and figure out what frames you actually care about since, as I mentioned earlier, the ID3 spec specifies many frames that are almost never used. Of course, what frames you care about depends on what kinds of applications you're interested in writing. If you're mostly interested in extracting information from existing ID3 tags, then you need implement only the classes representing the frames containing the information you care about. On the other hand, if you want to write an ID3 tag editor, you may need to support all the frames.

Rather than guessing which frames will be most useful, you can use the code you've already written to poke around a bit at the REPL and see what frames are actually used in your own MP3s. To start, you need an instance of id3-tag, which you can get with the read-id3 function.

ID3V2> (read-id3 '/usr2/mp3/Kitka/Wintersongs/02 Byla Cesta.mp3')

#<ID3V2.2-TAG @ #x727b2912>

Since you'll want to play with this object a bit, you should save it in a variable.

ID3V2> (defparameter *id3* (read-id3 '/usr2/mp3/Kitka/Wintersongs/02 Byla Cesta.mp3'))

*ID3*

Now you can see, for example, how many frames it has.

ID3V2> (length (frames *id3*))

11

Not too many—let's take a look at what they are.

ID3V2> (frames *id3*)

(#<GENERIC-FRAME-V2.2 @ #x72dabdda> #<GENERIC-FRAME-V2.2 @ #x72dabec2>

#<GENERIC-FRAME-V2.2 @ #x72dabfa2> #<GENERIC-FRAME-V2.2 @

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

0

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

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