Skip to content

Commit 4fc508d

Browse files
committed
✨feat: added metadata getting logic by image
1 parent d87c125 commit 4fc508d

7 files changed

Lines changed: 136 additions & 1 deletion

File tree

Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,9 @@ serde_json = "1.0"
2121
jsonwebtoken = "9.2"
2222
dotenvy = "0.15"
2323
once_cell = "1.21.3"
24-
terminal_size = "0.4.2"
2524
dialoguer = { version = "0.11", features = ["fuzzy-select"] }
25+
exif = "0.8.0"
26+
terminal_size = "0.4.2"
2627
ring = "0.17.14"
2728
shellexpand = "3.1.1"
2829
libloading = "0.8.8"

src/cli.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,22 @@ pub enum Commands {
1616
Auth(AuthArgs),
1717
Log(LogArgs),
1818
Http(HttpArgs),
19+
Image(ImageArgs),
20+
}
21+
22+
#[derive(Parser)]
23+
pub struct ImageArgs {
24+
#[command(subcommand)]
25+
pub action: ImageAction,
26+
}
27+
28+
#[derive(Subcommand)]
29+
pub enum ImageAction {
30+
Extract {
31+
path: String,
32+
#[arg(short, long, default_value_t = false)]
33+
all: bool,
34+
},
1935
}
2036

2137
#[derive(Parser)]

src/image/mod.rs

Whitespace-only changes.

src/main.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ mod cli;
22
mod core;
33
mod modules;
44
mod services;
5+
mod image;
56

67
use clap::Parser;
78
use std::error::Error;
@@ -17,6 +18,7 @@ async fn main() -> Result<(), Box<dyn Error>> {
1718
cli::Commands::Auth(args) => services::auth::handle(args),
1819
cli::Commands::Log(args) => modules::git::log::handle(&args),
1920
cli::Commands::Http(args) => modules::http::handler::handle(args.action).await?,
21+
cli::Commands::Image(args) => modules::image::image::handle(args),
2022
}
2123

2224
Ok(())

src/modules/image/image.rs

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
use crate::cli::{ImageAction, ImageArgs};
2+
use colored::*;
3+
use exif::{Field, In, Tag};
4+
use std::fs::File;
5+
use std::io::BufReader;
6+
7+
pub fn handle(args: ImageArgs) {
8+
match args.action {
9+
ImageAction::Extract { path, all } => {
10+
if let Err(e) = extract_image_metadata(&path, all) {
11+
eprintln!("{}: {}", "Error".red(), e);
12+
}
13+
}
14+
}
15+
}
16+
17+
fn extract_image_metadata(path: &str, show_all: bool) -> Result<(), Box<dyn std::error::Error>> {
18+
let file = File::open(path)?;
19+
let mut bufreader = BufReader::new(&file);
20+
let exifreader = exif::Reader::new();
21+
let exif = match exifreader.read_from_container(&mut bufreader) {
22+
Ok(exif) => exif,
23+
Err(_) => {
24+
println!("{}: No EXIF data found in image", "Warning".yellow());
25+
return Ok(());
26+
}
27+
};
28+
29+
println!("{}", "Extracted metadata:".green().bold());
30+
31+
let interesting_tags = if show_all {
32+
// Show all tags if --all flag is set
33+
exif.fields().collect::<Vec<_>>()
34+
} else {
35+
// Only show common interesting tags
36+
exif.fields()
37+
.filter(|field| {
38+
matches!(
39+
field.tag,
40+
Tag::DateTime
41+
| Tag::DateTimeOriginal
42+
| Tag::DateTimeDigitized
43+
| Tag::GPSLatitude
44+
| Tag::GPSLongitude
45+
| Tag::GPSLatitudeRef
46+
| Tag::GPSLongitudeRef
47+
| Tag::Make
48+
| Tag::Model
49+
| Tag::Software
50+
| Tag::Artist
51+
| Tag::Copyright
52+
| Tag::ImageDescription
53+
)
54+
})
55+
.collect()
56+
};
57+
58+
for field in interesting_tags {
59+
let tag_name = format!("{:?}", field.tag);
60+
let value = match field.tag {
61+
Tag::GPSLatitude | Tag::GPSLongitude => {
62+
if let Ok(coord) = field.value.get_float(0) {
63+
format!("{}°", coord)
64+
} else {
65+
field.display_value().to_string()
66+
}
67+
}
68+
_ => field.display_value().to_string(),
69+
};
70+
71+
println!("{:20}: {}", tag_name.cyan(), value);
72+
}
73+
74+
// Try to extract GPS coordinates if available
75+
if let (Some(lat), Some(lon)) = get_gps_coordinates(&exif) {
76+
println!();
77+
println!("{}", "GPS Coordinates:".green().bold());
78+
println!("Latitude: {}", lat.to_string().yellow());
79+
println!("Longitude: {}", lon.to_string().yellow());
80+
println!();
81+
println!(
82+
"{}: https://www.google.com/maps/place/{},{}",
83+
"View on map".blue(),
84+
lat,
85+
lon
86+
);
87+
}
88+
89+
Ok(())
90+
}
91+
92+
fn get_gps_coordinates(exif: &exif::Exif) -> (Option<f64>, Option<f64>) {
93+
let lat = get_gps_coordinate(exif, Tag::GPSLatitude, Tag::GPSLatitudeRef);
94+
let lon = get_gps_coordinate(exif, Tag::GPSLongitude, Tag::GPSLongitudeRef);
95+
(lat, lon)
96+
}
97+
98+
fn get_gps_coordinate(exif: &exif::Exif, coord_tag: Tag, ref_tag: Tag) -> Option<f64> {
99+
let coord = exif.get_field(coord_tag, In::PRIMARY)?;
100+
let ref_val = exif.get_field(ref_tag, In::PRIMARY)?;
101+
102+
let coord_vec = coord.value.as_rational()?.get(0..3)?;
103+
let degrees = coord_vec[0].to_f64();
104+
let minutes = coord_vec[1].to_f64();
105+
let seconds = coord_vec[2].to_f64();
106+
107+
let mut coord = degrees + minutes / 60.0 + seconds / 3600.0;
108+
109+
if ref_val.display_value().to_string() == "S" || ref_val.display_value().to_string() == "W" {
110+
coord = -coord;
111+
}
112+
113+
Some(coord)
114+
}

src/modules/image/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
pub mod image;

src/modules/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
pub mod docker;
22
pub mod git;
33
pub mod http;
4+
pub(crate) mod image;

0 commit comments

Comments
 (0)