Skip to content

Commit

Permalink
feat: output format + lib + opti + structure
Browse files Browse the repository at this point in the history
  • Loading branch information
i5-650 committed Sep 5, 2024
1 parent 99c7ebe commit 76932d7
Show file tree
Hide file tree
Showing 10 changed files with 322 additions and 159 deletions.
12 changes: 8 additions & 4 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
# Generated by Cargo
# will have compiled files and executables
/target/
/exif-samples/
data.zip

# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
Expand All @@ -9,8 +11,10 @@ Cargo.lock
# These are backup files generated by rustfmt
**/*.rs.bk



/data/
ouput.json
.cargo/
*.json
.cargo/
*.png
*.jpeg
*.jpg
*.heic
4 changes: 0 additions & 4 deletions .vscode/settings.json

This file was deleted.

25 changes: 18 additions & 7 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,13 +1,24 @@
[package]
name = "rusty-exif"
version = "0.2.1"
name = "rsexif"
version = "0.0.2"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
kamadak-exif = "0.5.4"
serde = { version = "1.0.145", features = ["derive"] }
serde_json = "1.0.59"
clap = {version = "4.0.22", features = ["derive"]}
sscanf = "0.4.0"
rayon = "1.10"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
clap = { version = "4.5", features = ["derive"] }
scanf = "1.2.1"
anyhow = "1.0"
rexiv2 = "0.10"


[lib]
name = "rsexif"
path = "src/lib.rs"

[[bin]]
name = "rsexif"
path = "src/main.rs"
43 changes: 34 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,18 +1,43 @@
# rusty-exif
<img src="froggy.png" alt="rusty-exif-icon" style="width:300px;height:auto;"/>

A simple exif tool for the command line written in Rust.

## Usage

The base tool is designed to be used in two modes
```
Usage: rsexif <COMMAND>
Commands:
file, -f, --file Extract exif from a single file
dir, -d, --dir Extract exif from every files in a directory
help Print this message or the help of the given subcommand(s)
Options:
-h, --help Print help
```
### Mode file
```
Usage: rusty-exif [OPTIONS]
Usage: rsexif {file|--file|-f} [OPTIONS] <file>
Arguments:
<file> image to extract exif from
Options:
-e, --export <export> Json file to output exifs to
-h, --help Print help
```

### Mode Directory
```
Usage: rsexif {dir|--dir|-d} [OPTIONS] <folder>
Arguments:
<folder> directory containing images to extract exifs from
Options:
-e, --export <EXPORT>
-f, --file <FILE>
-F, --folder <FOLDER>
-h, --help Print help information
-V, --version Print version information
-s, --split <split> Wether you decide to store all exifs into one file or multiples [possible values: true, false]
-e, --export <export> The name of the Json file containing all the exifs
-h, --help Print help
```

## Examples
Expand All @@ -36,4 +61,4 @@ rusty-exif -f image.jpg
- [ ] Add a GUI
- [X] Add the argument to convert GPS coordinates into a google maps link
- [ ] Add the argument to convert GPS coordinates into an address and/or a screenshot of the location on a map.
- [ ] Modify parameters to use rusty-exif the same way as exiftool
- [X] Make a cleaner version of the CLI
8 changes: 0 additions & 8 deletions src/exif_mapper/data_structures.rs

This file was deleted.

69 changes: 0 additions & 69 deletions src/exif_mapper/mod.rs

This file was deleted.

109 changes: 109 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
use std::path::PathBuf;
use std::{fs, collections::HashMap, collections::BTreeMap};
use scanf::sscanf;
use rayon::prelude::*;

use models::image::Image;

pub mod models;

const GOOGLE_MAP: &str = "googleMap";


pub fn from_file(path: String) -> HashMap<String, BTreeMap<String,String>> {

let metadata = match rexiv2::Metadata::new_from_path(path) {
Ok(m) => m,
Err(e) => {
println!("[*] Error while reading the exif from a file: {}", e);
return HashMap::new();
}
};

let tags = match metadata.get_exif_tags() {
Ok(t) => t,
Err(e) => {
println!("[*] Error while retreving the exif: {}", e);
return HashMap::new();
}
};

let map_data = tags.iter()
.map(|tag| {
let value = match metadata.get_tag_interpreted_string(tag) {
Ok(val) => val,
Err(_) => String::from("Failed to convert to string"),
};

// Exifs tags are like: Exif.Categ.TheTag
let parts: Vec<&str> = tag.split('.').collect();
if parts.len() >= 3 {
let category = parts[1].to_string();
let tag_name = parts[2..].join(".");
(category, tag_name, value)
} else {
let category = "Unknown".to_string();
let tag_name = parts[parts.len() -1].to_string();
(category, tag_name, value)
}
})
// We want the exifs to be in categories so we make a map of map
.fold(HashMap::new(), |mut acc: HashMap<String, BTreeMap<String, String>>, (category, tag_name, value)| {
// Use a BTreeMap to keep the elements sorted (better readability)
acc.entry(category)
.or_default()
.insert(tag_name, value);
acc
});

add_google_map(map_data)
}



fn add_google_map(mut map_data :HashMap<String, BTreeMap<String, String>>) -> HashMap<String, BTreeMap<String, String>> {

if !map_data.contains_key("GPSInfo") {
return map_data;
}

let gps_info = map_data.get_mut("GPSInfo").expect("Impossible missing GPSInfo");

if let (Some(longitude), Some(latitude)) = (gps_info.get("GPSLatitude"), gps_info.get("GPSLongitude")) {
gps_info.insert(
GOOGLE_MAP.to_string(),
format!("https://www.google.com/maps/search/?api=1&query={},{}",
to_decimal(latitude),
to_decimal(longitude)
)
);
}
map_data
}

fn to_decimal(dms: &str) -> f64 {
let mut degrees: f64 = 0.0;
let mut minutes: f64 = 0.0;
let mut seconds: f64 = 0.0;

if sscanf!(dms, "{f64} deg {f64}' {f64}\"", degrees, minutes, seconds).is_err() {
return 0.0;
}

degrees + minutes / 60.0 + seconds / 3600.0
}

pub fn from_folder(_path: PathBuf) -> Vec<Image> {
let files = fs::read_dir(_path).expect("Couldn't read the directory given");

files.par_bridge()
.filter_map(|f| f.ok())
.filter(|f| !f.path().ends_with(".DS_Store") && !f.path().ends_with("/"))
.map(|f| {
let entry_path = f.path().display().to_string();
Image{
name: entry_path.clone(),
exifs: from_file(entry_path)
}
}).collect::<Vec<Image>>()
}
Loading

0 comments on commit 76932d7

Please sign in to comment.