#!/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 -vx # debug mode 
# 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 ()
{   
    if [ "$CLEANUP" != "no" ] ; then
        setterm -cursor on 2>> /tmp/$NAME.debug        # Give $USER the cursor back ;-)
        tput sgr0 ; clear
        if [ -e /tmp/$NAME.lock ] ; then rm /tmp/$NAME.lock ; fi
        if [ -e /tmp/fifo ] ; then rm /tmp/fifo ; fi
        if [ -e /tmp/cddb ] ; then rm /tmp/cddb ; fi
        if [ -e /tmp/ok_cdroms ] ; then rm /tmp/ok_cdroms ; fi
        if [ -e /tmp/playlist ] ; then rm /tmp/playlist ; fi
        if [ -e /tmp/index ] ; then rm /tmp/index ; fi
        if [ -x $(which setcd) ] ; then setcd -x 0 ; fi
        if [ -e /tmp/current ] ; then rm /tmp/current ; fi
        if [ -e /tmp/track ] ; then rm /tmp/track ; fi
        kill_both 2>> /tmp/$NAME.debug
        echo -e "\n "$NAME": Error output is in /tmp/$NAME.debug \n"
        exit 0
    fi
}

make_playlist ()
{                                                      # Massage the cddb info a bit...    
    head -2 /tmp/cddb > /tmp/playlist                  # Add headings and clobber existing contents.
    
    for each in $LIST
    do 
        grep ^${each}: /tmp/cddb | sort -nu >> /tmp/playlist
    done
    
    COUNTER=0                                          # There's probably a better way to do this...
    for counter in $LIST
    do
        COUNTER=$(( $COUNTER + 1 ))
        echo $COUNTER >> /tmp/counter
    done
			        
    for tracknumber in $LIST
    do
        echo $tracknumber >> /tmp/sequence
    done
    paste -d, /tmp/counter /tmp/sequence > /tmp/index
    
    END=$(tail -1 /tmp/counter)
    rm /tmp/counter /tmp/sequence
    echo 1 > /tmp/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 /tmp/playlist > /tmp/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 < /tmp/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   
                                                       # Make a blank line between heading and playlist...
    cp /tmp/cddb /tmp/holdcddb                         # "holdcddb" is a "holding tank" for unedited cddb.
    head -1 /tmp/holdcddb > /tmp/cddb
    echo " " >> /tmp/cddb
    tail -$NUMBER_OF_TRACKS /tmp/holdcddb >> /tmp/cddb
    rm /tmp/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 /tmp/track): /tmp/playlist | uniq | head -1)" ; }

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

    ) & replaypid=$!           # This runs in a subshell, so we use /tmp/* 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  >> /tmp/$NAME.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 "CDMP"
    yellow ; printf "%${LINE_TO_END}s" " " | tr ' ' '_'    
    bblack ; yellow
    printf "\n\n"
                                                       # Print the playlist
    PLAYLIST_LINES=$(wc -l < /tmp/playlist)
    head -$SCROLL /tmp/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 > /dev/null 2>&1 ; fi ; }

kill_both ()       { kill_replay ; pkill -t $(tty | cut -c6-) mplayer ; } # Restrict pkill to controlling terminal
             
redraw ()          { top ; control ; }

redraw_and_play () { top ; replay ; control ; }

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

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
    bold ; yellow ; echo -e "\v\tCD reload... Please wait.........."
    eject -t $DEVICE 2> /dev/null # 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
    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
        line_erase
    fi
    printf "\tThanks for using ${NAME}!"
    sleep 2
    cleanup
    ;;
    s|S)
    cp /tmp/cddb /tmp/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 /tmp/current)
    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
    ;;
    ,|'<')
    kill_both
    INDEX=$(cat /tmp/current)
    if [ "$INDEX" = "" ] ; then
        INDEX=2
    elif [ "$INDEX" = "1" ] ; then
        INDEX=2
    fi
    INDEX=$(( $INDEX - 1 ))
    echo $INDEX > /tmp/current
    replay
    control
    ;;
    *)
    kill_both
    LIST=$NUMERICAL_LIST
    make_playlist
    redraw_and_play
    ;;
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
            echo $device >> /tmp/ok_cdroms       
            if [ "$(cdinfo -d $device)" != "data_disc" ] ; then
                if [ "$(cdinfo -d $device)" != "no_disc" ] ; then
                    DEVICE=$device
                    touch /tmp/$NAME.lock		    
                    CDCHECK=OK
                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
    fi
}
    NAME=$(basename $0)
    NAME_LENGTH=$(printf $NAME | wc -m)
                                                       # 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"
        exit 1
    fi
    echo -e "  Starting up "$NAME"... CD checks in progress..."
    sleep 2
                                                       # Don't run a new instance if one already exists.
    if [ -e /tmp/$NAME.lock ] ; then
        echo -e "\n\tAnother instance of $NAME appears to be running."
        echo -e "\tIf not, please remove /tmp/$NAME.lock ... Exiting...\n"
        CLEANUP=no # Don't run cleanup - running instance needs the files!
        setterm -cursor on
        exit 1
    fi
    
    # 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 ; fi
    
    # Terminal size - set the initial value for scrolling and track as well...
    LINES_AVAILABLE=$(( $(tput lines) - 12 ))
    SCROLL=$LINES_AVAILABLE
    echo 1 > /tmp/track
    
    # The cursor gets in the way for input and output fields as used in this script...
    setterm -cursor off    
                                                       # Stop accumulation of any error output.
    if [ -e /tmp/$NAME.debug ] ; then rm /tmp/$NAME.debug ; fi
    if ! [ -e /tmp/fifo ] ; then                       # Control input for mplayer in "slave" mode
        mkfifo /tmp/fifo
    fi
    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
