# Architecture How `lektor_ai_comfy.py` turns subtitles into a finished video, and why each stage works the way it does. Most of the non-obvious choices here exist because the straightforward version was tried first and broke. ``` SRT / embedded track │ parse, clean, split dialogue ▼ subtitle blocks ──────────────► /tts_input.srt │ one HTTP job per block ▼ ComfyUI + Higgs Audio V3 │ per-clip atempo + fade ▼ /clip_NNNN.flac │ adelay + amix, batched ▼ tts_assembled.flac │ pass 1: mix with original audio │ pass 2: stream-copy mux ▼ {stem}_lektor.mkv ``` ## 1. Subtitle parsing `parse_subtitle_blocks()` reads the SRT with `utf-8-sig` (tolerating a BOM), splits on blank lines, and keeps blocks whose start falls inside the requested range. Times become **relative to `--start`**, which is why a later `--rebuild` must repeat the same `--start`/`--end`. `_clean_text()` then normalises each block, in this order: 1. **Strip formatting tags.** `{...}` ASS overrides (`{\an8}`, `{\pos(x,y)}`) and `<...>` HTML tags (``, ``). 2. **Detect dialogue.** Lines beginning `- ` mean two speakers and are kept separate; ordinary wrapped lines are joined with a space. 3. **Sentence-case ALL-CAPS blocks**, which the model would otherwise shout. 4. **Capitalise a lowercase first letter**, since a fragment continuing from the previous subtitle confuses the model. 5. **Add terminal punctuation** so the model produces a natural falling tail. Step 1 must come first. `ALL CAPS` looks mixed-case while the tag is still present — the `i` in `` is a lowercase letter — so the caps detection in step 3 would silently miss it. `split_dialogue_blocks()` then divides a two-speaker block into one clip per line, apportioning the block's duration by character count. The result is written to `tts_input.srt` in that video's job folder (`output/clips/