[Tauri #07] Tauri v2:CDDB / FreeDB終了後にCDの曲名が出ない問題を、MusicBrainzと自作プレーヤー「DISC.TOC」で解決
はじめに
音楽CDをパソコンに入れても、曲名が出ない。手元では、WinampでもWindows Media Playerでも、そうなりました。CDの冊子を見ながら「3曲目は何だっけ」とやるのは、苦痛でしかありません。
この「曲名が出ない問題」には、背景があります。次の章で整理しますが、要点はCDそのものには曲名が入っていないことと、曲名を引くデータベース(CDDB系)の事情が変わったことです。
そこで、CDを入れるだけで曲名を出して再生する軽いアプリを、Tauri v2で作りました。名前は DISC.TOC です。
- CDを入れると、アルバム名・アーティスト・曲名を自動で表示(MusicBrainz)
- 管理者権限なしでドライブを直接読む
- 曲を選ぶとすぐ再生。シーク、音量、イコライザー、リピート、シャッフル付き
- 曲名・アーティストは手動編集もできて、TXT / CSV / CUE / JSON に書き出せる
- 選択曲、または全曲を WAV に書き出せる
スクリーンショット
リポジトリ(MIT):
GitHub - fixtan/DISC.TOC: Audio CD player with track names, no admin rights needed
音楽CDを入れるだけで曲名を表示して再生する、Tauri v2製のWindows向けアプリ。管理者権限不要。
github.com前回の記事:
[Tauri #06] Tauri v2:Honeyview置き換えの画像ビューア「Lookover」を作り、Windows / Mac / Linuxで公開した
見るのが主、直すのも一瞬、書き出しはワンキー。Tauri v2 + 素のJavaScriptで画像ビューアを作り、3OSのインストーラーまで公開した記録。
lain-lab.com1. なぜ曲名が出ないのか:CDDBとFreeDBの話
音楽CD(CD-DA)には、曲名もアーティスト名も入っていません。入っているのは、TOC(目次)、つまり各トラックの開始位置だけです。(CD-TEXT付きのCDは例外ですが、ごく一部です)
プレーヤーは、このTOCからディスクIDを計算して、ネット上のデータベースに「このIDのCDの曲名は?」と問い合わせています。昔その役を担っていたのが CDDB です。
- CDDBは、利用者が曲名を登録して育てる形で始まったが、のちに商用化された(現在のGracenote)
- その反動で、誰でも使えるデータベースとして FreeDB が生まれ、多くのプレーヤーがこれを参照した
- FreeDB は 2020年3月に終了した。継承する gnudb などは残っている
- MusicBrainz は、別系統の、誰でも編集できるオープンなデータベース。中心のデータはCC0で、APIも公開されている
どのプレーヤーがどのデータベースを引くかは、ソフトと時期でまちまちです。「昔は出たのに出なくなった」「このCDだけ出ない」が起きるのは、そのせいです。
DISC.TOC は、MusicBrainz を引く作りにしました。理由は、JSONのAPIがあること、データが再利用できること、載っていないCDは自分で登録できること(その導線として登録ページを開くボタンも付けました)の3つです。
CDDBのディスクIDも、TOCから計算できる
CDDB系のIDは、8桁の16進数で、TOCから計算します。
n = 各トラックの開始秒(オフセット/75 + 2)の「各桁の和」を、全トラックぶん足す
t = リードアウトの秒 − 最初のトラックの開始秒
ID = ((n % 255) << 24) | (t << 8) | トラック数
DISC.TOC も、このCDDB IDを計算して表示・書き出しに載せています。core に実装して、実機のTOCでテストしました。MusicBrainzのIDとは別物なので、両方持っておくと、ほかのデータベースと突き合わせるときに便利です。
2. 構成:Rustはドライブ担当、画面は素のJS
今回も、フロントは素のJavaScriptで、バンドラーなしです。Rust側は、ドライブとネットワークと保存だけを受け持ちます。
core/:純粋なロジック(TOCの解析、ディスクIDの計算、書き出し形式)。cargo testで確かめられるcore/src/cd_win.rs:Windowsのドライブアクセスsrc-tauri/:Tauriの本体(MusicBrainzへの問い合わせ、保存、WAV書き出し)ui/:画面(再生、イコライザー、編集)
ドライブに触る部分と、純粋な計算を分けたのが効きました。TOCとディスクIDは、ドライブがなくてもテストで回せます。
3. 管理者権限なしで、CDのTOCと音声を読む
一番の山場がここでした。CDの中身を生で読むソフトは、管理者権限を求めるものが多いからです。
結論から言うと、読み取りだけなら、通常の権限で足ります。ドライブを \\.\F: として読み取りで開き、DeviceIoControl を2種類使います。
// 読み取りだけで開く(書き込み権限は要らない)
CreateFileW(r"\\.\F:", GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, ..., OPEN_EXISTING, ...)
IOCTL_CDROM_READ_TOC = 0x24000 // 目次(各トラックの開始位置)
IOCTL_CDROM_RAW_READ = 0x2403E // 生のオーディオ(CD-DA)
RAW_READ には、RAW_READ_INFO(16バイト)を渡します。オフセット(LBA×2048)、セクタ数、トラックモード(CD-DAは2)の3つです。1セクタは2352バイトで、16bit・ステレオ・44.1kHzのPCMがそのまま返ってきます。1秒は75セクタです。
読み取りが失敗することもあるので、数セクタずつ読んで、失敗したら3回までやり直しています。
「Win32_Security」を足し忘れる
windows-sys で CreateFileW を使うには、Win32_Foundation、Win32_Storage_FileSystem、Win32_System_IO に加えて、Win32_Security のフィーチャーも要ります。最初に足りなくて、unresolved import の1エラーだけ出ました。
4. MusicBrainzのディスクIDを自前で計算する
曲名を引くには、CDを一意に決めるIDが要ります。MusicBrainzのディスクIDは、TOCから計算できます。
- 最初のトラック番号、最後のトラック番号、リードアウトの位置、99トラック分の開始位置を、16進の文字列に並べる(位置は150セクタ分を足す)
- その文字列のSHA-1を取る
- Base64にして、
+ / =を. _ -に置き換える
これを core に実装して、実機のTOCでテストしました。
問い合わせの順番
- ローカルに保存した曲情報(JSON)
- MusicBrainzのディスクID検索(
/ws/2/discid/<id>) - 見つからなければ、TOCでの近似検索(
/ws/2/discid/-?toc=...)
MusicBrainzのAPIは1秒に1回までなので、2回目の問い合わせの前に1.1秒待ちます。User-Agentには、連絡先つきの名前を入れました。
解けていない謎
手元のCDは、ディスクIDでは404になり、TOC検索なら見つかりました。プレス違いなど、原因の想像はつきますが、確かめていません。ディスクIDが外れてもTOC検索で拾えるので、使い勝手は変わりません。
5. 最初は「全部読んでから再生」で、遅すぎた
最初の再生は、トラックを丸ごとWAVに読み出してから <audio> に渡す作りでした。動きはしたのですが、曲の切り替えにとても時間がかかり、3曲目を選ぶと止まることもありました。
Winampなら1〜2秒で切り替わります。「ストリーミングに切り替えないと、プレーヤーとして使えない」と判断して、作り直しました。
読みながら再生する
Rust: read_pcm(drive, lba, count) → 生のPCMバイト列をそのまま返す
JS: 1秒ぶん(75セクタ)ずつ読む → AudioBufferSourceNode に並べて鳴らす
read_pcmは、JSONではなくtauri::ipc::Responseでバイナリのまま返す- 最初の1回だけは38セクタ(約0.5秒)にして、鳴り始めを早める
- 常に6秒先まで読んでおく。それ以上は200msごとに様子を見る
- 読み込みが間に合わなくなったら、再生時計を後ろにずらして続ける
- シークは、その位置から読み直す。一時停止は
AudioContext.suspend() - 世代番号(
gen)で、切り替え前の古い読み込みを捨てる
これで、動作がWinampと同じになりました。ドライブ側は、回転が止まった状態から読み始めるので、最初の1曲だけは数秒かかります。これは円盤を回し直す時間なので、ソフトでは縮められません。
ドライブのハンドルは、Rust側で使い回しています(失敗したら捨てて、次回に開き直す)。同時に呼ばれても壊れないよう、ミューテックスで1つずつ読ませています。
6. 画面は、WINAMP X2を丸ごと流用した
画面は、以前自作した「WINAMP X2」(lain-lab.comのトップページのプレーヤー)のコードを渡して、名前を変えて流用しました。CSSのクラスを dt-* に、色は --dt-* の変数にして、スキンはその値を差し替えるだけです(LAINとAQUA)。イコライザー、スペクトラム、プレイリストはそのまま動きました。
ただ、WINAMP X2では別々に動かせた MAIN / EQ / PLAYLIST の3つが、DISC.TOCでは1つのウィンドウに縦積みです。タイトルバーなしで窓を個別に動かすには、Tauriのウィンドウを分けて、状態を同期する作りが要ります。今回は見送りました。
名前については、WinampやWMPとは無関係の個人アプリだとREADMEに明記しています。見た目が似ていても、アプリ名もロゴも別物にしました。
7. WebView2では「ダウンロード」が動かない
曲リストの書き出しは、最初Blobを作って <a download> をクリックする、定番の書き方にしました。ところが、WebView2では何も起きません。
Rust側でネイティブの保存ダイアログを出して、そのパスにファイルを書く形に変えて解決しました。
app.dialog().file()
.add_filter("TXT", &["txt"])
.set_file_name("album.txt")
.blocking_save_file()
CSVには、Excelで文字化けしないようにBOMを付けています。JSONは読み込み直せるので、曲情報を他のPCへ持っていけます。CUEは、曲の開始位置まで書き出します。
編集した曲情報は、ディスクIDごとに app_data_dir/discs/<id>.json に自動保存されます。次回からは、ネットなしですぐ出ます。
MusicBrainzに載っていないCDのために、登録ページ(/cdtoc/attach?toc=...)を開くボタンも付けました。
8. v0.2.0:WAV書き出し
ストリーミング用の read_pcm があるので、WAVへの書き出しは、頭から読んで44バイトのヘッダーを付けて書くだけです。ただ、現実のCDは傷があるので、次の点は気を付けました。
- 書き込みは
.wav.partに行い、最後まで成功したら.wavに名前を変える。中止した場合は.partを消す - 読めないセクタは、1セクタずつやり直す。それでも読めなければ無音を入れて、終わったときに件数を報告する
- 書き出し中は再生を止める。同じドライブを同時に読むと、両方が遅くなるため
- 進捗は、Tauriのイベント(
rip-progress)で画面に流す
ファイル名は 01 - タイトル.wav の形で、コンピレーション盤は 01 - アーティスト - タイトル.wav にします。MP3 / OGG は、WAVが出せれば他のソフトで変換できるので後回しです。
9. 配布:GitHub Actionsで、17分かかった
配布は、[Tauri #06] と同じく、タグのpushでGitHub Actionsが動く構成です。ただ、今回はWindows専用にしたので、ジョブは1つだけです。
初回は17分かかりました。大半は cargo install tauri-cli で、約5分をここで使います。ほかに、Rustの依存のコンパイルがあります。
- 次の改善案:
tauri-cliをビルド済みのバイナリで入れる(taiki-e/install-actionなど)。依存のキャッシュも効かせる - 急ぎのときは、手元で
cargo tauri buildして、成果物を下書きに上げる手もある
インストーラーは .msi と .exe(NSIS)の2つが作れました。署名はしていないので、初回はSmartScreenの警告が出ることがあります。「詳細情報」→「実行」で起動できます。
インストール版も、一覧表示、再生、書き出しまで、cargo tauri dev と同じように動くことを確かめました。
まとめ:ドライブは生で読めて、再生はストリーミングにする
- 非管理者でも、
CreateFileWの読み取りと2種類のIOCTLで、TOCも生音声も読める - 曲名が出ないのは、CDにはTOCしかなく、問い合わせ先(CDDB / FreeDB系)の事情が変わったから
- ディスクIDはTOCから自前で計算できる。外れたらTOC検索で拾う
- 全部読んでから再生するのは遅い。読みながら鳴らして、6秒先まで先読みする
- WebView2は、
<a download>が動かない。保存はネイティブのダイアログで - 傷のあるCDには、リトライと無音埋めと、件数の報告を
動作確認は、1台のドライブと数枚のCDという限られた環境です。動かないドライブやCDがあれば、Issuesで教えてください。今後は、ウィンドウを個別に動かせるUIと、Linux対応を考えています。
Releases · fixtan/DISC.TOC
Windows用インストーラー(.msi / .exe)。署名なし。
github.com