63 lines
3.3 KiB
Markdown
63 lines
3.3 KiB
Markdown
|
|
# 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.
|