mirror of
https://github.com/jxxghp/MoviePilot.git
synced 2026-05-23 23:16:46 +00:00
193 lines
5.8 KiB
Python
193 lines
5.8 KiB
Python
from typing import Any, Dict, List, Optional, Tuple
|
||
|
||
from app.log import logger
|
||
from app.schemas.types import MediaType
|
||
|
||
try:
|
||
import moviepilot_rust as _moviepilot_rust
|
||
except Exception as err: # pragma: no cover - 取决于运行环境是否安装 Rust 扩展
|
||
_moviepilot_rust = None
|
||
_import_error = err
|
||
else:
|
||
_import_error = None
|
||
|
||
|
||
def is_available() -> bool:
|
||
"""
|
||
判断 Rust 扩展是否可用。
|
||
"""
|
||
return bool(_moviepilot_rust and _moviepilot_rust.is_available())
|
||
|
||
|
||
def import_error() -> Optional[Exception]:
|
||
"""
|
||
返回 Rust 扩展导入失败的异常,便于调试构建问题。
|
||
"""
|
||
return _import_error
|
||
|
||
|
||
def is_anime(name: str) -> Optional[bool]:
|
||
"""
|
||
使用 Rust 快路径判断标题是否为动漫格式,不可用时返回 None。
|
||
"""
|
||
if not _moviepilot_rust:
|
||
return None
|
||
try:
|
||
return bool(_moviepilot_rust.is_anime_fast(name or ""))
|
||
except BaseException as err:
|
||
_raise_non_rust_panic(err)
|
||
logger.debug(f"Rust 动漫识别失败,回退 Python:{err}")
|
||
return None
|
||
|
||
|
||
def find_metainfo(title: str) -> Optional[Tuple[str, Dict[str, Any]]]:
|
||
"""
|
||
使用 Rust 快路径提取标题中的内嵌媒体标签,不可用时返回 None。
|
||
"""
|
||
if not _moviepilot_rust:
|
||
return None
|
||
try:
|
||
result = _moviepilot_rust.find_metainfo_fast(title or "")
|
||
except BaseException as err:
|
||
_raise_non_rust_panic(err)
|
||
logger.debug(f"Rust 内嵌媒体标签识别失败,回退 Python:{err}")
|
||
return None
|
||
metainfo = {
|
||
"tmdbid": result.get("tmdbid"),
|
||
"doubanid": result.get("doubanid"),
|
||
"type": _coerce_media_type(result.get("type")),
|
||
"begin_season": result.get("begin_season"),
|
||
"end_season": result.get("end_season"),
|
||
"total_season": result.get("total_season"),
|
||
"begin_episode": result.get("begin_episode"),
|
||
"end_episode": result.get("end_episode"),
|
||
"total_episode": result.get("total_episode"),
|
||
}
|
||
return result.get("title"), metainfo
|
||
|
||
|
||
def parse_video_title(
|
||
title: str,
|
||
isfile: bool = False,
|
||
media_exts: Optional[List[str]] = None,
|
||
) -> Optional[Dict[str, Any]]:
|
||
"""
|
||
使用 Rust 执行影视标题主识别流程,不可用时返回 None。
|
||
"""
|
||
if not _moviepilot_rust:
|
||
return None
|
||
try:
|
||
return _moviepilot_rust.parse_video_title_fast(title or "", isfile, media_exts or [])
|
||
except BaseException as err:
|
||
_raise_non_rust_panic(err)
|
||
logger.debug(f"Rust 影视标题主识别失败,回退 Python:{err}")
|
||
return None
|
||
|
||
|
||
def parse_filter_rule(expression: str) -> Optional[list]:
|
||
"""
|
||
使用 Rust 解析过滤规则表达式,不可用时返回 None。
|
||
"""
|
||
if not _moviepilot_rust:
|
||
return None
|
||
try:
|
||
return _moviepilot_rust.parse_filter_rule_fast(expression)
|
||
except BaseException as err:
|
||
_raise_non_rust_panic(err)
|
||
logger.debug(f"Rust 过滤规则解析失败,回退 Python:{err}")
|
||
return None
|
||
|
||
|
||
def filter_torrents(
|
||
rule_set: Dict[str, dict],
|
||
rule_strings: List[str],
|
||
torrents: List[dict],
|
||
media_info: Optional[dict] = None,
|
||
) -> Optional[list]:
|
||
"""
|
||
使用 Rust 批量执行种子过滤,不可用或不兼容时返回 None。
|
||
"""
|
||
if not _moviepilot_rust:
|
||
return None
|
||
try:
|
||
return _moviepilot_rust.filter_torrents_fast(rule_set, rule_strings, torrents, media_info)
|
||
except BaseException as err:
|
||
_raise_non_rust_panic(err)
|
||
logger.debug(f"Rust 种子过滤失败,回退 Python:{err}")
|
||
return None
|
||
|
||
|
||
def build_indexer_search_url(config: dict) -> Optional[str]:
|
||
"""
|
||
使用 Rust 根据普通 indexer 配置生成搜索 URL,不可用时返回 None。
|
||
"""
|
||
if not _moviepilot_rust:
|
||
return None
|
||
try:
|
||
return _moviepilot_rust.build_indexer_search_url_fast(config)
|
||
except BaseException as err:
|
||
_raise_non_rust_panic(err)
|
||
logger.debug(f"Rust 站点搜索 URL 生成失败,回退 Python:{err}")
|
||
return None
|
||
|
||
|
||
def parse_indexer_torrents(
|
||
html_text: str,
|
||
domain: str,
|
||
list_config: dict,
|
||
fields: dict,
|
||
category: Optional[dict],
|
||
result_num: int,
|
||
) -> Optional[List[dict]]:
|
||
"""
|
||
使用 Rust 批量解析普通 indexer 页面,不支持的配置返回 None。
|
||
"""
|
||
if not _moviepilot_rust:
|
||
return None
|
||
try:
|
||
return _moviepilot_rust.parse_indexer_torrents_fast(
|
||
html_text or "",
|
||
domain or "",
|
||
list_config or {},
|
||
fields or {},
|
||
category,
|
||
int(result_num or 0),
|
||
)
|
||
except BaseException as err:
|
||
_raise_non_rust_panic(err)
|
||
logger.debug(f"Rust 站点页面解析失败,回退 Python:{err}")
|
||
return None
|
||
|
||
|
||
def parse_rss_items(xml_text: str, max_items: int) -> Optional[List[dict]]:
|
||
"""
|
||
使用 Rust 批量解析 RSS/Atom 条目,不可用或解析失败时返回 None。
|
||
"""
|
||
if not _moviepilot_rust:
|
||
return None
|
||
try:
|
||
return _moviepilot_rust.parse_rss_items_fast(xml_text or "", int(max_items or 0))
|
||
except BaseException as err:
|
||
_raise_non_rust_panic(err)
|
||
logger.debug(f"Rust RSS 条目解析失败,回退 Python:{err}")
|
||
return None
|
||
|
||
|
||
def _coerce_media_type(value: Optional[str]) -> Optional[MediaType]:
|
||
"""
|
||
将 Rust 返回的媒体类型字符串转换为系统 MediaType。
|
||
"""
|
||
if value == "movies":
|
||
return MediaType.MOVIE
|
||
if value == "tv":
|
||
return MediaType.TV
|
||
return None
|
||
|
||
|
||
def _raise_non_rust_panic(err: BaseException) -> None:
|
||
"""
|
||
只吞掉 Rust 扩展 panic/异常,保留用户中断和进程退出语义。
|
||
"""
|
||
if isinstance(err, (KeyboardInterrupt, SystemExit)):
|
||
raise err
|