FFmpeg is the standard tool for turning a video into a GIF, and its one-line conversion is also the reason so many GIFs are huge and blotchy. This page measures what each step does to file size and quality, gives the command that works, and ends with the case where you do not have a video at all — an animation built in HTML and CSS — which needs no FFmpeg.
Every number here was measured on 2026-11-07 with FFmpeg 9.0.2 (the linuxserver/ffmpeg Docker image), on a deliberately hard test clip: 6 seconds of 1280×720 at 30 fps, FFmpeg's testsrc2 pattern blended with a moving Mandelbrot set — lots of colour, detail and motion. Quality is SSIM against the source scaled the same way: 1.0 is identical, lower is worse.
What we measured
| Command (on the 6-second clip) | Output | Size | SSIM |
|---|---|---|---|
ffmpeg -i in.mp4 out.gif |
1280×720, 180 frames | 37.9 MB | — |
3 s, fps=15, scale=480:-1 |
480×270, 45 frames | 1.74 MB | 0.49 |
same + palettegen / paletteuse |
480×270, 45 frames | 2.83 MB | 0.87 |
same + stats_mode=diff, Bayer dithering |
480×270, 45 frames | 2.02 MB | 0.90 |
| same clip as MP4 (H.264, CRF 23) | 480×270 | 0.16 MB | 0.95 |
The one-liner made a 38 MB GIF from 6 seconds of video. Cutting length, frame rate and size took it to 1.7 MB — but without a palette, FFmpeg's GIF encoder maps every frame to a fixed set of colours, and the result scored 0.49: visibly banded and noisy. A palette made from the clip itself cost a bit more space and lifted the score to 0.87.
The command that works
ffmpeg -ss 1 -t 3 -i in.mp4 \
-vf "fps=15,scale=480:-1:flags=lanczos,split[s0][s1];[s0]palettegen[p];[s1][p]paletteuse" \
out.gif
Read it left to right:
-ss 1 -t 3— start at 1 second, keep 3 seconds. Placed before-i, FFmpeg seeks in the input instead of decoding everything before it. Length is the biggest lever: size grows with every frame.fps=15— 15 frames per second is smooth for most motion; GIFs at 30 fps double the frames for little visible gain.scale=480:-1:flags=lanczos— 480 pixels wide, height in proportion, with a sharp scaler. GIFs are shown small in chats and emails; 1280 pixels is wasted.split … palettegen … paletteuse— GIF allows at most 256 colours.palettegenpicks the 256 that best fit this clip, andpaletteusemaps the frames onto them, in one pass.
Looping is the default: FFmpeg's GIF muxer documents -loop as "0 for looping indefinitely (default)", and -loop -1 plays once (FFmpeg formats).
Tuning the palette
Two options moved our numbers further:
palettegen=stats_mode=diffbuilds the palette from the parts that change between frames. FFmpeg's docs describe it as giving "more importance to the moving part of your input if the background is static" — screen recordings, UI demos, a logo animating on a flat background.paletteuse=dither=bayer:bayer_scale=5:diff_mode=rectangleuses an ordered dither (a regular pattern compresses better than random noise) and redraws only the changed rectangle of each frame.
Together they made the GIF smaller (2.02 MB against 2.83 MB) and slightly closer to the source (0.90 against 0.87). For flat graphics — text, icons, solid colours — try dither=none too and compare: flat colours need no dithering.
When MP4 is the better answer
The same 3-second, 480-pixel clip was 160 KB as MP4 — about 13 times smaller than the best GIF — and the most faithful of all (0.95):
ffmpeg -ss 1 -t 3 -i in.mp4 -vf "fps=15,scale=480:-2:flags=lanczos" \
-c:v libx264 -crf 23 -pix_fmt yuv420p -movflags +faststart -an out.mp4
scale=480:-2 keeps the height even, which H.264 requires; -pix_fmt yuv420p makes the file play everywhere; -movflags +faststart lets it start before it has fully downloaded; -an drops the audio. On a web page, a muted, looping <video autoplay muted loop playsinline> behaves like a GIF. Social networks, Discord and Telegram all take MP4.
GIF still wins where video is not accepted: email, where video generally does not play and GIF is the usual way to animate, some forums and older chat tools, and places that only take an image upload.
When there is no video: GIFs from HTML
Many GIFs are not cut from footage at all. A countdown to a launch, an animated stat ("10,000 users"), a product reveal, a "typing" headline — these are designed as HTML and CSS animations, then screen-recorded and converted with FFmpeg. That round trip loses quality twice and needs a browser, a recorder and the commands above.
Shareglint renders a template's CSS animation straight to a GIF or MP4: the animation is drawn frame by frame by Chrome and encoded once, with the template's data filled in, so the same countdown can show a different date for every customer.
curl -X POST "https://shareglint.com/v1/images?wait=true" \
-H "Authorization: Bearer sk_live_..." \
-H "Content-Type: application/json" \
-d '{ "template": "animated-countdown", "format": "gif",
"modifications": { "event": "Doors open", "number": "3", "unit": "days" } }'
Limits, honestly: animations up to 5 seconds, a long edge of at most 1080 pixels, and 5 renders of quota per animated file. It records CSS animations of HTML templates — it does not take a video as input, and it does not trim, convert or edit videos; for that, FFmpeg as above is the right tool. HTML to GIF shows which templates animate and how to write your own.
Checklist for a GIF from video
- Keep it short: 2–4 seconds of the part that matters.
- 10–15 fps and 320–600 pixels wide.
- Always use a generated palette; try
stats_mode=difffor static backgrounds. - Check the file size before sending: a GIF over a few megabytes loads slowly in email and chat.
- If the destination plays video, send MP4 instead.