aboutsummaryrefslogtreecommitdiffstats
path: root/.config/init/funcs/audio-convert
diff options
context:
space:
mode:
Diffstat (limited to '.config/init/funcs/audio-convert')
-rwxr-xr-x.config/init/funcs/audio-convert233
1 files changed, 233 insertions, 0 deletions
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...] <infile>..."
+ echo "Convert multiple audio files to a new format"
+ echo ""
+ echo " \e[1mdescription opt longform arg default\e[0m"
+ echo " output codec -c --codec <codec> opus"
+ echo " output bitrate -b --bitrate <int>k (opus:35k, vorbis:50k, mp3:65k, flac:N/A)"
+ echo " output quality -q --quality <int> N/A"
+ echo " output directory -d --dest <dir> ."
+ echo " copy metadata -m --metadata <bool> false"
+ echo " background job count -j --jobs <int> 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