mp3-frame.lisp 38 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922
  1. ;;; -*- Mode: Lisp; show-trailing-whitespace: t; Base: 10; indent-tabs: nil; Syntax: ANSI-Common-Lisp; Package: MP3-FRAME; -*-
  2. ;;; Copyright (c) 2013, Mark VandenBrink. All rights reserved.
  3. (in-package #:mp3-frame)
  4. (log5:defcategory cat-log-mp3-frame)
  5. (defmacro log-mp3-frame (&rest log-stuff) `(log5:log-for (cat-log-mp3-frame) ,@log-stuff))
  6. (define-condition mp3-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 "mp3-frame condition at location: <~a> with object: <~a>: message: <~a>"
  12. (location condition) (object condition) (message condition)))))
  13. (defmethod print-object ((me mp3-frame-condition) stream)
  14. (format stream "location: <~a>, object: <~a>, message: <~a>" (location me) (object me) (message me)))
  15. (defparameter *pprint-mp3-frame* nil
  16. "Controls whether we pretty print frame data")
  17. (defclass mp3-id3-header ()
  18. ((version :accessor version :initarg :version :initform 0)
  19. (revision :accessor revision :initarg :revision :initform 0)
  20. (flags :accessor flags :initarg :flags :initform 0)
  21. (size :accessor size :initarg :size :initform 0)
  22. (ext-header :accessor ext-header :initarg :ext-header :initform nil)
  23. (frames :accessor frames :initarg :frames :initform nil)
  24. (v21-tag-header :accessor v21-tag-header :initarg :v21-tag-header :initform nil))
  25. (:documentation "The ID3 header, found at start of file"))
  26. (defmethod vpprint ((me mp3-id3-header) stream &key (indent 0))
  27. "Set *pprint-mp3-frame* to get pretty printing and call print-object via format"
  28. (let ((*pprint-mp3-frame* t))
  29. (format stream "~vt~a" (* indent 1) me)))
  30. (defun is-valid-mp3-file (mp3-file)
  31. "Make sure this is an MP3 file. Look for frames at begining and/or end"
  32. (log5:with-context "is-valid-mp3-file"
  33. (stream-seek mp3-file 0 :start)
  34. (let* ((id3 (stream-read-string-with-len mp3-file 3))
  35. (version (stream-read-u8 mp3-file))
  36. (tag))
  37. (stream-seek mp3-file 128 :end)
  38. (setf tag (stream-read-string-with-len mp3-file 3))
  39. (stream-seek mp3-file 0 :start)
  40. (log-mp3-frame "id3 = ~a, version = ~d" id3 version)
  41. (or (and (string= "ID3" id3)
  42. (or (= 2 version) (= 3 version) (= 4 version)))
  43. (string= tag "TAG")))))
  44. (defclass v21-tag-header ()
  45. ((songname :accessor songname :initarg :songname :initform nil)
  46. (artist :accessor artist :initarg :artist :initform nil)
  47. (album :accessor album :initarg :album :initform nil)
  48. (year :accessor year :initarg :year :initform nil)
  49. (comment :accessor comment :initarg :comment :initform nil)
  50. (genre :accessor genre :initarg :genre :initform nil))
  51. (:documentation "ID3 V2.1 old-style tag. If present, found in last 128 bytes of file."))
  52. (defmethod vpprint ((me v21-tag-header) stream &key (indent 0))
  53. "Set *pprint-mp3-frame* to get pretty printing and call print-object via format"
  54. (let ((*pprint-mp3-frame* t))
  55. (format stream "~vt~a" (* indent 1) me)))
  56. (defmethod print-object ((me v21-tag-header) stream)
  57. (if (null *pprint-mp3-frame*)
  58. (call-next-method)
  59. (with-slots (songname artist album year comment genre) me
  60. (format stream "songname = <~a>, artist = <~a>, album = <~a>, year = <~a>, comment = <~a>, genre = ~d"
  61. songname artist album year comment genre))))
  62. (defmethod initialize-instance ((me v21-tag-header) &key instream)
  63. "Read in a V2.1 tag. Caller will have stream-seek'ed file to correct location and ensured that TAG was present"
  64. (log5:with-context "v21-frame-initializer"
  65. (log-mp3-frame "reading v2.1 tag")
  66. (with-slots (songname artist album year comment genre) me
  67. (setf songname (trim-string (stream-read-string-with-len instream 30)))
  68. (setf artist (trim-string (stream-read-string-with-len instream 30)))
  69. (setf album (trim-string (stream-read-string-with-len instream 30)))
  70. (setf year (trim-string (stream-read-string-with-len instream 4)))
  71. (setf comment (trim-string (stream-read-string-with-len instream 30)))
  72. (setf genre (stream-read-u8 instream))
  73. (log-mp3-frame "v21 tag: ~a" (vpprint me nil)))))
  74. (defclass mp3-ext-header ()
  75. ((size :accessor size :initarg :size :initform 0)
  76. (flags :accessor flags :initarg :flags :initform 0)
  77. (padding :accessor padding :initarg :padding :initform 0)
  78. (crc :accessor crc :initarg :crc :initform nil))
  79. (:documentation "class representing a V2.3/4 extended header"))
  80. ;; (defmethod vpprint ((me mp3-ext-header) stream &key (indent 0))
  81. ;; "Set *pprint-mp3-frame* to get pretty printing and call print-object via format"
  82. ;; (let ((*pprint-mp3-frame* t))
  83. ;; (format stream "~vt~a" (* indent 1) me)))
  84. (defmacro ext-header-crc-p (flags) `(logbitp 15 ,flags))
  85. (defmethod initialize-instance ((me mp3-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)) ; this is sync-safe in 2.4?
  90. (setf flags (stream-read-u16 instream))
  91. (setf padding (stream-read-u32 instream)) ; this is sync-safe and 35 bits in 2.4?
  92. (when (ext-header-crc-p flags)
  93. (setf crc (stream-read-u32 instream)))))
  94. (defmethod print-object ((me mp3-ext-header) stream)
  95. (if (null *pprint-mp3-frame*)
  96. (call-next-method)
  97. (with-slots (size flags padding crc) me
  98. (format stream "extended header: size: ~d, flags: ~x, padding ~:d, crc = ~x~%"
  99. size flags padding crc))))
  100. (defmethod vpprint ((me mp3-ext-header) stream &key (indent 0))
  101. "Set *pprint-mp3-frame* to get pretty printing and call print-object via format"
  102. (let ((*pprint-mp3-frame* t))
  103. (format stream "~vt~a" (* indent 1) me)))
  104. (defmacro header-unsynchronized-p (flags) `(logbitp 7 ,flags))
  105. (defmacro header-extended-p (flags) `(logbitp 6 ,flags))
  106. (defmacro header-experimental-p (flags) `(logbitp 5 ,flags))
  107. (defmacro header-footer-p (flags) `(logbitp 4 ,flags)) ;; N.B. *NOT* defined for 2.3 tags
  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 print-object ((me mp3-id3-header) stream)
  116. (if (null *pprint-mp3-frame*)
  117. (call-next-method)
  118. (with-slots (version revision flags v21-tag-header size ext-header frames) me
  119. (format stream "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 stream "~&~4tFrames[~d]:~%~{~8t~a~^~%~}" (length frames) frames)))))
  129. (defmethod initialize-instance :after ((me mp3-id3-header) &key instream &allow-other-keys)
  130. "Fill in an mp3-header from INSTREAM."
  131. (log5:with-context "mp3-id3-header-initializer"
  132. (with-slots (version revision flags size ext-header frames v21-tag-header) me
  133. (stream-seek instream 128 :end)
  134. (when (string= "TAG" (stream-read-string-with-len instream 3))
  135. (log-mp3-frame "looking at last 128 bytes at ~:d to try to read id3v21 header" (stream-seek instream 0 :current))
  136. (handler-case
  137. (setf v21-tag-header (make-instance 'v21-tag-header :instream instream))
  138. (mp3-frame-condition (c)
  139. (log-mp3-frame "reading v21 got condition: ~a" c))))
  140. (stream-seek instream 0 :start)
  141. (when (string= "ID3" (stream-read-string-with-len instream 3))
  142. (setf version (stream-read-u8 instream))
  143. (setf revision (stream-read-u8 instream))
  144. (setf flags (stream-read-u8 instream))
  145. (setf size (stream-read-u32 instream :bits-per-byte 7))
  146. (when (header-unsynchronized-p flags)
  147. (log-mp3-frame "unsync"))
  148. (assert (not (header-footer-p flags)) () "Can't decode ID3 footer's yet"))
  149. (log-mp3-frame "~a" (vpprint me nil)))))
  150. (defclass id3-frame ()
  151. ((pos :accessor pos :initarg :pos)
  152. (id :accessor id :initarg :id)
  153. (len :accessor len :initarg :len)
  154. (version :accessor version :initarg :version)
  155. (flags :accessor flags :initarg :flags :initform nil)) ; unused in v2.2
  156. (:documentation "Base class for an ID3 frame"))
  157. (defmacro frame-23-altertag-p (frame-flags) `(logbitp 15 ,frame-flags))
  158. (defmacro frame-23-alterfile-p (frame-flags) `(logbitp 14 ,frame-flags))
  159. (defmacro frame-23-readonly-p (frame-flags) `(logbitp 13 ,frame-flags))
  160. (defmacro frame-23-compress-p (frame-flags) `(logbitp 7 ,frame-flags))
  161. (defmacro frame-23-encrypt-p (frame-flags) `(logbitp 6 ,frame-flags))
  162. (defmacro frame-23-group-p (frame-flags) `(logbitp 5 ,frame-flags))
  163. (defmacro frame-24-altertag-p (frame-flags) `(logbitp 14 ,frame-flags))
  164. (defmacro frame-24-alterfile-p (frame-flags) `(logbitp 13 ,frame-flags))
  165. (defmacro frame-24-readonly-p (frame-flags) `(logbitp 12 ,frame-flags))
  166. (defmacro frame-24-groupid-p (frame-flags) `(logbitp 6 ,frame-flags))
  167. (defmacro frame-24-compress-p (frame-flags) `(logbitp 3 ,frame-flags))
  168. (defmacro frame-24-encrypt-p (frame-flags) `(logbitp 2 ,frame-flags))
  169. (defmacro frame-24-unsynch-p (frame-flags) `(logbitp 1 ,frame-flags))
  170. (defmacro frame-24-datalen-p (frame-flags) `(logbitp 0 ,frame-flags))
  171. (defun valid-frame-flags (header-version frame-flags)
  172. (ecase header-version
  173. (3 (zerop (logand #b0001111100011111 frame-flags)))
  174. (4 (zerop (logand #b1000111110110000 frame-flags)))))
  175. (defmethod print-object ((me id3-frame) stream)
  176. (if (null *pprint-mp3-frame*)
  177. (call-next-method)
  178. (with-slots (pos version id len flags) me
  179. (format stream "@offset: ~:d, <version = ~d, id: ~s, len: ~:d "
  180. pos version id len)
  181. (if flags
  182. (ecase version
  183. (3 (format stream "flags: 0x~4,'0x: ~:[0/~;tag-alter-preservation/~]~:[0/~;file-alter-preservation/~]~:[0/~;read-only/~]~:[0/~;compress/~]~:[0/~;encypt/~]~:[0~;group~], "
  184. flags
  185. (frame-23-altertag-p flags)
  186. (frame-23-alterfile-p flags)
  187. (frame-23-readonly-p flags)
  188. (frame-23-compress-p flags)
  189. (frame-23-encrypt-p flags)
  190. (frame-23-group-p flags)))
  191. (4 (format stream "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~], "
  192. flags
  193. (frame-24-altertag-p flags)
  194. (frame-24-alterfile-p flags)
  195. (frame-24-readonly-p flags)
  196. (frame-24-groupid-p flags)
  197. (frame-24-compress-p flags)
  198. (frame-24-encrypt-p flags)
  199. (frame-24-unsynch-p flags)
  200. (frame-24-datalen-p flags))))))))
  201. (defclass raw-frame (id3-frame)
  202. ((octets :accessor octets :initform nil))
  203. (:documentation "Frame class that slurps in frame contents"))
  204. (defmethod initialize-instance :after ((me raw-frame) &key instream)
  205. (log5:with-context "raw-frame"
  206. (with-slots (pos len octets) me
  207. (log-mp3-frame "reading ~:d bytes from position ~:d" len pos)
  208. (setf octets (stream-read-sequence instream len))
  209. (log-mp3-frame "frame: ~a" (vpprint me nil)))))
  210. (defparameter *max-raw-bytes-print-len* 10)
  211. (defun printable-array (array)
  212. (let* ((len (length array))
  213. (print-len (min len *max-raw-bytes-print-len*))
  214. (printable-array (make-array print-len :displaced-to array)))
  215. (format nil "[~:d of ~:d bytes] <~x>" print-len len printable-array)))
  216. (defun upto-null (string)
  217. (subseq string 0 (position #\Null string)))
  218. (defmethod print-object :after ((me raw-frame) stream)
  219. (if (null *pprint-mp3-frame*)
  220. (call-next-method)
  221. (with-slots (octets) me
  222. (format stream "~a" (printable-array octets)))))
  223. (defmethod vpprint ((me raw-frame) stream &key (indent 0))
  224. "Set *pprint-mp3-frame* to get pretty printing and call print-object via format"
  225. (let ((*pprint-mp3-frame* t))
  226. (format stream "~vt~a" (* indent 1) me)))
  227. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  228. ;; V22 frames
  229. ;;
  230. ;;; frame I haven't parsed (or don't need to parse)
  231. (defclass frame-buf (raw-frame) ())
  232. (defclass frame-cnt (raw-frame) ())
  233. (defclass frame-cra (raw-frame) ())
  234. (defclass frame-crm (raw-frame) ())
  235. (defclass frame-equ (raw-frame) ())
  236. (defclass frame-etc (raw-frame) ())
  237. (defclass frame-geo (raw-frame) ())
  238. (defclass frame-ipl (raw-frame) ())
  239. (defclass frame-lnk (raw-frame) ())
  240. (defclass frame-mci (raw-frame) ())
  241. (defclass frame-mll (raw-frame) ())
  242. (defclass frame-pop (raw-frame) ())
  243. (defclass frame-rev (raw-frame) ())
  244. (defclass frame-rva (raw-frame) ())
  245. (defclass frame-slt (raw-frame) ())
  246. (defclass frame-ult (raw-frame) ())
  247. (defclass frame-waf (raw-frame) ())
  248. (defclass frame-war (raw-frame) ())
  249. (defclass frame-was (raw-frame) ())
  250. (defclass frame-wcm (raw-frame) ())
  251. (defclass frame-wcp (raw-frame) ())
  252. (defclass frame-wpb (raw-frame) ())
  253. (defclass frame-wxx (raw-frame) ())
  254. (defclass frame-stc (raw-frame) ())
  255. ;; COM frames
  256. ;; Comment "COM"
  257. ;; Frame size $xx xx xx
  258. ;; Text encoding $xx
  259. ;; Language $xx xx xx
  260. ;; Short content description <textstring> $00 (00)
  261. ;; The actual text <textstring>
  262. (defclass frame-com (id3-frame)
  263. ((encoding :accessor encoding)
  264. (lang :accessor lang)
  265. (desc :accessor desc)
  266. (text :accessor text)))
  267. (defmethod initialize-instance :after ((me frame-com) &key instream)
  268. (log5:with-context "frame-com"
  269. (with-slots (len encoding lang desc text) me
  270. (setf encoding (stream-read-u8 instream))
  271. (setf lang (stream-read-iso-string-with-len instream 3))
  272. (multiple-value-bind (n v) (get-name-value-pair instream (- len 1 3) encoding encoding)
  273. (setf desc n)
  274. (setf text v))
  275. (log-mp3-frame "encoding = ~d, lang = <~a>, desc = <~a>, text = <~a>" encoding lang desc text))))
  276. (defmethod print-object :after ((me frame-com) stream)
  277. (if (null *pprint-mp3-frame*)
  278. (call-next-method)
  279. (with-slots (len encoding lang desc text) me
  280. (format stream "frame-com, encoding = ~d, lang = <~a>, desc = <~a>, text = <~a>" encoding lang desc text))))
  281. (defmethod vpprint ((me frame-com) stream &key (indent 0))
  282. (let ((*pprint-mp3-frame* t))
  283. (format stream "~vt~a" (* indent 1) me)))
  284. ;; v22 PIC
  285. ;; Attached picture "PIC"
  286. ;; Frame size $xx xx xx
  287. ;; Text encoding $xx
  288. ;; Image format $xx xx xx
  289. ;; Picture type $xx
  290. ;; Description <textstring> $00 (00)
  291. ;; Picture data <binary data>
  292. (defclass frame-pic (id3-frame)
  293. ((encoding :accessor encoding)
  294. (img-format :accessor img-format)
  295. (type :accessor type)
  296. (desc :accessor desc)
  297. (data :accessor data)))
  298. (defmethod initialize-instance :after ((me frame-pic) &key instream)
  299. (log5:with-context "frame-pic"
  300. (with-slots (id len encoding img-format type desc data) me
  301. (setf encoding (stream-read-u8 instream))
  302. (setf img-format (stream-read-iso-string-with-len instream 3))
  303. (setf type (stream-read-u8 instream))
  304. (multiple-value-bind (n v) (get-name-value-pair instream (- len 5) encoding -1)
  305. (setf desc n)
  306. (setf data v)
  307. (log-mp3-frame "encoding: ~d, img-format = <~a>, type = ~d, desc = <~a>, value = ~a"
  308. encoding img-format type desc (printable-array data))))))
  309. (defmethod print-object :after ((me frame-pic) stream)
  310. (if (null *pprint-mp3-frame*)
  311. (call-next-method)
  312. (with-slots (encoding img-format type desc data) me
  313. (format stream "frame-pic: encoding ~d, img-format type: <~a>, picture type: ~d, description <~s>, data: ~a"
  314. encoding img-format type desc (printable-array data)))))
  315. (defmethod vpprint ((me frame-pic) stream &key (indent 0))
  316. (let ((*pprint-mp3-frame* t))
  317. (format stream "~vt~a" (* indent 1) me)))
  318. ;; Generic text-info frames
  319. ;; Text information identifier "T00" - "TZZ" , excluding "TXX", or "T000 - TZZZ", excluding "TXXX"
  320. ;; Text encoding $xx
  321. ;; Information <textstring>
  322. (defclass frame-text-info (id3-frame)
  323. ((encoding :accessor encoding)
  324. (info :accessor info)))
  325. (defmethod initialize-instance :after ((me frame-text-info) &key instream)
  326. (log5:with-context "frame-text-info"
  327. (with-slots (len encoding info) me
  328. (setf encoding (stream-read-u8 instream))
  329. (setf info (stream-read-string-with-len instream (1- len) :encoding encoding))
  330. ;; a null is ok, but according to the "spec", you're supposed to ignore anything after a 'Null'
  331. (setf info (upto-null info))
  332. (log-mp3-frame "encoding = ~d, info = <~a>" encoding info))))
  333. (defmethod print-object :after ((me frame-text-info) stream)
  334. (if (null *pprint-mp3-frame*)
  335. (call-next-method)
  336. (with-slots (len encoding info) me
  337. (format stream "frame-text-info, encoding = ~d, info = <~a>" encoding info))))
  338. (defmethod vpprint ((me frame-text-info) stream &key (indent 0))
  339. (let ((*pprint-mp3-frame* t))
  340. (format stream "~vt~a" (* indent 1) me)))
  341. ;; v22 User defined... "TXX" frames
  342. ;; Frame size $xx xx xx
  343. ;; Text encoding $xx
  344. ;; Description <textstring> $00 (00)
  345. ;; Value <textstring>
  346. (defclass frame-txx (id3-frame)
  347. ((encoding :accessor encoding)
  348. (desc :accessor desc)
  349. (value :accessor value)))
  350. (defmethod initialize-instance :after ((me frame-txx) &key instream)
  351. (log5:with-context "frame-txx"
  352. (with-slots (len encoding desc value) me
  353. (setf encoding (stream-read-u8 instream))
  354. (multiple-value-bind (n v) (get-name-value-pair instream (1- len) encoding encoding)
  355. (setf desc n)
  356. (setf value v)
  357. (log-mp3-frame "encoding = ~d, desc = <~a>, value = <~a>" encoding desc value)))))
  358. (defmethod print-object :after ((me frame-txx) stream)
  359. (if (null *pprint-mp3-frame*)
  360. (call-next-method)
  361. (with-slots (len encoding desc value) me
  362. (format stream "frame-txx, encoding = ~d, desc = <~a>, value = <~a>" encoding desc value))))
  363. (defmethod vpprint ((me frame-txx) stream &key (indent 0))
  364. (let ((*pprint-mp3-frame* t))
  365. (format stream "~vt~a" (* indent 1) me)))
  366. (defclass frame-ufi (id3-frame)
  367. ((name :accessor name)
  368. (value :accessor value)))
  369. (defmethod initialize-instance :after ((me frame-ufi) &key instream)
  370. (log5:with-context "frame-ufi"
  371. (with-slots (id len name value) me
  372. (multiple-value-bind (n v) (get-name-value-pair instream len 0 -1)
  373. (setf name n)
  374. (setf value v))
  375. (log-mp3-frame "name = <~a>, value = ~a" name (printable-array value)))))
  376. (defmethod print-object :after ((me frame-ufi) stream)
  377. (if (null *pprint-mp3-frame*)
  378. (call-next-method)
  379. (with-slots (id len name value) me
  380. (format stream "frame-ufi: name: <~s>, value: ~a" name (printable-array value)))))
  381. (defmethod vpprint ((me frame-ufi) stream &key (indent 0))
  382. (let ((*pprint-mp3-frame* t))
  383. (format stream "~vt~a" (* indent 1) me)))
  384. (defclass frame-tal (frame-text-info) ())
  385. (defclass frame-tbp (frame-text-info) ())
  386. (defclass frame-tcm (frame-text-info) ())
  387. (defclass frame-tco (frame-text-info) ())
  388. (defclass frame-tcp (frame-text-info) ())
  389. (defclass frame-tcr (frame-text-info) ())
  390. (defclass frame-tda (frame-text-info) ())
  391. (defclass frame-tdy (frame-text-info) ())
  392. (defclass frame-ten (frame-text-info) ())
  393. (defclass frame-tft (frame-text-info) ())
  394. (defclass frame-tim (frame-text-info) ())
  395. (defclass frame-tke (frame-text-info) ())
  396. (defclass frame-tla (frame-text-info) ())
  397. (defclass frame-tle (frame-text-info) ())
  398. (defclass frame-tmt (frame-text-info) ())
  399. (defclass frame-toa (frame-text-info) ())
  400. (defclass frame-tof (frame-text-info) ())
  401. (defclass frame-tol (frame-text-info) ())
  402. (defclass frame-tor (frame-text-info) ())
  403. (defclass frame-tot (frame-text-info) ())
  404. (defclass frame-tp1 (frame-text-info) ())
  405. (defclass frame-tp2 (frame-text-info) ())
  406. (defclass frame-tp3 (frame-text-info) ())
  407. (defclass frame-tp4 (frame-text-info) ())
  408. (defclass frame-tpa (frame-text-info) ())
  409. (defclass frame-tpb (frame-text-info) ())
  410. (defclass frame-trc (frame-text-info) ())
  411. (defclass frame-trd (frame-text-info) ())
  412. (defclass frame-trk (frame-text-info) ())
  413. (defclass frame-tsi (frame-text-info) ())
  414. (defclass frame-tss (frame-text-info) ())
  415. (defclass frame-tt1 (frame-text-info) ())
  416. (defclass frame-tt2 (frame-text-info) ())
  417. (defclass frame-tt3 (frame-text-info) ())
  418. (defclass frame-txt (frame-text-info) ())
  419. (defclass frame-tye (frame-text-info) ())
  420. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  421. ;; V2.3/4 frames
  422. ;;
  423. ;; <Header for 'Audio encryption', ID: "AENC">
  424. ;; Owner identifier <text string> $00
  425. ;; Preview start $xx xx
  426. ;; Preview length $xx xx
  427. ;; Encryption info <binary data>
  428. (defclass frame-aenc (raw-frame) ())
  429. (defclass frame-aspi (raw-frame) ())
  430. (defclass frame-comr (raw-frame) ())
  431. (defclass frame-encr (raw-frame) ())
  432. (defclass frame-equ2 (raw-frame) ())
  433. (defclass frame-equa (raw-frame) ())
  434. (defclass frame-etco (raw-frame) ())
  435. (defclass frame-geob (raw-frame) ())
  436. (defclass frame-grid (raw-frame) ())
  437. (defclass frame-ipls (raw-frame) ())
  438. (defclass frame-link (raw-frame) ())
  439. (defclass frame-mcdi (raw-frame) ())
  440. (defclass frame-mllt (raw-frame) ())
  441. (defclass frame-ncon (raw-frame) ())
  442. (defclass frame-owne (raw-frame) ())
  443. (defclass frame-popm (raw-frame) ())
  444. (defclass frame-poss (raw-frame) ())
  445. (defclass frame-rbuf (raw-frame) ())
  446. (defclass frame-rva2 (raw-frame) ())
  447. (defclass frame-rvad (raw-frame) ())
  448. (defclass frame-rvrb (raw-frame) ())
  449. (defclass frame-seek (raw-frame) ())
  450. (defclass frame-sign (raw-frame) ())
  451. (defclass frame-sylt (raw-frame) ())
  452. (defclass frame-sytc (raw-frame) ())
  453. (defclass frame-user (raw-frame) ())
  454. (defclass frame-uslt (raw-frame) ())
  455. ;; APIC
  456. ;; <Header for 'Attached picture', ID: "APIC">
  457. ;; Text encoding $xx
  458. ;; MIME type <text string> $00
  459. ;; Picture type $xx
  460. ;; Description <text string according to encoding> $00 (00)
  461. ;; Picture data <binary data>
  462. (defclass frame-apic (id3-frame)
  463. ((encoding :accessor encoding)
  464. (mime :accessor mime)
  465. (type :accessor type)
  466. (desc :accessor desc)
  467. (data :accessor data)))
  468. (defmethod initialize-instance :after ((me frame-apic) &key instream)
  469. (log5:with-context "frame-apic"
  470. (with-slots (id len encoding mime type desc data) me
  471. (setf encoding (stream-read-u8 instream))
  472. (setf mime (stream-read-iso-string instream))
  473. (setf type (stream-read-u8 instream))
  474. (multiple-value-bind (n v) (get-name-value-pair instream (- len 1 (length mime) 1 1) encoding -1)
  475. (setf desc n)
  476. (setf data v)
  477. (log-mp3-frame "enoding = ~d, mime = <~a>, type = ~d, descx = <~a>, data = ~a" encoding mime type desc (printable-array data))))))
  478. (defmethod print-object :after ((me frame-apic) stream)
  479. (if (null *pprint-mp3-frame*)
  480. (call-next-method)
  481. (with-slots (encoding mime type desc data) me
  482. (format stream "frame-apic: encoding ~d, mime type: ~s, picture type: ~d, description <~s>, data: ~a"
  483. encoding mime type desc (printable-array data)))))
  484. (defmethod vpprint ((me frame-apic) stream &key (indent 0))
  485. "Set *pprint-mp3-frame* to get pretty printing and call print-object via format"
  486. (let ((*pprint-mp3-frame* t))
  487. (format stream "~vt~a" (* indent 1) me)))
  488. ;; COMM frames
  489. ;; <Header for 'Comment', ID: "COMM">
  490. ;; Text encoding $xx
  491. ;; Language $xx xx xx
  492. ;; Short content descrip. <text string according to encoding> $00 (00)
  493. ;; The actual text <full text string according to encoding>
  494. (defclass frame-comm (id3-frame)
  495. ((encoding :accessor encoding)
  496. (lang :accessor lang)
  497. (desc :accessor desc)
  498. (val :accessor val)))
  499. (defmethod initialize-instance :after ((me frame-comm) &key instream)
  500. (log5:with-context "frame-comm"
  501. (with-slots (encoding lang len desc val) me
  502. (setf encoding (stream-read-u8 instream))
  503. (setf lang (stream-read-iso-string-with-len instream 3))
  504. (multiple-value-bind (n v) (get-name-value-pair instream (- len 1 3) encoding encoding)
  505. (setf desc n)
  506. (setf val v))
  507. (log-mp3-frame "encoding = ~d, lang = <~a>, desc = <~a>, val = <~a>" encoding lang desc val))))
  508. (defmethod print-object :after ((me frame-comm) stream)
  509. (if (null *pprint-mp3-frame*)
  510. (call-next-method)
  511. (with-slots (encoding lang desc val) me
  512. (format stream "frame-comm: encoding: ~d, lang: ~x, desc: ~s, val ~s"
  513. encoding lang desc val))))
  514. (defmethod vpprint ((me frame-comm) stream &key (indent 0))
  515. "Set *pprint-mp3-frame* to get pretty printing and call print-object via format"
  516. (let ((*pprint-mp3-frame* t))
  517. (format stream "~vt~a" (* indent 1) me)))
  518. ;; PCNT frames
  519. ;; <Header for 'Play counter', ID: "PCNT">
  520. ;; Counter $xx xx xx xx (xx ...)
  521. (defclass frame-pcnt (id3-frame)
  522. ((play-count :accessor play-count)))
  523. (defmethod initialize-instance :after ((me frame-pcnt) &key instream)
  524. (log5:with-context "frame-pcnt"
  525. (with-slots (play-count len) me
  526. (setf play-count (stream-read-sequence instream len)) ;; XXX read this as a number???
  527. (log-mp3-frame "play count = <~a>" play-count))))
  528. (defmethod print-object :after ((me frame-pcnt) stream)
  529. (if (null *pprint-mp3-frame*)
  530. (call-next-method)
  531. (with-slots (play-count) me
  532. (format stream "frame-pcnt: ~d" play-count))))
  533. (defmethod vpprint ((me frame-pcnt) stream &key (indent 0))
  534. "Set *pprint-mp3-frame* to get pretty printing and call print-object via format"
  535. (let ((*pprint-mp3-frame* t))
  536. (format stream "~vt~a" (* indent 1) me)))
  537. ;; PRIV frames
  538. ;; <Header for 'Private frame', ID: "PRIV">
  539. ;; Owner identifier <text string> $00
  540. ;; The private data <binary data>
  541. (defclass frame-priv (id3-frame)
  542. ((name :accessor name)
  543. (value :accessor value)))
  544. (defmethod initialize-instance :after ((me frame-priv) &key instream)
  545. (log5:with-context "frame-priv"
  546. (with-slots (id len name value) me
  547. (multiple-value-bind (n v) (get-name-value-pair instream len 0 -1)
  548. (setf name n)
  549. (setf value v)
  550. (log-mp3-frame "name = <~a>, value = <~a>" name value)))))
  551. (defmethod print-object :after ((me frame-priv) stream)
  552. (if (null *pprint-mp3-frame*)
  553. (call-next-method)
  554. (with-slots (id len name value) me
  555. (format stream "frame-priv: name: <~s>, data: ~a" name (printable-array value)))))
  556. (defmethod vpprint ((me frame-priv) stream &key (indent 0))
  557. "Set *pprint-mp3-frame* to get pretty printing and call print-object via format"
  558. (let ((*pprint-mp3-frame* t))
  559. (format stream "~vt~a" (* indent 1) me)))
  560. ;; TXXX frames
  561. ;; <Header for 'User defined text information frame', ID: "TXXX">
  562. ;; Text encoding $xx
  563. ;; Description <text string according to encoding> $00 (00)
  564. ;; Value <text string according to encoding>
  565. (defclass frame-txxx (id3-frame)
  566. ((encoding :accessor encoding)
  567. (desc :accessor desc)
  568. (value :accessor value)))
  569. (defmethod initialize-instance :after ((me frame-txxx) &key instream)
  570. (log5:with-context "frame-txxx"
  571. (with-slots (encoding len desc value) me
  572. (setf encoding (stream-read-u8 instream))
  573. (multiple-value-bind (n v) (get-name-value-pair instream
  574. (- len 1)
  575. encoding
  576. encoding)
  577. (setf desc n)
  578. (setf value v))
  579. (log-mp3-frame "encoding = ~d, desc = <~a>, value = <~a>" encoding desc value))))
  580. (defmethod print-object :after ((me frame-txxx) stream)
  581. (if (null *pprint-mp3-frame*)
  582. (call-next-method)
  583. (format stream "frame-txxx: <~s/~s>" (desc me) (value me))))
  584. (defmethod vpprint ((me frame-txxx) stream &key (indent 0))
  585. "Set *pprint-mp3-frame* to get pretty printing and call print-object via format"
  586. (let ((*pprint-mp3-frame* t))
  587. (format stream "~vt~a" (* indent 1) me)))
  588. ;; UFID frames
  589. ;; <Header for 'Unique file identifier', ID: "UFID">
  590. ;; Owner identifier <text string> $00
  591. ;; Identifier <up to 64 bytes binary data>
  592. (defclass frame-ufid (id3-frame)
  593. ((name :accessor name)
  594. (value :accessor value)))
  595. (defmethod initialize-instance :after ((me frame-ufid) &key instream)
  596. (log5:with-context "frame-ufid"
  597. (with-slots (id len name value) me
  598. (multiple-value-bind (n v) (get-name-value-pair instream len 0 -1)
  599. (setf name n)
  600. (setf value v))
  601. (log-mp3-frame "name = <~a>, value = ~a" name (printable-array value)))))
  602. (defmethod print-object :after ((me frame-ufid) stream)
  603. (if (null *pprint-mp3-frame*)
  604. (call-next-method)
  605. (with-slots (id len name value) me
  606. (format stream "frame-ufid: name: <~s>, value: ~a" name (printable-array value)))))
  607. (defmethod vpprint ((me frame-ufid) stream &key (indent 0))
  608. "Set *pprint-mp3-frame* to get pretty printing and call print-object via format"
  609. (let ((*pprint-mp3-frame* t))
  610. (format stream "~vt~a" (* indent 1) me)))
  611. ;; URL frame
  612. ;; <Header for 'URL link frame', ID: "W000" - "WZZZ", excluding "WXXX" described in 4.3.2.>
  613. ;; URL <text string>
  614. (defclass frame-url-link (id3-frame)
  615. ((url :accessor url)))
  616. (defmethod initialize-instance :after ((me frame-url-link) &key instream)
  617. (with-slots (id len url) me
  618. (log5:with-context "url"
  619. (setf url (stream-read-iso-string-with-len instream len))
  620. (log-mp3-frame "url = <~a>" url))))
  621. (defmethod print-object :after ((me frame-url-link) stream)
  622. (if (null *pprint-mp3-frame*)
  623. (call-next-method)
  624. (with-slots (url) me
  625. (format stream "frame-url-link: url: ~s" url))))
  626. (defmethod vpprint ((me frame-url-link) stream &key (indent 0))
  627. "Set *pprint-mp3-frame* to get pretty printing and call print-object via format"
  628. (let ((*pprint-mp3-frame* t))
  629. (format stream "~vt~a" (* indent 1) me)))
  630. (defclass frame-talb (frame-text-info) ())
  631. (defclass frame-tbpm (frame-text-info) ())
  632. (defclass frame-tcmp (frame-text-info) ())
  633. (defclass frame-tcom (frame-text-info) ())
  634. (defclass frame-tcon (frame-text-info) ())
  635. (defclass frame-tcop (frame-text-info) ())
  636. (defclass frame-tdat (frame-text-info) ())
  637. (defclass frame-tden (frame-text-info) ())
  638. (defclass frame-tdly (frame-text-info) ())
  639. (defclass frame-tdor (frame-text-info) ())
  640. (defclass frame-tdrc (frame-text-info) ())
  641. (defclass frame-tdrl (frame-text-info) ())
  642. (defclass frame-tdtg (frame-text-info) ())
  643. (defclass frame-tenc (frame-text-info) ())
  644. (defclass frame-text (frame-text-info) ())
  645. (defclass frame-tflt (frame-text-info) ())
  646. (defclass frame-time (frame-text-info) ())
  647. (defclass frame-tipl (frame-text-info) ())
  648. (defclass frame-tit1 (frame-text-info) ())
  649. (defclass frame-tit2 (frame-text-info) ())
  650. (defclass frame-tit3 (frame-text-info) ())
  651. (defclass frame-tkey (frame-text-info) ())
  652. (defclass frame-tlan (frame-text-info) ())
  653. (defclass frame-tlen (frame-text-info) ())
  654. (defclass frame-tmcl (frame-text-info) ())
  655. (defclass frame-tmed (frame-text-info) ())
  656. (defclass frame-tmoo (frame-text-info) ())
  657. (defclass frame-toal (frame-text-info) ())
  658. (defclass frame-tofn (frame-text-info) ())
  659. (defclass frame-toly (frame-text-info) ())
  660. (defclass frame-tope (frame-text-info) ())
  661. (defclass frame-tory (frame-text-info) ())
  662. (defclass frame-town (frame-text-info) ())
  663. (defclass frame-tpe1 (frame-text-info) ())
  664. (defclass frame-tpe2 (frame-text-info) ())
  665. (defclass frame-tpe3 (frame-text-info) ())
  666. (defclass frame-tpe4 (frame-text-info) ())
  667. (defclass frame-tpos (frame-text-info) ())
  668. (defclass frame-tpro (frame-text-info) ())
  669. (defclass frame-tpub (frame-text-info) ())
  670. (defclass frame-trck (frame-text-info) ()) ; XXX should change string of eg "1/10" to be (values 1 10)?
  671. (defclass frame-trda (frame-text-info) ())
  672. (defclass frame-trsn (frame-text-info) ())
  673. (defclass frame-trso (frame-text-info) ())
  674. (defclass frame-tsoa (frame-text-info) ())
  675. (defclass frame-tsop (frame-text-info) ())
  676. (defclass frame-tsot (frame-text-info) ())
  677. (defclass frame-tsst (frame-text-info) ())
  678. (defclass frame-tsse (frame-text-info) ())
  679. (defclass frame-tsrc (frame-text-info) ())
  680. (defclass frame-tsiz (frame-text-info) ())
  681. (defclass frame-tyer (frame-text-info) ())
  682. (defclass frame-wcom (frame-url-link) ())
  683. (defclass frame-wcop (frame-url-link) ())
  684. (defclass frame-woaf (frame-url-link) ())
  685. (defclass frame-woar (frame-url-link) ())
  686. (defclass frame-woas (frame-url-link) ())
  687. (defclass frame-wors (frame-url-link) ())
  688. (defclass frame-wpay (frame-url-link) ())
  689. (defclass frame-wpub (frame-url-link) ())
  690. (defclass frame-wxxx (frame-url-link) ())
  691. ;;
  692. ;; many id3 tags are name/value pairs, with the name/value encoded in various ways
  693. ;; this routine assumes that the name is always a string with a "normal" encoding (i.e. 0, 1, 2, or 3).
  694. ;; a value, however, accepts any negative number, which means read
  695. ;; the bytes an raw octets.
  696. (defun get-name-value-pair (instream len name-encoding value-encoding)
  697. (log5:with-context "get-name-value-pair"
  698. (log-mp3-frame "reading from ~:d, len ~:d, name-encoding = ~d, value-encoding = ~d" (stream-seek instream 0 :current) len name-encoding value-encoding)
  699. (let* ((old-pos (stream-seek instream 0 :current))
  700. (name (stream-read-string instream :encoding name-encoding))
  701. (name-len (- (stream-seek instream 0 :current) old-pos))
  702. (value))
  703. (log-mp3-frame "name = <~a>, name-len = ~d" name name-len)
  704. (setf value (if (>= value-encoding 0)
  705. (stream-read-string-with-len instream (- len name-len) :encoding value-encoding)
  706. (stream-read-sequence instream (- len name-len)))) ; if < 0, then just read as octets
  707. (values name value))))
  708. ;;
  709. ;; test to see if a string is a potentially valid frame id
  710. (defun possibly-valid-frame-id? (frame-id)
  711. (labels ((numeric-char-p (c)
  712. (let ((code (char-code c)))
  713. (and (>= code (char-code #\0))
  714. (<= code (char-code #\9))))))
  715. (dotimes (i (length frame-id))
  716. (let ((c (aref frame-id i)))
  717. (when (not (or (numeric-char-p c)
  718. (and (alpha-char-p c) (upper-case-p c))))
  719. (return-from possibly-valid-frame-id? nil))))
  720. t))
  721. ;;; Search by frame-id for a class, returning a class that can be used as arg to
  722. ;;; make-instance.
  723. (defun find-frame-class (id)
  724. (log5:with-context "find-frame-class"
  725. (log-mp3-frame "looking for class <~a>" id)
  726. (let ((found-class-symbol (find-symbol (string-upcase (concatenate 'string "frame-" id)) :MP3-FRAME))
  727. found-class)
  728. (when found-class-symbol
  729. (setf found-class (find-class found-class-symbol))
  730. (log-mp3-frame "found class: ~a" found-class)
  731. (return-from find-frame-class found-class))
  732. (log-mp3-frame "didn't find class, checking general cases")
  733. ;; if not a "normal" frame-id, look at general cases of
  734. ;; starting with a 'T' or a 'W'
  735. (setf found-class (case (aref id 0)
  736. (#\T (log-mp3-frame "assuming text-info") (find-class (find-symbol "FRAME-TEXT-INFO" :MP3-FRAME)))
  737. (#\W (log-mp3-frame "assuming url-link") (find-class (find-symbol "FRAME-URL-LINK" :MP3-FRAME)))
  738. (t
  739. ;; we don't recognize the frame name. if it could possibly be a real frame name,
  740. ;; then just read it raw
  741. (when (possibly-valid-frame-id? id)
  742. (log-mp3-frame "just reading raw")
  743. (find-class (find-symbol "RAW-FRAME" :MP3-FRAME))))))
  744. (log-mp3-frame "general case for id <~a> is ~a" id found-class)
  745. found-class)))
  746. (defun make-frame (version instream)
  747. "Create an appropriate mp3 frame by reading data from INSTREAM."
  748. (log5:with-context "find-mp3-frames"
  749. (let* ((pos (stream-seek instream 0 :current))
  750. (byte (stream-read-u8 instream))
  751. frame-name frame-len frame-flags frame-class)
  752. (log-mp3-frame "reading from position ~:d (size of stream = ~:d" pos (stream-size instream))
  753. (when (zerop byte)
  754. (log-mp3-frame "hit padding")
  755. (return-from make-frame nil)) ; hit padding
  756. (setf frame-name
  757. (concatenate 'string (string (code-char byte)) (stream-read-string-with-len instream (ecase version (2 2) (3 3) (4 3)))))
  758. (setf frame-len (ecase version
  759. (2 (stream-read-u24 instream))
  760. (3 (stream-read-u32 instream))
  761. (4 (stream-read-u32 instream :bits-per-byte 7))))
  762. (when (or (= version 3) (= version 4))
  763. (setf frame-flags (stream-read-u16 instream)))
  764. (log-mp3-frame "making frame: id:~a, version: ~d, len: ~:d, flags: ~x" frame-name version frame-len frame-flags)
  765. (setf frame-class (find-frame-class frame-name))
  766. (when (or (> (+ (stream-seek instream 0 :current) frame-len) (stream-size instream))
  767. (null frame-class))
  768. (error 'mp3-frame-condition :message "bad frame found" :object frame-name :location pos))
  769. (make-instance frame-class :pos pos :version version :id frame-name :len frame-len :flags frame-flags :instream instream))))
  770. (defun find-mp3-frames (mp3-file)
  771. "With an open mp3-file, make sure it is in fact an MP3 file, then read it's header and frames"
  772. (labels ((read-loop (version stream)
  773. (log-mp3-frame "Starting loop through ~:d bytes" (stream-size stream))
  774. (let (frames this-frame)
  775. (do ()
  776. ((>= (stream-seek stream 0 :current) (stream-size stream)))
  777. (handler-case
  778. (progn
  779. (setf this-frame (make-frame version stream))
  780. (when (null this-frame)
  781. (log-mp3-frame "hit padding: returning ~d frames" (length frames))
  782. (return-from read-loop (values t (nreverse frames))))
  783. (log-mp3-frame "bottom of read-loop: pos = ~:d, size = ~:d" (stream-seek stream 0 :current) (stream-size stream))
  784. (push this-frame frames))
  785. (condition (c)
  786. (log-mp3-frame "got condition ~a when making frame" c)
  787. (return-from read-loop (values nil (nreverse frames))))))
  788. (log-mp3-frame "hit end: returning ~d frames" (length frames))
  789. (values t (nreverse frames)))))
  790. (log5:with-context "find-mp3-frames"
  791. (when (not (is-valid-mp3-file mp3-file))
  792. (log-mp3-frame "~a is not an mp3 file" mp3-file)
  793. (error 'mp3-frame-condition :location "find-mp3-frames" :object mp3-file :message "is not an mp3 file"))
  794. (log-mp3-frame "~a is a valid mp3 file" (stream-filename mp3-file))
  795. (setf (mp3-header mp3-file) (make-instance 'mp3-id3-header :instream mp3-file))
  796. (with-slots (size ext-header frames flags version) (mp3-header mp3-file)
  797. (when (not (zerop size))
  798. (let ((mem-stream (make-mem-stream (stream-read-sequence mp3-file size
  799. :bits-per-byte (if (header-unsynchronized-p flags) 7 8)))))
  800. ;; must make extended header here since it is subject to unsynchronization.
  801. (when (header-extended-p flags)
  802. (setf ext-header (make-instance 'mp3-extended-header :instream mem-stream)))
  803. (multiple-value-bind (_ok _frames) (read-loop version mem-stream)
  804. (if (not _ok)
  805. (warn "had an error finding mp3 frames. potentially missed frames!"))
  806. (log-mp3-frame "ok = ~a, returing ~d frames" _ok (length _frames))
  807. (setf frames _frames)
  808. _ok)))))))
  809. (defun get-frame-info (mp3-file frame-id)
  810. (with-slots (frames version) (mp3-header mp3-file)
  811. (dolist (f frames)
  812. (if (string= frame-id (id f))
  813. (return-from get-frame-info f)))))