Beispiel #1
0
def test_pick_result(p, app_config, create_search_result, search_provider, create_tvshow, tvepisode, caplog):

    caplog.set_level(logging.DEBUG, logger='medusa')

    # Given
    config_attrs = p.get('config', {})
    for attr, value in iteritems(config_attrs):
        app_config(attr, value)

    series_attrs = p.get('series', {})
    series = create_tvshow(**series_attrs)

    provider_attrs = p.get('provider', {})

    make_result = functools.partial(
        create_search_result,
        provider=search_provider(**provider_attrs),
        series=series,
        episodes=[tvepisode]
    )

    results = [make_result(**item) for item in p['results']]
    expected = p['expected']
    if isinstance(expected, int):
        expected = results[expected]

    # When
    actual = pick_result(results)

    # Then
    assert expected == actual
Beispiel #2
0
def test_pick_result(p, app_config, create_search_result, search_provider, create_tvshow, tvepisode, caplog):

    caplog.set_level(logging.DEBUG, logger='medusa')

    # Given
    config_attrs = p.get('config', {})
    for attr, value in iteritems(config_attrs):
        app_config(attr, value)

    series_attrs = p.get('series', {})
    series = create_tvshow(**series_attrs)

    provider_attrs = p.get('provider', {})

    make_result = functools.partial(
        create_search_result,
        provider=search_provider(**provider_attrs),
        series=series,
        episodes=[tvepisode]
    )

    results = [make_result(**item) for item in p['results']]
    expected = p['expected']
    if isinstance(expected, int):
        expected = results[expected]

    # When
    actual = pick_result(results)

    # Then
    assert expected == actual
Beispiel #3
0
    def _get_proper_results(self):  # pylint: disable=too-many-locals, too-many-branches, too-many-statements
        """Retrieve a list of recently aired episodes, and search for these episodes in the different providers."""
        propers = {}

        # For each provider get the list of propers
        original_thread_name = threading.currentThread().name
        providers = enabled_providers('backlog')

        search_date = datetime.datetime.today() - datetime.timedelta(
            days=app.PROPERS_SEARCH_DAYS)
        main_db_con = db.DBConnection()
        if not app.POSTPONE_IF_NO_SUBS:
            # Get the recently aired (last 2 days) shows from DB
            recently_aired = main_db_con.select(
                'SELECT indexer, showid, season, episode, status, airdate'
                ' FROM tv_episodes'
                ' WHERE airdate >= ?'
                ' AND status = ?', [search_date.toordinal(), DOWNLOADED])
        else:
            # Get recently subtitled episodes (last 2 days) from DB
            # Episode status becomes downloaded only after found subtitles
            last_subtitled = search_date.strftime(History.date_format)
            recently_aired = main_db_con.select(
                'SELECT indexer_id AS indexer, showid, season, episode FROM history '
                'WHERE date >= ? AND action = ?', [last_subtitled, SUBTITLED])

        if not recently_aired:
            log.info('No recently aired new episodes, nothing to search for')
            return []

        # Loop through the providers, and search for releases
        for cur_provider in providers:
            threading.currentThread().name = '{thread} :: [{provider}]'.format(
                thread=original_thread_name, provider=cur_provider.name)

            log.info('Searching for any new PROPER releases from {provider}',
                     {'provider': cur_provider.name})

            try:
                cur_propers = cur_provider.find_propers(recently_aired)
            except AuthException as e:
                log.debug('Authentication error: {error}', {'error': ex(e)})
                continue

            # if they haven't been added by a different provider than add the proper to the list
            for proper in cur_propers:
                name = self._sanitize_name(proper.name)
                if name not in propers:
                    log.debug('Found new possible proper result: {name}',
                              {'name': proper.name})
                    propers[name] = proper

        threading.currentThread().name = original_thread_name

        # take the list of unique propers and get it sorted by
        sorted_propers = sorted(list(itervalues(propers)),
                                key=operator.attrgetter('date'),
                                reverse=True)
        final_propers = []

        # Keep only items from last PROPER_SEARCH_DAYS setting in processed propers:
        latest_proper = datetime.datetime.now() - datetime.timedelta(
            days=app.PROPERS_SEARCH_DAYS)
        self.processed_propers = [
            p for p in self.processed_propers if p.get('date') >= latest_proper
        ]

        # Get proper names from processed propers
        processed_propers_names = [
            proper.get('name') for proper in self.processed_propers
            if proper.get('name')
        ]

        for cur_proper in sorted_propers:

            if not self.ignore_processed_propers and cur_proper.name in processed_propers_names:
                log.debug(u'Proper already processed. Skipping: {proper_name}',
                          {'proper_name': cur_proper.name})
                continue

            try:
                cur_proper.parse_result = NameParser().parse(cur_proper.name)
            except (InvalidNameException, InvalidShowException) as error:
                log.debug('{error}', {'error': error})
                continue

            if not cur_proper.parse_result.proper_tags:
                log.info('Skipping non-proper: {name}',
                         {'name': cur_proper.name})
                continue

            if not cur_proper.series.episodes.get(cur_proper.parse_result.season_number) or \
                    any([ep for ep in cur_proper.parse_result.episode_numbers
                         if not cur_proper.series.episodes[cur_proper.parse_result.season_number].get(ep)]):
                log.info('Skipping proper for wrong season/episode: {name}',
                         {'name': cur_proper.name})
                continue

            log.debug(
                'Proper tags for {proper}: {tags}', {
                    'proper': cur_proper.name,
                    'tags': cur_proper.parse_result.proper_tags
                })

            if not cur_proper.parse_result.series_name:
                log.debug('Ignoring invalid show: {name}',
                          {'name': cur_proper.name})
                if cur_proper.name not in processed_propers_names:
                    self.processed_propers.append({
                        'name': cur_proper.name,
                        'date': cur_proper.date
                    })
                continue

            if not cur_proper.parse_result.episode_numbers:
                log.debug('Ignoring full season instead of episode: {name}',
                          {'name': cur_proper.name})
                if cur_proper.name not in processed_propers_names:
                    self.processed_propers.append({
                        'name': cur_proper.name,
                        'date': cur_proper.date
                    })
                continue

            log.debug(
                'Successful match! Matched {original_name} to show {new_name}',
                {
                    'original_name': cur_proper.parse_result.original_name,
                    'new_name': cur_proper.parse_result.series.name
                })

            # Map the indexerid in the db to the show's indexerid
            cur_proper.indexerid = cur_proper.parse_result.series.indexerid

            # Map the indexer in the db to the show's indexer
            cur_proper.indexer = cur_proper.parse_result.series.indexer

            # Map our Proper instance
            cur_proper.series = cur_proper.parse_result.series
            cur_proper.actual_season = cur_proper.parse_result.season_number \
                if cur_proper.parse_result.season_number is not None else 1
            cur_proper.actual_episodes = cur_proper.parse_result.episode_numbers
            cur_proper.release_group = cur_proper.parse_result.release_group
            cur_proper.version = cur_proper.parse_result.version
            cur_proper.quality = cur_proper.parse_result.quality
            cur_proper.content = None
            cur_proper.proper_tags = cur_proper.parse_result.proper_tags

            # filter release, in this case, it's just a quality gate. As we only send one result.
            wanted_results = filter_results(cur_proper)
            best_result = pick_result(wanted_results)

            if not best_result:
                log.info('Rejected proper: {name}', {'name': cur_proper.name})
                if cur_proper.name not in processed_propers_names:
                    self.processed_propers.append({
                        'name': cur_proper.name,
                        'date': cur_proper.date
                    })
                continue

            # only get anime proper if it has release group and version
            if best_result.series.is_anime:
                if not best_result.release_group and best_result.version == -1:
                    log.info(
                        'Ignoring proper without release group and version: {name}',
                        {'name': best_result.name})
                    if cur_proper.name not in processed_propers_names:
                        self.processed_propers.append({
                            'name': cur_proper.name,
                            'date': cur_proper.date
                        })
                    continue

            # check if we have the episode as DOWNLOADED
            main_db_con = db.DBConnection()
            sql_results = main_db_con.select(
                'SELECT quality, release_name '
                'FROM tv_episodes WHERE indexer = ? '
                'AND showid = ? AND season = ? '
                'AND episode = ? AND status = ?', [
                    best_result.indexer, best_result.series.indexerid,
                    best_result.actual_season, best_result.actual_episodes[0],
                    DOWNLOADED
                ])
            if not sql_results:
                log.info(
                    "Ignoring proper because this episode doesn't have 'DOWNLOADED' status: {name}",
                    {'name': best_result.name})
                continue

            # only keep the proper if we have already downloaded an episode with the same quality
            old_quality = int(sql_results[0]['quality'])
            if old_quality != best_result.quality:
                log.info(
                    'Ignoring proper because quality is different: {name}',
                    {'name': best_result.name})
                if cur_proper.name not in processed_propers_names:
                    self.processed_propers.append({
                        'name': cur_proper.name,
                        'date': cur_proper.date
                    })
                continue

            # only keep the proper if we have already downloaded an episode with the same codec
            release_name = sql_results[0]['release_name']
            if release_name:
                release_name_guess = NameParser()._parse_string(release_name)
                current_codec = release_name_guess.video_codec

                # Ignore proper if codec differs from downloaded release codec
                if all([
                        current_codec, best_result.parse_result.video_codec,
                        best_result.parse_result.video_codec != current_codec
                ]):
                    log.info(
                        'Ignoring proper because codec is different: {name}',
                        {'name': best_result.name})
                    if best_result.name not in processed_propers_names:
                        self.processed_propers.append({
                            'name': best_result.name,
                            'date': best_result.date
                        })
                    continue

                streaming_service = release_name_guess.guess.get(
                    u'streaming_service')
                # Ignore proper if streaming service differs from downloaded release streaming service
                if best_result.parse_result.guess.get(
                        u'streaming_service') != streaming_service:
                    log.info(
                        'Ignoring proper because streaming service is different: {name}',
                        {'name': best_result.name})
                    if best_result.name not in processed_propers_names:
                        self.processed_propers.append({
                            'name': best_result.name,
                            'date': best_result.date
                        })
                    continue
            else:
                log.debug(
                    "Coudn't find a release name in database. Skipping codec comparison for: {name}",
                    {'name': best_result.name})

            # check if we actually want this proper (if it's the right release group and a higher version)
            if best_result.series.is_anime:
                main_db_con = db.DBConnection()
                sql_results = main_db_con.select(
                    'SELECT release_group, version '
                    'FROM tv_episodes WHERE indexer = ? AND showid = ? '
                    'AND season = ? AND episode = ?', [
                        best_result.indexer, best_result.series.indexerid,
                        best_result.actual_season,
                        best_result.actual_episodes[0]
                    ])

                old_version = int(sql_results[0]['version'])
                old_release_group = (sql_results[0]['release_group'])

                if -1 < old_version < best_result.version:
                    log.info(
                        'Found new anime version {new} to replace existing version {old}: {name}',
                        {
                            'old': old_version,
                            'new': best_result.version,
                            'name': best_result.name
                        })
                else:
                    log.info(
                        'Ignoring proper with the same or lower version: {name}',
                        {'name': best_result.name})
                    if cur_proper.name not in processed_propers_names:
                        self.processed_propers.append({
                            'name': best_result.name,
                            'date': best_result.date
                        })
                    continue

                if old_release_group != best_result.release_group:
                    log.info(
                        'Ignoring proper from release group {new} instead of current group {old}',
                        {
                            'new': best_result.release_group,
                            'old': old_release_group
                        })
                    if best_result.name not in processed_propers_names:
                        self.processed_propers.append({
                            'name': best_result.name,
                            'date': best_result.date
                        })
                    continue

            # if the show is in our list and there hasn't been a proper already added for that particular episode
            # then add it to our list of propers
            if best_result.indexerid != -1 and (
                    best_result.indexerid, best_result.actual_season,
                    best_result.actual_episodes) not in list(
                        map(
                            operator.attrgetter('indexerid', 'actual_season',
                                                'actual_episodes'),
                            final_propers)):
                log.info('Found a desired proper: {name}',
                         {'name': best_result.name})
                final_propers.append(best_result)

            if best_result.name not in processed_propers_names:
                self.processed_propers.append({
                    'name': best_result.name,
                    'date': best_result.date
                })

        return final_propers
Beispiel #4
0
    def _get_proper_results(self):  # pylint: disable=too-many-locals, too-many-branches, too-many-statements
        """Retrieve a list of recently aired episodes, and search for these episodes in the different providers."""
        propers = {}

        # For each provider get the list of propers
        original_thread_name = threading.currentThread().name
        providers = enabled_providers('backlog')

        search_date = datetime.datetime.today() - datetime.timedelta(days=app.PROPERS_SEARCH_DAYS)
        main_db_con = db.DBConnection()
        if not app.POSTPONE_IF_NO_SUBS:
            # Get the recently aired (last 2 days) shows from DB
            recently_aired = main_db_con.select(
                'SELECT indexer, showid, season, episode, status, airdate'
                ' FROM tv_episodes'
                ' WHERE airdate >= ?'
                ' AND status = ?',
                [search_date.toordinal(), DOWNLOADED]
            )
        else:
            # Get recently subtitled episodes (last 2 days) from DB
            # Episode status becomes downloaded only after found subtitles
            last_subtitled = search_date.strftime(History.date_format)
            recently_aired = main_db_con.select('SELECT indexer_id AS indexer, showid, season, episode FROM history '
                                                'WHERE date >= ? AND action = ?', [last_subtitled, SUBTITLED])

        if not recently_aired:
            log.info('No recently aired new episodes, nothing to search for')
            return []

        # Loop through the providers, and search for releases
        for cur_provider in providers:
            threading.currentThread().name = '{thread} :: [{provider}]'.format(thread=original_thread_name,
                                                                               provider=cur_provider.name)

            log.info('Searching for any new PROPER releases from {provider}', {'provider': cur_provider.name})

            try:
                cur_propers = cur_provider.find_propers(recently_aired)
            except AuthException as e:
                log.debug('Authentication error: {error}', {'error': ex(e)})
                continue

            # if they haven't been added by a different provider than add the proper to the list
            for proper in cur_propers:
                name = self._sanitize_name(proper.name)
                if name not in propers:
                    log.debug('Found new possible proper result: {name}', {'name': proper.name})
                    propers[name] = proper

        threading.currentThread().name = original_thread_name

        # take the list of unique propers and get it sorted by
        sorted_propers = sorted(list(itervalues(propers)), key=operator.attrgetter('date'), reverse=True)
        final_propers = []

        # Keep only items from last PROPER_SEARCH_DAYS setting in processed propers:
        latest_proper = datetime.datetime.now() - datetime.timedelta(days=app.PROPERS_SEARCH_DAYS)
        self.processed_propers = [p for p in self.processed_propers if p.get('date') >= latest_proper]

        # Get proper names from processed propers
        processed_propers_names = [proper.get('name') for proper in self.processed_propers if proper.get('name')]

        for cur_proper in sorted_propers:

            if not self.ignore_processed_propers and cur_proper.name in processed_propers_names:
                log.debug(u'Proper already processed. Skipping: {proper_name}', {'proper_name': cur_proper.name})
                continue

            try:
                cur_proper.parse_result = NameParser().parse(cur_proper.name)
            except (InvalidNameException, InvalidShowException) as error:
                log.debug('{error}', {'error': error})
                continue

            if not cur_proper.parse_result.proper_tags:
                log.info('Skipping non-proper: {name}', {'name': cur_proper.name})
                continue

            if not cur_proper.series.episodes.get(cur_proper.parse_result.season_number) or \
                    any([ep for ep in cur_proper.parse_result.episode_numbers
                         if not cur_proper.series.episodes[cur_proper.parse_result.season_number].get(ep)]):
                log.info('Skipping proper for wrong season/episode: {name}', {'name': cur_proper.name})
                continue

            log.debug('Proper tags for {proper}: {tags}', {
                'proper': cur_proper.name,
                'tags': cur_proper.parse_result.proper_tags
            })

            if not cur_proper.parse_result.series_name:
                log.debug('Ignoring invalid show: {name}', {'name': cur_proper.name})
                if cur_proper.name not in processed_propers_names:
                    self.processed_propers.append({'name': cur_proper.name, 'date': cur_proper.date})
                continue

            if not cur_proper.parse_result.episode_numbers:
                log.debug('Ignoring full season instead of episode: {name}', {'name': cur_proper.name})
                if cur_proper.name not in processed_propers_names:
                    self.processed_propers.append({'name': cur_proper.name, 'date': cur_proper.date})
                continue

            log.debug('Successful match! Matched {original_name} to show {new_name}',
                      {'original_name': cur_proper.parse_result.original_name,
                       'new_name': cur_proper.parse_result.series.name
                       })

            # Map the indexerid in the db to the show's indexerid
            cur_proper.indexerid = cur_proper.parse_result.series.indexerid

            # Map the indexer in the db to the show's indexer
            cur_proper.indexer = cur_proper.parse_result.series.indexer

            # Map our Proper instance
            cur_proper.series = cur_proper.parse_result.series
            cur_proper.actual_season = cur_proper.parse_result.season_number \
                if cur_proper.parse_result.season_number is not None else 1
            cur_proper.actual_episodes = cur_proper.parse_result.episode_numbers
            cur_proper.release_group = cur_proper.parse_result.release_group
            cur_proper.version = cur_proper.parse_result.version
            cur_proper.quality = cur_proper.parse_result.quality
            cur_proper.content = None
            cur_proper.proper_tags = cur_proper.parse_result.proper_tags

            # filter release, in this case, it's just a quality gate. As we only send one result.
            wanted_results = filter_results(cur_proper)
            best_result = pick_result(wanted_results)

            if not best_result:
                log.info('Rejected proper: {name}', {'name': cur_proper.name})
                if cur_proper.name not in processed_propers_names:
                    self.processed_propers.append({'name': cur_proper.name, 'date': cur_proper.date})
                continue

            # only get anime proper if it has release group and version
            if best_result.series.is_anime:
                if not best_result.release_group and best_result.version == -1:
                    log.info('Ignoring proper without release group and version: {name}', {'name': best_result.name})
                    if cur_proper.name not in processed_propers_names:
                        self.processed_propers.append({'name': cur_proper.name, 'date': cur_proper.date})
                    continue

            # check if we have the episode as DOWNLOADED
            main_db_con = db.DBConnection()
            sql_results = main_db_con.select('SELECT quality, release_name '
                                             'FROM tv_episodes WHERE indexer = ? '
                                             'AND showid = ? AND season = ? '
                                             'AND episode = ? AND status = ?',
                                             [best_result.indexer,
                                              best_result.series.indexerid,
                                              best_result.actual_season,
                                              best_result.actual_episodes[0],
                                              DOWNLOADED])
            if not sql_results:
                log.info("Ignoring proper because this episode doesn't have 'DOWNLOADED' status: {name}", {
                    'name': best_result.name
                })
                continue

            # only keep the proper if we have already downloaded an episode with the same quality
            old_quality = int(sql_results[0]['quality'])
            if old_quality != best_result.quality:
                log.info('Ignoring proper because quality is different: {name}', {'name': best_result.name})
                if cur_proper.name not in processed_propers_names:
                    self.processed_propers.append({'name': cur_proper.name, 'date': cur_proper.date})
                continue

            # only keep the proper if we have already downloaded an episode with the same codec
            release_name = sql_results[0]['release_name']
            if release_name:
                release_name_guess = NameParser()._parse_string(release_name)
                current_codec = release_name_guess.video_codec

                # Ignore proper if codec differs from downloaded release codec
                if all([current_codec, best_result.parse_result.video_codec,
                        best_result.parse_result.video_codec != current_codec]):
                    log.info('Ignoring proper because codec is different: {name}', {'name': best_result.name})
                    if best_result.name not in processed_propers_names:
                        self.processed_propers.append({'name': best_result.name, 'date': best_result.date})
                    continue

                streaming_service = release_name_guess.guess.get(u'streaming_service')
                # Ignore proper if streaming service differs from downloaded release streaming service
                if best_result.parse_result.guess.get(u'streaming_service') != streaming_service:
                    log.info('Ignoring proper because streaming service is different: {name}',
                             {'name': best_result.name})
                    if best_result.name not in processed_propers_names:
                        self.processed_propers.append({'name': best_result.name, 'date': best_result.date})
                    continue
            else:
                log.debug("Coudn't find a release name in database. Skipping codec comparison for: {name}", {
                    'name': best_result.name
                })

            # check if we actually want this proper (if it's the right release group and a higher version)
            if best_result.series.is_anime:
                main_db_con = db.DBConnection()
                sql_results = main_db_con.select(
                    'SELECT release_group, version '
                    'FROM tv_episodes WHERE indexer = ? AND showid = ? '
                    'AND season = ? AND episode = ?',
                    [best_result.indexer, best_result.series.indexerid, best_result.actual_season,
                     best_result.actual_episodes[0]])

                old_version = int(sql_results[0]['version'])
                old_release_group = (sql_results[0]['release_group'])

                if -1 < old_version < best_result.version:
                    log.info('Found new anime version {new} to replace existing version {old}: {name}',
                             {'old': old_version,
                              'new': best_result.version,
                              'name': best_result.name
                              })
                else:
                    log.info('Ignoring proper with the same or lower version: {name}', {'name': best_result.name})
                    if cur_proper.name not in processed_propers_names:
                        self.processed_propers.append({'name': best_result.name, 'date': best_result.date})
                    continue

                if old_release_group != best_result.release_group:
                    log.info('Ignoring proper from release group {new} instead of current group {old}',
                             {'new': best_result.release_group,
                              'old': old_release_group})
                    if best_result.name not in processed_propers_names:
                        self.processed_propers.append({'name': best_result.name, 'date': best_result.date})
                    continue

            # if the show is in our list and there hasn't been a proper already added for that particular episode
            # then add it to our list of propers
            if best_result.indexerid != -1 and (
                best_result.indexerid, best_result.actual_season, best_result.actual_episodes
            ) not in list(map(operator.attrgetter('indexerid', 'actual_season', 'actual_episodes'), final_propers)):
                log.info('Found a desired proper: {name}', {'name': best_result.name})
                final_propers.append(best_result)

            if best_result.name not in processed_propers_names:
                self.processed_propers.append({'name': best_result.name, 'date': best_result.date})

        return final_propers