How to Make High-Quality GIFs Using FFmpeg

Generating standard GIFs with FFmpeg often results in severe color banding and heavy pixelation because the GIF format is restricted to a maximum palette of 256 colors. By default, FFmpeg relies on a generic, static color palette that rarely matches the colors in your source video. To create sharp, high-quality GIFs, you must use the palettegen filter to extract a custom color palette specifically tailored to your video frames, and then apply that palette using the paletteuse filter.

The Problem with Default Conversions

Running a simple command like ffmpeg -i input.mp4 output.gif creates poor results because FFmpeg forces the video into an arbitrary 256-color map. The palettegen and paletteuse filters solve this via a two-stage process:

  1. palettegen: Scans the input video, determines the 256 most dominant and visually significant colors across the frames, and generates a small PNG image containing this custom color map.
  2. paletteuse: Takes the original video and the generated palette, mapping the video pixels to the custom palette using advanced dithering algorithms to hide color transitions smoothly.

Method 1: The Single-Command Approach

You do not need to generate a physical palette file on your disk. You can use FFmpeg's -filter_complex pipeline with the split filter to generate and apply the palette in a single run.

ffmpeg -i input.mp4 -filter_complex "[0:v] fps=15,scale=640:-1:flags=lanczos,split [a][b];[a] palettegen [p];[b][p] paletteuse" output.gif

How this command works:

  • fps=15: Reduces the frame rate to 15 frames per second to reduce file size.
  • scale=640:-1:flags=lanczos: Resizes the width to 640 pixels, automatically calculates the proportional height (-1), and applies the high-quality Lanczos resampling filter.
  • split [a][b]: Duplicates the resized video stream into two separate streams, labeled [a] and [b].
  • [a] palettegen [p]: Sends stream [a] into palettegen, creating the color palette stream labeled [p].
  • [b][p] paletteuse: Combines stream [b] (the video) and stream [p] (the custom palette) to generate the final GIF output.

Method 2: The Two-Step Approach

If you prefer to inspect the generated palette or want granular control over multi-step processing, run the generation in two distinct steps.

Step 1: Generate the Palette

ffmpeg -y -i input.mp4 -vf "fps=15,scale=640:-1:flags=lanczos,palettegen" palette.png

Step 2: Render the GIF using the Palette

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

Fine-Tuning for Better Quality and Smaller File Sizes

Both palettegen and paletteuse support configuration flags to optimize quality depending on the scene content.

1. Handling Dynamic Scenes with stats_mode

By default, palettegen calculates a single global palette for the entire video clip (stats_mode=full). If your video features drastic scene changes or varying color schemes, you can generate a new palette for every single frame:

ffmpeg -i input.mp4 -filter_complex "[0:v] fps=15,scale=640:-1:flags=lanczos,split [a][b];[a] palettegen=stats_mode=single [p];[b][p] paletteuse=new=1" output.gif
  • stats_mode=single: Recomputes the color palette for each frame.
  • new=1: Tells paletteuse to acquire a new palette for each frame. Note that this option significantly increases file size.

2. Controlling Dithering

Dithering blends colors to prevent banding, but it can introduce background noise and bloat file sizes. You can adjust the dithering algorithm inside paletteuse:

  • Bayer Dithering (Lower file size, crosshatch pattern):

    paletteuse=dither=bayer:bayer_scale=3

    bayer_scale ranges from 0 to 5. Higher values reduce noise and file size at the cost of slight banding.

  • Floyd-Steinberg (Default, smoother gradients):

    paletteuse=dither=floyd_steinberg
  • No Dithering (Smallest file size, best for flat graphics or screen recordings):

    paletteuse=dither=none