feat: add Rust acceleration for core parsing

This commit is contained in:
jxxghp
2026-05-22 19:57:26 +08:00
parent 7daeb17d85
commit bd4d493f34
28 changed files with 5012 additions and 77 deletions

206
app/utils/rust_accel.py Normal file
View File

@@ -0,0 +1,206 @@
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 apply_indexer_text_filters(text: Any, filters: Optional[List[dict]]) -> Optional[str]:
"""
使用 Rust 执行 indexer 文本过滤器,不可用或遇到不支持过滤器时返回 None。
"""
if not _moviepilot_rust or not filters or not isinstance(filters, list):
return None
try:
return _moviepilot_rust.apply_indexer_text_filters_fast(None if text is None else str(text), filters)
except BaseException as err:
_raise_non_rust_panic(err)
logger.debug(f"Rust 站点文本过滤失败,回退 Python{err}")
return None
def parse_filesize(text: Any) -> Optional[int]:
"""
使用 Rust 将文件大小文本转换为字节,不可用时返回 None。
"""
if not _moviepilot_rust:
return None
try:
return int(_moviepilot_rust.parse_filesize_fast(text))
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 _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