mp4-atom.lisp 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445
  1. ;;; -*- Mode: Lisp; show-trailing-whitespace: t; Base: 10; indent-tabs: nil; Syntax: ANSI-Common-Lisp; Package: MP4-ATOM; -*-
  2. ;;; Copyright (c) 2013, Mark VandenBrink. All rights reserved.
  3. (in-package #:mp4-atom)
  4. (log5:defcategory cat-log-mp4-atom)
  5. (defmacro log-mp4-atom (&rest log-stuff) `(log5:log-for (cat-log-mp4-atom) ,@log-stuff))
  6. (define-condition mp4-atom-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 "mp4-atom condition at location: <~a> with object: <~a>: message: <~a>"
  12. (location condition) (object condition) (message condition)))))
  13. (defmethod print-object ((me mp4-atom-condition) stream)
  14. (format stream "location: <~a>, object: <~a>, message: <~a>" (location me) (object me) (message me)))
  15. (defmethod as-string ((atom-type integer))
  16. "Given an integer representing an atom type, return the string form"
  17. (with-output-to-string (s nil)
  18. (write-char (code-char (ldb (byte 8 24) atom-type)) s)
  19. (write-char (code-char (ldb (byte 8 16) atom-type)) s)
  20. (write-char (code-char (ldb (byte 8 8) atom-type)) s)
  21. (write-char (code-char (ldb (byte 8 0) atom-type)) s)))
  22. (eval-when (:compile-toplevel :load-toplevel :execute)
  23. (defun as-octet (c)
  24. "Used below so that we can create atom 'types' from char/ints"
  25. (cond ((typep c 'standard-char) (coerce (char-code c) '(unsigned-byte 8)))
  26. ((typep c 'integer) (coerce c '(unsigned-byte 8)))
  27. (t (error "can any handle characters and integers"))))
  28. (defmacro mk-mp4-atom-type (l1 l2 l3 l4)
  29. "Given 4 chars/ints, create a 32-bit word representing an atom 'type' (aka name)"
  30. `(let ((retval 0))
  31. (setf (ldb (byte 8 24) retval) ,(as-octet l1))
  32. (setf (ldb (byte 8 16) retval) ,(as-octet l2))
  33. (setf (ldb (byte 8 8) retval) ,(as-octet l3))
  34. (setf (ldb (byte 8 0) retval) ,(as-octet l4))
  35. retval))
  36. (defconstant +m4-ftyp+ (mk-mp4-atom-type #\f #\t #\y #\p)
  37. "This should be the first atom type found in file")
  38. (defconstant +itunes-ilst-data+ (mk-mp4-atom-type #\d #\a #\t #\a)
  39. "Carries the actual data under an ilst atom")
  40. (defconstant +itunes-lyrics+ (mk-mp4-atom-type #xa9 #\l #\y #\r)) ; text
  41. (defconstant +itunes-copyright+ (mk-mp4-atom-type #\c #\p #\r #\t)) ; text
  42. (defconstant +itunes-album+ (mk-mp4-atom-type #xa9 #\a #\l #\b)) ; text
  43. (defconstant +itunes-artist+ (mk-mp4-atom-type #xa9 #\A #\R #\T)) ; text
  44. (defconstant +itunes-comment+ (mk-mp4-atom-type #xa9 #\c #\m #\t)) ; text
  45. (defconstant +itunes-compilation+ (mk-mp4-atom-type #\c #\p #\i #\l)) ; byte/boolean
  46. (defconstant +itunes-composer+ (mk-mp4-atom-type #xa9 #\c #\o #\m)) ; text
  47. (defconstant +itunes-cover-art+ (mk-mp4-atom-type #\c #\o #\v #\r)) ; octets
  48. (defconstant +itunes-year+ (mk-mp4-atom-type #xa9 #\d #\a #\y)) ; text
  49. (defconstant +itunes-disk+ (mk-mp4-atom-type #\d #\i #\s #\k)) ; octets
  50. (defconstant +itunes-tool+ (mk-mp4-atom-type #xa9 #\t #\o #\o)) ; text
  51. (defconstant +itunes-genre+ (mk-mp4-atom-type #\g #\n #\r #\e)) ; octet
  52. (defconstant +itunes-genre-x+ (mk-mp4-atom-type #xa9 #\n #\r #\e)) ; text
  53. (defconstant +itunes-groups+ (mk-mp4-atom-type #xa9 #\g #\r #\p)) ; text
  54. (defconstant +itunes-title+ (mk-mp4-atom-type #xa9 #\n #\a #\m)) ; text
  55. (defconstant +itunes-tempo+ (mk-mp4-atom-type #\t #\m #\p #\o)) ; octet
  56. (defconstant +itunes-track+ (mk-mp4-atom-type #xa9 #\t #\r #\k)) ; octet
  57. (defconstant +itunes-track-n+ (mk-mp4-atom-type #\t #\r #\k #\n)) ; octet
  58. (defconstant +itunes-writer+ (mk-mp4-atom-type #xa9 #\w #\r #\t)) ; text
  59. (defconstant +itunes-encoder+ (mk-mp4-atom-type #xa9 #\e #\n #\c)) ; text
  60. (defconstant +itunes-album-artist+ (mk-mp4-atom-type #\a #\A #\R #\T)) ; text
  61. (defconstant +itunes-purchased-date+ (mk-mp4-atom-type #\p #\u #\r #\d)) ; text
  62. (defparameter *itunes-text-atom-types*
  63. (list
  64. +itunes-album+
  65. +itunes-album-artist+
  66. +itunes-artist+
  67. +itunes-comment+
  68. +itunes-composer+
  69. +itunes-copyright+
  70. +itunes-year+
  71. +itunes-encoder+
  72. +itunes-groups+
  73. +itunes-genre-x+
  74. +itunes-lyrics+
  75. +itunes-purchased-date+
  76. +itunes-title+
  77. +itunes-tool+
  78. +itunes-writer+)
  79. "These are all the itunes atoms that are stored as text")
  80. (defparameter *itunes-atom-types*
  81. (append *itunes-text-atom-types*
  82. (list
  83. +itunes-compilation+
  84. +itunes-cover-art+
  85. +itunes-disk+
  86. +itunes-genre+
  87. +itunes-tempo+
  88. +itunes-track+
  89. +itunes-track-n+))
  90. "The iTunes atom types we can decode")
  91. (defconstant +mp4-atom-moov+ (mk-mp4-atom-type #\m #\o #\o #\v))
  92. (defconstant +mp4-atom-udta+ (mk-mp4-atom-type #\u #\d #\t #\a))
  93. (defconstant +mp4-atom-mdia+ (mk-mp4-atom-type #\m #\d #\i #\a))
  94. (defconstant +mp4-atom-meta+ (mk-mp4-atom-type #\m #\e #\t #\a))
  95. (defconstant +mp4-atom-ilst+ (mk-mp4-atom-type #\i #\l #\s #\t))
  96. (defparameter *atoms-of-interest*
  97. (list +mp4-atom-moov+
  98. +mp4-atom-udta+
  99. +mp4-atom-mdia+
  100. +mp4-atom-meta+
  101. +mp4-atom-ilst+)
  102. "For these container atoms, we look inside these atoms to read nested atoms")
  103. (defparameter *tag-path* (list +mp4-atom-moov+ +mp4-atom-udta+ +mp4-atom-meta+ +mp4-atom-ilst+)
  104. "The 'path' of nested atoms at which tag data can be found.")
  105. (defgeneric decode-ilst-data-atom (type atom atom-parent-type mp4-file))
  106. (defmacro generate-generic-text-methods ()
  107. "generate the decode methods for text atoms"
  108. (let ((methods))
  109. (dolist (type *itunes-text-atom-types*)
  110. (push `(defmethod decode-ilst-data-atom ((type (eql +itunes-ilst-data+)) atom (atom-parent-type (eql ,type)) mp4-file)
  111. (stream-read-string-with-len mp4-file (- (atom-size atom) 16))) methods))
  112. `(progn ,@methods)))
  113. )
  114. (generate-generic-text-methods)
  115. (defmethod decode-ilst-data-atom ((type (eql +itunes-ilst-data+)) atom (atom-parent-type (eql +itunes-disk+)) mp4-file)
  116. "decode itunes DISK atom"
  117. (declare (ignore atom))
  118. (stream-read-u16 mp4-file) ; throw away
  119. (let ((a) (b))
  120. (setf a (stream-read-u16 mp4-file))
  121. (setf b (stream-read-u16 mp4-file))
  122. (list a b)))
  123. (defmethod decode-ilst-data-atom ((type (eql +itunes-ilst-data+)) atom (atom-parent-type (eql +itunes-track+)) mp4-file)
  124. "decode itunes TRK atom"
  125. (declare (ignore atom))
  126. (stream-read-u16 mp4-file) ; throw away
  127. (let ((a) (b))
  128. (setf a (stream-read-u16 mp4-file))
  129. (setf b (stream-read-u16 mp4-file))
  130. (stream-read-u16 mp4-file) ; throw away
  131. (list a b)))
  132. (defmethod decode-ilst-data-atom ((type (eql +itunes-ilst-data+)) atom (atom-parent-type (eql +itunes-track-n+)) mp4-file)
  133. "decode itunes TRKN atom"
  134. (declare (ignore atom))
  135. (stream-read-u16 mp4-file) ; throw away
  136. (let ((a) (b))
  137. (setf a (stream-read-u16 mp4-file))
  138. (setf b (stream-read-u16 mp4-file))
  139. (stream-read-u16 mp4-file) ; throw away
  140. (list a b)))
  141. (defmethod decode-ilst-data-atom ((type (eql +itunes-ilst-data+)) atom (atom-parent-type (eql +itunes-tempo+)) mp4-file)
  142. "decode itunes TMPO atom"
  143. (declare (ignore atom))
  144. (stream-read-u16 mp4-file))
  145. (defmethod decode-ilst-data-atom ((type (eql +itunes-ilst-data+)) atom (atom-parent-type (eql +itunes-genre+)) mp4-file)
  146. "decode itunes GNRE atom"
  147. (declare (ignore atom))
  148. (stream-read-u16 mp4-file))
  149. (defmethod decode-ilst-data-atom ((type (eql +itunes-ilst-data+)) atom (atom-parent-type (eql +itunes-compilation+)) mp4-file)
  150. "decode itunes CPIL atom"
  151. (declare (ignore atom))
  152. (stream-read-u8 mp4-file))
  153. (defmethod decode-ilst-data-atom ((type (eql +itunes-ilst-data+)) atom (atom-parent-type (eql +itunes-cover-art+)) mp4-file)
  154. (let ((blob (make-instance 'mp4-unhandled-data)))
  155. (setf (slot-value blob 'blob) (stream-read-sequence mp4-file (- (atom-size atom) 16)))
  156. blob))
  157. (defclass mp4-atom ()
  158. ((atom-file-position :accessor atom-file-position :initarg :atom-file-position)
  159. (atom-size :accessor atom-size :initarg :atom-size)
  160. (atom-type :accessor atom-type :initarg :atom-type)
  161. (atom-children :accessor atom-children :initform (make-mp4-atom-collection)))
  162. (:documentation "The minimal mp4-atom. Note: not all atoms have children, but we put them here anyway to make things 'simple'"))
  163. (defclass mp4-ilst-atom (mp4-atom)
  164. ())
  165. (defmethod initialize-instance :after ((me mp4-ilst-atom) &key (mp4-file nil) &allow-other-keys)
  166. "Construct an ilst atom"
  167. (log5:with-context "mp4-ilst-atom-initializer"
  168. (assert (not (null mp4-file)) () "Must pass a stream into this method")
  169. (with-slots (atom-size atom-type atom-children) me
  170. (let* ((start (stream-seek mp4-file 0 :current))
  171. (end (+ start (- atom-size 8))))
  172. (log-mp4-atom "mp4-ilst-atom-initializer:entry, start = ~:d, end = ~:d" start end)
  173. (do* ()
  174. ((>= (stream-seek mp4-file 0 :current) end))
  175. (log-mp4-atom "ilst atom top of loop: start = ~:d, current = ~:d, end = ~:d"
  176. start (stream-seek mp4-file 0 :current) end)
  177. (let ((child (make-mp4-atom mp4-file atom-type)))
  178. (log-mp4-atom "adding new child ~a" (vpprint child nil))
  179. (add atom-children child)))))
  180. (log-mp4-atom "Returning ilst atom: ~a" (vpprint me nil))))
  181. (defclass mp4-ilst-generic-data-atom (mp4-atom)
  182. ((atom-version :accessor atom-version :initarg :atom-version)
  183. (atom-flags :accessor atom-flags :initarg :atom-flags)
  184. (atom-value :accessor atom-value :initarg :atom-value)
  185. (atom-parent-type :accessor atom-parent-type :initarg :atom-parent-type :initform nil))
  186. (:documentation "Represents the 'data' portion of ilst data atom"))
  187. (defmethod initialize-instance :after ((me mp4-ilst-generic-data-atom) &key mp4-file &allow-other-keys)
  188. (log5:with-context "mp4-ilst-generic-data-atom-initializer"
  189. (assert (not (null mp4-file)) () "Must pass a stream into this method")
  190. (log-mp4-atom "mp4-ilst-generic-data-atom-initializer:entry")
  191. (with-slots (atom-size atom-type atom-version atom-flags atom-value atom-parent-type) me
  192. (setf atom-version (stream-read-u8 mp4-file))
  193. (setf atom-flags (stream-read-u24 mp4-file))
  194. (if (= atom-type +itunes-ilst-data+)
  195. (assert (= 0 (stream-read-u32 mp4-file)) () "a data atom lacks the required null field"))
  196. (log-mp4-atom "size = ~:d, name = ~a, version = ~d, flags = ~x"
  197. atom-size (as-string atom-type) atom-version atom-flags)
  198. (setf atom-value (decode-ilst-data-atom atom-type me atom-parent-type mp4-file))
  199. (log-mp4-atom "generic atom: ~a" (vpprint me nil)))))
  200. (defclass mp4-container-atom (mp4-atom)
  201. ()
  202. (:documentation "The class representing an mp4 container atom"))
  203. (defmethod initialize-instance :after ((me mp4-container-atom) &key (mp4-file nil) &allow-other-keys)
  204. "Upon initializing a container mp4 atom, read the nested atoms within it.'"
  205. (log5:with-context "mp4-container-atom-initializer"
  206. (assert (not (null mp4-file)) () "Must pass a stream into this method")
  207. (log-mp4-atom "mp4-container-atom-initializer")
  208. (with-slots (atom-children atom-file-position atom-of-interest atom-size atom-type atom-decoded) me
  209. (log-mp4-atom "entry: starting file position = ~:d, atom ~a" atom-file-position (vpprint me nil))
  210. (log-mp4-atom "type ~a is container atom of interest; read the nested atoms" (as-string atom-type))
  211. (cond ((= atom-type +mp4-atom-meta+)
  212. (log-mp4-atom "got META, moving file position forward 4 bytes") ;null field
  213. (stream-seek mp4-file 4 :current)))
  214. ;; we are now at the file-position we are need to be at, so start reading those atoms!
  215. (block stream-read-file
  216. (log-mp4-atom "starting stream-read-file block with file-position = ~:d and end = ~:d" atom-file-position (+ atom-file-position atom-size))
  217. (do ()
  218. ((>= (stream-seek mp4-file 0 :current) (+ atom-file-position atom-size)))
  219. (log-mp4-atom "Top of loop: currently at file-position ~:d (reading up to ~:d)" (stream-seek mp4-file 0 :current) (+ atom-file-position atom-size))
  220. (let ((child (make-mp4-atom mp4-file)))
  221. (log-mp4-atom "adding new child ~a" (vpprint child nil))
  222. (add atom-children child))))
  223. (log-mp4-atom "ended stream-read-file block, file position now ~:d" (stream-seek mp4-file 0 :current)))))
  224. (defun make-mp4-atom (mp4-file &optional atom-parent-type)
  225. "Get current file position, read in size/type, then construct the correct atom.
  226. If type is an ilst type, read it all it. If it is a container atom of interest,
  227. leave file position as is, since caller will want to read in nested atoms. Otherwise,
  228. seek forward past end of this atom."
  229. (log5:with-context "make-mp4-atom"
  230. (let* ((pos (stream-seek mp4-file 0 :current))
  231. (siz (stream-read-u32 mp4-file))
  232. (typ (stream-read-u32 mp4-file))
  233. (atom))
  234. (declare (type integer pos siz typ))
  235. (when (= 0 siz)
  236. (warn "trying to make an atom ~a with size of 0 at offset ~:d in ~a, ammending size to be 8" (as-string typ) pos (stream-filename mp4-file))
  237. (setf siz 8))
  238. (log-mp4-atom "pos = ~:d, size = ~:d, type = ~a" pos siz (as-string typ))
  239. (cond ((member typ *atoms-of-interest*)
  240. (log-mp4-atom "~a is a container atom we are interested in" (as-string typ))
  241. (setf atom (make-instance 'mp4-container-atom :atom-size siz :atom-type typ :atom-file-position pos :mp4-file mp4-file)))
  242. ((member typ *itunes-atom-types*)
  243. (log-mp4-atom "~a is an ilst atom, read it all in" (as-string typ))
  244. (setf atom (make-instance 'mp4-ilst-atom :atom-size siz :atom-type typ :atom-file-position pos :mp4-file mp4-file)))
  245. ((= typ +itunes-ilst-data+)
  246. (log-mp4-atom "~a is an ilst data atom, read it all in" (as-string typ))
  247. (setf atom (make-instance 'mp4-ilst-generic-data-atom :atom-parent-type atom-parent-type :atom-size siz :atom-type typ :atom-file-position pos :mp4-file mp4-file)))
  248. (t
  249. (log-mp4-atom "~a is an atom we are NOT interested in; seek past it" (as-string typ))
  250. (setf atom (make-instance 'mp4-atom :atom-size siz :atom-type typ :atom-file-position pos))
  251. (stream-seek mp4-file (- siz 8) :current)))
  252. (log-mp4-atom "returning ~a" (vpprint atom nil))
  253. atom)))
  254. (defparameter *pprint-mp4-atom* nil
  255. "Controls whether we pretty print an atom")
  256. (defmethod print-object ((me mp4-atom) stream)
  257. (if (null *pprint-mp4-atom*)
  258. (call-next-method)
  259. ;; else
  260. (format stream "~a" (with-output-to-string (s)
  261. (with-slots (atom-children atom-file-position atom-size atom-type) me
  262. (format s "Atom <~a> @ ~:d of size ~:d and child count of ~d"
  263. (as-string atom-type) atom-file-position atom-size (size atom-children)))
  264. (if (typep me 'mp4-ilst-generic-data-atom)
  265. (with-slots (atom-version atom-flags atom-value atom-type atom-parent-type) me
  266. (format s " having ilst fields: atom-parent-type = ~a, verison = ~d, flags = ~x, data = ~x"
  267. (as-string atom-parent-type) atom-version atom-flags atom-value)))))))
  268. (defmethod vpprint ((me mp4-atom) stream &key (indent 0))
  269. "set *pprint-mp4-atom* to get pretty printing and call print-object via format"
  270. (let ((*pprint-mp4-atom* t))
  271. (format stream "~vt~a" (* indent 1) me)))
  272. (defclass mp4-unhandled-data ()
  273. ((blob :accessor blob :initarg :blob :initform nil))
  274. (:documentation "abstraction for a 'blob' of data we don't want to or can't parse"))
  275. (defparameter *pprint-max-array-len* 10
  276. "Controls how long an array (atom-value, typically) we will print in pprint-atom")
  277. (defmethod print-object ((me mp4-unhandled-data) stream)
  278. "Print a 'blob' (unstructered data), limiting it to no more than *PPRINT-MAX-ARRAY-LEN* octets"
  279. (let* ((len (length (slot-value me 'blob)))
  280. (print-len (min len *pprint-max-array-len*))
  281. (printable-array (make-array print-len :displaced-to (slot-value me 'blob))))
  282. (format stream "[~:d of ~:d bytes] <~x>" print-len len printable-array)))
  283. ;;;;;;;;;;;;;;;;;;;; A collection of atoms (siblings) ;;;;;;;;;;;;;;;;;;;;
  284. (defclass atom-collection ()
  285. ((atoms :accessor atoms :initform nil))
  286. (:documentation "A collection of sibling atoms"))
  287. (defun make-mp4-atom-collection () (make-instance 'atom-collection))
  288. (defmethod add ((me atom-collection) new-atom)
  289. "Adds new atom to the *end* (need to keep them in order we found them in the file) of this collection"
  290. (log5:with-context "add-atom-collection"
  291. (with-slots (atoms) me
  292. (log-mp4-atom "adding ~a to atom collection: ~a" new-atom atoms)
  293. (setf atoms (append atoms (list new-atom)))
  294. (log-mp4-atom "collection now: ~a" atoms))))
  295. (defmethod size ((me atom-collection))
  296. "Returns the number of atoms in this collection"
  297. (length (slot-value me 'atoms)))
  298. (defmethod map-mp4-atom ((me atom-collection) &key (func nil) (depth nil))
  299. "Given a collection of atoms, call map-mp4-atom for each one"
  300. (log5:with-context "map-mp4-atom(collection)"
  301. (log-mp4-atom "mapping collection: ~a" (slot-value me 'atoms))
  302. (dolist (a (slot-value me 'atoms))
  303. (map-mp4-atom a :func func :depth depth))))
  304. (defun is-valid-m4-file (mp4-file)
  305. "Make sure this is an MP4 file. Quick check: is first atom (at file-offset 4) == FSTYP?"
  306. (stream-seek mp4-file 0 :start)
  307. (let* ((size (stream-read-u32 mp4-file))
  308. (header (stream-read-u32 mp4-file)))
  309. (declare (ignore size))
  310. (stream-seek mp4-file 0 :start)
  311. (= header +m4-ftyp+)))
  312. (defun find-mp4-atoms (mp4-file)
  313. "Given a valid MP4 file mp4-file, look for the 'right' atoms and return them.
  314. The 'right' atoms are those in *atoms-of-interest*"
  315. (log5:with-context "find-mp4-atoms"
  316. (when (not (is-valid-m4-file mp4-file))
  317. (error 'mp4-atom-condition :location "find-mp4-atoms" :object mp4-file :message "is not an mp4-file" ))
  318. (log-mp4-atom "before read-file loop, file-position = ~:d, end = ~:d" (stream-seek mp4-file 0 :current) (stream-size mp4-file))
  319. (setf (mp4-atoms mp4-file) (make-mp4-atom-collection))
  320. (do ((new-atom))
  321. ((> (+ 8 (stream-seek mp4-file 0 :current)) (stream-size mp4-file)))
  322. (log-mp4-atom "top of read-file loop, current file-position = ~:d, end = ~:d" (stream-seek mp4-file 0 :current) (stream-size mp4-file))
  323. (setf new-atom (make-mp4-atom mp4-file))
  324. (when new-atom (add (mp4-atoms mp4-file) new-atom)))
  325. (log-mp4-atom "returning atom-collection of size ~d" (size (mp4-atoms mp4-file)))))
  326. (defmethod map-mp4-atom ((me mp4-atom) &key (func nil) (depth nil))
  327. "traverse all atoms under a given atom"
  328. (log5:with-context "map-mp4-atom(single)"
  329. (labels ((_indented-atom (atom depth)
  330. (format t "~a~%" (vpprint atom nil :indent (if (null depth) 0 depth)))))
  331. (with-slots (atom-type atom-children) me
  332. (log-mp4-atom "Begining traversal with ~a, I have ~d children" (as-string atom-type) (size atom-children))
  333. (when (null func)
  334. (setf func #'_indented-atom))
  335. (funcall func me depth)
  336. (map-mp4-atom atom-children :func func :depth (if (null depth) nil (+ 1 depth)))))))
  337. (defmethod traverse ((me mp4-atom) path)
  338. "Used in finding nested atoms.
  339. Given an atom and a path, if atom-type matches first element of path, then we've found our match."
  340. (log5:with-context "traverse-atom"
  341. (log-mp4-atom "traverse-atom entered with ~a ~a" (atom-type me) path)
  342. (cond ((null path)
  343. (error "Path exhausted in travese atom") ; don't think this can happen?
  344. nil)
  345. ((= (atom-type me) (first path))
  346. (log-mp4-atom "current path matches thus far ~a ~a" (atom-type me) path)
  347. (cond
  348. ((= 1 (length path))
  349. (log-mp4-atom "length of path is 1, so found!")
  350. (return-from traverse me)))))
  351. (log-mp4-atom "Current path doesn't match ~a ~a" (atom-type me) path)
  352. nil))
  353. (defmethod traverse ((me atom-collection) path)
  354. "Used in finding nested atoms. Seach the collection and if we find a match with first of path,
  355. call traverse atom (unless length of path == 1, in which case, we've found out match)"
  356. (log5:with-context "traverse-atom-collection"
  357. (log-mp4-atom "entering with ~a ~a" me path)
  358. (dolist (sibling (atoms me)) ; cleaner than using map-mp4-atom, but still a kludge
  359. (with-slots (atom-type atom-children) sibling
  360. (log-mp4-atom "looking at ~x::~x" atom-type (first path))
  361. (when (= atom-type (first path))
  362. (cond
  363. ((= 1 (length path))
  364. (log-mp4-atom "Found ~a" sibling)
  365. (return-from traverse sibling))
  366. (t
  367. (log-mp4-atom "path matches, calling traverse atom with ~a, ~a" atom-children (rest path))
  368. (let ((found (traverse atom-children (rest path))))
  369. (if found (return-from traverse found))))))))
  370. (log-mp4-atom "Looked at all, found nothing")
  371. nil))
  372. (defmethod tag-get-value (atoms node)
  373. "Helper function to extract text from atom's data atom"
  374. (let ((atom (traverse atoms
  375. (list +mp4-atom-moov+ +mp4-atom-udta+ +mp4-atom-meta+ +mp4-atom-ilst+ node +itunes-ilst-data+))))
  376. (if atom
  377. (atom-value atom)
  378. nil)))
  379. (defun mp4-show-raw-tag-atoms (mp4-file-stream)
  380. (map-mp4-atom (mp4-atom::traverse (mp4-atoms mp4-file-stream)
  381. (list +mp4-atom-moov+ +mp4-atom-udta+ +mp4-atom-meta+ +mp4-atom-ilst+))
  382. :depth 0
  383. :func (lambda (atom depth)
  384. (when (= (atom-type atom) +itunes-ilst-data+)
  385. (format t "~4t~a~%" (vpprint atom nil :indent (if (null depth) 0 depth)))))))