3.2 ID3v1 tags

The orginal ID3v1 tag contained title, artist, album, year, comment & genre. The fields are fixed-size (30, 30, 30, 4, 30 & 1 byte, respectively). The original proposal called for filling out the fields with nil (zero) values, but that is not universally implemented (Winamp, for instance, pads fields out with ASCII spaces (i.e. 32 = 0x20)).

Michael Mutschier observed that if the fields were zero-padded, an implementation will likely stop on reading the first nil. Therefore, if the second-to-last byte of a field is nil, a one-byte value could be stored in the last field. He proposed storing the track number in the last byte of the comment field. This became known as ID3v1.1.

A thirty-byte limit quickly became constraining, leading to the ID3v1 “enhanced” specification. The origins of the proposal are unclear to me, but the proposal itself involves prepending a second two-hundred twenty-seven byte block to the ID3v1 block. This would extend the title, artist & album fields by sixty bytes each, adds a thirty-byte free-form genre field, and introduces start-time, end-time, and “speed” fields.

scribbu represents the ID3v1 tag by the GOOPS class <id3v1-tag>:

(define-class <id3v1-tag> ()
  (title      #:init-value ""  #:accessor title       #:init-keyword #:title)
  (artist     #:init-value ""  #:accessor artist      #:init-keyword #:artist)
  (album      #:init-value ""  #:accessor album       #:init-keyword #:album)
  (year       #:init-value '() #:accessor year        #:init-keyword #:year)
  (comment    #:init-value ""  #:accessor comment     #:init-keyword #:comment)
  (genre      #:init-value 255 #:accessor genre       #:init-keyword #:genre)
  (track-no   #:init-value '() #:accessor track-no    #:init-keyword #:track-no)
  (enh-genre  #:init-value '() #:accessor enh-genre  #:init-keyword #:enh-genre)
  (speed      #:init-value '() #:accessor speed       #:init-keyword #:speed)
  (start-time #:init-value '() #:accessor start-time #:init-keyword #:start-time)
  (end-time   #:init-value '() #:accessor end-time   #:init-keyword #:end-time))

The class’ fields include the union of all ID3v1, ID3v1.1 and ID3v1 enhanced fields. All fields above & beyond those present in ID3v1 however, have a default alue of '() (or nil, in Scheme). Whether a given <id3v1-tag> instance is ID3v1, ID3v1.1, and/or ID3v1 enchanced is implicitly determined by whether any of these fields are non-nil.

One can create an <id3v1-tag> instance directly, like any GOOPS class:

(use-modules (oop goops))
(define tag (make <id31-tag> #:title  "The Body of an American"
                             #:artist "Pogues, The"
                             #:album  "Poguetry in Motion"
                             #:year   "1986"
                             #:genre  88))

One can also create an instance from an existing tag on disk:

(use-modules (scribbu))
(define tag (read-id3v1-tag "foo.mp3"))
(format #t "~s - ~s\n" (slot-ref tag #:artist) (slot-ref tag #:title))

<id3v1-tag> instances can be written to disk via write-id3v1-tag: (write-id3v1-tag tag "bar.mp3"). If any of the title, artist or album slotes are longer than thirty characters, or any of the new fields (enhanced genreo, speed, start-time or end-time) are non-nil, it will be written as an ID3v1 enhanced tag.