diff --git a/lib/encode/mp3 b/lib/encode/mp3 index d9fdc36..9b4148c 100644 --- a/lib/encode/mp3 +++ b/lib/encode/mp3 @@ -1,13 +1,30 @@ #!/usr/bin/env 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. + encodeFile::mp3() { + # Build lame ABR encode command with all available metadata lameopts=(${ionice}lame --quiet --noreplaygain) lameopts+=(-v --abr ${destinationquality[$destination]}) + # Embed ID3 tags for each available metadata field [ -n "$album" ] && lameopts+=(--tl "$album" ) [ -n "$artist" ] && lameopts+=(--ta "$artist") [ -n "$genre" ] && lameopts+=(--tg "$genre") [ -n "$title" ] && lameopts+=(--tt "$title") [ -n "$track" ] && lameopts+=(--tn "$track") [ -n "$year" ] && lameopts+=(--ty "$year") + # Extended tags using ID3v2 frames (TXXX for custom/non-standard fields) [ -n "$albumartist" ] && lameopts+=(--tv TPE2="$albumartist") [ -n "$composer" ] && lameopts+=(--tv TCOM="$composer") [ -n "$performer" ] && lameopts+=(--tv TOPE="$performer") @@ -18,6 +35,9 @@ encodeFile::mp3() { [ -n "$replaygain_trk" ] \ && lameopts+=(--tv "TXXX=REPLAYGAIN_TRACK_GAIN=$replaygain_trk") [ -n "$disc" ] && lameopts+=(--tv TPOS="$disc") + + # Handle noresample: force lame to use a specific sample rate to prevent + # lame's own automatic downsampling at low bitrates if (( ${destinationnoresample[$destination]:-0} == 1 )) then # If 'rate' is not one of these value, it cannot be encoded to @@ -25,6 +45,7 @@ encodeFile::mp3() { # rate to use. if [ -n "${destinationfrequency["$destination"]}" ] then + # Target frequency was explicitly set; use that case ${destinationfrequency["$destination"]} in 48000) lameopts+=(--resample 48) ;; 44100) lameopts+=(--resample 44.1) ;; @@ -38,8 +59,10 @@ encodeFile::mp3() { esac elif (( rate > 48000 )) then + # Source rate exceeds MP3 maximum; cap at 48kHz lameopts+=(--resample 48) else + # Use the source file's own sample rate case $rate in 48000) lameopts+=(--resample 48) ;; 44100) lameopts+=(--resample 44.1) ;; @@ -53,7 +76,11 @@ encodeFile::mp3() { esac fi fi + # Append input WAV and output MP3 paths to complete the command lameopts+=("$tempdir/$tmpfile.wav" "${destinationpath[$destination]}/$destdir/$destfile.mp3") + + # Insert the encode task into the DB, linked to the decode (or sox) task + # Depend on sox task if it exists, else decode task encodetaskid=$( Insert tasks <<-EOInsert key ${fileid}lame$destination @@ -63,6 +90,9 @@ encodeFile::mp3() { $( for key in ${!lameopts[@]} do + # Escape special characters that could + # interfere with bash glob/brace + # expansion cleanedopts="${lameopts[key]//\&/\\\&}" cleanedopts="${cleanedopts//\[/\\[}" cleanedopts="${cleanedopts//\]/\\]}" @@ -78,6 +108,8 @@ encodeFile::mp3() { ascii ${destinationascii["$destination"]} EOInsert ) + # Increment parent task's required_by counter so it won't clean up + # until all children finish parent_required=$( Select tasks required_by \ <<<"id = ${soxtaskid:-$decodetaskid}" @@ -85,5 +117,5 @@ encodeFile::mp3() { Update tasks required_by $((++parent_required)) \ <<<"id = ${soxtaskid:-$decodetaskid}" progressSpin - soxtaskid='' + soxtaskid='' # Clear sox task ID so next destination starts fresh } diff --git a/lib/encode/opus b/lib/encode/opus index 29b833e..7c2ec11 100644 --- a/lib/encode/opus +++ b/lib/encode/opus @@ -1,9 +1,27 @@ #!/usr/bin/env 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. + encodeFile::opus() { + # Build opusenc VBR encode command opusencopts=(${ionice}opusenc --quiet) opusencopts+=(--bitrate ${destinationquality[$destination]}) + # Add Forward Error Correction if a packet loss percentage was + # configured [ -n "${destinationloss["$destination"]}" ] \ && opusencopts+=(--expect-loss "${destinationloss["$destination"]}") + # Embed Ogg comment tags [ -n "$albumartist" ] && opusencopts+=(--comment "ALBUMARTIST=$albumartist") [ -n "$album" ] && opusencopts+=(--comment "ALBUM=$album") [ -n "$artist" ] && opusencopts+=(--artist "$artist") @@ -20,6 +38,8 @@ encodeFile::opus() { && opusencopts+=(--comment) \ && opusencopts+=("REPLAYGAIN_TRACK_GAIN=$replaygain_trk") [ -n "$title" ] && opusencopts+=(--title "$title") + # Split "track/total" format: opusenc uses separate TRACKNUMBER and + # TRACKTOTAL fields [ -n "$track" ] && opusencopts+=(--comment "TRACKNUMBER=${track%/*}") [ -n "${track#*/}" ] && opusencopts+=(--comment "TRACKTOTAL=${track#*/}") [ -n "$year" ] && opusencopts+=(--comment "DATE=$year") @@ -31,6 +51,8 @@ encodeFile::opus() { fileid $destfileid filename $destdir/$destfile.opus $( + # Escape special characters that could + # interfere with bash glob/brace expansion for key in ${!opusencopts[@]} do cleanedopts="${opusencopts[key]//\&/\\\&}" @@ -48,6 +70,8 @@ encodeFile::opus() { ascii ${destinationascii["$destination"]} EOInsert ) + # Increment parent task's required_by counter so it won't clean up + # until all children finish parent_required=$( Select tasks required_by \ <<<"id = ${soxtaskid:-$decodetaskid}" diff --git a/lib/encode/vorbis b/lib/encode/vorbis index 40007c6..1de3f16 100644 --- a/lib/encode/vorbis +++ b/lib/encode/vorbis @@ -1,6 +1,23 @@ #!/usr/bin/env 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. + encodeFile::vorbis() { + # Build oggenc quality-based encode command + # (-Q = quiet, -q = quality level) oggencopts=(${ionice}oggenc -Q -q ${destinationquality[$destination]}) + # Embed Ogg comment tags using oggenc's -c "FIELD=value" syntax [ -n "$albumartist" ] && oggencopts+=(-c "ALBUMARTIST=$albumartist") [ -n "$album" ] && oggencopts+=(-l "$album") [ -n "$artist" ] && oggencopts+=(-a "$artist") @@ -17,6 +34,7 @@ encodeFile::vorbis() { [ -n "$title" ] && oggencopts+=(-t "$title") [ -n "$track" ] && oggencopts+=(-N "$track") [ -n "$year" ] && oggencopts+=(-d "$year") + # -o output must come before input for oggenc oggencopts+=(-o "${destinationpath[$destination]}/$destdir/$destfile.ogg" "$tempdir/$tmpfile.wav") encodetaskid=$( Insert tasks <<-EOInsert @@ -25,6 +43,8 @@ encodeFile::vorbis() { fileid $destfileid filename $destdir/$destfile.ogg $( + # Escape special characters that could + # interfere with bash glob/brace expansion for key in ${!oggencopts[@]} do cleanedopts="${oggencopts[key]//\&/\\\&}" @@ -42,6 +62,8 @@ encodeFile::vorbis() { ascii ${destinationascii["$destination"]} EOInsert ) + # Increment parent task's required_by counter so it won't clean up + # until all children finish parent_required=$( Select tasks required_by \ <<<"id = ${soxtaskid:-$decodetaskid}"