Speak the sidecar format every other editor already reads

FR-CAT-13 asked for standard XMP and nothing in the tree parsed or wrote a
byte of it. `keywords.rs` mentioned `dc:subject` in a comment about what a
keyword's text is for, `dr-export`'s metadata module said "neither is read by
`dr-decode` today" about its own half, and `dr-preset-xmp` reads a different
file for a different requirement. So a library imported from Lightroom could
come in and never go back out: a one-way door, which is not a thing a
photographer walks their archive through.

`core/dr-xmp` reads and writes the properties the requirement names —
`dc:subject`, `lr:hierarchicalSubject`, `xmp:Rating`, `xmp:Label` and the IPTC
core fields — from whichever shape the file happens to use. A property may
arrive as an attribute or as an element, inside a Bag, a Seq, an Alt or no
container at all, because the specification is not what wrote the file; so one
collector takes whatever is in a property and the declared shape decides only
how many values survive. `xmp:Rating="-1"` is modelled as Adobe's rejection
rather than folded into zero stars, since DarkRoom keeps those on two axes and
the mapping belongs where both are visible.

Writing is a rewrite rather than a serialisation, and that is the whole design.
An XMP sidecar is a shared document: the file beside a raw carries somebody
else's `crs:` settings and comments and namespaces, and rendering our record
over it would be data loss on every photograph but the first. The rule is
stated once, in the crate documentation and in `PROPERTIES`: DarkRoom owns
exactly those properties, identified by namespace URI and never by prefix, and
nothing else in the document. Everything unowned is copied through byte for
byte. A `Description` left empty once our properties come out of it is
withdrawn, which is what keeps a rewrite idempotent instead of adding a husk to
the file on every save.

Precedence is settled conservatively, because a standard XMP carries no
revision and no device and there is nothing in it to order two edits by.
Keywords union, following the rule `dr_catalog::merge` already makes for
assignments; every other field is taken only where DarkRoom holds none,
following `Version::merge`'s judgement rule, and a genuine disagreement is
reported rather than resolved so a caller can offer the reload the requirement
asks for. What is deliberately left open — when a reload may happen without
asking — is written down in the module rather than picked silently.

No new dependency: quick-xml was already in the tree for WebDAV and for
Lightroom presets. Nothing above the crate calls it yet, and `outstanding.md`
now says so along with the two smaller gaps, GPS and the filename convention.
This commit is contained in:
2026-09-06 19:01:48 +02:00
parent 68ebf5d78b
commit 5fa4c0772b
9 changed files with 2703 additions and 20 deletions
+25 -8
View File
@@ -305,14 +305,31 @@ well optimised — ETag pruning under FR-NC-4 turns an unchanged 50k library int
gap is narrower than it reads. It is the *first* build against a large remote library that pays, and
that is the moment a new user meets.
**FR-CAT-13 — XMP interoperability, untagged and not met.** Read and write standard XMP sidecars.
Its single tag sat on `keywords.rs`, which stores keywords in the catalog and mentions `dc:subject`
in a comment about what a keyword's text is *for*; no XMP is parsed or written anywhere in the tree,
and `dr-export`'s metadata module says so about its own half ("neither is read by `dr-decode`
today"). The tag has been removed — `dr-preset-xmp` is not the counter-example it looks like, being
a reader of Lightroom *presets* under FR-DEV-6, which is a different file and a different purpose.
Listed here rather than silently, because a tag makes a gap invisible and this one is load-bearing
for interoperating with the editors FR-CAT-14 imports from.
**FR-CAT-13 — XMP interoperability, built but not yet wired.** `core/dr-xmp` now reads and writes
standard XMP sidecars: `dc:subject` and `lr:hierarchicalSubject`, `xmp:Rating` and `xmp:Label`, and
the IPTC core fields, in both the attribute and the element form and whatever RDF container a file
happened to use. It states the ownership rule in one place — DarkRoom owns the properties in
`PROPERTIES` and nothing else in the document, identified by namespace URI rather than by prefix —
and enforces it by rewriting a packet event by event rather than serialising over it, so another
application's `crs:` settings, comments and processing instructions survive a write byte for byte.
**What remains is the wiring, and it is the larger half.** Nothing above the crate calls it: no scan
finds a `.xmp` beside a raw, no catalog row is populated from one, no edit writes one back, and the
external-modification detection the requirement also asks for does not exist. Two smaller gaps go
with it — GPS is not carried (`exif:GPSLatitude` is a format of its own, and `dr-decode` produces no
location for it to carry yet, which `dr-export`'s metadata module says about its own half), and the
filename convention is left to the caller, because Lightroom writes `IMG_0001.xmp` and darktable
writes `IMG_0001.CR3.xmp` and finding a file is not this crate's business.
The precedence question is settled conservatively rather than fully: keywords union, following
`dr_catalog::merge`, and every other field is taken only where DarkRoom holds none, following
`Version::merge`'s judgement rule — because a standard XMP carries no revision and no device, so
FR-NC-9's ordering cannot be performed against it. What is *not* settled, and is written down in the
crate rather than guessed at, is when a reload may happen without asking; see the module
documentation's "The open question".
`dr-preset-xmp` remains what it always was and is still not the counter-example it looks like: a
reader of Lightroom *presets* under FR-DEV-6, a different file for a different purpose.
---