refactor: enhance customization and streaming platform handling

This commit is contained in:
jxxghp
2026-07-06 09:33:49 +08:00
parent 2f0c1252da
commit db9960d9b9
7 changed files with 66 additions and 30 deletions
+9 -2
View File
@@ -17,7 +17,7 @@ class CustomizationMatcher(metaclass=Singleton):
self._customization_re_cache = {} self._customization_re_cache = {}
@staticmethod @staticmethod
def _normalize_customization(customization): def normalize_customization(customization):
""" """
规范化自定义占位符配置,兼容历史字符串与列表两种保存格式。 规范化自定义占位符配置,兼容历史字符串与列表两种保存格式。
""" """
@@ -27,6 +27,13 @@ class CustomizationMatcher(metaclass=Singleton):
return [] return []
return list(filter(None, customization)) return list(filter(None, customization))
@staticmethod
def _normalize_customization(customization):
"""
兼容旧调用,统一转到公开的自定义占位符规范化入口。
"""
return CustomizationMatcher.normalize_customization(customization)
def match(self, title=None): def match(self, title=None):
""" """
:param title: 资源标题或文件名 :param title: 资源标题或文件名
@@ -35,7 +42,7 @@ class CustomizationMatcher(metaclass=Singleton):
if not title: if not title:
return "" return ""
# 自定义占位符需要跟随系统配置实时生效,避免单例缓存导致保存后仍沿用旧规则。 # 自定义占位符需要跟随系统配置实时生效,避免单例缓存导致保存后仍沿用旧规则。
customization = self._normalize_customization( customization = self.normalize_customization(
self.systemconfig.get(SystemConfigKey.Customization) self.systemconfig.get(SystemConfigKey.Customization)
) )
if not customization: if not customization:
+13 -9
View File
@@ -89,6 +89,18 @@ class ReleaseGroupsMatcher(metaclass=Singleton):
self.systemconfig = SystemConfigOper() self.systemconfig = SystemConfigOper()
self.__groups_re_cache = {} self.__groups_re_cache = {}
def get_release_groups(self) -> str:
"""
返回内置与用户自定义制作组组成的匹配规则。
"""
custom_release_groups = self.systemconfig.get(SystemConfigKey.CustomReleaseGroups)
if isinstance(custom_release_groups, list):
custom_release_groups = list(filter(None, custom_release_groups))
if custom_release_groups:
custom_release_groups_str = '|'.join(custom_release_groups)
return f"{self.__release_groups}|{custom_release_groups_str}"
return self.__release_groups
def __get_groups_re(self, groups: str): def __get_groups_re(self, groups: str):
""" """
发布组规则通常很长,按规则文本缓存编译结果,避免每个标题都重复编译。 发布组规则通常很长,按规则文本缓存编译结果,避免每个标题都重复编译。
@@ -108,15 +120,7 @@ class ReleaseGroupsMatcher(metaclass=Singleton):
if not title: if not title:
return "" return ""
if not groups: if not groups:
# 自定义组 groups = self.get_release_groups()
custom_release_groups = self.systemconfig.get(SystemConfigKey.CustomReleaseGroups)
if isinstance(custom_release_groups, list):
custom_release_groups = list(filter(None, custom_release_groups))
if custom_release_groups:
custom_release_groups_str = '|'.join(custom_release_groups)
groups = f"{self.__release_groups}|{custom_release_groups_str}"
else:
groups = self.__release_groups
title = f"{title} " title = f"{title} "
groups_re = self.__get_groups_re(groups) groups_re = self.__get_groups_re(groups)
unique_groups = [] unique_groups = []
+6
View File
@@ -297,6 +297,12 @@ class StreamingPlatforms(metaclass=Singleton):
if alias: if alias:
self._lookup_cache[alias.upper()] = canonical_name self._lookup_cache[alias.upper()] = canonical_name
def get_lookup_cache(self) -> dict:
"""
返回流媒体平台查询表副本,供批量解析配置复用。
"""
return dict(self._lookup_cache)
def get_streaming_platform_name(self, platform_code: str) -> Optional[str]: def get_streaming_platform_name(self, platform_code: str) -> Optional[str]:
""" """
根据流媒体平台简称或全称获取标准名称。 根据流媒体平台简称或全称获取标准名称。
+31 -10
View File
@@ -1,3 +1,4 @@
import hashlib
from pathlib import Path from pathlib import Path
from functools import lru_cache from functools import lru_cache
from typing import Tuple, List, Optional from typing import Tuple, List, Optional
@@ -40,6 +41,7 @@ _EMBY_TMDB_RE_LIST = (
re.compile(r'\{tmdbid[=\-](\d+)\}'), re.compile(r'\{tmdbid[=\-](\d+)\}'),
re.compile(r'\{tmdb[=\-](\d+)\}'), re.compile(r'\{tmdb[=\-](\d+)\}'),
) )
_RUST_PARSE_OPTIONS_CACHE_KEY = "_cache_key"
def _empty_metainfo() -> dict: def _empty_metainfo() -> dict:
@@ -72,6 +74,28 @@ def _apply_range_total(metainfo: dict, begin_key: str, end_key: str, total_key:
metainfo[total_key] = 1 metainfo[total_key] = 1
def _rust_parse_options_cache_key(options: dict) -> str:
"""
生成 Rust Meta 配置缓存键,避免扩展层每次重新展开大配置。
"""
digest = hashlib.blake2b(digest_size=16)
def update(value) -> None:
digest.update(repr(value).encode("utf-8"))
digest.update(b"\0")
streaming_platforms = options.get("streaming_platforms") or {}
update(tuple(options.get("custom_words") or []))
update(tuple(options.get("media_exts") or []))
update(options.get("release_groups") or "")
update(tuple(options.get("customization") or []))
update(tuple(sorted(
(str(key), str(value))
for key, value in streaming_platforms.items()
)))
return digest.hexdigest()
def _find_metainfo_python(title: str) -> Tuple[str, dict]: def _find_metainfo_python(title: str) -> Tuple[str, dict]:
""" """
使用 Python 解析标题中的显式媒体标签,作为 Rust 入口不可用时的兜底。 使用 Python 解析标题中的显式媒体标签,作为 Rust 入口不可用时的兜底。
@@ -209,24 +233,20 @@ def _rust_default_parse_options() -> dict:
from app.schemas.types import SystemConfigKey from app.schemas.types import SystemConfigKey
systemconfig = SystemConfigOper() systemconfig = SystemConfigOper()
custom_release_groups = systemconfig.get(SystemConfigKey.CustomReleaseGroups) release_groups = ReleaseGroupsMatcher().get_release_groups()
if isinstance(custom_release_groups, list):
custom_release_groups = list(filter(None, custom_release_groups))
release_matcher = ReleaseGroupsMatcher()
release_groups = release_matcher._ReleaseGroupsMatcher__release_groups
if custom_release_groups:
release_groups = f"{release_groups}|{'|'.join(custom_release_groups)}"
customization = CustomizationMatcher._normalize_customization( customization = CustomizationMatcher.normalize_customization(
systemconfig.get(SystemConfigKey.Customization) systemconfig.get(SystemConfigKey.Customization)
) )
return { options = {
"custom_words": systemconfig.get(SystemConfigKey.CustomIdentifiers) or [], "custom_words": systemconfig.get(SystemConfigKey.CustomIdentifiers) or [],
"media_exts": settings.RMT_MEDIAEXT + settings.RMT_SUBEXT + settings.RMT_AUDIOEXT, "media_exts": settings.RMT_MEDIAEXT + settings.RMT_SUBEXT + settings.RMT_AUDIOEXT,
"release_groups": release_groups, "release_groups": release_groups,
"customization": customization, "customization": customization,
"streaming_platforms": StreamingPlatforms()._lookup_cache, "streaming_platforms": StreamingPlatforms().get_lookup_cache(),
} }
options[_RUST_PARSE_OPTIONS_CACHE_KEY] = _rust_parse_options_cache_key(options)
return options
@lru_cache(maxsize=256) @lru_cache(maxsize=256)
@@ -236,6 +256,7 @@ def _rust_custom_parse_options(custom_words: Tuple[str, ...]) -> dict:
""" """
options = dict(_rust_default_parse_options()) options = dict(_rust_default_parse_options())
options["custom_words"] = list(custom_words) options["custom_words"] = list(custom_words)
options[_RUST_PARSE_OPTIONS_CACHE_KEY] = _rust_parse_options_cache_key(options)
return options return options
+1 -1
View File
@@ -1,4 +1,4 @@
moviepilot-rust~=0.1.14 moviepilot-rust~=0.2.0
pydantic>=2.13.4,<3.0.0 pydantic>=2.13.4,<3.0.0
pydantic-settings>=2.14.1,<3.0.0 pydantic-settings>=2.14.1,<3.0.0
SQLAlchemy~=2.0.50 SQLAlchemy~=2.0.50
+2
View File
@@ -12,6 +12,7 @@ sys.path.insert(0, str(PROJECT_ROOT))
from app.helper import rss as rss_module from app.helper import rss as rss_module
from app.helper.rss import RssHelper from app.helper.rss import RssHelper
from app.utils import rust_accel from app.utils import rust_accel
from app.utils.http import RequestUtils
class FakeRequestUtils: class FakeRequestUtils:
@@ -20,6 +21,7 @@ class FakeRequestUtils:
""" """
xml_text = "" xml_text = ""
get_decoded_xml_content = staticmethod(RequestUtils.get_decoded_xml_content)
def __init__(self, **_kwargs): def __init__(self, **_kwargs):
""" """
+4 -8
View File
@@ -12,6 +12,7 @@ from app.core import metainfo as metainfo_module
from app.core.config import settings from app.core.config import settings
from app.core.meta.customization import CustomizationMatcher from app.core.meta.customization import CustomizationMatcher
from app.core.meta.releasegroup import ReleaseGroupsMatcher from app.core.meta.releasegroup import ReleaseGroupsMatcher
from app.core.meta.streamingplatform import StreamingPlatforms
from app.db.systemconfig_oper import SystemConfigOper from app.db.systemconfig_oper import SystemConfigOper
from app.modules.indexer.spider import SiteSpider from app.modules.indexer.spider import SiteSpider
from app.schemas.types import SystemConfigKey from app.schemas.types import SystemConfigKey
@@ -188,13 +189,8 @@ def _metainfo_options(custom_words=None):
构造 Rust MetaInfo 测试所需的配置,保持和生产入口一致。 构造 Rust MetaInfo 测试所需的配置,保持和生产入口一致。
""" """
systemconfig = SystemConfigOper() systemconfig = SystemConfigOper()
custom_release_groups = systemconfig.get(SystemConfigKey.CustomReleaseGroups) release_groups = ReleaseGroupsMatcher().get_release_groups()
if isinstance(custom_release_groups, list): customization = CustomizationMatcher.normalize_customization(
custom_release_groups = list(filter(None, custom_release_groups))
release_groups = ReleaseGroupsMatcher()._ReleaseGroupsMatcher__release_groups
if custom_release_groups:
release_groups = f"{release_groups}|{'|'.join(custom_release_groups)}"
customization = CustomizationMatcher._normalize_customization(
systemconfig.get(SystemConfigKey.Customization) systemconfig.get(SystemConfigKey.Customization)
) )
return { return {
@@ -202,7 +198,7 @@ def _metainfo_options(custom_words=None):
"media_exts": settings.RMT_MEDIAEXT + settings.RMT_SUBEXT + settings.RMT_AUDIOEXT, "media_exts": settings.RMT_MEDIAEXT + settings.RMT_SUBEXT + settings.RMT_AUDIOEXT,
"release_groups": release_groups, "release_groups": release_groups,
"customization": customization, "customization": customization,
"streaming_platforms": metainfo_module._rust_parse_options()["streaming_platforms"], "streaming_platforms": StreamingPlatforms().get_lookup_cache(),
} }