Skip to content

Instantly share code, notes, and snippets.

@NonaSuomy
Last active May 31, 2021 04:28
Show Gist options
  • Save NonaSuomy/5f09f76d60e30ad127230cf22709f986 to your computer and use it in GitHub Desktop.
Save NonaSuomy/5f09f76d60e30ad127230cf22709f986 to your computer and use it in GitHub Desktop.
Send A Radio Station From streamtheworld.com to https://github.com/Edzelf/Esp-radio hardware ESP8266 + VS1053
#!/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 esp-radio further information below in the comments and https://github.com/Edzelf/Esp-radio/issues/70 .
Usage: python2 streamtheworldespradio.py CIMXFM 10.13.37.65
^Radio callsign ^IP address of esp-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}/?station="{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 23, 2017

Send a radio station to your esp-radio player: GIST

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 streamtheworldespradio.py CIMXFM 10.13.37.65
                                   ^Radio callsign  ^IP address of esp-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 streamtheworldespradio.py, so type on the command line:

python2 streamtheworldespradio.py (The radio call sign + FM/AM on the end) (IP Address of your esp-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 streamtheworldespradio.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 esp-radio device:

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

Then esp-radio should start playing the stream.

If you attach a serial cable to esp-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.
D: Connect to new host 18163.live.streamtheworld.com:80/CIMXFMAAC_SC
D: Connect to 18163.live.streamtheworld.com on port 80, extension /CIMXFMAAC_SC
D: Connected to server
D: Expires: Thu, 01 Dec 2003 16:00:00 GMT
D: Cache-Control: no-cache, must-revalidate
D: Pragma: no-cache
D: Access-Control-Allow-Methods: GET, HEAD
D: Access-Control-Allow-Origin: *
D: Access-Control-Allow-Credentials: true
D: Set-Cookie: uuid=01e12b07-2b5b-4fe1-a009-0dd613569506; expires=Sat, 22-Dec-2029 21:30:41 GMT; path=
D: Content-Type: audio/aacp
D: Connection: close
D: icy-br: 40
D: icy-description: 
D: icy-genre: Rock
D: icy-name: 
D: icy-url: http://www.89xradio.com
D: icy-metaint: 16000
D: Server: MediaGateway 4.2.5-003
D: Switch to DATA, bitrate is 40
D: First chunk:
D: FF F1 5E 40 1D 5F FC 01
D: 58 15 25 0C C4 93 05 04
D: 21 5A F9 9F 77 44 F7 D5
D: F7 CD 6A B8 E5 75 15 08
D: Metadata block 48 bytes
D: Streamtitle found, 45 bytes
D: StreamTitle='Young the Giant - Silvertongue';

Hope it helps!

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