Convert Video to Animated WebP with FFmpeg (10x Smaller Than GIF)

You need a short looping clip on a page, in a README, or in a docs site, and the GIF your encoder just spat out is 4 MB. Dropping the frame rate makes it choppy, dropping the palette makes it band, and neither gets you under a megabyte. Animated WebP fixes that without changing how the clip is embedded.
Quick answer: To convert video to animated WebP, runffmpeg -i input.mp4 -vf "fps=15,scale=480:-1:flags=lanczos" -c:v libwebp -quality 75 -compression_level 6 -loop 0 -an out.webp. FFmpeg's WebP muxer plays the animation once by default, so-loop 0is the flag that makes it repeat forever. A 10-second clip that weighs 4 MB as a GIF usually lands between 300 KB and 700 KB as a lossy animated WebP, with 24-bit color instead of GIF's 256-color palette. If you'd rather not keep a libwebp-enabled FFmpeg build alive on a server, FFmpeg Micro runs the same conversion as one API call and can return the WebP and a GIF fallback from a single job.
Animated WebP wins on the embed, not just the bytes
The standard advice for fat GIFs is to stop using GIFs and ship an MP4 in a <video> tag instead. That advice is right about file size. An H.264 MP4 of the same clip will beat animated WebP almost every time, often by half again.
It's not a compression problem, though. It's an embed problem. The moment a GIF becomes a <video> element, you inherit autoplay policy: muted, playsinline, and a browser that may still decline on Low Power Mode. You also lose every surface that only accepts an image. GitHub READMEs, most CMS rich-text fields, Notion embeds, Slack unfurls, and email all take an <img src> and nothing else.
Animated WebP keeps the <img> tag and throws away the palette. That's the whole trade. You give up maybe 40% versus MP4 and you get back a file that renders anywhere an image renders, with full 8-bit alpha and no autoplay negotiation. We made the opposite argument in the case for converting legacy GIFs to MP4 when the target is a web page you control. When you don't control the container, WebP is the one that survives.
The FFmpeg command, and what each flag is doing
The full conversion is one pass through libwebp. Nothing needs a palette pre-pass, which is the step that makes GIF encoding a two-command job.
ffmpeg -i input.mp4 \
-vf "fps=15,scale=480:-1:flags=lanczos" \
-c:v libwebp -quality 75 -compression_level 6 \
-loop 0 -an out.webp
-quality runs 0 to 100 and defaults to 75. For lossy output it behaves like a JPEG quality dial: 50 is visibly soft on text, 75 is the sane default, and above 90 you're mostly buying file size. -compression_level runs 0 to 6 and defaults to 4. It costs encode time, not quality, so set it to 6 unless you're converting thousands of files on a clock.
-lossless 1 switches the encoder to WebP's lossless mode, where -quality stops meaning image quality and starts meaning compression effort. Use it for screen recordings of flat UI, line art, and anything with hard edges and few colors. On camera footage, lossless animated WebP will be several times larger than the GIF you're replacing.
-loop 0 deserves its own sentence because the default is wrong for most uses. FFmpeg's WebP muxer writes a loop count of 1, meaning the animation plays once and freezes on the last frame. GIF defaults to infinite. If your converted clip stops dead and you've stared at the filter chain for ten minutes, it's the loop count.
Frame rate and width move the file size more than quality does
With the standard libwebp encoder, every frame in the animation is compressed on its own. There's no inter-frame prediction, no motion vectors, nothing carried forward from the previous picture. File size tracks frame count almost linearly, which means going from 30 fps to 15 fps cuts the output roughly in half and going to 12 fps cuts it further.
Width behaves the same way, on the square. A 480 px wide animation is about 44% of the bytes of a 720 px one at identical settings. For a UI demo or a product loop, 480 to 600 px wide at 12 to 15 fps is the range where the file stops being a problem and the clip still reads.
Run ffmpeg -encoders | grep webp on your build and you may also see libwebp_anim, which uses libwebp's animation encoder API and does real frame differencing between pictures. On mostly static content like a screen recording where only a cursor moves, it can beat plain libwebp by a wide margin. It's build-dependent and less exercised, so test it against your own footage rather than switching blindly.
What you actually save
Numbers depend heavily on content, so here's one concrete clip: a 10-second 1080p screen recording of a dashboard with gradient backgrounds, converted at 15 fps and 480 px wide in every row.
| Output | Settings | Size |
|---|---|---|
| GIF | `palettegen` + `paletteuse`, 256 colors | 4.1 MB |
| Animated WebP, lossy | `-quality 75 -compression_level 6` | 410 KB |
| Animated WebP, lossy | `-quality 50 -compression_level 6` | 240 KB |
| Animated WebP, lossless | `-lossless 1` | 2.3 MB |
| H.264 MP4 | `-crf 28`, same fps and width | 180 KB |
Gradients are where GIF falls apart, because dithering a smooth ramp into 256 colors adds high-frequency noise that LZW can't compress. That's why this clip hits a 10x reduction. Google's own WebP documentation puts the average at about 64% smaller than GIF for lossy animation and 19% for lossless, measured across a broad corpus. Flat cartoon animation with six colors lands near that average. Camera footage and gradients land far past it.
Transparency survives the conversion, unlike in GIF
GIF gives you one transparent color index, which is why every transparent GIF has fringed, aliased edges. Animated WebP carries a real 8-bit alpha channel, so a logo sting or an animated overlay keeps its soft edges.
You need a source that actually has alpha (ProRes 4444, QuickTime Animation, or VP9 with alpha in WebM) and you need to tell libwebp to keep it:
ffmpeg -i logo-sting.mov \
-vf "fps=20,scale=400:-1" \
-c:v libwebp -pix_fmt yuva420p -quality 80 \
-loop 0 -an logo.webp
yuva420p gives you lossy color with a lossless alpha plane. For hard-edged graphics, -lossless 1 -pix_fmt bgra keeps the edges perfect at a size cost. If your output comes out with a black background instead of transparency, the alpha was already gone before libwebp saw it, which is nearly always an input or filter-chain problem rather than an encoder one. We walked through diagnosing that in why transparent video breaks FFmpeg overlays.
Where animated WebP still loses, and what to ship instead
Browser support stopped being the issue years ago. Chrome, Edge, and Firefox have rendered animated WebP for a decade, and Safari joined in version 14 (macOS Big Sur and iOS 14, released September 2020). Email is a different story, and it hasn't moved.
| Surface | Animated WebP | Ship this |
|---|---|---|
| Chrome, Edge, Firefox, Safari 14+ | Renders | WebP in a plain `<img>` |
| Safari 13 and older | No | `<picture>` with a GIF fallback |
| Outlook on Windows | No | GIF |
| Most webmail clients | Inconsistent | GIF |
| GitHub README, docs sites | Renders | WebP |
For the web, <picture> handles the split without JavaScript:
<picture>
<source srcset="demo.webp" type="image/webp">
<img src="demo.gif" alt="Dashboard filtering demo" width="480">
</picture>
Which means for anything that touches email you're maintaining two encodes of the same clip, at two sets of settings, forever. That's the part worth automating rather than scripting by hand on a box you have to patch. FFmpeg Micro takes the source once and returns both outputs from a single job, so the WebP and its GIF fallback stay in sync when the source changes. The job semantics and the parameters are in the docs.
Pitfalls that cost people an afternoon
Most animated WebP failures come from four places, and all four look like encoder bugs when they aren't.
- You got a folder of numbered stills. A
%03din the output filename tells FFmpeg to write an image sequence, not an animation. Remove it. Plainout.webpproduces one animated file. - The animation plays once. That's the muxer's default loop count of 1. Add
-loop 0. - The timing is wrong on a screen recording. Variable-frame-rate captures from QuickTime, OBS, and phone screen recorders confuse the encoder's frame durations. Putting
fps=15first in the filter chain forces a constant rate and fixes the drift. When converting an existing GIF, do the opposite and add-fps_mode passthrough(-vsync 0on older builds) to preserve the original per-frame delays. Unknown encoder 'libwebp'. Your FFmpeg wasn't built with--enable-libwebp. This is common in Alpine-based Docker images and the static builds bundled with automation tools. The same diagnosis applies as with any missing FFmpeg encoder: checkffmpeg -encoders, not the error message.
One more that isn't an error: don't convert a full 1080p 30 fps clip to animated WebP because you can. Thirty seconds at that resolution produces a file measured in tens of megabytes and a decode cost that stutters on mobile. Animated WebP is for clips under about 15 seconds at 600 px or less. Past that, the right answer is a video element.
FAQ
Is animated WebP really smaller than GIF?
Animated WebP is smaller than GIF on essentially all real content, by about 64% on average for lossy encoding according to Google's WebP documentation. Content with gradients, photography, or noise does much better than that average because GIF's 256-color palette handles those badly. Flat, few-color animation does closer to the average or slightly worse in lossless mode.
Does animated WebP work in email?
Animated WebP does not work reliably in email. Outlook on Windows renders nothing at all, and webmail support is inconsistent enough that animated GIF is still the only format you can send to an arbitrary inbox. Keep a GIF encode for email and use WebP everywhere else.
Can I convert an existing GIF to animated WebP?
Yes, and FFmpeg handles GIF input directly: ffmpeg -i in.gif -fps_mode passthrough -c:v libwebp -quality 75 -compression_level 6 -loop 0 -an out.webp. The -fps_mode passthrough flag matters because GIFs store per-frame delays that are often uneven, and forcing a constant frame rate will change the animation's timing.
How do I make an animated WebP loop forever in FFmpeg?
Pass -loop 0 to make an animated WebP loop forever. FFmpeg's WebP muxer defaults to a loop count of 1, which plays the animation once and holds the final frame, so an animation that appears to freeze is almost always missing this flag rather than encoded wrong.
What frame rate should I use for animated WebP?
Use 12 to 15 fps for animated WebP in most cases. Because the default libwebp encoder compresses each frame independently, file size scales close to linearly with frame count, so halving the frame rate roughly halves the output. UI demos read fine at 12 fps; fast camera motion needs 20 to 24 and will cost you proportionally.
If you'd rather hand the whole thing off than maintain a libwebp-enabled build, wire that step to an API instead: sign up free and send one job that returns the WebP and the GIF fallback together.
About Javid Jamae
Founder & CEO at FFmpeg Micro
Javid is a software engineer, author, and entrepreneur with over 25 years of professional software development experience across enterprise, startup, and consulting environments. He founded FFmpeg Micro to make video processing accessible to developers through a simple, automation-first REST API.
You might also like

FFmpeg container error? It's the subtitle track: -c:s mov_text
FFmpeg's codec not currently supported in container error is a subtitle mux failure. Fix it with -c:s mov_text, burn in bitmap subtitles, or switch to MKV.

Filtering and Streamcopy Cannot Be Used Together. Keep the Copy.
"Filtering and streamcopy cannot be used together" means your filtergraph forces a decode. Keep -c copy where it counts by splitting codecs per stream.

Fix Instagram Reels API Error 2207052 (Media Upload Has Failed)
Instagram error 2207052 means Meta's transcoder rejected your video's format, not your API code. Map every cause to an ffprobe check and one FFmpeg fix.
Skip the command line
The Video to Animated GIF Converter blueprint runs the same conversion for you: upload the video, pick the length and width, download the GIF.
Run it (free)