#!/usr/bin/env bash
# v3.3.4 - 2015/09/03 - Grozdan Nikolov <neutrino8@gmail.com>
# Re-encode audio files in a directory to another format
#
# audenc is free software ; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation ; either version 2 of the License, or
# (at your option) any later version.
#
# audenc is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY ; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program ; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA

export PATH=$PATH:/usr/local/bin

# Add some color
error() { echo -e "\e[1;31m$1\e[0;39;49m"; }
green() { echo -e "\e[1;32m$1\e[0;39;49m"; }

CONFIG="$HOME/.audenc"
VERSION="3.3.4"
CONFIG_VERSION="11"

config_func() {
cat<<EOF>>$CONFIG
##########################
# Config file for audenc #
##########################
#
# Do not delete/modify the line below!
#v=$CONFIG_VERSION

# Favorite text editor used for the -ec option
EDITOR="nano"

# Delete (y/n) the original file(s) after encoding?
DELORIG="n"

# Which encoder (nero/faac/fdk-aac/aacplusenc) to use for AAC encoding?
AACENC="faac"

# If using aacplusenc/fdk-aac encoder should we use MP4Box to store
# the audio inside an m4a container?
M4A="y"

# lame encoder options
LAMEOPTS="-V 2 -q 0"

# neroAacEnc encoder options
NEROOPTS="-q 0.63 -lc"

# fdk-aac (aac-enc) encoder options
FDKAACOPTS="-r 256000 -a 1"

# if using fdk-aac, which AAC mode to use?
# 2 = LC-AAC
# 5 = HE-AACv1
# 29 = HE-AACv2
AACMODE="2"

# aacplusenc encoder options
AACPLUSENCOPTS="45"

# faac encoder options
FAACOPTS="-q 140 --tns --mpeg-vers 4 -w"

# oggenc encoder options
OGGOPTS="-q 4"

# opusenc encoder options
OPUSOPTS="--bitrate 192"

# flac encoder options
FLACOPTS="-5"

# aften encoder options
AFTENOPTS="-b 448 -readtoeof 1"

# dcaenc encoder options
DCAOPTS="755000"

# amrenc encoder options
AMROPTS=""

# Nice/priority value for encoding
NVALUE="10"

# Paths to mplayer/mediainfo/encoders
MPLAYER="$(which mplayer 2>/dev/null)"
MEDIAINFO="$(which mediainfo 2>/dev/null)"
MP4BOX="$(which MP4Box 2>/dev/null)"
LAME="$(which lame 2>/dev/null)"
NERO="$(which neroAacEnc 2>/dev/null)"
NEROTAG="$(which neroAacTag 2>/dev/null)"
FAAC="$(which faac 2>/dev/null)"
AACPLUSENC="$(which aacplusenc 2>/dev/null)"
FDKAAC="$(which aac-enc 2>/dev/null)"
OGG="$(which oggenc 2>/dev/null)"
OPUS="$(which opusenc 2>/dev/null)"
FLAC="$(which flac 2>/dev/null)"
AFTEN="$(which aften 2>/dev/null)"
DCAENC="$(which dcaenc 2>/dev/null)"
AMRENC="$(which amrenc 2>/dev/null)"
EOF
}

if [ ! -f "$CONFIG" ]; then
	config_func
	error "-> Config file generated in '$CONFIG'"
	error "-> Please set things up in there before continuing"
	exit 1
else
	if [ "$(grep '^#v' "$CONFIG" | awk -F= '{print $2}')" != "$CONFIG_VERSION" ]; then
		rm -f "$CONFIG"
		config_func
		error "-> Config file '$CONFIG' updated!"
		error "-> Please recommit any prior changes made to it"
		exit 1
	else
		source "$CONFIG"
	fi
fi

CODEC="$(echo "$1" | awk -F: '{print $1}')"

case "$CODEC" in
	mp3|aac|amr|vorbis|opus|ac3|dts|flac|wav) true ;;
	"")
	error "Usage: $(basename $0) [[mp3|aac|amr|vorbis|opus|ac3|dts|flac|wav][:opts=\"<enc_opts>\"]] [-af <filters>] [indir] [outdir]"
	error "Use '$(basename $0) -h' for more information."
	exit 1
	;;
	-h)
	echo
	echo "audenc is a batch shell script for encoding audio files in directories from one format to another."
	echo "It uses MPlayer for decoding and filtering, MediaInfo for tagging and external audio encoders for"
	echo "encoding. The mode of operation is shown in the examples below."
	echo
	green "Example 1: $(basename $0) mp3 /path/to/indir /path/to/outdir"
	green "Example 2: $(basename $0) mp3:opts=\"--cbr -b320\" /path/to/indir /path/to/outdir"
	green "Example 3: $(basename $0) mp3 -af volume=5,lavcresample=44100 /path/to/indir /path/to/outdir"
	green "Example 4: $(basename $0) mp3:opts=\"--cbr -b320\" -af volume=5 /path/to/indir /path/to/outdir"
	echo
	echo "audenc uses a configuration file stored in '$HOME/.audenc' where the user can"
	echo "set the paths to the executables and encoder options. As audenc is a batch script,"
	echo "it does not accept individual files but operates on directories instead. For more"
	echo "information, please read the man page of the script (man audenc)."
	echo
	echo "Options"
	echo "~~~~~~~"
	echo " -r     Reset configuration file. Useful for when the user has installed an audio encoder"
	echo "        after the script has generated its config file and thus cannot find the newly"
	echo "        installed encoder."
	echo
	echo " -ec    Edit the config file."
	echo
	echo " -sc    Perform a sanity check."
	echo
	echo " -h     Display this help message."
	echo
	echo " -v     Display version info."
	echo
	exit 0
	;;
	-v)
	echo "audenc $VERSION"
	exit 0
	;;
	-r)
	rm -f "$CONFIG"
	config_func
	green "-> config file resetted"
	exit 0
	;;
	-ec)
	if [ -z "$EDITOR" ]; then
		error "-> Variable 'EDITOR' in config file is empty!"
		exit 1
	fi
	if [ -x "$(which "$EDITOR" 2>/dev/null)" ]; then
		$EDITOR "$CONFIG"
	else
		error "-> Specified editor '$EDITOR' does not exist!"
		exit 1
	fi
	exit 0
	;;
	-sc)
	echo
	echo "-> Variable EDITOR is set to..........'$(green "$EDITOR")'"
	echo "-> Variable DELORIG is set to.........'$(green "$DELORIG")'"
	echo "-> Variable AACENC is set to..........'$(green "$AACENC")'"
	echo "-> Variable M4A is set to.............'$(green "$M4A")'"
	echo "-> Variable AACPLUSENCOPTS is set to..'$(green "$AACPLUSENCOPTS")'"
	echo "-> Variable FDKAACOPTS is set to .....'$(green "$FDKAACOPTS")'"
	echo "-> Variable LAMEOPTS is set to........'$(green "$LAMEOPTS")'"
	echo "-> Variable NEROOPTS is set to........'$(green "$NEROOPTS")'"
	echo "-> Variable FAACOPTS is set to........'$(green "$FAACOPTS")'"
	echo "-> Variable OGGOPTS is set to.........'$(green "$OGGOPTS")'"
	echo "-> Variable OPUSOPTS is set to........'$(green "$OPUSOPTS")'"
	echo "-> Variable FLACOPTS is set to........'$(green "$FLACOPTS")'"
	echo "-> Variable AFTENOPTS is set to.......'$(green "$AFTENOPTS")'"
	echo "-> Variable DCAOPTS is set to.........'$(green "$DCAOPTS")'"
	echo "-> Variable NVALUE is set to..........'$(green "$NVALUE")'"
	echo
	test -x "$MPLAYER" && STATUS="$(green "OK")" || STATUS="$(error "Failed")"
	echo "-> Checking for MPlayer...............$STATUS"
	test -x "$MEDIAINFO" && STATUS="$(green "OK")" || STATUS="$(error "Failed")"
	echo "-> Checking for MediaInfo.............$STATUS"
	test -x "$MP4BOX" && STATUS="$(green "OK")" || STATUS="$(error "Failed")"
	echo "-> Checking for MP4Box................$STATUS"
	test -x "$NERO" && STATUS="$(green "OK")" || STATUS="$(error "Failed")"
	echo "-> Checking for neroAacEnc............$STATUS"
	test -x "$NEROTAG" && STATUS="$(green "OK")" || STATUS="$(error "Failed")"
	echo "-> Checking for neroAacTag............$STATUS"
	test -x "$FAAC" && STATUS="$(green "OK")" || STATUS="$(error "Failed")"
	echo "-> Checking for faac..................$STATUS"
	test -x "$AACPLUSENC" && STATUS="$(green "OK")" || STATUS="$(error "Failed")"
	echo "-> Checking for aacplusenc............$STATUS"
	test -x "$FDKAAC" && STATUS="$(green "OK")" || STATUS="$(error "Failed")"
	echo "-> Checking for aac-enc (fdk-aac).....$STATUS"
	test -x "$OGG" && STATUS="$(green "OK")" || STATUS="$(error "Failed")"
	echo "-> Checking for oggenc................$STATUS"
	test -x "$OPUS" && STATUS="$(green "OK")" || STATUS="$(error "Failed")"
	echo "-> Checking for opusenc...............$STATUS"
	test -x "$FLAC" && STATUS="$(green "OK")" || STATUS="$(error "Failed")"
	echo "-> Checking for flac..................$STATUS"
	test -x "$AFTEN" && STATUS="$(green "OK")" || STATUS="$(error "Failed")"
	echo "-> Checking for aften.................$STATUS"
	test -x "$DCAENC" && STATUS="$(green "OK")" || STATUS="$(error "Failed")"
	echo "-> Checking for dcaenc................$STATUS"
	test -x "$AMRENC" && STATUS="$(green "OK")" || STATUS="$(error "Failed")"
	echo "-> Checking for amrenc................$STATUS"
	echo
	echo "If you have installed a required program but the script"
	echo "can't find it, run '$(basename $0) -r' to reset the config file."
	echo
	exit 0
	;;
	*)
	error "-> Unknown option '$1'"
	exit 1
	;;
esac

# Overwrite encoder options from config file
# in case user provides them on the command line

OPTS="$(echo "$1" | sed "s|$CODEC:||; s|=.*||")"

if [ "$OPTS" = "opts" ]; then
	ENCOPTS="$(echo "$1" | sed "s|$CODEC:opts=||")"
	case "$CODEC" in
		mp3)	LAMEOPTS="$ENCOPTS" ;;
		aac)	NEROOPTS="$ENCOPTS"; FAACOPTS="$ENCOPTS"; AACPLUSENCOPTS="$ENCOPTS"; FDKAACOPTS="$ENCOPTS" ;;
		vorbis)	OGGOPTS="$ENCOPTS" ;;
		opus)	OPUSOPTS="$ENCOPTS" ;;
		ac3)	AFTENOPTS="$ENCOPTS" ;;
		dts)	DCAOPTS="$ENCOPTS" ;;
		flac)	FLACOPTS="$ENCOPTS" ;;
	esac
fi

# If ran under cron, in order not to
# collide if we're still running when
# cron executes us again, we just exit

RUNFILE="/tmp/audenc.run"
test ! -w "$(dirname "$RUNFILE")" && RUNFILE="$HOME/.audenc.run"

if [ -e "$RUNFILE" ]; then
	error "-> Error: $(basename $0) is already running ($RUNFILE)"
	exit 1
else
	touch "$RUNFILE"
fi

exit_func() {
	test -p "$INPUT" && rm -f "$INPUT"
	rm -f "$RUNFILE"; RET=$?
	case "$1" in
		conf)	error "-> Error: check config in '$CONFIG'"; RET=1 ;;
		idir)	error "-> Error: input dir not specified or doesn't exist"; RET=1 ;;
		odir)	error "-> Error: output dir not specified"; RET=1 ;;
		ird)	error "-> Error: input dir '$INDIR' not readable by user '$(id -un)'"; RET=1 ;;
		owrt)	error "-> Error: output dir '$OUTDIR' not writable by user '$(id -un)'"; RET=1 ;;
		mkfld)	error "-> Error: could not create output dir"; RET=1 ;;
		pipe)	error "-> Error: could not create named pipe in '$(dirname "$INPUT")'"; RET=1 ;;
	esac
	exit $RET
}

trap 'exit_func' SIGHUP SIGINT SIGQUIT SIGKILL SIGABRT SIGFPE SIGSEGV SIGTERM SIGPIPE SIGIO

# Do some checks
test -x "$MPLAYER" || exit_func conf
test -x "$MEDIAINFO" || exit_func conf
test -n "$NVALUE" || exit_func conf
test "$2" = "-af" && AUDFILTERS="-af $3,format=s16le"

if [ -n "$AUDFILTERS" ]; then
	# Input dir
	test ! -d "$4" && exit_func idir || IN_DIR="$4"
	# Output dir
	if [ -n "$5" ]; then
		if [ ! -d "$5" ]; then
			mkdir -p "$5" 2>/dev/null
			test $? != 0 && exit_func mkfld
			OUT_DIR="$5"
		else
			OUT_DIR="$5"
		fi
	else
		exit_func odir
	fi
else
	# Input dir
	test ! -d "$2" && exit_func idir || IN_DIR="$2"
	# Output dir
	if [ -n "$3" ]; then
		if [ ! -d "$3" ]; then
			mkdir -p "$3" 2>/dev/null
			test $? != 0 && exit_func mkfld
			OUT_DIR="$3"
		else
			OUT_DIR="$3"
		fi
	else
		exit_func odir
	fi
fi

test -n "$(echo "$IN_DIR" | grep '^/')" && INDIR="$IN_DIR" || INDIR="$(pwd)/$IN_DIR"
test -n "$(echo "$OUT_DIR" | grep '^/')" && OUTDIR="$OUT_DIR" || OUTDIR="$(pwd)/$OUT_DIR"

# Only root can use negative nice values
if [ "$UID" != "0" ]; then
	test -n "$(echo "$NVALUE" | grep '-')" && exit_func conf
fi

case "$CODEC" in
	mp3)	test -x $LAME || exit_func conf; ext="mp3" ;;
	aac)
	case "$AACENC" in
		nero|Nero|NERO)		test -x $NERO -a -x $NEROTAG || exit_func conf; ext="m4a" ;;
		faac|Faac|FAAC)		test -x $FAAC || exit_func conf; ext="m4a" ;;
		aacplusenc|AACPLUSENC|fdk-aac|FDK-AAC|fdkaac|FDKAAC)
		case "$AACENC" in
			aacplusenc|AACPLUSENC)
			test -x $AACPLUSENC || exit_func conf
			;;
			fdk-aac|FDK-AAC|fdkaac|FDKAAC)
			test -x $FDKAAC || exit_func conf
			;;
		esac
		if [ "$M4A" = "y" ]; then
			test -x $MP4BOX -a -x $NEROTAG || exit_func conf
		fi
		ext="aac"
		;;
		""|*) exit_func conf ;;
	esac
	;;
	vorbis)	test -x $OGG || exit_func conf; ext="ogg" ;;
	opus)	test -x $OPUS || exit_func conf; ext="opus" ;;
	ac3)	test -x $AFTEN || exit_func conf; ext="ac3" ;;
	dts)	test -x $DCAENC || exit_func conf; ext="dts" ;;
	flac)	test -x $FLAC || exit_func conf; ext="flac" ;;
	amr)	test -x $AMRENC || exit_func conf; ext="amr" ;; 
	wav)	ext="wav" ;;
esac

# Are the dirs readable/writable?
test -r "$INDIR" || exit_func ird
test -w "$OUTDIR" || exit_func owrt

# The real thing...

encode_func() {
	# Output file
	OUTPUT="$OUTDIR/${i%.*}_$$.$ext"
	
	# Tags
	case "$CODEC" in
		wav|ac3|dts|amr)
		# Not supported so do nothing
		true
		;;
		*)
		ARTIST="$($MEDIAINFO --Inform="General;%Performer%" "$i")"
		TITLE="$($MEDIAINFO --Inform="General;%Title%" "$i")"
		TRACK="$($MEDIAINFO --Inform="General;%Track/Position%" "$i")"
		TOTAL_TRACKS="$($MEDIAINFO --Inform="General;%Track/Position_Total%" "$i")"
		GENRE="$($MEDIAINFO --Inform="General;%Genre%" "$i")"
		ALBUM="$($MEDIAINFO --Inform="General;%Album%" "$i")"
		YEAR="$($MEDIAINFO --Inform="General;%Recorded_Date%" "$i")"
		DISC="$($MEDIAINFO --Inform="General;%Part/Position%" "$i")"
		COMMENT="$($MEDIAINFO --Inform="General;%Comment%" "$i")"
		test -z "$YEAR" && YEAR="$($MEDIAINFO --Inform="General;%Encoded_Date%" "$i")"
		test -z "$YEAR" && YEAR="$($MEDIAINFO --Inform="General;%DATE%" "$i")"
		test -z "$DISC" && DISC="$($MEDIAINFO --Inform="General;%Part%" "$i")"
		;;
	esac
	
	# Default to signed 16-bit WAV format
	if [ -z "$AUDFILTERS" ]; then
		AUDFILTERS="-af format=s16le"
	fi
	
	# Decode - We use a named pipe in order
	# not to dump potentionally large WAV
	# files to disk, except when we're
	# decoding to WAV as when requested
	# by the user.
	case "$CODEC" in
		wav)
		$MPLAYER "$i" -vo null -vc dummy $AUDFILTERS -channels 8 -ao pcm:fast:file="$OUTPUT"
		;;
		*)
		# Input for encoders
		INPUT="$OUTDIR/.pipe$$"
		if [ ! -p "$INPUT" ]; then
			mkfifo "$INPUT" 2>/dev/null
			if [ $? != 0 ]; then
				INPUT="$HOME/.pipe$$"
				mkfifo "$INPUT" 2>/dev/null
				test $? != 0 && exit_func pipe
			fi
		fi
		case "$CODEC" in
			mp3|amr) CHANNELS="-channels 2" ;;
			aac)
			case "$AACENC" in
				aacplusenc|AACPLUSENC)	CHANNELS="-channels 2" ;;
				*)			CHANNELS="-channels 6" ;;
			esac
			;;
			*) CHANNELS="-channels 6" ;;
		esac
		$MPLAYER "$i" -vo null -vc dummy -really-quiet $AUDFILTERS $CHANNELS -ao pcm:fast:file="$INPUT" &
		;;
	esac
	
	# Encode
	case "$CODEC" in
		mp3)	nice -n $NVALUE $LAME $LAMEOPTS --add-id3v2 --ta "$ARTIST" --tt "$TITLE" --tn "$TRACK/$TOTAL_TRACKS" --tg "$GENRE" --tl "$ALBUM" --ty "$YEAR" --tc "$COMMENT" "$INPUT" "$OUTPUT" ;;
		aac)
		case "$AACENC" in
			nero|Nero|NERO)
			nice -n $NVALUE $NERO $NEROOPTS -ignorelength -if "$INPUT" -of "$OUTPUT"
			$NEROTAG -meta:artist="$ARTIST" -meta:title="$TITLE" -meta:track="$TRACK" -meta:totaltracks="$TOTAL_TRACKS" -meta:genre="$GENRE" -meta:album="$ALBUM" -meta:disc="$DISC" -meta:comment="$COMMENT" -meta:year="$YEAR" "$OUTPUT"
			;;
			faac|Faac|FAAC)
			nice -n $NVALUE $FAAC $FAACOPTS --artist "$ARTIST" --title "$TITLE" --track "$TRACK/$TOTAL_TRACKS" --genre "$GENRE" --album "$ALBUM" --disc "$DISC" --comment "$COMMENT" --year "$YEAR" "$INPUT" -o "$OUTPUT"
			;;
			aacplusenc|AACPLUSENC|fdk-aac|fdkaac|FDKAAC|FDK-AAC)
			case "$AACENC" in
				fdk-aac|fdkaac|FDKAAC|FDK-AAC)
				nice -n $NVALUE $FDKAAC $FDKAACOPTS -t $AACMODE "$INPUT" "$OUTPUT"
				if [ "$AACMODE" = "5" -o "$AACMODE" = "29" ]; then
					SBR="-sbr"
				fi
				;;
				aacplusenc|AACPLUSENC)
				nice -n $NVALUE $AACPLUSENC "$INPUT" "$OUTPUT" $AACPLUSENCOPTS
				SBR="-sbr"
				;;
			esac
			if [ "$M4A" = "y" ]; then
				m4a="y"
				$MP4BOX -add "$OUTPUT" $SBR -new "${OUTPUT%.*}.m4a"
				rm -f "$OUTPUT" 2>/dev/null
				$NEROTAG -meta:artist="$ARTIST" -meta:title="$TITLE" -meta:track="$TRACK" -meta:totaltracks="$TOTAL_TRACKS" -meta:genre="$GENRE" -meta:album="$ALBUM" -meta:disc="$DISC" -meta:comment="$COMMENT" -meta:year="$YEAR" "${OUTPUT%.*}.m4a"
			fi
			;;
		esac
		;;
		vorbis)	nice -n $NVALUE $OGG $OGGOPTS -a "$ARTIST" -t "$TITLE" -N "$TRACK" -c Tracktotal="$TOTAL_TRACKS" -G "$GENRE" -l "$ALBUM" -c Disc="$DISC" -c Comment="$COMMENT" -d "$YEAR" "$INPUT" -o "$OUTPUT" ;;
		opus)	nice -n $NVALUE $OPUS $OPUSOPTS --artist "$ARTIST" --title "$TITLE" --genre "$GENRE" --album "$ALBUM" "$INPUT" "$OUTPUT" ;;
		ac3)	nice -n $NVALUE $AFTEN $AFTENOPTS "$INPUT" "$OUTPUT" ;;
		dts)	nice -n $NVALUE $DCAENC "$INPUT" "$OUTPUT" $DCAOPTS ;;
		flac)	nice -n $NVALUE $FLAC $FLACOPTS -T Artist="$ARTIST" -T Title="$TITLE" -T Track="$TRACK" -T Tracktotal="$TOTAL_TRACKS" -T Genre="$GENRE" -T Album="$ALBUM" -T Disc="$DISC" -T Comment="$COMMENT" -T Year="$YEAR" "$INPUT" -o "$OUTPUT" ;;
		amr)	nice -n $NVALUE $AMRENC "$INPUT" "$OUTPUT" ;;
	esac
	test "$DELORIG" = "y" -o "$DELORIG" = "Y" && rm -f "$i"
	if [ "$m4a" = "y" ]; then
		mv -f "${OUTPUT%.*}.m4a" "$OUTDIR/${i%.*}.m4a"
	else
		mv -f "$OUTPUT" "$OUTDIR/${i%.*}.$ext"
	fi
}

cd "$INDIR"

for i in *; do
	if [ -f "$i" ]; then
		case "$(echo "${i##*.}" | tr '[:upper:]' '[:lower:]')" in
			mp1|mp2|mp3|mpa|ogg|m4a|mp4a|aac|aa3|alac|atrac|oga|mka|flac|ape|wma|ac3|eac3|opus|dts|dtshd|wav|fla|aiff|midi|mid|ra|au|amr) encode_func ;;
			vob|mpg|mpg1|mpg2|mpeg|mpeg1|mpeg2|avi|mkv|mp4|m4v|ts|m2ts|mts|wmv|flv|mov|ogm|ogv|ogx|asf|divx|h264|3gp|3gp2|3gpp|3gpp2|rm|nut) encode_func ;;
			*) error "-> Skipping unsupported file format: ${i##*.} - $(basename "$i")" ;;
		esac
	fi
done

exit_func
