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)) raise IndexerShowNotFound( 'Show search failed in getting a result with reason: {0}'. format(error.reason)) except RequestException as error: raise IndexerException( 'Show search failed in getting a result with error: {0!r}'. format(error)) if results: return results else: return OrderedDict({'data': None})
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 Exception as e: raise IndexerException('Show search failed in getting a result with error: {0!r}'.format(e)) if results: return results else: return OrderedDict({'data': None})
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) ) raise IndexerShowNotFound( 'Show search failed in getting a result with reason: {reason} ({status})'.format( reason=error.reason, status=error.status ) ) except RequestException as error: raise IndexerException('Show search failed in getting a result with error: {0!r}'.format(error)) 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 _get_series_season_updates(self, tmdb_id, start_date=None, end_date=None): """ Retrieve all updates (show,season,episode) from TMDB. :return: A list of updated seasons for a show id. """ results = [] page = 1 total_pages = 1 try: while page <= total_pages: # Requesting for the changes on a specific showid, will result in json with changes per season. updates = self.tmdb.TV(tmdb_id).changes(start_date=start_date, end_date=end_date) if updates and updates.get('changes'): for items in [update['items'] for update in updates['changes'] if update['key'] == 'season']: for season in items: results += [season['value']['season_number']] total_pages = updates.get('total_pages', 0) page += 1 except RequestException as error: raise IndexerException('Could not get latest series season updates for series {series}. Cause: {cause}'.format( series=tmdb_id, cause=error )) return set(results)
def get_id_by_external(self, **kwargs): """Search tmdb for a show, using an external id. Accepts as kwargs, so you'l need to add the externals as key/values. :param tvrage_id: The tvrage id. :param tvdb_id: The tvdb id. :param imdb_id: An imdb id (inc. tt). :returns: A dict with externals, including the tvmaze id. """ try: wanted_externals = ['tvdb_id', 'imdb_id', 'tvrage_id'] for external_id in wanted_externals: if kwargs.get(external_id): result = self.tmdb.Find(kwargs.get(external_id)).info(**{'external_source': external_id}) if result.get('tv_results') and result['tv_results'][0]: # Get the external id's for the passed shows id. externals = self.tmdb.TV(result['tv_results'][0]['id']).external_ids() externals = {tmdb_external_id: external_value for tmdb_external_id, external_value in viewitems(externals) if external_value and tmdb_external_id in wanted_externals} externals['tmdb_id'] = result['tv_results'][0]['id'] return externals return {} except RequestException as error: raise IndexerException("Could not get external id's. Cause: {cause}".format(cause=error))
def _parse_actors(self, tmdb_id): """Parse actors XML.""" log.debug('Getting actors for {0}', tmdb_id) # TMDB also support passing language here as a param. try: credits = self.tmdb.TV(tmdb_id).credits(language=self.config['language']) # pylint: disable=W0622 except RequestException as error: raise IndexerException('Could not get actors. Cause: {cause}'.format(cause=error)) if not credits or not credits.get('cast'): log.debug('Actors result returned zero') return cur_actors = Actors() for cur_actor in credits.get('cast'): new_actor = Actor() new_actor['id'] = cur_actor['credit_id'] new_actor['image'] = \ '{base_url}{image_size}{profile_path}'.format(base_url=self.tmdb_configuration.images['base_url'], image_size='original', profile_path=cur_actor['profile_path']) new_actor['name'] = cur_actor['name'] new_actor['role'] = cur_actor['character'] new_actor['sortorder'] = cur_actor['order'] cur_actors.append(new_actor) self._set_show_data(tmdb_id, '_actors', cur_actors)
def _get_episodes(self, tvmaze_id, specials=False, aired_season=None): # pylint: disable=unused-argument """ Get all the episodes for a show by tvmaze id. :param tvmaze_id: Series tvmaze id. :return: An ordered dict with the show searched for. In the format of OrderedDict{"episode": [list of episodes]} """ # Parse episode data log.debug('Getting all episodes of {0}', tvmaze_id) try: results = self.tvmaze_api.episode_list(tvmaze_id, specials=specials) except IDNotFound: log.debug('Episode search did not return any results.') return False except BaseError as e: raise IndexerException('Show episodes search failed in getting a result with error: {0!r}'.format(e)) episodes = self._map_results(results, self.series_map) if not episodes: return False if not isinstance(episodes, list): episodes = [episodes] for cur_ep in episodes: if self.config['dvdorder']: log.debug('Using DVD ordering.') use_dvd = cur_ep.get('dvd_season') is not None and cur_ep.get('dvd_episodenumber') is not None else: use_dvd = False if use_dvd: seasnum, epno = cur_ep.get('dvd_season'), cur_ep.get('dvd_episodenumber') else: seasnum, epno = cur_ep.get('seasonnumber'), cur_ep.get('episodenumber') if self.config['dvdorder']: log.warning('No DVD order for episode (season: {0}, episode: {1}). ' 'Falling back to non-DVD order. ' 'Please consider disabling DVD order for the show with TVmaze ID: {2}', seasnum, epno, tvmaze_id) if seasnum is None or epno is None: log.warning('An episode has incomplete season/episode number (season: {0!r}, episode: {1!r})', seasnum, epno) continue # Skip to next episode seas_no = int(seasnum) ep_no = int(epno) for k, v in viewitems(cur_ep): k = k.lower() if v is not None: if k == 'image_medium': self._set_item(tvmaze_id, seas_no, ep_no, 'filename', v) self._set_item(tvmaze_id, seas_no, ep_no, k, v)
def _get_all_updates(self, start_date=None, end_date=None): """Retrieve all updates (show,season,episode) from TMDB.""" results = [] page = 1 total_pages = 1 try: while page <= total_pages: updates = self.tmdb.Changes().tv(start_date=start_date, end_date=end_date, page=page) if not updates or not updates.get('results'): break results += [_.get('id') for _ in updates.get('results')] total_pages = updates.get('total_pages') page += 1 except RequestException as error: raise IndexerException('Could not get latest updates. Cause: {cause}'.format( cause=error )) return set(results)
def _show_search(self, show, request_language='en'): """ Uses 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: raise IndexerShowNotFound( 'Show search failed in getting a result with reason: {0}'. format(error)) except BaseError as error: raise IndexerException( 'Show search failed in getting a result with error: {0!r}'. format(error)) if results: return results else: return None
def _get_episodes(self, tmdb_id, specials=False, aired_season=None): # pylint: disable=unused-argument """Get all the episodes for a show by TMDB id. :param tmdb_id: Series tmdb id. :return: An ordered dict with the show searched for. In the format of OrderedDict{"episode": [list of episodes]} """ results = [] if aired_season: aired_season = [aired_season] if not isinstance(aired_season, list) else aired_season else: if tmdb_id not in self.shows or not self.shows[tmdb_id].data.get('seasons'): self.config['episodes_enabled'] = False # Don't want to get episodes, as where already doing that. self._get_show_data(tmdb_id) # Get show data, with the list of seasons aired_season = [season['season_number'] for season in self.shows[tmdb_id].data.get('seasons', [])] if not aired_season: log.debug('Series does not have any seasons added on indexer TMDB.') return # Parse episode data log.debug('Getting all episodes of {0}', tmdb_id) # get episodes for each season for season in aired_season: try: season_info = self.tmdb.TV_Seasons(tmdb_id, season).info(language=self.config['language']) results += season_info['episodes'] except RequestException as error: raise IndexerException( 'Could not get episodes for series {series} using indexer TMDB. Cause: {cause}'.format( series=tmdb_id, cause=error ) ) if not results: log.debug('Series does not have any episodes added on indexer TMDB.') return mapped_episodes = self._map_results(results, self.episodes_map, '|') episode_data = OrderedDict({'episode': mapped_episodes}) if 'episode' not in episode_data: return False episodes = episode_data['episode'] if not isinstance(episodes, list): episodes = [episodes] for cur_ep in episodes: if self.config['dvdorder']: log.debug('Using DVD ordering.') use_dvd = cur_ep.get('dvd_season') is not None and cur_ep.get('dvd_episodenumber') is not None else: use_dvd = False if use_dvd: seasnum, epno = cur_ep.get('dvd_season'), cur_ep.get('dvd_episodenumber') else: seasnum, epno = cur_ep.get('seasonnumber'), cur_ep.get('episodenumber') if self.config['dvdorder']: log.warning( 'No DVD order available for episode (season: {0}, episode: {1}). ' 'Falling back to non-DVD order. ' 'Please consider disabling DVD order for the show with TMDB ID: {2}', seasnum, epno, tmdb_id ) if seasnum is None or epno is None: log.warning('Invalid episode numbering (season: {0!r}, episode: {1!r})', seasnum, epno) continue # Skip to next episode seas_no = int(seasnum) ep_no = int(epno) image_width = {'fanart': 'w1280', 'poster': 'w780', 'filename': 'w300'} for k, v in viewitems(cur_ep): k = k.lower() if v is not None: if k in ['filename', 'poster', 'fanart']: # I'm using the default 'original' quality. But you could also check tmdb_configuration, # for the available image sizes. v = self.config['artwork_prefix'].format(base_url=self.tmdb_configuration.images['base_url'], image_size=image_width[k], file_path=v) self._set_item(tmdb_id, seas_no, ep_no, k, v)
def _get_show_data(self, tmdb_id, language='en'): """Take a series ID, gets the epInfo URL and parses the TMDB json response. into the shows dict in layout: shows[series_id][season_number][episode_number] """ if self.config['language'] is None: log.debug('Config language is none, using show language') if language is None: raise IndexerError("config['language'] was None, this should not happen") get_show_in_language = language else: log.debug('Configured language {0} override show language of {1}', self.config['language'], language) get_show_in_language = self.config['language'] # Parse show information log.debug('Getting all series data for {0}', tmdb_id) # Parse show information series_info = self._get_show_by_id(tmdb_id, request_language=get_show_in_language) if not series_info: log.debug('Series result returned zero') raise IndexerError('Series result returned zero') # Get MPAA rating if available try: content_ratings = self.tmdb.TV(tmdb_id).content_ratings() if content_ratings and content_ratings.get('results'): mpaa_rating = next((r['rating'] for r in content_ratings['results'] if r['iso_3166_1'].upper() == 'US'), None) if mpaa_rating: self._set_show_data(tmdb_id, 'contentrating', mpaa_rating) except RequestException as error: raise IndexerException('Could not get series data for series {series}. Cause: {cause}'.format( series=tmdb_id, cause=error )) # get series data / add the base_url to the image urls # Create a key/value dict, to map the image type to a default image width. # possitlbe widths can also be retrieved from self.configuration.images['poster_sizes'] and # self.configuration.images['still_sizes'] image_width = {'fanart': 'w1280', 'poster': 'w500'} for k, v in viewitems(series_info['series']): if v is not None: if k in ['fanart', 'banner', 'poster']: v = self.config['artwork_prefix'].format(base_url=self.tmdb_configuration.images['base_url'], image_size=image_width[k], file_path=v) self._set_show_data(tmdb_id, k, v) # Get external ids. # As the external id's are not part of the shows default response, we need to make an additional call for it. try: external_ids = self.tmdb.TV(tmdb_id).external_ids() self._set_show_data(tmdb_id, 'externals', external_ids) except RequestException as error: raise IndexerException("Could not get external id's for series {series} Cause: {cause}".format( series=tmdb_id, cause=error )) # get episode data if self.config['episodes_enabled']: self._get_episodes(tmdb_id, specials=False, aired_season=None) # Parse banners if self.config['banners_enabled']: self._parse_images(tmdb_id) # Parse actors if self.config['actors_enabled']: self._parse_actors(tmdb_id) return True