63 lines
3.3 KiB
Markdown
63 lines
3.3 KiB
Markdown
|
|
# Fix Karaoke Burn-In Sync and Export Progress
|
||
|
|
|
||
|
|
**Date:** 2026-09-21 23:22
|
||
|
|
**Task:** Fix two bugs: karaoke ASS burn-in out of sync / skipping lines, and export progress stuck at 0% then jumping to 100%.
|
||
|
|
|
||
|
|
## Changes Made
|
||
|
|
|
||
|
|
### Bug 1: Karaoke ASS Burn-In — Two Root Causes Fixed
|
||
|
|
|
||
|
|
**A. YouTube VTT two-line overlay pattern.**
|
||
|
|
|
||
|
|
YouTube auto-generated VTT cues have a specific structure:
|
||
|
|
- Many cues have TWO text lines: line 1 is static context (previous cue text, no `<c>` tags), line 2 is the karaoke line (with `<timestamp><c> word</c>` tags)
|
||
|
|
- Zero-duration transition cues (e.g., `00:00:02.629 --> 00:00:02.639`) are just visual transition frames
|
||
|
|
|
||
|
|
The old code joined all text lines and tried to parse word timings from the combined mess. Fix:
|
||
|
|
- Skip cues where `(end - start) < 0.05s` — these are transition frames
|
||
|
|
- For multi-line cues, only process the line that contains `<c>` tags for karaoke
|
||
|
|
- Ignore the static context line (previous cue's text repeated)
|
||
|
|
- Cues with no `<c>` tags emit as plain text (graceful fallback)
|
||
|
|
|
||
|
|
**B. Wrong ffmpeg filter.**
|
||
|
|
|
||
|
|
Per ffmpeg-micro.com: "The `subtitles` filter routes your file through libavformat's subtitle converter and applies `force_style` overrides, which can flatten karaoke timing. The `ass` filter hands the file straight to libass."
|
||
|
|
|
||
|
|
Fix: Changed `build_ffmpeg_args_burnin_subs()` to use `-vf ass=<path>` instead of `-vf subtitles=<path>`. Since style is already baked into the ASS `[V4+ Styles]` header, no `force_style` is needed. The `_caption_style` parameter is now ignored (prefixed with `_`).
|
||
|
|
|
||
|
|
### Bug 2: Export Progress — Root Cause Fixed
|
||
|
|
|
||
|
|
ffmpeg writes its progress output to stderr using `\r` (carriage return) to overwrite the same line, NOT `\n` (newline). `BufReader::lines()` splits on `\n` only, so all progress was buffered as one giant "line" that only yielded when ffmpeg exited.
|
||
|
|
|
||
|
|
Fix: Use ffmpeg's `-progress pipe:1 -nostats` flag:
|
||
|
|
- Adds `-progress pipe:1 -nostats` to ffmpeg args (before the output file)
|
||
|
|
- Reads stdout (not stderr) — `-progress` outputs proper `\n`-delimited `key=value` pairs
|
||
|
|
- Parses `out_time_ms=<microseconds>` lines and computes `percent = out_time_ms / (duration * 1_000_000)`
|
||
|
|
- stderr is still piped but drained on a separate thread for error reporting on failure
|
||
|
|
|
||
|
|
Example `-progress pipe:1` output (proper newlines):
|
||
|
|
```
|
||
|
|
frame=150
|
||
|
|
fps=45.2
|
||
|
|
out_time_ms=6250000
|
||
|
|
progress=continue
|
||
|
|
```
|
||
|
|
|
||
|
|
## Files Modified
|
||
|
|
|
||
|
|
| File | Change |
|
||
|
|
|------|--------|
|
||
|
|
| `src-tauri/src/services/clip_exporter.rs` | Fixed VTT parsing, switched to `ass=` filter, rewrote progress to use `-progress pipe:1` |
|
||
|
|
|
||
|
|
## Build Verification
|
||
|
|
|
||
|
|
- `cargo test` — all 43 tests pass
|
||
|
|
- `cargo check` — passes (only pre-existing warnings)
|
||
|
|
- `svelte-check` — passes (only pre-existing `vite.config.ts` errors)
|
||
|
|
|
||
|
|
## Lessons Learned
|
||
|
|
|
||
|
|
- YouTube VTT is NOT simple cue-per-line. It uses a two-line overlay pattern where line 1 is a static context repeat and line 2 has the karaoke data. Zero-duration cues are transition frames.
|
||
|
|
- The `ass` filter and `subtitles` filter in ffmpeg both use libass, but `subtitles` applies format conversion and `force_style` that can destroy `\kf` karaoke timing. Always use `ass` for karaoke.
|
||
|
|
- ffmpeg's stderr progress uses `\r` not `\n`. For Rust's `BufReader::lines()` to work, use `-progress pipe:1 -nostats` which outputs proper `\n`-delimited key=value pairs to stdout.
|