Integrate batch 22: fix cross-PR breaks
- #540's crs::Target replaces the raw flag: #597/#604's folder sync and sidecar_op and #533's process tests pass the photo's target instead of a bool. - #568's DRO test fits through #528's fit_look (proxies now return the clipped mask). - #541 (French) and #564 (Ukrainian) gain the 15 keyword format strings #578-#592 added. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
987a572512
commit
b4ed033c2d
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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()]));
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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<FolderMoved>,
|
||||
missing: Vec<FolderPhoto>,
|
||||
/// (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<crate::sidecar::SidecarData, String> {
|
||||
fn read_sidecar(path: &str, naming: SidecarNaming, target: crate::crs::Target) -> std::result::Result<crate::sidecar::SidecarData, String> {
|
||||
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 {
|
||||
|
|
|
|||
|
|
@ -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#"<x:xmpmeta xmlns:x="adobe:ns:meta/"><rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
|
||||
<rdf:Description rdf:about="" xmlns:crs="http://ns.adobe.com/camera-raw-settings/1.0/"
|
||||
crs:ProcessVersion="11.0" crs:Exposure2012="+1.10" crs:Contrast2012="+20"/></rdf:RDF></x:xmpmeta>"#;
|
||||
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");
|
||||
|
|
|
|||
|
|
@ -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}"
|
||||
}
|
||||
|
|
@ -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}"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
Loading…
Reference in New Issue