#!/bin/bash
#===============================================================================
#   giffer.sh   |   Version 1.01    |   2-Clause BSD License    |   2014-02-21
#   James Hendrie                   |   hendrie dot james at gmail dot com
#
#   Description:    Script that, using ffmpeg and ImageMagick, allows the user
#                   to make animated gifs with relative ease.
#===============================================================================

##  First, we have to make sure ffmpeg/ffprobe and ImageMagick are installed
declare -a progsWeNeed=('convert' 'ffmpeg' 'ffprobe')
for a in ${progsWeNeed[@]}; do
    which "$a" &> /dev/null
    if [ $? -ne 0 ]; then
        echo "ERROR:  $a is required to run this script.  Aborting." 1>&2
        exit 1
    fi
done


##  Now, define a few global variables

##  What quality we want the output gif to be
#   0   Same resolution, do not optimize layers
#   1   same res, optimize layers (default)
#   2   half res, optimize layers
#   3   1/4 res, optimize layers
gifQuality=1

##  Whether to optimize layers
gifOptimize=1

##  Frame rate; this is grabbed later
fps=0

##  Verbosity
verbose=0

##  Full verbosity (raw program output)
reallyVerbose=0

##  Where the gif clip starts in the video, if anywhere.  0 = disabled
gifStart=0

##  The duration of the clip, if any.  0 = disabled
gifDuration=0

##  The looping option.  0 = loop forever (default)
loop=0

##  The 'map' option for the video stream
ffmpegMap=0

##  The optstrings, to be filled as the script goes along
ffmpegOptString1=""
ffmpegOptString2=""
convertOptString=""


##  Function to print help
function print_help
{
    echo -e "Usage:  giffer [OPTIONS] INPUT_FILE[s]\n"
    echo "This script uses ffmpeg and ImageMagick to convert a video file to an"
    echo "animated gif, or part of a video file to an animated gif.  Any output"
    echo "files will share the same name as the input files, except for the"
    echo "extension which will be changed to .gif."
    
    echo -e "\nOptions:"
    echo -e "\t-h   Print this help text and exit"
    echo -e "\t-v   Verbose output"
    echo -e "\t-V   Full verbosity ('raw' program verbosity)"
    echo -e "\t-o   Optimize layers of output gif (default on)"
    echo -e "\t-O   Do not optimize layers of output gif"
    echo -e "\t-s   Time at which to start clipping video (HH:MM:SS[.xxx] form)"
    echo -e "\t-t   Duration of video clip, in seconds"
    echo -e "\t-r   User-specified framerate for both ffmpeg and ImageMagick"
    echo -e "\t-m   Simple ffmpeg stream mapping option"
    echo -e "\t-l   Number of times to loop.  0 = forever (default)"
    echo -e "\t-q   Quality:"
    echo -e "\t\t0  Same resolution as clip, do not optimize layers"
    echo -e "\t\t1  Same resolution as clip, optimize layers (default)"
    echo -e "\t\t2  1/2 resolution of clip, optimize layers"
    echo -e "\t\t3  1/4 resolution of clip, optimize layers"
}


##  Get options
while getopts "hvVoOs:t:q:r:m:l:" flag; do
    if [[ "$flag" = "h" ]]; then
        print_help
        exit 0
    elif [[ "$flag" = "v" ]]; then
        let verbose=1
        let reallyVerbose=0
    elif [[ "$flag" = "V" ]]; then
        let reallyVerbose=1
        let verbose=0
    elif [[ "$flag" = "o" ]]; then
        gifOptimize=1
    elif [[ "$flag" = "O" ]]; then
        gifOptimize=0
    elif [[ "$flag" = "s" ]]; then
        gifStart="$OPTARG"
    elif [[ "$flag" = "t" ]]; then
        gifDuration="$OPTARG"
    elif [[ "$flag" = "q" ]]; then
        gifQuality="$OPTARG"
    elif [[ "$flag" = "r" ]]; then
        fps="$OPTARG"
    elif [[ "$flag" = "m" ]]; then
        map="$OPTARG"
    elif [[ "$flag" = "l" ]]; then
        loop="$OPTARG"
    fi
done


##  ====================    FFMPEG  options     ================================
##  Function to build the ffmpeg optStrings
function set_ffmpeg_options
{
    if [[ ! "${gifStart}" = "0" ]]; then
        ffmpegOptString1="${ffmpegOptString1} -ss $gifStart"
    fi

    if [[ ! "${gifDuration}" = "0" ]]; then
        ffmpegOptString1="${ffmpegOptString1} -t $gifDuration"
    fi

    ##  If the mapping feature isn't 0, use what the users entered
    if [[ ! "${ffmpegMap}" = "0" ]]; then
        ffmpegOptString2="${ffmpegOptString2} -map $ffmpegMap"
    fi

    ##  Normal verbosity or none means ffmpeg should shut up
    if [[ ! $reallyVerbose -eq 1 ]]; then
        ffmpegOptString1="-v quiet $ffmpegOptString1"
    fi

    ##  Set FPS
    ffmpegOptString2="${ffmpegOptString2} -r $fps"
}
##  ====================    END FFMPEG  options     ============================



##  ====================    IMAGEMAGIC options  ================================
##  Function to build the ImageMagic 'convert' program optString
function set_convert_options
{
    ##  Set delay
    convertOptString="$convertOptString -delay 1x$fps"

    ##  Adjust options according to quality selected
    if [[ "${gifQuality}" = "0" ]]; then
        gifOptimize=0
    elif [[ "${gifQuality}" = "2" ]]; then
        convertOptString="${convertOptString} -scale 50%"
    elif [[ "${gifQuality}" = "3" ]]; then
        convertOptString="${convertOptString} -scale 25%"
    fi

    ##  If we want to optimize layers
    if [[ "${gifOptimize}" = "1" ]]; then
        convertOptString="${convertOptString} -layers Optimize"
    fi

    ##  If verbosity is on
    if [[ $reallyVerbose -eq 1 ]]; then
        convertOptString="-verbose $convertOptString"
    fi
}
##  ====================    END IMAGEMAGIC options  ============================


function set_fps
{
    ##  Find the frame rate, round it up to nearest whole integer
    if [[ "${fps}" = "0" ]] || [[ "${fps}" = "copy" ]]; then
        fps=$(printf "%.0f" $(echo "scale=2;$(ffprobe "${i}" 2>&1 | \
            grep fps | cut -f5 -d',' | sed "s/ //g" | sed "s/fps//g")" | bc))
    fi

}



##  Meta-function to build the optStrings
function build_opt_strings
{
    ffmpegOptString1=""
    ffmpegOptString2=""
    convertOptString=""

    set_ffmpeg_options
    set_convert_options
}


##  Function to get and set the resolution of the gif to be produced
function set_gif_res
{
    ##  Grab original res from video
    gifRes=$(ffprobe "${i}" 2>&1 | grep fps | cut -f4 -d':' | cut -f3 -d',' |\
        sed -e "s/^[ \t]*//g" | cut -f1 -d' ')

    ##  If the quality is lower, scale down a bit
    if [[ "$gifQuality" = "2" ]]; then
        gr1=$(( $(echo "$gifRes" | cut -f1 -d'x') / 2 ))
        gr2=$(( $(echo "$gifRes" | cut -f2 -d'x') / 2 ))
        gifRes="${gr1}x${gr2}"
    elif [[ "$gifQuality" = "3" ]]; then
        gr1=$(( $(echo "$gifRes" | cut -f1 -d'x') / 4 ))
        gr2=$(( $(echo "$gifRes" | cut -f2 -d'x') / 4 ))
        gifRes="${gr1}x${gr2}"
    fi
}


################################################################################
##  ==========================      MAIN    ====================================
################################################################################

##  Find the index where we start counting 'after' recognized options
theIndex=$(( $# - $OPTIND + 1 ))
let theIndex=$(( $theIndex * -1 ))

##  For each argument after the last recognized option, do... everything.
for i in "${@:$theIndex}"; do

    ##  Create temporary directory
    tempDir=$(mktemp -d)

    ##  Strip the old suffix off the end of the input filename, add .gif suffix
    newName=$(echo "${i}" | sed "s/.$(echo "${i}" | rev | cut -f1 -d'.' | \
        rev)//1")
    newName="${newName}.gif"

    ##  Get/set FPS
    set_fps

    ##  If verbosity is on, tell the user what's up
    if [[ $verbose -eq 1 ]]; then
        ##  Grab resolution
        gifRes=""
        set_gif_res

        echo -e "\nConverting '${i}' to animated gif '$newName'..."
        echo -n "( Quality $gifQuality, fps $fps, $gifRes"

        if [[ "$gifOptimize" = "0" ]]; then
            echo ", layers not optimized )"
        else
            echo ", layers optimized )"
        fi

    fi

    ##  Re-built the optStrings for each program to be run
    build_opt_strings

    ##  Run ffmpeg
    ffmpeg ${ffmpegOptString1} -i "${i}"${ffmpegOptString2} \
        ${tempDir}/out%7d.png

    ##  Run ImageMagick
    convert ${convertOptString} ${tempDir}/*.png "${newName}"

    ##  Remove the temp directory
    rm -r "$tempDir"

done

exit 0
