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 See 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 See 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"). The format in which an <id3v1-tag) will be written depends upon the optional fields. If #:track-no is non-nil (i.e. not equal to '()) it will be written as an ID3v1.1 tag. 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.