Author: Kalin
I have a naming convention for my images. It goes like that:
images\20240223
images\20240224
images\20240225
So, I went into my Photos app and exported unmofied versions of all my photos to a directory called "images". That's great but now I have one directory with images inside. They are not sorted in subdirectories the way I like them to be. They look like that:
IMG_3734.HEIC
IMG_3735.HEIC
IMG_3736.HEIC
IMG_3737.HEIC
IMG_3738.HEIC
IMG_3739.HEIC
IMG_3740.HEIC
IMG_3741.HEIC
IMG_3742.HEIC
IMG_3743.HEIC
IMG_3744.HEIC
IMG_3745.HEIC
IMG_3746.HEIC
IMG_3748.HEIC
IMG_3749.HEIC
IMG_3750.HEIC
So, let's write a simple Rust application to move all files from "images" to multiple directories under "sorted_images".
We'll need just a bit of dependencies, here's the Cargo.toml file:
[package]
name = "image_sorter"
version = "0.1.0"
edition = "2021"
[dependencies]
chrono = "0.4"
glob = "0.3"
filetime = "0.2"
And here's the Rust application itself:
use chrono::{NaiveDateTime, DateTime, Utc}; // Import necessary types
use filetime::FileTime;
use glob::glob;
use std::fs;
use std::io;
use std::path::Path;
fn main() -> io::Result<()> {
let source_dir = "./images"; // Source directory containing images
let destination_base = "./sorted_images"; // Base directory for sorted images
// Ensure the destination base directory exists
fs::create_dir_all(destination_base)?;
// Iterate over all image files in the source directory
for entry in glob(&format!("{}/*.*", source_dir)).expect("Failed to read glob pattern") {
match entry {
Ok(path) => {
if let Ok(metadata) = fs::metadata(&path) {
if let Some(created_time) = FileTime::from_creation_time(&metadata) {
let created_naive = NaiveDateTime::from_timestamp(created_time.unix_seconds(), created_time.nanoseconds() as u32);
let datetime: DateTime<Utc> = DateTime::<Utc>::from_utc(created_naive, Utc);
let date = datetime.date();
let target_dir = format!("{}/{}", destination_base, date.format("%Y%m%d"));
fs::create_dir_all(&target_dir)?;
let filename = path.file_name().unwrap();
let destination = Path::new(&target_dir).join(filename);
fs::rename(&path, &destination)?;
println!("Moved {} to {}", path.display(), destination.display());
} else {
println!("Could not extract creation time from {}", path.display());
}
} else {
println!("Could not get metadata for {}", path.display());
}
}
Err(e) => println!("{:?}", e),
}
}
Ok(())
}
Here's what's going on:
The date is created using:
date.format("%Y%m%d"))
which gives the YYYYMMDD time format. And that's all there is to it!
Now we have a nice sorted list of directories with photos. I usually (when I find time) go over them to review them and add a bit of context like:
20240101_new_year_party
20240624_beach
Can I automate that? Hmm.. a bit of AI to recognize what images are about would go a long way here... but maybe next time.