bash
script=`basename "$0"` version='1.1'
function Usage {
version; cat <<EOF
USAGE
$script [-rndR] [-o OUTDIR] [-l PLAYLIST] [-p [N]] [INDIR | FLACFILE]
$script [-H | -V]
EOF
} >&2
function Help {
if ((${1:-1})); then
Usage; cat <<EOF
OPTIONS
-p [N]: Start N parallel conversion jobs (default: $optprocs).
-o OUTDIR: Output directory for MP3 files (default: INDIR)
-d: Delete input FLAC files after successful conversion.
-l NAME: Create a simple playlist NAME.m3u for all .mp3-files under OUTDIR.
-n: Dry mode (just print what would be done).
-r: Find FLAC files recursively.
-V, -H: Print version information and help.
DESCRIPTION
Export lossless audio files from your multimedia archive for portable audio
devices which cannot play FLAC. This program finds all .flac-files in INDIR,
decompresses them to temporary .wav-files and then encodes .mp3-files under
OUTDIR.
Conversion is done parallely (default: $optmaxprocs jobs). LAME is used as the
MP3-encoder with options "-V 0 -q 1 --vbr-new". These options are equivalent to
"--preset fast extreme" with variable bitrate. Normally resulting files
therefore have a variable bitrate of ~245 kbps, and the file-size is reduced
about 70%.
These options produce "transparent" encoding. Most people can't distinguish the
MP3 from the original FLAC in an ABX blind test. For more information see
http://wiki.hydrogenaudio.org/index.php?title=LAME and
http://wiki.hydrogenaudio.org/index.php?title=Flac
Installation:
$ chmod a+x flactool # make the script executable
$ cp flactool /usr/local/bin # as root
$ cp flactool ~/bin # alternate location
Make sure the directory where flactool has been copied appears in PATH.
$ flactool -V
flactool version 1.0.4
REQUIREMENTS
bash, flac, metaflac, lame, id3
EXIT CODES
0 Indicates success to the shell.
-1 Indicates an unexpected error.
$E_MISSING Missing programs
$E_BADARGS Bad command-line arguments.
$E_BADDIR Invalid directory.
EOF
else
echo "$script $version"
fi
} >&2
E_FAILED=-1 E_MISSING=64 E_BADARGS=65 E_BADDIR=66
case "`uname`" in
CYGWIN*)
under_cygwin=1
CYGWIN=${CYGWIN:-tty notitle glob nontsec}
CYGWIN+=" winsymlinks";;
*) under_cygwin=0;;
esac
function panic {
cat <<EOF >&2
Panic! in shell $$, pipe $?, terminal `tty`: $*
$script exits with -1
EOF
exit $E_FAILED
}
optdry=0 optdel=0 optrecurse=0 optprocs=1 optin= optoutdir= optplaylist=
while getopts 'dnro:p:l:HV' opt; do
case $opt in
d) optdel=1;;
p) optprocs=$OPTARG;;
r) optrecurse=1;;
n) optdry=1;;
o) optoutdir="$OPTARG";;
l) optplaylist="$OPTARG";;
V) echo "$script $version"; exit 0;;
H) Help; exit 0;;
*) Usage; exit $E_BADARGS;;
esac
done
shift $(($OPTIND - 1))
optin=${1:-.}
{
if [[ ! -f $optin ]] && [[ ! -d $optin ]]; then
echo "$optin: file or directory not found" >&2
exit $E_BADDIR
fi
if [[ -n $optoutdir ]]; then
if ((optdel)); then
:
elif [[ ! -d $optoutdir ]]; then
echo "$optoutdir: directory not found" >&2
exit $E_BADDIR
fi
fi
} >&2
for prog in flac lame id3 metaflac; do
which "$prog" >/dev/null || {
echo "'$prog' program not found" >&2
exit $E_MISSING
}
done
find=$(which find)
if ((under_cygwin)); then
res=$($find --version)
if [ "$?" -ne "0" ]; then
cat <<EOF >&2
CAUTION: Find is '$find', the Microsoft 'find' utility. Does the
Cygwin-bin-directory comes first in PATH?
EOF
find=/usr/bin/find res=$($find --version)
if [ "$?" -ne "0" ]; then
panic "'$find' not found"
else
echo "WARNING: using '$find' explicitly" >&2
fi
fi
fi
res=`$find -regextype posix-egrep 2>&1 &>/dev/null`
have_gnu_find=1
if [ "$?" -ne "0" ]; then
echo "WARNING: '$find' does not understand '-regextype'" >&2
have_gnu_find=0
fi
if [[ -d $optin ]]; then
declare -a findopts ouropts
((optrecurse)) || findopts=(-maxdepth 1)
declare flaccount=$($find "$optin" "${findopts[@]}" -type f -iname '*.flac' | wc -l)
printf "$optin/: $flaccount .flac-file(s)\n"
if ((flaccount)); then
i=0
if [[ -n $optoutdir ]] && [[ $optoutdir != '.' ]]; then
ouropts[$((i++))]='-o'
ouropts[$((i++))]="$optoutdir"
fi
((optdel)) && ouropts[$((i++))]='-d'
((optdry)) && ouropts[$((i++))]='-n'
$find "$optin" "${findopts[@]}" -iname '*.flac' -print0 | \
xargs -0 -P $optprocs -I{} "$0" "${ouropts[@]}" "{}"
fi
if ((!optdry)); then
if [[ -n $optplaylist ]]; then
declare mp3count=$($find "$optoutdir" -type f -name '*.mp3' | wc -l)
printf "$optoutdir/: recurse, $mp3count .mp3-file(s)\n"
declare m3ufile="$optoutdir/${optplaylist}.m3u"
if ((mp3count)); then
printf "$m3ufile: "
[[ -f "$m3ufile" ]] && printf "refreshing" || printf "new"
printf " playlist from .mp3-files in this directory\n"
cat <<EOF >"$m3ufile"
# $m3ufile -- Created $(date) by $script $version
EOF
if ! $find -L "$optoutdir" -name '*.mp3' -printf "%f\n" >> "$m3ufile"; then
echo "$m3ufile: id3-program failed ($?)" >&2
exit $E_FAILED
fi
else
rm -vf "$m3ufile"
fi
fi
fi
else
declare flacfile="${optin}" mp3file="${optin%%.flac}.mp3"
declare -a lameopts=(-V 0 --vbr-new -q 1 -m j)
declare -a flacopts=(-d -c --silent)
[[ -n $optoutdir ]] && mp3file="${optoutdir}/$(basename "${mp3file}")"
printf "\n$flacfile ->\n$mp3file\n"
((optdry)) || {
if flac "${flacopts[@]}" "$flacfile" | \
lame "${lameopts[@]}" - "$mp3file"; then :
else
echo "$mp3file: flac/lame chain failed ($?)" >&2
exit $E_FAILED
fi
}
for tag in ARTIST TITLE ALBUM GENRE TRACKNUMBER DATE COMMENT; do
x=$(metaflac "$flacfile" --show-tag=$tag | sed s/.*=//g)
x=${x/\"}
eval $tag=\"$x\"
done
printf "\tGENRE=$GENRE ARTIST=$ARTIST TITLE=$TITLE ALBUM=$ALBUM DATE=$DATE\n"
((optdry)) || {
if ! id3 -t "$TITLE" -T "${TRACKNUMBER:-0}" \
-a "$ARTIST" -A "$ALBUM" -y "$DATE" -g "${GENRE:-12}" \
-c "$COMMENT" "$mp3file"; then
echo "$mp3file: id3-program failed ($?)" >&2
exit $E_FAILED
fi
}
((optdry)) || { ((optdel)) && { rm -v "$flacfile" || exit $E_FAILED; } }
fi