给 AI Agent 写了个 Skill:扫描版电子书自动解压合并 + 加书签(附 PDG 格式逆向)
估计很多朋友喜欢和我一样去网上下载电子书,一般都是超星格式的压缩包,也就是 ZIP 分卷包,里面是 PDG 页面文件,没目录。需要自己转换为 PDF,以前一本书要手工走完解压、按页序合并、查目录、算页码偏移、PdgCntEditor 粘目录调层级、保存、抽查,十几分钟一本。书一多就是体力活,于是写了个 Skill(SKILL.md + Python 脚本)交给 Agent 全流程跑。
Skill 下载见博客后面的下载链接,其中的配置请自行修改。
1. 素材长什么样
ZIP(未加密;加密的用固定密码表,逐个试),内层是 <SS码>/ 文件夹 + PDG 文件:
text
14080995.zip
└── 14080995/
├── !00001.pdg ... ← 目录等前置页
├── 000001.pdg ... ← 正文
├── cov001/002.pdg ← 封面 / 封底
├── bok001.pdg ← 书名页
├── fow001.pdg ← 前言
└── leg001.pdg ← 版权页
大部分 PDG 只是改了扩展名的 JPEG(文件头 FF D8 FF);少数是真容器格式,第 3 节细说。文件名前缀对应书的部位,这是后面排序的依据。
2. Skill 结构与流程
SKILL.md 写流程和触发条件,scripts/bmtool.py 承担全部确定性计算,config.json 放机器路径。分工原则:能写成脚本的不让 Agent 现场发挥,Agent 只做两件事------驱动书签查询软件的 GUI、异常时找人确认。
text
zip ── bmtool merge ──▶ 无书签 PDF(输出页数/前置页定位/正文起始页 N)
── 书签软件查目录(Agent 填 SS 码,点"复制书签"读剪贴板)
── bmtool build(推层级)── bmtool write ── bmtool verify ──▶ 成品
bmtool 六个子命令:merge(解压合并)、toc(查现有书签)、build(生成书签文本)、write(写入 PDF)、verify(回读校验)、sheet(缩略图拼图,备用)。
四本书实测:
| 书 | 页数 | 书签数 | 验证 |
|---|---|---|---|
| 艾灸养生书 | 146 | 126 | 通过 |
| REVIT 族设计手册 | 331 | 221 | 通过 |
| 空气调节设计手册(第3版) | 1167 | 452 | 通过 |
| 暖通设计手册 | 1350 | 382 | 通过 |
3. PDG 容器:主图 JPEG 被剥掉了 SOI
第一版 merge 跑完发现 146 个 PDG 只合并出 121 页,静默丢了 25 页。查这 25 个文件,文件头是 48 48 02 4A(HH\x02J)------真 PDG 容器。十六进制逐段看,结构是:
text
[PDG头 ~0x2588 字节,含 "SUPER STAR SSR EADER"]
[缩略图 JPEG:FF D8 ... FF D9,完整,但只有 160×153]
[IPTC / 8BIM 元数据块]
[主图 JPEG:从 APP1 直接开始 ... 文件尾 FF D9] ← 没有 SOI
也就是说主图是完整 JPEG,唯独开头的 SOI 标记(FF D8)被剥掉了。全文件搜 FF D8 FF 只命中缩略图,按常规提取只能拿到 160×153 的小图,主图(1888×1808)全部丢失。
修复:在缩略图 EOI 之后找第一个 APPx 标记,补回 \xFF\xD8 前缀,并校验尺寸防止拿到缩略图:
python
def pdg_main_image_bytes(data):
j = data.find(b"\xff\xd8\xff", 1) # 缩略图 SOI
e = data.find(b"\xff\xd9", j) # 缩略图 EOI
for m in re.finditer(b"\xff[\xe0-\xee]", data[e+2:e+2+40000]):
cand = b"\xff\xd8" + data[e+2+m.start():]
im = Image.open(io.BytesIO(cand))
if max(im.size) >= 800: # 主图而非缩略图
return cand
return None
这次翻车后给脚本加了两条硬校验:解码失败的页面必须报警列出,不许静默跳过;合并页数必须等于包内 PDG 文件总数。
4. 页序与偏移 N
ZIP 条目顺序是乱的(cov/bok/fow/leg 全堆在目录末尾),不能直接当页序。按超星命名规范排:
text
cov001(封面) < bok(书名) < leg(版权) < fow(前言) < !(目录) < 000001...(正文) < cov002(封底)
排完有个直接产出:正文第一页(000001.pdg)在 PDF 里的位置就是加书签要的偏移 N,不用再翻缩略图人工找。merge 输出:
text
完成: 14314981 空气调节设计手册 第3版.pdf
共 1167 页, 241.0 MB
前置页定位: 封面=PDF第1页 书名=第2页 版权=第3页 前言=第4页 目录=第7页
正文第1页 = PDF第19页 (加书签时 N=19)
书签页码换算两条公式,从一本手工做好的成品书读回 110 条书签反推验证:
- 正文(正数页码):
PDF页 = N + 页码 − 1 - 前置(零/负数页码):
PDF页 = N + 页码
5. 为什么没用 PDG 保存
一开始走的就是 PdgCntEditor 路线:命令行带 PDF 路径启动、正文页文本区整体 set_value 都通了,卡在保存------工具栏按钮没有可访问性名称,图标位置不能猜;Ctrl+S 要窗口前台,Listary、spacedesk 的弹窗反复抢焦点;一次激活冲突直接把程序搞崩,未保存的编辑全丢。
改用 PyMuPDF 的 set_toc() 直写,和手工用 PdgCntEditor 做的成品逐条比对:110 条书签,层级、标题、页码零差异。等价就选脚本,自动化场景确定性优先。
顺带:原来配套的汇文馆客户端自带一键解压合并,登录服务器失联后整个客户端不可用。依赖第三方小工具的服务端说挂就挂,本地格式处理才是能长期持有的能力------这也是自己实现 merge 的原因。
6. 部署与配置
依赖:Python 3.10+,py -m pip install pymupdf pillow,7-Zip(官网下载)。把 pdf-bookmark/ 放进 Agent 的技能目录(如 ~/.agents/skills/)。本 Skill 完整代码可在此下载:下载链接。
改动集中在 config.json:
json
{
"archive_dir": "D:\\Books\\PDG书",
"output_dir": "D:\\Books",
"bookmark_tool": "D:\\Tools\\书签软件.exe",
"pdgcnt_editor": "D:\\Tools\\PdgCntEditor.exe",
"sevenzip": "C:\\Program Files\\7-Zip\\7z.exe",
"password_list": ""
}
sevenzip:必改;archive_dir/output_dir:分卷包目录、成品输出目录;bookmark_tool:按 SS 码联网查目录的小工具,本文不提供下载,请自行获取。没有它其余步骤照常------目录文本按标题<TAB>页码每行一条存 txt,从bmtool build接着跑;pdgcnt_editor:可选,手工编辑书签才用;password_list:可选,分卷包加密时放一个每行一个密码的文本文件。
也可以不改文件,用环境变量 PDFBK_7Z、PDFBK_PWLIST、PDFBK_ARCHIVE_DIR、PDFBK_OUTPUT_DIR 覆盖。
分卷包默认按文件名开头的 SS 码识别(如 14080995.zip);命名不带 SS 码就触发时直接报给 Agent。
7. 怎么用
两种用法,脚本可以完全脱离 Agent 单独跑。
方式一:直接用脚本 (不需要任何 Agent)。以 14080995.zip 为例:
bash
# 1. 解压合并。输出会给出总页数、前置页位置和 N,先看一眼
py bmtool.py merge "D:\Books\PDG 书\14080995.zip" "D:\Books\14080995.pdf"
# -> 正文第 1 页 = PDF 第 12 页 (加书签时 N=12)
# 2. 拿目录文本:书签软件填 SS 码查询、点"复制书签",粘贴存成 toc.txt
# (每行格式:标题<TAB>纸书页码;手工整理的目录也行)
# 3. 生成书签文本。前置页相对码 = 该页 PDF 页码 - N,
# 上一步输出 目录=PDF 第 6 页、N=12,所以 目录=-6
py bmtool.py build toc.txt 12 --front "封面=-11,书名=-10,版权=-9,前言=-8,目录=-6" --out outline.txt
# 4. 写入书签并验证
py bmtool.py write "D:\Books\14080995.pdf" outline.txt 12
py bmtool.py verify "D:\Books\14080995.pdf" 12
只有第 2 步需要人(或 GUI 自动化)参与,其余全是本地命令。
方式二:交给 Agent 。配置好 config.json 后一句话触发,比如"PDG 书 有新包,做书",Agent 按 SKILL.md 自己跑完全流程------merge、驱动书签软件查目录、build/write、verify------最后汇报成品路径和验证结果。
8. 其他实现细节
verify是硬闸门:首章节落点、页码单调、末条不超总页数,不过就回查偏移重做;- 已有书签的书默认拒绝覆盖,
--force才重建;标题全是"1、2、3..."的每页一条式垃圾书签(下载站自动生成的)直接替换; - 层级推断按标题规律:
第N章/Part N→1 级、第X节/N.N→2 级、N.N.N/一、→3 级,章标题后紧跟的无编号小标题当子级。四种目录风格(章-节式、Part 式、纯编号式)实测排布正确。
9. 本 Skill 完整代码及下载
本 Skill 完整代码可在此下载:下载链接。
文中提到的所有第三方软件(书签软件、PdgCntEditor、7-Zip 等)均不提供下载,请自行获取。本文仅讨论本地已有文件的格式处理与流程自动化,不提供任何书籍获取渠道;请支持正版,尊重版权。