{"id":362,"date":"2011-07-03T16:49:44","date_gmt":"2011-07-03T21:49:44","guid":{"rendered":"http:\/\/www.lazarenko.us\/?p=362"},"modified":"2011-07-03T17:03:14","modified_gmt":"2011-07-03T22:03:14","slug":"fm-radio-on-your-mp3-player-2","status":"publish","type":"post","link":"https:\/\/www.lazarenko.us\/?p=362","title":{"rendered":"FM radio on your mp3 player"},"content":{"rendered":"<p>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 <a href=\"http:\/\/www.mplayerhq.hu\">mplayer<\/a> as recording tool and <a href=\"http:\/\/lame.sourceforge.net\/\">lame<\/a> as mp3 encoder.<br \/>\n<!--more--><br \/>\nMy starting point was initialization part. I won\u2019t explain first few line, they are self-explanatory. Some comments about next ones. <strong>RESDIR<\/strong> is directory where we store our off-line playlists. I just <strong>DSTDIR<\/strong> is destination directory, it shared using Samba, so I can have access to my mp3 files from Windows box. <strong>LOGDIR<\/strong> is place where we store our debugging logs. NOW contains date and time of script\u2019s start. I used this string as part of file name. <strong>TODAY<\/strong> is same but used only date, useful for log filenames.<\/p>\n<pre class=\"brush: bash; first-line: 8; title: ; notranslate\" title=\"\">#\r\n# Global variable declaration\r\n#\r\nMPLAYER=&quot;\/usr\/bin\/mplayer&quot;\r\nLAME=&quot;\/usr\/local\/bin\/lame&quot;\r\nWGET=&quot;\/usr\/bin\/wget&quot;\r\nRESDIR=&quot;\/usr\/local\/share\/radio&quot;\r\nDSTDIR=&quot;\/files\/radio&quot;\r\nLOGDIR=&quot;\/var\/log\/radio&quot;\r\nNOW=$(date +&quot;%d-%b-%Y_%H-%M&quot;)\r\nTODAY=$(date +&quot;%d-%b-%Y&quot;)<\/pre>\n<p>Next I added help or description function that will show up when we made mistake in command line.<\/p>\n<pre class=\"brush: bash; first-line: 20; title: ; notranslate\" title=\"\">###############################################################################\r\n#\r\n# Help function\r\n#\r\nusage() {\r\n     echo &quot;usage: $0  (start|stop)&quot;\r\n     echo &quot;radio stations are:&quot;\r\n     echo &quot;nashe      -- Nashe Radio&quot;\r\n     echo &quot;nrj        -- Energy FM&quot;\r\n     echo &quot;mayak      -- Mayak24 FM&quot;\r\n     echo &quot;pioner     -- Pioner FM&quot;\r\n     echo &quot;montecarlo -- Radio Monte Carlo&quot;\r\n     echo &quot;silver     -- Silver Rain&quot;\r\n     echo &quot;europeplus -- Europe Plus&quot;\r\n     echo &quot;retrofm    -- Retro FM&quot;\r\n     exit 1\r\n}<\/pre>\n<p>Next part is defining where we are going to get our FM station stream, name of station and prefix for our files. <strong>RESFILE<\/strong> is location for station&#8217;s playlist, it maybe local or file or on-line. <strong>PREFIX<\/strong> is part of our filename that suppose to be in beginning. All our filenames will look like &lt;stationname&gt;_&lt;date_time&gt;.&lt;ext&gt;. <strong>ARTIST<\/strong> is how our station name will appear for user or in mp3 tag.<\/p>\n<pre class=\"brush: bash; first-line: 36; title: ; notranslate\" title=\"\">\r\n###############################################################################\r\n#\r\n# Sets vars according to FM station name from command-line\r\n#\r\ncase &quot;$1&quot; in\r\n    'silver')\r\n    RESFILE=&quot;http:\/\/www.silver.ru\/radio\/128.m3u&quot;\r\n    PREFIX=&quot;silver&quot;\r\n    ARTIST=&quot;Silver Rain&quot;\r\n    ;;\r\n    'nashe')\r\n    RESFILE=http:\/\/188.127.243.169\/nashe-192.m3u\r\n    PREFIX=&quot;nashe&quot;\r\n    ARTIST=&quot;Nashe Radio&quot;\r\n    ;;<\/pre>\n<p>I had problem with <a href=\"http:\/\/www.energyfm.ru\">Energy FM<\/a> 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 <a href=\"http:\/\/www.gnu.org\/software\/wget\/\">wget<\/a> to get page and <a href=\"http:\/\/en.wikipedia.org\/wiki\/AWK\">awk<\/a> as parser.<\/p>\n<pre class=\"brush: bash; first-line: 51; title: ; notranslate\" title=\"\">\t'nrj')\r\n    # These guys use snicky algorithm,they use token that changes once a day.\r\n    # If you come not from theyr site and\/or without token you just get\r\n    # some commercial crap not music. We should find out token first, then\r\n    # use it as stream URL.\r\n    $WGET --quiet -nv &quot;http:\/\/www.energyfm.ru\/?an=nrj_online_page&quot; -O \/tmp\/nrj.tmp\r\n    RESFILE=$(grep filename \/tmp\/nrj.tmp|awk '\r\n    { split($0,i,&quot;=&quot;) }\r\n    { a=i&#x5B;2] &quot;=&quot; i&#x5B;3] &quot;=&quot; i&#x5B;4] &quot;=&quot;i &#x5B;5] }\r\n    { split(a,i,&quot;\\&quot;&quot;)}\r\n    { print i&#x5B;2] }\r\n    ')\r\n    rm -f \/tmp\/nrj.tmp\r\n    PREFIX=&quot;nrj&quot;\r\n    ARTIST=&quot;Radio Energy&quot;\r\n    ;;<\/pre>\n<p>Next part is just continuation of list of FM stations.<\/p>\n<pre class=\"brush: bash; first-line: 67; title: ; notranslate\" title=\"\">    'mayak')\r\n    RESFILE=&quot;$RESDIR\/radiomayak_fm.asx&quot;\r\n    PREFIX=&quot;mayak&quot;\r\n    ARTIST=&quot;Radio Mayak FM&quot;\r\n    ;;\r\n    'pioner')\r\n    RESFILE=&quot;$RESDIR\/pionerfm.m3u&quot;\r\n    PREFIX=&quot;pioner&quot;\r\n    ARTIST=&quot;Pioner FM&quot;\r\n    ;;\r\n    'montecarlo')\r\n    RESFILE=&quot;$RESDIR\/montecarlo.asx&quot;\r\n    PREFIX=&quot;montecarlo&quot;\r\n    ARTIST=&quot;Radio Monte Carlo&quot;\r\n    ;;\r\n    'europeplus')\r\n    RESFILE=&quot;$RESDIR\/evropaplus.m3u&quot;\r\n    PREFIX=&quot;europeplus&quot;\r\n    ARTIST=&quot;Europe Plus&quot;\r\n    ;;\r\n    'retrofm')\r\n    RESFILE=&quot;$RESDIR\/retrofm.m3u&quot;\r\n    PREFIX=&quot;retrofm&quot;\r\n    ARTIST=&quot;Retro FM&quot;\r\n    ;;\r\n    'test')\r\n    # This is just for testing of attempts to connect.\r\n    RESFILE=&quot;test.com&quot;\r\n    PREFIX=&quot;test&quot;\r\n    ARTIST=&quot;Testing&quot;\r\n    ;;\r\n    *)\r\n    echo -e &quot;Station name ommited\\n&quot;\r\n    usage\r\n    ;;\r\nesac<\/pre>\n<p>Last one is for testing purposes, for debugging case when site is not responding.<br \/>\nOn next step we generate generic filename and creating log directory if it is not exist.<\/p>\n<pre class=\"brush: bash; first-line: 104; title: ; notranslate\" title=\"\">#\r\n# Creates file name from FM station name and current date and time\r\nFILE=&quot;$PREFIX&quot;_&quot;$NOW&quot;\r\n#\r\n# Check if log dir exist and create it if not\r\nif &#x5B; ! -d &quot;$LOGDIR&quot; ]; then\r\n     echo &quot;Creating log directory...&quot;\r\n     mkdir &quot;$LOGDIR&quot;\r\nfi<\/pre>\n<p>Now I created main body of script, it will start or stop recording. I used <strong>case<\/strong> operator again for it.<\/p>\n<pre class=\"brush: bash; first-line: 113; title: ; notranslate\" title=\"\">#\r\n# Perform action, start or stop\r\ncase &quot;$2&quot; in\r\n    'start')\r\n    echo $ARTIST recording...\r\n    echo ================================\r\n    # Creates output folder if doesn't exist\r\n    if &#x5B; ! -d &quot;$DSTDIR&quot; ]; then\r\n        echo &quot;Creating destination directory...&quot;\r\n        mkdir &quot;$DSTDIR&quot;\r\n    fi\r\n    echo `date +&quot;%m\/%d\/%Y %H:%M:%S&quot;`&quot;: Grabing audio stream...&quot;\r\n    # Loop makes 5 attemts to connect to server and dump stream to WAV file\r\n    i=1\r\n    # Start of logging mark\r\n    echo `date +&quot;%m\/%d\/%Y %H:%M:%S&quot;`&quot;: ************ Starting $ARTIST ************&quot; &amp;gt;&amp;gt; &quot;$LOGDIR\/$PREFIX&quot;_&quot;$TODAY.log&quot;\r\n    while &#x5B; ! -e \/tmp\/$FILE.wav ]; do\r\n        $MPLAYER -v -vo null -vc null -ao pcm:waveheader:file=&quot;\/tmp\/$FILE.wav&quot; -playlist &quot;$RESFILE&quot; &amp;gt;&amp;gt; &quot;$LOGDIR\/$PREFIX&quot;_&quot;$TODAY.log&quot;\r\n        # if no output then it was not successful, wait for 10 sec\r\n        if &#x5B; ! -e &quot;\/tmp\/$FILE.wav&quot; ]; then\r\n            echo `date +&quot;%m\/%d\/%Y %H:%M:%S&quot;`&quot;: Something is not right. Attempt #$i, pausing for 10 sec...&quot;\r\n            sleep 10\r\n        fi\r\n        i=$(($i+1))\r\n        # Check if we exceed number of attempts\r\n        if &#x5B; $i -gt 5 ]; then\r\n            echo &quot;Error: mplayer can't connect after 5 attempts.&quot;\r\n            exit 3\r\n        fi\r\n    done<\/pre>\n<p>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 <strong>\u2013vo null<\/strong> and <strong>\u2013vc null<\/strong> command-line options to prevent any video output because it suppose to work as background process, <strong>-ao pcm:waveheader<\/strong> for audio output into wave file and <strong>\u2013playlist<\/strong> 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).<\/p>\n<p>If attempt was successful our script is still running. We double-check existence of temporary file in line 146 and exit if it doesn&#8217;t exist or going to encoding section. First we remove previous mp3 files from destination directory in lines 151-154.<\/p>\n<pre class=\"brush: bash; first-line: 145; title: ; notranslate\" title=\"\">    # Worst case scenario, connected, dumped stream but somehow didn't create WAV file\r\n    if &#x5B; ! -e &quot;\/tmp\/$FILE.wav&quot; ]; then\r\n        echo &quot;Error: mplayer didn't work well.&quot;\r\n        exit 1\r\n    fi\r\n    # Removing mp3 files from destination dir\r\n    if &#x5B; ! -e &quot;$DSTDIR\/$PREFIX*.mp3&quot; ]; then\r\n        echo `date +&quot;%m\/%d\/%Y %H:%M:%S&quot;`&quot;: Removing files in destination directory...&quot;\r\n        rm -f $DSTDIR\/$PREFIX*.mp3\r\n    fi<\/pre>\n<p>Command-line options for lame are <strong>-S<\/strong> is silent mode, do not show any progress, <strong>-b 192<\/strong> is bit-rate 192 kbps. For tags we use station name as artist tag <strong>&#8211;ta<\/strong> and date and time of recording as track name <strong>&#8211;tt<\/strong>. At end we check if mp3 file exists, if no we are reporting about problem and exit with error code in lines 159-162.<\/p>\n<pre class=\"brush: bash; first-line: 155; title: ; notranslate\" title=\"\">    # Start to encode WAV into mp3\r\n    echo `date +&quot;%m\/%d\/%Y %H:%M:%S&quot;`&quot;: Encoding mp3 files...&quot;\r\n    $LAME -S -b 192 --ta &quot;$ARTIST&quot; --tt &quot;Air from $NOW&quot; \/tmp\/$FILE.wav $DSTDIR\/$FILE.mp3\r\n    # if mp3 file was not created something went bad\r\n    if &#x5B; ! -e &quot;$DSTDIR\/$FILE.mp3&quot; ]; then\r\n        echo &quot;Error: lame didn't work well.&quot;\r\n        exit 2\r\n    fi<\/pre>\n<p>Otherwise, we remove temporary files and change ownership for Windows users.<\/p>\n<pre class=\"brush: bash; first-line: 163; title: ; notranslate\" title=\"\">    # Everything good, removing temporary WAV file\r\n    echo `date +&quot;%m\/%d\/%Y %H:%M:%S&quot;`&quot;: Removing temporary files...&quot;\r\n    rm -f \/tmp\/$FILE.wav\r\n    # Change ownership for Windows users\r\n    chown XXXX &quot;$DSTDIR\/$FILE.mp3&quot;\r\n    echo `date +&quot;%m\/%d\/%Y %H:%M:%S&quot;`&quot;: Done.&quot;\r\n    ;;<\/pre>\n<p>Next is section for stopping our recording. We cannot just &#8220;kill&#8221; 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).<\/p>\n<pre class=\"brush: bash; first-line: 170; title: ; notranslate\" title=\"\">    'stop')\r\n    # Check if mplayer for FM station is realy running\r\n    if &#x5B; &quot;`ps afx|grep mplayer|grep $PREFIX|wc -l`&quot; -ne &quot;0&quot; ]; then\r\n        echo `date +&quot;%m\/%d\/%Y %H:%M:%S&quot;`&quot;: Stoping $ARTIST recording...&quot;\r\n        # if yes try to quit all mplayer's threads\r\n        ps afx|grep mplayer|grep $PREFIX|awk {'print $1'}|xargs -L 1 kill -3\r\n    else\r\n        # else typing info\r\n        echo &quot;Hmmm... nothing to stop.&quot;\r\n    fi\r\n    ;;\r\n    *)\r\n    echo -e &quot;start or stop ommited\\n&quot;\r\n    usage\r\n    ;;<\/pre>\n<p>Otherwise, if it is not valid command we show script usage information. <a href=\"http:\/\/www.lazarenko.us\/wp-content\/uploads\/2011\/07\/radio\">Here<\/a> is full script and <a href=\"http:\/\/www.lazarenko.us\/wp-content\/uploads\/2011\/07\/playlist.tar.bz2\">here<\/a> is playlist files for on-line stations.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>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 &hellip; <a href=\"https:\/\/www.lazarenko.us\/?p=362\" class=\"more-link\">Continue reading<span class=\"screen-reader-text\"> &#8220;FM radio on your mp3 player&#8221;<\/span><\/a><\/p>\n","protected":false},"author":2,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":[],"categories":[9,8],"tags":[],"_links":{"self":[{"href":"https:\/\/www.lazarenko.us\/index.php?rest_route=\/wp\/v2\/posts\/362"}],"collection":[{"href":"https:\/\/www.lazarenko.us\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.lazarenko.us\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.lazarenko.us\/index.php?rest_route=\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/www.lazarenko.us\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=362"}],"version-history":[{"count":5,"href":"https:\/\/www.lazarenko.us\/index.php?rest_route=\/wp\/v2\/posts\/362\/revisions"}],"predecessor-version":[{"id":367,"href":"https:\/\/www.lazarenko.us\/index.php?rest_route=\/wp\/v2\/posts\/362\/revisions\/367"}],"wp:attachment":[{"href":"https:\/\/www.lazarenko.us\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=362"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.lazarenko.us\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=362"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.lazarenko.us\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=362"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}