diff --git a/crates/engine/src/cmd/cull.rs b/crates/engine/src/cmd/cull.rs index 030cb6e93..1738cb609 100644 --- a/crates/engine/src/cmd/cull.rs +++ b/crates/engine/src/cmd/cull.rs @@ -21,6 +21,9 @@ fn secs(iso: &str) -> Option { fn analyze(s: &mut Session, p: &Value) -> Result { let ids: Vec = if p.get("ids").is_some() || s.selection.ids.len() > 1 { s.targets(p)? } else { s.visible_cloned() }; + // a photo listed twice is one photo, not a burst of two look-alikes (first occurrence kept) + let mut seen = std::collections::HashSet::new(); + let ids: Vec = ids.into_iter().filter(|id| seen.insert(*id)).collect(); let reject_below = p.get("rejectBelow").and_then(Value::as_f64).map(|v| v as f32); let pick_best = p.get("pickBest").and_then(Value::as_bool).unwrap_or(false); // measure (thumbnail-level sources: fast, and enough for focus at 512 px) diff --git a/crates/engine/src/tests_import.rs b/crates/engine/src/tests_import.rs index 13f02ce77..36fd75aae 100644 --- a/crates/engine/src/tests_import.rs +++ b/crates/engine/src/tests_import.rs @@ -778,6 +778,18 @@ fn assisted_culling_groups_bursts_and_scores_focus() { let _ = std::fs::remove_dir_all(&dir); } +/// Assisted culling: a photo listed twice is analysed once and is not a burst with itself. +#[test] +fn assisted_culling_ignores_repeated_ids() { + let mut s = Session::with_demo(); + let id = s.visible_cloned()[0]; + let r = s.execute("photo.analyze", &json!({"ids": [id.0, id.0]})).unwrap(); + assert_eq!(r["groups"], 0, "a photo listed twice is not a burst of two: {r}"); + assert_eq!(r["photos"].as_array().map(Vec::len), Some(1), "{r}"); + let an = s.catalog.photo(id).unwrap().analysis.unwrap(); + assert!(an.group.is_none() && !an.best, "{an:?}"); +} + /// Assisted culling: two look-alike shots one second apart across midnight form a burst at the end of /// every month, including those with fewer than 31 days. #[test]