#!/bin/bash
#
# flactool -- Convert FLAC to MP3 files
#
# VENUE
#
# Motivation: export lossless audio files from your multimedia archive for
# portable audio devices which cannot play FLAC.
#
# DESCRIPTION
#
# Decompresses FLAC to WAV and then encodes WAV as MP3. The original FLAC files
# stay untouched, and  all files not matching "*.flac"  are ignored. Conversion
# can happen in multiple parallel jobs (default: 2).
#
# To convert WAV to  MP3 this schript utilizes LAME with "-V  0 -q 1 --vbr-new"
# which results  in a variable bitrate  of ~245 kbps. The  file-size is reduced
# about 70%.  These options  are equivalent to  "--preset fast  extreme". These
# settings  will normally produce  "transparent" encoding.  It means  that 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
#
# CONVERSION MODES
#
# Inplace (default): Move FLAC to MP3 in the same directory.
#
# Copy: Create MP3 in a separate directory.
#
# REQUIREMENTS
#
#     bash, flac, metaflac, lame, id3
#
# INSTALL
#
#     $ chmod a+x flactool
#     $ cp flactool /usr/local/bin
#     $ cp flactool ~/bin               # alternate location
#
#########################
# $Compile: ./flactool -o 'e:/MUSIC' '/cygdrive/h/av/album/6 Rock & Pop/Yello/Yello (1994) Zebra'$
# $Compile: ./flactool -n -o 'e:/MUSIC' '/cygdrive/h/av/album/6 Rock & Pop/Yello/Yello (1994) Zebra'$
# $Compile: find '/cygdrive/h/av/album/6 Rock & Pop/Yello/Yello (1994) Zebra' -iname '*.flac'$

script=`basename "$0"`
version='1.0.1'

########################################################################
# Portability / Environment / Options

E_MISSING=64                                           # missing programs
E_BADARGS=65                                           # bad argument format
E_BADDIR=66                                            # can't change directory

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 -1
}

function Ok {
    [ -z "$*" ] || echo "$*"; exit 0
}

function CroakArgs {
    Usage; exit $E_BADARGS
}

function CroakUnlessGnuFind {
    if ((!$have_gnu_find)); then
        echo "CAUTION: GNU find required" >&2
        exit $E_NOTGNU
    fi
}

function Version {
    cat <<EOF >&2
$script version $version
EOF
}

function Usage {
    Version; cat <<EOF >&2

Usage:
    $script [-o OUTDIR] [-n] [-r] [DIRNAME | FLACFILE]
    $script [-h | -V]

EOF
}

function Help {
    Usage; cat <<EOF >&2
Description:

Convert FLAC to high-quality MP3 files. When no input directory or file name is
specified converts all .flac-files in current directory.

This portable shell script should work on any UNIX and Windows (Cygwin) system.
Simply copy it into your path (e.g. to "/usr/local/bin" or "~/bin"). Maintained
at <http://www.visualco.de>.

Options:

    -p, -p N    Start N parallel conversion jobs (default: $optmaxprocs).
    -o DIR      Output directory for MP3 files. Default is to write each MP3 along
                with its source FLAC.
    -r          Find FLAC recursively.
    -V          Print version information.
    -h, -H      Print this information.

Prerequisites:

    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
}

optrecurse=0 optmaxprocs=2 optdry=0 optin= optout=
optmetacmd=id3

while getopts 'nro:P:hHV' opt; do
    case $opt in
        P) optmaxprocs=$OPTARG;;
        r) optrecurse=1;;
        n) optdry=1;;
        o) optout="$OPTARG";;
        P) optmaxprocs=$OPTARG;;
        V) Version; Ok;;
        [hH]) Help; Ok;;
        *) CroakArgs;;
    esac
done
shift $(($OPTIND - 1))
optin=${1:-.}

if [ -d "$optin" ]; then
    [ -z "$2" ] || {
        echo "Only one search directory allowed" >&2
        exit $E_BADDIR
    }
elif [ -f "$optin" ]; then
    ((!$optrecurse)) || CroakArgs
else
    echo "$optin: directory not found" >&2
    exit $E_BADDIR
fi

# A few Windows tools, such as find.exe, link.exe and sort.exe, may conflict
# with the Cygwin versions. Try the full path /usr/bin/find then.

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. Possibly the
         Cygwin-bin-directory does not come first in PATH (see "~/.bash_login"
         and "/etc/profile").
EOF
        find=/usr/bin/find res=`$find --version`
        if [ "$?" -ne "0" ]; then
            Panic "'$find' not found"
        else
            cat <<EOF >&2
WARNING: Using '$find' explicitly.
EOF
        fi
    fi
fi

res=`$find -regextype posix-egrep 2>&1 &>/dev/null`
if [ "$?" -ne "0" ]; then # some prehistoric find version
    cat <<EOF >&2;
WARNING: '$find' does not understand '-regextype'.
EOF
    have_gnu_find=0
else
    have_gnu_find=1
fi

########################################################################
# Main

if [[ -d "$optin" ]]
then
    # Find flac, lame, id3, metaflac.

    for prog in flac lame id3 metaflac
    do
        which "$prog" >/dev/null || {
            echo "'$prog' program not found" >&2
            exit $E_MISSING
        }
    done

    # Find all FLAC files and fork this script on each through xargs. Parallize
    # encoding.

    declare -a findopts
    (($optrecurse)) || findopts=(-maxdepth 1)
    ouropts=() i=0

    [ -n "$optout" ] && {
        ouropts[$((i++))]="-o"
        ouropts[$((i++))]="$optout"
    }
    (($optdry)) && ouropts[$((i++))]="-n"

    $find "$optin" "${findopts[@]}" -iname '*.flac' -print0 | {
        xargs -0 -P $optmaxprocs -I{} "$0" "${ouropts[@]}" "{}"
    }
else
    declare    base="${optin%%.flac}"
    declare    flacfile="${optin}" mp3file="${base}.mp3"
    declare -a lameopts=(-V 0 --vbr-new -q 1 -m j)
    declare -a flacopts=(--silent)

    [ -n "${optout}" ] && mp3file="${optout}/$(basename "${mp3file}")"

    cat <<EOF
Converting:
    $flacfile

EOF

    # FLAC -> WAV -> MP3
    # https://wiki.archlinux.org/index.php/Convert_Flac_to_Mp3

    (($optdry)) || {
        flac -d -c "${flacopts[@]}"   "$flacfile" |
        lame       "${lameopts[@]}" - "$mp3file"
    }

    # "lame" does not copy the existing ID3 tags to the new file. Therefore we
    # an utility such as "id3" or "exiftool".

    case "$optmetacmd" in
        id3)
            # Default genre #12 is "Other". See
            # <http://www.multimediasoft.com/amp3dj/help/index.html?amp3dj_00003e.htm>

            for tag in ARTIST TITLE ALBUM GENRE TRACKNUMBER DATE COMMENT
            do
                x="$(metaflac "$flacfile" --show-tag=$tag | sed s/.*=//g)"
                eval $tag=\"$x\"
            done
            cat <<EOF

Copying ID3 tags
    $ARTIST
    $ALBUM ($DATE)
    $TITLE
    $GENRE
to
    $mp3file

EOF

            (($optdry)) || {
                id3 -t "$TITLE" -T "${TRACKNUMBER:-0}" \
                    -a "$ARTIST" -A "$ALBUM" -y "$DATE" -g "${GENRE:-12}" \
                    -c "$COMMENT" \
                    "$mp3file"
                #id3 -lR "$mp3file"
            }
            ;;
    esac
fi

Ok

# Local Variables:
# coding: iso-8859-1-unix
# fill-column: 79
# End:

# flactool ends here