#!/bin/bash
#
# flactool -- Convert FLAC to MP3 files with maximum quality
#
# This script was originally made to convert archived FLAC files to audio players.
#
# Compatible with UNIX and Cygwin.
#
# Use "-H" to get help.
#
# Copyright 2007-2016. Distributed under the terms of the GNU General Public
# License v3.
#
# $Compile: ./flactool$
# $Maintained at: http://www.visualco.de$
# $Author: Andreas Spindler <info@andreasspindler.de>$
# $Writestamp: 2016-04-20 12:52:29$

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

########################################################################
# Environment, Arguments
#
E_FAILED=-1                                            # general
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 $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

# Find required programs. 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.

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

########################################################################
# Main routine
#
if [[ -d $optin ]]; then
    # Find all FLAC files and fork this script (again, parallely) on each to
    # convert to MP3.
    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

    # Create .m3u-playlist from .mp3-files (remove if no such files)
    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}")"

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

    printf "\n$flacfile ->\n$mp3file\n"
    ((optdry)) || {
        if flac "${flacopts[@]}" "$flacfile" | \
            lame "${lameopts[@]}" - "$mp3file"; then          # /dev/null 2 &1
            :
        else
            echo "$mp3file: flac/lame chain failed ($?)" >&2
            exit $E_FAILED
        fi
    }

    # lame does not copy ID3 tags. We use metaflac to extract SOME tags, then
    # id3 to write them into the new .mp3-file. Note that EXIFTOOL could be used
    # too. Genres list
    # 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)
        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
    }

    # Delete FLAC file.
    ((optdry)) || { ((optdel)) && { rm -v "$flacfile" || exit $E_FAILED; } }
fi

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