commit - 869dc7dfb9791f0aa16d0febb6e99f39e13f81be
commit + 0114478d582c2ff26580de80dc9d5468832520d5
blob - a522e57b24bcb861bd9847043b3ebc2103943cd8
blob + 192f6fc9f28f70cc264756a848e80b292b5d8f78
--- src/main.rs
+++ src/main.rs
#[arg(long)]
dry_run: bool,
+ /// Show size statistics after finishing
+ #[arg(long)]
+ stats: bool,
+
/// Increase verbosity (use -v, -vv, ...).
///
/// When no RUST_LOG is set, a single -v switches the log level to DEBUG.
let skipped = Arc::new(AtomicUsize::new(0));
let failed = Arc::new(AtomicUsize::new(0));
+ // stats
+ let total_before = Arc::new(AtomicUsize::new(0));
+ let total_after = Arc::new(AtomicUsize::new(0));
+
let walker = WalkBuilder::new(&*input_root)
.hidden(false)
.follow_links(false)
let processed = Arc::clone(&processed);
let skipped = Arc::clone(&skipped);
let failed = Arc::clone(&failed);
+ let total_before = Arc::clone(&total_before);
+ let total_after = Arc::clone(&total_after);
Box::new(move |result| {
match result {
return WalkState::Continue;
}
- match process_img(&input_root, &output_root, path, dry_run)
- {
+ match process_img(
+ &input_root,
+ &output_root,
+ path,
+ dry_run,
+ &total_before,
+ &total_after,
+ ) {
Ok(()) => {
processed.fetch_add(1, Ordering::Relaxed);
}
failed.load(Ordering::Relaxed),
);
+ if total_before.load(Ordering::Relaxed) > 0 && args.stats {
+ let before = total_before.load(Ordering::Relaxed) as f64;
+ let after = total_after.load(Ordering::Relaxed) as f64;
+
+ let saved = before - after;
+ let saved_pct =
+ if before > 0.0 { (saved / before) * 100.0 } else { 0.0 };
+
+ println!();
+ println!("Stats:");
+ println!("Source total: {:.2} MB", before / (1024.0 * 1024.0));
+ if !dry_run {
+ println!("Clean total: {:.2} MB", after / (1024.0 * 1024.0));
+ println!(
+ "Saved: {:.2} MB ({:.1}%)",
+ saved / (1024.0 * 1024.0),
+ saved_pct
+ );
+ } else {
+ println!("Clean total: (DRY-RUN) skipped");
+ }
+ println!();
+ }
+
if failed.load(Ordering::Relaxed) > 0 {
warn!("some files failed to process");
}
output_root: &Path,
src: &Path,
dry_run: bool,
+ total_before: &AtomicUsize,
+ total_after: &AtomicUsize,
) -> anyhow::Result<()> {
let rel_path = match src.strip_prefix(input_root) {
Ok(rel) => rel.to_path_buf(),
let data = fs::read(src)
.with_context(|| format!("failed to read '{}'", src.display()))?;
+ let src_metadata = fs::metadata(src)
+ .with_context(|| format!("failed to stat '{}'", src.display()))?;
+
+ total_before.fetch_add(src_metadata.len() as usize, Ordering::Relaxed);
+
let cleaned =
web_image_meta::jpeg::clean_metadata(&data).with_context(|| {
format!("failed to clean metadata for '{}'", src.display())
fs::write(&dst, &cleaned)
.with_context(|| format!("failed to write '{}'", dst.display()))?;
+ total_after.fetch_add(cleaned.len(), Ordering::Relaxed);
+
debug!("cleaned '{}' -> '{}'", src.display(), dst.display());
Ok(())