Automating QA by comparing generated audio against the original script.
In my previous article, I wrote about solving ear fatigue in long-form TTS: repeated cadence, unnatural pacing, and the tendency for generated narration to fall into predictable patterns.
One of the conclusions I came away with was that higher temperature gave the model more room to vary its performance without changing the words.
Then I listened to the final master of Chapter 9.
At 03:48, the script called for a short exchange:
Sarah: “You made dinner?”
Daniel: “I tried.”
Instead, the model kept going.
It invented new dialogue. Then more dialogue. Then blocking, reactions, and tension that weren’t anywhere in the manuscript.
For roughly 73 seconds, it wrote and performed its own version of the story.
Then, at about 05:01, it found the script again and continued as though nothing had happened.
That created a much bigger problem than one bad section of audio.
If Chapter 9 could invent 73 seconds of dialogue, what had the other 12 chapters done?
Had the model dropped sentences? Substituted words? Added smaller pieces of dialogue that weren’t obvious enough for me to notice?
I had already listened to the audiobook. But listening for whether a performance sounds good is different from verifying that every generated word matches the manuscript.
I needed QA.
The Problem With Manual Verification
The audiobook was roughly 80 minutes long.
I could listen to the entire thing again while following the manuscript line by line, but that would turn 80 minutes of audio into several hours of concentrated verification.
More importantly, that doesn’t scale.
I already had three things:
- The ground-truth manuscript.
- The generated audio.
- A multimodal model capable of processing audio and text together.
So instead of manually comparing them, I used the model to perform a first-pass comparison.
The job was simple:
Listen to the generated audio, compare it against the source script, and flag meaningful discrepancies.
That became the Multimodal Script Verification Pipeline.
Treat the Script as Ground Truth
The verifier wasn’t judging whether the narration sounded good. It wasn’t evaluating pacing, emotion, or performance.
It had a narrower job: compare what was spoken with what was written.
For each chapter, it received the generated audio and corresponding script and looked for:
- inserted content,
- omitted content,
- substitutions,
- repeated passages,
- and other meaningful mismatches.
The manuscript remained the source of truth.
The verification request looked roughly like this:
# scripts/media/audit_all_chapters.py
import json
import time
from pathlib import Path
from google import genai
client = genai.Client()
AUDIT_PROMPT = """You are a strict professional audio QA auditor for an audiobook production company.
Ground Truth Narration Script:
\"\"\"
{script_text}
\"\"\"
Carefully listen to the entire audio file and compare it word-for-word against the ground truth script.
Look for ANY:
1. Hallucinations / Added words (words spoken in audio that are NOT in the script)
2. Omissions / Skipped words or lines (words in the script that were NOT spoken in the audio)
3. Substitutions or major stutters
Return a structured JSON report inside ```
json ...
``` code blocks:
{{
"status": "PERFECT_MATCH" | "DISCREPANCIES_FOUND",
"discrepancies": [
{{
"timestamp": "MM:SS",
"type": "HALLUCINATION" | "OMISSION" | "SUBSTITUTION",
"expected": "text in script",
"actual": "spoken text in audio",
"notes": "brief explanation"
}}
]
}}
"""
The architecture was essentially:
Ground-truth script ─┐
├──> Multimodal verifier ──> Structured discrepancies
Generated audio ─────┘
That gave me an automated QA pass I could run across the entire audiobook instead of manually comparing every spoken line against the manuscript.
Auditing the Book
I ran the verifier across all 13 tracks.
Most came back without a reported discrepancy. Others were flagged for review.
The original audit output included:
| Track # | Chapter Title | Status | Type | Timestamp | Detail |
|---|---|---|---|---|---|
| 00–02 | Credits & Ch 1–2 | ✅ Clean | — | — | 100% Verbatim |
| 03 | Ch 3: The Friday Dinner | ⚠️ Flagged | SUBSTITUTION |
06:32 | Spoke "We doing this" instead of "We're doing this" |
| 04–06 | Chapters 4–6 | ✅ Clean | — | — | 100% Verbatim |
| 07 | Ch 7: The Other Side of the Table | ⚠️ Flagged | OMISSION |
00:00 | Omitted chapter title heading entirely |
| 08 | Ch 8: The Chipped Mug | ⚠️ Flagged | SUBSTITUTION |
02:23 | Said "chip's lip" instead of "chipped lip" |
| 09 | Ch 9: The Table for Eight | ❌ Flagged | HALLUCINATION |
03:48 | 73-second unprompted argument inserted |
| 10–12 | Ch 10–11 & Credits | ✅ Clean | — | — | 100% Verbatim |
The verifier is another model. Its output shouldn’t be treated as unquestionable proof that every unflagged track is perfect.
Its job is to make QA tractable: compare generated audio against known ground truth and surface discrepancies worth investigating.
The Chapter 9 hallucination was dramatic enough that I noticed it while listening.
A missing heading or substituted word is much easier to miss.
The most obvious TTS failures may not be the most dangerous ones.
The dangerous ones can be the ones that still sound plausible.
Listening and Verification Are Different Jobs
I still need to listen to the audiobook.
Script verification doesn’t tell me whether a performance is emotionally convincing, whether the pacing feels right, or whether a voice choice becomes exhausting over a full chapter.
Those are performance-review problems.
But listening for performance and verifying fidelity aren’t the same task.
Before Chapter 9, my process was effectively:
Generate
↓
Listen
↓
Ship
The hallucination exposed the missing step.
Now the pipeline looks more like:
Generate
↓
Performance review
↓
Verify against ground truth
↓
Review flagged discrepancies
↓
Repair
↓
Ship
The model that generated the audio doesn’t get to define whether its output is correct.
The manuscript does.
The 73-second hallucination was the error I heard.
The auditor was how I checked for the errors I didn’t.
Top comments (0)