Skip to content

Instantly share code, notes, and snippets.

@NonaSuomy
Last active April 23, 2017 08:22
Show Gist options
  • Save NonaSuomy/e32ff94ed728c8d31947477787d59d89 to your computer and use it in GitHub Desktop.
Save NonaSuomy/e32ff94ed728c8d31947477787d59d89 to your computer and use it in GitHub Desktop.
Send A Radio Station From streamtheworld.com to https://github.com/karawin/Ka-Radio hardware ESP8266 + VS1053 + 23LC1024
#!/usr/bin/env python
'''
User: NonaSuomy
Date: 20170406
Upda: 20170422
Desc: Found this script at: https://www.toofishes.net/blog/streamtheworld-streams-command-line/
Desc: Modified to send the streams to Ka-Radio further information: https://github.com/karawin/Ka-Radio/issues/25.
Usage: python2 streamtheworldka.py CIMXFM 10.13.37.65
^Radio callsign ^IP address of Ka-Radio Device
Requirements: python2 + curl
'''
from random import choice
import os
import sys
import urllib2
import xml.dom.minidom as minidom
def validate_callsign(cs):
'''
Normal callsign format is 'WWWWFFAAA', where 'WWWW' is the radio station
callsign, 'FF' is either 'AM' or 'FM', and 'AAA' is always 'AAC'.
For this function, we expect the 'WWWWFF' part as input.
'''
if not cs or not isinstance(cs, str):
raise ValueError('callsign \'%s\' is not a string.' % cs)
#if len(cs) < 6:
# raise ValueError('callsign \'%s\' is too short.' % cs)
if not cs.endswith('AAC'):
cs = cs + 'AAC'
band = cs[-5:-3]
#if band != 'AM' and band != 'FM':
# raise ValueError('callsign \'%s\' is missing \'FM\' or \'AM\'.' % cs)
return cs
def make_request(callsign):
host = 'playerservices.streamtheworld.com'
req = urllib2.Request(
'http://%s/api/livestream?version=1.5&mount=%s&lang=en' %
(host, callsign))
req.add_header('User-Agent', 'Mozilla/5.0')
return req
## Example XML document we are parsing follows, as the minidom code is so beautiful to follow
#
#<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
#<live_stream_config xmlns="http://provisioning.streamtheworld.com/player/livestream-1.5" version="1.5">
# <script/>
# <mountpoints>
# <mountpoint>
# <status>
# <status-code>200</status-code>
# <status-message>OK</status-message>
# </status>
# <transports>
# <transport>http</transport>
# </transports>
# <metadata>
# <shoutcast-v1 enabled="true" mountSuffix="_SC"/>
# <shoutcast-v2 enabled="false" mountSuffix="_SC"/>
# <sse-sideband enabled="true" streamSuffix="_SC" metadataSuffix="_SBM"/>
# </metadata>
# <servers>
# <server sid="18853">
# <ip>18853.live.streamtheworld.com</ip>
# <ports>
# <port type="http">80</port>
# <port type="http">3690</port>
# </ports>
# </server>
# <server sid="17023">
# <ip>17023.live.streamtheworld.com</ip>
# <ports>
# <port type="http">80</port>
# <port type="http">3690</port>
# </ports>
# </server>
# <server sid="13733">
# <ip>13733.live.streamtheworld.com</ip>
# <ports>
# <port type="http">80</port>
# <port type="http">3690</port>
# </ports>
# </server>
# <server sid="14213">
# <ip>14213.live.streamtheworld.com</ip>
# <ports>
# <port type="http">80</port>
# <port type="http">3690</port>
# </ports>
# </server>
# </servers>
# <metrics>
# <tag name="uuid"/>
# </metrics>
# <mount>CIMXFM</mount>
# <format>FLV</format>
# <bitrate>48000</bitrate>
# <media-format container="flv" cuepoints="stwcue">
# <audio index="0" samplerate="22050" codec="mp3" bitrate="48000" channels="2"/>
# </media-format>
# <authentication>0</authentication>
# <timeout>0</timeout>
# <send-page-url>0</send-page-url>
# </mountpoint>
# </mountpoints>
#</live_stream_config>
def t(element):
'''get the text of a DOM element'''
return element.firstChild.data
def check_status(ele):
# should only be one status element inside a mountpoint
status = ele.getElementsByTagName('status')[0]
if t(status.getElementsByTagName('status-code')[0]) != '200':
msg = t(status.getElementsByTagName('status-message')[0])
raise Exception('Error locating stream: ' + msg)
def create_stream_urls(srcfile):
doc = minidom.parse(srcfile)
mp = doc.getElementsByTagName('mountpoint')[0]
check_status(mp)
mt = t(mp.getElementsByTagName('mount')[0])
allurls = []
for s in mp.getElementsByTagName('server'):
# a thing of beauty, right?
ip = t(s.getElementsByTagName('ip')[0])
ports = [t(p) for p in s.getElementsByTagName('port')]
# yes, it is always HTTP. We see ports 80, 443, and 3690 usually
urls = ['http://%s:%s/%s' % (ip, p, mt) for p in ports]
allurls.extend(urls)
return allurls
def start_mplayer(playerip, location):
return os.system('curl http://{0}/?instant="{1}"_SC'.format(playerip, location))
if __name__ == '__main__':
if len(sys.argv) < 2:
print 'usage: station callsign must be the first argument'
sys.exit(1)
callsign = validate_callsign(sys.argv[1])
playerip = sys.argv[2]
req = make_request(callsign)
result = urllib2.urlopen(req)
urls = create_stream_urls(result)
if len(urls) > 0:
u = choice(urls)
sys.exit(start_mplayer(playerip,u))
sys.exit(1)
@NonaSuomy
Copy link
Author

NonaSuomy commented Apr 22, 2017

Simple script, at the top of the script is the usage example, basically find a radio station on iheartradio:

iheartradio station list USA
iheartradio
iheartradio Canada

Look at the source code of the iheartradio page (click on song history will help a bit) you will find things like this in it:

view-source:http://www.iheartradio.ca/all-stations/7.13793734?mode=history

<div class="delayed-image-load" data-class=""  data-src="/image/policy:1.1870492:1472654994/Trancid.jpg?f=default&$p$f=a34e00f&{width}" data-ratio="" data-alt="">

                                        <noscript><img class="" itemscope="image" alt="" src="/image/policy:1.1870492:1472654994/Trancid.jpg?f=default&w=200&$p$f$w=a199ace" /></noscript>
                        
        </div>
        
    
                    <div class="streamIcon">
                        <button class="stream-link stream-radio-station" data-ajax="true"
                                                        data-station-callsign="IHR_TRAN"
                                data-ad-unit="/5479/iheartradio.en/allstations"
                                data-ad-ss="radioplayer"
                                data-station-id="1.1870491"
                        > 
                            <span class="ico ico-play">
                                <span class="ico load-hover"></span>
                            </span>
                        </button>

This is what we want:

data-station-callsign="IHR_TRAN"

IHR_TRAN is the callsign for the station you want to play. (Trancid)

Another stations callsign CIMXFM etc.

This is the radio station we are grabbing below from iheartradio 89x "CIMXFM"

Usage: python2 streamtheworldka.py CIMXFM 10.13.37.65
                                   ^Radio callsign  ^IP address of Ka-Radio Device

Make sure you have python2 and curl installed on the system you are sending the station from, download the script to a file on that device, then type the command, I named the script streamtheworldka.py, so type on the command line:

python2 streamtheworldka.py (The radio call sign + FM/AM on the end) (IP Address of your Ka-Radio Player)

First, test with the call sign above/below make sure it is all in CAPS as I know that one works (may not work outside of Canada), the hard part would be finding how they named the call sign for the station you want, and some may be location locked so be aware. Also add FM/AM on the end of the call sign or the script will complain "Callsign is missing FM, AM".

The script then tells the website to give us the ShoutCast stream URL for audio stream compatibility purposes with _SC.

python2 streamtheworldka.py CIMXFM 10.13.37.65

The script is grabbing the stream URL that is a randomly encoded string of characters for that station you want, then send the URL to the ESP8266.

The script requests a URL like this:
http://playerservices.streamtheworld.com/api/livestream?version=1.5&mount=CIMXFM&lang=en

Then it sends back an XML which we extract the values from:

<live_stream_config xmlns="http://provisioning.streamtheworld.com/player/livestream-1.5" version="1.5">
  <script/>
    <mountpoints>
      <mountpoint>
        <status>
          <status-code>200</status-code>
          <status-message>OK</status-message>
        </status>
        <transports>
          <transport>http</transport>
        </transports>
        <metadata>
          <shoutcast-v1 enabled="true" mountSuffix="_SC"/>
          <shoutcast-v2 enabled="false" mountSuffix="_SC"/>
          <sse-sideband enabled="true" streamSuffix="_SC" metadataSuffix="_SBM"/>
        </metadata>
        <servers>
          <server sid="18853">
          <ip>18853.live.streamtheworld.com</ip>
          <ports>
            <port type="http">80</port>
            <port type="http">3690</port>
          </ports>
        </server>
        <server sid="17023">
          <ip>17023.live.streamtheworld.com</ip>
          <ports>
            <port type="http">80</port>
            <port type="http">3690</port>
          </ports>
        </server>
        <server sid="13733">
          <ip>13733.live.streamtheworld.com</ip>
          <ports>
             <port type="http">80</port>
             <port type="http">3690</port>
          </ports>
        </server>
        <server sid="14213">
          <ip>14213.live.streamtheworld.com</ip>
          <ports>
            <port type="http">80</port>
            <port type="http">3690</port>
          </ports>
        </server>
      </servers>
      <metrics>
        <tag name="uuid"/>
      </metrics>
      <mount>CIMXFM</mount>
      <format>FLV</format>
      <bitrate>48000</bitrate>
      <media-format container="flv" cuepoints="stwcue">
        <audio index="0" samplerate="22050" codec="mp3" bitrate="48000" channels="2"/>
      </media-format>
      <authentication>0</authentication>
      <timeout>0</timeout>
      <send-page-url>0</send-page-url>
    </mountpoint>
  </mountpoints>
</live_stream_config>

The script extracts,

Random generated IP: 18853.live.streamtheworld.com
Port: 80, 3690, etc
MountPoint(callsign): CIMXFM

I noticed that there was ShoutCast compatibility in the XML which it shows to add "_CS" onto the mount point: CIMXFMAAC_CS

Then the script sends this data gathered from the XML to Ka-Radio device:

curl http://10.13.37.65/?instant="18853.live.streamtheworld.com:3690/CIMXFMAAC_SC"

Then Ka-Radio should start playing the stream.

If you attach a serial cable to Ka-Radio you should be able to debug what URL is getting passed to it, to see if the station is getting sent properly.

Successful run should look like this on the serial line:

$ cu -l ttyUSB0 -s 115200
Connected.
#INFO:""#
##CLI.STOPPED# from Web Instant
##CLI.URLSET#: 18863.live.streamtheworld.com
##CLI.PATHSET#: /CIMXFMAAC_SC
##CLI.PORTSET#: 3690
##CLI.ICY0#:  
##CLI.ICY3#:  http:\/\/www.89xradio.com
##CLI.ICY4#:  Rock
##CLI.ICY5#:  40
##CLI.ICY6#:  
##CLI.PLAYING#
##CLI.META#: Soundgarden - Fell On Black Days (89X Live-X

Someone with FreeRTOS skills could probably bake this into Ka-Radio, if there was enough memory, but probably best if it was a plugin of sorts, as they could change the way their API works at any moment. Have a hardware button that always generates the random URL and play's your favorite radio station.

Hope it helps!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment