Compare commits
71 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 3cf265f6c9 | |||
| 1b8dd8cc83 | |||
| f3292c2916 | |||
| 048d6adfa3 | |||
| f004271e8b | |||
| 63ef2f1273 | |||
| f7f2b80028 | |||
| ec21d564b2 | |||
| 6d2b772f21 | |||
| d506dd2095 | |||
| c66028793d | |||
| 0e3467d3aa | |||
| d9dc6832e3 | |||
| a57d49abf1 | |||
| 614388e28f | |||
| 7e1a09fb64 | |||
| 6efbbd9cad | |||
| e0b5894e3f | |||
| 5ed153425b | |||
| c961c9be93 | |||
| 0bafc9d31f | |||
| a48cd6c824 | |||
| cb899b81e4 | |||
| 918151013e | |||
| 4cb9ebb5e3 | |||
| e14a5f1776 | |||
| 2437dc602c | |||
| 8962d84ed3 | |||
| 4172d2a8b1 | |||
| 78a2166efe | |||
| 20a2519d92 | |||
| a7da526885 | |||
| d2010c3510 | |||
| 14c0c6fe6b | |||
| 5d8be66167 | |||
| 9ac838a257 | |||
| 0cd1016cae | |||
| 49b8486f44 | |||
| 0a363ba5d5 | |||
| 5fbfaef31d | |||
| 9324fc4c07 | |||
| 171147dfcd | |||
| dee0b33cec | |||
| f9c7654b7c | |||
| b1e0371bb5 | |||
| f94957c68d | |||
| 03dd15f718 | |||
| 938b30604b | |||
| acff303897 | |||
| 978f4fb376 | |||
| 50e0a6704b | |||
| b4e9c0e655 | |||
| 97ab555e2d | |||
| 9bc12ec619 | |||
| e4c12dfe5c | |||
| ca0d8e4ab1 | |||
| 570dc7ef9a | |||
| 3e7cac972d | |||
| 9c7ac431b5 | |||
| 8f50b317cc | |||
| c5e2e8c4b0 | |||
| 7aec1f7653 | |||
| 65a677e6d4 | |||
| 8e9781c081 | |||
| d76d81eda3 | |||
| 7b439bbf6f | |||
| aea078d65f | |||
| 5834c3edcf | |||
| 1d40c026f4 | |||
| 00fb74c5b1 | |||
| 066dc77bd9 |
@@ -2,3 +2,7 @@ build
|
||||
dist
|
||||
subliminal.egg-info
|
||||
*.pyc
|
||||
.settings
|
||||
.project
|
||||
.pydevproject
|
||||
*~
|
||||
|
||||
+45
-65
@@ -21,80 +21,60 @@
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
from optparse import OptionParser
|
||||
import argparse
|
||||
import subliminal
|
||||
import logging
|
||||
import mimetypes
|
||||
import os
|
||||
import os.path
|
||||
import sys
|
||||
|
||||
|
||||
def main():
|
||||
'''Download subtitles'''
|
||||
# parse command line options
|
||||
parser = OptionParser("usage: %prog [options] file1 file2", version=subliminal.__version__)
|
||||
parser.add_option("-l", "--language", action="append", dest="languages", help="wanted language (ISO 639-1 two chars) for the subtitles (e.g. fr, en). Multiple uses allowed such that `%prog -l fr -l en file1`")
|
||||
parser.add_option("-m", "--multi", action="store_true", dest="multi", help="download one subtitle per specified language (instead of one of them) and name them accordingly (e.g. .fr.srt, .en.srt)")
|
||||
parser.add_option("-p", "--plugin", action="append", dest="plugins", help="plugins to activate")
|
||||
parser.add_option("-f", "--force", action="store_true", dest="force", help="force download of a subtitle even there is already one present")
|
||||
parser.add_option("-C", "--no-config-file", action="store_false", dest="config", help="do not use configuration file (requires -l to be specified)")
|
||||
parser.add_option("-c", "--config-file", action="store", dest="config", help="configuration file to use")
|
||||
parser.add_option("-w", "--workers", action="store", dest="workers", help="specify the number of threads to use")
|
||||
parser.add_option("--cache-dir", action="store", dest="cache_dir", help="cache directory to use")
|
||||
parser.add_option("--no-cache-dir", action="store_false", dest="cache_dir", help="do not use cache directory (some plugins may not work)")
|
||||
parser.add_option("--list-all-plugins", action="store_true", dest="list_all_plugins", help="list all plugins available")
|
||||
parser.add_option("--list-api-plugins", action="store_true", dest="list_api_plugins", help="list api-based plugins")
|
||||
parser.add_option("--list-active-plugins", action="store_true", dest="list_active_plugins", help="list currently active plugins")
|
||||
parser.add_option("-v", "--verbose", action="count", dest="verbose", help="increase verbosity (maximum 2 times)")
|
||||
parser.set_defaults(verbose=0, cache_dir=True, config=True, workers=4)
|
||||
(options, args) = parser.parse_args()
|
||||
if not args:
|
||||
print parser.print_help()
|
||||
exit(1)
|
||||
if options.verbose == 2:
|
||||
logging.basicConfig(level=logging.DEBUG, format='%(asctime)s %(name)-24s %(levelname)-8s %(message)s', datefmt='%Y-%m-%d %H:%M:%S')
|
||||
elif options.verbose == 1:
|
||||
logging.basicConfig(level=logging.WARN, format='%(levelname)s: %(name)s %(message)s')
|
||||
else:
|
||||
logging.basicConfig(level=logging.ERROR)
|
||||
if not options.config and not options.languages:
|
||||
parser.error("Option -C (--no-config-file) is used without -l (--language)")
|
||||
subliminal_client = subliminal.Subliminal(config=options.config, cache_dir=options.cache_dir, workers=options.workers, multi=options.multi, force=options.force, max_depth=3, autostart=False)
|
||||
if options.plugins:
|
||||
subliminal_client.plugins = options.plugins
|
||||
if options.list_all_plugins:
|
||||
plugins = subliminal_client.listExistingPlugins()
|
||||
print ', '.join(subliminal_client.listExistingPlugins())
|
||||
exit(0)
|
||||
if options.list_api_plugins:
|
||||
plugins = subliminal_client.list_api_plugins()
|
||||
print ', '.join(subliminal_client.listExistingPlugins())
|
||||
exit(0)
|
||||
if options.list_active_plugins:
|
||||
plugins = subliminal_client.plugins
|
||||
print ', '.join(subliminal_client.listExistingPlugins())
|
||||
exit(0)
|
||||
if options.languages:
|
||||
subliminal_client.languages = options.languages
|
||||
else:
|
||||
logging.info(u"No language given, looking into configuration file")
|
||||
languages = subliminal_client.languages
|
||||
if not languages:
|
||||
logging.error(u"No language found in configuration file")
|
||||
sys.stderr.write("No language found in configuration file")
|
||||
exit(1)
|
||||
parser.exit
|
||||
subliminal_client.startWorkers()
|
||||
subtitles = subliminal_client.downloadSubtitles(args)
|
||||
subliminal_client.stopWorkers()
|
||||
if len(subtitles) == 0:
|
||||
sys.stderr.write("No subtitles found")
|
||||
exit(1)
|
||||
print "*" * 50
|
||||
print "Downloaded %s subtitles" % len(subtitles)
|
||||
for s in subtitles:
|
||||
print s['lang'] + " - " + s['subtitlepath']
|
||||
print "*" * 50
|
||||
parser = argparse.ArgumentParser(description='Subtitles, faster than your thoughts')
|
||||
parser.add_argument('-l', '--language', action='append', dest='languages', help='wanted language (ISO 639-1)', metavar='LG')
|
||||
parser.add_argument('-p', '--plugin', action='append', dest='plugins', help='plugin to use', metavar='NAME')
|
||||
parser.add_argument('-m', '--multi', action='store_true', help='download multiple subtitle languages')
|
||||
parser.add_argument('-f', '--force', action='store_true', help='replace existing subtitle file')
|
||||
parser.add_argument('-w', '--workers', action='store', help='use N threads (default: %(default)s)', metavar='N', default=4)
|
||||
group_verbosity = parser.add_mutually_exclusive_group()
|
||||
group_verbosity.add_argument('-q', '--quiet', action='store_true', help='disable output')
|
||||
group_verbosity.add_argument('-v', '--verbose', action='store_true', help='verbose output')
|
||||
group_cache = parser.add_mutually_exclusive_group()
|
||||
group_cache.add_argument('--cache-dir', action='store', dest='cache_dir', help='cache directory to use', metavar='DIR', default=os.path.expanduser('~/.config/subliminal'))
|
||||
group_cache.add_argument('--no-cache-dir', action='store_false', dest='cache_dir', help='do not use cache directory (some plugins may not work)')
|
||||
parser.add_argument('--version', action='version', version=subliminal.__version__)
|
||||
parser.add_argument('paths', nargs='+', help='path to video files or folders', metavar='PATH')
|
||||
args = parser.parse_args()
|
||||
|
||||
if __name__ == "__main__":
|
||||
# Set log verbosity
|
||||
if args.verbose:
|
||||
logging.basicConfig(level=logging.DEBUG, format='%(levelname)-8s %(asctime)s %(name)-24s %(message)s', datefmt='%Y-%m-%d %H:%M:%S')
|
||||
elif not args.quiet:
|
||||
logging.basicConfig(level=logging.WARN, format='%(levelname)s: %(name)s %(message)s')
|
||||
|
||||
# Initialize the instance
|
||||
subli = subliminal.Subliminal(cache_dir=args.cache_dir, workers=args.workers, multi=args.multi, force=args.force, max_depth=3, files_mode=-1)
|
||||
if args.plugins:
|
||||
subli.plugins = args.plugins
|
||||
if args.languages:
|
||||
subli.languages = args.languages
|
||||
try:
|
||||
subtitles = subli.downloadSubtitles([unicode(x) for x in args.paths])
|
||||
finally:
|
||||
subli.stopWorkers()
|
||||
if len(subtitles) == 0:
|
||||
if not args.quiet:
|
||||
sys.stderr.write('No subtitles found\n')
|
||||
exit(1)
|
||||
if not args.quiet:
|
||||
print '*' * 50
|
||||
print 'Downloaded %s subtitles' % len(subtitles)
|
||||
for subtitle in subtitles:
|
||||
print subtitle
|
||||
print '*' * 50
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
|
||||
@@ -1,71 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Subliminal - Subtitles, faster than your thoughts
|
||||
# Copyright (c) 2011 Antoine Bertin <diaoulael@gmail.com>
|
||||
#
|
||||
# This file is part of Subliminal.
|
||||
#
|
||||
# Subliminal is free software; you can redistribute it and/or modify it under
|
||||
# the terms of the Lesser GNU General Public License as published by
|
||||
# the Free Software Foundation; either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Subliminal 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
|
||||
# Lesser GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the Lesser GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
import threading
|
||||
import plugins
|
||||
import logging
|
||||
import traceback
|
||||
|
||||
|
||||
class PluginWorker(threading.Thread):
|
||||
"""Threaded plugin worker"""
|
||||
def __init__(self, taskQueue, resultQueue):
|
||||
threading.Thread.__init__(self)
|
||||
self.taskQueue = taskQueue
|
||||
self.resultQueue = resultQueue
|
||||
self.logger = logging.getLogger('subliminal.worker')
|
||||
|
||||
def run(self):
|
||||
while True:
|
||||
task = self.taskQueue.get()
|
||||
result = None
|
||||
try:
|
||||
if not task: # this is a poison pill
|
||||
break
|
||||
elif task['task'] == 'list': # the task is a listing
|
||||
# get the corresponding plugin
|
||||
plugin = getattr(plugins, task['plugin'])(task['config'])
|
||||
# split tasks if the plugin can't handle multi queries
|
||||
splitedTasks = plugin.splitTask(task)
|
||||
myTask = splitedTasks.pop()
|
||||
for st in splitedTasks:
|
||||
self.taskQueue.put(st)
|
||||
result = plugin.list(myTask['filenames'], myTask['languages'])
|
||||
elif task['task'] == 'download': # the task is to download
|
||||
result = None
|
||||
while task['subtitle']:
|
||||
subtitle = task['subtitle'].pop(0)
|
||||
# get the corresponding plugin
|
||||
plugin = getattr(plugins, subtitle['plugin'])(task['config'])
|
||||
path = plugin.download(subtitle)
|
||||
if path:
|
||||
subtitle['subtitlepath'] = path
|
||||
result = subtitle
|
||||
break
|
||||
else:
|
||||
self.logger.error(u'Unknown task %s submited to worker %s' % (task['task'], self.name))
|
||||
except:
|
||||
self.logger.debug(traceback.print_exc())
|
||||
self.logger.error(u"Worker couldn't do the job %s, continue anyway" % task['task'])
|
||||
finally:
|
||||
self.resultQueue.put(result)
|
||||
self.taskQueue.task_done()
|
||||
self.logger.debug(u'Thread %s terminated' % self.name)
|
||||
@@ -19,5 +19,7 @@
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
from subliminal import Subliminal
|
||||
__all__ = ['FORMATS', 'LANGUAGES', 'PLUGINS', 'API_PLUGINS', 'Subliminal', 'Subtitle']
|
||||
from classes import *
|
||||
from subliminal import *
|
||||
from version import __version__
|
||||
|
||||
@@ -0,0 +1,127 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Subliminal - Subtitles, faster than your thoughts
|
||||
# Copyright (c) 2011 Antoine Bertin <diaoulael@gmail.com>
|
||||
#
|
||||
# This file is part of Subliminal.
|
||||
#
|
||||
# Subliminal is free software; you can redistribute it and/or modify it under
|
||||
# the terms of the Lesser GNU General Public License as published by
|
||||
# the Free Software Foundation; either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Subliminal 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
|
||||
# Lesser GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the Lesser GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
|
||||
class Error(Exception):
|
||||
"""Base class for exceptions in subliminal"""
|
||||
pass
|
||||
|
||||
|
||||
class BadStateError(Error):
|
||||
"""Exception raised when an invalid action is asked
|
||||
|
||||
Attributes:
|
||||
current -- current state of Subliminal instance
|
||||
expected -- expected state of Subliminal instance
|
||||
"""
|
||||
def __init__(self, current, expected):
|
||||
self.current = current
|
||||
self.expected = expected
|
||||
|
||||
def __str__(self):
|
||||
return 'Expected state %d but current state is %d' % (self.expected, self.current)
|
||||
|
||||
|
||||
class LanguageError(Error):
|
||||
"""Exception raised when invalid language is submitted
|
||||
|
||||
Attributes:
|
||||
language -- language that cause the error
|
||||
"""
|
||||
def __init__(self, language):
|
||||
self.language = language
|
||||
|
||||
def __str__(self):
|
||||
return self.language
|
||||
|
||||
|
||||
class PluginError(Error):
|
||||
""""Exception raised when invalid plugin is submitted
|
||||
|
||||
Attributes:
|
||||
plugin -- plugin that cause the error
|
||||
"""
|
||||
def __init__(self, plugin):
|
||||
self.plugin = plugin
|
||||
|
||||
def __str__(self):
|
||||
return self.plugin
|
||||
|
||||
|
||||
class WrongTaskError(Error):
|
||||
""""Exception raised when invalid task is submitted"""
|
||||
pass
|
||||
|
||||
|
||||
class DownloadFailedError(Error):
|
||||
""""Exception raised when a download task has failed in plugin"""
|
||||
pass
|
||||
|
||||
|
||||
class Subtitle(object):
|
||||
"""Subtitle class
|
||||
|
||||
Attributes:
|
||||
video_path -- path to the video file
|
||||
path -- path to the subtitle file
|
||||
plugin -- plugin used
|
||||
language -- language of the subtitle
|
||||
link -- download link
|
||||
release -- release group identified by guessit
|
||||
teams -- identified by subliminal
|
||||
"""
|
||||
def __init__(self, video_path=None, path=None, plugin=None, language=None, link=None, release=None, teams=None):
|
||||
self.video_path = video_path
|
||||
self.path = path
|
||||
self.plugin = plugin
|
||||
self.language = language
|
||||
self.link = link
|
||||
self.release = release
|
||||
self.teams = teams
|
||||
|
||||
def __repr__(self):
|
||||
return repr({'video_path': self.video_path, 'path': self.path, 'plugin': self.plugin,
|
||||
'language': self.language, 'link': self.link, 'release': self.release, 'teams': self.teams})
|
||||
|
||||
|
||||
class Task(object):
|
||||
"""Base class for tasks to use in subliminal"""
|
||||
pass
|
||||
|
||||
|
||||
class ListTask(Task):
|
||||
"""List task to list subtitles"""
|
||||
def __init__(self, filepath, languages, plugin, config):
|
||||
self.filepath = filepath
|
||||
self.plugin = plugin
|
||||
self.languages = languages
|
||||
self.config = config
|
||||
|
||||
|
||||
class DownloadTask(Task):
|
||||
"""Download task to download subtitles"""
|
||||
def __init__(self, subtitles):
|
||||
self.subtitles = subtitles
|
||||
|
||||
|
||||
class StopTask(Task):
|
||||
"""Stop task to stop workers"""
|
||||
pass
|
||||
@@ -1,66 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Subliminal - Subtitles, faster than your thoughts
|
||||
# Copyright (c) 2011 Nic Wolfe <nic@wolfeden.ca>
|
||||
# Copyright (c) 2011 Antoine Bertin <diaoulael@gmail.com>
|
||||
#
|
||||
# This file is part of Subliminal.
|
||||
#
|
||||
# Subliminal is free software; you can redistribute it and/or modify it under
|
||||
# the terms of the Lesser GNU General Public License as published by
|
||||
# the Free Software Foundation; either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Subliminal 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
|
||||
# Lesser GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the Lesser GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
import os
|
||||
import subliminal
|
||||
|
||||
|
||||
# This module tries to deal with the apparently random behavior of python when dealing with unicode <-> utf-8
|
||||
# encodings. It tries to just use unicode, but if that fails then it tries forcing it to utf-8. Any functions
|
||||
# which return something should always return unicode.
|
||||
|
||||
def fixStupidEncodings(x, silent=False):
|
||||
if type(x) == str:
|
||||
try:
|
||||
return x.decode(subliminal.SYS_ENCODING)
|
||||
except UnicodeDecodeError:
|
||||
subliminal.logger.error(u"Unable to decode value: " + repr(x))
|
||||
return None
|
||||
elif type(x) == unicode:
|
||||
return x
|
||||
else:
|
||||
subliminal.logger.log(u"Unknown value passed in, ignoring it: " + str(type(x)) + " (" + repr(x) + ":" + repr(type(x)) + ")", logging.DEBUG if silent else logging.ERROR)
|
||||
return None
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def fixListEncodings(x):
|
||||
if type(x) != list:
|
||||
return x
|
||||
else:
|
||||
return filter(lambda x: x != None, map(fixStupidEncodings, x))
|
||||
|
||||
|
||||
def ek(func, *args):
|
||||
result = None
|
||||
if os.name == 'nt':
|
||||
result = func(*args)
|
||||
else:
|
||||
result = func(*[x.encode(subliminal.SYS_ENCODING) if type(x) in (str, unicode) else x for x in args])
|
||||
|
||||
if type(result) == list:
|
||||
return fixListEncodings(result)
|
||||
elif type(result) == str:
|
||||
return fixStupidEncodings(result)
|
||||
else:
|
||||
return result
|
||||
@@ -21,107 +21,99 @@
|
||||
#
|
||||
|
||||
from BeautifulSoup import BeautifulSoup
|
||||
from subliminal.classes import Subtitle
|
||||
import guessit
|
||||
import PluginBase
|
||||
import zipfile
|
||||
import os
|
||||
import urllib2
|
||||
import urllib
|
||||
import traceback
|
||||
import httplib
|
||||
import re
|
||||
import socket
|
||||
|
||||
|
||||
class Addic7ed(PluginBase.PluginBase):
|
||||
site_url = 'http://www.addic7ed.com'
|
||||
site_name = 'Addic7ed'
|
||||
server_url = 'http://www.addic7ed.com'
|
||||
multi_languages_queries = True
|
||||
multi_filename_queries = False
|
||||
api_based = False
|
||||
_plugin_languages = {u"English": "en",
|
||||
u"English (US)": "en",
|
||||
u"English (UK)": "en",
|
||||
u"Italian": "it",
|
||||
u"Portuguese": "pt",
|
||||
u"Portuguese (Brazilian)": "pt-br",
|
||||
u"Romanian": "ro",
|
||||
u"Español (Latinoamérica)": "es",
|
||||
u"Español (España)": "es",
|
||||
u"Spanish (Latin America)": "es",
|
||||
u"Español": "es",
|
||||
u"Spanish": "es",
|
||||
u"Spanish (Spain)": "es",
|
||||
u"French": "fr",
|
||||
u"Greek": "el",
|
||||
u"Arabic": "ar",
|
||||
u"German": "de",
|
||||
u"Croatian": "hr",
|
||||
u"Indonesian": "id",
|
||||
u"Hebrew": "he",
|
||||
u"Russian": "ru",
|
||||
u"Turkish": "tr",
|
||||
u"Swedish": "se",
|
||||
u"Czech": "cs",
|
||||
u"Dutch": "nl",
|
||||
u"Hungarian": "hu",
|
||||
u"Norwegian": "no",
|
||||
u"Polish": "pl",
|
||||
u"Persian": "fa"}
|
||||
_plugin_languages = {u'English': 'en',
|
||||
u'English (US)': 'en',
|
||||
u'English (UK)': 'en',
|
||||
u'Italian': 'it',
|
||||
u'Portuguese': 'pt',
|
||||
u'Portuguese (Brazilian)': 'pt-br',
|
||||
u'Romanian': 'ro',
|
||||
u'Español (Latinoamérica)': 'es',
|
||||
u'Español (España)': 'es',
|
||||
u'Spanish (Latin America)': 'es',
|
||||
u'Español': 'es',
|
||||
u'Spanish': 'es',
|
||||
u'Spanish (Spain)': 'es',
|
||||
u'French': 'fr',
|
||||
u'Greek': 'el',
|
||||
u'Arabic': 'ar',
|
||||
u'German': 'de',
|
||||
u'Croatian': 'hr',
|
||||
u'Indonesian': 'id',
|
||||
u'Hebrew': 'he',
|
||||
u'Russian': 'ru',
|
||||
u'Turkish': 'tr',
|
||||
u'Swedish': 'se',
|
||||
u'Czech': 'cs',
|
||||
u'Dutch': 'nl',
|
||||
u'Hungarian': 'hu',
|
||||
u'Norwegian': 'no',
|
||||
u'Polish': 'pl',
|
||||
u'Persian': 'fa'}
|
||||
|
||||
def __init__(self, config_dict=None):
|
||||
super(Addic7ed, self).__init__(self._plugin_languages, config_dict, isRevert=True)
|
||||
#http://www.addic7ed.com/serie/Smallville/9/11/Absolute_Justice
|
||||
self.release_pattern = re.compile(" \nVersion (.+), ([0-9]+).([0-9])+ MBs")
|
||||
self.release_pattern = re.compile(' \nVersion (.+), ([0-9]+).([0-9])+ MBs')
|
||||
|
||||
def list(self, filenames, languages):
|
||||
''' Main method to call when you want to list subtitles '''
|
||||
# as self.multi_filename_queries is false, we won't have multiple filenames in the list so pick the only one
|
||||
# once multi-filename queries are implemented, set multi_filename_queries to true and manage a list of multiple filenames here
|
||||
def list(self, filepath, languages):
|
||||
if not self.checkLanguages(languages):
|
||||
return []
|
||||
filepath = filenames[0]
|
||||
guess = guessit.guess_file_info(filepath, 'autodetect')
|
||||
if guess['type'] != 'episode':
|
||||
self.logger.debug(u'Not an episode')
|
||||
return []
|
||||
# add multiple things to the release group set
|
||||
release_group = set()
|
||||
if 'releaseGroup' in guess:
|
||||
release_group.add(guess['releaseGroup'])
|
||||
release_group.add(guess['releaseGroup'].lower())
|
||||
else:
|
||||
if 'title' in guess:
|
||||
release_group.add(guess['title'])
|
||||
release_group.add(guess['title'].lower())
|
||||
if 'screenSize' in guess:
|
||||
release_group.add(guess['screenSize'])
|
||||
release_group.add(guess['screenSize'].lower())
|
||||
if 'series' not in guess or len(release_group) == 0:
|
||||
self.logger.debug(u'Not enough information to proceed')
|
||||
return []
|
||||
self.release_group = release_group # used to sort results
|
||||
return self.query(guess['series'], guess['season'], guess['episodeNumber'], release_group, filepath, languages)
|
||||
|
||||
def query(self, name, season, episode, release_group, filepath, languages=None):
|
||||
''' Make a query and returns info about found subtitles '''
|
||||
searchname = name.lower().replace(" ", "_")
|
||||
searchurl = "%s/serie/%s/%s/%s/%s" % (self.server_url, searchname, season, episode, searchname)
|
||||
self.logger.debug(u"Searching in %s" % searchurl)
|
||||
searchname = name.lower().replace(' ', '_')
|
||||
if isinstance(searchname, unicode):
|
||||
searchname = searchname.encode('utf-8')
|
||||
searchurl = '%s/serie/%s/%s/%s/%s' % (self.server_url, urllib2.quote(searchname), season, episode, urllib2.quote(searchname))
|
||||
self.logger.debug(u'Searching in %s' % searchurl)
|
||||
try:
|
||||
req = urllib2.Request(searchurl, headers={'User-Agent': self.user_agent})
|
||||
page = urllib2.urlopen(req, timeout=self.timeout)
|
||||
except urllib2.HTTPError as inst:
|
||||
self.logger.info(u"Error: %s - %s" % (searchurl, inst))
|
||||
self.logger.info(u'Error: %s - %s' % (searchurl, inst))
|
||||
return []
|
||||
except urllib2.URLError as inst:
|
||||
self.logger.info(u"TimeOut: %s" % inst)
|
||||
self.logger.info(u'TimeOut: %s' % inst)
|
||||
return []
|
||||
soup = BeautifulSoup(page.read())
|
||||
sublinks = []
|
||||
for html_sub in soup("td", {"class": "NewsTitle", "colspan": "3"}):
|
||||
for html_sub in soup('td', {'class': 'NewsTitle', 'colspan': '3'}):
|
||||
if not self.release_pattern.match(str(html_sub.contents[1])): # On not needed soup td result
|
||||
continue
|
||||
sub_teams = self.listTeams([self.release_pattern.match(str(html_sub.contents[1])).groups()[0]], [".", "_", " "])
|
||||
sub_teams = self.listTeams([self.release_pattern.match(str(html_sub.contents[1])).groups()[0].lower()], ['.', '_', ' ', '/', '-'])
|
||||
if not release_group.intersection(sub_teams): # On wrong team
|
||||
continue
|
||||
html_language = html_sub.findNext("td", {"class": "language"})
|
||||
html_language = html_sub.findNext('td', {'class': 'language'})
|
||||
sub_language = self.getRevertLanguage(html_language.contents[0].strip().replace(' ', ''))
|
||||
if languages and not sub_language in languages: # On wrong language
|
||||
continue
|
||||
@@ -131,28 +123,12 @@ class Addic7ed(PluginBase.PluginBase):
|
||||
continue
|
||||
sub_link = self.server_url + html_status.findNextSibling('td', {'colspan': '3'}).find('a')['href']
|
||||
self.logger.debug(u'Found a match with teams: %s' % sub_teams)
|
||||
result = {}
|
||||
result["release"] = "%s.S%.2dE%.2d.%s" % (name.replace(" ", "."), int(season), int(episode), '.'.join(sub_teams))
|
||||
result["lang"] = sub_language
|
||||
result["link"] = sub_link
|
||||
result["page"] = searchurl
|
||||
result["filename"] = filepath
|
||||
result["plugin"] = self.getClassName()
|
||||
result["teams"] = sub_teams # used to sort
|
||||
result = Subtitle(filepath, self.getSubtitlePath(filepath, sub_language), self.__class__.__name__, sub_language, sub_link, teams=sub_teams)
|
||||
sublinks.append(result)
|
||||
sublinks.sort(self._cmpTeams)
|
||||
sublinks.sort(self._cmpReleaseGroup)
|
||||
return sublinks
|
||||
|
||||
def download(self, subtitle):
|
||||
'''pass the URL of the sub and the file it matches, will unzip it
|
||||
and return the path to the created file'''
|
||||
suburl = subtitle["link"]
|
||||
videofilename = subtitle["filename"]
|
||||
srtbasefilename = videofilename.rsplit(".", 1)[0]
|
||||
srtfilename = srtbasefilename + self.getExtension(subtitle)
|
||||
self.downloadFile(suburl, srtfilename)
|
||||
return srtfilename
|
||||
self.downloadFile(subtitle.link, subtitle.path)
|
||||
return subtitle
|
||||
|
||||
def _cmpTeams(self, x, y):
|
||||
''' Sort based on teams matching '''
|
||||
return -cmp(len(x['teams'].intersection(self.release_group)), len(y['teams'].intersection(self.release_group)))
|
||||
|
||||
@@ -24,19 +24,18 @@ from xml.dom import minidom
|
||||
import guessit
|
||||
import PluginBase
|
||||
import os
|
||||
import pickle
|
||||
import traceback
|
||||
import urllib
|
||||
try:
|
||||
import cPickle as pickle
|
||||
except ImportError:
|
||||
import pickle
|
||||
import urllib2
|
||||
from subliminal import encodingKludge as ek
|
||||
from subliminal.classes import Subtitle
|
||||
|
||||
|
||||
class BierDopje(PluginBase.PluginBase):
|
||||
site_url = 'http://bierdopje.com'
|
||||
site_name = 'BierDopje'
|
||||
server_url = 'http://api.bierdopje.com/A2B638AC5D804C2E/'
|
||||
multi_languages_queries = True
|
||||
multi_filename_queries = False
|
||||
api_based = True
|
||||
exceptions = {'the office': 10358,
|
||||
'the office us': 10358,
|
||||
@@ -50,34 +49,33 @@ class BierDopje(PluginBase.PluginBase):
|
||||
'hawaii five-0 2010': 14211}
|
||||
_plugin_languages = {'en': 'en', 'nl': 'nl'}
|
||||
|
||||
def __init__(self, config_dict):
|
||||
def __init__(self, config_dict=None):
|
||||
super(BierDopje, self).__init__(self._plugin_languages, config_dict)
|
||||
#http://api.bierdopje.com/23459DC262C0A742/GetShowByName/30+Rock
|
||||
#http://api.bierdopje.com/23459DC262C0A742/GetAllSubsFor/94/5/1/en (30 rock, season 5, episode 1)
|
||||
if not config_dict or not config_dict['cache_dir']:
|
||||
raise Exception('Cache directory is mandatory for this plugin')
|
||||
self.showid_cache = ek.ek(os.path.join, config_dict['cache_dir'], "bierdopje_showid.cache")
|
||||
with self.lock:
|
||||
if not ek.ek(os.path.exists, self.showid_cache):
|
||||
if not ek.ek(os.path.exists, ek.ek(os.path.dirname, self.showid_cache)):
|
||||
raise Exception("Cache directory doesn't exists")
|
||||
f = open(self.showid_cache, 'w')
|
||||
pickle.dump({}, f)
|
||||
if config_dict and config_dict['cache_dir']:
|
||||
self.showid_cache = os.path.join(config_dict['cache_dir'], 'bierdopje_showid.cache')
|
||||
with self.lock:
|
||||
if not os.path.exists(self.showid_cache):
|
||||
if not os.path.exists(os.path.dirname(self.showid_cache)):
|
||||
raise Exception('Cache directory does not exist')
|
||||
f = open(self.showid_cache, 'w')
|
||||
pickle.dump({}, f)
|
||||
f.close()
|
||||
f = open(self.showid_cache, 'r')
|
||||
self.showids = pickle.load(f)
|
||||
self.logger.debug(u'Reading showids from cache: %s' % self.showids)
|
||||
f.close()
|
||||
f = open(self.showid_cache, 'r')
|
||||
self.showids = pickle.load(f)
|
||||
self.logger.debug(u"Reading showids from cache: %s" % self.showids)
|
||||
f.close()
|
||||
|
||||
def list(self, filenames, languages):
|
||||
"""Main method to call when you want to list subtitles"""
|
||||
# as self.multi_filename_queries is false, we won't have multiple filenames in the list so pick the only one
|
||||
# once multi-filename queries are implemented, set multi_filename_queries to true and manage a list of multiple filenames here
|
||||
if not self.checkLanguages(languages):
|
||||
def list(self, filepath, languages):
|
||||
if not self.config_dict['cache_dir']:
|
||||
raise Exception('Cache directory is required for this plugin')
|
||||
possible_languages = self.possible_languages(languages)
|
||||
if not possible_languages:
|
||||
return []
|
||||
filepath = filenames[0]
|
||||
guess = guessit.guess_file_info(filepath, 'autodetect')
|
||||
if guess['type'] != 'episode':
|
||||
self.logger.debug(u'Not an episode')
|
||||
return []
|
||||
# add multiple things to the release group set
|
||||
release_group = set()
|
||||
@@ -89,24 +87,17 @@ class BierDopje(PluginBase.PluginBase):
|
||||
if 'screenSize' in guess:
|
||||
release_group.add(guess['screenSize'].lower())
|
||||
if 'series' not in guess or len(release_group) == 0:
|
||||
self.logger.debug(u'Not enough information to proceed')
|
||||
return []
|
||||
self.release_group = release_group # used to sort results
|
||||
return self.query(guess['series'], guess['season'], guess['episodeNumber'], release_group, filepath, languages)
|
||||
return self.query(guess['series'], guess['season'], guess['episodeNumber'], release_group, filepath, possible_languages)
|
||||
|
||||
def download(self, subtitle):
|
||||
"""Main method to call when you want to download a subtitle"""
|
||||
subpath = subtitle["filename"].rsplit(".", 1)[0] + self.getExtension(subtitle)
|
||||
self.downloadFile(subtitle["link"], subpath)
|
||||
return subpath
|
||||
self.downloadFile(subtitle.link, subtitle.path)
|
||||
return subtitle
|
||||
|
||||
def query(self, name, season, episode, release_group, filepath, languages=None):
|
||||
"""Makes a query and returns info (link, lang) about found subtitles"""
|
||||
if languages:
|
||||
available_languages = list(set(languages).intersection((self._plugin_languages.values())))
|
||||
else:
|
||||
available_languages = self._plugin_languages.values()
|
||||
def query(self, name, season, episode, release_group, filepath, languages):
|
||||
sublinks = []
|
||||
|
||||
# get the show id
|
||||
show_name = name.lower()
|
||||
if show_name in self.exceptions: # get it from exceptions
|
||||
@@ -114,8 +105,11 @@ class BierDopje(PluginBase.PluginBase):
|
||||
elif show_name in self.showids: # get it from cache
|
||||
show_id = self.showids[show_name]
|
||||
else: # retrieve it
|
||||
show_id_url = "%sGetShowByName/%s" % (self.server_url, urllib.quote(show_name))
|
||||
self.logger.debug(u"Retrieving show id from web at %s" % show_id_url)
|
||||
show_name_encoded = show_name
|
||||
if isinstance(show_name_encoded, unicode):
|
||||
show_name_encoded = show_name_encoded.encode('utf-8')
|
||||
show_id_url = '%sGetShowByName/%s' % (self.server_url, urllib2.quote(show_name_encoded))
|
||||
self.logger.debug(u'Retrieving show id from web at %s' % show_id_url)
|
||||
page = urllib2.urlopen(show_id_url)
|
||||
dom = minidom.parse(page)
|
||||
if not dom or len(dom.getElementsByTagName('showid')) == 0: # no proper result
|
||||
@@ -125,21 +119,21 @@ class BierDopje(PluginBase.PluginBase):
|
||||
self.showids[show_name] = show_id
|
||||
with self.lock:
|
||||
f = open(self.showid_cache, 'w')
|
||||
self.logger.debug(u"Writing showid %s to cache file" % show_id)
|
||||
self.logger.debug(u'Writing showid %s to cache file' % show_id)
|
||||
pickle.dump(self.showids, f)
|
||||
f.close()
|
||||
page.close()
|
||||
|
||||
# get the subs for the show id we have
|
||||
for language in available_languages:
|
||||
subs_url = "%sGetAllSubsFor/%s/%s/%s/%s" % (self.server_url, show_id, season, episode, language)
|
||||
self.logger.debug(u"Getting subtitles at %s" % subs_url)
|
||||
for language in languages:
|
||||
subs_url = '%sGetAllSubsFor/%s/%s/%s/%s' % (self.server_url, show_id, season, episode, language)
|
||||
self.logger.debug(u'Getting subtitles at %s' % subs_url)
|
||||
page = urllib2.urlopen(subs_url)
|
||||
dom = minidom.parse(page)
|
||||
page.close()
|
||||
for sub in dom.getElementsByTagName('result'):
|
||||
sub_release = sub.getElementsByTagName('filename')[0].firstChild.data
|
||||
if sub_release.endswith(".srt"):
|
||||
if sub_release.endswith('.srt'):
|
||||
sub_release = sub_release[:-4]
|
||||
sub_release = sub_release + '.avi' # put a random extension for guessit not to fail guessing that file
|
||||
# guess information from subtitle
|
||||
@@ -153,18 +147,8 @@ class BierDopje(PluginBase.PluginBase):
|
||||
if 'screenSize' in sub_guess:
|
||||
sub_release_group.add(sub_guess['screenSize'].lower())
|
||||
sub_link = sub.getElementsByTagName('downloadlink')[0].firstChild.data
|
||||
result = {}
|
||||
result["release"] = sub_release
|
||||
result["link"] = sub_link
|
||||
result["page"] = sub_link
|
||||
result["lang"] = language
|
||||
result["filename"] = filepath
|
||||
result["plugin"] = self.getClassName()
|
||||
result["releaseGroup"] = sub_release_group
|
||||
result = Subtitle(filepath, self.getSubtitlePath(filepath, language), self.__class__.__name__, language, sub_link, sub_release, sub_release_group)
|
||||
sublinks.append(result)
|
||||
sublinks.sort(self._cmpReleaseGroup)
|
||||
return sublinks
|
||||
|
||||
def _cmpReleaseGroup(self, x, y):
|
||||
"""Sort based on teams matching"""
|
||||
return -cmp(len(x['releaseGroup'].intersection(self.release_group)), len(y['releaseGroup'].intersection(self.release_group)))
|
||||
|
||||
@@ -26,97 +26,64 @@ import os
|
||||
import socket
|
||||
import xmlrpclib
|
||||
import guessit
|
||||
from subliminal import encodingKludge as ek
|
||||
import unicodedata
|
||||
from subliminal.classes import Subtitle, DownloadFailedError
|
||||
|
||||
|
||||
class OpenSubtitles(PluginBase.PluginBase):
|
||||
site_url = 'http://www.opensubtitles.org'
|
||||
site_name = 'OpenSubtitles'
|
||||
server_url = 'http://api.opensubtitles.org/xml-rpc'
|
||||
user_agent = 'Subliminal v0.3'
|
||||
multi_languages_queries = True
|
||||
multi_filename_queries = False
|
||||
user_agent = 'Subliminal v0.4'
|
||||
api_based = True
|
||||
_plugin_languages = {"en": "eng",
|
||||
"fr": "fre",
|
||||
"hu": "hun",
|
||||
"cs": "cze",
|
||||
"pl": "pol",
|
||||
"sk": "slo",
|
||||
"pt": "por",
|
||||
"pt-br": "pob",
|
||||
"es": "spa",
|
||||
"el": "ell",
|
||||
"ar": "ara",
|
||||
"sq": "alb",
|
||||
"hy": "arm",
|
||||
"ay": "ass",
|
||||
"bs": "bos",
|
||||
"bg": "bul",
|
||||
"ca": "cat",
|
||||
"zh": "chi",
|
||||
"hr": "hrv",
|
||||
"da": "dan",
|
||||
"nl": "dut",
|
||||
"eo": "epo",
|
||||
"et": "est",
|
||||
"fi": "fin",
|
||||
"gl": "glg",
|
||||
"ka": "geo",
|
||||
"de": "ger",
|
||||
"he": "heb",
|
||||
"hi": "hin",
|
||||
"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",
|
||||
"ro": "rum",
|
||||
"ru": "rus",
|
||||
"sr": "scc",
|
||||
"sl": "slv",
|
||||
"sv": "swe",
|
||||
"th": "tha",
|
||||
"tr": "tur",
|
||||
"uk": "ukr",
|
||||
"vi": "vie"}
|
||||
_plugin_languages = {'aa': 'aar', 'ab': 'abk', 'af': 'afr', 'ak': 'aka', 'sq': 'alb', 'am': 'amh', 'ar': 'ara', 'an': 'arg', 'hy': 'arm',
|
||||
'as': 'asm', 'av': 'ava', 'ae': 'ave', 'ay': 'aym', 'az': 'aze', 'ba': 'bak', 'bm': 'bam', 'eu': 'baq', 'be': 'bel', 'bn': 'ben',
|
||||
'bh': 'bih', 'bi': 'bis', 'bs': 'bos', 'br': 'bre', 'bg': 'bul', 'my': 'bur', 'ca': 'cat', 'ch': 'cha', 'ce': 'che', 'zh': 'chi',
|
||||
'cu': 'chu', 'cv': 'chv', 'kw': 'cor', 'co': 'cos', 'cr': 'cre', 'cs': 'cze', 'da': 'dan', 'dv': 'div', 'nl': 'dut', 'dz': 'dzo',
|
||||
'en': 'eng', 'eo': 'epo', 'et': 'est', 'ee': 'ewe', 'fo': 'fao', 'fj': 'fij', 'fi': 'fin', 'fr': 'fre', 'fy': 'fry', 'ff': 'ful',
|
||||
'ka': 'geo', 'de': 'ger', 'gd': 'gla', 'ga': 'gle', 'gl': 'glg', 'gv': 'glv', 'el': 'ell', 'gn': 'grn', 'gu': 'guj', 'ht': 'hat',
|
||||
'ha': 'hau', 'he': 'heb', 'hz': 'her', 'hi': 'hin', 'ho': 'hmo', 'hr': 'hrv', 'hu': 'hun', 'ig': 'ibo', 'is': 'ice', 'io': 'ido',
|
||||
'ii': 'iii', 'iu': 'iku', 'ie': 'ile', 'ia': 'ina', 'id': 'ind', 'ik': 'ipk', 'it': 'ita', 'jv': 'jav', 'ja': 'jpn', 'kl': 'kal',
|
||||
'kn': 'kan', 'ks': 'kas', 'kr': 'kau', 'kk': 'kaz', 'km': 'khm', 'ki': 'kik', 'rw': 'kin', 'ky': 'kir', 'kv': 'kom', 'kg': 'kon',
|
||||
'ko': 'kor', 'kj': 'kua', 'ku': 'kur', 'lo': 'lao', 'la': 'lat', 'lv': 'lav', 'li': 'lim', 'ln': 'lin', 'lt': 'lit', 'lb': 'ltz',
|
||||
'lu': 'lub', 'lg': 'lug', 'mk': 'mac', 'mh': 'mah', 'ml': 'mal', 'mi': 'mao', 'mr': 'mar', 'ms': 'may', 'mg': 'mlg', 'mt': 'mlt',
|
||||
'mo': 'mol', 'mn': 'mon', 'na': 'nau', 'nv': 'nav', 'nr': 'nbl', 'nd': 'nde', 'ng': 'ndo', 'ne': 'nep', 'nn': 'nno', 'nb': 'nob',
|
||||
'no': 'nor', 'ny': 'nya', 'oc': 'oci', 'oj': 'oji', 'or': 'ori', 'om': 'orm', 'os': 'oss', 'pa': 'pan', 'fa': 'per', 'pi': 'pli',
|
||||
'pl': 'pol', 'pt': 'por', 'ps': 'pus', 'qu': 'que', 'rm': 'roh', 'rn': 'run', 'ru': 'rus', 'sg': 'sag', 'sa': 'san', 'sr': 'scc',
|
||||
'si': 'sin', 'sk': 'slo', 'sl': 'slv', 'se': 'sme', 'sm': 'smo', 'sn': 'sna', 'sd': 'snd', 'so': 'som', 'st': 'sot', 'es': 'spa',
|
||||
'sc': 'srd', 'ss': 'ssw', 'su': 'sun', 'sw': 'swa', 'sv': 'swe', 'ty': 'tah', 'ta': 'tam', 'tt': 'tat', 'te': 'tel', 'tg': 'tgk',
|
||||
'tl': 'tgl', 'th': 'tha', 'bo': 'tib', 'ti': 'tir', 'to': 'ton', 'tn': 'tsn', 'ts': 'tso', 'tk': 'tuk', 'tr': 'tur', 'tw': 'twi',
|
||||
'ug': 'uig', 'uk': 'ukr', 'ur': 'urd', 'uz': 'uzb', 've': 'ven', 'vi': 'vie', 'vo': 'vol', 'cy': 'wel', 'wa': 'wln', 'wo': 'wol',
|
||||
'xh': 'xho', 'yi': 'yid', 'yo': 'yor', 'za': 'zha', 'zu': 'zul', 'ro': 'rum', 'pb': 'pob', 'un': 'unk', 'ay': 'ass'}
|
||||
|
||||
def __init__(self, config_dict=None):
|
||||
super(OpenSubtitles, self).__init__(self._plugin_languages, config_dict)
|
||||
|
||||
def list(self, filenames, languages):
|
||||
"""Main method to call when you want to list subtitles """
|
||||
# as self.multi_filename_queries is false, we won't have multiple filenames in the list so pick the only one
|
||||
# once multi-filename queries are implemented, set multi_filename_queries to true and manage a list of multiple filenames here
|
||||
filepath = filenames[0]
|
||||
if ek.ek(os.path.isfile, filepath):
|
||||
def list(self, filepath, languages):
|
||||
possible_languages = self.possible_languages(languages)
|
||||
if not possible_languages:
|
||||
return []
|
||||
if os.path.isfile(filepath):
|
||||
filehash = self.hashFile(filepath)
|
||||
size = ek.ek(os.path.getsize, filepath)
|
||||
return self.query(moviehash=filehash, languages=languages, bytesize=size, filepath=filepath)
|
||||
size = os.path.getsize(filepath)
|
||||
return self.query(moviehash=filehash, languages=possible_languages, bytesize=size, filepath=filepath)
|
||||
else:
|
||||
return self.query(languages=languages, filepath=filepath)
|
||||
return self.query(languages=possible_languages, filepath=filepath)
|
||||
|
||||
def download(self, subtitle):
|
||||
"""Main method to call when you want to download a subtitle """
|
||||
subtitleFilename = subtitle["filename"].rsplit(".", 1)[0] + self.getExtension(subtitle)
|
||||
self.downloadFile(subtitle["link"], subtitleFilename + ".gz")
|
||||
f = ek.ek(gzip.open, subtitleFilename + ".gz")
|
||||
dump = ek.ek(open, subtitleFilename, "wb")
|
||||
dump.write(f.read())
|
||||
self.adjustPermissions(subtitleFilename)
|
||||
dump.close()
|
||||
f.close()
|
||||
ek.ek(os.remove, subtitleFilename + ".gz")
|
||||
return subtitleFilename
|
||||
try:
|
||||
self.downloadFile(subtitle.link, subtitle.path + '.gz')
|
||||
with open(subtitle.path, 'wb') as dump:
|
||||
gz = gzip.open(subtitle.path + '.gz')
|
||||
dump.write(gz.read())
|
||||
gz.close()
|
||||
self.adjustPermissions(subtitle.path)
|
||||
os.remove(subtitle.path + '.gz')
|
||||
except Exception as e:
|
||||
if os.path.exists(subtitle.path):
|
||||
os.remove(subtitle.path)
|
||||
raise DownloadFailedError(str(e))
|
||||
return subtitle
|
||||
|
||||
def query(self, filepath, imdbID=None, moviehash=None, bytesize=None, languages=None):
|
||||
"""Makes a query on OpenSubtitles and returns info about found subtitles.
|
||||
@@ -130,26 +97,26 @@ class OpenSubtitles(PluginBase.PluginBase):
|
||||
if bytesize:
|
||||
search['moviebytesize'] = str(bytesize)
|
||||
if languages:
|
||||
search['sublanguageid'] = ",".join([self.getLanguage(l) for l in languages])
|
||||
search['sublanguageid'] = ','.join([self.getLanguage(l) for l in languages])
|
||||
if not imdbID and not moviehash and not bytesize:
|
||||
self.logger.debug(u"No search term, we'll use the filename")
|
||||
self.logger.debug(u'No search term, using the filename')
|
||||
guess = guessit.guess_file_info(filepath, 'autodetect')
|
||||
if guess['type'] == 'episode' and 'series' in guess:
|
||||
search['query'] = guess['series']
|
||||
search['query'] = guess['series'].lower()
|
||||
elif guess['type'] == 'movie':
|
||||
search['query'] = guess['title']
|
||||
search['query'] = guess['title'].lower()
|
||||
else: # we don't know what we have
|
||||
return[]
|
||||
# login
|
||||
self.server = xmlrpclib.Server(self.server_url)
|
||||
socket.setdefaulttimeout(self.timeout)
|
||||
try:
|
||||
log_result = self.server.LogIn("", "", "eng", self.user_agent)
|
||||
if not log_result["status"] or log_result["status"] != '200 OK' or not log_result["token"]:
|
||||
log_result = self.server.LogIn('', '', 'eng', self.user_agent)
|
||||
if not log_result['status'] or log_result['status'] != '200 OK' or not log_result['token']:
|
||||
raise Exception('OpenSubtitles login failed')
|
||||
token = log_result["token"]
|
||||
token = log_result['token']
|
||||
except Exception:
|
||||
self.logger.error(u"Cannot login")
|
||||
self.logger.error(u'Cannot login')
|
||||
token = None
|
||||
socket.setdefaulttimeout(None)
|
||||
return []
|
||||
@@ -159,32 +126,30 @@ class OpenSubtitles(PluginBase.PluginBase):
|
||||
try:
|
||||
self.server.LogOut(token)
|
||||
except:
|
||||
self.logger.error(u"Cannot logout")
|
||||
self.logger.error(u'Cannot logout')
|
||||
socket.setdefaulttimeout(None)
|
||||
return sublinks
|
||||
|
||||
def get_results(self, token, search, filepath):
|
||||
self.logger.debug(u"Query uses token %s and search parameters %s" % (token, search))
|
||||
self.logger.debug(u'Query uses token %s and search parameters %s' % (token, search))
|
||||
try:
|
||||
results = self.server.SearchSubtitles(token, [search])
|
||||
except Exception, e:
|
||||
self.logger.debug(u"Cannot query the server")
|
||||
except Exception:
|
||||
self.logger.debug(u'Cannot query the server')
|
||||
return []
|
||||
if not results['data']: # no subtitle found
|
||||
return []
|
||||
sublinks = []
|
||||
self.filename = self.getFileName(filepath)
|
||||
for r in sorted(results['data'], self._cmpSubFileName):
|
||||
result = {}
|
||||
result["release"] = r['SubFileName']
|
||||
result["link"] = r['SubDownloadLink']
|
||||
result["page"] = r['SubDownloadLink']
|
||||
result["lang"] = self.getRevertLanguage(r['SubLanguageID'])
|
||||
result["filename"] = filepath
|
||||
result["plugin"] = self.getClassName()
|
||||
if 'query' in search and not r["MovieReleaseName"].replace('.', ' ').startswith(search['query']): # query mode search, filter results
|
||||
self.logger.debug(u"Skipping %s it does not start with %s" % (r["MovieReleaseName"].replace('.', ' '), search['query']))
|
||||
continue
|
||||
result = Subtitle(filepath, self.getSubtitlePath(filepath, self.getRevertLanguage(r['SubLanguageID'])), self.__class__.__name__, self.getRevertLanguage(r['SubLanguageID']), r['SubDownloadLink'], r['SubFileName'])
|
||||
if 'query' in search: # query mode search, filter results
|
||||
query_encoded = search['query']
|
||||
if isinstance(query_encoded, unicode):
|
||||
query_encoded = unicodedata.normalize('NFKD', query_encoded).encode('ascii', 'ignore')
|
||||
if not r['MovieReleaseName'].replace('.', ' ').lower().startswith(query_encoded):
|
||||
self.logger.debug(u'Skipping %s it does not start with %s' % (r['MovieReleaseName'].replace('.', ' ').lower(), query_encoded))
|
||||
continue
|
||||
sublinks.append(result)
|
||||
return sublinks
|
||||
|
||||
@@ -204,3 +169,4 @@ class OpenSubtitles(PluginBase.PluginBase):
|
||||
if not xmatch and ymatch:
|
||||
return 1
|
||||
return 0
|
||||
|
||||
|
||||
@@ -22,18 +22,15 @@
|
||||
import abc
|
||||
import logging
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
import urllib2
|
||||
import struct
|
||||
import threading
|
||||
from subliminal import encodingKludge as ek
|
||||
import socket
|
||||
from subliminal.classes import DownloadFailedError
|
||||
|
||||
|
||||
class PluginBase(object):
|
||||
__metaclass__ = abc.ABCMeta
|
||||
multi_languages_queries = False
|
||||
multi_filename_queries = False
|
||||
api_based = True
|
||||
timeout = 3
|
||||
user_agent = 'Mozilla/5.0 (X11; U; Linux x86_64; en-US; rv:1.9.1.3)'
|
||||
@@ -51,128 +48,101 @@ class PluginBase(object):
|
||||
else:
|
||||
self.revertPluginLanguages = pluginLanguages
|
||||
self.pluginLanguages = dict((v, k) for k, v in self.revertPluginLanguages.iteritems())
|
||||
self.logger = logging.getLogger('subliminal.%s' % self.getClassName())
|
||||
self.logger = logging.getLogger('subliminal.%s' % self.__class__.__name__)
|
||||
|
||||
@staticmethod
|
||||
def getFileName(filepath):
|
||||
filename = filepath
|
||||
if ek.ek(os.path.isfile, filename):
|
||||
filename = ek.ek(os.path.basename, filename)
|
||||
if os.path.isfile(filename):
|
||||
filename = os.path.basename(filename)
|
||||
if filename.endswith(('.avi', '.wmv', '.mov', '.mp4', '.mpeg', '.mpg', '.mkv')):
|
||||
filename = filename.rsplit('.', 1)[0]
|
||||
return filename
|
||||
|
||||
def possible_languages(self, languages):
|
||||
possible_languages = languages & set(self.pluginLanguages.keys())
|
||||
if not possible_languages:
|
||||
self.logger.debug(u'Languages %r are not in supported languages' % languages)
|
||||
return possible_languages
|
||||
|
||||
def hashFile(self, filename):
|
||||
"""Hash a file like OpenSubtitles"""
|
||||
longlongformat = 'q' # long long
|
||||
bytesize = struct.calcsize(longlongformat)
|
||||
f = ek.ek(open, filename, "rb")
|
||||
filesize = ek.ek(os.path.getsize, filename)
|
||||
f = open(filename, 'rb')
|
||||
filesize = os.path.getsize(filename)
|
||||
hash = filesize
|
||||
if filesize < 65536 * 2:
|
||||
self.logger.error(u"File %s is too small (SizeError < 2**16)" % filename)
|
||||
self.logger.error(u'File %s is too small (SizeError < 2**16)' % filename)
|
||||
return []
|
||||
for x in range(65536 / bytesize):
|
||||
for _ in range(65536 / bytesize):
|
||||
buffer = f.read(bytesize)
|
||||
(l_value,) = struct.unpack(longlongformat, buffer)
|
||||
hash += l_value
|
||||
hash = hash & 0xFFFFFFFFFFFFFFFF # to remain as 64bit number
|
||||
f.seek(max(0, filesize - 65536), 0)
|
||||
for x in range(65536 / bytesize):
|
||||
for _ in range(65536 / bytesize):
|
||||
buffer = f.read(bytesize)
|
||||
(l_value,) = struct.unpack(longlongformat, buffer)
|
||||
hash += l_value
|
||||
hash = hash & 0xFFFFFFFFFFFFFFFF
|
||||
f.close()
|
||||
returnedhash = "%016x" % hash
|
||||
returnedhash = '%016x' % hash
|
||||
return returnedhash
|
||||
|
||||
def downloadFile(self, url, filename, data=None):
|
||||
"""Downloads the given url to the given filename"""
|
||||
def downloadFile(self, url, filepath, data=None):
|
||||
"""Download a subtitle file"""
|
||||
self.logger.info(u'Downloading %s' % url)
|
||||
socket.setdefaulttimeout(self.timeout)
|
||||
try:
|
||||
self.logger.info(u"Downloading %s" % url)
|
||||
req = urllib2.Request(url, headers={'Referer': url, 'User-Agent': self.user_agent})
|
||||
f = urllib2.urlopen(req, data=data)
|
||||
dump = ek.ek(open, filename, "wb")
|
||||
dump.write(f.read())
|
||||
self.adjustPermissions(filename)
|
||||
dump.close()
|
||||
f.close()
|
||||
self.logger.debug(u"Download finished for file %s. Size: %s" % (filename, ek.ek(os.path.getsize, filename)))
|
||||
except urllib2.HTTPError, e:
|
||||
self.logger.error(u"HTTP Error:", e.code, url)
|
||||
except urllib2.URLError, e:
|
||||
self.logger.error(u"URL Error:", e.reason, url)
|
||||
with open(filepath, 'wb') as dump:
|
||||
f = urllib2.urlopen(req, data=data)
|
||||
dump.write(f.read())
|
||||
self.adjustPermissions(filepath)
|
||||
f.close()
|
||||
except Exception as e:
|
||||
self.logger.error(u'Download %s failed: %s' % (url, e))
|
||||
if os.path.exists(filepath):
|
||||
os.remove(filepath)
|
||||
raise DownloadFailedError(str(e))
|
||||
finally:
|
||||
socket.setdefaulttimeout(self.timeout)
|
||||
self.logger.debug(u'Download finished for file %s. Size: %s' % (filepath, os.path.getsize(filepath)))
|
||||
|
||||
def adjustPermissions(self, filepath):
|
||||
if self.config_dict and 'files_mode' in self.config_dict and self.config_dict['files_mode'] != -1:
|
||||
ek.ek(os.chmod, filepath, self.config_dict['files_mode'])
|
||||
os.chmod(filepath, self.config_dict['files_mode'])
|
||||
|
||||
@abc.abstractmethod
|
||||
def list(self, filenames, languages):
|
||||
"""Main method to call when you want to list subtitles"""
|
||||
def list(self, filepath, languages):
|
||||
"""List subtitles"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def download(self, subtitle):
|
||||
"""Main method to call when you want to download a subtitle"""
|
||||
"""Download a subtitle"""
|
||||
|
||||
def getRevertLanguage(self, language):
|
||||
"""Returns the short (two-character) representation from the long language name"""
|
||||
"""ISO-639-1 language code from plugin language code"""
|
||||
try:
|
||||
return self.revertPluginLanguages[language]
|
||||
except KeyError, e:
|
||||
self.logger.warn(u"Ooops, you found a missing language in the configuration file of %s: %s. Send a bug report to have it added." % (self.getClassName(), language))
|
||||
|
||||
def checkLanguages(self, languages):
|
||||
if languages and not set(languages).intersection((self._plugin_languages.values())):
|
||||
self.logger.debug(u'None of requested languages %s are available' % languages)
|
||||
return False
|
||||
return True
|
||||
except KeyError:
|
||||
self.logger.warn(u'Ooops, you found a missing language in the configuration file of %s: %s. Send a bug report to have it added.' % (self.__class__.__name__, language))
|
||||
|
||||
def getLanguage(self, language):
|
||||
"""Returns the long naming of the language from a two character code"""
|
||||
"""Plugin language code from ISO-639-1 language code"""
|
||||
try:
|
||||
return self.pluginLanguages[language]
|
||||
except KeyError, e:
|
||||
self.logger.warn(u"Ooops, you found a missing language in the configuration file of %s: %s. Send a bug report to have it added." % (self.getClassName(), language))
|
||||
|
||||
def getExtension(self, subtitle):
|
||||
except KeyError:
|
||||
self.logger.warn(u'Ooops, you found a missing language in the configuration file of %s: %s. Send a bug report to have it added.' % (self.__class__.__name__, language))
|
||||
|
||||
def getSubtitlePath(self, video_path, language):
|
||||
if not os.path.exists(video_path):
|
||||
video_path = os.path.split(video_path)[1]
|
||||
path = video_path.rsplit('.', 1)[0]
|
||||
if self.config_dict and self.config_dict['multi']:
|
||||
return ".%s.srt" % subtitle['lang']
|
||||
return ".srt"
|
||||
|
||||
def getClassName(self):
|
||||
return self.__class__.__name__
|
||||
|
||||
def splitTask(self, task):
|
||||
"""Determines if the plugin can handle multi-thing queries and output splited tasks for list task only"""
|
||||
if task['task'] != 'list':
|
||||
return [task]
|
||||
tasks = [task]
|
||||
if not self.multi_filename_queries:
|
||||
tasks = self._splitOnField(tasks, 'filenames')
|
||||
if not self.multi_languages_queries:
|
||||
tasks = self._splitOnField(tasks, 'languages')
|
||||
return tasks
|
||||
|
||||
@staticmethod
|
||||
def _splitOnField(elements, field):
|
||||
"""
|
||||
Split a list of dict in a bigger one if the element field in the dict has multiple elements too
|
||||
i.e. [{'a': 1, 'b': [2,3]}, {'a': 7, 'b': [4]}] => [{'a': 1, 'b': [2]}, {'a': 1, 'b': [3]}, {'a': 7, 'b': [4]}]
|
||||
with field = 'b'
|
||||
"""
|
||||
results = []
|
||||
for e in elements:
|
||||
for v in e[field]:
|
||||
newElement = {}
|
||||
for (key, value) in e.items():
|
||||
if key != field:
|
||||
newElement[key] = value
|
||||
else:
|
||||
newElement[key] = [v]
|
||||
results.append(newElement)
|
||||
return results
|
||||
return path + '.%s.srt' % language
|
||||
return path + '.srt'
|
||||
|
||||
def listTeams(self, sub_teams, separators):
|
||||
"""List teams of a given string using separators"""
|
||||
@@ -186,3 +156,8 @@ class PluginBase(object):
|
||||
for t in sub_teams:
|
||||
teams += t.split(sep)
|
||||
return teams
|
||||
|
||||
def _cmpReleaseGroup(self, x, y):
|
||||
"""Sort based on teams matching"""
|
||||
return -cmp(len(x.teams.intersection(self.release_group)), len(y.teams.intersection(self.release_group)))
|
||||
|
||||
|
||||
@@ -23,22 +23,14 @@
|
||||
from hashlib import md5, sha256
|
||||
import PluginBase
|
||||
import xmlrpclib
|
||||
import struct
|
||||
import socket
|
||||
import zipfile
|
||||
import os
|
||||
import urllib2
|
||||
import urllib
|
||||
import traceback
|
||||
from subliminal import encodingKludge as ek
|
||||
|
||||
|
||||
class Podnapisi(PluginBase.PluginBase):
|
||||
site_url = "http://www.podnapisi.net"
|
||||
site_name = "Podnapisi"
|
||||
server_url = 'http://ssp.podnapisi.net:8000'
|
||||
multi_languages_queries = True
|
||||
multi_filename_queries = False
|
||||
api_based = True
|
||||
_plugin_languages = {"sl": "1",
|
||||
"en": "2",
|
||||
@@ -96,10 +88,8 @@ class Podnapisi(PluginBase.PluginBase):
|
||||
|
||||
def list(self, filenames, languages):
|
||||
"""Main method to call when you want to list subtitles"""
|
||||
# as self.multi_filename_queries is false, we won't have multiple filenames in the list so pick the only one
|
||||
# once multi-filename queries are implemented, set multi_filename_queries to true and manage a list of multiple filenames here
|
||||
filepath = filenames[0]
|
||||
if not ek.ek(os.path.isfile, filepath):
|
||||
if not os.path.isfile(filepath):
|
||||
return []
|
||||
return self.query(self.hashFile(filepath), languages)
|
||||
|
||||
@@ -116,7 +106,7 @@ class Podnapisi(PluginBase.PluginBase):
|
||||
self.logger.debug(u"Result: %s" % log_result)
|
||||
token = log_result["session"]
|
||||
nonce = log_result["nonce"]
|
||||
except Exception, e:
|
||||
except Exception:
|
||||
self.logger.error(u"Cannot login" % log_result)
|
||||
socket.setdefaulttimeout(None)
|
||||
return []
|
||||
@@ -141,6 +131,5 @@ class Podnapisi(PluginBase.PluginBase):
|
||||
subs = []
|
||||
for sub in results['results']:
|
||||
subs.append(sub)
|
||||
d = self.server.download(token, [173793])
|
||||
self.server.terminate(token)
|
||||
return subs
|
||||
|
||||
@@ -25,18 +25,12 @@ import PluginBase
|
||||
import zipfile
|
||||
import os
|
||||
import urllib2
|
||||
import urllib
|
||||
import traceback
|
||||
import httplib
|
||||
from subliminal import encodingKludge as ek
|
||||
|
||||
|
||||
class SubScene(PluginBase.PluginBase):
|
||||
site_url = 'http://subscene.com'
|
||||
site_name = 'SubScene'
|
||||
server_url = 'http://subscene.com/s.aspx?subtitle='
|
||||
multi_languages_queries = True
|
||||
multi_filename_queries = False
|
||||
api_based = False
|
||||
_plugin_languages = {"en": "English",
|
||||
"se": "Swedish",
|
||||
@@ -79,8 +73,6 @@ class SubScene(PluginBase.PluginBase):
|
||||
|
||||
def list(self, filenames, languages):
|
||||
"""Main method to call when you want to list subtitles"""
|
||||
# as self.multi_filename_queries is false, we won't have multiple filenames in the list so pick the only one
|
||||
# once multi-filename queries are implemented, set multi_filename_queries to true and manage a list of multiple filenames here
|
||||
filepath = filenames[0]
|
||||
fname = self.getFileName(filepath)
|
||||
subs = self.query(fname, filepath, languages)
|
||||
@@ -110,7 +102,7 @@ class SubScene(PluginBase.PluginBase):
|
||||
extension = el.orig_filename.rsplit(".", 1)[1]
|
||||
if extension in ("srt", "sub", "txt"):
|
||||
subtitlefilename = srtbasefilename + "." + extension
|
||||
outfile = ek.ek(open, subtitlefilename, "wb")
|
||||
outfile = open(subtitlefilename, "wb")
|
||||
outfile.write(zf.read(el.orig_filename))
|
||||
outfile.flush()
|
||||
self.adjustPermissions(subtitlefilename)
|
||||
@@ -119,7 +111,7 @@ class SubScene(PluginBase.PluginBase):
|
||||
self.logger.info(u"File %s does not seem to be valid " % el.orig_filename)
|
||||
# Deleting the zip file
|
||||
zf.close()
|
||||
ek.ek(os.remove, archivefilename)
|
||||
os.remove(archivefilename)
|
||||
return subtitlefilename
|
||||
elif archivefilename.endswith('.rar'):
|
||||
self.logger.warn(u'Rar is not really supported yet. Trying to call unrar')
|
||||
@@ -130,16 +122,16 @@ class SubScene(PluginBase.PluginBase):
|
||||
for el in output.splitlines():
|
||||
extension = el.rsplit(".", 1)[1]
|
||||
if extension in ("srt", "sub"):
|
||||
args = ['unrar', 'e', archivefilename, el, ek.ek(os.path.dirname, archivefilename)]
|
||||
args = ['unrar', 'e', archivefilename, el, os.path.dirname(archivefilename)]
|
||||
subprocess.Popen(args)
|
||||
tmpsubtitlefilename = ek.ek(os.path.join, ek.ek(os.path.dirname, archivefilename), el)
|
||||
subtitlefilename = ek.ek(os.path.join, ek.ek(os.path.dirname, archivefilename), srtbasefilename + "." + extension)
|
||||
if ek.ek(os.path.exists, tmpsubtitlefilename):
|
||||
tmpsubtitlefilename = os.path.join(os.path.dirname(archivefilename), el)
|
||||
subtitlefilename = os.path.join(os.path.dirname(archivefilename), srtbasefilename + "." + extension)
|
||||
if os.path.exists(tmpsubtitlefilename):
|
||||
# rename it to match the file
|
||||
ek.ek(os.rename, tmpsubtitlefilename, subtitlefilename)
|
||||
os.rename(tmpsubtitlefilename, subtitlefilename)
|
||||
# exit
|
||||
return subtitlefilename
|
||||
except OSError, e:
|
||||
except OSError as e:
|
||||
self.logger.error(u"Execution failed: %s" % e)
|
||||
return None
|
||||
else:
|
||||
@@ -149,12 +141,11 @@ class SubScene(PluginBase.PluginBase):
|
||||
def downloadFile(self, url, filename):
|
||||
"""Downloads the given url to the given filename"""
|
||||
#FIXME: Not working
|
||||
super(SubScene, self).downloadFile(url, filename, urllib.urlencode({'__EVENTTARGET': 's$lc$bcr$downloadLink', '__EVENTARGUMENT': '', '__VIEWSTATE': '/wEPDwUHNzUxOTkwNWRk4wau5efPqhlBJJlOkKKHN8FIS04='}))
|
||||
|
||||
def query(self, token, filepath, langs=None):
|
||||
"""Make a query on SubScene and returns info about found subtitles"""
|
||||
sublinks = []
|
||||
searchurl = "%s%s" % (self.server_url, urllib.quote(token))
|
||||
searchurl = "%s%s" % (self.server_url, urllib2.quote(token))
|
||||
self.logger.debug(u"Query: %s" % searchurl)
|
||||
page = urllib2.urlopen(searchurl)
|
||||
soup = BeautifulSoup(page.read())
|
||||
@@ -172,6 +163,6 @@ class SubScene(PluginBase.PluginBase):
|
||||
result["link"] = None
|
||||
result["page"] = self.site_url + sub_page
|
||||
result["filename"] = filepath
|
||||
result["plugin"] = self.getClassName()
|
||||
result["plugin"] = self.__class__.__name__
|
||||
sublinks.append(result)
|
||||
return sublinks
|
||||
|
||||
@@ -22,135 +22,95 @@
|
||||
|
||||
from BeautifulSoup import BeautifulSoup
|
||||
import PluginBase
|
||||
import zipfile
|
||||
import urllib2
|
||||
import urllib
|
||||
import logging
|
||||
import traceback
|
||||
import httplib
|
||||
import re
|
||||
import guessit
|
||||
from subliminal import encodingKludge as ek
|
||||
from subliminal.classes import Subtitle
|
||||
|
||||
|
||||
class SubsWiki(PluginBase.PluginBase):
|
||||
site_url = 'http://www.subswiki.com'
|
||||
site_name = 'SubsWiki'
|
||||
server_url = 'http://www.subswiki.com'
|
||||
multi_languages_queries = True
|
||||
multi_filename_queries = False
|
||||
api_based = False
|
||||
_plugin_languages = {u"English (US)": "en",
|
||||
u"English (UK)": "en",
|
||||
u"English": "en",
|
||||
u"French": "fr",
|
||||
u"Brazilian": "pt-br",
|
||||
u"Portuguese": "pt",
|
||||
u"Español (Latinoamérica)": "es",
|
||||
u"Español (España)": "es",
|
||||
u"Español": "es",
|
||||
u"Italian": "it",
|
||||
u"Català": "ca"}
|
||||
_plugin_languages = {u'English (US)': 'en',
|
||||
u'English (UK)': 'en',
|
||||
u'English': 'en',
|
||||
u'French': 'fr',
|
||||
u'Brazilian': 'pt-br',
|
||||
u'Portuguese': 'pt',
|
||||
u'Español (Latinoamérica)': 'es',
|
||||
u'Español (España)': 'es',
|
||||
u'Español': 'es',
|
||||
u'Italian': 'it',
|
||||
u'Català': 'ca'}
|
||||
|
||||
def __init__(self, config_dict=None):
|
||||
super(SubsWiki, self).__init__(self._plugin_languages, config_dict, True)
|
||||
self.release_pattern = re.compile("\nVersion (.+), ([0-9]+).([0-9])+ MBs")
|
||||
self.release_pattern = re.compile('\nVersion (.+), ([0-9]+).([0-9])+ MBs')
|
||||
|
||||
def list(self, filenames, languages):
|
||||
"""Main method to call when you want to list subtitles"""
|
||||
# as self.multi_filename_queries is false, we won't have multiple filenames in the list so pick the only one
|
||||
# once multi-filename queries are implemented, set multi_filename_queries to true and manage a list of multiple filenames here
|
||||
filepath = filenames[0]
|
||||
if not self.checkLanguages(languages):
|
||||
def list(self, filepath, languages):
|
||||
possible_languages = self.possible_languages(languages)
|
||||
if not possible_languages:
|
||||
return []
|
||||
guess = guessit.guess_file_info(filepath, 'autodetect')
|
||||
if guess['type'] != 'episode':
|
||||
self.logger.debug(u'Not an episode')
|
||||
return []
|
||||
# add multiple things to the release group set
|
||||
release_group = set()
|
||||
if 'releaseGroup' in guess:
|
||||
release_group.add(guess['releaseGroup'])
|
||||
release_group.add(guess['releaseGroup'].lower())
|
||||
else:
|
||||
if 'title' in guess:
|
||||
release_group.add(guess['title'])
|
||||
release_group.add(guess['title'].lower())
|
||||
if 'screenSize' in guess:
|
||||
release_group.add(guess['screenSize'])
|
||||
release_group.add(guess['screenSize'].lower())
|
||||
if 'series' not in guess or len(release_group) == 0:
|
||||
self.logger.debug(u'Not enough information to proceed')
|
||||
return []
|
||||
self.release_group = release_group # used to sort results
|
||||
return self.query(guess['series'], guess['season'], guess['episodeNumber'], release_group, filepath, languages)
|
||||
return self.query(guess['series'], guess['season'], guess['episodeNumber'], release_group, filepath, possible_languages)
|
||||
|
||||
def query(self, name, season, episode, release_group, filepath, languages=None):
|
||||
"""Make a query and returns info about found subtitles"""
|
||||
def query(self, name, season, episode, release_group, filepath, languages):
|
||||
sublinks = []
|
||||
searchname = name.lower().replace(" ", "_")
|
||||
searchurl = "%s/serie/%s/%s/%s/" % (self.server_url, searchname, season, episode)
|
||||
self.logger.debug(u"Searching in %s" % searchurl)
|
||||
searchname = name.lower().replace(' ', '_')
|
||||
if isinstance(searchname, unicode):
|
||||
searchname = searchname.encode('utf-8')
|
||||
searchurl = '%s/serie/%s/%s/%s/' % (self.server_url, urllib2.quote(searchname), season, episode)
|
||||
self.logger.debug(u'Searching in %s' % searchurl)
|
||||
try:
|
||||
req = urllib2.Request(searchurl, headers={'User-Agent': self.user_agent})
|
||||
page = urllib2.urlopen(req, timeout=self.timeout)
|
||||
except urllib2.HTTPError as inst:
|
||||
self.logger.info(u"Error: %s - %s" % (searchurl, inst))
|
||||
self.logger.info(u'Error: %s - %s' % (searchurl, inst))
|
||||
return []
|
||||
except urllib2.URLError as inst:
|
||||
self.logger.info(u"TimeOut: %s" % inst)
|
||||
self.logger.info(u'TimeOut: %s' % inst)
|
||||
return []
|
||||
soup = BeautifulSoup(page.read())
|
||||
for subs in soup("td", {"class": "NewsTitle"}):
|
||||
sub_teams = self.listTeams([self.release_pattern.search("%s" % subs.contents[1]).group(1)], [".", "_", " ", "/", "-"])
|
||||
for subs in soup('td', {'class': 'NewsTitle'}):
|
||||
sub_teams = self.listTeams([self.release_pattern.search('%s' % subs.contents[1]).group(1).lower()], ['.', '_', ' ', '/', '-'])
|
||||
if not release_group.intersection(sub_teams): # On wrong team
|
||||
continue
|
||||
self.logger.debug(u"Team from website: %s" % sub_teams)
|
||||
self.logger.debug(u"Team from file: %s" % release_group)
|
||||
for html_language in subs.parent.parent.findAll("td", {"class": "language"}):
|
||||
self.logger.debug(u'Team from website: %s' % sub_teams)
|
||||
self.logger.debug(u'Team from file: %s' % release_group)
|
||||
for html_language in subs.parent.parent.findAll('td', {'class': 'language'}):
|
||||
sub_language = self.getRevertLanguage(html_language.string.strip())
|
||||
self.logger.debug(u"Subtitle reverted language: %s" % sub_language)
|
||||
if languages and not sub_language in languages: # On wrong language
|
||||
self.logger.debug(u'Subtitle reverted language: %s' % sub_language)
|
||||
if not sub_language in languages: # On wrong language
|
||||
continue
|
||||
html_status = html_language.findNextSibling('td')
|
||||
sub_status = html_status.find('strong').string.strip()
|
||||
if not sub_status == 'Completed': # On not completed subtitles
|
||||
continue
|
||||
sub_link = html_status.findNext("td").find("a")["href"]
|
||||
result = {}
|
||||
result["release"] = "%s.S%.2dE%.2d.%s" % (name.replace(" ", "."), int(season), int(episode), '.'.join(sub_teams))
|
||||
result["lang"] = sub_language
|
||||
result["link"] = self.server_url + sub_link
|
||||
result["page"] = searchurl
|
||||
result["filename"] = filepath
|
||||
result["plugin"] = self.getClassName()
|
||||
result["teams"] = sub_teams # used to sort
|
||||
sub_link = html_status.findNext('td').find('a')['href']
|
||||
result = Subtitle(filepath, self.getSubtitlePath(filepath, sub_language), self.__class__.__name__, sub_language, self.server_url + sub_link, teams=sub_teams)
|
||||
sublinks.append(result)
|
||||
sublinks.sort(self._cmpTeams)
|
||||
sublinks.sort(self._cmpReleaseGroup)
|
||||
return sublinks
|
||||
|
||||
def download(self, subtitle):
|
||||
"""Main method to call when you want to download a subtitle"""
|
||||
subtitleFilename = subtitle["filename"].rsplit(".", 1)[0] + self.getExtension(subtitle)
|
||||
self.downloadFile(subtitle["link"], subtitleFilename)
|
||||
return subtitleFilename
|
||||
self.downloadFile(subtitle.link, subtitle.path)
|
||||
return subtitle
|
||||
|
||||
def listTeams(self, subteams, separators):
|
||||
teams = []
|
||||
for sep in separators:
|
||||
subteams = self.splitTeam(subteams, sep)
|
||||
return set(subteams)
|
||||
|
||||
def splitTeam(self, subteams, sep):
|
||||
teams = []
|
||||
for t in subteams:
|
||||
teams += t.split(sep)
|
||||
return teams
|
||||
|
||||
def downloadFile(self, url, filename):
|
||||
"""Downloads the given url to the given filename"""
|
||||
req = urllib2.Request(url, headers={'Referer': url, 'User-Agent': 'Mozilla/5.0 (X11; U; Linux x86_64; en-US; rv:1.9.1.3)'})
|
||||
f = urllib2.urlopen(req)
|
||||
dump = ek.ek(open, filename, "wb")
|
||||
dump.write(f.read())
|
||||
dump.close()
|
||||
f.close()
|
||||
|
||||
def _cmpTeams(self, x, y):
|
||||
"""Sort based on teams matching"""
|
||||
return -cmp(len(x['teams'].intersection(self.release_group)), len(y['teams'].intersection(self.release_group)))
|
||||
|
||||
@@ -1,122 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Subliminal - Subtitles, faster than your thoughts
|
||||
# Copyright (c) 2008-2011 Patrick Dessalle <patrick@dessalle.be>
|
||||
# Copyright (c) 2011 Antoine Bertin <diaoulael@gmail.com>
|
||||
#
|
||||
# This file is part of Subliminal.
|
||||
#
|
||||
# Subliminal is free software; you can redistribute it and/or modify it under
|
||||
# the terms of the Lesser GNU General Public License as published by
|
||||
# the Free Software Foundation; either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Subliminal 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
|
||||
# Lesser GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the Lesser GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
import ConfigParser
|
||||
import PluginBase
|
||||
import traceback
|
||||
import urllib
|
||||
import urllib2
|
||||
import xml.dom.minidom
|
||||
|
||||
|
||||
class SubtitleSource(PluginBase.PluginBase):
|
||||
site_url = 'http://www.subtitlesource.org'
|
||||
site_name = 'SubtitleSource'
|
||||
server_url = 'http://www.subtitlesource.org/api/%s/3.0/xmlsearch'
|
||||
multi_languages_queries = True
|
||||
multi_filename_queries = False
|
||||
api_based = True
|
||||
_plugin_languages = {"en": "English",
|
||||
"sv": "Swedish",
|
||||
"da": "Danish",
|
||||
"fi": "Finnish",
|
||||
"no": "Norwegian",
|
||||
"fr": "French",
|
||||
"es": "Spanish",
|
||||
"is": "Icelandic"}
|
||||
|
||||
def __init__(self, config_dict=None):
|
||||
super(SubtitleSource, self).__init__(self._plugin_languages, config_dict)
|
||||
if config_dict and "subtitlesource_key" in config_dict:
|
||||
self.server_url = self.server_url % config_dict["subtitlesource_key"]
|
||||
else:
|
||||
self.logger.error(u'SubtitleSource API Key is mandatory for this plugin')
|
||||
raise Exception('SubtitleSource API Key is mandatory for this plugin')
|
||||
|
||||
def list(self, filenames, languages):
|
||||
"""Main method to call when you want to list subtitles"""
|
||||
filepath = filenames[0]
|
||||
fname = self.getFileName(filepath)
|
||||
subs = self.query(fname, languages)
|
||||
if not subs and fname.rfind(".[") > 0:
|
||||
# Try to remove the [VTV] or [EZTV] at the end of the file
|
||||
teamless_filename = fname[0:fname.rfind(".[")]
|
||||
subs = self.query(teamless_filename, languages)
|
||||
return subs
|
||||
else:
|
||||
return subs
|
||||
|
||||
def query(self, token, languages=None):
|
||||
"""Makes a query on SubtitlesSource and returns info (link, lang) about found subtitles"""
|
||||
self.logger.debug(u"Local file is: %s " % token)
|
||||
sublinks = []
|
||||
if not languages: # langs is empty of None
|
||||
languages = ["all"]
|
||||
else: # parse each lang to generate the equivalent lang
|
||||
languages = [self._plugin_languages[l] for l in languages if l in self._plugin_languages.keys()]
|
||||
# Get the CD part of this
|
||||
metaData = self.guessFileData(token)
|
||||
multipart = metaData.get('part', None)
|
||||
part = metaData.get('part')
|
||||
if not part: # part will return None if not found using the regex
|
||||
part = 1
|
||||
for lang in languages:
|
||||
searchurl = "%s/%s/%s/0" % (self.server_url, urllib.quote(token), lang)
|
||||
self.logger.debug(u"dl'ing %s" % searchurl)
|
||||
page = urllib2.urlopen(searchurl, timeout=self.timeout)
|
||||
xmltree = xml.dom.minidom.parse(page)
|
||||
subs = xmltree.getElementsByTagName("sub")
|
||||
for sub in subs:
|
||||
sublang = self.getRevertLanguage(self.getValue(sub, "language"))
|
||||
if languages and not sublang in languages:
|
||||
continue # The language of this sub is not wanted => Skip
|
||||
if multipart and not int(self.getValue(sub, 'cd')) > 1:
|
||||
continue # The subtitle is not a multipart
|
||||
dllink = "http://www.subtitlesource.org/download/text/%s/%s" % (self.getValue(sub, "id"), part)
|
||||
self.logger.debug(u"Link added: %s (%s)" % (dllink, sublang))
|
||||
result = {}
|
||||
result["release"] = self.getValue(sub, "releasename")
|
||||
result["link"] = dllink
|
||||
result["page"] = dllink
|
||||
result["lang"] = sublang
|
||||
releaseMetaData = self.guessFileData(result['release'])
|
||||
teams = set(metaData['teams'])
|
||||
srtTeams = set(releaseMetaData['teams'])
|
||||
self.logger.debug(u"Analyzing: %s " % result['release'])
|
||||
self.logger.debug(u"Local file has: %s " % metaData['teams'])
|
||||
self.logger.debug(u"Remote sub has: %s " % releaseMetaData['teams'])
|
||||
if result['release'].startswith(token) or (releaseMetaData['name'] == metaData['name'] and releaseMetaData['type'] == metaData['type'] and (teams.issubset(srtTeams) or srtTeams.issubset(teams))):
|
||||
sublinks.append(result)
|
||||
return sublinks
|
||||
|
||||
def download(self, subtitle):
|
||||
"""Main method to call when you want to download a subtitle"""
|
||||
suburl = subtitle["link"]
|
||||
videofilename = subtitle["filename"]
|
||||
srtfilename = videofilename.rsplit(".", 1)[0] + self.getExtension(subtitle)
|
||||
self.downloadFile(suburl, srtfilename)
|
||||
return srtfilename
|
||||
|
||||
def getValue(self, sub, tagName):
|
||||
for node in sub.childNodes:
|
||||
if node.nodeType == node.ELEMENT_NODE and node.tagName == tagName:
|
||||
return node.childNodes[0].nodeValue
|
||||
@@ -22,49 +22,33 @@
|
||||
|
||||
from BeautifulSoup import BeautifulSoup
|
||||
import guessit
|
||||
import zipfile
|
||||
import urllib2
|
||||
import urllib
|
||||
import logging
|
||||
import traceback
|
||||
import httplib
|
||||
import unicodedata
|
||||
import re
|
||||
import PluginBase
|
||||
from subliminal import encodingKludge as ek
|
||||
from subliminal.classes import Subtitle
|
||||
|
||||
|
||||
class Subtitulos(PluginBase.PluginBase):
|
||||
site_url = 'http://www.subtitulos.es'
|
||||
site_name = 'Subtitulos'
|
||||
server_url = 'http://www.subtitulos.es'
|
||||
multi_languages_queries = True
|
||||
multi_filename_queries = False
|
||||
api_based = False
|
||||
_plugin_languages = {u"English (US)": "en",
|
||||
u"English (UK)": "en",
|
||||
u"English": "en",
|
||||
u"French": "fr",
|
||||
u"Brazilian": "pt-br",
|
||||
u"Portuguese": "pt",
|
||||
u"Español (Latinoamérica)": "es",
|
||||
u"Español (España)": "es",
|
||||
u"Español": "es",
|
||||
u"Italian": "it",
|
||||
u"Català": "ca"}
|
||||
_plugin_languages = {u'English (US)': 'en', u'English (UK)': 'en', u'English': 'en', u'French': 'fr', u'Brazilian': 'pt-br',
|
||||
u'Portuguese': 'pt', u'Español (Latinoamérica)': 'es', u'Español (España)': 'es', u'Español': 'es', u'Italian': 'it',
|
||||
u'Català': 'ca'}
|
||||
|
||||
def __init__(self, config_dict=None):
|
||||
super(Subtitulos, self).__init__(self._plugin_languages, config_dict, True)
|
||||
self.release_pattern = re.compile("Versión (.+) ([0-9]+).([0-9])+ megabytes")
|
||||
self.release_pattern = re.compile('Versión (.+) ([0-9]+).([0-9])+ megabytes')
|
||||
|
||||
def list(self, filenames, languages):
|
||||
"""Main method to call when you want to list subtitles"""
|
||||
# as self.multi_filename_queries is false, we won't have multiple filenames in the list so pick the only one
|
||||
# once multi-filename queries are implemented, set multi_filename_queries to true and manage a list of multiple filenames here
|
||||
if not self.checkLanguages(languages):
|
||||
def list(self, filepath, languages):
|
||||
possible_languages = self.possible_languages(languages)
|
||||
if not possible_languages:
|
||||
return []
|
||||
filepath = filenames[0]
|
||||
guess = guessit.guess_file_info(filepath, 'autodetect')
|
||||
if guess['type'] != 'episode':
|
||||
self.logger.debug(u'Not an episode')
|
||||
return []
|
||||
# add multiple things to the release group set
|
||||
release_group = set()
|
||||
@@ -76,75 +60,50 @@ class Subtitulos(PluginBase.PluginBase):
|
||||
if 'screenSize' in guess:
|
||||
release_group.add(guess['screenSize'].lower())
|
||||
if 'series' not in guess or len(release_group) == 0:
|
||||
self.logger.debug(u'Not enough information to proceed')
|
||||
return []
|
||||
self.release_group = release_group # used to sort results
|
||||
return self.query(guess['series'], guess['season'], guess['episodeNumber'], release_group, filepath, languages)
|
||||
return self.query(guess['series'], guess['season'], guess['episodeNumber'], release_group, filepath, possible_languages)
|
||||
|
||||
def query(self, name, season, episode, release_group, filepath, languages=None):
|
||||
"""Make a query and returns info about found subtitles"""
|
||||
def query(self, name, season, episode, release_group, filepath, languages):
|
||||
sublinks = []
|
||||
searchname = name.lower().replace(" ", "-")
|
||||
searchurl = "%s/%s/%sx%.2d" % (self.server_url, searchname, season, episode)
|
||||
self.logger.debug(u"Searching in %s" % searchurl)
|
||||
searchname = name.lower().replace(' ', '-')
|
||||
if isinstance(searchname, unicode):
|
||||
searchname = unicodedata.normalize('NFKD', searchname).encode('ascii','ignore')
|
||||
searchurl = '%s/%s/%sx%.2d' % (self.server_url, urllib2.quote(searchname), season, episode)
|
||||
self.logger.debug(u'Searching in %s' % searchurl)
|
||||
try:
|
||||
req = urllib2.Request(searchurl, headers={'User-Agent': self.user_agent})
|
||||
page = urllib2.urlopen(req, timeout=self.timeout)
|
||||
except urllib2.HTTPError as inst:
|
||||
self.logger.info(u"Error: %s - %s" % (searchurl, inst))
|
||||
self.logger.info(u'Error: %s - %s' % (searchurl, inst))
|
||||
return []
|
||||
except urllib2.URLError as inst:
|
||||
self.logger.info(u"TimeOut: %s" % inst)
|
||||
self.logger.info(u'TimeOut: %s' % inst)
|
||||
return []
|
||||
soup = BeautifulSoup(page.read())
|
||||
for subs in soup("div", {"id": "version"}):
|
||||
version = subs.find("p", {"class": "title-sub"})
|
||||
sub_teams = self.listTeams([self.release_pattern.search("%s" % version.contents[1]).group(1).lower()], [".", "_", " ", "/"])
|
||||
for subs in soup('div', {'id': 'version'}):
|
||||
version = subs.find('p', {'class': 'title-sub'})
|
||||
sub_teams = self.listTeams([self.release_pattern.search('%s' % version.contents[1]).group(1).lower()], ['.', '_', ' ', '/', '-'])
|
||||
self.logger.debug(u'Team from website: %s' % sub_teams)
|
||||
self.logger.debug(u'Team from file: %s' % release_group)
|
||||
if not release_group.intersection(sub_teams): # On wrong team
|
||||
continue
|
||||
self.logger.debug(u"Team from website: %s" % sub_teams)
|
||||
self.logger.debug(u"Team from file: %s" % release_group)
|
||||
for html_language in subs.findAllNext("ul", {"class": "sslist"}):
|
||||
sub_language = self.getRevertLanguage(html_language.findNext("li", {"class": "li-idioma"}).find("strong").contents[0].string.strip())
|
||||
if languages and not sub_language in languages: # On wrong language
|
||||
for html_language in subs.findAllNext('ul', {'class': 'sslist'}):
|
||||
sub_language = self.getRevertLanguage(html_language.findNext('li', {'class': 'li-idioma'}).find('strong').contents[0].string.strip())
|
||||
if not sub_language in languages: # On wrong language
|
||||
continue
|
||||
html_status = html_language.findNext("li", {"class": "li-estado green"})
|
||||
html_status = html_language.findNext('li', {'class': 'li-estado green'})
|
||||
sub_status = html_status.contents[0].string.strip()
|
||||
if not sub_status == 'Completado': # On not completed subtitles
|
||||
continue
|
||||
sub_link = html_status.findNext("span", {"class": "descargar green"}).find("a")["href"]
|
||||
result = {}
|
||||
result["release"] = "%s.S%.2dE%.2d.%s" % (name.replace(" ", "."), int(season), int(episode), '.'.join(sub_teams))
|
||||
result["lang"] = sub_language
|
||||
result["link"] = sub_link
|
||||
result["page"] = searchurl
|
||||
result["filename"] = filepath
|
||||
result["plugin"] = self.getClassName()
|
||||
result["teams"] = sub_teams # used to sort
|
||||
sub_link = html_status.findNext('span', {'class': 'descargar green'}).find('a')['href']
|
||||
result = Subtitle(filepath, self.getSubtitlePath(filepath, sub_language), self.__class__.__name__, sub_language, sub_link, teams=sub_teams)
|
||||
sublinks.append(result)
|
||||
sublinks.sort(self._cmpTeams)
|
||||
sublinks.sort(self._cmpReleaseGroup)
|
||||
return sublinks
|
||||
|
||||
def download(self, subtitle):
|
||||
"""
|
||||
Pass the URL of the sub and the file it matches, will unzip it
|
||||
and return the path to the created file
|
||||
"""
|
||||
suburl = subtitle["link"]
|
||||
videofilename = subtitle["filename"]
|
||||
srtbasefilename = videofilename.rsplit(".", 1)[0]
|
||||
srtfilename = srtbasefilename + ".srt"
|
||||
self.downloadFile(suburl, srtfilename)
|
||||
return srtfilename
|
||||
self.downloadFile(subtitle.link, subtitle.path)
|
||||
return subtitle
|
||||
|
||||
def downloadFile(self, url, filename):
|
||||
"""Downloads the given url to the given filename"""
|
||||
req = urllib2.Request(url, headers={'Referer': url, 'User-Agent': 'Mozilla/5.0 (X11; U; Linux x86_64; en-US; rv:1.9.1.3)'})
|
||||
f = urllib2.urlopen(req)
|
||||
dump = ek.ek(open, filename, "wb")
|
||||
dump.write(f.read())
|
||||
dump.close()
|
||||
f.close()
|
||||
|
||||
def _cmpTeams(self, x, y):
|
||||
"""Sort based on teams matching"""
|
||||
return -cmp(len(x['teams'].intersection(self.release_group)), len(y['teams'].intersection(self.release_group)))
|
||||
|
||||
@@ -24,51 +24,33 @@ import PluginBase
|
||||
import hashlib
|
||||
import os
|
||||
import urllib2
|
||||
from subliminal import encodingKludge as ek
|
||||
from subliminal.classes import Subtitle
|
||||
|
||||
|
||||
class TheSubDB(PluginBase.PluginBase):
|
||||
site_url = 'http://thesubdb.com'
|
||||
site_name = 'SubDB'
|
||||
server_url = 'http://api.thesubdb.com' # for testing purpose, use http://sandbox.thesubdb.com instead
|
||||
multi_languages_queries = True
|
||||
multi_filename_queries = False
|
||||
api_based = True
|
||||
user_agent = 'SubDB/1.0 (Subliminal/0.1; https://github.com/Diaoul/subliminal)' # defined by the API
|
||||
_plugin_languages = {'cs': 'cs', # the whole list is available with the API: http://sandbox.thesubdb.com/?action=languages
|
||||
'da': 'da',
|
||||
'de': 'de',
|
||||
'en': 'en',
|
||||
'fi': 'fi',
|
||||
'fr': 'fr',
|
||||
'hu': 'hu',
|
||||
'id': 'id',
|
||||
'it': 'it',
|
||||
'nl': 'nl',
|
||||
'no': 'no',
|
||||
'pl': 'pl',
|
||||
'pt': 'pt',
|
||||
'ro': 'ro',
|
||||
'ru': 'ru',
|
||||
'sl': 'sl',
|
||||
'sr': 'sr',
|
||||
'sv': 'sv',
|
||||
'tr': 'tr'}
|
||||
user_agent = 'SubDB/1.0 (Subliminal/0.4; https://github.com/Diaoul/subliminal)' # defined by the API
|
||||
_plugin_languages = {'af': 'af', 'cs': 'cs', 'da': 'da', 'de': 'de', 'en': 'en', 'es': 'es', 'fi': 'fi', 'fr': 'fr', 'hu': 'hu', 'id': 'id',
|
||||
'it': 'it', 'la': 'la', 'nl': 'nl', 'no': 'no', 'oc': 'oc', 'pl': 'pl', 'pt': 'pt', 'ro': 'ro', 'ru': 'ru', 'sl': 'sl', 'sr': 'sr',
|
||||
'sv': 'sv', 'tr': 'tr'} # list available with the API at http://sandbox.thesubdb.com/?action=languages
|
||||
|
||||
|
||||
def __init__(self, config_dict=None):
|
||||
super(TheSubDB, self).__init__(self._plugin_languages, config_dict)
|
||||
|
||||
def list(self, filenames, languages):
|
||||
"""Main method to call when you want to list subtitles"""
|
||||
# as self.multi_filename_queries is false, we won't have multiple filenames in the list so pick the only one
|
||||
# once multi-filename queries are implemented, set multi_filename_queries to true and manage a list of multiple filenames here
|
||||
filepath = filenames[0]
|
||||
if not ek.ek(os.path.isfile, filepath):
|
||||
def list(self, filepath, languages):
|
||||
possible_languages = self.possible_languages(languages)
|
||||
if not possible_languages:
|
||||
return []
|
||||
return self.query(filepath, self.hashFile(filepath), languages)
|
||||
if not os.path.isfile(filepath):
|
||||
return []
|
||||
return self.query(filepath, self.hashFile(filepath), possible_languages)
|
||||
|
||||
def query(self, filepath, moviehash, languages=None):
|
||||
searchurl = "%s/?action=%s&hash=%s" % (self.server_url, "search", moviehash)
|
||||
def query(self, filepath, moviehash, languages):
|
||||
searchurl = '%s/?action=%s&hash=%s' % (self.server_url, 'search', moviehash)
|
||||
self.logger.debug(u'Query URL: %s' % searchurl)
|
||||
try:
|
||||
req = urllib2.Request(searchurl, headers={'User-Agent': self.user_agent})
|
||||
@@ -76,50 +58,30 @@ class TheSubDB(PluginBase.PluginBase):
|
||||
except urllib2.HTTPError as inst:
|
||||
if inst.code == 404: # no result found
|
||||
return []
|
||||
self.logger.error(u"Error: %s - %s" % (searchurl, inst))
|
||||
self.logger.error(u'Error: %s - %s' % (searchurl, inst))
|
||||
return []
|
||||
except urllib2.URLError as inst:
|
||||
self.logger.error(u"TimeOut: %s" % inst)
|
||||
self.logger.error(u'TimeOut: %s' % inst)
|
||||
return []
|
||||
available_languages = page.readlines()[0].split(',')
|
||||
self.logger.debug(u'Available languages: %s' % available_languages)
|
||||
subs = []
|
||||
for l in available_languages:
|
||||
if not languages or l in languages:
|
||||
result = {}
|
||||
result['release'] = filepath
|
||||
result['lang'] = l
|
||||
result['link'] = "%s/?action=download&hash=%s&language=%s" % (self.server_url, moviehash, l)
|
||||
result['page'] = result['link']
|
||||
result['filename'] = filepath
|
||||
result['plugin'] = self.getClassName()
|
||||
if l in languages:
|
||||
result = Subtitle(filepath, self.getSubtitlePath(filepath, l), self.__class__.__name__, l, '%s/?action=download&hash=%s&language=%s' % (self.server_url, moviehash, l))
|
||||
subs.append(result)
|
||||
return subs
|
||||
|
||||
def hashFile(self, name):
|
||||
"""This hash function receives the filename and returns the hash code"""
|
||||
def hashFile(self, filepath):
|
||||
"""TheSubDB specific hash function"""
|
||||
readsize = 64 * 1024
|
||||
with ek.ek(open, name, 'rb') as f:
|
||||
size = ek.ek(os.path.getsize, name)
|
||||
with open(filepath, 'rb') as f:
|
||||
data = f.read(readsize)
|
||||
f.seek(-readsize, os.SEEK_END)
|
||||
data += f.read(readsize)
|
||||
return hashlib.md5(data).hexdigest()
|
||||
|
||||
def download(self, subtitle):
|
||||
"""Main method to call when you want to download a subtitle"""
|
||||
suburl = subtitle["link"]
|
||||
videofilename = subtitle["filename"]
|
||||
srtfilename = videofilename.rsplit(".", 1)[0] + self.getExtension(subtitle)
|
||||
self.downloadFile(suburl, srtfilename)
|
||||
return srtfilename
|
||||
self.downloadFile(subtitle.link, subtitle.path)
|
||||
return subtitle
|
||||
|
||||
def downloadFile(self, url, srtfilename):
|
||||
"""Downloads the given url to the given filename"""
|
||||
req = urllib2.Request(url)
|
||||
req.add_header('User-Agent', self.user_agent)
|
||||
f = urllib2.urlopen(req)
|
||||
dump = open(srtfilename, "wb")
|
||||
dump.write(f.read())
|
||||
dump.close()
|
||||
f.close()
|
||||
|
||||
@@ -20,12 +20,12 @@
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
from Addic7ed import Addic7ed
|
||||
#from Addic7ed import Addic7ed
|
||||
from BierDopje import BierDopje
|
||||
from OpenSubtitles import OpenSubtitles
|
||||
#from Podnapisi import Podnapisi
|
||||
#from SubScene import SubScene
|
||||
from SubsWiki import SubsWiki
|
||||
#from SubtitleSource import SubtitleSource
|
||||
from Subtitulos import Subtitulos
|
||||
from TheSubDB import TheSubDB
|
||||
|
||||
|
||||
Executable → Regular
+241
-343
@@ -19,409 +19,307 @@
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
import threading
|
||||
from itertools import groupby
|
||||
import ConfigParser
|
||||
import PluginWorker
|
||||
from classes import DownloadTask, ListTask, StopTask, LanguageError, PluginError, BadStateError, WrongTaskError, DownloadFailedError
|
||||
import Queue
|
||||
import locale
|
||||
import logging
|
||||
import mimetypes
|
||||
import os
|
||||
import plugins
|
||||
import sys
|
||||
import traceback
|
||||
import locale
|
||||
import encodingKludge as ek
|
||||
|
||||
|
||||
SUPPORTED_FORMATS = 'video/x-msvideo', 'video/quicktime', 'video/x-matroska', 'video/mp4'
|
||||
logger = logging.getLogger('subliminal')
|
||||
SYS_ENCODING = None
|
||||
# be nice
|
||||
try:
|
||||
locale.setlocale(locale.LC_ALL, "")
|
||||
SYS_ENCODING = locale.getpreferredencoding()
|
||||
except (locale.Error, IOError):
|
||||
pass
|
||||
# for OSes that are poorly configured I'll just force UTF-8
|
||||
if not SYS_ENCODING or SYS_ENCODING in ('ANSI_X3.4-1968', 'US-ASCII', 'ASCII'):
|
||||
SYS_ENCODING = 'UTF-8'
|
||||
from logging import NullHandler
|
||||
except ImportError:
|
||||
|
||||
class NullHandler(logging.Handler):
|
||||
def emit(self, record):
|
||||
pass
|
||||
logger = logging.getLogger('subliminal')
|
||||
logger.addHandler(NullHandler())
|
||||
|
||||
# const
|
||||
FORMATS = ['video/x-msvideo', 'video/quicktime', 'video/x-matroska', 'video/mp4']
|
||||
EXTENSIONS = set(['srt', 'sub'])
|
||||
LANGUAGES = set(['aa', 'ab', 'ae', 'af', 'ak', 'am', 'an', 'ar', 'as', 'av', 'ay', 'az', 'ba', 'be', 'bg', 'bh', 'bi',
|
||||
'bm', 'bn', 'bo', 'br', 'bs', 'ca', 'ce', 'ch', 'co', 'cr', 'cs', 'cu', 'cv', 'cy', 'da', 'de', 'dv',
|
||||
'dz', 'ee', 'el', 'en', 'eo', 'es', 'et', 'eu', 'fa', 'ff', 'fi', 'fj', 'fo', 'fr', 'fy', 'ga', 'gd',
|
||||
'gl', 'gn', 'gu', 'gv', 'ha', 'he', 'hi', 'ho', 'hr', 'ht', 'hu', 'hy', 'hz', 'ia', 'id', 'ie', 'ig',
|
||||
'ii', 'ik', 'io', 'is', 'it', 'iu', 'ja', 'jv', 'ka', 'kg', 'ki', 'kj', 'kk', 'kl', 'km', 'kn', 'ko',
|
||||
'kr', 'ks', 'ku', 'kv', 'kw', 'ky', 'la', 'lb', 'lg', 'li', 'ln', 'lo', 'lt', 'lu', 'lv', 'mg', 'mh',
|
||||
'mi', 'mk', 'ml', 'mn', 'mo', 'mr', 'ms', 'mt', 'my', 'na', 'nb', 'nd', 'ne', 'ng', 'nl', 'nn', 'no',
|
||||
'nr', 'nv', 'ny', 'oc', 'oj', 'om', 'or', 'os', 'pa', 'pi', 'pl', 'ps', 'pt', 'qu', 'rm', 'rn', 'ro',
|
||||
'ru', 'rw', 'sa', 'sc', 'sd', 'se', 'sg', 'si', 'sk', 'sl', 'sm', 'sn', 'so', 'sq', 'sr', 'ss', 'st',
|
||||
'su', 'sv', 'sw', 'ta', 'te', 'tg', 'th', 'ti', 'tk', 'tl', 'tn', 'to', 'tr', 'ts', 'tt', 'tw', 'ty',
|
||||
'ug', 'uk', 'ur', 'uz', 've', 'vi', 'vo', 'wa', 'wo', 'xh', 'yi', 'yo', 'za', 'zh', 'zu']) # ISO 639-1
|
||||
PLUGINS = ['BierDopje', 'OpenSubtitles', 'SubsWiki', 'Subtitulos', 'TheSubDB']
|
||||
API_PLUGINS = filter(lambda p: getattr(plugins, p).api_based, PLUGINS)
|
||||
IDLE = 0
|
||||
RUNNING = 1
|
||||
PAUSED = 2
|
||||
|
||||
|
||||
class Subliminal(object):
|
||||
"""Main Subliminal class"""
|
||||
|
||||
def __init__(self, config=True, cache_dir=True, workers=4, multi=False, force=False, max_depth=3, autostart=False, plugins_config=None, files_mode=-1):
|
||||
# set default values
|
||||
def __init__(self, cache_dir=None, workers=4, multi=False, force=False, max_depth=3, files_mode=-1):
|
||||
self.multi = multi
|
||||
self.force = force
|
||||
self.max_depth = max_depth
|
||||
self.config = None
|
||||
self.config_file = None
|
||||
self.cache_dir = None
|
||||
self.taskQueue = Queue.Queue()
|
||||
self.resultQueue = Queue.Queue()
|
||||
self._languages = None
|
||||
self._plugins = self.listAPIPlugins()
|
||||
self.taskQueue = Queue.PriorityQueue()
|
||||
self.listResultQueue = Queue.Queue()
|
||||
self.downloadResultQueue = Queue.Queue()
|
||||
self._languages = []
|
||||
self._plugins = API_PLUGINS
|
||||
self.workers = workers
|
||||
self.plugins_config = plugins_config
|
||||
self.files_mode = files_mode
|
||||
if autostart:
|
||||
self.startWorkers()
|
||||
# handle configuration file preferences
|
||||
self.state = IDLE
|
||||
try:
|
||||
if config == True: # default configuration file
|
||||
import xdg.BaseDirectory as bd
|
||||
self.config = ConfigParser.SafeConfigParser({"languages": "", "plugins": ""})
|
||||
self.config_file = ek.ek(os.path.join, bd.xdg_config_home, "subliminal", "config.ini")
|
||||
if not ek.ek(os.path.exists, self.config_file): # configuration file doesn't exist, create it
|
||||
self._createConfigFile()
|
||||
else: # configuration file exists, load it
|
||||
self._loadConfigFile()
|
||||
elif config: # custom configuration file
|
||||
self.config = ConfigParser.SafeConfigParser({"languages": "", "plugins": ""})
|
||||
self.config_file = config
|
||||
if not ek.ek(os.path.isfile, self.config_file): # custom configuration file doesn't exist, create it
|
||||
self._createConfigFile()
|
||||
else:
|
||||
self._loadConfigFile()
|
||||
except:
|
||||
self.config = None
|
||||
self.config_file = None
|
||||
logger.error(u"Failed to use the configuration file, continue without it")
|
||||
raise
|
||||
# handle cache directory preferences
|
||||
try:
|
||||
if cache_dir == True: # default cache directory
|
||||
import xdg.BaseDirectory as bd
|
||||
self.cache_dir = ek.ek(os.path.join, bd.xdg_config_home, "subliminal", "cache")
|
||||
if not ek.ek(os.path.exists, self.cache_dir): # cache directory doesn't exist, create it
|
||||
ek.ek(os.mkdir, self.cache_dir)
|
||||
logger.debug(u'Creating cache directory: %s' % self.cache_dir)
|
||||
elif cache_dir: # custom configuration file
|
||||
if cache_dir:
|
||||
self.cache_dir = cache_dir
|
||||
if not ek.ek(os.path.isdir, self.cache_dir): # custom v file doesn't exist, create it
|
||||
ek.ek(os.mkdir, self.cache_dir)
|
||||
logger.debug(u'Creating cache directory: %s' % self.cache_dir)
|
||||
if not os.path.isdir(self.cache_dir):
|
||||
os.makedirs(self.cache_dir)
|
||||
logger.debug(u'Creating cache directory: %r' % self.cache_dir)
|
||||
except:
|
||||
self.cache_dir = None
|
||||
logger.error(u"Failed to use the cache directory, continue without it")
|
||||
logger.error(u'Failed to use the cache directory, continue without it')
|
||||
|
||||
def _loadConfigFile(self):
|
||||
"""Load a configuration file specified in self.config_file"""
|
||||
self.config.read(self.config_file)
|
||||
self._loadLanguagesFromConfig()
|
||||
self._loadPluginsFromConfig()
|
||||
|
||||
def _createConfigFile(self):
|
||||
"""Create a configuration file specified in self.config_file"""
|
||||
folder = ek.ek(os.path.dirname, self.config_file)
|
||||
if not ek.ek(os.path.exists, folder):
|
||||
logger.info(u"Creating folder: %s" % folder)
|
||||
ek.ek(os.mkdir, folder)
|
||||
# try to load a language from system
|
||||
self._loadLanguageFromSystem()
|
||||
self.config.set("DEFAULT", "languages", ",".join(self._languages))
|
||||
self.config.set("DEFAULT", "plugins", ",".join(self._plugins))
|
||||
self.config.add_section("SubtitleSource")
|
||||
self.config.set("SubtitleSource", "key", "")
|
||||
self._writeConfigFile()
|
||||
logger.info(u"Creating configuration file: %s" % self.config_file)
|
||||
logger.debug(u"Languages in created configuration file: %s" % self._languages)
|
||||
logger.debug(u"Plugins in created configuration file: %s" % self._plugins)
|
||||
|
||||
@staticmethod
|
||||
def listExistingPlugins():
|
||||
"""List all possible plugins"""
|
||||
return map(lambda x: x.__name__, plugins.PluginBase.PluginBase.__subclasses__())
|
||||
|
||||
@staticmethod
|
||||
def listAPIPlugins():
|
||||
"""List plugins that use API"""
|
||||
return filter(Subliminal.isAPIBasedPlugin, Subliminal.listExistingPlugins())
|
||||
|
||||
def _writeConfigFile(self):
|
||||
"""Write the configuration file"""
|
||||
configfile = open(self.config_file, "w")
|
||||
self.config.write(configfile)
|
||||
configfile.close()
|
||||
|
||||
def get_languages(self):
|
||||
"""Get current languages"""
|
||||
@property
|
||||
def languages(self):
|
||||
"""Getter for languages"""
|
||||
return self._languages
|
||||
|
||||
def set_languages(self, value):
|
||||
"""Set languages and save to configuration file if specified by the constructor"""
|
||||
logger.debug(u"Setting languages to %s" % value)
|
||||
self._languages = value
|
||||
if self.config:
|
||||
self._saveLanguagesToConfig()
|
||||
@languages.setter
|
||||
def languages(self, languages):
|
||||
"""Setter for languages"""
|
||||
logger.debug(u'Setting languages to %r' % languages)
|
||||
self._languages = []
|
||||
for l in languages:
|
||||
if l not in LANGUAGES:
|
||||
raise LanguageError(l)
|
||||
if not l in self._languages:
|
||||
self._languages.append(l)
|
||||
|
||||
@staticmethod
|
||||
def isValidLanguage(language):
|
||||
"""Check if a language is valid"""
|
||||
if len(language) != 2:
|
||||
logger.error(u"Language %s is not valid" % language)
|
||||
return False
|
||||
return True
|
||||
|
||||
def _saveLanguagesToConfig(self):
|
||||
"""Save languages to configuration file"""
|
||||
logger.debug(u"Saving languages %s to configuration file" % self._languages)
|
||||
self.config.set("DEFAULT", "languages", ",".join(self._languages))
|
||||
self._writeConfigFile()
|
||||
|
||||
def _loadLanguagesFromConfig(self):
|
||||
"""Load languages from configuration file"""
|
||||
configLanguages = self.config.get("DEFAULT", "languages")
|
||||
logger.debug(u"Loading languages %s from configuration file" % configLanguages)
|
||||
if not configLanguages:
|
||||
self._languages = None
|
||||
return
|
||||
self._languages = filter(self.isValidLanguage, map(str.strip, configLanguages.split(",")))
|
||||
|
||||
def _loadLanguageFromSystem(self):
|
||||
"""Load language from system"""
|
||||
logger.debug(u"Loading language from system")
|
||||
try:
|
||||
self._languages = [locale.getdefaultlocale()[0][:2]]
|
||||
logger.debug(u"Language %s loaded from system" % self._languages)
|
||||
except:
|
||||
logger.warning(u"Could not read language from system")
|
||||
|
||||
def get_plugins(self):
|
||||
"""Get current plugins"""
|
||||
@property
|
||||
def plugins(self):
|
||||
"""Getter for plugins"""
|
||||
return self._plugins
|
||||
|
||||
def set_plugins(self, value):
|
||||
"""Set plugins and save to configuration file if specified by the constructor"""
|
||||
logger.debug(u'Setting plugins to %r' % value)
|
||||
self._plugins = filter(self.isValidPlugin, value)
|
||||
if self.config:
|
||||
self._savePluginsToConfig()
|
||||
@plugins.setter
|
||||
def plugins(self, plugins):
|
||||
"""Setter for plugins"""
|
||||
logger.debug(u'Setting plugins to %r' % plugins)
|
||||
self._plugins = []
|
||||
for p in plugins:
|
||||
if p not in PLUGINS:
|
||||
raise PluginError(p)
|
||||
if not p in self._plugins:
|
||||
self._plugins.append(p)
|
||||
|
||||
@staticmethod
|
||||
def isValidPlugin(pluginName):
|
||||
"""Check if a plugin is valid (exists)"""
|
||||
if pluginName not in Subliminal.listExistingPlugins():
|
||||
logger.error(u"Plugin %s does not exist" % pluginName)
|
||||
return False
|
||||
return True
|
||||
|
||||
@staticmethod
|
||||
def isAPIBasedPlugin(pluginName):
|
||||
"""Check if a plugin is API-based"""
|
||||
if not getattr(plugins, pluginName).api_based:
|
||||
return False
|
||||
return True
|
||||
|
||||
def _savePluginsToConfig(self):
|
||||
"""Save plugins to configuration file"""
|
||||
logger.debug(u"Saving plugins %s to configuration file" % self._plugins)
|
||||
self.config.set("DEFAULT", "plugins", ",".join(self._plugins))
|
||||
self._writeConfigFile()
|
||||
|
||||
def _loadPluginsFromConfig(self):
|
||||
"""Load plugins from configuration file"""
|
||||
configPlugins = self.config.get("DEFAULT", "plugins")
|
||||
logger.debug(u"Loading plugins %s from configuration file" % configPlugins)
|
||||
self._plugins = filter(self.isValidPlugin, map(str.strip, configPlugins.split(",")))
|
||||
|
||||
# getters/setters for the property _languages and _plugins
|
||||
languages = property(get_languages, set_languages)
|
||||
plugins = property(get_plugins, set_plugins)
|
||||
|
||||
def deactivatePlugin(self, plugin):
|
||||
"""Deactivate a plugin"""
|
||||
self._plugins.remove(plugin)
|
||||
if self.config:
|
||||
self._savePluginsToConfig()
|
||||
|
||||
def activatePlugin(self, plugin):
|
||||
"""Activate a plugin"""
|
||||
if self.isValidPlugin(plugin):
|
||||
self._plugins.append(plugin)
|
||||
if self.config:
|
||||
self._savePluginsToConfig()
|
||||
|
||||
def listSubtitles(self, entries):
|
||||
def listSubtitles(self, entries, auto=True):
|
||||
"""
|
||||
Searches subtitles within the active plugins and returns all found matching subtitles.
|
||||
entries can be:
|
||||
- filepaths
|
||||
- folderpaths (N.B. internal recursive search function will be used)
|
||||
- filenames
|
||||
"""
|
||||
search_results = []
|
||||
Search subtitles within the plugins and return all found subtitles in a list of Subtitle object.
|
||||
|
||||
Attributes:
|
||||
entries -- filepath or folderpath of video file or a list of that
|
||||
auto -- automaticaly manage workers"""
|
||||
if auto:
|
||||
if self.state != IDLE:
|
||||
raise BadStateError(self.state, IDLE)
|
||||
self.startWorkers()
|
||||
if isinstance(entries, basestring):
|
||||
entries = [ek.fixStupidEncodings(entries)]
|
||||
elif not isinstance(entries, list):
|
||||
raise TypeError('Entries should be a list or a string')
|
||||
entries = [entries]
|
||||
scan_result = []
|
||||
for e in entries:
|
||||
search_results.extend(self._recursiveSearch(e))
|
||||
taskCount = 0
|
||||
for (l, f) in search_results:
|
||||
taskCount += self.searchSubtitlesThreaded(f, l)
|
||||
if not isinstance(e, unicode):
|
||||
logger.warning(u'Entry %r is not unicode' % e)
|
||||
if not os.path.exists(e):
|
||||
scan_result.append((e, set(), False))
|
||||
continue
|
||||
scan_result.extend(scan(e))
|
||||
task_count = 0
|
||||
for filepath, languages, has_single in scan_result:
|
||||
wanted_languages = set(self._languages)
|
||||
if not wanted_languages:
|
||||
wanted_languages = LANGUAGES
|
||||
if not self.force and self.multi:
|
||||
wanted_languages = set(wanted_languages) - languages
|
||||
if not wanted_languages:
|
||||
logger.debug(u'No need to list multi subtitles %r for %r because %r subtitles detected' % (self._languages, filepath, languages))
|
||||
continue
|
||||
if not self.force and not self.multi and has_single:
|
||||
logger.debug(u'No need to list single subtitles %r for %r because one detected' % (self._languages, filepath))
|
||||
continue
|
||||
logger.debug(u'Listing subtitles %r for %r with %r' % (wanted_languages, filepath, self._plugins))
|
||||
for plugin in self._plugins:
|
||||
self.taskQueue.put((5, ListTask(filepath, wanted_languages, plugin, self.getConfigDict())))
|
||||
task_count += 1
|
||||
subtitles = []
|
||||
for i in range(taskCount):
|
||||
subtitles.extend(self.resultQueue.get(timeout=10))
|
||||
for _ in range(task_count):
|
||||
subtitles.extend(self.listResultQueue.get())
|
||||
if auto:
|
||||
self.stopWorkers()
|
||||
return subtitles
|
||||
|
||||
@staticmethod
|
||||
def arrangeSubtitles(subtitles):
|
||||
"""Arrange subtitles in a handy dict by filename, language and plugin"""
|
||||
arrangedSubtitles = {}
|
||||
for (filename, subsByFilename) in groupby(sorted(subtitles, key=lambda x: x["filename"]), lambda x: x["filename"]):
|
||||
arrangedSubtitles[filename] = {}
|
||||
for (language, subsByFilenameByLanguage) in groupby(sorted(subsByFilename, key=lambda x: x["lang"]), lambda x: x["lang"]):
|
||||
arrangedSubtitles[filename][language] = {}
|
||||
for (plugin, subsByFilenameByLanguageByPlugin) in groupby(sorted(subsByFilenameByLanguage, key=lambda x: x["plugin"]), lambda x: x["plugin"]):
|
||||
arrangedSubtitles[filename][language][plugin] = sorted(list(subsByFilenameByLanguageByPlugin))
|
||||
return arrangedSubtitles
|
||||
|
||||
def sortSubtitlesRaw(self, subtitles):
|
||||
"""Sort subtitles using user defined languages and plugins"""
|
||||
return sorted(subtitles, cmp=self._cmpSubtitles)
|
||||
|
||||
def _cmpSubtitles(self, x, y):
|
||||
def downloadSubtitles(self, entries, auto=True):
|
||||
"""
|
||||
Compares 2 subtitles elements x and y. Returns -1 if x < y, 0 if =, 1 if >
|
||||
Use filename, languages and plugin comparison
|
||||
"""
|
||||
filenames = sorted([x['filename'], y['filename']])
|
||||
if x['filename'] != y['filename'] and filenames.index(x['filename']) < filenames(y['filename']):
|
||||
Download subtitles using the plugins preferences and languages. Also use internal algorithm to find
|
||||
the best match inside a plugin.
|
||||
|
||||
Attributes:
|
||||
entries -- filepath or folderpath of video file or a list of that
|
||||
auto -- automaticaly manage workers"""
|
||||
if auto:
|
||||
if self.state != IDLE:
|
||||
raise BadStateError(self.state, IDLE)
|
||||
self.startWorkers()
|
||||
subtitles = self.listSubtitles(entries, False)
|
||||
task_count = 0
|
||||
for _, subsByVideoPath in groupby(sorted(subtitles, key=lambda x: x.video_path), lambda x: x.video_path):
|
||||
if not self.multi:
|
||||
self.taskQueue.put((5, DownloadTask(sorted(list(subsByVideoPath), cmp=self.cmpSubtitles))))
|
||||
task_count += 1
|
||||
continue
|
||||
for __, subsByVideoPathByLanguage in groupby(sorted(subsByVideoPath, key=lambda x: x.language), lambda x: x.language):
|
||||
self.taskQueue.put((5, DownloadTask(sorted(list(subsByVideoPathByLanguage), cmp=self.cmpSubtitles))))
|
||||
task_count += 1
|
||||
downloaded = []
|
||||
for _ in range(task_count):
|
||||
downloaded.extend(self.downloadResultQueue.get())
|
||||
if auto:
|
||||
self.stopWorkers()
|
||||
return downloaded
|
||||
|
||||
def cmpSubtitles(self, x, y):
|
||||
"""Compares 2 subtitles elements x and y using video_path, languages and plugin"""
|
||||
video_paths = sorted([x.video_path, y.video_path])
|
||||
if x.video_path != y.video_path and video_paths.index(x.video_path) < video_paths(y.video_path):
|
||||
return - 1
|
||||
if x['filename'] != y['filename'] and filenames.index(x['filename']) > filenames(y['filename']):
|
||||
if x.video_path != y.video_path and video_paths.index(x.video_path) > video_paths(y.video_path):
|
||||
return 1
|
||||
if self._languages and self._languages.index(x['lang']) < self._languages.index(y['lang']):
|
||||
if self._languages and self._languages.index(x.language) < self._languages.index(y.language):
|
||||
return - 1
|
||||
if self._languages and self._languages.index(x['lang']) > self._languages.index(y['lang']):
|
||||
if self._languages and self._languages.index(x.language) > self._languages.index(y.language):
|
||||
return 1
|
||||
if self._plugins.index(x['plugin']) < self._plugins.index(y['plugin']):
|
||||
if self._plugins.index(x.plugin) < self._plugins.index(y.plugin):
|
||||
return - 1
|
||||
if self._plugins.index(x['plugin']) > self._plugins.index(y['plugin']):
|
||||
if self._plugins.index(x.plugin) > self._plugins.index(y.plugin):
|
||||
return 1
|
||||
return 0
|
||||
|
||||
def searchSubtitlesThreaded(self, filenames, languages):
|
||||
"""
|
||||
Makes workers search for subtitles in different languages for multiple filenames and puts the result in the result queue.
|
||||
Aslo split the work in multiple tasks
|
||||
When the function returns, all the results may not be available yet!
|
||||
"""
|
||||
logger.info(u"Searching subtitles for %s with languages %s" % (filenames, languages))
|
||||
tasks = []
|
||||
for pluginName in self._plugins:
|
||||
try:
|
||||
plugin = getattr(plugins, pluginName)(self.getConfigDict())
|
||||
except:
|
||||
logger.debug(traceback.print_exc())
|
||||
continue
|
||||
# split tasks if the plugin can't handle multi-thing queries
|
||||
tasks.extend(plugin.splitTask({'task': 'list', 'plugin': pluginName, 'languages': languages, 'filenames': filenames, 'config': self.getConfigDict()}))
|
||||
for t in tasks:
|
||||
self.taskQueue.put(t)
|
||||
return len(tasks)
|
||||
|
||||
def downloadSubtitlesThreaded(self, subtitles):
|
||||
"""
|
||||
Makes workers download subtitles and puts the result in the result queue.
|
||||
When the function returns, all the results may not be available yet!
|
||||
"""
|
||||
# 1 task per file if not multi, 1 task per file and per language if multi
|
||||
taskCount = 0
|
||||
for (filename, subsByFilename) in groupby(sorted(subtitles, key=lambda x: x["filename"]), lambda x: x["filename"]):
|
||||
if not self.multi:
|
||||
self.taskQueue.put({'task': 'download', 'subtitle': sorted(list(subsByFilename), cmp=self._cmpSubtitles), 'config': self.getConfigDict()})
|
||||
taskCount += 1
|
||||
continue
|
||||
for (language, subsByFilenameByLanguage) in groupby(sorted(subsByFilename, key=lambda x: x["lang"]), lambda x: x["lang"]):
|
||||
self.taskQueue.put({'task': 'download', 'subtitle': sorted(list(subsByFilenameByLanguage), cmp=self._cmpSubtitles), 'config': self.getConfigDict()})
|
||||
taskCount += 1
|
||||
return taskCount
|
||||
|
||||
def downloadSubtitles(self, entries):
|
||||
"""Download subtitles recursivly in entries"""
|
||||
subtitles = self.listSubtitles(entries)
|
||||
taskCount = self.downloadSubtitlesThreaded(subtitles)
|
||||
paths = []
|
||||
for i in range(taskCount):
|
||||
paths.append(self.resultQueue.get(timeout=10))
|
||||
return paths
|
||||
|
||||
def _recursiveSearch(self, entry, depth=0):
|
||||
"""
|
||||
Searches files in the entry
|
||||
This will output a list of tuples (filename, languages)
|
||||
"""
|
||||
if depth > self.max_depth and self.max_depth != 0: # we do not want to search the whole file system except if max_depth = 0
|
||||
return []
|
||||
if ek.ek(os.path.isfile, entry): # a file? scan it
|
||||
if depth != 0: # only check for valid format if recursing, trust the user
|
||||
mimetypes.add_type("video/x-matroska", ".mkv")
|
||||
mimetype = mimetypes.guess_type(entry)[0]
|
||||
if mimetype not in SUPPORTED_FORMATS:
|
||||
return []
|
||||
basepath = ek.fixStupidEncodings(ek.ek(os.path.splitext, entry)[0])
|
||||
# check for .xx.srt if needed
|
||||
if self.multi and self.languages:
|
||||
if self.force:
|
||||
return [(self.languages, [ek.ek(os.path.normpath, entry)])]
|
||||
needed_languages = self.languages[:]
|
||||
for l in self.languages:
|
||||
if ek.ek(os.path.exists, basepath + '.%s.srt' % l):
|
||||
logger.info(u"Skipping language %s for file %s as it already exists. Use the --force option to force the download" % (l, entry))
|
||||
needed_languages.remove(l)
|
||||
if needed_languages:
|
||||
return [(needed_languages, [ek.ek(os.path.normpath, entry)])]
|
||||
return []
|
||||
# single subtitle download: .srt
|
||||
if self.force or not ek.ek(os.path.exists, basepath + '.srt'):
|
||||
return [(self.languages, [ek.ek(os.path.normpath, entry)])]
|
||||
if ek.ek(os.path.isdir, entry): # a dir? recurse
|
||||
#TODO if hidden folder, don't keep going (how to handle windows/mac/linux ?)
|
||||
files = []
|
||||
for e in ek.ek(os.listdir, entry):
|
||||
files.extend(self._recursiveSearch(ek.ek(os.path.join, entry, e), depth + 1))
|
||||
files.sort()
|
||||
grouped_files = []
|
||||
for languages, group in groupby(files, lambda t: t[0]):
|
||||
filenames = []
|
||||
for t in group:
|
||||
filenames.extend(t[1])
|
||||
grouped_files.append((languages, filenames))
|
||||
return grouped_files
|
||||
return [] # anything else, nothing.
|
||||
|
||||
def startWorkers(self):
|
||||
"""Create a pool of workers and start them"""
|
||||
self.pool = []
|
||||
for i in range(self.workers):
|
||||
worker = PluginWorker.PluginWorker(self.taskQueue, self.resultQueue)
|
||||
for _ in range(self.workers):
|
||||
worker = PluginWorker(self.taskQueue, self.listResultQueue, self.downloadResultQueue)
|
||||
worker.start()
|
||||
self.pool.append(worker)
|
||||
logger.debug(u"Worker %s added to the pool" % worker.name)
|
||||
|
||||
def sendStopSignal(self):
|
||||
"""Send a stop signal the pool of workers (poison pill)"""
|
||||
logger.debug(u"Sending %d poison pills into the task queue" % self.workers)
|
||||
for i in range(self.workers):
|
||||
self.taskQueue.put(None)
|
||||
logger.debug(u'Worker %s added to the pool' % worker.name)
|
||||
self.state = RUNNING
|
||||
|
||||
def stopWorkers(self):
|
||||
"""Stop workers using a stop signal and wait for them to terminate properly"""
|
||||
self.sendStopSignal()
|
||||
"""Stop workers using a lowest priority stop signal and wait for them to terminate properly"""
|
||||
for _ in range(self.workers):
|
||||
self.taskQueue.put((10, StopTask()))
|
||||
for worker in self.pool:
|
||||
worker.join()
|
||||
self.state = IDLE
|
||||
|
||||
def pauseWorkers(self):
|
||||
"""Pause workers using a highest priority stop signal and wait for them to terminate properly"""
|
||||
for _ in range(self.workers):
|
||||
self.taskQueue.put((0, StopTask()))
|
||||
for worker in self.pool:
|
||||
worker.join()
|
||||
self.state = PAUSED
|
||||
if self.taskQueue.empty():
|
||||
self.state = STOPPED
|
||||
|
||||
def getConfigDict(self):
|
||||
"""Produce a dict with configuration items. Used by plugins to read configuration"""
|
||||
config = {}
|
||||
config['multi'] = self.multi
|
||||
config['cache_dir'] = self.cache_dir
|
||||
if self.config:
|
||||
config['subtitlesource_key'] = self.config.get('SubtitleSource', 'key')
|
||||
if self.plugins_config and 'subtitlesource_key' in self.plugins_config:
|
||||
config['subtitlesource_key'] = self.plugins_config['subtitlesource_key']
|
||||
config['force'] = self.force
|
||||
config['files_mode'] = self.files_mode
|
||||
return config
|
||||
|
||||
def addTask(self, task):
|
||||
if not isinstance(task, Task) or isinstance(task, StopTask):
|
||||
raise WrongTaskError()
|
||||
self.taskQueue.put((5, task))
|
||||
|
||||
|
||||
class PluginWorker(threading.Thread):
|
||||
"""Threaded plugin worker"""
|
||||
def __init__(self, taskQueue, listResultQueue, downloadResultQueue):
|
||||
threading.Thread.__init__(self)
|
||||
self.taskQueue = taskQueue
|
||||
self.listResultQueue = listResultQueue
|
||||
self.downloadResultQueue = downloadResultQueue
|
||||
self.logger = logging.getLogger('subliminal.worker')
|
||||
|
||||
def run(self):
|
||||
while True:
|
||||
task = self.taskQueue.get()[1]
|
||||
if isinstance(task, StopTask):
|
||||
self.logger.debug(u'Poison pill received, terminating thread %s' % self.name)
|
||||
self.taskQueue.task_done()
|
||||
break
|
||||
result = []
|
||||
try:
|
||||
if isinstance(task, ListTask):
|
||||
plugin = getattr(plugins, task.plugin)(task.config)
|
||||
result = plugin.list(task.filepath, task.languages)
|
||||
elif isinstance(task, DownloadTask):
|
||||
for subtitle in task.subtitles:
|
||||
plugin = getattr(plugins, subtitle.plugin)()
|
||||
try:
|
||||
result = [plugin.download(subtitle)]
|
||||
break
|
||||
except DownloadFailedError as e:
|
||||
self.logger.warning(u'Could not download subtitle %r, trying next' % subtitle)
|
||||
continue
|
||||
if not result:
|
||||
self.logger.error(u'No subtitles could be downloaded for file %r' % subtitle.video_path)
|
||||
except:
|
||||
self.logger.error(u'Exception raised in worker %s' % self.name, exc_info=True)
|
||||
finally:
|
||||
if isinstance(task, ListTask):
|
||||
self.listResultQueue.put(result)
|
||||
elif isinstance(task, DownloadTask):
|
||||
self.downloadResultQueue.put(result)
|
||||
self.taskQueue.task_done()
|
||||
self.logger.debug(u'Thread %s terminated' % self.name)
|
||||
|
||||
|
||||
def scan(entry, depth=0, max_depth=3):
|
||||
"""Scan a path and return a list of tuples (filepath, set(languages), has single)"""
|
||||
if depth > max_depth and max_depth != 0: # we do not want to search the whole file system except if max_depth = 0
|
||||
return []
|
||||
if depth == 0:
|
||||
entry = os.path.abspath(entry)
|
||||
if os.path.isfile(entry): # a file? scan it
|
||||
if depth != 0: # trust the user: only check for valid format if recursing
|
||||
mimetypes.add_type('video/x-matroska', '.mkv')
|
||||
if mimetypes.guess_type(entry)[0] not in FORMATS:
|
||||
return []
|
||||
# check for .lg.ext and .ext
|
||||
available_languages = set()
|
||||
has_single = False
|
||||
basepath = os.path.splitext(entry)[0]
|
||||
for l in LANGUAGES:
|
||||
for e in EXTENSIONS:
|
||||
if os.path.exists(basepath + '.%s.%s' % (l, e)):
|
||||
available_languages.add(l)
|
||||
if os.path.exists(basepath + '.%s' % e):
|
||||
has_single = True
|
||||
return [(os.path.normpath(entry), available_languages, has_single)]
|
||||
if os.path.isdir(entry): # a dir? recurse
|
||||
result = []
|
||||
for e in os.listdir(entry):
|
||||
result.extend(scan(os.path.join(entry, e), depth + 1))
|
||||
return result
|
||||
return [] # anything else
|
||||
|
||||
@@ -1,206 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Subliminal - Subtitles, faster than your thoughts
|
||||
# Copyright (c) 2008-2011 Patrick Dessalle <patrick@dessalle.be>
|
||||
# Copyright (c) 2011 Antoine Bertin <diaoulael@gmail.com>
|
||||
#
|
||||
# This file is part of Subliminal.
|
||||
#
|
||||
# Subliminal is free software; you can redistribute it and/or modify it under
|
||||
# the terms of the Lesser GNU General Public License as published by
|
||||
# the Free Software Foundation; either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Subliminal 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
|
||||
# Lesser GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the Lesser GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
import unittest
|
||||
import logging
|
||||
import os
|
||||
|
||||
|
||||
logging.basicConfig(level=logging.DEBUG, format='%(name)-24s %(levelname)-8s %(message)s')
|
||||
if not os.path.exists('/tmp/subliminal/cache'):
|
||||
os.mkdir('/tmp/subliminal/cache')
|
||||
config = {'multi': True, 'cache_dir': '/tmp/subliminal/cache', 'subtitlesource_key': '', 'force': False}
|
||||
|
||||
|
||||
class Addic7edListTestCase1(unittest.TestCase):
|
||||
def runTest(self):
|
||||
from subliminal.plugins import Addic7ed
|
||||
plugin = Addic7ed(config)
|
||||
results = plugin.list(["The.Big.Bang.Theory.S03E13.HDTV.XviD-2HD.mkv"], ["en", "fr"])
|
||||
print results
|
||||
assert len(results) > 0
|
||||
|
||||
|
||||
class Addic7edListTestCase2(unittest.TestCase):
|
||||
def runTest(self):
|
||||
from subliminal.plugins import Addic7ed
|
||||
plugin = Addic7ed(config)
|
||||
results = plugin.list(["Dexter.S05E02.720p.HDTV.x264-IMMERSE.mkv"], ["en", "fr"])
|
||||
print results
|
||||
assert len(results) > 0
|
||||
|
||||
|
||||
class Addic7edDownloadTestCase(unittest.TestCase):
|
||||
def runTest(self):
|
||||
from subliminal.plugins import Addic7ed
|
||||
plugin = Addic7ed(config)
|
||||
results = plugin.download(plugin.list(["/tmp/The.Big.Bang.Theory.S03E13.HDTV.XviD-2HD.mkv"], ["en", "fr"])[0])
|
||||
print results
|
||||
assert len(results) > 0
|
||||
|
||||
|
||||
class BierDopjeListTestCase(unittest.TestCase):
|
||||
def runTest(self):
|
||||
from subliminal.plugins import BierDopje
|
||||
plugin = BierDopje(config)
|
||||
results = plugin.list(["The.Big.Bang.Theory.S03E13.HDTV.XviD-2HD.mkv"], ["en", "fr"])
|
||||
print results
|
||||
assert len(results) > 0
|
||||
|
||||
|
||||
class BierDopjeListExceptionTestCase(unittest.TestCase):
|
||||
def runTest(self):
|
||||
from subliminal.plugins import BierDopje
|
||||
plugin = BierDopje(config)
|
||||
results = plugin.list(["The.Office.US.S07E08.Viewing.Party.HDTV.XviD-FQM.[VTV].avi"], ["en", "fr"])
|
||||
print results
|
||||
assert len(results) > 0
|
||||
|
||||
|
||||
class BierDopjeListTestCase(unittest.TestCase):
|
||||
def runTest(self):
|
||||
from subliminal.plugins import BierDopje
|
||||
plugin = BierDopje(config)
|
||||
results = plugin.list(["The.Big.Bang.Theory.S03E13.HDTV.XviD-2HD.mkv"], ["en", "fr"])
|
||||
print results
|
||||
assert len(results) > 0
|
||||
|
||||
|
||||
class OpenSubtitlesQueryTestCase(unittest.TestCase):
|
||||
def runTest(self):
|
||||
from subliminal.plugins import OpenSubtitles
|
||||
plugin = OpenSubtitles()
|
||||
results = plugin.query('Night.Watch.2004.CD1.DVDRiP.XViD-FiCO.avi', moviehash="09a2c497663259cb", bytesize="733589504") # http://trac.opensubtitles.org/projects/opensubtitles/wiki/XMLRPC
|
||||
print results
|
||||
assert len(results) > 0
|
||||
|
||||
|
||||
class OpenSubtitlesQueryNoHashTestCase(unittest.TestCase):
|
||||
def runTest(self):
|
||||
from subliminal.plugins import OpenSubtitles
|
||||
plugin = OpenSubtitles()
|
||||
results = plugin.query('Night.Watch.2004.CD1.DVDRiP.XViD-FiCO.avi', languages=['en', 'fr']) # http://trac.opensubtitles.org/projects/opensubtitles/wiki/XMLRPC
|
||||
print results
|
||||
assert len(results) > 0
|
||||
|
||||
|
||||
class OpenSubtitlesListTestCase(unittest.TestCase):
|
||||
def runTest(self):
|
||||
from subliminal.plugins import OpenSubtitles
|
||||
plugin = OpenSubtitles()
|
||||
results = plugin.download(plugin.query('/tmp/Night.Watch.2004.CD1.DVDRiP.XViD-FiCO.avi', moviehash="09a2c497663259cb", bytesize="733589504")[0]) # http://trac.opensubtitles.org/projects/opensubtitles/wiki/XMLRPC
|
||||
assert len(results) > 0
|
||||
|
||||
|
||||
class SubtitulosListTestCase(unittest.TestCase):
|
||||
def runTest(self):
|
||||
from subliminal.plugins import Subtitulos
|
||||
plugin = Subtitulos()
|
||||
results = plugin.list(["The.Big.Bang.Theory.S03E13.HDTV.XviD-2HD.mkv"], ['en', 'fr'])
|
||||
print results
|
||||
assert len(results) > 0
|
||||
|
||||
|
||||
class SubtitulosDownloadTestCase(unittest.TestCase):
|
||||
def runTest(self):
|
||||
from subliminal.plugins import Subtitulos
|
||||
plugin = Subtitulos()
|
||||
results = plugin.download(plugin.list(["/tmp/The.Big.Bang.Theory.S03E13.HDTV.XviD-2HD.mkv"], ['en', 'fr'])[0])
|
||||
print results
|
||||
assert len(results) > 0
|
||||
|
||||
|
||||
class TheSubDBQueryTestCase(unittest.TestCase):
|
||||
def runTest(self):
|
||||
from subliminal.plugins import TheSubDB
|
||||
plugin = TheSubDB()
|
||||
results = plugin.query("test.mkv", "edc1981d6459c6111fe36205b4aff6c2")
|
||||
print results
|
||||
assert len(results) > 0
|
||||
|
||||
|
||||
class TheSubDBDownloadTestCase(unittest.TestCase):
|
||||
def runTest(self):
|
||||
from subliminal.plugins import TheSubDB
|
||||
plugin = TheSubDB()
|
||||
results = plugin.download(plugin.query("/tmp/test.mkv", "edc1981d6459c6111fe36205b4aff6c2")[0])
|
||||
print results
|
||||
assert len(results) > 0
|
||||
|
||||
|
||||
class SubsWikiListTestCase(unittest.TestCase):
|
||||
def runTest(self):
|
||||
from subliminal.plugins import SubsWiki
|
||||
plugin = SubsWiki()
|
||||
results = plugin.list(["The.Big.Bang.Theory.S03E13.HDTV.XviD-2HD.mkv"], ['en', 'es'])
|
||||
print results
|
||||
assert len(results) > 0
|
||||
|
||||
|
||||
class SubsWikiDownloadTestCase(unittest.TestCase):
|
||||
def runTest(self):
|
||||
from subliminal.plugins import SubsWiki
|
||||
plugin = SubsWiki()
|
||||
results = plugin.download(plugin.list(["/tmp/The.Big.Bang.Theory.S03E13.HDTV.XviD-2HD.mkv"], ['en', 'es'])[0])
|
||||
print results
|
||||
assert len(results) > 0
|
||||
'''
|
||||
class PodnapisiQueryTestCase(unittest.TestCase):
|
||||
def runTest(self):
|
||||
from subliminal.plugins import Podnapisi
|
||||
plugin = Podnapisi()
|
||||
results = plugin.query('09a2c497663259cb', ["en", "fr"])
|
||||
print results
|
||||
assert len(results) > 5
|
||||
|
||||
|
||||
class SubSceneListTestCase(unittest.TestCase):
|
||||
def runTest(self):
|
||||
from subliminal.plugins import SubScene
|
||||
plugin = SubScene()
|
||||
results = plugin.list(["Dexter.S04E01.HDTV.XviD-NoTV.avi"], ['en', 'fr'])
|
||||
print results
|
||||
assert len(results) > 0, "No result could be found for Dexter.S04E01.HDTV.XviD-NoTV.avi and no languages"
|
||||
|
||||
|
||||
class SubSceneDownloadTestCase(unittest.TestCase):
|
||||
def runTest(self):
|
||||
from subliminal.plugins import SubScene
|
||||
plugin = SubScene()
|
||||
results = plugin.download(plugin.list(["Dexter.S04E01.HDTV.XviD-NoTV.avi"], ['en', 'fr'])[0])
|
||||
print results
|
||||
assert len(results) > 0, "No result could be found for Dexter.S04E01.HDTV.XviD-NoTV.avi and no languages"
|
||||
|
||||
|
||||
class SubtitleSourceListTestCase(unittest.TestCase):
|
||||
def runTest(self):
|
||||
from subliminal.plugins import SubtitleSource
|
||||
plugin = SubtitleSource()
|
||||
results = plugin.list(["PrisM-Inception.2010"], ['en', 'fr'])
|
||||
print results
|
||||
assert len(results) > 0, "No result could be found for PrisM-Inception.2010"
|
||||
'''
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -19,4 +19,4 @@
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
__version__ = '0.3'
|
||||
__version__ = '0.4'
|
||||
|
||||
Executable
+152
@@ -0,0 +1,152 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Subliminal - Subtitles, faster than your thoughts
|
||||
# Copyright (c) 2011 Antoine Bertin <diaoulael@gmail.com>
|
||||
#
|
||||
# This file is part of Subliminal.
|
||||
#
|
||||
# Subliminal is free software; you can redistribute it and/or modify it under
|
||||
# the terms of the Lesser GNU General Public License as published by
|
||||
# the Free Software Foundation; either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Subliminal 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
|
||||
# Lesser GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the Lesser GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
import unittest
|
||||
import logging
|
||||
import os
|
||||
|
||||
|
||||
logging.basicConfig(level=logging.DEBUG, format='%(name)-24s %(levelname)-8s %(message)s')
|
||||
test_folder = u'/your/path/here/videos/'
|
||||
test_file = u'/your/path/here/videos/the.big.bang.theory.s04e01.hdtv.xvid-fqm.avi'
|
||||
cache_dir = u'/tmp/sublicache'
|
||||
if not os.path.exists(cache_dir):
|
||||
os.mkdir(cache_dir)
|
||||
|
||||
|
||||
class Addic7edTestCase(unittest.TestCase):
|
||||
def setUp(self):
|
||||
from subliminal.plugins import Addic7ed
|
||||
self.config = {'multi': True, 'cache_dir': cache_dir, 'files_mode': -1}
|
||||
self.plugin = Addic7ed(self.config)
|
||||
self.languages = set(['en', 'fr'])
|
||||
|
||||
def test_list(self):
|
||||
list = self.plugin.list(test_file, self.languages)
|
||||
assert list
|
||||
|
||||
def test_download(self):
|
||||
subtitle = self.plugin.list(test_file, self.languages)[0]
|
||||
if os.path.exists(subtitle.path):
|
||||
os.remove(subtitle.path)
|
||||
download = self.plugin.download(subtitle)
|
||||
assert download
|
||||
|
||||
|
||||
class BierDopjeTestCase(unittest.TestCase):
|
||||
def setUp(self):
|
||||
from subliminal.plugins import BierDopje
|
||||
self.config = {'multi': True, 'cache_dir': cache_dir, 'files_mode': -1}
|
||||
self.plugin = BierDopje(self.config)
|
||||
self.languages = set(['en', 'fr'])
|
||||
|
||||
def test_list(self):
|
||||
list = self.plugin.list(test_file, self.languages)
|
||||
assert list
|
||||
|
||||
def test_download(self):
|
||||
subtitle = self.plugin.list(test_file, self.languages)[0]
|
||||
if os.path.exists(subtitle.path):
|
||||
os.remove(subtitle.path)
|
||||
download = self.plugin.download(subtitle)
|
||||
assert download
|
||||
|
||||
|
||||
class OpenSubtitlesTestCase(unittest.TestCase):
|
||||
def setUp(self):
|
||||
from subliminal.plugins import OpenSubtitles
|
||||
self.config = {'multi': True, 'cache_dir': cache_dir, 'files_mode': -1}
|
||||
self.plugin = OpenSubtitles(self.config)
|
||||
self.languages = set(['en', 'fr'])
|
||||
|
||||
def test_list(self):
|
||||
list = self.plugin.list(test_file, self.languages)
|
||||
assert list
|
||||
|
||||
def test_download(self):
|
||||
subtitle = self.plugin.list(test_file, self.languages)[0]
|
||||
if os.path.exists(subtitle.path):
|
||||
os.remove(subtitle.path)
|
||||
download = self.plugin.download(subtitle)
|
||||
assert download
|
||||
|
||||
|
||||
class SubsWikiTestCase(unittest.TestCase):
|
||||
def setUp(self):
|
||||
from subliminal.plugins import SubsWiki
|
||||
self.config = {'multi': True, 'cache_dir': cache_dir, 'files_mode': -1}
|
||||
self.plugin = SubsWiki(self.config)
|
||||
self.languages = set(['en', 'fr', 'es', 'pt'])
|
||||
|
||||
def test_list(self):
|
||||
list = self.plugin.list(test_file, self.languages)
|
||||
assert list
|
||||
|
||||
def test_download(self):
|
||||
subtitle = self.plugin.list(test_file, self.languages)[0]
|
||||
if os.path.exists(subtitle.path):
|
||||
os.remove(subtitle.path)
|
||||
download = self.plugin.download(subtitle)
|
||||
assert download
|
||||
|
||||
|
||||
class SubtitulosTestCase(unittest.TestCase):
|
||||
def setUp(self):
|
||||
from subliminal.plugins import Subtitulos
|
||||
self.config = {'multi': True, 'cache_dir': cache_dir, 'files_mode': -1}
|
||||
self.plugin = Subtitulos(self.config)
|
||||
self.languages = set(['en', 'fr', 'es', 'pt'])
|
||||
|
||||
def test_list(self):
|
||||
list = self.plugin.list(test_file, self.languages)
|
||||
assert list
|
||||
|
||||
def test_download(self):
|
||||
subtitle = self.plugin.list(test_file, self.languages)[0]
|
||||
if os.path.exists(subtitle.path):
|
||||
os.remove(subtitle.path)
|
||||
download = self.plugin.download(subtitle)
|
||||
assert download
|
||||
|
||||
|
||||
class TheSubDBTestCase(unittest.TestCase):
|
||||
def setUp(self):
|
||||
from subliminal.plugins import TheSubDB
|
||||
self.config = {'multi': True, 'cache_dir': cache_dir, 'files_mode': -1}
|
||||
self.plugin = TheSubDB(self.config)
|
||||
self.languages = set(['en', 'fr', 'es', 'pt'])
|
||||
|
||||
def test_list(self):
|
||||
list = self.plugin.list(test_file, self.languages)
|
||||
assert list
|
||||
|
||||
def test_download(self):
|
||||
subtitle = self.plugin.list(test_file, self.languages)[0]
|
||||
if os.path.exists(subtitle.path):
|
||||
os.remove(subtitle.path)
|
||||
download = self.plugin.download(subtitle)
|
||||
assert download
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
||||
Executable
+90
@@ -0,0 +1,90 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Subliminal - Subtitles, faster than your thoughts
|
||||
# Copyright (c) 2011 Antoine Bertin <diaoulael@gmail.com>
|
||||
#
|
||||
# This file is part of Subliminal.
|
||||
#
|
||||
# Subliminal is free software; you can redistribute it and/or modify it under
|
||||
# the terms of the Lesser GNU General Public License as published by
|
||||
# the Free Software Foundation; either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Subliminal 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
|
||||
# Lesser GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the Lesser GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
import unittest
|
||||
import logging
|
||||
import os
|
||||
import subliminal
|
||||
|
||||
|
||||
logging.basicConfig(level=logging.DEBUG, format='%(name)-24s %(levelname)-8s %(message)s')
|
||||
test_folder = u'/your/path/here/videos/'
|
||||
test_file = u'/your/path/here/videos/the.big.bang.theory.s04e01.hdtv.xvid-fqm.avi'
|
||||
cache_dir = u'/tmp/sublicache'
|
||||
if not os.path.exists(cache_dir):
|
||||
os.mkdir(cache_dir)
|
||||
|
||||
|
||||
class FileTestCase(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.subli = subliminal.Subliminal(cache_dir=cache_dir, workers=4, multi=False, force=True, max_depth=3, files_mode=-1)
|
||||
self.subli.languages = ['en', 'fr', 'es', 'pt']
|
||||
self.subli.plugins = subliminal.PLUGINS
|
||||
|
||||
def test_list(self):
|
||||
results = self.subli.listSubtitles(test_file)
|
||||
self.assertTrue(len(results) > 0)
|
||||
|
||||
def test_download(self):
|
||||
results = self.subli.downloadSubtitles(test_file)
|
||||
self.assertTrue(len(results) > 0)
|
||||
|
||||
|
||||
class ErrorTestCase(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.subli = subliminal.Subliminal(cache_dir=cache_dir, workers=4, multi=False, force=True, max_depth=3, files_mode=-1)
|
||||
|
||||
def test_language(self):
|
||||
with self.assertRaises(subliminal.classes.LanguageError):
|
||||
self.subli.languages = ['en', 'fr', 'zz', 'pt']
|
||||
|
||||
def test_plugin(self):
|
||||
with self.assertRaises(subliminal.classes.PluginError):
|
||||
self.subli.plugins = ['WrongPlugin']
|
||||
|
||||
|
||||
class PriorityQueueTestCase(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.subli = subliminal.Subliminal(cache_dir=cache_dir, workers=4, multi=False, force=True, max_depth=3, files_mode=-1)
|
||||
self.subli.languages = ['en', 'fr', 'es', 'pt']
|
||||
self.subli.plugins = subliminal.PLUGINS
|
||||
|
||||
def test_bad_state_error(self):
|
||||
with self.assertRaises(subliminal.classes.BadStateError):
|
||||
self.subli.startWorkers()
|
||||
results = self.subli.listSubtitles(test_folder)
|
||||
self.subli.stopWorkers()
|
||||
|
||||
def test_manual_list(self):
|
||||
self.subli.taskQueue.put((5, subliminal.classes.ListTask(test_file, set(self.subli.languages), 'OpenSubtitles', self.subli.getConfigDict())))
|
||||
self.subli.startWorkers()
|
||||
# parallel stuff...
|
||||
self.subli.stopWorkers()
|
||||
result = self.subli.listResultQueue.get()
|
||||
self.assertTrue(len(result) > 0)
|
||||
|
||||
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
||||
Reference in New Issue
Block a user