Projects
Multimedia
caudec
Sign Up
Log In
Username
Password
Overview
Repositories
Revisions
Requests
Users
Attributes
Meta
Expand all
Collapse all
Changes of Revision 2
View file
caudec.changes
Changed
@@ -1,4 +1,69 @@ ------------------------------------------------------------------- +Mon Jul 28 18:50:12 UTC 2014 - dap.darkness@gmail.com + +- Updated to 1.7.5: + * new -2 parameter: convert mono and multichannel audio to stereo + (upmix / downmix), using proper channel mappings; + * '-r cd' preset now implies '-b 16 -r 44100 -2' + (includes automatic conversion to stereo where applicable); + * new -z parameter: produce machine-parsable output; + * added support for AIFF and CAF (Core Audio File); + * directories are now accepted as input: caudec will + automatically find all eligible files within those directories + (use in conjunction with 'ignoreUnsupportedFiles'); + * new 'ignoreUnsupportedFiles' configuration parameter: set to + true to prevent caudec from aborting when some of the input + files are unsupported (false by default); + * -t (testing file integrity) can now be used with all lossless + codecs, using hash metadata (-H); + * added SHA256 and SHA512 hashing; + * renamed 'CRC' to 'CRC32'; backward compatibility with files + tagged with 'CRC=' is maintained; + * check internal MD5 hash whenever possible, + to detect potential codec bugs; + * new 'enableColors' configuration parameter: true by default, + set to 'false' to disable coloring of human-readable output; + * new 'brightColors' configuration parameter: true by default, + set to 'false' to use darker colors + (enableColors needs to be true); + * -G parameter (apply gain) and -S parameter (compute Soundcheck + data) can now take an arbitrary value + (signed number from -99.99 to +99.99); + * significantly sped up computing album gain; + * check the output of replaygain tools more thoroughly; + * caudec -g: process multiline metadata; + * various fixes when computing Replaygain with Ogg Vorbis, + MP3 and AAC files; + * new dependencies: SoX (mandatory) and ffmpeg + (ALAC, AAC and Monkey's Audio); + * removed shntool and alac dependencies; + * let SoX output Microsoft-compliant WAV files (fixes some + compatibility issues with multichannel files); + * added support for eyeD3 version 0.6.x and 0.7.x; the former is + recommended however, as the latter is broken; + * workaround for broken eyeD3 0.7.1: set front cover artwork in MP3 files, if available; + * better handling of Windows binaries with Wine; search home + directory to find Wine user directories automatically; + * don't store empty hash metadata, when hashing fails; + * better detection of the number of CPU cores; + * improved example command line that caudec outputs + when fed too many files; + * removed confusing 'm4a' codec name (with -c); + * fixed lossyTAK regression; + * lossyWV: use --merge-blocks (better compression); + * better ramdisk space management + (various fixes and improvements); + * new ramdisk space usage monitoring function: + print a warning if the estimation was too low; + * better comparison of version numbers when checking for new + versions online; + * only strip out ENCODING metadata field when transcoding; + * more compact display of statistics in human-readable output: + removed unnecessary; values; + * many, many more fixes; + * read more at http://caudec.net/documentation/changelog/#v1.7.5 + +------------------------------------------------------------------- Tue Mar 26 20:03:17 UTC 2013 - dap.darkness@gmail.com - Initial package.
View file
caudec.spec
Changed
@@ -10,7 +10,7 @@ # Name: caudec -Version: 1.6.2 +Version: 1.7.5 Release: 0 License: GPL-3.0+ Summary: A multi-process audio transcoder @@ -22,7 +22,6 @@ BuildRoot: %{_tmppath}/build-%{name}-%{version} -Recommends: alac_decoder Recommends: apetag Recommends: cksfv Recommends: flac @@ -33,21 +32,19 @@ Recommends: nero-aac Recommends: opus Recommends: python-eyeD3 -Recommends: sox Recommends: vorbis-tools Recommends: vorbisgain +Recommends: wavegain Recommends: wavpack Recommends: wget Recommends: wine Requires: bash Requires: bc -Requires: coreutils Requires: findutils -Requires: grep Requires: procps Requires: sed -Requires: shntool +Requires: sox Requires: util-linux %description @@ -63,14 +60,14 @@ %install mkdir -p %{buildroot}%{_bindir} -%{__install} %{name} %{buildroot}%{_bindir} -mkdir -p %{buildroot}%{_sysconfdir} -%{__install} %{name}rc %{buildroot}%{_sysconfdir} +install -m 0755 %{name} APEv2 %{buildroot}%{_bindir} +install -D %{name}rc %{buildroot}%{_sysconfdir}/%{name}rc %files %defattr(-,root,root) -%doc LICENSE CHANGES README +%doc LICENSE* CHANGES README %{_bindir}/%{name} +%{_bindir}/APEv2 %{_sysconfdir}/%{name}rc %changelog
View file
caudec-1.7.5.tar.gz/APEv2
Added
@@ -0,0 +1,1648 @@ +#!/bin/bash + +# Copyright © 2013 - 2014 Guillaume Cocatre-Zilgien <gcocatre@gmail.com> +# http://caudec.net/ +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# +# This program is provided without warranty of any kind, either expressed, +# implied, or statutory. The entire risk as to the quality and performance +# of this program is with You. Under no circumstances and under no legal +# theory shall any Contributor, or anyone who distributes this program, +# be liable to You for any damages of any character. +# +# Full APEv2 specification: +# http://wiki.hydrogenaudio.org/index.php?title=APEv2_specification + +printUsage () +{ + echo "${me} ${VERSION}: APEv2 tagger +Copyright © 2013 - 2014 Guillaume Cocatre-Zilgien +http://caudec.net/ + +Usage: $me [ PARAMETERS ] FILE(S) +List, set or remove tags in APEv2 files (.ape, .wv, .mpc, .tak…). +Default action: list tags, with colored field names. + + +Tagging Parameters: +-------------------------------------------------------------------------------- + -i read and set tags from STDIN + -f FILE copy APEv2 tags from FILE (must contain a valid APEv2 tag) + -t F=V set tag item with field name F and value V + -r F remove tag item with field name F + -R remove all APEv2 data + + -a F=P set artwork tag item with field name F, with the contents of file P; + F must start with 'Cover Art', and P must be a valid path to the + file (normally, either .jpg, .png or .gif). + + -b F=P set binary tag item with field name F, with the contents of file P; + P must be a valid path to the file + + -d F=P dump value of tag item with field name F into file P; P may be a + filename, or '-' to dump to STDOUT. This parameter may be used with + any type of field (artwork, binary or text), and can only be used + on its own. When dumping artwork, ${me} will automatically append + the correct file extension, if the provided filename (P) ends with + a dot ('.'). + + +Output Formatting Parameters: +-------------------------------------------------------------------------------- + -C disable coloring + + -D print debugging information to STDERR + + -z machine-parsable output: replace new line chars with '\\n', + NULL chars with '\\x00', prefix read-only tag items with 'ro:', and + print artwork and binary data as Base64 (preceded by 'artwork:' and + 'data:', respectively). Implies -C. + + -Z raw mode: output new line chars, NULL chars and binary data as is. + Implies -C. Warning: this might mess up your terminal, unless you + redirect the output to a program that can handle it, or to a file. + + +Notes about output modes: +-------------------------------------------------------------------------------- +By default, $me prints tags in a colored, human readable format, with new line +characters printed as is, NULL characters replaced with ' / ', artwork +designated as 'artwork: <SIZE>' (byte size of the artwork) and binary data +designated as 'data: <SIZE>' (byte size of the data). + +To disable coloring, use -C. To change the color of field names, +export APEV2FCC='xyy', where 'x' is either '1' (bold) or '0', and 'yy' is +a number between 30 and 37 (see: man 4 console_codes). To change the color of +values, export APEV2VCC='xyy' (default: '000', i.e. no coloring of values). +If both environment variables are set to '000', coloring is disabled (like +using -C). Note that coloring is always disabled when using -z or -Z. + + +Notes about setting and removing tag items: +-------------------------------------------------------------------------------- +To specify new lines with -t or -i, write them as '\\n' (though actual new lines +are accepted as well); to specify NULL chars (for NULL separated lists of +values), write them as '\\x00'. The -z parameter uses those notations. + +When feeding tags via STDIN (-i), you must specify one FIELD=VALUE pair on each +line. Write new lines and NULL chars as '\\n' and '\\x00', respectively. +To read FIELD=VALUE pairs from a text file, simply run: +\$ ${me} -i file.ape < metadata.txt + +In order to mark a tag item read-only with -t, -i, -a or -b, precede the field +name with 'ro:', e.g.: -t \"ro:Artist=Daft Punk\". Read-only items cannot be +updated, but they may be explicitely removed with the -r parameter. + +Note that in the APEv2 specification, all field names are unique; using -i, -t, +-a or -b will either create new tag items, or replace existing ones. You may +also use the -r and -R parameters with -i, -t, -a and -b. + + +Miscellaneous: +-------------------------------------------------------------------------------- +The full APEv2 specification can be found on the Hydrogenaudio Wiki: +http://wiki.hydrogenaudio.org/index.php?title=APEv2_specification" +} + +cleanExit () +{ + if [ -n "$nProcesses" ]; then + if [ $nProcesses -gt 1 ]; then + kill $( jobs -p ) >/dev/null 2>&1 + fi + fi + if [ -n "$tempDir" -a -e "$tempDir" ]; then + rm -rf "$tempDir" >/dev/null 2>&1 + fi + exit $1 +} + +cleanAbort () +{ + if [ -n "$nProcesses" ]; then + if [ $nProcesses -gt 1 ]; then + kill $( jobs -p ) >/dev/null 2>&1 + fi + unset nProcesses + fi + echo + cleanExit $EX_INTERRUPT +} + +debugMsg () +{ + if [ $debug = true ]; then + echo -e "${WG}DEBUG:${NM} $1" 1>&2 + fi +} + +printWarning () +{ + if [ "$outputMode" = 'colors' ]; then + echo -e "${WG}Warning:${NM} $1" 1>&2 + else + echo -E "Warning: $1" 1>&2 + fi +} + +printError () +{ + if [ "$outputMode" = 'colors' ]; then + echo -e "${KO}Error:${NM} $1" 1>&2 + else + echo -E "Error: $1" 1>&2 + fi +} + +setColors () +{ + local boldCode='' colorCode='' + + if [ "$outputMode" = 'colors' ]; then # default mode + if [ "$APEV2FCC" = '000' -a "$APEV2VCC" = '000' ]; then + outputMode='human' # disable all coloring + else + NM="\\033[0m" # reset + OK="\\033[1;32m" # success: bright green + KO="\\033[1;31m" # failure: bright red + WG="\\033[1;33m" # warning: bright orange + FC="\\033[1;34m" # field names: bright blue + VC="$NM" # item values: no coloring + + # custom field name color + if [ "$APEV2FCC" = '000' ]; then + FC="$NM" # disable coloring of field names + elif [ "${#APEV2FCC}" -eq 3 ]; then + if [ "${APEV2FCC:0:1}" = '0' ]; then + boldCode='0' + else + boldCode='1' + fi + case "${APEV2FCC:1:2}" in + 3[0-7]) colorCode="${APEV2FCC:1:2}" ;; + *) colorCode='34' + esac + FC="\\033[${boldCode};${colorCode}m" + fi + + # custom item value color + if [ "$APEV2VCC" = '000' ]; then + VC="$NM" # disable coloring of item values + elif [ "${#APEV2VCC}" -eq 3 ]; then + if [ "${APEV2VCC:0:1}" = '0' ]; then + boldCode='0' + else + boldCode='1' + fi + colorCode="${APEV2VCC:1:2}" + case "$colorCode" in + 3[0-7]) VC="\\033[${boldCode};${colorCode}m" ;; + esac + fi + fi + else # outputMode != 'colors' + # disable all colors + NM='' OK='' KO='' WG='' FC='' VC='' + fi +} + +createTempDir () +{ + if [ -n "$tempDir" -a -d "$tempDir" -a -w "$tempDir" ]; then + return $EX_OK + fi + + tempDir='' + if [ -n "$TMPDIR" -a -d "$TMPDIR" -a -w "$TMPDIR" ]; then + tempDir="$( TMPDIR="$TMPDIR" mktemp -d "${TMPDIR}/${me}.XXXXXXXX" )" + elif [ -d '/tmp' -a -w '/tmp' ]; then + tempDir="$( TMPDIR='/tmp' mktemp -d "/tmp/${me}.XXXXXXXX" )" + elif [ -d '/dev/shm' -a -w '/dev/shm' ]; then + tempDir="$( TMPDIR='/dev/shm' mktemp -d "/dev/shm/${me}.XXXXXXXX" )" + elif [ -d "$PWD" -a -w "$PWD" ]; then + tempDir="$( TMPDIR="$PWD" mktemp -d "${PWD}/${me}.XXXXXXXX" )" + fi + if [ -z "$tempDir" -o ! -d "$tempDir" -o ! -w "$tempDir" ]; then + printError "can't create temporary directory." + if [ -n "$tempDir" -a -d "$tempDir" ]; then + rm -rf "$tempDir" >/dev/null 2>&1 + fi + cleanExit $EX_IOERR + else + mkdir -p "${tempDir}/common" >/dev/null 2>&1 + return $EX_OK + fi +} + +createTempProcessDir () +{ + tempProcessDir="${tempDir}/process.${pn}" + if [ -e "$tempProcessDir" ]; then + rm -rf "$tempProcessDir" >/dev/null 2>&1 + fi + mkdir -p "$tempProcessDir" +} + +checkBinary () +{ + local p rc=$EX_OK + + for b in "$@"; do + p="x${b}Y" + if [ "$searchedBinaries" = "${searchedBinaries//$p/@}" ]; then # search for binary hasn't been done before + searchedBinaries="${searchedBinaries}${p}" + if which "$b" >/dev/null 2>&1 ; then + foundBinaries="${foundBinaries}${p}" + continue + else + rc=$EX_KO binaryMissing=true + printWarning "binary \"${b}\" not found. Make sure it is in your \$PATH." + fi + elif [ -z "$foundBinaries" -o "$foundBinaries" = "${foundBinaries//$p/@}" ]; then # binary was previously searched, but not found + rc=$EX_KO + fi + done + + return $rc +} + +checkBinaries () +{ + searchedBinaries='' + binaryMissing=false + + if ! which 'which' >/dev/null 2>&1 ; then + printWarning "binary \"which\" not found. Make sure it is in your \$PATH." + cleanExit $EX_OSFILE + fi + + checkBinary "$sedcmd" 'od' 'tr' 'cut' 'dd' 'mktemp' 'dirname' 'wc' 'sort' 'head' 'tail' 'base64' 'stat' 'kill' + + if [ $binaryMissing = true ]; then + cleanExit $EX_OSFILE + else + return $EX_OK + fi +} + +startTimer () +{ + if [ $gnudate = true ]; then + timer1="$( $datecmd '+%s.%N' )" + else + timer1="$( date '+%s' ).0" + fi +} + +stopTimer () +{ + local seconds timer2 + + if [ $gnudate = true ]; then + timer2="$( $datecmd '+%s.%N' )" + else + timer2="$( date '+%s' ).0" + fi + seconds="$( printf 'scale=6; %.6f - %.6f\n' "$timer2" "$timer1" | bc 2>/dev/null )" + printf "${WG}%s:${NM} %.3f seconds\n" "$1" $seconds 1>&2 +} + +readBinaryDecimalInteger () +{ + local start="$1" length="$2" i=0 + + i="$( od -A n -j $start -N $length -i "$file" 2>/dev/null | tr -cd '0-9' )" + case "$i" in + [0-9]*) echo "$i" ;; + *) echo '-1' ;; + esac +} + +getHumanByteSize () +{ + local bytes="$1" + + if [ $bytes -lt 1024 ]; then + echo -n "$bytes B" + elif [ $bytes -lt 1048576 ]; then + printf "%.1f %s" "$( echo "scale=3; $bytes / 1024" | bc )" 'KiB' + elif [ $bytes -lt 1073741824 ]; then + printf "%.1f %s" "$( echo "scale=3; $bytes / 1048576" | bc )" 'MiB' + else # Houston, we have a problem. + printf "%.1f %s" "$( echo "scale=3; $bytes / 1073741824" | bc )" 'GiB' + fi +} + +integerToBinaryData () +{ + local h='' + + h="$( printf "%08X" "$1" )" + echo -en "\x${h:6:2}\x${h:4:2}\x${h:2:2}\x${h:0:2}" +} + +getFileSize () +{ + local file="$1" bytes + + if [ ! -e "$file" ]; then + printError "file \"${file}\" doesn't exist." + echo 0 + else + if [ "$OS" = 'Linux' ]; then + bytes="$( stat -L -c %s "$file" 2>/dev/null | tr -cd '0-9' )" + else + bytes="$( stat -L -f %z "$file" 2>/dev/null | tr -cd '0-9' )" + fi + case "$bytes" in + [0-9]*) echo $bytes ;; + *) echo 0 ;; + esac + fi +} + +getFilePortion () +{ + local offset=$1 size=$2 tailSize=0 + + if [ "$apetagLocation" = 'bottom' ]; then + tailSize=$(( fileSize - offset )) + tail -c $tailSize "$file" 2>/dev/null | head -c $size 2>/dev/null + else + head -c $(( offset + size )) "$file" 2>/dev/null | tail -c $size 2>/dev/null + fi +} + +hasApetag () +{ + local preamble='' + + if [ "$apetagPresent" = 'true' ]; then + return $EX_OK + elif [ "$apetagPresent" = 'false' ]; then + return $EX_KO + else + if [ $fileSize -le 64 ]; then + debugMsg 'no apetag present' + return $EX_KO + fi + + # check the end of the file first + preamble="$( dd if="$file" bs=1 skip=$(( fileSize - 32 )) count=8 2>/dev/null | LC_ALL=C tr -cd 'A-Z' )" + if [ "$preamble" = 'APETAGEX' ]; then + apetagVersion="$( readBinaryDecimalInteger $(( fileSize - 32 + 8 )) 4 )" + apetagPresent='true' apetagLocation='bottom' hasFooter=true footerOffset=$(( fileSize - 32 )) + debugMsg "apetag present: location=${apetagLocation}, version=${apetagVersion}, footerOffset=${footerOffset}" + else + # check the beginning of the file + preamble="$( dd if="$file" bs=8 count=1 2>/dev/null | LC_ALL=C tr -cd 'A-Z' )" + if [ "$preamble" = 'APETAGEX' ]; then + apetagVersion="$( readBinaryDecimalInteger 8 4 )" + apetagPresent='true' apetagLocation='top' hasHeader=true headerOffset=0 + debugMsg "apetag present: location=${apetagLocation}, version=${apetagVersion}, headerOffset=${headerOffset}" + fi + fi + + if [ "$preamble" = 'APETAGEX' ]; then + if [ "$apetagVersion" = '2000' ]; then + apetagPresent='true' + return $EX_OK + elif [ "$apetagVersion" = '-1' ]; then + printError "file \"$file\" is corrupted: invalid APE version number." + return $EX_DATAERR + else + printWarning "unsupported APE version (${apetagVersion})" + apetagPresent='false' + return $EX_KO + fi + else + apetagPresent='false' + debugMsg 'no apetag present' + return $EX_KO + fi + fi +} + +getApetagSize () +{ + # tag size does NOT include the header, if present + if [ "$apetagLocation" = 'bottom' ]; then + tagSize="$( readBinaryDecimalInteger $(( footerOffset + 8 + 4 )) 4 )" + if [ "$tagSize" = '-1' ]; then + printError "file \"$file\" is corrupted: invalid tag size." + return $EX_DATAERR + elif [ $tagSize -lt 32 ]; then # smaller than the footer alone + printError "file \"$file\" is corrupted: tag size smaller than 32 bytes." + return $EX_DATAERR + fi + else + tagSize="$( readBinaryDecimalInteger $(( headerOffset + 8 + 4 )) 4 )" + if [ $tagSize -lt 0 ]; then # invalid value (minimum: 0, no tag items, no footer) + printError "file \"$file\" is corrupted: invalid tag size." + return $EX_DATAERR + fi + fi + debugMsg "tagSize=${tagSize}" +} + +getApetagOffset () +{ + local preamble='' + + if [ "$apetagLocation" = 'bottom' ]; then + apetagOffset=$(( fileSize - tagSize )) + preamble="$( dd if="$file" bs=1 skip=$(( apetagOffset - 32 )) count=8 2>/dev/null | LC_ALL=C tr -cd 'A-Z' )" + if [ "$preamble" = 'APETAGEX' ]; then + hasHeader=true + apetagOffset=$(( apetagOffset - 32 )) + headerOffset=$apetagOffset + debugMsg "hasHeader=${hasHeader}, hasFooter=${hasFooter}, apetagOffset=${apetagOffset}, headerOffset=${headerOffset}" + else + debugMsg "hasHeader=${hasHeader}, hasFooter=${hasFooter}, apetagOffset=${apetagOffset}" + fi + else + apetagOffset=0 + preamble="$( dd if="$file" bs=1 skip=$(( apetagOffset + 32 + tagSize - 32 )) count=8 2>/dev/null | LC_ALL=C tr -cd 'A-Z' )" + if [ "$preamble" = 'APETAGEX' ]; then + hasFooter=true + footerOffset=$(( apetagOffset + 32 + tagSize - 32 )) + debugMsg "hasHeader=${hasHeader}, hasFooter=${hasFooter}, apetagOffset=${apetagOffset}, footerOffset=${footerOffset}" + else + debugMsg "hasHeader=${hasHeader}, hasFooter=${hasFooter}, apetagOffset=${apetagOffset}" + fi + fi +} + +resetApetagProperties () +{ + apetagPresent='false' + fileSize=$apetagOffset + tagSize=0 + apetagOffset=0 + headerOffset=0 + footerOffset=0 + itemsOffset=0 + apetagLocation='none' + hasHeader=false + hasFooter=false +} + +initFileVars () +{ + debugMsg '--------------------------------------------------------------------------------' + debugMsg "file: \"${file}\"" + fileSize="$( getFileSize "$file" )" + debugMsg "fileSize=${fileSize}" + apetagPresent='' + tagSize=0 + apetagOffset=0 + headerOffset=0 + footerOffset=0 + itemsOffset=0 + itemsCount=0 + allKeys='' + readOnlyKeys='' + apetagLocation='none' + hasHeader=false + hasFooter=false + sortedItemSizes='' + unset itemKeys itemLowercaseKeys itemValues + declare -a itemKeys=() + declare -a itemLowercaseKeys=() + declare -a itemValues=() +} + +removeApetag () +{ + local ec=$EX_OK tempFile='' + + if [ "$apetagLocation" = 'bottom' ]; then + if which 'truncate' >/dev/null 2>&1 ; then + truncate -s $apetagOffset "$file" 2>/dev/null ; ec=$? + elif which 'gtruncate' >/dev/null 2>&1 ; then + gtruncate -s $apetagOffset "$file" 2>/dev/null ; ec=$? + else + tempFile="${tempProcessDir}/tempfile" + head -c $apetagOffset "$file" 2>/dev/null | dd of="$tempFile" bs=$(( 8192 * 1024 )) >/dev/null 2>&1 ; ec=$? + if [ $ec -eq $EX_OK ]; then + rm -f "$file" >/dev/null 2>&1 && + mv -f "$tempFile" "$file" >/dev/null 2>&1 ; ec=$? + fi + if [ -e "$tempFile" ]; then rm -f "$tempFile"; fi + fi + resetApetagProperties + return $ec + else # apetagLocation = 'top' + tempFile="${tempProcessDir}/tempfile" + tail -c $(( fileSize - (32 + tagSize) )) "$file" 2>/dev/null | dd of="$tempFile" bs=$(( 8192 * 1024 )) >/dev/null 2>&1 ; ec=$? + resetApetagProperties + if [ $ec -eq $EX_OK ]; then + rm -f "$file" >/dev/null 2>&1 && + mv -f "$tempFile" "$file" >/dev/null 2>&1 ; ec=$? + fi + if [ -e "$tempFile" ]; then rm -f "$tempFile"; fi + return $ec + fi +} + +writeNewApetag () +{ + local itemKey itemValue itemValueSize itemSize itemValueTypes=() fileDir='' tempDirLength p artworkFilenameSize=0 artworkFilepath artworkFilename binaryFilepath + + sortedItemSizes='' + fileDir="$( dirname "$file" )" + if [ "$fileDir" != '/' ]; then fileDir="${fileDir}/"; fi + + tagSize=32 + for ((i=0; i<itemsCount; i++)); do + itemKey="${itemKeys[$i]}" + itemValue="${itemValues[$i]}" + if [ "${itemValue:0:15}" = 'binaryfilepath:' ]; then + itemValueTypes[$i]='binary' + binaryFilepath="${itemValue##*:}" + itemSize="$( getFileSize "$binaryFilepath" )" + itemSize=$(( 4 + 4 + ${#itemKey} + 1 + itemSize )) + sortedItemSizes="${sortedItemSizes}$( echo -en "\n${itemSize} ${i}" )" + elif [ "${itemValue:0:16}" = 'artworkfilepath:' ]; then + itemValueTypes[$i]='artwork' + artworkFilepath="${itemValue##*:}" + artworkFilename="${itemValue:16}"; artworkFilename="${artworkFilename%:*}" + itemSize="$( getFileSize "$artworkFilepath" )" + artworkFilenameSize="$( echo -En "$artworkFilename" | wc -c | tr -cd '0-9' )" + itemSize=$(( 4 + 4 + ${#itemKey} + 1 + artworkFilenameSize + 1 + itemSize )) + sortedItemSizes="${sortedItemSizes}$( echo -en "\n${itemSize} ${i}" )" + else + itemValue="${itemValues[$i]}" + # adding 'x...x' when testing $itemValue because a value of '(' would make the tests crap out with a BASH error: line 581: [: `)' expected, found ( + if [ "x${itemValue:0:7}x" = 'xhttp://x' -o "x${itemValue:0:6}x" = 'xftp://x' -o "x${itemValue:1:2}x" = 'x:/x' ]; then + itemValueTypes[$i]='locator' + elif [ "x${itemValue%.*}x" != "x${itemValue}x" -a -n "${itemValue##*.}" -a "x${itemValue:0:1}x" != 'x/x' -a -f "${fileDir}${itemValue}" ]; then + itemValueTypes[$i]='locator' + elif [ "x${itemValue%.*}x" != "x${itemValue}x" -a -n "${itemValue##*.}" -a "x${itemValue:0:1}x" = 'x/x' -a -f "${itemValue}" ]; then + itemValueTypes[$i]='locator' + else + itemValueTypes[$i]='text' + fi + itemSize="$( echo -En "$itemValue" | wc -c | tr -cd '0-9' )" + itemSize=$(( 4 + 4 + ${#itemKey} + 1 + $itemSize )) + sortedItemSizes="${sortedItemSizes}$( echo -en "\n${itemSize} ${i}" )" + fi + tagSize=$(( tagSize + itemSize )) + done + + sortedItemSizes="$( echo "${sortedItemSizes:1}" | sort -n )" + + { + # Header + echo -n 'APETAGEX' # preamble + echo -en "\xD0\x07\x00\x00" # version 2.0 + integerToBinaryData $tagSize + integerToBinaryData $itemsCount + echo -en "\x00\x00\x00\xA0" # this is the header + echo -en "\x00\x00\x00\x00\x00\x00\x00\x00" # reserved + + # Items + while read itemSize i; do + itemKey="${itemKeys[$i]}" + itemValue="${itemValues[$i]}" + if [ "${itemValueTypes[$i]}" = 'binary' ]; then + binaryFilepath="${itemValue##*:}" + integerToBinaryData "$( getFileSize "$binaryFilepath" )" # item value size + elif [ "${itemValueTypes[$i]}" = 'artwork' ]; then + artworkFilepath="${itemValue##*:}" + artworkFilename="${itemValue:16}"; artworkFilename="${artworkFilename%:*}" + artworkFilenameSize="$( echo -En "$artworkFilename" | wc -c | tr -cd '0-9' )" + itemValueSize="$( getFileSize "$artworkFilepath" )" # item value size + itemValueSize=$(( artworkFilenameSize + 1 + itemValueSize )) + integerToBinaryData "$itemValueSize" # item value size + else + itemSize="$( echo -En "$itemValue" | wc -c | tr -cd '0-9' )" + integerToBinaryData $itemSize # item value size + fi + + p="x$( echo -En "$itemKey" | tr '[:upper:]' '[:lower:]' )Y" + if [ "$readOnlyKeys" = "${readOnlyKeys//$p/@}" -a "$newReadOnlyKeys" = "${newReadOnlyKeys//$p/@}" ]; then + case "${itemValueTypes[$i]}" in + text) echo -en "\x00\x00\x00\x00" ;; + binary|artwork) echo -en "\x02\x00\x00\x00" ;; + locator) echo -en "\x04\x00\x00\x00" ;; + esac + else + case "${itemValueTypes[$i]}" in + text) echo -en "\x01\x00\x00\x00" ;; + binary|artwork) echo -en "\x03\x00\x00\x00" ;; + locator) echo -en "\x05\x00\x00\x00" ;; + esac + fi + + echo -En "$itemKey" + echo -en "\x00" + if [ "${itemValueTypes[$i]}" = 'binary' ]; then + cat "$binaryFilepath" 2>/dev/null + elif [ "${itemValueTypes[$i]}" = 'artwork' ]; then + echo -En "$artworkFilename" + echo -en "\x00" + cat "$artworkFilepath" 2>/dev/null + else + echo -En "$itemValue" | LC_ALL=C tr '\1' '\0' 2>/dev/null + fi + done < <( echo "$sortedItemSizes" ) + + # Footer + echo -n 'APETAGEX' # preamble + echo -en "\xD0\x07\x00\x00" # version 2.0 + integerToBinaryData $tagSize + integerToBinaryData $itemsCount + echo -en "\x00\x00\x00\x80" # this is the footer + echo -en "\x00\x00\x00\x00\x00\x00\x00\x00" # reserved + } >> "$file" + + return $EX_OK +} + +getItemsOffset () +{ + if [ "$apetagLocation" = 'bottom' ]; then + if [ $hasHeader = true ]; then + itemsOffset=$(( apetagOffset + 32 )) + else + itemsOffset=$apetagOffset + fi + else + itemsOffset=$(( apetagOffset + 32 )) + fi + debugMsg "itemsOffset=${itemsOffset}" +} + +getNumberOfItems () +{ + if [ "$apetagLocation" = 'bottom' ]; then + itemsCount="$( readBinaryDecimalInteger $(( footerOffset + 8 + 4 + 4 )) 4 )" + else + itemsCount="$( readBinaryDecimalInteger $(( headerOffset + 8 + 4 + 4 )) 4 )" + fi + + if [ $itemsCount -lt 0 ]; then + printError "file \"$file\" is corrupted: invalid number of items." + return $EX_DATAERR + fi + + debugMsg "itemsCount=${itemsCount}" +} + +getItem () +{ + local itemValueSize=0 itemKeyOffset=0 char='' o=0 itemValueType='' itemSize itemIsBinary=false itemIsReadOnly=false p='' itemFilename='' itemDataSize=0 + + itemKey='' itemLowercaseKey='' itemValue='' + + itemValueSize="$( readBinaryDecimalInteger $itemOffset 4 )" + if [ $itemValueSize -lt 1 ]; then + printError "file \"$file\" is corrupted: invalid length of item value." + return $EX_DATAERR + fi + itemValueType="$( readBinaryDecimalInteger $(( itemOffset + 4 )) 4)" + if [ $itemValueType -lt 0 -o $itemValueType -gt 5 ]; then + printError "file \"$file\" is corrupted: invalid item flags ($itemValueType)." + return $EX_DATAERR + fi + + itemKeyOffset=$(( itemOffset + 4 + 4 )) + o=$itemKeyOffset + + # here tr and cut fail on OS X if LC_ALL is not set to C + itemKey="$( dd if="$file" bs=1 skip=$o count=255 2>/dev/null | LC_ALL=C tr '\0\n' '\1\2' 2>/dev/null | LC_ALL=C cut -d "$( echo -en "\x01" )" -f 1 2>/dev/null )" + if [ ${#itemKey} -lt 2 ]; then + printError "file \"$file\" is invalid: length of field name (${#itemKey}) should range from 2 to 255 characters." + return $EX_DATAERR + elif [ ${#itemKey} -eq 255 ]; then + char="$( readBinaryDecimalInteger $(( o + 255 )) 1 )" + if [ $char -ne 0 ]; then # the mandatory null char that separates the key from the value isn't there + printError "file \"$file\" is corrupted: NULL char missing between field name and item value." + return $EX_DATAERR + fi + fi + itemLowercaseKey="$( echo -En "$itemKey" | tr '[:upper:]' '[:lower:]' )" + p="x${itemLowercaseKey}Y" + if [ "$allKeys" != "${allKeys//$p/@}" ]; then + printError "file \"$file\" is invalid: it contains two or more items with the same field name (they must be unique)." + return $EX_DATAERR + fi + o=$(( o + ${#itemKey} + 1 )) + + case "$itemValueType" in + 0|4) itemIsReadOnly=false itemIsBinary=false ;; + 1|5) itemIsReadOnly=true itemIsBinary=false ;; + 2) itemIsReadOnly=false itemIsBinary=true ;; + 3) itemIsReadOnly=true itemIsBinary=true ;; + esac + + if [ $itemIsReadOnly = true ]; then + readOnlyKeys="${readOnlyKeys}x${itemLowercaseKey}Y" + fi + + if [ $itemIsBinary = true ]; then # binary data + if [ "${itemLowercaseKey:0:9}" = 'cover art' ]; then + itemFilename="$( dd if="$file" bs=1 skip=$o count=255 2>/dev/null | LC_ALL=C tr '\0\n' '\1\2' 2>/dev/null | LC_ALL=C cut -d "$( echo -en "\x01" )" -f 1 2>/dev/null )" + if [ ${#itemFilename} -lt 5 ]; then + printError "file \"$file\" is invalid: length of cover art filename (${#itemFilename}) should range from 5 to 255 characters." + return $EX_DATAERR + elif [ ${#itemFilename} -eq 255 ]; then + char="$( readBinaryDecimalInteger $(( o + 255 )) 1 )" + if [ $char -ne 0 ]; then # the mandatory null char that separates the filename from the data isn't there + printError "file \"$file\" is corrupted: NULL char missing between filename and artwork data." + return $EX_DATAERR + fi + fi + itemDataSize=$(( itemValueSize - ${#itemFilename} - 1 )) + if [ $itemDataSize -lt 4 -o $itemDataSize -gt 1073741824 ]; then + printError "file \"$file\" is corrupted: invalid size (${itemDataSize} B) of cover art." + return $EX_DATAERR + fi + getFilePortion $(( o + ${#itemFilename} + 1 )) $itemDataSize > "${tempProcessDir}/artwork.${i}" + itemValue="artworkfilepath:${itemFilename}:${tempProcessDir}/artwork.${i}" + else # binary data + if [ $itemValueSize -lt 1 -o $itemValueSize -gt 1073741824 ]; then + printError "file \"$file\" is corrupted: invalid size (${itemValueSize} B) of binary data." + return $EX_DATAERR + fi + getFilePortion $o $itemValueSize > "${tempProcessDir}/binary.${i}" + itemValue="binaryfilepath:${tempProcessDir}/binary.${i}" + fi + else + if [ $itemValueSize -lt 1 -o $itemValueSize -gt 1073741824 ]; then + printError "file \"$file\" is corrupted: invalid length (${itemValueSize}) of item value." + return $EX_DATAERR + fi + itemValue="$( dd if="$file" bs=1 skip=$o count=$itemValueSize 2>/dev/null | LC_ALL=C tr '\0' '\1' 2>/dev/null | LC_ALL=C tr -d '\r' 2>/dev/null )" + fi + itemSize=$(( o + itemValueSize - itemOffset )) + debugMsg "itemNumber=$i, itemOffset=${itemOffset}, itemSize=${itemSize}, readOnly=${itemIsReadOnly}, isBinary=${itemIsBinary}, itemKey=\"${itemKey}\", itemKeySize=${#itemKey}, itemValueSize=${itemValueSize}" + itemOffset=$(( o + itemValueSize )) +} + +getItems () +{ + local ec=$EX_OK + + itemOffset="$itemsOffset" + for ((i=0; i<itemsCount; i++)); do + getItem; ec=$? + if [ $ec -eq $EX_OK ]; then + itemKeys[$i]="$itemKey" + itemLowercaseKeys[$i]="$itemLowercaseKey" + itemValues[$i]="$itemValue" + allKeys="${allKeys}x${itemLowercaseKey}Y" + else + return $ec + fi + done + return $ec +} + +removeItems () +{ + local tempItemCount=0 deleteKeys='' p='' itemValue='' + declare -a tempItemKeys=() + declare -a tempItemLowercaseKeys=() + declare -a tempItemValues=() + + for ((i=0; i<deletedKeysCount; i++)); do + p="x${deletedLowercaseKeys[$i]}Y" + readOnlyKeys="${readOnlyKeys//$p/}" + deleteKeys="${deleteKeys}${p}" + done + + for ((i=0; i<newTagsCount; i++)); do + p="x${newLowercaseKeys[$i]}Y" + if [ "$readOnlyKeys" = "${readOnlyKeys//$p/@}" ]; then + deleteKeys="${deleteKeys}${p}" + else + printWarning "item \"${newKeys[$i]}\" is marked read-only." + fi + done + + for ((i=0; i<itemsCount; i++)); do + p="x${itemLowercaseKeys[$i]}Y" + if [ "$deleteKeys" = "${deleteKeys//$p/@}" ]; then + tempItemKeys[$tempItemCount]="${itemKeys[$i]}" + tempItemLowercaseKeys[$tempItemCount]="${itemLowercaseKeys[$i]}" + tempItemValues[$tempItemCount]="${itemValues[$i]}" + if [ -f "${tempProcessDir}/artwork.${i}" ]; then + mv "${tempProcessDir}/artwork.${i}" "${tempProcessDir}/tempArtwork.${tempItemCount}" >/dev/null 2>&1 + elif [ -f "${tempProcessDir}/binary.${i}" ]; then + mv "${tempProcessDir}/binary.${i}" "${tempProcessDir}/tempBinary.${tempItemCount}" >/dev/null 2>&1 + fi + ((tempItemCount++)) + elif [ -f "${tempProcessDir}/artwork.${i}" ]; then + rm -f "${tempProcessDir}/artwork.${i}" >/dev/null 2>&1 + elif [ -f "${tempProcessDir}/binary.${i}" ]; then + rm -f "${tempProcessDir}/binary.${i}" >/dev/null 2>&1 + fi + done + + unset itemKeys itemLowercaseKeys itemValues + itemsCount=$tempItemCount + for ((i=0; i<tempItemCount; i++)); do + itemKeys[$i]="${tempItemKeys[$i]}" + itemLowercaseKeys[$i]="${tempItemLowercaseKeys[$i]}" + itemValues[$i]="${tempItemValues[$i]}" + if [ -f "${tempProcessDir}/tempArtwork.${i}" ]; then + mv "${tempProcessDir}/tempArtwork.${i}" "${tempProcessDir}/artwork.${i}" >/dev/null 2>&1 + itemValue="${tempItemValues[$i]}" + itemValues[$i]="${itemValue%:*}:${tempProcessDir}/artwork.${i}" + elif [ -f "${tempProcessDir}/tempBinary.${i}" ]; then + mv "${tempProcessDir}/tempBinary.${i}" "${tempProcessDir}/binary.${i}" >/dev/null 2>&1 + itemValue="${tempItemValues[$i]}" + itemValues[$i]="${itemValue%:*}:${tempProcessDir}/binary.${i}" + fi + done + unset tempItemKeys tempItemLowercaseKeys tempItemValues +} + +addNewItems () +{ + local p='' + + # itemsCount is either set by removeItems(), or equal to zero + for ((i=0; i<newTagsCount; i++)); do + p="x${newLowercaseKeys[$i]}Y" + if [ "$readOnlyKeys" = "${readOnlyKeys//$p/@}" ]; then + itemKeys[$itemsCount]="${newKeys[$i]}" + itemLowercaseKeys[$itemsCount]="${newLowercaseKeys[$i]}" + itemValues[$itemsCount]="${newValues[$i]}" + ((itemsCount++)) + fi + done +} + +listItems () +{ + local itemKey='' itemLowercaseKey='' itemValue='' itemValueSize=0 roPrefix='' artworkFilename='' p='' ppn=0 + + if [ -z "$sortedItemSizes" ]; then + for ((i=0; i<itemsCount; i++)); do + sortedItemSizes="${sortedItemSizes}$( echo -en "\n0 ${i}" )" + done + sortedItemSizes="${sortedItemSizes:1}" + fi + + case "$outputMode" in + colors) + { + while read foo i; do + itemKey="${itemKeys[$i]}" + itemValue="${itemValues[$i]}" + echo -en "${FC}" + echo -En "$itemKey" + echo -en "${NM}=${VC}" + if [ "${itemValue:0:15}" = 'binaryfilepath:' ]; then + itemValueSize="$( getFileSize "${itemValue:15}" )" + echo -En "data: $( getHumanByteSize "${itemValueSize}" )" + elif [ "${itemValue:0:16}" = 'artworkfilepath:' ]; then + itemValueSize="$( getFileSize "${itemValue##*:}" )" + echo -En "artwork: $( getHumanByteSize "${itemValueSize}" )" + else + echo -En "${itemValue}" + fi + echo -e "${NM}" + done < <( echo "$sortedItemSizes" ) + } | $sedcmd -e 's@\x01@ / @g' > "${tempDir}/common/outputQueue.${pn}" + ;; + + human) + { + while read foo i; do + itemKey="${itemKeys[$i]}" + itemValue="${itemValues[$i]}" + if [ "${itemValue:0:15}" = 'binaryfilepath:' ]; then + itemValueSize="$( getFileSize "${itemValue:15}" )" + echo -E "${itemKey}=data: $( getHumanByteSize "${itemValueSize}" )" + elif [ "${itemValue:0:16}" = 'artworkfilepath:' ]; then + itemValueSize="$( getFileSize "${itemValue##*:}" )" + echo -E "${itemKey}=artwork: $( getHumanByteSize "${itemValueSize}" )" + else + echo -E "${itemKey}=${itemValue}" + fi + done < <( echo "$sortedItemSizes" ) + } | $sedcmd -e 's@\x01@ / @g' > "${tempDir}/common/outputQueue.${pn}" + ;; + + machine) + { + while read foo i; do + itemKey="${itemKeys[$i]}" + itemLowercaseKey="${itemLowercaseKeys[$i]}" + itemValue="${itemValues[$i]}" + p="x${itemLowercaseKey}Y" + if [ "$readOnlyKeys" != "${readOnlyKeys//$p/@}" -o "$newReadOnlyKeys" != "${newReadOnlyKeys//$p/@}" ]; then + roPrefix='ro:' + else + roPrefix='' + fi + if [ "${itemValue:0:15}" = 'binaryfilepath:' ]; then + echo -En "${roPrefix}${itemKey}=data:" + if [ "$OS" = 'Linux' ]; then + base64 -w 0 "${itemValue:15}" + echo + else + # base64 on OS X outputs a trailing new line already + base64 -b 0 "${itemValue:15}" + fi + elif [ "${itemValue:0:16}" = 'artworkfilepath:' ]; then + artworkFilename="${itemValue:16}"; artworkFilename="${artworkFilename%:*}" + echo -En "${roPrefix}${itemKey}=artwork:${artworkFilename}\x00" + if [ "$OS" = 'Linux' ]; then + base64 -w 0 "${itemValue##*:}" + echo + else + # base64 on OS X outputs a trailing new line already + base64 -b 0 "${itemValue##*:}" + fi + else + echo -En "${roPrefix}${itemKey}=" + echo -En "${itemValues[$i]}" | LC_ALL=C tr '\n' '\2' 2>/dev/null + echo + fi + done < <( echo "$sortedItemSizes" ) + } | $sedcmd -e 's@\x01@\\x00@g' -e 's@\x02@\\n@g' > "${tempDir}/common/outputQueue.${pn}" + ;; + + raw) + { + while read foo i; do + itemKey="${itemKeys[$i]}" + itemValue="${itemValues[$i]}" + if [ "${itemValue:0:15}" = 'binaryfilepath:' ]; then + echo -En "${itemKeys[$i]}=" + cat "${itemValue:15}" + elif [ "${itemValue:0:16}" = 'artworkfilepath:' ]; then + artworkFilename="${itemValue:16}"; artworkFilename="${artworkFilename%:*}" + echo -En "${itemKeys[$i]}=${artworkFilename}" + echo -en "\x00" + cat "${itemValue##*:}" + else + echo -E "${itemKeys[$i]}=${itemValue}" + fi + done < <( echo "$sortedItemSizes" ) + } | LC_ALL=C tr '\1' '\0' 2>/dev/null > "${tempDir}/common/outputQueue.${pn}" + ;; + esac +} + +flushOutputQueue () +{ + local oq=0 + + for ((oq=0; oq<nProcesses; oq++)); do + if [ -f "${tempDir}/common/outputQueue.${oq}" ]; then + if [ "$outputMode" = 'colors' ]; then + echo -e "$( cat "${tempDir}/common/outputQueue.${oq}" )" + else + cat "${tempDir}/common/outputQueue.${oq}" + fi + rm -f "${tempDir}/common/outputQueue.${oq}" + fi + done +} + +dumpItems () +{ + local itemValue='' itemValueSize=0 dumpFilePath='' someFieldNotFound=false artworkFilename='' artworkFileExtension='' + + for (( k=0; k<dumpKeysCount; k++ )); do + for ((i=0; i<itemsCount; i++)); do + if [ "${itemLowercaseKeys[$i]}" = "${dumpKeys[$k]}" ]; then + itemValue="${itemValues[$i]}" + if [ "${itemValue:0:15}" = 'binaryfilepath:' ]; then + if [ "${dumpFiles[$k]}" = '-' ]; then + cat "${itemValue:15}" 2>/dev/null + else + cp "${itemValue:15}" "${dumpFiles[$k]}" >/dev/null 2>&1 + fi + elif [ "${itemValue:0:16}" = 'artworkfilepath:' ]; then + if [ "${dumpFiles[$k]}" = '-' ]; then + cat "${itemValue##*:}" 2>/dev/null + else + case "${dumpFiles[$k]}" in + *.) + artworkFilename="${itemValue:16}"; artworkFilename="${artworkFilename%:*}" + case "$artworkFilename" in + *.png|*.PNG) artworkFilename="${dumpFiles[$k]}png" ;; + *.gif|*.GIF) artworkFilename="${dumpFiles[$k]}gif" ;; + *) artworkFilename="${dumpFiles[$k]}jpg" ;; + esac + ;; + *) artworkFilename="${dumpFiles[$k]}" ;; + esac + cp "${itemValue##*:}" "$artworkFilename" >/dev/null 2>&1 + fi + else + if [ "${dumpFiles[$k]}" = '-' ]; then + echo -En "$itemValue" | LC_ALL=C tr '\1' '\0' 2>/dev/null + else + echo -En "$itemValue" | LC_ALL=C tr '\1' '\0' > "${dumpFiles[$k]}" 2>/dev/null + fi + fi + continue 2 + fi + done + printWarning "file \"${file}\" doesn't have a field named \"${dumpKeys[$k]}\"." + someFieldNotFound=true + done + + if [ $someFieldNotFound = true ]; then + return $EX_KO + fi +} + +checkInputFieldValueFormat () +{ + local string="$1" + + if [ "$string" = "${string//=/@}" ]; then + printError "-${o}: format must be FIELDNAME=VALUE." + cleanExit $EX_USAGE + fi +} + +checkInputFieldLength () +{ + local field="$1" + + if [ ${#field} -lt 2 -o ${#field} -gt 255 ]; then + printError "-${o}: length of field name \"$field\" (${#field}) is outside valid boundaries (2-255)" + cleanExit $EX_USAGE + fi +} + +checkInputFieldUnicity () +{ + local p='' key="$1" lowercaseKey="$2" + + p="x${lowercaseKey}Y" + if [ "$newTagString" != "${newTagString//$p/@}" ]; then + printError "-${o}: all tags must be unique (field name \"${key}\" already specified)." + cleanExit $EX_USAGE + fi + newTagString="${newTagString}${p}" +} + +checkInputValueLength () +{ + local field="$1" value="$2" + + if [ ${#value} -lt 1 ]; then + printError "-${o}: value of field \"${field}\" must not be empty." + cleanExit $EX_USAGE + fi +} + +doListAction () +{ + local ec=$EX_OK + + if hasApetag ; then + getApetagSize && + getApetagOffset && + getItemsOffset && + getNumberOfItems && + getItems && + listItems ; ec=$? + else + printWarning "file \"${file}\" does not contain an APEv2 tag." + fi + return $ec +} + +doDumpAction () +{ + local ec=$EX_OK + + if hasApetag ; then + getApetagSize && + getApetagOffset && + getItemsOffset && + getNumberOfItems && + getItems && + dumpItems ; ec=$? + else + printWarning "file \"${file}\" does not contain an APEv2 tag." + ec=$EX_KO + fi + return $ec +} + +doCopyAction () +{ + local ec=$EX_OK + + if hasApetag ; then + getApetagSize && + getApetagOffset && + removeApetag; ec=$? + fi + + if [ $ec -eq $EX_OK ]; then + if [ "$copyApetagLocation" = 'bottom' ]; then + cat "${tempDir}/common/apecopy" >> "$file" 2>/dev/null || ec=$EX_KO + elif [ "$copyApetagLocation" = 'top' ]; then + tempFile="${tempProcessDir}/tempfile" + cp "${tempDir}/common/apecopy" "$tempFile" >/dev/null 2>&1 && + cat "$file" >> "$tempFile" >/dev/null 2>&1 && + rm -f "$file" >/dev/null 2>&1 && + mv -f "$tempFile" "$file" >/dev/null 2>&1 || ec=$EX_KO + else + ec=$EX_SOFTWARE + fi + else + ec=$EX_SOFTWARE + fi + + if [ $ec -eq $EX_SOFTWARE ]; then + printError "internal software error." + fi + return $ec +} + +doWriteAction () +{ + local ec=$EX_OK artworkFilename + + if [ $removeAllTags = true ]; then + if hasApetag ; then + getApetagSize && + getApetagOffset && + removeApetag; ec=$? + if [ $newTagsCount = 0 ]; then + return $ec + fi + elif [ $newTagsCount = 0 ]; then + return $ec + fi + + itemsCount=0 allKeys='' + unset itemKeys itemLowercaseKeys itemValues + declare -a itemKeys=() + declare -a itemLowercaseKeys=() + declare -a itemValues=() + fi + + if [ $ec -eq $EX_OK ]; then + if hasApetag ; then + getApetagSize && + getApetagOffset && + getItemsOffset && + getNumberOfItems && + getItems && + removeItems && + removeApetag ; ec=$? + fi + + if [ $ec -eq $EX_OK ]; then + addNewItems; ec=$? + if [ $itemsCount -gt 0 ]; then + writeNewApetag && + listItems; ec=$? + fi + fi + fi + + return $ec +} + +doAction () +{ + local ec=$EX_OK + + initFileVars + case "$action" in + list) doListAction ; ec=$? ;; + dump) doDumpAction ; ec=$? ;; + copy) doCopyAction ; ec=$? ;; + write) doWriteAction ; ec=$? ;; + esac + return $ec +} + +waitForJobs () +{ + local jn ec=$EX_OK jec + + for jn in $( jobs -p ); do + wait $jn ; jec=$? + if [ $jec -ne $EX_OK ]; then + ec=$jec + fi + done + return $ec +} + +# main() ====================================================================== + +me='APEv2' +calledAs="${0##*/}" +VERSION='1.0.2' + +EX_OK=0 # successful termination +EX_KO=1 # unsuccessful termination +EX_USAGE=64 # command line usage error +EX_DATAERR=65 # data format error +EX_NOINPUT=66 # cannot open input +EX_NOUSER=67 # addressee unknown +EX_NOHOST=68 # host name unknown +EX_UNAVAILABLE=69 # service unavailable +EX_SOFTWARE=70 # internal software error +EX_OSERR=71 # system error (e.g., can't fork) +EX_OSFILE=72 # critical OS file missing +EX_CANTCREAT=73 # can't create (user) output file +EX_IOERR=74 # input/output error +EX_TEMPFAIL=75 # temp failure; user is invited to retry +EX_PROTOCOL=76 # remote error in protocol +EX_NOPERM=77 # permission denied +EX_CONFIG=78 # configuration error +EX_INTERRUPT=143 # user interruption (Ctrl+C) + +if [ -n "$LC_ALL" ]; then + unset LC_ALL +fi +export LANG='en_US.UTF-8' +export LC_NUMERIC='C' + +sedcmd='gsed' datecmd='date' gnudate=false +if which 'uname' >/dev/null 2>&1 ; then + OS="$( uname -s )" + if [ "$OS" = 'Linux' ]; then + sedcmd='sed' datecmd='date' gnudate=true + else + if which 'gdate' >/dev/null 2>&1; then + datecmd='gdate' gnudate=true + else + datecmd='date' gnudate=false + fi + fi +fi + +checkBinaries + +for signal in INT TERM ABRT PIPE; do + trap "cleanAbort" $signal +done + +action='' outputMode='colors' removeAllTags=false newTagString='' debug=false newReadOnlyKeys='' +newTagsCount=0 deletedKeysCount=0 dumpKeysCount=0 copyFromFile='' +declare -a newKeys=() +declare -a newLowercaseKeys=() +declare -a newValues=() +declare -a newValues=() +declare -a deletedKeys=() +declare -a deletedLowercaseKeys=() +declare -a dumpKeys=() +declare -a dumpFiles=() + +setColors # initiate default coloring + +# C, z and Z must be parsed first in order to disable coloring before everything else +while getopts 'CzZDhVit:r:Ra:b:d:f:' o ; do + case $o in + C) outputMode='human'; setColors ;; + D) debug=true ;; + z) outputMode='machine'; setColors ;; + Z) outputMode='raw'; setColors ;; + R) removeAllTags=true; if [ -z "$action" ]; then action='write'; fi ;; + + f) + copyFromFile="$OPTARG" + if [ ! -e "$copyFromFile" ]; then + printError "-${o}: no such file (\"${copyFromFile}\")." + cleanExit $EX_NOINPUT + elif [ ! -f "$copyFromFile" ]; then + printError "-${o}: \"${copyFromFile}\" is not a regular file." + cleanExit $EX_NOINPUT + elif [ ! -r "$copyFromFile" ]; then + printError "-${o}: file \"${copyFromFile}\" is not readable (permission denied)." + cleanExit $EX_NOPERM + fi + if [ -z "$action" ]; then action='copy'; fi + ;; + + i) + if [ -z "$action" ]; then action='write'; fi + + while read -r line; do + checkInputFieldValueFormat "$line" + newKey="${line%%=*}" + checkInputFieldLength "$newKey" + newLowercaseKey="$( echo -En "$newKey" | tr '[:upper:]' '[:lower:]' )" + if [ "${newKey:0:3}" = 'ro:' ]; then + newKey="${newKey:3}" + newLowercaseKey="${newLowercaseKey:3}" + newReadOnlyKeys="x${newLowercaseKey}Y" + fi + checkInputFieldUnicity "$newKey" "$newLowercaseKey" + + newValue="${line#*=}" + checkInputValueLength "$newKey" "$newValue" + + newKeys[$newTagsCount]="$newKey" + newLowercaseKeys[$newTagsCount]="$newLowercaseKey" + if [ "${newValue:0:5}" = 'data:' ]; then + if [ "${newLowercaseKey:0:9}" = 'cover art' ]; then + printError "-${o}: bad syntax for cover art item (use \"artwork:filename\x00:base64\")." + cleanExit $EX_USAGE + fi + createTempDir + if [ "$OS" = 'Linux' ]; then + echo -En "${newValue:5}" | base64 -d > "${tempDir}/common/newBinary.${newTagsCount}" 2>/dev/null + else + echo -En "${newValue:5}" | base64 -D -o "${tempDir}/common/newBinary.${newTagsCount}" >/dev/null 2>&1 + fi + newValues[$newTagsCount]="binaryfilepath:${tempDir}/common/newBinary.${newTagsCount}" + elif [ "${newValue:0:8}" = 'artwork:' ]; then + if [ "${newLowercaseKey:0:9}" != 'cover art' ]; then + printError "-${o}: cover art field name must begin with \"Cover Art\"." + cleanExit $EX_USAGE + fi + createTempDir + artworkFilename="${newValue:8}"; artworkFilename="${artworkFilename%:*}" + artworkFilename="$( echo -en "${newValue:8}" | LC_ALL=C tr '\0' '\1' 2>/dev/null | LC_ALL=C cut -d "$( echo -en "\x01" )" -f 1 2>/dev/null )" + if [ "$OS" = 'Linux' ]; then + echo -en "${newValue:8}" | LC_ALL=C tr '\0' '\1' 2>/dev/null | LC_ALL=C cut -d "$( echo -en "\x01" )" -f 2 2>/dev/null | base64 -d > "${tempDir}/common/newArtwork.${newTagsCount}" 2>/dev/null + else + echo -en "${newValue:8}" | LC_ALL=C tr '\0' '\1' 2>/dev/null | LC_ALL=C cut -d "$( echo -en "\x01" )" -f 2 2>/dev/null | base64 -D -o "${tempDir}/common/newArtwork.${newTagsCount}" 2>/dev/null + fi + newValues[$newTagsCount]="artworkfilepath:${artworkFilename}:${tempDir}/common/newArtwork.${newTagsCount}" + else + newValues[$newTagsCount]="$( echo -En "$newValue" | $sedcmd -e 's@\\x00@\x01@g' -e 's@\\n@\n@g' )" + fi + ((newTagsCount++)) + done <&0 + ;; + + a|b) + if [ -z "$action" ]; then action='write'; fi + + checkInputFieldValueFormat "$OPTARG" + + newKey="${OPTARG%%=*}" + checkInputFieldLength "$newKey" + newLowercaseKey="$( echo -En "$newKey" | tr '[:upper:]' '[:lower:]' )" + if [ "${newKey:0:3}" = 'ro:' ]; then + newKey="${newKey:3}" + newLowercaseKey="${newLowercaseKey:3}" + newReadOnlyKeys="x${newLowercaseKey}Y" + fi + checkInputFieldUnicity "$newKey" "$newLowercaseKey" + + if [ "$o" = 'a' -a "${newLowercaseKey:0:9}" != 'cover art' ]; then + printError "-${o}: item field name must begin with \"Cover Art\"." + cleanExit $EX_USAGE + elif [ "$o" = 'b' -a "${newLowercaseKey:0:9}" = 'cover art' ]; then + printError "-${o}: item field name must NOT begin with \"Cover Art\". To attach cover artwork, use -a instead." + cleanExit $EX_USAGE + fi + + newValue="${OPTARG#*=}" + checkInputValueLength "$newKey" "$newValue" + if [ ! -e "$newValue" ]; then + printError "-${o}: no such file (\"${newValue}\")." + cleanExit $EX_NOINPUT + elif [ ! -f "$newValue" ]; then + printError "-${o}: \"${newValue}\" is not a regular file." + cleanExit $EX_NOINPUT + elif [ ! -r "$newValue" ]; then + printError "-${o}: file \"${newValue}\" is not readable (permission denied)." + cleanExit $EX_NOPERM + fi + + if [ "$o" = 'a' ]; then + case "$newValue" in + *.jpeg|*.jpg|*.JPEG|*.JPG) artworkFileExtension='jpg' ;; + *.png|*.PNG) artworkFileExtension='png' ;; + *.gif|*.GIF) artworkFileExtension='gif' ;; + *) printError "-${o}: image file extension must be .jpg, .jpeg, .png or .gif." ; cleanExit $EX_DATAERR ;; + esac + fi + + newKeys[$newTagsCount]="$newKey" + newLowercaseKeys[$newTagsCount]="$newLowercaseKey" + if [ "$o" = 'a' ]; then + case "$newLowercaseKey" in + 'cover art (front)') newValueFilename="cover.${artworkFileExtension}" ;; + 'cover art (back)') newValueFilename="back.${artworkFileExtension}" ;; + *) newValueFilename="other.${artworkFileExtension}" ;; + esac + newValues[$newTagsCount]="artworkfilepath:${newValueFilename}:${newValue}" + else + newValues[$newTagsCount]="binaryfilepath:${newValue}" + fi + ((newTagsCount++)) + ;; + + d) + if [ -z "$action" ]; then action='dump'; fi + + checkInputFieldValueFormat "$OPTARG" + + dumpKey="${OPTARG%%=*}" + checkInputFieldLength "$dumpKey" + dumpKey="$( echo -En "$dumpKey" | tr '[:upper:]' '[:lower:]' )" + + dumpFile="${OPTARG#*=}" + if [ ${#dumpFile} -lt 1 ]; then + printError "-${o}: you must provide a valid file path." + cleanExit $EX_USAGE + fi + + dumpKeys[$dumpKeysCount]="$dumpKey" + dumpFiles[$dumpKeysCount]="$dumpFile" + ((dumpKeysCount++)) + ;; + + t) + if [ -z "$action" ]; then action='write'; fi + + checkInputFieldValueFormat "$OPTARG" + newKey="${OPTARG%%=*}" + checkInputFieldLength "$newKey" + newLowercaseKey="$( echo -En "$newKey" | tr '[:upper:]' '[:lower:]' )" + if [ "${newKey:0:3}" = 'ro:' ]; then + newKey="${newKey:3}" + newLowercaseKey="${newLowercaseKey:3}" + newReadOnlyKeys="x${newLowercaseKey}Y" + fi + checkInputFieldUnicity "$newKey" "$newLowercaseKey" + + newValue="${OPTARG#*=}" + checkInputValueLength "$newKey" "$newValue" + + newKeys[$newTagsCount]="$newKey" + newLowercaseKeys[$newTagsCount]="$newLowercaseKey" + newValues[$newTagsCount]="$( echo -En "$newValue" | $sedcmd -e 's@\\x00@\x01@g' -e 's@\\n@\n@g' )" + ((newTagsCount++)) + ;; + + r) + if [ -z "$action" ]; then action='write'; fi + checkInputFieldLength "$OPTARG" + deletedKeys[$deletedKeysCount]="$OPTARG" + deletedLowercaseKeys[$deletedKeysCount]="$( echo -En "$OPTARG" | tr '[:upper:]' '[:lower:]' )" + ((deletedKeysCount++)) + ;; + + h) printUsage ; cleanExit $EX_OK ;; + + V) echo "$me $VERSION" ; cleanExit $EX_OK ;; + + *) printError "try '$me -h' for more information." ; cleanExit $EX_USAGE ;; + esac +done +if [ -z "$action" ]; then action='list'; fi + +shift $(( OPTIND - 1 )) +if [ $# -lt 1 ]; then + printWarning "no files specified on the command line; try '$me -h' for more information." + cleanExit $EX_USAGE +fi + +gotFileErrors=false +for f in "$@"; do + if [ ! -e "$f" ]; then + printError "file \"${f}\" doesn't exist." + gotFileErrors=true + elif [ ! -f "$f" ]; then + printError "\"${f}\" is not a regular file." + gotFileErrors=true + elif [ ! -r "$f" ]; then + printError "cannot open \"${f}\" for reading (permission denied)." + gotFileErrors=true + elif [ "$action" != 'list' -a ! -w "$f" ]; then + printError "cannot open \"${f}\" for writing (permission denied)." + gotFileErrors=true + fi +done +if [ $gotFileErrors = true ]; then + cleanExit $EX_NOINPUT +fi + +createTempDir + +nFiles=$# +declare -a inputFiles=("$@") + +if [ $debug = true ]; then + nProcesses=1 +else + if which 'nproc' >/dev/null 2>&1; then # GNU Coreutils installed + nProcesses="$( nproc 2>/dev/null )" + elif which 'gnproc' >/dev/null 2>&1; then # GNU Coreutils installed on OS Ⅹ + nProcesses="$( gnproc 2>/dev/null )" + elif [ -e '/proc/cpuinfo' ]; then # Linux + nProcesses="$( grep -cF 'cpu MHz' /proc/cpuinfo 2>/dev/null )" + elif [ "$OS" = 'Darwin' ]; then # OS Ⅹ + # Many thanks to Tobias Link for helping me port APEv2 to OS Ⅹ + nProcesses="$( system_profiler -detailLevel full SPHardwareDataType 2>/dev/null | grep -F 'Total Number of Cores:' 2>/dev/null | cut -d ':' -f 2 2>/dev/null | tr -d ' ' 2>/dev/null )" + else + nProcesses=4 + fi + if [ $nFiles -lt $nProcesses ]; then + nProcesses=$nFiles + fi +fi + +ec=$EX_OK +if [ "$action" = 'copy' ]; then + file="$copyFromFile" + initFileVars + pn=0 + createTempProcessDir + if hasApetag ; then + getApetagSize && + getApetagOffset; ec=$? + if [ $ec -eq $EX_OK ]; then + if [ "$apetagLocation" = 'bottom' ]; then + if [ $hasHeader = true ]; then + tail -c $(( 32 + tagSize )) "$copyFromFile" > "${tempDir}/common/apecopy" 2>/dev/null ; ec=$? + else + tail -c $tagSize "$copyFromFile" > "${tempDir}/common/apecopy" 2>/dev/null ; ec=$? + fi + elif [ "$apetagLocation" = 'top' ]; then + head -c $(( 32 + tagSize )) "$copyFromFile" > "${tempDir}/common/apecopy" 2>/dev/null ; ec=$? + else + ec=$EX_SOFTWARE + fi + if [ $ec -ne $EX_OK ]; then + printError "internal software error." + cleanExit $EX_SOFTWARE + fi + copyApetagLocation="$apetagLocation" + + getItemsOffset && + getNumberOfItems && + getItems && + listItems; ec=$? + if [ $ec -ne $EX_OK ]; then + cleanExit $ec + fi + fi + else + printWarning "file \"${file}\" does not contain an APEv2 tag." + cleanExit $EX_OK + fi +elif [ "$action" = 'write' ]; then + for ((i=0; i<newTagsCount; i++)); do + if [ "${newValues[$i]:0:5}" = 'data:' ]; then + if [ "$OS" = 'Linux' ]; then + echo -En "${newValues[$i]:5}" | base64 -d > "${tempDir}/common/newBinary.${i}" 2>/dev/null + else + echo -En "${newValues[$i]:5}" | base64 -D -o "${tempDir}/common/newBinary.${i}" >/dev/null 2>&1 + fi + newValues[$i]="binaryfilepath:${tempDir}/common/newBinary.${i}" + elif [ "${newValues[$i]:0:8}" = 'artwork:' ]; then + artworkFilename="${newValues[$i]:8}"; artworkFilename="${artworkFilename%:*}" + artworkFilename="$( echo -en "${newValues[$i]:8}" | LC_ALL=C tr '\0' '\1' 2>/dev/null | LC_ALL=C cut -d "$( echo -en "\x01" )" -f 1 2>/dev/null )" + if [ "$OS" = 'Linux' ]; then + echo -en "${newValues[$i]:8}" | LC_ALL=C tr '\0' '\1' 2>/dev/null | LC_ALL=C cut -d "$( echo -en "\x01" )" -f 2 2>/dev/null | base64 -d > "${tempDir}/common/newArtwork.${i}" 2>/dev/null + else + echo -en "${newValues[$i]:8}" | LC_ALL=C tr '\0' '\1' 2>/dev/null | LC_ALL=C cut -d "$( echo -en "\x01" )" -f 2 2>/dev/null | base64 -D -o "${tempDir}/common/newArtwork.${i}" 2>/dev/null + fi + newValues[$i]="artworkfilepath:${artworkFilename}:${tempDir}/common/newArtwork.${i}" + fi + done +fi + +ec=$EX_OK +if [ $nProcesses -eq 1 ]; then + pn=0 + for ((fn=0; fn<nFiles; fn++)); do + file="${inputFiles[$fn]}" + createTempProcessDir + doAction; jec=$? + if [ $jec -ne $EX_OK ]; then ec=$jec; fi + flushOutputQueue + done +else + for ((fn=0, pn=0; fn<nFiles; fn++, pn++)); do + createTempProcessDir + if [ $pn -lt $nProcesses ]; then + file="${inputFiles[$fn]}" + doAction & + else + waitForJobs ; jec=$? + if [ $jec -ne $EX_OK ]; then ec=$jec; fi + flushOutputQueue + pn=0 + file="${inputFiles[$fn]}" + createTempProcessDir + doAction & + fi + done + + waitForJobs ; jec=$? + if [ $jec -ne $EX_OK ]; then ec=$jec; fi + flushOutputQueue +fi +cleanExit $ec
View file
caudec-1.6.2.tar.gz/CHANGES -> caudec-1.7.5.tar.gz/CHANGES
Changed
@@ -1,7 +1,72 @@ +v1.7.5: WavPack lossy: don't remove Replaygain tags after transcoding + APEv2: don't store duplicate value, transform duplicate fields into unique multi-value fields + APEv2: fix handling of tags that start with '(' + Opus: added support for embedded artwork +v1.7.4: by default, set the number of processes to the number of logical CPU cores if equal to 4 or lower, or to the number of physical CPU cores + when applying Replaygain, make additional -G parameter act as a preamp value +v1.7.3: new -DDD parameter: delete source file upon successful transcoding (USE WITH CARE) + new sampling rates and presets: 352800 Hz, 384000 Hz, -r dxd + replaygain bugfix: check for multichannel audio instead of strictly mono or stereo + bugfix: sanitize track and disc number metadata +v1.7.2: added support for WavPack 4.70.0 + updated all URLs to http://caudec.net/ +v1.7.1: added peak normalization with '-G albumpeak' (by album) and '-G trackpeak' (by track) + use TAK multithreading when it makes sense + replaced apetag optional dependency with APEv2, which supports previously missing features: NULL separated lists, embedded artwork and binary data + added support for extracting and importing APEv2 artwork + significantly sped up extracting embedded artwork from FLAC files + support parsing version number of lossyWAV 1.3.1 and up + added support for PERFORMER metadata + new 'ID3Padding' configuration parameter: size in bytes of padding to add to ID3v2 tags (default: 512 bytes) +v1.7.0: new -2 parameter: convert mono and multichannel audio to stereo (upmix / downmix), using proper channel mappings + '-r cd' preset now implies '-b 16 -r 44100 -2' (includes automatic conversion to stereo where applicable) + new -z parameter: produce machine-parsable output + added support for AIFF and CAF (Core Audio File) + directories are now accepted as input: caudec will automatically find all eligible files within those directories (use in conjunction with 'ignoreUnsupportedFiles') + new 'ignoreUnsupportedFiles' configuration parameter: set to true to prevent caudec from aborting when some of the input files are unsupported (false by default) + -t (testing file integrity) can now be used with all lossless codecs, using hash metadata (-H) + added SHA256 and SHA512 hashing + renamed 'CRC' to 'CRC32'; backward compatibility with files tagged with 'CRC=' is maintained + check internal MD5 hash whenever possible, to detect potential codec bugs + new 'enableColors' configuration parameter: true by default, set to 'false' to disable coloring of human-readable output + new 'brightColors' configuration parameter: true by default, set to 'false' to use darker colors (enableColors needs to be true) + -G parameter (apply gain) and -S parameter (compute Soundcheck data) can now take an arbitrary value (signed number from -99.99 to +99.99) + significantly sped up computing album gain + check the output of replaygain tools more thoroughly + caudec -g: process multiline metadata + various fixes when computing Replaygain with Ogg Vorbis, MP3 and AAC files + new dependencies: SoX (mandatory) and ffmpeg (ALAC, AAC and Monkey's Audio) + removed shntool and alac dependencies + let SoX output Microsoft-compliant WAV files (fixes some compatibility issues with multichannel files) + added support for eyeD3 version 0.6.x and 0.7.x; the former is recommended however, as the latter is broken + workaround for broken eyeD3 0.7.1: set front cover artwork in MP3 files, if available + better handling of Windows binaries with Wine; search home directory to find Wine user directories automatically + don't store empty hash metadata, when hashing fails + better detection of the number of CPU cores + improved example command line that caudec outputs when fed too many files + removed confusing 'm4a' codec name (with -c) + fixed lossyTAK regression + lossyWV: use --merge-blocks (better compression) + better ramdisk space management (various fixes and improvements) + new ramdisk space usage monitoring function: print a warning if the estimation was too low + better comparison of version numbers when checking for new versions online + only strip out ENCODING metadata field when transcoding + more compact display of statistics in human-readable output: removed unnecessary values + many, many more fixes +v1.6.4: -o/O/P may now be specified after each -c parameter, in order to set per-codec destination directories (useful when transcoding to several codecs at once) + allow simultaneous usage of codecs with the same file extension (e.g. ALAC and AAC; just make sure to specify per-codec destination directories) + reduced ramdisk space requirements, especially with lossyWAV +v1.6.3: new -k parameter: keep existing destination files (don't overwrite them) + new -K parameter: keep existing destination files if they're newer than their source + fixed OS Ⅹ compatibility (GNU version of sed required: gsed) + fixed: -P DIR -c lossyWAV + fixed APEv2 to APEv2 tagging regression + fixed division by zero bug; use gdate on OS Ⅹ if available + removed fgrep and readlink dependencies, consolidated various command lines v1.6.2: new -u parameter: check for new versions new 'sendHardwareDetails' configuration parameter (defaults to false): include CPU and RAM information in caudec's User Agent string when checking for new versions added caudec's User Agent string in the output of 'caudec -h' (test with 'sendHardwareDetails' set to true and false) - added CBR (constant) and CVBR (constrained) bitrate modes and parameters (-b/-B) for Opus (see important note: http://caudec.outpost.fr/documentation/versions/#opus) + added CBR (constant) and CVBR (constrained) bitrate modes and parameters (-b/-B) for Opus (see important note: http://caudec.net/documentation/versions/#opus) added 'tagCompressionSetting' configuration parameter: store compression setting in metadata (defaults to false) v1.6.1: updated all files with new URL: http://caudec.outpost.fr/ v1.6.0: new feature: tag whitelisting and blacklisting (tagWhitelist='' and tagBlacklist='' in caudecrc) @@ -54,7 +119,7 @@ precise evaluation of ramdisk space requirements with TAK input files v1.4.1: fixed bug that made other instances stall when aborting one instance install.sh: autodetect path of current caudec installation -v1.4.0: now runs on Mac OS X (tested on Lion) +v1.4.0: now runs on OS Ⅹ (tested on OS Ⅹ Lion) smart handling of concurrent instances better detection of ramdisks don't abort if no ramdisk is available
View file
caudec-1.7.5.tar.gz/LICENSE-APEv2
Added
@@ -0,0 +1,373 @@ +Mozilla Public License Version 2.0 +================================== + +1. Definitions +-------------- + +1.1. "Contributor" + means each individual or legal entity that creates, contributes to + the creation of, or owns Covered Software. + +1.2. "Contributor Version" + means the combination of the Contributions of others (if any) used + by a Contributor and that particular Contributor's Contribution. + +1.3. "Contribution" + means Covered Software of a particular Contributor. + +1.4. "Covered Software" + means Source Code Form to which the initial Contributor has attached + the notice in Exhibit A, the Executable Form of such Source Code + Form, and Modifications of such Source Code Form, in each case + including portions thereof. + +1.5. "Incompatible With Secondary Licenses" + means + + (a) that the initial Contributor has attached the notice described + in Exhibit B to the Covered Software; or + + (b) that the Covered Software was made available under the terms of + version 1.1 or earlier of the License, but not also under the + terms of a Secondary License. + +1.6. "Executable Form" + means any form of the work other than Source Code Form. + +1.7. "Larger Work" + means a work that combines Covered Software with other material, in + a separate file or files, that is not Covered Software. + +1.8. "License" + means this document. + +1.9. "Licensable" + means having the right to grant, to the maximum extent possible, + whether at the time of the initial grant or subsequently, any and + all of the rights conveyed by this License. + +1.10. "Modifications" + means any of the following: + + (a) any file in Source Code Form that results from an addition to, + deletion from, or modification of the contents of Covered + Software; or + + (b) any new file in Source Code Form that contains any Covered + Software. + +1.11. "Patent Claims" of a Contributor + means any patent claim(s), including without limitation, method, + process, and apparatus claims, in any patent Licensable by such + Contributor that would be infringed, but for the grant of the + License, by the making, using, selling, offering for sale, having + made, import, or transfer of either its Contributions or its + Contributor Version. + +1.12. "Secondary License" + means either the GNU General Public License, Version 2.0, the GNU + Lesser General Public License, Version 2.1, the GNU Affero General + Public License, Version 3.0, or any later versions of those + licenses. + +1.13. "Source Code Form" + means the form of the work preferred for making modifications. + +1.14. "You" (or "Your") + means an individual or a legal entity exercising rights under this + License. For legal entities, "You" includes any entity that + controls, is controlled by, or is under common control with You. For + purposes of this definition, "control" means (a) the power, direct + or indirect, to cause the direction or management of such entity, + whether by contract or otherwise, or (b) ownership of more than + fifty percent (50%) of the outstanding shares or beneficial + ownership of such entity. + +2. License Grants and Conditions +-------------------------------- + +2.1. Grants + +Each Contributor hereby grants You a world-wide, royalty-free, +non-exclusive license: + +(a) under intellectual property rights (other than patent or trademark) + Licensable by such Contributor to use, reproduce, make available, + modify, display, perform, distribute, and otherwise exploit its + Contributions, either on an unmodified basis, with Modifications, or + as part of a Larger Work; and + +(b) under Patent Claims of such Contributor to make, use, sell, offer + for sale, have made, import, and otherwise transfer either its + Contributions or its Contributor Version. + +2.2. Effective Date + +The licenses granted in Section 2.1 with respect to any Contribution +become effective for each Contribution on the date the Contributor first +distributes such Contribution. + +2.3. Limitations on Grant Scope + +The licenses granted in this Section 2 are the only rights granted under +this License. No additional rights or licenses will be implied from the +distribution or licensing of Covered Software under this License. +Notwithstanding Section 2.1(b) above, no patent license is granted by a +Contributor: + +(a) for any code that a Contributor has removed from Covered Software; + or + +(b) for infringements caused by: (i) Your and any other third party's + modifications of Covered Software, or (ii) the combination of its + Contributions with other software (except as part of its Contributor + Version); or + +(c) under Patent Claims infringed by Covered Software in the absence of + its Contributions. + +This License does not grant any rights in the trademarks, service marks, +or logos of any Contributor (except as may be necessary to comply with +the notice requirements in Section 3.4). + +2.4. Subsequent Licenses + +No Contributor makes additional grants as a result of Your choice to +distribute the Covered Software under a subsequent version of this +License (see Section 10.2) or under the terms of a Secondary License (if +permitted under the terms of Section 3.3). + +2.5. Representation + +Each Contributor represents that the Contributor believes its +Contributions are its original creation(s) or it has sufficient rights +to grant the rights to its Contributions conveyed by this License. + +2.6. Fair Use + +This License is not intended to limit any rights You have under +applicable copyright doctrines of fair use, fair dealing, or other +equivalents. + +2.7. Conditions + +Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted +in Section 2.1. + +3. Responsibilities +------------------- + +3.1. Distribution of Source Form + +All distribution of Covered Software in Source Code Form, including any +Modifications that You create or to which You contribute, must be under +the terms of this License. You must inform recipients that the Source +Code Form of the Covered Software is governed by the terms of this +License, and how they can obtain a copy of this License. You may not +attempt to alter or restrict the recipients' rights in the Source Code +Form. + +3.2. Distribution of Executable Form + +If You distribute Covered Software in Executable Form then: + +(a) such Covered Software must also be made available in Source Code + Form, as described in Section 3.1, and You must inform recipients of + the Executable Form how they can obtain a copy of such Source Code + Form by reasonable means in a timely manner, at a charge no more + than the cost of distribution to the recipient; and + +(b) You may distribute such Executable Form under the terms of this + License, or sublicense it under different terms, provided that the + license for the Executable Form does not attempt to limit or alter + the recipients' rights in the Source Code Form under this License. + +3.3. Distribution of a Larger Work + +You may create and distribute a Larger Work under terms of Your choice, +provided that You also comply with the requirements of this License for +the Covered Software. If the Larger Work is a combination of Covered +Software with a work governed by one or more Secondary Licenses, and the +Covered Software is not Incompatible With Secondary Licenses, this +License permits You to additionally distribute such Covered Software +under the terms of such Secondary License(s), so that the recipient of +the Larger Work may, at their option, further distribute the Covered +Software under the terms of either this License or such Secondary +License(s). + +3.4. Notices + +You may not remove or alter the substance of any license notices +(including copyright notices, patent notices, disclaimers of warranty, +or limitations of liability) contained within the Source Code Form of +the Covered Software, except that You may alter any license notices to +the extent required to remedy known factual inaccuracies. + +3.5. Application of Additional Terms + +You may choose to offer, and to charge a fee for, warranty, support, +indemnity or liability obligations to one or more recipients of Covered +Software. However, You may do so only on Your own behalf, and not on +behalf of any Contributor. You must make it absolutely clear that any +such warranty, support, indemnity, or liability obligation is offered by +You alone, and You hereby agree to indemnify every Contributor for any +liability incurred by such Contributor as a result of warranty, support, +indemnity or liability terms You offer. You may include additional +disclaimers of warranty and limitations of liability specific to any +jurisdiction. + +4. Inability to Comply Due to Statute or Regulation +--------------------------------------------------- + +If it is impossible for You to comply with any of the terms of this +License with respect to some or all of the Covered Software due to +statute, judicial order, or regulation then You must: (a) comply with +the terms of this License to the maximum extent possible; and (b) +describe the limitations and the code they affect. Such description must +be placed in a text file included with all distributions of the Covered +Software under this License. Except to the extent prohibited by statute +or regulation, such description must be sufficiently detailed for a +recipient of ordinary skill to be able to understand it. + +5. Termination +-------------- + +5.1. The rights granted under this License will terminate automatically +if You fail to comply with any of its terms. However, if You become +compliant, then the rights granted under this License from a particular +Contributor are reinstated (a) provisionally, unless and until such +Contributor explicitly and finally terminates Your grants, and (b) on an +ongoing basis, if such Contributor fails to notify You of the +non-compliance by some reasonable means prior to 60 days after You have +come back into compliance. Moreover, Your grants from a particular +Contributor are reinstated on an ongoing basis if such Contributor +notifies You of the non-compliance by some reasonable means, this is the +first time You have received notice of non-compliance with this License +from such Contributor, and You become compliant prior to 30 days after +Your receipt of the notice. + +5.2. If You initiate litigation against any entity by asserting a patent +infringement claim (excluding declaratory judgment actions, +counter-claims, and cross-claims) alleging that a Contributor Version +directly or indirectly infringes any patent, then the rights granted to +You by any and all Contributors for the Covered Software under Section +2.1 of this License shall terminate. + +5.3. In the event of termination under Sections 5.1 or 5.2 above, all +end user license agreements (excluding distributors and resellers) which +have been validly granted by You or Your distributors under this License +prior to termination shall survive termination. + +************************************************************************ +* * +* 6. Disclaimer of Warranty * +* ------------------------- * +* * +* Covered Software is provided under this License on an "as is" * +* basis, without warranty of any kind, either expressed, implied, or * +* statutory, including, without limitation, warranties that the * +* Covered Software is free of defects, merchantable, fit for a * +* particular purpose or non-infringing. The entire risk as to the * +* quality and performance of the Covered Software is with You. * +* Should any Covered Software prove defective in any respect, You * +* (not any Contributor) assume the cost of any necessary servicing, * +* repair, or correction. This disclaimer of warranty constitutes an * +* essential part of this License. No use of any Covered Software is * +* authorized under this License except under this disclaimer. * +* * +************************************************************************ + +************************************************************************ +* * +* 7. Limitation of Liability * +* -------------------------- * +* * +* Under no circumstances and under no legal theory, whether tort * +* (including negligence), contract, or otherwise, shall any * +* Contributor, or anyone who distributes Covered Software as * +* permitted above, be liable to You for any direct, indirect, * +* special, incidental, or consequential damages of any character * +* including, without limitation, damages for lost profits, loss of * +* goodwill, work stoppage, computer failure or malfunction, or any * +* and all other commercial damages or losses, even if such party * +* shall have been informed of the possibility of such damages. This * +* limitation of liability shall not apply to liability for death or * +* personal injury resulting from such party's negligence to the * +* extent applicable law prohibits such limitation. Some * +* jurisdictions do not allow the exclusion or limitation of * +* incidental or consequential damages, so this exclusion and * +* limitation may not apply to You. * +* * +************************************************************************ + +8. Litigation +------------- + +Any litigation relating to this License may be brought only in the +courts of a jurisdiction where the defendant maintains its principal +place of business and such litigation shall be governed by laws of that +jurisdiction, without reference to its conflict-of-law provisions. +Nothing in this Section shall prevent a party's ability to bring +cross-claims or counter-claims. + +9. Miscellaneous +---------------- + +This License represents the complete agreement concerning the subject +matter hereof. If any provision of this License is held to be +unenforceable, such provision shall be reformed only to the extent +necessary to make it enforceable. Any law or regulation which provides +that the language of a contract shall be construed against the drafter +shall not be used to construe this License against a Contributor. + +10. Versions of the License +--------------------------- + +10.1. New Versions + +Mozilla Foundation is the license steward. Except as provided in Section +10.3, no one other than the license steward has the right to modify or +publish new versions of this License. Each version will be given a +distinguishing version number. + +10.2. Effect of New Versions + +You may distribute the Covered Software under the terms of the version +of the License under which You originally received the Covered Software, +or under the terms of any subsequent version published by the license +steward. + +10.3. Modified Versions + +If you create software not governed by this License, and you want to +create a new license for such software, you may create and use a +modified version of this License if you rename the license and remove +any references to the name of the license steward (except to note that +such modified license differs from this License). + +10.4. Distributing Source Code Form that is Incompatible With Secondary +Licenses + +If You choose to distribute Source Code Form that is Incompatible With +Secondary Licenses under the terms of this version of the License, the +notice described in Exhibit B of this License must be attached. + +Exhibit A - Source Code Form License Notice +------------------------------------------- + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. + +If it is not possible or desirable to put the notice in a particular +file, then You may include the notice in a location (such as a LICENSE +file in a relevant directory) where a recipient would be likely to look +for such a notice. + +You may add additional accurate notices of copyright ownership. + +Exhibit B - "Incompatible With Secondary Licenses" Notice +--------------------------------------------------------- + + This Source Code Form is "Incompatible With Secondary Licenses", as + defined by the Mozilla Public License, v. 2.0.
View file
caudec-1.7.5.tar.gz/LICENSE-caudec
Changed
(renamed from LICENSE)
View file
caudec-1.6.2.tar.gz/README -> caudec-1.7.5.tar.gz/README
Changed
@@ -1,8 +1,8 @@ -caudec 1.6.2: multi-process audio transcoder -Copyright © 2012 - 2013 Guillaume Cocatre-Zilgien -http://caudec.outpost.fr/ +caudec 1.7.5: multiprocess audio converter +Copyright © 2012 - 2014 Guillaume Cocatre-Zilgien +http://caudec.net/ -caudec is a BASH script for GNU/Linux that transcodes audio files from +caudec is a command-line utility that transcodes (converts) audio files from one format (codec) to another. It leverages multi-core CPUs with lots of RAM by using a ramdisk, and running multiple processes concurrently (one per file and per codec). @@ -11,12 +11,13 @@ arguably classier than ABBA, but caudec doesn't discriminate) from WAV to FLAC --best is done at 183x real time on a Core i7 @ 2.2 GHz with 8 processes, versus 46x with one process. FLAC -5 encodes at 532x, TAK -p2 at 705x. -* Supported input codecs: WAV, FLAC, WavPack, Monkey's Audio, TAK, - Apple Lossless (ALAC). -* Supported output codecs: WAV, FLAC, Flake, WavPack, Monkey's Audio, TAK, - Apple Lossless (ALAC), lossyWAV, LAME, Ogg Vorbis, Nero AAC, qaac, Musepack, - Opus. -* Support for high quality resampling with sox. +* Supported input formats / codecs: WAV, AIFF, CAF, FLAC, WavPack, + Monkey's Audio, TAK, Apple Lossless (ALAC). +* Supported output formats / codecs: WAV, AIFF, CAF, FLAC, Flake, WavPack, + Monkey's Audio, TAK, Apple Lossless (ALAC), lossyWAV, LAME, Ogg Vorbis, + Nero AAC, qaac, Musepack, Opus. +* Support for high quality resampling and downmixing / upmixing to stereo, + with SoX. * Optimized I/O: input files are copied onto a ramdisk sequentially, so as to get the best performance out of the underlying medium (e.g. a hard drive). Transcoding however is done concurrently. Example: file 1 gets copied. When @@ -26,62 +27,77 @@ decoding of input files is done only once. * Multiple instances of caudec can be run concurrently while sharing ressources. * Metadata is preserved (as much as possible) from one codec to another. -* Multi-process Replaygain scanner (except for Opus and Musepack) +* Multiprocess Replaygain scanner (except for Opus and Musepack) * Uses existing, popular command line encoders/decoders. caudec is most useful when dealing with one album at a time (with 1, 2 or 3 discs, with each track as a separate file). Handling of multiple albums is done -via scripting (see http://caudec.outpost.fr/documentation/examples/). +via scripting (see http://caudec.net/documentation/examples/). caudec cannot efficiently deal with very large files (recordings that are several hours long), or very large numbers of files (messy collections with a single directory and thousands of files). Those are known limitations, and they are not within the scope of this program. -Tested under Arch Linux (latest version) and Mac OS X Lion (version 1.4.1). -Thanks to Tobias Link for kindly giving me remote shell access to his Macbook -so I could port caudec to Mac OS X. +Tested under Arch Linux and OS Ⅹ Mountain Lion. Thanks to Tobias Link for +kindly giving me remote shell access to his Macbook so I could port caudec +to OS Ⅹ. -Please submit bug reports here: http://caudec.outpost.fr/redirect/bugs +Please submit bug reports here: http://caudec.net/redirect/bugs If you enjoy caudec and would like to make a donation, see: -http://caudec.outpost.fr/donate/ +http://caudec.net/donate/ -caudec uses common UNIX tools (grep, stat, sed, tr, cut, wc, df, mktemp, -readlink, xargs, nohup), as well as the following software: +caudec uses common UNIX tools (which, uname, grep, stat, sed/gsed, date/gdate, +tr, cut, wc, df, mktemp, xargs, nohup), as well as the following software: + +Mandatory Software: +-------------------------------------------------------------------------------- * bc: http://www.gnu.org/software/bc/ -* shntool: http://etree.org/shnutils/shntool/ -* sox: http://sox.sourceforge.net/ -* wget: http://www.gnu.org/software/wget/wget.html +* SoX: http://sox.sourceforge.net/ + +Optional Software: +-------------------------------------------------------------------------------- + +Native Codecs: * FLAC: http://flac.sourceforge.net/ * Flake: http://flake-enc.sourceforge.net/ * WavPack: http://www.wavpack.com/ * Monkey's Audio: http://www.monkeysaudio.com/ and http://etree.org/shnutils/shntool/support/formats/ape/unix/ -* TAK: http://thbeck.de/Tak/Tak.html -* ALAC decoder: http://craz.net/programs/itunes/alac.html -* ffmpeg (ALAC encoding): http://ffmpeg.org/ -* lossyWAV: http://wiki.hydrogenaudio.org/index.php?title=LossyWAV +* ffmpeg (ALAC, AAC, Monkey's Audio): http://ffmpeg.org/ * LAME: http://lame.sourceforge.net/ * Ogg Vorbis: http://www.vorbis.com/ * Nero AAC: http://www.nero.com/eng/technologies-aac-codec.html -* qaac (iTunes AAC encoding): https://sites.google.com/site/qaacpage/ * Musepack: http://musepack.net/ and http://wiki.hydrogenaudio.org/index.php?title=Musepack * Opus: http://www.opus-codec.org/ + +Windows Codecs: * Wine (for Windows binaries): http://www.winehq.org/ -* Apetag (for files with APEv2 metadata): http://muth.org/Robert/Apetag/ -* cksfv (for hashing CRCs): http://zakalwe.fi/~shd/foss/cksfv/ +* TAK: http://wiki.hydrogenaudio.org/index.php?title=TAK +* lossyWAV: http://wiki.hydrogenaudio.org/index.php?title=LossyWAV +* LAME: http://www.rarewares.org/mp3-lame-bundle.php +* Ogg Vorbis: http://www.rarewares.org/ogg-oggenc.php +* qaac (iTunes AAC encoding): https://sites.google.com/site/qaacpage/ + +Replaygain: * wavegain: http://freecode.com/projects/wavegain * vorbisgain: http://sjeng.org/vorbisgain.html * mp3gain: http://mp3gain.sourceforge.net/ * aacgain: http://aacgain.altosdesign.com/ -* eyeD3: http://eyed3.nicfit.net/ -In order to install the required software on Mac OS X, you may find the -following package management software useful: -* Homebrew: http://mxcl.github.com/homebrew/ -* MacPorts: http://www.macports.org/ -* Fink: http://www.finkproject.org/ +Miscellaneous: +* wget (for checking for new versions): http://www.gnu.org/software/wget/wget.html +* APEv2 (for files with APEv2 metadata): http://caudec.net/ +* cksfv (for hashing CRC32): http://zakalwe.fi/~shd/foss/cksfv/ +* md5sum (Linux) or md5 (OS Ⅹ) for hashing MD5 sums +* sha1sum, sha256sum, sha512sum (Linux) or shasum (OS Ⅹ) for hashing SHA sums +* eyeD3 (for MP3 tagging): http://eyed3.nicfit.net/ + + +In order to install caudec, required and optional software on OS Ⅹ, use the +Homebrew package management software: http://mxcl.github.io/homebrew/ +Note that the GNU version of sed (gsed) is required; gdate is optional. To install caudec when no package is available for your distribution, run: $ sudo ./install.sh [ --prefix=/some/custom/path ]
View file
caudec-1.6.2.tar.gz/caudec -> caudec-1.7.5.tar.gz/caudec
Changed
@@ -1,7 +1,7 @@ #!/bin/bash -# Copyright © 2012 - 2013 Guillaume Cocatre-Zilgien <guillaume@cocatre.net> -# http://caudec.outpost.fr/ +# Copyright © 2012 - 2014 Guillaume Cocatre-Zilgien <gcocatre@gmail.com> +# http://caudec.net/ # # 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 @@ -23,7 +23,22 @@ me='caudec' calledAs="${0##*/}" -VERSION='1.6.2' +VERSION='1.7.5' + +# EX_USAGE: The command was used incorrectly, e.g., with the wrong number of arguments, a bad flag, a bad syntax in a parameter, or whatever. +# EX_DATAERR: The input data was incorrect in some way. This should only be used for user's data & not system files. +# EX_NOINPUT: An input file (not a system file) did not exist or was not readable. This could also include errors like "No message" to a mailer (if it cared to catch it). +# EX_NOUSER: The user specified did not exist. This might be used for mail addresses or remote logins. +# EX_NOHOST: The host specified did not exist. This is used in mail addresses or network requests. +# EX_UNAVAILABLE: A service is unavailable. This can occur if a support program or file does not exist. This can also be used as a catchall message when something you wanted to do doesn't work, but you don't know why. +# EX_SOFTWARE: An internal software error has been detected. This should be limited to non-operating system related errors as possible. +# EX_OSERR: An operating system error has been detected. This is intended to be used for such things as "cannot fork", "cannot create pipe", or the like. It includes things like getuid returning a user that does not exist in the passwd file. +# EX_OSFILE: Some system file (e.g., /etc/passwd, /etc/utmp, etc.) does not exist, cannot be opened, or has some sort of error (e.g., syntax error). +# EX_CANTCREAT: A (user specified) output file cannot be created. +# EX_IOERR: An error occurred while doing I/O on some file. +# EX_TEMPFAIL: temporary failure, indicating something that is not really an error. In sendmail, this means that a mailer (e.g.) could not create a connection, and the request should be reattempted later. +# EX_PROTOCOL: the remote system returned something that was "not possible" during a protocol exchange. +# EX_NOPERM: You did not have sufficient permission to perform the operation. This is not intended for file system problems, which should use NOINPUT or CANTCREAT, but rather for higher level permissions. EX_OK=0 # successful termination EX_KO=1 # unsuccessful termination @@ -46,6 +61,11 @@ EL="\\033[2K\\033[0G" OK="\\033[1;32m" KO="\\033[1;31m" WG="\\033[1;33m" +DG="\\033[0;32m" # dark green +DR="\\033[0;31m" # dark red +BR="\\033[0;33m" # brown +DB="\\033[0;34m" # dark blue +BB="\\033[1;34m" # bright blue NM="\\033[0m" BD="\\033[1;37m" GR="\\033[1;30m" CY="\\033[0;36m" BCY="\\033[1;36m" # User settings ================================================================ @@ -53,9 +73,9 @@ if [ -r '/etc/caudecrc' ]; then . '/etc/caudecrc' test -n "$maxInstances" && rootMaxInstances="$maxInstances" - test -n "$maxInputFiles" && rootMaxInputFiles="$maxInputFIles" + test -n "$maxInputFiles" && rootMaxInputFiles="$maxInputFiles" test -n "$sendHardwareDetails" && rootSendHardwareDetails="$sendHardwareDetails" - test -n "$preloadSources" && rootPreloadSources="$preloadSource" + test -n "$preloadSources" && rootPreloadSources="$preloadSources" fi if [ -r "${HOME}/.caudecrc" ]; then @@ -64,18 +84,32 @@ # sanitize caudecrc input -test -z "$WIN32PATH" && WIN32PATH="${HOME}/.wine" -test -z "$WIN64PATH" && WIN64PATH="${HOME}/win64" +if [ -n "$WIN64PATH" -a ! -d "${WIN64PATH}/drive_c/windows/syswow64" ]; then WIN64PATH=''; fi +if [ -n "$WIN32PATH" -a ! -d "${WIN32PATH}/drive_c/windows" ]; then WIN32PATH=''; fi +export WINEDEBUG='err-all,warn-all,fixme-all,trace-all' + +if [ -n "$replaygain_percentile" ]; then # backwards compatibility + replaygainPercentile="$replaygain_percentile" + unset replaygain_percentile +fi +case "$replaygainPercentile" in + [0-9]*) if [ $replaygainPercentile -le 0 -o $replaygainPercentile -ge 100 ]; then replaygainPercentile=34; fi ;; + *) replaygainPercentile=34 ;; +esac -case "$replaygain_percentile" in - [0-9]*) if [ $replaygain_percentile -le 0 -o $replaygain_percentile -ge 100 ]; then replaygain_percentile=34; fi ;; - *) replaygain_percentile=34 ;; +case "$ID3Padding" in + [0-9]*) true ;; # nothing to do + *) ID3Padding=512 ;; esac -if [ "$preventClipping" = "true" ]; then preventClipping=true; else preventClipping=false; fi -if [ "$setCompilationFlagWithAlbumArtist" = "true" ]; then setCompilationFlagWithAlbumArtist=true; else setCompilationFlagWithAlbumArtist=false; fi -if [ "$keepWavMetadata" = "true" ]; then keepWavMetadata=true; else keepWavMetadata=false; fi -if [ "$tagCompressionSetting" = "true" ]; then tagCompressionSetting=true; else tagCompressionSetting=false; fi +# true by default +if [ "$preventClipping" = 'false' ]; then preventClipping=false; else preventClipping=true; fi + +# false by default +if [ "$tagCompressionSetting" = 'true' ]; then tagCompressionSetting=true; else tagCompressionSetting=false; fi +if [ "$setCompilationFlagWithAlbumArtist" = 'true' ]; then setCompilationFlagWithAlbumArtist=true; else setCompilationFlagWithAlbumArtist=false; fi +if [ "$keepWavMetadata" = 'true' ]; then keepWavMetadata=true; else keepWavMetadata=false; fi +if [ "$ignoreUnsupportedFiles" = 'true' ]; then ignoreUnsupportedFiles=true; else ignoreUnsupportedFiles=false; fi test -z "$hashes" && hashes='' if [ -n "$hashes" ]; then @@ -95,39 +129,41 @@ tagBlacklist="${tagBlacklist%,}" fi -if [ -n "$rootMaxInstances" ]; then - maxInstances="$rootMaxInstances" -elif [ -z "$maxInstances" ]; then - maxInstances=1 -fi +if [ -n "$rootMaxInstances" ]; then maxInstances="$rootMaxInstances"; fi case "$maxInstances" in [0-9]*) if [ $maxInstances -lt 1 ]; then maxInstances=1 ; fi ;; *) maxInstances=1 ;; esac -if [ -n "$rootMaxInputFiles" ]; then - maxInputFiles="$rootMaxInputFiles" -elif [ -z "$maxInputFiles" ]; then - maxInputFiles=100 -fi +if [ -n "$rootMaxInputFiles" ]; then maxInputFiles="$rootMaxInputFiles"; fi case "$maxInputFiles" in [0-9]*) if [ $maxInputFiles -lt 1 ]; then maxInputFiles=100 ; fi ;; *) maxInputFiles=100 ;; esac -if [ -n "$rootSendHardwareDetails" ]; then - sendHardwareDetails="$rootSendHardwareDetails" -elif [ -z "$sendHardwareDetails" ]; then - sendHardwareDetails=false +# false by default +if [ -n "$rootSendHardwareDetails" ]; then sendHardwareDetails="$rootSendHardwareDetails"; fi +if [ "$sendHardwareDetails" = 'true' ]; then sendHardwareDetails=true; else sendHardwareDetails=false; fi + +# true by default +if [ -n "$rootPreloadSources" ]; then preloadSources="$rootPreloadSources"; fi +if [ "$preloadSources" = 'false' ]; then preloadSources=false; else preloadSources=true; fi + +# true by default +if [ "$enableColors" = 'false' ]; then + enableColors=false + OK='' KO='' WG='' DG='' NM='' BD='' GR='' CY='' BCY='' +else + enableColors=true fi -if [ "$sendHardwareDetails" = "true" ]; then sendHardwareDetails=true; else sendHardwareDetails=false; fi -if [ -n "$rootPreloadSources" ]; then - preloadSources="$rootPreloadSources" -elif [ -z "$preloadSources" ]; then - preloadSources=false +# true by default +if [ "$useBrightColors" = 'false' ]; then + useBrightColors=false + OK="$DG" KO="$DR" WG="$BR" CY="$DB" +else + useBrightColors=true fi -if [ "$preloadSources" = "true" ]; then preloadSources=true; else preloadSources=false; fi # Global values ================================================================ @@ -136,116 +172,470 @@ piddir='/tmp/caudec' iodir="${piddir}/io" -takWinePrefix="$WIN32PATH" takWineExe='wine' -if [ -e "${WIN64PATH}/drive_c/windows/Takc.exe" ]; then takWinePrefix="$WIN64PATH" takWineExe='wine64' ; fi -lossywavWinePrefix="$WIN32PATH" lossywavWineExe='wine' -if [ -e "${WIN64PATH}/drive_c/windows/lossyWAV.exe" ]; then lossywavWinePrefix="$WIN64PATH" lossywavWineExe='wine64' ; fi -lameWinePrefix="$WIN32PATH" lameWineExe='wine' -if [ -e "${WIN64PATH}/drive_c/windows/lame.exe" ]; then lameWinePrefix="$WIN64PATH" lameWineExe='wine64' ; fi -qaacWinePrefix="$WIN32PATH" qaacWineExe='wine' -if [ -e "${WIN64PATH}/drive_c/windows/qaac.exe" ]; then qaacWinePrefix="$WIN64PATH" qaacWineExe='wine64' ; fi -oggencWinePrefix="$WIN32PATH" oggencWineExe='wine' -if [ -e "${WIN64PATH}/drive_c/windows/oggenc2.exe" ]; then oggencWinePrefix="$WIN64PATH" oggencWineExe='wine64' ; fi - # Functions ==================================================================== printUsage () { if [ "$calledAs" = 'decaude' ]; then - echo "caudec ${VERSION}: multi-process audio transcoder -Copyright © 2012 - 2013 Guillaume Cocatre-Zilgien -http://caudec.outpost.fr/ + echo "caudec ${VERSION}: multiprocess audio converter +Copyright © 2012 - 2014 Guillaume Cocatre-Zilgien +http://caudec.net/ Usage: decaude FILES -Decodes FILES to WAV (same as caudec -d). +Decodes FILES to WAV (same as 'caudec -d' and 'caudec -c wav'). +Instead of multiple files, one or more directories may be specified. See also: caudec -h" else genUserAgent - echo "caudec ${VERSION}: multi-process audio transcoder -Copyright © 2012 - 2013 Guillaume Cocatre-Zilgien -http://caudec.outpost.fr/ + echo "caudec ${VERSION}: multiprocess audio converter +Copyright © 2012 - 2014 Guillaume Cocatre-Zilgien +http://caudec.net/ -Usage: $me [ GLOBAL OPTIONS ] [ RESAMPLING ] [ ENCODING/DECODING/RG ] FILES +Usage: $me [ GLOBAL OPTIONS ] [ PROCESSING ] [ ENCODING/DECODING/RG ] FILES Operate on multiple audio files at once, in parallel. +Instead of multiple files, one or more directories may be specified. Multiple codec switches (optionally paired with a -q switch) may be specified. -Supported input files: .wav, .flac, .wv, .ape, .tak, .m4a (ALAC) +Supported input files: .wav, .aiff, .caf, .flac, .wv, .ape, .tak, .m4a (ALAC) + Global options: -s be silent, only print errors -n N launch N processes concurrently (1-${maxProcesses}); - by default, the number of logical CPUs. + by default, the number of CPU cores. -o DIR set already existing output directory -O DIR set output directory, create it if it doesn't exist already -P DIR set and create output directory, and mirror the source file's path components (e.g. 'a/b/file.flac' => 'DIR/a/b/file.ogg') + -k keep existing destination files (don't overwrite them) + -K keep existing destination files if they're newer than their source + + -DDD delete source file upon successful transcoding (USE WITH CARE) + Note: the triple D is not a typo, but a precaution to avoid + accidental deletion; also, read-only source files will NOT be + deleted. + + -z produce machine-parsable output; must be the first parameter on the + command line to take effect. Run 'caudec -z' on its own to print + a description of the syntax. -Resampling options: +When transcoding to multiple codecs at once (multiple -c parameters), +specify a -o/O/P parameter after each -c parameter in order to set per-codec +output directories. For instance: +\$ caudec -c flac -P '/data/flac' -c mp3 -P '/data/mp3' \"artist/album\"/*.flac + + +Processing / resampling options: -b BITS bit depth (16, 24) - -r HZ sampling rate in Hz (44100, 48000, 88200, 96000, 176400, 192000) - -r KHZ sampling rate in kHz (44[.1], 48, 88[.2], 96, 176[.4], 192) - -r cd equivalent to -b 16 -r 44100 + -r HZ sampling rate in Hz (44100, 48000, 88200, 96000, 176400, 192000, + 352800, 384000) + -r KHZ sampling rate in kHz (44[.1], 48, 88[.2], 96, 176[.4], 192, 352[.8], + 384) + -r cd equivalent to -b 16 -r 44100 -2 (includes conversion to stereo) -r dvd equivalent to -b 16 -r 48000 -r sacd equivalent to -b 24 -r 88200 -r dvda equivalent to -b 24 -r 96000 -r bluray equivalent to -b 24 -r 96000 -r pono equivalent to -b 24 -r 192000 + -r dxd equivalent to -b 24 -r 352800 + -2 convert to stereo: 2.1, 4.0, 5.0, 5.1 and 7.1 audio will be + downmixed, using proper channel mappings; mono audio will be + upmixed to dual-mono (stereo with two identical channels) + Encoding options: - -c CODEC use specified CODEC: + -c CODEC use specified CODEC: wav, aiff, caf, flac, flake, wv (WavPack), wvh (WavPack Hybrid), wvl (WavPack lossy) - ape, tak, alac, lossyWAV, lossyFLAC, lossyWV, lossyTAK, mp3, lame, - winlame, ogg, vorbis, winvorbis, m4a, aac, qaac, mpc, musepack, - opus. Note that artwork preservation in MP3s requires eyeD3. + ape, tak, alac, lossyWAV, lossyFLAC, lossyWV, lossyTAK, mp3 / lame, + winlame, ogg / vorbis, winvorbis, aac, qaac, mpc / musepack, opus. + Note that artwork preservation in MP3s requires eyeD3. + -C CODEC use specified CODEC, but discard existing metadata + -q ARG set compression level (variable bitrate mode; try -q help for a list of valid values) + -b ARG constant or target bitrate in bits per sample (for -c wvh/wvl) or in kilobits per second (for -c wvh/wvl, opus, mp3/lame/winlame, aac, qaac, ogg/vorbis/winvorbis) + -B ARG average bitrate in kilobits per second (for -c mp3/lame/winlame, aac, qaac, ogg/vorbis/winvorbis, opus) + -G ARG apply Replaygain (album or track) if found in source file metadata, - after decoding and BEFORE encoding (irreversible) - -H HASH compute hash of raw PCM (crc, md5 or sha1, lossless codecs and - lossyFLAC, lossyWV, lossyTAK only) + after decoding and BEFORE encoding (irreversible). Note: it is + possible to specify a preamp value with an additional -G parameter, + for instance '-G album -G -3' or '-G track -G +2'. Only use + positive preamp values if you know what you're doing. + + -G ARG apply peak normalization (albumpeak or trackpeak); this makes the + tracks as loud as possible without clipping; requires Replaygain + metadata to be available in the source files. Note: it is possible + to specify an arbitrary peak reference lower than 0dBFS with an + additional -G parameter: '-G albumpeak -G -4'. + + -G GAIN apply arbitrary GAIN (signed number from -99.99 to +99.99) + + -H HASH compute hash of raw PCM (CRC32, MD5, SHA1, SHA256 or SHA512, + lossless codecs and lossyFLAC, lossyWV, lossyTAK only) -H ^HASH do NOT compute HASH even if it's in caudecrc + Decoding options: - -d decode to WAV + -d decode to WAV (same as -c wav) -t test file integrity - -H HASH compute hash of raw PCM (crc, md5 or sha1, lossless codecs and - lossyFLAC, lossyWV, lossyTAK only) + -H HASH compute hash of raw PCM (CRC32, MD5, SHA1, SHA256 or SHA512, + lossless codecs and lossyFLAC, lossyWV, lossyTAK only) -H ^HASH do NOT compute HASH even if it's in caudecrc + Replaygain options (mutually exclusive from -c/-d/-t): -g generate Replaygain metadata -G ARG MP3 & AAC: compute and apply gain of type ARG (album or track) (no tags, works everywhere) + -G ARG MP3 & AAC: apply peak normalization (albumpeak or trackpeak); + this makes the tracks as loud as possible without clipping. + Note: it is possible to specify an arbitrary peak reference lower + than 0dBFS with an additional -G parameter: '-G albumpeak -G -4'. + -G GAIN MP3 & AAC: apply arbitrary GAIN (signed number from -99.99 to + +99.99) -S ARG MP3, AAC & ALAC: generate Soundcheck metadata (album or track) Note: Replaygain metadata is also generated with this switch. + Information: -h display this help and exit -u check for new versions -V output version information and exit + User Agent string, sent when checking for new versions (-u): $userAgent See 'sendHardwareDetails' configuration parameter in caudecrc. caudec uses a temporary directory for processing files (default: \$TMPDIR, /tmp or /dev/shm). If you wish to use another directory, set the CAUDECDIR -environment variable to its path. It is strongly recommended that the directory -be mounted on some kind of ramdisk. +environment variable to its path (export CAUDECDIR=\"/some/dir\"). It is +strongly recommended that the directory be mounted on some kind of ramdisk. + +To enable debugging, set the CAUDECDEBUG environment variable to 'true' +(export CAUDECDEBUG='true'). caudec will output some additional information, +as well as a log of errors that occurred while using external tools. For more help, see the online documentation: -http://caudec.outpost.fr/documentation/" +http://caudec.net/documentation/" + fi +} + +printMachineSyntax () +{ + echo "------------------------------------------------------------------------------- + Format specification of caudec's machine-parsable output (-z) +------------------------------------------------------------------------------- + +Fields are separated with pipes ('|', Unicode: U+007C); pipe characters present +in filenames or messages are replaced with the visually similar 'broken bar' +sign ('¦', Unicode: U+00A6). + +status|status_info|status_data|file|message|process_id + +status: one of info, success, error, warning, abort. + +status_info: optional additional information about the status (usage, + initialization, decoding, processing, encoding, testing, hashing, debugging, + track_gain, album_gain, checking_version, aborting, hash_type, + bitrate_codecName, processing_rate). + +status_data: optional data about the status (hash type, hash value, hash error, + gain value in dB, bitrate in bits per second, processing rate, version + status). Some status_data keywords: + * filesystem (filesystem related error or information), + * server (server related error or information), + * command_line (command line related error or information), + * bad_value (user provided a parameter with a bad argument), + * quota (some limit has been reached), + * internal (internal software error / warning), + * no_hash (no hash metadata found), + * no_hash_tool (no hashing tools available), + * bad_internal_hash (internal MD5 hash is incorrect), + * user_agent (user agent sent to caudec's server), + * up_to_date (caudec is up to date), + * new_version_available (there's a new version of caudec available), + * running_newer_version (local copy of caudec is newer than the latest + available online) + +file: path of the file that is affected by the status (if applicable). + +message: optional human-readable information about the status. + +process_id: identifier (PID) of the caudec process." +} + +# status;status_info;status_data;file;message;process_number +genMachineParsableString () +{ + local pid='' + + if [ -n "$BASHPID" ]; then + pid="$BASHPID" + elif [ -n "$$" ]; then + pid="$$" + fi + + # replace pipes ('|', Unicode: U+007C) with 'broken bar' signs ('¦', Unicode: U+00A6) + file="${file//|/¦}" + msg="${msg//|/¦}" + + case "$msgType" in + abort) str="$( printf "%s|%s|%s|%s|%s|%s\n" 'info' 'aborting' '' '' 'Aborting.' '' )" ;; + + info) + if [ -n "$bitrate" ]; then + str="$( printf "%s|%s|%s|%s|%s|%s\n" 'info' "bitrate_${codec}" "$bitrate" '' "$msg" "$pid" )" + elif [ -n "$rate" ]; then + str="$( printf "%s|%s|%s|%s|%s|%s\n" 'info' 'processing_rate' "$rate" '' "$msg" "$pid" )" + elif [ -n "$hashType" ]; then + str="$( printf "%s|%s|%s|%s|%s|%s\n" 'info' "$hashType" "$statusData" "$file" '' "$pid" )" + else + str="$( printf "%s|%s|%s|%s|%s|%s\n" 'info' "$statusInfo" "$statusData" '' "$msg" "$pid" )" + fi + ;; + + success) + if [ "$statusInfo" = 'track_gain' ]; then + str="$( printf "%s|%s|%s|%s|%s|%s\n" "$msgType" "$statusInfo" "$gain" "$file" '' "$pid" )" + elif [ "$statusInfo" = 'testing' ]; then + if [ -n "$hashError" ]; then + str="$( printf "%s|%s|%s|%s|%s|%s\n" "$msgType" "$statusInfo" "$hashError" "$file" '' "$pid" )" + else + str="$( printf "%s|%s|%s|%s|%s|%s\n" "$msgType" "$statusInfo" "$hashType" "$file" '' "$pid" )" + fi + elif [ "$statusInfo" = 'album_gain' ]; then + str="$( printf "%s|%s|%s|%s|%s|%s\n" "$msgType" "$statusInfo" "$gain" '' "$msg" "$pid" )" + else + str="$( printf "%s|%s|%s|%s|%s|%s\n" "$msgType" "$statusInfo" "$statusData" "$file" "$msg" "$pid" )" + fi + ;; + + error) + if [ "$statusInfo" = 'testing' ]; then + if [ -n "$hashError" ]; then + str="$( printf "%s|%s|%s|%s|%s|%s\n" "$msgType" "$statusInfo" "$hashError" "$file" '' "$pid" )" + else + str="$( printf "%s|%s|%s|%s|%s|%s\n" "$msgType" "$statusInfo" "$hashType" "$file" '' "$pid" )" + fi + elif [ "$statusInfo" = 'album_gain' ]; then + str="$( printf "%s|%s|%s|%s|%s|%s\n" "$msgType" "$statusInfo" '' '' "$msg" "$pid" )" + else + str="$( printf "%s|%s|%s|%s|%s|%s\n" "$msgType" "$statusInfo" "$statusData" "$file" "$msg" "$pid" )" + fi + ;; + + warning) + if [ "$statusInfo" = 'testing' ]; then + str="$( printf "%s|%s|%s|%s|%s|%s\n" "$msgType" "$statusInfo" "$hashError" "$file" "$msg" "$pid" )" + elif [ -n "$hashError" ]; then + str="$( printf "%s|%s|%s|%s|%s|%s\n" "$msgType" "$statusInfo" "$hashError" "$file" "$msg" "$pid" )" + else + str="$( printf "%s|%s|%s|%s|%s|%s\n" "$msgType" "$statusInfo" "$statusData" "$file" "$msg" "$pid" )" + fi + ;; + esac +} + +genHumanReadableString () +{ + case "$msgType" in + info) + if [ -n "$msg" ]; then + str="$( printf "${GR} * ${NM}%s\n" "$msg" )" + fi ;; + + abort) str="$( printf "${WG} * ${NM}%s\n" 'Aborting.' )" ;; + + success) + if [ "$statusInfo" = 'track_gain' ]; then + str="$( printf "${GR}%2u ${OK}OK ${NM}%9s ${CY}%s${NM}\n" $p "$gain dB" "$fileName" )" + elif [ "$statusInfo" = 'album_gain' ]; then + str="$( printf "${GR} * ${OK}OK ${NM}%9s ${NM}%s\n" "$gain dB" 'Album gain' )" + elif [ "$statusInfo" = 'testing' ]; then + if [ -n "$hashType" ]; then + str="$( printf "${GR}%2u ${OK}OK ${NM}%s ${CY}%s${NM}\n" $p "$hashType" "$fileName" )" + else + str="$( printf "${GR}%2u ${OK}OK ${CY}%s${NM}\n" $p "$fileName" )" + fi + elif [ -n "$file" ]; then + if [ -n "$p" ]; then + str="$( printf "${GR}%2u ${OK}OK ${CY}%s${NM}\n" $p "$fileName" )" + else + str="$( printf "${GR} * ${OK}OK ${CY}%s${NM}\n" "$fileName" )" + fi + elif [ -n "$msg" ]; then + str="$( printf "${GR} * ${OK}OK${NM}: %s\n" "$msg" )" + fi + ;; + + error) + if [ "$statusInfo" = 'decoding' ]; then + str="$( printf "${GR}%2u ${KO}DC ${CY}%s${NM}\n" $p "$fileName" )" + elif [ "$statusInfo" = 'processing' ]; then + str="$( printf "${GR}%2u ${KO}PR ${CY}%s${NM}\n" $p "$fileName" )" + elif [ "$statusInfo" = 'testing' ]; then + if [ -n "$hashType" ]; then + str="$( printf "${GR}%2u ${KO}ER ${NM}%s ${CY}%s${NM}\n" $p "$hashType" "$fileName" )" + elif [ -n "$msg" ]; then + str="$( printf "${GR}%2u ${KO}ER ${CY}%s${NM}: %s\n" $p "$fileName" "$msg" )" + else + str="$( printf "${GR}%2u ${KO}ER ${CY}%s${NM}\n" $p "$fileName" )" + fi + elif [ -n "$file" ]; then + if [ -n "$msg" ]; then + if [ -n "$p" ]; then + str="$( printf "${GR}%2u ${KO}ER ${CY}%s${NM}: %s\n" $p "$fileName" "$msg" )" + else + str="$( printf "${GR} * ${KO}ER ${CY}%s${NM}: %s\n" "$fileName" "$msg" )" + fi + else + if [ -n "$p" ]; then + str="$( printf "${GR}%2u ${KO}ER ${CY}%s${NM}\n" $p "$fileName" )" + else + str="$( printf "${GR} * ${KO}ER ${CY}%s${NM}\n" "$fileName" )" + fi + fi + elif [ -n "$msg" ]; then + if [ -n "$p" ]; then + str="$( printf "${GR}%2u ${KO}ER${NM}: %s\n" $p "$msg" )" + else + str="$( printf "${GR} * ${KO}ER${NM}: %s\n" "$msg" )" + fi + fi + ;; + + warning) + if [ -n "$file" ]; then + if [ -n "$p" ]; then + str="$( printf "${GR}%2u ${WG}WG ${CY}%s${NM}: %s\n" $p "$fileName" "$msg" )" + else + str="$( printf "${GR} * ${WG}WG ${CY}%s${NM}: %s\n" "$fileName" "$msg" )" + fi + else + if [ -n "$p" ]; then + str="$( printf "${GR}%2u ${WG}WG${NM}: %s\n" $p "$msg" )" + else + str="$( printf "${GR} * ${WG}WG${NM}: %s\n" "$msg" )" + fi + fi + ;; + esac +} + +printMessage () +{ + local msgType='info' msgVerbose='default' msgDebug='normal' msgOut='stdout' statusInfo='' statusData='' file='' path='' filePath='' msg='' p='' hashType='' hashError='' gain='' bitrate='' rate='' codec='' str='' + + for a in "$@"; do + case "$a" in + info) msgType="$a" msgVerbose='verbose' ;; + success) msgType="$a" msgVerbose='verbose' ;; + warning|error|abort) msgType="$a" msgOut='stderr' ;; + verbose) msgVerbose='verbose' ;; + debug) msgDebug='debug' statusInfo='debugging' ;; + stderr) msgOut='stderr' ;; + initialization|usage|encoding|processing|decoding|hashing|testing|album_gain|track_gain|checking_version) statusInfo="$a" ;; + unsupported|quota|bad_value|filesystem|command_line|internal|user_agent) statusData="$a" ;; + server|up_to_date|new_version_available|running_newer_version) statusData="$a" ;; + file:*) file="${a#file:}"; filePath="$( dirname "$file" )"; fileName="$( basename "$file" )" ;; + path:*) file="${a#path:}"; fileName="$file" ;; + +*dB|-*dB) gain="${a% dB}" ;; + *bps) bitrate="${a%bps}" ;; + *x) rate="${a%x}" ;; + bitrate_*) codec="${a#bitrate_}" ;; + [0-9]*) p="$a" ;; + + CRC32|MD5|SHA1|SHA256|SHA512) hashType="$a" ;; + no_hash|no_hash_tool|bad_internal_hash) hashError="$a" ;; + + CRC32=*) statusData="${a#CRC32=}" ;; + MD5=*) statusData="${a#MD5=}" ;; + SHA1=*) statusData="${a#SHA1=}" ;; + SHA256=*) statusData="${a#SHA256=}" ;; + SHA512=*) statusData="${a#SHA512=}" ;; + + *) msg="$a" ;; + esac + done + + if [ "$msgVerbose" = 'verbose' -a "$verbose" != 'true' ]; then return $EX_OK; fi + if [ "$msgDebug" = 'debug' -a -z "$CAUDECDEBUG" ]; then return $EX_OK; fi + + if [ "$outputMode" = 'machine' ]; then + genMachineParsableString + else + genHumanReadableString + fi + + if [ -n "$str" ]; then + if [ "$msgOut" = 'stderr' ]; then + echo -e "$str" 1>&2 + else + echo -e "$str" + fi + fi + + return $EX_OK +} + +findWineDirs () +{ + local newlineAdded=false informWineDirsFound=false + + if [ -z "$WIN64PATH" ]; then + WIN64PATH="$( find "$HOME" -type d -regex '.*/drive_c/windows/syswow64$' 2>/dev/null | head -n 1 )" + if [ -n "$WIN64PATH" ]; then + WIN64PATH="${WIN64PATH%/drive_c/windows/syswow64}" + echo '' >> "${HOME}/.caudecrc" + echo "WIN64PATH=\"${WIN64PATH}\" # Wine user dir, 64 bit (automatically found by caudec)" >> "${HOME}/.caudecrc" + informWineDirsFound=true + newlineAdded=true + fi + fi + + if [ -z "$WIN32PATH" ]; then + while IFS= read -d $'\0' -r path; do + if [ -z "$path" ]; then continue; fi + path="${path%/drive_c/windows}" + if [ -n "$WIN64PATH" -a "$path" = "$WIN64PATH" ]; then + continue + else + WIN32PATH="$path" + if [ $newlineAdded = false ]; then + echo '' >> "${HOME}/.caudecrc" + newlineAdded=true + fi + echo "WIN32PATH=\"${WIN32PATH}\" # Wine user dir, 32 bit (automatically found by caudec)" >> "${HOME}/.caudecrc" + informWineDirsFound=true + break + fi + done < <(find "$HOME" -type d -regex '.*/drive_c/windows$' -print0 2>/dev/null) + fi + + if [ $informWineDirsFound = true ]; then + printMessage 'info' 'initialization' 'Wine user directories were found, and the relevant configuration variables were saved to ~/.caudecrc.' fi } genUserAgent () { - local kernelName archName cpuName='-' cpuCores='-' ram='-' + local kernelName archName cpuName='-' cpuCores='-' ram='-' cpuFreq='' configFile='none' + + if [ -e "${HOME}/.caudecrc" -a -e '/etc/caudecrc' ]; then + configFile='both' + elif [ -e "${HOME}/.caudecrc" ]; then + configFile='home' + elif [ -e '/etc/caudecrc' ]; then + configFile='etc' + else + configFile='none' + fi kernelName="$( uname -s 2>/dev/null )" if [ -n "$kernelName" ]; then @@ -263,17 +653,30 @@ if [ $sendHardwareDetails = true ]; then if [ -e '/proc/cpuinfo' ]; then - cpuName="$( grep -i 'model name' /proc/cpuinfo 2>/dev/null | head -n 1 2>/dev/null | tr -d "\t" 2>/dev/null | tr -s ' ' 2>/dev/null | cut -d ' ' -f 3- 2>/dev/null )" + cpuName="$( grep -i 'model name' /proc/cpuinfo 2>/dev/null | head -n 1 2>/dev/null | tr -ds "\t" ' ' 2>/dev/null | cut -d ' ' -f 3- 2>/dev/null )" if [ -n "$cpuName" ]; then cpuName="${cpuName// \/ / ∕ }" else cpuName='unknown' fi - cpuCores="$( grep -i 'model name' /proc/cpuinfo 2>/dev/null | wc -l 2>/dev/null )" + cpuCores="$( getNumberOfCpuCores )" if [ -z "$cpuCores" ]; then cpuCores='unknown' fi + elif [ "$OS" = 'Darwin' ]; then # OS Ⅹ + cpuName="$( system_profiler -detailLevel full SPHardwareDataType 2>/dev/null | grep -F 'Processor Name:' 2>/dev/null | cut -d ':' -f 2 2>/dev/null | tr -s ' ' 2>/dev/null )" + if [ -z "$cpuName" ]; then cpuName='unknown'; else cpuName="${cpuName:1}"; fi + cpuFreq="$( system_profiler -detailLevel full SPHardwareDataType 2>/dev/null | grep -F 'Processor Speed:' 2>/dev/null | cut -d ':' -f 2 2>/dev/null | tr -s ' ' 2>/dev/null )" + if [ -n "$cpuFreq" ]; then + cpuName="$cpuName @ ${cpuFreq:1}" + fi + if which 'gnproc' >/dev/null 2>&1; then + cpuCores="$( gnproc 2>/dev/null )" + else + cpuCores="$( system_profiler -detailLevel full SPHardwareDataType 2>/dev/null | grep -F 'Total Number of Cores:' 2>/dev/null | cut -d ':' -f 2 2>/dev/null | tr -d ' ' 2>/dev/null )" + fi + if [ -z "$cpuCores" ]; then cpuCores='unknown'; fi else cpuName='unknown' cpuCores='unknown' @@ -286,40 +689,73 @@ else ram='unknown' fi + elif [ "$OS" = 'Darwin' ]; then # OS Ⅹ + ram="$( system_profiler -detailLevel full SPHardwareDataType 2>/dev/null | grep -F 'Memory:' 2>/dev/null | cut -d ':' -f 2 2>/dev/null | tr -s ' ' 2>/dev/null )" + if [ -z "$ram" ]; then ram='unknown'; else ram="${ram:1}"; fi else ram='unknown' fi fi - userAgent="caudec $VERSION / $kernelName / $archName / $cpuName / $cpuCores / $ram" + userAgent="caudec $VERSION / $kernelName / $archName / $cpuName / $cpuCores / $ram / $configFile" } checkNewVersion () { - local v foo + local v foo a='' b='' c='' x='' y='' z='' m='' n='' ec if checkBinary 'wget'; then genUserAgent - if [ -n "$CAUDECDEBUG" ]; then - echo "$userAgent" 1>&2 - fi - v="$( wget -U "$userAgent" -O - 'http://caudec.outpost.fr/downloads/version' 2>/dev/null )" + printMessage 'info' 'debug' 'user_agent' 'stderr' 'verbose' "$userAgent" + v="$( wget -U "$userAgent" -O - 'http://caudec.net/downloads/version' 2>/dev/null | head -n 1 2>/dev/null )" if [ -z "$v" ]; then - echo "$me: error while trying to connect to server, please try again later." 1>&2 ; exit $EX_TEMPFAIL + printMessage 'error' 'checking_version' 'server' "an error occurred while trying to connect to server, please try again later." ; exit $EX_TEMPFAIL else - foo="$( echo "$v" | tr -d [0-9] | tr -s . )" - if [ "$foo" != '.' ]; then - echo "$me: server returned bad data, please try again later." 1>&2 ; exit $EX_PROTOCOL - elif [ "$v" == "$VERSION" ]; then - echo "caudec is up to date (version $VERSION)." ; exit $EX_OK - else - echo "A new version of caudec is available ($v):" - echo "http://caudec.outpost.fr/downloads/" - exit $EX_OK + echo "$v" | grep -E '^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$' >/dev/null 2>&1 ; ec=$? + if [ $ec -ne $EX_OK ]; then + printMessage 'error' 'checking_version' 'server' "server returned bad data, please try again later." ; exit $EX_PROTOCOL + else + foo="${VERSION% *}" # strip out ' SVN' + if [ "$v" == "$foo" ]; then + printMessage 'success' 'checking_version' 'up_to_date' "caudec is up to date (version $VERSION)." ; exit $EX_OK + else + a="${v%%.*}" + b="${v%.*}"; b="${b#*.}" + c="${v##*.}" + x="${foo%%.*}" + y="${foo%.*}"; y="${y#*.}" + z="${foo##*.}" + + m=$(( (a * 1000000) + (b * 1000) + c )) + n=$(( (x * 1000000) + (y * 1000) + z )) + if [ $m -gt $n ]; then + printMessage 'info' 'checking_version' 'new_version_available' "A new version of caudec is available ($v): http://caudec.net/downloads/" ; exit $EX_OK + else + printMessage 'error' 'checking_version' 'running_newer_version' "you're running a version that's newer than the one available online." ; exit $EX_KO + fi + fi fi fi fi } +isALAC () +{ + if ffprobe -print_format 'default' -show_streams -select_streams 'a:0' "$1" 2>/dev/null | grep -F 'codec_name=alac' >/dev/null 2>&1; then + return $EX_OK + else + return $EX_KO + fi +} + +isAAC () +{ + if ffprobe -print_format 'default' -show_streams -select_streams 'a:0' "$1" 2>/dev/null | grep -F 'codec_name=aac' >/dev/null 2>&1; then + return $EX_OK + else + return $EX_KO + fi +} + getCompressionSetting () { local c="$1" v="$2" s="$3" errormsg="$me -q:" @@ -335,7 +771,7 @@ f|fast) compression_FLAC=0 ;; b|best) compression_FLAC=8 ;; '') compression_FLAC=5 ;; - *) echo "$errormsg compression level for $c must be an integer between 0 and 8, or one of fast or best" 1>&2; exit $EX_USAGE ;; + *) printMessage 'error' 'usage' 'bad_value' "$errormsg compression level for $c must be an integer between 0 and 8, or one of fast or best" ; exit $EX_USAGE ;; esac ;; @@ -343,7 +779,7 @@ case "$v" in [0-9]|1[0-2]) compression_Flake="$v" ;; '') compression_Flake=5 ;; - *) echo "$errormsg compression level for $c must be an integer between 0 and 12" 1>&2; exit $EX_USAGE ;; + *) printMessage 'error' 'usage' 'bad_value' "$errormsg compression level for $c must be an integer between 0 and 12" ; exit $EX_USAGE ;; esac ;; @@ -352,7 +788,7 @@ d|default) compression_WavPack='d' ;; x|x[1-6]|f|fx|fx[1-6]|h|hx|hx[1-6]|hh|hhx|hhx[1-6]) compression_WavPack="$v" ;; '') compression_WavPack='d' ;; - *) echo "$errormsg compression level for $c must be a combination of [f|h|hh][x[1-6]], or d[efault]" 1>&2; exit $EX_USAGE ;; + *) printMessage 'error' 'usage' 'bad_value' "$errormsg compression level for $c must be a combination of [f|h|hh][x[1-6]], or d[efault]" ; exit $EX_USAGE ;; esac ;; @@ -365,7 +801,7 @@ extra*) compression_MonkeysAudio=4 ;; insane) compression_MonkeysAudio=5 ;; '') compression_MonkeysAudio=2 ;; - *) echo "$errormsg compression level for $c must be a number between 1 and 5, or one of fast, normal, high, extra, insane" 1>&2; exit $EX_USAGE ;; + *) printMessage 'error' 'usage' 'bad_value' "$errormsg compression level for $c must be a number between 1 and 5, or one of fast, normal, high, extra, insane" ; exit $EX_USAGE ;; esac ;; @@ -373,7 +809,7 @@ case "$v" in [0-4]|[0-4]e|[0-4]m) compression_TAK="$v" ;; '') compression_TAK=2 ;; - *) echo "$errormsg compression level for $c must be an integer between 0 and 4, optionally followed by 'e' (extra) or 'm' (max)" 1>&2; exit $EX_USAGE ;; + *) printMessage 'error' 'usage' 'bad_value' "$errormsg compression level for $c must be an integer between 0 and 4, optionally followed by 'e' (extra) or 'm' (max)" ; exit $EX_USAGE ;; esac ;; @@ -389,7 +825,7 @@ extreme) compression_lossyWAV='E' ;; insane) compression_lossyWAV='I' ;; '') compression_lossyWAV='S' ;; - *) echo "$errormsg compression level for $c must be one of X, P, C, S, H, E, I, or one of extraportable, portable, economic, standard, high, extreme or insane" 1>&2; exit $EX_USAGE ;; + *) printMessage 'error' 'usage' 'bad_value' "$errormsg compression level for $c must be one of X, P, C, S, H, E, I, or one of extraportable, portable, economic, standard, high, extreme or insane" ; exit $EX_USAGE ;; esac ;; @@ -401,7 +837,7 @@ extreme) compression_LAME=0 ;; insane|320) compression_LAME=320 ;; '') compression_LAME=2 ;; - *) echo "$errormsg compression level for $c must be an integer between 0 and 9 or one of medium, standard, extreme, insane or 320" 1>&2; exit $EX_USAGE ;; + *) printMessage 'error' 'usage' 'bad_value' "$errormsg compression level for $c must be an integer between 0 and 9 or one of medium, standard, extreme, insane or 320" ; exit $EX_USAGE ;; esac ;; @@ -409,7 +845,7 @@ case "$v" in 0|1|0.[1-9]|0.[0-9][0-9]|1.0) compression_AAC="$v" ;; '') compression_AAC='0.5' ;; - *) echo "$errormsg compression level for $c must be a float between 0 and 1" 1>&2; exit $EX_USAGE ;; + *) printMessage 'error' 'usage' 'bad_value' "$errormsg compression level for $c must be a float between 0 and 1" ; exit $EX_USAGE ;; esac ;; @@ -418,7 +854,7 @@ [0-9]|[1-9][0-9]|1[0-1][0-9]|12[0-7]) compression_QAAC="$v" ;; itunes|iTunes|ITUNES) compression_QAAC='iTunes' ;; '') compression_QAAC=90 ;; - *) echo "$errormsg compression level for $c must be an integer between 0 and 127 (True VBR), or 'iTunes' (constrained VBR 256 kbps)" 1>&2; exit $EX_USAGE ;; + *) printMessage 'error' 'usage' 'bad_value' "$errormsg compression level for $c must be an integer between 0 and 127 (True VBR), or 'iTunes' (constrained VBR 256 kbps)" ; exit $EX_USAGE ;; esac ;; @@ -426,7 +862,7 @@ case "$v" in [0-9]|10) compression_OggVorbis="$v" ;; '') compression_OggVorbis=3 ;; - *) echo "$errormsg compression level for $c must be an integer between 0 and 10" 1>&2; exit $EX_USAGE ;; + *) printMessage 'error' 'usage' 'bad_value' "$errormsg compression level for $c must be an integer between 0 and 10" ; exit $EX_USAGE ;; esac ;; @@ -441,7 +877,7 @@ insane) compression_Musepack=7 ;; braindead) compression_Musepack=8 ;; '') compression_Musepack=5 ;; - *) echo "$errormsg compression level for $c must be an integer between 0 and 10, or one of telephone, thumb, radio, standard, normal, extreme, xtreme, insane or braindead" 1>&2; exit $EX_USAGE ;; + *) printMessage 'error' 'usage' 'bad_value' "$errormsg compression level for $c must be an integer between 0 and 10, or one of telephone, thumb, radio, standard, normal, extreme, xtreme, insane or braindead" ; exit $EX_USAGE ;; esac ;; @@ -451,10 +887,10 @@ else case "$v" in [6-9]|[1-9][0-9]|[1-3][0-9][0-9]) true ;; - *) echo "$errormsg target bitrate for $c must be an integer between 6 and 320 in kilobits per second" 1>&2; exit $EX_USAGE ;; + *) printMessage 'error' 'usage' 'bad_value' "$errormsg target bitrate for $c must be an integer between 6 and 320 in kilobits per second" ; exit $EX_USAGE ;; esac if [ $v -lt 6 -o $v -gt 320 ]; then - echo "$errormsg target bitrate for $c must be an integer between 6 and 320 in kilobits per second" 1>&2; exit $EX_USAGE + printMessage 'error' 'usage' 'bad_value' "$errormsg target bitrate for $c must be an integer between 6 and 320 in kilobits per second" ; exit $EX_USAGE fi compression_Opus="$v" fi @@ -478,10 +914,10 @@ case "$v" in [2-9]|[2-9].[0-9]|1[0-9]|1[0-9].[0-9]|2[0-3]|2[0-3].[0-9]) true ;; [2-9][0-9]|[1-9][0-9][0-9]|[1-9][0-9][0-9][0-9]) true ;; - *) echo "$errormsg target bitrate for $c must be a float between 2.0 and 23.9 in bits per sample, or an integer between 24 and 9600 in kilobits per second" 1>&2 ; exit $EX_USAGE ;; + *) printMessage 'error' 'usage' 'bad_value' "$errormsg target bitrate for $c must be a float between 2.0 and 23.9 in bits per sample, or an integer between 24 and 9600 in kilobits per second" ; exit $EX_USAGE ;; esac if [ "${v%.*}" -lt 2 -o "${v%.*}" -gt 9600 ]; then - echo "$errormsg target bitrate for $c must be a float between 2.0 and 23.9 in bits per sample, or an integer between 24 and 9600 in kilobits per second" 1>&2 ; exit $EX_USAGE + printMessage 'error' 'usage' 'bad_value' "$errormsg target bitrate for $c must be a float between 2.0 and 23.9 in bits per sample, or an integer between 24 and 9600 in kilobits per second" ; exit $EX_USAGE fi bitrate_WavPackLossy="$v" fi @@ -493,7 +929,7 @@ else case "$v" in 1[6-9]|[2-9][0-9]|[1-2][0-9][0-9]|3[0-1][0-9]|320) bitrate_LAME="$v" ;; - *) echo "$errormsg constant bitrate for $c must be an integer between 16 and 320 in kilobits per second" 1>&2 ; exit $EX_USAGE ;; + *) printMessage 'error' 'usage' 'bad_value' "$errormsg constant bitrate for $c must be an integer between 16 and 320 in kilobits per second" ; exit $EX_USAGE ;; esac fi ;; @@ -504,7 +940,7 @@ else case "$v" in [0-9]|[1-9][0-9]|[1-2][0-9][0-9]|3[0-1][0-9]|320) bitrate_AAC="$v" ;; - *) echo "$errormsg constant bitrate for $c must be an integer between 0 and 320 in kilobits per second" 1>&2; exit $EX_USAGE ;; + *) printMessage 'error' 'usage' 'bad_value' "$errormsg constant bitrate for $c must be an integer between 0 and 320 in kilobits per second" ; exit $EX_USAGE ;; esac fi ;; @@ -515,7 +951,7 @@ else case "$v" in [0-9]|[1-9][0-9]|[1-2][0-9][0-9]|3[0-1][0-9]|320) bitrate_QAAC="$v" ;; - *) echo "$errormsg constant bitrate for $c must be an integer between 0 and 320 in kilobits per second" 1>&2; exit $EX_USAGE ;; + *) printMessage 'error' 'usage' 'bad_value' "$errormsg constant bitrate for $c must be an integer between 0 and 320 in kilobits per second" ; exit $EX_USAGE ;; esac fi ;; @@ -526,7 +962,7 @@ else case "$v" in 3[2-9][4-9][0-9]|[1-4][0-9][0-9]|500) bitrate_OggVorbis="$v" ;; - *) echo "$errormsg constant bitrate for $c must be an integer between 32 and 500 in kilobits per second" 1>&2; exit $EX_USAGE ;; + *) printMessage 'error' 'usage' 'bad_value' "$errormsg constant bitrate for $c must be an integer between 32 and 500 in kilobits per second" ; exit $EX_USAGE ;; esac fi ;; @@ -537,10 +973,10 @@ else case "$v" in [6-9]|[1-9][0-9]|[1-3][0-9][0-9]) true ;; - *) echo "$errormsg constant bitrate for $c must be an integer between 6 and 320 in kilobits per second" 1>&2; exit $EX_USAGE ;; + *) printMessage 'error' 'usage' 'bad_value' "$errormsg constant bitrate for $c must be an integer between 6 and 320 in kilobits per second" ; exit $EX_USAGE ;; esac if [ $v -lt 6 -o $v -gt 320 ]; then - echo "$errormsg constant bitrate for $c must be an integer between 6 and 320 in kilobits per second" 1>&2; exit $EX_USAGE + printMessage 'error' 'usage' 'bad_value' "$errormsg constant bitrate for $c must be an integer between 6 and 320 in kilobits per second" ; exit $EX_USAGE fi bitrate_Opus="$v" fi @@ -563,7 +999,7 @@ else case "$v" in [8-9]|[1-9][0-9]|[1-2][0-9][0-9]|30[0-9]|310) average_bitrate_LAME="$v" ;; - *) echo "$errormsg average bitrate for $c must be an integer between 8 and 310 in kilobits per second" 1>&2 ; exit $EX_USAGE ;; + *) printMessage 'error' 'usage' 'bad_value' "$errormsg average bitrate for $c must be an integer between 8 and 310 in kilobits per second" ; exit $EX_USAGE ;; esac fi ;; @@ -574,7 +1010,7 @@ else case "$v" in [0-9]|[1-9][0-9]|[1-2][0-9][0-9]|3[0-1][0-9]|320) average_bitrate_AAC="$v" ;; - *) echo "$errormsg average bitrate for $c must be an integer between 0 and 320 in kilobits per second" 1>&2; exit $EX_USAGE ;; + *) printMessage 'error' 'usage' 'bad_value' "$errormsg average bitrate for $c must be an integer between 0 and 320 in kilobits per second" ; exit $EX_USAGE ;; esac fi ;; @@ -585,7 +1021,7 @@ else case "$v" in [0-9]|[1-9][0-9]|[1-2][0-9][0-9]|3[0-1][0-9]|320) average_bitrate_QAAC="$v" ;; - *) echo "$errormsg average bitrate for $c must be an integer between 0 and 320 in kilobits per second" 1>&2; exit $EX_USAGE ;; + *) printMessage 'error' 'usage' 'bad_value' "$errormsg average bitrate for $c must be an integer between 0 and 320 in kilobits per second" ; exit $EX_USAGE ;; esac fi ;; @@ -596,7 +1032,7 @@ else case "$v" in 3[2-9][4-9][0-9]|[1-4][0-9][0-9]|500) average_bitrate_OggVorbis="$v" ;; - *) echo "$errormsg average bitrate for $c must be an integer between 32 and 500 in kilobits per second" 1>&2; exit $EX_USAGE ;; + *) printMessage 'error' 'usage' 'bad_value' "$errormsg average bitrate for $c must be an integer between 32 and 500 in kilobits per second" ; exit $EX_USAGE ;; esac fi ;; @@ -607,10 +1043,10 @@ else case "$v" in [6-9]|[1-9][0-9]|[1-3][0-9][0-9]) true ;; - *) echo "$errormsg average bitrate for $c must be an integer between 6 and 320 in kilobits per second" 1>&2; exit $EX_USAGE ;; + *) printMessage 'error' 'usage' 'bad_value' "$errormsg average bitrate for $c must be an integer between 6 and 320 in kilobits per second" ; exit $EX_USAGE ;; esac if [ $v -lt 6 -o $v -gt 320 ]; then - echo "$errormsg average bitrate for $c must be an integer between 6 and 320 in kilobits per second" 1>&2; exit $EX_USAGE + printMessage 'error' 'usage' 'bad_value' "$errormsg average bitrate for $c must be an integer between 6 and 320 in kilobits per second" ; exit $EX_USAGE fi average_bitrate_Opus="$v" fi @@ -626,7 +1062,7 @@ cbr|abr|vbr) mode="$( echo "$mode" | tr '[:lower:]' '[:upper:]' )" ;; CBR|ABR|VBR) true ;; '') mode='VBR' ;; - *) echo "Configuration error (caudecrc): bitrate mode for $c must be one of VBR, ABR or CBR" 1>&2; exit $EX_USAGE ;; + *) printMessage 'error' 'usage' 'bad_value' "Configuration error (caudecrc): bitrate mode for $c must be one of VBR, ABR or CBR" ; exit $EX_USAGE ;; esac case "$c" in @@ -640,9 +1076,14 @@ cleanExit () { - if [ -n "$CAUDECDEBUG" -a $1 -ne $EX_OK -a $1 -ne $EX_INTERRUPT -a -e "$errorLogFile" ]; then - echo 1>&2 - cat "$errorLogFile" 1>&2 + local nLines=0 + + if [ -n "$CAUDECDEBUG" -a "$verbose" = 'true' -a "$outputMode" != 'machine' -a $1 -ne $EX_OK -a $1 -ne $EX_INTERRUPT -a -e "$errorLogFile" ]; then + nLines="$( cat "$errorLogFile" 2>/dev/null | wc -l | tr -cd '0-9' )" + if [ $nLines -gt 0 ]; then + echo -e "\nError Log:\n================================================================================" 1>&2 + cat "$errorLogFile" 1>&2 + fi fi test -n "$SWAPDIR" && rm -rf "$SWAPDIR" @@ -676,15 +1117,15 @@ cleanAbort () { echo - echo -e "${WG} * ${NM}Aborting..." 1>&2 + printMessage 'abort' kill $( jobs -p ) >/dev/null 2>&1 cleanExit $EX_INTERRUPT } startTimer () { - if [ "$OS" = 'Linux' ]; then - timer1="$( date '+%s.%N' )" + if [ $gnudate = true ]; then + timer1="$( $datecmd '+%s.%N' )" else timer1="$( date '+%s' ).0" fi @@ -694,46 +1135,91 @@ { local seconds timer2 - if [ "$OS" = 'Linux' ]; then - timer2="$( date '+%s.%N' )" + if [ $gnudate = true ]; then + timer2="$( $datecmd '+%s.%N' )" else timer2="$( date '+%s' ).0" fi seconds="$( printf 'scale=6; %.6f - %.6f\n' "$timer2" "$timer1" | bc )" - printf '%s: %.2f seconds\n' "$1" $seconds + printf '%s: %.2f seconds\n' "$1" $seconds 1>&2 +} + +getInputFiles () +{ + local ec=$EX_OK + + nTracks=0 + for a in "${inputFilesAndDirs[@]}"; do + if [ -d "$a" ]; then + if [ -r "$a" ]; then + if [ "$OS" = 'Linux' ]; then + while IFS= read -d $'\0' -r file ; do + if [ -f "$file" -a -r "$file" ]; then + inputFiles[$nTracks]="$file" + ((nTracks++)) + fi + done < <(find "$a" -type f -regex '.*\.\(wav\|aiff\|caf\|flac\|wv\|ape\|tak\|m4a\|mp3\|ogg\|mpc\|opus\)$' -print0 2>/dev/null) + else + while IFS= read -d $'\0' -r file ; do + if [ -f "$file" -a -r "$file" ]; then + inputFiles[$nTracks]="$file" + ((nTracks++)) + fi + done < <(find -E "$a" -type f -regex '.*\.(wav|aiff|caf|flac|wv|ape|tak|m4a|mp3|ogg|mpc|opus)$' -print0 2>/dev/null) + fi + else + printMessage 'warning' 'usage' 'filesystem' "path:${a}" 'directory is not readable (permission denied).' ; ec=$EX_NOINPUT + fi + else + inputFiles[$nTracks]="$a" + ((nTracks++)) + fi + done + + unset inputFilesAndDirs + if [ $ec -eq $EX_OK ]; then + return $EX_OK + else + printMessage 'abort' 'verbose' + cleanExit $ec + fi } checkInputFiles () { - local f ec=$EX_OK bn dn cdpath pdpath basenames='' m4aFileType + local f ec=$EX_OK bn dn cdpath pdpath basenames='' isf=0 keepInputFile errorMSG gotExistingFiles=false nChannels=0 if [ $nTracks -gt $maxInputFiles ]; then - echo -e "${GR} * ${WG}WG${NM}: the number of input files ($nTracks) is greater than maxInputFiles=${maxInputFiles}." 1>&2 - echo 1>&2 - echo "If you're processing multiple directories, try running caudec in a loop: -find * -type f -name '*.flac' -exec dirname '{}' ';' | sort -u | while read d -do caudec -P /some/dir -c ogg \"\$d\"/*.flac ; done" 1>&2 + printMessage 'warning' 'usage' 'quota' "the number of input files ($nTracks) is greater than maxInputFiles=${maxInputFiles}." + printMessage 'abort' + if [ "$outputMode" != 'machine' ]; then + echo 1>&2 + echo "If you're processing multiple directories, try running caudec in a loop: +find * -type f -name '*.flac' -exec dirname '{}' ';' | sort -u | while read d; +do echo \"\$d\"; caudec -s -K -P /some/destination/dir -c ogg \"\$d\"/*.flac; done" 1>&2 + fi cleanExit $EX_USAGE fi - for f in "${sourceFiles[@]}" ; do + for f in "${inputFiles[@]}" ; do + keepInputFile=false if [ ! -e "$f" ]; then - echo -e "${GR} * ${WG}WG ${CY}${f}${NM}: no such file" 1>&2; ec=$EX_NOINPUT + printMessage 'warning' 'usage' 'filesystem' "path:${f}" 'no such file.' ; ec=$EX_NOINPUT elif [ ! -f "$f" ]; then - echo -e "${GR} * ${WG}WG ${CY}${f}${NM}: not a regular file" 1>&2; ec=$EX_DATAERR + printMessage 'warning' 'usage' 'filesystem' "path:${f}" 'not a regular file.' ; ec=$EX_DATAERR elif [ ! -r "$f" ]; then - echo -e "${GR} * ${WG}WG ${CY}${f}${NM}: cannot open file for reading" 1>&2; ec=$EX_NOINPUT + printMessage 'warning' 'usage' 'filesystem' "path:${f}" 'cannot open file for reading (permission denied).' ; ec=$EX_NOINPUT else dn="$( dirname "$f" )" if [ $copyPath = true ]; then cdpath='/./' pdpath='/../' - if [ "${dn:0:2}" = './' -o "${dn:0:3}" = '../' -o "$dn" != "${dn//$cdpath/@}" -o "$dn" != "${dn//$pdpath/@}" ]; then - echo -e "${GR} * ${WG}WG ${CY}${f}${NM}: can't use paths containing ./ or ../ with caudec -P" 1>&2; ec=$EX_USAGE + if [ "${f:0:2}" = './' -o "${f:0:3}" = '../' -o "$dn" != "${dn//$cdpath/@}" -o "$dn" != "${dn//$pdpath/@}" ]; then + printMessage 'warning' 'usage' 'filesystem' "path:${f}" "can't use paths containing ./ or ../ with caudec -P." ; ec=$EX_USAGE continue fi fi - if [ -n "$outputCodecs" ]; then + if [ -n "$outputCodecs" ]; then # transcode bn="$( basename "$f" )" bn="${bn%.*}" if [ $copyPath = true ]; then @@ -742,53 +1228,166 @@ bn="x${bn%.lossy}Y"; fi if [ "$basenames" != "${basenames//$bn/@}" ]; then - echo -e "${GR} * ${WG}WG ${CY}${f##*/}${NM}: there's already an input file with the same basename" 1>&2; ec=$EX_DATAERR + printMessage 'warning' 'usage' 'filesystem' "file:${f}" "there's already an input file with the same basename." ; ec=$EX_DATAERR continue fi basenames="${basenames}${bn}" - fi - case "$f" in - *.wav) - if [ $computeReplaygain = true -o $checkFiles = true ]; then - echo -e "${GR} * ${WG}WG ${CY}${f##*/}${NM}: unsupported format" 1>&2; ec=$EX_DATAERR - fi - if [ -z "$outputCodecs" -a $actionHash = true ]; then - echo -e "${GR} * ${WG}WG ${CY}${f##*/}${NM}: unsupported format" 1>&2; ec=$EX_DATAERR + if [ $keepExistingFiles = true -o $keepNewerFiles = true ]; then + sourceFile="$f" + for outputCodec in $outputCodecs; do + getFileProps "$sourceFile" "$outputCodec" + if [ ! -e "$destFile" ]; then + keepInputFile=true; break + elif [ $keepNewerFiles = true ]; then + if ! isFileNewer "$destFile" "$sourceFile"; then + keepInputFile=true; break + fi + fi + done + if [ $keepInputFile = false ]; then + gotExistingFiles=true + continue fi - ;; + fi + keepInputFile=false + fi - *.m4a) - if [ $checkFiles = true ]; then - echo -e "${GR} * ${WG}WG ${CY}${f##*/}${NM}: unsupported format" 1>&2; ec=$EX_DATAERR - elif [ $actionHash = true ]; then - if which "alac" >/dev/null 2>&1 ; then - m4aFileType="$( alac -t "$f" 2>/dev/null )" - if [ "$m4aFileType" != 'file type: alac' ]; then - echo -e "${GR} * ${WG}WG ${CY}${f##*/}${NM}: unsupported format" 1>&2; ec=$EX_DATAERR + if [ -n "$outputCodecs" ]; then # transcode + case "$f" in + *.wav|*.aiff|*.caf|*.flac|*.wv|*.ape|*.tak) keepInputFile=true ;; + *.m4a) + if isALAC "$f"; then + keepInputFile=true + else + if [ $ignoreUnsupportedFiles = true ]; then + continue + else + printMessage 'warning' 'processing' 'unsupported' "file:${f}" 'unsupported format (not ALAC).' ; ec=$EX_DATAERR ; continue fi fi - fi - ;; + ;; + *) + if [ $ignoreUnsupportedFiles = true ]; then + continue + else + printMessage 'warning' 'processing' 'unsupported' "file:${f}" 'unsupported format.' ; ec=$EX_DATAERR ; continue + fi + ;; + esac + else # actions other than transcoding + if [ $checkFiles = true -o $actionHash = true ]; then + case "$f" in + *.flac|*.wv|*.ape|*.tak) keepInputFile=true ;; + *.m4a) + if isALAC "$f"; then + keepInputFile=true + else + if [ $ignoreUnsupportedFiles = true ]; then + continue + else + printMessage 'warning' 'processing' 'unsupported' "file:${f}" 'unsupported format (not ALAC).' ; ec=$EX_DATAERR ; continue + fi + fi + ;; + *) + if [ $ignoreUnsupportedFiles = true ]; then + continue + else + printMessage 'warning' 'processing' 'unsupported' "file:${f}" 'unsupported format.' ; ec=$EX_DATAERR ; continue + fi + ;; + esac + fi - *.flac|*.wv|*.ape|*.tak) continue ;; + if [ $computeSoundcheck = true ]; then + case "$f" in + *.mp3|*.m4a) keepInputFile=true ;; + *) + if [ $ignoreUnsupportedFiles = true ]; then + continue + else + printMessage 'warning' 'processing' 'unsupported' "file:${f}" 'unsupported format.' ; ec=$EX_DATAERR ; continue + fi + ;; + esac + fi - *.mp3|*.ogg) - if [ $computeReplaygain = true ]; then - continue - else - echo -e "${GR} * ${WG}WG ${CY}${f##*/}${NM}: unsupported format" 1>&2; ec=$EX_DATAERR - fi - ;; - *) echo -e "${GR} * ${WG}WG ${CY}${f##*/}${NM}: unsupported format" 1>&2; ec=$EX_DATAERR ;; - esac + if [ $computeReplaygain = true ]; then + case "$f" in + *.flac|*.wv|*.ape|*.tak|*.m4a|*.mp3|*.ogg) + nChannels="$( getNumberOfChannels "$f" )" + if [ $nChannels -le 2 ]; then + keepInputFile=true + else + if [ $ignoreUnsupportedFiles = true ]; then + continue + else + printMessage 'warning' 'processing' 'unsupported' "file:${f}" 'unsupported format (neither mono or stereo).' ; ec=$EX_DATAERR ; continue + fi + fi + ;; + + *) + if [ $ignoreUnsupportedFiles = true ]; then + continue + else + printMessage 'warning' 'processing' 'unsupported' "file:${f}" 'unsupported format.' ; ec=$EX_DATAERR ; continue + fi + ;; + esac + fi + + if [ $applyGain = true ]; then + case "$f" in + *.mp3) keepInputFile=true ;; + *.m4a) + if isAAC "$f"; then + keepInputFile=true + else + if [ $ignoreUnsupportedFiles = true ]; then + continue + else + printMessage 'warning' 'processing' 'unsupported' "file:${f}" 'unsupported format (not AAC).' ; ec=$EX_DATAERR ; continue + fi + fi + ;; + *) + if [ $ignoreUnsupportedFiles = true ]; then + continue + else + printMessage 'warning' 'processing' 'unsupported' "file:${f}" 'unsupported format.' ; ec=$EX_DATAERR ; continue + fi + ;; + esac + fi + fi + fi + if [ $keepInputFile = true ]; then + sourceFiles[$isf]="$f" + ((isf++)) fi done + unset inputFiles + if [ $ec -ne $EX_OK ]; then - echo -e "${GR} *${NM} Aborting." 1>&2 + printMessage 'abort' + cleanExit $ec + elif [ $isf -eq 0 ]; then + if [ $gotExistingFiles = true ]; then + if [ $keepExistingFiles = true ]; then + printMessage 'warning' 'usage' 'filesystem' 'verbose' "-k: all destination files exist already, nothing to do." + elif [ $keepNewerFiles = true ]; then + printMessage 'warning' 'usage' 'filesystem' 'verbose' "-K: all destination files are newer than their source, nothing to do." + fi + else + printMessage 'warning' 'usage' 'filesystem' 'verbose' "no files to work with, nothing to do." + fi + printMessage 'abort' 'verbose' cleanExit $ec else + nTracks="${#sourceFiles[@]}" return $EX_OK fi } @@ -803,23 +1402,33 @@ searchedBinaries="${searchedBinaries}${p}" case "$b" in *.exe) - if [ -f "${WIN64PATH}/drive_c/windows/${b}" -o -f "${WIN32PATH}/drive_c/windows/${b}" ]; then + if [ -d "$WIN64PATH" -a -e "${WIN64PATH}/drive_c/windows/${b}" ]; then + foundBinaries="${foundBinaries}${p}" + continue + elif [ -d "$WIN32PATH" -a -e "${WIN32PATH}/drive_c/windows/${b}" ]; then + foundBinaries="${foundBinaries}${p}" continue else rc=$EX_KO binaryMissing=true - echo -e "${GR} * ${WG}WG${NM} Binary \"${b}\" not found. Make sure it is present in either \"${WIN64PATH}/drive_c/windows/\" or \"${WIN32PATH}/drive_c/windows/\"." 1>&2 + printMessage 'warning' 'initialization' 'filesystem' "Binary \"${b}\" not found. Make sure it is present in either \"\${WIN64PATH}/drive_c/windows/\" or \"\${WIN32PATH}/drive_c/windows/\"." fi ;; *) if which "$b" >/dev/null 2>&1 ; then + foundBinaries="${foundBinaries}${p}" + if [ "$b" = 'wine' ]; then + findWineDirs + fi continue else rc=$EX_KO binaryMissing=true - echo -e "${GR} * ${WG}WG${NM} Binary \"${b}\" not found. Make sure it is in your \$PATH." 1>&2 + printMessage 'warning' 'initialization' 'filesystem' "Binary \"${b}\" not found. Make sure it is in your \$PATH." fi ;; esac + elif [ -z "$foundBinaries" -o "$foundBinaries" = "${foundBinaries//$p/@}" ]; then # binary was previously searched, but not found + rc=$EX_KO fi done @@ -828,36 +1437,36 @@ checkBinaries () { - local m4aFileType - searchedBinaries='' binaryMissing=false if ! which 'which' >/dev/null 2>&1 ; then - echo -e "${GR} * ${WG}WG${NM} Binary \"which\" not found. Make sure it is in your \$PATH." 1>&2 - echo -e "${GR} *${NM} Aborting." 1>&2 + printMessage 'warning' 'initialization' 'filesystem' "Binary \"which\" not found. Make sure it is in your \$PATH." + printMessage 'abort' cleanExit $EX_OSFILE fi if [ -z "$nTracks" ]; then - checkBinary 'uname' 'bc' 'grep' 'fgrep' 'sed' 'tr' 'cut' 'wc' 'df' 'stat' 'mktemp' 'xargs' 'shninfo' 'head' 'tail' 'sort' 'ps' - if [ "$OS" = 'Linux' ]; then - checkBinary 'readlink' - fi + checkBinary 'uname' 'bc' 'grep' "$sedcmd" 'tr' 'cut' 'wc' 'df' 'stat' 'mktemp' 'xargs' 'soxi' 'head' 'tail' 'sort' 'ps' 'find' else + for f in "${sourceFiles[@]}" ; do + case "$f" in + *.ape|*.m4a) checkBinary 'ffprobe' ; break ;; + esac + done + if [ $computeReplaygain = true ]; then for f in "${sourceFiles[@]}" ; do case "$f" in *.flac) checkBinary 'metaflac' ;; - *.wv) checkBinary 'apetag' 'wvgain' ;; - *.ape) checkBinary 'mac' 'apetag' 'wavegain' ;; - *.tak) checkBinary 'wine' 'Takc.exe' 'apetag' 'wavegain' ;; + *.wv) checkBinary 'APEv2' 'wvgain' ;; + *.ape) checkBinary 'mac' 'APEv2' 'wavegain' ;; + *.tak) checkBinary 'wine' 'Takc.exe' 'APEv2' 'wavegain' ;; *.m4a) - if checkBinary 'alac'; then - m4aFileType="$( alac -t "$f" 2>/dev/null )" - if [ "$m4aFileType" = 'file type: alac' ]; then - checkBinary 'wavegain' 'neroAacTag' - else + if checkBinary 'ffprobe'; then + if isALAC "$f"; then + checkBinary 'nohup' 'ffmpeg' 'wavegain' 'neroAacTag' + elif isAAC "$f"; then checkBinary 'neroAacDec' 'aacgain' 'neroAacTag' fi fi @@ -870,22 +1479,25 @@ checkBinary 'awk' fi elif [ $checkFiles = true ]; then + checkBinary 'sox' for f in "${sourceFiles[@]}" ; do case "$f" in - *.flac) checkBinary 'flac' ;; - *.wv) checkBinary 'wvunpack' ;; - *.ape) checkBinary 'mac' ;; - *.tak) checkBinary 'wine' 'Takc.exe' ;; + *.flac) checkBinary 'flac' 'metaflac' ;; + *.wv) checkBinary 'wvunpack' 'APEv2' ;; + *.ape) checkBinary 'mac' 'APEv2' ;; + *.tak) checkBinary 'wine' 'Takc.exe' 'APEv2' ;; + *.m4a) checkBinary 'nohup' 'ffmpeg' 'neroAacTag' ;; esac done - else + else # transcode for f in "${sourceFiles[@]}" ; do case "$f" in + *.aiff|*.caf) checkBinary 'sox' ;; *.flac) checkBinary 'flac' 'metaflac' ;; - *.wv) checkBinary 'wvunpack' 'apetag' ;; - *.ape) checkBinary 'mac' 'apetag' ;; - *.tak) checkBinary 'wine' 'Takc.exe' 'apetag' ;; - *.m4a) checkBinary 'alac' 'neroAacTag' ;; + *.wv) checkBinary 'wvunpack' 'APEv2' ;; + *.ape) checkBinary 'mac' 'APEv2' ;; + *.tak) checkBinary 'wine' 'Takc.exe' 'APEv2' ;; + *.m4a) checkBinary 'nohup' 'ffmpeg' 'neroAacTag' ;; esac done @@ -896,10 +1508,11 @@ for outputCodec in $outputCodecs; do case "$outputCodec" in + AIFF|CAF) checkBinary 'sox' ;; FLAC) checkBinary 'flac' ;; Flake) checkBinary 'flake' 'metaflac' ;; WavPack|WavPackHybrid|WavPackLossy) checkBinary 'wavpack' ;; - MonkeysAudio) checkBinary 'mac' 'apetag' ;; + MonkeysAudio) checkBinary 'mac' 'APEv2' ;; TAK) checkBinary 'wine' 'Takc.exe' ;; ALAC) checkBinary 'nohup' 'ffmpeg' 'neroAacTag' ;; lossyWAV) checkBinary 'wine' 'lossyWAV.exe' ;; @@ -917,20 +1530,36 @@ esac done - for h in $hashes; do - case $h in - CRC) checkBinary 'shncat' 'cksfv' ;; - MD5|SHA1) checkBinary 'shnhash' ;; - esac - done + if [ -n "$hashes" ]; then + checkBinary 'sox' + if [ "$OS" = 'Linux' ]; then + for h in $hashes; do + case $h in + CRC32) checkBinary 'cksfv' 'mkfifo' ;; + MD5) checkBinary 'md5sum' ;; + SHA1) checkBinary 'sha1sum' ;; + SHA256) checkBinary 'sha256sum' ;; + SHA512) checkBinary 'sha512sum' ;; + esac + done + else + for h in $hashes; do + case $h in + CRC32) checkBinary 'cksfv' 'mkfifo' ;; + MD5) checkBinary 'md5' ;; + SHA1|SHA256|SHA512) checkBinary 'shasum' ;; + esac + done + fi + fi - if [ -n "$bitDepth" -o -n "$samplingRate" ]; then + if [ -n "$bitDepth" -o -n "$samplingRate" -o "$convertToStereo" != 'false' ]; then checkBinary 'sox' fi fi if [ $binaryMissing = true ]; then - echo -e "${GR} *${NM} Aborting." 1>&2 + printMessage 'abort' cleanExit $EX_OSFILE else return $EX_OK @@ -1002,12 +1631,12 @@ fi if [ ! -d "$piddir" ]; then - echo -e "${GR} * ${WG}WG${NM} Couldn't create directory $piddir" 1>&2 - echo -e "${GR} *${NM} Aborting." 1>&2 + printMessage 'warning' 'initialization' 'filesystem' "path:${piddir}" "couldn't create directory." + printMessage 'abort' cleanExit $EX_CANTCREAT elif [ ! -w "$piddir" ]; then - echo -e "${GR} * ${WG}WG${NM} $piddir isn't writable." 1>&2 - echo -e "${GR} *${NM} Aborting." 1>&2 + printMessage 'warning' 'initialization' 'filesystem' "path:${piddir}" "directory is not writable (permission denied)." + printMessage 'abort' cleanExit $EX_NOPERM fi @@ -1017,12 +1646,12 @@ instanceDir="$( TMPDIR="$piddir" mktemp -d "${piddir}/instance.XXXXXXXX" 2>/dev/null )" if [ -z "$instanceDir" ]; then - echo -e "${GR} * ${WG}WG${NM} mktemp failed." 1>&2 - echo -e "${GR} *${NM} Aborting." 1>&2 + printMessage 'warning' 'initialization' 'filesystem' "path:${piddir}" "mktemp failed to create a directory (do you have write permissions?)." + printMessage 'abort' cleanExit $EX_OSERR elif [ ! -w "$instanceDir" ]; then - echo -e "${GR} * ${WG}WG${NM} $instanceDir isn't writable." 1>&2 - echo -e "${GR} *${NM} Aborting." 1>&2 + printMessage 'warning' 'initialization' 'filesystem' "path:${instanceDir}" "directory is not writable (permission denied)." + printMessage 'abort' cleanExit $EX_NOPERM fi chmod 0775 "$instanceDir" @@ -1031,18 +1660,16 @@ touch "$errorLogFile" echo "$$" > "${instanceDir}/PID" - nInstances=$( find "$piddir" -type d -name 'instance.*' 2>/dev/null | wc -l | tr -d ' ' ) + nInstances=$( find "$piddir" -type d -name 'instance.*' 2>/dev/null | wc -l | tr -cd '0-9' ) if [ $nInstances -le $maxInstances ]; then - nRunningProcesses=$( find "$piddir" -type f -name 'process.*' 2>/dev/null | wc -l | tr -d ' ' ) + nRunningProcesses="$( getNumberOfCaudecProcesses )" nAvail=$(( maxProcesses - nRunningProcesses )) if [ $nAvail -eq 0 ]; then - echo -e "${GR} * ${WG}WG${NM} There are too many caudec processes already running." 1>&2 - echo -e "${GR} *${NM} Aborting." 1>&2 + printMessage 'warning' 'usage' 'quota' "There are too many caudec processes already running." + printMessage 'abort' cleanExit $EX_TEMPFAIL elif [ $nProcesses -gt $nAvail ]; then - if [ $verbose = true ]; then - echo -e "${GR} * ${WG}WG${NM} Number of processes reduced to $nAvail in order to stay within limits." 1>&2 - fi + printMessage 'warning' 'usage' 'quota' 'verbose' "Number of processes reduced to $nAvail in order to stay within limits." setNProcesses $nAvail else setNProcesses $nProcesses @@ -1050,12 +1677,12 @@ return 0 else if [ $maxInstances -eq 1 ]; then - echo -e "${GR} * ${WG}WG${NM} Another instance of caudec is already running." 1>&2 + printMessage 'warning' 'usage' 'quota' "Another instance of caudec is already running." else - echo -e "${GR} * ${WG}WG${NM} Too many instances of caudec are already running." 1>&2 + printMessage 'warning' 'usage' 'quota' "Too many instances of caudec are already running." fi - echo -e "${GR} *${NM} You might want to increase the 'maxInstances' value in /etc/caudecrc or ~/.caudecrc." 1>&2 - echo -e "${GR} *${NM} Aborting." 1>&2 + printMessage 'info' 'stderr' "You might want to increase the 'maxInstances' value in /etc/caudecrc or ~/.caudecrc." + printMessage 'abort' cleanExit $EX_TEMPFAIL fi } @@ -1078,21 +1705,19 @@ which 'diskutil' >/dev/null 2>&1 || return $EX_KO touch "${piddir}/creatingRamdisk" - freePages="$( vm_stat | fgrep 'Pages free:' | tr -s ' ' | cut -d ' ' -f 3 )" + freePages="$( vm_stat | grep -F 'Pages free:' | tr -s ' ' | cut -d ' ' -f 3 )" freePages="${freePages%*.}" freeBytes=$(( freePages * 4096 )) sectors=$(( freeBytes / 512 * 90 / 100 )) # we want 90% of free RAM if [ $sectors -ge 20480 ]; then # >= 10 MiB - ramdiskDevice="$( hdiutil attach -nomount ram://${sectors} 2>> "$errorLogFile" | tr -d '\t' | sed -e 's@[ ]*$@@' )" + ramdiskDevice="$( hdiutil attach -nomount ram://${sectors} 2>> "$errorLogFile" | tr -d '\t' | $sedcmd -e 's@[ ]*$@@' )" test -z "$ramdiskDevice" && return $EX_KO diskutil eraseVolume HFS+ "$ramdiskName" "$ramdiskDevice" >/dev/null 2>> "$errorLogFile" ; ec=$? if [ $ec -eq $EX_OK -a -d "/Volumes/${ramdiskName}" ]; then echo "$ramdiskDevice" > "/Volumes/${ramdiskName}/device" - if [ $verbose = true -a -n "$CAUDECDEBUG" ]; then - mebibytes=$(( ((sectors * 512) + 524288) / 1048576 )) # add half a mebibyte for proper rounding - echo -e "${GR} *${NM} Set up ramdisk with $mebibytes MiB" - fi + mebibytes=$(( ((sectors * 512) + 524288) / 1048576 )) # add half a mebibyte for proper rounding + printMessage 'info' 'debug' "Set up ramdisk with $mebibytes MiB" rm -f "${piddir}/creatingRamdisk" return $EX_OK else @@ -1131,6 +1756,8 @@ { local i c mktempFS='other' devshmFS='other' mktempSpace devshmSpace ec=1 copyLockFile + tdirIsRamdisk=true + if [ -n "$TMPDIR" ]; then mktempDir="$TMPDIR"; else mktempDir='/tmp'; fi mktempFS="$( isRamdisk "$mktempDir" )" @@ -1139,37 +1766,33 @@ # Find out which of $mktempDir or /dev/shm is more appropriate # First they need to be on a tmpfs, then they need to be writable, then we choose the one with the most available space if [ -n "$CAUDECDIR" ]; then - if [ "${CAUDECDIR%/}" != "$CAUDECDIR" ]; then - CAUDECDIR="${CAUDECDIR%/}" + if [ "$CAUDECDIR" != '/' ]; then + CAUDECDIR="${CAUDECDIR%/}" # remove trailing slash fi - - if [ "$OS" = 'Linux' ]; then - CAUDECDIR="$( readlink -f "$CAUDECDIR" )" - elif [ "${CAUDECDIR:0:1}" != '/' ]; then + if [ "${CAUDECDIR:0:1}" != '/' ]; then # if not an absolute path, prepend current dir CAUDECDIR="${PWD}/${CAUDECDIR}" fi if [ ! -e "$CAUDECDIR" ]; then - echo -e "${GR} * ${WG}WG${NM} $CAUDECDIR doesn't exist." 1>&2 - echo -e "${GR} *${NM} Aborting." 1>&2 + printMessage 'warning' 'initialization' 'filesystem' "path:${CAUDECDIR}" "CAUDECDIR doesn't exist." + printMessage 'abort' cleanExit $EX_OSFILE elif [ ! -d "$CAUDECDIR" ]; then - echo -e "${GR} * ${WG}WG${NM} $CAUDECDIR isn't a directory." 1>&2 - echo -e "${GR} *${NM} Aborting." 1>&2 + printMessage 'warning' 'initialization' 'filesystem' "path:${CAUDECDIR}" "CAUDECDIR is not a directory." + printMessage 'abort' cleanExit $EX_CANTCREAT elif [ ! -w "$CAUDECDIR" ]; then - echo -e "${GR} * ${WG}WG${NM} $CAUDECDIR isn't writable." 1>&2 - echo -e "${GR} *${NM} Aborting." 1>&2 + printMessage 'warning' 'initialization' 'filesystem' "path:${CAUDECDIR}" "CAUDECDIR is not writable (permission denied)." + printMessage 'abort' cleanExit $EX_NOPERM fi mktempDir="$CAUDECDIR" mktempFS="$( isRamdisk "$mktempDir" )" if [ "$mktempFS" != 'ramdisk' ]; then + tdirIsRamdisk=false preloadSources=false - if [ $verbose = true ]; then - echo -e "${GR} * ${WG}WG${NM} $CAUDECDIR isn't on a ramdisk." 1>&2 - echo -e "${GR} *${NM} Performance will likely suffer." 1>&2 - fi + printMessage 'warning' 'initialization' 'filesystem' 'verbose' "path:${CAUDECDIR}" "CAUDECDIR isn't on a ramdisk." + printMessage 'info' 'stderr' "Performance will likely suffer." fi elif [ "$mktempFS" = 'ramdisk' -a "$devshmFS" = 'ramdisk' ]; then # both filesystems are ramdisks if [ -w "$mktempDir" -a -w '/dev/shm' ]; then # both dirs are writable @@ -1184,67 +1807,67 @@ elif [ -w '/dev/shm' ]; then mktempDir='/dev/shm' else # neither dir is writable - echo -e "${GR} * ${WG}WG${NM} Neither ${mktempDir} or /dev/shm is writable." 1>&2 - echo -e "${GR} *${NM} Aborting." 1>&2 + printMessage 'warning' 'initialization' 'filesystem' "path:${mktempDir}" "directory is not writable (permission denied)." + printMessage 'warning' 'initialization' 'filesystem' "path:/dev/shm" "directory is not writable (permission denied)." + printMessage 'abort' cleanExit $EX_NOPERM fi elif [ "$mktempFS" = 'ramdisk' ]; then if [ ! -w "$mktempDir" ]; then - echo -e "${GR} * ${WG}WG${NM} ${mktempDir} isn't writable." 1>&2 - echo -e "${GR} *${NM} Aborting." 1>&2 + printMessage 'warning' 'initialization' 'filesystem' "path:${mktempDir}" "directory is not writable (permission denied)." + printMessage 'abort' cleanExit $EX_NOPERM fi elif [ "$devshmFS" = 'ramdisk' ]; then if [ -w '/dev/shm' ]; then mktempDir='/dev/shm' else - echo -e "${GR} * ${WG}WG${NM} /dev/shm isn't writable." 1>&2 - echo -e "${GR} *${NM} Aborting." 1>&2 + printMessage 'warning' 'initialization' 'filesystem' "path:/dev/shm" "directory is not writable (permission denied)." + printMessage 'abort' cleanExit $EX_NOPERM fi else # neither dir is on a tmpfs - if [ "$OS" = 'Darwin' ]; then # Mac OS X + if [ "$OS" = 'Darwin' ]; then # OS Ⅹ setupMacOSRamdisk ; ec=$? if [ $ec -eq $EX_OK ]; then mktempDir="/Volumes/${ramdiskName}" else + tdirIsRamdisk=false preloadSources=false - echo -e "${GR} * ${WG}WG${NM} Attempt to create a ramdisk failed." 1>&2 - echo -e "${GR} *${NM} Performance will likely suffer." 1>&2 + printMessage 'warning' 'initialization' 'filesystem' 'verbose' "Attempt to create a ramdisk failed." + printMessage 'info' 'stderr' "Performance will likely suffer." fi else + tdirIsRamdisk=false preloadSources=false - if [ $verbose = true ]; then - echo -e "${GR} * ${WG}WG${NM} Neither ${mktempDir} or /dev/shm is on a ramdisk." 1>&2 - echo -e "${GR} *${NM} Performance will likely suffer." 1>&2 - fi + printMessage 'warning' 'initialization' 'filesystem' 'verbose' "Neither \"${mktempDir}\" or \"/dev/shm\" is on a ramdisk." + printMessage 'info' 'stderr' "Performance will likely suffer." fi fi if [ ! -e "$mktempDir" ]; then - echo -e "${GR} * ${WG}WG${NM} $mktempDir doesn't exist." 1>&2 - echo -e "${GR} *${NM} Aborting." 1>&2 + printMessage 'warning' 'initialization' 'filesystem' "path:${mktempDir}" "directory doesn't exist." + printMessage 'abort' cleanExit $EX_OSFILE elif [ ! -d "$mktempDir" ]; then - echo -e "${GR} * ${WG}WG${NM} $mktempDir isn't a directory." 1>&2 - echo -e "${GR} *${NM} Aborting." 1>&2 + printMessage 'warning' 'initialization' 'filesystem' "path:${mktempDir}" "not a directory." + printMessage 'abort' cleanExit $EX_CANTCREAT elif [ ! -w "$mktempDir" ]; then - echo -e "${GR} * ${WG}WG${NM} $mktempDir isn't writable." 1>&2 - - echo -e "${GR} *${NM} Aborting." 1>&2 + printMessage 'warning' 'initialization' 'filesystem' "path:${mktempDir}" "directory is not writable (permission denied)." + printMessage 'abort' cleanExit $EX_NOPERM fi TDIR="$( TMPDIR="$mktempDir" mktemp -d "${mktempDir}/${me}.XXXXXXXX" 2>/dev/null )" if [ -z "$TDIR" ]; then - echo -e "${GR} * ${WG}WG${NM} mktemp failed." 1>&2 - echo -e "${GR} *${NM} Aborting." 1>&2 + printMessage 'warning' 'initialization' 'filesystem' "path:${mktempDir}" "mktemp failed to create a directory (do you have write permissions?)." + printMessage 'abort' cleanExit $EX_OSERR elif [ ! -w "$TDIR" ]; then - echo -e "${GR} * ${WG}WG${NM} $TDIR isn't writable." 1>&2 - echo -e "${GR} *${NM} Aborting." 1>&2 + printMessage 'warning' 'initialization' 'filesystem' "path:${TDIR}" "directory is not writable (permission denied)." + printMessage 'abort' cleanExit $EX_NOPERM fi @@ -1277,175 +1900,371 @@ fi for outputCodec in $outputCodecs; do touch "${TDIR}/${outputCodec}_${i}" - touch "${TDIR}/${outputCodec}_${i}_WAV_NEEDED" if [ "$outputCodec" = 'lossyFLAC' -o "$outputCodec" = 'lossyWV' -o "$outputCodec" = 'lossyTAK' ]; then touch "${TDIR}/${outputCodec}_${i}_LOSSYWAV_NEEDED" touch "${TDIR}/lossyWAV_${i}_WAV_NEEDED" + else + touch "${TDIR}/${outputCodec}_${i}_WAV_NEEDED" fi done done echo -n "0" > "${TDIR}/durations" echo -n "0" > "${TDIR}/readTimes" + touch "${TDIR}/transcodingErrorFiles" } -getDuration () +getNumberOfChannels () { - local duration minutes milliseconds bn - seconds=0 - bn="$( basename "$1" )" - shninfoFile="${TDIR}/${bn}.shninfo" - if [ ! -f "$shninfoFile" ]; then - shninfo "$1" > "$shninfoFile" 2>> "$errorLogFile" || return $EX_KO - fi - duration="$( fgrep 'Length:' "$shninfoFile" | cut -d ':' -f 2- | tr -d ' ' )" - if [ -n "$duration" ]; then - duration="${duration##* }" - minutes="${duration%:*}" ; minutes="${minutes#0}" - seconds="${duration#*:}" - milliseconds="${seconds#*.}" ; milliseconds="${milliseconds#0}" - seconds="${seconds%.*}" ; seconds="${seconds#0}" - seconds=$(( (minutes * 60) + seconds )) - if [ "${milliseconds:2:1}" = '' ]; then # CD frames, i.e. <= 75 - if [ $milliseconds -ge 38 ]; then - ((seconds++)) - fi - else # milliseconds - if [ $milliseconds -ge 500 ]; then - ((seconds++)) + local f="$1" c=0 bn='' dn='' + + case "$f" in + *.flac) c="$( metaflac --show-channels "$f" 2>/dev/null )" ;; + + *.wv) + if gotNewWavpack; then + c="$( wvunpack -f "$f" 2>/dev/null | cut -d ';' -f 4 )" + else + c="$( wvunpack -s "$f" 2>/dev/null | grep -E '^channels:' | tr -cd '0-9' )" fi - fi - fi + ;; + + *.tak) + bn="$( basename "$f" )" + dn="$( dirname "$f" )" + cd "$dn" + if gotNewTAK ; then + c="$( runWineExe Takc -fi -fim5 ".\\${bn}" 2>/dev/null | tr -d '\r' | cut -d ';' -f 4 )" + else + c="$( runWineExe Takc -fi -fim0 ".\\${bn}" 2>/dev/null | tr -d '\r' | grep -F 'Audio format:' | cut -d ',' -f 4 | tr -cd '0-9' )" + fi + cd "$OLDPWD" + ;; + + *) c="$( ffprobe -print_format 'default' -show_streams -select_streams 'a:0' "$f" 2>/dev/null | grep -F 'channels=' | tr -cd '0-9' )" ;; + esac + + if [ -z "$c" ]; then c=0; fi + echo "$c" } -getDecodedSize () -{ - local bd sr nChannels - getDuration "$1" - if [ $seconds -gt 0 ]; then # shninfo doesn't seem to support multi-channel WAV files - bd="$( fgrep 'Bits/sample:' "$shninfoFile" | cut -d ':' -f 2 )" - bd="${bd##* }" - sr="$( fgrep 'Samples/sec:' "$shninfoFile" | cut -d ':' -f 2 )" - sr="${sr##* }" - nChannels="$( fgrep 'Channels:' "$shninfoFile" | cut -d ':' -f 2 )" - nChannels="${nChannels##* }" - decodedSize=$(( seconds * nChannels * sr * (bd / 8) )) +getFlacDecodedSize () +{ + local b='' c='' samples='' data='' + data="$( metaflac --show-bps --show-channels --show-total-samples "$1" 2>/dev/null )" + if [ -n "$data" ]; then + for v in $data; do + if [ -z "$b" ]; then + b="$v" + elif [ -z "$c" ]; then + c="$v" + else + samples="$v" + fi + done + decodedSize=$(( samples * c * (b / 8) )) fi } getTakDecodedSize () { - local f d c + local bn dn data b c samples - f="$( basename "$1" )" - d="$( dirname "$1" )" - cd "$d" - c="$( WINEPREFIX="$takWinePrefix" $takWineExe takc -fim2 ".\\$f" 2>/dev/null | fgrep 'Compression:' | tr -s ' ' | cut -d ' ' -f 3 )" + bn="$( basename "$1" )" + dn="$( dirname "$1" )" + cd "$dn" + if gotNewTAK ; then + data="$( runWineExe Takc -fi -fim5 ".\\${bn}" 2>/dev/null | tr -d '\r\n' )" + if [ -n "$data" ]; then + b="$( echo "$data" | cut -d ';' -f 3 )" + c="$( echo "$data" | cut -d ';' -f 4 )" + samples="$( echo "$data" | cut -d ';' -f 5 )" + if [ -n "$b" -a -n "$c" -a -n "$samples" ]; then + decodedSize=$(( samples * c * (b / 8) )) + fi + fi + else + data="$( runWineExe Takc -fi -fim0 ".\\${bn}" 2>/dev/null | tr -d '\r' )" + if [ -n "$data" ]; then + b="$( echo "$data" | grep -F 'Audio format:' | cut -d ',' -f 3 | tr -cd '0-9' )" + c="$( echo "$data" | grep -F 'Audio format:' | cut -d ',' -f 4 | tr -cd '0-9' )" + samples="$( echo "$data" | grep -F 'Samples per channel:' | tr -cd '0-9' )" + if [ -n "$b" -a -n "$c" -a -n "$samples" ]; then + decodedSize=$(( samples * c * (b / 8) )) + fi + fi + fi cd "$OLDPWD" - if [ -n "$c" ]; then - decodedSize="$( echo "scale=0; $fsize * 100 / $c" | bc )" +} + +getWavpackDecodedSize () +{ + local ratio data b c samples + + if gotNewWavpack; then + data="$( wvunpack -f "$1" 2>/dev/null )" + if [ -n "$data" ]; then + b="$( echo "$data" | cut -d ';' -f 2 )" + c="$( echo "$data" | cut -d ';' -f 4 )" + samples="$( echo "$data" | cut -d ';' -f 6 )" + if [ -n "$b" -a -n "$c" -a -n "$samples" ]; then + decodedSize=$(( samples * c * (b / 8) )) + fi + fi + else + ratio="$( wvunpack -s "$1" 2>/dev/null | grep -F 'compression:' | cut -d ':' -f 2 | tr -d ' %' )" + ratio="$( echo "scale=2; 100 - $ratio" | bc )" + decodedSize="$( echo "scale=1; ($fsize * 100 / $ratio) + 0.5" | bc )" + decodedSize="${decodedSize%.*}" fi } -getFlacDecodedSize () +getFfprobeDecodedSize () { - local b c samples - b="$( metaflac --show-bps "$1" )" - c="$( metaflac --show-channels "$1" )" - samples="$( metaflac --show-total-samples "$1" )" - decodedSize=$(( samples * c * (b / 8) )) + local data='' b c samples + + data="$( ffprobe -print_format 'default' -show_streams -select_streams 'a:0' "$1" 2>/dev/null )" + if [ -n "$data" ]; then + b="$( echo "$data" | grep -F 'sample_fmt=' | cut -d '=' -f 2 | tr -cd '0-9' )" + if [ -z "$b" ]; then + b=16 + fi + c="$( echo "$data" | grep -F 'channels=' | cut -d '=' -f 2 | tr -cd '0-9' )" + samples="$( echo "$data" | grep -F 'duration_ts=' | cut -d '=' -f 2 | tr -cd '0-9' )" + if [ -n "$b" -a -n "$c" -a -n "$samples" ]; then + decodedSize=$(( samples * c * (b / 8) )) + fi + fi } -getWavpackDecodedSize () +getFfprobeResampledSize () { - local ratio - ratio="$( wvunpack -s "$1" 2>/dev/null | fgrep 'compression:' | cut -d ':' -f 2 | tr -d ' %' )" - ratio="$( echo "scale=2; 100 - $ratio" | bc )" - decodedSize="$( echo "scale=1; ($fsize * 100 / $ratio) + 0.5" | bc )" - decodedSize="${decodedSize%.*}" + local data='' sr b c samples + + data="$( ffprobe -print_format 'default' -show_streams -select_streams 'a:0' "$1" 2>/dev/null )" + if [ -n "$data" ]; then + samples="$( echo "$data" | grep -F 'duration_ts=' | cut -d '=' -f 2 | tr -cd '0-9' )" + if [ $convertToStereo = 'false' ]; then + c="$( echo "$data" | grep -F 'channels=' | cut -d '=' -f 2 | tr -cd '0-9' )" + else + c=2 + fi + if [ -n "$bitDepth" ]; then + b=$bitDepth + else + b="$( echo "$data" | grep -F 'sample_fmt=' | cut -d '=' -f 2 | tr -cd '0-9' )" + fi + if [ -n "$samplingRate" ]; then + osr="$( echo "$data" | grep -F 'sample_rate=' | cut -d '=' -f 2 | tr -cd '0-9' )" + sr=$samplingRate + if [ -n "$samples" -a -n "$osr" -a -n "$c" -a -n "$b" ]; then + resampledSize=$(( (samples * sr / osr) * c * (b / 8) )) + fi + else + if [ -n "$samples" -a -n "$c" -a -n "$b" ]; then + resampledSize=$(( samples * c * (b / 8) )) + fi + fi + fi } getFlacResampledSize () { - local sr b c samples - samples="$( metaflac --show-total-samples "$1" )" - c="$( metaflac --show-channels "$1" )" - if [ -n "$bitDepth" ]; then - b=$bitDepth - else - b="$( metaflac --show-bps "$1" )" - fi - if [ -n "$samplingRate" ]; then - osr="$( metaflac --show-sample-rate "$1" )" - sr=$samplingRate - resampledSize=$(( (samples * sr / osr) * c * (b / 8) )) - else - resampledSize=$(( samples * c * (b / 8) )) + local samples='' c='' b='' osr='' data='' + + data="$( metaflac --show-total-samples --show-channels --show-bps --show-sample-rate "$1" 2>/dev/null )" + if [ -n "$data" ]; then + for v in $data; do + if [ -z "$samples" ]; then + samples="$v" + elif [ -z "$c" ]; then + c="$v" + elif [ -z "$b" ]; then + b="$v" + else + osr="$v" + fi + done + + if [ $convertToStereo != 'false' ]; then c=2 ; fi + if [ -n "$bitDepth" ]; then b=$bitDepth ; fi + + if [ -n "$samplingRate" ]; then + resampledSize=$(( (samples * samplingRate / osr) * c * (b / 8) )) + else + resampledSize=$(( samples * c * (b / 8) )) + fi fi } getTakResampledSize () { - local f d str sr b c seconds - f="$( basename "$1" )" - d="$( dirname "$1" )" - cd "$d" - str="$( WINEPREFIX="$takWinePrefix" $takWineExe takc -fim2 ".\\$f" 2>/dev/null | fgrep 'Audio format:' | tr -d ' \r' | cut -d ':' -f 2 )" - seconds="$( WINEPREFIX="$takWinePrefix" $takWineExe takc -fim2 ".\\$f" 2>/dev/null | fgrep 'File duration:' | tr -d ' \r' | cut -d ':' -f 2 )" - seconds="${seconds%sec}" + local bn dn data samples c b osr sr + + bn="$( basename "$1" )" + dn="$( dirname "$1" )" + cd "$dn" + if gotNewTAK ; then + data="$( runWineExe Takc -fi -fim5 ".\\${bn}" 2>/dev/null | tr -d '\r\n' )" + if [ -n "$data" ]; then + samples="$( echo "$data" | cut -d ';' -f 5 )" + if [ $convertToStereo = 'false' ]; then + c="$( echo "$data" | cut -d ';' -f 4 )" + else + c=2 + fi + if [ -n "$bitDepth" ]; then + b=$bitDepth + else + b="$( echo "$data" | cut -d ';' -f 3 )" + fi + if [ -n "$samplingRate" ]; then + osr="$( echo "$data" | cut -d ';' -f 2 )" + sr=$samplingRate + if [ -n "$samples" -a -n "$sr" -a -n "$osr" -a -n "$c" -a -n "$b" ]; then + resampledSize=$(( (samples * sr / osr) * c * (b / 8) )) + fi + else + if [ -n "$samples" -a -n "$c" -a -n "$b" ]; then + resampledSize=$(( samples * c * (b / 8) )) + fi + fi + fi + else + data="$( runWineExe Takc -fi -fim0 ".\\${bn}" 2>/dev/null | tr -d '\r' )" + if [ -n "$data" ]; then + samples="$( echo "$data" | grep -F 'Samples per channel:' | tr -cd '0-9' )" + if [ $convertToStereo = 'false' ]; then + c="$( echo "$data" | grep -F 'Audio format:' | cut -d ',' -f 4 | tr -cd '0-9' )" + else + c=2 + fi + if [ -n "$bitDepth" ]; then + b=$bitDepth + else + b="$( echo "$data" | grep -F 'Audio format:' | cut -d ',' -f 3 | tr -cd '0-9' )" + fi + if [ -n "$samplingRate" ]; then + osr="$( echo "$data" | grep -F 'Audio format:' | cut -d ',' -f 2 | tr -cd '0-9' )" + sr=$samplingRate + if [ -n "$samples" -a -n "$sr" -a -n "$osr" -a -n "$c" -a -n "$b" ]; then + resampledSize=$(( (samples * sr / osr) * c * (b / 8) )) + fi + else + if [ -n "$samples" -a -n "$c" -a -n "$b" ]; then + resampledSize=$(( samples * c * (b / 8) )) + fi + fi + fi + fi cd "$OLDPWD" +} - if [ -n "$samplingRate" ]; then - sr=$samplingRate +getWavpackResampledSize () +{ + local bn dn data samples c b osr sr + + if gotNewWavpack ; then + data="$( wvunpack -f "$1" 2>/dev/null )" + if [ -n "$data" ]; then + samples="$( echo "$data" | cut -d ';' -f 6 )" + if [ $convertToStereo = 'false' ]; then + c="$( echo "$data" | cut -d ';' -f 4 )" + else + c=2 + fi + if [ -n "$bitDepth" ]; then + b=$bitDepth + else + b="$( echo "$data" | cut -d ';' -f 2 )" + fi + if [ -n "$samplingRate" ]; then + osr="$( echo "$data" | cut -d ';' -f 1 )" + sr=$samplingRate + if [ -n "$samples" -a -n "$sr" -a -n "$osr" -a -n "$c" -a -n "$b" ]; then + resampledSize=$(( (samples * sr / osr) * c * (b / 8) )) + fi + else + if [ -n "$samples" -a -n "$c" -a -n "$b" ]; then + resampledSize=$(( samples * c * (b / 8) )) + fi + fi + fi else - sr="$( echo "$str" | cut -d ',' -f 2 )"; sr="${sr%Hz}" + getSoxResampledSize "$1" fi +} +getSoxResampledSize () +{ + local osr b c samples out='' + + samples="$( soxi -s "$1" 2>/dev/null )" + if [ $convertToStereo = 'false' ]; then + c="$( soxi -c "$1" 2>/dev/null )" + else + c=2 + fi if [ -n "$bitDepth" ]; then b=$bitDepth else - b="$( echo "$str" | cut -d ',' -f 3 )"; b="${b%Bits}" + b="$( soxi -b "$1" 2>/dev/null )" + fi + osr="$( soxi -r "$1" 2>/dev/null )" + # check the output of soxi + for v in "$samples" "$c" "$b" "$osr"; do + case "$v" in + [1-9]*) true ;; # nothing to do + *) + if [ $unable = false ]; then + printMessage 'warning' 'initialization' 'internal' 'verbose' "unable to predict required space in $mktempDir" + unable=true + fi + return + ;; + esac + done + if [ -n "$samplingRate" ]; then + resampledSize=$(( (samples * samplingRate / osr) * c * (b / 8) )) + else + resampledSize=$(( samples * c * (b / 8) )) fi - - c="$( echo "$str" | cut -d ',' -f 4 )"; c="${c%Channels}" - resampledSize="$( echo "scale=0; $seconds * $sr * $c * ($b / 8)" | bc )" - resampledSize="${resampledSize%.*}" } -getResampledSize () +getStatBytes () { - local bd sr nChannels - getDuration "$1" - if [ $seconds -gt 0 ]; then # shninfo doesn't seem to support multi-channel WAV files - bd="$bitDepth" - if [ -z "$bitDepth" ]; then - bd="$( shninfo "$1" 2>/dev/null | fgrep 'Bits/sample:' | cut -d ':' -f 2 )" - bd="${bd##* }" - fi - sr="$samplingRate" - if [ -z "$samplingRate" ]; then - sr="$( shninfo "$1" 2>/dev/null | fgrep 'Samples/sec:' | cut -d ':' -f 2 )" - sr="${sr##* }" - fi - nChannels="$( shninfo "$1" 2>/dev/null | fgrep 'Channels:' | cut -d ':' -f 2 )" - nChannels="${nChannels##* }" - resampledSize=$(( seconds * nChannels * sr * (bd / 8) )) + if [ "$OS" = 'Linux' ]; then + statbytes="$( stat -c '%b * %B' "$1" | bc )" else - if [ $unable = false ]; then - echo -e "${GR} * ${WG}WG${NM} unable to predict required space in $mktempDir" 1>&2 - unable=true - fi + statbytes="$( stat -f '%b * 512' "$1" | bc )" fi } +getFileSize () +{ + local f="$1" + + getStatBytes "$f" + filesize=$statbytes + case "$f" in + *.wv) + if [ -e "${f}c" ]; then # is there a WavPack Hybrid correction file (.wvc)? + getStatBytes "${f}c" + filesize=$(( filesize + statbytes )) + fi + ;; + esac +} + checkFreeSpace () { - local f fsize fsizec nBytesFiles=0 otdev otdir kbytes requiredSpace=0 freeSpace=0 copySpace decodedSpace requiredDecodedSpace=0 requiredResampledSpace=0 decodedSize encodedSize requiredMiB freeMiB np=$nProcesses m4aFileType + local f fsize nBytesFiles=0 otdev otdir kbytes freeSpace=0 copySpace decodedSpace resampledSpace requiredDecodedSpace=0 requiredResampledSpace=0 requiredLossyWAVSpace=0 decodedSize encodedSize requiredMiB freeMiB np=$nProcesses + requiredSpace=0 if [ "$OS" = 'Linux' ]; then freeSpace="$( stat -f -c '%a * %S' "$TDIR" | bc )" else - freeSpace="$( df "$TDIR" 2>/dev/null | fgrep -v 'Available' | tr -s ' ' | cut -d ' ' -f 4 )" + freeSpace="$( df "$TDIR" 2>/dev/null | grep -Fv 'Available' | tr -s ' ' | cut -d ' ' -f 4 )" freeSpace=$(( freeSpace * 512 )) fi @@ -1473,201 +2292,237 @@ done fi - freeMiB=$(( (freeSpace + 524288) / 1048576 )) # add half a mebibyte for proper rounding + freeMiB=$(( (freeSpace + 524288) / 1048576 )) + # list all file sizes + echo -n > "${TDIR}/fileSizes" + echo -n > "${TDIR}/rgFileSizes" for f in "${sourceFiles[@]}" ; do - if [ "$OS" = 'Linux' ]; then - fsize="$( stat -c '%b * %B' "$f" | bc )" - if [ -e "${f}c" ]; then - fsizec="$( stat -c '%b * %B' "${f}c" | bc )" - fsize=$(( fsize + fsizec )) - fi - else - fsize="$( stat -f '%b * 512' "$f" | bc )" - if [ -e "${f}c" ]; then - fsizec="$( stat -f '%b * 512' "${f}c" | bc )" - fsize=$(( fsize + fsizec )) - fi + getFileSize "$f" + if [ "$1" = 'replaygain' ]; then # special case: only some formats need to be decoded + case "$f" in + *.tak|*.ape) echo "$filesize $f" >> "${TDIR}/rgFileSizes" ;; + *.m4a) if isALAC "$f"; then echo "$filesize $f" >> "${TDIR}/rgFileSizes" ; fi ;; + esac fi - echo "$fsize $f" >> "${TDIR}/fileSizes" + echo "$filesize $f" >> "${TDIR}/fileSizes" done - while (( nProcesses > 0 )); do + while (( nProcesses > 0 )); do # loop and reduce nProcesses with each turn, until the ramdisk requirements are low enough # At worst, we'll be processing $nProcesses files at once and they're going to be the largest of the bunch sort -n -r "${TDIR}/fileSizes" | head -n $nProcesses > "${TDIR}/sortedSizes" copySpace=0 if [ $preloadSources = true ]; then + # evaluate how much ramdisk space is needed to cache input files while read s f; do copySpace=$(( copySpace + s )) + if [ "$1" = 'replaygain' ]; then + case "$f" in + *.m4a) if isAAC "$f"; then copySpace=$(( copySpace + s )) ; fi ;; # aacgain creates a temporary copy + esac + fi + done < "${TDIR}/sortedSizes" + elif [ "$1" = 'replaygain' ]; then # MP3 and AAC files needs to be copied + while read s f; do + case "$f" in + *.mp3) copySpace=$(( copySpace + s )) ;; + *.m4a) if isAAC "$f"; then copySpace=$(( copySpace + s + s )) ; fi ;; # aacgain creates a temporary copy + esac done < "${TDIR}/sortedSizes" fi - if [ "$1" = 'testing' ]; then - requiredSpace=$copySpace + if [ "$1" = 'testing' ]; then # caudec -t + requiredDecodedSpace=$copySpace + while read fsize f; do + # estimate by default that the decoded WAV file is twice as large (50% compression ratio) as the losslessly compressed file (worst case scenario) + decodedSize=$(( fsize * 100 / 50 )) + case "$f" in + *.flac) getFlacDecodedSize "$f" ;; + *.tak) getTakDecodedSize "$f" ;; + *.wv) getWavpackDecodedSize "$f" ;; + *.ape|*.m4a) getFfprobeDecodedSize "$f" ;; + esac + # if none of the above functions apply, or succeeded, we already have a default $decodedSize from above + requiredDecodedSpace=$(( requiredDecodedSpace + decodedSize )) + done < "${TDIR}/sortedSizes" + requiredSpace=$requiredDecodedSpace if [ $requiredSpace -ge $freeSpace ]; then - setNProcesses $(( nProcesses - 1 )) - continue + # there is not enough ramdisk space to cache $nProcesses files (worst case scenario) + setNProcesses $(( nProcesses - 1 )) # reduce the number of concurrent processes + continue # try again from the start (while loop), with one less process else - break + break # we've got enough ramdisk space, get out of the while loop fi - elif [ "$1" = 'hashes' ]; then - requiredDecodedSpace=$copySpace decodedSpace=0 + elif [ "$1" = 'hashes' ]; then # caudec -H + requiredDecodedSpace=$copySpace while read fsize f; do - getMD5 "$f" - if [ "$hashes" = 'MD5' -a -n "$sourceMD5" ]; then - if [ $preloadSources = true ]; then - requiredDecodedSpace=$(( requiredDecodedSpace - fsize )) - fi - continue - fi + # estimate by default that the decoded WAV file is twice as large (50% compression ratio) as the losslessly compressed file (worst case scenario) decodedSize=$(( fsize * 100 / 50 )) case "$f" in *.flac) getFlacDecodedSize "$f" ;; *.tak) getTakDecodedSize "$f" ;; *.wv) getWavpackDecodedSize "$f" ;; - *.ape) getDecodedSize "$f" ;; + *.ape|*.m4a) getFfprobeDecodedSize "$f" ;; esac + # if none of the above functions apply, or succeeded, we already have a default $decodedSize from above requiredDecodedSpace=$(( requiredDecodedSpace + decodedSize )) done < "${TDIR}/sortedSizes" - if [ $requiredDecodedSpace -gt 0 ]; then - requiredDecodedSpace=$(( requiredDecodedSpace * 105 / 100 )) # add 5% of margin - fi +# if [ $requiredDecodedSpace -gt 0 ]; then +# requiredDecodedSpace=$(( requiredDecodedSpace * 105 / 100 )) # add 5% of margin just to be safe +# fi if [ $requiredDecodedSpace -ge $freeSpace ]; then - setNProcesses $(( nProcesses - 1 )) - continue - else - break - fi - elif [ "$1" = 'replaygain' ]; then - requiredDecodedSpace=$copySpace decodedSpace=0 + # there is not enough ramdisk space to cache and decode $nProcesses files simultaneously (worst case scenario) + setNProcesses $(( nProcesses - 1 )) # reduce the number of concurrent processes + continue # try again from the start (while loop), with one less process + else + break # we've got enough ramdisk space, get out of the while loop + fi + elif [ "$1" = 'replaygain' ]; then # caudec -g + requiredDecodedSpace=$copySpace + sort -n -r "${TDIR}/rgFileSizes" | head -n $nProcesses > "${TDIR}/rgSortedSizes" # only take into account files that will get decoded while read fsize f; do - case "$f" in + case "$f" in # filetypes other than those listed below don't need to be decoded *.tak) - decodedSize=$(( fsize * 100 / 50 )) + decodedSize=$(( fsize * 100 / 50 )) # estimate by default a 50% compression ratio getTakDecodedSize "$f" requiredDecodedSpace=$(( requiredDecodedSpace + decodedSize )) ;; *.ape) - decodedSize=$(( fsize * 100 / 50 )) - getDecodedSize "$f" + decodedSize=$(( fsize * 10 / 50 )) + getFfprobeDecodedSize "$f" requiredDecodedSpace=$(( requiredDecodedSpace + decodedSize )) ;; *.m4a) - m4aFileType="$( alac -t "$f" 2>> "$errorLogFile" )" - if [ "$m4aFileType" = 'file type: alac' ]; then - decodedSize=$(( fsize * 100 / 50 )) - else - decodedSize=$(( fsize * 100 / 12 )) - fi - requiredDecodedSpace=$(( requiredDecodedSpace + decodedSize )) - ;; - *.mp3) - decodedSize=$(( fsize * 100 / 12 )) + decodedSize=$(( fsize * 100 / 50 )) # file is ALAC, go with the usual lossless compression ratio estimation + getFfprobeDecodedSize "$f" requiredDecodedSpace=$(( requiredDecodedSpace + decodedSize )) ;; esac - done < "${TDIR}/sortedSizes" - requiredDecodedSpace=$(( requiredDecodedSpace * 105 / 100 )) # add 5% of margin + done < "${TDIR}/rgSortedSizes" +# requiredDecodedSpace=$(( requiredDecodedSpace * 105 / 100 )) # add 5% of margin just to be safe if [ $requiredDecodedSpace -ge $freeSpace ]; then - setNProcesses $(( nProcesses - 1 )) - continue - else - break - fi - elif [ "$1" = 'transcoding' ]; then + # there is not enough ramdisk space to cache and decode $nProcesses files simultaneously (worst case scenario) + setNProcesses $(( nProcesses - 1 )) # reduce the number of concurrent processes + continue # try again from the start (while loop), with one less process + else + break # we've got enough ramdisk space, get out of the while loop + fi + elif [ "$1" = 'transcoding' ]; then # caudec -c + # Stages of transcoding, and their respective ramdisk space requirements: + # 1) Cache source files, if requested. Requirement: size(cached file) + # 2) Decode the source (cached) file to WAV, then delete the cached file, if any. Requirement: size(cached file) + size(WAV) + # 3) Optionally, resample the WAV file, then delete the original WAV file. Requirement: size(WAV) + size(WAV_RESAMPLED) + # 4) Transcode the final WAV file. Requirement: size(WAV or WAV_RESAMPLED) + size(transcode) + + # we're in a loop, so we need to make sure to zero out temp files before usage + echo -n > "${TDIR}/decodedFileSizes" + echo -n > "${TDIR}/resampledFileSizes" + + # First the source file needs to be decoded. Estimate the size of the resulting WAV file: requiredDecodedSpace=$copySpace decodedSpace=0 while read fsize f; do case "$f" in *.flac) getFlacDecodedSize "$f" requiredDecodedSpace=$(( requiredDecodedSpace + decodedSize )) + decodedSpace=$(( decodedSpace + decodedSize )) # add up the space requirements for just the decoded WAV files, it will be needed for the next step ;; *.wv) getWavpackDecodedSize "$f" requiredDecodedSpace=$(( requiredDecodedSpace + decodedSize )) + decodedSpace=$(( decodedSpace + decodedSize )) ;; *.tak) - decodedSize=$(( fsize * 100 / 50 )) + decodedSize=$(( fsize * 100 / 50 )) # estimate a 50% compression ratio by default getTakDecodedSize "$f" requiredDecodedSpace=$(( requiredDecodedSpace + decodedSize )) + decodedSpace=$(( decodedSpace + decodedSize )) ;; - *.ape) + *.ape|*.m4a) decodedSize=$(( fsize * 100 / 50 )) - getDecodedSize "$f" + getFfprobeDecodedSize "$f" requiredDecodedSpace=$(( requiredDecodedSpace + decodedSize )) + decodedSpace=$(( decodedSpace + decodedSize )) ;; - *.m4a) - decodedSize=$(( fsize * 100 / 50 )) + *.aiff|*.caf) + decodedSize=$fsize requiredDecodedSpace=$(( requiredDecodedSpace + decodedSize )) + decodedSpace=$(( decodedSpace + decodedSize )) + ;; + *.wav) + decodedSize=$fsize # source file is a .wav file, its size is already included in $copySpace + if [ $preloadSources = true ]; then + decodedSpace=$(( decodedSpace + decodedSize )) + fi ;; - *.wav) decodedSize=$fsize ;; # already included in copySpace esac - decodedSpace=$(( decodedSpace + decodedSize )) + echo "$decodedSize $f" >> "${TDIR}/decodedFileSizes" # save the decoded sizes for later done < "${TDIR}/sortedSizes" - requiredDecodedSpace=$(( requiredDecodedSpace * 105 / 100 )) # add 5% of margin +# requiredDecodedSpace=$(( requiredDecodedSpace * 105 / 100 )) # add 5% of margin just to be safe if [ $requiredDecodedSpace -ge $freeSpace ]; then - setNProcesses $(( nProcesses - 1 )) - continue + # there is not enough ramdisk space to cache and decode $nProcesses files simultaneously (worst case scenario) + setNProcesses $(( nProcesses - 1 )) # reduce the number of concurrent processes + continue # try again from the start (while loop), with one less process fi - if [ -n "$bitDepth" -o -n "$samplingRate" ]; then # resampling - requiredResampledSpace=$decodedSpace resampledSpace=0 unable=false + # at this point, we no longer have to include $copySpace, since the cached files (if any) are deleted + if [ -n "$bitDepth" -o -n "$samplingRate" -o $convertToStereo != 'false' ]; then + # The WAV file needs to be resampled. Estimate the size of the resampled WAV file, and add it to the size of the original WAV file: + resampledSpace=0 unable=false while read fsize f; do - resampledSize=$fsize + # if we're unable to estimate the size of the resampled WAV file, we can't do much but assume that it is equal to the original WAV file (no resampling) + resampledSize="$( grep -F "$f" "${TDIR}/decodedFileSizes" | cut -d ' ' -f 1 )" case "$f" in *.flac) getFlacResampledSize "$f" ;; *.tak) getTakResampledSize "$f" ;; - *.wav|*.ape|*.wv) getResampledSize "$f" ;; + *.wv) getWavpackResampledSize "$f" ;; + *.wav|*.aiff|*.caf) getSoxResampledSize "$f" ;; + *.ape|*.m4a) getFfprobeResampledSize "$f" ;; *) if [ $unable = false ]; then - echo -e "${GR} * ${WG}WG${NM} unable to predict required space in $mktempDir" 1>&2 - unable=true + # only print that warning once + printMessage 'warning' 'initialization' 'internal' 'verbose' "unable to predict required space in $mktempDir" + unable=true # the warning won't be printed again fi ;; esac - resampledSpace=$(( resampledSpace + resampledSize )) + resampledSpace=$(( resampledSpace + resampledSize )) # add up the space requirements for just the resampled WAV files, it will be needed for the next step + echo "$resampledSize $f" >> "${TDIR}/resampledFileSizes" # save the resampled sizes for later done < "${TDIR}/sortedSizes" - requiredResampledSpace=$(( decodedSpace + resampledSpace )) - requiredResampledSpace=$(( requiredResampledSpace * 105 / 100 )) # add 5% of margin + requiredResampledSpace=$(( decodedSpace + resampledSpace )) # original WAV files + resampled WAV files +# requiredResampledSpace=$(( requiredResampledSpace * 105 / 100 )) # add 5% of margin just to be safe if [ $requiredResampledSpace -ge $freeSpace ]; then - setNProcesses $(( nProcesses - 1 )) - continue + # there is not enough ramdisk space to decode and resample $nProcesses files simultaneously (worst case scenario) + setNProcesses $(( nProcesses - 1 )) # reduce the number of concurrent processes + continue # try again from the start (while loop), with one less process fi decodedSpace=$resampledSpace - elif [ $applyGain = true ]; then - requiredResampledSpace=$(( decodedSpace * 2 )) - requiredResampledSpace=$(( requiredResampledSpace * 105 / 100 )) # add 5% of margin + mv "${TDIR}/resampledFileSizes" "${TDIR}/decodedFileSizes" + elif [ $applyGain = true ]; then # caudec -G + requiredResampledSpace=$(( decodedSpace * 2 )) # original WAV files + replaygained WAV files (they're the same size) + if [ $preloadSources = false ]; then + while read fsize f; do + case "$f" in + *.wav) # not included in decodedSpace + requiredResampledSpace=$(( requiredResampledSpace + fsize )) + decodedSpace=$(( decodedSpace + fsize )) + ;; + esac + done < "${TDIR}/sortedSizes" + fi +# requiredResampledSpace=$(( requiredResampledSpace * 105 / 100 )) # add 5% of margin just to be safe if [ $requiredResampledSpace -ge $freeSpace ]; then - setNProcesses $(( nProcesses - 1 )) - continue + # there is not enough ramdisk space to decode and replaygain $nProcesses files simultaneously (worst case scenario) + setNProcesses $(( nProcesses - 1 )) # reduce the number of concurrent processes + continue # try again from the start (while loop), with one less process fi fi + # At this point there's only one final WAV file remaining. requiredSpace=$decodedSpace - while read fsize f; do - if [ -n "$bitDepth" -o -n "$samplingRate" ]; then # resampling - resampledSize=$(( fsize * 100 / 50 )) - case "$f" in - *.flac) getFlacResampledSize "$f" ;; - *.tak) getTakResampledSize "$f" ;; - *.wav|*.wv|*.ape) getResampledSize "$f" ;; - esac - decodedSize=$resampledSize - else - decodedSize='' - case "$f" in - *.flac) getFlacDecodedSize "$f" ;; - *.wv) getWavpackDecodedSize "$f" ;; - *.tak) getTakDecodedSize "$f" ;; - *.ape) getDecodedSize "$f" ;; - *.m4a) decodedSize=$(( fsize * 100 / 50 )) ;; - *.wav) decodedSize=$fsize ;; - esac - if [ -z "$decodedSize" ]; then - decodedSize=$(( fsize * 100 / 50 )) - fi - fi + requiredLossyWAVSpace=$decodedSpace + while read decodedSize f; do sourceFilename="${f##*/}" sourceBasename="${sourceFilename%.*}" if [ "$sourceBasename" != "${sourceBasename%.lossy}" ]; then @@ -1676,13 +2531,31 @@ sourceIsLossyWAV=false fi if [ $nLossyWAV -ge 1 -a $sourceIsLossyWAV = false ]; then - requiredSpace=$(( requiredSpace + decodedSize )) # encoded lossyWAV is equivalent to decodedSize + # here some lossy* codec is requested, but the source WAV file isn't already lossyWAV + + # first, the source WAV file and the lossyWAV file will need to co-exist + requiredLossyWAVSpace=$(( requiredLossyWAVSpace + decodedSize )) # encoded lossyWAV is the same as decodedSize + + # let's see if any codec other than lossy* is requested, as well + for outputCodec in $outputCodecs ; do + case $outputCodec in + lossyWAV|lossyFLAC|lossyTAK|lossyWV) true ;; # nothing to do + *) # some codec other than lossy* is requested so… + # …we're going to need both the source WAV file, and the lossyWAV file, at once + requiredSpace=$(( requiredSpace + decodedSize )) # encoded lossyWAV is the same as decodedSize + break + ;; + esac + done + fi + # Estimate the size of the transcoded file, and add it to the WAV files for outputCodec in $outputCodecs ; do case $outputCodec in WAV) encodedSize=0 ;; - FLAC|Flake|WavPack|WavPackHybrid|MonkeysAudio|TAK|ALAC) encodedSize=$(( decodedSize * 80 / 100 )) ;; + AIFF|CAF) encodedSize=$decodedSize ;; + FLAC|Flake|WavPack|WavPackHybrid|MonkeysAudio|TAK|ALAC) encodedSize=$(( decodedSize * 80 / 100 )) ;; # guestimate a compression ratio of 80% lossyFLAC|lossyWV|lossyTAK|WavPackLossy) encodedSize=$(( decodedSize * 60 / 100 )) ;; LAME|WinLAME) encodedSize=$(( decodedSize * 25 / 100 )) ;; OggVorbis|WinVorbis) encodedSize=$(( decodedSize * 45 / 100 )) ;; @@ -1692,48 +2565,49 @@ esac requiredSpace=$(( requiredSpace + encodedSize )) done - done < "${TDIR}/sortedSizes" + done < "${TDIR}/decodedFileSizes" - requiredSpace=$(( requiredSpace * 105 / 100 )) # add 5% of margin +# requiredSpace=$(( requiredSpace * 105 / 100 )) # add 5% of margin just to be safe + if [ $requiredLossyWAVSpace -gt $requiredSpace ]; then + requiredSpace=$requiredLossyWAVSpace + fi if [ $requiredSpace -ge $freeSpace ]; then - setNProcesses $(( nProcesses - 1 )) - continue + # there is not enough ramdisk space to transcode $nProcesses files simultaneously (worst case scenario) + setNProcesses $(( nProcesses - 1 )) # reduce the number of concurrent processes + continue # try again from the start (while loop), with one less process else - break + break # we've got enough ramdisk space, get out of the while loop fi fi - done - - if [ $requiredDecodedSpace -ge $requiredSpace -a $requiredDecodedSpace -ge $requiredResampledSpace ]; then - requiredSpace=$requiredDecodedSpace - elif [ $requiredResampledSpace -ge $requiredSpace -a $requiredResampledSpace -ge $requiredDecodedSpace ]; then - requiredSpace=$requiredResampledSpace - fi + done # we're done looping and reducing the number of concurrent processes (if necessary) + requiredSpace="$( echo -e "${requiredSpace}\n${requiredDecodedSpace}\n${requiredResampledSpace}" | sort -n | tail -n 1 )" + requiredSpace=$(( requiredSpace + (10 * 1024 * 1024) )) # add 10 MiB for good measure requiredMiB=$(( (requiredSpace + 524288) / 1048576 )) # add half a mebibyte for rounding - if [ $requiredSpace -lt $freeSpace ]; then - if [ $verbose = true ]; then - if [ $np -gt $nProcesses ]; then - echo -e "${GR} * ${WG}WG${NM} Number of processes reduced to $nProcesses due to insufficient space in $mktempDir" 1>&2 - echo -e "${GR} *${NM} Projected required space: $requiredMiB MiB, $freeMiB MiB available." 1>&2 - elif [ -n "$CAUDECDEBUG" ]; then - echo -e "${GR} *${NM} Projected required space: $requiredMiB MiB, $freeMiB MiB available." - fi + if [ $requiredSpace -lt $freeSpace ]; then # we've got enough ramdisk space + if [ $np -gt $nProcesses ]; then + printMessage 'warning' 'initialization' 'filesystem' 'verbose' "Number of processes reduced to $nProcesses due to insufficient space in $mktempDir" + printMessage 'info' 'stderr' "Projected required space: $requiredMiB MiB (available: $freeMiB MiB)" + printMessage 'info' 'stderr' "You might want to set preloadSources to false in ~/.caudecrc or /etc/caudecrc." + else + printMessage 'info' 'debug' "Projected required space: $requiredMiB MiB (available: $freeMiB MiB)" fi echo $requiredSpace > "${instanceDir}/bytes" return $EX_OK - else - echo -e "${GR} * ${WG}WG${NM} Insufficient space in ${mktempDir}! $requiredMiB MiB required, $freeMiB MiB available." 1>&2 + else # not enough ramdisk space, even after reducing the number of concurrent processes to 1 + printMessage 'warning' 'initialization' 'filesystem' 'verbose' "Insufficient space in ${mktempDir}! $requiredMiB MiB required, $freeMiB MiB available." + printMessage 'info' 'stderr' "You might want to set preloadSources to false in ~/.caudecrc or /etc/caudecrc." + # try other temporary dirs, maybe they have more available space if [ -n "$CAUDECDIR" ]; then if [ "$CAUDECDIR" = "$TMPDIR" -a "$CAUDECDIR" != '/tmp' ]; then CAUDECDIR='/tmp' rm -rf "$TDIR" - echo -e "${GR} *${NM} Switching to $CAUDECDIR." 1>&2 + printMessage 'info' 'stderr' "Switching to $CAUDECDIR." setNProcesses $oProcesses setupSwapdir checkFreeSpace "$1" else - echo -e "${GR} *${NM} Aborting." 1>&2 + printMessage 'abort' cleanExit $EX_CANTCREAT fi else @@ -1742,11 +2616,11 @@ elif [ "$mktempDir" != '/tmp' ]; then CAUDECDIR='/tmp' else - echo -e "${GR} *${NM} Aborting." 1>&2 + printMessage 'abort' cleanExit $EX_CANTCREAT fi rm -rf "$TDIR" - echo -e "${GR} *${NM} Switching to $CAUDECDIR." 1>&2 + printMessage 'info' 'stderr' "Switching to $CAUDECDIR." setNProcesses $oProcesses setupSwapdir checkFreeSpace "$1" @@ -1754,6 +2628,76 @@ fi } +processFilesExist () +{ + for ((i=0; i<2; i++)); do # loop twice to make really sure + for f in "$instanceDir"/process.* ; do + if [ -e "$f" ]; then + return $EX_OK + fi + done + sleep 0.1 + done + return $EX_KO +} + +getNumberOfCaudecProcesses () +{ + local n=0 + + n=$( find "$piddir" -type f -name 'process.*' 2>/dev/null | wc -l | tr -cd '0-9' ) + case "$n" in + [0-9]*) echo "$n" ;; + *) echo '0' ;; + esac +} + +getNumberOfTakProcesses () +{ + local nRunningProcesses nTakProcesses + + nRunningProcesses="$( getNumberOfCaudecProcesses )" + nTakProcesses=$(( maxProcesses - nRunningProcesses )) + if [ $nTakProcesses -gt 4 ]; then + nTakProcesses=4 + elif [ $nTakProcesses -lt 1 ]; then + nTakProcesses=1 + fi + echo "$nTakProcesses" +} + +monitorRamdiskSpace () +{ + local sizeFile="${TDIR}/ramdiskSpaceUsage" kbytes bytes mib requiredMiB sleepTime='0.2' + + if [ $tdirIsRamdisk = false ]; then + sleepTime='0.5' + fi + + > "$sizeFile" + while processFilesExist ; do + sleep "$sleepTime" + du -k "$SWAPDIR" 2>/dev/null | cut -f 1 >> "$sizeFile" 2>/dev/null + done + + kbytes="$( sort -n "$sizeFile" 2>/dev/null | tail -n 1 )" + if [ -z "$kbytes" ]; then + return $EX_OK + fi + + bytes=$(( kbytes * 1024 )) + mib="$( echo "scale=3; $kbytes / 1024" | bc )" + mib="$( printf "%.0f" "$mib" )" + requiredMiB="$( echo "scale=3; $requiredSpace / (1024 * 1024)" | bc )" + requiredMiB="$( printf "%.0f" "$requiredMiB" )" + if [ $bytes -gt $requiredSpace ]; then + printMessage 'warning' 'internal' "ramdisk space usage larger than anticipated ($mib / $requiredMiB MiB)!" + printMessage 'info' 'stderr' 'Please file a bug report: http://caudec.net/redirect/bugReport' + else + printMessage 'info' 'debug' "ramdisk space usage: $mib / $requiredMiB MiB" + fi +} + tagline () { local IFS=' ' tags switch value pattern nLines ereg @@ -1764,12 +2708,13 @@ if [ -n "$outputCodecs" ]; then pattern="x${outputCodec}Y" if [ "$preserveMetadata" = "${preserveMetadata//$pattern/@}" ]; then - ereg="" for h in $hashes; do case $h in - CRC) ereg="${ereg}|CRC=" ;; + CRC32) ereg="${ereg}|CRC32=" ;; MD5) ereg="${ereg}|MD5=" ;; SHA1) ereg="${ereg}|SHA1=" ;; + SHA256) ereg="${ereg}|SHA256=" ;; + SHA512) ereg="${ereg}|SHA512=" ;; esac done if [ -n "$ereg" ]; then @@ -1781,41 +2726,49 @@ case "$outputCodec" in WavPackLossy|LAME|WinLAME|AAC|QAAC|OggVorbis|WinVorbis|Musepack|Opus) - grep -viE "replaygain|MD5=|CRC=|SHA1=" "${destTagFile}.${outputCodec}" > "${destTagFile}.${outputCodec}.tmp" + if [ "$outputCodec" = 'WavPackLossy' ]; then + grep -viE "MD5=|CRC32=|SHA1=|SHA256=|SHA512=" "${destTagFile}.${outputCodec}" > "${destTagFile}.${outputCodec}.tmp" + else + grep -viE "replaygain|MD5=|CRC32=|SHA1=|SHA256=|SHA512=" "${destTagFile}.${outputCodec}" > "${destTagFile}.${outputCodec}.tmp" + fi mv "${destTagFile}.${outputCodec}.tmp" "${destTagFile}.${outputCodec}" for h in $hashes; do grep -i "SOURCE${h}=" "$destTagFile" >> "${destTagFile}.${outputCodec}" done ;; - FLAC|Flake|WavPack|MonkeysAudio|TAK|ALAC) - if [ -z "$gainValue" -a -z "$bitDepth" -a -z "$samplingRate" ]; then + + FLAC|Flake|WavPack|WavPackHybrid|MonkeysAudio|TAK|ALAC) + if isWavProcessed ; then # SoX was used on the WAV file for resampling, applying gain, or converting to stereo + grep -viE "µµµCRC32=|µµµMD5=|µµµSHA1=|µµµSHA256=|µµµSHA512=" "${destTagFile}.${outputCodec}" > "${destTagFile}.${outputCodec}.tmp" + mv "${destTagFile}.${outputCodec}.tmp" "${destTagFile}.${outputCodec}" + else # no processing was done to the original WAV file for h in $hashes; do grep -viE "SOURCE${h}=" "${destTagFile}.${outputCodec}" > "${destTagFile}.${outputCodec}.tmp" mv "${destTagFile}.${outputCodec}.tmp" "${destTagFile}.${outputCodec}" done - sed -i -e 's@µµµCRC=@SOURCECRC=@i' -e 's@µµµMD5=@SOURCEMD5=@i' -e 's@µµµSHA1=@SOURCESHA1=@i' "${destTagFile}.${outputCodec}" - else - grep -viE "µµµCRC=|µµµMD5=|µµµSHA1=" "${destTagFile}.${outputCodec}" > "${destTagFile}.${outputCodec}.tmp" - mv "${destTagFile}.${outputCodec}.tmp" "${destTagFile}.${outputCodec}" + $sedcmd -i'' -e 's@µµµCRC32=@SOURCECRC32=@i' -e 's@µµµMD5=@SOURCEMD5=@i' -e 's@µµµSHA1=@SOURCESHA1=@i' -e 's@µµµSHA256=@SOURCESHA256=@i' -e 's@µµµSHA512=@SOURCESHA512=@i' "${destTagFile}.${outputCodec}" fi ;; + lossy*) - grep -viE "MD5=|CRC=|SHA1=" "${destTagFile}.${outputCodec}" > "${destTagFile}.${outputCodec}.tmp" + grep -viE "MD5=|CRC32=|SHA1=|SHA256=|SHA512=" "${destTagFile}.${outputCodec}" > "${destTagFile}.${outputCodec}.tmp" mv "${destTagFile}.${outputCodec}.tmp" "${destTagFile}.${outputCodec}" for h in $hashes; do grep -i "SOURCE${h}=" "$destTagFile" >> "${destTagFile}.${outputCodec}" case "$h" in - CRC) if [ -f "$lossywavCRCFile" ]; then cat "$lossywavCRCFile" >> "${destTagFile}.${outputCodec}" ; fi ;; + CRC32) if [ -f "$lossywavCRC32File" ]; then cat "$lossywavCRC32File" >> "${destTagFile}.${outputCodec}" ; fi ;; MD5) if [ -f "$lossywavMD5File" ]; then cat "$lossywavMD5File" >> "${destTagFile}.${outputCodec}" ; fi ;; SHA1) if [ -f "$lossywavSHA1File" ]; then cat "$lossywavSHA1File" >> "${destTagFile}.${outputCodec}" ; fi ;; + SHA256) if [ -f "$lossywavSHA256File" ]; then cat "$lossywavSHA256File" >> "${destTagFile}.${outputCodec}" ; fi ;; + SHA512) if [ -f "$lossywavSHA512File" ]; then cat "$lossywavSHA512File" >> "${destTagFile}.${outputCodec}" ; fi ;; esac done ;; esac fi - if [ $applyGain = true ]; then - fgrep -vi 'replaygain' "${destTagFile}.${outputCodec}" > "${destTagFile}.${outputCodec}.tmp" + if isWavProcessed ; then # SoX was used on the WAV file for resampling, applying gain, or converting to stereo + grep -Fvi 'replaygain' "${destTagFile}.${outputCodec}" > "${destTagFile}.${outputCodec}.tmp" mv "${destTagFile}.${outputCodec}.tmp" "${destTagFile}.${outputCodec}" fi @@ -1823,7 +2776,7 @@ echo "$compressionTag" >> "${destTagFile}.${outputCodec}" fi - nLines="$( cat "${destTagFile}.${outputCodec}" | wc -l )" + nLines="$( cat "${destTagFile}.${outputCodec}" | wc -l | tr -cd '0-9' )" if [ $nLines -eq 0 ]; then return fi @@ -1842,7 +2795,7 @@ done < "${destTagFile}.${outputCodec}" fi rm -f "${destTagFile}.${outputCodec}" - printf -- "%s" "${tags//%/%%}" | sed -e 's#§#\n#g' + printf -- "%s" "${tags//%/%%}" | $sedcmd -e 's#§#\n#g' -e 's@\x02@\\\\x00@g' } # http://wiki.xiph.org/Field_names @@ -1851,19 +2804,55 @@ # http://wiki.hydrogenaudio.org/index.php?title=APE_key # http://www.id3.org/id3v2.3.0 -vorbisCommentsToAPEv2 () +# Sanitize track or disc number +getTrackOrDiscNumber () { - local line field value totalDiscs='' totalTracks='' + local s="$1" + + s="${s%/*}" + case "$s" in + [0-9]|[0-9][0-9]|[0-9][0-9][0-9]|[0-9][0-9][0-9][0-9]) echo "$s" ;; + *) echo '0' ;; + esac +} + +getTotalDiscsAndTracks () +{ + local field value + while read line; do field="${line%%=*}" ; value="${line#*=}" case "$field" in - DISCTOTAL|TOTALDISCS) totalDiscs="$value" ;; - TRACKTOTAL|TOTALTRACKS) totalTracks="$value" ;; + DISCTOTAL|TOTALDISCS) totalDiscs="$( getTrackOrDiscNumber "$value" )" ;; + TRACKTOTAL|TOTALTRACKS) totalTracks="$( getTrackOrDiscNumber "$value" )" ;; + + Media|Disc|DISCNUMBER) + case "$value" in + [0-9]*) + if [ "${value#*/}" != "$value" ]; then + totalDiscs="$( getTrackOrDiscNumber "${value#*/}" )" + fi + ;; + esac + ;; + + Track|TRACKNUMBER) + if [ "${value#*/}" != "$value" ]; then + totalTracks="$( getTrackOrDiscNumber "${value#*/}" )" + fi + ;; esac done < "$sourceTagFile" +} +vorbisCommentsToAPEv2 () +{ + local line field value totalDiscs='' totalTracks='' + + getTotalDiscsAndTracks while read line; do field="${line%%=*}" ; value="${line#*=}" + if [ "$value" = '' ]; then continue; fi case "$field" in ALBUM) echo "Album=$value" ;; ALBUMARTIST|"ALBUM ARTIST") echo "Album Artist=$value" ;; @@ -1871,14 +2860,14 @@ COMPOSER) echo "Composer=$value" ;; CONDUCTOR) echo "Conductor=$value" ;; COPYRIGHT) echo "Copyright=$value" ;; - CRC) echo "CRC=$value" ;; + CRC32) echo "CRC32=$value" ;; DATE) printf 'Year=%.4s\n' "$value" ;; DESCRIPTION) echo "Comment=$value" ;; DISCNUMBER) if [ -n "$totalDiscs" ]; then - printf 'Disc=%g/%g\n' "$value" "$totalDiscs" + printf 'Disc=%g/%g\n' "$( getTrackOrDiscNumber "$value" )" "$totalDiscs" else - printf 'Disc=%g\n' "$value" + printf 'Disc=%g\n' "$( getTrackOrDiscNumber "$value" )" fi ;; DISCTOTAL|TOTALDISCS) continue ;; @@ -1890,6 +2879,7 @@ LICENSE) echo "License=$value" ;; LOCATION) echo "Record Location=$value" ;; MD5) echo "MD5=$value" ;; + PERFORMER) echo "Performer=$value" ;; PUBLISHER) echo "Publisher=$value" ;; REPLAYGAIN_REFERENCE_LOUDNESS) echo "Replaygain_Reference_Loudness=$value" ;; REPLAYGAIN_TRACK_GAIN) echo "Replaygain_Track_Gain=$value" ;; @@ -1897,14 +2887,16 @@ REPLAYGAIN_ALBUM_GAIN) echo "Replaygain_Album_Gain=$value" ;; REPLAYGAIN_ALBUM_PEAK) echo "Replaygain_Album_Peak=$value" ;; SHA1) echo "SHA1=$value" ;; + SHA256) echo "SHA256=$value" ;; + SHA512) echo "SHA512=$value" ;; SOURCEMEDIA) echo "Media=$value" ;; SUBTITLE) echo "Subtitle=$value" ;; TITLE) echo "Title=$value" ;; TRACKNUMBER) if [ -n "$totalTracks" ]; then - printf 'Track=%g/%g\n' "$value" "$totalTracks" + printf 'Track=%g/%g\n' "$( getTrackOrDiscNumber "$value" )" "$totalTracks" else - printf 'Track=%g\n' "$value" + printf 'Track=%g\n' "$( getTrackOrDiscNumber "$value" )" fi ;; TRACKTOTAL|TOTALTRACKS) continue ;; @@ -1916,16 +2908,11 @@ vorbisCommentsToLAME () { local line field value totalTracks='' totalDiscs='' - while read line; do - field="${line%%=*}" ; value="${line#*=}" - case "$field" in - DISCTOTAL|TOTALDISCS) totalDiscs="$value" ;; - TRACKTOTAL|TOTALTRACKS) totalTracks="$value" ;; - esac - done < "$sourceTagFile" + getTotalDiscsAndTracks while read line; do field="${line%%=*}" ; value="${line#*=}" + if [ "$value" = '' ]; then continue; fi case "$field" in ALBUM) echo "--tl $value" ;; ALBUMARTIST|"ALBUM ARTIST") @@ -1941,9 +2928,9 @@ DESCRIPTION) echo "--tc $value" ;; DISCNUMBER) if [ -n "$totalDiscs" ]; then - printf -- '--tv TPOS=%g/%g\n' "$value" "$totalDiscs" + printf -- '--tv TPOS=%g/%g\n' "$( getTrackOrDiscNumber "$value" )" "$totalDiscs" else - printf -- '--tv TPOS=%g\n' "$value" + printf -- '--tv TPOS=%g\n' "$( getTrackOrDiscNumber "$value" )" fi ;; DISCTOTAL|TOTALDISCS) continue ;; @@ -1952,14 +2939,15 @@ ISRC) echo "--tv TSRC=$value" ;; LICENSE) if [ "${value:0:7}" = 'http://' ]; then echo "--tv WCOP=$value" ; fi ;; LYRICIST) echo "--tv TEXT=$value" ;; + PERFORMER) echo "--tv TPE3=$value" ;; PUBLISHER) echo "--tv TPUB=$value" ;; SUBTITLE) echo "--tv TIT3=$value" ;; TITLE) echo "--tt $value" ;; TRACKNUMBER) if [ -n "$totalTracks" ]; then - printf -- '--tn %g/%g\n' "$value" "$totalTracks" + printf -- '--tn %g/%g\n' "$( getTrackOrDiscNumber "$value" )" "$totalTracks" else - printf -- '--tn %g\n' "$value" + printf -- '--tn %g\n' "$( getTrackOrDiscNumber "$value" )" fi ;; TRACKTOTAL|TOTALTRACKS) continue ;; @@ -1970,9 +2958,19 @@ vorbisCommentsToM4A () { - local line field value + local line field value totalTracks='' totalDiscs='' + + getTotalDiscsAndTracks + if [ -n "$totalDiscs" ]; then + printf -- '-meta:totaldiscs=%g\n' "$totalDiscs" + fi + if [ -n "$totalTracks" ]; then + printf -- '-meta:totaltracks=%g\n' "$totalTracks" + fi + while read line; do field="${line%%=*}" ; value="${line#*=}" + if [ "$value" = '' ]; then continue; fi case "$field" in ALBUM) echo "-meta:album=$value" ;; ALBUMARTIST|"ALBUM ARTIST") @@ -1986,15 +2984,15 @@ COPYRIGHT) echo "-meta:copyright=$value" ;; DATE) printf -- '-meta:year=%.4s\n' "$value" ;; DESCRIPTION) echo "-meta:comment=$value" ;; - DISCNUMBER) printf -- '-meta:disc=%g\n' "$value" ;; - DISCTOTAL|TOTALDISCS) printf -- '-meta:totaldiscs=%g\n' "$value" ;; + DISCNUMBER) printf -- '-meta:disc=%g\n' "$( getTrackOrDiscNumber "$value" )" ;; + DISCTOTAL|TOTALDISCS) continue ;; ENCODER|ENCODING) continue ;; GENRE) echo "-meta:genre=$value" ;; ISRC) echo "-meta:isrc=$value" ;; ORGANIZATION) echo "-meta:label=$value" ;; TITLE) echo "-meta:title=$value" ;; - TRACKNUMBER) printf -- '-meta:track=%g\n' "$value" ;; - TRACKTOTAL|TOTALTRACKS) printf -- '-meta:totaltracks=%g\n' "$value" ;; + TRACKNUMBER) printf -- '-meta:track=%g\n' "$( getTrackOrDiscNumber "$value" )" ;; + TRACKTOTAL|TOTALTRACKS) continue ;; cdec|encoding*|itunnorm|itunsmpb|tool) continue ;; *) echo "-meta-user:${field}=${value}" ;; esac @@ -2003,29 +3001,19 @@ APEv2ToVorbisComments () { - local line field value - while read line; do - field="${line%%=*}" ; value="${line#*=}" - case "$field" in - Media|Disc) - case "$value" in - [0-9]*) - if echo "$value" | fgrep '/' >/dev/null 2>&1; then - printf 'DISCTOTAL=%g\n' "${value#*/}" - fi - ;; - esac - ;; - Track) - if echo "$value" | fgrep '/' >/dev/null 2>&1; then - printf 'TRACKTOTAL=%g\n' "${value#*/}" - fi - ;; - esac - done < "$sourceTagFile" + local line field value totalTracks='' totalDiscs='' + + getTotalDiscsAndTracks + if [ -n "$totalDiscs" ]; then + printf 'DISCTOTAL=%g\n' "$totalDiscs" + fi + if [ -n "$totalTracks" ]; then + printf 'TRACKTOTAL=%g\n' "$totalTracks" + fi while read line; do field="${line%%=*}" ; value="${line#*=}" + if [ "$value" = '' ]; then continue; fi case "$field" in Album) echo "ALBUM=$value" ;; Albumartist|"Album Artist") echo "ALBUMARTIST=$value" ;; @@ -2035,7 +3023,7 @@ Composer) echo "COMPOSER=$value" ;; Conductor) echo "CONDUCTOR=$value" ;; Copyright) echo "COPYRIGHT=$value" ;; - CRC) echo "CRC=$value" ;; + CRC32) echo "CRC32=$value" ;; EAN/UPC) echo "EAN/UPC=$value" ;; Encoder|Encoding) continue ;; Genre) echo "GENRE=$value" ;; @@ -2044,9 +3032,10 @@ MD5) echo "MD5=$value" ;; Media|Disc) case "$value" in - [0-9]*) printf 'DISCNUMBER=%g\n' "${value%/*}" ;; - *) echo "SOURCEMEDIA=$value" ;; + [0-9]*) printf 'DISCNUMBER=%g\n' "$( getTrackOrDiscNumber "$value" )" ;; + *) echo "SOURCEMEDIA=$value" ;; esac ;; + Performer) echo "PERFORMER=$value" ;; Publisher) echo "PUBLISHER=$value" ;; 'Record Location') echo "LOCATION=$value" ;; Replaygain_Reference_Loudness) echo "REPLAYGAIN_REFERENCE_LOUDNESS=$value" ;; @@ -2055,9 +3044,11 @@ Replaygain_Album_Gain) echo "REPLAYGAIN_ALBUM_GAIN=$value" ;; Replaygain_Album_Peak) echo "REPLAYGAIN_ALBUM_PEAK=$value" ;; SHA1) echo "SHA1=$value" ;; + SHA256) echo "SHA256=$value" ;; + SHA512) echo "SHA512=$value" ;; Subtitle) echo "SUBTITLE=$value" ;; Title) echo "TITLE=$value" ;; - Track) printf 'TRACKNUMBER=%g\n' "${value%/*}" ;; + Track) printf 'TRACKNUMBER=%g\n' "$( getTrackOrDiscNumber "$value" )" ;; Year) printf 'DATE=%.4s\n' "$value" ;; *) echo "${field}=${value}" ;; esac @@ -2066,9 +3057,12 @@ APEv2ToLAME () { - local line field value + local line field value totalTracks='' totalDiscs='' + + getTotalDiscsAndTracks while read line; do field="${line%%=*}" ; value="${line#*=}" + if [ "$value" = '' ]; then continue; fi case "$field" in Album) echo "--tl $value" ;; Albumartist|"Album Artist") @@ -2088,22 +3082,23 @@ Media|Disc) case "$value" in [0-9]*) - if [ "$value" = "${value%/*}" ]; then - printf -- '--tv TPOS=%g\n' "$value" + if [ -n "$totalDiscs" ]; then + printf -- '--tv TPOS=%g/%g\n' "$( getTrackOrDiscNumber "$value" )" "$totalDiscs" else - printf -- '--tv TPOS=%g/%g\n' "${value%/*}" "${value#*/}" + printf -- '--tv TPOS=%g\n' "$( getTrackOrDiscNumber "$value" )" fi ;; esac ;; + Performer) echo "--tv TPE3=$value" ;; Publisher) echo "--tv TPUB=$value" ;; Subtitle) echo "--tv TIT3=$value" ;; Title) echo "--tt $value" ;; Track) - if [ "$value" = "${value%/*}" ]; then - printf -- '--tn %g\n' "$value" + if [ -n "$totalTracks" ]; then + printf -- '--tn %g/%g\n' "$( getTrackOrDiscNumber "$value" )" "$totalTracks" else - printf -- '--tn %g/%g\n' "${value%/*}" "${value#*/}" + printf -- '--tn %g\n' "$( getTrackOrDiscNumber "$value" )" fi ;; Year) printf -- '--ty %.4s\n' "$value" ;; @@ -2114,25 +3109,19 @@ APEv2ToM4A () { - local line field value - while read line; do - field="${line%%=*}" ; value="${line#*=}" - case "$field" in - Media|Disc) - if echo "$value" | fgrep '/' >/dev/null 2>&1; then - printf -- '-meta:totaldiscs=%g\n' "${value#*/}" - fi - ;; - Track) - if echo "$value" | fgrep '/' >/dev/null 2>&1; then - printf -- '-meta:totaltracks=%g\n' "${value#*/}" - fi - ;; - esac - done < "$sourceTagFile" + local line field value totalTracks='' totalDiscs='' + + getTotalDiscsAndTracks + if [ -n "$totalDiscs" ]; then + printf -- '-meta:totaldiscs=%g\n' "$totalDiscs" + fi + if [ -n "$totalTracks" ]; then + printf -- '-meta:totaltracks=%g\n' "$totalTracks" + fi while read line; do field="${line%%=*}" ; value="${line#*=}" + if [ "$value" = '' ]; then continue; fi case "$field" in Album) echo "-meta:album=$value" ;; Albumartist|"Album Artist") @@ -2150,10 +3139,10 @@ ISRC) echo "-meta:isrc=$value" ;; Media|Disc) case "$value" in - [0-9]*) printf -- '-meta:disc=%g\n' "${value%/*}" ;; + [0-9]*) printf -- '-meta:disc=%g\n' "$( getTrackOrDiscNumber "$value" )" ;; esac ;; Title) echo "-meta:title=$value" ;; - Track) printf -- '-meta:track=%g\n' "${value%/*}" ;; + Track) printf -- '-meta:track=%g\n' "$( getTrackOrDiscNumber "$value" )" ;; Year) printf -- '-meta:year=%.4s\n' "$value" ;; cdec|encoding*|itunnorm|itunsmpb|tool) continue ;; *) echo "-meta-user:${field}=${value}" ;; @@ -2166,6 +3155,7 @@ local line field value while read line; do field="${line%%=*}" ; value="${line#*=}" + if [ "$value" = '' ]; then continue; fi case "$field" in album|artist|comment|composer|copyright|credits|disc|genre|isrc|label|lyrics|mood|rating|tempo|title|totaldiscs|totaltracks|track|url|year) echo "-meta:${field}=${value}" @@ -2184,9 +3174,19 @@ M4AToVorbisComments () { - local line field value + local line field value totalTracks='' totalDiscs='' + + getTotalDiscsAndTracks + if [ -n "$totalDiscs" ]; then + printf 'DISCTOTAL=%g\n' "$totalDiscs" + fi + if [ -n "$totalTracks" ]; then + printf 'TRACKTOTAL=%g\n' "$totalTracks" + fi + while read line; do field="${line%%=*}" ; value="${line#*=}" + if [ "$value" = '' ]; then continue; fi case "$field" in album) echo "ALBUM=$value" ;; "album artist") echo "ALBUMARTIST=$value" ;; @@ -2194,14 +3194,15 @@ comment) echo "DESCRIPTION=$value" ;; composer) echo "COMPOSER=$value" ;; copyright) echo "COPYRIGHT=$value" ;; - disc) printf 'DISCNUMBER=%g\n' "$value" ;; + disc) printf 'DISCNUMBER=%g\n' "$( getTrackOrDiscNumber "$value" )" ;; genre) echo "GENRE=$value" ;; isrc) echo "ISRC=$value" ;; label) echo "ORGANIZATION=$value" ;; + performer) echo "PERFORMER=$value" ;; title) echo "TITLE=$value" ;; - totaldiscs) printf 'DISCTOTAL=%g\n' "$value" ;; - totaltracks) printf 'TRACKTOTAL=%g\n' "$value" ;; - track) printf 'TRACKNUMBER=%g\n' "$value" ;; + totaldiscs) continue ;; + totaltracks) continue ;; + track) printf 'TRACKNUMBER=%g\n' "$( getTrackOrDiscNumber "$value" )" ;; year) printf 'DATE=%.4s\n' "$value" ;; cdec|encoding*|itunnorm|itunsmpb|tool) continue ;; *) echo "${field}=${value}" ;; @@ -2212,16 +3213,11 @@ M4AToAPEv2 () { local line field value totalDiscs='' totalTracks='' - while read line; do - field="${line%%=*}" ; value="${line#*=}" - case "$field" in - totaldiscs) totalDiscs="$value" ;; - totaltracks) totalTracks="$value" ;; - esac - done < "$sourceTagFile" + getTotalDiscsAndTracks while read line; do field="${line%%=*}" ; value="${line#*=}" + if [ "$value" = '' ]; then continue; fi case "$field" in album) echo "Album=$value" ;; "album artist") echo "Album Artist=$value" ;; @@ -2231,20 +3227,21 @@ copyright) echo "Copyright=$value" ;; disc) if [ -n "$totalDiscs" ]; then - printf 'Disc=%g/%g\n' "$value" "$totalDiscs" + printf 'Disc=%g/%g\n' "$( getTrackOrDiscNumber "$value" )" "$totalDiscs" else - printf 'Disc=%g\n' "$value" + printf 'Disc=%g\n' "$( getTrackOrDiscNumber "$value" )" fi ;; genre) echo "Genre=$value" ;; isrc) echo "Isrc=$value" ;; + performer) echo "Performer=$value" ;; title) echo "Title=$value" ;; totaldiscs|totaltracks) continue ;; track) if [ -n "$totalTracks" ]; then - printf 'Track=%g/%g\n' "$value" "$totalTracks" + printf 'Track=%g/%g\n' "$( getTrackOrDiscNumber "$value" )" "$totalTracks" else - printf 'Track=%g\n' "$value" + printf 'Track=%g\n' "$( getTrackOrDiscNumber "$value" )" fi ;; year) printf 'Year=%.4s\n' "$value" ;; @@ -2257,16 +3254,11 @@ M4AToLAME () { local line field value totalTracks='' totalDiscs='' - while read line; do - field="${line%%=*}" ; value="${line#*=}" - case "$field" in - totaldiscs) totalDiscs="$value" ;; - totaltracks) totalTracks="$value" ;; - esac - done < "$sourceTagFile" + getTotalDiscsAndTracks while read line; do field="${line%%=*}" ; value="${line#*=}" + if [ "$value" = '' ]; then continue; fi case "$field" in album) echo "--tl $value" ;; "album artist") @@ -2280,19 +3272,20 @@ composer) echo "--tv TCOM=$value" ;; disc) if [ -n "$totalDiscs" ]; then - printf -- '--tv TPOS=%g/%g\n' "$value" "$totalDiscs" + printf -- '--tv TPOS=%g/%g\n' "$( getTrackOrDiscNumber "$value" )" "$totalDiscs" else - printf -- '--tv TPOS=%g\n' "$value" + printf -- '--tv TPOS=%g\n' "$( getTrackOrDiscNumber "$value" )" fi ;; genre) echo "--tg $value" ;; isrc) echo "--tv TSRC=$value" ;; + performer) echo "--tv TPE3=$value" ;; title) echo "--tt $value" ;; track) if [ -n "$totalTracks" ]; then - printf -- '--tn %g/%g\n' "$value" "$totalTracks" + printf -- '--tn %g/%g\n' "$( getTrackOrDiscNumber "$value" )" "$totalTracks" else - printf -- '--tn %g\n' "$value" + printf -- '--tn %g\n' "$( getTrackOrDiscNumber "$value" )" fi ;; year) printf -- '--ty %.4s\n' "$value" ;; @@ -2322,7 +3315,7 @@ search="@${taglist//,/@,@}@" OIFS="$IFS"; IFS=',' for w in $search; do - match="$( echo "$translations" | fgrep -i "$w" 2>/dev/null )" + match="$( echo "$translations" | grep -Fi "$w" 2>/dev/null )" if [ -n "$match" ]; then match="${match//@/}"; match="${match//,/=|^}" ereg="${ereg}|^${match}=" @@ -2351,8 +3344,9 @@ fi done < "$sourceTagFile" > "${sourceTagFile}.tmp" mv "${sourceTagFile}.tmp" "$sourceTagFile" - nChars="$( cat "$sourceTagFile" | wc -m )" + nChars="$( cat "$sourceTagFile" | wc -m | tr -d ' ' )" if [ $nChars -gt 0 ]; then + $sedcmd -i'' -e 's@crc=@CRC32=@i' "$sourceTagFile" >/dev/null 2>&1 echo >> "$sourceTagFile" fi @@ -2364,28 +3358,28 @@ mv "${sourceTagFile}.tmp" "$sourceTagFile" elif [ -n "$tagBlacklist" ]; then genTagFilter "$tagBlacklist" - grep -ivE "$ereg" "$sourceTagFile" > "${sourceTagFile}.tmp" + grep -viE "$ereg" "$sourceTagFile" > "${sourceTagFile}.tmp" mv "${sourceTagFile}.tmp" "$sourceTagFile" fi - if [ -n "$gainValue" -o -n "$bitDepth" -o -n "$samplingRate" ]; then - ereg='^sourcecrc=|^sourcemd5=|^sourcesha1=|^crc=|^md5=|^sha1=|^µµµcrc=|^µµµmd5=|^µµµsha1=' + if isWavProcessed ; then + ereg='^sourcecrc32=|^sourcemd5=|^sourcesha1=|^crc32=|^md5=|^sha1=|^µµµcrc32=|^µµµmd5=|^µµµsha1=' grep -viE "$ereg" "$sourceTagFile" > "${sourceTagFile}.tmp" # purge former hash tags mv -f "${sourceTagFile}.tmp" "$sourceTagFile" fi for h in $hashes; do case "$h" in - CRC) - if [ -f "$sourceCRCFile" ]; then - grep -viE "^SOURCECRC=" "$sourceTagFile" > "${sourceTagFile}.tmp" + CRC32) + if [ -f "$sourceCRC32File" ]; then + grep -viE "^SOURCECRC32=" "$sourceTagFile" > "${sourceTagFile}.tmp" mv -f "${sourceTagFile}.tmp" "$sourceTagFile" - cat "$sourceCRCFile" >> "$sourceTagFile" + cat "$sourceCRC32File" >> "$sourceTagFile" fi - if [ -f "$losslessCRCFile" ]; then - grep -viE "^CRC=" "$sourceTagFile" > "${sourceTagFile}.tmp" + if [ -f "$losslessCRC32File" ]; then + grep -viE "^CRC32=" "$sourceTagFile" > "${sourceTagFile}.tmp" mv -f "${sourceTagFile}.tmp" "$sourceTagFile" - cat "$losslessCRCFile" >> "$sourceTagFile" + cat "$losslessCRC32File" >> "$sourceTagFile" fi ;; @@ -2414,11 +3408,65 @@ cat "$losslessSHA1File" >> "$sourceTagFile" fi ;; + + SHA256) + if [ -f "$sourceSHA256File" ]; then + grep -viE "^SOURCESHA256=" "$sourceTagFile" > "${sourceTagFile}.tmp" + mv -f "${sourceTagFile}.tmp" "$sourceTagFile" + cat "$sourceSHA256File" >> "$sourceTagFile" + fi + if [ -f "$losslessSHA256File" ]; then + grep -viE "^SHA256=" "$sourceTagFile" > "${sourceTagFile}.tmp" + mv -f "${sourceTagFile}.tmp" "$sourceTagFile" + cat "$losslessSHA256File" >> "$sourceTagFile" + fi + ;; + + SHA512) + if [ -f "$sourceSHA512File" ]; then + grep -viE "^SOURCESHA512=" "$sourceTagFile" > "${sourceTagFile}.tmp" + mv -f "${sourceTagFile}.tmp" "$sourceTagFile" + cat "$sourceSHA512File" >> "$sourceTagFile" + fi + if [ -f "$losslessSHA512File" ]; then + grep -viE "^SHA512=" "$sourceTagFile" > "${sourceTagFile}.tmp" + mv -f "${sourceTagFile}.tmp" "$sourceTagFile" + cat "$losslessSHA512File" >> "$sourceTagFile" + fi + ;; esac done fi } +sanitizeApeTags () +{ + local field lastField value lastValue first=true + + # transform duplicate fields into unique multi-value fields + while read line; do + lastField="$field" lastValue="$value" + field="$( echo "${line%%=*}" | tr '[:upper:]' '[:lower:]' 2>/dev/null )" + value="${line#*=}" + if [ "$field" != "$lastField" ]; then + if [ "$first" = 'true' ]; then + echo -en "${line}" + first=false + else + echo -en "\n${line}" + fi + elif [ "$value" != "$lastValue" ]; then + echo -n '\\x01' + echo -n "$value" + fi + done < <( sort -u "$destTagFile" 2>/dev/null ) > "${destTagFile}.tmp" + if [ -f "${destTagFile}.tmp" ]; then + echo -en "\n" >> "${destTagFile}.tmp" + rm -f "$destTagFile" >/dev/null 2>&1 + mv "${destTagFile}.tmp" "$destTagFile" >/dev/null 2>&1 + fi +} + convertTags () { destTagFile="${TDIR}/${i}.${2}.txt" @@ -2428,18 +3476,37 @@ case $1 in vc) case $2 in - vc) grep -viE '^encoder=|^encoding=' "$sourceTagFile" > "$destTagFile" ;; + vc) + if [ -n "$outputCodecs" ]; then + grep -viE '^encoder=|^encoding=' "$sourceTagFile" > "$destTagFile" + else + cp "$sourceTagFile" "$destTagFile" >/dev/null 2>&1 + fi + ;; + ape) vorbisCommentsToAPEv2 > "$destTagFile" ;; lame) vorbisCommentsToLAME > "$destTagFile" ;; m4a) vorbisCommentsToM4A > "$destTagFile" ;; esac ;; + ape) case $2 in - ape) grep -viE '^encoder=|^encoding=' "$sourceTagFile" "$destTagFile" ;; + ape) + if [ -n "$outputCodecs" ]; then + grep -viE '^encoder=|^encoding=' "$sourceTagFile" > "$destTagFile" + else + cp "$sourceTagFile" "$destTagFile" >/dev/null 2>&1 + fi + ;; + vc) APEv2ToVorbisComments > "$destTagFile" ;; lame) APEv2ToLAME > "$destTagFile" ;; m4a) APEv2ToM4A > "$destTagFile" ;; + esac + case $2 in + vc|lame|m4a) $sedcmd -i'' -e 's@\x02@ / @g' -e 's@^ro:@@' "$destTagFile" ;; esac ;; + m4a) case $2 in m4a) M4AToM4A > "$destTagFile" ;; @@ -2448,6 +3515,10 @@ lame) M4AToLAME > "$destTagFile" ;; esac ;; esac + + case "$destTagFile" in + *.ape.txt) sanitizeApeTags ;; + esac shopt -qu nocasematch } @@ -2457,12 +3528,12 @@ for outputCodec in $outputCodecs; do case "$outputCodec" in - OggVorbis|WinVorbis|Opus|WavPack*|MonkeysAudio|TAK|lossyWV|lossyTAK|Musepack) continue ;; # unsupported formats + OggVorbis|WinVorbis) continue ;; # unsupported formats esac pattern="x${outputCodec}Y" if [ "$preserveMetadata" != "${preserveMetadata//$pattern/@}" ]; then - metaflac --list "$copyFile" 2>> "$errorLogFile" > "${TDIR}/${i}.flist" || return $EX_KO - sed -i -e 's@METADATA block #@\nMETADATA block #@' "${TDIR}/${i}.flist" # binary data can screw up the 'METADATA block #' line + metaflac --list "$copyFile" 2>> "$errorLogFile" | grep -vE '[0-9A-F]{8}:' > "${TDIR}/${i}.flist" 2>/dev/null + $sedcmd -i'' -e 's@METADATA block #@\nMETADATA block #@' "${TDIR}/${i}.flist" # binary data can screw up the 'METADATA block #' line while read line; do if [ "${line:0:16}" = 'METADATA block #' ]; then blockNumber="${line#*#}" @@ -2503,15 +3574,15 @@ for outputCodec in $outputCodecs; do case "$outputCodec" in - OggVorbis|WinVorbis|Opus|WavPack*|MonkeysAudio|TAK|lossyWV|lossyTAK|Musepack) continue ;; # unsupported formats + OggVorbis|WinVorbis) continue ;; # unsupported formats esac pattern="x${outputCodec}Y" if [ "$preserveMetadata" != "${preserveMetadata//$pattern/@}" ]; then - if neroAacTag -list-covers "$copyFile" 2>&1 | fgrep 'front cover #1' >/dev/null 2>&1 ; then + if neroAacTag -list-covers "$copyFile" 2>&1 | grep -F 'front cover #1' >/dev/null 2>&1 ; then neroAacTag -write-nd-covers "-dump-cover:front:${SWAPDIR}/picture-${i}-${picNumber}_3.jpg" "$copyFile" >/dev/null 2>> "$errorLogFile" || ec=$EX_KO if [ $ec -eq $EX_OK ]; then ((picNumber++)); fi fi - if neroAacTag -list-covers "$copyFile" 2>&1 | fgrep 'back cover #1' >/dev/null 2>&1 ; then + if neroAacTag -list-covers "$copyFile" 2>&1 | grep -F 'back cover #1' >/dev/null 2>&1 ; then neroAacTag -write-nd-covers "-dump-cover:back:${SWAPDIR}/picture-${i}-${picNumber}_4.jpg" "$copyFile" >/dev/null 2>> "$errorLogFile" || ec=$EX_KO fi return $ec @@ -2520,6 +3591,57 @@ return $ec } +extractAPEv2Artwork () +{ + local picNumber=0 picExt='' picType=0 picTypeText='' pattern ec=$EX_OK otherType='' argString='' fieldName='' + + for outputCodec in $outputCodecs; do + case "$outputCodec" in + OggVorbis|WinVorbis) continue ;; # unsupported formats + esac + pattern="x${outputCodec}Y" + if [ "$preserveMetadata" = "${preserveMetadata//$pattern/@}" ]; then continue; fi + + for fieldName in 'other' 'icon' 'other icon' 'front' 'back' 'leaflet' 'media' 'lead artist' 'artist' 'conductor' 'band' 'composer' 'lyricist' 'recording location' 'during recording' 'during performance' 'video' 'a bright colored fish' 'illustration' 'band logo' 'publisher logo'; do + argString="${argString}\\x00-d\\x00cover art (${fieldName})=${SWAPDIR}/picture-${i}-${picNumber}_${picType}." + ((picType++)) + ((picNumber++)) + done + echo -en "-z${argString}\x00${copyFile}" | xargs -0 APEv2 >/dev/null 2>> "$errorLogFile" || ec=$EX_KO + return $ec + done + return $ec +} + +extractAPEv2Binaries () +{ + local pattern ec=$EX_OK argString='' fieldName='' b=0 + + for outputCodec in $outputCodecs; do + case "$outputCodec" in + MonkeysAudio|WavPack*|Musepack|TAK|lossyWV|lossyTAK) + pattern="x${outputCodec}Y" + if [ "$preserveMetadata" = "${preserveMetadata//$pattern/@}" ]; then continue; fi + + if [ ! -d "$apeBinariesDir" ]; then mkdir -p "$apeBinariesDir" >/dev/null 2>&1 ; fi + while read fieldName; do + echo "$fieldName" > "${apeBinariesDir}/${b}.txt" 2>/dev/null + argString="${argString}\\x00-d\\x00${fieldName}=${apeBinariesDir}/${b}.bin" + ((b++)) + done < <( APEv2 -z "$copyFile" 2>/dev/null | grep -F '=data:' 2>/dev/null | cut -d '=' -f 1 2>/dev/null ) + + if [ -n "$argString" ]; then + echo -en "-z${argString}\x00${copyFile}" | xargs -0 APEv2 >/dev/null 2>> "$errorLogFile" || ec=$EX_KO + fi + return $ec + ;; + + *) continue ;; + esac + done + return $ec +} + importArtworkIntoFLAC () { local ec=$EX_OK picType description='' descFile='' pattern @@ -2532,16 +3654,38 @@ for f in "${SWAPDIR}/picture-${i}-"*_*; do if [ ! -e "$f" ]; then continue; fi picType="${f#*_}"; picType="${picType%.*}"; descFile="${f%_*}.txt" + description='' if [ -f "$descFile" ]; then - description="$( cat "$descFile" )"; description="${description//|//}" # replace | with / - else - description='' + description="$( cat "$descFile" 2>> "$errorLogFile" )"; description="${description//|//}" # replace | with / fi metaflac --import-picture-from="${picType}||${description}||${f}" "$encodedFile" >/dev/null 2>> "$errorLogFile" || ec=$EX_KO done return $ec } +genOpusArtworkCommandLine () +{ + local ec=$EX_OK picType description='' descFile='' pattern cmdline='' + + pattern="x${outputCodec}Y" + if [ "$preserveMetadata" = "${preserveMetadata//$pattern/@}" ]; then + echo "$cmdline" + return 0 + fi + + for f in "${SWAPDIR}/picture-${i}-"*_*; do + if [ ! -e "$f" ]; then continue; fi + picType="${f#*_}"; picType="${picType%.*}"; descFile="${f%_*}.txt" + description='' + if [ -f "$descFile" ]; then + description="$( cat "$descFile" 2>> "$errorLogFile" )"; description="${description//|//}" # replace | with / + fi + cmdline="${cmdline}\x00--picture=${picType}||${description}||${f}" + done + echo "$cmdline" + return 0 +} + importArtworkIntoM4A () { local ec=$EX_OK picType picTypeText pattern @@ -2564,9 +3708,82 @@ return $ec } +importArtworkIntoAPEv2 () +{ + local ec=$EX_OK picType picTypeText pattern af='' argString='' lastPicType='' + + pattern="x${outputCodec}Y" + if [ "$preserveMetadata" = "${preserveMetadata//$pattern/@}" ]; then + return $EX_OK + fi + + for f in "${SWAPDIR}/picture-${i}-"*_*; do + if [ ! -e "$f" ]; then continue; fi + picType="${f#*_}"; picType="${picType%.*}" + if [ "$lastPicType" = "$picType" ]; then + continue + fi + lastPicType="$picType" + case "$picType" in + 0) picTypeText='other' ;; + 1) picTypeText='icon' ;; + 2) picTypeText='other icon' ;; + 3) picTypeText='front' ;; + 4) picTypeText='back' ;; + 5) picTypeText='leaflet' ;; + 6) picTypeText='media' ;; + 7) picTypeText='lead artist' ;; + 8) picTypeText='artist' ;; + 9) picTypeText='conductor' ;; + 10) picTypeText='band' ;; + 11) picTypeText='composer' ;; + 12) picTypeText='lyricist' ;; + 13) picTypeText='recording location' ;; + 14) picTypeText='during recording' ;; + 15) picTypeText='during performance' ;; + 16) picTypeText='video' ;; + 17) picTypeText='a bright colored fish' ;; + 18) picTypeText='illustration' ;; + 19) picTypeText='band logo' ;; + 20) picTypeText='publisher logo' ;; + esac + argString="${argString}\\x00-a\\x00Cover Art (${picTypeText})=${f}" + done + + if [ -n "$argString" ]; then + echo -en "-z${argString}\x00${encodedFile}" | xargs -0 APEv2 >/dev/null 2>> "$errorLogFile" || ec=$EX_KO + fi + + return $ec +} + +importBinariesIntoAPEv2 () +{ + local ec=$EX_OK pattern argString='' fieldName='' bf='' + + if [ ! -d "$apeBinariesDir" ]; then return $EX_OK; fi + + pattern="x${outputCodec}Y" + if [ "$preserveMetadata" = "${preserveMetadata//$pattern/@}" ]; then + return $EX_OK + fi + + for bf in "${apeBinariesDir}"/*.txt; do + if [ ! -e "$bf" ]; then continue; fi + fieldName="$( cat "$bf" )" + argString="${argString}\\x00-b\\x00${fieldName}=${bf/.txt/.bin}" + done + + if [ -n "$argString" ]; then + echo -en "-z${argString}\x00${encodedFile}" | xargs -0 APEv2 >/dev/null 2>> "$errorLogFile" || ec=$EX_KO + fi + + return $ec +} + importArtworkIntoMP3 () { - local ec=$EX_OK picType picTypeText pattern + local ec=$EX_OK picType picTypeText pattern descFile description='' frontCoverFile='' n=0 pattern="x${outputCodec}Y" if [ "$preserveMetadata" = "${preserveMetadata//$pattern/@}" ]; then @@ -2576,12 +3793,13 @@ which 'eyeD3' >/dev/null 2>&1 || return $EX_OK for f in "${SWAPDIR}/picture-${i}-"*_*; do if [ ! -e "$f" ]; then continue; fi - picType="${f#*_}"; picType="${picType%.*}" + ((n++)) + picType="${f#*_}"; picType="${picType%.*}"; descFile="${f%_*}.txt" case $picType in 0) picTypeText='OTHER' ;; 1) picTypeText='ICON' ;; 2) picTypeText='OTHER_ICON' ;; - 3) picTypeText='FRONT_COVER' ;; + 3) picTypeText='FRONT_COVER' ; frontCoverFile="$f" ;; 4) picTypeText='BACK_COVER' ;; 5) picTypeText='LEAFLET' ;; 6) picTypeText='MEDIA' ;; @@ -2601,49 +3819,110 @@ 20) picTypeText='PUBLISHER_LOGO' ;; *) continue ;; esac - eyeD3 --add-image="${f}:${picTypeText}" "$encodedFile" >/dev/null 2>> "$errorLogFile" || ec=$EX_KO + description='' + if [ -f "$descFile" ]; then + description="$( cat "$descFile" 2>> "$errorLogFile" )"; description="${description//|//}" # replace | with / + fi + if [ "$eyeD3Version" = '0.7+' ]; then + if [ -z "$description" ]; then + description="${picTypeText}_${n}" # add a number to make it unique + fi + # eyeD3 0.7.1 requires a description, but it's broken (the command fails) + # https://bitbucket.org/nicfit/eyed3/issue/23/unicode-typeerror-adding-image-with-apic + if ! eyeD3 --add-image="${f}:${picTypeText}:${description}" "$encodedFile" >/dev/null 2>&1; then + ec=$EX_KO + eyeD3 --add-image="${f}:${picTypeText}" "$encodedFile" >/dev/null 2>&1 + fi + else + if [ -n "$description" ]; then + eyeD3 --no-tagging-time-frame --itunes --add-image="${f}:${picTypeText}:${description}" "$encodedFile" >/dev/null 2>> "$errorLogFile" || ec=$EX_KO + else + eyeD3 --no-tagging-time-frame --itunes --add-image="${f}:${picTypeText}" "$encodedFile" >/dev/null 2>> "$errorLogFile" || ec=$EX_KO + fi + fi done + + if [ "$eyeD3Version" = '0.7+' ]; then + # workaround + if [ $ec -ne $EX_OK -a -n "$frontCoverFile" ]; then + # since eyeD3 is broken and will only add one cover art if there's no description, use the front cover if available + ec=$EX_OK + eyeD3 --add-image="${frontCoverFile}:FRONT_COVER" "$encodedFile" >/dev/null 2>> "$errorLogFile" || ec=$EX_KO + fi + fi return $ec } -computeCRC () +computeCRC32 () { - local hfile="$1" fifo dgst + local hfile="$1" fifo dgst wtype='' earg='' + + wtype="$( soxi -e "$wavFile" 2>/dev/null )" + if [ "$wtype" = 'Floating Point PCM' ]; then + earg='-e unsigned-integer -b 16' + fi + fifo="${TDIR}/${i}.fifo" - if [ ! -e "$fifo" ]; then mkfifo "$fifo"; fi - shncat -q -e "$hfile" > "$fifo" & - dgst="$( cksfv -b "$fifo" | fgrep -v ';' )" - echo -n "CRC=${dgst##* }" + if [ ! -e "$fifo" ]; then mkfifo "$fifo" >/dev/null 2>> "$errorLogFile" ; fi + sox $earg "$hfile" -t raw "$fifo" 2>> "$errorLogFile" & + dgst="$( cksfv -b "$fifo" 2>> "$errorLogFile" | grep -Fv ';' )" + dgst="${dgst##* }" + if [ -n "$dgst" ]; then + echo -n "${dgst##* }" + fi } computeMD5 () { - local wavFile="$1" dgst - dgst="$( shnhash -q -m "$wavFile" )" + local wavFile="$1" dgst wtype='' earg='' + + wtype="$( soxi -e "$wavFile" 2>/dev/null )" + if [ "$wtype" = 'Floating Point PCM' ]; then + earg='-e unsigned-integer -b 16' # courtesy of David Bryant, author of WavPack + fi + + if [ "$OS" = 'Linux' ]; then + dgst="$( sox $earg "$wavFile" -t raw - 2>> "$errorLogFile" | md5sum - 2>> "$errorLogFile" )" + else + dgst="$( sox $earg "$wavFile" -t raw - 2>> "$errorLogFile" | md5 -q - 2>> "$errorLogFile" )" + fi dgst="${dgst%% *}" - echo -n "MD5=${dgst}" + if [ -n "$dgst" ]; then + echo -n "$dgst" + fi } -computeSHA1 () +computeSHA () { - local wavFile="$1" dgst - dgst="$( shnhash -q -s "$wavFile" )" - echo -n "SHA1=${dgst%% *}" -} + local wavFile="$1" hashtype="$2" hashvar='' dgst wtype='' earg='' -filterApetagOutput () -{ - local nLines nTail + wtype="$( soxi -e "$wavFile" 2>/dev/null )" + if [ "$wtype" = 'Floating Point PCM' ]; then + earg='-e unsigned-integer -b 16' + fi + + if [ "$OS" = 'Linux' ]; then + case "$hashtype" in + SHA1) hashvar='sha1sum' ;; + SHA256) hashvar='sha256sum' ;; + SHA512) hashvar='sha512sum' ;; + esac + else + case "$hashtype" in + SHA1) hashvar='1' ;; + SHA256) hashvar='256' ;; + SHA512) hashvar='512' ;; + esac + fi - test -f "$sourceTagFile" || return $EX_KO - nLines="$( cat "$sourceTagFile" | wc -l | tr -d ' ' )" - nTail=$(( nLines - 2 )) - if [ $nTail -gt 0 ]; then - tail -n $nTail "$sourceTagFile" > "${sourceTagFile}.tmp" - mv "${sourceTagFile}.tmp" "$sourceTagFile" + if [ "$OS" = 'Linux' ]; then + dgst="$( sox $earg "$wavFile" -t raw - 2>> "$errorLogFile" | $hashvar - 2>> "$errorLogFile" )" else - rm -f "$sourceTagFile" - touch "$sourceTagFile" + dgst="$( sox $earg "$wavFile" -t raw - 2>> "$errorLogFile" | shasum -a $hashvar -p - 2>> "$errorLogFile" )" + fi + dgst="${dgst%% *}" + if [ -n "$dgst" ]; then + echo -n "$dgst" fi } @@ -2674,68 +3953,238 @@ mv "${sourceTagFile}.tmp" "$sourceTagFile" } +getSoxDuration () +{ + local f="$1" hours minutes seconds milliseconds + + duration="$( soxi -D "$f" 2>/dev/null )" + if [ -z "$duration" -o -z "${duration//0/}" -o "${duration//0/}" = '.' ]; then + duration='1.0' + fi +} + +getSoxSeconds () +{ + getSoxDuration "$1" + seconds="${duration%.*}" + milliseconds="${duration#*.}" + if [ -z "$milliseconds" ]; then + milliseconds='0' + fi + if [ ${milliseconds:0:1} -ge 5 ]; then + ((seconds++)) + fi +} + saveDurations () { - local samples sampleRate duration hours minutes seconds centiseconds f + local samples sampleRate duration hours minutes seconds centiseconds f data case "$copyFile" in *.flac) - samples="$( metaflac --show-total-samples "$copyFile" 2>> "$errorLogFile" )" - sampleRate="$( metaflac --show-sample-rate "$copyFile" 2>> "$errorLogFile" )" - if [ -n "$samples" -a -n "$sampleRate" ]; then - duration="$( echo "scale=3; $samples / $sampleRate" | bc )" - echo -n " + $duration" >> "${TDIR}/durations" + data="$( metaflac --show-total-samples --show-sample-rate "$copyFile" 2>/dev/null )" + if [ -n "$data" ]; then + for v in $data; do + if [ -z "$samples" ]; then + samples="$v" + else + sampleRate="$v" + fi + done + if [ -n "$samples" -a -n "$sampleRate" ]; then + duration="$( echo "scale=3; $samples / $sampleRate" | bc )" + echo -n " + $duration" >> "${TDIR}/durations" + fi fi - return ;; *.wv) - duration="$( wvunpack -s "$copyFile" 2>> "$errorLogFile" | fgrep 'duration:' | cut -d ':' -f 2- | tr -d ' ' )" - if [ -n "$duration" ]; then - hours="${duration%%:*}" ; hours="${hours#0}" - minutes="${duration%:*}" ; minutes="${minutes#*:}" ; minutes="${minutes#0}" - seconds="${duration##*:}" - centiseconds="${seconds#*.}" - seconds="${seconds%.*}" ; seconds="${seconds#0}" - seconds=$(( (hours * 3600) + (minutes * 60) + seconds )) - echo -n " + ${seconds}.${centiseconds}" >> "${TDIR}/durations" + if gotNewWavpack ; then + data="$( wvunpack -f "$copyFile" 2>/dev/null )" + if [ -n "$data" ]; then + samples="$( echo "$data" | cut -d ';' -f 6 )" + sampleRate="$( echo "$data" | cut -d ';' -f 1 )" + if [ -n "$samples" -a -n "$sampleRate" ]; then + seconds="$( echo "scale=2; $samples / $sampleRate" | bc )" + echo -n " + ${seconds}" >> "${TDIR}/durations" + fi + fi + else + duration="$( wvunpack -s "$copyFile" 2>/dev/null | grep -F 'duration:' | cut -d ':' -f 2- | tr -d ' ' )" + if [ -n "$duration" ]; then + hours="${duration%%:*}" ; hours="${hours#0}" + minutes="${duration%:*}" ; minutes="${minutes#*:}" ; minutes="${minutes#0}" + seconds="${duration##*:}" + centiseconds="${seconds#*.}" + seconds="${seconds%.*}" ; seconds="${seconds#0}" + seconds=$(( (hours * 3600) + (minutes * 60) + seconds )) + echo -n " + ${seconds}.${centiseconds}" >> "${TDIR}/durations" + fi fi - return ;; *.tak) cd "$SWAPDIR" - seconds="$( WINEPREFIX="$takWinePrefix" $takWineExe takc -fim2 ".\\${i}.$sourceFilename" 2>/dev/null | fgrep 'File duration:' | tr -d ' \r' | cut -d ':' -f 2 )" - seconds="${seconds%sec}" + if gotNewTAK ; then + data="$( runWineExe Takc -fi -fim5 ".\\${i}.$sourceFilename" 2>/dev/null | tr -d '\r\n' )" + if [ -n "$data" ]; then + samples="$( echo "$data" | cut -d ';' -f 5 )" + sampleRate="$( echo "$data" | cut -d ';' -f 2 )" + if [ -n "$samples" -a -n "$sampleRate" ]; then + seconds="$( echo "scale=2; $samples / $sampleRate" | bc )" + fi + fi + else + data="$( runWineExe Takc -fi -fim0 ".\\${i}.$sourceFilename" 2>/dev/null | tr -d '\r' )" + if [ -n "$data" ]; then + samples="$( echo "$data" | grep -F 'Samples per channel:' | tr -cd '0-9' )" + sampleRate="$( echo "$data" | grep -F 'Audio format:' | cut -d ',' -f 2 | tr -cd '0-9' )" + if [ -n "$samples" -a -n "$sampleRate" ]; then + seconds="$( echo "scale=2; $samples / $sampleRate" | bc )" + fi + fi + fi cd "$OLDPWD" - echo -n " + ${seconds}" >> "${TDIR}/durations" - return + if [ -n "$seconds" ]; then + echo -n " + ${seconds}" >> "${TDIR}/durations" + fi + ;; + + *.ape|*.m4a) + duration="$( ffprobe -print_format 'default' -show_streams -select_streams 'a:0' "$copyFile" 2>/dev/null | grep -F 'duration=' 2>/dev/null | cut -d '=' -f 2 | tr -cd '0-9.' )" + if [ -n "$duration" ]; then + echo -n " + $duration" >> "${TDIR}/durations" + fi + ;; + + *) + getSoxDuration "$wavFile" + echo -n " + $duration" >> "${TDIR}/durations" + ;; + esac +} + +getDownmixFactors () +{ + local divider="$1" + + fv="$( echo "scale=5; 1 / $divider" | bc )" + fv="$( printf "%.4f" "$fv" )" + + pv="$( echo "scale=5; (1 / sqrt(2)) / $divider" | bc )" + pv="$( printf "%.4f" "$pv" )" + + hv="$( echo "scale=5; 0.5 / $divider" | bc )" + hv="$( printf "%.4f" "$hv" )" +} + +upmixToStereo () +{ + local ec=$EX_OK + + sox $soxGuard -M "$wavFile" "$wavFile" $bitdepthcmd -c 2 "$resampledWavFile" $gaincmd $ratecmd $dithercmd >/dev/null 2>> "$errorLogFile" || ec=$EX_KO + + if [ $ec -eq $EX_OK -a -f "$resampledWavFile" ]; then + mv -f "$resampledWavFile" "$wavFile" >/dev/null 2>> "$errorLogFile" || ec=$EX_KO + if [ $ec -eq $EX_OK ]; then + isWavProcessed 'true' + fi + fi + if [ -e "$resampledWavFile" ]; then rm -f "$resampledWavFile"; fi + return $ec +} + +downmixToStereo () +{ + local ec=$EX_OK surroundConfig='' fv='' pv='' hv='' + + # The following channel mappings have been thoroughly tested and should never clip, even in worst-case scenarios + # Front ($fv): factor 1, LFE ($hv): factor 0.5 (1/2), everything else ($pv): factor 0.7071 (1 / sqrt(2)) + # http://www.academia.edu/2397757/A_downmix_approach + case "$nChannels" in + 3) # FL, FR, LFE + surroundConfig='2.1' + getDownmixFactors '1.55' + sox $soxGuard "$wavFile" $bitdepthcmd "$resampledWavFile" $ratecmd remix -m 1v${fv},3v${hv} 2v${fv},3v${hv} $dithercmd >/dev/null 2>> "$errorLogFile" || ec=$EX_KO + ;; + + 4) # FL, FR, SL, SR + surroundConfig='4.0' + getDownmixFactors '1.75' + sox $soxGuard "$wavFile" $bitdepthcmd "$resampledWavFile" $ratecmd remix -m 1v${fv},3v${pv} 2v${fv},4v${pv} $dithercmd >/dev/null 2>> "$errorLogFile" || ec=$EX_KO + ;; + + 5) # FL, FR, C, SL, SR + surroundConfig='5.0' + getDownmixFactors '2.5' + sox $soxGuard "$wavFile" $bitdepthcmd "$resampledWavFile" $ratecmd remix -m 1v${fv},3v${pv},4v${pv} 2v${fv},3v${pv},5v${pv} $dithercmd >/dev/null 2>> "$errorLogFile" || ec=$EX_KO ;; - *.ape) f="$copyFile" ;; - *) f="$wavFile" ;; + 6) # FL, FR, C, LFE, SL, SR + surroundConfig='5.1' + getDownmixFactors '3' + sox $soxGuard "$wavFile" $bitdepthcmd "$resampledWavFile" $ratecmd remix -m 1v${fv},3v${pv},4v${hv},5v${pv} 2v${fv},3v${pv},4v${hv},6v${pv} $dithercmd >/dev/null 2>> "$errorLogFile" || ec=$EX_KO + ;; + + 8) # FL, FR, C, LFE, SL, SR, BL, BR + surroundConfig='7.1' + getDownmixFactors '3.75' + sox $soxGuard "$wavFile" $bitdepthcmd "$resampledWavFile" $ratecmd remix -m 1v${fv},3v${pv},4v${hv},5v${pv},7v${pv} 2v${fv},3v${pv},4v${hv},6v${pv},8v${pv} $dithercmd >/dev/null 2>> "$errorLogFile" || ec=$EX_KO + ;; + + *) return $EX_OK ;; # nothing to do esac - duration="$( shninfo "$f" 2>> "$errorLogFile" | fgrep 'Length:' | cut -d ':' -f 2- | tr -d ' ' )" - if [ -n "$duration" ]; then - duration="${duration##* }" - minutes="${duration%:*}" ; minutes="${minutes#0}" - seconds="${duration#*:}" - milliseconds="${seconds#*.}" ; milliseconds="${milliseconds#0}" - seconds="${seconds%.*}" ; seconds="${seconds#0}" - seconds=$(( (minutes * 60) + seconds )) - if [ "${milliseconds:2:1}" = '' ]; then # CD frames, i.e. <= 75 - milliseconds=$(( milliseconds * 100 / 75 )) + unset fv pv hv + + if [ $ec -eq $EX_OK -a -f "$resampledWavFile" ]; then + mv -f "$resampledWavFile" "$wavFile" >/dev/null 2>> "$errorLogFile" || ec=$EX_KO + if [ $ec -eq $EX_OK ]; then + isWavProcessed 'true' + fi + fi + if [ -e "$resampledWavFile" ]; then rm -f "$resampledWavFile"; fi + return $ec +} + +isWavProcessed () +{ + local val="$1" + + if [ -z "$wavProcessedStatusFile" ]; then + wavProcessed='false' + else + if [ "$val" = 'true' ]; then + if [ ! -e "$wavProcessedStatusFile" ]; then + touch "$wavProcessedStatusFile" + fi + wavProcessed='true' + elif [ "$val" = 'false' ]; then + if [ -e "$wavProcessedStatusFile" ]; then + rm -f "$wavProcessedStatusFile" >/dev/null 2>&1 + fi + wavProcessed='false' + else + if [ -e "$wavProcessedStatusFile" ]; then + wavProcessed='true' + else + wavProcessed='false' + fi fi - duration="${seconds}.$milliseconds" - echo -n " + $duration" >> "${TDIR}/durations" + fi + + if [ "$wavProcessed" = 'true' ]; then + return $EX_OK + else + return $EX_KO fi } decode () { - local ec=$EX_OK sourceTagFormat hline samples sampleRate duration hours minutes seconds centiseconds pattern soxGuard sourceMD5='' kfm='' + local ec=$EX_OK sourceTagFormat hline samples sampleRate duration hours minutes seconds centiseconds pattern soxGuard='' sourceMD5='' kfm='' nChannels=2 gaincmd='' ratecmd='' bitdepthcmd='' dithercmd='' gainPeak='' + isWavProcessed 'false' if [ -e "$copyFile" ]; then case "$sourceExtension" in wav) @@ -2744,11 +4193,18 @@ touch "$sourceTagFile" ;; + aiff|caf) + sourceTagFormat='vc' + sox "$copyFile" -t wav "$wavFile" >/dev/null 2>> "$errorLogFile" || ec=$EX_KO + touch "$sourceTagFile" + ;; + flac) sourceTagFormat='vc' - sourceMD5="$( metaflac --show-md5sum "$copyFile" 2>/dev/null )" + getInternalMD5 "$copyFile" if [ $keepWavMetadata = true ]; then if ! flac -s -d --keep-foreign-metadata -o "$wavFile" "$copyFile" >/dev/null 2>&1; then + if [ -e "$wavFile" ]; then rm -f "$wavFile"; fi flac -s -d -o "$wavFile" "$copyFile" >/dev/null 2>> "$errorLogFile" || ec=$EX_KO fi else @@ -2757,42 +4213,57 @@ if [ $ec -eq $EX_OK ]; then metaflac --no-utf8-convert --export-tags-to="$sourceTagFile" "$copyFile" >/dev/null 2>> "$errorLogFile" && extractFlacArtwork || ec=$EX_KO + if [ -e "$sourceTagFile" ]; then + grep -iv 'WAVEFORMATEXTENSIBLE_CHANNEL_MASK=' "$sourceTagFile" > "${sourceTagFile}.tmp" + mv "${sourceTagFile}.tmp" "$sourceTagFile" + fi fi ;; wv) sourceTagFormat='ape' kfm='-w' - sourceMD5="$( wvunpack -s "$copyFile" 2>&1 | fgrep 'original md5:' | tr -d ' ' | cut -d ':' -f 2 )" + getInternalMD5 "$copyFile" if [ $keepWavMetadata = true ]; then kfm='' ; fi - wvunpack -q $kfm -m -o "$wavFile" "$copyFile" >/dev/null 2>> "$errorLogFile" && - apetag -i "$copyFile" 2>> "$errorLogFile" | sed -e 's@\x0@ / @g' -e 's@" "@=@' -e 's@^"@@' -e 's@"$@@' > "$sourceTagFile" && - filterApetagOutput || ec=$EX_KO + wvunpack -q $kfm -m -o "$wavFile" "$copyFile" >/dev/null 2>> "$errorLogFile" || ec=$EX_KO + if [ $ec -eq $EX_OK ]; then + APEv2 -z "$copyFile" 2>> "$errorLogFile" | grep -vE '(=data:)|(=artwork:)' | $sedcmd -e 's@\\n@\n@g' -e 's@\\x00@\x02@g' > "$sourceTagFile" 2>/dev/null + extractAPEv2Binaries + extractAPEv2Artwork + fi ;; ape) sourceTagFormat='ape' - mac "$copyFile" "$wavFile" -d >/dev/null 2>> "$errorLogFile" && - apetag -i "$copyFile" 2>> "$errorLogFile" | sed -e 's@\x0@, @g' | sed -e 's@" "@=@' -e 's@^"@@' -e 's@"$@@' > "$sourceTagFile" && - filterApetagOutput || ec=$EX_KO + mac "$copyFile" "$wavFile" -d >/dev/null 2>> "$errorLogFile" || ec=$EX_KO + if [ $ec -eq $EX_OK ]; then + APEv2 -z "$copyFile" 2>> "$errorLogFile" | grep -vE '(=data:)|(=artwork:)' | $sedcmd -e 's@\\n@\n@g' -e 's@\\x00@\x02@g' > "$sourceTagFile" 2>/dev/null + extractAPEv2Binaries + extractAPEv2Artwork + fi ;; tak) sourceTagFormat='ape' + getInternalMD5 "$copyFile" cd "$SWAPDIR" - sourceMD5="$( WINEPREFIX="$takWinePrefix" $takWineExe takc -fim3 ".\\${i}.${sourceFilename}" 2>/dev/null | fgrep 'MD5:' | tr -d ' ' | cut -d ':' -f 2 )" - WINEPREFIX="$takWinePrefix" $takWineExe takc -d -md5 ".\\${i}.${sourceFilename}" ".\\${i}.wav" >/dev/null 2>> "$errorLogFile" || ec=$EX_KO + runWineExe Takc -d ".\\${i}.${sourceFilename}" ".\\${i}.wav" >/dev/null 2>> "$errorLogFile" || ec=$EX_KO cd "$OLDPWD" if [ $ec -eq $EX_OK ]; then - apetag -i "$copyFile" 2>> "$errorLogFile" | sed -e 's@\x0@, @g' | sed -e 's@" "@=@' -e 's@^"@@' -e 's@"$@@' > "$sourceTagFile" && - filterApetagOutput || ec=$EX_KO + APEv2 -z "$copyFile" 2>> "$errorLogFile" | grep -vE '(=data:)|(=artwork:)' | $sedcmd -e 's@\\n@\n@g' -e 's@\\x00@\x02@g' > "$sourceTagFile" 2>/dev/null + extractAPEv2Binaries + extractAPEv2Artwork fi ;; m4a) sourceTagFormat='m4a' - alac -f "$wavFile" "$copyFile" >/dev/null 2>> "$errorLogFile" && - extractAlacMetadata && - extractAlacArtwork || ec=$EX_KO + # nohup is needed because ffmpeg messes with the terminal somehow + nohup ffmpeg -v quiet -i "$copyFile" "$wavFile" > "${TDIR}/${i}.nohup" 2>> "$errorLogFile"; ec=$? + if [ -e "${TDIR}/${i}.nohup" ]; then rm -f "${TDIR}/${i}.nohup"; fi + if [ $ec -eq $EX_OK ]; then + extractAlacMetadata && + extractAlacArtwork || ec=$EX_KO + fi ;; *) ec=$EX_KO ;; @@ -2807,23 +4278,82 @@ if [ $ec -eq $EX_OK ]; then saveDurations + if [ -e "$copyFile" ]; then + rm -f "$copyFile" "${copyFile}c" + fi if [ -f "$sourceTagFile" ]; then - sed -i -e 's@SOURCEMD5=@µµµMD5=@i' -e 's@SOURCECRC=@µµµCRC=@i' -e 's@SOURCESHA1=@µµµSHA1=@i' "$sourceTagFile" + $sedcmd -i'' -e 's@SOURCEMD5=@µµµMD5=@i' -e 's@SOURCECRC32=@µµµCRC32=@i' -e 's@SOURCESHA1=@µµµSHA1=@i' -e 's@SOURCESHA256=@µµµSHA256=@i' -e 's@SOURCESHA512=@µµµSHA512=@i' "$sourceTagFile" fi - gainValue='' + gainValue='' gaincmd='' if [ $applyGain = true ]; then if [ "$applyGainType" = 'ALBUM' ]; then - gainValue="$( fgrep -i 'replaygain_album_gain' "$sourceTagFile" | cut -d '=' -f 2 | cut -d ' ' -f 1 )" + gainValue="$( grep -Fi 'replaygain_album_gain' "$sourceTagFile" 2>/dev/null | cut -d '=' -f 2 | cut -d ' ' -f 1 )" + if [ "${preamp:0:1}" = '-' ]; then + gainValue="$( printf '%.2f' "$( echo "$gainValue - ${preamp:1}" | bc -l 2>/dev/null )" )" + elif [ "${preamp:0:1}" = '+' ]; then + gainValue="$( printf '%.2f' "$( echo "$gainValue + ${preamp:1}" | bc -l 2>/dev/null )" )" + fi + if [ "${gainValue:0:1}" != '-' -a "${gainValue:0:1}" != '+' ]; then + gainValue="+${gainValue}" + fi + elif [ "$applyGainType" = 'TRACK' ]; then + gainValue="$( grep -Fi 'replaygain_track_gain' "$sourceTagFile" 2>/dev/null | cut -d '=' -f 2 | cut -d ' ' -f 1 )" + if [ "${preamp:0:1}" = '-' ]; then + gainValue="$( printf '%.2f' "$( echo "$gainValue - ${preamp:1}" | bc -l 2>/dev/null )" )" + elif [ "${preamp:0:1}" = '+' ]; then + gainValue="$( printf '%.2f' "$( echo "$gainValue + ${preamp:1}" | bc -l 2>/dev/null )" )" + fi + if [ "${gainValue:0:1}" != '-' -a "${gainValue:0:1}" != '+' ]; then + gainValue="+${gainValue}" + fi + elif [ "$applyGainType" = 'ALBUM_PEAK' -o "$applyGainType" = 'TRACK_PEAK' ]; then + if [ "$applyGainType" = 'ALBUM_PEAK' ]; then + gainPeak="$( grep -Fi 'replaygain_album_peak' "$sourceTagFile" 2>/dev/null | cut -d '=' -f 2 )" + elif [ "$applyGainType" = 'TRACK_PEAK' ]; then + gainPeak="$( grep -Fi 'replaygain_track_peak' "$sourceTagFile" 2>/dev/null | cut -d '=' -f 2 )" + fi + if [ "${gainPeak:0:1}" = '0' ]; then + gainValue="$( printf "%.2f" "$( echo "20 * (l(${gainPeak}) / l(10))" | bc -l )" )" + if [ "${gainValue:0:1}" != '-' ]; then # gainValue is positive + if [ "${peakReference:0:1}" = '-' ]; then + gainValue="${peakReference}" + else + gainValue='' + fi + else # gainValue is negative + if [ "${peakReference:0:1}" = '-' ]; then + gainValue="$( printf "%.2f" "$( echo "${gainValue:1} + (${peakReference})" | bc -l )" )" + if [ "${gainValue:0:1}" != '-' ]; then + gainValue="+${gainValue}" + fi + else + gainValue="+${gainValue:1}" + fi + fi + elif [ "${peakReference:0:1}" = '-' ]; then + gainValue="$peakReference" + fi else - gainValue="$( fgrep -i 'replaygain_track_gain' "$sourceTagFile" | cut -d '=' -f 2 | cut -d ' ' -f 1 )" + gainValue="$applyGainType" + fi + + if [ -n "$gainValue" ]; then + gaincmd="gain $gainValue" fi fi + nChannels="$( soxi -c "$wavFile" 2>> "$errorLogFile" )" + if [ -z "$nChannels" -o "$nChannels" = '0' ]; then + nChannels=2 + fi + hasLossy=false hasLossless=false if [ -n "$gainValue" -o -n "$bitDepth" -o -n "$samplingRate" ]; then hasLossy=true + elif [ "$convertToStereo" != 'false' -a $nChannels -ne 2 ]; then + hasLossy=true fi for outputCodec in $outputCodecs; do case "$outputCodec" in @@ -2835,65 +4365,108 @@ if [ $hasLossy = true ]; then for h in $hashes; do case "$h" in - CRC) - hline="$( computeCRC "$wavFile" )" - echo "SOURCE${hline}" > "$sourceCRCFile" - ;; + CRC32) + { + hline="$( computeCRC32 "$wavFile" )" + test -n "$hline" && echo "SOURCECRC32=${hline}" > "$sourceCRC32File" + } & ;; + MD5) - if [ -n "$sourceMD5" ]; then - echo "SOURCEMD5=${sourceMD5}" > "$sourceMD5File" - else - hline="$( computeMD5 "$wavFile" )" - echo "SOURCE${hline}" > "$sourceMD5File" + { + hline="$( computeMD5 "$wavFile" )" + if [ -n "$hline" ]; then + echo "SOURCEMD5=${hline}" > "$sourceMD5File" + if [ -n "$sourceMD5" -a "$sourceMD5" != "$hline" ]; then + printMessage 'warning' 'decoding' 'bad_internal_hash' $p "file:${sourceFile}" 'internal MD5 hash is incorrect (possible bug in the codec)!' + printMessage 'info' 'decoding' 'stderr' 'Please file a bug report: http://caudec.net/redirect/bugReport' + fi fi - ;; + } & ;; + SHA1) - hline="$( computeSHA1 "$wavFile" )" - echo "SOURCE${hline}" > "$sourceSHA1File" - ;; + { + hline="$( computeSHA "$wavFile" "$h" )" + test -n "$hline" && echo "SOURCESHA1=${hline}" > "$sourceSHA1File" + } & ;; + + SHA256) + { + hline="$( computeSHA "$wavFile" "$h" )" + test -n "$hline" && echo "SOURCESHA256=${hline}" > "$sourceSHA256File" + } & ;; + + SHA512) + { + hline="$( computeSHA "$wavFile" "$h" )" + test -n "$hline" && echo "SOURCESHA512=${hline}" > "$sourceSHA512File" + } & ;; esac done + wait fi - soxGuard='' if [ $preventClipping = true ]; then soxGuard='-G' fi + if [ -n "$bitDepth" ]; then + bitdepthcmd="-b $bitDepth" + fi + + if [ -n "$samplingRate" ]; then + ratecmd="rate -v $samplingRate" + fi + if [ -n "$bitDepth" -o -n "$samplingRate" ]; then - if [ -n "$bitDepth" -a -n "$samplingRate" ]; then - if [ -n "$gainValue" ]; then - sox $soxGuard "$wavFile" -b $bitDepth -t wavpcm "$resampledWavFile" rate -v $samplingRate dither -a -s gain "$gainValue" >/dev/null 2>> "$errorLogFile" || ec=$EX_KO - else - sox $soxGuard "$wavFile" -b $bitDepth -t wavpcm "$resampledWavFile" rate -v $samplingRate dither -a -s >/dev/null 2>> "$errorLogFile" || ec=$EX_KO - fi - elif [ -n "$bitDepth" ]; then - if [ -n "$gainValue" ]; then - sox $soxGuard "$wavFile" -b $bitDepth -t wavpcm "$resampledWavFile" dither -a -s gain "$gainValue" >/dev/null 2>> "$errorLogFile" || ec=$EX_KO - else - sox $soxGuard "$wavFile" -b $bitDepth -t wavpcm "$resampledWavFile" dither -a -s >/dev/null 2>> "$errorLogFile" || ec=$EX_KO - fi - elif [ -n "$samplingRate" ]; then - if [ -n "$gainValue" ]; then - sox $soxGuard "$wavFile" -t wavpcm "$resampledWavFile" rate -v $samplingRate dither -a -s gain "$gainValue" >/dev/null 2>> "$errorLogFile" || ec=$EX_KO - else - sox $soxGuard "$wavFile" -t wavpcm "$resampledWavFile" rate -v $samplingRate dither -a -s >/dev/null 2>> "$errorLogFile" || ec=$EX_KO - fi + dithercmd='dither -a -s' # FL, FR, C, LFE, SL, SR, BL, BR; 1/4 + fi + + # convert to stereo (and at the same time, resample and / or apply gain if requested) + if [ "$convertToStereo" != 'false' ]; then + if [ $nChannels -gt 2 ]; then + downmixToStereo || ec=$EX_IOERR + elif [ $nChannels -eq 1 ]; then + upmixToStereo || ec=$EX_IOERR fi - if [ $ec -eq $EX_OK -a -f "$resampledWavFile" ]; then - mv -f "$resampledWavFile" "$wavFile" >/dev/null 2>> "$errorLogFile" || ec=$EX_KO + fi + + # resample (and apply gain if requested) if not already done + # make sure to test both wavProcessed != 'true' and ec = EX_OK, because wavProcessed will only be true if prior processing succeeded + if [ $ec -eq $EX_OK -a "$wavProcessed" != 'true' ]; then # no prior processing, resampling has not yet be done + if [ -n "$bitdepthcmd" -o -n "$ratecmd" ]; then + sox $soxGuard "$wavFile" $bitdepthcmd "$resampledWavFile" $gaincmd $ratecmd $dithercmd >/dev/null 2>> "$errorLogFile" || ec=$EX_IOERR + if [ $ec -eq $EX_OK -a -f "$resampledWavFile" ]; then + mv -f "$resampledWavFile" "$wavFile" >/dev/null 2>> "$errorLogFile" || ec=$EX_IOERR + if [ $ec -eq $EX_OK ]; then + isWavProcessed 'true' + fi + fi + if [ -e "$resampledWavFile" ]; then rm -f "$resampledWavFile"; fi fi - elif [ -n "$gainValue" ]; then - sox $soxGuard "$wavFile" -t wavpcm "$resampledWavFile" gain "$gainValue" >/dev/null 2>> "$errorLogFile" || ec=$EX_KO + fi + + # apply gain if not already done + if [ $ec -eq $EX_OK -a "$wavProcessed" != 'true' -a -n "$gaincmd" ]; then # no prior processing, gain has not yet been applied + sox $soxGuard "$wavFile" "$resampledWavFile" $gaincmd >/dev/null 2>> "$errorLogFile" || ec=$EX_IOERR if [ $ec -eq $EX_OK -a -f "$resampledWavFile" ]; then - mv -f "$resampledWavFile" "$wavFile" >/dev/null 2>> "$errorLogFile" || ec=$EX_KO + mv -f "$resampledWavFile" "$wavFile" >/dev/null 2>> "$errorLogFile" || ec=$EX_IOERR + if [ $ec -eq $EX_OK ]; then + isWavProcessed 'true' + fi fi + if [ -e "$resampledWavFile" ]; then rm -f "$resampledWavFile"; fi fi - else - rm -f "$wavFile" "$lossywavFile" # in case it exists + fi + + if [ $ec -ne $EX_OK ]; then + rm -f "$wavFile" "$copyFile" "${copyFile}c" "$lossywavFile" "$resampledWavFile" # in case it exists rm -f "${TDIR}/"*"_${i}" # delete all codec lock files rm -f "${TDIR}/"*"_${i}_WAV_NEEDED" # delete associated lock files - printf "${GR}%2u ${KO}DC ${CY}%s${NM}\n" $p "$sourceFilename" 1>&2 + if [ $ec -eq $EX_IOERR ]; then + printMessage 'error' 'processing' "file:${sourceFile}" $p + elif [ $ec -ne $EX_OK ]; then + printMessage 'error' 'decoding' "file:${sourceFile}" $p + fi return $EX_KO fi @@ -2906,41 +4479,84 @@ if [ $hasLossless = true ]; then for h in $hashes; do case "$h" in - CRC) - if [ -z "$gainValue" -a -z "$bitDepth" -a -z "$samplingRate" -a -f "$sourceCRCFile" ]; then - sed -e 's@SOURCE@@' "$sourceCRCFile" > "$losslessCRCFile" + CRC32) + if [ "$wavProcessed" != 'true' -a -f "$sourceCRC32File" ]; then + $sedcmd -e 's@SOURCE@@' "$sourceCRC32File" > "$losslessCRC32File" 2>/dev/null + hval="$( $sedcmd -e 's@SOURCE@@' "$sourceCRC32File" 2>/dev/null )" + test -n "$hval" && printMessage 'info' "$h" "$hval" "file:${sourceFile}" $p else - hline="$( computeCRC "$wavFile" )" - echo "${hline}" > "$losslessCRCFile" + { + hline="$( computeCRC32 "$wavFile" )" + test -n "$hline" && echo "CRC32=${hline}" > "$losslessCRC32File" + test -n "$hline" && printMessage 'info' "$h" "CRC32=$hline" "file:${sourceFile}" $p + } & fi ;; MD5) - if [ -z "$gainValue" -a -z "$bitDepth" -a -z "$samplingRate" ]; then - if [ -f "$sourceMD5File" ]; then - sed -e 's@SOURCE@@' "$sourceMD5File" > "$losslessMD5File" - elif [ -n "$sourceMD5" ]; then - echo "MD5=${sourceMD5}" > "$losslessMD5File" - else - hline="$( computeMD5 "$wavFile" )" - echo "${hline}" > "$losslessMD5File" - fi + if [ "$wavProcessed" != 'true' -a -f "$sourceMD5File" ]; then + $sedcmd -e 's@SOURCE@@' "$sourceMD5File" > "$losslessMD5File" + hval="$( $sedcmd -e 's@SOURCE@@' "$sourceMD5File" 2>/dev/null )" + test -n "$hval" && printMessage 'info' "$h" "$hval" "file:${sourceFile}" $p else + { hline="$( computeMD5 "$wavFile" )" - echo "${hline}" > "$losslessMD5File" + test -n "$hline" && printMessage 'info' "$h" "MD5=$hline" "file:${sourceFile}" $p + if [ -n "$hline" ]; then + echo "MD5=${hline}" > "$losslessMD5File" + if [ "$wavProcessed" != 'true' -a -n "$sourceMD5" -a "$sourceMD5" != "$hline" ]; then + printMessage 'warning' 'decoding' 'bad_internal_hash' $p "file:${sourceFile}" 'internal MD5 hash is incorrect (possible bug in the codec)!' + printMessage 'info' 'decoding' 'stderr' 'Please file a bug report: http://caudec.net/redirect/bugReport' + fi + fi + } & fi ;; SHA1) - if [ -z "$gainValue" -a -z "$bitDepth" -a -z "$samplingRate" -a -f "$sourceSHA1File" ]; then - sed -e 's@SOURCE@@' "$sourceSHA1File" > "$losslessSHA1File" + if [ "$wavProcessed" != 'true' -a -f "$sourceSHA1File" ]; then + $sedcmd -e 's@SOURCE@@' "$sourceSHA1File" > "$losslessSHA1File" + hval="$( $sedcmd -e 's@SOURCE@@' "$sourceSHA1File" 2>/dev/null )" + test -n "$hval" && printMessage 'info' "$h" "$hval" "file:${sourceFile}" $p else - hline="$( computeSHA1 "$wavFile" )" - echo "${hline}" > "$losslessSHA1File" + { + hline="$( computeSHA "$wavFile" "$h" )" + test -n "$hline" && echo "SHA1=${hline}" > "$losslessSHA1File" + test -n "$hline" && printMessage 'info' "$h" "SHA1=$hline" "file:${sourceFile}" $p + } & + fi + ;; + + SHA256) + if [ "$wavProcessed" != 'true' -a -f "$sourceSHA256File" ]; then + $sedcmd -e 's@SOURCE@@' "$sourceSHA256File" > "$losslessSHA256File" + hval="$( $sedcmd -e 's@SOURCE@@' "$sourceSHA256File" 2>/dev/null )" + test -n "$hval" && printMessage 'info' "$h" "$hval" "file:${sourceFile}" $p + else + { + hline="$( computeSHA "$wavFile" "$h" )" + test -n "$hline" && echo "SHA256=${hline}" > "$losslessSHA256File" + test -n "$hline" && printMessage 'info' "$h" "SHA256=$hline" "file:${sourceFile}" $p + } & + fi + ;; + + SHA512) + if [ "$wavProcessed" != 'true' -a -f "$sourceSHA512File" ]; then + $sedcmd -e 's@SOURCE@@' "$sourceSHA512File" > "$losslessSHA512File" + hval="$( $sedcmd -e 's@SOURCE@@' "$sourceSHA512File" 2>/dev/null )" + test -n "$hval" && printMessage 'info' "$h" "$hval" "file:${sourceFile}" $p + else + { + hline="$( computeSHA "$wavFile" "$h" )" + test -n "$hline" && echo "SHA512=${hline}" > "$losslessSHA512File" + test -n "$hline" && printMessage 'info' "$h" "SHA512=$hline" "file:${sourceFile}" $p + } & fi ;; esac done + wait fi processSourceTagFile @@ -2959,26 +4575,57 @@ encodeLossyWAV () { - local ec=$EX_OK + local ec=$EX_OK overwriteLossyWAV=true + + encodedFile="${SWAPDIR}/${i}.lossy.wav" - if [ ! -e "$lossywavFile" ]; then - cd "$SWAPDIR" - WINEPREFIX="$lossywavWinePrefix" $lossywavWineExe lossyWAV "${i}.wav" -q $compression_lossyWAV >/dev/null 2>> "$errorLogFile" || ec=$EX_KO - cd "$OLDPWD" + if [ $copyLossyWAV = true ]; then + getFileProps "$sourceFile" 'lossyWAV' + if [ $copyPath = true ]; then + if ! mkdir -p "$destPath" >/dev/null 2>&1 ; then + ec=$EX_KO + fi + fi + fi + + if [ $ec -eq $EX_OK ]; then + if [ ! -e "$lossywavFile" ]; then + cd "$SWAPDIR" + runWineExe lossyWAV "${i}.wav" -q $compression_lossyWAV >/dev/null 2>> "$errorLogFile" || ec=$EX_KO + cd "$OLDPWD" + fi fi - encodedFile="${SWAPDIR}/${i}.lossy.wav" - destFilename="${sourceBasename}.lossy.wav" - destFile="${destDir}/${destFilename}" if [ $ec -eq $EX_OK ]; then for outputCodec in $outputCodecs; do case "$outputCodec" in lossyFLAC|lossyWV|lossyTAK) for h in $hashes; do case $h in - CRC) hline="$( computeCRC "$lossywavFile" )" ; echo "$hline" > "$lossywavCRCFile" ;; - MD5) hline="$( computeMD5 "$lossywavFile" )" ; echo "$hline" > "$lossywavMD5File" ;; - SHA1) hline="$( computeSHA1 "$lossywavFile" )" ; echo "$hline" > "$lossywavSHA1File" ;; + CRC32) + hline="$( computeCRC32 "$lossywavFile" )" + test -n "$hline" && echo "CRC32=$hline" > "$lossywavCRC32File" + ;; + + MD5) + hline="$( computeMD5 "$lossywavFile" )" + test -n "$hline" && echo "MD5=$hline" > "$lossywavMD5File" + ;; + + SHA1) + hline="$( computeSHA "$lossywavFile" "$h" )" + test -n "$hline" && echo "SHA1=$hline" > "$lossywavSHA1File" + ;; + + SHA256) + hline="$( computeSHA "$lossywavFile" "$h" )" + test -n "$hline" && echo "SHA256=$hline" > "$lossywavSHA256File" + ;; + + SHA512) + hline="$( computeSHA "$lossywavFile" "$h" )" + test -n "$hline" && echo "SHA512=$hline" > "$lossywavSHA512File" + ;; esac done break @@ -2987,10 +4634,21 @@ done if [ $copyLossyWAV = true ]; then - chmod 0644 "$encodedFile" - cp "$encodedFile" "$destFile" >/dev/null 2>> "$errorLogFile" - if [ $verbose = true ]; then - printf "${GR}%2u ${OK}OK ${CY}%s${NM}\n" $p "$destFilename" + overwriteLossyWAV=true + if [ -e "$destFile" ]; then + if [ $keepExistingFiles = true ]; then + overwriteLossyWAV=false + elif [ $keepNewerFiles = true ]; then + if isFileNewer "$destFile" "$sourceFile"; then + overwriteLossyWAV=false + fi + fi + fi + if [ $overwriteLossyWAV = true ]; then + chmod 0644 "$encodedFile" + cp "$encodedFile" "$destFile" >/dev/null 2>> "$errorLogFile" + touch "$destFile" >/dev/null 2>> "$errorLogFile" # make sure that the mtime value is current + printMessage 'success' 'encoding' $p "file:${destFile}" fi fi else @@ -2998,34 +4656,190 @@ rm -f "${TDIR}/lossy"*"_${i}" # delete all lossy* codec lock files rm -f "${TDIR}/"*"_${i}_LOSSYWAV_NEEDED" # delete associated codec lock files rm -f "${TDIR}/lossyWAV_${i}_WAV_NEEDED" - printf "${GR}%2u ${KO}ER ${CY}%s${NM}\n" $p "$destFilename" 1>&2 + printMessage 'error' 'encoding' $p "file:${sourceFile}" fi return $ec } +getEyeD3Version () +{ + local foo major minor + + if which 'eyeD3' >/dev/null 2>&1; then + foo="$( eyeD3 --version 2>&1 | head -n 1 2>/dev/null | tr -cd '0-9.' 2>/dev/null )" + foo="${foo:1}" ; foo="${foo%2002*}" + major="${foo%%.*}" + minor="${foo#*.}"; minor="${minor%%.*}" + case "$major" in + 0[0-9]) major="${major#0}" ;; + [1-9]|[1-9][0-9]) true ;; + *) major=0 ;; + esac + case "$minor" in + 0[0-9]) minor="${minor#0}" ;; + [1-9]|[1-9][0-9]) true ;; + *) minor=0 ;; + esac + if [ $major -gt 0 -o $minor -ge 7 ]; then + eyeD3Version='0.7+' + else + eyeD3Version='0.6-' + fi + else # assume user has the latest version + eyeD3Version='0.7+' + fi +} + +gotNewTAK () +{ + if [ -z "$gotRecentTAK" ]; then + gotRecentTAK=false + if runWineExe Takc -version >/dev/null 2>&1; then + gotRecentTAK=true + fi + fi + + if [ "$gotRecentTAK" = 'true' ]; then + return $EX_OK + else + return $EX_KO + fi + +# New TAK data format (-fi -fim5): +# Sample type: 0 = integer / 1 = floating point +# Sampling rate +# Bit depth +# Number of channels +# Total number of samples +# MD5 hash +# Encoder version +# Internal codec number +# Compression preset +# Frame size in samples +# Wave file meta data present: 0 = no / 1 = yes +} + +gotNewWavpack () +{ + local o='' + + if [ -z "$gotRecentWavpack" ]; then + gotRecentWavpack=false + o="$( wavpack --version 2>/dev/null )" + if [ -n "$o" ]; then + gotRecentWavpack=true + fi + fi + + if [ "$gotRecentWavpack" = 'true' ]; then + return $EX_OK + else + return $EX_KO + fi + +# New WavPack data format (-f): +# 1. sampling rate +# 2. bit-depth (1-32) +# 3. format ("int" or "float") +# 4. number of channels +# 5. channel mask (in hex because it's a mask, always prefixed with "0x") +# 6. number of samples (missing if unknown) +# 7. md5sum (technically is hex, but not prefixed with "0x", might be missing) +# 8. encoder version (basically this will always be 4, but there are some old files out there, could be 5 one day) +# 9. encoding mode (in hex because it's a bitfield, always prefixed with "0x") +# 10. filename +} + +runWineExe () +{ + local exe="$1" + + if [ -d "$WIN64PATH" -a -e "${WIN64PATH}/drive_c/windows/${exe}.exe" ]; then + WINEPREFIX="$WIN64PATH" wineserver -d0 -p0 >/dev/null 2>&1 + if [ $gotWine64 = true ]; then + WINEPREFIX="$WIN64PATH" wine64 "$@" + else + WINEPREFIX="$WIN64PATH" wine "$@" + fi + elif [ -d "$WIN32PATH" -a -e "${WIN32PATH}/drive_c/windows/${exe}.exe" ]; then + WINEPREFIX="$WIN32PATH" wineserver -d0 -p0 >/dev/null 2>&1 + WINEPREFIX="$WIN32PATH" wine "$@" + else + echo "Windows EXE '$exe' not found." >> "$errorLogFile" + return $EX_KO + fi +} + +setWineParams () +{ + local exe="$1" + + if [ -z "$exe" ]; then + unset winePath wineExe + else + if [ -d "$WIN64PATH" -a -e "${WIN64PATH}/drive_c/windows/${exe}.exe" ]; then + winePath="$WIN64PATH" + if [ $gotWine64 = true ]; then + wineExe='wine64' + else + wineExe='wine' + fi + else + winePath="$WIN32PATH" + wineExe='wine' + fi + fi +} + getEncoderVersions () { + local oggencPath vorbisPrefix libvorbisVersion + + # ALAC and AAC are not in the list because encoders already add encoding metadata on their own for outputCodec in $outputCodecs; do case "$outputCodec" in - FLAC) flacVersion="$( flac --version | tr -cd '0-9\.' )" ;; - Flake) flakeVersion="$( flake 2>&1 | fgrep version | tr -cd '0-9\.' )" ;; - WavPack|WavPackHybrid|WavPackLossy) wavpackVersion="$( wavpack --help 2>&1 | grep Version | tr -cd '0-9\.' )" ;; - MonkeysAudio) apeVersion="$( mac 2>&1 | fgrep '(c)' | tr -d ' ' | cut -d '(' -f 2 )"; apeVersion="${apeVersion#v}"; apeVersion="${apeVersion%)}" ;; - TAK) takVersion="$( WINEPREFIX="$takWinePrefix" $takWineExe takc 2>/dev/null | fgrep 'TAKC V' | tr -cd '0-9\.' )" ;; - lossy*) lossywavVersion="$( WINEPREFIX="$lossywavWinePrefix" $lossywavWineExe lossyWAV -v 2>&1 | fgrep lossyWAV | tr -cd '0-9\.' )" ;; - WinVorbis) winoggencVersion="$( WINEPREFIX="$oggencWinePrefix" $oggencWineExe oggenc2 -v 2>/dev/null | tr -d '\r\n' )" ;; - LAME) lameVersion="$( lame --version 2>/dev/null | grep -E 'LAME.*version' | cut -d ' ' -f 4 )" ;; - WinLAME) winlameVersion="$( WINEPREFIX="$lameWinePrefix" $lameWineExe lame --version 2>/dev/null | grep -E 'LAME.*version' | cut -d ' ' -f 4 )" ;; - Musepack) mpcVersion="$( mpcenc 2>&1 | fgrep 'MPC Encoder' | cut -d ' ' -f 3 )" ;; - Opus) opusVersion="$( opusenc --version 2>/dev/null | fgrep opusenc )" ;; + FLAC) flacVersion="$( flac --version | head -n 1 )"; flacVersion="${flacVersion##* }" ;; + Flake) flakeVersion="$( flake -h 2>&1 | grep -F 'version' )"; flakeVersion="${flakeVersion##* }" ;; + MonkeysAudio) apeVersion="$( mac 2>&1 | grep -F '(c)' | tr -d ' ' | cut -d '(' -f 2 | cut -d ')' -f 1 )"; apeVersion="${apeVersion#v}" ;; + WinVorbis) winoggencVersion="$( runWineExe oggenc2 -v 2>/dev/null | head -n 1 | tr -d '\r\n' )" ;; + LAME) lameVersion="$( lame --version 2>/dev/null | grep -E 'LAME.*version' | cut -d 'v' -f 2 | cut -d '(' -f 1 | cut -d ' ' -f 2- )" ; lameVersion="${lameVersion% *}" ;; + WinLAME) winlameVersion="$( runWineExe lame --version 2>/dev/null | grep -E 'LAME.*version' | cut -d 'v' -f 2 | cut -d '(' -f 1 | cut -d ' ' -f 2- )" ; winlameVersion="${winlameVersion% *}" ;; + Musepack) mpcVersion="$( mpcenc 2>&1 | grep -F 'MPC Encoder' | cut -d ' ' -f 3 )" ;; + Opus) opusVersion="$( opusenc --version 2>/dev/null | grep -F 'opusenc' )" ;; + + WavPack|WavPackHybrid|WavPackLossy) + if gotNewWavpack; then + wavpackVersion="$( wavpack --version 2>/dev/null | head -n 1 )" + else + wavpackVersion="$( wavpack --help 2>&1 | grep -F 'Version' )" + fi + wavpackVersion="${wavpackVersion##* }" + ;; + + lossy*) + lossywavVersion="$( runWineExe lossyWAV -v 2>/dev/null | grep -F 'lossyWAV' | tr -d '\r\n' )" # recent lossyWAV + if [ -z "$lossywavVersion" ]; then + lossywavVersion="$( runWineExe lossyWAV -v 2>&1 | grep -F 'lossyWAV' | tr -d '\r\n' )" # old lossyWAV + fi + lossywavVersion="${lossywavVersion##* }" + ;; + + TAK) + if gotNewTAK; then + takVersion="$( runWineExe Takc -version 2>/dev/null | head -n 1 | tr -d '\r\n' )" + takVersion="${takVersion##* }" + else + takVersion="$( runWineExe Takc 2>/dev/null | grep -F 'TAKC V' | cut -d 'V' -f 2- | tr -d '\r\n' )" + fi + ;; OggVorbis) - oggencVersion="$( oggenc -V | tr -cd '0-9\.' )" + oggencVersion="$( oggenc -V | tr -cd '0-9.' )" oggencPath="$( which oggenc )" vorbisPrefix="${oggencPath%/oggenc}"; vorbisPrefix="${vorbisPrefix%/bin}" if [ -f "${vorbisPrefix}/lib/pkgconfig/vorbisenc.pc" ]; then - libvorbisVersion="$( grep -i 'version' "${vorbisPrefix}/lib/pkgconfig/vorbisenc.pc" | tr -cd '0-9\.' )" + libvorbisVersion="$( grep -i 'version' "${vorbisPrefix}/lib/pkgconfig/vorbisenc.pc" | tr -cd '0-9.' )" oggencVersion="${oggencVersion}, libvorbis $libvorbisVersion" fi ;; @@ -3033,310 +4847,426 @@ done } -encode () +isFileNewer () { - local ec=$EX_OK pattern="x${outputCodec}Y" kfm='' + local a="$1" b="$2" mtimeA mtimeB - compressionTag='' + if [ "$a" -nt "$b" ]; then + return $EX_OK + else + if [ "$OS" = 'Linux' ]; then + mtimeA="$( stat -c '%Y' "$a" )" + mtimeB="$( stat -c '%Y' "$b" )" + else + mtimeA="$( stat -f '%m' "$a" )" + mtimeB="$( stat -f '%m' "$b" )" + fi + if [ "$mtimeA" = "$mtimeB" ]; then + return $EX_OK + else + return $EX_KO + fi + fi +} - case "$outputCodec" in - WAV) - if [ $sourceIsLossyWAV = true ]; then - destExtension='lossy.wav'; encodedFile="$lossywavFile" - else - destExtension='wav'; encodedFile="$wavFile" +overwriteDestFile () +{ + local overwrite=true + + getFileProps "$1" "$2" + if [ -e "$destFile" ]; then + if [ $keepExistingFiles = true ]; then + overwrite=false + elif [ $keepNewerFiles = true ]; then + if isFileNewer "$destFile" "$sourceFile"; then + overwrite=false fi - ;; + fi + fi - FLAC) - destExtension='flac'; encodedFile="${SWAPDIR}/${i}.${destExtension}" destTagFile="${TDIR}/${i}.vc.txt" kfm='' - if [ $keepWavMetadata = true ]; then kfm='--keep-foreign-metadata' ; fi - if [ $tagCompressionSetting = true ]; then compressionTag="ENCODING=FLAC $flacVersion / -$compression_FLAC"; fi - printf -- "-s`tagline -T`\x00%s" "$wavFile" | xargs -0 flac -P 4096 $kfm -${compression_FLAC} -o "$encodedFile" >/dev/null 2>> "$errorLogFile" && - importArtworkIntoFLAC || ec=$EX_KO - ;; + if [ $overwrite = true ]; then + return $EX_OK + else + return $EX_KO + fi +} - Flake) - destExtension='flac'; encodedFile="${SWAPDIR}/${i}.${destExtension}" destTagFile="${TDIR}/${i}.vc.txt" - if [ $tagCompressionSetting = true ]; then compressionTag="ENCODING=Flake $flakeVersion / -$compression_Flake"; fi - flake -q -p 4096 -${compression_Flake} "$wavFile" -o "$encodedFile" >/dev/null 2>> "$errorLogFile" || ec=$EX_KO - if [ $ec -eq $EX_OK ]; then - if [ -f "$destTagFile" ]; then - printf -- "--no-utf8-convert`tagline --set-tag`\x00%s" "$encodedFile" | xargs -0 metaflac >/dev/null 2>> "$errorLogFile" || ec=$EX_KO +encode () +{ + local ec=$EX_OK pattern="x${outputCodec}Y" kfm='' statusInfo='' nRunningProcesses nTakProcesses + + compressionTag='' + + if overwriteDestFile "$sourceFile" "$outputCodec"; then + case "$outputCodec" in + WAV) + if [ $sourceIsLossyWAV = true ]; then + destExtension='lossy.wav'; encodedFile="$lossywavFile" + else + destExtension='wav'; encodedFile="$wavFile" fi - test $ec -eq $EX_OK && importArtworkIntoFLAC || ec=$EX_KO - fi - ;; + ;; - WavPack) - destExtension='wv'; encodedFile="${SWAPDIR}/${i}.${destExtension}" destTagFile="${TDIR}/${i}.ape.txt" kfm='-r' - if [ $keepWavMetadata = true ]; then kfm=''; fi - if [ -z "$compression_WavPack" -o "$compression_WavPack" = 'd' -o "$compression_WavPack" = 'default' ]; then - if [ $tagCompressionSetting = true ]; then compressionTag="Encoding=WavPack $wavpackVersion / default"; fi - printf -- "-q`tagline -w`\x00%s" "$wavFile" | xargs -0 wavpack -m $kfm -o "$encodedFile" >/dev/null 2>> "$errorLogFile" || ec=$EX_KO - else - if [ $tagCompressionSetting = true ]; then compressionTag="Encoding=WavPack $wavpackVersion / -$compression_WavPack"; fi - printf -- "-q`tagline -w`\x00%s" "$wavFile" | xargs -0 wavpack -m $kfm -o "$encodedFile" -${compression_WavPack} >/dev/null 2>> "$errorLogFile" || ec=$EX_KO - fi - ;; + AIFF) + destExtension='aiff'; encodedFile="${SWAPDIR}/${i}-${outputCodec}.${destExtension}" + sox "$wavFile" "$encodedFile" >/dev/null 2>> "$errorLogFile" || ec=$EX_KO + ;; - WavPackHybrid) - destExtension='wv'; encodedFile="${SWAPDIR}/${i}.${destExtension}" destTagFile="${TDIR}/${i}.ape.txt" kfm='-r' - if [ $keepWavMetadata = true ]; then kfm=''; fi - if [ -z "$compression_WavPack" -o "$compression_WavPack" = 'd' -o "$compression_WavPack" = 'default' ]; then - if [ $tagCompressionSetting = true ]; then - if [ "${bitrate_WavPackLossy%.*}" -lt 24 ]; then - compressionTag="Encoding=WavPack $wavpackVersion / default / $bitrate_WavPackLossy bits per sample (ABR)" - else - compressionTag="Encoding=WavPack $wavpackVersion / default / $bitrate_WavPackLossy kbps (ABR)" + CAF) + destExtension='caf'; encodedFile="${SWAPDIR}/${i}-${outputCodec}.${destExtension}" + sox "$wavFile" "$encodedFile" >/dev/null 2>> "$errorLogFile" || ec=$EX_KO + ;; + + FLAC) + destExtension='flac'; encodedFile="${SWAPDIR}/${i}-${outputCodec}.${destExtension}" destTagFile="${TDIR}/${i}.vc.txt" kfm='' + # Kohlrabi is a very cool dude + if [ $keepWavMetadata = true ]; then kfm='--keep-foreign-metadata' ; fi + if [ $tagCompressionSetting = true ]; then compressionTag="ENCODING=FLAC $flacVersion / -$compression_FLAC"; fi + printf -- "-s`tagline -T`\x00%s" "$wavFile" | xargs -0 flac -P 4096 $kfm -${compression_FLAC} -o "$encodedFile" >/dev/null 2>> "$errorLogFile" && + importArtworkIntoFLAC || ec=$EX_KO + ;; + + Flake) + destExtension='flac'; encodedFile="${SWAPDIR}/${i}-${outputCodec}.${destExtension}" destTagFile="${TDIR}/${i}.vc.txt" + if [ $tagCompressionSetting = true ]; then compressionTag="ENCODING=Flake $flakeVersion / -$compression_Flake"; fi + flake -q -p 4096 -${compression_Flake} "$wavFile" -o "$encodedFile" >/dev/null 2>> "$errorLogFile" || ec=$EX_KO + if [ $ec -eq $EX_OK ]; then + if [ -f "$destTagFile" ]; then + printf -- "--no-utf8-convert`tagline --set-tag`\x00%s" "$encodedFile" | xargs -0 metaflac >/dev/null 2>> "$errorLogFile" || ec=$EX_KO fi - fi - printf -- "-q`tagline -w`\x00%s" "$wavFile" | xargs -0 wavpack -m $kfm -o "$encodedFile" -b${bitrate_WavPackLossy} -c >/dev/null 2>> "$errorLogFile" || ec=$EX_KO - else - if [ $tagCompressionSetting = true ]; then - if [ "${bitrate_WavPackLossy%.*}" -lt 24 ]; then - compressionTag="Encoding=WavPack $wavpackVersion / -$compression_WavPack / $bitrate_WavPackLossy bits per sample (ABR)" - else - compressionTag="Encoding=WavPack $wavpackVersion / -$compression_WavPack / $bitrate_WavPackLossy kbps (ABR)" + if [ $ec -eq $EX_OK ]; then + importArtworkIntoFLAC || ec=$EX_KO fi fi - printf -- "-q`tagline -w`\x00%s" "$wavFile" | xargs -0 wavpack -m $kfm -o "$encodedFile" -b${bitrate_WavPackLossy} -c${compression_WavPack} >/dev/null 2>> "$errorLogFile" || ec=$EX_KO - fi - ;; + ;; - WavPackLossy) - destExtension='wv'; encodedFile="${SWAPDIR}/${i}.${destExtension}" destTagFile="${TDIR}/${i}.ape.txt" kfm='-r' - if [ $keepWavMetadata = true ]; then kfm=''; fi - if [ -z "$compression_WavPack" -o "$compression_WavPack" = 'd' -o "$compression_WavPack" = 'default' ]; then - if [ $tagCompressionSetting = true ]; then - if [ "${bitrate_WavPackLossy%.*}" -lt 24 ]; then - compressionTag="Encoding=WavPack $wavpackVersion / default / $bitrate_WavPackLossy bits per sample (ABR)" - else - compressionTag="Encoding=WavPack $wavpackVersion / default / $bitrate_WavPackLossy kbps (ABR)" + WavPack) + destExtension='wv'; encodedFile="${SWAPDIR}/${i}-${outputCodec}.${destExtension}" destTagFile="${TDIR}/${i}.ape.txt" kfm='-r' + if [ $keepWavMetadata = true ]; then kfm=''; fi + if [ -z "$compression_WavPack" -o "$compression_WavPack" = 'd' -o "$compression_WavPack" = 'default' ]; then + if [ $tagCompressionSetting = true ]; then compressionTag="Encoding=WavPack $wavpackVersion / default"; fi + wavpack -q -m $kfm -o "$encodedFile" "$wavFile" >/dev/null 2>> "$errorLogFile" || ec=$EX_KO + else + if [ $tagCompressionSetting = true ]; then compressionTag="Encoding=WavPack $wavpackVersion / -$compression_WavPack"; fi + wavpack -q -m $kfm -o "$encodedFile" -${compression_WavPack} "$wavFile" >/dev/null 2>> "$errorLogFile" || ec=$EX_KO + fi + if [ $ec -eq $EX_OK -a -f "$destTagFile" ]; then + printf -- "-R`tagline -t`\x00%s" "$encodedFile" | xargs -0 APEv2 -z >/dev/null 2>> "$errorLogFile" || ec=$EX_KO + importBinariesIntoAPEv2 || ec=$EX_KO + importArtworkIntoAPEv2 || ec=$EX_KO + fi + ;; + + WavPackHybrid) + destExtension='wv'; encodedFile="${SWAPDIR}/${i}-${outputCodec}.${destExtension}" destTagFile="${TDIR}/${i}.ape.txt" kfm='-r' + if [ $keepWavMetadata = true ]; then kfm=''; fi + if [ -z "$compression_WavPack" -o "$compression_WavPack" = 'd' -o "$compression_WavPack" = 'default' ]; then + if [ $tagCompressionSetting = true ]; then + if [ "${bitrate_WavPackLossy%.*}" -lt 24 ]; then + compressionTag="Encoding=WavPack $wavpackVersion / default / $bitrate_WavPackLossy bits per sample (ABR)" + else + compressionTag="Encoding=WavPack $wavpackVersion / default / $bitrate_WavPackLossy kbps (ABR)" + fi + fi + wavpack -q -m $kfm -o "$encodedFile" -b${bitrate_WavPackLossy} -c "$wavFile" >/dev/null 2>> "$errorLogFile" || ec=$EX_KO + else + if [ $tagCompressionSetting = true ]; then + if [ "${bitrate_WavPackLossy%.*}" -lt 24 ]; then + compressionTag="Encoding=WavPack $wavpackVersion / -$compression_WavPack / $bitrate_WavPackLossy bits per sample (ABR)" + else + compressionTag="Encoding=WavPack $wavpackVersion / -$compression_WavPack / $bitrate_WavPackLossy kbps (ABR)" + fi fi + wavpack -q -m $kfm -o "$encodedFile" -b${bitrate_WavPackLossy} -c${compression_WavPack} "$wavFile" >/dev/null 2>> "$errorLogFile" || ec=$EX_KO fi - printf -- "-q`tagline -w`\x00%s" "$wavFile" | xargs -0 wavpack $kfm -o "$encodedFile" -b${bitrate_WavPackLossy} >/dev/null 2>> "$errorLogFile" || ec=$EX_KO - else - if [ $tagCompressionSetting = true ]; then - if [ "${bitrate_WavPackLossy%.*}" -lt 24 ]; then - compressionTag="Encoding=WavPack $wavpackVersion / -$compression_WavPack / $bitrate_WavPackLossy bits per sample (ABR)" - else - compressionTag="Encoding=WavPack $wavpackVersion / -$compression_WavPack / $bitrate_WavPackLossy kbps (ABR)" + if [ $ec -eq $EX_OK -a -f "$destTagFile" ]; then + printf -- "-R`tagline -t`\x00%s" "$encodedFile" | xargs -0 APEv2 -z >/dev/null 2>> "$errorLogFile" || ec=$EX_KO + importBinariesIntoAPEv2 || ec=$EX_KO + importArtworkIntoAPEv2 || ec=$EX_KO + fi + ;; + + WavPackLossy) + destExtension='wv'; encodedFile="${SWAPDIR}/${i}-${outputCodec}.${destExtension}" destTagFile="${TDIR}/${i}.ape.txt" kfm='-r' + if [ $keepWavMetadata = true ]; then kfm=''; fi + if [ -z "$compression_WavPack" -o "$compression_WavPack" = 'd' -o "$compression_WavPack" = 'default' ]; then + if [ $tagCompressionSetting = true ]; then + if [ "${bitrate_WavPackLossy%.*}" -lt 24 ]; then + compressionTag="Encoding=WavPack $wavpackVersion / default / $bitrate_WavPackLossy bits per sample (ABR)" + else + compressionTag="Encoding=WavPack $wavpackVersion / default / $bitrate_WavPackLossy kbps (ABR)" + fi + fi + wavpack -q $kfm -o "$encodedFile" -b${bitrate_WavPackLossy} "$wavFile" >/dev/null 2>> "$errorLogFile" || ec=$EX_KO + else + if [ $tagCompressionSetting = true ]; then + if [ "${bitrate_WavPackLossy%.*}" -lt 24 ]; then + compressionTag="Encoding=WavPack $wavpackVersion / -$compression_WavPack / $bitrate_WavPackLossy bits per sample (ABR)" + else + compressionTag="Encoding=WavPack $wavpackVersion / -$compression_WavPack / $bitrate_WavPackLossy kbps (ABR)" + fi fi + wavpack -q $kfm -o "$encodedFile" -b${bitrate_WavPackLossy} -${compression_WavPack} "$wavFile" >/dev/null 2>> "$errorLogFile" || ec=$EX_KO fi - printf -- "-q`tagline -w`\x00%s" "$wavFile" | xargs -0 wavpack $kfm -o "$encodedFile" -b${bitrate_WavPackLossy} -${compression_WavPack} >/dev/null 2>> "$errorLogFile" || ec=$EX_KO - fi - ;; + if [ $ec -eq $EX_OK -a -f "$destTagFile" ]; then + printf -- "-R`tagline -t`\x00%s" "$encodedFile" | xargs -0 APEv2 -z >/dev/null 2>> "$errorLogFile" || ec=$EX_KO + importBinariesIntoAPEv2 || ec=$EX_KO + importArtworkIntoAPEv2 || ec=$EX_KO + fi + ;; - MonkeysAudio) - destExtension='ape'; encodedFile="${SWAPDIR}/${i}.${destExtension}" destTagFile="${TDIR}/${i}.ape.txt" - mac "$wavFile" "$encodedFile" -c${compression_MonkeysAudio}000 >/dev/null 2>> "$errorLogFile" || ec=$EX_KO - if [ $ec -eq $EX_OK -a -f "$destTagFile" ]; then - if [ $tagCompressionSetting = true ]; then compressionTag="Encoding=Monkey's Audio $apeVersion / -c${compression_MonkeysAudio}000"; fi - printf -- "-m\x00overwrite`tagline -p`" | xargs -0 apetag -i "$encodedFile" >/dev/null 2>> "$errorLogFile" || ec=$EX_KO - fi - ;; + MonkeysAudio) + destExtension='ape'; encodedFile="${SWAPDIR}/${i}-${outputCodec}.${destExtension}" destTagFile="${TDIR}/${i}.ape.txt" + mac "$wavFile" "$encodedFile" -c${compression_MonkeysAudio}000 >/dev/null 2>> "$errorLogFile" || ec=$EX_KO + if [ $ec -eq $EX_OK -a -f "$destTagFile" ]; then + if [ $tagCompressionSetting = true ]; then compressionTag="Encoding=Monkey's Audio $apeVersion / -c${compression_MonkeysAudio}000"; fi + printf -- "-R`tagline -t`\x00%s" "$encodedFile" | xargs -0 APEv2 -z >/dev/null 2>> "$errorLogFile" || ec=$EX_KO + importBinariesIntoAPEv2 || ec=$EX_KO + importArtworkIntoAPEv2 || ec=$EX_KO + fi + ;; - TAK) - destExtension='tak'; encodedFile="${SWAPDIR}/${i}.${destExtension}" destTagFile="${TDIR}/${i}.ape.txt" - cd "$SWAPDIR" - if [ $tagCompressionSetting = true ]; then compressionTag="Encoding=TAK $takVersion / -p$compression_TAK"; fi - printf -- "-md5`tagline -tt`\x00%s\x00%s" ".\\${i}.wav" ".\\${i}.${destExtension}" | WINEPREFIX="$takWinePrefix" xargs -0 $takWineExe takc -e -p${compression_TAK} -tn1 >/dev/null 2>> "$errorLogFile" || ec=$EX_KO - cd "$OLDPWD" - ;; + TAK) + destExtension='tak'; encodedFilename="${i}-${outputCodec}.${destExtension}"; encodedFile="${SWAPDIR}/${encodedFilename}"; destTagFile="${TDIR}/${i}.ape.txt" + cd "$SWAPDIR" + nTakProcesses="$( getNumberOfTakProcesses )" + runWineExe Takc -e -p${compression_TAK} -tn${nTakProcesses} -md5 ".\\${i}.wav" ".\\${encodedFilename}" >/dev/null 2>> "$errorLogFile" || ec=$EX_KO + cd "$OLDPWD" + if [ $ec -eq $EX_OK -a -f "$destTagFile" ]; then + if [ $tagCompressionSetting = true ]; then compressionTag="Encoding=TAK $takVersion / -p$compression_TAK"; fi + printf -- "-R`tagline -t`\x00%s" "$encodedFile" | xargs -0 APEv2 -z >/dev/null 2>> "$errorLogFile" || ec=$EX_KO + importBinariesIntoAPEv2 || ec=$EX_KO + importArtworkIntoAPEv2 || ec=$EX_KO + fi + ;; - ALAC) - destExtension='m4a'; encodedFile="${SWAPDIR}/${i}.${destExtension}" destTagFile="${TDIR}/${i}.m4a.txt" - # nohup is needed because ffmpeg messes with the terminal somehow - nohup ffmpeg -v quiet -i "$wavFile" -acodec alac "$encodedFile" > "${TDIR}/${i}.out" 2>> "$errorLogFile" && - rm -f "${TDIR}/${i}.out" >/dev/null 2>&1 || ec=$EX_KO - if [ $ec -eq $EX_OK ]; then - if [ -f "$destTagFile" ]; then - printf -- "%s`tagline`" "$encodedFile" | xargs -0 neroAacTag >/dev/null 2>> "$errorLogFile" || ec=$EX_KO + ALAC) + destExtension='m4a'; encodedFile="${SWAPDIR}/${i}-${outputCodec}.${destExtension}" destTagFile="${TDIR}/${i}.m4a.txt" + # nohup is needed because ffmpeg messes with the terminal somehow + nohup ffmpeg -v quiet -i "$wavFile" -acodec alac "$encodedFile" > "${TDIR}/${i}.nohup" 2>> "$errorLogFile" ; ec=$? + if [ -e "${TDIR}/${i}.nohup" ]; then rm -f "${TDIR}/${i}.nohup" >/dev/null 2>&1 ; fi + if [ $ec -eq $EX_OK ]; then + if [ -f "$destTagFile" ]; then + printf -- "%s`tagline`" "$encodedFile" | xargs -0 neroAacTag >/dev/null 2>> "$errorLogFile" || ec=$EX_KO + fi + if [ $ec -eq $EX_OK ]; then + importArtworkIntoM4A || ec=$EX_KO + fi fi - test $ec -eq $EX_OK && importArtworkIntoM4A || ec=$EX_KO - fi - ;; + ;; - lossyFLAC) - destExtension='lossy.flac'; encodedFile="${SWAPDIR}/${i}.${destExtension}" destTagFile="${TDIR}/${i}.vc.txt" - if [ $tagCompressionSetting = true ]; then compressionTag="ENCODING=lossyWAV $lossywavVersion / -q $compression_lossyWAV"; fi - printf -- "--totally-silent`tagline -T`\x00%s" "$lossywavFile" | xargs -0 flac -P 4096 -5 -b 512 --keep-foreign-metadata -o "$encodedFile" >/dev/null 2>> "$errorLogFile" && - importArtworkIntoFLAC || ec=$EX_KO - ;; + lossyFLAC) + destExtension='lossy.flac'; encodedFile="${SWAPDIR}/${i}-${outputCodec}.${destExtension}" destTagFile="${TDIR}/${i}.vc.txt" + if [ $tagCompressionSetting = true ]; then compressionTag="ENCODING=lossyWAV $lossywavVersion / -q $compression_lossyWAV"; fi + printf -- "--totally-silent`tagline -T`\x00%s" "$lossywavFile" | xargs -0 flac -P 4096 -5 -b 512 --keep-foreign-metadata -o "$encodedFile" >/dev/null 2>> "$errorLogFile" && + importArtworkIntoFLAC || ec=$EX_KO + ;; - lossyWV) - destExtension='lossy.wv'; encodedFile="${SWAPDIR}/${i}.${destExtension}" destTagFile="${TDIR}/${i}.ape.txt" - if [ $tagCompressionSetting = true ]; then compressionTag="Encoding=lossyWAV $lossywavVersion / -q $compression_lossyWAV"; fi - printf -- "-q`tagline -w`\x00%s" "$lossywavFile" | xargs -0 wavpack -m -o "$encodedFile" --blocksize=512 >/dev/null 2>> "$errorLogFile" || ec=$EX_KO - ;; + lossyWV) + destExtension='lossy.wv'; encodedFile="${SWAPDIR}/${i}-${outputCodec}.${destExtension}" destTagFile="${TDIR}/${i}.ape.txt" + wavpack -q -m -o "$encodedFile" --blocksize=512 --merge-blocks "$lossywavFile" >/dev/null 2>> "$errorLogFile" || ec=$EX_KO + if [ $ec -eq $EX_OK -a -f "$destTagFile" ]; then + if [ $tagCompressionSetting = true ]; then compressionTag="Encoding=lossyWAV $lossywavVersion / -q $compression_lossyWAV"; fi + printf -- "-R`tagline -t`\x00%s" "$encodedFile" | xargs -0 APEv2 -z >/dev/null 2>> "$errorLogFile" || ec=$EX_KO + importBinariesIntoAPEv2 || ec=$EX_KO + importArtworkIntoAPEv2 || ec=$EX_KO + fi + ;; - lossyTAK) - destExtension='lossy.tak'; encodedFile="${SWAPDIR}/${i}.${destExtension}" destTagFile="${TDIR}/${i}.ape.txt" - cd "$SWAPDIR" - if [ $tagCompressionSetting = true ]; then compressionTag="Encoding=lossyWAV $lossywavVersion / -q $compression_lossyWAV"; fi - printf -- "-md5`tagline -tt`\x00%s\x00%s" ".\\${i}.lossy.wav" ".\\${i}.${destExtension}" | WINEPREFIX="$takWinePrefix" xargs -0 $takWineExe takc -e -p2 -tn1 -fsl512 >/dev/null 2>> "$errorLogFile" || ec=$EX_KO - cd "$OLDPWD" - ;; + lossyTAK) + destExtension='lossy.tak'; encodedFilename="${i}-${outputCodec}.${destExtension}"; encodedFile="${SWAPDIR}/${encodedFilename}"; destTagFile="${TDIR}/${i}.ape.txt" + cd "$SWAPDIR" + nTakProcesses="$( getNumberOfTakProcesses )" + runWineExe Takc -e -p2 -tn${nTakProcesses} -fsl512 -md5 ".\\${i}.lossy.wav" ".\\${encodedFilename}" >/dev/null 2>> "$errorLogFile" || ec=$EX_KO + cd "$OLDPWD" + if [ $ec -eq $EX_OK -a -f "$destTagFile" ]; then + if [ $tagCompressionSetting = true ]; then compressionTag="Encoding=lossyWAV $lossywavVersion / -q $compression_lossyWAV"; fi + printf -- "-R`tagline -t`\x00%s" "$encodedFile" | xargs -0 APEv2 -z >/dev/null 2>> "$errorLogFile" || ec=$EX_KO + importBinariesIntoAPEv2 || ec=$EX_KO + importArtworkIntoAPEv2 || ec=$EX_KO + fi + ;; - OggVorbis) - destExtension='ogg'; encodedFile="${SWAPDIR}/${i}.${destExtension}" destTagFile="${TDIR}/${i}.vc.txt" - if [ "$OggVorbis_MODE" = 'VBR' ]; then - if [ $tagCompressionSetting = true ]; then compressionTag="ENCODING=oggenc $oggencVersion / -q $compression_OggVorbis"; fi - printf -- "-Q`tagline -c`\x00%s" "$wavFile" | xargs -0 oggenc -q $compression_OggVorbis -o "$encodedFile" >/dev/null 2>> "$errorLogFile" || ec=$EX_KO - elif [ "$OggVorbis_MODE" = 'ABR' ]; then - if [ $tagCompressionSetting = true ]; then compressionTag="ENCODING=oggenc $oggencVersion / $average_bitrate_OggVorbis kbps (ABR)"; fi - printf -- "-Q`tagline -c`\x00%s" "$wavFile" | xargs -0 oggenc --managed -b $average_bitrate_OggVorbis -o "$encodedFile" >/dev/null 2>> "$errorLogFile" || ec=$EX_KO - else - if [ $tagCompressionSetting = true ]; then compressionTag="ENCODING=oggenc $oggencVersion / $bitrate_OggVorbis kbps (CBR)"; fi - printf -- "-Q`tagline -c`\x00%s" "$wavFile" | xargs -0 oggenc --managed -b $bitrate_OggVorbis -m $bitrate_OggVorbis -M $bitrate_OggVorbis -o "$encodedFile" >/dev/null 2>> "$errorLogFile" || ec=$EX_KO - fi - ;; + OggVorbis) + destExtension='ogg'; encodedFile="${SWAPDIR}/${i}-${outputCodec}.${destExtension}" destTagFile="${TDIR}/${i}.vc.txt" + if [ "$OggVorbis_MODE" = 'VBR' ]; then + if [ $tagCompressionSetting = true ]; then compressionTag="ENCODING=oggenc $oggencVersion / -q $compression_OggVorbis"; fi + printf -- "-Q`tagline -c`\x00%s" "$wavFile" | xargs -0 oggenc -q $compression_OggVorbis -o "$encodedFile" >/dev/null 2>> "$errorLogFile" || ec=$EX_KO + elif [ "$OggVorbis_MODE" = 'ABR' ]; then + if [ $tagCompressionSetting = true ]; then compressionTag="ENCODING=oggenc $oggencVersion / $average_bitrate_OggVorbis kbps (ABR)"; fi + printf -- "-Q`tagline -c`\x00%s" "$wavFile" | xargs -0 oggenc --managed -b $average_bitrate_OggVorbis -o "$encodedFile" >/dev/null 2>> "$errorLogFile" || ec=$EX_KO + else + if [ $tagCompressionSetting = true ]; then compressionTag="ENCODING=oggenc $oggencVersion / $bitrate_OggVorbis kbps (CBR)"; fi + printf -- "-Q`tagline -c`\x00%s" "$wavFile" | xargs -0 oggenc --managed -b $bitrate_OggVorbis -m $bitrate_OggVorbis -M $bitrate_OggVorbis -o "$encodedFile" >/dev/null 2>> "$errorLogFile" || ec=$EX_KO + fi + ;; - WinVorbis) - destExtension='ogg'; encodedFile="${SWAPDIR}/${i}.${destExtension}" destTagFile="${TDIR}/${i}.vc.txt" - cd "$SWAPDIR" - if [ "$OggVorbis_MODE" = 'VBR' ]; then - if [ $tagCompressionSetting = true ]; then compressionTag="ENCODING=$winoggencVersion / -q $compression_OggVorbis"; fi - printf -- "-Q`tagline -c`\x00%s" "${i}.wav" | WINEPREFIX="$oggencWinePrefix" xargs -0 $oggencWineExe oggenc2 -q $compression_OggVorbis -o "${i}.${destExtension}" >/dev/null 2>> "$errorLogFile" || ec=$EX_KO - elif [ "$OggVorbis_MODE" = 'ABR' ]; then - if [ $tagCompressionSetting = true ]; then compressionTag="ENCODING=$winoggencVersion / $average_bitrate_OggVorbis kbps (ABR)"; fi - printf -- "-Q`tagline -c`\x00%s" "${i}.wav" | WINEPREFIX="$oggencWinePrefix" xargs -0 $oggencWineExe oggenc2 --managed -b $average_bitrate_OggVorbis -o "${i}.${destExtension}" >/dev/null 2>> "$errorLogFile" || ec=$EX_KO - else - if [ $tagCompressionSetting = true ]; then compressionTag="ENCODING=$winoggencVersion / $bitrate_OggVorbis kbps (CBR)"; fi - printf -- "-Q`tagline -c`\x00%s" "${i}.wav" | WINEPREFIX="$oggencWinePrefix" xargs -0 $oggencWineExe oggenc2 --managed -b $bitrate_OggVorbis -m $bitrate_OggVorbis -M $bitrate_OggVorbis -o "${i}.${destExtension}" >/dev/null 2>> "$errorLogFile" || ec=$EX_KO - fi - cd "$OLDPWD" - ;; + WinVorbis) + destExtension='ogg'; encodedFilename="${i}-${outputCodec}.${destExtension}"; encodedFile="${SWAPDIR}/${encodedFilename}"; destTagFile="${TDIR}/${i}.vc.txt" + cd "$SWAPDIR" + setWineParams 'oggenc2' + if [ "$OggVorbis_MODE" = 'VBR' ]; then + if [ $tagCompressionSetting = true ]; then compressionTag="ENCODING=$winoggencVersion / -q $compression_OggVorbis"; fi + printf -- "-Q`tagline -c`\x00%s" "${i}.wav" | WINEPREFIX="$winePath" xargs -0 $wineExe oggenc2 -q $compression_OggVorbis -o "$encodedFilename" >/dev/null 2>> "$errorLogFile" || ec=$EX_KO + elif [ "$OggVorbis_MODE" = 'ABR' ]; then + if [ $tagCompressionSetting = true ]; then compressionTag="ENCODING=$winoggencVersion / $average_bitrate_OggVorbis kbps (ABR)"; fi + printf -- "-Q`tagline -c`\x00%s" "${i}.wav" | WINEPREFIX="$winePath" xargs -0 $wineExe oggenc2 --managed -b $average_bitrate_OggVorbis -o "$encodedFilename" >/dev/null 2>> "$errorLogFile" || ec=$EX_KO + else + if [ $tagCompressionSetting = true ]; then compressionTag="ENCODING=$winoggencVersion / $bitrate_OggVorbis kbps (CBR)"; fi + printf -- "-Q`tagline -c`\x00%s" "${i}.wav" | WINEPREFIX="$winePath" xargs -0 $wineExe oggenc2 --managed -b $bitrate_OggVorbis -m $bitrate_OggVorbis -M $bitrate_OggVorbis -o "$encodedFilename" >/dev/null 2>> "$errorLogFile" || ec=$EX_KO + fi + setWineParams '' + cd "$OLDPWD" + ;; - LAME) - destExtension='mp3'; encodedFile="${SWAPDIR}/${i}.${destExtension}" destTagFile="${TDIR}/${i}.lame.txt" - if [ "$compression_LAME" = 'insane' -o "$compression_LAME" = '320' ]; then - LAME_MODE='CBR' bitrate_LAME=320 - fi - if [ "$LAME_MODE" = 'VBR' ]; then - if [ $tagCompressionSetting = true ]; then compressionTag="--tv TSSE=LAME $lameVersion / -V $compression_LAME"; fi - printf -- "-o`tagline`\x00%s\x00%s" "$wavFile" "$encodedFile" | xargs -0 lame -S -V $compression_LAME --noreplaygain --id3v2-only --ignore-tag-errors >/dev/null 2>> "$errorLogFile" || ec=$EX_KO - elif [ "$LAME_MODE" = 'ABR' ]; then - if [ $tagCompressionSetting = true ]; then compressionTag="--tv TSSE=LAME $lameVersion / $average_bitrate_LAME kbps (ABR)"; fi - printf -- "-o`tagline`\x00%s\x00%s" "$wavFile" "$encodedFile" | xargs -0 lame -S --abr $average_bitrate_LAME --noreplaygain --id3v2-only --ignore-tag-errors >/dev/null 2>> "$errorLogFile" || ec=$EX_KO - else - if [ $tagCompressionSetting = true ]; then compressionTag="--tv TSSE=LAME $lameVersion / $bitrate_LAME kbps (CBR)"; fi - if [ $bitrate_LAME -eq 320 ]; then - printf -- "-o`tagline`\x00%s\x00%s" "$wavFile" "$encodedFile" | xargs -0 lame -S --preset insane --noreplaygain --id3v2-only --ignore-tag-errors >/dev/null 2>> "$errorLogFile" || ec=$EX_KO + LAME) + destExtension='mp3'; encodedFile="${SWAPDIR}/${i}-${outputCodec}.${destExtension}" destTagFile="${TDIR}/${i}.lame.txt" + if [ "$compression_LAME" = 'insane' -o "$compression_LAME" = '320' ]; then + LAME_MODE='CBR' bitrate_LAME=320 + fi + if [ "$LAME_MODE" = 'VBR' ]; then + if [ $tagCompressionSetting = true ]; then compressionTag="--tv TSSE=LAME $lameVersion / -V $compression_LAME"; fi + printf -- "-o`tagline`\x00%s\x00%s" "$wavFile" "$encodedFile" | xargs -0 lame -S -V $compression_LAME --noreplaygain --id3v2-only --pad-id3v2-size $ID3Padding --ignore-tag-errors >/dev/null 2>> "$errorLogFile" || ec=$EX_KO + elif [ "$LAME_MODE" = 'ABR' ]; then + if [ $tagCompressionSetting = true ]; then compressionTag="--tv TSSE=LAME $lameVersion / $average_bitrate_LAME kbps (ABR)"; fi + printf -- "-o`tagline`\x00%s\x00%s" "$wavFile" "$encodedFile" | xargs -0 lame -S --abr $average_bitrate_LAME --noreplaygain --id3v2-only --pad-id3v2-size $ID3Padding --ignore-tag-errors >/dev/null 2>> "$errorLogFile" || ec=$EX_KO else - printf -- "-o`tagline`\x00%s\x00%s" "$wavFile" "$encodedFile" | xargs -0 lame -S -b $bitrate_LAME --cbr --noreplaygain --id3v2-only --ignore-tag-errors >/dev/null 2>> "$errorLogFile" || ec=$EX_KO + if [ $tagCompressionSetting = true ]; then compressionTag="--tv TSSE=LAME $lameVersion / $bitrate_LAME kbps (CBR)"; fi + if [ $bitrate_LAME -eq 320 ]; then + printf -- "-o`tagline`\x00%s\x00%s" "$wavFile" "$encodedFile" | xargs -0 lame -S --preset insane --noreplaygain --id3v2-only --pad-id3v2-size $ID3Padding --ignore-tag-errors >/dev/null 2>> "$errorLogFile" || ec=$EX_KO + else + printf -- "-o`tagline`\x00%s\x00%s" "$wavFile" "$encodedFile" | xargs -0 lame -S -b $bitrate_LAME --cbr --noreplaygain --id3v2-only --pad-id3v2-size $ID3Padding --ignore-tag-errors >/dev/null 2>> "$errorLogFile" || ec=$EX_KO + fi fi - fi - test $ec -eq $EX_OK && importArtworkIntoMP3 || ec=$EX_KO - ;; + if [ $ec -eq $EX_OK ]; then + importArtworkIntoMP3 || ec=$EX_KO + fi + ;; - WinLAME) - destExtension='mp3'; encodedFile="${SWAPDIR}/${i}.${destExtension}" destTagFile="${TDIR}/${i}.lame.txt" - cd "$SWAPDIR" - if [ "$LAME_MODE" = 'VBR' ]; then - if [ $tagCompressionSetting = true ]; then compressionTag="--tv TSSE=LAME $winlameVersion / -V $compression_LAME"; fi - printf -- "-o`tagline`\x00%s\x00%s" "${i}.wav" "${i}.${destExtension}" | WINEPREFIX="$lameWinePrefix" xargs -0 $lameWineExe lame -S -V $compression_LAME --noreplaygain --id3v2-only --ignore-tag-errors >/dev/null 2>> "$errorLogFile" || ec=$EX_KO - elif [ "$LAME_MODE" = 'ABR' ]; then - if [ $tagCompressionSetting = true ]; then compressionTag="--tv TSSE=LAME $winlameVersion / $average_bitrate_LAME kbps (ABR)"; fi - printf -- "-o`tagline`\x00%s\x00%s" "${i}.wav" "${i}.${destExtension}" | WINEPREFIX="$lameWinePrefix" xargs -0 $lameWineExe lame -S --abr $average_bitrate_LAME --noreplaygain --id3v2-only --ignore-tag-errors >/dev/null 2>> "$errorLogFile" || ec=$EX_KO - else - if [ $tagCompressionSetting = true ]; then compressionTag="--tv TSSE=LAME $winlameVersion / $bitrate_LAME kbps (CBR)"; fi - if [ $bitrate_LAME -eq 320 ]; then - printf -- "-o`tagline`\x00%s\x00%s" "${i}.wav" "${i}.${destExtension}" | WINEPREFIX="$lameWinePrefix" xargs -0 $lameWineExe lame -S --preset insane --noreplaygain --id3v2-only --ignore-tag-errors >/dev/null 2>> "$errorLogFile" || ec=$EX_KO + WinLAME) + destExtension='mp3'; encodedFilename="${i}-${outputCodec}.${destExtension}"; encodedFile="${SWAPDIR}/${encodedFilename}"; destTagFile="${TDIR}/${i}.lame.txt" + cd "$SWAPDIR" + setWineParams 'lame' + if [ "$LAME_MODE" = 'VBR' ]; then + if [ $tagCompressionSetting = true ]; then compressionTag="--tv TSSE=LAME $winlameVersion / -V $compression_LAME"; fi + printf -- "-o`tagline`\x00%s\x00%s" "${i}.wav" "$encodedFilename" | WINEPREFIX="$winePath" xargs -0 $wineExe lame -S -V $compression_LAME --noreplaygain --id3v2-only --pad-id3v2-size $ID3Padding --ignore-tag-errors >/dev/null 2>> "$errorLogFile" || ec=$EX_KO + elif [ "$LAME_MODE" = 'ABR' ]; then + if [ $tagCompressionSetting = true ]; then compressionTag="--tv TSSE=LAME $winlameVersion / $average_bitrate_LAME kbps (ABR)"; fi + printf -- "-o`tagline`\x00%s\x00%s" "${i}.wav" "$encodedFilename" | WINEPREFIX="$winePath" xargs -0 $wineExe lame -S --abr $average_bitrate_LAME --noreplaygain --id3v2-only --pad-id3v2-size $ID3Padding --ignore-tag-errors >/dev/null 2>> "$errorLogFile" || ec=$EX_KO else - printf -- "-o`tagline`\x00%s\x00%s" "${i}.wav" "${i}.${destExtension}" | WINEPREFIX="$lameWinePrefix" xargs -0 $lameWineExe lame -S -b $bitrate_LAME --cbr --noreplaygain --id3v2-only --ignore-tag-errors >/dev/null 2>> "$errorLogFile" || ec=$EX_KO + if [ $tagCompressionSetting = true ]; then compressionTag="--tv TSSE=LAME $winlameVersion / $bitrate_LAME kbps (CBR)"; fi + if [ $bitrate_LAME -eq 320 ]; then + printf -- "-o`tagline`\x00%s\x00%s" "${i}.wav" "$encodedFilename" | WINEPREFIX="$winePath" xargs -0 $wineExe lame -S --preset insane --noreplaygain --id3v2-only --pad-id3v2-size $ID3Padding --ignore-tag-errors >/dev/null 2>> "$errorLogFile" || ec=$EX_KO + else + printf -- "-o`tagline`\x00%s\x00%s" "${i}.wav" "$encodedFilename" | WINEPREFIX="$winePath" xargs -0 $wineExe lame -S -b $bitrate_LAME --cbr --noreplaygain --id3v2-only --pad-id3v2-size $ID3Padding --ignore-tag-errors >/dev/null 2>> "$errorLogFile" || ec=$EX_KO + fi fi - fi - cd "$OLDPWD" - test $ec -eq $EX_OK && importArtworkIntoMP3 || ec=$EX_KO - ;; + setWineParams '' + cd "$OLDPWD" + if [ $ec -eq $EX_OK ]; then + importArtworkIntoMP3 || ec=$EX_KO + fi + ;; - AAC) - destExtension='m4a'; encodedFile="${SWAPDIR}/${i}.${destExtension}" destTagFile="${TDIR}/${i}.m4a.txt" - if [ "$AAC_MODE" = 'VBR' ]; then - neroAacEnc -q $compression_AAC '-if' "$wavFile" -of "$encodedFile" >/dev/null 2>> "$errorLogFile" || ec=$EX_KO - elif [ "$AAC_MODE" = 'ABR' ]; then - neroAacEnc -br ${average_bitrate_AAC}000 '-if' "$wavFile" -of "$encodedFile" >/dev/null 2>> "$errorLogFile" || ec=$EX_KO - else - neroAacEnc -cbr ${bitrate_AAC}000 '-if' "$wavFile" -of "$encodedFile" >/dev/null 2>> "$errorLogFile" || ec=$EX_KO - fi - if [ $ec -eq $EX_OK ]; then - if [ -f "$destTagFile" ]; then - printf -- "%s`tagline`" "$encodedFile" | xargs -0 neroAacTag >/dev/null 2>> "$errorLogFile" || ec=$EX_KO + AAC) + destExtension='m4a'; encodedFile="${SWAPDIR}/${i}-${outputCodec}.${destExtension}" destTagFile="${TDIR}/${i}.m4a.txt" + if [ "$AAC_MODE" = 'VBR' ]; then + neroAacEnc -q $compression_AAC '-if' "$wavFile" -of "$encodedFile" >/dev/null 2>> "$errorLogFile" || ec=$EX_KO + elif [ "$AAC_MODE" = 'ABR' ]; then + neroAacEnc -br ${average_bitrate_AAC}000 '-if' "$wavFile" -of "$encodedFile" >/dev/null 2>> "$errorLogFile" || ec=$EX_KO + else + neroAacEnc -cbr ${bitrate_AAC}000 '-if' "$wavFile" -of "$encodedFile" >/dev/null 2>> "$errorLogFile" || ec=$EX_KO fi - test $ec -eq $EX_OK && importArtworkIntoM4A || ec=$EX_KO - fi - ;; + if [ $ec -eq $EX_OK ]; then + if [ -f "$destTagFile" ]; then + printf -- "%s`tagline`" "$encodedFile" | xargs -0 neroAacTag >/dev/null 2>> "$errorLogFile" || ec=$EX_KO + fi + if [ $ec -eq $EX_OK ]; then + importArtworkIntoM4A || ec=$EX_KO + fi + fi + ;; - QAAC) - destExtension='m4a'; encodedFile="${SWAPDIR}/${i}.${destExtension}" destTagFile="${TDIR}/${i}.m4a.txt" - cd "$SWAPDIR" - if [ "$compression_QAAC" = 'iTunes' ]; then - WINEPREFIX="$qaacWinePrefix" $qaacWineExe qaac -s --cvbr 256 -o "${i}.${destExtension}" "${i}.wav" >/dev/null 2>&1 || ec=$EX_KO - elif [ "$QAAC_MODE" = 'VBR' ]; then - WINEPREFIX="$qaacWinePrefix" $qaacWineExe qaac -s -V $compression_QAAC -o "${i}.${destExtension}" "${i}.wav" >/dev/null 2>&1 || ec=$EX_KO - elif [ "$QAAC_MODE" = 'ABR' ]; then - WINEPREFIX="$qaacWinePrefix" $qaacWineExe qaac -s -a $average_bitrate_QAAC -o "${i}.${destExtension}" "${i}.wav" >/dev/null 2>&1 || ec=$EX_KO - else - WINEPREFIX="$qaacWinePrefix" $qaacWineExe qaac -s -c $bitrate_QAAC -o "${i}.${destExtension}" "${i}.wav" >/dev/null 2>&1 || ec=$EX_KO - fi - if [ $ec -eq $EX_OK ]; then - if [ -f "$destTagFile" ]; then - printf -- "%s`tagline`" "${i}.${destExtension}" | xargs -0 neroAacTag >/dev/null 2>> "$errorLogFile" || ec=$EX_KO + QAAC) + destExtension='m4a'; encodedFilename="${i}-${outputCodec}.${destExtension}"; encodedFile="${SWAPDIR}/${encodedFilename}"; destTagFile="${TDIR}/${i}.m4a.txt" + cd "$SWAPDIR" + setWineParams 'qaac' + if [ "$compression_QAAC" = 'iTunes' ]; then + WINEPREFIX="$winePath" $wineExe qaac -s --cvbr 256 -o "$encodedFilename" "${i}.wav" >/dev/null 2>&1 || ec=$EX_KO + elif [ "$QAAC_MODE" = 'VBR' ]; then + WINEPREFIX="$winePath" $wineExe qaac -s -V $compression_QAAC -o "$encodedFilename" "${i}.wav" >/dev/null 2>&1 || ec=$EX_KO + elif [ "$QAAC_MODE" = 'ABR' ]; then + WINEPREFIX="$winePath" $wineExe qaac -s -a $average_bitrate_QAAC -o "$encodedFilename" "${i}.wav" >/dev/null 2>&1 || ec=$EX_KO + else + WINEPREFIX="$winePath" $wineExe qaac -s -c $bitrate_QAAC -o "$encodedFilename" "${i}.wav" >/dev/null 2>&1 || ec=$EX_KO fi - test $ec -eq $EX_OK && importArtworkIntoM4A || ec=$EX_KO - fi - cd "$OLDPWD" - ;; + if [ $ec -eq $EX_OK ]; then + if [ -f "$destTagFile" ]; then + printf -- "%s`tagline`" "$encodedFilename" | xargs -0 neroAacTag >/dev/null 2>> "$errorLogFile" || ec=$EX_KO + fi + if [ $ec -eq $EX_OK ]; then + importArtworkIntoM4A || ec=$EX_KO + fi + fi + setWineParams '' + cd "$OLDPWD" + ;; - Musepack) - destExtension='mpc'; encodedFile="${SWAPDIR}/${i}.${destExtension}" destTagFile="${TDIR}/${i}.ape.txt" - if [ $tagCompressionSetting = true ]; then compressionTag="Encoding=MPC Encoder $mpcVersion / --quality $compression_Musepack"; fi - printf -- "--silent`tagline --tag`\x00%s\x00%s" "$wavFile" "$encodedFile" | xargs -0 mpcenc --quality ${compression_Musepack} >/dev/null 2>> "$errorLogFile" || ec=$EX_KO - ;; + Musepack) + destExtension='mpc'; encodedFile="${SWAPDIR}/${i}-${outputCodec}.${destExtension}" destTagFile="${TDIR}/${i}.ape.txt" + mpcenc --quality ${compression_Musepack} --silent "$wavFile" "$encodedFile" >/dev/null 2>> "$errorLogFile" || ec=$EX_KO + if [ $ec -eq $EX_OK -a -f "$destTagFile" ]; then + if [ $tagCompressionSetting = true ]; then compressionTag="Encoding=MPC Encoder $mpcVersion / --quality $compression_Musepack"; fi + printf -- "-R`tagline -t`\x00%s" "$encodedFile" | xargs -0 APEv2 -z >/dev/null 2>> "$errorLogFile" || ec=$EX_KO + importBinariesIntoAPEv2 || ec=$EX_KO + importArtworkIntoAPEv2 || ec=$EX_KO + fi + ;; - Opus) - destExtension='opus'; encodedFile="${SWAPDIR}/${i}.${destExtension}" destTagFile="${TDIR}/${i}.vc.txt" - if [ "$Opus_MODE" = 'VBR' ]; then - if [ $tagCompressionSetting = true ]; then compressionTag="ENCODING=$opusVersion / $compression_Opus kbps (VBR)"; fi - printf -- "--quiet`tagline --comment`\x00%s\x00%s" "$wavFile" "$encodedFile" | xargs -0 opusenc --vbr --bitrate $compression_Opus >/dev/null 2>> "$errorLogFile" || ec=$EX_KO - elif [ "$Opus_MODE" = 'ABR' ]; then - if [ $tagCompressionSetting = true ]; then compressionTag="ENCODING=$opusVersion / $average_bitrate_Opus kbps (CVBR)"; fi - printf -- "--quiet`tagline --comment`\x00%s\x00%s" "$wavFile" "$encodedFile" | xargs -0 opusenc --cvbr --bitrate $average_bitrate_Opus >/dev/null 2>> "$errorLogFile" || ec=$EX_KO - else - if [ $tagCompressionSetting = true ]; then compressionTag="ENCODING=$opusVersion / $bitrate_Opus kbps (CBR)"; fi - printf -- "--quiet`tagline --comment`\x00%s\x00%s" "$wavFile" "$encodedFile" | xargs -0 opusenc --hard-cbr --bitrate $bitrate_Opus >/dev/null 2>> "$errorLogFile" || ec=$EX_KO + Opus) + destExtension='opus'; encodedFile="${SWAPDIR}/${i}-${outputCodec}.${destExtension}" destTagFile="${TDIR}/${i}.vc.txt" + if [ "$Opus_MODE" = 'VBR' ]; then + if [ $tagCompressionSetting = true ]; then compressionTag="ENCODING=$opusVersion / $compression_Opus kbps (VBR)"; fi + printf -- "--quiet`tagline --comment`\x00%s`genOpusArtworkCommandLine`\x00%s" "$wavFile" "$encodedFile" | xargs -0 opusenc --vbr --bitrate $compression_Opus >/dev/null 2>> "$errorLogFile" || ec=$EX_KO + elif [ "$Opus_MODE" = 'ABR' ]; then + if [ $tagCompressionSetting = true ]; then compressionTag="ENCODING=$opusVersion / $average_bitrate_Opus kbps (CVBR)"; fi + printf -- "--quiet`tagline --comment`\x00%s`genOpusArtworkCommandLine`\x00%s" "$wavFile" "$encodedFile" | xargs -0 opusenc --cvbr --bitrate $average_bitrate_Opus >/dev/null 2>> "$errorLogFile" || ec=$EX_KO + else + if [ $tagCompressionSetting = true ]; then compressionTag="ENCODING=$opusVersion / $bitrate_Opus kbps (CBR)"; fi + printf -- "--quiet`tagline --comment`\x00%s`genOpusArtworkCommandLine`\x00%s" "$wavFile" "$encodedFile" | xargs -0 opusenc --hard-cbr --bitrate $bitrate_Opus >/dev/null 2>> "$errorLogFile" || ec=$EX_KO + fi + ;; + esac + + if [ $copyPath = true ]; then + if ! mkdir -p "$destPath" >/dev/null 2>&1 ; then + ec=$EX_KO fi - ;; - esac + fi - destFilename="${sourceBasename}.${destExtension}" - if [ $copyPath = true ]; then - destPath="${destDir}/${sourceDirname#/}" - if ! mkdir -p "$destPath" >/dev/null 2>&1 ; then - ec=$EX_KO + if [ "$outputCodec" = 'WAV' ]; then + statusInfo='decoding' else - destFile="${destPath}/${destFilename}" + statusInfo='encoding' fi - else - destFile="${destDir}/${destFilename}" - fi - if [ $ec -eq $EX_OK ]; then - chmod 0644 "$encodedFile" - if [ -L "$encodedFile" -o "$outputCodec" = 'WAV' ]; then - cp "$encodedFile" "$destFile" >/dev/null 2>> "$errorLogFile" + if [ $ec -eq $EX_OK ]; then + if [ -e "$encodedFile" ]; then + chmod 0644 "$encodedFile" + if [ -L "$encodedFile" -o "$outputCodec" = 'WAV' ]; then + cp "$encodedFile" "$destFile" >/dev/null 2>> "$errorLogFile" + else + mv "$encodedFile" "$destFile" >/dev/null 2>> "$errorLogFile" + fi + touch "$destFile" >/dev/null 2>> "$errorLogFile" # make sure that the mtime value is current + test -e "${encodedFile}c" && mv "${encodedFile}c" "${destFile}c" >/dev/null 2>> "$errorLogFile" # WavPack Hybrid correction files + test -e "${destFile}c" && touch "${destFile}c" >/dev/null 2>> "$errorLogFile" # make sure that the mtime value is current + printMessage 'success' "$statusInfo" "file:${destFile}" $p + fi else - mv "$encodedFile" "$destFile" >/dev/null 2>> "$errorLogFile" - fi - test -e "${encodedFile}c" && mv "${encodedFile}c" "${destFile}c" >/dev/null 2>> "$errorLogFile" # WavPack Hybrid correction files - if [ $verbose = true ]; then - printf "${GR}%2u ${OK}OK ${CY}%s${NM}\n" $p "$destFilename" + rm -f "$encodedFile" >/dev/null 2>&1 # in case it exists + printMessage 'error' "$statusInfo" "file:${sourceFile}" $p fi - else - rm -f "$encodedFile" >/dev/null 2>&1 # in case it exists - printf "${GR}%2u ${KO}ER ${CY}%s${NM}\n" $p "$destFilename" 1>&2 fi return $ec @@ -3359,7 +5289,7 @@ prepareSource () { - local arg="$1" sourcePath copyDone=false ec=$EX_OK cptimer1 cptimer2 cpseconds copyLockFile n=0 + local arg="$1" sourcePath copyDone=false ec=$EX_OK cptimer1='' cptimer2='' cpseconds copyLockFile n=0 if [ $preloadSources = true -a -z "$arg" ]; then if [ "$OS" = 'Linux' ]; then @@ -3372,8 +5302,8 @@ touch "${instanceDir}/ioLockFiles/${copyLockFile##*/}" echo "$$" > "${copyLockFile}.lock" - if [ "$OS" = 'Linux' ]; then - cptimer1="$( date '+%s.%N' )" + if [ $gnudate = true ]; then + cptimer1="$( $datecmd '+%s.%N' )" fi cp "$sourceFile" "$copyFile" >/dev/null 2>> "$errorLogFile" || ec=$EX_KO @@ -3386,10 +5316,14 @@ rm -f "${instanceDir}/ioLockFiles/${copyLockFile##*/}" >/dev/null 2>&1 copyDone=true - if [ "$OS" = 'Linux' ]; then - cptimer2="$( date '+%s.%N' )" - cpseconds="$( printf 'scale=6; %.6f - %.6f\n' "$cptimer2" "$cptimer1" | bc )" - echo -n " + $cpseconds" >> "${TDIR}/readTimes" + if [ -n "$cptimer1" ]; then + if [ $gnudate = true ]; then + cptimer2="$( $datecmd '+%s.%N' )" + fi + if [ -n "$cptimer2" ]; then + cpseconds="$( printf 'scale=6; %.6f - %.6f\n' "$cptimer2" "$cptimer1" | bc )" + echo -n " + $cpseconds" >> "${TDIR}/readTimes" + fi fi else if [ $n -ge 100 ]; then @@ -3402,14 +5336,10 @@ fi done else - if [ "$OS" = 'Linux' ]; then - sourcePath="$( readlink -f "$sourceFile" 2>> "$errorLogFile" )" - else - if [ "${sourceFile:0:1}" = '/' ]; then - sourcePath="$sourceFile" - else - sourcePath="${PWD}/${sourceFile}" - fi + if [ "${sourceFile:0:1}" = '/' ]; then # sourceFile is an absolute path + sourcePath="$sourceFile" + else # sourceFile is a relative path; prepend current directory + sourcePath="${PWD}/${sourceFile}" fi ln -s "$sourcePath" "$copyFile" >/dev/null 2>> "$errorLogFile" || ec=$EX_KO if [ -e "${sourcePath}c" ]; then # WavPack correction file @@ -3423,6 +5353,11 @@ return $ec } +addTranscodingErrorFile () +{ + echo "$1" >> "${TDIR}/transcodingErrorFiles" +} + transcode () { local lastCodec="${outputCodecs##* }" allDone=false copyDone=false encodingDone=false ec=$EX_OK lsec=0 @@ -3430,17 +5365,7 @@ until test $allDone = true; do allDone=true for ((i=0; i<${#sourceFiles[@]}; i++)); do - sourceFile="${sourceFiles[$i]}" - sourceFilename="${sourceFile##*/}" - sourceDirname="$( dirname "$sourceFile" )" - sourceBasename="${sourceFilename%.*}" - if [ "$sourceBasename" != "${sourceBasename%.lossy}" ]; then - sourceIsLossyWAV=true - else - sourceIsLossyWAV=false - fi - sourceBasename="${sourceBasename%.lossy}"; - sourceExtension="${sourceFilename##*.}" + getFileProps "${sourceFiles[$i]}" copyFile="${SWAPDIR}/${i}.${sourceFilename}" transcodingLockFile="${TDIR}/${i}" @@ -3448,25 +5373,34 @@ wavFile="${SWAPDIR}/${i}.wav" resampledWavFile="${SWAPDIR}/${i}_resampled.wav" lossywavFile="${SWAPDIR}/${i}.lossy.wav" + wavProcessedStatusFile="${TDIR}/${i}.wavProcessed" sourceTagFile="${TDIR}/${i}.txt" + apeBinariesDir="${TDIR}/${i}.apeBinariesDir" trackGainFile="${TDIR}/${i}.trackgain" trackPeakFile="${TDIR}/${i}.trackpeak" secondsFile="${TDIR}/${i}.seconds" - sourceCRCFile="${TDIR}/${i}.sourceCRC" + sourceCRC32File="${TDIR}/${i}.sourceCRC32" sourceMD5File="${TDIR}/${i}.sourceMD5" sourceSHA1File="${TDIR}/${i}.sourceSHA1" - losslessCRCFile="${TDIR}/${i}.losslessCRC" + sourceSHA256File="${TDIR}/${i}.sourceSHA256" + sourceSHA512File="${TDIR}/${i}.sourceSHA512" + losslessCRC32File="${TDIR}/${i}.losslessCRC32" losslessMD5File="${TDIR}/${i}.losslessMD5" losslessSHA1File="${TDIR}/${i}.losslessSHA1" - lossywavCRCFile="${TDIR}/${i}.lossywavCRC" + losslessSHA256File="${TDIR}/${i}.losslessSHA256" + losslessSHA512File="${TDIR}/${i}.losslessSHA512" + lossywavCRC32File="${TDIR}/${i}.lossywavCRC32" lossywavMD5File="${TDIR}/${i}.lossywavMD5" lossywavSHA1File="${TDIR}/${i}.lossywavSHA1" + lossywavSHA256File="${TDIR}/${i}.lossywavSHA256" + lossywavSHA512File="${TDIR}/${i}.lossywavSHA512" if test -e "$transcodingLockFile" && mv "$transcodingLockFile" "$decodingLockFile" 2>/dev/null; then allDone=false prepareSource && decode || ec=$EX_KO - rm -f "$decodingLockFile" "$copyFile" "${copyFile}c" >/dev/null 2>&1 + if [ $ec -ne $EX_OK ]; then addTranscodingErrorFile "$sourceFile" ; fi + rm -f "$decodingLockFile" >/dev/null 2>&1 elif [ -e "$decodingLockFile" ]; then # can't do anything for this track yet, gotta wait for it to be decoded allDone=false continue # skip to next track @@ -3478,6 +5412,7 @@ if test -e "$lossywavLockFile" && mv "$lossywavLockFile" "$lossywavEncodingLockFile" 2>/dev/null ; then allDone=false encodeLossyWAV || ec=$EX_KO + if [ $ec -ne $EX_OK ]; then addTranscodingErrorFile "$sourceFile" ; fi rm -f "$lossywavEncodingLockFile" "${lossywavLockFile}_WAV_NEEDED" ls "${TDIR}/"*"_${i}_WAV_NEEDED" >/dev/null 2>&1 || rm -f "$wavFile" ls "${TDIR}/"*"_${i}_LOSSYWAV_NEEDED" >/dev/null 2>&1 || rm -f "$lossywavFile" @@ -3496,6 +5431,7 @@ if test -e "$encodingLockFile" && unlink "$encodingLockFile" 2>/dev/null; then allDone=false encode || ec=$EX_KO + if [ $ec -ne $EX_OK ]; then addTranscodingErrorFile "$sourceFile" ; fi rm -f "${encodingLockFile}_WAV_NEEDED" ls "${TDIR}/"*"_${i}_WAV_NEEDED" >/dev/null 2>&1 || rm -f "$wavFile" if [ "$outputCodec" = 'lossyFLAC' -o "$outputCodec" = 'lossyWV' -o "$outputCodec" = 'lossyTAK' ]; then @@ -3509,6 +5445,9 @@ if ! ls "${TDIR}/"*"_${i}_WAV_NEEDED" >/dev/null 2>&1 ; then ls "${SWAPDIR}/picture-${i}"* >/dev/null 2>&1 && rm -f "${SWAPDIR}/picture-${i}"* >/dev/null 2>&1 + if [ -e "$apeBinariesDir" ]; then + rm -rf "$apeBinariesDir" + fi fi fi done @@ -3519,45 +5458,336 @@ return $ec } +compareHashes () +{ + local hashvalA='' hashvalB='' + + case "$testHashType" in + SHA512) hashvalA="$hashSHA512"; hashvalB="$( computeSHA "$wavFile" 'SHA512' )" ;; + SHA256) hashvalA="$hashSHA256"; hashvalB="$( computeSHA "$wavFile" 'SHA256' )" ;; + SHA1) hashvalA="$hashSHA1"; hashvalB="$( computeSHA "$wavFile" 'SHA1' )" ;; + MD5) hashvalA="$hashMD5"; hashvalB="$( computeMD5 "$wavFile" )" ;; + CRC32) hashvalA="$hashCRC32"; hashvalB="$( computeCRC32 "$wavFile" )" ;; + esac + + if [ -z "$hashvalA" -o -z "$hashvalB" ]; then + return $EX_DATAERR + elif [ "$hashvalA" = "$hashvalB" ]; then + return $EX_OK + else + return $EX_KO + fi +} + +checkHashAvailability () +{ + local hashtype="$1" hashval="$2" + + test -z "$hashval" && return $EX_DATAERR + which 'sox' >/dev/null 2>&1 || return $EX_OSFILE + + case "$hashtype" in + SHA512) + if [ "$OS" = 'Linux' ]; then + which 'sha512sum' >/dev/null 2>&1 || return $EX_OSFILE + else + which 'shasum' >/dev/null 2>&1 || return $EX_OSFILE + fi + ;; + + SHA256) + if [ "$OS" = 'Linux' ]; then + which 'sha256sum' >/dev/null 2>&1 || return $EX_OSFILE + else + which 'shasum' >/dev/null 2>&1 || return $EX_OSFILE + fi + ;; + + SHA1) + if [ "$OS" = 'Linux' ]; then + which 'sha1sum' >/dev/null 2>&1 || return $EX_OSFILE + else + which 'shasum' >/dev/null 2>&1 || return $EX_OSFILE + fi + ;; + + MD5) + if [ "$OS" = 'Linux' ]; then + which 'md5sum' >/dev/null 2>&1 || return $EX_OSFILE + else + which 'md5' >/dev/null 2>&1 || return $EX_OSFILE + fi + ;; + + CRC32) + which 'cksfv' >/dev/null 2>&1 || return $EX_OSFILE + ;; + esac + + return $EX_OK +} + +getSuitableHash () +{ + local errType=$EX_DATAERR + + if [ -z "$hashtags" ]; then + return $EX_DATAERR + fi + + checkHashAvailability 'SHA512' "$hashSHA512"; ec=$? + if [ $ec -eq $EX_OK ]; then + testHashType='SHA512' + return $EX_OK + elif [ $ec -eq $EX_OSFILE ]; then + errType=$EX_OSFILE + fi + + checkHashAvailability 'SHA256' "$hashSHA256"; ec=$? + if [ $ec -eq $EX_OK ]; then + testHashType='SHA256' + return $EX_OK + elif [ $ec -eq $EX_OSFILE ]; then + errType=$EX_OSFILE + fi + + + checkHashAvailability 'SHA1' "$hashSHA1"; ec=$? + if [ $ec -eq $EX_OK ]; then + testHashType='SHA1' + return $EX_OK + elif [ $ec -eq $EX_OSFILE ]; then + errType=$EX_OSFILE + fi + + checkHashAvailability 'MD5' "$hashMD5"; ec=$? + if [ $ec -eq $EX_OK ]; then + testHashType='MD5' + return $EX_OK + elif [ $ec -eq $EX_OSFILE ]; then + errType=$EX_OSFILE + fi + + checkHashAvailability 'CRC32' "$hashCRC32"; ec=$? + if [ $ec -eq $EX_OK ]; then + testHashType='CRC32' + return $EX_OK + elif [ $ec -eq $EX_OSFILE ]; then + errType=$EX_OSFILE + fi + + return $errType +} + +getHashTags () +{ + hashtags='' hashSHA512='' hashSHA256='' hashSHA1='' hashMD5='' hashCRC32='' + if [ ! -e "$sourceTagFile" ]; then return $EX_DATAERR; fi + + $sedcmd -i'' -e 's@crc=@CRC32=@i' "$sourceTagFile" >/dev/null 2>&1 + hashtags="$( grep -iE '^SHA512=|^SHA256=|^SHA1=|^MD5=|^CRC32=' "$sourceTagFile" 2>/dev/null | sort -u 2>/dev/null )" + if [ -z "$hashtags" ]; then + return $EX_DATAERR + fi + + hashSHA512="$( echo "$hashtags" | grep -Fi 'SHA512=' 2>/dev/null | cut -d '=' -f 2 2>/dev/null )" + hashSHA256="$( echo "$hashtags" | grep -Fi 'SHA256=' 2>/dev/null | cut -d '=' -f 2 2>/dev/null )" + hashSHA1="$( echo "$hashtags" | grep -Fi 'SHA1=' 2>/dev/null | cut -d '=' -f 2 2>/dev/null )" + hashMD5="$( echo "$hashtags" | grep -Fi 'MD5=' 2>/dev/null | cut -d '=' -f 2 2>/dev/null )" + hashCRC32="$( echo "$hashtags" | grep -Fi 'CRC32=' 2>/dev/null | cut -d '=' -f 2 2>/dev/null )" + + return $EX_OK +} + +testHashedFile () +{ + local ec=$EX_OK + + case "$copyFile" in + *.flac) + if metaflac --no-utf8-convert --export-tags-to="$sourceTagFile" "$copyFile" >/dev/null 2>&1; then + getHashTags ; ec=$? + if [ $ec -eq $EX_OK ]; then + if [ -n "$hashMD5" ]; then + getInternalMD5 "$copyFile" + if [ -n "$sourceMD5" -a "$sourceMD5" != "$hashMD5" ]; then + ec=$EX_IOERR + fi + fi + if [ $ec -eq $EX_OK ]; then + getSuitableHash ; ec=$? + if [ -n "$testHashType" ]; then + flac -s -d -o "$wavFile" "$copyFile" >/dev/null 2>&1 && + compareHashes || ec=$EX_KO + fi + fi + fi + else + ec=$EX_DATAERR + fi + ;; + + *.wv) + APEv2 "$copyFile" > "$sourceTagFile" 2>/dev/null || ec=$EX_KO + if [ $ec -eq $EX_OK ]; then + getHashTags ; ec=$? + if [ $ec -eq $EX_OK ]; then + if [ -n "$hashMD5" ]; then + getInternalMD5 "$copyFile" + if [ -n "$sourceMD5" -a "$sourceMD5" != "$hashMD5" ]; then + ec=$EX_IOERR + fi + fi + if [ $ec -eq $EX_OK ]; then + getSuitableHash ; ec=$? + if [ -n "$testHashType" ]; then + wvunpack -q -m -o "$wavFile" "$copyFile" >/dev/null 2>&1 && + compareHashes || ec=$EX_KO + fi + fi + fi + else + ec=$EX_DATAERR + fi + ;; + + *.ape) + APEv2 "$copyFile" > "$sourceTagFile" 2>/dev/null || ec=$EX_KO + if [ $ec -eq $EX_OK ]; then + getHashTags ; getSuitableHash ; ec=$? + if [ -n "$testHashType" ]; then + mac "$copyFile" "$wavFile" -d >/dev/null 2>&1 && + compareHashes || ec=$EX_KO + fi + else + ec=$EX_DATAERR + fi + ;; + + *.tak) + APEv2 "$copyFile" > "$sourceTagFile" 2>/dev/null || ec=$EX_KO + if [ $ec -eq $EX_OK ]; then + getHashTags ; ec=$? + if [ $ec -eq $EX_OK ]; then + if [ -n "$hashMD5" ]; then + getInternalMD5 "$copyFile" + if [ -n "$sourceMD5" -a "$sourceMD5" != "$hashMD5" ]; then + ec=$EX_IOERR + fi + fi + if [ $ec -eq $EX_OK ]; then + getSuitableHash ; ec=$? + if [ -n "$testHashType" ]; then + cd "$SWAPDIR" + runWineExe Takc -d ".\\${i}.${sourceFilename}" ".\\${i}.wav" >/dev/null 2>&1 || ec=$EX_KO + cd "$OLDPWD" + if [ $ec -eq $EX_OK ]; then + compareHashes || ec=$EX_KO + fi + fi + fi + fi + else + ec=$EX_DATAERR + fi + ;; + + *.m4a) + if extractAlacMetadata; then + getHashTags ; getSuitableHash ; ec=$? + if [ -n "$testHashType" ]; then + nohup ffmpeg -v quiet -i "$copyFile" "$wavFile" > "${TDIR}/${i}.nohup" 2>> "$errorLogFile"; ec=$? + if [ -e "${TDIR}/${i}.nohup" ]; then rm -f "${TDIR}/${i}.nohup"; fi + if [ $ec -eq $EX_OK ]; then + compareHashes || ec=$EX_KO + fi + fi + else + ec=$EX_DATAERR + fi + ;; + esac + + return $ec +} + testFile () { local ec=$EX_OK + testHashType='' if [ -e "$copyFile" ]; then case "$copyFile" in *.flac) - flac -st "$copyFile" >/dev/null 2>> "$errorLogFile" && - saveDurations || ec=$EX_KO + testHashedFile ; ec=$? + if [ $ec -eq $EX_DATAERR -o $ec -eq $EX_OSFILE ]; then + flac -st "$copyFile" >/dev/null 2>&1; ec=$? + fi + saveDurations ;; *.wv) - wvunpack -qmv "$copyFile" >/dev/null 2>> "$errorLogFile" && - saveDurations || ec=$EX_KO + testHashedFile ; ec=$? + if [ $ec -eq $EX_DATAERR -o $ec -eq $EX_OSFILE ]; then + wvunpack -qmv "$copyFile" >/dev/null 2>&1; ec=$? + fi + saveDurations ;; *.ape) - mac "$copyFile" -v >/dev/null 2>> "$errorLogFile" && - saveDurations || ec=$EX_KO + testHashedFile ; ec=$? + if [ $ec -eq $EX_DATAERR -o $ec -eq $EX_OSFILE ]; then + if [ $macHasVerify = true ]; then + mac "$copyFile" -v >/dev/null 2>&1; ec=$? + fi + fi + saveDurations ;; *.tak) - cd "$SWAPDIR" - WINEPREFIX="$takWinePrefix" $takWineExe takc -t ".\\${i}.${sourceFilename}" >/dev/null 2>> "$errorLogFile" || ec=$EX_KO - cd "$OLDPWD" + testHashedFile ; ec=$? + if [ $ec -eq $EX_DATAERR -o $ec -eq $EX_OSFILE ]; then + cd "$SWAPDIR" + runWineExe Takc -t -md5 ".\\${i}.${sourceFilename}" >/dev/null 2>&1; ec=$? + cd "$OLDPWD" + fi + saveDurations + ;; + + *.m4a) + testHashedFile ; ec=$? + if [ -e "$wavFile" ]; then + saveDurations + fi ;; - *) printf "${GR}%2u ${WG}WG ${CY}%s${NM}: unsupported format\n" $p "$sourceFilename" 1>&2; return $EX_KO ;; + *) printMessage 'warning' 'testing' 'unsupported' "file:${sourceFile}" $p 'unsupported format' ; return $EX_KO ;; esac else ec=$EX_KO fi + if [ -e "$wavFile" ]; then rm -f "$wavFile"; fi + if [ -e "$copyFile" ]; then rm -f "$copyFile" "${copyFile}c" >/dev/null 2>&1; fi + if [ $ec -eq $EX_OK ]; then - if [ $verbose = true ]; then - printf "${GR}%2u ${OK}OK ${CY}%s${NM}\n" $p "$sourceFilename" + if [ -n "$testHashType" ]; then + printMessage 'success' 'testing' "$testHashType" "file:${sourceFile}" $p + else + printMessage 'success' 'testing' 'no_hash' "file:${sourceFile}" $p fi + elif [ $ec -eq $EX_DATAERR ]; then + printMessage 'warning' 'testing' 'no_hash' $p "file:${sourceFile}" 'missing hash metadata' + elif [ $ec -eq $EX_OSFILE ]; then + printMessage 'warning' 'testing' 'no_hash_tool' $p "file:${sourceFile}" 'unable to find appropriate hashing tools in your $PATH' + elif [ $ec -eq $EX_IOERR ]; then + printMessage 'error' 'testing' 'bad_internal_hash' $p "file:${sourceFile}" "internal MD5 hash doesn't match MD5 hash in metadata!" else - printf "${GR}%2u ${KO}ER ${CY}%s${NM}\n" $p "$sourceFilename" 1>&2 + if [ -n "$testHashType" ]; then + printMessage 'error' 'testing' "$testHashType" $p "file:${sourceFile}" + else + printMessage 'error' 'testing' 'no_hash' $p "file:${sourceFile}" + fi fi return $ec } @@ -3569,12 +5799,15 @@ sourceFile="${sourceFiles[$i]}" sourceFilename="${sourceFile##*/}" copyFile="${SWAPDIR}/${i}.${sourceFilename}" + wavFile="${SWAPDIR}/${i}.wav" + sourceTagFile="${TDIR}/${i}.txt" testingLockFile="${TDIR}/${i}" if unlink "$testingLockFile" 2>/dev/null; then prepareSource && testFile || ec=$EX_KO - rm -f "$copyFile" "${copyFile}c" >/dev/null 2>&1 + if [ -e "$wavFile" ]; then rm -f "$wavFile" >/dev/null 2>&1; fi + if [ -e "$copyFile" ]; then rm -f "$copyFile" "${copyFile}c" >/dev/null 2>&1; fi fi done @@ -3582,20 +5815,36 @@ return $ec } -getMD5 () +getInternalMD5 () { - local f="$1" d bn + local f="$1" dn bn sourceMD5='' case "$f" in *.flac) sourceMD5="$( metaflac --show-md5sum "$f" 2>/dev/null )" ;; - *.wv) sourceMD5="$( wvunpack -s "$f" 2>&1 | fgrep 'original md5:' | tr -d ' ' | cut -d ':' -f 2 )" ;; + + *.wv) + if gotNewWavpack; then + sourceMD5="$( wvunpack -f "$f" 2>/dev/null | cut -d ';' -f 7 )" + else + sourceMD5="$( wvunpack -s "$f" 2>&1 | grep -F 'original md5:' | tr -d ' ' | cut -d ':' -f 2 )" + fi + ;; + *.tak) - d="$( dirname "$f" 2>/dev/null )" - if [ -d "$d" ]; then - cd "$d" + dn="$( dirname "$f" 2>/dev/null )" + if [ -n "$dn" -a -d "$dn" ]; then + cd "$dn" bn="$( basename "$f" )" - sourceMD5="$( WINEPREFIX="$takWinePrefix" $takWineExe takc -fim3 ".\\${bn}" 2>/dev/null | fgrep 'MD5:' | tr -d ' ' | cut -d ':' -f 2 )" + if gotNewTAK; then + sourceMD5="$( runWineExe Takc -fi -fim5 ".\\${bn}" 2>/dev/null | cut -d ';' -f 6 )" + else + sourceMD5="$( runWineExe Takc -fi -fim3 ".\\${bn}" 2>/dev/null | tr -s ' ' | tr -d '\r\n' )" + sourceMD5="${sourceMD5##* }" + if [ "$sourceMD5" = 'available' ]; then + sourceMD5='' + fi + fi cd "$OLDPWD" fi ;; @@ -3612,128 +5861,124 @@ *.flac) metaflac --no-utf8-convert --export-tags-to="$sourceTagFile" "$copyFile" >/dev/null 2>> "$errorLogFile" || ec=$EX_KO if [ $ec -eq $EX_OK ]; then - getMD5 "$copyFile" - if [ -n "$sourceMD5" ]; then - echo "MD5=${sourceMD5}" > "$sourceMD5File" - fi - if [ "$hashes" != 'MD5' -o -z "$sourceMD5" ]; then - flac -s -d -o "$wavFile" "$copyFile" >/dev/null 2>> "$errorLogFile" || ec=$EX_KO - fi + getInternalMD5 "$copyFile" + flac -s -d -o "$wavFile" "$copyFile" >/dev/null 2>> "$errorLogFile" || ec=$EX_KO fi ;; *.wv) - apetag -i "$copyFile" 2>> "$errorLogFile" | sed -e 's@\x0@ / @g' -e 's@" "@=@' -e 's@^"@@' -e 's@"$@@' > "$sourceTagFile" && - filterApetagOutput || ec=$EX_KO + touch "$sourceTagFile" if [ $ec -eq $EX_OK ]; then - getMD5 "$copyFile" - if [ -n "$sourceMD5" ]; then - echo "MD5=${sourceMD5}" > "$sourceMD5File" - fi - if [ "$hashes" != 'MD5' -o -z "$sourceMD5" ]; then - wvunpack -q -m -o "$wavFile" "$copyFile" >/dev/null 2>> "$errorLogFile" || ec=$EX_KO - fi + getInternalMD5 "$copyFile" + wvunpack -q -m -o "$wavFile" "$copyFile" >/dev/null 2>> "$errorLogFile" || ec=$EX_KO fi ;; *.tak) - apetag -i "$copyFile" 2>> "$errorLogFile" | sed -e 's@\x0@, @g' | sed -e 's@" "@=@' -e 's@^"@@' -e 's@"$@@' > "$sourceTagFile" && - filterApetagOutput || ec=$EX_KO + touch "$sourceTagFile" if [ $ec -eq $EX_OK ]; then - getMD5 "$copyFile" - if [ -n "$sourceMD5" ]; then - echo "MD5=${sourceMD5}" > "$sourceMD5File" - fi - if [ "$hashes" != 'MD5' -o -z "$sourceMD5" ]; then - cd "$SWAPDIR" - WINEPREFIX="$takWinePrefix" $takWineExe takc -d -md5 ".\\${i}.${sourceFilename}" ".\\${i}.wav" >/dev/null 2>> "$errorLogFile" || ec=$EX_KO - cd "$OLDPWD" - fi + getInternalMD5 "$copyFile" + cd "$SWAPDIR" + runWineExe Takc -d ".\\${i}.${sourceFilename}" ".\\${i}.wav" >/dev/null 2>> "$errorLogFile" || ec=$EX_KO + cd "$OLDPWD" fi ;; *.ape) - apetag -i "$copyFile" 2>> "$errorLogFile" | sed -e 's@\x0@, @g' | sed -e 's@" "@=@' -e 's@^"@@' -e 's@"$@@' > "$sourceTagFile" && - filterApetagOutput || ec=$EX_KO + touch "$sourceTagFile" if [ $ec -eq $EX_OK ]; then mac "$copyFile" "$wavFile" -d >/dev/null 2>> "$errorLogFile" || ec=$EX_KO fi ;; *.m4a) - alac -f "$wavFile" "$copyFile" >/dev/null 2>> "$errorLogFile" || ec=$EX_KO + nohup ffmpeg -v quiet -i "$copyFile" "$wavFile" > "${TDIR}/${i}.nohup" 2>> "$errorLogFile"; ec=$? + if [ -e "${TDIR}/${i}.nohup" ]; then rm -f "${TDIR}/${i}.nohup"; fi touch "$sourceTagFile" ;; - *) printf "${GR}%2u ${WG}WG ${CY}%s${NM}: unsupported format\n" $p "$sourceFilename" 1>&2; return $EX_KO ;; + *) printMessage 'warning' 'hashing' 'unsupported' "file:${sourceFile}" $p 'unsupported format' ; return $EX_KO ;; esac + if [ -e "$copyFile" ]; then rm -f "$copyFile" "${copyFile}c" >/dev/null 2>&1; fi + if [ $ec -eq $EX_OK ]; then + processSourceTagFile ereg="" for h in $hashes; do ereg="${ereg}|^${h}=" + if [ "$h" = 'CRC32' ]; then + ereg="${ereg}|^crc=" + fi done + # filter out existing hashes grep -viE "${ereg:1}" "$sourceTagFile" > "${sourceTagFile}.tmp" mv "${sourceTagFile}.tmp" "$sourceTagFile" for h in $hashes; do case "$h" in - CRC) + CRC32) { - hline="$( computeCRC "$wavFile" )" - echo "$hline" >> "$sourceTagFile" + hline="$( computeCRC32 "$wavFile" )" + test -n "$hline" && echo "CRC32=$hline" >> "$sourceTagFile" + test -n "$hline" && printMessage 'info' "$h" "${h}=${hline}" "file:${sourceFile}" $p } & ;; MD5) { - if [ -f "$sourceMD5File" ]; then - cat "$sourceMD5File" >> "$sourceTagFile" - else - hline="$( computeMD5 "$wavFile" )" - echo "$hline" >> "$sourceTagFile" + hline="$( computeMD5 "$wavFile" )" + test -n "$hline" && printMessage 'info' "$h" "${h}=${hline}" "file:${sourceFile}" $p + if [ -n "$hline" ]; then + echo "MD5=${hline}" >> "$sourceTagFile" + if [ -n "$sourceMD5" -a "$sourceMD5" != "$hline" ]; then + printMessage 'warning' 'hashing' 'bad_internal_hash' $p "file:${sourceFile}" 'internal MD5 hash is incorrect (possible bug in the codec)!' + printMessage 'info' 'hashing' 'stderr' 'Please file a bug report: http://caudec.net/redirect/bugReport' + fi fi } & ;; - SHA1) + + SHA1|SHA256|SHA512) { - hline="$( computeSHA1 "$wavFile" )" - echo "$hline" >> "$sourceTagFile" + hline="$( computeSHA "$wavFile" "$h" )" + test -n "$hline" && echo "${h}=${hline}" >> "$sourceTagFile" + test -n "$hline" && printMessage 'info' "$h" "${h}=${hline}" "file:${sourceFile}" $p } & ;; esac done + wait fi - wait - processSourceTagFile + if [ -e "$wavFile" ]; then rm -f "$wavFile" >/dev/null 2>&1; fi - case "$copyFile" in - *.flac) - destTagFile="${TDIR}/${i}.vc.txt" outputCodec="FLAC" - cp "$sourceTagFile" "$destTagFile" >/dev/null 2>&1 && - printf -- "--no-utf8-convert`tagline --set-tag`\x00%s" "$sourceFile" | xargs -0 metaflac --remove-all-tags >/dev/null 2>> "$errorLogFile" || ec=$EX_KO - ;; + if [ $ec -eq $EX_OK ]; then + case "$copyFile" in + *.flac) + destTagFile="${TDIR}/${i}.vc.txt" outputCodec="FLAC" + cp "$sourceTagFile" "$destTagFile" >/dev/null 2>&1 && + printf -- "--no-utf8-convert`tagline --set-tag`\x00%s" "$sourceFile" | xargs -0 metaflac --remove-all-tags >/dev/null 2>> "$errorLogFile" || ec=$EX_KO + ;; - *.wv|*.tak|*.ape) - destTagFile="${TDIR}/${i}.ape.txt" outputCodec="WavPack" - cp "$sourceTagFile" "$destTagFile" >/dev/null 2>&1 && - printf -- "-m\x00overwrite`tagline -p`" | xargs -0 apetag -i "$sourceFile" >/dev/null 2>> "$errorLogFile" || ec=$EX_KO - ;; + *.wv|*.tak|*.ape) + destTagFile="${TDIR}/${i}.ape.txt" outputCodec="WavPack" + cp "$sourceTagFile" "$destTagFile" >/dev/null 2>&1 && + printf -- "-z`tagline -t`\x00%s" "$sourceFile" | xargs -0 APEv2 >/dev/null 2>> "$errorLogFile" || ec=$EX_KO + ;; - *.m4a) - destTagFile="${TDIR}/${i}.m4a.txt" outputCodec="ALAC" - convertTags 'm4a' 'm4a' - printf -- "%s`tagline`" "$sourceFile" | xargs -0 neroAacTag >/dev/null 2>> "$errorLogFile" || ec=$EX_KO - ;; - esac + *.m4a) + destTagFile="${TDIR}/${i}.m4a.txt" outputCodec="ALAC" + convertTags 'm4a' 'm4a' + printf -- "%s`tagline`" "$sourceFile" | xargs -0 neroAacTag >/dev/null 2>> "$errorLogFile" || ec=$EX_KO + ;; + esac + fi else ec=$EX_KO fi if [ $ec -eq $EX_OK ]; then - if [ $verbose = true ]; then - printf "${GR}%2u ${OK}OK ${CY}%s${NM}\n" $p "$sourceFilename" - fi + printMessage 'success' 'hashing' $p "file:${sourceFile}" else - printf "${GR}%2u ${KO}ER ${CY}%s${NM}\n" $p "$sourceFilename" 1>&2 + printMessage 'error' 'decoding' $p "file:${sourceFile}" fi return $ec } @@ -3751,20 +5996,12 @@ hashingLockFile="${TDIR}/${i}" if unlink "$hashingLockFile" 2>/dev/null; then - if [ "$hashes" = 'MD5' ]; then - getMD5 "$sourceFile" - if [ -z "$sourceMD5" ]; then - prepareSource || ec=$EX_KO - else - prepareSource 'symlink' || ec=$EX_KO - fi - else - prepareSource || ec=$EX_KO - fi + prepareSource || ec=$EX_KO if [ $ec -eq $EX_OK ]; then computeHash || ec=$EX_KO fi - rm -f "$copyFile" "${copyFile}c" >/dev/null 2>&1 + if [ -e "$wavFile" ]; then rm -f "$wavFile" >/dev/null 2>&1; fi + if [ -e "$copyFile" ]; then rm -f "$copyFile" "${copyFile}c" >/dev/null 2>&1; fi fi done @@ -3795,6 +6032,34 @@ echo " $scg1 $scg1 $scg2 $scg2 00024CA8 00024CA8 00007FFF 00007FFF 00024CA8 00024CA8" } +saveReplaygainToMP3 () +{ + local ec=$EX_OK + + if [ "$eyeD3Version" = '0.7+' ]; then + eyeD3 -2 --user-text-frame="REPLAYGAIN_${gainType}_GAIN:${gain} dB" \ + --user-text-frame="REPLAYGAIN_${gainType}_PEAK:${peak}" "$destFile" >/dev/null 2>> "$errorLogFile" || ec=$EX_KO + else + eyeD3 -2 --no-tagging-time-frame --itunes --set-user-text-frame="REPLAYGAIN_${gainType}_GAIN:${gain} dB" \ + --set-user-text-frame="REPLAYGAIN_${gainType}_PEAK:${peak}" "$destFile" >/dev/null 2>> "$errorLogFile" || ec=$EX_KO + fi + + return $ec +} + +saveSoundcheckToMP3 () +{ + local ec=$EX_OK + + if [ "$eyeD3Version" = '0.7+' ]; then + eyeD3 -2 --comment="eng:iTunNORM:$( getSoundcheck "$uGain" )" "$destFile" >/dev/null 2>> "$errorLogFile" || ec=$EX_KO + else + eyeD3 -2 --no-tagging-time-frame --itunes --comment="eng:iTunNORM:$( getSoundcheck "$uGain" )" "$destFile" >/dev/null 2>> "$errorLogFile" || ec=$EX_KO + fi + + return $ec +} + saveGain () { local gainType="$1" gainTypeText ec=$EX_OK gain peak uGain @@ -3817,12 +6082,16 @@ *.wv) if [ "$gainType" = 'TRACK' ]; then - return $EX_OK + gainTypeText='Track' + else + gainTypeText='Album' fi destTagFile="${TDIR}/${i}.ape.txt" - echo "Replaygain_Album_Gain=${gain} dB" >> "$destTagFile" - echo "Replaygain_Album_Peak=${peak}" >> "$destTagFile" - printf -- "-m\x00overwrite`tagline -p`" | xargs -0 apetag -i "$destFile" >/dev/null 2>> "$errorLogFile" || ec=$EX_KO + if [ "$gainType" = 'ALBUM' ]; then + echo "Replaygain_${gainTypeText}_Gain=${gain} dB" >> "$destTagFile" + echo "Replaygain_${gainTypeText}_Peak=${peak}" >> "$destTagFile" + fi + printf -- "-z`tagline -t`\x00%s" "$destFile" | xargs -0 APEv2 >/dev/null 2>> "$errorLogFile" || ec=$EX_KO ;; *.ape|*.tak) @@ -3834,10 +6103,7 @@ fi echo "Replaygain_${gainTypeText}_Gain=${gain} dB" >> "$destTagFile" echo "Replaygain_${gainTypeText}_Peak=${peak}" >> "$destTagFile" - if [ "$gainType" = 'TRACK' ]; then - return $EX_OK - fi - printf -- "-m\x00overwrite`tagline -p`" | xargs -0 apetag -i "$destFile" >/dev/null 2>> "$errorLogFile" || ec=$EX_KO + printf -- "-z`tagline -t`\x00%s" "$destFile" | xargs -0 APEv2 >/dev/null 2>> "$errorLogFile" || ec=$EX_KO ;; *.m4a) @@ -3851,26 +6117,39 @@ if [ $computeSoundcheck = true -a "$gainType" = "$soundcheckMode" ]; then neroAacTag "$destFile" "-meta-user:itunnorm=$( getSoundcheck "$uGain" )" >/dev/null 2>> "$errorLogFile" || ec=$EX_KO fi - else # .m4a is AAC + elif [ -e "${TDIR}/${i}.isAAC" ]; then # .m4a is AAC if [ $applyGain = true ]; then - if [ "$gainType" = "$applyGainType" ]; then + if [ "$applyGainType" = 'ALBUM' -o "$applyGainType" = 'TRACK' -o "$applyGainType" = 'ALBUM_PEAK' -o "$applyGainType" = 'TRACK_PEAK' ]; then + if [ "$gainType" = "${applyGainType%_*}" ]; then + gainX="$( echo "scale=2; $uGain / 1.5" | bc )" + gainX="$( printf '%.0f' "$gainX" )" + aacgain -c -g "$gainX" "$destFile" >/dev/null 2>> "$errorLogFile" || ec=$EX_KO + fi + elif [ "$gainType" = 'TRACK' ]; then # arbitrary gain given + uGain="${applyGainType#+}" gainX="$( echo "scale=2; $uGain / 1.5" | bc )" gainX="$( printf '%.0f' "$gainX" )" aacgain -c -g "$gainX" "$destFile" >/dev/null 2>> "$errorLogFile" || ec=$EX_KO fi else neroAacTag "$destFile" "-meta-user:replaygain_${gainTypeText}_gain=${gain} dB" "-meta-user:replaygain_${gainTypeText}_peak=${peak}" >/dev/null 2>> "$errorLogFile" || ec=$EX_KO - if [ $computeSoundcheck = true -a "$gainType" = "$soundcheckMode" ]; then - neroAacTag "$destFile" "-meta-user:itunnorm=$( getSoundcheck "$uGain" )" >/dev/null 2>> "$errorLogFile" || ec=$EX_KO + if [ $computeSoundcheck = true ]; then + if [ "$soundcheckMode" = 'ALBUM' -o "$soundcheckMode" = 'TRACK' ]; then + if [ "$gainType" = "$soundcheckMode" ]; then + neroAacTag "$destFile" "-meta-user:itunnorm=$( getSoundcheck "$uGain" )" >/dev/null 2>> "$errorLogFile" || ec=$EX_KO + fi + elif [ "$gainType" = 'TRACK' ]; then + uGain="${soundcheckMode#+}" + neroAacTag "$destFile" "-meta-user:itunnorm=$( getSoundcheck "$uGain" )" >/dev/null 2>> "$errorLogFile" || ec=$EX_KO + fi fi fi + else + ec=$EX_KO fi ;; *.ogg) - if [ "$gainType" = 'TRACK' ]; then - return $EX_OK - fi destTagFile="${TDIR}/${i}.vc.txt" echo "REPLAYGAIN_${gainType}_PEAK=${peak}" >> "$destTagFile" echo "REPLAYGAIN_${gainType}_GAIN=${gain} dB" >> "$destTagFile" @@ -3879,16 +6158,29 @@ *.mp3) if [ $applyGain = true ]; then - if [ "$gainType" = "$applyGainType" ]; then + if [ "$applyGainType" = 'ALBUM' -o "$applyGainType" = 'TRACK' -o "$applyGainType" = 'ALBUM_PEAK' -o "$applyGainType" = 'TRACK_PEAK' ]; then + if [ "$gainType" = "${applyGainType%_*}" ]; then + gainX="$( echo "scale=2; $uGain / 1.5" | bc )" + gainX="$( printf '%.0f' "$gainX" )" + mp3gain -c -g "$gainX" "$destFile" >/dev/null 2>> "$errorLogFile" || ec=$EX_KO + fi + elif [ "$gainType" = 'TRACK' ]; then + uGain="${applyGainType#+}" gainX="$( echo "scale=2; $uGain / 1.5" | bc )" gainX="$( printf '%.0f' "$gainX" )" mp3gain -c -g "$gainX" "$destFile" >/dev/null 2>> "$errorLogFile" || ec=$EX_KO fi else - eyeD3 -2 --user-text-frame="REPLAYGAIN_${gainType}_GAIN:${gain} dB" \ - --user-text-frame="REPLAYGAIN_${gainType}_PEAK:${peak}" "$destFile" >/dev/null 2>> "$errorLogFile" || ec=$EX_KO - if [ $computeSoundcheck = true -a "$gainType" = "$soundcheckMode" ]; then - eyeD3 -2 --comment="eng:iTunNORM:$( getSoundcheck "$uGain" )" "$destFile" >/dev/null 2>> "$errorLogFile" || ec=$EX_KO + saveReplaygainToMP3 || ec=$EX_KO + if [ $computeSoundcheck = true ]; then + if [ "$soundcheckMode" = 'ALBUM' -o "$soundcheckMode" = 'TRACK' ]; then + if [ "$gainType" = "$soundcheckMode" ]; then + saveSoundcheckToMP3 || ec=$EX_KO + fi + elif [ "$gainType" = 'TRACK' ]; then + uGain="${soundcheckMode#+}" + saveSoundcheckToMP3 || ec=$EX_KO + fi fi fi ;; @@ -3901,32 +6193,51 @@ saveSeconds () { - local samples sr seconds=1 duration hours minutes centiseconds ec=$EX_OK line milliseconds + local samples sr seconds=1 duration hours minutes centiseconds ec=$EX_OK line milliseconds data case "$copyFile" in *.flac) - samples="$( metaflac --show-total-samples "$copyFile" 2>> "$errorLogFile" )" - sr="$( metaflac --show-sample-rate "$copyFile" 2>> "$errorLogFile" )" - seconds=$(( (samples + (sr / 2)) / sr )) + data="$( metaflac --show-total-samples --show-sample-rate "$copyFile" 2>> "$errorLogFile" )" + if [ -n "$data" ]; then + for v in $data; do + if [ -z "$samples" ]; then + samples="$v" + else + sr="$v" + fi + done + seconds=$(( (samples + (sr / 2)) / sr )) + fi ;; *.wv) - duration="$( wvunpack -s "$copyFile" 2>/dev/null | fgrep 'duration:' | cut -d ':' -f 2- | tr -d ' ' )" - if [ -n "$duration" ]; then - hours="${duration%%:*}" ; hours="${hours#0}" - minutes="${duration%:*}" ; minutes="${minutes#*:}" ; minutes="${minutes#0}" - seconds="${duration##*:}" - centiseconds="${seconds#*.}" ; centiseconds="${centiseconds#0}" - seconds="${seconds%.*}" ; seconds="${seconds#0}" - seconds=$(( (hours * 3600) + (minutes * 60) + seconds )) - if [ $centiseconds -ge 50 ]; then - ((seconds++)) + if gotNewWavpack ; then + data="$( wvunpack -f "$copyFile" 2>/dev/null )" + if [ -n "$data" ]; then + samples="$( echo "$data" | cut -d ';' -f 6 )" + sr="$( echo "$data" | cut -d ';' -f 1 )" + if [ -n "$samples" -a -n "$sr" ]; then + seconds=$(( (samples + (sr / 2)) / sr )) + fi + fi + else + duration="$( wvunpack -s "$copyFile" 2>/dev/null | grep -F 'duration:' | cut -d ':' -f 2- | tr -d ' ' )" + if [ -n "$duration" ]; then + hours="${duration%%:*}" ; hours="${hours#0}" + minutes="${duration%:*}" ; minutes="${minutes#*:}" ; minutes="${minutes#0}" + seconds="${duration##*:}" + centiseconds="${seconds#*.}" ; centiseconds="${centiseconds#0}" + seconds="${seconds%.*}" ; seconds="${seconds#0}" + seconds=$(( (hours * 3600) + (minutes * 60) + seconds )) + if [ $centiseconds -ge 50 ]; then + ((seconds++)) + fi fi fi ;; *.ogg) - line="$( ogginfo "$copyFile" 2>> "$errorLogFile" | fgrep 'Playback length:' | tr -d ' ' | sed -e 's@\t@@g' | cut -d ':' -f 2-3 )" + line="$( ogginfo "$copyFile" 2>> "$errorLogFile" | grep -F 'Playback length:' | tr -d '\t ' | cut -d ':' -f 2-3 )" if [ -n "$line" ]; then minutes="${line%:*}" ; minutes="${minutes%m}" seconds="${line#*:}" ; seconds="${seconds#s}" @@ -3939,26 +6250,23 @@ fi ;; - *) - duration="$( shninfo "$wavFile" 2>> "$errorLogFile" | fgrep 'Length:' | cut -d ':' -f 2- | tr -d ' ' )" - if [ -n "$duration" ]; then - duration="${duration##* }" - minutes="${duration%:*}" ; minutes="${minutes#0}" - seconds="${duration#*:}" - milliseconds="${seconds#*.}" ; milliseconds="${milliseconds#0}" - seconds="${seconds%.*}" ; seconds="${seconds#0}" - seconds=$(( (minutes * 60) + seconds )) - if [ "${milliseconds:2:1}" = '' ]; then # CD frames, i.e. <= 75 - if [ $milliseconds -ge 38 ]; then - ((seconds++)) - fi - else # milliseconds - if [ $milliseconds -ge 500 ]; then - ((seconds++)) - fi + *.m4a) + line="$( ffprobe -print_format 'default' -show_streams -select_streams 'a:0' "$copyFile" 2>/dev/null | grep -F 'duration=' 2>/dev/null | cut -d '=' -f 2 | tr -cd '0-9.' )" + if [ -n "$line" ]; then + seconds="${line%.*}" milliseconds="${line#*.}" + if [ "${milliseconds:0:1}" -ge 5 ]; then + ((seconds++)) fi fi ;; + + *.mp3) + getSoxSeconds "$copyFile" + ;; + + *) + getSoxSeconds "$wavFile" + ;; esac echo "$seconds" > "$secondsFile" @@ -3971,16 +6279,20 @@ local line if which 'wavegain' >/dev/null 2>&1; then - line="$( wavegain -c -n "$wavFile" 2>&1 | fgrep "$wavFile" | tr -d ' ' | cut -d '|' -f 1-2 | tr -d '+' )" ; ec=$? - if [ $ec -ne $EX_OK ]; then return $EX_KO; fi + line="$( wavegain -c -n "$wavFile" 2>&1 | grep -F "$wavFile" | tr -d ' +' | cut -d '|' -f 1-2 )" ; ec=$? + if [ $ec -ne $EX_OK -o -z "$line" ]; then return $EX_KO; fi trackGain="${line%|*}" ; trackGain="${trackGain%dB}" trackPeak="${line#*|}" ; trackPeak="$( echo "scale=8; $trackPeak / 32767" | bc )" if [ "${trackPeak:0:1}" = '.' ]; then trackPeak="0${trackPeak}" fi - echo "$trackGain" > "$trackGainFile" - echo "$trackPeak" > "$trackPeakFile" - saveSeconds + if [ -n "$trackGain" -a -n "$trackPeak" ]; then + echo "$trackGain" > "$trackGainFile" + echo "$trackPeak" > "$trackPeakFile" + saveSeconds + else + return $EX_KO + fi else return $EX_KO fi @@ -3988,36 +6300,45 @@ computeTrackGain () { - local ec=$EX_OK line decimals sGain m4aFileType + local ec=$EX_OK line decimals sGain if [ -e "$copyFile" ]; then case "$copyFile" in *.flac) metaflac --add-replay-gain "$copyFile" >/dev/null 2>> "$errorLogFile" || ec=$EX_KO if [ $ec -eq $EX_OK ]; then - trackGain="$( metaflac --export-tags-to=- "$copyFile" 2>> "$errorLogFile" | fgrep 'REPLAYGAIN_TRACK_GAIN=' | cut -d '=' -f 2 | cut -d ' ' -f 1 | tr -d '+' )" - trackPeak="$( metaflac --export-tags-to=- "$copyFile" 2>> "$errorLogFile" | fgrep 'REPLAYGAIN_TRACK_PEAK=' | cut -d '=' -f 2 )" - echo "$trackGain" > "$trackGainFile" - echo "$trackPeak" > "$trackPeakFile" - saveSeconds + trackGain="$( metaflac --export-tags-to=- "$copyFile" 2>> "$errorLogFile" | grep -F 'REPLAYGAIN_TRACK_GAIN=' | cut -d '=' -f 2 | cut -d ' ' -f 1 | tr -d '+' )" + trackPeak="$( metaflac --export-tags-to=- "$copyFile" 2>> "$errorLogFile" | grep -F 'REPLAYGAIN_TRACK_PEAK=' | cut -d '=' -f 2 )" + if [ -n "$trackGain" -a -n "$trackPeak" ]; then + echo "$trackGain" > "$trackGainFile" + echo "$trackPeak" > "$trackPeakFile" + saveSeconds + else + ec=$EX_KO + fi fi ;; *.wv) - wvgain -q -c "$copyFile" >/dev/null 2>> "$errorLogFile" + APEv2 -z -r 'replaygain_track_gain' -r 'replaygain_album_gain' -r 'replaygain_track_peak' -r 'replaygain_album_peak' "$copyFile" >/dev/null 2>&1 wvgain -q "$copyFile" >/dev/null 2>> "$errorLogFile" || ec=$EX_KO if [ $ec -eq $EX_OK ]; then - apetag -i "$copyFile" 2>> "$errorLogFile" | sed -e 's@\x0@ / @g' -e 's@" "@=@' -e 's@^"@@' -e 's@"$@@' \ - -e 's@replaygain_track_gain@Replaygain_Track_Gain@i' -e 's@replaygain_track_peak@Replaygain_Track_Peak@i' > "$sourceTagFile" && - filterApetagOutput || ec=$EX_KO + APEv2 -z "$copyFile" 2>> "$errorLogFile" | grep -iE '^replaygain_track_' | $sedcmd \ + -e 's@replaygain_track_gain@Replaygain_Track_Gain@i' \ + -e 's@replaygain_track_peak@Replaygain_Track_Peak@i' > "$sourceTagFile" 2>/dev/null || ec=$EX_KO if [ $ec -eq $EX_OK ]; then + processSourceTagFile destTagFile="${TDIR}/${i}.ape.txt" convertTags 'ape' 'ape' - trackGain="$( fgrep 'Replaygain_Track_Gain=' "$destTagFile" | cut -d '=' -f 2 | cut -d ' ' -f 1 | tr -d '+' )" - trackPeak="$( fgrep 'Replaygain_Track_Peak=' "$destTagFile" | cut -d '=' -f 2 )" - echo "$trackGain" > "$trackGainFile" - echo "$trackPeak" > "$trackPeakFile" - saveSeconds + trackGain="$( grep -i 'Replaygain_Track_Gain=' "$destTagFile" | cut -d '=' -f 2 | cut -d ' ' -f 1 | tr -d '+' )" + trackPeak="$( grep -i 'Replaygain_Track_Peak=' "$destTagFile" | cut -d '=' -f 2 )" + if [ -n "$trackGain" -a -n "$trackPeak" ]; then + echo "$trackGain" > "$trackGainFile" + echo "$trackPeak" > "$trackPeakFile" + saveSeconds + else + ec=$EX_KO + fi fi fi ;; @@ -4025,96 +6346,159 @@ *.ape) mac "$copyFile" "$wavFile" -d >/dev/null 2>> "$errorLogFile" ; ec=$? if [ $ec -eq $EX_OK ]; then - apetag -i "$copyFile" 2>> "$errorLogFile" | sed -e 's@\x0@, @g' | sed -e 's@" "@=@' -e 's@^"@@' -e 's@"$@@' | grep -i -v 'replaygain_' > "$sourceTagFile" && - filterApetagOutput || ec=$EX_KO - if [ $ec -eq $EX_OK ]; then - destTagFile="${TDIR}/${i}.ape.txt" - convertTags 'ape' 'ape' - runWavegain || ec=$EX_KO - fi + touch "$sourceTagFile" + destTagFile="${TDIR}/${i}.ape.txt" + runWavegain || ec=$EX_KO fi - rm -f "$wavFile" >/dev/null 2>&1 ;; *.tak) cd "$SWAPDIR" - WINEPREFIX="$takWinePrefix" $takWineExe takc -d -md5 ".\\${i}.${sourceFilename}" ".\\${i}.wav" >/dev/null 2>> "$errorLogFile"; ec=$? + runWineExe Takc -d ".\\${i}.${sourceFilename}" ".\\${i}.wav" >/dev/null 2>> "$errorLogFile"; ec=$? cd "$OLDPWD" if [ $ec -eq $EX_OK ]; then - apetag -i "$copyFile" 2>> "$errorLogFile" | sed -e 's@\x0@, @g' | sed -e 's@" "@=@' -e 's@^"@@' -e 's@"$@@' | grep -i -v 'replaygain_' > "$sourceTagFile" && - filterApetagOutput || ec=$EX_KO - if [ $ec -eq $EX_OK ]; then - destTagFile="${TDIR}/${i}.ape.txt" - convertTags 'ape' 'ape' - runWavegain || ec=$EX_KO - fi + touch "$sourceTagFile" + destTagFile="${TDIR}/${i}.ape.txt" + runWavegain || ec=$EX_KO fi - rm -f "$wavFile" >/dev/null 2>&1 ;; *.m4a) - m4aFileType="$( alac -t "$f" 2>> "$errorLogFile" )" - if [ "$m4aFileType" = 'file type: alac' ]; then + if isALAC "$copyFile"; then touch "${TDIR}/${i}.isALAC" - alac -f "$wavFile" "$copyFile" >/dev/null 2>> "$errorLogFile" && - runWavegain || ec=$EX_KO - else - line="$( aacgain -e -k -o -q "$copyFile" 2>> "$errorLogFile" | fgrep -v 'MP3 gain' | tr '\t' '|' | tr -d '+' | cut -d '|' -f 3-4 )" ; ec=$? + nohup ffmpeg -v quiet -i "$copyFile" "$wavFile" > "${TDIR}/${i}.nohup" 2>> "$errorLogFile"; ec=$? + if [ -e "${TDIR}/${i}.nohup" ]; then rm -f "${TDIR}/${i}.nohup"; fi if [ $ec -eq $EX_OK ]; then - trackGain="${line%|*}" - decimals="${trackGain#*.}" - trackGain="${trackGain%.*}.${decimals:0:2}" - trackPeak="${line#*|}"; trackPeak="${trackPeak%.*}" - trackPeak="$( echo "scale=8; $trackPeak / 32767" | bc )" - if [ "${trackPeak:0:1}" = '.' ]; then - trackPeak="0${trackPeak}" + runWavegain || ec=$EX_KO + fi + elif isAAC "$copyFile"; then + touch "${TDIR}/${i}.isAAC" + if [ -L "$copyFile" ]; then # make a real copy so that APE metadata doesn't get added to the source + rm -f "$copyFile" >/dev/null 2>&1 && cp "$sourceFile" "$copyFile" >/dev/null 2>&1 ; ec=$? + fi + if [ $ec -eq $EX_OK ]; then + line="$( aacgain -e -k -o -q "$copyFile" 2>> "$errorLogFile" | grep -Fv 'MP3 gain' | tr '\t' '|' | tr -d '+' | cut -d '|' -f 3-4 )" ; ec=$? + if [ $ec -eq $EX_OK -a -n "$line" ]; then + trackGain="${line%|*}" + decimals="${trackGain#*.}" + trackGain="${trackGain%.*}.${decimals:0:2}" + trackPeak="${line#*|}"; trackPeak="${trackPeak%.*}" + trackPeak="$( echo "scale=8; $trackPeak / 32767" | bc )" + if [ "${trackPeak:0:1}" = '.' ]; then + trackPeak="0${trackPeak}" + fi + if [ -n "$trackGain" -a -n "$trackPeak" ]; then + if [ $applyGain = true -a "$applyGainType" = 'TRACK_PEAK' ]; then + trackGain="$( printf "%.2f" "$( echo "20 * (l(${trackPeak}) / l(10))" | bc -l )" )" + if [ "${trackGain:0:1}" = '-' ]; then + trackGain="${trackGain:1}" + if [ "${peakReference:0:1}" = '-' ]; then + trackGain="$( printf "%.2f" "$( echo "$trackGain + ($peakReference)" | bc -l )" )" + fi + elif [ "${peakReference:0:1}" = '-' ]; then + trackGain="$peakReference" + else + trackGain="-0" + fi + elif [ $applyGain = true -a "$applyGainType" = 'TRACK' -a -n "$preamp" ]; then + if [ "${preamp:0:1}" = '-' ]; then + trackGain="$( printf "%.2f" "$( echo "$trackGain - ${preamp:1}" | bc -l 2>/dev/null )" )" + elif [ "${preamp:0:1}" = '+' ]; then + trackGain="$( printf "%.2f" "$( echo "$trackGain + ${preamp:1}" | bc -l 2>/dev/null )" )" + fi + fi + echo "$trackGain" > "$trackGainFile" + echo "$trackPeak" > "$trackPeakFile" + saveSeconds || ec=$EX_KO + else + ec=$EX_KO + fi + else + ec=$EX_KO fi - echo "$trackGain" > "$trackGainFile" - echo "$trackPeak" > "$trackPeakFile" - neroAacDec '-if' "$copyFile" '-of' "$wavFile" >/dev/null 2>> "$errorLogFile" && - saveSeconds || ec=$EX_KO fi + else + printMessage 'warning' 'track_gain' 'unsupported' "file:${sourceFile}" $p 'unsupported format' ; return $EX_KO fi - rm -f "$wavFile" >/dev/null 2>&1 ;; *.ogg) - vorbisgain -c "$copyFile" >/dev/null 2>> "$errorLogFile" - vorbisgain -q "$copyFile" >/dev/null 2>> "$errorLogFile" || ec=$EX_KO + line="$( vorbisgain -d "$copyFile" 2>/dev/null | grep -F "$copyFile" | tr -cd '.|\-0-9' )" + if [ -z "$line" ]; then ec=$EX_KO; fi if [ $ec -eq $EX_OK ]; then - vorbiscomment -l "$copyFile" 2>> "$errorLogFile" > "$sourceTagFile" || ec=$EX_KO - if [ $ec -eq $EX_OK ]; then - destTagFile="${TDIR}/${i}.vc.txt" - convertTags 'vc' 'vc' - trackGain="$( fgrep 'REPLAYGAIN_TRACK_GAIN=' "$destTagFile" | cut -d '=' -f 2 | cut -d ' ' -f 1 | tr -d '+' )" - trackPeak="$( fgrep 'REPLAYGAIN_TRACK_PEAK=' "$destTagFile" | cut -d '=' -f 2 )" - echo "$trackGain" > "$trackGainFile" - echo "$trackPeak" > "$trackPeakFile" - saveSeconds + trackGain="$( echo "$line" | cut -d '|' -f 1 )" + trackGain="${trackGain#+}" + decimals="${trackGain#*.}" + trackGain="${trackGain%.*}.${decimals:0:2}" + trackPeak="$( echo "$line" | cut -d '|' -f 2 )" + trackPeak="$( echo "scale=8; $trackPeak / 32767" | bc )" + if [ "${trackPeak:0:1}" = '.' ]; then + trackPeak="0${trackPeak}" + fi + if [ -n "$trackGain" -a -n "$trackPeak" ]; then + vorbiscomment -l "$copyFile" | grep -ivE '^replaygain_' 2>> "$errorLogFile" > "$sourceTagFile" || ec=$EX_KO + if [ $ec -eq $EX_OK ]; then + processSourceTagFile + destTagFile="${TDIR}/${i}.vc.txt" + cp "$sourceTagFile" "$destTagFile" >/dev/null 2>&1 + echo "$trackGain" > "$trackGainFile" + echo "$trackPeak" > "$trackPeakFile" + saveSeconds + fi + else + ec=$EX_KO fi fi ;; *.mp3) - line="$( mp3gain -e -k -o -q "$copyFile" 2>> "$errorLogFile" | fgrep -v 'MP3 gain' | tr '\t' '|' | tr -d '+' | cut -d '|' -f 3-4 )" ; ec=$? + if [ -L "$copyFile" ]; then # make a real copy so that APE metadata doesn't get added to the source + rm -f "$copyFile" >/dev/null 2>&1 && cp "$sourceFile" "$copyFile" >/dev/null 2>&1 ; ec=$? + fi if [ $ec -eq $EX_OK ]; then - trackGain="${line%|*}" - decimals="${trackGain#*.}" - trackGain="${trackGain%.*}.${decimals:0:2}" - trackPeak="${line#*|}"; trackPeak="${trackPeak%.*}" - trackPeak="$( echo "scale=8; $trackPeak / 32767" | bc )" - if [ "${trackPeak:0:1}" = '.' ]; then - trackPeak="0${trackPeak}" + line="$( mp3gain -e -k -o -q "$copyFile" 2>> "$errorLogFile" | grep -Fv 'MP3 gain' | tr '\t' '|' | tr -d '+' | cut -d '|' -f 3-4 )" ; ec=$? + if [ $ec -eq $EX_OK -a -n "$line" ]; then + trackGain="${line%|*}" + decimals="${trackGain#*.}" + trackGain="${trackGain%.*}.${decimals:0:2}" + trackPeak="${line#*|}"; trackPeak="${trackPeak%.*}" + trackPeak="$( echo "scale=8; $trackPeak / 32767" | bc )" + if [ "${trackPeak:0:1}" = '.' ]; then + trackPeak="0${trackPeak}" + fi + if [ -n "$trackGain" -a -n "$trackPeak" ]; then + if [ $applyGain = true -a "$applyGainType" = 'TRACK_PEAK' ]; then + trackGain="$( printf "%.2f" "$( echo "20 * (l(${trackPeak}) / l(10))" | bc -l )" )" + if [ "${trackGain:0:1}" = '-' ]; then + trackGain="${trackGain:1}" + if [ "${peakReference:0:1}" = '-' ]; then + trackGain="$( printf "%.2f" "$( echo "$trackGain + ($peakReference)" | bc -l )" )" + fi + elif [ "${peakReference:0:1}" = '-' ]; then + trackGain="$peakReference" + else + trackGain="-0" + fi + elif [ $applyGain = true -a "$applyGainType" = 'TRACK' -a -n "$preamp" ]; then + if [ "${preamp:0:1}" = '-' ]; then + trackGain="$( printf "%.2f" "$( echo "$trackGain - ${preamp:1}" | bc -l 2>/dev/null )" )" + elif [ "${preamp:0:1}" = '+' ]; then + trackGain="$( printf "%.2f" "$( echo "$trackGain + ${preamp:1}" | bc -l 2>/dev/null )" )" + fi + fi + echo "$trackGain" > "$trackGainFile" + echo "$trackPeak" > "$trackPeakFile" + saveSeconds || ec=$EX_KO + else + ec=$EX_KO + fi + else + ec=$EX_KO fi - echo "$trackGain" > "$trackGainFile" - echo "$trackPeak" > "$trackPeakFile" - lame --silent --decode "$copyFile" "$wavFile" >/dev/null 2>> "$errorLogFile" && - saveSeconds || ec=$EX_KO - rm -f "$wavFile" >/dev/null 2>&1 fi ;; - *) printf "${GR}%2u ${WG}WG ${CY}%s${NM}: unsupported format\n" $p "$sourceFilename" 1>&2; return $EX_KO ;; + *) printMessage 'warning' 'track_gain' 'unsupported' "file:${sourceFile}" $p 'unsupported format' ; return $EX_KO ;; esac else ec=$EX_KO @@ -4124,16 +6508,27 @@ saveGain 'TRACK' || ec=$EX_KO fi + if [ -e "$wavFile" ]; then rm -f "$wavFile" >/dev/null 2>&1; fi + if [ -e "$copyFile" ]; then rm -f "$copyFile" "${copyFile}c" >/dev/null 2>&1; fi + if [ $ec -eq $EX_OK ]; then if [ $verbose = true ]; then - sGain="$trackGain" - if [ "${trackGain:0:1}" != '-' ]; then + if [ -n "$applyGainType" ]; then + if [ "$applyGainType" = 'ALBUM' -o "$applyGainType" = 'TRACK' -o "$applyGainType" = 'ALBUM_PEAK' -o "$applyGainType" = 'TRACK_PEAK' ]; then + sGain="$trackGain" + else + sGain="$applyGainType" + fi + else + sGain="$trackGain" + fi + if [ "${sGain:0:1}" != '-' -a "${sGain:0:1}" != '+' ]; then sGain="+${sGain}" fi - printf "${GR}%2u ${OK}OK ${NM}%9s ${CY}%s${NM}\n" $p "$sGain dB" "$sourceFilename" + printMessage 'success' 'track_gain' $p "$sGain dB" "file:${sourceFile}" fi else - printf "${GR}%2u ${KO}ER ${CY}%s${NM}\n" $p "$sourceFilename" 1>&2 + printMessage 'error' 'track_gain' "file:${sourceFile}" $p fi return $ec } @@ -4158,7 +6553,8 @@ if unlink "$gainLockFile" 2>/dev/null; then prepareSource && computeTrackGain || ec=$EX_KO - rm -f "$copyFile" "${copyFile}c" >/dev/null 2>&1 + if [ -e "$wavFile" ]; then rm -f "$wavFile" >/dev/null 2>&1; fi + if [ -e "$copyFile" ]; then rm -f "$copyFile" "${copyFile}c" >/dev/null 2>&1; fi fi done @@ -4172,6 +6568,7 @@ for ((i=0; i<${#sourceFiles[@]}; i++)); do if [ ! -f "${TDIR}/${i}.trackgain" ]; then + printMessage 'error' 'album_gain' "Album gain" return $EX_KO fi tg="$( cat "${TDIR}/${i}.trackgain" )" @@ -4185,31 +6582,65 @@ echo "$tg" >> "${TDIR}/trackgains" ((N++)) done - cat "${TDIR}/${i}.trackpeak" >> "${TDIR}/trackpeaks" + cat "${TDIR}/${i}.trackpeak" >> "${TDIR}/trackpeaks" 2>/dev/null done - albumPeak="$( sort -n "${TDIR}/trackpeaks" | tail -n 1 )" + albumPeak="$( sort -n "${TDIR}/trackpeaks" 2>/dev/null | tail -n 1 )" + if [ -z "$albumPeak" ]; then + return $EX_KO + fi - sort -n "${TDIR}/trackgains" > "${TDIR}/trackgains.tmp" - mv "${TDIR}/trackgains.tmp" "${TDIR}/trackgains" + if [ $applyGain = true -a "$applyGainType" = 'ALBUM_PEAK' ]; then + albumGain="$( printf "%.2f" "$( echo "20 * (l(${albumPeak}) / l(10))" | bc -l )" )" + if [ "${albumGain:0:1}" = '-' ]; then + albumGain="${albumGain:1}" + if [ "${peakReference:0:1}" = '-' ]; then + albumGain="$( printf "%.2f" "$( echo "$albumGain + ($peakReference)" | bc -l )" )" + fi + elif [ "${peakReference:0:1}" = '-' ]; then + albumGain="$peakReference" + else + albumGain="-0" + fi + else + sort -n "${TDIR}/trackgains" > "${TDIR}/trackgains.tmp" 2>/dev/null + mv "${TDIR}/trackgains.tmp" "${TDIR}/trackgains" - rank="$( echo "scale=2; (${replaygain_percentile}/100) * $N + (1 / 2)" | bc )" - rank="$( printf "%.0f" "$rank" )" - albumGain="$( head -n $rank "${TDIR}/trackgains" | tail -n 1 )" + rank="$( echo "scale=2; (${replaygainPercentile}/100) * $N + (1 / 2)" | bc )" + rank="$( printf "%.0f" "$rank" )" + albumGain="$( head -n $rank "${TDIR}/trackgains" 2>/dev/null | tail -n 1 )" + if [ -z "$albumGain" ]; then + printMessage 'error' 'album_gain' "Album gain" + return $EX_KO + elif [ $applyGain = true -a "$applyGainType" = 'ALBUM' -a -n "$preamp" ]; then + if [ "${preamp:0:1}" = '-' ]; then + albumGain="$( printf "%.2f" "$( echo "$albumGain - ${preamp:1}" | bc -l 2>/dev/null )" )" + elif [ "${preamp:0:1}" = '+' ]; then + albumGain="$( printf "%.2f" "$( echo "$albumGain + ${preamp:1}" | bc -l 2>/dev/null )" )" + fi + fi + fi - for ((i=0; i<${#sourceFiles[@]}; i++)); do - sourceTagFile="${TDIR}/${i}.txt" - destFile="${sourceFiles[$i]}" - saveGain 'ALBUM' || ec=$EX_KO - done - - if [ $verbose = true ]; then - gain="$albumGain" - if [ "${gain:0:1}" != '-' ]; then - gain="+${gain}" + for ((i=0, j=1; i<${#sourceFiles[@]}; i++, j++)); do + if [ $j -le 8 ]; then + sourceTagFile="${TDIR}/${i}.txt" + destFile="${sourceFiles[$i]}" + saveGain 'ALBUM' & + else + wait + j=1 + sourceTagFile="${TDIR}/${i}.txt" + destFile="${sourceFiles[$i]}" + saveGain 'ALBUM' & fi - echo "Album gain: $gain dB" + done + wait + + gain="$albumGain" + if [ "${gain:0:1}" != '-' ]; then + gain="+${gain}" fi + printMessage 'success' 'album_gain' "${gain} dB" "Album gain: $gain dB" return $ec } @@ -4229,52 +6660,147 @@ getOutputCodecProps () { - case "$outputCodec" in - WAV) destExtension='wav' cname='WAV' ;; - FLAC) destExtension='flac' cname='FLAC' ;; - Flake) destExtension='flac' cname='Flake' ;; - WavPack) destExtension='wv' cname='WavPack' ;; - WavPackHybrid) destExtension='wv' cname='WavPack Hybrid' ;; - WavPackLossy) destExtension='wv' cname='WavPack Lossy' ;; - MonkeysAudio) destExtension='ape' cname="Monkey's Audio" ;; - TAK) destExtension='tak' cname="TAK" ;; - ALAC) destExtension='m4a' cname='ALAC' ;; - lossyWAV) destExtension='lossy.wav' cname='lossyWAV' ;; - lossyFLAC) destExtension='lossy.flac' cname='lossyFLAC' ;; - lossyWV) destExtension='lossy.wv' cname='lossyWV' ;; - lossyTAK) destExtension='lossy.tak' cname='lossyTAK' ;; - OggVorbis|WinVorbis) destExtension='ogg' cname='Ogg Vorbis' ;; - LAME|WinLAME) destExtension='mp3' cname='LAME' ;; - AAC) destExtension='m4a' cname='AAC' ;; - QAAC) destExtension='m4a' cname='QAAC' ;; - Musepack) destExtension='mpc' cname='Musepack' ;; - Opus) destExtension='opus' cname='Opus' ;; - esac + local outputCodec="$1" + + case "$outputCodec" in + WAV) destExtension='wav' cname='WAV' ;; + AIFF) destExtension='aiff' cname='AIFF' ;; + CAF) destExtension='caf' cname='CAF' ;; + FLAC) destExtension='flac' cname='FLAC' ;; + Flake) destExtension='flac' cname='Flake' ;; + WavPack) destExtension='wv' cname='WavPack' ;; + WavPackHybrid) destExtension='wv' cname='WavPack Hybrid' ;; + WavPackLossy) destExtension='wv' cname='WavPack Lossy' ;; + MonkeysAudio) destExtension='ape' cname="Monkey's Audio" ;; + TAK) destExtension='tak' cname="TAK" ;; + ALAC) destExtension='m4a' cname='ALAC' ;; + lossyWAV) destExtension='lossy.wav' cname='lossyWAV' ;; + lossyFLAC) destExtension='lossy.flac' cname='lossyFLAC' ;; + lossyWV) destExtension='lossy.wv' cname='lossyWV' ;; + lossyTAK) destExtension='lossy.tak' cname='lossyTAK' ;; + OggVorbis) destExtension='ogg' cname='Ogg Vorbis' ;; + WinVorbis) destExtension='ogg' cname='Win Vorbis' ;; + LAME) destExtension='mp3' cname='LAME' ;; + WinLAME) destExtension='mp3' cname='Win LAME' ;; + AAC) destExtension='m4a' cname='AAC' ;; + QAAC) destExtension='m4a' cname='QAAC' ;; + Musepack) destExtension='mpc' cname='Musepack' ;; + Opus) destExtension='opus' cname='Opus' ;; + esac } -getDestFile () +getFileProps () { + local outputCodec="$2" + + sourceFile="$1" sourceFilename="${sourceFile##*/}" sourceDirname="$( dirname "$sourceFile" )" sourceBasename="${sourceFilename%.*}" - if [ "$outputCodec" = 'WAV' -a "$sourceBasename" != "${sourceBasename%.lossy}" ]; then - destExtension='lossy.wav' + if [ "$sourceBasename" != "${sourceBasename%.lossy}" ]; then + sourceIsLossyWAV=true + else + sourceIsLossyWAV=false fi sourceBasename="${sourceBasename%.lossy}"; sourceExtension="${sourceFilename##*.}" - if [ $copyPath = true ]; then - destPath="${destDir}/${sourceDirname#/}" - destFile="${destPath}/${sourceBasename}.${destExtension}" + + if [ -n "$outputCodec" ]; then + getOutputCodecProps "$outputCodec" + getDestDir "$outputCodec" + if [ "$outputCodec" = 'WAV' -a $sourceIsLossyWAV = true ]; then + destExtension='lossy.wav' + fi + destFilename="${sourceBasename}.${destExtension}" + if [ $copyPath = true ]; then + destPath="${destDir}/${sourceDirname#/}" + destFile="${destPath}/${destFilename}" + else + destPath="$destDir" + destFile="${destDir}/${destFilename}" + fi + fi +} + +printMachineStats () +{ + local f c destExtension dest bcmd seconds slist bytes_compressed cname duration='0' rate='' + + seconds="$( printf 'scale=6; %.6f - %.6f\n' "$time2" "$time1" | bc )" + if [ "$seconds" = '0' ]; then seconds='1'; fi # prevent division by 0 situations + if [ -f "${TDIR}/durations" ]; then + duration="$( { echo -n 'scale=2; ' ; cat "${TDIR}/durations" ; echo ; } | bc )" + if [ "$duration" != '0' ]; then + duration="$( printf "%.0f" "$duration" )" + rate="$( echo "scale=1; $duration / $seconds" | bc )" + fi + fi + + if [ -z "$outputCodecs" ]; then + if [ -n "$rate" ]; then + printMessage 'info' "${rate}x" + fi + return + fi + + if [ -n "$rate" ]; then + printMessage 'info' "${rate}x" + fi + + if [ "$outputCodecs" = 'WAV' ]; then return; fi + + bcmd='' + for outputCodec in $outputCodecs; do + for sourceFile in "${sourceFiles[@]}"; do + getFileProps "$sourceFile" "$outputCodec" + if [ -e "$destFile" ]; then + bcmd="${bcmd}\x00${destFile//%/%%}" + if [ -e "${destFile}c" ]; then # WavPack correction file + bcmd="${bcmd}\x00${destFile//%/%%}c" + fi + fi + done + done + + if [ "$OS" = 'Linux' ]; then + bytes_compressed="$( { echo -n 'scale=0; 0'; printf "%s${bcmd}" ' + %s' | xargs -0 stat -L --printf; echo; } | bc )" else - destFile="${destDir}/${sourceBasename}.${destExtension}" + bytes_compressed="$( { echo -n 'scale=0; 0'; printf "%s${bcmd}" ' + %z' | xargs -0 stat -L -n -f; echo; } | bc )" fi + + for outputCodec in $outputCodecs; do + bcmd='' + for sourceFile in "${sourceFiles[@]}"; do + getFileProps "$sourceFile" "$outputCodec" + if [ -e "$destFile" ]; then + bcmd="${bcmd}\x00${destFile//%/%%}" + if [ -e "${destFile}c" ]; then # WavPack correction file + bcmd="${bcmd}\x00${destFile//%/%%}c" + fi + fi + done + + if [ "$OS" = 'Linux' ]; then + bytes_compressed="$( { echo -n 'scale=0; 0'; printf "%s${bcmd}" ' + %s' | xargs -0 stat -L --printf; echo; } | bc )" + else + bytes_compressed="$( { echo -n 'scale=0; 0'; printf "%s${bcmd}" ' + %z' | xargs -0 stat -L -n -f; echo; } | bc )" + fi + + if [ $duration -gt 0 ]; then + bitrate_compressed="$( echo "scale=0; (${bytes_compressed} * 8) / ${duration}" | bc )" + printMessage 'info' "bitrate_${outputCodec}" "${bitrate_compressed}bps" + fi + done + + return $EX_OK } -printStats () +printHumanStats () { local f c destExtension dest bcmd seconds slist bytes_wav bytes_compressed imbsec ombsec mib_compressed ratio cname duration='0' rate='' readDuration seconds="$( printf 'scale=6; %.6f - %.6f\n' "$time2" "$time1" | bc )" + if [ "$seconds" = '0' ]; then seconds='1'; fi # prevent division by 0 situations if [ -f "${TDIR}/durations" ]; then duration="$( { echo -n 'scale=2; ' ; cat "${TDIR}/durations" ; echo ; } | bc )" if [ "$duration" != '0' ]; then @@ -4282,11 +6808,15 @@ rate="$( echo "scale=1; $duration / $seconds" | bc )" fi fi - printf '%.2f seconds' $seconds + printf "${GR} * ${NM}%.2f seconds" $seconds readDuration="$( { echo -n 'scale=6; ' ; cat "${TDIR}/readTimes" ; echo ; } | bc )" - if [ "$readDuration" = '0' ]; then - readDuration="$seconds" + if [ "$readDuration" = '0' ]; then # prevent division by 0 situations + if [ "$seconds" = '0' ]; then + readDuration='1' + else + readDuration="$seconds" + fi fi for f in "${sourceFiles[@]}"; do @@ -4313,9 +6843,8 @@ bcmd='' for outputCodec in $outputCodecs; do - getOutputCodecProps for sourceFile in "${sourceFiles[@]}"; do - getDestFile + getFileProps "$sourceFile" "$outputCodec" if [ -e "$destFile" ]; then bcmd="${bcmd}\x00${destFile//%/%%}" if [ -e "${destFile}c" ]; then # WavPack correction file @@ -4343,9 +6872,7 @@ bytes_wav="$( { echo -n 'scale=0; 0' ; cat "${TDIR}/bytes" ; echo ; } | bc )" mib_source="$( echo "scale=3; $bytes_source / 1048576" | bc )" mib_uncompressed="$( echo "scale=3; $bytes_wav / 1048576" | bc )" - mib_diff="$( diffStr $mib_uncompressed $mib_source ' MiB' )" ratio="$( echo "scale=3; $bytes_source * 100 / $bytes_wav" | bc )" - ratio_diff="$( diffStr 100 $ratio '%' )" if [ $duration -gt 0 ]; then bitrate_source="$( echo "scale=0; (${bytes_source} * 8) / ${duration} / 1000" | bc )" @@ -4353,14 +6880,14 @@ bitrate_source='?' fi - printf '\n%-15s %6.1f MiB %6.1f%%\n%-15s %6.1f MiB (%11s) %5.1f%% (%7s) %7s kbps\n' \ - 'WAV:' "$mib_uncompressed" '100' 'Source:' "$mib_source" "$mib_diff" "$ratio" "$ratio_diff" "$bitrate_source" + # each item is separated by 4 spaces + printf '\n%-15s %6.1f MiB %5.1f%%' 'WAV:' "$mib_uncompressed" '100' + printf '\n%-15s %6.1f MiB %5.1f%% %4s kbps\n' 'Source:' "$mib_source" "$ratio" "$bitrate_source" for outputCodec in $outputCodecs; do - getOutputCodecProps bcmd='' for sourceFile in "${sourceFiles[@]}"; do - getDestFile + getFileProps "$sourceFile" "$outputCodec" if [ -e "$destFile" ]; then bcmd="${bcmd}\x00${destFile//%/%%}" if [ -e "${destFile}c" ]; then # WavPack correction file @@ -4382,18 +6909,99 @@ fi mib_compressed="$( echo "scale=3; $bytes_compressed / 1048576" | bc )" - mib_to_source_diff="$( diffStr $mib_source $mib_compressed ' MiB' )" ratio="$( echo "scale=3; $bytes_compressed * 100 / $bytes_wav" | bc )" - compressed_to_source_ratio="$( echo "scale=3; $bytes_compressed * 100 / $bytes_source" | bc )" - ratio_to_source_diff="$( diffStr 100 $compressed_to_source_ratio '%' )" - printf '%-15s %6.1f MiB (%11s) %5.1f%% (%7s) %7s kbps\n' \ - "${cname}:" $mib_compressed "$mib_to_source_diff" "$ratio" "$ratio_to_source_diff" "$bitrate_compressed" + # each item is separated by 4 spaces + printf '%-15s %6.1f MiB %5.1f%% %4s kbps\n' "${cname}:" $mib_compressed "$ratio" "$bitrate_compressed" done return $EX_OK } +setDestDir () +{ + local destDir="$1" outputCodec="$2" + + if [ "$outputCodec" = 'WAV' -o -z "$dir_WAV" ]; then dir_WAV="$destDir"; fi + if [ "$outputCodec" = 'AIFF' -o -z "$dir_AIFF" ]; then dir_AIFF="$destDir"; fi + if [ "$outputCodec" = 'CAF' -o -z "$dir_CAF" ]; then dir_CAF="$destDir"; fi + if [ "$outputCodec" = 'FLAC' -o -z "$dir_FLAC" ]; then dir_FLAC="$destDir"; fi + if [ "$outputCodec" = 'Flake' -o -z "$dir_Flake" ]; then dir_Flake="$destDir"; fi + if [ "$outputCodec" = 'WavPack' -o -z "$dir_WavPack" ]; then dir_WavPack="$destDir"; fi + if [ "$outputCodec" = 'WavPackHybrid' -o -z "$dir_WavPackHybrid" ]; then dir_WavPackHybrid="$destDir"; fi + if [ "$outputCodec" = 'WavPackLossy' -o -z "$dir_WavPackLossy" ]; then dir_WavPackLossy="$destDir"; fi + if [ "$outputCodec" = 'MonkeysAudio' -o -z "$dir_MonkeysAudio" ]; then dir_MonkeysAudio="$destDir"; fi + if [ "$outputCodec" = 'TAK' -o -z "$dir_TAK" ]; then dir_TAK="$destDir"; fi + if [ "$outputCodec" = 'ALAC' -o -z "$dir_ALAC" ]; then dir_ALAC="$destDir"; fi + if [ "$outputCodec" = 'lossyWAV' -o -z "$dir_lossyWAV" ]; then dir_lossyWAV="$destDir"; fi + if [ "$outputCodec" = 'lossyFLAC' -o -z "$dir_lossyFLAC" ]; then dir_lossyFLAC="$destDir"; fi + if [ "$outputCodec" = 'lossyWV' -o -z "$dir_lossyWV" ]; then dir_lossyWV="$destDir"; fi + if [ "$outputCodec" = 'lossyTAK' -o -z "$dir_lossyTAK" ]; then dir_lossyTAK="$destDir"; fi + if [ "$outputCodec" = 'LAME' -o -z "$dir_LAME" ]; then dir_LAME="$destDir"; fi + if [ "$outputCodec" = 'WinLAME' -o -z "$dir_WinLAME" ]; then dir_WinLAME="$destDir"; fi + if [ "$outputCodec" = 'AAC' -o -z "$dir_AAC" ]; then dir_AAC="$destDir"; fi + if [ "$outputCodec" = 'QAAC' -o -z "$dir_QAAC" ]; then dir_QAAC="$destDir"; fi + if [ "$outputCodec" = 'OggVorbis' -o -z "$dir_OggVorbis" ]; then dir_OggVorbis="$destDir"; fi + if [ "$outputCodec" = 'WinVorbis' -o -z "$dir_WinVorbis" ]; then dir_WinVorbis="$destDir"; fi + if [ "$outputCodec" = 'Musepack' -o -z "$dir_Musepack" ]; then dir_Musepack="$destDir"; fi + if [ "$outputCodec" = 'Opus' -o -z "$dir_Opus" ]; then dir_Opus="$destDir"; fi +} + +getDestDir () +{ + local outputCodec="$1" + + destDir='' + case "$outputCodec" in + WAV) destDir="$dir_WAV" ;; + AIFF) destDir="$dir_AIFF" ;; + CAF) destDir="$dir_CAF" ;; + FLAC) destDir="$dir_FLAC" ;; + Flake) destDir="$dir_Flake" ;; + WavPack) destDir="$dir_WavPack" ;; + WavPackHybrid) destDir="$dir_WavPackHybrid" ;; + WavPackLossy) destDir="$dir_WavPackLossy" ;; + MonkeysAudio) destDir="$dir_MonkeysAudio" ;; + TAK) destDir="$dir_TAK" ;; + ALAC) destDir="$dir_ALAC" ;; + lossyWAV) destDir="$dir_lossyWAV" ;; + lossyFLAC) destDir="$dir_lossyFLAC" ;; + lossyWV) destDir="$dir_lossyWV" ;; + lossyTAK) destDir="$dir_lossyTAK" ;; + LAME) destDir="$dir_LAME" ;; + WinLAME) destDir="$dir_WinLAME" ;; + AAC) destDir="$dir_AAC" ;; + QAAC) destDir="$dir_QAAC" ;; + OggVorbis) destDir="$dir_OggVorbis" ;; + WinVorbis) destDir="$dir_WinVorbis" ;; + Musepack) destDir="$dir_Musepack" ;; + Opus) destDir="$dir_Opus" ;; + esac +} + +getNumberOfCpuCores () +{ + local x lastCpuID currentCpuID totalNumberOfCores=0 + + while read line; do + x="$( echo "$line" | tr -cd '0-9' 2>/dev/null )" + case "$line" in + 'physical id'*) + lastCpuID="$currentCpuID" + currentCpuID="$x" + ;; + + 'cpu cores'*) + if [ "$currentCpuID" != "$lastCpuID" ]; then + totalNumberOfCores=$(( totalNumberOfCores + x )) + fi + ;; + esac + done < <( grep -E '^(physical id|cpu cores)' '/proc/cpuinfo' 2>/dev/null ) + + echo $totalNumberOfCores +} + # main() ======================================================================= if [ -n "$LC_ALL" ]; then @@ -4403,30 +7011,62 @@ export LANG='en_US.UTF-8' export LC_NUMERIC='C' -checkBinaries - -OS="$( uname -s )" -nProcesses=2 maxProcesses=16 -if [ -e '/proc/cpuinfo' ]; then - maxProcesses="$( fgrep 'cpu MHz' /proc/cpuinfo | wc -l )" - nProcesses=$maxProcesses - if [ $maxProcesses -eq 0 ]; then - nProcesses=2 maxProcesses=16 - fi - ((maxProcesses++)) -elif [ "$OS" = 'Darwin' ]; then # Mac OS X - maxProcesses="$( system_profiler -detailLevel full SPHardwareDataType | fgrep 'Total Number of Cores:' | cut -d ':' -f 2 | tr -d ' ' )" - nProcesses=$maxProcesses - if [ -z "$maxProcesses" ]; then - nProcesses=2 maxProcesses=16 +sedcmd='gsed' datecmd='date' gnudate=false +if which 'uname' >/dev/null 2>&1 ; then + OS="$( uname -s )" + if [ "$OS" = 'Linux' ]; then + sedcmd='sed' gnudate=true + elif which 'gdate' >/dev/null 2>&1; then + datecmd='gdate' gnudate=true fi - ((maxProcesses++)) fi -destDir="$PWD" copyPath=false outputCodecs='' lastCodec='' nCodecs=0 copyWAV=false verbose=true checkFiles=false +checkBinaries +if which 'wine64' >/dev/null 2>&1; then + gotWine64=true +else + gotWine64=false +fi + +maxProcesses='' +if which 'nproc' >/dev/null 2>&1; then # GNU Coreutils installed + maxProcesses="$( nproc 2>/dev/null )" +elif which 'gnproc' >/dev/null 2>&1; then # GNU Coreutils installed on OS Ⅹ + maxProcesses="$( gnproc 2>/dev/null )" +elif [ -e '/proc/cpuinfo' ]; then # Linux + maxProcesses="$( grep -cF 'cpu MHz' /proc/cpuinfo 2>/dev/null )" +elif [ "$OS" = 'Darwin' ]; then # OS Ⅹ + # Many thanks to Tobias Link for helping me port caudec to OS Ⅹ + maxProcesses="$( system_profiler -detailLevel full SPHardwareDataType 2>/dev/null | grep -F 'Total Number of Cores:' 2>/dev/null | cut -d ':' -f 2 2>/dev/null | tr -d ' ' 2>/dev/null )" +fi + +case "$maxProcesses" in + [1-9]*) + if [ -e '/proc/cpuinfo' ]; then + nProcesses="$( getNumberOfCpuCores )" + if [ $nProcesses -eq $maxProcesses ]; then # no hyperthreading + ((maxProcesses++)) # for good measure + elif [ $maxProcesses -le 4 ]; then # got hyperthreading on a CPU with 1 or 2 cores + nProcesses=$maxProcesses + fi + else + nProcesses=$maxProcesses + ((maxProcesses++)) # for good measure + fi + ;; + + *) + nProcesses=2 + maxProcesses=3 + ;; +esac + +destDir='' copyPath=false outputCodecs='' lastCodec='' nCodecs=0 copyWAV=false verbose=true checkFiles=false nLossyWAV=0 copyLossyWAV=false -bitDepth='' samplingRate='' preserveMetadata='' computeReplaygain=false actionHash=false applyGain=false applyGainType='' computeSoundcheck=true +bitDepth='' samplingRate='' preserveMetadata='' computeReplaygain=false actionHash=false applyGain=false applyGainType='' computeSoundcheck=false soundcheckMode='' convertToStereo='false' +keepExistingFiles=false keepNewerFiles=false macHasVerify=false outputMode='human' deleteSourceFiles=false +peakReference=0 if [ "$calledAs" = 'decaude' ]; then lastCodec='WAV' @@ -4471,7 +7111,13 @@ getBitrateMode 'OggVorbis' "$OggVorbis_MODE" 'caudecrc' getBitrateMode 'Opus' "$Opus_MODE" 'caudecrc' -while getopts 'sn:o:O:P:tdc:C:H:q:b:B:r:gG:S:huV' o ; do +if which 'mac' >/dev/null 2>&1; then + if mac 2>&1 | grep -F 'Verify:' >/dev/null 2>&1; then # mac has the patch that adds the -v parameter + macHasVerify=true + fi +fi + +while getopts 'zsn:o:O:P:kKDtdc:C:H:q:b:B:r:2gG:S:huV' o ; do case $o in s) verbose=false ;; @@ -4481,43 +7127,70 @@ if [ $OPTARG -le $maxProcesses ]; then nProcesses=$OPTARG else - echo "$me -n: the number of processes must be an integer between 1 and $maxProcesses" 1>&2; exit $EX_USAGE + printMessage 'error' 'usage' 'bad_value' "$me -n: the number of processes must be an integer between 1 and $maxProcesses" ; exit $EX_USAGE fi ;; - *) echo "$me -n: the number of processes must be an integer between 1 and $maxProcesses" 1>&2; exit $EX_USAGE ;; + *) printMessage 'error' 'usage' 'bad_value' "$me -n: the number of processes must be an integer between 1 and $maxProcesses" ; exit $EX_USAGE ;; esac ;; o) destDir="$OPTARG" - if [ "${destDir%/}" != "$destDir" ]; then + if [ "$destDir" != '/' -a "${destDir%/}" != "$destDir" ]; then destDir="${destDir%/}" fi if [ ! -e "$destDir" ]; then - echo "$me -o \"$destDir\": directory doesn't exist. Either create it manually, or try again with -O." 1>&2; exit $EX_CANTCREAT + printMessage 'error' 'usage' 'filesystem' "path:${destDir}" "$me -o: directory doesn't exist. Either create it manually, or try again with -O." ; exit $EX_CANTCREAT elif [ ! -d "$destDir" ]; then - echo "$me -o \"$destDir\": not a directory." 1>&2; exit $EX_CANTCREAT + printMessage 'error' 'usage' 'filesystem' "path:${destDir}" "$me -o: not a directory." ; exit $EX_CANTCREAT elif [ ! -w "$destDir" ]; then - echo "$me -o \"$destDir\": directory is not writable." 1>&2; exit $EX_CANTCREAT + printMessage 'error' 'usage' 'filesystem' "path:${destDir}" "$me -o: directory is not writable (permission denied)." ; exit $EX_CANTCREAT fi + setDestDir "$destDir" "$lastCodec" + unset destDir ;; O|P) destDir="$OPTARG" - if [ "${destDir%/}" != "$destDir" ]; then + if [ "$destDir" != '/' -a "${destDir%/}" != "$destDir" ]; then destDir="${destDir%/}" fi if [ ! -e "$destDir" ]; then if ! mkdir -p "$destDir" >/dev/null 2>&1 ; then - echo "$me -O/P \"$destDir\": failed to create directory." 1>&2; exit $EX_CANTCREAT + printMessage 'error' 'usage' 'filesystem' "path:${destDir}" "$me -O/P: failed to create directory (do you have write permissions?)." ; exit $EX_CANTCREAT fi elif [ ! -d "$destDir" ]; then - echo "$me -O/P \"$destDir\": not a directory." 1>&2; exit $EX_CANTCREAT + printMessage 'error' 'usage' 'filesystem' "path:${destDir}" "$me -O/P: not a directory." ; exit $EX_CANTCREAT elif [ ! -w "$destDir" ]; then - echo "$me -O/P \"$destDir\": directory is not writable." 1>&2; exit $EX_CANTCREAT + printMessage 'error' 'usage' 'filesystem' "path:${destDir}" "$me -O/P: directory is not writable (permission denied)." ; exit $EX_CANTCREAT fi if [ "$o" = 'P' ]; then copyPath=true; fi + setDestDir "$destDir" "$lastCodec" + unset destDir + ;; + + k) + keepExistingFiles=true + if [ $keepNewerFiles = true ]; then + printMessage 'error' 'usage' 'command_line' "$me -k: parameters -k and -K are mutually exclusive. Choose one or the other." ; exit $EX_USAGE + fi + ;; + + K) + keepNewerFiles=true + if [ $keepExistingFiles = true ]; then + printMessage 'error' 'usage' 'command_line' "$me -K: parameters -k and -K are mutually exclusive. Choose one or the other." ; exit $EX_USAGE + fi + ;; + + D) + case "$deleteSourceFiles" in + false) deleteSourceFiles='1' ;; + 1) deleteSourceFiles='2' ;; + 2) deleteSourceFiles='true' ;; + *) deleteSourceFiles='not true' ;; + esac ;; t) checkFiles=true ;; @@ -4525,6 +7198,8 @@ c|C) case "$OPTARG" in wav) lastCodec='WAV' copyWAV=true ;; + aiff) lastCodec='AIFF' ;; + caf) lastCodec='CAF' ;; flac) lastCodec='FLAC' ;; flake) lastCodec='Flake' ;; wv) lastCodec='WavPack' ;; @@ -4539,13 +7214,13 @@ lossyTAK|lossytak) lastCodec='lossyTAK'; ((nLossyWAV++)) ;; mp3|lame) lastCodec='LAME' ;; winlame) lastCodec='WinLAME' ;; - m4a|aac) lastCodec='AAC' ;; + aac) lastCodec='AAC' ;; qaac) lastCodec='QAAC' ;; ogg|vorbis) lastCodec='OggVorbis' ;; winvorbis) lastCodec='WinVorbis' ;; mpc|musepack) lastCodec='Musepack' ;; opus) lastCodec='Opus' ;; - *) echo "$me -c: invalid codec (try $me -h)" 1>&2; exit $EX_USAGE ;; + *) printMessage 'error' 'usage' 'bad_value' "$me -c: invalid codec (try $me -h)" ; exit $EX_USAGE ;; esac outputCodecs="$outputCodecs $lastCodec"; ((nCodecs++)) if [ "$o" = 'c' ]; then @@ -4557,20 +7232,24 @@ H) case "$OPTARG" in - crc|CRC) hashes="${hashes}CRC " ;; + crc32|CRC32) hashes="${hashes}CRC32 " ;; md5|MD5) hashes="${hashes}MD5 " ;; sha1|SHA1) hashes="${hashes}SHA1 " ;; - ^crc|^CRC) hashes="${hashes//CRC/}" ;; + sha256|SHA256) hashes="${hashes}SHA256 " ;; + sha512|SHA512) hashes="${hashes}SHA512 " ;; + ^crc32|^CRC32) hashes="${hashes//CRC32/}" ;; ^md5|^MD5) hashes="${hashes//MD5/}" ;; ^sha1|^SHA1) hashes="${hashes//SHA1/}" ;; - *) echo "$me -H: hash algorithm must be one of crc, md5 or sha1" 1>&2; exit $EX_USAGE ;; + ^sha256|^SHA256) hashes="${hashes//SHA256/}" ;; + ^sha512|^SHA512) hashes="${hashes//SHA512/}" ;; + *) printMessage 'error' 'usage' 'bad_value' "$me -H: hash algorithm must be one of CRC32, MD5, SHA1, SHA256 or SHA512" ; exit $EX_USAGE ;; esac actionHash=true ;; q) if [ -z "$lastCodec" ]; then - echo "$me -q: you must specify a codec first (-c)" 1>&2; exit $EX_USAGE + printMessage 'error' 'usage' 'command_line' "$me -q: you must specify a codec first (-c)" ; exit $EX_USAGE fi case "$lastCodec" in @@ -4586,14 +7265,14 @@ OggVorbis|WinVorbis) getCompressionSetting 'OggVorbis' "$OPTARG" ; OggVorbis_MODE='VBR' ;; Musepack) getCompressionSetting 'Musepack' "$OPTARG" ;; Opus) getCompressionSetting 'Opus' "$OPTARG" ; Opus_MODE='VBR' ;; - *) echo "$me -q: parameter not available with the selected codec" 1>&2; exit $EX_USAGE ;; + *) printMessage 'error' 'usage' 'command_line' "$me -q: parameter not available with the selected codec" ; exit $EX_USAGE ;; esac ;; b) if [ -z "$lastCodec" ]; then case "$OPTARG" in 16|24) bitDepth=$OPTARG ;; - *) echo "$me -b: bit depth must be either 16 or 24" 1>&2; exit $EX_USAGE + *) printMessage 'error' 'usage' 'bad_value' "$me -b: bit depth must be either 16 or 24" ; exit $EX_USAGE esac else case "$lastCodec" in @@ -4603,14 +7282,14 @@ OggVorbis|WinVorbis) getConstantBitrate 'OggVorbis' "$OPTARG" ; OggVorbis_MODE='CBR' ;; AAC) getConstantBitrate 'AAC' "$OPTARG" ; AAC_MODE='CBR' ;; QAAC) getConstantBitrate 'QAAC' "$OPTARG" ; QAAC_MODE='CBR' ;; - *) echo "$me -b: parameter not available with the selected codec" 1>&2; exit $EX_USAGE ;; + *) printMessage 'error' 'usage' 'command_line' "$me -b: parameter not available with the selected codec" ; exit $EX_USAGE ;; esac fi ;; B) if [ -z "$lastCodec" ]; then - echo "$me -B: you must select a codec first (-c)" 1>&2; exit $EX_USAGE + printMessage 'error' 'usage' 'command_line' "$me -B: you must select a codec first (-c)" ; exit $EX_USAGE fi case "$lastCodec" in LAME|WinLAME) getAverageBitrate 'LAME' "$OPTARG" ; LAME_MODE='ABR' ;; @@ -4618,50 +7297,90 @@ AAC) getAverageBitrate 'AAC' "$OPTARG" ; AAC_MODE='ABR' ;; QAAC) getAverageBitrate 'QAAC' "$OPTARG" ; QAAC_MODE='ABR' ;; Opus) getAverageBitrate 'Opus' "$OPTARG" ; Opus_MODE='ABR' ;; - *) echo "$me -B: parameter not available with the selected codec" 1>&2; exit $EX_USAGE ;; + *) printMessage 'error' 'usage' 'command_line' "$me -B: parameter not available with the selected codec" ; exit $EX_USAGE ;; esac ;; r) if [ -n "$lastCodec" -a "$calledAs" != 'decaude' ]; then - echo "$me -r: parameter must be given before -c" 1>&2; exit $EX_USAGE + printMessage 'error' 'usage' 'command_line' "$me -r: parameter must be given before -c" ; exit $EX_USAGE fi case "$OPTARG" in 44|44.1) samplingRate=44100 ;; 88|88.2) samplingRate=88200 ;; 176|176.4) samplingRate=176400 ;; - 48|96|192) samplingRate="${OPTARG}000" ;; - 44100|48000|88200|96000|176400|192000) samplingRate=$OPTARG ;; - cd|CD) bitDepth=16 samplingRate=44100 ;; + 352|352.8) samplingRate=352800 ;; + 48|96|192|384) samplingRate="${OPTARG}000" ;; + 44100|48000|88200|96000|176400|192000|352800|384000) samplingRate=$OPTARG ;; + cd|CD) bitDepth=16 samplingRate=44100 convertToStereo='flat' ;; dvd|DVD) bitDepth=16 samplingRate=48000 ;; sacd|SACD) bitDepth=24 samplingRate=88200 ;; dvda|DVDA|dvdaudio|DVDAUDIO|DVDAudio|bd|BD|bluray|BLURAY|BluRay) bitDepth=24 samplingRate=96000 ;; pono|Pono|PONO) bitDepth=24 samplingRate=192000 ;; - *) echo "$me -r: sampling rate must be one of 44[100], 48[000], 88[200], 96[000], 176[400], 192[000], cd, dvd, sacd, dvda, bluray or pono" 1>&2; exit $EX_USAGE + dxd|DXD) bitDepth=24 samplingRate=352800 ;; + *) printMessage 'error' 'usage' 'bad_value' "$me -r: sampling rate must be one of 44[100], 48[000], 88[200], 96[000], 176[400], 192[000], 352[800], 384[000], cd, dvd, sacd, dvda, bluray, pono or dxd" ; exit $EX_USAGE esac ;; + 2) convertToStereo='flat' ;; + g) computeReplaygain=true ;; G) if [ -z "$lastCodec" ]; then computeReplaygain=true fi - applyGain=true case "$OPTARG" in - album) applyGainType='ALBUM' ;; - track) applyGainType='TRACK' ;; - *) echo "$me -G: gain type must be either album or track" 1>&2; exit $EX_USAGE + album) gainValue='ALBUM' ;; + track) gainValue='TRACK' ;; + albumpeak) gainValue='ALBUM_PEAK' ;; + trackpeak) gainValue='TRACK_PEAK' ;; + +[0-9]|+[1-9][0-9]) gainValue="${OPTARG}.00" ;; # integer + +[0-9].[0-9]|+[1-9][0-9].[0-9]) gainValue="${OPTARG}0" ;; # floating point, 1 decimal + +[0-9].[0-9][0-9]|+[1-9][0-9].[0-9][0-9]) gainValue="$OPTARG" ;; # floating point, 2 decimals + -[0-9]|-[1-9][0-9]) gainValue="${OPTARG}.00" ;; # integer + -[0-9].[0-9]|-[1-9][0-9].[0-9]) gainValue="${OPTARG}0" ;; # floating point, 1 decimal + -[0-9].[0-9][0-9]|-[1-9][0-9].[0-9][0-9]) gainValue="$OPTARG" ;; # floating point, 2 decimals + *) printMessage 'error' 'usage' 'bad_value' "$me -G: gain type must be either album or track, albumpeak or trackpeak, or a signed number between -99.99 and +99.99" ; exit $EX_USAGE esac + if [ $applyGain = true ]; then + case "$applyGainType" in + ALBUM|TRACK) + preamp="$gainValue" + ;; + + ALBUM_PEAK|TRACK_PEAK) + if [ "${gainValue:0:1}" = '-' ]; then + peakReference="$gainValue" + fi + ;; + esac + else + applyGainType="$gainValue" + fi + applyGain=true + if [ "${gainValue:1}" = '0.00' ]; then + printMessage 'error' 'usage' 'bad_value' "$me -G: gain value must not be equal to 0" ; exit $EX_USAGE + fi + unset gainValue ;; S) computeReplaygain=true computeSoundcheck=true case "$OPTARG" in - album) soundcheckMode="ALBUM" ;; - track) soundcheckMode="TRACK" ;; - *) echo "$me -S: gain type must be either album or track" 1>&2; exit $EX_USAGE + album) soundcheckMode='ALBUM' ;; + track) soundcheckMode='TRACK' ;; + +[0-9]|+[1-9][0-9]) soundcheckMode="${OPTARG}.00" ;; # integer + +[0-9].[0-9]|+[1-9][0-9].[0-9]) soundcheckMode="${OPTARG}0" ;; # floating point, 1 decimal + +[0-9].[0-9][0-9]|+[1-9][0-9].[0-9][0-9]) soundcheckMode="$OPTARG" ;; # floating point, 2 decimals + -[0-9]|-[1-9][0-9]) soundcheckMode="${OPTARG}.00" ;; # integer + -[0-9].[0-9]|-[1-9][0-9].[0-9]) soundcheckMode="${OPTARG}0" ;; # floating point, 1 decimal + -[0-9].[0-9][0-9]|-[1-9][0-9].[0-9][0-9]) soundcheckMode="$OPTARG" ;; # floating point, 2 decimals + *) printMessage 'error' 'usage' 'bad_value' "$me -S: gain type must be either album or track, or a signed number between -99.99 and +99.99" ; exit $EX_USAGE esac + if [ "${soundcheckMode:1}" = '0.00' ]; then + printMessage 'error' 'usage' 'bad_value' "$me -S: gain value must not be equal to 0" ; exit $EX_USAGE + fi ;; h) printUsage; exit $EX_OK ;; @@ -4670,59 +7389,87 @@ V) echo "$me $VERSION"; exit $EX_OK ;; - *) echo "Try '$me -h' for more information." 1>&2; exit $EX_USAGE ;; + z) outputMode='machine' ;; + + *) printMessage 'error' 'usage' 'command_line' "Try '$me -h' for more information." ; exit $EX_USAGE ;; esac done shift $(( OPTIND - 1 )) if [ $# -lt 1 ]; then - printUsage 1>&2 - exit $EX_USAGE + if [ "$outputMode" = 'machine' ]; then + printMachineSyntax + exit $EX_OK + else + printUsage 1>&2 + exit $EX_USAGE + fi fi -if [ "$outputCodecs" = 'WAV' ]; then +outputCodecs="${outputCodecs# }" +if [ -z "$outputCodecs" ]; then + gotPCMCodecsOnly=false +else + gotPCMCodecsOnly=true + for outputCodec in $outputCodecs; do + case "$outputCodec" in + WAV|AIFF|CAF) continue ;; + *) gotPCMCodecsOnly=false ; break ;; + esac + done +fi +if [ $gotPCMCodecsOnly = true ]; then hashes='' else - hashCRC=false hashMD5=false hashSHA1=false + hashCRC32=false hashMD5=false hashSHA1=false hashSHA256=false hashSHA512=false hashes="${hashes// / }" if [ "${hashes:0:1}" = ' ' ]; then hashes="${hashes:1}"; fi hashes="${hashes% }" newHashes='' for h in $hashes; do case "$h" in - CRC) if [ $hashCRC = false ]; then newHashes="${newHashes}${h} "; hashCRC=true; fi ;; + CRC32) if [ $hashCRC32 = false ]; then newHashes="${newHashes}${h} "; hashCRC32=true; fi ;; MD5) if [ $hashMD5 = false ]; then newHashes="${newHashes}${h} "; hashMD5=true; fi ;; SHA1) if [ $hashSHA1 = false ]; then newHashes="${newHashes}${h} "; hashSHA1=true; fi ;; + SHA256) if [ $hashSHA256 = false ]; then newHashes="${newHashes}${h} "; hashSHA256=true; fi ;; + SHA512) if [ $hashSHA512 = false ]; then newHashes="${newHashes}${h} "; hashSHA512=true; fi ;; esac done hashes="${newHashes% }" fi +unset gotPCMCodecsOnly if [ -z "$hashes" ]; then actionHash=false; fi if [ -z "$outputCodecs" -a $checkFiles = false -a $computeReplaygain = false -a $actionHash = false ]; then - echo "$me: error: no action specified" 1>&2 + printMessage 'error' 'usage' 'command_line' "$me: no action specified" exit $EX_USAGE elif [ -n "$outputCodecs" -a $checkFiles = true ]; then - echo "$me: error: -c/-d and -t are mutually exclusive. Try again with either one alone." 1>&2 + printMessage 'error' 'usage' 'command_line' "$me: -c/-d and -t are mutually exclusive. Try again with either one alone." exit $EX_USAGE elif [ -n "$outputCodecs" -a $computeReplaygain = true ]; then - echo "$me: error: -c/-d and -g/-S are mutually exclusive. Try again with either one alone." 1>&2 + printMessage 'error' 'usage' 'command_line' "$me: -c/-d and -g/-S are mutually exclusive. Try again with either one alone." exit $EX_USAGE elif [ $checkFiles = true -a $computeReplaygain = true ]; then - echo "$me: error: -g/-G/-S and -t are mutually exclusive. Try again with either one alone." 1>&2 + printMessage 'error' 'usage' 'command_line' "$me: -g/-G/-S and -t are mutually exclusive. Try again with either one alone." exit $EX_USAGE elif [ $checkFiles = true -a $actionHash = true ]; then - echo "$me: error: -H and -t are mutually exclusive. Try again with either one alone." 1>&2 + printMessage 'error' 'usage' 'command_line' "$me: -H and -t are mutually exclusive. Try again with either one alone." exit $EX_USAGE elif [ $computeReplaygain = true -a $actionHash = true ]; then - echo "$me: error: -g/-G/-S and -H are mutually exclusive. Try again with either one alone." 1>&2 + printMessage 'error' 'usage' 'command_line' "$me: -g/-G/-S and -H are mutually exclusive. Try again with either one alone." exit $EX_USAGE fi -outputCodecs="${outputCodecs# }" nTracks=$# ec=$EX_OK -declare -a sourceFiles=("$@") +if [ -z "$dir_WAV" ]; then + setDestDir "$PWD" +fi + +nTracks=$# ec=$EX_OK +declare -a inputFilesAndDirs=("$@") +declare -a inputFiles=() +declare -a sourceFiles=() -checkInputFiles && checkBinaries && handleInstance && setupSwapdir +getInputFiles && checkInputFiles && checkBinaries && handleInstance && setupSwapdir #startTimer && checkInputFiles && stopTimer 'checkInputFiles()' && #startTimer && checkBinaries && stopTimer 'checkBinaries()' && #startTimer && handleInstance && stopTimer 'handleInstance()' && @@ -4735,17 +7482,23 @@ if [ -n "$outputCodecs" -a $tagCompressionSetting = true ]; then getEncoderVersions fi +getEyeD3Version if [ $nCodecs -gt 0 ]; then # action: transcode files + if [ "$deleteSourceFiles" != 'false' -a "$deleteSourceFiles" != 'true' ]; then + printMessage 'warning' 'usage' 'command_line' "$me -D: deletion parameter was not specified exactly 3 times, ignoring it" + fi + nJobs=$(( nTracks * nCodecs )) if [ $nProcesses -gt $nJobs ]; then setNProcesses $nJobs ; fi oProcesses=$nProcesses checkFreeSpace 'transcoding' - if [ "$OS" = 'Linux' ]; then - time1="$( date '+%s.%N' )" + if [ $gnudate = true ]; then + time1="$( $datecmd '+%s.%N' )" else time1="$( date '+%s' ).0" fi + monitorRamdiskSpace & for ((p=0; p<nProcesses; p++)); do transcode & done @@ -4753,53 +7506,86 @@ if [ $nProcesses -gt $nTracks ]; then setNProcesses $nTracks ; fi oProcesses=$nProcesses checkFreeSpace 'testing' - if [ "$OS" = 'Linux' ]; then - time1="$( date '+%s.%N' )" + if [ $gnudate = true ]; then + time1="$( $datecmd '+%s.%N' )" else time1="$( date '+%s' ).0" fi - for ((p=0; p<=nProcesses; p++)); do + monitorRamdiskSpace & + for ((p=0; p<nProcesses; p++)); do testFiles & done elif [ $computeReplaygain = true ]; then if [ $nProcesses -gt $nTracks ]; then setNProcesses $nTracks ; fi oProcesses=$nProcesses checkFreeSpace 'replaygain' - if [ "$OS" = 'Linux' ]; then - time1="$( date '+%s.%N' )" + if [ $gnudate = true ]; then + time1="$( $datecmd '+%s.%N' )" else time1="$( date '+%s' ).0" fi - for ((p=0; p<=nProcesses; p++)); do + monitorRamdiskSpace & + for ((p=0; p<nProcesses; p++)); do computeTrackGains & done elif [ $actionHash = true ]; then if [ $nProcesses -gt $nTracks ]; then setNProcesses $nTracks ; fi oProcesses=$nProcesses checkFreeSpace 'hashes' - if [ "$OS" = 'Linux' ]; then - time1="$( date '+%s.%N' )" + if [ $gnudate = true ]; then + time1="$( $datecmd '+%s.%N' )" else time1="$( date '+%s' ).0" fi - for ((p=0; p<=nProcesses; p++)); do + monitorRamdiskSpace & + for ((p=0; p<nProcesses; p++)); do computeHashes & done fi ec=$EX_OK ; for p in $( jobs -p ); do wait $p || ec=$EX_KO ; done if [ $computeReplaygain = true -a $ec -eq $EX_OK ]; then - computeAlbumGain || ec=$EX_KO + if [ $applyGain = false -o "${applyGainType%_*}" = 'ALBUM' ]; then + computeAlbumGain || ec=$EX_KO + fi fi -if [ "$OS" = 'Linux' ]; then - time2="$( date '+%s.%N' )" +if [ $gnudate = true ]; then + time2="$( $datecmd '+%s.%N' )" else time2="$( date '+%s' ).0" fi # print transcoding stats if applicable -if [ $verbose = true -a $ec -eq $EX_OK ]; then - printStats +if [ $ec -eq $EX_OK -a $verbose = true ]; then + if [ "$outputMode" = 'machine' ]; then + printMachineStats + else + printHumanStats + fi +fi + +if [ $nCodecs -gt 0 -a "$deleteSourceFiles" = 'true' ]; then + for ((i=0; i<${#sourceFiles[@]}; i++)); do + sourceFile="${sourceFiles[$i]}" + for outputCodec in $outputCodecs; do + # make sure the destination file and the source file don't have the exact same path + getFileProps "$sourceFile" "$outputCodec" + if [ "$sourceFile" -ef "$destFile" ]; then # source and destination files are one and the same + continue 2 + fi + done + + while read errorFile; do + if [ "$sourceFile" = "$errorFile" ]; then + continue 2 + fi + done < "${TDIR}/transcodingErrorFiles" + + if [ -w "$sourceFile" ]; then # don't delete read-only files + rm -f "$sourceFile" >/dev/null 2>&1 + fi + done fi + cleanExit $ec
View file
caudec-1.6.2.tar.gz/caudecrc -> caudec-1.7.5.tar.gz/caudecrc
Changed
@@ -2,9 +2,13 @@ # save this file to either /etc/caudecrc or ~/.caudecrc -# please set the full path, don't use '~' as an abbreviation for "${HOME}" -WIN32PATH="${HOME}/.wine" -WIN64PATH="${HOME}/win64" +# Wine user directory paths: please set the full path, don't use '~' as an abbreviation for "${HOME}" +# Examples: +# WIN32PATH="${HOME}/.wine" +# WIN64PATH="${HOME/win64" +# Leave blank if you want caudec to search your home directory and find the paths automatically. +WIN32PATH="" +WIN64PATH="" # default compression levels (try 'caudec -c CODEC -q help' for a full list of possible values) compression_FLAC=5 # 0-8 @@ -20,6 +24,7 @@ bitrate_LAME=320 # 16-320, used with LAME_MODE='CBR' average_bitrate_LAME=256 # 8-310, used with LAME_MODE='ABR' LAME_MODE='VBR' # 'VBR' (variable bitrate), 'CBR' (constant bitrate) or 'ABR' (average bitrate) +ID3Padding=512 # size in bytes of padding to add to ID3v2 tags compression_AAC=0.5 # 0.0-1.0, used with AAC_MODE='VBR' bitrate_AAC=256 # 0-320, used with AAC_MODE='CBR' @@ -47,8 +52,11 @@ preventClipping=true # reduce volume to prevent clipping, when resampling or applying gain setCompilationFlagWithAlbumArtist=false # always true when ALBUMARTIST="Various Artists" regardless of this setting keepWavMetadata=false # set to true to keep WAV metadata with FLAC and WavPack -hashes='' # comma separated list of hashes to compute and tag (CRC, MD5 and / or SHA1) +hashes='' # comma separated list of hashes to compute and tag (CRC32, MD5, SHA1, SHA256 and / or SHA512) tagCompressionSetting=false # store the compression setting in metadata +ignoreUnsupportedFiles=false # set to true to prevent caudec from aborting when some of the input files are unsupported +enableColors=true # Enable colors in human-readable output +useBrightColors=true # If enableColors=true, use bright colors (instead of darker ones) # Whitelist / Blacklist: lists of comma separated tags # Example: 'artist, date, album, tracknumber, title, genre, replaygain_reference_loudness, replaygain_track_gain, replaygain_track_peak, replaygain_album_gain, replaygain_album_peak' @@ -69,7 +77,7 @@ # tracktotal <---> totaltracks # If you find a tag mismatch, or a missing tag name variation, please report it on the issues tracker: -# http://caudec.outpost.fr/redirect/bugs +# http://caudec.net/redirect/bugs # tagWhitelist: tags to keep when transcoding (all others are discarded - inactive if empty) tagWhitelist='' @@ -78,10 +86,11 @@ tagBlacklist='' # By default, 'caudec -u' sets a user agent string specifying which version of caudec you are running, -# as well as the name of your kernel and the architecture of your computer: 'caudec x.x.x / Linux / x86_64' +# as well as the name of your kernel, the architecture of your computer, and whether you are using a caudecrc file: +# 'caudec x.x.x / Linux / x86_64 / - / - / - / none' # If you set the following parameter to true, caudec will put some extra information in its user agent string: # CPU name, number of logical CPU cores, and total amount of RAM: -# 'caudec x.x.x / Linux / x86_64 / Intel(R) Core(TM) i7-2670QM CPU @ 2.20GHz / 8 / 7898' +# 'caudec x.x.x / Linux / x86_64 / Intel(R) Core(TM) i7-2670QM CPU @ 2.20GHz / 8 / 7898 / none' # This parameter defaults to false (don't send information about CPU and RAM). # To see your user agent string without contacting the server, run 'caudec -h' sendHardwareDetails=false
View file
caudec-1.6.2.tar.gz/install.sh -> caudec-1.7.5.tar.gz/install.sh
Changed
@@ -57,7 +57,8 @@ install -m 0755 'caudec' "$bindir" && rm -f "${bindir}/decaude" && ln -s 'caudec' "${bindir}/decaude" && -echo "caudec and decaude installed in $bindir. See $caudecrcPath for configuration." +install -m 0755 'APEv2' "$bindir" && +echo "caudec, decaude and APEv2 installed in $bindir. See $caudecrcPath for configuration." if [ "$caudecrcPath" = '/etc/caudecrc' ]; then echo "You may also copy /etc/caudecrc to ~/.caudecrc." fi
Locations
Projects
Search
Status Monitor
Help
Open Build Service
OBS Manuals
API Documentation
OBS Portal
Reporting a Bug
Contact
Mailing List
Forums
Chat (IRC)
Twitter
Open Build Service (OBS)
is an
openSUSE project
.