Книга: Practical Common Lisp
ID3 Tag Header
ID3 Tag Header
With the basic primitive types done, you're ready to switch to a high-level view and start defining binary classes to represent first the ID3 tag as a whole and then the individual frames.
If you turn first to the ID3v2.2 specification, you'll see that the basic structure of the tag is this header:
ID3/file identifier "ID3"
ID3 version $02 00
ID3 flags %xx000000
ID3 size 4 * %0xxxxxxx
followed by frame data and padding. Since you've already defined binary types to read and write all the fields in the header, defining a class that can read the header of an ID3 tag is just a matter of putting them together.
(define-binary-class id3-tag ()
((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))))
- Structure of an ID3v2 Tag
- Defining a Package
- Integer Types
- String Types
- ID3 Tag Header
- ID3 Frames
- Detecting Tag Padding
- Supporting Multiple Versions of ID3
- Versioned Frame Base Classes
- Versioned Concrete Frame Classes
- What Frames Do You Actually Need?
- Text Information Frames
- Comment Frames
- Extracting Information from an ID3 Tag