diff --git a/lib/decode/file b/lib/decode/file index aa3ce75..2666e11 100644 --- a/lib/decode/file +++ b/lib/decode/file @@ -1,12 +1,34 @@ #!/bin/bash + +# Copyright © 2012-2026 ScriptFanix +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# A copy of the GNU General Public License v3 is includded in the LICENSE file +# at the root of the project. + +# soxtaskid persists across destinations so encode tasks share a single sox task declare soxtaskid decodeFile() { + # copy destinations bypass decoding entirely if [[ ${destinationformat["$destination"]} == copy ]] then copied=1 else + # Dispatch to the appropriate decoder based on the source + # MIME type. + # sox_needed is set when normalization, resampling, or channel + # up/downmixing is required. Used to determine whether to + # insert an intermediate sox processing task. case "$mimetype" in 'video/'*) + # Extract audio if ffmpeg is available (( disablevideo )) && continue extractAudio if (( ${destinationnormalize["$destination"]}))\ @@ -22,6 +44,8 @@ decodeFile() { fi ;; 'audio/mpeg') + # Copy MP3 as-is if format and quality match + # otherwise decode with sox if [[ ${destinationformat[$destination]} = mp3 ]] \ && checkCopy then @@ -30,7 +54,9 @@ decodeFile() { decodeSox fi ;; - 'application/ogg opus') + 'application/ogg opus'|'audio/ogg opus') + # Copy Opus as-is if format and quality match + # otherwise decode with opusdec if [[ ${destinationformat[$destination]} = opus ]] \ && checkCopy then @@ -51,28 +77,9 @@ decodeFile() { fi fi ;; - 'audio/ogg opus') - if [[ ${destinationformat[$destination]} = opus ]] \ - && checkCopy - then - copied=1 - else - (( disableopusdec )) && continue - decodeOpusdec - if (( ${destinationnormalize["$destination"]}))\ - || ( - [ -n "${destinationfrequency["$destination"]}" ]\ - && (( ${rate:-0} != ${destinationfrequency["$destination"]}))\ - ) || ( - [ -n "${destinationchannels["$destination"]}" ]\ - && (( ${channels:-0} != ${destinationchannels["$destination"]} )) - ) - then - sox_needed=1 - fi - fi - ;; - 'application/ogg'*) + 'application/ogg'*|'audio/ogg'*) + # Ogg Vorbis: copy if format/quality match + # otherwise decode with sox if [[ ${destinationformat[$destination]} = vorbis ]] \ && checkCopy then @@ -81,22 +88,14 @@ decodeFile() { decodeSox fi ;; - 'audio/ogg'*) - if [[ ${destinationformat[$destination]} = vorbis ]] \ - && checkCopy - then - copied=1 - else - decodeSox - fi - ;; - 'audio/x-flac') - decodeSox - ;; - 'audio/flac') + 'audio/x-flac'|'audio/flac') + # FLAC: always decode via sox + # copy for FLAC makes little sense decodeSox ;; *) + # Unknown MIME type: probe with file to detect + # Musepack extendedtype=$(file -b "$sourcepath/$filename") case "$extendedtype" in *'Musepack '*) @@ -115,6 +114,9 @@ decodeFile() { fi ;; *) + # Truly unknown format: try + # ffmpeg if available, + # otherwise fall back to sox if (( disablevideo )) then decodeSox @@ -138,6 +140,10 @@ decodeFile() { esac if ! (( copied )) then + # Insert a decode task if one doesn't already exist for + # this source file + # keyed by $tmpfile so multiple destinations share a + # single decode task if ! decodetaskid=$( Select tasks id <<<"key = $tmpfile" ) @@ -160,11 +166,18 @@ decodeFile() { fi if (( sox_needed )) then + # Insert a sox post-processing task chained + # after the decode task decodeSox "$tempdir/$tmpfile.wav" if ! soxtaskid=$( Select tasks id <<<"key = $tmpfile" ) then + # Increment the decode task's + # required_by counter so cleaner() + # waits for all dependent tasks to + # finish before cleaning up the + # intermediate file parent_required=$( Select tasks required_by \ <<<"id = $decodetaskid" diff --git a/lib/decode/mpcdec b/lib/decode/mpcdec index c0621ff..fe9edaf 100644 --- a/lib/decode/mpcdec +++ b/lib/decode/mpcdec @@ -1,6 +1,26 @@ #!/bin/bash + +# Copyright © 2012-2026 ScriptFanix +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# A copy of the GNU General Public License v3 is includded in the LICENSE file +# at the root of the project. + decodeMpcdec() { + # Set the tmpfile base name for this specific decode task + # source file id + decoder name tmpfile="${fileid}mpcdec" + # Build mpcdec command: decode Musepack to WAV in tempdir + # ${ionice} prepends the ionice invocation string if configured commandline=(${ionice}mpcdec) + # Encode any literal newlines in the filename as the SQL-safe inline + # marker commandline+=("$sourcepath/${filename//$'\n'/::AtOM:NewLine:SQL:Inline::}" "$tempdir/$tmpfile.wav") } diff --git a/lib/decode/opusdec b/lib/decode/opusdec index b2cac97..39d4c67 100644 --- a/lib/decode/opusdec +++ b/lib/decode/opusdec @@ -1,6 +1,26 @@ #!/bin/bash + +# Copyright © 2012-2026 ScriptFanix +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# A copy of the GNU General Public License v3 is includded in the LICENSE file +# at the root of the project. + decodeOpusdec() { + # Set the tmpfile base name for this specific decode task + # source file id + decoder name tmpfile="${fileid}opusdec" + # Build opusdec command: decode Opus to WAV in tempdir + # ${ionice} prepends the ionice invocation string if configured commandline=(${ionice}opusdec) + # Encode any literal newlines in the filename as the SQL-safe inline + # marker commandline+=("$sourcepath/${filename//$'\n'/::AtOM:NewLine:SQL:Inline::}" "$tempdir/$tmpfile.wav") } diff --git a/lib/decode/sox b/lib/decode/sox index e12bf4c..1bbfbe4 100644 --- a/lib/decode/sox +++ b/lib/decode/sox @@ -1,36 +1,67 @@ #!/bin/bash + +# Copyright © 2012-2026 ScriptFanix +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# A copy of the GNU General Public License v3 is includded in the LICENSE file +# at the root of the project. + decodeSox() { + # Build a SoX decode command with optional processing (normalize, + # resample, up/downmix) commandline=(${ionice}sox --single-threaded --temp "$tempdir") soxoptions_in='' soxoptions_out='' + + # Add --norm if output normalization is requested if (( ${destinationnormalize["$destination"]} )) then commandline+=(--norm) soxoptions_in+=' --norm' fi + + # $1 can be set to pass an already decoded file. + # Use the the original file when unset if [ -n "$1" ] then commandline+=("$1") else commandline+=("$sourcepath/${filename//$'\n'/::AtOM:NewLine:SQL:Inline::}") fi + + # Add resampling if requested if [ -n "${destinationfrequency["$destination"]}" ] \ && (( ${rate:-0} != ${destinationfrequency["$destination"]} )) then commandline+=(-r ${destinationfrequency["$destination"]}) soxoptions_out+=" -r ${destinationfrequency["$destination"]}" fi + + # Add channel up/downmixing if requested if [ -n "${destinationchannels["$destination"]}" ] \ && (( ${channels:-0} != ${destinationchannels["$destination"]} )) then commandline+=(-c ${destinationchannels["$destination"]}) soxoptions_out+=" -c ${destinationchannels["$destination"]}" fi + + # Downsample to 16-bit if source resolution exceeds 16 bits if (( ${depth:-0} > 16 )) then commandline+=(-b 16) soxoptions_out+=" -b 16" fi + + # Encode all sox options into the tmpfile name so different processing + # chains get unique WAV files + # Avoids conflicts with different samplerate/norm/channel counts tmpfile="$fileid${soxoptions_in// /}${soxoptions_out// /}" commandline+=("$tempdir/$tmpfile.wav") }