1 #!/bin/bash
   2 ## THIS PROGRAM COMES WITHOUT ANY WARRANTY
   3 ## USE AT YOUR OWN RISK!
   4 ##
   5 ## Nimmt Webradio auf.
   6 ## Die Originalversion geht zurück auf
   7 ## http://groups.google.com/group/de.comp.os.unix.shell/browse_thread/thread/e215f94ccf572331
   8 ## $Id$
   9 ##
  10 ## USAGE:
  11 ## record.sh (start|stop|status) <sendername> <sendung>
  12 ##
  13 ## Dieses Skript ist dafuer gedacht als cron job zu laufen.
  14 ## Ein crontab Eintrag wuerde dabei typischerweise so aussehen:
  15 ## 00 10 * * 6 record.sh start wdr5 WDR5_Krimi_am_Samstag
  16 ## 00 11 * * 6 record.sh stop  wdr5 WDR5_Krimi_am_Samstag
  17 ##
  18 ## Wenn das Ergebnis ein anderes Format als mp3 hat, kann es in
  19 ## der Regel nur mit mplayer wiedergegeben werden.
  20 ##
  21 
  22 ## Setzt das Home-Verzeichnis wenn es noch nicht gesetzt ist
  23 if [[ -z $HOME ]]
  24 then
  25     HOME=/home/$(whoami)
  26 fi
  27 
  28 ## MPlayer-Aufruf als UNIX-Pfad
  29 ## Unter cygwin können Sie den Befehl
  30 ## cygpath -ua 'C:\Programme\mplayer\mplayer.exe'
  31 ## benutzen, um den richtigen Pfad zu bekommen.
  32 mplayer="/usr/bin/mplayer"
  33 
  34 ## Das Zielverzeichnis, in dem die Aufnahmen gespeichert werden.
  35 ## Unter Windows muss das ein Windows-Pfad sein.
  36 #dest='C:\Benutzer\HoerspielNutzer\Downloads\Hoerspiele'
  37 dest="$HOME/aufnahmen"
  38 
  39 ## Automatisches Herunterladen von Metadaten (s. a. Nachverarbeitung)
  40 ## Dokumentation unter http://hspiel.mospace.de/autotag.html
  41 ## Bitte erwaegen Sie eine Spende an hoerdat!
  42 hoerdat_html=true
  43 hoerdat_xml=true     # geht nur mit hoerdat_html
  44 hoerdat_id3=false     # geht nur mit hoerdat_xml
  45 hoerdat_sh=false      # geht nur mit hoerdat_id3
  46 
  47 ## Schneiden (siehe http://hspiel.mospace.de/mp3autocut.html)
  48 ## Nicht schneiden:
  49 autocut=
  50 ## Schnitt-Dateien fuer Mp3DirectCut
  51 # autocut=mpd
  52 #mp3autocut="java -jar -DHoerdatExtraMins=5.0 $dest/mp3autocut.jar"
  53 ## Direkt schneiden
  54 #autocut=mp3
  55 #mp3autocut="java -jar -DHoerdatExtraMins=5.0 $dest/mp3autocut.jar"
  56 
  57 ## Taggen (siehe unter http://hspiel.mospace.de/autotag.html)
  58 run_hoerdat_sh=false  # geht nur mit hoerdat_sh
  59 
  60 ## RSS 2.0 XML-Datei fuer Podcatcher erzeugen.
  61 ## Podcast funktioniert beispielsweise mit gpodder.
  62 ## geht nur mit hoerdat_xml
  63 podcast=false
  64 
  65 ## Wenn nicht gesetzt
  66 #podcastbaseuri=file:///$dest
  67 ## Local web server, e.g. a NanoHTTPD started with
  68 ## java NanoHTTPD -p 8081
  69 #ipaddr=$(ip -4  addr show scope global  | awk '$1=="inet" { print $2 }' | cut -d'/' -f1  | head -n 1)
  70 if [[ -n "$ipaddr" ]]
  71 then
  72     podcastbaseuri=http://$ipaddr:8081
  73 else
  74     podcastbaseuri=
  75 fi
  76 
  77 ## Wenn nicht gesetzt:
  78 #rssfile=rss.xml
  79 rssfile=
  80 
  81 ## Wenn nicht gesetzt
  82 #oldrssfile=$podcastbaseuri/$rssfile
  83 oldrssfile="file:///$dest/$rssfile"
  84 #oldrssfile=
  85 
  86 ## Zusätzliche mplayer-Argumente.
  87 #  mplayerflags="-priority belownormal"
  88 mplayerflags="-nolirc -prefer-ipv4"
  89 
  90 ## UNIX-Pfad oder Adresse der Datei mit den Daten der Webradios.
  91 ## Für jeden Sender gibt es einen Eintrag der Form
  92 ## STATIONNAME URL TYPE
  93 ## wobei TYPE entweder smil, m3u, asx ist (für Playlists)
  94 ## oder die richtige Erweiterung fuer das betreffende Dateiformat.
  95 ## Weder URL noch STATIONNAME duerfen Leerzeichen enthalten,
  96 ## ersetzen Sie Leerzeichen in URLs durch %20.
  97 # /cygdrive/c/Benutzer/HoerspielNutzer/livestreams.txt
  98 # streams="/usr/share/streams.dat"
  99 streams="http://hspiel.mospace.de/livestreams.txt"
 100 
 101 ## Lokale Datei, die die Daten in $streams ergänzt oder überschreibt.
 102 ## Kann auch ungesetzt bleiben.
 103 # localstreams=
 104 # localstreams="$HOME/share/livestreams.txt"
 105 localstreams="$dest/livestreams.txt"
 106 
 107 ## Eine temporäre Datei, in der die Prozess-ID einer Aufnahme abgelegt, wird.
 108 ## Der Name muss für 'start' und 'stop' gleich sein.
 109 pidfile="$dest/.$3-mplayer.pid"
 110 
 111 ## Wohin Fehler geschrieben werden.
 112 ## Auf das Terminal (stderr)
 113 #errors=/dev/stderr
 114 ## Ignorieren
 115 #errors=/dev/null
 116 errors="$dest/.record.sh.log"
 117 
 118 # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
 119 # AB HIER MUSS FUER GEWOEHNLICH NICHTS MEHR GEAENDERT WERDEN!
 120 # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
 121 
 122 # error codes
 123 E_FILE_EXISTS=42
 124 E_NO_STREAM_URL=43
 125 E_NO_STREAM_TYPE=44
 126 E_WRONG_NUMBER_OF_ARGUMENTS=45
 127 E_LOCKFILE_FOUND=46
 128 
 129 # Returns url and type for a station name
 130 # @param station the name of the station
 131 # @return a single string consisting of
 132 #  stationname, url and type separated by a blank
 133 stream(){
 134     local station=$1
 135     result=""
 136     if [[ -f "$localstreams" ]]
 137     then
 138         result=$( grep -m 1 -e ^${station} "$localstreams" )
 139     fi
 140     if [[ -z "$result" ]]
 141     then
 142         if [[ ${streams:0:7} = "http://" ]]
 143         then
 144             result=$( curl $streams 2> /dev/null | grep -m 1 -e ^${station})
 145         else
 146             grep -m 1 -e ^${station} "$streams"
 147         fi
 148     fi
 149     echo $result
 150 }
 151 
 152 # Starts recording a livestream from a station
 153 # @param station the station name
 154 # @param alias a name for the recording
 155 start(){
 156     local station=$1
 157     local alias=$2
 158     local stream=(`stream $station`)
 159     local url=${stream[1]}
 160     local type=${stream[2]}
 161     local dayHourMinute=$(date +'%d-%H-%M')
 162 
 163     if [[ $# -ne 2 ]]
 164     then
 165         usage
 166         return $E_WRONG_NUMBER_OF_ARGUMENTS
 167     fi
 168 
 169     if [[ -z $url ]]
 170     then
 171         echo "No stream URL for $station" >> "$errors"
 172         return $E_NO_STREAM_URL
 173     fi
 174 
 175     if [[ -z $type ]]
 176     then
 177         echo "No stream type for $station" >> "$errors"
 178         return $E_NO_STREAM_TYPE
 179     fi
 180 
 181     local playlist=""
 182     #handle playlists
 183     if [[ "$type" = "smil" || "$type" = "rm" ]]
 184         then
 185         type="ra"
 186         playlist="-playlist"
 187     elif [[ "$type" = "m3u" ]]
 188         then
 189         type="mp3"
 190         playlist="-playlist"
 191     elif  [[ "$type" = "asx" ]]
 192         then type="wma"
 193         playlist="-playlist"
 194     fi
 195 
 196     if test -f "$pidfile"
 197         then
 198         echo "Lockfile found!"
 199         return $E_LOCKFILE_FOUND
 200     fi
 201     echo $$ > $pidfile
 202 
 203     dat=`date '+%Y-%m-%d'`
 204     fname="${alias}_${dat}"
 205     CYG="$(uname  | grep CYGWIN)"
 206     if [[ -n "$CYG" ]]
 207     then
 208         file="${dest}\\${fname}.${type}"
 209     else
 210         file="${dest}/${fname}.${type}"
 211     fi
 212 
 213     if test -f "$file"
 214         then
 215         echo "Target file $file exists." >> "$errors"
 216         return $E_FILE_EXISTS
 217     fi
 218     #echo $file
 219     # unfortunately -endpos does not work with -dumpstream so we cannot use that to terminate mplayer
 220     "$mplayer" $playlist $url $mplayerflags -msglevel all=2 -dumpstream -dumpfile "$file"  >/dev/null 2>>"$errors" <&2 &
 221     if [[ ! $? ]]
 222     then
 223         echo "FAILED: $mplayer $playlist $url $mplayerflags -msglevel all=2 -dumpstream -dumpfile $file"
 224         return $?
 225     fi
 226     echo $! >$pidfile
 227     disown
 228 
 229     echo "$file" >> $pidfile
 230 
 231     if [[ "$hoerdat_html" = "true" ]]
 232     then
 233         local html="${dest}/${fname}.html"
 234         local xmln="${dest}/${fname}.xml"
 235         local xml1="${dest}/${fname}~1.xml"
 236         local id3="${dest}/${fname}.id3"
 237         local sh="${dest}/${fname}.sh"
 238         local mp3="${fname}.mp3"
 239         local hoerdatsender=
 240         case $station in
 241         einslive)
 242             hoerdatsender=wdr
 243             ;;
 244         *)
 245             # use first three letters with numbers removed
 246             hoerdatsender=$(echo ${station:0:3} | sed 's/[0-9]//')
 247             ;;
 248         esac
 249 
 250         curl -s http://www.xn--hrdat-jua.de/index.php?dat=${dat}\&sender=${hoerdatsender} \
 251             | tidy -asxhtml -numeric \
 252             > "$html" \
 253             2> /dev/null
 254         if [[ "$hoerdat_xml" == true ]]
 255         then
 256 
 257             # Extract data as XML from XHTML
 258             xsltproc -novalid -nodtdattr http://hspiel.mospace.de/xslt/hoerdat-parser2.xsl "$html" \
 259                 > "${xmln}" \
 260                 2>> "$errors"
 261             # Try to select the currently running program
 262             xsltproc --stringparam day-hour-minute $dayHourMinute \
 263                 http://hspiel.mospace.de/xslt/hoerdat-select.xsl \
 264                 "${xmln}" \
 265                 > "${xml1}" \
 266                 2>> "$errors"
 267             # If we have found a matching entry use it, else remove the empty output file
 268             if [[ $(stat -c %s "${xml1}") -gt "80" ]]
 269             then
 270                 mv "${xml1}" "${xmln}"
 271             else
 272                 rm "${xml1}"
 273             fi
 274 
 275             if [[ "$hoerdat_id3" == "true" ]]
 276             then
 277                 # Generate ID3 XML
 278                 xsltproc http://hspiel.mospace.de/xslt/hoerdat-id3.xsl "${xmln}" \
 279                     > "$id3" \
 280                     2>> "$errors"
 281 
 282                 if [[ "$hoerdat_sh" == "true" ]]
 283                 then
 284                     # Generate bash script for tagging
 285                     echo '#!/bin/bash' > "$sh"
 286                     cmd=$(xsltproc http://hspiel.mospace.de/xslt/id3-bash.xsl "$id3") \
 287                         >> "$sh" \
 288                         2>> "$errors"
 289                     echo $cmd $mp3 >> "$sh"
 290                 fi
 291             fi
 292         fi
 293     fi
 294 
 295 }
 296 
 297 # pgrep substitute
 298 function _pgrep_(){
 299     if which pgrep >/dev/null 2>/dev/null
 300     then
 301         pgrep $1
 302     elif [[ $(uname -o) == 'Cygwin' ]]
 303     then
 304         ps -e | awk '
 305         NR==1 {
 306             for (c=1;c<=NF;c++){
 307                 if ($c == "PID") {
 308                     break;
 309                 }
 310             }
 311         };
 312         match($0, prog) {
 313             print $c;
 314         }' prog="$1"
 315     else
 316         ps -eopid,comm | grep mplayer | grep -v grep | awk '{print $1}'
 317     fi
 318 }
 319 
 320 # Creates or extends an RSS 2.0 podcast file
 321 # @param audiofile the absolute path of the audio file
 322 makepodcast(){
 323     audiofile=$1
 324     hoerdatxml="${audiofile%%mp3}xml"
 325     if [[ "$podcast" == "true" && -f "$hoerdatxml" ]]
 326     then
 327         mp3="$audiofile"
 328 
 329         if [[ -z $podcastbaseuri ]]
 330         then
 331             podcastbaseuri="file:///$dest"
 332         fi
 333 
 334         fname=$(basename "$mp3")
 335         mp3fileuri="$podcastbaseuri/$fname"
 336         mp3filesize=$(stat -c %s "$mp3")
 337         pubdate=$(date -R)
 338 
 339         if [[ -z $rssfile ]]
 340         then
 341             rssfile=rss.xml
 342         fi
 343 
 344         rsspath="$dest/$rssfile"
 345 
 346         oldrssfileuri=$podcastbaseuri/$rssfile
 347 
 348         newxml=$(xsltproc \
 349             --stringparam mp3-file-size $mp3filesize \
 350             --stringparam mp3-file-uri "$mp3fileuri" \
 351             --stringparam pubDate "$pubdate" \
 352             --stringparam old-rss-file "$oldrssfileuri" \
 353             http://hspiel.mospace.de/xslt/podcast.xsl \
 354             "$hoerdatxml") 2>> "$errors"
 355         if [[ $? -eq 0 ]]
 356         then
 357             echo "$newxml" > $rsspath
 358         fi
 359     fi
 360 }
 361 
 362 # Performs autocutting if autocutting is active
 363 # @param audiofile
 364 cut(){
 365     audiofile=$1
 366     if [[ -n "$autocut" && -n "$mp3autocut" ]]
 367     then
 368         if [[ "$autocut" -eq "mp3" ]]
 369         then
 370             $mp3autocut mp3 /var/tmp "$audiofile" >/dev/null 2>> /dev/null
 371             tmpfile=/var/tmp/$(basename "$audiofile")
 372             if [[ -f "$tmpfile" ]]
 373             then
 374                 mv "$audiofile" "$audiofile.original"
 375                 mv /var/tmp/$(basename "$audiofile") "$audiofile"
 376             fi
 377         else
 378             $mp3autocut "$autocut" "$audiofile" >/dev/null 2>> /dev/null
 379         fi
 380     fi
 381 }
 382 
 383 # Performs autotagging if autotagging is active.
 384 # @param audiofile The file to tag
 385 tag(){
 386     audiofile=$1
 387     hoerdatsh="${audiofile%%mp3}sh"
 388     if [[ "$run_hoerdat_sh" -eq "true" && -f "$hoerdatsh" ]]
 389     then
 390         olddir=$PWD
 391         cd $dest
 392         source $hoerdatsh 2>> "$errors"
 393         cd $olddir
 394     fi
 395 }
 396 
 397 
 398 # Stops recording a livestream from a station
 399 stop(){
 400     audiofile=
 401     if test -f "$pidfile"
 402         then
 403 
 404         # retrieve the pid from the pidfile
 405         pid=`head -n 1 "$pidfile"`
 406 
 407         # read the audio file name from the pidfile
 408         audiofile=$(tail -n +2 "$pidfile" | head -n 1)
 409 
 410         # try to terminate mplayer nicely
 411         kill -SIGTERM $pid >/dev/null 2>/dev/null
 412 
 413         # give mplayer 2 seconds to terminate
 414         sleep 2
 415 
 416         # if the mplayer process is still running: kill it
 417         while [[ $(ps -p $pid  | grep $pid) != "" ]]
 418         do
 419             echo record.sh stop: Trying to kill process $pid at `date` >> "$errors"
 420             kill -KILL $pid >/dev/null 2>/dev/null
 421 
 422             # wait for 5 seconds before trying again
 423             sleep 5
 424         done
 425         rm -f $pidfile
 426     else
 427         echo record.sh stop: pidfile missing >> "$errors"
 428 
 429         # no pid? kill all mplayers
 430         # could use pgrep, but as far as I remember that does not work on Cygwin
 431         for psid in $(_pgrep_ mplayer)
 432         do
 433             echo record.sh stop: Trying to kill $psid at `date` >> "$errors"
 434             kill -KILL $psid 2>> "$errors"
 435         done
 436     fi
 437 
 438     # post-processing
 439     if [[ -n "$audiofile" ]]
 440     then
 441         cut $audiofile
 442         tag $audiofile
 443         makepodcast $audiofile
 444     fi
 445 }
 446 
 447 # Prints whether we are currently recording
 448 status(){
 449     if test -f "$pidfile"
 450         then
 451         echo "Mplayer is recording $1"
 452     else
 453         echo "Mplayer is not recording $1"
 454     fi
 455 }
 456 
 457 # Displays a usage message
 458 usage(){
 459     echo "Usage: $0 {start|stop|status} station alias"
 460 }
 461 
 462 #main
 463 case "$1" in
 464 start)
 465     start $2 $3
 466     error=$?
 467     if let $error
 468     then
 469         echo "\"$0 $*\" failed with error code $error" >> "$errors"
 470         exit $?
 471     fi
 472 ;;
 473 stop)
 474     stop
 475 ;;
 476 status)
 477     status $3
 478 ;;
 479 *)
 480     usage
 481 ;;
 482 esac