def _get_show_by_id(self, tmdb_id, request_language='en', extra_info=None): """Retrieve tmdb show information by tmdb id. :param tmdb_id: The show's tmdb id :param request_language: Language to get the show in :type request_language: string or unicode :extra_info: Extra details of the show to get (e.g. ['content_ratings', 'external_ids']) :type extra_info: list, tuple or None :return: An ordered dict with the show searched for. """ if extra_info and isinstance(extra_info, (list, tuple)): extra_info = ','.join(extra_info) log.debug('Getting all show data for {0}', tmdb_id) try: results = self.tmdb.TV(tmdb_id).info( language='{0},null'.format(request_language), append_to_response=extra_info ) if not results: return except RequestException as error: raise IndexerUnavailable('Show info retrieval failed using indexer TMDB. Cause: {cause!r}'.format( cause=error )) mapped_results = self._map_results(results, self.series_map, '|') return OrderedDict({'series': mapped_results})
def _get_show_by_id(self, tvdbv2_id, request_language='en'): # pylint: disable=unused-argument """Retrieve tvdbv2 show information by tvdbv2 id, or if no tvdbv2 id provided by passed external id. :param tvdbv2_id: The shows tvdbv2 id :return: An ordered dict with the show searched for. """ results = None if tvdbv2_id: log.debug('Getting all show data for {0}', tvdbv2_id) try: results = self.config['session'].series_api.series_id_get( tvdbv2_id, accept_language=request_language) except ApiException as error: if error.status == 401: raise IndexerAuthFailed( 'Authentication failed, possible bad API key. Reason: {reason} ({status})' .format(reason=error.reason, status=error.status)) if error.status == 404: raise IndexerShowNotFound( 'Show search failed in getting a result with reason: {reason} ({status})' .format(reason=error.reason, status=error.status)) raise IndexerUnavailable(error.reason) if not results: return if not getattr(results.data, 'series_name', None): raise IndexerShowNotFoundInLanguage( 'Missing attribute series_name, cant index in language: {0}'. format(request_language), request_language) mapped_results = self._object_to_dict(results, self.series_map, '|') return OrderedDict({'series': mapped_results})
def _show_search(self, show, request_language='en'): """Use TMDB API to search for a show. :param show: The show name that's searched for as a string :param request_language: Language in two letter code. TMDB fallsback to en itself. :return: A list of Show objects. """ try: # get paginated pages page = 1 last = 1 results = [] while page <= last: search_result = self.tmdb.Search().tv(query=show, language=request_language, page=page) last = search_result.get('total_pages', 0) results += search_result.get('results') page += 1 except RequestException as error: raise IndexerUnavailable('Show search failed using indexer TMDB. Cause: {cause}'.format(cause=error)) if results: return results else: return OrderedDict({'data': None})
def _get_show_by_id(self, tvmaze_id, request_language='en'): # pylint: disable=unused-argument """ Retrieve tvmaze show information by tvmaze id, or if no tvmaze id provided by passed external id. :param tvmaze_id: The shows tvmaze id :return: An ordered dict with the show searched for. """ results = None if tvmaze_id: log.debug('Getting all show data for {0}', tvmaze_id) try: results = self.tvmaze_api.get_show(maze_id=tvmaze_id) except ShowNotFound as error: # Use error.value because TVMaze API exceptions may be utf-8 encoded when using __str__ raise IndexerShowNotFound( 'Show search failed in getting a result with reason: {0}'.format(error.value) ) except BaseError as error: raise IndexerUnavailable('Show search failed in getting a result with error: {0!r}'.format(error)) if not results: return mapped_results = self._map_results(results, self.series_map) return OrderedDict({'series': mapped_results})
def get_last_updated_series(self, from_time, weeks=1, filter_show_list=None): """Retrieve a list with updated shows. :param from_time: epoch timestamp, with the start date/time :param weeks: number of weeks to get updates for. :param filter_show_list: Optional list of show objects, to use for filtering the returned list. :returns: A list of show_id's. """ total_updates = [] updates = True count = 0 try: while updates and count < weeks: updates = self.config['session'].updates_api.updated_query_get( from_time).data if updates: last_update_ts = max(x.last_updated for x in updates) from_time = last_update_ts total_updates += [int(_.id) for _ in updates] count += 1 except ApiException as e: if e.status == 401: raise IndexerAuthFailed( 'Authentication failed, possible bad api key. reason: {reason} ({status})' .format(reason=e.reason, status=e.status)) raise IndexerUnavailable( 'Error connecting to Tvdb api. Caused by: {0}'.format( e.reason)) except RequestException as e: raise IndexerUnavailable( 'Error connecting to Tvdb api. Caused by: {0}'.format( e.reason)) if total_updates and filter_show_list: new_list = [] for show in filter_show_list: if show.indexerid in total_updates: new_list.append(show.indexerid) total_updates = new_list return total_updates
def __init__(self, *args, **kwargs): # pylint: disable=too-many-locals,too-many-arguments """Tmdb api constructor.""" super(Tmdb, self).__init__(*args, **kwargs) self.indexer = 4 self.tmdb = tmdb self.tmdb.API_KEY = TMDB_API_KEY self.tmdb.REQUESTS_SESSION = self.config['session'] self.tmdb_configuration = self.tmdb.Configuration() try: self.response = self.tmdb_configuration.info() except RequestException as e: raise IndexerUnavailable( 'Indexer TMDB is unavailable at this time. Cause: {cause}'. format(cause=e)) self.config['artwork_prefix'] = '{base_url}{image_size}{file_path}' # An api to indexer series/episode object mapping self.series_map = { 'id': 'id', 'name': 'seriesname', 'original_name': 'aliasnames', 'overview': 'overview', 'air_date': 'firstaired', 'first_air_date': 'firstaired', 'backdrop_path': 'fanart', 'url': 'show_url', 'episode_number': 'episodenumber', 'season_number': 'seasonnumber', 'dvd_episode_number': 'dvd_episodenumber', 'last_air_date': 'airs_dayofweek', 'last_updated': 'lastupdated', 'network_id': 'networkid', 'vote_average': 'contentrating', 'poster_path': 'poster', 'genres': 'genre', 'type': 'classification', 'networks': 'network', 'episode_run_time': 'runtime' } self.episodes_map = { 'id': 'id', 'name': 'episodename', 'overview': 'overview', 'air_date': 'firstaired', 'episode_run_time': 'runtime', 'episode_number': 'episodenumber', 'season_number': 'seasonnumber', 'vote_average': 'contentrating', 'still_path': 'filename' }
def _get_show_by_id(self, tmdb_id, request_language='en'): # pylint: disable=unused-argument """Retrieve tmdb show information by tmdb id. :param tmdb_id: The show's tmdb id :return: An ordered dict with the show searched for. """ log.debug('Getting all show data for {0}', tmdb_id) try: results = self.tmdb.TV(tmdb_id).info(language='{0},null'.format(request_language)) if not results: return except RequestException as error: raise IndexerUnavailable('Show info retrieval failed using indexer TMDB. Cause: {cause!r}'.format( cause=error )) mapped_results = self._map_results(results, self.series_map, '|') return OrderedDict({'series': mapped_results})
def _show_search(self, show, request_language='en'): """ Use the TVMaze API to search for a show. :param show: The show name that's searched for as a string :param request_language: Language in two letter code. TVMaze fallsback to en itself. :return: A list of Show objects. """ try: results = self.tvmaze_api.get_show_list(show) except ShowNotFound as error: # Use error.value because TVMaze API exceptions may be utf-8 encoded when using __str__ raise IndexerShowNotFound( 'Show search failed in getting a result with reason: {0}'.format(error.value) ) except BaseError as error: raise IndexerUnavailable('Show search failed in getting a result with error: {0!r}'.format(error)) return results
def _show_search(self, show, request_language='en'): """Use the pytvdbv2 API to search for a show. @param show: The show name that's searched for as a string @return: A list of Show objects. """ try: results = self.config['session'].search_api.search_series_get( name=show, accept_language=request_language) except ApiException as error: if error.status == 401: raise IndexerAuthFailed( 'Authentication failed, possible bad API key. Reason: {reason} ({status})' .format(reason=error.reason, status=error.status)) if error.status == 404: raise IndexerShowNotFound( 'Show search failed in getting a result with reason: {reason} ({status})' .format(reason=error.reason, status=error.status)) raise IndexerUnavailable(error.reason) return results
def _query_series(self, tvdb_id, specials=False, aired_season=None, full_info=False): """Query against episodes for the given series. :param tvdb_id: tvdb series id. :param specials: enable/disable download of specials. Currently not used. :param aired_season: the episodes returned for a specific aired season. :param full_info: add full information to the episodes :return: An ordered dict of {'episode': [list of episode dicts]} """ results = [] if aired_season: aired_season = [ aired_season ] if not isinstance(aired_season, list) else aired_season # Parse episode data log.debug('Getting all episodes of {0}', tvdb_id) # get paginated pages page = 1 last = 1 try: if aired_season: for season in aired_season: page = 1 last = 1 while page <= last: paged_episodes = self.config[ 'session'].series_api.series_id_episodes_query_get( tvdb_id, page=page, aired_season=season, accept_language=self.config['language']) results += paged_episodes.data last = paged_episodes.links.last page += 1 else: while page <= last: paged_episodes = self.config[ 'session'].series_api.series_id_episodes_query_get( tvdb_id, page=page, accept_language=self.config['language']) results += paged_episodes.data last = paged_episodes.links.last page += 1 if results and full_info: results = self._get_episodes_info(tvdb_id, results, season=aired_season) except ApiException as e: log.debug('Error trying to index the episodes') if e.status == 401: raise IndexerAuthFailed( 'Authentication failed, possible bad api key. reason: {reason} ({status})' .format(reason=e.reason, status=e.status)) raise IndexerShowIncomplete( 'Show episode search exception, ' 'could not get any episodes. Did a {search_type} search. Exception: {e}' .format(search_type='full' if not aired_season else 'season {season}'.format(season=aired_season), e=e.reason)) except RequestException as error: raise IndexerUnavailable( 'Error connecting to Tvdb api. Caused by: {error!r}'.format( error=error)) if not results: log.debug('Series results incomplete') raise IndexerShowIncomplete( 'Show episode search returned incomplete results, ' 'could not get any episodes. Did a {search_type} search.'. format(search_type='full' if not aired_season else 'season {season}'.format(season=aired_season))) mapped_episodes = self._object_to_dict(results, self.series_map, '|') return OrderedDict({ 'episode': mapped_episodes if isinstance(mapped_episodes, list) else [mapped_episodes] })
def _query_series(self, tvdb_id, specials=False, aired_season=None, full_info=False): """Query against episodes for the given series. :param tvdb_id: tvdb series id. :param specials: enable/disable download of specials. Currently not used. :param aired_season: the episodes returned for a specific aired season. :param full_info: add full information to the episodes :return: An ordered dict of {'episode': [list of episode dicts]} """ results = [] if aired_season: aired_season = [ aired_season ] if not isinstance(aired_season, list) else aired_season # Parse episode data log.debug('Getting all episodes of {0}', tvdb_id) # get paginated pages page = 1 last = 1 try: if aired_season: for season in aired_season: page = 1 last = 1 while page <= last: paged_episodes = self.config[ 'session'].series_api.series_id_episodes_query_get( tvdb_id, page=page, aired_season=season, accept_language=self.config['language']) results += paged_episodes.data last = paged_episodes.links.last page += 1 else: while page <= last: paged_episodes = self.config[ 'session'].series_api.series_id_episodes_query_get( tvdb_id, page=page, accept_language=self.config['language']) results += paged_episodes.data last = paged_episodes.links.last page += 1 if results and full_info: results = self._get_episodes_info(tvdb_id, results, season=aired_season) except ApiException as error: log.debug('Error trying to index the episodes') if error.status == 401: raise IndexerAuthFailed( 'Authentication failed, possible bad API key. Reason: {reason} ({status})' .format(reason=error.reason, status=error.status)) if error.status == 404 and not self.shows[tvdb_id]['firstaired']: log.info( 'Show {name} does not have any episodes yet, adding it anyway', {'name': self.shows[tvdb_id]['seriesname']}) else: raise IndexerUnavailable( 'Error connecting to TVDB API. Reason: {reason}'.format( reason=error.reason)) mapped_episodes = self._object_to_dict(results, self.series_map, '|') return OrderedDict({ 'episode': mapped_episodes if isinstance(mapped_episodes, list) else [mapped_episodes] })
def _parse_images(self, tmdb_id): """Parse images. This interface will be improved in future versions. """ key_mapping = {'file_path': 'bannerpath', 'vote_count': 'ratingcount', 'vote_average': 'rating', 'id': 'id'} image_sizes = {'fanart': 'backdrop_sizes', 'poster': 'poster_sizes'} typecasts = {'rating': float, 'ratingcount': int} log.debug('Getting show banners for {series}', series=tmdb_id) _images = {} # Let's get the different type of images available for this series params = {'include_image_language': '{search_language},null'.format(search_language=self.config['language'])} try: images = self.tmdb.TV(tmdb_id).images(params=params) except RequestException as error: raise IndexerUnavailable('Error trying to get images. Cause: {cause}'.format(cause=error)) bid = images['id'] for image_type, images in viewitems({'poster': images['posters'], 'fanart': images['backdrops']}): try: if images and image_type not in _images: _images[image_type] = {} for image in images: bid += 1 image_mapped = self._map_results(image, key_mappings=key_mapping) for size in self.tmdb_configuration.images.get(image_sizes[image_type]): if size == 'original': width = image_mapped['width'] height = image_mapped['height'] else: width = int(size[1:]) height = int(round(width / float(image_mapped['aspect_ratio']))) resolution = '{0}x{1}'.format(width, height) if resolution not in _images[image_type]: _images[image_type][resolution] = {} if bid not in _images[image_type][resolution]: _images[image_type][resolution][bid] = {} for k, v in viewitems(image_mapped): if k is None or v is None: continue try: typecast = typecasts[k] except KeyError: pass else: v = typecast(v) _images[image_type][resolution][bid][k] = v if k.endswith('path'): new_key = '_{0}'.format(k) log.debug('Adding base url for image: {0}', v) _images[image_type][resolution][bid][new_key] = self.config['artwork_prefix'].format( base_url=self.tmdb_configuration.images['base_url'], image_size=size, file_path=v) if size != 'original': _images[image_type][resolution][bid]['rating'] = 0 except Exception as error: log.warning('Could not parse Poster for show id: {0}, with exception: {1!r}', tmdb_id, error) return False season_images = self._parse_season_images(tmdb_id) if season_images: _images.update(season_images) self._save_images(tmdb_id, _images) self._set_show_data(tmdb_id, '_banners', _images)