Ahosting Logo
Knowledge Base

How to Create Animated GIFs with FFmpeg

Why the obvious GIF command produces a bad resultGIF holds 256 coloursso a default conversionpicks a poor paletteFirst pass: generate a palettefrom the actual contentof the clipSecond pass: use itwith a dithering choiceyou made deliberatelyThen the size leversframe rate, dimensions,and lengthThree levers control GIF size and all of them are blunt: fewer frames per second, smaller dimensions, and a shorterclip.

FFmpeg makes good animated GIFs, and the naive command makes bad ones. The difference is a step called palette generation, and it is the whole subject.

Why the obvious command looks wrong

ffmpeg -i input.mp4 output.gif

That works, and the result is usually banded, dirty-looking, and enormous.

GIF supports only 256 colours. Encoding directly means FFmpeg picks a generic palette without looking at your video, so gradients break into bands and colours shift.

Generate a palette from the video

Two passes. First, examine the video and produce the best 256 colours for it:

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

Then encode using that palette:

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

The filters in both commands must match. A palette built at one size and applied at another is built from the wrong pixels, and the improvement is lost.

The result is dramatically better and usually smaller, because a palette that fits the content wastes fewer colours.

One command, no temporary file

ffmpeg -i input.mp4 -vf \
"fps=15,scale=480:-1:flags=lanczos,split[a][b];[a]palettegen[p];[b][p]paletteuse" \
output.gif

split duplicates the stream so one copy generates the palette and the other uses it. Convenient, and the two-file version is easier to reason about when you are adjusting settings.

The three settings that control size

GIF has no real compression, so file size is roughly width × height × frames. All three are yours to choose.

Frame rate. The largest lever. 15 fps looks fine for most content; 10 is acceptable for screen recordings. Going from 30 to 15 halves the file.

Dimensions. 480 pixels wide is generous for something embedded in a page. 320 is often enough.

Duration. Trim first, not after. Trimming and cutting goes into taking the segment you want.

A ten-second clip at 480 pixels and 15 fps lands around one to three megabytes depending on how much movement there is. Static content compresses far better than a panning shot.

Dithering

paletteuse takes a dither option, and the default suits most content.

For flat colours: screen recordings, diagrams, anything with large uniform areas, turning dithering off produces a cleaner, smaller result:

paletteuse=dither=none

For photographic content, leave the default on. Without it, gradients band visibly.

This is the setting worth trying both ways on a short sample, because which is better depends entirely on the content.

A palette that changes with the scene

By default the palette is built from the whole clip. If your video changes scene dramatically, one palette serves both badly.

palettegen=stats_mode=diff

This weights the palette toward what changes between frames rather than what is merely present, which usually helps on moving content.

For a clip with genuinely different scenes, the better answer is to cut it into separate GIFs.

Looping

GIFs loop forever by default. To change it:

-loop 0

Confusingly, 0 means infinite and -1 means play once. A specific number plays that many additional times.

Consider not using GIF

Worth saying, because GIF is usually the wrong format for the web now.

A short MP4 or WebM is typically five to ten times smaller than the same content as a GIF, with better colour. Modern browsers autoplay a muted, looping, inline video exactly like a GIF, and the visitor cannot tell the difference except that it loads faster.

<video autoplay loop muted playsinline>
 <source src="clip.mp4" type="video/mp4">
</video>

Use GIF when the destination requires it: some chat platforms, some email clients, some documentation systems. For your own website, video is better in every measurable way.

If the concern is bandwidth on a busy page, this single change often saves more than any image optimisation. See optimizing images.

When the output is enormous

Check the three levers in order: frame rate first, then dimensions, then duration.

If it is still large, the content is the reason. A GIF stores every frame nearly in full, so continuous motion. A pan, a fade, camera shake, is expensive in a way that a static shot with one moving element is not.

Cropping to the part that actually moves is often the biggest saving available, and it is easy to overlook. Resizing and cropping has the detail.

Decide the dimensions before anything else

Size is the single largest factor in the output, and it is decided by the width more than by any encoding setting.

ffmpeg -i in.mp4 -vf "fps=12,scale=480:-1:flags=lanczos" -frames:v 1 test.png
ls -la test.png

Halving the width reduces the pixel count to a quarter, which reduces the file by roughly the same proportion. No other setting has that effect.

Choose the width from where it will be displayed rather than from the source. A clip shown at 400 pixels wide gains nothing from being produced at 800, and costs four times the bytes to deliver it.

Keep the clip short and the rate low

Every frame is stored separately in this format, so the size is close to linear in both duration and rate.

ffmpeg -ss 5 -t 4 -i in.mp4 -vf "fps=10,scale=480:-1:flags=lanczos" out.gif
ffprobe -v error -show_entries format=duration -of csv=p=0 out.gif 2>/dev/null
ls -la out.gif

Ten frames a second is enough for most demonstrations and half the size of twenty. Four seconds is enough to show a single action.

Where the material genuinely needs to be longer or smoother, that is the point at which the format is the wrong choice rather than a reason to accept a large file.

Check where it is going to be used

Several platforms convert this format to video on upload, which makes the careful optimisation irrelevant.

curl -sI https://example.com/out.gif | grep -iE 'content-type|content-length'

Where the destination converts anyway, producing a video directly is smaller, better quality and faster to deliver.

The remaining genuine uses are contexts that cannot play video: some email clients, some documentation systems, and anywhere autoplay without controls is required. Those are the cases where the format earns its cost, and they are fewer than habit suggests.

Test the result at its display size

A clip judged at full size can be illegible where it actually appears, particularly when it shows an interface.

ffmpeg -i out.gif -vf "scale=240:-1" -frames:v 1 small.png

Look at whether the thing being demonstrated is still visible. Text in a user interface is the usual casualty, and it disappears at exactly the sizes these clips are embedded at.

Where it is not legible, cropping to the relevant region rather than scaling the whole frame keeps the detail and reduces the size at the same time.