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 ++++++++++++ .config/ranger/rifle.conf | 5 +- README.md | 9 +- 9 files changed, 458 insertions(+), 9 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 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 +} diff --git a/.config/ranger/rifle.conf b/.config/ranger/rifle.conf index 9f1f981..7000ba6 100644 --- a/.config/ranger/rifle.conf +++ b/.config/ranger/rifle.conf @@ -47,7 +47,6 @@ # only running the current file even if you have marked multiple files. # music conversion and import -directory, has flac2all = flac2all vorbis "$1" -v quality=10 -o "$1/converted" directory, has beet = beet imp -t "$1" # view man pages @@ -80,6 +79,10 @@ ext php = php -- "$1" mime ^audio, terminal, has mpv = mpv --no-video -- "$@" mime ^audio, !terminal, has mpv, X, flag f = mpv --force-window=yes -- "$@" +mime ^audio, terminal, has audio-convert = audio-convert -d converted -c vorbis -q 10 "$@" +mime ^audio, terminal, has audio-convert = audio-convert -d converted -c opus "$@" +mime ^audio, terminal, has audio-concat = audio-concat "$@" + mime ^video, has mpv, X, flag f = mpv -- "$@" mime ^video, has mpv, terminal, !X = mpv --vo=tct -- "$@" diff --git a/README.md b/README.md index c3df76f..e12008b 100644 --- a/README.md +++ b/README.md @@ -19,12 +19,13 @@ folders "[funcs](.config/init/funcs/)" and "[funcreqs](.config/init/funcreqs/)", which, respectively, contain executable scripts and their prerequisite commands and arbitrary check commands. the latter prerequisites are tested from [.zprofile](.zprofile) at login and, if -passed, the functions are symlinked into /tmp/funcs, which is included in -$PATH. thus, this system allows for configs which automatically adapt to the +passed, the functions are symlinked into /tmp/funcs, which is appended to +`$PATH`. thus, this system allows for configs which automatically adapt to the host environment, enabling only what functionality is compatible. so far i've been the only user on systems using this config, but will probably make the dir -these are written into configurable as well if that ever happens (not safe at -all to stick the things you're running in tmp on a multi-user system!). +these are written into configurable as well if that ever changes (since +otherwise that's a pretty big security flaw, with anyone who can write to tmp +able to add commands to the shell). ## Current Utilities -- cgit v1.2.3