feat: embedded subtitles, resumed downloads, subtitle-only track menu
Verified in the running app and against the files on disk. Subtitles are muxed into the download as a soft SubRip track rather than written beside it. Without --write-subs, yt-dlp fetches them, embeds them and removes the WebVTT, so a download is one file — checked with ffprobe: video, audio, one mov_text track, no sidecar. WebKit exposes it, so the player lists and shows it with no fetch at all, which also means it works offline. They are converted to SubRip on the way in. YouTube's WebVTT pins every cue to the left edge and fills it with karaoke timing tags; the first embed carried both and rendered clamped to the side of the picture. SubRip carries neither. Only the plain language is embedded — "en" and "en-orig" are usually the same captions, and nothing can collapse duplicates once they are muxed in. Downloads left unfinished when the app closes are picked up on the next launch, at the quality and language they were asked for, which the downloads table now records. Verified by leaving a row "running" with no process behind it and restarting: it downloaded and completed. The player's track menu is subtitles only — the audio renditions are gone, the original-audio default still being fixed in the manifest — and tracks read "English" rather than "en". Turning subtitles off in one video now sticks for the next. The guard that applies the preference keyed on the number of text tracks alone, which does not change from one video to the next, so a choice of Off was never re-applied; it keys on the video as well. Embedding no longer depends on the on/off preference, the same trap that kept fetching switched off: a file downloaded without a subtitle track can never gain one offline. Delete all is a trash icon.
This commit is contained in:
@@ -1026,7 +1026,7 @@ async fn cache_thumbnails(state: &State<'_, AppState>) {
|
||||
pub async fn download_video(
|
||||
video_id: String,
|
||||
quality: String,
|
||||
sub_langs: String,
|
||||
sub_lang: String,
|
||||
app: AppHandle,
|
||||
state: State<'_, AppState>,
|
||||
) -> Result<(), String> {
|
||||
@@ -1038,6 +1038,7 @@ pub async fn download_video(
|
||||
{
|
||||
let db = state.db.lock().await;
|
||||
db.set_download_state(&video_id, DownloadState::Queued, None)?;
|
||||
db.set_download_request(&video_id, &quality, &sub_lang)?;
|
||||
}
|
||||
let _ = app.emit(
|
||||
"download:state",
|
||||
@@ -1070,6 +1071,7 @@ pub async fn download_video(
|
||||
.join(downloader::OUTPUT_TEMPLATE)
|
||||
.to_string_lossy()
|
||||
.to_string();
|
||||
let sub_langs = downloader::embed_sub_langs_for(&sub_lang);
|
||||
let mut args = downloader::build_args(&video_id, &out_template, &quality, &sub_langs);
|
||||
// Without this yt-dlp looks for ffmpeg on PATH, which a bundled app has no
|
||||
// reason to have. Merging video and audio would fail on a clean machine.
|
||||
@@ -1443,6 +1445,18 @@ pub async fn fetch_subtitles(
|
||||
Ok(read_vtt_dir(&dir).await)
|
||||
}
|
||||
|
||||
/// Downloads that were still going when the app last closed.
|
||||
///
|
||||
/// Killing the app kills yt-dlp with it, leaving rows queued or running that no
|
||||
/// process backs. The front end hands these straight back to `download_video`,
|
||||
/// so they rejoin the same queue rather than needing a second code path.
|
||||
#[tauri::command]
|
||||
pub async fn interrupted_downloads(
|
||||
state: State<'_, AppState>,
|
||||
) -> Result<Vec<(String, String, String)>, String> {
|
||||
state.db.lock().await.interrupted_downloads()
|
||||
}
|
||||
|
||||
/// Stops everything downloading or waiting to download.
|
||||
///
|
||||
/// Kills the running processes, then marks every row the database still calls
|
||||
|
||||
Reference in New Issue
Block a user