Index: channels/__init__.py ================================================================== --- channels/__init__.py +++ channels/__init__.py @@ -1,8 +1,63 @@ # # encoding: UTF-8 +# api: streamtuner2 +# title: Plugin handling +# description: Channels and feature plugins reside in channels/ # api: python # type: R +# category: core +# priority: core +# +# +# +# +# # from channels._generic import * + +# Only reexport plugin classes +__all__ = [ + "GenericChannel", "ChannelPlugin" +] + + + +# Search through ./channels/ and get module basenames. +# Also order them by conf.channel_order +# +def module_list(): + + # find plugin files + ls = os.listdir(conf.share + "/channels/") + ls = [fn[:-3] for fn in ls if re.match("^[a-z][\w\d_]+\.py$", fn)] + + # resort with tab order + order = [module.strip() for module in conf.channel_order.lower().replace(".","_").replace("-","_").split(",")] + ls = [module for module in (order) if (module in ls)] + [module for module in (ls) if (module not in order)] + + return ls + + +# Parse plugin comment blocks. +# +def module_meta(): + meta = {} + + rx_meta = re.compile(r"""^#\s*(\w+):\s*(.+)$""", re.M) + + # Loop through all existing module.py scripts + for name in module_list(): + meta[name] = dict(title="", type="", description="") + + # Read and regex-extract into dict + with open("%s/channels/%s.py" % (conf.share, name)) as f: + for field in re.findall(rx_meta, f.read(1024)): + meta[name][field[0]] = field[1] + + return meta + + + + Index: channels/_generic.py ================================================================== --- channels/_generic.py +++ channels/_generic.py @@ -1,14 +1,16 @@ # # encoding: UTF-8 # api: streamtuner2 -# type: class +# type: internal +# category: ui # title: channel objects # description: base functionality for channel modules -# version: 1.0 +# version: 1.1 # author: mario # license: public domain +# priority: core # # # GenericChannel implements the basic GUI functions and defines # the default channel data structure. It implements base and # fallback logic for all other channel implementations. @@ -43,14 +45,12 @@ # generic channel module --------------------------------------- class GenericChannel(object): # desc - api = "streamtuner2" module = "generic" title = "GenericChannel" - version = 1.0 homepage = "http://milki.inlcude-once.org/streamtuner2/" base_url = "" listformat = "audio/x-scpls" audioformat = "audio/mp3" # fallback value config = [] Index: channels/file.py ================================================================== --- channels/file.py +++ channels/file.py @@ -1,15 +1,14 @@ # # api: streamtuner2 -# title: file browser plugin -# description: browses through local files, displays mp3/oggs and m3u/pls files -# version: -0.5 +# title: File browser +# description: Displays mp3/oggs or m3u/pls files from local media file directories. +# type: channel +# category: media +# version: 0.0 +# priority: optional # depends: mutagen, kiwi -# x: -# x: -# x: -# x: # # # Local file browser. # # Index: channels/global_key.py ================================================================== --- channels/global_key.py +++ channels/global_key.py @@ -1,11 +1,13 @@ # -# type: feature # api: streamtuner2 -# title: global keyboard shortcut -# description: allows switching radios in bookmarks list via key press +# title: Global keyboard shortcut +# description: Allows switching between bookmarked radios via key press. +# type: feature +# category: ui # version: 0.2 +# priority: extra # depends: python-keybinder # # # Binds a key to global desktop (F13 = left windows key). On keypress # it switches the currently playing radio station to another one in @@ -25,11 +27,10 @@ # register a key class global_key(object): module = "global_key" title = "keyboard shortcut" - version = 0.2 config = [ dict(name="switch_key", type="text", value="XF86Forward", description="global key for switching radio"), dict(name="switch_channel", type="text", value="bookmarks:favourite", description="station list to alternate in"), dict(name="switch_random", type="boolean", value=0, description="pick random channel, instead of next"), Index: channels/google.py ================================================================== --- channels/google.py +++ channels/google.py @@ -1,12 +1,15 @@ # # encoding: ISO-8859-1 # api: streamtuner2 -# title: google stations -# description: Looks up web radio stations from DMOZ/Google directory +# title: Google stations +# description: Looks up web radio homepages from DMOZ/Google directory. +# type: channel +# category: web +# priority: deprecated +# version: 0.2 # depends: channels, re, http -# version: 0.1 # author: Mario, original: Jean-Yves Lefort # # This is a plugun from streamtuner1. It has been rewritten for the # more mundane plugin API of streamtuner2 - reimplementing ST seemed # to much work. @@ -80,11 +83,10 @@ # description title = "Google" module = "google" homepage = GOOGLE_STATIONS_HOME - version = 0.2 # config data config = [ # {"name": "theme", "type": "text", "value":"Tactile", "description":"Streamtuner2 theme; no this isn't a google-specific option. But you know, the plugin options are a new toy."}, # {"name": "flag2", "type": "boolean", "value":1, "description":"oh see, an unused checkbox"} Index: channels/internet_radio_org_uk.py ================================================================== --- channels/internet_radio_org_uk.py +++ channels/internet_radio_org_uk.py @@ -1,10 +1,13 @@ # # api: streamtuner2 -# title: internet-radio.org.uk -# description: io channel +# title: Internet-Radio.org.uk +# description: Broad list of webradios from all genres. +# type: channel +# category: radio # version: 0.1 +# priority: standard # # # Might become new main plugin # # @@ -27,11 +30,10 @@ # description title = "InternetRadio" module = "internet_radio_org_uk" homepage = "http://www.internet-radio.org.uk/" - version = 0.1 listformat = "audio/x-scpls" # settings config = [ {"name":"internetradio_max_pages", "type":"int", "value":5, "description":"How many pages to fetch and read."}, Index: channels/jamendo.py ================================================================== --- channels/jamendo.py +++ channels/jamendo.py @@ -1,10 +1,13 @@ # api: streamtuner2 -# title: jamendo browser -# description: Jamendo is a license-free music collection and artist hub +# title: Jamendo +# description: A license-free music collection and artist hub. +# type: channel +# category: radio # depends: json +# priority: default # # Now utilizes the Jamendo /v3.0/ API. # # Radio station lists are fixed for now. Querying the API twice per station # doesn't seem overly sensible. Index: channels/links.py ================================================================== --- channels/links.py +++ channels/links.py @@ -1,11 +1,13 @@ # # api: streamtuner2 -# title: links to directory services -# description: provides a simple list of homepages for directory services +# title: Links to directory services +# description: Static list of various music directory websites. +# type: category +# category: web # version: 0.1 -# priority: rare +# priority: default # # # Simply adds a "links" entry in bookmarks tab, where known channels # and some others are listed with homepage links. # Index: channels/live365.py ================================================================== --- channels/live365.py +++ channels/live365.py @@ -1,7 +1,13 @@ -# api: st2 -# title: live365 channel + +# api: streamtunter2 +# title: Live365 +# description: Around 5000 categorized internet radio streams, some paid ad-free ones. +# type: channel +# category: radio +# version: 0.2 +# priority: optional # # 2.0.9 fixed by Abhisek Sanyal # @@ -26,14 +32,12 @@ # channel live365 class live365(ChannelPlugin): # desc - api = "streamtuner2" module = "live365" title = "Live365" - version = 0.1 homepage = "http://www.live365.com/" base_url = "http://www.live365.com/" listformat = "url/http" mediatype = "audio/mpeg" Index: channels/modarchive.py ================================================================== --- channels/modarchive.py +++ channels/modarchive.py @@ -1,8 +1,13 @@ # api: streamtuner2 -# title: modarchive browser +# title: MODarchive +# description: Collection of module / tracker audio files (MOD, S3M, XM, etc.) +# type: channel +# version: 0.1 +# priority: extra +# category: media # # # Just a genre browser. # # MOD files dodn't work with all audio players. And with the default @@ -36,11 +41,10 @@ # description title = "modarchive" module = "modarchive" homepage = "http://www.modarchive.org/" - version = 0.1 base = "http://modarchive.org/" # keeps category titles->urls catmap = {} categories = [] Index: channels/musicgoal.py ================================================================== --- channels/musicgoal.py +++ channels/musicgoal.py @@ -1,12 +1,14 @@ # # api: streamtuner2 -# title: MUSICGOAL channel -# description: musicgoal.com/.de combines radio and podcast listings +# title: MUSICGOAL +# description: Broad list of radio stations and podcasts. Provides a sane API, but only 5 results each. +# type: channel +# category: radio # version: 0.1 +# priority: optional # status: experimental -# pre-config: # # Musicgoal.com is a radio and podcast directory. This plugin tries to use # the new API for accessing listing data. # # @@ -30,11 +32,10 @@ class musicgoal (ChannelPlugin): # desc module = "musicgoal" title = "MUSICGOAL" - version = 0.1 homepage = "http://www.musicgoal.com/" base_url = homepage listformat = "url/direct" # settings Index: channels/myoggradio.py ================================================================== --- channels/myoggradio.py +++ channels/myoggradio.py @@ -1,14 +1,13 @@ # # api: streamtuner2 -# title: MyOggRadio channel plugin -# description: open source internet radio directory MyOggRadio +# title: MyOggRadio +# description: Open source internet radio directory. +# type: channel +# category: radio # version: 0.5 -# config: -# # priority: standard -# category: channel # depends: json, StringIO # # MyOggRadio is an open source radio station directory. Because this matches # well with streamtuner2, there's now a project partnership. Shared streams can easily # be downloaded in this channel plugin. And streamtuner2 users can easily share their @@ -37,11 +36,10 @@ # description title = "MyOggRadio" module = "myoggradio" homepage = "http://www.myoggradio.org/" api = "http://ehm.homelinux.org/MyOggRadio/" - version = 0.5 listformat = "url/direct" # config data config = [ {"name":"myoggradio_login", "type":"text", "value":"user:password", "description":"Account for storing personal favourites."}, Index: channels/punkcast.py ================================================================== --- channels/punkcast.py +++ channels/punkcast.py @@ -1,8 +1,13 @@ # api: streamtuner2 -# title: punkcast listing +# title: PunkCast +# description: Online video site that covered NYC artists. Not updated anymore. +# type: channel +# category: video +# version: 0.1 +# priority: rare # # # Disables itself per default. # ST1 looked prettier with random images within. # @@ -36,11 +41,10 @@ # description title = "punkcast" module = "punkcast" homepage = "http://www.punkcast.com/" - version = 0.1 # keeps category titles->urls catmap = {} categories = ["list"] default = "list" Index: channels/shoutcast.py ================================================================== --- channels/shoutcast.py +++ channels/shoutcast.py @@ -1,11 +1,14 @@ # # api: streamtuner2 -# title: shoutcast -# description: Channel/tab for Shoutcast.com directory -# depends: pq, re, http +# title: Shoutcast.com +# description: Primary list of shoutcast servers (now managed by radionomy). +# type: channel +# category: radio +# priority: default # version: 1.3 +# depends: pq, re, http # author: Mario # original: Jean-Yves Lefort # # Shoutcast is a server software for audio streaming. It automatically spools # station information on shoutcast.com, which this plugin can read out. @@ -33,11 +36,10 @@ # desc api = "streamtuner2" module = "shoutcast" title = "SHOUTcast" - version = 1.2 homepage = "http://www.shoutcast.com/" base_url = "http://shoutcast.com/" listformat = "audio/x-scpls" # settings Index: channels/timer.py ================================================================== --- channels/timer.py +++ channels/timer.py @@ -1,13 +1,13 @@ # # api: streamtuner2 -# title: radio scheduler -# description: time play/record events for radio stations +# title: Recording timer +# description: Schedules play/record events for bookmarked radio stations. +# type: feature +# category: ui # depends: kronos # version: 0.5 -# config: -# category: features # priority: optional # support: unsupported # # Okay, while programming this, I missed the broadcast I wanted to hear. Again(!) # But still this is a useful extension, as it allows recording and playing specific @@ -33,11 +33,10 @@ class timer: # plugin info module = "timer" title = "Timer" - version = 0.5 # configuration settings config = [ ] Index: channels/xiph.py ================================================================== --- channels/xiph.py +++ channels/xiph.py @@ -1,10 +1,13 @@ # # api: streamtuner2 # title: Xiph.org -# description: Xiph/ICEcast radio directory +# description: ICEcast radio directory. Now utilizes a cached JSON API. +# type: channel +# category: radio # version: 0.3 +# priority: standard # # # Xiph.org maintains the Ogg streaming standard and Vorbis audio compression # format, amongst others. The ICEcast server is an alternative to SHOUTcast. # @@ -37,11 +40,10 @@ # desc api = "streamtuner2" module = "xiph" title = "Xiph.org" - version = 0.3 homepage = "http://dir.xiph.org/" #base_url = "http://api.dir.xiph.org/" json_url = "http://api.include-once.org/xiph/cache.php" listformat = "url/http" config = [ Index: st2.py ================================================================== --- st2.py +++ st2.py @@ -3,11 +3,11 @@ # api: python # type: application # title: streamtuner2 # description: directory browser for internet radio / audio streams # depends: pygtk | pygi, threading, pyquery, kronos, requests -# version: 2.1.0 +# version: 2.1.1 # author: mario salzer # license: public domain # url: http://freshmeat.net/projects/streamtuner2 # config: # category: multimedia @@ -76,11 +76,11 @@ from threading import Thread Thread.stop = lambda self: None # add library path sys.path.insert(0, "/usr/share/streamtuner2") # pre-defined directory for modules -sys.path.insert(0, "/usr/share/streamtuner2/bundle") # external libraries +sys.path.append( "/usr/share/streamtuner2/bundle") # external libraries sys.path.insert(0, ".") # development module path # gtk modules from mygtk import pygtk, gtk, gobject, ui_file, mygtk, ver as GTK_VER @@ -87,10 +87,11 @@ # custom modules from config import conf # initializes itself, so all conf.vars are available right away from config import __print__, dbg import ahttp import action # needs workaround... (action.main=main) +import channels from channels import * import favicon __version__ = "2.1.0" @@ -440,17 +441,12 @@ # load plugins from /usr/share/streamtuner2/channels/ def load_plugin_channels(self): - # find plugin files - ls = os.listdir(conf.share + "/channels/") - ls = [fn[:-3] for fn in ls if re.match("^[a-z][\w\d_]+\.py$", fn)] - - # resort with tab order - order = [module.strip() for module in conf.channel_order.lower().replace(".","_").replace("-","_").split(",")] - ls = [module for module in (order) if (module in ls)] + [module for module in (ls) if (module not in order)] + # find and order plugin files + ls = channels.module_list() # step through for module in ls: gui_startup(2/10.0 + 7/10.0 * float(ls.index(module))/len(ls), "loading module "+module) @@ -751,15 +747,17 @@ class config_dialog (auxiliary_window): # display win_config, pre-fill text fields from global conf. object def open(self, widget): - self.add_plugins() + if self.first_open: + self.add_plugins() + self.combobox_theme() + self.first_open = 0 self.apply(conf.__dict__, "config_", 0) - #self.win_config.modify_bg(gtk.STATE_NORMAL, gtk.gdk.color_parse('#443399')) - self.combobox_theme() self.win_config.show() + first_open = 1 def hide(self, *args): self.win_config.hide() return True @@ -816,22 +814,19 @@ conf.theme = self.theme.get_model()[ self.theme.get_active()][0] main.load_theme() # add configuration setting definitions from plugins - once = 0 def add_plugins(self): - if self.once: - return - for name,enabled in conf.plugins.items(): + for name,meta in channels.module_meta().items(): # add plugin load entry if name: - label = ("enable ⎗ %s channel" if self.channels.get(name) else "use ⎗ %s plugin") - cb = gtk.ToggleButton(label=label % name) - self.add_( "config_plugins_"+name, cb )#, label=None, color="#ddd" ) + cb = gtk.CheckButton(name) + cb.child.set_markup("%s (%s) %s\n%s" % (meta["title"], meta["type"], meta.get("version", ""), meta["description"])) + self.add_( "config_plugins_"+name, cb ) # look up individual plugin options, if loaded if self.channels.get(name) or self.features.get(name): c = self.channels.get(name) or self.features.get(name) for opt in c.config: @@ -846,31 +841,36 @@ else: self.add_( "config_"+opt["name"], gtk.Entry(), opt["description"] ) # spacer self.add_( "filler_pl_"+name, gtk.HSeparator() ) - self.once = 1 # put gtk widgets into config dialog notebook def add_(self, id, w, label=None, color=""): w.set_property("visible", True) main.widgets[id] = w if label: w.set_width_chars(10) - label = gtk.Label(label) - label.set_property("visible", True) - label.set_line_wrap(True) - label.set_size_request(250, -1) - vbox = gtk.HBox(homogeneous=False, spacing=10) - vbox.set_property("visible", True) - vbox.pack_start(w, expand=False, fill=False) - vbox.pack_start(label, expand=True, fill=True) - w = vbox + w = self.hbox(w, self.label(label)) if color: w = mygtk.bg(w, color) self.plugin_options.pack_start(w) + + def label(self, label): + label = gtk.Label(label) + label.set_property("visible", True) + label.set_line_wrap(True) + label.set_size_request(250, -1) + return label + + def hbox(self, w1, w2): + vbox = gtk.HBox(homogeneous=False, spacing=10) + vbox.set_property("visible", True) + vbox.pack_start(w1, expand=False, fill=False) + vbox.pack_start(w2, expand=True, fill=True) + return vbox # save config def save(self, widget): self.apply(conf.__dict__, "config_", 1)