1#!/usr/bin/python 2# -*- coding: utf-8 -*- 3 4# This file is part of periscope. 5# 6# periscope is free software; you can redistribute it and/or modify 7# it under the terms of the GNU Lesser General Public License as published by 8# the Free Software Foundation; either version 2 of the License, or 9# (at your option) any later version. 10# 11# periscope is distributed in the hope that it will be useful, 12# but WITHOUT ANY WARRANTY; without even the implied warranty of 13# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14# GNU Lesser General Public License for more details. 15# 16# You should have received a copy of the GNU Lesser General Public License 17# along with periscope; if not, write to the Free Software 18# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 19 20import getopt 21import sys 22import os 23import threading 24import logging 25from Queue import Queue 26 27import traceback 28import ConfigParser 29 30log = logging.getLogger(__name__) 31 32try: 33 import xdg.BaseDirectory as bd 34 is_local = True 35except ImportError: 36 is_local = False 37 38import plugins 39import version 40import locale 41 42SUPPORTED_FORMATS = 'video/x-msvideo', 'video/quicktime', 'video/x-matroska', 'video/mp4' 43VERSION = version.VERSION 44 45class Periscope: 46 ''' Main Periscope class''' 47 48 def __init__(self, cache_folder=None): 49 self.config = ConfigParser.SafeConfigParser({"lang": "", "plugins" : "" }) 50 self.config_file = os.path.join(cache_folder, "config") 51 self.cache_path = cache_folder 52 if not os.path.exists(self.config_file): 53 folder = os.path.dirname(self.config_file) 54 if not os.path.exists(folder): 55 log.info("Creating folder %s" %folder) 56 os.mkdir(folder) 57 log.info("Creating config file") 58 configfile = open(self.config_file, "w") 59 self.config.write(configfile) 60 configfile.close() 61 else: 62 #Load it 63 self.config.read(self.config_file) 64 65 self.pluginNames = self.get_preferedPlugins() 66 self._preferedLanguages = None 67 68 def get_preferedLanguages(self): 69 ''' Get the prefered language from the config file ''' 70 configLang = self.config.get("DEFAULT", "lang") 71 log.info("lang read from config: " + configLang) 72 if configLang == "": 73 try : 74 l = [locale.getdefaultlocale()[0][:2]] 75 except : 76 return None 77 else: 78 return map(lambda x : x.strip(), configLang.split(",")) 79 80 def set_preferedLanguages(self, langs): 81 ''' Update the config file to set the prefered language ''' 82 self.config.set("DEFAULT", "lang", ",".join(langs)) 83 configfile = open(self.config_file, "w") 84 self.config.write(configfile) 85 configfile.close() 86 87 def get_preferedPlugins(self): 88 ''' Get the prefered plugins from the config file ''' 89 configPlugins = self.config.get("DEFAULT", "plugins") 90 if not configPlugins or configPlugins.strip() == "": 91 return self.listExistingPlugins() 92 else : 93 log.info("plugins read from config : " + configPlugins) 94 return map(lambda x : x.strip(), configPlugins.split(",")) 95 96 def set_preferedPlugins(self, newPlugins): 97 ''' Update the config file to set the prefered plugins) ''' 98 self.config.set("DEFAULT", "plugins", ",".join(newPlugins)) 99 configfile = open(self.config_file, "w") 100 self.config.write(configfile) 101 configfile.close() 102 103 104 # Getter/setter for the property preferedLanguages 105 preferedLanguages = property(get_preferedLanguages, set_preferedLanguages) 106 preferedPlugins = property(get_preferedPlugins, set_preferedPlugins) 107 108 def deactivatePlugin(self, pluginName): 109 ''' Remove a plugin from the list ''' 110 self.pluginNames -= pluginName 111 self.set_preferedPlugins(self.pluginNames) 112 113 def activatePlugin(self, pluginName): 114 ''' Activate a plugin ''' 115 if pluginName not in self.listExistingPlugins(): 116 raise ImportError("No plugin with the name %s exists" %pluginName) 117 self.pluginNames += pluginName 118 self.set_preferedPlugins(self.pluginNames) 119 120 def listActivePlugins(self): 121 ''' Return all active plugins ''' 122 return self.pluginNames 123 124 def listExistingPlugins(self): 125 ''' List all possible plugins from the plugin folder ''' 126 return map(lambda x : x.__name__, plugins.SubtitleDatabase.SubtitleDB.__subclasses__()) 127 128 def listSubtitles(self, filename, langs=None): 129 '''Searches subtitles within the active plugins and returns all found matching subtitles ordered by language then by plugin.''' 130 #if not os.path.isfile(filename): 131 #raise InvalidFileException(filename, "does not exist") 132 133 log.info("Searching subtitles for %s with langs %s" %(filename, langs)) 134 subtitles = [] 135 q = Queue() 136 for name in self.pluginNames: 137 try : 138 plugin = getattr(plugins, name)(self.config, self.cache_path) 139 log.info("Searching on %s " %plugin.__class__.__name__) 140 thread = threading.Thread(target=plugin.searchInThread, args=(q, filename, langs)) 141 thread.start() 142 except ImportError : 143 log.error("Plugin %s is not a valid plugin name. Skipping it.") 144 145 # Get data from the queue and wait till we have a result 146 for name in self.pluginNames: 147 subs = q.get(True) 148 if subs and len(subs) > 0: 149 if not langs: 150 subtitles += subs 151 else: 152 for sub in subs: 153 if sub["lang"] in langs: 154 subtitles += [sub] # Add an array with just that sub 155 156 if len(subtitles) == 0: 157 return [] 158 return subtitles 159 160 161 def selectBestSubtitle(self, subtitles, langs=None): 162 '''Searches subtitles from plugins and returns the best subtitles from all candidates''' 163 if not subtitles: 164 return None 165 166 if not langs: # No preferred language => return the first 167 return subtitles[0] 168 169 subtitles = self.__orderSubtitles__(subtitles) 170 for l in langs: 171 if subtitles.has_key(l) and len(subtitles[l]): 172 return subtitles[l][0] 173 174 return None #Could not find subtitles 175 176 def downloadSubtitle(self, filename, langs=None): 177 ''' Takes a filename and a language and creates ONE subtitle through plugins''' 178 subtitles = self.listSubtitles(filename, langs) 179 if subtitles: 180 log.debug("All subtitles: ") 181 log.debug(subtitles) 182 return self.attemptDownloadSubtitle(subtitles, langs) 183 else: 184 return None 185 186 187 def attemptDownloadSubtitle(self, subtitles, langs): 188 subtitle = self.selectBestSubtitle(subtitles, langs) 189 if subtitle: 190 log.info("Trying to download subtitle: %s" %subtitle['link']) 191 #Download the subtitle 192 try: 193 subpath = subtitle["plugin"].createFile(subtitle) 194 if subpath: 195 subtitle["subtitlepath"] = subpath 196 return subtitle 197 else: 198 # throw exception to remove it 199 raise Exception("Not downloaded") 200 except Exception as inst: 201 # Could not download that subtitle, remove it 202 log.warn("Subtitle %s could not be downloaded, trying the next on the list" %subtitle['link']) 203 log.error(inst) 204 subtitles.remove(subtitle) 205 return self.attemptDownloadSubtitle(subtitles, langs) 206 else : 207 log.error("No subtitles could be chosen.") 208 return None 209 210 def guessFileData(self, filename): 211 subdb = plugins.SubtitleDatabase.SubtitleDB(None) 212 return subdb.guessFileData(filename) 213 214 def __orderSubtitles__(self, subs): 215 '''reorders the subtitles according to the languages then the website''' 216 try: 217 from collections import defaultdict 218 subtitles = defaultdict(list) #Order matters (order of plugin and result from plugins) 219 for s in subs: 220 subtitles[s["lang"]].append(s) 221 return subtitles 222 except ImportError, e: #Don't use Python 2.5 223 subtitles = {} 224 for s in subs: 225 # return subtitles[s["lang"]], if it does not exist, set it to [] and return it, then append the subtitle 226 subtitles.setdefault(s["lang"], []).append(s) 227 return subtitles 228