Skip to content

Commit 82cdc5d

Browse files
committed
Add metadata capability query API and bindings
Introduce a runtime capability query API for host integrations: adds C++ header and implementation (MetadataCapability, families, support levels, helpers) to report read/structured_decode/transfer_prepare/target_edit/raw_preservation per format/family and a stable contract version. Adds Python bindings to expose enums and a dict-shaped query result, updates CMake to build the sources and tests, and adds comprehensive unit tests and smoke-test coverage. Updates documentation and sphinx pages with usage examples and notes about support states (unsupported/supported/bounded/disabled) and build-time XMP disabling when XML support is absent.
1 parent d5a5e64 commit 82cdc5d

12 files changed

Lines changed: 732 additions & 7 deletions

CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -256,6 +256,7 @@ set(OPENMETA_SOURCES
256256
src/openmeta/jumbf_decode.cc
257257
src/openmeta/libraw_adapter.cc
258258
src/openmeta/meta_key.cc
259+
src/openmeta/metadata_capabilities.cc
259260
src/openmeta/metadata_transfer.cc
260261
src/openmeta/meta_store.cc
261262
src/openmeta/meta_edit.cc
@@ -608,6 +609,7 @@ if(OPENMETA_BUILD_TESTS)
608609
tests/interop_export_test.cc
609610
tests/dji_app4_decode_test.cc
610611
tests/makernote_decode_test.cc
612+
tests/metadata_capabilities_test.cc
611613
tests/metadata_transfer_api_test.cc
612614
tests/flir_fff_decode_test.cc
613615
tests/meta_store_test.cc

docs/host_integration.md

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -321,7 +321,40 @@ Python now exposes those same split path/state controls directly:
321321
`xmp_existing_destination_embedded_path`, and
322322
`xmp_existing_destination_sidecar_state`.
323323

324-
## 7. Use The Optional Adobe DNG SDK Bridge
324+
## 7. Query Runtime Capabilities
325+
326+
Hosts can ask OpenMeta what the current build supports before wiring format
327+
menus, warnings, or integration feature flags.
328+
329+
```cpp
330+
#include "openmeta/metadata_capabilities.h"
331+
332+
openmeta::MetadataCapability cap = openmeta::metadata_capability(
333+
openmeta::TransferTargetFormat::Avif,
334+
openmeta::MetadataCapabilityFamily::Xmp);
335+
336+
if (openmeta::metadata_capability_available(cap.target_edit)) {
337+
// The current build can edit AVIF XMP within the reported support level.
338+
}
339+
```
340+
341+
Each operation reports one of `unsupported`, `supported`, `bounded`, or
342+
`disabled`. `bounded` means the capability exists within OpenMeta's documented
343+
contract, not that it is arbitrary metadata-editor parity. `disabled` is used
344+
for compile-time-disabled support such as XMP decode when XML support is not
345+
available.
346+
347+
Python exposes the same query:
348+
349+
```python
350+
cap = openmeta.metadata_capability(
351+
openmeta.TransferTargetFormat.Avif,
352+
openmeta.MetadataCapabilityFamily.Xmp,
353+
)
354+
print(cap["target_edit_name"])
355+
```
356+
357+
## 8. Use The Optional Adobe DNG SDK Bridge
325358

326359
If OpenMeta was built with `OPENMETA_WITH_DNG_SDK_ADAPTER=ON`, you can use the
327360
optional SDK bridge in two ways.
@@ -355,7 +388,7 @@ openmeta::apply_prepared_dng_sdk_metadata(
355388
This bridge is for applications that already use the Adobe DNG SDK. OpenMeta
356389
still does not encode pixels or invent raw-image structure.
357390

358-
## 8. Build `MetaStore` Yourself
391+
## 9. Build `MetaStore` Yourself
359392

360393
If your application creates metadata directly, build the store first and then
361394
reuse the same export and transfer APIs.

docs/metadata_support.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,11 @@ It is meant to answer four basic questions:
1414
- `Partial`: supported, but still bounded or best-effort
1515
- `No`: not supported yet
1616

17+
Host integrations can query the same kind of runtime support information with
18+
`openmeta/metadata_capabilities.h`. That API reports read, structured decode,
19+
transfer preparation, target edit, and raw-preservation support by target
20+
format and metadata family.
21+
1722
## Coverage Snapshot
1823

1924
Current tracked-gate status:

docs/metadata_transfer_plan.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -777,7 +777,7 @@ writer-confidence slice above; it should be sequenced around it.
777777

778778
#### Near-Term Host Contract Work
779779

780-
- [ ] add a small runtime capability query API for read, structured decode,
780+
- [x] add a small runtime capability query API for read, structured decode,
781781
transfer preparation, and target edit support by format and metadata family
782782
- [ ] mark public host-facing APIs with stability levels such as stable,
783783
experimental, or internal; start with `visit_metadata(...)`, snapshot

docs/quick_start.md

Lines changed: 36 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -286,7 +286,39 @@ The Python snapshot helpers intentionally cover the core transfer/edit/persist
286286
path. The older `transfer_probe(...)` / `unsafe_transfer_probe(...)` entry
287287
points remain the broader artifact-dump/debug surface.
288288

289-
## 8. Prepare Metadata For Host-Owned Encoders
289+
## 8. Check Runtime Capabilities
290+
291+
Use the capability API before enabling format/family operations in a host UI
292+
or integration layer.
293+
294+
```cpp
295+
#include "openmeta/metadata_capabilities.h"
296+
297+
const openmeta::MetadataCapability cap = openmeta::metadata_capability(
298+
openmeta::TransferTargetFormat::Avif,
299+
openmeta::MetadataCapabilityFamily::Xmp);
300+
301+
if (openmeta::metadata_capability_available(cap.target_edit)) {
302+
// The current build can edit AVIF XMP within the reported support level.
303+
}
304+
```
305+
306+
Each operation reports `unsupported`, `supported`, `bounded`, or `disabled`.
307+
`bounded` means the capability exists within OpenMeta's documented contract.
308+
`disabled` is used for compile-time-disabled support such as XMP decode when
309+
XML support is not available.
310+
311+
Python exposes the same query:
312+
313+
```python
314+
cap = openmeta.metadata_capability(
315+
openmeta.TransferTargetFormat.Avif,
316+
openmeta.MetadataCapabilityFamily.Xmp,
317+
)
318+
print(cap["target_edit_name"])
319+
```
320+
321+
## 9. Prepare Metadata For Host-Owned Encoders
290322

291323
Some applications do not want a file helper. They already own the encoder or
292324
the output container and just want OpenMeta to prepare metadata bytes.
@@ -375,7 +407,7 @@ transfer path is designed for that kind of integration through
375407
For fuller C++ host-side examples, see
376408
[host_integration.md](host_integration.md).
377409
378-
## 9. Optional Adobe DNG SDK Bridge
410+
## 10. Optional Adobe DNG SDK Bridge
379411
380412
If OpenMeta was built with `OPENMETA_WITH_DNG_SDK_ADAPTER=ON`, you can update
381413
an existing DNG file through the Adobe SDK bridge:
@@ -390,7 +422,7 @@ openmeta::ApplyDngSdkMetadataFileResult result =
390422
This is for applications that already use the Adobe DNG SDK. It is not a raw
391423
image encoder.
392424

393-
## 10. CLI And Python Are Convenience Layers
425+
## 11. CLI And Python Are Convenience Layers
394426

395427
The CLI and Python bindings are useful, but they are thin layers over the same
396428
public C++ APIs.
@@ -413,7 +445,7 @@ print(doc.entry_count)
413445
PY
414446
```
415447

416-
## 11. Pick The Next Doc
448+
## 12. Pick The Next Doc
417449

418450
- Detailed read support:
419451
[metadata_support.md](metadata_support.md)

docs/sphinx/host_integration.rst

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -284,6 +284,40 @@ Python now exposes those same split path/state controls directly:
284284
``xmp_existing_destination_embedded_path``, and
285285
``xmp_existing_destination_sidecar_state``.
286286

287+
Query runtime capabilities
288+
--------------------------
289+
290+
Hosts can ask OpenMeta what the current build supports before wiring format
291+
menus, warnings, or integration feature flags.
292+
293+
.. code-block:: cpp
294+
295+
#include "openmeta/metadata_capabilities.h"
296+
297+
openmeta::MetadataCapability cap = openmeta::metadata_capability(
298+
openmeta::TransferTargetFormat::Avif,
299+
openmeta::MetadataCapabilityFamily::Xmp);
300+
301+
if (openmeta::metadata_capability_available(cap.target_edit)) {
302+
// The current build can edit AVIF XMP within the reported support level.
303+
}
304+
305+
Each operation reports one of ``unsupported``, ``supported``, ``bounded``, or
306+
``disabled``. ``bounded`` means the capability exists within OpenMeta's
307+
documented contract, not that it is arbitrary metadata-editor parity.
308+
``disabled`` is used for compile-time-disabled support such as XMP decode when
309+
XML support is not available.
310+
311+
Python exposes the same query:
312+
313+
.. code-block:: python
314+
315+
cap = openmeta.metadata_capability(
316+
openmeta.TransferTargetFormat.Avif,
317+
openmeta.MetadataCapabilityFamily.Xmp,
318+
)
319+
print(cap["target_edit_name"])
320+
287321
Optional Adobe DNG SDK bridge
288322
-----------------------------
289323

docs/sphinx/quick_start.rst

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -243,6 +243,39 @@ The Python snapshot helpers intentionally cover the core transfer/edit/persist
243243
path. The older ``transfer_probe(...)`` / ``unsafe_transfer_probe(...)``
244244
entry points remain the broader artifact-dump/debug surface.
245245

246+
Check runtime capabilities
247+
--------------------------
248+
249+
Use the capability API before enabling format/family operations in a host UI
250+
or integration layer.
251+
252+
.. code-block:: cpp
253+
254+
#include "openmeta/metadata_capabilities.h"
255+
256+
const openmeta::MetadataCapability cap = openmeta::metadata_capability(
257+
openmeta::TransferTargetFormat::Avif,
258+
openmeta::MetadataCapabilityFamily::Xmp);
259+
260+
if (openmeta::metadata_capability_available(cap.target_edit)) {
261+
// The current build can edit AVIF XMP within the reported support level.
262+
}
263+
264+
Each operation reports ``unsupported``, ``supported``, ``bounded``, or
265+
``disabled``. ``bounded`` means the capability exists within OpenMeta's
266+
documented contract. ``disabled`` is used for compile-time-disabled support
267+
such as XMP decode when XML support is not available.
268+
269+
Python exposes the same query:
270+
271+
.. code-block:: python
272+
273+
cap = openmeta.metadata_capability(
274+
openmeta.TransferTargetFormat.Avif,
275+
openmeta.MetadataCapabilityFamily.Xmp,
276+
)
277+
print(cap["target_edit_name"])
278+
246279
Next steps
247280
----------
248281

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
// SPDX-License-Identifier: Apache-2.0
2+
3+
#pragma once
4+
5+
#include "openmeta/metadata_transfer.h"
6+
7+
#include <cstdint>
8+
9+
/**
10+
* \file metadata_capabilities.h
11+
* \brief Runtime capability query API for host integrations.
12+
*/
13+
14+
namespace openmeta {
15+
16+
/// Stable metadata capability contract version.
17+
inline constexpr uint32_t kMetadataCapabilitiesContractVersion = 1U;
18+
19+
/// Metadata family used by \ref metadata_capability.
20+
enum class MetadataCapabilityFamily : uint8_t {
21+
Exif,
22+
Xmp,
23+
Icc,
24+
Iptc,
25+
MakerNote,
26+
PhotoshopIrb,
27+
Jumbf,
28+
C2pa,
29+
BmffFields,
30+
GeoTiff,
31+
ExrAttribute,
32+
};
33+
34+
/// Support level for one operation in \ref MetadataCapability.
35+
enum class MetadataCapabilitySupport : uint8_t {
36+
Unsupported,
37+
Supported,
38+
Bounded,
39+
Disabled,
40+
};
41+
42+
/// Capability record for one format/family pair.
43+
struct MetadataCapability final {
44+
TransferTargetFormat format = TransferTargetFormat::Jpeg;
45+
MetadataCapabilityFamily family = MetadataCapabilityFamily::Exif;
46+
47+
MetadataCapabilitySupport read = MetadataCapabilitySupport::Unsupported;
48+
MetadataCapabilitySupport structured_decode
49+
= MetadataCapabilitySupport::Unsupported;
50+
MetadataCapabilitySupport transfer_prepare
51+
= MetadataCapabilitySupport::Unsupported;
52+
MetadataCapabilitySupport target_edit
53+
= MetadataCapabilitySupport::Unsupported;
54+
55+
/// Existing raw-carrier preservation in current transfer flows. This does
56+
/// not imply raw-preserving source snapshots.
57+
MetadataCapabilitySupport raw_preservation
58+
= MetadataCapabilitySupport::Unsupported;
59+
};
60+
61+
const char*
62+
metadata_capability_family_name(MetadataCapabilityFamily family) noexcept;
63+
64+
const char*
65+
metadata_capability_support_name(MetadataCapabilitySupport support) noexcept;
66+
67+
bool
68+
metadata_capability_available(MetadataCapabilitySupport support) noexcept;
69+
70+
MetadataCapability
71+
metadata_capability(TransferTargetFormat format,
72+
MetadataCapabilityFamily family) noexcept;
73+
74+
} // namespace openmeta
75+

0 commit comments

Comments
 (0)