taglib-tests.lisp 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238
  1. ;;; -*- Mode: Lisp; show-trailing-whitespace: t; Base: 10; indent-tabs: nil; Syntax: ANSI-Common-Lisp; Package: TAGLIB-TESTS; -*-
  2. ;;; Copyright (c) 2013, Mark VandenBrink. All rights reserved.
  3. (in-package #:cl-user)
  4. (defpackage #:taglib-tests
  5. (:use #:common-lisp #:audio-streams #:utils))
  6. (in-package #:taglib-tests)
  7. ;;; some convenient songs to parse
  8. (defparameter *song-m4a*
  9. "/home/markv/Music/Queen/Queen I/01 Keep Yourself Alive.m4a")
  10. (defparameter *song-mp3*
  11. "/home/markv/Music/Queen/Sheer Heart Attack/07 In The Lap Of The Gods.mp3")
  12. (defparameter *song-flac*
  13. "/home/markv/Music/Frank Zappa/Baby Snakes/02. Baby Snakes.flac")
  14. ;;;
  15. ;;; Set the pathname (aka filename) encoding for appropriate platform
  16. ;;;
  17. ;;; A note re filesystem encoding: my music collection is housed on a Mac and shared via SAMBA.
  18. ;;; In order to make sure we get valid pathnames, we need to set CCL's filesystem encoding to
  19. ;;; :UTF-8
  20. (defun set-pathname-encoding (enc)
  21. #+CCL (setf (ccl:pathname-encoding-name) enc)
  22. #-CCL (declare (ignore enc))
  23. t)
  24. (defun set-pathname-encoding-for-osx () (set-pathname-encoding :utf-8))
  25. (defun set-pathname-encoding-for-linux () (set-pathname-encoding nil))
  26. (defun do-audio-file (&key (file *song-m4a*)
  27. (func #'abstract-tag:show-tags))
  28. "Parse one audio file and display the tags"
  29. (awhen (open-audio-file file)
  30. (funcall func it)))
  31. (defstruct file-counts
  32. (mp3-count 0 :type fixnum)
  33. (flac-count 0 :type fixnum)
  34. (mp4-count 0 :type fixnum)
  35. (other-count 0 :type fixnum))
  36. (defmethod print-object ((me file-counts) stream)
  37. (with-slots (mp3-count flac-count mp4-count other-count) me
  38. (format stream
  39. "~&~:d MP3s, ~:d MP4s, ~:d FLACs, ~:d Others, for a total of ~:d files~%"
  40. mp3-count mp4-count flac-count other-count
  41. (+ mp3-count mp4-count flac-count other-count))))
  42. (defun do-audio-dir (&key (dir "/home/markv/Music/Queen")
  43. (file-system-encoding :utf-8)
  44. (func #'abstract-tag:show-tags))
  45. "Walk :DIR and FUNCALL specified function for each file audio found."
  46. (set-pathname-encoding file-system-encoding)
  47. (let ((file-counts (make-file-counts)))
  48. (with-slots (mp3-count flac-count mp4-count other-count) file-counts
  49. (cl-fad:walk-directory dir
  50. (lambda (f)
  51. (do-audio-file :file f
  52. :func (lambda (s)
  53. (cond ((typep s 'id3:mp3-file)
  54. (incf mp3-count))
  55. ((typep s 'flac:flac-file)
  56. (incf flac-count))
  57. ((typep s 'm4a:mp4-file)
  58. (incf mp4-count))
  59. ((null s)
  60. (incf other-count)))
  61. (when (and (not (null s)) func)
  62. (funcall func s)))))))
  63. file-counts))
  64. (defun time-test (&key (dir "/home/markv/Music/Queen")
  65. (file-system-encoding :utf-8) (do-audio-processing t))
  66. "Time parsing of DIR."
  67. (set-pathname-encoding file-system-encoding)
  68. (let ((audio-streams:*get-audio-info* do-audio-processing))
  69. (time (format t "~a~%"
  70. (do-audio-dir :dir dir
  71. :file-system-encoding file-system-encoding :func nil)))))
  72. ;; (defun get-stats (&key (dir "/home/markv/Music/Queen")
  73. ;; (file-system-encoding :utf-8)
  74. ;; (do-audio-processing t))
  75. ;; "Gen up some interesting statistics on DIR"
  76. ;; (let ((m4-ht (make-hash-table :test #'equalp))
  77. ;; (m3-ht (make-hash-table :test #'equalp))
  78. ;; (fl-ht (make-hash-table :test #'equalp)))
  79. ;; (do-audio-dir
  80. ;; :dir dir
  81. ;; :file-system-encoding file-system-encoding
  82. ;; :func (lambda (s)
  83. ;; (cond ((typep s 'id3:mp3-file)
  84. ;; (id3:map-id3-frames
  85. ;; s
  86. ;; :func (lambda (f)
  87. ;; (multiple-value-bind (value foundp)
  88. ;; (gethash (id3:id f) m3-ht)
  89. ;; (setf (gethash (id3:id f) m3-ht)
  90. ;; (if foundp
  91. ;; (1+ value)
  92. ;; 1))))))
  93. ;; ((typep s 'flac:flac-file)
  94. ;; t)
  95. ;; ((typep s 'm4a:mp4-file)
  96. ;; (tree:traverse
  97. ;; (m4a:mp4-atoms s)
  98. ;; (lambda (node depth)
  99. ;; (declare (ignore depth))
  100. ;; (setf node (tree:data node))
  101. ;; (multiple-value-bind (value foundp)
  102. ;; (gethash (m4a:atom-type node) m3-ht)
  103. ;; (setf (gethash (m4a:atom-type node) m3-ht)
  104. ;; (if foundp
  105. ;; (1+ value)
  106. ;; 1))))))
  107. ;; ((null s)
  108. ;; (incf other-count)))))
  109. ;; (format t "MP3 Stats:~%")
  110. ;; (loop for key being the hash-keys of m3-ht
  111. ;; using (hash-value value)
  112. ;; do (format t "~a:~:d~%" key value))
  113. ;; (format t "M4A Stats:~%")
  114. ;; (loop for key being the hash-keys of m4-ht
  115. ;; using (hash-value value)
  116. ;; do (format t "~a:~:d~%" key value))
  117. ;; (values m3-ht m4-ht fl-ht)))
  118. ;;;; multi-thread code below
  119. #+(or :ccl :sbcl :abcl)
  120. (progn
  121. (defparameter *end-thread* #xdeadbeef)
  122. (defparameter *max-threads* 4)
  123. ;;; Simple structure to hold a thread's results
  124. (defstruct chanl-results
  125. name
  126. mp3-count
  127. flac-count
  128. mp4-count
  129. other-count)
  130. (defun mp-do-audio-dir (&key (dir "/home/markv/Music/Queen")
  131. (file-system-encoding :utf-8)
  132. (func nil))
  133. "Walk :DIR and FUNCALL specified function for each file audio found."
  134. (set-pathname-encoding file-system-encoding)
  135. (let ((channel (make-instance 'chanl:unbounded-channel))
  136. (dead-channel (make-instance 'chanl:unbounded-channel))
  137. (mp3-count 0)
  138. (flac-count 0)
  139. (mp4-count 0)
  140. (other-count 0))
  141. ;; This function is run by each thread
  142. ;; Thread sits in a loop, reading from CHAN. If that read
  143. ;; returns the integer *END-THREAD*, then thread exits; otherwise,
  144. ;; it runs DO-AUDIO-FILE on the file passed in.
  145. (labels ((thread-reader ()
  146. (declare (special *me*))
  147. (let ((f)
  148. (results (make-chanl-results :name *me* :mp3-count 0
  149. :flac-count 0 :mp4-count 0
  150. :other-count 0)))
  151. (loop
  152. (with-slots (name mp3-count mp4-count flac-count other-count) results
  153. (setf f (chanl:recv channel))
  154. (when (and (typep f 'integer)
  155. (= f *end-thread*))
  156. (chanl:send dead-channel results)
  157. (return-from thread-reader nil))
  158. (do-audio-file :file f
  159. :func (lambda (s)
  160. (cond ((typep s 'id3:mp3-file)
  161. (incf mp3-count))
  162. ((typep s 'flac:flac-file)
  163. (incf flac-count))
  164. ((typep s 'm4a:mp4-file)
  165. (incf mp4-count))
  166. ((null s)
  167. (incf other-count)))
  168. (when (and (not (null s)) func)
  169. (funcall func s)))))))))
  170. ;; first, add all files in DIR to CHANNEL
  171. (cl-fad:walk-directory dir (lambda (f) (chanl:send channel f)))
  172. ;; At this point, CHANNEL is stuffed with files.
  173. ;; Now, send *MAX-THREADS* "ends" (at end of CHANNEL) and
  174. ;; spawn *MAX-THREADS* threads
  175. (dotimes (i *max-threads*)
  176. (chanl:send channel *end-thread*))
  177. (dotimes (i *max-threads*)
  178. (chanl:pcall
  179. #'thread-reader
  180. :initial-bindings `((*me* ,(format nil "reader-thread-~d" i)))))
  181. ;; sit in loop until we read *MAX-THREADS* results
  182. (block thread-reap
  183. (let ((i 0)
  184. results)
  185. (format t "Waiting on ~d threads~%" *max-threads*)
  186. (loop
  187. (force-output *standard-output*)
  188. (setf results (chanl:recv dead-channel))
  189. (format t "~4t~a died, ~:d MP3s, ~:d MP4s, ~:d FLACs, ~:d Others~%"
  190. (chanl-results-name results)
  191. (chanl-results-mp3-count results)
  192. (chanl-results-mp4-count results)
  193. (chanl-results-flac-count results)
  194. (chanl-results-other-count results))
  195. (force-output *standard-output*)
  196. (incf mp3-count (chanl-results-mp3-count results))
  197. (incf mp4-count (chanl-results-mp4-count results))
  198. (incf flac-count (chanl-results-flac-count results))
  199. (incf other-count (chanl-results-other-count results))
  200. (incf i)
  201. (when (= i *max-threads*)
  202. (return-from thread-reap *max-threads*)))))
  203. (format t "All threads done~%")
  204. (format t "~&~:d MP3s, ~:d MP4s, ~:d FLACS, ~:d Others, for a total of ~:d files~%"
  205. mp3-count mp4-count flac-count other-count
  206. (+ mp3-count mp4-count flac-count other-count)))))
  207. (defun mp-time-test (&key (dir "/home/markv/Music/Queen")
  208. (file-system-encoding :utf-8) (do-audio-processing t))
  209. "Time parsing of DIR."
  210. (set-pathname-encoding file-system-encoding)
  211. (let ((audio-streams:*get-audio-info* do-audio-processing))
  212. (time (mp-do-audio-dir :dir dir :file-system-encoding file-system-encoding :func nil))))
  213. )