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