Skip to content

ADR 0003:内容定位 —— 一行 locator_jsonord

日期:2026-09-17 · 状态:已采纳

背景

M3 要把漫画(mokuro)、EPUB 轻小说与流媒体接进来。现有模型是流式的:Source → CaptureSession → Line, 一条台词的定位信息散在三处:

  • start_ms / end_ms:字幕与音频用;
  • position_json:OCR 截屏在屏幕上的框;
  • 顺序:默认靠 captured_at 与自增 id

漫画需要「第几页 + 页面上的框」,EPUB 需要「第几章 + 章内偏移」,两者在模型里都没有位置。照 「一种格式加两列」的路子,Line 会长出 page_nochapter_idchar_offsetcfi……每种内容 只用得上其中一小撮,而每次导入都要迁移一次表。

决策

Line 增加两个可空列:

  • locator_json(Text):{"kind": "time" | "page" | "offset" | …, …}kind 必填,其余字段随 kind 而定;
  • ord(Integer,索引):作品内的稳定顺序,流式内容用采集顺序,文档式内容用页/章顺序。

老的 start_ms / end_ms / position_json 保留不动,新代码写 locator_jsonschemas/lines.line_locator() 提供兼容读取:没有 locator_json 时从旧字段合成,保证老数据照样能排序与渲染。

理由

  1. 加列不可持续。 每接一种内容就要迁移一次表,且列在不同内容间互斥,读的代码到处 if
  2. 多态表过度。 单独一张 locators 表要 join、要区分来源,而定位信息永远与行一一对应、 读写都跟着行走;SQLite 下没有把行拆开的理由。
  3. JSON 合适。 定位结构随内容格式演化,查询需求弱(只按 kind 判读、按 ord 排序), SQLite 的 JSON1 也足以在需要时按内部字段过滤。格式继续增加时不需要再动 schema。
  4. ord 是排序问题的正解。 导入一本书时所有行的时间戳几乎相同,captured_at 不再是顺序; ord 让「作品内顺序」显式化,流式与文档式共用一种表达。

代价与边界

  • 无法对 locator 内部字段建索引。 需要按页/章查询时,先用 ord 范围或额外列再过滤; 若真的出现高频的「按页查」,那时再加具体列或生成列,而不是现在预判。
  • 弱类型。 kind 与字段的合法性只有约定的校验函数保证,数据库不管。
  • 兼容期同时存在两套读法;等所有写入方迁移完(字幕、OCR、漫画、EPUB)后,旧字段可另开 issue 讨论弃用。

代码 AGPL-3.0-or-later · 词典数据 © EDRDG,CC BY-SA 4.0