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

  1. Why naive conversion (ffmpeg -i input.mp4 output.gif) looks bad
  2. How the palettegen → paletteuse two-pass pipeline works
  3. Single-command (lavfi) vs. two-file two-pass — when to use each
  4. Optimal fps, resolution, and dithering with a file-size comparison table
  5. How to convert a specific clip segment to GIF
  6. Batch GIF generation
  7. Five common errors and fixes
  8. 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:

  1. Pass 1 — palettegen: Analyze the video and generate a 256-color palette optimized for that specific content
  2. 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 size
  • scale=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
# 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:

FPSWidthApprox. File SizeBest For
24640pxLarge (8–20 MB)Quality-first (large size OK)
15480pxMedium (3–8 MB)Balanced — general use
12320pxSmall (1–3 MB)Social media, chat sharing
10240pxSmallest (0.5–1.5 MB)Icon-size, mini GIFs

Sizes vary heavily by content. High-motion video produces much larger files.


Dithering Comparison

Dither ModeVisual QualityFile SizeBest Content Type
bayer (default)Regular dot patternSmallest (best compression)High-motion video
floyd_steinbergSmooth gradientsSlightly largerPhotos, gradients
sierra2Balanced error-diffusionMediumGeneral use
noneSharp edgesSmall for flat artIcons, 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 ValueBehavior
0Loop forever (browser default)
-1Play once then stop
N (N ≥ 1)Loop N times then stop

Option Reference

OptionMeaningRecommended
fps=NOutput frame rate10–15 for practical use
scale=W:-1Width-specified resize, auto heightUse :-2 to ensure even height
flags=lanczosResampling algorithmBest quality; use bilinear for speed
stats_mode=fullpalettegen sampling modefull (default) / diff (motion-heavy)
dither=bayerDithering algorithmbayer (smaller) / floyd_steinberg (quality)
bayer_scale=NBayer 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


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