改完 EPUB 元数据阅读器不认?系列字段双写、封面缓存与非破坏式打包

· 约 6 分钟 🏷️ EPUB 元数据编辑

从各处收来的 EPUB,书名张冠李戴、作者留着转换工具的名字、封面缺失、同一系列的分册散落在书架各处——这些都存在同一个地方:包内 OPF 文件的 <metadata> 段。

EPUB 元数据编辑 改起来只是填表单,但改完发现阅读器不认的情况非常常见。这篇按”元数据存在哪 → 为什么不生效 → 非破坏式怎么保证正文不坏”讲清楚。

各阅读器对具体字段的支持情况截至 2026-07-28,软件更新后可能变化;判断是否真的写入成功,永远以解包后 OPF 文件里的实际内容为准。

元数据存在 OPF 的一段 XML 里

解开 EPUB(改名 .zip),META-INF/container.xml 指向的那个 .opf 文件里,头部是这样:

<metadata xmlns:dc="http://purl.org/dc/elements/1.1/">
  <dc:identifier id="bookid">urn:uuid:8f3a...</dc:identifier>
  <dc:title>三体</dc:title>
  <dc:creator>刘慈欣</dc:creator>
  <dc:language>zh-Hans</dc:language>
  <dc:publisher>重庆出版社</dc:publisher>
  <dc:date>2008-01-01</dc:date>
  <dc:subject>科幻</dc:subject>
  <dc:subject>长篇小说</dc:subject>
  <meta name="calibre:series" content="地球往事"/>
  <meta name="calibre:series_index" content="1"/>
  <meta property="dcterms:modified">2026-07-28T10:00:00Z</meta>
  <meta name="cover" content="cover-image"/>
</metadata>

两类元素:dc:* 是 Dublin Core 标准字段(书名、作者、语言这些),<meta> 是扩展字段(系列、封面指针、修改时间)。扩展字段没有统一标准,这是所有兼容性问题的根源。

表单栏位实际写入
书名 / 作者 / 出版社 / 出版日期 / 语言dc:title / dc:creator / dc:publisher / dc:date / dc:language
标签(多个)多个 dc:subject
简介 / 版权dc:description / dc:rights
标识符 / ISBNdc:identifier
系列 + 序号两套 <meta>,见下
封面图片字节 + manifest 项 + 两处标记

系列:必须写两套字段

这是最容易”填了没反应”的一栏,因为历史上有两套做法:

Calibre 系(EPUB2 风格自定义 meta)

<meta name="calibre:series" content="地球往事"/>
<meta name="calibre:series_index" content="1"/>

EPUB3 官方写法(带 refines 的属性链)

<meta property="belongs-to-collection" id="c1">地球往事</meta>
<meta refines="#c1" property="collection-type">series</meta>
<meta refines="#c1" property="group-position">1</meta>

各家读哪套:

阅读器 / 软件读取的字段
Calibrecalibre:series*
静读天下、KOReader 等第三方calibre:series*
Apple Booksbelongs-to-collection 一系
部分国产阅读器两者之一,视版本

只写一套,另一半阅读器就不归组。工具的做法是两套同时写、值保持一致,所以在 Calibre 和 Apple Books 里都能正确成组、按序号排序。

两个实操细节:

  • 系列名必须逐字一致——“地球往事” 和 “地球往事 ” (尾部多个空格)会被当成两个系列
  • 序号可以用小数:番外、加卷用 1.5、2.5,Calibre 一侧排序支持最好

封面:也是两处标记

EPUB2 和 EPUB3 指认封面的方式不同,所以要写两处:

<!-- manifest 里:EPUB3 方式 -->
<item id="cover-image" href="images/cover.jpg"
      media-type="image/jpeg" properties="cover-image"/>

<!-- metadata 里:EPUB2 方式 -->
<meta name="cover" content="cover-image"/>

替换封面时工具的行为分两种:

原书情况处理方式
已有封面覆盖原封面文件的字节,保留原路径,同步更新 manifest 里的 media-type
没有封面新增 images/cover.<ext>,补一条 manifest 项(带 properties="cover-image")和 <meta name="cover">

保留原路径的好处是:原书里所有引用封面的地方(封面页 XHTML、目录)都不用改,不会出现”封面页指向一个已经不存在的图片”。

格式支持 JPG / PNG / WebP / GIF,单张 20MB 内。建议优先 JPG 或 PNG——WebP 在老设备和部分 E-ink 阅读器上仍可能不显示。比例上竖版 2:3(如 1600×2400)在各家书架里观感最一致。

改完不生效?按这个顺序排查

排查顺序现象原因处理
1书名 / 封面还是旧的阅读器缓存了书目和缩略图从书架删除后重新导入
2Calibre 里没变Calibre 读自己的 metadata.db右键 → 从文件格式中读取元数据
3系列不归组阅读器读的是另一套字段工具已双写,检查系列名是否逐字一致
4导入后覆盖了另一本书两本书的 dc:identifier 相同给其中一本换一个新标识符
5阅读进度丢了改了 dc:identifier别改标识符,留空即保留原值
6完全打不开 / 无法解析带 DRM 的商店购买书先用合法手段解除 DRM

最可靠的验证方式:把导出的 .epub 改名成 .zip 解开,直接看 .opf 里的字段值。文件里是新值 + 阅读器显示旧值 = 缓存问题,跟工具无关。

非破坏式是怎么做到的

“只改身份信息、不碰正文”这句话具体拆成六步:

  1. 定位 OPF:读 META-INF/container.xml 拿到 OPF 路径
  2. 原位替换 metadata 块:用正则把 OPF 里 <metadata>…</metadata> 整段换成新生成的内容,OPF 的其余字节(manifest、spine、guide)完全不动
  3. 保留未托管字段:只有工具管理的那批字段会被重新生成,其余原样搬回(清单见下表)
  4. 良构自检:生成的 OPF 会被重新解析一遍,只要出现 XML 解析错误就中止导出并保留原文件不动——不会给你一个坏包
  5. 重打包mimetype 用 STORE(不压缩)写在包内第一个位置,其余文件按原始字节复制、DEFLATE 级别 6 压缩
  6. 只替换两个文件:OPF,以及换封面时的那张图

第 3 步的托管范围:

工具托管(会重写)工具保留(原样搬回)
dc:title dc:creator dc:language dc:publisher dc:description dc:date dc:identifier dc:rights dc:subjectdc:contributor(译者 / 编者)
meta name="cover"其它 dcterms:* 字段
calibre:series / calibre:series_indexCalibre 自定义列
belongs-to-collection / collection-type / group-position其它自定义 <meta>
dcterms:modified(自动刷新为当前时间)

第 5 步的 mimetype 是 OCF 规范的硬要求(必须是首个文件、不压缩、无额外字段),很多手工用压缩软件”重新打个包”的 EPUB 就是在这一步坏掉的——阅读器和 epubcheck 会直接说这不是有效的 EPUB。

dcterms:modified 会自动更新为当前 UTC 时间,这是 EPUB 3 要求的最后修改时间字段,缺失会导致部分校验器报警。

规模与限制

  • 单文件上限 200 MB,多本可一次拖入、打包 ZIP 下载
  • 不支持 DRM 加密书——商店购买的加密包无法解析
  • 只处理 EPUB。Kindle 的 AZW3 / MOBI 需要先转成 EPUB
  • 全程在浏览器本地完成,文件不上传服务器,断网可用

相关

❓ 常见问题

我把书名改了,Calibre 书库里还是旧名字,是没保存成功吗?

多半保存成功了,是 Calibre 自己的数据库没刷新。Calibre 导入时把元数据抄进自己的 metadata.db,之后书架显示的一直是数据库里的值,不会反过来盯着 EPUB 文件看。解法:在 Calibre 里选中该书 → 右键 → 编辑元数据 → 从文件格式中读取元数据(不同版本菜单文字略有差异),或者干脆把书从书库删除后重新添加。验证是否真的改成功:把 .epub 改名成 .zip 解开,用文本编辑器打开里面的 .opf 文件,看 <dc:title> 是不是新值——这是唯一的事实来源。

系列名和序号为什么要写两套字段?

因为历史上有两套互不兼容的做法。EPUB 2 时代没有系列的官方字段,Calibre 用自定义 <meta name="calibre:series">calibre:series_index 占了这个位置,事实上成了标准,Calibre、静读天下、KOReader 等都读它;EPUB 3 后来定义了官方写法 belongs-to-collection 配合 refinescollection-type="series"group-position,Apple Books 一类走这条。只写一套,另一半阅读器就不认。工具的做法是两套同时写、内容保持一致,覆盖面最大。序号支持小数(1.5 表示番外或加卷),Calibre 一侧对小数支持最好。

标识符 / ISBN 那栏到底该不该改?

默认别动dc:identifier 是这本书的身份证,阅读器和书库软件用它判断"是不是同一本书"——改了它,同一本书可能被当成新书重新导入(阅读进度、笔记、书签有丢失风险);不改它,重新导入时可能被识别为已有书而沿用旧的库记录。什么时候该改:(1) 你把两本不同的书从同一个模板导出,标识符撞了,需要给其中一本换成新的 UUID;(2) 你确实拿到了正确的 ISBN 想补进去。留空时工具会保留原值不动;原书连标识符都没有时会自动生成一个 urn:uuid: 形式的新标识。

语言那栏填 zh 还是 zh-CN、zh-Hans?

BCP 47 语言标签,常用几个:zh-Hans(简体中文)、zh-Hant(繁体中文)、zh-CN(中国大陆,通常等同简体)、zh-TWenja它不是装饰——阅读器按这个字段选择断行规则、默认字体和连字符处理,日语书填成 en 会得到很奇怪的排版,中文书填对了才会用中文断行而不是按空格断词。不确定时简体书填 zh-Hans、繁体书填 zh-Hant 最稳。

一本书有作者 + 译者 + 编者,怎么填?

工具托管的是一个 dc:creator(作者)。其它角色用 dc:contributor 表示,工具不编辑但会原样保留——原书里已有的译者、编者信息不会因为你改书名而丢失。想把译者显示在作者位置,可以在作者栏写成"某某 著 / 某某 译"这样的一行文本,代价是阅读器会把它当成一个完整的作者名(按作者分组时会单独成组)。同理保留的还有:其它 dcterms:* 字段、Calibre 的自定义列、以及工具不认识的自定义 <meta>

重新打包后文件大小变了,正文是不是被动过?

正文一个字节都没动。工具遍历原包里的每个文件,按原始字节写进新包,只替换 OPF 和(如果你换了封面)封面图片这两项。大小变化来自重新压缩——新包统一用 DEFLATE 级别 6,而原包可能用了不同的压缩级别或存储方式,所以最终字节数会略有出入。结构上有一处是硬要求mimetype 必须是包里第一个文件且不压缩(STORE),这是 OCF 规范的规定,否则部分阅读器和 epubcheck 会直接判定"不是有效的 EPUB"——工具重打包时会保证这一点。

🏷️ 打开 EPUB 元数据编辑 改书名/作者/出版社/系列/标签·替换封面·非破坏保留正文·Calibre+EPUB3 双字段·多本批量·本地处理