mirror of
https://github.com/magnus919/agent-skills.git
synced 2026-09-16 14:06:27 +03:00
Complete EPUB creation, editing, validation, and knowledge extraction skill for the Agent Skills open format. Built from spec research, real EPUB testing on 2.1MB commercial Apress title, and Apple Books compatibility verification on macOS 26. Scripts (11): epub-scaffold — Create valid EPUB3 with cover XHTML, Apple Books CSS epub-edit — Surgical editing (8 subcommands, epublib) epub-info — Structure/metadata dump as JSON epub-text — Clean text extraction, per-chapter or single-file epub-extract-knowledge — Heuristic + LLM extraction (env var auto-detect) epub-validate — EPUBCheck or Python fallback validation epub-images — List/extract all images with cover detection epub-batch — Multi-file processing (extract-text, validate, metadata) epub-convert — EPUB2→EPUB3 conversion with validation epub-repair — Diagnose & auto-fix common structural issues epub-cover — Add cover XHTML wrapper for Apple Books compatibility References (9): epub-format-internals.md, python-libraries.md, spec-and-validation.md, tutorials-and-guides.md, agent-capability-discovery.md, fixed-layout-epub.md, accessibility.md, media-overlays.md, apple-books-compatibility.md (NEW — verified on macOS 26) Test: 46/46 passing (test_epub_skill.sh)
326 lines
11 KiB
Markdown
326 lines
11 KiB
Markdown
# Python Libraries for EPUB
|
|
|
|
Three main Python libraries exist for EPUB work. This reference covers their
|
|
capabilities, tradeoffs, and when to use each.
|
|
|
|
## Comparison
|
|
|
|
| Library | License | EPUB | Status | PyPI | Best For |
|
|
|---------|---------|------|--------|------|----------|
|
|
| **EbookLib** | AGPL | 2/3 | Active (v0.20) | `ebooklib` | Creating & reading from scratch |
|
|
| **epublib** | MIT | 3 only | Active (2026) | `epublib` | Editing existing EPUBs |
|
|
| **pyepub** | MIT | 2 only | Discontinued (2020) → `yael` | `pyepub` | Do not use for new work |
|
|
|
|
## EbookLib
|
|
|
|
The most widely used Python EPUB library. Handles reading, writing, and basic
|
|
manipulation of EPUB2 and EPUB3 files.
|
|
|
|
### Installation
|
|
|
|
```bash
|
|
pip install EbookLib
|
|
```
|
|
|
|
### Reading
|
|
|
|
```python
|
|
import ebooklib
|
|
from ebooklib import epub
|
|
|
|
book = epub.read_epub('book.epub')
|
|
|
|
# Get all items of a specific type
|
|
for item in book.get_items_of_type(ebooklib.ITEM_DOCUMENT):
|
|
print(item.get_name(), item.get_content())
|
|
|
|
# Get images
|
|
for image in book.get_items_of_type(ebooklib.ITEM_IMAGE):
|
|
with open(image.get_name(), 'wb') as f:
|
|
f.write(image.get_content())
|
|
|
|
# Access metadata
|
|
title = book.get_metadata('DC', 'title')
|
|
creator = book.get_metadata('DC', 'creator')
|
|
```
|
|
|
|
### Creating
|
|
|
|
```python
|
|
from ebooklib import epub
|
|
|
|
book = epub.EpubBook()
|
|
book.set_identifier('urn:uuid:123e4567-e89b-12d3-a456-426614174000')
|
|
book.set_title('My Book')
|
|
book.set_language('en')
|
|
book.add_author('Author Name')
|
|
|
|
# Create a chapter
|
|
c1 = epub.EpubHtml(title='Chapter 1', file_name='chap1.xhtml', lang='en')
|
|
c1.content = '<h1>Chapter 1</h1><p>Content here.</p>'
|
|
book.add_item(c1)
|
|
|
|
# Add an image
|
|
img = epub.EpubImage(
|
|
uid='cover',
|
|
file_name='images/cover.jpg',
|
|
media_type='image/jpeg',
|
|
content=open('cover.jpg', 'rb').read()
|
|
)
|
|
book.add_item(img)
|
|
|
|
# Define spine (reading order)
|
|
book.spine = ['nav', c1]
|
|
|
|
# Add navigation (EPUB3) and NCX (EPUB2 compat)
|
|
book.add_item(epub.EpubNcx())
|
|
book.add_item(epub.EpubNav())
|
|
|
|
# Write
|
|
epub.write_epub('out.epub', book)
|
|
```
|
|
|
|
### Key Classes
|
|
|
|
| Class | Purpose |
|
|
|-------|---------|
|
|
| `EpubBook` | The book container |
|
|
| `EpubHtml` | XHTML content document (chapter) |
|
|
| `EpubImage` | Image resource |
|
|
| `EpubItem` | Generic resource (CSS, fonts, etc.) |
|
|
| `EpubNcx` | EPUB2 NCX navigation |
|
|
| `EpubNav` | EPUB3 NAV navigation |
|
|
| `EpubCover` | Cover page |
|
|
| `Link` / `Section` | TOC structure elements |
|
|
|
|
### Strengths
|
|
- Most widely used — large community, many projects depend on it
|
|
- Supports both EPUB2 and EPUB3
|
|
- Simple, intuitive API for creating EPUBs from scratch
|
|
- Handles cover pages, TOC, spine, and metadata well
|
|
|
|
### Weaknesses
|
|
- AGPL license — if you *distribute* software using EbookLib, your software
|
|
must also be AGPL. Calling it at runtime (without bundling) is generally
|
|
considered fine.
|
|
- Reading large EPUBs loads everything into memory
|
|
- Currently undergoing a refresh (see GitHub issue #318)
|
|
|
|
### Documentation
|
|
- PyPI: https://pypi.org/project/EbookLib/
|
|
- ReadTheDocs: http://ebooklib.readthedocs.io
|
|
- GitHub: https://github.com/aerkalov/ebooklib
|
|
|
|
## epublib
|
|
|
|
A newer library (2026) designed specifically for editing existing EPUB3 files.
|
|
Memory-efficient, spec-compliant, and MIT-licensed.
|
|
|
|
### Installation
|
|
|
|
```bash
|
|
pip install epublib
|
|
```
|
|
|
|
### Key Design
|
|
|
|
- **Memory-efficient:** Lazy-loads resources from the ZIP as needed using
|
|
Python's `zipfile` module. Does not load everything into memory at once.
|
|
- **Non-intrusive editing:** When you open and save an EPUB, it preserves the
|
|
original structure. Only modified parts are changed — the manifest isn't
|
|
regenerated from scratch, metadata items keep their order, etc.
|
|
- **Spec-compliant:** Follows the EPUB 3.3 specification. Resource classes
|
|
mirror the spec's type hierarchy.
|
|
- **BeautifulSoup-based:** Content documents (`ContentDocument`) expose a
|
|
`.soup` attribute for DOM manipulation.
|
|
|
|
### Editing Example
|
|
|
|
```python
|
|
from epublib import EPUB
|
|
|
|
with EPUB('book.epub') as book:
|
|
# Edit metadata
|
|
book.metadata.title = 'New Title'
|
|
|
|
# Edit all content documents
|
|
for doc in book.documents:
|
|
# Insert a heading at the top
|
|
new_h1 = doc.soup.new_tag('h1', string='New Heading')
|
|
doc.soup.body.insert(0, new_h1)
|
|
|
|
# Add a CSS file link to all documents
|
|
for doc in book.documents:
|
|
new_link = doc.soup.new_tag('link', rel='stylesheet',
|
|
href='../Styles/new.css',
|
|
type='text/css')
|
|
doc.soup.head.append(new_link)
|
|
|
|
book.update_manifest_properties()
|
|
book.write('book-modified.epub')
|
|
```
|
|
|
|
### Resource Hierarchy
|
|
|
|
```
|
|
Resource
|
|
├── XMLResource (has .soup)
|
|
│ ├── PackageDocument (content.opf)
|
|
│ └── ContentDocument (XHTML, SVG)
|
|
│ └── NavigationDocument (nav.xhtml)
|
|
├── PublicationResource (has .media_type)
|
|
│ ├── ContentDocument (also above)
|
|
│ └── NCXFile (toc.ncx)
|
|
```
|
|
|
|
### Resource Operations
|
|
|
|
```python
|
|
# Add a resource
|
|
from epublib.resources.create import create_resource
|
|
new_resource = create_resource(xhtml_bytes, 'Text/chapter2.xhtml')
|
|
book.resources.add(resource=new_resource, add_to_spine=True, after='Text/chapter1.xhtml')
|
|
|
|
# Remove a resource
|
|
book.resources.remove('Text/chapter1.xhtml')
|
|
|
|
# Rename (auto-updates all references)
|
|
book.resources.rename('Text/chapter1.xhtml', 'Text/chapter-one.xhtml')
|
|
|
|
# Filter by media type
|
|
from epublib.media_type import MediaType, Category
|
|
pngs = book.resources.filter(MediaType.IMAGE_PNG)
|
|
images = book.resources.filter(Category.IMAGE) # all image types
|
|
```
|
|
|
|
### Strengths
|
|
- MIT license — no AGPL concerns
|
|
- Memory-efficient for large EPUBs
|
|
- Spec-compliant resource type hierarchy
|
|
- Non-intrusive editing preserves original structure
|
|
- BeautifulSoup integration for content manipulation
|
|
- Resource renaming auto-updates all references
|
|
|
|
### Weaknesses
|
|
- Newer library — smaller community, fewer tutorials
|
|
- EPUB3 only (no EPUB2 creation, though EPUB2 files can be read)
|
|
- Creating EPUBs from scratch requires more manual setup than EbookLib
|
|
- Can't write to the same file (must write to temp file then copy — EOFError)
|
|
|
|
### Real-World API Quirks (from testing on 2.1MB commercial EPUB)
|
|
|
|
These were discovered during integration testing and are not in the docs:
|
|
|
|
- **`book.resources` is iterable, not dict-like.** There is no `.all()` method.
|
|
Use `for r in book.resources:` directly. `len(book.resources)` works.
|
|
- **`metadata.author` doesn't exist.** Dublin Core uses `dc:creator`. Read it via
|
|
`book.metadata.items` — iterate and check `item.name == 'creator'`.
|
|
- **Write to temp file, never same file.** `EPUB(src)` opens the ZIP, and
|
|
`book.write(src)` will fail with `EOFError` because the source file is truncated
|
|
while epublib still reads from it. Write to a temp file then copy: `book.write(tmp); shutil.copy(tmp, src)`.
|
|
- **Python 3.13+ required.** The wheel targets `>=3.13`. Install with `python3 -m pip
|
|
install epublib` — the bare `pip` command may point to an older Python.
|
|
- **`book.documents[0]` may be SVG, not XHTML.** The first content document isn't
|
|
guaranteed to be the first chapter. Filter by filename or check `media_type`.
|
|
- **`update_manifest_properties()` must be called explicitly** after adding or
|
|
modifying resources. epublib doesn't auto-recalculate manifest properties.
|
|
- **`remove_item(item)` takes the item object, not a string.** Use
|
|
`book.metadata.remove_item(item)` where `item` is a metadata item object.
|
|
The `metadata.items` attribute is a `tuple`, so `.items.remove()` fails.
|
|
- **First document after cover may be nav.** When the cover XHTML is in the spine,
|
|
`book.documents` ordering reflects spine order, not alphabetical.
|
|
- **`book.write()` with cover image >2MB is slow.** The entire zip is rewritten;
|
|
epublib doesn't do incremental ZIP updates. For large covers, expect 2-5 second
|
|
write times.
|
|
|
|
### Documentation
|
|
- PyPI: https://pypi.org/project/epublib/
|
|
- GitLab: https://gitlab.com/joaoseckler/epublib
|
|
|
|
### Common Editing Patterns (epublib)
|
|
|
|
These are the patterns used by the `epub-edit` CLI. When writing custom editing
|
|
logic, follow these recipes:
|
|
|
|
**Update metadata:**
|
|
```python
|
|
with EPUB('book.epub') as book:
|
|
book.metadata.title = 'New Title'
|
|
book.metadata.author = 'Author Name'
|
|
book.metadata.language = 'fr'
|
|
book.write('book-updated.epub')
|
|
```
|
|
|
|
**Add a chapter from an XHTML file:**
|
|
```python
|
|
from epublib.resources.create import create_resource_from_path
|
|
with EPUB('book.epub') as book:
|
|
new = create_resource_from_path('new-chapter.xhtml', 'Text/chapter3.xhtml')
|
|
book.resources.add(resource=new, add_to_spine=True, add_to_toc=True,
|
|
after='Text/chapter2.xhtml')
|
|
book.update_manifest_properties()
|
|
book.write('book-expanded.epub')
|
|
```
|
|
|
|
**Remove a chapter:**
|
|
```python
|
|
with EPUB('book.epub') as book:
|
|
book.resources.remove('Text/chapter2.xhtml')
|
|
book.write('book-trimmed.epub')
|
|
```
|
|
|
|
**Reorder spine:**
|
|
```python
|
|
with EPUB('book.epub') as book:
|
|
book.spine.reorder(['chapter3', 'chapter1', 'chapter2', 'nav'])
|
|
book.write('book-reordered.epub')
|
|
```
|
|
|
|
**Inject CSS into all documents:**
|
|
```python
|
|
from epublib.resources.create import create_resource_from_path
|
|
with EPUB('book.epub') as book:
|
|
css = create_resource_from_path('dark.css', 'Styles/dark.css')
|
|
book.resources.add(resource=css, add_to_spine=False)
|
|
for doc in book.documents:
|
|
link = doc.soup.new_tag('link', rel='stylesheet',
|
|
href='../Styles/dark.css', type='text/css')
|
|
doc.soup.head.append(link)
|
|
book.write('book-dark.epub')
|
|
```
|
|
|
|
**Rename a resource (auto-updates all references):**
|
|
```python
|
|
with EPUB('book.epub') as book:
|
|
book.resources.rename('Images/old.jpg', 'Images/new.jpg')
|
|
book.write('book-renamed.epub')
|
|
```
|
|
|
|
## When to Use Which
|
|
|
|
| Task | Use |
|
|
|------|-----|
|
|
| Create a new EPUB from scratch | EbookLib |
|
|
| Read EPUB for text extraction | Either (EbookLib simpler, epublib more memory-efficient) |
|
|
| Edit existing EPUB (modify metadata, reorder spine, add/remove chapters) | epublib |
|
|
| Modify content within chapters | epublib (BeautifulSoup access) |
|
|
| Convert EPUB2 → EPUB3 | epublib (non-intrusive editing) |
|
|
| Batch process many EPUBs | epublib (memory-efficient) |
|
|
| Quick one-off script, don't care about license | EbookLib |
|
|
|
|
## Running Without Installation
|
|
|
|
The scripts in this skill require at least EbookLib. To install dependencies:
|
|
|
|
```bash
|
|
pip install EbookLib
|
|
# Optional: for advanced editing
|
|
pip install epublib
|
|
```
|
|
|
|
## License Note
|
|
|
|
EbookLib is AGPL. The scripts in this skill call it at runtime but do not
|
|
bundle or distribute it. Users install it independently via pip. This is the
|
|
standard pattern for MIT-licensed tools that wrap AGPL libraries.
|