Skip to content

Instantly share code, notes, and snippets.

@tgroshon
Created January 26, 2026 15:42
Show Gist options
  • Select an option

  • Save tgroshon/491d7292673c369e7909cf8b2003a299 to your computer and use it in GitHub Desktop.

Select an option

Save tgroshon/491d7292673c369e7909cf8b2003a299 to your computer and use it in GitHub Desktop.
MOV to GIF on macOS
#!/bin/zsh
# mov2gif - Convert MOV video to optimized GIF
# Usage: mov2gif input.mov [output.gif] [options]
set -e
# Defaults
fps=10
width=320
start="00:00:00"
duration=""
usage() {
cat <<EOF
Usage: $(basename $0) input.mov [output.gif] [options]
Options:
-f, --fps NUM Frame rate (default: 10)
-w, --width NUM Width in pixels, height auto-scaled (default: 320)
-s, --start TIME Start time, e.g., 00:00:05 (default: 00:00:00)
-d, --duration TIME Duration, e.g., 00:00:10 (default: full length)
--setup Install dependencies (ffmpeg, imagemagick) via Homebrew
-h, --help Show this help
EOF
exit 1
}
# Parse arguments
input=""
output=""
while [[ $# -gt 0 ]]; do
case $1 in
-f|--fps) fps="$2"; shift 2 ;;
-w|--width) width="$2"; shift 2 ;;
-s|--start) start="$2"; shift 2 ;;
-d|--duration) duration="$2"; shift 2 ;;
--setup)
echo "Installing dependencies..."
brew install ffmpeg imagemagick
echo "Setup complete!"
exit 0 ;;
-h|--help) usage ;;
-*) echo "Unknown option: $1"; usage ;;
*)
if [[ -z "$input" ]]; then
input="$1"
elif [[ -z "$output" ]]; then
output="$1"
fi
shift ;;
esac
done
[[ -z "$input" ]] && usage
[[ ! -f "$input" ]] && echo "Error: File not found: $input" && exit 1
# After parsing, before conversion
if ! command -v ffmpeg &>/dev/null || ! command -v magick &>/dev/null; then
echo "Error: Missing dependencies. Run '$0 --setup' to install."
exit 1
fi
# Default output name
[[ -z "$output" ]] && output="${input:r}.gif"
# Build duration flag
duration_flag=""
[[ -n "$duration" ]] && duration_flag="-t $duration"
# Temp file for unoptimized GIF
tmp_gif=$(mktemp /tmp/gif_convert.XXXXXX.gif)
trap "rm -f $tmp_gif" EXIT
echo "Converting $input -> $output"
echo " FPS: $fps, Width: $width, Start: $start${duration:+, Duration: $duration}"
# Convert with ffmpeg (using palettegen for better colors)
palette=$(mktemp /tmp/palette.XXXXXX.png)
trap "rm -f $tmp_gif $palette" EXIT
ffmpeg -hide_banner -loglevel warning \
-ss "$start" -i "$input" $duration_flag \
-vf "fps=$fps,scale=$width:-1:flags=lanczos,palettegen=stats_mode=diff" \
-y "$palette"
ffmpeg -hide_banner -loglevel warning \
-ss "$start" -i "$input" $duration_flag \
-i "$palette" \
-lavfi "fps=$fps,scale=$width:-1:flags=lanczos [x]; [x][1:v] paletteuse=dither=bayer:bayer_scale=5" \
-y "$tmp_gif"
# Optimize with ImageMagick
echo "Optimizing..."
magick "$tmp_gif" -layers Optimize "$output"
# Report sizes
orig_size=$(stat -f%z "$tmp_gif" 2>/dev/null || stat -c%s "$tmp_gif")
final_size=$(stat -f%z "$output" 2>/dev/null || stat -c%s "$output")
savings=$(( (orig_size - final_size) * 100 / orig_size ))
echo "Done! $output"
echo " Size: $(( final_size / 1024 ))KB (saved ${savings}% from optimization)"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment