ffmpeg: Convert video to optimized GIF on macOS
Whenever I need to turn a video into a GIF, most search results lead me to upload-based websites. These usually have strict file size or length limits, and I prefer not to upload my raw files to a third-party server. Since I use a Mac mini with Apple Silicon, I wanted a way to do this locally.
To get started, you need Homebrew (a package manager for macOS). If you don't have it, install it via the terminal:
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
Verify the installation with brew --version. Once that is sorted, install ffmpeg, a powerful tool for handling almost any video or audio task:
brew install ffmpeg
Run ffmpeg -version to confirm it's working. On my M1 Mac mini, the installation took less than a minute.
My first attempt was to keep it simple. I found a basic one-line command and tried it on a 15-second Full HD (1920x1080) video:
ffmpeg -i input.mp4 output_single_pass.gif
The result was a "capacity bomb." The GIF ended up being 25.1 MB, even though the source was just a simple smartphone clip. While usable for very short clips, the file size becomes impossible to share for anything longer.
The issue is that GIFs only support 256 colors. Trying to preserve every color from a high-resolution video causes the file size to explode. To fix this, you must first generate a custom color palette.
Using a two-step conversion for optimization
To dramatically reduce the size, you need to extract an optimized color palette from the source video and then use that palette to render the GIF.
Step 1: Generate the palette
First, extract the best 256 colors from input.mp4 and save them as palette.png.
ffmpeg -i input.mp4 -vf "fps=10,scale=480:-1:flags=lanczos,palettegen" palette.png
Here is what the -vf (video filter) options are doing:
fps=10: Sets the frames per second to 10. Standard video is usually 24–30 fps, but 10–15 is plenty for a GIF and saves significant space.scale=480:-1: Resizes the width to 480 pixels. The-1tells ffmpeg to calculate the height automatically to maintain the aspect ratio.flags=lanczos: A high-quality scaling algorithm that minimizes quality loss during resizing.palettegen: The filter that creates the optimized 256-color palette.
On my 15-second clip, this step took about 4 seconds to produce palette.png.
Step 2: Convert using the palette
Now, combine the original video and the palette to create the final GIF.
ffmpeg -i input.mp4 -i palette.png -filter_complex "fps=10,scale=480:-1:flags=lanczos[x];[x][1:v]paletteuse" output_optimized.gif
In this command, -filter_complex is used because there are two inputs. The video stream [x] (processed with the same fps and scale) is combined with the palette stream [1:v] using the paletteuse filter. This final step took about 5 seconds.
Performance Comparison
To see the actual difference, I tested both methods using the same 15-second 1080p MP4 (original size: 8.7 MB).
| Method | File Size | Conversion Time |
|---|---|---|
| Single-pass | 25.1 MB | 3.2s |
| Two-step (Optimized) | 2.5 MB | 9.0s (3.8s palette + 5.2s GIF) |
The optimized version is over 10 times smaller than the single-pass version. While it takes about three times longer to process, that is a small price to pay for such a massive reduction in file size.
A few things to keep in mind: fps is critical; pushing it above 20 will cause your file size to skyrocket. Also, reducing the scale is the most effective way to cut size—dropping from 960px to 480px reduces total pixels by a factor of four. I once tried to turn a full hour of footage into a GIF, and my Mac mini fans started screaming; keep your clips short for the best results.
It was a good reminder that simply finding a command that "works" isn't enough. Taking the time to measure results and understand the underlying logic—like how the GIF color palette works—saves a lot of frustration in the long run.
Related posts
- uv install: How much faster is it than pip?
- ffmpeg: Converting Video to Optimized GIF
- codex CLI login not working on macOS: what fixed it
This post is the English edition of a Korean write-up: 원문 보기
Comments