- 新表 identify_words(0094):全局一块 + 每部作品一块原文,带版本与审计;episode_mapping_rule 转成文本行后删表
- 全局识别词移植 MP WordsMatcher(屏蔽 / 替换 / 集数偏移 / 替换+偏移 / {[tmdbid=…]}),所有按标题 / 文件名识别的地方解析前套用,命中的行记在解析结果上;保存后后台按改动行重新识别
- 作品识别词:S02 >> S01 EP+24 / S01E13 >> S00E01 / S01 @组 >> 忽略,解析器 + 打印器 + 编译;发布组繁简视为同一个
- 去掉规则自动停用:算出来对不上季表只待确认,要停用就注释掉那一行
- 集数待确认的采用 / 忽略与智能体 apply_rule 改为往作品识别词追加一行(带来历注释)
- 编辑器可视化 / 文本两种模式改同一份原文,保存前试算;作品页「集数映射」用同一个编辑器
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Cxwgnn2pbzQ7rXv3rq7fei
97 lines
8.5 KiB
Markdown
97 lines
8.5 KiB
Markdown
# 识别词
|
|
|
|
2026-10-08 定。识别词是给识别用的文本规则,分两级,都在规则页「识别词」页签里看:
|
|
|
|
- **全局识别词**:一块文本,写法与 MoviePilot 的「自定义识别词」一字不差,可以把 MP 的列表直接粘进来。识别前先套到标题与副标题上。
|
|
- **作品识别词**:每部作品一块文本,写这部作品的季集换算(取代原来散落在作品页的结构化映射规则)。作品页「集数映射」里改的是同一份。
|
|
|
|
规则照写的用:作品识别词算出来对不上季表的资源保持集数待确认,在预览里标「对不上」,不会自动停用那一行。要停用某一行就把它注释掉。
|
|
|
|
## 存储
|
|
|
|
表 `identify_words`(迁移 0094):`media_entity_id` 为空的一行是全局,其余每部作品一行;`text` 存原文(注释、空行原样保留),`version` 每次保存 +1,`updated_by_*` 记最后修改者,`reresolve` 是全局识别词保存后的后台重算进度。保存要带读到的版本(`expectedVersion`),别处先改过返回 409;每次保存写审计 `identify_words.updated`(增删的生效行,各最多 50 行)。
|
|
|
|
作品识别词在用的时候现场编译成规则(`compileEntityWords`,contracts),不另存结构化规则。0094 把旧表 `episode_mapping_rule` 逐作品转成文本行后删表:同季、同换算的多个发布组合成一行,单集对照一行一条,停用的变成注释行,智能体写的上方加一行注释;已有文本时只追加还没有的行。
|
|
|
|
## 全局识别词(MP 语法)
|
|
|
|
逐行处理,前一行的结果交给后一行。每行四种写法之一,`#` 开头是注释,空行跳过:
|
|
|
|
| 写法 | 含义 |
|
|
| --- | --- |
|
|
| `国语中字` | 屏蔽词:整行当正则,从标题里删掉 |
|
|
| `A => B` | 替换:`A` 是正则,`B` 可以用 `\1`、`\g<name>` 引用分组,留空就是删掉 |
|
|
| `前 <> 后 >> EP±N` | 集数偏移:前后定位词(正则)之间的数字按表达式换算;前导零保留宽度,中文数字仍写中文 |
|
|
| `A => B && 前 <> 后 >> 偏移` | 替换 + 偏移:替换成功才偏移 |
|
|
|
|
- 标题里偏移不了时偏移副标题:副标题里能直接定位就偏移副标题;定位词出现在「标题 + 副标题」里时只改副标题中会被识别成集数的数字(`第8集`、`Episode 8`、`E08`、`1-12 完结`),不动季号和年份。
|
|
- 替换词里带 `{[tmdbid=…;type=tv|movie;s=季;e=集]}` 时,解析器把它当显式身份标签:识别直接用这个 TMDB 作品,`s` / `e` 指定季集,`g=` 指定剧集组。
|
|
- 偏移表达式只允许数字、`EP`、括号和 `+ - * / // %`,结果取整;`2EP`、`EP(1)` 这类省略运算符的写法报错;`EP` 区分大小写;不含 `EP` 的表达式算出常数(与 MP 一致)。
|
|
- 正则按 Python 写法:`(?P<n>…)`、`(?P=n)`、开头的 `(?i)(?s)(?m)`、`\A` / `\Z`、`\p{Han}` 等脚本名都转成 JS 写法再编译。
|
|
- 写错的行(正则编译不了、偏移表达式不合法)编辑器里标红,但可以保存,套用时跳过这一行(MP 也是套用时才失败)。「替换 + 偏移」的偏移写错时替换照做、这一行不算命中,与 MP 一致。
|
|
- 命中的行记在解析结果上(`parsed.words.applied`,另记套用前的原标题),集数映射预览、全局试算里显示「命中识别词」。
|
|
|
|
实现:语法与 Python 正则 / 替换模板 / 算术在 `packages/contracts/src/identify-words-syntax.ts`,套用引擎移植 MP `WordsMatcher` 在 `packages/domain/src/identify-words/apply.ts`,`parseRelease(title, { words })` 解析前先套用(`parseReleasePath` 对文件、上级、上上级目录各自套用)。MP 用例移植在 `packages/domain/test/identify-words.test.ts`。
|
|
|
|
### 在哪里生效
|
|
|
|
服务端进程内缓存全局识别词(`identify-words-cache.ts`,5 秒按版本核对一次,本进程保存后立即失效),这些地方解析前都套用:
|
|
|
|
- 资源识别:站点列表抓取落库、RSS / 列表 / 搜索来的资源识别(`resolveRelease`)、手动指定作品、整理元数据补全、集数待确认的同组判断
|
|
- 搜索结果的快速筛选、规则组 / 收录版本的试算
|
|
- 下载:季包选文件时对种子里的文件名
|
|
- 整理:整理计划里每个文件(文件名改了季集时把覆盖值交给命名)、接管下载器里已有的任务
|
|
- 文件管理:识别、推荐文件名
|
|
- 刮削:未登记文件的季集
|
|
- 集数映射:按作品重算、预览、智能体的上下文包
|
|
|
|
### 保存后重算
|
|
|
|
保存时比较生效行(注释、空行之外的行)的增减。有增减才排后台任务 `reresolveIdentifyWords`:每批 500 条扫全部资源,标题 / 副标题被某个增减的行单独命中的,入队 `resolveObservation`(强制重新识别,身份与集数都重来,人工锁定的跳过)。进度写回 `identify_words.reresolve`,规则页每 3 秒刷一次;更新的保存会取代还在跑的那一版。
|
|
|
|
## 作品识别词
|
|
|
|
一行一条,`#` 开头是注释,空行跳过。三种写法:
|
|
|
|
```
|
|
S<来源季> [@发布组 …] >> [S<目标季>] <集号表达式> 季集换算
|
|
S<季>E<集>[-E<集>] [@发布组 …] >> S<季>E<集> 单集对照
|
|
S<来源季> [@发布组 …] >> 特别篇 | 花絮 | 忽略 整季另作处理
|
|
```
|
|
|
|
- 来源季写 `*`(或 `S*`)表示任意季;单集对照不写季(`E05 >> S00E02`)同样是任意季。
|
|
- 目标季可省,省略 = 来源季;只写目标季(`S02 >> S01`)= 集号不变。
|
|
- 集号表达式与全局识别词同一套算术,`EP` 不分大小写,必须出现 `EP`;`EP` 单独写 = 集号不变。打印时规范成大写、去空格(`ep - 12` → `EP-12`)。
|
|
- 发布组前面加 `@`,可以写多个;带空格的写成 `@"Some Group"`。比较时不分大小写,繁简写法算同一个(`綠茶字幕組` = `绿茶字幕组`)。
|
|
- 单集对照可以多行,同一范围合成一张对照表,区间不能重叠;区间里每一集都要在表里,目标要在同一季且连续。
|
|
- 同一范围(季 + 发布组)只能有一行换算或整季处理;一行多个发布组拆成多条规则。冲突、写错都按行号报出来,有错不能保存。
|
|
|
|
药屋少女的呢喃(TMDB 只有一季,后面的季接着编号):
|
|
|
|
```
|
|
# 药屋少女的呢喃:TMDB 只有一季,第二、三季接着第一季编号
|
|
S02 >> S01 EP+24
|
|
S03 >> S01 EP+48
|
|
S03 @ADWeb @MWeb @CHDWEB @黒ネズミたち @Judas @绿茶字幕组 @綠茶字幕組 >> S01 EP
|
|
```
|
|
|
|
### 优先级
|
|
|
|
资源的(来源季, 发布组)找最具体的一条:季 + 发布组 > 只写季 > 只写发布组 > 任意。命中规则就只按它算,算出来对不上季表 → 待确认(不再回退到绝对集号 / 播出日期换算,也不停用这一行);没命中规则才走原号校验与确定性换算。解析结果记命中的行号与原文(`episode_resolution.ruleLine / ruleText`)。
|
|
|
|
### 保存与试算
|
|
|
|
编辑后自动试算(纯读,`POST /media-entities/{id}/identify-words/dry-run`):逐行错误、近期 100 条资源换算前后、仍对不上季表的条数。保存(`PUT /media-entities/{id}/identify-words`)在同一事务里按作品重算已绑定资源,返回重算 / 变化 / 待确认转已解决的条数。
|
|
|
|
集数待确认事项的「采用建议」「忽略并写忽略规则」、智能体的 `episode_mapping.apply_rule` 都是往作品识别词末尾追加一行,上方加一行注释写来历(日期、智能体会话、理由)。识别词里有同范围被注释掉的行(用户停用过)时智能体不再自动写。
|
|
|
|
## 编辑器
|
|
|
|
`components/identify-words/words-editor.tsx`,规则页「识别词」的全局区块、作品抽屉(`?open=<作品 id>`)和作品页「集数映射」用的是同一个组件:
|
|
|
|
- 「可视化 / 文本」两种模式改同一份原文,随时切换不丢内容,上次用的模式记在本机。
|
|
- 文本模式:等宽、行号、按语法着色,写错的行行号标红、整行浅红底;粘贴 MP 列表就是普通粘贴。
|
|
- 可视化模式:一行规则一张卡。先选类型(全局:屏蔽词 / 替换 / 集数偏移 / 替换 + 偏移 / 指定作品;作品:季集换算 / 单集对照 / 特别篇·花絮·忽略),字段按类型展开;集号换算有「不变 / +N / −N / 自定义表达式」;发布组是标签输入,提示这部作品资源里出现过的发布组。开关 = 注释掉与否,拖动(或焦点在手柄上按上下方向键)排序,备注写成上方的注释行,卡片下面拿近期资源(或自己给的标题)试这一行。
|
|
- 可视化表达不了的行(写错的)显示成只读的「原文」行,带错误和「在文本里改」,不会丢。
|
|
- 行模型 `wordsToRows` / `rowsToText`(contracts):紧贴规则上方、没有空行隔开的注释是它的备注;注释内容本身是一条规则的(全局里只认带运算符的)是停用的规则行;没改过的行原样拼回,往返逐字不变。
|