One day I was tired listening local radio and my music collection. I was missing Russian FM station that I listened before. I knew most of them broadcast in Internet and I could grab audio stream and create mp3 file and listen it later when I am driving to work. What I needed to do it is to find or create appropriate tools. I already had server running Slackware Linux 13.0 and I knew it already has many tools. I choose mplayer as recording tool and lame as mp3 encoder.
My starting point was initialization part. I won’t explain first few line, they are self-explanatory. Some comments about next ones. RESDIR is directory where we store our off-line playlists. I just DSTDIR is destination directory, it shared using Samba, so I can have access to my mp3 files from Windows box. LOGDIR is place where we store our debugging logs. NOW contains date and time of script’s start. I used this string as part of file name. TODAY is same but used only date, useful for log filenames.
# # Global variable declaration # MPLAYER="/usr/bin/mplayer" LAME="/usr/local/bin/lame" WGET="/usr/bin/wget" RESDIR="/usr/local/share/radio" DSTDIR="/files/radio" LOGDIR="/var/log/radio" NOW=$(date +"%d-%b-%Y_%H-%M") TODAY=$(date +"%d-%b-%Y")
Next I added help or description function that will show up when we made mistake in command line.
############################################################################### # # Help function # usage() { echo "usage: $0 (start|stop)" echo "radio stations are:" echo "nashe -- Nashe Radio" echo "nrj -- Energy FM" echo "mayak -- Mayak24 FM" echo "pioner -- Pioner FM" echo "montecarlo -- Radio Monte Carlo" echo "silver -- Silver Rain" echo "europeplus -- Europe Plus" echo "retrofm -- Retro FM" exit 1 }
Next part is defining where we are going to get our FM station stream, name of station and prefix for our files. RESFILE is location for station’s playlist, it maybe local or file or on-line. PREFIX is part of our filename that suppose to be in beginning. All our filenames will look like <stationname>_<date_time>.<ext>. ARTIST is how our station name will appear for user or in mp3 tag.
############################################################################### # # Sets vars according to FM station name from command-line # case "$1" in 'silver') RESFILE="http://www.silver.ru/radio/128.m3u" PREFIX="silver" ARTIST="Silver Rain" ;; 'nashe') RESFILE=http://188.127.243.169/nashe-192.m3u PREFIX="nashe" ARTIST="Nashe Radio" ;;
I had problem with Energy FM station. They are sneaky, they use token key to make sure user plays their stream from web-site and not grabbing it using some tool like me. This token key changes every day. I should write script that parse their web-page and use URL for getting stream. I used wget to get page and awk as parser.
'nrj') # These guys use snicky algorithm,they use token that changes once a day. # If you come not from theyr site and/or without token you just get # some commercial crap not music. We should find out token first, then # use it as stream URL. $WGET --quiet -nv "http://www.energyfm.ru/?an=nrj_online_page" -O /tmp/nrj.tmp RESFILE=$(grep filename /tmp/nrj.tmp|awk ' { split($0,i,"=") } { a=i[2] "=" i[3] "=" i[4] "="i [5] } { split(a,i,"\"")} { print i[2] } ') rm -f /tmp/nrj.tmp PREFIX="nrj" ARTIST="Radio Energy" ;;
Next part is just continuation of list of FM stations.
'mayak') RESFILE="$RESDIR/radiomayak_fm.asx" PREFIX="mayak" ARTIST="Radio Mayak FM" ;; 'pioner') RESFILE="$RESDIR/pionerfm.m3u" PREFIX="pioner" ARTIST="Pioner FM" ;; 'montecarlo') RESFILE="$RESDIR/montecarlo.asx" PREFIX="montecarlo" ARTIST="Radio Monte Carlo" ;; 'europeplus') RESFILE="$RESDIR/evropaplus.m3u" PREFIX="europeplus" ARTIST="Europe Plus" ;; 'retrofm') RESFILE="$RESDIR/retrofm.m3u" PREFIX="retrofm" ARTIST="Retro FM" ;; 'test') # This is just for testing of attempts to connect. RESFILE="test.com" PREFIX="test" ARTIST="Testing" ;; *) echo -e "Station name ommited\n" usage ;; esac
Last one is for testing purposes, for debugging case when site is not responding.
On next step we generate generic filename and creating log directory if it is not exist.
# # Creates file name from FM station name and current date and time FILE="$PREFIX"_"$NOW" # # Check if log dir exist and create it if not if [ ! -d "$LOGDIR" ]; then echo "Creating log directory..." mkdir "$LOGDIR" fi
Now I created main body of script, it will start or stop recording. I used case operator again for it.
# # Perform action, start or stop case "$2" in 'start') echo $ARTIST recording... echo ================================ # Creates output folder if doesn't exist if [ ! -d "$DSTDIR" ]; then echo "Creating destination directory..." mkdir "$DSTDIR" fi echo `date +"%m/%d/%Y %H:%M:%S"`": Grabing audio stream..." # Loop makes 5 attemts to connect to server and dump stream to WAV file i=1 # Start of logging mark echo `date +"%m/%d/%Y %H:%M:%S"`": ************ Starting $ARTIST ************" >> "$LOGDIR/$PREFIX"_"$TODAY.log" while [ ! -e /tmp/$FILE.wav ]; do $MPLAYER -v -vo null -vc null -ao pcm:waveheader:file="/tmp/$FILE.wav" -playlist "$RESFILE" >> "$LOGDIR/$PREFIX"_"$TODAY.log" # if no output then it was not successful, wait for 10 sec if [ ! -e "/tmp/$FILE.wav" ]; then echo `date +"%m/%d/%Y %H:%M:%S"`": Something is not right. Attempt #$i, pausing for 10 sec..." sleep 10 fi i=$(($i+1)) # Check if we exceed number of attempts if [ $i -gt 5 ]; then echo "Error: mplayer can't connect after 5 attempts." exit 3 fi done
In lines 117 and 118 we report about starting recording. In 119-123 we create destination directory if it is not exist yet. Sometimes happens we are not able to connect to stream from first attempt, to avoid fail we make 5 attempts, in line 129 we start loop. We will leave this loop if we created output wave file in temporary directory. In line 130 we start mplayer. I used –vo null and –vc null command-line options to prevent any video output because it suppose to work as background process, -ao pcm:waveheader for audio output into wave file and –playlist for stream playlist like that. In line 132 we check temporary file existence, if it is not exist we assume something went wrong, then we report about it and wait for 10 seconds in lines 133, 134. In line 136 we increment counter and check if we exceeded number of attempts in line 138. If we did we assume all 5 attempts was not successful and we give up, report about it and exit with appropriate exit code (lines 139,140).
If attempt was successful our script is still running. We double-check existence of temporary file in line 146 and exit if it doesn’t exist or going to encoding section. First we remove previous mp3 files from destination directory in lines 151-154.
# Worst case scenario, connected, dumped stream but somehow didn't create WAV file if [ ! -e "/tmp/$FILE.wav" ]; then echo "Error: mplayer didn't work well." exit 1 fi # Removing mp3 files from destination dir if [ ! -e "$DSTDIR/$PREFIX*.mp3" ]; then echo `date +"%m/%d/%Y %H:%M:%S"`": Removing files in destination directory..." rm -f $DSTDIR/$PREFIX*.mp3 fi
Command-line options for lame are -S is silent mode, do not show any progress, -b 192 is bit-rate 192 kbps. For tags we use station name as artist tag –ta and date and time of recording as track name –tt. At end we check if mp3 file exists, if no we are reporting about problem and exit with error code in lines 159-162.
# Start to encode WAV into mp3 echo `date +"%m/%d/%Y %H:%M:%S"`": Encoding mp3 files..." $LAME -S -b 192 --ta "$ARTIST" --tt "Air from $NOW" /tmp/$FILE.wav $DSTDIR/$FILE.mp3 # if mp3 file was not created something went bad if [ ! -e "$DSTDIR/$FILE.mp3" ]; then echo "Error: lame didn't work well." exit 2 fi
Otherwise, we remove temporary files and change ownership for Windows users.
# Everything good, removing temporary WAV file echo `date +"%m/%d/%Y %H:%M:%S"`": Removing temporary files..." rm -f /tmp/$FILE.wav # Change ownership for Windows users chown XXXX "$DSTDIR/$FILE.mp3" echo `date +"%m/%d/%Y %H:%M:%S"`": Done." ;;
Next is section for stopping our recording. We cannot just “kill” mplayer process because we can record more than one station at time. We need to find out PIDs of certain station first. In line 172 we figure out if we recording station right now, if not we just are saying there is not recording. Otherwise we trying to nicely terminate mplayer sending QUIT signal to our processes (line 175).
'stop') # Check if mplayer for FM station is realy running if [ "`ps afx|grep mplayer|grep $PREFIX|wc -l`" -ne "0" ]; then echo `date +"%m/%d/%Y %H:%M:%S"`": Stoping $ARTIST recording..." # if yes try to quit all mplayer's threads ps afx|grep mplayer|grep $PREFIX|awk {'print $1'}|xargs -L 1 kill -3 else # else typing info echo "Hmmm... nothing to stop." fi ;; *) echo -e "start or stop ommited\n" usage ;;
Otherwise, if it is not valid command we show script usage information. Here is full script and here is playlist files for on-line stations.