作ったもの
手元に保存している音声・動画をフォルダごと読み込んで表紙つきのライブラリにし、日本語の音声をPCの中だけで文字起こし・翻訳して、再生に合わせて字幕を出すデスクトップアプリ「Local Media」を作りました(macOS / Windows)。
- クラウドなし・アカウントなし。ファイルはどこにも送りません
- 文字起こし: whisper.cpp
- 翻訳(日本語 → 英語・韓国語・中国語): llama.cpp で動かす 1.8B の翻訳モデル
- GPU があれば使う: macOS は Metal、Windows は Vulkan(NVIDIA / AMD / Intel)、無ければ CPU
この記事では、ローカルで「字幕が再生に追いつく」ようにするために工夫したところを書きます。
構成
UI (Tauri 2 / React)
└─ Rust 本体(ライブラリ・SQLite)
├─ --worker : whisper.cpp(whisper-rs)で文字起こし
└─ --translate-worker: 同梱の llama-server(127.0.0.1 のみ)に翻訳をバッチで投げる
重い処理はすべて別プロセスのワーカーに分けています。UI プロセスでは GPU もモデルも初期化しないので、文字起こし中でも画面が固まりません。ワーカーが落ちても本体は生きていて、次の区間からやり直せます。
1. 「入れただけでは何もしない」
最初の版は、フォルダを入れた瞬間に全トラックの文字起こしを始めていました。数百トラックあると PC のファンが回りっぱなしになります。
今はフォルダを入れたときはタイトル・表紙・トラック順の整理だけにして、字幕はユーザーが「✨ 字幕を作る」を押した作品だけ作ります。しかも今聴いているトラックから先に作ります。
2. 約 26 秒の区間に分けて、再生位置の近くから
トラックを約 26 秒ずつの区間に分けて文字起こしします。再生中のトラックは再生位置に近い区間を優先するので、最初の日本語は数十秒で出て、以降は再生に先回りしていきます。
GPU では 1 トラックの複数区間を同時に処理します。一方で「静かに」モードではグラフィックカードを半分ほどしか使わないようにして、作業しながら裏で進められるようにしました。
3. 翻訳は llama-server に「まとめて」投げる
翻訳は、同梱した llama.cpp のサーバーをワーカーから起動し、文字起こしの行を最大 48 行ずつまとめて渡しています。サーバーのスロットを埋めるように投げると、1 行ずつより大幅に速くなりました。
各行には直前の 2 行を文脈として付けています。これは翻訳させるためではなく、話し方(くだけているか、丁寧か)を決めるためです。これが無いと、くだけた台詞が全部丁寧語になりがちでした(今でも完全ではありません)。
4. 字幕は「映画のように」1 行ずつ
文字起こしの区切りと表示のタイミングは別物です。表示側では前の字幕と重ならないように 1 行ずつ即座に切り替え、台詞の無い間は自然に消えるようにしました。ずれる音声には −0.5 秒 / +0.5 秒の同期調整をトラックごとに保存できます。
ハマったところ
- Windows の GPU: CUDA 前提にすると AMD / Intel が動かないので、whisper.cpp も llama.cpp も Vulkan にしました。Vulkan ドライバが無い PC では自動で CPU に落ちます
- 大きいライブラリ: 1 万ファイルでも一覧は数ミリ秒で返るように、一覧は SQLite の結果をそのまま返し、サムネイルは画面に見えているカードの分だけ作ります
おわりに
「買ったけれど聴けていない日本語の音声を、字幕つきで気軽に聴けるようにしたい」というのが動機でした。無料の体験版(作品 3 つ・各トラック最初の 3 分の字幕)があるので、興味があれば触ってみてください。