#!/bin/sh
#
# Copyright 2003-2024 Luke Mewburn <luke@mewburn.net>
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
# 1. Redistributions of source code must retain the above copyright
#    notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
#    notice, this list of conditions and the following disclaimer in the
#    documentation and/or other materials provided with the distribution.
# 3. The name of the author may not be used to endorse or promote products
#    derived from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
# IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
# OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
# TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
# USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

#
# flagtoogg, flactomp3, oggtomp3 --
#	Transcode between the appropriate formats, attempting to retain as
#	much of the appropriate metadata tags as possible.
#
#	http://www.mewburn.net/luke/src/flactoogg
#

# REQUIRES:
#	tool	format		url
#	----	------		---
#	ffmpeg	AAC/M4A		http://ffmpeg.mplayerhq.hu/
#	flac	FLAC		http://flac.sourceforge.net/
#	id3v2	MP3		http:://id3v2.sourceforge.net/
#	lame	MP3		http://lame.sourceforge.net/
#	ogg123	Ogg Vorbis	http://www.vorbis.com/	(vorbis-tools)

#
# TODO:
#	apetoflac
#	shntoflac
#	flactoaac tag support, if possible


# fail immediately on error
set -e

# wav_to_ogg <oggfile>
#	encode from .wav on stdin to ogg, writing to <oggfile>
#
wav_to_aac()
{
	ffmpeg -i - -ab 160kb "$1"
}

# wav_to_ogg <oggfile>
#	encode from .wav on stdin to ogg, writing to <oggfile>
#
wav_to_ogg()
{
	oggenc -Q -q 1 -o "$1" -
}

# wav_to_mp3 <mp3file>
#	encode from .wav on stdin to mp3, writing to <mp3file>
#
wav_to_mp3()
{
	lame -h /dev/stdin "$1"
}

# tag_mp3 <tagfile> <mp3file>
#	parse <tagfile> into ID3v2 tags and apply to <mp3file>
#
tag_mp3()
{
	parse_tags "$1"
	id3v2 -A "${album}" -a "${artist}" -t "${title}" -T "${track}" \
		-y "${year}" "$2"
}

# parse_tags <tagfile>
#	parse <tagfile> (containing key=val) into variables:
#		album
#		artist
#		title
#		track
#		year
#
parse_tags()
{
	album=
	artist=
	title=
	track=
	year=
	local tagvar
	local tarval
	while read -r tag; do
		tagvar="${tag%%=*}"
		tagval="${tag#*=}"
		case "${tagvar}" in
		ALBUM|album|Album)
			album="${tagval}"
			;;
		ARTIST|artist|Artist)
			artist="${tagval}"
			;;
		TITLE|title|Title)
			title="${tagval}"
			;;
		TRACKNUMBER|tracknumber|Tracknumber)
			track="${tagval}"
			;;
		DATE)
			year="${tagval}"
			;;
		esac
	done < "$1"
}

# flag_to_aac <srcfile> <dstfile> <tagfile>
#	transcode <srcfile> (flac) to <dstfile> (AAC)
#	(<tagfile> is ignored for now)
#
flac_to_aac()
{
	flac -sdc "$1" | wav_to_aac "$2"
}

# flag_to_ogg <srcfile> <dstfile> <tagfile>
#	transcode <srcfile> (flac) to <dstfile> (ogg vorbis) using <tagfile>
#	as a scratch for tag conversion, if necessary
#
flac_to_ogg()
{
	metaflac --export-tags-to="$3" "$1"
	flac -sdc "$1" | wav_to_ogg "$2"
	if [ -s "$3" ]; then
		vorbiscomment -w -c "$3" "$2"
	fi
}

# flag_to_mp3 <srcfile> <dstfile> <tagfile>
#	transcode <srcfile> (flac) to <dstfile> (mp3) using <tagfile>
#	as a scratch for tag conversion, if necessary
#
flac_to_mp3()
{
	metaflac --export-tags-to="$3" "$1"
	flac -sdc "$1" | wav_to_mp3 "$2"
	if [ -s "$3" ]; then
		tag_mp3 "$3" "$2"
	fi
}

# ogg_to_mp3 <srcfile> <dstfile> <tagfile>
#	transcode <srcfile> (ogg) to <dstfile> (mp3) using <tagfile>
#	as a scratch for tag conversion, if necessary
#
ogg_to_mp3()
{
	vorbiscomment -l "$1" > "$3"
	oggdec -q -o - "$1" | wav_to_mp3 "$2"
	if [ -s "$3" ]; then
		tag_mp3 "$3" "$2"
	fi
}

# usage
#	print a usage and exit
usage()
{
	cat 1>&2 <<USAGE
Usage: ${progname} [-f] [-d destdir] [-o operation]
	-f		Force transcoding and retagging (even if target exists)
	-d destdir	Write output relative to destdir
	-o operation	Operation.  One of:
				flactomp3 flactoogg oggtomp3
USAGE
exit 1
}

# main app
#
main()
{
		# parse args
	progname=$(basename $0)
	operation=$(basename $0)
	destdir=""
	forceall=false
	while getopts "d:fo:" opt; do
		case "${opt}" in
		d)
			destdir="${OPTARG}"
			;;
		f)
			forceall=true
			;;
		o)
			operation="${OPTARG}"
			;;
		'?')
			usage
			;;
		esac
	done
	shift $(($OPTIND - 1))

		# determine default mode of operation
	case "${operation}" in
	flactoaac)
		operation="flac_to_aac"
		srcext="flac"
		dstext="m4a"
		;;
	flactoogg)
		operation="flac_to_ogg"
		srcext="flac"
		dstext="ogg"
		;;
	flactomp3)
		operation="flac_to_mp3"
		srcext="flac"
		dstext="mp3"
		;;
	oggtomp3)
		operation="ogg_to_mp3"
		srcext="ogg"
		dstext="mp3"
		;;
	*)
		echo 1>&2 "${progname}: unknown operation \"${operation}\""
		exit 1
		;;
	esac

		# process the files
	for filesrc in "$@"; do
		fileraw="${filesrc%.${srcext}}"
		filedst="${destdir}${destdir:+/}${fileraw}.${dstext}"
		if [ "${fileraw}" = "${filesrc}" ]; then
			echo "Skipping ${filesrc} (not $srcext)"
			continue
		fi
		local forcemsg=""
		if [ "${filedst}" -nt "${filesrc}" ]; then
			if ${forceall}; then
				forcemsg=" [forced]"
			else
				echo "Skipping ${filesrc}, ${filedst} is newer"
				continue
			fi
		fi

		mkdir -p $(dirname "${filedst}")

		echo "Transcoding ${filesrc} to ${filedst}${forcemsg}"

		tmptag="${filesrc}.tmp"
		tmpdst="${filedst}.tmp"
		trap "rm -f \"${tmptag}\" \"${tmpdst}\"; exit 0"  0 2 3 13

		"${operation}" "${filesrc}" "${tmpdst}" "${tmptag}"
		mv "${tmpdst}" "${filedst}"

		rm -f "${tmptag}" "${tmpdst}"
		trap "-"  0 2 3 13
	done
}


main "$@"
