#!/bin/bash
# THIS PROGRAM COMES WITHOUT ANY WARRANTY
# USE AT YOUR OWN RISK!
#
# Records a livestream from an internet radio station.
# The original version of record.sh was derived from
# http://groups.google.com/group/de.comp.os.unix.shell/browse_thread/thread/e215f94ccf572331
# Last changed: 2009-08-10
# USAGE:
# record.sh (start|stop|status) station name_of_program
#
# This script is designed to run as a cron job.
# A typical user crontab entry would look like this:
# 00 10 * * 6 record.sh start wdr5 WDR5_Krimi_am_Samstag
# 00 11 * * 6 record.sh stop  wdr5 WDR5_Krimi_am_Samstag
#
# Livestreams are dumped as raw streams and can in general be played with
# mplayer only. Use
# mplayer recordedfile -ao pcm:file=fifo & lame fifo
# to transcode to mp3 (fifos don't seem to work under cygwin,
# you probably need to use a non-fifo temporary wave file).
#

# Sets the home directory if it has not previously been set
if [[ -z $HOME ]]
then
    HOME=/home/$(whoami)
fi

# how to invoke the mplayer executable
# Under cygwin start a cygwin bash shell and type e. g.
# cygpath -ua 'C:\Programme\mplayer\mplayer.exe'
# to get a UNIX-style path
mplayer="/usr/bin/mplayer"

# Would you like to download metadata from hoerdat?
# see http://hspiel.mospace.de/autotag.html
# Bitte erwaegen Sie eine Spende an hoerdat!
hoerdat_html=false
hoerdat_xml=false     # depends on hoerdat_html
hoerdat_id3=false     # depends on hoerdat_xml
hoerdat_sh=false      # depends on hoerdat_id3

# Additional mplayerflags. This is optional. Under windows you might want to set
# the process priority for mplayer using
#  mplayerflags="-priority belownormal"
# a user has reported this to avoid race conditions when two mplayers are
# running at the same time.
mplayerflags="-nolirc -prefer-ipv4"

# The location of the stream info file
# This can be a local file or a web URL
# It has one line for each stream with the structure
# STATIONNAME URL TYPE
# where TYPE is one of smil, m3u, asx for playlists
# or the proper file extension for the stream format
# of the respective internet radio station.
# Neither URL nor STATIONNAME may contain any blanks
# Replace blanks in URLs by %20.
# Under cygwin start a cygwin bash shell and type e. g.
# cygpath -ua 'C:\downloads\livestreams.txt'
# to get a UNIX-style path
# streams="/usr/share/streams.dat"
streams="http://hspiel.mospace.de/livestreams.txt"
# A local stream info file that complements or overrides
# definitions in $streams.
# localstreams="$HOME/share/livestreams.txt"
#localstreams=

# A temporary file where the process id of a recording is
# saved as long as it is running
# If you don't know any better try
# pidfile=~/.$3-mplayer.pid
pidfile="$HOME/aufnahmen/.$3-mplayer.pid"

# The place where recordings are stored. When using the
# Win32 (MinGW) port of mplayer this must be a windows
# path (with backslashes).
dest="$HOME/aufnahmen"

# Where errors are logged.
errors="$dest/record.sh.errors"
# Write errors to stderr (console). Not recommended when running record.sh
# as a cron job, but useful when testing record.sh on the command line.
#errors=/dev/stderr
# Ignore errors
#errors=/dev/null

# error codes
E_FILE_EXISTS=42
E_NO_STREAM_URL=43
E_NO_STREAM_TYPE=44
E_WRONG_NUMBER_OF_ARGUMENTS=45
E_LOCKFILE_FOUND=46

# Returns url and type for a station name
# @param station the name of the station
# @return a single string consisting of
#  stationname, url and type separated by a blank
stream(){
    local station=$1
    result=""
    if [[ -f "$localstreams" ]]
    then
        result=$( grep -m 1 -e ^${station} "$localstreams" )
    fi
    if [[ -z "$result" ]]
    then
        if [[ ${streams:0:7} = "http://" ]]
        then
            result=$( curl $streams 2> /dev/null | grep -m 1 -e ^${station})
        else
            grep -m 1 -e ^${station} "$streams"
        fi
    fi
    echo $result
}

# Starts recording a livestream from a station
# @param station the station name
# @param alias a name for the recording
start(){
    local station=$1
    local alias=$2
    local stream=(`stream $station`)
    local url=${stream[1]}
    local type=${stream[2]}
    local dayHourMinute=$(date +'%d-%H-%M')

    if [[ $# -ne 2 ]]
    then
        usage
        return $E_WRONG_NUMBER_OF_ARGUMENTS
    fi

    if [[ -z $url ]]
    then
        echo "No stream URL for $station" >> "$errors"
        return $E_NO_STREAM_URL
    fi

    if [[ -z $type ]]
    then
        echo "No stream type for $station" >> "$errors"
        return $E_NO_STREAM_TYPE
    fi

    local playlist=""
    #handle playlists
    if [[ "$type" = "smil" || "$type" = "rm" ]]
        then
        type="ra"
        playlist="-playlist"
    elif [[ "$type" = "m3u" ]]
        then
        type="mp3"
        playlist="-playlist"
    elif  [[ "$type" = "asx" ]]
        then type="wma"
        playlist="-playlist"
    fi

    if test -f "$pidfile"
        then
        echo "Lockfile found!"
        return $E_LOCKFILE_FOUND
    fi
    echo $$ > $pidfile

    dat=`date '+%Y-%m-%d'`
    fname="${alias}_${dat}"
    CYG="$(uname  | grep CYGWIN)"
    if [[ -n "$CYG" ]]
    then
        file="${dest}\\${fname}.${type}"
    else
        file="${dest}/${fname}.${type}"
    fi

    if test -f "$file"
        then
        echo "Target file $file exists." >> "$errors"
        return $E_FILE_EXISTS
    fi
    #echo $file
    # unfortunately -endpos does not work with -dumpstream so we cannot use that to terminate mplayer
    "$mplayer" $playlist $url $mplayerflags -msglevel all=2 -dumpstream -dumpfile "$file"  >/dev/null 2>>"$errors" <&2 &
    if [[ ! $? ]]
    then
        echo "FAILED: $mplayer $playlist $url $mplayerflags -msglevel all=2 -dumpstream -dumpfile $file"
        return $?
    fi
    echo $! >$pidfile
    disown

    if [[ "$hoerdat_html" = "true" ]]
    then
        local html="${dest}/${fname}.html"
        local xmln="${dest}/${fname}.xml"
        local xml1="${dest}/${fname}~1.xml"
        local id3="${dest}/${fname}.id3"
        local sh="${dest}/${fname}.sh"
        local mp3="${fname}.mp3"
        local hoerdatsender=
        case $station in
        einslive)
            hoerdatsender=wdr
            ;;
        *)
            # use first three letters with numbers removed
            hoerdatsender=$(echo ${station:0:3} | sed 's/[0-9]//')
            ;;
        esac

        curl -s http://www.hoerdat.in-berlin.de/heute.php?dat=${dat}\&sender=${hoerdatsender} \
            | tidy -asxhtml -numeric \
            > "$html" \
            2> /dev/null
        if [[ "$hoerdat_xml" == true ]]
        then

            # Extract data as XML from XHTML
            xsltproc http://hspiel.mospace.de/xslt/hoerdat-parser.xsl "$html" \
                > "${xmln}" \
                2>> "$errors"
            # Try to select the currently running program
            xsltproc --stringparam day-hour-minute $dayHourMinute \
                http://hspiel.mospace.de/xslt/hoerdat-select.xsl \
                "${xmln}" \
                > "${xml1}" \
                2>> "$errors"
            # If we have found a matching entry use it, else remove the empty output file
            if [[ $(stat -c %s "${xml1}") -gt "80" ]]
            then
                mv "${xml1}" "${xmln}"
            else
                rm "${xml1}"
            fi

            if [[ "$hoerdat_id3" == "true" ]]
            then
                # Generate ID3 XML
                xsltproc http://hspiel.mospace.de/xslt/hoerdat-id3.xsl "${xmln}" \
                    > "$id3" \
                    2>> "$errors"

                if [[ "$hoerdat_sh" == "true" ]]
                then
                    # Generate bash script for tagging
                    echo '#!/bin/bash' > "$sh"
                    cmd=$(xsltproc http://hspiel.mospace.de/xslt/id3-bash.xsl "$id3") \
                        >> "$sh" \
                        2>> "$errors"
                    echo $cmd $mp3 >> "$sh"
                fi
            fi
        fi
    fi

}

# pgrep substitute
function _pgrep_(){
    if which pgrep >/dev/null 2>/dev/null
    then
        pgrep $1
    elif [[ $(uname -o) == 'Cygwin' ]]
    then
        ps -e | awk '
        NR==1 {
            for (c=1;c<=NF;c++){
                if ($c == "PID") {
                    break;
                }
            }
        };
        match($0, prog) {
            print $c;
        }' prog="$1"
    else
        ps -eopid,comm | grep mplayer | grep -v grep | awk '{print $1}'
    fi
}


# Stops recording a livestream from a station
stop(){
    if test -f "$pidfile"
        then
        # retrieve the pid from the pidfile
        pid=`cat "$pidfile"`
        # try to terminate mplayer nicely
        kill -SIGTERM $pid >/dev/null 2>/dev/null
        # give mplayer 2 seconds to terminate
        sleep 2
        # if the mplayer process is still running: kill it
        while [[ $(ps -p $pid  | grep $pid) != "" ]]
        do
            echo record.sh stop: Trying to kill process $pid at `date` >> "$errors"
            kill -KILL $pid >/dev/null 2>/dev/null
            # wait for 5 seconds before trying again
            sleep 5
        done
        rm -f $pidfile
    else
        echo record.sh stop: pidfile missing >> "$errors"
        #no pid? kill all mplayers
        # could use pgrep, but as far as I remember that does not work on Cygwin
        for psid in $(_pgrep_ mplayer)
        do
            echo record.sh stop: Trying to kill $psid at `date` >> "$errors"
            kill -KILL $psid 2>> "$errors"
        done
    fi
}

# Prints whether we are currently recording
status(){
    if test -f "$pidfile"
        then
        echo "Mplayer is recording $1"
    else
        echo "Mplayer is not recording $1"
    fi
}

# Displays a usage message
usage(){
    echo "Usage: $0 {start|stop|status} station alias"
}

#main
case "$1" in
start)
    start $2 $3
    error=$?
    if let $error
    then
        echo "\"$0 $*\" failed with error code $error" >> "$errors"
        exit $?
    fi
;;
stop)
    stop
;;
status)
    status $3
;;
*)
    usage
;;
esac
