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.
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.
| 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.
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 | 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.
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.
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.
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.
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.
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.
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.
For email and Slack: GIF (broadest compatibility). For modern web: WebP or AV1 video.
Technically 65,535×65,535. Practically: 1080×608 at 30 fps approaches a 15+ MB file. Stay under 1080p for usability.
Yes: 320×180 at 8 fps for short loops. Quality drops but file size meets email constraints.
GIF frame timing is in 10ms units. Frame timing requires precise FPS calculation. Use fps=15 not fps=15.0.
Pass 1 (palette generation) must complete before pass 2 (encoding). Single-command syntax handles this internally.
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
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.