diff options
author | shmibs <shmibs@gmail.com> | 2017-03-28 15:56:24 -0700 |
---|---|---|
committer | shmibs <shmibs@gmail.com> | 2017-03-28 15:56:24 -0700 |
commit | 846a81d4006e141a7667f459b25e76d769b8d3e9 (patch) | |
tree | 9eba9497ba4f7efef8bdb5bee5dc9ee4e2fa2782 | |
parent | d2b1b1a49bea88263d8a8b8f1488061642ba479a (diff) | |
download | make-gif-846a81d4006e141a7667f459b25e76d769b8d3e9.tar.gz |
use zparseopts for options
-rw-r--r-- | Readme.md | 10 | ||||
-rwxr-xr-x | make-gif | 202 |
2 files changed, 90 insertions, 122 deletions
@@ -12,26 +12,26 @@ files as painless as possible, with support for things like embedding subtitles, various dithering algorithms, cropping, and post-optimisation (via [gifsicle](https://www.lcdf.org/gifsicle/)) -just clone and run `make-gif -h` to get started! +just clone and run `make-gif --help` to get started! examples ======== some example usages and their outputs: -`make-gif -o -s 36:18 -t 3 菊次郎の夏.mkv out.gif` +`make-gif -o -s 36:18 -l 3 菊次郎の夏.mkv out.gif` ![default](examples/default.gif) -`make-gif -or -s 36:18 -t 3 -c 64 -d bayer2 菊次郎の夏.mkv out.gif` +`make-gif -or -s 36:18 -l 3 -c 64 -d bayer2 菊次郎の夏.mkv out.gif` ![c-64_d-bayer2_r](examples/c-64_d-bayer2_r.gif) -`make-gif -o -s 36:18 -t 3 -c 8 -d bayer5 -f 15 -w 320 菊次郎の夏.mkv out.gif` +`make-gif -o -s 36:18 -l 3 -c 8 -d bayer5 -f 15 -w 320 菊次郎の夏.mkv out.gif` ![c-8_d-bayer5_f-15](examples/c-8_d-bayer5_f-15_w-320.gif) -`make-gif -o -s 16:21 -t 3.5 -b 0 'Christiane F.mkv' out.gif` +`make-gif -o -b -s 16:21 -l 3.5 'Christiane F.mkv' out.gif` ![subtitles](examples/subtitles.gif) @@ -1,28 +1,32 @@ #!/usr/bin/env zsh # export a clip from a video as a gif -local callstr="$0" -local hasgsic=$(whence gifsicle) +local callstr=$0 +local hasgsic + +[[ $(whence gifsicle) ]] && hasgsic=true print_error() { echo -e "\e[1;31merror:\e[0m $1\n" } usage() { - [[ "$1" != "" ]] && print_error $1 + [[ -z $1 ]] || print_error $1 echo "Usage: $callstr [OPTIONS...] <infile> <outfile>" + echo "Create an animated gif from a video" echo "" - echo " description option default val" - echo " start time -s <time> 00:00:00" - echo " length in seconds -t <num> full length" - echo " gif fps -f <num> 10" - echo " gif pixel width -w <num> 480" - echo " use subtitle track -b <int> none" - echo " number of colours -c <int> 256" - echo " dithering algorithm -d <str> sierra2_4a" - echo "redraw only changed rect -r" - [[ $hasgsic ]] && echo " optimise with gifsicle -o" - echo " print this help -h" + 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 } @@ -37,7 +41,7 @@ rm_tmps() { } abort() { - [[ "$1" != "" ]] && print_error $1 + [[ -z $1 ]] || print_error $1 rm_tmps exit 1 } @@ -59,123 +63,85 @@ 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='^(bayer[0-5]|heckbert|floyd_steinberg|sierra2|sierra2_4a)$' - -# used to hold '-t' if length is used -local t="" - -# used to hold subtitle options, if present -local substr="" - -# really annoying, but no other good way to do optional args -if [[ $hasgsic ]]; then - while getopts :s:t:f:w:b:c:d:rho opt; do - case "$opt" in - s) - [[ ! $(echo $OPTARG | grep -oE "$timepat") ]] \ +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" + start="$optarg" ;; - t) - [[ ! $(echo $OPTARG | grep -oE "$numpat") ]] \ + -l) + [[ ! $(echo $optarg | grep -oE "$numpat") ]] \ && usage "length must be a positive rational number" - length=$OPTARG - t="-t" + length=(-t $optarg) ;; - f) - [[ ! $(echo $OPTARG | grep -oE "$numpat") ]] \ + -f) + [[ ! $(echo $optarg | grep -oE "$numpat") ]] \ && usage "fps must be a positive rational number" - fps=$OPTARG + fps=$optarg ;; - w) - [[ ! $(echo $OPTARG | grep -oE "$intpat") ]] \ + -w) + [[ ! $(echo $optarg | grep -oE "$intpat") ]] \ && usage "width must be a positive integer" - width=$OPTARG + width=$optarg ;; - b) - [[ ! $(echo $OPTARG | grep -oE "$zintpat") ]] \ + -b) + [[ ! -z $optarg ]] && [[ ! $(echo $optarg | grep -oE "$zintpat") ]] \ && usage "sub track index must be a non-negative integer" - strack=$OPTARG + [[ -z $optarg ]] && strack=0 || start=$optarg subs=true ;; - c) - [[ ! $(echo $OPTARG | grep -oE "$intpat") ]] \ - || [[ $OPTARG -gt 256 || $OPTARG -lt 2 ]] \ + -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 + colour_count=$optarg ;; - d) - [[ ! $(echo $OPTARG | grep -oE "$dithpat") ]] \ - && usage "dithering algorithm must be one of bayer<0-5>, heckbert, floyd_steinberg, sierra2, sierra2_4a" - dithalg=$OPTARG - [[ $(echo $OPTARG | grep -o bayer) ]] \ - && dithalg="bayer:bayer_scale=$(echo $OPTARG | grep -oE '[0-5]')" - ;; - o) gsic=true ;; - r) rectmode=":diff_mode=rectangle" ;; - h) usage ;; - [?]) usage "unrecognised option" ;; - esac - done -else - while getopts :s:t:f:w:b:c:d:rh opt; do - case "$opt" in - s) - [[ ! $(echo $OPTARG | grep -oE "$timepat") ]] \ - && usage "malformed start timestamp" - start="$OPTARG" - ;; - t) - [[ ! $(echo $OPTARG | grep -oE "$numpat") ]] \ - && usage "length must be a positive rational number" - length=$OPTARG - t="-t" - ;; - 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 + -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]')" ;; - b) - [[ ! $(echo $OPTARG | grep -oE "$zintpat") ]] \ - && usage "sub track index must be a non-negative integer" - strack=$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 bayer<0-5>, heckbert, floyd_steinberg, sierra2, sierra2_4a" - dithalg=$OPTARG - [[ $(echo $OPTARG | grep -o bayer) ]] \ - && dithalg="bayer:bayer_scale=$(echo $OPTARG | grep -oE '[0-5]')" - ;; - r) rectmode=":diff_mode=rectangle" ;; - h) usage ;; - [?]) usage "unrecognised option" ;; - esac + -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 -shift $OPTIND-1 -# check some error conditions -[[ ${#@} -gt 2 ]] && usage "trailing arguments detected" +[[ ${#@} -eq 1 ]] && usage "no output file specified" -[[ ${#@} -lt 2 ]] && usage "no output file specified" +[[ ${#@} -eq 0 ]] && usage "no input file specified" -[[ "${2:e}" != "gif" ]] && usage "output file must have a .gif file extension" +[[ $1 != "%d.png" ]] && [[ ! -f $1 ]] && usage "input file not found" -[[ "$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 @@ -186,7 +152,7 @@ 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}" +tmp_pref=${tmp_pref_i} ln -s "${1:a}" "${tmp_pref}-in" \ @@ -195,25 +161,26 @@ ln -s "${1:a}" "${tmp_pref}-in" \ # 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 -[[ "$as" != "" ]] && let "height = ${width} * ${as[2]} / ${as[1]}" +[[ -z $as ]] || 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" $t $length -i "${tmp_pref}-in" -copyts -filter_complex \ +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 $t $length \ + -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" @@ -221,6 +188,7 @@ ffmpeg -loglevel 16 -y -ss "$start" $t $length -i "${tmp_pref}-in" -copyts -filt [[ $? -ne 0 ]] && fferr=true [[ $fferr ]] && abort + rm_tmps # gifsicle |