32blogby StudioMitsu
ffmpeg9 min read

FFmpeg × Whisperで自動字幕を生成する

OpenAIのWhisperとFFmpegを組み合わせて動画に自動字幕を付ける方法。音声抽出からSRT生成、字幕の焼き込みまでパイプラインを構築する。

FFmpegWhisper字幕自動化AI
目次

動画に字幕を付けるのは地味に面倒だ。手作業で文字を起こして、タイムコードを合わせて、ファイルを書き出して……。毎回やるには割に合わない。

この記事では、OpenAI の Whisper と FFmpeg を組み合わせて 字幕生成を全自動化するパイプライン を僕が作る。音声抽出 → 文字起こし → SRT 生成 → 字幕焼き込みまでを一本のスクリプトで処理できるようにする。

字幕作成の手間を消す

字幕作成のボトルネックはふつう「文字起こし」だ。話し言葉をテキストに変換して、どこで区切るかを決めて、タイムコードを付ける。これが全体の 9 割の時間を食う。

Whisper はこの問題をほぼ解決する。音声ファイルを渡すと、文字起こしとタイムコードの付与を自動でやってくれる。出力形式に SRT を選べば、そのまま FFmpeg に渡して字幕を焼き込める。

必要なものは以下の 2 つ。

  • Python 3.8 以上 + Whisper
  • FFmpeg (インストール済みであること)

FFmpeg の基本的な使い方は FFmpeg 入門チュートリアル にまとめてある。インストールがまだなら先にそちらを見てほしい。

Whisperのインストール(モデルサイズ選び)

Whisper は pip でインストールできる。

bash
pip install openai-whisper

インストールが終わったら、モデルサイズを選ぶ。Whisper にはいくつかのサイズがある。

モデルVRAM速度精度
tiny~1GB最速
base~1GB速い普通
small~2GB普通良い
medium~5GB遅い高い
large~10GB最遅最高

FFmpegで音声を抽出する

Whisper は動画ファイルを直接渡すこともできるが、音声だけ先に抽出しておくほうが処理が速い。FFmpeg で音声を WAV に変換する。

bash
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 で使う場合

bash
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 から呼ぶ。

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"] の各要素に startend(秒)・text が入っている。これを SRT 形式に整形して書き出す。

FFmpegで字幕を焼き込む

SRT ファイルができたら FFmpeg で動画に焼き込む。subtitles フィルターを使う。

bash
ffmpeg -i input.mp4 -vf "subtitles=subtitles/audio.srt" -c:a copy output.mp4

フォントや文字サイズを調整したいときは force_style オプションを使う。

bash
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 JPYu Gothic などの日本語フォントを指定すると文字化けを防げる。

パイプラインを1つのスクリプトにまとめる

ここまでの手順を一本の Python スクリプトにまとめる。動画ファイルを渡すだけで字幕焼き込み済みの動画が出力される。

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 コマンドで済む。

bash
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 に任せて編集に集中しよう。僕はこのパイプラインを導入してから、字幕作業にかける時間が大幅に減った。