def _create_recommended_show(self, series): """Create the RecommendedShow object from the returned showobj.""" externals = { 'imdb_id': ImdbIdentifier(series.get('imdb_tt')).series_id } # Get tmdb id using a call to tmdb api. t = indexerApi(INDEXER_TMDB).indexer( **indexerApi(INDEXER_TMDB).api_params.copy()) externals.update(t.get_id_by_external(**externals)) rec_show = RecommendedShow( self, ImdbIdentifier(series.get('imdb_tt')).series_id, series.get('name'), **{ 'rating': series.get('rating'), 'votes': series.get('votes'), 'image_href': series.get('imdb_url'), 'ids': externals, 'subcat': 'popular', 'genres': [genre.lower() for genre in series.get('genres')], 'plot': series.get('outline') }) if series.get('image_url'): rec_show.cache_image(series.get('image_url')) return rec_show
def _get_custom_exceptions(force): """Exceptions maintained by the medusa.github.io repo.""" custom_exceptions = defaultdict(dict) if force or should_refresh('custom_exceptions'): for indexer in indexerApi().indexers: location = indexerApi(indexer).config['scene_loc'] logger.info('Checking for scene exception updates from {location}', location=location) try: # When any Medusa Safe session exception, session returns None and then AttributeError when json() jdata = safe_session.get(location, timeout=60).json() except (ValueError, AttributeError) as error: logger.debug('Check scene exceptions update failed. Unable to ' 'update from {location}. Error: {error}'.format( location=location, error=error)) # If unable to get scene exceptions, assume we can't connect to CDN so we don't `continue` return custom_exceptions # let Glotz use the TVDB exceptions if indexerApi(indexer).config['name'] == 'Glotz': indexer_ids = jdata['tvdb'] else: indexer_ids = jdata[indexerApi(indexer).config['identifier']] for indexer_id in indexer_ids: indexer_exceptions = indexer_ids[indexer_id] alias_list = [{ exception: int(season) } for season in indexer_exceptions for exception in indexer_exceptions[season]] custom_exceptions[indexer][indexer_id] = alias_list set_last_refresh('custom_exceptions') return custom_exceptions
def get_externals(show=None, indexer=None, indexed_show=None): """Use as much as possible sources to map known id's. Provide the external id's you have in a dictionary, and use as much available resources as possible to retrieve external id's. :param show: Series object. :param indexer: Indexer id. For example 1 for tvdb or 4 for tmdb. :param indexed_show: The result of a fully indexed shows. For example after an t['12345'] """ if show: indexer = show.indexer new_show_externals = show.externals else: if not indexer or not indexed_show: raise Exception( 'Need a minimum of a show object or an indexer + indexer_api ' '(Show searched through indexerApi.') new_show_externals = getattr(indexed_show, 'externals', {}) # For this show let's get all externals, and use them. mappings = { indexer: indexerConfig[indexer]['mapped_to'] for indexer in indexerConfig } other_indexers = [ mapped_indexer for mapped_indexer in mappings if mapped_indexer != indexer ] # We for example want to add through tmdb, but the show is already added through tvdb. # If tmdb doesn't have a mapping to imdb, but tvmaze does, there is a small chance we can use that. for other_indexer in other_indexers: lindexer_api_pararms = indexerApi(other_indexer).api_params.copy() try: t = indexerApi(other_indexer).indexer(**lindexer_api_pararms) except IndexerUnavailable: continue if hasattr(t, 'get_id_by_external'): log.debug(u'Trying other indexer: {indexer} get_id_by_external', {'indexer': indexerApi(other_indexer).name}) # Call the get_id_by_external and pass all the externals we have, # except for the indexers own. try: new_show_externals.update( t.get_id_by_external(**new_show_externals)) except (IndexerException, RequestException) as error: log.warning( u'Error getting external ids for other' u' indexer {name}: {reason!r}', { 'name': indexerApi(other_indexer).name, 'reason': error }) # Try to update with the Trakt externals. if app.USE_TRAKT: new_show_externals.update(get_trakt_externals(new_show_externals)) return new_show_externals
def run(self): ShowQueueItem.run(self) log.info('{id}: Performing refresh on {show}', { 'id': self.show.series_id, 'show': self.show.name }) try: self.show.refresh_dir() if self.force: self.show.update_metadata() self.show.write_metadata() self.show.populate_cache() # Load XEM data to DB for show scene_numbering.xem_refresh(self.show, force=True) except IndexerException as error: log.warning( '{id}: Unable to contact {indexer}. Aborting: {error_msg}', { 'id': self.show.series_id, 'indexer': indexerApi(self.show.indexer).name, 'error_msg': error }) except Exception as error: log.error( '{id}: Error while refreshing show {show}. Error: {error_msg}', { 'id': self.show.series_id, 'show': self.show.name, 'error_msg': error }) self.finish()
def __init__(self): """Initialize the trakt recommended list object.""" self.cache_subfolder = __name__.split( '.')[-1] if '.' in __name__ else __name__ self.recommender = 'Trakt Popular' self.default_img_src = 'trakt-default.png' self.tvdb_api_v2 = indexerApi(INDEXER_TVDBV2).indexer()
def __init__(self): """Initialize the trakt recommended list object.""" super(TraktPopular, self).__init__() self.cache_subfolder = TraktPopular.CACHE_SUBFOLDER self.source = EXTERNAL_TRAKT self.recommender = TraktPopular.TITLE self.default_img_src = 'trakt-default.png' self.tvdb_api_v2 = indexerApi(INDEXER_TVDBV2).indexer()
def newShow(self, show_to_add=None, other_shows=None, search_string=None): """ Display the new show page which collects a tvdb id, folder, and extra options and posts them to addNewShow """ t = PageTemplate(rh=self, filename='addShows_newShow.mako') indexer, show_dir, indexer_id, show_name = self.split_extra_show(show_to_add) use_provided_info = bool(indexer_id and indexer and show_name) # use the given show_dir for the indexer search if available if not show_dir: if search_string: default_show_name = search_string else: default_show_name = '' elif not show_name: default_show_name = re.sub(r' \(\d{4}\)', '', os.path.basename(os.path.normpath(show_dir))) else: default_show_name = show_name # carry a list of other dirs if given if not other_shows: other_shows = [] elif not isinstance(other_shows, list): other_shows = [other_shows] other_shows = decode_shows(other_shows) provided_indexer_id = int(indexer_id or 0) provided_indexer_name = show_name provided_indexer = int(indexer or app.INDEXER_DEFAULT) return t.render( enable_anime_options=True, use_provided_info=use_provided_info, default_show_name=default_show_name, other_shows=other_shows, provided_show_dir=show_dir, provided_indexer_id=provided_indexer_id, provided_indexer_name=provided_indexer_name, provided_indexer=provided_indexer, indexers=indexerApi().indexers, whitelist=[], blacklist=[], groups=[], controller='addShows', action='newShow' )
def run(self): ShowQueueItem.run(self) log.info( '{id}: Beginning update of {show}{season}', { 'id': self.show.series_id, 'show': self.show.name, 'season': ' with season(s) [{0}]'.format(','.join( text_type(s) for s in self.seasons) if self.seasons else '') }) log.debug( '{id}: Retrieving show info from {indexer}', { 'id': self.show.series_id, 'indexer': indexerApi(self.show.indexer).name }) try: # Let's make sure we refresh the indexer_api object attached to the show object. self.show.create_indexer() self.show.load_from_indexer() except IndexerError as error: log.warning( '{id}: Unable to contact {indexer}. Aborting: {error_msg}', { 'id': self.show.series_id, 'indexer': indexerApi(self.show.indexer).name, 'error_msg': error }) return except IndexerAttributeNotFound as error: log.warning( '{id}: Data retrieved from {indexer} was incomplete.' ' Aborting: {error_msg}', { 'id': self.show.series_id, 'indexer': indexerApi(self.show.indexer).name, 'error_msg': error }) return log.debug('{id}: Retrieving show info from IMDb', {'id': self.show.series_id}) try: self.show.load_imdb_info() except ImdbAPIError as error: log.info('{id}: Something wrong on IMDb api: {error_msg}', { 'id': self.show.series_id, 'error_msg': error }) except RequestException as error: log.warning('{id}: Error loading IMDb info: {error_msg}', { 'id': self.show.series_id, 'error_msg': error }) # have to save show before reading episodes from db try: log.debug('{id}: Saving new IMDb show info to database', {'id': self.show.series_id}) self.show.save_to_db() except Exception as error: log.warning( '{id}: Error saving new IMDb show info to database: {error_msg}', { 'id': self.show.series_id, 'error_msg': error }) log.error(traceback.format_exc()) # get episode list from DB try: episodes_from_db = self.show.load_episodes_from_db(self.seasons) except IndexerException as error: log.warning( '{id}: Unable to contact {indexer}. Aborting: {error_msg}', { 'id': self.show.series_id, 'indexer': indexerApi(self.show.indexer).name, 'error_msg': error }) return # get episode list from the indexer try: episodes_from_indexer = self.show.load_episodes_from_indexer( self.seasons) except IndexerException as error: log.warning( '{id}: Unable to get info from {indexer}. The show info will not be refreshed.' ' Error: {error_msg}', { 'id': self.show.series_id, 'indexer': indexerApi(self.show.indexer).name, 'error_msg': error }) episodes_from_indexer = None if episodes_from_indexer is None: log.warning( '{id}: No data returned from {indexer} during season show update.' ' Unable to update this show', { 'id': self.show.series_id, 'indexer': indexerApi(self.show.indexer).name }) else: # for each ep we found on the Indexer delete it from the DB list for cur_season in episodes_from_indexer: for cur_episode in episodes_from_indexer[cur_season]: if cur_season in episodes_from_db and cur_episode in episodes_from_db[ cur_season]: del episodes_from_db[cur_season][cur_episode] # remaining episodes in the DB list are not on the indexer, just delete them from the DB for cur_season in episodes_from_db: for cur_episode in episodes_from_db[cur_season]: log.debug( '{id}: Permanently deleting episode {show} {ep} from the database', { 'id': self.show.series_id, 'show': self.show.name, 'ep': episode_num(cur_season, cur_episode) }) # Create the ep object only because Im going to delete it ep_obj = self.show.get_episode(cur_season, cur_episode) try: ep_obj.delete_episode() except EpisodeDeletedException: log.debug( '{id}: Episode {show} {ep} successfully deleted from the database', { 'id': self.show.series_id, 'show': self.show.name, 'ep': episode_num(cur_season, cur_episode) }) # Save only after all changes were applied try: log.debug('{id}: Saving all updated show info to database', {'id': self.show.series_id}) self.show.save_to_db() except Exception as error: log.warning( '{id}: Error saving all updated show info to database: {error_msg}', { 'id': self.show.series_id, 'error_msg': error }) log.error(traceback.format_exc()) log.info('{id}: Finished update of {show}', { 'id': self.show.series_id, 'show': self.show.name }) self.finish()
def run(self): ShowQueueItem.run(self) log.debug('{id}: Beginning update of {show}', { 'id': self.show.series_id, 'show': self.show.name }) log.debug( '{id}: Retrieving show info from {indexer}', { 'id': self.show.series_id, 'indexer': indexerApi(self.show.indexer).name }) try: # Let's make sure we refresh the indexer_api object attached to the show object. self.show.create_indexer() self.show.load_from_indexer() except IndexerError as error: log.warning( '{id}: Unable to contact {indexer}. Aborting: {error_msg}', { 'id': self.show.series_id, 'indexer': indexerApi(self.show.indexer).name, 'error_msg': error }) return except IndexerAttributeNotFound as error: log.warning( '{id}: Data retrieved from {indexer} was incomplete. Aborting: {error_msg}', { 'id': self.show.series_id, 'indexer': indexerApi(self.show.indexer).name, 'error_msg': error }) return except IndexerShowNotFoundInLanguage as error: log.warning( '{id}: Data retrieved from {indexer} was incomplete. The indexer does not provide' ' show information in the searched language {language}. Aborting: {error_msg}', { 'id': self.show.series_id, 'indexer': indexerApi(self.show.indexer).name, 'language': error.language, 'error_msg': error }) ui.notifications.error( 'Error changing language show!', 'Unable to change language for show {show_name}' ' on {indexer} to language: {language}'.format( show_name=self.show.name, indexer=indexerApi(self.show.indexer).name, language=error.language)) return log.debug('{id}: Retrieving show info from IMDb', {'id': self.show.series_id}) try: self.show.load_imdb_info() except ImdbAPIError as error: log.info('{id}: Something wrong on IMDb api: {error_msg}', { 'id': self.show.series_id, 'error_msg': error }) except RequestException as error: log.warning('{id}: Error loading IMDb info: {error_msg}', { 'id': self.show.series_id, 'error_msg': error }) # have to save show before reading episodes from db try: log.debug('{id}: Saving new IMDb show info to database', {'id': self.show.series_id}) self.show.save_to_db() except Exception as error: log.warning( '{id}: Error saving new IMDb show info to database: {error_msg}', { 'id': self.show.series_id, 'error_msg': error }) log.error(traceback.format_exc()) # get episode list from DB try: episodes_from_db = self.show.load_episodes_from_db() except IndexerException as error: log.warning( '{id}: Unable to contact {indexer}. Aborting: {error_msg}', { 'id': self.show.series_id, 'indexer': indexerApi(self.show.indexer).name, 'error_msg': error }) return # get episode list from the indexer try: episodes_from_indexer = self.show.load_episodes_from_indexer() except IndexerException as error: log.warning( '{id}: Unable to get info from {indexer}. The show info will not be refreshed.' ' Error: {error_msg}', { 'id': self.show.series_id, 'indexer': indexerApi(self.show.indexer).name, 'error_msg': error }) episodes_from_indexer = None if episodes_from_indexer is None: log.warning( '{id}: No data returned from {indexer} during full show update.' ' Unable to update this show', { 'id': self.show.series_id, 'indexer': indexerApi(self.show.indexer).name }) else: # for each ep we found on the Indexer delete it from the DB list for cur_season in episodes_from_indexer: for cur_episode in episodes_from_indexer[cur_season]: if cur_season in episodes_from_db and cur_episode in episodes_from_db[ cur_season]: del episodes_from_db[cur_season][cur_episode] # remaining episodes in the DB list are not on the indexer, just delete them from the DB for cur_season in episodes_from_db: for cur_episode in episodes_from_db[cur_season]: log.debug( '{id}: Permanently deleting episode {show} {ep} from the database', { 'id': self.show.series_id, 'show': self.show.name, 'ep': episode_num(cur_season, cur_episode) }) # Create the ep object only because Im going to delete it ep_obj = self.show.get_episode(cur_season, cur_episode) try: ep_obj.delete_episode() except EpisodeDeletedException: log.debug( '{id}: Episode {show} {ep} successfully deleted from the database', { 'id': self.show.series_id, 'show': self.show.name, 'ep': episode_num(cur_season, cur_episode) }) # Save only after all changes were applied try: log.debug('{id}: Saving all updated show info to database', {'id': self.show.series_id}) self.show.save_to_db() except Exception as error: log.warning( '{id}: Error saving all updated show info to database: {error_msg}', { 'id': self.show.series_id, 'error_msg': error }) log.error(traceback.format_exc()) # Replace the images in cache log.info('{id}: Replacing images for show {show}', { 'id': self.show.series_id, 'show': self.show.name }) replace_images(self.show) log.debug('{id}: Finished update of {show}', { 'id': self.show.series_id, 'show': self.show.name }) # Refresh show needs to be forced since current execution locks the queue app.show_queue_scheduler.action.refreshShow(self.show, True) self.finish()
def run(self, force=False): self.amActive = True refresh_shows = [] # A list of shows, that need to be refreshed season_updates = [] # A list of show seasons that have passed their next_update timestamp update_max_weeks = 12 network_timezones.update_network_dict() # Refresh the exceptions_cache from db. refresh_exceptions_cache() logger.info('Started periodic show updates') # Cache for the indexers list of updated show indexer_updated_shows = {} # Cache for the last indexer update timestamp last_updates = {} # Loop through the list of shows, and per show evaluate if we can use the .get_last_updated_seasons() for show in app.showList: if show.paused: logger.info('The show {show} is paused, not updating it.', show=show.name) continue indexer_name = indexerApi(show.indexer).name indexer_api_params = indexerApi(show.indexer).api_params.copy() try: indexer_api = indexerApi(show.indexer).indexer(**indexer_api_params) except IndexerUnavailable: logger.warning('Problem running show_updater, Indexer {indexer_name} seems to be having ' 'connectivity issues. While trying to look for show updates on show: {show}', indexer_name=indexer_name, show=show.name) continue # Get the lastUpdate timestamp for this indexer. if indexer_name not in last_updates: last_indexer_update = self.update_cache.get_last_indexer_update(indexer_name) if not last_indexer_update: last_updates[indexer_name] = int(time.time() - 86400) # 1 day ago elif last_indexer_update < time.time() - 604800 * update_max_weeks: last_updates[indexer_name] = int(time.time() - 604800) # 1 week ago else: last_updates[indexer_name] = last_indexer_update # Get a list of updated shows from the indexer, since last update. if show.indexer not in indexer_updated_shows: try: indexer_updated_shows[show.indexer] = indexer_api.get_last_updated_series( last_updates[indexer_name], update_max_weeks ) except IndexerUnavailable: logger.warning('Problem running show_updater, Indexer {indexer_name} seems to be having ' 'connectivity issues while trying to look for show updates on show: {show}', indexer_name=indexer_name, show=show.name) continue except IndexerException as error: logger.warning('Problem running show_updater, Indexer {indexer_name} seems to be having ' 'issues while trying to get updates for show {show}. Cause: {cause!r}', indexer_name=indexer_name, show=show.name, cause=error) continue except RequestException as error: logger.warning('Problem running show_updater, Indexer {indexer_name} seems to be having ' 'issues while trying to get updates for show {show}. Cause: {cause!r}', indexer_name=indexer_name, show=show.name, cause=error) if isinstance(error, HTTPError): if error.response.status_code == 503: logger.warning('API Service offline: ' 'This service is temporarily offline, try again later.') elif error.response.status_code == 429: logger.warning('Your request count (#) is over the allowed limit of (40).') continue except Exception as error: logger.exception('Problem running show_updater, Indexer {indexer_name} seems to be having ' 'issues while trying to get updates for show {show}. Cause: {cause!r}.', indexer_name=indexer_name, show=show.name, cause=error) continue # Update shows that were updated in the last X weeks # or were not updated within the last X weeks if show.indexerid not in indexer_updated_shows.get(show.indexer, []): if show.last_update_indexer > time.time() - 604800 * update_max_weeks: logger.debug('Skipping show update for {show}. Show was not in the ' 'indexers {indexer_name} list with updated shows and it ' 'was updated within the last {weeks} weeks.', show=show.name, indexer_name=indexer_name, weeks=update_max_weeks) continue # If indexer doesn't have season updates. if not hasattr(indexer_api, 'get_last_updated_seasons'): logger.debug('Adding the following show for full update to queue: {show}', show=show.name) refresh_shows.append(show) # Else fall back to per season updates. elif hasattr(indexer_api, 'get_last_updated_seasons'): # Get updated seasons and add them to the season update list. try: updated_seasons = indexer_api.get_last_updated_seasons( [show.indexerid], show.last_update_indexer, update_max_weeks) except IndexerUnavailable: logger.warning('Problem running show_updater, Indexer {indexer_name} seems to be having ' 'connectivity issues while trying to look for show updates for show: {show}', indexer_name=indexer_name, show=show.name) continue except IndexerException as error: logger.warning('Problem running show_updater, Indexer {indexer_name} seems to be having ' 'issues while trying to get updates for show {show}. Cause: {cause!r}', indexer_name=indexer_name, show=show.name, cause=error) continue except Exception as error: logger.exception('Problem running show_updater, Indexer {indexer_name} seems to be having ' 'issues while trying to get updates for show {show}. Cause: {cause!r}', indexer_name=indexer_name, show=show.name, cause=error) continue if updated_seasons[show.indexerid]: logger.info('{show_name}: Adding the following seasons for update to queue: {seasons}', show_name=show.name, seasons=updated_seasons[show.indexerid]) season_updates.append((show.indexer, show, updated_seasons[show.indexerid])) elif show.indexerid in indexer_updated_shows.get(show.indexer, []): # This show was marked to have an update, but it didn't get a season update. Let's fully # update the show anyway. logger.debug('Could not detect a season update, but an update is required. \n' 'Adding the following show for full update to queue: {show}', show=show.name) refresh_shows.append(show) pi_list = [] # Full refreshes for show in refresh_shows: logger.info('Full update on show: {show}', show=show.name) try: pi_list.append(app.show_queue_scheduler.action.updateShow(show)) except (CantUpdateShowException, CantRefreshShowException) as e: logger.warning('Automatic update failed. Error: {error}', error=e) except Exception as e: logger.error('Automatic update failed: Error: {error}', error=e) # Only update expired season for show in season_updates: logger.info('Updating season {season} for show: {show}.', season=show[2], show=show[1].name) try: pi_list.append(app.show_queue_scheduler.action.updateShow(show[1], season=show[2])) except CantUpdateShowException as e: logger.warning('Automatic update failed. Error: {error}', error=e) except Exception as e: logger.error('Automatic update failed: Error: {error}', error=e) ui.ProgressIndicators.setIndicator('dailyUpdate', ui.QueueProgressIndicator('Daily Update', pi_list)) # Only refresh updated shows that have been updated using the season updates. # The full refreshed shows, are updated from the queueItem. for show in set(show[1] for show in season_updates): try: app.show_queue_scheduler.action.refreshShow(show, True) except CantRefreshShowException as e: logger.warning('Show refresh on show {show_name} failed. Error: {error}', show_name=show.name, error=e) except Exception as e: logger.error('Show refresh on show {show_name} failed: Unexpected Error: {error}', show_name=show.name, error=e) if refresh_shows or season_updates: for indexer in set([s.indexer for s in refresh_shows] + [s[1].indexer for s in season_updates]): indexer_api = indexerApi(indexer) self.update_cache.set_last_indexer_update(indexer_api.name) logger.info('Updated lastUpdate timestamp for {indexer_name}', indexer_name=indexer_api.name) logger.info('Completed scheduling updates on shows') else: logger.info('Completed scheduling updates on shows, but there was nothing to update') self.amActive = False
def _ep_data(self, ep_obj): """ Creates an elementTree XML structure for a WDTV style episode.xml and returns the resulting data object. ep_obj: a Series instance to create the NFO for """ eps_to_write = [ep_obj] + ep_obj.related_episodes my_show = self._get_show_data(ep_obj.series) if not my_show: return None root_node = etree.Element('details') # write an WDTV XML containing info for all matching episodes for ep_to_write in eps_to_write: try: my_ep = my_show[ep_to_write.season][ep_to_write.episode] except (IndexerEpisodeNotFound, IndexerSeasonNotFound): log.info( 'Unable to find episode {number} on {indexer}... has it been removed? Should I delete from db?', { 'number': ep_num(ep_to_write.season, ep_to_write.episode), 'indexer': indexerApi(ep_obj.series.indexer).name, }) return None if ep_obj.season == 0 and not getattr(my_ep, 'firstaired', None): my_ep['firstaired'] = str(datetime.date.fromordinal(1)) if not (getattr(my_ep, 'episodename', None) and getattr(my_ep, 'firstaired', None)): return None if len(eps_to_write) > 1: episode = etree.SubElement(root_node, 'details') else: episode = root_node # TODO: get right EpisodeID episode_id = etree.SubElement(episode, 'id') episode_id.text = str(ep_to_write.indexerid) title = etree.SubElement(episode, 'title') title.text = ep_obj.pretty_name() if getattr(my_show, 'seriesname', None): series_name = etree.SubElement(episode, 'series_name') series_name.text = my_show['seriesname'] if ep_to_write.name: episode_name = etree.SubElement(episode, 'episode_name') episode_name.text = ep_to_write.name season_number = etree.SubElement(episode, 'season_number') season_number.text = str(ep_to_write.season) episode_num = etree.SubElement(episode, 'episode_number') episode_num.text = str(ep_to_write.episode) first_aired = etree.SubElement(episode, 'firstaired') if ep_to_write.airdate != datetime.date.fromordinal(1): first_aired.text = str(ep_to_write.airdate) if getattr(my_show, 'firstaired', None): try: year_text = str( datetime.datetime.strptime(my_show['firstaired'], dateFormat).year) if year_text: year = etree.SubElement(episode, 'year') year.text = year_text except Exception: pass if ep_to_write.season != 0 and getattr(my_show, 'runtime', None): runtime = etree.SubElement(episode, 'runtime') runtime.text = str(my_show['runtime']) if getattr(my_show, 'genre', None): genre = etree.SubElement(episode, 'genre') genre.text = ' / '.join([ x.strip() for x in my_show['genre'].split('|') if x.strip() ]) if getattr(my_ep, 'director', None): director = etree.SubElement(episode, 'director') director.text = my_ep['director'] if getattr(my_show, '_actors', None): for actor in my_show['_actors']: if not ('name' in actor and actor['name'].strip()): continue cur_actor = etree.SubElement(episode, 'actor') cur_actor_name = etree.SubElement(cur_actor, 'name') cur_actor_name.text = actor['name'] if 'role' in actor and actor['role'].strip(): cur_actor_role = etree.SubElement(cur_actor, 'role') cur_actor_role.text = actor['role'].strip() if ep_to_write.description: overview = etree.SubElement(episode, 'overview') overview.text = ep_to_write.description # Make it purdy helpers.indent_xml(root_node) data = etree.ElementTree(root_node) return data
def xem_refresh(series_obj, force=False): """ Refresh data from xem for a tv show. :param indexer_id: int """ if not series_obj or series_obj.series_id < 1: return indexer_id = series_obj.indexer series_id = series_obj.series_id MAX_REFRESH_AGE_SECS = 86400 # 1 day main_db_con = db.DBConnection() rows = main_db_con.select( 'SELECT last_refreshed FROM xem_refresh WHERE indexer = ? and indexer_id = ?', [indexer_id, series_id]) if rows: last_refresh = int(rows[0]['last_refreshed']) refresh = int(time.mktime(datetime.datetime.today().timetuple()) ) > last_refresh + MAX_REFRESH_AGE_SECS else: refresh = True if refresh or force: logger.log( u'Looking up XEM scene mapping for show ID {0} on {1}'.format( series_id, series_obj.indexer_name), logger.DEBUG) # mark refreshed main_db_con.upsert( 'xem_refresh', { 'last_refreshed': int(time.mktime(datetime.datetime.today().timetuple())) }, { 'indexer': indexer_id, 'indexer_id': series_id }) try: if not indexerApi(indexer_id).config.get('xem_origin'): logger.log( u'{0} is an unsupported indexer in XEM'.format( indexerApi(indexer_id).name), logger.DEBUG) return # XEM MAP URL url = 'http://thexem.de/map/havemap?origin={0}'.format( indexerApi(indexer_id).config['xem_origin']) parsed_json = safe_session.get_json(url) if not parsed_json or 'result' not in parsed_json or 'success' not in parsed_json[ 'result'] or 'data' not in parsed_json or str( series_id) not in parsed_json['data']: logger.log( u'No XEM data for show ID {0} on {1}'.format( series_id, series_obj.indexer_name), logger.DEBUG) return # XEM API URL url = 'http://thexem.de/map/all?id={0}&origin={1}&destination=scene'.format( series_id, indexerApi(indexer_id).config['xem_origin']) parsed_json = safe_session.get_json(url) if not parsed_json or 'result' not in parsed_json or 'success' not in parsed_json[ 'result']: logger.log( u'No XEM data for show ID {0} on {1}'.format( indexer_id, series_obj.indexer_name), logger.DEBUG) return cl = [] for entry in parsed_json['data']: if 'scene' in entry: cl.append([ 'UPDATE tv_episodes SET scene_season = ?, scene_episode = ?, scene_absolute_number = ? ' 'WHERE indexer = ? AND showid = ? AND season = ? AND episode = ?', [ entry['scene']['season'], entry['scene']['episode'], entry['scene']['absolute'], indexer_id, series_id, entry[indexerApi(indexer_id).config['xem_origin']] ['season'], entry[indexerApi( indexer_id).config['xem_origin']]['episode'] ] ]) # Update the absolute_number from xem, but do not set it when it has already been set by tvdb. # We want to prevent doubles and tvdb is leading in that case. cl.append([ 'UPDATE tv_episodes SET absolute_number = ? ' 'WHERE indexer = ? AND showid = ? AND season = ? AND episode = ? AND absolute_number = 0 ' 'AND {absolute_number} NOT IN ' '(SELECT absolute_number ' 'FROM tv_episodes ' 'WHERE absolute_number = ? AND indexer = ? AND showid = ?)' .format(absolute_number=entry[indexerApi( indexer_id).config['xem_origin']]['absolute']), [ entry[indexerApi( indexer_id).config['xem_origin']]['absolute'], indexer_id, series_id, entry[indexerApi( indexer_id).config['xem_origin']]['season'], entry[indexerApi(indexer_id).config['xem_origin']] ['episode'], entry[indexerApi( indexer_id).config['xem_origin']]['absolute'], indexer_id, series_id ] ]) if 'scene_2' in entry: # for doubles cl.append([ 'UPDATE tv_episodes SET scene_season = ?, scene_episode = ?, scene_absolute_number = ? ' 'WHERE indexer = ? AND showid = ? AND season = ? AND episode = ?', [ entry['scene_2']['season'], entry['scene_2']['episode'], entry['scene_2']['absolute'], indexer_id, series_id, entry[indexerApi( indexer_id).config['xem_origin']]['season'], entry[indexerApi( indexer_id).config['xem_origin']]['episode'] ] ]) if cl: main_db_con = db.DBConnection() main_db_con.mass_action(cl) except Exception as e: logger.log( u'Exception while refreshing XEM data for show ID {0} on {1}: {2}' .format(series_id, series_obj.indexer_name, ex(e)), logger.WARNING) logger.log(traceback.format_exc(), logger.DEBUG)
def _ep_data(self, ep_obj): """ Creates an elementTree XML structure for an KODI-style episode.nfo and returns the resulting data object. show_obj: a Episode instance to create the NFO for """ eps_to_write = [ep_obj] + ep_obj.related_episodes series_obj = self._get_show_data(ep_obj.series) if not series_obj: return None if len(eps_to_write) > 1: root_node = etree.Element('kodimultiepisode') else: root_node = etree.Element('episodedetails') # write an NFO containing info for all matching episodes for ep_to_write in eps_to_write: try: my_ep = series_obj[ep_to_write.season][ep_to_write.episode] except (IndexerEpisodeNotFound, IndexerSeasonNotFound): log.info( u'Unable to find episode {ep_num} on {indexer}...' u' has it been removed? Should I delete from db?', { 'ep_num': episode_num(ep_to_write.season, ep_to_write.episode), 'indexer': indexerApi(ep_obj.series.indexer).name, } ) return None if not getattr(my_ep, 'firstaired', None): my_ep['firstaired'] = text_type(datetime.date.fromordinal(1)) if not getattr(my_ep, 'episodename', None): log.debug(u'Not generating nfo because the ep has no title') return None log.debug(u'Creating metadata for episode {0}', episode_num(ep_obj.season, ep_obj.episode)) if len(eps_to_write) > 1: episode = etree.SubElement(root_node, 'episodedetails') else: episode = root_node if getattr(my_ep, 'episodename', None): title = etree.SubElement(episode, 'title') title.text = my_ep['episodename'] if getattr(series_obj, 'seriesname', None): showtitle = etree.SubElement(episode, 'showtitle') showtitle.text = series_obj['seriesname'] season = etree.SubElement(episode, 'season') season.text = text_type(ep_to_write.season) episodenum = etree.SubElement(episode, 'episode') episodenum.text = text_type(ep_to_write.episode) uniqueid = etree.SubElement(episode, 'uniqueid') uniqueid.set('type', ep_obj.indexer_name) uniqueid.set('default', 'true') uniqueid.text = text_type(ep_to_write.indexerid) if ep_to_write.airdate != datetime.date.fromordinal(1): aired = etree.SubElement(episode, 'aired') aired.text = text_type(ep_to_write.airdate) if getattr(my_ep, 'overview', None): plot = etree.SubElement(episode, 'plot') plot.text = my_ep['overview'] if ep_to_write.season and getattr(series_obj, 'runtime', None): runtime = etree.SubElement(episode, 'runtime') runtime.text = text_type(series_obj['runtime']) if getattr(my_ep, 'airsbefore_season', None): displayseason = etree.SubElement(episode, 'displayseason') displayseason.text = my_ep['airsbefore_season'] if getattr(my_ep, 'airsbefore_episode', None): displayepisode = etree.SubElement(episode, 'displayepisode') displayepisode.text = my_ep['airsbefore_episode'] if getattr(my_ep, 'filename', None): thumb = etree.SubElement(episode, 'thumb') thumb.text = my_ep['filename'].strip() # watched = etree.SubElement(episode, 'watched') # watched.text = 'false' if getattr(my_ep, 'rating', None): rating = etree.SubElement(episode, 'rating') rating.text = text_type(my_ep['rating']) if getattr(my_ep, 'writer', None) and isinstance(my_ep['writer'], string_types): for writer in self._split_info(my_ep['writer']): cur_writer = etree.SubElement(episode, 'credits') cur_writer.text = writer if getattr(my_ep, 'director', None) and isinstance(my_ep['director'], string_types): for director in self._split_info(my_ep['director']): cur_director = etree.SubElement(episode, 'director') cur_director.text = director if getattr(my_ep, 'gueststars', None) and isinstance(my_ep['gueststars'], string_types): for actor in self._split_info(my_ep['gueststars']): cur_actor = etree.SubElement(episode, 'actor') cur_actor_name = etree.SubElement(cur_actor, 'name') cur_actor_name.text = actor if getattr(series_obj, '_actors', None): for actor in series_obj['_actors']: cur_actor = etree.SubElement(episode, 'actor') if 'name' in actor and actor['name'].strip(): cur_actor_name = etree.SubElement(cur_actor, 'name') cur_actor_name.text = actor['name'].strip() else: continue if 'role' in actor and actor['role'].strip(): cur_actor_role = etree.SubElement(cur_actor, 'role') cur_actor_role.text = actor['role'].strip() if 'image' in actor and actor['image'].strip(): cur_actor_thumb = etree.SubElement(cur_actor, 'thumb') cur_actor_thumb.text = actor['image'].strip() # Make it purdy helpers.indent_xml(root_node) data = etree.ElementTree(root_node) return data
def _ep_data(self, ep_obj): """ Creates an elementTree XML structure for a MediaBrowser style episode.xml and returns the resulting data object. show_obj: a Series instance to create the NFO for """ eps_to_write = [ep_obj] + ep_obj.related_episodes persons_dict = {u'Director': [], u'GuestStar': [], u'Writer': []} my_show = self._get_show_data(ep_obj.series) if not my_show: return None root_node = etree.Element(u'Item') # write an MediaBrowser XML containing info for all matching episodes for ep_to_write in eps_to_write: try: my_ep = my_show[ep_to_write.season][ep_to_write.episode] except (IndexerEpisodeNotFound, IndexerSeasonNotFound): log.info( u'Unable to find episode {number} on {indexer}... has it been removed? Should I delete from db?', { u'number': episode_num(ep_to_write.season, ep_to_write.episode), u'indexer': indexerApi(ep_obj.series.indexer).name }) return None if ep_to_write == ep_obj: # root (or single) episode # default to today's date for specials if firstaired is not set if ep_to_write.season == 0 and not getattr( my_ep, u'firstaired', None): my_ep[u'firstaired'] = str(datetime.date.fromordinal(1)) if not (getattr(my_ep, u'episodename', None) and getattr(my_ep, u'firstaired', None)): return None episode = root_node if ep_to_write.name: episode_name = etree.SubElement(episode, u'EpisodeName') episode_name.text = ep_to_write.name episode_number = etree.SubElement(episode, u'EpisodeNumber') episode_number.text = str(ep_obj.episode) if ep_obj.related_episodes: episode_number_end = etree.SubElement( episode, u'EpisodeNumberEnd') episode_number_end.text = str(ep_to_write.episode) season_number = etree.SubElement(episode, u'SeasonNumber') season_number.text = str(ep_to_write.season) if not ep_obj.related_episodes and getattr( my_ep, u'absolute_number', None): absolute_number = etree.SubElement(episode, u'absolute_number') absolute_number.text = str(my_ep[u'absolute_number']) if ep_to_write.airdate != datetime.date.fromordinal(1): first_aired = etree.SubElement(episode, u'FirstAired') first_aired.text = str(ep_to_write.airdate) metadata_type = etree.SubElement(episode, u'Type') metadata_type.text = u'Episode' if ep_to_write.description: overview = etree.SubElement(episode, u'Overview') overview.text = ep_to_write.description if not ep_obj.related_episodes: if getattr(my_ep, u'rating', None): rating = etree.SubElement(episode, u'Rating') rating.text = str(my_ep[u'rating']) if getattr(my_show, u'imdb_id', None): IMDB_ID = etree.SubElement(episode, u'IMDB_ID') IMDB_ID.text = my_show[u'imdb_id'] IMDB = etree.SubElement(episode, u'IMDB') IMDB.text = my_show[u'imdb_id'] IMDbId = etree.SubElement(episode, u'IMDbId') IMDbId.text = my_show[u'imdb_id'] indexer_id = etree.SubElement(episode, u'id') indexer_id.text = str(ep_to_write.indexerid) persons = etree.SubElement(episode, u'Persons') if getattr(my_show, u'_actors', None): for actor in my_show[u'_actors']: if not (u'name' in actor and actor[u'name'].strip()): continue cur_actor = etree.SubElement(persons, u'Person') cur_actor_name = etree.SubElement(cur_actor, u'Name') cur_actor_name.text = actor[u'name'].strip() cur_actor_type = etree.SubElement(cur_actor, u'Type') cur_actor_type.text = u'Actor' if u'role' in actor and actor[u'role'].strip(): cur_actor_role = etree.SubElement( cur_actor, u'Role') cur_actor_role.text = actor[u'role'].strip() language = etree.SubElement(episode, u'Language') try: language.text = my_ep[u'language'] except Exception: language.text = app.INDEXER_DEFAULT_LANGUAGE # tvrage api doesn't provide language so we must assume a value here thumb = etree.SubElement(episode, u'filename') # TODO: See what this is needed for.. if its still needed # just write this to the NFO regardless of whether it actually exists or not # note: renaming files after nfo generation will break this, tough luck thumb_text = self.get_episode_thumb_path(ep_obj) if thumb_text: thumb.text = thumb_text else: # append data from (if any) related episodes episode_number_end.text = str(ep_to_write.episode) if ep_to_write.name: if not episode_name.text: episode_name.text = ep_to_write.name else: episode_name.text = u', '.join( [episode_name.text, ep_to_write.name]) if ep_to_write.description: if not overview.text: overview.text = ep_to_write.description else: overview.text = u'\r'.join( [overview.text, ep_to_write.description]) # collect all directors, guest stars and writers if getattr(my_ep, u'director', None): persons_dict[u'Director'] += [ x.strip() for x in my_ep[u'director'].split(u'|') if x.strip() ] if getattr(my_ep, u'gueststars', None): persons_dict[u'GuestStar'] += [ x.strip() for x in my_ep[u'gueststars'].split(u'|') if x.strip() ] if getattr(my_ep, u'writer', None): persons_dict[u'Writer'] += [ x.strip() for x in my_ep[u'writer'].split(u'|') if x.strip() ] # fill in Persons section with collected directors, guest starts and writers for person_type, names in iteritems(persons_dict): # remove doubles names = list(set(names)) for cur_name in names: person = etree.SubElement(persons, u'Person') cur_person_name = etree.SubElement(person, u'Name') cur_person_name.text = cur_name cur_person_type = etree.SubElement(person, u'Type') cur_person_type.text = person_type # Make it purdy helpers.indent_xml(root_node) data = etree.ElementTree(root_node) return data
def run(self): """Run QueueItemChangeIndexer queue item.""" step = [] # Small helper, to reduce code for messaging def message_step(new_step): step.append(new_step) ws.Message( 'QueueItemShow', dict(step=step, oldShow=self.old_show.to_json() if self.old_show else {}, newShow=self.new_show.to_json() if self.new_show else {}, **self.to_json)).push() ShowQueueItem.run(self) def get_show_from_slug(slug): identifier = SeriesIdentifier.from_slug(slug) if not identifier: raise ChangeIndexerException( f'Could not create identifier with slug {slug}') show = Series.find_by_identifier(identifier) return show try: # Create reference to old show, before starting the remove it. self.old_show = get_show_from_slug(self.old_slug) # Store needed options. self._store_options() # Start of removing the old show log.info('{id}: Removing {show}', { 'id': self.old_show.series_id, 'show': self.old_show.name }) message_step(f'Removing old show {self.old_show.name}') # Need to first remove the episodes from the Trakt collection, because we need the list of # Episodes from the db to know which eps to remove. if app.USE_TRAKT: message_step('Removing episodes from trakt collection') try: app.trakt_checker_scheduler.action.remove_show_trakt_library( self.old_show) except TraktException as error: log.warning( '{id}: Unable to delete show {show} from Trakt.' ' Please remove manually otherwise it will be added again.' ' Error: {error_msg}', { 'id': self.old_show.series_id, 'show': self.old_show.name, 'error_msg': error }) except Exception as error: log.exception( 'Exception occurred while trying to delete show {show}, error: {error', { 'show': self.old_show.name, 'error': error }) self.old_show.delete_show(full=False) # Send showRemoved to frontend, so we can remove it from localStorage. ws.Message('showRemoved', self.old_show.to_json( detailed=False)).push() # Send ws update to client # Double check to see if the show really has been removed, else bail. if get_show_from_slug(self.old_slug): raise ChangeIndexerException( f'Could not create identifier with slug {self.old_slug}') # Start adding the new show log.info('Starting to add show by {0}', ('show_dir: {0}'.format(self.show_dir) if self.show_dir else 'New slug: {0}'.format(self.new_slug))) self.new_show = Series.from_identifier( SeriesIdentifier.from_slug(self.new_slug)) try: # Push an update to any open Web UIs through the WebSocket message_step('load show from {indexer}'.format( indexer=indexerApi(self.new_show.indexer).name)) api = self.new_show.identifier.get_indexer_api(self.options) if getattr(api[self.new_show.series_id], 'seriesname', None) is None: log.error( 'Show in {path} has no name on {indexer}, probably searched with the wrong language.', { 'path': self.show_dir, 'indexer': indexerApi(self.new_show.indexer).name }) ui.notifications.error( 'Unable to add show', 'Show in {path} has no name on {indexer}, probably the wrong language.' ' Delete .nfo and manually add the correct language.'. format(path=self.show_dir, indexer=indexerApi(self.new_show.indexer).name)) self._finish_early() raise SaveSeriesException( 'Indexer is missing a showname in this language: {0!r}' ) self.new_show.load_from_indexer(tvapi=api) message_step('load info from imdb') self.new_show.load_imdb_info() except IndexerException as error: log.warning( 'Unable to load series from indexer: {0!r}'.format(error)) raise SaveSeriesException( 'Unable to load series from indexer: {0!r}'.format(error)) try: message_step('configure show options') self.new_show.configure(self) except KeyError as error: log.error( 'Unable to add show {series_name} due to an error with one of the provided options: {error}', { 'series_name': self.new_show.name, 'error': error }) ui.notifications.error( 'Unable to add show {series_name} due to an error with one of the provided options: {error}' .format(series_name=self.new_show.name, error=error)) raise SaveSeriesException( 'Unable to add show {series_name} due to an error with one of the provided options: {error}' .format(series_name=self.new_show.name, error=error)) except Exception as error: log.error('Error trying to configure show: {0}', error) log.debug(traceback.format_exc()) raise app.showList.append(self.new_show) self.new_show.save_to_db() try: message_step('load episodes from {indexer}'.format( indexer=indexerApi(self.new_show.indexer).name)) self.new_show.load_episodes_from_indexer(tvapi=api) # If we provide a default_status_after through the apiv2 series route options object. # set it after we've added the episodes. self.new_show.default_ep_status = self.options[ 'default_status_after'] or app.STATUS_DEFAULT_AFTER except IndexerException as error: log.warning( 'Unable to load series episodes from indexer: {0!r}'. format(error)) raise SaveSeriesException( 'Unable to load series episodes from indexer: {0!r}'. format(error)) message_step('create metadata in show folder') self.new_show.write_metadata() self.new_show.update_metadata() self.new_show.populate_cache() build_name_cache(self.new_show) # update internal name cache self.new_show.flush_episodes() self.new_show.sync_trakt() message_step('add scene numbering') self.new_show.add_scene_numbering() if self.show_dir: # If a show dir was passed, this was added as an existing show. # For new shows we shouldn't have any files on disk. message_step('refresh episodes from disk') try: app.show_queue_scheduler.action.refreshShow(self.new_show) except CantRefreshShowException as error: log.warning( 'Unable to rescan episodes from disk: {0!r}'.format( error)) except (ChangeIndexerException, SaveSeriesException) as error: log.warning('Unable to add series: {0!r}'.format(error)) self.success = False self._finish_early() log.debug(traceback.format_exc()) default_status = self.options['default_status'] or app.STATUS_DEFAULT if statusStrings[default_status] == 'Wanted': message_step('trigger backlog search') app.backlog_search_scheduler.action.search_backlog([self.new_show]) self.success = True ws.Message('showAdded', self.new_show.to_json( detailed=False)).push() # Send ws update to client message_step('finished') self.finish()
def _get_xem_exceptions(force): xem_exceptions = defaultdict(dict) url = 'http://thexem.de/map/allNames' params = { 'origin': None, 'seasonNumbers': 1, } if force or should_refresh('xem'): for indexer in indexerApi().indexers: indexer_api = indexerApi(indexer) try: # Get XEM origin for indexer origin = indexer_api.config['xem_origin'] if origin not in VALID_XEM_ORIGINS: msg = 'invalid origin for XEM: {0}'.format(origin) raise ValueError(msg) except KeyError: # Indexer has no XEM origin continue except ValueError as error: # XEM origin for indexer is invalid logger.error( 'Error getting XEM scene exceptions for {indexer}:' ' {error}', { 'indexer': indexer_api.name, 'error': error }) continue else: # XEM origin for indexer is valid params['origin'] = origin logger.info( 'Checking for XEM scene exceptions updates for' ' {indexer_name}', {'indexer_name': indexer_api.name}) response = safe_session.get(url, params=params, timeout=60) try: jdata = response.json() except (ValueError, AttributeError) as error: logger.debug( 'Check scene exceptions update failed for {indexer}.' ' Unable to get URL: {url} Error: {error}', { 'indexer': indexer_api.name, 'url': url, 'error': error }) continue if not jdata['data'] or jdata['result'] == 'failure': logger.debug( 'No data returned from XEM while checking for scene' ' exceptions. Update failed for {indexer}', {'indexer': indexer_api.name}) continue for indexer_id, exceptions in iteritems(jdata['data']): try: xem_exceptions[indexer][indexer_id] = exceptions except Exception as error: logger.warning( 'XEM: Rejected entry: Indexer ID: {indexer_id},' ' Exceptions: {exceptions}', { 'indexer_id': indexer_id, 'exceptions': exceptions }) logger.warning( 'XEM: Rejected entry error message: {error}', {'error': error}) set_last_refresh('xem') return xem_exceptions
def run(self): ShowQueueItem.run(self) log.info('Starting to add show by {0}', ('show_dir: {0}'.format(self.show_dir) if self.show_dir else 'Indexer Id: {0}'.format(self.indexer_id))) # make sure the Indexer IDs are valid try: l_indexer_api_params = indexerApi(self.indexer).api_params.copy() if self.lang: l_indexer_api_params['language'] = self.lang log.info( '{indexer_name}: {indexer_params!r}', { 'indexer_name': indexerApi(self.indexer).name, 'indexer_params': l_indexer_api_params }) indexer_api = indexerApi( self.indexer).indexer(**l_indexer_api_params) s = indexer_api[self.indexer_id] # Let's try to create the show Dir if it's not provided. This way we force the show dir # to build build using the Indexers provided series name if not self.show_dir and self.root_dir: show_name = get_showname_from_indexer(self.indexer, self.indexer_id, self.lang) if show_name: self.show_dir = os.path.join(self.root_dir, sanitize_filename(show_name)) dir_exists = make_dir(self.show_dir) if not dir_exists: log.info( "Unable to create the folder {0}, can't add the show", self.show_dir) return chmod_as_parent(self.show_dir) else: log.info("Unable to get a show {0}, can't add the show", self.show_dir) return # this usually only happens if they have an NFO in their show dir which gave us a Indexer ID that # has no proper english version of the show if getattr(s, 'seriesname', None) is None: log.error( 'Show in {path} has no name on {indexer}, probably searched with the wrong language.', { 'path': self.show_dir, 'indexer': indexerApi(self.indexer).name }) ui.notifications.error( 'Unable to add show', 'Show in {path} has no name on {indexer}, probably the wrong language.' ' Delete .nfo and manually add the correct language.'. format(path=self.show_dir, indexer=indexerApi(self.indexer).name)) self._finishEarly() return # Check if we can already find this show in our current showList. try: check_existing_shows(s, self.indexer) except IndexerShowAlreadyInLibrary as error: log.warning( 'Could not add the show {series}, as it already is in your library.' ' Error: {error}', { 'series': s['seriesname'], 'error': error }) ui.notifications.error('Unable to add show', 'reason: {0}'.format(error)) self._finishEarly() # Clean up leftover if the newly created directory is empty. delete_empty_folders(self.show_dir) return # TODO: Add more specific indexer exceptions, that should provide the user with some accurate feedback. except IndexerShowNotFound as error: log.warning( '{id}: Unable to look up the show in {path} using id {id} on {indexer}.' ' Delete metadata files from the folder and try adding it again.\n' 'With error: {error}', { 'id': self.indexer_id, 'path': self.show_dir, 'indexer': indexerApi(self.indexer).name, 'error': error }) ui.notifications.error( 'Unable to add show', 'Unable to look up the show in {path} using id {id} on {indexer}.' ' Delete metadata files from the folder and try adding it again.' .format(path=self.show_dir, id=self.indexer_id, indexer=indexerApi(self.indexer).name)) self._finishEarly() return except IndexerShowNotFoundInLanguage as error: log.warning( '{id}: Data retrieved from {indexer} was incomplete. The indexer does not provide' ' show information in the searched language {language}. Aborting: {error_msg}', { 'id': self.indexer_id, 'indexer': indexerApi(self.indexer).name, 'language': error.language, 'error_msg': error }) ui.notifications.error( 'Error adding show!', 'Unable to add show {indexer_id} on {indexer} with this language: {language}' .format(indexer_id=self.indexer_id, indexer=indexerApi(self.indexer).name, language=error.language)) self._finishEarly() return except Exception as error: log.error( '{id}: Error while loading information from indexer {indexer}. Error: {error!r}', { 'id': self.indexer_id, 'indexer': indexerApi(self.indexer).name, 'error': error }) ui.notifications.error( 'Unable to add show', 'Unable to look up the show in {path} on {indexer} using ID {id}.' .format(path=self.show_dir, indexer=indexerApi(self.indexer).name, id=self.indexer_id)) self._finishEarly() return try: newShow = Series(self.indexer, self.indexer_id, self.lang) newShow.load_from_indexer(indexer_api) self.show = newShow # set up initial values self.show.location = self.show_dir self.show.subtitles = self.subtitles if self.subtitles is not None else app.SUBTITLES_DEFAULT self.show.quality = self.quality if self.quality else app.QUALITY_DEFAULT self.show.season_folders = self.season_folders if self.season_folders is not None \ else app.SEASON_FOLDERS_DEFAULT self.show.anime = self.anime if self.anime is not None else app.ANIME_DEFAULT self.show.scene = self.scene if self.scene is not None else app.SCENE_DEFAULT self.show.paused = self.paused if self.paused is not None else False # set up default new/missing episode status log.info( 'Setting all previously aired episodes to the specified status: {status}', {'status': statusStrings[self.default_status]}) self.show.default_ep_status = self.default_status if self.show.anime: self.show.release_groups = BlackAndWhiteList(self.show) if self.blacklist: self.show.release_groups.set_black_keywords(self.blacklist) if self.whitelist: self.show.release_groups.set_white_keywords(self.whitelist) except IndexerException as error: log.error( 'Unable to add show due to an error with {indexer}: {error}', { 'indexer': indexerApi(self.indexer).name, 'error': error }) ui.notifications.error( 'Unable to add {series_name} due to an error with {indexer_name}' .format(series_name=self.show.name if self.show else 'show', indexer_name=indexerApi(self.indexer).name)) self._finishEarly() return except MultipleShowObjectsException: log.warning( 'The show in {show_dir} is already in your show list, skipping', {'show_dir': self.show_dir}) ui.notifications.error( 'Show skipped', 'The show in {show_dir} is already in your show list'.format( show_dir=self.show_dir)) self._finishEarly() return except Exception as error: log.error('Error trying to add show: {0}', error) log.debug(traceback.format_exc()) self._finishEarly() raise log.debug('Retrieving show info from IMDb') try: self.show.load_imdb_info() except ImdbAPIError as error: log.info('Something wrong on IMDb api: {0}', error) except RequestException as error: log.warning('Error loading IMDb info: {0}', error) try: log.debug('{id}: Saving new show to database', {'id': self.show.series_id}) self.show.save_to_db() except Exception as error: log.error('Error saving the show to the database: {0}', error) log.debug(traceback.format_exc()) self._finishEarly() raise # add it to the show list app.showList.append(self.show) try: self.show.load_episodes_from_indexer(tvapi=indexer_api) except Exception as error: log.error( 'Error with {indexer}, not creating episode list: {error}', { 'indexer': indexerApi(self.show.indexer).name, 'error': error }) log.debug(traceback.format_exc()) # update internal name cache name_cache.build_name_cache(self.show) try: self.show.load_episodes_from_dir() except Exception as error: log.error('Error searching dir for episodes: {0}', error) log.debug(traceback.format_exc()) # if they set default ep status to WANTED then run the backlog to search for episodes if self.show.default_ep_status == WANTED: log.info( 'Launching backlog for this show since its episodes are WANTED' ) wanted_segments = self.show.get_wanted_segments() for season, segment in viewitems(wanted_segments): cur_backlog_queue_item = BacklogQueueItem(self.show, segment) app.forced_search_queue_scheduler.action.add_item( cur_backlog_queue_item) log.info('Sending forced backlog for {show} season {season}' ' because some episodes were set to wanted'.format( show=self.show.name, season=season)) self.show.write_metadata() self.show.update_metadata() self.show.populate_cache() self.show.flush_episodes() if app.USE_TRAKT: # if there are specific episodes that need to be added by trakt app.trakt_checker_scheduler.action.manage_new_show(self.show) # add show to trakt.tv library if app.TRAKT_SYNC: app.trakt_checker_scheduler.action.add_show_trakt_library( self.show) if app.TRAKT_SYNC_WATCHLIST: log.info('update watchlist') notifiers.trakt_notifier.update_watchlist(show_obj=self.show) # Load XEM data to DB for show scene_numbering.xem_refresh(self.show, force=True) # check if show has XEM mapping so we can determine if searches # should go by scene numbering or indexer numbering. Warn the user. if not self.scene and scene_numbering.get_xem_numbering_for_show( self.show): log.warning( '{id}: while adding the show {title} we noticed thexem.de has an episode mapping available' '\nyou might want to consider enabling the scene option for this show.', { 'id': self.show.series_id, 'title': self.show.name }) ui.notifications.message( 'consider enabling scene for this show', 'for show {title} you might want to consider enabling the scene option' .format(title=self.show.name)) # After initial add, set to default_status_after. self.show.default_ep_status = self.default_status_after try: log.debug('{id}: Saving new show info to database', {'id': self.show.series_id}) self.show.save_to_db() except Exception as error: log.warning( '{id}: Error saving new show info to database: {error_msg}', { 'id': self.show.series_id, 'error_msg': error }) log.error(traceback.format_exc()) # Send ws update to client ws.Message('showAdded', self.show.to_json(detailed=False)).push() self.finish()
def resource_search_indexers_for_show_name(self): """ Search indexers for show name. Query parameters: :param query: Search term :param indexerId: Indexer to search, defined by ID. '0' for all indexers. :param language: 2-letter language code to search the indexer(s) in """ query = self.get_argument('query', '').strip() indexer_id = self.get_argument('indexerId', '0').strip() language = self.get_argument('language', '').strip() if not query: return self._bad_request('No search term provided.') enabled_indexers = indexerApi().indexers indexer_id = try_int(indexer_id) if indexer_id > 0 and indexer_id not in enabled_indexers: return self._bad_request('Invalid indexer id.') if not language or language == 'null': language = app.INDEXER_DEFAULT_LANGUAGE search_terms = [query] # If search term ends with what looks like a year, enclose it in () matches = re.match(r'^(.+ |)([12][0-9]{3})$', query) if matches: search_terms.append('{0}({1})'.format(matches.group(1), matches.group(2))) for search_term in search_terms: # If search term begins with an article, let's also search for it without matches = re.match(r'^(?:a|an|the) (.+)$', search_term, re.I) if matches: search_terms.append(matches.group(1)) results = {} final_results = [] # Query indexers for each search term and build the list of results for indexer in enabled_indexers if indexer_id == 0 else [indexer_id]: indexer_instance = indexerApi(indexer) custom_api_params = indexer_instance.api_params.copy() custom_api_params['language'] = language custom_api_params['custom_ui'] = classes.AllShowsListUI try: indexer_api = indexer_instance.indexer(**custom_api_params) except IndexerUnavailable as msg: log.info('Could not initialize indexer {indexer}: {error}', { 'indexer': indexer_instance.name, 'error': msg }) continue log.debug( 'Searching for show with search term(s): {terms} on indexer: {indexer}', { 'terms': search_terms, 'indexer': indexer_api.name }) for search_term in search_terms: try: indexer_results = indexer_api[search_term] # add search results results.setdefault(indexer, []).extend(indexer_results) except IndexerException as error: log.info( 'Error searching for show. term(s): {terms} indexer: {indexer} error: {error}', { 'terms': search_terms, 'indexer': indexer_api.name, 'error': error }) except Exception as error: log.error( 'Internal Error searching for show. term(s): {terms} indexer: {indexer} error: {error}', { 'terms': search_terms, 'indexer': indexer_api.name, 'error': error }) # Get all possible show ids all_show_ids = {} for show in app.showList: for ex_indexer_name, ex_show_id in iteritems(show.externals): ex_indexer_id = reverse_mappings.get(ex_indexer_name) if not ex_indexer_id: continue all_show_ids[(ex_indexer_id, ex_show_id)] = (show.indexer_name, show.series_id) for indexer, shows in iteritems(results): indexer_api = indexerApi(indexer) indexer_results_set = set() for show in shows: show_id = int(show['id']) indexer_results_set.add( (indexer_api.name, indexer, indexer_api.config['show_url'], show_id, show['seriesname'], show['firstaired'] or 'N/A', show.get('network', '') or 'N/A', sanitize_filename(show['seriesname']), all_show_ids.get((indexer, show_id), False))) final_results.extend(indexer_results_set) language_id = indexerApi().config['langabbv_to_id'][language] data = {'results': final_results, 'languageId': language_id} return self._ok(data=data)
def _parse_air_by_date(self, result): """ Parse anime season episode results. Translate scene episode and season numbering to indexer numbering, using an air date to indexer season/episode translation. :param result: Guessit parse result object. :return: tuple of found indexer episode numbers and indexer season numbers """ log.debug('Series {name} is air by date', {'name': result.series.name}) new_episode_numbers = [] new_season_numbers = [] episode_by_air_date = self._get_episodes_by_air_date(result) season_number = None episode_numbers = [] if episode_by_air_date: season_number = int(episode_by_air_date[0]['season']) episode_numbers = [int(episode_by_air_date[0]['episode'])] # Use the next query item if we have multiple results # and the current one is a special episode (season 0) if season_number == 0 and len(episode_by_air_date) > 1: season_number = int(episode_by_air_date[1]['season']) episode_numbers = [int(episode_by_air_date[1]['episode'])] log.debug( 'Database info for series {name}: Season: {season} Episode(s): {episodes}', { 'name': result.series.name, 'season': season_number, 'episodes': episode_numbers } ) if season_number is None or not episode_numbers: log.debug('Series {name} has no season or episodes, using indexer', {'name': result.series.name}) indexer_api_params = indexerApi(result.series.indexer).api_params.copy() indexer_api = indexerApi(result.series.indexer).indexer(**indexer_api_params) try: if result.series.lang: indexer_api_params['language'] = result.series.lang tv_episode = indexer_api[result.series.indexerid].aired_on(result.air_date)[0] season_number = int(tv_episode['seasonnumber']) episode_numbers = [int(tv_episode['episodenumber'])] log.debug( 'Indexer info for series {name}: {ep}', { 'name': result.series.name, 'ep': episode_num(season_number, episode_numbers[0]), } ) except IndexerEpisodeNotFound: log.warning( 'Unable to find episode with date {date} for series {name}. Skipping', {'date': result.air_date, 'name': result.series.name} ) episode_numbers = [] except IndexerError as error: log.warning( 'Unable to contact {indexer_api.name}: {error!r}', {'indexer_api': indexer_api, 'error': error} ) episode_numbers = [] except IndexerException as error: log.warning( 'Indexer exception: {indexer_api.name}: {error!r}', {'indexer_api': indexer_api, 'error': error} ) episode_numbers = [] for episode_number in episode_numbers: season = season_number episode = episode_number if result.series.is_scene: (season, episode) = scene_numbering.get_indexer_numbering( result.series, season_number, episode_number, ) log.debug( 'Scene numbering enabled series {name}, using indexer numbering: {ep}', {'name': result.series.name, 'ep': episode_num(season, episode)} ) new_episode_numbers.append(episode) new_season_numbers.append(season) return new_episode_numbers, new_season_numbers
def check_existing_shows(indexed_show, indexer): """Check if the searched show already exists in the current library. :param indexed_show: (Indexer Show object) The indexed show from -for example- tvdb. It might already have some externals like imdb_id which can be used to search at tmdb, tvmaze or trakt. :param indexer: (int) The indexer id, which has been used to search the indexed_show with. :return: Raises the exception IndexerShowAlreadyInLibrary() when the show is already in your library. """ # For this show let's get all externals, and use them. mappings = { indexer: indexerConfig[indexer]['mapped_to'] for indexer in indexerConfig } other_indexers = [ mapped_indexer for mapped_indexer in mappings if mapped_indexer != indexer ] # This will query other indexer api's. new_show_externals = get_externals(indexer=indexer, indexed_show=indexed_show) # Iterate through all shows in library, and see if one of our externals matches it's indexer_id # Or one of it's externals. for show in app.showList: # Check if the new shows indexer id matches the external for the show # in library if show.externals.get(mappings[indexer] ) and indexed_show['id'] == show.externals.get( mappings[indexer]): log.debug(u'Show already in database. [{id}] {name}', { 'name': show.name, 'id': indexed_show['id'] }) raise IndexerShowAlreadyInLibrary( 'The show {0} has already been added by the indexer {1}. ' 'Please remove the show, before you can add it through {2}.'. format(show.name, indexerApi(show.indexer).name, indexerApi(indexer).name)) for new_show_external_key in list(new_show_externals): if show.indexer not in other_indexers: continue # Check if one of the new shows externals matches one of the # externals for the show in library. if not new_show_externals.get( new_show_external_key) or not show.externals.get( new_show_external_key): continue if new_show_externals.get( new_show_external_key) == show.externals.get( new_show_external_key): log.debug( u'Show already in database under external ID ({existing})' u' for ({id}) {name}', { 'name': show.name, 'id': show.externals.get(new_show_external_key), 'existing': new_show_external_key, }) raise IndexerShowAlreadyInLibrary( 'The show {0} has already been added by the indexer {1}. ' 'Please remove the show, before you can add it through {2}.' .format(show.name, indexerApi(show.indexer).name, indexerApi(indexer).name))
def run(self): ShowQueueItem.run(self) log.info('Starting to add show by {0}', ('show_dir: {0}'.format(self.show_dir) if self.show_dir else 'Indexer Id: {0}'.format(self.indexer_id))) show_slug = indexer_id_to_slug(self.indexer, self.indexer_id) series = Series.from_identifier(SeriesIdentifier.from_slug(show_slug)) step = [] # Small helper, to reduce code for messaging def message_step(new_step): step.append(new_step) ws.Message('QueueItemShowAdd', dict(step=step, **self.to_json)).push() try: try: # Push an update to any open Web UIs through the WebSocket message_step('load show from {indexer}'.format( indexer=indexerApi(self.indexer).name)) api = series.identifier.get_indexer_api(self.options) if getattr(api[self.indexer_id], 'seriesname', None) is None: log.error( 'Show in {path} has no name on {indexer}, probably searched with the wrong language.', { 'path': self.show_dir, 'indexer': indexerApi(self.indexer).name }) ui.notifications.error( 'Unable to add show', 'Show in {path} has no name on {indexer}, probably the wrong language.' ' Delete .nfo and manually add the correct language.'. format(path=self.show_dir, indexer=indexerApi(self.indexer).name)) self._finish_early() raise SaveSeriesException( 'Indexer is missing a showname in this language: {0!r}' ) series.load_from_indexer(tvapi=api) message_step('load info from imdb') series.load_imdb_info() except IndexerException as error: log.warning( 'Unable to load series from indexer: {0!r}'.format(error)) raise SaveSeriesException( 'Unable to load series from indexer: {0!r}'.format(error)) message_step('check if show is already added') try: message_step('configure show options') series.configure(self) except KeyError as error: log.error( 'Unable to add show {series_name} due to an error with one of the provided options: {error}', { 'series_name': series.name, 'error': error }) ui.notifications.error( 'Unable to add show {series_name} due to an error with one of the provided options: {error}' .format(series_name=series.name, error=error)) raise SaveSeriesException( 'Unable to add show {series_name} due to an error with one of the provided options: {error}' .format(series_name=series.name, error=error)) except Exception as error: log.error('Error trying to configure show: {0}', error) log.debug(traceback.format_exc()) raise app.showList.append(series) series.save_to_db() try: message_step('load episodes from {indexer}'.format( indexer=indexerApi(self.indexer).name)) series.load_episodes_from_indexer(tvapi=api) # If we provide a default_status_after through the apiv2 series route options object. # set it after we've added the episodes. self.default_ep_status = self.options[ 'default_status_after'] or app.STATUS_DEFAULT_AFTER except IndexerException as error: log.warning( 'Unable to load series episodes from indexer: {0!r}'. format(error)) raise SaveSeriesException( 'Unable to load series episodes from indexer: {0!r}'. format(error)) message_step('create metadata in show folder') series.write_metadata() series.update_metadata() series.populate_cache() build_name_cache(series) # update internal name cache series.flush_episodes() series.sync_trakt() message_step('add scene numbering') series.add_scene_numbering() except SaveSeriesException as error: log.warning('Unable to add series: {0!r}'.format(error)) self.success = False self._finish_early() log.debug(traceback.format_exc()) default_status = self.options['default_status'] or app.STATUS_DEFAULT if statusStrings[default_status] == 'Wanted': message_step('trigger backlog search') app.backlog_search_scheduler.action.search_backlog([series]) self.success = True ws.Message( 'showAdded', series.to_json(detailed=False)).push() # Send ws update to client message_step('finished') self.finish()
def _ep_data(self, ep_obj): """ Creates a key value structure for a Tivo episode metadata file and returns the resulting data object. ep_obj: a Episode instance to create the metadata file for. Lookup the show in http://thetvdb.com/ using the python library: https://github.com/dbr/indexer_api/ The results are saved in the object myShow. The key values for the tivo metadata file are from: http://pytivo.sourceforge.net/wiki/index.php/Metadata """ data = '' eps_to_write = [ep_obj] + ep_obj.related_episodes my_show = self._get_show_data(ep_obj.series) if not my_show: return None for ep_to_write in eps_to_write: try: my_ep = my_show[ep_to_write.season][ep_to_write.episode] except (IndexerEpisodeNotFound, IndexerSeasonNotFound): log.debug( u'Unable to find episode {number} on {indexer}... has it been removed? Should I delete from db?', { 'number': episode_num(ep_to_write.season, ep_to_write.episode), 'indexer': indexerApi(ep_obj.series.indexer).name, }) return None if ep_obj.season == 0 and not getattr(my_ep, 'firstaired', None): my_ep['firstaired'] = text_type(datetime.date.fromordinal(1)) if not (getattr(my_ep, 'episodename', None) and getattr(my_ep, 'firstaired', None)): return None if getattr(my_show, 'seriesname', None): data += ('title : {title}\n'.format( title=my_show['seriesname'])) data += ('seriesTitle : {title}\n'.format( title=my_show['seriesname'])) data += ('episodeTitle : {title}\n'.format( title=ep_to_write._format_pattern('%Sx%0E %EN'))) # This should be entered for episodic shows and omitted for movies. The standard tivo format is to enter # the season number followed by the episode number for that season. For example, enter 201 for season 2 # episode 01. # This only shows up if you go into the Details from the Program screen. # This seems to disappear once the video is transferred to TiVo. # NOTE: May not be correct format, missing season, but based on description from wiki leaving as is. data += ('episodeNumber : {ep_num}\n'.format( ep_num=ep_to_write.episode)) # Must be entered as true or false. If true, the year from originalAirDate will be shown in parentheses # after the episode's title and before the description on the Program screen. # FIXME: Hardcode isEpisode to true for now, not sure how to handle movies data += 'isEpisode : true\n' # Write the synopsis of the video here # Micrsoft Word's smartquotes can die in a fire. sanitized_description = ep_to_write.description # Replace double curly quotes sanitized_description = sanitized_description.replace( u'\u201c', "'").replace(u'\u201d', "'") # Replace single curly quotes sanitized_description = sanitized_description.replace( u'\u2018', "'").replace(u'\u2019', "'").replace(u'\u02BC', "'") data += ('description : {desc}\n'.format( desc=sanitized_description)) # Usually starts with 'SH' and followed by 6-8 digits. # TiVo uses zap2it for their data, so the series id is the zap2it_id. if getattr(my_show, 'zap2it_id', None): data += ('seriesId : {zap2it}\n'.format( zap2it=my_show['zap2it_id'])) # This is the call sign of the channel the episode was recorded from. if getattr(my_show, 'network', None): data += ('callsign : {network}\n'.format( network=my_show['network'])) # This must be entered as yyyy-mm-ddThh:mm:ssZ (the t is capitalized and never changes, the Z is also # capitalized and never changes). This is the original air date of the episode. # NOTE: Hard coded the time to T00:00:00Z as we really don't know when during the day the first run happened. if ep_to_write.airdate != datetime.date.fromordinal(1): data += ('originalAirDate : {airdate}T00:00:00Z\n'.format( airdate=ep_to_write.airdate)) # This shows up at the beginning of the description on the Program screen and on the Details screen. if getattr(my_show, '_actors', None): for actor in my_show['_actors']: if 'name' in actor and actor['name'].strip(): data += ('vActor : {actor}\n'.format( actor=actor['name'].strip())) # This is shown on both the Program screen and the Details screen. if getattr(my_ep, 'rating', None): try: rating = float(my_ep['rating']) except ValueError: rating = 0.0 # convert 10 to 4 star rating. 4 * rating / 10 # only whole numbers or half numbers work. multiply by 2, round, divide by 2.0 rating = round(8 * rating / 10) / 2.0 data += ('starRating : {rating}\n'.format(rating=rating)) # This is shown on both the Program screen and the Details screen. # It uses the standard TV rating system of: TV-Y7, TV-Y, TV-G, TV-PG, TV-14, TV-MA and TV-NR. if getattr(my_show, 'contentrating', None): data += ('tvRating : {rating}\n'.format( rating=my_show['contentrating'])) # This field can be repeated as many times as necessary or omitted completely. if ep_obj.series.genre: for genre in ep_obj.series.genre.split('|'): if genre: data += ('vProgramGenre : {genre}\n'.format( genre=genre)) # NOTE: The following are metadata keywords are not used # displayMajorNumber # showingBits # displayMinorNumber # colorCode # vSeriesGenre # vGuestStar, vDirector, vExecProducer, vProducer, vWriter, vHost, vChoreographer # partCount # partIndex return data
def _ep_data(self, ep_obj): """ Creates an elementTree XML structure for a MediaBrowser style episode.xml and returns the resulting data object. show_obj: a Series instance to create the NFO for """ eps_to_write = [ep_obj] + ep_obj.related_episodes my_show = self._get_show_data(ep_obj.series) if not my_show: return None root_node = etree.Element('details') movie = etree.SubElement(root_node, 'movie') movie.attrib['isExtra'] = 'false' movie.attrib['isSet'] = 'false' movie.attrib['isTV'] = 'true' # write an MediaBrowser XML containing info for all matching episodes for ep_to_write in eps_to_write: try: my_ep = my_show[ep_to_write.season][ep_to_write.episode] except (IndexerEpisodeNotFound, IndexerSeasonNotFound): log.info( 'Unable to find episode {ep_num} on {indexer}...' ' has it been removed? Should I delete from db?', { 'ep_num': episode_num(ep_to_write.season, ep_to_write.episode), 'indexer': indexerApi(ep_obj.series.indexer).name, }) return None if ep_to_write == ep_obj: # root (or single) episode # default to today's date for specials if firstaired is not set if ep_to_write.season == 0 and not getattr( my_ep, 'firstaired', None): my_ep['firstaired'] = str(datetime.date.fromordinal(1)) if not (getattr(my_ep, 'episodename', None) and getattr(my_ep, 'firstaired', None)): return None episode = movie if ep_to_write.name: episode_name = etree.SubElement(episode, 'title') episode_name.text = ep_to_write.name season_number = etree.SubElement(episode, 'season') season_number.text = str(ep_to_write.season) episode_number = etree.SubElement(episode, 'episode') episode_number.text = str(ep_to_write.episode) if getattr(my_show, 'firstaired', None): try: year_text = str( datetime.datetime.strptime(my_show['firstaired'], dateFormat).year) if year_text: year = etree.SubElement(episode, 'year') year.text = year_text except Exception: pass if getattr(my_show, 'overview', None): plot = etree.SubElement(episode, 'plot') plot.text = my_show['overview'] if ep_to_write.description: overview = etree.SubElement(episode, 'episodeplot') overview.text = ep_to_write.description if getattr(my_show, 'contentrating', None): mpaa = etree.SubElement(episode, 'mpaa') mpaa.text = my_show['contentrating'] if not ep_obj.related_episodes and getattr( my_ep, 'rating', None): try: rating = int((float(my_ep['rating']) * 10)) except ValueError: rating = 0 if rating: rating = etree.SubElement(episode, 'rating') rating.text = str(rating) if getattr(my_ep, 'director', None): director = etree.SubElement(episode, 'director') director.text = my_ep['director'] if getattr(my_ep, 'writer', None): writer = etree.SubElement(episode, 'credits') writer.text = my_ep['writer'] if getattr(my_show, '_actors', None) or getattr( my_ep, 'gueststars', None): cast = etree.SubElement(episode, 'cast') if getattr(my_ep, 'gueststars', None) and isinstance( my_ep['gueststars'], string_types): for actor in (x.strip() for x in my_ep['gueststars'].split('|') if x.strip()): cur_actor = etree.SubElement(cast, 'actor') cur_actor.text = actor if getattr(my_show, '_actors', None): for actor in my_show['_actors']: if 'name' in actor and actor['name'].strip(): cur_actor = etree.SubElement(cast, 'actor') cur_actor.text = actor['name'].strip() else: # append data from (if any) related episodes if ep_to_write.name: if not episode_name.text: episode_name.text = ep_to_write.name else: episode_name.text = ', '.join( [episode_name.text, ep_to_write.name]) if ep_to_write.description: if not overview.text: overview.text = ep_to_write.description else: overview.text = '\r'.join( [overview.text, ep_to_write.description]) # Make it purdy helpers.indent_xml(root_node) data = etree.ElementTree(root_node) return data