ffmpeggifvideo-conversion

Convert MP4 to GIF with FFmpeg: The Palette Method That Doesn't Look Muddy

·Javid Jamae·10 min read
Convert MP4 to GIF with FFmpeg: The Palette Method That Doesn't Look Muddy

You ran ffmpeg -i input.mp4 output.gif, waited, and got a 47 MB file that looks like it was colored in with eight crayons. The obvious conclusion is that GIF is just a bad format. That's half right, but the muddy output isn't GIF's fault, it's the palette FFmpeg picked for you.

Quick answer: To convert MP4 to GIF with usable quality, use FFmpeg's two-pass palette method: build a custom 256-color palette from the clip with palettegen, then encode against it with paletteuse. One command does both: ffmpeg -i input.mp4 -vf "fps=15,scale=480:-1:flags=lanczos,split[a][b];[a]palettegen[p];[b][p]paletteuse" -loop 0 output.gif. Frame rate and width control the file size more than any other setting.

Why the one-line MP4 to GIF command looks bad

GIF stores at most 256 colors per frame. When you don't give FFmpeg a palette, its GIF encoder falls back to a generic built-in palette that was never derived from your footage, so skin tones band, gradients posterize, and a dark UI recording turns into gray mush.

The naive command:

ffmpeg -i input.mp4 -vf "fps=15,scale=480:-1:flags=lanczos" -loop 0 output.gif

This runs. It's fine for a throwaway. It's also why people conclude GIFs have to look like 2009.

FFmpeg shipped the fix in 2015 with the palettegen and paletteuse filters, written by Clément Bœsch. palettegen scans the frames you're actually encoding and computes the best 256 colors for that specific clip. paletteuse then maps every frame onto it, with dithering to hide the banding.

The two-pass palette method

Two passes, one palette file. This is the version to memorize:

# Pass 1: build a palette from the exact frames you'll encode
ffmpeg -i input.mp4 -vf "fps=15,scale=480:-1:flags=lanczos,palettegen=stats_mode=diff" -y palette.png

# Pass 2: encode against it
ffmpeg -i input.mp4 -i palette.png -lavfi "fps=15,scale=480:-1:flags=lanczos,paletteuse=dither=bayer:bayer_scale=5:diff_mode=rectangle" -loop 0 -y output.gif

The filters must match in both passes. If you generate a palette at 480px and encode at 720px, you get colors chosen for the wrong image.

What the options do:

  • stats_mode=diff weights the palette toward the parts of the frame that move, which is right for screen recordings and talking-head clips where the background never changes.
  • dither=bayer with bayer_scale=5 produces a regular, low-noise dither pattern that compresses well. dither=floyd_steinberg looks better on photographic footage but adds noise that inflates the file, often by 20 to 40 percent.
  • diff_mode=rectangle tells the encoder to only rewrite the changed rectangle of each frame. On static-background clips this is the single biggest free size win.

If you don't want a temp file, split does it in one command:

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

-loop 0 means loop forever, which is the default for the GIF muxer. -loop -1 plays the animation exactly once. Those numbers surprise people every time.

Frame rate and width decide your file size

GIF has no interframe motion compensation the way H.264 does. Every frame is close to a full image, so file size scales almost linearly with frame count and with pixel area. Halving the width cuts roughly three quarters of the bytes.

Rough landing zones for a 10-second clip, two-pass palette, mixed motion:

SettingsTypical sizeGood for
1080p, 30 fps40 MB and upNothing. Don't.
720px wide, 20 fps8 to 15 MBNothing that has an upload limit
480px wide, 15 fps3 to 6 MBREADME demos, Slack, docs
320px wide, 12 fps1 to 2 MBEmail, chat, tight embeds

Motion-heavy footage can double those. A static terminal recording can land at a fifth of them. The point isn't the exact number, it's the order of the levers: length first, then width, then fps, then colors. Tuning dither before you've cut the clip to five seconds is wasted effort.

Trim before you convert, and seek before -i so FFmpeg jumps to the keyframe instead of decoding from zero:

ffmpeg -ss 00:00:03 -t 5 -i input.mp4 -vf "fps=15,scale=480:-1:flags=lanczos,split[a][b];[a]palettegen[p];[b][p]paletteuse" -loop 0 clip.gif

Still too big? Run it through gifsicle, which does LZW-level optimization FFmpeg doesn't:

gifsicle -O3 --lossy=80 --colors 128 output.gif -o smaller.gif

--lossy=80 routinely takes another 30 to 50 percent off with damage you won't notice at 480px. Push past 120 and you'll see it.

The one-click version: the Video to Animated GIF Converter blueprint

If what you want is the GIF and not the filtergraph, the Video to Animated GIF Converter blueprint runs this exact conversion as a hosted job. You upload an MP4, MOV, or WebM up to 200 MB, pick two settings, and download a palette-optimized GIF.

The two settings are the two levers from the section above:

  • Length: the first 5, 10, or 15 seconds. Fifteen is the practical ceiling for a GIF anyone will actually load.
  • Width: 480 px for chat and doc friendly file sizes, or 640 px when you need it sharper and can afford the bytes.

There's also an optional caption burned into the frames, positioned top, center, or bottom, which is the meme-text case that otherwise means learning drawtext. Emoji aren't supported in captions yet.

The palette handling isn't exposed as a knob, because there's no version of this where you want the generic palette. The blueprint runs the palettegen/paletteuse graph on every job, so you don't get the banding from the naive command at the top of this post. It runs on FFmpeg Micro compute minutes, which the free plan includes, and the same blueprint can be triggered from the dashboard, the API, or the MCP server if you'd rather drive it from an agent.

Converting MP4 to GIF inside a workflow

The commands above assume a working FFmpeg on the machine that has the file, which is a fine assumption on your laptop and a bad one inside an n8n node, a Vercel function, or a Zapier step. Those environments have no FFmpeg binary, tight memory ceilings, and short execution timeouts, so "just shell out to ffmpeg" quietly stops being an option the moment the job moves off your desk.

FFmpeg Micro exposes the same operation as a job on a hosted API: send the source video and the GIF options, get a job ID, poll it or take a webhook, download the output. No binary, no /tmp cleanup, no worker that dies at 60 seconds. The full request shape and parameter names are in the docs, and you can fire one without writing code in the playground.

FFmpeg on your boxOne API call
SetupInstall, keep versions in syncNone
Palette handlingYou write the filtergraphPalette-optimized by default
Runs inside n8n / Make / ZapierNoYes, HTTP node
20 clips at onceYou manage the queueSubmit 20 jobs
CostYour CPU and your timeFree tier, then usage-based

Common pitfalls

Mismatched filters between passes. The most frequent bug. If fps or scale differ between palettegen and paletteuse, colors get chosen for frames you never encode.

Forgetting flags=lanczos. FFmpeg's default scaler is bicubic. On text and UI elements, lanczos is visibly sharper, and sharper edges dither better.

Odd or fractional widths. scale=480:-1 computes the height automatically and GIF doesn't care about even dimensions, so -1 is safe here. Don't copy the -2 habit over from H.264 encoding.

Uploading a GIF to a platform that transcodes it anyway. X, Slack, and Discord convert uploaded GIFs to video for playback. Your 30 MB GIF becomes a 900 KB MP4 on their side. You paid the upload cost for nothing.

Encoding audio. GIF has no audio track. If your clip needs sound, you don't want a GIF, and no flag will change that.

When a GIF is the wrong answer

Be honest about the destination. If the target supports video, a 10-second H.264 MP4 at 480p is usually 300 to 800 KB, an order of magnitude smaller than the same clip as a GIF, with real color depth. Animated WebP splits the difference: it supports full color and alpha, is broadly supported in current browsers, and typically lands 40 to 60 percent under an equivalent GIF.

Use GIF when the destination genuinely requires it: GitHub README animations, email clients, older chat tools, and anywhere an inline autoplaying loop with no player chrome is the point. GitHub caps image uploads in issues and pull requests at 10 MB, which is exactly the constraint that makes the fps-and-width math above worth doing.

For one-off conversions, our free MP4 to GIF tool runs this exact palette pipeline in the browser with no signup, and ezgif.com and CloudConvert work too. Reach for FFmpeg or a hosted job when it's the hundredth clip, not the first. If you're sizing media for other destinations, compressing video for the web and getting under the Gmail 25 MB limit cover the same math for MP4.

FAQ

How do I convert MP4 to GIF without losing quality?

Use the two-pass palette method. palettegen builds a 256-color palette from your actual frames and paletteuse maps the video onto it, which removes the banding you get from FFmpeg's default built-in palette. You still lose color depth, because GIF caps at 256 colors per frame, but the result looks close to the source at 480px.

Why is my GIF bigger than the original MP4?

Because GIF has no interframe compression comparable to H.264. Each frame is stored close to whole, so a 2 MB MP4 can easily become a 25 MB GIF at the same resolution and frame rate. Cut the length, drop the width to 480px, and drop to 12 to 15 fps.

Is there a no-code way to convert MP4 to GIF?

Yes, two of them, and they are for different jobs. For a single clip, the free MP4 to GIF tool runs the palette method in your browser with no signup and nothing to install. When the same conversion has to run again, or you need a 200 MB upload, a 15-second length, or burned-in caption text, the Video to Animated GIF Converter blueprint takes an uploaded MP4, MOV, or WebM up to 200 MB, a length of 5, 10, or 15 seconds, and a width of 480 or 640 px, then returns a palette-optimized GIF with no command line involved. It runs on free-plan compute minutes.

What frame rate should I use for a GIF?

12 to 15 fps for most content. Screen recordings and slow motion read fine at 10 fps. Going above 20 fps roughly doubles the file for animation smoothness most viewers won't notice in a looping clip.

Can I convert MP4 to GIF inside n8n or Make?

Yes, with an HTTP request node calling a video API. Neither n8n Cloud nor Make ships an FFmpeg binary, so the pattern is: submit the job, then either wait on a webhook or poll the job status before the next step. If you've hit execution timeouts doing this, the n8n timeout recipe covers the polling setup.

Keep the two-pass command for the clips you convert by hand, and when the same conversion has to run for someone else or inside a workflow, sign up free and let the blueprint run it.

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.

Software EngineeringVideo ProcessingFFmpegCloud ArchitectureAPI DesignAutomation

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)