FM radio on your mp3 player

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 ************" &gt;&gt; "$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" &gt;&gt; "$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.

Leave a Reply

Your email address will not be published. Required fields are marked *