taglib-tests.lisp 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161
  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))
  6. (in-package #:taglib-tests)
  7. ;;; some convenient songs to parse
  8. (defparameter *song-m4a* "/home/markv/Music/Queen/Queen I/01 Keep Yourself Alive.m4a")
  9. (defparameter *song-mp3* "/home/markv/Music/Queen/Sheer Heart Attack/07 In The Lap Of The Gods.mp3")
  10. (defparameter *song-flac* "/home/markv/Music/Frank Zappa/Baby Snakes/02. Baby Snakes.flac")
  11. ;;;
  12. ;;; Set the pathname (aka filename) encoding in CCL for appropriate platform
  13. ;;;
  14. ;;; A note re filesystem encoding: my music collection is housed on a Mac and shared via SAMBA.
  15. ;;; In order to make sure we get valid pathnames, we need to set CCL's filesystem encoding to
  16. ;;; :UTF-8
  17. (defun set-pathname-encoding (enc)
  18. #+CCL (setf (ccl:pathname-encoding-name) enc)
  19. #-CCL (declare (ignore enc))
  20. t)
  21. (defun set-pathname-encoding-for-osx () (set-pathname-encoding :utf-8))
  22. (defun set-pathname-encoding-for-linux () (set-pathname-encoding nil))
  23. (defun do-audio-file (&key (file *song-m4a*) (func (constantly t)))
  24. "Parse one audio file (with condition handling)."
  25. (let ((foo))
  26. (unwind-protect
  27. (handler-case
  28. (progn
  29. (setf foo (make-file-stream file))
  30. (when foo
  31. (parse-audio-file foo)) ; only call parse-audio if we got back a known file type
  32. (funcall func foo)) ; call func even if foo is null so it can account for unkown file types
  33. (condition (c)
  34. (utils:warn-user "File: ~a~%Got condition: <~a>" file c)))
  35. (when foo
  36. (stream-close foo)))))
  37. (defun do-audio-dir (&key (dir "/home/markv/Music/Queen") (file-system-encoding :utf-8)
  38. (func #'abstract-tag:show-tags))
  39. "Walk :DIR and FUNCALL specified function for each file audio found."
  40. (set-pathname-encoding file-system-encoding)
  41. (let ((mp3-count 0)
  42. (flac-count 0)
  43. (mp4-count 0)
  44. (other-count 0))
  45. (cl-fad:walk-directory dir (lambda (f)
  46. (do-audio-file :file f
  47. :func (lambda (s)
  48. (cond ((typep s 'mp3-file-stream) (incf mp3-count))
  49. ((typep s 'flac-file-stream) (incf flac-count))
  50. ((typep s 'mp4-file-stream) (incf mp4-count))
  51. ((null s) (incf other-count)))
  52. (when (and (not (null s)) func) (funcall func s))))))
  53. (format t "~&~:d MP3s, ~:d MP4s, ~:d FLACs, ~:d Others, for a total of ~:d~%"
  54. mp3-count mp4-count flac-count other-count (+ mp3-count mp4-count flac-count other-count))))
  55. (defun time-test (&key (dir "/home/markv/Music/Queen") (file-system-encoding :utf-8) (do-audio-processing t))
  56. "Time parsing of DIR."
  57. (set-pathname-encoding file-system-encoding)
  58. (let ((audio-streams:*get-audio-info* do-audio-processing))
  59. (time (do-audio-dir :dir dir :file-system-encoding file-system-encoding :func nil))))
  60. ;;;;;;;;;;;;;;;;;;;; multi-thread code below ;;;;;;;;;;;;;;;;;;;;
  61. (defparameter *end-thread* #xdeadbeef)
  62. (defparameter *max-threads* 4)
  63. ;;; Simple structure to hold a thread's results
  64. (defstruct chanl-results
  65. name
  66. mp3-count
  67. flac-count
  68. mp4-count
  69. other-count)
  70. (defun mp-do-audio-dir (&key (dir "/home/markv/Music/Queen") (file-system-encoding :utf-8)
  71. (func #'abstract-tag:show-tags))
  72. "Walk :DIR and FUNCALL specified function for each file audio found."
  73. (set-pathname-encoding file-system-encoding)
  74. (let ((channel (make-instance 'chanl:unbounded-channel))
  75. (dead-channel (make-instance 'chanl:unbounded-channel))
  76. (mp3-count 0)
  77. (flac-count 0)
  78. (mp4-count 0)
  79. (other-count 0))
  80. ;; This function is run by each thread
  81. ;; Thread sits in a loop, reading from CHAN. If that read
  82. ;; returns the integer *END-THREAD*, then thread exits; otherwise,
  83. ;; it runs DO-AUDIO-FILE on the file passed in.
  84. (labels ((thread-reader ()
  85. (declare (special *me*))
  86. (let ((f)
  87. (results (make-chanl-results :name *me* :mp3-count 0 :flac-count 0 :mp4-count 0 :other-count 0)))
  88. (loop
  89. (with-slots (name mp3-count mp4-count flac-count other-count) results
  90. (setf f (chanl:recv channel))
  91. (when (and (typep f 'integer)
  92. (= f *end-thread*))
  93. (chanl:send dead-channel results) ; send structure of stats back to parent
  94. (return-from thread-reader nil))
  95. (do-audio-file :file f :func (lambda (s)
  96. (cond ((typep s 'mp3-file-stream) (incf mp3-count))
  97. ((typep s 'flac-file-stream) (incf flac-count))
  98. ((typep s 'mp4-file-stream) (incf mp4-count))
  99. ((null s) (incf other-count)))
  100. (when (and (not (null s)) func) (funcall func s)))))))))
  101. ;; first, add all files in DIR to CHANNEL
  102. (cl-fad:walk-directory dir (lambda (f) (chanl:send channel f)))
  103. ;; At this point, CHANNEL is stuffed with files.
  104. ;; Now, send *MAX-THREADS* "ends" (at end of CHANNEL) and
  105. ;; spawn *MAX-THREADS* threads
  106. (dotimes (i *max-threads*)
  107. (chanl:send channel *end-thread*))
  108. (dotimes (i *max-threads*)
  109. (chanl:pcall #'thread-reader :initial-bindings `((*me* ,(format nil "reader-thread-~d" i)))))
  110. ;; sit in loop until we read *MAX-THREADS* results
  111. (block thread-reap
  112. (let ((i 0)
  113. results)
  114. (format t "Waiting on ~d threads~%" *max-threads*)
  115. (loop
  116. (force-output *standard-output*)
  117. (setf results (chanl:recv dead-channel))
  118. (format t "~4t~a died, ~:d MP3s, ~:d MP4s, ~:d FLACs, ~:d Others~%"
  119. (chanl-results-name results)
  120. (chanl-results-mp3-count results)
  121. (chanl-results-mp4-count results)
  122. (chanl-results-flac-count results)
  123. (chanl-results-other-count results))
  124. (force-output *standard-output*)
  125. (incf mp3-count (chanl-results-mp3-count results))
  126. (incf mp4-count (chanl-results-mp4-count results))
  127. (incf flac-count (chanl-results-flac-count results))
  128. (incf other-count (chanl-results-other-count results))
  129. (incf i)
  130. (when (= i *max-threads*)
  131. (return-from thread-reap *max-threads*)))))
  132. (format t "All threads done~%")
  133. (format t "~&~:d MP3s, ~:d MP4s, ~:d FLACS, ~:d Others, for a total of ~:d files~%"
  134. mp3-count mp4-count flac-count other-count (+ mp3-count mp4-count flac-count other-count)))))
  135. (defun mp-time-test (&key (dir "/home/markv/Music/Queen") (file-system-encoding :utf-8) (do-audio-processing t))
  136. "Time parsing of DIR."
  137. (set-pathname-encoding file-system-encoding)
  138. (let ((audio-streams:*get-audio-info* do-audio-processing))
  139. (time (mp-do-audio-dir :dir dir :file-system-encoding file-system-encoding :func nil))))