/notes/
Extracting EXIF data with Rust
21 Oct 2024, 19:41 — 4 min read
Disclaimer: This is probably an overkill for such task, but I’m currently learning Rust and wanted to write a small program to do the job.
I was coding my photo gallery this weekend and I had to extract EXIF data somehow programatically and include them in the individual photo pages. As in the other parts of my website, I also wanted to use the Astro Content Collections, so there is a route for every photo with detailed information as the EXIF and possibly some comments and other stuff.
Problem Description: Creating a bunch of MDX files with the same names as the images/photographs and put the EXIF, metadata and content inside them automatically in a directory.
So, I have a lot of photos and I need an .mdx file for every one of them in a directory. The photos are already processed -resized and converted to .webp- and being hosted on my server. Therefore I am not processing them using the Astro’s image processing features. I could do it and push everything in my Astro deploy process, but I am hosting my photos on my own server and this website is still on the Netlify’s free plan. Soon I will migrate it to my server too when I have time.
Anyway, below is the code where I use the rexiv2 crate to generate the necessary files with the required EXIF data inside them in a formatted manner.
exif-to-md.rs
use chrono::NaiveDateTime; //for formatting time and date
use rexiv2::Metadata;
use std::fs::{self, File};
use std::io::Write;
fn main() -> std::io::Result<()> { let folder_path = "./photos"; // using MDX in Astro, so the files
should be .mdx let markdown_extension = ".mdx";
// initialize Rexiv2 crate
let _ = rexiv2::initialize();
// read the photos directory
for entry in fs::read_dir(folder_path)? {
let entry = entry?;
let path = entry.path();
// check if the entry is a file
if path.is_file() {
if let Some(file_stem) = path.file_stem() {
let file_stem = file_stem.to_string_lossy().trim().to_lowercase();
let markdown_file_path =
format!("{}/{}{}", folder_path, file_stem, markdown_extension);
// extract EXIF data if non-existent -> default is an empty string
let mut date_taken = "".to_string();
let mut camera_model = "".to_string();
let mut camera_make = "".to_string();
// content of the files to match my Astro component structure
let md_content = r#"
import { Picture } from "astro:assets";
<picture>
<source
srcset={`https://img.kenan.fyi/photos/originals/${frontmatter.title}_1920px.webp`}
type="image/webp"
/>
<img
src={`https://img.kenan.fyi/photos/originals/${frontmatter.title}_1920px.jpg`}
alt=""
width="2880"
height="1200"
loading="lazy"
decoding="async"
/>
</picture>
"#;
// load the metadata using rexiv2
if let Ok(metadata) = Metadata::new_from_path(&path) {
// get the date
if let Ok(value) = metadata.get_tag_string("Exif.Image.DateTime") {
// parse the date from YYYY:MM:DD HH:MM:SS to YYYY-MM-DD
if let Ok(parsed_date) = NaiveDateTime::parse_from_str(&value, "%Y:%m:%d %H:%M:%S")
{
date_taken = parsed_date.format("%Y-%m-%d").to_string();
}
}
// get the camera
if let Ok(value) = metadata.get_tag_string("Exif.Image.Make") {
camera_make = value;
}
// get the camera model
if let Ok(value) = metadata.get_tag_string("Exif.Image.Model") {
camera_model = value;
}
}
// create the markdown file and write to it
let mut file = File::create(&markdown_file_path)?;
writeln!(file, "---")?;
writeln!(file, "draft: false")?;
writeln!(file, "title: \"{}\"", file_stem)?; // Title
writeln!(file, "shotDate: {}", date_taken)?; // EXIF date taken
writeln!(file, "camera: \"{} {}\"", camera_make, camera_model)?; // EXIF camera model
writeln!(file, "---\n")?;
writeln!(file, "{}", md_content)?;
println!("Created: {}", markdown_file_path);
}
}
}
Ok(())
}
So, what it does basically is the following:
- Check the folder for files
- Assign empty strings to the exif data variables if the photo does not have any
- Extract the metadata
- Create the file
- Write the metadata to the file with writeln! macro
- Write the boilerplate content for the .mdx file afterwards
It is a one-time solution for a specific problem, but as I said, it is a small practice in Rust and an easy way to create multiple files I needed with specific data in them. I would probably implement a better program (maybe a CLI tool?) to streamline the whole process, because this just solves the .mdx creation problem of a photo. The resizing, processing and upload of them are another sections of the whole process, which I have not mentioned.
Maybe I will post about them later too. Cheers.