Index: channels/__init__.py ================================================================== --- channels/__init__.py +++ channels/__init__.py @@ -104,10 +104,14 @@ empty_stub = [dict(genre="./.", title="No categories found (HTTP error)", playing="Try Channel→Reload Categories later..", url="none:", listeners=0, bitrate=0, homepage="", state="gtk-stop")] # regex rx_www_url = re.compile("""(www(\.\w+[\w-]+){2,}|(\w+[\w-]+[ ]?\.)+(com|FM|net|org|de|PL|fr|uk))""", re.I) + + + #--------------------------- initialization -------------------------------- + # constructor def __init__(self, parent=None): #self.streams = {} @@ -127,36 +131,10 @@ # only if streamtuner2 is run in graphical mode if (parent): self.cache() self.gui(parent) pass - - - # These are all implemented in main (where they don't belong!) - def stations(self): - return self.streams.get(self.current, []) - def rowno(self): - pass - def row(self): - pass - - - # read previous channel/stream data, if there is any - def cache(self): - # stream list - cache = conf.load("cache/" + self.module) - if (cache): - self.streams = cache - # categories - cache = conf.load("cache/categories_" + self.module) - if (cache): - self.categories = cache - # catmap (optional) - cache = conf.load("cache/catmap_" + self.module) - if (cache): - self.catmap = cache - pass # initialize Gtk widgets / data objects def gui(self, parent): #print(self.module + ".gui()") @@ -187,10 +165,72 @@ uikit.columns(self.gtk_list, self.datamap, []) # add to main menu uikit.add_menu([parent.channelmenuitems], self.meta["title"], lambda w: parent.channel_switch_by_name(self.module) or 1) + + # Statusbar stub (defers to parent/main window, if in GUI mode) + def status(self, *v): + if self.parent: self.parent.status(*v) + else: __print__(dbg.INFO, "status():", *v) + + + + #--------------------- streams/model data accesss --------------------------- + + # Get list of stations in current category + def stations(self): + return self.streams.get(self.current, []) + + # Convert ListStore iter to row number + def rowno(self): + (model, iter) = self.model_iter() + return model.get_path(iter)[0] + + # Return ListStore object and Iterator for currently selected row in gtk.TreeView station list + def model_iter(self): + return self.gtk_list.get_selection().get_selected() + + # Currently selected entry in stations list, return complete data dict + def row(self): + return self.stations() [self.rowno()] + + # Fetches a single varname from currently selected station entry + def selected(self, name="url"): + return self.row().get(name) + + # Inject status icon into currently selected row (used by main.bookmark() call) + def row_icon(self, gtkIcon = gtk.STOCK_ABOUT): + try: + # Updates gtk_list store, set icon in current display. + # Since it is used by bookmarks, would be reshown with next display() anyhow, + # and there's no need to invalidate the ls cache, because that's referenced by model anyhow. + (model,iter) = self.model_iter() + model.set_value(iter, 0, gtkIcon) + except: + pass + + + + #------------------------ base implementations ----------------------------- + + # read previous channel/stream data, if there is any + def cache(self): + # stream list + cache = conf.load("cache/" + self.module) + if (cache): + self.streams = cache + # categories + cache = conf.load("cache/categories_" + self.module) + if (cache): + self.categories = cache + # catmap (optional) + cache = conf.load("cache/catmap_" + self.module) + if (cache): + self.catmap = cache + pass + # make private copy of .datamap and modify field (title= only ATM) def update_datamap(self, search="name", title=None): if self.datamap == GenericChannel.datamap: self.datamap = copy.deepcopy(self.datamap) @@ -441,22 +481,32 @@ #--------------------------- actions --------------------------------- - # invoke action.play, - # can be overridden to provide channel-specific "play" alternative - def play(self, row): - if row.get("url"): + # Invoke action.play() for current station. + # Can be overridden to provide channel-specific "play" alternative + def play(self): + row = self.row() + if row: + # playlist and audio type + audioformat = row.get("format", self.audioformat) + listformat = row.get("listformat", self.listformat) + # invoke audio player + action.play(row["url"], audioformat, listformat, row) + else: + self.status("No station selected for playing.") + return row - # parameters + # Start streamripper/youtube-dl/etc + def record(self): + row = self.row() + if row: audioformat = row.get("format", self.audioformat) listformat = row.get("listformat", self.listformat) - - # invoke audio player - action.play(row["url"], audioformat, listformat) - + action.record(row.get("url"), audioformat, listformat, row=row) + return row #--------------------------- utility functions ----------------------- Index: st2.py ================================================================== --- st2.py +++ st2.py @@ -68,16 +68,17 @@ # This represents the main window, dispatches Gtk events, # and shares most application behaviour with the channel modules. class StreamTunerTwo(gtk.Builder): # object containers - widgets = {} # non-glade widgets (the manually instantiated ones) + widgets = {} # non-glade widgets (any manually instantiated ones) channels = {} # channel modules features = {} # non-channel plugins working = [] # threads hooks = { "play": [favicon.download_playing], # observers queue here + "record": [], "init": [], "config_load": [], "config_save": [], } meta = plugin_meta() @@ -260,41 +261,32 @@ # Mirror selected channel tab into main window title def update_title(self): self.win_streamtuner2.set_title("Streamtuner2 - %s" % self.channel().meta.get("title")) - # Convert ListStore iter to row number - def rowno(self): - (model, iter) = self.model_iter() - return model.get_path(iter)[0] - - # Currently selected entry in stations list, return complete data dict + # Channel: row{} dict for current station def row(self): - return self.channel().stations() [self.rowno()] - - - # return ListStore object and Iterator for currently selected row in gtk.TreeView station list - def model_iter(self): - return self.channel().gtk_list.get_selection().get_selected() - - # Fetches a single varname from currently selected station entry + return self.channel().row() + + # Channel: fetch single varname from station row{} dict def selected(self, name="url"): return self.row().get(name) # Play button def on_play_clicked(self, widget, event=None, *args): - row = self.row() - if row: - self.channel().play(row) - [callback(row) for callback in self.hooks["play"]] + self.status("Starting player...") + row = self.channel().play() + self.status("") + [callback(row) for callback in self.hooks["play"]] # Recording: invoke streamripper for current stream URL def on_record_clicked(self, widget): - row = self.row() - action.record(row.get("url"), row.get("format", "audio/mpeg"), "url/direct", row=row) + self.status("Recording station...") + row = self.channel().record() + [callback(row) for callback in self.hooks["record"]] # Open stream homepage in web browser def on_homepage_stream_clicked(self, widget): url = self.selected("homepage") if url and len(url): action.browser(url) @@ -331,16 +323,11 @@ pass # Add current selection to bookmark store def bookmark(self, widget): self.bookmarks.add(self.row()) - # code to update current list (set icon just in on-screen liststore, it would be updated with next display() anyhow - and there's no need to invalidate the ls cache, because that's referenced by model anyhow) - try: - (model,iter) = self.model_iter() - model.set_value(iter, 0, gtk.STOCK_ABOUT) - except: - pass + self.channel().row_icon(gtk.STOCK_ABOUT) # refresh bookmarks tab self.bookmarks.load(self.bookmarks.default) # Reload category tree def update_categories(self, widget): @@ -355,23 +342,26 @@ def save_as(self, widget): row = self.row() default_fn = row["title"] + ".m3u" fn = uikit.save_file("Save Stream", None, default_fn, [(".m3u","*m3u"),(".pls","*pls"),(".xspf","*xspf"),(".smil","*smil"),(".asx","*asx"),("all files","*")]) if fn: - action.save(row, fn) + source = row.get("listformat", self.channel().listformat) + dest = (re.findall("\.(m3u|pls|xspf|jspf|json|smil|asx|wpl)8?$", fn) or ["pls"])[0] + action.save_playlist(source=source, multiply=True).save(rows=[row], fn=fn, dest=dest) pass # Save current stream URL into clipboard def menu_copy(self, w): gtk.clipboard_get().set_text(self.selected("url")) # Remove a stream entry def delete_entry(self, w): - n = self.rowno() - del self.channel().stations()[ n ] - self.channel().switch() - self.channel().save() + cn = self.channel() + n = cn.rowno() + del cn.stations()[ n ] + cn.switch() + cn.save() # Alternative Notebook channel tabs between TOP and LEFT position def switch_notebook_tabs_position(self, w, pos): self.notebook_channels.set_tab_pos(pos);