# Changelog of PDF to MP3 - Convert PDF, EPUB, DOCX & Text to Audiobook (`marielise.dev/pdf-to-mp3`) Actor

- **URL**: https://apify.com/marielise.dev/pdf-to-mp3/changelog.md
- **Full Actor documentation**: https://apify.com/marielise.dev/pdf-to-mp3.md

## Changelog

All notable changes to this Actor will be documented in this file.

### \[Unreleased]

#### Pricing

- **Start fee cut from $0.12 to $0.05** and **OCR page fee cut from $0.10 to
  $0.03**; the $0.04 audio minute is unchanged. Measured platform cost is
  $0.0007 for a failing run and $0.28 for a 500-page book, so the old start fee
  was 170x the cost it existed to cover and was pure friction on the short calls
  AI agents make most often. `PRICING.md` documents the measured model and the
  margin table behind the new numbers.
- `maxMemoryMbytes` lowered from 8192 to 4096. The pipeline runs at 21% CPU, so
  more memory buys nothing, and the Apify MCP server rejects Actors that ask for
  more than 4 GB.

#### Cost ceilings now stop the run instead of giving audio away

- **`maxCostUsd` no longer delivers a whole audiobook for the capped price.** It
  used to clamp the charge while still narrating everything, so a caller could
  set the floor value on a 500-page book and receive ~18 hours of audio for
  $0.12 against $0.28 of platform cost. Narration now stops at the last chunk
  the budget covers and the run returns the new `status: "capped"` with the
  audio produced so far.
- **The run-level "Max total charge" limit is honoured.** `Actor.charge` returns
  `eventChargeLimitReached` and `chargedCount`; both were discarded, so a run
  started with a low platform limit kept synthesizing audio nobody could be
  billed for. Both are now read, and either ceiling stops narration.
- **OCR is bounded by the cost ceiling before it runs.** OCR is charged per page
  during extraction, before the pre-flight cost gate, so a 500-page scan could
  bill past the ceiling before the gate was ever reached.
- The start fee mirrored in the `cost` field now scales with allocated memory,
  matching how the platform actually charges it (one event per GB).

#### Usable as an MCP tool for AI agents

- `documentUrl` gained a `default`, so a call with no arguments produces audio
  rather than an input error. This also gives Apify's daily health check, which
  runs with empty input, real work to verify.
- Every input description rewritten for an LLM caller and kept inside the MCP
  server's 500-character truncation limit.
- The dataset row leads with `status` and a plain-language `statusMessage`, and
  carries `audioUrls`, `durationHuman`, `sizeBytes`, `audioFormat`, `title` and
  `language`. `parts` and `failedChunkIndexes` are capped so a long book cannot
  bloat a tool response. Every audio link is a direct, publicly fetchable MP3
  URL, so an agent never pulls a binary through the tool result.
- Added an `overview` dataset view and a field-level dataset schema; the output
  schema now points at both views.
- README documents the hosted MCP route (`https://mcp.apify.com?tools=...`), a
  Claude Desktop and Claude Code configuration, an example tool call and the
  direct API route.
- Categories corrected to AI, FOR\_CREATORS, EDUCATION (the platform caps
  categories at three), and the Actor description shortened to fit the 300-character
  platform limit that the previous text exceeded.

#### Run reliability

- **Expected outcomes no longer fail the run.** A bad URL, an empty input, a
  document with no extractable text or a TTS refusal now writes the labelled
  `status: "failed"` dataset row and exits 0. Only an unclassified error exits
  non-zero. Crashing on these hid the explanation from the user and reported the
  Actor as broken to Apify's daily health check, which runs empty input.
- **Several document sources no longer conflict.** `documentUrl` carries a demo
  prefill, so uploading a file used to fail validation through no fault of the
  user. Sources are ranked instead — upload, pasted text, base64, URL — and the
  chosen one is logged.
- **Audio minutes are billed per part as each part is uploaded**, not once at the
  end, so a run that hits its timeout mid-book still charges for the audio it
  already delivered. The running total keeps the bill identical to a single
  end-of-run charge.
- Live default run options corrected to 7200s / 2048 MB. The 300s default could
  not finish a full-length book.

### \[0.1.1] - 2026-06-08

#### Production-hardening: OCR, modern PDF engine, security, billing safety

##### New capabilities

- **OCR fallback for scanned / image-only PDFs** — pages with no text layer are
  auto-rendered (poppler `pdftoppm`) and OCR'd (Tesseract: EN, ES, FR, DE, IT,
  PT, NL) and narrated. New `ocr-page-processed` billing event ($0.10/page),
  charged only for pages that actually need OCR. Toggle with `enableOcr`.
- **Encrypted PDF support** — decrypt password-protected PDFs via `pdfPassword`.
- **Proxy support** — optional `proxyConfiguration` for the Document URL fetch.
- **ID3 tags** on every MP3 part (title / album / track / genre=Audiobook).

##### Engineering

- **Replaced `pdf-parse` (2018 PDF.js, unmaintained) with `unpdf`** — current,
  serverless-friendly PDF.js. Per-page extraction via a direct array index
  (no render-hook page-order invariant to desync).
- Real unit test suite (Node test runner) for chunking, page-range parsing,
  SSRF address checks, format detection, strippers, key sanitization, voice +
  OCR-language mapping. ESLint flat config added.

##### Security

- **SSRF guard** on Document URL fetch: rejects non-http(s) schemes and any host
  resolving to private / loopback / link-local / CGNAT ranges (incl. cloud
  metadata 169.254.169.254). Redirects are followed manually and re-validated
  at every hop.
- Added `.dockerignore` so `.env` / secrets / local state never enter image layers.

##### Pricing (PPE) — simpler + loss-proof

- **Single value meter:** removed `pdf-page-narrated`; narration is now billed
  purely per `audio-minute-generated` ($0.04/min) so the same value isn't
  double-billed on two axes.
- **`apify-actor-start` raised to $0.12** (from the $0.00005 platform default):
  it's the only revenue on a failed / 0-page run, so it's sized to stay
  margin-positive on failures after Apify's 20% cut while remaining trivial
  against value events on real documents (and absorbed by free trials).
- `ocr-page-processed` stays $0.10. Net effect: ~90%+ margin on every real run,
  no loss on failed runs.

##### Billing safety

- `maxCostUsd` now also clamps the **actual** audio-minute charge (not just the
  pre-flight estimate), so slow-speech / CJK runs can't bill past the cap.
- `maxCostUsd` floor is now $0.12 (the unavoidable actor-start fee); sub-floor /
  zero values are rejected instead of silently disabling the cap.

### \[0.1.0] - 2026-06-02

#### Initial public release as **Text to Audio Narrator**

Multi-format document narration: PDF, Markdown, plain text, and HTML in, MP3 out.

##### Supported inputs (7 formats)

- PDF (`.pdf`) — native text-layer extraction (no OCR)
- DOCX (`.docx`) — Word documents via `mammoth`: styles, lists, tables, footnotes
- EPUB (`.epub`) — ebooks via `epub2`: spine-ordered chapter walk, HTML stripped per chapter
- Markdown (`.md`, `.markdown`, `.mdx`) — syntax stripped before TTS so the voice reads natural prose
- Plain text (`.txt`, `.text`) — UTF-8 with BOM handling
- HTML (`.html`, `.htm`, `.xhtml`) — tags stripped, entities decoded
- RTF (`.rtf`) — control codes stripped, unicode + hex escapes decoded
- Raw text paste (new `text` input field) for blog drafts, ChatGPT replies, READMEs
- All four input modes: URL fetch, file upload, base64 paste, raw text paste
- ZIP-based formats (DOCX vs EPUB) distinguished by mimetype sniff + extension hint

##### TTS engines

- **Edge TTS** (free, no API key) — 400+ neural voices, 70+ languages, the recommended default
- **OpenAI BYOK**: `gpt-4o-mini-tts` (steerable), `tts-1`, `tts-1-hd`
- **ElevenLabs BYOK**: `flash-v2_5`, `turbo-v2_5`

##### Quality & reliability

- Auto language detection + Edge voice picker
- Provider-adaptive concurrency (Edge 8, OpenAI 10, ElevenLabs 2) to avoid 429 storms
- Word-boundary-aware chunking — no mid-word audio cuts in long technical paragraphs
- Edge TTS WebSocket guarded with 90 s timeout to avoid silent hangs
- ffmpeg concat with correct MP3 duration metadata
- Resume cache: re-runs skip already-synthesized chunks (no re-paying for TTS already done)
- Skip-failed-chunks mode for messy documents (auth / quota errors still abort cleanly)
- Friendly errors for scanned / encrypted / password-protected PDFs
- Collision-safe KV store key sanitization for long Apify run IDs

##### Output

- Chapter-sized MP3 parts for long books (configurable via `maxPartMb`)
- Shareable `INDEX.html` page with inline players + download links for every part
- `PREVIEW` key written before TTS starts with pages-to-process + estimated cost
- `OUTPUT` JSON record with `indexUrl`, `audioUrl`, `parts[]`, durations, voice, model, cost, status

##### Pricing & safety

- Pay-per-event: actor-start $0.02, per-page $0.05, per-audio-minute $0.03
- No provider markups, no premium-voice surcharges
- Optional `maxCostUsd` hard cap aborts cleanly before any TTS if pre-flight estimate exceeds it
- For non-PDF formats: ~3000 chars = 1 pseudo-page for fair billing parity with PDF inputs
