CDDBサーバーに検索クエリーを投げる

in  Clojure

WAVファイルからCDDBへ検索クエリーを投げるプログラムを作ってみました。 色々試しながらプログラミングするには、EmacsでClojureな環境はホントいいですね。

CDDBへクエリーを投げる方法はここを参考にさせてもらいました。

CDDBへクエリーを投げるには曲長を基に算出したdiscIdが必要になります。 さらに各曲のフレームオフセット数も必要です。フレーム数とは1秒辺り75フレームとなる値になります。 フレームオフセットとは、各曲が先頭から何フレーム目から開始されるかを表します。 これらを算出するためにjavax.sound.sampled.AudioSystemクラスを利用しました。 こんなクラスがあったんですね。 曲長ををだして、1秒辺りのフレーム数75をかけています。

1
2
3
4
5
;; オーディオファイルのフレーム数を出す
(defn get-frame [file]
  (let [audio (javax.sound.sampled.AudioSystem/getAudioFileFormat file)]
    (* (/ (.getFrameLength audio)
          (.getFrameRate (.getFormat audio)))  75)))

まずdiscIdを算出します。 曲長を秒で算出して、桁毎に足しあわせます。 例えば185秒の曲長だったら、1+8+5=14 という具合です。 これをアルバムの全曲に対して行なって、足しあわせたあとに255で割ったあまりをだします。(A)

次に全曲長を算出します。(B)

最後に曲数を算出します。(C)

これらをビット演算します。 A « 24 | B « 8 | C

discIDを算出するClojureのコードです。HelloweenのTime Of The OathのCDをwavファイルに落としているものをCDDBへ問い合わせています。 拡張子wavのWAVファイルがトラックナンバーをファイル名として保存されている前提です。 例えば1曲目はTrack1.wavというファイル名で保存されている前提です。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
;; 1曲目のオフセット
(def init-offset 187)

;; .wavファイルでフィルター
(def files (filter #(re-find #".*wav$" (.getName %))
                   (file-seq (clojure.java.io/file "/home/takeshi/scorpions_eye2eye/"))))

;; Track numberでソートする
(defn sorted-files [file]
  (sort-by #(Integer. (re-find #"d+" (.getName %))) file))


;; フレームオフセットを計算する
(defn get-frame-offset [seq]
  (letfn [
          (sum [seq sum-num sum-seq]
          (if (= (count seq) 1)
            (sort sum-seq)
            (let [res (+ (first seq) sum-num)]
              (sum (rest seq) res (cons res sum-seq)))))]
  (sum (cons init-offset seq) 0 '())))



;; フレーム数から10進数の桁毎に足した合計を算出する
(defn sum-frame [frame]
  (letfn [
        (digit  [div-frame sum]
          (let [int-div-frame (bigint div-frame)]
            (if (< div-frame 1)
              (math/floor  sum)
              (digit (/ int-div-frame 10.0) (+ sum (- int-div-frame (* (math/floor (/ int-div-frame 10.0)) 10)) ) ))))]
    (digit frame 0)))

;;
(def A
  (mod
   (reduce + (map #(sum-frame (/ % 75))
                  (get-frame-offset (map #(get-frame %) (sorted-files files))))) 0xff))

;; 曲長
(def B
  (reduce + (map #(/ (BigDecimal. (.getFrameLength %))
         (.getFrameRate (.getFormat %))) (map #(javax.sound.sampled.AudioSystem/getAudioFileFormat %) files))))

;; 曲数
(def C
(count files))


;; discid
(def discid
  (Long/toHexString (bit-or (bit-shift-left (long A) 24) (bit-shift-left (long B) 8) C)))

フレームオフセットを+でつなぎあわせた文字列を生成します。 ;; オフセット

1
2
(def offset-string
  (clojure.string/join "+" (map long (get-frame-offset (map #(get-frame %) (sorted-files files))))))

discId、フレームオフセット、全曲長、曲数をつなぎあわせて、CDDBへの検索クエリーを生成します。

1
2
(with-open [rdr (clojure.java.io/reader (str "http://freedb.freedb.org/~cddb/cddb.cgi?cmd=cddb+query+" (clojure.string/join "+" (list discid C offset-string (int B))) "&hello=username+hostname+clripper+R0.1&proto=6"))]
  (prn "%sn" (str "n" (line-seq rdr))))

クエリーの結果はこんなかんじです。

1
"%sn" "n("200 rock d510710e Helloween / The Time Of The Oath [Japan Bonus Tracks]")"

ちゃんとした実行形式として作れてないですが、CDDBを検索するときに何をやっているかよくわかりました。 疑問点としては1曲目のオフセットが150で固定にしていますが、187のアルバムもあるようです。 一度wavファイルに落としちゃうと、算出は難しいようです。


Share