Skip to content

Commit 6d70547

Browse files
committed
Add destination carrier precedence for XMP
Introduce XmpExistingDestinationCarrierPrecedence to control conflict precedence between an existing destination sidecar XMP and existing destination embedded XMP (default: SidecarWins). Wire the new enum into PrepareTransferFileOptions and the prepare_metadata_for_target_file implementation so merge ordering is determined by the carrier precedence; refactor merging into helper lambdas and apply_existing_xmp_merge_stage to apply the chosen order. Expose the option in the CLI tool and Python bindings (arg/enum and parsing), and add unit tests that validate both SidecarWins and EmbeddedWins behaviours.
1 parent 570f3eb commit 6d70547

6 files changed

Lines changed: 338 additions & 29 deletions

File tree

src/include/openmeta/metadata_transfer.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -934,6 +934,13 @@ enum class XmpExistingDestinationEmbeddedPrecedence : uint8_t {
934934
SourceWins,
935935
};
936936

937+
/// Conflict precedence between existing destination sidecar XMP and existing
938+
/// destination embedded XMP when both are merged during transfer preparation.
939+
enum class XmpExistingDestinationCarrierPrecedence : uint8_t {
940+
SidecarWins,
941+
EmbeddedWins,
942+
};
943+
937944
/// File-read + decode options for \ref prepare_metadata_for_target_file.
938945
struct PrepareTransferFileOptions final {
939946
bool include_pointer_tags = true;
@@ -952,6 +959,9 @@ struct PrepareTransferFileOptions final {
952959
XmpExistingDestinationEmbeddedPrecedence
953960
xmp_existing_destination_embedded_precedence
954961
= XmpExistingDestinationEmbeddedPrecedence::DestinationWins;
962+
XmpExistingDestinationCarrierPrecedence
963+
xmp_existing_destination_carrier_precedence
964+
= XmpExistingDestinationCarrierPrecedence::SidecarWins;
955965

956966
OpenMetaResourcePolicy policy;
957967
PrepareTransferRequest prepare;

src/openmeta/metadata_transfer.cc

Lines changed: 55 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -15246,6 +15246,9 @@ prepare_metadata_for_target_file_impl(
1524615246
= destination_embedded_merge_requested
1524715247
&& existing_destination_embedded->precedence
1524815248
== XmpExistingDestinationEmbeddedPrecedence::SourceWins;
15249+
const XmpExistingDestinationCarrierPrecedence
15250+
destination_carrier_precedence
15251+
= options.xmp_existing_destination_carrier_precedence;
1524915252

1525015253
if (destination_embedded_merge_requested
1525115254
&& existing_destination_embedded->out_path) {
@@ -15265,30 +15268,71 @@ prepare_metadata_for_target_file_impl(
1526515268
&out.xmp_existing_sidecar_status, &out.xmp_existing_sidecar_message);
1526615269
}
1526715270

15268-
for (;;) {
15269-
store = MetaStore();
15270-
if (!sidecar_source_precedence
15271-
&& !existing_xmp_sidecar_bytes.empty()) {
15271+
auto merge_existing_sidecar_into_store =
15272+
[&](bool preserve_store_on_failure) noexcept {
15273+
if (existing_xmp_sidecar_bytes.empty()) {
15274+
return;
15275+
}
1527215276
decode_existing_xmp_sidecar_into_store(
1527315277
std::span<const std::byte>(existing_xmp_sidecar_bytes.data(),
1527415278
existing_xmp_sidecar_bytes.size()),
15275-
decode_options.xmp, false, &store,
15279+
decode_options.xmp, preserve_store_on_failure, &store,
1527615280
&out.xmp_existing_sidecar_loaded,
1527715281
&out.xmp_existing_sidecar_status,
1527815282
&out.xmp_existing_sidecar_message);
1527915283
if (!out.xmp_existing_sidecar_loaded) {
1528015284
existing_xmp_sidecar_bytes.clear();
1528115285
}
15282-
}
15283-
if (!destination_embedded_source_precedence
15284-
&& destination_embedded_merge_requested) {
15286+
};
15287+
15288+
auto merge_existing_destination_embedded_into_store =
15289+
[&](bool preserve_store_on_failure) noexcept {
15290+
if (!destination_embedded_merge_requested) {
15291+
return;
15292+
}
1528515293
decode_existing_destination_embedded_xmp_into_store(
1528615294
existing_destination_embedded->path, policy, decode_options.xmp,
15287-
false, &store,
15295+
preserve_store_on_failure, &store,
1528815296
existing_destination_embedded->out_loaded,
1528915297
existing_destination_embedded->out_status,
1529015298
existing_destination_embedded->out_message);
15291-
}
15299+
};
15300+
15301+
auto apply_existing_xmp_merge_stage =
15302+
[&](bool after_source, bool preserve_store_on_failure) noexcept {
15303+
const bool merge_sidecar
15304+
= !existing_xmp_sidecar_bytes.empty()
15305+
&& sidecar_source_precedence == after_source;
15306+
const bool merge_destination_embedded
15307+
= destination_embedded_merge_requested
15308+
&& destination_embedded_source_precedence == after_source;
15309+
if (merge_sidecar && merge_destination_embedded) {
15310+
if (destination_carrier_precedence
15311+
== XmpExistingDestinationCarrierPrecedence::SidecarWins) {
15312+
merge_existing_sidecar_into_store(
15313+
preserve_store_on_failure);
15314+
merge_existing_destination_embedded_into_store(
15315+
preserve_store_on_failure);
15316+
} else {
15317+
merge_existing_destination_embedded_into_store(
15318+
preserve_store_on_failure);
15319+
merge_existing_sidecar_into_store(
15320+
preserve_store_on_failure);
15321+
}
15322+
return;
15323+
}
15324+
if (merge_sidecar) {
15325+
merge_existing_sidecar_into_store(preserve_store_on_failure);
15326+
}
15327+
if (merge_destination_embedded) {
15328+
merge_existing_destination_embedded_into_store(
15329+
preserve_store_on_failure);
15330+
}
15331+
};
15332+
15333+
for (;;) {
15334+
store = MetaStore();
15335+
apply_existing_xmp_merge_stage(false, false);
1529215336
out.read = simple_meta_read(
1529315337
mapped.bytes(), store,
1529415338
std::span<ContainerBlockRef>(blocks.data(), blocks.size()),
@@ -15327,25 +15371,7 @@ prepare_metadata_for_target_file_impl(
1532715371
}
1532815372
}
1532915373

15330-
if (sidecar_source_precedence && !existing_xmp_sidecar_bytes.empty()) {
15331-
decode_existing_xmp_sidecar_into_store(
15332-
std::span<const std::byte>(existing_xmp_sidecar_bytes.data(),
15333-
existing_xmp_sidecar_bytes.size()),
15334-
decode_options.xmp, true, &store, &out.xmp_existing_sidecar_loaded,
15335-
&out.xmp_existing_sidecar_status,
15336-
&out.xmp_existing_sidecar_message);
15337-
if (!out.xmp_existing_sidecar_loaded) {
15338-
existing_xmp_sidecar_bytes.clear();
15339-
}
15340-
}
15341-
if (destination_embedded_source_precedence
15342-
&& destination_embedded_merge_requested) {
15343-
decode_existing_destination_embedded_xmp_into_store(
15344-
existing_destination_embedded->path, policy, decode_options.xmp,
15345-
true, &store, existing_destination_embedded->out_loaded,
15346-
existing_destination_embedded->out_status,
15347-
existing_destination_embedded->out_message);
15348-
}
15374+
apply_existing_xmp_merge_stage(true, true);
1534915375

1535015376
if (has_read_failure(out.read)) {
1535115377
out.file_status = TransferFileStatus::ReadFailed;

src/python/openmeta/python/metatransfer.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -333,6 +333,7 @@ def main(argv: list[str]) -> int:
333333
ap.add_argument("--xmp-existing-sidecar-precedence", choices=["sidecar_wins", "source_wins"], default="sidecar_wins", help="conflict precedence between an existing output-side .xmp and source-embedded existing XMP")
334334
ap.add_argument("--xmp-include-existing-destination-embedded", action="store_true", help="include existing embedded XMP from the edit target in generated transfer XMP")
335335
ap.add_argument("--xmp-existing-destination-embedded-precedence", choices=["destination_wins", "source_wins"], default="destination_wins", help="conflict precedence between existing destination embedded XMP and source-embedded existing XMP")
336+
ap.add_argument("--xmp-existing-destination-carrier-precedence", choices=["sidecar_wins", "embedded_wins"], default="sidecar_wins", help="conflict precedence between an existing destination sidecar and existing destination embedded XMP")
336337
ap.add_argument("--xmp-no-exif-projection", action="store_true", help="do not mirror EXIF-derived properties into generated XMP")
337338
ap.add_argument("--xmp-no-iptc-projection", action="store_true", help="do not mirror IPTC-derived properties into generated XMP")
338339
ap.add_argument("--xmp-conflict-policy", choices=["current", "existing_wins", "generated_wins"], default="current", help="conflict policy between existing decoded XMP and generated portable EXIF/IPTC XMP")
@@ -959,6 +960,13 @@ def main(argv: list[str]) -> int:
959960
xmp_existing_destination_embedded_precedence = (
960961
openmeta.XmpExistingDestinationEmbeddedPrecedence.SourceWins
961962
)
963+
xmp_existing_destination_carrier_precedence = (
964+
openmeta.XmpExistingDestinationCarrierPrecedence.SidecarWins
965+
)
966+
if args.xmp_existing_destination_carrier_precedence == "embedded_wins":
967+
xmp_existing_destination_carrier_precedence = (
968+
openmeta.XmpExistingDestinationCarrierPrecedence.EmbeddedWins
969+
)
962970

963971
for path in input_paths:
964972
source_path = args.source_meta if args.source_meta else path
@@ -1017,6 +1025,7 @@ def main(argv: list[str]) -> int:
10171025
xmp_existing_sidecar_precedence=xmp_existing_sidecar_precedence,
10181026
xmp_existing_destination_embedded_mode=xmp_existing_destination_embedded_mode,
10191027
xmp_existing_destination_embedded_precedence=xmp_existing_destination_embedded_precedence,
1028+
xmp_existing_destination_carrier_precedence=xmp_existing_destination_carrier_precedence,
10201029
edit_apply=edit_apply,
10211030
include_edited_bytes=need_edited_bytes,
10221031
include_c2pa_binding_bytes=need_c2pa_binding_bytes,

src/python/src/openmeta_module.cc

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1815,6 +1815,8 @@ namespace {
18151815
xmp_existing_destination_embedded_mode,
18161816
XmpExistingDestinationEmbeddedPrecedence
18171817
xmp_existing_destination_embedded_precedence,
1818+
XmpExistingDestinationCarrierPrecedence
1819+
xmp_existing_destination_carrier_precedence,
18181820
bool edit_do_apply,
18191821
bool include_edited_bytes,
18201822
bool unsafe_edited_bytes_access, bool include_c2pa_binding_bytes,
@@ -1859,6 +1861,8 @@ namespace {
18591861
= xmp_existing_sidecar_mode;
18601862
prepare_options.xmp_existing_sidecar_precedence
18611863
= xmp_existing_sidecar_precedence;
1864+
prepare_options.xmp_existing_destination_carrier_precedence
1865+
= xmp_existing_destination_carrier_precedence;
18621866

18631867
if (!policy_obj.is_none()) {
18641868
prepare_options.policy = nb::cast<OpenMetaResourcePolicy>(
@@ -4001,6 +4005,13 @@ NB_MODULE(_openmeta, m)
40014005
.value("SourceWins",
40024006
XmpExistingDestinationEmbeddedPrecedence::SourceWins);
40034007

4008+
nb::enum_<XmpExistingDestinationCarrierPrecedence>(
4009+
m, "XmpExistingDestinationCarrierPrecedence")
4010+
.value("SidecarWins",
4011+
XmpExistingDestinationCarrierPrecedence::SidecarWins)
4012+
.value("EmbeddedWins",
4013+
XmpExistingDestinationCarrierPrecedence::EmbeddedWins);
4014+
40044015
nb::enum_<TransferTargetFormat>(m, "TransferTargetFormat")
40054016
.value("Jpeg", TransferTargetFormat::Jpeg)
40064017
.value("Tiff", TransferTargetFormat::Tiff)
@@ -5089,6 +5100,8 @@ NB_MODULE(_openmeta, m)
50895100
xmp_existing_destination_embedded_mode,
50905101
XmpExistingDestinationEmbeddedPrecedence
50915102
xmp_existing_destination_embedded_precedence,
5103+
XmpExistingDestinationCarrierPrecedence
5104+
xmp_existing_destination_carrier_precedence,
50925105
bool edit_apply,
50935106
bool include_edited_bytes, bool include_c2pa_binding_bytes,
50945107
bool include_c2pa_handoff_bytes,
@@ -5117,6 +5130,7 @@ NB_MODULE(_openmeta, m)
51175130
xmp_existing_sidecar_mode, xmp_existing_sidecar_precedence,
51185131
xmp_existing_destination_embedded_mode,
51195132
xmp_existing_destination_embedded_precedence,
5133+
xmp_existing_destination_carrier_precedence,
51205134
edit_apply,
51215135
include_edited_bytes, false, include_c2pa_binding_bytes, false,
51225136
include_c2pa_handoff_bytes, include_c2pa_signed_package_bytes,
@@ -5159,6 +5173,8 @@ NB_MODULE(_openmeta, m)
51595173
= XmpExistingDestinationEmbeddedMode::Ignore,
51605174
"xmp_existing_destination_embedded_precedence"_a
51615175
= XmpExistingDestinationEmbeddedPrecedence::DestinationWins,
5176+
"xmp_existing_destination_carrier_precedence"_a
5177+
= XmpExistingDestinationCarrierPrecedence::SidecarWins,
51625178
"edit_apply"_a = true, "include_edited_bytes"_a = false,
51635179
"include_c2pa_binding_bytes"_a = false,
51645180
"include_c2pa_handoff_bytes"_a = false,
@@ -5200,6 +5216,8 @@ NB_MODULE(_openmeta, m)
52005216
xmp_existing_destination_embedded_mode,
52015217
XmpExistingDestinationEmbeddedPrecedence
52025218
xmp_existing_destination_embedded_precedence,
5219+
XmpExistingDestinationCarrierPrecedence
5220+
xmp_existing_destination_carrier_precedence,
52035221
bool edit_apply,
52045222
bool include_edited_bytes, bool include_c2pa_binding_bytes,
52055223
bool include_c2pa_handoff_bytes,
@@ -5228,6 +5246,7 @@ NB_MODULE(_openmeta, m)
52285246
xmp_existing_sidecar_mode, xmp_existing_sidecar_precedence,
52295247
xmp_existing_destination_embedded_mode,
52305248
xmp_existing_destination_embedded_precedence,
5249+
xmp_existing_destination_carrier_precedence,
52315250
edit_apply,
52325251
include_edited_bytes, true, include_c2pa_binding_bytes, true,
52335252
include_c2pa_handoff_bytes, include_c2pa_signed_package_bytes,
@@ -5270,6 +5289,8 @@ NB_MODULE(_openmeta, m)
52705289
= XmpExistingDestinationEmbeddedMode::Ignore,
52715290
"xmp_existing_destination_embedded_precedence"_a
52725291
= XmpExistingDestinationEmbeddedPrecedence::DestinationWins,
5292+
"xmp_existing_destination_carrier_precedence"_a
5293+
= XmpExistingDestinationCarrierPrecedence::SidecarWins,
52735294
"edit_apply"_a = true, "include_edited_bytes"_a = false,
52745295
"include_c2pa_binding_bytes"_a = false,
52755296
"include_c2pa_handoff_bytes"_a = false,
@@ -5311,6 +5332,8 @@ NB_MODULE(_openmeta, m)
53115332
xmp_existing_destination_embedded_mode,
53125333
XmpExistingDestinationEmbeddedPrecedence
53135334
xmp_existing_destination_embedded_precedence,
5335+
XmpExistingDestinationCarrierPrecedence
5336+
xmp_existing_destination_carrier_precedence,
53145337
bool edit_apply,
53155338
bool include_edited_bytes, bool include_c2pa_binding_bytes,
53165339
bool include_c2pa_handoff_bytes,
@@ -5342,6 +5365,7 @@ NB_MODULE(_openmeta, m)
53425365
xmp_existing_sidecar_mode, xmp_existing_sidecar_precedence,
53435366
xmp_existing_destination_embedded_mode,
53445367
xmp_existing_destination_embedded_precedence,
5368+
xmp_existing_destination_carrier_precedence,
53455369
edit_apply,
53465370
include_edited_bytes, false, include_c2pa_binding_bytes, false,
53475371
include_c2pa_handoff_bytes, include_c2pa_signed_package_bytes,
@@ -5386,6 +5410,8 @@ NB_MODULE(_openmeta, m)
53865410
= XmpExistingDestinationEmbeddedMode::Ignore,
53875411
"xmp_existing_destination_embedded_precedence"_a
53885412
= XmpExistingDestinationEmbeddedPrecedence::DestinationWins,
5413+
"xmp_existing_destination_carrier_precedence"_a
5414+
= XmpExistingDestinationCarrierPrecedence::SidecarWins,
53895415
"edit_apply"_a = true, "include_edited_bytes"_a = false,
53905416
"include_c2pa_binding_bytes"_a = false,
53915417
"include_c2pa_handoff_bytes"_a = false,
@@ -5430,6 +5456,8 @@ NB_MODULE(_openmeta, m)
54305456
xmp_existing_destination_embedded_mode,
54315457
XmpExistingDestinationEmbeddedPrecedence
54325458
xmp_existing_destination_embedded_precedence,
5459+
XmpExistingDestinationCarrierPrecedence
5460+
xmp_existing_destination_carrier_precedence,
54335461
bool edit_apply,
54345462
bool include_edited_bytes, bool include_c2pa_binding_bytes,
54355463
bool include_c2pa_handoff_bytes,
@@ -5461,6 +5489,7 @@ NB_MODULE(_openmeta, m)
54615489
xmp_existing_sidecar_mode, xmp_existing_sidecar_precedence,
54625490
xmp_existing_destination_embedded_mode,
54635491
xmp_existing_destination_embedded_precedence,
5492+
xmp_existing_destination_carrier_precedence,
54645493
edit_apply,
54655494
include_edited_bytes, true, include_c2pa_binding_bytes, true,
54665495
include_c2pa_handoff_bytes, include_c2pa_signed_package_bytes,
@@ -5505,6 +5534,8 @@ NB_MODULE(_openmeta, m)
55055534
= XmpExistingDestinationEmbeddedMode::Ignore,
55065535
"xmp_existing_destination_embedded_precedence"_a
55075536
= XmpExistingDestinationEmbeddedPrecedence::DestinationWins,
5537+
"xmp_existing_destination_carrier_precedence"_a
5538+
= XmpExistingDestinationCarrierPrecedence::SidecarWins,
55085539
"edit_apply"_a = true, "include_edited_bytes"_a = false,
55095540
"include_c2pa_binding_bytes"_a = false,
55105541
"include_c2pa_handoff_bytes"_a = false,

0 commit comments

Comments
 (0)