What You Will Learn
- Why naive
ffmpeg -i input.mp4 output.gifproduces poor results - The two-pass
palettegen→paletteusepipeline for optimized GIFs - How to control frame rate, resolution, and dithering
- Techniques to reduce file size without sacrificing visible quality
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:
- Pass 1 — palettegen: Analyze the video and generate a 256-color palette optimized for that specific content.
- 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:
- Sets the frame rate to 15 fps (reduces file size)
- Scales width to 480 px, preserving aspect ratio (
-1) with Lanczos resampling - Splits the filtered stream: one copy feeds
palettegen, the other feedspaletteuse - Merges the palette back via
[p]intopaletteuse
Two-File Two-Pass Pipeline (Recommended for Control)
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:
-lavfiis used instead of-vfbecause two inputs need to be referenced in the filter graph[1:v]refers to the second input (the palette PNG)fpsandscalemust be applied again in pass 2 to match the palette
Controlling Frame Rate and Size
GIF file size grows quickly with resolution and frame count. The two main levers are:
| Parameter | Effect | Example |
|---|---|---|
fps=N | Reduce frames per second | fps=10 for small looping clips |
scale=W:-1 | Scale to width W, auto height | scale=320:-1 |
scale=-1:H | Auto width, scale to height H | scale=-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 Mode | Description |
|---|---|
bayer (default) | Ordered dither — produces regular patterns but good compression |
floyd_steinberg | Error-diffusion — smoother gradients, slightly larger files |
sierra2 | Balanced error-diffusion |
none | No 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 value | Behavior |
|---|---|
0 | Loop forever (default in most players) |
-1 | No 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
- Reduce fps (try 10–12 for slow motion, 15 for normal content)
- Reduce scale (320 px wide is a common starting point)
- Use
dither=bayerwhich compresses better thanfloyd_steinberg
Colors Look Washed Out
- Ensure you are running the full two-pass pipeline (not just
ffmpeg -i input.mp4 output.gif) - Check that
palettegenis analyzing the actual frames you want (same fps/scale as pass 2)
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.
Related Articles
- Scaling and Resizing Video — scale Filter
- Trimming Video — -ss/-to/-t and the Keyframe Problem
- Adding a Watermark or Logo Overlay
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