diff --git a/crates/engine/src/crs.rs b/crates/engine/src/crs.rs index 9de8c3d64..093f3a84f 100644 --- a/crates/engine/src/crs.rs +++ b/crates/engine/src/crs.rs @@ -93,6 +93,17 @@ const NON_ADJUSTMENT: &[&str] = &[ // the file's as-shot white, recorded next to the edit (ours comes from the file itself) "crs:AsShotTemperature", "crs:AsShotTint", + // which Lightroom version may read the edit, and checksums of the profiles it names + "crs:CompatibleVersion", + "crs:CameraProfileDigest", + "crs:LensProfileDigest", + // the lens profile Lightroom picked: ours is the file's own, only `LensProfileEnable` carries over + "crs:LensProfileName", + "crs:LensProfileFilename", + "crs:LensProfileIsEmbedded", + "crs:LensProfileSetup", + // a crop tool option (keep the crop inside the photo), not the crop itself + "crs:CropConstrainToUnitSquare", ]; /// True if the packet carries any `crs:` adjustment (not just bookkeeping fields). @@ -492,6 +503,8 @@ pub fn to_partial_report(props: &Props, values: Option<&crate::crs_masks::Values || k == "AutoTone" || k == "AutoGrayscaleMix" || (k == "PointColors" && value_is(k, &empty_points)) + // the lens profile's strength, written at its default (100 %) next to every profile + || (matches!(k, "LensProfileDistortionScale" | "LensProfileVignettingScale") && value_is(k, &|v| v.iter().all(|s| s.trim() == "100"))) }; let mut unmapped: Vec = props .keys() @@ -869,6 +882,21 @@ mod tests { assert_eq!(got, ["FutureSlider", "PointColors"]); } + #[test] + fn lightroom_profile_bookkeeping_is_not_reported() { + // what Lightroom Classic writes next to every raw's edit: none of it changes the rendering + let bookkeeping = r#"crs:CompatibleVersion="268435456" crs:CameraProfileDigest="0A1B" crs:LensProfileDigest="2C3D" + crs:LensProfileName="Sony FE 24-70mm" crs:LensProfileFilename="x.lcp" crs:LensProfileIsEmbedded="False" + crs:LensProfileSetup="LensDefaults" crs:LensProfileDistortionScale="100" crs:LensProfileVignettingScale="100" + crs:CropConstrainToUnitSquare="1""#; + let look = r#"Vivid"#; + assert_eq!(unmapped(&preset_packet(bookkeeping, "")), Vec::::new()); + assert_eq!(unmapped(&preset_packet(bookkeeping, look)), ["Look"], "the Look is a real gap"); + // a lens profile's strength away from 100 % is an adjustment we don't carry over + let scaled = preset_packet(r#"crs:LensProfileDistortionScale="60""#, ""); + assert_eq!(unmapped(&scaled), ["LensProfileDistortionScale"]); + } + #[test] fn disabled_lens_profile_switch_keeps_embedded_corrections() { // Lightroom applies a file's built-in lens corrections regardless of its profile switch, diff --git a/docs/parity-checklist.md b/docs/parity-checklist.md index 87f521286..6ae777f98 100644 --- a/docs/parity-checklist.md +++ b/docs/parity-checklist.md @@ -1,6 +1,6 @@ # Lightroom parity checklist -> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-20 · **Change:** minor (LR-VIEW-ZOOM: only a full-size drag within 20 ms clears a slow-drag verdict) · **Target:** Adobe Lightroom Classic 15.5 / Lightroom 9.6 +> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-20 · **Change:** patch (LR-META-XMP: fewer false unimported-XMP warnings) · **Target:** Adobe Lightroom Classic 15.5 / Lightroom 9.6 The measured, row-by-row checklist: one row per Lightroom feature, menu item and shortcut, what we have and what is missing. It answers *does the feature exist?* (feature breadth). Whether it is good @@ -170,7 +170,7 @@ differences and missing bindings are in [ui-parity.md](ui-parity.md#shortcuts). | LR-META-COPYRIGHT-DEFAULT | Default copyright on import | P1 | ✅ | `cmd:library.preferences` (`import.copyright`, `import.creator`), `crates/ui-egui/src/panels/settings.rs` | Settings → Import → Metadata; fills only photos without their own | | LR-META-LOCATION | Location editing | P2 | 🟡 | `cmd:photo.setMeta` (`location`, `city`, `state`, `country`, `gps`), `cmd:metadata.reverseGeocode` | place fields and GPS (decimal or degrees / minutes / seconds) edited in Info, read/written as IPTC Core / Photoshop / EXIF XMP; Show on Map opens OpenStreetMap; Fill Location fills empty City / State / Country from GPS offline (GeoNames, downloaded on request: `cmd:metadata.downloadPlaceNames`); no embedded map | | LR-META-COPYPASTE | Copy / paste metadata | P2 | ✅ | `cmd:photo.copyMetadata`, `cmd:photo.pasteMetadata` | title, caption, alt text, extended description, copyright (notice, status, usage terms, info URL), creator, place fields, keywords; `fields` picks a subset | -| LR-META-XMP | XMP read/write | P0 | ✅ | `cmd:photo.saveMetadataToFile`, `cmd:photo.readMetadataFromFile`, `cmd:library.xmpPreferences`, `crates/engine/src/sidecar.rs`, `docs/xmp-interop.md` | manual rereading also reads sidecar-free JPEG embedded XMP (first complete standard APP1 before SOS/EOI, within 16 MiB of headers and 4096 marker/fill steps; no extended assembly or between-scan packets, not strict whole-JPEG validation; reader cases in `crates/engine/src/sidecar.rs`); a discovered unreadable sidecar fails without embedded fallback; procedural refresh/undo, non-UTF-8 sidecar and two explicit malformed fixtures in `crates/engine/src/tests_xmp.rs` (file-write checks use auto-write off); sidecar wins for metadata; a sidecar capture time (`exif:DateTimeOriginal` / `photoshop:DateCreated` / `xmp:CreateDate`) fills in only when the file has none; saving merges into an existing sidecar (`crates/meta/src/xmp_merge.rs`): another app's `crs:`, `xmpMM:History` and unknown namespaces are kept byte for byte, an unreadable sidecar is backed up first; with stem naming, files sharing a stem (raw + JPEG) get separate sidecars (`.xmp` for all but the raw; an import ranks the files it is adding the same way, so a TIFF or JPEG arriving beside its raw reads its own `.xmp` and never the raw's `IMG_0001.xmp`, issue #1905); a `crs:` custom white balance on a relative-WB raw is shifted from `crs:AsShotTemperature` rather than read as Kelvin, or kept As Shot and reported when the packet has no as-shot white (issue #510); heal and clone spots (`crs:RetouchAreas`, legacy `crs:RetouchInfo`) are read into our spots (`crates/engine/src/crs_masks.rs`, see `docs/xmp-interop.md` → Spot removal); radial masks (`Mask/CircularGradient`) keep Lightroom's inside / outside: `crs:Flipped="true"` is the effect-inside flag of current masks and of the legacy Radial Filter, `crs:MaskInverted` the inversion, checked against Lightroom Classic's renders (issue #1815); Read Metadata from File never writes the sidecar back, even with auto-write on, so another app's later `crs:` edits are still read (issue #934) | +| LR-META-XMP | XMP read/write | P0 | ✅ | `cmd:photo.saveMetadataToFile`, `cmd:photo.readMetadataFromFile`, `cmd:library.xmpPreferences`, `crates/engine/src/sidecar.rs`, `docs/xmp-interop.md` | manual rereading also reads sidecar-free JPEG embedded XMP (first complete standard APP1 before SOS/EOI, within 16 MiB of headers and 4096 marker/fill steps; no extended assembly or between-scan packets, not strict whole-JPEG validation; reader cases in `crates/engine/src/sidecar.rs`); a discovered unreadable sidecar fails without embedded fallback; procedural refresh/undo, non-UTF-8 sidecar and two explicit malformed fixtures in `crates/engine/src/tests_xmp.rs` (file-write checks use auto-write off); sidecar wins for metadata; a sidecar capture time (`exif:DateTimeOriginal` / `photoshop:DateCreated` / `xmp:CreateDate`) fills in only when the file has none; saving merges into an existing sidecar (`crates/meta/src/xmp_merge.rs`): another app's `crs:`, `xmpMM:History` and unknown namespaces are kept byte for byte, an unreadable sidecar is backed up first; with stem naming, files sharing a stem (raw + JPEG) get separate sidecars (`.xmp` for all but the raw; an import ranks the files it is adding the same way, so a TIFF or JPEG arriving beside its raw reads its own `.xmp` and never the raw's `IMG_0001.xmp`, issue #1905); a `crs:` custom white balance on a relative-WB raw is shifted from `crs:AsShotTemperature` rather than read as Kelvin, or kept As Shot and reported when the packet has no as-shot white (issue #510); heal and clone spots (`crs:RetouchAreas`, legacy `crs:RetouchInfo`) are read into our spots (`crates/engine/src/crs_masks.rs`, see `docs/xmp-interop.md` → Spot removal); radial masks (`Mask/CircularGradient`) keep Lightroom's inside / outside: `crs:Flipped="true"` is the effect-inside flag of current masks and of the legacy Radial Filter, `crs:MaskInverted` the inversion, checked against Lightroom Classic's renders (issue #1815); Read Metadata from File never writes the sidecar back, even with auto-write on, so another app's later `crs:` edits are still read (issue #934); settings Lightroom writes only for itself (`CompatibleVersion`, the profile digests, the lens profile's name / file / setup, `CropConstrainToUnitSquare`, the lens profile's strength at 100 %) are not reported as unimported (`crates/engine/src/crs.rs`) | | LR-META-EXIF-FULL | Full EXIF/IPTC [Classic] | P1 | ✅ | `cmd:photo.allMetadata`, `cmd:dialog.allMetadata`, `crates/meta/src/tags.rs`, `crates/meta/src/exif.rs`, `crates/meta/src/iptc.rs` | `photo.allMetadata` reuses loaded original bytes for embedded XMP and keeps sidecar precedence (`crates/engine/src/tests_xmp.rs`); read and written on export; Info ▸ All Metadata…: every TIFF / EXIF / GPS / interop tag (named, common values spelled out) and the XMP fields, searchable; metadata presets | ## F. Edit panel — global adjustments (EDIT) @@ -799,6 +799,7 @@ Latin stays upright. Advanced Japanese composition remains open. Regression cove |---|---|---| | 2026-10-20 | patch | LR-VIEW-ZOOM: the 20 ms rule clears a slow-drag verdict only on a drag timed at full size; a drag that drafts small leaves it unchanged, as the code does; status unchanged | | 2026-10-19 | patch | LR-VIEW-ZOOM: the slow slider-drag verdict is described as per photo and control, as the code keys it; status unchanged | +| 2026-10-18 | patch | LR-META-XMP: Lightroom's profile and version bookkeeping (`CompatibleVersion`, profile digests, lens profile identity, `CropConstrainToUnitSquare`, default-strength lens profile scales) is no longer reported as unimported XMP; status unchanged (✅) | | 2026-10-15 | minor | LR-VIEW-ZOOM: a slow slider-drag verdict needs two drags over 20 ms and expires after 30 s (doubling to 240 s); hidden-window frame times are ignored; the slow set forgets its oldest entry, not all (issue #330); status unchanged | | 2026-10-15 | minor | LR-BEHAV-BG-TASKS: Reload from Disk reads its files on a worker thread with a cancellable activity row instead of freezing the window on a selection of raws; its result is committed through the command wrapper (XMP sidecars auto-written as before) and an older reload finishing late no longer overwrites a newer one; status unchanged (✅) | | 2026-10-14 | patch | LR-EXP-TYPE: opaque 8-bit PNG/TIFF/WebP use RGB; transparent exports retain alpha (#1670). Statuses and feature counts unchanged. | diff --git a/docs/whats-new.md b/docs/whats-new.md index cfeed33ed..f1eb100fb 100644 --- a/docs/whats-new.md +++ b/docs/whats-new.md @@ -8,6 +8,13 @@ ## 2026-10-18 +### Fewer false "not imported" warnings for Lightroom raws +- Importing raws with Lightroom XMP sidecars no longer lists settings Lightroom writes only for itself as adjustments + that weren't imported: `CompatibleVersion`, the camera and lens profile checksums, the lens profile's name, file and + setup, the crop's "constrain to image" option, and the lens profile's strength when it is at its default of 100 %. + A raw sidecar used to name all of them for every photo. Settings LightCraft really doesn't apply, such as `Look`, + are still reported. + ### Fill Location from GPS: no more neighbouring town in City - The offline place names list towns of 15,000 people or more, so a photo taken in a smaller one got the nearest larger town in City, sometimes another municipality (issue #2242). The Fill Location tooltip and the command help now say so,