Skip to content

Commit

Permalink
Merge pull request #84 from picture-pro/64-exif-data
Browse files Browse the repository at this point in the history
64 exif data
  • Loading branch information
johnbchron authored Mar 10, 2024
2 parents 0d84f96 + 4c79d2a commit 6740b26
Show file tree
Hide file tree
Showing 4 changed files with 132 additions and 13 deletions.
17 changes: 17 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 4 additions & 1 deletion crates/bl/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ artifact = { path = "../artifact", optional = true }
rmp-serde = "1.1.2"

base64 = { workspace = true, optional = true }
chrono = { workspace = true, optional = true }
color-eyre = { workspace = true, optional = true }
image = { workspace = true, optional = true }
qrcode = { workspace = true, optional = true }
Expand All @@ -28,11 +29,13 @@ surrealdb = { workspace = true, optional = true }
strum.workspace = true
tokio = { workspace = true, optional = true }

kamadak-exif = { version = "0.5", optional = true }

[features]
default = []
hydrate = [ "leptos/hydrate" ]
ssr = [
"core_types/ssr", "leptos/ssr", "dep:clients", "dep:artifact", "dep:base64",
"dep:color-eyre", "dep:image", "dep:qrcode", "dep:rayon", "dep:surrealdb",
"dep:tokio", "dep:tracing",
"dep:tokio", "dep:tracing", "dep:kamadak-exif", "dep:chrono",
]
100 changes: 92 additions & 8 deletions crates/bl/src/upload/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,13 @@ pub async fn upload_photo_group(
.enumerate()
.par_bridge()
// load original images
.map(|(i, p)| image::load_from_memory(&p.original).map(|img| (i, img)))
.map(|(i, p)| {
let exif_reader = exif::Reader::new();
let meta = exif_reader
.read_from_container(&mut std::io::Cursor::new(&p.original))
.ok();
image::load_from_memory(&p.original).map(|img| (i, (img, meta)))
})
// collect into a hashmap & short circuit on error
.collect::<Result<HashMap<_, _>, _>>()
.map_err(|e| {
Expand All @@ -65,10 +71,10 @@ pub async fn upload_photo_group(
})?;

// spawn tasks for each image
for (i, img) in images {
for (i, (img, meta)) in images {
let tx = tx.clone();
tokio::spawn(async move {
let result = create_photo(img).await;
let result = create_photo(img, meta).await;
tx.send((i, result)).await.unwrap();
});
}
Expand Down Expand Up @@ -135,12 +141,89 @@ pub async fn upload_photo_group(
}

#[cfg(feature = "ssr")]
async fn create_photo(img: image::DynamicImage) -> Result<PhotoRecordId> {
fn photo_meta_from_exif(input: Option<exif::Exif>) -> 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,
meta: Option<exif::Exif>,
) -> Result<PhotoRecordId> {
use artifact::Artifact;
use color_eyre::eyre::WrapErr;

use crate::model_ext::ModelExt;

// rotate image based on exif orientation
rotate_image_from_exif(&mut img, meta.as_ref());

// encode original image as jpeg
let mut original_jpeg_bytes = Vec::new();
let encoder = image::codecs::jpeg::JpegEncoder::new(&mut original_jpeg_bytes);
Expand Down Expand Up @@ -203,9 +286,9 @@ async fn create_photo(img: image::DynamicImage) -> Result<PhotoRecordId> {

// create a photo and upload it to surreal
let photo = core_types::Photo {
id: core_types::PhotoRecordId(core_types::Ulid::new()),
group: core_types::PhotoGroupRecordId(core_types::Ulid::nil()),
artifacts: core_types::PhotoArtifacts {
id: core_types::PhotoRecordId(core_types::Ulid::new()),
group: core_types::PhotoGroupRecordId(core_types::Ulid::nil()),
artifacts: core_types::PhotoArtifacts {
original: core_types::PrivateImageArtifact {
artifact_id: original_artifact.id,
size: (img.width(), img.height()),
Expand All @@ -215,7 +298,8 @@ async fn create_photo(img: image::DynamicImage) -> Result<PhotoRecordId> {
size: (thumbnail_image.width(), thumbnail_image.height()),
},
},
meta: Default::default(),
photo_meta: photo_meta_from_exif(meta),
meta: Default::default(),
};

let client = clients::surreal::SurrealRootClient::new().await?;
Expand Down
23 changes: 19 additions & 4 deletions crates/core_types/src/photo.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use std::collections::HashMap;

use serde::{Deserialize, Serialize};

/// The table name for the photo table.
Expand All @@ -18,13 +20,26 @@ pub struct PhotoRecordId(pub ulid::Ulid);
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct Photo {
/// The record ID.
pub id: PhotoRecordId,
pub id: PhotoRecordId,
/// The photo group that contains this photo.
pub group: PhotoGroupRecordId,
pub group: PhotoGroupRecordId,
/// The photo's artifacts.
pub artifacts: PhotoArtifacts,
pub artifacts: PhotoArtifacts,
/// Data derived from the photo's EXIF data.
pub photo_meta: PhotoMeta,
/// Object metadata.
pub meta: crate::ObjectMeta,
pub meta: crate::ObjectMeta,
}

/// Photo metadata derived from EXIF data.
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
pub struct PhotoMeta {
/// The date and time the photo was taken.
pub date_time: Option<chrono::NaiveDateTime>,
/// The GPS coordinates where the photo was taken.
pub gps: Option<(f64, f64)>,
/// Extra EXIF data.
pub extra: HashMap<String, String>,
}

/// The artifacts for a photo. Not a table.
Expand Down

0 comments on commit 6740b26

Please sign in to comment.