pdf-to-audiobook/README.md
bitscuit a1796e82aa pdf-to-audiobook: PDF -> narrated MP3 via edge-tts
Pipeline: extract (pdftotext) -> scan (OCR-defect report + fix template)
-> clean (noise/header removal, hyphenation, exact-string/regex OCR
repairs, chapter-heading detection) -> voices (sample + comparison)
-> chunk (sentence-aware) -> synth (resumable edge-tts) ->
stitch (ffmpeg concat + RMS verify) -> chapters (ID3v2.4 CHAP).

Config-driven per book; validated end-to-end on a clean digital PDF
and on a 546-page scanned book (byte-identical clean output to the
reference run, 95 auto-detected chapters).
2026-08-25 14:15:50 +02:00

7.7 KiB

pdf-to-audiobook

Turn a PDF into a narrated MP3 audiobook: text extraction, OCR-defect reporting and repair, voice sampling, resumable free edge-tts synthesis, ffmpeg stitching, and ID3 chapter markers.

Proven on a 546-page scanned book: 546 pages in, a 14h MP3 out, 100+ chapters, zero cost.

How it works

            +----------+      +--------+      +-------+      +-------+      +--------+      +----------+
PDF  -----+> | extract  | ---> |  scan  | ---> | clean | ---> | chunk | ---> | synth  | ---> |  stitch  |
           +----------+      +--------+      +-------+      +-------+      +--------+      +----------+
             raw.txt        report +      clean.txt     chunks/        audio/*.mp3   audiobook.mp3
             (pdftotext)    template      (+ chapters)  (~2600 chars)  (edge-tts)    + chapters
  • extract - pdftotext + a text-layer quality report (word count, unreadable-glyph count, verdict).
  • scan - enumerates every distinct OCR-suspect token with its count and surrounding context, and can emit a fix-config template. This is the work horse for scanned PDFs.
  • clean - noise/page-header removal, hyphenation joining, and applies your fix config (exact-string maps, regexes, keep/drop windows). Also records chapter heading positions.
  • voices - renders one identical sample in N voices, plus one combined comparison MP3, so you can pick a voice in one playthrough.
  • chunk / synth / stitch - sentence-aware chunking, resumable parallel edge-tts synthesis (re-run to pick up stragglers), lossless concat with RMS spot-checks.
  • chapters - maps heading positions onto audio time and tags the MP3 with standard ID3v2.4 CHAP chapters (play in players with chapter support).

Install

pip install -r requirements.txt     # edge-tts, mutagen
# plus system tools:
#   ffmpeg/ffprobe   (any recent build)
#   pdftotext        (poppler-utils / poppler)

Quick start (clean digital PDF, no OCR damage)

python -m pdf2audiobook full book.pdf --voice en-GB-LibbyNeural

Everything lands in book_work/: raw.txt, clean.txt, chunks/, audio/, audiobook.mp3, audiobook_chapters.mp3.

A 250-page book at ~150 wpm is roughly 10h of audio; synthesis of ~100 chunks takes about 15-20 min on the free edge-tts endpoint.

Step-by-step (scanned PDF, OCR damage, voice selection)

# 1. extract + quality report
python -m pdf2audiobook extract book.pdf          # workdir: book_work/

# 2. see what the OCR broke
python -m pdf2audiobook --workdir book_work scan --top 200
python -m pdf2audiobook --workdir book_work scan --top 300 \
    --template configs/book.json

# 3. fill in configs/book.json (see below), then clean
python -m pdf2audiobook --workdir book_work clean --config configs/book.json

# 4. pick a voice: render a sample in several candidates
python -m pdf2audiobook --workdir book_work voices \
    en-GB-LibbyNeural en-GB-SoniaNeural en-US-AriaNeural en-US-ChristopherNeural
#    -> book_work/samples/compare_all_voices.mp3 (one playthrough, all voices)
#    listen, choose one

# 5. chunk + synthesize (resumable; safe to Ctrl-C and re-run)
python -m pdf2audiobook --workdir book_work chunk
python -m pdf2audiobook --workdir book_work synth en-GB-LibbyNeural
#    quick check first:  ... synth en-GB-LibbyNeural --limit 5

# 6. stitch + verify (RMS spot-checks for silent sections)
python -m pdf2audiobook --workdir book_work stitch

# 7. chapter markers
python -m pdf2audiobook --workdir book_work chapters
#    -> book_work/audiobook_chapters.mp3

Fixing OCR defects

Scanned PDFs break text in systematic ways. The OCR engine of a given scan misreads the SAME glyph the SAME way (e.g. ; for s, J for th, 9 for d, and U+FFFD replacement characters for diacritics). So the reliable tool is not a clever regex but an exact-string map built from the actual output:

  1. scan --template configs/book.json prints the distinct suspect tokens with counts and context, and writes a config with every token as an empty value.
  2. Fill in the correct reading for the tokens that matter. Prioritize by count: the high-frequency terms (names, technical vocabulary) are worth mapping; hundreds of one-offs in an index or reference table usually aren't. Leave the rest empty (empty values are ignored).
  3. For a handful of pattern-like misreads, regex_fixes works, but only with anchored patterns you have verified against the raw context. A loose short-pattern "fix" will corrupt real words (this bit the reference project: a catch-all rule corrupted measure six times).
  4. clean --config ... applies everything and reports remaining U+FFFD glyphs. Re-run scan clean.txt to eyeball what's left.

The config keys:

key meaning
include.start / include.end regexes marking the first/last body lines (cuts front/back matter; end_fallback is a second try for end, min_line_* guard against early false hits)
drop_blocks e.g. remove the table of contents: start + end line regexes, optional end_followed_by to require the block's end to be followed by specific lines
page_header_patterns running headers to drop as whole lines (match at line start)
drop_line_patterns other whole lines to drop
strip_patterns inline removals applied to the joined text (plate/figure references, stray inline headers)
raw_fixes exact-string fixes applied to the raw text before any structure is touched (typically U+FFFD glyph repairs that must happen before line filtering)
regex_fixes [pattern, replacement] pairs, applied in order to the joined text
fixes exact-string map applied longest-first (the main OCR repair table)
word_fixes anchored word-boundary [pattern, replacement] pairs, applied last
heading_pattern regex matching chapter heading lines (used for chapter markers)
heading_exclude_patterns lines that must never count as headings (e.g. numbered instruction sentences)
heading_next_pattern / heading_next_skip require a heading to be followed by this pattern within N lines (filters running headers that share a heading's shape)
heading_count_max safety cap on chapter count

A heavily-scanned book (foreign-language terms, diacritics, drop caps) typically needs a few hundred exact-string fixes plus a smaller set of raw_fixes (for U+FFFD glyphs) and a handful of anchored regex_fixes. Start from a scan --template and fill in the high-frequency tokens first.

Voices

python -m edge_tts --list-voices lists the full catalogue. Useful filters: en-GB-, en-US-, en-AU-, en-CA-, en-IN-, en-IE-. Known-good as of 2026-08: en-US-{Aria,Jenny,Ava,Christopher,AndrewMultilingual,EmmaMultilingual}, en-GB-{Sonia,Libby,Ryan,Thomas}, en-AU-Natasha, en-CA-{Clara,Liam}, en-IN-{Neerja,Prabhat}. Some names 404 (Davis, Sinara, IE-Emma, ZA-James); the sampler reports which ones failed.

edge-tts is free and unauthenticated. Keep concurrency at 2 (the default) and let the built-in retries/backoff handle hiccups. A 546-page book is ~250 chunks in ~30-35 min.

Output

  • audiobook.mp3 - one continuous file (24 kHz MP3, what edge-tts emits; fine for speech)
  • audiobook_chapters.mp3 - same audio + ID3v2.4 CHAP chapters
  • verify any time: ffprobe -v error -show_chapters audiobook_chapters.mp3

Caveats

  • One consistent voice; no expression/pacing control beyond SSML-free plain text.
  • OCR in reference tables, indices, and heavily illustrated pages will not be fully repairable; the tool reports what remains instead of faking 100% clean.
  • Chapters come from real headings in the text; if the scan ate the heading numbers, some chapters will be missing (the tool would rather leave a gap than mislabel a chapter).
  • Respect copyright: use this for books you own or that are public domain.