ffmpeg: Converting Video to Optimized GIF

When searching for ways to convert videos to GIFs, most results point toward upload-based websites. I found these frustrating because they require uploading files to external servers and often impose strict limits on file size and duration. Since I use a Mac mini with Apple Silicon, I wanted a local solution that gave me full control.

It turns out that ffmpeg—a powerful tool for handling almost any video and audio task—is all you need. If you have Homebrew (the macOS package manager) installed, getting started is easy. If not, you can install it via the terminal:

/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"

Once installed, verify it with brew --version, then install ffmpeg:

brew install ffmpeg

I verified the installation with ffmpeg -version. On my M1 Mac mini, the entire process took less than a minute.

Comparing Conversion Approaches

My first instinct was to use the simplest command possible. I took a 15-second Full HD (1920x1080) smartphone clip and ran a single-pass conversion.

ffmpeg -i input.mp4 output_single_pass.gif

The result was a "capacity bomb." The GIF ended up being 25MB, which is far too large for easy sharing. This happens because GIFs are limited to 256 colors; trying to preserve every color from a high-resolution video bloats the file size.

To fix this, I tried a two-step approach: first extracting an optimized color palette from the video, then using that palette to generate the GIF.

Step 1: Generate the Palette

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

Here, fps=10 reduces the frame rate for efficiency, scale=480:-1 sets the width to 480px (adjusting the height automatically), and flags=lanczos ensures high-quality resizing. The palettegen filter then creates the optimal 256-color map in palette.png.

Step 2: Convert using the Palette

ffmpeg -i input.mp4 -i palette.png -filter_complex "fps=10,scale=480:-1:flags=lanczos[x];[x][1:v]paletteuse" output_optimized.gif

This command takes both the original video and the palette, applies the same scaling/FPS filters (labeled as [x]), and uses the paletteuse filter to map those colors onto the final GIF.

Here is how the two methods compared using the same 15-second, 8.7MB source video:

ApproachResult (File Size)Conversion TimeNotes
Single-pass25.1 MB3.2sExtremely large; impractical for sharing
Two-pass (Optimized)2.5 MB9.0sPalette gen (3.8s) + GIF conversion (5.2s)

I chose the two-step approach. While it took roughly three times longer to process, reducing the file size by over 10x is a massive win. When sharing clips on the web, file size is critical, and this difference only grows as video length increases.

Tips for further optimization

If you want to reduce the file size even further, keep these three things in mind:

1. FPS is key: Setting fps to 20 or higher usually causes the file size to explode. Staying between fps=10 and fps=15 is generally the sweet spot for smoothness and efficiency.

2. Scale matters: A width of 480px is usually sufficient for mobile screens. Since reducing resolution decreases pixels quadratically, dropping from 960px to 480px effectively reduces the resolution to 1/4, drastically cutting the size. I learned this the hard way when my Mac mini fans started screaming because I tried to convert a full-hour video.

3. Keep it short: No matter how much you optimize, a long GIF will always be large. The format is designed for short, looping clips. It is always better to trim the video to only the essential scenes before converting.

This process taught me that simply finding a command that "works" isn't enough. By measuring the output and comparing the numbers, I found a solution that was genuinely usable for the web.

ffmpeg: Converting Video to Optimized GIF

Related posts

This post is the English edition of a Korean write-up: 원문 보기

Comments

Popular posts from this blog

Codex CLI 401 Unauthorized and Installation Fixes

uv install: How much faster is it than pip?

Discord bot not responding but still running: how to catch it