32blogby Studio Mitsu

fqmpegで動画編集: トリム・速度・連結・フレーム

fqmpegの15動詞で動画の時間軸を編集する。trim/concat/speed/fade/interpolate などの実装と落とし穴をソース読みで解説。

by omitsu32 min read
目次

fqmpeg の C4 クラスタは動画の時間軸を操作する 15 個の動詞だ。切る (trim, split)・繋げる (concat, crossfade)・繰り返す/逆再生する (loop, reverse, boomerang)・速度を変える (speed, interpolate, fps)・端をフェードする (fade, fade-between)・特定のフレームを止める/伸ばす (freeze, repeat-frame, frame-step) — 「素材ができた」から「公開できる」までの間の編集操作を一通りカバーする。

この記事では各動詞が生成する FFmpeg コマンド、デフォルト値、出力ファイル名規則、そして裏で使われているフィルタ由来の落とし穴(atempo の 0.5–2.0 制約、xfade の offset セマンティクス、tpad のタイムスタンプ計算)を 1 つずつ解説する。記述は fqmpeg 3.0.1src/commands/ ソースを実読して検証している。

この記事で得られるもの

  • 15 動詞をタスク別(カット/結合/時間/トランジション/フレーム)に分類する判断マトリックス
  • 各動詞が生成する正確な FFmpeg コマンド(--dry-run 出力で検証済み)
  • 全動詞のデフォルト値・許容引数・出力ファイル名規則
  • スムーズなスローモーション生成パイプラインを含む実用レシピ 3 本

15 動詞の全体像

クラスタは 4 つのタスクグループに綺麗に分かれる。グループから動詞へとたどればよい。

グループ動詞用途
カットと結合trim, split, concat1 区間を切り出す / 等間隔で分割する / 複数ファイルを繋ぐ
時間再生loop, reverse, speed, boomerangN 回再生 / 逆再生 / 早送り遅送り / 順再生→逆再生
トランジションcrossfade, fade, fade-between2 クリップをブレンド / 端をフェード / 間に黒を挟む
フレーム単位freeze, repeat-frame, frame-step, interpolate, fps1 フレームを止める / 最終フレームを伸ばす / 間引く / 補間する / フレームレート変更

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

  1. trim はキーフレーム精度・フレーム精度ではない-c copy を使うため、カットは指定時刻直前のキーフレームにスナップする。フレーム精度のカットが必要なら再エンコードになる — トレードオフは FFmpeg 無劣化カット記事 で詳しく扱った
  2. speed は大きな倍率で atempo をチェーンする。FFmpeg の atempo は 1 段あたり 0.5–2.0 しか受け付けないので、4× は内部的に atempo=2.0,atempo=2 のチェーンになる。fqmpeg が自動で組んでくれるので倍率は何でも渡してよい
  3. interpolate は CPU 重い。動き補償補間はノートPCで実時間の 5〜20 倍。スムーズな動きが本当に必要なとき(スローモ、フレームレートアップコンバート)だけ使う。単に目標レートに合わせるだけ(タイムコード変換など)なら fps で十分

カットと結合

trim — 区間を切り出す(ストリームコピー)

--start--duration または --to の間を切り出す。-c copy なので高速だが、カット位置は直前のキーフレームにスナップする。フレーム精度のカットが必要なら継ぎ目を再エンコードする必要がある。

  • ソース: src/commands/trim.js
  • コーデック: -c copy -map 0(パススルー、全ストリーム)
  • 時刻形式: 秒 (30) または HH:MM:SS (00:01:30)
  • --duration または --to のいずれかが必須 — どちらも指定しないと Error: specify --duration (-d) or --to (-t). で終了
引数 / オプションデフォルト備考
<input>必須入力動画
-s, --start <time>0開始時刻
-d, --duration <time>--start からの長さ
-t, --to <time>終了時刻(--duration と排他)
-o, --output <path><入力名>-trimmed.<拡張子>出力先を上書き
bash
$ npx fqmpeg trim input.mp4 --start 00:00:10 --duration 00:00:30 --dry-run

  ffmpeg -i input.mp4 -ss 00:00:10 -t 00:00:30 -c copy -map 0 input-trimmed.mp4

カット開始位置がキーフレームでない場合、FFmpeg は直前のキーフレームまで遡るため、出力が要求より少し長くなったり最初の数百ミリ秒がフリーズしたりする。これは -c copy の代償。継ぎ目だけ再エンコードする 2 段階アプローチは FFmpeg 無劣化カット記事 で扱っている。

split — 等間隔で分割

FFmpeg の segment muxer を使って N 秒ごとのセグメントに分割する。ストリームコピーなので一瞬で終わる。デフォルト出力パターンは %03d(3 桁ゼロパディング): input-part000.mp4, input-part001.mp4, ...

  • ソース: src/commands/split.js
  • muxer: -f segment -segment_time <s> -reset_timestamps 1
  • コーデック: -c copy -map 0
引数 / オプションデフォルト備考
<input>必須入力動画
<seconds>必須セグメント長(秒)
-o, --output <pattern><入力名>-part%03d.<拡張子>printf 形式のプレースホルダ付きパターン
bash
$ npx fqmpeg split input.mp4 60 --dry-run

  ffmpeg -i input.mp4 -c copy -map 0 -f segment -segment_time 60 -reset_timestamps 1 input-part%03d.mp4

trim と同じく、セグメント境界はキーフレームにスナップするため、「60 秒」と指定しても実際は 58–62 秒になることがある(GOP 配置次第)。-reset_timestamps 1 で各セグメントの PTS が 0 にリセットされる — これは下流のツール(HLS, DASH, プレイヤ)が期待する動作。

concat — 複数ファイルを繋ぐ

2 つ以上の動画を連結する。2 モードある:

  • demuxer モード(デフォルト、ストリームコピー): 絶対パスで一時 filelist.txt を生成し、-f concat -safe 0 で繋ぐ。終了時に一時ファイルは削除。高速だが、入力のコーデック/コンテナ/解像度が一致している必要がある

  • 再エンコードモード (--re-encode): filter_complex concat でコーデック・解像度が異なる入力でも連結できる。遅いが寛容

  • ソース: src/commands/concat.js

  • デフォルトサフィックス: -joined

引数 / オプションデフォルト備考
<inputs...>必須2 つ以上の動画ファイル
--re-encodeofffilter ベースの concat に切り替え(コーデック不一致時に使う)
-o, --output <path><最初の入力名>-joined.<拡張子>出力先を上書き
bash
$ npx fqmpeg concat clip1.mp4 clip2.mp4 clip3.mp4 --dry-run

  # File list (auto-generated):
  # file '/abs/path/clip1.mp4'
  # file '/abs/path/clip2.mp4'
  # file '/abs/path/clip3.mp4'

  ffmpeg -f concat -safe 0 -i filelist.txt -c copy clip1-joined.mp4

$ npx fqmpeg concat clip1.mp4 clip2.mp4 --re-encode --dry-run

  ffmpeg -i clip1.mp4 -i clip2.mp4 -filter_complex [0:v][0:a][1:v][1:a]concat=n=2:v=1:a=1[outv][outa] -map [outv] -map [outa] clip1-joined.mp4

demuxer モードの --dry-run 出力には自動生成されたファイルリストがコメントブロックで含まれる — 絶対パス周りのデバッグに便利。実際の filelist.txt は入力と同じディレクトリにハッシュ付きの名前 .fqmpeg-concat-1715450000000.txt で書かれ、プロセス終了時に unlink される(実行後にファイルシステムには残らない)。

demuxer モードで Non-monotonous DTS in output streamCould not write header が出たら、入力のタイムスタンプ・コーデック・解像度が異なる可能性が高い。--re-encode を付けて再試行する。

時間再生

loop — N 回再生

-stream_loop を使って N 回ループする。ストリームコピーなので一瞬。

  • ソース: src/commands/loop.js
  • フィルタ: -stream_loop <N-1>(FFmpeg の stream_loop は「追加」ループ数を数えるので、loop 3 = 3 回再生 = stream_loop 2
  • デフォルトサフィックス: -loop<N>
引数 / オプションデフォルト備考
<input>必須入力動画
<count>必須再生回数(3 = 3 回再生)
-o, --output <path><入力名>-loop<N>.<拡張子>出力先を上書き
bash
$ npx fqmpeg loop input.mp4 3 --dry-run

  ffmpeg -stream_loop 2 -i input.mp4 -c copy input-loop3.mp4

count は正の数値としてバリデーションされ、整数部分だけが FFmpeg に渡る (Math.floor(n) - 1)。0 や負数を渡すと FFmpeg を呼ぶ前にバリデータエラーになる。

reverse — 逆再生

reverse ビデオフィルタと areverse オーディオフィルタを適用するため、映像も音声も逆再生になる。--no-audio で音声を完全にドロップ可能(areverse が音声ストリーム全体をメモリにバッファするため、長尺ではこちらの方がはるかに速い)。

  • ソース: src/commands/reverse.js
  • フィルタ: -vf reverse -af areverse(または --no-audio-vf reverse -an
引数 / オプションデフォルト備考
<input>必須入力動画
--no-audio(音声保持)音声を捨てる(長尺で高速化)
-o, --output <path><入力名>-reversed.<拡張子>出力先を上書き
bash
$ npx fqmpeg reverse input.mp4 --dry-run

  ffmpeg -i input.mp4 -vf reverse -af areverse input-reversed.mp4

speed — 再生速度を変える

setpts で映像を、1 つ以上の atempo チェーンで音声を再タイミングする。atempo のチェーン化を fqmpeg が自動でやってくれるので、倍率は何でも渡せる。

  • ソース: src/commands/speed.js
  • 映像フィルタ: setpts=(1/speed)*PTS
  • 音声フィルタ: atempo チェーン(FFmpeg の atempo は 1 段 0.5–2.0 のみ。4× は atempo=2.0,atempo=2、0.25× は atempo=0.5,atempo=0.5
  • デフォルトサフィックス: 倍速時は <N>x、スロー時は slow<N>x
引数 / オプションデフォルト備考
<input>必須入力動画
<factor>必須速度倍率(2 = 2×、0.5 = 半速)
--no-audio(音声保持)音声を捨てる(タイムラプス時に有用)
-o, --output <path><入力名>-<サフィックス>.<拡張子>出力先を上書き
bash
$ npx fqmpeg speed input.mp4 2 --dry-run

  ffmpeg -i input.mp4 -filter:v setpts=0.500000*PTS -filter:a atempo=2 input-2x.mp4

$ npx fqmpeg speed input.mp4 4 --dry-run

  ffmpeg -i input.mp4 -filter:v setpts=0.250000*PTS -filter:a atempo=2.0,atempo=2 input-4x.mp4

$ npx fqmpeg speed input.mp4 0.5 --dry-run

  ffmpeg -i input.mp4 -filter:v setpts=2.000000*PTS -filter:a atempo=0.5 input-slow0.5x.mp4

タイムラプス用途(高倍速・音声不要)では必ず --no-audio を付ける。atempo を 4 段 5 段とチェーンするのは技術的には動くが、結果は耳に痛いアーティファクトだらけになる。スローモーションが目的なら speed 0.5 は単にフレーム複製になるだけ。中間フレームを合成したいならレシピ 3 のように interpolate をチェーンする。

boomerang — 順再生 + 逆再生

映像ストリームを split して片方を逆再生し、順 + 逆を concat してシームレスなピンポンループを作る。映像は再エンコード、音声はストリームコピー(または --no-audio でドロップ)。

  • ソース: src/commands/boomerang.js
  • フィルタ: [0:v]split[fwd][rev];[rev]reverse[reversed];[fwd][reversed]concat=n=2:v=1:a=0
bash
$ npx fqmpeg boomerang input.mp4 --dry-run

  ffmpeg -i input.mp4 -filter_complex [0:v]split[fwd][rev];[rev]reverse[reversed];[fwd][reversed]concat=n=2:v=1:a=0 -c:a copy input-boomerang.mp4

音声処理は意図的にシンプル: フィルタグラフは映像のみ concat するため、コピーされた音声 (-c:a copy) は順再生分だけ流れて途中で終わる。Instagram 風クリップなら気にならない(オートプレイで音消し前提のため)。音声も含めてエフェクトの一部にしたいなら --no-audio で意図的な無音にするか、後処理で audio 系動詞でカスタムトラックを作る。

トランジション

crossfade — 2 クリップを xfade でブレンド

FFmpeg の組み込み xfade トランジションを 2 つの動画間に適用する。デフォルトでは ffprobe を clip1 にかけて長さを検出し、トランジションが clip1 の終わり際から始まるよう offset を自動計算する — つまり clip1 が最後まで再生され、そのまま clip2 にスムーズにクロスフェードする。音声側も acrossfade で並行してクロスフェードする。

  • ソース: src/commands/crossfade.js
  • フィルタ(デフォルト): [0:v][1:v]xfade=transition=<種類>:duration=<秒>:offset=<auto>[v];[0:a][1:a]acrossfade=d=<秒>[a]
  • 自動 offset: offset = ffprobe(clip1).duration - <クロスフェード長>--offset <n> で上書き可能
  • トランジション (21 種): fade, wipeleft, wiperight, wipeup, wipedown, slideleft, slideright, slideup, slidedown, circlecrop, rectcrop, distance, fadeblack, fadewhite, radial, smoothleft, smoothright, smoothup, smoothdown, squeezev, squeezeh
引数 / オプションデフォルト備考
<input1>必須1 本目の動画
<input2>必須2 本目の動画
<duration>必須クロスフェード長(秒)
--transition <type>fade上記 21 種のいずれか
--offset <seconds>自動検出clip1 タイムライン上のトランジション開始時刻
--no-audio-fadeoffacrossfade の代わりに音声をストリームコピー(音声トラックがない場合に使う)
-o, --output <path><input1名>-crossfade.<拡張子>出力先を上書き
bash
# デフォルト: ffprobe で clip1 の長さを自動検出(ここでは clip1 が 8.5 秒)
$ npx fqmpeg crossfade clip1.mp4 clip2.mp4 1.5 --transition wipeleft --dry-run

  ffmpeg -i clip1.mp4 -i clip2.mp4 -filter_complex [0:v][1:v]xfade=transition=wipeleft:duration=1.5:offset=7[v];[0:a][1:a]acrossfade=d=1.5[a] -map [v] -map [a] clip1-crossfade.mp4

# 手動 offset、音声クロスフェードなし(片方に音声トラックがないとき)
$ npx fqmpeg crossfade clip1.mp4 clip2.mp4 1.5 --offset 5 --no-audio-fade --dry-run

  ffmpeg -i clip1.mp4 -i clip2.mp4 -filter_complex xfade=transition=fade:duration=1.5:offset=5 -c:a copy clip1-crossfade.mp4

自動 offset は最も多い期待挙動 — 「clip1 を最後まで再生してから clip2 へクロスフェード」 — に一致する。重ね合わせを早めに始めたいなら --offset <n> で指定、片方のクリップに音声がないなら --no-audio-fade を使う(acrossfade は音声ストリームが片側に欠けるとエラーになる)。

fade — 端をフェードイン / フェードアウト

開始時のフェードイン、終了時のフェードアウト、または両方を加える。映像 (fade) と音声 (afade) のフィルタが lockstep で発行されるため、音量カーブも視覚フェードと同期する。

  • ソース: src/commands/fade.js
  • --in または --out のいずれかが必須
  • --out を指定するときは --duration(動画の総尺)が必須 — フェードアウト開始時刻を計算するため
引数 / オプションデフォルト備考
<input>必須入力動画
--in <seconds>0フェードイン長
--out <seconds>0フェードアウト長
--duration <seconds>動画の総尺(--out > 0 のとき必須)
-o, --output <path><入力名>-fade.<拡張子>出力先を上書き
bash
$ npx fqmpeg fade input.mp4 --in 2 --dry-run

  ffmpeg -i input.mp4 -vf fade=t=in:st=0:d=2 -af afade=t=in:st=0:d=2 input-fade.mp4

$ npx fqmpeg fade input.mp4 --in 2 --out 2 --duration 30 --dry-run

  ffmpeg -i input.mp4 -vf fade=t=in:st=0:d=2,fade=t=out:st=28:d=2 -af afade=t=in:st=0:d=2,afade=t=out:st=28:d=2 input-fade.mp4

総尺は事前に npx fqmpeg duration input.mp4 で取得する。フェードアウト開始時刻は duration - fadeOut で自動計算される。入力に音声トラックがない場合、-af フィルタチェーンが失敗する — 先に strip-audio で音声を取り除くか、--dry-run 出力から -af を削って FFmpeg を直接実行する。

fade-between — 2 クリップの間に黒を挟む

clip1 を黒へフェードアウト、その後 clip2 を黒からフェードインして concat する。crossfade とは違い、間に黒フレームが挟まる(直接ブレンドしない)。音声は単純に end-to-end で連結される(音声側のクロスフェードはなし)。

引数 / オプションデフォルト備考
<input1>必須1 本目の動画
<input2>必須2 本目の動画
--duration <n>1フェード長(両クリップに適用)
-o, --output <path><input1名>-faded.<拡張子>出力先を上書き
bash
$ npx fqmpeg fade-between clip1.mp4 clip2.mp4 --duration 1.5 --dry-run

  ffmpeg -i clip1.mp4 -i clip2.mp4 -filter_complex [0]fade=t=out:st=0:d=1.5[v0];[1]fade=t=in:st=0:d=1.5[v1];[v0][v1]concat=n=2:v=1:a=0[v];[0:a][1:a]concat=n=2:v=0:a=1[a] -map [v] -map [a] clip1-faded.mp4

注意: フェードの st=0(開始時刻 = 0)は各クリップの個別タイムラインに適用される — つまり clip1 は最初からフェードを始める。「clip1 の最後 1.5 秒をフェードアウトして clip2 の最初 1.5 秒をフェードイン」というよくあるパターンには別のフィルタグラフが必要。この動詞は「クリップ自体が短く、全体がランプして良い」ケースに向く。

フレーム単位の操作

freeze — 1 フレームを静止

指定時刻のフレームを指定秒数だけ静止させる。tpad + setpts のトリックを使う。音声はストリームコピー(つまり静止中も音声は流れ続ける)。

  • ソース: src/commands/freeze.js
  • フィルタ: tpad=stop_mode=clone:stop_duration=0,setpts='if(gte(T,<at>),if(lte(T,<at>+<hold>),<at>/TB,PTS-<hold>/TB),PTS)'
引数 / オプションデフォルト備考
<input>必須入力動画
<at>必須静止する時刻(秒または HH:MM:SS
<hold>必須静止する長さ(秒)
-o, --output <path><入力名>-freeze.<拡張子>出力先を上書き
bash
$ npx fqmpeg freeze input.mp4 5 2 --dry-run

  ffmpeg -i input.mp4 -vf tpad=stop_mode=clone:stop_duration=0,setpts='if(gte(T,5),if(lte(T,5+2),5/TB,PTS-2/TB),PTS)' -c:a copy input-freeze.mp4

setpts 式は <at> 時点で時間を <hold> 秒間止め、その後 <hold> 秒分だけシフトして再開する。音声はストリームコピーなので静止中も流れ続ける(音声側にはパディングが入らない)。完全同期の静止(音声も止める)が必要なら、FFmpeg を直接呼んで -af "asetpts=..." を書くか、trim で 3 区間(前・1 フレーム・後)に分割して中央の 1 フレームに repeat-frame を適用してから concat --re-encode で繋ぐ。

repeat-frame — 最終フレームを伸ばす

最終フレームを N 秒だけ繰り返して動画末尾を延長する。タイトルカードを足したり、短いクリップを目標尺に合わせるのに使う。音声はストリームコピー(音声は元の長さで終わり、伸びた部分は無音)。

引数 / オプションデフォルト備考
<input>必須入力動画
<seconds>必須最終フレームを保持する長さ
-o, --output <path><入力名>-hold.<拡張子>出力先を上書き
bash
$ npx fqmpeg repeat-frame input.mp4 3 --dry-run

  ffmpeg -i input.mp4 -vf tpad=stop_mode=clone:stop_duration=3 -c:a copy input-hold.mp4

frame-step — N フレームに 1 つだけ残す

N フレームに 1 つだけ残してタイムスタンプを reflow する間引き処理。音声は常に -an で削除される(元のタイミングが意味をなさなくなるため)。出力フレームレートは入力と同じだが、内容は実質 N 倍速のタイムラプス(ただし補間なしで素のまま間引き)。

引数 / オプションデフォルト備考
<input>必須入力動画
<n>必須N フレームに 1 つ残す(正の整数)
-o, --output <path><入力名>-step.<拡張子>出力先を上書き
bash
$ npx fqmpeg frame-step input.mp4 5 --dry-run

  ffmpeg -i input.mp4 -vf select='not(mod(n\,5))',setpts=N/FRAME_RATE/TB -an input-step.mp4

スムーズなタイムラプスが欲しいなら(カクカクの間引きでなく)speed --no-audio を使う方が良い。speed はタイムスタンプを連続的に縮める一方、frame-step は時間軸方向の最近傍サンプリングに近い。

interpolate — 動き補償でスムーズなスローモーション

FFmpeg の minterpolate を motion-compensated interpolation モードで使い、合成された中間フレームを生成する。これが「スムーズスローモ」動詞 — 30fps 素材を半速にして 60fps へ補間すると、speed 0.5 が出すフレーム複製とは違って滑らかに見える。

  • ソース: src/commands/interpolate.js
  • フィルタ: minterpolate=fps=<目標>:mi_mode=mci:mc_mode=aobmc:me_mode=bidir:vsbmc=1
  • 遅い。動き補償補間は CPU 重め — ノートPCで実時間の 5〜20 倍
引数 / オプションデフォルト備考
<input>必須入力動画
<target-fps>必須目標フレームレート(60, 120 など)
-o, --output <path><入力名>-<N>fps-interp.<拡張子>出力先を上書き
bash
$ npx fqmpeg interpolate input.mp4 60 --dry-run

  ffmpeg -i input.mp4 -vf minterpolate=fps=60:mi_mode=mci:mc_mode=aobmc:me_mode=bidir:vsbmc=1 -c:a copy input-60fps-interp.mp4

mi_mode=mci は motion-compensated interpolation(最高品質・最低速)。mc_mode=aobmc は適応的オーバーラップブロックマッチング、me_mode=bidir は双方向動き推定、vsbmc=1 は可変サイズブロック動き補償を有効化。これらは品質寄りのデフォルト。速い動きで shimmering / ghosting が出るときは --dry-run 出力を編集して mi_mode=blendmi_mode=dup に落として FFmpeg を直接実行する。

fps — フレームレート変更(補間なし)

シンプル版: フレームを drop / duplicate して目標フレームレートに合わせる。動き補償なしなので倍速はカクカク・スローはガタガタ — ただし interpolate が遅いのに対してこちらは一瞬。

引数 / オプションデフォルト備考
<input>必須入力動画
<rate>必須目標フレームレート(24, 30, 60
-o, --output <path><入力名>-<N>fps.<拡張子>出力先を上書き
bash
$ npx fqmpeg fps input.mp4 24 --dry-run

  ffmpeg -i input.mp4 -vf fps=24 -c:a copy input-24fps.mp4

fps vs interpolate の使い分け: 単に目標レートに合わせたい(60fps ゲームプレイ録画を 30fps チュートリアルに変換するなど)なら fps、本当にスムーズな高フレームレート出力が欲しい(スローモ再生、滑らかなパン)なら interpolate

実用レシピ

各レシピは複数動詞を組み合わせた実ワークフロー。

レシピ 1: 3 つのクリップをクロスフェードで繋いでアウトロをフェード

よくある編集パターン: 3 本のクリップをスムーズなクロスフェードで連結し、最後に 1 秒のフェードアウトを足す。各 crossfade 呼び出しは前のクリップ長を自動検出してトランジション開始時刻を正しく合わせてくれる。

bash
# Step 1: clip1 と clip2 をクロスフェード(1 秒)
npx fqmpeg crossfade clip1.mp4 clip2.mp4 1 -o c12.mp4

# Step 2: c12 と clip3 をクロスフェード
npx fqmpeg crossfade c12.mp4 clip3.mp4 1 -o c123.mp4

# Step 3: 総尺を取って 1 秒フェードアウト
total=$(npx fqmpeg duration c123.mp4 | awk -F: '{print ($1*3600)+($2*60)+$3}')
npx fqmpeg fade c123.mp4 --out 1 --duration "$total" -o final.mp4

各中間ファイルは再エンコードされる(xfade はフィルタグラフでストリームコピーは効かない)ので、長尺のクリップではエンコードコストが累積する。長尺の場合は Step 2 の --dry-run 出力を編集して 3 本目の入力を直接組み込み、1 つのフィルタグラフで完結させる方が速い。3 本のうち音声がないクリップが 1 つでもあれば --no-audio-fade を付ける — acrossfade は片側に音声がないとエラーで落ちる。

レシピ 2: 30fps 素材からスムーズなスローモ

クリップを半速に落とし、ガクつかないように 60fps へ補間する。2 段パイプラインで、120fps 撮影のクリップを半速再生したのに近い見た目になる。

bash
# Step 1: 0.5x にスロー(音声は捨てる - スロー音声は使い道がほぼない)
npx fqmpeg speed source.mp4 0.5 --no-audio
# → source-slow0.5x.mp4

# Step 2: 60fps へ補間してスムーズに
npx fqmpeg interpolate source-slow0.5x.mp4 60
# → source-slow0.5x-60fps-interp.mp4

Step 2 が遅い — 動き補償補間は実時間の 5〜20 倍。バッチ処理は夜間か高性能マシンで。素材が既に 60fps なら Step 2 は省略可(speed 0.5 で実効 30fps となり、60fps ディスプレイでは十分滑らかに見える)。

レシピ 3: 1 時間の配信からハイライトリール

長い配信から 3 つのハイライトを切り出し、間に黒を挟んで繋ぎ、冒頭に 1 秒のフェードインを足す。

bash
# Step 1: 3 つのハイライトを trim で切り出す
npx fqmpeg trim stream.mp4 --start 00:12:30 --duration 00:00:20 -o h1.mp4
npx fqmpeg trim stream.mp4 --start 00:34:15 --duration 00:00:25 -o h2.mp4
npx fqmpeg trim stream.mp4 --start 00:51:00 --duration 00:00:15 -o h3.mp4

# Step 2: h1 と h2 の間に黒を挟む
npx fqmpeg fade-between h1.mp4 h2.mp4 --duration 0.8 -o h12.mp4

# Step 3: (h1+h2) と h3 の間にも黒を挟む
npx fqmpeg fade-between h12.mp4 h3.mp4 --duration 0.8 -o h123.mp4

# Step 4: 総尺を取って 1 秒フェードイン
total=$(npx fqmpeg duration h123.mp4)
# → 0:00:60.xxxxxx (フェードで両端が少し削れて 60 秒前後)
npx fqmpeg fade h123.mp4 --in 1 --duration 60 -o reel.mp4

trim はキーフレームスナップになる — 特定フレームに合わせたカット(アクションの瞬間など)が必要なら、表の冒頭で言及した 無劣化カット記事を参照。Step 2〜4 は再エンコードなので、Step 1 のキーフレームスナップ誤差はパイプライン全体には伝播しない。

よくある質問

loop 3 がなぜ -stream_loop 2 を生成するのか?

FFmpeg の -stream_loop は「追加」ループ数を数える仕様だから。-stream_loop 0 は「1 回再生」、-stream_loop 2 は「1 回再生してさらに 2 回ループ = 計 3 回再生」を意味する。fqmpeg はユーザー意図(「3 回再生」)と一致するようにユーザー指定のカウントから 1 を引いている。

trim は速いけどカット位置がずれる。何が起きている?

trim-c copy を使うため、出力は指定 --start 直前のキーフレームから始まる。GOP サイズが 250 フレームで 5 秒地点を指定した場合、実際は 4.0 秒くらいから始まることがある。トレードオフは速度 — ストリームコピーは I/O バウンド(SSD なら一瞬)だが、フレーム精度のカットは継ぎ目の GOP を再エンコードする必要がある。継ぎ目だけ再エンコードする方法は FFmpeg 無劣化カット記事で扱った。

concat が "Non-monotonous DTS" で落ちる。どうすればいい?

入力のタイムスタンプ・コーデック・解像度が異なっており、ストリームコピー concat ではマージできない。--re-encode を付けて再実行する — filter_complex concat に切り替わり、全部デコード→再エンコードするため遅いが不一致を許容する。多数のクリップのうち 1 つだけ異質ならば、まずその 1 つを compress で揃えてから demuxer concat する手もある。

speed は高倍速で atempo をチェーンする。音質に影響は?

聞いて分かるレベルで悪化する。各 atempo インスタンスはフェーズボコーダーによる時間伸縮を適用するため、2〜3 段なら一般用途で許容範囲だが、音楽のような音程要素では warbling(揺れ)が目立つ。タイムラプス用途(高倍速)なら --no-audio を付けて、後から音楽を載せる方が良い。中程度の倍速(1.0〜2.0×)なら atempo は 1 段で済むので音質は問題なし。

frame-stepspeed と同じ結果になる?

ならない。frame-step N は N フレームに 1 つだけ残してタイムスタンプを reflow する「ハード間引き」で補間なし。speed N はタイムスタンプを連続的に rescale するため、ソースのフレームを全部保持したまま再生が速くなる。タイムラプス: スムーズな動きが欲しいなら speed --no-audio、カクカクの間引きルックが欲しいなら frame-step

freeze で音声も止められる?

そのままではできない(音声はストリームコピーされ、静止中も流れ続ける)。同期した静止が必要なら、trim で 3 区間(静止前・静止する 1 フレーム・静止後)に分割し、中央の 1 フレームに repeat-frame で必要長を適用してから concat --re-encode で繋ぐパターンが綺麗。

interpolate は遅い。どんなときに使う価値がある?

最終的な見た目に「スムーズな動き」が必要なときだけ: 速いアクションのスローモ再生、ハイリフレッシュレートディスプレイへ送る前のアニメ素材、24fps 素材を 60fps プラットフォーム向けに救済するなど。チュートリアル画面録画を再エンコードするだけ(速い動きがない)には不要、タイムラプス出力には逆効果(補間方向が間違う)、再エンコードがかかるプラットフォーム(YouTube, Instagram)への投稿でも効果が薄い(再エンコードでスムーズさが大半失われる)。

フォルダ内の動画を一括で固定長にトリムするには?

普通のシェルループ:

bash
for v in raw/*.mp4; do
  npx fqmpeg trim "$v" --start 0 --duration 60 -o "trimmed/$(basename "$v")"
done

各出力は trimmed/ に同名で出る。フレーム精度のバッチカットが必要なら、trim無劣化カット記事 の再エンコードパターンに置き換える。

まとめ

C4 の 15 動詞は、典型的な編集パスで触る時間軸操作を一通りカバーする:

  • trim, split, concat はカットと結合(コーデック一致時はストリームコピー、不一致時は --re-encode
  • loop, reverse, speed, boomerang は時間再生(speedatempo チェーン化、reverse の音声バッファコストに注意)
  • crossfade, fade, fade-between はトランジション(crossfade は clip1 長を自動検出して音声も並行クロスフェード。--offset--no-audio-fade で挙動を上書きできる)
  • freeze, repeat-frame, frame-step, interpolate, fps はフレーム単位(interpolate は本当にスムーズさが必要なときだけ — 遅い)

すべての動詞は --dry-run で内部の FFmpeg コマンドを表示するため、デフォルトが用途に合わないとき(freeze の音声処理、カスタム crossfade --offset など)はコマンドをコピーしてカスタマイズし、FFmpeg を直接実行できる。フレーム精度のトリミングや fqmpeg 全体マップは 無劣化カット記事fqmpeg complete guide を参照。