def _get_season_subtitles(self, series_id, season, sub_format): params = { 'apikey': self.apikey, 'series_id': series_id, 'q': 'Stagione %{}'.format(season), 'version': sub_format } r = self.session.get(self.server_url + 'subtitles/search', params=params, timeout=30) r.raise_for_status() root = etree.fromstring(r.content) if int(root.find('data/count').text) == 0: logger.warning( 'Subtitles for season not found, try with rip suffix') params['version'] = sub_format + 'rip' r = self.session.get(self.server_url + 'subtitles/search', params=params, timeout=30) r.raise_for_status() root = etree.fromstring(r.content) if int(root.find('data/count').text) == 0: logger.warning('Subtitles for season not found') return [] subs = [] # Looking for subtitles in first page season_re = re.compile('.*?stagione 0*?{}.*'.format(season)) for subtitle in root.findall('data/subtitles/subtitle'): if season_re.match(subtitle.find('name').text.lower()): logger.debug('Found season zip id %d - %r - %r', int(subtitle.find('id').text), subtitle.find('name').text, subtitle.find('version').text) content = self._download_zip(int(subtitle.find('id').text)) if not is_zipfile(io.BytesIO(content)): # pragma: no cover if 'limite di download' in content: raise DownloadLimitExceeded() else: raise ConfigurationError( 'Not a zip file: {!r}'.format(content)) with ZipFile(io.BytesIO(content)) as zf: episode_re = re.compile('s(\d{1,2})e(\d{1,2})') for index, name in enumerate(zf.namelist()): match = episode_re.search(name) if not match: # pragma: no cover logger.debug('Cannot decode subtitle %r', name) else: sub = ItaSASubtitle( int(subtitle.find('id').text), subtitle.find('show_name').text, int(match.group(1)), int(match.group(2)), None, None, None, name) sub.content = fix_line_ending(zf.read(name)) subs.append(sub) return subs
def query(self, series, season, episode, source, resolution, country=None): # To make queries you need to be logged in if not self.logged_in: # pragma: no cover raise ConfigurationError("Cannot query if not logged in") # get the show id show_id = self.get_show_id(series, country) if show_id is None: logger.error("No show id found for %r ", series) return [] # get the page of the season of the show logger.info( "Getting the subtitle of show id %d, season %d episode %d, source %r", show_id, season, episode, source) subtitles = [] # Default source is SDTV if not source or source.lower() == "hdtv": if resolution in ("1080i", "1080p", "720p"): sub_source = resolution else: sub_source = "normale" else: sub_source = source.lower() # Look for year params = {"apikey": self.apikey} r = self.session.get(self.server_url + "shows/" + str(show_id), params=params, timeout=30) r.raise_for_status() root = ElementTree.fromstring(r.content) year = root.find("data/show/started").text if year: year = int(year.split("-", 1)[0]) tvdb_id = root.find("data/show/id_tvdb").text if tvdb_id: tvdb_id = int(tvdb_id) params = { "apikey": self.apikey, "show_id": show_id, "q": "%dx%02d" % (season, episode), "version": sub_source } r = self.session.get(self.server_url + "subtitles/search", params=params, timeout=30) r.raise_for_status() root = ElementTree.fromstring(r.content) if int(root.find("data/count").text) == 0: logger.warning("Subtitles not found, try with rip suffix") params["version"] = sub_source + "rip" r = self.session.get(self.server_url + "subtitles/search", params=params, timeout=30) r.raise_for_status() root = ElementTree.fromstring(r.content) if int(root.find("data/count").text) == 0: logger.warning("Subtitles not found, go season mode") # If no subtitle are found for single episode try to download all season zip subs = self._get_season_subtitles(show_id, season, sub_source) if subs: for subtitle in subs: subtitle.source = source subtitle.year = year subtitle.tvdb_id = tvdb_id return subs else: return [] # Looking for subtitles in first page for subtitle in root.findall("data/subtitles/subtitle"): if "%dx%02d" % (season, episode) in subtitle.find("name").text.lower(): logger.debug("Found subtitle id %d - %r - %r", int(subtitle.find("id").text), subtitle.find("name").text, subtitle.find("version").text) sub = ItaSASubtitle(int(subtitle.find("id").text), subtitle.find("show_name").text, season, episode, source, year, tvdb_id, subtitle.find("name").text) subtitles.append(sub) # Not in the first page of result try next (if any) next_page = root.find("data/next") while next_page.text is not None: # pragma: no cover r = self.session.get(next_page.text, timeout=30) r.raise_for_status() root = ElementTree.fromstring(r.content) # logger.info('Loading subtitles page %r', root.data.page.text) # Looking for show in following pages for subtitle in root.findall("data/subtitles/subtitle"): if "%dx%02d" % (season, episode) in subtitle.find("name").text.lower(): logger.debug("Found subtitle id %d - %r - %r", int(subtitle.find("id").text), subtitle.find("name").text, subtitle.find("version").text) sub = ItaSASubtitle(int(subtitle.find("id").text), subtitle.find("show_name").text, season, episode, source, year, tvdb_id, subtitle.find("name").text) subtitles.append(sub) next_page = root.find("data/next") # Download the subs found, can be more than one in zip additional_subs = [] for sub in subtitles: # open the zip content = self._download_zip(sub.sub_id) if not is_zipfile(io.BytesIO(content)): # pragma: no cover if "limite di download" in content: raise DownloadLimitExceeded() else: raise ConfigurationError("Not a zip file: %r" % content) with ZipFile(io.BytesIO(content)) as zf: if len(zf.namelist()) > 1: # pragma: no cover for index, name in enumerate(zf.namelist()): if index == 0: # First element sub.content = fix_line_ending(zf.read(name)) sub.full_data = name else: add_sub = copy.deepcopy(sub) add_sub.content = fix_line_ending(zf.read(name)) add_sub.full_data = name additional_subs.append(add_sub) else: sub.content = fix_line_ending(zf.read(zf.namelist()[0])) sub.full_data = zf.namelist()[0] return subtitles + additional_subs
def query(self, series, season, episode, video_format, resolution, country=None): # To make queries you need to be logged in if not self.logged_in: # pragma: no cover raise ConfigurationError('Cannot query if not logged in') # get the show id show_id = self.get_show_id(series, country) if show_id is None: logger.error('No show id found for %r ', series) return [] # get the page of the season of the show logger.info( 'Getting the subtitle of show id %d, season %d episode %d, format %r', show_id, season, episode, video_format) subtitles = [] # Default format is SDTV if not video_format or video_format.lower() == 'hdtv': if resolution in ('1080i', '1080p', '720p'): sub_format = resolution else: sub_format = 'normale' else: sub_format = video_format.lower() # Look for year params = {'apikey': self.apikey} r = self.session.get(self.server_url + 'shows/' + str(show_id), params=params, timeout=30) r.raise_for_status() root = etree.fromstring(r.content) year = root.find('data/show/started').text if year: year = int(year.split('-', 1)[0]) tvdb_id = root.find('data/show/id_tvdb').text if tvdb_id: tvdb_id = int(tvdb_id) params = { 'apikey': self.apikey, 'show_id': show_id, 'q': '%dx%02d' % (season, episode), 'version': sub_format } r = self.session.get(self.server_url + 'subtitles/search', params=params, timeout=30) r.raise_for_status() root = etree.fromstring(r.content) if int(root.find('data/count').text) == 0: logger.warning('Subtitles not found, try with rip suffix') params['version'] = sub_format + 'rip' r = self.session.get(self.server_url + 'subtitles/search', params=params, timeout=30) r.raise_for_status() root = etree.fromstring(r.content) if int(root.find('data/count').text) == 0: logger.warning('Subtitles not found, go season mode') # If no subtitle are found for single episode try to download all season zip subs = self._get_season_subtitles(show_id, season, sub_format) if subs: for subtitle in subs: subtitle.format = video_format subtitle.year = year subtitle.tvdb_id = tvdb_id return subs else: return [] # Looking for subtitles in first page for subtitle in root.findall('data/subtitles/subtitle'): if '%dx%02d' % (season, episode) in subtitle.find('name').text.lower(): logger.debug('Found subtitle id %d - %r - %r', int(subtitle.find('id').text), subtitle.find('name').text, subtitle.find('version').text) sub = ItaSASubtitle(int(subtitle.find('id').text), subtitle.find('show_name').text, season, episode, video_format, year, tvdb_id, subtitle.find('name').text) subtitles.append(sub) # Not in the first page of result try next (if any) next_page = root.find('data/next') while next_page.text is not None: # pragma: no cover r = self.session.get(next_page.text, timeout=30) r.raise_for_status() root = etree.fromstring(r.content) logger.info('Loading subtitles page %r', root.data.page.text) # Looking for show in following pages for subtitle in root.findall('data/subtitles/subtitle'): if '%dx%02d' % (season, episode) in subtitle.find('name').text.lower(): logger.debug('Found subtitle id %d - %r - %r', int(subtitle.find('id').text), subtitle.find('name').text, subtitle.find('version').text) sub = ItaSASubtitle(int(subtitle.find('id').text), subtitle.find('show_name').text, season, episode, video_format, year, tvdb_id, subtitle.find('name').text) subtitles.append(sub) next_page = root.find('data/next') # Download the subs found, can be more than one in zip additional_subs = [] for sub in subtitles: # open the zip content = self._download_zip(sub.sub_id) if not is_zipfile(io.BytesIO(content)): # pragma: no cover if 'limite di download' in content: raise DownloadLimitExceeded() else: raise ConfigurationError('Not a zip file: %r' % content) with ZipFile(io.BytesIO(content)) as zf: if len(zf.namelist()) > 1: # pragma: no cover for index, name in enumerate(zf.namelist()): if index == 0: # First element sub.content = fix_line_ending(zf.read(name)) sub.full_data = name else: add_sub = copy.deepcopy(sub) add_sub.content = fix_line_ending(zf.read(name)) add_sub.full_data = name additional_subs.append(add_sub) else: sub.content = fix_line_ending(zf.read(zf.namelist()[0])) sub.full_data = zf.namelist()[0] return subtitles + additional_subs
def _get_season_subtitles(self, show_id, season, sub_source): params = { "apikey": self.apikey, "show_id": show_id, "q": "Stagione %%%d" % season, "version": sub_source } r = self.session.get(self.server_url + "subtitles/search", params=params, timeout=30) r.raise_for_status() root = ElementTree.fromstring(r.content) if int(root.find("data/count").text) == 0: logger.warning( "Subtitles for season not found, try with rip suffix") params["version"] = sub_source + "rip" r = self.session.get(self.server_url + "subtitles/search", params=params, timeout=30) r.raise_for_status() root = ElementTree.fromstring(r.content) if int(root.find("data/count").text) == 0: logger.warning("Subtitles for season not found") return [] subs = [] # Looking for subtitles in first page season_re = re.compile(".*?stagione 0*?%d.*" % season) for subtitle in root.findall("data/subtitles/subtitle"): if season_re.match(subtitle.find("name").text.lower()): logger.debug("Found season zip id %d - %r - %r", int(subtitle.find("id").text), subtitle.find("name").text, subtitle.find("version").text) content = self._download_zip(int(subtitle.find("id").text)) if not is_zipfile(io.BytesIO(content)): # pragma: no cover if "limite di download" in content: raise DownloadLimitExceeded() else: raise ConfigurationError("Not a zip file: %r" % content) with ZipFile(io.BytesIO(content)) as zf: episode_re = re.compile(r"s(\d{1,2})e(\d{1,2})") for index, name in enumerate(zf.namelist()): match = episode_re.search(name) if not match: # pragma: no cover logger.debug("Cannot decode subtitle %r", name) else: sub = ItaSASubtitle( int(subtitle.find("id").text), subtitle.find("show_name").text, int(match.group(1)), int(match.group(2)), None, None, None, name) sub.content = fix_line_ending(zf.read(name)) subs.append(sub) return subs
def checked(fn, raise_api_limit=False, validate_token=False, validate_json=False, json_key_name=None, validate_content=False): """Run :fn: and check the response status before returning it. :param fn: the function to make an API call to OpenSubtitles.com. :param raise_api_limit: if True we wait a little bit longer before running the call again. :param validate_token: test if token is valid and return 401 if not. :param validate_json: test if response is valid json. :param json_key_name: test if returned json contain a specific key. :param validate_content: test if response have a content (used with download). :return: the response. """ response = None try: try: response = fn() except APIThrottled: if not raise_api_limit: logger.info( "API request limit hit, waiting and trying again once.") time.sleep(2) return checked(fn, raise_api_limit=True) raise except (ConnectionError, Timeout, ReadTimeout): raise ServiceUnavailable( f'Unknown Error, empty response: {response.status_code}: {response}' ) except Exception: logging.exception('Unhandled exception raised.') raise ProviderError('Unhandled exception raised. Check log.') else: status_code = response.status_code except Exception: status_code = None else: if status_code == 401: if validate_token: return 401 else: raise AuthenticationError(f'Login failed: {response.reason}') elif status_code == 403: raise ProviderError("Bazarr API key seems to be in problem") elif status_code == 406: raise DownloadLimitExceeded("Daily download limit reached") elif status_code == 410: raise ProviderError("Download as expired") elif status_code == 429: raise TooManyRequests() elif status_code == 502: # this one should deal with Bad Gateway issue on their side. raise APIThrottled() elif 500 <= status_code <= 599: raise ProviderError(response.reason) if status_code != 200: raise ProviderError(f'Bad status code: {response.status_code}') if validate_json: try: json_test = response.json() except JSONDecodeError: raise ProviderError('Invalid JSON returned by provider') else: if json_key_name not in json_test: raise ProviderError( f'Invalid JSON returned by provider: no {json_key_name} key in returned json.' ) if validate_content: if not hasattr(response, 'content'): logging.error('Download link returned no content attribute.') return False elif not response.content: logging.error( f'This download link returned empty content: {response.url}' ) return False return response