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