diff --git a/docs/porting/moviepilot-episode-groups.md b/docs/porting/moviepilot-episode-groups.md new file mode 100644 index 00000000..4c264a27 --- /dev/null +++ b/docs/porting/moviepilot-episode-groups.md @@ -0,0 +1,17 @@ +# MoviePilot 剧集组对照(TMDB episode groups) + +上游(只读):`/tmp/umt-moviepilot-v3-review-20261001`(v3.0.10-1);前端 `/opt/moviepilot-plus/upstream/MoviePilot-Frontend`。使用说明见 `docs/runbook/identity.md`「剧集组」。 + +| MP | 我们 | 说明 | +|---|---|---| +| `tmdbv3api/objs/tv.py episode_groups`(`/tv/{id}/episode_groups`) | `adapters/tmdb/episode-groups.ts fetchEpisodeGroups` | id、名称、类型、组数、集数、平台;走同一请求缓存(`moviepilot-tmdb-cache.md`) | +| `objs/group.py details`、`tmdbapi.py get_tv_group_seasons`(组内 `episode_number` 按顺序从 1 重排)、`get_tv_group_detail`(按 `order` 取组) | `fetchEpisodeGroup` | 组 `order` = 季号;组内按 TMDB `order` 排序后编号(MP 用返回顺序,实际一致);重复 `order` 顺延;保留默认季集号 `tmdbSeason/tmdbEpisode`;占位名按无标题 | +| `MediaInfo.episode_group` / `_fill_group_season_info`(识别时带 `episode_group` 把季集换成组的) | 作品级设置 `media_entity.episode_group_id`(迁移 0075),季 / 集单元按组生成 | MP 每次识别 / 订阅 / 整理各带一个 `episode_group`;我们存在作品上,季表、映射、订阅、刷新、刮削都读它 | +| `_process_episode_groups`(没指定组时从 type 6 组取季年份) | 不做 | 我们的季年份来自季单元 | +| `api/endpoints/media.py groups/{tmdbid}`、`group/seasons/{groupId}` | `GET /media-entities/{id}/episode-groups`、`GET /media-entities/{id}/episode-structure?groupId=` | 季卡片额外带本地已入库(按 TMDB 原季集对照)与当前结构下的季单元 id | +| `schemas/subscribe.py episode_group`、`SubscribeSeasonDialog.vue`(剧集组横向轨道 + 季卡片:海报、集数、首播、已入库) | 订阅表单 `SubscriptionSeasonPicker` | 订阅不单独存组:选了非当前结构的季要确认改作品设置 | +| `scraper.py`:NFO / 剧照按 `get_tv_group_detail` 取季 | 刮削 NFO 用单元元数据(本来就按组);缺元数据时 `scrapeTmdbSeason(…, episodeGroupId)` 按组取 | 季海报 MP 仍取默认季;我们取组内首集所在默认季的海报 | +| `schemas/transfer.py episode_group`、`ReorganizeDialog.vue` | 不做 | 整理按作品当前季集结构映射 | +| `metabase.py` 识别词 `g=` 指定剧集组 | 不做 | 单条资源不指定组;规则的目标季集写组内的即可 | + +我们多出来的:切换结构时已有单元按 TMDB 原季集原地迁移(引用不动,对不上的标临时并列出);集数解析多一步「按 TMDB 默认季集换算」(`tmdb_season`),发布者按默认季编号时自动换到组内季集;刷新时 TMDB 调整了组内顺序也按原季集挪单元。 diff --git a/docs/runbook/identity.md b/docs/runbook/identity.md index 644fb3de..0028aeb7 100644 --- a/docs/runbook/identity.md +++ b/docs/runbook/identity.md @@ -171,6 +171,32 @@ select key, value->>'title' title, value->>'year' y, value->>'source' src from k - 没有授权(或模式是只读调查)时降级只读:智能体在会话里用 `simulate`(带事项 ID 和理由)试出的通过规则,会作为「智能体建议」写进事项证据(`evidence.agentSuggestion`,事项刷新时保留),待处理卡片显示规则、理由、样本套用后的季集与模拟结果,点「确认智能体建议」(选项 `confirm_agent_suggestion`)即建规则 + 重算;建之前按当前资源再模拟一次,不通过返回 409 不写。人工确认的规则记操作者为管理员、证据等级「人工确认」。 - 怎么撤销:作品页「集数映射」里智能体写的规则带「智能体 · 多证据 / 待复核」标记和依据,点「停用」:立即按作品重算(资源回到待确认并重新开事项),且同作用域有手动停用的规则时 `apply_rule` 一律拒绝,智能体不会再自动写回(仍可留建议给人确认)。直接删除不留这个记号,新开的事项可能再被自动写一次(受同根因自动恢复次数上限 `agent.investigateMaxAttempts` 约束)。要彻底停掉自动写规则,撤销那条持续授权或把自动调查模式改回只读调查。 +## 剧集组(季集结构) + +TMDB 默认季有时和发布者、媒体服务器的分季不一样(最常见:番剧两个 cour 被合成一季 25 集)。TMDB 的「剧集组」(episode group)是编辑整理的其它分法(原始播出 / 绝对集号 / DVD / 数字发行 / 故事线 / 制作季 / 电视)。作品可以选「TMDB 默认季」或某个剧集组作为**季集结构**,选了之后季表、季 / 集单元、集数映射、订阅、元数据刷新与刮削都按这套季集走。对照 MP 见 `docs/porting/moviepilot-episode-groups.md`。 + +**在哪选**:作品页「季与集」顶部的「季集结构」轨道(TMDB 有剧集组或作品已选了剧集组时才出现)。点一个非当前的结构先预览它的季卡片与每集(已入库按 TMDB 原季集对照),点「改用这套结构」确认后切换。新建订阅选季时同样有轨道 + 季卡片;选了非当前结构的季,确认后等同改作品设置,改完按新结构的同号季回填订阅。编辑订阅时季不能改。 + +**接口**:`GET /media-entities/{id}/episode-groups`(列表 + 当前选择)、`GET /media-entities/{id}/episode-structure?groupId=`(某套结构的季卡片,纯读)、`PUT /media-entities/{id}/episode-group`(`{groupId}`,null = 改回默认季;scope `organize.execute`,写审计 `media_entity.episode_group`)。作品上存 `episode_group_id / _name / _set_at / _set_by`(迁移 0075);改 TMDB 身份时一并清掉。 + +**剧集组怎么变成季集**:组的 `order` = 季号(重复的顺延),组内按 `order` 排序后从 1 编集号(同 MP `get_tv_group_seasons`)。每集记下 TMDB 默认季集号(单元字段 `tmdbSeason` / `tmdbEpisode`,tmdb 来源),季海报取组内首集所在默认季的海报。 + +**切换时单元怎么迁移**(`service/episode-structure.ts relayoutUnits`,同一事务): + +1. 每个未退役的集单元先求「TMDB 原季集」:单元上记的 `tmdbSeason / tmdbEpisode` 优先;否则按当前结构换算(默认季 = 自身季集;剧集组 = 当前组里这个位置的原季集)。 +2. 按目标结构把原季集换成新季集,**原地**改单元的季号、集号、所属季单元。单元 id 不变,资产覆盖、资源、下载计划、订阅覆盖、字段覆盖等引用全部跟着走,文件不用重新认。 +3. 编号人工锁定的单元不动;求不出原季集、或目标结构里没有这一集(比如剧集组不含特别篇)的单元保留原号、标临时(来源记为资源),切换结果里逐条列出(含有没有被引用)。同季同集已有对上的单元时,随后的元数据刷新按重复单元合并(DH-1,引用并到对上的那条)。 +4. 季单元:目标结构的每季确保有季单元(同号沿用,临时的转正),总集数直接改成新结构的集数并解除锁定;不在目标结构里的季单元标临时、清总集数(订阅等引用不动)。 +5. 记下选择 → 按作品**全量**重算已绑定资源(`reresolveEntityEpisodes`)→ 提交后 full 刷新元数据(季 / 集标题、播出日期、原季集对照按新结构写入;写进 NFO 的字段变了按原规则排「更新」重刮)。 + +切回默认季同样按这几步,单元回到原号、原季集对照清掉、特别篇季转正。已整理的文件路径不随之改名(S01E14 的文件现在对应 S02E02 单元),要改名用「重新整理」;刮削写的 NFO 季集号、标题、简介随单元走。 + +**集数映射**:季表就是组的季集,校验、换算、规则都按组;规则的目标季集写组内的(作品选了剧集组时,`EP-12 → 第 2 季` 指组的第 2 季)。额外一步换算 **按 TMDB 默认季集**(方式 `tmdb_season`):季表带原季集对照(`SeasonTable.origins`),发布者按 TMDB 默认季编号(S01E14)而原号对不上组的季表时,换到组内季集(S02E02);整段区间要落在同一季且连续。原号对得上时原号优先。集数映射分区的季表摘要会标「按剧集组「X」分季」。 + +**刷新**:剧集组作品的元数据刷新拉 `/tv/episode_group/{id}` 而不是默认季;TMDB 编辑调整过组内顺序时,集单元按记下的原季集挪到新位置(规则同切换)。组取不到(404 / 网络)时本次不动季集结构,只刷作品字段。刮削缺季集元数据时也按组取。 + +**没做**:识别词 / 单条资源指定剧集组(MP 的 `g=`)、整理时按剧集组(MP `TransferInfo.episode_group`)——季集结构是作品级的一个选择。 + ## 池的治理 - 复查有上限:`unmatched` 每天一次本地召回,最多 3 次;查过 TMDB 的按原退避(无结果 1h 起、查询失败 15min 起,翻倍到 7 天,最多 8 次)。