mirror of
https://github.com/jxxghp/MoviePilot.git
synced 2026-09-06 07:56:52 +08:00
refactor: enhance customization and streaming platform handling
This commit is contained in:
@@ -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:
|
||||||
|
|||||||
@@ -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 = []
|
||||||
|
|||||||
@@ -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
@@ -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
@@ -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
|
||||||
|
|||||||
@@ -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):
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -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(),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user