예제 #1
0
    def test_rich_comparison_and_len(self):
        # dicts for testing
        dict_0 = {}  # Empty dictionary
        dict_1 = {1: 'Elephant'}  # Single numeric key
        dict_2 = {1: 'Elephant', 2: 'Mouse'}  # Multiple numeric keys

        # Construct NumDicts from dicts
        num_dict = NumDict()
        num_dict_0 = NumDict(dict_0)
        num_dict_1 = NumDict(dict_1)
        num_dict_2 = NumDict(dict_2)

        # Construct NumDicts from NumDicts
        num_dict_from_num_dict = NumDict(num_dict)
        num_dict_from_num_dict_0 = NumDict(num_dict_0)
        num_dict_from_num_dict_1 = NumDict(num_dict_1)
        num_dict_from_num_dict_2 = NumDict(num_dict_2)

        all_dicts = [
            dict_0, dict_1, dict_2, num_dict, num_dict_0, num_dict_1,
            num_dict_2, num_dict_from_num_dict, num_dict_from_num_dict_0,
            num_dict_from_num_dict_1, num_dict_from_num_dict_2
        ]
        for val_a in all_dicts:
            for val_b in all_dicts:
                self.assertEqual(val_a == val_b, len(val_a) == len(val_b))
예제 #2
0
class Overview(object):
    UNAIRED = UNAIRED  # 1
    SNATCHED = SNATCHED  # 2
    WANTED = WANTED  # 3
    GOOD = DOWNLOADED  # 4
    SKIPPED = SKIPPED  # 5
    SNATCHED_PROPER = SNATCHED_PROPER  # 9
    SNATCHED_BEST = SNATCHED_BEST  # 12

    # Should suffice!
    QUAL = 50

    overviewStrings = NumDict({
        SKIPPED: "skipped",
        WANTED: "wanted",
        QUAL: "qual",
        GOOD: "good",
        UNAIRED: "unaired",
        SNATCHED: "snatched",
        # we can give these a different class later, otherwise
        # breaks checkboxes in displayShow for showing different statuses
        SNATCHED_BEST: "snatched",
        SNATCHED_PROPER: "snatched"
    })
예제 #3
0
class Quality(object):
    """Determine quality and set status codes."""

    NONE = 0  # 0
    SDTV = 1  # 1
    SDDVD = 1 << 1  # 2
    HDTV = 1 << 2  # 4
    RAWHDTV = 1 << 3  # 8  -- 720p/1080i mpeg2 (trollhd releases)
    FULLHDTV = 1 << 4  # 16 -- 1080p HDTV (QCF releases)
    HDWEBDL = 1 << 5  # 32
    FULLHDWEBDL = 1 << 6  # 64 -- 1080p web-dl
    HDBLURAY = 1 << 7  # 128
    FULLHDBLURAY = 1 << 8  # 256
    UHD_4K_TV = 1 << 9  # 512 -- 2160p aka 4K UHD aka UHD-1
    UHD_4K_WEBDL = 1 << 10  # 1024
    UHD_4K_BLURAY = 1 << 11  # 2048
    UHD_8K_TV = 1 << 12  # 4096 -- 4320p aka 8K UHD aka UHD-2
    UHD_8K_WEBDL = 1 << 13  # 8192
    UHD_8K_BLURAY = 1 << 14  # 16384
    ANYHDTV = HDTV | FULLHDTV  # 20
    ANYWEBDL = HDWEBDL | FULLHDWEBDL  # 96
    ANYBLURAY = HDBLURAY | FULLHDBLURAY  # 384

    # put these bits at the other end of the spectrum,
    # far enough out that they shouldn't interfere
    UNKNOWN = 1 << 15  # 32768

    qualityStrings = NumDict({
        None: "None",
        NONE: "N/A",
        UNKNOWN: "Unknown",
        SDTV: "SDTV",
        SDDVD: "SD DVD",
        HDTV: "720p HDTV",
        RAWHDTV: "RawHD",
        FULLHDTV: "1080p HDTV",
        HDWEBDL: "720p WEB-DL",
        FULLHDWEBDL: "1080p WEB-DL",
        HDBLURAY: "720p BluRay",
        FULLHDBLURAY: "1080p BluRay",
        UHD_4K_TV: "4K UHD TV",
        UHD_8K_TV: "8K UHD TV",
        UHD_4K_WEBDL: "4K UHD WEB-DL",
        UHD_8K_WEBDL: "8K UHD WEB-DL",
        UHD_4K_BLURAY: "4K UHD BluRay",
        UHD_8K_BLURAY: "8K UHD BluRay",
    })

    sceneQualityStrings = NumDict({
        None: "None",
        NONE: "N/A",
        UNKNOWN: "Unknown",
        SDTV: "",
        SDDVD: "",
        HDTV: "720p",
        RAWHDTV: "1080i",
        FULLHDTV: "1080p",
        HDWEBDL: "720p",
        FULLHDWEBDL: "1080p",
        HDBLURAY: "720p BluRay",
        FULLHDBLURAY: "1080p BluRay",
        UHD_4K_TV: "2160p",
        UHD_8K_TV: "4320p",
        UHD_4K_WEBDL: "2160p",
        UHD_8K_WEBDL: "4320p",
        UHD_4K_BLURAY: "2160p BluRay",
        UHD_8K_BLURAY: "4320p BluRay",
    })

    combinedQualityStrings = NumDict({
        ANYHDTV: "HDTV",
        ANYWEBDL: "WEB-DL",
        ANYBLURAY: "BluRay"
    })

    cssClassStrings = NumDict({
        None: "None",
        NONE: "N/A",
        UNKNOWN: "Unknown",
        SDTV: "SDTV",
        SDDVD: "SDDVD",
        HDTV: "HD720p",
        RAWHDTV: "RawHD",
        FULLHDTV: "HD1080p",
        HDWEBDL: "HD720p",
        FULLHDWEBDL: "HD1080p",
        HDBLURAY: "HD720p",
        FULLHDBLURAY: "HD1080p",
        UHD_4K_TV: "UHD-4K",
        UHD_8K_TV: "UHD-8K",
        UHD_4K_WEBDL: "UHD-4K",
        UHD_8K_WEBDL: "UHD-8K",
        UHD_4K_BLURAY: "UHD-4K",
        UHD_8K_BLURAY: "UHD-8K",
        ANYHDTV: "any-hd",
        ANYWEBDL: "any-hd",
        ANYBLURAY: "any-hd"
    })

    statusPrefixes = NumDict({
        DOWNLOADED: "Downloaded",
        SNATCHED: "Snatched",
        SNATCHED_PROPER: "Snatched (Proper)",
        FAILED: "Failed",
        SNATCHED_BEST: "Snatched (Best)",
        ARCHIVED: "Archived"
    })

    @staticmethod
    def _get_status_strings(status):
        """
        Return string values associated with Status prefix.

        :param status: Status prefix to resolve
        :return: Human readable status value
        """
        to_return = {}
        for quality in Quality.qualityStrings:
            if quality is not None:
                stat = Quality.statusPrefixes[status]
                qual = Quality.qualityStrings[quality]
                comp = Quality.composite_status(status, quality)
                to_return[comp] = '%s (%s)' % (stat, qual)
        return to_return

    @staticmethod
    def combine_qualities(allowed_qualities, preferred_qualities):
        any_quality = 0
        best_quality = 0
        if allowed_qualities:
            any_quality = reduce(operator.or_, allowed_qualities)
        if preferred_qualities:
            best_quality = reduce(operator.or_, preferred_qualities)
        return any_quality | (best_quality << 16)

    @staticmethod
    def split_quality(quality):
        if quality is None:
            quality = Quality.NONE
        allowed_qualities = []
        preferred_qualities = []
        for cur_qual in Quality.qualityStrings:
            if cur_qual is None:
                cur_qual = Quality.NONE
            if cur_qual & quality:
                allowed_qualities.append(cur_qual)
            if cur_qual << 16 & quality:
                preferred_qualities.append(cur_qual)

        return sorted(allowed_qualities), sorted(preferred_qualities)

    @staticmethod
    def name_quality(name, anime=False, extend=True):
        """
        Return The quality from an episode File renamed by the application.

        If no quality is achieved it will try scene_quality regex

        :param name: to parse
        :param anime: Boolean to indicate if the show we're resolving is Anime
        :param extend: boolean to extend methods to try
        :return: Quality prefix
        """
        # Try Scene names first
        quality = Quality.scene_quality(name, anime)
        if quality != Quality.UNKNOWN:
            return quality

        # Additional methods to get quality should be added here
        if extend:
            return Quality._extend_quality(name)

        return Quality.UNKNOWN

    @staticmethod
    def scene_quality(name, anime=False):
        """
        Return The quality from the Scene episode File.

        :param name: Episode filename to analyse
        :param anime: Boolean to indicate if the show we're resolving is Anime
        :return: Quality
        """
        from .tagger.episode import EpisodeTags
        if not name:
            return Quality.UNKNOWN
        else:
            name = path.basename(name)

        result = None
        ep = EpisodeTags(name)

        if anime:
            sd_options = tags.anime_sd.search(name)
            hd_options = tags.anime_hd.search(name)
            full_hd = tags.anime_fullhd.search(name)
            ep.rex[u'bluray'] = tags.anime_bluray

            # BluRay
            if ep.bluray and (full_hd or hd_options):
                result = Quality.FULLHDBLURAY if full_hd else Quality.HDBLURAY
            # HD TV
            elif not ep.bluray and (full_hd or hd_options):
                result = Quality.FULLHDTV if full_hd else Quality.HDTV
            # SD DVD
            elif ep.dvd:
                result = Quality.SDDVD
            # SD TV
            elif sd_options:
                result = Quality.SDTV

            return Quality.UNKNOWN if result is None else result

        # Is it UHD?
        if ep.vres in [2160, 4320] and ep.scan == u'p':
            # BluRay
            full_res = (ep.vres == 4320)
            if ep.avc and ep.bluray:
                result = Quality.UHD_4K_BLURAY if not full_res else Quality.UHD_8K_BLURAY
            # WEB-DL
            elif (ep.avc and ep.itunes) or ep.web:
                result = Quality.UHD_4K_WEBDL if not full_res else Quality.UHD_8K_WEBDL
            # HDTV
            elif ep.avc and ep.tv == u'hd':
                result = Quality.UHD_4K_TV if not full_res else Quality.UHD_8K_TV

        # Is it HD?
        elif ep.vres in [1080, 720]:
            if ep.scan == u'p':
                # BluRay
                full_res = (ep.vres == 1080)
                if ep.avc and (ep.bluray or ep.hddvd):
                    result = Quality.FULLHDBLURAY if full_res else Quality.HDBLURAY
                # WEB-DL
                elif (ep.avc and ep.itunes) or ep.web:
                    result = Quality.FULLHDWEBDL if full_res else Quality.HDWEBDL
                # HDTV
                elif ep.avc and ep.tv == u'hd':
                    result = Quality.FULLHDTV if full_res else Quality.HDTV
                elif all([ep.vres == 720, ep.tv == u'hd', ep.mpeg]):
                    result = Quality.RAWHDTV
            elif (ep.res == u'1080i') and ep.tv == u'hd':
                if ep.mpeg or (ep.raw and ep.avc_non_free):
                    result = Quality.RAWHDTV
        elif ep.hrws:
            result = Quality.HDTV

        # Is it SD?
        elif ep.xvid or ep.avc:
            # Is it aussie p2p?  If so its 720p
            if all([ep.tv == u'hd', ep.widescreen, ep.aussie]):
                result = Quality.HDTV
            # SD DVD
            elif ep.dvd or ep.bluray:
                result = Quality.SDDVD
            # SDTV
            elif ep.res == u'480p' or any([ep.tv, ep.sat, ep.web]):
                result = Quality.SDTV

        return Quality.UNKNOWN if result is None else result

    @staticmethod
    def _extend_quality(file_path):
        """
        Try other methods to get the file quality.

        :param file_path: File path of episode to analyse
        :return: Quality prefix
        """
        quality = Quality.quality_from_file_meta(file_path)
        if quality != Quality.UNKNOWN:
            return quality

        # This assumes that any .ts file is RAWHDTV (probably wrong)
        if file_path.lower().endswith('.ts'):
            return Quality.RAWHDTV

        return Quality.UNKNOWN

    @staticmethod
    def quality_from_file_meta(file_path):
        """
        Get quality file file metadata.

        :param file_path: File path to analyse
        :return: Quality prefix
        """
        if not os.path.isfile(file_path):
            return Quality.UNKNOWN

        knowledge = knowit.know(file_path)

        if not knowledge:
            return Quality.UNKNOWN

        height = None
        for track in knowledge.get('video') or []:
            height = track.get('height')
            if height:
                break

        if not height:
            return Quality.UNKNOWN

        height = int(height.magnitude)

        # TODO: Use knowledge information like 'resolution'
        base_filename = path.basename(file_path)
        bluray = re.search(r"blue?-?ray|hddvd|b[rd](rip|mux)", base_filename,
                           re.I) is not None
        webdl = re.search(r"web.?dl|web(rip|mux|hd)", base_filename,
                          re.I) is not None

        ret = Quality.UNKNOWN
        if 3240 < height:
            ret = ((Quality.UHD_8K_TV, Quality.UHD_8K_BLURAY)[bluray],
                   Quality.UHD_8K_WEBDL)[webdl]
        if 1620 < height <= 3240:
            ret = ((Quality.UHD_4K_TV, Quality.UHD_4K_BLURAY)[bluray],
                   Quality.UHD_4K_WEBDL)[webdl]
        elif 800 < height <= 1620:
            ret = ((Quality.FULLHDTV, Quality.FULLHDBLURAY)[bluray],
                   Quality.FULLHDWEBDL)[webdl]
        elif 680 < height <= 800:
            ret = ((Quality.HDTV, Quality.HDBLURAY)[bluray],
                   Quality.HDWEBDL)[webdl]
        elif height <= 680:
            ret = (Quality.SDTV, Quality.SDDVD)[re.search(
                r'dvd|b[rd]rip|blue?-?ray', base_filename, re.I) is not None]

        return ret

    composite_status_quality = namedtuple('composite_status',
                                          ['status', 'quality'])

    @staticmethod
    def composite_status(status, quality):
        if quality is None:
            quality = Quality.NONE
        return status + 100 * quality

    @staticmethod
    def quality_downloaded(status):
        return (status - DOWNLOADED) / 100

    @staticmethod
    def split_composite_status(status):
        """
        Split a composite status code into a status and quality.

        :param status: to split
        :returns: a namedtuple containing (status, quality)
        """
        status = long(status)
        if status == UNKNOWN:
            return Quality.composite_status_quality(UNKNOWN, Quality.UNKNOWN)

        for q in sorted(Quality.qualityStrings.keys(), reverse=True):
            if status > q * 100:
                return Quality.composite_status_quality(status - q * 100, q)

        return Quality.composite_status_quality(status, Quality.NONE)

    @staticmethod
    def scene_quality_from_name(name, quality):
        """
        Get Scene naming parameters from filename and quality.

        :param name: filename to check
        :type name: text_type
        :param quality: int of quality to make sure we get the right release type
        :type quality: int
        :return: release type and/or encoder type for scene quality naming
        :rtype: text_type
        """
        rel_type = ''
        name = name.lower()
        codec = re.search(r'[xh].?26[45]', name) or ''

        if codec and codec.group(0).endswith('4') or 'avc' in name:
            if codec and codec.group(0).startswith('h'):
                codec = ' h264'
            else:
                codec = ' x264'
        elif codec and codec.group(0).endswith('5') or 'hevc' in name:
            if codec and codec.group(0).startswith('h'):
                codec = ' h265'
            else:
                codec = ' x265'
        elif 'xvid' in name:
            codec = ' XviD'
        elif 'divx' in name:
            codec = ' DivX'

        # If any HDTV type or SDTV
        if quality in (1, 4, 8, 16, 512, 4096):
            rel_type = ' HDTV'
            if 'ahdtv' in name:
                rel_type = ' AHDTV'
            elif 'hr.pdtv' in name:
                rel_type = ' HR.PDTV'
            elif 'pdtv' in name:
                rel_type = ' PDTV'
            elif 'satrip' in name:
                rel_type = ' SATRip'
            elif 'dsr' in name:
                rel_type = ' DSR'
            elif 'uhdtv' in name:
                rel_type = ' UHDTV'

        # If SDDVD
        if quality == 2:
            rel_type = ' BDRip'
            if re.search(r'br(-| |\.)?(rip|mux)', name):
                rel_type = ' BRRip'
            elif re.search(r'dvd(-| |\.)?(rip|mux)', name):
                rel_type = ' DVDRip'

        # If any WEB type
        if quality in (32, 64, 1024, 8192):
            rel_type = ' WEB'
            if re.search(r'web(-| |\.)?dl', name):
                rel_type = ' WEB-DL'
            elif re.search(r'web(-| |\.)?(rip|mux)', name):
                rel_type = ' WEBRip'

        return rel_type + codec

    @staticmethod
    def status_from_name(name, anime=False):
        """
        Get a status object from filename.

        :param name: Filename to check
        :param anime: boolean to enable anime parsing
        :return: Composite status/quality object
        """
        quality = Quality.name_quality(name, anime)
        return Quality.composite_status(DOWNLOADED, quality)

    guessit_map = {
        '720p': {
            'HDTV': HDTV,
            'WEB-DL': HDWEBDL,
            'WEBRip': HDWEBDL,
            'BluRay': HDBLURAY,
        },
        '1080i': RAWHDTV,
        '1080p': {
            'HDTV': FULLHDTV,
            'WEB-DL': FULLHDWEBDL,
            'WEBRip': FULLHDWEBDL,
            'BluRay': FULLHDBLURAY
        },
        '4K': {
            'HDTV': UHD_4K_TV,
            'WEB-DL': UHD_4K_WEBDL,
            'WEBRip': UHD_4K_WEBDL,
            'BluRay': UHD_4K_BLURAY
        }
    }

    to_guessit_format_list = [
        ANYHDTV, ANYWEBDL, ANYBLURAY, ANYHDTV | UHD_4K_TV,
        ANYWEBDL | UHD_4K_WEBDL, ANYBLURAY | UHD_4K_BLURAY
    ]

    to_guessit_screen_size_map = {
        HDTV | HDWEBDL | HDBLURAY: '720p',
        RAWHDTV: '1080i',
        FULLHDTV | FULLHDWEBDL | FULLHDBLURAY: '1080p',
        UHD_4K_TV | UHD_4K_WEBDL | UHD_4K_BLURAY: '4K',
    }

    @staticmethod
    def should_search(status, show_obj, manually_searched):
        """Return true if that episodes should be search for a better quality.

        If cur_quality is Quality.NONE, it will return True as its a invalid quality
        If cur_quality is Quality.UNKNOWN it will return True only if is not in Allowed (Unknown can be in Allowed)

        :param status: current status of the episode
        :param show_obj: Series object of the episode we will check if we should search or not
        :param manually_searched: if episode was manually searched by user
        :return: True if need to run a search for given episode
        """
        cur_status, cur_quality = Quality.split_composite_status(
            int(status) or UNKNOWN)
        allowed_qualities, preferred_qualities = show_obj.current_qualities

        # When user manually searched, we should consider this as final quality.
        if manually_searched:
            return False, 'Episode was manually searched. Skipping episode'

        #  Can't be SNATCHED_BEST because the quality is already final (unless user changes qualities).
        #  All other status will return false: IGNORED, SKIPPED, FAILED.
        if cur_status not in (WANTED, DOWNLOADED, SNATCHED, SNATCHED_PROPER,
                              SNATCHED_BEST):
            return False, 'Status is not allowed: {0}. Skipping episode'.format(
                statusStrings[cur_status])

        # If current status is WANTED, we must always search
        if cur_status != WANTED:
            if cur_quality not in allowed_qualities + preferred_qualities:
                return True, 'Quality is not in Allowed|Preferred. Searching episode'
            elif preferred_qualities:
                if cur_quality in preferred_qualities:
                    return False, 'Quality is already Preferred. Skipping episode'
                else:
                    return True, 'Quality is not Preferred. Searching episode'
            elif cur_quality in allowed_qualities:
                return False, 'Quality is already Allowed. Skipping episode'
        else:
            return True, 'Status is WANTED. Searching episode'

        return False, 'No rule set to allow the search'

    @staticmethod
    def should_replace(ep_status,
                       old_quality,
                       new_quality,
                       allowed_qualities,
                       preferred_qualities,
                       download_current_quality=False,
                       force=False,
                       manually_searched=False,
                       search_type=None):
        """Return true if the old quality should be replaced with new quality.

        If not preferred qualities, then any downloaded quality is final
        if preferred quality, then new quality should be higher than existing one AND not be in preferred
        If new quality is already in preferred then is already final quality.
        Force (forced search) bypass episode status only or unknown quality
        If old quality is Quality.NONE, it will be replaced

        :param ep_status: current status of the episode
        :param old_quality: current quality of the episode
        :param new_quality: quality of the episode we found it and check if we should snatch it
        :param allowed_qualities: List of selected allowed qualities of the show we are checking
        :param preferred_qualities: List of selected preferred qualities of the show we are checking
        :param download_current_quality: True if user wants the same existing quality to be snatched
        :param force: True if user did a forced search for that episode
        :param manually_searched: True if episode was manually searched by user
        :param search_type: The search type, that started this method
        :return: True if the old quality should be replaced with new quality.
        """
        if ep_status and ep_status not in Quality.DOWNLOADED + Quality.SNATCHED + Quality.SNATCHED_PROPER:
            if not force:
                return False, 'Episode status is not DOWNLOADED|SNATCHED|SNATCHED PROPER. Ignoring new quality'

        # If existing quality is UNKNOWN but Preferred is set, UNKNOWN should be replaced.
        if old_quality == Quality.UNKNOWN:
            if not (force or preferred_qualities):
                return False, 'Existing quality is UNKNOWN. Ignoring new quality'

        if manually_searched:
            if not force:
                # We only allow replace a manual searched episode if is a forced search
                return False, 'Existing episode quality was manually snatched. Ignoring all new qualities'

        if not Quality.wanted_quality(new_quality, allowed_qualities,
                                      preferred_qualities):
            return False, 'New quality is not in any wanted quality lists. Ignoring new quality'

        if search_type == PROPER_SEARCH:
            if new_quality == old_quality:
                return True, 'New quality is the same as the existing quality. Accepting PROPER'
            return False, 'New quality is different from the existing quality.' \
                          'Ignoring PROPER, as we only PROPER the same release.'

        if old_quality not in allowed_qualities + preferred_qualities:
            # If old quality is no longer wanted quality and new quality is wanted, we should replace.
            return True, 'Existing quality is no longer in any wanted quality lists. Accepting new quality'

        if force and download_current_quality:
            # If we already downloaded quality, just redownload it as long is still part of the wanted qualities
            return new_quality == old_quality, 'Redownloading same quality'

        if preferred_qualities:
            # Don't replace because old quality is already best quality.
            if old_quality in preferred_qualities:
                return False, 'Existing quality is already a preferred quality. Ignoring new quality'

            # Old quality is not final. Check if we should replace:

            # Replace if preferred quality
            if new_quality in preferred_qualities:
                return True, 'New quality is preferred. Accepting new quality'

            return False, 'New quality is same/lower quality (and not preferred). Ignoring new quality'

        else:
            # Allowed quality should never be replaced
            return False, 'Existing quality is already final (allowed only). Ignoring new quality'

    @staticmethod
    def is_higher_quality(current_quality, new_quality, allowed_qualities,
                          preferred_qualities):
        """Check is new quality is better than current quality based on allowed and preferred qualities."""
        if new_quality in preferred_qualities:
            return new_quality > current_quality
        elif new_quality in allowed_qualities:
            if current_quality in preferred_qualities:
                return False
            return new_quality > current_quality

    @staticmethod
    def wanted_quality(new_quality, allowed_qualities, preferred_qualities):
        """Check if new quality is wanted."""
        return new_quality in allowed_qualities + preferred_qualities

    @staticmethod
    def from_guessit(guess):
        """
        Return a Quality from a guessit dict.

        :param guess: guessit dict
        :type guess: dict
        :return: quality
        :rtype: int
        """
        screen_size = guess.get('screen_size')
        fmt = guess.get('format')

        if not screen_size or isinstance(screen_size, list):
            return Quality.UNKNOWN

        format_map = Quality.guessit_map.get(screen_size)
        if not format_map:
            return Quality.UNKNOWN

        if isinstance(format_map, int):
            return format_map

        if not fmt or isinstance(fmt, list):
            return Quality.UNKNOWN

        quality = format_map.get(fmt)
        return quality if quality is not None else Quality.UNKNOWN

    @staticmethod
    def to_guessit(status):
        """Return a guessit dict containing 'screen_size and format' from a Quality (composite status).

        :param status: a quality composite status
        :type status: int
        :return: dict {'screen_size': <screen_size>, 'format': <format>}
        :rtype: dict (str, str)
        """
        _, quality = Quality.split_composite_status(status)
        screen_size = Quality.to_guessit_screen_size(quality)
        fmt = Quality.to_guessit_format(quality)
        result = dict()
        if screen_size:
            result['screen_size'] = screen_size
        if fmt:
            result['format'] = fmt

        return result

    @staticmethod
    def to_guessit_format(quality):
        """Return a guessit format from a Quality.

        :param quality: the quality
        :type quality: int
        :return: guessit format
        :rtype: str
        """
        for q in Quality.to_guessit_format_list:
            if quality & q:
                key = q & (
                    512 - 1
                )  # 4k formats are bigger than 384 and are not part of ANY* bit set
                return Quality.combinedQualityStrings.get(key)

    @staticmethod
    def to_guessit_screen_size(quality):
        """Return a guessit screen_size from a Quality.

        :param quality: the quality
        :type quality: int
        :return: guessit screen_size
        :rtype: str
        """
        for key, value in Quality.to_guessit_screen_size_map.items():
            if quality & key:
                return value

    DOWNLOADED = None
    SNATCHED = None
    SNATCHED_PROPER = None
    FAILED = None
    SNATCHED_BEST = None
    ARCHIVED = None
예제 #4
0
# Notification Types
NOTIFY_SNATCH = 1
NOTIFY_DOWNLOAD = 2
NOTIFY_SUBTITLE_DOWNLOAD = 3
NOTIFY_GIT_UPDATE = 4
NOTIFY_GIT_UPDATE_TEXT = 5
NOTIFY_LOGIN = 6
NOTIFY_LOGIN_TEXT = 7
NOTIFY_SNATCH_PROPER = 8

notifyStrings = NumDict({
    NOTIFY_SNATCH: "Started Download",
    NOTIFY_DOWNLOAD: "Download Finished",
    NOTIFY_SUBTITLE_DOWNLOAD: "Subtitle Download Finished",
    NOTIFY_GIT_UPDATE: "Medusa Updated",
    NOTIFY_GIT_UPDATE_TEXT: "Medusa Updated To Commit#: ",
    NOTIFY_LOGIN: "******",
    NOTIFY_LOGIN_TEXT:
    "New login from IP: {0}. http://geomaplookup.net/?ip={0}",
    NOTIFY_SNATCH_PROPER: "Started PROPER Download"
})

# Episode statuses
UNKNOWN = -1  # should never happen
UNAIRED = 1  # episodes that haven't aired yet
SNATCHED = 2  # qualified with quality
WANTED = 3  # episodes we don't have but want to get
DOWNLOADED = 4  # qualified with quality
SKIPPED = 5  # episodes we don't want
ARCHIVED = 6  # non-local episodes (counts toward download completion stats)
IGNORED = 7  # episodes that you don't want included in your download stats
예제 #5
0
 def __init__(self):
     # An instance variable __missing__ should have no effect
     self.__missing__ = lambda key: None
     NumDict.__init__(self)
예제 #6
0
    def test_dict_access_and_mod(self):  # pylint: disable=too-many-locals, too-many-statements
        # dicts for testing
        dict_0 = {}  # Empty dictionary
        dict_1 = {1: 'Elephant'}  # Single numeric key
        dict_2 = {1: 'Elephant', 2: 'Mouse'}  # Multiple numeric keys

        #  Construct NumDicts from dicts
        num_dict_0 = NumDict()
        num_dict_1 = NumDict(dict_1)
        num_dict_2 = NumDict(dict_2)

        # test __getitem__
        self.assertEqual(num_dict_2[1], 'Elephant')
        with self.assertRaises(KeyError):
            print(num_dict_1['Mouse'])  # key is not numeric
        with self.assertRaises(KeyError):
            num_dict_1.__getitem__('Mouse')  # key is not numeric
        with self.assertRaises(KeyError):
            print(num_dict_1[None])  # key does not exist
        with self.assertRaises(KeyError):
            num_dict_1.__getitem__(None)  # key does not exist

        # Test __setitem__
        num_dict_3 = NumDict(num_dict_2)
        self.assertEqual(num_dict_2, num_dict_3)

        num_dict_3[2] = 'Frog'
        self.assertNotEqual(num_dict_2, num_dict_3)

        # Check None keys and numeric key conversion
        num_dict_3['3'] = 'Armadillo'
        num_dict_3[None] = 'Cockroach'

        # Check long ints
        num_dict_3[12390809518259081208909880312] = 'Squid'
        num_dict_3['12390809518259081208909880312'] = 'Octopus'
        self.assertEqual(num_dict_3[12390809518259081208909880312], 'Octopus')

        with self.assertRaises(TypeError):
            num_dict_3.__setitem__('Gorilla', 1)  # key is not numeric
        with self.assertRaises(TypeError):
            num_dict_3['Chimpanzee'] = 1  # key is not numeric
        with self.assertRaises(TypeError):
            num_dict_3[(4, 1)] = 1  # key is not numeric
        with self.assertRaises(TypeError):
            num_dict_3[[1, 3, 4]] = 1  # key is not numeric and is not hashable

        # Test __delitem__
        del num_dict_3[3]
        del num_dict_3[None]
        with self.assertRaises(KeyError):
            del num_dict_3[3]  # already deleted
        with self.assertRaises(KeyError):
            num_dict_3.__delitem__(3)  # already deleted
        with self.assertRaises(KeyError):
            del num_dict_3[
                'Mouse']  # key would not exist, since it is not numeric

        # Test clear
        num_dict_3.clear()
        self.assertEqual(num_dict_3, {})

        # Test copy()
        num_dict_2a = dict_2.copy()
        self.assertEqual(num_dict_2, num_dict_2a)
        num_dict_2b = num_dict_2.copy()
        self.assertEqual(num_dict_2b, num_dict_2)
        num_dict_2c = UserDict({1: 'Elephant', 2: 'Mouse'})
        num_dict_2d = num_dict_2c.copy(
        )  # making a copy of a UserDict is special cased
        self.assertEqual(num_dict_2c, num_dict_2d)

        class MyNumDict(NumDict):
            """Subclass Numdict for testing."""
            def display(self):
                """Add a method to subclass to differentiate from superclass."""
                print('MyNumDict:', self)

        my_num_dict = MyNumDict(num_dict_2)
        my_num_dict_a = my_num_dict.copy()
        self.assertEqual(my_num_dict_a, my_num_dict)

        my_num_dict[1] = 'Frog'
        self.assertNotEqual(my_num_dict_a, my_num_dict)

        # Test keys, items, values
        self.assertEqual(sorted(num_dict_2.keys()), sorted(dict_2.keys()))
        self.assertEqual(sorted(num_dict_2.items()), sorted(dict_2.items()))
        self.assertEqual(sorted(num_dict_2.values()), sorted(dict_2.values()))

        # Test "in".
        for i in num_dict_2:
            self.assertIn(i, num_dict_2)
            self.assertEqual(i in num_dict_1, i in dict_1)
            self.assertEqual(i in num_dict_0, i in dict_0)

        self.assertFalse(None in num_dict_2)
        self.assertEqual(None in num_dict_2, None in dict_2)

        dict_2[None] = 'Cow'
        num_dict_2[None] = dict_2[None]
        self.assertTrue(None in num_dict_2)
        self.assertEqual(None in num_dict_2, None in dict_2)

        self.assertFalse('Penguin' in num_dict_2)

        # Test update
        test = NumDict()
        test.update(dict_2)
        self.assertEqual(test, num_dict_2)

        # Test get
        for i in num_dict_2:
            self.assertEqual(num_dict_2.get(i), num_dict_2[i])
            self.assertEqual(num_dict_1.get(i), dict_1.get(i))
            self.assertEqual(num_dict_0.get(i), dict_0.get(i))

        for i in ['purple', None, 12312301924091284, 23]:
            self.assertEqual(num_dict_2.get(i), dict_2.get(i), i)

        with self.assertRaises(AssertionError):
            i = '1'
            self.assertEqual(
                num_dict_2.get(i), dict_2.get(i),
                i)  # dict_2 expects string key which does not exist

        # Test "in" iteration.
        num_dict_2b = num_dict_2
        for i in range(20):
            num_dict_2[i] = str(i)
            num_dict_2b[str(i)] = str(i)
        self.assertEqual(num_dict_2, num_dict_2b)

        ikeys = []
        for k in num_dict_2:
            ikeys.append(k)
        self.assertEqual(set(ikeys), set(num_dict_2.keys()))

        # Test setdefault
        val = 1
        test = NumDict()
        self.assertEqual(test.setdefault(val, 42), 42)
        self.assertEqual(test.setdefault(val, '42'), 42)
        self.assertNotEqual(test.setdefault(val, 42), '42')
        self.assertNotEqual(test.setdefault(val, '42'), '42')
        self.assertIn(val, test)

        self.assertEqual(test.setdefault(val, 23), 42)
        self.assertEqual(test.setdefault(val, '23'), 42)
        self.assertNotEqual(test.setdefault(val, 23), '42')
        self.assertNotEqual(test.setdefault(val, '23'), '42')
        self.assertIn(val, test)

        # Test pop
        val = 1
        test = NumDict({val: 42})
        self.assertEqual(test.pop(val), 42)
        self.assertRaises(KeyError, test.pop, val)
        self.assertEqual(test.pop(val, 1), 1)
        test[val] = 42
        self.assertEqual(test.pop(val, 1), 42)

        # Test popitem
        val = 1
        test = NumDict({val: 42})
        self.assertEqual(test.popitem(), (val, 42))
        self.assertRaises(KeyError, test.popitem)
예제 #7
0
    def test_constructors(self):  # pylint: disable=too-many-locals, too-many-statements
        # dicts for testing
        dict_0 = {}  # Empty dictionary
        dict_1 = {1: 'Elephant'}  # Single numeric key
        dict_2 = {1: 'Elephant', 2: 'Mouse'}  # Multiple numeric keys
        dict_3 = {'3': 'Aardvark'}  # Numeric string key
        dict_4 = {'3': 'Aardvark', '4': 'Ant'}  # Multiple numeric string keys
        dict_5 = {
            5: 'Cat',
            '6': 'Dog'
        }  # Mixed numeric and numeric string keys
        dict_6 = {1: None, '2': None}  # None as values
        dict_7 = {None: 'Empty'}  # None as key

        # Construct NumDicts from dicts
        num_dict = NumDict()
        num_dict_0 = NumDict(dict_0)
        num_dict_1 = NumDict(dict_1)
        num_dict_2 = NumDict(dict_2)
        num_dict_3 = NumDict(dict_3)
        num_dict_4 = NumDict(dict_4)
        num_dict_5 = NumDict(dict_5)
        num_dict_6 = NumDict(dict_6)
        num_dict_7 = NumDict(dict_7)

        # Most NumDicts from dicts should compare equal...
        self.assertEqual(num_dict, {})
        self.assertEqual(num_dict_0, dict_0)
        self.assertEqual(num_dict_1, dict_1)
        self.assertEqual(num_dict_2, dict_2)

        # ...however, numeric keys are not equal to numeric string keys...
        self.assertNotEqual(num_dict_3, dict_3)
        self.assertNotEqual(num_dict_4, dict_4)
        self.assertNotEqual(num_dict_5, dict_5)
        self.assertNotEqual(num_dict_6, dict_6)

        # ...but None keys work just fine
        self.assertEqual(num_dict_7, dict_7)

        # Construct dicts from NumDicts
        dict_from_num_dict = dict(num_dict)
        dict_from_num_dict_1 = dict(num_dict_1)
        dict_from_num_dict_2 = dict(num_dict_2)
        dict_from_num_dict_3 = dict(num_dict_3)
        dict_from_num_dict_4 = dict(num_dict_4)
        dict_from_num_dict_5 = dict(num_dict_5)
        dict_from_num_dict_6 = dict(num_dict_6)
        dict_from_num_dict_7 = dict(num_dict_7)

        # All dicts from NumDicts should compare equal
        self.assertEqual(num_dict, dict_from_num_dict)
        self.assertEqual(num_dict_1, dict_from_num_dict_1)
        self.assertEqual(num_dict_2, dict_from_num_dict_2)
        self.assertEqual(num_dict_3, dict_from_num_dict_3)
        self.assertEqual(num_dict_4, dict_from_num_dict_4)
        self.assertEqual(num_dict_5, dict_from_num_dict_5)
        self.assertEqual(num_dict_6, dict_from_num_dict_6)
        self.assertEqual(num_dict_7, dict_from_num_dict_7)

        # Construct NumDicts from NumDicts
        num_dict_from_num_dict = NumDict(num_dict)
        num_dict_from_num_dict_0 = NumDict(num_dict_0)
        num_dict_from_num_dict_1 = NumDict(num_dict_1)
        num_dict_from_num_dict_2 = NumDict(num_dict_2)
        num_dict_from_num_dict_3 = NumDict(num_dict_3)
        num_dict_from_num_dict_4 = NumDict(num_dict_4)
        num_dict_from_num_dict_5 = NumDict(num_dict_5)
        num_dict_from_num_dict_6 = NumDict(num_dict_6)
        num_dict_from_num_dict_7 = NumDict(num_dict_7)

        # All NumDicts from NumDicts should compare equal
        self.assertEqual(num_dict, num_dict_from_num_dict)
        self.assertEqual(num_dict_0, num_dict_from_num_dict_0)
        self.assertEqual(num_dict_1, num_dict_from_num_dict_1)
        self.assertEqual(num_dict_2, num_dict_from_num_dict_2)
        self.assertEqual(num_dict_3, num_dict_from_num_dict_3)
        self.assertEqual(num_dict_4, num_dict_from_num_dict_4)
        self.assertEqual(num_dict_5, num_dict_from_num_dict_5)
        self.assertEqual(num_dict_6, num_dict_from_num_dict_6)
        self.assertEqual(num_dict_7, num_dict_from_num_dict_7)

        # keyword arg constructor should fail
        with self.assertRaises(TypeError):
            NumDict(
                one=1,
                two=2)  # Raise TypeError since we can't have numeric keywords

        # item sequence constructors work fine...
        self.assertEqual(NumDict([(1, 'Elephant'), (2, 'Mouse')]),
                         dict_from_num_dict_2)
        self.assertEqual(NumDict(dict=[(1, 'Elephant'), (2, 'Mouse')]),
                         dict_from_num_dict_2)
        self.assertEqual(NumDict([(1, 'Elephant'), ('2', 'Mouse')]),
                         dict_from_num_dict_2)
        self.assertEqual(NumDict(dict=[('1', 'Elephant'), (2, 'Mouse')]),
                         dict_from_num_dict_2)

        # ...unless you have a non-numeric key
        with self.assertRaises(TypeError):
            NumDict([('Rat', 11), ('Snake', 12)])
        with self.assertRaises(TypeError):
            NumDict(dict=[('Rat', 11), ('Snake', 12)])

        # combining item sequence constructors with keyword args does not work
        with self.assertRaises(
                TypeError
        ):  # Raise TypeError since we can't have numeric keywords
            NumDict([(1, 'one'), (2, 'two')], two=3, five=4)

        # alternate constructors
        dict_8 = {1: 'Echo', 2: 'Echo'}

        self.assertEqual(NumDict.fromkeys('1 2'.split()), dict_from_num_dict_6)
        self.assertEqual(NumDict().fromkeys('1 2'.split()),
                         dict_from_num_dict_6)
        self.assertEqual(NumDict.fromkeys('1 2'.split(), 'Echo'), dict_8)
        self.assertEqual(NumDict().fromkeys('1 2'.split(), 'Echo'), dict_8)
        self.assertTrue(num_dict_1.fromkeys('1 2'.split()) is not num_dict_1)
        self.assertIsInstance(num_dict_1.fromkeys('1 2'.split()), NumDict)
        self.assertIsInstance(num_dict_2.fromkeys('1 2'.split()), NumDict)
        self.assertIsInstance(num_dict_3.fromkeys('1 2'.split()), NumDict)
        self.assertIsInstance(num_dict_4.fromkeys('1 2'.split()), NumDict)
예제 #8
0
    def test_repr(self):  # pylint: disable=too-many-locals
        # dicts for testing
        dict_0 = {}  # Empty dictionary
        dict_1 = {1: 'Elephant'}  # Single numeric key
        dict_2 = {1: 'Elephant', 2: 'Mouse'}  # Multiple numeric keys
        dict_3 = {'3': 'Aardvark'}  # Numeric string key
        dict_4 = {'3': 'Aardvark', '4': 'Ant'}  # Multiple numeric string keys
        dict_5 = {
            5: 'Cat',
            '6': 'Dog'
        }  # Mixed numeric and numeric string keys
        dict_6 = {1: None, '2': None}  # None as values
        dict_7 = {None: 'Empty'}  # None as key

        #  Construct NumDicts from dicts
        num_dict = NumDict()
        num_dict_0 = NumDict(dict_0)
        num_dict_1 = NumDict(dict_1)
        num_dict_2 = NumDict(dict_2)
        num_dict_3 = NumDict(dict_3)
        num_dict_4 = NumDict(dict_4)
        num_dict_5 = NumDict(dict_5)
        num_dict_6 = NumDict(dict_6)
        num_dict_7 = NumDict(dict_7)

        reps = (
            "{}",
            "{1: 'Elephant'}",
            "{1: 'Elephant', 2: 'Mouse'}",
            "'3': 'Aardvark'",
            "{'3': 'Aardvark', '4': 'Ant'}",
            "{5: 'Cat', '6': 'Dog'}",
            "{1: None, '2': None}",
            "{None: 'Empty'}",
        )

        # Most representations of NumDicts should compare equal to dicts...
        self.assertEqual(str(num_dict), str({}))
        self.assertEqual(repr(num_dict), repr({}))
        self.assertIn(repr(num_dict), reps)

        self.assertEqual(str(num_dict_0), str(dict_0))
        self.assertEqual(repr(num_dict_0), repr(dict_0))
        self.assertIn(repr(num_dict_0), reps)

        self.assertEqual(str(num_dict_1), str(dict_1))
        self.assertEqual(repr(num_dict_1), repr(dict_1))
        self.assertIn(repr(num_dict_1), reps)

        self.assertEqual(str(num_dict_2), str(dict_2))
        self.assertEqual(repr(num_dict_2), repr(dict_2))
        self.assertIn(repr(num_dict_2), reps)

        # ...however, numeric keys are not equal to numeric string keys...
        # ...so the string representations for those are different...
        self.assertNotEqual(str(num_dict_3), str(dict_3))
        self.assertNotEqual(repr(num_dict_3), repr(dict_3))
        self.assertNotIn(repr(num_dict_3), reps)

        self.assertNotEqual(str(num_dict_4), str(dict_4))
        self.assertNotEqual(repr(num_dict_4), repr(dict_4))
        self.assertNotIn(repr(num_dict_4), reps)

        self.assertNotEqual(str(num_dict_5), str(dict_5))
        self.assertNotEqual(repr(num_dict_5), repr(dict_5))
        self.assertNotIn(repr(num_dict_5), reps)

        self.assertNotEqual(str(num_dict_6), str(dict_6))
        self.assertNotEqual(repr(num_dict_6), repr(dict_6))
        self.assertNotIn(repr(num_dict_6), reps)

        # ...but None keys work just fine
        self.assertEqual(str(num_dict_7), str(dict_7))
        self.assertEqual(repr(num_dict_7), repr(dict_7))
        self.assertIn(repr(num_dict_7), reps)