# Paged Teleprompter Burn-In Subtitles **Date:** 2026-09-22 **Task:** Replace rolling per-line ASS subtitle generation with a paged teleprompter model for burn-in captions. ## Problem The previous rolling subtitle implementation generated 3 independent ASS Dialogue events per spoken line (`Active → Context → Disappear`) with `\move` animations. When a new line arrived, both the new and previous lines scrolled simultaneously, creating a "2-line block jump" rather than a natural top-to-bottom reading flow. The active line always reset to the bottom position. ## Solution Rewrote the ASS generation to use **2-line paged blocks**: - **Paged model:** Spoken lines are grouped into consecutive pairs. Each page is a single ASS Dialogue event with `\N` (hard line break) between lines. - **Continuous karaoke:** `\k` tags flow from line 1 through line 2 within the same Dialogue, creating a natural top-to-bottom reading experience (teleprompter style). - **Cross-fade transitions:** Pages transition via 300ms cross-dissolve using `\fad` tags — no `\move` or `\clip` tags needed. - **Static positioning:** `\an2\pos(960, 1040)` anchors each page at bottom-center. No animation on position. - **Gap-based splitting:** If two consecutive lines are >2s apart, they're split into separate single-line pages. ## Changes Made | File | Change | |------|--------| | `src-tauri/src/services/clip_exporter.rs` | Added `group_into_pages()`, `build_page_karaoke_text()`. Rewrote `vtt_to_ass_with_karaoke()`. Removed `RollingLayout` struct and dead `build_karaoke_text()`. Fixed stale comments. | | `docs/superpowers/specs/2026-09-22-paged-teleprompter-subtitles-design.md` | Design spec | | `docs/superpowers/plans/2026-09-22-paged-teleprompter-subtitles.md` | Implementation plan | ## Key Implementation Details ### Karaoke Stitching Across Lines The `build_page_karaoke_text()` function collects word timings from both lines into a flat sequence. The last word of line 1 gets a `\k` duration that extends to line 2's first word start time, naturally covering any gap (silence) between lines. The `\N` is purely visual and doesn't interrupt the karaoke timeline. ### Cross-Fade Timing | Page Position | `\fad` value | |---|---| | First page | `\fad(0, 300)` | | Middle pages | `\fad(300, 300)` | | Last page | `\fad(300, 0)` | Display times are extended/preponed by 300ms to create overlap. ## Commits - `934c270` — feat(subtitles): add page grouping and cross-line karaoke stitching - `d13ba27` — feat(subtitles): rewrite ASS generation to paged teleprompter model - `2b95d8a` — chore: remove dead build_karaoke_text, fix stale comments ## Tests 23 tests passing (8 new + 15 existing). ## Lessons Learned 1. **ASS `\N` doesn't interrupt `\k` flow** — Hard line breaks within a single Dialogue event are purely visual; karaoke timing continues seamlessly across them. 2. **`\fad` > `\move` for transitions** — Static positioning with opacity-only transitions (`\fad`) is much simpler and cleaner than coordinate-based animations, especially for avoiding background-box artifacts. 3. **Trailing `\N` bug recurrence** — The trailing `\\N` in Dialogue format strings was a recurring bug from previous iterations. Must always check for this when writing ASS output. ## Follow-Up - Manual smoke test needed: export a clip with burn-in captions and verify the teleprompter reading flow.