32blogby Studio Mitsu

fqmpegフォーマット変換&配信: GIF/HLS/DASH

fqmpegの7動詞でコンテナ変換・GIF生成・HLS/DASH配信・ファイル分割・ストリーム抽出までをFFmpeg実装ソース読みベースで解説

by omitsu26 min read
目次

fqmpeg の C2 クラスタは「ファイルの形を変える / 配信用に整える」7つの動詞をまとめたものだ。convert でコンテナを差し替え、gif / gif-to-video で GIF と MP4 の往復、hlsdash でアダプティブ配信用マニフェスト生成、segment で固定長分割、extract-stream で動画 / 音声 / 字幕トラックを 1 本だけ抜き出す。コーデックの選択は C1 の話で、ここで扱うのは「次に何の形にするか」だけ。

この記事では各コマンドの内部 FFmpeg 呼び出し・デフォルト・出力ファイル名規則を実装ソース読みで確認しつつ、最後にこれらを組み合わせた実用的なパイプラインを 3 つ紹介する。すべて fqmpeg 3.0.1src/commands/ で検証済み。

この記事で得られるもの

  • どの動詞をどの場面で選ぶか(決定マトリックス)
  • 各動詞が生成する FFmpeg コマンド(--dry-run で取得した実出力)
  • デフォルト値・許容範囲・出力ファイル名のルール
  • 静的ホストでの HLS 配信、長い動画から GIF プレビュー、複数音声の MKV から欲しいトラックだけ抜く、の 3 レシピ

どの動詞をどの場面で選ぶか

このクラスタは 2 軸で考えると整理しやすい。1 つは「1 ファイル出力か / セグメント出力か」、もう 1 つは「画素を再エンコードするか / 容器だけ差し替えるか」。

やりたいこと動詞中身出力
容器だけ差し替え(再エンコードなし)convert --copyストリームコピー remux<名前>.<拡張子>
容器差し替え + 再エンコードconvertデコード + 再エンコード<名前>.<拡張子>
GIF プレビューを作るgif切り出し + パレット最適化 + スケール<名前>-gif.gif
GIF を MP4 に昇格gif-to-video偶数化 pad + yuv420p<名前>.mp4
HLS プレイリストhls.m3u8 + .ts セグメント<名前>.m3u8 + _%03d.ts
MPEG-DASH マニフェストdash.mpd + DASH セグメント<名前>.mpd
固定長で分割segmentストリームコピー -f segmentsegment_%03d.<ext>
ストリームを 1 本だけ抜くextract-stream-map 0:<spec> -c copy<名前>-stream-<spec>.<ext>

読み進める前に押さえておきたいポイントは 2 つ。

  1. ストリームコピーか再エンコードか。 convert --copy / segment / extract-stream-c copy モードで、画素を一切いじらない。容器だけ書き換えるので数 GB のファイルでも数秒で終わる。逆に convert(オプションなし)/ gif / gif-to-video / hls / dash は再エンコードする。判断に迷ったらまず stream-copy 形式から試すのが定石。
  2. 1 ファイル出力か、セグメント出力か。 hls / dash / segment はマニフェストと多数のセグメントファイルをカレントディレクトリにバラ撒く。ソースファイルがあるディレクトリで実行すると .ts ファイルが大量に並ぶハメになるので、出力用のサブディレクトリを掘ってから走らせる癖をつけておくと安全。

コーデックや圧縮率の側の話は 動画圧縮ガイド が補完記事になる。HLS で実際に CDN まで持っていくエンドツーエンドの流れは HLS CDN ストリーミング構築ガイド が詳しい。

コンテナ変換

convert — 容器を差し替える(mp4 ↔ mov ↔ mkv ↔ webm ↔ mp3 ...)

容器のリネーム。デフォルトでは FFmpeg が新しい容器に合うコーデックを選んで再エンコードする。--copy を付けると音声 / 動画ストリームを触らず、容器だけ書き換える。

  • ソース: src/commands/convert.js
  • デフォルト: デコード + 再エンコード(容器デフォルトコーデック)
  • --copy: -c copy を追加して remux のみ。高速・無劣化。ただしソースのコーデックが新しい容器で合法なときだけ通る
引数 / オプションデフォルト補足
<format> (位置引数)必須拡張子。先頭ドットは剥がされる(.movmov も同じ扱い)
--copyoffストリームコピーモード
-o, --output <path><入力名>.<format>出力パス上書き
bash
$ npx fqmpeg convert input.mp4 mov --dry-run

  ffmpeg -i input.mp4 input.mov

$ npx fqmpeg convert input.mp4 mov --copy --dry-run

  ffmpeg -i input.mp4 -c copy input.mov

--copy が通る場合は数 GB のファイルでも 1 秒以下で終わる。デコードを一切しないからだ。逆に通らないケースもあって、それはコーデックと容器の相性の問題:

  • MP4 ↔ MOV: H.264 + AAC ならどちらでもコピー可能
  • MP4 ↔ MKV: MKV はかなり寛容なのでほぼ何でも通る
  • WebM: VP8 / VP9 / AV1 + Vorbis / Opus しか入らないので、H.264 を .webm にコピーするのは失敗する。その場合は C1 の encode-vp9 を使う

Could not find tag for codec ... in stream というエラーが出たらコーデックと容器が不整合なサイン。--copy を外して再エンコードに落とすか、容器をもっと寛容な MKV にするのが解決策。

GIF の往復

gif — 動画クリップから GIF を作る

動画から指定区間を切り出して高品質な GIF を書き出す。デフォルトの 480px × 15fps × 5秒 は README 埋め込み用途を想定したサイズ感(4MB の README 制限内に収まる範囲で読める大きさ)。

  • ソース: src/commands/gif.js
  • 品質のキモ: palettegen / paletteuse の 2 パスフィルタを使う。クリップから最適な 256 色パレットを生成して 2 パス目で適用する。素朴な GIF エンコードと比べると劇的にきれい(バンディングなし、ディザノイズなし)
  • スケーラ: scale=<width>:-1:flags=lanczos — 高さは自動、lanczos は高品質リサンプル
  • ループ: -loop 0(無限)
オプションデフォルト範囲 / 形式補足
-s, --start <sec>0非負数開始秒
-d, --duration <sec>5正数GIF の長さ
--fps <n>15正整数高いほど滑らか・ファイル肥大
--width <px>480正整数高さはアスペクト比保持で自動算出

デフォルト出力ファイル名: <入力名>-gif.gif

bash
$ npx fqmpeg gif input.mp4 --dry-run

  ffmpeg -ss 0 -t 5 -i input.mp4 -vf fps=15,scale=480:-1:flags=lanczos,split[s0][s1];[s0]palettegen[p];[s1][p]paletteuse -loop 0 input-gif.gif

# カスタム: 10秒地点から3秒、20fps、幅600px
$ npx fqmpeg gif input.mp4 --start 10 --duration 3 --fps 20 --width 600 --dry-run

  ffmpeg -ss 10 -t 3 -i input.mp4 -vf fps=20,scale=600:-1:flags=lanczos,split[s0][s1];[s0]palettegen[p];[s1][p]paletteuse -loop 0 input-gif.gif

gif-to-video — GIF を MP4 に昇格

逆方向の変換。GIF を H.264 MP4 にする。サイズが小さくなり、<video> タグがどこでも動くようになる。

  • ソース: src/commands/gif-to-video.js
  • ピクセル形式: -pix_fmt yuv420p(ブラウザ / コーデック互換性のため必須。8-bit RGB MP4 はほとんどのデコーダが拒否する)
  • 偶数化: -vf scale=trunc(iw/2)*2:trunc(ih/2)*2 — H.264 は偶数の幅 / 高さを要求するので、奇数なら 1 ピクセル切り捨てる
  • Faststart: -movflags faststart で moov アトムを先頭に移し、ダウンロード途中でも再生開始できるようにする
  • ループ: -stream_loop でソースを N 回繰り返す。--loop 0 だと 1 回再生(ソースをそのまま 1 本入れる)、--loop 3 だと 4 回再生になる
オプションデフォルト範囲補足
--loop <n>0非負整数0 = 1 回再生。3 = 4 回再生

デフォルト出力ファイル名: <入力名>.mp4

bash
$ npx fqmpeg gif-to-video input.gif --dry-run

  ffmpeg -i input.gif -movflags faststart -pix_fmt yuv420p -vf scale=trunc(iw/2)*2:trunc(ih/2)*2 input.mp4

# GIF を 4 回再生する MP4 に
$ npx fqmpeg gif-to-video input.gif --loop 3 --dry-run

  ffmpeg -stream_loop 3 -i input.gif -movflags faststart -pix_fmt yuv420p -vf scale=trunc(iw/2)*2:trunc(ih/2)*2 input.mp4

一番ありがちな用途は「肥大化したチュートリアル GIF を縮める」目的。12MB の GIF が体感画質ほぼ変わらず 600KB の MP4 になることはザラ。

アダプティブ配信

この 2 つの動詞は CDN に置いて hls.js / dash.js で再生するタイプの出力を作る。

hls — HLS プレイリスト + セグメント

HTTP Live Streaming。Apple のアダプティブ配信フォーマットで、Safari と iOS でネイティブ、それ以外は hls.js でカバーできる。出力は .m3u8 プレイリスト + .ts(MPEG-TS)セグメント群。

  • ソース: src/commands/hls.js
  • 動画コーデック: libx264 固定(.ts 容器と .m3u8 プレイヤーの H.264 サポートが最も強いため)
  • 音声コーデック: aac
  • -hls_list_size 0: すべてのセグメントをプレイリストに残す(VOD モード)。ライブのローリングウィンドウなら下げるが、VOD が一般用途
  • セグメントファイル名パターン: プレイリストと同じディレクトリの <プレイリスト名>_%03d.ts
オプションデフォルト範囲補足
--segment <sec>6正数セグメント長。Apple は VOD で 6 秒推奨、低遅延で 2〜4 秒
-o, --output <path><入力名>.m3u8パスプレイリスト出力先 + セグメント配置場所
bash
$ npx fqmpeg hls input.mp4 --dry-run

  ffmpeg -i input.mp4 -c:v libx264 -c:a aac -hls_time 6 -hls_list_size 0 -hls_segment_filename input_%03d.ts input.m3u8

ここで生成されるのは「アダプティブ配信のラダー 1 段分」だけだ。hls は複数ビットレート版のマスタープレイリストは作らない。本格的なアダプティブにするには C1 の bitrate でソースを 3〜5 種類のビットレートでエンコードして、各 .m3u8 を参照するマスタープレイリストを手で書く必要がある。R2 / S3 のデプロイまで含めた具体的な流れは HLS CDN ストリーミング構築ガイド を参照。

dash — MPEG-DASH マニフェスト

W3C 標準のアダプティブ配信フォーマット。出力は .mpd マニフェスト + フラグメント MP4 セグメント群。再生は dash.js でほとんどのブラウザがカバー。Safari は HLS 寄りだが、hls.js の DASH 実験モードや MSE 経由で動く。

  • ソース: src/commands/dash.js
  • 動画 / 音声: libx264 + aac(HLS と同じデフォルト)
  • -use_timeline 1 -use_template 1: SegmentTemplate ベースのマニフェストを生成する。VOD では SegmentList より小さくて新しい形式
オプションデフォルト補足
--segment <sec>4DASH のデフォルトは HLS より短い(4 秒 vs 6 秒)。DASH プレイヤーは短いセグメントへの耐性が高い
-o, --output <path><入力名>.mpd
bash
$ npx fqmpeg dash input.mp4 --dry-run

  ffmpeg -i input.mp4 -c:v libx264 -c:a aac -f dash -seg_duration 4 -use_timeline 1 -use_template 1 input.mpd

HLS と同じく単一レンディションのみ生成する。複数ビットレートの DASH ラダーが必要なら、bitrate で複数バージョン作ってマニフェストを統合するか、--dry-run の出力を起点に FFmpeg の adaptation_sets 引数を直書きする。

選び方の目安: 視聴者が iOS / Safari 中心なら HLS、Android TV / スマート TV / W3C 準拠スタックなら DASH。プロダクション規模だと両方ともリリースしておくのが定石だが、個人プロジェクトなら hls.js のおかげで HLS 単独で 9 割以上カバーできる。

分割とストリーム抽出

segment — 固定長で分割

入力を指定秒数のチャンクに分割する。ストリームコピーなのでほぼ瞬時に終わる。出力ファイル名は printf スタイルの番号付きパターン。

  • ソース: src/commands/segment.js
  • モード: -f segment -c copy(再エンコードなし)
  • -reset_timestamps 1: 各セグメントの PTS を 0 にリセット。セグメント単独で再生できる
  • 容器: 入力の拡張子を保持する(input.mp4 のセグメントは .mp4input.mkv のセグメントは .mkv
引数 / オプションデフォルト補足
<duration> (位置引数)必須セグメント長(秒)
-o, --output <pattern>segment_%03d.<入力拡張子>printf パターン。%03d001, 002, … になる
bash
$ npx fqmpeg segment input.mp4 10 --dry-run

  ffmpeg -i input.mp4 -c copy -f segment -segment_time 10 -reset_timestamps 1 segment_%03d.mp4

実用上の注意:

  • キーフレーム単位でしか切れない。 ストリームコピーモードは既存の GOP 境界でしか分割できない。ソースの GOP 間隔が 5 秒なら、10 秒指定でも実際の分割点は 9.7 秒だったり 10.3 秒だったりする。フレーム精度の分割が必要なら、--dry-run で出力したコマンドから -c copy を外して再エンコードに切り替えるか、ソース側で先に -g 30 -keyint_min 30 などで GOP を短くしておく
  • HLS のセグメントとは違う。 HLS はマニフェスト + チャンクのペアを生成するが、segment はチャンクだけ。HLS が欲しいなら hls を使う
  • デフォルトの出力パターンは CWD にバラ撒く。 出力用ディレクトリで実行するか、-o /path/to/dir/seg_%03d.mp4 と明示する

extract-stream — インデックス指定でストリームを 1 本抜く

複数ストリームのファイルから動画 / 音声 / 字幕トラックを 1 本だけ抜き出す。ストリームコピーのみ — 高速・無劣化。引数は FFmpeg のストリームスペシファイア構文に従う: v:0(最初の動画)、a:1(2 つめの音声)、s:0(最初の字幕)。

  • ソース: src/commands/extract-stream.js
  • マッピング: -map 0:<spec> -c copy
  • 出力拡張子: ストリーム種別から自動決定 — v:*.mp4a:*.aacs:*.srt。実コーデックがこれと違う(音声が Opus、動画が HEVC など)場合は --output で上書きする
引数補足
<input>入力ファイル
<stream>ストリームスペシファイア: v:N, a:N, s:N

デフォルト出力ファイル名:

  • v:0<入力名>-stream-v0.mp4
  • a:1<入力名>-stream-a1.aac
  • s:0<入力名>-stream-s0.srt
bash
$ npx fqmpeg extract-stream input.mkv v:0 --dry-run

  ffmpeg -i input.mkv -map 0:v:0 -c copy input-stream-v0.mp4

$ npx fqmpeg extract-stream input.mkv a:1 --dry-run

  ffmpeg -i input.mkv -map 0:a:1 -c copy input-stream-a1.aac

$ npx fqmpeg extract-stream input.mkv s:0 --dry-run

  ffmpeg -i input.mkv -map 0:s:0 -c copy input-stream-s0.srt

ファイルにどんなストリームが入っているか確認するには、ハブ記事の 4 つのインスペクション動詞 のうち info を使うか、ffprobe -v error -show_streams input.mkv を直接叩く。インデックスマップ(v:0, v:1, a:0, a:1, ...)が得られる。

実用レシピ

各レシピは複数の動詞(クラスタを跨ぐ場合あり)を組み合わせた実際のワークフロー。

レシピ 1: 静的ホスト HLS パイプライン

30 分の動画を静的ホスト(Cloudflare R2、S3 + CloudFront、Vercel など)から <video> タグ + hls.js で配信する。シングルレンディションで十分なケース。

bash
# Step 1: 配信用ビットレートに圧縮
npx fqmpeg compress source.mov --crf 22 --preset slow
# → source-compressed.mp4

# Step 2: 出力ディレクトリを切って HLS パッケージング
mkdir hls-out
npx fqmpeg hls source-compressed.mp4 -o hls-out/stream.m3u8
# → hls-out/stream.m3u8 + hls-out/stream_001.ts ... stream_NNN.ts

hls-out/ をパブリック読み取り可能でホストにアップロード。HTML 側:

html
<video controls></video>
<script type="module">
  import Hls from "https://cdn.jsdelivr.net/npm/hls.js@latest/+esm";
  const hls = new Hls();
  hls.loadSource("https://cdn.example.com/hls-out/stream.m3u8");
  hls.attachMedia(document.querySelector("video"));
</script>

複数ビットレートのラダー、CDN のキャッシュヘッダ、バイトレンジリクエスト対応などプロダクション設定は HLS CDN ストリーミング構築ガイド で扱っている。

レシピ 2: 長い編集動画から GIF プレビュー

4 分のスクリーンキャストを MP4 で書き出した。README ヒーロー画像用に GIF が必要で、サイズは 2MB 以下、横幅 600px くらい。

bash
# Step 1: 代表的な 5 秒ウィンドウを選ぶ
# 編集タイムラインで 1:30 あたりにいい場面があるとして
# Step 2: その区間を GIF に
npx fqmpeg gif screencast.mp4 --start 90 --duration 5 --width 600 --fps 12
# → screencast-gif.gif

なぜ --fps 12 でデフォルトの 15 じゃないのか? トーキングヘッド系やマウスカーソル中心のスクリーンキャストだと 12fps でも体感の滑らかさはほぼ変わらず、ファイルが 2 割小さくなるから。それでもサイズオーバーなら --fps 10--width 480 まで落とす。

GIF の調整でもサイズに収まらない場合の正解は、たいてい「GIF をさらに削る」ではなく「MP4 で配信する」だ。compress で MP4 化して <video autoplay loop muted playsinline> で埋め込めば、見た目は同じで 10 倍以上小さく、画質もシャープになる。

レシピ 3: 多トラック MKV を切り分ける

Blu-ray リップの .mkv に音声 3 トラック(日本語、英語、英語コメンタリー)と字幕 2 トラック(英語、英語 SDH)が入っている。日本語音声と英語字幕を別ファイルにして、最終的にクリーンな MP4 + 字幕 sidecar を作りたい。

bash
# Step 1: ストリームを確認(ハブの info 動詞か、ffprobe を直接叩く)
ffprobe -v error -show_streams source.mkv | grep -E "index=|codec_type=|TAG:language"
# 仮: v:0 H.264, a:0 jpn, a:1 eng, a:2 eng-comm, s:0 eng, s:1 eng-sdh

# Step 2: 日本語音声を抽出
npx fqmpeg extract-stream source.mkv a:0 -o ja.aac

# Step 3: 英語字幕を抽出
npx fqmpeg extract-stream source.mkv s:0 -o eng.srt

# Step 4: 動画を抽出
npx fqmpeg extract-stream source.mkv v:0 -o video.mp4

# Step 5: 動画 + 日本語音声を 1 本の MP4 に結合
# (この最後の合成は C2 の範囲外で、生の ffmpeg を使う)
ffmpeg -i video.mp4 -i ja.aac -c copy -map 0:v -map 1:a final.mp4

「MKV → 動画 + 選んだ音声 + 字幕 sidecar」の流れは「Blu-ray リップを iPhone で観られる形に変える」の中核パターン。extract-stream がストリームコピーで再エンコードしないので、4GB のファイルでも数秒で展開できる。最後の結合は普通の FFmpeg 呼び出しで、必要なら --dry-run で近い動詞(例えば convert をカスタムフラグ付きで)の出力を起点に書き換えてもいい。

よくある質問

Q1: convert --copy で「Could not find tag for codec」エラーが出る

ソースのコーデックがターゲット容器で合法じゃないため。WebM は VP8/VP9/AV1 動画 + Vorbis/Opus 音声しか入らないので、H.264 を .webm にコピーすると拒否される。MP4 は H.264/H.265/AV1 動画は OK だが VP9 はダメ。--copy を外して再エンコードするか、もっと寛容な .mkv に逃げるのが解決策。

Q2: hls は複数ビットレートのアダプティブラダーを作る?

作らない。生成されるのは単一レンディション(1 本の .m3u8 + チャンク)だけ。複数ビットレートのアダプティブにしたいなら、圧縮クラスタbitrate で 2〜5 種類のビットレートでソースをエンコードし、各レンディションの .m3u8 を参照するマスタープレイリストを手で書く。具体的なフォーマットは HLS CDN ストリーミング構築ガイド で扱っている。

Q3: HLS と DASH、どっちで配信すべき?

視聴者が iOS / Safari / Apple TV 中心なら HLS。Apple エコシステムでは HLS がネイティブで DASH は二級市民扱い。Android TV、スマート TV、W3C 準拠スタックなら DASH。大規模配信サービスはリクエスト時に多重化して両方流すのが普通。個人プロジェクトなら hls.js のおかげで HLS 単独で視聴者の 9 割以上をカバーできる。

Q4: segment の分割が秒単位でズレるのはなぜ?

segment はストリームコピーモードで動くので、既存のキーフレーム(GOP 境界)でしか切れないから。ソースの GOP 間隔が 5 秒なら、10 秒指定の分割点は実際には 5 秒〜15 秒のあいだで、10 秒に最も近い場所になる。フレーム精度で切りたい場合は --dry-run の出力から -c copy を外して再エンコードに切り替えるか、エンコード時点で -g 30 -keyint_min 30 などで GOP を短くしておく。

Q5: gif のデフォルト出力が ffmpeg -i in.mp4 out.gif よりずっと綺麗なのはなぜ?

素朴な 1 行は GIF のネイティブパレット(クリップから導出されない固定 256 色)を使うのでバンディングとディザノイズが出る。gif は FFmpeg の 2 パスフィルタ palettegen / paletteuseそのクリップに最適な パレットを動的に計算する。Stack Overflow で「FFmpeg で高品質 GIF」を検索すると同じ手順がいくつも引っかかるが、それを毎回打つ手間を gif がなくしてくれる。

Q6: extract-stream の出力拡張子をカスタムにできる?

できる。-o output.<ext> を渡せば任意の拡張子で書き出せる。デフォルトは AAC / SRT / MP4 を仮定しているのでほとんどのファイルでは合うが、MKV 内の Opus 音声などは extract-stream input.mkv a:0 -o audio.opus のように明示する必要がある。-c copy 指定なのでソースストリームの中身は変わらず、拡張子だけ実コーデックに合わせるのが目的。

Q7: 複数ファイルを一気に segment したい

シェルで回すのが最短:

bash
for f in *.mp4; do
  mkdir -p "segments/${f%.mp4}"
  ( cd "segments/${f%.mp4}" && npx fqmpeg segment "../../$f" 60 )
done

segment 呼び出しを専用サブディレクトリで実行することでチャンク同士が衝突しない。外側の ( ... )cd を次のループに漏らさないためのもの。

Q8: HLS の Low-Latency (LL-HLS) はサポートしてる?

専用フラグはない(標準 HLS のみ生成)。LL-HLS が欲しい場合は --segment 12 にして、--dry-run の出力に FFmpeg の -hls_flags +program_date_time -hls_segment_type fmp4 と部分セグメント関連オプションを手で足す。Apple の HTTP Live Streaming 仕様 が部分セグメントとレンディションレポートの正規リファレンス。

まとめ

C2 の 7 動詞はコーデックが決まったあとの「ファイルの形」の決定をカバーする:

  • convert — 同じ中身を別の容器に(--copy で瞬時に remux)
  • gif / gif-to-video — GIF 往復
  • hls / dash — アダプティブ配信マニフェスト
  • segment — 固定長チャンク分割
  • extract-stream — ストリーム単位の摘出

どの動詞も --dry-run を付ければ生の FFmpeg 呼び出しが見えるので、コピペして使う・調整する(LL-HLS、カスタムセグメントパターン、NVENC への差し替え)・FFmpeg 構文の学習素材にする、すべてが可能。fqmpeg 全体の俯瞰は fqmpeg コンプリートガイド に戻る。コーデック側の決定は 圧縮 & エンコード詳細記事 を参照。