mp3-tag.lisp 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388
  1. ;;; -*- Mode: Lisp; show-trailing-whitespace: t; Base: 10; indent-tabs: nil; Syntax: ANSI-Common-Lisp; Package: MP3-TAG; -*-
  2. ;;; Copyright (c) 2013, Mark VandenBrink. All rights reserved.
  3. (in-package #:mp3-tag)
  4. (defparameter *id3v1-genres*
  5. #("Blues"
  6. "Classic Rock"
  7. "Country"
  8. "Dance"
  9. "Disco"
  10. "Funk"
  11. "Grunge"
  12. "Hip-Hop"
  13. "Jazz"
  14. "Metal"
  15. "New Age"
  16. "Oldies"
  17. "Other"
  18. "Pop"
  19. "R&B"
  20. "Rap"
  21. "Reggae"
  22. "Rock"
  23. "Techno"
  24. "Industrial"
  25. "Alternative"
  26. "Ska"
  27. "Death Metal"
  28. "Pranks"
  29. "Soundtrack"
  30. "Euro-Techno"
  31. "Ambient"
  32. "Trip-Hop"
  33. "Vocal"
  34. "Jazz+Funk"
  35. "Fusion"
  36. "Trance"
  37. "Classical"
  38. "Instrumental"
  39. "Acid"
  40. "House"
  41. "Game"
  42. "Sound Clip"
  43. "Gospel"
  44. "Noise"
  45. "Alternative Rock"
  46. "Bass"
  47. "Soul"
  48. "Punk"
  49. "Space"
  50. "Meditative"
  51. "Instrumental Pop"
  52. "Instrumental Rock"
  53. "Ethnic"
  54. "Gothic"
  55. "Darkwave"
  56. "Techno-Industrial"
  57. "Electronic"
  58. "Pop-Folk"
  59. "Eurodance"
  60. "Dream"
  61. "Southern Rock"
  62. "Comedy"
  63. "Cult"
  64. "Gangsta"
  65. "Top 40"
  66. "Christian Rap"
  67. "Pop/Funk"
  68. "Jungle"
  69. "Native American"
  70. "Cabaret"
  71. "New Wave"
  72. "Psychedelic"
  73. "Rave"
  74. "Showtunes"
  75. "Trailer"
  76. "Lo-Fi"
  77. "Tribal"
  78. "Acid Punk"
  79. "Acid Jazz"
  80. "Polka"
  81. "Retro"
  82. "Musical"
  83. "Rock & Roll"
  84. "Hard Rock"
  85. "Folk"
  86. "Folk/Rock"
  87. "National Folk"
  88. "Swing"
  89. "Fusion"
  90. "Bebob"
  91. "Latin"
  92. "Revival"
  93. "Celtic"
  94. "Bluegrass"
  95. "Avantgarde"
  96. "Gothic Rock"
  97. "Progressive Rock"
  98. "Psychedelic Rock"
  99. "Symphonic Rock"
  100. "Slow Rock"
  101. "Big Band"
  102. "Chorus"
  103. "Easy Listening"
  104. "Acoustic"
  105. "Humour"
  106. "Speech"
  107. "Chanson"
  108. "Opera"
  109. "Chamber Music"
  110. "Sonata"
  111. "Symphony"
  112. "Booty Bass"
  113. "Primus"
  114. "Porn Groove"
  115. "Satire"
  116. "Slow Jam"
  117. "Club"
  118. "Tango"
  119. "Samba"
  120. "Folklore"
  121. "Ballad"
  122. "Power Ballad"
  123. "Rhythmic Soul"
  124. "Freestyle"
  125. "Duet"
  126. "Punk Rock"
  127. "Drum Solo"
  128. "A Cappella"
  129. "Euro-House"
  130. "Dance Hall"
  131. "Goa"
  132. "Drum & Bass"
  133. "Club-House"
  134. "Hardcore"
  135. "Terror"
  136. "Indie"
  137. "BritPop"
  138. "Negerpunk"
  139. "Polsk Punk"
  140. "Beat"
  141. "Christian Gangsta Rap"
  142. "Heavy Metal"
  143. "Black Metal"
  144. "Crossover"
  145. "Contemporary Christian"
  146. "Christian Rock"
  147. "Merengue"
  148. "Salsa"
  149. "Thrash Metal"
  150. "Anime"
  151. "Jpop"
  152. "Synthpop"))
  153. (defun find-genre (name)
  154. "For debug purpose only: test function to return index of genre, given a name. ignores case and returns first complete match"
  155. (let ((i 0)
  156. (match-str (string-downcase name)))
  157. (loop for s across *id3v1-genres* do
  158. (if (string= (string-downcase s) match-str)
  159. (return-from find-genre i))
  160. (incf i))))
  161. (defun get-id3v1-genre (n)
  162. "Given N, a supposed ID3 genre, range check it to make sure it is > 0 and < (sizeof *ID3V1-GENRES*)"
  163. (if (or (> n (1- (length *id3v1-genres*)))
  164. (< n 0))
  165. "BAD GENRE"
  166. (aref *id3v1-genres* n)))
  167. (defun get-frames (stream names)
  168. "Given a MP3-STREAM, search its frames for NAMES. Return file-order list of matching frames"
  169. (let (found-frames)
  170. (map-id3-frames stream
  171. :func (lambda (f)
  172. (when (member (id f) names :test #'string=)
  173. (push f found-frames))))
  174. (nreverse found-frames)))
  175. ;;; Abstract TAG interface
  176. ;;; The following probably should be macro-ized in the future---lots of cut/paste going on...
  177. (defmethod album ((me mp3-file-stream))
  178. (let ((frames (get-frames me '("TAL" "TALB"))))
  179. (when frames
  180. (assert (= 1 (length frames)) () "There can be only one album tag")
  181. (return-from album (info (first frames)))))
  182. (if (v21-tag-header (id3-header me))
  183. (album (v21-tag-header (id3-header me)))
  184. nil))
  185. (defmethod artist ((me mp3-file-stream))
  186. (let ((frames (get-frames me '("TP1" "TPE1"))))
  187. (when frames
  188. (assert (= 1 (length frames)) () "There can be only one artist tag")
  189. (return-from artist (info (first frames)))))
  190. (if (v21-tag-header (id3-header me))
  191. (artist (v21-tag-header (id3-header me)))
  192. nil))
  193. (defmethod comment ((me mp3-file-stream))
  194. (let ((frames (get-frames me '("COM" "COMM"))))
  195. (when frames
  196. (let ((new-frames))
  197. (dolist (f frames)
  198. (push (list (encoding f) (lang f) (desc f) (val f)) new-frames))
  199. ;; XXX need to render this into text
  200. (return-from comment new-frames))))
  201. (if (v21-tag-header (id3-header me))
  202. (comment (v21-tag-header (id3-header me)))
  203. nil))
  204. (defmethod year ((me mp3-file-stream))
  205. (let ((frames (get-frames me '("TRD" "TDRC"))))
  206. (when frames
  207. (assert (= 1 (length frames)) () "There can be only one year tag")
  208. (return-from year (info (first frames)))))
  209. (if (v21-tag-header (id3-header me))
  210. (year (v21-tag-header (id3-header me)))
  211. nil))
  212. (defmethod title ((me mp3-file-stream))
  213. (let ((frames (get-frames me '("TT2" "TIT2"))))
  214. (when frames
  215. (assert (= 1 (length frames)) () "There can be only one title tag")
  216. (return-from title (info (first frames)))))
  217. (if (v21-tag-header (id3-header me))
  218. (title (v21-tag-header (id3-header me)))
  219. nil))
  220. (defmethod genre ((me mp3-file-stream))
  221. (let ((frames (get-frames me '("TCO" "TCON"))))
  222. (when frames
  223. (when (> (length frames) 1)
  224. (warn-user "file ~a has more than one genre frame, will only use the first" (stream-filename me)))
  225. (let ((count)
  226. (end)
  227. (str (info (first frames))))
  228. ;; XXX for V23/V24 TCON frames, a genre can be pretty gnarly.
  229. ;; if the first byte of the TCON INFO field is a '(', what is between this '('
  230. ;; and the next ')' is interpreted as an ID3v2.1 genre number.
  231. ;; These can stack up (called "refinements") too.
  232. ;; The INFO field can also just be a string.
  233. ;; We're taking a simplistic approach here: we can handle the '(' case, but
  234. ;; only allow one (no refinements) OR we can handle the simple string case
  235. (when (and (>= (length str) 1) (eq #\( (aref str 0)))
  236. (setf count (count #\( str))
  237. (when (> count 1) (warn-user "Don't support genre refinement yet, found ~d genres" count))
  238. (setf end (position #\) str))
  239. (when (null end) (warn-user "Bad format for genre, ending paren is missing"))
  240. (setf str (get-id3v1-genre (parse-integer (subseq str 1 end)))))
  241. (return-from genre str))))
  242. (if (v21-tag-header (id3-header me))
  243. (get-id3v1-genre (genre (v21-tag-header (id3-header me))))
  244. nil))
  245. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; no V2.1 tags for any of these ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  246. (defmethod album-artist ((me mp3-file-stream))
  247. (let ((frames (get-frames me '("TP2" "TPE2"))))
  248. (when frames
  249. (assert (= 1 (length frames)) () "There can be only one album-artist tag")
  250. (return-from album-artist (info (first frames)))))
  251. nil)
  252. (defmethod composer ((me mp3-file-stream))
  253. (let ((frames (get-frames me '("TCM" "TCOM"))))
  254. (when frames
  255. (assert (= 1 (length frames)) () "There can be only one composer tag")
  256. (return-from composer (info (first frames)))))
  257. nil)
  258. (defmethod copyright ((me mp3-file-stream))
  259. (let ((frames (get-frames me '("TCR" "TCOP"))))
  260. (when frames
  261. (assert (= 1 (length frames)) () "There can be only one copyright tag")
  262. (return-from copyright (info (first frames)))))
  263. nil)
  264. (defmethod encoder ((me mp3-file-stream))
  265. (let ((frames (get-frames me '("TEN" "TENC"))))
  266. (when frames
  267. (assert (= 1 (length frames)) () "There can be only one encoder tag")
  268. (return-from encoder (info (first frames)))))
  269. nil)
  270. (defmethod groups ((me mp3-file-stream))
  271. (let ((frames (get-frames me '("TT1" "TTE1"))))
  272. (when frames
  273. (assert (= 1 (length frames)) () "There can be only one group tag")
  274. (return-from groups (info (first frames)))))
  275. nil)
  276. (defmethod lyrics ((me mp3-file-stream))
  277. (let ((frames (get-frames me '("ULT" "USLT"))))
  278. (when frames
  279. (assert (= 1 (length frames)) () "There can be only one lyrics tag")
  280. (return-from lyrics (val (first frames)))))
  281. nil)
  282. (defmethod writer ((me mp3-file-stream))
  283. (let ((frames (get-frames me '("TCM" "TCOM"))))
  284. (when frames
  285. (assert (= 1 (length frames)) () "There can be only one composer tag")
  286. (return-from writer (info (first frames)))))
  287. nil)
  288. (defmethod compilation ((me mp3-file-stream))
  289. (let ((frames (get-frames me '("TCMP"))))
  290. (when frames
  291. (assert (= 1 (length frames)) () "There can be only one compilation tag")
  292. (let ((str (info (first frames))))
  293. (return-from compilation (if str 1 0)))))
  294. nil)
  295. (defmethod disk ((me mp3-file-stream))
  296. (let ((frames (get-frames me '("TPA" "TPOS"))))
  297. (when frames
  298. (assert (= 1 (length frames)) () "There can be only one disk number tag")
  299. (return-from disk (mk-lst (info (first frames))))))
  300. nil)
  301. (defmethod tempo ((me mp3-file-stream))
  302. (let ((frames (get-frames me '("TBP" "TBPM"))))
  303. (when frames
  304. (assert (= 1 (length frames)) () "There can be only one tempo tag")
  305. (return-from tempo (info (first frames)))))
  306. nil)
  307. (defun mk-lst (str)
  308. (let ((pos (position #\/ str)))
  309. (if (null pos)
  310. (list str)
  311. (list (subseq str 0 pos) (subseq str (+ 1 pos))))))
  312. (defmethod track ((me mp3-file-stream))
  313. (let ((frames (get-frames me '("TRK" "TRCK"))))
  314. (when frames
  315. (assert (= 1 (length frames)) () "There can be only one track number tag")
  316. (return-from track (mk-lst (info (first frames))))))
  317. nil)
  318. (defmethod show-tags ((me mp3-file-stream) &key (raw nil))
  319. "Show the tags for an mp3-file. If RAW is non-nil, dump all the frames; else, print out a subset."
  320. (if raw
  321. (format t "~a~%~a~%" (stream-filename me)
  322. (with-output-to-string (s)
  323. (when (audio-info me)
  324. (mpeg::vpprint (audio-info me) s)
  325. (format s "~%"))
  326. (vpprint (id3-header me) s)))
  327. (let ((album (album me))
  328. (album-artist (album-artist me))
  329. (artist (artist me))
  330. (comment (comment me))
  331. (compilation (compilation me))
  332. (composer (composer me))
  333. (copyright (copyright me))
  334. (disk (disk me))
  335. (encoder (encoder me))
  336. (genre (genre me))
  337. (groups (groups me))
  338. (lyrics (lyrics me))
  339. (tempo (tempo me))
  340. (title (title me))
  341. (track (track me))
  342. (writer (writer me))
  343. (year (year me)))
  344. (format t "~a~%~a~%" (stream-filename me)
  345. (if (audio-info me)
  346. (mpeg::vpprint (audio-info me) nil) ""))
  347. (when album (format t "~4talbum: ~a~%" album))
  348. (when album-artist (format t "~4talbum-artist: ~a~%" album-artist))
  349. (when artist (format t "~4tartist: ~a~%" artist))
  350. (when comment (format t "~4tcomment: ~a~%" comment))
  351. (when compilation (format t "~4tcompilation: ~a~%" compilation))
  352. (when composer (format t "~4tcomposer: ~a~%" composer))
  353. (when copyright (format t "~4tcopyright: ~a~%" copyright))
  354. (when disk (format t "~4tdisk: ~a~%" disk))
  355. (when encoder (format t "~4tencoder: ~a~%" encoder))
  356. (when genre (format t "~4tgenre: ~a~%" genre))
  357. (when groups (format t "~4tgroups: ~a~%" groups))
  358. (when lyrics (format t "~4tlyrics: ~a~%" lyrics))
  359. (when tempo (format t "~4ttempo: ~a~%" tempo))
  360. (when title (format t "~4ttitle: ~a~%" title))
  361. (when track (format t "~4ttrack: ~a~%" track))
  362. (when writer (format t "~4twriter: ~a~%" writer))
  363. (when year (format t "~4tyear: ~a~%" year)))))