diff --git a/sickbeard/common.py b/sickbeard/common.py index 9f68d6fb7595dab3207c6e2a353b16978fcc4000..a0532c632a60c4bdab20f6d445fca460b4a82059 100644 --- a/sickbeard/common.py +++ b/sickbeard/common.py @@ -24,7 +24,6 @@ Common interface for Quality and Status # pylint: disable=line-too-long - import operator from os import path import platform @@ -38,6 +37,10 @@ from hachoir_core.log import log # pylint: disable=import-error from fake_useragent import settings as UA_SETTINGS, UserAgent from sickbeard.numdict import NumDict from sickrage.helper.encoding import ek +from sickrage.helper.common import try_int +from sickrage.tagger.episode import EpisodeTags +from sickrage.recompiled import tags + # If some provider has an issue with functionality of SR, other than user agents, it's best to come talk to us rather than block. # It is no different than us going to a provider if we have questions or issues. Be a team player here. @@ -124,6 +127,12 @@ class Quality(object): 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 @@ -143,7 +152,13 @@ class Quality(object): HDWEBDL: "720p WEB-DL", FULLHDWEBDL: "1080p WEB-DL", HDBLURAY: "720p BluRay", - FULLHDBLURAY: "1080p 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({ @@ -158,7 +173,13 @@ class Quality(object): HDWEBDL: "720p WEB-DL", FULLHDWEBDL: "1080p WEB-DL", HDBLURAY: "720p BluRay", - FULLHDBLURAY: "1080p 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", }) combinedQualityStrings = NumDict({ @@ -180,6 +201,12 @@ class Quality(object): 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" @@ -212,13 +239,13 @@ class Quality(object): return to_return @staticmethod - def combineQualities(any_qualities, best_qualities): + def combineQualities(allowed_qualities, prefferred_qualities): any_quality = 0 best_quality = 0 - if any_qualities: - any_quality = reduce(operator.or_, any_qualities) - if best_qualities: - best_quality = reduce(operator.or_, best_qualities) + if allowed_qualities: + any_quality = reduce(operator.or_, allowed_qualities) + if prefferred_qualities: + best_quality = reduce(operator.or_, prefferred_qualities) return any_quality | (best_quality << 16) @staticmethod @@ -260,7 +287,7 @@ class Quality(object): return Quality.UNKNOWN @staticmethod - def sceneQuality(name, anime=False): # pylint: disable=too-many-branches + def old_scene_quality(name, anime=False): # pylint: disable=too-many-branches """ Return The quality from the scene episode File @@ -322,6 +349,107 @@ class Quality(object): return ret + @staticmethod + def scene_quality(name, anime=False): # pylint: disable=too-many-branches + """ + 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 prefix + """ + + if not name: + return Quality.UNKNOWN + else: + name = ek(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) + + # 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': + if not all([ep.vres == 1080, ep.raw, ep.avc_non_free]): + result = Quality.FULLHDTV if full_res else Quality.HDTV + else: + result = Quality.RAWHDTV + 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: + # SD DVD + if 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 + + new = result or Quality.UNKNOWN + if new <= Quality.FULLHDBLURAY or new == Quality.UNKNOWN: + old = Quality.old_scene_quality(name, anime) + assert old == new, 'Old quality does not match new: %s != %s : %s' % (Quality.qualityStrings[old], Quality.qualityStrings[new], name) + + return Quality.UNKNOWN if result is None else result + + @staticmethod + def sceneQuality(name, anime=False): + # new = Quality.scene_quality(name, anime) + # + # if new <= Quality.FULLHDBLURAY or new == Quality.UNKNOWN: + # old = Quality.old_scene_quality(name, anime) + # assert old == new, 'New quality does not match old: %s != %s : %s' % (Quality.qualityStrings[new], Quality.qualityStrings[old], name) + + return Quality.scene_quality(name, anime) + @staticmethod def assumeQuality(name): """ @@ -389,11 +517,15 @@ class Quality(object): webdl = re.search(r"web.?dl|web(rip|mux|hd)", base_filename, re.I) is not None ret = Quality.UNKNOWN - if height > 1000: + 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: + elif 680 < height <= 800: ret = ((Quality.HDTV, Quality.HDBLURAY)[bluray], Quality.HDWEBDL)[webdl] - elif height < 680: + 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 @@ -516,21 +648,32 @@ Quality.ARCHIVED = [Quality.compositeStatus(ARCHIVED, x) for x in Quality.qualit HD720p = Quality.combineQualities([Quality.HDTV, Quality.HDWEBDL, Quality.HDBLURAY], []) HD1080p = Quality.combineQualities([Quality.FULLHDTV, Quality.FULLHDWEBDL, Quality.FULLHDBLURAY], []) +UHD_4K = Quality.combineQualities([Quality.UHD_4K_TV, Quality.UHD_4K_WEBDL, Quality.UHD_4K_BLURAY], []) +UHD_8K = Quality.combineQualities([Quality.UHD_8K_TV, Quality.UHD_8K_WEBDL, Quality.UHD_8K_BLURAY], []) SD = Quality.combineQualities([Quality.SDTV, Quality.SDDVD], []) HD = Quality.combineQualities([HD720p, HD1080p], []) -ANY = Quality.combineQualities([SD, HD], []) +UHD = Quality.combineQualities([UHD_4K, UHD_8K], []) +ANY = Quality.combineQualities([SD, HD, UHD], []) # legacy template, cant remove due to reference in mainDB upgrade? BEST = Quality.combineQualities([Quality.SDTV, Quality.HDTV, Quality.HDWEBDL], [Quality.HDTV]) -qualityPresets = (SD, HD, HD720p, HD1080p, ANY) +qualityPresets = ( + SD, + HD, HD720p, HD1080p, + UHD, UHD_4K, UHD_8K, + ANY, +) qualityPresetStrings = NumDict({ SD: "SD", HD: "HD", HD720p: "HD720p", HD1080p: "HD1080p", - ANY: "Any" + UHD: "UHD", + UHD_4K: "UHD-4K", + UHD_8K: "UHD-8K", + ANY: "Any", }) diff --git a/sickrage/recompiled/__init__.py b/sickrage/recompiled/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/sickrage/recompiled/tags.py b/sickrage/recompiled/tags.py new file mode 100644 index 0000000000000000000000000000000000000000..398481577b69072c83d20498068a7bb3c2ddf785 --- /dev/null +++ b/sickrage/recompiled/tags.py @@ -0,0 +1,24 @@ + +import re + + +# Resolutions +resolution = re.compile(r'(?P<vres>4320|2160|1080|720|480|360)(?P<scan>[pi])', re.IGNORECASE) + +# Sources +tv = re.compile(r'([sph]d).?tv|tv(rip|mux)', re.IGNORECASE) +dvd = re.compile(r'(?P<hd>hd)dvd|dvd(?P<rip>rip|mux)', re.IGNORECASE) +web = re.compile(r'(web.?(?P<dl>dl)|web(?P<rip>rip|mux|hd))', re.IGNORECASE) +bluray = re.compile(r'(blue?-?ray|b[rd](?:rip|mux))', re.IGNORECASE) +sat = re.compile(r'(dsr|satrip)', re.IGNORECASE) +itunes = re.compile(r'(itunes)', re.IGNORECASE) + +# Codecs +avc = re.compile(r'([xh].?26[45])', re.IGNORECASE) +xvid = re.compile(r'(xvid|divx)', re.IGNORECASE) +mpeg = re.compile(r'(mpeg-?2)', re.IGNORECASE) + +# anime +anime_sd = re.compile(r'(848x480|480p|360p|xvid)', re.IGNORECASE) +anime_hd = re.compile(r'((1280|960)x720|720p)', re.IGNORECASE) +anime_fullhd = re.compile(r'(1920x1080|1080p)', re.IGNORECASE) diff --git a/sickrage/tagger/__init__.py b/sickrage/tagger/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/sickrage/tagger/episode.py b/sickrage/tagger/episode.py new file mode 100644 index 0000000000000000000000000000000000000000..e3c9543400b0ca970ff5752ec5935f980061f5a5 --- /dev/null +++ b/sickrage/tagger/episode.py @@ -0,0 +1,246 @@ +# coding=utf-8 + +""" +Episode tagger to extract information from episodes +""" + +from __future__ import unicode_literals + +import re +from sickrage.recompiled import tags +from sickrage.helper.common import try_int + + +class EpisodeTags(object): + """ + Quality tags + """ + def __init__(self, name): + self.name = name + self.rex = { + u'res': tags.resolution, + u'bluray': tags.bluray, + u'web': tags.web, + u'itunes': tags.itunes, + u'dvd': tags.dvd, + u'sat': tags.sat, + u'tv': tags.tv, + u'avc': tags.avc, + u'mpeg': tags.mpeg, + u'xvid': tags.xvid, + } + + def _get_match_obj(self, attr, regex=None, flags=0): + match_obj = '%s_match' % attr + try: + return getattr(self, match_obj) + except (KeyError, AttributeError): + regex = regex or self.rex[attr] + result = regex.search(self.name, flags) + setattr(self, match_obj, result) + return result + + # RESOLUTION + @property + def res(self): + """ + The resolution tag found in the name + + :returns: an empty string if not found + """ + attr = 'res' + match = self._get_match_obj(attr) + return '' if not match else match.group().lower() + + @property + def vres(self): + """ + The vertical found in the name + + :returns: an empty string if not found + """ + attr = 'res' + match = self._get_match_obj(attr) + return None if not match else try_int(match.group('vres')) + + @property + def scan(self): + """ + The type of scan found in the name + + e.g. `i` for Interlaced, `p` for Progressive Scan + + :returns: an empty string if not found + """ + attr = 'res' + match = self._get_match_obj(attr) + return '' if not match else match.group('scan').lower() + + # SOURCES + @property + def bluray(self): + """ + The bluray tag found in the name + + :returns: an empty string if not found + """ + attr = 'bluray' + match = self._get_match_obj(attr) + return '' if not match else match.group() + + @property + def hddvd(self): + """ + The hddvd tag found in the name + + :returns: an empty string if not found + """ + attr = 'dvd' + match = self._get_match_obj(attr) + return None if not match else match.group('hd') + + @property + def itunes(self): + """ + The iTunes tag found in the name + + :returns: an empty string if not found + """ + attr = 'itunes' + match = self._get_match_obj(attr) + return '' if not match else match.group() + + @property + def web(self): + """ + The web tag found in the name + + :returns: an empty string if not found + """ + attr = 'web' + match = self._get_match_obj(attr) + return '' if not match else match.group('dl') or match.group('rip') + + @property + def sat(self): + """ + The sat tag found in the name + + :returns: an empty string if not found + """ + attr = 'sat' + match = self._get_match_obj(attr) + return None if not match else match.group() + + @property + def dvd(self): + """ + The dvd tag found in the name + + :returns: an empty string if not found + """ + attr = 'dvd' + match = self._get_match_obj(attr) + return '' if not match else match.group('rip') + + @property + def tv(self): + attr = 'tv' + match = self._get_match_obj(attr) + return '' if not match else (match.group(1) or match.group(2)).lower() + + # CODECS + @property + def hevc(self): + """ + The hevc tag found in the name + + :returns: an empty string if not found + """ + return '' if not (self.avc[:-1] == '5') else self.avc + + @property + def avc(self): + """ + The avc tag found in the name + + :returns: an empty string if not found + """ + attr = 'avc' + match = self._get_match_obj(attr) + return '' if not match else match.group() + + @property + def avc_free(self): + """ + The free avc codec found in the name + e.g.: x.265 or X264 + + :returns: an empty string if not found + """ + return u'' if self.avc[0].lower() != 'x' else self.avc + + @property + def avc_non_free(self): + """ + The non-free avc codec found in the name + e.g.: h.265 or H264 + + :returns: an empty string if not found + """ + return u'' if self.avc[0].lower() != 'h' else self.avc + + @property + def mpeg(self): + """ + The mpeg tag found in the name + + :returns: an empty string if not found + """ + attr = 'mpeg' + match = self._get_match_obj(attr) + return '' if not match else match.group() + + @property + def xvid(self): + """ + The xvid tag found in the name + + :returns: an empty string if not found + """ + attr = 'xvid' + match = self._get_match_obj(attr) + return '' if not match else match.group() + + # MISCELLANEOUS + @property + def hrws(self): + """ + The hrws tag found in the name + + HR = High Resolution + WS = Wide Screen + PD TV = Pure Digital Television + + :returns: an empty string if not found + """ + attr = 'hrws' + match = None + if self.avc and self.tv == 'pd': + regex = re.compile(ur'(hr.ws.pdtv).%s' % self.avc, re.IGNORECASE) + match = self._get_match_obj(attr, regex) + return '' if not match else match.group() + + @property + def raw(self): + """ + The raw tag found in the name + + :return: an empty string if not found + """ + attr = 'raw' + match = None + if self.res and self.tv == 'hd': + regex = re.compile(ur'(%s.hdtv)' % self.res, re.IGNORECASE) + match = self._get_match_obj(attr, regex) + return '' if not match else match.group() diff --git a/tests/common_tests.py b/tests/common_tests.py index 9dd29d9d17808fbbc0bd1d6d494c62bf20f67216..e7f654cac5102edad840b0b4d86b76831636c6b7 100644 --- a/tests/common_tests.py +++ b/tests/common_tests.py @@ -97,7 +97,9 @@ class QualityStringTests(unittest.TestCase): "Test.Show.S01E02.720p.HDDVD.x264-GROUP", ], 'full_hd_bluray': ["Test.Show.S01E02.1080p.BluRay.x264-GROUP", "Test.Show.S01E02.1080p.HDDVD.x264-GROUP", ], - 'unknown': ["Test.Show.S01E02-SiCKBEARD", ], + 'unknown': ["Test.Show.S01E02-SiCKBEARD", + "Test.Show.S01E01-20.1080i.[Mux.-.1080i.-.H264.-.Ac3.].HDTVMux.GROUP", + ], } def test_sd_tv(self):