# Fix Burn-In Export and Re-Introduce Karaoke Preview **Date:** 2026-09-21 22:06 **Task:** Implement graceful burn-in fallback and re-introduce karaoke word-by-word highlighting in preview captions. ## Changes Made ### 1. Detect `subtitles` Filter Availability (Burn-In Guard) **Problem:** The user's ffmpeg build (Homebrew 9.0.1_1) lacks `libass`, so the `subtitles` filter is unavailable. Attempting burn-in export caused ffmpeg to crash with a filter parse error. **Fix:** - **`src-tauri/src/services/dependency_manager.rs`** — Added `check_subtitles_filter()` that runs `ffmpeg -filters` and checks for `subtitles V->V` in the output. - **`src-tauri/src/commands/dependencies.rs`** — Added `check_subtitles_filter_available()` Tauri command. - **`src-tauri/src/lib.rs`** — Registered the new command. - **`src/lib/bindings/dependencies.ts`** — Added `checkSubtitlesFilterAvailable()` frontend binding. - **`src/lib/components/ExportDialog.svelte`** — On mount, checks filter availability via the new command. If unavailable, the "Burn into video" checkbox is disabled with a help message telling the user how to install libass. ### 2. Parse Word-Level Timings from VTT **Problem:** YouTube auto-generated VTT files contain word-level timestamps in ` word` patterns. The old parser stripped ALL tags, losing this timing data. **Fix:** - **`src/lib/utils/vttParser.ts`** — Complete rewrite: - Added `WordSegment` interface (`{ text: string; startTime: number }`). - Added optional `words?: WordSegment[]` to `VttCue`. - Added `parseWordTimings()` that extracts ` word` patterns. - Added `getActiveWords()` helper that returns each word marked as `spoken` or upcoming based on `currentTime`. - The plain `text` field is preserved (stripped) for backward compat. ### 3. Render Karaoke Word-by-Word Highlights **Fix:** - **`src/lib/components/VideoPlayer.svelte`** — Updated caption rendering: - When `wordHighlight` is enabled and the cue has word segments, each word is rendered as a separate ``. - Spoken words display at full opacity; upcoming words display at 40% opacity. - CSS transition smooths the opacity change. - When disabled or no word data available, falls back to plain text rendering. ### 4. Add Word Highlight Toggle **Fix:** - **`src/lib/stores/preferences.svelte.ts`** — Added `wordHighlight: boolean` to `CaptionSettings` interface (default: `true`). - **`src/lib/components/CaptionSettingsPanel.svelte`** — Added "Word-by-word highlight" checkbox toggle. ## Files Modified | File | Change | |------|--------| | `src-tauri/src/services/dependency_manager.rs` | Added `check_subtitles_filter()` | | `src-tauri/src/commands/dependencies.rs` | Added `check_subtitles_filter_available` command | | `src-tauri/src/lib.rs` | Registered new command | | `src/lib/bindings/dependencies.ts` | Added `checkSubtitlesFilterAvailable()` binding | | `src/lib/components/ExportDialog.svelte` | Conditional burn-in disable + help text | | `src/lib/utils/vttParser.ts` | Word-level timing parser + `getActiveWords()` | | `src/lib/components/VideoPlayer.svelte` | Karaoke word rendering | | `src/lib/stores/preferences.svelte.ts` | Added `wordHighlight` to `CaptionSettings` | | `src/lib/components/CaptionSettingsPanel.svelte` | Added word highlight toggle | ## Build Verification - `cargo check` — passes (only pre-existing warnings) - `svelte-check` — passes (only pre-existing `vite.config.ts` type errors) ## Lessons Learned - YouTube VTT word-timing format: leading text has no `` wrapper, only subsequent words do. The first word inherits the cue's start time. - The `subtitles` filter requires libass at ffmpeg compile time — it's not enough to have ffmpeg installed. Homebrew's default build may or may not include it depending on the formula version. ## Follow-Up Items - Manual smoke test: Load a video with captions, verify karaoke highlighting works during playback. - Test export with captions enabled (muxed track) to confirm no regressions. - If user installs libass and reinstalls ffmpeg, burn-in should automatically become available next time ExportDialog opens.