Convert a video to GIF with FFmpeg
A GIF has 256 colors per frame, so the quality depends on the palette. The primary command generates one from the clip and applies it in a single pass; the variants shrink the file.
Verified with FFmpeg 9.0.1 on 26 August 2026. Search intent: ffmpeg video to gif.
ffmpeg -ss 2 -t 4 -i input.mp4 -filter_complex "fps=15,scale=640:-1:flags=lanczos,split[s0][s1];[s0]palettegen[p];[s1][p]paletteuse" -loop 0 output.gifTakes 4 s starting at 2 s, drops to 15 frames per second, scales to 640 px wide, builds a 256-color palette from those frames and encodes the GIF with it. Output: 640x360, 60 frames, 4.0 s, looping forever. 1.4 MB on the test clip.
Flags, explained
- -ss 2 -t 4
- Start at 2 s, take 4 s. GIFs get heavy fast; keep them short.
- fps=15
- Frame rate of the GIF. 10 to 15 is the usual range; 30 doubles the size for little gain.
- scale=640:-1:flags=lanczos
- Width 640, height to match, Lanczos resampling for sharper downscaling. -1 is safe here because GIF has no even-size rule.
- split[s0][s1]
- Duplicate the frames so one copy feeds palettegen and the other paletteuse in the same graph.
- palettegen
- Analyse the frames and produce the best 256-color palette (max_colors lowers that).
- paletteuse
- Quantise every frame with that palette. dither=bayer:bayer_scale=5 swaps error diffusion for an ordered pattern that compresses better.
- -loop 0
- Loop forever. -loop -1 plays once, a positive number loops that many extra times.
Variants
Classic two-pass version (palette file on disk)
ffmpeg -ss 2 -t 4 -i input.mp4 -vf "fps=15,scale=640:-1:flags=lanczos,palettegen" palette.png
ffmpeg -ss 2 -t 4 -i input.mp4 -i palette.png -lavfi "fps=15,scale=640:-1:flags=lanczos[x];[x][1:v]paletteuse" -loop 0 output.gifSame result as the single command (byte-identical here), split into a palette pass and an encode pass. Useful when you want to reuse or edit the palette.
Smaller: 10 fps, 480 px, 64 colors, Bayer dithering
ffmpeg -ss 2 -t 4 -i input.mp4 -filter_complex "fps=10,scale=480:-1:flags=lanczos,split[s0][s1];[s0]palettegen=max_colors=64[p];[s1][p]paletteuse=dither=bayer:bayer_scale=5" -loop 0 small.gifEach knob cuts size: fewer frames per second, fewer pixels, fewer colors, and an ordered dither that compresses better than the default error diffusion. 402 KB against 1.4 MB for the same 4 s.
Quick and dirty, no palette
ffmpeg -ss 2 -t 4 -i input.mp4 -vf "fps=15,scale=640:-1" -loop 0 output.gifUses the encoder's generic palette. On our flat synthetic test pattern it was even smaller (833 KB), but on real footage it produces visible banding and speckle; the palette commands above exist for that reason.
Pitfalls we hit
- Size scales with frames x pixels x colors. A 640 px, 15 fps, 4 s GIF was 1.4 MB; the 480 px, 10 fps, 64-color version was 402 KB. Going to 30 fps or 1080 px produces multi-megabyte files.
- GIF has no audio. Anything you hear in the source is dropped silently.
- The single-pass command needs -filter_complex (not -vf) because the graph has two branches. Passing it to -vf fails to parse.
- Colors are limited to 256 per frame. Gradients and dark scenes band; the palette commands minimise it but cannot remove it. For real quality at a small size, an MP4 or WebM loop beats a GIF.
How we verified it
Run with FFmpeg 9.0.1 on 26 August 2026 against a 12 s, 1280x720, 30 fps H.264 file. Primary: gif, 640x360, 60 frames, 4.000 s, 1,417,600 bytes. Two-pass: identical size. Small variant: 480x270, 40 frames, 402,004 bytes. No palette: 640x360, 60 frames, 833,024 bytes on the synthetic pattern.
The same edit as a typed operation
convert_video_to_gif takes start, duration, fps and width, runs the palette pass for you and enforces the limits that keep GIFs sane (30 s, 160 to 1280 px, 5 to 30 fps). The filter graph never has to be written by hand or by an agent.
{
"inputs": [{ "id": "main", "url": "https://example.com/video.mp4" }],
"options": { "start": 2, "duration": 4, "fps": 15, "width": 640 }
}FAQ
Why is my GIF so big?
Every frame is stored almost fully, so size grows with frames per second, pixel count and duration. Cut the clip, drop to 10 to 15 fps, scale to 480 or 640 px and reduce colors before anything else.
Why does my GIF look grainy or banded?
You skipped the palette. The default encoder maps colors to a generic palette with heavy dithering. palettegen plus paletteuse builds a palette from your own frames, which removes most of it.
How do I make the GIF loop or play once?
The -loop output option: 0 loops forever (the default for the GIF muxer), -1 plays once, a positive number repeats that many extra times.
Should I use a GIF at all?
For chat and documentation, yes, because it embeds everywhere. For a web page, an MP4 or WebM in a muted, looping video tag is many times smaller at far better quality.
Related commands
- ffmpeg trim videoTrim a video with FFmpegRead
- ffmpeg resize videoResize a video with FFmpegRead
- ffmpeg compress videoCompress a video with FFmpegRead