Files
gui-video-clipper/chat-summaries/2026-09-21_23-11-karaoke-burnin-export-summary.md
cottongin 8ad2f1c800 chore: stage all pending work — caption styling, media server, processing modal, docs, summaries
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>
2026-09-22 10:48:16 -04:00

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 TypeScript parseWordTimings(). Extracts (word, start_time) pairs from YouTube's <timestamp><c> word</c> format.

  • format_ass_timestamp() — Formats seconds as ASS timestamp H:MM:SS.cc (centiseconds).

  • hex_to_ass_color_with_alpha() — Like hex_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.4 for upcoming words.
  • No regex crate was needed — the VTT word timing pattern is simple enough to parse with manual string operations.
  • The subtitles ffmpeg filter handles ASS files natively (same libass backend), so no filter change was needed.