From 545ac141fff3715ac13c3acb012d74a27edd99f8 Mon Sep 17 00:00:00 2001 From: Jan' s Date: Wed, 6 May 2026 20:54:11 +0800 Subject: [PATCH] feat: add AIRecognizerEnhancer plugin --- icons/airecognizerenhancer.png | Bin 0 -> 221789 bytes package.json | 27 +- package.v2.json | 25 + plugins.v2/airecognizerenhancer/README.md | 99 + plugins.v2/airecognizerenhancer/__init__.py | 2039 +++++++++++++++++++ plugins/airecognizerenhancer/README.md | 99 + plugins/airecognizerenhancer/__init__.py | 2039 +++++++++++++++++++ 7 files changed, 4327 insertions(+), 1 deletion(-) create mode 100644 icons/airecognizerenhancer.png create mode 100644 plugins.v2/airecognizerenhancer/README.md create mode 100644 plugins.v2/airecognizerenhancer/__init__.py create mode 100644 plugins/airecognizerenhancer/README.md create mode 100644 plugins/airecognizerenhancer/__init__.py diff --git a/icons/airecognizerenhancer.png b/icons/airecognizerenhancer.png new file mode 100644 index 0000000000000000000000000000000000000000..0185d1c8d9cbf3fb65b71f6182526e2cd68d1ecc GIT binary patch literal 221789 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 #WKcp;_8+9RG4brOf;WU6GeJR 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 N002ovPDHLkV1lghsHp$| literal 0 HcmV?d00001 diff --git a/package.json b/package.json index 8af8f18..52c2343 100644 --- a/package.json +++ b/package.json @@ -1073,5 +1073,30 @@ "history": { "v1.0": "初始版本,支持 IPv4/IPv6、泛域名、多记录配置、更新历史详情页" } + }, + "AIRecognizerEnhancer": { + "name": "AI识别增强", + "description": "直接复用 MoviePilot 当前 LLM 配置,在原生识别失败后做本地结构化识别兜底,并交回原生链路继续二次识别。", + "labels": "AI,识别,LLM,本地兜底,MoviePilot,TMDB", + "version": "0.1.12", + "icon": "airecognizerenhancer.png", + "author": "liuyuexi1987", + "level": 1, + "v2": true, + "history": { + "0.1.12": "兼容 MoviePilot 最新 LLM 路径与异步 get_llm 接口,修复最新版 MP 下插件加载失败问题。", + "0.1.11": "同步运行态版本,保持本地结构化识别、失败样本闭环和识别词建议能力一致。", + "0.1.10": "新增识别词建议模型退化时的精确规则兜底,保证批量建议/批量写入在上游异常时仍能尽量落地。", + "0.1.9": "新增失败样本精简摘要接口,并让批量建议/批量写入附带低 token 文本摘要,便于智能体直接消费。", + "0.1.8": "新增失败样本批量建议与批量写入接口,可一次处理一批失败样本,进一步减少人工逐条操作。", + "0.1.7": "新增失败样本批量复查接口,可批量重跑样本并在确认修复后批量出队。", + "0.1.6": "新增失败样本复查接口,可按当前识别词与当前识别器重跑样本,并在确认修复后自动出队。", + "0.1.5": "新增失败样本出队动作,支持按索引移除单条样本,并在写入识别词后自动移除已处理样本。", + "0.1.4": "新增失败样本洞察接口,自动归纳重复问题、失败原因和优先处理样本,帮助更快挑出值得写识别词的样本。", + "0.1.3": "新增失败样本摘要、样本清理、样本去重和保留上限控制,让样本工作流更适合长期运行与智能体使用。", + "0.1.2": "新增按失败样本直接生成建议和直接写入规则的快捷 API,进一步缩短从失败样本到 CustomIdentifiers 的闭环。", + "0.1.1": "新增失败样本查看、自定义识别词建议和一键追加写入能力,让 AI 识别增强开始和 MoviePilot 原生 CustomIdentifiers 闭环联动。", + "0.1.0": "首个可用版本,复用 MoviePilot 当前 LLM 配置,在原生识别失败后通过 Chain NameRecognize 做本地结构化兜底。" + } } -} \ No newline at end of file +} diff --git a/package.v2.json b/package.v2.json index 2059ee5..da0a17e 100644 --- a/package.v2.json +++ b/package.v2.json @@ -663,4 +663,29 @@ "v0.1.0": "初始版本" } } +, + "AIRecognizerEnhancer": { + "name": "AI识别增强", + "description": "直接复用 MoviePilot 当前 LLM 配置,在原生识别失败后做本地结构化识别兜底,并交回原生链路继续二次识别。", + "labels": "AI,识别,LLM,本地兜底,MoviePilot,TMDB", + "version": "0.1.12", + "icon": "airecognizerenhancer.png", + "author": "liuyuexi1987", + "level": 1, + "history": { + "0.1.12": "兼容 MoviePilot 最新 LLM 路径与异步 get_llm 接口,修复最新版 MP 下插件加载失败问题。", + "0.1.11": "同步运行态版本,保持本地结构化识别、失败样本闭环和识别词建议能力一致。", + "0.1.10": "新增识别词建议模型退化时的精确规则兜底,保证批量建议/批量写入在上游异常时仍能尽量落地。", + "0.1.9": "新增失败样本精简摘要接口,并让批量建议/批量写入附带低 token 文本摘要,便于智能体直接消费。", + "0.1.8": "新增失败样本批量建议与批量写入接口,可一次处理一批失败样本,进一步减少人工逐条操作。", + "0.1.7": "新增失败样本批量复查接口,可批量重跑样本并在确认修复后批量出队。", + "0.1.6": "新增失败样本复查接口,可按当前识别词与当前识别器重跑样本,并在确认修复后自动出队。", + "0.1.5": "新增失败样本出队动作,支持按索引移除单条样本,并在写入识别词后自动移除已处理样本。", + "0.1.4": "新增失败样本洞察接口,自动归纳重复问题、失败原因和优先处理样本,帮助更快挑出值得写识别词的样本。", + "0.1.3": "新增失败样本摘要、样本清理、样本去重和保留上限控制,让样本工作流更适合长期运行与智能体使用。", + "0.1.2": "新增按失败样本直接生成建议和直接写入规则的快捷 API,进一步缩短从失败样本到 CustomIdentifiers 的闭环。", + "0.1.1": "新增失败样本查看、自定义识别词建议和一键追加写入能力,让 AI 识别增强开始和 MoviePilot 原生 CustomIdentifiers 闭环联动。", + "0.1.0": "首个可用版本,复用 MoviePilot 当前 LLM 配置,在原生识别失败后通过 Chain NameRecognize 做本地结构化兜底。" + } + } } diff --git a/plugins.v2/airecognizerenhancer/README.md b/plugins.v2/airecognizerenhancer/README.md new file mode 100644 index 0000000..ea48943 --- /dev/null +++ b/plugins.v2/airecognizerenhancer/README.md @@ -0,0 +1,99 @@ +# AI识别增强 + +`AI识别增强` 用来补强 MoviePilot 原生整理链里的识别阶段。 + +它的核心思路很简单: + +- 复用 MoviePilot 当前已经启用的 LLM 配置 +- 在原生识别失败或置信度不足时,做一次本地结构化识别兜底 +- 把结果回写给 MoviePilot,继续走原生二次识别和后续整理链 + +## 适合什么场景 + +- 文件名比较脏,混有压制组、分辨率、语言、站点标记 +- 同一部剧经常出现英文名、别名、原名、翻译名混用 +- 网盘挂载、手动整理、历史资源补录时,原生识别偶尔不稳定 +- 你想把失败样本沉淀下来,后面持续优化 `CustomIdentifiers` + +## 和 MoviePilot 原版智能体的区别 + +MoviePilot 原版智能体已经提供“整理失败后自动接管再试一次”的能力。 + +这和 `AI识别增强` 有重叠,但定位不同: + +- **MP 原版智能体** + - 更偏“一次性补救” + - 适合偶发失败、想省事的场景 + +- **AI识别增强** + - 更偏“识别失败治理层” + - 除了补救当前这次,还能: + - 保存失败样本 + - 汇总样本洞察 + - 生成 `CustomIdentifiers` 建议 + - 写入识别词 + - 重放 / 复查 / 批量出队 + +一句话区分: + +- 原版智能体:自动接管一次 +- `AI识别增强`:把失败样本沉淀下来,长期减少同类失败 + +## 当前能力 + +- 监听 `ChainEventType.NameRecognize` +- 用当前 LLM 结构化判断标题、年份、类型、季集 +- 回写 `name / year / season / episode` +- 交回 MoviePilot 原生链路继续二次识别 +- 保存低置信度失败样本 +- 提供失败样本工作清单、洞察、重放、删除和清空能力 +- 生成并应用 `CustomIdentifiers` 建议 + +## 主要接口 + +- `GET /api/v1/plugin/AIRecognizerEnhancer/health` + - 查看插件状态、LLM 提供方、模型、阈值和超时配置 +- `POST /api/v1/plugin/AIRecognizerEnhancer/recognize` + - 对单个标题做一次本地结构化识别测试 +- `GET /api/v1/plugin/AIRecognizerEnhancer/failed_samples` + - 查看最近保存的失败样本 +- `GET /api/v1/plugin/AIRecognizerEnhancer/sample_worklist` + - 返回适合继续处理的失败样本摘要列表 +- `GET /api/v1/plugin/AIRecognizerEnhancer/sample_insights` + - 汇总失败原因、重复问题和优先处理样本 +- `POST /api/v1/plugin/AIRecognizerEnhancer/replay_failed_sample` + - 用当前识别词和当前识别器重放复查某条失败样本 +- `POST /api/v1/plugin/AIRecognizerEnhancer/suggest_identifiers_from_sample` + - 直接基于失败样本生成识别词建议 +- `POST /api/v1/plugin/AIRecognizerEnhancer/apply_suggested_identifier` + - 把建议规则写入系统 `CustomIdentifiers` + +其余批量接口和清理接口可以按需要继续使用,详细路径以插件 `get_api()` 暴露结果为准。 + +## 配置建议 + +- 先确认 MoviePilot 本身已经配置好可用的 LLM +- 建议保持“保存失败样本”开启 +- 如果你经常处理历史资源或网盘资源,建议定期查看: + - `failed_samples` + - `sample_worklist` + - `sample_insights` + +## 已验证情况 + +当前版本:`0.1.12` + +这版已经验证过: + +- 最新版 MoviePilot 下可以正常加载 +- 正常中文标题识别可用 +- 英文别名、韩文原名、中文别名可识别回标准媒体信息 +- 低置信度标题会落失败样本 +- `replay_failed_sample` 复查链可用 + +## 说明 + +- 这个插件不依赖外部 AI Gateway 回调链 +- 重点是增强识别,不负责替代 MoviePilot 全部整理流程 +- 如果你只是偶发整理失败,原版智能体可能已经够用 +- 如果你长期受命名混乱困扰,这个插件更有价值 diff --git a/plugins.v2/airecognizerenhancer/__init__.py b/plugins.v2/airecognizerenhancer/__init__.py new file mode 100644 index 0000000..4471184 --- /dev/null +++ b/plugins.v2/airecognizerenhancer/__init__.py @@ -0,0 +1,2039 @@ +import hmac +import asyncio +import inspect +import json +import re +import threading +from collections import Counter +from pathlib import Path +from typing import Any, Dict, List, Optional, Tuple + +from fastapi import Request +from langchain_core.prompts import ChatPromptTemplate +from pydantic import BaseModel, Field + +from app.chain.media import MediaChain +from app.core.config import settings +from app.core.event import eventmanager +from app.core.meta.words import WordsMatcher +from app.core.metainfo import MetaInfo +from app.db.systemconfig_oper import SystemConfigOper +try: + from app.helper.llm import LLMHelper +except ImportError: # MoviePilot 新版已迁移到 app.agent.llm + from app.agent.llm import LLMHelper +from app.log import logger +from app.plugins import _PluginBase +from app.schemas.types import ChainEventType, MediaType, SystemConfigKey + + +class AIRecognitionGuess(BaseModel): + name: str = Field(default="", description="标准化后的影视标题;无法判断时返回空字符串") + year: str = Field(default="", description="四位年份;无法判断时返回空字符串") + media_type: str = Field(default="unknown", description="movie、tv 或 unknown") + season: int = Field(default=0, description="剧集季号,电影填 0") + episode: int = Field(default=0, description="剧集集号,电影或未知填 0") + confidence: float = Field(default=0.0, description="0 到 1 之间的置信度") + reason: str = Field(default="", description="简短说明为什么这样判断") + + +class IdentifierSuggestion(BaseModel): + comment: str = Field(default="", description="可选注释,不带 #") + rule: str = Field(default="", description="一条 MoviePilot 自定义识别词规则") + confidence: float = Field(default=0.0, description="0 到 1 之间的置信度") + reason: str = Field(default="", description="为什么建议这条规则") + + +class IdentifierSuggestionBundle(BaseModel): + summary: str = Field(default="", description="整体建议摘要") + suggestions: List[IdentifierSuggestion] = Field(default_factory=list, description="建议规则列表") + + +class AIRecognizerEnhancer(_PluginBase): + plugin_name = "AI识别增强" + plugin_desc = "直接复用 MoviePilot 当前 LLM 配置,在原生识别失败后做本地结构化识别兜底,并交回原生链路继续二次识别。" + plugin_icon = "https://raw.githubusercontent.com/liuyuexi1987/MoviePilot-Plugins/main/icons/airecognizerenhancer.png" + plugin_version = "0.1.12" + plugin_author = "liuyuexi1987" + plugin_level = 1 + author_url = "https://github.com/liuyuexi1987" + plugin_config_prefix = "arrecognizerenhancer_" + plugin_order = 41 + auth_level = 1 + + _enabled = False + _debug = False + _confidence_threshold = 0.65 + _request_timeout = 25 + _max_retries = 2 + _save_failed_samples = True + _max_failed_samples = 200 + _auto_remove_applied_sample = True + _systemconfig: Optional[SystemConfigOper] = None + + def init_plugin(self, config: Optional[Dict[str, Any]] = None): + config = config or {} + self._enabled = bool(config.get("enabled", False)) + self._debug = bool(config.get("debug", False)) + self._confidence_threshold = self._safe_float(config.get("confidence_threshold"), 0.65) + self._request_timeout = self._safe_int(config.get("request_timeout"), 25) + self._max_retries = max(1, min(5, self._safe_int(config.get("max_retries"), 2))) + self._save_failed_samples = bool(config.get("save_failed_samples", True)) + self._max_failed_samples = max(20, min(1000, self._safe_int(config.get("max_failed_samples"), 200))) + self._auto_remove_applied_sample = bool(config.get("auto_remove_applied_sample", True)) + self._systemconfig = SystemConfigOper() + self._register_events() + + def get_state(self) -> bool: + return self._enabled + + @staticmethod + def get_command() -> List[Dict[str, Any]]: + return [] + + def stop_service(self): + try: + eventmanager.disable_event_handler(self.on_chain_name_recognize) + except Exception: + pass + + @staticmethod + def _safe_int(value: Any, default: int) -> int: + try: + return int(value) + except Exception: + return default + + @staticmethod + def _safe_float(value: Any, default: float) -> float: + try: + return float(value) + except Exception: + return default + + @staticmethod + def _extract_apikey(request: Request, body: Optional[Dict[str, Any]] = None) -> str: + header = str(request.headers.get("Authorization") or "").strip() + if header.lower().startswith("bearer "): + return header.split(" ", 1)[1].strip() + if body: + for key in ("apikey", "api_key"): + token = str(body.get(key) or "").strip() + if token: + return token + return str(request.query_params.get("apikey") or "").strip() + + def _check_api_access(self, request: Request, body: Optional[Dict[str, Any]] = None) -> Tuple[bool, str]: + expected = str(getattr(settings, "API_TOKEN", "") or "").strip() + if not expected: + return False, "服务端未配置 API Token" + actual = self._extract_apikey(request, body) + if not hmac.compare_digest(actual, expected): + return False, "API Token 无效" + return True, "" + + def _register_events(self) -> None: + try: + eventmanager.register(ChainEventType.NameRecognize)(self.on_chain_name_recognize) + if self._enabled: + eventmanager.enable_event_handler(self.on_chain_name_recognize) + else: + eventmanager.disable_event_handler(self.on_chain_name_recognize) + except Exception as exc: + logger.warning(f"[AI识别增强] 注册链式识别事件失败: {exc}") + + @staticmethod + def _extract_title_path(event_data: Any) -> Tuple[str, str]: + title = "" + path = "" + if isinstance(event_data, dict): + title = ( + event_data.get("title") + or event_data.get("name") + or event_data.get("org_string") + or "" + ) + path = ( + event_data.get("path") + or event_data.get("file_path") + or event_data.get("org_string") + or "" + ) + else: + title = ( + getattr(event_data, "title", "") + or getattr(event_data, "name", "") + or getattr(event_data, "org_string", "") + or "" + ) + path = ( + getattr(event_data, "path", "") + or getattr(event_data, "file_path", "") + or getattr(event_data, "org_string", "") + or "" + ) + return str(title or "").strip(), str(path or "").strip() + + def _build_meta_hint(self, raw_text: str) -> Dict[str, Any]: + try: + meta = MetaInfo(raw_text) + except Exception: + return {} + return { + "name": getattr(meta, "name", "") or "", + "year": getattr(meta, "year", "") or "", + "type": getattr(getattr(meta, "type", None), "to_agent", lambda: None)() or "", + "season": getattr(meta, "begin_season", None) or 0, + "episode": getattr(meta, "begin_episode", None) or 0, + "org_string": getattr(meta, "org_string", "") or "", + } + + @staticmethod + def _clean_guess_name(name: str) -> str: + text = str(name or "").strip() + if not text: + return "" + text = text.split("/")[0].strip().replace(".", " ") + return " ".join(text.split()) + + def _normalize_guess(self, guess: AIRecognitionGuess) -> AIRecognitionGuess: + name = self._clean_guess_name(guess.name) + year = str(guess.year or "").strip() + if not (len(year) == 4 and year.isdigit()): + year = "" + media_type = str(guess.media_type or "unknown").strip().lower() + if media_type not in {"movie", "tv"}: + media_type = "unknown" + season = max(0, self._safe_int(guess.season, 0)) + episode = max(0, self._safe_int(guess.episode, 0)) + confidence = min(1.0, max(0.0, self._safe_float(guess.confidence, 0.0))) + reason = str(guess.reason or "").strip() + return AIRecognitionGuess( + name=name, + year=year, + media_type=media_type, + season=season, + episode=episode, + confidence=confidence, + reason=reason, + ) + + def _sample_path(self) -> Path: + return self.get_data_path() / "failed_samples.jsonl" + + @staticmethod + def _sample_identity(payload: Dict[str, Any]) -> str: + return json.dumps( + { + "title": str(payload.get("title") or "").strip(), + "path": str(payload.get("path") or "").strip(), + "reason": str(payload.get("reason") or "").strip(), + }, + ensure_ascii=False, + sort_keys=True, + ) + + def _write_failed_samples(self, rows: List[Dict[str, Any]]) -> None: + sample_path = self._sample_path() + sample_path.parent.mkdir(parents=True, exist_ok=True) + trimmed = rows[-self._max_failed_samples:] + with sample_path.open("w", encoding="utf-8") as f: + for row in trimmed: + f.write(json.dumps(row, ensure_ascii=False) + "\n") + + def _record_failed_sample(self, payload: Dict[str, Any]) -> None: + if not self._save_failed_samples: + return + try: + rows = self._read_failed_samples(limit=1000) + rows.reverse() + identity = self._sample_identity(payload) + filtered = [row for row in rows if self._sample_identity(row) != identity] + filtered.append(payload) + self._write_failed_samples(filtered) + except Exception as exc: + logger.warning(f"[AI识别增强] 写入失败样本失败: {exc}") + + def _read_failed_samples(self, limit: int = 20) -> List[Dict[str, Any]]: + sample_path = self._sample_path() + if not sample_path.exists(): + return [] + rows: List[Dict[str, Any]] = [] + try: + with sample_path.open("r", encoding="utf-8") as f: + for line in f: + line = line.strip() + if not line: + continue + try: + rows.append(json.loads(line)) + except Exception: + continue + except Exception as exc: + logger.warning(f"[AI识别增强] 读取失败样本失败: {exc}") + return [] + if limit > 0: + rows = rows[-limit:] + rows.reverse() + return rows + + def _clear_failed_samples(self) -> int: + rows = self._read_failed_samples(limit=1000) + sample_path = self._sample_path() + if sample_path.exists(): + sample_path.unlink() + return len(rows) + + def _remove_failed_sample(self, sample_index: Optional[Any], limit: int = 1000) -> Dict[str, Any]: + rows = self._read_failed_samples(limit=max(1, min(limit, 1000))) + if not rows: + return {"removed": False, "message": "暂无失败样本", "removed_count": 0} + index = self._safe_int(sample_index, 0) + if index < 0: + index = 0 + if index >= len(rows): + return { + "removed": False, + "message": f"失败样本索引超出范围,当前共有 {len(rows)} 条", + "removed_count": 0, + } + removed_sample = dict(rows[index] or {}) + del rows[index] + if rows: + rows.reverse() + self._write_failed_samples(rows) + else: + self._clear_failed_samples() + return { + "removed": True, + "message": "success", + "removed_count": 1, + "remaining_count": len(rows), + "removed_sample": removed_sample, + "removed_sample_index": index, + } + + def _remove_failed_samples(self, sample_indexes: List[Any], limit: int = 1000) -> Dict[str, Any]: + rows = self._read_failed_samples(limit=max(1, min(limit, 1000))) + if not rows: + return {"removed": False, "message": "暂无失败样本", "removed_count": 0, "remaining_count": 0} + normalized_indexes = sorted( + {self._safe_int(index, -1) for index in (sample_indexes or []) if self._safe_int(index, -1) >= 0}, + reverse=True, + ) + valid_indexes = [index for index in normalized_indexes if index < len(rows)] + if not valid_indexes: + return { + "removed": False, + "message": "没有可移除的有效样本索引", + "removed_count": 0, + "remaining_count": len(rows), + } + removed_samples: List[Dict[str, Any]] = [] + for index in valid_indexes: + removed_samples.append(dict(rows[index] or {})) + del rows[index] + if rows: + rows.reverse() + self._write_failed_samples(rows) + else: + self._clear_failed_samples() + removed_samples.reverse() + return { + "removed": True, + "message": "success", + "removed_count": len(valid_indexes), + "remaining_count": len(rows), + "removed_sample_indexes": sorted(valid_indexes), + "removed_samples": removed_samples, + } + + def _resolve_failed_sample( + self, + sample_index: Optional[Any] = None, + limit: int = 100, + ) -> Tuple[Optional[int], Optional[Dict[str, Any]], str]: + samples = self._read_failed_samples(limit=max(1, min(limit, 200))) + if not samples: + return None, None, "暂无失败样本" + index = self._safe_int(sample_index, 0) + if index < 0: + index = 0 + if index >= len(samples): + return None, None, f"失败样本索引超出范围,当前共有 {len(samples)} 条" + row = dict(samples[index] or {}) + row["sample_index"] = index + return index, row, "" + + def _select_failed_sample_indexes( + self, + sample_indexes: Optional[List[Any]] = None, + limit: int = 10, + pool_limit: int = 200, + ) -> Tuple[List[int], List[Dict[str, Any]], str]: + current_samples = self._inject_sample_indices(self._read_failed_samples(limit=max(1, min(pool_limit, 1000)))) + if not current_samples: + return [], [], "暂无失败样本" + if isinstance(sample_indexes, list) and sample_indexes: + selected_indexes: List[int] = [] + seen = set() + for raw in sample_indexes: + idx = self._safe_int(raw, -1) + if idx < 0 or idx >= len(current_samples) or idx in seen: + continue + seen.add(idx) + selected_indexes.append(idx) + else: + selected_indexes = [int(sample.get("sample_index", 0)) for sample in current_samples[: max(1, min(limit, 50))]] + if not selected_indexes: + return [], current_samples, "没有可处理的有效样本索引" + return selected_indexes, current_samples, "" + + def _inject_sample_indices(self, samples: List[Dict[str, Any]]) -> List[Dict[str, Any]]: + indexed: List[Dict[str, Any]] = [] + for idx, sample in enumerate(samples): + row = dict(sample or {}) + row["sample_index"] = idx + indexed.append(row) + return indexed + + def _summarize_sample(self, sample: Dict[str, Any]) -> Dict[str, Any]: + sample = dict(sample or {}) + guess = sample.get("guess") or {} + verified = sample.get("verified_media_info") or {} + inferred_target = { + "name": verified.get("title") or guess.get("name") or "", + "year": verified.get("year") or guess.get("year") or "", + "media_type": self._normalize_media_type(verified.get("type") or guess.get("media_type")), + "season": self._safe_int(guess.get("season"), 0), + "episode": self._safe_int(guess.get("episode"), 0), + "tmdb_id": self._safe_int(verified.get("tmdb_id"), 0), + } + return { + "sample_index": sample.get("sample_index"), + "title": sample.get("title"), + "path": sample.get("path"), + "reason": sample.get("reason"), + "guess_name": guess.get("name"), + "guess_confidence": self._safe_float(guess.get("confidence"), 0.0), + "verified_title": verified.get("title"), + "verified_year": verified.get("year"), + "verified_tmdb_id": verified.get("tmdb_id"), + "inferred_target": inferred_target, + "can_auto_suggest": bool(inferred_target["name"]), + } + + def _target_from_sample(self, sample: Dict[str, Any]) -> Dict[str, Any]: + summary = self._summarize_sample(sample) + return summary.get("inferred_target") or {} + + @staticmethod + def _normalize_reason_tag(reason: Any) -> str: + text = str(reason or "").strip() + if not text: + return "unknown" + if ":" in text: + return text.split(":", 1)[0].strip() or "unknown" + return text + + @staticmethod + def _sample_group_key(summary: Dict[str, Any]) -> str: + target = summary.get("inferred_target") or {} + title = ( + str(target.get("name") or "").strip() + or str(summary.get("verified_title") or "").strip() + or str(summary.get("guess_name") or "").strip() + or str(summary.get("title") or "").strip() + ) + media_type = str(target.get("media_type") or "unknown").strip().lower() + season = int(target.get("season") or 0) + episode = int(target.get("episode") or 0) + return json.dumps( + { + "title": title.lower(), + "media_type": media_type, + "season": season, + "episode": episode, + }, + ensure_ascii=False, + sort_keys=True, + ) + + @staticmethod + def _sample_display_name(summary: Dict[str, Any]) -> str: + target = summary.get("inferred_target") or {} + title = ( + str(target.get("name") or "").strip() + or str(summary.get("verified_title") or "").strip() + or str(summary.get("guess_name") or "").strip() + or str(summary.get("title") or "").strip() + ) + if not title: + return "未命名样本" + media_type = str(target.get("media_type") or "").strip().lower() + season = int(target.get("season") or 0) + episode = int(target.get("episode") or 0) + suffix = "" + if media_type == "tv" and (season or episode): + suffix = f" S{season:02d}E{episode:02d}" + return f"{title}{suffix}" + + def _build_sample_insights(self, samples: List[Dict[str, Any]], top: int = 10) -> Dict[str, Any]: + summaries = [self._summarize_sample(sample) for sample in samples] + reason_counter = Counter() + title_counter = Counter() + group_counter = Counter() + for summary in summaries: + reason_counter[self._normalize_reason_tag(summary.get("reason"))] += 1 + title_counter[self._sample_display_name(summary)] += 1 + group_counter[self._sample_group_key(summary)] += 1 + + actionable: List[Dict[str, Any]] = [] + for summary in summaries: + duplicate_count = group_counter[self._sample_group_key(summary)] + priority_reasons: List[str] = [] + score = 0 + if duplicate_count >= 2: + score += min(duplicate_count, 5) + priority_reasons.append(f"同类样本重复出现 {duplicate_count} 次") + if summary.get("verified_tmdb_id"): + score += 3 + priority_reasons.append("已有 TMDB 命中") + if summary.get("can_auto_suggest"): + score += 2 + priority_reasons.append("可直接生成识别词") + confidence = self._safe_float(summary.get("guess_confidence"), 0.0) + if 0 < confidence < self._confidence_threshold: + gap = round(self._confidence_threshold - confidence, 2) + score += 1 + priority_reasons.append(f"距注入阈值还差 {gap}") + row = dict(summary) + row["duplicate_count"] = duplicate_count + row["priority_score"] = score + row["priority_reasons"] = priority_reasons + actionable.append(row) + + actionable.sort( + key=lambda item: ( + -int(item.get("priority_score") or 0), + -int(item.get("duplicate_count") or 0), + -self._safe_float(item.get("guess_confidence"), 0.0), + int(item.get("sample_index") or 0), + ) + ) + + repeated_groups = [ + {"title": name, "count": count} + for name, count in title_counter.most_common(top) + if count >= 2 + ] + + return { + "total_count": len(summaries), + "reason_counts": [ + {"reason": reason, "count": count} + for reason, count in reason_counter.most_common(top) + ], + "top_titles": [ + {"title": title, "count": count} + for title, count in title_counter.most_common(top) + ], + "repeated_groups": repeated_groups, + "priority_samples": actionable[:top], + } + + def _render_sample_brief(self, samples: List[Dict[str, Any]], top: int = 5) -> str: + summaries = [self._summarize_sample(sample) for sample in samples[: max(1, min(top, 20))]] + if not summaries: + return "当前没有失败样本。" + lines = [f"失败样本 {len(samples)} 条,展示前 {len(summaries)} 条:"] + for summary in summaries: + label = self._sample_display_name(summary) + confidence = round(self._safe_float(summary.get("guess_confidence"), 0.0), 2) + can_suggest = "可建议" if summary.get("can_auto_suggest") else "需人工" + lines.append(f"{summary.get('sample_index')}. {label} | 置信度 {confidence} | {can_suggest}") + lines.append("下一步:可直接调用批量建议或批量复查接口。") + return "\n".join(lines) + + @staticmethod + def _render_batch_results_brief( + action_name: str, + requested_count: int, + success_count: int, + failed_count: int, + results: List[Dict[str, Any]], + ) -> str: + lines = [f"{action_name}:共处理 {requested_count} 条,成功 {success_count},失败 {failed_count}。"] + for item in results[:10]: + idx = item.get("sample_index") + if item.get("success"): + label = ( + ((item.get("source_sample") or {}).get("title")) + or ((item.get("target") or {}).get("name")) + or "样本" + ) + lines.append(f"{idx}. 成功 | {label}") + else: + lines.append(f"{idx}. 失败 | {item.get('message', '未知错误')}") + return "\n".join(lines) + + def _build_body_from_sample(self, body: Dict[str, Any]) -> Tuple[Dict[str, Any], Optional[Dict[str, Any]], str]: + body = dict(body or {}) + title = str(body.get("title") or "").strip() + path = str(body.get("path") or "").strip() + sample_requested = body.get("use_latest_sample") or body.get("sample_index") is not None + if title or path: + return body, None, "" + if not sample_requested: + return body, None, "" + + sample_index, sample, message = self._resolve_failed_sample(body.get("sample_index"), limit=100) + if not sample: + return body, None, message + body["title"] = str(sample.get("title") or "").strip() + body["path"] = str(sample.get("path") or "").strip() + verified = sample.get("verified_media_info") or {} + guess = sample.get("guess") or {} + if not body.get("desired_name"): + body["desired_name"] = verified.get("title") or guess.get("name") or "" + if not body.get("desired_year"): + body["desired_year"] = verified.get("year") or guess.get("year") or "" + if not body.get("desired_media_type"): + body["desired_media_type"] = self._normalize_media_type( + verified.get("type") or guess.get("media_type") + ) + if body.get("desired_season") is None: + body["desired_season"] = guess.get("season") or 0 + if body.get("desired_episode") is None: + body["desired_episode"] = guess.get("episode") or 0 + if body.get("desired_tmdb_id") is None: + body["desired_tmdb_id"] = verified.get("tmdb_id") or 0 + body["sample_index"] = sample_index + return body, sample, "" + + def _build_prompt(self) -> ChatPromptTemplate: + return ChatPromptTemplate.from_messages( + [ + ( + "system", + """你是 MoviePilot 的影视文件名识别增强助手。 + +你的任务不是搜索 TMDB,也不是编造结果,而是根据文件名、路径和已有解析提示,尽量提炼出更适合 MoviePilot 二次识别的结构化信息。 + +规则: +1. 只依据输入内容推断,不要臆造不存在的信息。 +2. 如果不确定,请返回空标题,并把 media_type 设为 unknown,confidence 降低。 +3. title/name 只保留作品名,不要包含分辨率、制作组、音频编码、网盘标记等噪音。 +4. year 只有在比较确定时才给四位年份。 +5. 电影 season/episode 必须为 0。 +6. 剧集如果能确定季集就填写,否则保持 0。 +7. media_type 只能是 movie、tv、unknown。 +8. confidence 范围为 0 到 1。 +""", + ), + ( + "human", + """原始标题: +{title} + +原始路径: +{path} + +MoviePilot 当前基础解析提示: +{meta_hint} +""", + ), + ] + ) + + def _build_identifier_prompt(self) -> ChatPromptTemplate: + return ChatPromptTemplate.from_messages( + [ + ( + "system", + """你是 MoviePilot 自定义识别词规则助手。 + +你的任务是根据错误标题、当前解析结果和目标结果,生成尽量窄作用域、可直接用于 MoviePilot CustomIdentifiers 的规则。 + +支持格式只有四种: +1. 屏蔽词 +2. 替换词:被替换词 => 替换词 +3. 集偏移:前定位词 <> 后定位词 >> EP±N +4. 组合规则:被替换词 => 替换词 && 前定位词 <> 后定位词 >> EP±N + +硬性要求: +1. 运算符两侧必须保留空格: => 、 <> 、 >> 、 && +2. 优先生成窄作用域规则,尽量带发布组、年份、季集、分辨率等锚点 +3. 不要生成过宽的裸屏蔽词,比如 1080p、WEB-DL、字幕 +4. 如果需要强制绑 TMDB,可使用 {{[tmdbid=xxx;type=tv/movies;s=1;e=14]}} 这种替换词 +5. comment 不带 #,rule 里不要再包 markdown 或代码块 +6. 如果没有把握,请返回空 suggestions +""", + ), + ( + "human", + """原始标题: +{title} + +原始路径: +{path} + +MoviePilot 当前基础解析: +{meta_hint} + +AI 识别增强结果: +{guess} + +二次校验到的媒体信息摘要: +{verified_summary} + +希望修正成的目标结果: +{target} +""", + ), + ] + ) + + @staticmethod + def _run_async_compatible(value: Any) -> Any: + """ + 兼容 MoviePilot 新版 `LLMHelper.get_llm()` 的异步返回。 + 在同步上下文直接 asyncio.run;如果当前线程已有事件循环,则开一个短线程执行。 + """ + if not inspect.isawaitable(value): + return value + try: + asyncio.get_running_loop() + except RuntimeError: + return asyncio.run(value) + + result: Dict[str, Any] = {} + error: Dict[str, BaseException] = {} + + def _worker() -> None: + try: + result["value"] = asyncio.run(value) + except BaseException as exc: # noqa: BLE001 + error["exc"] = exc + + thread = threading.Thread(target=_worker, daemon=True) + thread.start() + thread.join() + if "exc" in error: + raise error["exc"] + return result.get("value") + + def _get_llm(self): + llm = LLMHelper.get_llm(streaming=False) + return self._run_async_compatible(llm) + + def _invoke_llm(self, title: str, path: str) -> AIRecognitionGuess: + raw_text = path or title + meta_hint = self._build_meta_hint(raw_text) + llm = self._get_llm() + prompt = self._build_prompt() + chain = ( + prompt + | llm.with_structured_output(AIRecognitionGuess).with_retry(stop_after_attempt=self._max_retries) + ) + result: AIRecognitionGuess = chain.invoke( + { + "title": title, + "path": path, + "meta_hint": meta_hint, + }, + config={"configurable": {"timeout": self._request_timeout}}, + ) + return self._normalize_guess(result) + + @staticmethod + def _normalize_media_type(value: Any) -> str: + if value == MediaType.MOVIE: + return "movie" + if value == MediaType.TV: + return "tv" + text = str(value or "").strip().lower() + if text in {"movie", "movies", "电影"}: + return "movie" + if text in {"tv", "电视剧", "剧集"}: + return "tv" + return "unknown" + + def _build_target(self, body: Dict[str, Any], result: Optional[Dict[str, Any]] = None) -> Dict[str, Any]: + body = body or {} + result = result or {} + guess = result.get("guess") or {} + verified = result.get("verified_media_info") or {} + verified_type = self._normalize_media_type(verified.get("type")) + target = { + "name": str(body.get("desired_name") or verified.get("title") or guess.get("name") or "").strip(), + "year": str(body.get("desired_year") or verified.get("year") or guess.get("year") or "").strip(), + "media_type": self._normalize_media_type( + body.get("desired_media_type") or verified_type or guess.get("media_type") + ), + "season": self._safe_int( + body.get("desired_season"), + self._safe_int(guess.get("season"), 0), + ), + "episode": self._safe_int( + body.get("desired_episode"), + self._safe_int(guess.get("episode"), 0), + ), + "tmdb_id": self._safe_int(body.get("desired_tmdb_id") or verified.get("tmdb_id"), 0), + } + if len(target["year"]) != 4 or not target["year"].isdigit(): + target["year"] = "" + return target + + @staticmethod + def _compact_verified_summary(verified: Optional[Dict[str, Any]]) -> Dict[str, Any]: + verified = verified or {} + return { + "title": verified.get("title"), + "year": verified.get("year"), + "type": verified.get("type"), + "tmdb_id": verified.get("tmdb_id"), + "title_year": verified.get("title_year"), + "season_years": verified.get("season_years"), + "seasons": verified.get("seasons"), + "names": (verified.get("names") or [])[:8], + } + + @staticmethod + def _normalize_identifier_line(value: Any) -> str: + return " ".join(str(value or "").strip().split()) + + def _validate_identifier_rule(self, rule: str) -> bool: + rule = self._normalize_identifier_line(rule) + if not rule or rule.startswith("#"): + return False + if " => " in rule and " && " in rule and " >> " in rule and " <> " in rule: + return True + if " => " in rule: + return True + if " >> " in rule and " <> " in rule: + return True + return len(rule) >= 4 + + def _enrich_identifier_rule(self, rule: str, target: Dict[str, Any]) -> str: + rule = self._normalize_identifier_line(rule) + target_name = str((target or {}).get("name") or "").strip() + if not target_name or " => " not in rule: + return rule + left, right = rule.split(" => ", 1) + suffix = "" + replace_part = right + if " && " in right: + replace_part, extra = right.split(" && ", 1) + suffix = f" && {extra}" + if replace_part.startswith("{["): + replace_part = f"{target_name}{replace_part}" + return f"{left} => {replace_part}{suffix}" + + @staticmethod + def _clean_comment_line(comment: str) -> str: + text = str(comment or "").strip() + if not text: + return "" + return f"#{text.lstrip('#').strip()}" + + def _preview_custom_words(self, title: str, custom_words: List[str], target: Dict[str, Any]) -> Dict[str, Any]: + prepared_title, apply_words = WordsMatcher().prepare(title, custom_words=custom_words) + meta = MetaInfo(title=title, custom_words=custom_words) + preview = { + "prepared_title": prepared_title, + "applied_words": apply_words or [], + "applied": bool(apply_words), + "name": getattr(meta, "name", "") or "", + "year": getattr(meta, "year", "") or "", + "media_type": self._normalize_media_type(getattr(meta, "type", None)), + "season": getattr(meta, "begin_season", None) or 0, + "episode": getattr(meta, "begin_episode", None) or 0, + } + if target: + matched = True + if target.get("name"): + matched = matched and (preview["name"].strip().lower() == str(target["name"]).strip().lower()) + if target.get("year"): + matched = matched and (preview["year"] == target["year"]) + if target.get("media_type") and target.get("media_type") != "unknown": + matched = matched and (preview["media_type"] == target["media_type"]) + if target.get("season"): + matched = matched and (preview["season"] == target["season"]) + if target.get("episode"): + matched = matched and (preview["episode"] == target["episode"]) + preview["matched_target"] = matched + return preview + + def _preview_identifier_rule(self, title: str, rule: str, target: Dict[str, Any]) -> Dict[str, Any]: + preview = self._preview_custom_words(title=title, custom_words=[rule], target=target) + preview["applied"] = rule in (preview.get("applied_words") or []) + return preview + + def _preview_current_identifiers(self, title: str, target: Dict[str, Any]) -> Dict[str, Any]: + custom_words = self._get_custom_identifiers() + preview = self._preview_custom_words(title=title, custom_words=custom_words, target=target) + preview["custom_identifier_count"] = len(custom_words) + preview["applied_count"] = len(preview.get("applied_words") or []) + return preview + + @staticmethod + def _match_recognize_result_to_target(result: Dict[str, Any], target: Dict[str, Any]) -> bool: + if not target: + return bool(result.get("success")) + guess = result.get("guess") or {} + matched = True + if target.get("name"): + matched = matched and (str(guess.get("name") or "").strip().lower() == str(target.get("name") or "").strip().lower()) + if target.get("year"): + matched = matched and (str(guess.get("year") or "") == str(target.get("year") or "")) + if target.get("media_type") and target.get("media_type") != "unknown": + matched = matched and (str(guess.get("media_type") or "unknown") == str(target.get("media_type") or "unknown")) + if target.get("season"): + matched = matched and (int(guess.get("season") or 0) == int(target.get("season") or 0)) + if target.get("episode"): + matched = matched and (int(guess.get("episode") or 0) == int(target.get("episode") or 0)) + return bool(result.get("success")) and matched + + def _replay_failed_sample(self, body: Dict[str, Any]) -> Dict[str, Any]: + body = dict(body or {}) + sample_index, sample, message = self._resolve_failed_sample( + body.get("sample_index"), + limit=1000, + ) + if not sample: + return {"success": False, "message": message} + title = str(sample.get("title") or "").strip() + path = str(sample.get("path") or "").strip() + target = self._target_from_sample(sample) + identifier_preview = self._preview_current_identifiers(title=title, target=target) + recognize_result = self._recognize(title=title, path=path, record_failed_sample=False) + resolved_by_identifiers = bool(identifier_preview.get("applied")) and bool(identifier_preview.get("matched_target")) + resolved_by_recognizer = self._match_recognize_result_to_target(recognize_result, target) + resolved = resolved_by_identifiers or resolved_by_recognizer + removal_result = None + if resolved and bool(body.get("remove_if_resolved")): + removal_result = self._remove_failed_sample(sample_index, limit=1000) + return { + "success": True, + "message": "success", + "data": { + "source_sample_index": sample_index, + "source_sample": sample, + "target": target, + "identifier_preview": identifier_preview, + "recognize_result": recognize_result, + "resolved_by_identifiers": resolved_by_identifiers, + "resolved_by_recognizer": resolved_by_recognizer, + "resolved": resolved, + "sample_removed": bool(removal_result and removal_result.get("removed")), + "sample_removal_result": removal_result, + }, + } + + def _replay_failed_samples(self, body: Dict[str, Any]) -> Dict[str, Any]: + body = dict(body or {}) + limit = max(1, min(self._safe_int(body.get("limit"), 10), 50)) + selected_indexes, _, message = self._select_failed_sample_indexes( + sample_indexes=body.get("sample_indexes"), + limit=limit, + pool_limit=200, + ) + if not selected_indexes: + return {"success": False, "message": message} + + replay_results: List[Dict[str, Any]] = [] + resolved_indexes: List[int] = [] + for sample_index in selected_indexes: + replay = self._replay_failed_sample( + { + "sample_index": sample_index, + "remove_if_resolved": False, + } + ) + if not replay.get("success"): + replay_results.append( + { + "sample_index": sample_index, + "success": False, + "message": replay.get("message", "复查失败"), + } + ) + continue + data = replay.get("data") or {} + replay_results.append( + { + "sample_index": sample_index, + "success": True, + "resolved": bool(data.get("resolved")), + "resolved_by_identifiers": bool(data.get("resolved_by_identifiers")), + "resolved_by_recognizer": bool(data.get("resolved_by_recognizer")), + "source_sample": data.get("source_sample"), + "target": data.get("target"), + "identifier_preview": data.get("identifier_preview"), + "recognize_result": data.get("recognize_result"), + } + ) + if data.get("resolved"): + resolved_indexes.append(sample_index) + + removal_result = None + if body.get("remove_if_resolved") and resolved_indexes: + removal_result = self._remove_failed_samples(resolved_indexes, limit=1000) + + success_count = sum(1 for item in replay_results if item.get("success")) + resolved_count = sum(1 for item in replay_results if item.get("resolved")) + unresolved_count = success_count - resolved_count + failed_count = len(replay_results) - success_count + return { + "success": True, + "message": "success", + "data": { + "requested_count": len(selected_indexes), + "success_count": success_count, + "resolved_count": resolved_count, + "unresolved_count": unresolved_count, + "failed_count": failed_count, + "sample_removed_count": int((removal_result or {}).get("removed_count") or 0), + "sample_removal_result": removal_result, + "results": replay_results, + }, + } + + def _suggest_identifiers_for_failed_samples(self, body: Dict[str, Any]) -> Dict[str, Any]: + body = dict(body or {}) + limit = max(1, min(self._safe_int(body.get("limit"), 5), 20)) + selected_indexes, _, message = self._select_failed_sample_indexes( + sample_indexes=body.get("sample_indexes"), + limit=limit, + pool_limit=200, + ) + if not selected_indexes: + return {"success": False, "message": message} + + results: List[Dict[str, Any]] = [] + success_count = 0 + for sample_index in selected_indexes: + suggest_body = dict(body) + suggest_body.pop("sample_indexes", None) + suggest_body["sample_index"] = sample_index + suggest_body["use_latest_sample"] = False + suggested = self._suggest_identifiers(suggest_body) + if suggested.get("success"): + success_count += 1 + data = suggested.get("data") or {} + results.append( + { + "sample_index": sample_index, + "success": True, + "summary": data.get("summary"), + "source_sample": data.get("source_sample"), + "target": data.get("target"), + "suggestions": data.get("suggestions") or [], + } + ) + else: + results.append( + { + "sample_index": sample_index, + "success": False, + "message": suggested.get("message", "建议生成失败"), + "data": suggested.get("data"), + } + ) + return { + "success": True, + "message": "success", + "data": { + "requested_count": len(selected_indexes), + "success_count": success_count, + "failed_count": len(selected_indexes) - success_count, + "brief": self._render_batch_results_brief( + action_name="批量建议", + requested_count=len(selected_indexes), + success_count=success_count, + failed_count=len(selected_indexes) - success_count, + results=results, + ), + "results": results, + }, + } + + def _apply_suggested_identifier_internal(self, body: Dict[str, Any]) -> Dict[str, Any]: + body = dict(body or {}) + if body.get("title") is None and body.get("path") is None: + body["use_latest_sample"] = True if body.get("use_latest_sample") is None else body.get("use_latest_sample") + suggested = self._suggest_identifiers(body) + if not suggested.get("success"): + return suggested + data = suggested.get("data") or {} + suggestions = data.get("suggestions") or [] + suggestion_index = self._safe_int(body.get("suggestion_index"), 0) + if suggestion_index < 0: + suggestion_index = 0 + if suggestion_index >= len(suggestions): + return {"success": False, "message": f"建议索引超出范围,当前共有 {len(suggestions)} 条"} + chosen = suggestions[suggestion_index] + applied = self._append_custom_identifiers(chosen.get("lines") or []) + should_remove_sample = bool( + self._auto_remove_applied_sample if body.get("remove_sample") is None else body.get("remove_sample") + ) + removal_result = None + source_sample = data.get("source_sample") or {} + if should_remove_sample and source_sample.get("sample_index") is not None: + removal_result = self._remove_failed_sample(source_sample.get("sample_index"), limit=1000) + return { + "success": True, + "message": "success", + "data": { + "chosen_suggestion": chosen, + "apply_result": applied, + "source_sample_index": source_sample.get("sample_index"), + "source_sample": source_sample, + "sample_removed": bool(removal_result and removal_result.get("removed")), + "sample_removal_result": removal_result, + "target": data.get("target"), + }, + } + + def _apply_suggested_identifiers_for_failed_samples(self, body: Dict[str, Any]) -> Dict[str, Any]: + body = dict(body or {}) + limit = max(1, min(self._safe_int(body.get("limit"), 5), 20)) + selected_indexes, _, message = self._select_failed_sample_indexes( + sample_indexes=body.get("sample_indexes"), + limit=limit, + pool_limit=200, + ) + if not selected_indexes: + return {"success": False, "message": message} + + results: List[Dict[str, Any]] = [] + success_count = 0 + removable_indexes: List[int] = [] + should_remove_samples = bool( + self._auto_remove_applied_sample if body.get("remove_sample") is None else body.get("remove_sample") + ) + for sample_index in selected_indexes: + apply_body = dict(body) + apply_body.pop("sample_indexes", None) + apply_body["sample_index"] = sample_index + apply_body["use_latest_sample"] = False + apply_body["remove_sample"] = False + applied = self._apply_suggested_identifier_internal(apply_body) + if applied.get("success"): + success_count += 1 + data = applied.get("data") or {} + if should_remove_samples: + removable_indexes.append(sample_index) + results.append( + { + "sample_index": sample_index, + "success": True, + "source_sample": data.get("source_sample"), + "target": data.get("target"), + "chosen_suggestion": data.get("chosen_suggestion"), + "apply_result": data.get("apply_result"), + "sample_removed": False, + } + ) + else: + results.append( + { + "sample_index": sample_index, + "success": False, + "message": applied.get("message", "写入失败"), + "data": applied.get("data"), + } + ) + removal_result = None + if should_remove_samples and removable_indexes: + removal_result = self._remove_failed_samples(removable_indexes, limit=1000) + removed_index_set = set((removal_result or {}).get("removed_sample_indexes") or []) + for item in results: + if item.get("success"): + item["sample_removed"] = item.get("sample_index") in removed_index_set + return { + "success": True, + "message": "success", + "data": { + "requested_count": len(selected_indexes), + "success_count": success_count, + "failed_count": len(selected_indexes) - success_count, + "sample_removed_count": int((removal_result or {}).get("removed_count") or 0), + "sample_removal_result": removal_result, + "brief": self._render_batch_results_brief( + action_name="批量写入", + requested_count=len(selected_indexes), + success_count=success_count, + failed_count=len(selected_indexes) - success_count, + results=results, + ), + "results": results, + }, + } + + def _build_exact_identifier_fallback(self, title: str, target: Dict[str, Any]) -> Optional[Dict[str, Any]]: + target_name = str((target or {}).get("name") or "").strip() + tmdb_id = self._safe_int((target or {}).get("tmdb_id"), 0) + media_type = self._normalize_media_type((target or {}).get("media_type")) + if not title or not target_name or not tmdb_id or media_type == "unknown": + return None + replace = target_name + target_year = str((target or {}).get("year") or "").strip() + if len(target_year) == 4 and target_year.isdigit(): + replace += f".{target_year}" + replace += f"{{[tmdbid={tmdb_id};type={'tv' if media_type == 'tv' else 'movie'}" + if media_type == "tv" and self._safe_int(target.get("season"), 0): + replace += f";s={self._safe_int(target.get('season'), 0)}" + if media_type == "tv" and self._safe_int(target.get("episode"), 0): + replace += f";e={self._safe_int(target.get('episode'), 0)}" + replace += "]}" + rule = f"{re.escape(title)} => {replace}" + preview = self._preview_identifier_rule(title=title, rule=rule, target=target) + if not preview.get("applied"): + return None + return { + "comment": "当 AI 建议无法稳定通过本地预演时,使用精确标题绑定规则直接固定到目标 TMDB 与季集", + "comment_line": "#当 AI 建议无法稳定通过本地预演时,使用精确标题绑定规则直接固定到目标 TMDB 与季集", + "rule": rule, + "confidence": 0.95, + "reason": "精确匹配当前标题并强制绑定目标 TMDB / 季集,作用域最窄,稳定性最高。", + "preview": preview, + "lines": [ + "#当 AI 建议无法稳定通过本地预演时,使用精确标题绑定规则直接固定到目标 TMDB 与季集", + rule, + ], + } + + def _invoke_identifier_llm( + self, + title: str, + path: str, + result: Dict[str, Any], + target: Dict[str, Any], + ) -> IdentifierSuggestionBundle: + llm = self._get_llm() + prompt = self._build_identifier_prompt() + chain = ( + prompt + | llm.with_structured_output(IdentifierSuggestionBundle).with_retry( + stop_after_attempt=self._max_retries + ) + ) + bundle: IdentifierSuggestionBundle = chain.invoke( + { + "title": title, + "path": path, + "meta_hint": self._build_meta_hint(path or title), + "guess": result.get("guess") or {}, + "verified_summary": self._compact_verified_summary(result.get("verified_media_info")), + "target": target, + }, + config={"configurable": {"timeout": self._request_timeout}}, + ) + return bundle + + def _suggest_identifiers(self, body: Dict[str, Any]) -> Dict[str, Any]: + body, source_sample, sample_message = self._build_body_from_sample(body) + if sample_message: + return {"success": False, "message": sample_message} + title = str(body.get("title") or "").strip() + path = str(body.get("path") or "").strip() + if not title and path: + title = Path(path).name + if not title: + return {"success": False, "message": "标题为空"} + + result = self._recognize(title=title, path=path, record_failed_sample=False) + target = self._build_target(body, result=result) + invoke_error = "" + try: + bundle = self._invoke_identifier_llm(title=title, path=path, result=result, target=target) + except Exception as exc: + bundle = IdentifierSuggestionBundle( + summary="识别词建议模型暂不可用,已自动回退到精确规则兜底。", + suggestions=[], + ) + invoke_error = str(exc) + + cleaned: List[Dict[str, Any]] = [] + for item in bundle.suggestions: + rule = self._enrich_identifier_rule(item.rule, target=target) + if not self._validate_identifier_rule(rule): + continue + comment_line = self._clean_comment_line(item.comment) + preview = self._preview_identifier_rule(title=title, rule=rule, target=target) + if not preview.get("applied"): + continue + if target and any(target.values()) and preview.get("matched_target") is False: + continue + cleaned.append( + { + "comment": item.comment.strip(), + "comment_line": comment_line, + "rule": rule, + "confidence": min(1.0, max(0.0, self._safe_float(item.confidence, 0.0))), + "reason": str(item.reason or "").strip(), + "preview": preview, + "lines": [line for line in [comment_line, rule] if line], + } + ) + + if not cleaned: + fallback = self._build_exact_identifier_fallback(title=title, target=target) + if fallback: + if invoke_error: + fallback["reason"] = f"{fallback.get('reason', '')} 当前识别词建议模型不可用,已自动切到精确规则兜底。".strip() + cleaned.append(fallback) + + if not cleaned: + return { + "success": False, + "message": f"识别词建议生成失败: {invoke_error}" if invoke_error else "没有生成可直接使用的识别词规则", + "data": { + "summary": bundle.summary, + "target": target, + "recognize_result": result, + }, + } + return { + "success": True, + "message": "success", + "data": { + "summary": bundle.summary, + "source_sample_index": (source_sample or {}).get("sample_index"), + "source_sample": source_sample, + "target": target, + "recognize_result": result, + "suggestions": cleaned, + }, + } + + def _get_custom_identifiers(self) -> List[str]: + if not self._systemconfig: + self._systemconfig = SystemConfigOper() + return self._systemconfig.get(SystemConfigKey.CustomIdentifiers) or [] + + def _append_custom_identifiers(self, lines: List[str]) -> Dict[str, Any]: + existing = self._get_custom_identifiers() + added: List[str] = [] + for line in lines: + normalized = str(line or "").rstrip() + if not normalized: + continue + if normalized in existing or normalized in added: + continue + added.append(normalized) + if added: + merged = existing + added + self._systemconfig.set(SystemConfigKey.CustomIdentifiers, merged) + return { + "added": added, + "added_count": len(added), + "total_count": len(self._get_custom_identifiers()), + } + + def _verify_guess(self, title: str, path: str, guess: AIRecognitionGuess) -> Optional[Dict[str, Any]]: + if not guess.name: + return None + try: + raw_text = path or title or guess.name + meta = MetaInfo(raw_text) + meta.name = guess.name + meta.year = guess.year or None + meta.begin_season = guess.season or None + meta.begin_episode = guess.episode or None + if guess.media_type == "tv" or meta.begin_season or meta.begin_episode: + meta.type = MediaType.TV + elif guess.media_type == "movie": + meta.type = MediaType.MOVIE + mediainfo = MediaChain().recognize_media(meta=meta, cache=False) + if not mediainfo: + return None + return mediainfo.to_dict() + except Exception as exc: + if self._debug: + logger.warning(f"[AI识别增强] 二次校验失败: {exc}") + return None + + def _recognize(self, title: str, path: str = "", record_failed_sample: bool = True) -> Dict[str, Any]: + title = str(title or "").strip() + path = str(path or "").strip() + if not title and path: + title = Path(path).name + if not title: + return {"success": False, "message": "标题为空"} + try: + guess = self._invoke_llm(title, path) + except Exception as exc: + if record_failed_sample: + self._record_failed_sample( + { + "title": title, + "path": path, + "meta_hint": self._build_meta_hint(path or title), + "reason": f"llm_error:{exc}", + } + ) + return {"success": False, "message": f"LLM 调用失败: {exc}"} + + verified = self._verify_guess(title, path, guess) + passed = bool(guess.name and guess.confidence >= self._confidence_threshold) + if not passed and record_failed_sample: + self._record_failed_sample( + { + "title": title, + "path": path, + "meta_hint": self._build_meta_hint(path or title), + "guess": guess.model_dump(), + "verified_media_info": self._compact_verified_summary(verified), + "reason": "low_confidence_or_empty_name", + } + ) + return { + "success": passed, + "message": "success" if passed else "识别结果置信度不足,已放弃注入", + "guess": guess.model_dump(), + "verified_media_info": verified, + } + + def on_chain_name_recognize(self, event) -> None: + if not self._enabled: + return + event_data = getattr(event, "event_data", None) or {} + title, path = self._extract_title_path(event_data) + if not title and not path: + return + result = self._recognize(title=title, path=path) + if not result.get("success"): + if self._debug: + logger.info(f"[AI识别增强] 跳过注入: {title or path} - {result.get('message')}") + return + guess = result.get("guess") or {} + if isinstance(event_data, dict): + event_data["name"] = guess.get("name", "") + event_data["year"] = guess.get("year", "") + event_data["season"] = guess.get("season", 0) + event_data["episode"] = guess.get("episode", 0) + event_data["source_plugin"] = "AIRecognizerEnhancer" + event_data["confidence"] = guess.get("confidence", 0) + event_data["reason"] = guess.get("reason", "") + + async def api_health(self, request: Request): + ok, message = self._check_api_access(request) + if not ok: + return {"success": False, "message": message} + llm_ready = bool(getattr(settings, "LLM_API_KEY", None)) + return { + "success": True, + "data": { + "plugin_version": self.plugin_version, + "enabled": self._enabled, + "llm_ready": llm_ready, + "llm_provider": getattr(settings, "LLM_PROVIDER", ""), + "llm_model": getattr(settings, "LLM_MODEL", ""), + "confidence_threshold": self._confidence_threshold, + "request_timeout": self._request_timeout, + }, + } + + async def api_recognize(self, request: Request): + body = await request.json() + ok, message = self._check_api_access(request, body) + if not ok: + return {"success": False, "message": message} + if not self._enabled: + return {"success": False, "message": "插件未启用"} + title = str(body.get("title") or "").strip() + path = str(body.get("path") or "").strip() + result = self._recognize(title=title, path=path) + return { + "success": result.get("success", False), + "message": result.get("message", ""), + "data": { + "guess": result.get("guess"), + "verified_media_info": result.get("verified_media_info"), + }, + } + + async def api_failed_samples(self, request: Request): + ok, message = self._check_api_access(request) + if not ok: + return {"success": False, "message": message} + limit = self._safe_int(request.query_params.get("limit"), 20) + limit = max(1, min(limit, 100)) + samples = self._inject_sample_indices(self._read_failed_samples(limit=limit)) + return { + "success": True, + "data": { + "count": len(samples), + "samples": samples, + }, + } + + async def api_sample_worklist(self, request: Request): + ok, message = self._check_api_access(request) + if not ok: + return {"success": False, "message": message} + limit = self._safe_int(request.query_params.get("limit"), 20) + limit = max(1, min(limit, 100)) + samples = self._inject_sample_indices(self._read_failed_samples(limit=limit)) + worklist = [self._summarize_sample(sample) for sample in samples] + return { + "success": True, + "data": { + "count": len(worklist), + "samples": worklist, + }, + } + + async def api_sample_insights(self, request: Request): + ok, message = self._check_api_access(request) + if not ok: + return {"success": False, "message": message} + limit = self._safe_int(request.query_params.get("limit"), 50) + limit = max(1, min(limit, 200)) + top = self._safe_int(request.query_params.get("top"), 10) + top = max(1, min(top, 20)) + samples = self._inject_sample_indices(self._read_failed_samples(limit=limit)) + insights = self._build_sample_insights(samples, top=top) + return { + "success": True, + "data": insights, + } + + async def api_sample_brief(self, request: Request): + ok, message = self._check_api_access(request) + if not ok: + return {"success": False, "message": message} + limit = self._safe_int(request.query_params.get("limit"), 5) + limit = max(1, min(limit, 20)) + samples = self._inject_sample_indices(self._read_failed_samples(limit=100)) + return { + "success": True, + "data": { + "count": len(samples), + "text": self._render_sample_brief(samples, top=limit), + }, + } + + async def api_suggest_identifiers(self, request: Request): + body = await request.json() + ok, message = self._check_api_access(request, body) + if not ok: + return {"success": False, "message": message} + if not self._enabled: + return {"success": False, "message": "插件未启用"} + return self._suggest_identifiers(body) + + async def api_apply_identifiers(self, request: Request): + body = await request.json() + ok, message = self._check_api_access(request, body) + if not ok: + return {"success": False, "message": message} + identifiers = body.get("identifiers") or [] + if not isinstance(identifiers, list): + return {"success": False, "message": "identifiers 必须是数组"} + result = self._append_custom_identifiers([str(line or "") for line in identifiers]) + return { + "success": True, + "message": "success", + "data": result, + } + + async def api_clear_failed_samples(self, request: Request): + ok, message = self._check_api_access(request) + if not ok: + return {"success": False, "message": message} + cleared = self._clear_failed_samples() + return { + "success": True, + "message": "success", + "data": { + "cleared_count": cleared, + }, + } + + async def api_remove_failed_sample(self, request: Request): + body = await request.json() + ok, message = self._check_api_access(request, body) + if not ok: + return {"success": False, "message": message} + result = self._remove_failed_sample(body.get("sample_index"), limit=1000) + if not result.get("removed"): + return {"success": False, "message": result.get("message", "移除失败"), "data": result} + return { + "success": True, + "message": "success", + "data": result, + } + + async def api_replay_failed_sample(self, request: Request): + body = await request.json() + ok, message = self._check_api_access(request, body) + if not ok: + return {"success": False, "message": message} + if not self._enabled: + return {"success": False, "message": "插件未启用"} + return self._replay_failed_sample(body) + + async def api_replay_failed_samples(self, request: Request): + body = await request.json() + ok, message = self._check_api_access(request, body) + if not ok: + return {"success": False, "message": message} + if not self._enabled: + return {"success": False, "message": "插件未启用"} + return self._replay_failed_samples(body) + + async def api_suggest_identifiers_from_sample(self, request: Request): + body = await request.json() + body["use_latest_sample"] = True if body.get("use_latest_sample") is None else body.get("use_latest_sample") + ok, message = self._check_api_access(request, body) + if not ok: + return {"success": False, "message": message} + if not self._enabled: + return {"success": False, "message": "插件未启用"} + if body.get("sample_index") is None and body.get("use_latest_sample") is False: + body["use_latest_sample"] = True + return self._suggest_identifiers(body) + + async def api_suggest_identifiers_for_failed_samples(self, request: Request): + body = await request.json() + ok, message = self._check_api_access(request, body) + if not ok: + return {"success": False, "message": message} + if not self._enabled: + return {"success": False, "message": "插件未启用"} + return self._suggest_identifiers_for_failed_samples(body) + + async def api_apply_suggested_identifier(self, request: Request): + body = await request.json() + ok, message = self._check_api_access(request, body) + if not ok: + return {"success": False, "message": message} + if not self._enabled: + return {"success": False, "message": "插件未启用"} + return self._apply_suggested_identifier_internal(body) + + async def api_apply_suggested_identifiers_for_failed_samples(self, request: Request): + body = await request.json() + ok, message = self._check_api_access(request, body) + if not ok: + return {"success": False, "message": message} + if not self._enabled: + return {"success": False, "message": "插件未启用"} + return self._apply_suggested_identifiers_for_failed_samples(body) + + def get_api(self) -> List[Dict[str, Any]]: + return [ + { + "path": "/health", + "endpoint": self.api_health, + "methods": ["GET"], + "summary": "检查 AI识别增强 的运行状态", + }, + { + "path": "/recognize", + "endpoint": self.api_recognize, + "methods": ["POST"], + "summary": "用当前 LLM 对失败标题做一次本地结构化识别测试", + }, + { + "path": "/failed_samples", + "endpoint": self.api_failed_samples, + "methods": ["GET"], + "summary": "查看最近保存的低置信度失败样本", + }, + { + "path": "/sample_worklist", + "endpoint": self.api_sample_worklist, + "methods": ["GET"], + "summary": "返回适合智能体使用的失败样本摘要列表", + }, + { + "path": "/sample_insights", + "endpoint": self.api_sample_insights, + "methods": ["GET"], + "summary": "汇总失败样本原因、重复问题和优先处理样本", + }, + { + "path": "/sample_brief", + "endpoint": self.api_sample_brief, + "methods": ["GET"], + "summary": "返回适合智能体低 token 消费的失败样本精简摘要", + }, + { + "path": "/suggest_identifiers", + "endpoint": self.api_suggest_identifiers, + "methods": ["POST"], + "summary": "根据标题和目标结果生成 MoviePilot 自定义识别词建议", + }, + { + "path": "/suggest_identifiers_from_sample", + "endpoint": self.api_suggest_identifiers_from_sample, + "methods": ["POST"], + "summary": "直接基于最近失败样本或指定样本生成自定义识别词建议", + }, + { + "path": "/suggest_identifiers_for_failed_samples", + "endpoint": self.api_suggest_identifiers_for_failed_samples, + "methods": ["POST"], + "summary": "批量为失败样本生成自定义识别词建议", + }, + { + "path": "/apply_identifiers", + "endpoint": self.api_apply_identifiers, + "methods": ["POST"], + "summary": "将确认后的自定义识别词追加写入系统 CustomIdentifiers", + }, + { + "path": "/clear_failed_samples", + "endpoint": self.api_clear_failed_samples, + "methods": ["POST"], + "summary": "清空失败样本文件", + }, + { + "path": "/remove_failed_sample", + "endpoint": self.api_remove_failed_sample, + "methods": ["POST"], + "summary": "按索引移除单条失败样本", + }, + { + "path": "/replay_failed_sample", + "endpoint": self.api_replay_failed_sample, + "methods": ["POST"], + "summary": "按当前识别词和当前识别器复查某条失败样本,并可在确认修复后自动出队", + }, + { + "path": "/replay_failed_samples", + "endpoint": self.api_replay_failed_samples, + "methods": ["POST"], + "summary": "批量复查失败样本,并可在确认修复后批量出队", + }, + { + "path": "/apply_suggested_identifier", + "endpoint": self.api_apply_suggested_identifier, + "methods": ["POST"], + "summary": "直接把最近失败样本或指定样本生成的建议规则写入 CustomIdentifiers,并按需移除该样本", + }, + { + "path": "/apply_suggested_identifiers_for_failed_samples", + "endpoint": self.api_apply_suggested_identifiers_for_failed_samples, + "methods": ["POST"], + "summary": "批量把失败样本生成的建议规则写入 CustomIdentifiers,并按需移除对应样本", + }, + ] + + def get_page(self) -> List[dict]: + llm_ready = bool(getattr(settings, "LLM_API_KEY", None)) + failed_samples_count = len(self._read_failed_samples(limit=200)) + custom_identifiers_count = len(self._get_custom_identifiers()) + llm_provider = getattr(settings, "LLM_PROVIDER", "—") + llm_model = getattr(settings, "LLM_MODEL", "—") + + def stat_card(title: str, value: Any, subtitle: str = "") -> dict: + content = [ + { + "component": "div", + "props": {"class": "text-caption text-medium-emphasis mb-1"}, + "text": title, + }, + { + "component": "div", + "props": {"class": "text-h6 font-weight-bold"}, + "text": str(value), + }, + ] + if subtitle: + content.append( + { + "component": "div", + "props": {"class": "text-caption text-medium-emphasis mt-1"}, + "text": subtitle, + } + ) + return { + "component": "VCard", + "props": {"variant": "tonal", "class": "pa-4 h-100"}, + "content": content, + } + + return [ + { + "component": "VContainer", + "props": {"fluid": True, "class": "pa-0"}, + "content": [ + { + "component": "VAlert", + "props": { + "type": "info", + "variant": "tonal", + "class": "mb-4", + "title": "本地 LLM 识别兜底", + "text": "复用 MoviePilot 当前 LLM 配置,在原生识别失败时做结构化兜底,并把结果交回 MoviePilot 继续二次识别。", + }, + }, + { + "component": "VRow", + "props": {"dense": True, "class": "mb-2"}, + "content": [ + { + "component": "VCol", + "props": {"cols": 12, "md": 3}, + "content": [stat_card("当前状态", "已启用" if self._enabled else "未启用")], + }, + { + "component": "VCol", + "props": {"cols": 12, "md": 3}, + "content": [stat_card("LLM 可用", "是" if llm_ready else "否", f"{llm_provider} / {llm_model}")], + }, + { + "component": "VCol", + "props": {"cols": 12, "md": 3}, + "content": [stat_card("失败样本", f"{failed_samples_count} 条", f"上限 {self._max_failed_samples} 条")], + }, + { + "component": "VCol", + "props": {"cols": 12, "md": 3}, + "content": [stat_card("自定义识别词", f"{custom_identifiers_count} 条", "系统 CustomIdentifiers")], + }, + ], + }, + { + "component": "VRow", + "props": {"dense": True}, + "content": [ + { + "component": "VCol", + "props": {"cols": 12, "md": 6}, + "content": [ + { + "component": "VCard", + "props": {"variant": "outlined", "class": "pa-4 h-100"}, + "content": [ + { + "component": "div", + "props": {"class": "text-subtitle-1 font-weight-bold mb-2"}, + "text": "识别兜底", + }, + { + "component": "div", + "props": {"class": "text-body-2 text-medium-emphasis"}, + "text": "在 Chain NameRecognize 阶段回写 name / year / season / episode,供 MoviePilot 继续原生二次识别。", + }, + { + "component": "div", + "props": {"class": "text-caption text-medium-emphasis mt-3"}, + "text": f"置信度阈值:{self._confidence_threshold};请求超时:{self._request_timeout} 秒", + }, + ], + } + ], + }, + { + "component": "VCol", + "props": {"cols": 12, "md": 6}, + "content": [ + { + "component": "VCard", + "props": {"variant": "outlined", "class": "pa-4 h-100"}, + "content": [ + { + "component": "div", + "props": {"class": "text-subtitle-1 font-weight-bold mb-2"}, + "text": "识别词闭环", + }, + { + "component": "div", + "props": {"class": "text-body-2 text-medium-emphasis"}, + "text": "失败样本可生成 CustomIdentifiers 建议,并按需追加写入系统配置。", + }, + { + "component": "div", + "props": {"class": "text-caption text-medium-emphasis mt-3"}, + "text": f"写入后自动移除样本:{'是' if self._auto_remove_applied_sample else '否'}", + }, + ], + } + ], + }, + ], + }, + ], + } + ] + + @staticmethod + def get_render_mode() -> Tuple[str, Optional[str]]: + return "vuetify", None + + def get_form(self) -> Tuple[List[dict], Dict[str, Any]]: + form = [ + { + "component": "VForm", + "content": [ + { + "component": "VRow", + "content": [ + { + "component": "VCol", + "props": {"cols": 12}, + "content": [ + { + "component": "VAlert", + "props": { + "type": "info", + "variant": "tonal", + "text": "当前版本已改为直接复用 MoviePilot 当前启用的 LLM 配置,在原生识别失败后做本地结构化兜底。", + }, + } + ], + } + ], + }, + { + "component": "VRow", + "content": [ + { + "component": "VCol", + "props": {"cols": 12, "md": 4}, + "content": [ + { + "component": "VSwitch", + "props": {"model": "enabled", "label": "启用 AI识别增强"}, + } + ], + }, + { + "component": "VCol", + "props": {"cols": 12, "md": 4}, + "content": [ + { + "component": "VSwitch", + "props": {"model": "debug", "label": "调试模式"}, + } + ], + }, + { + "component": "VCol", + "props": {"cols": 12, "md": 4}, + "content": [ + { + "component": "VSwitch", + "props": {"model": "save_failed_samples", "label": "保存低置信度样本"}, + } + ], + }, + ], + }, + { + "component": "VRow", + "content": [ + { + "component": "VCol", + "props": {"cols": 12, "md": 6}, + "content": [ + { + "component": "VTextField", + "props": { + "model": "confidence_threshold", + "label": "置信度阈值", + "type": "number", + "hint": "低于该值的结果不注入 MoviePilot,默认 0.65", + "persistent-hint": True, + }, + } + ], + }, + { + "component": "VCol", + "props": {"cols": 12, "md": 6}, + "content": [ + { + "component": "VTextField", + "props": { + "model": "request_timeout", + "label": "LLM 请求超时(秒)", + "type": "number", + "hint": "默认 25 秒", + "persistent-hint": True, + }, + } + ], + }, + ], + }, + { + "component": "VRow", + "content": [ + { + "component": "VCol", + "props": {"cols": 12}, + "content": [ + { + "component": "VTextField", + "props": { + "model": "max_retries", + "label": "结构化输出重试次数", + "type": "number", + "hint": "默认 2 次", + "persistent-hint": True, + }, + } + ], + } + ], + }, + { + "component": "VRow", + "content": [ + { + "component": "VCol", + "props": {"cols": 12}, + "content": [ + { + "component": "VTextField", + "props": { + "model": "max_failed_samples", + "label": "失败样本保留上限", + "type": "number", + "hint": "默认保留最近 200 条,并对重复样本自动去重", + "persistent-hint": True, + }, + } + ], + } + ], + }, + { + "component": "VRow", + "content": [ + { + "component": "VCol", + "props": {"cols": 12}, + "content": [ + { + "component": "VSwitch", + "props": { + "model": "auto_remove_applied_sample", + "label": "写入识别词后自动移除对应失败样本", + }, + } + ], + } + ], + }, + ], + } + ] + return form, { + "enabled": False, + "debug": False, + "confidence_threshold": 0.65, + "request_timeout": 25, + "max_retries": 2, + "save_failed_samples": True, + "max_failed_samples": 200, + "auto_remove_applied_sample": True, + } diff --git a/plugins/airecognizerenhancer/README.md b/plugins/airecognizerenhancer/README.md new file mode 100644 index 0000000..ea48943 --- /dev/null +++ b/plugins/airecognizerenhancer/README.md @@ -0,0 +1,99 @@ +# AI识别增强 + +`AI识别增强` 用来补强 MoviePilot 原生整理链里的识别阶段。 + +它的核心思路很简单: + +- 复用 MoviePilot 当前已经启用的 LLM 配置 +- 在原生识别失败或置信度不足时,做一次本地结构化识别兜底 +- 把结果回写给 MoviePilot,继续走原生二次识别和后续整理链 + +## 适合什么场景 + +- 文件名比较脏,混有压制组、分辨率、语言、站点标记 +- 同一部剧经常出现英文名、别名、原名、翻译名混用 +- 网盘挂载、手动整理、历史资源补录时,原生识别偶尔不稳定 +- 你想把失败样本沉淀下来,后面持续优化 `CustomIdentifiers` + +## 和 MoviePilot 原版智能体的区别 + +MoviePilot 原版智能体已经提供“整理失败后自动接管再试一次”的能力。 + +这和 `AI识别增强` 有重叠,但定位不同: + +- **MP 原版智能体** + - 更偏“一次性补救” + - 适合偶发失败、想省事的场景 + +- **AI识别增强** + - 更偏“识别失败治理层” + - 除了补救当前这次,还能: + - 保存失败样本 + - 汇总样本洞察 + - 生成 `CustomIdentifiers` 建议 + - 写入识别词 + - 重放 / 复查 / 批量出队 + +一句话区分: + +- 原版智能体:自动接管一次 +- `AI识别增强`:把失败样本沉淀下来,长期减少同类失败 + +## 当前能力 + +- 监听 `ChainEventType.NameRecognize` +- 用当前 LLM 结构化判断标题、年份、类型、季集 +- 回写 `name / year / season / episode` +- 交回 MoviePilot 原生链路继续二次识别 +- 保存低置信度失败样本 +- 提供失败样本工作清单、洞察、重放、删除和清空能力 +- 生成并应用 `CustomIdentifiers` 建议 + +## 主要接口 + +- `GET /api/v1/plugin/AIRecognizerEnhancer/health` + - 查看插件状态、LLM 提供方、模型、阈值和超时配置 +- `POST /api/v1/plugin/AIRecognizerEnhancer/recognize` + - 对单个标题做一次本地结构化识别测试 +- `GET /api/v1/plugin/AIRecognizerEnhancer/failed_samples` + - 查看最近保存的失败样本 +- `GET /api/v1/plugin/AIRecognizerEnhancer/sample_worklist` + - 返回适合继续处理的失败样本摘要列表 +- `GET /api/v1/plugin/AIRecognizerEnhancer/sample_insights` + - 汇总失败原因、重复问题和优先处理样本 +- `POST /api/v1/plugin/AIRecognizerEnhancer/replay_failed_sample` + - 用当前识别词和当前识别器重放复查某条失败样本 +- `POST /api/v1/plugin/AIRecognizerEnhancer/suggest_identifiers_from_sample` + - 直接基于失败样本生成识别词建议 +- `POST /api/v1/plugin/AIRecognizerEnhancer/apply_suggested_identifier` + - 把建议规则写入系统 `CustomIdentifiers` + +其余批量接口和清理接口可以按需要继续使用,详细路径以插件 `get_api()` 暴露结果为准。 + +## 配置建议 + +- 先确认 MoviePilot 本身已经配置好可用的 LLM +- 建议保持“保存失败样本”开启 +- 如果你经常处理历史资源或网盘资源,建议定期查看: + - `failed_samples` + - `sample_worklist` + - `sample_insights` + +## 已验证情况 + +当前版本:`0.1.12` + +这版已经验证过: + +- 最新版 MoviePilot 下可以正常加载 +- 正常中文标题识别可用 +- 英文别名、韩文原名、中文别名可识别回标准媒体信息 +- 低置信度标题会落失败样本 +- `replay_failed_sample` 复查链可用 + +## 说明 + +- 这个插件不依赖外部 AI Gateway 回调链 +- 重点是增强识别,不负责替代 MoviePilot 全部整理流程 +- 如果你只是偶发整理失败,原版智能体可能已经够用 +- 如果你长期受命名混乱困扰,这个插件更有价值 diff --git a/plugins/airecognizerenhancer/__init__.py b/plugins/airecognizerenhancer/__init__.py new file mode 100644 index 0000000..4471184 --- /dev/null +++ b/plugins/airecognizerenhancer/__init__.py @@ -0,0 +1,2039 @@ +import hmac +import asyncio +import inspect +import json +import re +import threading +from collections import Counter +from pathlib import Path +from typing import Any, Dict, List, Optional, Tuple + +from fastapi import Request +from langchain_core.prompts import ChatPromptTemplate +from pydantic import BaseModel, Field + +from app.chain.media import MediaChain +from app.core.config import settings +from app.core.event import eventmanager +from app.core.meta.words import WordsMatcher +from app.core.metainfo import MetaInfo +from app.db.systemconfig_oper import SystemConfigOper +try: + from app.helper.llm import LLMHelper +except ImportError: # MoviePilot 新版已迁移到 app.agent.llm + from app.agent.llm import LLMHelper +from app.log import logger +from app.plugins import _PluginBase +from app.schemas.types import ChainEventType, MediaType, SystemConfigKey + + +class AIRecognitionGuess(BaseModel): + name: str = Field(default="", description="标准化后的影视标题;无法判断时返回空字符串") + year: str = Field(default="", description="四位年份;无法判断时返回空字符串") + media_type: str = Field(default="unknown", description="movie、tv 或 unknown") + season: int = Field(default=0, description="剧集季号,电影填 0") + episode: int = Field(default=0, description="剧集集号,电影或未知填 0") + confidence: float = Field(default=0.0, description="0 到 1 之间的置信度") + reason: str = Field(default="", description="简短说明为什么这样判断") + + +class IdentifierSuggestion(BaseModel): + comment: str = Field(default="", description="可选注释,不带 #") + rule: str = Field(default="", description="一条 MoviePilot 自定义识别词规则") + confidence: float = Field(default=0.0, description="0 到 1 之间的置信度") + reason: str = Field(default="", description="为什么建议这条规则") + + +class IdentifierSuggestionBundle(BaseModel): + summary: str = Field(default="", description="整体建议摘要") + suggestions: List[IdentifierSuggestion] = Field(default_factory=list, description="建议规则列表") + + +class AIRecognizerEnhancer(_PluginBase): + plugin_name = "AI识别增强" + plugin_desc = "直接复用 MoviePilot 当前 LLM 配置,在原生识别失败后做本地结构化识别兜底,并交回原生链路继续二次识别。" + plugin_icon = "https://raw.githubusercontent.com/liuyuexi1987/MoviePilot-Plugins/main/icons/airecognizerenhancer.png" + plugin_version = "0.1.12" + plugin_author = "liuyuexi1987" + plugin_level = 1 + author_url = "https://github.com/liuyuexi1987" + plugin_config_prefix = "arrecognizerenhancer_" + plugin_order = 41 + auth_level = 1 + + _enabled = False + _debug = False + _confidence_threshold = 0.65 + _request_timeout = 25 + _max_retries = 2 + _save_failed_samples = True + _max_failed_samples = 200 + _auto_remove_applied_sample = True + _systemconfig: Optional[SystemConfigOper] = None + + def init_plugin(self, config: Optional[Dict[str, Any]] = None): + config = config or {} + self._enabled = bool(config.get("enabled", False)) + self._debug = bool(config.get("debug", False)) + self._confidence_threshold = self._safe_float(config.get("confidence_threshold"), 0.65) + self._request_timeout = self._safe_int(config.get("request_timeout"), 25) + self._max_retries = max(1, min(5, self._safe_int(config.get("max_retries"), 2))) + self._save_failed_samples = bool(config.get("save_failed_samples", True)) + self._max_failed_samples = max(20, min(1000, self._safe_int(config.get("max_failed_samples"), 200))) + self._auto_remove_applied_sample = bool(config.get("auto_remove_applied_sample", True)) + self._systemconfig = SystemConfigOper() + self._register_events() + + def get_state(self) -> bool: + return self._enabled + + @staticmethod + def get_command() -> List[Dict[str, Any]]: + return [] + + def stop_service(self): + try: + eventmanager.disable_event_handler(self.on_chain_name_recognize) + except Exception: + pass + + @staticmethod + def _safe_int(value: Any, default: int) -> int: + try: + return int(value) + except Exception: + return default + + @staticmethod + def _safe_float(value: Any, default: float) -> float: + try: + return float(value) + except Exception: + return default + + @staticmethod + def _extract_apikey(request: Request, body: Optional[Dict[str, Any]] = None) -> str: + header = str(request.headers.get("Authorization") or "").strip() + if header.lower().startswith("bearer "): + return header.split(" ", 1)[1].strip() + if body: + for key in ("apikey", "api_key"): + token = str(body.get(key) or "").strip() + if token: + return token + return str(request.query_params.get("apikey") or "").strip() + + def _check_api_access(self, request: Request, body: Optional[Dict[str, Any]] = None) -> Tuple[bool, str]: + expected = str(getattr(settings, "API_TOKEN", "") or "").strip() + if not expected: + return False, "服务端未配置 API Token" + actual = self._extract_apikey(request, body) + if not hmac.compare_digest(actual, expected): + return False, "API Token 无效" + return True, "" + + def _register_events(self) -> None: + try: + eventmanager.register(ChainEventType.NameRecognize)(self.on_chain_name_recognize) + if self._enabled: + eventmanager.enable_event_handler(self.on_chain_name_recognize) + else: + eventmanager.disable_event_handler(self.on_chain_name_recognize) + except Exception as exc: + logger.warning(f"[AI识别增强] 注册链式识别事件失败: {exc}") + + @staticmethod + def _extract_title_path(event_data: Any) -> Tuple[str, str]: + title = "" + path = "" + if isinstance(event_data, dict): + title = ( + event_data.get("title") + or event_data.get("name") + or event_data.get("org_string") + or "" + ) + path = ( + event_data.get("path") + or event_data.get("file_path") + or event_data.get("org_string") + or "" + ) + else: + title = ( + getattr(event_data, "title", "") + or getattr(event_data, "name", "") + or getattr(event_data, "org_string", "") + or "" + ) + path = ( + getattr(event_data, "path", "") + or getattr(event_data, "file_path", "") + or getattr(event_data, "org_string", "") + or "" + ) + return str(title or "").strip(), str(path or "").strip() + + def _build_meta_hint(self, raw_text: str) -> Dict[str, Any]: + try: + meta = MetaInfo(raw_text) + except Exception: + return {} + return { + "name": getattr(meta, "name", "") or "", + "year": getattr(meta, "year", "") or "", + "type": getattr(getattr(meta, "type", None), "to_agent", lambda: None)() or "", + "season": getattr(meta, "begin_season", None) or 0, + "episode": getattr(meta, "begin_episode", None) or 0, + "org_string": getattr(meta, "org_string", "") or "", + } + + @staticmethod + def _clean_guess_name(name: str) -> str: + text = str(name or "").strip() + if not text: + return "" + text = text.split("/")[0].strip().replace(".", " ") + return " ".join(text.split()) + + def _normalize_guess(self, guess: AIRecognitionGuess) -> AIRecognitionGuess: + name = self._clean_guess_name(guess.name) + year = str(guess.year or "").strip() + if not (len(year) == 4 and year.isdigit()): + year = "" + media_type = str(guess.media_type or "unknown").strip().lower() + if media_type not in {"movie", "tv"}: + media_type = "unknown" + season = max(0, self._safe_int(guess.season, 0)) + episode = max(0, self._safe_int(guess.episode, 0)) + confidence = min(1.0, max(0.0, self._safe_float(guess.confidence, 0.0))) + reason = str(guess.reason or "").strip() + return AIRecognitionGuess( + name=name, + year=year, + media_type=media_type, + season=season, + episode=episode, + confidence=confidence, + reason=reason, + ) + + def _sample_path(self) -> Path: + return self.get_data_path() / "failed_samples.jsonl" + + @staticmethod + def _sample_identity(payload: Dict[str, Any]) -> str: + return json.dumps( + { + "title": str(payload.get("title") or "").strip(), + "path": str(payload.get("path") or "").strip(), + "reason": str(payload.get("reason") or "").strip(), + }, + ensure_ascii=False, + sort_keys=True, + ) + + def _write_failed_samples(self, rows: List[Dict[str, Any]]) -> None: + sample_path = self._sample_path() + sample_path.parent.mkdir(parents=True, exist_ok=True) + trimmed = rows[-self._max_failed_samples:] + with sample_path.open("w", encoding="utf-8") as f: + for row in trimmed: + f.write(json.dumps(row, ensure_ascii=False) + "\n") + + def _record_failed_sample(self, payload: Dict[str, Any]) -> None: + if not self._save_failed_samples: + return + try: + rows = self._read_failed_samples(limit=1000) + rows.reverse() + identity = self._sample_identity(payload) + filtered = [row for row in rows if self._sample_identity(row) != identity] + filtered.append(payload) + self._write_failed_samples(filtered) + except Exception as exc: + logger.warning(f"[AI识别增强] 写入失败样本失败: {exc}") + + def _read_failed_samples(self, limit: int = 20) -> List[Dict[str, Any]]: + sample_path = self._sample_path() + if not sample_path.exists(): + return [] + rows: List[Dict[str, Any]] = [] + try: + with sample_path.open("r", encoding="utf-8") as f: + for line in f: + line = line.strip() + if not line: + continue + try: + rows.append(json.loads(line)) + except Exception: + continue + except Exception as exc: + logger.warning(f"[AI识别增强] 读取失败样本失败: {exc}") + return [] + if limit > 0: + rows = rows[-limit:] + rows.reverse() + return rows + + def _clear_failed_samples(self) -> int: + rows = self._read_failed_samples(limit=1000) + sample_path = self._sample_path() + if sample_path.exists(): + sample_path.unlink() + return len(rows) + + def _remove_failed_sample(self, sample_index: Optional[Any], limit: int = 1000) -> Dict[str, Any]: + rows = self._read_failed_samples(limit=max(1, min(limit, 1000))) + if not rows: + return {"removed": False, "message": "暂无失败样本", "removed_count": 0} + index = self._safe_int(sample_index, 0) + if index < 0: + index = 0 + if index >= len(rows): + return { + "removed": False, + "message": f"失败样本索引超出范围,当前共有 {len(rows)} 条", + "removed_count": 0, + } + removed_sample = dict(rows[index] or {}) + del rows[index] + if rows: + rows.reverse() + self._write_failed_samples(rows) + else: + self._clear_failed_samples() + return { + "removed": True, + "message": "success", + "removed_count": 1, + "remaining_count": len(rows), + "removed_sample": removed_sample, + "removed_sample_index": index, + } + + def _remove_failed_samples(self, sample_indexes: List[Any], limit: int = 1000) -> Dict[str, Any]: + rows = self._read_failed_samples(limit=max(1, min(limit, 1000))) + if not rows: + return {"removed": False, "message": "暂无失败样本", "removed_count": 0, "remaining_count": 0} + normalized_indexes = sorted( + {self._safe_int(index, -1) for index in (sample_indexes or []) if self._safe_int(index, -1) >= 0}, + reverse=True, + ) + valid_indexes = [index for index in normalized_indexes if index < len(rows)] + if not valid_indexes: + return { + "removed": False, + "message": "没有可移除的有效样本索引", + "removed_count": 0, + "remaining_count": len(rows), + } + removed_samples: List[Dict[str, Any]] = [] + for index in valid_indexes: + removed_samples.append(dict(rows[index] or {})) + del rows[index] + if rows: + rows.reverse() + self._write_failed_samples(rows) + else: + self._clear_failed_samples() + removed_samples.reverse() + return { + "removed": True, + "message": "success", + "removed_count": len(valid_indexes), + "remaining_count": len(rows), + "removed_sample_indexes": sorted(valid_indexes), + "removed_samples": removed_samples, + } + + def _resolve_failed_sample( + self, + sample_index: Optional[Any] = None, + limit: int = 100, + ) -> Tuple[Optional[int], Optional[Dict[str, Any]], str]: + samples = self._read_failed_samples(limit=max(1, min(limit, 200))) + if not samples: + return None, None, "暂无失败样本" + index = self._safe_int(sample_index, 0) + if index < 0: + index = 0 + if index >= len(samples): + return None, None, f"失败样本索引超出范围,当前共有 {len(samples)} 条" + row = dict(samples[index] or {}) + row["sample_index"] = index + return index, row, "" + + def _select_failed_sample_indexes( + self, + sample_indexes: Optional[List[Any]] = None, + limit: int = 10, + pool_limit: int = 200, + ) -> Tuple[List[int], List[Dict[str, Any]], str]: + current_samples = self._inject_sample_indices(self._read_failed_samples(limit=max(1, min(pool_limit, 1000)))) + if not current_samples: + return [], [], "暂无失败样本" + if isinstance(sample_indexes, list) and sample_indexes: + selected_indexes: List[int] = [] + seen = set() + for raw in sample_indexes: + idx = self._safe_int(raw, -1) + if idx < 0 or idx >= len(current_samples) or idx in seen: + continue + seen.add(idx) + selected_indexes.append(idx) + else: + selected_indexes = [int(sample.get("sample_index", 0)) for sample in current_samples[: max(1, min(limit, 50))]] + if not selected_indexes: + return [], current_samples, "没有可处理的有效样本索引" + return selected_indexes, current_samples, "" + + def _inject_sample_indices(self, samples: List[Dict[str, Any]]) -> List[Dict[str, Any]]: + indexed: List[Dict[str, Any]] = [] + for idx, sample in enumerate(samples): + row = dict(sample or {}) + row["sample_index"] = idx + indexed.append(row) + return indexed + + def _summarize_sample(self, sample: Dict[str, Any]) -> Dict[str, Any]: + sample = dict(sample or {}) + guess = sample.get("guess") or {} + verified = sample.get("verified_media_info") or {} + inferred_target = { + "name": verified.get("title") or guess.get("name") or "", + "year": verified.get("year") or guess.get("year") or "", + "media_type": self._normalize_media_type(verified.get("type") or guess.get("media_type")), + "season": self._safe_int(guess.get("season"), 0), + "episode": self._safe_int(guess.get("episode"), 0), + "tmdb_id": self._safe_int(verified.get("tmdb_id"), 0), + } + return { + "sample_index": sample.get("sample_index"), + "title": sample.get("title"), + "path": sample.get("path"), + "reason": sample.get("reason"), + "guess_name": guess.get("name"), + "guess_confidence": self._safe_float(guess.get("confidence"), 0.0), + "verified_title": verified.get("title"), + "verified_year": verified.get("year"), + "verified_tmdb_id": verified.get("tmdb_id"), + "inferred_target": inferred_target, + "can_auto_suggest": bool(inferred_target["name"]), + } + + def _target_from_sample(self, sample: Dict[str, Any]) -> Dict[str, Any]: + summary = self._summarize_sample(sample) + return summary.get("inferred_target") or {} + + @staticmethod + def _normalize_reason_tag(reason: Any) -> str: + text = str(reason or "").strip() + if not text: + return "unknown" + if ":" in text: + return text.split(":", 1)[0].strip() or "unknown" + return text + + @staticmethod + def _sample_group_key(summary: Dict[str, Any]) -> str: + target = summary.get("inferred_target") or {} + title = ( + str(target.get("name") or "").strip() + or str(summary.get("verified_title") or "").strip() + or str(summary.get("guess_name") or "").strip() + or str(summary.get("title") or "").strip() + ) + media_type = str(target.get("media_type") or "unknown").strip().lower() + season = int(target.get("season") or 0) + episode = int(target.get("episode") or 0) + return json.dumps( + { + "title": title.lower(), + "media_type": media_type, + "season": season, + "episode": episode, + }, + ensure_ascii=False, + sort_keys=True, + ) + + @staticmethod + def _sample_display_name(summary: Dict[str, Any]) -> str: + target = summary.get("inferred_target") or {} + title = ( + str(target.get("name") or "").strip() + or str(summary.get("verified_title") or "").strip() + or str(summary.get("guess_name") or "").strip() + or str(summary.get("title") or "").strip() + ) + if not title: + return "未命名样本" + media_type = str(target.get("media_type") or "").strip().lower() + season = int(target.get("season") or 0) + episode = int(target.get("episode") or 0) + suffix = "" + if media_type == "tv" and (season or episode): + suffix = f" S{season:02d}E{episode:02d}" + return f"{title}{suffix}" + + def _build_sample_insights(self, samples: List[Dict[str, Any]], top: int = 10) -> Dict[str, Any]: + summaries = [self._summarize_sample(sample) for sample in samples] + reason_counter = Counter() + title_counter = Counter() + group_counter = Counter() + for summary in summaries: + reason_counter[self._normalize_reason_tag(summary.get("reason"))] += 1 + title_counter[self._sample_display_name(summary)] += 1 + group_counter[self._sample_group_key(summary)] += 1 + + actionable: List[Dict[str, Any]] = [] + for summary in summaries: + duplicate_count = group_counter[self._sample_group_key(summary)] + priority_reasons: List[str] = [] + score = 0 + if duplicate_count >= 2: + score += min(duplicate_count, 5) + priority_reasons.append(f"同类样本重复出现 {duplicate_count} 次") + if summary.get("verified_tmdb_id"): + score += 3 + priority_reasons.append("已有 TMDB 命中") + if summary.get("can_auto_suggest"): + score += 2 + priority_reasons.append("可直接生成识别词") + confidence = self._safe_float(summary.get("guess_confidence"), 0.0) + if 0 < confidence < self._confidence_threshold: + gap = round(self._confidence_threshold - confidence, 2) + score += 1 + priority_reasons.append(f"距注入阈值还差 {gap}") + row = dict(summary) + row["duplicate_count"] = duplicate_count + row["priority_score"] = score + row["priority_reasons"] = priority_reasons + actionable.append(row) + + actionable.sort( + key=lambda item: ( + -int(item.get("priority_score") or 0), + -int(item.get("duplicate_count") or 0), + -self._safe_float(item.get("guess_confidence"), 0.0), + int(item.get("sample_index") or 0), + ) + ) + + repeated_groups = [ + {"title": name, "count": count} + for name, count in title_counter.most_common(top) + if count >= 2 + ] + + return { + "total_count": len(summaries), + "reason_counts": [ + {"reason": reason, "count": count} + for reason, count in reason_counter.most_common(top) + ], + "top_titles": [ + {"title": title, "count": count} + for title, count in title_counter.most_common(top) + ], + "repeated_groups": repeated_groups, + "priority_samples": actionable[:top], + } + + def _render_sample_brief(self, samples: List[Dict[str, Any]], top: int = 5) -> str: + summaries = [self._summarize_sample(sample) for sample in samples[: max(1, min(top, 20))]] + if not summaries: + return "当前没有失败样本。" + lines = [f"失败样本 {len(samples)} 条,展示前 {len(summaries)} 条:"] + for summary in summaries: + label = self._sample_display_name(summary) + confidence = round(self._safe_float(summary.get("guess_confidence"), 0.0), 2) + can_suggest = "可建议" if summary.get("can_auto_suggest") else "需人工" + lines.append(f"{summary.get('sample_index')}. {label} | 置信度 {confidence} | {can_suggest}") + lines.append("下一步:可直接调用批量建议或批量复查接口。") + return "\n".join(lines) + + @staticmethod + def _render_batch_results_brief( + action_name: str, + requested_count: int, + success_count: int, + failed_count: int, + results: List[Dict[str, Any]], + ) -> str: + lines = [f"{action_name}:共处理 {requested_count} 条,成功 {success_count},失败 {failed_count}。"] + for item in results[:10]: + idx = item.get("sample_index") + if item.get("success"): + label = ( + ((item.get("source_sample") or {}).get("title")) + or ((item.get("target") or {}).get("name")) + or "样本" + ) + lines.append(f"{idx}. 成功 | {label}") + else: + lines.append(f"{idx}. 失败 | {item.get('message', '未知错误')}") + return "\n".join(lines) + + def _build_body_from_sample(self, body: Dict[str, Any]) -> Tuple[Dict[str, Any], Optional[Dict[str, Any]], str]: + body = dict(body or {}) + title = str(body.get("title") or "").strip() + path = str(body.get("path") or "").strip() + sample_requested = body.get("use_latest_sample") or body.get("sample_index") is not None + if title or path: + return body, None, "" + if not sample_requested: + return body, None, "" + + sample_index, sample, message = self._resolve_failed_sample(body.get("sample_index"), limit=100) + if not sample: + return body, None, message + body["title"] = str(sample.get("title") or "").strip() + body["path"] = str(sample.get("path") or "").strip() + verified = sample.get("verified_media_info") or {} + guess = sample.get("guess") or {} + if not body.get("desired_name"): + body["desired_name"] = verified.get("title") or guess.get("name") or "" + if not body.get("desired_year"): + body["desired_year"] = verified.get("year") or guess.get("year") or "" + if not body.get("desired_media_type"): + body["desired_media_type"] = self._normalize_media_type( + verified.get("type") or guess.get("media_type") + ) + if body.get("desired_season") is None: + body["desired_season"] = guess.get("season") or 0 + if body.get("desired_episode") is None: + body["desired_episode"] = guess.get("episode") or 0 + if body.get("desired_tmdb_id") is None: + body["desired_tmdb_id"] = verified.get("tmdb_id") or 0 + body["sample_index"] = sample_index + return body, sample, "" + + def _build_prompt(self) -> ChatPromptTemplate: + return ChatPromptTemplate.from_messages( + [ + ( + "system", + """你是 MoviePilot 的影视文件名识别增强助手。 + +你的任务不是搜索 TMDB,也不是编造结果,而是根据文件名、路径和已有解析提示,尽量提炼出更适合 MoviePilot 二次识别的结构化信息。 + +规则: +1. 只依据输入内容推断,不要臆造不存在的信息。 +2. 如果不确定,请返回空标题,并把 media_type 设为 unknown,confidence 降低。 +3. title/name 只保留作品名,不要包含分辨率、制作组、音频编码、网盘标记等噪音。 +4. year 只有在比较确定时才给四位年份。 +5. 电影 season/episode 必须为 0。 +6. 剧集如果能确定季集就填写,否则保持 0。 +7. media_type 只能是 movie、tv、unknown。 +8. confidence 范围为 0 到 1。 +""", + ), + ( + "human", + """原始标题: +{title} + +原始路径: +{path} + +MoviePilot 当前基础解析提示: +{meta_hint} +""", + ), + ] + ) + + def _build_identifier_prompt(self) -> ChatPromptTemplate: + return ChatPromptTemplate.from_messages( + [ + ( + "system", + """你是 MoviePilot 自定义识别词规则助手。 + +你的任务是根据错误标题、当前解析结果和目标结果,生成尽量窄作用域、可直接用于 MoviePilot CustomIdentifiers 的规则。 + +支持格式只有四种: +1. 屏蔽词 +2. 替换词:被替换词 => 替换词 +3. 集偏移:前定位词 <> 后定位词 >> EP±N +4. 组合规则:被替换词 => 替换词 && 前定位词 <> 后定位词 >> EP±N + +硬性要求: +1. 运算符两侧必须保留空格: => 、 <> 、 >> 、 && +2. 优先生成窄作用域规则,尽量带发布组、年份、季集、分辨率等锚点 +3. 不要生成过宽的裸屏蔽词,比如 1080p、WEB-DL、字幕 +4. 如果需要强制绑 TMDB,可使用 {{[tmdbid=xxx;type=tv/movies;s=1;e=14]}} 这种替换词 +5. comment 不带 #,rule 里不要再包 markdown 或代码块 +6. 如果没有把握,请返回空 suggestions +""", + ), + ( + "human", + """原始标题: +{title} + +原始路径: +{path} + +MoviePilot 当前基础解析: +{meta_hint} + +AI 识别增强结果: +{guess} + +二次校验到的媒体信息摘要: +{verified_summary} + +希望修正成的目标结果: +{target} +""", + ), + ] + ) + + @staticmethod + def _run_async_compatible(value: Any) -> Any: + """ + 兼容 MoviePilot 新版 `LLMHelper.get_llm()` 的异步返回。 + 在同步上下文直接 asyncio.run;如果当前线程已有事件循环,则开一个短线程执行。 + """ + if not inspect.isawaitable(value): + return value + try: + asyncio.get_running_loop() + except RuntimeError: + return asyncio.run(value) + + result: Dict[str, Any] = {} + error: Dict[str, BaseException] = {} + + def _worker() -> None: + try: + result["value"] = asyncio.run(value) + except BaseException as exc: # noqa: BLE001 + error["exc"] = exc + + thread = threading.Thread(target=_worker, daemon=True) + thread.start() + thread.join() + if "exc" in error: + raise error["exc"] + return result.get("value") + + def _get_llm(self): + llm = LLMHelper.get_llm(streaming=False) + return self._run_async_compatible(llm) + + def _invoke_llm(self, title: str, path: str) -> AIRecognitionGuess: + raw_text = path or title + meta_hint = self._build_meta_hint(raw_text) + llm = self._get_llm() + prompt = self._build_prompt() + chain = ( + prompt + | llm.with_structured_output(AIRecognitionGuess).with_retry(stop_after_attempt=self._max_retries) + ) + result: AIRecognitionGuess = chain.invoke( + { + "title": title, + "path": path, + "meta_hint": meta_hint, + }, + config={"configurable": {"timeout": self._request_timeout}}, + ) + return self._normalize_guess(result) + + @staticmethod + def _normalize_media_type(value: Any) -> str: + if value == MediaType.MOVIE: + return "movie" + if value == MediaType.TV: + return "tv" + text = str(value or "").strip().lower() + if text in {"movie", "movies", "电影"}: + return "movie" + if text in {"tv", "电视剧", "剧集"}: + return "tv" + return "unknown" + + def _build_target(self, body: Dict[str, Any], result: Optional[Dict[str, Any]] = None) -> Dict[str, Any]: + body = body or {} + result = result or {} + guess = result.get("guess") or {} + verified = result.get("verified_media_info") or {} + verified_type = self._normalize_media_type(verified.get("type")) + target = { + "name": str(body.get("desired_name") or verified.get("title") or guess.get("name") or "").strip(), + "year": str(body.get("desired_year") or verified.get("year") or guess.get("year") or "").strip(), + "media_type": self._normalize_media_type( + body.get("desired_media_type") or verified_type or guess.get("media_type") + ), + "season": self._safe_int( + body.get("desired_season"), + self._safe_int(guess.get("season"), 0), + ), + "episode": self._safe_int( + body.get("desired_episode"), + self._safe_int(guess.get("episode"), 0), + ), + "tmdb_id": self._safe_int(body.get("desired_tmdb_id") or verified.get("tmdb_id"), 0), + } + if len(target["year"]) != 4 or not target["year"].isdigit(): + target["year"] = "" + return target + + @staticmethod + def _compact_verified_summary(verified: Optional[Dict[str, Any]]) -> Dict[str, Any]: + verified = verified or {} + return { + "title": verified.get("title"), + "year": verified.get("year"), + "type": verified.get("type"), + "tmdb_id": verified.get("tmdb_id"), + "title_year": verified.get("title_year"), + "season_years": verified.get("season_years"), + "seasons": verified.get("seasons"), + "names": (verified.get("names") or [])[:8], + } + + @staticmethod + def _normalize_identifier_line(value: Any) -> str: + return " ".join(str(value or "").strip().split()) + + def _validate_identifier_rule(self, rule: str) -> bool: + rule = self._normalize_identifier_line(rule) + if not rule or rule.startswith("#"): + return False + if " => " in rule and " && " in rule and " >> " in rule and " <> " in rule: + return True + if " => " in rule: + return True + if " >> " in rule and " <> " in rule: + return True + return len(rule) >= 4 + + def _enrich_identifier_rule(self, rule: str, target: Dict[str, Any]) -> str: + rule = self._normalize_identifier_line(rule) + target_name = str((target or {}).get("name") or "").strip() + if not target_name or " => " not in rule: + return rule + left, right = rule.split(" => ", 1) + suffix = "" + replace_part = right + if " && " in right: + replace_part, extra = right.split(" && ", 1) + suffix = f" && {extra}" + if replace_part.startswith("{["): + replace_part = f"{target_name}{replace_part}" + return f"{left} => {replace_part}{suffix}" + + @staticmethod + def _clean_comment_line(comment: str) -> str: + text = str(comment or "").strip() + if not text: + return "" + return f"#{text.lstrip('#').strip()}" + + def _preview_custom_words(self, title: str, custom_words: List[str], target: Dict[str, Any]) -> Dict[str, Any]: + prepared_title, apply_words = WordsMatcher().prepare(title, custom_words=custom_words) + meta = MetaInfo(title=title, custom_words=custom_words) + preview = { + "prepared_title": prepared_title, + "applied_words": apply_words or [], + "applied": bool(apply_words), + "name": getattr(meta, "name", "") or "", + "year": getattr(meta, "year", "") or "", + "media_type": self._normalize_media_type(getattr(meta, "type", None)), + "season": getattr(meta, "begin_season", None) or 0, + "episode": getattr(meta, "begin_episode", None) or 0, + } + if target: + matched = True + if target.get("name"): + matched = matched and (preview["name"].strip().lower() == str(target["name"]).strip().lower()) + if target.get("year"): + matched = matched and (preview["year"] == target["year"]) + if target.get("media_type") and target.get("media_type") != "unknown": + matched = matched and (preview["media_type"] == target["media_type"]) + if target.get("season"): + matched = matched and (preview["season"] == target["season"]) + if target.get("episode"): + matched = matched and (preview["episode"] == target["episode"]) + preview["matched_target"] = matched + return preview + + def _preview_identifier_rule(self, title: str, rule: str, target: Dict[str, Any]) -> Dict[str, Any]: + preview = self._preview_custom_words(title=title, custom_words=[rule], target=target) + preview["applied"] = rule in (preview.get("applied_words") or []) + return preview + + def _preview_current_identifiers(self, title: str, target: Dict[str, Any]) -> Dict[str, Any]: + custom_words = self._get_custom_identifiers() + preview = self._preview_custom_words(title=title, custom_words=custom_words, target=target) + preview["custom_identifier_count"] = len(custom_words) + preview["applied_count"] = len(preview.get("applied_words") or []) + return preview + + @staticmethod + def _match_recognize_result_to_target(result: Dict[str, Any], target: Dict[str, Any]) -> bool: + if not target: + return bool(result.get("success")) + guess = result.get("guess") or {} + matched = True + if target.get("name"): + matched = matched and (str(guess.get("name") or "").strip().lower() == str(target.get("name") or "").strip().lower()) + if target.get("year"): + matched = matched and (str(guess.get("year") or "") == str(target.get("year") or "")) + if target.get("media_type") and target.get("media_type") != "unknown": + matched = matched and (str(guess.get("media_type") or "unknown") == str(target.get("media_type") or "unknown")) + if target.get("season"): + matched = matched and (int(guess.get("season") or 0) == int(target.get("season") or 0)) + if target.get("episode"): + matched = matched and (int(guess.get("episode") or 0) == int(target.get("episode") or 0)) + return bool(result.get("success")) and matched + + def _replay_failed_sample(self, body: Dict[str, Any]) -> Dict[str, Any]: + body = dict(body or {}) + sample_index, sample, message = self._resolve_failed_sample( + body.get("sample_index"), + limit=1000, + ) + if not sample: + return {"success": False, "message": message} + title = str(sample.get("title") or "").strip() + path = str(sample.get("path") or "").strip() + target = self._target_from_sample(sample) + identifier_preview = self._preview_current_identifiers(title=title, target=target) + recognize_result = self._recognize(title=title, path=path, record_failed_sample=False) + resolved_by_identifiers = bool(identifier_preview.get("applied")) and bool(identifier_preview.get("matched_target")) + resolved_by_recognizer = self._match_recognize_result_to_target(recognize_result, target) + resolved = resolved_by_identifiers or resolved_by_recognizer + removal_result = None + if resolved and bool(body.get("remove_if_resolved")): + removal_result = self._remove_failed_sample(sample_index, limit=1000) + return { + "success": True, + "message": "success", + "data": { + "source_sample_index": sample_index, + "source_sample": sample, + "target": target, + "identifier_preview": identifier_preview, + "recognize_result": recognize_result, + "resolved_by_identifiers": resolved_by_identifiers, + "resolved_by_recognizer": resolved_by_recognizer, + "resolved": resolved, + "sample_removed": bool(removal_result and removal_result.get("removed")), + "sample_removal_result": removal_result, + }, + } + + def _replay_failed_samples(self, body: Dict[str, Any]) -> Dict[str, Any]: + body = dict(body or {}) + limit = max(1, min(self._safe_int(body.get("limit"), 10), 50)) + selected_indexes, _, message = self._select_failed_sample_indexes( + sample_indexes=body.get("sample_indexes"), + limit=limit, + pool_limit=200, + ) + if not selected_indexes: + return {"success": False, "message": message} + + replay_results: List[Dict[str, Any]] = [] + resolved_indexes: List[int] = [] + for sample_index in selected_indexes: + replay = self._replay_failed_sample( + { + "sample_index": sample_index, + "remove_if_resolved": False, + } + ) + if not replay.get("success"): + replay_results.append( + { + "sample_index": sample_index, + "success": False, + "message": replay.get("message", "复查失败"), + } + ) + continue + data = replay.get("data") or {} + replay_results.append( + { + "sample_index": sample_index, + "success": True, + "resolved": bool(data.get("resolved")), + "resolved_by_identifiers": bool(data.get("resolved_by_identifiers")), + "resolved_by_recognizer": bool(data.get("resolved_by_recognizer")), + "source_sample": data.get("source_sample"), + "target": data.get("target"), + "identifier_preview": data.get("identifier_preview"), + "recognize_result": data.get("recognize_result"), + } + ) + if data.get("resolved"): + resolved_indexes.append(sample_index) + + removal_result = None + if body.get("remove_if_resolved") and resolved_indexes: + removal_result = self._remove_failed_samples(resolved_indexes, limit=1000) + + success_count = sum(1 for item in replay_results if item.get("success")) + resolved_count = sum(1 for item in replay_results if item.get("resolved")) + unresolved_count = success_count - resolved_count + failed_count = len(replay_results) - success_count + return { + "success": True, + "message": "success", + "data": { + "requested_count": len(selected_indexes), + "success_count": success_count, + "resolved_count": resolved_count, + "unresolved_count": unresolved_count, + "failed_count": failed_count, + "sample_removed_count": int((removal_result or {}).get("removed_count") or 0), + "sample_removal_result": removal_result, + "results": replay_results, + }, + } + + def _suggest_identifiers_for_failed_samples(self, body: Dict[str, Any]) -> Dict[str, Any]: + body = dict(body or {}) + limit = max(1, min(self._safe_int(body.get("limit"), 5), 20)) + selected_indexes, _, message = self._select_failed_sample_indexes( + sample_indexes=body.get("sample_indexes"), + limit=limit, + pool_limit=200, + ) + if not selected_indexes: + return {"success": False, "message": message} + + results: List[Dict[str, Any]] = [] + success_count = 0 + for sample_index in selected_indexes: + suggest_body = dict(body) + suggest_body.pop("sample_indexes", None) + suggest_body["sample_index"] = sample_index + suggest_body["use_latest_sample"] = False + suggested = self._suggest_identifiers(suggest_body) + if suggested.get("success"): + success_count += 1 + data = suggested.get("data") or {} + results.append( + { + "sample_index": sample_index, + "success": True, + "summary": data.get("summary"), + "source_sample": data.get("source_sample"), + "target": data.get("target"), + "suggestions": data.get("suggestions") or [], + } + ) + else: + results.append( + { + "sample_index": sample_index, + "success": False, + "message": suggested.get("message", "建议生成失败"), + "data": suggested.get("data"), + } + ) + return { + "success": True, + "message": "success", + "data": { + "requested_count": len(selected_indexes), + "success_count": success_count, + "failed_count": len(selected_indexes) - success_count, + "brief": self._render_batch_results_brief( + action_name="批量建议", + requested_count=len(selected_indexes), + success_count=success_count, + failed_count=len(selected_indexes) - success_count, + results=results, + ), + "results": results, + }, + } + + def _apply_suggested_identifier_internal(self, body: Dict[str, Any]) -> Dict[str, Any]: + body = dict(body or {}) + if body.get("title") is None and body.get("path") is None: + body["use_latest_sample"] = True if body.get("use_latest_sample") is None else body.get("use_latest_sample") + suggested = self._suggest_identifiers(body) + if not suggested.get("success"): + return suggested + data = suggested.get("data") or {} + suggestions = data.get("suggestions") or [] + suggestion_index = self._safe_int(body.get("suggestion_index"), 0) + if suggestion_index < 0: + suggestion_index = 0 + if suggestion_index >= len(suggestions): + return {"success": False, "message": f"建议索引超出范围,当前共有 {len(suggestions)} 条"} + chosen = suggestions[suggestion_index] + applied = self._append_custom_identifiers(chosen.get("lines") or []) + should_remove_sample = bool( + self._auto_remove_applied_sample if body.get("remove_sample") is None else body.get("remove_sample") + ) + removal_result = None + source_sample = data.get("source_sample") or {} + if should_remove_sample and source_sample.get("sample_index") is not None: + removal_result = self._remove_failed_sample(source_sample.get("sample_index"), limit=1000) + return { + "success": True, + "message": "success", + "data": { + "chosen_suggestion": chosen, + "apply_result": applied, + "source_sample_index": source_sample.get("sample_index"), + "source_sample": source_sample, + "sample_removed": bool(removal_result and removal_result.get("removed")), + "sample_removal_result": removal_result, + "target": data.get("target"), + }, + } + + def _apply_suggested_identifiers_for_failed_samples(self, body: Dict[str, Any]) -> Dict[str, Any]: + body = dict(body or {}) + limit = max(1, min(self._safe_int(body.get("limit"), 5), 20)) + selected_indexes, _, message = self._select_failed_sample_indexes( + sample_indexes=body.get("sample_indexes"), + limit=limit, + pool_limit=200, + ) + if not selected_indexes: + return {"success": False, "message": message} + + results: List[Dict[str, Any]] = [] + success_count = 0 + removable_indexes: List[int] = [] + should_remove_samples = bool( + self._auto_remove_applied_sample if body.get("remove_sample") is None else body.get("remove_sample") + ) + for sample_index in selected_indexes: + apply_body = dict(body) + apply_body.pop("sample_indexes", None) + apply_body["sample_index"] = sample_index + apply_body["use_latest_sample"] = False + apply_body["remove_sample"] = False + applied = self._apply_suggested_identifier_internal(apply_body) + if applied.get("success"): + success_count += 1 + data = applied.get("data") or {} + if should_remove_samples: + removable_indexes.append(sample_index) + results.append( + { + "sample_index": sample_index, + "success": True, + "source_sample": data.get("source_sample"), + "target": data.get("target"), + "chosen_suggestion": data.get("chosen_suggestion"), + "apply_result": data.get("apply_result"), + "sample_removed": False, + } + ) + else: + results.append( + { + "sample_index": sample_index, + "success": False, + "message": applied.get("message", "写入失败"), + "data": applied.get("data"), + } + ) + removal_result = None + if should_remove_samples and removable_indexes: + removal_result = self._remove_failed_samples(removable_indexes, limit=1000) + removed_index_set = set((removal_result or {}).get("removed_sample_indexes") or []) + for item in results: + if item.get("success"): + item["sample_removed"] = item.get("sample_index") in removed_index_set + return { + "success": True, + "message": "success", + "data": { + "requested_count": len(selected_indexes), + "success_count": success_count, + "failed_count": len(selected_indexes) - success_count, + "sample_removed_count": int((removal_result or {}).get("removed_count") or 0), + "sample_removal_result": removal_result, + "brief": self._render_batch_results_brief( + action_name="批量写入", + requested_count=len(selected_indexes), + success_count=success_count, + failed_count=len(selected_indexes) - success_count, + results=results, + ), + "results": results, + }, + } + + def _build_exact_identifier_fallback(self, title: str, target: Dict[str, Any]) -> Optional[Dict[str, Any]]: + target_name = str((target or {}).get("name") or "").strip() + tmdb_id = self._safe_int((target or {}).get("tmdb_id"), 0) + media_type = self._normalize_media_type((target or {}).get("media_type")) + if not title or not target_name or not tmdb_id or media_type == "unknown": + return None + replace = target_name + target_year = str((target or {}).get("year") or "").strip() + if len(target_year) == 4 and target_year.isdigit(): + replace += f".{target_year}" + replace += f"{{[tmdbid={tmdb_id};type={'tv' if media_type == 'tv' else 'movie'}" + if media_type == "tv" and self._safe_int(target.get("season"), 0): + replace += f";s={self._safe_int(target.get('season'), 0)}" + if media_type == "tv" and self._safe_int(target.get("episode"), 0): + replace += f";e={self._safe_int(target.get('episode'), 0)}" + replace += "]}" + rule = f"{re.escape(title)} => {replace}" + preview = self._preview_identifier_rule(title=title, rule=rule, target=target) + if not preview.get("applied"): + return None + return { + "comment": "当 AI 建议无法稳定通过本地预演时,使用精确标题绑定规则直接固定到目标 TMDB 与季集", + "comment_line": "#当 AI 建议无法稳定通过本地预演时,使用精确标题绑定规则直接固定到目标 TMDB 与季集", + "rule": rule, + "confidence": 0.95, + "reason": "精确匹配当前标题并强制绑定目标 TMDB / 季集,作用域最窄,稳定性最高。", + "preview": preview, + "lines": [ + "#当 AI 建议无法稳定通过本地预演时,使用精确标题绑定规则直接固定到目标 TMDB 与季集", + rule, + ], + } + + def _invoke_identifier_llm( + self, + title: str, + path: str, + result: Dict[str, Any], + target: Dict[str, Any], + ) -> IdentifierSuggestionBundle: + llm = self._get_llm() + prompt = self._build_identifier_prompt() + chain = ( + prompt + | llm.with_structured_output(IdentifierSuggestionBundle).with_retry( + stop_after_attempt=self._max_retries + ) + ) + bundle: IdentifierSuggestionBundle = chain.invoke( + { + "title": title, + "path": path, + "meta_hint": self._build_meta_hint(path or title), + "guess": result.get("guess") or {}, + "verified_summary": self._compact_verified_summary(result.get("verified_media_info")), + "target": target, + }, + config={"configurable": {"timeout": self._request_timeout}}, + ) + return bundle + + def _suggest_identifiers(self, body: Dict[str, Any]) -> Dict[str, Any]: + body, source_sample, sample_message = self._build_body_from_sample(body) + if sample_message: + return {"success": False, "message": sample_message} + title = str(body.get("title") or "").strip() + path = str(body.get("path") or "").strip() + if not title and path: + title = Path(path).name + if not title: + return {"success": False, "message": "标题为空"} + + result = self._recognize(title=title, path=path, record_failed_sample=False) + target = self._build_target(body, result=result) + invoke_error = "" + try: + bundle = self._invoke_identifier_llm(title=title, path=path, result=result, target=target) + except Exception as exc: + bundle = IdentifierSuggestionBundle( + summary="识别词建议模型暂不可用,已自动回退到精确规则兜底。", + suggestions=[], + ) + invoke_error = str(exc) + + cleaned: List[Dict[str, Any]] = [] + for item in bundle.suggestions: + rule = self._enrich_identifier_rule(item.rule, target=target) + if not self._validate_identifier_rule(rule): + continue + comment_line = self._clean_comment_line(item.comment) + preview = self._preview_identifier_rule(title=title, rule=rule, target=target) + if not preview.get("applied"): + continue + if target and any(target.values()) and preview.get("matched_target") is False: + continue + cleaned.append( + { + "comment": item.comment.strip(), + "comment_line": comment_line, + "rule": rule, + "confidence": min(1.0, max(0.0, self._safe_float(item.confidence, 0.0))), + "reason": str(item.reason or "").strip(), + "preview": preview, + "lines": [line for line in [comment_line, rule] if line], + } + ) + + if not cleaned: + fallback = self._build_exact_identifier_fallback(title=title, target=target) + if fallback: + if invoke_error: + fallback["reason"] = f"{fallback.get('reason', '')} 当前识别词建议模型不可用,已自动切到精确规则兜底。".strip() + cleaned.append(fallback) + + if not cleaned: + return { + "success": False, + "message": f"识别词建议生成失败: {invoke_error}" if invoke_error else "没有生成可直接使用的识别词规则", + "data": { + "summary": bundle.summary, + "target": target, + "recognize_result": result, + }, + } + return { + "success": True, + "message": "success", + "data": { + "summary": bundle.summary, + "source_sample_index": (source_sample or {}).get("sample_index"), + "source_sample": source_sample, + "target": target, + "recognize_result": result, + "suggestions": cleaned, + }, + } + + def _get_custom_identifiers(self) -> List[str]: + if not self._systemconfig: + self._systemconfig = SystemConfigOper() + return self._systemconfig.get(SystemConfigKey.CustomIdentifiers) or [] + + def _append_custom_identifiers(self, lines: List[str]) -> Dict[str, Any]: + existing = self._get_custom_identifiers() + added: List[str] = [] + for line in lines: + normalized = str(line or "").rstrip() + if not normalized: + continue + if normalized in existing or normalized in added: + continue + added.append(normalized) + if added: + merged = existing + added + self._systemconfig.set(SystemConfigKey.CustomIdentifiers, merged) + return { + "added": added, + "added_count": len(added), + "total_count": len(self._get_custom_identifiers()), + } + + def _verify_guess(self, title: str, path: str, guess: AIRecognitionGuess) -> Optional[Dict[str, Any]]: + if not guess.name: + return None + try: + raw_text = path or title or guess.name + meta = MetaInfo(raw_text) + meta.name = guess.name + meta.year = guess.year or None + meta.begin_season = guess.season or None + meta.begin_episode = guess.episode or None + if guess.media_type == "tv" or meta.begin_season or meta.begin_episode: + meta.type = MediaType.TV + elif guess.media_type == "movie": + meta.type = MediaType.MOVIE + mediainfo = MediaChain().recognize_media(meta=meta, cache=False) + if not mediainfo: + return None + return mediainfo.to_dict() + except Exception as exc: + if self._debug: + logger.warning(f"[AI识别增强] 二次校验失败: {exc}") + return None + + def _recognize(self, title: str, path: str = "", record_failed_sample: bool = True) -> Dict[str, Any]: + title = str(title or "").strip() + path = str(path or "").strip() + if not title and path: + title = Path(path).name + if not title: + return {"success": False, "message": "标题为空"} + try: + guess = self._invoke_llm(title, path) + except Exception as exc: + if record_failed_sample: + self._record_failed_sample( + { + "title": title, + "path": path, + "meta_hint": self._build_meta_hint(path or title), + "reason": f"llm_error:{exc}", + } + ) + return {"success": False, "message": f"LLM 调用失败: {exc}"} + + verified = self._verify_guess(title, path, guess) + passed = bool(guess.name and guess.confidence >= self._confidence_threshold) + if not passed and record_failed_sample: + self._record_failed_sample( + { + "title": title, + "path": path, + "meta_hint": self._build_meta_hint(path or title), + "guess": guess.model_dump(), + "verified_media_info": self._compact_verified_summary(verified), + "reason": "low_confidence_or_empty_name", + } + ) + return { + "success": passed, + "message": "success" if passed else "识别结果置信度不足,已放弃注入", + "guess": guess.model_dump(), + "verified_media_info": verified, + } + + def on_chain_name_recognize(self, event) -> None: + if not self._enabled: + return + event_data = getattr(event, "event_data", None) or {} + title, path = self._extract_title_path(event_data) + if not title and not path: + return + result = self._recognize(title=title, path=path) + if not result.get("success"): + if self._debug: + logger.info(f"[AI识别增强] 跳过注入: {title or path} - {result.get('message')}") + return + guess = result.get("guess") or {} + if isinstance(event_data, dict): + event_data["name"] = guess.get("name", "") + event_data["year"] = guess.get("year", "") + event_data["season"] = guess.get("season", 0) + event_data["episode"] = guess.get("episode", 0) + event_data["source_plugin"] = "AIRecognizerEnhancer" + event_data["confidence"] = guess.get("confidence", 0) + event_data["reason"] = guess.get("reason", "") + + async def api_health(self, request: Request): + ok, message = self._check_api_access(request) + if not ok: + return {"success": False, "message": message} + llm_ready = bool(getattr(settings, "LLM_API_KEY", None)) + return { + "success": True, + "data": { + "plugin_version": self.plugin_version, + "enabled": self._enabled, + "llm_ready": llm_ready, + "llm_provider": getattr(settings, "LLM_PROVIDER", ""), + "llm_model": getattr(settings, "LLM_MODEL", ""), + "confidence_threshold": self._confidence_threshold, + "request_timeout": self._request_timeout, + }, + } + + async def api_recognize(self, request: Request): + body = await request.json() + ok, message = self._check_api_access(request, body) + if not ok: + return {"success": False, "message": message} + if not self._enabled: + return {"success": False, "message": "插件未启用"} + title = str(body.get("title") or "").strip() + path = str(body.get("path") or "").strip() + result = self._recognize(title=title, path=path) + return { + "success": result.get("success", False), + "message": result.get("message", ""), + "data": { + "guess": result.get("guess"), + "verified_media_info": result.get("verified_media_info"), + }, + } + + async def api_failed_samples(self, request: Request): + ok, message = self._check_api_access(request) + if not ok: + return {"success": False, "message": message} + limit = self._safe_int(request.query_params.get("limit"), 20) + limit = max(1, min(limit, 100)) + samples = self._inject_sample_indices(self._read_failed_samples(limit=limit)) + return { + "success": True, + "data": { + "count": len(samples), + "samples": samples, + }, + } + + async def api_sample_worklist(self, request: Request): + ok, message = self._check_api_access(request) + if not ok: + return {"success": False, "message": message} + limit = self._safe_int(request.query_params.get("limit"), 20) + limit = max(1, min(limit, 100)) + samples = self._inject_sample_indices(self._read_failed_samples(limit=limit)) + worklist = [self._summarize_sample(sample) for sample in samples] + return { + "success": True, + "data": { + "count": len(worklist), + "samples": worklist, + }, + } + + async def api_sample_insights(self, request: Request): + ok, message = self._check_api_access(request) + if not ok: + return {"success": False, "message": message} + limit = self._safe_int(request.query_params.get("limit"), 50) + limit = max(1, min(limit, 200)) + top = self._safe_int(request.query_params.get("top"), 10) + top = max(1, min(top, 20)) + samples = self._inject_sample_indices(self._read_failed_samples(limit=limit)) + insights = self._build_sample_insights(samples, top=top) + return { + "success": True, + "data": insights, + } + + async def api_sample_brief(self, request: Request): + ok, message = self._check_api_access(request) + if not ok: + return {"success": False, "message": message} + limit = self._safe_int(request.query_params.get("limit"), 5) + limit = max(1, min(limit, 20)) + samples = self._inject_sample_indices(self._read_failed_samples(limit=100)) + return { + "success": True, + "data": { + "count": len(samples), + "text": self._render_sample_brief(samples, top=limit), + }, + } + + async def api_suggest_identifiers(self, request: Request): + body = await request.json() + ok, message = self._check_api_access(request, body) + if not ok: + return {"success": False, "message": message} + if not self._enabled: + return {"success": False, "message": "插件未启用"} + return self._suggest_identifiers(body) + + async def api_apply_identifiers(self, request: Request): + body = await request.json() + ok, message = self._check_api_access(request, body) + if not ok: + return {"success": False, "message": message} + identifiers = body.get("identifiers") or [] + if not isinstance(identifiers, list): + return {"success": False, "message": "identifiers 必须是数组"} + result = self._append_custom_identifiers([str(line or "") for line in identifiers]) + return { + "success": True, + "message": "success", + "data": result, + } + + async def api_clear_failed_samples(self, request: Request): + ok, message = self._check_api_access(request) + if not ok: + return {"success": False, "message": message} + cleared = self._clear_failed_samples() + return { + "success": True, + "message": "success", + "data": { + "cleared_count": cleared, + }, + } + + async def api_remove_failed_sample(self, request: Request): + body = await request.json() + ok, message = self._check_api_access(request, body) + if not ok: + return {"success": False, "message": message} + result = self._remove_failed_sample(body.get("sample_index"), limit=1000) + if not result.get("removed"): + return {"success": False, "message": result.get("message", "移除失败"), "data": result} + return { + "success": True, + "message": "success", + "data": result, + } + + async def api_replay_failed_sample(self, request: Request): + body = await request.json() + ok, message = self._check_api_access(request, body) + if not ok: + return {"success": False, "message": message} + if not self._enabled: + return {"success": False, "message": "插件未启用"} + return self._replay_failed_sample(body) + + async def api_replay_failed_samples(self, request: Request): + body = await request.json() + ok, message = self._check_api_access(request, body) + if not ok: + return {"success": False, "message": message} + if not self._enabled: + return {"success": False, "message": "插件未启用"} + return self._replay_failed_samples(body) + + async def api_suggest_identifiers_from_sample(self, request: Request): + body = await request.json() + body["use_latest_sample"] = True if body.get("use_latest_sample") is None else body.get("use_latest_sample") + ok, message = self._check_api_access(request, body) + if not ok: + return {"success": False, "message": message} + if not self._enabled: + return {"success": False, "message": "插件未启用"} + if body.get("sample_index") is None and body.get("use_latest_sample") is False: + body["use_latest_sample"] = True + return self._suggest_identifiers(body) + + async def api_suggest_identifiers_for_failed_samples(self, request: Request): + body = await request.json() + ok, message = self._check_api_access(request, body) + if not ok: + return {"success": False, "message": message} + if not self._enabled: + return {"success": False, "message": "插件未启用"} + return self._suggest_identifiers_for_failed_samples(body) + + async def api_apply_suggested_identifier(self, request: Request): + body = await request.json() + ok, message = self._check_api_access(request, body) + if not ok: + return {"success": False, "message": message} + if not self._enabled: + return {"success": False, "message": "插件未启用"} + return self._apply_suggested_identifier_internal(body) + + async def api_apply_suggested_identifiers_for_failed_samples(self, request: Request): + body = await request.json() + ok, message = self._check_api_access(request, body) + if not ok: + return {"success": False, "message": message} + if not self._enabled: + return {"success": False, "message": "插件未启用"} + return self._apply_suggested_identifiers_for_failed_samples(body) + + def get_api(self) -> List[Dict[str, Any]]: + return [ + { + "path": "/health", + "endpoint": self.api_health, + "methods": ["GET"], + "summary": "检查 AI识别增强 的运行状态", + }, + { + "path": "/recognize", + "endpoint": self.api_recognize, + "methods": ["POST"], + "summary": "用当前 LLM 对失败标题做一次本地结构化识别测试", + }, + { + "path": "/failed_samples", + "endpoint": self.api_failed_samples, + "methods": ["GET"], + "summary": "查看最近保存的低置信度失败样本", + }, + { + "path": "/sample_worklist", + "endpoint": self.api_sample_worklist, + "methods": ["GET"], + "summary": "返回适合智能体使用的失败样本摘要列表", + }, + { + "path": "/sample_insights", + "endpoint": self.api_sample_insights, + "methods": ["GET"], + "summary": "汇总失败样本原因、重复问题和优先处理样本", + }, + { + "path": "/sample_brief", + "endpoint": self.api_sample_brief, + "methods": ["GET"], + "summary": "返回适合智能体低 token 消费的失败样本精简摘要", + }, + { + "path": "/suggest_identifiers", + "endpoint": self.api_suggest_identifiers, + "methods": ["POST"], + "summary": "根据标题和目标结果生成 MoviePilot 自定义识别词建议", + }, + { + "path": "/suggest_identifiers_from_sample", + "endpoint": self.api_suggest_identifiers_from_sample, + "methods": ["POST"], + "summary": "直接基于最近失败样本或指定样本生成自定义识别词建议", + }, + { + "path": "/suggest_identifiers_for_failed_samples", + "endpoint": self.api_suggest_identifiers_for_failed_samples, + "methods": ["POST"], + "summary": "批量为失败样本生成自定义识别词建议", + }, + { + "path": "/apply_identifiers", + "endpoint": self.api_apply_identifiers, + "methods": ["POST"], + "summary": "将确认后的自定义识别词追加写入系统 CustomIdentifiers", + }, + { + "path": "/clear_failed_samples", + "endpoint": self.api_clear_failed_samples, + "methods": ["POST"], + "summary": "清空失败样本文件", + }, + { + "path": "/remove_failed_sample", + "endpoint": self.api_remove_failed_sample, + "methods": ["POST"], + "summary": "按索引移除单条失败样本", + }, + { + "path": "/replay_failed_sample", + "endpoint": self.api_replay_failed_sample, + "methods": ["POST"], + "summary": "按当前识别词和当前识别器复查某条失败样本,并可在确认修复后自动出队", + }, + { + "path": "/replay_failed_samples", + "endpoint": self.api_replay_failed_samples, + "methods": ["POST"], + "summary": "批量复查失败样本,并可在确认修复后批量出队", + }, + { + "path": "/apply_suggested_identifier", + "endpoint": self.api_apply_suggested_identifier, + "methods": ["POST"], + "summary": "直接把最近失败样本或指定样本生成的建议规则写入 CustomIdentifiers,并按需移除该样本", + }, + { + "path": "/apply_suggested_identifiers_for_failed_samples", + "endpoint": self.api_apply_suggested_identifiers_for_failed_samples, + "methods": ["POST"], + "summary": "批量把失败样本生成的建议规则写入 CustomIdentifiers,并按需移除对应样本", + }, + ] + + def get_page(self) -> List[dict]: + llm_ready = bool(getattr(settings, "LLM_API_KEY", None)) + failed_samples_count = len(self._read_failed_samples(limit=200)) + custom_identifiers_count = len(self._get_custom_identifiers()) + llm_provider = getattr(settings, "LLM_PROVIDER", "—") + llm_model = getattr(settings, "LLM_MODEL", "—") + + def stat_card(title: str, value: Any, subtitle: str = "") -> dict: + content = [ + { + "component": "div", + "props": {"class": "text-caption text-medium-emphasis mb-1"}, + "text": title, + }, + { + "component": "div", + "props": {"class": "text-h6 font-weight-bold"}, + "text": str(value), + }, + ] + if subtitle: + content.append( + { + "component": "div", + "props": {"class": "text-caption text-medium-emphasis mt-1"}, + "text": subtitle, + } + ) + return { + "component": "VCard", + "props": {"variant": "tonal", "class": "pa-4 h-100"}, + "content": content, + } + + return [ + { + "component": "VContainer", + "props": {"fluid": True, "class": "pa-0"}, + "content": [ + { + "component": "VAlert", + "props": { + "type": "info", + "variant": "tonal", + "class": "mb-4", + "title": "本地 LLM 识别兜底", + "text": "复用 MoviePilot 当前 LLM 配置,在原生识别失败时做结构化兜底,并把结果交回 MoviePilot 继续二次识别。", + }, + }, + { + "component": "VRow", + "props": {"dense": True, "class": "mb-2"}, + "content": [ + { + "component": "VCol", + "props": {"cols": 12, "md": 3}, + "content": [stat_card("当前状态", "已启用" if self._enabled else "未启用")], + }, + { + "component": "VCol", + "props": {"cols": 12, "md": 3}, + "content": [stat_card("LLM 可用", "是" if llm_ready else "否", f"{llm_provider} / {llm_model}")], + }, + { + "component": "VCol", + "props": {"cols": 12, "md": 3}, + "content": [stat_card("失败样本", f"{failed_samples_count} 条", f"上限 {self._max_failed_samples} 条")], + }, + { + "component": "VCol", + "props": {"cols": 12, "md": 3}, + "content": [stat_card("自定义识别词", f"{custom_identifiers_count} 条", "系统 CustomIdentifiers")], + }, + ], + }, + { + "component": "VRow", + "props": {"dense": True}, + "content": [ + { + "component": "VCol", + "props": {"cols": 12, "md": 6}, + "content": [ + { + "component": "VCard", + "props": {"variant": "outlined", "class": "pa-4 h-100"}, + "content": [ + { + "component": "div", + "props": {"class": "text-subtitle-1 font-weight-bold mb-2"}, + "text": "识别兜底", + }, + { + "component": "div", + "props": {"class": "text-body-2 text-medium-emphasis"}, + "text": "在 Chain NameRecognize 阶段回写 name / year / season / episode,供 MoviePilot 继续原生二次识别。", + }, + { + "component": "div", + "props": {"class": "text-caption text-medium-emphasis mt-3"}, + "text": f"置信度阈值:{self._confidence_threshold};请求超时:{self._request_timeout} 秒", + }, + ], + } + ], + }, + { + "component": "VCol", + "props": {"cols": 12, "md": 6}, + "content": [ + { + "component": "VCard", + "props": {"variant": "outlined", "class": "pa-4 h-100"}, + "content": [ + { + "component": "div", + "props": {"class": "text-subtitle-1 font-weight-bold mb-2"}, + "text": "识别词闭环", + }, + { + "component": "div", + "props": {"class": "text-body-2 text-medium-emphasis"}, + "text": "失败样本可生成 CustomIdentifiers 建议,并按需追加写入系统配置。", + }, + { + "component": "div", + "props": {"class": "text-caption text-medium-emphasis mt-3"}, + "text": f"写入后自动移除样本:{'是' if self._auto_remove_applied_sample else '否'}", + }, + ], + } + ], + }, + ], + }, + ], + } + ] + + @staticmethod + def get_render_mode() -> Tuple[str, Optional[str]]: + return "vuetify", None + + def get_form(self) -> Tuple[List[dict], Dict[str, Any]]: + form = [ + { + "component": "VForm", + "content": [ + { + "component": "VRow", + "content": [ + { + "component": "VCol", + "props": {"cols": 12}, + "content": [ + { + "component": "VAlert", + "props": { + "type": "info", + "variant": "tonal", + "text": "当前版本已改为直接复用 MoviePilot 当前启用的 LLM 配置,在原生识别失败后做本地结构化兜底。", + }, + } + ], + } + ], + }, + { + "component": "VRow", + "content": [ + { + "component": "VCol", + "props": {"cols": 12, "md": 4}, + "content": [ + { + "component": "VSwitch", + "props": {"model": "enabled", "label": "启用 AI识别增强"}, + } + ], + }, + { + "component": "VCol", + "props": {"cols": 12, "md": 4}, + "content": [ + { + "component": "VSwitch", + "props": {"model": "debug", "label": "调试模式"}, + } + ], + }, + { + "component": "VCol", + "props": {"cols": 12, "md": 4}, + "content": [ + { + "component": "VSwitch", + "props": {"model": "save_failed_samples", "label": "保存低置信度样本"}, + } + ], + }, + ], + }, + { + "component": "VRow", + "content": [ + { + "component": "VCol", + "props": {"cols": 12, "md": 6}, + "content": [ + { + "component": "VTextField", + "props": { + "model": "confidence_threshold", + "label": "置信度阈值", + "type": "number", + "hint": "低于该值的结果不注入 MoviePilot,默认 0.65", + "persistent-hint": True, + }, + } + ], + }, + { + "component": "VCol", + "props": {"cols": 12, "md": 6}, + "content": [ + { + "component": "VTextField", + "props": { + "model": "request_timeout", + "label": "LLM 请求超时(秒)", + "type": "number", + "hint": "默认 25 秒", + "persistent-hint": True, + }, + } + ], + }, + ], + }, + { + "component": "VRow", + "content": [ + { + "component": "VCol", + "props": {"cols": 12}, + "content": [ + { + "component": "VTextField", + "props": { + "model": "max_retries", + "label": "结构化输出重试次数", + "type": "number", + "hint": "默认 2 次", + "persistent-hint": True, + }, + } + ], + } + ], + }, + { + "component": "VRow", + "content": [ + { + "component": "VCol", + "props": {"cols": 12}, + "content": [ + { + "component": "VTextField", + "props": { + "model": "max_failed_samples", + "label": "失败样本保留上限", + "type": "number", + "hint": "默认保留最近 200 条,并对重复样本自动去重", + "persistent-hint": True, + }, + } + ], + } + ], + }, + { + "component": "VRow", + "content": [ + { + "component": "VCol", + "props": {"cols": 12}, + "content": [ + { + "component": "VSwitch", + "props": { + "model": "auto_remove_applied_sample", + "label": "写入识别词后自动移除对应失败样本", + }, + } + ], + } + ], + }, + ], + } + ] + return form, { + "enabled": False, + "debug": False, + "confidence_threshold": 0.65, + "request_timeout": 25, + "max_retries": 2, + "save_failed_samples": True, + "max_failed_samples": 200, + "auto_remove_applied_sample": True, + }