EPUB Creation
Create EPUB ebook files from markdown or plain text content. EPUB is the universal ebook format — opens natively in Apple Books (iPhone/iPad/Mac), Google Play Books, Kobo, and most readers. Kindle uses MOBI/AZW3 but can accept EPUB via Send-to-Kindle.
When to Use
- User asks to "make a book", "save as ebook", "EPUB", "iPhone Books", "도서 앱"
- User has long-form content (novel, guide, collection) and wants it readable on a phone/tablet
- User asks how to read something on their e-reader
- User asks for an HTML site / converter page ("html사이트", "변환 사이트", "텍스트를 epub으로") that turns TXT into EPUB → build it client-side, see below
- User has an existing EPUB missing a cover and wants to add/replace the cover photo ("표지없는 epub에 표지 추가", "표지 교체") → patch the zip in place, see "Replacing/adding a cover in an EXISTING EPUB" in
references/browser-side-epub.md and replaceEpubCover() in the template
Two routes
- Python/ebooklib script (this SKILL.md) — batch conversion, agent-driven, delivers a .epub file.
- Browser-side single-HTML converter — when the user wants a site/tool rather than one converted file: everything runs in the browser (no upload, works offline, WebView-embeddable). Full structure, zip rules, and Korean TXT intake in
references/browser-side-epub.md; ready-to-adapt generator core (EPUB 3.0 + NCX, cover image, offline ZIP fallback, EUC-KR sniffing, chapter detection) in templates/browser-epub-builder.js. Known-good full implementation: C:/Users/okya1/txt2epub/index.html ("제본소").
Prerequisites
# Use uv on Windows (pip may not be available in Hermes venv)
uv pip install ebooklib --python "C:/Users/<user>/AppData/Local/Programs/Python/Python312/python.exe"
# Or on Linux/macOS:
pip install ebooklib
ebooklib depends on lxml. If lxml is missing, install it first.
Workflow
- Prepare source text — markdown with
#/##/### headers as chapter boundaries
- Run the conversion script — see
scripts/make_epub.py in this skill
- Deliver the .epub file — via MEDIA: path, AirDrop, email, or cloud drive
Chapter Splitting Logic
Split on markdown headers:
# Title → book title (skip as chapter)
## Part/Section → major division
### Chapter N → individual chapter
Each chapter becomes a separate XHTML file inside the EPUB. The TOC (table of contents) is auto-generated from chapter titles.
Markdown → HTML Conversion
Minimal conversion needed:
**bold** → <strong>
*italic* → <em>
--- → <hr/>
- Double newlines → paragraph breaks (
<p>)
- Single newlines within paragraph →
<br/>
CSS for Korean/Japanese text
body { font-family: "Apple SD Gothic Neo", "Malgun Gothic", "Noto Sans KR", sans-serif; line-height: 1.8; }
p { text-indent: 1em; margin: 0.5em 0; }
Pitfalls
- Empty chapters crash ebooklib. If a header is followed by another header with no content between them, the resulting EpubHtml has an empty body. lxml's
document_fromstring raises ParserError: Document is empty. FIX: filter out chapters where content.strip() is empty or < 10 chars before adding to the book.
- XML declaration in chapter content. Do NOT include
<?xml version="1.0"?> in chapter.content — ebooklib adds its own. Including it can cause parse errors.
- Pass
content as a str wrapped in <html><body>, not as bytes. Setting chap.content = html_string.encode("utf-8") (bytes) or a bare HTML fragment without <html><body>...</body></html> can trigger lxml.etree.ParserError: Document is empty during write_epub. The reliable pattern (verified on Windows/lxml): chap.content = '<html><body>' + body_html + '</body></html>' as a plain str. ebooklib handles encoding itself.
- Special characters in titles. Escape
&, <, > in chapter titles before embedding in HTML.
- ebooklib not in Hermes venv. The Hermes agent venv often lacks pip. Use
uv pip install --python <system-python-path> to install into the system Python, then run the script with that Python explicitly.
- Spine order matters.
book.spine = ["nav"] + epub_chapters — nav must come first or the TOC won't render on some readers.
Delivery to iPhone Books
Tell the user (pick whichever fits their setup):
- AirDrop (easiest): send .epub → tap → opens in Books
- KakaoTalk/email to self: open attachment → share sheet → "Books"
- iCloud Drive: upload → Files app → tap → share → Books
- USB (Windows): connect iPhone → iTunes/Finder → Books → drag .epub
Verification
After generating, confirm:
- File exists and is > 0 bytes
- Chapter count matches expected
- Open with
epub.EpubBook() reader to validate structure (optional)
Related skills
pdf (PDF creation/manipulation), docx (Word documents).
1---2name: epub-creation3description: Create EPUB ebooks from markdown/text for e-readers.4---56# EPUB Creation78Create EPUB ebook files from markdown or plain text content. EPUB is the universal ebook format — opens natively in Apple Books (iPhone/iPad/Mac), Google Play Books, Kobo, and most readers. Kindle uses MOBI/AZW3 but can accept EPUB via Send-to-Kindle.910## When to Use1112- User asks to "make a book", "save as ebook", "EPUB", "iPhone Books", "도서 앱"13- User has long-form content (novel, guide, collection) and wants it readable on a phone/tablet14- User asks how to read something on their e-reader15- User asks for an **HTML site / converter page** ("html사이트", "변환 사이트", "텍스트를 epub으로") that turns TXT into EPUB → build it client-side, see below16- User has an **existing EPUB missing a cover** and wants to add/replace the cover photo ("표지없는 epub에 표지 추가", "표지 교체") → patch the zip in place, see "Replacing/adding a cover in an EXISTING EPUB" in `references/browser-side-epub.md` and `replaceEpubCover()` in the template1718## Two routes19201. **Python/ebooklib script** (this SKILL.md) — batch conversion, agent-driven, delivers a .epub file.212. **Browser-side single-HTML converter** — when the user wants a *site/tool* rather than one converted file: everything runs in the browser (no upload, works offline, WebView-embeddable). Full structure, zip rules, and Korean TXT intake in `references/browser-side-epub.md`; ready-to-adapt generator core (EPUB 3.0 + NCX, cover image, offline ZIP fallback, EUC-KR sniffing, chapter detection) in `templates/browser-epub-builder.js`. Known-good full implementation: `C:/Users/okya1/txt2epub/index.html` ("제본소").2223## Prerequisites2425```bash26# Use uv on Windows (pip may not be available in Hermes venv)27uv pip install ebooklib --python "C:/Users/<user>/AppData/Local/Programs/Python/Python312/python.exe"28# Or on Linux/macOS:29pip install ebooklib30```3132ebooklib depends on lxml. If lxml is missing, install it first.3334## Workflow35361. **Prepare source text** — markdown with `#`/`##`/`###` headers as chapter boundaries372. **Run the conversion script** — see `scripts/make_epub.py` in this skill383. **Deliver the .epub file** — via MEDIA: path, AirDrop, email, or cloud drive3940## Chapter Splitting Logic4142Split on markdown headers:43- `# Title` → book title (skip as chapter)44- `## Part/Section` → major division45- `### Chapter N` → individual chapter4647Each chapter becomes a separate XHTML file inside the EPUB. The TOC (table of contents) is auto-generated from chapter titles.4849## Markdown → HTML Conversion5051Minimal conversion needed:52- `**bold**` → `<strong>`53- `*italic*` → `<em>`54- `---` → `<hr/>`55- Double newlines → paragraph breaks (`<p>`)56- Single newlines within paragraph → `<br/>`5758## CSS for Korean/Japanese text5960```css61body { font-family: "Apple SD Gothic Neo", "Malgun Gothic", "Noto Sans KR", sans-serif; line-height: 1.8; }62p { text-indent: 1em; margin: 0.5em 0; }63```6465## Pitfalls6667- **Empty chapters crash ebooklib.** If a header is followed by another header with no content between them, the resulting EpubHtml has an empty body. lxml's `document_fromstring` raises `ParserError: Document is empty`. FIX: filter out chapters where `content.strip()` is empty or < 10 chars before adding to the book.68- **XML declaration in chapter content.** Do NOT include `<?xml version="1.0"?>` in `chapter.content` — ebooklib adds its own. Including it can cause parse errors.69- **Pass `content` as a str wrapped in `<html><body>`, not as bytes.** Setting `chap.content = html_string.encode("utf-8")` (bytes) or a bare HTML fragment without `<html><body>...</body></html>` can trigger `lxml.etree.ParserError: Document is empty` during `write_epub`. The reliable pattern (verified on Windows/lxml): `chap.content = '<html><body>' + body_html + '</body></html>'` as a plain `str`. ebooklib handles encoding itself.70- **Special characters in titles.** Escape `&`, `<`, `>` in chapter titles before embedding in HTML.71- **ebooklib not in Hermes venv.** The Hermes agent venv often lacks pip. Use `uv pip install --python <system-python-path>` to install into the system Python, then run the script with that Python explicitly.72- **Spine order matters.** `book.spine = ["nav"] + epub_chapters` — nav must come first or the TOC won't render on some readers.7374## Delivery to iPhone Books7576Tell the user (pick whichever fits their setup):771. **AirDrop** (easiest): send .epub → tap → opens in Books782. **KakaoTalk/email to self**: open attachment → share sheet → "Books"793. **iCloud Drive**: upload → Files app → tap → share → Books804. **USB (Windows)**: connect iPhone → iTunes/Finder → Books → drag .epub8182## Verification8384After generating, confirm:85- File exists and is > 0 bytes86- Chapter count matches expected87- Open with `epub.EpubBook()` reader to validate structure (optional)8889## Related skills9091`pdf` (PDF creation/manipulation), `docx` (Word documents).