ソースを参照

Massive rewrite.

New logic is similar to id3-frame.lisp in that it uses find-class to instantiate atoms.

There are some potential problems with this: first, the ILST DATA atoms are a potential
problem area in that there are generically named.  For now, I'm not reading any container
atoms that have DATA atoms in them (other than ILST), but ...

The second potential problem is that I'm ignoring case when doing find-symbol and
some atoms (eg "aArt") are case-sensitive.
Mark VandenBrink 12 年 前
コミット
a38815524d
1 ファイル変更399 行追加290 行削除
  1. 399 290
      mp4-atom.lisp

+ 399 - 290
mp4-atom.lisp

@@ -16,7 +16,28 @@
 (defmethod print-object ((me mp4-atom-condition) stream)
   (format stream "location: <~a>, object: <~a>, message: <~a>" (location me) (object me) (message me)))
 
-(defmethod  as-string ((atom-type integer))
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ATOMS ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;;;
+;;; A word about atoms (aka "boxes").  There are three kinds of atoms: ones that are containers, ones
+;;; that are data, and ones that are both. A lot of the source code for taggers out there mostly ignore
+;;; the third class and treat "container atoms that also have data" as a big blob of data that they
+;;; rummage around in via indices.  Seems sort of broke, IMHO, so we'll try to handle all three if
+;;; at all possible.
+;;;
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+(defun as-int (str)
+  "Given a 4-byte string, return an integer type equivalent.
+(eg (as-int \"hdlr\" == +audioprop-hdlr+))"
+  (let ((int 0))
+	(declare (integer int))
+	(setf (ldb (byte 8 24) int) (char-code (aref str 0)))
+	(setf (ldb (byte 8 16) int) (char-code (aref str 1)))
+	(setf (ldb (byte 8 8) int)  (char-code (aref str 2)))
+	(setf (ldb (byte 8 0) int)  (char-code (aref str 3)))
+	int))
+
+(defmethod as-string ((atom-type integer))
   "Given an integer representing an atom type, return the string form"
   (with-output-to-string (s nil)
 	(write-char (code-char (ldb (byte 8 24) atom-type)) s)
@@ -38,305 +59,369 @@
 	   (setf (ldb (byte 8 16) retval) ,(as-octet l2))
 	   (setf (ldb (byte 8 8) retval)  ,(as-octet l3))
 	   (setf (ldb (byte 8 0) retval)  ,(as-octet l4))
-	   retval))
-
-  (defconstant +m4-ftyp+             (mk-mp4-atom-type #\f #\t #\y #\p)
-	"This should be the first atom type found in file")
-
-  (defconstant +itunes-ilst-data+      (mk-mp4-atom-type #\d #\a #\t #\a)
-	"Carries the actual data under an ilst atom")
-
-  (defconstant +itunes-lyrics+         (mk-mp4-atom-type #xa9 #\l #\y #\r)) ; text
-  (defconstant +itunes-copyright+	   (mk-mp4-atom-type #\c  #\p #\r #\t)) ; text
-  (defconstant +itunes-album+          (mk-mp4-atom-type #xa9 #\a #\l #\b)) ; text
-  (defconstant +itunes-artist+         (mk-mp4-atom-type #xa9 #\A #\R #\T)) ; text
-  (defconstant +itunes-comment+        (mk-mp4-atom-type #xa9 #\c #\m #\t)) ; text
-  (defconstant +itunes-compilation+    (mk-mp4-atom-type #\c  #\p #\i #\l)) ; byte/boolean
-  (defconstant +itunes-composer+       (mk-mp4-atom-type #xa9 #\c #\o #\m)) ; text
-  (defconstant +itunes-cover-art+      (mk-mp4-atom-type #\c  #\o #\v #\r)) ; octets
-  (defconstant +itunes-year+           (mk-mp4-atom-type #xa9 #\d #\a #\y)) ; text
-  (defconstant +itunes-disk+           (mk-mp4-atom-type #\d  #\i #\s #\k)) ; octets
-  (defconstant +itunes-tool+           (mk-mp4-atom-type #xa9 #\t #\o #\o)) ; text
-  (defconstant +itunes-genre+		   (mk-mp4-atom-type #\g  #\n #\r #\e)) ; octet
-  (defconstant +itunes-genre-x+		   (mk-mp4-atom-type #xa9 #\n #\r #\e)) ; text
-  (defconstant +itunes-groups+         (mk-mp4-atom-type #xa9 #\g #\r #\p)) ; text
-  (defconstant +itunes-title+          (mk-mp4-atom-type #xa9 #\n #\a #\m)) ; text
-  (defconstant +itunes-tempo+          (mk-mp4-atom-type #\t  #\m #\p #\o)) ; octet
-  (defconstant +itunes-track+          (mk-mp4-atom-type #xa9 #\t #\r #\k)) ; octet
-  (defconstant +itunes-track-n+        (mk-mp4-atom-type #\t  #\r #\k #\n)) ; octet
-  (defconstant +itunes-writer+         (mk-mp4-atom-type #xa9 #\w #\r #\t)) ; text
-  (defconstant +itunes-encoder+        (mk-mp4-atom-type #xa9 #\e #\n #\c)) ; text
-  (defconstant +itunes-album-artist+   (mk-mp4-atom-type #\a  #\A #\R #\T)) ; text
-  (defconstant +itunes-purchased-date+ (mk-mp4-atom-type #\p  #\u #\r #\d)) ; text
-
-  (defparameter *itunes-text-atom-types*
-	(list
-	 +itunes-album+
-	 +itunes-album-artist+
-	 +itunes-artist+
-	 +itunes-comment+
-	 +itunes-composer+
-	 +itunes-copyright+
-	 +itunes-year+
-	 +itunes-encoder+
-	 +itunes-groups+
-	 +itunes-genre-x+
-	 +itunes-lyrics+
-	 +itunes-purchased-date+
-	 +itunes-title+
-	 +itunes-tool+
-	 +itunes-writer+)
-	"These are all the itunes atoms that are stored as text")
-
-  (defparameter *itunes-atom-types*
-	(append *itunes-text-atom-types*
-			(list
-			 +itunes-compilation+
-			 +itunes-cover-art+
-			 +itunes-disk+
-			 +itunes-genre+
-			 +itunes-tempo+
-			 +itunes-track+
-			 +itunes-track-n+))
-	"The iTunes atom types we can decode")
-
-  (defconstant +mp4-atom-moov+ (mk-mp4-atom-type #\m #\o #\o #\v))
-  (defconstant +mp4-atom-udta+ (mk-mp4-atom-type #\u #\d #\t #\a))
-  (defconstant +mp4-atom-mdia+ (mk-mp4-atom-type #\m #\d #\i #\a))
-  (defconstant +mp4-atom-meta+ (mk-mp4-atom-type #\m #\e #\t #\a))
-  (defconstant +mp4-atom-ilst+ (mk-mp4-atom-type #\i #\l #\s #\t))
-
-  (defparameter *atoms-of-interest*
-	(list +mp4-atom-moov+
-		  +mp4-atom-udta+
-		  +mp4-atom-mdia+
-		  +mp4-atom-meta+
-		  +mp4-atom-ilst+)
-	"For these container atoms, we look inside these atoms to read nested atoms")
-
-  (defparameter *tag-path* (list +mp4-atom-moov+ +mp4-atom-udta+ +mp4-atom-meta+ +mp4-atom-ilst+)
-	"The 'path' of nested atoms at which tag data can be found.")
-
-  (defgeneric decode-ilst-data-atom (type atom atom-parent-type mp4-file))
-
-  (defmacro generate-generic-text-methods ()
-	"generate the decode methods for text atoms"
-	(let ((methods))
-	  (dolist (type *itunes-text-atom-types*)
-		(push `(defmethod decode-ilst-data-atom ((type (eql +itunes-ilst-data+)) atom (atom-parent-type (eql ,type)) mp4-file)
-				 (stream-read-string-with-len mp4-file (- (atom-size atom) 16))) methods))
-	  `(progn ,@methods)))
-  )
+	   retval)))
+
+(defconstant +itunes-album+          (mk-mp4-atom-type #xa9 #\a #\l #\b) "text: album name")
+(defconstant +itunes-album-artist+   (mk-mp4-atom-type #\a  #\A #\R #\T) "text: album artist")
+(defconstant +itunes-artist+         (mk-mp4-atom-type #xa9 #\A #\R #\T) "text: artist name")
+(defconstant +itunes-comment+        (mk-mp4-atom-type #xa9 #\c #\m #\t) "text: comment, commonly used by iTunes for sound info, etc")
+(defconstant +itunes-compilation+    (mk-mp4-atom-type #\c  #\p #\i #\l) "byte/boolean: is this file part of a compilation?")
+(defconstant +itunes-composer+       (mk-mp4-atom-type #xa9 #\c #\o #\m) "text: composer name")
+(defconstant +itunes-copyright+	     (mk-mp4-atom-type #\c  #\p #\r #\t) "text: copyright info")
+(defconstant +itunes-cover-art+      (mk-mp4-atom-type #\c  #\o #\v #\r) "octets: cover art, PNG, etc")
+(defconstant +itunes-disk+           (mk-mp4-atom-type #\d  #\i #\s #\k) "octets: disk number, can be n of N")
+(defconstant +itunes-encoder+        (mk-mp4-atom-type #xa9 #\e #\n #\c) "text: who encoded")
+(defconstant +itunes-genre+		     (mk-mp4-atom-type #\g  #\n #\r #\e) "octets: genre of file")
+(defconstant +itunes-genre-x+		 (mk-mp4-atom-type #xa9 #\n #\r #\e) "text: yet another genre atom")
+(defconstant +itunes-groups+         (mk-mp4-atom-type #xa9 #\g #\r #\p) "text: ???")
+(defconstant +itunes-lyrics+         (mk-mp4-atom-type #xa9 #\l #\y #\r) "text: lyrics tag")
+(defconstant +itunes-purchased-date+ (mk-mp4-atom-type #\p  #\u #\r #\d) "text: when song was purchased")
+(defconstant +itunes-tempo+          (mk-mp4-atom-type #\t  #\m #\p #\o) "octet: tempo of song")
+(defconstant +itunes-title+          (mk-mp4-atom-type #xa9 #\n #\a #\m) "text: title of song")
+(defconstant +itunes-tool+           (mk-mp4-atom-type #xa9 #\t #\o #\o) "text: what tool encoded this file")
+(defconstant +itunes-track+          (mk-mp4-atom-type #xa9 #\t #\r #\k) "octet: track number")
+(defconstant +itunes-track-n+        (mk-mp4-atom-type #\t  #\r #\k #\n) "octet: yet another track number")
+(defconstant +itunes-writer+         (mk-mp4-atom-type #xa9 #\w #\r #\t) "text: who wrote the song")
+(defconstant +itunes-year+           (mk-mp4-atom-type #xa9 #\d #\a #\y) "text: year album was released")
+(defconstant +itunes-ilst-data+      (mk-mp4-atom-type #\d #\a #\t #\a) "Carries the actual data under an ilst atom")
+
+(defconstant +m4-ftyp+               (mk-mp4-atom-type #\f #\t #\y #\p) "This should be the first atom type found in file")
+
+(defconstant +audioprop-hdlr+        (mk-mp4-atom-type #\h #\d #\l #\r) "Found under trak.mdia and tells what kind of handler it is")
+(defconstant +audioprop-mdhd+        (mk-mp4-atom-type #\m #\d #\h #\d) "Found under trak.mdia and holds data to calculate length of audio")
+(defconstant +audioprop-stsd+        (mk-mp4-atom-type #\s #\t #\s #\d) "Container atom: found under trak.mdia.minf.stbl and holds bit-rate, etc")
+(defconstant +audioprop-mp4a+        (mk-mp4-atom-type #\m #\p #\4 #\a) "Found under trak.mdia.minf.stbl.stsd")
+(defconstant +audioprop-essd+        (mk-mp4-atom-type #\e #\s #\s #\d) "Found under trak.mdia.minf.stbl.stsd.mp4a")
+
+(defconstant +mp4-atom-ilst+         (mk-mp4-atom-type #\i #\l #\s #\t))
+(defconstant +mp4-atom-mdia+         (mk-mp4-atom-type #\m #\d #\i #\a))
+(defconstant +mp4-atom-meta+         (mk-mp4-atom-type #\m #\e #\t #\a))
+(defconstant +mp4-atom-minf+         (mk-mp4-atom-type #\m #\i #\n #\f))
+(defconstant +mp4-atom-moov+         (mk-mp4-atom-type #\m #\o #\o #\v))
+(defconstant +mp4-atom-stbl+         (mk-mp4-atom-type #\s #\t #\b #\l))
+(defconstant +mp4-atom-trak+         (mk-mp4-atom-type #\t #\r #\a #\k))
+(defconstant +mp4-atom-udta+         (mk-mp4-atom-type #\u #\d #\t #\a))
+
+(defun atom-read-loop (mp4-file end func)
+  "Loop from start to end through a file and call FUNC for every ATOM we find. Used
+at top-level and also for container ATOMs that need to read their contents."
+  (log5:with-context "atom-read-loop"
+	(do ()
+		((>= (stream-seek mp4-file 0 :current) end))
+	  (log-mp4-atom "atom-read-loop: @~:d before dispatch" (stream-seek mp4-file 0 :current))
+	  (funcall func)
+	  (log-mp4-atom "atom-read-loop: @~:d after dispatch" (stream-seek mp4-file 0 :current)))))
 
-(generate-generic-text-methods)
+(defclass mp4-atom ()
+  ((atom-file-position :accessor atom-file-position :initarg :atom-file-position)
+   (atom-size :accessor atom-size :initarg :atom-size)
+   (atom-type :accessor atom-type :initarg :atom-type)
+   (atom-children :accessor atom-children :initform (make-mp4-atom-collection)))
+  (:documentation "The minimal mp4-atom.  Note: not all atoms have children, but we put them here anyway to make things 'simple'"))
 
-(defmethod decode-ilst-data-atom ((type (eql +itunes-ilst-data+)) atom (atom-parent-type (eql +itunes-disk+)) mp4-file)
-  "decode itunes DISK atom"
-  (declare (ignore atom))
-  (stream-read-u16 mp4-file)					; throw away
-  (let ((a) (b))
-	(setf a (stream-read-u16 mp4-file))
-	(setf b (stream-read-u16 mp4-file))
-	(list a b)))
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; A collection of atoms (siblings) ;;;;;;;;;;;;;;;;;;;;
+(defclass atom-collection ()
+  ((atoms :accessor atoms :initform nil))
+  (:documentation "A collection of sibling atoms"))
 
-(defmethod decode-ilst-data-atom ((type (eql +itunes-ilst-data+)) atom (atom-parent-type (eql +itunes-track+)) mp4-file)
-  "decode itunes TRK atom"
-  (declare (ignore atom))
-  (stream-read-u16 mp4-file)					; throw away
-  (let ((a) (b))
-	(setf a (stream-read-u16 mp4-file))
-	(setf b (stream-read-u16 mp4-file))
-	(stream-read-u16 mp4-file)					; throw away
-	(list a b)))
+(defun make-mp4-atom-collection () (make-instance 'atom-collection))
 
-(defmethod decode-ilst-data-atom ((type (eql +itunes-ilst-data+)) atom (atom-parent-type (eql +itunes-track-n+)) mp4-file)
-  "decode itunes TRKN atom"
+(defmethod add ((me atom-collection) new-atom)
+  "Adds new atom to the *end* (need to keep them in order we found them in the file) of this collection"
+  (log5:with-context "add-atom-collection"
+	(with-slots (atoms) me
+	  ;(log-mp4-atom "adding ~a to atom collection: ~a" new-atom atoms)
+	  (setf atoms (append atoms (list new-atom)))
+	  ;(log-mp4-atom "collection now: ~a" atoms)
+	  )))
+
+(defmethod size ((me atom-collection))
+  "Returns the number of atoms in this collection"
+  (length (slot-value me 'atoms)))
+
+(defmethod map-mp4-atom ((me atom-collection) &key (func nil) (depth nil))
+  "Given a collection of atoms, call map-mp4-atom for each one"
+  (log5:with-context "map-mp4-atom(collection)"
+	(log-mp4-atom "map-mp4-atom: mapping collection: ~a" (slot-value me 'atoms))
+	(dolist (a (slot-value me 'atoms))
+	  (map-mp4-atom a :func func :depth depth))))
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; Concreate atoms ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+(defclass atom-skip (mp4-atom) ())
+(defmethod initialize-instance :after ((me atom-skip) &key (mp4-file nil) &allow-other-keys)
+  "The 'skip' atom.  Used when we want to capture the header of atom, but don't want/need
+to read the payload of an atom."
+  (with-slots (atom-size atom-type) me
+	(stream-seek mp4-file (- atom-size 8) :current)))
+
+(defclass atom-raw-mixin ()
+  ((raw-data :accessor raw-data)))
+(defmethod initialize-instance :after ((me atom-raw-mixin) &key (mp4-file nil) &allow-other-keys)
+  "The 'don't need to know contents, but want 'blob' of data read in' atom"
+  (log5:with-context "atom-raw-mixin"
+	(with-slots (raw-data atom-type atom-size) me
+	  (log-mp4-atom "atom-raw-mixin: reading in ~d raw bytes for ~a" (- atom-size 8) (vpprint me nil))
+	  (setf raw-data (stream-read-sequence mp4-file (- atom-size 8)))
+	  ;;(utils::dump-data "/tmp/o.txt" raw-data)
+	  )))
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ILST ATOMS ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+(defclass atom-ilst (mp4-atom) ())
+(defmethod initialize-instance :after ((me atom-ilst) &key (mp4-file nil) &allow-other-keys)
+  "Construct an ilst atom.  ILST atoms are containers that hold data elements related to tagging.
+Loop through this container and construct constituent atoms"
+  (log5:with-context "atom-ilst-initializer"
+	(with-slots (atom-size atom-type atom-children) me
+	  (log-mp4-atom "atom-ilst-init: found ilst atom <~a> @ ~:d, looping for ~:d bytes"
+					(as-string atom-type) (stream-seek mp4-file 0 :current) (- atom-size 8))
+	  (atom-read-loop mp4-file (+ (stream-seek mp4-file 0 :current)  (- atom-size 8))
+					  (lambda ()
+						(let ((child (make-mp4-atom mp4-file atom-type)))
+						  ;(log-mp4-atom "adding new child ~a" (vpprint child nil))
+						  (add atom-children child))))))
+  ;(log-mp4-atom "returning ilst atom: ~a" (vpprint me nil))
+  )
+
+(defclass atom-©alb (atom-ilst) ())
+(defclass atom-aART (atom-ilst) ())
+(defclass atom-©art (atom-ilst) ())
+(defclass atom-©cmt (atom-ilst) ())
+(defclass atom-cpil (atom-ilst) ())
+(defclass atom-©com (atom-ilst) ())
+(defclass atom-cprt (atom-ilst) ())
+(defclass atom-covr (atom-ilst) ())
+(defclass atom-disk (atom-ilst) ())
+(defclass atom-©enc (atom-ilst) ())
+(defclass atom-gnre (atom-ilst) ())
+(defclass atom-©nre (atom-ilst) ())
+(defclass atom-©grp (atom-ilst) ())
+(defclass atom-©lyr (atom-ilst) ())
+(defclass atom-purd (atom-ilst) ())
+(defclass atom-tmpo (atom-ilst) ())
+(defclass atom-©nam (atom-ilst) ())
+(defclass atom-©too (atom-ilst) ())
+(defclass atom-©trk (atom-ilst) ())
+(defclass atom-trkn (atom-ilst) ())
+(defclass atom-©wrt (atom-ilst) ())
+(defclass atom-©day (atom-ilst) ())
+
+(defclass atom-data (mp4-atom)
+   ((atom-version      :accessor atom-version     :initarg :atom-version)
+	(atom-flags        :accessor atom-flags       :initarg :atom-flags)
+	(atom-value        :accessor atom-value       :initarg :atom-value)
+	(atom-parent-type  :accessor atom-parent-type :initarg :atom-parent-type :initform nil))
+   (:documentation "Represents the 'data' portion of ilst data atom"))
+
+ (defmethod initialize-instance :after ((me atom-data) &key mp4-file &allow-other-keys)
+   (log5:with-context "atom-data-init"
+	 (with-slots (atom-size atom-type atom-version atom-flags atom-value atom-parent-type) me
+	   (setf atom-version (stream-read-u8 mp4-file))
+	   (setf atom-flags (stream-read-u24 mp4-file))
+	   (assert (= 0 (stream-read-u32 mp4-file)) () "a data atom lacks the required null field") ; XXX is this true?
+	   (log-mp4-atom "atom-data-init: size = ~:d, name = ~a, version = ~d, flags = ~x" atom-size (as-string atom-type) atom-version atom-flags)
+	   (setf atom-value (decode-ilst-data-atom atom-type me atom-parent-type mp4-file))
+	   (log-mp4-atom "atom-data-init: made an ilst atom-data: ~a" (vpprint me nil)))))
+
+;;; the ILST atom decoders.  First, a lot of the decoders do the same thing, so we define a macros
+;;; and use those for the relevants atoms.
+(defgeneric decode-ilst-data-atom (type atom atom-parent-type mp4-file))
+
+(defmacro simple-text-decode (type)
+  `(defmethod decode-ilst-data-atom ((type (eql +itunes-ilst-data+)) atom (atom-parent-type (eql ,type)) mp4-file)
+	 (stream-read-string-with-len mp4-file (- (atom-size atom) 16))))
+
+(simple-text-decode +itunes-album+)
+(simple-text-decode +itunes-album-artist+)
+(simple-text-decode +itunes-artist+)
+(simple-text-decode +itunes-comment+)
+(simple-text-decode +itunes-composer+)
+(simple-text-decode +itunes-copyright+)
+(simple-text-decode +itunes-year+)
+(simple-text-decode +itunes-encoder+)
+(simple-text-decode +itunes-groups+)
+(simple-text-decode +itunes-genre-x+)
+(simple-text-decode +itunes-lyrics+)
+(simple-text-decode +itunes-purchased-date+)
+(simple-text-decode +itunes-title+)
+(simple-text-decode +itunes-tool+)
+(simple-text-decode +itunes-writer+)
+
+(defmacro simple-a-b-decode (type)
+  `(defmethod decode-ilst-data-atom ((type (eql +itunes-ilst-data+)) atom (atom-parent-type (eql ,type)) mp4-file)
+	 (declare (ignore atom))
+	 (stream-read-u16 mp4-file)					; throw away XXX Why?
+	 (let ((a) (b))
+	   (setf a (stream-read-u16 mp4-file))
+	   (setf b (stream-read-u16 mp4-file))
+	   (stream-read-u16 mp4-file)				; throw away XXX Why?
+	   (list a b))))
+
+(simple-a-b-decode +itunes-track+)
+(simple-a-b-decode +itunes-track-n+)
+
+(defmethod decode-ilst-data-atom ((type (eql +itunes-ilst-data+)) atom (atom-parent-type (eql +itunes-disk+)) mp4-file)
   (declare (ignore atom))
-  (stream-read-u16 mp4-file)					; throw away
+  (stream-read-u16 mp4-file)					; throw away XXX Why?
   (let ((a) (b))
 	(setf a (stream-read-u16 mp4-file))
 	(setf b (stream-read-u16 mp4-file))
-	(stream-read-u16 mp4-file)					; throw away
 	(list a b)))
 
-(defmethod decode-ilst-data-atom ((type (eql +itunes-ilst-data+)) atom (atom-parent-type (eql +itunes-tempo+)) mp4-file)
-  "decode itunes TMPO atom"
-  (declare (ignore atom))
-  (stream-read-u16 mp4-file))
+(defmacro simple-u16-decode (type)
+  `(defmethod decode-ilst-data-atom ((type (eql +itunes-ilst-data+)) atom (atom-parent-type (eql ,type)) mp4-file)
+	 (declare (ignore atom))
+	 (stream-read-u16 mp4-file)))
 
-(defmethod decode-ilst-data-atom ((type (eql +itunes-ilst-data+)) atom (atom-parent-type (eql +itunes-genre+)) mp4-file)
-  "decode itunes GNRE atom"
-  (declare (ignore atom))
-  (stream-read-u16 mp4-file))
+(simple-u16-decode +itunes-tempo+)
+(simple-u16-decode +itunes-genre+)
 
 (defmethod decode-ilst-data-atom ((type (eql +itunes-ilst-data+)) atom (atom-parent-type (eql +itunes-compilation+)) mp4-file)
-  "decode itunes CPIL atom"
   (declare (ignore atom))
   (stream-read-u8 mp4-file))
 
 (defmethod decode-ilst-data-atom ((type (eql +itunes-ilst-data+)) atom (atom-parent-type (eql +itunes-cover-art+)) mp4-file)
-  (let ((blob (make-instance 'mp4-unhandled-data)))
-	(setf (slot-value blob 'blob) (stream-read-sequence mp4-file (- (atom-size atom) 16)))
-	blob))
-
-
-(defclass mp4-atom ()
-  ((atom-file-position :accessor atom-file-position :initarg :atom-file-position)
-   (atom-size :accessor atom-size :initarg :atom-size)
-   (atom-type :accessor atom-type :initarg :atom-type)
-   (atom-children :accessor atom-children :initform (make-mp4-atom-collection)))
-  (:documentation "The minimal mp4-atom.  Note: not all atoms have children, but we put them here anyway to make things 'simple'"))
-
-(defclass mp4-ilst-atom (mp4-atom)
-  ())
-
-(defmethod initialize-instance :after ((me mp4-ilst-atom) &key (mp4-file nil) &allow-other-keys)
-  "Construct an ilst atom"
-  (log5:with-context "mp4-ilst-atom-initializer"
-	(assert (not (null mp4-file)) () "Must pass a stream into this method")
-	(with-slots (atom-size atom-type atom-children) me
-	  (let* ((start (stream-seek mp4-file 0 :current))
-			 (end (+ start (- atom-size 8))))
-		(log-mp4-atom "mp4-ilst-atom-initializer:entry, start = ~:d, end = ~:d" start end)
-		(do* ()
-			 ((>= (stream-seek mp4-file 0 :current) end))
-		  (log-mp4-atom "ilst atom top of loop: start = ~:d, current = ~:d, end = ~:d"
-						start (stream-seek mp4-file 0 :current) end)
-		  (let ((child (make-mp4-atom mp4-file atom-type)))
-			 (log-mp4-atom "adding new child ~a" (vpprint child nil))
-			 (add atom-children child)))))
-	 (log-mp4-atom "Returning ilst atom: ~a" (vpprint me nil))))
-
- (defclass mp4-ilst-generic-data-atom (mp4-atom)
-   ((atom-version :accessor atom-version :initarg :atom-version)
-	(atom-flags   :accessor atom-flags   :initarg :atom-flags)
-	(atom-value   :accessor atom-value   :initarg :atom-value)
-	(atom-parent-type  :accessor atom-parent-type  :initarg :atom-parent-type :initform nil))
-   (:documentation   "Represents the 'data' portion of ilst data atom"))
-
- (defmethod initialize-instance :after ((me mp4-ilst-generic-data-atom) &key mp4-file &allow-other-keys)
-   (log5:with-context "mp4-ilst-generic-data-atom-initializer"
-	 (assert (not (null mp4-file)) () "Must pass a stream into this method")
-	 (log-mp4-atom "mp4-ilst-generic-data-atom-initializer:entry")
-	 (with-slots (atom-size atom-type atom-version atom-flags atom-value atom-parent-type) me
-	   (setf atom-version (stream-read-u8 mp4-file))
-	   (setf atom-flags (stream-read-u24 mp4-file))
-	   (if (= atom-type +itunes-ilst-data+)
-		   (assert (= 0 (stream-read-u32 mp4-file)) () "a data atom lacks the required null field"))
-	   (log-mp4-atom "size = ~:d, name = ~a, version = ~d, flags = ~x"
-					 atom-size (as-string atom-type) atom-version atom-flags)
-	   (setf atom-value (decode-ilst-data-atom atom-type me atom-parent-type mp4-file))
-	   (log-mp4-atom "generic atom: ~a" (vpprint me nil)))))
-
-(defclass mp4-container-atom (mp4-atom)
-  ()
-  (:documentation "The class representing an mp4 container atom"))
-
-(defmethod initialize-instance :after ((me mp4-container-atom) &key (mp4-file nil) &allow-other-keys)
-  "Upon initializing a container mp4 atom, read the nested atoms within it.'"
-  (log5:with-context "mp4-container-atom-initializer"
-	(assert (not (null mp4-file)) () "Must pass a stream into this method")
-
-	(log-mp4-atom "mp4-container-atom-initializer")
-	(with-slots (atom-children atom-file-position atom-of-interest atom-size atom-type atom-decoded) me
-
-	  (log-mp4-atom "entry: starting file position = ~:d, atom ~a" atom-file-position (vpprint me nil))
-	  (log-mp4-atom "type ~a is container atom of interest; read the nested atoms" (as-string atom-type))
-	  (cond ((= atom-type +mp4-atom-meta+)
-			 (log-mp4-atom "got META, moving file position forward 4 bytes") ;null field
-			 (stream-seek mp4-file 4 :current)))
-
-	  ;; we are now at the file-position we are need to be at, so start reading those atoms!
-	  (block stream-read-file
-		(log-mp4-atom "starting stream-read-file block with file-position = ~:d and end = ~:d" atom-file-position (+ atom-file-position atom-size))
-		(do ()
-			((>= (stream-seek mp4-file 0 :current) (+ atom-file-position atom-size)))
-		  (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))
-		  (let ((child (make-mp4-atom mp4-file)))
-			(log-mp4-atom "adding new child ~a" (vpprint child nil))
-			(add atom-children child))))
-	  (log-mp4-atom "ended stream-read-file block, file position now ~:d" (stream-seek mp4-file 0 :current)))))
-
-(defun make-mp4-atom (mp4-file &optional atom-parent-type)
-  "Get current file position, read in size/type, then construct the correct atom.
-If type is an ilst type, read it all it.  If it is a container atom of interest,
-leave file position as is, since caller will want to read in nested atoms.  Otherwise,
-seek forward past end of this atom."
+  (stream-read-sequence mp4-file (- (atom-size atom) 16)))
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; AUDIO PROPERTY ATOMS ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; (defclass mp4-hdlr-atom (atom-raw-mixin mp4-atom)())  ; XXX need to get real format for this...
+
+;;; song length is seconds is (float duration) / (float scale)
+;; (defclass atom-mdhd (mp4-atom)
+;;   ((version  :accessor version)
+;;    (flags    :accessor flags)
+;;    (c-time   :accessor c-time)
+;;    (m-time   :accessor m-time)
+;;    (scale    :accessor scale)
+;;    (duration :accessor duration)
+;;    (lang     :accessor lang)
+;;    (quality  :accessor quality)))
+
+;; (defmethod initialize-instance :after ((me atom-mdhd) &key (mp4-file nil) &allow-other-keys)
+;;   (with-slots (version flags c-time m-time scale duration lang quality) me
+;; 	(setf version  (stream-read-u8 mp4-file))
+;; 	(setf flags    (stream-read-u24 mp4-file))
+;; 	(setf c-time   (stream-read-u32 mp4-file))
+;; 	(setf m-time   (stream-read-u32 mp4-file))
+;; 	(setf scale    (stream-read-u32 mp4-file))
+;; 	(setf duration (if (= 0 version) (stream-read-u32 mp4-file) (stream-read-u64 mp4-file)))
+;; 	(setf lang     (stream-read-u16 mp4-file))
+;; 	(setf quality  (stream-read-u16 mp4-file))))
+
+;; (defclass atom-stsd (mp4-atom)
+;;   ((flags       :accessor flags)
+;;    (version     :accessor version)
+;;    (num-entries :accessor num-entries)))
+
+;; (defmethod initialize-instance :after ((me atom-stsd) &key (mp4-file nil) &allow-other-keys)
+;;   (log5:with-context "atom-stsd"
+;; 	(with-slots (flags version num-entries)
+;; 	  (setf version (stream-read-u8 mp4-file))
+;; 	  (setf flags (stream-read-u24 mp4-file))
+;; 	  (setf num-entries (stream-read-u32 mp4-file))
+;; 	  (log-mp4-atom "atom-stsd: version = ~d, flags = ~x, num-fields = ~d" version flags num-entries))))
+
+;; (defclass atom-mp4a (mp4-atom)
+;;   ((reserved    :accessor reserved)    ; 6 bytes
+;;    (d-ref-idx   :accessor d-ref-idx)   ; 2 bytes
+;;    (version     :accessor version)     ; 2 bytes
+;;    (revision    :accessor revision)    ; 2 bytes
+;;    (vendor      :accessor vendor)      ; 4 bytes
+;;    (num-chans   :accessor num-chans)   ; 2 bytes
+;;    (samp-size   :accessor samp-size)   ; 2 bytes
+;;    (comp-id     :accessor comp-id)     ; 2 bytes
+;;    (packet-size :accessor packet-size) ; 2 bytes
+;;    (samp-rate   :accessor samp-rate)   ; 4 bytes
+
+;; (defmethod initialize-instance :after ((me atom-mp4a) &key (mp4-file nil) &allow-other-keys)
+;;   (log5:with-context "atom-mp4a"
+;; 	(with-slots (reserved d-ref-idx version revision vendor num-chans samp-size comp-id packet-size samp-rate) me
+;; 	  (setf reserved   (stream-read-sequence mp4-file 6))
+;; 	  (setf d-ref-idx  (stream-read-u16 mp4-file))
+;; 	  (setf version	   (stream-read-u16 mp4-file))
+;; 	  (setf revision   (stream-read-u16 mp4-file))
+;; 	  (setf vendor	   (stream-read-u32 mp4-file))
+;; 	  (setf num-chans  (stream-read-u16 mp4-file))
+;; 	  (setf samp-size  (stream-read-u16 mp4-file))
+;; 	  (setf comp-id	   (stream-read-u16 mp4-file))
+;; 	  (setf packet-size (stream-read-u16 mp4-file))
+;; 	  (setf samp-rate  (stream-read-u32 mp4-file)))))
+
+(defun read-container-atoms (mp4-file parent-atom)
+  "loop through a container atom and add it's children to it"
+  (with-slots (atom-children atom-file-position atom-of-interest atom-size atom-type atom-decoded) parent-atom
+	(atom-read-loop mp4-file (+ atom-file-position atom-size)
+					(lambda ()
+					  (let ((child (make-mp4-atom mp4-file atom-type)))
+						(log-mp4-atom "read-container-atoms: adding new child ~a" (vpprint child nil))
+						(add atom-children child))))))
+
+(defclass atom-meta (mp4-atom)
+  ((version  :accessor version)
+   (flags    :accessor flags)))
+(defmethod initialize-instance :after ((me atom-meta) &key (mp4-file nil) &allow-other-keys)
+   (with-slots (version flags) me
+	 (setf version  (stream-read-u8 mp4-file))
+	 (setf flags    (stream-read-u24 mp4-file))
+	 (read-container-atoms mp4-file me)))
+
+(defclass atom-moov (mp4-atom) ())
+(defmethod initialize-instance :after ((me atom-moov) &key (mp4-file nil) &allow-other-keys)
+  (read-container-atoms mp4-file me))
+(defclass atom-udta (mp4-atom) ())
+(defmethod initialize-instance :after ((me atom-udta) &key (mp4-file nil) &allow-other-keys)
+  (read-container-atoms mp4-file me))
+(defclass atom-mdia (mp4-atom) ())
+(defmethod initialize-instance :after ((me atom-mdia) &key (mp4-file nil) &allow-other-keys)
+  (read-container-atoms mp4-file me))
+
+(defun find-atom-class (id)
+  "Search by concatenating 'atom-' with ID and look for that symbol in this package"
+  (log5:with-context "find-atom-class"
+	(log-mp4-atom "find-atom-class: looking for class <~a>" (as-string id))
+	(let ((found-class-symbol (find-symbol (string-upcase (concatenate 'string "atom-" (as-string id))) :MP4-ATOM))
+		  (found-class))
+
+	  ;; if we found the class name, return the class (to be used for MAKE-INSTANCE)
+	  (when found-class-symbol
+		(setf found-class (find-class found-class-symbol))
+		(log-mp4-atom "find-atom-class: found class: ~a" found-class)
+		(return-from find-atom-class (find-class found-class-symbol))))
+
+	;; didn't find a class, so return ATOM-SKIP class
+	(log-mp4-atom "find-atom-class: class not found")
+	'atom-skip))
+
+(defun make-mp4-atom (mp4-file &optional parent-type)
+  "Get current file position, read in size/type, then construct the correct atom."
   (log5:with-context "make-mp4-atom"
 	(let* ((pos (stream-seek mp4-file 0 :current))
 		   (siz (stream-read-u32 mp4-file))
 		   (typ (stream-read-u32 mp4-file))
 		   (atom))
 	  (declare (type integer pos siz typ))
+
+	  (log-mp4-atom "make-mp4-atom: @ pos = ~:d of size = ~:d and type = ~a" pos siz (as-string typ))
+
 	  (when (= 0 siz)
-		(warn-user "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))
-		(setf siz 8))
-	  (log-mp4-atom "pos = ~:d, size = ~:d, type = ~a" pos siz (as-string typ))
-	  (cond ((member typ *atoms-of-interest*)
-			 (log-mp4-atom  "~a is a container atom we are interested in" (as-string typ))
-			 (setf atom (make-instance 'mp4-container-atom :atom-size siz :atom-type typ :atom-file-position pos :mp4-file mp4-file)))
-			((member typ *itunes-atom-types*)
-			 (log-mp4-atom "~a is an ilst atom, read it all in" (as-string typ))
-			 (setf atom (make-instance 'mp4-ilst-atom :atom-size siz :atom-type typ :atom-file-position pos :mp4-file mp4-file)))
-			((= typ +itunes-ilst-data+)
-			 (log-mp4-atom "~a is an ilst data atom, read it all in" (as-string typ))
-			 (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)))
-			(t
-			 (log-mp4-atom "~a is an atom we are NOT interested in; seek past it" (as-string typ))
-			 (setf atom (make-instance 'mp4-atom :atom-size siz :atom-type typ :atom-file-position pos))
-			 (stream-seek mp4-file (- siz 8) :current)))
-	  (log-mp4-atom "returning ~a" (vpprint atom nil))
+		(error "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)))
+
+	  (setf atom (make-instance (find-atom-class typ) :atom-size siz :atom-type typ :atom-file-position pos :mp4-file mp4-file :atom-parent-type parent-type))
+	  (log-mp4-atom "make-mp4-atom: made ~a" (vpprint atom nil))
 	  atom)))
 
 (defmethod vpprint ((me mp4-atom) stream)
   (format stream "~a" (with-output-to-string (s)
 						(with-slots (atom-children atom-file-position atom-size atom-type) me
-						  (format s "Atom <~a> @ ~:d of size ~:d and child count of ~d"
+						  (format s "ATOM: type: <~a> @ ~:d of size ~:d and child count of ~d"
 								  (as-string atom-type) atom-file-position atom-size (size atom-children)))
-						(if (typep me 'mp4-ilst-generic-data-atom)
+						(if (typep me 'atom-data)
 							(with-slots (atom-version atom-flags atom-value atom-type atom-parent-type) me
 							  (format s " having ilst fields: atom-parent-type = ~a, verison = ~d, flags = ~x, data = ~x"
-									  (as-string atom-parent-type) atom-version atom-flags atom-value))))))
+									  (as-string atom-parent-type) atom-version atom-flags 
+									  (if (typep atom-value 'array) (printable-array atom-value) atom-value)))))))
 
-(defclass mp4-unhandled-data ()
-  ((blob :accessor blob :initarg :blob :initform nil))
-  (:documentation "abstraction for a 'blob' of data we don't want to or can't parse"))
-
-(defparameter *pprint-max-array-len* 10
-  "Controls how long an array (atom-value, typically) we will print in pprint-atom")
-
-(defmethod print-object ((me mp4-unhandled-data) stream)
-  "Print a 'blob' (unstructered data), limiting it to no more than *PPRINT-MAX-ARRAY-LEN* octets"
-  (let* ((len (length (slot-value me 'blob)))
-		 (print-len (min len *pprint-max-array-len*))
-		 (printable-array (make-array print-len :displaced-to (slot-value me 'blob))))
-	(format stream "[~:d of ~:d bytes] <~x>" print-len len printable-array)))
-
-;;;;;;;;;;;;;;;;;;;; A collection of atoms (siblings) ;;;;;;;;;;;;;;;;;;;;
-(defclass atom-collection ()
-  ((atoms :accessor atoms :initform nil))
-  (:documentation "A collection of sibling atoms"))
-
-(defun make-mp4-atom-collection () (make-instance 'atom-collection))
-
-(defmethod add ((me atom-collection) new-atom)
-  "Adds new atom to the *end* (need to keep them in order we found them in the file) of this collection"
-  (log5:with-context "add-atom-collection"
-	(with-slots (atoms) me
-	  (log-mp4-atom "adding ~a to atom collection: ~a" new-atom atoms)
-	  (setf atoms (append atoms (list new-atom)))
-	  (log-mp4-atom "collection now: ~a" atoms))))
-
-(defmethod size ((me atom-collection))
-  "Returns the number of atoms in this collection"
-  (length (slot-value me 'atoms)))
-
-(defmethod map-mp4-atom ((me atom-collection) &key (func nil) (depth nil))
-  "Given a collection of atoms, call map-mp4-atom for each one"
-  (log5:with-context "map-mp4-atom(collection)"
-	(log-mp4-atom "mapping collection: ~a" (slot-value me 'atoms))
-	(dolist (a (slot-value me 'atoms))
-	  (map-mp4-atom a :func func :depth depth))))
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 
 (defun is-valid-m4-file (mp4-file)
   "Make sure this is an MP4 file.  Quick check: is first atom (at file-offset 4) == FSTYP?"
@@ -348,23 +433,21 @@ seek forward past end of this atom."
 	(= header +m4-ftyp+)))
 
 (defun find-mp4-atoms (mp4-file)
-  "Given a valid MP4 file mp4-file, look for the 'right' atoms and return them.
-The 'right' atoms are those in *atoms-of-interest*"
+  "Given a valid MP4 file mp4-file, look for the 'right' atoms and return them."
   (log5:with-context "find-mp4-atoms"
 	(when (not (is-valid-m4-file mp4-file))
 	  (error 'mp4-atom-condition :location "find-mp4-atoms" :object mp4-file :message "is not an mp4-file" ))
 
-	(log-mp4-atom "before read-file loop, file-position = ~:d, end = ~:d" (stream-seek mp4-file 0 :current) (stream-size mp4-file))
-
-	(setf (audio-streams:mp4-atoms mp4-file) (make-mp4-atom-collection))
-	(do ((new-atom))
-		((> (+ 8 (stream-seek mp4-file 0 :current)) (stream-size mp4-file)))
-	  (log-mp4-atom "top of read-file loop, current file-position = ~:d, end = ~:d" (stream-seek mp4-file 0 :current) (stream-size mp4-file))
-	  (setf new-atom (make-mp4-atom mp4-file))
-	  (when new-atom (add (mp4-atoms mp4-file) new-atom)))
-
-	(log-mp4-atom "returning atom-collection of size ~d" (size (mp4-atoms mp4-file)))))
+	(log-mp4-atom "find-mp4-atoms: ~a, before read-file loop, file-position = ~:d, end = ~:d"
+				  (stream-filename mp4-file) (stream-seek mp4-file 0 :current) (stream-size mp4-file))
+	(setf (mp4-atoms mp4-file) (make-mp4-atom-collection))
+	(atom-read-loop mp4-file (stream-size mp4-file)
+					(lambda ()
+					  (let ((new-atom (make-mp4-atom mp4-file)))
+						(when new-atom
+						  (add (mp4-atoms mp4-file) new-atom)))))
 
+	(log-mp4-atom "find-mp4-atoms: returning atom-collection of size ~d" (size (mp4-atoms mp4-file)))))
 
 (defmethod map-mp4-atom ((me mp4-atom) &key (func nil) (depth nil))
   "traverse all atoms under a given atom"
@@ -372,7 +455,7 @@ The 'right' atoms are those in *atoms-of-interest*"
 	(labels ((_indented-atom (atom depth)
 			   (format t "~vt~a~%"  (if (null depth) 0 depth) (vpprint atom nil))))
 	  (with-slots (atom-type atom-children) me
-		(log-mp4-atom "Begining traversal with ~a, I have ~d children" (as-string atom-type) (size atom-children))
+		(log-mp4-atom "map-mp4-atom: begining traversal with ~a, I have ~d children" (as-string atom-type) (size atom-children))
 		(when (null func)
 		  (setf func #'_indented-atom))
 		(funcall func me depth)
@@ -382,38 +465,38 @@ The 'right' atoms are those in *atoms-of-interest*"
   "Used in finding nested atoms.
 Given an atom and a path, if atom-type matches first element of path, then we've found our match."
   (log5:with-context "traverse-atom"
-	(log-mp4-atom "traverse-atom entered with ~a ~a" (atom-type me) path)
+	(log-mp4-atom "traverse (mp4-atom): entered with ~a ~a" (as-string (atom-type me)) path)
 	(cond ((null path)
 		   (error "Path exhausted in travese atom")	; don't think this can happen?
 		   nil)
 		  ((= (atom-type me) (first path))
-		   (log-mp4-atom "current path matches thus far ~a ~a" (atom-type me) path)
+		   (log-mp4-atom "traverse (mp4-atom): current path matches thus far ~a ~a" (atom-type me) path)
 		   (cond
 			 ((= 1 (length path))
-			  (log-mp4-atom "length of path is 1, so found!")
+			  (log-mp4-atom "traverse (mp4-atom): length of path is 1, so found!")
 			  (return-from traverse me)))))
 
-	(log-mp4-atom "Current path doesn't match ~a ~a" (atom-type me) path)
+	(log-mp4-atom "traverse (mp4-atom): current path doesn't match ~a ~a" (atom-type me) path)
 	nil))
 
 (defmethod traverse ((me atom-collection) path)
   "Used in finding nested atoms. Seach the collection and if we find a match with first of path,
-call traverse atom (unless length of path == 1, in which case, we've found out match)"
+call traverse atom (unless length of path == 1, in which case, we've found our match)"
   (log5:with-context "traverse-atom-collection"
-	(log-mp4-atom "entering with ~a ~a" me path)
+	(log-mp4-atom "traverse (atom-collection): entering with ~a ~a" me path)
 	(dolist (sibling (atoms me))	; cleaner than using map-mp4-atom, but still a kludge
 	  (with-slots (atom-type atom-children) sibling
-		(log-mp4-atom "looking at ~x::~x" atom-type (first path))
+		(log-mp4-atom "traverse (atom-collection): looking at ~x::~x" atom-type (first path))
 		(when (= atom-type (first path))
 		  (cond
 			((= 1 (length path))
-			 (log-mp4-atom "Found ~a" sibling)
+			 (log-mp4-atom "traverse (atom-collection): found ~a" sibling)
 			 (return-from traverse sibling))
 			(t
-			 (log-mp4-atom "path matches, calling traverse atom with ~a, ~a" atom-children (rest path))
+			 (log-mp4-atom "traverse (atom-collection): path matches, calling traverse atom with ~a, ~a" atom-children (rest path))
 			 (let ((found (traverse atom-children (rest path))))
 			   (if found (return-from traverse found))))))))
-	(log-mp4-atom "Looked at all, found nothing")
+	(log-mp4-atom "traverse (atom-collection): looked at all, found nothing")
 	nil))
 
 (defmethod tag-get-value (atoms node)
@@ -433,3 +516,29 @@ call traverse atom (unless length of path == 1, in which case, we've found out m
 						  (format t "~vt~a~%" depth (vpprint atom nil))))))
 
 
+(defun find-all (base name)
+  "Starting as BASE atom, recursively search for all instances of NAME"
+  (let* ((search-name (if (typep name 'string) (as-int name) name))
+		 (found))
+
+	(map-mp4-atom base
+				  :func (lambda (atom depth)
+						  (declare (ignore depth))
+						  (when (= (atom-type atom) search-name)
+							(push atom found))))
+	found))
+
+;; (defun get-audio-properties-atoms (mp4-file)
+;;   "First, find all TRAKs under moov. For the one that contains a HDLR atom with DATA of 'soun',
+;; return trak.mdia.mdhd and trak.mdia.minf.stbl.stsd"
+;;   (dolist (track (find-all (traverse (mp4-atoms mp4-file) (list +mp4-atom-moov+)) "trak"))
+;; 	(format t "track = ~a~%" track)
+;; 	(let ((hdlr (traverse track (list +mp4-atom-mdia+ +audioprop-hdlr+))))
+;; 	  (format t "hdlr = ~a~%" hdlr)
+;; 	  (when (and (not (null hdlr))
+;; 				 (string= "soun" (subseq (data hdlr) 8 12)))
+
+;; 		;; we've found the correct track, extract atoms
+;; 		(return-from get-audio-properties-atoms (values (traverse track (list +mp4-atom-mdia+ +audioprop-mdhd+))
+;; 														(traverse track (list +mp4-atom-mdia+ +mp4-atom-minf+ +mp4-atom-stbl+ +audioprop-stsd+)))))))
+;;   nil)