calibre 电子书编辑工具 API 深度指南:Container 容器模型与 Polish 模块实战
【免费下载链接】calibreThe official source code repository for the calibre ebook manager项目地址: https://gitcode.com/GitHub_Trending/ca/calibre
本指南以 calibre 官方仓库 manual/polish.rst 的 API 文档为骨架,系统讲解电子书编辑工具(E-book editing tools / Edit Book)的核心架构:Container容器对象如何把一本书表示为“HTML + 资源文件”的集合,以及calibre.ebooks.oeb.polish.*各模块提供的文件管理、HTML 修复、封面处理、CSS 清理、目录(ToC)生成、文件拆分合并等可编程能力。读完本文,你将掌握如何用几行 Python 代码对 EPUB/AZW3 书执行批量编辑,并能在 E-book editor 插件中直接操作当前打开的书籍。
概述:编辑工具的两大组成
calibre 的电子书编辑工具由两部分构成(见 manual/polish.rst):
Container容器对象(定义于calibre.ebooks.oeb.polish.container):将一本书表示为一个文件夹里的 HTML 与资源文件集合,是所有编辑操作的数据中枢;- 一组模块级函数工具:分布在
calibre.ebooks.oeb.polish.*的各个子模块中,例如replace、pretty、jacket、split、cover、css、toc、fonts,用于在容器上执行具体操作。
两者配合,即可对书籍做无副作用的程序化编辑:所有操作都作用于容器,最后统一commit回磁盘。
获取 Container 对象
命令行 / 脚本场景
对一个位于某路径的书文件(EPUB、AZW3、MOBI 等)获取容器:
from calibre.ebooks.oeb.polish.container import get_container container = get_container('Path to book file', tweak_mode=True)get_container的原型见 container.py:
def get_container(path, log=None, tdir=None, tweak_mode=False, ebook_cls=None) -> Container:其行为要点:
- 根据文件扩展名自动选择容器实现:
.azw3、.mobi、.original_azw3、.original_mobi使用AZW3Container,.kepub、.original_kepub使用KEPUBContainer,其余(含目录)默认使用EpubContainer; - EPUB 会被解压到临时目录(
PersistentTemporaryDirectory('_epub_container'))再解析,is_dir为 True 时按目录方式复制; tweak_mode是解析模式开关:True表示“编辑模式”(HTML/CSS 解析更宽容,保留原始格式细节,供编辑器使用),False表示“打磨模式”(polish,可对解析做预处理)。源码中ContainerBase明确注释:tweak_mode = False(polishing 使用),编辑器则使用tweak_mode=True(见 container.py)。
插件场景:获取正在编辑的书
如果你正在为E-book editor(Edit Book)编写插件,可用模块级函数获取当前编辑中的容器:
from calibre.gui2.tweak_book import current_container container = current_container() if container is None: report_error # No book has been opened yet该函数的实现位于 src/calibre/gui2/tweak_book/init.py,内部就是一个模块级全局变量_current_container,由set_current_container()在打开书籍时设置。因此在编辑器未打开任何书时,current_container()返回None,插件代码必须做判空处理。
此外,在插件Tool子类内部,self.current_container属性直接封装了上面的调用(见 plugin.py)。
Container 对象:电子书的统一视图
Container类完整定义于 src/calibre/ebooks/oeb/polish/container.py。其文档字符串定义了三个核心概念:
- 根目录(root folder):电子书的基准目录,书内所有文件都在该目录或其子目录下;
- Names:相对于根目录的文件路径,永远使用 POSIX 分隔符(
/)、不带 URL 引号、且处于 NFC Unicode 规范化形式。Names 是容器内文件的“规范标识符”,容器的大部分方法都以 name 为参数; - Clones:容器支持高效的磁盘克隆(
clone_data/data_for_clone),这是 E-book editor 实现检查点(checkpoint)/ 撤销功能的基础。正因如此,永远不要直接访问文件系统,应通过raw_data()或open()读写书内文件。
各容器子类通过类属性区分能力:book_type(epub/azw3)、is_dir、SUPPORTS_TITLEPAGES、SUPPORTS_FILENAMES、MAX_HTML_FILE_SIZE(EPUB 容器为 260 KiB)。
关键方法速览
| 方法 | 作用 |
|---|---|
raw_data(name)/open(name, mode) | 读取/写入书内文件(推荐,兼容克隆机制) |
parsed(name) | 返回文件的解析树(HTML/CSS/XML) |
replace(name, obj) | 用新解析对象替换某文件内容并标记 dirty |
dirty(name) | 标记文件已修改,等待提交 |
commit(outpath=None, keep_parsed=False) | 把所有 dirty 文件写回磁盘/写出书文件 |
add_file(...) | 添加文件,自动登记 OPF manifest 与 spine |
rename(current_name, new_name) | 重命名文件并处理 OPF |
remove_item(name) | 从容器删除文件(含 manifest/guide) |
add_name_to_manifest(name) | 为文件创建 manifest 条目,返回 item id |
manifest_has_name(name)/make_name_unique(name) | manifest 检查与重名处理 |
opf()/mi()/opf_version() | 访问 OPF 解析树、元数据对象与版本 |
spine_items/spine_names()/set_spine(...) | 阅读与重排 spine |
href_to_name(href, base)/name_to_href(name, base) | href(带引号)与 name 互转 |
iterlinks(name) | 遍历文件中的链接(含行号) |
compare_to(other) | 对比两个容器差异(测试用) |
所有写操作(如add_file、dirty)最终由commit()汇总:commit()遍历self.dirtied集合逐个调用commit_item()(见 container.py)。修改过的文件只有调用commit()才会真正落盘。
管理容器中的组件文件(replace 模块)
模块calibre.ebooks.oeb.polish.replace提供三类文件级操作(源码见 replace.py)。
replace_links —— 批量替换链接
def replace_links(container, link_map, frag_map=lambda name, frag: frag, replace_in_opf=False):遍历容器内所有文件,把指向旧文件名的链接改为新文件名:
link_map:{旧规范名: 新规范名}映射,例如{'images/old.png': 'images/new.png'};frag_map:可调用对象,接收(name, anchor)返回新锚点,用于同时改写 HTML 内锚点(如#chapter2);replace_in_opf:为False(默认)时跳过 OPF 文件,为True时连 OPF 里的 href 一并替换。
实现上由LinkReplacer类配合container.replace_links(name, repl)逐文件执行。
rename_files —— 重命名并自动修复链接
def rename_files(container, file_map):file_map:{旧规范名: 新规范名},例如{'text/chapter1.html': 'chapter1.html'};- 自动调用
container.rename()并随后replace_links(container, link_map, replace_in_opf=True),因此所有指向旧文件名的链接会被同步更新; - 安全性检查:拒绝循环重命名(目标同时也是源)、目标已存在、以及目标名重复;大小写变化在大小写不敏感文件系统上会被特殊处理。
get_recommended_folders —— 推荐文件夹
def get_recommended_folders(container, names):根据容器内同类文件多数所在位置,为给定文件名推荐存放目录;若某种类型不存在,则推荐 OPF 所在文件夹。内部按 MIME 类型归类(text / style / font / opf / toc 等,见mt_to_category),同名工具被 Edit Book 的“Rationalize Folders”(整理文件夹)功能使用。
同模块还提供replace_ids(container, id_map)(批量改写 id 及指向它们的 idref)、smarten_punctuation(container, report)(智能标点转换)、replace_file(...)(用外部文件替换书内文件)、remove_links_to(container, predicate)(按谓词删除链接)等函数。
美化打印与自动修复解析错误(pretty 模块)
模块calibre.ebooks.oeb.polish.pretty提供对 HTML/CSS/XML 的美化与修复能力(源码见 pretty.py):
| 函数 | 行为 |
|---|---|
fix_html(container, raw) | 用HTML5 解析算法修复raw字符串中的解析错误,返回序列化后的 HTML |
fix_all_html(container) | 对容器内所有 HTML 文档执行修复(标记 dirty 触发重写) |
pretty_html(container, name, raw) | 美化单个 HTML 字符串 |
pretty_css(container, name, raw) | 美化单个 CSS 字符串 |
pretty_xml(container, name, raw) | 美化单个 XML 字符串;若name是 OPF,还会执行 OPF 专属美化(pretty_opf) |
pretty_all(container) | 美化容器内全部 HTML/CSS/XML 文件 |
内部实现要点:
pretty_html先经parse_xhtml解析成树,再通过pretty_html_tree对块级元素重新缩进;pretty_script_or_style专门处理<script>/<style>内的内容;pretty_css用parse_css解析后序列化,实现 CSS 规则的统一缩进;fix_all_html并不逐个比较新旧内容,而是直接对每个 OEB 文档调用parsed(name)+dirty(name),让 HTML5 解析器在重写时自动纠正错误。
这两个函数集正是 Edit Book 中Fix HTML与Pretty Print工具的后端:GUI 菜单Tools → Fix HTML/Tools → Pretty Print(见Boss.fix_html与Boss.pretty_print,boss.py)。
管理书籍封套(jacket 模块)
模块calibre.ebooks.oeb.polish.jacket用于管理 EPUB 的封套页(jacket,书籍开头的装饰页)(源码见 jacket.py):
remove_jacket(container):删除书中已存在的封套页及其图片资源(remove_jacket_images);add_or_replace_jacket(container):为书添加封套页;若已存在旧式/现式封套则先替换。
实现细节:find_existing_jacket在 spine 中定位封套文档,is_legacy_jacket/is_current_jacket用于识别不同版本的封套结构,render_jacket根据书籍元数据渲染封套 HTML,replace_jacket完成内容替换并处理 OPF 条目。
文件的拆分与合并(split 模块)
模块calibre.ebooks.oeb.polish.split提供对 HTML 文档的拆分与合并(源码见 split.py)。
split —— 单点拆分
def split(container, name, loc_or_xpath, before=True, totals=None):把name指定的文件在loc_or_xpath处一分为二,自动迁移所有受影响的链接与引用:
loc_or_xpath:XPath 表达式,如//h:div[@id="split_here"];也可传内部使用的loc(预览面板拆分时使用);before=True表示在命中元素之前拆分,否则在之后;- 失败保护:若定位节点在
<table>内或为<body>标签则抛出AbortError;若 HTML 解析计数不一致,会强制用 HTML5 解析器重试,并提示“Try running the Fix HTML tool before splitting”。
multisplit —— 多点拆分
def multisplit(container, name, xpath, before=True):按 XPath 命中的所有元素将文件拆成多份,逻辑基于split的循环调用。
merge —— 合并文件
def merge(container, category, names, master):把同类别(category)的一组文件合并进master:
- HTML 合并由
merge_html实现,insert_page_breaks=False时按顺序拼接各文件 body 内容,并把锚点改名以保证唯一性(unique_anchor、remove_name_attributes); - CSS 合并由
merge_css实现,把各样式表规则并入主样式表; - 合并后调用
remove_names_from_toc等清理工作,确保 ToC 不再指向被合并掉的旧文件。
管理封面(cover 模块)
模块calibre.ebooks.oeb.polish.cover负责书籍封面的设置与识别(源码见 cover.py):
| 函数 | 行为 |
|---|---|
set_cover(container, cover_path, report=None, options=None) | 把外部图片设为书籍封面:写入封面图、生成/替换封面页(EPUB 走set_epub_cover,AZW3 走set_azw3_cover),并清理旧封面残留 |
mark_as_cover(container, name) | 把容器内已有文件标记为封面(EPUB 走mark_as_cover_epub,AZW3 走mark_as_cover_azw3) |
mark_as_titlepage(container, name, move_to_start=True) | 把指定文件标记为书名页(titlepage),默认移到 spine 开头 |
find_cover_image(container, strict=False) | 智能探测书籍封面图 |
has_epub_cover(container) | 判断 EPUB 是否已有封面 |
EPUB 封面处理会同时维护三处状态:OPF 的metadata(meta name="cover")、manifest中封面图条目、以及 spine 中的封面页;create_epub_cover负责生成包含封面图的书名页 HTML,remove_cover_image_in_page在更换封面时清理旧图。
处理 CSS(css 模块与 fonts 模块)
remove_unused_css —— 清理无用样式
def remove_unused_css( container, report=None, remove_unused_classes=False, merge_rules=False, merge_rules_with_identical_properties=False, remove_unreferenced_sheets=False, ):删除书中所有不匹配任何实际内容的 CSS 规则(源码见 css.py):
remove_unused_classes=True:同时移除 HTML 中不匹配任何 CSS 规则的class属性;merge_rules=True:合并选择器相同的规则(merge_identical_selectors);merge_rules_with_identical_properties=True:合并属性完全相同的规则;remove_unreferenced_sheets=True:移除未被任何内容引用的样式表文件;- 内部通过
mark_used_selectors用 CSS 选择器引擎(select)对全部文档做使用标记,get_imported_sheets处理@import链(默认递归深度 10),未命中的选择器与规则进入removal_stats被删除。
filter_css —— 过滤 CSS 属性
def filter_css(container, properties, names=()):从样式表中删除指定 CSS 属性(如filter_css(container, {'color'})),可限定只处理names列出的文件;transform_css是更通用的“CSS 变换”入口(可传入transform_sheet/transform_style回调,支持把font-size转成pt/em这类整体重写),二者实现均在 css.py。
change_font —— 全局更换字体
def change_font(container, old_name, new_name=None):把字体族old_name全局替换为new_name(源码见 fonts.py):
- 作用于样式表、
<style>标签与内联style属性三处; - 若
old_name是内嵌字体,替换时会被一并移除; new_name=None表示只移除该字体族而非替换;- 底层由
font_family_data汇总所有字体声明,change_font_in_sheet/change_font_in_declaration分别处理样式表与声明。
处理目录 ToC(toc 模块)
模块calibre.ebooks.oeb.polish.toc提供目录的生成与提交能力(源码见 toc.py)。
从 XPath 生成 ToC
def from_xpaths(container, xpaths, prefer_title=False):用一组 XPath 表达式生成目录,每个表达式对应一级:['//h:h1', '//h:h2', '//h:h3']会从<h1>/<h2>/<h3>生成三级目录。实现会自动剔除在所有 spine 文档中均无匹配的“空层级”,并用node_level_map维护父子关系;prefer_title=True时优先取title属性作为条目文本。
从链接与文件生成 ToC
from_links(container):把 spine 文档中已有的<a>链接结构转换为目录(适用于从 HTML 链接推断章节结构);from_files(container):把每个 spine 文件作为目录的一个条目(文件级目录)。
提交与内联目录
commit_toc(container, toc, lang=None, uid=None):根据 EPUB 版本写入 NCX 或 EPUB3nav文档——commit_ncx_toc负责 EPUB2 的toc.ncx,commit_nav_toc负责 EPUB3 的nav.xhtml,并同步ensure_container_has_nav/set_landmarks(landmarks 导航);create_inline_toc(container, title=None):生成内联目录页(toc_to_html),把当前 ToC 渲染成书内一个 HTML 页面并插入 spine。
Edit Book 插件工具类(Tool)
为 E-book editor 编写插件时,工具类继承:
class Tool: # calibre.gui2.tweak_book.plugin.Tool定义于 plugin.py,常用成员:
self.plugin:所属calibre.customize.Plugin对象;self.boss:全局Boss对象,用于控制用户界面(get_boss());self.gui:编辑器主窗口;self.current_container:当前编辑书籍的Container(即current_container());self.name:工具的唯一名称(作 key 使用);allowed_in_toolbar/allowed_in_menu:用户是否可把该工具放入插件工具栏/插件菜单;- 必须覆写的方法:
create_action、register_shortcut。
register_shortcut(qaction, unique_name, default_keys=(), ...)用于注册快捷键,例如default_keys=('Ctrl+J', 'F9');注册后用户可在编辑器的快捷键偏好设置中自定义,若与内置快捷键或用户配置冲突则自动忽略。
控制编辑器用户界面(Boss)
编辑器的用户界面由一个全局Boss对象统一控制(calibre.gui2.tweak_book.boss.Boss,见 boss.py),插件代码可通过get_boss()获取它来执行常见任务,常用方法包括:
| 方法 | 用途 |
|---|---|
currently_editing() | 当前是否处于编辑状态 |
open_book(path, ...)/new_book()/import_book(path) | 打开/新建/导入书籍 |
book_opened(job) | 书籍打开完成的回调(可在此刷新插件 UI) |
add_file()/add_files()/add_cover() | 添加文件/封面 |
edit_toc()/insert_inline_toc() | 编辑 ToC / 插入内联目录 |
polish(action, name, parent=None) | 调用 polish 工具(Editor Polish 菜单入口) |
transform_html()/transform_styles() | 打开 HTML/CSS 变换对话框 |
manage_fonts()/manage_fonts_embed()/manage_fonts_subset() | 字体管理(内嵌/子集化) |
rationalize_folders() | 整理文件夹结构 |
rename_requested(...)/bulk_rename_requested(...) | 文件重命名/批量重命名 |
do_global_undo()/do_global_redo() | 全局撤销/重做 |
add_savepoint(msg)/rewind_savepoint() | 保存点管理(撤销层级) |
show_current_diff(...)/compare_book() | 显示当前改动差异 / 对比书籍 |
set_modified() | 标记书籍已修改 |
fix_html(current)/pretty_print(current) | 修复 HTML / 美化打印 |
完整示例:脚本化打磨一本书
把上述 API 串起来,一个典型的“打磨流程”脚本大致如下(脚本式使用需在 calibre 的 Python 环境运行,例如calibre-debug -e script.py,入口见 develop/calibre-debug):
from calibre.ebooks.oeb.polish.container import get_container from calibre.ebooks.oeb.polish.pretty import fix_all_html, pretty_all from calibre.ebooks.oeb.polish.css import remove_unused_css from calibre.ebooks.oeb.polish.toc import from_xpaths, commit_toc # 1. 以打磨模式打开书籍(tweak_mode=False,即 polish 模式) container = get_container('/path/to/book.epub') # 2. 修复所有 HTML 解析错误并美化 fix_all_html(container) pretty_all(container) # 3. 清理无用 CSS,合并重复规则 remove_unused_css(container, remove_unused_classes=True, merge_rules=True, remove_unreferenced_sheets=True) # 4. 按 h1/h2 重新生成三级目录 toc = from_xpaths(container, ['//h:h1', '//h:h2', '//h:h3']) commit_toc(container, toc) # 5. 写回磁盘 container.commit()若是在E-book editor 插件里,则第 1 步改为from calibre.gui2.tweak_book import current_container,第 5 步改为依赖编辑器的“保存”流程,插件只需对容器做修改即可(编辑器通过克隆容器实现撤销/恢复)。
参考资源
- API 文档原文:manual/polish.rst
- 容器核心实现:src/calibre/ebooks/oeb/polish/container.py
- 各工具模块:replace.py、pretty.py、jacket.py、split.py、cover.py、css.py、toc.py、fonts.py
- 编辑器 GUI 集成:src/calibre/gui2/tweak_book/init.py 中的
current_container、plugin.py 中的Tool、boss.py 中的Boss - 插件编写综合指南:manual/creating_plugins.rst(含 manual/plugin_examples/editor_demo/main.py 可运行的编辑器插件示例)
- 单元测试:src/calibre/ebooks/oeb/polish/tests/(container、split、cascade、structure 等测试覆盖了上述多数 API)
【免费下载链接】calibreThe official source code repository for the calibre ebook manager项目地址: https://gitcode.com/GitHub_Trending/ca/calibre
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考