#!/usr/bin/env zsh # export a clip from a video as a gif local callstr="$0" local hasgsic=$(whence gifsicle) usage() { [[ "$1" != "" ]] && echo -e "\e[1;31merror:\e[0m $1\n" echo "Usage: $callstr [OPTIONS...] <infile> <outfile>" echo "" echo " description option default val" echo " start time -s <time> 00:00:00" echo " length in seconds -t <int> full length" echo " gif fps -f <int> 10" echo " gif pixel width -w <int> 480" echo " use subtitles -b" echo " use subtitle track -n <int> 0" [[ $hasgsic ]] && echo "optimise with gifsicle -g" echo " print this help -h" exit 1 } local start="00:00:00" local length="" local fps=10 local width=480 local subs="" local strack=0 local gsic="" local timepat='^(([0-9][0-9]:){1,2}[0-9][0-9]|[0-9]+)$' local intpat='^[0-9]+$' # tmp var used to old '-t' if length is used local t="" # really annoying, but no other good way to do this if [[ $hasgsic ]]; then while getopts s:t:f:w:bhn:g opt; do case "$opt" in s) [[ ! $(echo $OPTARG | grep -oE "$timepat") ]] \ && usage "malformed start timestamp" start="$OPTARG" ;; t) [[ ! $(echo $OPTARG | grep -oE "$intpat") ]] \ && usage "length must be an integer" length=$OPTARG t="-t" ;; f) [[ ! $(echo $OPTARG | grep -oE "$intpat") ]] \ && usage "start time must be an integer" fps=$OPTARG ;; w) [[ ! $(echo $OPTARG | grep -oE "$intpat") ]] \ && usage "fps must be an integer" width=$OPTARG ;; b) subs=true ;; n) [[ ! $(echo $OPTARG | grep -oE "$intpat") ]] \ && usage "sub track specifier must be an integer" strack=$OPTARG ;; g) gsic=true ;; h) usage ;; [?]) usage ;; esac done else while getopts s:t:f:w:bhn: opt; do case "$opt" in s) [[ ! $(echo $OPTARG | grep -oE "$timepat") ]] \ && usage "malformed start timestamp" start="$OPTARG" ;; t) [[ ! $(echo $OPTARG | grep -oE "$intpat") ]] \ && usage "length must be an integer" length=$OPTARG t="-t" ;; f) [[ ! $(echo $OPTARG | grep -oE "$intpat") ]] \ && usage "start time must be an integer" fps=$OPTARG ;; w) [[ ! $(echo $OPTARG | grep -oE "$intpat") ]] \ && usage "fps must be an integer" width=$OPTARG ;; b) subs=true ;; n) [[ ! $(echo $OPTARG | grep -oE "$intpat") ]] \ && usage "sub track specifier must be an integer" strack=$OPTARG ;; h) usage ;; [?]) usage ;; esac done fi shift $OPTIND-1 [[ ${#@} -gt 2 ]] && usage "trailing arguments detected" [[ ${#@} -lt 2 ]] && usage "no output file specified" [[ "${2:e}" != "gif" ]] && usage "output file must have a .gif file extension" [[ ! -f "$1" ]] && usage "input file not found" local paltmp="make-gif" while [[ -f "${paltmp}-palette.png" ]] || [[ -f "${paltmp}-in" ]]; do paltmp="${paltmp}-1" done ln -s "$1" "${paltmp}-in" local fferr if [[ $subs ]]; then echo "pass 1..." ffmpeg -loglevel 16 -y -ss $start $t $length -i "$1" -copyts \ -vf "subtitles=${paltmp}-in:si=$strack,fps=$fps,scale=$width:-1:flags=lanczos,palettegen" \ ${paltmp}-palette.png [[ $? -ne 0 ]] && fferr=true [[ ! $fferr ]] && echo "pass 2..." && ffmpeg -loglevel 24 \ -ss $start $t $length -i "$1" -i ${paltmp}-palette.png \ -copyts -filter_complex \ "subtitles=${paltmp}-in:si=$strack,fps=$fps,scale=$width:-1:flags=lanczos[x];[x][1:v]paletteuse" \ "$2" else echo "pass 1..." ffmpeg -loglevel 16 -y -ss "$start" $t $length -i "$1" \ -vf "fps=$fps,scale=$width:-1:flags=lanczos,palettegen" \ ${paltmp}-palette.png [[ $? -ne 0 ]] && fferr=true [[ ! $fferr ]] && echo "pass 2..." && ffmpeg -loglevel 24 \ -ss $start $t $length -i "$1" -i ${paltmp}-palette.png -filter_complex \ "fps=$fps,scale=$width:-1:flags=lanczos[x];[x][1:v]paletteuse" \ "$2" fi rm -f ${paltmp}-palette.png rm -f ${paltmp}-in if [[ -f "$2" ]] && [[ ! $fferr ]]; then if [[ $gsic ]]; then local gsictmp="$2.out" while [[ -f "$gsictmp" ]]; do gsictmp="$gsictmp.out" done echo "optimising..." gifsicle -O3 -i "$2" -o "$gsictmp" [[ $? -eq 0 ]] && rm -f "$2" && mv "$gsictmp" "$2" fi fi