mirror of
https://github.com/storytold/lightcraft.git
synced 2026-10-27 11:37:13 -06:00
XMP: preserve Upright warning modes across import paths
This commit is contained in:
@@ -987,7 +987,7 @@ pub fn specs() -> Vec<CommandSpec> {
|
||||
"Auto Import Now",
|
||||
[],
|
||||
None,
|
||||
"{listing?: [[path, size]] (the folder as listed by the caller, e.g. on a worker thread), start?: bool (false: return the `library.import` params as `import` instead of importing)} — add the watched folder's new photos (files the library doesn't have yet; partial / still-copying files wait for the next scan) → {imported, folder, import?}",
|
||||
"{listing?: [[path, size]] (the folder as listed by the caller, e.g. on a worker thread), start?: bool (false: return the `library.import` params as `import` instead of importing)} — add the watched folder's new photos (files the library doesn't have yet; partial / still-copying files wait for the next scan) → {imported, folder, warnings?: [{path, code, profile?, mode?, fields?}], import?}",
|
||||
always,
|
||||
|s, p| {
|
||||
let Some(folder) = s.import_defaults.auto_folder.clone() else { return Ok(json!({"imported": [], "folder": null})) };
|
||||
@@ -1041,7 +1041,11 @@ pub fn specs() -> Vec<CommandSpec> {
|
||||
let r = s.execute("library.import", ¶ms)?;
|
||||
// arriving photos don't take over the selection
|
||||
s.selection = sel;
|
||||
Ok(json!({"imported": r["imported"], "folder": folder}))
|
||||
let mut result = json!({"imported": r["imported"], "folder": folder});
|
||||
if let Some(warnings) = r.get("warnings") {
|
||||
result["warnings"] = warnings.clone();
|
||||
}
|
||||
Ok(result)
|
||||
}
|
||||
),
|
||||
cmd!("album.removePhotos", "Remove from Album", [], None, "{id: albumId, ids?}", has_selection, |s, p| {
|
||||
@@ -1103,7 +1107,7 @@ pub fn specs() -> Vec<CommandSpec> {
|
||||
"Import Photos",
|
||||
["File"],
|
||||
Some("Cmd+Shift+I"),
|
||||
"{paths: [file or folder (recursive)], mode?: add|copy|move (add = reference the files in place; copy = into the library's Originals/YYYY/YYYY-MM-DD/; move = as copy, then each original and its XMP sidecars are removed from the source — only after the copy is verified (a hard link on the same volume, else copied, synced and compared byte for byte) and its catalog record is saved; failed, duplicate and unchecked files keep their sources; a taken name gets -1, -2…; undo removes the photos from the library but leaves the files at the destination), destination?: folder for copies / moves, organize?: date (YYYY/YYYY-MM-DD) | month (YYYY/YYYY-MM) | flat | a folder template, e.g. `{date:%Y}/{date:%Y%m%d}` → 2026/20260114 (the template's `/` make the folders, each level expanded with the rename tokens and made a safe folder name: never outside the destination; must be relative, no `..`; a level with missing metadata is `unknown`) — dated by capture time, else the import time, rename?: file-name template for copies, original extension added (tokens: {name} {num} {seq} {seq:N} {date} {date:%Y%m%d} {folder} {camera} {lens} {iso} {rating} {title} {creator} {ext}; photo.renameTokens explains each; blank = keep names), renameStart?: 1, metadataPreset?: name, dng?: bool (copy raws as DNG; copy only), local?: bool (browsing: the photos stay out of the library, like library.browse; not with move), album?: albumId, albumName?: new album, preset?: presetId, keywords?: [..], onDeleted?: skip|restore|fresh (a file that is in Recently Deleted: skip = leave it there and say so, restore = bring the photo back with its edits, fresh = delete the trashed record and import the file as new)} → {imported, duplicates: [{path, existing, reason, existingDeleted?}], restored?: [photoId], failed, warnings?: [{path, code: cameraProfile|uprightGeometry|unmappedXmp, profile?, fields?}], moved?: [{from, to, sidecars?}], kept?: [{path, reason}] (move: sources left in place and why), album?}",
|
||||
"{paths: [file or folder (recursive)], mode?: add|copy|move (add = reference the files in place; copy = into the library's Originals/YYYY/YYYY-MM-DD/; move = as copy, then each original and its XMP sidecars are removed from the source — only after the copy is verified (a hard link on the same volume, else copied, synced and compared byte for byte) and its catalog record is saved; failed, duplicate and unchecked files keep their sources; a taken name gets -1, -2…; undo removes the photos from the library but leaves the files at the destination), destination?: folder for copies / moves, organize?: date (YYYY/YYYY-MM-DD) | month (YYYY/YYYY-MM) | flat | a folder template, e.g. `{date:%Y}/{date:%Y%m%d}` → 2026/20260114 (the template's `/` make the folders, each level expanded with the rename tokens and made a safe folder name: never outside the destination; must be relative, no `..`; a level with missing metadata is `unknown`) — dated by capture time, else the import time, rename?: file-name template for copies, original extension added (tokens: {name} {num} {seq} {seq:N} {date} {date:%Y%m%d} {folder} {camera} {lens} {iso} {rating} {title} {creator} {ext}; photo.renameTokens explains each; blank = keep names), renameStart?: 1, metadataPreset?: name, dng?: bool (copy raws as DNG; copy only), local?: bool (browsing: the photos stay out of the library, like library.browse; not with move), album?: albumId, albumName?: new album, preset?: presetId, keywords?: [..], onDeleted?: skip|restore|fresh (a file that is in Recently Deleted: skip = leave it there and say so, restore = bring the photo back with its edits, fresh = delete the trashed record and import the file as new)} → {imported, duplicates: [{path, existing, reason, existingDeleted?}], restored?: [photoId], failed, warnings?: [{path, code: cameraProfile|uprightGeometry|unmappedXmp, profile?, mode?: off|auto|level|vertical|full|guided|unknown, fields?}], moved?: [{from, to, sidecars?}], kept?: [{path, reason}] (move: sources left in place and why), album?}",
|
||||
always,
|
||||
|s, p| {
|
||||
let req = import_params(s, p)?;
|
||||
|
||||
@@ -35,11 +35,13 @@ fn read(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
let mut ops = Vec::new();
|
||||
let mut read = Vec::new();
|
||||
let mut failed = Vec::new();
|
||||
let mut warnings = Vec::new();
|
||||
for id in s.targets(p) {
|
||||
match s.read_sidecar_op(id) {
|
||||
Ok(Some((op, from))) => {
|
||||
match s.read_sidecar_op_with_warnings(id) {
|
||||
Ok(Some((op, from, mut photo_warnings))) => {
|
||||
ops.push(op);
|
||||
read.push(json!({"id": id.0, "from": from.display().to_string()}));
|
||||
warnings.append(&mut photo_warnings);
|
||||
}
|
||||
Ok(None) => failed.push(json!({"id": id.0, "error": "no XMP sidecar or embedded XMP"})),
|
||||
Err(e) => failed.push(json!({"id": id.0, "error": e.to_string()})),
|
||||
@@ -48,7 +50,11 @@ fn read(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
if !ops.is_empty() {
|
||||
s.commit("Read Metadata from File", Op::Batch { ops })?;
|
||||
}
|
||||
Ok(json!({"read": read, "failed": failed}))
|
||||
let mut result = json!({"read": read, "failed": failed});
|
||||
if !warnings.is_empty() {
|
||||
result["warnings"] = serde_json::to_value(warnings).unwrap_or_default();
|
||||
}
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
fn prefs(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
@@ -84,7 +90,7 @@ pub fn specs() -> Vec<CommandSpec> {
|
||||
"Read Metadata from File",
|
||||
["Photo"],
|
||||
None,
|
||||
"{ids?} — reads each photo's XMP sidecar (or a raw/DNG's embedded XMP): metadata and develop settings, one undo step → {read, failed}",
|
||||
"{ids?} — reads each photo's XMP sidecar (or a raw/DNG's embedded XMP): metadata and develop settings, one undo step → {read, failed, warnings?: [{path, code, profile?, mode?, fields?}]}",
|
||||
has_selection,
|
||||
read
|
||||
),
|
||||
|
||||
@@ -215,6 +215,19 @@ pub enum ImportWarningCode {
|
||||
UnmappedXmp,
|
||||
}
|
||||
|
||||
/// The saved Lightroom Upright mode associated with a foreign geometry transform.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub enum ImportUprightMode {
|
||||
Off,
|
||||
Auto,
|
||||
Level,
|
||||
Vertical,
|
||||
Full,
|
||||
Guided,
|
||||
Unknown,
|
||||
}
|
||||
|
||||
/// One actionable XMP import diagnostic. `path` is filled when its photo is committed.
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
@@ -223,6 +236,8 @@ pub struct ImportWarning {
|
||||
pub code: ImportWarningCode,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub profile: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub mode: Option<ImportUprightMode>,
|
||||
#[serde(skip_serializing_if = "Vec::is_empty")]
|
||||
pub fields: Vec<String>,
|
||||
}
|
||||
|
||||
@@ -172,15 +172,32 @@ pub fn parse_sidecar(xmp: &str, raw: bool) -> std::result::Result<SidecarData, S
|
||||
path: String::new(),
|
||||
code: crate::import::ImportWarningCode::CameraProfile,
|
||||
profile: Some(name),
|
||||
mode: None,
|
||||
fields: Vec::new(),
|
||||
});
|
||||
}
|
||||
let upright = crate::crs::has_saved_upright_transform(&d.properties);
|
||||
if upright {
|
||||
let mode = d
|
||||
.properties
|
||||
.get("crs:PerspectiveUpright")
|
||||
.and_then(|values| values.first())
|
||||
.map(String::as_str)
|
||||
.map(|mode| match mode {
|
||||
"0" => crate::import::ImportUprightMode::Off,
|
||||
"1" => crate::import::ImportUprightMode::Auto,
|
||||
"2" => crate::import::ImportUprightMode::Level,
|
||||
"3" => crate::import::ImportUprightMode::Vertical,
|
||||
"4" => crate::import::ImportUprightMode::Full,
|
||||
"5" => crate::import::ImportUprightMode::Guided,
|
||||
_ => crate::import::ImportUprightMode::Unknown,
|
||||
})
|
||||
.unwrap_or(crate::import::ImportUprightMode::Unknown);
|
||||
out.warnings.push(crate::import::ImportWarning {
|
||||
path: String::new(),
|
||||
code: crate::import::ImportWarningCode::UprightGeometry,
|
||||
profile: None,
|
||||
mode: Some(mode),
|
||||
fields: Vec::new(),
|
||||
});
|
||||
}
|
||||
@@ -198,6 +215,7 @@ pub fn parse_sidecar(xmp: &str, raw: bool) -> std::result::Result<SidecarData, S
|
||||
path: String::new(),
|
||||
code: crate::import::ImportWarningCode::UnmappedXmp,
|
||||
profile: None,
|
||||
mode: None,
|
||||
fields: other.into_iter().map(str::to_string).collect(),
|
||||
});
|
||||
}
|
||||
@@ -511,12 +529,21 @@ impl Session {
|
||||
|
||||
/// The op that applies a photo's sidecar (or embedded XMP) to the catalog, if there is one.
|
||||
pub fn read_sidecar_op(&self, id: PhotoId) -> Result<Option<(Op, PathBuf)>> {
|
||||
self.read_sidecar_op_with_warnings(id).map(|result| result.map(|(op, from, _)| (op, from)))
|
||||
}
|
||||
|
||||
/// Like [`Session::read_sidecar_op`], retaining transient diagnostics for the command report.
|
||||
pub fn read_sidecar_op_with_warnings(&self, id: PhotoId) -> Result<Option<(Op, PathBuf, Vec<crate::import::ImportWarning>)>> {
|
||||
let p = self.catalog.photo(id).ok_or(lightcraft_catalog::CatalogError::NoPhoto(id))?;
|
||||
let Some(orig) = file_path(p) else { return Ok(None) };
|
||||
let Some((packet, from)) = read_packet(orig, p.kind, self.sidecar_naming(id)) else { return Ok(None) };
|
||||
let sc = parse_sidecar(&packet, p.kind == MediaKind::Raw)
|
||||
.map_err(|e| EngineError::Other(format!("{}: {e}", from.display())))?
|
||||
.resolve_label(&self.catalog);
|
||||
let mut warnings = sc.warnings.clone();
|
||||
for warning in &mut warnings {
|
||||
warning.path = orig.to_string();
|
||||
}
|
||||
let mut q = (**p).clone();
|
||||
let develop_changed = merge_into(&mut q, &sc, &(self.clock)());
|
||||
let mut ops = vec![
|
||||
@@ -531,7 +558,7 @@ impl Session {
|
||||
if develop_changed {
|
||||
ops.extend(self.develop_op(id, (*q.develop).clone(), "Read Metadata from File"));
|
||||
}
|
||||
Ok(Some((Op::Batch { ops }, from)))
|
||||
Ok(Some((Op::Batch { ops }, from, warnings)))
|
||||
}
|
||||
|
||||
/// Auto-write: sidecars for photos changed by `ops` (errors are logged, not returned).
|
||||
|
||||
@@ -395,11 +395,17 @@ fn auto_import_watched_folder() {
|
||||
s.execute("library.autoImport", &json!({"folder": dir.to_string_lossy(), "album": "Tethered"})).unwrap();
|
||||
assert_eq!(s.execute("library.autoImportScan", &json!({})).unwrap()["imported"], json!([]));
|
||||
write_png(&dir.join("one.png"), 1);
|
||||
std::fs::write(
|
||||
dir.join("one.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 xmlns:crs="http://ns.adobe.com/camera-raw-settings/1.0/" crs:CameraProfile="Color" crs:Exposure2012="0.5"/></rdf:RDF></x:xmpmeta>"#,
|
||||
)
|
||||
.unwrap();
|
||||
std::fs::write(dir.join("notes.txt"), "not a photo").unwrap();
|
||||
// first sight: wait (it may still be copying)
|
||||
assert_eq!(s.execute("library.autoImportScan", &json!({})).unwrap()["imported"], json!([]));
|
||||
let r = s.execute("library.autoImportScan", &json!({})).unwrap();
|
||||
assert_eq!(r["imported"].as_array().unwrap().len(), 1, "{r}");
|
||||
assert!(r["warnings"].as_array().unwrap().iter().any(|w| w["code"] == "cameraProfile" && w["profile"] == "Color"), "{r}");
|
||||
let album = s.catalog.albums().find(|a| a.name == "Tethered").expect("album").id;
|
||||
assert_eq!(s.catalog.album_count(album), 1);
|
||||
assert!(s.selection.ids.is_empty(), "arrivals don't take the selection");
|
||||
|
||||
@@ -170,6 +170,7 @@ fn foreign_crs_sidecar_on_import_and_read_from_file() {
|
||||
std::fs::remove_file(src.join("harbour.xmp")).unwrap();
|
||||
let r = s.execute("photo.readMetadataFromFile", &json!({})).unwrap();
|
||||
assert_eq!(r["failed"].as_array().unwrap().len(), 1);
|
||||
assert!(r.get("warnings").is_none(), "{r}");
|
||||
let _ = std::fs::remove_dir_all(&src);
|
||||
}
|
||||
|
||||
@@ -195,12 +196,43 @@ fn foreign_profile_and_upright_import_diagnostics_are_actionable() {
|
||||
let r = s.execute("library.import", &json!({"paths": [photo.to_string_lossy()]})).unwrap();
|
||||
let warnings = r["warnings"].as_array().unwrap();
|
||||
assert!(warnings.iter().any(|w| w["code"] == "cameraProfile" && w["profile"] == "Camera Faithful"), "{r}");
|
||||
assert!(warnings.iter().any(|w| w["code"] == "uprightGeometry"), "{r}");
|
||||
let p = s.catalog.photos().next().unwrap();
|
||||
assert!(warnings.iter().any(|w| w["code"] == "uprightGeometry" && w["mode"] == "auto"), "{r}");
|
||||
let p = s.catalog.photos().next().unwrap().clone();
|
||||
assert_eq!(p.develop.profile.id, "lc.color");
|
||||
assert_eq!(p.develop.light.exposure, 1.68);
|
||||
assert_eq!(std::fs::read(&sidecar).unwrap(), before, "import never rewrites foreign XMP");
|
||||
|
||||
// Metadata reread carries same structured diagnostics, with each saved Upright mode typed.
|
||||
let photo_path = photo.to_string_lossy().to_string();
|
||||
let reread = s.execute("photo.readMetadataFromFile", &json!({"ids": [p.id.0]})).unwrap();
|
||||
assert!(
|
||||
reread["warnings"]
|
||||
.as_array()
|
||||
.unwrap()
|
||||
.iter()
|
||||
.any(|w| w["code"] == "cameraProfile" && w["profile"] == "Camera Faithful" && w["path"] == photo_path),
|
||||
"{reread}"
|
||||
);
|
||||
assert!(reread["warnings"].as_array().unwrap().iter().any(|w| w["code"] == "uprightGeometry" && w["mode"] == "auto"), "{reread}");
|
||||
for (source, mode) in [("0", "off"), ("2", "level"), ("3", "vertical"), ("4", "full"), ("5", "guided"), ("9", "unknown")] {
|
||||
let mode_xmp = xmp.replace("crs:PerspectiveUpright=\"1\"", &format!("crs:PerspectiveUpright=\"{source}\""));
|
||||
std::fs::write(&sidecar, mode_xmp).unwrap();
|
||||
let reread = s.execute("photo.readMetadataFromFile", &json!({"ids": [p.id.0]})).unwrap();
|
||||
assert!(reread["warnings"].as_array().unwrap().iter().any(|w| w["code"] == "uprightGeometry" && w["mode"] == mode), "{source}: {reread}");
|
||||
}
|
||||
|
||||
// A foreign name colliding with a built-in profile label remains warning metadata only.
|
||||
let collision = src.join("collision.png");
|
||||
write_png(&collision, 8);
|
||||
std::fs::write(src.join("collision.xmp"), xmp.replace("Camera Faithful", "Color")).unwrap();
|
||||
let collision_report = s.execute("library.import", &json!({"paths": [collision.to_string_lossy()]})).unwrap();
|
||||
assert!(
|
||||
collision_report["warnings"].as_array().unwrap().iter().any(|w| w["code"] == "cameraProfile" && w["profile"] == "Color"),
|
||||
"{collision_report}"
|
||||
);
|
||||
let collision_photo = s.catalog.photos().find(|photo| photo.file_name == "collision.png").unwrap();
|
||||
assert_eq!(collision_photo.develop.profile.id, "lc.color");
|
||||
|
||||
// A second import still diagnoses the untouched foreign packet, while an XMP-free photo does not.
|
||||
drop(s);
|
||||
let clean = src.join("clean.png");
|
||||
|
||||
@@ -62,7 +62,10 @@
|
||||
"Import failed: {e}": "Import fehlgeschlagen: {e}",
|
||||
"Import warnings ({n}):\n{warnings}": "Importwarnungen ({n}):\n{warnings}",
|
||||
"XMP profile \"{profile}\" was not applied for {path}; colors may differ from Lightroom.": "XMP-Profil \"{profile}\" wurde für {path} nicht angewendet; Farben können von Lightroom abweichen.",
|
||||
"Lightroom geometry was not preserved for {path}; Upright will be recalculated.": "Lightroom-Geometrie wurde für {path} nicht beibehalten; Upright wird neu berechnet.",
|
||||
"Lightroom geometry was not preserved for {path}; {mode} will be recalculated.": "Lightroom-Geometrie wurde für {path} nicht beibehalten; {mode} wird neu berechnet.",
|
||||
"Lightroom geometry for {path} was not preserved; saved transform is ignored because Upright is Off.": "Lightroom-Geometrie für {path} wurde nicht beibehalten; gespeicherte Transformation wird ignoriert, weil Upright deaktiviert ist.",
|
||||
"Lightroom geometry for {path} was not preserved; Guided Upright saved transform is unsupported.": "Lightroom-Geometrie für {path} wurde nicht beibehalten; gespeicherte Guided-Upright-Transformation wird nicht unterstützt.",
|
||||
"Lightroom geometry for {path} was not preserved; saved Upright transform is unsupported.": "Lightroom-Geometrie für {path} wurde nicht beibehalten; gespeicherte Upright-Transformation wird nicht unterstützt.",
|
||||
"XMP adjustments were not imported for {path}: {fields}.": "XMP-Anpassungen wurden für {path} nicht importiert: {fields}.",
|
||||
"Imported {n} point curve preset{}": "Importierte Punktkurvenvorgaben: {n}{:.0}",
|
||||
"Imported {n} preset{}": "Importierte Vorgaben: {n}{:.0}",
|
||||
|
||||
@@ -62,7 +62,10 @@
|
||||
"Import failed: {e}": "Error de importación: {e}",
|
||||
"Import warnings ({n}):\n{warnings}": "Avisos de importación ({n}):\n{warnings}",
|
||||
"XMP profile \"{profile}\" was not applied for {path}; colors may differ from Lightroom.": "El perfil XMP \"{profile}\" no se aplicó a {path}; los colores pueden diferir de Lightroom.",
|
||||
"Lightroom geometry was not preserved for {path}; Upright will be recalculated.": "La geometría de Lightroom no se conservó para {path}; Upright se recalculará.",
|
||||
"Lightroom geometry was not preserved for {path}; {mode} will be recalculated.": "La geometría de Lightroom no se conservó para {path}; {mode} se recalculará.",
|
||||
"Lightroom geometry for {path} was not preserved; saved transform is ignored because Upright is Off.": "La geometría de Lightroom no se conservó para {path}; la transformación guardada se ignora porque Upright está desactivado.",
|
||||
"Lightroom geometry for {path} was not preserved; Guided Upright saved transform is unsupported.": "La geometría de Lightroom no se conservó para {path}; la transformación guardada de Upright guiado no es compatible.",
|
||||
"Lightroom geometry for {path} was not preserved; saved Upright transform is unsupported.": "La geometría de Lightroom no se conservó para {path}; la transformación guardada de Upright no es compatible.",
|
||||
"XMP adjustments were not imported for {path}: {fields}.": "Los ajustes XMP no se importaron para {path}: {fields}.",
|
||||
"Imported {n} point curve preset{}": "{n} ajuste{0} preestablecido{0} de curva de puntos importado{0}",
|
||||
"Imported {n} preset{}": "{n} ajuste{0} preestablecido{0} importado{0}",
|
||||
|
||||
@@ -155,7 +155,10 @@
|
||||
"Import failed: {e}": "読み込みに失敗しました:{e}",
|
||||
"Import warnings ({n}):\n{warnings}": "読み込みの警告({n}):\n{warnings}",
|
||||
"XMP profile \"{profile}\" was not applied for {path}; colors may differ from Lightroom.": "{path} に XMP プロファイル \"{profile}\" を適用できませんでした。色は Lightroom と異なる場合があります。",
|
||||
"Lightroom geometry was not preserved for {path}; Upright will be recalculated.": "{path} の Lightroom ジオメトリは保持されませんでした。Upright を再計算します。",
|
||||
"Lightroom geometry was not preserved for {path}; {mode} will be recalculated.": "{path} の Lightroom ジオメトリは保持されませんでした。{mode} を再計算します。",
|
||||
"Lightroom geometry for {path} was not preserved; saved transform is ignored because Upright is Off.": "{path} の Lightroom ジオメトリは保持されませんでした。Upright がオフのため保存された変換は無視されます。",
|
||||
"Lightroom geometry for {path} was not preserved; Guided Upright saved transform is unsupported.": "{path} の Lightroom ジオメトリは保持されませんでした。ガイド付き Upright の保存された変換はサポートされていません。",
|
||||
"Lightroom geometry for {path} was not preserved; saved Upright transform is unsupported.": "{path} の Lightroom ジオメトリは保持されませんでした。保存された Upright の変換はサポートされていません。",
|
||||
"XMP adjustments were not imported for {path}: {fields}.": "{path} の XMP 調整は読み込まれませんでした:{fields}。",
|
||||
"Imported {profiles} profile{} (Profile browser ▸ their groups)": "{profiles}個のプロファイルを読み込みました{:.0}(プロファイルブラウザー → 各グループ)",
|
||||
"Insert at the cursor (also written {})": "カーソル位置に挿入({}とも表記可)",
|
||||
|
||||
@@ -155,7 +155,10 @@
|
||||
"Import failed: {e}": "Falha na importação: {e}",
|
||||
"Import warnings ({n}):\n{warnings}": "Avisos de importação ({n}):\n{warnings}",
|
||||
"XMP profile \"{profile}\" was not applied for {path}; colors may differ from Lightroom.": "O perfil XMP \"{profile}\" não foi aplicado a {path}; as cores podem diferir do Lightroom.",
|
||||
"Lightroom geometry was not preserved for {path}; Upright will be recalculated.": "A geometria do Lightroom não foi preservada para {path}; o Upright será recalculado.",
|
||||
"Lightroom geometry was not preserved for {path}; {mode} will be recalculated.": "A geometria do Lightroom não foi preservada para {path}; {mode} será recalculado.",
|
||||
"Lightroom geometry for {path} was not preserved; saved transform is ignored because Upright is Off.": "A geometria do Lightroom não foi preservada para {path}; a transformação salva é ignorada porque o Upright está desativado.",
|
||||
"Lightroom geometry for {path} was not preserved; Guided Upright saved transform is unsupported.": "A geometria do Lightroom não foi preservada para {path}; a transformação salva do Upright guiado não é compatível.",
|
||||
"Lightroom geometry for {path} was not preserved; saved Upright transform is unsupported.": "A geometria do Lightroom não foi preservada para {path}; a transformação salva do Upright não é compatível.",
|
||||
"XMP adjustments were not imported for {path}: {fields}.": "Os ajustes XMP não foram importados para {path}: {fields}.",
|
||||
"Imported {profiles} profile{} (Profile browser ▸ their groups)": "{profiles} perfil(is) importado(s){:.0} (Navegador de perfis → seus grupos)",
|
||||
"Insert at the cursor (also written {})": "Inserir no cursor (também escrito {})",
|
||||
|
||||
@@ -65,7 +65,10 @@
|
||||
"Import failed: {e}": "Ошибка импорта: {e}",
|
||||
"Import warnings ({n}):\n{warnings}": "Предупреждения импорта ({n}):\n{warnings}",
|
||||
"XMP profile \"{profile}\" was not applied for {path}; colors may differ from Lightroom.": "Профиль XMP \"{profile}\" не применён к {path}; цвета могут отличаться от Lightroom.",
|
||||
"Lightroom geometry was not preserved for {path}; Upright will be recalculated.": "Геометрия Lightroom для {path} не сохранена; Upright будет пересчитан.",
|
||||
"Lightroom geometry was not preserved for {path}; {mode} will be recalculated.": "Геометрия Lightroom для {path} не сохранена; режим {mode} будет пересчитан.",
|
||||
"Lightroom geometry for {path} was not preserved; saved transform is ignored because Upright is Off.": "Геометрия Lightroom для {path} не сохранена; сохранённое преобразование игнорируется, поскольку Upright выключен.",
|
||||
"Lightroom geometry for {path} was not preserved; Guided Upright saved transform is unsupported.": "Геометрия Lightroom для {path} не сохранена; сохранённое преобразование Guided Upright не поддерживается.",
|
||||
"Lightroom geometry for {path} was not preserved; saved Upright transform is unsupported.": "Геометрия Lightroom для {path} не сохранена; сохранённое преобразование Upright не поддерживается.",
|
||||
"XMP adjustments were not imported for {path}: {fields}.": "Коррекции XMP для {path} не импортированы: {fields}.",
|
||||
"Imported {n} point curve preset{}": "Импортировано пресетов точечной кривой: {n}{:.0}",
|
||||
"Imported {n} preset{}": "Импортировано пресетов: {n}{:.0}",
|
||||
|
||||
@@ -155,7 +155,10 @@
|
||||
"Import failed: {e}": "导入失败:{e}",
|
||||
"Import warnings ({n}):\n{warnings}": "导入警告({n}):\n{warnings}",
|
||||
"XMP profile \"{profile}\" was not applied for {path}; colors may differ from Lightroom.": "未对 {path} 应用 XMP 配置文件“{profile}”;颜色可能与 Lightroom 不同。",
|
||||
"Lightroom geometry was not preserved for {path}; Upright will be recalculated.": "未保留 {path} 的 Lightroom 几何信息;将重新计算 Upright。",
|
||||
"Lightroom geometry was not preserved for {path}; {mode} will be recalculated.": "未保留 {path} 的 Lightroom 几何信息;将重新计算 {mode}。",
|
||||
"Lightroom geometry for {path} was not preserved; saved transform is ignored because Upright is Off.": "未保留 {path} 的 Lightroom 几何信息;由于 Upright 已关闭,将忽略保存的变换。",
|
||||
"Lightroom geometry for {path} was not preserved; Guided Upright saved transform is unsupported.": "未保留 {path} 的 Lightroom 几何信息;不支持保存的引导 Upright 变换。",
|
||||
"Lightroom geometry for {path} was not preserved; saved Upright transform is unsupported.": "未保留 {path} 的 Lightroom 几何信息;不支持保存的 Upright 变换。",
|
||||
"XMP adjustments were not imported for {path}: {fields}.": "未导入 {path} 的 XMP 调整:{fields}。",
|
||||
"Imported {profiles} profile{} (Profile browser ▸ their groups)": "已导入 {profiles} 个配置文件{0:.0}(配置文件浏览器 → 各自的分组)",
|
||||
"Insert at the cursor (also written {})": "在光标处插入(也可写作 {0})",
|
||||
|
||||
@@ -155,7 +155,10 @@
|
||||
"Import failed: {e}": "匯入失敗:{e}",
|
||||
"Import warnings ({n}):\n{warnings}": "匯入警告({n}):\n{warnings}",
|
||||
"XMP profile \"{profile}\" was not applied for {path}; colors may differ from Lightroom.": "未將 XMP 描述檔「{profile}」套用至 {path};色彩可能與 Lightroom 不同。",
|
||||
"Lightroom geometry was not preserved for {path}; Upright will be recalculated.": "未保留 {path} 的 Lightroom 幾何資料;將重新計算 Upright。",
|
||||
"Lightroom geometry was not preserved for {path}; {mode} will be recalculated.": "未保留 {path} 的 Lightroom 幾何資料;將重新計算 {mode}。",
|
||||
"Lightroom geometry for {path} was not preserved; saved transform is ignored because Upright is Off.": "未保留 {path} 的 Lightroom 幾何資料;Upright 已關閉,因此忽略儲存的轉換。",
|
||||
"Lightroom geometry for {path} was not preserved; Guided Upright saved transform is unsupported.": "未保留 {path} 的 Lightroom 幾何資料;不支援儲存的引導 Upright 轉換。",
|
||||
"Lightroom geometry for {path} was not preserved; saved Upright transform is unsupported.": "未保留 {path} 的 Lightroom 幾何資料;不支援儲存的 Upright 轉換。",
|
||||
"XMP adjustments were not imported for {path}: {fields}.": "未匯入 {path} 的 XMP 調整:{fields}。",
|
||||
"Imported {profiles} profile{} (Profile browser ▸ their groups)": "已匯入 {profiles} 個描述檔{:.0}(描述檔瀏覽器 → 各自的群組)",
|
||||
"Insert at the cursor (also written {})": "在游標處插入(也可寫成 {})",
|
||||
|
||||
@@ -8,7 +8,9 @@
|
||||
//! catalog between frames, with a progress window and Cancel; the whole import is one undo step.
|
||||
|
||||
use egui::{Align2, Color32, Rect, Sense, Stroke, StrokeKind, pos2, vec2};
|
||||
use lightcraft_engine::import::{ImportCandidate, ImportWarning, ImportWarningCode, ScanInput, ScanOutput, ScanProgress, scan_with};
|
||||
use lightcraft_engine::import::{
|
||||
ImportCandidate, ImportUprightMode, ImportWarning, ImportWarningCode, ScanInput, ScanOutput, ScanProgress, scan_with,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::{Value, json};
|
||||
use std::sync::atomic::Ordering;
|
||||
@@ -718,9 +720,39 @@ fn xmp_warning_text(warning: &ImportWarning) -> String {
|
||||
profile = warning.profile.as_deref().unwrap_or_default(),
|
||||
path = path,
|
||||
),
|
||||
ImportWarningCode::UprightGeometry => {
|
||||
crate::i18n::tr_format!("Lightroom geometry was not preserved for {path}; Upright will be recalculated.", path = path,)
|
||||
}
|
||||
ImportWarningCode::UprightGeometry => match warning.mode.unwrap_or(ImportUprightMode::Unknown) {
|
||||
ImportUprightMode::Off => crate::i18n::tr_format!(
|
||||
"Lightroom geometry for {path} was not preserved; saved transform is ignored because Upright is Off.",
|
||||
path = path,
|
||||
),
|
||||
ImportUprightMode::Guided => crate::i18n::tr_format!(
|
||||
"Lightroom geometry for {path} was not preserved; Guided Upright saved transform is unsupported.",
|
||||
path = path,
|
||||
),
|
||||
ImportUprightMode::Auto => crate::i18n::tr_format!(
|
||||
"Lightroom geometry was not preserved for {path}; {mode} will be recalculated.",
|
||||
path = path,
|
||||
mode = crate::i18n::tr("Auto")
|
||||
),
|
||||
ImportUprightMode::Level => crate::i18n::tr_format!(
|
||||
"Lightroom geometry was not preserved for {path}; {mode} will be recalculated.",
|
||||
path = path,
|
||||
mode = crate::i18n::tr("Level")
|
||||
),
|
||||
ImportUprightMode::Vertical => crate::i18n::tr_format!(
|
||||
"Lightroom geometry was not preserved for {path}; {mode} will be recalculated.",
|
||||
path = path,
|
||||
mode = crate::i18n::tr("Vertical")
|
||||
),
|
||||
ImportUprightMode::Full => crate::i18n::tr_format!(
|
||||
"Lightroom geometry was not preserved for {path}; {mode} will be recalculated.",
|
||||
path = path,
|
||||
mode = crate::i18n::tr("Full")
|
||||
),
|
||||
ImportUprightMode::Unknown => {
|
||||
crate::i18n::tr_format!("Lightroom geometry for {path} was not preserved; saved Upright transform is unsupported.", path = path,)
|
||||
}
|
||||
},
|
||||
ImportWarningCode::UnmappedXmp => {
|
||||
crate::i18n::tr_format!("XMP adjustments were not imported for {path}: {fields}.", path = path, fields = warning.fields.join(", "),)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user