From a2abc4901226f68bd8a2035b0c9e8dd4e58bab65 Mon Sep 17 00:00:00 2001 From: Justin Forest Date: Wed, 15 Oct 2008 15:47:27 +0400 Subject: [PATCH] New podcast finder dialog Replaces the old web OPML importer, includes top 50 podcasts from Podcast Alley and a YouTube channel finder. --- data/gpodder.glade | 618 +++++++++++++++++++++++++++++++++--------------- src/gpodder/gui.py | 108 +++++++-- src/gpodder/resolver.py | 24 ++ 3 files changed, 540 insertions(+), 210 deletions(-) diff --git a/data/gpodder.glade b/data/gpodder.glade index cfec6270..ee0d27b4 100644 --- a/data/gpodder.glade +++ b/data/gpodder.glade @@ -114,6 +114,34 @@ + + True + Find new podcasts + True + + + + + + True + gtk-find + 1 + 0.5 + 0.5 + 0 + 0 + + + + + + + + True + + + + True Preferences @@ -362,21 +390,6 @@ - - True - - - - - - True - Add podcasts from OPML on the web - True - - - - - True Send subscription list via e-mail @@ -5841,7 +5854,7 @@ MTP-based player True False - Import podcasts from web + Find new podcasts GTK_WINDOW_TOPLEVEL GTK_WIN_POS_CENTER_ON_PARENT False @@ -5864,256 +5877,485 @@ MTP-based player False 0 - - - 5 + + True - False - 5 + GTK_BUTTONBOX_END - + True - OPML: - False - False - GTK_JUSTIFY_LEFT - False - False - 0.5 - 0.5 - 0 - 0 - PANGO_ELLIPSIZE_NONE - -1 - False - 0 + True + Select All + True + GTK_RELIEF_NORMAL + True + 0 + - - 0 - False - False - - + True True - True - True - 0 - - True - - False + Select None + True + GTK_RELIEF_NORMAL + True + 0 + + + + + + + True + True + gtk-add + True + GTK_RELIEF_NORMAL + True + 0 + - - 0 - True - True - - + True + True + True True + True + gtk-close + True GTK_RELIEF_NORMAL True - + 0 + + + + + + 0 + False + True + GTK_PACK_END + + + + + + True + True + True + True + GTK_POS_TOP + False + False + + + + True + False + 0 - + + 5 True - 0.5 - 0.5 - 0 - 0 - 0 - 0 - 0 - 0 + False + 5 - + True - False - 2 + OPML: + False + False + GTK_JUSTIFY_LEFT + False + False + 0.5 + 0.5 + 0 + 0 + PANGO_ELLIPSIZE_NONE + -1 + False + 0 + + + 0 + False + False + + - - - True - gtk-goto-bottom - 4 - 0.5 - 0.5 - 0 - 0 - - - 0 - False - False - - + + + True + True + True + True + 0 + + True + + False + + + 0 + True + True + + + + + + True + True + GTK_RELIEF_NORMAL + True + - + True - Download - True - False - GTK_JUSTIFY_LEFT - False - False 0.5 0.5 - 0 - 0 - PANGO_ELLIPSIZE_NONE - -1 - False - 0 + 0 + 0 + 0 + 0 + 0 + 0 + + + + True + False + 2 + + + + True + gtk-goto-bottom + 4 + 0.5 + 0.5 + 0 + 0 + + + 0 + False + False + + + + + + True + Download + True + False + GTK_JUSTIFY_LEFT + False + False + 0.5 + 0.5 + 0 + 0 + PANGO_ELLIPSIZE_NONE + -1 + False + 0 + + + 0 + False + False + + + + - - 0 - False - False - + + 0 + False + False + + + + + 0 + False + True + + + + + + 5 + True + True + GTK_POLICY_AUTOMATIC + GTK_POLICY_AUTOMATIC + GTK_SHADOW_IN + GTK_CORNER_TOP_LEFT + + + + True + True + True + False + False + True + False + False + False + + + 0 + True + True + - 0 - False - False + False + True - - - 0 - False - True - - - - - - 5 - True - True - GTK_POLICY_AUTOMATIC - GTK_POLICY_AUTOMATIC - GTK_SHADOW_IN - GTK_CORNER_TOP_LEFT - + True - True - True - False - False - True - False - False - False + _OPML + True + False + GTK_JUSTIFY_LEFT + False + False + 0.5 + 0.5 + 0 + 0 + PANGO_ELLIPSIZE_NONE + -1 + False + 0 + + tab + - - - 0 - True - True - - - - - - True - False - 5 - + + 5 True True - Select All - True - GTK_RELIEF_NORMAL - True - + GTK_POLICY_NEVER + GTK_POLICY_ALWAYS + GTK_SHADOW_NONE + GTK_CORNER_TOP_LEFT + + + + True + True + True + False + False + True + False + False + False + + - 0 - False - False + False + True - + True - True - Select None + Top _podcasts True - GTK_RELIEF_NORMAL - True - + False + GTK_JUSTIFY_LEFT + False + False + 0.5 + 0.5 + 0 + 0 + PANGO_ELLIPSIZE_NONE + -1 + False + 0 - 0 - False - False + tab - + True - True - gtk-add - True - GTK_RELIEF_NORMAL - True - + False + 0 + + + + 5 + True + False + 0 + + + + True + Search for: + False + False + GTK_JUSTIFY_LEFT + False + False + 0.5 + 0.5 + 0 + 0 + PANGO_ELLIPSIZE_NONE + -1 + False + 0 + + + 0 + False + False + + + + + + True + True + True + True + 0 + + True + + False + + + + 0 + True + True + + + + + + True + True + Find + True + GTK_RELIEF_NORMAL + True + + + + 0 + False + False + + + + + 0 + False + True + + + + + + 5 + True + True + GTK_POLICY_NEVER + GTK_POLICY_ALWAYS + GTK_SHADOW_NONE + GTK_CORNER_TOP_LEFT + + + + True + True + True + False + False + True + False + False + False + + + + + 0 + True + True + + - 0 - False - False - GTK_PACK_END + False + True - + True - True - True - True - True - gtk-close - True - GTK_RELIEF_NORMAL - True - + _YouTube channels + True + False + GTK_JUSTIFY_LEFT + False + False + 0.5 + 0.5 + 0 + 0 + PANGO_ELLIPSIZE_NONE + -1 + False + 0 - 0 - False - False - GTK_PACK_END + tab 0 - False + True True diff --git a/src/gpodder/gui.py b/src/gpodder/gui.py index b7aa6de7..a563e840 100644 --- a/src/gpodder/gui.py +++ b/src/gpodder/gui.py @@ -47,6 +47,7 @@ from gpodder import download from gpodder import SimpleGladeApp from gpodder.liblogger import log from gpodder.dbsqlite import db +from gpodder import resolver try: from gpodder import trayicon @@ -3232,6 +3233,13 @@ class gPodderOpmlLister(GladeWidget): if hasattr(self, 'hide_url_entry'): self.hbox25.hide_all() + self.setup_treeview(self.treeviewChannelChooser) + self.setup_treeview(self.treeviewPodcastAlleyChooser) + self.setup_treeview(self.treeviewYouTubeChooser) + + self.notebookChannelAdder.connect('switch-page', lambda a, b, c: self.on_change_tab(c)) + + def setup_treeview(self, tv): togglecell = gtk.CellRendererToggle() togglecell.set_property( 'activatable', True) togglecell.connect( 'toggled', self.callback_edited) @@ -3242,10 +3250,10 @@ class gPodderOpmlLister(GladeWidget): titlecolumn = gtk.TreeViewColumn(_('Podcast'), titlecell, markup=1) for itemcolumn in ( togglecolumn, titlecolumn ): - self.treeviewChannelChooser.append_column( itemcolumn) + tv.append_column(itemcolumn) def callback_edited( self, cell, path): - model = self.treeviewChannelChooser.get_model() + model = self.get_treeview().get_model() url = model[path][2] @@ -3255,22 +3263,54 @@ class gPodderOpmlLister(GladeWidget): else: self.channels.remove( url) - self.btnOK.set_sensitive( bool(len(self.channels))) + self.btnOK.set_sensitive( bool(len(self.get_selected_channels()))) - def thread_finished(self, model): - self.treeviewChannelChooser.set_model(model) - self.btnDownloadOpml.set_sensitive(True) - self.entryURL.set_sensitive(True) - self.treeviewChannelChooser.set_sensitive(True) - self.channels = [] + def get_selected_channels(self, tab=None): + channels = [] + + model = self.get_treeview(tab).get_model() + if model is not None: + for row in model: + if row[0]: + channels.append(row[2]) - def thread_func(self): - url = self.entryURL.get_text() - importer = opml.Importer(url) - model = importer.get_model() - if len(model) == 0: - self.notification(_('The specified URL does not provide any valid OPML podcast items.'), _('No feeds found')) - util.idle_add(self.thread_finished, model) + return channels + + def on_change_tab(self, tab): + self.btnOK.set_sensitive( bool(len(self.get_selected_channels(tab)))) + + def thread_finished(self, model, tab=0): + if tab == 1: + tv = self.treeviewPodcastAlleyChooser + elif tab == 2: + tv = self.treeviewYouTubeChooser + self.entryYoutubeSearch.set_sensitive(True) + self.btnSearchYouTube.set_sensitive(True) + self.btnOK.set_sensitive(False) + else: + tv = self.treeviewChannelChooser + self.btnDownloadOpml.set_sensitive(True) + self.entryURL.set_sensitive(True) + self.channels = [] + + tv.set_model(model) + tv.set_sensitive(True) + + def thread_func(self, tab=0): + if tab == 1: + model = opml.Importer('http://podcastalley.com/feeds/PodcastAlleyTop50.opml').get_model() + if len(model) == 0: + self.notification(_('Something is wrong with PodcastAlley.com'), _('Could not get top 50 channels')) + elif tab == 2: + model = resolver.find_youtube_channels(self.entryYoutubeSearch.get_text()) + if len(model) == 0: + self.notification(_('There are no YouTube channels that would match this query.'), _('No channels found')) + else: + model = opml.Importer(self.entryURL.get_text()).get_model() + if len(model) == 0: + self.notification(_('The specified URL does not provide any valid OPML podcast items.'), _('No feeds found')) + + util.idle_add(self.thread_finished, model, tab) def get_channels_from_url( self, url, callback_for_channel = None, callback_finished = None): if callback_for_channel: @@ -3283,14 +3323,17 @@ class gPodderOpmlLister(GladeWidget): self.btnOK.set_sensitive( False) self.treeviewChannelChooser.set_sensitive( False) Thread( target = self.thread_func).start() + Thread( target = lambda: self.thread_func(1)).start() def select_all( self, value ): - self.channels = [] - for row in self.treeviewChannelChooser.get_model(): - row[0] = value - if value: - self.channels.append(row[2]) - self.btnOK.set_sensitive(bool(len(self.channels))) + enabled = False + model = self.get_treeview().get_model() + if model is not None: + for row in model: + row[0] = value + if value: + enabled = True + self.btnOK.set_sensitive(enabled) def on_gPodderOpmlLister_destroy(self, widget, *args): pass @@ -3298,6 +3341,12 @@ class gPodderOpmlLister(GladeWidget): def on_btnDownloadOpml_clicked(self, widget, *args): self.get_channels_from_url( self.entryURL.get_text()) + def on_btnSearchYouTube_clicked(self, widget, *args): + self.entryYoutubeSearch.set_sensitive(False) + self.treeviewYouTubeChooser.set_sensitive(False) + self.btnSearchYouTube.set_sensitive(False) + Thread(target = lambda: self.thread_func(2)).start() + def on_btnSelectAll_clicked(self, widget, *args): self.select_all(True) @@ -3305,6 +3354,7 @@ class gPodderOpmlLister(GladeWidget): self.select_all(False) def on_btnOK_clicked(self, widget, *args): + self.channels = self.get_selected_channels() self.gPodderOpmlLister.destroy() # add channels that have been selected @@ -3318,6 +3368,20 @@ class gPodderOpmlLister(GladeWidget): def on_btnCancel_clicked(self, widget, *args): self.gPodderOpmlLister.destroy() + def on_entryYoutubeSearch_key_press_event(self, widget, event): + if event.keyval == gtk.keysyms.Return: + self.on_btnSearchYouTube_clicked(widget) + + def get_treeview(self, tab=None): + if tab is None: + tab = self.notebookChannelAdder.get_current_page() + + if tab == 0: + return self.treeviewChannelChooser + elif tab == 1: + return self.treeviewPodcastAlleyChooser + else: + return self.treeviewYouTubeChooser class gPodderEpisodeSelector( GladeWidget): """Episode selection dialog diff --git a/src/gpodder/resolver.py b/src/gpodder/resolver.py index 18fc59ab..fb99d6fb 100644 --- a/src/gpodder/resolver.py +++ b/src/gpodder/resolver.py @@ -25,7 +25,11 @@ # * Support for Vimeo, maybe blip.tv and others. import re +import urllib import urllib2 +import gtk +import gobject +from xml.sax import saxutils from gpodder.liblogger import log from gpodder.util import proxy_request @@ -106,3 +110,23 @@ def get_real_episode_length(episode): pass return 0 + +def find_youtube_channels(string): + url = 'http://www.youtube.com/results?search_query='+ urllib.quote(string, '') +'&search_type=search_users&aq=f' + + r = re.compile('>\s+<') + data = r.sub('><', urllib.urlopen(url).read()) + + r1 = re.compile('([^<]+)[^<]*([^<]+)?') + m1 = r1.findall(data) + + r2 = re.compile('\s+') + + model = gtk.ListStore(gobject.TYPE_BOOLEAN, gobject.TYPE_STRING, gobject.TYPE_STRING) + + for (name, title, text) in m1: + link = 'http://www.youtube.com/rss/user/'+ name +'/videos.rss' + name = '%s\n%s' % (name, saxutils.escape(r2.sub(' ', text)).strip()) + model.append([False, name, link]) + + return model -- 2.11.4.GIT