1:#!/bin/bash
   2:# THIS PROGRAM COMES WITHOUT ANY WARRANTY
   3:# USE AT YOUR OWN RISK!
   4:#
   5:# Records a livestream from an internet radio station.
   6:# The original version of record.sh was derived from
   7:# http://groups.google.com/group/de.comp.os.unix.shell/browse_thread/thread/e215f94ccf572331
   8:# Last changed: 2009-08-10
   9:# USAGE:
  10:# record.sh (start|stop|status) station name_of_program
  11:#
  12:# This script is designed to run as a cron job.
  13:# A typical user crontab entry would look like this:
  14:# 00 10 * * 6 record.sh start wdr5 WDR5_Krimi_am_Samstag
  15:# 00 11 * * 6 record.sh stop  wdr5 WDR5_Krimi_am_Samstag
  16:#
  17:# Livestreams are dumped as raw streams and can in general be played with
  18:# mplayer only. Use
  19:# mplayer recordedfile -ao pcm:file=fifo & lame fifo
  20:# to transcode to mp3 (fifos don't seem to work under cygwin,
  21:# you probably need to use a non-fifo temporary wave file).
  22:#
  23:
  24:# Sets the home directory if it has not previously been set
  25:if [[ -z $HOME ]]
  26:then
  27:    HOME=/home/$(whoami)
  28:fi
  29:
  30:# how to invoke the mplayer executable
  31:# Under cygwin start a cygwin bash shell and type e. g.
  32:# cygpath -ua 'C:\Programme\mplayer\mplayer.exe'
  33:# to get a UNIX-style path
  34:mplayer="/usr/bin/mplayer"
  35:
  36:# Would you like to download metadata from hoerdat?
  37:# see http://hspiel.mospace.de/autotag.html
  38:# Bitte erwaegen Sie eine Spende an hoerdat!
  39:hoerdat_html=false
  40:hoerdat_xml=false     # depends on hoerdat_html
  41:hoerdat_id3=false     # depends on hoerdat_xml
  42:hoerdat_sh=false      # depends on hoerdat_id3
  43:
  44:# Additional mplayerflags. This is optional. Under windows you might want to set
  45:# the process priority for mplayer using
  46:#  mplayerflags="-priority belownormal"
  47:# a user has reported this to avoid race conditions when two mplayers are
  48:# running at the same time.
  49:mplayerflags="-nolirc -prefer-ipv4"
  50:
  51:# The location of the stream info file
  52:# This can be a local file or a web URL
  53:# It has one line for each stream with the structure
  54:# STATIONNAME URL TYPE
  55:# where TYPE is one of smil, m3u, asx for playlists
  56:# or the proper file extension for the stream format
  57:# of the respective internet radio station.
  58:# Neither URL nor STATIONNAME may contain any blanks
  59:# Replace blanks in URLs by %20.
  60:# Under cygwin start a cygwin bash shell and type e. g.
  61:# cygpath -ua 'C:\downloads\livestreams.txt'
  62:# to get a UNIX-style path
  63:# streams="/usr/share/streams.dat"
  64:streams="http://hspiel.mospace.de/livestreams.txt"
  65:# A local stream info file that complements or overrides
  66:# definitions in $streams.
  67:# localstreams="$HOME/share/livestreams.txt"
  68:#localstreams=
  69:
  70:# A temporary file where the process id of a recording is
  71:# saved as long as it is running
  72:# If you don't know any better try
  73:# pidfile=~/.$3-mplayer.pid
  74:pidfile="$HOME/aufnahmen/.$3-mplayer.pid"
  75:
  76:# The place where recordings are stored. When using the
  77:# Win32 (MinGW) port of mplayer this must be a windows
  78:# path (with backslashes).
  79:dest="$HOME/aufnahmen"
  80:
  81:# Where errors are logged.
  82:errors="$dest/record.sh.errors"
  83:# Write errors to stderr (console). Not recommended when running record.sh
  84:# as a cron job, but useful when testing record.sh on the command line.
  85:#errors=/dev/stderr
  86:# Ignore errors
  87:#errors=/dev/null
  88:
  89:# error codes
  90:E_FILE_EXISTS=42
  91:E_NO_STREAM_URL=43
  92:E_NO_STREAM_TYPE=44
  93:E_WRONG_NUMBER_OF_ARGUMENTS=45
  94:E_LOCKFILE_FOUND=46
  95:
  96:# Returns url and type for a station name
  97:# @param station the name of the station
  98:# @return a single string consisting of
  99:#  stationname, url and type separated by a blank
 100:stream(){
 101:    local station=$1
 102:    result=""
 103:    if [[ -f "$localstreams" ]]
 104:    then
 105:        result=$( grep -m 1 -e ^${station} "$localstreams" )
 106:    fi
 107:    if [[ -z "$result" ]]
 108:    then
 109:        if [[ ${streams:0:7} = "http://" ]]
 110:        then
 111:            result=$( curl $streams 2> /dev/null | grep -m 1 -e ^${station})
 112:        else
 113:            grep -m 1 -e ^${station} "$streams"
 114:        fi
 115:    fi
 116:    echo $result
 117:}
 118:
 119:# Starts recording a livestream from a station
 120:# @param station the station name
 121:# @param alias a name for the recording
 122:start(){
 123:    local station=$1
 124:    local alias=$2
 125:    local stream=(`stream $station`)
 126:    local url=${stream[1]}
 127:    local type=${stream[2]}
 128:    local dayHourMinute=$(date +'%d-%H-%M')
 129:
 130:    if [[ $# -ne 2 ]]
 131:    then
 132:        usage
 133:        return $E_WRONG_NUMBER_OF_ARGUMENTS
 134:    fi
 135:
 136:    if [[ -z $url ]]
 137:    then
 138:        echo "No stream URL for $station" >> "$errors"
 139:        return $E_NO_STREAM_URL
 140:    fi
 141:
 142:    if [[ -z $type ]]
 143:    then
 144:        echo "No stream type for $station" >> "$errors"
 145:        return $E_NO_STREAM_TYPE
 146:    fi
 147:
 148:    local playlist=""
 149:    #handle playlists
 150:    if [[ "$type" = "smil" || "$type" = "rm" ]]
 151:        then
 152:        type="ra"
 153:        playlist="-playlist"
 154:    elif [[ "$type" = "m3u" ]]
 155:        then
 156:        type="mp3"
 157:        playlist="-playlist"
 158:    elif  [[ "$type" = "asx" ]]
 159:        then type="wma"
 160:        playlist="-playlist"
 161:    fi
 162:
 163:    if test -f "$pidfile"
 164:        then
 165:        echo "Lockfile found!"
 166:        return $E_LOCKFILE_FOUND
 167:    fi
 168:    echo $$ > $pidfile
 169:
 170:    dat=`date '+%Y-%m-%d'`
 171:    fname="${alias}_${dat}"
 172:    CYG="$(uname  | grep CYGWIN)"
 173:    if [[ -n "$CYG" ]]
 174:    then
 175:        file="${dest}\\${fname}.${type}"
 176:    else
 177:        file="${dest}/${fname}.${type}"
 178:    fi
 179:
 180:    if test -f "$file"
 181:        then
 182:        echo "Target file $file exists." >> "$errors"
 183:        return $E_FILE_EXISTS
 184:    fi
 185:    #echo $file
 186:    # unfortunately -endpos does not work with -dumpstream so we cannot use that to terminate mplayer
 187:    "$mplayer" $playlist $url $mplayerflags -msglevel all=2 -dumpstream -dumpfile "$file"  >/dev/null 2>>"$errors" <&2 &
 188:    if [[ ! $? ]]
 189:    then
 190:        echo "FAILED: $mplayer $playlist $url $mplayerflags -msglevel all=2 -dumpstream -dumpfile $file"
 191:        return $?
 192:    fi
 193:    echo $! >$pidfile
 194:    disown
 195:
 196:    if [[ "$hoerdat_html" = "true" ]]
 197:    then
 198:        local html="${dest}/${fname}.html"
 199:        local xmln="${dest}/${fname}.xml"
 200:        local xml1="${dest}/${fname}~1.xml"
 201:        local id3="${dest}/${fname}.id3"
 202:        local sh="${dest}/${fname}.sh"
 203:        local mp3="${fname}.mp3"
 204:        local hoerdatsender=
 205:        case $station in
 206:        einslive)
 207:            hoerdatsender=wdr
 208:            ;;
 209:        *)
 210:            # use first three letters with numbers removed
 211:            hoerdatsender=$(echo ${station:0:3} | sed 's/[0-9]//')
 212:            ;;
 213:        esac
 214:
 215:        curl -s http://www.hoerdat.in-berlin.de/heute.php?dat=${dat}\&sender=${hoerdatsender} \
 216:            | tidy -asxhtml -numeric \
 217:            > "$html" \
 218:            2> /dev/null
 219:        if [[ "$hoerdat_xml" == true ]]
 220:        then
 221:
 222:            # Extract data as XML from XHTML
 223:            xsltproc http://hspiel.mospace.de/xslt/hoerdat-parser.xsl "$html" \
 224:                > "${xmln}" \
 225:                2>> "$errors"
 226:            # Try to select the currently running program
 227:            xsltproc --stringparam day-hour-minute $dayHourMinute \
 228:                http://hspiel.mospace.de/xslt/hoerdat-select.xsl \
 229:                "${xmln}" \
 230:                > "${xml1}" \
 231:                2>> "$errors"
 232:            # If we have found a matching entry use it, else remove the empty output file
 233:            if [[ $(stat -c %s "${xml1}") -gt "80" ]]
 234:            then
 235:                mv "${xml1}" "${xmln}"
 236:            else
 237:                rm "${xml1}"
 238:            fi
 239:
 240:            if [[ "$hoerdat_id3" == "true" ]]
 241:            then
 242:                # Generate ID3 XML
 243:                xsltproc http://hspiel.mospace.de/xslt/hoerdat-id3.xsl "${xmln}" \
 244:                    > "$id3" \
 245:                    2>> "$errors"
 246:
 247:                if [[ "$hoerdat_sh" == "true" ]]
 248:                then
 249:                    # Generate bash script for tagging
 250:                    echo '#!/bin/bash' > "$sh"
 251:                    cmd=$(xsltproc http://hspiel.mospace.de/xslt/id3-bash.xsl "$id3") \
 252:                        >> "$sh" \
 253:                        2>> "$errors"
 254:                    echo $cmd $mp3 >> "$sh"
 255:                fi
 256:            fi
 257:        fi
 258:    fi
 259:
 260:}
 261:
 262:# pgrep substitute
 263:function _pgrep_(){
 264:    if which pgrep >/dev/null 2>/dev/null
 265:    then
 266:        pgrep $1
 267:    elif [[ $(uname -o) == 'Cygwin' ]]
 268:    then
 269:        ps -e | awk '
 270:        NR==1 {
 271:            for (c=1;c<=NF;c++){
 272:                if ($c == "PID") {
 273:                    break;
 274:                }
 275:            }
 276:        };
 277:        match($0, prog) {
 278:            print $c;
 279:        }' prog="$1"
 280:    else
 281:        ps -eopid,comm | grep mplayer | grep -v grep | awk '{print $1}'
 282:    fi
 283:}
 284:
 285:
 286:# Stops recording a livestream from a station
 287:stop(){
 288:    if test -f "$pidfile"
 289:        then
 290:        # retrieve the pid from the pidfile
 291:        pid=`cat "$pidfile"`
 292:        # try to terminate mplayer nicely
 293:        kill -SIGTERM $pid >/dev/null 2>/dev/null
 294:        # give mplayer 2 seconds to terminate
 295:        sleep 2
 296:        # if the mplayer process is still running: kill it
 297:        while [[ $(ps -p $pid  | grep $pid) != "" ]]
 298:        do
 299:            echo record.sh stop: Trying to kill process $pid at `date` >> "$errors"
 300:            kill -KILL $pid >/dev/null 2>/dev/null
 301:            # wait for 5 seconds before trying again
 302:            sleep 5
 303:        done
 304:        rm -f $pidfile
 305:    else
 306:        echo record.sh stop: pidfile missing >> "$errors"
 307:        #no pid? kill all mplayers
 308:        # could use pgrep, but as far as I remember that does not work on Cygwin
 309:        for psid in $(_pgrep_ mplayer)
 310:        do
 311:            echo record.sh stop: Trying to kill $psid at `date` >> "$errors"
 312:            kill -KILL $psid 2>> "$errors"
 313:        done
 314:    fi
 315:}
 316:
 317:# Prints whether we are currently recording
 318:status(){
 319:    if test -f "$pidfile"
 320:        then
 321:        echo "Mplayer is recording $1"
 322:    else
 323:        echo "Mplayer is not recording $1"
 324:    fi
 325:}
 326:
 327:# Displays a usage message
 328:usage(){
 329:    echo "Usage: $0 {start|stop|status} station alias"
 330:}
 331:
 332:#main
 333:case "$1" in
 334:start)
 335:    start $2 $3
 336:    error=$?
 337:    if let $error
 338:    then
 339:        echo "\"$0 $*\" failed with error code $error" >> "$errors"
 340:        exit $?
 341:    fi
 342:;;
 343:stop)
 344:    stop
 345:;;
 346:status)
 347:    status $3
 348:;;
 349:*)
 350:    usage
 351:;;
 352:esac