test(porting): differential tests against MoviePilot Rust-accelerated parsers
Snapshot differential tests (known deviations registered, current state passes): - title/path recognition: 216 samples vs MP MetaInfo/MetaInfoPath - filter rules: 42 rules x 28 torrents vs MP FilterModule - RSS/indexer parsing: 15 feeds, 72 indexer samples - zhconv: 69 samples Generator: scripts/mp-sync/gen-differential.py; report: docs/porting/mp-rust-logic-parity.md Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
b86f75c011
commit
591cc78c7d
44 files changed
+32206
No files matched your search
@@ -0,0 +1,222 @@
|
||||
# MoviePilot Rust 加速函数:产品逻辑迁移核对(差分测试)
|
||||
|
||||
> 上游 `/opt/moviepilot-plus/upstream/MoviePilot` @ `e6820a8`,`moviepilot_rust` 0.3.3。函数清单见 `moviepilot-rust-transfer.md` §1。
|
||||
> 方法:用 MP 自己的 venv 调用上游实现生成期望值(`scripts/mp-sync/gen-differential.py`),我们的 TS 实现跑同样的输入,逐字段比较。已知差异写进快照的 `deviations`,测试断言"实际差异集合等于登记集合"。所以现在测试是通过的;以后补迁消掉一条差异,测试会失败,提醒删掉对应登记。
|
||||
> 差异分类:**一致** / **intentional**(故意偏离,README 或 porting 文档有记录)/ **missing**(漏迁)/ **our_bug**(疑似我们的 bug)/ **mp_bug**(疑似 MP 的缺陷,我们的结果更好)/ **representation**(只是表示方式不同)/ **not_expressible**(Slot 表达不了)。
|
||||
|
||||
## 0. 结论
|
||||
|
||||
| Rust 函数 | 业务链路 | 差分规模 | 一致率 | missing / our_bug / mp_bug |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| `parse_metainfo_fast` / `parse_metainfo_path_fast` | 搜索/订阅/RSS 的标题识别;整理规划(MetaInfoPath);过滤器按集算体积 | 216 条(标题 186、路径 30)× 18 字段 | 字段级 92.9%;扣掉 intentional/representation/mp_bug 后 97.5%;整条完全一致 84 条(38.9%),产品等价 177 条(81.9%) | 14 条 / 27 条 / 28 条样本 |
|
||||
| `find_metainfo_fast` | 标题中的显式 ID 标签 | 11 条带标签 | 8/11 | 2 / 0 / 1 |
|
||||
| `parse_filter_rule_fast` / `filter_torrents(_with_trace)_fast` | 搜索、订阅匹配、洗版、工作流过滤、下载排序 | 43 条规则;28 种子 × 23 条可完整表达规则 = 644 case | 95.5%(615/644);另有 3 条只能近似表达的规则,84 case 里 31 处不一致,全是能力缺口 | 26 / 14 / 7(另 not_expressible 13) |
|
||||
| `parse_rss_items_fast` | 订阅刷新(站点 RSS 模式)、工作流 fetch_rss、插件 | 15 份 Feed / 36 条,245 字段 | 95.5%(234/245) | 0 / 0 / 4(另 intentional 12) |
|
||||
| `parse_indexer_torrents_fast` / `parse_indexer_subtitles_fast` | 站点搜索/浏览(SiteSpider.parse);字幕搜索 | 72 样本 / 127 行,2108 字段 | 98.1%(2069/2108) | 1(字幕分支)/ 4 / 35 |
|
||||
| `zhconv_fast` | 识别查询名简体化、中文别名判定、音乐元数据 | 69 条 | 84.1%(58/69) | 10 / 0 / 1 |
|
||||
| `jieba_cut` | 仅用于"整理历史按标题模糊搜索"(含 Agent 工具) | 静态对照 | — | 不需要迁 |
|
||||
|
||||
MP 自身两条路径也不一致:标题识别的 Python 实现和 Rust 快路径在 4 条样本上输出不同(`meta-mp.json#rustMismatch`:T-06、A-03、Z-06、Z-08);索引解析在 **40/72** 个样本上不同(`indexer.json` 的 `mp_rust`);RSS 的 Atom/RDF 命名空间也不同(Python 0 条,Rust 有结果)。过滤器的 Python 和 Rust 交叉核对 0 处不一致。我们的对照基准一律用 **Python 参考实现**。
|
||||
|
||||
## 1. 产物与复现
|
||||
|
||||
| 文件 | 内容 |
|
||||
| --- | --- |
|
||||
| `scripts/mp-sync/gen-differential.py {meta,zhconv,filter,rss,indexer,all}` | 生成器,必须用 `$MP_ROOT/.venv/bin/python` 运行。只读调用上游,不复制上游源码 |
|
||||
| `packages/domain/test/differential/meta-{samples,mp,deviations}.json` + `meta-map.ts` + `meta.test.ts` | 标题识别。`meta-map.ts` 把双方输出规范化到同一形式(名称忽略大小写和标点、来源归到 REMUX/BluRay/WEB-DL/…、HDR 集合、编码族、音频去分隔符、发布组集合) |
|
||||
| `packages/domain/test/differential/filter-cases.json` + `filter.test.ts` | 过滤规则 |
|
||||
| `packages/domain/test/differential/zhconv-*.json` + `zhconv.test.ts` | 繁简转换 |
|
||||
| `packages/adapters/test/differential/{rss,indexer}.{json,test.ts}`、`diff-util.ts`、`inputs/**`、`export-inputs.ts` | RSS 和索引解析(§4、§5)。生成前先跑 `deno run -A packages/adapters/test/differential/export-inputs.ts` 导出 testkit fixture;生成器把时区固定为 UTC,并冻结 dateparser 的 now,重跑结果逐字节相同 |
|
||||
|
||||
样本来源:我们已有的 107 条(`release-parser.test.ts` 91 条标题加 16 条路径)、MP `tests/cases/meta.py` 的 76 条(只用标题字符串作输入语料,属于事实数据;这份语料的来源**待登记进 ledger**)、补充 33 条(综艺、动漫、多集、合集、国配、特效字幕、显式 ID)。
|
||||
|
||||
## 2. parse_metainfo_fast / parse_metainfo_path_fast(标题与路径识别)
|
||||
|
||||
**用途**:`MetaInfo()` 用于种子标题、站点副标题和文件名,在搜索、订阅匹配、RSS 刷新、过滤器按集算体积(`total_episode`)时调用。`MetaInfoPath()` 用于整理链路(`transfer/request.py _build_path_meta`)。Rust 只是快路径,返回 None 时回退 Python。
|
||||
|
||||
**分组结果**(216 条):
|
||||
|
||||
| 来源 | 条数 | 完全一致 | 产品等价 | 说明 |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| 我们已有样本 | 107 | 46 | 106 | 差异几乎都是 README 登记的故意偏离 |
|
||||
| MP 自带用例 | 76 | 29 | 43 | **our_bug 65 处里有 57 处在这里**,主要是动漫/字幕组命名 |
|
||||
| 补充 | 33 | 9 | 28 | — |
|
||||
|
||||
按字段看一致率:resource_effect 和 edition 100%;part 99.1%;year 98.6%;video_encode 96.8%;audio_encode 96.3%;begin_episode 89.8%;type 88.9%;begin_season 84.3%;name 81.9%;resource_team 72.7%(59 处差异中 52 处是故意兜底)。`customization` 双方都没配置,不参与比较。
|
||||
|
||||
### 2.1 our_bug(27 条样本,按根因归并)
|
||||
|
||||
| # | 输入(节选) | MP | 我们 | 建议 |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| B1 | `【爪爪字幕组】★7月新番[欢迎来到…教室 第二季/…]`(MP-03/17/18/20/32/45/56/63) | cn=`欢迎来到实力至上主义的教室` | cn=`★7月新番欢迎来到…` / `国漫寻剑` / `22年日剧据…` | 剥离动漫前缀标签:`★N月新番★`、`N年日剧`、`国漫`、`新番` |
|
||||
| B2 | `[桜都字幕组] 异世界归来的舅舅 / Isekai Ojisan [01][1080p]`(MP-44/45、Z-07/27) | E=1,T=TV | en=`Isekai Ojisan 01`,无集号 | 名称后紧跟的 `[NN]` / `[NN][分辨率]` 识别为集号(我们已支持 `[Group][Name][05]`,不支持 `Name [05]`) |
|
||||
| B3 | `[秋叶原冥途战争][Akiba Maid Sensou][2022]…[LeagueWEB]`(MP-05/13/27/57) | grp=LeagueWEB | grp=`秋叶原冥途战争` | 开头方括号含中文或能解析成片名时不当作组;末尾 `[已知组]` 优先 |
|
||||
| B4 | `[Caligula.The.Ultimate.Cut.2023.2160p…-BHYS@OurBits][DIY中字原盘][…].iso`(MP-74) | 正常解析出 en、y、2160p、UHD BluRay、HEVC、DTS-HD MA 5.1 | 名称取成 `DIY`,技术字段全丢 | 迁 MP 的 `BRACKET_DOT_TITLE_RE`:方括号内是"点分隔发布名 + 年份 + 资源词"时去括号保留内容 |
|
||||
| B5 | `The Long Season 2017 2160p…`(MP-01) | en=`The Long Season` y=2017 | 第 2017 季,年份被吞 | `Season` 后的数字 ≥1900 时按年份处理(MP 限制 Season 后 <3 位) |
|
||||
| B6 | `2022.8.2.Twelve.Monkeys.1995…`(MP-52),父目录 `…Collection.20240501.FRDS`(MP-73) | 电影 | 按 airDate 判成 TV | 标题**开头**的日期、以及和年份同时出现的日期,不当作播出日期;父目录的 airDate 不参与类型合并 |
|
||||
| B7 | `[YTS.MX] …`(MP-76)、`[Hall_of_C] 诛仙 …`(MP-60) | 名称干净 | 组名混进英文名 | 开头方括号里是单个 token(含 `.`/`_`)时当作组名剥离 |
|
||||
| B8 | `组长女儿与照料专员 / 组长女儿与保姆 …`(MP-21);`传颂之物/Utawarerumono/うたわれるもの`(MP-55) | 取其一 | 两个中文名被拼接,或三段没拆开 | `/` 分隔的多名称按语言拆分,中文取一个 |
|
||||
| B9 | `哆啦A梦:大雄的宇宙小战争`(MP-06) | cn 含 A | `A` 被拆成英文名 | 中文词中夹的单个拉丁字母并入中文名 |
|
||||
| B10 | `[Gal to Kyouryuu][02][BDRIP]`(MP-24) | en=`Gal To Kyouryuu` | en=`BDRIP` | 资源词不能当名称 |
|
||||
| B11 | `DTS-HD MA5.1`(MP-48)、`E13-14`(MP-51)、`【极影字幕社】…GB`(MP-63) | `DTS-HD MA 5.1`、E13-14、组=极影字幕社 | `DTSHD`、只有 E13、组缺失且 GB 进了名称 | 各补一条词元规则 |
|
||||
|
||||
### 2.2 missing(14 条样本)
|
||||
|
||||
| # | 输入 | MP | 我们 | 建议 |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| M1 | `{[tmdbid=94997;type=tv;s=1;e=3-4]}House.of.the.Dragon…`(Z-31) | tmdb:94997,S1,E3-4,TV | 整块进名称 | 迁 `{[k=v;…]}` 显式身份块(也见 §3) |
|
||||
| M2 | `[bangumiid=400602]`(Z-32) | bangumi:400602 | 无 | ExplicitId 增加 bangumi/anilist |
|
||||
| M3 | `#13`(MP-25)、`[TV 04]`(MP-57)、`西部世界 12.mkv`(MP-29)、`S01 13-14`(MP-50) | 能识别集号 | 集号为空 | 补充 4 种集号写法 |
|
||||
| M4 | `Mai Xiang S01 …`,副标题 `麦香 \| 全36集`(MP-64) | cn=麦香 | 无中文名 | 迁"英文名是拼音时从副标题第一个词取中文名" |
|
||||
| M5 | `[1080]`(MP-05)、`[5.1]`(MP-12) | 1080p、5.1 | 空 | 补方括号内的裸分辨率和裸声道 |
|
||||
| M6 | `iP`(T-13)、`B-Global`(MP-10) | BBC iPlayer、B-Global | 空 | 流媒体平台表补齐(MP 有约 270 条) |
|
||||
| M7 | `cXcY@FRDS`(MP-49/54)、`指环王三部曲合集`(Z-12) | 两个组都识别;去掉"合集" | 只有 FRDS;保留"合集" | 支持 `£` 分隔符;"合集/三部曲合集"列为干扰词 |
|
||||
|
||||
### 2.3 intentional(与 README 偏离表一致,125 处)
|
||||
|
||||
未知发布组兜底 52 条;第 N 期记为 `issue` 12 条;有集号无季号时推定第 1 季 23 条;播出日期 → airDate 且判为 TV 6 条;路径合并(文件无剧集信号、目录是剧集)3 条;辅助词字幕文件(P-07);附加内容目录(P-05);`CD1`/`D02` 作为 part;`第八季 全12期` 保留季号;先导片。**每一条都能在 `packages/domain/README.md` 的偏离表或 `moviepilot-metadata-recognition.md` 里找到依据,没有发现未经记录的故意偏离。**
|
||||
|
||||
### 2.4 mp_bug(28 条样本,保留我们的行为)
|
||||
|
||||
- MetaAnime(anitopy 路径)不输出来源和编码(15 条)。
|
||||
- 不认 `1x05`、`S01D02`、`Season.2`、`BD50`、`{doubanid=}`。
|
||||
- 丢中文名(MP-13/44/55、Z-07)。
|
||||
- 把 `[Sub]` 当成名称。
|
||||
- `AC3£` 没切开,音频丢失。
|
||||
- 把 `7³` 拼进音频。
|
||||
- `AVC-8bit` 的结果 Python 和 Rust 不一致(Z-06)。
|
||||
|
||||
representation 共 10 处:`name` 的取法(我们有中文名就取中文,MP 只在中文名全是汉字时才取);`HMAX` 映射成 Max 还是 HBO Max;`2Audio(s)`;`flac_ac3` 记成一条还是两条。
|
||||
|
||||
## 3. find_metainfo_fast(显式 ID 标签)
|
||||
|
||||
**用途**:从标题里剥离 `[tmdbid=…]`、`{tmdb-…}`、`{[k=v;…]}`、`[bangumiid=…]`,得到 media_source/media_id 以及季、集、类型。在 `_prepare_meta_input` 里调用。差分并入 §2 的 `media_id` 字段:8 条 Emby 风格的 TMDB 标签(`[tmdbid=]`、`[tmdbid-]`、`[tmdb=]`、`{tmdb=}`、`{tmdb-}`)**全部一致**。不一致的 3 条:M1、M2 是漏迁;`{doubanid=}` 是 mp_bug,我们能认。另外我们多支持 imdb 和 tvdb,MP 不认。**建议**:把 `{[…]}` 块(含 `type/s/e/g`)和 bangumi 补上。按不变量 1,它们仍只作为证据加一级,不直接确认身份。
|
||||
|
||||
## 4. parse_rss_items_fast(RSS/Atom)
|
||||
|
||||
**用途**:`RssHelper._parse_impl`(`application/rss.py`)先走 Rust(最多 1001 条,截到 1000),返回 None 时回退 lxml。调用方:`TorrentsChain.rss(domain)`(订阅刷新的站点 RSS 模式)、工作流 `fetch_rss`、插件。解析失败时触发 `__renew_rss_url` 重新生成地址。
|
||||
|
||||
**MP 字段语义**:title 取第一个后代 `title` 的文本(不 strip,为空就跳过);description 取 `description|summary`;link 取文本,没有就取 href;enclosure 取 `@url`,缺失回退到 link;**size 只取 `enclosure@length` 的纯数字**(description 里的大小、Torznab/nyaa 的 size 都不读);pubDate/published/updated 用 `dateutil.parse` 后 `astimezone(None)`(不带时区的按宿主本地时区);`dc:creator` 输出为 nickname;不输出 guid 和 category。促销、HR、做种这些字段 RSS 路径都没有。
|
||||
|
||||
**差分**:15 份输入(testkit 2 份、手写 13 份),MP Python 解析出 36 条,逐字段比较标题、描述、链接、enclosure、大小、pubdate、作者,共 245 个字段,一致 234 个。登记 16 条(`packages/adapters/test/differential/rss.json#deviations`):
|
||||
|
||||
| 输入 | MP | 我们 | 分类 | 建议 |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| 带 xmlns 的标准 Atom、RSS 1.0(RDF) | Python 0 条(Rust 有) | 3 条 / 1 条 | mp_bug | 保持 |
|
||||
| 纯文本"RSS 已过期"提示 | 在"不以 `<` 开头"处就提前返回,过期判断走不到,续期永远不触发 | `FeedError('expired')` | mp_bug | 保持;续期逻辑我们自己做 |
|
||||
| 条目内 `<source><title>` 排在前面 | 取到来源标题 | 取直接子元素 `title` | mp_bug | 保持 |
|
||||
| enclosure 带 passkey | 原样 | `[REDACTED]` | intentional(SA5) | — |
|
||||
| 带 DTD | `[]` | 整份拒绝 `forbidden_dtd` | intentional | — |
|
||||
| 值里有换行缩进、`length=" 2048 "` | 保留空白,大小为 0 | trim 后大小为 2048 | intentional | 在 `rss/README` 补登记 |
|
||||
| ` ` 实体 | 在此截断,标题变成 "Nbsp" | 保留字面量 | intentional | P3:补 HTML 命名实体解码表 |
|
||||
| 未闭合标签 | 容错恢复 2 条 | 1 条 | intentional | — |
|
||||
| pubDate `… CST`、纯日期 `2026-09-26` | 按宿主本地时区解释 | 按美国中部 -6 / UTC 午夜 | intentional(两边都可能错) | P2:不带时区的时间按站点 `timezone` 解释 |
|
||||
|
||||
RFC822、ISO、无秒、星期写错这几类两边一致;中文日期、"3 小时前"、纯数字两边都不解析。
|
||||
|
||||
## 5. parse_indexer_torrents_fast / parse_indexer_subtitles_fast(站点列表页)
|
||||
|
||||
**用途**:`SiteSpider.parse`(`modules/indexer/spider/__init__.py`)先走 Rust,返回 None 时回退 PyQuery 的 `get_info`。链路是 `IndexerModule.search_torrents`(关键词搜索)和 `refresh_torrents`(浏览 `browse.list/fields`);字幕分支 `search_type=="subtitles"` 走 `get_subtitle_info` 和 `__fill_subtitle_ids`,由 `/api/search` 字幕接口经 `SearchChain.search_subtitles_*` 调用。
|
||||
|
||||
**MP 字段语义**:size 用 `parse_size`(二进制单位,Python 银行家舍入);做种、下载、完成数取 `/` 前的部分,去掉逗号,不是纯数字时为 0;促销 case 按声明顺序取第一个命中(另有 selector 模式,用 `int()`);freedate 是原样文本;HR 配置了就输出 bool;category 投影成电影/电视剧/音乐/未知;pubdate 是站点墙上时间;imdbid 用 `re_search`;labels 是列表。
|
||||
|
||||
**能否独立调用**:`SiteSpider(indexer).parse(html)` 和 `RssHelper` 注入假 HTTP 端口后可以离线调用。**但 MP 的内置站点定义调不出来**:它们在 Cython `app/application/site/sites.*.so` 和加密的 `user.sites.v3.bin` 里,未认证时 `get_indexer()` 返回 `{}`,`SitesHelper()` 还依赖数据库事务执行器。所以双方共用我们的 `definitions/*.json`(与 MP 同一 schema,来自 MovieClaw)作为输入,**MP 真实站点的选择器只能静态对照**。
|
||||
|
||||
**差分**:72 个样本(21 个 NexusPHP 定义 × 3 个列表页、2 个登录页、3 个手写 DSL 配置、1 个字幕页),MP 解析出 127 行,比较 title、description、size、seeders、peers、grabs、促销系数、freedate、HR、分类、pubdate、链接、imdbid、labels 共 2108 个字段,一致 2069 个。登记 41 条(`indexer.json#deviations`):
|
||||
|
||||
| 输入 | MP | 我们 | 分类 | 建议 |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| hdarea、hddolby、hdfans 等 9 个定义的列表选择器命中表头行 | Jinja 把 None 渲染成标题 "None" | 空串 | mp_bug ×32 | 两边都会产出垃圾行:**收紧这 9 个定义** |
|
||||
| 相对时间 "2天"、"3小时前" | 用 dateparser 按当前时刻换算,结果随抓取时间漂移 | 不写 pubdate | mp_bug | 保持(生成时冻结了 now) |
|
||||
| 促销 selector 取到 "0.5" | `int()` 报错,整行丢失 | 保留 | intentional(SA4) | — |
|
||||
| `"2.5 B"` | 2(银行家舍入) | 3 | our_bug | P3:10 分钟 |
|
||||
| 行元素自身带 `free_bg`/`sticky` class | PyQuery 查询包含行自身,得到 dl=0、hr=true、labels | `.find` 只查后代 | our_bug ×3 | **P1:先决定跟 Python 还是 Rust**(MP 的 Rust 和我们一样),再改 `spider/spider.ts` 或在 README 登记 |
|
||||
| 字幕页 | 解析出 3 条 | 没有这个分支 | missing | P1:补字幕分支,期望数据已在 `indexer.json` |
|
||||
|
||||
**静态发现(差分之外)**:`table.torrents > tr:has(...)` 这种写法在显式带 `<tbody>` 的页面上,我们和 MP Python 都取到 0 行,MP Rust 能取到 5 行。受影响的定义有 agsvpt、chdbits、hdhome、keepfrds、ourbits、pter、pthome、ssd、tjupt。建议在 `scripts/mp-sync/movieclaw-overrides/` 统一补 `, table.torrents > tbody > tr` 这一支(audiences 已经这样写)。
|
||||
|
||||
## 6. parse_filter_rule_fast / filter_torrents(_with_trace)_fast(过滤规则)
|
||||
|
||||
**调用点**:`FilterModule.filter_torrents(rule_groups, torrents, mediainfo)`。先走 Rust,它返回 None 时回退 Python(规则解析在 `app/application/rules.py`,基于 pyparsing)。
|
||||
|
||||
- **搜索**:`chain/search/result.py`,按站点过滤。规则组用 `SearchFilterRuleGroups`。
|
||||
- **订阅匹配 / RSS 刷新**:`chain/subscribe/match.py`,每个种子单独过滤。规则组依次取 `subscribe.filter_groups`、`BestVersionFilterRuleGroups`(洗版)、`SubscribeFilterRuleGroups`。洗版时 `pri_order <= current_priority` 的种子会被拒;`current_priority == 100` 表示已完成。
|
||||
- **其他调用**:订阅搜索、工作流 filter 动作、规则测试接口、下载排序(把 pri_order 当排序键)。
|
||||
- **规则组选取**:按 `media_type` 和 `category` 选组;多个组之间是 AND 关系。
|
||||
|
||||
**语法**:用 `>` 切出优先层,第 1 层 pri_order=100,依次递减。层内运算优先级为 `!` > `&` > `|`,可以加括号。规则名不存在时视为不匹配。单条规则的判定顺序是:tmdb 条件命中就直接放行;然后在 `title+description+labels` 上跑 include/exclude 正则;接着检查 `size_range`(单位 MB,**按每集算**,即总大小除以 MetaInfo 得到的 `total_episode`)、`seeders`、`downloadvolumefactor`、`publish_time`(延迟 N 分钟)。
|
||||
|
||||
**内置关键字 → Slot**:
|
||||
|
||||
| 能表达 ✔ | 近似 ~ | 不能 ✗ |
|
||||
| --- | --- | --- |
|
||||
| `4K/1080P/720P` → `hard.resolutions`;`BLU/REMUX/WEBDL` 及其取反 → `sources` 允许列表加 `unknownPolicy`;`BLURAY & !BLU` → `requireEncode` | `CNSUB` → `requiredSubtitles:["zh"]`(缺华语片由 tmdb 放行这条);体积区间用总量而非每集;`A > B` 用多个 Slot 或 `rankForSlot`(按来源等级优先,不按层级) | `UHD`、`H265/H264`(只有软偏好)、`HDR/DOLBY/SDR`(只有软偏好)、`ATMOS/60FPS/3D`、`SPECSUB`(特效字幕)、`CNVOI/HKVOI`(国配/粤配;我们把"国语"当音轨词剔掉)、`GZ`(官方标签)、`FREE`、做种数、`publish_time`、跨字段 OR、音乐的 13 条规则 |
|
||||
|
||||
**43 条规则样例的结论**:✔ 22、~ 6、✗ 15。完整表格见 `filter-cases.json#rules`,其中 `verdict` 和 `note` 是逐条结论。有代表性的几条:
|
||||
|
||||
- `4K & !REMUX > 1080P & WEBDL` ✔,用两层 Slot 表达。
|
||||
- `SPECSUB & CNSUB & !BLU` ~,SPECSUB 那部分丢了。
|
||||
- `!DOLBY & !REMUX` ~,只能表达 `!REMUX`。
|
||||
- `CNSUB | 4K` ✗,属于跨字段 OR。
|
||||
- `FREE`、`SEED5`、`GZ` ✗,这些是站点元数据。
|
||||
|
||||
**差异**(不一致共 61 处已登记,完整在 `filter-cases.json#deviations`):
|
||||
|
||||
| 输入 | MP | 我们 | 分类 | 建议 |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| `2160p UHD Blu-ray HEVC … -BeyondHD`、`1080p Blu-ray AVC DTS-HD MA` | BLU(原盘) | BluRay 压制(isEncode) | our_bug ×12 | Blu-ray 加 AVC/HEVC/VC-1、且没有 x26x 时,判为 `BluRay-Disc` |
|
||||
| 副标题 `CHS/ENG` | 中字 | 不识别 | our_bug ×2 | 字幕词元补 CHS/CHT/GB/BIG5 |
|
||||
| `S03 全10集 60G` 配体积规则 | 按每集 6G 判 | 按总量 60G 判 | missing ×8 | Slot 加 `sizeBasis: per_episode` |
|
||||
| 华语片 + CNSUB | tmdb 放行 18 个种子 | 拒绝 | missing | 允许条件依赖媒体上下文 |
|
||||
| `2160p.HDR.HEVC-NOSRC`(没有来源) | 误命中 BLU | 来源未知 | mp_bug ×5 | 保持我们的做法 |
|
||||
| 副标题只有 `国语` | 算作中字 | 当作音轨 | mp_bug ×2 | 保持;以后另加 audioLang 条件 |
|
||||
| 单 Slot 的 `4K > 1080P > 720P` | 4K 总排在前面 | 先比来源等级,共 44 个逆序对 | intentional | 要复现 MP 的语义,就用多层 Slot 或优先级阶梯 |
|
||||
|
||||
**trace 对比**:MP 的 `filter_torrents_with_trace_fast` 返回 `([(index, pri_order)], [str])`,trace 是中文自由文本,只写进 debug 日志。例如 `种子 X 不包含任何项 [...]`、`大小 5G 不在范围 10000-50000MB`、`做种人数 3 小于 5`、`规则 X 不存在`。它不区分"字段未知"和"不满足";OR 分支里失败的原子也会被打印出来。我们的 `SlotEvaluation` 是结构化的:`violations[{field, code, expected, actual}]` 会列出全部违规,`unknown_field` 单独给出,accept 时带 `tier/softRank/reasons`。**缺两样**:命中的是第几层(对应 pri_order),以及逐层的失败原因。
|
||||
|
||||
## 7. zhconv_fast(繁简转换)
|
||||
|
||||
**用途**:`app/foundation/text.py convert()` 直接调用 `moviepilot_rust.zhconv_fast`,**没有 Python 回退**,原生扩展是硬依赖。调用点:
|
||||
|
||||
- TMDB/豆瓣/IMDb 识别时,把 `cn_name` 简体化后加入查询名(`themoviedb/__init__.py:285` 等)。
|
||||
- TMDB 中文别名的"是否已是简体"判定(`tmdbapi.py:981`)。
|
||||
- MetaAnime 分类标签判定。
|
||||
- 音乐元数据简体化。
|
||||
|
||||
用的是 MediaWiki 词表,**有词组级转换**。
|
||||
|
||||
**差分**:69 条(动漫、港台片名、一对多字)中 58 条一致。
|
||||
|
||||
- missing 10 条:其中 6 条是字表缺字(迴、滾、魷、瑯、臥、駭);4 条是一对多字(乾杯、瞭解、頭髮、理髮),我们刻意不收,MP 靠词组表处理。
|
||||
- mp_bug 1 条:`慶餘年` 转成了 `庆馀年`,官方简体片名是 `庆余年`。
|
||||
|
||||
**我们的替代**:`identity/zh-map.ts` 大约 930 对单字映射,只在 `normalizeTitle` 里用于召回比较。召回本来就会再经过 pg_trgm 相似度和别名表,一两个字不同不会直接失败。
|
||||
**建议**:暂不引入 `opencc-js`(约 1 MB 词典,MIT 许可,需要按需加载)。先把字表补到覆盖 TMDB 中文标题的常用繁体字(把本差分的 missing 清零,约 0.5 小时)。等到要把"简体化后的查询名"作为 TMDB 查询词时(对应 MP `_prepare_search_names`)再评估 opencc。它能提供词组转换和台湾/香港用词转换(如 蜘蛛人 → 蜘蛛侠),代价是包体积和冷启动。
|
||||
|
||||
## 8. jieba_cut(中文分词)
|
||||
|
||||
**用途**:只有两处,都是"整理历史按标题模糊搜索":`application/history.py:712` 用 `"%".join(jieba_cut(title))` 生成 SQL LIKE 模式,另一处是 Agent 工具 `query_transfer_history`。**不参与识别、匹配和过滤。**同样没有 Python 回退。
|
||||
**我们的替代**:资产和别名检索用 pg_trgm(`0004_content`、`0006_sources` 迁移)加 `normalizeTitle`。三元组相似度天然不依赖分词,对中文短标题的召回不比"分词后 LIKE"差。
|
||||
**结论**:**不引入 `jieba-wasm`**。它的代价是 wasm 约 4 MB、词典加载 100 ms 级,而收益只落在历史搜索这一个次要功能上。以后如果要做全文检索,优先考虑 PG 的 `zhparser` 或 `pg_bigm`,而不是在应用层分词。
|
||||
|
||||
## 9. 补迁清单(按优先级)
|
||||
|
||||
| 优先级 | 项 | 涉及文件 | 工作量 | 消除的登记 |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| P0 | 原盘判定:Blu-ray 加 AVC/HEVC/VC-1 且无 x26x → `BluRay-Disc`;`BDMV/BD25/50/66/100`、`COMPLETE BLURAY` | `release-parser/parse.ts` | 0.5 天 | filter our_bug ×12 |
|
||||
| P0 | 动漫/字幕组命名:前缀标签剥离(B1)、`Name [NN]` 集号(B2)、开头方括号是片名还是组名(B3、B7)、`/` 多名称拆分(B8) | `release-parser/parse.ts`、`release-groups.ts` | 1.5 天 | meta our_bug 约 40 处 |
|
||||
| P0 | `[点分隔发布名][…]` 去括号(B4);`Season 2017` 按年份处理(B5);开头日期和目录日期不当 airDate(B6) | `parse.ts`、`path.ts` | 0.5 天 | meta our_bug 约 15 处 |
|
||||
| P1 | Slot 硬条件扩展:`excludeSources`、hdr require/exclude、codec 硬条件、`sizeBasis: per_episode`、`requireUhd` | `slot/types.ts`、`slot/evaluate.ts` | 1 天 | filter missing ×8,规则可表达数 22 → 约 30 |
|
||||
| P1 | 字幕和音轨语言:CHS/CHT/GB/BIG5 识别为中字;国语、粤语作为 `audioLanguages`;特效字幕标记 | `parse.ts`、`types.ts`、Slot | 1 天 | filter our_bug ×2,SPECSUB/CNVOI 可表达 |
|
||||
| P1 | 显式身份块 `{[tmdbid;type;s;e;g]}`,以及 bangumi/anilist | `parse.ts`(`explicitIds`) | 0.5 天 | meta missing ×2 |
|
||||
| P1 | 优先级阶梯:`SlotSpec.tiers[]`,评估结果带 `level`,洗版时按 level 比较 | `slot/*`、server `acquisition.ts` | 1–1.5 天 | filter R05 intentional |
|
||||
| P2 | 集号写法 `#13`、`[TV 04]`、`名称 12.mkv`、`S01 13-14`;裸 `[1080]`、`[5.1]`;`MA5.1`、`E13-14` | `parse.ts` | 0.5 天 | meta missing/our_bug 约 10 处 |
|
||||
| P2 | 站点元数据条件:minSeeders、freeOnly、publishDelay、labels(需要改 `evaluateCandidate` 的签名) | `slot/*`、server | 1 天 | FREE/SEED/GZ 可表达 |
|
||||
| P2 | 流媒体平台表补齐;`£` 多组分隔;"合集"作干扰词;拼音英文名从副标题取中文名 | `parse.ts`、`release-groups.ts` | 0.5 天 | meta missing 约 6 处 |
|
||||
| P1 | 站点定义:9 个定义补 `tbody` 分支,9 个定义收紧表头 | `movieclaw-overrides/*.json` | 0.5 天 | indexer mp_bug ×32 的根因 |
|
||||
| P1 | 选择器是否包含行元素自身的语义(case/hr/labels) | `sites/spider/spider.ts` | 0.5 天 | indexer our_bug ×3 |
|
||||
| P1 | 字幕页解析分支(`get_subtitle_info`、`__fill_subtitle_ids`) | `spider/spider.ts`、`spider/schema.ts`、adapter 接口 | 1–1.5 天 | indexer missing ×1 |
|
||||
| P2 | RSS 中不带时区的时间按站点时区解释 | `rss/parse.ts` | 0.5 天 | rss intentional ×2 |
|
||||
| P3 | zh-map 补字(§7);`parseSizeMp` 改银行家舍入;RSS 补 HTML 命名实体 | `identity/zh-map.ts`、`sites/spider`、`rss/parse.ts` | 2 小时 | zhconv missing ×6、indexer our_bug ×1 |
|
||||
|
||||
**不迁**:MetaAnime 不输出来源和编码的行为(mp_bug);期号当集号、日期删除、未知组置空(与 README 偏离表一致);jieba。
|
||||
Reference in new issue
Block a user