def _get_custom_exceptions(force): 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}', {'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 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_custom_exceptions(force): 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}', {'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 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 as error: log.warning( u'Error getting external ids for other' u' indexer {name}: {reason}', { 'name': indexerApi(show.indexer).name, 'reason': error.message }) # 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 _get_xem_exceptions(force): xem_exceptions = defaultdict(dict) xem_url = 'http://thexem.de/map/allNames?origin={0}&seasonNumbers=1' if force or should_refresh('xem'): for indexer in indexerApi().indexers: indexer_api = indexerApi(indexer) # Not query XEM for unsupported indexers if not indexer_api.config.get('xem_origin'): continue logger.info( 'Checking for XEM scene exceptions updates for' ' {indexer_name}'.format(indexer_name=indexer_api.name)) url = xem_url.format(indexer_api.config['xem_origin']) response = helpers.get_url(url, session=xem_session, timeout=60, returns='response') 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}'.format( 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}'.format( 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: {e}'.format(indexer_id=indexer_id, e=exceptions)) logger.warning('XEM: Rejected entry error message:' ' {error}'.format(error=error)) set_last_refresh('xem') return xem_exceptions
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 IndexerShowAllreadyInLibrary() 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] 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 IndexerShowAllreadyInLibrary('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 IndexerShowAllreadyInLibrary('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 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] 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 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 __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 _get_show_data(self, show_obj): """ Retrieve show data from the indexer. Try to reuse the indexer_api class instance attribute. As we are reusing the indexers results, we need to do a full index including actors and images. :param show_obj: A TVshow object. :return: A re-indexed show object. """ show_id = show_obj.indexerid try: if not (show_obj.indexer_api and all([ show_obj.indexer_api.config[u'banners_enabled'], show_obj.indexer_api.config[u'actors_enabled'] ])): show_obj.create_indexer(banners=True, actors=True) self.indexer_api = show_obj.indexer_api my_show = self.indexer_api[int(show_id)] except IndexerShowNotFound: log.warning(u'Unable to find {indexer} show {id}, skipping it', { u'indexer': indexerApi(show_obj.indexer).name, u'id': show_id }) return False except (IndexerException, RequestException): log.warning( u'{indexer} is down, cannot use its data to add this show', {u'indexer': indexerApi(show_obj.indexer).name}) return False # check for title and id if not (getattr(my_show, u'seriesname', None) and getattr(my_show, u'id', None)): log.warning( u'Incomplete info for {indexer} show {id}, skipping it', { u'indexer': indexerApi(show_obj.indexer).name, u'id': show_id }) return False return my_show
def __init__(self): """Initialize the trakt recommended list object.""" self.cache_subfolder = __name__.split( '.')[-1] if '.' in __name__ else __name__ self.session = requests.Session() self.recommender = "Trakt Popular" self.default_img_src = 'trakt-default.png' self.anidb = Anidb(cache_dir=app.CACHE_DIR) self.tvdb_api_v2 = indexerApi(INDEXER_TVDBV2).indexer()
def _get_custom_exceptions(force): custom_exceptions = defaultdict(dict) if force or should_refresh('custom_exceptions'): for indexer in indexerApi().indexers: try: location = indexerApi(indexer).config['scene_loc'] logger.info( 'Checking for scene exception updates from {location}', location=location ) try: jdata = indexerApi(indexer).session.get(location, timeout=60).json() except (ValueError, AttributeError, RequestException) as error: logger.debug( 'Check scene exceptions update failed. Unable to ' 'update from {location}. Error: {error}'.format( location=location, error=error ) ) return custom_exceptions 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 except Exception as error: logger.error( 'Unable to update scene exceptions for {indexer}.' ' Error: {error}'.format( indexer=indexer, error=error ) ) continue set_last_refresh('custom_exceptions') return custom_exceptions
def _retrieve_show_image(self, image_type, show_obj, episode=None, which=None): """ Get an image URL from theTVDB.com and TMDB.com, download it and returns the data. :param image_type: type of image to retrieve (currently supported: fanart, poster, banner) :param show_obj: a Series object to use when searching for the image :param episode: Episode object (only needed for episode thumbnails) :param which: optional, a specific numbered poster to look for :return: the binary image data if available, or else None """ image_url = None indexer_show_obj = self._get_show_data(show_obj) if image_type not in (u'fanart', u'poster', u'banner', u'thumbnail', u'poster_thumb', u'banner_thumb'): log.error( u'Invalid {image}, unable to find it in the {indexer}', { u'image': image_type, u'indexer': indexerApi(show_obj.indexer).name }) return None if image_type == u'thumbnail' and episode: image_url = self._get_episode_thumb_url(indexer_show_obj, episode) elif image_type == u'poster_thumb': if getattr(indexer_show_obj, u'poster', None): image_url = re.sub(u'posters', u'_cache/posters', indexer_show_obj[u'poster']) if not image_url: # Try and get images from TMDB image_url = self._retrieve_show_images_from_tmdb( show_obj, image_type) elif image_type == u'banner_thumb': if getattr(indexer_show_obj, u'banner', None): image_url = re.sub(u'graphical', u'_cache/graphical', indexer_show_obj[u'banner']) else: if getattr(indexer_show_obj, image_type, None): image_url = indexer_show_obj[image_type] if not image_url and show_obj.indexer != 4: # Try and get images from TMDB image_url = self._retrieve_show_images_from_tmdb( show_obj, image_type) if image_url: image_data = get_image(image_url, which) return image_data return None
def searchIndexersForShowName(search_term, lang=None, indexer=None): if not lang or lang == 'null': lang = app.INDEXER_DEFAULT_LANGUAGE search_term = search_term.encode('utf-8') search_terms = [search_term] # If search term ends with what looks like a year, enclose it in () matches = re.match(r'^(.+ |)([12][0-9]{3})$', search_term) if matches: search_terms.append('%s(%s)' % (matches.group(1), matches.group(2))) for searchTerm in search_terms: # If search term begins with an article, let's also search for it without matches = re.match(r'^(?:a|an|the) (.+)$', searchTerm, 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 indexerApi().indexers if not int(indexer) else [int(indexer)]: l_indexer_api_parms = indexerApi(indexer).api_params.copy() l_indexer_api_parms['language'] = lang l_indexer_api_parms['custom_ui'] = classes.AllShowsListUI try: indexer_api = indexerApi(indexer).indexer(**l_indexer_api_parms) except IndexerUnavailable as msg: logger.log(u'Could not initialize Indexer {indexer}: {error}'. format(indexer=indexerApi(indexer).name, error=msg)) continue logger.log(u'Searching for Show with searchterm(s): %s on Indexer: %s' % ( search_terms, indexerApi(indexer).name), logger.DEBUG) for searchTerm in search_terms: try: indexer_results = indexer_api[searchTerm] # add search results results.setdefault(indexer, []).extend(indexer_results) except IndexerException as e: logger.log(u'Error searching for show: {error}'.format(error=e.message)) for i, shows in iteritems(results): final_results.extend({(indexerApi(i).name, i, indexerApi(i).config['show_url'], int(show['id']), show['seriesname'].encode('utf-8'), show['firstaired'] or 'N/A', show.get('network', '').encode('utf-8') or 'N/A') for show in shows}) lang_id = indexerApi().config['langabbv_to_id'][lang] return json.dumps({'results': final_results, 'langid': lang_id})
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] 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=[], title='New Show', header='New Show', topmenu='home', controller='addShows', action='newShow')
def _get_custom_exceptions(): custom_exceptions = {} if should_refresh('custom_exceptions'): for indexer in indexerApi().indexers: try: location = indexerApi(indexer).config['scene_loc'] logger.log('Checking for scene exception updates from {0}'.format(location)) response = helpers.getURL(location, session=indexerApi(indexer).session, timeout=60, returns='response') try: jdata = response.json() except (ValueError, AttributeError) as error: logger.log('Check scene exceptions update failed. Unable to update from {0}. Error: {1}'.format (location, error), logger.DEBUG) return custom_exceptions if indexer not in custom_exceptions: custom_exceptions[indexer] = {} for indexer_id in jdata[indexerApi(indexer).config['identifier']]: alias_list = [{scene_exception: int(scene_season)} for scene_season in jdata[indexerApi(indexer).config['identifier']][indexer_id] for scene_exception in jdata[indexerApi(indexer).config ['identifier']][indexer_id][scene_season]] custom_exceptions[indexer][indexer_id] = alias_list except Exception as error: logger.log('Unable to update scene exceptions for {0}. Error: {1}'.format (indexer, error), logger.ERROR) continue set_last_refresh('custom_exceptions') return custom_exceptions
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 _get_xem_exceptions(): xem_exceptions = {} if should_refresh('xem'): for indexer in indexerApi().indexers: # Not query XEM for unsupported indexers if not indexerApi(indexer).config.get('xem_origin'): continue logger.log('Checking for XEM scene exceptions updates for {0}'.format (indexerApi(indexer).name)) if indexer not in xem_exceptions: xem_exceptions[indexer] = {} xem_url = 'http://thexem.de/map/allNames?origin={0}&seasonNumbers=1'.format( indexerApi(indexer).config['xem_origin']) response = helpers.getURL(xem_url, session=xem_session, timeout=60, returns='response') try: jdata = response.json() except (ValueError, AttributeError) as error: logger.log('Check scene exceptions update failed for {0}. Unable to get URL: {1}'.format (indexerApi(indexer).name, xem_url), logger.DEBUG) continue if not jdata['data'] or jdata['result'] == 'failure': logger.log('No data returned from XEM while checking for scene exceptions. ' 'Update failed for {0}'.format(indexerApi(indexer).name), logger.DEBUG) continue for indexer_id, exceptions in iteritems(jdata['data']): try: xem_exceptions[indexer][indexer_id] = exceptions except Exception as error: logger.log('XEM: Rejected entry: Indexer ID: {0}, Exceptions: {1}'.format (indexer_id, exceptions), logger.WARNING) logger.log('XEM: Rejected entry error message: {0}'.format(error), logger.ERROR) set_last_refresh('xem') return xem_exceptions
def run(self): ShowQueueItem.run(self) logger.log( u'{id}: Beginning update of {show}'.format(id=self.show.indexerid, show=self.show.name), logger.DEBUG) logger.log( u'{id}: Retrieving show info from {indexer}'.format( id=self.show.indexerid, indexer=indexerApi(self.show.indexer).name), logger.DEBUG) 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 e: logger.log( u'{id}: Unable to contact {indexer}. Aborting: {error_msg}'. format(id=self.show.indexerid, indexer=indexerApi(self.show.indexer).name, error_msg=e.message), logger.WARNING) return except IndexerAttributeNotFound as e: logger.log( u'{id}: Data retrieved from {indexer} was incomplete. Aborting: {error_msg}' .format(id=self.show.indexerid, indexer=indexerApi(self.show.indexer).name, error_msg=e.message), logger.WARNING) return except IndexerShowNotFoundInLanguage as e: logger.log( u'{id}: Data retrieved from {indexer} was incomplete. The indexer does not provide ' u'show information in the searched language {language}. Aborting: {error_msg}' .format(id=self.show.indexerid, indexer=indexerApi(self.show.indexer).name, language=e.language, error_msg=e.message), logger.WARNING) 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=e.language)) return logger.log( u'{id}: Retrieving show info from IMDb'.format( id=self.show.indexerid), logger.DEBUG) try: self.show.load_imdb_info() except ImdbAPIError as e: logger.log( u'{id}: Something wrong on IMDb api: {error_msg}'.format( id=self.show.indexerid, error_msg=e.message), logger.INFO) except Exception as e: logger.log( u'{id}: Error loading IMDb info: {error_msg}'.format( id=self.show.indexerid, error_msg=e.message), logger.WARNING) # have to save show before reading episodes from db try: logger.log( u'{id}: Saving new IMDb show info to database'.format( id=self.show.indexerid), logger.DEBUG) self.show.save_to_db() except Exception as e: logger.log( u"{id}: Error saving new IMDb show info to database: {error_msg}" .format(id=self.show.indexerid, error_msg=e.message), logger.WARNING) logger.log(traceback.format_exc(), logger.ERROR) # get episode list from DB try: episodes_from_db = self.show.load_episodes_from_db() except IndexerException as e: logger.log( u'{id}: Unable to contact {indexer}. Aborting: {error_msg}'. format(id=self.show.indexerid, indexer=indexerApi(self.show.indexer).name, error_msg=e.message), logger.WARNING) return # get episode list from the indexer try: episodes_from_indexer = self.show.load_episodes_from_indexer() except IndexerException as e: logger.log( u'{id}: Unable to get info from {indexer}. The show info will not be refreshed. ' u'Error: {error_msg}'.format( id=self.show.indexerid, indexer=indexerApi(self.show.indexer).name, error_msg=e.message), logger.WARNING) episodes_from_indexer = None if episodes_from_indexer is None: logger.log( u'{id}: No data returned from {indexer} during full show update. Unable to update this show' .format(id=self.show.indexerid, indexer=indexerApi(self.show.indexer).name), logger.WARNING) 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]: logger.log( u'{id}: Permanently deleting episode {show} {ep} from the database' .format(id=self.show.indexerid, show=self.show.name, ep=episode_num(cur_season, cur_episode)), logger.DEBUG) # 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: logger.log( u'{id}: Episode {show} {ep} successfully deleted from the database' .format(id=self.show.indexerid, show=self.show.name, ep=episode_num(cur_season, cur_episode)), logger.DEBUG) # Save only after all changes were applied try: logger.log( u'{id}: Saving all updated show info to database'.format( id=self.show.indexerid), logger.DEBUG) self.show.save_to_db() except Exception as e: logger.log( u'{id}: Error saving all updated show info to database: {error_msg}' .format(id=self.show.indexerid, error_msg=e.message), logger.WARNING) logger.log(traceback.format_exc(), logger.ERROR) logger.log( u'{id}: Finished update of {show}'.format(id=self.show.indexerid, show=self.show.name), logger.DEBUG) # 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): ShowQueueItem.run(self) logger.log(u"Starting to add show {0}".format( "by ShowDir: {0}".format(self.showDir) if self. showDir else u"by 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 logger.log(u"" + str(indexerApi(self.indexer).name) + ": " + repr(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.showDir and self.root_dir: show_name = get_showname_from_indexer(self.indexer, self.indexer_id, self.lang) if show_name: self.showDir = os.path.join(self.root_dir, sanitize_filename(show_name)) dir_exists = make_dir(self.showDir) if not dir_exists: logger.log( u"Unable to create the folder {0}, can't add the show" .format(self.showDir)) return chmod_as_parent(self.showDir) else: logger.log( u"Unable to get a show {0}, can't add the show".format( self.showDir)) 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: logger.log( u"Show in {0} has no name on {1}, probably searched with the wrong language." .format(self.showDir, indexerApi(self.indexer).name), logger.ERROR) ui.notifications.error( 'Unable to add show', 'Show in {0} has no name on {1}, probably the wrong language. \ Delete .nfo and manually add the correct language.' .format(self.showDir, indexerApi(self.indexer).name)) self._finishEarly() return # if the show has no episodes/seasons if not s: logger.log(u"Show " + str(s['seriesname']) + u" is on " + str(indexerApi(self.indexer).name) + u" but contains no season/episode data.") ui.notifications.error( "Unable to add show", "Show {0} is on {1} but contains no season/episode data.". format(s['seriesname'], 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 IndexerShowAllreadyInLibrary as e: logger.log( u"Could not add the show %s, as it already is in your library." u" Error: %s" % (s['seriesname'], e.message), logger.WARNING) ui.notifications.error('Unable to add show', 'reason: {0}'.format(e.message)) self._finishEarly() # Clean up leftover if the newly created directory is empty. delete_empty_folders(self.showDir) return # TODO: Add more specific indexer exceptions, that should provide the user with some accurate feedback. except IndexerShowIncomplete as e: logger.log( u"%s Error while loading information from indexer %s. " u"Error: %s" % (self.indexer_id, indexerApi(self.indexer).name, e.message), logger.WARNING) ui.notifications.error( "Unable to add show", "Unable to look up the show in {0} on {1} using ID {2} " "Reason: {3}".format(self.showDir, indexerApi(self.indexer).name, self.indexer_id, e.message)) self._finishEarly() return except IndexerShowNotFoundInLanguage as e: logger.log( u'{id}: Data retrieved from {indexer} was incomplete. The indexer does not provide ' u'show information in the searched language {language}. Aborting: {error_msg}' .format(id=self.indexer_id, indexer=indexerApi(self.indexer).name, language=e.language, error_msg=e.message), logger.WARNING) 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=e.language)) self._finishEarly() return except Exception as e: logger.log( u"%s Error while loading information from indexer %s. " u"Error: %r" % (self.indexer_id, indexerApi(self.indexer).name, e.message), logger.ERROR) ui.notifications.error( "Unable to add show", "Unable to look up the show in {0} on {1} using ID {2}, not using the NFO. " "Delete .nfo and try adding manually again.".format( self.showDir, indexerApi(self.indexer).name, 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.showDir 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.flatten_folders = self.flatten_folders if self.flatten_folders is not None \ else app.FLATTEN_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 logger.log( u"Setting all previously aired episodes to the specified status: {status}" .format(status=statusStrings[self.default_status])) self.show.default_ep_status = self.default_status if self.show.anime: self.show.release_groups = BlackAndWhiteList( self.show.indexerid) if self.blacklist: self.show.release_groups.set_black_keywords(self.blacklist) if self.whitelist: self.show.release_groups.set_white_keywords(self.whitelist) # # be smartish about this # if self.show.genre and "talk show" in self.show.genre.lower(): # self.show.air_by_date = 1 # if self.show.genre and "documentary" in self.show.genre.lower(): # self.show.air_by_date = 0 # if self.show.classification and "sports" in self.show.classification.lower(): # self.show.sports = 1 except IndexerException as e: logger.log( u"Unable to add show due to an error with " + indexerApi(self.indexer).name + ": " + e.message, logger.ERROR) if self.show: ui.notifications.error("Unable to add " + str(self.show.name) + " due to an error with " + indexerApi(self.indexer).name + "") else: ui.notifications.error( "Unable to add show due to an error with " + indexerApi(self.indexer).name + "") self._finishEarly() return except MultipleShowObjectsException: logger.log( u"The show in " + self.showDir + " is already in your show list, skipping", logger.WARNING) ui.notifications.error( 'Show skipped', "The show in " + self.showDir + " is already in your show list") self._finishEarly() return except Exception as e: logger.log(u"Error trying to add show: " + e.message, logger.ERROR) logger.log(traceback.format_exc(), logger.DEBUG) self._finishEarly() raise logger.log(u"Retrieving show info from IMDb", logger.DEBUG) try: self.show.load_imdb_info() except ImdbAPIError as e: logger.log(u"Something wrong on IMDb api: " + e.message, logger.INFO) except Exception as e: logger.log(u"Error loading IMDb info: " + e.message, logger.ERROR) try: self.show.save_to_db() except Exception as e: logger.log(u"Error saving the show to the database: " + e.message, logger.ERROR) logger.log(traceback.format_exc(), logger.DEBUG) 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 e: logger.log( u"Error with " + indexerApi(self.show.indexer).name + ", not creating episode list: " + e.message, logger.ERROR) logger.log(traceback.format_exc(), logger.DEBUG) # update internal name cache name_cache.build_name_cache(self.show) try: self.show.load_episodes_from_dir() except Exception as e: logger.log(u"Error searching dir for episodes: " + e.message, logger.ERROR) logger.log(traceback.format_exc(), logger.DEBUG) # if they set default ep status to WANTED then run the backlog to search for episodes # FIXME: This needs to be a backlog queue item!!! if self.show.default_ep_status == WANTED: logger.log( u"Launching backlog for this show since its episodes are WANTED" ) app.backlog_search_scheduler.action.search_backlog([self.show]) 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: logger.log(u"update watchlist") notifiers.trakt_notifier.update_watchlist(show_obj=self.show) # Load XEM data to DB for show scene_numbering.xem_refresh(self.show.indexerid, self.show.indexer, force=True) # check if show has XEM mapping so we can determine if searches # should go by scene numbering or indexer numbering. if not self.scene and scene_numbering.get_xem_numbering_for_show( self.show.indexerid, self.show.indexer): self.show.scene = 1 # After initial add, set to default_status_after. self.show.default_ep_status = self.default_status_after 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 _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 _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
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
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 _parse_string(self, name): guess = guessit.guessit(name, dict(show_type=self.show_type)) result = self.to_parse_result(name, guess) search_series = helpers.get_show(result.series_name, self.try_indexers) if not self.naming_pattern else None # confirm passed in show object indexer id matches result show object indexer id series_obj = None if search_series and self.series and search_series.indexerid != self.series.indexerid else search_series result.series = series_obj or self.series # if this is a naming pattern test or result doesn't have a show object then return best result if not result.series or self.naming_pattern: return result new_episode_numbers = [] new_season_numbers = [] new_absolute_numbers = [] # if we have an air-by-date show and the result is air-by-date, # then get the real season/episode numbers if result.series.air_by_date and result.is_air_by_date: log.debug('Series {name} is air by date', {'name': result.series.name}) airdate = result.air_date.toordinal() main_db_con = db.DBConnection() sql_result = main_db_con.select( b'SELECT season, episode FROM tv_episodes WHERE indexer = ? AND showid = ? AND airdate = ?', [result.series.indexer, result.series.series_id, airdate]) season_number = None episode_numbers = [] if sql_result: season_number = int(sql_result[0][0]) episode_numbers = [int(sql_result[0][1])] # 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(sql_result) > 1: season_number = int(sql_result[1][0]) episode_numbers = [int(sql_result[1][1])] 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}) try: indexer_api_params = indexerApi(result.series.indexer).api_params.copy() if result.series.lang: indexer_api_params['language'] = result.series.lang indexer_api = indexerApi(result.series.indexer).indexer(**indexer_api_params) 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}', {'indexer_api': indexer_api, 'error': error.message} ) episode_numbers = [] except IndexerException as error: log.warning( 'Indexer exception: {indexer_api.name}: {error}', {'indexer_api': indexer_api, 'error': error.message} ) 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) elif result.series.is_anime and result.is_anime: log.debug('Scene numbering enabled series {name} is anime', {'name': result.series.name}) scene_season = scene_exceptions.get_scene_exceptions_by_name(result.series_name)[0][1] for absolute_episode in result.ab_episode_numbers: a = absolute_episode # Apparently we got a scene_season using the season scene exceptions. If we also do not have a season # parsed, guessit made a 'mistake' and it should have set the season with the value. # This is required for titles like: '[HorribleSubs].Kekkai.Sensen.&.Beyond.-.01.[1080p].mkv' if result.season_number is None and scene_season > 0: season = scene_season episode = [a] log.debug( 'Detected a season scene exception [{series_name} -> {scene_season}] without a ' 'season number in the title, ' 'assuming the episode # [{scene_absolute}] is the scene_absolute #.', {'series_name': result.series_name, 'scene_season': scene_season, 'scene_absolute': a} ) else: if result.series.is_scene: a = scene_numbering.get_indexer_absolute_numbering(result.series, absolute_episode, True, scene_season) # Translate the absolute episode number, back to the indexers season and episode. (season, episode) = helpers.get_all_episodes_from_absolute_number(result.series, [a]) log.debug( 'Scene numbering enabled series {name} using indexer for absolute {absolute}: {ep}', {'name': result.series.name, 'absolute': a, 'ep': episode_num(season, episode, 'absolute')} ) new_absolute_numbers.append(a) new_episode_numbers.extend(episode) new_season_numbers.append(season) elif result.season_number and result.episode_numbers: for episode_number in result.episode_numbers: season = result.season_number episode = episode_number if result.series.is_scene: (season, episode) = scene_numbering.get_indexer_numbering( result.series, result.season_number, episode_number ) log.debug( 'Scene numbering enabled series {name} using indexer numbering: {ep}', {'name': result.series.name, 'ep': episode_num(season, episode)} ) if result.series.is_anime: a = helpers.get_absolute_number_from_season_and_episode(result.series, season, episode) if a: new_absolute_numbers.append(a) log.debug( 'Scene numbering enabled anime {name} using indexer with absolute {absolute}: {ep}', {'name': result.series.name, 'absolute': a, 'ep': episode_num(season, episode, 'absolute')} ) new_episode_numbers.append(episode) new_season_numbers.append(season) # need to do a quick sanity check heregex. It's possible that we now have episodes # from more than one season (by tvdb numbering), and this is just too much # for the application, so we'd need to flag it. new_season_numbers = sorted(set(new_season_numbers)) # remove duplicates if len(new_season_numbers) > 1: raise InvalidNameException('Scene numbering results episodes from seasons {seasons}, (i.e. more than one) ' 'and Medusa does not support this. Sorry.'.format(seasons=new_season_numbers)) # If guess it's possible that we'd have duplicate episodes too, # so lets eliminate them new_episode_numbers = sorted(set(new_episode_numbers)) # maybe even duplicate absolute numbers so why not do them as well new_absolute_numbers = sorted(set(new_absolute_numbers)) if new_absolute_numbers: result.ab_episode_numbers = new_absolute_numbers if new_season_numbers and new_episode_numbers: result.episode_numbers = new_episode_numbers result.season_number = new_season_numbers[0] # For anime that we still couldn't get a season, let's assume we should use 1. if result.series.is_anime and result.season_number is None and result.episode_numbers: result.season_number = 1 log.warning( 'Unable to parse season number for anime {name}, ' 'assuming absolute numbered anime with season 1', {'name': result.series.name} ) if result.series.is_scene: log.debug( 'Converted parsed result {original} into {result}', {'original': result.original_name, 'result': result} ) return result
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(u'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(u'The show {show} is paused, not updating it.', show=show.name) continue indexer_api_params = indexerApi(show.indexer).api_params.copy() try: indexer_api = indexerApi(show.indexer).indexer(**indexer_api_params) except IndexerUnavailable: logger.warning(u'Problem running show_updater, Indexer {indexer_name} seems to be having ' u'connectivity issues. While trying to look for show updates on show: {show}', indexer_name=indexerApi(show.indexer).name, show=show.name) continue # Get the lastUpdate timestamp for this indexer. if indexerApi(show.indexer).name not in last_updates: last_updates[indexerApi(show.indexer).name] = \ self.update_cache.get_last_indexer_update(indexerApi(show.indexer).name) last_update = last_updates[indexerApi(show.indexer).name] # Get a list of updated shows from the indexer, since last update. # Use the list, to limit the shows for which are requested for the last updated seasons. if last_update and last_update > time.time() - (604800 * update_max_weeks): if show.indexer not in indexer_updated_shows: try: indexer_updated_shows[show.indexer] = indexer_api.get_last_updated_series( last_update, update_max_weeks ) except IndexerUnavailable: logger.warning(u'Problem running show_updater, Indexer {indexer_name} seems to be having ' u'connectivity issues while trying to look for show updates on show: {show}', indexer_name=indexerApi(show.indexer).name, show=show.name) continue except IndexerException as error: logger.warning(u'Problem running show_updater, Indexer {indexer_name} seems to be having ' u'issues while trying to get updates for show {show}. Cause: {cause}', indexer_name=indexerApi(show.indexer).name, show=show.name, cause=error.message) continue except RequestException as error: logger.warning(u'Problem running show_updater, Indexer {indexer_name} seems to be having ' u'issues while trying to get updates for show {show}. ', indexer_name=indexerApi(show.indexer).name, show=show.name) if isinstance(error, HTTPError): if error.response.status_code == 503: logger.warning(u'API Service offline: ' u'This service is temporarily offline, try again later.') elif error.response.status_code == 429: logger.warning(u'Your request count (#) is over the allowed limit of (40).') logger.warning(u'Cause: {cause}.', cause=error) continue except Exception as error: logger.exception(u'Problem running show_updater, Indexer {indexer_name} seems to be having ' u'issues while trying to get updates for show {show}. Cause: {cause}.', indexer_name=indexerApi(show.indexer).name, show=show.name, cause=error) continue # If the current show is not in the list, move on to the next. # Only do this for shows, if the indexer has had a successful update run within the last 12 weeks. if all([isinstance(indexer_updated_shows[show.indexer], list), show.indexerid not in indexer_updated_shows.get(show.indexer)]): logger.debug(u'Skipping show update for {show}. As the show is not ' u'in the indexers {indexer_name} list with updated ' u'shows within the last {weeks} weeks.', show=show.name, indexer_name=indexerApi(show.indexer).name, weeks=update_max_weeks) continue # These are the criteria for performing a full show refresh. if any([not hasattr(indexer_api, 'get_last_updated_seasons'), not last_update, last_update < time.time() - 604800 * update_max_weeks]): # no entry in lastUpdate, or last update was too long ago, # let's refresh the show for this indexer logger.debug(u'Trying to update {show}. Your lastUpdate for {indexer_name} is older then {weeks} weeks,' u" or the indexer doesn't support per season updates. Doing a full update.", show=show.name, indexer_name=indexerApi(show.indexer).name, weeks=update_max_weeks) 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], last_update, update_max_weeks) except IndexerUnavailable: logger.warning(u'Problem running show_updater, Indexer {indexer_name} seems to be having ' u'connectivity issues while trying to look for showupdates on show: {show}', indexer_name=indexerApi(show.indexer).name, show=show.name) continue except IndexerException as e: logger.warning(u'Problem running show_updater, Indexer {indexer_name} seems to be having ' u'issues while trying to get updates for show {show}. Cause: {cause}', indexer_name=indexerApi(show.indexer).name, show=show.name, cause=e.message) continue except Exception as e: logger.exception(u'Problem running show_updater, Indexer {indexer_name} seems to be having ' u'issues while trying to get updates for show {show}. Cause: {cause}', indexer_name=indexerApi(show.indexer).name, show=show.name, cause=e) continue if updated_seasons[show.indexerid]: logger.info(u'{show_name}: Adding the following seasons for update to queue: {seasons}', show_name=show.name, seasons=updated_seasons[show.indexerid]) for season in updated_seasons[show.indexerid]: season_updates.append((show.indexer, show, season)) pi_list = [] # Full refreshes for show in refresh_shows: # If the cur_show is not 'paused' then add to the show_queue_scheduler if not show.paused: logger.info(u'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(u'Automatic update failed. Error: {error}', error=e) except Exception as e: logger.error(u'Automatic update failed: Error: {error}', error=e) else: logger.info(u'Show update skipped, show: {show} is paused.', show=show.name) # Only update expired season for show in season_updates: # If the cur_show is not 'paused' then add to the show_queue_scheduler if not show[1].paused: logger.info(u'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(u'Automatic update failed. Error: {error}', error=e) except Exception as e: logger.error(u'Automatic update failed: Error: {error}', error=e) else: logger.info(u'Show update skipped, show: {show} is paused.', show=show[1].name) 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): if not show.paused: try: app.show_queue_scheduler.action.refreshShow(show, True) except CantRefreshShowException as e: logger.warning(u'Show refresh on show {show_name} failed. Error: {error}', show_name=show.name, error=e) except Exception as e: logger.error(u'Show refresh on show {show_name} failed: Unexpected Error: {error}', show_name=show.name, error=e) else: logger.info(u'Show refresh skipped, show: {show_name} is paused.', show_name=show.name) if refresh_shows or season_updates: for indexer in set([show.indexer for show 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(u'Updated lastUpdate timestamp for {indexer_name}', indexer_name=indexer_api.name) logger.info(u'Completed scheduling updates on shows') else: logger.info(u'Completed but there was nothing to update') self.amActive = False
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 my_show = self._get_show_data(ep_obj.series) if not my_show: 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 = my_show[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'] = str(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(my_show, 'seriesname', None): showtitle = etree.SubElement(episode, 'showtitle') showtitle.text = my_show['seriesname'] season = etree.SubElement(episode, 'season') season.text = str(ep_to_write.season) episodenum = etree.SubElement(episode, 'episode') episodenum.text = str(ep_to_write.episode) uniqueid = etree.SubElement(episode, 'uniqueid') uniqueid.text = str(ep_to_write.indexerid) if ep_to_write.airdate != datetime.date.fromordinal(1): aired = etree.SubElement(episode, 'aired') aired.text = str(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(my_show, 'runtime', None): runtime = etree.SubElement(episode, 'runtime') runtime.text = my_show['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 = 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(my_show, '_actors', None): for actor in my_show['_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 _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 run(self): ShowQueueItem.run(self) logger.log( u'{id}: Beginning update of {show}{season}'.format( id=self.show.indexerid, show=self.show.name, season=u' with season(s) [{0}]'.format(u','.join( str(s) for s in self.seasons) if self.seasons else u'')), logger.INFO) logger.log( u'{id}: Retrieving show info from {indexer}'.format( id=self.show.indexerid, indexer=indexerApi(self.show.indexer).name), logger.DEBUG) 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 e: logger.log( u'{id}: Unable to contact {indexer}. Aborting: {error_msg}'. format(id=self.show.indexerid, indexer=indexerApi(self.show.indexer).name, error_msg=e.message), logger.WARNING) return except IndexerAttributeNotFound as e: logger.log( u'{id}: Data retrieved from {indexer} was incomplete. Aborting: {error_msg}' .format(id=self.show.indexerid, indexer=indexerApi(self.show.indexer).name, error_msg=e.message), logger.WARNING) return logger.log( u'{id}: Retrieving show info from IMDb'.format( id=self.show.indexerid), logger.DEBUG) try: self.show.load_imdb_info() except ImdbAPIError as e: logger.log( u'{id}: Something wrong on IMDb api: {error_msg}'.format( id=self.show.indexerid, error_msg=e.message), logger.INFO) except Exception as e: logger.log( u'{id}: Error loading IMDb info: {error_msg}'.format( id=self.show.indexerid, error_msg=e.message), logger.WARNING) # have to save show before reading episodes from db try: logger.log( u'{id}: Saving new IMDb show info to database'.format( id=self.show.indexerid), logger.DEBUG) self.show.save_to_db() except Exception as e: logger.log( u"{id}: Error saving new IMDb show info to database: {error_msg}" .format(id=self.show.indexerid, error_msg=e.message), logger.WARNING) logger.log(traceback.format_exc(), logger.ERROR) # get episode list from DB try: episodes_from_db = self.show.load_episodes_from_db(self.seasons) except IndexerException as e: logger.log( u'{id}: Unable to contact {indexer}. Aborting: {error_msg}'. format(id=self.show.indexerid, indexer=indexerApi(self.show.indexer).name, error_msg=e.message), logger.WARNING) return # get episode list from the indexer try: episodes_from_indexer = self.show.load_episodes_from_indexer( self.seasons) except IndexerException as e: logger.log( u'{id}: Unable to get info from {indexer}. The show info will not be refreshed. ' u'Error: {error_msg}'.format( id=self.show.indexerid, indexer=indexerApi(self.show.indexer).name, error_msg=e.message), logger.WARNING) episodes_from_indexer = None if episodes_from_indexer is None: logger.log( u'{id}: No data returned from {indexer} during season show update. Unable to update this show' .format(id=self.show.indexerid, indexer=indexerApi(self.show.indexer).name), logger.WARNING) 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]: logger.log( u'{id}: Permanently deleting episode {show} {ep} from the database' .format(id=self.show.indexerid, show=self.show.name, ep=episode_num(cur_season, cur_episode)), logger.DEBUG) # 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: logger.log( u'{id}: Episode {show} {ep} successfully deleted from the database' .format(id=self.show.indexerid, show=self.show.name, ep=episode_num(cur_season, cur_episode)), logger.DEBUG) # Save only after all changes were applied try: logger.log( u'{id}: Saving all updated show info to database'.format( id=self.show.indexerid), logger.DEBUG) self.show.save_to_db() except Exception as e: logger.log( u'{id}: Error saving all updated show info to database: {error_msg}' .format(id=self.show.indexerid, error_msg=e.message), logger.WARNING) logger.log(traceback.format_exc(), logger.ERROR) logger.log( u'{id}: Finished update of {show}'.format(id=self.show.indexerid, show=self.show.name), logger.INFO) self.finish()
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 __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 _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.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 _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 _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 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: lastRefresh = int(rows[0]['last_refreshed']) refresh = int(time.mktime(datetime.datetime.today().timetuple()) ) > lastRefresh + 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( indexer_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( indexer_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'] ] ]) cl.append([ "UPDATE tv_episodes SET absolute_number = ? WHERE indexer = ? AND showid = ? AND season = ? AND episode = ? AND absolute_number = 0", [ 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'] ] ]) 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 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 = 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 _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 = 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, 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(u'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(u'The show {show} is paused, not updating it.', show=show.name) continue indexer_api_params = indexerApi(show.indexer).api_params.copy() try: indexer_api = indexerApi(show.indexer).indexer(**indexer_api_params) except IndexerUnavailable: logger.warning(u'Problem running show_updater, Indexer {indexer_name} seems to be having ' u'connectivity issues. While trying to look for show updates on show: {show}', indexer_name=indexerApi(show.indexer).name, show=show.name) continue # Get the lastUpdate timestamp for this indexer. if indexerApi(show.indexer).name not in last_updates: last_updates[indexerApi(show.indexer).name] = \ self.update_cache.get_last_indexer_update(indexerApi(show.indexer).name) last_update = last_updates[indexerApi(show.indexer).name] # Get a list of updated shows from the indexer, since last update. # Use the list, to limit the shows for which are requested for the last updated seasons. if last_update and last_update > time.time() - (604800 * update_max_weeks): if show.indexer not in indexer_updated_shows: try: indexer_updated_shows[show.indexer] = indexer_api.get_last_updated_series( last_update, update_max_weeks ) except IndexerUnavailable: logger.warning(u'Problem running show_updater, Indexer {indexer_name} seems to be having ' u'connectivity issues while trying to look for show updates on show: {show}', indexer_name=indexerApi(show.indexer).name, show=show.name) continue except IndexerException as error: logger.warning(u'Problem running show_updater, Indexer {indexer_name} seems to be having ' u'issues while trying to get updates for show {show}. Cause: {cause!r}', indexer_name=indexerApi(show.indexer).name, show=show.name, cause=error) continue except RequestException as error: logger.warning(u'Problem running show_updater, Indexer {indexer_name} seems to be having ' u'issues while trying to get updates for show {show}. Cause: {cause!r}', indexer_name=indexerApi(show.indexer).name, show=show.name, cause=error) if isinstance(error, HTTPError): if error.response.status_code == 503: logger.warning(u'API Service offline: ' u'This service is temporarily offline, try again later.') elif error.response.status_code == 429: logger.warning(u'Your request count (#) is over the allowed limit of (40).') continue except Exception as error: logger.exception(u'Problem running show_updater, Indexer {indexer_name} seems to be having ' u'issues while trying to get updates for show {show}. Cause: {cause!r}.', indexer_name=indexerApi(show.indexer).name, show=show.name, cause=error) continue # If the current show is not in the list, move on to the next. # Only do this for shows, if the indexer has had a successful update run within the last 12 weeks. if all([isinstance(indexer_updated_shows[show.indexer], list), show.indexerid not in indexer_updated_shows.get(show.indexer)]): logger.debug(u'Skipping show update for {show}. As the show is not ' u'in the indexers {indexer_name} list with updated ' u'shows within the last {weeks} weeks.', show=show.name, indexer_name=indexerApi(show.indexer).name, weeks=update_max_weeks) continue # These are the criteria for performing a full show refresh. if any([not hasattr(indexer_api, 'get_last_updated_seasons'), not last_update or last_update < time.time() - 604800 * update_max_weeks]): # no entry in lastUpdate, or last update was too long ago, # let's refresh the show for this indexer logger.debug(u'Trying to update {show}. Your lastUpdate for {indexer_name} is older then {weeks} weeks,' u" or the indexer doesn't support per season updates. Doing a full update.", show=show.name, indexer_name=indexerApi(show.indexer).name, weeks=update_max_weeks) 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], last_update, update_max_weeks) except IndexerUnavailable: logger.warning(u'Problem running show_updater, Indexer {indexer_name} seems to be having ' u'connectivity issues while trying to look for showupdates on show: {show}', indexer_name=indexerApi(show.indexer).name, show=show.name) continue except IndexerException as error: logger.warning(u'Problem running show_updater, Indexer {indexer_name} seems to be having ' u'issues while trying to get updates for show {show}. Cause: {cause!r}', indexer_name=indexerApi(show.indexer).name, show=show.name, cause=error) continue except Exception as error: logger.exception(u'Problem running show_updater, Indexer {indexer_name} seems to be having ' u'issues while trying to get updates for show {show}. Cause: {cause!r}', indexer_name=indexerApi(show.indexer).name, show=show.name, cause=error) continue if updated_seasons[show.indexerid]: logger.info(u'{show_name}: Adding the following seasons for update to queue: {seasons}', show_name=show.name, seasons=updated_seasons[show.indexerid]) for season in updated_seasons[show.indexerid]: season_updates.append((show.indexer, show, season)) pi_list = [] # Full refreshes for show in refresh_shows: # If the cur_show is not 'paused' then add to the show_queue_scheduler if not show.paused: logger.info(u'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(u'Automatic update failed. Error: {error}', error=e) except Exception as e: logger.error(u'Automatic update failed: Error: {error}', error=e) else: logger.info(u'Show update skipped, show: {show} is paused.', show=show.name) # Only update expired season for show in season_updates: # If the cur_show is not 'paused' then add to the show_queue_scheduler if not show[1].paused: logger.info(u'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(u'Automatic update failed. Error: {error}', error=e) except Exception as e: logger.error(u'Automatic update failed: Error: {error}', error=e) else: logger.info(u'Show update skipped, show: {show} is paused.', show=show[1].name) 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): if not show.paused: try: app.show_queue_scheduler.action.refreshShow(show, True) except CantRefreshShowException as e: logger.warning(u'Show refresh on show {show_name} failed. Error: {error}', show_name=show.name, error=e) except Exception as e: logger.error(u'Show refresh on show {show_name} failed: Unexpected Error: {error}', show_name=show.name, error=e) else: logger.info(u'Show refresh skipped, show: {show_name} is paused.', show_name=show.name) if refresh_shows or season_updates: for indexer in set([show.indexer for show 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(u'Updated lastUpdate timestamp for {indexer_name}', indexer_name=indexer_api.name) logger.info(u'Completed scheduling updates on shows') else: logger.info(u'Completed but there was nothing to update') self.amActive = False
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 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 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.info('Starting to add show by {0}', ('ShowDir: {0}'.format(self.showDir) if self.showDir 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.showDir and self.root_dir: show_name = get_showname_from_indexer(self.indexer, self.indexer_id, self.lang) if show_name: self.showDir = os.path.join(self.root_dir, sanitize_filename(show_name)) dir_exists = make_dir(self.showDir) if not dir_exists: log.info( "Unable to create the folder {0}, can't add the show", self.showDir) return chmod_as_parent(self.showDir) else: log.info("Unable to get a show {0}, can't add the show", self.showDir) 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.showDir, '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.showDir, 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.showDir) return # TODO: Add more specific indexer exceptions, that should provide the user with some accurate feedback. except IndexerShowNotFound: 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.', { 'id': self.indexer_id, 'path': self.showDir, 'indexer': indexerApi(self.indexer).name }) 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.showDir, 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.showDir, 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.showDir 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) # # be smartish about this # if self.show.genre and "talk show" in self.show.genre.lower(): # self.show.air_by_date = 1 # if self.show.genre and "documentary" in self.show.genre.lower(): # self.show.air_by_date = 0 # if self.show.classification and "sports" in self.show.classification.lower(): # self.show.sports = 1 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.showDir}) ui.notifications.error( 'Show skipped', 'The show in {show_dir} is already in your show list'.format( show_dir=self.showDir)) 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 # FIXME: This needs to be a backlog queue item!!! if self.show.default_ep_status == WANTED: log.info( 'Launching backlog for this show since its episodes are WANTED' ) app.backlog_search_scheduler.action.search_backlog([self.show]) 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. if not self.scene and scene_numbering.get_xem_numbering_for_show( self.show): self.show.scene = 1 # 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()) self.finish()
def getIndexerLanguages(): result = indexerApi().config['valid_languages'] return json.dumps({'results': result})
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: lastRefresh = int(rows[0]['last_refreshed']) refresh = int(time.mktime(datetime.datetime.today().timetuple())) > lastRefresh + 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)