Skip to content

Commit 4339c96

Browse files
committed
Add TransferSafetyMode and rendered-image filters
Introduce TransferSafetyMode (CompatibleFile, RenderedImage) and thread it through TransferProfile (default CompatibleFile). When RenderedImage is selected, prepare/apply logic filters safety-sensitive metadata: drop source ICC for rendered outputs, raw color calibration tags, camera raw settings XMP, opaque MakerNotes and non-C2PA JUMBF, and adjust C2PA handling (invalidate when appropriate). Implement counting and policy-decision reporting for filtered groups, apply safety-aware resolution of makernote/jumbf/c2pa policies, and add BMFF insertion compatibility changes (newly inserted item records use iloc construction method 0 with absolute file-offset extents and compact zero-width base-offset when safe). Update public docs, examples, Python/CLI wrappers (new --transfer-safety option), and tests to cover the new safety mode and the BMFF compatibility behavior.
1 parent a1f5d2f commit 4339c96

22 files changed

Lines changed: 1881 additions & 103 deletions

CHANGES.md

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,29 @@ Changes compared with `0.4.7`.
99
- Added bounded 32-bit BMFF item-id insertion for parseable foreign item graphs
1010
that already use `iloc` version 2. `iloc` version 0/1 targets remain on the
1111
existing 16-bit insertion path.
12+
- Added `TransferSafetyMode` on `TransferProfile`. The default
13+
`CompatibleFile` mode keeps current transfer behavior, while `RenderedImage`
14+
drops source raw color calibration/correction metadata, camera raw settings
15+
XMP, source ICC profiles, MakerNotes, and non-C2PA JUMBF data for rendered
16+
image outputs.
17+
- Added transfer policy decisions for filtered image properties, ICC profiles,
18+
raw color calibration, and camera raw settings.
1219

1320
### Changed
1421

1522
- Public BMFF writer-contract docs now state the remaining item-id-width limit
1623
explicitly: 32-bit inserted item IDs require an existing `iloc` v2 graph, and
1724
unsupported or exhausted item-id spaces fail safely instead of truncating IDs.
25+
- BMFF foreign-`meta` insertion keeps newly inserted metadata item records on
26+
`iloc` construction method 0 with absolute file-offset extents for broad
27+
reader compatibility.
28+
- BMFF foreign-`meta` insertion now compacts zero/foldable `iloc` base offsets
29+
to a zero-width base-offset field when rebuilding supported item graphs,
30+
preserving absolute self-contained item extents for simpler readers.
31+
- C++ and Python `metatransfer` wrappers now accept
32+
`--transfer-safety compatible|rendered`.
33+
- Writer-contract docs now include a per-group transfer safety matrix for
34+
rendered-image exports, including opaque MakerNote handling.
1835

1936
### Fixed
2037

@@ -26,6 +43,19 @@ Changes compared with `0.4.7`.
2643
- Added a BMFF API roundtrip test that writes Exif and XMP into an `iloc` v2
2744
target with high item IDs and scans the result back as one Exif item and one
2845
XMP item.
46+
- Added focused BMFF coverage that verifies inserted Exif/XMP item records use
47+
construction method 0 and absolute file-offset extents.
48+
- Extended the BMFF image-usability gate with an explicit ExifTool
49+
reader-layout regression check for transferred Exif items in HEIF/AVIF/CR3
50+
targets.
51+
- Extended rendered-image safety coverage to require policy decisions for
52+
image-layout fields, ICC, RAW/DNG color and correction tags, camera raw
53+
settings XMP, opaque MakerNotes, and non-C2PA JUMBF data.
54+
- Extended the `metatransfer` smoke gate to verify that
55+
`--transfer-safety rendered` prints user-visible policy decisions for the
56+
same safety-filtered groups.
57+
- Extended the Python `metatransfer` smoke gate with the same
58+
`--transfer-safety rendered` policy-output coverage.
2959

3060
## 0.4.7 - 2026-04-27
3161

docs/development.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -835,9 +835,10 @@ transfer path only runs for configured targets that match the 64x32,
835835
3-channel fixture shape, so the gate does not intentionally write mismatched
836836
image geometry. The configured XMP assertion is based on OpenMeta's BMFF
837837
summary; ExifTool title validation is also applied for formats where ExifTool
838-
exposes the generic BMFF XMP item. If the local `oiiotool` build cannot decode
839-
a configured BMFF target after rewrite, `OPENMETA_FFMPEG_EXECUTABLE` can
840-
provide the decode fallback.
838+
exposes the generic BMFF XMP item. ExifTool is also used for BMFF EXIF/ICC
839+
reader checks when available. If the local `oiiotool` build cannot decode a
840+
configured BMFF target after rewrite, `OPENMETA_FFMPEG_EXECUTABLE` can provide
841+
the decode fallback.
841842

842843
The public GitHub Actions workflow `.github/workflows/ci.yml` runs two Linux
843844
variants of these public release gates:

docs/host_integration.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -425,9 +425,20 @@ container or inject values derived from the actual output buffer. Enable source
425425
ICC transfer only when the host has verified that the profile matches the target
426426
pixel buffer; otherwise preserve or write the target profile.
427427

428+
Use `TransferProfile::safety` for the broad source/destination relationship:
429+
430+
| Mode | Use when | Transfer policy |
431+
| --- | --- | --- |
432+
| `CompatibleFile` | Metadata is repackaged or recompressed into a compatible file/pixel representation | Preserve source camera, color, ICC, and camera-specific data except target-owned image-layout fields |
433+
| `RenderedImage` | Pixels may have changed, especially RAW-to-JPEG/PNG/WebP/JXL/HEIF/AVIF export | Keep general/time/GPS/IPTC/portable XMP; drop source raw color calibration, linearization/crop/correction metadata, camera raw settings XMP, source ICC, opaque MakerNotes, and non-C2PA JUMBF |
434+
435+
See [writer_target_contract.md](writer_target_contract.md#transfer-safety-matrix)
436+
for the detailed per-group transfer matrix.
437+
428438
```cpp
429439
openmeta::PrepareTransferRequest request;
430440
request.target_format = openmeta::TransferTargetFormat::Jpeg;
441+
request.profile.safety = openmeta::TransferSafetyMode::RenderedImage;
431442

432443
request.target_image_spec.has_dimensions = true;
433444
request.target_image_spec.width = encoded_width;

docs/metadata_backend_matrix.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,10 @@ For the public per-target preserve/replace guarantees, see
156156
Inserted item IDs follow the existing `iloc` item-id width: `iloc` version 0/1
157157
remains a 16-bit insertion path, while `iloc` version 2 can use 32-bit item IDs
158158
and emits wider `infe`/`iref` records when needed.
159+
Newly inserted metadata item records keep `iloc` construction method 0 and
160+
use absolute file-offset extents for broad reader
161+
compatibility. When retained self-contained records can be represented that
162+
way, the rebuilt `iloc` also compacts the base-offset field width to zero.
159163
- Foreign-`meta` ICC property merge is bounded to
160164
`bmff:property-colr-icc`. OpenMeta removes prior ICC `colr/prof` and
161165
`colr/rICC` properties from `iprp/ipco`, compacts/remaps existing `ipma`

docs/metadata_transfer_plan.md

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,9 @@ The main remaining work is now on the target side.
4141
The first public write-side sync controls are also in place:
4242
- generated XMP can explicitly suppress EXIF-derived projection
4343
- generated XMP can explicitly suppress IPTC-derived projection
44+
- `TransferProfile::safety` can distinguish compatible metadata
45+
repackage/recompression from rendered-image export, so callers can select a
46+
safe coarse policy without hand-picking individual tags
4447
- prepared bundles record those resolved projection decisions alongside the
4548
existing preservation policies
4649

@@ -113,6 +116,8 @@ OpenMeta now has explicit end-to-end read-backed transfer tests for:
113116
- source XMP -> bounded BMFF XMP item edit/apply -> read-back and external
114117
validation on configured HEIF/AVIF/CR3 targets where local tools expose the
115118
transferred XMP payload
119+
- source EXIF -> bounded BMFF Exif item edit/apply -> explicit ExifTool
120+
reader-layout regression check on configured HEIF/AVIF/CR3 targets
116121

117122
That does not make all targets equally mature, but it does mean the transfer
118123
core has real roundtrip regression gates across the primary supported export
@@ -147,7 +152,8 @@ CR3 target files via CMake cache paths when local tools cannot create those
147152
formats. Arbitrary configured BMFF targets exercise the ICC property and XMP
148153
item transfer/read-back routes; the EXIF image-property route remains limited
149154
to the 64x32, 3-channel fixture shape to avoid intentionally mismatched
150-
geometry.
155+
geometry. ExifTool is used when available for BMFF EXIF/XMP/ICC reader
156+
compatibility checks.
151157

152158
## Per-Target Notes
153159

@@ -199,6 +205,12 @@ Implemented:
199205
source image-layout fields have been filtered. The Python binding and both
200206
command-line transfer wrappers now expose the same target image spec surface
201207
for file-helper integration tests.
208+
- rendered-output safety mode: `TransferProfile::safety =
209+
TransferSafetyMode::RenderedImage` additionally drops source raw color
210+
calibration, linearization/crop/correction metadata, camera raw settings XMP,
211+
source ICC profiles, MakerNotes, and non-C2PA JUMBF data. It is intended for
212+
RAW-to-rendered or otherwise pixel-changing exports where host code must
213+
provide target-correct color/profile data.
202214
- bounded DNG-style merge policy in the file-helper path:
203215
source-supplied preview/aux front structures replace the target front
204216
structures, while existing target page tails and trailing auxiliary
@@ -307,6 +319,10 @@ Implemented as a bounded BMFF target family:
307319
- bounded 32-bit item-id insertion for foreign item graphs that already use
308320
`iloc` version 2; `iloc` version 0/1 targets remain constrained to 16-bit
309321
inserted item IDs
322+
- inserted metadata item records keep `iloc` construction method 0 and use
323+
absolute file-offset extents for broad reader compatibility
324+
- rebuilt foreign `iloc` graphs compact foldable self-contained base offsets to
325+
a zero-width base-offset field when safe
310326
- bounded foreign top-level `meta` ICC property merge by replacing prior ICC
311327
`colr/prof` and `colr/rICC` properties, remapping `ipma`, and associating the
312328
transferred `colr/prof` property with the primary item
@@ -893,8 +909,8 @@ writer-confidence slice above; it should be sequenced around it.
893909
offset or byte range where available, and short host-facing messages
894910
- [ ] extend resource accounting beyond current hard limits with preflight
895911
estimates for prepared transfers, sidecar output, and serialized snapshots
896-
- [ ] add clean-room public micro fixtures for host integration tests; do not use
897-
private corpus files, vendor drops, or scraped/spec text
912+
- [ ] add clean-room public micro fixtures for host integration tests; do not
913+
vendor large binary assets, third-party source drops, or scraped/spec text
898914
- [ ] add examples for `read bytes -> snapshot -> target bytes -> edited bytes`
899915
and `visit_metadata(...) -> flat host attribute list`
900916
- [ ] consider a bounded random-access IO callback only after bytes/file APIs

docs/quick_start.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,8 @@ This is the common export workflow:
207207
208208
openmeta::ExecutePreparedTransferFileOptions options;
209209
options.prepare.prepare.target_format = openmeta::TransferTargetFormat::Jpeg;
210+
options.prepare.prepare.profile.safety =
211+
openmeta::TransferSafetyMode::RenderedImage;
210212
options.edit_target_path = "rendered.jpg";
211213
212214
const openmeta::ExecutePreparedTransferFileResult exec =

docs/sphinx/host_integration.rst

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -393,10 +393,29 @@ container or inject values derived from the actual output buffer. Enable source
393393
ICC transfer only when the host has verified that the profile matches the target
394394
pixel buffer; otherwise preserve or write the target profile.
395395

396+
Use ``TransferProfile::safety`` for the broad source/destination relationship:
397+
398+
.. list-table::
399+
:header-rows: 1
400+
:widths: 18 32 50
401+
402+
* - Mode
403+
- Use when
404+
- Transfer policy
405+
* - ``CompatibleFile``
406+
- Metadata is repackaged or recompressed into a compatible file/pixel representation
407+
- Preserve source camera, color, ICC, and camera-specific data except target-owned image-layout fields
408+
* - ``RenderedImage``
409+
- Pixels may have changed, especially RAW-to-JPEG/PNG/WebP/JXL/HEIF/AVIF export
410+
- Keep general/time/GPS/IPTC/portable XMP; drop source raw color calibration, linearization/crop/correction metadata, camera raw settings XMP, source ICC, opaque MakerNotes, and non-C2PA JUMBF
411+
412+
See :ref:`transfer-safety-matrix` for the detailed per-group transfer matrix.
413+
396414
.. code-block:: cpp
397415
398416
openmeta::PrepareTransferRequest request;
399417
request.target_format = openmeta::TransferTargetFormat::Jpeg;
418+
request.profile.safety = openmeta::TransferSafetyMode::RenderedImage;
400419
401420
request.target_image_spec.has_dimensions = true;
402421
request.target_image_spec.width = encoded_width;

docs/sphinx/quick_start.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,8 @@ Copy metadata into an existing target
168168
169169
openmeta::ExecutePreparedTransferFileOptions options;
170170
options.prepare.prepare.target_format = openmeta::TransferTargetFormat::Jpeg;
171+
options.prepare.prepare.profile.safety =
172+
openmeta::TransferSafetyMode::RenderedImage;
171173
options.edit_target_path = "rendered.jpg";
172174
173175
openmeta::ExecutePreparedTransferFileResult exec =

docs/sphinx/testing.rst

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -117,9 +117,10 @@ transfer path only runs for configured targets that match the 64x32,
117117
3-channel fixture shape, so the gate does not intentionally write mismatched
118118
image geometry. The configured XMP assertion is based on OpenMeta's BMFF
119119
summary; ExifTool title validation is also applied for formats where ExifTool
120-
exposes the generic BMFF XMP item. If the local ``oiiotool`` build cannot
121-
decode a configured BMFF target after rewrite, ``OPENMETA_FFMPEG_EXECUTABLE``
122-
can provide the decode fallback.
120+
exposes the generic BMFF XMP item. ExifTool is also used for BMFF EXIF/ICC
121+
reader checks when available. If the local ``oiiotool`` build cannot decode a
122+
configured BMFF target after rewrite, ``OPENMETA_FFMPEG_EXECUTABLE`` can
123+
provide the decode fallback.
123124

124125
The public GitHub Actions workflow ``.github/workflows/ci.yml`` runs two Linux
125126
variants of these public release gates:

docs/sphinx/writer_target_contract.rst

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,123 @@ inject the target profile. The Python binding exposes the same structure as
5151
command wrappers expose matching ``--target-*`` flags for smoke tests and
5252
file-helper integration checks.
5353

54+
For coarse transfer safety, use ``TransferProfile::safety``. The default
55+
``CompatibleFile`` mode is for metadata repackage or recompression into a
56+
compatible target and preserves source camera/color metadata after the
57+
target-owned image-layout filter above. ``RenderedImage`` is for exports whose
58+
pixels may have changed, including RAW-to-rendered outputs. It keeps general
59+
descriptive metadata, time fields, GPS, IPTC, and portable XMP, but filters
60+
source raw color calibration, linearization/crop/correction tags, camera raw
61+
settings XMP, source ICC profiles, MakerNotes, and non-C2PA JUMBF data. Host
62+
code should provide target-correct ICC/profile data and image specs separately.
63+
64+
.. _transfer-safety-matrix:
65+
66+
Transfer Safety Matrix
67+
----------------------
68+
69+
The table below is the public coarse policy for automatic transfer. It is not
70+
a privacy policy and it is not a replacement for application-specific metadata
71+
controls. Hosts may still strip more metadata.
72+
73+
.. list-table::
74+
:header-rows: 1
75+
:widths: 18 28 18 18 18
76+
77+
* - Metadata group
78+
- Examples
79+
- ``CompatibleFile``
80+
- ``RenderedImage``
81+
- Notes
82+
* - General descriptive metadata
83+
- title, description, creator, artist, copyright, rating, label, keywords
84+
- Keep
85+
- Keep
86+
- Safe because it describes the asset or authorship rather than the source
87+
pixel encoding.
88+
* - Capture time
89+
- ``DateTimeOriginal``, ``CreateDate``, subsecond fields, timezone offset
90+
fields, GPS time
91+
- Keep
92+
- Keep
93+
- If the output represents a new capture or synthetic composition, host
94+
policy should replace or strip these values.
95+
* - Camera and lens acquisition facts
96+
- ``Make``, ``Model``, ``LensModel``, exposure time, f-number, ISO, focal
97+
length, flash, metering mode
98+
- Keep
99+
- Keep
100+
- These are safe as capture facts. They are not treated as processing
101+
instructions.
102+
* - Location and IPTC editorial fields
103+
- EXIF GPS, XMP GPS aliases, IPTC location, caption, byline, credit,
104+
rights, job/reference fields
105+
- Keep
106+
- Keep
107+
- Privacy-sensitive data is intentionally left to host policy.
108+
* - Portable XMP
109+
- Dublin Core, XMP Rights, IPTC Extension, generated EXIF/IPTC projections
110+
- Keep after image-layout filtering
111+
- Keep after image-layout and rendered-safety filtering
112+
- XMP properties from raw-processing namespaces are handled separately
113+
below.
114+
* - Target image layout and storage
115+
- width, height, orientation, samples per pixel, bits per sample, sample
116+
format, photometric interpretation, compression, rows/strips/tiles,
117+
offsets, byte counts, thumbnail/interchange offsets
118+
- Target-owned; source values filtered
119+
- Target-owned; source values filtered
120+
- Host code must preserve target values or provide
121+
``target_image_spec``.
122+
* - Output color/profile metadata
123+
- ICC profile blocks, EXIF/XMP ``ColorSpace``, color-space aliases,
124+
Photoshop ICC profile name
125+
- Keep only when the source profile is valid for the target pixel buffer
126+
- Drop source ICC/profile facts; host writes target profile
127+
- Rendered exports often need a new output profile such as sRGB, Display
128+
P3, or a host-managed working/output profile.
129+
* - RAW/DNG sensor and color pipeline
130+
- CFA pattern, black/white levels, linearization tables,
131+
``ColorMatrix*``, ``ForwardMatrix*``, ``CameraCalibration*``,
132+
``AsShotNeutral``, DNG private/profile tags
133+
- Keep only for compatible RAW/DNG-style transfer
134+
- Drop
135+
- These values describe how to turn original sensor data into rendered
136+
color. Reusing them on already-rendered pixels can make CMS or editors
137+
apply the raw transform twice.
138+
* - RAW crop, geometry, and correction data
139+
- ``ActiveArea``, ``DefaultCrop*``, masked areas, opcode lists,
140+
distortion/vignetting/camera-profile correction data
141+
- Keep only for compatible RAW/DNG-style transfer
142+
- Drop
143+
- These values are tied to the original sensor geometry and raw-processing
144+
pipeline.
145+
* - Camera raw settings XMP
146+
- ``crs:*`` development settings and raw-edit recipe metadata
147+
- Keep
148+
- Drop
149+
- A rendered file should not normally carry a source raw-edit recipe
150+
unless the host intentionally writes a sidecar/workflow record.
151+
* - Opaque MakerNote payloads
152+
- vendor MakerNote blobs and private nested IFDs
153+
- Keep by default, or follow explicit MakerNote policy
154+
- Drop
155+
- Decoded safe facts can still be carried through standard EXIF/XMP
156+
fields; the opaque vendor blob is not copied for rendered outputs.
157+
* - C2PA and JUMBF
158+
- APP11/JUMBF boxes, C2PA manifests, assertions, signatures
159+
- Follow explicit JUMBF/C2PA policy
160+
- Drop non-C2PA JUMBF; invalidate C2PA by default if it would otherwise
161+
be kept
162+
- Pixel-changing exports need a new content binding and signature from
163+
the host or signer.
164+
* - Embedded previews and thumbnails
165+
- TIFF preview pages, SubIFD previews, JPEG interchange thumbnail data
166+
- Preserve only within bounded target-owned rewrite rules
167+
- Target-owned; host should regenerate or preserve target previews
168+
- Source previews commonly describe the source pixels, not the rendered
169+
target.
170+
54171
Target Summary
55172
--------------
56173

@@ -266,6 +383,13 @@ targets, OpenMeta can allocate 32-bit item IDs and uses ``infe`` version 3 plus
266383
existing graph has exhausted the usable item-id space or mixes item-table widths
267384
outside this shape, the edit fails instead of truncating IDs.
268385

386+
Newly inserted metadata item payloads are appended to the rebuilt ``idat``
387+
payload. To preserve broad reader compatibility, their ``iloc`` records keep
388+
construction method 0 and use absolute file-offset extents within the existing
389+
field widths. When all retained self-contained item locations can be
390+
represented as absolute extents, OpenMeta compacts the rebuilt ``iloc``
391+
base-offset field width to zero for simpler reader compatibility.
392+
269393
For bounded ICC transfer, OpenMeta removes prior ICC ``colr/prof`` and
270394
``colr/rICC`` properties from ``iprp/ipco``, compacts/remaps existing ``ipma``
271395
associations, appends the transferred ``colr/prof`` property, and associates it

0 commit comments

Comments
 (0)