id3-frame.lisp 42 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959
  1. ;;; -*- Mode: Lisp; show-trailing-whitespace: t; Base: 10; indent-tabs: nil; Syntax: ANSI-Common-Lisp; Package: ID3-FRAME; -*-
  2. ;;; Copyright (c) 2013, Mark VandenBrink. All rights reserved.
  3. (in-package #:id3-frame)
  4. (log5:defcategory cat-log-id3-frame)
  5. (defmacro log-id3-frame (&rest log-stuff) `(log5:log-for (cat-log-id3-frame) ,@log-stuff))
  6. (define-condition id3-frame-condition ()
  7. ((location :initarg :location :reader location :initform nil)
  8. (object :initarg :object :reader object :initform nil)
  9. (messsage :initarg :message :reader message :initform "Undefined Condition"))
  10. (:report (lambda (condition stream)
  11. (format stream "id3-frame condition at location: <~a> with object: <~a>: message: <~a>"
  12. (location condition) (object condition) (message condition)))))
  13. (defmethod print-object ((me id3-frame-condition) stream)
  14. (format stream "location: <~a>, object: <~a>, message: <~a>" (location me) (object me) (message me)))
  15. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ID3 header/extended header/v2.1 header ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  16. (defclass id3-header ()
  17. ((version :accessor version :initarg :version :initform 0 :documentation "ID3 version: 2, 3, or 4")
  18. (revision :accessor revision :initarg :revision :initform 0 :documentation "ID3 revision---is this ever non-zero?")
  19. (flags :accessor flags :initarg :flags :initform 0 :documentation "ID3 header flags")
  20. (size :accessor size :initarg :size :initform 0 :documentation "size of ID3 info")
  21. (ext-header :accessor ext-header :initarg :ext-header :initform nil :documentation "holds v2.3/4 extended header")
  22. (frames :accessor frames :initarg :frames :initform nil :documentation "holds ID3 frames")
  23. (v21-tag-header :accessor v21-tag-header :initarg :v21-tag-header :initform nil :documentation "old-style v2.1 header (if present)"))
  24. (:documentation "The ID3 header, found at start of file"))
  25. (defun is-valid-mp3-file (mp3-file)
  26. "Make sure this is an MP3 file. Look for ID3 header at begining (versions 2, 3, 4)
  27. and/or end (version 2.1)"
  28. (log5:with-context "is-valid-mp3-file"
  29. (stream-seek mp3-file 0 :start)
  30. (let* ((id3 (stream-read-string-with-len mp3-file 3))
  31. (version (stream-read-u8 mp3-file))
  32. (tag))
  33. (stream-seek mp3-file 128 :end)
  34. (setf tag (stream-read-string-with-len mp3-file 3))
  35. (stream-seek mp3-file 0 :start)
  36. (log-id3-frame "id3 = ~a, version = ~d" id3 version)
  37. (or (and (string= "ID3" id3)
  38. (or (= 2 version) (= 3 version) (= 4 version)))
  39. (string= tag "TAG")))))
  40. (defclass v21-tag-header ()
  41. ((title :accessor title :initarg :title :initform nil)
  42. (artist :accessor artist :initarg :artist :initform nil)
  43. (album :accessor album :initarg :album :initform nil)
  44. (year :accessor year :initarg :year :initform nil)
  45. (comment :accessor comment :initarg :comment :initform nil)
  46. (track :accessor track :initarg :track :initform nil :documentation "some taggers allow the last 2 bytes of comment to be used as track number")
  47. (genre :accessor genre :initarg :genre :initform nil))
  48. (:documentation "ID3 V2.1 old-style tag. If present, found in last 128 bytes of file."))
  49. (defmethod vpprint ((me v21-tag-header) stream)
  50. (with-slots (title artist album year comment track genre) me
  51. (format stream "title = <~a>, artist = <~a>, album = <~a>, year = <~a>, comment = <~a>, track = <~d>, genre = ~d (~a)"
  52. title artist album year comment track genre (mp3-tag:get-id3v1-genre genre))))
  53. (defmethod initialize-instance ((me v21-tag-header) &key instream)
  54. "Read in a V2.1 tag. Caller will have stream-seek'ed file to correct location and ensured that TAG was present"
  55. (log5:with-context "v21-frame-initializer"
  56. (log-id3-frame "reading v2.1 tag")
  57. (with-slots (title artist album year comment genre track) me
  58. (setf title (upto-null (stream-read-string-with-len instream 30)))
  59. (setf artist (upto-null (stream-read-string-with-len instream 30)))
  60. (setf album (upto-null (stream-read-string-with-len instream 30)))
  61. (setf year (upto-null (stream-read-string-with-len instream 4)))
  62. (setf comment (stream-read-string-with-len instream 30))
  63. ;; In V21, a comment can be split into comment and track#
  64. ;; find the first #\Null then check to see if that index < 28. If so, the check the last two bytes being
  65. ;; non-zero---if so, then track can be set to integer value of last two bytes
  66. (let ((trimmed-comment (upto-null comment))
  67. (trck 0))
  68. (when (<= (length trimmed-comment) 28)
  69. (setf (ldb (byte 8 8) trck) (char-code (aref comment 28)))
  70. (setf (ldb (byte 8 0) trck) (char-code (aref comment 29)))
  71. (setf comment trimmed-comment)
  72. (if (> trck 0)
  73. (setf track trck)
  74. (setf track nil))))
  75. (setf genre (stream-read-u8 instream))
  76. (log-id3-frame "v21 tag: ~a" (vpprint me nil)))))
  77. (defclass id3-ext-header ()
  78. ((size :accessor size :initarg :size :initform 0)
  79. (flags :accessor flags :initarg :flags :initform 0)
  80. (padding :accessor padding :initarg :padding :initform 0)
  81. (crc :accessor crc :initarg :crc :initform nil))
  82. (:documentation "class representing a V2.3/4 extended header"))
  83. (defmacro ext-header-crc-p (flags) `(logbitp 14 ,flags))
  84. ;;; XXX I almost certainly don't have handling of extended headers complete/correct wrt FLAGS
  85. (defmethod initialize-instance ((me id3-ext-header) &key instream)
  86. "Read in the extended header. Caller will have stream-seek'ed to correct location in file.
  87. Note: extended headers are subject to unsynchronization, so make sure that INSTREAM has been made sync-safe."
  88. (with-slots (size flags padding crc) me
  89. (setf size (stream-read-u32 instream))
  90. (setf flags (stream-read-u16 instream))
  91. (setf padding (stream-read-u32 instream))
  92. (when (not (zerop flags))
  93. ;; at this point, we have to potentially read in other fields depending on flags.
  94. ;; for now, just error out...
  95. (assert (zerop flags) () "non-zero extended header flags = ~x, check validity"))))
  96. ;;(when (ext-header-crc-p flags)
  97. ;; (setf crc (stream-read-u32 instream)))))
  98. (defmethod vpprint ((me id3-ext-header) stream)
  99. (with-slots (size flags padding crc) me
  100. (format stream "extended header: size: ~d, flags: ~x, padding ~:d, crc = ~x~%"
  101. size flags padding crc)))
  102. ;;; NB: v2.2 only really defines bit-7. It does document bit-6 as being the compression flag, but then states
  103. ;;; that if it is set, the software should "ignore the entire tag if this (bit-6) is set"
  104. (defmacro header-unsynchronized-p (flags) `(logbitp 7 ,flags)) ; all share this flag
  105. (defmacro header-extended-p (flags) `(logbitp 6 ,flags)) ; 2.3/2.4
  106. (defmacro header-experimental-p (flags) `(logbitp 5 ,flags)) ; 2.3/2.4
  107. (defmacro header-footer-p (flags) `(logbitp 4 ,flags)) ; 2.4 only
  108. (defmacro print-header-flags (stream flags)
  109. `(format ,stream "0x~2,'0x: ~:[0/~;unsynchronized-frames/~]~:[0/~;extended-header/~]~:[0/~;expermental-tag/~]~:[0~;footer-present~]"
  110. ,flags
  111. (header-unsynchronized-p ,flags)
  112. (header-extended-p ,flags)
  113. (header-experimental-p ,flags)
  114. (header-footer-p ,flags)))
  115. (defmethod vpprint ((me id3-header) stream)
  116. (with-slots (version revision flags v21-tag-header size ext-header frames) me
  117. (format stream "~a"
  118. (with-output-to-string (s)
  119. (format s "Header: version/revision: ~d/~d, flags: ~a, size = ~:d bytes; ~a; ~a"
  120. version revision (print-header-flags nil flags) size
  121. (if (header-extended-p flags)
  122. (concatenate 'string "Extended header: " (vpprint ext-header nil))
  123. "No extended header")
  124. (if v21-tag-header
  125. (concatenate 'string "V21 tag: " (vpprint v21-tag-header nil))
  126. "No V21 tag"))
  127. (when frames
  128. (format s "~&~4tFrames[~d]:~%" (length frames))
  129. (dolist (f frames)
  130. (format s "~8t~a~%" (vpprint f nil))))))))
  131. (defmethod initialize-instance :after ((me id3-header) &key instream &allow-other-keys)
  132. "Fill in an mp3-header from INSTREAM."
  133. (log5:with-context "id3-header-initializer"
  134. (with-slots (version revision flags size ext-header frames v21-tag-header) me
  135. (stream-seek instream 128 :end)
  136. (when (string= "TAG" (stream-read-string-with-len instream 3))
  137. (log-id3-frame "looking at last 128 bytes at ~:d to try to read id3v21 header" (stream-seek instream 0 :current))
  138. (handler-case
  139. (setf v21-tag-header (make-instance 'v21-tag-header :instream instream))
  140. (id3-frame-condition (c)
  141. (log-id3-frame "reading v21 got condition: ~a" c))))
  142. (stream-seek instream 0 :start)
  143. (when (string= "ID3" (stream-read-string-with-len instream 3))
  144. (setf version (stream-read-u8 instream))
  145. (setf revision (stream-read-u8 instream))
  146. (setf flags (stream-read-u8 instream))
  147. (setf size (stream-read-u32 instream :bits-per-byte 7))
  148. (when (header-unsynchronized-p flags)
  149. (log-id3-frame "header flags indicate unsync"))
  150. (assert (not (header-footer-p flags)) () "Can't decode ID3 footer's yet")
  151. (log-id3-frame "id3 header = ~a" (vpprint me nil))))))
  152. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; frames ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  153. ;;;
  154. ;;; General plan: for each frame type we are interested in, DEFCLASS a class with
  155. ;;; specfic naming convention: frame-xxx/frame-xxxx, where xxx is valid ID3V2.2 frame name
  156. ;;; and xxxx is a valid ID3V2.[34] frame name. Upon finding a frame name in an MP3 file,
  157. ;;; we can then do a FIND-CLASS on the "frame-xxx", and a MAKE-INSTANCE on the found class
  158. ;;; to read in that class (each defined class is assumed to have an INITIALIZE-INSTANCE method
  159. ;;; that reads in data to build class.
  160. ;;;
  161. ;;; Each frame class assumes that the STREAM being passed in is sync-safe.
  162. ;;;
  163. ;;; For any class we don't want to parse (eg, haven't gotten around to it yet, etc), we create
  164. ;;; a RAW-FRAME class that can be subclassed. RAW-FRAME simply reads in the frame header, and then
  165. ;;; the frame "payload" as raw OCTETS.
  166. ;;;
  167. ;;; many ID3 tags are name/value pairs, with the name/value encoded in various ways
  168. ;;; this routine assumes that the "name" is always a string with a "normal" encoding (i.e. 0, 1, 2, or 3).
  169. ;;; The "value", however, also accepts any negative number, which means read
  170. ;;; the bytes an raw octets.
  171. (defun get-name-value-pair (instream len name-encoding value-encoding)
  172. (log5:with-context "get-name-value-pair"
  173. (log-id3-frame "reading from ~:d, len ~:d, name-encoding = ~d, value-encoding = ~d" (stream-seek instream 0 :current) len name-encoding value-encoding)
  174. (let* ((old-pos (stream-seek instream 0 :current))
  175. (name (stream-read-string instream :encoding name-encoding))
  176. (name-len (- (stream-seek instream 0 :current) old-pos))
  177. (value))
  178. (log-id3-frame "name = <~a>, name-len = ~d" name name-len)
  179. (setf value (if (>= value-encoding 0)
  180. (stream-read-string-with-len instream (- len name-len) :encoding value-encoding)
  181. (stream-read-sequence instream (- len name-len)))) ; if < 0, then just read as octets
  182. (values name value))))
  183. (defclass id3-frame ()
  184. ((pos :accessor pos :initarg :pos :documentation "the offset in the buffer were this frame was found")
  185. (id :accessor id :initarg :id :documentation "the 3-4 character name of this frame")
  186. (len :accessor len :initarg :len :documentation "the length of this frame")
  187. (version :accessor version :initarg :version :documentation "the ID3-HEADER version number stored here for convenience")
  188. (flags :accessor flags :initarg :flags :initform nil :documentation "the frame's flags"))
  189. (:documentation "Base class for an ID3 frame. Used for versions 2.2, 2.3, and 2.4"))
  190. ;;; The frame flags are the same for V22/V23
  191. (defmacro frame-23-altertag-p (frame-flags) `(logbitp 15 ,frame-flags))
  192. (defmacro frame-23-alterfile-p (frame-flags) `(logbitp 14 ,frame-flags))
  193. (defmacro frame-23-readonly-p (frame-flags) `(logbitp 13 ,frame-flags))
  194. (defmacro frame-23-compress-p (frame-flags) `(logbitp 7 ,frame-flags))
  195. (defmacro frame-23-encrypt-p (frame-flags) `(logbitp 6 ,frame-flags))
  196. (defmacro frame-23-group-p (frame-flags) `(logbitp 5 ,frame-flags))
  197. ;;; frame flags are different for 2.4. Also note, that some flags indicate that additional data
  198. ;;; follows the frame header and these must be read in the order of the flags
  199. (defmacro frame-24-altertag-p (frame-flags) `(logbitp 14 ,frame-flags)) ; no additional data
  200. (defmacro frame-24-alterfile-p (frame-flags) `(logbitp 13 ,frame-flags)) ; no additional data
  201. (defmacro frame-24-readonly-p (frame-flags) `(logbitp 12 ,frame-flags)) ; no additional data
  202. (defmacro frame-24-groupid-p (frame-flags) `(logbitp 6 ,frame-flags)) ; one byte added to frame
  203. (defmacro frame-24-compress-p (frame-flags) `(logbitp 3 ,frame-flags)) ; one byte added to frame
  204. (defmacro frame-24-encrypt-p (frame-flags) `(logbitp 2 ,frame-flags)) ; wonky case, may or may not be set, dependin on encryption type
  205. (defmacro frame-24-unsynch-p (frame-flags) `(logbitp 1 ,frame-flags)) ; *may* have a 4-byte field after header, iff datalen is set
  206. (defmacro frame-24-datalen-p (frame-flags) `(logbitp 0 ,frame-flags)) ; if unsynch is set and this too, 4-bytes are added to frame
  207. ;; NB version 2.2 does NOT have FLAGS field in a frame; hence, the ECASE
  208. (defun valid-frame-flags (header-version frame-flags)
  209. (ecase header-version
  210. (3 (zerop (logand #b0001111100011111 frame-flags)))
  211. (4 (zerop (logand #b1000111110110000 frame-flags)))))
  212. (defun print-frame-flags (version flags stream)
  213. (ecase version
  214. (2 (format stream "None"))
  215. (3 (format stream
  216. "flags: 0x~4,'0x: ~:[0/~;tag-alter-preservation/~]~:[0/~;file-alter-preservation/~]~:[0/~;read-only/~]~:[0/~;compress/~]~:[0/~;encypt/~]~:[0~;group~]"
  217. flags
  218. (frame-23-altertag-p flags)
  219. (frame-23-alterfile-p flags)
  220. (frame-23-readonly-p flags)
  221. (frame-23-compress-p flags)
  222. (frame-23-encrypt-p flags)
  223. (frame-23-group-p flags)))
  224. (4 (format stream
  225. "flags: 0x~4,'0x: ~:[0/~;tag-alter-preservation/~]~:[0/~;file-alter-preservation/~]~:[0/~;read-only/~]~:[0/~;group-id/~]~:[0/~;compress/~]~:[0/~;encypt/~]~:[0/~;unsynch/~]~:[0~;datalen~], "
  226. flags
  227. (frame-24-altertag-p flags)
  228. (frame-24-alterfile-p flags)
  229. (frame-24-readonly-p flags)
  230. (frame-24-groupid-p flags)
  231. (frame-24-compress-p flags)
  232. (frame-24-encrypt-p flags)
  233. (frame-24-unsynch-p flags)
  234. (frame-24-datalen-p flags)))))
  235. (defun vpprint-frame-header (id3-frame)
  236. (with-output-to-string (stream)
  237. (with-slots (pos version id len flags) id3-frame
  238. (format stream "offset: ~:d, version = ~d, id: ~a, len: ~:d, ~a" pos version id len
  239. (if flags
  240. (print-frame-flags version flags stream)
  241. "flags: none")))))
  242. (defclass frame-raw (id3-frame)
  243. ((octets :accessor octets :initform nil))
  244. (:documentation "Frame class that slurps in frame contents w/no attempt to grok them"))
  245. (defmethod initialize-instance :after ((me frame-raw) &key instream)
  246. (log5:with-context "frame-raw"
  247. (with-slots (pos len octets) me
  248. (log-id3-frame "reading ~:d bytes from position ~:d" len pos)
  249. (setf octets (stream-read-sequence instream len))
  250. (log-id3-frame "frame: ~a" (vpprint me nil)))))
  251. (defparameter *max-raw-bytes-print-len* 10)
  252. (defun printable-array (array)
  253. "given an array, return a string of the first *MAX-RAW-BYTES-PRINT-LEN* bytes"
  254. (let* ((len (length array))
  255. (print-len (min len *max-raw-bytes-print-len*))
  256. (printable-array (make-array print-len :displaced-to array)))
  257. (format nil "[~:d of ~:d bytes] <~x>" print-len len printable-array)))
  258. (defun upto-null (string)
  259. "Trim STRING to end at first NULL found"
  260. (subseq string 0 (position #\Null string)))
  261. (defmethod vpprint ((me frame-raw) stream)
  262. (with-slots (octets) me
  263. (format stream "frame-raw: ~a, ~a" (vpprint-frame-header me) (printable-array octets))))
  264. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; V2.1 frames ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  265. ;;; v22 frames I haven't written a class for yet
  266. (defclass frame-buf (frame-raw) ())
  267. (defclass frame-cnt (frame-raw) ())
  268. (defclass frame-cra (frame-raw) ())
  269. (defclass frame-crm (frame-raw) ())
  270. (defclass frame-equ (frame-raw) ())
  271. (defclass frame-etc (frame-raw) ())
  272. (defclass frame-geo (frame-raw) ())
  273. (defclass frame-ipl (frame-raw) ())
  274. (defclass frame-lnk (frame-raw) ())
  275. (defclass frame-mci (frame-raw) ())
  276. (defclass frame-mll (frame-raw) ())
  277. (defclass frame-pop (frame-raw) ())
  278. (defclass frame-rev (frame-raw) ())
  279. (defclass frame-rva (frame-raw) ())
  280. (defclass frame-slt (frame-raw) ())
  281. (defclass frame-waf (frame-raw) ())
  282. (defclass frame-war (frame-raw) ())
  283. (defclass frame-was (frame-raw) ())
  284. (defclass frame-wcm (frame-raw) ())
  285. (defclass frame-wcp (frame-raw) ())
  286. (defclass frame-wpb (frame-raw) ())
  287. (defclass frame-stc (frame-raw) ())
  288. ;;; V22 User defined... "WXX"
  289. ;;; Text encoding $xx
  290. ;;; Description <textstring> $00 (00)
  291. ;;; URL <textstring>
  292. ;;; Identical to TXX
  293. (defclass frame-wxx (frame-txx) ())
  294. ;; V22 COM frames
  295. ;; Comment "COM"
  296. ;; Text encoding $xx
  297. ;; Language $xx xx xx
  298. ;; Short content description <textstring> $00 (00)
  299. ;; The actual text <textstring>
  300. (defclass frame-com (id3-frame)
  301. ((encoding :accessor encoding)
  302. (lang :accessor lang)
  303. (desc :accessor desc)
  304. (val :accessor val)))
  305. (defmethod initialize-instance :after ((me frame-com) &key instream)
  306. (log5:with-context "frame-com"
  307. (with-slots (len encoding lang desc val) me
  308. (setf encoding (stream-read-u8 instream))
  309. (setf lang (stream-read-iso-string-with-len instream 3))
  310. (multiple-value-bind (n v) (get-name-value-pair instream (- len 1 3) encoding encoding)
  311. (setf desc n)
  312. ;; iTunes broken-ness... for frame-coms, there can be an additional null or two at the end
  313. (setf val (upto-null v)))
  314. (log-id3-frame "encoding = ~d, lang = <~a>, desc = <~a>, text = <~a>" encoding lang desc val))))
  315. (defmethod vpprint ((me frame-com) stream)
  316. (with-slots (len encoding lang desc val) me
  317. (format stream "frame-com: ~a, encoding = ~d, lang = <~a> (~a), desc = <~a>, val = <~a>"
  318. (vpprint-frame-header me) encoding lang (get-iso-639-2-language lang) desc val)))
  319. ;;; ULT's are same format as COM's... XXX rewrite this as suggested in comment at bottom of this file
  320. ;;; V22 unsynced lyrics/text "ULT"
  321. ;;; Text encoding $xx
  322. ;;; Language $xx xx xx
  323. ;;; Content descriptor <textstring> $00 (00)
  324. ;;; Lyrics/text <textstring>
  325. (defclass frame-ult (frame-com) ())
  326. ;; V22 PIC frames
  327. ;; Attached picture "PIC"
  328. ;; Text encoding $xx
  329. ;; Image format $xx xx xx
  330. ;; Picture type $xx
  331. ;; Description <textstring> $00 (00)
  332. ;; Picture data <binary data>
  333. (defclass frame-pic (id3-frame)
  334. ((encoding :accessor encoding)
  335. (img-format :accessor img-format)
  336. (type :accessor type)
  337. (desc :accessor desc)
  338. (data :accessor data)))
  339. (defmethod initialize-instance :after ((me frame-pic) &key instream)
  340. (log5:with-context "frame-pic"
  341. (with-slots (id len encoding img-format type desc data) me
  342. (setf encoding (stream-read-u8 instream))
  343. (setf img-format (stream-read-iso-string-with-len instream 3))
  344. (setf type (stream-read-u8 instream))
  345. (multiple-value-bind (n v) (get-name-value-pair instream (- len 5) encoding -1)
  346. (setf desc n)
  347. (setf data v)
  348. (log-id3-frame "encoding: ~d, img-format = <~a>, type = ~d (~a), desc = <~a>, value = ~a"
  349. encoding img-format type (get-picture-type type) desc (printable-array data))))))
  350. (defmethod vpprint ((me frame-pic) stream)
  351. (with-slots (encoding img-format type desc data) me
  352. (format stream "frame-pic: ~a, encoding ~d, img-format type: <~a>, picture type: ~d (~a), description <~a>, data: ~a"
  353. (vpprint-frame-header me) encoding img-format type (get-picture-type type) desc (printable-array data))))
  354. ;; Version 2, 3, or 4 generic text-info frames
  355. ;; Text information identifier "T00" - "TZZ", excluding "TXX", or "T000 - TZZZ", excluding "TXXX"
  356. ;; Text encoding $xx
  357. ;; Information <textstring>
  358. (defclass frame-text-info (id3-frame)
  359. ((encoding :accessor encoding)
  360. (info :accessor info))
  361. (:documentation "V2/V3/V4 T00-TZZ and T000-TZZZ frames, but not TXX or TXXX"))
  362. (defmethod initialize-instance :after ((me frame-text-info) &key instream)
  363. (log5:with-context "frame-text-info"
  364. (with-slots (version flags len encoding info) me
  365. (let ((read-len len))
  366. ;; in version 4 frames, each frame may also have an unsync flag. since we have unsynced already
  367. ;; the only thing we need to do here is check for the optional DATALEN field. If it is present
  368. ;; then it has the actual number of octets to read
  369. (when (and (= version 4) (frame-24-unsynch-p flags))
  370. (if (frame-24-datalen-p flags)
  371. (setf read-len (stream-read-u32 instream :bits-per-byte 7))))
  372. (setf encoding (stream-read-u8 instream))
  373. (setf info (stream-read-string-with-len instream (1- read-len) :encoding encoding)))
  374. ;; a null is ok, but according to the "spec", you're supposed to ignore anything after a 'Null'
  375. (log-id3-frame "made text-info-frame: ~a" (vpprint me nil))
  376. (setf info (upto-null info))
  377. (log-id3-frame "encoding = ~d, info = <~a>" encoding info))))
  378. (defmethod vpprint ((me frame-text-info) stream)
  379. (with-slots (len encoding info) me
  380. (format stream "frame-text-info: ~a, encoding = ~d, info = <~a>" (vpprint-frame-header me) encoding info)))
  381. (defclass frame-tal (frame-text-info) ())
  382. (defclass frame-tbp (frame-text-info) ())
  383. (defclass frame-tcm (frame-text-info) ())
  384. (defclass frame-tco (frame-text-info) ())
  385. (defclass frame-tcp (frame-text-info) ())
  386. (defclass frame-tcr (frame-text-info) ())
  387. (defclass frame-tda (frame-text-info) ())
  388. (defclass frame-tdy (frame-text-info) ())
  389. (defclass frame-ten (frame-text-info) ())
  390. (defclass frame-tft (frame-text-info) ())
  391. (defclass frame-tim (frame-text-info) ())
  392. (defclass frame-tke (frame-text-info) ())
  393. (defclass frame-tla (frame-text-info) ())
  394. (defclass frame-tle (frame-text-info) ())
  395. (defclass frame-tmt (frame-text-info) ())
  396. (defclass frame-toa (frame-text-info) ())
  397. (defclass frame-tof (frame-text-info) ())
  398. (defclass frame-tol (frame-text-info) ())
  399. (defclass frame-tor (frame-text-info) ())
  400. (defclass frame-tot (frame-text-info) ())
  401. (defclass frame-tp1 (frame-text-info) ())
  402. (defclass frame-tp2 (frame-text-info) ())
  403. (defclass frame-tp3 (frame-text-info) ())
  404. (defclass frame-tp4 (frame-text-info) ())
  405. (defclass frame-tpa (frame-text-info) ())
  406. (defclass frame-tpb (frame-text-info) ())
  407. (defclass frame-trc (frame-text-info) ())
  408. (defclass frame-trd (frame-text-info) ())
  409. (defclass frame-trk (frame-text-info) ())
  410. (defclass frame-tsi (frame-text-info) ())
  411. (defclass frame-tss (frame-text-info) ())
  412. (defclass frame-tt1 (frame-text-info) ())
  413. (defclass frame-tt2 (frame-text-info) ())
  414. (defclass frame-tt3 (frame-text-info) ())
  415. (defclass frame-txt (frame-text-info) ())
  416. (defclass frame-tye (frame-text-info) ())
  417. ;; v22 User defined "TXX" frames
  418. ;; Text encoding $xx
  419. ;; Description <textstring> $00 (00)
  420. ;; Value <textstring>
  421. (defclass frame-txx (id3-frame)
  422. ((encoding :accessor encoding)
  423. (desc :accessor desc)
  424. (val :accessor val))
  425. (:documentation "TXX is the only frame starting with a 'T' that has a different format"))
  426. (defmethod initialize-instance :after ((me frame-txx) &key instream)
  427. (log5:with-context "frame-txx"
  428. (with-slots (len encoding desc val) me
  429. (setf encoding (stream-read-u8 instream))
  430. (multiple-value-bind (n v) (get-name-value-pair instream (1- len) encoding encoding)
  431. (setf desc n)
  432. (setf val v)
  433. (log-id3-frame "encoding = ~d, desc = <~a>, val = <~a>" encoding desc val)))))
  434. (defmethod vpprint ((me frame-txx) stream)
  435. (with-slots (len encoding desc val) me
  436. (format stream "frame-txx: ~a, encoding = ~d, desc = <~a>, val = <~a>" (vpprint-frame-header me) encoding desc val)))
  437. ;;; V22 unique file identifier "UFI"
  438. ;;; Owner identifier <textstring> $00
  439. ;;; Identifier <up to 64 bytes binary data>
  440. (defclass frame-ufi (id3-frame)
  441. ((name :accessor name)
  442. (value :accessor value))
  443. (:documentation "Unique File Identifier"))
  444. (defmethod initialize-instance :after ((me frame-ufi) &key instream)
  445. (log5:with-context "frame-ufi"
  446. (with-slots (id len name value) me
  447. (multiple-value-bind (n v) (get-name-value-pair instream len 0 -1)
  448. (setf name n)
  449. (setf value v))
  450. (log-id3-frame "name = <~a>, value = ~a" name (printable-array value)))))
  451. (defmethod vpprint ((me frame-ufi) stream)
  452. (with-slots (id len name value) me
  453. (format stream "frame-ufi: ~a, name: <~a>, value: ~a" (vpprint-frame-header me) name (printable-array value))))
  454. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; V23/V24 frames ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  455. ;;; v23/v24 frame I haven't written a class for yet
  456. (defclass frame-aenc (frame-raw) ())
  457. (defclass frame-aspi (frame-raw) ())
  458. (defclass frame-comr (frame-raw) ())
  459. (defclass frame-encr (frame-raw) ())
  460. (defclass frame-equ2 (frame-raw) ())
  461. (defclass frame-equa (frame-raw) ())
  462. (defclass frame-etco (frame-raw) ())
  463. (defclass frame-geob (frame-raw) ())
  464. (defclass frame-grid (frame-raw) ())
  465. (defclass frame-ipls (frame-raw) ())
  466. (defclass frame-link (frame-raw) ())
  467. (defclass frame-mcdi (frame-raw) ())
  468. (defclass frame-mllt (frame-raw) ())
  469. (defclass frame-ncon (frame-raw) ())
  470. (defclass frame-owne (frame-raw) ())
  471. (defclass frame-popm (frame-raw) ())
  472. (defclass frame-poss (frame-raw) ())
  473. (defclass frame-rbuf (frame-raw) ())
  474. (defclass frame-rva2 (frame-raw) ())
  475. (defclass frame-rvad (frame-raw) ())
  476. (defclass frame-rvrb (frame-raw) ())
  477. (defclass frame-seek (frame-raw) ())
  478. (defclass frame-sign (frame-raw) ())
  479. (defclass frame-sylt (frame-raw) ())
  480. (defclass frame-sytc (frame-raw) ())
  481. (defclass frame-user (frame-raw) ())
  482. ;;; V23/V24 text-info frames
  483. (defclass frame-talb (frame-text-info) ())
  484. (defclass frame-tbpm (frame-text-info) ())
  485. (defclass frame-tcmp (frame-text-info) ())
  486. (defclass frame-tcom (frame-text-info) ())
  487. (defclass frame-tcon (frame-text-info) ())
  488. (defclass frame-tcop (frame-text-info) ())
  489. (defclass frame-tdat (frame-text-info) ())
  490. (defclass frame-tden (frame-text-info) ())
  491. (defclass frame-tdly (frame-text-info) ())
  492. (defclass frame-tdor (frame-text-info) ())
  493. (defclass frame-tdrc (frame-text-info) ())
  494. (defclass frame-tdrl (frame-text-info) ())
  495. (defclass frame-tdtg (frame-text-info) ())
  496. (defclass frame-tenc (frame-text-info) ())
  497. (defclass frame-text (frame-text-info) ())
  498. (defclass frame-tflt (frame-text-info) ())
  499. (defclass frame-time (frame-text-info) ())
  500. (defclass frame-tipl (frame-text-info) ())
  501. (defclass frame-tit1 (frame-text-info) ())
  502. (defclass frame-tit2 (frame-text-info) ())
  503. (defclass frame-tit3 (frame-text-info) ())
  504. (defclass frame-tkey (frame-text-info) ())
  505. (defclass frame-tlan (frame-text-info) ())
  506. (defclass frame-tlen (frame-text-info) ())
  507. (defclass frame-tmcl (frame-text-info) ())
  508. (defclass frame-tmed (frame-text-info) ())
  509. (defclass frame-tmoo (frame-text-info) ())
  510. (defclass frame-toal (frame-text-info) ())
  511. (defclass frame-tofn (frame-text-info) ())
  512. (defclass frame-toly (frame-text-info) ())
  513. (defclass frame-tope (frame-text-info) ())
  514. (defclass frame-tory (frame-text-info) ())
  515. (defclass frame-town (frame-text-info) ())
  516. (defclass frame-tpe1 (frame-text-info) ())
  517. (defclass frame-tpe2 (frame-text-info) ())
  518. (defclass frame-tpe3 (frame-text-info) ())
  519. (defclass frame-tpe4 (frame-text-info) ())
  520. (defclass frame-tpos (frame-text-info) ())
  521. (defclass frame-tpro (frame-text-info) ())
  522. (defclass frame-tpub (frame-text-info) ())
  523. (defclass frame-trda (frame-text-info) ())
  524. (defclass frame-trsn (frame-text-info) ())
  525. (defclass frame-trso (frame-text-info) ())
  526. (defclass frame-tsoa (frame-text-info) ())
  527. (defclass frame-tsop (frame-text-info) ())
  528. (defclass frame-tsot (frame-text-info) ())
  529. (defclass frame-tsst (frame-text-info) ())
  530. (defclass frame-tsse (frame-text-info) ())
  531. (defclass frame-tsrc (frame-text-info) ())
  532. (defclass frame-tsiz (frame-text-info) ())
  533. (defclass frame-tyer (frame-text-info) ())
  534. (defclass frame-trck (frame-text-info) ())
  535. (defparameter *picture-type*
  536. '("Other"
  537. "32x32 pixels 'file icon' (PNG only)"
  538. "Other file icon"
  539. "Cover (front)"
  540. "Cover (back)"
  541. "Leaflet page"
  542. "Media (e.g. lable side of CD)"
  543. "Lead artist/lead performer/soloist"
  544. "Artist/performer"
  545. "Conductor"
  546. "Band/Orchestra"
  547. "Composer"
  548. "Lyricist/text writer"
  549. "Recording Location"
  550. "During recording"
  551. "During performance"
  552. "Movie/video screen capture"
  553. "A bright coloured fish" ; how do you know the fish is smart :)
  554. "Illustration"
  555. "Band/artist logotype"
  556. "Publisher/Studio logotype"))
  557. (defun get-picture-type (n)
  558. "Function to return picture types for APIC frames"
  559. (if (and (>= n 0) (< n (length *picture-type*)))
  560. (nth n *picture-type*)
  561. "Unknown"))
  562. ;; V23/V24 APIC frames
  563. ;; <Header for 'Attached picture', ID: "APIC">
  564. ;; Text encoding $xx
  565. ;; MIME type <text string> $00
  566. ;; Picture type $xx
  567. ;; Description <text string according to encoding> $00 (00)
  568. ;; Picture data <binary data>
  569. (defclass frame-apic (id3-frame)
  570. ((encoding :accessor encoding)
  571. (mime :accessor mime)<
  572. (type :accessor type)
  573. (desc :accessor desc)
  574. (data :accessor data))
  575. (:documentation "Holds an attached picture (cover art)"))
  576. (defmethod initialize-instance :after ((me frame-apic) &key instream)
  577. (log5:with-context "frame-apic"
  578. (with-slots (id len encoding mime type desc data) me
  579. (setf encoding (stream-read-u8 instream))
  580. (setf mime (stream-read-iso-string instream))
  581. (setf type (stream-read-u8 instream))
  582. (multiple-value-bind (n v) (get-name-value-pair instream (- len 1 (length mime) 1 1) encoding -1)
  583. (setf desc n)
  584. (setf data v)
  585. (log-id3-frame "enoding = ~d, mime = <~a>, type = ~d (~a), desc = <~a>, data = ~a" encoding mime type (get-picture-type type) desc (printable-array data))))))
  586. (defmethod vpprint ((me frame-apic) stream)
  587. (with-slots (encoding mime type desc data) me
  588. (format stream "frame-apic: ~a, encoding ~d, mime type: ~a, picture type: ~d (~a), description <~a>, data: ~a"
  589. (vpprint-frame-header me) encoding mime type (get-picture-type type) desc (printable-array data))))
  590. ;;; V23/V24 COMM frames
  591. ;;; <Header for 'Comment', ID: "COMM">
  592. ;;; Text encoding $xx
  593. ;;; Language $xx xx xx
  594. ;;; Short content descrip. <text string according to encoding> $00 (00)
  595. ;;; The actual text <full text string according to encoding>
  596. (defclass frame-comm (id3-frame)
  597. ((encoding :accessor encoding)
  598. (lang :accessor lang)
  599. (desc :accessor desc)
  600. (val :accessor val))
  601. (:documentation "Comment frame"))
  602. (defmethod initialize-instance :after ((me frame-comm) &key instream)
  603. (log5:with-context "frame-comm"
  604. (with-slots (encoding lang len desc val) me
  605. (setf encoding (stream-read-u8 instream))
  606. (setf lang (stream-read-iso-string-with-len instream 3))
  607. (multiple-value-bind (n v) (get-name-value-pair instream (- len 1 3) encoding encoding)
  608. (setf desc n)
  609. ;; iTunes broken-ness... for frame-coms, there can be an additional null or two at the end
  610. (setf val (upto-null v)))
  611. (log-id3-frame "encoding = ~d, lang = <~a>, desc = <~a>, val = <~a>" encoding lang desc val))))
  612. (defmethod vpprint ((me frame-comm) stream)
  613. (with-slots (encoding lang desc val) me
  614. (format stream "frame-comm: ~a, encoding: ~d, lang: <~a> (~a), desc = <~a>, val = <~a>"
  615. (vpprint-frame-header me) encoding lang (get-iso-639-2-language lang) desc val)))
  616. ;;; Unsynchronized lyrics frames look very much like comment frames...
  617. (defclass frame-uslt (frame-comm) ())
  618. ;;; v23/24 PCNT frames
  619. ;;; <Header for 'Play counter', ID: "PCNT">
  620. ;;; Counter $xx xx xx xx (xx ...)
  621. (defclass frame-pcnt (id3-frame)
  622. ((play-count :accessor play-count))
  623. (:documentation "Play count frame"))
  624. (defmethod initialize-instance :after ((me frame-pcnt) &key instream)
  625. (log5:with-context "frame-pcnt"
  626. (with-slots (play-count len) me
  627. (assert (= 4 len) () "Ran into a play count with ~d bytes" len)
  628. (setf play-count (stream-read-u32 instream)) ; probably safe---play count *can* be longer than 4 bytes, but...
  629. (log-id3-frame "play count = <~d>" play-count))))
  630. (defmethod vpprint ((me frame-pcnt) stream)
  631. (with-slots (play-count) me
  632. (format stream "frame-pcnt: ~a, count = ~d" (vpprint-frame-header me) play-count)))
  633. ;;; V23/V24 PRIV frames
  634. ;;; <Header for 'Private frame', ID: "PRIV">
  635. ;;; Owner identifier <text string> $00
  636. ;;; The private data <binary data>
  637. (defclass frame-priv (id3-frame)
  638. ((name :accessor name)
  639. (value :accessor value))
  640. (:documentation "Private frame"))
  641. (defmethod initialize-instance :after ((me frame-priv) &key instream)
  642. (log5:with-context "frame-priv"
  643. (with-slots (id len name value) me
  644. (multiple-value-bind (n v) (get-name-value-pair instream len 0 -1)
  645. (setf name n)
  646. (setf value v)
  647. (log-id3-frame "name = <~a>, value = <~a>" name value)))))
  648. (defmethod vpprint ((me frame-priv) stream)
  649. (with-slots (id len name value) me
  650. (format stream "frame-priv: ~a, name: <~a>, data: ~a" (vpprint-frame-header me) name (printable-array value))))
  651. ;; V23/V24 TXXX frames
  652. ;; <Header for 'User defined text information frame', ID: "TXXX">
  653. ;; Text encoding $xx
  654. ;; Description <text string according to encoding> $00 (00)
  655. ;; Value <text string according to encoding>
  656. (defclass frame-txxx (id3-frame)
  657. ((encoding :accessor encoding)
  658. (desc :accessor desc)
  659. (val :accessor val))
  660. (:documentation "TXXX frame"))
  661. (defmethod initialize-instance :after ((me frame-txxx) &key instream)
  662. (log5:with-context "frame-txxx"
  663. (with-slots (encoding len desc val) me
  664. (setf encoding (stream-read-u8 instream))
  665. (multiple-value-bind (n v) (get-name-value-pair instream
  666. (- len 1)
  667. encoding
  668. encoding)
  669. (setf desc n)
  670. (setf val v))
  671. (log-id3-frame "encoding = ~d, desc = <~a>, value = <~a>" encoding desc val))))
  672. (defmethod vpprint ((me frame-txxx) stream)
  673. (format stream "frame-txxx: ~a, <~a/~a>" (vpprint-frame-header me) (desc me) (val me)))
  674. ;; V23/V24 UFID frames
  675. ;; <Header for 'Unique file identifier', ID: "UFID">
  676. ;; Owner identifier <text string> $00
  677. ;; Identifier <up to 64 bytes binary data>
  678. (defclass frame-ufid (id3-frame)
  679. ((name :accessor name)
  680. (value :accessor value))
  681. (:documentation "Unique file identifier frame"))
  682. (defmethod initialize-instance :after ((me frame-ufid) &key instream)
  683. (log5:with-context "frame-ufid"
  684. (with-slots (id len name value) me
  685. (multiple-value-bind (n v) (get-name-value-pair instream len 0 -1)
  686. (setf name n)
  687. (setf value v))
  688. (log-id3-frame "name = <~a>, value = ~a" name (printable-array value)))))
  689. (defmethod vpprint ((me frame-ufid) stream)
  690. (with-slots (id len name value) me
  691. (format stream "frame-ufid: ~a, name: <~a>, value: ~a" (vpprint-frame-header me) name (printable-array value))))
  692. ;;; V23/V24 URL frame
  693. ;;; <Header for 'URL link frame', ID: "W000" - "WZZZ", excluding "WXXX" described in 4.3.2.>
  694. ;;; URL <text string>
  695. (defclass frame-url-link (id3-frame)
  696. ((url :accessor url))
  697. (:documentation "URL link frame"))
  698. (defmethod initialize-instance :after ((me frame-url-link) &key instream)
  699. (with-slots (id len url) me
  700. (log5:with-context "url"
  701. (setf url (stream-read-iso-string-with-len instream len))
  702. (log-id3-frame "url = <~a>" url))))
  703. (defmethod vpprint ((me frame-url-link) stream)
  704. (with-slots (url) me
  705. (format stream "frame-url-link: ~a, url: ~a" (vpprint-frame-header me) url)))
  706. ;;; V23/V24 frames URL link frames
  707. (defclass frame-wcom (frame-url-link) ())
  708. (defclass frame-wcop (frame-url-link) ())
  709. (defclass frame-woaf (frame-url-link) ())
  710. (defclass frame-woar (frame-url-link) ())
  711. (defclass frame-woas (frame-url-link) ())
  712. (defclass frame-wors (frame-url-link) ())
  713. (defclass frame-wpay (frame-url-link) ())
  714. (defclass frame-wpub (frame-url-link) ())
  715. ;;; Identical to frame-txx
  716. (defclass frame-wxxx (frame-txx) ())
  717. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; frame finding/creation ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  718. (defun possibly-valid-frame-id? (frame-id)
  719. "test to see if a string is a potentially valid frame id"
  720. (labels ((numeric-char-p (c)
  721. (let ((code (char-code c)))
  722. (and (>= code (char-code #\0))
  723. (<= code (char-code #\9))))))
  724. ;; test each octet to see if it is alphanumeric
  725. (dotimes (i (length frame-id))
  726. (let ((c (aref frame-id i)))
  727. (when (not (or (numeric-char-p c)
  728. (and (alpha-char-p c) (upper-case-p c))))
  729. (return-from possibly-valid-frame-id? nil))))
  730. t))
  731. (defun find-frame-class (id)
  732. "Search by concatenating 'frame-' with ID and look for that symbol in this package"
  733. (log5:with-context "find-frame-class"
  734. (log-id3-frame "looking for class <~a>" id)
  735. (let ((found-class-symbol (find-symbol (string-upcase (concatenate 'string "frame-" id)) :ID3-FRAME))
  736. found-class)
  737. ;; if we found the class name, return the class (to be used for MAKE-INSTANCE)
  738. (when found-class-symbol
  739. (setf found-class (find-class found-class-symbol))
  740. (log-id3-frame "found class: ~a" found-class)
  741. (return-from find-frame-class found-class))
  742. (log-id3-frame "didn't find class, checking general cases")
  743. ;; if not a "normal" frame-id, look at general cases of
  744. ;; starting with a 'T' or a 'W'
  745. (setf found-class (case (aref id 0)
  746. (#\T (log-id3-frame "assuming text-info") (find-class (find-symbol "FRAME-TEXT-INFO" :ID3-FRAME)))
  747. (#\W (log-id3-frame "assuming url-link") (find-class (find-symbol "FRAME-URL-LINK" :ID3-FRAME)))
  748. (t
  749. ;; we don't recognize the frame name. if it could possibly be a real frame name,
  750. ;; then just read it raw
  751. (when (possibly-valid-frame-id? id)
  752. (log-id3-frame "just reading raw")
  753. (find-class (find-symbol "FRAME-RAW" :ID3-FRAME))))))
  754. (log-id3-frame "general case for id <~a> is ~a" id found-class)
  755. found-class)))
  756. (defun make-frame (version instream)
  757. "Create an appropriate mp3 frame by reading data from INSTREAM."
  758. (log5:with-context "make-frame"
  759. (let* ((pos (stream-seek instream 0 :current))
  760. (byte (stream-read-u8 instream))
  761. frame-name frame-len frame-flags frame-class)
  762. (log-id3-frame "reading from position ~:d (size of stream = ~:d)" pos (stream-size instream))
  763. (when (zerop byte) ; XXX should this be correlated to PADDING in the extended header???
  764. (log-id3-frame "hit padding of size ~:d while making a frame" 9999) ;(- (stream-size instream) pos))
  765. (return-from make-frame nil)) ; hit padding
  766. (setf frame-name
  767. (concatenate 'string (string (code-char byte)) (stream-read-string-with-len instream (ecase version (2 2) (3 3) (4 3)))))
  768. (setf frame-len (ecase version
  769. (2 (stream-read-u24 instream))
  770. (3 (stream-read-u32 instream))
  771. (4 (stream-read-u32 instream :bits-per-byte 7))))
  772. (when (or (= version 3) (= version 4))
  773. (setf frame-flags (stream-read-u16 instream))
  774. (when (not (valid-frame-flags version frame-flags))
  775. (warn "Invalid frame flags found ~a" (print-frame-flags version frame-flags nil))))
  776. (log-id3-frame "making frame: id:~a, version: ~d, len: ~:d, flags: ~a"
  777. frame-name version frame-len
  778. (print-frame-flags version frame-flags nil))
  779. (setf frame-class (find-frame-class frame-name))
  780. ;; edge case where found a frame name, but it is not valid or where making this frame
  781. ;; would blow past the end of the file/buffer
  782. (when (or (> (+ (stream-seek instream 0 :current) frame-len) (stream-size instream))
  783. (null frame-class))
  784. (error 'id3-frame-condition :message "bad frame found" :object frame-name :location pos))
  785. (make-instance frame-class :pos pos :version version :id frame-name :len frame-len :flags frame-flags :instream instream))))
  786. (defun find-id3-frames (mp3-file)
  787. "With an open mp3-file, make sure it is in fact an MP3 file, then read it's header and frames"
  788. (labels ((read-loop (version stream)
  789. (log5:with-context "read-loop-in-find-id3-frames"
  790. (log-id3-frame "Starting loop through ~:d bytes" (stream-size stream))
  791. (let (frames this-frame)
  792. (do ()
  793. ((>= (stream-seek stream 0 :current) (stream-size stream)))
  794. (handler-case
  795. (progn
  796. (setf this-frame (make-frame version stream))
  797. (when (null this-frame)
  798. (log-id3-frame "hit padding: returning ~d frames" (length frames))
  799. (return-from read-loop (values t (nreverse frames))))
  800. (log-id3-frame "bottom of read-loop: pos = ~:d, size = ~:d" (stream-seek stream 0 :current) (stream-size stream))
  801. (push this-frame frames))
  802. (condition (c)
  803. (log-id3-frame "got condition ~a when making frame" c)
  804. (return-from read-loop (values nil (nreverse frames))))))
  805. (log-id3-frame "Succesful read: returning ~d frames" (length frames))
  806. (values t (nreverse frames)))))) ; reverse this so we have frames in "file order"
  807. (log5:with-context "find-id3-frames"
  808. (when (not (is-valid-mp3-file mp3-file))
  809. (log-id3-frame "~a is not an mp3 file" (stream-filename mp3-file))
  810. (error 'id3-frame-condition :location "find-id3-frames" :object (stream-filename mp3-file) :message "is not an mp3 file"))
  811. (log-id3-frame "~a is a valid mp3 file" (stream-filename mp3-file))
  812. (setf (id3-header mp3-file) (make-instance 'id3-header :instream mp3-file))
  813. (with-slots (size ext-header frames flags version) (id3-header mp3-file)
  814. ;; at this point, we switch from reading the file stream and create a memory stream
  815. ;; rationale: it may need to be unsysnc'ed and it helps prevent run-away reads with
  816. ;; mis-formed frames
  817. (when (not (zerop size))
  818. (let ((mem-stream (make-mem-stream (stream-read-sequence mp3-file size
  819. :bits-per-byte (if (header-unsynchronized-p flags) 7 8)))))
  820. ;; must make extended header here since it is subject to unsynchronization.
  821. (when (header-extended-p flags)
  822. (setf ext-header (make-instance 'id3-extended-header :instream mem-stream)))
  823. ;; start reading frames from memory stream
  824. (multiple-value-bind (_ok _frames) (read-loop version mem-stream)
  825. (if (not _ok)
  826. (warn "File ~a had errors finding mp3 frames. potentially missed frames!" (stream-filename mp3-file)))
  827. (log-id3-frame "ok = ~a, returning ~d frames" _ok (length _frames))
  828. (setf frames _frames)
  829. _ok)))))))
  830. (defun map-id3-frames (mp3-file &key (func (constantly t)))
  831. "Iterates through the ID3 frames found in an MP3 file"
  832. (mapcar func (frames (id3-header mp3-file))))
  833. #|
  834. XXX
  835. Random ideas for rewrite:
  836. -might be simplest to read in frame payloads (sync'ed appropriately) for all frames and then move parsing into
  837. accessor methods? This might be easier to handle sync/compression/etc.
  838. -probably should rewrite name/value pairs as a mixin class? Or more broadly, there is a finite set of frame-encodings,
  839. so abstact to that, then subclass for frame-????
  840. |#