diff --git a/Cargo.lock b/Cargo.lock index a23c31d8..e1e42059 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -178,6 +178,15 @@ version = "1.0.89" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "86fdf8605db99b54d3cd748a44c6d04df638eb5dafb219b135d0149bd0db01f6" +[[package]] +name = "approx" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cab112f0a86d568ea0e627cc1d6be74a1e9cd55214684db5561995f6dad897c6" +dependencies = [ + "num-traits", +] + [[package]] name = "arbitrary" version = "1.3.2" @@ -1355,6 +1364,7 @@ dependencies = [ "lofty", "log", "mime_guess", + "nom-exif", "once_cell", "os_info", "pdf", @@ -2318,6 +2328,17 @@ dependencies = [ "version_check", ] +[[package]] +name = "geo-types" +version = "0.7.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ff16065e5720f376fbced200a5ae0f47ace85fd70b7e54269790281353b6d61" +dependencies = [ + "approx", + "num-traits", + "serde", +] + [[package]] name = "gethostname" version = "0.4.3" @@ -3364,6 +3385,16 @@ version = "1.70.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" +[[package]] +name = "iso6709parse" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e8731493611ee073436908f372020038da3715865a68d024f4fa3d800f13ac7" +dependencies = [ + "geo-types", + "nom", +] + [[package]] name = "istring" version = "0.3.4" @@ -4156,6 +4187,21 @@ dependencies = [ "minimal-lexical", ] +[[package]] +name = "nom-exif" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8de573fe84056ba8233f8f541aed143d98587e7986b8da2220acf463cd181a7f" +dependencies = [ + "bytes", + "chrono", + "iso6709parse", + "nom", + "regex", + "thiserror", + "tracing", +] + [[package]] name = "noop_proc_macro" version = "0.3.0" diff --git a/czkawka_core/Cargo.toml b/czkawka_core/Cargo.toml index 76e7671f..ebb3a196 100644 --- a/czkawka_core/Cargo.toml +++ b/czkawka_core/Cargo.toml @@ -76,6 +76,7 @@ libheif-rs = { version = "=0.18.0", optional = true } # Do not upgrade now, sinc libheif-sys = { version = "=1.14.2", optional = true } # 1.14.3 brake compilation on Ubuntu 22.04, so pin it to this version anyhow = { version = "1.0.89" } +nom-exif = "2.1.0" state = "0.6" trash = "5.1" diff --git a/czkawka_core/src/common_cache.rs b/czkawka_core/src/common_cache.rs index 77b80c4c..9faa8c93 100644 --- a/czkawka_core/src/common_cache.rs +++ b/czkawka_core/src/common_cache.rs @@ -15,7 +15,7 @@ use crate::duplicate::HashType; use crate::similar_images::{convert_algorithm_to_string, convert_filters_to_string}; const CACHE_VERSION: &str = "70"; -const CACHE_IMAGE_VERSION: &str = "80"; +const CACHE_IMAGE_VERSION: &str = "90"; pub fn get_broken_files_cache_file() -> String { format!("cache_broken_files_{CACHE_VERSION}.bin") diff --git a/czkawka_core/src/common_image.rs b/czkawka_core/src/common_image.rs index 73f7be41..f192b819 100644 --- a/czkawka_core/src/common_image.rs +++ b/czkawka_core/src/common_image.rs @@ -25,6 +25,7 @@ use libheif_rs::{ColorSpace, HeifContext, RgbChroma}; #[cfg(feature = "libraw")] use libraw::Processor; use log::{debug, error, info, warn, LevelFilter, Record}; +use nom_exif::{ExifIter, ExifTag, MediaParser, MediaSource}; use rawloader::RawLoader; use symphonia::core::conv::IntoSample; @@ -87,7 +88,19 @@ pub fn get_dynamic_image_from_path(path: &str) -> Result { if let Ok(res) = res { match res { - Ok(t) => Ok(t), + Ok(t) => { + let rotation = get_rotation_from_exif(path).unwrap_or(None); + match rotation { + Some(ExifOrientation::Normal) | None => Ok(t), + Some(ExifOrientation::MirrorHorizontal) => Ok(t.fliph()), + Some(ExifOrientation::Rotate180) => Ok(t.rotate180()), + Some(ExifOrientation::MirrorVertical) => Ok(t.flipv()), + Some(ExifOrientation::MirrorHorizontalAndRotate270CW) => Ok(t.fliph().rotate270()), + Some(ExifOrientation::Rotate90CW) => Ok(t.rotate90()), + Some(ExifOrientation::MirrorHorizontalAndRotate90CW) => Ok(t.fliph().rotate90()), + Some(ExifOrientation::Rotate270CW) => Ok(t.rotate270()), + } + } Err(e) => Err(format!("Cannot open image file \"{path}\": {e}")), } } else { @@ -185,3 +198,43 @@ pub fn check_if_can_display_image(path: &str) -> bool { allowed_extensions.iter().any(|ext| extension_str.ends_with(ext)) } + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum ExifOrientation { + Normal, + MirrorHorizontal, + Rotate180, + MirrorVertical, + MirrorHorizontalAndRotate270CW, + Rotate90CW, + MirrorHorizontalAndRotate90CW, + Rotate270CW, +} + +pub fn get_rotation_from_exif(path: &str) -> Result, nom_exif::Error> { + let mut parser = MediaParser::new(); + let ms = MediaSource::file_path(path)?; + if !ms.has_exif() { + return Ok(None); + } + let exif_iter: ExifIter = parser.parse(ms)?; + for exif_entry in exif_iter { + if exif_entry.tag() == Some(ExifTag::Orientation) { + if let Some(value) = exif_entry.get_value() { + return match value.to_string().as_str() { + "1" => Ok(Some(ExifOrientation::Normal)), + "2" => Ok(Some(ExifOrientation::MirrorHorizontal)), + "3" => Ok(Some(ExifOrientation::Rotate180)), + "4" => Ok(Some(ExifOrientation::MirrorVertical)), + "5" => Ok(Some(ExifOrientation::MirrorHorizontalAndRotate270CW)), + "6" => Ok(Some(ExifOrientation::Rotate90CW)), + "7" => Ok(Some(ExifOrientation::MirrorHorizontalAndRotate90CW)), + "8" => Ok(Some(ExifOrientation::Rotate270CW)), + _ => Ok(None), + }; + } + } + } + + Ok(None) +} diff --git a/krokiet/ui/gui_state.slint b/krokiet/ui/gui_state.slint index 9c9952fb..59dc4a46 100644 --- a/krokiet/ui/gui_state.slint +++ b/krokiet/ui/gui_state.slint @@ -18,7 +18,7 @@ export global GuiState { in-out property visible_tool_settings; in-out property available_subsettings: active_tab == CurrentTab.SimilarImages || active_tab == CurrentTab.DuplicateFiles || active_tab == CurrentTab.SimilarVideos || active_tab == CurrentTab.SimilarMusic || active_tab == CurrentTab.BigFiles || active_tab == CurrentTab.BrokenFiles; - in-out property active_tab: CurrentTab.SimilarImages; + in-out property active_tab: CurrentTab.DuplicateFiles; in-out property is_tool_tab_active: active_tab != CurrentTab.Settings && active_tab != CurrentTab.About; in-out property <[SelectModel]> select_results_list: [{data: SelectMode.SelectAll, name: "Select All"}, {data: SelectMode.UnselectAll, name: "Deselect All"}, {data: SelectMode.SelectTheSmallestResolution, name: "Select the smallest resolution"}];