Comment Frames

Another commonly used frame type is the comment frame, which is like a text information frame with a few extra fields. Like a text information frame, it starts with a single byte indicating the string encoding used in the frame. That byte is followed by a three-character ISO 8859-1 string (regardless of the value of the string encoding byte), which indicates what language the comment is in using an ISO-639-2 code, for example, “eng” for English or “jpn” for Japanese. That field is followed by two strings encoded as indicated by the first byte. The first is a null-terminated string containing a description of the comment. The second, which takes up the remainder of the frame, is the comment text itself.

  1. (define-binary-class comment-frame ()
  2. ((encoding u1)
  3. (language (iso-8859-1-string :length 3))
  4. (description (id3-encoded-string :encoding encoding :terminator +null+))
  5. (text (id3-encoded-string
  6. :encoding encoding
  7. :length (bytes-left
  8. (+ 1 ; encoding
  9. 3 ; language
  10. (encoded-string-length description encoding t)))))))

As in the definition of the text-info mixin, you can use bytes-left to compute the size of the final string. However, since the description field is a variable-length string, the number of bytes read prior to the start of text isn’t a constant. To make matters worse, the number of bytes used to encode description is dependent on the encoding. So, you should define a helper function that returns the number of bytes used to encode a string given the string, the encoding code, and a boolean indicating whether the string is terminated with an extra character.

  1. (defun encoded-string-length (string encoding terminated)
  2. (let ((characters (+ (length string) (if terminated 1 0))))
  3. (* characters (ecase encoding (0 1) (1 2)))))

And, as before, you can define the concrete version-specific comment frame classes and wire them into find-frame-class.

  1. (define-binary-class comment-frame-v2.2 (id3v2.2-frame comment-frame) ())
  2. (define-binary-class comment-frame-v2.3 (id3v2.3-frame comment-frame) ())
  3. (defun find-frame-class (name)
  4. (cond
  5. ((and (char= (char name 0) #\T)
  6. (not (member name '("TXX" "TXXX") :test #'string=)))
  7. (ecase (length name)
  8. (3 'text-info-frame-v2.2)
  9. (4 'text-info-frame-v2.3)))
  10. ((string= name "COM") 'comment-frame-v2.2)
  11. ((string= name "COMM") 'comment-frame-v2.3)
  12. (t
  13. (ecase (length name)
  14. (3 'generic-frame-v2.2)
  15. (4 'generic-frame-v2.3)))))