Files
UHD_Media_Tool/docs/porting/moviepilot-v3-comparison.md
T
rootandClaude Opus 5.5 ed083fd138 feat(subscriptions): 按 MP 收敛订阅默认值与表单
- 定时主动搜索默认关(DEFAULT_SEARCH_INTERVAL_HOURS = 0),新建 / 手动照常各搜一次,之后靠 RSS 与站点快照
- 多版本自动补全系统缺省改为关
- 表单:基础页 = 作品与范围、下载、洗版;收录版本、优先级、自动补全折叠到进阶页「多版本」
- 对照文档 §7.3 记录落实范围

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-02 22:34:48 +08:00

352 lines
55 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# MoviePilot v3 逐项业务对照
继续开发前,先逐项核对现有业务与 MoviePilot v3 的处理逻辑。每项同时说明业务结果、失败边界、本项目差异和需要补的证据;完成对照后再讨论实现调整。本文是当前对照记录,尚未完成的项目明确列为待核对。
日期:2026-10-01。本项目业务代码基线:`8013196e3f5e6a8c4ea0cd26cf77294a82a13ddd`,`v0.3.7`。前置材料:开发情况基线(已删除,见 git `ceb66f8`)、工程检查(已删除,见 git `ceb66f8`)。初始对照阶段只读源码并更新文档;后续用户授权的实现状态见第 4、5 节。
## 1 参考基线和判定方法
| 项目 | 本轮固定基线 |
| --- | --- |
| MoviePilot 仓库 | [jxxghp/MoviePilot](https://github.com/jxxghp/MoviePilot) |
| 参考发布版本 | `v3.0.10-1`,提交 [`0aa857173f77de31c7c8d9d2e12052d99f37bcc1`](https://github.com/jxxghp/MoviePilot/tree/0aa857173f77de31c7c8d9d2e12052d99f37bcc1),2026-09-28;`version.py` 已核对 |
| 分支查询 | 本轮只读查询时 `v3` 分支为 `5741d3c56d761d87868d4f2d0515ac5c498f94d6`;本报告采用上面的发布标签,后续逐项核对不随分支移动 |
| 本地参考位置 | `/tmp/umt-moviepilot-v3-review-20261001`,独立浅克隆,未改上游源码 |
| 旧资料 | [既有移植文档](moviepilot-transfer.md)使用 `e6820a8`/早期 v3.0.0;原参考仓库保持不变,旧样本结论不直接升级为本轮结论 |
| 证据范围 | 阅读当前源码、迁移和测试断言;没有启动 MP 服务或运行上游测试,也没有对本项目新增故障注入 |
MP 是优先采用的业务参考;用户已确认尽量复用其成熟业务逻辑和实现,存在明确需求差异时再讨论调整。最终行为仍由本项目需求和用户确认决定。两边使用不同任务和数据机制,只比较同一场景的业务结果,不因模块名字相近就认定能力相同。已有 MP 差分样本、运行记录和有意偏离登记继续保留,按各自提交解释。
每项记录五件事:触发场景;MP 的明确处理分支;本项目正式调用路径;差异属于实现缺口、验收缺口、有意调整还是暂不适用;拟采用的规则及最小验证场景。源码结论、未复现影响和讨论建议分开。参考行为与来源登记沿用 [移植规范](license-and-provenance.md)。
## 2 顺序和进度
按下面顺序逐项讨论。状态「源码对照完成」只表示这一项的控制流核对已完成,不表示故障验收或实现调整已经完成。
| 顺序 | 业务主题 | 核对重点/需求 | 本轮状态 |
| --- | --- | --- | --- |
| 01 | 任务持久化、恢复和升级兼容 | 接受请求、入队、回填、中断恢复、旧任务处置;工程检查 §2 | 源码对照完成,恢复原则已认可;局部恢复修正和 9 个故障测试已完成,见第 4 节 |
| 02 | 下载纳管与实例隔离 | 管理标签、接管、去标签、同 hash 多实例、暂停/删除范围;R14、R28 | 正在逐项讨论;下载入口与浏览器详情页交接已确认,见第 5 节;其余待核对 |
| 03 | 部分入库、缓存清理与磁盘水位 | 未完成文件、H&R、保留期、未纳管占用、同卷汇总;R15、R21 | 待核对 |
| 04 | 整理计划、替换、路径与记录 | 冻结目标、跨卷、字幕随行、替换旧版本、记录删除;R07、R15、R23 | 待核对;01 仅覆盖恢复机制 |
| 05 | 刮削与媒体文件布局 | NFO/图片、缺失与覆盖、部分失败、任意目录、自动触发;R10、R11 | 待核对;01 仅覆盖刮削任务入队 |
| 06 | 识别、身份与映射治理 | 本地身份、人工锁、详情 ID、跨季、元数据滞后、错误关联;R03、R08、R09、R22 | 待核对 |
| 07 | 订阅完成条件与多版本 | 追更、综艺正片/上下段、整季包、独立双版本;R02、R04、R05 | 场景对照完成,待讨论,见第 7 节 |
| 08 | 规则、选片、升级与换源 | 硬条件、优先级、偏好学习、替换计划、授权边界;R16、R20、R27 | 选片与洗版部分并入第 7 节;换源与授权边界待核对 |
| 09 | RSS、站点快照、搜索与详情 | 去重补漏、缓存新鲜度、限流、文件清单、四站证据;R01、R25、R30 | 待核对 |
| 10 | 浏览器辅助与人工接管 | HTTP/浏览器切换、挑战升级、同会话、交还与重启;R06、R31、R32 | 待核对 |
| 11 | 旧库接管、库存与媒体服务器 | 初次扫描、影响预览、分范围纳管、查询 unknown、资产对账;R09、R18、R24 | 待核对 |
| 12 | 智能体执行与授权 | 能力调用、审批、后台等待、预算、恢复与模型证据;R12、R17、R19、R26、R29 | 待核对 |
| 13 | API、公开记录与管理员入口 | 双入口行为、增量、鉴权、多实例配置;R13、R23、R28 | 待核对 |
| 14 | 验收、备份恢复与发布 | 必执行测试、页面样本、版本迁移、恢复演练、镜像来源;工程检查 §3 | 待核对;MP 中无等价要求的部分独立验收 |
## 3 第一项:任务持久化、恢复和升级兼容
### 3.1 业务场景与对照结论
| 场景 | MP v3.0.10-1 | 本项目 v0.3.7 | 差异判定与建议 |
| --- | --- | --- | --- |
| 请求已接受,执行队列暂时失败 | 整理入口先持久化 admission 并取得租约,再入进程内队列;入队失败保留持久记录和错误,交给恢复路径 | `enqueueTracked` 依次写业务任务、引擎入队、回填 run;`queueScrape` 也有三步,回填失败共用入队失败 catch | **恢复边界需要补强。** 已接受但未关联成功应保留可恢复事实;不能仅因回填失败就宣称外部入队失败。具体双写方案待故障验证后选择 |
| 查询恢复记录失败 | `__replay_pending` 的 claim 查询异常记录日志并返回,本轮不恢复;明确空结果另行返回 | `enqueueSweep` 的 `engine.get(...).catch(() => null)` 把查询异常与不存在合并,随后可按新时间槽补投 | **状态判定需要调整。** 查询失败应记录并跳过本轮;补投必须依据明确的不存在/可重投终态,并保留现有幂等与租约边界 |
| 升级后遇到旧任务/检查点 | 有版本化输入和检查点;当前和指定 legacy 检查点显式解码,未知版本拒绝;一次执行账本迁移将有旧执行迹象的任务隔离到 `manual_review` | 当前只注册 `scrapeMedia` v2,旧 v1 输入不同;0058 删除旧 `media_scrape` 表,worker 对旧版本没有对应认领处理器 | **升级处置缺口。** 不能无限承诺自动兼容,也不能让旧任务无声悬空。升级前列出在途版本,再决定兼容执行、迁移或带理由终止;无法确定副作用时保留证据并人工处理 |
| 已完成的外部步骤遇到进程中断 | 成功步骤直接返回持久结果;遗留 `started` 先观察,已发生则补成功,未发生才重做,未知/冲突进入人工复核 | OpenWorkflow 持久步骤、`tasks_op_ledger` fencing、`asset_operation`、冻结计划和文件恢复已经存在;E4 和 tasks 崩溃恢复测试覆盖同版本 worker 中断 | **保留现有机制,补指定故障证据。** 不能把入队关联缺口扩展成「没有崩溃恢复」。完整文件冲突和替换场景留在 04 逐项核对 |
| 业务结果提交与后续事件通知 | 整理历史、终态结算、receipt 和 outbox 由同一短事务提交,提交后再派发 | 整理核验中资产/路径、业务记录和 `organization.changed` 在业务事务内提交;outbox 中继再向引擎派发 | **方向一致。** 本项目已有 outbox,但刮削与通用任务入口尚未因此获得原子关联;还需核对中继的重投窗口和业务唯一约束 |
| 正式 handler 意外漏注册 | MP 的 `runtime/tasks.py` 管理进程内异步任务生命周期,与本项目的任务名+版本 handler 注册不是同一机制 | `buildTaskRegistry` 缺核心任务时注册 stub;现有测试主要断言名称。当前正式装配已覆盖最初 9 个核心任务 | **本项目独立维护风险。** 建议正式装配验证 handler 来源,缺失时明确失败;MP 的生命周期 TaskRegistry 不能证明这个问题已有等价解决 |
这里的 MP 行为限定在已读的整理任务链,不能推广成 MP 的全部刮削、下载或任意任务都遵守同一协议。尤其 MP 的「持久准入+内存队列」与本项目的「业务库+持久引擎库」不等价:可以借鉴恢复规则,不能照搬队列实现。MP claim 的未命中也可能表示没有取得租约,并非严格等价于本项目查询 run 得到「不存在」。
升级兼容也有明确范围:MP 的分类检查点 v1→v2 迁移遇到已有执行状态、执行载荷或步骤证据时不重写旧计划,继续使用已知旧版本解码路径。这支持「保留已经产生的执行证据」的规则,不能据此承诺任意旧 handler 都能继续执行。见 [检查点迁移](https://github.com/jxxghp/MoviePilot/blob/0aa857173f77de31c7c8d9d2e12052d99f37bcc1/database/versions/d8f2b6a4c1e7_3_0_28.py#L290)。
本项目刮削双写控制流已确认,但本轮没有故障注入证明实际受影响。上一轮线上只读检查未发现未结束的旧版刮削 run,也没有 `scrape_record` 记录;该时点结论不代替升级策略。见 工程检查 §2(已删除,见 git `ceb66f8`)。
### 3.2 精确来源
以下 MP 链接全部固定在 `0aa8571`,行号仅适用于该提交。上游测试名称说明已存在对应断言,**本轮没有运行这些测试**。
| 结论 | MP 源码与测试 | 本项目源码/已有验证 |
| --- | --- | --- |
| 先持久接受,再安排执行 | [workflow.py](https://github.com/jxxghp/MoviePilot/blob/0aa857173f77de31c7c8d9d2e12052d99f37bcc1/app/application/transfer/workflow.py#L116) `TransferQueueService.put`;[queue.py](https://github.com/jxxghp/MoviePilot/blob/0aa857173f77de31c7c8d9d2e12052d99f37bcc1/app/chain/transfer/queue.py#L1194) `__record_enqueue_failure`;[test_transfer_queue_service.py](https://github.com/jxxghp/MoviePilot/blob/0aa857173f77de31c7c8d9d2e12052d99f37bcc1/tests/test_transfer_queue_service.py#L139) `test_transfer_queue_service_commits_admission_before_failed_enqueue` 使用 SQLite 仓储注入队列失败 | [common.ts](../../apps/server/src/modules/acquisition-organize/services/common.ts) `enqueueTracked` 第 134 行;[scrape.ts](../../apps/server/src/modules/acquisition-organize/services/scrape.ts) `queueScrape` 第 152 行 |
| 查询异常不当作不存在 | [queue.py](https://github.com/jxxghp/MoviePilot/blob/0aa857173f77de31c7c8d9d2e12052d99f37bcc1/app/chain/transfer/queue.py#L754) `__replay_pending` 第 754 行;[admission.py](https://github.com/jxxghp/MoviePilot/blob/0aa857173f77de31c7c8d9d2e12052d99f37bcc1/app/db/adapters/transfer/admission.py#L296) `claim_recoverable`;[test_transfer_pending_replay.py](https://github.com/jxxghp/MoviePilot/blob/0aa857173f77de31c7c8d9d2e12052d99f37bcc1/tests/test_transfer_pending_replay.py#L423) `test_replay_survives_db_failure` 断言不执行恢复 | [common.ts](../../apps/server/src/modules/acquisition-organize/services/common.ts) `enqueueSweep` 第 159 行起 |
| 旧执行迹象保守隔离 | [models.py](https://github.com/jxxghp/MoviePilot/blob/0aa857173f77de31c7c8d9d2e12052d99f37bcc1/app/application/transfer/models.py#L485) `TransferPlanCheckpoint.from_payload`;[迁移](https://github.com/jxxghp/MoviePilot/blob/0aa857173f77de31c7c8d9d2e12052d99f37bcc1/database/versions/e5c7a9b1d3f6_3_0_16.py#L404) `_backfill_pending`;[test_transfer_execution_migration.py](https://github.com/jxxghp/MoviePilot/blob/0aa857173f77de31c7c8d9d2e12052d99f37bcc1/tests/test_transfer_execution_migration.py#L282) `test_upgrade_is_conservative_and_repairs_interrupted_indexes` | [handlers.ts](../../apps/server/src/modules/acquisition-organize/handlers.ts) 第 193 行;[0058](../../packages/db/migrations/0058_scrape_record.ts);[引擎](../../packages/tasks/src/openworkflow-engine.ts) `startWorker` 第 221 行 |
| 步骤恢复先看已有结果和实际副作用 | [execution.py](https://github.com/jxxghp/MoviePilot/blob/0aa857173f77de31c7c8d9d2e12052d99f37bcc1/app/chain/transfer/execution.py#L193) `_DurableTransferStepRunner.run`;[test_transfer_execution_runner.py](https://github.com/jxxghp/MoviePilot/blob/0aa857173f77de31c7c8d9d2e12052d99f37bcc1/tests/test_transfer_execution_runner.py#L148) 成功步骤不重做、未知孤立步骤进入人工复核断言 | [op-ledger.ts](../../packages/tasks/src/op-ledger.ts);[organize.ts](../../apps/server/src/modules/acquisition-organize/services/organize.ts);[E4](../../apps/server/test/e2e/crash-recovery.test.ts)、[tasks 崩溃恢复](../../packages/tasks/test/crash-recovery.test.ts) |
| 结果和事件共同提交 | [chain.py](https://github.com/jxxghp/MoviePilot/blob/0aa857173f77de31c7c8d9d2e12052d99f37bcc1/app/db/adapters/chain.py#L219) `TransactionalChainDurableEventWriter.transfer_result` | [organize.ts](../../apps/server/src/modules/acquisition-organize/services/organize.ts) 核验事务第 1386 行、`emitRecord` 第 1710 行;[outbox-relay.ts](../../packages/tasks/src/outbox-relay.ts) 第 46 行起 |
| handler 装配风险不可强行类比 | [runtime/tasks.py](https://github.com/jxxghp/MoviePilot/blob/0aa857173f77de31c7c8d9d2e12052d99f37bcc1/app/runtime/tasks.py) | [context.ts](../../apps/server/src/context.ts) 第 96 行;[modules.test.ts](../../apps/server/test/modules.test.ts) 第 86 行 |
另有结算回滚/重放专项断言:[test_chain_durable_events.py](https://github.com/jxxghp/MoviePilot/blob/0aa857173f77de31c7c8d9d2e12052d99f37bcc1/tests/test_chain_durable_events.py#L1373) 检查 outbox 冲突回滚历史与 pending、陈旧租约不写业务、并发结算只提交一次。这些场景值得纳入后续验收,不能把本项目已有的业务事务直接等同于全部专项均已通过。
MP 还在 [test_transfer_planning_checkpoint.py](https://github.com/jxxghp/MoviePilot/blob/0aa857173f77de31c7c8d9d2e12052d99f37bcc1/tests/test_transfer_planning_checkpoint.py#L474) 检查非预览整理缺少 durable writer/execution repository 时,在副作用前拒绝执行。这可支持本项目「关键装配缺失应明确失败」的原则,仍不是相同的 handler 注册方案。
### 3.3 已确认的恢复原则和验收场景
用户认可这一项的恢复方向,并明确避免过度设计和过度防御。以下原则按团队内部产品的规模执行,讨论阶段的原则已落实局部修正,见第 4 节。
1. **请求接受后必须可追踪、可恢复。** 已创建的业务记录、稳定身份和原输入要能关联到已有 run;关联回填失败与真正入队失败要能区分。查不清入队结果时记录不确定性,不能直接假定没有副作用。
2. **不存在、已结束和查不到是三种不同事实。** 临时查询失败跳过本轮并保留诊断;明确查无 run 才进入补投判断;已有运行不能只因到达新时间槽就替换。
3. **升级检查实际在途任务。** 优先等待旧任务结束,或明确说明后终止并通过现有入口重新发起;确有未完成任务必须继续且不能重做时,再评估兼容或迁移。没有在途旧任务时,不新增旧处理器和通用兼容层。涉及已经执行的文件操作时保留原输入和执行证据。
4. **恢复复用已有结果。** 沿用现有冻结计划和已完成步骤;普通查询故障等待下轮重查,可重试失败使用已有重试。删除、覆盖或替换文件的实际状态不明或冲突时,停止自动重放并在现有记录中说明,不为所有异常新增人工审核流程。
最小验收样本:
| 样本 | 注入点 | 应核对的结果 |
| --- | --- | --- |
| T01 | 业务记录插入后、引擎入队前退出 | 能补投或显式结束,不能永远只显示 queued;保留同一业务身份 |
| T02 | 引擎已接受,但 run 关联回填失败 | 找到原 run 并修复关联;不能错误标成入队失败而让 worker 跳过;不能创建第二次外部动作 |
| T03 | 引擎提交后响应丢失,调用方不知道是否接受 | 通过稳定身份与原输入确认已有 run;确认不了时有诊断及等待/人工路径 |
| T04 | 已有 active run,巡检查询一次失败 | 本轮无新增 run;原关联不被覆盖;下一轮查询正常时恢复判断 |
| T05 | 上一版本存在未完成 `scrapeMedia` v1,再启动当前版本与迁移 | 兼容、转换、终止或人工处置有明确结果和证据;无旧 run 永久悬空;迁移前后输入可解释 |
| T06 | 正式装配遗漏一个核心 handler | 装配检查失败或启动时给出明确错误;不能以 stub 的成功回显通过验收 |
已有同版本 kill-worker 测试继续作为回归。T01 至 T06 是针对不同改动的验收场景清单,按实际修复选择,不要求每个功能开发前全部执行。T02、T03 涉及两边不同的队列机制,因此样本按本项目的引擎接口设计,不机械复用 MP 测试。
**当前取舍:保留 OpenWorkflow、冻结计划和现有账本;优先局部修正查询异常判定与入队/关联回填的异常分类,再核实是否可以用已有幂等和巡检完成恢复。** 只有故障样本证明现有机制无法处理时,才评估复用 outbox 等进一步调整;专门恢复器、新状态机和补偿服务不作为默认方案。旧任务处置先落实发布检查清单。第一项实现细节确定后,第二项核对下载纳管与多实例隔离,再进入部分入库后的清理保护。
## 4 用户确认后的当前实现
### 4.1 局部任务恢复
已按第 3 节原则修正入队后关联回填、查询失败补投判定、run 关联恢复与刮削去重。9 个真实 PG 故障样本通过;未新增另一套恢复基础设施。旧版本实际在途任务的发布处置写入部署手册。T05 的跨版本专项和 T06 的 handler 装配强化仍不能因这 9 个测试而宣称完成。
### 4.2 分类、来源与存量
参考插件 [`categoryeditor` v1.3](https://github.com/jxxghp/MoviePilot-Plugins/tree/9b9b14bc0f0bb3890b0f699a84f9bd0df063b2e3/plugins/categoryeditor):插件保存 YAML 并刷新分类 helper,匹配由 MP core 实现。参考 v3 的 `app/domain/classification/projection.py` 的 legacy TMDB 匹配分支:顺序首次匹配、字段 AND、值 OR、排除、年份和空规则兜底。我们内置同类规则编辑能力,保留用户提供的全部名称和兜底习惯;不复制 v3 的完整分类策略版本发布体系。
目录接管参考 MP `app/application/transfer/monitor/snapshot.py`、`poller.py`:先快照现状,再比对新增/变化;完整存量作为单独手动操作。我们采用数据库持久快照、已有识别与整理计划,一条配置一个来源路径。同分类可配置多条不同来源,综艺归 tv;只有复制与移动。qB 标签继续使用现有任务管理和实例范围。
当前源码已接入分类编辑和测试、来源选择、自动/固定分类、qB 配置标签、快照与存量向导。已有路径绑定保留,不按新规则迁移旧库;源离线保留基线,已知 qB 任务不被目录扫描重复接管。具体入口和分批限制见 [整理手册](../runbook/organize.md)。测试使用本项目临时目录与隔离 PG,不是运行 MP 服务;未完成浏览器截图审查;后续 v0.3.9 已发布,见 发布记录(已删除,见 git `ceb66f8`)。
其他主题仍按第 2 节逐项讨论。这轮只补齐与已讨论整理功能有关的分类、标签与目录接管行为,不宣称整个下载隔离、清理、订阅和智能体业务对照已完成。
### 4.3 本次验证记录
- 分类匹配 domain 12 个定向测试;domain 全量 22 文件 / 654 测试通过。contracts 全量 7 文件 / 1048 测试、db 全量 4 文件 / 21 测试通过。
- source monitor 13 个真实 PG/临时文件/HTTP mock qB 测试:基线不处理存量、稳定观察、旧基线保留、分类和失败文件处理、存量只读与分批、文件事实变化、无标签未登记下载等待并在完成后接管。
- 最终合并复验分类 API、来源规划和 source monitor:3 文件 / 23 个真实 PG 测试全部通过。分类 API 与来源规划其中 10 个,包括并发配置保存、固定目录不重复、自动目录层级、已有绑定保留、qB 标签与来源/实例范围,以及媒体类型限制。
- 文件执行、移动、恢复、刮削、接管和向导覆盖最终回归 7 文件 / 38 测试通过;worker lane 注册与文档归属 8 测试通过。
- server 完整回归初次运行 127 文件,783 测试通过、2 失败、1 跳过;失败涉及新任务文档归属及开发过程中分类并发合并,最终相关 3 文件 / 23 测试和 worker lane 8 测试单独复验通过。完整回归不是最终同一时间源码的全绿报告,环境条件跳过也不计为完成验收。
- web 全量 25 文件 / 228 测试、web typecheck 与 production build 通过。workspace TypeScript / Deno 检查、Biome 检查和 `git diff --check` 通过。
没有使用浏览器截图替代或冒充前端验收;本次未替换线上镜像、未执行线上迁移,也未修改宿主机程序。
### 4.4 v0.3.9 发布与文件管理入口
用户要求部署后,核对发现文件管理入口原来只复用向导,没有携带来源配置。本次补齐同根/目录边界/最长来源匹配、唯一匹配自动应用、多配置明确选择及首击等待加载;H&R 与根目录保护保留。新增匹配测试后 web 全量 26 文件 / 232 项通过,typecheck、Biome 和 production build 通过。
v0.3.9 Docker 镜像完成 web build、server compile 和空环境二进制自检后部署;执行 `0059` 迁移,注册 `scanOrganizeSource@1` 和 `organize.sources`。发布前没有在途业务或引擎任务,备份校验通过;部署后 HTTP / HTTPS、版本一致、登录后的分类/来源/文件根读取以及 3 个分类预览通过。详细镜像与证据见发布记录。
## 5 下载入口与浏览器详情页交接(讨论记录)
2026-10-01 用户继续逐项讨论后确认以下业务决策,并随后授权实施。实现与验证见第 5.6 节;这批变更已部署为 v0.3.10。
### 5.1 已确认
- 常规入口包括订阅自动下载、手动搜索选择下载、外部 qB 任务接管,前两种使用较多。三种入口进入共同的后续下载管理和整理流程,保留来源。
- 手动下载独立于订阅,不要求先建订阅,不自动创建持续订阅。
- 优先沿用 MP 普通订阅的完成口径:下载器成功接收资源后记录下载事实,按目标覆盖判断完成,不以整理入库成功作为普通订阅完成的前置条件。下载与整理状态分别展示;洗版和多版本口径后续单独核对。
- 浏览器手动下载入口位于串流窗口外的工具操作栏,桌面串流与截图模式共用。用户进入原站资源详情页后点击「下载当前资源」,复用现有详情解析和下载提交能力,不要求用户点击原站下载按钮或等待文件捕获。
- 下载按钮绑定当前浏览器标签页的资源详情;按站点定义解析资源身份和详情,使用对应站点的种子获取方式。默认下载器与保存目录可直接提交,也提供修改选项;提交结果及下载任务入口在工具操作栏显示。
- 浏览器详情页入口不以资源预先存在于本地搜索/快照索引或已有订阅为条件。需要新增当前页面与详情解析、手动下载的衔接;本地缺少记录不能阻止用户选择的资源进入手动流程。
### 5.2 已核对的代码依据与现有差异
MP 固定基线中的 `app/api/endpoints/download.py` 手动入口使用 `source="Manual"`,不创建订阅;`app/chain/subscribe/search.py` 提交订阅下载后调用完成判断,`app/chain/subscribe/completion.py` 普通电影可在取得下载结果后完成,普通剧集按剩余目标和下载事实判断。外部 qB 任务在配置管理标签时按标签筛选,并由 `app/chain/transfer/queue.py` 检查下载完成、来源监控目录及已有整理状态;无下载历史的任务由文件重新识别。
本项目已有 `packages/adapters/src/browser/camofox/client.ts` 的 `pageContent` 当前页面读取、`packages/adapters/src/sites/spider/detail.ts` 的可配置详情解析,以及 M-Team 等站点的 `detail`/`downloadTarget` 能力。现有 `apps/server/src/modules/browser/routes.ts` 文件捕获入口要求匹配本地资源,不能替代本次确认的详情页直接下载入口。现有搜索/工作台手动提交与收录版本的关联限制也需在后续实现中梳理。
### 5.3 已确认:未识别资源与 TMDB/Local 元数据
MP 手动添加入口识别失败时返回 `requires_confirmation`;前端 `AddDownloadDialog.vue` 允许用户确认继续下载,通过 `allow_unrecognized` 提交最小媒体上下文。该分支不强制用户先指定 TMDB 身份。
用户确认未识别资源可以下载。整理阶段采用以下识别与元数据补全流程:
1. 常规识别先匹配 TMDB;成功后按 TMDB 元数据整理。
2. 自动匹配失败时由 Agent 接棒,读取 PT 详情页并结合网络搜索核对原名、译名、别名、年份及季集信息,再尝试匹配 TMDB。因翻译/别名导致的失败应在查明正确条目后回到 TMDB 模式。
3. 找不到合适 TMDB 条目时进入 Local 模式,由 Agent 基于 PT 详情页及搜寻资料补全标题、简介、年份、类型、演职人员等信息,选取封面,生成本地 NFO 和图片。有依据的字段补全,缺少资料的字段留空。
4. TMDB 与 Local 模式共用分类、命名、复制/移动、整理记录和入库流程。Local 保存独立身份及资料来源,后续可以重新匹配 TMDB;不为 Local 新建一套整理系统。
5. Local 使用补全后的语种、国家、类型等字段匹配现有分类配置;字段不足时使用对应电影/剧集的兜底分类,允许手动调整。
实现复用现有身份候选、元数据补全、字段投影与 NFO 刮削链路;资料不足或候选冲突仍保留待处理。具体实现和验收范围见第 5.6 节。
### 5.4 已确认:已有任务与已有作品
用户认可同种子复用已有下载任务,同一作品的不同资源仍允许手动下载。媒体详情需要展示库内已有版本及规格,用户据此决定下载何种资源;已有作品不作为手动下载的阻断条件。
### 5.5 已确认:正常入库与一次性洗版入库
用户确认在本次下载中选择「正常入库」或「洗版入库」:正常模式允许多版本并存;洗版模式在下载完成后整理时用新版本替换旧版本,是本次任务的一次性规划,不自动变成持续洗版订阅。
- 洗版允许用户选择替换哪些已有版本。只有一个旧版本时默认选中,有多个时由用户勾选,未选择的版本保留。
- 新文件成功落库后再清理对应旧文件;部分整理失败时保留相应旧文件。
- 剧集按实际成功入库的季集范围替换。例如旧版有第 1–10 集、新资源只包含第 1–6 集,只替换前六集。特殊编号、上下段及合并集由 Agent 辅助核对,不能明确对应时供用户调整。
固定 MP 基线 `app/modules/filemanager/transhandler.py` 提供 `always`/`size`/`never`/`latest` 覆盖模式;`latest` 其它版本清理按同目录、季集与 Part 匹配视频文件。它的清理步骤在新文件转移前执行,不能把本项目拟采用的「新文件成功落库后清理旧文件」描述为 MP 原样行为。
多版本是否产生不同路径取决于命名模板。MP 的 `app/runtime/config.py` 默认电影文件名包含 `videoFormat`,默认剧集文件名没有分辨率/编码等版本字段;本项目 `packages/domain/src/naming/defaults.ts` 当前沿用这两条模板。因此需要明确多版本命名方案,不能假定不同资源会自动产生不同文件名。已有 `path_binding` 仍按前述约定保留,模板调整不自动迁移旧库。
用户要求模板可配置,并提供目前使用的两条模板作为参考配置,原样记录如下。本轮未修改线上命名设置。
电影:
```jinja
{{title}}{% if year %} ({{year}}){% endif %}/{{title}}{% if year %} ({{year}}){% endif %}{% if part %}-{{part}}{% endif %}{% if videoFormat %} - {{videoFormat}}{% endif %}{% if edition %} - {{edition}}{% endif %}{{fileExt}}
```
电视剧:
```jinja
{{title}}{% if year %} ({{year}}){% endif %}/Season {{season}}/{{title}} - {{season_episode}}{% if part %}-{{part}}{% endif %}{% if episode %} - 第 {{episode}} 集{% endif %}{% if videoFormat %} - {{videoFormat}}{% endif %}{% if edition %} - {{edition}}{% endif %}{{fileExt}}
```
已核对本项目支持模板编辑、`videoFormat` 和 `edition`。`edition` 按 MP 语义为来源与特效组合(如 `WEB-DL DV`),剪辑版本另有 `cut` 变量,不改变用户提供的模板。模板不能区分的同名文件冲突需沿用整理冲突处理;不据此宣称模板可以区分所有同规格版本。
使用现有命名引擎直接编译并渲染上述两条模板,完整字段及年份/分辨率/edition 缺失的样本均通过;文档差异空白检查通过。此验证未修改任何媒体文件或线上配置。
一次性洗版已接入手动计划和冻结整理计划;同路径与不同路径替换复用现有整理记录、文件执行和操作账本。用户选择的旧版本及季集范围随本次计划保存,验证记录见第 5.6 节。
后续仍需讨论手动选择与自动筛选规则、同 hash 多实例及删除范围。
### 5.6 本轮实现与验收(已部署 v0.3.10)
手动搜索、站点工作台与原站详情共用独立手动下载入口,支持下载器、映射保存目录和正常/一次性洗版选择。未识别先尝试现有身份解析,仍未识别时返回确认结果;确认后建立无 Slot 的手动计划,不创建订阅。迁移 `0060_manual_acquisition` 允许计划无 Slot,并添加 `download_task.import_plan_id` 保存本次入库选择,原下载任务的订阅计划关联继续保留。
原站详情入口在桌面串流/截图之外显示「下载当前资源」与「下载选项」。`POST /browser/sessions/{id}/resource` 从当前可见标签按站点定义读取详情,登记此前没有索引的资源并缓存 PT 内容;不点击原站下载按钮。桌面新增 popup 会被发现,无法唯一确定当前标签时给出明确提示,不提交旧页面。普通 Agent 浏览器动作沿用原标签选择。原生桌面新页未被上游登记时,仍需回到已登记详情页;本轮没有改浏览器插件登记协议。
整理增加 `metadata.resolve_organize`:常规匹配 → 详情页/网络补全与模型抽取 → 别名重查 TMDB → 有资料的 Local 身份。复用现有预算、补全记录、字段来源、分类和刮削;不能将 TMDB 查询故障视为没有条目,候选冲突或资料不足保留待处理。Local 身份不锁定,后续可重新识别。公网封面通过现有图片抓取能力写入;成功的 Local 入库安排 NFO/图片任务,普通 TMDB 自动刮削继续遵循原设置。
一次性洗版冻结用户所选旧文件及事实;新文件核验和资产登记成功后,按实际季集/Part 覆盖清理所选旧版及私有附属文件。同路径替换先暂存旧文件,核验失败恢复旧文件;未选文件、未成功替换的季集、未完整覆盖的合并集、共享图片/NFO及下载缓存保留。清理复用现有回收站与 `asset_operation`;不增加持续洗版订阅。命名模板仍通过设置编辑,本轮没有修改用户线上模板或已有路径绑定。
普通订阅现在按下载器确认接收及实际目标覆盖结束;多目标必须全部满足,TV 未知总集数不伪造完整覆盖。Slot 与下载任务继续表示真实下载/核验进度,洗版目标沿用入库后的判定。
本轮验证:
- 手动下载/订阅/面板 15 项与搜索入口 11 项隔离 PG 测试通过;覆盖未知资源确认、无假订阅、映射目录、同 hash 原路径及新入库计划、多目标订阅完成。
- 浏览器详情入口及既有浏览器 API 共 13 项通过;适配器全量 58 文件/461 项通过、1 文件/3 项环境跳过,其中可见标签交接 12 项定向通过。没有用 fixture 结果替代用户真实浏览器验收。
- 整理、分类与来源 4 文件/18 项通过,包含同路径失败恢复、旧文件变化保护、剧集部分失败/合并集保留,以及 Local 在自动刮削关闭时仍排 NFO/图片任务。领域整理专项 22 项通过。
- Local 与既有元数据补全 21 项通过;NFO/Local/刮削差分 25 项通过,Local 内部人物编号不会写成负 TMDB ID。模型和外部服务使用受控 fixture,没有声称真实模型或全部 PT 站已验收。
- server 完整回归 132 文件:804 项通过、9 项失败、1 项环境跳过。9 个失败集中在 4 个旧测试文件,仍把手动提交视作订阅评估、使用旧订阅完成原因或固定 41 能力。修正测试准备入口和新口径后,这 4 文件合并复验 19 项全部通过;保留实际入库、季集覆盖、资产/文件保留和不重复下载断言,并增加独立手动不创建订阅/Slot 的验证。没有将这次分批复验描述为最终同一时点的单次全量全绿报告。
- contracts 全量 1052 项、db 21 项、domain 656 项、web 232 项通过。workspace TypeScript/Deno、Biome、设计 token、文案标点和 `git diff --check` 通过。
- 用本机 Chromium 与真实 React 组件验证单旧版默认选择、无选择禁用洗版、勾选洗版、切回正常、未知资源二次确认、串流外按钮交接;API 为 fixture,未产生真实下载,页面无未捕获异常。
- web production build、server compile 与空环境二进制模块自检通过。局部证据存于本机 `.local/manual-download-ui/`。
以上业务回归主要为源码与隔离环境验证。随后于 2026-10-01 发布 v0.3.10,线上 `0060` 迁移、HTTP/HTTPS、管理员接口及 6 个生产页面冒烟通过,未触发真实下载或媒体整理;镜像、备份与验证证据见 发布记录(已删除,见 git `ceb66f8`)。
### 5.7 v0.3.11 体验修复
搜索资源标题接入与站点工作台同一详情抽屉与读取接口,复用按需详情抓取和缓存,不增加第二套解析器。详情抽屉仍提供下载选项、库存和 Agent 入口。桌面浏览器的最大化规则修复后,上下黑边消失。已部署并经过组件交互、生产真实缓存详情接口与 HTTPS 页面验证,范围和证据见 发布记录(已删除,见 git `ceb66f8`)。
## 6 整理使用方式与文件管理器专项研究(2026-10-02)
本节只记录研究与最少改动建议,未修改业务代码。MP 后端仍使用 §1 固定提交;补充前端固定源码 `/tmp/umt-moviepilot-frontend-v3-review-20261001`,提交 `63515db2e873d6adaf6aa385a89c5901e3bc1ea6`。**MP 仅源码对照,没有 MP 实际页面截图。** 本项目截图来自本轮 `https://localhost:4380` 已登录页面,使用 Chromium / Playwright,只打开页面、选择项与整理对话框;未点击执行整理、下载、接管或清理,未新增测试数据和来源配置。
### 6.1 实际页面步骤与证据
截图均在 `.local/organize-flow-review-20261002/`,已打开检查;这是本机研究证据,不作为发布包。
| 步骤 | 实际操作与健康情况 | 截图 |
| --- | --- | --- |
| 1 | 整理记录页正常显示作品、源→目标、复制、失败原因和来源;手动整理入口可见 | `01-organize-history.png` |
| 2 | 点击手动整理,当前环境没有整理来源配置,仅显示「手动选择其他来源目录」;可以继续,但空状态没有去新增来源的入口 | `02-manual-source-picker.png` |
| 3 | 打开手动来源目录对话框,下载根与媒体库根可浏览;尚未选择时确认禁用,正常 | `08-manual-directory.png` |
| 4 | 文件管理器打开下载目录,选择一个电影目录后出现底部整理/移动/复制工具栏;移动因做种义务禁用,正常 | `03-file-manager.png`、`05-file-selection.png` |
| 5 | 点击整理,目录展开为一个视频,自动识别作品、显示已有记录和 H&R;页面显示「已选 0 项」但下一步可点,执行范围表达容易混淆 | `06-file-organize-wizard.png` |
| 6 | 下一步显示复制/移动、冲突/跳过/替换、自动/媒体库/指定目录、命名和元数据刷新;H&R 禁用移动,正常;没有提交预览或执行 | `07-file-organize-options.png` |
| 7 | 失败记录菜单点击重新整理,源文件与原作品进入相同向导,正常;未执行 | `10-record-reorganize.png` |
| 8 | 设置页实际显示媒体库、整理方式、刮削、分类、来源与命名;本环境没有来源配置 | `04-settings.png` |
| 9 | 下载面板切换已完成正常,显示已入库与入库失败及原因;下载盘独立展示。未操作下载或接管,下载详情整理仅做源码核对 | `09-download-board.png`、`11-download-completed.png` |
### 6.2 业务边界与 MP 差异
| 场景 | MP 固定源码行为 | 本项目当前路径、差异与最少建议 |
| --- | --- | --- |
| 文件管理器单项/多选整理 | `components/filebrowser/FileList.vue:562`、`:568` 将当前项或去重后的 selected 传给同一个 `ReorganizeDialog`,不是另建整理系统。对话框 `ReorganizeDialog.vue:667` 使用文件项或历史 ID 查询 `transfer/manual/target-path`,展示匹配目的目录;用户明确选择目标后不再覆盖 | `routes/console/files.tsx:392` 过滤视频/目录,进入 `FileSourceSelection` 再共用三步向导,方向一致。来源匹配按根、目录边界和最长路径,唯一匹配自动应用,多匹配需用户选择(`components/organize/source-match.ts:4`、`file-source-selection.tsx:23`)。保留已有保护和预览,不需要再做第二个简化整理弹窗 |
| 手动整理、文件整理、历史重整的来源默认 | MP 相同对话框对 fileitems 与 logids 都查询自动目标,匹配结果包含整理方式和刮削/分类目录选项;后端 `app/api/endpoints/transfer.py:549` 针对各来源匹配目录,不同目标时不宣称共同匹配 | 本项目来源存量带 `sourceConfigId/defaultAction`,文件管理器先匹配来源;但「其他来源目录」直接创建不含配置的 files 来源(`source-selection.tsx:37`),历史和下载详情也直接进向导(`routes/console/organize.tsx:319`、`downloads/download-detail.tsx:233`)。默认方式因此回到全局(`organize-wizard.tsx:233`),请求只在 files 分支透传配置(`:327`、`:341`)。服务器 `services/organize-config.ts:17` 对手动文件不会按目录自动匹配,只有显式配置或带实例/标签的 qB 自动来源。这是入口默认不一致,建议解析完 FileRef 后共用已有来源选择,不另建配置体系;历史重新整理应优先保留原次策略或明确展示当前匹配策略 |
| 成功移动后的重新整理 | MP `app/api/endpoints/transfer.py:737` 对成功且方式含 move 的历史,用历史目标文件作为本次输入;失败/复制使用原源文件 | 本项目 `file-manager/tree.ts:75` 的 `recordSources` 只解析原 source_path/主文件 source_path;源不存在则进入 unresolved。`record-parts.ts:30` 仍让已结束移动记录显示重新整理入口。这会让成功移动后的正常重整无可用文件。最少修正是按成功移动历史选择现存目标,源缺失其他场景说明不可重整;不承诺任意丢失文件恢复。此项为源码确定差异,本轮没有制造真实移动样本 |
| 已有成功整理记录的手动扫描 | MP `ReorganizeDialog.vue:1388` 查询历史,显示成功记录处理选项,`:1314` 对预览与提交一致传 `reorganize/skip_success` | 本项目文件行显示「有 N 条整理记录」,但勾选框是批量修改作品/季集的选择,不是纳入整理范围;所有未排除行仍进入预览(`organize-wizard.tsx:274`、`:588`)。已有文件靠冲突/跳过/替换处理,而「跳过目标已有」与「跳过成功记录」不是相同概念。先把「已选」改为「批量修改已选」,明确「本次整理 N 个未排除视频」;历史成功跳过有实际需求再复用已有记录判断,不默认新增整套状态 |
| 下载接管和手动整理 | MP 管理标签决定外部下载纳管,完成后进入整理;文件管理器整理是明确选中文件的单次操作 | 本项目 qB 标签来源用于自动纳管/整理(`organize-config.ts:55`、`:64`),文件管理器单次整理和来源存量不等于下载任务接管,也不改变目录监控基线。下载详情可以在完成/importing/imported 时打开向导(`download-detail.tsx:98`),内容路径先转换为登记根 FileRef(`organize-wizard.tsx:126`);向导请求没有传下载 taskId/instance/tags(`model.ts:248`)。建议文案明确「整理所选文件」与「接管下载」;不在文件整理时隐式增加标签或接管任务 |
| 整理、刷新元数据和刮削 | MP 对话框支持 scrape;匹配目录可给出 scraping 默认(`ReorganizeDialog.vue:711`),设置用于长期默认,本次选项用于本次操作 | 本项目第 2 步的「刷新元数据」是字段级 refreshMetadata(`file-manager/organize.ts:523`),不是 NFO/海报刮削;页面底部已经说明现有 NFO/图片不搬,可整理后刮削。Local 成功/部分成功后另有 NFO/图片任务(`acquisition-organize/services/organize.ts:1225`),应保留 §5 已确认的 Agent→TMDB/Local,不因对标 MP 强迫仅 TMDB。最少先区分刷新资料与写入 NFO/图片;如需本次刮削开关,复用现有 queue,不创建第二套刮削 |
### 6.3 保留与最小验证
保留仅复制/移动、同分类多个来源、固定/自动分类、已绑定作品沿用原路径、执行前预览、跨卷核验后删源、H&R、来源监控不回填存量,以及 Agent/TMDB/Local 共用整理。不要照抄 MP 的软/硬链接、rclone、音乐或额外分类发布系统。
建议优先处理:① 文件/手动/下载/历史入口统一来源策略展示;② 成功移动历史改用目标重整;③ 向导明确批量修改选择与实际整理范围;④ 无来源空状态加现有设置入口。完成这些后再讨论成功记录跳过和本次刮削,不把它们提前扩成新业务系统。
最小验证使用同一来源下同一目录,从文件管理、手动其他目录、下载详情、历史入口进入,核对所选来源、方式、目标、分类一致;增加成功移动后源已删除、多个来源同路径、跨不同来源多选及部分失败样本。H&R 在本轮文件入口可见且禁用移动;其他入口及服务器执行保护还需定向验证。预览冻结、实际文件执行、Agent 实际补全、来源多配置和源离线没有在本轮页面运行,不把源码能力等同浏览器验收。截图也不能证明完整可访问性;批量单选的方向键导航、焦点与读屏说明仍需交互验证。
### 6.4 用户反馈与后续讨论(2026-10-02)
用户要求以下样式问题先记下,等整理模块讨论完成后一并修改;当前不改样式:
- 右上角通知数字角标目前为金色,与用户期望的整体样式不一致;具体替代颜色尚未确定。
- 分页的每页条数选择控件仍呈现原生样式,应复用项目现有选择控件与统一样式。
整理讨论按实际场景逐项进行,每项对照「MP 行为 → 当前行为 → 希望的行为 → 验收示例」,讨论确认后再实施。建议顺序:入口和来源默认;文件范围与已有记录;作品识别、目标命名与刮削;执行结果、失败重试与历史重新整理。前面已确认的复制/移动、分类 YAML、存量单次触发、qB 标签/快照、TMDB/Local 和多版本/洗版约定继续沿用,不重新扩展需求。
用户进一步倾向于 MP 的独立、共用整理弹窗方案,整理流程也优先参考 MP。据此讨论统一弹窗:手动整理、文件管理器、下载详情和历史重整传入各自上下文,共用来源/目标配置、识别修正、整理选项、预览和提交逻辑。「全局生效」指规则与交互在各入口一致;单次调整只作用于本次整理,长期默认在设置中保存。自动整理共用业务规则,后台执行不要求弹出窗口。弹窗具体操作结构与细节继续讨论,尚未授权据此实施业务改动。
后续讨论中用户认可「本次设置作用于整个所选范围」:单文件只处理该文件,目录递归处理其中视频,多选合并去重;自动识别按文件进行,手动指定作品作用于本批范围,混合批次中需要单独纠正的作品另行选择整理。用户要求参考 MP 的弹窗能力:手动指定电影/剧集,直接输入 TMDB ID 或调用 TMDB 搜索并选择作品,选择转移模式与目标路径、是否刮削,并通过队列执行。既有范围仍仅复制/移动,TMDB/Local 与 Agent 兜底继续沿用。
用户另提出学习 MP 的「整理队列/整理记录」划分。当前本项目整理页将记录和进行中状态集中展示;底层已有 task、引擎调度与整理记录关联(`file-manager/organize.ts`),可优先复用。拟将等待/执行中的任务展示于整理队列,将处理结果展示于整理记录;整理成功的文件在开启刮削时再触发 NFO/图片任务,界面区分整理与刮削状态。MP 固定前端提供预览、加入队列和立即整理按钮,是否完整沿用按钮组合尚未确定;本项目不因拆分视图另造调度机制。以上仍为需求讨论记录,未实施业务改动。
用户随后确认提交按钮只保留「加入整理队列」,不提供「立即整理」。任务受理成功后即可关闭弹窗或继续其他操作,不要求用户停留等待文件处理完成;进度和结果通过整理队列/整理记录查看。仍沿用既有执行前预览约定。本项已定,下一项继续讨论已有成功整理记录的跳过与重新整理语义。
已有成功记录的处理,用户要求采用 MP 做法。重新核对固定前端源码:`ReorganizeDialog.vue` 的 `skipSuccessfulRecords` 初始为 `false`;手动文件/目录入口查询成功历史,有记录时显示数量和「跳过成功记录」开关,默认关闭,用户本次开启后才跳过;改变开关会清除旧预览,预览与提交共用该参数。历史 ID 入口直接采用重新整理语义,不走上述手动历史提示查询。本项目采用这些默认与交互,先前讨论提出的「普通整理默认跳过」不采用。成功记录跳过和目标文件冲突仍是两个独立判断;保留本项目只提供「加入整理队列」的已确认差异。
### 6.5 本轮落实(v0.3.13)
用户授权「一鼓作气做完」。共用整理弹窗已改为单个配置表单与可展开预览,手动、文件管理、下载详情、历史重整统一来源匹配和选项;支持自动/电影/剧集、TMDB 搜索与 ID 定位、本批作品与季号、复制/移动、目标路径、命名/冲突、刮削开关,只提交现有整理队列。成功受理即可关闭;部分受理后已入队文件从本批移除,避免重新预览时重复提交。异步定位取消后不会覆盖用户新选择,选项或识别变化使旧预览失效。
整理页分队列与记录:队列直接查询现有 `verifyAndImport` 与 `scrapeMedia` 的 pending/running/sleeping 任务,覆盖未生成记录的下载核验任务;记录默认终态且聚焦整理行为,旧记录/筛选深链接仍可打开。成功移动重整用结果的媒体库目标路径;复制和失败仍用源。成功历史判断同时要求完整成功记录与成功主文件,识别提示与执行共用查询;手动跳过默认关闭,目标冲突另行判断。本次刮削值冻结到计划,TMDB/Local 及恢复执行都复用原刮削任务机制。
通知数字角标改为主题 primary/primary-foreground;每页条数控件改为项目现有 Select,保留分页行为。使用手册同步更新。验证与发布证据集中于 `.local/organize-final-20261002/`,浏览器交互的写请求使用拦截响应,不对用户媒体进行实际下载、移动、替换或刮削;真正文件执行由隔离数据库与临时目录回归验证。
### 6.6 原地入库补充(2026-10-02,v0.3.14 待部署)
用户指出文件管理整理不应一律复制/移动,并要求修复和记录。核对已有固定 MP 前后端源码:`api/constants.ts` 的整理方式是 copy/move/link/softlink,没有独立的原地登记枚举;文件管理的 `ScrapeDialog.vue`/`app/chain/scraping.py` 提供独立就地刮削。`storages/local.py` 的同路径 move 快速返回只是底层保护,不能据此认定上层整理流程已支持原地入库。
本项目既有「刮削」可就地写 NFO/图片,`addToLibrary` 仅创建作品条目,不登记视频位置、资产及季集覆盖;保留原文件名选项仍会搬运到模板目录。新增共用弹窗「原地入库」,保留路径和名称,复用识别、预览、队列、逐文件结果与资产登记事务;记录方式为 `in_place`,迁移 `0061` 扩展两个枚举约束。媒体库内选择默认原地入库,下载缓存继续原复制/移动。原地模式不使用分类搬运目标和命名模板、不建立路径绑定,配对字幕一并登记;可选刮削只针对本次成功文件。
这是为本项目资产管理补齐的行为,参考 MP 就地刮削语义,并非声称 MP 有同名模式。未改动自动接管配置或加入链接方式。实现与验证边界见发布记录和整理手册。
## 7 订阅:追更、选片、洗版与完成(2026-10-02)
MP 依据 §1 固定提交 `app/chain/subscribe/*`,旧版逐函数记录见 [订阅移植对照](moviepilot-subscribe.md)(基线 `e6820a8`,本轮抽查完成判定、整理后事件、状态机未见行为变化)。本项目依据 `acquisition-organize/services/{subscriptions,acquisition,coverage}.ts`、`contracts/src/policy.ts`、订阅表单。测试环境现有 5 条订阅均已取消,收录版本(Slot)0 个:多版本相关机制尚无实际使用记录。
### 7.1 场景对照
| # | 场景 | MP | 本项目当前 | 差异与建议 |
| --- | --- | --- | --- | --- |
| 1 | 订阅对象 | 一条订阅 = 一个作品的一季;过滤条件(质量、分辨率、特效、包含/排除、站点、规则组、洗版开关)直接是订阅字段 | 订阅 → 一个或多个「收录版本」(Slot),硬条件/软偏好/升级阈值/完成条件放在 Slot;订阅表单约 30 个字段 | **建议简化**:默认一条订阅只有一个隐式收录版本,表单回到 MP 那组字段;多版本作为高级选项折叠 |
| 2 | 新建订阅 | 识别失败不按标题兜底;总集数为 0 拒绝创建;建好后 5 分钟内搜一次 | 允许作品未建档(`titleQuery`)、总集数未知照样建(pending 不可完成);建好后排一次搜索 | 保留「总集数未知也能建」(短剧、平台剧常见);其余一致 |
| 3 | 追更来源 | 默认靠站点首页/RSS 刷新匹配(每天 32 个随机时刻或 RSS 每 30 分钟);定时主动搜索**默认关闭** | 新资源识别后逐个评估所有收录版本;另有按订阅的周期主动搜索(缺省 24 小时,可设 0 关闭) | 机制等价;**建议主动搜索默认关**,靠 RSS/站点快照,减少站点请求,需要时单个订阅打开 |
| 4 | 选片与优先级 | 规则组分层 `A > B > C`,命中第几层得 pri 100、99…,不命中丢弃;订阅字段的包含/排除、质量、分辨率是硬过滤 | 规则组 + 收录版本硬条件 + 软偏好排序;`resolution / source / effect` 存进订阅自己的收录版本 | 能力等价;问题在入口分散(订阅字段、收录版本、规则组三处),并入第 1 项一起收敛 |
| 5 | 同一集多个资源同时到 | 第一个成功提交下载即写 note,其余按"已下载"跳过 | Slot 行锁内复核在途单元,冲突的资源记「延后」,Slot 空出后重评 | 我们多了延后/重评调度。**建议讨论**:普通订阅是否直接跳过(MP 做法),只在洗版时才需要重评 |
| 6 | 整季包与单集 | 整季缺失找单季包,开种子核对集数;从整季包按文件选集下载(qB/TR) | 季包总集数已知按缺集展开,未知用整季通配预留;提交后开文件清单按缺集选文件 | 基本一致,保留 |
| 7 | 洗版 | 三种:电影洗版、分集洗版、全集洗版;目标 = 拿到规则组最高档(pri 100),到了就完成 | 收录版本的升级阈值 + 规则组升级链;手动下载另有一次性洗版(已定) | **建议对齐 MP 三种模式**作为订阅上的一个选项,「到最高档即完成」 |
| 8 | 多版本补全 | 无(一条订阅一个目标) | 自动补全其他收录版本 + 按订阅的每日字节/单元预算 | MP 没有、实际也未用过。**建议隐藏**,默认关;先不删代码 |
| 9 | 完成口径 | 普通订阅:下载提交成功即记事实,目标范围都有了就完成;P 状态不完成 | 已按 MP 口径(v0.3.10):下载器接收即计入;pending 不完成 | 一致 |
| 10 | 完成之后 | 写历史并**删除订阅**;重新订阅从历史新建 | 状态改 completed、写历史、**行保留**可改回 active | 我们的做法更省事,**建议保留** |
| 11 | 后续季 | 不追:一季一条订阅,新季要重新订阅 | 「持续后续季」:同一订阅自动追后面的季 | MP 没有。**建议讨论**是否保留:保留则需要明确"新季何时出现、何时算完成" |
| 12 | 订阅方式与优先级 | 无「一次性/持续」之分;无优先级档 | `once / continuous` 两种方式;即时 > 追更 > 新作品 > 升级四档优先级 | 优先级档只影响下载队列先后。**建议**:方式与第 11 项合并成一个"只这一季/持续追后续季";优先级默认隐藏 |
| 13 | 状态 | N 新建 → R 订阅中;P 待定(可搜不可完成);S 暂停 | active/pending/paused/completed/cancelled | 一致,保留 |
| 14 | 总集数变化 | 6 小时刷新;变大继续缺集;变小不低于已有最高集;全集洗版基线清零 | 元数据刷新改季总集数;回退不低于已覆盖最高集 | 一致,保留 |
| 15 | 分享、Follow、Seerr | 有 | 无 | 不做(团队内部用) |
### 7.2 建议的讨论顺序
1. 第 1、4、8、12 项:订阅表单和收录版本怎么收敛,这决定后面几项的界面。
2. 第 7 项:洗版是否改成 MP 三种模式。
3. 第 5、11 项:延后重评、持续后续季是否保留。
4. 第 3 项:主动搜索默认开关。
### 7.3 本轮落实(用户授权自主收尾,v0.3.17)
只改默认值和表单布局,不动数据模型与评估逻辑:
- 第 3 项:定时主动搜索默认关(`DEFAULT_SEARCH_INTERVAL_HOURS = 0`)。新建、手动搜索照常各搜一次,之后靠 RSS/站点快照追更;单个订阅填间隔才定时搜。旧订阅没填间隔的随之变为不定时搜索。
- 第 8 项:多版本自动补全系统缺省改为关(订阅或规则组显式打开仍生效),代码保留。
- 第 1、4、12 项:表单收敛为「基础」=作品与范围、下载、洗版;「进阶」=过滤(规则组、包含/排除、分辨率、质量、特效、关键词)、搜索;收录版本模板/顺序、优先级、自动补全折叠到「多版本(一般不用改)」。
- 第 7 项:洗版已是 MP 三种(电影洗版、按集洗版、全集洗版),文案改为「拿到规则组最高档即完成」,不再另做。
- 第 5、10、11、13、14 项维持现状;第 15 项不做。