From db947a1f555eab19b87cd540c831d1fc30310025 Mon Sep 17 00:00:00 2001 From: katherine Date: Wed, 19 Dec 2018 17:35:39 -0700 Subject: add fancy new functions, with shared helpers --- .config/init/funcreqs/audio-concat | 2 + .config/init/funcreqs/audio-convert | 2 + .config/init/funcs/audio-concat | 127 +++++++++++++++++++ .config/init/funcs/audio-convert | 233 +++++++++++++++++++++++++++++++++++ .config/init/funcs/mpd-cover-convert | 8 +- .config/init/funcs/scap | 4 +- .config/init/helpers | 77 ++++++++++++ 7 files changed, 449 insertions(+), 4 deletions(-) create mode 100644 .config/init/funcreqs/audio-concat create mode 100644 .config/init/funcreqs/audio-convert create mode 100755 .config/init/funcs/audio-concat create mode 100755 .config/init/funcs/audio-convert create mode 100644 .config/init/helpers (limited to '.config/init') diff --git a/.config/init/funcreqs/audio-concat b/.config/init/funcreqs/audio-concat new file mode 100644 index 0000000..b3cc909 --- /dev/null +++ b/.config/init/funcreqs/audio-concat @@ -0,0 +1,2 @@ +func_init_prereqs=(ffmpeg) +func_init_checks=() diff --git a/.config/init/funcreqs/audio-convert b/.config/init/funcreqs/audio-convert new file mode 100644 index 0000000..b3cc909 --- /dev/null +++ b/.config/init/funcreqs/audio-convert @@ -0,0 +1,2 @@ +func_init_prereqs=(ffmpeg) +func_init_checks=() diff --git a/.config/init/funcs/audio-concat b/.config/init/funcs/audio-concat new file mode 100755 index 0000000..73b3ae7 --- /dev/null +++ b/.config/init/funcs/audio-concat @@ -0,0 +1,127 @@ +#!/usr/bin/env zsh +# concatenate multiple audio files into one + +source "$HOME/.config/init/helpers" || exit 1 + +local callstr=$0 + +usage() { + echo "Usage: $callstr [OPTIONS...] ..." + echo "Concatenate multiple audio files into one" + echo "" + echo " \e[1mdescription opt longform arg default\e[0m" + echo " specify destination file -o --outfile out.ogg" + echo " specify output bitrate -b --bitrate k (opus:35k, vorbis:50k, mp3:65k, flac:N/A)" + echo " workaround for many inputs -m --many" + echo " print this help -h --help" + exit 0 +} + +trap_abort() { + echo "" + abort "process interrupted" +} + +trap 'trap_abort' SIGABRT SIGHUP SIGINT SIGQUIT SIGTERM + +local dest='out.opus' + +local destpat='^.*\.(opus|mp3|ogg|flac)$' +local bitratepat='^[1-9][0-9]*k$' + +local filterstr +local codecstr +local bitratestr +local many + +local parseerr=$(2>&1 zparseopts -D -E -M -A args \ + o: -outfile:=o \ + b: -bitrate:=b \ + m -many=m \ + h -help=h | cut -d ' ' -f 2-) + +[[ -z $parseerr ]] || abort $parseerr + +zparseopts -D -E -M -A args \ + o: -outfile:=o \ + b: -bitrate:=b \ + m -many=m \ + h -help=h + +local optarg +for opt in ${(@k)args}; do + unset optarg + [[ -z $args[$opt] ]] || optarg=$args[$opt] + case $opt in + -o) [[ $optarg =~ $destpat ]] || \ + abort "destination file must be of type opus, mp3, or ogg" + dest=$optarg + [[ ${optarg:e} == 'opus' ]] && codecstr='libopus' \ + && [[ -z $bitratestr ]] && bitratestr='35k' + [[ ${optarg:e} == 'mp3' ]] && codecstr='libmp3lame' \ + && [[ -z $bitratestr ]] && bitratestr='65k' + [[ ${optarg:e} == 'ogg' ]] && codecstr='libvorbis' \ + && [[ -z $bitratestr ]] && bitratestr='50k' + [[ ${optarg:e} == 'flac' ]] && codecstr='flac' \ + && [[ -z $bitratestr ]] && bitratestr='' + ;; + -b) [[ $optarg =~ $bitratepat ]] || \ + abort "bitrate must positive integer + k, for kbps" + bitratestr=$optarg + ;; + -m) many=true ;; + -h) usage ;; + esac +done + +[[ -z $codecstr ]] && codecstr='libopus' +[[ -z $bitratestr ]] && bitratestr='35k' + +[[ $codecstr == "flac" ]] && bitratestr='' + +[[ ${#@} -eq 0 ]] && abort "no input files specified" + +if [[ -e $dest ]]; then + local delprompt + printf "destination file \`$dest\` exists\ndelete it? [y/N]: " + read -q delprompt + echo "" + [[ $delprompt = "y" ]] || return 0 + 2>/dev/null rm $dest || abort "could not delete file \`$dest\`" +fi + +local pid + +if [[ -z $many ]]; then + local inputlist=() + local i=0 + for f in $@; do + inputlist+=(-i $f) + filterstr="${filterstr}[${i}:a:0] " + i=$((i+1)) + done + + filterstr="${filterstr}concat=n=${i}:v=0:a=1 [outa]" + + ( + ffmpeg -loglevel -8 $inputlist \ + -filter_complex $filterstr -map '[outa]' \ + -c:a $codecstr ${bitratestr/*k/-b:a} $bitratestr \ + -map_metadata -1 $dest + ) & + pid=$! +else + ( + for f in $@; do + ffmpeg -loglevel -8 -i $f -f s16le -ar 44.1k -ac 2 pipe: + done | ffmpeg -loglevel -8 -f s16le -ar 44.1k -ac 2 -i pipe: \ + -c:a $codecstr ${bitratestr/*k/-b:a} $bitratestr \ + -map_metadata -1 $dest + ) & + pid=$! +fi + +local plural +[[ $#@ -ne 1 ]] && plural='s' + +wait-anim $pid "concatenating $#@ file$plural to \`$dest\`" diff --git a/.config/init/funcs/audio-convert b/.config/init/funcs/audio-convert new file mode 100755 index 0000000..4923baa --- /dev/null +++ b/.config/init/funcs/audio-convert @@ -0,0 +1,233 @@ +#!/usr/bin/env zsh +# concatenate multiple audio files into one + +source "$HOME/.config/init/helpers" || exit 1 + +local callstr=$0 + +usage() { + echo "Usage: $callstr [OPTIONS...] ..." + echo "Convert multiple audio files to a new format" + echo "" + echo " \e[1mdescription opt longform arg default\e[0m" + echo " output codec -c --codec opus" + echo " output bitrate -b --bitrate k (opus:35k, vorbis:50k, mp3:65k, flac:N/A)" + echo " output quality -q --quality N/A" + echo " output directory -d --dest ." + echo " copy metadata -m --metadata false" + echo " background job count -j --jobs 4" + echo " print this help -h --help" + exit 0 +} + +local fifoname +local fifomade=0 +local pid + +trap_abort() { + echo "" + print-error "process interrupted" + # race condition-y, but eh + repeat 5; do + [[ -z $pid ]] && sleep 0.1 || break + done + [[ -z $pid ]] || kill -s SIGKILL $pid + [[ -p $fifoname ]] && [[ $fifomade -eq 1 ]] && 2>/dev/null rm $fifoname + exit 1 +} + +trap 'trap_abort' SIGABRT SIGHUP SIGINT SIGQUIT SIGTERM + +local codecpat='^(opus|mp3|vorbis|flac)$' +local bitratepat='^[1-9][0-9]*k$' +local metadatapat='^(true|false)$' +local jobcountpat='^[1-9][0-9]*$' + +local qpatvorbis='^([0-9]|10)$' +local qpatmp3='^[0-9]$' + +local codecstr +local codecprintstr +local extstr +local bitratestr +local bitrateset +local metastr +local dirstr +local qstr + +local jobcount + +local parseerr=$(2>&1 zparseopts -D -E -M -A args \ + c: -codec:=c \ + b: -bitrate:=b \ + q: -quality:=q \ + d: -dest:=d -destination:=d \ + j: -jobs:=j \ + m:: -metadata::=m \ + h -help=h | cut -d ' ' -f 2-) + +[[ -z $parseerr ]] || abort $parseerr + +zparseopts -D -E -M -A args \ + c: -codec:=c \ + b: -bitrate:=b \ + q: -quality:=q \ + d: -dest:=d -destination:=d \ + j: -jobs:=j \ + m:: -metadata::=m \ + h -help=h + +local optarg +local opt +for opt in ${(@k)args}; do + unset optarg + [[ -z $args[$opt] ]] || optarg=$args[$opt] + case $opt in + -c) [[ $optarg =~ $codecpat ]] || \ + abort "codec must be one of \`opus\`, \`mp3\`, \`flac\`, or \`vorbis\`" + [[ ${optarg} == 'opus' ]] \ + && codecprintstr=${optarg} \ + && codecstr='libopus' && extstr='opus' \ + && [[ -z $bitratestr ]] && bitratestr='35k' + [[ ${optarg} == 'mp3' ]] \ + && codecprintstr=${optarg} \ + && codecstr='libmp3lame' && extstr='mp3' \ + && [[ -z $bitratestr ]] && bitratestr='65k' + [[ ${optarg} == 'vorbis' ]] \ + && codecprintstr=${optarg} \ + && codecstr='libvorbis' && extstr='ogg' \ + && [[ -z $bitratestr ]] && bitratestr='50k' + [[ ${optarg} == 'flac' ]] \ + && codecprintstr=${optarg} \ + && codecstr='flac' && extstr='flac' \ + && [[ -z $bitratestr ]] && bitratestr='' + ;; + -b) [[ $optarg =~ $bitratepat ]] || \ + abort "bitrate must positive integer + k, for kbps" + bitratestr=$optarg + bitrateset=1 + ;; + -q) qstr=$optarg ;; + -d) dirstr=$optarg ;; + -j) [[ $optarg =~ $jobcountpat ]] || \ + abort "job count must be a non-negative integer" + jobcount=$optarg + ;; + -m) if [[ $optarg ]]; then + [[ $optarg =~ $metadatapat ]] || \ + abort "metadata must be either \`true\` or \`false\`" + + [[ $optarg == "false" ]] && metastr="" \ + || metastr=("-map_metadata" "-1") + else + metastr=("-map_metadata" "-1") + fi + ;; + -h) usage ;; + esac +done + +[[ -z $codecstr ]] && codecstr='libopus' +[[ -z $bitratestr ]] && bitratestr='35k' +[[ -z $codecprintstr ]] && codecprintstr='opus' +[[ -z $extstr ]] && extstr='opus' +[[ -z $jobcount ]] && jobcount=4 +[[ -z $dirstr ]] && dirstr='.' +[[ -z $metastr ]] && metastr=("-map_metadata" "-1") + +if [[ ! -z $qstr ]]; then + [[ -z $bitratset ]] || abort "-b and -q are not compatible" + [[ $codecstr == "libopus" ]] && abort "-q cannot be used with opus" + [[ $codecstr == "libflac" ]] && abort "-q cannot be used with flac" + [[ $codecstr == "libvorbis" ]] && [[ ! $qstr =~ $qpatvorbis ]] \ + && abort "for vorbis, -q must be in the range [0-10]" + [[ $codecstr == "libmp3lame" ]] && [[ ! $qstr =~ $qpatmp3 ]] \ + && abort "for mp3, -q must be in the range [0-9]" +else +fi + +[[ $codecstr == "flac" ]] && bitratestr='' + +[[ $#@ -eq 0 ]] && abort "no input files specified" + +local f +for f in $@; do + [[ -f $f ]] || abort "could not read file \`$f\`" +done + +2>/dev/null mkdir -p $dirstr || abort "could not write to directory \`$dirstr\`" + +repeat 5; do + fifoname="$dirstr/audio-convert-tmp-$RANDOM" + [[ -e $fifoname ]] && continue + 2>/dev/null mkfifo $fifoname || return 1 + fifomade=1 + break +done + +[[ $fifomade -eq 1 ]] || abort "could not make temporary fifo" + +local delprompt +for f in $@; do + local dest="$dirstr/${f:t:r}.$extstr" + + if [[ -e $dest ]]; then + printf "destination file \`$dest\` exists\ndelete it? [y/N]: " + read -q delprompt + print "" + [[ $delprompt = "y" ]] || return 0 + 2>/dev/null rm $dest || abort "could not delete file \`$dest\`" + fi +done + +local totalstr="${#@} file" +[[ $#@ -ne 1 ]] && totalstr="${totalstr}s" +local jobstr=$jobcount +[[ $#@ -lt $jobcount ]] && jobstr=$#@ +[[ $jobstr -gt 1 ]] && jobstr="${jobstr} jobs" || jobstr="${jobstr} job" + +( +{ + local joblist=() + while read -d $'\0' f; do + local dest="$dirstr/${f:t:r}.$extstr" + ( + ffmpeg -loglevel -8 -i $f \ + -vn -sn -c:a $codecstr \ + ${bitratestr/*k/-b:a} $bitratestr \ + ${qstr/[0-9]*/-q:a} $qstr \ + $metastr \ + $dest + echo "done" + ) & + joblist+=$! + done + + local j + for j in $joblist; do + 2>/dev/null wait $j + done +} < $fifoname | { + repeat $jobcount; do + if [[ $#@ -gt 0 ]]; then + printf "%s\0" $@[1] + shift + fi + done + + while read line; do + if [[ $#@ -gt 0 ]]; then + printf "%s\0" $@[1] + shift + else + break + fi + done +} > $fifoname +)& + +pid=$! + +wait-anim $pid "using $jobstr to convert $totalstr to $codecprintstr" + +2>/dev/null rm $fifoname diff --git a/.config/init/funcs/mpd-cover-convert b/.config/init/funcs/mpd-cover-convert index e8359b7..d9d7a2c 100755 --- a/.config/init/funcs/mpd-cover-convert +++ b/.config/init/funcs/mpd-cover-convert @@ -3,10 +3,14 @@ # leaks memory for some reason, so don't run on a clean library # or it'll crash everything -local d +source "$HOME/.config/init/helpers" || exit 1 + +(local file find ~/music -type f -regextype posix-extended -regex ".*cover\.(png|jpg)"\ | while read file; do if [[ ! -f "${file:h}/cover-small.png" ]]; then convert "$file" -resize 250x "${file:h}/cover-small.png" fi -done +done) & + +wait-anim $! "converting" diff --git a/.config/init/funcs/scap b/.config/init/funcs/scap index 85a083f..f87eb93 100755 --- a/.config/init/funcs/scap +++ b/.config/init/funcs/scap @@ -7,8 +7,8 @@ xset q archey3 sleep .2 -if [[ -f '/tmp/cap.mp4' ]]; then - rm '/tmp/cap.mp4' +if [[ -e '/tmp/cap.mp4' ]]; then + rm '/tmp/cap.mp4' || return 1 fi echo 'recording...' diff --git a/.config/init/helpers b/.config/init/helpers new file mode 100644 index 0000000..69963cb --- /dev/null +++ b/.config/init/helpers @@ -0,0 +1,77 @@ +print-error() { + 1>&2 echo "\e[1;31merror:\e[0m $1" +} + +abort() { + print-error $1 + exit 1 +} + +wait-anim() { + [[ $#@ -ne 0 ]] && [[ $#@ -lt 4 ]] || return 1 + + local pidpat='^[1-9][0-9]*$' + local colourpat='^(black|light_black|red|light_red|green|light_green|yellow|light_yellow|blue|light_blue|magenta|light_magenta|cyan|light_cyan|white|light_white)$' + + [[ $1 =~ $pidpat ]] || return 1 + if [[ ! -z $3 ]]; then + [[ $3 =~ $colourpat ]] || return 1 + fi + + local message + local colour + + [[ -z $2 ]] && message='waiting...' || message=$2 + [[ -z $3 ]] && colour='yellow' || colour=$3 + + case $colour in + 'black') colour='\e[30m' ;; + 'light_black') colour='\e[1;30m' ;; + 'red') colour='\e[31m' ;; + 'light_red') colour='\e[1;31m' ;; + 'green') colour='\e[32m' ;; + 'light_green') colour='\e[1;32m' ;; + 'yellow') colour='\e[33m' ;; + 'light_yellow') colour='\e[1;33m' ;; + 'blue') colour='\e[34m' ;; + 'light_blue') colour='\e[1;34m' ;; + 'magenta') colour='\e[35m' ;; + 'light_magenta') colour='\e[1;35m' ;; + 'cyan') colour='\e[36m' ;; + 'light_cyan') colour='\e[1;36m' ;; + 'white') colour='\e[37m' ;; + 'light_white') colour='\e[1;37m' ;; + esac + + local curframe + local frames + + case $(($RANDOM % 7)) in + 0) frames=('▁' '▂' '▃' '▄' '▅' '▆' '▇' '█' '▇' '▆' '▅' '▄' '▃' '▂') ;; + 1) frames=(' ' '▖' '▚' '▝' ' ' '▘' '▞' '▗') ;; + 2) frames=('╀' '╂' '╁' '┼' '┽' '┿' '┾' '┼' '╁' '╂' '╀' '┼' '┾' '┿' '┽' '┼') ;; + 3) frames=('─' '└' '│' '┌' '─' '┐' '│' '┘') ;; + 4) frames=('.' '。' '×' ':' '*' '.' '°' '♪' '×' '♫' '°' '♡') ;; + 5) frames=(' ' ' ' '░' '░' '▒' '▒' '▓' '▓' '█' '█' '▓' '▓' '▒' '▒' '░' '░') ;; + 6) frames=('O' 'o' '.' 'o') ;; + esac + + (while 2>/dev/null kill -0 $1; do + if [[ -z $curframe ]]; then + curframe=0 + printf "\e[s" + else + printf "\e[u\e[K" + fi + printf "|\e[1;32m${frames[$(($curframe + 1))]}\e[0m| $colour$message\e[0m" + curframe=$(( ($curframe + 1) % $#frames )) + sleep .2 + done) & + + # trap "echo -n '\e[G\e[J\e[s'" SIGWINCH + + wait $1 + local r=$? + printf "\e[u\e[K" + return $r +} -- cgit v1.2.3