Check-in [e3d1d9a216]
Overview
| Comment: | Add developer API support for shoutcast. |
|---|---|
| Downloads: | Tarball | ZIP archive | SQL archive |
| Timelines: | family | ancestors | descendants | both | trunk |
| Files: | files | file ages | folders |
| SHA1: |
e3d1d9a21600ee1b2f67c18fa7c32962 |
| User & Date: | mario on 2022-03-01 08:37:13 |
| Other Links: | manifest | tags |
Context
|
2022-09-16
| ||
| 05:02 | also create CACHEDIR.TAG for data subdirs check-in: de206be857 user: mario tags: trunk | |
|
2022-03-01
| ||
| 08:37 | Add developer API support for shoutcast. check-in: e3d1d9a216 user: mario tags: trunk | |
|
2022-02-28
| ||
| 08:12 | add ajax station url fetching mode check-in: a1af3dc990 user: mario tags: trunk | |
Changes
Modified channels/shoutcast.py from [39c956323b] to [030a641d51].
1 2 3 4 5 6 7 | # api: streamtuner2 # title: Shoutcast.com # description: Primary list of shoutcast servers (now managed by radionomy). # type: channel # category: radio # author: Mario # original: Jean-Yves Lefort | > | | > > | | > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
# api: streamtuner2
# encoding: utf-8
# title: Shoutcast.com
# description: Primary list of shoutcast servers (now managed by radionomy).
# type: channel
# category: radio
# author: Mario
# original: Jean-Yves Lefort
# version: 1.8
# url: http://directory.shoutcast.com/
# config:
# { name: shoutcast_format, type: select, select: pls|m3u|xspf|raw, value: pls, description: "Shoutcast playlist format to retrieve" }
# { name: shoutcast_api, type: bool, value: 0, description: "Use Shoutcast developer API (key from .netrc)" }
# priority: default
# suggests: python:shoutcast-api
# png:
# iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABHNCSVQICAgIfAhkiAAAAelJREFUOI2NU0toE2EYnM12t2wLkhSXSIgEJMHFQ2naQ+kpoPYQoaXH3gRFsegloUhRQTyU2oOgggQUzzlEQomIBzU+EHooBIol0cOGLqFFFiJ5SB5skvFU6ebRduA7/DAz
# /PM9BJLoh3Q6zVQqBZfLhXq9jlAohHA4LHTzhvqJ2+02c7kcgsEgfD4fRFFEPp+HZVmUJEk41kAURcHv99Pj8cAwDGiaBkVR0C0GAJDsW7VajYVCgYlEguVymZZlsVKpcG1tlYd5fX8AAIqiCF6vF6VSibIsI5lMYvvDE1xymwDu/ec5BhkcIJPJIHJzFqf372P1cgMf
# f46cLIKu61yJXufr5VO0voyzEZ/k8sI4s9ns0RFarRZjL56inIshekWGenYS6IzhR9PCntRBIBCw8XsiFItFNLMxPJgfwVjDi4Y8g2b9DILaMKZGd2Ca5tEGiqJg2xjF200H6J+AvKtjeG8T3998xW5nAk6n08bviSBJEqhewLlpN4bMHfwxfuH5J8J98SGerS/B4XDY
# d+FwQ6rVKm8vXeP++6vku2lu3FEZubFIXdc5qNm2x93ILZobszRfaYwuaIzH4wOFfafwt7CFb59/Y0uYx8rLR1BVtXd1u2AzCMwsQg6cx+O5uWOFBxAGnfNJ8Q/z/DNTtgbnsgAAAABJRU5ErkJggg==
# depends: re, ahttp
# extraction-method: json, regex
#
# Shoutcast is a server software for audio streaming. It automatically spools
# station information on shoutcast.com, which today lists over 85000 radios.
#
# It has been aquired by Radionomy in 2014. Since then significant changes
# took place. The former yellow pages API got deprecated. Streamtuner2 now
# utilizes the AJAX interface for speedy playlist discovery.
#
# Optionally can use the https://pypi.org/project/shoutcast-api/ for
# scanning stations. But that requires a developer API key in .netrc
# via `machine shoutcast.com\n password XYZ`. (UNTESTED, and still using
# standard url lookup handler. Their API is actually meant for setting up
# a shoutcast-like website mirror, unsuitable for desktop apps.)
#
import ahttp
from json import loads as json_decode
import re
from config import *
|
| ︙ | ︙ | |||
43 44 45 46 47 48 49 |
# the eligibility of open source desktop apps for an authhash.
#
# Therefore we'll be retrieving stuff from the homepage still. The new
# interface conveniently uses JSON already, so let's use that:
#
# POST http://www.shoutcast.com/Home/BrowseByGenre {genrename: Pop}
#
| | < | | | | > > > > < | < | | < < | > > > > > | > > > > | | > > > | 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 |
# the eligibility of open source desktop apps for an authhash.
#
# Therefore we'll be retrieving stuff from the homepage still. The new
# interface conveniently uses JSON already, so let's use that:
#
# POST http://www.shoutcast.com/Home/BrowseByGenre {genrename: Pop}
#
# Catmap actually has become redundant.
#
class shoutcast(channels.ChannelPlugin):
# attrs
base_url = "http://directory.shoutcast.com/"
listformat = "pls"
has_search = False # may be True now
# categories
categories = ["Top 500", 'Alternative', ['Adult Alternative', 'Britpop', 'Classic Alternative', 'College', 'Dancepunk', 'Dream Pop', 'Emo', 'Goth', 'Grunge', 'Hardcore', 'Indie Pop', 'Indie Rock', 'Industrial', 'LoFi', 'Modern Rock', 'New Wave', 'Noise Pop', 'Post Punk', 'Power Pop', 'Punk', 'Ska', 'Xtreme'], 'Blues', ['Acoustic Blues','Chicago Blues','Contemporary Blues','Country Blues','Delta Blues','Electric Blues','Cajun and Zydeco'],'Classical', ['Baroque','Chamber','Choral','Classical Period','Early Classical','Impressionist','Modern','Opera','Piano','Romantic','Symphony'],'Country', ['Alt Country','Americana','Bluegrass','Classic Country','Contemporary Bluegrass','Contemporary Country','Honky Tonk','Hot Country Hits','Western'],'Easy Listening', ['Exotica','Light Rock','Lounge','Orchestral Pop','Polka','Space Age Pop'],'Electronic', ['Acid House','Ambient','Big Beat','Breakbeat','Dance','Demo','Disco','Downtempo','Drum and Bass','Electro','Garage','Hard House','House','IDM','Jungle','Progressive','Techno','Trance','Tribal','Trip Hop','Dubstep'],'Folk', ['Alternative Folk','Contemporary Folk','Folk Rock','New Acoustic','Traditional Folk','World Folk','Old Time'],'Themes', ['Adult','Best Of','Chill','Eclectic','Experimental','Female','Heartache','Instrumental','LGBT','Love and Romance','Party Mix','Patriotic','Rainy Day Mix','Reality','Sexy','Shuffle','Travel Mix','Tribute','Trippy','Work Mix'],'Rap', ['Alternative Rap','Dirty South','East Coast Rap','Freestyle','Hip Hop','Gangsta Rap','Mixtapes','Old School','Turntablism','Underground Hip Hop','West Coast Rap'],'Inspirational', ['Christian','Christian Metal','Christian Rap','Christian Rock','Classic Christian','Contemporary Gospel','Gospel','Praise and Worship','Sermons and Services','Southern Gospel','Traditional Gospel'],'International', ['African','Arabic','Asian','Bollywood','Brazilian','Caribbean','Celtic','Chinese','European','Filipino','French','Greek','Hawaiian and Pacific','Hindi','Indian','Japanese','Hebrew','Klezmer','Korean','Mediterranean','Middle Eastern','North American','Russian','Soca','South American','Tamil','Worldbeat','Zouk','German','Turkish','Islamic','Afrikaans','Creole'],'Jazz', ['Acid Jazz','Avant Garde','Big Band','Bop','Classic Jazz','Cool Jazz','Fusion','Hard Bop','Latin Jazz','Smooth Jazz','Swing','Vocal Jazz','World Fusion'],'Latin', ['Bachata','Banda','Bossa Nova','Cumbia','Latin Dance','Latin Pop','Latin Rap and Hip Hop','Latin Rock','Mariachi','Merengue','Ranchera','Reggaeton','Regional Mexican','Salsa','Tango','Tejano','Tropicalia','Flamenco','Samba'],'Metal', ['Black Metal','Classic Metal','Extreme Metal','Grindcore','Hair Metal','Heavy Metal','Metalcore','Power Metal','Progressive Metal','Rap Metal','Death Metal','Thrash Metal'],'New Age', ['Environmental','Ethnic Fusion','Healing','Meditation','Spiritual'],'Decades', ['30s','40s','50s','60s','70s','80s','90s','00s'],'Pop', ['Adult Contemporary','Barbershop','Bubblegum Pop','Dance Pop','Idols','Oldies','JPOP','Soft Rock','Teen Pop','Top 40','World Pop','KPOP'],'R&B and Urban', ['Classic R&B','Contemporary R&B','Doo Wop','Funk','Motown','Neo Soul','Quiet Storm','Soul','Urban Contemporary'],'Reggae', ['Contemporary Reggae','Dancehall','Dub','Pop Reggae','Ragga','Rock Steady','Reggae Roots'],'Rock', ['Adult Album Alternative','British Invasion','Classic Rock','Garage Rock','Glam','Hard Rock','Jam Bands','Piano Rock','Prog Rock','Psychedelic','Rock & Roll','Rockabilly','Singer and Songwriter','Surf','JROCK','Celtic Rock'],'Seasonal and Holiday', ['Anniversary','Birthday','Christmas','Halloween','Hanukkah','Honeymoon','Kwanzaa','Valentine','Wedding','Winter'],'Soundtracks', ['Anime','Kids','Original Score','Showtunes','Video Game Music'],'Talk', ['Comedy','Community','Educational','Government','News','Old Time Radio','Other Talk','Political','Scanner','Spoken Word','Sports','Technology','BlogTalk'],'Misc', [],'Public Radio', ['News','Talk','College','Sports','Weather']]
catmap = {}
# redefine
streams = {}
# API usage
api_key = None
api_stations = None
def init2(self, parent):
self.listformat = conf.shoutcast_format
# Extracts the category list from www.shoutcast.com,
def update_categories(self):
html = ahttp.get(self.base_url)
#log.DATA( html )
self.categories = ["Top 500"]
# Genre list in sidebar
"""
<li id="genre-3" class="sub-genre " genreid="3" parentgenreid="1">
<a href="/Genre?name=Britpop" onclick="return loadStationsByGenre('Britpop', 3, 1);">Britpop</a>
</li>
"""
rx = re.compile(r"loadStationsByGenre\( '([^']+)' [,\s]* (\d+) [,\s]* (\d+) \)", re.X)
subs = rx.findall(html)
# group
current = []
for (title, id, main) in subs:
#self.catmap[title] = int(id)
if not int(main):
self.categories.append(title)
current = []
self.categories.append(current)
else:
current.append(title)
# .categories/.catmap get saved by reload_categories()
# downloads stream list from shoutcast for given category
def update_streams(self, cat, search=None):
if conf.get("shoutcast_api"):
try:
return self.update_streams_api(cat)
except Exception as e:
log.ERR(e)
# page (always one result set รก 500 entries)
if cat in ("top", "Top", "Top 500"):
url = self.base_url + "Home/Top"
params = {}
elif cat:
url = self.base_url + "Home/BrowseByGenre"
params = { "genrename": cat }
elif search:
url = self.base_url + "Search/UpdateSearch"
params = { "query": search }
referer = self.base_url
try:
json = ahttp.get(url, params=params, referer=referer, post=1, ajax=1)
json = json_decode(json)
except:
log.ERR("HTTP request or JSON decoding failed. Outdated python/requests perhaps.")
return []
|
| ︙ | ︙ | |||
129 130 131 132 133 134 135 |
IsRadionomy:true
Listeners:159
Name:"AOLMRadio"
StreamUrl:null
"""
entries = []
for e in json:
| | | | | | > > > > > > > > > > > > > > > > > > > > > > > > > > > | | 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 |
IsRadionomy:true
Listeners:159
Name:"AOLMRadio"
StreamUrl:null
"""
entries = []
for e in json:
if conf.shoutcast_format in ("raw","json","href"):
url = "urn:shoutcast:{}".format(e.get("ID",0))
else:
url = "http://yp.shoutcast.com/sbin/tunein-station.%s?id=%s" % (conf.shoutcast_format, e.get("ID", "0"))
entries.append({
"id": int(e.get("ID", 0)),
"genre": str(e.get("Genre", "")),
"title": str(e.get("Name", "")),
"playing": str(e.get("CurrentTrack", "")),
"bitrate": int(e.get("Bitrate", 0)),
"listeners": int(e.get("Listeners", 0)),
"url": url,
"listformat": conf.shoutcast_format,
"homepage": "",
"format": str(e.get("Format", ""))
})
#log.DATA(entries)
return entries
# import API module
def prepare_api(self):
if not self.api_key:
self.api_key = conf.netrc(["shoutcast.com"])[2]
if not self.api_stations:
from shoutcast_api import stations
self.api_stations = stations
# via developer API
def update_streams_api(self, cat):
self.prepare_api()
r = []
for e in self.api_stations.get_stations_by_genre(self.api_key, cat):
r.append({
"title": e["name"],
"id": e["id"],
"bitrate": e["br"],
"playing": e["ct"],
"img": e["logo_url"],
"listeners": e["lc"],
"url": "urn:shoutcast:{}".format(e["id"]),
"genre": e["genre"],
"format": "audio/mpeg",
"listformat": "href",
})
return r
# in case we're using AJAX lookups over tunein-station.pls
def resolve_urn(self, row):
if not row.get("id") or not row.get("url", "").startswith("urn:shoutcast:"):
return
url = ahttp.get("https://directory.shoutcast.com/Player/GetStreamUrl", {"station":row["id"]}, post=1)
row["url"] = json_decode(url) # response is just a string literal of streaming url
return row["url"]
|
Modified help/channel_shoutcast.page from [91f42f9004] to [3e03d2917a].
1 2 3 4 5 6 7 8 9 10 11 |
<page xmlns="http://projectmallard.org/1.0/"
type="guide"
id="shoutcast">
<info>
<link type="guide" xref="index#channels"/>
<link type="guide" xref="channels#list"/>
<desc>Probably still the largest radio station list.</desc>
</info>
<title><media type="image" src="img/channel_shoutcast.png" /> Shoutcast</title>
| | | > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 |
<page xmlns="http://projectmallard.org/1.0/"
type="guide"
id="shoutcast">
<info>
<link type="guide" xref="index#channels"/>
<link type="guide" xref="channels#list"/>
<desc>Probably still the largest radio station list.</desc>
</info>
<title><media type="image" src="img/channel_shoutcast.png" /> Shoutcast</title>
<subtitle><link href="http://directory.shoutcast.com/">//directory.shoutcast.com/</link></subtitle>
<p>SHOUTcast is the name of a MP3 streaming server software. It automatically collects all
station lists on shoutcast.com. Currently lists over 85000 stations.
</p>
<list>
<item><p>Station entries usually provide current playing information.</p></item>
<item><p>Stream links are plain <link xref="filetypes#pls">PLS files</link>.</p></item>
<item><p>Genres are subcategorized, so the main groups in the category
list must be expanded to see the interesting entries.</p></item>
</list>
<p>Since being sold from AOL to Radionomy, the directory got cut down. There are no longer
entries for currently played songs, and homepage links are largely gone. Hencewhy the
Shoutcast channel is no longer considered a primary feature.</p>
<section id="options">
<title>Configuration</title>
<terms>
<item>
<title><code>๐ Shoutcast playlist format </code></title>
<p>Per default <link xref="filetypes#pls">*.pls</link> station
links will be retrieved. But shoutcast also allows for
<link xref="filetypes#m3u">*.m3u</link> or <link
xref="filetypes#xspf">*.xspf</link> playlists.</p>
<p> Alternatively the "raw" option will use IDs to look up the
direct streaming URL with a subsequent AJAX request (bit slower;
meant as fallback in case the tunein-station.pls links break).</p>
</item>
<item>
<title><code>โ Use developer API </code></title>
<p>Instead of scanning the website, there's also an API for
searching stations. It's only meant for alternative shoutcast
directory websites; and you won't easily get an API key.
Required installed module: <cmd>pip install shoutcast-api</cmd></p>
</item>
</terms>
</section>
</page>
|