Skip to content

MP4 to GIF Tutorial: FFmpeg Two-Stage Algorithm and Quality Optimization

Making GIFs from MP4 videos sounds easy — until you see the result: muddy colors, huge file size, choppy playback. The problem isn't GIF format itself; it's that default conversion ignores GIF's 256-color limitation. Here's how to make smooth, sharp GIFs using FFmpeg's two-stage palette algorithm.

Why GIF conversion is tricky

GIF format has two key limitations:

  1. Maximum 256 colors per frame (no matter the source)
  2. No inter-frame compression (each frame is a separate image)

When you run ffmpeg -i input.mp4 output.gif, FFmpeg uses a default 256-color palette that doesn't match your video. The result is color banding, washed-out colors, and artifacts.

The solution is FFmpeg's palettegen and paletteuse filters, which generate a custom palette from your video and apply it optimally.

Method 1: Naive conversion (don't use this)

bash
ffmpeg -i input.mp4 output.gif

This produces a working GIF but with poor colors and large file size. Avoid this method.

Step 1: Generate custom palette

bash
ffmpeg -i input.mp4 -vf "fps=15,scale=540:-1:flags=lanczos,palettegen" palette.png
  • fps=15: Output 15 frames per second
  • scale=540:-1:flags=lanczos: Scale to 540px width, maintain aspect ratio, use lanczos resampling for quality
  • palettegen: Generate a 256-color palette optimized for this video

Step 2: Apply palette to create GIF

bash
ffmpeg -i input.mp4 -i palette.png -filter_complex "fps=15,scale=540:-1:flags=lanczos[x];[x][1:v]paletteuse" output.gif
  • [x]: First filter chain output (scaled video)
  • [1:v]: Second input (palette.png)
  • paletteuse: Apply the palette to the video

This produces a GIF with optimal colors and reasonable file size.

Method 3: Advanced paletteuse options

The paletteuse filter has options that affect quality and file size:

bash
ffmpeg -i input.mp4 -i palette.png -filter_complex "fps=15,scale=540:-1:flags=lanczos[x];[x][1:v]paletteuse=dither=bayer:bayer_scale=5:diff_mode=rectangle" output.gif
  • dither=bayer: Use Bayer dithering (good balance of quality and compression)
  • bayer_scale=5: Dithering intensity (1-5, higher = more dithering but smaller file)
  • diff_mode=rectangle: Only encode changed regions between frames (smaller file)

Dithering options comparison

Dither ModeQualityFile SizeUse Case
noneWorstSmallestSharp edges, simple graphics
bayerGoodSmallGeneral use (recommended)
heckbertGoodMediumSmooth gradients
floyd_steinbergBestLargePhotos, complex scenes
sierra2_4aBestLargePhotos, alternative to floyd_steinberg

Method 4: Trim and convert in one step

To convert only a portion of the video:

bash
# Generate palette from a 5-second clip starting at 10 seconds
ffmpeg -ss 00:00:10 -t 00:00:05 -i input.mp4 -vf "fps=15,scale=540:-1:flags=lanczos,palettegen" palette.png

# Convert the same clip using the palette
ffmpeg -ss 00:00:10 -t 00:00:05 -i input.mp4 -i palette.png -filter_complex "fps=15,scale=540:-1:flags=lanczos[x];[x][1:v]paletteuse" output.gif

Method 5: Batch conversion script

bash
#!/bin/bash
for f in *.mp4; do
  filename="${f%.*}"
  ffmpeg -i "$f" -vf "fps=15,scale=540:-1:flags=lanczos,palettegen" "/tmp/${filename}_palette.png"
  ffmpeg -i "$f" -i "/tmp/${filename}_palette.png" -filter_complex "fps=15,scale=540:-1:flags=lanczos[x];[x][1:v]paletteuse" "${filename}.gif"
  rm "/tmp/${filename}_palette.png"
done

Quality optimization tips

Frame rate

  • 10-15 fps is enough for most GIFs (YouTube videos at 30fps look fine at 12fps)
  • Higher frame rate = smoother motion but much larger file
  • For text-heavy or static scenes, 8-10 fps is fine

Resolution

  • 480-540px wide is the sweet spot for web
  • 720p+ GIFs become very large quickly
  • For thumbnails, 320px is sufficient

Duration

  • 3-8 seconds is ideal for most use cases
  • Longer GIFs have huge file sizes
  • If you need 30+ seconds, consider MP4 or WebM instead

Color optimization

  • Videos with limited color palettes (animations, screen recordings) compress better
  • Photos and videos with many colors produce larger GIFs
  • Use palettegen=stats_mode=single for videos with changing color schemes (each frame gets its own palette, larger file)

File size comparison

For a 5-second 720p video:

MethodFile SizeQuality
Naive conversion18 MBPoor
Two-stage (540px, 15fps)3.2 MBGood
Two-stage (480px, 12fps)1.8 MBGood
Two-stage (320px, 10fps)0.9 MBAcceptable
WebP equivalent1.1 MBExcellent

Modern alternatives to GIF

GIF is an old format (1987) with significant limitations. Modern alternatives:

WebP animation

bash
# Convert MP4 to animated WebP
ffmpeg -i input.mp4 -vcodec libwebp -filter:v "fps=15,scale=540:-1" -loop 0 -preset default -an output.webp
  • 30-60% smaller than GIF at same quality
  • Supports more than 256 colors
  • Modern browser support (Chrome, Firefox, Edge)

APNG (Animated PNG)

bash
# Requires additional tools; ffmpeg doesn't natively support APNG output
# But you can create a sequence of PNGs and assemble with other tools
ffmpeg -i input.mp4 -vf "fps=15,scale=540:-1" frame_%04d.png
  • Better quality than GIF (full color)
  • Native browser support (except older IE)

MP4 for short clips

If you don't specifically need GIF, MP4 with H.264 is much smaller:

bash
ffmpeg -i input.mp4 -ss 00:00:10 -t 00:00:05 -c:v libx264 -crf 23 -an output.mp4
  • 5-10x smaller than equivalent GIF
  • Universal browser support
  • Better quality

Common issues

IssueCauseSolution
GIF too largeHigh resolution or frame rateUse 480-540px and 12-15fps
Poor colorsDefault paletteUse two-stage palettegen/paletteuse
Choppy playbackFrame rate too lowUse 12-15fps minimum
Black bordersAspect ratio mismatchUse scale=540:-1 to maintain aspect ratio
GIF doesn't loopLoop flag missingAdd -loop 0 for infinite loop
Memory errorVideo too longTrim to 5-10 seconds

Summary

High-quality GIFs require FFmpeg's two-stage palette algorithm: palettegen to create a custom color palette, then paletteuse to apply it. The basic workflow is:

  1. ffmpeg -i input.mp4 -vf "fps=15,scale=540:-1:flags=lanczos,palettegen" palette.png
  2. ffmpeg -i input.mp4 -i palette.png -filter_complex "fps=15,scale=540:-1:flags=lanczos[x];[x][1:v]paletteuse" output.gif

For modern web use, consider WebP or MP4 instead of GIF — they're much smaller at the same quality.

Quick reference:

  • Always use two-stage palette algorithm
  • Sweet spot: 480-540px, 12-15fps, 5-8 seconds
  • Dithering: bayer for general use
  • Looping: -loop 0 (infinite)
  • Modern alternative: WebP or MP4

References

Last updated: