A transcript is a record of what a person said. If an AI “fixes” it into what the person should have said, it is no longer a record. I learned this on my own archive of Korean sermon recordings.
The Need: Fix the Mistakes, Keep the Speaker
Speech-to-text gets Bible words wrong. It writes a hardship word for a different hardship word, a town name as nonsense, a book of the Bible as another book. For a searchable archive, that matters, because people search for those names.
So I added a step that compares the transcript with the full text of a Korean Bible. The idea is simple: if the speaker says a place name and the text has a wrong spelling of it, the Bible tells us the right one.
The problem is what the AI does with that Bible text. Preachers often read from an older translation than the one I use for checking. When I let the model compare freely, it “corrected” those readings into the modern wording. The sermon was no longer what the speaker said.
The Proof: Three Episodes, Same Rule
Three episodes are now live in my archive with the new method. The rest of the archive is still running while I write this.
| Episode A | Episode B | Episode C | |
|---|---|---|---|
| Lines | 132 | 108 | 207 |
| Input / output tokens (Gemini) | 61K / 10K | 34K / 12K | 188K / 18K |
| Single-word fixes applied | 14 lines | 2 lines | 11 lines |
| Verse rewrites held back as suggestions | 25 lines | 5 lines | 35 lines |
One example of each kind, from a psalm that was read aloud:
- Applied: the speaker said a word that the speech-to-text wrote as yeonan (연안). The Bible has yeonhan (연한), “span of life”. One letter apart, and it sounds the same. This is a real recognition error, so it was fixed.
- Held back: the speaker read “dark valley” (그늘진 골짜기). The modern text says “shadowy valley” (음침한 골짜기). Both are real translations. The speaker used the first, so the transcript keeps it. The modern wording is saved next to the line as a suggestion, shown in a blue box in my viewer, and it is not used.
The Story: Why I Care About This
When I listen to a recording and then read the text, I want the same words. A transcript that quietly changes the translation is a small lie, even if every change is “more correct”.
While testing, I told Claude exactly what the comparison was for:
💬 Prompt that worked “Please keep the transcript the way the speaker said it. Names and other words the speaker said can be transcribed wrongly, and that is why I asked you to compare it with the full Bible text.”
That one sentence changed the design. The Bible text is a reference for spotting misheard words. It is not a script to replace the speaker.
One more rule came from the admin page of my archive, where I can hide an episode:
🗂 Claude.md Rule Episodes hidden in the admin page (
published: false) must never be transcribed. Re-check this right before each episode, not only when the job list is made.
The How: What the Pipeline Does
Hardware and cost. The speech-to-text runs on a GPU server that someone lent me access to, so it costs me $0. I use Whisper large-v3 through the Hugging Face transformers pipeline. (The faster-whisper build on that machine ran on CPU only, which was too slow.) The correction uses Gemini 2.5 Flash with temperature 0. I have not looked at my actual bill. From token counts at list price, one of these episodes costs about $0.04 to $0.13. While tuning, I ran the same three episodes through the correction step about seven times. By my estimate that was about $1.20 in total. Claude is not used in the pipeline at all, on purpose, to keep the cost predictable.
Step 1: transcribe on the GPU box. The script copies the audio to /dev/shm (RAM), runs the model, copies back only the text, then deletes the folder in a finally block. Nothing stays on the machine. It cuts the audio at silences with a voice-activity detector (chunks of at most 27 seconds), transcribes in batches of 16, and retries any chunk that looks like a hallucination, for example one that repeats itself.
Step 2: general fixes, 40 lines at a time. Each chunk is sent as [12] sentence, and the model must return the same numbered lines. The prompt says: change as little as possible, keep the speaker’s grammar, leave person names alone.
Step 3: compare with the Bible. The script finds which chapters are mentioned (“Psalm 23” and so on) and puts only those chapters in the prompt. The model returns a corrected version of each line.
Step 4: the gate. This is the part that matters. I do not accept the model’s whole line. I compare the line to the earlier version word by word, and I accept a change only if all of these are true:
- It replaces one word with one word. No insertions, no deletions, no words merged or split.
- The two words sound alike. I break each Hangul syllable into its letters (
unicodedata.normalize("NFD", ...)) and require a similarity of 0.65 or more. A second rule also allows a single changed syllable that keeps the same first consonant. A hardship word and its modern synonym share almost no sounds, so they fail. A misheard word and the true word usually pass. - The word is not on a short list of translation-style words (old and new forms of “said”, “lives”, “is not”).
Everything else goes to a bible_suggest field. The line keeps the speaker’s words. Every applied change is also logged in a bible_fix list, such as 연안 → 연한, so I can check it later.
Step 5: small clean-ups. Filler sounds like “uh” and “um” are removed by a fixed rule, not by the model. If the model empties a line that is not a filler or a broadcast hallucination, the original line is restored.
The bug that almost got through. In one episode the model returned correct line numbers, but the text was shifted by one or two lines for about 40 lines. Line 80 had the content of line 81. My first check only asked “are all the numbers there?”, so it passed. Now, if three or more lines in a chunk are less than 45% similar to what was sent, the chunk is retried, up to three times, and then left as it was.
🗂 Claude.md Rule Check an AI’s output for line count and for similarity to the input. Numbers can be right while the text is shifted.
I should be honest: after I added the guard, it never triggered again on my tests. I cannot say whether it fixed the problem or whether I got lucky.
What Is Still Not Solved
- The gate lets through some changes of word endings, such as an old ending replaced by a modern one, because they sound alike. I check them from the
bible_fixlist. - The model sometimes changes a person’s name by a single letter, even though the prompt forbids it. The script flags it, but it cannot know the right name. A person has to check.
- I looked at three episodes closely. Whether the same rules hold for all of the rest, I will find out as they finish.
I read the results in a small HTML viewer: raw text and corrected text side by side with changed words highlighted, and a filter for each kind of flag. It is a single local file, and it made the difference between “I trust it” and “I checked it”.