- metadata.includeSpecials (default true): full refresh and the language backfill fetch TMDB season 0 (specials) when present, like MP; season 0 stays out of the episode-total ledger - refreshMetadata promotes provisional season/episode units that match the TMDB season/episode layout to confirmed (source=tmdb), keeping ids and all references; non-matching units stay provisional - content_unit.source (migration 0053): release / file / tmdb / user - duplicate units (same entity/kind/season/episode/content type/segment) are merged as a refresh step and via `scripts/identity-cleanup.ts --merge-duplicates` (dry-run by default); keeper = most referenced, then confirmed, then earliest; references auto-discovered via FKs and repointed - ensureUnits takes a per-unit advisory xact lock to stop concurrent lazy creation from producing duplicates - runbooks updated; user DB read-only dry-run: 征途 S01E09/E10 4 units each -> 1 each (6 removed), not applied Co-Authored-By: Claude Opus 5.5 <[email protected]>
7.5 KiB
7.5 KiB
元数据运行手册(MD-1:语言回退链 + 每集元数据 + 媒体详情)
语言回退链
- 设置 → 元数据 → TMDB →「语言回退链」,即
settings.metadata.languages,缺省zh-CN, zh-SG, zh-TW, zh-HK, en-US。 - 作品标题:按链逐个语言查 TMDB
translations(语言 + 地区精确匹配);该语言译名为空时查同地区、文字相符的alternative_titles(类型为空的优先);原始语言等于链中语言时原名也算。全部落空用请求语言下的 name / 原名。对照 MP__update_tmdbinfo_cn_title(中文名 → CN 别名 → SG 译名)。 - 中文名
titleZh:只在链里的中文语言中按顺序取(zh-CN 优先),值必须含汉字。链首改成 en-US 时标题变英文,titleZh不变。 - 简介 / 标语:按链取
translations的第一个非空值。 - 季 / 集:先按链首请求
/tv/{id}/season/{n};已播出的集缺标题(TMDB 占位名"第 N 集""Episode N")或缺简介时,按链逐个语言补拉同一端点,补齐即停;链走完仍没有标题的集保留占位名(语言记为空)。未播出的集不触发补拉。 - 每个字段的来源值写
metadata_field,language记实际取到的语言;同字段语言变了,旧语言的 tmdb 行删掉。 translations与alternative_titles写title_alias(来源 tmdb,标语言;别名只在地区与文字相符时标,如 CN + 汉字 → zh-CN)。已有同文本别名(繁简归一化后同键)不重复登记,只给语言为空的 tmdb 行补语言。
刷新
| 触发 | 拉哪些季的每集信息 | 新建集单元 |
|---|---|---|
POST /media-entities/{id}/refresh(定时追踪刷新同) |
追踪中订阅的季 ∪ 库里已有集单元的季(含季 0,最近 8 季) | 只在追踪中的季 |
POST /media-entities/{id}/refresh?full=1(详情页「刷新元数据」「更新每集信息」) |
全部季(最多 60);TMDB 有季 0 且 metadata.includeSpecials 开时含季 0 |
是 |
| 新建档:按 TMDB 候选建作品(订阅建档、搜索 / TMDB 预览页建档、整理向导)、手动指定未登记的 TMDB 身份 | 同 full=1(幂等键 refreshMetadata:created:{id}) |
是 |
- 新建档时标题 / 简介当场按语言链取(富详情失败沿用基础详情),每集信息由随后入队的 full 刷新补齐,不用手动刷新。自动识别(RSS)新建的作品不入队 full,仍按追踪刷新。
- 刷新是后台任务
refreshMetadata(输入{mediaEntityId, full?}),结果里titles是本次取到的标题 / 语言 / 中文名,episodes[]是各季写入统计(created新建单元数、changedUnits有变化的单元数)。 - 增量:来源值和语言都没变的字段不写;只投影有变化的单元。不改编号、路径绑定和资产覆盖。
- 季 0(特别篇,DH-1):设置 → 元数据 → TMDB「包含特别篇(季 0)」(
metadata.includeSpecials,缺省开)。开时 full 刷新和存量回刷 在 TMDB 有季 0 时一并拉取每集信息并建季 0 的季 / 集单元(对照 MP);季 0 不进正片季总集数账本,不影响订阅完成判定。关掉后 已建的季 0 单元保留,只是不再主动拉取。 - 临时转正式(DH-1):刷新时与 TMDB 季集结构对得上的临时(provisional)季 / 集单元转正式(
status = confirmed、source = tmdb), id 与所有引用不变;对不上的(季号不在 TMDB、集号超出该季集数)保持临时。只看本次拉了季详情的季(full 刷新即全部季)。content_unit.source(迁移 0053):release资源解析懒建、file整理 / 文件懒建、tmdb按 TMDB 建或转正、user人工;旧行为空。 - 重复单元合并(DH-1):每次刷新先把同作品、同类型、同季同集号、同内容类型与分段的多条未退役单元合并为一条(规则同
scripts/identity-cleanup.ts --merge-duplicates,见identity.md),结果里mergedUnits/promotedUnits是合并 / 转正数。 懒建单元(ensureUnits)按(作品, 类型, 季, 集)取事务级咨询锁,并发识别不再建出重复。 - 存量回刷(FIX-5):维护任务
metadata.backfill_languages「元数据语言回刷」在部署后首次启动即运行,对所有已确认 TMDB 身份 (active / provisional)的作品逐个跑full刷新。用独立的 TMDB 客户端串行请求,网络请求间隔 ≥ 500 ms(每秒最多 2 次), 请求照常走tmdb.requestPG 缓存(对照 MP@cached),中断后重跑时已取过的不再请求。进度每 5 秒写进本次运行记录的摘要, 结束写「N 部作品:刷新 / 跳过 / 失败;TMDB 请求 X 次」和前 5 条失败原因。跑完(有个别失败也算)任务自动停用; 计划页「立即执行」可再跑一次。TMDB 未配置时记为跳过、保持启用,第二天再试。只读对账模式下跳过。 代码:apps/server/src/modules/sources-identity/service/backfill-languages.ts。 - 单部作品补齐中文名 / 每集信息:对作品跑一次
full=1。手动批量:
# 所有有 TMDB 身份的作品各跑一次(TMDB 请求走缓存;数量多时分批)
curl -s -H "Authorization: Bearer $KEY" "$BASE/api/v1/media-entities?limit=200" | jq -r '.data.items[].id' |
while read -r id; do curl -s -X POST -H "Authorization: Bearer $KEY" "$BASE/api/v1/media-entities/$id/refresh?full=1"; done
集字段(字段级,可锁)
- 集单元字段:
name(投影到content_unit.title)、overview、airDate(投影到air_date)、runtime、stillPath、voteAverage、voteCount;季单元另有posterPath。最终值在content_unit.metadata,每个字段带来源 / 语言 / 时间 / 锁。 - 覆盖并锁定:
PUT /content-units/{id}/fields/{field}{value, lock?, reason?};取消:DELETE同路径。锁定的字段刷新不改。界面:媒体库详情 → 季与集 → 每集行右侧铅笔。
TMDB 请求与缓存
- 富详情一次请求:
/{type}/{id}?language=链首&append_to_response=alternative_titles,credits,external_ids,translations;季详情每语言一次。都走tmdb.requestPG 缓存(TTL / 空结果短 TTL / 条数上限见设置 → 元数据 → TMDB 缓存;对照 MP@cached:成功才缓存、业务失败不缓存)。 - 想立刻拿到 TMDB 刚改的数据:设置 → 元数据 → TMDB 缓存「清空」,再刷新。
- 仅 TMDB 作品详情页(
/console/media/tmdb/{type}/{id})的季在展开时拉GET /api/v1/media/tmdb/tv/{id}/season/{n},不落库。
媒体详情页
- 媒体库详情与仅 TMDB 作品详情共用
apps/web/app/components/media/media-detail.tsx:背景图(图片代理 w1280,scrim 遮罩)、海报、标题 / 原名 / 年份 / 类型 / 评分 / 状态 / 季集数、简介、外部链接,主创(导演 / 主创 + 演员前 10,头像 w185),季折叠 + 每集行(剧照 w300、集号与标题、播出日期、时长、评分、简介折叠、库内状态)。 - 库内状态:作品有订阅时取覆盖矩阵(可切换版本目标);没有订阅时只看是否已入库。默认展开订阅的季,否则库里有文件的最新季,否则最新季。
排查
| 现象 | 看哪里 |
|---|---|
| 标题仍是英文原名 | 作品详情「字段来源与锁」里 标题 的来源与语言;是否被锁;设置里的语言链;对作品跑 full=1 |
| 某集没有中文标题 | 该集在 TMDB 各语言都没有译名时保留占位名 / 英文名,属正常;可在每集行手动改并锁定 |
| 季里没有集 | 默认刷新不为未追踪的季建集单元,点「更新每集信息」(full) |
| 没有特别篇 | TMDB 该剧有没有季 0;设置 metadata.includeSpecials 是否关了;对作品跑 full=1 |
| 集还是「临时」 | 该季是否拉过季详情(跑 full=1);集号是否超出 TMDB 该季集数(对不上的保持临时,属正常) |