🔀 自定义映射
当媒体库里的番剧名称和 Bangumi 上的条目对不上时(译名不同、多季合并、特摄/纪录片、续作命名特殊等),程序可能无法自动找到正确的条目。此时可以在 「映射管理」 里为「某个标题」指定对应的 Bangumi 条目 ID,同步时会优先使用这条规则。
匹配优先级
程序在自动匹配时会按以下顺序查找自定义映射(命中即停止):
- 集数分段映射(同一条目标题下细分了集数区间)
- 季度感知精确映射(指定了季度的条目)
- 简单精确映射(标题完全一致,所有季度共用)
- 正则规则(按规则列表顺序依次尝试)
这四类都属于「自定义映射」,整体优先于本地资料库(bangumi-data)、本地归档与 Bangumi 在线搜索,是最高优先级。
⚠️ 先读:映射的威力与风险
自定义映射是最高优先级的规则,命中后程序会直接采用你指定的条目,不再做自动匹配。
这带来两个后果,请务必理解:
- 写错会「稳定地错」。程序不会再用搜索、本地归档等兜底手段去纠正它。一个配错的映射会让本来能自动匹配正确的条目变成错误,而且因为每次都很「稳定」,你很难察觉。
- 它会豁免屏蔽关键词。命中映射的标题即使包含你设置的屏蔽词也会照常同步。这通常是你想要的(你亲手指定了它),但要知道这个副作用。
建议的做法
- 一次只加一条,加完观察一次同步结果,确认无误再加下一条。
- 优先用 「候选确认」 页面一键写入(见下文),它带回填与校验,比手填 ID 不容易错。
- 配完之后如果不确定,到 「同步记录」 详情里核对实际命中的条目。
三种常用做法
1. 简单精确映射(默认)
- 适用:媒体库标题固定、与 Bangumi 条目一一对应。
- 填写:番剧名称 + Bangumi ID;季度留空。
- 行为:只要同步请求里的标题(或原始标题)与「番剧名称」完全一致即命中,所有季度共用同一个 ID。
2. 季度感知映射
- 适用:同一标题字符串在不同季度应对应 Bangumi 上不同 subject(例如媒体库不按季拆分,但 Bangumi 分季条目)。
- 填写:番剧名称 + Bangumi ID + 季度(正整数)。
- 行为:仅当同步请求的
season与填写的季度一致时才命中;留空季度则视为简单映射,所有季度共用。
例如媒体库把整部番放在「第 1 季」下、集数连续,而 Bangumi 分成两个条目:
| 番剧名称 | Bangumi ID | 季度 |
|---|---|---|
| 某番剧 | 111111 | 1 |
| 某番剧 | 222222 | 2 |
3. 正则规则
- 适用:一批标题共享命名规律(续作、系列作、带后缀的变体等),不适合逐条写精确名称。
- 填写:添加映射时类型选 「正则规则」,填写正则表达式、Bangumi ID,以及可选的说明与限定季度。
- 行为:对同步请求的标题与原始标题依次做匹配;第一条匹配成功的规则生效。页面底部提供 「正则匹配测试」,可先验证表达式再保存。
限定季度用于「同名番剧有多季、且各季是不同条目」的情况:填了它,规则只在指定季生效;留空则对所有季生效。
具体写法与大量示例见 正则匹配(进阶)。
季数对不上?用「集数分段」
上面三种做法解决的是「标题对不上」。还有一类更麻烦的情况:两边都匹配上了,但季/集的划分不一样。
典型场景:TMDB 把一部番拆成 3 季,而 Bangumi 只分成 2 季,两边的总集数是相同的。此时无论指定哪个条目,集号都对不上。
这时候用「集数分段」:在编辑映射时打开 「集数分段」 开关,按提示三选一即可。
三种情形
| 你的情况 | 选哪个 | 要填什么 |
|---|---|---|
| 这一季整个都对应另一个条目 | 整季都对应另一个条目 | 目标 Bangumi ID |
| 从第 N 集开始换成另一个条目 | 从某一集开始对应另一个条目 | 起始集号、目标 Bangumi ID |
| 其中某几集对应另一个条目 | 某几集对应另一个条目 | 起始集号、结束集号、目标 Bangumi ID |
填完后页面会实时显示人话预览,例如:
媒体库 S6 E3 起 → 条目 639938(E3→该条目 E2,E4→E3,依次类推)
请务必核对这行预览再保存。
「自动推算」怎么用
点 「自动推算」 按钮,程序会读取目标条目的章节列表,把「这一段第 1 集」对齐到该条目的第一集,自动填好下面的「对应该条目的第几集」。
如果推算结果和人话预览不一致,直接手动改那个数字即可。
⚠️ 集号填写须知(最容易错的地方)
「从第几集开始 / 到第几集结束」填的是你媒体库里的集号,也就是 Emby / Jellyfin / Plex 在该季内显示的集号(第 1 集就是 1),不是整部番连续累加的集号。
举例:媒体库第 6 季显示 E1–E12,你要让 E3 起对应另一条目,就填「从 3 开始」,而不是填 15(3 + 前面 5 季的 12 集)。
复杂度提示
集数分段比前面三种做法明显更难,需要你先弄清两边各自的季/集划分。建议:
- 先只用一条分段,覆盖最常见的那一段;
- 确认同步正确后,再逐步补充分段;
- 遇到下面「常见场景与写法」里没覆盖的情况,再考虑手改 JSON。
手改 JSON(不推荐,供需要多条分段时使用)
弹窗界面一次只能编辑一条分段;存在多条分段时它会提示你去手改配置文件,且不会在保存时改动它们。
配置文件为程序目录下的 bangumi_mapping.json,分段写在对应标题下:
{
"mappings": {
"某番剧": {
"subject_id": "111111",
"segments": [
{ "season": 6, "from": 1, "to": 12, "subject_id": "111111", "offset": 1 },
{ "season": 7, "from": 1, "to": null, "subject_id": "222222", "offset": 1 }
]
}
}
}字段含义:
| 字段 | 含义 |
|---|---|
season | 该段作用于媒体库的哪一季;省略表示所有季 |
from / to | 媒体库该季内的集号区间(含两端);from 省略表示从第 1 集起,to 为 null 表示不设上界 |
subject_id | 这一段对应的 Bangumi 条目 ID |
offset | 该段第 1 集对应的目标条目第几集(目标 = 集号 − from + offset) |
episodes_of | (可选)段内逐集例外,如 {"11": 5} 表示第 11 集对应目标第 5 集,优先于 offset |
offset 是「第几集」,不是章节编号(sort)
offset: 1 表示「对应目标条目的第 1 集」,即使该条目在 Bangumi 上的章节编号(sort)从 13、99 或其它值开始。
程序按条目内相对集号定位(sort − 该条目首个正片 sort + 1),匹配不上才回退按 sort 直比。所以你只要填「第几集」这个人类语义即可,不用去数章节编号。
也允许填 0 或负数(媒体库集号比目标集号大时用)。但算出来的目标集号必须 ≥ 1,否则该段会被跳过并在日志里告警。
规则说明:
- 多段重叠时取数组中靠前的一段,因此把更具体的段写在前面。
- 集号落在所有段之外时,回退到该标题顶层的
subject_id。 :::
常见场景与写法
上面三个选项覆盖不了的所有情况,都可以用「多条分段」表达。下面每个例子都标注了实测结果,可照着改。
场景 A:TMDB 拆成 3 季,Bangumi 只分 2 季
两边总集数相同,前两季合并对应 Bangumi 第一个条目,第三季对应第二个条目:
{
"mappings": {
"某番剧": {
"subject_id": "100",
"segments": [
{ "season": 1, "from": 1, "to": 12, "subject_id": "100", "offset": 1 },
{ "season": 2, "from": 1, "to": 12, "subject_id": "100", "offset": 13 },
{ "season": 3, "from": 1, "to": 12, "subject_id": "200", "offset": 1 }
]
}
}
}实测:S1E1→100/1、S2E1→100/13、S2E12→100/24、S3E1→200/1
关键在第二条的 offset: 13——媒体库第 2 季的第 1 集,要接在第 1 季的 12 集后面。
场景 B:同一季内,第 13 集起换成另一个条目
{
"mappings": {
"某番剧": {
"subject_id": "100",
"segments": [
{ "season": 1, "from": 1, "to": 12, "subject_id": "100", "offset": 1 },
{ "season": 1, "from": 13, "to": null, "subject_id": "200", "offset": 1 }
]
}
}
}实测:S1E12→100/12、S1E13→200/1
to: null 表示「从这里一直到最后」,不用管这一季到底有多少集。
先确认是否需要手写:当 bangumi-data 已为该条目标注 TMDB 集边界 (形如
tv/278043/season/1/episode/13的来源,表示该条目从合并季第 13 集起), 程序会自动完成同样的换算,无需再配segments。只有库中未标注边界时, 才需要按本场景手写。二者可共存:映射仍然优先,自动换算只在没有映射命中时生效。
场景 C:整季对应另一个条目(不用写集号)
{
"mappings": {
"某番剧": {
"subject_id": "100",
"segments": [
{ "season": 2, "to": null, "subject_id": "300", "offset": 1 }
]
}
}
}实测:S2E1→300/1、S2E5→300/5;而 S1E1 不在任何段内,回退到顶层 subject_id。
from 省略即从第 1 集开始。
场景 D:多集并一集(媒体库集数比 Bangumi 少)
例如媒体库把两集并成了一集,媒体库 E5、E6 实际都对应 Bangumi 第 5 集:
{
"mappings": {
"某番剧": {
"subject_id": "100",
"segments": [
{ "season": 1, "from": 5, "to": 5, "subject_id": "100", "offset": 5 },
{ "season": 1, "from": 6, "to": 6, "subject_id": "100", "offset": 5 }
]
}
}
}实测:S1E5→100/5、S1E6→100/5
写法要点:把范围拆成多段,每段都指向同一个目标集号。因为 offset 是「该段第 1 集对应的目标集号」,所以每段写 offset: 5 就都落到第 5 集。
重复同步不会有副作用
E5、E6 都指向 Bangumi 第 5 集时,无论先看完哪一集,另一集再上报都会发现第 5 集已经是看过状态,程序会跳过写入(不报错、不重复计数)。所以这类配置是安全的。
区间较长时,逐段写会很啰嗦,可以改用下面的 episodes_of 一次钉死。
场景 E:一集拆多集(媒体库集数比 Bangumi 多)
方向和场景 D 相反:Bangumi 的一集,在媒体库/媒体库里被拆成了两集。典型是总集篇——Bangumi 上第 3 集是 recap,媒体库里却是 E3 和 E4 两个文件。
这时媒体库 E3、E4 都应对应 Bangumi 第 3 集,而后面的集号要整体顺延:
{
"mappings": {
"某番剧": {
"subject_id": "100",
"segments": [
{ "season": 1, "from": 1, "to": 2, "subject_id": "100", "offset": 1 },
{ "season": 1, "from": 3, "to": 4, "subject_id": "100", "offset": 3,
"episodes_of": { "3": 3, "4": 3 } },
{ "season": 1, "from": 5, "to": null, "subject_id": "100", "offset": 4 }
]
}
}
}实测:E1→1、E2→2、E3→3、E4→3、E5→4、E6→5、E7→6
写法要点:拆集的处理分三步——拆开的那几集用 episodes_of 钉到同一目标集,下一段的 offset 要相应加 1(上例第三段 offset: 4,因为 E5 应接在目标第 4 集)。若不调整后面的 offset,从 E5 起会整体错位一集。
判断用 D 还是 E
两者在配置上都是「多个媒体库集指向同一个 Bangumi 集」,区别只在业务语义:
- D(并集):媒体库少了几集 → 是媒体库把两集并成了一集。
- E(拆集):媒体库多了几集 → 是媒体库把一集拆成了两集。
写法上的实际差别:D 每段各自独立、互不影响;E 需要在拆集点之后调整后续段的 offset,否则会错位。
场景 F:整部均匀拆集(用 episodes_of 批量生成)
如果每一集都被均匀拆成了 2 集(Bangumi 12 集 ↔ 媒体库 24 集),逐条手写 episodes_of 不现实,可按规律生成:
{
"mappings": {
"某番剧": {
"subject_id": "100",
"segments": [
{ "season": 1, "from": 1, "to": 24, "subject_id": "100", "offset": 1,
"episodes_of": {
"1": 1, "2": 1, "3": 2, "4": 2, "5": 3, "6": 3,
"7": 4, "8": 4, "9": 5, "10": 5, "11": 6, "12": 6,
"13": 7, "14": 7, "15": 8, "16": 8, "17": 9, "18": 9,
"19": 10, "20": 10, "21": 11, "22": 11, "23": 12, "24": 12
} }
]
}
}
}规律是:媒体库第 n 集 → 目标第 ⌈n / 2⌉ 集(向上取整)。实测该配置下 E1–E24 全部映射正确。
episodes_of 必须覆盖到段末
episodes_of 只写一部分(比如只写到第 12 集)而段里 to 是 24 时,没覆盖到的集不会按前面的规律延续,而是直接回退到 offset 的线性推算。
实测:只写前 12 个键时,E13 得到的是 13(而不是期望的 7),E14→14、E24→24——和拆集语义完全不符,且不会报错。
所以要用 episodes_of,就得把它写满整个段;否则请改用「多段拼接」(见场景 E)。
只写 offset 是不够的
单靠 offset 是线性的,无法表达「两集对一集」。实测 offset: 1 只给出 E1→1, E2→2, E3→3…,与拆集语义不符。遇到拆集必须用 episodes_of(或拆成多段)。
场景 G:用 episodes_of 一次钉死多个例外
episodes_of 是「段内逐集例外」,比 offset 优先。同一段里可以让多集指向同一目标:
{
"mappings": {
"某番剧": {
"subject_id": "100",
"segments": [
{ "season": 1, "from": 4, "to": 8, "subject_id": "100", "offset": 1,
"episodes_of": { "5": 5, "6": 5, "7": 6 } }
]
}
}
}上例中第 4、8 集按 offset 推(分别对应目标第 1、5 集),第 5、6 集都对应目标第 5 集,第 7 集对应目标第 6 集。
它也能用来处理小数集号或跳号:"episodes_of": { "3": 4 } 会让第 3 集指向目标第 4 集,其余仍按 offset 推。
场景 H:只有某几集属于另一个条目(特别篇、总集篇)
想把第 7 集单独指到另一个条目,顺序很关键:
{
"mappings": {
"某番剧": {
"subject_id": "100",
"segments": [
{ "season": 1, "from": 7, "to": 7, "subject_id": "999", "offset": 1 },
{ "season": 1, "from": 1, "to": null, "subject_id": "100", "offset": 1 }
]
}
}
}实测:S1E6→100/6、S1E7→999/1、S1E8→100/8
最常见的配置错误
如果把宽段(from: 1, to: null)写在前面,它会先命中,特例段永远不会生效,而且不会报错:
// ❌ 错误:第 7 集的段被前面的宽段吞掉了
"segments": [
{ "season": 1, "from": 1, "to": null, "subject_id": "100", "offset": 1 },
{ "season": 1, "from": 7, "to": 7, "subject_id": "999", "offset": 1 }
]实测结果:S1E7→100/7(特例没有生效)
规则:to 为 null(或很大的数)的段会一直吃到这一季末尾,必须放在最后。把特例、例外写在前面,兜底的宽段写在最后。
场景 I:不写 season,对所有季生效
省略 season 时该段对所有季都生效,适合「媒体库每季都是同一个编号规律」的情况:
{
"mappings": {
"某番剧": {
"subject_id": "100",
"segments": [
{ "from": 1, "to": 12, "subject_id": "200", "offset": 1 }
]
}
}
}实测:S1E1→200/1、S3E1→200/1
场景 J:offset 为负或很小时
offset 允许负数,当媒体库集号比目标集号大时使用。但要注意算出来的目标集号必须 ≥ 1,否则该段会被跳过并记录告警:
{
"mappings": {
"某番剧": {
"subject_id": "100",
"segments": [
{ "season": 1, "from": 1, "to": null, "subject_id": "100", "offset": -1 }
]
}
}
}实测:S1E1 算出 1−1+(−1) = −1,非法 → 跳过该段并告警;S1E5→100/3 正常。
若要整段向左平移,请改用「把该段起点写在第一个有效集号上」,而不是用负偏移硬怼。
排错清单
分段配置写完不生效时,按这个顺序自查:
| 现象 | 多半是 |
|---|---|
| 某一集仍匹配到别的条目 | 前面有 to: null 的宽段把它吞了 → 把宽段挪到最后 |
| 整条配置完全没生效 | season 写错了(注意这里填的是媒体库的季号) |
| 集号对不上,差了固定的几集 | offset 算错 → 记住它是「该段第 1 集对应的目标第几集」(不是章节编号 sort) |
| 某几集之后目标集号变成和媒体库集号一样 | episodes_of 没写满整个段 → 补全,或改用多段拼接 |
| 拆集场景里后续集号整体错位一集 | 拆集点之后那一段的 offset 忘了 +1 |
| 段内个别集对不上 | 用 episodes_of 单独钉死那几集 |
| 集号填成了累计值 | 「从/到」填的是该季内的集号,不是整部番累加的集号 |
配置改动后,程序会在下次同步时自动重新加载。想立刻验证,可在 「调试工具」 里发起一次测试匹配,或在 「同步记录」 详情里核对实际命中的条目与集号。
映射里要填什么
每条映射至少需要:
- 番剧名称 / 正则表达式:精确映射需与同步请求里的标题字符串尽量一致(包括标点、空格、季名等)。不确定时,可到 「同步记录」 查看失败条目实际带入的标题,按日志里的写法填写。
- Bangumi ID:该番在 Bangumi 上的 subject 编号(纯数字)。打开对应条目页面,地址一般为
https://bgm.tv/subject/数字,其中的数字即为 ID。
对于简单精确映射,程序会按「第一季 / 主条目」逻辑使用你填的 ID:一般填系列第一季或主条目的 ID,后续季数由程序根据续集链等逻辑处理,无需为每一季各写一条。若某季在 Bangumi 上是独立 subject 且简单映射对不上,请改用 季度感知映射 分别指定。
写入时会校验 ID
添加或编辑映射时,程序会校验你填的 Bangumi ID:必须是纯数字,且对应条目必须是动画或三次元。填了书籍 / 音乐 / 游戏的 ID 会被直接拒绝并说明原因。
如果当时没有可用的 Bangumi 账号或网络不通,校验会放行(不阻塞你编辑),无效 ID 会在真正同步时才暴露。
映射是全局配置
映射只按标题保存,不分用户、也不分媒体源。也就是说:只要标题匹配,所有媒体服务器用户、所有 Bangumi 账号、所有媒体源都会使用同一条映射。
这是有意设计的。之所以不需要按用户区分:能走到匹配这一步的请求,其用户已经通过了账号权限校验,同一账号体系内不会出现「同一个标题需要映射到不同条目」的正当场景。
批量导入与导出
- 批量导入:粘贴 JSON 或上传
.json文件。可勾选「与现有映射合并」;不勾选则覆盖全部现有映射与规则。 - 导出映射:下载当前映射的完整 JSON,便于备份或迁移。
- 清空所有:删除全部映射与规则(不可撤销,操作前建议先导出备份)。
导入支持两种 JSON 形态:顶层 {"番剧名": "id"} 的简单对象,或带 mappings / rules 字段的完整结构。
与候选确认的关系
自动匹配失败但存在相近搜索结果时,程序会将候选沉淀到 「候选确认」 页面。确认某条候选后,可一键写入自定义映射,无需手填 Bangumi ID。
候选列表按相似度 score 降序展示,最相近的候选排在最前;若候选项均不符合,可点击「手动指定 Bangumi ID」直接输入条目 ID(方案 A),系统会校验 ID 有效性后再写入映射。
确认即补发
确认候选或手动指定 ID 后,若原同步记录状态为 error / queued,会自动触发一次补发同步,无需手动重试。
接下来
- 想用正则覆盖一批标题 → 正则匹配(进阶)
- 没写过正则,想先学会 → 正则入门(新手)
- 同步仍失败?看 排错说明 第 4、5 节。
- 想理解程序为何没匹配上?在 「同步记录」 详情查看完整匹配过程(含搜索参数、候选列表、各阶段耗时),或在 「调试工具」 发起测试匹配。
- API 不可达时想仍能匹配新条目?看 🔄 Bangumi Replay 待同步队列补发 与 🗄️ Bangumi Archive 离线查询层。
