1
0
Fork 0
mirror of synced 2024-05-17 19:03:08 +12:00

Reading tags

This commit is contained in:
Rafał Mikrut 2023-05-07 10:33:49 +02:00
parent d593b225d6
commit 466970eb41
2 changed files with 190 additions and 185 deletions

View file

@ -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::<u32>() {
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<ProgressData>>) -> 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<ProgressData>>) -> 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<MusicEntry>, Vec<MusicEntry>) {
let base_files: Vec<MusicEntry>;
@ -746,11 +707,9 @@ impl SameMusic {
}
fn check_for_duplicate_fingerprints(&mut self, stop_receiver: Option<&Receiver<()>>, progress_sender: Option<&UnboundedSender<ProgressData>>) -> 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<Path>, 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::<u32>() {
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 {

View file

@ -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())])
}