Last active
April 23, 2017 08:22
-
-
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
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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) | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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
This is what we want:
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"
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:
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.
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:
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:
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!