From b5a8c357526bb6246de0ceb05e7e54aec184937d Mon Sep 17 00:00:00 2001 From: John Lewis Date: Mon, 11 Mar 2024 11:54:15 -0500 Subject: [PATCH 1/2] lint(upload): reorganized exif data into module --- crates/bl/src/upload/exif_ops.rs | 69 ++++++++++++++++++++++++++++ crates/bl/src/upload/mod.rs | 77 ++------------------------------ 2 files changed, 73 insertions(+), 73 deletions(-) create mode 100644 crates/bl/src/upload/exif_ops.rs diff --git a/crates/bl/src/upload/exif_ops.rs b/crates/bl/src/upload/exif_ops.rs new file mode 100644 index 0000000..c7e7264 --- /dev/null +++ b/crates/bl/src/upload/exif_ops.rs @@ -0,0 +1,69 @@ +use exif::{DateTime, Exif, In, Tag}; + +/// Creates a `PhotoMeta` from an optional `Exif` object. +pub fn photo_meta_from_exif(input: Option) -> core_types::PhotoMeta { + let mut meta = core_types::PhotoMeta::default(); + + let Some(exif) = input else { + return meta; + }; + + // extract datetime + if let Some(field) = exif.get_field(Tag::DateTime, In::PRIMARY) { + match field.value { + exif::Value::Ascii(ref vec) if !vec.is_empty() => { + if let Ok(datetime) = DateTime::from_ascii(&vec[0]) { + meta.date_time = chrono::NaiveDate::from_ymd_opt( + datetime.year.into(), + datetime.month.into(), + datetime.day.into(), + ) + .and_then(|date| { + chrono::NaiveTime::from_hms_opt( + datetime.hour.into(), + datetime.minute.into(), + datetime.second.into(), + ) + .map(|time| date.and_time(time)) + }); + } + } + _ => {} + } + } + + // extract gps + // this isn't implemented yet + + meta +} + +/// Extracts the orientation from an optional `Exif` object. +pub fn orientation_from_exif(input: Option<&Exif>) -> Option { + if let Some(exif) = input { + if let Some(orientation) = exif.get_field(Tag::Orientation, In::PRIMARY) { + return orientation.value.as_uint().ok().and_then(|v| v.get(0)); + } + } + None +} + +/// Rotates the given image based on the orientation from the exif data. +pub fn rotate_image_from_exif( + img: &mut image::DynamicImage, + orientation: Option<&Exif>, +) { + let Some(value) = orientation_from_exif(orientation) else { + return; + }; + *img = match value { + 2 => img.fliph(), + 3 => img.rotate180(), + 4 => img.flipv(), + 5 => img.rotate90().fliph(), + 6 => img.rotate90(), + 7 => img.rotate270().fliph(), + 8 => img.rotate270(), + _ => img.clone(), + }; +} diff --git a/crates/bl/src/upload/mod.rs b/crates/bl/src/upload/mod.rs index 013a5e0..576a8a0 100644 --- a/crates/bl/src/upload/mod.rs +++ b/crates/bl/src/upload/mod.rs @@ -1,4 +1,6 @@ #[cfg(feature = "ssr")] +mod exif_ops; +#[cfg(feature = "ssr")] mod watermark; use std::collections::HashMap; @@ -140,77 +142,6 @@ pub async fn upload_photo_group( Ok(group.id) } -#[cfg(feature = "ssr")] -fn photo_meta_from_exif(input: Option) -> core_types::PhotoMeta { - let mut meta = core_types::PhotoMeta::default(); - - let Some(exif) = input else { - return meta; - }; - - // extract datetime - if let Some(field) = exif.get_field(exif::Tag::DateTime, exif::In::PRIMARY) { - match field.value { - exif::Value::Ascii(ref vec) if !vec.is_empty() => { - if let Ok(datetime) = exif::DateTime::from_ascii(&vec[0]) { - meta.date_time = chrono::NaiveDate::from_ymd_opt( - datetime.year.into(), - datetime.month.into(), - datetime.day.into(), - ) - .and_then(|date| { - chrono::NaiveTime::from_hms_opt( - datetime.hour.into(), - datetime.minute.into(), - datetime.second.into(), - ) - .map(|time| date.and_time(time)) - }); - } - } - _ => {} - } - } - - // extract gps - // this isn't implemented yet - - meta -} - -#[cfg(feature = "ssr")] -fn rotate_image_from_exif( - img: &mut image::DynamicImage, - orientation: Option<&exif::Exif>, -) { - let Some(exif) = orientation else { - return; - }; - - let Some(orientation) = - exif.get_field(exif::Tag::Orientation, exif::In::PRIMARY) - else { - return; - }; - - let Some(value) = orientation.value.as_uint().ok().and_then(|v| v.get(0)) - else { - return; - }; - - *img = match value { - 1 => img.clone(), - 2 => img.fliph(), - 3 => img.rotate180(), - 4 => img.flipv(), - 5 => img.rotate90().fliph(), - 6 => img.rotate90(), - 7 => img.rotate270().fliph(), - 8 => img.rotate270(), - _ => img.clone(), - }; -} - #[cfg(feature = "ssr")] async fn create_photo( mut img: image::DynamicImage, @@ -222,7 +153,7 @@ async fn create_photo( use crate::model_ext::ModelExt; // rotate image based on exif orientation - rotate_image_from_exif(&mut img, meta.as_ref()); + exif_ops::rotate_image_from_exif(&mut img, meta.as_ref()); // encode original image as jpeg let mut original_jpeg_bytes = Vec::new(); @@ -298,7 +229,7 @@ async fn create_photo( size: (thumbnail_image.width(), thumbnail_image.height()), }, }, - photo_meta: photo_meta_from_exif(meta), + photo_meta: exif_ops::photo_meta_from_exif(meta), meta: Default::default(), }; From 45a3fb2adeb5084e593edb9aef476573ddf72b84 Mon Sep 17 00:00:00 2001 From: John Lewis Date: Mon, 11 Mar 2024 11:56:02 -0500 Subject: [PATCH 2/2] feat: add exif orientation to `PhotoMeta` --- crates/bl/src/upload/exif_ops.rs | 3 +++ crates/core_types/src/photo.rs | 8 +++++--- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/crates/bl/src/upload/exif_ops.rs b/crates/bl/src/upload/exif_ops.rs index c7e7264..7fc6836 100644 --- a/crates/bl/src/upload/exif_ops.rs +++ b/crates/bl/src/upload/exif_ops.rs @@ -32,6 +32,9 @@ pub fn photo_meta_from_exif(input: Option) -> core_types::PhotoMeta { } } + // extract orientation + meta.orientation = orientation_from_exif(Some(&exif)); + // extract gps // this isn't implemented yet diff --git a/crates/core_types/src/photo.rs b/crates/core_types/src/photo.rs index 17ba2e0..78dca92 100644 --- a/crates/core_types/src/photo.rs +++ b/crates/core_types/src/photo.rs @@ -35,11 +35,13 @@ pub struct Photo { #[derive(Clone, Debug, Default, Deserialize, Serialize)] pub struct PhotoMeta { /// The date and time the photo was taken. - pub date_time: Option, + pub date_time: Option, /// The GPS coordinates where the photo was taken. - pub gps: Option<(f64, f64)>, + pub gps: Option<(f64, f64)>, + /// The original orientation of the photo (uses EXIF orientations). + pub orientation: Option, /// Extra EXIF data. - pub extra: HashMap, + pub extra: HashMap, } /// The artifacts for a photo. Not a table.