32blogby StudioMitsu
Archive7 min read

FFmpegで動画の最終フレームを高速に切り出す方法

動画全体をデコードせず最終フレームだけを瞬時に抽出する方法を解説。-sseof と -update 1 の内部挙動と、サムネイル生成パイプラインへの応用まで網羅。

動画コンテンツの管理において、確認用やサムネイル生成のために「動画の最後の瞬間(ラストフレーム)」を静止画として保存したいニーズは頻出する。しかし、尺の長い動画に対して単純なフレーム抽出を行うと、全フレームのデコード処理が発生し、著しいタイムロスを招く。

本稿では、FFmpeg の入力シーク機能と画像更新オプションを組み合わせ、動画サイズに依存せず一瞬で最終フレームを特定・保存する最適解を提示する。

bash
ffmpeg -sseof -1 -i "input.mp4" -update 1 "output.png"

実戦用スクリプト

コマンドだけではなくスクリプトが欲しい場合もあると思うので用意した。Windows 環境(PowerShell)と macOS/Linux 環境(Bash)でそれぞれ使えるものだ。

Windows (PowerShell)

このコマンドは、動画の末尾 1 秒間のみを読み込み、その範囲内のフレームを同じファイル名で上書きし続けることで、結果的に「最後の 1 枚」をストレージに残す。

powershell
# 変数定義
$InputVideo = "input.mp4"
$OutputImage = "output.png"

# コマンド実行
# -sseof -1 : ファイル末尾から1秒前へシーク
# -update 1 : 連番にせず同一ファイルに上書き保存
ffmpeg -y -sseof -1 -i "$InputVideo" -update 1 -vframes 1 "$OutputImage"

macOS / Linux (Bash)

bash
#!/bin/bash

INPUT="input.mp4"
OUTPUT="output.png"

# -sseof -1  : ファイル末尾から1秒前へシーク(Input Seeking)
# -update 1  : 画像muxerに対し、単一ファイルへの上書きを指示
# -vframes 1 : 最後の1フレームのみを出力
ffmpeg -y -sseof -1 -i "$INPUT" -update 1 -vframes 1 "$OUTPUT"

技術解説:なぜこのパラメーターが最適解なのか

提示したコマンドは、単純に見えて FFmpeg のパイプライン挙動を巧みに利用したロジックで構成されている。各フラグの内部挙動を解説する。

1. -sseof -1 による Input Seeking(入力シーク)

FFmpeg のシークオプションには、-ss(開始時刻指定)などが存在するが、重要なのは記述する位置だ。

  • 入力オプションとしての配置: -i の手前に配置することで、デコーダーはファイルを読み込む前に指定位置(EOF: End Of File からマイナス 1 秒)へジャンプする。
  • 高速化の理由: これにより、先頭から末尾までのデコード処理をスキップ(Demuxer レベルでのシーク)する。1 時間の動画であっても、処理対象は「最後の 1 秒+キーフレーム探索分」のみとなるため、実行時間は動画の尺に依存せず数ミリ秒〜数百ミリ秒で完了する。

2. -update 1 による上書きロジック

通常、FFmpeg で動画を静止画に出力する場合、image2 muxer は %03d.png のような連番出力を期待する。単一ファイル名を指定するとエラーになる、あるいは最初の 1 枚で処理が止まる場合がある。

  • -update 1 の役割: このオプションは、muxer に対して「ファイル名が固定であっても、新しいフレームが来るたびにファイルの中身を更新(上書き)せよ」と指示する。
  • 最終フレームが残る仕組み:
    1. FFmpeg は末尾 1 秒間のデータをデコードし、フレームを順次生成する
    2. フレームが生成されるたびに output.png が書き換わる
    3. ストリームが終了(EOF)した時点で書き込まれていた画像、つまり時系列的に最も遅いフレームがファイルとして確定し、残る

3. その他の考慮すべき挙動

  • キーフレームへのスナップ: -sseof による Input Seeking は、正確には指定時間の「直前のキーフレーム(I フレーム)」から読み込みを開始する。そのため、GOP(Group of Pictures)構造によっては 1 秒以上前から読み込まれることがあるが、最終的な出力結果(ラストフレーム)への到達には影響しない。
  • PNG と JPG の使い分け: PNG 出力の場合は可逆圧縮であるため画質オプションは基本的に不要だ。JPG 出力の場合は -q:v 2 などで画質を指定する(-q:v 2 は高品質、-q:v 31 は低品質)。

従来手法との比較

手法コマンド例評価デメリット
全フレームデコード方式(非推奨)ffmpeg -i in.mp4 -frames:v 1 out.png(先頭から全デコード)非推奨動画の長さ分だけ時間がかかる
総フレーム数計算ffprobe でカウント → -vf select非推奨事前にフレーム数を数える処理が重い。二度手間
本手法-sseof -1 ... -update 1 -vframes 1推奨O(1) に近い速度で完了し、事前のメタデータ解析も不要

バッチ処理への応用

複数の動画ファイルからまとめてサムネイルを生成したい場合の例だ。

Bash でのバッチ処理:

bash
#!/bin/bash

INPUT_DIR="./videos"
OUTPUT_DIR="./thumbnails"

mkdir -p "$OUTPUT_DIR"

for video in "$INPUT_DIR"/*.mp4; do
    filename=$(basename "$video" .mp4)
    output="$OUTPUT_DIR/${filename}_last_frame.png"
    ffmpeg -y -sseof -1 -i "$video" -update 1 -vframes 1 "$output"
    echo "処理完了: $filename"
done

PowerShell でのバッチ処理:

powershell
$InputDir = ".\videos"
$OutputDir = ".\thumbnails"

New-Item -ItemType Directory -Force -Path $OutputDir | Out-Null

Get-ChildItem -Path $InputDir -Filter "*.mp4" | ForEach-Object {
    $filename = $_.BaseName
    $output = Join-Path $OutputDir "${filename}_last_frame.png"
    ffmpeg -y -sseof -1 -i $_.FullName -update 1 $output
    Write-Host "処理完了: $filename"
}

まとめ

動画のラストフレームを取得するタスクにおいて、全編を解析する必要はない。「後ろからシークして(-sseof)、上書きし続ける(-update)」 というアプローチを採用することで、サーバーリソースを最小限に抑えつつ、高速なサムネイル生成パイプラインを構築できる。

大量の動画アセットを扱うバッチ処理においては、この数秒の短縮が全体のパフォーマンスに大きく寄与する。