Why GIFs Look Bad By Default
GIF supports only 256 colors per frame. MP4 video can have millions. The conversion needs to pick which 256 colors best represent each frame, a process called palette generation.
Default tools (most online converters, naive FFmpeg commands) generate a single palette for the entire animation. Result: gradients band, skin tones shift, dark scenes posterize.
The two-pass palette workflow solves this. Generate an optimal palette per scene, then encode using that palette. The result looks dramatically better at the same file size.
This post covers the practical FFmpeg workflow. For broader animated image context, see Animated WebP vs GIF vs APNG.
Single-Pass Default
The default FFmpeg command:
ffmpeg -i input.mp4 output.gif
This works but uses a generic palette. Quality is poor for any content with gradients or smooth color transitions.
Two-Pass Palette Workflow
The improved approach:
# Pass 1: Generate optimal palette
ffmpeg -i input.mp4 -vf "fps=15,scale=480:-1:flags=lanczos,palettegen=stats_mode=diff" palette.png
# Pass 2: Use palette for conversion
ffmpeg -i input.mp4 -i palette.png -filter_complex "fps=15,scale=480:-1:flags=lanczos[x];[x][1:v]paletteuse=dither=bayer:bayer_scale=5:diff_mode=rectangle" -loop 0 output.gif
Or in one command:
ffmpeg -i input.mp4 -vf "fps=15,scale=480:-1:flags=lanczos,split[s0][s1];[s0]palettegen=stats_mode=diff[p];[s1][p]paletteuse=dither=bayer:bayer_scale=5:diff_mode=rectangle" -loop 0 output.gif
The result: dramatically cleaner gradients, accurate skin tones, smaller file size. Same source material, dramatically better output.
Parameters Explained
| Parameter | Purpose |
|---|---|
fps=15 | Frame rate (15 is GIF sweet spot) |
scale=480:-1 | Width 480px, auto-height (proportional) |
flags=lanczos | High-quality resampling |
palettegen=stats_mode=diff | Palette based on differences between frames |
paletteuse=dither=bayer:bayer_scale=5 | Use palette with Bayer dithering |
diff_mode=rectangle | Detect changing rectangles (smaller files) |
For complex scenes: increase to fps=30. For simple animations: 12-15 fps is sufficient.
Dither Options
Dithering breaks up color banding by adding controlled noise:
| Dither | Quality | File size |
|---|---|---|
| none | No dither | Smaller files, visible banding |
| bayer:bayer_scale=5 | Recommended | Good balance |
| sierra2_4a | Floyd-Steinberg derivative | Highest quality |
| heckbert | Older algorithm | Smaller files |
For most output: bayer:bayer_scale=5 is the production sweet spot. For maximum quality: sierra2_4a.
Resolution and Frame Rate
| Resolution | Frame rate | File size (5s clip) |
|---|---|---|
| 320x180 | 12 fps | ~500 KB |
| 480x270 | 15 fps | ~1.5 MB |
| 640x360 | 20 fps | ~3 MB |
| 720x405 | 24 fps | ~5 MB |
| 1080x608 | 30 fps | ~15 MB |
For most use cases: 480-640 wide at 15 fps. Larger is overkill for GIF (use video instead).
For broader format choice, see Animated WebP vs GIF vs APNG.
Trimming Source
For specific clip from a longer video:
ffmpeg -ss 00:01:30 -t 5 -i input.mp4 \
-vf "fps=15,scale=480:-1:flags=lanczos,split[s0][s1];[s0]palettegen[p];[s1][p]paletteuse" \
-loop 0 output.gif
-ss 00:01:30 -t 5 extracts 5 seconds starting at 1:30. Reduces processing time by extracting only what's needed.
For broader trim context, see FFmpeg Trim Without Re-encoding.
Background Removal
For GIFs with transparent background, source video must have alpha:
# WebM source with alpha
ffmpeg -i input.webm \
-vf "fps=15,scale=480:-1:flags=lanczos,split[s0][s1];[s0]palettegen=reserve_transparent=1[p];[s1][p]paletteuse=alpha_threshold=128" \
-loop 0 output.gif
GIF transparency is 1-bit (full or none). For partial transparency: APNG or WebP.
File Size Optimization
For email/Slack-friendly output:
# Aggressive: 320 width, 12 fps, no dither
ffmpeg -i input.mp4 \
-vf "fps=12,scale=320:-1:flags=lanczos,split[s0][s1];[s0]palettegen[p];[s1][p]paletteuse=dither=none" \
-loop 0 output.gif
Dither=none produces visible banding but smallest files. Acceptable for cartoon-style content; not for photographic.
For batch optimization with gifsicle:
# Reduce file size further
gifsicle -O3 --lossy=80 input.gif > output.gif
gifsicle removes redundant frames and applies optimization beyond FFmpeg's capability.
For batch processing patterns, see Batch Processing Files Guide.
Color Tuning
For specific color emphasis:
# Reserve more palette space for warm tones
ffmpeg -i input.mp4 -vf "fps=15,scale=480:-1,palettegen=max_colors=128:stats_mode=diff" palette.png
max_colors=128 reserves the remaining 128 for transparency or specific colors. For most cases: default 256 is fine.
Comparison: Default vs Two-Pass
For a 5-second clip:
| Method | File size | Quality |
|---|---|---|
| Default FFmpeg | 1.8 MB | Visible banding |
| Two-pass with bayer dither | 1.4 MB | Clean gradients |
| Two-pass with sierra2_4a | 1.6 MB | Best quality |
| Two-pass + gifsicle optimize | 950 KB | Same quality, smaller |
The two-pass workflow produces smaller, cleaner GIFs. The combination with gifsicle achieves what online converters can't match.
Common Issues
Output GIF is huge: bitrate calculation. For sub-1MB target: reduce resolution or fps.
Colors look posterized: dither=none, switch to bayer or sierra2_4a.
Animation jerky: fps too low. Increase to 20-24 fps for smooth motion.
File doesn't loop: missing -loop 0 flag.
Transparency not preserved: source must have alpha; GIF supports 1-bit only.
For broader video conversion, see HEVC to H.264 for Premiere.
Frequently Asked Questions
Should I use GIF or modern formats?
For email and Slack: GIF (broadest compatibility). For modern web: WebP or AV1 video.
What's the maximum GIF dimensions?
Technically 65,535×65,535. Practically: 1080×608 at 30 fps approaches a 15+ MB file. Stay under 1080p for usability.
Can I make GIFs smaller than 100KB?
Yes: 320×180 at 8 fps for short loops. Quality drops but file size meets email constraints.
Why is my GIF looping at wrong speed?
GIF frame timing is in 10ms units. Frame timing requires precise FPS calculation. Use fps=15 not fps=15.0.
Does the order of pass matter?
Pass 1 (palette generation) must complete before pass 2 (encoding). Single-command syntax handles this internally.
Can I add captions to GIF?
Use FFmpeg's drawtext filter:
ffmpeg -i input.mp4 -vf "fps=15,scale=480:-1,drawtext=text='Caption':x=20:y=20:fontsize=24:fontcolor=white,split[s0][s1];[s0]palettegen[p];[s1][p]paletteuse" -loop 0 output.gif
Related Reading
Bottom Line
For MP4 to GIF in 2026: two-pass palette workflow with FFmpeg, bayer dithering, gifsicle optimization for further size reduction. 480×270 at 15 fps is the production sweet spot for most use cases. Our GIF maker and MP4 to GIF converter automate this pipeline.


