diff --git a/crates/gen-wallpapers/src/lib.rs b/crates/gen-wallpapers/src/lib.rs index 8446cd8..f94e470 100644 --- a/crates/gen-wallpapers/src/lib.rs +++ b/crates/gen-wallpapers/src/lib.rs @@ -1,79 +1,27 @@ +mod location; + +use location::Gps; +use location::Location; +use location::parse_coordinates; + use exif::{DateTime, Exif, In, Tag}; use serde::Serialize; use std::fs::ReadDir; use std::io::BufReader; -fn parse_coordinates(exif: &Exif, tag: Tag, r#ref: Tag) -> Option { - let mut coord = None; - let mut coord_ref = None; - // Parse DMS coordinates - if let Some(field) = exif.get_field(tag, In::PRIMARY) { - match field.value { - exif::Value::Rational(ref vec) if !vec.is_empty() => { - let deg = vec[0].to_f64() as u16; - let min = vec[1].to_f64() as u8; - let sec = vec[2].to_f64(); - - coord = Some((deg, min, sec)); - } - _ => {} - } - } - - // Get bearing - if let Some(field) = exif.get_field(r#ref, In::PRIMARY) { - coord_ref = Some(field.display_value().to_string()); - } - - match (coord, coord_ref) { - (Some((deg, min, sec)), Some(r#ref)) => { - use dms_coordinates::Cardinal; - use dms_coordinates::Cardinal::*; - let bearing: Option = match r#ref.as_str() { - "N" => Some(North), - "NE" => Some(NorthEast), - "NW" => Some(NorthWest), - "S" => Some(South), - "SE" => Some(SouthEast), - "SW" => Some(SouthWest), - "E" => Some(East), - "W" => Some(West), - _ => None, - }; - - let dms = dms_coordinates::DMS::new(deg, min, sec, bearing); - - Some(dms.to_ddeg_angle() as f32) - } - (_, _) => None, - } +#[derive(Debug, Serialize)] +pub struct Metadata { + pub filename: String, + pub date: String, + pub width: u32, + pub height: u32, + pub gps: Gps, + pub location: Option, } #[derive(Debug, Serialize)] -struct Metadata { - filename: String, - date: String, - width: u32, - height: u32, - gps: Gps, - location: Option, -} - -#[derive(Debug, Serialize)] -pub struct Location { - precise: String, - broad: String, -} - -#[derive(Debug, Serialize)] -pub struct Gps { - latitude: f32, - longitude: f32, -} - -#[derive(Debug, Serialize)] -pub struct MetadataList(Vec); +pub struct MetadataList(pub Vec); impl MetadataList { pub fn process_folder(dir: ReadDir, get_location: bool) -> Self { @@ -105,7 +53,7 @@ impl MetadataList { // Get date let mut date = None; - if let Some(field) = exif.get_field(Tag::DateTime, In::PRIMARY) { + if let Some(field) = exif.get_field(Tag::DateTimeOriginal, In::PRIMARY) { match field.value { exif::Value::Ascii(ref vec) if !vec.is_empty() => { if let Ok(datetime) = DateTime::from_ascii(&vec[0]) { @@ -118,6 +66,8 @@ impl MetadataList { _ => {} } } + // TODO: get OffsetTimeOriginal as well + // TODO: use crate `jiff` to store dates with timezone offsets // Get image width let mut width = None; @@ -137,10 +87,7 @@ impl MetadataList { match (date, latitude, longitude, width, height) { (Some(date), Some(latitude), Some(longitude), Some(width), Some(height)) => { - let gps = Gps { - latitude, - longitude, - }; + let gps = Gps::new(latitude,longitude); let location = if get_location { eprintln!("Getting location for `{}`", filename); gps.get_location() @@ -151,7 +98,7 @@ impl MetadataList { filename, width, height, - date, + date: date.to_string(), gps, location, }); @@ -169,54 +116,3 @@ impl MetadataList { serde_json::to_string_pretty(&self.0) } } - -impl Gps { - fn get_location(&self) -> Option { - match reqwest::blocking::Client::new() - .get(format!( - // Documentation: https://nominatim.org/release-docs/develop/api/Reverse/ - "https://nominatim.openstreetmap.org/reverse?format=jsonv2&lat={}&lon={}&zoom=8", - self.latitude, self.longitude, - )) - .header("User-Agent", "https://philippeloctaux.com") - .send() - .and_then(|data| data.json::()) - { - Ok(data) => { - let location = &data["display_name"]; - - if location.is_string() { - let location = location.to_string(); - // Remove first and last characters (the string is wrapped in double quotes '"') - let location = { - let mut chars = location.chars(); - chars.next(); - chars.next_back(); - chars.as_str() - }; - eprintln!("Raw location is `{}`", location); - - let mut location = location.split(','); - let precise = location.next().unwrap_or("?").to_string(); - - let mut broad: String = - location.collect::>().join(",").trim().to_string(); - if broad.is_empty() { - broad.push('?'); - } - - let location = Location { precise, broad }; - eprintln!("Location is `{:?}`", location); - Some(location) - } else { - eprintln!("Failed to find location."); - None - } - } - Err(_) => { - eprintln!("Failed to make API call to get location."); - None - } - } - } -} diff --git a/crates/gen-wallpapers/src/location.rs b/crates/gen-wallpapers/src/location.rs new file mode 100644 index 0000000..c556cf9 --- /dev/null +++ b/crates/gen-wallpapers/src/location.rs @@ -0,0 +1,116 @@ +use exif::{Exif, In, Tag}; +use serde::Serialize; + +#[derive(Debug, Serialize)] +pub struct Location { + precise: String, + broad: String, +} + +#[derive(Debug, Serialize)] +pub struct Gps { + latitude: f32, + longitude: f32, +} + +impl Gps { + pub fn new(latitude: f32, longitude: f32) -> Self { + Self { latitude, longitude } + } + + pub fn get_location(&self) -> Option { + match reqwest::blocking::Client::new() + .get(format!( + // Documentation: https://nominatim.org/release-docs/develop/api/Reverse/ + "https://nominatim.openstreetmap.org/reverse?format=jsonv2&lat={}&lon={}&zoom=8", + self.latitude, self.longitude, + )) + .header("User-Agent", "https://philippeloctaux.com") + .send() + .and_then(|data| data.json::()) + { + Ok(data) => { + let location = &data["display_name"]; + + if location.is_string() { + let location = location.to_string(); + // Remove first and last characters (the string is wrapped in double quotes '"') + let location = { + let mut chars = location.chars(); + chars.next(); + chars.next_back(); + chars.as_str() + }; + eprintln!("Raw location is `{}`", location); + + let mut location = location.split(','); + let precise = location.next().unwrap_or("?").to_string(); + + let mut broad: String = + location.collect::>().join(",").trim().to_string(); + if broad.is_empty() { + broad.push('?'); + } + + let location = Location { precise, broad }; + eprintln!("Location is `{:?}`", location); + Some(location) + } else { + eprintln!("Failed to find location."); + None + } + } + Err(_) => { + eprintln!("Failed to make API call to get location."); + None + } + } + } +} + +pub fn parse_coordinates(exif: &Exif, tag: Tag, r#ref: Tag) -> Option { + let mut coord = None; + let mut coord_ref = None; + + // Parse DMS coordinates + if let Some(field) = exif.get_field(tag, In::PRIMARY) { + match field.value { + exif::Value::Rational(ref vec) if !vec.is_empty() => { + let deg = vec[0].to_f64() as u16; + let min = vec[1].to_f64() as u8; + let sec = vec[2].to_f64(); + + coord = Some((deg, min, sec)); + } + _ => {} + } + } + + // Get bearing + if let Some(field) = exif.get_field(r#ref, In::PRIMARY) { + coord_ref = Some(field.display_value().to_string()); + } + + match (coord, coord_ref) { + (Some((deg, min, sec)), Some(r#ref)) => { + use dms_coordinates::Cardinal; + use dms_coordinates::Cardinal::*; + let bearing: Option = match r#ref.as_str() { + "N" => Some(North), + "NE" => Some(NorthEast), + "NW" => Some(NorthWest), + "S" => Some(South), + "SE" => Some(SouthEast), + "SW" => Some(SouthWest), + "E" => Some(East), + "W" => Some(West), + _ => None, + }; + + let dms = dms_coordinates::DMS::new(deg, min, sec, bearing); + + Some(dms.to_ddeg_angle() as f32) + } + (_, _) => None, + } +}