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 not results: raise IndexerShowNotFound( 'Show search failed in getting a result with reason: Not found' ) return results
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}'.format(request_language), append_to_response=extra_info) except RequestException as error: raise IndexerUnavailable( 'Show info retrieval failed using indexer TMDB. Cause: {cause!r}' .format(cause=error)) if not results: return mapped_results = self._map_results(results, self.series_map, '|') return OrderedDict({'series': mapped_results})
def _get_show_by_id(self, tvdb_id, request_language='de'): # pylint: disable=unused-argument """Retrieve Glotz show information by tvdb id. :param tvdb_id: The shows tvdb id :return: An ordered dict with the show searched for. """ results = None if tvdb_id: log.debug('Getting all show data for {0}', tvdb_id) try: results = self.glotz_api.get_show(tvdb_id=tvdb_id, language=request_language) except IDNotFound as error: 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 results: log.debug('Getting aliases for show {0}', tvdb_id) results.aliases = self.glotz_api.get_show_aliases(tvdb_id) if not results: log.debug('Getting show data for {0} on Glotz failed', tvdb_id) return 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._map_results(results, self.series_map, '|') return OrderedDict({'series': mapped_results})
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 __init__(self, *args, **kwargs): # pylint: disable=too-many-locals,too-many-arguments """Tmdb api constructor.""" super(Tmdb, self).__init__(*args, **kwargs) 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 (AttributeError, 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'), ('status', 'status'), ('seriesname', 'name'), ('aliasnames', 'original_name'), ('overview', 'overview'), ('firstaired', 'air_date'), ('firstaired', 'first_air_date'), ('show_url', 'url'), ('episodenumber', 'episode_number'), ('seasonnumber', 'season_number'), ('dvd_episodenumber', 'dvd_episode_number'), ('airs_dayofweek', 'last_air_date'), ('lastupdated', 'last_updated'), ('networkid', 'network_id'), ('rating', 'vote_average'), ('genre', 'genres'), ('classification', 'type'), ('network', 'networks[0].name'), ('runtime', 'episode_run_time'), ('seasons', 'seasons'), ('poster_thumb', 'poster_path'), ('fanart', 'backdrop_path'), ('origin_country', 'origin_country'), ('external_ids', 'external_ids'), ] self.episodes_map = [ ('id', 'id'), ('episodename', 'name'), ('overview', 'overview'), ('firstaired', 'air_date'), ('runtime', 'episode_run_time'), ('episodenumber', 'episode_number'), ('seasonnumber', 'season_number'), ('rating', 'vote_average'), ('filename', 'still_path'), ]
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 as int :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) if last_update_ts == from_time: break from_time = last_update_ts total_updates += [int(_.id) for _ in updates] count += 1 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 IndexerUnavailable( 'Error connecting to TVDB API. Reason: {0}'.format( error.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 _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='de'): """ Use the Glotz 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. As Glotz is primarily a German indexer, default is German :return: A list of Show objects. """ try: results = self.glotz_api.get_show_list(show, request_language) except ShowNotFound as error: # Use error.value because Glotz 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 _show_search(self, series): """ Use the Imdb API to search for a show. :param series: The series name that's searched for as a string :return: A list of Show objects.series_map """ try: results = self.imdb_api.search_for_title(series) except LookupError as error: raise IndexerShowNotFound( 'Could not get any results searching for {series} using indexer Imdb. Cause: {cause!r}' .format(series=series, cause=error)) except (AttributeError, RequestException) as error: raise IndexerUnavailable( 'Could not get any results searching for {series} using indexer Imdb. Cause: {cause!r}' .format(series=series, cause=error)) if results: return results else: return None
def _parse_actors(self, imdb_id): """Get and parse actors using the get_title_credits route. Actors are retrieved using t['show name]['_actors']. Any key starting with an underscore has been processed (not the raw data from the indexer) """ log.debug('Getting actors for {0}', imdb_id) try: actors = self.imdb_api.get_title_credits( ImdbIdentifier(imdb_id).imdb_id) except LookupError as error: raise IndexerShowNotFound( 'Could not find show {imdb_id} using indexer Imdb. Cause: {cause!r}' .format(imdb_id=imdb_id, cause=error)) except (AttributeError, RequestException) as error: raise IndexerUnavailable( 'Could not get actors for show {imdb_id} using indexer Imdb. Cause: {cause!r}' .format(imdb_id=imdb_id, cause=error)) if not actors.get('credits') or not actors['credits'].get('cast'): return cur_actors = Actors() for order, cur_actor in enumerate(actors['credits']['cast'][:25]): save_actor = Actor() save_actor['id'] = cur_actor['id'].split('/')[-2] save_actor['image'] = cur_actor.get('image', {}).get('url', None) save_actor['name'] = cur_actor['name'] save_actor['role'] = cur_actor['characters'][0] if cur_actor.get( 'characters') else '' save_actor['sortorder'] = order cur_actors.append(save_actor) self._set_show_data(imdb_id, '_actors', cur_actors)
def _get_episodes(self, imdb_id, detailed=True, aired_season=None, *args, **kwargs): # pylint: disable=unused-argument """Get all the episodes for a show by imdb id. :param imdb_id: Series imdb 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}', imdb_id) if aired_season: aired_season = [ aired_season ] if not isinstance(aired_season, list) else aired_season series_id = imdb_id imdb_id = ImdbIdentifier(imdb_id).imdb_id try: # results = self.imdb_api.get_title_episodes(imdb_id) results = self.imdb_api.get_title_episodes( ImdbIdentifier(imdb_id).imdb_id) except LookupError as error: raise IndexerShowIncomplete( 'Show episode search exception, ' 'could not get any episodes. Exception: {e!r}'.format(e=error)) except (AttributeError, RequestException) as error: raise IndexerUnavailable( 'Error connecting to Imdb api. Caused by: {0!r}'.format(error)) if not results or not results.get('seasons'): return False absolute_number_counter = 1 for season in results.get('seasons'): if aired_season and season.get('season') not in aired_season: continue for episode in season['episodes']: season_no, episode_no = episode.get('season'), episode.get( 'episode') if season_no is None or episode_no is None: log.debug( '{0}: Found incomplete episode with season: {1!r} and episode: {2!r})', imdb_id, season_no, episode_no) continue # Skip to next episode if season_no > 0: episode['absolute_number'] = absolute_number_counter absolute_number_counter += 1 for k, config in self.episode_map: v = self.get_nested_value(episode, config) if v is not None: if k == 'id': v = ImdbIdentifier(v).series_id if k == 'firstaired': v = '{year}-01-01'.format(year=v) self._set_item(series_id, season_no, episode_no, k, v) if detailed and season.get('season'): # Enrich episode for the current season. self._get_episodes_detailed(imdb_id, season['season']) # Scrape the synopsys and the episode thumbnail. self._enrich_episodes(imdb_id, season['season']) # Try to calculate the airs day of week self._calc_airs_day_of_week(imdb_id)
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}'.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)
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']) if paged_episodes.data: 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']) if paged_episodes.data: 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: show_data = self.shows.get(tvdb_id) if show_data and not show_data['firstaired']: log.info('Show {name} does not have any episodes yet.', {'name': self.shows[tvdb_id]['seriesname']}) else: raise IndexerUnavailable( 'Error connecting to TVDB API. Reason: {reason}'.format( reason=error.reason)) mapped_episodes = self._map_results(results, self.series_map, '|') return OrderedDict({ 'episode': mapped_episodes if isinstance(mapped_episodes, list) else [mapped_episodes] })
def get_last_updated_seasons(self, show_list=None, cache=None, *args, **kwargs): """Return updated seasons for shows passed, using the from_time. :param show_list[int]: The list of shows, where seasons updates are retrieved for. :param from_time[int]: epoch timestamp, with the start date/time :param weeks: number of weeks to get updates for. """ show_season_updates = {} # we don't have a single api call tha we can run to check if an update is required. # So we'll have to check what's there in the library, and decide based on the last episode's date, if a # season update is needed. for series_id in show_list: series_obj = Show.find_by_id(app.showList, self.indexer, series_id) all_episodes_local = series_obj.get_all_episodes() total_updates = [] results = None # A small api call to get the amount of known seasons try: results = self.imdb_api.get_title_episodes( ImdbIdentifier(series_id).imdb_id) except LookupError as error: raise IndexerShowIncomplete( 'Show episode search exception, ' 'could not get any episodes. Exception: {error!r}'.format( error=error)) except (AttributeError, RequestException) as error: raise IndexerUnavailable( 'Error connecting to Imdb api. Caused by: {0!r}'.format( error)) if not results or not results.get('seasons'): continue # Get all the seasons # Loop through seasons for season in results['seasons']: season_number = season.get('season') # Imdb api gives back a season without the 'season' key. This season has special episodes. # Dont know what this is, but skipping it. if not season_number: continue # Check if the season is already known in our local db. local_season_episodes = [ ep for ep in all_episodes_local if ep.season == season_number ] remote_season_episodes = season['episodes'] if not local_season_episodes or len( remote_season_episodes) != len(local_season_episodes): total_updates.append(season_number) log.debug( '{series}: Season {season} seems to be a new season. Adding it.', { 'series': series_obj.name, 'season': season_number }) continue # Per season, get latest episode airdate sorted_episodes = sorted(local_season_episodes, key=lambda x: x.airdate) # date_season_start = sorted_episodes[0].airdate date_season_last = sorted_episodes[-1].airdate # Get date for last updated, from the cache object. # Calculate update interval for the season update_interval = self._calc_update_interval( # date_season_start, date_season_last, season_finished=bool([ s for s in results['seasons'] if s.get('season') == season_number + 1 ])) last_update = cache.get_last_update_season( self.indexer, series_id, season_number) if last_update < time() - update_interval: # This season should be updated. total_updates.append(season_number) # Update last_update for this season. cache.set_last_update_season(self.indexer, series_id, season_number) else: log.debug( '{series}: Season {season} seems to have been recently updated. Not scheduling a new refresh', { 'series': series_obj.name, 'season': season_number }) show_season_updates[series_id] = list(set(total_updates)) return show_season_updates
def _parse_images(self, imdb_id, language='en'): """Parse Show and Season posters. Any key starting with an underscore has been processed (not the raw data from the XML) This interface will be improved in future versions. Available sources: amazon, custom, getty, paidcustomer, presskit, userupload. Available types: behind_the_scenes, event, poster, product, production_art, publicity, still_frame """ log.debug('Getting show banners for {0}', imdb_id) try: images = self.imdb_api.get_title_images( ImdbIdentifier(imdb_id).imdb_id) except LookupError as error: raise IndexerShowNotFound( 'Could not find show {imdb_id} using indexer Imdb. Cause: {cause!r}' .format(imdb_id=imdb_id, cause=error)) except (AttributeError, RequestException) as error: raise IndexerUnavailable( 'Could not get images for show {imdb_id} using indexer Imdb. Cause: {cause!r}' .format(imdb_id=imdb_id, cause=error)) image_mapping = { 'poster': 'poster', 'production_art': 'fanart' } # Removed 'still_frame': 'fanart', thumb_height = 640 _images = {} try: for image in images.get('images', []): image_type = image_mapping.get(image.get('type')) if image_type not in ('poster', 'fanart'): continue image_type_thumb = image_type + '_thumb' if image_type not in _images: _images[image_type] = {} _images[image_type + '_thumb'] = {} # Store the images for each resolution available # Always provide a resolution or 'original'. resolution = '{0}x{1}'.format(image['width'], image['height']) thumb_width = int( (float(image['width']) / image['height']) * thumb_height) resolution_thumb = '{0}x{1}'.format(thumb_width, thumb_height) if resolution not in _images[image_type]: _images[image_type][resolution] = {} _images[image_type_thumb][resolution_thumb] = {} bid = image['id'].split('/')[-1] if image_type in ['season', 'seasonwide']: if int(image.sub_key ) not in _images[image_type][resolution]: _images[image_type][resolution][int( image.sub_key)] = {} if bid not in _images[image_type][resolution][int( image.sub_key)]: _images[image_type][resolution][int( image.sub_key)][bid] = {} base_path = _images[image_type_thumb][resolution][int( image.sub_key)][bid] else: if bid not in _images[image_type][resolution]: _images[image_type][resolution][bid] = {} _images[image_type_thumb][resolution_thumb][bid] = {} base_path = _images[image_type][resolution][bid] base_path_thumb = _images[image_type_thumb][ resolution_thumb][bid] base_path['bannertype'] = image_type base_path['bannertype2'] = resolution base_path['_bannerpath'] = image.get('url') base_path['bannerpath'] = image.get('url').split('/')[-1] base_path['languages'] = image.get('languages') base_path['source'] = image.get('source') base_path['id'] = bid base_path_thumb['bannertype'] = image_type_thumb base_path_thumb['bannertype2'] = resolution_thumb base_path_thumb['_bannerpath'] = image['url'].split( 'V1')[0] + 'V1_SY{0}_AL_.jpg'.format(thumb_height) base_path_thumb['bannerpath'] = image['url'].split('V1')[ 0] + 'V1_SY{0}_AL_.jpg'.format(thumb_height).split('/')[-1] base_path_thumb['id'] = bid except Exception as error: log.warning( 'Could not parse Poster for show id: {0}, with exception: {1!r}', imdb_id, error) return def _get_poster_thumb(thumbs): for bid in thumbs.values(): for image in bid.values(): return image.get('bannerpath') if _images.get('poster_thumb'): self._set_show_data(imdb_id, 'poster_thumb', _get_poster_thumb(_images.get('poster_thumb'))) self._save_images(imdb_id, _images, language=language) self._set_show_data(imdb_id, '_banners', _images)
def _get_show_by_id(self, imdb_id): # pylint: disable=unused-argument """Retrieve imdb show information by imdb id, or if no imdb id provided by passed external id. :param imdb_id: The shows imdb id :return: An ordered dict with the show searched for. """ results = None log.debug('Getting all show data for {0}', imdb_id) try: results = self.imdb_api.get_title(ImdbIdentifier(imdb_id).imdb_id) except LookupError as error: raise IndexerShowNotFound( 'Could not find show {imdb_id} using indexer Imdb. Cause: {cause!r}' .format(imdb_id=imdb_id, cause=error)) except (AttributeError, RequestException) as error: raise IndexerUnavailable( 'Could not find show {imdb_id} using indexer Imdb. Cause: {cause!r}' .format(imdb_id=imdb_id, cause=error)) if not results: return mapped_results = self._map_results(results, self.series_map) if not mapped_results: return try: # Get firstaired releases = self.imdb_api.get_title_releases( ImdbIdentifier(imdb_id).imdb_id) except LookupError as error: raise IndexerShowNotFound( 'Could not find show {imdb_id} using indexer Imdb. Cause: {cause!r}' .format(imdb_id=imdb_id, cause=error)) except (AttributeError, RequestException) as error: raise IndexerUnavailable( 'Could not get title releases for show {imdb_id} using indexer Imdb. Cause: {cause!r}' .format(imdb_id=imdb_id, cause=error)) if releases.get('releases'): first_released = sorted([r['date'] for r in releases['releases']])[0] mapped_results['firstaired'] = first_released try: companies = self.imdb_api.get_title_companies( ImdbIdentifier(imdb_id).imdb_id) # If there was a release check if it was distributed. if companies.get('distribution'): origins = self.imdb_api.get_title_versions( ImdbIdentifier(imdb_id).imdb_id)['origins'][0] released_in_regions = [ dist for dist in companies['distribution'] if dist.get('regions') and origins in dist['regions'] ] # Used item.get('startYear') because a startYear is not always available. first_release = sorted(released_in_regions, key=lambda x: x.get('startYear')) if first_release: mapped_results['network'] = first_release[0]['company'][ 'name'] except (AttributeError, LookupError, RequestException): log.info('No company data available for {0}, cant get a network', imdb_id) return OrderedDict({'series': mapped_results})