Archive for the ‘gmusicbrowser’ Category

(身から出た錆?に追われて疲れた話: 僕しか使っていないソフトの話で普遍性がないが、なるべく一般的に書く。)

数日前、PCを再起動した後に、Linux用音楽プレーヤーgmusicbrowser(GMB, 「僕版」)の再生履歴を自作の音楽再生履歴・感想記録システムMlhiのwebで見たら、アルバム画像がおかしいのに気付いた。別のアルバムの画像が表示されていた。実はこれは今年の3月頃にも起こっていて、GMBの曲の番号(ID)が詰められた(昔のBASICのリナンバーみたいなもの)ため、MlhiのDBに登録されたIDとずれて、別の曲のアルバム画像がGMBから取得・表示されることが分かったので、その時はIDを詰める処理をスキップするようにして解決したと思っていた。

僕としては、ユニーク(重複しない)かつ不変であるべきIDを詰める(変える)なんて意図が理解できない※。詰める理由はライブラリ内の曲が削除されたからだと想像しているのだが、IDは整数で、今は少なくとも32ビット(64ビットだって行けるだろう)だから、少なくとも2000万、あるいは4000万曲には割り当てられるから、使い放題だ。だから、曲を削除してもIDを詰める必要は全くない。にも関わらず、なぜそんな危険で無駄なことをするのだろうか。まあ、作者の思想とか気の迷いなのだろう。

※こういうのが、いわゆる「想定外」問題だ。「普通はないよね」って思うことは、実はいくらでもあるのだ。普通なんて幻想だ。そして、全部「想定外」と言えば何でも許される訳ではなく、どこまで想定したかがポイントだ。偉い人たちは、そこが分かってない(、というかうまく逃げているのだろう)。

それから、そういう事態になってもなるべく破綻しないように作るのも、設計・開発者のセンスとか能力だ。

それに、曲の削除にしたって、個人で何千万曲も入れる訳じゃないから多少無効な曲があったって、(今もそうしているように)フラグだけ設定してエントリは削除せず、残しておいてもいい(それが気になるなら、DBの構造をちゃんとすべきなのだ)。

これに似た問題として、マイナンバーの番号不足を想像する。(以前書いたが、)僕の計算では遅かれ早かれ足りなくなると思うのだが、いつかリナンバーするのか、あるいは、また作り直すのか。

のだが、やっぱり再発した。こういう根深い問題は大抵再発して、なかなか直らないと相場が決まっているので、「やっぱり」と思うとともに結構がっかりした。

IDを詰めないようにしたつもりなのに、なぜかまだ詰めるようだ。もちろん、作者は行方不明で聞くことはできず、直してももらえないので、自分で解決するしかない。そして、3月に(いや、それ以前から)思ったが、GMBは超大嫌いなPerlで書かれているうえに、内部構造が妙に凝っていて理解不能なので、根本的に直すことができない。

仕方ないので、MlhiがGMBの曲を指定するのにGMBのIDでなく、別の新しいIDを使うことにした。結構大改造なので、なるべく避けたかった。まあ、そもそも、GMBのIDの仕様を確認もせずに勝手に(ユニークかつ)不変と思い込んで使った僕が悪いので、全く身から出た錆ではあるが、なかなか大変だった。

大改造だけど、なるべく変更を少なくしたかった。そこで、MlhiのDBの構造は変えず、GMBのIDを入れていたプレーヤー固有のトラックIDを格納するフィールド(カラム)に格納する内容を変えることにした。GMBのIDの代わりに、曲の音響的な特徴から生成したID(今までもMlhiのトラックID(メインのキー・ID)として使っていたもので、AcoustIDのfpcalcコマンドで計算している)を格納するようにした。GMBのIDと新しいIDはプリフィクスで識別できるようにして、同じフィールドに混在可能にした。なお、この新しいIDは、当然ながらGMBのDBにも登録し(フィールドを追加した)、GMBのIDがいくら変わっても(その新しいIDで)同じ曲が検索できるようにした。

GMBに曲の固有ID(utid)を追加した("Track ID"の列)。まだ再生していない曲("Today's girl")では空だが、再生すると自動で設定される(「ザ・ベスト」)。"ID"は従来のGMBのID(固有でない)。

また、そのようなDBの中身の変更をバッチ処理(SQL)で行うのは失敗のリスクがあったので※、使いながら自動的に変更するようにした。つまり、GMBでの曲の再生時(Mlhiへの履歴の登録時)に、既存の曲のプレーヤー固有のトラックIDのフィールドに昔のID(GMBのID)が入っていたら、新しいID(曲の特徴から生成したID)に入れ替えるのである。

※実際、MlhiのDBは確認のためにしかツールで開いていなかったのに、いつの間にかDBのエントリが一個空になっていた・・・ 間違って何か押したか、修正中に(あるいはもっと前に?)バグが出たのかは不明だ。 ← バックアップを調べたら、変更版を試している時に誤動作して消えたようだ。

なお、曲の特徴からIDを生成するのは、今まではMlhiに履歴を登録するプログラム(登録プログラム)で行っていたが、そのIDをGMBにも格納するようになったので、GMBで生成して登録プログラムに渡すようにした。

余談だが、PerlからID生成プログラムを実行する時、曲のファイル名に日本語が入っていると文字化けして、どうしても直らなかった。ごく当たり前のことすら普通にできないのでブチ切れそうになったが、仕方ないので、Base64でエンコードしてプログラムに渡すようにした。

もう、とにかくPerlなんて1文字だって見たくない!! 何もかもオカシくぐちゃぐちゃで※、見るだけでイライラが溜まる。Perlだったら断然Pythonの方がいい(でも、僕にはPHPが一番だ)w 良くあんなものでまともなプログラムが書けるものだ。そこには感心する。

※"my"や"our"なんてセンスが悪いし、関数(サブルーチン)の引数は(,)で囲い区切ってってもそうでなくてもいいなんて見難いし、$とか@とか%とか()とか[]とか{}とか=~とか\とか、とにかく記号が多過ぎて(しかも、それらの複合もある)、「いい加減にしろ!」だ。

GMBの場合の処理の流れの概要は、以下のようになる(Spotifyでは曲の固有IDがISRCになっている以外は概ね同様)。

曲の再生開始

GMB

  1. 開始から指定時間(例: 3秒)後に、以下を実行する(すぐにスキップした場合は登録しないようにしたかったため)。
  2. その曲に固有ID(utid)が設定されていなければ、音響的特徴から生成して、GMBの曲DBのutidフィールドに格納する。+
  3. Mlhiに再生開始を通知する(utidを指定)。

Mlhi(履歴登録プログラム)

  1. 指定された曲が以前再生したことのあるか(指定されたutidがトラックIDのエントリがDBに格納されているか)調べ、なければ(初めて再生する曲)そのエントリを作成する。その時、GMBから曲の情報(例: タイトル、アーティスト、アルバム)を取得する(utidで曲を指定する)。
  2. 以前再生したことのある曲の場合、DB内のプレーヤー固有のトラックIDがutidでなかったら、指定されたutidに置換する。+
  3. 再生開始日時をDBに記録する。また、再生回数を+1する。

曲の再生終了

GMB

  • Mlhiに再生終了を通知(utidを指定)する。併せて完奏率も送る。

Mlhi(履歴登録プログラム)

  • 再生終了日時と完奏率をDBに記録(追加)する。完奏率は、それまでの完奏率と合わせる処理をする(履歴表示時に平均の完奏率を求めるため)。

曲の再生履歴の表示

Mlhi(web)

  1. DBから再生履歴を抽出し、GMBの曲の場合には、アルバム画像のファイル名や曲の長さなど(履歴には関係なく自明なため)DBには格納していないが、履歴表示にはあった方がいい追加情報をGMBから取得する(utidで曲を指定する)。
  2. その他の情報(例: コメント、評価)をDBから抽出し、履歴と共に成形して表示する(→ )。

※今回追加した処理は項目の最後に"+"を付けて示した。

 

変更を少なくしようとはしたけれど(実際、処理の流れの本筋は何も変わっていない)、やっぱり結構多くなり、昔作ったのですっかり忘れていたこともあって大変で、4日くらい掛かった(ている)。その間は、例によって、気付くと一日が終わっていたり、食事中にもバグが見付かったりして、全く気が休まらなかった。更に、昔の誤り(しかも、関係ないところにも!)も結構あって、「どんなものでもテキトーに作ってはいかん」と思い知らされた。。。

とは言え、何でもきっちり作っていたら、終わらずに使えなくなってしまう(機会を逃す)ので、そこの見極めが難しい。そこら辺もセンスや能力なのだろう。

そして、動作確認して基本的にはOK(何度も戻って修正した)だったのだが、そもそもの、GMBのIDのリナンバーが起こらず、本当に効果があるのかが確かめられない。曲を削除しても強制終了しても、なぜか起こらない。どうにも挙動不審なプログラムで、そういうのを使い続けるのもどうかとは思うのだが、他にいいものがないので仕方ない(いつかは自分で作り直したいが、無理だろうな)。

問題の現象が再現しないのでは仕方ないので、定期的にGMBのIDのリナンバーが起こったかどうかを検出するように定期実行機能(crontab)に設定しておいて、起こったら改めてアルバム画像を確かめることにした。

 

なお、僕しか使っていないものなので、他の方では直ったどうかも判然としないからキャプチャを載せても意味がないが、以下のように直った(単なる自己満足で載せるw)。

問題修正後のMlhiの再生履歴のアルバム画像

図中の"FCM5:"で始まるのが、曲の特徴から生成したIDである(それをGMBの新しいIDにも使っているので、"GMB:FCM5:-"と略記している)。また、"AIM5:"はアルバムのIDである。

 

PS. 当然ではあるが、曲の特徴から生成するIDは、同じ曲(ファイル)ならいつ何回生成しても同じ値になり(昔生成した値と今回生成した値が同じだった)、まだ重複していないので、ちょっと感心した。

PS2. 誰も気にしてないはと思うが、僕が好きなのは80年代のコイズミまたはkyon2であって、現代のダサい小泉さんではない。この投稿は妙なタイミングになったが、そこのところははっきりさせたいw (5/11 19:28)

 

(5/11 9:10 画像を追加、わずかに加筆・変更; 18:52 処理の流れの概要を追加、その他に加筆)

  •  1
  •  0

ようやく正式な名前を決めた、Music listening history and impressions "Mlhi" DB (音楽再生履歴と感想の記録・検索システム※)に、先日やる気が出てしまったgmusicbrowser(GMB)対応機能にを追加しようと作り出し、さっきようやく動き出したのだが、気付いたら一週間経っていたw

※日本語の名前は今考えたので、やけに長いうえにダサいw

まあ、見た目はほとんど変わらない(そういうふうに作ったので)。GMBで再生した曲は再生の都度DBに記録され、履歴webに出る。Spotifyと同様に完奏率が出るし、評価やコメントを付けることもできる(それ以外の細かいことはまだだが)。

GMB対応に伴うDBの構成変更の内容が確定していなかったのと、うっかりDBを壊すのが怖いので、今はテスト用のDBを使っているが、もう少ししたら本物に切り替えたい。

それから、Spotifyでもそうしたのだが、再生した曲のDBへの登録を外部プロセスでなく、GMB本体(Spotifyではミニプレーヤー)に組み込んだ。これもまだできたてで怖いのだが、外部プロセスの場合、再生開始してからDBに登録されるまで30秒近く掛かっていて、いつも「遅いなあ、まだかよ」と思っていたのが、瞬時に(数秒以内に)登録されるようになったのが大変気持ちいい。これなんだよ! (と、ひとりごつw)

以下に、少し実装の話を書く。

  • 以下のような流れでGMBの再生履歴をDBに登録する。
    1. GMBで曲の再生を開始する。
    2. GMBはDB登録プログラムにトラックIDを指定して、「再生開始の登録」として起動する。
    3. DB登録プログラムは、GMBからDbusで曲情報を取得して本システムのトラックIDを生成し、再生開始日時と再生回数をDBに登録(それぞれ、追記、加算)する。
    4. 最初に再生する曲の場合、主な曲情報(タイトル、アーティスト名など)と完奏率、評価、コメントなどもDBに登録(転記)する。
    5. その曲の再生が終了する。
    6. GMBはDB登録プログラムにトラックIDと完奏率を指定して「再生終了の登録」として起動する。
    7. DB登録プログラムは完奏率をDBに登録(加算)する。
  • GMBの曲のトラックIDは、MD5で音響指紋のハッシュを求め、それをBase64(base64url)で短くしたものにした。
    • 今は、仮にGMBの曲がSpotifyと同じものでも関連付かないが、将来は(ISRCへの変換・対応テーブルのようなものを作って、)同じ演奏を関連付けられるようにしたい。
  • 本システムでは複数種類のトラックID(例: ISRC, Spotify, GMB, 音響指紋)を混在して使うが、それらをプリフィクスで区別(URLのスキームのような感じ)するようにしている。
  • GMBでのジャケット画像は当然ながらローカルのファイルだが、近頃は(リモートの)webページと(ローカルの)file:スキームは混在して使えないようなので、画像ファイルをsym-linkで仮想的にwebサーバの配下に置き、リモートのファイルとして指定している。馬鹿らしいが、サーバの設定では対処できなかったので、そうした。GMBの演奏がSpotifyと関連付けられるようになれば、Spotifyの画像を参照することもできるが、それも馬鹿らしい。
    • なお、ジャケット画像が曲のファイルに埋め込まれている場合もあるが、それを表示するには、抽出して一時ファイルを作ることになって管理が煩雑になるので、現在は使っていない。
  • GMBは、内部に曲の再生開始と終了時に実行される関数があり、しかも、終了時に完奏率を計算してくれるので、事前に「面倒だ」と思っていたのが馬鹿らしいほど、手間が掛からずにさらっと組み込めた。
    • また、今までの再生履歴を記録してくれていたのもありがたい。ただ、今までにファイルを移動したりして履歴が消えてしまったものがあるのが残念だ。今後は本システムを使うことで、そういう問題を防げる。
  • DBには2つフィールド(カラム)を追加した。音楽プレーヤspecificなトラックIDとアルバムIDである(後者は念のために追加したが、まだ使っていない)。将来的には、現在あるSpotfify専用のトラックIDとアルバムIDを廃止して、こちらだけにしたい。

思わぬ利点は、今までは、GMBはSpotifyと違って評価が付けられない(実際にはできる)とか再生履歴が残らない(上記のとおり、実際には残る)とかコメントが書けない(実際には書ける)などの理由で(要は、見やすくないとか手軽に更新できないのが面倒だったようだ)、余りGMBで聴かなくなっていたのだが、これができてから、(テストの意味もあるが、)俄然使う気が起こったことだ。将来的には、SpotifyとGMBをシームレスに(シャフル)再生できそうな(したい)気がして来て、夢にはまったく限りがないw

BTTFで言えば、やっと落ち着いたと思っていたら、またドクが来て、「未来に道は要らない!」とか言われて引き戻されるようなものだw

あと、やけに長いコメントが入っている曲が見付かったりする(CD-DBに入っていたのが取り込み時にそのまま入ったのだろうが、今まで全然知らなかった。 ← 実際にはコメントに購入日を記載した時に気付いたはずだが、すっかり忘れていた)のは、おもしろいのはもちろん、今までは不便で活用できなかった情報が生かせそうな点もwebでの履歴表示のいいところだ。

これでもまだ83%といった感じだが、大分落ち着いてきた。でも、プログラム中に山ほど"TODO"があるので、なかなか先は長い。そういうのは普段は何もないのだが、突然牙を剥くので(早速、ここに載せる画像を追加する時に出たw)まったく油断できない。

 

(6/17 19:46 若干加筆・修正)

  •  0
  •  2

鋭意寄り道しながら開発中の音楽再生履歴記録・検索システム(仮)の横道の、gmusicbrowser(GMB)で再生した曲も履歴DBに入れようと四苦八苦準備している中で出たこぼれ話。

そもそもは、GMBで各演奏(録音, 曲)を区別する値「トラックID」は、音響指紋をハッシュ(ダイジェスト)処理で短くしたものを使う方針にした時、有名なハッシュ処理方式のMD5の結果が32文字と長いのが気に入らなくて、何とかしたいと思ったのが発端である。そんな、とても細かい話が書いてあるので、読まれる方はほとんど居ない気はするが、(いつものように)僕の整理のために書く。

まあ、そもそも、トラックIDは曲名などではないので、手で打ち込んだり目で見て何かする(読む)ことはないし、長いとはいっても音響指紋自体ではないので1つが数KBになる訳でもないから、それによってDBのサイズが肥大化することもないので、完全に好みの問題だ(有名な林檎のオジさんがやってそうな話かもw)。

なぜ"CRC48"がないのか?

はじめに目を付けたのはCRCである。ただ、僕が使っているPHPには32ビット(8文字)のCRC32しかなく、どうも心許ない気がした。具体的には、ID(曲数)が増えた時に重複(「衝突」)しそうな気がした。とは言え、僕はこの分野に詳しくないので、本当に32ビットで不十分なのかの確証はない(理論上は、32ビットでは約40億(232-1)通りの表現ができるが、重複(冗長性)を持たせているだろうから、現実的にはもっと少なそうだ。それでも、数万曲だったら完全に余裕だろう。ただ、ハッシュの表現範囲がどうやって決まるのかが分からないので、特定の使い方で範囲が狭くなるのなら安心できないと思った)。まあ、これも全く気分的な問題だ。

探すと、64ビットのCRC64(16文字)はあった。が、それは少し長く、僕としては12文字くらいのが欲しかった。それは"CRC48"というべきものなのだが、いくら探してもほとんど出て来ない。調べると、ハッシュ値を短くする方法が分かったので、それでCRC64を48ビットに短くできた。最初は単純にマスク(≒除算または剰余)して短くすればいいのかとは思ったのだが、詳しくないので、安直なことをしてハッシュの強度が下がる(→ 衝突しやすくなる)のが嫌なので、丹念に調べた。結局は普通にマスクしても問題なさそうだった。念入りにするには、長いハッシュを分割してXORする方法があった。

それにしても、CRC48がないのが不思議だ。Stack overflow(コンピュータ技術者のQ&Aサイト)でも、「結構要る」という意見もあった(UUIDに使うらしい)。全く不便だ。

音響指紋計算プログラムfpcalcの隠れオプション-hashの不思議

CRC48を探したり試行錯誤しているうちに、音響指紋の計算に使っているfpcalcに、探していたものそのものかも知れないオプションがあることが分かった。それが-hashで、検索してもほとんど出て来ないので、知っている人は少なそうだ。

これを指定すると、音響指紋の値に加えて、音響指紋のハッシュ値も出てくる。この値があまりにも妙なので、うなってしまった。この値を使うと、同じ演奏(録音)が分かる可能性がある。ただ、残念ながら、常に分かるとは限らない。

同じ演奏(録音)のハッシュ値は同じか近いことがあるが、全く違うこともある。全く同じファイルなら同じだが、リマスターや単にベスト盤に入っているだけで異なってしまうことがあった。逆に、リマスターでもオリジナル版でもモノラル版でもレコード版でも同じ値になったりもした。同じ演奏で値が違う場合も、最後の桁が1だけ違う場合もあったし、特定の桁が思わせぶりに違う場合もあった。以下にその例を示す。

  • "Bohemian Rhapsody": b04cb826 (リマスターでないベスト盤), 904cb826 (リマスターとそうでないオリジナルアルバム)
  • "Crazy Little Thing Called Love": d434a0ae (リマスターでないオリジナルアルバムと新しいベスト盤), d400a0ae (リマスターでないベスト盤), d42ca02e (リマスターのオリジナルアルバム)

謎の挙動だ。それぞれの桁に意味があるような気がして、それを突き詰めるのもおもしろそうだったが、いつも同じ動きをしないと不便なので(僕としては、同じ演奏ならすべて同じ値になるか、同じファイルでない限り、全部別になって欲しい)、使うのは見送った。そもそも、同じ演奏(録音)かどうかは音響指紋の間の「距離」で確率として表すとのことなので、ハッシュは簡易的に似た演奏を見分けるにはいいだろうが(-hashオプションを紹介しているページには、重複したファイルを減らす使い方が書いてあった)、IDとして使うのは良くなさそうだ。

SHA-3(というかWikipedia)に騙されたw

CRC48に落ち着き掛けたのだが、厳密に言えば、CRCはハッシュではないし、自分で勝手に短くするのが気に入らず、任意の長さで出せる標準的なハッシュ方式を探した。すると、SHA-3はそれができるとのことだったので、プログラムをダウンロードして48ビットで試してみたら、エラーになった。調べたら、最短ビット数(224ビット)が決まっていた。あと、任意でなく数とおりしか指定できなかった。任意の長さにできるというのは、確かWikipediaで見たのだが、鵜呑みにするのは良くないと実感した。今回はちゃんと確かめたから良かった。もしかしたら、プログラムを変更すればできたのかも知れないが、中身がチンプンカンプンだった。

MD5を短くする。

SHA-3は諦めたものの、「短いハッシュ」で調べているうちに、Stack overflowで目から鱗のアイデアを見付けた。Base64エンコーディングで文字数を減らすのだ。ハッシュ値は普通は、"10f280198c517c830fa302c4e1bcca7b"(MD5の例)のように16進数で書く。これは元々は数値なので、Base64で表せば短くできるのだ。簡単に言うと、16進数は0-9とa-fしか使わないが、Base64はもっと多くの文字を使うから1桁で示せる値が多いので、短く書ける(桁数が減る)。

MD5は128ビットと数が大きいので手こずったが、できた。元は32文字だったのが"EPKAGYxRfIMPowLE4bzKew"のように22文字で表わすことができ、10文字も短くできた。希望は12文字くらいだったが、(今はobsoleteになっているけど)実績あるハッシュ処理を使うことで、衝突の心配が皆無になるだろうから許せると思い、今に至る。これで、いつでもGMBで再生した曲をDBに登録できる(屏風の中から虎を出してくれたらそのプログラムを書きさえすればねw)。

なお、この方法でCRC64も12文字にできる(基本的には、数値なら何でも可)ので、途中まではそれを使おうとしていたが、CRC48を止めたのと同じ理由でMD5の方が良さそうだったので乗り換えた。

(6/11 6:09追記) ちょっと思い付いて調べたら、SpotifyのトラックIDも僕と同じ22文字だった(アルバムIDも同じ)。これもBase64みたいな表記なので(例: 7oYN5pKRoFiVLoDFK6R3O1)、元はMD5のような長さの値なのだろう。偶然の一致だけどおもしろい。ハッシュ値だとしたら、何の情報を元にしているのか興味あるところだ。そして、(これは想像だが、)ID数が数億になるとCRC64では不足するのかも知れない。もちろん、使い切るまで衝突しない訳ではないので、ある程度の余裕が要ると思う。その点で、MD5にしたのは正解だったのではないか。

それに引き換え、(前にも書いたが)マイナンバーは脆弱だ。個人の番号は10進数で12桁なので全く余裕がない。当てずっぽうな値でも有効になるだろうし、(おそらくありえないがw、)長く使われ続けると不足するので、将来は死んだ人の番号を使い回す羽目になると思う。素人でも分かるくらいのしょうもなさだ。そのうち、そういう問題の解消を名目にしてまた新しい仕組みが(利権の確保とともに)生まれるのだろう。役人連中は楽だね。

似たようなことはISRCにも言える。12桁と短いうえに、区分されていて使える数が少ないから、いつかは破綻するし、年を2桁で表記するからとても古い録音は扱えないし(例えば、"19"は1919年なのか2019年なのか? まあ、仕組みができてから録音されたものを登録するスタンスなのだろうが・・・)、2100年以降は使えない。始まったのは2000年以降と推測するが、だとしたら日本の役所並みにお粗末だ。

ちなみに、確認と評価のため、音響指紋のMD5でGMBに入っている全曲(約1.5万曲)のトラックIDを生成してみたところ、90組くらい同じIDが出たが、衝突ではなく、元が同じ演奏(例: 重複したファイル、タグを変えたものとオリジナル、(ごくまれに)収録アルバムが異なる同じ演奏)のためだったので、本来の目的が達成できていることが分かったし、問題なくIDに使えることも分かったので安心した。あと、大量の曲ファイルに重複がほとんどなかったのには、我ながら感心した。なお、音響指紋の計算(fpcalc)が意外に遅く、大量の曲の処理には結構時間が掛かった。

心なしか、ものすご〜く本題から外れている気がするし、ちょっと買い物に行くのにハマーとかモビルスーツに乗るようなくらいやり過ぎな気がしないでもないが、趣味だし、いろいろな知識が得られたので良しとしようw

(6/11 5:41, 6:09 わずかに加筆・修正、追記; 6/11 10:32 わずかに補足)

  •  0
  •  1

開発中の音楽再生履歴記録・検索システム(仮)、そこそこ使えているうえに、煮詰まっているというか、完成度が高くなってきたために(それでもまだ8割くらいか)、いくら作り・直してもキリがなくて(しかも、劇的にすごくはならない)疲れたので、ちょっと脇道に逸れて、先日書いたgmusicbrowser(以下GMB)との連携・統合の一部の、GMBの再生履歴を取って記録することを試してみた。

まあ、GMBで再生している曲情報を取ること自体は全く簡単で、Dbusインタフェースを使えば目をつぶってもできたくらいだが(実際には少し手こずった)w、その先が大変だ。

先日も書いたように、再生している曲・演奏・録音(以下「演奏」)が「何か」を特定(同定)することが難しい。なぜ同定が要るかというと、GMBでもSpotifyでも同じ演奏を再生したら、同じ演奏の履歴として記録したいのだ。片方で再生したらもう片方とは別扱いになるのでは全く無意味だ。

そして、上記のGMBから取れる曲情報は、曲名、アーティスト名、アルバム名などで、それだけあれば充分だと思われるだろうが、実際には不十分だ。例えば、以下のような問題がある。

  • 同じアーティストが同じ曲(だけど実体が異なる)を何度も演奏/録音/発売した場合(例: ライブ、再録、リミックス、シングル/アルバム版、リマスター)、それらの区別が付かない。: 例: John Lennon "Give peace a chance" (通常版と"Shaved fish"中の短縮版)
  • 同じ演奏だけど微妙に曲名などが異なる場合、同じ演奏と判定できない。: 例: "Somebody To Love"と"Somebody To Love - Remastered 2011"
    • 逆に、何度も演奏/録音/発売した場合に微妙に曲名を変えてくれると、1番目の問題が緩和できる。: 例: 「かもめが翔んだ日」と「かもめが翔んだ日(スタジアム・バージョン)」
    • 「ファジー判定」のような仕組みがあるといいのかも知れないが、今度は上のような微妙に曲名を変えてくれた別演奏が同じになってしまいそうだ。
    • CDをPCに取り込んだ時に使ったCDDBの曲情報と、Spotifyの情報が異なる場合もあるし、自分で記述を変更した場合もある。
    • 日本のアーティストでは、表記が日本語かローマ字かの違いも問題になる。

そこで、先日も書いたように、各演奏はISRCで区別し(ISRCのないものは別扱いにする)、ある演奏がどのISRCに対応するかを音響指紋で識別しようと考えた。音響指紋と演奏を照合するためのシステムは、AcoustIDとMusicBrainzを使うことにした(無料で使えるのはこれらしかないため)。

手順は以下である。

  1. GMBの演奏(ファイル)の音響指紋を計算する。: fpcalcコマンド
  2. その音響指紋をAcoustIDに送り、演奏に紐付けられたMusicBrainz ID(以下MBID)を得る。
  3. 得られたMBIDでMusicBrainzからISRCを得る。

全くシンプルで、すぐにでもできそうに見える。確かに、それほど時間が掛からずに作れた(でも疲れたw)。が、ちょっと試したらとんでもなかった。以下のような問題が噴出した。

  • ある音響指紋に対して、実際とは異なる演奏(候補)が出てくることが結構ある。: 確率的なものなので仕方ない。
  • AcoustIDに登録されていてもMBIDがない演奏が結構ある。: ボランティアベースだから仕方ない。。。
  • MBIDがあってもISRCがない演奏が結構ある。: ボランティアベースだから仕方ない。。。
  • 登録内容(例: 曲名)が誤っている演奏がある。: ボランティアベースだから仕方ない?? 気付いたら修正しろってこと?
  • MusicBrainzのアクセス制限(アクセス頻度)が厳しく(最大 平均1回/秒)、それを破らないように、間隔をおいて何度も検索すると時間が掛かる。: 無料だから仕方ない。。。
    • 素行の悪いアプリは公表されてアクセス制限されるようなので、なるべく規則を破らないようにしないといけない。
    • AcoustIDにも同様に制限があるが、まだ緩い(最大 平均3回/秒)。

まさに、「聞いてないよー!」の世界だw

それでいろいろな工夫をしたが、現時点では、完全な自動処理(演奏の同定)をするのは難しい感じだ。以下に、したことと起こった問題を書く。

  • (MusicBrainzに情報がない場合に、)曲情報(曲名、アーティスト名など)でSpotifyを検索する。演奏時間やリリース年などでフィルタリングを厳しくする。
    • 最初の問題(同名曲の区別ができない/微妙に違う名前の曲が同じと判定できない)が起こる。
    • ライブ版がアルバム版と認識されることがある(曲名などが同じで、たまたま演奏時間が近い場合)。
    • "Various Artists"が幅広くマッチしてしまう。 → 無視するようにした。
  • (MusicBrainzの誤情報に備えて、)MusicBrainzで得られたISRCでSpotifyを検索して正当性を確認する。
    • Spotifyにない演奏が結構ある。SpotifyにあってもISRCがない演奏もあるうえに、MusicBrainzでもSpotifyでも複数のISRCがある演奏もある。そもそも、曲情報の表記が異なることがあるので、判定すら難しい。

ちょっと見た感じでは、多くの(2/3以上?)普通の演奏では問題なく動いている(→ 出力の例)のだが、たまに間違うのはやっぱり気に入らない。

他の問題として、MusicBrainzではリマスターやレコードの演奏などが全部通常版(例: CD)と同じに区分されている(Spotifyは、基本的に最新の1種類しかない。リマスターがあればそれだけになるし、当然ながらレコード版はない)。これは、技術だけでなくポリシーの問題も絡むので、すぐにはどうすればいいかが決められない。リマスターやレコードを通常版と別扱いにしたいこともあるし、そうでない場合もあるのだ。

AcoustIDとMusicBrainzはイマイチなので使わなくてもいいのかと思ったが、そうではない。それ以外に有効な手段がないので、やっぱり音響指紋での識別をした方がいい。その点で、Spotifyが音響指紋を提供してくれると一番ありがたいが、そうも行かない。今のところは、(リアルタイムでの)完璧な同定は諦め、バッチ処理でGMBとISRCの対応表を作って、その結果を「見て」修正するのがいい感じだ。ただ、GMBには1.5万曲くらい入っているので何とも大変そうだし、時代遅れな気がする。

次善の策として、SpotifyにはなくてGMBでしか再生できない演奏についてだけ対応表を作って、その内容を確認することが考えられる。それなら2500曲以下と大分減るが、そういう演奏はISRCがないものも多いので、別の問題(ISRC以外の何で演奏を区別するか)がある・・・

できそうでできないのは、SpotifyのAPIの音楽分析アルゴリズム(演奏の「活発さ」や拍などが分析されて公開されている)でGMBの演奏を分析して音響指紋の代わりに照合に使うことだ。でも、SpotifyのAPIではローカルファイルの分析はできないと思うし、Spotifyのアルゴリズムは公開されていないので、自分で実装することもできない。

もう一つ、最後の手段としてあるのは、仕舞い込んだCDを引っ張りだして、その中に記録されているであろうISRCを取り込むことだ。これが一番確実ではあるが(こうなると分かっていたら、最初からやっていただろう)、ISRCが入ってないものもあるだろうし、レンタルのものはできないし、そもそも、面倒だからやりたくないw

てな訳で、またしても(普通の人には全くどうでもいい)泥沼にはまり込んだ今日この頃・・・

(6/9 7:15追記) 昨夜、寝る直前に思い付いたのだが、IDとして各演奏に固有の識別記号を割り当てたいので、曲ファイルのダイジェスト(ハッシュ)値(例: MD5)をIDに使ってみたい。ただし、普通にファイルのダイジェスト値を使うと、それはタグ(例: 曲名)を変更しただけでも変わってしまって不便(タグを変えるとファイルとIDが合わなくなってしまう)なので、音響指紋のダイジェスト値を使うことを考えた。

ファイル名などをテーブルに記録して、そこでの通し番号などでIDを割り当てることもできる(GMBの方式)が、ファイルを移動したり名前を変えたらテーブルを更新しなくてはならないから不便だ。それを解消するために、曲のファイル自体にIDを持たせたい。そのためには、ファイル内(例: タグ)にIDを記録するか、ファイル名にIDを付加するか、上記のようなファイルの特徴をIDにするのがいいだろう。

IDをファイル内に記録する場合、既存のファイルすべてに記録する必要があって面倒なうえに、内容が変わらないのにファイルが更新されるのは嫌だし、今後、ファイルを追加するたびに忘れずにIDを記録しなくてはならないので煩雑だ。

ファイル名にIDを付加する(デジカメ画像の管理で実施している)のは簡便だが、既存の多くのファイル名を変えるのは好ましくない。例えば、GMBへの曲の登録をし直す必要があって、現実的でない。また、ファイルを追加するたびにIDを追加しなくてはならない煩雑さもある。

ファイルの特徴をIDにするのであれば、ファイル自体には何もしなくていいうえに、ファイルの中身を変更しない限り、あらかじめ決めた手順(計算)によっていつでも同じIDが得られるから便利だ。

音響指紋は、その名のとおり、(ファイルに格納された)音の特徴(だけ)に基づいているので、タグは無関係なはずだ。実際に試してみたら、本当に、タグを変えても音響指紋とそのダイジェスト値は変わらなかった(当たり前のことだが、結構うれしかった)。なお、当然ながら、異なる演奏は別の(音響指紋・)ダイジェスト値になった。ただし、同じ演奏でも、通常(CD)版とリマスター版やレコード版の音響指紋は異なったので、結構敏感なようだ。

この方式なら、IDはファイル(の中身の音)に固有だし、生成にも再現性がある(同じ音のファイルに対してはいつでも同じ値が生成できる)。ただ、音質や雑音などの条件が異なると「同じ演奏」でも同じIDにはならないが、そこは諦める(そもそも、GMBの中では重複した演奏(ファイル)は極力排除しているから、大きな問題ではないと思う。あるとすれば、リマスター版やレコードをオリジナル(CD)と同じと扱うかどうかの類だ)。

逆に、ダイジェスト値に衝突(偶然の一致、重複)が生じて、別の演奏に同じIDが割り当たる可能性はある。ただ、広く使われている方式でも(ハッキングを除いて)そういう問題が起こったという話は聞かないので、確率は低そうだ(また、音響指紋のデータは短いので、指紋の小さな変化でもダイジェスト値に反映されるから衝突は起こりにくそうだと想像するが、確証はない)。万が一衝突した場合は、(どうにかしてそれが検出できたとすれば、)IDに子番号を追加すればいいのではないかと思っている(と書いたものの、IDをテーブルなどで一元管理しないので、IDを生成した時点では衝突は分からなそうだ・・・ が、下に書いた変換テーブルで対応できるかも知れない)。

また、ISRCへの変換は変換テーブルで行う。その時に、重複した同じ演奏のIDに一つのISRCを割り当てることで集約できそうだ。

再生履歴DBでは、当初は、同じ演奏であっても、GMBのもの(音響指紋からIDを生成)とSpotifyのもの(ISRCからIDを生成)とは別のものとして記録するが、上記ISRCへの変換テーブルができたら、履歴などの情報を自動的にマージ(統合)したい(実際には、記録されたデータ自体は変更せず、DBの機能を使って統合されているように見せたい)。

と、またしても夢は膨らむのであったw (そして、例によって落とし穴が待ち構えている?)

(21:06 わずかに加筆・修正; 21:19 題の誤りを修正; 6/9 7:15 追記)

  •  1
  •  0

[10/1 8:18 この問題は解決した。原因と対処を最後に示す。]

面倒なことは、いつも突然やって来る。昨日、ふと、SpotifyとGMB(gmusicbrowser)を切り替えると音量が違うのが気になっていたので、それを可能な限り合わせたくなって、テスト信号を作って再生したら、思わぬことに気付いてしまった。

MP3のスイープ信号を再生したら、GMBからの音がおかしかったのだ。特定の周波数(例: 5.5kHz)が小さくなった。以下に問題の波形(問題の起こった5.5kHzの正弦波の再生波形の比較)を示す。上は問題がある場合、下は問題のない場合である(左チャネルだけを抜粋した)。なぜか変調が掛かっている感じで、音量が小刻みに振動している。そのために音が濁って聞こえる。

興味から、この変調波の周波数を調べたいのだが、普通にスペクトルを出しても出ず、なぜかLPFでも抽出できない(LFP後のスペクトルが出ない)。目視では24Hz程度と分かるが、なぜLPFで取れないのだろうか。振幅がとても小さいのか(そうなら、こんなに振れない気がする)。まだまだ知識が足らないようだ。。。

↑ 分かった! スペクトルを拡大したら、5.5kHz付近に2つの山があった。それらが干渉していたのだ。2つの差は約25Hzで、上記の目視と一致した。でも、なぜこういうことが起こるのだろうか? 新たな謎だ。

参考のため、実際の音(再生音を収録したもの)をこちらに置く。最初が問題のある場合、次が問題のない場合である。音量が大きいが、小さくしても現象は変わらなかった。多くの環境で再生できるようにMP3にしたので、環境によってはどちらも同じように(問題があるように聞こえる)可能性がある。

同じ音でもFLACのファイルを再生する場合は問題が起こらず、出力系をJACKからPulseAudioに換えても問題は起こった。それから、同じMP3のファイルを他のプレーヤー(例: XPlayer)やGstreamer(以下、GST)の再生プログラム(gst-play-1.0)で再生しても同じ現象が起こるが、問題の起こらないプレーヤー(例: Spotify, AlsaPlayer)もあるので、GMBが再生に使っているGSTのMP3デコーダ(デフォルトのもの)に問題がある可能性が高いことが分かった。

更に試したら、madというデコーダを使うと問題が起こらなくなることが分かった。以下に、gst-launch-1.0での例を示す。下記太字の"mad"を"decodebin"に変えると、問題が起こる。

gst-launch-1.0 filesrc location=test.mp3 ! mad ! audioconvert ! autoaudiosink

結局、GSTのデフォルトのデコーダ(decodebin?)に問題があるようなのだが、仮にGSTのバージョンが古いせいだとしても、OSのパッケージのものを使っているため、それより新しいものにバージョンを上げるのはかなりの手間(自分でコンパイルする)なので、上の例のようにGMBで使うMP3デコーダを変更するようにした。

MP3デコーダはmad以外にmpg123audiodecやavdec_mp3でも問題が起こらなかった。ただ、gst-launch-1.0で使う場合にはmadしか音が出なかった(他はprerollのエラーが起こった)。これは、僕がGSTの使い方を良く理解していないせいだ思う。

それで、3つの中では(名前から)一番品質が良さそうな(イメージの)avdec_mp3を使うことにした。ただ、何か問題があった時やデフォルトのデコーダで試したい時に手軽にデコーダを変更できるように、GMBの拡張設定で変えられるようにした。

他に分からなかったのは、GSTのパイプライン(上記のコマンド例のようなもの)でMP3デコーダを指定する順番が最初でないと うまく動かなかった(再生開始しない)ことだが、これも僕がGSTを良く理解していないせいだと思う。

 

昨夜から延々と試行錯誤と対処をして、ようやく本題の音量を合わせる話に進むことができた。テスト信号とメーターで音量を比較してみたら、結局、音量正規化を行わない場合には、どちらも同じ音量で再生されることが分かった(まあ、デジタルなのだから、大きく違ったらおかしいが・・・)。僕のシステムではDACの前に部屋の特性補正用のイコライザを入れているが、両方とも音量を100%にしてもオーバーフローは起こらないようだった。ただ、念のため(論理的でない「気休め」)、イコライザの前で1dB下げるようにした。

音量正規化を行う場合には、SpotifyとGMBの音量は異なっていた(これが、気になっていた音量差である)。それぞれの仕組みや目標音量が異なるためだろう。手持ちとSpotify両方にある数曲で調べたら、曲にもよるが、1〜3dB程度Spotifyが大きかった。それでミキサー(jack_mixer)を使って2dB程度下げたら、差が1dB以下となった。これで聴感的にも合えばいいが・・・ → 残念ながら、曲によってSpotifyの正規化後の音量が違うようで、あまり合わない感じだ。

まったく、手製のシステムは不意に問題が起こって気が抜けないものだ。まあ、オーディオ道 趣味なんてそんなものだろうw ただ、JACKだといろいろな部品をマウスで配線して手軽に試行錯誤や確認ができるのは便利だし、いろいろな知らなかったことが分かるのはおもしろい。

それから、MP3デコーダの問題はダウンロード購入したMP3音源の音質劣化を起こすことに気付いて確認したら、確かに若干音がおかしくなっていた。具体的には、ルプーのモーツァルト ピアノ協奏曲 第21番(1975)で弦(バイオリン)の音量が大きい箇所で違和感がある。実際、今年の頭に音が悪いことに気付いて書いている(これはこっちの問題だし、苦情を言ったら確実に泥沼化して疲弊しただろうから、黙ってて良かったw)。今聴くと、MP3デコーダを変更すると、違和感がほとんどなくなって、すっきりした気分のいい音(曇り空がちょっとした青空になった感じ)になる。確かに、録音が古いことによる音の悪さもあるのだが、MP3デコーダの問題が大きかったようだ。

もちろん、他のMP3音源でも同じ問題があるはずだが、特定の音(例: 5.5kHz)が続く場合にしか顕著にならないようで、たまたま僕の手持ちの曲ではそのパターンが少なかったらしく、気付かなかったようだ。実際、ルガンスキーのラフマニノフ ピアノ協奏曲 第2,3番(2005, 2003)ではまったく差が分からず、以前どおりのいい音にしか聞こえない。

それにしても、ルプーについては本当に音が悪いことが分かっていたなんて、僕の耳と環境は随分良くなったものだ。つい最近まで何がいい音か分からなかったことを思うと、全く感慨深い・・・

 

(10/1 8:18追記) MP3の音がおかしい問題の原因が分かり、対処できた。

しつこく試したり調べていたら、ヒントになる投稿が見つかった。どうも、GSTにインストールしていたFluendo MP3 Decoder(flump3dec、パッケージ: gstreamer1.0-fluendo-mp3)が古い(その投稿が2007年なので、それより前!)うえにバグがあったようだ。そして、今(実際には2007年以前から)はそれがなくてもMP3を再生できるとのこと。僕が自分でgstreamer1.0-fluendo-mp3をインストールしたのかは今となっては分からないが、余計だったようだ(← 別のPCには入っていないので、自分で入れたようだ)。

それで、gstreamer1.0-fluendo-mp3をアンインストールして問題のファイル(5.5kHzおよびスイープ)を再生してみたら、正しい音が出た(再度インストールしたら、やっぱり駄目になった)。ルプーのモーツァルト ピアノ協奏曲 第21番も大丈夫そうだ。それで、GMBのMP3デコーダの指定も止めて、デフォルトに戻した。

いつものように最後はあっけなかったが、原因が分かって良かった。それにしても、余計なパッケージは削除して欲しいものだ(→ Ubuntuの人)・・・

  •  0
  •  0