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

44 lines
1.5 KiB
Python

"""Text extraction from PDF (poppler pdftotext)."""
import os
import subprocess
FFFD = "\ufffd"
def extract(pdf_path: str, out_path: str) -> dict:
"""Run pdftotext and report basic text-layer statistics.
Returns a dict with path, chars, words, fffd_count, and a short
quality verdict (useful for deciding how much cleanup a book needs).
"""
if not os.path.exists(pdf_path):
raise FileNotFoundError(pdf_path)
if not os.path.exists(out_path):
subprocess.run(
["pdftotext", pdf_path, out_path],
check=True,
capture_output=True,
text=True,
)
text = open(out_path, encoding="utf-8").read()
words = len(text.split())
fffd = text.count(FFFD)
if words == 0:
verdict = "NO TEXT LAYER: the PDF appears to be a pure image scan. " \
"OCR it first (e.g. ocrmypdf) or the pipeline has nothing to read."
elif fffd > words * 0.01:
verdict = "HEAVY OCR DAMAGE: many unreadable glyphs (U+FFFD). " \
"Run `scan` and build a fix config before synthesizing."
elif fffd > 0:
verdict = "MINOR OCR DAMAGE: some unreadable glyphs. " \
"Run `scan` to see them; a fix config is recommended."
else:
verdict = "CLEAN TEXT LAYER: no replacement characters found."
return {
"path": out_path,
"chars": len(text),
"words": words,
"fffd": fffd,
"verdict": verdict,
}