Skip to content

feat(xmp): read digiKam/darktable sidecars and add conflict policy - #1818

Open
yoyozbi wants to merge 1 commit into
CyberTimon:mainfrom
yoyozbi:feature/xmp-digikam-sidecars
Open

yoyozbi wants to merge 1 commit into
CyberTimon:mainfrom
yoyozbi:feature/xmp-digikam-sidecars

Conversation

@yoyozbi

@yoyozbi yoyozbi commented Oct 1, 2026 •

Copy link
Copy Markdown
  • Move XMP code into a new xmp_sidecar module and parse with quick-xml
  • Resolve IMG.CR2.xmp (digiKam/darktable) before IMG.xmp
  • Read digiKam ColorLabel, darktable colorlabels, digiKam TagsList and lr:hierarchicalSubject (leaf names) as fallbacks
  • Store imported tags as user tags so clearing AI tags keeps them
  • Add an XMP conflict policy setting (fill empty / newest wins / XMP wins)
  • Never write to IMG.CR2.xmp; escape tags and drop the user: prefix when writing IMG.xmp

Description

Type of Change

  • Bug fix
  • New feature
  • Breaking change
  • Performance improvement
  • Code refactoring
  • Documentation update
  • UI/UX improvement
  • Build/CI or Dependency update

Changes Made

  • Moved over xmp handling into a xmp_sidecar module.
  • Used quick-xml to serialize part of digikam/darktable CR2.xmp files and resolve them before .xmp files.
  • Add xmp conflicts policy settings between the .rrdata and .CR2.xmp (fill empty / newest wins / XMP wins).
  • We don't write to the CR2.xmp files we only read them.

Screenshots/Videos

image

Testing

  • These changes were tested locally by a human and confirmed to work.
  • I haven't added any automated tests to the code because the codebase currently lacks a test suite.

Test Configuration:

  • OS: Omarchy 4.0.4-1
  • Hardware: Intel I7-12700H

Checklist

  • My code follows the project's code style
  • I haven't added unnecessary AI-generated code comments
  • My changes generate no new warnings or errors

Additional Notes

The project has known quite a duplication of code between the original .xml sidecars code that supports read/write and the added .cr2.xmp code. I didn't know what you had in mind about the .xml format, if it was used in another software, or something else.
If required I could also rework this change to handle sidecars in a more abstract way, building the foundation for future sidecars format support.

AI Disclaimer:

Please state the involvement of AI in this PR:

  • This PR is created by an AI agent
  • This PR is mostly AI-generated but edited/merged together by a human
  • This PR was handwritten with AI assistance (spell check, logic suggestions, error resolving)
  • This PR contains only blood, sweat, and coffee (AI-free)

- Move XMP code into a new xmp_sidecar module and parse with quick-xml
- Resolve IMG.CR2.xmp (digiKam/darktable) before IMG.xmp
- Read digiKam ColorLabel, darktable colorlabels, digiKam TagsList and
  lr:hierarchicalSubject (leaf names) as fallbacks
- Store imported tags as user tags so clearing AI tags keeps them
- Add an XMP conflict policy setting (fill empty / newest wins / XMP wins)
- Never write to IMG.CR2.xmp; escape tags and drop the user: prefix when
  writing IMG.xmp

Co-Authored-By: Claude Opus 5.5 <[email protected]>
@apweisskopf

Copy link
Copy Markdown
Contributor

Hi @yoyozbi, thanks for this PR. I ran into XMP sync problems with Lightroom sidecars and wrote them up in #1945. Your reader with quick-xml looks like a good basis, and I would like to build on it.

In a quick test with a few of my Lightroom sidecars I noticed some points that might need a closer look. Some are probably just because main moved on since you opened the PR:

  • It looks like an unknown label (e.g. a localized Lightroom label like Rot) is read as no color, and the next write removes xmp:Label from the file.
  • A reject (xmp:Rating="-1", added with the new reject flag) seems to be written back as 0.
  • If a sidecar fails to parse, the partial data seems to be used. With "XMP always wins", keywords then got removed on the next write.
  • When only IMG.CR2.xmp exists, a rating change goes to a new IMG.xmp. With "XMP always wins", it was reverted from IMG.CR2.xmp on the next load.

In the issue I suggested splitting the work into smaller PRs, with the reader and the darktable/digiKam support from this PR as separate steps. How would you like to proceed? Happy to work on it together, or to take over parts if you prefer.

@yoyozbi

yoyozbi commented Oct 22, 2026

Copy link
Copy Markdown
Author

Hello @apweisskopf ,
I would love to help out with this !

Your multiple PRs approach is a good thing, and the approach you describe in the issue is great.

For me, the first step would be to clearly define how we would tackle the different XML file formats between lightroom and darktable.
With that we could then build the new base in one or mutiple PRs with the new system you described in your issue. Since lightroom is already there, we would adapt it in the PRs, then when everything is done, we should then add the digikam support (adapting this PR or creating a new one).

In my opinion, the digikam or lightroom processing should be clearly separated, serializing/deserializing the data into a common struct. The app should then decide which backend to use.
When reading, pick the first format it recognize and serialize correctly (first .xml then .cr2.xml) and remembers what it used to then write to the correct format.
The handling should be transparent for the user.

@apweisskopf

Copy link
Copy Markdown
Contributor

Thanks, glad to work on this together

While waiting for your reply I started drafting the steps from the issue, mostly to check that the split into small PRs holds up. It turned into a full draft. The numbers follow the list in #1945, and each link shows only the commits of that step:

  1. Safety fixes: atomic writes, virtual copies stay away from the master XMP. Write XMP sidecars atomically and keep virtual copies off the master XMP #1967
  2. Reader: own xmp_sidecar module, namespace-aware parser based on your PR
  3. Writer: only update rating, label and keywords, keep everything else
  4. Sync: detect conflicts against the last synced state, so removals in other apps come through
  5. darktable and digiKam: read and write IMG.CR2.xmp, sync their label fields
  6. RAW+JPEG and backfill: the RAW gets its own IMG.xmp, missing sidecars are written from rrdata
  7. Hierarchical keywords: sync lr:hierarchicalSubject, keep digiKam:TagsList entries RapidRAW does not read

The branches are stacked and nothing is final, so please treat it as a proposal, not a done deal.

On the backends: I think we mostly agree, but I would avoid separate backends. All these apps write XMP. They differ in the file name and a few extra fields like digiKam:TagsList or darktable:colorlabels, and one file often has fields from several apps. So the draft uses one namespace-aware reader that reads all known fields, and a writer that only updates rating, label and keywords and keeps everything else.

Remembering the format isn't needed either. One lookup picks the sidecar for both read and write (IMG.xmp first, then IMG.CR2.xmp, same order as you suggest). So RapidRAW always writes the file it read.

For the darktable/digiKam step, I mainly drafted it to see how your PR could fit on top of the new reader and writer. I don't have much experience with darktable or digiKam, so if you would like to take it over, rework it or start fresh from your PR, that's fine with me. Happy to help or review either way.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants