#!/bin/bash
# Script: Copyleft GNU General Public Licence Version 3             Peter Garrett <peter-garrett@ubuntu.com>
# Contributions: David Symons                                       David Symons <david.symons@liberatedcomputing.net>
# set -vex # debug mode 
# set -e   # exit on error
# CD Menu and control for mplayer to play audio CDs in terminal. Also uses "abcde" and "cdtool", plus "setcd"

# Foreground colours

white ()   { printf "\E[37m" ; }
yellow ()  { printf "\E[33m" ; }
bold ()    { printf "\E[1m"  ; }

# Background colours

bblack ()   { printf "\E[40m" ; }
bgreen ()   { printf "\E[42m" ; }

cleanup ()
{   
        setterm -cursor on 2>> $DEBUG       # Give $USER the cursor back ;-)
        tput sgr0 ; clear    
        if [ -e /tmp/fifo ] ; then rm /tmp/fifo ; fi       
        if [ -x $(which setcd) ] ; then setcd -x 0 1>> $DEBUG ; fi
        if [ -d $TMPDIR ] ; then rm -rf $TMPDIR ; fi
        kill_both 2>> $DEBUG
        echo -e "\n "$NAME": Error/Debug output is in $DEBUG \n"
        exit 0
}

make_playlist ()
{                                                      # Massage the cddb info a bit...    
    head -2 $TMPDIR/cddb > $TMPDIR/playlist            # Add headings and clobber existing contents.
    
    for each in $LIST
    do 
        grep ^${each}: $TMPDIR/cddb | sort -nu >> $TMPDIR/playlist
    done
    
    COUNTER=0                                          # There's probably a better way to do this...
    for counter in $LIST
    do
        COUNTER=$(( $COUNTER + 1 ))
        echo $COUNTER >> $TMPDIR/counter
    done
			        
    for tracknumber in $LIST
    do
        echo $tracknumber >> $TMPDIR/sequence
    done
    paste -d, $TMPDIR/counter $TMPDIR/sequence > $TMPDIR/index
    
    END=$(tail -1 $TMPDIR/counter)
    rm $TMPDIR/counter $TMPDIR/sequence
    echo 1 > $TMPDIR/current                           # New playlist always starts at INDEX=1
}

cddb ()
{
    # Get cddb data etc.
    echo -e "\v\v\tPerforming CDDB audio disc information check, please wait... "    
    ABCDE_DIRECTORY=$(abcde -N -d $DEVICE -a cddb | tail -1 | cut -d" " -f4 | sed 's/.$//') 
    egrep -v "(Retrieving|^I|^Multiple)" ${ABCDE_DIRECTORY}/cddbchoices \
    | sort -nu | tee $TMPDIR/playlist > $TMPDIR/cddb   # Clean up the mess made by multiples, like classing
                                                       # Edith Piaf as "classical" (huh?) and "something-equally-silly"
    NUMBER_OF_CDDB_LINES=$(wc -l < $TMPDIR/cddb)
    NUMBER_OF_TRACKS=$(( $NUMBER_OF_CDDB_LINES - 1 ))  # This assumes that some genius didn't add extra lines...
    NUMERICAL_LIST=$(seq 1 $NUMBER_OF_TRACKS)
    LIST=$NUMERICAL_LIST   
                                                       
    cp $TMPDIR/cddb $TMPDIR/holdcddb                   # "holdcddb" is a "holding tank" for unedited cddb.
    head -1 $TMPDIR/holdcddb > $TMPDIR/cddb
    echo " " >> $TMPDIR/cddb                           # Make a blank line between heading and playlist...
    tail -$NUMBER_OF_TRACKS $TMPDIR/holdcddb >> $TMPDIR/cddb
    rm $TMPDIR/holdcddb
    
    make_playlist
}

line_erase () { printf "\r" ; tput el ; printf "\r" ; } # Erases current output line without making a new line.

                                                        # The "uniq" and "head" get rid of duplicates from cddb.
track_title () { printf "$(grep ^$(cat $TMPDIR/track): $TMPDIR/playlist | uniq | head -1)" ; }

replay ()
{       
    (
        INDEX=$(cat $TMPDIR/current)
        for tracks in $(seq $INDEX $END)
        do
            TRACK=$(grep ^"${INDEX}," $TMPDIR/index | cut -d, -f2)
	    echo $TRACK > $TMPDIR/track          
            run_mplayer
	    INDEX=$(( $INDEX + 1 ))
	    echo $INDEX > $TMPDIR/current
        done

    ) & replaypid=$!           # This runs in a subshell, so we use $TMPDIR/* to store track and index.
}
    
run_mplayer ()
{
    line_erase
    track_title 
    # These mplayer settings may not be ideal for your system. Fiddle with cache and speed 'ad lib'.                    
    mplayer               \
    -really-quiet         \
    -cache 500            \
    -cdda speed=1         \
     cdda://${TRACK}      \
    -slave                \
    -input file=/tmp/fifo \
    -cdrom-device $DEVICE  >> $DEBUG 2>&1   
}

top ()
{
    bblack ; white ; clear ; bold
                                                       # This stuff is positioning, and colour...
    LINE_TO_NAME=58
    LINE_TO_END=$(( $(tput cols) - $LINE_TO_NAME - $NAME_LENGTH ))
    bgreen ; yellow ; printf "%$(tput cols)s" " " | tr ' ' '_'
    bblack ; yellow ; printf "%${LINE_TO_NAME}s" " " | tr ' ' '_'
    white ; printf "$(echo $NAME | tr "[a-z]" "[A-Z]")"
    yellow ; printf "%${LINE_TO_END}s" " " | tr ' ' '_'    
    bblack ; yellow
    printf "\n\n"
                                                       # Print the playlist
    PLAYLIST_LINES=$(wc -l < $TMPDIR/playlist)
    head -$SCROLL $TMPDIR/playlist | tail -$LINES_AVAILABLE
    echo
    
    echo -n -e "\033[s"                                # Cursor's current position is saved by escape sequence
    bold ; bgreen ; white                              # Make a colour block to terminal width, to draw on...
    for colour_block in $(seq 1 5) ; do tput el && printf "\n" ; done
    echo -n -e "\033[u"                                # Restore the cursor to whatever was its previous position
    bold ; bgreen ; white

    echo " *   Volume Up                                            s   Show All Tracks   "
    echo " /   Volume Down          <enter>   Start All             p   Custom Playlist   "
    echo " m   Mute|Unmute          <space>   Pause|Play            r   Range of Tracks   "
    echo " c   Close|Reload CD        +|-     Scroll Playlist       >   Next Track        "
    echo " e   Eject CD                q      Stop and Quit         <   Previous Track    "
 
    bblack ; yellow ; tput el ; printf "\r" ; printf "%$(tput cols)s" " " | tr ' ' '_' ; printf "\n"
    white ; track_title
}

nonsense_check ()  { if [ "$REPLY" = "" ] ; then redraw ; fi ; }

kill_replay ()     
{
    if [ "$replaypid" != "" ] ; then 
        kill $replaypid 2>> $DEBUG || true 
    fi    
} 
                                                                         # Restrict pkill to controlling terminal
kill_both ()       { kill_replay ; pkill -t $(tty | cut -c6-) mplayer 2>> $DEBUG || true ; } 
             
redraw ()          { top ; control ; }

redraw_and_play () { top ; replay ; control ; }

list_post_read ()  { setterm -cursor off ; nonsense_check ; kill_both ; }

start_at_1 ()      { kill_both ; LIST=$NUMERICAL_LIST ; make_playlist ; redraw_and_play ; }

control ()
{
    read -s -n 1
    trap cleanup 15 EXIT  # CTRL+C etc.

case $REPLY in
    '*')
    echo "volume +1" > /tmp/fifo
    control
    ;;
    '/')
    echo "volume -1" > /tmp/fifo
    control
    ;;
    ' ')
    echo "pause" > /tmp/fifo &
    control
    ;;
    +|=)
    if [ $SCROLL -eq $PLAYLIST_LINES ] ; then control ; fi
    SCROLL=$(( $SCROLL + 5 ))
    redraw
    ;;
    -|_)
    if [ $SCROLL -eq $LINES_AVAILABLE ] ; then control ; fi
    SCROLL=$(( $SCROLL - 5 ))
    redraw
    ;;
    c|C)
    clear
    CDCHECK=                            # Avoid restarting without a CD
    bold ; yellow ; echo -e "\v\tCD reload... Please wait.........."
    eject -t $DEVICE 2>> $DEBUG || true # Errors if used with non-tray CD ROM - harmless...
    cdcheck
    cddb
    redraw
    ;;  
    e|E)
    kill_both
    LIST=$NUMERICAL_LIST
    clear ; bold ; yellow
    echo -e "\v\tEjecting CD....."
    eject $DEVICE
    echo -e "\n\tPlease remove the CD."
    echo -e "\tThen press \"q\"  to quit "$NAME" ..."
    echo -e "\tOr, insert another CD, then press \"c\".\n"
    control
    redraw
    ;;
    m|M)
    echo "mute" > /tmp/fifo &
    control
    ;;
    q|Q)
    kill_both 
    line_erase
    white
    if [ "$(cdinfo -d $DEVICE)" = "no_disc" ] ; then
            eject -t $DEVICE 2>> $DEBUG || true
    else
        printf "\tDo you want to eject the CD? (y/n)"
        read -s -n 1
        if [ "$REPLY" = "y" -o "$REPLY" = "Y" ] ; then
            eject $DEVICE
        fi
    fi
    line_erase
    printf "\tThanks for using ${NAME}!"
    sleep 2
    cleanup
    ;;
    s|S)
    cp $TMPDIR/cddb $TMPDIR/playlist   
    redraw
    ;;
    r|R)
    line_erase
    printf "Type two space-separated start and end track numbers: " 
    setterm -cursor on                                 # Visible prompt
    read
    list_post_read
    LIST=$(seq $REPLY)
    make_playlist    
    redraw_and_play
    ;;
    p|P)
    line_erase
    printf "Type a space-separated list of track numbers: "
    setterm -cursor on
    read
    list_post_read
    LIST=$(printf "$REPLY")
    make_playlist   
    redraw_and_play
    ;;
    '.'|'>')
    INDEX=$(cat $TMPDIR/current)
    if [ "$replaypid" = "" ] ; then start_at_1 ; fi
    if [ "$INDEX" != "$END" ] ; then
        echo quit > /tmp/fifo & # Interrupt mplayer in the "replay" loop function --> next track in playlist.
        INDEX=$(( $INDEX + 1 ))
    else
        kill_both
        replay      
    fi
    control
    ;;
    ,|'<')
    INDEX=$(cat $TMPDIR/current)
    if [ "$INDEX" = "" -o "$INDEX" = "1" ] ; then start_at_1 ; fi    
    kill_both
    INDEX=$(( $INDEX - 1 ))
    echo $INDEX > $TMPDIR/current
    replay
    control
    ;;
    *)
    start_at_1
    ;;
esac    
}

cdcheck ()                                             # See if we have anything to play...
{
    for device in $(ls /dev/hd* /dev/scd* 2> /dev/null)
    do                                                 # Check for cdrom group.
        OKGROUP="$(ls -l $device | awk '{print $4}')"
        if [ "$OKGROUP" = "cdrom" ] ; then       
            if [ "$(cdinfo -d $device)" != "data_disc" ] ; then
                if [ "$(cdinfo -d $device)" != "no_disc" ] ; then
                    DEVICE=$device		    
                    CDCHECK=OK
                    TMPDIR=$(mktemp -td $NAME.XXXXXXXXXX)
                    for tmp in cddb current index playlist track holdcddb sequence
                      do
                        mktemp $TMPDIR/$tmp 1> /dev/null 2>> $DEBUG 
                      done    
                    echo "1" > $TMPDIR/track           # Set initial track   
                fi    
            fi 
        fi   
    done
    
    if [ "$CDCHECK" != "OK" ] ; then
        echo -e "\n\tI can't find an audio CD to play... Did you forget something?\n"
        sleep 4
        cleanup
    else
        # Using "setcd" to quieten the drive. Can't see why fast spin is needed for audio CDs.
        if [ -x $(which setcd) ] ; then setcd -x 1 $DEVICE 1>> $DEBUG ; fi        
    fi
}

existing_instance ()                                   # Don't run a new instance if one already exists.
{
        echo -e "\n\tAnother instance of $NAME appears to be running."
        echo -e "\tIf not, please remove /tmp/${NAME}* and /tmp/fifo \n"
        setterm -cursor on
        exit 1
}

    NAME=$(basename $0)
    NAME_LENGTH=$(printf $NAME | wc -m)
    # The cursor gets in the way for input and output fields as used in this script...
    setterm -cursor off
    mkfifo /tmp/fifo || existing_instance              # Control input for mplayer in "slave" mode          
    DEBUG=$(mktemp -t $NAME.debug.XXXXXXXXXX)
                                                       # Warn $USER if terminal is too small
    if [ $(tput cols) -lt 80 ] || [ $(tput lines) -lt 17 ] ; then
        echo -e "\n  $NAME works properly in a terminal *at least* 80x17," 
        echo -e "  or *larger* - sorry... try a bigger terminal!\n"
	sleep 5
        cleanup
    fi
    
    echo -e "  Starting up "$NAME"... CD checks in progress..."
    sleep 1

    # Terminal size - set the initial value for scrolling and track as well...
    LINES_AVAILABLE=$(( $(tput lines) - 12 ))
    SCROLL=$LINES_AVAILABLE
    rm -rf /tmp/$NAME.* 2>> $DEBUG || true             # Stop accumulation of output.
    bblack ; bold ; yellow
    clear
    mkdir -p $HOME/cddb-$NAME
    cd $HOME/cddb-$NAME    # Make abcde dump its stuff in one place instead of spraying it all over $HOME

    cdcheck     
    cddb
    redraw
