Update subliminal

This commit is contained in:
Ruud
2012-06-16 18:12:41 +02:00
parent 79e842951e
commit d3f5f1408e
18 changed files with 1235 additions and 238 deletions
+12 -11
View File
@@ -18,8 +18,7 @@
from .core import (SERVICES, LANGUAGE_INDEX, SERVICE_INDEX, SERVICE_CONFIDENCE, from .core import (SERVICES, LANGUAGE_INDEX, SERVICE_INDEX, SERVICE_CONFIDENCE,
MATCHING_CONFIDENCE, create_list_tasks, consume_task, create_download_tasks, MATCHING_CONFIDENCE, create_list_tasks, consume_task, create_download_tasks,
group_by_video, key_subtitles) group_by_video, key_subtitles)
import guessit from .language import language_set, language_list, LANGUAGES
from guessit.language import ALL_LANGUAGES
import logging import logging
@@ -32,7 +31,8 @@ def list_subtitles(paths, languages=None, services=None, force=True, multi=False
:param paths: path(s) to video file or folder :param paths: path(s) to video file or folder
:type paths: string or list :type paths: string or list
:param list languages: languages to search for, in preferred order :param languages: languages to search for, in preferred order
:type languages: list of :class:`~subliminal.language.Language` or string
:param list services: services to use for the search, in preferred order :param list services: services to use for the search, in preferred order
:param bool force: force searching for subtitles even if some are detected :param bool force: force searching for subtitles even if some are detected
:param bool multi: search multiple languages for the same video :param bool multi: search multiple languages for the same video
@@ -44,7 +44,7 @@ def list_subtitles(paths, languages=None, services=None, force=True, multi=False
""" """
services = services or SERVICES services = services or SERVICES
languages = set(map(guessit.Language, languages or []) or ALL_LANGUAGES) languages = language_set(languages) if languages is not None else language_set(LANGUAGES)
if isinstance(paths, basestring): if isinstance(paths, basestring):
paths = [paths] paths = [paths]
if any([not isinstance(p, unicode) for p in paths]): if any([not isinstance(p, unicode) for p in paths]):
@@ -68,7 +68,8 @@ def download_subtitles(paths, languages=None, services=None, force=True, multi=F
:param paths: path(s) to video file or folder :param paths: path(s) to video file or folder
:type paths: string or list :type paths: string or list
:param list languages: languages to search for, in preferred order :param languages: languages to search for, in preferred order
:type languages: list of :class:`~subliminal.language.Language` or string
:param list services: services to use for the search, in preferred order :param list services: services to use for the search, in preferred order
:param bool force: force searching for subtitles even if some are detected :param bool force: force searching for subtitles even if some are detected
:param bool multi: search multiple languages for the same video :param bool multi: search multiple languages for the same video
@@ -77,16 +78,16 @@ def download_subtitles(paths, languages=None, services=None, force=True, multi=F
:param function scan_filter: filter function that takes a path as argument and returns a boolean indicating whether it has to be filtered out (``True``) or not (``False``) :param function scan_filter: filter function that takes a path as argument and returns a boolean indicating whether it has to be filtered out (``True``) or not (``False``)
:param order: preferred order for subtitles sorting :param order: preferred order for subtitles sorting
:type list: list of :data:`~subliminal.core.LANGUAGE_INDEX`, :data:`~subliminal.core.SERVICE_INDEX`, :data:`~subliminal.core.SERVICE_CONFIDENCE`, :data:`~subliminal.core.MATCHING_CONFIDENCE` :type list: list of :data:`~subliminal.core.LANGUAGE_INDEX`, :data:`~subliminal.core.SERVICE_INDEX`, :data:`~subliminal.core.SERVICE_CONFIDENCE`, :data:`~subliminal.core.MATCHING_CONFIDENCE`
:return: found subtitles :return: downloaded subtitles
:rtype: list of (:class:`~subliminal.videos.Video`, [:class:`~subliminal.subtitles.ResultSubtitle`]) :rtype: dict of :class:`~subliminal.videos.Video` => [:class:`~subliminal.subtitles.ResultSubtitle`]
""" """
services = services or SERVICES services = services or SERVICES
languages = map(guessit.Language, languages or []) or list(ALL_LANGUAGES) languages = language_list(languages) if languages is not None else language_list(LANGUAGES)
if isinstance(paths, basestring): if isinstance(paths, basestring):
paths = [paths] paths = [paths]
order = order or [LANGUAGE_INDEX, SERVICE_INDEX, SERVICE_CONFIDENCE, MATCHING_CONFIDENCE] order = order or [LANGUAGE_INDEX, SERVICE_INDEX, SERVICE_CONFIDENCE, MATCHING_CONFIDENCE]
subtitles_by_video = list_subtitles(paths, set(languages), services, force, multi, cache_dir, max_depth, scan_filter) subtitles_by_video = list_subtitles(paths, languages, services, force, multi, cache_dir, max_depth, scan_filter)
for video, subtitles in subtitles_by_video.iteritems(): for video, subtitles in subtitles_by_video.iteritems():
subtitles.sort(key=lambda s: key_subtitles(s, video, languages, services, order), reverse=True) subtitles.sort(key=lambda s: key_subtitles(s, video, languages, services, order), reverse=True)
results = [] results = []
@@ -95,9 +96,9 @@ def download_subtitles(paths, languages=None, services=None, force=True, multi=F
for task in tasks: for task in tasks:
try: try:
result = consume_task(task, service_instances) result = consume_task(task, service_instances)
results.append(result) results.append((task.video, result))
except: except:
logger.error(u'Error consuming task %r' % task, exc_info=True) logger.error(u'Error consuming task %r' % task, exc_info=True)
for service_instance in service_instances.itervalues(): for service_instance in service_instances.itervalues():
service_instance.terminate() service_instance.terminate()
return results return group_by_video(results)
+6 -5
View File
@@ -18,13 +18,14 @@
from .core import (consume_task, LANGUAGE_INDEX, SERVICE_INDEX, from .core import (consume_task, LANGUAGE_INDEX, SERVICE_INDEX,
SERVICE_CONFIDENCE, MATCHING_CONFIDENCE, SERVICES, create_list_tasks, SERVICE_CONFIDENCE, MATCHING_CONFIDENCE, SERVICES, create_list_tasks,
create_download_tasks, group_by_video, key_subtitles) create_download_tasks, group_by_video, key_subtitles)
from guessit.language import ALL_LANGUAGES from .language import language_list, language_set, LANGUAGES
from .tasks import StopTask from .tasks import StopTask
import Queue import Queue
import logging import logging
import threading import threading
__all__ = ['Worker', 'Pool']
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -111,7 +112,7 @@ class Pool(object):
def list_subtitles(self, paths, languages=None, services=None, force=True, multi=False, cache_dir=None, max_depth=3, scan_filter=None): def list_subtitles(self, paths, languages=None, services=None, force=True, multi=False, cache_dir=None, max_depth=3, scan_filter=None):
"""See :meth:`subliminal.list_subtitles`""" """See :meth:`subliminal.list_subtitles`"""
services = services or SERVICES services = services or SERVICES
languages = set(languages or ALL_LANGUAGES) languages = language_set(languages) if languages is not None else language_set(LANGUAGES)
if isinstance(paths, basestring): if isinstance(paths, basestring):
paths = [paths] paths = [paths]
if any([not isinstance(p, unicode) for p in paths]): if any([not isinstance(p, unicode) for p in paths]):
@@ -126,11 +127,11 @@ class Pool(object):
def download_subtitles(self, paths, languages=None, services=None, force=True, multi=False, cache_dir=None, max_depth=3, scan_filter=None, order=None): def download_subtitles(self, paths, languages=None, services=None, force=True, multi=False, cache_dir=None, max_depth=3, scan_filter=None, order=None):
"""See :meth:`subliminal.download_subtitles`""" """See :meth:`subliminal.download_subtitles`"""
services = services or SERVICES services = services or SERVICES
languages = languages or list(ALL_LANGUAGES) languages = language_list(languages) if languages is not None else language_list(LANGUAGES)
if isinstance(paths, basestring): if isinstance(paths, basestring):
paths = [paths] paths = [paths]
order = order or [LANGUAGE_INDEX, SERVICE_INDEX, SERVICE_CONFIDENCE, MATCHING_CONFIDENCE] order = order or [LANGUAGE_INDEX, SERVICE_INDEX, SERVICE_CONFIDENCE, MATCHING_CONFIDENCE]
subtitles_by_video = self.list_subtitles(paths, set(languages), services, force, multi, cache_dir, max_depth, scan_filter) subtitles_by_video = self.list_subtitles(paths, languages, services, force, multi, cache_dir, max_depth, scan_filter)
for video, subtitles in subtitles_by_video.iteritems(): for video, subtitles in subtitles_by_video.iteritems():
subtitles.sort(key=lambda s: key_subtitles(s, video, languages, services, order), reverse=True) subtitles.sort(key=lambda s: key_subtitles(s, video, languages, services, order), reverse=True)
tasks = create_download_tasks(subtitles_by_video, multi) tasks = create_download_tasks(subtitles_by_video, multi)
@@ -138,4 +139,4 @@ class Pool(object):
self.tasks.put(task) self.tasks.put(task)
self.join() self.join()
results = self.collect() results = self.collect()
return results return group_by_video(results)
+10 -8
View File
@@ -15,25 +15,26 @@
# #
# You should have received a copy of the GNU Lesser General Public License # You should have received a copy of the GNU Lesser General Public License
# along with subliminal. If not, see <http://www.gnu.org/licenses/>. # along with subliminal. If not, see <http://www.gnu.org/licenses/>.
import os.path
from collections import defaultdict from collections import defaultdict
import threading
from functools import wraps from functools import wraps
import logging import logging
import os.path
import threading
try: try:
import cPickle as pickle import cPickle as pickle
except ImportError: except ImportError:
import pickle import pickle
__all__ = ['Cache', 'cachedmethod']
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
class Cache(object): class Cache(object):
"""A Cache object contains cached values for methods. It can have """A Cache object contains cached values for methods. It can have
separate internal caches, one for each service. separate internal caches, one for each service
"""
"""
def __init__(self, cache_dir): def __init__(self, cache_dir):
self.cache_dir = cache_dir self.cache_dir = cache_dir
self.cache = defaultdict(dict) self.cache = defaultdict(dict)
@@ -100,9 +101,12 @@ class Cache(object):
def cachedmethod(function): def cachedmethod(function):
"""Decorator to make a method use the cache. """Decorator to make a method use the cache.
WARNING: this can NOT be used with static functions, it has to be used on .. note::
methods of some class."""
This can NOT be used with static functions, it has to be used on
methods of some class
"""
@wraps(function) @wraps(function)
def cached(*args): def cached(*args):
c = args[0].config.cache c = args[0].config.cache
@@ -126,7 +130,5 @@ def cachedmethod(function):
# meantime, but that's ok as we prefer to keep the latest value in # meantime, but that's ok as we prefer to keep the latest value in
# the cache # the cache
func_cache[key] = result func_cache[key] = result
return result return result
return cached return cached
+4 -5
View File
@@ -20,10 +20,9 @@ from .services import ServiceConfig
from .tasks import DownloadTask, ListTask from .tasks import DownloadTask, ListTask
from .utils import get_keywords from .utils import get_keywords
from .videos import Episode, Movie, scan from .videos import Episode, Movie, scan
from guessit.language import lang_set
import bs4
from collections import defaultdict from collections import defaultdict
from itertools import groupby from itertools import groupby
import bs4
import guessit import guessit
import logging import logging
@@ -119,7 +118,7 @@ def consume_task(task, services=None):
:type task: :class:`~subliminal.tasks.ListTask` or :class:`~subliminal.tasks.DownloadTask` :type task: :class:`~subliminal.tasks.ListTask` or :class:`~subliminal.tasks.DownloadTask`
:param dict services: mapping between the service name and an instance of this service :param dict services: mapping between the service name and an instance of this service
:return: the result of the task :return: the result of the task
:rtype: list of :class:`~subliminal.subtitles.ResultSubtitle` or :class:`~subliminal.subtitles.Subtitle` :rtype: list of :class:`~subliminal.subtitles.ResultSubtitle`
""" """
if services is None: if services is None:
@@ -134,12 +133,11 @@ def consume_task(task, services=None):
service = get_service(services, subtitle.service) service = get_service(services, subtitle.service)
try: try:
service.download(subtitle) service.download(subtitle)
result = subtitle result = [subtitle]
break break
except DownloadFailedError: except DownloadFailedError:
logger.warning(u'Could not download subtitle %r, trying next' % subtitle) logger.warning(u'Could not download subtitle %r, trying next' % subtitle)
continue continue
if result is None: if result is None:
logger.error(u'No subtitles could be downloaded for video %r' % task.video) logger.error(u'No subtitles could be downloaded for video %r' % task.video)
return result return result
@@ -225,6 +223,7 @@ def key_subtitles(subtitle, video, languages, services, order):
for sort_item in order: for sort_item in order:
if sort_item == LANGUAGE_INDEX: if sort_item == LANGUAGE_INDEX:
key += '{0:03d}'.format(len(languages) - languages.index(subtitle.language) - 1) key += '{0:03d}'.format(len(languages) - languages.index(subtitle.language) - 1)
key += '{0:01d}'.format(subtitle.language == languages[languages.index(subtitle.language)])
elif sort_item == SERVICE_INDEX: elif sort_item == SERVICE_INDEX:
key += '{0:02d}'.format(len(services) - services.index(subtitle.service) - 1) key += '{0:02d}'.format(len(services) - services.index(subtitle.service) - 1)
elif sort_item == SERVICE_CONFIDENCE: elif sort_item == SERVICE_CONFIDENCE:
-49
View File
@@ -22,60 +22,11 @@ class Error(Exception):
pass pass
class InvalidLanguageError(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 MissingLanguageError(Error):
"""Exception raised when a missing language is found
Attributes:
language -- the missing language
"""
def __init__(self, language):
self.language = language
def __str__(self):
return self.language
class InvalidServiceError(Error):
"""Exception raised when invalid service is submitted
:param string service: service that causes the error
"""
def __init__(self, service):
self.service = service
def __str__(self):
return self.service
class ServiceError(Error): class ServiceError(Error):
""""Exception raised by services""" """"Exception raised by services"""
pass pass
class WrongTaskError(Error):
""""Exception raised when invalid task is submitted"""
pass
class DownloadFailedError(Error): class DownloadFailedError(Error):
""""Exception raised when a download task has failed in service""" """"Exception raised when a download task has failed in service"""
pass pass
class UnknownVideoError(Error):
""""Exception raised when a video could not be identified"""
pass
+1032
View File
File diff suppressed because it is too large Load Diff
+59 -20
View File
@@ -15,10 +15,10 @@
# #
# You should have received a copy of the GNU Lesser General Public License # You should have received a copy of the GNU Lesser General Public License
# along with subliminal. If not, see <http://www.gnu.org/licenses/>. # along with subliminal. If not, see <http://www.gnu.org/licenses/>.
from .. import cache from ..cache import Cache
from ..exceptions import MissingLanguageError, DownloadFailedError, ServiceError from ..exceptions import DownloadFailedError, ServiceError
from ..language import language_set, Language
from ..subtitles import EXTENSIONS from ..subtitles import EXTENSIONS
from guessit.language import lang_set, UNDETERMINED
import logging import logging
import os import os
import requests import requests
@@ -49,8 +49,14 @@ class ServiceBase(object):
#: Timeout for web requests #: Timeout for web requests
timeout = 5 timeout = 5
#: Mapping to Service's language codes and subliminal's #: :class:`~subliminal.language.language_set` of available languages
languages = {} languages = language_set()
#: Map between language objects and language codes used in the service
language_map = {}
#: Default attribute of a :class:`~subliminal.language.Language` to get with :meth:`get_code`
language_code = 'alpha2'
#: Accepted video classes (:class:`~subliminal.videos.Episode`, :class:`~subliminal.videos.Movie`, :class:`~subliminal.videos.UnknownVideo`) #: Accepted video classes (:class:`~subliminal.videos.Episode`, :class:`~subliminal.videos.Movie`, :class:`~subliminal.videos.UnknownVideo`)
videos = [] videos = []
@@ -63,6 +69,7 @@ class ServiceBase(object):
def __init__(self, config=None): def __init__(self, config=None):
self.config = config or ServiceConfig() self.config = config or ServiceConfig()
self.session = None
def __enter__(self): def __enter__(self):
self.init() self.init()
@@ -80,33 +87,60 @@ class ServiceBase(object):
"""Initialize cache, make sure it is loaded from disk""" """Initialize cache, make sure it is loaded from disk"""
if not self.config or not self.config.cache: if not self.config or not self.config.cache:
raise ServiceError('Cache directory is required') raise ServiceError('Cache directory is required')
self.config.cache.load(self.__class__.__name__)
service_name = self.__class__.__name__
self.config.cache.load(service_name)
def save_cache(self): def save_cache(self):
service_name = self.__class__.__name__ self.config.cache.save(self.__class__.__name__)
self.config.cache.save(service_name)
def clear_cache(self): def clear_cache(self):
service_name = self.__class__.__name__ self.config.cache.clear(self.__class__.__name__)
self.config.cache.clear(service_name)
def cache_for(self, func, args, result): def cache_for(self, func, args, result):
service_name = self.__class__.__name__ return self.config.cache.cache_for(self.__class__.__name__, func, args, result)
return self.config.cache.cache_for(service_name, func, args, result)
def cached_value(self, func, args): def cached_value(self, func, args):
service_name = self.__class__.__name__ return self.config.cache.cached_value(self.__class__.__name__, func, args)
return self.config.cache.cached_value(service_name, func, args)
def terminate(self): def terminate(self):
"""Terminate connection""" """Terminate connection"""
logger.debug(u'Terminating %s' % self.__class__.__name__) logger.debug(u'Terminating %s' % self.__class__.__name__)
def get_code(self, language):
"""Get the service code for a :class:`~subliminal.language.Language`
It uses the :data:`language_map` and if there's no match, falls back
on the :data:`language_code` attribute of the given :class:`~subliminal.language.Language`
"""
if language in self.language_map:
return self.language_map[language]
if self.language_code is None:
raise ValueError('%r has no matching code' % language)
return getattr(language, self.language_code)
def get_language(self, code):
"""Get a :class:`~subliminal.language.Language` from a service code
It uses the :data:`language_map` and if there's no match, uses the
given code as ``language`` parameter for the :class:`~subliminal.language.Language`
constructor
.. note::
A warning is emitted if the generated :class:`~subliminal.language.Language`
is "Undetermined"
"""
if code in self.language_map:
return self.language_map[code]
language = Language(code, strict=False)
if language == Language('Undetermined'):
logger.warning(u'Code %s could not be identified as a language for %s' % (code, self.__class__.__name__))
return language
def query(self, *args): def query(self, *args):
"""Make the actual query""" """Make the actual query"""
pass raise NotImplementedError()
def list(self, video, languages): def list(self, video, languages):
"""List subtitles """List subtitles
@@ -119,6 +153,10 @@ class ServiceBase(object):
return [] return []
return self.list_checked(video, languages) return self.list_checked(video, languages)
def list_checked(self, video, languages):
"""List subtitles without having to check parameters for validity"""
raise NotImplementedError()
def download(self, subtitle): def download(self, subtitle):
"""Download a subtitle""" """Download a subtitle"""
self.download_file(subtitle.link, subtitle.path) self.download_file(subtitle.link, subtitle.path)
@@ -129,11 +167,12 @@ class ServiceBase(object):
:param video: the video to check :param video: the video to check
:type video: :class:`~subliminal.videos.video` :type video: :class:`~subliminal.videos.video`
:param set languages: languages to check :param languages: languages to check
:type languages: :class:`~subliminal.language.Language`
:rtype: bool :rtype: bool
""" """
languages = (lang_set(languages) & cls.languages) - set([UNDETERMINED]) languages = (languages & cls.languages) - language_set(['Undetermined'])
if not languages: if not languages:
logger.debug(u'No language available for service %s' % cls.__name__.lower()) logger.debug(u'No language available for service %s' % cls.__name__.lower())
return False return False
@@ -211,7 +250,7 @@ class ServiceConfig(object):
self.cache_dir = cache_dir self.cache_dir = cache_dir
self.cache = None self.cache = None
if cache_dir is not None: if cache_dir is not None:
self.cache = cache.Cache(cache_dir) self.cache = Cache(cache_dir)
def __repr__(self): def __repr__(self):
return 'ServiceConfig(%r, %s)' % (self.multi, self.cache.cache_dir) return 'ServiceConfig(%r, %s)' % (self.multi, self.cache.cache_dir)
+7 -14
View File
@@ -17,12 +17,11 @@
# along with subliminal. If not, see <http://www.gnu.org/licenses/>. # along with subliminal. If not, see <http://www.gnu.org/licenses/>.
from . import ServiceBase from . import ServiceBase
from ..cache import cachedmethod from ..cache import cachedmethod
from ..language import Language, language_set
from ..subtitles import get_subtitle_path, ResultSubtitle from ..subtitles import get_subtitle_path, ResultSubtitle
from ..utils import get_keywords
from ..videos import Episode from ..videos import Episode
from bs4 import BeautifulSoup from bs4 import BeautifulSoup
from guessit.language import lang_set
from subliminal.utils import get_keywords
import guessit
import logging import logging
import re import re
@@ -49,13 +48,10 @@ def matches(pattern, string):
class Addic7ed(ServiceBase): class Addic7ed(ServiceBase):
server_url = 'http://www.addic7ed.com' server_url = 'http://www.addic7ed.com'
api_based = False api_based = False
languages = lang_set([u'English', u'Italian', u'Portuguese', #TODO: Complete this
u'Portuguese (Brazilian)', u'Romanian', languages = language_set(['ar', 'ca', 'de', 'el', 'en', 'es', 'eu', 'fr', 'ga', 'he', 'hr', 'hu', 'it',
u'Spanish', u'French', u'Greek', u'Arabic', 'pl', 'pt', 'ro', 'ru', 'se', 'pt-br'])
u'German', u'Croatian', u'Indonesian', u'Hebrew', language_map = {'Portuguese (Brazilian)': Language('por-BR'), 'Greek': Language('gre')}
u'Russian', u'Turkish', u'Swedish', u'Czech',
u'Dutch', u'Hungarian', u'Norwegian', u'Polish',
u'Persian'], strict=True)
videos = [Episode] videos = [Episode]
require_video = False require_video = False
required_features = ['permissive'] required_features = ['permissive']
@@ -119,7 +115,7 @@ class Addic7ed(ServiceBase):
if 'href' not in link.attrs or not (link['href'].startswith('/original') or link['href'].startswith('/updated')): if 'href' not in link.attrs or not (link['href'].startswith('/original') or link['href'].startswith('/updated')):
continue continue
suburl = link['href'] suburl = link['href']
lang = guessit.Language(row.find('td', 'language').text.strip()) lang = self.get_language(row.find('td', 'language').text.strip())
result = {'suburl': suburl, 'language': lang, 'release': release} result = {'suburl': suburl, 'language': lang, 'release': release}
suburls.append(result) suburls.append(result)
return suburls return suburls
@@ -135,21 +131,18 @@ class Addic7ed(ServiceBase):
except KeyError: except KeyError:
logger.debug(u'Could not find series id for %s' % series) logger.debug(u'Could not find series id for %s' % series)
return [] return []
try: try:
ep_url = self.get_episode_url(sid, season, episode) ep_url = self.get_episode_url(sid, season, episode)
except KeyError: except KeyError:
logger.debug(u'Could not find episode id for %s season %d episode %d' % (series, season, episode)) logger.debug(u'Could not find episode id for %s season %d episode %d' % (series, season, episode))
return [] return []
suburls = self.get_sub_urls(ep_url) suburls = self.get_sub_urls(ep_url)
# filter the subtitles with our queried languages # filter the subtitles with our queried languages
subtitles = [] subtitles = []
for suburl in suburls: for suburl in suburls:
language = suburl['language'] language = suburl['language']
if language not in languages: if language not in languages:
continue continue
path = get_subtitle_path(filepath, language, self.config.multi) path = get_subtitle_path(filepath, language, self.config.multi)
subtitle = ResultSubtitle(path, language, self.__class__.__name__.lower(), subtitle = ResultSubtitle(path, language, self.__class__.__name__.lower(),
'%s/%s' % (self.server_url, suburl['suburl']), '%s/%s' % (self.server_url, suburl['suburl']),
+2 -3
View File
@@ -18,11 +18,11 @@
from . import ServiceBase from . import ServiceBase
from ..cache import cachedmethod from ..cache import cachedmethod
from ..exceptions import ServiceError from ..exceptions import ServiceError
from ..language import language_set
from ..subtitles import get_subtitle_path, ResultSubtitle from ..subtitles import get_subtitle_path, ResultSubtitle
from ..utils import to_unicode from ..utils import to_unicode
from ..videos import Episode from ..videos import Episode
from bs4 import BeautifulSoup from bs4 import BeautifulSoup
from guessit.language import lang_set
import logging import logging
import urllib import urllib
try: try:
@@ -37,7 +37,7 @@ logger = logging.getLogger(__name__)
class BierDopje(ServiceBase): class BierDopje(ServiceBase):
server_url = 'http://api.bierdopje.com/A2B638AC5D804C2E/' server_url = 'http://api.bierdopje.com/A2B638AC5D804C2E/'
api_based = True api_based = True
languages = lang_set(['en', 'nl']) languages = language_set(['eng', 'dut'])
videos = [Episode] videos = [Episode]
require_video = False require_video = False
required_features = ['xml'] required_features = ['xml']
@@ -52,7 +52,6 @@ class BierDopje(ServiceBase):
if soup.status.contents[0] == 'false': if soup.status.contents[0] == 'false':
logger.debug(u'Could not find show %s' % series) logger.debug(u'Could not find show %s' % series)
return None return None
return int(soup.showid.contents[0]) return int(soup.showid.contents[0])
def load_cache(self): def load_cache(self):
+47 -69
View File
@@ -17,11 +17,10 @@
# along with subliminal. If not, see <http://www.gnu.org/licenses/>. # along with subliminal. If not, see <http://www.gnu.org/licenses/>.
from . import ServiceBase from . import ServiceBase
from ..exceptions import ServiceError, DownloadFailedError from ..exceptions import ServiceError, DownloadFailedError
from ..language import Language, language_set
from ..subtitles import get_subtitle_path, ResultSubtitle from ..subtitles import get_subtitle_path, ResultSubtitle
from ..utils import to_unicode from ..utils import to_unicode
from ..videos import Episode, Movie from ..videos import Episode, Movie
from guessit.language import lang_set
import guessit
import gzip import gzip
import logging import logging
import os.path import os.path
@@ -34,71 +33,50 @@ logger = logging.getLogger(__name__)
class OpenSubtitles(ServiceBase): class OpenSubtitles(ServiceBase):
server_url = 'http://api.opensubtitles.org/xml-rpc' server_url = 'http://api.opensubtitles.org/xml-rpc'
api_based = True api_based = True
# language list fetched from: # Source: http://www.opensubtitles.org/addons/export_languages.php
# http://www.opensubtitles.org/addons/export_languages.php languages = language_set(['aar', 'abk', 'ace', 'ach', 'ada', 'ady', 'afa', 'afh', 'afr', 'ain', 'aka', 'akk',
languages = lang_set(['aar', 'abk', 'ace', 'ach', 'ada', 'ady', 'afa', 'afh', 'alb', 'ale', 'alg', 'alt', 'amh', 'ang', 'apa', 'ara', 'arc', 'arg', 'arm', 'arn',
'afr', 'ain', 'aka', 'akk', 'alb', 'ale', 'alg', 'alt', 'arp', 'art', 'arw', 'asm', 'ast', 'ath', 'aus', 'ava', 'ave', 'awa', 'aym', 'aze',
'amh', 'ang', 'apa', 'ara', 'arc', 'arg', 'arm', 'arn', 'bad', 'bai', 'bak', 'bal', 'bam', 'ban', 'baq', 'bas', 'bat', 'bej', 'bel', 'bem',
'arp', 'art', 'arw', 'asm', 'ast', 'ath', 'aus', 'ava', 'ben', 'ber', 'bho', 'bih', 'bik', 'bin', 'bis', 'bla', 'bnt', 'bos', 'bra', 'bre',
'ave', 'awa', 'aym', 'aze', 'bad', 'bai', 'bak', 'bal', 'btk', 'bua', 'bug', 'bul', 'bur', 'byn', 'cad', 'cai', 'car', 'cat', 'cau', 'ceb',
'bam', 'ban', 'baq', 'bas', 'bat', 'bej', 'bel', 'bem', 'cel', 'cha', 'chb', 'che', 'chg', 'chi', 'chk', 'chm', 'chn', 'cho', 'chp', 'chr',
'ben', 'ber', 'bho', 'bih', 'bik', 'bin', 'bis', 'bla', 'chu', 'chv', 'chy', 'cmc', 'cop', 'cor', 'cos', 'cpe', 'cpf', 'cpp', 'cre', 'crh',
'bnt', 'bod', 'bos', 'bra', 'bre', 'btk', 'bua', 'bug', 'crp', 'csb', 'cus', 'cze', 'dak', 'dan', 'dar', 'day', 'del', 'den', 'dgr', 'din',
'bul', 'bur', 'byn', 'cad', 'cai', 'car', 'cat', 'cau', 'div', 'doi', 'dra', 'dua', 'dum', 'dut', 'dyu', 'dzo', 'efi', 'egy', 'eka', 'ell',
'ceb', 'cel', 'cha', 'chb', 'che', 'chg', 'chi', 'chk', 'elx', 'eng', 'enm', 'epo', 'est', 'ewe', 'ewo', 'fan', 'fao', 'fat', 'fij', 'fil',
'chm', 'chn', 'cho', 'chp', 'chr', 'chu', 'chv', 'chy', 'fin', 'fiu', 'fon', 'fre', 'frm', 'fro', 'fry', 'ful', 'fur', 'gaa', 'gay', 'gba',
'cmc', 'cop', 'cor', 'cos', 'cpe', 'cpf', 'cpp', 'cre', 'gem', 'geo', 'ger', 'gez', 'gil', 'gla', 'gle', 'glg', 'glv', 'gmh', 'goh', 'gon',
'crh', 'crp', 'csb', 'cus', 'cym', 'cze', 'dak', 'dan', 'gor', 'got', 'grb', 'grc', 'grn', 'guj', 'gwi', 'hai', 'hat', 'hau', 'haw', 'heb',
'dar', 'day', 'del', 'den', 'deu', 'dgr', 'din', 'div', 'her', 'hil', 'him', 'hin', 'hit', 'hmn', 'hmo', 'hrv', 'hun', 'hup', 'iba', 'ibo',
'doi', 'dra', 'dua', 'dum', 'dut', 'dyu', 'dzo', 'efi', 'ice', 'ido', 'iii', 'ijo', 'iku', 'ile', 'ilo', 'ina', 'inc', 'ind', 'ine', 'inh',
'egy', 'eka', 'elx', 'eng', 'enm', 'epo', 'est', 'eus', 'ipk', 'ira', 'iro', 'ita', 'jav', 'jpn', 'jpr', 'jrb', 'kaa', 'kab', 'kac', 'kal',
'ewe', 'ewo', 'fan', 'fao', 'fas', 'fat', 'fij', 'fil', 'kam', 'kan', 'kar', 'kas', 'kau', 'kaw', 'kaz', 'kbd', 'kha', 'khi', 'khm', 'kho',
'fin', 'fiu', 'fon', 'fra', 'fre', 'frm', 'fro', 'fry', 'kik', 'kin', 'kir', 'kmb', 'kok', 'kom', 'kon', 'kor', 'kos', 'kpe', 'krc', 'kro',
'ful', 'fur', 'gaa', 'gay', 'gba', 'gem', 'geo', 'ger', 'kru', 'kua', 'kum', 'kur', 'kut', 'lad', 'lah', 'lam', 'lao', 'lat', 'lav', 'lez',
'gez', 'gil', 'gla', 'gle', 'glg', 'glv', 'gmh', 'goh', 'lim', 'lin', 'lit', 'lol', 'loz', 'ltz', 'lua', 'lub', 'lug', 'lui', 'lun', 'luo',
'gon', 'gor', 'got', 'grb', 'grc', 'ell', 'grn', 'guj', 'lus', 'mac', 'mad', 'mag', 'mah', 'mai', 'mak', 'mal', 'man', 'mao', 'map', 'mar',
'gwi', 'hai', 'hat', 'hau', 'haw', 'heb', 'her', 'hil', 'mas', 'may', 'mdf', 'mdr', 'men', 'mga', 'mic', 'min', 'mkh', 'mlg', 'mlt', 'mnc',
'him', 'hin', 'hit', 'hmn', 'hmo', 'hrv', 'hun', 'hup', 'mni', 'mno', 'moh', 'mon', 'mos', 'mun', 'mus', 'mwl', 'mwr', 'myn', 'myv', 'nah',
'hye', 'iba', 'ibo', 'ice', 'ido', 'iii', 'ijo', 'iku', 'nai', 'nap', 'nau', 'nav', 'nbl', 'nde', 'ndo', 'nds', 'nep', 'new', 'nia', 'nic',
'ile', 'ilo', 'ina', 'inc', 'ind', 'ine', 'inh', 'ipk', 'niu', 'nno', 'nob', 'nog', 'non', 'nor', 'nso', 'nub', 'nwc', 'nya', 'nym', 'nyn',
'ira', 'iro', 'isl', 'ita', 'jav', 'jpn', 'jpr', 'jrb', 'nyo', 'nzi', 'oci', 'oji', 'ori', 'orm', 'osa', 'oss', 'ota', 'oto', 'paa', 'pag',
'kaa', 'kab', 'kac', 'kal', 'kam', 'kan', 'kar', 'kas', 'pal', 'pam', 'pan', 'pap', 'pau', 'peo', 'per', 'phi', 'phn', 'pli', 'pol', 'pon',
'kat', 'kau', 'kaw', 'kaz', 'kbd', 'kha', 'khi', 'khm', 'por', 'pra', 'pro', 'pus', 'que', 'raj', 'rap', 'rar', 'roa', 'roh', 'rom', 'rum',
'kho', 'kik', 'kin', 'kir', 'kmb', 'kok', 'kom', 'kon', 'run', 'rup', 'rus', 'sad', 'sag', 'sah', 'sai', 'sal', 'sam', 'san', 'sas', 'sat',
'kor', 'kos', 'kpe', 'krc', 'kro', 'kru', 'kua', 'kum', 'scn', 'sco', 'sel', 'sem', 'sga', 'sgn', 'shn', 'sid', 'sin', 'sio', 'sit', 'sla',
'kur', 'kut', 'lad', 'lah', 'lam', 'lao', 'lat', 'lav', 'slo', 'slv', 'sma', 'sme', 'smi', 'smj', 'smn', 'smo', 'sms', 'sna', 'snd', 'snk',
'lez', 'lim', 'lin', 'lit', 'lol', 'loz', 'ltz', 'lua', 'sog', 'som', 'son', 'sot', 'spa', 'srd', 'srp', 'srr', 'ssa', 'ssw', 'suk', 'sun',
'lub', 'lug', 'lui', 'lun', 'luo', 'lus', 'mac', 'mad', 'sus', 'sux', 'swa', 'swe', 'syr', 'tah', 'tai', 'tam', 'tat', 'tel', 'tem', 'ter',
'mag', 'mah', 'mai', 'mak', 'mal', 'man', 'mao', 'map', 'tet', 'tgk', 'tgl', 'tha', 'tib', 'tig', 'tir', 'tiv', 'tkl', 'tlh', 'tli', 'tmh',
'mar', 'mas', 'may', 'mdf', 'mdr', 'men', 'mga', 'mic', 'tog', 'ton', 'tpi', 'tsi', 'tsn', 'tso', 'tuk', 'tum', 'tup', 'tur', 'tut', 'tvl',
'min', 'mis', 'mkd', 'mkh', 'mlg', 'mlt', 'mnc', 'mni', 'twi', 'tyv', 'udm', 'uga', 'uig', 'ukr', 'umb', 'urd', 'uzb', 'vai', 'ven', 'vie',
'mno', 'moh', 'mol', 'mon', 'mos', 'mri', 'msa', 'mwl', 'vol', 'vot', 'wak', 'wal', 'war', 'was', 'wel', 'wen', 'wln', 'wol', 'xal', 'xho',
'mul', 'mun', 'mus', 'mwr', 'mya', 'myn', 'myv', 'nah', 'yao', 'yap', 'yid', 'yor', 'ypk', 'zap', 'zen', 'zha', 'znd', 'zul', 'zun',
'nai', 'nap', 'nau', 'nav', 'nbl', 'nde', 'ndo', 'nds', 'por-BR', 'rum-MD'])
'nep', 'new', 'nia', 'nic', 'niu', 'nld', 'nno', 'nob', language_map = {'mol': Language('rum-MD'), 'scc': Language('srp'), 'pob': Language('por-BR'),
'nog', 'non', 'nor', 'nso', 'nub', 'nwc', 'nya', 'nym', Language('rum-MD'): 'mol', Language('srp'): 'scc', Language('por-BR'): 'pob'}
'nyn', 'nyo', 'nzi', 'oci', 'oji', 'ori', 'orm', 'osa', language_code = 'alpha3'
'oss', 'ota', 'oto', 'paa', 'pag', 'pal', 'pam', 'pan',
'pap', 'pau', 'peo', 'per', 'phi', 'phn', 'pli', 'pol',
'pon', 'por', 'pra', 'pro', 'pus', 'que', 'raj', 'rap',
'rar', 'roa', 'roh', 'rom', 'ron', 'run', 'rup', 'rus',
'sad', 'sag', 'sah', 'sai', 'sal', 'sam', 'san', 'sas',
'sat', 'scc', 'scn', 'sco', 'scr', 'sel', 'sem', 'sga',
'sgn', 'shn', 'sid', 'sin', 'sio', 'sit', 'sla', 'slk',
'slo', 'slv', 'sma', 'sme', 'smi', 'smj', 'smn', 'smo',
'sms', 'sna', 'snd', 'snk', 'sog', 'som', 'son', 'sot',
'spa', 'sqi', 'srd', 'srp', 'srr', 'ssa', 'ssw', 'suk',
'sun', 'sus', 'sux', 'swa', 'swe', 'syr', 'tah', 'tai',
'tam', 'tat', 'tel', 'tem', 'ter', 'tet', 'tgk', 'tgl',
'tha', 'tib', 'tig', 'tir', 'tiv', 'tkl', 'tlh', 'tli',
'tmh', 'tog', 'ton', 'tpi', 'tsi', 'tsn', 'tso', 'tuk',
'tum', 'tup', 'tur', 'tut', 'tvl', 'twi', 'tyv', 'udm',
'uga', 'uig', 'ukr', 'umb', 'und', 'urd', 'uzb', 'vai',
'ven', 'vie', 'vol', 'vot', 'wak', 'wal', 'war', 'was',
'wel', 'wen', 'wln', 'wol', 'xal', 'xho', 'yao', 'yap',
'yid', 'yor', 'ypk', 'zap', 'zen', 'zha', 'zho', 'znd',
'zul', 'zun', 'rum', 'pob', 'unk', 'ass'])
videos = [Episode, Movie] videos = [Episode, Movie]
require_video = False require_video = False
confidence_order = ['moviehash', 'imdbid', 'fulltext'] confidence_order = ['moviehash', 'imdbid', 'fulltext']
@@ -131,7 +109,7 @@ class OpenSubtitles(ServiceBase):
if not searches: if not searches:
raise ServiceError('One or more parameter missing') raise ServiceError('One or more parameter missing')
for search in searches: for search in searches:
search['sublanguageid'] = ','.join(l.opensubtitles for l in languages) search['sublanguageid'] = ','.join(self.get_code(l) for l in languages)
logger.debug(u'Getting subtitles %r with token %s' % (searches, self.token)) logger.debug(u'Getting subtitles %r with token %s' % (searches, self.token))
results = self.server.SearchSubtitles(self.token, searches) results = self.server.SearchSubtitles(self.token, searches)
if not results['data']: if not results['data']:
@@ -139,7 +117,7 @@ class OpenSubtitles(ServiceBase):
return [] return []
subtitles = [] subtitles = []
for result in results['data']: for result in results['data']:
language = guessit.Language(result['SubLanguageID']) language = self.get_language(result['SubLanguageID'])
path = get_subtitle_path(filepath, language, self.config.multi) path = get_subtitle_path(filepath, language, self.config.multi)
confidence = 1 - float(self.confidence_order.index(result['MatchedBy'])) / float(len(self.confidence_order)) confidence = 1 - float(self.confidence_order.index(result['MatchedBy'])) / float(len(self.confidence_order))
subtitle = ResultSubtitle(path, language, service=self.__class__.__name__.lower(), link=result['SubDownloadLink'], subtitle = ResultSubtitle(path, language, service=self.__class__.__name__.lower(), link=result['SubDownloadLink'],
+13 -9
View File
@@ -17,12 +17,11 @@
# along with subliminal. If not, see <http://www.gnu.org/licenses/>. # along with subliminal. If not, see <http://www.gnu.org/licenses/>.
from . import ServiceBase from . import ServiceBase
from ..exceptions import ServiceError, DownloadFailedError from ..exceptions import ServiceError, DownloadFailedError
from ..language import language_set, Language
from ..subtitles import get_subtitle_path, ResultSubtitle from ..subtitles import get_subtitle_path, ResultSubtitle
from ..utils import to_unicode from ..utils import to_unicode
from ..videos import Episode, Movie from ..videos import Episode, Movie
from guessit.language import lang_set
from hashlib import md5, sha256 from hashlib import md5, sha256
import guessit
import logging import logging
import xmlrpclib import xmlrpclib
@@ -33,11 +32,16 @@ logger = logging.getLogger(__name__)
class Podnapisi(ServiceBase): class Podnapisi(ServiceBase):
server_url = 'http://ssp.podnapisi.net:8000' server_url = 'http://ssp.podnapisi.net:8000'
api_based = True api_based = True
languages = lang_set(['sl', 'en', 'nn', 'ko', 'de', 'is', 'cs', 'fr', 'it', 'bs', 'jp', 'ar', 'ro', languages = language_set(['ar', 'be', 'bg', 'bs', 'ca', 'ca', 'cs', 'da', 'de', 'el', 'en',
'hu', 'gr', 'zh', 'lt', 'et', 'lv', 'he', 'nl', 'da', 'sv', 'pl', 'ru', 'es', 'es', 'et', 'fa', 'fi', 'fr', 'ga', 'he', 'hi', 'hr', 'hu', 'id',
'sq', 'tr', 'fi', 'pt', 'bg', 'mk', 'sr', 'sk', 'hr', 'hi', 'th', 'ca', 'uk', 'is', 'it', 'ja', 'ko', 'lt', 'lv', 'mk', 'ms', 'nl', 'nn', 'pl',
'pb', 'ga', 'be', 'vi', 'fa', 'ca', 'id', 'ms']) 'pt', 'ro', 'ru', 'sk', 'sl', 'sq', 'sr', 'sv', 'th', 'tr', 'uk',
#FIXME: ag and cyr not recognized by guessit 'vi', 'zh', 'es-ar', 'pt-br'])
language_map = {'jp': Language('jpn'), Language('jpn'): 'jp',
'gr': Language('gre'), Language('gre'): 'gr',
'pb': Language('por-BR'), Language('por-BR'): 'pb',
'ag': Language('spa-AR'), Language('spa-AR'): 'ag',
'cyr': Language('srp')}
videos = [Episode, Movie] videos = [Episode, Movie]
require_video = True require_video = True
@@ -71,8 +75,8 @@ class Podnapisi(ServiceBase):
return [] return []
subtitles = [] subtitles = []
for result in results['results'][moviehash]['subtitles']: for result in results['results'][moviehash]['subtitles']:
language = guessit.Language(result['lang']) language = self.get_language(result['lang'])
if language == guessit.language.UNDETERMINED or language not in languages: if language not in languages:
continue continue
path = get_subtitle_path(filepath, language, self.config.multi) path = get_subtitle_path(filepath, language, self.config.multi)
subtitle = ResultSubtitle(path, language, service=self.__class__.__name__.lower(), link=result['id'], subtitle = ResultSubtitle(path, language, service=self.__class__.__name__.lower(), link=result['id'],
+7 -6
View File
@@ -17,12 +17,11 @@
# along with subliminal. If not, see <http://www.gnu.org/licenses/>. # along with subliminal. If not, see <http://www.gnu.org/licenses/>.
from . import ServiceBase from . import ServiceBase
from ..exceptions import ServiceError from ..exceptions import ServiceError
from ..language import language_set, Language
from ..subtitles import get_subtitle_path, ResultSubtitle from ..subtitles import get_subtitle_path, ResultSubtitle
from ..videos import Episode, Movie from ..videos import Episode, Movie
from bs4 import BeautifulSoup from bs4 import BeautifulSoup
from guessit.language import lang_set
from subliminal.utils import get_keywords, split_keyword from subliminal.utils import get_keywords, split_keyword
import guessit
import logging import logging
import re import re
import urllib import urllib
@@ -34,9 +33,11 @@ logger = logging.getLogger(__name__)
class SubsWiki(ServiceBase): class SubsWiki(ServiceBase):
server_url = 'http://www.subswiki.com' server_url = 'http://www.subswiki.com'
api_based = False api_based = False
languages = lang_set([u'English (US)', u'English (UK)', u'English', u'French', u'Brazilian', languages = language_set(['eng-US', 'eng-GB', 'eng', 'fre', 'por-BR', 'por', 'spa-ES', u'spa', u'ita', u'cat'])
u'Portuguese', u'Español (Latinoamérica)', u'Español (España)', language_map = {u'Español': Language('spa'), u'Español (España)': Language('spa'), u'Español (Latinoamérica)': Language('spa'),
u'Español', u'Italian', u'Català'], strict=True) u'Català': Language('cat'), u'Brazilian': Language('por-BR'), u'English (US)': Language('eng-US'),
u'English (UK)': Language('eng-GB')}
language_code = 'name'
videos = [Episode, Movie] videos = [Episode, Movie]
require_video = False require_video = False
release_pattern = re.compile('\nVersion (.+), ([0-9]+).([0-9])+ MBs') release_pattern = re.compile('\nVersion (.+), ([0-9]+).([0-9])+ MBs')
@@ -82,7 +83,7 @@ class SubsWiki(ServiceBase):
logger.debug(u'None of subtitle keywords %r in %r' % (sub_keywords, keywords)) logger.debug(u'None of subtitle keywords %r in %r' % (sub_keywords, keywords))
continue continue
for html_language in sub.parent.parent.findAll('td', {'class': 'language'}): for html_language in sub.parent.parent.findAll('td', {'class': 'language'}):
language = guessit.Language(html_language.string.strip()) language = self.get_language(html_language.string.strip())
if language not in languages: if language not in languages:
logger.debug(u'Language %r not in wanted languages %r' % (language, languages)) logger.debug(u'Language %r not in wanted languages %r' % (language, languages))
continue continue
+7 -6
View File
@@ -16,12 +16,11 @@
# You should have received a copy of the GNU Lesser General Public License # You should have received a copy of the GNU Lesser General Public License
# along with subliminal. If not, see <http://www.gnu.org/licenses/>. # along with subliminal. If not, see <http://www.gnu.org/licenses/>.
from . import ServiceBase from . import ServiceBase
from ..language import language_set, Language
from ..subtitles import get_subtitle_path, ResultSubtitle from ..subtitles import get_subtitle_path, ResultSubtitle
from ..videos import Episode from ..videos import Episode
from bs4 import BeautifulSoup from bs4 import BeautifulSoup
from guessit.language import lang_set
from subliminal.utils import get_keywords, split_keyword from subliminal.utils import get_keywords, split_keyword
import guessit
import logging import logging
import re import re
import unicodedata import unicodedata
@@ -34,9 +33,11 @@ logger = logging.getLogger(__name__)
class Subtitulos(ServiceBase): class Subtitulos(ServiceBase):
server_url = 'http://www.subtitulos.es' server_url = 'http://www.subtitulos.es'
api_based = False api_based = False
languages = lang_set([u'English (US)', u'English (UK)', u'English', u'French', u'Brazilian', languages = language_set(['eng-US', 'eng-GB', 'eng', 'fre', 'por-BR', 'por', 'spa-ES', u'spa', u'ita', u'cat'])
u'Portuguese', u'Español (Latinoamérica)', u'Español (España)', u'Español', language_map = {u'Español': Language('spa'), u'Español (España)': Language('spa'), u'Español (Latinoamérica)': Language('spa'),
u'Italian', u'Català'], strict=True) u'Català': Language('cat'), u'Brazilian': Language('por-BR'), u'English (US)': Language('eng-US'),
u'English (UK)': Language('eng-GB')}
language_code = 'name'
videos = [Episode] videos = [Episode]
require_video = False require_video = False
required_features = ['permissive'] required_features = ['permissive']
@@ -68,7 +69,7 @@ class Subtitulos(ServiceBase):
logger.debug(u'None of subtitle keywords %r in %r' % (sub_keywords, keywords)) logger.debug(u'None of subtitle keywords %r in %r' % (sub_keywords, keywords))
continue continue
for html_language in sub.findAllNext('ul', {'class': 'sslist'}): for html_language in sub.findAllNext('ul', {'class': 'sslist'}):
language = guessit.Language(html_language.findNext('li', {'class': 'li-idioma'}).find('strong').contents[0].string.strip()) language = self.get_language(html_language.findNext('li', {'class': 'li-idioma'}).find('strong').contents[0].string.strip())
if language not in languages: if language not in languages:
logger.debug(u'Language %r not in wanted languages %r' % (language, languages)) logger.debug(u'Language %r not in wanted languages %r' % (language, languages))
continue continue
+8 -9
View File
@@ -16,10 +16,9 @@
# You should have received a copy of the GNU Lesser General Public License # You should have received a copy of the GNU Lesser General Public License
# along with subliminal. If not, see <http://www.gnu.org/licenses/>. # along with subliminal. If not, see <http://www.gnu.org/licenses/>.
from . import ServiceBase from . import ServiceBase
from ..language import language_set
from ..subtitles import get_subtitle_path, ResultSubtitle from ..subtitles import get_subtitle_path, ResultSubtitle
from ..videos import Episode, Movie, UnknownVideo from ..videos import Episode, Movie, UnknownVideo
from guessit.language import lang_set
import guessit
import logging import logging
@@ -27,13 +26,13 @@ logger = logging.getLogger(__name__)
class TheSubDB(ServiceBase): class TheSubDB(ServiceBase):
server_url = 'http://api.thesubdb.com/' # for testing purpose, use http://sandbox.thesubdb.com/ instead server_url = 'http://api.thesubdb.com'
user_agent = 'SubDB/1.0 (subliminal/0.6; https://github.com/Diaoul/subliminal)' # defined by the API user_agent = 'SubDB/1.0 (subliminal/0.6; https://github.com/Diaoul/subliminal)'
api_based = True api_based = True
languages = lang_set(['af', 'cs', 'da', 'de', 'en', 'es', 'fi', # Source: http://api.thesubdb.com/?action=languages
'fr', 'hu', 'id', 'it', 'la', 'nl', 'no', languages = language_set(['af', 'cs', 'da', 'de', 'en', 'es', 'fi', 'fr', 'hu', 'id', 'it',
'oc', 'pl', 'pt', 'ro', 'ru', 'sl', 'sr', 'la', 'nl', 'no', 'oc', 'pl', 'pt', 'ro', 'ru', 'sl', 'sr', 'sv',
'sv', 'tr'], strict=True) # list available with the API at http://sandbox.thesubdb.com/?action=languages 'tr'])
videos = [Movie, Episode, UnknownVideo] videos = [Movie, Episode, UnknownVideo]
require_video = True require_video = True
@@ -48,7 +47,7 @@ class TheSubDB(ServiceBase):
if r.status_code != 200: if r.status_code != 200:
logger.error(u'Request %s returned status code %d' % (r.url, r.status_code)) logger.error(u'Request %s returned status code %d' % (r.url, r.status_code))
return [] return []
available_languages = set(guessit.Language(l) for l in r.content.split(',')) available_languages = language_set(r.content.split(','))
languages &= available_languages languages &= available_languages
if not languages: if not languages:
logger.debug(u'Could not find subtitles for hash %s with languages %r (only %r available)' % (moviehash, languages, available_languages)) logger.debug(u'Could not find subtitles for hash %s with languages %r (only %r available)' % (moviehash, languages, available_languages))
+8 -13
View File
@@ -17,12 +17,11 @@
# along with subliminal. If not, see <http://www.gnu.org/licenses/>. # along with subliminal. If not, see <http://www.gnu.org/licenses/>.
from . import ServiceBase from . import ServiceBase
from ..cache import cachedmethod from ..cache import cachedmethod
from ..language import language_set, Language
from ..subtitles import get_subtitle_path, ResultSubtitle from ..subtitles import get_subtitle_path, ResultSubtitle
from ..utils import get_keywords
from ..videos import Episode from ..videos import Episode
from bs4 import BeautifulSoup from bs4 import BeautifulSoup
from guessit.language import lang_set
from subliminal.utils import get_keywords
import guessit
import logging import logging
import re import re
@@ -41,12 +40,11 @@ def match(pattern, string):
class TvSubtitles(ServiceBase): class TvSubtitles(ServiceBase):
server_url = 'http://www.tvsubtitles.net' server_url = 'http://www.tvsubtitles.net'
api_based = False api_based = False
languages = lang_set([u'English', u'Español', u'French', u'German', languages = language_set(['ar', 'bg', 'cs', 'da', 'de', 'el', 'en', 'es', 'fi', 'fr', 'hu',
u'Brazilian', u'Russian', u'Ukrainian', u'Italian', 'it', 'ja', 'ko', 'nl', 'pl', 'pt', 'ro', 'ru', 'sv', 'tr', 'uk',
u'Greek', u'Arabic', u'Hungarian', u'Polish', 'zh', 'pt-br'])
u'Turkish', u'Dutch', u'Portuguese', u'Swedish', #TODO: Find more exceptions
u'Danish', u'Finnish', u'Korean', u'Chinese', language_map = {'gr': Language('gre'), 'cz': Language('cze'), 'ua': Language('ukr')}
u'Japanese', u'Bulgarian', u'Czech', u'Romanian'], strict=True)
videos = [Episode] videos = [Episode]
require_video = False require_video = False
required_features = ['permissive'] required_features = ['permissive']
@@ -78,11 +76,9 @@ class TvSubtitles(ServiceBase):
cells = row.find_all('td') cells = row.find_all('td')
if not cells: if not cells:
continue continue
episode_number = match('x([0-9]+)', cells[0].text) episode_number = match('x([0-9]+)', cells[0].text)
if not episode_number: if not episode_number:
continue continue
episode_number = int(episode_number) episode_number = int(episode_number)
episode_id = int(match('episode-([0-9]+)', cells[1].a['href'])) episode_id = int(match('episode-([0-9]+)', cells[1].a['href']))
# we could just return the id of the queried episode, but as we # we could just return the id of the queried episode, but as we
@@ -102,14 +98,13 @@ class TvSubtitles(ServiceBase):
if 'href' not in subdiv.attrs or not subdiv['href'].startswith('/subtitle'): if 'href' not in subdiv.attrs or not subdiv['href'].startswith('/subtitle'):
continue continue
subid = int(match('([0-9]+)', subdiv['href'])) subid = int(match('([0-9]+)', subdiv['href']))
lang = guessit.Language(match('flags/(.*).gif', subdiv.img['src'])) lang = self.get_language(match('flags/(.*).gif', subdiv.img['src']))
result = {'subid': subid, 'language': lang} result = {'subid': subid, 'language': lang}
for p in subdiv.find_all('p'): for p in subdiv.find_all('p'):
if 'alt' in p.attrs and p['alt'] == 'rip': if 'alt' in p.attrs and p['alt'] == 'rip':
result['rip'] = p.text.strip() result['rip'] = p.text.strip()
if 'alt' in p.attrs and p['alt'] == 'release': if 'alt' in p.attrs and p['alt'] == 'release':
result['release'] = p.text.strip() result['release'] = p.text.strip()
subids.append(result) subids.append(result)
return subids return subids
+8 -10
View File
@@ -15,9 +15,8 @@
# #
# You should have received a copy of the GNU Lesser General Public License # You should have received a copy of the GNU Lesser General Public License
# along with subliminal. If not, see <http://www.gnu.org/licenses/>. # along with subliminal. If not, see <http://www.gnu.org/licenses/>.
from .language import Language
import os.path import os.path
import guessit
from guessit.language import is_language
__all__ = ['Subtitle', 'EmbeddedSubtitle', 'ExternalSubtitle', 'ResultSubtitle', 'get_subtitle_path'] __all__ = ['Subtitle', 'EmbeddedSubtitle', 'ExternalSubtitle', 'ResultSubtitle', 'get_subtitle_path']
@@ -31,7 +30,7 @@ class Subtitle(object):
:param string path: path to the subtitle :param string path: path to the subtitle
:param language: language of the subtitle :param language: language of the subtitle
:type language: :class:`guessit.Language` :type language: :class:`~subliminal.language.Language`
""" """
def __init__(self, path, language): def __init__(self, path, language):
@@ -51,7 +50,7 @@ class EmbeddedSubtitle(Subtitle):
:param string path: path to the subtitle :param string path: path to the subtitle
:param language: language of the subtitle :param language: language of the subtitle
:type language: :class:`guessit.Language` :type language: :class:`~subliminal.language.Language`
:param int track_id: id of the subtitle track in the container :param int track_id: id of the subtitle track in the container
""" """
@@ -61,7 +60,7 @@ class EmbeddedSubtitle(Subtitle):
@classmethod @classmethod
def from_enzyme(cls, path, subtitle): def from_enzyme(cls, path, subtitle):
language = guessit.Language(subtitle.language) or None language = Language(subtitle.language) or None
return cls(path, language, subtitle.trackno) return cls(path, language, subtitle.trackno)
@@ -78,8 +77,7 @@ class ExternalSubtitle(Subtitle):
if not extension: if not extension:
raise ValueError('Not a supported subtitle extension') raise ValueError('Not a supported subtitle extension')
language = os.path.splitext(path[:len(path) - len(extension)])[1][1:] language = os.path.splitext(path[:len(path) - len(extension)])[1][1:]
language = guessit.Language(language) or None language = Language(language) or None
return cls(path, language) return cls(path, language)
@@ -88,7 +86,7 @@ class ResultSubtitle(ExternalSubtitle):
:param string path: path to the subtitle :param string path: path to the subtitle
:param language: language of the subtitle :param language: language of the subtitle
:type language: :class:`guessit.Language` :type language: :class:`~subliminal.language.Language`
:param string service: name of the service :param string service: name of the service
:param string link: download link for the subtitle :param string link: download link for the subtitle
:param string release: release name of the video :param string release: release name of the video
@@ -114,7 +112,7 @@ class ResultSubtitle(ExternalSubtitle):
""" """
extension = os.path.splitext(self.path)[0] extension = os.path.splitext(self.path)[0]
language = os.path.splitext(self.path[:len(self.path) - len(extension)])[1][1:] language = os.path.splitext(self.path[:len(self.path) - len(extension)])[1][1:]
return not is_language(language) return Language(language) == Language('und')
def __repr__(self): def __repr__(self):
return 'ResultSubtitle(%s, %s, %.2f, %s)' % (self.language, self.service, self.confidence, self.release) return 'ResultSubtitle(%s, %s, %.2f, %s)' % (self.language, self.service, self.confidence, self.release)
@@ -125,7 +123,7 @@ def get_subtitle_path(video_path, language, multi):
:param string video_path: path to the video :param string video_path: path to the video
:param language: language of the subtitle :param language: language of the subtitle
:type language: :class:`guessit.Language` :type language: :class:`~subliminal.language.Language`
:param bool multi: whether to use multi language naming or not :param bool multi: whether to use multi language naming or not
:return: path of the subtitle :return: path of the subtitle
:rtype: string :rtype: string
+2
View File
@@ -35,6 +35,7 @@ class ListTask(Task):
""" """
def __init__(self, video, languages, service, config): def __init__(self, video, languages, service, config):
super(ListTask, self).__init__()
self.video = video self.video = video
self.service = service self.service = service
self.languages = languages self.languages = languages
@@ -54,6 +55,7 @@ class DownloadTask(Task):
""" """
def __init__(self, video, subtitles): def __init__(self, video, subtitles):
super(DownloadTask, self).__init__()
self.video = video self.video = video
self.subtitles = subtitles self.subtitles = subtitles
+3 -1
View File
@@ -145,12 +145,14 @@ class Video(object):
lang = guessit.Language(possible_lang) lang = guessit.Language(possible_lang)
if lang: if lang:
results.append(subtitles.ExternalSubtitle(path, lang)) results.append(subtitles.ExternalSubtitle(path, lang))
return results return results
def __repr__(self): def __repr__(self):
return '%s(%r)' % (self.__class__.__name__, self.release) return '%s(%r)' % (self.__class__.__name__, self.release)
def __hash__(self):
return hash(self.path or self.release)
class Episode(Video): class Episode(Video):
"""Episode :class:`Video` """Episode :class:`Video`