From 53a169d955ab3f9a292ba6edce63b9e168dd7e19 Mon Sep 17 00:00:00 2001 From: jxxghp Date: Sun, 9 Aug 2026 18:41:54 +0800 Subject: [PATCH] fix: preserve all recognized resource types --- app/core/meta/metavideo.py | 97 +++++++++++++++++++++----------------- app/core/metainfo.py | 6 +-- requirements.in | 2 +- tests/cases/meta.py | 2 +- tests/test_metainfo.py | 51 ++++++++++++++------ 5 files changed, 95 insertions(+), 63 deletions(-) diff --git a/app/core/meta/metavideo.py b/app/core/meta/metavideo.py index c7a52f9a3..0f9e8641b 100644 --- a/app/core/meta/metavideo.py +++ b/app/core/meta/metavideo.py @@ -28,22 +28,27 @@ DIY_TITLE_RE = re.compile(r'-DIY@', re.IGNORECASE) DESCRIPTION_SPLIT_RE = re.compile(r'[\s/|]+') SPACE_RE = re.compile(r'\s+') SEASON_SUFFIX_RE = re.compile(r"SEASON$", re.IGNORECASE) -REMUX_RESOURCE_RE = re.compile(r'(? Optional[str]: - """ - 根据原始标题补全连续出现的复合资源类型。 - - :param resource_type: 解析器已识别的资源类型 - :param title: 原始标题 - :return: 补全后的资源类型 - """ - if resource_type == "UHD BluRay" \ - and title \ - and REMUX_RESOURCE_RE.search(title): - return f"{resource_type} REMUX" - return resource_type +SOURCE_RE = ( + r"^BLURAY$|^HDTV$|^UHDTV$|^HDDVD$|^WEBRIP$|^DVDRIP$|^BDRIP$|" + r"^BLU$|^WEB$|^BD$|^HDRip$|^REMUX$|^UHD$" +) +SOURCE_PATTERN = re.compile(r"(%s)" % SOURCE_RE, re.IGNORECASE) +SOURCE_NAMES = { + "BLURAY": "BluRay", + "HDTV": "HDTV", + "UHDTV": "UHDTV", + "HDDVD": "HDDVD", + "WEBRIP": "WEBRip", + "DVDRIP": "DVDRip", + "BDRIP": "BDRIP", + "BLU": "BLU", + "WEB": "WEB", + "BD": "BD", + "HDRIP": "HDRip", + "REMUX": "REMUX", + "UHD": "UHD", +} class MetaVideo(MetaBase): @@ -57,14 +62,14 @@ class MetaVideo(MetaBase): _last_token_type = "" _continue_flag = True _unknown_name_str = "" - _source = "" + _sources = [] _effect = [] # 正则式区 _season_re = r"S(\d{3})|^S(\d{1,3})$|S(\d{1,3})E" _episode_re = r"EP?(\d{2,4})$|^EP?(\d{1,4})$|^S\d{1,2}EP?(\d{1,4})$|S\d{2}EP?(\d{2,4})" _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$" + _source_re = SOURCE_RE _effect_re = r"^SDR$|^HDR\d*$|^HDRVIVID$|^DOLBY$|^DOVI$|^DV$|^3D$|^REPACK$|^HLG$|^HDR10(\+|Plus)$|^HDR10P$|^VIVID$|^EDR$|^HQ$" _resources_type_re = r"%s|%s" % (_source_re, _effect_re) _name_no_begin_re = r"^[\[【].+?[\]】]" @@ -91,7 +96,7 @@ class MetaVideo(MetaBase): _episode_pattern = re.compile(_episode_re, re.IGNORECASE) _part_pattern = re.compile(_part_re, re.IGNORECASE) _roman_numerals_pattern = re.compile(_roman_numerals) - _source_pattern = re.compile(r"(%s)" % _source_re, re.IGNORECASE) + _source_pattern = SOURCE_PATTERN _effect_pattern = re.compile(r"(%s)" % _effect_re, re.IGNORECASE) _resources_type_pattern = re.compile(r"(%s)" % _resources_type_re, re.IGNORECASE) _name_no_chinese_pattern = re.compile(_name_no_chinese_re, re.IGNORECASE) @@ -114,7 +119,7 @@ class MetaVideo(MetaBase): if not title: return original_title = title - self._source = "" + self._sources = [] self._effect = [] self._index = 0 # 判断是否纯数字命名 @@ -200,8 +205,8 @@ class MetaVideo(MetaBase): if self._effect: self._effect.reverse() self.resource_effect = " ".join(self._effect) - if self._source: - self.resource_type = normalize_resource_type(self._source.strip(), original_title) + if self._sources: + self.resource_type = " ".join(self._sources) # 提取原盘DIY if self.resource_type and "BluRay" in self.resource_type: if (self.subtitle and DIY_RE.search(self.subtitle)) \ @@ -590,6 +595,29 @@ class MetaVideo(MetaBase): elif token.upper() == "EPISODE": self._last_token_type = "EPISODE" + def __append_resource_source(self, source: str) -> None: + """ + 按出现顺序追加资源类型并忽略重复项。 + + :param source: 原始资源类型标记 + """ + source_name = SOURCE_NAMES.get(source.upper(), source) + if source_name.casefold() not in { + item.casefold() for item in self._sources + }: + self._sources.append(source_name) + + def __replace_last_resource_source(self, source: str, replacement: str) -> None: + """ + 将拆分的资源类型前缀替换为完整规范名称。 + + :param source: 待替换的末尾资源类型 + :param replacement: 完整资源类型 + """ + if self._sources and self._sources[-1].casefold() == source.casefold(): + self._sources.pop() + self.__append_resource_source(replacement) + def __init_resource_type(self, token): """ 识别资源类型 @@ -599,32 +627,17 @@ class MetaVideo(MetaBase): if token.upper() == "DL" \ and self._last_token_type == "source" \ and self._last_token == "WEB": - self._source = "WEB-DL" + self.__replace_last_resource_source("WEB", "WEB-DL") self._continue_flag = False return elif token.upper() == "RAY" \ and self._last_token_type == "source" \ and self._last_token == "BLU": - # UHD BluRay组合 - if self._source == "UHD": - self._source = "UHD BluRay" - else: - self._source = "BluRay" + self.__replace_last_resource_source("BLU", "BluRay") self._continue_flag = False return elif token.upper() == "WEBDL": - self._source = "WEB-DL" - self._continue_flag = False - return - # UHD REMUX组合 - if token.upper() == "REMUX" \ - and self._source == "BluRay": - self._source = "BluRay REMUX" - self._continue_flag = False - return - elif token.upper() == "BLURAY" \ - and self._source == "UHD": - self._source = "UHD BluRay" + self.__append_resource_source("WEB-DL") self._continue_flag = False return source_res = self._source_pattern.search(token) @@ -632,9 +645,9 @@ class MetaVideo(MetaBase): self._last_token_type = "source" self._continue_flag = False self._stop_name_flag = True - if not self._source: - self._source = source_res.group(1) - self._last_token = self._source.upper() + source = source_res.group(1) + self.__append_resource_source(source) + self._last_token = source.upper() return effect_res = self._effect_pattern.search(token) if effect_res: diff --git a/app/core/metainfo.py b/app/core/metainfo.py index 1c19345e7..db8dbc69c 100644 --- a/app/core/metainfo.py +++ b/app/core/metainfo.py @@ -11,7 +11,6 @@ from app.core.meta.infopath import ( clear_parsed_title_for_parent_merge, should_use_parent_title_for_file_stem, ) -from app.core.meta.metavideo import normalize_resource_type from app.core.meta.words import WordsMatcher from app.log import logger from app.schemas.types import MediaType @@ -380,10 +379,7 @@ def _meta_from_rust(parsed: dict) -> Optional[MetaBase]: "begin_episode": parsed.get("begin_episode"), "end_episode": parsed.get("end_episode"), "part": parsed.get("part"), - "resource_type": normalize_resource_type( - parsed.get("resource_type"), - parsed.get("org_string") or parsed.get("title"), - ), + "resource_type": parsed.get("resource_type"), "resource_effect": parsed.get("resource_effect"), "resource_pix": parsed.get("resource_pix"), "resource_team": parsed.get("resource_team"), diff --git a/requirements.in b/requirements.in index c87dad879..b9500b48b 100644 --- a/requirements.in +++ b/requirements.in @@ -1,4 +1,4 @@ -moviepilot-rust~=0.2.5 +moviepilot-rust~=0.2.6 pydantic>=2.13.4,<3.0.0 pydantic-settings>=2.14.2,<3.0.0 SQLAlchemy~=2.0.50 diff --git a/tests/cases/meta.py b/tests/cases/meta.py index 3ee5c870a..a53a0bb93 100644 --- a/tests/cases/meta.py +++ b/tests/cases/meta.py @@ -1246,7 +1246,7 @@ meta_cases = [{ "part": "", "season": "", "episode": "", - "restype": "UHD", + "restype": "UHD BluRay", "pix": "2160p", "video_codec": "HEVC", "audio_codec": "DTS-HD MA 5.1" diff --git a/tests/test_metainfo.py b/tests/test_metainfo.py index be074934c..d8e0975e6 100644 --- a/tests/test_metainfo.py +++ b/tests/test_metainfo.py @@ -3,6 +3,8 @@ from pathlib import Path from types import SimpleNamespace from unittest.mock import patch +import pytest + from app.core.metainfo import MetaInfo, MetaInfoPath, find_metainfo from app.core.meta import MetaBase, MetaMusic from app.core.meta.metaanime import MetaAnime @@ -173,25 +175,46 @@ def test_python_metainfo_fallback_preserves_xxx_movie_title(): assert meta.audio_encode == "DDP 5.1" -def test_metainfo_preserves_uhd_bluray_remux_resource_type(): - """默认解析入口应保留 UHD、BluRay 与 REMUX 三段资源类型。""" - meta = MetaInfo( +RESOURCE_TYPE_CASES = [ + ( "They.Will.Kill.You.2026.2160p.UHD.BluRay.Remux." - "HEVC.DV.TrueHD.7.1.Atmos.mkv" - ) - - assert meta.resource_type == "UHD BluRay REMUX" + "HEVC.DV.TrueHD.7.1.Atmos.mkv", + "UHD BluRay REMUX", + ), + ( + "Movie.2026.2160p.UHD.Blu-ray.Remux.BDRip.HEVC.mkv", + "UHD BluRay REMUX BDRIP", + ), + ( + "Movie.2026.2160p.UHD.BluRay.UHD.Remux.Remux.HEVC.mkv", + "UHD BluRay REMUX", + ), + ( + "Movie.2026.1080p.WEB-DL.WEBRip.Remux.H264.mkv", + "WEB-DL WEBRip REMUX", + ), +] -def test_python_metainfo_preserves_uhd_bluray_remux_resource_type(): - """Python 兜底解析应保留 UHD、BluRay 与 REMUX 三段资源类型。""" +@pytest.mark.parametrize(("title", "expected"), RESOURCE_TYPE_CASES) +def test_metainfo_preserves_all_resource_types(title, expected): + """默认解析入口应按顺序保留并去重所有受支持的资源类型。""" + meta = MetaInfo(title) + + assert meta.resource_type == expected + assert meta.edition.startswith(expected) + assert expected in meta.resource_term + + +@pytest.mark.parametrize(("title", "expected"), RESOURCE_TYPE_CASES) +def test_python_metainfo_preserves_all_resource_types(title, expected): + """Python 兜底解析应按顺序保留并去重所有受支持的资源类型。""" with patch("app.core.metainfo.rust_accel.parse_metainfo", return_value=None): - meta = MetaInfo( - "They.Will.Kill.You.2026.2160p.UHD.BluRay.Remux." - "HEVC.DV.TrueHD.7.1.Atmos.mkv" - ) + meta = MetaInfo(title) - assert meta.resource_type == "UHD BluRay REMUX" + assert meta.resource_type == expected + assert meta.edition.startswith(expected) + assert expected in meta.resource_term def test_metainfo_routes_audio_filename_to_music():