扫描 PDF 转文字 EPUB
生成可重排的文字 EPUB,只把真正有意义的视觉内容保留为图片。视觉复核和重复校验是必做步骤,不是可选的润色工作。
运行环境
优先使用工作区自带的 Python。转换环境必须提供 pypdfium2、pdfplumber、Pillow、rapidocr_onnxruntime;正式发布校验还需要 ebooklib、Java 和 EPUBCheck。不知道 Python 路径时,调用 codex_app__load_workspace_dependencies 获取。外部工具缺失时校验器会明确报告降级,但降级结果不能作为最终发布通过。
运行命令前设置以下路径:
SKILL_DIR=<包含本 SKILL.md 的目录>
PYTHON=<工作区 Python 可执行文件>
编辑书籍配置前,先阅读 references/config-schema.md。
工作流程
1. 准备扫描文件
保持源 PDF 不变,并为本书建立独立的工作目录。
& $PYTHON "$SKILL_DIR\scripts\prepare_scan.py" $PDF --work-dir $WORK_DIR --ocr-mode auto
只有脚本判断内嵌文本层可用时才使用它。纯扫描文件交给脚本选择 RapidOCR。检查脚本报告的识别模式,不要因为页面看起来像扫描图就假定它没有隐藏文本层。
2. 生成检查材料
& $PYTHON "$SKILL_DIR\scripts\inspect_scan.py" "$WORK_DIR\manifest.json" `
--title $TITLE --author $AUTHOR --publisher $PUBLISHER --publication-date $DATE
脚本会生成:
inspection.json:自动视觉候选、可能的图表标题和文章日期。视觉候选包含建议框、类别、启发式置信度、发现原因和稳定编号。book-config.json:本书专用配置。新候选会作为默认禁用的视觉项写入初始配置,必须人工确认后才能启用。review/all-pages/:用于发现无图题视觉内容的全书联系表。review/candidate-pages/:带候选框、短编号、置信度、简短信号代码和 PDF 坐标网格的候选页;完整理由保留在inspection.json。review/history/:重跑检查器时归档上一轮inspection.json、候选页和联系表,当前目录不会混入过期候选,旧材料仍可追溯。
检测器会结合 OCR 文字掩膜后的剩余墨迹、区域密度、连通区域、投影结构、留白分割和图题模式。它不依赖图题才能发现照片、示意图或表格,但输出仍只是待复核建议。再次运行时会根据同页图题或区域重叠尽量复用原有 candidate_id;普通重跑不会改写现有人工 book-config.json。显式传入 --overwrite-config 时,检查器会先把原配置归档到 config-history/,再生成新模板。
初始配置中的 publication.identifier 根据源内容稳定生成且不包含源路径;publication.modified_utc 会保持为空。开始正式构建前,按 配置文档的发布信息与封面章节填写显式 UTC 修改时间,并确认原书 ISBN 应放在 source_identifier 还是确实属于当前 EPUB 的 isbn。
3. 复核全书
检查每一张全书联系表,不能只看自动候选页。把检测器漏掉的示意图、照片、地图、书法或其他有意义的视觉内容补进配置;自动建议框也必须对照源页调整,不能把启发式置信度当成正确概率。第 7 步的本地工作台用于完成逐页决定和几何调整,但不能替代全书联系表总览。
每个自动检测到的视觉候选项必须采用以下一种处理方式:
- 在本地工作台中接受并完成视觉配置,或手工在
visuals中启用并完成配置。 - 在本地工作台中填写理由后忽略,或把它的
candidate_id记录到ignored_visual_candidates,并在工作笔记中写明合理的忽略原因。
根据印刷目录和各章起始页建立真实的 articles 列表,确认标题、日期、起始页,以及推断或明确指定的结束页。
每个启用的视觉内容都要满足:
text_exclusion_boxes使用一个或多个二维框覆盖印刷图像、图内标签及印刷图题;只有中心点落入框内的 OCR 行会从可重排正文中排除。crop只包含视觉内容本身,通常不包含印刷图题;EPUB 会生成干净的语义化图注。anchor_y单独指定视觉内容在正文流中的插入位置,不能用裁切框或排除框隐式代替。- 相邻正文句子的 OCR 行中心点必须位于所有
text_exclusion_boxes之外。 - 图题应显示在图片上方时使用
kind: table,应显示在图片下方时使用kind: figure。 - 把视觉内容附近的正文句子加入
text_checks,避免错误边界悄悄删掉正文。 - 如果图中的独特标签、图例或坐标数字绝不能混入正文,把它们加入
forbidden_text。
坐标使用候选页上标注的 PDF 点数,二维框顺序为 [left, top, right, bottom]。禁止裁切整页;构建器会拒绝覆盖页面面积超过 75% 的裁切框和新版文字排除框。
根 schema_version 只表示视觉几何格式,不表示发布信息是否完整。旧版 schema_version: 1 配置中的 region: [top, bottom] 无需迁移几何字段;程序会在内存中按全页宽排除,不会改写原配置。新配置使用 schema_version: 2;同一视觉项同时出现 region 和 text_exclusion_boxes 属于歧义,必须先人工选择正确边界。
这项几何兼容不等于旧配置可以直接构建。Phase 7 起每次构建都必须有 publication.identifier 和 publication.modified_utc;缺少 publication 或任一字段时,构建会失败并提示迁移这两个字段,不能靠保留 schema_version: 1 绕过。
迁移旧配置时保留原有几何设置,只补发布信息。优先用同一 manifest 重新运行 inspect_scan.py 并把 --config 指向新模板,从模板取得不泄漏路径的稳定标识,再人工填写真实的 UTC 修改时间;也可以手工添加符合 schema 的 publication。不要覆盖旧配置,不要自动升级几何,也不要使用动态当前时钟、文件 mtime、随机临时 ID 或假日期。完整命令和字段示例见 旧配置兼容。
4. 配置文本清理
按需设置:
running_headers:重复出现的书名或文章页眉。header_patterns:只匹配可靠页眉的正则表达式。footer_patterns:只匹配可靠页脚的正则表达式。note_patterns:识别页底来源或出处说明,并将其移动到章节标题下方。replacements:已经对照扫描页确认无误的固定 OCR 错字替换。skip_pages:空白页,或已由生成目录替代的扫描目录页。front_matter:需要按阅读顺序保留的出版说明、序言等前置内容。page_offset:印刷页码与 PDF 页序号之间的偏移量。
不要猜测性修改 OCR 文字。每一项替换都必须与源扫描页核对。
边注、脚注和页底出处要采用保守的复核边界:
- 未命中已确认的页眉、页脚或视觉排除规则时,边注或侧栏文字按普通可搜索正文保留,不只因横向位置、短文本或位于页底而静默删除;同一条 OCR 输入只应在正文中出现一次。
- 只有在源扫描页确认了明确的页底来源/出处说明后,才配置
note_patterns。正则应尽量使用首尾锚点,例如^资料来源:.*$,避免把普通正文或边注误移走。匹配项会从正文段落移到章节标题后的<aside epub:type='note'>;有日期时它位于日期之后,并同步保留在 TXT 中。 note_patterns是人工确认的文字规则,不是页底位置检测器。未配置的脚注、出处或侧栏不会自动分类;配置前应把前后相邻正文加入text_checks,构建后分别检查 EPUB 的正文和 note。
这组边界的外部回归见 tests/test_review_boundaries.py。表格或其他视觉内容附近的正文仍使用二维 text_exclusion_boxes 的 OCR 行中心点命中规则:相邻正文加入 text_checks,绝不能混入正文的图内 OCR 加入 forbidden_text。已有 tests/test_epub_geometry.py::test_v2_exclusion_boxes_keep_side_text_and_remove_visual_ocr 覆盖这条行为,本 skill 不再重复建立同一夹具。
5. 生成 OCR 质量报告
& $PYTHON "$SKILL_DIR\scripts\report_ocr_quality.py" "$WORK_DIR\book-config.json"
默认报告位于 review/ocr-quality.json 和 review/ocr-quality.md。先处理 high 和 medium 风险,再复核 low 风险;报告只提出问题,不会改写 OCR、配置或正文。
报告包括低置信度、乱码、缺失置信度、罕见字符、异常长数字、同页重复行和中英文直接相邻七类信号,并按风险和页码提供两种稳定复核顺序。中英文直接相邻只是低风险启发式信号,A股、GDP增长 等正确写法也可能出现,不能据此自动替换。
默认低置信度阈值为 0.80。需要调整复核规模时使用 --low-confidence-threshold 0.75;阈值和 OCR 来源行数会写入报告摘要。逐项检查 replacements 的实际出现次数和源行证据,零命中替换同样需要确认或删除。
跨页重复文字通常是页眉、页脚或正常复现,不属于本报告的同页 OCR 重复规则;在结构推断或人工配置页眉页脚时处理。
6. 生成结构建议
& $PYTHON "$SKILL_DIR\scripts\suggest_structure.py" "$WORK_DIR\book-config.json"
默认报告位于 review/structure-suggestions.json 和 review/structure-suggestions.md。报告会提出目录页、章节、页码偏移、页眉页脚、空白页和非正文页候选,但不会改写正式配置。
逐项对照扫描页处理候选。可以在第 7 步的本地工作台中完成决定,也可以手工维护配置:
- 接受候选时,按
proposed_change把值人工写入对应配置字段;下次运行会把它标为configured。 - 拒绝候选时,把稳定的
candidate_id加入ignored_structure_candidates;下次运行会把它标为ignored。 - 正式配置优先于旧忽略记录。候选置信度只用于安排复核顺序,不能代替人工确认。
完整结构报告可能包含目录标题、章节名和页边文字。用户书籍的报告只能保存在本机,不得提交仓库。
7. 使用本地复核工作台
生成视觉检查材料、OCR 质量报告和结构建议后,启动只监听本机回环地址的复核服务:
& $PYTHON "$SKILL_DIR\scripts\review_server.py" "$WORK_DIR\book-config.json" `
--state "$WORK_DIR\review\review-decisions.json"
命令会输出并打开一个 http://127.0.0.1:<端口>/ 地址。页面序列、扫描页画布和复核面板分别用于:
- 接受或带理由忽略视觉候选,新增人工视觉,并拖动或精确填写裁切框、文字排除框和插入锚点。
- 接受或带理由忽略结构候选,新增、更新或删除章节起点。
- 查看并记录 OCR 风险、正文保留检查和图内文字禁止泄漏检查。
- 使用撤销恢复最近决定;撤销历史在关闭并重新打开工作台后仍保留。
决定默认写入独立的 review-decisions.json,不会直接改写 book-config.json。状态文件记录原配置摘要;原配置发生变化后,旧决定不会被静默重放。所有页面内容和决定只通过本机服务传输,不依赖外部网络。
复核完成后点击“导出”,把合并后的配置写到新的目标,例如 $WORK_DIR\book-config.reviewed.json。导出会保留原配置中的未知字段并保护书籍输入文件;已有目标必须显式允许覆盖。旧版 schema_version: 1 只有在明确选择“升级旧版配置”后才转换为新版几何字段。相同输入和决定的连续导出应字节一致。
后续命令使用最终确认的配置:
$CONFIG = "$WORK_DIR\book-config.reviewed.json"
如果没有使用工作台,而是直接手工完成原配置,则把 $CONFIG 指向该文件。
8. 构建
& $PYTHON "$SKILL_DIR\scripts\build_epub.py" $CONFIG
& $PYTHON "$SKILL_DIR\scripts\review_visuals.py" $CONFIG
构建结果必须同时包含 EPUB 和配套 TXT。检查每一张生成视觉联系表;如果标签、边框、图例或相邻正文靠近裁切边缘,还要单独打开该图片检查。
正式构建必须包含稳定的 publication.identifier 和显式 publication.modified_utc。封面可以从源页裁切,也可以读取本地 PNG/JPEG;构建器会生成 cover-image 声明,并在 visible: true 时把 cover.xhtml 放入书脊。封面不进入正文 visuals,源页封面允许覆盖整页。
构建器会根据 OCR 行框保守恢复单栏、双栏、跨栏文字、窄侧栏、首行缩进、印刷标题和跨页续句,并把栏内视觉绑定到相同的纵向区段。布局证据不足时应降级为单栏;任何布局分类都不得删除、复制或改写 OCR 行。发现复杂版式时,除检查最终阅读顺序外,还要确认同一段文字在 EPUB 和 TXT 中只出现一次。
反复调整配置并重新构建,直到所有图片完整,且没有夹带无关正文。
最终配置确认后连续构建两次,并用 Get-FileHash -Algorithm SHA256 $EPUB 比较结果。相同源文件、配置和修改时间必须生成相同的 EPUB 字节;配套 TXT 也应保持一致。
9. 反复校验
平时调整配置时运行内部结构和正文校验:
& $PYTHON "$SKILL_DIR\scripts\validate_epub.py" $CONFIG
每次修改图片边界或文本清理规则后都要运行校验,最终重建后再运行一次。校验不通过时不得报告完成。
校验器会检查:
- EPUB ZIP 文件顺序和完整性。
- XML/XHTML 解析、清单、书脊、链接、导航和元数据。
- 视觉内容数量、图注、替代文本、PNG 有效性,以及是否存在整页图片。
- 每个检测候选项是否已嵌入或明确忽略。
- 章节标题和日期。
- EPUB 正文与配套 TXT 是否一致。
- 必须保留的文字、禁止泄漏的图内文字和源页面抽样文字。
校验入口始终会调用外部状态模块;未提供 EPUBCheck 路径时只报告降级,不会伪装成通过。发布前用同一个入口显式运行 EPUBCheck 和 ebooklib,并把稳定 JSON 报告保存在本书工作目录或用户指定的输出目录:
& $PYTHON "$SKILL_DIR\scripts\validate_epub.py" $CONFIG `
--java $JAVA `
--epubcheck-jar $EPUBCHECK_JAR `
--external-report "$WORK_DIR\external-validation.json" `
--require-epubcheck
EPUBCheck 缺失、超时或返回 warning/error/fatal 时不能当作通过。--require-epubcheck 是发布门槛:EPUBCheck 和 ebooklib 必须同时通过,任一检查跳过、失败或出错都会阻断发布;ebooklib 缺失时报告仍明确记为 skipped。Calibre、Apple Books 或 Kindle 只属于可选兼容性抽检,不能替代 EPUBCheck。
质量门槛
只有同时满足以下条件才算完成:
- 正文可选择、可搜索、可重排。
- 阅读内容不使用整页扫描图。
- 原书中真正的图、表、示意图和照片都以本地裁切图片保留。
- 坐标轴、图例和表格单元格文字不会散落成正文段落。
- 视觉内容相邻的正文句子仍可搜索。
- 导航使用真实的章节或文章标题。
- 生成图片至少经过两轮视觉检查:先看总览,再单独检查高风险裁切。
- 当前 EPUB 的标识、修改时间、来源标识和封面声明正确,不泄漏本地路径。
- 相同输入连续构建的 EPUB SHA-256 一致,配套 TXT 内容不变。
- 最终重建后的内部校验、EPUBCheck 和第二个独立解析器检查均通过;未执行的工具明确标记为跳过。
除非用户要求删除,否则保留中间页面、OCR 数据、配置、联系表和校验材料。它们是以后修正问题所需的审计记录。