FFmpegで動画からGIFを作るときは、palettegen + paletteuse の2パスで変換します。GIFの色が汚くなる原因は、パレットを最適化していないことです。2パスにすれば品質が向上し、ファイルサイズも最小化できます。

なぜ2パスが必要か

GIF形式はフレームごとに256色パレットしか持てません。ffmpeg -i input.mp4 output.gif とだけ実行すると、FFmpegは素材に関係なく決まった固定の256色パレットを使うため、色が薄くなり、バンディング(帯状ノイズ)が発生します。

解決策:

  1. パス1(palettegen): 動画を分析して、その素材専用の256色最適パレットを生成
  2. パス2(paletteuse): 最適パレットを各フレームに適用(ディザリングで色の遷移を滑らかに)

コマンド例

1. 最もシンプル:1コマンドでpalettegen+paletteuse(lavfi方式)

ffmpeg -i input.mp4 -vf "fps=15,scale=480:-1:flags=lanczos,split[s0][s1];[s0]palettegen[p];[s1][p]paletteuse" output.gif
  • fps=15 … 15fpsに落としてサイズ削減
  • scale=480:-1:flags=lanczos … 幅480px・Lanczosリサンプリング(高品質)
  • split[s0][s1] … ストリームを2つに分岐
  • [s0]palettegen[p] … 一方でパレット生成
  • [s1][p]paletteuse … 他方にパレットを適用

2. 特定区間だけをGIFに変換

# 5秒から3秒間を変換(ループ無限・幅320px)
ffmpeg -ss 00:00:05 -i input.mp4 -t 3 \
  -vf "fps=12,scale=320:-1:flags=lanczos,split[s0][s1];[s0]palettegen[p];[s1][p]paletteuse" \
  -loop 0 output.gif

3. 高品質:2ファイル2パス方式(推奨)

長い動画や、設定を細かく調整したい場合に向いています。

# パス1:パレット生成(PNG保存)
ffmpeg -i input.mp4 -vf "fps=15,scale=480:-1:flags=lanczos,palettegen=stats_mode=full" palette.png

# パス2:パレット適用してGIF生成
ffmpeg -i input.mp4 -i palette.png \
  -lavfi "fps=15,scale=480:-1:flags=lanczos[x];[x][1:v]paletteuse=dither=bayer:bayer_scale=5" \
  output.gif

stats_mode=full … 全フレームを均等にサンプリング(デフォルトはfull。diffは前のフレームから変化した部分だけで集計するので、背景が動かない動画で動く部分の色を優先したいときに使います)

4. 小サイズ最優先(Slack・メッセージアプリ向け)

ffmpeg -ss 00:00:10 -i input.mp4 -t 5 \
  -vf "fps=10,scale=320:-1:flags=lanczos,split[s0][s1];[s0]palettegen[p];[s1][p]paletteuse=dither=bayer" \
  -loop 0 output_small.gif

5. 1回だけ再生するGIF(ループなし)

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

-loop -1 … 1回再生で停止(0は無限ループ)

6. バッチGIF生成(フォルダ内の全mp4)

for f in *.mp4; do
  ffmpeg -i "$f" \
    -vf "fps=15,scale=480:-1:flags=lanczos,split[s0][s1];[s0]palettegen[p];[s1][p]paletteuse" \
    "${f%.mp4}.gif" -y
done

ファイルサイズ比較(fps × 解像度)

FFmpeg 8.1 で、Big Buck Bunny(CC BY 3.0)の 1080p・30fps の動画の最初の5秒を変換した実測値です。どれも上の「1.」のコマンドの fps と幅だけを変えています。

fps 幅 ファイルサイズ 用途
24 640px 大(17.5MB) 映像品質重視(大容量許容)
15 480px 中(6.5MB) バランス(一般的な用途)
12 320px 小(2.3MB) SNS・チャット共有
10 240px 最小(1.1MB) アイコン・ミニGIF

数値は素材により大きく変動します。動きが多い素材ほどサイズは大きくなります。

ディザリング比較

ディザリングモード 見た目 ファイルサイズ 向いているコンテンツ
bayer 規則的なドットパターン 最小(圧縮に有利) 動きの多い動画
floyd_steinberg 滑らかなグラデーション やや大きい 写真・グラデーション多め
sierra2 floyd_steinbergのバランス型 中程度 バランス重視
none シャープなエッジ 小(均一色に最小) アイコン・フラットデザイン

dither を指定しないときの既定値は sierra2_4a です。

# floyd_steinbergを試す場合
ffmpeg -i input.mp4 -i palette.png \
  -lavfi "fps=15,scale=320:-1:flags=lanczos[x];[x][1:v]paletteuse=dither=floyd_steinberg" \
  output_smooth.gif

ループ制御

-loop 値 動作
0 無限ループ(FFmpeg の既定値)
-1 1回再生で停止
N(N ≥ 1) N回ループして停止

オプション詳解

オプション 意味 推奨値
fps=N 出力フレームレート 10〜15が実用的
scale=W:-1 幅指定・高さ自動(奇数になる可能性あり) scale=480:-2 も使用可
flags=lanczos リサンプリングアルゴリズム 品質最高。速度優先ならbilinear
palettegen=stats_mode=full パレット生成方法 full(デフォルト)/ diff(動き重視)
paletteuse=dither=bayer ディザリング方式 bayer(サイズ小)/ floyd_steinberg(品質高)
bayer_scale=N bayerの模様の出方(0〜5、既定値2。小さいほど模様が目立つ) 5(パターンは目立たないが、色の段差が出やすい)

トラブルシューティング

エラー1: GIFファイルが数十MB以上になった

原因: 高fps・高解像度・ディザリングがファイルサイズを増大させている。
解決策:

# fpsを10に、幅を320に落とす
ffmpeg -i input.mp4 -vf "fps=10,scale=320:-1:flags=lanczos,split[s0][s1];[s0]palettegen[p];[s1][p]paletteuse=dither=bayer" output.gif

エラー2: 色が薄い・バンディング(帯状ノイズ)が出る

原因: palettegen を使わず直接 output.gif に変換している(汎用パレット使用)。
解決策: 必ず palettegen → paletteuse の2パスパイプラインを使用してください。

エラー3: パス2で Error opening input file palette.png.

原因: パス1のパレットPNGのパスがパス2と一致していない。
解決策: 両方のコマンドで絶対パスを使います。Windowsの場合:

ffmpeg -i input.mp4 -vf "fps=15,scale=320:-1:flags=lanczos,palettegen" C:/tmp/palette.png
ffmpeg -i input.mp4 -i C:/tmp/palette.png -lavfi "fps=15,scale=320:-1:flags=lanczos[x];[x][1:v]paletteuse" output.gif

エラー4: 同じフィルターでMP4を作ると height not divisible by 2 が出る(奇数高さ)

原因: scale=480:-1 で計算された高さが奇数(例: 271)になっています。GIFは奇数の高さでもそのまま出力できますが、H.264(libx264・yuv420p)は奇数の高さをエンコードできません。
解決策: -1 の代わりに -2(偶数に丸める)を使います:

-vf "fps=15,scale=480:-2:flags=lanczos,..."

エラー5: GIFが一部のアプリで正しくループしない

原因: -loop の指定が意図した動作と異なる、またはアプリ側の実装依存。
解決策: 多くのブラウザ・アプリでは -loop 0(無限ループ)が最も互換性が高いです。

FAQ

Q1. ffmpeg -i input.mp4 output.gif ではなく2パスが必要なのはなぜですか?
A. GIFは256色しか持てないため、素材に合わせた最適パレットが必要です。2パスなしでは汎用パレットで代替され、色が大きく劣化します。

Q2. GIFとWebPやMP4ループ、どれを選ぶべきですか?
A. GIFは最も互換性が高く、メールやSlack等でも問題なく再生されます。ファイルサイズを重視するなら WebP(非対応環境あり)やMP4の loop 属性(Webページ限定)が有効です。

Q3. GIFにアルファ透過は使えますか?
A. GIFの透過は1bit(完全に透明か、不透明か)だけです。半透明は使えません。透過付きの素材は、palettegen と paletteuse で変換すれば透過が残ります。透明にするかどうかは paletteuse の alpha_threshold(既定値128)で決まり、アルファ値がこれより小さい画素が透明になります。半透明が必要な場合は WebP/APNG を検討してください。

Q4. fps=15 と fps=24、品質の違いはどれほどですか?
A. 5秒以下の短いGIFならfps=15でもほぼ自然に見えます。fps=24は滑らかですが、フレーム数が1.6倍(24÷15)になるぶん、ファイルサイズも大きくなります(増え方は素材次第です)。動きが緩やかな素材はfps=10〜12で十分です。

Q5. スケール指定なしで元の解像度のままGIFにできますか?
A. できますが、HD動画(1920px幅)をそのままGIFにするとサイズが巨大になります。scaleを省略する場合は少なくともfpsを落としてください:

ffmpeg -i input.mp4 -vf "fps=10,split[s0][s1];[s0]palettegen[p];[s1][p]paletteuse" output.gif

関連記事