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>
5.5 KiB
Fix Multi-Clip Workflow + Caption Support
Date: 2026-09-21 14:09
Task: Fix multi-clip creation and add time-synced caption support
Task Description
Two main issues addressed:
- Multi-clip bug: Pressing I/O keys always edited the selected clip instead of creating new clips when the playhead was outside the clip region.
- Caption support: Full pipeline for downloading, displaying, and exporting time-synced captions (embedded > external, English preferred, auto-generated deprioritized).
Changes Made
1. Fix Multi-Clip Workflow
src/lib/stores/clips.svelte.ts:
- Added
isTimeInsideClip(time, clipId)helper with 0.5s tolerance markInPoint(time): Now checks if playhead is inside the selected clip's range. If outside, deselects and setspendingInPoint(starts new clip). If inside, edits the existing clip.markOutPoint(time): Same logic — edits selected clip if inside, creates new clip from pending in-point if outside.
src/lib/components/Timeline.svelte:
- Added
selectClip(null)call when clicking empty timeline space (nohitTestClipresult), so the next I/O presses create a new clip.
2. Caption Metadata Parsing
src-tauri/src/services/video_resolver.rs:
- Extended
YtDlpJsonstruct withsubtitlesandautomatic_captionsfields (bothHashMap<String, Vec<SubtitleFormat>>) - Added
detect_captions()function: checks for English subtitles (en, en-US, en-GB variants), prioritizes manual over auto-generated - Added
SubtitleFormatstruct for deserialization - Added 4 new unit tests for caption detection
src-tauri/src/models.rs:
- Added
has_captions: boolandcaptions_are_auto: booltoVideoMetadata
src/lib/bindings/video.ts:
- Added
hasCaptionsandcaptionsAreAutotoVideoMetadatainterface
3. Caption Download
src-tauri/src/services/subtitle_downloader.rs (new file):
- Downloads English VTT subtitles via
yt-dlp --write-subs(manual) or--write-auto-subs(auto-generated) - Uses
--sub-langs en.*,en --sub-format vtt --convert-subs vtt - Searches output directory for
.en.vttfiles, falls back to any.vtt
src-tauri/src/commands/video.rs:
- Added
download_subtitlesTauri command
src/lib/bindings/video.ts:
- Added
downloadSubtitles()binding
4. Embedded Subtitle Check
src-tauri/src/commands/media_analysis.rs:
- Added
check_embedded_subtitlescommand that usesffprobe -show_streams -select_streams sto detect subtitle streams, then extracts as VTT viaffmpeg -map 0:s:0 -f webvtt
src/lib/bindings/mediaAnalysis.ts:
- Added
checkEmbeddedSubtitles()binding
5. Caption Display in Player
src/lib/stores/videoSession.svelte.ts:
- Added
hasCaptions,captionsAreAuto,captionFilePathto session state - Added
loadCaptions()function that checks embedded subs first, then downloads external - Integrated into
triggerPostDownloadProcessing()
src/lib/components/VideoPlayer.svelte:
- Loads VTT file as blob URL via
captionBlobUrl - Renders
<track>element with propersrclang,label(with "(auto)" suffix), anddefaultattribute - Added CC toggle button (bottom-right overlay) with active/inactive styling
- Syncs
textTracks[].modewithcaptionsEnabledstate
6. Caption Export
src-tauri/src/models.rs:
- Added
include_captions: boolandcaption_file_path: Option<String>toExportConfig
src-tauri/src/services/clip_exporter.rs:
- Added
build_ffmpeg_args_with_subs(): lossless → mux asmov_text/srt; precise → burn-in viasubtitles=filter - Added
export_single_clip_with_subs()with fallback to no-subs on failure - Added
export_merged_with_subs() - Added 2 new unit tests for subtitle-aware arg building
src-tauri/src/commands/export.rs:
- Updated to conditionally use
_with_subsvariants based oninclude_captions
src/lib/bindings/export.ts:
- Added
includeCaptionsandcaptionFilePathtoExportConfiginterface
src/lib/components/ExportDialog.svelte:
- Added "Captions" section with checkbox toggle (only shown when captions are available)
- Shows "(auto-generated)" label when applicable
- Shows mux vs. burn-in note based on cut mode
Infrastructure
src-tauri/src/services/mod.rs: Registered subtitle_downloader module
src-tauri/src/lib.rs: Registered download_subtitles and check_embedded_subtitles commands
Build & Test Results
- Rust: 34 tests pass (including 4 new caption tests + 2 new export tests)
- Frontend: Builds cleanly (0 errors)
- Rust build: Compiles with only pre-existing unused variant warnings
Lessons Learned
yt-dlp --dump-jsonincludessubtitlesandautomatic_captionsmaps — both map language codes to arrays of{ext, url}objects- WKWebView
<track>elements work with blob URLs but needtextTracks[].modemanaged manually to sync with a toggle - For ffmpeg subtitle burn-in, paths with colons need escaping (
\:) in thesubtitles=filter - Subtitle muxing in lossless mode requires separate
-ifor the subtitle file and explicit stream mapping (-map 0:v -map 0:a -map 1:s)
Follow-up Items
- The
SubtitleFormat.extandSubtitleFormat.urlfields generate "never read" warnings — they exist for serde deserialization but could be suppressed with#[allow(dead_code)] - Caption time offset accuracy: when using
-ssbefore-i, subtitle timestamps may need adjustment for precise alignment in edge cases - Sprite sheet optimization (noted from prior session) is still pending