動画に字幕を付けるのは地味に面倒だ。手作業で文字を起こして、タイムコードを合わせて、ファイルを書き出して……。毎回やるには割に合わない。
この記事では、OpenAI の Whisper と FFmpeg を組み合わせて 字幕生成を全自動化するパイプライン を僕が作る。音声抽出 → 文字起こし → SRT 生成 → 字幕焼き込みまでを一本のスクリプトで処理できるようにする。
字幕作成の手間を消す
字幕作成のボトルネックはふつう「文字起こし」だ。話し言葉をテキストに変換して、どこで区切るかを決めて、タイムコードを付ける。これが全体の 9 割の時間を食う。
Whisper はこの問題をほぼ解決する。音声ファイルを渡すと、文字起こしとタイムコードの付与を自動でやってくれる。出力形式に SRT を選べば、そのまま FFmpeg に渡して字幕を焼き込める。
必要なものは以下の 2 つ。
- Python 3.8 以上 + Whisper
- FFmpeg (インストール済みであること)
FFmpeg の基本的な使い方は FFmpeg 入門チュートリアル にまとめてある。インストールがまだなら先にそちらを見てほしい。
Whisperのインストール(モデルサイズ選び)
Whisper は pip でインストールできる。
pip install openai-whisper
インストールが終わったら、モデルサイズを選ぶ。Whisper にはいくつかのサイズがある。
| モデル | VRAM | 速度 | 精度 |
|---|---|---|---|
| tiny | ~1GB | 最速 | 低 |
| base | ~1GB | 速い | 普通 |
| small | ~2GB | 普通 | 良い |
| medium | ~5GB | 遅い | 高い |
| large | ~10GB | 最遅 | 最高 |
FFmpegで音声を抽出する
Whisper は動画ファイルを直接渡すこともできるが、音声だけ先に抽出しておくほうが処理が速い。FFmpeg で音声を WAV に変換する。
ffmpeg -i input.mp4 -vn -acodec pcm_s16le -ar 16000 -ac 1 audio.wav
オプションの意味は以下のとおり。
-vn— 映像を無視して音声だけを処理する-acodec pcm_s16le— WAV 形式(16bit リニア PCM)で出力する-ar 16000— サンプリングレートを 16kHz に設定する(Whisper の推奨値)-ac 1— モノラルに変換する(Whisper はモノラルで精度が安定する)
出力の audio.wav を次のステップで Whisper に渡す。
Whisperで文字起こしとSRT生成
音声ファイルができたら Whisper に渡す。CLI と Python API の両方で使える。
CLI で使う場合
whisper audio.wav --model medium --language ja --output_format srt --output_dir ./subtitles
--language ja を指定しないと言語自動検出モードになる。日本語動画は明示的に指定したほうが精度が上がる。
./subtitles/audio.srt にファイルが生成される。中身はこんな形式だ。
1
00:00:00,000 --> 00:00:03,500
こんにちは、今日はFFmpegとWhisperの使い方を紹介します。
2
00:00:03,500 --> 00:00:07,200
まずはWhisperのインストールから始めましょう。
Python API で使う場合
細かい制御が必要なときは Python から呼ぶ。
import whisper
model = whisper.load_model("medium")
result = model.transcribe("audio.wav", language="ja")
# SRT形式で保存する
def format_timestamp(seconds: float) -> str:
ms = int((seconds % 1) * 1000)
s = int(seconds) % 60
m = int(seconds) // 60 % 60
h = int(seconds) // 3600
return f"{h:02d}:{m:02d}:{s:02d},{ms:03d}"
with open("subtitles/audio.srt", "w", encoding="utf-8") as f:
for i, segment in enumerate(result["segments"], start=1):
start = format_timestamp(segment["start"])
end = format_timestamp(segment["end"])
text = segment["text"].strip()
f.write(f"{i}\n{start} --> {end}\n{text}\n\n")
print("SRT ファイルを生成しました")
result["segments"] の各要素に start・end(秒)・text が入っている。これを SRT 形式に整形して書き出す。
FFmpegで字幕を焼き込む
SRT ファイルができたら FFmpeg で動画に焼き込む。subtitles フィルターを使う。
ffmpeg -i input.mp4 -vf "subtitles=subtitles/audio.srt" -c:a copy output.mp4
フォントや文字サイズを調整したいときは force_style オプションを使う。
ffmpeg -i input.mp4 \
-vf "subtitles=subtitles/audio.srt:force_style='FontName=Noto Sans CJK JP,FontSize=24,PrimaryColour=&Hffffff,OutlineColour=&H000000,Outline=2'" \
-c:a copy output.mp4
force_style の値は ASS/SSA フォーマットのスタイル指定に従う。日本語動画には Noto Sans CJK JP や Yu Gothic などの日本語フォントを指定すると文字化けを防げる。
パイプラインを1つのスクリプトにまとめる
ここまでの手順を一本の Python スクリプトにまとめる。動画ファイルを渡すだけで字幕焼き込み済みの動画が出力される。
import subprocess
import sys
import os
import whisper
def extract_audio(input_video: str, output_audio: str) -> None:
"""動画から音声を抽出する"""
cmd = [
"ffmpeg", "-i", input_video,
"-vn", "-acodec", "pcm_s16le",
"-ar", "16000", "-ac", "1",
output_audio, "-y"
]
subprocess.run(cmd, check=True, capture_output=True)
print(f"音声を抽出しました: {output_audio}")
def transcribe_to_srt(audio_path: str, srt_path: str, model_name: str = "medium") -> None:
"""Whisper で文字起こしして SRT を生成する"""
model = whisper.load_model(model_name)
result = model.transcribe(audio_path, language="ja")
def format_timestamp(seconds: float) -> str:
ms = int((seconds % 1) * 1000)
s = int(seconds) % 60
m = int(seconds) // 60 % 60
h = int(seconds) // 3600
return f"{h:02d}:{m:02d}:{s:02d},{ms:03d}"
with open(srt_path, "w", encoding="utf-8") as f:
for i, segment in enumerate(result["segments"], start=1):
start = format_timestamp(segment["start"])
end = format_timestamp(segment["end"])
text = segment["text"].strip()
f.write(f"{i}\n{start} --> {end}\n{text}\n\n")
print(f"SRT を生成しました: {srt_path}")
def burn_subtitles(input_video: str, srt_path: str, output_video: str) -> None:
"""字幕を動画に焼き込む"""
# パスに非ASCII文字が含まれないことを前提とする
vf = f"subtitles={srt_path}"
cmd = [
"ffmpeg", "-i", input_video,
"-vf", vf,
"-c:a", "copy",
output_video, "-y"
]
subprocess.run(cmd, check=True, capture_output=True)
print(f"字幕焼き込み完了: {output_video}")
def main(input_video: str) -> None:
base = os.path.splitext(input_video)[0]
audio_path = f"{base}_audio.wav"
srt_path = f"{base}_subtitles.srt"
output_video = f"{base}_subtitled.mp4"
print("=== 字幕自動生成パイプライン ===")
extract_audio(input_video, audio_path)
transcribe_to_srt(audio_path, srt_path)
burn_subtitles(input_video, srt_path, output_video)
# 中間ファイルを削除する
os.remove(audio_path)
print(f"\n完了: {output_video}")
if __name__ == "__main__":
if len(sys.argv) != 2:
print("使い方: python subtitle_pipeline.py <input_video>")
sys.exit(1)
main(sys.argv[1])
実行は 1 コマンドで済む。
python subtitle_pipeline.py input.mp4
バッチ処理でフォルダ内の動画を一括処理したい場合は、FFmpeg × Python バッチ自動化 の記事が参考になる。
まとめ
FFmpeg と Whisper を組み合わせると、字幕作成のほぼ全工程を自動化できる。
- 音声抽出 → Whisper 文字起こし → SRT 生成 → FFmpeg 焼き込みの 4 ステップ
- 日本語は medium モデル以上を使う
- SRT ファイルのパスに日本語や空白を含めない
- Python スクリプトにまとめれば
python subtitle_pipeline.py input.mp4の 1 コマンドで完結する
手作業の文字起こしに時間を使うより、Whisper に任せて編集に集中しよう。僕はこのパイプラインを導入してから、字幕作業にかける時間が大幅に減った。