Index: channels/myoggradio.py
==================================================================
--- channels/myoggradio.py
+++ channels/myoggradio.py
@@ -2,11 +2,11 @@
# api: streamtuner2
# title: MyOggRadio
# description: Open source internet radio directory.
# type: channel
# category: radio
-# version: 0.5
+# version: 0.6
# url: http://www.myoggradio.org/
# depends: json, StringIO
# config:
# { name: myoggradio_login, type: text, value: "user:password", description: "Account for storing personal favourites." }
# { name: myoggradio_morph, type: boolean, value: 0, description: "Convert pls/m3u into direct shoutcast url." }
@@ -37,25 +37,26 @@
# open source radio sharing stie
class myoggradio(ChannelPlugin):
- # description
- title = "MyOggRadio"
+ # settings
+ title ="MOR"
module = "myoggradio"
- homepage = "http://www.myoggradio.org/"
api = "http://www.myoggradio.org/"
listformat = "url/direct"
# hide unused columns
titles = dict(playing=False, listeners=False, bitrate=False)
-
# category map
categories = ['common', 'personal']
default = 'common'
current = 'common'
+
+ # netrc instance
+ netrc = None
# prepare GUI
def __init__(self, parent):
@@ -171,13 +172,16 @@
# let's hope the JSESSIONID cookie is kept
# returns login (user,pw)
def user_pw(self):
- if conf.myoggradio_login != "user:password":
+ if len(conf.myoggradio_login) and conf.myoggradio_login != "user:password":
return conf.myoggradio_login.split(":")
- else: pass
-
+ else:
+ lap = conf.netrc(["myoggradio", "myoggradio.org", "www.myoggradio.org"])
+ if lap:
+ return [lap[0] or lap[1], lap[2]]
+ pass
Index: config.py
==================================================================
--- config.py
+++ config.py
@@ -31,189 +31,209 @@
#-- create a stub instance of config object
conf = object()
+# separate instance of netrc, if needed
+netrc = None
+
#-- global configuration data ---------------------------------------------
class ConfigDict(dict):
- # start
- def __init__(self):
-
- # object==dict means conf.var is conf["var"]
- self.__dict__ = self # let's pray this won't leak memory due to recursion issues
-
- # prepare
- self.defaults()
- self.xdg()
-
- # runtime
- self.share = os.path.dirname(os.path.abspath(__file__))
-
- # settings from last session
- last = self.load("settings")
- if (last):
- if "share" in last:
- del last["share"]
- self.update(last)
- self.migrate()
- # store defaults in file
- else:
- self.save("settings")
- self.firstrun = 1
-
-
- # some defaults
- def defaults(self):
- self.play = {
- "audio/mpeg": "audacious ", # %u for url to .pls, %g for downloaded .m3u
- "audio/ogg": "audacious ",
- "audio/*": "audacious ",
- "video/youtube": "totem $(youtube-dl -g %srv)",
- "video/*": "vlc --one-instance %srv",
- "url/http": "sensible-browser",
- }
- self.record = {
- "audio/*": "xterm -e streamripper %srv", # -d /home/***USERNAME***/Musik
- "video/youtube": "xterm -e \"youtube-dl %srv\"",
- }
- self.plugins = {
- "bookmarks": 1, # built-in plugin, cannot be disabled
- "search": 1,
- "streamedit": 1,
- "configwin": 1,
- "shoutcast": 1,
- "xiph": 1,
- "modarchive": 0, # disable per default
- "file": 0, # disable per default
- "punkcast": 0, # disable per default
- "history": 0,
- "basicch": 0, # ceased
- "tv": 0, # ceased
- }
- self.tmp = os.environ.get("TEMP", "/tmp")
- self.max_streams = "500"
- self.show_bookmarks = 1
- self.show_favicons = 1
- self.load_favicon = 1
- self.heuristic_bookmark_update = 0
- self.retain_deleted = 0
- self.auto_save_appstate = 1
- self.theme = "" #"MountainDew"
- self.channel_order = "shoutcast, xiph, internet_radio, jamendo, myoggradio, .."
- self.reuse_m3u = 1
- self.google_homepage = 0
- self.windows = platform.system()=="Windows"
- self.pyquery = 1
- self.debug = 0
-
-
- # each plugin has a .config dict list, we add defaults here
- def add_plugin_defaults(self, config, module=""):
-
- # options
- for opt in config:
- if ("name" in opt) and ("value" in opt) and (opt["name"] not in vars(self)):
- self.__dict__[opt["name"]] = opt["value"]
-
- # plugin state
- if module and module not in conf.plugins:
- conf.plugins[module] = 1
-
-
-
- # http://standards.freedesktop.org/basedir-spec/basedir-spec-0.6.html
- def xdg(self):
- home = os.environ.get("HOME", self.tmp)
- config = os.environ.get("XDG_CONFIG_HOME", os.environ.get("APPDATA", home+"/.config"))
-
- # storage dir
- self.dir = config + "/streamtuner2"
-
- # create if necessary
- if (not os.path.exists(self.dir)):
- os.makedirs(self.dir)
-
-
- # store some configuration list/dict into a file
- def save(self, name="settings", data=None, gz=0, nice=0):
- name = name + ".json"
- if (data is None):
- data = dict(self.__dict__) # ANOTHER WORKAROUND: typecast to plain dict(), else json filter_data sees it as object and str()s it
- nice = 1
- # check for subdir
- if (name.find("/") > 0):
- subdir = name[0:name.find("/")]
- subdir = self.dir + "/" + subdir
- if (not os.path.exists(subdir)):
- os.mkdir(subdir)
- open(subdir+"/.nobackup", "w").close()
- # write
- file = self.dir + "/" + name
- # .gz or normal file
- if gz:
- f = gzip.open(file+".gz", "w")
- if os.path.exists(file):
- os.unlink(file)
- else:
- f = open(file, "w")
- # encode
- data = json.dumps(data, indent=(4 if nice else None))
- try:
- f.write(data.encode("utf-8"))
- except TypeError as e:
- f.write(data) # Python3 sometimes wants to write strings rather than bytes
- f.close()
-
-
- # retrieve data from config file
- def load(self, name):
- name = name + ".json"
- file = self.dir + "/" + name
- try:
- # .gz or normal file
- if os.path.exists(file + ".gz"):
- f = gzip.open(file + ".gz", "rt")
- elif os.path.exists(file):
- f = open(file, "rt")
- else:
- return # file not found
- # decode
- r = json.load(f)
- f.close()
- return r
- except Exception as e:
- print(dbg.ERR, "JSON parsing error (in "+name+")", e)
-
-
- # recursive dict update
- def update(self, with_new_data):
- for key,value in with_new_data.items():
- if type(value) == dict:
- self[key].update(value)
- else:
- self[key] = value
- # descends into sub-dicts instead of wiping them with subkeys
-
- # update old setting names
- def migrate(self):
- # 2.1.1
- if "audio/mp3" in self.play:
- self.play["audio/mpeg"] = self.play["audio/mp3"]
- del self.play["audio/mp3"]
-
-
- # check for existing filename in directory list
- def find_in_dirs(self, dirs, file):
- for d in dirs:
- if os.path.exists(d+"/"+file):
- return d+"/"+file
-
+ # start
+ def __init__(self):
+
+ # object==dict means conf.var is conf["var"]
+ self.__dict__ = self # let's pray this won't leak memory due to recursion issues
+
+ # prepare
+ self.defaults()
+ self.xdg()
+
+ # runtime
+ self.share = os.path.dirname(os.path.abspath(__file__))
+
+ # settings from last session
+ last = self.load("settings")
+ if (last):
+ if "share" in last:
+ del last["share"]
+ self.update(last)
+ self.migrate()
+ # store defaults in file
+ else:
+ self.save("settings")
+ self.firstrun = 1
+
+
+ # some defaults
+ def defaults(self):
+ self.play = {
+ "audio/mpeg": "audacious ", # %u for url to .pls, %g for downloaded .m3u
+ "audio/ogg": "audacious ",
+ "audio/*": "audacious ",
+ "video/youtube": "totem $(youtube-dl -g %srv)",
+ "video/*": "vlc --one-instance %srv",
+ "url/http": "sensible-browser",
+ }
+ self.record = {
+ "audio/*": "xterm -e streamripper %srv", # -d /home/***USERNAME***/Musik
+ "video/youtube": "xterm -e \"youtube-dl %srv\"",
+ }
+ self.plugins = {
+ "bookmarks": 1, # built-in plugin, cannot be disabled
+ "search": 1,
+ "streamedit": 1,
+ "configwin": 1,
+ "shoutcast": 1,
+ "xiph": 1,
+ "modarchive": 0, # disable per default
+ "file": 0, # disable per default
+ "punkcast": 0, # disable per default
+ "history": 0,
+ "basicch": 0, # ceased
+ "tv": 0, # ceased
+ }
+ self.tmp = os.environ.get("TEMP", "/tmp")
+ self.max_streams = "500"
+ self.show_bookmarks = 1
+ self.show_favicons = 1
+ self.load_favicon = 1
+ self.heuristic_bookmark_update = 0
+ self.retain_deleted = 0
+ self.auto_save_appstate = 1
+ self.theme = "" #"MountainDew"
+ self.channel_order = "shoutcast, xiph, internet_radio, jamendo, myoggradio, .."
+ self.reuse_m3u = 1
+ self.google_homepage = 0
+ self.windows = platform.system()=="Windows"
+ self.pyquery = 1
+ self.debug = 0
+
+
+ # each plugin has a .config dict list, we add defaults here
+ def add_plugin_defaults(self, config, module=""):
+
+ # options
+ for opt in config:
+ if ("name" in opt) and ("value" in opt) and (opt["name"] not in vars(self)):
+ self.__dict__[opt["name"]] = opt["value"]
+
+ # plugin state
+ if module and module not in conf.plugins:
+ conf.plugins[module] = 1
+
+
+
+ # http://standards.freedesktop.org/basedir-spec/basedir-spec-0.6.html
+ def xdg(self, path="/streamtuner2"):
+ home = os.environ.get("HOME", self.tmp)
+ config = os.environ.get("XDG_CONFIG_HOME", os.environ.get("APPDATA", home+"/.config"))
+
+ # storage dir
+ self.dir = config + path
+
+ # create if necessary
+ if (not os.path.exists(self.dir)):
+ os.makedirs(self.dir)
+
+
+ # store some configuration list/dict into a file
+ def save(self, name="settings", data=None, gz=0, nice=0):
+ name = name + ".json"
+ if (data is None):
+ data = dict(self.__dict__) # ANOTHER WORKAROUND: typecast to plain dict(), else json filter_data sees it as object and str()s it
+ nice = 1
+ # check for subdir
+ if (name.find("/") > 0):
+ subdir = name[0:name.find("/")]
+ subdir = self.dir + "/" + subdir
+ if (not os.path.exists(subdir)):
+ os.mkdir(subdir)
+ open(subdir+"/.nobackup", "w").close()
+ # write
+ file = self.dir + "/" + name
+ # .gz or normal file
+ if gz:
+ f = gzip.open(file+".gz", "w")
+ if os.path.exists(file):
+ os.unlink(file)
+ else:
+ f = open(file, "w")
+ # encode
+ data = json.dumps(data, indent=(4 if nice else None))
+ try:
+ f.write(data.encode("utf-8"))
+ except TypeError as e:
+ f.write(data) # Python3 sometimes wants to write strings rather than bytes
+ f.close()
+
+
+ # retrieve data from config file
+ def load(self, name):
+ name = name + ".json"
+ file = self.dir + "/" + name
+ try:
+ # .gz or normal file
+ if os.path.exists(file + ".gz"):
+ f = gzip.open(file + ".gz", "rt")
+ elif os.path.exists(file):
+ f = open(file, "rt")
+ else:
+ return # file not found
+ # decode
+ r = json.load(f)
+ f.close()
+ return r
+ except Exception as e:
+ print(dbg.ERR, "JSON parsing error (in "+name+")", e)
+
+
+ # recursive dict update
+ def update(self, with_new_data):
+ for key,value in with_new_data.items():
+ if type(value) == dict:
+ self[key].update(value)
+ else:
+ self[key] = value
+ # descends into sub-dicts instead of wiping them with subkeys
+
+ # update old setting names
+ def migrate(self):
+ # 2.1.1
+ if "audio/mp3" in self.play:
+ self.play["audio/mpeg"] = self.play["audio/mp3"]
+ del self.play["audio/mp3"]
+
+
+ # check for existing filename in directory list
+ def find_in_dirs(self, dirs, file):
+ for d in dirs:
+ if os.path.exists(d+"/"+file):
+ return d+"/"+file
+
+ # standard user account storage in ~/.netrc or less standard but contemporarily in ~/.config/netrc
+ def netrc(self, varhosts=("shoutcast.com")):
+ global netrc
+ if not netrc:
+ netrc = {}
+ try:
+ from netrc import netrc as parser
+ try:
+ netrc = parser().hosts
+ except:
+ netrc = parser(self.xdg() + "/netrc").hosts
+ except:
+ pass
+ for server in varhosts:
+ if server in netrc:
+ return netrc[server]
+
# Plugin meta data extraction
#
# Extremely crude version for Python and streamtuner2 plugin usage.
Index: help/channel_myoggradio.page
==================================================================
--- help/channel_myoggradio.page
+++ help/channel_myoggradio.page
@@ -21,26 +21,40 @@
The personal section is empty per default. You need to specify an user account in the settings dialog, and actually bookmark stations in the MyOggRadio web site. - Shared entries aren't automatically in the MOR favorite list.
+ Shared entries aren't automatically in theThere's also JMyOggRadioPlayer - as specific frontend and player for MyOggRadio.
+MyOggRadio also has a neat cross-platform player: + JMyOggRadioPlayer.
Login settings
If you want to upload station infos to MyOggRadio, you need an account there. Registration - is free and doesn't require personal information nor email address. Specify username and - password separated with a : colon in this field.
+If you want to upload station infos to MyOggRadio, you need an account there. + Registration is free and doesn't require personal information nor email address. + Specify it as username: separated with a : colon in this field.
+Alternatively you can store your account settings in the central
+
Your entry for MyOggRadio should follow the common format:
+
+machine myoggradio.org
+ login usr123
+ password pw123
+
+ Which is useful because it's a standard format, and prevents + leaking authorization data into per-application config stores. + Note that a user:pw setting in streamtuner takes precedence + though.
+stream URL format
When uploading stations, the streaming URL can be converted into RAW format. You can however leave it as .PLS link file.