mirror of
https://github.com/storytold/lightcraft.git
synced 2026-10-27 19:47:35 -06:00
- crates/engine/src/sidecar.rs: <stem>.xmp (default) or <file>.xmp next to originals with rating,
label, title, caption, copyright, creator, keywords, capture time/GPS, pick flag + full develop
settings in lc:settings; atomic writes; read on import (sidecar wins for metadata, edits restored)
and via photo.readMetadataFromFile (one undo step); raw/DNG embedded XMP used without a sidecar.
- Commands: photo.saveMetadataToFile (Cmd+S), photo.readMetadataFromFile, library.xmpPreferences
{autoWrite, naming}, library.toggleAutoWriteXmp; prefs persisted in the library's prefs.json.
Auto-write rewrites sidecars after each committed change (not mid-drag; undo/redo too).
- crates/engine/src/crs.rs: crs: develop fields (PV2012 light, WB abs/incremental, presence, HSL,
B&W mix, parametric + point curves, colour grading, sharpening, NR, vignette, grain, optics,
geometry, crop) → partial settings; documented in docs/xmp-interop.md.
- meta: write_xmp_lc for extra lc: properties; crs namespace canonicalised on read (never written).
- Tests: packet round trip, crs mapping with hand-written snippets, save → delete library →
re-import restores edits, auto-write + prefs persistence, foreign sidecar, DNG-embedded XMP.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
94 lines
3.2 KiB
Rust
94 lines
3.2 KiB
Rust
//! XMP sidecar commands: save/read metadata to/from files, sidecar preferences.
|
|
|
|
use lightcraft_catalog::Op;
|
|
use serde_json::{Value, json};
|
|
|
|
use super::{CommandSpec, always, bad, cmd, has_selection, str_param};
|
|
use crate::sidecar::SidecarNaming;
|
|
use crate::{Result, Session};
|
|
|
|
fn save(s: &mut Session, p: &Value) -> Result<Value> {
|
|
let mut written = Vec::new();
|
|
let mut failed = Vec::new();
|
|
for id in s.targets(p) {
|
|
match s.save_sidecar(id) {
|
|
Ok(path) => written.push(path.display().to_string()),
|
|
Err(e) => failed.push(json!({"id": id.0, "error": e.to_string()})),
|
|
}
|
|
}
|
|
Ok(json!({"written": written, "failed": failed}))
|
|
}
|
|
|
|
fn read(s: &mut Session, p: &Value) -> Result<Value> {
|
|
let mut ops = Vec::new();
|
|
let mut read = Vec::new();
|
|
let mut failed = Vec::new();
|
|
for id in s.targets(p) {
|
|
match s.read_sidecar_op(id) {
|
|
Ok(Some((op, from))) => {
|
|
ops.push(op);
|
|
read.push(json!({"id": id.0, "from": from.display().to_string()}));
|
|
}
|
|
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()})),
|
|
}
|
|
}
|
|
if !ops.is_empty() {
|
|
s.commit("Read Metadata from File", Op::Batch { ops })?;
|
|
}
|
|
Ok(json!({"read": read, "failed": failed}))
|
|
}
|
|
|
|
fn prefs(s: &mut Session, p: &Value) -> Result<Value> {
|
|
if let Some(a) = p.get("autoWrite").and_then(Value::as_bool) {
|
|
s.xmp.auto_write = a;
|
|
}
|
|
if let Some(n) = str_param(p, "naming") {
|
|
s.xmp.naming = match n {
|
|
"stem" => SidecarNaming::Stem,
|
|
"full" => SidecarNaming::Full,
|
|
other => return Err(bad("library.xmpPreferences", format!("unknown naming `{other}` (stem|full)"))),
|
|
};
|
|
}
|
|
if p.as_object().is_some_and(|o| !o.is_empty()) {
|
|
s.save_prefs()?;
|
|
}
|
|
Ok(serde_json::to_value(s.xmp).unwrap_or_default())
|
|
}
|
|
|
|
pub fn specs() -> Vec<CommandSpec> {
|
|
vec![
|
|
cmd!(
|
|
"photo.saveMetadataToFile",
|
|
"Save Metadata to File",
|
|
["Photo"],
|
|
Some("Cmd+S"),
|
|
"{ids?} — writes each photo's XMP sidecar (metadata + develop settings) next to the original → {written: [path], failed}",
|
|
has_selection,
|
|
save
|
|
),
|
|
cmd!(
|
|
"photo.readMetadataFromFile",
|
|
"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}",
|
|
has_selection,
|
|
read
|
|
),
|
|
cmd!(
|
|
"library.xmpPreferences",
|
|
"XMP Sidecar Preferences",
|
|
[],
|
|
None,
|
|
"{autoWrite?: bool, naming?: stem (IMG_1.xmp, default) | full (IMG_1.CR3.xmp)} → current preferences (saved with the library)",
|
|
always,
|
|
prefs
|
|
),
|
|
cmd!("library.toggleAutoWriteXmp", "Automatically Write Changes into XMP", ["File"], None, "{} → {autoWrite}", always, |s, _| {
|
|
let on = !s.xmp.auto_write;
|
|
prefs(s, &json!({"autoWrite": on}))
|
|
}),
|
|
]
|
|
}
|