Skip to content

Repository files navigation

Project Workspace

Create the local working folders before the first book:

./pdftoaudio workspace --fix

Put source PDFs in books/:

books/my-book.pdf

Generated job files are written under jobs/<book>/. The repository keeps *.pdf, books/*, and jobs/ in .gitignore so source PDFs, generated text, and audio do not get checked into GitHub by accident. Credentials belong outside repositories under $HOME/.keys/.

Machine setup lives in REQUIREMENTS.md.

Set up the locked Python environment with uv:

direnv allow
uv sync --locked

Job CLI Workflow

The new workflow uses one directory per book under jobs/.

./pdftoaudio workspace --fix
./pdftoaudio init my-book ./books/my-book.pdf
./pdftoaudio extract my-book
./pdftoaudio sanitize my-book
./pdftoaudio review my-book
./pdftoaudio clean my-book --mode codex
./pdftoaudio chapters my-book
./pdftoaudio chunk my-book
./pdftoaudio synthesize my-book --provider google
./pdftoaudio finalize my-book
./pdftoaudio status my-book

Files are written under:

jobs/my-book/
  source/original.pdf
  text/raw.txt
  text/sanitized.txt
  text/cleaned.txt
  cleanup/sections.json
  cleanup/input/001.json
  cleanup/output/001.json
  book.json
  chapters.json
  reports/sanitize.json
  reports/line-map.json
  reports/review.json
  reports/cleanup.json
  reports/cleanup.md
  reports/tts-validation.json
  reports/chunk.json
  reports/synthesize.json
  reports/finalize.json
  reports/finalize-chapters.ffmetadata
  chunks/001.txt
  chunks/001.txt.done
  chunks/002.txt
  audio/001.mp3
  audio/final.mp3
  audio/final.m4b
  manifest.json

sanitize applies conservative character and whitespace cleanup. review flags suspicious line ranges without editing text. clean --mode codex repairs extraction damage in resumable sections and records each change. chapters creates an editable structure plan. chunk splits each included chapter into TTS requests, synthesize creates MP3 segments, and finalize publishes MP3 and M4B audiobooks.

Codex Cleanup

./pdftoaudio clean my-book --mode codex

Cleanup preserves prose meaning and order, joins PDF-wrapped paragraphs, repairs split words, and removes reviewed page furniture. Codex converts dense code listings and tables into spoken explanations while retaining technical meaning. Cleanup requests audiobook-ready plain text and removes paired Markdown backticks around inline technical tokens while preserving the enclosed token. Each section carries a source digest, so reruns reuse completed work and reject stale output.

Use --section N to clean one section without assembling the book. Use --force after changing sanitized text or cleanup settings. Audit details live in reports/cleanup.json and reports/cleanup.md.

To inspect an existing cleaned job for Markdown that Google might pronounce aloud, run:

./pdftoaudio remediate-markdown my-book

This is a dry run and does not modify files. Review its findings first. To remove safe paired inline backticks from text/cleaned.txt and existing chunk text, run:

./pdftoaudio remediate-markdown my-book --apply --force

The command refuses to rewrite anything if unpaired or fenced backticks remain, and it never deletes or rewrites audio. After applying remediation, regenerate chapter/chunk metadata as needed and explicitly resynthesize affected audio. Google may bill those synthesis requests again.

Chapters and Book Metadata

./pdftoaudio chapters my-book

The command writes book.json and chapters.json in the job directory. Review both files before paid synthesis. You can edit titles, chapter types, inclusion flags, authors, narrator, description, language, genre, and the optional cover path. Leave chapter offsets and the source digest unchanged.

Testimonials, copyright pages, contents, prefaces, introductions, and other front matter become named chapters by default. To omit them before chunking and avoid their TTS cost, run:

./pdftoaudio chapters my-book --force --exclude-front-matter

chunk validates the chapter plan against text/cleaned.txt. It never combines text from two chapters in one TTS request. If cleaned text changes, regenerate the plan before chunking.

Example book.json:

{
  "schema_version": 1,
  "title": "The Pragmatic Programmer",
  "authors": ["Andrew Hunt", "David Thomas"],
  "narrator": "Google en-US-Casual-K",
  "description": "",
  "language": "en-US",
  "genre": "Audiobook",
  "cover": "/path/outside/the/repository/cover.jpg"
}

Use a JPEG or PNG cover. The pipeline reads the image from its existing location and does not copy it into the repository.

chunk reads text/cleaned.txt by default:

./pdftoaudio chunk my-book

For diagnostics, you can bypass cleanup explicitly:

./pdftoaudio chunk my-book --source sanitized

The default chunk limit is 4,900 UTF-8 bytes. chunk keeps each request-sized file under that byte limit. It prefers sentence endings, then paragraph, line, and whitespace boundaries; if punctuation is missing, it still chunks by size instead of treating the whole run as one sentence.

synthesize uses Google TTS for now:

./pdftoaudio synthesize my-book --provider google

By default it uses en-US-Casual-K, writes MP3 segments under jobs/my-book/audio/, and creates chunks/NNN.txt.done sidecars after successful synthesis. Keep Google credentials outside repositories at $HOME/.keys/googletexttospeech/gen-lang-client.json, or pass --credentials with another external path.

Before contacting Google, synthesis rejects empty text, replacement characters, unsafe controls, Markdown backticks, and sentences of 500 characters or more. Literal <, >, and & produce warnings because the current provider sends plain text rather than SSML.

Final Audio

./pdftoaudio finalize my-book

finalize verifies the ordered synthesis report and every expected MP3. It produces:

  • audio/final.mp3, the broad-compatibility copy of the synthesized segments.
  • audio/final.m4b, an AAC audiobook with embedded chapter timestamps, book metadata, and optional cover art.

M4B uses the MPEG-4 audio container and the .m4b extension so audiobook applications treat it as a book instead of a song. FFmpeg encodes AAC at 64 kbps. The command uses ffprobe to verify duration, size, codec, chapter order, timestamps, and requested cover art before publishing the file.

stitch is an alias. Use --cover PATH to override the cover in book.json for one run. Use --no-m4b only when you need the legacy MP3 output. The command records both outputs and embedded chapters in reports/finalize.json.

Listening on iPhone and Other Platforms

Use Apple Books as the default player on iPhone, iPad, Mac, Apple Watch, and CarPlay. Import final.m4b through Books on a Mac, then use iCloud or Finder audiobook sync to move it to an iPhone. Apple documents audiobook import and Finder syncing.

BookPlayer is a free, open-source iPhone alternative that imports M4B, M4A, and MP3 files from Files, AirDrop, and other applications. VLC provides a free cross-platform fallback. Audiobookshelf adds a self-hosted library and cross-device progress when you need server-backed listening.

Apple Podcasts is designed around podcast feeds. It is not the default application for local audiobook files.

Long Sentences

Google TTS can reject input that contains very long sentences. The older sentences_length helper still exists for inspecting one legacy chunk file:

./sentences_length ../text/document_title/04.txt

The new CLI checks this earlier:

./pdftoaudio review my-book

review scans jobs/my-book/text/sanitized.txt and writes jobs/my-book/reports/review.json. Sentences at 500 characters or longer are reported with the issue code long_sentence. The command only reports the issue; it does not split or rewrite the sentence.

The older script workflow still exists while the CLI migration is in progress.

Older Script Workflow

  • Place a text PDF within the books/ directory. To make it easier, rename it something that is easy to type such as, pdf_name.pdf
  • Within the project root directory, run ./read pdf ${pdf_name.pdf} in the terminal.
  • App will extract the text from the pdf and give a rough estimate on the cost to convert to audio .mp3 file using Google TTS standard.
  • Review and clean up the .txt file manually, erase any oddities that you don't want in your mp3 file.
  • Activate the Google TTS / Audio container by running ./read speech pdf_name.pdf.txt (the pdf_name.pdf.txt is a placeholder to later turn this into a full function). This will put you in the container shell after installing needed tools.
  • With your cleaned up pdf_name.pdf.txt file, run chunk_texts pdf_name.pdf.txt to chunk the large text file that you manually cleaned up into 5000 or less byte chunks numbered 01.txt, 02.txt and so on in the directory pdf_name. This is necessary to make requests via Google TTS.
  • Run texttospeech pdf_name on your designated text file. This will send requests to Google TTS and output individual files of the format 01.mp3,02.mp3, etc. to the directory audio/pdf_name. Please see the section on Google TTS Quotas below for API usage.
  • Finally you can string together all of the mp3s generated by running the command string_together_mp3s pdf_name which will place a final mp3 titled audio/finalized/pdf_name.mp3 which will be a concatenation of all of the individual numbered mp3 files.

About Google TTS Quotas

  • As of writing this readme file, Google TTS imposes a 5000 bytes per request quota, and a 1000 requests per minute quota.
  • Therefore:
(5000 bytes/request)(1000 requests/minute) = 5000000 bytes/minute = 5M bytes/minute
  • If appropriately chunked, assuming there are 300,000 characters in a large document, this would suggest around 16 large documents per minute can be processed.
  • That being said, the free tier of Google TTS is 1,000,000 or 1M bytes, so after 1M bytes there would be a cost per byte.
  • Bytes are approximately equal to one character, accepting special characters which may be more than one byte, but our cost estimation function assumes incorrectly that 1 character = 1 byte.

Google TTS Quotas

SSML Reference Guide

https://cloud.google.com/text-to-speech/docs/ssml

Known Errors in Google SSML

Sentences Too Long

  • The following type of error may arise:

An error occurred: 400 This request contains sentences that are too long. Consider splitting up long sentences with sentence ending punctuation e.g. periods. Also consider removing SSML sentence tags (e.g. '<s>') as they can confuse Cloud Text-to-Speech.

  • We have put together sentences_length to analyze individual documents for sentences that might be too long. Run this on the document in question with, for example:
./sentences_length ../text/document_title/04.txt
  • ...This will print out the length of each sentence via a simple regex function, demarcating sentences by ., ! or ?.

  • Typically sentences with 500+ characters are too long for Google TTS.

About

No description or website provided.

Topics

Resources

Stars

0 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages