Table of Contents
- Why it works this way
- What a dubtitle is, and why it cannot be a fansub
- Why signs and songs are merged into the same track
- Anchored and unanchored repair
- Why the repair stage rejects so much
- Why the queues are smaller
- The glossary queue was mostly empty questions
- Two write guards, and what they cost
- The repair queue: the guards were right
- Punctuation that changes meaning
- What "reviewed" claims, and what it does not
- Why saving a verdict does not change the video
- Why a failed stage does not ship, and why a stop waits
- Why repair refuses on a failure rate, and keeps going past one bad line
- Why validation names three shows
Why it works this way
Understanding-oriented. Nothing here is a step to follow; it is the reasoning behind choices that otherwise look arbitrary, or look like bugs.
If you want to do something, see How-to guides. If you want a value looked up, see Reference.
What a dubtitle is, and why it cannot be a fansub
A dubtitle is a subtitle that carries what the English dub audio actually says.
That sounds obvious until you hold it next to the alternative. Most anime releases already ship an English subtitle track: a translation of the Japanese audio, written by a fansub group or a licensor. It is usually excellent prose. It is also a different translation of the same scene — different word choices, different sentence breaks, often a different register, and sometimes a different meaning where the dub script was rewritten for lip flap.
So a dubtitle cannot be produced by taking the existing subtitle track and re-timing it. That produces a subtitle that reads beautifully and is wrong against the sound. This is the worst failure mode available here, because it does not look like an error. A viewer reading along has no way to notice.
Everything downstream follows from that single constraint. The transcript comes from the dub audio and nothing else. The existing subtitle track, where one exists, is used only as a reference — never as a source.
Why signs and songs are merged into the same track
Most players show only one subtitle track at a time, so you get dub captions or signs, not both. Anime releases usually ship a Signs and Songs track: only on-screen text and song lyrics, carefully positioned near where they appear (an .ass file with placement tags), no dialogue. The dubtitle sidecar is the complement: the English dub dialogue at the bottom of the screen (a .srt generated by Whisper or subgen). The two share a timeline, so the pipeline extracts the signs track from the video, appends the dub dialogue under a clean bottom style, and writes one .ass that renders both at once. The project started as this merge step alone and has grown into the full pipeline (transcribe, repair, merge, mux) with additive per-show dictionaries.
Anchored and unanchored repair
Speech recognition is good at English sentences and bad at invented proper nouns. It will
hear Doflamingo and write Dothamingo. No amount of audio quality fixes this: the word
is not in any dictionary the model has.
So a repair stage takes the low-confidence and name-suspect lines and asks a small language model to fix them. That model needs to know what it is allowed to write.
Anchored repair happens when your copy of the episode carries an English subtitle track for the Japanese audio. The repair model gets the overlapping reference line as context. It can see that the scene involves a character called Doflamingo, and correct the name without inventing anything.
Unanchored repair happens when there is no such track — a dub-only release. The model
gets only the show's glossary. This is more dangerous, and the danger is measured, not
theoretical: on an earlier model, glossary-only repair turned Oimo into Zoro. A
confident, fluent, entirely fabricated name.
Why the default is off, and why you may still want it on
Because unanchored repair can fabricate, it is off by default. But leaving it off is not
free either. Measured on One Pace S31E01: 161 lines were refused for want of an anchor and 0
were repaired. That season carries 6,492 such lines. Among them was Dothamingo, which the
deterministic glossary matcher cannot reach either — too far from Doflamingo by edit
distance, and phonetically distinct under Metaphone. Nothing in the pipeline could fix it.
Re-running those 161 lines with the gate open produced 21 repairs, 18 of them acceptable,
including Dothamingo → Doflamingo.
That is one episode, of one show, on one model. Not enough to flip a default. Enough to make the choice yours.
Why it is declared per show, not globally
Originally this was a single environment variable. That turned out to be a trap.
The One Pace library was produced with the flag hand-set in a shell. Nothing committed recorded that. A later merge pass, run from the committed scripts with no hand-set environment, therefore skipped every line for want of an anchor — and then rebuilt the subtitle from the raw transcript over the top of the shipped repairs. Reproduced on S31E24: 144 targets, 0 repaired, 144 skipped, and a season's worth of corrections silently reverted to raw speech recognition.
The setting now lives in the show's glossary file, which is a committed artifact. A show that declares nothing behaves exactly as it did before. And a run that would skip every line on an episode that already has repairs now refuses that episode and says so, rather than quietly overwriting it.
The question the setting answers is one you can answer about your own library without knowing any of the above: does your copy of this show have English subtitles for the Japanese audio? If no, unanchored repair is what you want.
Why the repair stage rejects so much
The repair model proposes a line. A gate decides whether to write it.
The standard the gate is trying to enforce is same referent, same sense — not
word-for-word fidelity. Shortening Hawkeye Dracule Mihawk to Mihawk is fine: same
character, same information. Changing factory to needle is not. Changing VIVRA card to
Vivi card swaps an item for a character.
The gate cannot tell those apart. Its checks are mechanical — length ratio, does it fit the card's timing, how many words were lifted from the reference, does it invent a name that is not in the glossary. Both bad examples above pass every one of them.
This is stated plainly rather than hidden because it determines what the human review stage is for. The mechanical gate removes the obviously wrong. A person removes the plausibly wrong. There is no third thing.
The reference-borrowing check
The most common way a repair goes wrong is not fabrication — it is the model quietly copying the reference translation instead of correcting the transcript.
Measured across every repair the library had accumulated: one model imported reference
wording in 84.1% of its repairs, a quarter of them importing three or more words.
That's enough of that, idiots! became Hold it, you brats! — the reference, verbatim, and
not what the dub says.
The old gate was a length band, which a same-length rewrite passes without slowing down. The check now counts words taken from the reference.
Why a repair that does not fit is rejected rather than accommodated
A card's timing comes from the spoken onset in the dub audio. It is not adjustable at repair time. If a corrected line does not fit the card it is correcting — too many characters for the seconds available — the repair is refused.
The alternative would be to move or extend the card, which desynchronises it from the audio to make room for text. That trades a visible problem for an invisible one.
This has a consequence worth knowing: some genuinely correct repairs are refused for want of room. Splitting a card so a longer correction fits is a known gap, deliberately out of scope for the beta.
Why the queues are smaller
Both the glossary and the repair stage hand decisions to a human. As the guards multiplied the queues grew faster than anyone could read them, so each queue now has to carry a decision. The changes below come from measuring the queues, not from trimming them by feel.
The glossary queue was mostly empty questions
A rebuild of the One Pace glossary flagged 687 terms. 578 of them were no-match: the wiki had
no candidate, so there was nothing to accept or reject. 109 carried a decision. A term with no
candidate is now recorded as checked and counted, and the review lists the other 109.
Two write guards, and what they cost
Verify writes an expansion such as Momonosuke → Kouzuki Momonosuke next to the short form,
and never over it, so a wrong write costs one unused phrase. Wrong writes still add up: with the
guards off, the Jev decision backend wrote 87 expansions and 45% were right. Two guards fix most of it.
- Whole word.
Usop→Usoppis a spelling fix andBurn→Coburnis a different word, but a substring test passes both. The term must now be a whole word of the title. - Common word.
Castle→Hancock Castleexpands an ordinary word into a name. A term the word list spells in lowercase is now flagged. The list capitalises names (Hancock,Rayleigh), so real names are not caught.
With both on, Jev wrote 29 expansions, 86% right, and sent 58 to the queue, of which 14 were
right. That is the price: a person reads 58 items to find 14. VERIFY_QUEUE_BLOCKED_EXPANSIONS=0
stops queuing those, and 9 correct fuller names are then never offered. Because the short form
stays, the loss is small, which is why it is a setting and not a default.
The repair queue: the guards were right
A blind sample of 100 rejected repairs from the Wano episodes, judged by two models that agreed on 86%, found that about four in five rejections were correct: a wrong name, added words, a rewording. So nothing is accepted automatically. Two things did come out of it.
- A rejection that changed more than six words was never one a person accepted. These are still recorded and now hidden from the default view.
- Seven of the 100 changed no word at all, only a comma or a capital, and were refused anyway. The
check that stops a repair from making a crowded card worse treats one added period as one more
visible character. An opt-in setting,
REPAIR_ACCEPT_PUNCT_CASE_ONLY, relaxes it for added.and,only.
Punctuation that changes meaning
Let's eat, Grandma. and Let's eat Grandma. are the same words. The gate compares words, so it
cannot tell them apart, and in a set of 60 invented pairs 33 pass it today. Refusing every removed
or changed mark would be worse: on the real Wano repairs about half the accepted ones changed a
mark, and a hand-judged sample of 40 found 30 harmless, 7 that shifted the tone and 3 that also
changed words. At least four of those seven were improvements. So there is no general guard.
REPAIR_GUARD_TERMINAL_PUNCT refuses the narrow case of a flipped ?, a swapped ! and ?, or an
ellipsis turned into a stop. On 451 real pairs it refused 21, and most were corrections of a stray
?, so it is off. If you turn on REPAIR_ACCEPT_PUNCT_CASE_ONLY, audit a sample of what it
accepted first.
What "reviewed" claims, and what it does not
The review stage does not present you with the whole episode. It presents the lines the pipeline was unsure about — low confidence, or suspected of containing a mangled name.
So when an episode is described as reviewed, the claim is precisely:
Every line the pipeline flagged as uncertain has been read and judged by a human.
It is not a claim that the episode was proofread. The lines the pipeline was confident about were never shown to anyone, and some of them are wrong.
This distinction is why verdicts are stored the way they are.
Why verdicts are keyed on text, not position
A decision records the pair (original text, proposed text), normalised — never the
episode and line number.
Position is fragile. It does not survive a re-transcription, and it means nothing in somebody else's library where the same show is a different encode. The text survives both.
It also pays for itself: Roger's treasure belongs to me occurs in three consecutive
episodes with an identical mis-transcription. One verdict settles all three. In a season
with heavy catchphrase repetition this is the difference between a reviewable workload and
an unreviewable one.
The trade is that a verdict is show-wide. Judging a line in one episode judges it everywhere in that show. That is intended — it is the same line — but it means the review page shows you which other episodes a verdict will touch before you make it.
Why punctuation is part of a line's identity
Verdict matching folds away case and whitespace, but keeps punctuation.
Restoring punctuation is most of what the repair stage does, so punctuation is signal, not
noise. CP-0. and CP? are a real transcript/proposal pair; the owner rejected that
repair. If punctuation were folded, the rejection would match — and suppress — the very text
it was rejecting in favour of.
The one exception is the apostrophe. ' and ' are two renderings of one character, and
English dub dialogue is mostly contractions, so this is not an edge case but the majority of
lines.
Why saving a verdict does not change the video
The review page has two buttons, deliberately.
Save verdicts is cheap. It records your judgement and changes what the next repair run ships for those lines.
Apply decisions to this episode rewrites the subtitle, drops the completion stamp, and lets the merge loop re-mux the file. On a multi-gigabyte MKV that is not something to do every time a reviewer scrolls halfway down a list and closes the tab.
One button for both would trigger a full re-mux on every partial pass through an episode. So there are two, and the page says which does what.
The consequence: an episode you have already reviewed and muxed does not update itself. Nothing re-opens it. If you want your verdicts in the video, press the second button.
Why a failed stage does not ship, and why a stop waits
Mux ends with a completion stamp, and a valid stamp tells every later sweep the episode is done. That is what makes re-runs safe, and it is also why a wrong result is expensive: once the stamp is written, nothing looks at the episode again.
So mux has to be sure what it is shipping. If repair refuses to overwrite repairs that already
shipped, the .srt on disk is raw speech recognition. Muxing it would put that in the video
and mark the episode done. Each stage therefore records its outcome in
<stem>.dubtitles.stages.json, and mux runs only for an episode whose earlier stages passed.
A failed episode is retried on the next pass instead.
The same reasoning shapes the stop. generate.py writes the .dubtitles.fail marker before it
starts transcribing, so that a crash does not loop forever. A process killed at an arbitrary
moment is indistinguishable from a crash, and it leaves the marker behind. A stop therefore
waits for a safe point instead of interrupting: the unit in flight finishes, no new one
starts, and the exit code is 0. The cost is that docker stop needs a grace period as long as
your longest unit of work. Stop the container without losing
work says how to set it.
Why repair refuses on a failure rate, and keeps going past one bad line
Repair rebuilds the whole .srt from its results. A run that loses its model halfway would
ship a file that mixes repaired lines with raw speech recognition, so a failing backend has to
be judged across the run, not one line at a time.
The 0.2.2 guard refused an episode only when every target had failed. That missed the ordinary
case. Ten of a hundred targets had no fansub line to anchor on, the backend went down, and the
ninety targets that did reach it all failed. Ninety is not a hundred, so the guard stayed quiet
and the .srt was rebuilt as raw speech recognition over repairs that had already shipped. The
count now starts from the targets that actually reached the model.
The trigger is a share of failures, because a single failure says too little. Some providers refuse certain lines every time, for example with a content filter. A line the model rejects as a request (HTTP 400, 404, 409, 413 or 422) therefore counts as no repair for that line, and the transcript text stays. A bad key, a rate limit, a server error and a dead connection do count. They say nothing about the line and a lot about the backend. If every request of an episode is rejected as a request, the model id or the URL is wrong, and the episode is refused.
Five failures in a row count as an outage on their own. Once the backend is clearly down, sending the other targets only costs time.
The deadlines exist because retries and slow answers add up. A throttled provider that asks for a long wait on every line can hold one episode for hours, and the stop at the end of the nightly window would arrive in the middle of a wait. The default of 3600 seconds per episode is about four times the slowest of the 1,631 episodes repaired on the reference install (897 seconds; the median took 52). A separate cap on each request stops a server that sends a byte every few seconds from outlasting the per-read timeout.
A refusal leaves the .srt as it was, records backend-unreachable (or stopped, for a soft
stop), and the next pass tries again. Below the 10% line the episode ships, and the few lines
that failed keep their transcript text.
Why validation names three shows
The code is general. It has no special cases for any show.
But "general" and "validated" are different claims. One Pace is the show the pipeline was built for and has been measured against, episode by episode, for months: its glossary is complete, its failure modes are catalogued, and its output has been watched. Sword Art Online and Digimon were added because one show hides whole classes of bug, and each found problems One Pace could not:
- Sword Art Online carries real fansub tracks, so repair runs anchored there, while One
Pace's S31 has no anchor at all. It found the audio-offset defect that shifted every cue in
an episode. It also showed that the glossary miner had never worked: it passed
ffprobea flagffprobedoes not have and read nothing from any file, which One Pace's hand-curated glossary had hidden. Fixing that exposed an unguarded timeout that killed a whole run, and fixing that exposed the miner reading a signs-and-songs track as if it were dialogue. - Digimon files carry the official English dub subtitles as an image track, which makes them an answer key. Over episodes 1 to 27, about 91% of the output's words matched it (88 to 94% per episode, measured October 2026). Most misses were names: Etemon written as "Edamon", Gennai heard as "Jedi". The official track leaves out shouts and attack calls, so lines with no counterpart there are not counted as errors.
Other shows work. They are also where the bugs are found. Point it at your library, and expect to file something.