Skip to content

feat: add cover image support for EPUB output#3

Open
fredchu wants to merge 3 commits intodeusyu:mainfrom
fredchu:feat/cover-image-support
Open

feat: add cover image support for EPUB output#3
fredchu wants to merge 3 commits intodeusyu:mainfrom
fredchu:feat/cover-image-support

Conversation

@fredchu
Copy link
Copy Markdown

@fredchu fredchu commented Mar 21, 2026

Summary

  • Add --cover <path> flag to embed a specific cover image in the EPUB
  • Add --cover-from <epub> flag to auto-extract cover from the original EPUB using OPF metadata (id="cover-image")
  • After extraction, re-embed via Calibre to fix macOS Quick Look thumbnails (Pandoc wraps covers in SVG which Quick Look doesn't render)

Usage

# Explicit cover image
python3 merge_and_build.py --temp-dir ./output --title "Book Title" --cover cover.jpg

# Auto-extract from original EPUB
python3 merge_and_build.py --temp-dir ./output --title "Book Title" --cover-from original.epub

Why Calibre re-embedding?

Pandoc generates cover pages using <svg><image xlink:href="..."/></svg>. This is valid EPUB but macOS Quick Look (Finder spacebar preview) doesn't render it as a thumbnail. Running through Calibre's ebook-convert replaces the SVG wrapper with a standard <img>, which Quick Look recognizes.

Test plan

  • Tested extract_cover_from_epub() with a real EPUB (Animal Spirits) — correctly finds OEBPS/Image00003.jpg (60KB)
  • Tested embed_cover_in_epub() — Calibre re-embedding produces working Quick Look thumbnails
  • Verified macOS Finder spacebar preview shows cover after fix

🤖 Generated with Claude Code

Add --cover and --cover-from flags to merge_and_build.py:

- --cover <path>: Embed a specific cover image in the EPUB
- --cover-from <epub>: Auto-extract cover from an original EPUB using
  OPF metadata (finds the item with id="cover-image")

After extraction, the cover is re-embedded via Calibre's ebook-convert.
This is necessary because Pandoc wraps cover images in an SVG element,
which macOS Quick Look and some e-readers don't render as thumbnails.
Calibre re-embeds the cover as a standard <img>, fixing this.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown
Owner

@deusyu deusyu left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

感谢这个 PR!封面支持是个好需求,不过当前实现的范围我想收敛一下。

核心问题

这个 PR 的核心需求其实很简单:给生成的 EPUB 传入封面即可,或者支持从原始 EPUB 自动提取封面后再传入生成流程。当前实现为了修 macOS Quick Look 预览,新增了一次 EPUB 生成后的二次 epub → epub 处理,这个范围有点偏大,也偏离了需求本身。

具体建议

请改成更小的实现:

  1. merge_and_build.py 支持 --cover <image>
  2. 可以保留 --cover-from <original.epub>,但它只负责从原始 EPUB 提取封面图片
  3. 真正的封面接入点应是 HTML → EPUB 的那次 ebook-convert,直接传 --cover,不要在生成完 EPUB 之后再做二次转换。calibre_html_publish.py 里的 Calibre 命令已经有 --preserve-cover-aspect-ratio,加一个 --cover 参数即可
  4. 不要引入额外的 EPUB 临时重写流程,也不要从 convert.py 反向 import 工具函数(find_calibre_convert 已经存在于 calibre_html_publish.py

OPF 解析

如果保留 --cover-from,建议用 xml.etree.ElementTree(标准库)解析 OPF,而非正则。当前正则假设 id="cover-image" 且属性顺序固定,不够健壮。

这样更符合这个项目当前的复杂度,也更接近我想要的功能边界。期待你的更新!

- Pass --cover directly to Calibre during HTML→EPUB conversion
  instead of doing a separate EPUB→EPUB re-embedding step
- Remove embed_cover_in_epub() and the reverse import from convert.py
- Replace regex OPF parsing with xml.etree.ElementTree (stdlib)
- Add <meta name="cover"> fallback for broader EPUB compatibility

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@fredchu
Copy link
Copy Markdown
Author

fredchu commented Mar 22, 2026

感谢 review!你说得对,二次转换确实偏重了。已按建议重构:

  1. 封面接入点改到 calibre_html_publish.pyconvert_html_with_calibre() 新增 cover 参数,在 HTML→EPUB 那次 Calibre 转换直接传 --cover,不再事后重写
  2. 删除 embed_cover_in_epub() — 整个 EPUB→EPUB 二次转换流程已移除
  3. 删除反向 import — 不再从 convert.py import find_calibre_convert
  4. OPF 解析改用 xml.etree.ElementTree — 替换了正则,同时加了 <meta name="cover" content="..."> 的 fallback,兼容性更好

净减 14 行代码,逻辑更简单。请再看看 🙏

@deusyu
Copy link
Copy Markdown
Owner

deusyu commented Mar 25, 2026

补充一个建议:当前 --cover 已经接入到 HTML -> EPUB 的 Calibre 调用里,这个方向是对的。

不过还有一个行为点建议一起处理:generate_format() 现有的 skip 逻辑只看 HTML 和 images/ 的 mtime,没有把 --cover 本身当成输入依赖。这样如果 book.epub 已经存在,后续再加 --cover,或者换一张新的封面图,只要 HTML 和 images/ 没变化,就会直接 skip,最终保留旧的无封面/旧封面 EPUB。

建议把 cover 文件也纳入 rebuild 判断,至少覆盖这两种情况:

  1. 首次在已有 temp dir 上加 --cover
  2. 更换封面文件后重新运行

这也是后续测试里最值得加的一条检查点。

Add cover file mtime to generate_format() skip logic so that:
1. Adding --cover to an existing temp dir triggers a rebuild
2. Replacing the cover image triggers a rebuild
@fredchu
Copy link
Copy Markdown
Author

fredchu commented Mar 26, 2026

好建议!已修复。

generate_format() 的 skip 逻辑现在把 cover 文件也纳入 mtime 检查:

  • 首次在已有 temp dir 上加 --cover → 触发 rebuild
  • 更换封面图后重新运行 → 触发 rebuild

改动很小(+8 行),核心就是在 html_newer / images_newer 旁边加了 cover_newer 检查。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants