文字起こし基盤を自前で組もうとして、思ったより沼だった話
はじめに
チームのMTG議事録に加えて、リサーチ用にYouTubeやTikTokの動画も文字起こしして検索できるようにしたい——そんな要件が出てきたので、最初は「Whisperでいけるっしょ」と軽い気持ちで自前構築を始めました。
結論から言うと、想像より沼でした。この記事では、自前構築でぶつかった壁と、最終的にどう解決したかを書きます。「今からWhisperベースの文字起こし基盤を作ろうとしている人」の判断材料になれば嬉しいです。
まずは定番構成で自作してみる
最初に組んだのはこんな構成です。
- 音声認識:
faster-whisper(ローカルGPU) - 話者分離:
pyannote.audio - 動画→音声変換:
ffmpeg - YouTube取得:
youtube-transcript-api/yt-dlp
Whisper単体はよく知られている通り「誰が喋ったか」の情報を持っていないので、複数人の会議やインタビューを扱うなら話者分離は事実上必須です。pyannoteを組み合わせれば実現できる、というのはQiitaでも定番の構成なので、素直にこの並びで進めました。
壁その1: GPU前提の環境構築
CPUだけで長尺の音声を処理すると、体感で数時間かかるレベルで待たされます。GPU環境を用意すると数十分まで縮まりますが、今度はCUDAとcuDNNのバージョン、PyTorchのビルド、pyannoteが要求する依存関係がそれぞれ微妙に噛み合わず、環境構築だけで半日以上溶かしました。
壁その2: pyannoteのモデル利用申請
pyannoteの話者分離モデルはHugging Face上でゲート付き(利用規約への同意が必要)になっているものがあり、segmentation や speaker-diarization など複数のモデルそれぞれで申請フォームを埋める必要があります。個人開発ならまだしも、チームで使うとなると「誰のアカウントで申請するか」「商用利用の扱いはどうするか」も地味に詰める必要がありました。
壁その3: 精度は思ったより気を遣う
話者分離の精度は、静かな1対1の会話なら悪くありません。ただ、早口で発言が被る会議や、3人以上が入り乱れるディスカッションになると、体感の精度が目に見えて落ちます。「pyannote導入した」だけで安心せず、自分たちの実データで検証しないと本番投入は怖いな、というのが正直な感想でした。
壁その4: 音声ソースが増えるたびに個別対応
会議録音だけならまだよかったのですが、「YouTubeのこの動画も文字起こしして」「TikTokのこれも」という要望が来た瞬間に話がややこしくなります。
- YouTubeは公式字幕があればまだ楽ですが、無い動画も多く、結局音声を落としてWhisperに通す必要がある
-
pytubeやyoutube-transcript-apiはYouTube側の仕様変更で突然動かなくなることがあり、メンテコストが地味にかかる - TikTokやInstagramは公式APIでの字幕取得手段がほぼ無く、動画ダウンロード→音声抽出→Whisperという自前パイプラインを都度書く羽目に
つまり「音声認識モデルをどう動かすか」よりも、「入力ソースごとに違う取得方法をどう吸収するか」の方が実は面倒だった、というのがこのフェーズの学びです。
方針転換: 自前運用するもの/しないものを切り分ける
ここで一度立ち止まって、何を自分たちで持つべきかを整理しました。
- 自前で持つ価値があるもの: 文字起こし結果をどう検索・活用するか(社内ナレッジ化、要約、タグ付けなど)
- 自前で持つ価値が薄いもの: GPUインフラの面倒を見ること、プラットフォームごとの取得ロジックのメンテ、話者分離モデルの追従
後者はプロダクトの差別化に関係ない「配管」の部分なので、ここは素直に外部APIに寄せる判断をしました。選定基準は次の3つです。
- YouTube・TikTok・Instagramなど複数ソースを、同じレスポンス形式で扱えること(ソースごとに分岐を書きたくない)
- 話者分離とタイムスタンプが標準で付いてくること(オプション課金や別モデル組み合わせが要らない)
- SRT/VTT/JSONなど複数フォーマットでそのまま吐き出せること(字幕用・検索用で変換スクリプトを書きたくない)
いくつか比較した中で、Video Transcriber AIのTranscript API がこの3条件を素直に満たしていたので採用しました。YouTube・TikTok・Instagram・Facebook・X・BiliBili・Google Drive・Dropbox・直接アップロードまで単一エンドポイントで受け付けてくれるので、ソースごとの分岐処理を書かずに済んだのが一番効きました。
実装イメージ
サインアップしてAPIキーを取得し、Bearerトークンで認証してPOSTするだけの、いたってシンプルなREST構成です。動画・音声ファイルへの直接URLでも、YouTubeやTikTokのリンクでもそのまま渡せます。
import requests
API_KEY = "YOUR_API_KEY"
ENDPOINT = "https://videotranscriber.ai/api/..." # 実際のエンドポイントはAPIドキュメントを参照
payload = {
"source_url": "https://www.youtube.com/watch?v=xxxxxxxx",
"diarization": True,
"export_formats": ["json", "srt"],
}
resp = requests.post(
ENDPOINT,
headers={"Authorization": f"Bearer {API_KEY}"},
json=payload,
)
result = resp.json()
for segment in result["segments"]:
print(f'[{segment["start"]:.1f}s - {segment["end"]:.1f}s] '
f'{segment["speaker"]}: {segment["text"]}')
レスポンスには話者ラベル付きのセグメントと単語単位のタイムスタンプ、検出された言語がまとめて入ってくるので、自前構成のときのように「Whisperの出力とpyannoteの出力を時間軸で突き合わせる」処理を自分で書かずに済みます。SRTが欲しければフォーマット指定だけで、断り文字数の調整やMeCabでの分かち書きといった前処理も不要でした。
※ エンドポイントやパラメータ名は変更される可能性があるため、実装時は必ず公式APIドキュメントの最新版を確認してください。
移行してみての所感
- 環境構築(GPU・CUDA・pyannote申請)にかけていた時間がまるごと不要になった
- プラットフォームが増えるたびに取得ロジックを書き足す必要がなくなり、「新しいソースに対応して」と言われても怖くなくなった
- 話者分離の精度については、自前構成のときと同じく「自分たちの実データで検証する」姿勢は変えていません。込み入った会議での話者分離は依然として簡単な問題ではないので、そこはAPIに乗り換えても過信しないようにしています
インフラの面倒を見ること自体が目的でないなら、「文字起こしをどう活かすか」に時間を使えるようになったのが一番の収穫でした。同じような要件で自前構築を検討している方は、一度Transcript APIのドキュメントも覗いてみると、判断材料が増えるかもしれません。