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)