Exemple #1
0
    def _create_recommended_show(self, series):
        """Create the RecommendedShow object from the returned showobj."""
        externals = {
            'imdb_id': ImdbIdentifier(series.get('imdb_tt')).series_id
        }

        # Get tmdb id using a call to tmdb api.
        t = indexerApi(INDEXER_TMDB).indexer(
            **indexerApi(INDEXER_TMDB).api_params.copy())
        externals.update(t.get_id_by_external(**externals))

        rec_show = RecommendedShow(
            self,
            ImdbIdentifier(series.get('imdb_tt')).series_id,
            series.get('name'), **{
                'rating': series.get('rating'),
                'votes': series.get('votes'),
                'image_href': series.get('imdb_url'),
                'ids': externals,
                'subcat': 'popular',
                'genres': [genre.lower() for genre in series.get('genres')],
                'plot': series.get('outline')
            })

        if series.get('image_url'):
            rec_show.cache_image(series.get('image_url'))

        return rec_show
Exemple #2
0
def _get_custom_exceptions(force):
    """Exceptions maintained by the medusa.github.io repo."""
    custom_exceptions = defaultdict(dict)

    if force or should_refresh('custom_exceptions'):
        for indexer in indexerApi().indexers:
            location = indexerApi(indexer).config['scene_loc']
            logger.info('Checking for scene exception updates from {location}',
                        location=location)
            try:
                # When any Medusa Safe session exception, session returns None and then AttributeError when json()
                jdata = safe_session.get(location, timeout=60).json()
            except (ValueError, AttributeError) as error:
                logger.debug('Check scene exceptions update failed. Unable to '
                             'update from {location}. Error: {error}'.format(
                                 location=location, error=error))
                # If unable to get scene exceptions, assume we can't connect to CDN so we don't `continue`
                return custom_exceptions

            # let Glotz use the TVDB exceptions
            if indexerApi(indexer).config['name'] == 'Glotz':
                indexer_ids = jdata['tvdb']
            else:
                indexer_ids = jdata[indexerApi(indexer).config['identifier']]
            for indexer_id in indexer_ids:
                indexer_exceptions = indexer_ids[indexer_id]
                alias_list = [{
                    exception: int(season)
                } for season in indexer_exceptions
                              for exception in indexer_exceptions[season]]
                custom_exceptions[indexer][indexer_id] = alias_list

            set_last_refresh('custom_exceptions')

    return custom_exceptions
Exemple #3
0
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
Exemple #4
0
    def run(self):

        ShowQueueItem.run(self)

        log.info('{id}: Performing refresh on {show}', {
            'id': self.show.series_id,
            'show': self.show.name
        })

        try:
            self.show.refresh_dir()
            if self.force:
                self.show.update_metadata()
            self.show.write_metadata()
            self.show.populate_cache()

            # Load XEM data to DB for show
            scene_numbering.xem_refresh(self.show, force=True)
        except IndexerException as error:
            log.warning(
                '{id}: Unable to contact {indexer}. Aborting: {error_msg}', {
                    'id': self.show.series_id,
                    'indexer': indexerApi(self.show.indexer).name,
                    'error_msg': error
                })
        except Exception as error:
            log.error(
                '{id}: Error while refreshing show {show}. Error: {error_msg}',
                {
                    'id': self.show.series_id,
                    'show': self.show.name,
                    'error_msg': error
                })

        self.finish()
Exemple #5
0
 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()
Exemple #6
0
 def __init__(self):
     """Initialize the trakt recommended list object."""
     super(TraktPopular, self).__init__()
     self.cache_subfolder = TraktPopular.CACHE_SUBFOLDER
     self.source = EXTERNAL_TRAKT
     self.recommender = TraktPopular.TITLE
     self.default_img_src = 'trakt-default.png'
     self.tvdb_api_v2 = indexerApi(INDEXER_TVDBV2).indexer()
Exemple #7
0
    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'
        )
Exemple #8
0
    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()
Exemple #9
0
    def run(self):

        ShowQueueItem.run(self)

        log.debug('{id}: Beginning update of {show}', {
            'id': self.show.series_id,
            'show': self.show.name
        })

        log.debug(
            '{id}: Retrieving show info from {indexer}', {
                'id': self.show.series_id,
                'indexer': indexerApi(self.show.indexer).name
            })
        try:
            # Let's make sure we refresh the indexer_api object attached to the show object.
            self.show.create_indexer()
            self.show.load_from_indexer()
        except IndexerError as error:
            log.warning(
                '{id}: Unable to contact {indexer}. Aborting: {error_msg}', {
                    'id': self.show.series_id,
                    'indexer': indexerApi(self.show.indexer).name,
                    'error_msg': error
                })
            return
        except IndexerAttributeNotFound as error:
            log.warning(
                '{id}: Data retrieved from {indexer} was incomplete. Aborting: {error_msg}',
                {
                    'id': self.show.series_id,
                    'indexer': indexerApi(self.show.indexer).name,
                    'error_msg': error
                })
            return
        except IndexerShowNotFoundInLanguage as error:
            log.warning(
                '{id}: Data retrieved from {indexer} was incomplete. The indexer does not provide'
                ' show information in the searched language {language}. Aborting: {error_msg}',
                {
                    'id': self.show.series_id,
                    'indexer': indexerApi(self.show.indexer).name,
                    'language': error.language,
                    'error_msg': error
                })
            ui.notifications.error(
                'Error changing language show!',
                'Unable to change language for show {show_name}'
                ' on {indexer} to language: {language}'.format(
                    show_name=self.show.name,
                    indexer=indexerApi(self.show.indexer).name,
                    language=error.language))
            return

        log.debug('{id}: Retrieving show info from IMDb',
                  {'id': self.show.series_id})
        try:
            self.show.load_imdb_info()
        except ImdbAPIError as error:
            log.info('{id}: Something wrong on IMDb api: {error_msg}', {
                'id': self.show.series_id,
                'error_msg': error
            })
        except RequestException as error:
            log.warning('{id}: Error loading IMDb info: {error_msg}', {
                'id': self.show.series_id,
                'error_msg': error
            })

        # have to save show before reading episodes from db
        try:
            log.debug('{id}: Saving new IMDb show info to database',
                      {'id': self.show.series_id})
            self.show.save_to_db()
        except Exception as error:
            log.warning(
                '{id}: Error saving new IMDb show info to database: {error_msg}',
                {
                    'id': self.show.series_id,
                    'error_msg': error
                })
            log.error(traceback.format_exc())

        # get episode list from DB
        try:
            episodes_from_db = self.show.load_episodes_from_db()
        except IndexerException as error:
            log.warning(
                '{id}: Unable to contact {indexer}. Aborting: {error_msg}', {
                    'id': self.show.series_id,
                    'indexer': indexerApi(self.show.indexer).name,
                    'error_msg': error
                })
            return

        # get episode list from the indexer
        try:
            episodes_from_indexer = self.show.load_episodes_from_indexer()
        except IndexerException as error:
            log.warning(
                '{id}: Unable to get info from {indexer}. The show info will not be refreshed.'
                ' Error: {error_msg}', {
                    'id': self.show.series_id,
                    'indexer': indexerApi(self.show.indexer).name,
                    'error_msg': error
                })
            episodes_from_indexer = None

        if episodes_from_indexer is None:
            log.warning(
                '{id}: No data returned from {indexer} during full show update.'
                ' Unable to update this show', {
                    'id': self.show.series_id,
                    'indexer': indexerApi(self.show.indexer).name
                })
        else:
            # for each ep we found on the Indexer delete it from the DB list
            for cur_season in episodes_from_indexer:
                for cur_episode in episodes_from_indexer[cur_season]:
                    if cur_season in episodes_from_db and cur_episode in episodes_from_db[
                            cur_season]:
                        del episodes_from_db[cur_season][cur_episode]

            # remaining episodes in the DB list are not on the indexer, just delete them from the DB
            for cur_season in episodes_from_db:
                for cur_episode in episodes_from_db[cur_season]:
                    log.debug(
                        '{id}: Permanently deleting episode {show} {ep} from the database',
                        {
                            'id': self.show.series_id,
                            'show': self.show.name,
                            'ep': episode_num(cur_season, cur_episode)
                        })
                    # Create the ep object only because Im going to delete it
                    ep_obj = self.show.get_episode(cur_season, cur_episode)
                    try:
                        ep_obj.delete_episode()
                    except EpisodeDeletedException:
                        log.debug(
                            '{id}: Episode {show} {ep} successfully deleted from the database',
                            {
                                'id': self.show.series_id,
                                'show': self.show.name,
                                'ep': episode_num(cur_season, cur_episode)
                            })

        # Save only after all changes were applied
        try:
            log.debug('{id}: Saving all updated show info to database',
                      {'id': self.show.series_id})
            self.show.save_to_db()
        except Exception as error:
            log.warning(
                '{id}: Error saving all updated show info to database: {error_msg}',
                {
                    'id': self.show.series_id,
                    'error_msg': error
                })
            log.error(traceback.format_exc())

        # Replace the images in cache
        log.info('{id}: Replacing images for show {show}', {
            'id': self.show.series_id,
            'show': self.show.name
        })
        replace_images(self.show)

        log.debug('{id}: Finished update of {show}', {
            'id': self.show.series_id,
            'show': self.show.name
        })

        # Refresh show needs to be forced since current execution locks the queue
        app.show_queue_scheduler.action.refreshShow(self.show, True)
        self.finish()
Exemple #10
0
    def run(self, force=False):

        self.amActive = True
        refresh_shows = []  # A list of shows, that need to be refreshed
        season_updates = []  # A list of show seasons that have passed their next_update timestamp
        update_max_weeks = 12

        network_timezones.update_network_dict()

        # Refresh the exceptions_cache from db.
        refresh_exceptions_cache()

        logger.info('Started periodic show updates')

        # Cache for the indexers list of updated show
        indexer_updated_shows = {}
        # Cache for the last indexer update timestamp
        last_updates = {}

        # Loop through the list of shows, and per show evaluate if we can use the .get_last_updated_seasons()
        for show in app.showList:
            if show.paused:
                logger.info('The show {show} is paused, not updating it.', show=show.name)
                continue

            indexer_name = indexerApi(show.indexer).name
            indexer_api_params = indexerApi(show.indexer).api_params.copy()
            try:
                indexer_api = indexerApi(show.indexer).indexer(**indexer_api_params)
            except IndexerUnavailable:
                logger.warning('Problem running show_updater, Indexer {indexer_name} seems to be having '
                               'connectivity issues. While trying to look for show updates on show: {show}',
                               indexer_name=indexer_name, show=show.name)
                continue

            # Get the lastUpdate timestamp for this indexer.
            if indexer_name not in last_updates:
                last_indexer_update = self.update_cache.get_last_indexer_update(indexer_name)
                if not last_indexer_update:
                    last_updates[indexer_name] = int(time.time() - 86400)  # 1 day ago
                elif last_indexer_update < time.time() - 604800 * update_max_weeks:
                    last_updates[indexer_name] = int(time.time() - 604800)  # 1 week ago
                else:
                    last_updates[indexer_name] = last_indexer_update

            # Get a list of updated shows from the indexer, since last update.
            if show.indexer not in indexer_updated_shows:
                try:
                    indexer_updated_shows[show.indexer] = indexer_api.get_last_updated_series(
                        last_updates[indexer_name], update_max_weeks
                    )
                except IndexerUnavailable:
                    logger.warning('Problem running show_updater, Indexer {indexer_name} seems to be having '
                                   'connectivity issues while trying to look for show updates on show: {show}',
                                   indexer_name=indexer_name, show=show.name)
                    continue
                except IndexerException as error:
                    logger.warning('Problem running show_updater, Indexer {indexer_name} seems to be having '
                                   'issues while trying to get updates for show {show}. Cause: {cause!r}',
                                   indexer_name=indexer_name, show=show.name, cause=error)
                    continue
                except RequestException as error:
                    logger.warning('Problem running show_updater, Indexer {indexer_name} seems to be having '
                                   'issues while trying to get updates for show {show}. Cause: {cause!r}',
                                   indexer_name=indexer_name, show=show.name, cause=error)

                    if isinstance(error, HTTPError):
                        if error.response.status_code == 503:
                            logger.warning('API Service offline: '
                                           'This service is temporarily offline, try again later.')
                        elif error.response.status_code == 429:
                            logger.warning('Your request count (#) is over the allowed limit of (40).')
                    continue
                except Exception as error:
                    logger.exception('Problem running show_updater, Indexer {indexer_name} seems to be having '
                                     'issues while trying to get updates for show {show}. Cause: {cause!r}.',
                                     indexer_name=indexer_name, show=show.name, cause=error)
                    continue

            # Update shows that were updated in the last X weeks
            # or were not updated within the last X weeks
            if show.indexerid not in indexer_updated_shows.get(show.indexer, []):
                if show.last_update_indexer > time.time() - 604800 * update_max_weeks:
                    logger.debug('Skipping show update for {show}. Show was not in the '
                                 'indexers {indexer_name} list with updated shows and it '
                                 'was updated within the last {weeks} weeks.', show=show.name,
                                 indexer_name=indexer_name, weeks=update_max_weeks)
                    continue

            # If indexer doesn't have season updates.
            if not hasattr(indexer_api, 'get_last_updated_seasons'):
                logger.debug('Adding the following show for full update to queue: {show}', show=show.name)
                refresh_shows.append(show)

            # Else fall back to per season updates.
            elif hasattr(indexer_api, 'get_last_updated_seasons'):
                # Get updated seasons and add them to the season update list.
                try:
                    updated_seasons = indexer_api.get_last_updated_seasons(
                        [show.indexerid], show.last_update_indexer, update_max_weeks)
                except IndexerUnavailable:
                    logger.warning('Problem running show_updater, Indexer {indexer_name} seems to be having '
                                   'connectivity issues while trying to look for show updates for show: {show}',
                                   indexer_name=indexer_name, show=show.name)
                    continue
                except IndexerException as error:
                    logger.warning('Problem running show_updater, Indexer {indexer_name} seems to be having '
                                   'issues while trying to get updates for show {show}. Cause: {cause!r}',
                                   indexer_name=indexer_name, show=show.name, cause=error)
                    continue
                except Exception as error:
                    logger.exception('Problem running show_updater, Indexer {indexer_name} seems to be having '
                                     'issues while trying to get updates for show {show}. Cause: {cause!r}',
                                     indexer_name=indexer_name, show=show.name, cause=error)
                    continue

                if updated_seasons[show.indexerid]:
                    logger.info('{show_name}: Adding the following seasons for update to queue: {seasons}',
                                show_name=show.name, seasons=updated_seasons[show.indexerid])
                    season_updates.append((show.indexer, show, updated_seasons[show.indexerid]))
                elif show.indexerid in indexer_updated_shows.get(show.indexer, []):
                    # This show was marked to have an update, but it didn't get a season update. Let's fully
                    # update the show anyway.
                    logger.debug('Could not detect a season update, but an update is required. \n'
                                 'Adding the following show for full update to queue: {show}', show=show.name)
                    refresh_shows.append(show)

        pi_list = []
        # Full refreshes
        for show in refresh_shows:
            logger.info('Full update on show: {show}', show=show.name)
            try:
                pi_list.append(app.show_queue_scheduler.action.updateShow(show))
            except (CantUpdateShowException, CantRefreshShowException) as e:
                logger.warning('Automatic update failed. Error: {error}', error=e)
            except Exception as e:
                logger.error('Automatic update failed: Error: {error}', error=e)

        # Only update expired season
        for show in season_updates:
            logger.info('Updating season {season} for show: {show}.', season=show[2], show=show[1].name)
            try:
                pi_list.append(app.show_queue_scheduler.action.updateShow(show[1], season=show[2]))
            except CantUpdateShowException as e:
                logger.warning('Automatic update failed. Error: {error}', error=e)
            except Exception as e:
                logger.error('Automatic update failed: Error: {error}', error=e)

        ui.ProgressIndicators.setIndicator('dailyUpdate', ui.QueueProgressIndicator('Daily Update', pi_list))

        # Only refresh updated shows that have been updated using the season updates.
        # The full refreshed shows, are updated from the queueItem.
        for show in set(show[1] for show in season_updates):
            try:
                app.show_queue_scheduler.action.refreshShow(show, True)
            except CantRefreshShowException as e:
                logger.warning('Show refresh on show {show_name} failed. Error: {error}',
                               show_name=show.name, error=e)
            except Exception as e:
                logger.error('Show refresh on show {show_name} failed: Unexpected Error: {error}',
                             show_name=show.name, error=e)

        if refresh_shows or season_updates:
            for indexer in set([s.indexer for s in refresh_shows] + [s[1].indexer for s in season_updates]):
                indexer_api = indexerApi(indexer)
                self.update_cache.set_last_indexer_update(indexer_api.name)
                logger.info('Updated lastUpdate timestamp for {indexer_name}', indexer_name=indexer_api.name)
            logger.info('Completed scheduling updates on shows')
        else:
            logger.info('Completed scheduling updates on shows, but there was nothing to update')

        self.amActive = False
Exemple #11
0
    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
Exemple #12
0
def xem_refresh(series_obj, force=False):
    """
    Refresh data from xem for a tv show.

    :param indexer_id: int
    """
    if not series_obj or series_obj.series_id < 1:
        return

    indexer_id = series_obj.indexer
    series_id = series_obj.series_id

    MAX_REFRESH_AGE_SECS = 86400  # 1 day

    main_db_con = db.DBConnection()
    rows = main_db_con.select(
        'SELECT last_refreshed FROM xem_refresh WHERE indexer = ? and indexer_id = ?',
        [indexer_id, series_id])
    if rows:
        last_refresh = int(rows[0]['last_refreshed'])
        refresh = int(time.mktime(datetime.datetime.today().timetuple())
                      ) > last_refresh + MAX_REFRESH_AGE_SECS
    else:
        refresh = True

    if refresh or force:
        logger.log(
            u'Looking up XEM scene mapping for show ID {0} on {1}'.format(
                series_id, series_obj.indexer_name), logger.DEBUG)

        # mark refreshed
        main_db_con.upsert(
            'xem_refresh', {
                'last_refreshed':
                int(time.mktime(datetime.datetime.today().timetuple()))
            }, {
                'indexer': indexer_id,
                'indexer_id': series_id
            })

        try:
            if not indexerApi(indexer_id).config.get('xem_origin'):
                logger.log(
                    u'{0} is an unsupported indexer in XEM'.format(
                        indexerApi(indexer_id).name), logger.DEBUG)
                return
            # XEM MAP URL
            url = 'http://thexem.de/map/havemap?origin={0}'.format(
                indexerApi(indexer_id).config['xem_origin'])
            parsed_json = safe_session.get_json(url)
            if not parsed_json or 'result' not in parsed_json or 'success' not in parsed_json[
                    'result'] or 'data' not in parsed_json or str(
                        series_id) not in parsed_json['data']:
                logger.log(
                    u'No XEM data for show ID {0} on {1}'.format(
                        series_id, series_obj.indexer_name), logger.DEBUG)
                return

            # XEM API URL
            url = 'http://thexem.de/map/all?id={0}&origin={1}&destination=scene'.format(
                series_id,
                indexerApi(indexer_id).config['xem_origin'])
            parsed_json = safe_session.get_json(url)
            if not parsed_json or 'result' not in parsed_json or 'success' not in parsed_json[
                    'result']:
                logger.log(
                    u'No XEM data for show ID {0} on {1}'.format(
                        indexer_id, series_obj.indexer_name), logger.DEBUG)
                return

            cl = []
            for entry in parsed_json['data']:
                if 'scene' in entry:
                    cl.append([
                        'UPDATE tv_episodes SET scene_season = ?, scene_episode = ?, scene_absolute_number = ? '
                        'WHERE indexer = ? AND showid = ? AND season = ? AND episode = ?',
                        [
                            entry['scene']['season'],
                            entry['scene']['episode'],
                            entry['scene']['absolute'], indexer_id, series_id,
                            entry[indexerApi(indexer_id).config['xem_origin']]
                            ['season'], entry[indexerApi(
                                indexer_id).config['xem_origin']]['episode']
                        ]
                    ])
                    # Update the absolute_number from xem, but do not set it when it has already been set by tvdb.
                    # We want to prevent doubles and tvdb is leading in that case.
                    cl.append([
                        'UPDATE tv_episodes SET absolute_number = ? '
                        'WHERE indexer = ? AND showid = ? AND season = ? AND episode = ? AND absolute_number = 0 '
                        'AND {absolute_number} NOT IN '
                        '(SELECT absolute_number '
                        'FROM tv_episodes '
                        'WHERE absolute_number = ? AND indexer = ? AND showid = ?)'
                        .format(absolute_number=entry[indexerApi(
                            indexer_id).config['xem_origin']]['absolute']),
                        [
                            entry[indexerApi(
                                indexer_id).config['xem_origin']]['absolute'],
                            indexer_id, series_id, entry[indexerApi(
                                indexer_id).config['xem_origin']]['season'],
                            entry[indexerApi(indexer_id).config['xem_origin']]
                            ['episode'], entry[indexerApi(
                                indexer_id).config['xem_origin']]['absolute'],
                            indexer_id, series_id
                        ]
                    ])
                if 'scene_2' in entry:  # for doubles
                    cl.append([
                        'UPDATE tv_episodes SET scene_season = ?, scene_episode = ?, scene_absolute_number = ? '
                        'WHERE indexer = ? AND showid = ? AND season = ? AND episode = ?',
                        [
                            entry['scene_2']['season'],
                            entry['scene_2']['episode'],
                            entry['scene_2']['absolute'], indexer_id,
                            series_id, entry[indexerApi(
                                indexer_id).config['xem_origin']]['season'],
                            entry[indexerApi(
                                indexer_id).config['xem_origin']]['episode']
                        ]
                    ])

            if cl:
                main_db_con = db.DBConnection()
                main_db_con.mass_action(cl)

        except Exception as e:
            logger.log(
                u'Exception while refreshing XEM data for show ID {0} on {1}: {2}'
                .format(series_id, series_obj.indexer_name,
                        ex(e)), logger.WARNING)
            logger.log(traceback.format_exc(), logger.DEBUG)
Exemple #13
0
    def _ep_data(self, ep_obj):
        """
        Creates an elementTree XML structure for an KODI-style episode.nfo and
        returns the resulting data object.

        show_obj: a Episode instance to create the NFO for
        """
        eps_to_write = [ep_obj] + ep_obj.related_episodes

        series_obj = self._get_show_data(ep_obj.series)
        if not series_obj:
            return None

        if len(eps_to_write) > 1:
            root_node = etree.Element('kodimultiepisode')
        else:
            root_node = etree.Element('episodedetails')

        # write an NFO containing info for all matching episodes
        for ep_to_write in eps_to_write:

            try:
                my_ep = series_obj[ep_to_write.season][ep_to_write.episode]
            except (IndexerEpisodeNotFound, IndexerSeasonNotFound):
                log.info(
                    u'Unable to find episode {ep_num} on {indexer}...'
                    u' has it been removed? Should I delete from db?', {
                        'ep_num': episode_num(ep_to_write.season, ep_to_write.episode),
                        'indexer': indexerApi(ep_obj.series.indexer).name,
                    }
                )
                return None

            if not getattr(my_ep, 'firstaired', None):
                my_ep['firstaired'] = text_type(datetime.date.fromordinal(1))

            if not getattr(my_ep, 'episodename', None):
                log.debug(u'Not generating nfo because the ep has no title')
                return None

            log.debug(u'Creating metadata for episode {0}',
                      episode_num(ep_obj.season, ep_obj.episode))

            if len(eps_to_write) > 1:
                episode = etree.SubElement(root_node, 'episodedetails')
            else:
                episode = root_node

            if getattr(my_ep, 'episodename', None):
                title = etree.SubElement(episode, 'title')
                title.text = my_ep['episodename']

            if getattr(series_obj, 'seriesname', None):
                showtitle = etree.SubElement(episode, 'showtitle')
                showtitle.text = series_obj['seriesname']

            season = etree.SubElement(episode, 'season')
            season.text = text_type(ep_to_write.season)

            episodenum = etree.SubElement(episode, 'episode')
            episodenum.text = text_type(ep_to_write.episode)

            uniqueid = etree.SubElement(episode, 'uniqueid')
            uniqueid.set('type', ep_obj.indexer_name)
            uniqueid.set('default', 'true')
            uniqueid.text = text_type(ep_to_write.indexerid)

            if ep_to_write.airdate != datetime.date.fromordinal(1):
                aired = etree.SubElement(episode, 'aired')
                aired.text = text_type(ep_to_write.airdate)

            if getattr(my_ep, 'overview', None):
                plot = etree.SubElement(episode, 'plot')
                plot.text = my_ep['overview']

            if ep_to_write.season and getattr(series_obj, 'runtime', None):
                runtime = etree.SubElement(episode, 'runtime')
                runtime.text = text_type(series_obj['runtime'])

            if getattr(my_ep, 'airsbefore_season', None):
                displayseason = etree.SubElement(episode, 'displayseason')
                displayseason.text = my_ep['airsbefore_season']

            if getattr(my_ep, 'airsbefore_episode', None):
                displayepisode = etree.SubElement(episode, 'displayepisode')
                displayepisode.text = my_ep['airsbefore_episode']

            if getattr(my_ep, 'filename', None):
                thumb = etree.SubElement(episode, 'thumb')
                thumb.text = my_ep['filename'].strip()

            # watched = etree.SubElement(episode, 'watched')
            # watched.text = 'false'

            if getattr(my_ep, 'rating', None):
                rating = etree.SubElement(episode, 'rating')
                rating.text = text_type(my_ep['rating'])

            if getattr(my_ep, 'writer', None) and isinstance(my_ep['writer'], string_types):
                for writer in self._split_info(my_ep['writer']):
                    cur_writer = etree.SubElement(episode, 'credits')
                    cur_writer.text = writer

            if getattr(my_ep, 'director', None) and isinstance(my_ep['director'], string_types):
                for director in self._split_info(my_ep['director']):
                    cur_director = etree.SubElement(episode, 'director')
                    cur_director.text = director

            if getattr(my_ep, 'gueststars', None) and isinstance(my_ep['gueststars'], string_types):
                for actor in self._split_info(my_ep['gueststars']):
                    cur_actor = etree.SubElement(episode, 'actor')
                    cur_actor_name = etree.SubElement(cur_actor, 'name')
                    cur_actor_name.text = actor

            if getattr(series_obj, '_actors', None):
                for actor in series_obj['_actors']:
                    cur_actor = etree.SubElement(episode, 'actor')

                    if 'name' in actor and actor['name'].strip():
                        cur_actor_name = etree.SubElement(cur_actor, 'name')
                        cur_actor_name.text = actor['name'].strip()
                    else:
                        continue

                    if 'role' in actor and actor['role'].strip():
                        cur_actor_role = etree.SubElement(cur_actor, 'role')
                        cur_actor_role.text = actor['role'].strip()

                    if 'image' in actor and actor['image'].strip():
                        cur_actor_thumb = etree.SubElement(cur_actor, 'thumb')
                        cur_actor_thumb.text = actor['image'].strip()

        # Make it purdy
        helpers.indent_xml(root_node)

        data = etree.ElementTree(root_node)

        return data
Exemple #14
0
    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
Exemple #15
0
    def run(self):
        """Run QueueItemChangeIndexer queue item."""
        step = []

        # Small helper, to reduce code for messaging
        def message_step(new_step):
            step.append(new_step)

            ws.Message(
                'QueueItemShow',
                dict(step=step,
                     oldShow=self.old_show.to_json() if self.old_show else {},
                     newShow=self.new_show.to_json() if self.new_show else {},
                     **self.to_json)).push()

        ShowQueueItem.run(self)

        def get_show_from_slug(slug):
            identifier = SeriesIdentifier.from_slug(slug)
            if not identifier:
                raise ChangeIndexerException(
                    f'Could not create identifier with slug {slug}')

            show = Series.find_by_identifier(identifier)
            return show

        try:
            # Create reference to old show, before starting the remove it.
            self.old_show = get_show_from_slug(self.old_slug)

            # Store needed options.
            self._store_options()

            # Start of removing the old show
            log.info('{id}: Removing {show}', {
                'id': self.old_show.series_id,
                'show': self.old_show.name
            })
            message_step(f'Removing old show {self.old_show.name}')

            # Need to first remove the episodes from the Trakt collection, because we need the list of
            # Episodes from the db to know which eps to remove.
            if app.USE_TRAKT:
                message_step('Removing episodes from trakt collection')
                try:
                    app.trakt_checker_scheduler.action.remove_show_trakt_library(
                        self.old_show)
                except TraktException as error:
                    log.warning(
                        '{id}: Unable to delete show {show} from Trakt.'
                        ' Please remove manually otherwise it will be added again.'
                        ' Error: {error_msg}', {
                            'id': self.old_show.series_id,
                            'show': self.old_show.name,
                            'error_msg': error
                        })
                except Exception as error:
                    log.exception(
                        'Exception occurred while trying to delete show {show}, error: {error',
                        {
                            'show': self.old_show.name,
                            'error': error
                        })

            self.old_show.delete_show(full=False)

            # Send showRemoved to frontend, so we can remove it from localStorage.
            ws.Message('showRemoved', self.old_show.to_json(
                detailed=False)).push()  # Send ws update to client

            # Double check to see if the show really has been removed, else bail.
            if get_show_from_slug(self.old_slug):
                raise ChangeIndexerException(
                    f'Could not create identifier with slug {self.old_slug}')

            # Start adding the new show
            log.info('Starting to add show by {0}',
                     ('show_dir: {0}'.format(self.show_dir) if self.show_dir
                      else 'New slug: {0}'.format(self.new_slug)))

            self.new_show = Series.from_identifier(
                SeriesIdentifier.from_slug(self.new_slug))

            try:
                # Push an update to any open Web UIs through the WebSocket
                message_step('load show from {indexer}'.format(
                    indexer=indexerApi(self.new_show.indexer).name))

                api = self.new_show.identifier.get_indexer_api(self.options)

                if getattr(api[self.new_show.series_id], 'seriesname',
                           None) is None:
                    log.error(
                        'Show in {path} has no name on {indexer}, probably searched with the wrong language.',
                        {
                            'path': self.show_dir,
                            'indexer': indexerApi(self.new_show.indexer).name
                        })

                    ui.notifications.error(
                        'Unable to add show',
                        'Show in {path} has no name on {indexer}, probably the wrong language.'
                        ' Delete .nfo and manually add the correct language.'.
                        format(path=self.show_dir,
                               indexer=indexerApi(self.new_show.indexer).name))
                    self._finish_early()
                    raise SaveSeriesException(
                        'Indexer is missing a showname in this language: {0!r}'
                    )

                self.new_show.load_from_indexer(tvapi=api)

                message_step('load info from imdb')
                self.new_show.load_imdb_info()
            except IndexerException as error:
                log.warning(
                    'Unable to load series from indexer: {0!r}'.format(error))
                raise SaveSeriesException(
                    'Unable to load series from indexer: {0!r}'.format(error))

            try:
                message_step('configure show options')
                self.new_show.configure(self)
            except KeyError as error:
                log.error(
                    'Unable to add show {series_name} due to an error with one of the provided options: {error}',
                    {
                        'series_name': self.new_show.name,
                        'error': error
                    })
                ui.notifications.error(
                    'Unable to add show {series_name} due to an error with one of the provided options: {error}'
                    .format(series_name=self.new_show.name, error=error))
                raise SaveSeriesException(
                    'Unable to add show {series_name} due to an error with one of the provided options: {error}'
                    .format(series_name=self.new_show.name, error=error))

            except Exception as error:
                log.error('Error trying to configure show: {0}', error)
                log.debug(traceback.format_exc())
                raise

            app.showList.append(self.new_show)
            self.new_show.save_to_db()

            try:
                message_step('load episodes from {indexer}'.format(
                    indexer=indexerApi(self.new_show.indexer).name))
                self.new_show.load_episodes_from_indexer(tvapi=api)
                # If we provide a default_status_after through the apiv2 series route options object.
                # set it after we've added the episodes.
                self.new_show.default_ep_status = self.options[
                    'default_status_after'] or app.STATUS_DEFAULT_AFTER

            except IndexerException as error:
                log.warning(
                    'Unable to load series episodes from indexer: {0!r}'.
                    format(error))
                raise SaveSeriesException(
                    'Unable to load series episodes from indexer: {0!r}'.
                    format(error))

            message_step('create metadata in show folder')
            self.new_show.write_metadata()
            self.new_show.update_metadata()
            self.new_show.populate_cache()
            build_name_cache(self.new_show)  # update internal name cache
            self.new_show.flush_episodes()
            self.new_show.sync_trakt()

            message_step('add scene numbering')
            self.new_show.add_scene_numbering()

            if self.show_dir:
                # If a show dir was passed, this was added as an existing show.
                # For new shows we shouldn't have any files on disk.
                message_step('refresh episodes from disk')
                try:
                    app.show_queue_scheduler.action.refreshShow(self.new_show)
                except CantRefreshShowException as error:
                    log.warning(
                        'Unable to rescan episodes from disk: {0!r}'.format(
                            error))

        except (ChangeIndexerException, SaveSeriesException) as error:
            log.warning('Unable to add series: {0!r}'.format(error))
            self.success = False
            self._finish_early()
            log.debug(traceback.format_exc())

        default_status = self.options['default_status'] or app.STATUS_DEFAULT
        if statusStrings[default_status] == 'Wanted':
            message_step('trigger backlog search')
            app.backlog_search_scheduler.action.search_backlog([self.new_show])

        self.success = True

        ws.Message('showAdded', self.new_show.to_json(
            detailed=False)).push()  # Send ws update to client
        message_step('finished')
        self.finish()
Exemple #16
0
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
Exemple #17
0
    def run(self):

        ShowQueueItem.run(self)

        log.info('Starting to add show by {0}',
                 ('show_dir: {0}'.format(self.show_dir) if self.show_dir else
                  'Indexer Id: {0}'.format(self.indexer_id)))

        # make sure the Indexer IDs are valid
        try:
            l_indexer_api_params = indexerApi(self.indexer).api_params.copy()
            if self.lang:
                l_indexer_api_params['language'] = self.lang

            log.info(
                '{indexer_name}: {indexer_params!r}', {
                    'indexer_name': indexerApi(self.indexer).name,
                    'indexer_params': l_indexer_api_params
                })

            indexer_api = indexerApi(
                self.indexer).indexer(**l_indexer_api_params)
            s = indexer_api[self.indexer_id]

            # Let's try to create the show Dir if it's not provided. This way we force the show dir
            # to build build using the Indexers provided series name
            if not self.show_dir and self.root_dir:
                show_name = get_showname_from_indexer(self.indexer,
                                                      self.indexer_id,
                                                      self.lang)
                if show_name:
                    self.show_dir = os.path.join(self.root_dir,
                                                 sanitize_filename(show_name))
                    dir_exists = make_dir(self.show_dir)
                    if not dir_exists:
                        log.info(
                            "Unable to create the folder {0}, can't add the show",
                            self.show_dir)
                        return

                    chmod_as_parent(self.show_dir)
                else:
                    log.info("Unable to get a show {0}, can't add the show",
                             self.show_dir)
                    return

            # this usually only happens if they have an NFO in their show dir which gave us a Indexer ID that
            # has no proper english version of the show
            if getattr(s, 'seriesname', None) is None:
                log.error(
                    'Show in {path} has no name on {indexer}, probably searched with the wrong language.',
                    {
                        'path': self.show_dir,
                        'indexer': indexerApi(self.indexer).name
                    })

                ui.notifications.error(
                    'Unable to add show',
                    'Show in {path} has no name on {indexer}, probably the wrong language.'
                    ' Delete .nfo and manually add the correct language.'.
                    format(path=self.show_dir,
                           indexer=indexerApi(self.indexer).name))
                self._finishEarly()
                return

            # Check if we can already find this show in our current showList.
            try:
                check_existing_shows(s, self.indexer)
            except IndexerShowAlreadyInLibrary as error:
                log.warning(
                    'Could not add the show {series}, as it already is in your library.'
                    ' Error: {error}', {
                        'series': s['seriesname'],
                        'error': error
                    })
                ui.notifications.error('Unable to add show',
                                       'reason: {0}'.format(error))
                self._finishEarly()

                # Clean up leftover if the newly created directory is empty.
                delete_empty_folders(self.show_dir)
                return

        # TODO: Add more specific indexer exceptions, that should provide the user with some accurate feedback.
        except IndexerShowNotFound as error:
            log.warning(
                '{id}: Unable to look up the show in {path} using id {id} on {indexer}.'
                ' Delete metadata files from the folder and try adding it again.\n'
                'With error: {error}', {
                    'id': self.indexer_id,
                    'path': self.show_dir,
                    'indexer': indexerApi(self.indexer).name,
                    'error': error
                })
            ui.notifications.error(
                'Unable to add show',
                'Unable to look up the show in {path} using id {id} on {indexer}.'
                ' Delete metadata files from the folder and try adding it again.'
                .format(path=self.show_dir,
                        id=self.indexer_id,
                        indexer=indexerApi(self.indexer).name))
            self._finishEarly()
            return
        except IndexerShowNotFoundInLanguage as error:
            log.warning(
                '{id}: Data retrieved from {indexer} was incomplete. The indexer does not provide'
                ' show information in the searched language {language}. Aborting: {error_msg}',
                {
                    'id': self.indexer_id,
                    'indexer': indexerApi(self.indexer).name,
                    'language': error.language,
                    'error_msg': error
                })
            ui.notifications.error(
                'Error adding show!',
                'Unable to add show {indexer_id} on {indexer} with this language: {language}'
                .format(indexer_id=self.indexer_id,
                        indexer=indexerApi(self.indexer).name,
                        language=error.language))
            self._finishEarly()
            return
        except Exception as error:
            log.error(
                '{id}: Error while loading information from indexer {indexer}. Error: {error!r}',
                {
                    'id': self.indexer_id,
                    'indexer': indexerApi(self.indexer).name,
                    'error': error
                })
            ui.notifications.error(
                'Unable to add show',
                'Unable to look up the show in {path} on {indexer} using ID {id}.'
                .format(path=self.show_dir,
                        indexer=indexerApi(self.indexer).name,
                        id=self.indexer_id))
            self._finishEarly()
            return

        try:
            newShow = Series(self.indexer, self.indexer_id, self.lang)
            newShow.load_from_indexer(indexer_api)

            self.show = newShow

            # set up initial values
            self.show.location = self.show_dir
            self.show.subtitles = self.subtitles if self.subtitles is not None else app.SUBTITLES_DEFAULT
            self.show.quality = self.quality if self.quality else app.QUALITY_DEFAULT
            self.show.season_folders = self.season_folders if self.season_folders is not None \
                else app.SEASON_FOLDERS_DEFAULT
            self.show.anime = self.anime if self.anime is not None else app.ANIME_DEFAULT
            self.show.scene = self.scene if self.scene is not None else app.SCENE_DEFAULT
            self.show.paused = self.paused if self.paused is not None else False

            # set up default new/missing episode status
            log.info(
                'Setting all previously aired episodes to the specified status: {status}',
                {'status': statusStrings[self.default_status]})
            self.show.default_ep_status = self.default_status

            if self.show.anime:
                self.show.release_groups = BlackAndWhiteList(self.show)
                if self.blacklist:
                    self.show.release_groups.set_black_keywords(self.blacklist)
                if self.whitelist:
                    self.show.release_groups.set_white_keywords(self.whitelist)

        except IndexerException as error:
            log.error(
                'Unable to add show due to an error with {indexer}: {error}', {
                    'indexer': indexerApi(self.indexer).name,
                    'error': error
                })
            ui.notifications.error(
                'Unable to add {series_name} due to an error with {indexer_name}'
                .format(series_name=self.show.name if self.show else 'show',
                        indexer_name=indexerApi(self.indexer).name))
            self._finishEarly()
            return

        except MultipleShowObjectsException:
            log.warning(
                'The show in {show_dir} is already in your show list, skipping',
                {'show_dir': self.show_dir})
            ui.notifications.error(
                'Show skipped',
                'The show in {show_dir} is already in your show list'.format(
                    show_dir=self.show_dir))
            self._finishEarly()
            return

        except Exception as error:
            log.error('Error trying to add show: {0}', error)
            log.debug(traceback.format_exc())
            self._finishEarly()
            raise

        log.debug('Retrieving show info from IMDb')
        try:
            self.show.load_imdb_info()
        except ImdbAPIError as error:
            log.info('Something wrong on IMDb api: {0}', error)
        except RequestException as error:
            log.warning('Error loading IMDb info: {0}', error)

        try:
            log.debug('{id}: Saving new show to database',
                      {'id': self.show.series_id})
            self.show.save_to_db()
        except Exception as error:
            log.error('Error saving the show to the database: {0}', error)
            log.debug(traceback.format_exc())
            self._finishEarly()
            raise

        # add it to the show list
        app.showList.append(self.show)

        try:
            self.show.load_episodes_from_indexer(tvapi=indexer_api)
        except Exception as error:
            log.error(
                'Error with {indexer}, not creating episode list: {error}', {
                    'indexer': indexerApi(self.show.indexer).name,
                    'error': error
                })
            log.debug(traceback.format_exc())

        # update internal name cache
        name_cache.build_name_cache(self.show)

        try:
            self.show.load_episodes_from_dir()
        except Exception as error:
            log.error('Error searching dir for episodes: {0}', error)
            log.debug(traceback.format_exc())

        # if they set default ep status to WANTED then run the backlog to search for episodes
        if self.show.default_ep_status == WANTED:
            log.info(
                'Launching backlog for this show since its episodes are WANTED'
            )
            wanted_segments = self.show.get_wanted_segments()
            for season, segment in viewitems(wanted_segments):
                cur_backlog_queue_item = BacklogQueueItem(self.show, segment)
                app.forced_search_queue_scheduler.action.add_item(
                    cur_backlog_queue_item)

                log.info('Sending forced backlog for {show} season {season}'
                         ' because some episodes were set to wanted'.format(
                             show=self.show.name, season=season))

        self.show.write_metadata()
        self.show.update_metadata()
        self.show.populate_cache()

        self.show.flush_episodes()

        if app.USE_TRAKT:
            # if there are specific episodes that need to be added by trakt
            app.trakt_checker_scheduler.action.manage_new_show(self.show)

            # add show to trakt.tv library
            if app.TRAKT_SYNC:
                app.trakt_checker_scheduler.action.add_show_trakt_library(
                    self.show)

            if app.TRAKT_SYNC_WATCHLIST:
                log.info('update watchlist')
                notifiers.trakt_notifier.update_watchlist(show_obj=self.show)

        # Load XEM data to DB for show
        scene_numbering.xem_refresh(self.show, force=True)

        # check if show has XEM mapping so we can determine if searches
        # should go by scene numbering or indexer numbering. Warn the user.
        if not self.scene and scene_numbering.get_xem_numbering_for_show(
                self.show):
            log.warning(
                '{id}: while adding the show {title} we noticed thexem.de has an episode mapping available'
                '\nyou might want to consider enabling the scene option for this show.',
                {
                    'id': self.show.series_id,
                    'title': self.show.name
                })
            ui.notifications.message(
                'consider enabling scene for this show',
                'for show {title} you might want to consider enabling the scene option'
                .format(title=self.show.name))

        # After initial add, set to default_status_after.
        self.show.default_ep_status = self.default_status_after

        try:
            log.debug('{id}: Saving new show info to database',
                      {'id': self.show.series_id})
            self.show.save_to_db()
        except Exception as error:
            log.warning(
                '{id}: Error saving new show info to database: {error_msg}', {
                    'id': self.show.series_id,
                    'error_msg': error
                })
            log.error(traceback.format_exc())

        # Send ws update to client
        ws.Message('showAdded', self.show.to_json(detailed=False)).push()

        self.finish()
Exemple #18
0
    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)
Exemple #19
0
    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
Exemple #20
0
def check_existing_shows(indexed_show, indexer):
    """Check if the searched show already exists in the current library.

    :param indexed_show: (Indexer Show object) The indexed show from -for example- tvdb. It might already have some
    externals like imdb_id which can be used to search at tmdb, tvmaze or trakt.
    :param indexer: (int) The indexer id, which has been used to search the indexed_show with.
    :return: Raises the exception IndexerShowAlreadyInLibrary() when the show is already in your library.
    """
    # For this show let's get all externals, and use them.
    mappings = {
        indexer: indexerConfig[indexer]['mapped_to']
        for indexer in indexerConfig
    }
    other_indexers = [
        mapped_indexer for mapped_indexer in mappings
        if mapped_indexer != indexer
    ]

    # This will query other indexer api's.
    new_show_externals = get_externals(indexer=indexer,
                                       indexed_show=indexed_show)

    # Iterate through all shows in library, and see if one of our externals matches it's indexer_id
    # Or one of it's externals.
    for show in app.showList:

        # Check if the new shows indexer id matches the external for the show
        # in library
        if show.externals.get(mappings[indexer]
                              ) and indexed_show['id'] == show.externals.get(
                                  mappings[indexer]):
            log.debug(u'Show already in database. [{id}] {name}', {
                'name': show.name,
                'id': indexed_show['id']
            })
            raise IndexerShowAlreadyInLibrary(
                'The show {0} has already been added by the indexer {1}. '
                'Please remove the show, before you can add it through {2}.'.
                format(show.name,
                       indexerApi(show.indexer).name,
                       indexerApi(indexer).name))

        for new_show_external_key in list(new_show_externals):
            if show.indexer not in other_indexers:
                continue

            # Check if one of the new shows externals matches one of the
            # externals for the show in library.
            if not new_show_externals.get(
                    new_show_external_key) or not show.externals.get(
                        new_show_external_key):
                continue

            if new_show_externals.get(
                    new_show_external_key) == show.externals.get(
                        new_show_external_key):
                log.debug(
                    u'Show already in database under external ID ({existing})'
                    u' for ({id}) {name}', {
                        'name': show.name,
                        'id': show.externals.get(new_show_external_key),
                        'existing': new_show_external_key,
                    })
                raise IndexerShowAlreadyInLibrary(
                    'The show {0} has already been added by the indexer {1}. '
                    'Please remove the show, before you can add it through {2}.'
                    .format(show.name,
                            indexerApi(show.indexer).name,
                            indexerApi(indexer).name))
Exemple #21
0
    def run(self):

        ShowQueueItem.run(self)

        log.info('Starting to add show by {0}',
                 ('show_dir: {0}'.format(self.show_dir) if self.show_dir else
                  'Indexer Id: {0}'.format(self.indexer_id)))

        show_slug = indexer_id_to_slug(self.indexer, self.indexer_id)
        series = Series.from_identifier(SeriesIdentifier.from_slug(show_slug))

        step = []

        # Small helper, to reduce code for messaging
        def message_step(new_step):
            step.append(new_step)
            ws.Message('QueueItemShowAdd', dict(step=step,
                                                **self.to_json)).push()

        try:
            try:
                # Push an update to any open Web UIs through the WebSocket
                message_step('load show from {indexer}'.format(
                    indexer=indexerApi(self.indexer).name))

                api = series.identifier.get_indexer_api(self.options)

                if getattr(api[self.indexer_id], 'seriesname', None) is None:
                    log.error(
                        'Show in {path} has no name on {indexer}, probably searched with the wrong language.',
                        {
                            'path': self.show_dir,
                            'indexer': indexerApi(self.indexer).name
                        })

                    ui.notifications.error(
                        'Unable to add show',
                        'Show in {path} has no name on {indexer}, probably the wrong language.'
                        ' Delete .nfo and manually add the correct language.'.
                        format(path=self.show_dir,
                               indexer=indexerApi(self.indexer).name))
                    self._finish_early()
                    raise SaveSeriesException(
                        'Indexer is missing a showname in this language: {0!r}'
                    )

                series.load_from_indexer(tvapi=api)

                message_step('load info from imdb')
                series.load_imdb_info()
            except IndexerException as error:
                log.warning(
                    'Unable to load series from indexer: {0!r}'.format(error))
                raise SaveSeriesException(
                    'Unable to load series from indexer: {0!r}'.format(error))

            message_step('check if show is already added')

            try:
                message_step('configure show options')
                series.configure(self)
            except KeyError as error:
                log.error(
                    'Unable to add show {series_name} due to an error with one of the provided options: {error}',
                    {
                        'series_name': series.name,
                        'error': error
                    })
                ui.notifications.error(
                    'Unable to add show {series_name} due to an error with one of the provided options: {error}'
                    .format(series_name=series.name, error=error))
                raise SaveSeriesException(
                    'Unable to add show {series_name} due to an error with one of the provided options: {error}'
                    .format(series_name=series.name, error=error))

            except Exception as error:
                log.error('Error trying to configure show: {0}', error)
                log.debug(traceback.format_exc())
                raise

            app.showList.append(series)
            series.save_to_db()

            try:
                message_step('load episodes from {indexer}'.format(
                    indexer=indexerApi(self.indexer).name))
                series.load_episodes_from_indexer(tvapi=api)
                # If we provide a default_status_after through the apiv2 series route options object.
                # set it after we've added the episodes.
                self.default_ep_status = self.options[
                    'default_status_after'] or app.STATUS_DEFAULT_AFTER

            except IndexerException as error:
                log.warning(
                    'Unable to load series episodes from indexer: {0!r}'.
                    format(error))
                raise SaveSeriesException(
                    'Unable to load series episodes from indexer: {0!r}'.
                    format(error))

            message_step('create metadata in show folder')
            series.write_metadata()
            series.update_metadata()
            series.populate_cache()
            build_name_cache(series)  # update internal name cache
            series.flush_episodes()
            series.sync_trakt()

            message_step('add scene numbering')
            series.add_scene_numbering()

        except SaveSeriesException as error:
            log.warning('Unable to add series: {0!r}'.format(error))
            self.success = False
            self._finish_early()
            log.debug(traceback.format_exc())

        default_status = self.options['default_status'] or app.STATUS_DEFAULT
        if statusStrings[default_status] == 'Wanted':
            message_step('trigger backlog search')
            app.backlog_search_scheduler.action.search_backlog([series])

        self.success = True

        ws.Message(
            'showAdded',
            series.to_json(detailed=False)).push()  # Send ws update to client
        message_step('finished')
        self.finish()
Exemple #22
0
    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
Exemple #23
0
    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