diff --git a/czkawka_core/src/same_music.rs b/czkawka_core/src/same_music.rs index 305c4c8..44e20f5 100644 --- a/czkawka_core/src/same_music.rs +++ b/czkawka_core/src/same_music.rs @@ -1,3 +1,4 @@ +use std::cmp::max; use std::collections::{BTreeMap, HashMap, HashSet}; use std::fs::File; use std::io::prelude::*; @@ -191,6 +192,10 @@ impl SameMusic { self.stopped_search = true; return; } + if !self.read_tags_to_files_similar_by_content(stop_receiver, progress_sender) { + self.stopped_search = true; + return; + } } _ => panic!(), } @@ -393,7 +398,7 @@ impl SameMusic { let (loaded_hash_map, records_already_cached, non_cached_files_to_check) = self.load_cache(false); let (progress_thread_handle, progress_thread_run, atomic_counter, check_was_stopped) = - prepare_thread_handler_common(progress_sender, 1, 2, non_cached_files_to_check.len(), self.check_type); + prepare_thread_handler_common(progress_sender, 1, 3, non_cached_files_to_check.len(), self.check_type); let configuration = &self.hash_preset_config; // Clean for duplicate files @@ -450,7 +455,7 @@ impl SameMusic { check_was_stopped.store(true, Ordering::Relaxed); return None; } - if self.read_single_file_tag(&path, &mut music_entry) { + if read_single_file_tag(&path, &mut music_entry) { Some(Some(music_entry)) } else { Some(None) @@ -477,102 +482,8 @@ impl SameMusic { true } - fn read_single_file_tag(&self, path: &str, music_entry: &mut MusicEntry) -> bool { - let Ok(mut file) = File::open(path) else { return false; }; - - let result = panic::catch_unwind(move || { - match read_from(&mut file) { - Ok(t) => Some(t), - Err(_inspected) => { - // println!("Failed to open {}", path); - None - } - } - }); - - let tagged_file = if let Ok(t) = result { - match t { - Some(r) => r, - None => { - return true; - } - } - } else { - let message = create_crash_message("Lofty", path, "https://github.com/image-rs/image/issues"); - println!("{message}"); - return false; - }; - - let properties = tagged_file.properties(); - - let mut track_title = String::new(); - let mut track_artist = String::new(); - let mut year = String::new(); - let mut genre = String::new(); - - let bitrate = properties.audio_bitrate().unwrap_or(0); - let mut length = properties.duration().as_millis().to_string(); - - if let Some(tag) = tagged_file.primary_tag() { - track_title = tag.get_string(&ItemKey::TrackTitle).unwrap_or("").to_string(); - track_artist = tag.get_string(&ItemKey::TrackArtist).unwrap_or("").to_string(); - year = tag.get_string(&ItemKey::Year).unwrap_or("").to_string(); - genre = tag.get_string(&ItemKey::Genre).unwrap_or("").to_string(); - } - - for tag in tagged_file.tags() { - if track_title.is_empty() { - if let Some(tag_value) = tag.get_string(&ItemKey::TrackTitle) { - track_title = tag_value.to_string(); - } - } - if track_artist.is_empty() { - if let Some(tag_value) = tag.get_string(&ItemKey::TrackArtist) { - track_artist = tag_value.to_string(); - } - } - if year.is_empty() { - if let Some(tag_value) = tag.get_string(&ItemKey::Year) { - year = tag_value.to_string(); - } - } - if genre.is_empty() { - if let Some(tag_value) = tag.get_string(&ItemKey::Genre) { - genre = tag_value.to_string(); - } - } - // println!("{:?}", tag.items()); - } - - if let Ok(old_length_number) = length.parse::() { - let length_number = old_length_number / 60; - let minutes = length_number / 1000; - let seconds = (length_number % 1000) * 6 / 100; - if minutes != 0 || seconds != 0 { - length = format!("{minutes}:{seconds:02}"); - } else if old_length_number > 0 { - // That means, that audio have length smaller that second, but length is properly read - length = "0:01".to_string(); - } else { - length = String::new(); - } - } else { - length = String::new(); - } - - music_entry.track_title = track_title; - music_entry.track_artist = track_artist; - music_entry.year = year; - music_entry.length = length; - music_entry.genre = genre; - music_entry.bitrate = bitrate; - - true - } fn check_for_duplicate_tags(&mut self, stop_receiver: Option<&Receiver<()>>, progress_sender: Option<&UnboundedSender>) -> bool { - assert_ne!(MusicSimilarity::NONE, self.music_similarity, "This can't be none"); - let (progress_thread_handle, progress_thread_run, atomic_counter, _check_was_stopped) = prepare_thread_handler_common(progress_sender, 2, 2, self.music_to_check.len(), self.check_type); @@ -670,6 +581,56 @@ impl SameMusic { true } + fn read_tags_to_files_similar_by_content(&mut self, stop_receiver: Option<&Receiver<()>>, progress_sender: Option<&UnboundedSender>) -> bool { + let groups_to_check = max(self.duplicated_music_entries.len(), self.duplicated_music_entries_referenced.len()); + let (progress_thread_handle, progress_thread_run, atomic_counter, check_was_stopped) = + prepare_thread_handler_common(progress_sender, 3, 3, groups_to_check, self.check_type); + + // TODO is ther a way to just run iterator and not collect any info? + if !self.duplicated_music_entries.is_empty() { + let _: Vec<_> = self + .duplicated_music_entries + .par_iter_mut() + .map(|vec_me| { + atomic_counter.fetch_add(1, Ordering::Relaxed); + if stop_receiver.is_some() && stop_receiver.unwrap().try_recv().is_ok() { + check_was_stopped.store(true, Ordering::Relaxed); + return None; + } + for me in vec_me { + let me_path = me.path.to_string_lossy().to_string(); + read_single_file_tag(&me_path, me); + } + Some(()) + }) + .while_some() + .collect(); + } else { + let _: Vec<_> = self + .duplicated_music_entries_referenced + .par_iter_mut() + .map(|(me_o, vec_me)| { + atomic_counter.fetch_add(1, Ordering::Relaxed); + if stop_receiver.is_some() && stop_receiver.unwrap().try_recv().is_ok() { + check_was_stopped.store(true, Ordering::Relaxed); + return None; + } + let me_o_path = me_o.path.to_string_lossy().to_string(); + read_single_file_tag(&me_o_path, me_o); + for me in vec_me { + let me_path = me.path.to_string_lossy().to_string(); + read_single_file_tag(&me_path, me); + } + Some(()) + }) + .while_some() + .collect(); + } + + send_info_and_wait_for_ending_all_threads(&progress_thread_run, progress_thread_handle); + + !check_was_stopped.load(Ordering::Relaxed) + } fn split_fingerprints_to_check(&mut self) -> (Vec, Vec) { let base_files: Vec; @@ -746,11 +707,9 @@ impl SameMusic { } fn check_for_duplicate_fingerprints(&mut self, stop_receiver: Option<&Receiver<()>>, progress_sender: Option<&UnboundedSender>) -> bool { - assert_ne!(MusicSimilarity::NONE, self.music_similarity, "This can't be none"); - let (base_files, files_to_compare) = self.split_fingerprints_to_check(); let (progress_thread_handle, progress_thread_run, atomic_counter, _check_was_stopped) = - prepare_thread_handler_common(progress_sender, 2, 2, base_files.len(), self.check_type); + prepare_thread_handler_common(progress_sender, 2, 3, base_files.len(), self.check_type); let Some(duplicated_music_entries) = self.compare_fingerprints(stop_receiver, &atomic_counter, base_files, &files_to_compare) else { send_info_and_wait_for_ending_all_threads(&progress_thread_run, progress_thread_handle); @@ -974,6 +933,99 @@ fn calc_fingerprint_helper(path: impl AsRef, config: &Configuration) -> an Ok(printer.fingerprint().to_vec()) } +fn read_single_file_tag(path: &str, music_entry: &mut MusicEntry) -> bool { + let Ok(mut file) = File::open(path) else { return false; }; + + let result = panic::catch_unwind(move || { + match read_from(&mut file) { + Ok(t) => Some(t), + Err(_inspected) => { + // println!("Failed to open {}", path); + None + } + } + }); + + let tagged_file = if let Ok(t) = result { + match t { + Some(r) => r, + None => { + return true; + } + } + } else { + let message = create_crash_message("Lofty", path, "https://github.com/image-rs/image/issues"); + println!("{message}"); + return false; + }; + + let properties = tagged_file.properties(); + + let mut track_title = String::new(); + let mut track_artist = String::new(); + let mut year = String::new(); + let mut genre = String::new(); + + let bitrate = properties.audio_bitrate().unwrap_or(0); + let mut length = properties.duration().as_millis().to_string(); + + if let Some(tag) = tagged_file.primary_tag() { + track_title = tag.get_string(&ItemKey::TrackTitle).unwrap_or("").to_string(); + track_artist = tag.get_string(&ItemKey::TrackArtist).unwrap_or("").to_string(); + year = tag.get_string(&ItemKey::Year).unwrap_or("").to_string(); + genre = tag.get_string(&ItemKey::Genre).unwrap_or("").to_string(); + } + + for tag in tagged_file.tags() { + if track_title.is_empty() { + if let Some(tag_value) = tag.get_string(&ItemKey::TrackTitle) { + track_title = tag_value.to_string(); + } + } + if track_artist.is_empty() { + if let Some(tag_value) = tag.get_string(&ItemKey::TrackArtist) { + track_artist = tag_value.to_string(); + } + } + if year.is_empty() { + if let Some(tag_value) = tag.get_string(&ItemKey::Year) { + year = tag_value.to_string(); + } + } + if genre.is_empty() { + if let Some(tag_value) = tag.get_string(&ItemKey::Genre) { + genre = tag_value.to_string(); + } + } + // println!("{:?}", tag.items()); + } + + if let Ok(old_length_number) = length.parse::() { + let length_number = old_length_number / 60; + let minutes = length_number / 1000; + let seconds = (length_number % 1000) * 6 / 100; + if minutes != 0 || seconds != 0 { + length = format!("{minutes}:{seconds:02}"); + } else if old_length_number > 0 { + // That means, that audio have length smaller that second, but length is properly read + length = "0:01".to_string(); + } else { + length = String::new(); + } + } else { + length = String::new(); + } + + music_entry.track_title = track_title; + music_entry.track_artist = track_artist; + music_entry.year = year; + music_entry.length = length; + music_entry.genre = genre; + music_entry.bitrate = bitrate; + + true +} + // Using different cache folders, because loading cache just for finding duplicated tags would be really slow fn get_cache_file(checking_tags: bool) -> &'static str { if checking_tags { diff --git a/czkawka_gui/src/connect_things/connect_progress_window.rs b/czkawka_gui/src/connect_things/connect_progress_window.rs index ea35d06..b4111eb 100644 --- a/czkawka_gui/src/connect_things/connect_progress_window.rs +++ b/czkawka_gui/src/connect_things/connect_progress_window.rs @@ -1,4 +1,5 @@ use std::cell::RefCell; +use std::collections::HashMap; use std::rc::Rc; use futures::channel::mpsc::UnboundedReceiver; @@ -52,10 +53,7 @@ fn process_bar_empty_files(gui_data: &GuiData, main_context: &MainContext, mut f let taskbar_state = gui_data.taskbar_state.clone(); let future = async move { while let Some(item) = futures_receiver_empty_files.next().await { - label_stage.set_text(&flg!( - "progress_scanning_general_file", - generate_translation_hashmap(vec![("file_number", item.entries_checked.to_string())]) - )); + label_stage.set_text(&flg!("progress_scanning_general_file", file_number_tm(&item))); taskbar_state.borrow().set_progress_state(TBPF_INDETERMINATE); } }; @@ -80,10 +78,7 @@ fn process_bar_big_files(gui_data: &GuiData, main_context: &MainContext, mut fut let taskbar_state = gui_data.taskbar_state.clone(); let future = async move { while let Some(item) = futures_receiver_big_files.next().await { - label_stage.set_text(&flg!( - "progress_scanning_general_file", - generate_translation_hashmap(vec![("file_number", item.entries_checked.to_string())]) - )); + label_stage.set_text(&flg!("progress_scanning_general_file", file_number_tm(&item))); taskbar_state.borrow().set_progress_state(TBPF_INDETERMINATE); } }; @@ -99,31 +94,33 @@ fn process_bar_same_music(gui_data: &GuiData, main_context: &MainContext, mut fu match item.current_stage { 0 => { progress_bar_current_stage.hide(); - label_stage.set_text(&flg!( - "progress_scanning_general_file", - generate_translation_hashmap(vec![("file_number", item.entries_checked.to_string())]) - )); + label_stage.set_text(&flg!("progress_scanning_general_file", file_number_tm(&item))); taskbar_state.borrow().set_progress_state(TBPF_INDETERMINATE); } 1 => { progress_bar_current_stage.show(); common_set_data(&item, &progress_bar_all_stages, &progress_bar_current_stage, &taskbar_state); - let translation_map = generate_translation_hashmap(vec![("file_checked", item.entries_checked.to_string()), ("all_files", item.entries_to_check.to_string())]); match item.checking_method { - CheckingMethod::AudioTags => label_stage.set_text(&flg!("progress_scanning_music_tags", translation_map)), - CheckingMethod::AudioContent => label_stage.set_text(&flg!("progress_scanning_music_content", translation_map)), + CheckingMethod::AudioTags => label_stage.set_text(&flg!("progress_scanning_music_tags", progress_ratio_tm(&item))), + CheckingMethod::AudioContent => label_stage.set_text(&flg!("progress_scanning_music_content", progress_ratio_tm(&item))), _ => panic!(), } } 2 => { common_set_data(&item, &progress_bar_all_stages, &progress_bar_current_stage, &taskbar_state); - let translation_map = generate_translation_hashmap(vec![("file_checked", item.entries_checked.to_string()), ("all_files", item.entries_to_check.to_string())]); + match item.checking_method { + CheckingMethod::AudioTags => label_stage.set_text(&flg!("progress_scanning_music_tags_end", progress_ratio_tm(&item))), + CheckingMethod::AudioContent => label_stage.set_text(&flg!("progress_scanning_music_content_end", progress_ratio_tm(&item))), + _ => panic!(), + } + } + 3 => { + common_set_data(&item, &progress_bar_all_stages, &progress_bar_current_stage, &taskbar_state); match item.checking_method { - CheckingMethod::AudioTags => label_stage.set_text(&flg!("progress_scanning_music_tags_end", translation_map)), - CheckingMethod::AudioContent => label_stage.set_text(&flg!("progress_scanning_music_content_end", translation_map)), + CheckingMethod::AudioContent => label_stage.set_text(&flg!("progress_scanning_music_tags", progress_ratio_tm(&item))), _ => panic!(), } } @@ -143,27 +140,18 @@ fn process_bar_similar_images(gui_data: &GuiData, main_context: &MainContext, mu match item.current_stage { 0 => { progress_bar_current_stage.hide(); - label_stage.set_text(&flg!( - "progress_scanning_general_file", - generate_translation_hashmap(vec![("file_number", item.entries_checked.to_string())]) - )); + label_stage.set_text(&flg!("progress_scanning_general_file", file_number_tm(&item))); taskbar_state.borrow().set_progress_state(TBPF_INDETERMINATE); } 1 => { progress_bar_current_stage.show(); common_set_data(&item, &progress_bar_all_stages, &progress_bar_current_stage, &taskbar_state); - label_stage.set_text(&flg!( - "progress_scanning_image", - generate_translation_hashmap(vec![("file_checked", item.entries_checked.to_string()), ("all_files", item.entries_to_check.to_string())]) - )); + label_stage.set_text(&flg!("progress_scanning_image", progress_ratio_tm(&item))); } 2 => { progress_bar_current_stage.show(); common_set_data(&item, &progress_bar_all_stages, &progress_bar_current_stage, &taskbar_state); - label_stage.set_text(&flg!( - "progress_comparing_image_hashes", - generate_translation_hashmap(vec![("file_checked", item.entries_checked.to_string()), ("all_files", item.entries_to_check.to_string())]) - )); + label_stage.set_text(&flg!("progress_comparing_image_hashes", progress_ratio_tm(&item))); } _ => panic!(), } @@ -181,19 +169,13 @@ fn process_bar_similar_videos(gui_data: &GuiData, main_context: &MainContext, mu match item.current_stage { 0 => { progress_bar_current_stage.hide(); - label_stage.set_text(&flg!( - "progress_scanning_general_file", - generate_translation_hashmap(vec![("file_number", item.entries_checked.to_string())]) - )); + label_stage.set_text(&flg!("progress_scanning_general_file", file_number_tm(&item))); taskbar_state.borrow().set_progress_state(TBPF_INDETERMINATE); } 1 => { progress_bar_current_stage.show(); common_set_data(&item, &progress_bar_all_stages, &progress_bar_current_stage, &taskbar_state); - label_stage.set_text(&flg!( - "progress_scanning_video", - generate_translation_hashmap(vec![("file_checked", item.entries_checked.to_string()), ("all_files", item.entries_to_check.to_string())]) - )); + label_stage.set_text(&flg!("progress_scanning_video", progress_ratio_tm(&item))); } _ => panic!(), } @@ -206,10 +188,7 @@ fn process_bar_temporary(gui_data: &GuiData, main_context: &MainContext, mut fut let taskbar_state = gui_data.taskbar_state.clone(); let future = async move { while let Some(item) = futures_receiver_temporary.next().await { - label_stage.set_text(&flg!( - "progress_scanning_general_file", - generate_translation_hashmap(vec![("file_number", item.entries_checked.to_string())]) - )); + label_stage.set_text(&flg!("progress_scanning_general_file", file_number_tm(&item))); taskbar_state.borrow().set_progress_state(TBPF_INDETERMINATE); } }; @@ -220,10 +199,7 @@ fn process_bar_invalid_symlinks(gui_data: &GuiData, main_context: &MainContext, let taskbar_state = gui_data.taskbar_state.clone(); let future = async move { while let Some(item) = futures_receiver_invalid_symlinks.next().await { - label_stage.set_text(&flg!( - "progress_scanning_general_file", - generate_translation_hashmap(vec![("file_number", item.entries_checked.to_string())]) - )); + label_stage.set_text(&flg!("progress_scanning_general_file", file_number_tm(&item))); taskbar_state.borrow().set_progress_state(TBPF_INDETERMINATE); } }; @@ -239,19 +215,13 @@ fn process_bar_broken_files(gui_data: &GuiData, main_context: &MainContext, mut match item.current_stage { 0 => { progress_bar_current_stage.hide(); - label_stage.set_text(&flg!( - "progress_scanning_general_file", - generate_translation_hashmap(vec![("file_number", item.entries_checked.to_string())]) - )); + label_stage.set_text(&flg!("progress_scanning_general_file", file_number_tm(&item))); taskbar_state.borrow().set_progress_state(TBPF_INDETERMINATE); } 1 => { progress_bar_current_stage.show(); common_set_data(&item, &progress_bar_all_stages, &progress_bar_current_stage, &taskbar_state); - label_stage.set_text(&flg!( - "progress_scanning_broken_files", - generate_translation_hashmap(vec![("file_checked", item.entries_checked.to_string()), ("all_files", item.entries_to_check.to_string())]) - )); + label_stage.set_text(&flg!("progress_scanning_broken_files", progress_ratio_tm(&item))); } _ => panic!(), } @@ -269,19 +239,13 @@ fn process_bar_bad_extensions(gui_data: &GuiData, main_context: &MainContext, mu match item.current_stage { 0 => { progress_bar_current_stage.hide(); - label_stage.set_text(&flg!( - "progress_scanning_general_file", - generate_translation_hashmap(vec![("file_number", item.entries_checked.to_string())]) - )); + label_stage.set_text(&flg!("progress_scanning_general_file", file_number_tm(&item))); taskbar_state.borrow().set_progress_state(TBPF_INDETERMINATE); } 1 => { progress_bar_current_stage.show(); common_set_data(&item, &progress_bar_all_stages, &progress_bar_current_stage, &taskbar_state); - label_stage.set_text(&flg!( - "progress_scanning_extension_of_files", - generate_translation_hashmap(vec![("file_checked", item.entries_checked.to_string()), ("all_files", item.entries_to_check.to_string())]) - )); + label_stage.set_text(&flg!("progress_scanning_extension_of_files", progress_ratio_tm(&item))); } _ => panic!(), } @@ -306,10 +270,7 @@ fn process_bar_duplicates(gui_data: &GuiData, main_context: &MainContext, mut fu progress_bar_current_stage.hide(); // progress_bar_all_stages.hide(); progress_bar_all_stages.set_fraction(0 as f64); - label_stage.set_text(&flg!( - "progress_scanning_size", - generate_translation_hashmap(vec![("file_number", item.entries_checked.to_string())]) - )); + label_stage.set_text(&flg!("progress_scanning_size", file_number_tm(&item))); taskbar_state.borrow().set_progress_state(TBPF_INDETERMINATE); } // Hash - first 1KB file @@ -318,18 +279,12 @@ fn process_bar_duplicates(gui_data: &GuiData, main_context: &MainContext, mut fu // progress_bar_all_stages.show(); common_set_data(&item, &progress_bar_all_stages, &progress_bar_current_stage, &taskbar_state); - label_stage.set_text(&flg!( - "progress_analyzed_partial_hash", - generate_translation_hashmap(vec![("file_checked", item.entries_checked.to_string()), ("all_files", item.entries_to_check.to_string())]) - )); + label_stage.set_text(&flg!("progress_analyzed_partial_hash", progress_ratio_tm(&item))); } // Hash - normal hash 2 => { common_set_data(&item, &progress_bar_all_stages, &progress_bar_current_stage, &taskbar_state); - label_stage.set_text(&flg!( - "progress_analyzed_full_hash", - generate_translation_hashmap(vec![("file_checked", item.entries_checked.to_string()), ("all_files", item.entries_to_check.to_string())]) - )); + label_stage.set_text(&flg!("progress_analyzed_full_hash", progress_ratio_tm(&item))); } _ => { panic!("Not available current_stage"); @@ -340,30 +295,21 @@ fn process_bar_duplicates(gui_data: &GuiData, main_context: &MainContext, mut fu label_stage.show(); grid_progress_stages.hide(); - label_stage.set_text(&flg!( - "progress_scanning_name", - generate_translation_hashmap(vec![("file_number", item.entries_checked.to_string())]) - )); + label_stage.set_text(&flg!("progress_scanning_name", file_number_tm(&item))); taskbar_state.borrow().set_progress_state(TBPF_INDETERMINATE); } CheckingMethod::SizeName => { label_stage.show(); grid_progress_stages.hide(); - label_stage.set_text(&flg!( - "progress_scanning_size_name", - generate_translation_hashmap(vec![("file_number", item.entries_checked.to_string())]) - )); + label_stage.set_text(&flg!("progress_scanning_size_name", file_number_tm(&item))); taskbar_state.borrow().set_progress_state(TBPF_INDETERMINATE); } CheckingMethod::Size => { label_stage.show(); grid_progress_stages.hide(); - label_stage.set_text(&flg!( - "progress_scanning_size", - generate_translation_hashmap(vec![("file_number", item.entries_checked.to_string())]) - )); + label_stage.set_text(&flg!("progress_scanning_size", file_number_tm(&item))); taskbar_state.borrow().set_progress_state(TBPF_INDETERMINATE); } _ => panic!(), @@ -387,3 +333,10 @@ fn common_set_data(item: &ProgressData, progress_bar_all_stages: &ProgressBar, p taskbar_state.borrow().set_progress_value(item.current_stage as u64, 1 + item.max_stage as u64); } } + +fn file_number_tm(item: &ProgressData) -> HashMap<&'static str, String> { + generate_translation_hashmap(vec![("file_number", item.entries_checked.to_string())]) +} +fn progress_ratio_tm(item: &ProgressData) -> HashMap<&'static str, String> { + generate_translation_hashmap(vec![("file_checked", item.entries_checked.to_string()), ("all_files", item.entries_to_check.to_string())]) +}