A GIF made straight from a video clip often looks washed out. The fix is to build a color palette from the clip first with palettegen, then apply it with paletteuse. The colors come out much better, and the file stays small, so it works well for Slack or social media.

Why Two Passes?

A GIF frame can hold at most 256 colors. If you convert directly with ffmpeg -i input.mp4 output.gif, FFmpeg uses a fixed, generic 256-color palette that ignores what is in your footage. Colors look washed out, and smooth gradients turn into visible bands.

Two passes fix this:

  1. Pass 1 (palettegen): reads the video and builds a 256-color palette for this clip.
  2. Pass 2 (paletteuse): redraws each frame with that palette. It can also dither (mix dots of nearby colors) to smooth out the steps between colors.

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: lowers the frame rate, which cuts the file size
  • scale=480:-1:flags=lanczos: resizes to 480px wide with Lanczos resampling (highest quality)
  • split[s0][s1]: makes two copies of the stream
  • [s0]palettegen[p]: builds the palette from the first copy
  • [s1][p]paletteuse: applies the palette to the second 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 (the default) counts every frame equally. stats_mode=diff counts only the parts that changed from the previous frame. When the background stays still, diff gives the moving parts more weight in the palette.

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 plays the GIF once and stops. -loop 0 loops 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)

Sizes measured with FFmpeg 8.1 on the first 5 seconds of a 1080p, 30fps clip of Big Buck Bunny (CC BY 3.0). Each GIF was made with command 1, changing only fps and the width:

FPS Width File Size Best For
24 640px Large (17.5 MB) Quality first, a large file is OK
15 480px Medium (6.5 MB) General use
12 320px Small (2.3 MB) Social media, chat
10 240px Smallest (1.1 MB) Icon-size, mini GIFs

Clips with a lot of motion come out much larger.

Dithering Comparison

Dither Mode Visual Quality File Size Best Content Type
bayer 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

If you leave out dither, FFmpeg uses sierra2_4a.

# 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 (FFmpeg’s 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 How visible the Bayer pattern is (0–5, default 2; lower is more visible) 5 (less visible pattern, more banding)

Troubleshooting

Problem 1: GIF File Is Tens of MB

Cause: a high frame rate, a large size and dithering all make a GIF bigger.
Fix: lower the frame rate and the width, and use bayer dithering:

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: the GIF was converted directly, without palettegen.
Fix: build a palette with palettegen and apply it with paletteuse. A plain ffmpeg -i input.mp4 output.gif always uses the generic palette.

Problem 3: Error opening input file palette.png. in Pass 2

Cause: pass 2 looks for the palette in a different place from where pass 1 saved it.
Fix: give both commands the same absolute path. 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: height not divisible by 2 When Reusing the Filter for MP4 (Odd Height)

Cause: scale=480:-1 gave an odd height, such as 271. The GIF encoder accepts odd heights, but H.264 (libx264 with yuv420p) cannot encode them.
Fix: use :-2 instead of :-1. FFmpeg then rounds the height to an even number:

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

Problem 5: GIF Doesn’t Loop Correctly in Some Apps

Cause: apps read the -loop value differently.
Fix: use -loop 0 (loop forever). Most browsers and chat apps follow it.

FAQ

Q1. Why use two passes instead of a direct conversion?
A. A GIF can hold only 256 colors. Without its own palette, FFmpeg uses a fixed, generic 256-color palette, and the result looks washed out and banded. The two passes build a palette from your video.

Q2. GIF, WebP or looping MP4: which should I use?
A. GIF plays in the most places (email, Slack, all chat apps). If file size matters and the platform supports them, an animated WebP or a looping MP4 (for web pages) gives a smaller file.

Q3. Can a GIF be transparent?
A. Only on or off: each pixel is either fully transparent or fully opaque (1-bit transparency). For partly transparent pixels, use WebP or APNG.

Q4. How different are fps=15 and fps=24?
A. In a short GIF (under 5 seconds), fps=15 looks almost as smooth as 24. fps=24 has 1.6× as many frames (24 ÷ 15), so the file is larger too; how much larger depends on the content. For slow movement or a talking head, fps=10–12 is often enough.

Q5. Can I make a GIF at the original video resolution?
A. Yes, but HD video (1920px wide) gives a very large GIF. If you leave out scale, at least lower the frame rate:

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