Viewing file: opensubtitles.py (21.45 KB) -rw-r--r-- Select action/file-type: (+) | (+) | (+) | Code (+) | Session (+) | (+) | SDB (+) | (+) | (+) | (+) | (+) | (+) |
import totem import gobject, gtk, gio, gconf gobject.threads_init() import xmlrpclib import threading import xdg.BaseDirectory from os import sep, path, mkdir import gettext import pango
from hash import hashFile
D_ = gettext.dgettext
USER_AGENT = 'Totem' OK200 = '200 OK' TOTEM_REMOTE_COMMAND_REPLACE = 14
SUBTITLES_EXT = [ "asc", "txt", "sub", "srt", "smi", "ssa", "ass", ]
# Map of the language codes used by opensubtitles.org's API to their human-readable name LANGUAGES_STR = [(D_('iso_639_3', 'Albanian'), 'sq'), (D_('iso_639_3', 'Arabic'), 'ar'), (D_('iso_639_3', 'Armenian'), 'hy'), (D_('iso_639_3', 'Neo-Aramaic, Assyrian'), 'ay'), (D_('iso_639_3', 'Bosnian'), 'bs'), (_('Brasilian Portuguese'), 'pb'), (D_('iso_639_3', 'Bulgarian'), 'bg'), (D_('iso_639_3', 'Catalan'), 'ca'), (D_('iso_639_3', 'Chinese'), 'zh'), (D_('iso_639_3', 'Croatian'), 'hr'), (D_('iso_639_3', 'Czech'), 'cs'), (D_('iso_639_3', 'Danish'), 'da'), (D_('iso_639_3', 'Dutch'), 'nl'), (D_('iso_639_3', 'English'), 'en'), (D_('iso_639_3', 'Esperanto'), 'eo'), (D_('iso_639_3', 'Estonian'), 'et'), (D_('iso_639_3', 'Finnish'), 'fi'), (D_('iso_639_3', 'French'), 'fr'), (D_('iso_639_3', 'Galician'), 'gl'), (D_('iso_639_3', 'Georgian'), 'ka'), (D_('iso_639_3', 'German'), 'de'), (D_('iso_639_3', 'Greek, Modern (1453-)'), 'el'), (D_('iso_639_3', 'Hebrew'), 'he'), (D_('iso_639_3', 'Hindi'), 'hi'), (D_('iso_639_3', 'Hungarian'), 'hu'), (D_('iso_639_3', 'Icelandic'), 'is'), (D_('iso_639_3', 'Indonesian'), 'id'), (D_('iso_639_3', 'Italian'), 'it'), (D_('iso_639_3', 'Japanese'), 'ja'), (D_('iso_639_3', 'Kazakh'), 'kk'), (D_('iso_639_3', 'Korean'), 'ko'), (D_('iso_639_3', 'Latvian'), 'lv'), (D_('iso_639_3', 'Lithuanian'), 'lt'), (D_('iso_639_3', 'Luxembourgish'), 'lb'), (D_('iso_639_3', 'Macedonian'), 'mk'), (D_('iso_639_3', 'Malay (macrolanguage)'), 'ms'), (D_('iso_639_3', 'Norwegian'), 'no'), (D_('iso_639_3', 'Occitan (post 1500)'), 'oc'), (D_('iso_639_3', 'Persian'), 'fa'), (D_('iso_639_3', 'Polish'), 'pl'), (D_('iso_639_3', 'Portuguese'), 'pt'), (D_('iso_639_3', 'Romanian'), 'ro'), (D_('iso_639_3', 'Russian'), 'ru'), (D_('iso_639_3', 'Serbian'), 'sr'), (D_('iso_639_3', 'Slovak'), 'sk'), (D_('iso_639_3', 'Slovenian'), 'sl'), (D_('iso_639_3', 'Spanish'), 'es'), (D_('iso_639_3', 'Swedish'), 'sv'), (D_('iso_639_3', 'Thai'), 'th'), (D_('iso_639_3', 'Turkish'), 'tr'), (D_('iso_639_3', 'Ukrainian'), 'uk'), (D_('iso_639_3', 'Vietnamese'), 'vi'),]
# Map of ISO 639-1 language codes to the codes used by opensubtitles.org's API LANGUAGES = {'sq':'alb', 'ar':'ara', 'hy':'arm', 'ay':'ass', 'bs':'bos', 'pb':'pob', 'bg':'bul', 'ca':'cat', 'zh':'chi', 'hr':'hrv', 'cs':'cze', 'da':'dan', 'nl':'dut', 'en':'eng', 'eo':'epo', 'et':'est', 'fi':'fin', 'fr':'fre', 'gl':'glg', 'ka':'geo', 'de':'ger', 'el':'ell', 'he':'heb', 'hi':'hin', 'hu':'hun', 'is':'ice', 'id':'ind', 'it':'ita', 'ja':'jpn', 'kk':'kaz', 'ko':'kor', 'lv':'lav', 'lt':'lit', 'lb':'ltz', 'mk':'mac', 'ms':'may', 'no':'nor', 'oc':'oci', 'fa':'per', 'pl':'pol', 'pt':'por', 'ro':'rum', 'ru':'rus', 'sr':'scc', 'sk':'slo', 'sl':'slv', 'es':'spa', 'sv':'swe', 'th':'tha', 'tr':'tur', 'uk':'ukr', 'vi':'vie',}
class SearchThread(threading.Thread): """ This is the thread started when the dialog is searching for subtitles """ def __init__(self, model): self.model = model self._done = False self._lock = threading.Lock() threading.Thread.__init__(self)
def run(self): self.model.lock.acquire(True) self.model.results = self.model.os_search_subtitles() self.model.lock.release() self._done = True @property def done(self): """ Thread-safe property to know whether the query is done or not """ self._lock.acquire(True) res = self._done self._lock.release() return res
class DownloadThread(threading.Thread): """ This is the thread started when the dialog is downloading the subtitles. """ def __init__(self, model, subtitle_id): self.model = model self.subtitle_id = subtitle_id self._done = False self._lock = threading.Lock() threading.Thread.__init__(self)
def run(self): self.model.lock.acquire(True) self.model.subtitles = self.model.os_download_subtitles(self.subtitle_id) self.model.lock.release() self._done = True @property def done(self): """ Thread-safe property to know whether the query is done or not """ self._lock.acquire(True) res = self._done self._lock.release() return res
# OpenSubtitles.org API abstraction
class OpenSubtitlesModel(object): """ This contains the logic of the opensubtitles service. """ def __init__(self, server): self.server = server self.token = None
try: import locale self.lang = LANGUAGES[locale.getlocale()[0].split('_')[0]] except: self.lang = 'eng' self.hash = None self.size = 0
self.lock = threading.Lock() self.results = [] self.subtitles = ''
self.message = ''
def os_login(self, username='', password=''): """ Logs into the opensubtitles web service and gets a valid token for the comming comunications. If we are already logged it only checks the if the token is still valid.
@rtype : bool """ result = None self.message = ''
if self.token: # We have already logged-in before, check the connection try: result = self.server.NoOperation(self.token) except: pass if result and result['status'] != OK200: return True try: result = self.server.LogIn(username, password, self.lang, USER_AGENT) except: pass if result and result.get('status') == OK200: self.token = result.get('token') if self.token: return True
self.message = _('Could not contact the OpenSubtitles website')
return False
def os_search_subtitles(self): """
""" self.message = '' if self.os_login(): searchdata = {'sublanguageid': self.lang, 'moviehash' : self.hash, 'moviebytesize': str(self.size)} try: result = self.server.SearchSubtitles(self.token, [searchdata]) except xmlrpclib.ProtocolError: self.message = _('Could not contact the OpenSubtitles website')
if result.get('data'): return result['data'] else: self.message = _('No results found')
return None
def os_download_subtitles(self, subtitleId): """ """ self.message = '' if self.os_login(): try: result = self.server.DownloadSubtitles(self.token, [subtitleId]) except xmlrpclib.ProtocolError: self.message = _('Could not contact the OpenSubtitles website')
if result and result.get('status') == OK200: try: subtitle64 = result['data'][0]['data'] except: self.message = _('Could not contact the OpenSubtitles website') return None
import StringIO, gzip, base64 subtitleDecoded = base64.decodestring(subtitle64) subtitleGzipped = StringIO.StringIO(subtitleDecoded) subtitleGzippedFile = gzip.GzipFile(fileobj=subtitleGzipped)
return subtitleGzippedFile.read()
return None
class OpenSubtitles(totem.Plugin): def __init__(self): totem.Plugin.__init__(self) self.dialog = None self.gconf_client = gconf.client_get_default() self.GCONF_BASE_DIR = "/apps/totem/plugins/opensubtitles/" self.GCONF_LANGUAGE = "language"
# totem.Plugin methods
def activate(self, totem_object): """ Called when the plugin is activated. Here the sidebar page is initialized(set up the treeview, connect the callbacks, ...) and added to totem.
@param totem_object: @type totem_object: {totem.TotemObject} """ self.totem = totem_object self.filename = None
self.manager = self.totem.get_ui_manager() self.os_append_menu()
self.totem.connect('file-opened', self.on_totem__file_opened) self.totem.connect('file-closed', self.on_totem__file_closed)
# Obtain the ServerProxy and init the model server = xmlrpclib.Server('http://api.opensubtitles.org/xml-rpc') self.model = OpenSubtitlesModel(server)
def deactivate(self, totem): if self.dialog: self.dialog.destroy() self.dialog = None self.os_delete_menu()
# UI related code
def os_build_dialog(self, action, totem_object): builder = self.load_interface("opensubtitles.ui", True, self.totem.get_main_window(), self)
# Obtain all the widgets we need to initialize combobox = builder.get_object('language_combobox') languages = builder.get_object('language_model') self.progress = builder.get_object('progress_bar') self.treeview = builder.get_object('subtitle_treeview') self.liststore = builder.get_object('subtitle_model') self.dialog = builder.get_object('subtitles_dialog') self.find_button = builder.get_object('find_button') self.apply_button = builder.get_object('apply_button') self.close_button = builder.get_object('close_button')
# Set up and populate the languages combobox renderer = gtk.CellRendererText() sorted_languages = gtk.TreeModelSort(languages) sorted_languages.set_sort_column_id(0, gtk.SORT_ASCENDING) combobox.set_model(sorted_languages) combobox.pack_start(renderer, True) combobox.add_attribute(renderer, 'text', 0)
self.model.lang = self.gconf_get_str (self.GCONF_BASE_DIR + self.GCONF_LANGUAGE, self.model.lang) for lang in LANGUAGES_STR: it = languages.append(lang) if LANGUAGES[lang[1]] == self.model.lang: parentit = sorted_languages.convert_child_iter_to_iter (None, it) combobox.set_active_iter(parentit)
# Set up the results treeview renderer = gtk.CellRendererText() self.treeview.set_model(self.liststore) self.treeview.set_headers_visible(False) renderer.set_property('ellipsize', pango.ELLIPSIZE_END) column = gtk.TreeViewColumn(_("Subtitles"), renderer, text=0) column.set_resizable(True) column.set_expand(True) self.treeview.append_column(column) # translators comment: # This is the file-type of the subtitle file detected column = gtk.TreeViewColumn(_("Format"), renderer, text=1) column.set_resizable(False) self.treeview.append_column(column) # translators comment: # This is a rating of the quality of the subtitle column = gtk.TreeViewColumn(_("Rating"), renderer, text=2) column.set_resizable(False) self.treeview.append_column(column)
self.apply_button.set_sensitive(False)
self.apply_button.connect('clicked', self.on_apply_clicked) self.find_button.connect('clicked', self.on_find_clicked) self.close_button.connect('clicked', self.on_close_clicked)
# Set up signals
combobox_changed_id = combobox.connect('changed', self.on_combobox__changed) self.dialog.connect ('delete-event', self.dialog.hide_on_delete) self.dialog.set_transient_for (self.totem.get_main_window()) self.dialog.set_position (gtk.WIN_POS_CENTER_ON_PARENT)
# Connect the callbacks self.dialog.connect ('key-press-event', self.on_window__key_press_event) self.treeview.get_selection().connect('changed', self.on_treeview__row_change) self.treeview.connect('row-activated', self.on_treeview__row_activate)
def os_show_dialog(self, action, totem_object): if not self.dialog: self.os_build_dialog(action, totem_object)
filename = self.totem.get_current_mrl() if not self.model.results or filename != self.filename: self.filename = filename
self.dialog.show_all()
self.progress.set_fraction(0.0)
def os_append_menu(self): """ """ self.os_action_group = gtk.ActionGroup('OpenSubtitles')
self.action = gtk.Action('opensubtitles', _('_Download Movie Subtitles...'), _("Download movie subtitles from OpenSubtitles"), '')
self.os_action_group.add_action(self.action)
self.manager.insert_action_group(self.os_action_group, 0)
self.menu_id = self.manager.new_merge_id() self.manager.add_ui(self.menu_id, '/tmw-menubar/view/subtitles/subtitle-download-placeholder', 'opensubtitles', 'opensubtitles', gtk.UI_MANAGER_MENUITEM, False ) self.action.set_visible(True)
self.manager.ensure_update()
self.action.connect('activate', self.os_show_dialog, self.totem)
self.action.set_sensitive(self.totem.is_playing() and self.os_check_allowed_scheme() and not self.os_check_is_audio())
def os_check_allowed_scheme(self): scheme = gio.File(self.totem.get_current_mrl()).get_uri_scheme() if scheme == 'dvd' or scheme == 'http' or scheme == 'dvb' or scheme == 'vcd': return False return True
def os_check_is_audio(self): # FIXME need to use something else here # I think we must use video widget metadata but I don't found a way # to get this info from python filename = self.totem.get_current_mrl() if gio.content_type_guess(filename).split('/')[0] == 'audio': return True return False
def os_delete_menu(self): self.manager.remove_action_group(self.os_action_group) self.manager.remove_ui(self.menu_id)
def os_get_results(self): """ """ self.liststore.clear() self.treeview.set_headers_visible(False) self.model.results = [] self.apply_button.set_sensitive(False) self.find_button.set_sensitive(False)
self.dialog.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.WATCH))
thread = SearchThread(self.model) thread.start() gobject.idle_add(self.os_populate_treeview)
self.progress.set_text(_('Searching subtitles...')) gobject.timeout_add(350, self.os_progress_bar_increment, thread)
def os_populate_treeview(self): """ """ if self.model.lock.acquire(False) == False: return True
if self.model.results: self.apply_button.set_sensitive(True) for subData in self.model.results: if not SUBTITLES_EXT.count(subData['SubFormat']): continue self.liststore.append([subData['SubFileName'], subData['SubFormat'], subData['SubRating'], subData['IDSubtitleFile'],]) self.treeview.set_headers_visible(True) else: self.apply_button.set_sensitive(False)
self.model.lock.release()
self.dialog.window.set_cursor(None)
return False
def os_save_selected_subtitle(self, filename=None): """ """ self.dialog.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.WATCH))
model, rows = self.treeview.get_selection().get_selected_rows() if rows: iter = model.get_iter(rows[0]) subtitle_id = model.get_value(iter, 3) subtitle_format = model.get_value(iter, 1)
gfile = None
if not filename: directory = gio.File(xdg.BaseDirectory.xdg_cache_home + sep + 'totem' + sep + 'subtitles' + sep) if not directory.query_exists(): if not path.exists (xdg.BaseDirectory.xdg_cache_home + sep + 'totem' + sep): mkdir (xdg.BaseDirectory.xdg_cache_home + sep + 'totem' + sep) if not path.exists (xdg.BaseDirectory.xdg_cache_home + sep + 'totem' + sep + 'subtitles' + sep): mkdir (xdg.BaseDirectory.xdg_cache_home + sep + 'totem' + sep + 'subtitles' + sep) # FIXME: We can't use this function until we depend on GLib (PyGObject) 2.18 # directory.make_directory_with_parents()
file = gio.File(self.filename) movie_name = file.get_basename().rpartition('.')[0] filename = directory.get_uri() + sep + movie_name + '.' + subtitle_format
self.model.subtitles = ''
thread = DownloadThread(self.model, subtitle_id) thread.start() gobject.idle_add(self.os_save_subtitles, filename)
self.progress.set_text(_('Downloading the subtitles...')) gobject.timeout_add(350, self.os_progress_bar_increment, thread) else: #warn user! pass
def os_save_subtitles(self, filename): if self.model.lock.acquire(False) == False: return True
if self.model.subtitles: # Delete all previous cached subtitle for this file for ext in SUBTITLES_EXT: fp = gio.File(filename[:-3] + ext) if fp.query_exists(): fp.delete()
fp = gio.File(filename) suburi = fp.get_uri ()
subFile = fp.replace('', False) subFile.write(self.model.subtitles) subFile.close()
self.model.lock.release()
self.dialog.window.set_cursor(None)
if suburi: self.totem.set_current_subtitle(suburi)
return False
def os_progress_bar_increment(self, thread):
if not thread.done: self.progress.pulse() return True
if self.model.message: self.progress.set_text(self.model.message) else: self.progress.set_text('') self.progress.set_fraction(0.0) self.find_button.set_sensitive(True) self.apply_button.set_sensitive(False) self.treeview.set_sensitive(True) return False
def os_download_and_apply(self): self.apply_button.set_sensitive(False) self.find_button.set_sensitive(False) self.action.set_sensitive(False) self.treeview.set_sensitive(False) self.os_save_selected_subtitle()
# Callbacks
def on_window__key_press_event(self, widget, event): if event.keyval == gtk.keysyms.Escape: self.dialog.destroy() self.dialog = None return True return False
def on_treeview__row_change(self, selection): if selection.count_selected_rows() > 0: self.apply_button.set_sensitive(True) else: self.apply_button.set_sensitive(False)
def on_treeview__row_activate(self, path, column, data): self.os_download_and_apply()
def on_totem__file_opened(self, totem, filename): """ """ # Check if allows subtitles if self.os_check_allowed_scheme() and not self.os_check_is_audio(): self.action.set_sensitive(True) if self.dialog: self.find_button.set_sensitive(True) self.filename = self.totem.get_current_mrl() self.liststore.clear() self.treeview.set_headers_visible(False) self.apply_button.set_sensitive(False) self.results = [] else: self.action.set_sensitive(False) if self.dialog and self.dialog.is_active(): self.liststore.clear() self.treeview.set_headers_visible(False) self.apply_button.set_sensitive(False) self.find_button.set_sensitive(False)
def on_totem__file_closed(self, totem): self.action.set_sensitive(False) if self.dialog: self.apply_button.set_sensitive(False) self.find_button.set_sensitive(False)
def on_combobox__changed(self, combobox): iter = combobox.get_active_iter() self.model.lang = LANGUAGES[combobox.get_model().get_value(iter, 1)] self.gconf_set_str(self.GCONF_BASE_DIR + self.GCONF_LANGUAGE, self.model.lang)
def on_close_clicked(self, data): self.dialog.destroy() self.dialog = None
def on_apply_clicked(self, data): self.os_download_and_apply()
def on_find_clicked(self, data): self.apply_button.set_sensitive(False) self.find_button.set_sensitive(False) self.filename = self.totem.get_current_mrl() self.model.hash , self.model.size = hashFile(self.filename)
self.os_get_results()
def gconf_get_str(self, key, default = ""): val = self.gconf_client.get(key) if val is not None and val.type == gconf.VALUE_STRING: return val.get_string() else: return default
def gconf_set_str(self, key, val): self.gconf_client.set_string(key, val)
|