# EPUB to Markdown: Getting Books Into Obsidian, Git and Your LLM Pipeline

> Source: <https://dev.to/selenium39dev/epub-to-markdown-getting-books-into-obsidian-git-and-your-llm-pipeline-476h>
> Published: 2026-09-24 22:45:05+00:00

Ebooks are strangely locked away. The text is right there (an EPUB is literally a ZIP of XHTML files), yet it lives inside reader apps that don't talk to the rest of your tools. You can't grep it, diff it, link it from your notes, or chunk it for a retrieval pipeline without some glue.

Markdown is the glue. Once a book is a `.md` file, it drops into the plain-text ecosystem: Obsidian vaults, Notion imports, static-site generators, Git repos, and any script that eats text.

This post walks through converting an EPUB to Markdown in the browser, what survives the conversion, and a couple of small scripts for the most common next steps.

You could `unzip` the EPUB and strip tags, or convert to TXT. That works for word-frequency experiments, but you lose the one thing that makes a book navigable: **structure**. Chapter titles, section headings, lists, block quotes, and links all flatten into an undifferentiated wall.

Markdown keeps that structure with almost no syntax overhead:

`#` headings`>`
For humans, that means the file still reads like a book in a text editor. For machines, it means explicit hierarchy you can split on.

The [iloveepub EPUB to Markdown converter](https://iloveepub.com/epub-to-markdown) does the whole thing client-side. You pick a file, each chapter is parsed and rebuilt as Markdown, a progress bar tracks it, and you download a single `.md` file. The book is never uploaded, which you can confirm yourself: open the Network tab in DevTools while it runs and you'll see zero upload requests. No account or API key is needed, and there's no rate limit.

Steps:

`.epub`.`.md` file.
That's genuinely it. There are no options to tune, which is a feature when you just want the text out.

**Carried over:** headings (as `#` levels), bold and italics, links, lists including nested ones, block quotes, code blocks, tables, and horizontal rules. The rule of thumb is that anything the book marks up *semantically* survives; purely visual styling (fonts, colors, custom spacing) doesn't.

**Images:** each one becomes a standard reference like `![alt text](original-path)`, preserving alt text and the original path. The image files themselves aren't exported; the output is one self-contained text file. If you need the images too, unzip the EPUB separately and copy its image folder next to the `.md` file. The references keep the paths used inside the book, so a quick find-and-replace on the folder prefix is usually all it takes to make them resolve in your vault.

**DRM-protected books** can't be opened by any browser tool, so this is for DRM-free purchases, public-domain titles, and your own documents.

Dropping a 400 KB single file into a vault works, but one note per chapter is nicer for linking and backlinks. Here's a small Python script that splits on the shallowest heading level that repeats, which is almost always the chapter level:

``` python
import re, sys, pathlib

src = pathlib.Path(sys.argv[1])
text = src.read_text(encoding="utf-8")

# Pick the shallowest heading level that appears more than once
# (so a single "# Book Title" at the top doesn't swallow everything)
levels = [len(m) for m in re.findall(r"^(#{1,6}) ", text, flags=re.M)]
if not levels:
    sys.exit("no headings found")
repeated = [lv for lv in sorted(set(levels)) if levels.count(lv) > 1]
top = "#" * (repeated[0] if repeated else min(levels))

parts = re.split(rf"^(?={re.escape(top)} )", text, flags=re.M)
out = src.with_suffix("")
out.mkdir(exist_ok=True)

count = 0
for i, part in enumerate(p for p in parts if p.strip()):
    count += 1
    first = part.splitlines()[0].lstrip("# ").strip() or f"part-{i}"
    slug = re.sub(r"[^\w\- ]", "", first)[:60].strip()
    (out / f"{i:02d} {slug}.md").write_text(part, encoding="utf-8")

print(f"wrote {count} files to {out}/")
```

Run it with `python split_md.py my-book.md`, then move the folder into your vault. The zero-padded prefix keeps chapters sorted in the file explorer.

If you're feeding books to a language model for Q&A, summaries, or retrieval, Markdown is about as LLM-friendly as a book gets: one clean file, explicit heading hierarchy, and no layout noise for the model to trip over.

Headings also make natural chunk boundaries. Rather than slicing every N tokens and cutting paragraphs in half, split on headings and carry the heading path as metadata:

``` python
import re

def chunk_markdown(md: str, max_chars: int = 4000):
    path, buf, chunks = [], [], []

    def flush():
        body = "\n".join(buf).strip()
        if body:
            for i in range(0, len(body), max_chars):
                chunks.append({"section": " > ".join(path), "text": body[i:i + max_chars]})
        buf.clear()

    for line in md.splitlines():
        m = re.match(r"^(#{1,6}) (.*)", line)
        if m:
            flush()
            depth = len(m.group(1))
            path[:] = path[: depth - 1] + [m.group(2).strip()]
        else:
            buf.append(line)
    flush()
    return chunks
```

Each chunk arrives with its context attached (`"Part Two > Chapter 7 > The Harbor"`), which tends to give a retriever (and the model reading the results) far more to work with than anonymous fixed-size windows. Swap the naive character split for a token-aware splitter if you need precise budgets.

`git diff` them. Prose diffs are much easier to review in Markdown than in XHTML.`ripgrep` is a surprisingly good full-text search engine.
Books deserve to be first-class text, not blobs trapped in a reader app. Converting EPUB to Markdown is the shortest path from "file on disk" to "something my tools understand." Convert one book, split it, and see how differently you use it once it lives next to your notes.
