feature/exif-endpoint #44
@@ -2,6 +2,7 @@ use std::path::PathBuf;
|
|||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
|
|
||||||
use chrono::Utc;
|
use chrono::Utc;
|
||||||
|
use clap::Parser;
|
||||||
use rayon::prelude::*;
|
use rayon::prelude::*;
|
||||||
use walkdir::WalkDir;
|
use walkdir::WalkDir;
|
||||||
|
|
||||||
@@ -9,16 +10,30 @@ use image_api::database::models::InsertImageExif;
|
|||||||
use image_api::database::{ExifDao, SqliteExifDao};
|
use image_api::database::{ExifDao, SqliteExifDao};
|
||||||
use image_api::exif;
|
use image_api::exif;
|
||||||
|
|
||||||
|
#[derive(Parser, Debug)]
|
||||||
|
#[command(name = "migrate_exif")]
|
||||||
|
#[command(about = "Extract and store EXIF data from images", long_about = None)]
|
||||||
|
struct Args {
|
||||||
|
#[arg(long, help = "Skip files that already have EXIF data in database")]
|
||||||
|
skip_existing: bool,
|
||||||
|
}
|
||||||
|
|
||||||
fn main() -> anyhow::Result<()> {
|
fn main() -> anyhow::Result<()> {
|
||||||
env_logger::init();
|
env_logger::init();
|
||||||
dotenv::dotenv()?;
|
dotenv::dotenv()?;
|
||||||
|
|
||||||
|
let args = Args::parse();
|
||||||
let base_path = dotenv::var("BASE_PATH")?;
|
let base_path = dotenv::var("BASE_PATH")?;
|
||||||
let base = PathBuf::from(&base_path);
|
let base = PathBuf::from(&base_path);
|
||||||
|
|
||||||
println!("EXIF Migration Tool");
|
println!("EXIF Migration Tool");
|
||||||
println!("===================");
|
println!("===================");
|
||||||
println!("Base path: {}", base.display());
|
println!("Base path: {}", base.display());
|
||||||
|
if args.skip_existing {
|
||||||
|
println!("Mode: Skip existing (incremental)");
|
||||||
|
} else {
|
||||||
|
println!("Mode: Upsert (insert new, update existing)");
|
||||||
|
}
|
||||||
println!();
|
println!();
|
||||||
|
|
||||||
// Collect all image files that support EXIF
|
// Collect all image files that support EXIF
|
||||||
@@ -59,6 +74,19 @@ fn main() -> anyhow::Result<()> {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Check if EXIF data already exists
|
||||||
|
let existing = if let Ok(mut dao_lock) = dao.lock() {
|
||||||
|
dao_lock.get_exif(&relative_path).ok().flatten()
|
||||||
|
} else {
|
||||||
|
eprintln!("✗ {} - Failed to acquire database lock", relative_path);
|
||||||
|
return Err(anyhow::anyhow!("Lock error"));
|
||||||
|
};
|
||||||
|
|
||||||
|
// Skip if exists and skip_existing flag is set
|
||||||
|
if args.skip_existing && existing.is_some() {
|
||||||
|
return Ok(("skip".to_string(), relative_path));
|
||||||
|
}
|
||||||
|
|
||||||
match exif::extract_exif_from_path(path) {
|
match exif::extract_exif_from_path(path) {
|
||||||
Ok(exif_data) => {
|
Ok(exif_data) => {
|
||||||
let timestamp = Utc::now().timestamp();
|
let timestamp = Utc::now().timestamp();
|
||||||
@@ -78,16 +106,31 @@ fn main() -> anyhow::Result<()> {
|
|||||||
shutter_speed: exif_data.shutter_speed,
|
shutter_speed: exif_data.shutter_speed,
|
||||||
iso: exif_data.iso,
|
iso: exif_data.iso,
|
||||||
date_taken: exif_data.date_taken,
|
date_taken: exif_data.date_taken,
|
||||||
created_time: timestamp,
|
created_time: existing
|
||||||
|
.as_ref()
|
||||||
|
.map(|e| e.created_time)
|
||||||
|
.unwrap_or(timestamp),
|
||||||
last_modified: timestamp,
|
last_modified: timestamp,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Store in database
|
// Store or update in database
|
||||||
if let Ok(mut dao_lock) = dao.lock() {
|
if let Ok(mut dao_lock) = dao.lock() {
|
||||||
match dao_lock.store_exif(insert_exif) {
|
let result = if existing.is_some() {
|
||||||
Ok(_) => {
|
// Update existing record
|
||||||
println!("✓ {}", relative_path);
|
dao_lock.update_exif(insert_exif).map(|_| "update")
|
||||||
Ok(relative_path)
|
} else {
|
||||||
|
// Insert new record
|
||||||
|
dao_lock.store_exif(insert_exif).map(|_| "insert")
|
||||||
|
};
|
||||||
|
|
||||||
|
match result {
|
||||||
|
Ok(action) => {
|
||||||
|
if action == "update" {
|
||||||
|
println!("↻ {} (updated)", relative_path);
|
||||||
|
} else {
|
||||||
|
println!("✓ {} (inserted)", relative_path);
|
||||||
|
}
|
||||||
|
Ok((action.to_string(), relative_path))
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
eprintln!("✗ {} - Database error: {:?}", relative_path, e);
|
eprintln!("✗ {} - Database error: {:?}", relative_path, e);
|
||||||
@@ -107,23 +150,42 @@ fn main() -> anyhow::Result<()> {
|
|||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
let success_count = results.iter().filter(|r| r.is_ok()).count();
|
// Count results
|
||||||
let error_count = results.len() - success_count;
|
let mut success_count = 0;
|
||||||
|
let mut inserted_count = 0;
|
||||||
|
let mut updated_count = 0;
|
||||||
|
let mut skipped_count = 0;
|
||||||
|
|
||||||
|
for result in &results {
|
||||||
|
if let Ok((action, _)) = result {
|
||||||
|
success_count += 1;
|
||||||
|
match action.as_str() {
|
||||||
|
"insert" => inserted_count += 1,
|
||||||
|
"update" => updated_count += 1,
|
||||||
|
"skip" => skipped_count += 1,
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let error_count = results.len() - success_count - skipped_count;
|
||||||
|
|
||||||
println!();
|
println!();
|
||||||
println!("===================");
|
println!("===================");
|
||||||
println!("Migration complete!");
|
println!("Migration complete!");
|
||||||
println!(
|
println!("Total images processed: {}", image_files.len());
|
||||||
"Successfully extracted EXIF from {}/{} images",
|
|
||||||
success_count,
|
|
||||||
image_files.len()
|
|
||||||
);
|
|
||||||
|
|
||||||
|
if inserted_count > 0 {
|
||||||
|
println!(" New EXIF records inserted: {}", inserted_count);
|
||||||
|
}
|
||||||
|
if updated_count > 0 {
|
||||||
|
println!(" Existing records updated: {}", updated_count);
|
||||||
|
}
|
||||||
|
if skipped_count > 0 {
|
||||||
|
println!(" Skipped (already exists): {}", skipped_count);
|
||||||
|
}
|
||||||
if error_count > 0 {
|
if error_count > 0 {
|
||||||
println!(
|
println!(" Errors (no EXIF data or failures): {}", error_count);
|
||||||
"{} images had no EXIF data or encountered errors",
|
|
||||||
error_count
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|||||||
Reference in New Issue
Block a user