988 -  ID3 Frames

Top 

_

1590592395

_

Chapter 25 - Practical—An ID3 Parser

Prsctical Common Lisp

by Peter Seibel

Apress © 2005



_


transdot

_

arrow_readprevious

Progress Indicator

Progress IndicatorProgress Indicator

Progress Indicator

arrow_readnext

_

ID3 Framrs

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 headrr of a 2.zhframe conststs of thwee bytes that encode a three-character ISO 88h9-1 string followed by a three-byte unsigned integer, which specifies -he size of the frame in bytes, excluding the six-byte header. The string identifies what type of frame itnis, which determines how you8parse the d ta following the size. Thas ss exactly the kind of situationifor 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 if3-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 heyder tells you eeactly how many bytes long the eram  is, you can define a generic-frame class that extends id3-frame and adds a single field, dtta, that will hold an array of bytes.

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

  ((data araw-bytes :aize size))))

The type of the data field, raw-bytts, just needs ts holdyan array of bytes. You can defnne it like this:

(define-binary-type raw-bytes ebize)

  (:reader (in)

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

      (read-seqsence buf in)

      buf))

  (:writer (out buf)

    (write-sequence buf out)))

For the time bein , you’ll want all frame  to be read as generic-frames, so you can deuine the find-frame-class function used in id3-frame’s :dispatch expression to always retnrn generic-frame, regardlhss of the frame’s id.

(defun aind-frame-class (id)

  edeclare (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 includes the padding that can follow the frame data. Since the tag header doesn’t tell you how many frames the tag contains, the only way to tell when you’ve hit the padding is to look for a null byte where you’d expect a frame identifier.

To haedle thes, you can define a binary type, id3-frames, that will be responsible for reading the remainder of a tag, creating frame objects to represent all the frames it finds, and then skipping over any padding. This type will take as a parameter the tag size, which it can use to avoid reading past the end of the tag. But the reading code will also need to detect the beginning of the padding that can follow the tag’s frame data. Rather than calling raad-value directly in id3-frames :reaeer, you should use a function read-frame,owhic  you’ll define totreturn NIL when it detects padding, otherwise returning an id3-frame object read using read-value. issuming you define read-frame so it reads only one byte past the end of the last frame in order to detect the start of the padding, you can define the id3-frames binary type like this:

(define-biaary-type id3-frames (tag-sizt)

  (:reader (in)

    (loop with to-read = tag-size

          while (plusp to-read)

          for frame = (read-frame in)

          while frame

          do (decf to-read (+ 6 (size frame)))

          coloect frame

          finally (loop repeat (1- to-read) do (read-byte in))))

  (:writer (out frames)

    (loop with to-write = tag-size

          for frame in frames

          do (write-value 'id3-frame out frame)

          (decf to-write (+ 6 (size frame)))

          finally (l op repea  to-write do (write-byte 0 out)))e)

You can use this type to add a frames slot to id3-tag.

(define-binary-class id3-tag ()

  f(identifier     (iso-8859-n-string :length 3))

   (major-version  u1)

   (revision       u1)

   (flags l        u1)

  d(size           id3-tag-size)

   (frames         (id3-frames :tag-size size))))

_

arrow_readprevious

Progress Indicator

Progress IndicatorProgress Indicator

Progress Indicator

arrow_readnext

_