はじめに
動画や音声ファイルをアップロードすると文字起こししてくれるサービスはたくさんありますが、「ファイルがサーバーに送信される」ことが気になる場面も多いはずです。会議の録画、取材音声、未公開の動画など、外部に出したくない素材は意外とあります。
そこで、ファイルが端末の外に出ないことを前提に、ブラウザ内だけで完結する文字起こしツールを自作しました。本記事はその実装メモです(自作ツールの紹介を含みます)。
完成品はこちらで公開しています:
https://mytoolster.com/ja/video-to-text
なぜブラウザでやるのか
理由は3つあります。
- プライバシー:音声データがネットワークに流れない。推論がローカルで完結するので、機密性の高い録音でも扱えます
- サーバーコストがゼロ:推論はユーザーの端末で走るため、GPUサーバーの維持費がかかりません。無料で公開し続けられます
- 登録不要で使える:アカウントもAPIキーも要らないので、開いてすぐ使えます
代わりに、端末の性能に依存する・初回のモデルダウンロードが必要、といった制約はあります。このトレードオフをどう実装で吸収したかを書いていきます。
全体の構成
処理の流れはシンプルです。
- ファイルを読み込み、Web Audio API でデコードする
- OfflineAudioContext で 16kHz モノラルにリサンプルする
- Web Worker 上の Transformers.js で Whisper を推論する
- 結果をテキスト(字幕用途なら SRT)としてダウンロードする
音声抽出に ffmpeg.wasm は使っていません。ブラウザがデコードできる形式であれば、Web Audio API だけで十分に処理でき、余分な数十MBのWasmバイナリを読み込まずに済みます。
音声の前処理
Whisper は 16kHz モノラルの音声を前提としているので、入力を揃えます。
// 抜粋・簡略化
const arrayBuffer = await file.arrayBuffer()
const decoded = await audioCtx.decodeAudioData(arrayBuffer)
const offlineCtx = new OfflineAudioContext(
1, // モノラル
Math.ceil(decoded.duration * 16000),
16000
)
const source = offlineCtx.createBufferSource()
source.buffer = decoded
source.connect(offlineCtx.destination)
source.start()
const resampled = await offlineCtx.startRendering()
const audioData = resampled.getChannelData(0) // Float32Array
推論は Web Worker で
推論をメインスレッドで回すと UI が固まるので、Transformers.js の automatic-speech-recognition パイプラインは Web Worker の中で動かしています。
モデルは ONNX 化された Whisper(onnx-community の whisper-tiny / base / small)で、デフォルトはバランスの良い base です。デバイスは WebGPU が使える環境では WebGPU、使えない環境では WASM にフォールバックします。ページ側で対応状況を判定してバッジ表示するようにしたら、「自分の環境で動くのか?」という不安はだいぶ減りました。
ハマったところ
1. モデルの配信と初回ロード
Hugging Face Hub からだけモデルを取得する構成だと、初回ダウンロードが失敗したときに利用者を待たせてしまいます。そこで、モデル一式は 自前の CDN にもミラーしてあり、Hugging Face からの取得に失敗した場合はそちらにフォールバックする構成にしました。
- レスポンスには
Cache-Control: public, max-age=31536000, immutableを付与 - Transformers.js 側は
env.useBrowserCacheを有効化し、Cache API に保存
これで2回目以降の起動はダウンロードなしで開始できます。モデル選択肢(tiny / base / small)を残したのは、端末性能と精度のバランスを利用者に選んでもらうためです。
2. ブラウザがデコードできる形式に依存する
Web Audio API で抽出する方式の弱点はここです。ブラウザがデコードできないコーデックのファイルは処理できません。ffmpeg.wasm を積めば対応範囲は広がりますが、読み込みサイズと引き換えになります。「プライバシーと手軽さ」を優先した今回の設計では、割り切った部分です。
3. 長い音声はメモリを食う
16kHz モノラルの Float32 は 1秒あたり約64KB です。数時間の録音になると、波形を丸ごとメモリに抱える現在の実装では重くなります。チャンク分割して逐次処理するのが正攻法ですが、まずは「短〜中尺の素材を手早く」用途に絞って公開しました。
まとめ
- 音声抽出は Web Audio API、推論は Transformers.js + Whisper(ONNX)を Web Worker で
- WebGPU 優先、WASM フォールバック
- モデルは自前 CDN にもミラーし、ブラウザキャッシュとあわせて初回コストを吸収
- ファイルは端末の外に出ないので、サーバーコストゼロで無料公開できる
「サーバーで推論するのが当たり前」という前提を疑うと、ブラウザ推論は小規模なツールにとってかなり現実的な選択肢でした。同じような構成で作っている方の参考になれば嬉しいです。
ツール本体:https://mytoolster.com/ja/video-to-text
(MyToolster という無料ツール集の1つとして公開しています。他のツールもすべてブラウザ内で処理します。)
