diff --git a/crates/engine/src/camera_preview.rs b/crates/engine/src/camera_preview.rs index fa6731b3..fd7c58f8 100644 --- a/crates/engine/src/camera_preview.rs +++ b/crates/engine/src/camera_preview.rs @@ -1742,10 +1742,10 @@ mod tests { let transform = lightcraft_raw::color::camera_transform(&raw, lightcraft_raw::color::as_shot_white_xy(&raw)); let look = fit_preview(&raw, &bytes, &transform).expect("a camera look"); // the look fitted to the JPEG, as before this change - let (sensor, reference) = proxies(&raw, &bytes, &transform, PROXY).unwrap(); + let (sensor, reference, clipped) = proxies(&raw, &bytes, &transform, PROXY).unwrap(); let profile = raw.metadata.model.as_deref().and_then(crate::camera_profiles::get); let colour = profile.as_ref().and_then(|p| Some((p.matrix().mul(&transform.matrix.inverse()?), p.hue_sat.clone()))); - let fitted = fit_pairs_with(&sensor, &reference, colour).unwrap(); + let fitted = fit_look(&sensor, &reference, &clipped, colour).unwrap(); if dro { assert_lowered(&fitted, &look, name); for x in lowered { diff --git a/crates/engine/src/sidecar.rs b/crates/engine/src/sidecar.rs index 96384d45..e92d193a 100644 --- a/crates/engine/src/sidecar.rs +++ b/crates/engine/src/sidecar.rs @@ -545,7 +545,7 @@ mod tests { hierarchical_keywords: vec!["Places|Lisbon".into()], ..Default::default() }; - let sc = super::parse_sidecar(&lightcraft_meta::write_xmp(&m, None), false).unwrap(); + let sc = super::parse_sidecar(&lightcraft_meta::write_xmp(&m, None), crate::crs::Target::Rendered).unwrap(); assert_eq!(sc.keywords, Some(vec!["Places|Lisbon".to_string()])); } diff --git a/crates/engine/src/sync.rs b/crates/engine/src/sync.rs index 126cac37..0a00a59a 100644 --- a/crates/engine/src/sync.rs +++ b/crates/engine/src/sync.rs @@ -427,7 +427,7 @@ fn sidecar_has_news(labels: &Catalog, p: &Photo, naming: SidecarNaming) -> bool return false; } let Ok(text) = std::fs::read_to_string(&file) else { return false }; - let Ok(sc) = crate::sidecar::parse_sidecar(&text, p.kind == lightcraft_catalog::MediaKind::Raw) else { return false }; + let Ok(sc) = crate::sidecar::parse_sidecar(&text, crate::crs::Target::for_photo(p)) else { return false }; let sc = sc.resolve_label(labels); let mut q = p.clone(); crate::sidecar::merge_into(&mut q, &sc, ""); @@ -464,7 +464,7 @@ pub struct SyncWork { moved: Vec, missing: Vec, /// (photo, its file, how its sidecar is named) - sidecars: Vec<(PhotoId, String, SidecarNaming, bool)>, + sidecars: Vec<(PhotoId, String, SidecarNaming, crate::crs::Target)>, } /// The catalog half of a synchronize (see [`SyncJob`]). @@ -518,7 +518,7 @@ impl SyncJob { .zip(namings) .filter_map(|(m, naming)| { let p = s.catalog.photo(PhotoId(m.id))?; - Some((p.id, m.path, naming, p.kind == lightcraft_catalog::MediaKind::Raw)) + Some((p.id, m.path, naming, crate::crs::Target::for_photo(p))) }) .collect() } else { @@ -590,11 +590,11 @@ impl SyncWork { return; } let mut read = Vec::with_capacity(sidecars.len()); - for (id, path, naming, raw) in sidecars { + for (id, path, naming, target) in sidecars { if cancel.load(Relaxed) { break; } - read.push((id, path.clone(), read_sidecar(&path, naming, raw))); + read.push((id, path.clone(), read_sidecar(&path, naming, target))); step(1); } if !read.is_empty() { @@ -604,14 +604,14 @@ impl SyncWork { } /// Read and parse photo file `path`'s XMP sidecar file. -fn read_sidecar(path: &str, naming: SidecarNaming, raw: bool) -> std::result::Result { +fn read_sidecar(path: &str, naming: SidecarNaming, target: crate::crs::Target) -> std::result::Result { let file = crate::sidecar::find_sidecar(path, naming).ok_or_else(|| "no XMP sidecar".to_string())?; let len = std::fs::metadata(&file).map_err(|e| format!("{}: {e}", file.display()))?.len(); if len > MAX_SIDECAR_BYTES { return Err(format!("{}: too large for an XMP sidecar", file.display())); } let text = std::fs::read_to_string(&file).map_err(|e| format!("{}: {e}", file.display()))?; - crate::sidecar::parse_sidecar(&text, raw).map_err(|e| format!("{}: {e}", file.display())) + crate::sidecar::parse_sidecar(&text, target).map_err(|e| format!("{}: {e}", file.display())) } impl SyncCommit { diff --git a/crates/engine/src/tests_process.rs b/crates/engine/src/tests_process.rs index efa46601..d101073e 100644 --- a/crates/engine/src/tests_process.rs +++ b/crates/engine/src/tests_process.rs @@ -196,19 +196,21 @@ fn sidecars_keep_the_process_and_interchange_edits_get_the_latest() { // versions, so sidecars written then read as V1) put(&mut s, id, NEWER, 0.3); let p = s.catalog.photo(id).unwrap().clone(); - let back = parse_sidecar(&sidecar_packet(&p, &s.catalog), false).unwrap(); + let back = parse_sidecar(&sidecar_packet(&p, &s.catalog), crate::crs::Target::Rendered).unwrap(); let Some(DevelopPatch::Full(d)) = &back.develop else { panic!("no lc:settings") }; assert_eq!((d.process, d.light.exposure), (NEWER, 0.3)); put(&mut s, id, ProcessVersion::V1, 0.3); let packet = sidecar_packet(s.catalog.photo(id).unwrap(), &s.catalog); assert!(!packet.contains("process"), "{packet}"); - let Some(DevelopPatch::Full(d)) = parse_sidecar(&packet, false).unwrap().develop else { panic!("no lc:settings") }; + let Some(DevelopPatch::Full(d)) = parse_sidecar(&packet, crate::crs::Target::Rendered).unwrap().develop else { panic!("no lc:settings") }; assert_eq!(d.process, ProcessVersion::V1); // a damaged process value reads as V1 and costs nothing else: the sidecar's edits still come back for bad in ["-1", "1.5", "\"two\"", "null", "4294967296"] { let json = format!(r#"{{"process": {bad}, "light": {{"exposure": 0.5}}}}"#); let packet = lightcraft_meta::write_xmp(&lightcraft_meta::Metadata::default(), Some(&json)); - let Some(DevelopPatch::Full(d)) = parse_sidecar(&packet, false).unwrap().develop else { panic!("{bad}: the edit was dropped") }; + let Some(DevelopPatch::Full(d)) = parse_sidecar(&packet, crate::crs::Target::Rendered).unwrap().develop else { + panic!("{bad}: the edit was dropped") + }; assert_eq!((d.process, d.light.exposure), (ProcessVersion::V1, 0.5), "{bad}"); } // `crs:` edits (other raw developers, Lightroom): their ProcessVersion numbers another @@ -217,7 +219,7 @@ fn sidecars_keep_the_process_and_interchange_edits_get_the_latest() { let xmp = r#" "#; - let sc = parse_sidecar(xmp, true).unwrap(); + let sc = parse_sidecar(xmp, crate::crs::Target::RawAbsolute).unwrap(); let Some(DevelopPatch::Partial(partial)) = &sc.develop else { panic!("no crs: edit") }; assert!(partial.get("process").is_none(), "{partial}"); let mut fresh = Photo::new(PhotoId(999), Source::Demo { scene: 1 }, "a.jpg", "JPEG", 10, 10, "2026-10-09T00:00:00"); diff --git a/crates/ui-egui/locales/fr-formats.json b/crates/ui-egui/locales/fr-formats.json index aff2e039..6e609329 100644 --- a/crates/ui-egui/locales/fr-formats.json +++ b/crates/ui-egui/locales/fr-formats.json @@ -206,5 +206,20 @@ "In line for AI Denoise ({ahead} ahead)…": "En attente de la réduction du bruit par IA ({ahead} devant)…", "Running on {}.": "En cours d’exécution sur {}.", "{file} · {size}": "{file} · {size}", - "• {assumption}": "• {assumption}" + "• {assumption}": "• {assumption}", + "Put inside “{parent}”": "Placer dans « {parent} »", + "Delete the keyword “{name}”?": "Supprimer le mot-clé « {name} » ?", + "It is taken off {count} photo{}, with the keywords below it. Undo brings it back.": "Il est retiré de {count} photo{}, avec les mots-clés qu’il contient. Annuler le rétablit.", + "Create Keyword Tag Inside “{name}”…": "Créer un mot-clé dans « {name} »…", + "“{parent}” has a “{name}” already. Merge the two?": "« {parent} » contient déjà « {name} ». Fusionner les deux ?", + "There is a “{name}” at the top level already. Merge the two?": "Il existe déjà « {name} » au premier niveau. Fusionner les deux ?", + "Added “{keyword}” to {n} photo{}": "« {keyword} » ajouté à {n} photo{}", + "On {have} of {of} selected photos": "Sur {have} des {of} photos sélectionnées", + "Remove “{name}”": "Retirer « {name} »", + "There is a keyword set “{name}” already": "Un jeu de mots-clés « {name} » existe déjà", + "Added {n} keyword{}": "Mots-clés ajoutés : {n}{:.0}", + "; {n} gained synonyms": " ; avec de nouveaux synonymes : {n}", + "Exported {n} keyword{}": "Mots-clés exportés : {n}{:.0}", + " — Capture One won't import a list with ; , < or > in a keyword: {names}": " — Capture One n’importe pas une liste dont un mot-clé contient ; , < ou > : {names}", + " — left out, as a keyword list can't hold them: {names}": " — omis, car une liste de mots-clés ne peut pas les contenir : {names}" } \ No newline at end of file diff --git a/crates/ui-egui/locales/uk-formats.json b/crates/ui-egui/locales/uk-formats.json index 2649d380..2196c6ba 100644 --- a/crates/ui-egui/locales/uk-formats.json +++ b/crates/ui-egui/locales/uk-formats.json @@ -206,5 +206,20 @@ "In line for AI Denoise ({ahead} ahead)…": "У черзі на усунення шуму ШІ (попереду: {ahead})…", "Running on {}.": "Працює на {}.", "{file} · {size}": "{file} · {size}", - "• {assumption}": "• {assumption}" + "• {assumption}": "• {assumption}", + "Put inside “{parent}”": "Помістити в «{parent}»", + "Delete the keyword “{name}”?": "Видалити ключове слово «{name}»?", + "It is taken off {count} photo{}, with the keywords below it. Undo brings it back.": "Його буде знято з фото (кількість: {count}{:.0}) разом із вкладеними ключовими словами. Скасування поверне його.", + "Create Keyword Tag Inside “{name}”…": "Створити ключове слово в «{name}»…", + "“{parent}” has a “{name}” already. Merge the two?": "«{parent}» уже містить «{name}». Об’єднати їх?", + "There is a “{name}” at the top level already. Merge the two?": "На верхньому рівні вже є «{name}». Об’єднати їх?", + "Added “{keyword}” to {n} photo{}": "«{keyword}» додано до фото: {n}{:.0}", + "On {have} of {of} selected photos": "На {have} з {of} вибраних фото", + "Remove “{name}”": "Вилучити «{name}»", + "There is a keyword set “{name}” already": "Набір ключових слів «{name}» уже існує", + "Added {n} keyword{}": "Додано ключових слів: {n}{:.0}", + "; {n} gained synonyms": "; отримали синоніми: {n}", + "Exported {n} keyword{}": "Експортовано ключових слів: {n}{:.0}", + " — Capture One won't import a list with ; , < or > in a keyword: {names}": " — Capture One не імпортує список, у якому ключове слово містить ; , < або >: {names}", + " — left out, as a keyword list can't hold them: {names}": " — пропущено, бо список ключових слів не може їх містити: {names}" } diff --git a/crates/ui-egui/src/lib.rs b/crates/ui-egui/src/lib.rs index bfeb198e..1666748f 100644 --- a/crates/ui-egui/src/lib.rs +++ b/crates/ui-egui/src/lib.rs @@ -36,11 +36,11 @@ pub mod theme; pub mod titlebar; pub mod widgets; -#[cfg(test)] -mod tests_crop_rotate; #[cfg(test)] mod tests_album_picker; #[cfg(test)] +mod tests_crop_rotate; +#[cfg(test)] mod tests_curve; #[cfg(test)] mod tests_date_picker;