#!/usr/bin/env zsh
# export a clip from a video as a gif

local callstr=$0
local hasgsic

[[ $(whence gifsicle) ]] && hasgsic=true

print_error() {
	echo -e "\e[1;31merror:\e[0m $1\n"
}

usage() {
	[[ -z $1 ]] || print_error $1
	echo "Usage: $callstr [OPTIONS...] <infile> <outfile>"
	echo "Create an animated gif from a video"
	echo ""
	echo -e "                  \e[1mdescription  opt longform  arg     default val\e[0m"
	echo "                   start time  -s  --start   <time>  00:00:00"
	echo "            length in seconds  -l  --length  <num>   full length"
	echo "                      gif fps  -f  --fps     <num>   10"
	echo "           output pixel width  -w  --width   <num>   480"
	echo "                use subtitles  -b  --sub     [int]   track 0, if enabled"
	echo "   (optionally specify track)"
	echo "            number of colours  -c  --colours <int>   256"
	echo "          dithering algorithm  -d  --dither  <str>   sierra2_4a"
	echo "redraw only changed rectangle  -r  --rect"
	[[ $hasgsic ]] && echo "       optimise with gifsicle  -o  --optimise"
	echo "              print this help  -h  --help"
	exit 1
}

# preface used to ensure unique inputs, just in case links exist. see below
local tmp_pref

rm_tmps() {
	if [[ ${tmp_pref} ]]; then
		rm -f ${tmp_pref}-palette.png
		rm -f ${tmp_pref}-in
	fi
}

abort() {
	[[ -z $1 ]] || print_error $1
	rm_tmps
	exit 1
}

trap 'abort' SIGABRT SIGHUP SIGINT SIGQUIT SIGTERM

local start="00:00:00"
local length=""
local fps=10
local width=480
local subs=""
local strack=0
local gsic=""
local dithalg="sierra2_4a"
local colour_count=256
local rect=""

local timepat='^(([0-9][0-9]:){1,2}[0-9][0-9]|[0-9]+)(\.[0-9]+){0,1}$'
local numpat='^([1-9][0-9]*(\.[0-9]+){0,1}|0\.[0-9]*[1-9])$'
local intpat='^[1-9][0-9]*$'
local zintpat='^[0-9]+$'
local dithpat='^(none|bayer[0-5]|heckbert|floyd_steinberg|sierra2|sierra2_4a)$'

zparseopts -D -E -M -A args \
	s:  -start:=s \
	l:  -length:=l \
	f:  -fps:=f \
	w:  -width:=w \
	b:: -sub::=b \
	c:  -colours:=c -colors:=c \
	d:  -dither:=d \
	r   -rect=r \
	o   -optimise=o -optimize=o \
	h   -help=h

local optarg
for opt in ${(@k)args}; do
	unset optarg
	[[ -z $args[$opt] ]] || optarg=$args[$opt]
	case $opt in
			-s) 
				[[ ! $(echo $optarg | grep -oE "$timepat") ]] \
					&& usage "malformed start timestamp"
				start="$optarg"
				;;
			-l)
				[[ ! $(echo $optarg | grep -oE "$numpat") ]] \
					&& usage "length must be a positive rational number"
				length=(-t $optarg)
				;;
			-f)
				[[ ! $(echo $optarg | grep -oE "$numpat") ]] \
					&& usage "fps must be a positive rational number"
				fps=$optarg
				;;
			-w)
				[[ ! $(echo $optarg | grep -oE "$intpat") ]] \
					&& usage "width must be a positive integer"
				width=$optarg
				;;
			-b)
				[[ ! -z $optarg ]] && [[ ! $(echo $optarg | grep -oE "$zintpat") ]] \
					&& usage "sub track index must be a non-negative integer"
				[[ -z $optarg ]] && strack=0 || start=$optarg
				subs=true
				;;
			-c)
				[[ ! $(echo $optarg | grep -oE "$intpat") ]] \
					|| [[ $optarg -gt 256 || $optarg -lt 2 ]] \
					&& usage "colour count must be an integer in the range 2-256"
				colour_count=$optarg
				;;
			-d)
				[[ ! $(echo $optarg | grep -oE "$dithpat") ]] \
					&& usage "dithering algorithm must be one of none, bayer<0-5>, heckbert,\nfloyd_steinberg, sierra2, sierra2_4a"
				dithalg=$optarg
				[[ $(echo $optarg | grep -o bayer) ]] \
					&& dithalg="bayer:bayer_scale=$(echo $optarg | grep -oE '[0-5]')"
				;;
			-o) gsic=true; [[ -z $hasgsic ]] && usage "gifsicle program not found" ;;
			-r) rectmode=":diff_mode=rectangle" ;;
			-h) usage ;;
	esac
done

# check some error conditions
if [[ ${#@} -gt 2 ]]; then
	for opt in $@; do
		[[ $opt[1] == '-' ]] && usage "unrecognised option $opt"
	done
	usage "trailing file arguments detected"
fi

[[ ${#@} -eq 1 ]] && usage "no output file specified"

[[ ${#@} -eq 0 ]] && usage "no input file specified"

[[ $1 != "%d.png" ]] && [[ ! -f $1 ]] && usage "input file not found"

[[ ${2:e} != "gif" ]] && usage "output file must have a .gif file extension"

# make links
local tmp_pref_i
tmp_pref_i="${2:h}/make-gif"

while [[ -f "${tmp_pref_i}-palette.png" ]] || [[ -f "${tmp_pref_i}-in" ]]; do
	tmp_pref_i="${tmp_pref_i}-1"
done

tmp_pref=${tmp_pref_i}


ln -s "${1:a}" "${tmp_pref}-in" \
	|| abort "could not write to output dir" 

# get output height using aspect ratio
local height
local as
height=-1
ffprobe -loglevel -8 -print_format json -show_streams "${tmp_pref}-in" \
	| grep -m 1 display_aspect_ratio | grep -Eo '[0-9]+:[0-9]+' \
	| IFS=':' read -A as
[[ -z $as ]] || [[ ${as[1]} -eq 0 ]] || [[ ${as[2]} -eq 0 ]] \
	|| let "height = (${width} * ${as[2]}) / ${as[1]}"

# convenience var
local substr
[[ $subs ]] && substr="subtitles=${tmp_pref}-in:si=$strack,"

# convert
echo "pass 1..."
ffmpeg -loglevel 16 -y -ss "$start" ${length[@]} -i "${tmp_pref}-in" -copyts -filter_complex \
	"${substr}setsar=1/1,fps=$fps,scale=${width}:${height}:flags=lanczos,palettegen=max_colors=${colour_count}" \
	${tmp_pref}-palette.png

[[ $? -ne 0 ]] && fferr=true

[[ ! $fferr ]] && echo "pass 2..." && ffmpeg -loglevel 24 \
	-ss $start ${length[@]} \
	-i "${tmp_pref}-in" -i ${tmp_pref}-palette.png -copyts -filter_complex \
	"${substr}setsar=1/1,fps=$fps,scale=${width}:${height}:flags=lanczos[x];[x][1:v]paletteuse=dither=${dithalg}${rectmode}" \
	"$2"

[[ $? -ne 0 ]] && fferr=true

[[ $fferr ]] && abort

rm_tmps

# gifsicle
if [[ -f "$2" && $gsic ]]; then
	echo "optimising..."
	gifsicle --batch -O3 -i "$2"
fi