diff options
Diffstat (limited to 'totem')
-rw-r--r-- | totem/Makefile.am | 1 | ||||
-rw-r--r-- | totem/plugin/Makefile.am | 23 | ||||
-rw-r--r-- | totem/plugin/SohuVideoList.py | 143 | ||||
-rw-r--r-- | totem/plugin/sohuvideo-config.ui | 238 | ||||
-rw-r--r-- | totem/plugin/sohuvideo-ui.xml | 7 | ||||
-rw-r--r-- | totem/plugin/sohuvideo.py | 1743 | ||||
-rw-r--r-- | totem/plugin/sohuvideo.totem-plugin.in | 9 | ||||
-rw-r--r-- | totem/plugin/sohuvideo.ui | 378 | ||||
-rw-r--r-- | totem/plugin/sohuvideo_translation.py.in | 9 | ||||
-rw-r--r-- | totem/plugin/sohuvideolist.py | 522 |
10 files changed, 3073 insertions, 0 deletions
diff --git a/totem/Makefile.am b/totem/Makefile.am new file mode 100644 index 0000000..3f7beb2 --- /dev/null +++ b/totem/Makefile.am @@ -0,0 +1 @@ +SUBDIRS = plugin
\ No newline at end of file diff --git a/totem/plugin/Makefile.am b/totem/plugin/Makefile.am new file mode 100644 index 0000000..2d9360b --- /dev/null +++ b/totem/plugin/Makefile.am @@ -0,0 +1,23 @@ +plugindir = $(PLUGINDIR)/sohuvideo +uidir = $(plugindir) + +plugin_PYTHON = \ + sohuvideolist.py \ + sohuvideo.py \ + SohuVideoList.py + +sohuvideo_translation.py: sohuvideo_translation.py.in + sed -e "s,\@GETTEXT_PACKAGE\@,$(GETTEXT_PACKAGE),g" \ + -e "s,\@LOCALEDIR\@,$(datadir)/locale,g" $< >$@ + +plugin_in_files = sohuvideo.totem-plugin.in + +%.totem-plugin: %.totem-plugin.in $(INTLTOOL_MERGE) $(wildcard $(top_srcdir)/po/*po) ; $(INTLTOOL_MERGE) $(top_srcdir)/po $< $@ -d -u -c $(top_builddir)/po/.intltool-merge-cache + +plugin_DATA = $(plugin_in_files:.totem-plugin.in=.totem-plugin) sohuvideo_translation.py +ui_DATA = sohuvideo.ui sohuvideo-ui.xml sohuvideo-config.ui + +EXTRA_DIST = $(plugin_in_files) $(ui_DATA) sohuvideo.py sohuvideo_translation.py.in SohuVideoList.py + +CLEANFILES = $(plugin_DATA) sohuvideo_translation.py +DISTCLEANFILES = $(plugin_DATA) sohuvideo_translation.py diff --git a/totem/plugin/SohuVideoList.py b/totem/plugin/SohuVideoList.py new file mode 100644 index 0000000..6c6bdfd --- /dev/null +++ b/totem/plugin/SohuVideoList.py @@ -0,0 +1,143 @@ +# Copyright (C) 2009 Luo Jinghua <sunmoon1997@gmail.com>. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or (at +# your option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +import pygtk +pygtk.require('2.0') + +import sys +import gobject +import gtk +import totem + +class SohuVideoList (gtk.TreeView): + __gproperties__ = { + 'title-column' : (gobject.TYPE_INT, "title-column", + "title-column", -1, gobject.G_MAXINT, -1, + gobject.PARAM_READWRITE), + 'tooltip-column' : (gobject.TYPE_INT, 'tooltip-column', + 'tooltip-column', -1, gobject.G_MAXINT, -1, + gobject.PARAM_READWRITE), + 'mrl-column' : (gobject.TYPE_INT, 'mrl-column', 'mrl-column', + -1, gobject.G_MAXINT, -1, gobject.PARAM_READWRITE), + "totem": (gobject.TYPE_OBJECT, "Totem", + "Totem", gobject.PARAM_READWRITE), + "plugin": (gobject.TYPE_OBJECT, "plugin", + "plugin", gobject.PARAM_READWRITE), + } + __gsignals__ = { 'starting-video': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_BOOLEAN, + (gobject.TYPE_OBJECT, gobject.TYPE_PYOBJECT )), + } + + __gtype_name__ = 'SohuVideoList' + + def __init__ (self): + self.plugin = None + self.totem = None + self.builder = None + gtk.TreeView.__init__ (self) + + self.set_property ("has-tooltip", True) + self.connect ("row-activated", self.row_activated_cb) + self.connect ("query-tooltip", self.query_tooltip_cb) + self.connect ("button-press-event", self.button_pressed_cb) + self.connect ("popup-menu", self.popup_menu_cb) + + selection = self.get_selection () + selection.connect ("changed", self.selection_changed_cb) + selection.set_mode (gtk.SELECTION_MULTIPLE) + + def do_set_property(self, pspec, value): + setattr(self, pspec.name, value) + + def do_get_property(self, pspec): + return getattr(self, pspec.name) + + def row_activated_cb (self, widget, path, column): + #mrl_column = widget.get_property ("mrl-column") + if self.get_property ("mrl-column") == -1: + return + + play_video = self.emit ("starting-video", self, path) + if not play_video: + return + + model = widget.get_model () + iter = model.get_iter (path) + + if type (model) is gtk.TreeModelFilter: + iter = model.convert_iter_to_child_iter (iter) + model = model.get_model () + + mrl = model.get (iter, self.get_property ("mrl-column"))[0] + title = model.get (iter, self.get_property ("title-column"))[0] + if not mrl: + return + if hasattr(self.totem, "add_to_playlist_and_play"): + self.totem.add_to_playlist_and_play (mrl, title, False) + else: + self.totem.action_remote (totem.REMOTE_COMMAND_REPLACE, mrl) + + if not hasattr (gtk.TreeView, 'get_tooltip_context'): + def get_tooltip_context (self, x, y, keyboard_mode): + model = self.get_model () + if keyboard_mode: + # Keyboard mode + ret = self.get_cursor () + if not ret[0]: + return None + path = ret[0] + else: + coords = self.convert_widget_to_bin_window_coords (x, y) + + # Mouse mode + path = self.get_path_at_pos (coords[0], coords[1]) + if not path: + return None + path = path[0] + return model, path, model.get_iter (path) + + def query_tooltip_cb (self, widget, x, y, keyboard_mode, tooltip): + if not widget.get_tooltip_context (x, y, keyboard_mode): + return False + + model, path, iter = widget.get_tooltip_context (x, y, keyboard_mode) + if type (model) is gtk.TreeModelFilter: + iter = model.convert_iter_to_child_iter (iter) + model = model.get_model () + mrl_column = self.get_property ("mrl-column") + if mrl_column == -1: + text = model.get (iter, self.get_property ("tooltip-column"))[0] + tooltip.set_text (text) + else: + text = model.get (iter, self.get_property ("tooltip-column"))[0] + text += '\n' + model.get (iter, self.get_property ("mrl-column"))[0] + tooltip.set_text (text) + widget.set_tooltip_row (tooltip, path) + return True + + def button_pressed_cb (self, widget, event): + pass + + def selection_changed_cb (self, widget): + self.trigger_tooltip_query () + + def popup_menu_cb (self, treeview, widget): + return False + + def get_ui_manager (self): + return self.ui_manager + +gobject.type_register (SohuVideoList) diff --git a/totem/plugin/sohuvideo-config.ui b/totem/plugin/sohuvideo-config.ui new file mode 100644 index 0000000..6c417d9 --- /dev/null +++ b/totem/plugin/sohuvideo-config.ui @@ -0,0 +1,238 @@ +<?xml version="1.0"?> +<interface> + <!-- interface-requires gtk+ 2.12 --> + <!-- interface-naming-policy project-wide --> + <object class="GtkDialog" id="sohuvideo_config_dialog"> + <property name="border_width">5</property> + <property name="title" translatable="yes">Configure Sohu video</property> + <property name="type_hint">normal</property> + <property name="has_separator">False</property> + <child internal-child="vbox"> + <object class="GtkVBox" id="dialog-vbox1"> + <property name="visible">True</property> + <property name="orientation">vertical</property> + <property name="spacing">2</property> + <child> + <object class="GtkFrame" id="frame1"> + <property name="visible">True</property> + <property name="label_xalign">0</property> + <property name="shadow_type">none</property> + <child> + <object class="GtkAlignment" id="alignment1"> + <property name="visible">True</property> + <property name="left_padding">12</property> + <child> + <object class="GtkVBox" id="vbox1"> + <property name="visible">True</property> + <property name="orientation">vertical</property> + <child> + <object class="GtkCheckButton" id="sohuvideo_config_show_posters"> + <property name="label" translatable="yes">Show posters</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">False</property> + <property name="draw_indicator">True</property> + </object> + <packing> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkCheckButton" id="sohuvideo_config_compatible"> + <property name="label" translatable="yes">Be compatible with old totem</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">False</property> + <property name="tooltip_text" translatable="yes">Enable a workaround for old totem such as totem 2.22 shiped with ubuntu 8.04. If the sohu video sidebar doesn't show anything in "categories" page, please enable this.</property> + <property name="draw_indicator">True</property> + </object> + <packing> + <property name="position">1</property> + </packing> + </child> + <child> + <placeholder/> + </child> + </object> + </child> + </object> + </child> + <child type="label"> + <object class="GtkLabel" id="label1"> + <property name="visible">True</property> + <property name="label" translatable="yes"><b>Sohu video</b></property> + <property name="use_markup">True</property> + </object> + </child> + </object> + <packing> + <property name="position">1</property> + </packing> + </child> + <child internal-child="action_area"> + <object class="GtkHButtonBox" id="dialog-action_area1"> + <property name="visible">True</property> + <property name="layout_style">end</property> + <child> + <object class="GtkButton" id="sohuvideo_config_ok_button"> + <property name="label">gtk-ok</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">True</property> + <property name="use_stock">True</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkButton" id="sohuvideo_config_cancel_button"> + <property name="label">gtk-cancel</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">True</property> + <property name="use_stock">True</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + <property name="position">1</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="pack_type">end</property> + <property name="position">0</property> + </packing> + </child> + </object> + </child> + <action-widgets> + <action-widget response="0">sohuvideo_config_ok_button</action-widget> + <action-widget response="1">sohuvideo_config_cancel_button</action-widget> + </action-widgets> + </object> +</interface> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/totem/plugin/sohuvideo-ui.xml b/totem/plugin/sohuvideo-ui.xml new file mode 100644 index 0000000..d0c6398 --- /dev/null +++ b/totem/plugin/sohuvideo-ui.xml @@ -0,0 +1,7 @@ + <ui> + <popup name="sohuvideo-list-popup"> + <menuitem name="add-to-playlist" action="add-to-playlist"/> + <menuitem name="copy-location" action="copy-location"/> + </popup> + </ui> + diff --git a/totem/plugin/sohuvideo.py b/totem/plugin/sohuvideo.py new file mode 100644 index 0000000..bef9930 --- /dev/null +++ b/totem/plugin/sohuvideo.py @@ -0,0 +1,1743 @@ +import totem +import gobject, gtk, gconf +gobject.threads_init() +import urllib +import httplib +import htmlentitydefs +import threading +import time +import re +import os +import random +import gettext +import copy +import pickle + +from xml.dom import minidom +from traceback import print_exc + +from sohuvideo_translation import _ + +import sohuvideolist +import SohuVideoList + +try: + from hashlib import md5 + + def url_digest (url): + m = md5 () + m.update (url) + return m.hexdigest () +except ImportError: + import md5 + + def url_digest (url): + m = md5.new () + m.update (url) + return m.hexdigest () + +def unescape_charref (ref): + name = ref[2:-1] + base = 10 + if name[0] == 'x': + name = name[1:] + base = 16 + return unichr (int(name, base)) + +def replace_entities (match): + ent = match.group () + if ent[1] == "#": + return unescape_charref (ent) + + repl = htmlentitydefs.name2codepoint.get (ent[1:-1]) + if repl is not None: + repl = unichr (repl) + else: + repl = ent + return repl + +def unescape (data): + return re.sub (r"&#?[A-Za-z0-9]+?;", replace_entities, data) + +def unescape_xml (data): + ustr = unicode(data, 'utf-8') + return unescape(ustr).encode ('utf-8') + +gconf_key = '/apps/totem/plugins/sohuvideo' + +sohuvideo_cache_dir = os.path.expanduser ('~/.local/share/totem/plugin/sohuvideo') + + +sohuvideo_images_dir = os.path.join (sohuvideo_cache_dir, "images") +if not os.path.exists (sohuvideo_images_dir): + os.makedirs (sohuvideo_images_dir) + +### special category ids +CATEGORY_ID_FAVORITES = 0 +CATEGORY_ID_RECENT = -1 +CATEGORY_ID_SEARCH = -2 + +### special category index +CATEGORY_FAVORITES = 0 +CATEGORY_RECENT = 1 +CATEGORY_SEARCH = 2 +CATEGORY_NUM_SPECIAL = 3 + +### maximium number of recents +MAX_RECENT = 25 + +### maximium cached time +CATEGORY_MAX_CACHED_TIME = 2 * 24 * 60 * 60 +MOVIES_MAX_CACHED_TIME = 7 * 24 * 60 * 60 +MOVIE_MAX_CACHED_TIME = 1 * 24 * 60 * 60 + +def xml_node_value (node): + return node.firstChild.nodeValue + +def xml_child_value (node, id): + child = node.getElementsByTagName(id)[0].firstChild; + if child: + return child.nodeValue + return None + +class ProcessThread (threading.Thread): + def __init__ (self, data, process, callback, *args, **kwargs): + self.data = data + self.process = process + self.callback = callback + self.args = args + self.kwargs = kwargs + threading.Thread.__init__ (self) + + def run (self): + try: + res = self.process (self.data, *self.args, **self.kwargs) + except Exception, e: + print "Couldn't process data: ", e + print_exc() + res = None + + gobject.idle_add (self.publish_results, res) + + def publish_results(self, res): + self.callback (res, *self.args, **self.kwargs) + return False + +class DownloadThread (threading.Thread): + def __init__ (self, url, callback, *args, **kwargs): + self.url = url + self.callback = callback + self.args = args + self.kwargs = kwargs + threading.Thread.__init__ (self) + + def run (self): + for i in range (3): + try: + res = urllib.urlopen (self.url).read () + except Exception, e: + print "Coudn't open url: ", e + res = None + if res: + break + gobject.idle_add (self.publish_results, res) + + def publish_results(self, res): + self.callback (res, *self.args, **self.kwargs) + return False + +class RetrieveImageTask: + pass + +class RetrieveImage (threading.Thread): + def __init__ (self): + self.queue = [] + self._cache = {} + self._lock = threading.Lock () + self._done = False + threading.Thread.__init__ (self) + + def retrieve (self, url, callback, *args): + task = RetrieveImageTask () + task.url = url + task.callback = callback + task.args = args + self._lock.acquire () + self.queue.append (task) + self._lock.release () + + def cancel_tasks (self): + self._lock.acquire (True) + self.queue = [] + self._cache = {} + self._lock.release () + + def cancel (self): + self._done = True + self._lock.acquire (True) + self.queue = [] + self._cache = {} + self._lock.release () + + def is_busy (self): + self._lock.acquire (True) + busy = len (self.queue) != 0 + self._lock.release () + + return busy + + def get_remaning_tasks (self): + self._lock.acquire (True) + num = len (self.queue) + self._lock.release () + + return num + + def image_name_from_url (self, url): + ext = url.rfind('.') + if ext >= 0: + ext = url[ext:] + else: + ext = '' + filename = url_digest (url) + ext + return os.path.join (sohuvideo_cache_dir, "images", filename) + + def load_image_from_disk (self, url): + filename = self.image_name_from_url (url) + if not os.access (filename, os.R_OK): + return None + + #print 'loading image', filename + try: + pixbuf = gtk.gdk.pixbuf_new_from_file (filename) + except gobject.GError: + return None + return pixbuf + + def store_image_to_disk (self, url, origin): + filename = self.image_name_from_url (url) + df = None + sf = None + try: + df = file (filename, 'wb') + sf = file (origin, 'rb') + while True: + buf = sf.read (4096) + if not buf: + break + df.write (buf) + except Exception, e: + print 'Failed to copy ', origin, ' to ', filename + print e + finally: + if df: + df.close () + if sf: + sf.close () + + def load_pixbuf (self, url): + pixbuf = self.load_image_from_disk (url) + if pixbuf: + return pixbuf + + try: + filename, headers = urllib.urlretrieve (url) + except Exception, e: + print "Couldn't download ", url + print "Error: ", e + return None + + try: + pixbuf = gtk.gdk.pixbuf_new_from_file (filename) + except gobject.GError: + return None + + self.store_image_to_disk (url, filename) + os.unlink (filename) + return pixbuf + + def load_from_cache (self, url): + pixbuf = None + self._lock.acquire (True) + if url in self._cache: + pixbuf = self._cache [url] + self._lock.release () + return pixbuf + + def put_in_cache (self, url, pixbuf): + self._lock.acquire (True) + self._cache[url] = pixbuf + self._lock.release () + + def run (self): + import time + while not self.done: + task = None + self._lock.acquire (True) + if len (self.queue): + #print 'pending tasks:', len(self.queue) + task = self.queue.pop (0) + self._lock.release () + + if not task: + time.sleep (0.1) + continue + + pixbuf = self.load_from_cache (task.url) + if not pixbuf: + #print 'loading:', task.url + pixbuf = self.load_pixbuf (task.url) + if pixbuf: + self.put_in_cache (task.url, pixbuf) + time.sleep (0.10) + + if not pixbuf: + continue + + #print 'image loaded:', task.url + gobject.timeout_add (50, self.publish_result, pixbuf, task) + + def publish_result (self, pixbuf, task): + task.callback (pixbuf, *task.args) + return False + + @property + def done (self): + self._lock.acquire (True) + res = self._done + self._lock.release () + return res + +class Sohuvideo (totem.Plugin): + def __init__ (self): + totem.Plugin.__init__ (self) + self.debug = False + self.gstreamer_plugins_present = True + + self.gconf = gconf.client_get_default () + + self.max_results = 20 + self.button_down = False + + self.start_index = {} + self.entry = {} + + self.current_treeview_name = "" + self.notebook_pages = [] + + self.vadjust = {} + self.liststore = {} + self.treeview = {} + + self.subclass_index = 0 + self.sohuvideolist = sohuvideolist.SohuVideoList() + + def load_ui (self, filename, fatal, parent, user_data): + datadir = os.path.dirname (__file__) + filename = os.path.join (datadir, filename) + builder = gtk.Builder () + builder.set_translation_domain ('totem-sohuvideo') + builder.add_from_file (filename) + builder.connect_signals (self, user_data) + return builder + + def activate (self, totem_object): + bvw_name = totem_object.get_video_widget_backend_name () + self.can_recode_mrl = False + + """Continue loading the plugin as before""" + self.builder = self.load_ui ("sohuvideo.ui", True, + totem_object.get_main_window (), self) + self.config_builder = self.load_ui ("sohuvideo-config.ui", True, + totem_object.get_main_window (), self) + + self.totem = totem_object + + self.search_entry = self.builder.get_object ("sohuvideo_search_entry") + self.search_entry.connect ("activate", self.on_search_entry_activated) + self.search_button = self.builder.get_object ("sohuvideo_search_button") + self.search_mode_button = self.builder.get_object ('sohuvideo_search_checkbutton') + self.search_mode_button.set_label (_("filter:")) + self.search_mode_button.connect ('toggled', self.on_search_mode_entry_toggled) + + #self.search_button.set_label ('') + self.search_button.connect ("clicked", self.on_search_button_clicked) + self.refresh_button = self.builder.get_object ("sohuvideo_refresh_button") + #self.refresh_button.set_label ('') + self.refresh_button.connect ("clicked", self.on_refresh_button_clicked) + self.progress_bar = self.builder.get_object ("sohuvideo_progress_bar") + self.progress_bar.hide () + + self.notebook = self.builder.get_object ("sohuvideo_notebook") + self.notebook.connect ("switch-page", self.on_notebook_page_changed) + + self.notebook_pages = ["categories", "movies", "files"] + self.current_treeview_name = "categories" + self.load_config () + self.setup_favorites () + self.setup_recent () + self.setup_search () + self.setup_categories () + self.setup_movies () + self.setup_files () + self.setup_page_ui () + self.setup_config_dialog () + + self.vbox = self.builder.get_object ("sohuvideo_vbox") + self.vbox.show_all () + self.page_hbox.hide() + self.favorites_hbox.hide() + + totem_object.add_sidebar_page ("sohuvideo", _("Sohu video"), self.vbox) + + self.retrieveimage = RetrieveImage () + self.retrieveimage.start () + + self.search_terms = '' + self.search_web_terms = '' + self.search_web = False + self.classes = [] + self.fetch_classes () + + self.start_idle_task () + + def deactivate (self, totem): + self.clear_ui () + self.categories_count +=1 + self.movies_count += 1 + self.classes = [] + + self.retrieveimage.cancel () + self.retrieveimage.join () + + self.stop_idle_task () + + self.sohuvideolist.reset() + + totem.remove_sidebar_page ("sohuvideo") + + def is_configurable (self): + return True + + def create_configure_dialog (self): + dialog = self.config_builder.get_object ("sohuvideo_config_dialog") + button = self.config_builder.get_object ("sohuvideo_config_show_posters") + button.set_active (self.configs['show_posters']) + button = self.config_builder.get_object ("sohuvideo_config_compatible") + button.set_active (self.configs['compatible']) + return dialog + + def start_idle_task (self): + if self.configs['compatible']: + self.source_id = gobject.idle_add (self.on_idle) + else: + self.source_id = 0 + + def stop_idle_task (self): + if self.source_id: + gobject.source_remove (self.source_id) + self.source_id = 0 + + def on_idle (self): + time.sleep (0.001) + return True + + def load_special_classes (self): + ### show favorites + self.subclass_index += 1 + self.sohuvideolist.updateClasses([self.favorites]) + + ### show recent + self.subclass_index += 1 + self.sohuvideolist.updateClasses([self.recent]) + + ### search + self.subclass_index += 1 + self.sohuvideolist.updateClasses([self.search]) + + ### fill loaded categories + self.fill_all_categories () + + def __fetch_classes (self, *args): + self.fetching_classes = True + return self.sohuvideolist.fetchClasses() + + def fetch_classes (self, force = False): + if force: + self.sohuvideolist.reset() + + if self.sohuvideolist.getClasses() == 0: + return + + self.movie_class_path = () + self.subclass_index = 0 + self.categories_count += 1 + self.movies_count += 1 + self.fetching_classes = False + self.clear_ui () + self.retrieveimage.cancel_tasks () + + ### load special classes first + self.load_special_classes () + + ### download classes from sohuvideo.tv + thread = ProcessThread (None, + self.__fetch_classes, + self.on_classes_fetched, + self.categories_count) + thread.start() + + gobject.timeout_add (300, + self.update_categories_progress, + self.categories_count) + + def on_classes_fetched (self, res, count): + self.fetching_classes = False + if not res: + print "Couldn't load sohuvideo playlist" + return + + ### check if this is a canceled task + if self.categories_count == count: + self.parse_class (res) + + def __parse_movies (self, res, class_path, page, count): + cls = self.get_movie_class (class_path) + + if res: + try: + result = self.sohuvideolist.parseMovieList(cls, res, page) + except Exception, e: + print "Couldn't parse movie list:", class_path, e + result = None + + cls.parsing_movies = False + return result + + def on_movies_fetched (self, res, class_path, page, count, new = True): + if self.movies_count != count: + return + + ### check if the server repeats the last page + cls = self.get_movie_class (class_path) + + if res is None: + cls.fetching_movies = False + return + + ### If the size of content is larger then 4k, parse it in another thread. + ### Blocking the main thread is bad. + if res and len(res) > 1024 * 2: + cls.parsing_movies = True + process = ProcessThread (res, self.__parse_movies, + self.add_parsed_movies, + class_path, page, count) + process.start () + self.update_progress_bar () + return + + result = self.__parse_movies (res, class_path, page, count) + self.add_parsed_movies (result, class_path, page, count) + + def add_parsed_movies (self, result, class_path, page, count): + if self.movies_count != count or page != self.movies_page: + return + cls = self.get_movie_class (class_path) + + if result: + cls.addMovies(result, page) + + cls.fetching_movies = False + + if result: + ## for movie in result: + ## print movie + self.fill_movies (result, class_path) + self.set_page_max(cls.getMaxPage()) + + def parse_class (self, res): + ### parse the class list + classes = self.sohuvideolist.parseClasses(res) + for cls in classes: + cls.fetching_movies = False + self.sohuvideolist.updateClasses(classes) + + ### fill category list + self.clear_pages ('categories') + for cls in self.sohuvideolist.getClasses(): + self.fill_categories (cls.id) + + def get_movie_class (self, class_path): + def find_class (classes, clsid): + for cls in classes: + if cls.id == clsid: + return cls + return None + if not class_path: + return None + classes = self.sohuvideolist.getClasses() + return find_class (classes, class_path[0]) + + def download_movies (self, callback, class_path, page, count): + cls = self.get_movie_class (class_path) + thread = ProcessThread (None, self.__fetch_movies, + callback, class_path, + page, count) + thread.start() + + def __fetch_movies(self, dummy, class_path, page, *args): + cls = self.get_movie_class (class_path) + if cls.id != CATEGORY_ID_SEARCH: + return self.sohuvideolist.fetchMovieList(cls, page) + return self.sohuvideolist.searchMovieList(cls.keyword, page) + + def fetch_movies (self, class_path): + cls = self.get_movie_class (class_path) + cls.fetching_movies = True + #print cls, cls.page + self.download_movies (self.on_movies_fetched, class_path, + self.movies_page, self.movies_count) + + def on_category_row_activated(self, treeview, path, view_column, + data = None): + model, rows = treeview.get_selection ().get_selected_rows () + iter = model.get_iter (rows[0]) + title = model.get_value (iter, 3) + classid = model.get_value (iter, 0) + + class_path = (classid,) + #print title, class_path + + self.movies_page = 0 + self.show_movies (class_path) + self.notebook.set_current_page (1) + + def setup_categories (self): + treeview_name = 'categories' + treeview = self.builder.get_object ("sohuvideo_treeview_" + treeview_name) + renderer = gtk.CellRendererText() + renderer.set_property("xalign", 0.0) + + column = gtk.TreeViewColumn(_("Name"), renderer, text = 3) + column.set_clickable(True) + treeview.append_column(column) + + column = gtk.TreeViewColumn(_("Number"), renderer, text = 4) + treeview.append_column(column) + + self.vadjust[treeview_name] = treeview.get_vadjustment () + self.vadjust[treeview_name].connect ("value-changed", self.on_value_changed) + vscroll = self.builder.get_object ("sohuvideo_scrolled_window_" + treeview_name).get_vscrollbar () + vscroll.connect ("button-press-event", self.on_button_press_event) + vscroll.connect ("button-release-event", self.on_button_release_event) + + self.liststore[treeview_name] = self.builder.get_object ("sohuvideo_liststore_" + treeview_name) + self.treeview[treeview_name] = treeview + treeview.set_model (self.liststore[treeview_name]) + + treeview.connect("row_activated", self.on_category_row_activated) + + self.categories_count = 0 + + def on_add_to_favorites (self, button): + selection = self.treeview['movies'].get_selection () + model, rows = selection.get_selected_rows () + if not rows: + return + + iter = model.get_iter (rows[0]) + class_path = eval(model.get_value (iter, 4)) + + for row in rows: + iter = model.get_iter (row) + index = rows + if type (model) is gtk.TreeModelFilter: + iter = model.convert_iter_to_child_iter (iter) + filtermodel = model.get_model () + else: + filtermodel = model + class_path = eval(filtermodel.get_value (iter, 4)) + self.add_favorites (class_path, + filtermodel.get_path (iter)[0], False) + self.save_favorites () + self.refresh_favorites () + + def on_remove_from_favorites (self, button): + selection = self.treeview['movies'].get_selection () + model, rows = selection.get_selected_rows () + if not rows: + return + rows.reverse () + for row in rows: + iter = model.get_iter (row) + index = rows + if type (model) is gtk.TreeModelFilter: + iter = model.convert_iter_to_child_iter (iter) + filtermodel = model.get_model () + else: + filtermodel = model + class_path = eval(filtermodel.get_value (iter, 4)) + self.remove_favorites (class_path, + filtermodel.get_path (iter)[0], False) + self.save_favorites () + self.refresh_favorites () + + def setup_favorites (self): + button = self.builder.get_object ('sohuvideo_add_to_favorites_button') + self.add_to_favorites_button = button + button.set_sensitive (False) + button.connect ('clicked', self.on_add_to_favorites) + button = self.builder.get_object ('sohuvideo_remove_from_favorites_button') + button.set_sensitive (False) + button.connect ('clicked', self.on_remove_from_favorites) + self.remove_from_favorites_button = button + self.favorites_hbox = self.builder.get_object ('sohuvideo_favorites_hbox') + self.load_favorites () + + def load_favorites (self): + self.favorites = sohuvideolist.SohuClass () + self.favorites.id = CATEGORY_ID_FAVORITES + self.favorites.title = _('Favorites') + self.favorites.fetching_movies = False + + filename = os.path.join (sohuvideo_cache_dir, 'favorites.pickle') + if not os.path.exists (filename): + return + try: + dump = pickle.load (file(filename, 'rb')) + except Exception, e: + print "Couldn't load favorite list", e + dump = [] + + movies = [] + for d in dump: + movie = sohuvideolist.SohuMovie() + movie.load(d) + movies.append(movie) + self.favorites.setMovies(movies) + + def save_favorites (self): + treeview_name = 'categories' + filename = os.path.join (sohuvideo_cache_dir, 'favorites.pickle') + fp = None + try: + fp = file (filename, 'wb') + except: + return + d = [ movie.dictionary() for movie in self.favorites.getPage(0)] + try: + pickle.dump(d, fp) + except: + pass + + def add_favorites (self, class_path, index, update = True): + cls = self.get_movie_class (class_path) + movies = cls.getPage(0) + movie = movies[index] + if movie in self.favorites.getPage(0): + return + movie = copy.copy(movie) + movie.model_path = () + self.favorites.addMovies([movie]) + + if not update: + return + self.refresh_favorites () + self.save_favorites () + + def remove_favorites (self, class_path, index, update = False): + cls = self.get_movie_class (class_path) + movies = cls.getPage(self.movies_page) + movie = movies[index] + #print self.favorites.movies, movie + if not movie in self.favorites.getPage(0): + print "Couldn't find ", movie, ' in favorites' + return + movies.remove(movie) + + if not update: + return + self.refresh_favorites () + self.save_favorites () + + def refresh_favorites (self): + treeview_name = 'categories' + treeview = self.builder.get_object ("sohuvideo_liststore_" + treeview_name) + if not len (treeview): + return + + self.update_category (self.favorites) + + if self.movie_class_path == (): + return + + class_path = self.movie_class_path + if class_path != (CATEGORY_ID_FAVORITES,): + return + + self.movie_class_path = () + self.show_movies (class_path, False, True) + + def setup_recent (self): + self.load_recent () + + def load_recent (self): + treeview_name = 'categories' + + self.recent = sohuvideolist.SohuClass () + self.recent.id = CATEGORY_ID_RECENT; + self.recent.title = _('Recent') + self.recent.fetching_movies = False + + filename = os.path.join (sohuvideo_cache_dir, 'recent.pickle') + if not os.path.exists (filename): + return + try: + dump = pickle.load (file(filename, 'rb')) + except Exception, e: + print "Couldn't load recent list", e + dump = [] + + movies = [] + for d in dump: + movie = sohuvideolist.SohuMovie() + movie.load(d) + movies.append(movie) + self.recent.setMovies(movies) + + def save_recent (self): + treeview_name = 'categories' + filename = os.path.join (sohuvideo_cache_dir, 'recent.pickle') + fp = None + try: + fp = file (filename, 'wb') + except: + return + d = [ movie.dictionary() for movie in self.recent.getPage(0)] + try: + pickle.dump(d, fp) + except: + pass + + def add_recent (self, class_path, index): + cls = self.get_movie_class (class_path) + movie = cls.getPage(self.movies_page)[index] + + liststore_name = 'movies' + liststore = self.liststore[liststore_name] + old_index = -1 + movies = self.recent.getPage(0) + if movie in movies: + ### it's alreay the last one + if movie == movies[0]: + return + old_index = movies.index (movie) + movies.remove (movie) + else: + while len (movies) > MAX_RECENT - 1: + if self.movie_class_path == (CATEGORY_ID_RECENT,): + del self.liststore[-1] + del movies[-1] + self.recent.resetMovies() + movie = copy.copy(movie) + self.recent.setMovies([movie] + movies) + + if self.movie_class_path == (CATEGORY_ID_RECENT,): + if old_index < 0: + self.storelist_append_movie (liststore, movie, + self.movie_class_path, 0) + else: + iter = liststore.get_iter (old_index) + liststore.move_after (iter, None) + self.refresh_recent_num () + self.save_recent () + + def refresh_recent_num (self): + liststore_name = 'categories' + liststore = self.liststore[liststore_name] + if not len (liststore): + return + + self.update_category (self.recent) + + def refresh_recent (self): + self.refresh_recent_num () + if self.movie_class_path == (): + return + + class_path = self.movie_class_path + if class_path != (CATEGORY_ID_RECENT,): + return + + self.movie_class_path = () + self.show_movies (class_path, False, True) + + def setup_search (self): + self.search = sohuvideolist.SohuClass() + self.search.id = CATEGORY_ID_SEARCH + self.search.title = _('Search result') + self.search.keyword = '' + self.search.fetching_movies = False + + def fill_all_categories (self): + treeview_name = 'categories' + treeview = self.liststore[treeview_name] + self.clear_pages (treeview_name) + for cls in self.sohuvideolist.getClasses(): + iter = treeview.append(None) + s = '%d/%d' % (len(cls.getMovies()), cls.getMaxPage()) + treeview.set(iter, + 0, cls.id, + 1, 0, + 2, 0, + 3, cls.title, + 4, s) + cls.model_path = treeview.get_path(iter) + + def fill_categories (self, clsid): + treeview_name = 'categories' + treeview = self.liststore[treeview_name] + + cls = None + for cls in self.sohuvideolist.getClasses(): + if cls.id == clsid: + break + if not cls: + return + + s = '%d/%d' % (len(cls.getMovies()), cls.getMaxPage()) + iter = treeview.append(None) + treeview.set(iter, + 0, cls.id, + 1, 0, + 2, 0, + 3, cls.title, + 4, s) + cls.model_path = treeview.get_path(iter) + #print cls.id, cls.title + + def update_category (self, cls): + treeview_name = 'categories' + liststore = self.liststore[treeview_name] + iter = liststore.get_iter (cls.model_path) + s = '%d/%d' % (len(cls.getMovies()), cls.getMaxPage()) + liststore.set (iter, 4, s) + + def update_current_category (self): + if not self.movie_class_path: + return + cls = self.get_movie_class(self.movie_class_path) + self.update_category (cls) + + def get_movie_tooltip (self, movie): + tip = movie.title + if movie.director: + tip += '\n' + _("Director: ") + movie.director + if movie.actor: + tip += '\n' + _("Actor: ") + movie.actor + if movie.area: + tip += '\n' + _("Area: ") + movie.area + if movie.size: + tip += '\n' + _("Size: ") + str(movie.size) + _('MB') + if movie.pubtime: + tip += '\n' + _("Pubtime: ") + movie.pubtime + if movie.length: + tip += '\n' + _("Length: ") + str(movie.length) + _('Min') + tip += '\n' + _("Score: ") + str(movie.score) + tip += '\n' + _("Episodes: ") + str(movie.cn) + tip += '\n' + _("Description: ") + '\n' + movie.desc + if hasattr(file, 'longdesc'): + tip += '\n' + _("Long Description: ") + movie.longdesc + + return unescape_xml (tip) + + def on_image_retrieved (self, pixbuf, movie, iter, class_path, count): + treeview_name = 'movies' + liststore = self.liststore[treeview_name] + movie.pixbuf = pixbuf + + try: + ### check whether the treeview has been cleared. + if self.movie_class_path == class_path and self.movies_count == count: + if self.configs['show_posters']: + liststore.set (iter, 0, pixbuf) + else: + print 'not showing posters' + except Exception, e: + print 'Failed to loading posters:', class_path, e + pass + + def recode_mrl (self, url): + if not self.can_recode_mrl: + return url + path, qs = urllib.splitquery (url) + if '%' in path: + path = urllib.unquote (path) + try: + path = unicode (path, 'gbk').encode('utf-8') + except Exception, e: + print 'Failed to recode url:', e + return url + if qs is None: + qs = '' + return path + qs + + def storelist_append_movie (self, treeview, movie, class_path, pos = -1): + files = movie.getFiles() + #if not movie.getFiles(): + # return + f = None + if len(files): + f = files[0] + origurl = f.url + else: + origurl = '' + tip = self.get_movie_tooltip (movie) + pixbuf = movie.pixbuf + if not self.configs['show_posters']: + pixbuf = None + url = self.recode_mrl (origurl) + if pos == -1: + it = treeview.append ([pixbuf, movie.title, url, tip, + repr(class_path), origurl]) + else: + it = treeview.insert (0) + treeview.set (it, + 0, pixbuf, + 1, movie.title, + 2, url, + 3, tip, + 4, repr(class_path), + 5, f.url) + if not movie.pixbuf and movie.smallimage and self.configs['show_posters']: + self.retrieveimage.retrieve (movie.smallimage, self.on_image_retrieved, + movie, it, class_path, + self.movies_count) + movie.model_path = treeview.get_path(it) + + def fill_files (self, movie): + treeview_name = 'files' + self.clear_pages (treeview_name) + treeview = self.liststore[treeview_name] + for f in movie.getFiles(): + no = str(f.ci + 1) + '/' + str(movie.cn) + it = treeview.append ([no, f.title, self.recode_mrl (f.url), f.url]) + + def show_files (self, movie): + self.current_movie = movie + if movie.getFiles(): + self.fill_files(movie) + else: + self.clear_pages('files') + self.fetch_files(movie) + + def download_files (self, callback, movie, show, count): + thread = ProcessThread (None, self.__fetch_files, + callback, movie, show, count) + thread.start() + + def __fetch_files(self, dummy, movie, *args): + cls = movie.parent + assert (cls is not None) + return self.sohuvideolist.fetchMovie(cls, movie) + + def fetch_files (self, movie, show = True): + self.download_files (self.on_files_fetched, movie, show, + self.movies_count) + + def on_files_fetched(self, res, movie, show, movies_count): + if res: + files = self.sohuvideolist.parseMovie(movie.parent, movie, res) + movie.setFiles(files) + if show: + if self.movies_count == movies_count and \ + movie == self.current_movie: + self.fill_files(movie) + self.movies_update_files_status() + + def fill_all_movies (self, class_path): + cls = self.get_movie_class (class_path) + + self.set_page_max(cls.getMaxPage()) + + treeview_name = 'movies' + liststore = self.liststore[treeview_name] + for movie in cls.getPage(self.movies_page): + self.storelist_append_movie (liststore, movie, class_path) + + self.update_category (cls) + + def fill_movies (self, movies, class_path): + if self.movie_class_path != class_path: + return + treeview_name = 'movies' + liststore = self.liststore[treeview_name] + for movie in movies: + self.storelist_append_movie (liststore, movie, class_path) + + self.update_current_category () + + def refetch_movies (self, class_path = None): + if class_path is None: + class_path = self.movie_class_path + + self.retrieveimage.cancel_tasks () + self.movie_class_path = () + self.show_movies (class_path, True) + + def show_movies (self, class_path, force = False, cancel = True): + #print class_path, force, cancel + ### force -- refetch movie list from server + ### cancel -- discard the current movie list + if self.movie_class_path == class_path and not force: + return + + ### cancel current fetching thread + if cancel: + self.movies_count += 1 + if self.movie_class_path != (): + cls = self.get_movie_class (self.movie_class_path) + cls.fetching_movies = False + + self.movie_class_path = class_path + + if not self.configs['show_posters']: + self.retrieveimage.cancel_tasks () + + self.clear_pages (['movies', 'files']) + + cls = self.get_movie_class (class_path) + + ### reset movie list(except the favorites/recent list) + if force: + cls.setMovies([], self.movies_page) + + if cancel or (not cls.fetching_movies and \ + not self.retrieveimage.is_busy ()): + self.add_progress_callback (class_path, + self.movies_count) + + ### fill all loaded movies + self.set_current_page(self.movies_page + 1) + self.fill_all_movies (class_path) + if not len(cls.getPage(self.movies_page)) and \ + not cls.fetching_movies: + self.fetch_movies (class_path) + + def on_movies_visible_func (self, model, iter): + terms = self.search_terms + if not terms: + return True + treeview = self.treeview['movies'] + title = model.get (iter, treeview.get_property ("title-column"))[0] or '' + tooltip = model.get (iter, treeview.get_property ("tooltip-column"))[0] or '' + if terms in title or terms in tooltip: + return True + return False + + def setup_movies (self): + treeview_name = 'movies' + self.movie_class_path = () + self.movies_count = 0 + self.movies_page = 0 + + """This is done here rather than in the UI file, + because UI files parsed in C and GObjects created in + Python apparently don't mix.""" + renderer = totem.CellRendererVideo (use_placeholder = self.configs['show_posters']) + treeview = SohuVideoList.SohuVideoList() + treeview.set_property('headers-visible', False) + treeview.set_property('fixed-height-mode', False) + treeview.set_property('title-column', 1) + treeview.set_property('tooltip-column', 3) + treeview.set_property('mrl-column', 2) + treeview.set_property ("totem", self.totem) + + window = self.builder.get_object('sohuvideo_scrolled_window_movies') + window.add(treeview) + + treeview.connect ("row-activated", self.on_movies_row_activated) + treeview.connect_after ("starting-video", self.on_movies_starting_video) + treeview.insert_column_with_attributes (0, _("Videos"), + renderer, thumbnail=0, title=1) + self.cell_video_renderer = renderer + + self.vadjust[treeview_name] = treeview.get_vadjustment () + self.vadjust[treeview_name].connect ("value-changed", self.on_value_changed) + vscroll = self.builder.get_object ("sohuvideo_scrolled_window_" + treeview_name).get_vscrollbar () + vscroll.connect ("button-press-event", self.on_button_press_event) + vscroll.connect ("button-release-event", self.on_button_release_event) + + self.liststore[treeview_name] = self.builder.get_object ("sohuvideo_liststore_" + treeview_name) + self.treeview[treeview_name] = treeview + treefilter = self.liststore[treeview_name].filter_new () + treefilter.set_visible_func (self.on_movies_visible_func) + treeview.set_model (treefilter) + + selection = treeview.get_selection () + selection.connect ("changed", self.on_movies_selection_changed) + + def on_add_to_playlist (self, treeview): + ### add selected movie to recent list + treeview_name = 'movies' + treeview = self.treeview[treeview_name] + selection = treeview.get_selection () + model = treeview.get_model () + model, rows = selection.get_selected_rows () + self.add_movie_to_recent (treeview, rows[0]) + + treeview_name = 'files' + treeview = self.treeview[treeview_name] + selection = treeview.get_selection () + model = treeview.get_model () + model, rows = selection.get_selected_rows () + + for row in rows: + iter = model.get_iter (row) + mrl = model.get_value (iter, 2) + self.totem.action_remote (totem.REMOTE_COMMAND_ENQUEUE, mrl) + + def on_copy_location (self, action): + treeview_name = 'files' + treeview = self.treeview[treeview_name] + selection = treeview.get_selection () + model = treeview.get_model () + model, rows = selection.get_selected_rows () + iter = model.get_iter (rows[0]) + mrl = model.get_value (iter, 3) + + clip = gtk.Clipboard () + clip.set_text (mrl) + clip = gtk.Clipboard (display = gtk.gdk.display_get_default(), + selection = "PRIMARY") + clip.set_text (mrl) + + def setup_files_action (self): + datadir = os.path.dirname (__file__) + filename = os.path.join (datadir, 'sohuvideo-ui.xml') + + self.files_uimanager = self.builder.get_object ('sohuvideo-list-ui-manager') + self.files_uimanager.add_ui_from_file(filename) + + action_group = gtk.ActionGroup(name = 'sohuvideo-list-action-group') + self.files_uimanager.insert_action_group(action_group, 0) + + action = gtk.Action(name = 'add-to-playlist', + label = _('_Add to Playlist'), + tooltip = _('Add the video to the playlist'), + stock_id = 'gtk-add') + action.connect ('activate', self.on_add_to_playlist) + action_group.add_action(action) + + action = gtk.Action(name = 'copy-location', + label = _('_Copy Location'), + tooltip = _('Copy the location to the clipboard'), + stock_id = 'gtk-copy') + action.connect ('activate', self.on_copy_location) + action_group.add_action(action) + + def files_show_popup_menu (self, event = None): + treeview_name = 'files' + treeview = self.treeview [treeview_name] + selection = treeview.get_selection () + + if event: + button = event.button + time = event.time + path = treeview.get_path_at_pos (int(event.x), int(event.y)) + if not path or not selection.path_is_selected (path[0]): + selection.unselect_all () + else: + time = gtk.get_current_event_time () + button = None + + count = selection.count_selected_rows () + if not count: + return False + + action_group = self.files_uimanager.get_action_groups () [0] + action = action_group.get_action ("copy-location") + action.set_sensitive (count == 1) + + menu = self.files_uimanager.get_widget ("/sohuvideo-list-popup") + menu.select_first (False) + menu.popup (None, None, None, button, time) + + return True + + def on_files_popup_menu (self): + self.files_show_popup_menu () + + def on_files_button_pressed (self, widget, event, user_data = None): + if event.type == gtk.gdk.BUTTON_PRESS and event.button == 3: + return self.files_show_popup_menu (event) + + return False + + def setup_files (self): + treeview_name = 'files' + treeview = self.builder.get_object ("sohuvideo_treeview_" + treeview_name) + renderer = gtk.CellRendererText() + renderer.set_property("xalign", 0.0) + + column = gtk.TreeViewColumn(_("No"), renderer, text = 0) + column.set_clickable(True) + treeview.append_column(column) + + column = gtk.TreeViewColumn(_("Title"), renderer, text = 1) + treeview.append_column(column) + + column = gtk.TreeViewColumn(_("Mrl"), renderer, text = 2) + treeview.append_column(column) + + self.vadjust[treeview_name] = treeview.get_vadjustment () + self.vadjust[treeview_name].connect ("value-changed", self.on_value_changed) + vscroll = self.builder.get_object ("sohuvideo_scrolled_window_" + treeview_name).get_vscrollbar () + vscroll.connect ("button-press-event", self.on_button_press_event) + vscroll.connect ("button-release-event", self.on_button_release_event) + + self.liststore[treeview_name] = self.builder.get_object ("sohuvideo_liststore_" + treeview_name) + self.treeview[treeview_name] = treeview + treeview.set_model (self.liststore[treeview_name]) + + treeview.connect("row_activated", self.on_files_treeview_row_activated) + + self.setup_files_action () + treeview.connect ("popup-menu", self.on_files_popup_menu) + treeview.connect ("button-press-event", self.on_files_button_pressed) + + selection = treeview.get_selection () + selection.set_mode (gtk.SELECTION_MULTIPLE) + + self.files_count = 0 + + def on_files_treeview_row_activated(self, treeview, path, view_column, data=None): + model, rows = treeview.get_selection ().get_selected_rows () + try: + iter = model.get_iter (rows[0]) + except IndexError: + print 'activated invalid index:', rows[0] + return + + ### play the movie + title = model.get_value (iter, 1) + mrl = model.get_value (iter, 2) + #print title, mrl + if hasattr(self.totem, "add_to_playlist_and_play"): + self.totem.add_to_playlist_and_play (mrl, title, False) + else: + self.totem.action_remote (totem.REMOTE_COMMAND_REPLACE, mrl) + + ### add selected movie to recent list + movies_treeview = self.treeview['movies'] + movies_model, movies_rows = movies_treeview.get_selection ().get_selected_rows () + self.add_movie_to_recent (movies_treeview, movies_rows[0]) + + def on_movies_selection_changed (self, selection): + model, rows = selection.get_selected_rows () + if not rows: + self.clear_pages ('files') + return + + all_files_fetched = True + for row in rows: + iter = model.get_iter(row) + if type (model) is gtk.TreeModelFilter: + iter = model.convert_iter_to_child_iter (iter) + model = model.get_model () + + class_path = eval(model.get_value (iter, 4)) + cls = self.get_movie_class (class_path) + + index = model.get_path(iter)[0] + movie = cls.getPage(self.movies_page)[index] + if not movie.getFiles(): + all_files_fetched = False + if row != rows[0]: + self.fetch_files(movie, False) + if row == rows[0]: + self.show_files(movie) + if self.movie_class_path == (CATEGORY_ID_FAVORITES,): + self.add_to_favorites_button.set_sensitive(False) + else: + self.add_to_favorites_button.set_sensitive(all_files_fetched) + + def movies_update_files_status(self): + selection = self.treeview['movies'].get_selection () + model, rows = selection.get_selected_rows () + if not rows: + self.clear_pages ('files') + return + + all_files_fetched = True + for row in rows: + iter = model.get_iter(row) + if type (model) is gtk.TreeModelFilter: + iter = model.convert_iter_to_child_iter (iter) + model = model.get_model () + + class_path = eval(model.get_value (iter, 4)) + cls = self.get_movie_class (class_path) + + index = model.get_path(iter)[0] + movie = cls.getPage(self.movies_page)[index] + if not movie.getFiles(): + all_files_fetched = False + else: + url = model.get(iter, 2)[0] + if not url: + url = movie.getFiles()[0].url + model.set(iter, 2, self.recode_mrl(url)) + model.set(iter, 5, url) + + if self.movie_class_path == (CATEGORY_FAVORITES,): + self.add_to_favorites_button.set_sensitive(False) + else: + self.add_to_favorites_button.set_sensitive(all_files_fetched) + + def on_first_page_clicked(self, *args): + if self.movie_class_path == (): + return + + cls = self.get_movie_class(self.movie_class_path) + + if self.movies_page == 0: + return + self.movies_page = 0 + + self.show_movies(self.movie_class_path, force = True) + + def on_prev_page_clicked(self, *args): + if self.movie_class_path == (): + return + if self.movies_page < 1: + return + self.movies_page -= 1 + + self.show_movies(self.movie_class_path, force = True) + + def on_next_page_clicked(self, *args): + if self.movie_class_path == (): + return + cls = self.get_movie_class(self.movie_class_path) + + if self.movies_page >= cls.getMaxPage() - 1: + return + self.movies_page += 1 + + self.show_movies(self.movie_class_path, force = True) + + def on_last_page_clicked(self, *args): + if self.movie_class_path == (): + return + + cls = self.get_movie_class(self.movie_class_path) + + if self.movies_page == cls.getMaxPage() - 1: + return + self.movies_page = cls.getMaxPage() - 1 + + self.show_movies(self.movie_class_path, force = True) + + def on_goto_page_clicked(self, *args): + if self.movie_class_path == (): + return + + cls = self.get_movie_class(self.movie_class_path) + + page = self.which_page_button.get_value_as_int() - 1 + if page >= cls.getMaxPage(): + return + if page == self.movies_page: + return + self.movies_page = page + self.show_movies(self.movie_class_path, force = True) + + def set_page_max(self, max_page): + adjustment = self.which_page_button.get_adjustment() + adjustment.upper = max_page + self.max_page_label.set_text (_('Total: %d') % max_page) + + def set_current_page(self, current): + adjustment = self.which_page_button.get_adjustment() + adjustment.value = current + + def setup_page_ui (self): + self.page_hbox = self.builder.get_object('sohuvideo_page_hbox') + self.first_page_button = self.builder.get_object('sohuvideo_first_page_button') + self.prev_page_button = self.builder.get_object('sohuvideo_prev_page_button') + self.next_page_button = self.builder.get_object('sohuvideo_next_page_button') + self.last_page_button = self.builder.get_object('sohuvideo_last_page_button') + self.goto_page_button = self.builder.get_object('sohuvideo_goto_page_button') + self.which_page_button = self.builder.get_object('sohuvideo_which_page_button') + self.max_page_label = self.builder.get_object('sohuvideo_max_page_label') + + self.page_hbox.set_sensitive(False) + self.set_page_max (1) + + adjustment = self.which_page_button.get_adjustment() + adjustment.lower = 1 + adjustment.upper = 1 + adjustment.value = 1 + + self.first_page_button.connect('clicked', self.on_first_page_clicked) + self.prev_page_button.connect('clicked', self.on_prev_page_clicked) + self.next_page_button.connect('clicked', self.on_next_page_clicked) + self.last_page_button.connect('clicked', self.on_last_page_clicked) + self.goto_page_button.connect('clicked', self.on_goto_page_clicked) + + def on_notebook_page_changed (self, notebook, notebook_page, page_num): + self.current_treeview_name = self.notebook_pages[page_num] + self.add_to_favorites_button.set_sensitive (False) + self.remove_from_favorites_button.set_sensitive (False) + if self.current_treeview_name == 'files': + self.refresh_button.set_sensitive (False) + self.page_hbox.set_sensitive (False) + self.page_hbox.show() + self.favorites_hbox.hide() + self.update_progress_bar () + elif self.current_treeview_name == 'movies': + if self.movie_class_path == (): + self.refresh_button.set_sensitive (False) + elif self.movie_class_path == (CATEGORY_ID_FAVORITES,): + self.remove_from_favorites_button.set_sensitive (True) + else: + self.refresh_button.set_sensitive (True) + self.page_hbox.set_sensitive (True) + self.page_hbox.show () + self.favorites_hbox.show () + self.movies_update_files_status() + self.update_progress_bar () + else: + self.page_hbox.set_sensitive (False) + self.page_hbox.hide () + self.favorites_hbox.hide () + self.refresh_button.set_sensitive (True) + self.update_categories_progress (self.categories_count) + + def update_categories_progress (self, count): + if count != self.categories_count: + return False + if self.current_treeview_name != 'categories': + return True + classes = self.sohuvideolist.getClasses() + if self.fetching_classes: + self.progress_bar.pulse () + self.progress_bar.set_text (_("Refreshing category list...")) + self.progress_bar.show () + return True + else: + self.progress_bar.set_fraction (0.0) + self.progress_bar.set_text ("") + self.progress_bar.hide () + return False + return True + + def update_progress_bar (self): + if self.current_treeview_name == 'categories': + return True + if self.movie_class_path == (): + self.progress_bar.set_fraction (0.0) + self.progress_bar.set_text ("") + self.progress_bar.hide () + return True + + cls = self.get_movie_class (self.movie_class_path) + if cls.fetching_movies: + self.progress_bar.pulse() + progressstr = '(%d/%d)' % (self.movies_page + 1, cls.max_page) + if hasattr(cls, 'parsing_movies') and cls.parsing_movies: + self.progress_bar.set_text (_("Parsing movie list%s...") % progressstr) + else: + self.progress_bar.set_text (_("Loading movie list%s...") % progressstr) + self.progress_bar.show () + return True + elif self.retrieveimage.is_busy (): + remaining = self.retrieveimage.get_remaning_tasks () + num_movies = len(cls.getMovies()) + if remaining > num_movies: + remaining = num_movies + finished = num_movies - remaining + progress = float(finished) / num_movies + self.progress_bar.set_fraction (progress) + progressstr = str(finished) + '/' + str(num_movies) + self.progress_bar.set_text (_('loading posters(%s)...') % progressstr) + self.progress_bar.show () + else: + self.progress_bar.set_fraction (0.0) + self.progress_bar.set_text ("") + self.progress_bar.hide () + + return False + + return True + + def on_update_progress_bar (self, class_path, count): + if self.movie_class_path == class_path and self.movies_count == count: + return self.update_progress_bar () + return False + + def add_progress_callback (self, class_path, count): + gobject.timeout_add (350, self.on_update_progress_bar, + class_path, count) + + def on_movies_row_activated (self, treeview, path, column): + pass + + def add_movie_to_recent (self, treeview, path): + model = treeview.get_model () + iter = model.get_iter (path) + + if type (model) is gtk.TreeModelFilter: + iter = model.convert_iter_to_child_iter (iter) + model = model.get_model () + + class_path = eval(model.get_value (iter, 4)) + cls = self.get_movie_class(class_path) + + movie = cls.getPage(self.movies_page)[path[0]] + files = movie.getFiles() + if not files: + return False + + self.add_recent (class_path, model.get_path (iter)[0]) + return True + + def on_movies_starting_video (self, widget, treeview, path): + return self.add_movie_to_recent (treeview, path) + + def on_button_press_event (self, widget, event): + self.button_down = True + + def on_button_release_event (self, widget, event): + self.button_down = False + self.on_value_changed (self.vadjust[self.current_treeview_name]) + + def on_value_changed (self, adjustment): + """Load more results when we get near the bottom of the treeview""" + pass + + def clear_ui (self, what = None): + #window = self.vbox.window + #window.set_cursor (None) + self.progress_bar.set_fraction (0.0) + self.progress_bar.set_text ("") + self.clear_pages (what) + + def clear_pages (self, what = None): + if what is None: + what = self.notebook_pages + if not type(what) is list: + what = [what] + + for treeview_name in what: + treeview = self.treeview[treeview_name] + treeview.get_selection ().unselect_all () + liststore = self.liststore[treeview_name] + liststore.clear () + + def refresh_categories (self): + self.fetch_classes (True) + + def refresh_movies (self): + if self.movie_class_path != (): + self.refetch_movies () + + def set_show_posters (self, show = True): + self.cell_video_renderer.set_property ('use_placeholder', show) + if self.movie_class_path == (): + return + class_path = self.movie_class_path + self.movie_class_path = () + self.show_movies (class_path, False, False) + + def sync_config (self): + for key in self.input_configs.keys (): + keyname = gconf_key + '/' + key + value = self.input_configs [key] + if type (value) is bool: + self.gconf.set_bool (keyname, value) + elif type (value) is int: + self.gconf.set_int (keyname, value) + elif type (value) is float: + self.gconf.set_float (keyname, value) + else: + self.gconf.set_string (keyname, value) + if self.input_configs['show_posters'] != self.configs['show_posters']: + self.configs['show_posters'] = self.input_configs['show_posters'] + self.set_show_posters (self.configs['show_posters']) + + if self.input_configs['compatible'] != self.configs['compatible']: + self.configs['comptiable'] = self.input_configs['compatible'] + self.stop_idle_task () + self.start_idle_task () + + def on_refresh_button_clicked (self, button): + if self.current_treeview_name == 'categories': + self.refresh_categories () + elif self.current_treeview_name == 'movies': + self.refresh_movies () + + def on_search_mode_entry_toggled (self, button): + self.search_web = self.search_mode_button.get_active() + if self.search_web: + button.set_label (_('search:')) + else: + button.set_label (_("filter:")) + + def on_search_entry_activated (self, entry): + if self.search_web: + self.search.keyword = self.search_entry.get_text () + self.search_terms = '' + self.movies_page = 0 + self.show_movies ((CATEGORY_ID_SEARCH,), True, True) + self.notebook.set_current_page (1) + else: + self.search_terms = self.search_entry.get_text () + self.treeview['movies'].get_model ().refilter () + + def on_search_button_clicked (self, button): + self.on_search_entry_activated (self.search_entry) + + def on_config_dialog_delete (self, dialog, event): + dialog.hide () + return True + + def on_config_dialog_close (self, dialog): + dialog.hide () + + def on_config_dialog_response (self, dialog, response_id): + dialog.hide () + if response_id == gtk.RESPONSE_DELETE_EVENT: + return + elif response_id == 1: + return + + self.sync_config () + + def on_config_checkbutton_toggle (self, button, which): + self.input_configs[which] = button.get_active () + + def load_config (self): + ### current configs + self.configs = {} + self.configs['show_posters'] = self.gconf.get_bool ('%s/show_posters' % gconf_key) + self.configs['compatible'] = self.gconf.get_bool ('%s/compatible' % gconf_key) + + ### user input configs + self.input_configs = self.configs.copy () + + def setup_config_dialog (self): + dialog = self.config_builder.get_object ("sohuvideo_config_dialog") + dialog.connect ("response", self.on_config_dialog_response) + dialog.connect ("close", self.on_config_dialog_close) + dialog.connect ("delete-event", self.on_config_dialog_delete) + + button = self.config_builder.get_object ("sohuvideo_config_show_posters") + button.set_active (self.configs['show_posters']) + button.connect ("toggled", self.on_config_checkbutton_toggle, 'show_posters') + + button = self.config_builder.get_object ("sohuvideo_config_compatible") + button.set_active (self.configs['compatible']) + button.connect ("toggled", self.on_config_checkbutton_toggle, 'compatible') + diff --git a/totem/plugin/sohuvideo.totem-plugin.in b/totem/plugin/sohuvideo.totem-plugin.in new file mode 100644 index 0000000..d978f5f --- /dev/null +++ b/totem/plugin/sohuvideo.totem-plugin.in @@ -0,0 +1,9 @@ +[Totem Plugin] +Loader=python +Module=sohuvideo +IAge=1 +_Name=Sohu video browser +_Description=A plugin to let you browse sohu videos. +Authors=Luo Jinghua <sunmoon1997@gmail.com> +Copyright=Copyright © 2010 Luo Jinghua +Website=http://code.google.com/p/totem-sohu/ diff --git a/totem/plugin/sohuvideo.ui b/totem/plugin/sohuvideo.ui new file mode 100644 index 0000000..e6f9c12 --- /dev/null +++ b/totem/plugin/sohuvideo.ui @@ -0,0 +1,378 @@ +<?xml version="1.0"?> +<interface> + <!-- interface-requires gtk+ 2.12 --> + <!-- interface-naming-policy project-wide --> + <object class="GtkTreeStore" id="sohuvideo_liststore_categories"> + <columns> + <!-- column-name Class --> + <column type="gint"/> + <!-- column-name Type --> + <column type="gint"/> + <!-- column-name Subclass --> + <column type="gint"/> + <!-- column-name Title --> + <column type="gchararray"/> + <!-- column-name Content --> + <column type="gchararray"/> + </columns> + </object> + <object class="GtkListStore" id="sohuvideo_liststore_files"> + <columns> + <!-- column-name No. --> + <column type="gchararray"/> + <!-- column-name Title --> + <column type="gchararray"/> + <!-- column-name MRL --> + <column type="gchararray"/> + <!-- column-name OrigMRL --> + <column type="gchararray"/> + </columns> + </object> + <object class="GtkListStore" id="sohuvideo_liststore_movies"> + <columns> + <!-- column-name Poster --> + <column type="GdkPixbuf"/> + <!-- column-name Title --> + <column type="gchararray"/> + <!-- column-name MRL --> + <column type="gchararray"/> + <!-- column-name gchararray1 --> + <column type="gchararray"/> + <!-- column-name ClassPath --> + <column type="gchararray"/> + <!-- column-name OrigMRL --> + <column type="gchararray"/> + </columns> + </object> + <object class="GtkVBox" id="sohuvideo_vbox"> + <property name="visible">True</property> + <property name="orientation">vertical</property> + <property name="spacing">4</property> + <child> + <object class="GtkHBox" id="hbox1"> + <property name="visible">True</property> + <child> + <object class="GtkToggleButton" id="sohuvideo_search_checkbutton"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">False</property> + <property name="tooltip_text" translatable="yes">Filtering the movie list using the keyword or search the keyword on site tv.sohu.com</property> + <property name="draw_indicator">True</property> + </object> + <packing> + <property name="expand">False</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkEntry" id="sohuvideo_search_entry"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="tooltip_text" translatable="yes">The keyword for filtering the movie list or searching movies on site tv.sohu.com</property> + <property name="invisible_char">●</property> + </object> + <packing> + <property name="position">1</property> + </packing> + </child> + <child> + <object class="GtkButton" id="sohuvideo_search_button"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">True</property> + <property name="tooltip_text" translatable="yes">Filter the movie list using the keyword or search movies by the keyword</property> + <child> + <object class="GtkImage" id="sohuvideo_find_image"> + <property name="visible">True</property> + <property name="stock">gtk-find</property> + </object> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="position">2</property> + </packing> + </child> + <child> + <object class="GtkButton" id="sohuvideo_refresh_button"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">False</property> + <child> + <object class="GtkImage" id="sohuvideo_refresh_image"> + <property name="visible">True</property> + <property name="tooltip_text" translatable="yes">Refresh the movie list</property> + <property name="stock">gtk-refresh</property> + </object> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="position">3</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkNotebook" id="sohuvideo_notebook"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <child> + <object class="GtkScrolledWindow" id="sohuvideo_scrolled_window_categories"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="hscrollbar_policy">automatic</property> + <property name="vscrollbar_policy">automatic</property> + <child> + <object class="GtkTreeView" id="sohuvideo_treeview_categories"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="model">sohuvideo_liststore_movies</property> + <property name="headers_visible">False</property> + <property name="rules_hint">True</property> + </object> + </child> + </object> + </child> + <child type="tab"> + <object class="GtkLabel" id="label1"> + <property name="visible">True</property> + <property name="label" translatable="yes">Categories</property> + </object> + <packing> + <property name="tab_fill">False</property> + </packing> + </child> + <child> + <object class="GtkScrolledWindow" id="sohuvideo_scrolled_window_movies"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="hscrollbar_policy">automatic</property> + <property name="vscrollbar_policy">automatic</property> + </object> + <packing> + <property name="position">1</property> + </packing> + </child> + <child type="tab"> + <object class="GtkLabel" id="label2"> + <property name="visible">True</property> + <property name="label" translatable="yes">Movies</property> + </object> + <packing> + <property name="position">1</property> + <property name="tab_fill">False</property> + </packing> + </child> + <child> + <object class="GtkScrolledWindow" id="sohuvideo_scrolled_window_files"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="hscrollbar_policy">automatic</property> + <property name="vscrollbar_policy">automatic</property> + <child> + <object class="GtkTreeView" id="sohuvideo_treeview_files"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="model">sohuvideo_liststore_categories</property> + <property name="headers_visible">False</property> + <property name="rules_hint">True</property> + </object> + </child> + </object> + <packing> + <property name="position">2</property> + </packing> + </child> + <child type="tab"> + <object class="GtkLabel" id="label3"> + <property name="visible">True</property> + <property name="label" translatable="yes">Files</property> + </object> + <packing> + <property name="position">2</property> + <property name="tab_fill">False</property> + </packing> + </child> + </object> + <packing> + <property name="position">1</property> + </packing> + </child> + <child> + <object class="GtkProgressBar" id="sohuvideo_progress_bar"> + <property name="visible">True</property> + </object> + <packing> + <property name="expand">False</property> + <property name="position">2</property> + </packing> + </child> + <child> + <object class="GtkHBox" id="sohuvideo_page_hbox"> + <child> + <object class="GtkButton" id="sohuvideo_first_page_button"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">False</property> + <property name="tooltip_text" translatable="yes">Go to the first page</property> + <child> + <object class="GtkImage" id="image2"> + <property name="visible">True</property> + <property name="stock">gtk-goto-first</property> + </object> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkButton" id="sohuvideo_prev_page_button"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">False</property> + <property name="tooltip_text" translatable="yes">Go to the previous page</property> + <child> + <object class="GtkImage" id="image3"> + <property name="visible">True</property> + <property name="stock">gtk-go-back</property> + </object> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="position">1</property> + </packing> + </child> + <child> + <object class="GtkButton" id="sohuvideo_next_page_button"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">True</property> + <property name="tooltip_text" translatable="yes">Go to the next page</property> + <child> + <object class="GtkImage" id="image4"> + <property name="visible">True</property> + <property name="stock">gtk-go-forward</property> + </object> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="position">2</property> + </packing> + </child> + <child> + <object class="GtkButton" id="sohuvideo_last_page_button"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">True</property> + <property name="tooltip_text" translatable="yes">Go to the last page</property> + <child> + <object class="GtkImage" id="image5"> + <property name="visible">True</property> + <property name="tooltip_text" translatable="yes">Goto the last page</property> + <property name="stock">gtk-goto-last</property> + </object> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="position">3</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="sohuvideo_max_page_label"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="tooltip_text" translatable="yes">The number of pages in this category</property> + </object> + <packing> + <property name="expand">False</property> + <property name="position">4</property> + </packing> + </child> + <child> + <object class="GtkSpinButton" id="sohuvideo_which_page_button"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="tooltip_text" translatable="yes">The current page</property> + <property name="invisible_char">●</property> + <property name="adjustment">sohuvideo_page_adjustment</property> + <property name="climb_rate">1</property> + </object> + <packing> + <property name="expand">False</property> + <property name="position">5</property> + </packing> + </child> + <child> + <object class="GtkButton" id="sohuvideo_goto_page_button"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">True</property> + <property name="tooltip_text" translatable="yes">Go to specified page</property> + <child> + <object class="GtkImage" id="image6"> + <property name="visible">True</property> + <property name="stock">gtk-jump-to</property> + </object> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="position">6</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="position">3</property> + </packing> + </child> + <child> + <object class="GtkHBox" id="sohuvideo_favorites_hbox"> + <child> + <object class="GtkButton" id="sohuvideo_add_to_favorites_button"> + <property name="label" translatable="yes">Add to favorites</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">True</property> + </object> + <packing> + <property name="expand">False</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkButton" id="sohuvideo_remove_from_favorites_button"> + <property name="label" translatable="yes">Remove from favorites</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">True</property> + </object> + <packing> + <property name="expand">False</property> + <property name="position">1</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="position">4</property> + </packing> + </child> + </object> + <object class="GtkUIManager" id="sohuvideo-list-ui-manager"/> + <object class="GtkAdjustment" id="sohuvideo_page_adjustment"> + <property name="upper">1</property> + <property name="step_increment">1</property> + <property name="page_increment">10</property> + </object> +</interface> diff --git a/totem/plugin/sohuvideo_translation.py.in b/totem/plugin/sohuvideo_translation.py.in new file mode 100644 index 0000000..efc4c62 --- /dev/null +++ b/totem/plugin/sohuvideo_translation.py.in @@ -0,0 +1,9 @@ +import gettext + +GETTEXT_PACKAGE = '@GETTEXT_PACKAGE@' +SOHU_LOCALEDIR = '@LOCALEDIR@' +gettext.bindtextdomain (GETTEXT_PACKAGE, SOHU_LOCALEDIR); +gettext.bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8"); +def _(message): + return gettext.ldgettext(GETTEXT_PACKAGE, message) + diff --git a/totem/plugin/sohuvideolist.py b/totem/plugin/sohuvideolist.py new file mode 100644 index 0000000..81b26a1 --- /dev/null +++ b/totem/plugin/sohuvideolist.py @@ -0,0 +1,522 @@ +#!/bin/env python +# -*- coding: utf-8 -*- +# -*- python -*- +# Author: Luo Jinghua + +import urllib +import httplib +import htmlentitydefs +import time +import re +import os +import StringIO +import copy +import sys +import time + +from xml.dom import minidom +import BeautifulSoup + +KANPPS = 'http://kan.pps.tv' +MOVIE_LIST_PATH = '/movie_index.html' +MOVIE_LIST_URL = KANPPS + MOVIE_LIST_PATH + +class SohuClass: + def __init__ (self): + self.id = 0 + self.title = '' + self.url = '' + self.pages = {} + self.max_page = 1 + self.parent = None + + def __str__(self): + return 'SohuClass<%d %s %s>' % (self.id, self.title, self.url) + + def __repr__(self): + return repr(self.dictionary()) + + def dictionary(self): + d = {} + d['id'] = self.id + d['url'] = self.url + d['title'] = self.title + return d + + def parseid (self, url): + return int(re.sub('.*/(\d+)/.*', r'\1', url)) + + def parse (self, node): + self.title = unicode(node.contents[0]).encode('utf-8') + self.url = KANPPS + node['href'].encode('utf-8') + self.id = self.parseid(self.url) + return self + + def load(self, d): + self.__dict__.update(d) + + def resetMovies(self): + self.pages = {} + self.max_page = 1 + + def addMovies(self, movies, page_id = 0): + movies = copy.copy(movies) + for movie in movies: + movie.parent = self + + if not self.pages.has_key(page_id): + self.pages[page_id] = [] + self.pages[page_id] += movies + + def setMovies(self, movies, page_id = 0, clone = True): + if clone: + movies = copy.copy(movies) + for movie in movies: + movie.parent = self + + self.pages[page_id] = movies + + def getMovies(self): + pages = self.getPages() + movies = [] + for page in pages: + movies += page + return movies + + def getPage(self, page_id): + assert (page_id >= 0 and page_id < self.max_page) + if self.pages.has_key(page_id): + return self.pages[page_id] + return [] + + def getPages(self): + keys = self.pages.keys() + keys.sort() + pages = [] + for key in keys: + pages.append(self.pages[key]) + return pages + + def getMaxPage(self): + return self.max_page + +class SohuFile: + elements = [ 'id', 'ci', 'size', 'url' ] + + def __str__ (self): + return 'SohuFile<%d %s %s>' % (self.id, self.title, self.url) + + def __repr__(self): + return repr(self.dictionary()) + + def __eq__ (self, other): + for attr in SohuFile.elements: + if getattr (self, attr) != getattr (other, attr): + #print attr, getattr (self, attr), getattr (other, attr) + return False + return True + + def dictionary(self): + d = {} + d['id'] = self.id + d['url'] = self.url + d['title'] = self.title + return d + + def __init__ (self): + self.id = 0 + self.ci = 0 + self.size = 0 + self.title = '' + self.url = '' + self.parent = None + + def parse (self, s, ci): + idendpos = s.find(']') + valuestartpos = s.find('=') + 2 + valueendpos = len(s) - 1 + values = s[valuestartpos:valueendpos].split('|||') + self.id = int(s[6:idendpos]) + self.title = values[0] + self.url = values[2] + self.ci = ci + return self + + def load(self, d): + self.__dict__.update(d) + +class SohuMovie: + elements = [ 'id', 'title', 'director', 'actor', + 'area', 'size', 'pubtime', 'length', + 'lang', 'score', 'desc', 'image', + 'cn', 'playerurl'] + def __str__(self): + return 'SohuMovie<%d %s %s %d %s>' % (self.id, self.title, + self.score, self.cn, + self.actor) + + def __eq__ (self, other): + for i in SohuMovie.elements: + if getattr (self, i) != getattr (other, i): + return False + return self.files == other.files + + def __repr__(self): + return repr(self.dictionary()) + + def dictionary(self): + d = {} + for key in SohuMovie.elements: + d[key] = getattr(self, key) + return d + + def __init__ (self): + self.id = 0 + self.title = '' + self.director = '' + self.actor = '' + self.area = '' + self.size = 0 + self.pubtime = '' + self.length = '' + self.lang = '' + self.score = '' + self.desc = '' + self.image = '' + self.cn = 1 + self.playerurl = '' + self.files = [] + self.pixbuf = None + self.parent = None + + def parseid (self, url): + s = url[url.rfind('/') + 1:] + if s.find('_') < 0: + ids = s.split('.') + else: + ids = s.split('_') + return int(ids[0]) + + def parse_actors(self, node): + actors = node.findAll('a') + result = [] + for actor in actors: + if actor.contents: + result.append(extractString(actor.contents[-1])) + result = {}.fromkeys(result).keys() + return result + + def parse_area (self, node): + area = unicode(extractString(node.contents[-1]), 'utf-8') + if area.find (u':') >= 0: + area = area[area.find(u':') + 1:] + return area.encode('utf-8') + + def parse (self, node): + self.files = [] + self.pixbuf = None + self.image = extractString(node.find('img')['src']) + tr = node.find('dl', { "class" : "tr" }) + dt = node.find('dt') + self.score = extractNumberString(dt.find('span').contents[0]) + self.title = extractString(dt.find('a').contents[0]) + self.cn = extractNumber(dt.contents[-1], 1) + self.playerurl = KANPPS + extractString(dt.find('a')['href']) + li = node.findAll('li') + self.area = self.parse_area(li[0].find('span')) + self.pubtime = extractString(li[0].contents[-1]) + self.actor = ', '.join(self.parse_actors(li[1])) + self.desc = extractString(li[2].contents[-1]) + self.id = self.parseid(self.playerurl) + self.smallimage = self.image + return self + + def load(self, d): + self.__dict__.update(d) + self.smallimage = self.image + + def getFiles(self): + return self.files + + def setFiles(self, files): + self.files = copy.copy(files) + for f in self.files: + f.parent = self + +def download(url, max_retry = 3, interval = 5): + if not url: + return '' + for i in range (max_retry): + try: + res = urllib.urlopen (url).read () + except Exception, e: + print "Coudn't open url: ", e + res = None + time.sleep (interval) + if res: + break + return res + +def gbk2utf8(s): + return unicode(s, 'gb18030', 'ignore').encode('utf-8') + +def extractString(s): + return unicode(s).encode('utf-8') + +def extractStrings(ss): + result = [] + for s in ss: + result.append(extractString(s)) + return result + +def extractNumberString(s, default = ''): + s = extractString(s) + n = re.search(r'[-+]?(\d+(\.\d*)?|\.\d+)([eE][-+]?\d+)?', s) + if n: + return n.group() + return default + +def extractNumber(s, default = 0): + s = extractString(s) + n = re.search(r'[-+]?(\d+(\.\d*)?|\.\d+)([eE][-+]?\d+)?', s) + if n: + if '.' in n.group() or 'e' in n.group() or 'E' in n.group(): + return float(n.group()) + return int(n.group()) + return default + +def extractNavigableStrings(node): + result = [] + for content in node.contents: + if type(content) is BeautifulSoup.NavigableString: + result.append(extractString(content)) + return result + +def parseMovieClassList(res): + ppslist = gbk2utf8(res) + client_list = '<dt>客户端列表</dt>' + startpos = ppslist.find(client_list) + if startpos < 0: + return [] + endpos = ppslist[startpos:].find('</dd>') + if endpos < 0: + return [] + soup = BeautifulSoup.BeautifulSoup(ppslist[startpos:startpos + endpos + 5]) + result = [] + for l in soup.findAll('li'): + cls = SohuClass() + result.append(cls.parse(l.next)) + return result + +def getMovieClassList(): + movie_index_fname = 'movie_index.html' + if os.path.exists(movie_index_fname): + ppslist = file(movie_index_fname).read() + else: + ppslist = download(MOVIE_LIST_URL) + file(movie_index_fname, 'wb').write(ppslist) + + return parseMovieClassList(ppslist) + +def parseMovieList(movie_list, s): + s = gbk2utf8(s) + soup = BeautifulSoup.BeautifulSoup(s) + result = [] + for l in soup.findAll('div', { "class" : "pltr" }): + movie = SohuMovie() + result.append(movie.parse(l)) + return result + +def parseMovieListPage(movie_list, s): + tag = '<div class="pageNav">' + tagpos = s.find(tag) + if tagpos < 0: + return (1, 1) + startpos = s[tagpos:].find('<span>') + endpos = s[tagpos:].find('</span>') + pagestr = s[tagpos + startpos:tagpos + endpos] + m = re.search('(\d+)/(\d+)', pagestr) + result = m.groups() + return (int(result[0]), int(result[1])) + +def getMovieList(movie_list, retrieve_all = False): + s = download(movie_list.url) + page = parseMovieListPage(movie_list, s) + result = parseMovieList(movie_list, s) + if retrieve_all: + url = movie_list.url + pos = url.rfind('/') + baseurl = url[:pos + 1] + pos = url.rfind('.') + suffix = url[pos:] + for i in range(page[1] - 1): + print 'retrieving %d of %d' % (i + 2, page[1]) + url = '%s%d%s' % (baseurl, i + 2, suffix) + s = download(url) + print url + result += parseMovieList(movie_list, s) + return result + +def parseMovieFileList(movie, s): + s = gbk2utf8(s) + result = [] + ci = 0 + for f in re.findall('plist\[\d+\][^;]*', s): + ppsfile = SohuFile() + result.append(ppsfile.parse(f, ci)) + ci += 1 + return result + +def getMovieFileList(movie): + s = download(movie.playerurl) + return parseMovieFileList(movie, s) + +class SohuVideoList: + def __init__(self): + self.classes = [] + + def reset(self): + self.classes = [] + + def getClasses(self): + return self.classes + + def fetchClasses(self): + s = download(MOVIE_LIST_URL) + return s + + def parseClasses(self, s): + if s: + result = parseMovieClassList(s) + else: + result = [] + return result + + def updateClasses(self, classes): + self.classes += copy.copy(classes) + + def fetchMovieList(self, movie_class, page_id = 0): + url = movie_class.url + if page_id != 0: + assert (page_id < movie_class.max_page) + pos = url.rfind('/') + baseurl = url[:pos + 1] + pos = url.rfind('.') + suffix = url[pos:] + url = '%s%d%s' % (baseurl, page_id + 2, suffix) + s = download(url) + return s + + def searchMovieList(self, keyword, page_id = 0): + baseurl = 'http://kan.pps.tv/search/' + keyword = unicode(keyword, 'utf-8').encode('gb18030') + url = baseurl + urllib.quote(keyword) + '/' + if page_id > 0: + url += "%d.html" % (page_id + 1) + s = download(url, interval = 10) + #print keyword, url, gbk2utf8(s) + return s + + def parseMovieList(self, movie_class, s, page_id = 0): + page = parseMovieListPage(movie_class, s) + result = parseMovieList(movie_class, s) + for movie in result: + movie.page_id = page_id + if page_id == 0: + movie_class.max_page = page[1] + return result + + def updateMovieList(self, movie_class, movie_list, page_id = 0): + if page_id == 0: + movie_class.movies = [] + movie_class.movies += movie_list + movie_class.pages[page_id] = copy.copy(movie_list) + + def fetchMovie(self, cls, movie): + s = download(movie.playerurl) + return s + + def parseMovie(self, cls, movie, s): + result = parseMovieFileList(movie, s) + return result + + def updateMovie(self, cls, movie, file_list): + movie.files = copy.copy(file_list) + +if __name__ == '__main__': + ## movie_class_list = getMovieClassList() + + ## for movie_class in movie_class_list: + ## print movie_class + + ## movie_list = getMovieList(movie_class_list[1]) + + ## for movie in movie_list: + ## print movie + + ## file_list = getMovieFileList(movie_list[0]) + + ## for f in file_list: + ## print f + + def test_get_file_list(): + s = download('http://kan.pps.tv/play/281874.html') + file_list = parseMovieFileList(SohuMovie(), s) + for f in file_list: + print f + + sys.exit(0) + + def test_search (): + ppslist = SohuVideoList() + s = ppslist.searchMovieList('周星驰') + movie_list = ppslist.parseMovieList(SohuClass(), s) + for m in movie_list: + print m + sys.exit(0) + + def test_ppslist(): + ppslist = SohuVideoList() + s = ppslist.fetchClasses() + ppslist.updateClasses(ppslist.parseClasses(s)) + classes = ppslist.getClasses() + for cls in classes: + print cls + + cls = classes[0] + s = ppslist.fetchMovieList(cls) + ppslist.updateMovieList(cls, ppslist.parseMovieList(cls, s)) + page_id = 1 + while page_id < cls.max_page: + s = ppslist.fetchMovieList(cls, page_id) + ppslist.updateMovieList(cls, ppslist.parseMovieList(cls, s, page_id), + page_id) + page_id += 1 + + movies = cls.getMovies() + for movie in movies: + print movie + + page = cls.getPage(0) + for movie in page: + s = ppslist.fetchMovie(cls, movie) + ppslist.updateMovie(cls, movie, ppslist.parseMovie(cls, movie, s)) + + for movie in page: + print movie + for f in movie.files: + print f + print + + movie = page[0] + ppsfile = movie.files[0] + d = ppsfile.dictionary() + + print ppsfile, d + ppsfile1 = SohuFile() + ppsfile1.load(d) + print ppsfile1, ppsfile == ppsfile1 + + #test_ppslist() + #test_search() + #test_get_file_list() |