Design notes

Pipeline

parse -> prepare texts -> [glossary] -> batch -> translate (validate/retry) -> render (wrap, flags) -> write

Everything that can be decided in Python is decided before the model is called: which cues have text, scene boundaries, glossary candidates, batch layout, prompt size.

Scenes and batches

A scene is a run of cues where each gap to the previous cue is at most 2 s. Scenes matter in two places:

  1. Batch cutting. A batch is filled to the batch size (60). If more cues remain, the batch is cut at the last scene start that falls in its second half; otherwise it is cut at the size limit. With scene starts at cue positions 40 and 80 and size 60, the batches are 40, 40 and 40 cues rather than 60/60/0, so conversations are not split mid-exchange when avoidable.
  2. Prompt hints. A blank line before the first cue of every scene tells the model the dialogue topic may change.

Each batch also carries the previous 5 translated cues (source => translation) as context, taken across batch boundaries, so tone and recurring phrasing carry over.

Id validation and retry

The model returns {"translations": [{"id": 12, "text": "..."}]} under a JSON schema. A response is reduced to the valid subset:

Missing ids are re-requested in a smaller prompt with the same context, up to 3 attempts total; then MissingCues lists what is left and the progress sidecar keeps everything done so far. Example: a 5-cue batch where the model omits 2 and 4 costs one extra call that contains only [2] and [4].

Two further failure modes are handled in the same loop: invalid JSON counts as an attempt that returned nothing, and a max_tokens cut-off splits the request in half and retries each half.

Only fully valid responses are written to the disk cache. Otherwise a retry with an identical prompt would replay the bad answer.

Glossary

Candidates are capitalised words that appear mid-sentence at least once, at least twice overall, and never in lowercase anywhere in the file. "Mainland" is excluded when "mainland" also appears; "Mira" in this is Mira and Mira, wait is kept with count 2. Contractions like I'm are not matched. The model receives term (count): example line for at most 60 candidates and returns source/target pairs; the result is saved as JSON for editing. Because the glossary is part of the progress fingerprint, changing it starts a fresh run rather than mixing renderings.

Wrapping

Visible length ignores tags and {\an8} overrides. Text that fits in 42 characters stays on one line. Otherwise every word boundary is scored:

score = 100 * overflow + |len(line1) - len(line2)| - 10 (if line1 ends in punctuation)

"We waited for hours, but nobody ever came to the door" splits after hours, (20/32: difference 12, minus the bonus 10, score 2) rather than after but (24/28: difference 4, score 4). Tags with attributes (<font color="red">) are treated as single tokens. When a split lands inside <i>...</i>, the first line is closed and the second reopened, so each line renders on its own. Cues made entirely of dash-prefixed speaker turns keep one line per speaker.

If the model drops italics from a fully italic source cue, they are restored; unbalanced <i>/<b>/<u> tags are repaired.

Reading speed

visible characters (newlines excluded) / cue duration above 21 chars/s is flagged in the summary. Nothing is retimed.

Resume

After every batch the sidecar <out>.progress.json is rewritten atomically (temp file plus os.replace). It stores a fingerprint of the input text, model, languages, formality, glossary and batch settings; a mismatch is ignored. On success it is deleted. Ctrl-C therefore loses at most the batch in flight, and the next identical command skips finished batches.

Estimate

Input tokens are computed from the real prompts (system text, schema, glossary candidates, context, cues) using ascii chars / 4 plus non-ascii chars / 1.5. Output tokens are source tokens times a per-language factor (Spanish 1.25, Hindi 2.6, Tamil 3.2, default 1.3) plus 12 tokens per cue for JSON framing. Cost uses $0.10 / 1M input and $0.50 / 1M output.