diff --git a/medusa/init/__init__.py b/medusa/init/__init__.py index 1a6309c952..db24e4de7b 100644 --- a/medusa/init/__init__.py +++ b/medusa/init/__init__.py @@ -209,6 +209,7 @@ def _configure_subliminal(): # Register for name in ('napiprojekt = subliminal.providers.napiprojekt:NapiProjektProvider', 'itasa = {basename}.subtitle_providers.itasa:ItaSAProvider'.format(basename=basename), + 'subtitulamos = {basename}.subtitle_providers.subtitulamos:SubtitulamosProvider'.format(basename=basename), 'wizdom = {basename}.subtitle_providers.wizdom:WizdomProvider'.format(basename=basename)): provider_manager.register(name) diff --git a/medusa/subtitle_providers/subtitulamos.py b/medusa/subtitle_providers/subtitulamos.py new file mode 100644 index 0000000000..486066fa8b --- /dev/null +++ b/medusa/subtitle_providers/subtitulamos.py @@ -0,0 +1,237 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +import json +import logging +import re + +from babelfish import Language, LanguageReverseConverter, language_converters + +from guessit import guessit + +from requests import Session + +from subliminal import __short_version__ +from subliminal.cache import EPISODE_EXPIRATION_TIME, region +from subliminal.exceptions import ProviderError +from subliminal.providers import ParserBeautifulSoup, Provider +from subliminal.score import get_equivalent_release_groups +from subliminal.subtitle import Subtitle, fix_line_ending, guess_matches +from subliminal.utils import sanitize, sanitize_release_group +from subliminal.video import Episode + +logger = logging.getLogger(__name__) +basename = __name__.split('.')[0] + + +class SubtitulamosConverter(LanguageReverseConverter): + def __init__(self): + self.name_converter = language_converters['name'] + self.from_subtitulamos = {u'Español': ('spa',), u'Español (España)': ('spa',), u'Español (Latinoamérica)': ('spa', 'MX'), + u'Català': ('cat',), 'English': ('eng',), 'Galego': ('glg',), 'Portuguese': ('por',), + 'English (US)': ('eng', 'US'), 'English (UK)': ('eng', 'GB'), 'Brazilian': ('por', 'BR')} + + self.to_subtitulamos = {('cat',): u'Català', ('glg',): 'Galego', ('por', 'BR'): 'Brazilian'} + + self.codes = set(self.from_subtitulamos.keys()) + + def convert(self, alpha3, country=None, script=None): + if (alpha3, country, script) in self.to_subtitulamos: + return self.to_subtitulamos[[alpha3, country, script]] + if (alpha3, country) in self.to_subtitulamos: + return self.to_subtitulamos[(alpha3, country)] + if (alpha3,) in self.to_subtitulamos: + return self.to_subtitulamos[(alpha3,)] + + return self.name_converter.convert(alpha3, country, script) + + def reverse(self, subtitulamos): + if subtitulamos in self.from_subtitulamos: + return self.from_subtitulamos[subtitulamos] + + return self.name_converter.reverse(subtitulamos) + + +language_converters.register('subtitulamos = {basename}.subtitle_providers.subtitulamos:SubtitulamosConverter'.format(basename=basename)) + + +class SubtitulamosSubtitle(Subtitle): + """Subtitulamos Subtitle.""" + + provider_name = 'subtitulamos' + + def __init__(self, language, hearing_impaired, page_link, series, season, episode, title, year, version, + download_link): + super(SubtitulamosSubtitle, self).__init__(language, hearing_impaired, page_link) + self.page_link = page_link + self.series = series + self.season = season + self.episode = episode + self.title = title + self.year = year + self.version = version + self.download_link = download_link + + @property + def id(self): + return self.download_link + + def get_matches(self, video): + matches = set() + + # series name + if video.series and sanitize(self.series) == sanitize(video.series): + matches.add('series') + # season + if video.season and self.season == video.season: + matches.add('season') + # episode + if video.episode and self.episode == video.episode: + matches.add('episode') + # title of the episode + if video.title and sanitize(self.title) == sanitize(video.title): + matches.add('title') + # year + if video.original_series and self.year is None or video.year and video.year == self.year: + matches.add('year') + # release_group + if (video.release_group and self.version and + any(r in sanitize_release_group(self.version) + for r in get_equivalent_release_groups(sanitize_release_group(video.release_group)))): + matches.add('release_group') + # resolution + if video.resolution and self.version and video.resolution in self.version.lower(): + matches.add('resolution') + # other properties + matches |= guess_matches(video, guessit(self.version), partial=True) + + return matches + + +class SubtitulamosProvider(Provider): + """Subtitulamos Provider.""" + + languages = {Language('por', 'BR')} | {Language(l) for l in [ + 'cat', 'eng', 'glg', 'por', 'spa' + ]} + video_types = (Episode,) + server_url = 'https://www.subtitulamos.tv/' + search_url = server_url + 'search/query' + + def initialize(self): + self.session = Session() + self.session.headers['User-Agent'] = 'Subliminal/%s' % __short_version__ + + def terminate(self): + self.session.close() + + @region.cache_on_arguments(expiration_time=EPISODE_EXPIRATION_TIME) + def _search_url_titles(self, series, season, episode, year=None): + """Search the URL titles by kind for the given `title`, `season` and `episode`. + + :param str series: series to search for. + :param int season: season to search for. + :param int episode: episode to search for. + :param int year: year to search for. + :return: the episode URL. + :rtype: str + + """ + # make the search + logger.info('Searching episode url for %s, season %d, episode %d', series, season, episode) + episode_url = None + + search = '{} {}x{}'.format(series, season, episode) + r = self.session.get(self.search_url, headers={'Referer': self.server_url}, params={'q': search}, timeout=10) + r.raise_for_status() + + if r.status_code != 200: + logger.warning('Error getting episode url') + raise ProviderError('%s: Error getting episode url', self.__class__.__name__.upper()) + + results = json.loads(r.text) + + for result in results: + title = sanitize(result['name']) + + # attempt series with year + if sanitize('{} ({})'.format(series, year)) in title: + for episode_data in result['episodes']: + if season == episode_data['season'] and episode == episode_data['number']: + episode_url = self.server_url + 'episodes/{}'.format(episode_data['id']) + logger.info('Episode url found with year %s', episode_url) + return episode_url + # attempt series without year + elif sanitize(series) in title: + for episode_data in result['episodes']: + if season == episode_data['season'] and episode == episode_data['number']: + episode_url = self.server_url + 'episodes/{}'.format(episode_data['id']) + logger.info('Episode url found without year %s', episode_url) + return episode_url + + return episode_url + + def query(self, series, season, episode, year=None): + # get the episode url + episode_url = self._search_url_titles(series, season, episode, year) + if episode_url is None: + logger.info('No episode url found for %s, season %d, episode %d', series, season, episode) + return [] + + r = self.session.get(episode_url, headers={'Referer': self.server_url}, timeout=10) + r.raise_for_status() + soup = ParserBeautifulSoup(r.content, ['lxml', 'html.parser']) + + # get episode title + logger.debug('Getting episode title') + + title_pattern = re.compile('{}x{:02d} - (.+)'.format(season, episode)) + title = title_pattern.search(soup.select('.episode-name')[0].get_text(strip=True).lower()).group(1) + + logger.debug('Episode title found: "%s"', title.upper()) + + subtitles = [] + for sub in soup.find_all('div', attrs={'id': 'progress_buttons_row'}): + # read the language + language = Language.fromsubtitulamos(sub.find_previous('div', class_='subtitle_language').get_text(strip=True)) + hearing_impaired = False + + # modify spanish latino subtitle language to only spanish and set hearing_impaired = True + # because if exists spanish and spanish latino subtitle for the same episode, the score will be + # higher with spanish subtitle. Spanish subtitle takes priority. + if language == Language('spa', 'MX'): + language = Language('spa') + hearing_impaired = True + + # read the release subtitle + release = sub.find_next('div', class_='version_name').get_text(strip=True) + + # ignore incomplete subtitles + status = sub.find_next('div', class_='subtitle_buttons').contents[1] + # if there isn't tag, subtitle not finished and no link available to download it + if status.name != 'a': + logger.info('Ignoring subtitle in [%s] because it is not finished', language) + continue + + # read the subtitle url + subtitle_url = self.server_url + status['href'][1:] + subtitle = SubtitulamosSubtitle(language, hearing_impaired, episode_url, series, season, episode, title, + year, release, subtitle_url) + logger.info('Found subtitle %r', subtitle) + subtitles.append(subtitle) + + return subtitles + + def list_subtitles(self, video, languages): + return [s for s in self.query(video.series, video.season, video.episode, + video.year) + if s.language in languages] + + def download_subtitle(self, subtitle): + # download the subtitle + logger.info('Downloading subtitle %s', subtitle.download_link) + r = self.session.get(subtitle.download_link, headers={'Referer': subtitle.page_link}, + timeout=10) + r.raise_for_status() + + subtitle.content = fix_line_ending(r.content) diff --git a/medusa/subtitles.py b/medusa/subtitles.py index 3982091c2f..f131f369ac 100644 --- a/medusa/subtitles.py +++ b/medusa/subtitles.py @@ -69,6 +69,7 @@ 'opensubtitles': 'http://www.opensubtitles.org', 'podnapisi': 'https://www.podnapisi.net', 'shooter': 'http://www.shooter.cn', + 'subtitulamos': 'https://www.subtitulamos.tv', 'thesubdb': 'http://www.thesubdb.com', 'tvsubtitles': 'http://www.tvsubtitles.net', 'wizdom': 'http://wizdom.xyz' diff --git a/setup.cfg b/setup.cfg index 640f17a42f..7ae95fbac8 100644 --- a/setup.cfg +++ b/setup.cfg @@ -171,6 +171,7 @@ flake8-ignore = medusa/subtitle_providers/__init__.py D104 medusa/subtitle_providers/legendastv.py D100 D102 D103 D105 D204 medusa/subtitle_providers/itasa.py D100 D101 D102 D105 D400 N813 + medusa/subtitle_providers/subtitulamos.py D100 D101 D102 medusa/subtitle_providers/wizdom.py D100 D102 D204 medusa/system/__init__.py D104 medusa/system/restart.py D100 D101 D102 diff --git a/tests/test_subtitles.py b/tests/test_subtitles.py index 95f85b0edf..95b6951922 100644 --- a/tests/test_subtitles.py +++ b/tests/test_subtitles.py @@ -35,6 +35,7 @@ def test_sorted_service_list(monkeypatch): {'name': 'napiprojekt', 'enabled': False}, {'name': 'opensubtitles', 'enabled': False}, {'name': 'podnapisi', 'enabled': False}, + {'name': 'subtitulamos', 'enabled': False}, {'name': 'tvsubtitles', 'enabled': False}, {'name': 'wizdom', 'enabled': False}, ] diff --git a/themes-default/slim/static/images/subtitles/subtitulamos.png b/themes-default/slim/static/images/subtitles/subtitulamos.png new file mode 100644 index 0000000000..ae238042b9 Binary files /dev/null and b/themes-default/slim/static/images/subtitles/subtitulamos.png differ diff --git a/themes/dark/assets/img/subtitles/subtitulamos.png b/themes/dark/assets/img/subtitles/subtitulamos.png new file mode 100644 index 0000000000..ae238042b9 Binary files /dev/null and b/themes/dark/assets/img/subtitles/subtitulamos.png differ diff --git a/themes/light/assets/img/subtitles/subtitulamos.png b/themes/light/assets/img/subtitles/subtitulamos.png new file mode 100644 index 0000000000..ae238042b9 Binary files /dev/null and b/themes/light/assets/img/subtitles/subtitulamos.png differ