From 74bcb1dcd0885eaded33adf664174ad0ff38171c Mon Sep 17 00:00:00 2001
From: Philippe Loctaux
Date: Sun, 3 Nov 2024 16:47:03 +0100
Subject: [PATCH] gen-wallpapers: use DateTimeOriginal exif tag, module for
location, preparation for the future
---
crates/gen-wallpapers/src/lib.rs | 144 ++++----------------------
crates/gen-wallpapers/src/location.rs | 116 +++++++++++++++++++++
2 files changed, 136 insertions(+), 124 deletions(-)
create mode 100644 crates/gen-wallpapers/src/location.rs
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,
+ }
+}