diff --git a/crates/engine/src/lens_profiles.rs b/crates/engine/src/lens_profiles.rs
index 65144d5b7..6e7f28120 100644
--- a/crates/engine/src/lens_profiles.rs
+++ b/crates/engine/src/lens_profiles.rs
@@ -46,7 +46,7 @@ pub fn lookup(db: &LensDb, m: &lightcraft_meta::Metadata, w: f64, h: f64, orient
// (a user's alias), since the specification fits other makers' lenses too
None => {
let name = lightcraft_lensdb::spec_name(m.lens_spec?);
- db.find_lens(m.lens_make.as_deref(), &name, camera).filter(|l| l.names_exactly(&name))?
+ db.find_lens_named(m.lens_make.as_deref(), &name, camera)?
}
};
let crop = frame_crop_factor(camera.map(|c| c.crop_factor), focal, m.focal_length_35mm);
diff --git a/crates/lensdb/src/lib.rs b/crates/lensdb/src/lib.rs
index cfdda2eaf..93b75f68b 100644
--- a/crates/lensdb/src/lib.rs
+++ b/crates/lensdb/src/lib.rs
@@ -169,13 +169,24 @@ impl LensDb {
/// The best profile for an EXIF lens model (and lens make, when the file has one), preferring lenses for the
/// camera's mount. `None` when no name is a convincing match.
pub fn find_lens(&self, lens_make: Option<&str>, lens_model: &str, camera: Option<&Camera>) -> Option<&Profile> {
+ self.best_lens(lens_make, lens_model, camera, |_| true)
+ }
+
+ /// [`find_lens`](Self::find_lens) among the profiles one of whose names is exactly `name`
+ /// ([`Profile::names_exactly`]), for names that carry no maker, such as [`spec_name`]s: another maker's lens with
+ /// the same specification scores as high, and must neither win nor, by coming first, hide the profile named so.
+ pub fn find_lens_named(&self, lens_make: Option<&str>, name: &str, camera: Option<&Camera>) -> Option<&Profile> {
+ self.best_lens(lens_make, name, camera, |l| l.names_exactly(name))
+ }
+
+ fn best_lens(&self, lens_make: Option<&str>, lens_model: &str, camera: Option<&Camera>, eligible: impl Fn(&Profile) -> bool) -> Option<&Profile> {
let exif = tokens(lens_model);
if exif.is_empty() {
return None;
}
let make = lens_make.map(tokens).unwrap_or_default();
let mut best: Option<(f64, &Profile)> = None;
- for lens in &self.lenses {
+ for lens in self.lenses.iter().filter(|l| eligible(l)) {
let maker = tokens(&lens.maker);
if !make.is_empty() && !maker.is_empty() && !maker_matches(&maker, &make) {
continue;
diff --git a/crates/lensdb/src/tests.rs b/crates/lensdb/src/tests.rs
index f87193fbc..435882381 100644
--- a/crates/lensdb/src/tests.rs
+++ b/crates/lensdb/src/tests.rs
@@ -265,3 +265,25 @@ fn spec_names_need_an_exact_profile_name() {
assert!(aliased.names_exactly(&spec_name([24.0, 70.0, 2.8, 2.8])));
assert!(!aliased.names_exactly("24-105mm f/4"));
}
+
+/// Another maker's lens with the same specification scores as high as the aliased one; coming first in the
+/// database it must not hide the profile named for the specification.
+#[test]
+fn a_named_profile_is_found_behind_a_tie() {
+ let lens = |maker: &str, models: &str| {
+ format!(
+ r#"{maker}{models}Test Mount1
+ "#
+ )
+ };
+ let xml = format!(
+ r#"{}{}"#,
+ lens("Otherco", "Otherco 18-55mm f/3.5-5.6"),
+ lens("Testcam", "Testcam 18-55mm f/3.5-5.6 VR18-55mm f/3.5-5.6"),
+ );
+ let db = LensDb::parse(&xml).unwrap();
+ let name = spec_name([18.0, 55.0, 3.5, 5.6]);
+ assert_eq!(db.find_lens(None, &name, None).unwrap().maker, "Otherco", "a plain lookup keeps the first of a tie");
+ assert_eq!(db.find_lens_named(None, &name, None).unwrap().maker, "Testcam");
+ assert!(db.find_lens_named(None, "70-200mm f/4", None).is_none());
+}