diff --git a/README.md b/README.md index b00e844e1..418c33ac4 100644 --- a/README.md +++ b/README.md @@ -48,6 +48,7 @@ If you need something that is not already part of Bazarr, feel free to create a * BSPlayer * GreekSubtitles * Hosszupuska +* LegendasDivx * LegendasTV * Napiprojekt * Napisy24 diff --git a/bazarr/config.py b/bazarr/config.py index ca61871e0..b64040ebf 100644 --- a/bazarr/config.py +++ b/bazarr/config.py @@ -102,6 +102,10 @@ defaults = { 'password': '', 'random_agents': 'True' }, + 'legendasdivx': { + 'username': '', + 'password': '' + }, 'legendastv': { 'username': '', 'password': '' diff --git a/bazarr/get_providers.py b/bazarr/get_providers.py index c543f930d..d345b79ff 100644 --- a/bazarr/get_providers.py +++ b/bazarr/get_providers.py @@ -38,7 +38,7 @@ PROVIDER_THROTTLE_MAP = { } } -PROVIDERS_FORCED_OFF = ["addic7ed", "tvsubtitles", "legendastv", "napiprojekt", "shooter", "hosszupuska", +PROVIDERS_FORCED_OFF = ["addic7ed", "tvsubtitles", "legendasdivx", "legendastv", "napiprojekt", "shooter", "hosszupuska", "supersubtitles", "titlovi", "argenteam", "assrt", "subscene"] throttle_count = {} @@ -114,6 +114,9 @@ def get_providers_auth(): 'password': settings.subscene.password, 'only_foreign': False, # fixme }, + 'legendasdivx': {'username': settings.legendasdivx.username, + 'password': settings.legendasdivx.password, + }, 'legendastv': {'username': settings.legendastv.username, 'password': settings.legendastv.password, }, diff --git a/bazarr/main.py b/bazarr/main.py index 0a149e31c..2115e2976 100644 --- a/bazarr/main.py +++ b/bazarr/main.py @@ -398,6 +398,8 @@ def save_wizard(): settings.addic7ed.password = request.forms.get('settings_addic7ed_password') settings.addic7ed.random_agents = text_type(settings_addic7ed_random_agents) settings.assrt.token = request.forms.get('settings_assrt_token') + settings.legendasdivx.username = request.forms.get('settings_legendasdivx_username') + settings.legendasdivx.password = request.forms.get('settings_legendasdivx_password') settings.legendastv.username = request.forms.get('settings_legendastv_username') settings.legendastv.password = request.forms.get('settings_legendastv_password') settings.opensubtitles.username = request.forms.get('settings_opensubtitles_username') @@ -1527,6 +1529,8 @@ def save_settings(): settings.addic7ed.password = request.forms.get('settings_addic7ed_password') settings.addic7ed.random_agents = text_type(settings_addic7ed_random_agents) settings.assrt.token = request.forms.get('settings_assrt_token') + settings.legendasdivx.username = request.forms.get('settings_legendasdivx_username') + settings.legendasdivx.password = request.forms.get('settings_legendasdivx_password') settings.legendastv.username = request.forms.get('settings_legendastv_username') settings.legendastv.password = request.forms.get('settings_legendastv_password') settings.opensubtitles.username = request.forms.get('settings_opensubtitles_username') diff --git a/libs/subliminal_patch/providers/legendasdivx.py b/libs/subliminal_patch/providers/legendasdivx.py new file mode 100644 index 000000000..a6d297b5d --- /dev/null +++ b/libs/subliminal_patch/providers/legendasdivx.py @@ -0,0 +1,327 @@ +# -*- coding: utf-8 -*- +from __future__ import absolute_import +import logging +import io +import os +import sys +import rarfile +import zipfile + +from requests import Session +from guessit import guessit +from subliminal_patch.providers import Provider +from subliminal.providers import ParserBeautifulSoup +from subliminal_patch.subtitle import Subtitle +from subliminal_patch.utils import sanitize +from subliminal.exceptions import ProviderError +from subliminal.utils import sanitize_release_group +from subliminal.subtitle import guess_matches +from subliminal.video import Episode, Movie +from subliminal.subtitle import SUBTITLE_EXTENSIONS, fix_line_ending,guess_matches +from subzero.language import Language + +import gzip + +logger = logging.getLogger(__name__) + +class LegendasdivxSubtitle(Subtitle): + """Legendasdivx Subtitle.""" + provider_name = 'legendasdivx' + + def __init__(self, language, video, data): + super(LegendasdivxSubtitle, self).__init__(language) + self.language = language + self.page_link = data['link'] + self.hits=data['hits'] + self.exact_match=data['exact_match'] + self.description=data['description'].lower() + self.video = video + self.videoname =data['videoname'] + + @property + def id(self): + return self.page_link + + @property + def release_info(self): + return self.description + + def get_matches(self, video): + matches = set() + + logger.info('got %s' % self.videoname) + logger.info('got %s' % self.description) + logger.info('title %s' % video.title) + if self.videoname.lower() in self.description: + matches.update(['title']) + matches.update(['season']) + matches.update(['episode']) + + # episode + if video.title and video.title.lower() in self.description: + logger.info('title matched') + matches.update(['title']) + if video.year and '{:04d}'.format(video.year) in self.description: + logger.info('year matched') + matches.update(['year']) + + if isinstance(video, Episode): + # already matched in search query + if video.season and 's{:02d}'.format(video.season) in self.description: + logger.info('season matched') + matches.update(['season']) + if video.episode and 'e{:02d}'.format(video.episode) in self.description: + logger.info('episode matched') + matches.update(['episode']) + if video.episode and video.season and video.series: + if '{}.s{:02d}e{:02d}'.format(video.series.lower(),video.season,video.episode) in self.description: + logger.info('series matched') + matches.update(['series']) + matches.update(['season']) + matches.update(['episode']) + if '{} s{:02d}e{:02d}'.format(video.series.lower(),video.season,video.episode) in self.description: + logger.info('series matched') + matches.update(['series']) + matches.update(['season']) + matches.update(['episode']) + + logger.info('matches: %s' % matches) + # release_group + if video.release_group and video.release_group.lower() in self.description: + matches.update(['release_group']) + + # resolution + + if video.resolution and video.resolution.lower() in self.description: + matches.update(['resolution']) + + # format + formats = [] + if video.format: + formats = [video.format.lower()] + if formats[0] == "web-dl": + formats.append("webdl") + formats.append("webrip") + formats.append("web ") + for frmt in formats: + if frmt.lower() in self.description: + matches.update(['format']) + break + + # video_codec + if video.video_codec: + video_codecs = [video.video_codec.lower()] + if video_codecs[0] == "h264": + formats.append("x264") + elif video_codecs[0] == "h265": + formats.append("x265") + for vc in formats: + if vc.lower() in self.description: + matches.update(['video_codec']) + break + + matches |= guess_matches(video, guessit(self.description)) + return matches + + + + +class LegendasdivxProvider(Provider): + """Legendasdivx Provider.""" + languages = {Language('por', 'BR')} | {Language('por')} + SEARCH_THROTTLE = 8 + site = 'https://www.legendasdivx.pt' + headers = { + 'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64; rv:72.0) Gecko/20100101 Firefox/72.0', + 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8', + 'Origin': 'https://www.legendasdivx.pt', + 'Referer': 'https://www.legendasdivx.pt', + 'Pragma': 'no-cache', + 'Cache-Control': 'no-cache' + } + loginpage = site + '/forum/ucp.php?mode=login' + searchurl = site + '/modules.php?name=Downloads&file=jz&d_op=search&op=_jz00&query={query}' + language_list = list(languages) + + + def __init__(self, username, password): + self.username = username + self.password = password + + def initialize(self): + self.session = Session() + self.login() + + def terminate(self): + self.logout() + self.session.close() + + def login(self): + logger.info('Starting login') + self.headers['Referer'] = self.site + '/index.php' + self.session.headers.update(self.headers.items()) + res = self.session.get(self.loginpage) + bsoup = ParserBeautifulSoup(res.content, ['lxml']) + + _allinputs = bsoup.findAll('input') + fields = {} + for field in _allinputs: + fields[field.get('name')] = field.get('value') + logger.debug('%s: %s' % (field.get('name'), field.get('value'))) + + fields['username'] = self.username + fields['password'] = self.password + fields['autologin'] = 'on' + fields['viewonline'] = 'on' + + self.headers['Referer'] = self.loginpage + self.session.headers.update(self.headers.items()) + res = self.session.post(self.loginpage, fields) + try: + logger.debug('Got session id %s' % + self.session.cookies.get_dict()['PHPSESSID']) + except KeyError as e: + logger.error(repr(e)) + logger.error("Didn't get session id, check your credentials") + return False + except Exception as e: + logger.error(repr(e)) + logger.error('uncached error #legendasdivx #AA') + return False + + return True + def logout(self): + # need to figure this out + return True + + def query(self, video, language): + try: + logger.debug('Got session id %s' % + self.session.cookies.get_dict()['PHPSESSID']) + except Exception as e: + self.login() + return [] + + language_ids = '0' + if isinstance(language, (tuple, list, set)): + logger.error('language %s' % language) + if len(language) == 1: + language_ids = ','.join(sorted(l.opensubtitles for l in language)) + logger.error('language_ids %s' % language_ids) + if language_ids == 'por': + language_ids = '&form_cat=28' + else: + language_ids = '&form_cat=29' + + querytext = video.name + querytext = os.path.basename(querytext) + querytext, _ = os.path.splitext(querytext) + videoname = querytext + querytext = querytext.lower() + querytext = querytext.replace( + ".", "+").replace("[", "").replace("]", "") + if language_ids != '0': + querytext = querytext + language_ids + self.headers['Referer'] = self.site + '/index.php' + self.session.headers.update(self.headers.items()) + res = self.session.get(self.searchurl.format(query=querytext)) + # form_cat=28 = br + # form_cat=29 = pt + if "A legenda não foi encontrada" in res.text: + logger.warning('%s not found', querytext) + return [] + logger.warning(self.searchurl.format(query=querytext)) + + bsoup = ParserBeautifulSoup(res.content, ['html.parser']) + _allsubs = bsoup.findAll("div", {"class": "sub_box"}) + subtitles = [] + lang = Language.fromopensubtitles("pob") + for _subbox in _allsubs: + logger.info("============================================") + hits=0 + for th in _subbox.findAll("th", {"class": "color2"}): + if th.string == 'Hits:': + hits = int(th.parent.find("td").string) + if th.string == 'Idioma:': + lang = th.parent.find("td").find ("img").get ('src') + logger.debug('lang img %s' % lang) + if 'brazil' in lang: + lang = Language.fromopensubtitles('pob') + else: + lang = Language.fromopensubtitles('por') + + + description = _subbox.find("td", {"class": "td_desc brd_up"}) + download = _subbox.find("a", {"class": "sub_download"}) + try: + # sometimes BSoup just doesn't get the link + logger.debug(download.get('href')) + except Exception as e: + logger.warning('skipping subbox on %s' % self.searchurl.format(query=querytext)) + continue + + logger.info(hits) + exact_match = False + if video.name.lower() in description.get_text().lower(): + logger.info("exact match!") + exact_match = True + data = {'link': self.site + '/modules.php' + download.get('href'), + 'exact_match': exact_match, + 'hits': hits, + 'videoname': videoname, + 'description': description.get_text() } + subtitles.append( + LegendasdivxSubtitle(lang, video, data) + ) + + return subtitles + + def list_subtitles(self, video, languages): + return self.query(video, languages) + + def download_subtitle(self, subtitle): + res = self.session.get(subtitle.page_link) + if res: + if res.text == '500': + raise ValueError('Error 500 on server') + + archive = self._get_archive(res.content) + # extract the subtitle + subtitle_content = self._get_subtitle_from_archive(archive) + subtitle.content = fix_line_ending(subtitle_content) + subtitle.normalize() + + return subtitle + raise ValueError('Problems conecting to the server') + + def _get_archive(self, content): + # open the archive + # stole^H^H^H^H^H inspired from subvix provider + archive_stream = io.BytesIO(content) + if rarfile.is_rarfile(archive_stream): + logger.debug('Identified rar archive') + archive = rarfile.RarFile(archive_stream) + elif zipfile.is_zipfile(archive_stream): + logger.debug('Identified zip archive') + archive = zipfile.ZipFile(archive_stream) + else: + # raise ParseResponseError('Unsupported compressed format') + raise Exception('Unsupported compressed format') + + return archive + + def _get_subtitle_from_archive(self, archive): + logger.warning(archive.namelist()) + for name in archive.namelist(): + # discard hidden files + if os.path.split(name)[-1].startswith('.'): + continue + + # discard non-subtitle files + if not name.lower().endswith(SUBTITLE_EXTENSIONS): + continue + + logger.warning(name) + return archive.read(name) + + raise ParseResponseError('Can not find the subtitle in the compressed file') diff --git a/views/providers.tpl b/views/providers.tpl index 08148e895..510ee7857 100644 --- a/views/providers.tpl +++ b/views/providers.tpl @@ -226,6 +226,47 @@ +
+
+ +
+
+
+ + +
+
+ +
+
+
+
+ +
+
+
+ +
+
+
+
+
+ +
+
+
+ +
+
+
+
+