The fast answer: ffmpeg mp4 to gif in one command
That's it. FFmpeg sees the .gif extension and handles the conversion. You now have a GIF.
You also have a problem. That GIF is probably 10-20x larger than the original MP4. A 2MB video just became a 40MB GIF. GIF compression is ancient (LZW from 1987), and it stores every frame as a separate image. H.264 video compression is far more efficient.
So the real question isn't "how do I convert MP4 to GIF." It's "how do I convert MP4 to GIF without the output being unusable."
Reduce the size first
Before touching color optimization, cut the dimensions and frame rate. These two changes alone will shrink your GIF by 90%+.
What this does:
fps=10drops the frame rate to 10 frames per second. Most videos are 24-60fps. GIFs don't need that. 10fps looks fine for most content, and 15fps is smooth enough for anything.scale=480:-1sets the width to 480 pixels and calculates the height automatically to keep the aspect ratio. GIFs are small by nature. Nobody is viewing your GIF at 1920px wide.flags=lanczosuses the Lanczos resampling algorithm for the downscale. Better quality than the default bilinear.
I ran this on a 10-second 1080p clip at 30fps. The naive ffmpeg -i input.mp4 output.gif produced a 38MB file. With fps=10,scale=480:-1, it dropped to 3.8MB. Same content, watchable quality, 90% smaller.
Fix the colors with palette optimization
Here's something most tutorials gloss over. GIFs only support 256 colors per frame. That's a hard limit baked into the format specification from 1987. When FFmpeg converts video to GIF without any palette work, it falls back to a generic 256-color palette. This palette tries to represent everything and does a mediocre job at all of it. You end up with color banding in gradients, washed-out skin tones, and that distinctly "cheap GIF" look everyone recognizes.
The fix is two FFmpeg filters that work together: palettegen and paletteuse. The first one analyzes every frame of your video and builds a custom 256-color palette optimized for the actual colors in your footage. The second one applies that palette during the conversion. The difference is obvious side by side.
One-pass method (single command)
The filter chain looks complicated but it's doing something simple:
splitduplicates the video stream into two copiespalettegenanalyzes one copy and generates the optimal palettepaletteuseapplies that palette to the other copy
The quality difference is immediately visible. Colors stay true. Gradients don't band. Skin tones don't go green.
Two-pass method (separate palette file)
Some people prefer generating the palette as a separate file first. This gives you more control and lets you inspect the palette before applying it:
The two-pass approach is functionally identical to the single-command split method. Use whichever you find easier to read. The one-pass version is better for scripting since it's a single command.
Fine-tuning the palette
For even better results, you can tweak palettegen and paletteuse parameters:
max_colors=128uses fewer colors. Sounds counterintuitive, but for some content (screen recordings, simple animations) fewer colors with better dithering beats 256 poorly chosen colors. File size drops too.stats_mode=diffonly considers pixels that change between frames when building the palette. Good for screencasts and UI recordings where most of the frame is static.dither=bayeruses ordered dithering instead of the default Sierra. Produces a cross-hatch pattern that some people prefer for its retro look. Also compresses better.bayer_scale=5controls the cross-hatch pattern size (range 0-5). Lower values give a finer pattern. 5 gives the coarsest dither, which usually compresses the smallest.diff_mode=rectangleonly re-encodes the rectangular region that changed between frames. Reduces file size without touching quality.
Which dither method to use
FFmpeg supports several dithering algorithms. Here's a quick comparison:
| Dither | Look | File size | Best for |
sierra2_4a (default) | Smooth gradients | Larger | Photos, live video |
bayer | Cross-hatch pattern | Smaller | Screen recordings, UI demos |
none | Hard color edges | Smallest | Flat-color animations, pixel art |
floyd_steinberg | Smooth, classic | Medium | General purpose |
For screen recordings and tutorials, dither=bayer with bayer_scale=3 is a safe bet. For live video with lots of gradients, stick with the default.
Trim before converting
You almost never want to convert an entire video to GIF. Trim it first:
-ss 5 seeks to the 5-second mark. -t 3 captures 3 seconds. Placing -ss before -i makes the seek fast (input seeking) rather than decoding every frame up to that point.
Keep GIFs under 5 seconds. Anything longer and you're fighting the format. File sizes balloon. Loading times suffer. If your clip needs to be longer than 5 seconds, it probably shouldn't be a GIF.
Speed up or slow down the GIF
You can change playback speed during conversion. Useful for turning a 10-second clip into a 5-second GIF without cutting content:
setpts=0.5*PTS halves the presentation timestamp of each frame, making the video play at double speed. Put it before fps in the filter chain so the frame rate filter applies to the sped-up video.
Batch convert multiple MP4 files to GIF
Nobody on the first page of Google covers this, and it's the most common real-world scenario. You don't have one video. You have a folder of them.
Bash loop
This converts every MP4 in the current directory. ${f%.mp4}.gif strips the .mp4 extension and adds .gif. Simple enough for a handful of files.
Parallel processing with GNU parallel
The bash loop works for 5 files. Maybe 10. But at scale, sequential processing is slow. FFmpeg's palette-based GIF conversion is single-threaded, so throwing more cores at a single file doesn't help. But you can run multiple files at once:
This runs 4 conversions simultaneously. Adjust -j4 to match your CPU core count. The trade-off: your machine becomes unusable while it's running. For more FFmpeg batch processing patterns, see our guide to making videos unique in batch.
Where local batch processing breaks down
The parallel approach works for 10-50 files on a beefy machine. Beyond that, you hit real walls: CPU saturation, thermal throttling on laptops, and the fact that your machine is a brick while encoding runs. If you're doing this on a CI server or a production machine that needs to serve other traffic, it's a non-starter.
Convert MP4 to GIF via API
The alternative: send the FFmpeg command to an API and let someone else's servers do the work. Same ffmpeg syntax, zero local CPU usage.
With RenderIO's FFmpeg API, the command is a single HTTP request:
You get back a command_id. Poll for the result or set up a webhook. When it's done, your GIF is stored and ready to download. For 20 more copy-paste curl examples like this, check the FFmpeg API curl examples.
Batch conversion via API
Batch processing is where the API pays for itself. Instead of processing files sequentially on your machine, fire off requests in parallel:
All conversions run in parallel on cloud infrastructure. No local CPU usage. No waiting. For more patterns like this, the Node.js FFmpeg API tutorial covers error handling, polling, and webhook setup.
Python batch conversion
If Python is your language, here's the same idea:
For a more complete Python integration including polling and error handling, see the FFmpeg API with Python guide.
Add text overlays to your GIF
Want captions or watermarks? FFmpeg's drawtext filter handles this:
The drawtext filter goes before the split in the filter chain. Key parameters: x and y for position (this example centers horizontally and places text near the bottom), box=1 adds a semi-transparent background so text stays readable regardless of the video content.
Create a reverse or boomerang GIF
To play a clip in reverse:
reverse flips the frame order so the clip plays backward. -loop 0 makes it loop forever.
For a true boomerang (plays forward then backward), you need to duplicate the frames and concatenate the reversed copy:
This splits the stream into two copies, reverses one, and concatenates them so the GIF plays forward then backward in a seamless loop. Keep the source clip short (1-2 seconds) or the file size doubles and the effect loses its punch.
Control looping behavior
By default, FFmpeg GIFs loop infinitely. You can change this:
-loop 0 means infinite. -loop 3 means play 3 times. -loop -1 means no looping. Confusing? Yes. That's the GIF spec for you.
When to skip GIF entirely
GIF is a 39-year-old format. It has 256 colors, no compression to speak of, and produces massive files. For many use cases, animated WebP or even looping MP4 is the better choice.
Animated WebP supports millions of colors, alpha transparency, and lossy compression. A WebP version of the same content is typically 50-70% smaller than the equivalent GIF. FFmpeg can create them:
AVIF animated is even newer. Browsers shipped support in 2023-2024, and file sizes are roughly 80-90% smaller than GIF. Support is still spotty in email clients and older apps though.
Looping MP4 is even smaller. Most platforms that "support GIF" actually convert uploaded GIFs to MP4 or WebM behind the scenes (Twitter, Imgur, Giphy all do this). If you already have GIFs and want to convert them to MP4 with proper browser compatibility, the GIF to MP4 conversion guide covers the flags you need. If you're displaying the animation on a web page, a muted autoplay MP4 is lighter and better looking:
Size comparison
Here's what a 5-second 480p clip looks like across formats:
| Format | File size | Colors | Browser support |
| GIF (no palette) | ~15MB | 256 | Everything |
| GIF (with palette) | ~4MB | 256 | Everything |
| Animated WebP | ~1.5MB | 16.7M | 97%+ browsers |
| Animated AVIF | ~0.8MB | 16.7M | ~92% browsers |
| Looping MP4 (H.264) | ~0.4MB | 16.7M | Everything |
When GIF still wins: email (most clients don't support video or WebP), Slack/Discord embeds (auto-play without user interaction), GitHub READMEs, documentation sites, and any context where you need the widest possible compatibility. GIF works in every browser, every email client, every messaging app. That's its only remaining advantage over WebP and looping video, but it's a genuine one.
If your GIFs are going in emails or documentation, stick with GIF. If they're going on a web page you control, use WebP or MP4 and save your users the bandwidth. For more on processing video for the web without managing servers, see running FFmpeg in the cloud.
Common errors and how to fix them
"No such filter: palettegen" -- You're running an old FFmpeg build. palettegen and paletteuse were added in FFmpeg 2.6 (March 2015). Update your FFmpeg installation. Check your version with ffmpeg -version.
"Output file is 0 bytes" -- Usually means the input video has no video stream (audio-only file) or the seek position (-ss) is past the end of the video. Check with ffprobe input.mp4 first.
"GIF is the same size as without palette" -- Make sure you're using the split filter chain correctly. A common mistake is putting palettegen and paletteuse in the wrong order or forgetting the stream labels ([s0][s1], [p]).
"Colors look wrong/shifted" -- Try a different dither method. The default sierra2_4a can struggle with certain color ranges. Switch to dither=floyd_steinberg or dither=bayer:bayer_scale=3.
"GIF won't loop" -- Add -loop 0 to the command. Some FFmpeg builds default to one-shot playback.
Frequently asked questions
How do I reduce GIF file size without losing quality?
The single biggest reduction comes from lowering resolution and frame rate. scale=480:-1 and fps=10 will cut 90% of the file size. After that, palette optimization with palettegen/paletteuse improves quality at the same size. Going below 8fps starts to look choppy. Going below 320px wide makes text unreadable.
Can I convert just a part of an MP4 to GIF?
Yes. Use -ss for start time and -t for duration: ffmpeg -ss 5 -t 3 -i input.mp4 [filters] output.gif. Place -ss before -i for faster seeking.
Why is my GIF so much bigger than the original MP4?
GIF stores each frame as a separate image with LZW compression. MP4 uses inter-frame compression (only storing what changes between frames). A 2MB MP4 can become a 40MB GIF. Use fps=10, scale=480:-1, and palette optimization to keep sizes manageable.
What's the maximum GIF size for Discord/Slack/GitHub?
Discord: 25MB for Nitro users, 8MB for free. Slack: 1GB (but thumbnails only generate for files under ~50MB). GitHub: 10MB for READMEs and issues. In practice, keep your GIFs under 5MB for best compatibility and fast loading.
Is there a better tool than FFmpeg for GIF conversion?
gifski produces slightly better quality GIFs by using pngquant's color quantization. It's worth trying for quality-critical GIFs. For automation and batch processing though, FFmpeg is harder to beat since it handles the full pipeline (trim, resize, convert) in one command.
Can I do this without installing FFmpeg?
Yes. Cloud APIs let you run any ffmpeg mp4 to gif command over HTTP. Send a POST with your command, get the result back. No local installation needed. Get an API key and try it, or read the complete FFmpeg API guide to see how it works. Our comparison of hosted vs self-hosted FFmpeg covers the cost math.
Quick reference: every ffmpeg mp4 to gif command
| Task | Command |
| Basic conversion | ffmpeg -i input.mp4 output.gif |
| Optimized (small + good colors) | ffmpeg -i input.mp4 -vf "fps=10,scale=480:-1:flags=lanczos,split[s0][s1];[s0]palettegen[p];[s1][p]paletteuse" output.gif |
| Trim first | ffmpeg -ss 5 -t 3 -i input.mp4 -vf "fps=10,scale=480:-1:flags=lanczos,split[s0][s1];[s0]palettegen[p];[s1][p]paletteuse" output.gif |
| With text overlay | Add drawtext=text='Your Text':fontsize=24:fontcolor=white:x=(w-text_w)/2:y=h-th-10 before split |
| Reverse | Add reverse after paletteuse |
| Boomerang | Use split[fwd][rev];[rev]reverse[bwd];[fwd][bwd]concat=n=2:v=1:a=0 before palette filters |
| As WebP instead | ffmpeg -i input.mp4 -vf "fps=10,scale=480:-1" -c:v libwebp -quality 75 -loop 0 output.webp |
| 2x speed | Add setpts=0.5*PTS, before fps in the filter chain |
| Screen recording (low colors) | Use palettegen=max_colors=128:stats_mode=diff and dither=bayer |
Further reading
The complete FFmpeg API guide covers authentication, error handling, and all available endpoints
FFmpeg API with Node.js has full code examples for video processing from JavaScript
FFmpeg API with Python if Python is your stack
FFmpeg API curl examples for 20 copy-paste commands including GIF conversion
FFmpeg cheat sheet has 50 commands covering every common video task
Running FFmpeg in the cloud without a server compares Lambda, Cloud Run, and API services
Hosted vs self-hosted FFmpeg breaks down the cost math at different volumes
Strip video metadata with FFmpeg is worth reading if you're processing user-uploaded content