flac-frame.lisp 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180
  1. ;;; -*- Mode: Lisp; show-trailing-whitespace: t; Base: 10; indent-tabs: nil; Syntax: ANSI-Common-Lisp; Package: FLAC-FRAME; -*-
  2. ;;; Copyright (c) 2013, Mark VandenBrink. All rights reserved.
  3. (in-package #:flac-frame)
  4. ;;; FLAC header types
  5. (defconstant +metadata-streaminfo+ 0)
  6. (defconstant +metadata-padding+ 1)
  7. (defconstant +metadata-application+ 2)
  8. (defconstant +metadata-seektable+ 3)
  9. (defconstant +metadata-comment+ 4)
  10. (defconstant +metadata-cuesheet+ 5)
  11. (defconstant +metadata-picture+ 6)
  12. (defclass flac-header ()
  13. ((pos :accessor pos :initarg :pos
  14. :documentation "file location of this flac header")
  15. (last-bit :accessor last-bit :initarg :last-bit
  16. :documentation "if set, this is the last flac header in file")
  17. (header-type :accessor header-type :initarg :header-type
  18. :documentation "one of the flac header types above")
  19. (header-len :accessor header-len :initarg :header-len
  20. :documentation "how long the info associated w/header is"))
  21. (:documentation "Representation of FLAC stream header"))
  22. (defmacro with-flac-slots ((instance) &body body)
  23. `(with-slots (pos last-bit header-type header-len) ,instance
  24. ,@body))
  25. (defmethod vpprint ((me flac-header) stream)
  26. (with-flac-slots (me)
  27. (format stream "pos = ~:d, last-bit = ~b, header-type = ~d, length = ~:d"
  28. pos
  29. last-bit
  30. header-type
  31. header-len)))
  32. (defun is-valid-flac-file (flac-file)
  33. "Make sure this is a FLAC file. Look for FLAC header at begining"
  34. (declare #.utils:*standard-optimize-settings*)
  35. (stream-seek flac-file 0 :start)
  36. (let ((valid nil))
  37. (when (> (stream-size flac-file) 4)
  38. (let ((hdr (stream-read-iso-string flac-file 4)))
  39. (setf valid (string= "fLaC" hdr))))
  40. (stream-seek flac-file 0 :start)
  41. valid))
  42. (defun make-flac-header (stream)
  43. "Make a flac header from current position in stream"
  44. (declare #.utils:*standard-optimize-settings*)
  45. (let* ((header (stream-read-u32 stream))
  46. (flac-header (make-instance 'flac-header
  47. :pos (- (stream-seek stream) 4)
  48. :last-bit (utils:get-bitfield header 31 1)
  49. :header-type (utils:get-bitfield header 30 7)
  50. :header-len (utils:get-bitfield header 23 24))))
  51. flac-header))
  52. (defparameter *flac-tag-pattern*
  53. "(^[a-zA-Z]+)=(.*$)" "regex used to parse FLAC/ORBIS comments")
  54. (defclass flac-tags ()
  55. ((vendor-str :accessor vendor-str :initarg :vendor-str :initform nil)
  56. (comments :accessor comments :initarg :comments :initform nil)
  57. (tags :accessor tags :initform (make-hash-table :test 'equal))))
  58. (defmethod flac-add-tag ((me flac-tags) new-tag new-val)
  59. (declare #.utils:*standard-optimize-settings*)
  60. (let ((l-new-tag (string-downcase new-tag)))
  61. (setf (gethash l-new-tag (tags me)) new-val)))
  62. (defmethod flac-get-tag ((me flac-tags) key)
  63. (declare #.utils:*standard-optimize-settings*)
  64. (gethash (string-downcase key) (tags me)))
  65. (defun flac-get-tags (stream)
  66. "Loop through file and find all comment tags."
  67. (declare #.utils:*standard-optimize-settings*)
  68. (let* ((tags (make-instance 'flac-tags))
  69. (vendor-len (stream-read-u32 stream :endian :big-endian))
  70. (vendor-str (stream-read-utf-8-string stream vendor-len))
  71. (lst-len (stream-read-u32 stream :endian :big-endian)))
  72. (setf (vendor-str tags) vendor-str)
  73. (dotimes (i lst-len)
  74. (let* ((comment-len (stream-read-u32 stream :endian :big-endian))
  75. (comment (stream-read-utf-8-string stream comment-len)))
  76. (push comment (comments tags))
  77. (optima:match comment ((optima.ppcre:ppcre *flac-tag-pattern* tag value)
  78. (flac-add-tag tags tag value)))))
  79. (setf (comments tags) (nreverse (comments tags)))
  80. tags))
  81. (defclass flac-file ()
  82. ((filename :accessor filename :initform nil :initarg :filename
  83. :documentation "filename that was parsed")
  84. (flac-headers :accessor flac-headers :initform nil
  85. :documentation "holds all the flac headers in file")
  86. (audio-info :accessor audio-info :initform nil
  87. :documentation "parsed audio info")
  88. (flac-tags :accessor flac-tags :initform nil
  89. :documentation "parsed comment tags."))
  90. (:documentation "Stream for parsing flac files"))
  91. (defun parse-audio-file (instream &optional (get-audio-info nil))
  92. "Loop through file and find all FLAC headers. If we find comment or audio-info
  93. headers, go ahead and parse them too."
  94. (declare #.utils:*standard-optimize-settings*)
  95. (declare (ignore get-audio-info)) ; audio info comes for "free"
  96. (stream-seek instream 4 :start)
  97. (let ((parsed-info (make-instance 'flac-file
  98. :filename (stream-filename instream))))
  99. (let (headers)
  100. (loop for h = (make-flac-header instream)
  101. then (make-flac-header instream) do
  102. (push h headers)
  103. (cond
  104. ((= +metadata-comment+ (header-type h))
  105. (setf (flac-tags parsed-info) (flac-get-tags instream)))
  106. ((= +metadata-streaminfo+ (header-type h))
  107. (setf (audio-info parsed-info) (get-flac-audio-info instream)))
  108. (t (stream-seek instream (header-len h) :current)))
  109. (when (not (zerop (last-bit h)))
  110. (return)))
  111. (setf (flac-headers parsed-info) (nreverse headers)))
  112. parsed-info))
  113. (defclass flac-audio-properties ()
  114. ((min-block-size :accessor min-block-size :initarg :min-block-size :initform 0)
  115. (max-block-size :accessor max-block-size :initarg :max-block-size :initform 0)
  116. (min-frame-size :accessor min-frame-size :initarg :min-frame-size :initform 0)
  117. (max-frame-size :accessor max-frame-size :initarg :max-frame-size :initform 0)
  118. (sample-rate :accessor sample-rate :initarg :sample-rate :initform 0)
  119. (num-channels :accessor num-channels :initarg :num-channels :initform 0)
  120. (bits-per-sample :accessor bits-per-sample :initarg :bits-per-sample :initform 0)
  121. (total-samples :accessor total-samples :initarg :total-samples :initform 0)
  122. (md5-sig :accessor md5-sig :initarg :md5-sig :initform 0))
  123. (:documentation "FLAC audio file properties"))
  124. (defmethod vpprint ((me flac-audio-properties) stream)
  125. (format stream
  126. "min/max block size: ~:d/~:d; min/max frame size: ~:d/~:d; sample rate: ~d Hz; # channels: ~d; bps: ~:d; total-samples: ~:d; sig: ~x"
  127. (min-block-size me) (max-block-size me)
  128. (min-frame-size me) (max-frame-size me)
  129. (sample-rate me) (num-channels me) (bits-per-sample me)
  130. (total-samples me) (md5-sig me)))
  131. (defun get-flac-audio-info (flac-stream)
  132. "Read in the the audio properties from current file position."
  133. (declare #.utils:*standard-optimize-settings*)
  134. (let ((info (make-instance 'flac-audio-properties)))
  135. (setf (min-block-size info) (stream-read-u16 flac-stream)
  136. (max-block-size info) (stream-read-u16 flac-stream)
  137. (min-frame-size info) (stream-read-u24 flac-stream)
  138. (max-frame-size info) (stream-read-u24 flac-stream))
  139. (let* ((int1 (stream-read-u32 flac-stream))
  140. (int2 (stream-read-u32 flac-stream)))
  141. (setf (total-samples info) (logior (ash (get-bitfield int1 3 4) -32) int2)
  142. (bits-per-sample info) (1+ (get-bitfield int1 8 5))
  143. (num-channels info) (1+ (get-bitfield int1 11 3))
  144. (sample-rate info) (get-bitfield int1 31 20)
  145. (md5-sig info) (stream-read-u128 flac-stream)))
  146. info))
  147. (defun flac-show-raw-tag (flac-file-stream out-stream)
  148. "Spit out the raw form of comments we found"
  149. (declare #.utils:*standard-optimize-settings*)
  150. (format out-stream "Vendor string: <~a>~%" (vendor-str (flac-tags flac-file-stream)))
  151. (dotimes (i (length (comments (flac-tags flac-file-stream))))
  152. (format out-stream "~4t[~d]: <~a>~%" i (nth i (comments (flac-tags flac-file-stream))))))