Library update
This commit is contained in:
@@ -15,11 +15,15 @@
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with subliminal. If not, see <http://www.gnu.org/licenses/>.
|
||||
from ..exceptions import MissingLanguageError, DownloadFailedError
|
||||
from .. import cache
|
||||
from ..exceptions import MissingLanguageError, DownloadFailedError, ServiceError
|
||||
from ..subtitles import EXTENSIONS
|
||||
from guessit.language import lang_set, UNDETERMINED
|
||||
import logging
|
||||
import os
|
||||
import requests
|
||||
import threading
|
||||
import zipfile
|
||||
|
||||
|
||||
__all__ = ['ServiceBase', 'ServiceConfig']
|
||||
@@ -37,7 +41,7 @@ class ServiceBase(object):
|
||||
server_url = ''
|
||||
|
||||
#: User Agent for any HTTP-based requests
|
||||
user_agent = 'subliminal v0.5'
|
||||
user_agent = 'subliminal v0.6'
|
||||
|
||||
#: Whether based on an API or not
|
||||
api_based = False
|
||||
@@ -45,21 +49,18 @@ class ServiceBase(object):
|
||||
#: Timeout for web requests
|
||||
timeout = 5
|
||||
|
||||
#: Lock for cache interactions
|
||||
lock = threading.Lock()
|
||||
|
||||
#: Mapping to Service's language codes and subliminal's
|
||||
languages = {}
|
||||
|
||||
#: Whether the mapping is reverted or not
|
||||
reverted_languages = False
|
||||
|
||||
#: Accepted video classes (:class:`~subliminal.videos.Episode`, :class:`~subliminal.videos.Movie`, :class:`~subliminal.videos.UnknownVideo`)
|
||||
videos = []
|
||||
|
||||
#: Whether the video has to exist or not
|
||||
require_video = False
|
||||
|
||||
#: List of required features for BeautifulSoup
|
||||
required_features = None
|
||||
|
||||
def __init__(self, config=None):
|
||||
self.config = config or ServiceConfig()
|
||||
|
||||
@@ -75,6 +76,30 @@ class ServiceBase(object):
|
||||
logger.debug(u'Initializing %s' % self.__class__.__name__)
|
||||
self.session = requests.session(timeout=10, headers={'User-Agent': self.user_agent})
|
||||
|
||||
def init_cache(self):
|
||||
"""Initialize cache, make sure it is loaded from disk"""
|
||||
if not self.config or not self.config.cache:
|
||||
raise ServiceError('Cache directory is required')
|
||||
|
||||
service_name = self.__class__.__name__
|
||||
self.config.cache.load(service_name)
|
||||
|
||||
def save_cache(self):
|
||||
service_name = self.__class__.__name__
|
||||
self.config.cache.save(service_name)
|
||||
|
||||
def clear_cache(self):
|
||||
service_name = self.__class__.__name__
|
||||
self.config.cache.clear(service_name)
|
||||
|
||||
def cache_for(self, func, args, result):
|
||||
service_name = self.__class__.__name__
|
||||
return self.config.cache.cache_for(service_name, func, args, result)
|
||||
|
||||
def cached_value(self, func, args):
|
||||
service_name = self.__class__.__name__
|
||||
return self.config.cache.cached_value(service_name, func, args)
|
||||
|
||||
def terminate(self):
|
||||
"""Terminate connection"""
|
||||
logger.debug(u'Terminating %s' % self.__class__.__name__)
|
||||
@@ -84,25 +109,20 @@ class ServiceBase(object):
|
||||
pass
|
||||
|
||||
def list(self, video, languages):
|
||||
"""List subtitles"""
|
||||
pass
|
||||
"""List subtitles
|
||||
|
||||
As a service writer, you can either override this method or implement
|
||||
:meth:`list_checked` instead to have the languages pre-filtered for you
|
||||
|
||||
"""
|
||||
if not self.check_validity(video, languages):
|
||||
return []
|
||||
return self.list_checked(video, languages)
|
||||
|
||||
def download(self, subtitle):
|
||||
"""Download a subtitle"""
|
||||
self.download_file(subtitle.link, subtitle.path)
|
||||
|
||||
@classmethod
|
||||
def available_languages(cls):
|
||||
"""Available languages in the Service
|
||||
|
||||
:return: available languages
|
||||
:rtype: set
|
||||
|
||||
"""
|
||||
if not cls.reverted_languages:
|
||||
return set(cls.languages.keys())
|
||||
return set(cls.languages.values())
|
||||
|
||||
@classmethod
|
||||
def check_validity(cls, video, languages):
|
||||
"""Check for video and languages validity in the Service
|
||||
@@ -113,72 +133,15 @@ class ServiceBase(object):
|
||||
:rtype: bool
|
||||
|
||||
"""
|
||||
languages &= cls.available_languages()
|
||||
languages = (lang_set(languages) & cls.languages) - set([UNDETERMINED])
|
||||
if not languages:
|
||||
logger.debug(u'No language available for service %s' % cls.__class__.__name__.lower())
|
||||
logger.debug(u'No language available for service %s' % cls.__name__.lower())
|
||||
return False
|
||||
if not cls.is_valid_video(video):
|
||||
logger.debug(u'%r is not valid for service %s' % (video, cls.__class__.__name__.lower()))
|
||||
if cls.require_video and not video.exists or not isinstance(video, tuple(cls.videos)):
|
||||
logger.debug(u'%r is not valid for service %s' % (video, cls.__name__.lower()))
|
||||
return False
|
||||
return True
|
||||
|
||||
@classmethod
|
||||
def is_valid_video(cls, video):
|
||||
"""Check if video is valid in the Service
|
||||
|
||||
:param video: the video to check
|
||||
:type video: :class:`~subliminal.videos.Video`
|
||||
:rtype: bool
|
||||
|
||||
"""
|
||||
if cls.require_video and not video.exists:
|
||||
return False
|
||||
if not isinstance(video, tuple(cls.videos)):
|
||||
return False
|
||||
return True
|
||||
|
||||
@classmethod
|
||||
def is_valid_language(cls, language):
|
||||
"""Check if language is valid in the Service
|
||||
|
||||
:param string language: the language to check
|
||||
:rtype: bool
|
||||
|
||||
"""
|
||||
if language in cls.available_languages():
|
||||
return True
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
def get_revert_language(cls, language):
|
||||
"""ISO-639-1 language code from service language code
|
||||
|
||||
:param string language: service language code
|
||||
:return: ISO-639-1 language code
|
||||
:rtype: string
|
||||
|
||||
"""
|
||||
if not cls.reverted_languages and language in cls.languages.values():
|
||||
return [k for k, v in cls.languages.iteritems() if v == language][0]
|
||||
if cls.reverted_languages and language in cls.languages.keys():
|
||||
return cls.languages[language]
|
||||
raise MissingLanguageError(language)
|
||||
|
||||
@classmethod
|
||||
def get_language(cls, language):
|
||||
"""Service language code from ISO-639-1 language code
|
||||
|
||||
:param string language: ISO-639-1 language code
|
||||
:return: service language code
|
||||
:rtype: string
|
||||
|
||||
"""
|
||||
if not cls.reverted_languages and language in cls.languages.keys():
|
||||
return cls.languages[language]
|
||||
if cls.reverted_languages and language in cls.languages.values():
|
||||
return [k for k, v in cls.languages.iteritems() if v == language][0]
|
||||
raise MissingLanguageError(language)
|
||||
|
||||
def download_file(self, url, filepath):
|
||||
"""Attempt to download a file and remove it in case of failure
|
||||
|
||||
@@ -198,6 +161,43 @@ class ServiceBase(object):
|
||||
raise DownloadFailedError(str(e))
|
||||
logger.debug(u'Download finished for file %s. Size: %s' % (filepath, os.path.getsize(filepath)))
|
||||
|
||||
def download_zip_file(self, url, filepath):
|
||||
"""Attempt to download a zip file and extract any subtitle file from it, if any.
|
||||
This cleans up after itself if anything fails.
|
||||
|
||||
:param string url: URL of the zip file to download
|
||||
:param string filepath: destination path for the subtitle
|
||||
|
||||
"""
|
||||
logger.info(u'Downloading %s' % url)
|
||||
try:
|
||||
zippath = filepath + '.zip'
|
||||
r = self.session.get(url, headers={'Referer': url, 'User-Agent': self.user_agent})
|
||||
with open(zippath, 'wb') as f:
|
||||
f.write(r.content)
|
||||
if not zipfile.is_zipfile(zippath):
|
||||
# TODO: could check if maybe we already have a text file and
|
||||
# download it directly
|
||||
raise DownloadFailedError('Downloaded file is not a zip file')
|
||||
zipsub = zipfile.ZipFile(zippath)
|
||||
for subfile in zipsub.namelist():
|
||||
if os.path.splitext(subfile)[1] in EXTENSIONS:
|
||||
open(filepath, 'w').write(zipsub.open(subfile).read())
|
||||
break
|
||||
else:
|
||||
logger.debug(u'No subtitles found in zip file')
|
||||
raise DownloadFailedError('No subtitles found in zip file')
|
||||
os.remove(zippath)
|
||||
logger.debug(u'Download finished for file %s. Size: %s' % (filepath, os.path.getsize(filepath)))
|
||||
return
|
||||
except Exception as e:
|
||||
logger.error(u'Download %s failed: %s' % (url, e))
|
||||
if os.path.exists(zippath):
|
||||
os.remove(zippath)
|
||||
if os.path.exists(filepath):
|
||||
os.remove(filepath)
|
||||
raise DownloadFailedError(str(e))
|
||||
|
||||
|
||||
class ServiceConfig(object):
|
||||
"""Configuration for any :class:`Service`
|
||||
@@ -209,6 +209,9 @@ class ServiceConfig(object):
|
||||
def __init__(self, multi=False, cache_dir=None):
|
||||
self.multi = multi
|
||||
self.cache_dir = cache_dir
|
||||
self.cache = None
|
||||
if cache_dir is not None:
|
||||
self.cache = cache.Cache(cache_dir)
|
||||
|
||||
def __repr__(self):
|
||||
return 'ServiceConfig(%r, %s)' % (self.multi, self.cache_dir)
|
||||
return 'ServiceConfig(%r, %s)' % (self.multi, self.cache.cache_dir)
|
||||
|
||||
Executable
+161
@@ -0,0 +1,161 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright 2012 Olivier Leveau <olifozzy@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 GNU Lesser 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
|
||||
# GNU Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with subliminal. If not, see <http://www.gnu.org/licenses/>.
|
||||
from . import ServiceBase
|
||||
from ..cache import cachedmethod
|
||||
from ..subtitles import get_subtitle_path, ResultSubtitle
|
||||
from ..videos import Episode
|
||||
from bs4 import BeautifulSoup
|
||||
from guessit.language import lang_set
|
||||
from subliminal.utils import get_keywords
|
||||
import guessit
|
||||
import logging
|
||||
import re
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def match(pattern, string):
|
||||
try:
|
||||
return re.search(pattern, string).group(1)
|
||||
except AttributeError:
|
||||
logger.debug(u'Could not match %r on %r' % (pattern, string))
|
||||
return None
|
||||
|
||||
|
||||
def matches(pattern, string):
|
||||
try:
|
||||
return re.search(pattern, string).group(1, 2)
|
||||
except AttributeError:
|
||||
logger.debug(u'Could not match %r on %r' % (pattern, string))
|
||||
return None
|
||||
|
||||
|
||||
class Addic7ed(ServiceBase):
|
||||
server_url = 'http://www.addic7ed.com'
|
||||
api_based = False
|
||||
languages = lang_set([u'English', u'Italian', u'Portuguese',
|
||||
u'Portuguese (Brazilian)', u'Romanian',
|
||||
u'Spanish', u'French', u'Greek', u'Arabic',
|
||||
u'German', u'Croatian', u'Indonesian', u'Hebrew',
|
||||
u'Russian', u'Turkish', u'Swedish', u'Czech',
|
||||
u'Dutch', u'Hungarian', u'Norwegian', u'Polish',
|
||||
u'Persian'], strict=True)
|
||||
videos = [Episode]
|
||||
require_video = False
|
||||
required_features = ['permissive']
|
||||
|
||||
@cachedmethod
|
||||
def get_likely_series_id(self, name):
|
||||
r = self.session.get('%s/shows.php' % self.server_url)
|
||||
soup = BeautifulSoup(r.content, self.required_features)
|
||||
for elem in soup.find_all('h3'):
|
||||
show_name = elem.a.text.lower()
|
||||
show_id = int(match('show/([0-9]+)', elem.a['href']))
|
||||
# we could just return the id of the queried show, but as we
|
||||
# already downloaded the whole page we might as well fill in the
|
||||
# information for all the shows
|
||||
self.cache_for(self.get_likely_series_id, args=(show_name,), result=show_id)
|
||||
return self.cached_value(self.get_likely_series_id, args=(name,))
|
||||
|
||||
@cachedmethod
|
||||
def get_episode_url(self, series_id, season, number):
|
||||
"""Get the Addic7ed id for the given episode. Raises KeyError if none
|
||||
could be found
|
||||
|
||||
"""
|
||||
# download the page of the show, contains ids for all episodes all seasons
|
||||
r = self.session.get('%s/show/%d' % (self.server_url, series_id))
|
||||
soup = BeautifulSoup(r.content, self.required_features)
|
||||
form = soup.find('form', attrs={'name': 'multidl'})
|
||||
for table in form.find_all('table'):
|
||||
for row in table.find_all('tr'):
|
||||
cell = row.find('td', 'MultiDldS')
|
||||
if not cell:
|
||||
continue
|
||||
m = matches('/serie/.+/([0-9]+)/([0-9]+)/', cell.a['href'])
|
||||
if not m:
|
||||
continue
|
||||
episode_url = cell.a['href']
|
||||
season_number = int(m[0])
|
||||
episode_number = int(m[1])
|
||||
# we could just return the url of the queried episode, but as we
|
||||
# already downloaded the whole page we might as well fill in the
|
||||
# information for all the episodes of the show
|
||||
self.cache_for(self.get_episode_url, args=(series_id, season_number, episode_number), result=episode_url)
|
||||
# raises KeyError if not found
|
||||
return self.cached_value(self.get_episode_url, args=(series_id, season, number))
|
||||
|
||||
# Do not cache this method in order to always check for the most recent
|
||||
# subtitles
|
||||
def get_sub_urls(self, episode_url):
|
||||
suburls = []
|
||||
r = self.session.get('%s/%s' % (self.server_url, episode_url))
|
||||
epsoup = BeautifulSoup(r.content, self.required_features)
|
||||
for releaseTable in epsoup.find_all('table', 'tabel95'):
|
||||
releaseRow = releaseTable.find('td', 'NewsTitle')
|
||||
if not releaseRow:
|
||||
continue
|
||||
release = releaseRow.text.strip()
|
||||
for row in releaseTable.find_all('tr'):
|
||||
link = row.find('a', 'buttonDownload')
|
||||
if not link:
|
||||
continue
|
||||
if 'href' not in link.attrs or not (link['href'].startswith('/original') or link['href'].startswith('/updated')):
|
||||
continue
|
||||
suburl = link['href']
|
||||
lang = guessit.Language(row.find('td', 'language').text.strip())
|
||||
result = {'suburl': suburl, 'language': lang, 'release': release}
|
||||
suburls.append(result)
|
||||
return suburls
|
||||
|
||||
def list_checked(self, video, languages):
|
||||
return self.query(video.path or video.release, languages, get_keywords(video.guess), video.series, video.season, video.episode)
|
||||
|
||||
def query(self, filepath, languages, keywords, series, season, episode):
|
||||
logger.debug(u'Getting subtitles for %s season %d episode %d with languages %r' % (series, season, episode, languages))
|
||||
self.init_cache()
|
||||
try:
|
||||
sid = self.get_likely_series_id(series.lower())
|
||||
except KeyError:
|
||||
logger.debug(u'Could not find series id for %s' % series)
|
||||
return []
|
||||
|
||||
try:
|
||||
ep_url = self.get_episode_url(sid, season, episode)
|
||||
except KeyError:
|
||||
logger.debug(u'Could not find episode id for %s season %d episode %d' % (series, season, episode))
|
||||
return []
|
||||
suburls = self.get_sub_urls(ep_url)
|
||||
|
||||
# filter the subtitles with our queried languages
|
||||
subtitles = []
|
||||
for suburl in suburls:
|
||||
language = suburl['language']
|
||||
if language not in languages:
|
||||
continue
|
||||
|
||||
path = get_subtitle_path(filepath, language, self.config.multi)
|
||||
subtitle = ResultSubtitle(path, language, self.__class__.__name__.lower(),
|
||||
'%s/%s' % (self.server_url, suburl['suburl']),
|
||||
keywords=[suburl['release']])
|
||||
subtitles.append(subtitle)
|
||||
return subtitles
|
||||
|
||||
|
||||
Service = Addic7ed
|
||||
@@ -16,13 +16,14 @@
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with subliminal. If not, see <http://www.gnu.org/licenses/>.
|
||||
from . import ServiceBase
|
||||
from ..cache import cachedmethod
|
||||
from ..exceptions import ServiceError
|
||||
from ..subtitles import get_subtitle_path, ResultSubtitle
|
||||
from ..videos import Episode
|
||||
from ..utils import to_unicode
|
||||
import BeautifulSoup
|
||||
from ..videos import Episode
|
||||
from bs4 import BeautifulSoup
|
||||
from guessit.language import lang_set
|
||||
import logging
|
||||
import os.path
|
||||
import urllib
|
||||
try:
|
||||
import cPickle as pickle
|
||||
@@ -36,30 +37,23 @@ logger = logging.getLogger(__name__)
|
||||
class BierDopje(ServiceBase):
|
||||
server_url = 'http://api.bierdopje.com/A2B638AC5D804C2E/'
|
||||
api_based = True
|
||||
languages = {'en': 'en', 'nl': 'nl'}
|
||||
reverted_languages = False
|
||||
languages = lang_set(['en', 'nl'])
|
||||
videos = [Episode]
|
||||
require_video = False
|
||||
required_features = ['xml']
|
||||
|
||||
def __init__(self, config=None):
|
||||
super(BierDopje, self).__init__(config)
|
||||
self.showids = {}
|
||||
if self.config and self.config.cache_dir:
|
||||
self.init_cache()
|
||||
@cachedmethod
|
||||
def get_show_id(self, series):
|
||||
r = self.session.get('%sGetShowByName/%s' % (self.server_url, urllib.quote(series.lower())))
|
||||
if r.status_code != 200:
|
||||
logger.error(u'Request %s returned status code %d' % (r.url, r.status_code))
|
||||
return None
|
||||
soup = BeautifulSoup(r.content, self.required_features)
|
||||
if soup.status.contents[0] == 'false':
|
||||
logger.debug(u'Could not find show %s' % series)
|
||||
return None
|
||||
|
||||
def init_cache(self):
|
||||
logger.debug(u'Initializing cache...')
|
||||
if not self.config or not self.config.cache_dir:
|
||||
raise ServiceError('Cache directory is required')
|
||||
self.showids_cache = os.path.join(self.config.cache_dir, 'bierdopje_showids.cache')
|
||||
if not os.path.exists(self.showids_cache):
|
||||
self.save_cache()
|
||||
|
||||
def save_cache(self):
|
||||
logger.debug(u'Saving showids to cache...')
|
||||
with self.lock:
|
||||
with open(self.showids_cache, 'w') as f:
|
||||
pickle.dump(self.showids, f)
|
||||
return int(soup.showid.contents[0])
|
||||
|
||||
def load_cache(self):
|
||||
logger.debug(u'Loading showids from cache...')
|
||||
@@ -67,25 +61,12 @@ class BierDopje(ServiceBase):
|
||||
with open(self.showids_cache, 'r') as f:
|
||||
self.showids = pickle.load(f)
|
||||
|
||||
def query(self, season, episode, languages, filepath, tvdbid=None, series=None):
|
||||
self.load_cache()
|
||||
def query(self, filepath, season, episode, languages, tvdbid=None, series=None):
|
||||
self.init_cache()
|
||||
if series:
|
||||
if series.lower() in self.showids: # from cache
|
||||
request_id = self.showids[series.lower()]
|
||||
logger.debug(u'Retreived showid %d for %s from cache' % (request_id, series))
|
||||
else: # query to get showid
|
||||
logger.debug(u'Getting showid from show name %s...' % series)
|
||||
r = self.session.get('%sGetShowByName/%s' % (self.server_url, urllib.quote(series.lower())))
|
||||
if r.status_code != 200:
|
||||
logger.error(u'Request %s returned status code %d' % (r.url, r.status_code))
|
||||
return []
|
||||
soup = BeautifulSoup.BeautifulStoneSoup(r.content)
|
||||
if soup.status.contents[0] == 'false':
|
||||
logger.debug(u'Could not find show %s' % series)
|
||||
return []
|
||||
request_id = int(soup.showid.contents[0])
|
||||
self.showids[series.lower()] = request_id
|
||||
self.save_cache()
|
||||
request_id = self.get_show_id(series.lower())
|
||||
if request_id is None:
|
||||
return []
|
||||
request_source = 'showid'
|
||||
request_is_tvdbid = 'false'
|
||||
elif tvdbid:
|
||||
@@ -96,14 +77,14 @@ class BierDopje(ServiceBase):
|
||||
raise ServiceError('One or more parameter missing')
|
||||
subtitles = []
|
||||
for language in languages:
|
||||
logger.debug(u'Getting subtitles for %s %d season %d episode %d with language %s' % (request_source, request_id, season, episode, language))
|
||||
r = self.session.get('%sGetAllSubsFor/%s/%s/%s/%s/%s' % (self.server_url, request_id, season, episode, language, request_is_tvdbid))
|
||||
logger.debug(u'Getting subtitles for %s %d season %d episode %d with language %s' % (request_source, request_id, season, episode, language.alpha2))
|
||||
r = self.session.get('%sGetAllSubsFor/%s/%s/%s/%s/%s' % (self.server_url, request_id, season, episode, language.alpha2, request_is_tvdbid))
|
||||
if r.status_code != 200:
|
||||
logger.error(u'Request %s returned status code %d' % (r.url, r.status_code))
|
||||
return []
|
||||
soup = BeautifulSoup.BeautifulStoneSoup(r.content)
|
||||
soup = BeautifulSoup(r.content, self.required_features)
|
||||
if soup.status.contents[0] == 'false':
|
||||
logger.debug(u'Could not find subtitles for %s %d season %d episode %d with language %s' % (request_source, request_id, season, episode, language))
|
||||
logger.debug(u'Could not find subtitles for %s %d season %d episode %d with language %s' % (request_source, request_id, season, episode, language.alpha2))
|
||||
continue
|
||||
path = get_subtitle_path(filepath, language, self.config.multi)
|
||||
for result in soup.results('result'):
|
||||
@@ -112,11 +93,8 @@ class BierDopje(ServiceBase):
|
||||
subtitles.append(subtitle)
|
||||
return subtitles
|
||||
|
||||
def list(self, video, languages):
|
||||
if not self.check_validity(video, languages):
|
||||
return []
|
||||
results = self.query(video.season, video.episode, languages, video.path or video.release, video.tvdbid, video.series)
|
||||
return results
|
||||
def list_checked(self, video, languages):
|
||||
return self.query(video.path or video.release, video.season, video.episode, languages, video.tvdbid, video.series)
|
||||
|
||||
|
||||
Service = BierDopje
|
||||
|
||||
@@ -18,8 +18,10 @@
|
||||
from . import ServiceBase
|
||||
from ..exceptions import ServiceError, DownloadFailedError
|
||||
from ..subtitles import get_subtitle_path, ResultSubtitle
|
||||
from ..videos import Episode, Movie
|
||||
from ..utils import to_unicode
|
||||
from ..videos import Episode, Movie
|
||||
from guessit.language import lang_set
|
||||
import guessit
|
||||
import gzip
|
||||
import logging
|
||||
import os.path
|
||||
@@ -32,34 +34,71 @@ logger = logging.getLogger(__name__)
|
||||
class OpenSubtitles(ServiceBase):
|
||||
server_url = 'http://api.opensubtitles.org/xml-rpc'
|
||||
api_based = True
|
||||
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', 'po': 'pob', 'un': 'unk', 'ay': 'ass'}
|
||||
reverted_languages = False
|
||||
# language list fetched from:
|
||||
# http://www.opensubtitles.org/addons/export_languages.php
|
||||
languages = lang_set(['aar', 'abk', 'ace', 'ach', 'ada', 'ady', 'afa', 'afh',
|
||||
'afr', 'ain', 'aka', 'akk', 'alb', 'ale', 'alg', 'alt',
|
||||
'amh', 'ang', 'apa', 'ara', 'arc', 'arg', 'arm', 'arn',
|
||||
'arp', 'art', 'arw', 'asm', 'ast', 'ath', 'aus', 'ava',
|
||||
'ave', 'awa', 'aym', 'aze', 'bad', 'bai', 'bak', 'bal',
|
||||
'bam', 'ban', 'baq', 'bas', 'bat', 'bej', 'bel', 'bem',
|
||||
'ben', 'ber', 'bho', 'bih', 'bik', 'bin', 'bis', 'bla',
|
||||
'bnt', 'bod', 'bos', 'bra', 'bre', 'btk', 'bua', 'bug',
|
||||
'bul', 'bur', 'byn', 'cad', 'cai', 'car', 'cat', 'cau',
|
||||
'ceb', 'cel', 'cha', 'chb', 'che', 'chg', 'chi', 'chk',
|
||||
'chm', 'chn', 'cho', 'chp', 'chr', 'chu', 'chv', 'chy',
|
||||
'cmc', 'cop', 'cor', 'cos', 'cpe', 'cpf', 'cpp', 'cre',
|
||||
'crh', 'crp', 'csb', 'cus', 'cym', 'cze', 'dak', 'dan',
|
||||
'dar', 'day', 'del', 'den', 'deu', 'dgr', 'din', 'div',
|
||||
'doi', 'dra', 'dua', 'dum', 'dut', 'dyu', 'dzo', 'efi',
|
||||
'egy', 'eka', 'elx', 'eng', 'enm', 'epo', 'est', 'eus',
|
||||
'ewe', 'ewo', 'fan', 'fao', 'fas', 'fat', 'fij', 'fil',
|
||||
'fin', 'fiu', 'fon', 'fra', 'fre', 'frm', 'fro', 'fry',
|
||||
'ful', 'fur', 'gaa', 'gay', 'gba', 'gem', 'geo', 'ger',
|
||||
'gez', 'gil', 'gla', 'gle', 'glg', 'glv', 'gmh', 'goh',
|
||||
'gon', 'gor', 'got', 'grb', 'grc', 'ell', 'grn', 'guj',
|
||||
'gwi', 'hai', 'hat', 'hau', 'haw', 'heb', 'her', 'hil',
|
||||
'him', 'hin', 'hit', 'hmn', 'hmo', 'hrv', 'hun', 'hup',
|
||||
'hye', 'iba', 'ibo', 'ice', 'ido', 'iii', 'ijo', 'iku',
|
||||
'ile', 'ilo', 'ina', 'inc', 'ind', 'ine', 'inh', 'ipk',
|
||||
'ira', 'iro', 'isl', 'ita', 'jav', 'jpn', 'jpr', 'jrb',
|
||||
'kaa', 'kab', 'kac', 'kal', 'kam', 'kan', 'kar', 'kas',
|
||||
'kat', 'kau', 'kaw', 'kaz', 'kbd', 'kha', 'khi', 'khm',
|
||||
'kho', 'kik', 'kin', 'kir', 'kmb', 'kok', 'kom', 'kon',
|
||||
'kor', 'kos', 'kpe', 'krc', 'kro', 'kru', 'kua', 'kum',
|
||||
'kur', 'kut', 'lad', 'lah', 'lam', 'lao', 'lat', 'lav',
|
||||
'lez', 'lim', 'lin', 'lit', 'lol', 'loz', 'ltz', 'lua',
|
||||
'lub', 'lug', 'lui', 'lun', 'luo', 'lus', 'mac', 'mad',
|
||||
'mag', 'mah', 'mai', 'mak', 'mal', 'man', 'mao', 'map',
|
||||
'mar', 'mas', 'may', 'mdf', 'mdr', 'men', 'mga', 'mic',
|
||||
'min', 'mis', 'mkd', 'mkh', 'mlg', 'mlt', 'mnc', 'mni',
|
||||
'mno', 'moh', 'mol', 'mon', 'mos', 'mri', 'msa', 'mwl',
|
||||
'mul', 'mun', 'mus', 'mwr', 'mya', 'myn', 'myv', 'nah',
|
||||
'nai', 'nap', 'nau', 'nav', 'nbl', 'nde', 'ndo', 'nds',
|
||||
'nep', 'new', 'nia', 'nic', 'niu', 'nld', 'nno', 'nob',
|
||||
'nog', 'non', 'nor', 'nso', 'nub', 'nwc', 'nya', 'nym',
|
||||
'nyn', 'nyo', 'nzi', 'oci', 'oji', 'ori', 'orm', 'osa',
|
||||
'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]
|
||||
require_video = False
|
||||
confidence_order = ['moviehash', 'imdbid', 'fulltext']
|
||||
@@ -92,7 +131,7 @@ class OpenSubtitles(ServiceBase):
|
||||
if not searches:
|
||||
raise ServiceError('One or more parameter missing')
|
||||
for search in searches:
|
||||
search['sublanguageid'] = ','.join([self.get_language(l) for l in languages])
|
||||
search['sublanguageid'] = ','.join(l.opensubtitles for l in languages)
|
||||
logger.debug(u'Getting subtitles %r with token %s' % (searches, self.token))
|
||||
results = self.server.SearchSubtitles(self.token, searches)
|
||||
if not results['data']:
|
||||
@@ -100,7 +139,7 @@ class OpenSubtitles(ServiceBase):
|
||||
return []
|
||||
subtitles = []
|
||||
for result in results['data']:
|
||||
language = self.get_revert_language(result['SubLanguageID'])
|
||||
language = guessit.Language(result['SubLanguageID'])
|
||||
path = get_subtitle_path(filepath, language, self.config.multi)
|
||||
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'],
|
||||
@@ -108,9 +147,7 @@ class OpenSubtitles(ServiceBase):
|
||||
subtitles.append(subtitle)
|
||||
return subtitles
|
||||
|
||||
def list(self, video, languages):
|
||||
if not self.check_validity(video, languages):
|
||||
return []
|
||||
def list_checked(self, video, languages):
|
||||
results = []
|
||||
if video.exists:
|
||||
results = self.query(video.path or video.release, languages, moviehash=video.hashes['OpenSubtitles'], size=str(video.size))
|
||||
|
||||
Executable
+106
@@ -0,0 +1,106 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright 2011-2012 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 GNU Lesser 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
|
||||
# GNU Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with subliminal. If not, see <http://www.gnu.org/licenses/>.
|
||||
from . import ServiceBase
|
||||
from ..exceptions import ServiceError, DownloadFailedError
|
||||
from ..subtitles import get_subtitle_path, ResultSubtitle
|
||||
from ..utils import to_unicode
|
||||
from ..videos import Episode, Movie
|
||||
from guessit.language import lang_set
|
||||
from hashlib import md5, sha256
|
||||
import guessit
|
||||
import logging
|
||||
import xmlrpclib
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Podnapisi(ServiceBase):
|
||||
server_url = 'http://ssp.podnapisi.net:8000'
|
||||
api_based = True
|
||||
languages = lang_set(['sl', 'en', 'nn', 'ko', 'de', 'is', 'cs', 'fr', 'it', 'bs', 'jp', 'ar', 'ro',
|
||||
'hu', 'gr', 'zh', 'lt', 'et', 'lv', 'he', 'nl', 'da', 'sv', 'pl', 'ru', 'es',
|
||||
'sq', 'tr', 'fi', 'pt', 'bg', 'mk', 'sr', 'sk', 'hr', 'hi', 'th', 'ca', 'uk',
|
||||
'pb', 'ga', 'be', 'vi', 'fa', 'ca', 'id', 'ms'])
|
||||
#FIXME: ag and cyr not recognized by guessit
|
||||
videos = [Episode, Movie]
|
||||
require_video = True
|
||||
|
||||
def __init__(self, config=None):
|
||||
super(Podnapisi, self).__init__(config)
|
||||
self.server = xmlrpclib.ServerProxy(self.server_url)
|
||||
self.token = None
|
||||
|
||||
def init(self):
|
||||
super(Podnapisi, self).init()
|
||||
result = self.server.initiate(self.user_agent)
|
||||
if result['status'] != 200:
|
||||
raise ServiceError('Initiate failed')
|
||||
username = 'python_subliminal'
|
||||
password = sha256(md5('XWFXQ6gE5Oe12rv4qxXX').hexdigest() + result['nonce']).hexdigest()
|
||||
self.token = result['session']
|
||||
result = self.server.authenticate(self.token, username, password)
|
||||
if result['status'] != 200:
|
||||
raise ServiceError('Authenticate failed')
|
||||
|
||||
def terminate(self):
|
||||
super(Podnapisi, self).terminate()
|
||||
|
||||
def query(self, filepath, languages, moviehash):
|
||||
results = self.server.search(self.token, [moviehash])
|
||||
if results['status'] != 200:
|
||||
logger.error('Search failed with error code %d' % results['status'])
|
||||
return []
|
||||
if not results['results'] or not results['results'][moviehash]['subtitles']:
|
||||
logger.debug(u'Could not find subtitles for %r with token %s' % (moviehash, self.token))
|
||||
return []
|
||||
subtitles = []
|
||||
for result in results['results'][moviehash]['subtitles']:
|
||||
language = guessit.Language(result['lang'])
|
||||
if language == guessit.language.UNDETERMINED or language not in languages:
|
||||
continue
|
||||
path = get_subtitle_path(filepath, language, self.config.multi)
|
||||
subtitle = ResultSubtitle(path, language, service=self.__class__.__name__.lower(), link=result['id'],
|
||||
release=to_unicode(result['release']), confidence=result['weight'])
|
||||
subtitles.append(subtitle)
|
||||
if not subtitles:
|
||||
return []
|
||||
# Convert weight to confidence
|
||||
max_weight = float(max([s.confidence for s in subtitles]))
|
||||
min_weight = float(min([s.confidence for s in subtitles]))
|
||||
for subtitle in subtitles:
|
||||
if max_weight == 0 and min_weight == 0:
|
||||
subtitle.confidence = 1.0
|
||||
else:
|
||||
subtitle.confidence = (subtitle.confidence - min_weight) / (max_weight - min_weight)
|
||||
return subtitles
|
||||
|
||||
def list_checked(self, video, languages):
|
||||
results = self.query(video.path, languages, video.hashes['OpenSubtitles'])
|
||||
return results
|
||||
|
||||
def download(self, subtitle):
|
||||
results = self.server.download(self.token, [subtitle.link])
|
||||
if results['status'] != 200:
|
||||
raise DownloadFailedError()
|
||||
subtitle.link = 'http://www.podnapisi.net/static/podnapisi/' + results['names'][0]['filename']
|
||||
self.download_file(subtitle.link, subtitle.path)
|
||||
return subtitle
|
||||
|
||||
|
||||
Service = Podnapisi
|
||||
@@ -19,8 +19,10 @@ from . import ServiceBase
|
||||
from ..exceptions import ServiceError
|
||||
from ..subtitles import get_subtitle_path, ResultSubtitle
|
||||
from ..videos import Episode, Movie
|
||||
from bs4 import BeautifulSoup
|
||||
from guessit.language import lang_set
|
||||
from subliminal.utils import get_keywords, split_keyword
|
||||
import BeautifulSoup
|
||||
import guessit
|
||||
import logging
|
||||
import re
|
||||
import urllib
|
||||
@@ -32,17 +34,15 @@ logger = logging.getLogger(__name__)
|
||||
class SubsWiki(ServiceBase):
|
||||
server_url = 'http://www.subswiki.com'
|
||||
api_based = False
|
||||
languages = {u'English (US)': 'en', u'English (UK)': 'en', u'English': 'en', u'French': 'fr', u'Brazilian': 'po',
|
||||
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'}
|
||||
reverted_languages = True
|
||||
languages = lang_set([u'English (US)', u'English (UK)', u'English', u'French', u'Brazilian',
|
||||
u'Portuguese', u'Español (Latinoamérica)', u'Español (España)',
|
||||
u'Español', u'Italian', u'Català'], strict=True)
|
||||
videos = [Episode, Movie]
|
||||
require_video = False
|
||||
release_pattern = re.compile('\nVersion (.+), ([0-9]+).([0-9])+ MBs')
|
||||
required_features = ['permissive']
|
||||
|
||||
def list(self, video, languages):
|
||||
if not self.check_validity(video, languages):
|
||||
return []
|
||||
def list_checked(self, video, languages):
|
||||
results = []
|
||||
if isinstance(video, Episode):
|
||||
results = self.query(video.path or video.release, languages, get_keywords(video.guess), series=video.series, season=video.season, episode=video.episode)
|
||||
@@ -74,7 +74,7 @@ class SubsWiki(ServiceBase):
|
||||
if r.status_code != 200:
|
||||
logger.error(u'Request %s returned status code %d' % (r.url, r.status_code))
|
||||
return []
|
||||
soup = BeautifulSoup.BeautifulSoup(r.content)
|
||||
soup = BeautifulSoup(r.content, self.required_features)
|
||||
subtitles = []
|
||||
for sub in soup('td', {'class': 'NewsTitle'}):
|
||||
sub_keywords = split_keyword(self.release_pattern.search(sub.contents[1]).group(1).lower())
|
||||
@@ -82,8 +82,8 @@ class SubsWiki(ServiceBase):
|
||||
logger.debug(u'None of subtitle keywords %r in %r' % (sub_keywords, keywords))
|
||||
continue
|
||||
for html_language in sub.parent.parent.findAll('td', {'class': 'language'}):
|
||||
language = self.get_revert_language(html_language.string.strip())
|
||||
if not language in languages:
|
||||
language = guessit.Language(html_language.string.strip())
|
||||
if language not in languages:
|
||||
logger.debug(u'Language %r not in wanted languages %r' % (language, languages))
|
||||
continue
|
||||
html_status = html_language.findNextSibling('td')
|
||||
@@ -96,4 +96,5 @@ class SubsWiki(ServiceBase):
|
||||
subtitles.append(subtitle)
|
||||
return subtitles
|
||||
|
||||
|
||||
Service = SubsWiki
|
||||
|
||||
@@ -18,8 +18,10 @@
|
||||
from . import ServiceBase
|
||||
from ..subtitles import get_subtitle_path, ResultSubtitle
|
||||
from ..videos import Episode
|
||||
from bs4 import BeautifulSoup
|
||||
from guessit.language import lang_set
|
||||
from subliminal.utils import get_keywords, split_keyword
|
||||
import BeautifulSoup
|
||||
import guessit
|
||||
import logging
|
||||
import re
|
||||
import unicodedata
|
||||
@@ -32,19 +34,19 @@ logger = logging.getLogger(__name__)
|
||||
class Subtitulos(ServiceBase):
|
||||
server_url = 'http://www.subtitulos.es'
|
||||
api_based = False
|
||||
languages = {u'English (US)': 'en', u'English (UK)': 'en', u'English': 'en', u'French': 'fr', u'Brazilian': 'po',
|
||||
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'}
|
||||
reverted_languages = True
|
||||
languages = lang_set([u'English (US)', u'English (UK)', u'English', u'French', u'Brazilian',
|
||||
u'Portuguese', u'Español (Latinoamérica)', u'Español (España)', u'Español',
|
||||
u'Italian', u'Català'], strict=True)
|
||||
videos = [Episode]
|
||||
require_video = False
|
||||
release_pattern = re.compile('Versión (.+) ([0-9]+).([0-9])+ megabytes')
|
||||
required_features = ['permissive']
|
||||
# the '.+' in the pattern for Version allows us to match both 'ó'
|
||||
# and the 'ó' char directly. This is because now BS4 converts the html
|
||||
# code chars into their equivalent unicode char
|
||||
release_pattern = re.compile('Versi.+n (.+) ([0-9]+).([0-9])+ megabytes')
|
||||
|
||||
def list(self, video, languages):
|
||||
if not self.check_validity(video, languages):
|
||||
return []
|
||||
results = self.query(video.path or video.release, languages, get_keywords(video.guess), video.series, video.season, video.episode)
|
||||
return results
|
||||
def list_checked(self, video, languages):
|
||||
return self.query(video.path or video.release, languages, get_keywords(video.guess), video.series, video.season, video.episode)
|
||||
|
||||
def query(self, filepath, languages, keywords, series, season, episode):
|
||||
request_series = series.lower().replace(' ', '_')
|
||||
@@ -58,7 +60,7 @@ class Subtitulos(ServiceBase):
|
||||
if r.status_code != 200:
|
||||
logger.error(u'Request %s returned status code %d' % (r.url, r.status_code))
|
||||
return []
|
||||
soup = BeautifulSoup.BeautifulSoup(r.content)
|
||||
soup = BeautifulSoup(r.content, self.required_features)
|
||||
subtitles = []
|
||||
for sub in soup('div', {'id': 'version'}):
|
||||
sub_keywords = split_keyword(self.release_pattern.search(sub.find('p', {'class': 'title-sub'}).contents[1]).group(1).lower())
|
||||
@@ -66,8 +68,8 @@ class Subtitulos(ServiceBase):
|
||||
logger.debug(u'None of subtitle keywords %r in %r' % (sub_keywords, keywords))
|
||||
continue
|
||||
for html_language in sub.findAllNext('ul', {'class': 'sslist'}):
|
||||
language = self.get_revert_language(html_language.findNext('li', {'class': 'li-idioma'}).find('strong').contents[0].string.strip())
|
||||
if not language in languages:
|
||||
language = guessit.Language(html_language.findNext('li', {'class': 'li-idioma'}).find('strong').contents[0].string.strip())
|
||||
if language not in languages:
|
||||
logger.debug(u'Language %r not in wanted languages %r' % (language, languages))
|
||||
continue
|
||||
html_status = html_language.findNext('li', {'class': 'li-estado green'})
|
||||
@@ -80,4 +82,5 @@ class Subtitulos(ServiceBase):
|
||||
subtitles.append(subtitle)
|
||||
return subtitles
|
||||
|
||||
|
||||
Service = Subtitulos
|
||||
|
||||
@@ -18,6 +18,8 @@
|
||||
from . import ServiceBase
|
||||
from ..subtitles import get_subtitle_path, ResultSubtitle
|
||||
from ..videos import Episode, Movie, UnknownVideo
|
||||
from guessit.language import lang_set
|
||||
import guessit
|
||||
import logging
|
||||
|
||||
|
||||
@@ -26,21 +28,17 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
class TheSubDB(ServiceBase):
|
||||
server_url = 'http://api.thesubdb.com/' # for testing purpose, use http://sandbox.thesubdb.com/ instead
|
||||
user_agent = 'SubDB/1.0 (subliminal/0.5; https://github.com/Diaoul/subliminal)' # defined by the API
|
||||
user_agent = 'SubDB/1.0 (subliminal/0.6; https://github.com/Diaoul/subliminal)' # defined by the API
|
||||
api_based = True
|
||||
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
|
||||
reverted_languages = False
|
||||
languages = lang_set(['af', 'cs', 'da', 'de', 'en', 'es', 'fi',
|
||||
'fr', 'hu', 'id', 'it', 'la', 'nl', 'no',
|
||||
'oc', 'pl', 'pt', 'ro', 'ru', 'sl', 'sr',
|
||||
'sv', 'tr'], strict=True) # list available with the API at http://sandbox.thesubdb.com/?action=languages
|
||||
videos = [Movie, Episode, UnknownVideo]
|
||||
require_video = True
|
||||
|
||||
def list(self, video, languages):
|
||||
if not self.check_validity(video, languages):
|
||||
return []
|
||||
results = self.query(video.path, video.hashes['TheSubDB'], languages)
|
||||
return results
|
||||
def list_checked(self, video, languages):
|
||||
return self.query(video.path, video.hashes['TheSubDB'], languages)
|
||||
|
||||
def query(self, filepath, moviehash, languages):
|
||||
r = self.session.get(self.server_url, params={'action': 'search', 'hash': moviehash})
|
||||
@@ -50,7 +48,7 @@ class TheSubDB(ServiceBase):
|
||||
if r.status_code != 200:
|
||||
logger.error(u'Request %s returned status code %d' % (r.url, r.status_code))
|
||||
return []
|
||||
available_languages = set([self.get_revert_language(l) for l in r.content.split(',')])
|
||||
available_languages = set(guessit.Language(l) for l in r.content.split(','))
|
||||
languages &= available_languages
|
||||
if not languages:
|
||||
logger.debug(u'Could not find subtitles for hash %s with languages %r (only %r available)' % (moviehash, languages, available_languages))
|
||||
@@ -58,8 +56,9 @@ class TheSubDB(ServiceBase):
|
||||
subtitles = []
|
||||
for language in languages:
|
||||
path = get_subtitle_path(filepath, language, self.config.multi)
|
||||
subtitle = ResultSubtitle(path, language, service=self.__class__.__name__.lower(), link='%s?action=download&hash=%s&language=%s' % (self.server_url, moviehash, self.get_language(language)))
|
||||
subtitle = ResultSubtitle(path, language, self.__class__.__name__.lower(), '%s?action=download&hash=%s&language=%s' % (self.server_url, moviehash, language.alpha2))
|
||||
subtitles.append(subtitle)
|
||||
return subtitles
|
||||
|
||||
|
||||
Service = TheSubDB
|
||||
|
||||
Executable
+146
@@ -0,0 +1,146 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright 2012 Nicolas Wack <wackou@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 GNU Lesser 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
|
||||
# GNU Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with subliminal. If not, see <http://www.gnu.org/licenses/>.
|
||||
from . import ServiceBase
|
||||
from ..cache import cachedmethod
|
||||
from ..subtitles import get_subtitle_path, ResultSubtitle
|
||||
from ..videos import Episode
|
||||
from bs4 import BeautifulSoup
|
||||
from guessit.language import lang_set
|
||||
from subliminal.utils import get_keywords
|
||||
import guessit
|
||||
import logging
|
||||
import re
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def match(pattern, string):
|
||||
try:
|
||||
return re.search(pattern, string).group(1)
|
||||
except AttributeError:
|
||||
logger.debug(u'Could not match %r on %r' % (pattern, string))
|
||||
return None
|
||||
|
||||
|
||||
class TvSubtitles(ServiceBase):
|
||||
server_url = 'http://www.tvsubtitles.net'
|
||||
api_based = False
|
||||
languages = lang_set([u'English', u'Español', u'French', u'German',
|
||||
u'Brazilian', u'Russian', u'Ukrainian', u'Italian',
|
||||
u'Greek', u'Arabic', u'Hungarian', u'Polish',
|
||||
u'Turkish', u'Dutch', u'Portuguese', u'Swedish',
|
||||
u'Danish', u'Finnish', u'Korean', u'Chinese',
|
||||
u'Japanese', u'Bulgarian', u'Czech', u'Romanian'], strict=True)
|
||||
videos = [Episode]
|
||||
require_video = False
|
||||
required_features = ['permissive']
|
||||
|
||||
@cachedmethod
|
||||
def get_likely_series_id(self, name):
|
||||
r = self.session.post('%s/search.php' % self.server_url, data={'q': name})
|
||||
soup = BeautifulSoup(r.content, self.required_features)
|
||||
maindiv = soup.find('div', 'left')
|
||||
results = []
|
||||
for elem in maindiv.find_all('li'):
|
||||
sid = int(match('tvshow-([0-9]+)\.html', elem.a['href']))
|
||||
show_name = match('(.*) \(', elem.a.text)
|
||||
results.append((show_name, sid))
|
||||
#TODO: pick up the best one in a smart way
|
||||
result = results[0]
|
||||
return result[1]
|
||||
|
||||
@cachedmethod
|
||||
def get_episode_id(self, series_id, season, number):
|
||||
"""Get the TvSubtitles id for the given episode. Raises KeyError if none
|
||||
could be found."""
|
||||
# download the page of the season, contains ids for all episodes
|
||||
episode_id = None
|
||||
r = self.session.get('%s/tvshow-%d-%d.html' % (self.server_url, series_id, season))
|
||||
soup = BeautifulSoup(r.content, self.required_features)
|
||||
table = soup.find('table', id='table5')
|
||||
for row in table.find_all('tr'):
|
||||
cells = row.find_all('td')
|
||||
if not cells:
|
||||
continue
|
||||
|
||||
episode_number = match('x([0-9]+)', cells[0].text)
|
||||
if not episode_number:
|
||||
continue
|
||||
|
||||
episode_number = int(episode_number)
|
||||
episode_id = int(match('episode-([0-9]+)', cells[1].a['href']))
|
||||
# we could just return the id of the queried episode, but as we
|
||||
# already downloaded the whole page we might as well fill in the
|
||||
# information for all the episodes of the season
|
||||
self.cache_for(self.get_episode_id, args=(series_id, season, episode_number), result=episode_id)
|
||||
# raises KeyError if not found
|
||||
return self.cached_value(self.get_episode_id, args=(series_id, season, number))
|
||||
|
||||
# Do not cache this method in order to always check for the most recent
|
||||
# subtitles
|
||||
def get_sub_ids(self, episode_id):
|
||||
subids = []
|
||||
r = self.session.get('%s/episode-%d.html' % (self.server_url, episode_id))
|
||||
epsoup = BeautifulSoup(r.content, self.required_features)
|
||||
for subdiv in epsoup.find_all('a'):
|
||||
if 'href' not in subdiv.attrs or not subdiv['href'].startswith('/subtitle'):
|
||||
continue
|
||||
subid = int(match('([0-9]+)', subdiv['href']))
|
||||
lang = guessit.Language(match('flags/(.*).gif', subdiv.img['src']))
|
||||
result = {'subid': subid, 'language': lang}
|
||||
for p in subdiv.find_all('p'):
|
||||
if 'alt' in p.attrs and p['alt'] == 'rip':
|
||||
result['rip'] = p.text.strip()
|
||||
if 'alt' in p.attrs and p['alt'] == 'release':
|
||||
result['release'] = p.text.strip()
|
||||
|
||||
subids.append(result)
|
||||
return subids
|
||||
|
||||
def list_checked(self, video, languages):
|
||||
return self.query(video.path or video.release, languages, get_keywords(video.guess), video.series, video.season, video.episode)
|
||||
|
||||
def query(self, filepath, languages, keywords, series, season, episode):
|
||||
logger.debug(u'Getting subtitles for %s season %d episode %d with languages %r' % (series, season, episode, languages))
|
||||
self.init_cache()
|
||||
sid = self.get_likely_series_id(series.lower())
|
||||
try:
|
||||
ep_id = self.get_episode_id(sid, season, episode)
|
||||
except KeyError:
|
||||
logger.debug(u'Could not find episode id for %s season %d episode %d' % (series, season, episode))
|
||||
return []
|
||||
subids = self.get_sub_ids(ep_id)
|
||||
# filter the subtitles with our queried languages
|
||||
subtitles = []
|
||||
for subid in subids:
|
||||
language = subid['language']
|
||||
if language not in languages:
|
||||
continue
|
||||
path = get_subtitle_path(filepath, language, self.config.multi)
|
||||
subtitle = ResultSubtitle(path, language, self.__class__.__name__.lower(),
|
||||
'%s/download-%d.html' % (self.server_url, subid['subid']),
|
||||
keywords=[subid['rip'], subid['release']])
|
||||
subtitles.append(subtitle)
|
||||
return subtitles
|
||||
|
||||
def download(self, subtitle):
|
||||
self.download_zip_file(subtitle.link, subtitle.path)
|
||||
|
||||
|
||||
Service = TvSubtitles
|
||||
Reference in New Issue
Block a user