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 @@ Share on MyOggRadio... menu entry to upload the currently selected radio (e.g. from your favourite bookmarks).

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 the personal list.

-

There's also JMyOggRadioPlayer - as specific frontend and player for MyOggRadio.

+

MyOggRadio also has a neat cross-platform player: + JMyOggRadioPlayer.

Channel options. <code>Login settings</code> -

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 + ~/.netrc config file. Or in ~/.config/netc even.

+

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.

+
<code>stream URL format</code>

When uploading stations, the streaming URL can be converted into RAW format. You can however leave it as .PLS link file.