What You Will Learn

Tested version: FFmpeg 6.1 (ubuntu-latest / CI-validated)
Target OS: Windows / macOS / Linux


Why GIF Needs a Two-Pass Approach

The GIF format is limited to a 256-color palette per frame. If you convert video directly without a custom palette, FFmpeg falls back to a generic web-safe palette that looks washed out and banded.

The solution is a two-pass approach:

  1. Pass 1 — palettegen: Analyze the video and generate a 256-color palette optimized for that specific content.
  2. Pass 2 — paletteuse: Apply that custom palette to each frame, optionally with dithering to smooth color transitions.

Quick Start — Single-Line Version (lavfi)

For simple cases you can do both passes in a single command using the lavfi device and filter graph splitting:

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

This command:


The single-line version re-encodes video twice inside one process. For longer clips or when you want to tweak palette options separately, use explicit two steps:

Step 1 — Generate the palette:

ffmpeg -i input.mp4 -vf "fps=15,scale=320:-1:flags=lanczos,palettegen" /tmp/palette.png

palettegen outputs a 16×16 PNG containing the 256 optimized colors.

Step 2 — Apply the palette:

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

Key points:


Controlling Frame Rate and Size

GIF file size grows quickly with resolution and frame count. The two main levers are:

ParameterEffectExample
fps=NReduce frames per secondfps=10 for small looping clips
scale=W:-1Scale to width W, auto heightscale=320:-1
scale=-1:HAuto width, scale to height Hscale=-1:240

Use flags=lanczos for the highest-quality downscale. For fast previews flags=bilinear is faster.


Dithering Options

The paletteuse filter supports several dithering algorithms via the dither option:

ffmpeg -i input.mp4 -i /tmp/palette.png -lavfi "fps=15,scale=320:-1:flags=lanczos[x];[x][1:v]paletteuse=dither=bayer:bayer_scale=5" output.gif
Dither ModeDescription
bayer (default)Ordered dither — produces regular patterns but good compression
floyd_steinbergError-diffusion — smoother gradients, slightly larger files
sierra2Balanced error-diffusion
noneNo dithering — best for flat graphics, worst for photos

bayer_scale (1–5) controls the pattern size when using bayer dithering. Higher values reduce visible pattern but can hurt compression.


Looping

GIFs loop by default in most browsers. To control looping, add -loop:

ffmpeg -i input.mp4 -vf "fps=15,scale=320:-1:flags=lanczos,split[s0][s1];[s0]palettegen[p];[s1][p]paletteuse" -loop 0 output.gif
-loop valueBehavior
0Loop forever (default in most players)
-1No loop (play once)
N (N ≥ 1)Loop N times then stop

Trimming Before GIF Conversion

To convert only a portion of the video, combine -ss and -t with the GIF filters:

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" output.gif

This converts 3 seconds starting at the 5-second mark.


Common Pitfalls

Output Is Very Large

Colors Look Washed Out

Palette PNG Is Not Found in Pass 2

Make sure the /tmp/palette.png path matches between pass 1 and pass 2. On Windows use a full path like C:/tmp/palette.png.



Tested with: ffmpeg 6.1.1 / Ubuntu 24.04 (GitHub Actions runner)
Primary sources: ffmpeg.org/ffmpeg-filters.html#palettegen / ffmpeg.org/ffmpeg-filters.html#paletteuse