Skip to content

Instantly share code, notes, and snippets.

@SheldonWangRJT
Last active April 17, 2025 05:37
Show Gist options
  • Save SheldonWangRJT/8d3f44a35c8d1386a396b9b49b43c385 to your computer and use it in GitHub Desktop.
Save SheldonWangRJT/8d3f44a35c8d1386a396b9b49b43c385 to your computer and use it in GitHub Desktop.
Convert Movie(.mov) file to Gif(.gif) file in one command line in Mac Terminal

This notes is written by Sheldon. You can find me with #iOSBySheldon in Github, Youtube, Facebook, etc.

Need

Convert .mov/.MP4 to .gif

Reason

As a developer, I feel better to upload a short video when I create the pull request to show other viewers what I did in this PR. I tried .mov format directly got after finishing recording screen using Quicktime, however, gif offers preview in most web pages, and has smaller file size.

This is not limited to developer, anyone has this need can use this method to convert the files.

Thoughts

I tried to use some software to do the job, but my hands are tied since I don't have admin in my office comupter, but I do have brew installed. And I think using command line tool will be a good choice. So I will use HomeBrew, ffmpeg, gifsicle to do the job.

Solution

  1. download HomeBrew
  2. $brew install ffmpeg
  3. $brew install gifsicle
  4. $ffmpeg -i in.mov -pix_fmt rgb8 -r 10 output.gif && gifsicle -O3 output.gif -o output.gif

Explanation

  1. Convert the file to gif using ffmpeg
- input path argument `-i`
- pixel format argument `-pix_fmt`
- removing some frames using framerate argument `-r`
- end `ffmpeg` with new path/to/filename
  1. Optimize the same output file with third option -O3 and rewrite the generated gif file from last step
  2. Notes: using && to make sure the conversion sucess before optimizing

References

  1. https://brew.sh
  2. https://www.ffmpeg.org
  3. https://www.lcdf.org/gifsicle/
  4. full video tutorial with explanation https://www.youtube.com/watch?v=QKtRMFvvDL0
@SheldonWangRJT
Copy link
Author

Updated the format of the doc to be mark down for easier reading. thanks for the support guys.

@RoySegall
Copy link

RoySegall commented Sep 2, 2020

And with alias:

alias video_to_gif='function video_to_gif(){ ffmpeg -i $1 output.gif && gifsicle -O3 output.gif -o output.gif && say "Video is ready!"};video_to_gif'

And just type video_to_gif cat.mov

@SheldonWangRJT
Copy link
Author

And with alias:

alias video_to_gif='function video_to_gif(){ ffmpeg -i $1 output.gif && gifsicle -O3 output.gif -o output.gif && say "Video is ready!"};video_to_gif'

And just type video_to_gif cat.mov

👍

@artemartemov
Copy link

Updated the alias a bit instead of using the speakers, it displays a native macOS notification:

alias video_to_gif='function video_to_gif(){ ffmpeg -i "$1" "${1%.*}.gif" && gifsicle -O3 "${1%.*}.gif" -o "${1%.*}.gif" && osascript -e "display notification \"${1%.*}.gif successfully converted and saved\" with title \"MOV2GIF SUCCESS!\""};video_to_gif'

Also another example where you can specify the input and output in case you want to rename the file:

alias rename_video_to_gif='function rename_video_to_gif(){ ffmpeg -i $1 $2.gif && gifsicle -O3 $2.gif -o $2.gif && osascript -e "display notification \"$2.gif successfully converted and saved\" with title \"MOV2GIF SUCCESS!\""};rename_video_to_gif'

and you can use that with: rename_video_to_gif cat.mov my-new-file-name

@arikanev
Copy link

Any idea why this alias does not work for me?

alias v2g='function v2g(){ ffmpeg -i $1 -pix_fmt rgb8 -r 10 -s 640x360 $2 && gifsicle -O3 $2 -o $2};v2g'

ran by

v2g tr.mov tr.gif

@RoySegall
Copy link

RoySegall commented Jun 15, 2021

@arikanev Try this one:

alias video_to_gif='function video_to_gif(){ ffmpeg -i $1 $2 && gifsicle -O3 $2 -o $2 && say "Video is ready!"};video_to_gif'

# And now you can do this:
video_to_gif video.mov output2.gif

@bahamas10
Copy link

  1. thank you so much for this, this is super useful.
  2. You don't need to define a function inside an alias... you can simply just define the function once in bash.
function video_to_gif() {
  ffmpeg -i "$1" "$2" ... etc
}

# call it like normal
video_to_gif video.mov output.gif

@mcmoe
Copy link

mcmoe commented Jan 7, 2022

Lovely gist, very helpful. I've taken your ideas and wrapped them into a function for me to use as such:

function v2g() {
    src="" # required
    target="" # optional (defaults to source file name)
    resolution="" # optional (defaults to source video resolution)
    fps=10 # optional (defaults to 10 fps -- helps drop frames)

    while [ $# -gt 0 ]; do
        if [[ $1 == *"--"* ]]; then
                param="${1/--/}"
                declare $param="$2"
        fi
        shift
    done

    if [[ -z $src ]]; then
        echo -e "\nPlease call 'v2g --src <source video file>' to run this command\n"
        return 1
    fi

    if [[ -z $target ]]; then
        target=$src
    fi

    basename=${target%.*}
    [[ ${#basename} = 0 ]] && basename=$target
    target="$basename.gif"

    if [[ -n $fps ]]; then
        fps="-r $fps"
    fi

    if [[ -n $resolution ]]; then
        resolution="-s $resolution"
    fi

    echo "ffmpeg -i "$src" -pix_fmt rgb8 $fps $resolution "$target" && gifsicle -O3 "$target" -o "$target""
    ffmpeg -i "$src" -pix_fmt rgb8 $fps $resolution "$target" && gifsicle -O3 "$target" -o "$target"
    osascript -e "display notification \"$target successfully converted and saved\" with title \"v2g complete\""
}

# call it as such
# v2g --src orig.mp4 --target newname --resolution 800x400 --fps 30

@seanf
Copy link

seanf commented Jan 17, 2022

Thanks for the function @mcmoe! But maybe return 1 would be safer than exit 1 in a function. My whole shell exited the first time I ran v2g without arguments.

@mcmoe
Copy link

mcmoe commented Jan 18, 2022

Ah, a return would be much better indeed @seanf 👍
I've updated that in my fork: https://gist.github.com/mcmoe/c76895ee86bd5293d58aca7a75afb6b2 and in my comment above

@Vages
Copy link

Vages commented Aug 16, 2022

Here is a simple shell function I wrote encapsulating this:

,to-gif() {
    # Based on https://gist.github.com/SheldonWangRJT/8d3f44a35c8d1386a396b9b49b43c385
    output_file="$1.gif"

    ffmpeg -i $1 -pix_fmt rgb8 -r 10 $output_file && gifsicle -O3 $output_file -o $output_file
}

@mcmoe's function has way more options, and is probably better if you care more about your results than I 😅

@toridoriv
Copy link

Thanks! To be honest, I'm way more comfortable with the terminal than with GUIs, so this is greatly appreciated 😊

@BinaryShrub
Copy link

BinaryShrub commented Feb 7, 2023

Update on @Vages to mute console and downscale by 50% for propersize with retina on macOS

gif() {
    # Based on https://gist.github.com/SheldonWangRJT/8d3f44a35c8d1386a396b9b49b43c385
    output_file="$1.gif"
    ffmpeg -y -i $1 -v quiet -vf scale=iw/2:ih/2 -pix_fmt rgb8 -r 10 $output_file && gifsicle -O3 $output_file -o $output_file
}

@pdegnan
Copy link

pdegnan commented Sep 22, 2023

very handy, thanks!

@iBanJavascript
Copy link

Glad I came across this via ddg search. Thanks for sharing. Worked for me perfectly.

@tommydangerous
Copy link

Yea this is awesome, thank you.

@adamamyl
Copy link

adamamyl commented Feb 5, 2024

Update on @Vages to mute console and downscale by 50% for propersize with retina on macOS

gif() {
    # Based on https://gist.github.com/SheldonWangRJT/8d3f44a35c8d1386a396b9b49b43c385
    output_file="$1.gif"
    ffmpeg -y -i $1 -v quiet -vf scale=iw/2:ih/2 -pix_fmt rgb8 -r 10 $output_file && gifsicle -O3 $output_file -o $output_file
}

I got a bit annoyed by Screen Recording YYYY-MM-DD… and superflous extensions, so I've added some extension stripping (far from perfect, but YOLO) and some quoting

v2gif() {
    # Based on https://gist.github.com/SheldonWangRJT/8d3f44a35c8d1386a396b9b49b43c385
    output_file="${1%.*}.gif"
    ffmpeg -y -i "$1" -v quiet -vf scale=iw/2:ih/2 -pix_fmt rgb8 -r 10 "$output_file" && gifsicle -O3 "$output_file" -o "$output_file"
}

@sfarkas1988
Copy link

sfarkas1988 commented Jun 4, 2024

Great work! Thank you very much! @SheldonWangRJT

@bohyunjung
Copy link

yay

@jsabilla
Copy link

jsabilla commented Oct 9, 2024

super handy! thank you!

@ruhendrawan
Copy link

Just added this to my .zshrc file. It works great with my workflow in ObsidianMD! Thank youu

@yyahav
Copy link

yyahav commented Mar 3, 2025

Awesome solution! thank you @SheldonWangRJT !

@LuciKritZ
Copy link

Thanks a lot, it's super helpful! :D

@centminmod
Copy link

centminmod commented Apr 17, 2025

Thanks for sharing this, used this info for my own spin on conversion - noob to MacOS - my repo at https://github.com/centminmod/ffmpeg-video-to-gif but shared the current version below HTH!

A combined version. It prioritizes explicit parameters (--resolution, --fps) but adds a --half-size flag inspired by v2gif and includes the -y and -v quiet flags.

#!/bin/bash
# File: (e.g., ~/.my_scripts/vid2gif_func.sh)
# Contains the updated vid2gif_pro function using a two-pass
# palettegen/paletteuse approach for better quality and size,
# including the -update 1 fix for single palette image output.

# --- Combined video to GIF conversion function ---
# Inspired by:
# - https://gist.github.com/SheldonWangRJT/8d3f44a35c8d1386a396b9b49b43c385
# - v2gif (fixed scaling, quiet, overwrite)
# - v2g (parameter parsing, notifications, gifsicle)

vid2gif_pro() {
    # --- Defaults ---
    local src=""             # Input video file (required)
    local target=""          # Output GIF file (optional, defaults to source name .gif)
    local resolution=""      # Specific output resolution e.g., 640:480 (optional)
    local fps=10             # Frames per second (optional, default 10)
    local half_size=false    # Scale to 50% width/height (optional, overrides --resolution)
    local optimize=true      # Run gifsicle -O3 (optional, default true)
    local dither_algo="sierra2_4a" # Dithering algorithm for paletteuse (optional, could add param later)

    # --- Parameter Parsing ---
    # More robust parsing using case statement
    while [[ $# -gt 0 ]]; do
        local key="$1"
        case $key in
            --src)
            src="$2"
            shift 2 # past argument and value
            ;;
            --target)
            target="$2"
            shift 2
            ;;
            --resolution)
            resolution="$2" # e.g., 640:480 or 640x480
            shift 2
            ;;
            --fps)
            fps="$2"
            shift 2
            ;;
            --half-size)
            half_size=true
            shift 1 # past argument (it's a flag, no value)
            ;;
            --no-optimize)
            optimize=false
            shift 1
            ;;
            # Example for potentially adding dither choice later
            # --dither)
            # dither_algo="$2"
            # shift 2
            # ;;
            *)    # unknown option
            echo "Unknown option: $1"
            echo "Usage: vid2gif_pro --src <input> [--target <output>] [--resolution <WxH>] [--fps <rate>] [--half-size] [--no-optimize]"
            return 1
            ;;
        esac
    done

    # --- Input Validation ---
    if [[ -z "$src" ]]; then
        echo -e "\nError: Source file required. Use '--src <input video file>'.\n"
        echo "Usage: vid2gif_pro --src <input> [--target <output>] [--resolution <WxH>] [--fps <rate>] [--half-size] [--no-optimize]"
        return 1
    fi
    if [[ ! -f "$src" ]]; then
        echo -e "\nError: Source file not found: $src\n"
        return 1
    fi

    # --- Determine Output Filename ---
    if [[ -z "$target" ]]; then
        local basename="${src%.*}"
        [[ "$basename" == "$src" ]] && basename="${src}_converted"
        target="$basename.gif"
    fi

    # --- Prepare Filters ---
    # Build filter string for ffmpeg's -vf or -filter_complex
    local filters=""
    local scale_applied_msg=""
    if [[ "$half_size" == true ]]; then
        filters="scale=iw/2:ih/2"
        scale_applied_msg="Applying 50% scaling (--half-size)."
    elif [[ -n "$resolution" ]]; then
        resolution="${resolution//x/:}" # Ensure ':' separator
        filters="scale=$resolution"
        scale_applied_msg="Applying custom resolution: $resolution (--resolution)."
    fi
    # Add fps filter - needs comma separator if scale filter already exists
    if [[ -n "$filters" ]]; then
        filters+=",fps=${fps}"
    else
        filters="fps=${fps}"
        # Set message here if only fps is applied (no scaling)
        scale_applied_msg="Using original resolution (adjusting FPS to $fps)."
    fi
    # Print status only once
    if [[ -n "$scale_applied_msg" ]]; then
       echo "$scale_applied_msg"
    fi


    # --- Temporary Palette File ---
    # Using mktemp for safer temporary file handling
    local palette_file
    # Ensure TMPDIR is checked for macOS/Linux compatibility
    palette_file=$(mktemp "${TMPDIR:-/tmp}/palette_${BASHPID}_XXXXXX.png")
    # Ensure palette is cleaned up reliably on exit, interrupt, or termination
    trap 'rm -f "$palette_file"' EXIT INT TERM HUP

    # --- Pass 1: Generate Palette ---
    echo "Pass 1: Generating palette (using filters: $filters)..."
    local palettegen_cmd_array=(
        "ffmpeg" "-y" "-v" "warning" # Less verbose for palettegen
        "-i" "$src"
        "-vf" "${filters},palettegen=stats_mode=diff" # Apply filters THEN generate palette
        "-update" "1"                       # Ensure single image output for palette
        "$palette_file"
    )
    # Optional: uncomment to debug palettegen command
    # printf "Palette Command:" '%s ' "${palettegen_cmd_array[@]}"; echo

    if ! "${palettegen_cmd_array[@]}"; then
        echo "Error during palette generation. Check FFMPEG warnings above."
        # trap will handle cleanup
        return 1
    fi
    # Check if palette file was actually created and is not empty
    if [[ ! -s "$palette_file" ]]; then
       echo "Error: Palette file generation failed or created an empty file."
       # trap will handle cleanup
       return 1
    fi

    # --- Pass 2: Generate GIF using Palette ---
    echo "Pass 2: Generating GIF using palette (dither: $dither_algo)..."
    local gifgen_cmd_array=(
        "ffmpeg" "-y" "-v" "quiet"     # Quiet for final output
        "-i" "$src"                    # Input 0: video
        "-i" "$palette_file"           # Input 1: palette
        "-filter_complex" "[0:v]${filters}[s]; [s][1:v]paletteuse=dither=${dither_algo}" # Apply filters and use palette
        # Note: No -pix_fmt or -r needed here; handled by filters/paletteuse
        "$target"
    )
    # Optional: uncomment to debug gif generation command
    # printf "GIF Command:" '%s ' "${gifgen_cmd_array[@]}"; echo

    if ! "${gifgen_cmd_array[@]}"; then
        echo "Error during final GIF generation."
        # trap will handle cleanup
        return 1
    fi

    # Palette file is automatically removed by trap EXIT INT TERM HUP

    # --- Execute Gifsicle Optimization (if enabled) ---
    if [[ "$optimize" == true ]]; then
        if [[ ! -f "$target" ]]; then
             echo "Warning: Target file '$target' not found after ffmpeg step. Skipping optimization."
        else
            echo "Optimizing '$target' with gifsicle..."
            if ! gifsicle -O3 "$target" -o "$target"; then
                echo "Warning: gifsicle optimization failed, but GIF was created."
            fi
         fi
    else
        echo "Skipping gifsicle optimization (--no-optimize)."
    fi

    # --- Notification (macOS specific) ---
    # Check if target file exists before notifying success
    if [[ -f "$target" ]] && command -v osascript &> /dev/null; then
        osascript -e "display notification \"'$target' successfully converted and saved\" with title \"Video to GIF Complete\""
    fi

    # --- Final Success Message ---
    if [[ -f "$target" ]]; then
        echo "Successfully created '$target'"
        return 0
    else
        echo "Error: Conversion finished, but target file '$target' was not found."
        return 1
    fi
}

# --- How to Use ---
# 1. Save this code:
#    - As a separate file (e.g., ~/".my_scripts/vid2gif_func.sh").
#    - OR paste directly into your ~/.zshrc (or ~/.bash_profile for Bash).
# 2. If saved as a separate file, add this line to your ~/.zshrc (or ~/.bash_profile):
#    if [[ -f ~/".my_scripts/vid2gif_func.sh" ]]; then
#      source ~/".my_scripts/vid2gif_func.sh"
#    fi
# 3. Apply changes:
#    - Open a new terminal window.
#    - OR run `source ~/.zshrc` (or `source ~/.bash_profile`).
# 4. Ensure dependencies are installed (Homebrew recommended on macOS):
#    brew install ffmpeg gifsicle
# 5. Run the function:
#    vid2gif_pro --src <input_video> [options...]
#    Example: vid2gif_pro --src my_video.mov --half-size --fps 15
#    Example: vid2gif_pro --src screen_rec.mp4 --resolution 800:-1 # Keep aspect ratio

~/.bashrc or ~/.zshrc

if [[ -f ~/".my_scripts/vid2gif_func.sh" ]]; then
  source ~/".my_scripts/vid2gif_func.sh"
fi

Key changes and features of the combined vid2gif_pro function:

  1. Robust Parameter Parsing: Uses a case statement, which is generally better for handling flags (like --half-size, --no-optimize) and parameters with values.
  2. Clear Defaults: Defaults are set at the beginning (fps=10, optimize=true).
  3. Required Source: Explicitly checks if --src was provided and if the file exists.
  4. Flexible Output Naming: Uses --target if provided, otherwise derives from --src filename.
  5. Combined Scaling Options:
    • You can use --half-size for the 50% scaling behaviour from v2gif.
    • You can use --resolution WxH (or W:H) for specific dimensions like v2g.
    • If neither is specified, it uses the original resolution.
    • --half-size takes precedence over --resolution if both are accidentally provided.
  6. FPS Control: Uses --fps like v2g, defaulting to 10.
  7. Includes v2gif Flags: Adds -y (overwrite output) and -v quiet (less FFMPEG chatter) to the ffmpeg command by default.
  8. Optional Optimization: Includes gifsicle -O3 optimization by default but allows disabling it with --no-optimize.
  9. macOS Notifications: Keeps the osascript notification from v2g.
  10. Error Handling: Basic checks for source file existence and reports FFMPEG errors. Added a warning if gifsicle fails.
  11. Local Variables: Uses local for variables inside the function to avoid polluting the global shell environment.
  12. Clearer Output: Prints messages about the parameters being used and the steps being taken.

This combined function offers the flexibility of v2g with the useful defaults and flags found in v2gif.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment