Want to turn a video clip into an animated GIF for Slack or social media? If the colors look washed out, the culprit is missing palette optimization. The palettegen + paletteuse two-pass pipeline dramatically improves quality while keeping file size small. This guide covers every setting you need. Time to complete: 10 minutes.
Tested with: FFmpeg 6.1 (ubuntu-latest / GitHub Actions CI-validated)
What You Will Learn
- Why naive conversion (
ffmpeg -i input.mp4 output.gif) looks bad - How the
palettegen → paletteusetwo-pass pipeline works - Single-command (lavfi) vs. two-file two-pass — when to use each
- Optimal fps, resolution, and dithering with a file-size comparison table
- How to convert a specific clip segment to GIF
- Batch GIF generation
- Five common errors and fixes
- Five frequently asked questions
Why Two Passes?
The GIF format is limited to a 256-color palette per frame. Converting directly with ffmpeg -i input.mp4 output.gif falls back to a generic web-safe palette, resulting in washed-out colors and banding artifacts.
The solution:
- Pass 1 — palettegen: Analyze the video and generate a 256-color palette optimized for that specific content
- Pass 2 — paletteuse: Apply the custom palette to each frame (with optional dithering to smooth color transitions)
Command Examples
1. Simplest: One-Command Pipeline (lavfi split)
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— reduce frame rate to cut file sizescale=480:-1:flags=lanczos— resize to 480px wide, Lanczos resampling (highest quality)split[s0][s1]— fork the stream into two copies[s0]palettegen[p]— generate palette from one copy[s1][p]paletteuse— apply palette to the other copy
2. Convert a Specific Clip Segment to GIF
# Convert 3 seconds starting at 5s, infinite loop, 320px wide
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. High Quality: Two-File Two-Pass (Recommended for Long Clips)
# Pass 1: generate palette PNG
ffmpeg -i input.mp4 -vf "fps=15,scale=480:-1:flags=lanczos,palettegen=stats_mode=full" palette.png
# Pass 2: apply palette and create 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 — samples all frames equally (default; use diff for high-motion video)
4. Minimum File Size (Chat / Messaging Apps)
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. Play Once (No Loop)
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 — play once and stop; 0 = loop forever
6. Batch GIF Creation
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
File Size Reference (fps × Resolution)
Approximate sizes for a 5-second clip from 1080p/30fps source:
| FPS | Width | Approx. File Size | Best For |
|---|---|---|---|
| 24 | 640px | Large (8–20 MB) | Quality-first (large size OK) |
| 15 | 480px | Medium (3–8 MB) | Balanced — general use |
| 12 | 320px | Small (1–3 MB) | Social media, chat sharing |
| 10 | 240px | Smallest (0.5–1.5 MB) | Icon-size, mini GIFs |
Sizes vary heavily by content. High-motion video produces much larger files.
Dithering Comparison
| Dither Mode | Visual Quality | File Size | Best Content Type |
|---|---|---|---|
bayer (default) | Regular dot pattern | Smallest (best compression) | High-motion video |
floyd_steinberg | Smooth gradients | Slightly larger | Photos, gradients |
sierra2 | Balanced error-diffusion | Medium | General use |
none | Sharp edges | Small for flat art | Icons, flat design |
# Try floyd_steinberg for smoother gradients
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 Control
-loop Value | Behavior |
|---|---|
0 | Loop forever (browser default) |
-1 | Play once then stop |
N (N ≥ 1) | Loop N times then stop |
Option Reference
| Option | Meaning | Recommended |
|---|---|---|
fps=N | Output frame rate | 10–15 for practical use |
scale=W:-1 | Width-specified resize, auto height | Use :-2 to ensure even height |
flags=lanczos | Resampling algorithm | Best quality; use bilinear for speed |
stats_mode=full | palettegen sampling mode | full (default) / diff (motion-heavy) |
dither=bayer | Dithering algorithm | bayer (smaller) / floyd_steinberg (quality) |
bayer_scale=N | Bayer pattern size (1–5) | 5 (less visible, slightly worse compression) |
Troubleshooting
Problem 1: GIF File Is Tens of MB
Cause: High fps, high resolution, or dithering all contribute to large GIF files.
Fix:
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
Problem 2: Colors Look Washed Out / Banding Artifacts
Cause: Using direct conversion without the palettegen pipeline.
Fix: Always use palettegen → paletteuse. Never use ffmpeg -i input.mp4 output.gif alone for quality output.
Problem 3: No such file or directory: palette.png in Pass 2
Cause: Path mismatch between the palette PNG written in pass 1 and read in pass 2.
Fix: Use absolute paths. On 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
Problem 4: Error With scale=480:-1 (Odd Height)
Cause: Calculated height is an odd number, which some GIF encoders reject.
Fix: Use :-2 instead of :-1 to round to an even number:
-vf "fps=15,scale=480:-2:flags=lanczos,..."
Problem 5: GIF Doesn’t Loop Correctly in Some Apps
Cause: Different apps interpret -loop values differently.
Fix: -loop 0 (infinite) has the broadest compatibility. Most browsers and chat apps respect it. For Discord, -loop -1 also works correctly.
FAQ
Q1. Why use the two-pass pipeline instead of a direct conversion?
A. GIF can only store 256 colors. Without palette optimization, FFmpeg uses a generic web-safe palette that produces washed-out, banded results. The two-pass pipeline generates a palette tailored to your specific video.
Q2. GIF vs WebP vs looping MP4 — which format should I choose?
A. GIF has the highest compatibility (email, Slack, all chat apps). If file size matters and your audience’s platform supports it, WebP animated or looping MP4 (for web pages) is more efficient.
Q3. Can GIF support transparency?
A. GIF supports only 1-bit transparency (fully transparent or fully opaque — no semi-transparency). For full alpha transparency, use WebP or APNG.
Q4. What’s the real difference between fps=15 and fps=24?
A. For short GIFs (under 5 seconds), fps=15 looks nearly as smooth as 24 to the human eye. fps=24 produces about 1.6× larger files. For slow motion or talking-head content, fps=10–12 is often sufficient.
Q5. Can I create a GIF at the original video resolution?
A. Yes, but HD video (1920px wide) will produce very large GIFs. If you omit scale, at least reduce fps:
ffmpeg -i input.mp4 -vf "fps=10,split[s0][s1];[s0]palettegen[p];[s1][p]paletteuse" output.gif
Related Articles
- Scaling and Resizing Video — scale Filter Complete Guide
- Trimming Video — -ss/-to/-t Complete Guide
- Watermark and Logo Overlay
- Batch Convert with Shell Scripts
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