#!/bin/sh
# Copyright (c) 1998,1999 Robert Woodcock <rcw@debian.org>
# This code is hereby licensed for public consumption under either the
# GNU GPL v2 or greater, or Larry Wall's Artistic license - your choice.
#
# 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.

VERSION=0.8.2

usage ()
{
echo "This is cdgrab v$VERSION."
echo "Usage: cdgrab [options] [tracks]"
echo "Options:"
echo "-d    Specify CDROM device to grab"
echo "-e    Edit returned CDDB information before using"
echo "-h    This help information"
echo "-j    Number of encoder processes to run at once"
echo "-l [num] Low disk space handling option"
echo "      1 = normal parallelization"
echo "      2 = disk space conservation"
echo "-n    Don't query CDDB, just create and use template"
echo "-p    Create m3u playlist"
echo "-P    Only create m3u playlist (do no CD ripping)"
echo "Tracks is a space-delimited list of tracks to grab."
echo "No wildcards accepted yet."
}

# Builtin defaults
CDDBURL="http://cddb.cddb.com/~cddb/cddb.cgi"
HELLOINFO="`whoami`@`hostname`"
CDROM=/dev/cdrom
CDROMREADERSYNTAX=cdparanoia
ENCODERSYNTAX=lame
OUTPUTFORMAT='${ARTISTFILE}/${TRACKFILE}.mp3'
PLAYLISTFORMAT='${ARTISTFILE}_-_${ALBUMFILE}.m3u'
LOWDISK=1

# program paths - defaults to checking your $PATH
LAME=lame
GOGO=gogo
BLADEENC=bladeenc
L3ENC=l3enc
ID3=id3
CDPARANOIA=cdparanoia
CDDA2WAV=cdda2wav
WGET=wget
CDDISCID=cd-discid
CDDBTOOL=cddb-tool

# Options for programs called from cdgrab
LAMEOPTS=
GOGOOPTS=
BLADEENCOPTS=
L3ENCOPTS=
ID3OPTS=
CDPARANOIAOPTS=
CDDA2WAVOPTS=
WGETOPTS=
CDDBTOOLOPTS=

# Default to one process if -j isn't specified
MAXPROCS=1

# Momma always taught me not to use no double negatives :)
# If NOCDDB is set to y, no CDDB read is done
# If NOID3 is set to y, no ID3 tagging is done
NOCDDB=n
NOID3=n

if [ "$OUTPUTDIR" = "" ]; then
	OUTPUTDIR=`pwd`
fi

if [ "$WAVOUTPUTDIR" = "" ]; then
	WAVOUTPUTDIR="$OUTPUTDIR"
fi


# Load system defaults
if [ -r /etc/cdgrab.conf ]; then
	. /etc/cdgrab.conf
fi
# Load user preference defaults
if [ -r $HOME/.cdgrab.conf ]; then
	. $HOME/.cdgrab.conf
fi

# Parse command line options
while getopts d:ehj:l:npP opt ; do
	case "$opt" in
		d) CDROM="$OPTARG" ;;
		e) EDITCDDB="y" ;;
		j) MAXPROCS="$OPTARG" ;;
		h) usage; exit ;;
		l) LOWDISK="$OPTARG" ;;
		n) NOCDDBQUERY="y" ;;
		p) PLAYLIST="y" ;;
		P) PLAYLISTONLY="y"; PLAYLIST="y" ;;
		?) usage; exit ;;
	esac
done

if [ $LOWDISK -lt 1 ] || [ $LOWDISK -gt 2 ]; then
	echo    "cdgrab: invalid lowdisk option argument."
	echo    "Lowdisk options:"
	echo -e "1:\tDefaults (Parallelize entire CD)"
	echo -e "2:\tLowest disk usage (No parallelization)"
	echo -e "3:\tRip timing prediction (best case parallelization, not yet implemented)"
	exit 1
fi

shift $(($OPTIND - 1))

while [ $# -gt 0 ]; do
	TRACKQUEUE=`echo "$TRACKQUEUE" $1`
	shift
done

# Decide which CDROM reader we're gonna use
case "$CDROMREADERSYNTAX" in
	cdparanoia|debug)
		CDROMREADER="$CDPARANOIA"
		CDROMREADEROPTS="$CDPARANOIAOPTS"
		;;
	cdda2wav)
		CDROMREADER="$CDDA2WAV"
		CDROMREADEROPTS="$CDDA2WAVOPTS"
		;;
esac

# and which encoder
case "$ENCODERSYNTAX" in
	lame)
		ENCODEROPTS="$LAMEOPTS"
		ENCODER="$LAME"
		;;
	gogo)
		ENCODEROPTS="$GOGOOPTS"
		ENCODER="$GOGO"
		;;
	bladeenc)
		ENCODEROPTS="$BLADEENCOPTS"
		ENCODER="$BLADEENC"
		;;
	l3enc)
		ENCODEROPTS="$L3ENCOPTS"
		ENCODER="$L3ENC"
		;;
esac
	
# Make sure a buncha things exist
for X in $CDROMREADER $CDDISCID $ID3 $ENCODER $WGET
do
	# Cut off the command-line options we just added in
	X=`echo $X | cut -d' ' -f2`
	if [ "`which $X`" = "" ]; then
		echo "cdgrab error: $X is not in your path."
		exit 1
	elif [ \! -x `which $X` ]; then
		echo "cdgrab error: $X is not executable."
		exit 1
	fi 
done

CDROMREADER="$CDROMREADER $CDROMREADEROPTS"
CDDBTOOL="$CDDBTOOL $CDDBTOOLOPTS"

# Query the CD to get the track info
echo -n "Getting CD track info... "
TRACKINFO=`$CDDISCID $CDROM`

# Make sure there's a CD in there by checking cd-discid's return code
if [ "$?" = "1" ]; then
	echo "cdgrab error: CD could not be read. Perhaps there's no CD in the drive?"
	exit 1
fi

# Get a full enumeration of tracks, sort it, and put it in the TRACKQUEUE.

TRACKS=`echo $TRACKINFO | cut -f2 -d' '`

if [ "$TRACKQUEUE" = "" ]; then
	echo -n "Grabbing entire CD - tracks: "
	X=0
	while [ "$X" != "$TRACKS" ]
	do
		X=`expr $X + 1`
		TRACKQUEUE=`echo "$TRACKQUEUE" $X`
	done
	echo $TRACKQUEUE
else
	TRACKQUEUE=`(for X in $TRACKQUEUE; do echo $X; done) | sort -n | uniq | xargs`
	echo Grabbing tracks: "$TRACKQUEUE"
fi

# TRACKQUEUE has now been sorted - get the last variable in the list
for X in $TRACKQUEUE; do :; done
# get the number of digits to pad TRACKNUM with - we'll use this down below
TRACKNUMPADDING=`echo -n $X | wc -c | xargs`
CDDBDATA=`mktemp $OUTPUTDIR/cdgrab.XXXXXX` || exit 1
if [ "$NOCDDBQUERY" = "y" ]; then
	ERRORCODE=no_query
else
	CDDBUSER=`echo $HELLOINFO | cut -f1 -d'@'`
	CDDBHOST=`echo $HELLOINFO | cut -f2 -d'@'`
	$CDDBTOOL get $CDDBURL $CDDBUSER $CDDBHOST $TRACKINFO >$CDDBDATA
	ERRORCODE=$?
fi
case $ERRORCODE in
	0)  # success
	;;
	12|13|no_query)
		# no match found in database
		# wget error, or user requested not to use CDDB
		if [ "$ERRORCODE" = "13" -o $ERRORCODE = "no_query" ]
		then
			CANTSUBMIT=y
		else
			CANTSUBMIT=n
		fi
		echo -ne "Creating template..."
		$CDDBTOOL template $TRACKINFO >$CDDBDATA
		echo "done."
		NOCDDB=y
		NOID3=y
		NOSUBMIT=y
		# Ok now that we have this template file let's
		# ask the user if they want to put something in it
		echo "Would you like to edit this blank template file?"
		echo "If you choose no, your tracks will be named like this:"
		echo -e "\tUnknown_Artist/Track_01.mp3"
		echo "and will not be ID3 tagged."
		echo -n "Edit CDDB template? [y/n] "
		if [ "$ERRORCODE" = "no_query" ]
		then
			YESNO=y
			echo y
		else
			read YESNO
		fi
		while [ "$YESNO" != "y" ] && [ "$YESNO" != "n" ] && [ "$YESNO" != "Y" ] && [ "$YESNO" != "N" ]
		do
			echo -n 'Invalid selection. Please answer "y" or "n": '
			read YESNO
		done
		if [ "$YESNO" = "y" ] || [ "$YESNO" = "Y" ]; then
			EDITCDDB=y
			if [ "$CANTSUBMIT" = "y" ]; then
				NOSUBMIT=y   #redundant
			else
				NOSUBMIT=n
			fi
			NOID3=n
		fi
	;;
	*) # strange and unknown error
		echo "cdgrab: $CDDBTOOL returned unknown error code"
	;;
esac
# Let user edit CDDB data if they requested such a thing
if [ "$EDITCDDB" = "y" ]; then
	# Use the debian sensible-editor wrapper to pick the editor that the
	# user has requested via their $EDITOR environment variable
	if [ -x "/usr/bin/sensible-editor" ]; then
		/usr/bin/sensible-editor $CDDBDATA
	elif [ -x "$EDITOR" ]; then
		# That failed, try to load the preferred editor, starting
		# with their EDITOR variable
		$EDITOR $CDDBDATA
	# If that fails, check for a vi
	elif [ -x /usr/bin/vi ]; then
		/usr/bin/vi $CDDBDATA
	# ae should be on all debian systems
	elif [ -x /bin/ae ]; then
		/bin/ae $CDDBDATA
	# bomb out
	else
		echo "No editor available. Check your EDITOR environment variable."
		exit 1
	fi
	# delete editor backup file if it exists
	if [ -w $CDDBDATA~ ]; then
		rm -f $CDDBDATA~
	fi
#
#	This is temporarily commented out until I have a chance to mess with it a
#	little more. --rcw 8/31/1999
#
#	# submit the modified file, if they want
#	if [ "$NOSUBMIT" != "y" ]; then
#		echo -n "Do you want to submit this entry to $CDDBSUBMIT? [y|N] "
#		read YESNO
#		while [ "$YESNO" != "y" ] && [ "$YESNO" != "n" ] && [ "$YESNO" != "Y" ] && [ "$YESNO" != "N" ]
#		do
#			echo -n 'Invalid selection. Please answer "y" or "n": '
#			read YESNO
#		done
#		if [ "$YESNO" = "y" ] || [ "$YESNO" = "Y" ]; then
#			echo -n "Sending..."
#			$CDDBTOOL send $CDDBDATA $CDDBSUBMIT
#			echo "done."
#		fi
#	fi

fi
# Get Artist and Album info from the CDDB response
eval `$CDDBTOOL parse $CDDBDATA all`
echo Title: $DALBUM
echo Artist: $DARTIST
ARTISTFILE=`echo $DARTIST | tr \ / __ | tr -d \'\? | tr -d \[:cntrl:\]`
ALBUMFILE=`echo $DALBUM | tr \ / __ | tr -d \'\? | tr -d \[:cntrl:\]`
TRACKNAME=foo
OUTPUTFILE=`eval echo $OUTPUTFORMAT`
if [ "$PLAYLISTONLY" != "y" ]; then
	if [ \! -e `dirname $OUTPUTDIR/$OUTPUTFILE` ]; then
		mkdir -p `dirname $OUTPUTDIR/$OUTPUTFILE`;
	fi
fi
PLAYLISTFILE=`eval echo $PLAYLISTFORMAT`
if [ "$PLAYLIST" = "y" ]; then
	rm -f "$PLAYLISTFILE"
	touch "$PLAYLISTFILE"
fi
# Go through the tracks and list them all
# UTRACKNUM is the un-0-padded tracknum counter
for UTRACKNUM in $TRACKQUEUE
do
	echo -n "Track $UTRACKNUM: "
	eval echo \$TRACK${UTRACKNUM}
done
# For option #2, only one program is running at once so the encoder can be
# unsilenced right away
# MAKE SURE that you don't write anything to $CDDBDATA until the _after_
# the cddb-tool send above.
if [ "$LOWDISK" = "2" ]; then
	echo "cdgrab-control-unsilence" >> $CDDBDATA
fi

# This is where we split into two programs - one does the ripping, the other
# does the encoding, tagging, and deletion of all temporary files.
# Communication is via pipe on file descriptor #3. One filename per line.

export TRACKNUMPADDING CDDBDATA WAVOUTPUTDIR OUTPUTDIR OUTPUTFORMAT ENCODER ID3
export ID3OPTS TRACKQUEUE DALBUM DARTIST PLAYLIST PLAYLISTONLY
(
# Start ripping in process #1.
for UTRACKNUM in $TRACKQUEUE
do
	# This entire section must be completely silent to stdout - all
	# output *must* go to stderr. Otherwise it'll get sent down the
	# pipe.
	TRACKNUM=`printf %0.${TRACKNUMPADDING}d ${UTRACKNUM}`
	TRACKNAME=`$CDDBTOOL parse $CDDBDATA track $UTRACKNUM | tr -d \[:cntrl:\]`
	if [ "$PLAYLISTONLY" != "y" ]; then
		echo "Grabbing track $UTRACKNUM: $TRACKNAME..." >&2
		WAVDATA=`mktemp -u $WAVOUTPUTDIR/cdgrab.currenttrack.XXXXXX`.wav || exit 1
		case "$CDROMREADERSYNTAX" in
			cdparanoia) $CDPARANOIA -d $CDROM $UTRACKNUM $WAVDATA ;;
			cdda2wav) $CDDA2WAV -H -D $CDROM -t $UTRACKNUM $WAVDATA ;;
			debug) $CDPARANOIA -d $CDROM -w $UTRACKNUM-[:5] $WAVDATA ;;
		esac
	fi
	# The filename gets sent down the pipe - the encoder thread will
	# block on the pipe read until this is sent, automagically pausing things
	# nicely 
	echo $WAVDATA

	if [ "$PLAYLISTONLY" = "y" ]; then LOWDISK=1; fi # this is essentially a goto

	case $LOWDISK in
	1) # do nothing here - proceed straight to next track (default)
		;;
	2) # eat up as little disk space as possible - wait for this track
	   # to finish encoding
		while true
		do
			grep -q cdgrab-control-completed-track-$UTRACKNUM $CDDBDATA 2>/dev/null
			RESULT=$?
			if [ "$RESULT" = "2" ] || [ "$RESULT" = "0" ]; then
				# grep hit some fatal error (like the CDDBDATA file
				# was cleaned by an exiting encoder thread),
				# or found a match, either way we exit
				break
			fi
			sleep 2
		done
		;;
	3) # Door #3 hasn't been built yet - tell the user that
		echo "Low Disk option #3 not yet available, defaulting to normal parallelization."
		# I would jump back to #1 to do nothing, but I figured I
		# could just do nothing right here instead. Code duplication
		# sucks but it does have its place.
		;;
	esac
done
# This is downright evil, but elegant nonetheless (it's either this or
# another tempfile) - we tack a keyword at the end of the CDDBDATA tempfile
# that signals that the encoder/tagger can produce output.
if [ "$PLAYLISTONLY" != "y" ]; then
	echo cdgrab-control-unsilence >> $CDDBDATA
	echo Finishing encoding... >&2
fi
) | (
# The variables down here live in a kind of alternate reality so some of
# them need to be recreated.
SILENCE='2>/dev/null'
NUMPROCS=0
for UTRACKNUM in $TRACKQUEUE
do
	# get next file to process
	read WAVDATA
	# quit if we're all done
	if [ "$?" != "0" ]; then exit; fi
	TRACKNUM=`printf %0.${TRACKNUMPADDING}d ${UTRACKNUM}`
	CDDBTRACKNUM=`expr $UTRACKNUM - 1`
	TRACKNAME=`grep ^TTITLE$CDDBTRACKNUM= $CDDBDATA | head -1 | cut -f2 -d= | tr -d \[:cntrl:\]`
	TRACKFILE=`echo $TRACKNAME | sed 's- -_-g' | tr -d \'\?/`
	OUTPUTFILE=`eval echo $OUTPUTFORMAT`
	if [ "$PLAYLIST" = "y" ]; then
		echo "$OUTPUTFILE" >> "$PLAYLISTFILE"
	fi
	if [ "$PLAYLISTONLY" = "y" ]; then continue; fi
	if grep -q ^cdgrab-control-unsilence$ $CDDBDATA >/dev/null; then
		unset SILENCE
	else
		SILENCE='2>/dev/null'
	fi
	NUMPROCS=`expr $NUMPROCS + 1`
	if [ "$NUMPROCS" = "$MAXPROCS" ]
	then
		# Don't background this one - reset the counter, encode the
		# mp3, lather, rinse, repeat.
		NUMPROCS=0
		eval "echo Encoding track" '"$UTRACKNUM: $TRACKNAME..."' "$SILENCE >&2"
		case "$ENCODERSYNTAX" in
		lame|gogo)
			eval "$ENCODER" "$ENCODEROPTS" "$WAVDATA" '"$OUTPUTDIR/$OUTPUTFILE"' "$SILENCE >&2"
			;;
		bladeenc|l3enc)
			eval "$ENCODER" "$WAVDATA" '"$OUTPUTDIR/$OUTPUTFILE"' "$ENCODEROPTS" "$SILENCE >&2"
			;;
		esac
		rm -f "$WAVDATA"
		if [ "$NOID3" != "y" ]; then
			eval "echo Tagging track" '"$UTRACKNUM: $TRACKNAME..."' "$SILENCE >&2"
			eval "$ID3" "$ID3OPTS" -A '"$DALBUM"' -a '"$DARTIST"' \
				-t '"$TRACKNAME"' '"$OUTPUTDIR/$OUTPUTFILE"' "$SILENCE >&2"
		fi
		echo "cdgrab-control-completed-track-$UTRACKNUM" >> $CDDBDATA
	else
		# Background this one, so it immediately goes through the
		# for loop and finds more stuff to run
		( SILENCE='2>/dev/null'
		eval "echo Encoding track" '"$UTRACKNUM: $TRACKNAME..."' "$SILENCE >&2"
		case "$ENCODERSYNTAX" in
		lame|gogo)
			eval "$ENCODER" "$ENCODEROPTS" "$WAVDATA" '"$OUTPUTDIR/$OUTPUTFILE"' "$SILENCE >&2"
			;;
		bladeenc|l3enc)
			eval "$ENCODER" "$WAVDATA" '"$OUTPUTDIR/$OUTPUTFILE"' "$ENCODEROPTS" "$SILENCE >&2"
			;;
		esac
		rm -f "$WAVDATA"
		if [ "$NOID3" != "y" ]; then
			eval "echo Tagging track" '"$UTRACKNUM: $TRACKNAME..."' "$SILENCE >&2"
			eval "$ID3" "$ID3OPTS" -A '"$DALBUM"' -a '"$DARTIST"' \
				-t '"$TRACKNAME"' '"$OUTPUTDIR/$OUTPUTFILE"' "$SILENCE >&2"
		fi
		echo "cdgrab-control-completed-track-$UTRACKNUM" >> $CDDBDATA
		) &
	fi		
done
echo "Finished."
rm -f $CDDBDATA
)

