From fcc1172a898791180fae34de0dfa5bd251ca9d40 Mon Sep 17 00:00:00 2001 From: anhtahaylove Date: Sat, 4 Jul 2026 21:15:41 +0700 Subject: [PATCH] Keep Unicode titles from lossy download output Windows process output can drop Vietnamese characters in yt-dlp progress lines, so completed single downloads now prefer metadata or the UTF-8 final filepath when choosing the display title. Playlist items keep the current item title so collection/history rows do not regress. Constraint: yt-dlp stdout/stderr can be decoded differently from the final filesystem path on Windows. Rejected: Always prefer filepath titles | playlist outputs can use representative filenames that are less accurate than the current playlist item title. Confidence: high Scope-risk: narrow Directive: Do not let lossy progress output override a known-good metadata or final filepath title for single downloads. Tested: bun run build:sdk Tested: cargo test display_title --lib Tested: cargo check --- src-tauri/src/commands/download.rs | 88 +++++++++++++++++++++++++----- 1 file changed, 74 insertions(+), 14 deletions(-) diff --git a/src-tauri/src/commands/download.rs b/src-tauri/src/commands/download.rs index 7c190aee..70db2696 100644 --- a/src-tauri/src/commands/download.rs +++ b/src-tauri/src/commands/download.rs @@ -203,6 +203,68 @@ fn title_from_filepath(filepath: &str) -> Option { .map(|s| s.to_string()) } +fn display_title_for_download( + metadata_title: Option, + current_title: Option, + final_filepath: &Option, + total_count: Option, +) -> Option { + let filepath_title = || { + final_filepath + .as_ref() + .and_then(|path| title_from_filepath(path)) + }; + + if total_count.unwrap_or(1) > 1 { + current_title.or_else(filepath_title).or(metadata_title) + } else { + metadata_title.or_else(filepath_title).or(current_title) + } +} + +#[cfg(test)] +mod display_title_tests { + use super::*; + + #[test] + fn single_download_display_title_keeps_unicode_metadata_title() { + let title = display_title_for_download( + Some("Machiot - Đổi Nhạc Nền Vol. 2".to_string()), + Some("Machiot - i Nhc Nn Vol. 2".to_string()), + &None, + None, + ); + + assert_eq!(title.as_deref(), Some("Machiot - Đổi Nhạc Nền Vol. 2")); + } + + #[test] + fn single_download_display_title_prefers_utf8_filepath_over_stdout_title() { + let filepath = Some("Machiot - Đổi Nhạc Nền Vol. 2.mp4".to_string()); + let title = display_title_for_download( + None, + Some("Machiot - i Nhc Nn Vol. 2".to_string()), + &filepath, + None, + ); + + assert_eq!(title.as_deref(), Some("Machiot - Đổi Nhạc Nền Vol. 2")); + } + + #[test] + fn playlist_display_title_keeps_current_item_title() { + let filepath = Some("Playlist name.mp4".to_string()); + let title = display_title_for_download( + Some("Playlist name".to_string()), + Some("Video item title".to_string()), + &filepath, + Some(3), + ); + + assert_eq!(title.as_deref(), Some("Video item title")); + } +} + #[cfg(test)] mod playlist_chapter_tests { use super::*; @@ -1380,8 +1442,8 @@ pub async fn download_video( ); // Only use frontend title if it's not a URL (placeholder) - let mut current_title: Option = - title.clone().filter(|t| !t.starts_with("http")); + let metadata_title: Option = title.clone().filter(|t| !t.starts_with("http")); + let mut current_title = metadata_title.clone(); let mut current_index: Option = None; let mut total_count: Option = None; let mut total_filesize: u64 = 0; @@ -1633,11 +1695,12 @@ pub async fn download_video( } }); - let display_title = current_title.clone().or_else(|| { - final_filepath - .as_ref() - .and_then(|path| title_from_filepath(path)) - }); + let display_title = display_title_for_download( + metadata_title.clone(), + current_title.clone(), + &final_filepath, + total_count, + ); let output_paths = output_filepaths(&printed_filepaths, &final_filepath); let auto_collection_names = build_auto_collection_names( @@ -1984,7 +2047,8 @@ async fn handle_tokio_download( let mut stdout_reader = BufReader::new(stdout); // Only use frontend title if it's not a URL (placeholder) - let mut current_title: Option = title.filter(|t| !t.starts_with("http")); + let metadata_title: Option = title.filter(|t| !t.starts_with("http")); + let mut current_title = metadata_title.clone(); let mut current_index: Option = None; let mut total_count: Option = None; let mut total_filesize: u64 = 0; @@ -2301,12 +2365,8 @@ async fn handle_tokio_download( } }); - // Fallback: extract title from final_filepath if current_title is None - let display_title = current_title.or_else(|| { - final_filepath - .as_ref() - .and_then(|path| title_from_filepath(path)) - }); + let display_title = + display_title_for_download(metadata_title, current_title, &final_filepath, total_count); let output_paths = output_filepaths(&printed_filepaths, &final_filepath); let auto_collection_names = build_auto_collection_names( auto_organize_collections,