32blogby Studio Mitsu

fqmpeg で字幕処理 — 焼き込み・抽出・ソフト埋め込み

fqmpeg の字幕クラスタ 4 コマンドを実装読みベースで整理。焼き込み・抽出・MKV へのソフト埋め込みと Whisper 連携レシピまで。

by omitsu24 min read
目次

fqmpeg の C3 クラスタは「字幕に関わるすべて」を 4 コマンドでカバーする。subtitlesubtitle-burn は字幕を映像のピクセルに焼き込み、extract-subtitle は MKV などから字幕トラックを .srt として抜き出し、sidecar は外部の .srt/.ass/.vtt を MKV のトグル可能なトラックとして埋め込む。読み・書きの両方向、ハード/ソフトの両方向をこの 4 つで行き来できる。

この記事ではコマンドごとに「生成される FFmpeg コマンド」「デフォルト値」「出力ファイル名」を実装ソース (src/commands/、fqmpeg 3.0.1) を読んで整理し、最後に Whisper 連携を含む実用レシピを 3 本紹介する。

この記事で得られること

  • 4 コマンドの使い分け(ハード/ソフト × 読み/書きの 2 軸)
  • 各コマンドが実際に生成する FFmpeg コマンド(--dry-run で検証済み)
  • デフォルト値・許容値・出力ファイル名規則
  • Whisper との連携を含む実用レシピ 3 本

fqmpeg の字幕モデル — ハード/ソフト × 読み/書きの 2 軸

コマンドを覚える前に、字幕処理が必ず 2 つの軸で分類される事実を頭に入れる。

軸 1 — ハードサブ vs ソフトサブ。 ハードサブ(焼き込み・open caption)は映像のピクセルに直接描画される。プレイヤー側で消したり再翻訳したりはできず、永久に映像と一体化する。ソフトサブはコンテナ内の別トラックとして格納され、プレイヤーで ON/OFF を切り替えられる。YouTube や Netflix の多言語字幕メニューが成立するのはこの仕組みのおかげ。

軸 2 — 読み vs 書き。 ファイルに字幕を「追加する」コマンド(subtitlesubtitle-burnsidecar)と、既存ファイルから字幕を「取り出す」コマンド(extract-subtitle)に分かれる。

やりたいことハード / ソフトコマンド出力
スタイル指定(フォント・色・位置)付きで焼き込みハードsubtitle<元ファイル>-subtitled.<拡張子>
サイズ指定のみで焼き込みハードsubtitle-burn<元ファイル>-burned.<拡張子>
MKV/MP4 から既存字幕を SRT として抜き出すn/a (読み)extract-subtitle<元ファイル>-sub.srt
外部 SRT をトグル可能なトラックとして埋め込みソフトsidecar<元ファイル>-subtitled.mkv

読み進める前に押さえておきたい 3 点:

  1. subtitlesubtitle-burn もハードサブ。 名前から「subtitle は柔らかい埋め込み?」と誤解しやすいが、両方とも焼き込み。違いはスタイル制御の有無だけ。トグル可能なソフトサブにしたいときは必ず sidecar を使う。
  2. ソフトサブは MKV が無難。 MP4 にも mov_text という字幕トラック規格はあるが、複数トラックや日本語などの非ラテン文字でプレイヤー互換性が崩れがち。sidecar.mkv をデフォルトにしているのはこの理由で、Plex/Jellyfin など一般的な Matroska ベースのアーカイブと相性がいい。
  3. extract-subtitle はテキスト字幕専用がデフォルト。 出力が .srt なので、Blu-ray の PGS や DVD の VobSub のような画像字幕は直接抜けない。OCR を別途かける別ジャンルの作業になる。テキストトラック(最近のファイルはほぼテキスト)なら一行で済む。

字幕ファイル自体をまだ用意できていない場合は、FFmpeg + Whisper で字幕自動生成 を先に読むと音声から .srt を作るパイプラインがそのまま組める。後述のレシピ 1 はこのパイプラインを subtitle に直結させる例。

焼き込み: subtitlesubtitle-burn

両コマンドとも FFmpeg の subtitles フィルタを内部で使う。libass で字幕ファイルをデコードし、エンコード前のフレームに描画する仕組みなので、出力は字幕が映像に直接ペイントされた普通の動画になる。別トラックは作られない。

subtitle — スタイル指定ありの焼き込み

C3 のメイン。フォントサイズ、色(5 プリセット)、画面上の位置(top/center/bottom)を指定できる。音声は -c:a copy でストリームコピーされるので、再エンコードがかかるのは映像だけ。

  • ソース: src/commands/subtitle.js
  • フィルタ: subtitles=<path>:force_style='FontSize=N,PrimaryColour=…,Alignment=…'
  • 音声: -c:a copy(再エンコードなし)
  • パスエスケープ: 字幕ファイルパスは絶対パスに解決され、:\: に、バックスラッシュはスラッシュに自動変換される。これは Windows のドライブレター(C:\...)や複雑なフィルタグラフで libavfilter が破綻するのを防ぐため
引数 / オプションデフォルト補足
<input>必須入力動画
<sub>必須字幕ファイル(.srt / .ass / .vtt
--font-size <n>24ピクセル
--color <name>whitewhite, yellow, red, green, cyan
--pos <position>bottombottom(Alignment 2), top(8), center(5)
-o, --output <path><元ファイル名>-subtitled.<拡張子>出力先を上書き
bash
$ npx fqmpeg subtitle input.mp4 captions.srt --dry-run

  ffmpeg -i input.mp4 -vf subtitles=/abs/path/to/captions.srt:force_style='FontSize=24,PrimaryColour=&H00FFFFFF,Alignment=2' -c:a copy input-subtitled.mp4

$ npx fqmpeg subtitle input.mp4 captions.srt --font-size 32 --color yellow --pos top --dry-run

  ffmpeg -i input.mp4 -vf subtitles=/abs/path/to/captions.srt:force_style='FontSize=32,PrimaryColour=&H0000FFFF,Alignment=8' -c:a copy input-subtitled.mp4

色名は ASS/SSA 規格の BGR 16 進にマップされる: white = &H00FFFFFFyellow = &H0000FFFFred = &H000000FF といった具合(RGB ではなく BGR 順なので「赤」が 0000FF、「シアン」が FFFF00 になる点に注意)。プリセット以外の色を使いたければ --dry-run の出力をコピーして 16 進値だけ書き換えればいい。

位置の数値は libass の Alignment 値で、1〜3 が画面下、4〜6 が中央、7〜9 が上。fqmpeg はよく使う 3 種類(bottom = 2, top = 8, center = 5)だけ公開している。左寄せや右寄せが必要なら FFmpeg を直接叩く方向に降りるしかない。

subtitle-burn — フォントサイズだけのシンプル版

subtitle と効果は同じだが、スタイル指定が --font-size だけ。.ass ファイル自身が色や位置を持っているケース(fan-translate された字幕など)や、何も考えず焼き込みたいときに使う。

  • ソース: src/commands/subtitle-burn.js
  • フィルタ: subtitles=<path>:force_style='FontSize=N'
  • パスエスケープ: なし。パスはそのまま渡される。スペースや日本語ファイル名がある場合は危険なので注意
引数 / オプションデフォルト補足
<input>必須入力動画
<srt>必須字幕ファイル(.srt または .ass
--font-size <n>24ピクセル
-o, --output <path><元ファイル名>-burned.<拡張子>出力先を上書き
bash
$ npx fqmpeg subtitle-burn input.mp4 captions.srt --dry-run

  ffmpeg -i input.mp4 -vf subtitles=captions.srt:force_style='FontSize=24' -c:a copy input-burned.mp4

$ npx fqmpeg subtitle-burn input.mp4 captions.srt --font-size 28 --dry-run

  ffmpeg -i input.mp4 -vf subtitles=captions.srt:force_style='FontSize=28' -c:a copy input-burned.mp4

使い分け: 基本は subtitle を選ぶ(パスエスケープ + スタイル指定の安全側)。.ass ファイルが自分で色や位置を持っていて、こちら側でスタイルを上書きしたくないときだけ subtitle-burn に切り替える、と覚えておけばいい。

既存トラックを読む: extract-subtitle

extract-subtitle — 字幕トラックを .srt として抜き出す

複数トラックを持つファイル(Blu-ray リッパーから出る MKV や、yt-dlp で取った多言語字幕付き動画)から字幕ストリームを 1 本だけ選んで .srt に書き出す。ストリームコピーなので入力サイズに関係なく一瞬で終わる。

  • ソース: src/commands/extract-subtitle.js
  • マッピング: -map 0:s:<n> -c:s srt
  • 出力: SubRip テキスト形式(.srt)。画像ベースの字幕(PGS、VobSub)は SRT に変換できない(OCR が必要)。
引数 / オプションデフォルト補足
<input>必須入力動画(典型的には .mkv
--stream <n>0字幕ストリームのインデックス(s:0 は 1 本目、s:1 は 2 本目…)
-o, --output <path><元ファイル名>-sub.srt出力先を上書き
bash
$ npx fqmpeg extract-subtitle movie.mkv --dry-run

  ffmpeg -i movie.mkv -map 0:s:0 -c:s srt movie-sub.srt

$ npx fqmpeg extract-subtitle movie.mkv --stream 1 -o english.srt --dry-run

  ffmpeg -i movie.mkv -map 0:s:1 -c:s srt english.srt

ファイルにどの字幕トラックが入っているかは ffprobe -v error -show_streams input.mkv | grep -E "index=|codec_type=subtitle|TAG:language" で確認するか、fqmpeg の info コマンド(ハブ記事 で解説)を使えばマッピング(s:0 = jpn, s:1 = eng など)が出る。これを見て --stream <n> に渡す。

extract-subtitleSubtitle encoding currently only possible from text to text or bitmap to bitmap というエラーが出たら、入力が画像字幕(Blu-ray の PGS、DVD の VobSub)。この場合は (1) -c:s srt-c:s copy に変えてバイナリのまま .sup.idx/.sub に書き出すか、(2) pgs2srt や Subtitle Edit のような OCR ツールを別途通すかの 2 択になる。

ソフト埋め込み: sidecar

sidecar — 外部 .srt をトグル可能なトラックとして MKV に同梱

動画と字幕ファイルを 1 本の MKV にまとめる。字幕は再生中にプレイヤー側で ON/OFF できる選択可能トラックとして格納される(多くのプレイヤーではデフォルト OFF だが、メニューから切り替えられる)。映像も音声もストリームコピーなので再エンコードはかからない。

  • ソース: src/commands/sidecar.js
  • コーデック: -c copy -c:s srt(映像/音声はそのまま、字幕は SRT として格納)
  • マッピング: 入力 0 から映像と音声、入力 1 から字幕
  • コンテナ: デフォルト MKV。MP4 にも 1 本だけなら mov_text で字幕を入れられるが、複数トラックや非ラテン文字でプレイヤー互換性が安定しないため
引数 / オプションデフォルト補足
<input>必須入力動画
<sub>必須字幕ファイル(.srt, .ass, .vtt
--language <lang>und(未指定)ISO 639-2 3 文字コード: eng, jpn, spa, fra
--title <text>なしプレイヤーのメニューに表示されるトラック名
-o, --output <path><元ファイル名>-subtitled.mkv出力先を上書き(複数字幕を入れるなら .mkv 必須)
bash
$ npx fqmpeg sidecar input.mp4 captions.srt --dry-run

  ffmpeg -i input.mp4 -i captions.srt -c copy -c:s srt -map 0:v -map 0:a -map 1:0 -metadata:s:s:0 language=und input-subtitled.mkv

$ npx fqmpeg sidecar input.mp4 captions.srt --language en --title "English" --dry-run

  ffmpeg -i input.mp4 -i captions.srt -c copy -c:s srt -map 0:v -map 0:a -map 1:0 -metadata:s:s:0 language=en -metadata:s:s:0 title=English input-subtitled.mkv

--language の値はトラックのメタデータに書き込まれ、VLC・MPV・Plex・Jellyfin などのプレイヤーが字幕メニューで言語名を表示するときの判定材料になる。互換性最大化のため ISO 639-2 の 3 文字コード(eng, jpn, spa)を使うのが無難。2 文字コード(en, ja, es)も libavformat が正規化してくれるので大体動くが、Plex の自動字幕選択ロジックなどは 3 文字コード前提で書かれていることが多い。本当に言語が分からないときは und(デフォルト)のままで OK。

複数言語の字幕(例: 英語 + 日本語)を 1 本の MKV に入れたい場合は、sidecar を 2 回適用する:

bash
npx fqmpeg sidecar input.mp4 en.srt --language eng --title "English" -o step1.mkv
npx fqmpeg sidecar step1.mkv jp.srt --language jpn --title "日本語" -o final.mkv

ステップ 1 で出力された MKV はそのまま 2 本目の入力として有効で、既存の英語トラックは保持されたまま日本語トラックが追加される。

実用レシピ

複数コマンドを組み合わせた実例。

レシピ 1: Whisper → fqmpeg の字幕自動焼き込み

チュートリアルの画面録画があり、英語キャプションを字幕ファイル手書きせずに焼き込みたい。Whisper で音声を書き起こし、subtitle で焼き込めばパイプラインは 3 ステップで完結する。

bash
# Step 1: Whisper 用に音声を抽出(16kHz モノラルで十分)
ffmpeg -i screencast.mp4 -ac 1 -ar 16000 audio.wav

# Step 2: Whisper で SRT を生成
whisper audio.wav --model small --output_format srt
# → audio.srt が生成される

# Step 3: 元動画に焼き込み(読みやすいスタイルで)
npx fqmpeg subtitle screencast.mp4 audio.srt --font-size 28 --color white --pos bottom
# → screencast-subtitled.mp4

CPU だけのノート PC で --model small を使うと所要時間は動画長の 2〜3 倍ほど(GPU があれば短く、--model large を選べば長くなる)。バッチ処理や精度を本格的に詰めたい場合は FFmpeg + Whisper で字幕自動生成 で Python スクリプト化、モデル選択、言語自動検出、品質チューニングまで踏み込んでいる。

レシピ 2: yt-dlp で取った多言語字幕を 1 本の MKV に

yt-dlp --write-subs --sub-langs "en,ja,es" --convert-subs srt URL で英・日・西の自動生成キャプションをダウンロード済み。これを 1 本の MKV にまとめてどのプレイヤーからでも切り替えられるようにする。

bash
# Step 1: 英語字幕を最初に追加
npx fqmpeg sidecar video.mp4 video.en.srt --language eng --title "English" -o step1.mkv

# Step 2: 日本語字幕を追加
npx fqmpeg sidecar step1.mkv video.ja.srt --language jpn --title "日本語" -o step2.mkv

# Step 3: スペイン語字幕を追加
npx fqmpeg sidecar step2.mkv video.es.srt --language spa --title "Español" -o final.mkv

final.mkv を VLC や MPV で開けば字幕メニューに 3 言語すべてが並ぶ。全ステップがストリームコピーなので所要時間は秒単位。Plex/Jellyfin で運用している人は、サーバー設定の優先字幕言語と組み合わせると、デバイスごとに自動で適切なトラックが選ばれる(Apple TV では日本語、ブラウザでは英語、のように)。

レシピ 3: Blu-ray リップの字幕をリスタイルして焼き込む

テキストベースの英語字幕を持つ MKV リップがあり、テレビから離れた場所で観る用に「もっと大きく、黄色で」焼き直したい。

bash
# Step 1: 英語字幕を SRT として抜き出す
npx fqmpeg extract-subtitle source.mkv --stream 0 -o english.srt

# Step 2: 大きめ・黄色で焼き込む
npx fqmpeg subtitle source.mkv english.srt --font-size 32 --color yellow --pos bottom
# → source-subtitled.mkv

ステップ 1 はストリームコピーなので一瞬。ステップ 2 は映像が再エンコードされる。元が 1080p で再生環境が 4K なら、どうせ再エンコードがかかるのでこのタイミングで compress を高めのビットレートで挟むのも有り。リップの字幕が画像形式(PGS)の場合はステップ 1 が失敗するので、pgs2srt などで OCR をかけてからステップ 2 に進む。

よくある質問

subtitle という名前なのに焼き込まれるのはなぜ?

内部で使っている FFmpeg の subtitles フィルタが「エンコード前のフレームに字幕を描画する」フィルタだから。コマンド名は「ユーザーがやりたいこと(この動画に字幕を付ける)」を表していて、技術的な仕組み(焼き込み)を表しているわけではない。トグル可能なソフトトラックが欲しいときは sidecar を使う(こちらは字幕をストリームとしてマックスする)。

subtitlesubtitle-burn の違いは結局何?

両方ともハードサブで、内部の FFmpeg フィルタも同じ。subtitle には 3 つのスタイル制御(--font-size--color--pos)があり、字幕ファイルパスを自動エスケープしてくれるので絶対パス・スペース入りパス・Windows ドライブレター付きパスのどれでも動く。subtitle-burn--font-size のみで、パスはそのまま渡される。.ass ファイルの自前スタイルを尊重したいケース以外は subtitle を選んでおけばいい。

パスエスケープの何が問題なの?

libavfilter のパーサは : をフィルタ内のオプション区切りとして解釈する。なので C:\Users\me\sub.srt のようなパスを渡すと「フィルタ "C" のオプション "Users" 」と読まれて即死する。subtitle は内部でパスを絶対化、バックスラッシュをスラッシュに置換、:\: にエスケープ、までを 1 ステップでやってくれる。Stack Overflow から subtitles= を含む FFmpeg コマンドをコピペして Windows で Unable to open エラーに当たった経験があるなら、原因はだいたいこれ。

.ass ファイルの自前スタイルで焼き込むには?

.ass(Advanced SubStation Alpha)形式は字幕ファイル自体にフォント・色・位置情報を持っており、libass がそれを忠実に再現する。subtitle--color を渡すとファイル側のスタイル指定が上書きされてしまうので、この用途では subtitle-burn のほうがクリーン。ただし .ass が参照しているフォントが OS にインストールされていないと libass がデフォルトフォントにフォールバックする点だけ注意。

抽出した SRT がタイムコードだけでテキストが空になるのは?

ほぼ 2 つのどちらか。(1) 元の字幕が画像形式(PGS / VobSub)で、FFmpeg が中身のないテキスト SRT を書き出してしまった場合 — ffprobe -show_streamscodec_name=hdmv_pgs_subtitledvd_subtitle が出ていないか確認する。(2) 元の SRT が文字エンコーディングの問題を抱えている場合 — iconv -f WINDOWS-1252 -t UTF-8 input.srt > output.srt で UTF-8 に揃えてから処理し直すと解消することが多い。SRT 自体はプレーンテキストなので、入力さえ綺麗なら抽出は完全可逆。

sidecar を MP4 に対して使えますか?

理屈上は可能(MP4 は mov_text で字幕トラックを 1 本だけ持てる)。ただし fqmpeg のデフォルトが MKV なのは、mov_text がスタイル情報を捨てる、日本語などの非ラテン文字で乱れる、モバイル Safari など一部プレイヤーで認識されない、といった実用上の不安定さがあるため。MP4 で出したい場合は -o output.mp4 を渡せば動くが、上記制約は受け入れることになる。普通に MKV のままで、VLC/MPV/Plex/Jellyfin・最近のスマート TV・hls.js で fragment 化したブラウザ再生まで全部カバーできる。

--language jpn が 2 文字(ja)ではなく 3 文字なのはなぜ?

Matroska 仕様が Language 要素に ISO 639-2(3 文字)コードを推奨していて、多くのプレイヤー実装も 3 文字を前提にしているため。fqmpeg のデフォルト und も ISO 639-2 で「言語未指定」を表す公式コード。libavformat は緩くて 2 文字(ja, en, es)も受け付けてくれるが、mediainfo や MakeMKV、Plex の自動選択ロジックは 3 文字前提のものが多いので、3 文字に揃えておくのが安全側。

動画フォルダに対して一括で字幕焼き込みするには?

普通のシェルループで十分。各動画に対応する .srt がある前提(例: lesson-01.mp4lesson-01.srt):

bash
for v in lessons/*.mp4; do
  srt="${v%.mp4}.srt"
  [ -f "$srt" ] || { echo "skip: $v (srt なし)"; continue; }
  npx fqmpeg subtitle "$v" "$srt" --font-size 28
done

出力は各動画の隣に lesson-01-subtitled.mp4 のように生成される。-f チェックを入れているので SRT がない動画はスキップされ、ループ全体が落ちることはない。

まとめ

C3 クラスタの 4 コマンドで字幕の入出力すべてをカバーできる:

  • subtitle — スタイル指定付き焼き込み(基本これを選ぶ)
  • subtitle-burn — 焼き込みのみ。.ass の自前スタイルを活かしたいときに
  • extract-subtitle — MKV/MP4 から既存トラックを SRT として抜き出す
  • sidecar — 外部 SRT をトグル可能なソフトトラックとして MKV に同梱

どのコマンドも --dry-run で生の FFmpeg 呼び出しが見られるので、コピーして調整(カスタム HEX 色、ISO 639-3 言語コード、MP4 出力など)したり、フィルタ構文を学んだりするときに再利用できる。音声から字幕を自動生成するパイプラインは Whisper 字幕自動生成 を、fqmpeg 全体像は fqmpeg 完全ガイド を参照。