diff --git a/app/core/meta/metavideo.py b/app/core/meta/metavideo.py index aa9caa7d..5941d0d8 100644 --- a/app/core/meta/metavideo.py +++ b/app/core/meta/metavideo.py @@ -10,6 +10,7 @@ from app.core.meta.releasegroup import ReleaseGroupsMatcher from app.schemas.types import MediaType from app.utils.string import StringUtils from app.utils.tokens import Tokens +from app.core.meta.streamingplatform import StreamingPlatforms class MetaVideo(MetaBase): @@ -31,7 +32,7 @@ class MetaVideo(MetaBase): _part_re = r"(^PART[0-9ABI]{0,2}$|^CD[0-9]{0,2}$|^DVD[0-9]{0,2}$|^DISK[0-9]{0,2}$|^DISC[0-9]{0,2}$)" _roman_numerals = r"^(?=[MDCLXVI])M*(C[MD]|D?C{0,3})(X[CL]|L?X{0,3})(I[XV]|V?I{0,3})$" _source_re = r"^BLURAY$|^HDTV$|^UHDTV$|^HDDVD$|^WEBRIP$|^DVDRIP$|^BDRIP$|^BLU$|^WEB$|^BD$|^HDRip$|^REMUX$|^UHD$" - _effect_re = r"^SDR$|^HDR\d*$|^DOLBY$|^DOVI$|^DV$|^3D$|^REPACK$|^HLG$|^HDR10(\+|Plus)$" + _effect_re = r"^SDR$|^HDR\d*$|^DOLBY$|^DOVI$|^DV$|^3D$|^REPACK$|^HLG$|^HDR10(\+|Plus)$|^EDR$|^HQ$" _resources_type_re = r"%s|%s" % (_source_re, _effect_re) _name_no_begin_re = r"^[\[【].+?[\]】]" _name_no_chinese_re = r".*版|.*字幕" @@ -51,7 +52,7 @@ class MetaVideo(MetaBase): _resources_pix_re = r"^[SBUHD]*(\d{3,4}[PI]+)|\d{3,4}X(\d{3,4})" _resources_pix_re2 = r"(^[248]+K)" _video_encode_re = r"^(H26[45])$|^(x26[45])$|^AVC$|^HEVC$|^VC\d?$|^MPEG\d?$|^Xvid$|^DivX$|^AV1$|^HDR\d*$|^AVS(\+|[23])$" - _audio_encode_re = r"^DTS\d?$|^DTSHD$|^DTSHDMA$|^Atmos$|^TrueHD\d?$|^AC3$|^\dAudios?$|^DDP\d?$|^DD\+\d?$|^DD\d?$|^LPCM\d?$|^AAC\d?$|^FLAC\d?$|^HD\d?$|^MA\d?$|^HR\d?$|^Opus\d?$|^Vorbis\d?$" + _audio_encode_re = r"^DTS\d?$|^DTSHD$|^DTSHDMA$|^Atmos$|^TrueHD\d?$|^AC3$|^\dAudios?$|^DDP\d?$|^DD\+\d?$|^DD\d?$|^LPCM\d?$|^AAC\d?$|^FLAC\d?$|^HD\d?$|^MA\d?$|^HR\d?$|^Opus\d?$|^Vorbis\d?$|^AV[3S]A$" def __init__(self, title: str, subtitle: str = None, isfile: bool = False): """ @@ -66,6 +67,8 @@ class MetaVideo(MetaBase): original_title = title self._source = "" self._effect = [] + self.web_source = None + self._index = 0 # 判断是否纯数字命名 if isfile \ and title.isdigit() \ @@ -93,9 +96,12 @@ class MetaVideo(MetaBase): # 拆分tokens tokens = Tokens(title) self.tokens = tokens + # 实例化StreamingPlatforms对象 + streaming_platforms = StreamingPlatforms() # 解析名称、年份、季、集、资源类型、分辨率等 token = tokens.get_next() while token: + self._index += 1 # 更新当前处理的token索引 # Part self.__init_part(token) # 标题 @@ -116,6 +122,9 @@ class MetaVideo(MetaBase): # 资源类型 if self._continue_flag: self.__init_resource_type(token) + # 流媒体平台 + if self._continue_flag: + self.__init_web_source(token, streaming_platforms) # 视频编码 if self._continue_flag: self.__init_video_encode(token) @@ -131,6 +140,9 @@ class MetaVideo(MetaBase): self.resource_effect = " ".join(self._effect) if self._source: self.resource_type = self._source.strip() + # 添加流媒体平台 + if self.web_source: + self.resource_type = f"{self.web_source} {self.resource_type}" # 提取原盘DIY if self.resource_type and "BluRay" in self.resource_type: if (self.subtitle and re.findall(r'D[Ii]Y', self.subtitle)) \ @@ -574,6 +586,57 @@ class MetaVideo(MetaBase): self._effect.append(effect) self._last_token = effect.upper() + def __init_web_source(self, token: str, streaming_platforms: StreamingPlatforms): + """ + 识别流媒体平台 + """ + if not self.name: + return + + platform_name = None + query_range = 1 + + prev_token = None + prev_idx = self._index - 2 + if prev_idx >= 0 and prev_idx < len(self.tokens._tokens): + prev_token = self.tokens._tokens[prev_idx] + + next_token = self.tokens.peek() + + if streaming_platforms.is_streaming_platform(token): + platform_name = streaming_platforms.get_streaming_platform_name(token) + else: + for adjacent_token, is_next in [(prev_token, False), (next_token, True)]: + if not adjacent_token or platform_name: + continue + + for separator in [" ", "-"]: + if is_next: + combined_token = f"{token}{separator}{adjacent_token}" + else: + combined_token = f"{adjacent_token}{separator}{token}" + + if streaming_platforms.is_streaming_platform(combined_token): + platform_name = streaming_platforms.get_streaming_platform_name(combined_token) + query_range = 2 + if is_next: + self.tokens.get_next() + break + + if not platform_name: + return + + web_tokens = ["WEB", "DL", "WEBDL", "WEBRIP"] + match_start_idx = self._index - query_range + match_end_idx = self._index - 1 + start_index = max(0, match_start_idx - query_range) + end_index = min(len(self.tokens._tokens), match_end_idx + 1 + query_range) + tokens_to_check = self.tokens._tokens[start_index:end_index] + + if any(tok and tok.upper() in web_tokens for tok in tokens_to_check): + self.web_source = platform_name + self._continue_flag = False + def __init_video_encode(self, token: str): """ 识别视频编码 diff --git a/app/core/meta/streamingplatform.py b/app/core/meta/streamingplatform.py new file mode 100644 index 00000000..ea33d88e --- /dev/null +++ b/app/core/meta/streamingplatform.py @@ -0,0 +1,104 @@ +from typing import Dict, Optional, List, Tuple + +from app.utils.singleton import Singleton + + +class StreamingPlatforms(metaclass=Singleton): + """ + 流媒体平台简称与全称。 + """ + STREAMING_PLATFORMS: List[Tuple[str, str]] = [ + ("AMZN", "Amazon"), + ("NF", "Netflix"), + ("ATVP", "Apple TV+"), + ("iT", "iTunes"), + ("DSNP", "Disney+"), + ("HS", "Hotstar"), + ("APPS", "Disney+ MENA"), + ("PMTP", "Paramount+"), + ("HMAX", "Max"), + ("", "Max"), + ("HULU", "Hulu"), + ("MA", "Movies Anywhere"), + ("BCORE", "Bravia Core"), + ("MS", "Microsoft Store"), + ("SHO", "Showtime"), + ("STAN", "Stan"), + ("PCOK", "Peacock"), + ("SKST", "SkyShowtime"), + ("NOW", "Now TV"), + ("FXTL", "Foxtel Now"), + ("BNGE", "Binge"), + ("CRKL", "Crackle"), + ("RKTN", "Rakuten TV"), + ("ALL4", "All 4"), + ("AS", "Adult Swim"), + ("BRTB", "Brtb TV"), + ("CNLP", "Canal+"), + ("CRIT", "Criterion Channel"), + ("DSCP", "Discovery+"), + ("", "ESPN"), + ("FOOD", "Food Network"), + ("MUBI", "Mubi"), + ("PLAY", "Google Play"), + ("YT", "YouTube"), + ("", "friDay"), + ("", "KKTV"), + ("", "ofiii"), + ("", "LiTV"), + ("", "MyVideo"), + ("Hami", "Hami Video"), + ("", "meWATCH"), + ("CATCHPLAY", "CATCHPLAY+"), + ("", "LINE TV"), + ("VIU", "Viu"), + ("IQ", ""), + ("", "WeTV"), + ("ABMA", "Abema"), + ("ADN", ""), + ("AT-X", ""), + ("Baha", ""), + ("BG", "B-Global"), + ("CR", "Crunchyroll"), + ("", "DMM"), + ("FOD", ""), + ("FUNi", "Funimation"), + ("HIDI", "HIDIVE"), + ("UNXT", "U-NEXT"), + ] + + def __init__(self): + """初始化流媒体平台匹配器""" + self._lookup_cache = {} + self._build_cache() + + def _build_cache(self) -> None: + """ + 构建查询缓存。 + """ + self._lookup_cache.clear() + for short_name, full_name in self.STREAMING_PLATFORMS: + canonical_name = full_name or short_name + if not canonical_name: + continue + + aliases = {short_name, full_name} + for alias in aliases: + if alias: + self._lookup_cache[alias.upper()] = canonical_name + + def get_streaming_platform_name(self, platform_code: str) -> Optional[str]: + """ + 根据流媒体平台简称或全称获取标准名称。 + """ + if platform_code is None: + return None + return self._lookup_cache.get(platform_code.upper()) + + def is_streaming_platform(self, name: str) -> bool: + """ + 判断给定的字符串是否为已知的流媒体平台代码或名称。 + """ + if name is None: + return False + return name.upper() in self._lookup_cache \ No newline at end of file diff --git a/tests/cases/meta.py b/tests/cases/meta.py index 79ef9cb0..e28f80fc 100644 --- a/tests/cases/meta.py +++ b/tests/cases/meta.py @@ -153,7 +153,7 @@ meta_cases = [{ "part": "", "season": "S01", "episode": "E02", - "restype": "WEB-DL", + "restype": "B-Global WEB-DL", "pix": "1080p", "video_codec": "x264", "audio_codec": "AAC" @@ -569,7 +569,7 @@ meta_cases = [{ "part": "", "season": "S02", "episode": "E05", - "restype": "WEB-DL", + "restype": "Crunchyroll WEB-DL", "pix": "1080p", "video_codec": "x264", "audio_codec": "AAC" @@ -649,7 +649,7 @@ meta_cases = [{ "part": "", "season": "", "episode": "", - "restype": "WEBRip", + "restype": "Netflix WEBRip", "pix": "1080p", "video_codec": "H264", "audio_codec": "DDP 5.1" @@ -681,7 +681,7 @@ meta_cases = [{ "part": "", "season": "S01", "episode": "E16", - "restype": "WEB-DL", + "restype": "KKTV WEB-DL", "pix": "1080p", "video_codec": "x264", "audio_codec": "AAC" @@ -921,7 +921,7 @@ meta_cases = [{ "part": "", "season": "S06", "episode": "E06", - "restype": "WEBRip", + "restype": "Max WEBRip", "pix": "1080p", "video_codec": "x264", "audio_codec": "DD 5.1" @@ -937,7 +937,7 @@ meta_cases = [{ "part": "", "season": "S06", "episode": "E05", - "restype": "WEBRip", + "restype": "Max WEBRip", "pix": "1080p", "video_codec": "x264", "audio_codec": "DD 5.1" @@ -969,7 +969,7 @@ meta_cases = [{ "part": "", "season": "S02", "episode": "", - "restype": "WEB-DL", + "restype": "Netflix WEB-DL", "pix": "2160p", "video_codec": "H265", "audio_codec": "DDP 5.1 Atmos"