#
# api: streamtuner2
# title: Xiph.org
# description: Xiph/ICEcast radio directory
# version: 0.2
#
#
# Xiph.org maintains the Ogg streaming standard and Vorbis audio compression
# format, amongst others. The ICEcast server is an alternative to SHOUTcast.
#
# It meanwhile provides a JSOL dump, which is faster to download and process.
# So we'll use that over the older yp.xml. (Sadly it also doesn't output
# homepage URLs, listeners, etc.)
#
#
# streamtuner2 modules
from config import conf
from mygtk import mygtk
import ahttp as http
from channels import *
from config import __print__, dbg
import json
# python modules
import re
#from xml.sax.saxutils import unescape as entity_decode, escape as xmlentities
#import xml.dom.minidom
# I wonder what that is for ---------------------------------------
class xiph (ChannelPlugin):
# desc
api = "streamtuner2"
module = "xiph"
title = "Xiph.org"
version = 0.2
homepage = "http://dir.xiph.org/"
base_url = "http://api.dir.xiph.org/"
json = "experimental/full"
old_yp = "yp.xml"
listformat = "url/http"
config = [
{"name":"xiph_min_bitrate", "value":64, "type":"int", "description":"minimum bitrate, filter anything below", "category":"filter"}
]
# content
categories = ["all", [], ]
current = ""
default = "all"
empty = None
# prepare category names
def __init__(self, parent=None):
self.categories = ["all"]
self.filter = {}
for main in self.genres:
if (type(main) == str):
id = main.split("|")
self.categories.append(id[0].title())
self.filter[id[0]] = main
else:
l = []
for sub in main:
id = sub.split("|")
l.append(id[0].title())
self.filter[id[0]] = sub
self.categories.append(l)
# GUI
ChannelPlugin.__init__(self, parent)
# just counts genre tokens, does not automatically create a category tree from it
def update_categories(self):
g = {}
for row in self.streams["all"]:
for t in row["genre"].split():
if g.has_key(t):
g[t] += 1
else:
g[t] = 0
g = [ [v[1],v[0]] for v in g.items() ]
g.sort()
g.reverse()
for row in g:
pass
__print__( dbg.DATA, ' "' + row[1] + '", #' + str(row[0]) )
# xml dom node shortcut to text content
def x(self, entry, name):
e = entry.getElementsByTagName(name)
if (e):
if (e[0].childNodes):
return e[0].childNodes[0].data
# convert bitrate string to integer
# (also convert "Quality \d+" to pseudo bitrate)
def bitrate(self, s):
uu = re.findall("(\d+)", s)
if uu:
br = uu[0]
if br > 10:
return int(br)
else:
return int(br * 25.6)
else:
return 0
# downloads stream list from shoutcast for given category
def update_streams(self, cat, search=""):
# there is actually just a single category to download,
# all else are virtual
if (cat == "all"):
#-- get data
data = http.get(self.base_url + self.json)
data = re.sub('":NaN,', '":0,', data) # the output is JSOL, not valid JSON
data = re.sub('[\\\\]"(?!,)', '\'', data) # and some invalid string escaping
data = re.sub('[\\\\]{1}', '', data) # and all other backslashes to be safe
data = data.decode("latin-1")
#-- extract
l = []
__print__( dbg.DATA, "processing dir.xiph.org/experimental/full JSON" )
for e in json.loads(data):
bitrate = e["bitrate"]
if conf.xiph_min_bitrate and bitrate and bitrate >= int(conf.xiph_min_bitrate):
l.append({
"title": e["stream_name"],
"url": e["listen_url"],
"format": "audio/mpeg",
"bitrate": int(e["bitrate"]),
"channels": e["channels"],
"samplerate": e["samplerate"],
"genre": e["genre"],
"playing": e["current_song"],
"listeners": 0,
"max": 0,
"homepage": "",
})
# filter out a single subtree
else:
rx = re.compile(self.filter.get(cat.lower(), cat.lower()))
l = []
for i,row in enumerate(self.streams["all"]):
if rx.search(row["genre"]):
l.append(row)
# send back the list
return l
genres = [
"scanner", #442
"rock", #305
[
"metal|heavy", #36
],
"various", #286
[
"mixed", #96
],
"pop", #221
[
"top40|top|40|top 40", #32
"charts|hits", #20+4
"80s", #68
"90s", #20
"disco", #17
"remixes", #10
],
"electronic|electro", #33
[
"dance", #161
"house", #106
"trance", #82
"techno", #72
"chillout", #16
"lounge", #12
],
"alternative", #68
[
"college", #32
"student", #20
"progressive", #20
],
"classical|classic", #58+20
"live", #57
"jazz", #42
[
"blues", #19
],
"talk|spoken|speak", #41
[
"news", #39
"public", #12
"info", #5
],
"world|international", #25
[
"latin", #34
"reggae", #12
"indie", #12
"folk", #9
"schlager", #14
"jungle", #13
"country", #7
"russian", #6
],
"hip hop|hip|hop", #34
[
"oldschool", #10
"rap",
],
"ambient", #34
"adult", #33
## "music", #32
"oldies", #31
[
"60s", #2
"70s", #17
],
"religious", #4
[
"christian|bible", #14
],
"rnb|r&b", #12
[
"soul", #11
"funk", #24
"urban", #11
],
"other", #25
[
"deep", #14
"soft", #12
"minimal", #12
"eclectic", #12
"drum", #12
"bass", #12
"experimental", #11
"hard", #10
"funky", #10
"downtempo", #10
"slow", #9
"break", #9
"electronica", #8
"dub", #8
"retro", #7
"punk", #7
"psychedelic", #7
"goa", #7
"freeform", #7
"c64", #7
"breaks", #7
"anime", #7
"variety", #6
"psytrance", #6
"island", #6
"downbeat", #6
"underground", #5
"newage", #5
"gothic", #5
"dnb", #5
"club", #5
"acid", #5
"video", #4
"trip", #4
"pure", #4
"industrial", #4
"groove", #4
"gospel", #4
"gadanie", #4
"french", #4
"dark", #4
"chill", #4
"age", #4
"wave", #3
"vocal", #3
"tech", #3
"studio", #3
"relax", #3
"rave", #3
"hardcore", #3
"breakbeat", #3
"avantgarde", #3
"swing", #2
"soundtrack", #2
"salsa", #2
"italian", #2
"independant", #2
"groovy", #2
"european", #2
"darkwave", #2
],
]