gen-wallpapers: use DateTimeOriginal exif tag, module for location, preparation for the future
This commit is contained in:
parent
a770e3e209
commit
74bcb1dcd0
2 changed files with 136 additions and 124 deletions
|
|
@ -1,79 +1,27 @@
|
||||||
|
mod location;
|
||||||
|
|
||||||
|
use location::Gps;
|
||||||
|
use location::Location;
|
||||||
|
use location::parse_coordinates;
|
||||||
|
|
||||||
use exif::{DateTime, Exif, In, Tag};
|
use exif::{DateTime, Exif, In, Tag};
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use std::fs::ReadDir;
|
use std::fs::ReadDir;
|
||||||
use std::io::BufReader;
|
use std::io::BufReader;
|
||||||
|
|
||||||
fn parse_coordinates(exif: &Exif, tag: Tag, r#ref: Tag) -> Option<f32> {
|
|
||||||
let mut coord = None;
|
|
||||||
let mut coord_ref = None;
|
|
||||||
|
|
||||||
// Parse DMS coordinates
|
#[derive(Debug, Serialize)]
|
||||||
if let Some(field) = exif.get_field(tag, In::PRIMARY) {
|
pub struct Metadata {
|
||||||
match field.value {
|
pub filename: String,
|
||||||
exif::Value::Rational(ref vec) if !vec.is_empty() => {
|
pub date: String,
|
||||||
let deg = vec[0].to_f64() as u16;
|
pub width: u32,
|
||||||
let min = vec[1].to_f64() as u8;
|
pub height: u32,
|
||||||
let sec = vec[2].to_f64();
|
pub gps: Gps,
|
||||||
|
pub location: Option<Location>,
|
||||||
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<Cardinal> = 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)]
|
#[derive(Debug, Serialize)]
|
||||||
struct Metadata {
|
pub struct MetadataList(pub Vec<Metadata>);
|
||||||
filename: String,
|
|
||||||
date: String,
|
|
||||||
width: u32,
|
|
||||||
height: u32,
|
|
||||||
gps: Gps,
|
|
||||||
location: Option<Location>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[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<Metadata>);
|
|
||||||
|
|
||||||
impl MetadataList {
|
impl MetadataList {
|
||||||
pub fn process_folder(dir: ReadDir, get_location: bool) -> Self {
|
pub fn process_folder(dir: ReadDir, get_location: bool) -> Self {
|
||||||
|
|
@ -105,7 +53,7 @@ impl MetadataList {
|
||||||
|
|
||||||
// Get date
|
// Get date
|
||||||
let mut date = None;
|
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 {
|
match field.value {
|
||||||
exif::Value::Ascii(ref vec) if !vec.is_empty() => {
|
exif::Value::Ascii(ref vec) if !vec.is_empty() => {
|
||||||
if let Ok(datetime) = DateTime::from_ascii(&vec[0]) {
|
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
|
// Get image width
|
||||||
let mut width = None;
|
let mut width = None;
|
||||||
|
|
@ -137,10 +87,7 @@ impl MetadataList {
|
||||||
|
|
||||||
match (date, latitude, longitude, width, height) {
|
match (date, latitude, longitude, width, height) {
|
||||||
(Some(date), Some(latitude), Some(longitude), Some(width), Some(height)) => {
|
(Some(date), Some(latitude), Some(longitude), Some(width), Some(height)) => {
|
||||||
let gps = Gps {
|
let gps = Gps::new(latitude,longitude);
|
||||||
latitude,
|
|
||||||
longitude,
|
|
||||||
};
|
|
||||||
let location = if get_location {
|
let location = if get_location {
|
||||||
eprintln!("Getting location for `{}`", filename);
|
eprintln!("Getting location for `{}`", filename);
|
||||||
gps.get_location()
|
gps.get_location()
|
||||||
|
|
@ -151,7 +98,7 @@ impl MetadataList {
|
||||||
filename,
|
filename,
|
||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
date,
|
date: date.to_string(),
|
||||||
gps,
|
gps,
|
||||||
location,
|
location,
|
||||||
});
|
});
|
||||||
|
|
@ -169,54 +116,3 @@ impl MetadataList {
|
||||||
serde_json::to_string_pretty(&self.0)
|
serde_json::to_string_pretty(&self.0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Gps {
|
|
||||||
fn get_location(&self) -> Option<Location> {
|
|
||||||
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::<serde_json::Value>())
|
|
||||||
{
|
|
||||||
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::<Vec<&str>>().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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
116
crates/gen-wallpapers/src/location.rs
Normal file
116
crates/gen-wallpapers/src/location.rs
Normal file
|
|
@ -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<Location> {
|
||||||
|
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::<serde_json::Value>())
|
||||||
|
{
|
||||||
|
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::<Vec<&str>>().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<f32> {
|
||||||
|
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<Cardinal> = 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,
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue