Includes: - Extended caption styling (font, shadow, dimmed color, bg toggle) - Media server, subtitle downloader, VTT parser, processing modal - Waveform tiers, thumbnail/timeline improvements, transport controls - Hybrid download model, dependency management, clip export enhancements - 21 chat summaries, 2 implementation plans, 2 design specs Co-authored-by: Cursor <cursoragent@cursor.com>
3.1 KiB
Karaoke Burn-In for Exported Subtitles
Date: 2026-09-21 23:11
Task: Convert word-timed YouTube VTT captions to ASS format with karaoke \kf tags so burned-in subtitles have the same word-by-word highlight effect as the preview.
Changes Made
1. VTT-to-ASS Converter with Karaoke Tags
src-tauri/src/services/clip_exporter.rs — Added several new functions:
-
vtt_to_ass_with_karaoke()— Main converter. Reads raw VTT, generates a complete ASS file with:[Script Info]header (1920x1080 play resolution)[V4+ Styles]section with the user's CaptionStyle baked in (PrimaryColour, SecondaryColour at 60% alpha for the "dim/upcoming" look, BackColour, Outline, Shadow, Alignment, BorderStyle=4)[Events]section where each cue is a Dialogue line with\kf<centiseconds>tags for word-level karaoke fill animation- Graceful fallback: cues without word-level timestamps render as plain text (no
\kf)
-
parse_vtt_word_timings()— Rust equivalent of the TypeScriptparseWordTimings(). Extracts(word, start_time)pairs from YouTube's<timestamp><c> word</c>format. -
format_ass_timestamp()— Formats seconds as ASS timestampH:MM:SS.cc(centiseconds). -
hex_to_ass_color_with_alpha()— Likehex_to_ass_color()but with a specific alpha byte. -
find_timestamp_tag_pos()/find_next_timestamp()— Helpers for parsing VTT timestamp tags without regex.
2. Updated Burn-In Export Path
In export_single_clip_with_subs(), the burn-in path now uses:
VTT → vtt_to_ass_with_karaoke() → .ass file → subtitles= filter
Instead of the previous:
VTT → sanitize_vtt_for_ffmpeg() → plain VTT → subtitles= filter
Since the style is now embedded in the ASS file header, caption_style is passed as None to build_ffmpeg_args_burnin_subs() (no force_style needed — avoids potential conflicts between the ASS header and force_style overrides).
3. Updated UI Note
src/lib/components/ExportDialog.svelte — Changed the burn-in note from "word-by-word highlighting is preview-only" to "Captions will be burned into the video with your style settings".
Files Modified
| File | Change |
|---|---|
src-tauri/src/services/clip_exporter.rs |
Added VTT-to-ASS converter, word timing parser, ASS timestamp formatter, updated burn-in path |
src/lib/components/ExportDialog.svelte |
Updated burn-in note text |
Build Verification
cargo check— passes (only pre-existing warnings)cargo test— all 43 tests pass
Technical Notes
- ASS karaoke
\kf<N>means "fill this word over N centiseconds", transitioning from SecondaryColour to PrimaryColour. This matches the preview behavior where upcoming words are dimmed and spoken words are bright. - The ASS SecondaryColour is set to the same text color with 0x99 alpha (~60% transparent), matching the preview's
opacity: 0.4for upcoming words. - No regex crate was needed — the VTT word timing pattern is simple enough to parse with manual string operations.
- The
subtitlesffmpeg filter handles ASS files natively (same libass backend), so no filter change was needed.