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:
- Pass 1 (palettegen): reads the video and builds a 256-color palette for this clip.
- 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 sizescale=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
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 (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