openmeta/metadata_translation.h provides bounded explicit projection between
metadata families. Current contracts translate edited XMP creation dates into
native EXIF/IPTC date groups, exact technical XMP/TIFF properties into native
EXIF fields, typed capture properties into native EXIF scalars, target-bound
image geometry into native TIFF/EXIF groups, and exact descriptive, flat
location, and editorial XMP properties into native IPTC-IIM datasets before writing.
The APIs are experimental and versioned by
kMetadataDateTranslationContractVersion == 1 and
kMetadataTechnicalTranslationContractVersion == 1 and
kMetadataCaptureTranslationContractVersion == 1 and
kMetadataGeometryTranslationContractVersion == 1 and
kMetadataDescriptiveTranslationContractVersion == 1 and
kMetadataLocationTranslationContractVersion == 1 and
kMetadataEditorialTranslationContractVersion == 1 and
kMetadataIptcTranslationContractVersion == 1 and
kMetadataGpsTranslationContractVersion == 1 and
kMetadataStructuredLocationTranslationContractVersion == 1 and
kMetadataGpsNavigationTranslationContractVersion == 1 and
kMetadataLearningOptOutInTranslationContractVersion == 1 and
kMetadataProfileTranslationContractVersion == 1.
Translation is a separate step. Creation, editing, transfer, and writing do not invoke it implicitly:
- Read, create, or edit a finalized
MetaStore. - Call
translate_xmp_creation_dates(...),translate_xmp_technical_metadata(...),translate_xmp_capture_metadata(...),translate_xmp_image_geometry(...),translate_xmp_descriptive_metadata(...),translate_xmp_location_metadata(...),translate_xmp_editorial_metadata(...),translate_xmp_iptc_metadata(...),translate_xmp_gps_metadata(...),translate_xmp_structured_location_metadata(...),translate_xmp_gps_navigation_metadata(...),translate_xmp_learning_opt_out_in_metadata(...),translate_xmp_profile_metadata(...), or the required combination with explicit mapping and conflict options. - Pass the returned finalized store to transfer preparation or a writer.
This separation prevents a transfer from unexpectedly replacing native camera
dates merely because decoded XMP is present. The default source mode is
DirtyOnly, so only caller-modified XMP values and tombstones are eligible.
| XMP source | Native destination | Precision requirements |
|---|---|---|
xmp:CreateDate |
EXIF DateTimeDigitized, OffsetTimeDigitized, and SubSecTimeDigitized |
Time is required. Timezone and up to nine fractional digits are preserved in companion tags. |
xmp:CreateDate |
IPTC DigitalCreationDate and DigitalCreationTime |
Date-only or whole seconds are accepted. Fractional seconds are rejected. |
photoshop:DateCreated |
IPTC DateCreated and TimeCreated |
Date-only or whole seconds are accepted. Fractional seconds are rejected. |
XMP exif:DateTimeOriginal |
EXIF DateTimeOriginal, OffsetTimeOriginal, and SubSecTimeOriginal |
Time is required. Timezone and up to nine fractional digits are preserved in companion tags. |
Accepted values use a full Gregorian YYYY-MM-DD date, optionally followed by
T and hh:mm:ss, up to nine fractional digits, and Z or a +/-HH:MM
timezone. Invalid dates, malformed values, and conversions that would discard
precision fail instead of being normalized or truncated. Lexical -00:00 is
preserved as a negative-zero offset.
Each mapping can be disabled independently. For example, callers that need to
retain fractional xmp:CreateDate can disable its IPTC projection while
keeping exact EXIF projection.
| XMP source | Native EXIF destination | Requirements |
|---|---|---|
xmp:ModifyDate |
IFD0 DateTime plus ExifIFD OffsetTime and SubSecTime |
Time is required. Timezone and up to nine fractional digits are preserved in companion tags. |
tiff:Make |
IFD0 Make |
Non-empty 7-bit ASCII without embedded NUL bytes. |
tiff:Model |
IFD0 Model |
Non-empty 7-bit ASCII without embedded NUL bytes. |
xmp:CreatorTool |
IFD0 Software |
Non-empty 7-bit ASCII without embedded NUL bytes. |
Namespaces and property paths must match exactly. These mappings are intended
for edited or newly created host metadata, not for copying source-bound camera
processing data. Each singleton and the complete ModifyDate companion group
reconcile independently, so a conflict in Make does not silently change
Model.
| XMP source | Native EXIF destination | Required native type |
|---|---|---|
exif:ExposureTime |
ExifIFD ExposureTime |
One unsigned RATIONAL, greater than zero |
exif:FNumber |
ExifIFD FNumber |
One unsigned RATIONAL, greater than zero |
exif:ISO, exif:ISOSpeedRatings, or exif:ISOSpeedRatings[1] |
ExifIFD ISOSpeedRatings |
One SHORT in 1..65535 |
exif:FocalLength |
ExifIFD FocalLength |
One unsigned RATIONAL, greater than zero |
exif:ExposureCompensation or exif:ExposureBiasValue |
ExifIFD ExposureBiasValue |
One signed SRATIONAL |
Rational sources may be typed scalar XMP values or full text integers,
decimals, scientific decimals, and numerator/denominator values. Focal length
also accepts the OpenMeta portable mm suffix. Conversion uses integer
arithmetic and reduces the exact source value before checking the 32-bit EXIF
numerator and denominator limits. It never uses a floating-point approximation.
For example, 2.8 becomes 14/5 and 8e-3 becomes 1/125.
This exactness is intentionally strict. A bounded repeating decimal such as
0.333333333333333 does not fit native SRATIONAL exactly and returns
ValueOutOfRange; provide 1/3 or a typed signed rational when exact thirds
are required. ISO rejects multi-value arrays, decimals, zero, and values above
65535 instead of selecting, truncating, or changing the native TIFF type.
Portable and standard aliases target the same native singleton. If more than one eligible alias is present, the source is ambiguous and translation fails rather than selecting one.
The 0.5.4 capture synchronization milestone inventories
all ten capture APIs, their 46 distinct native targets, shared-target composition
and the portable round-trip fixes in 0.5.5. Primary ExifIFD FNumber, FocalLength
and DigitalZoomRatio now emit exact numerator/denominator values. FocalLength
is in millimeters; the portable value no longer has the display suffix mm.
Invalid scalar types/counts and values leave valid existing XMP available.
Previously rounded packets must be regenerated from native EXIF to recover
original precision. Reader and host display mappings remain unchanged.
translate_xmp_capture_settings_metadata(...) and Python
Document.translate_capture_settings_metadata(...) add twelve independent
SHORT singleton mappings with MetadataCaptureSettingsTranslationOptions,
contract version 1. Exact unindexed paths use http://ns.adobe.com/exif/1.0/.
| XMP path | ExifIFD tag | Accepted codes | Flag |
|---|---|---|---|
| ExposureProgram | 0x8822 | 0..8 | exposure_program_to_exif |
| MeteringMode | 0x9207 | 0..6, 255 | metering_mode_to_exif |
| SensingMethod | 0xA217 | 1..5, 7, 8 | sensing_method_to_exif |
| CustomRendered | 0xA401 | 0..1 | custom_rendered_to_exif |
| ExposureMode | 0xA402 | 0..2 | exposure_mode_to_exif |
| WhiteBalance | 0xA403 | 0..1 | white_balance_to_exif |
| SceneCaptureType | 0xA406 | 0..3 | scene_capture_type_to_exif |
| GainControl | 0xA407 | 0..4 | gain_control_to_exif |
| Contrast | 0xA408 | 0..2 | contrast_to_exif |
| Saturation | 0xA409 | 0..2 | saturation_to_exif |
| Sharpness | 0xA40A | 0..2 | sharpness_to_exif |
| SubjectDistanceRange | 0xA40C | 0..3 | subject_distance_range_to_exif |
Sources accept signed or unsigned scalar integers in the listed sets, unsigned
decimal integer text, or exact existing OpenMeta enum labels. SceneCaptureType
also accepts the portable label Night scene for code 3. Text must use Ascii,
Utf8, or Unknown encoding. Case changes, whitespace, signs in text, decimals,
fractions, floats, arrays, and unknown codes fail. ExposureProgram code 9/Bulb
remains a read-only extension. No camera state or rendering behavior is inferred.
Defaults are DirtyOnly/FailOnConflict with every field enabled. Eligible duplicate sources fail; clean sources are ignored in DirtyOnly, following the existing numeric capture contract. Native values must be one SHORT and equal to the selected code. PreserveExisting keeps existing fields, FailOnConflict protects non-equivalent values, and ReplaceExisting repairs types/duplicates. Dirty source tombstones remove the corresponding field under ReplaceExisting. All selected fields share one atomic transaction, including source/output aliasing and owned provenance. Existing numeric capture options remain unchanged.
Limits are 12 added entries, 1024 operations, 128 source text bytes per selected active property, and 1536 total source text bytes. Limits may be lowered and never cause truncation. Preparation may allocate. EXIF versions are retained; the API does not create or upgrade version metadata.
Capture coverage has no fixed all-tags denominator. Camera/lens/spectral text and direct APEX values have separate contracts below, as do focal-plane/subject arrays. These twelve settings do not close arbitrary EXIF writeback.
translate_xmp_capture_rational_metadata(...) and Python
Document.translate_capture_rational_metadata(...) use
MetadataCaptureRationalTranslationOptions, contract version 1, for four
independent unsigned RATIONAL singletons. Exact unindexed source paths use
http://ns.adobe.com/exif/1.0/.
| XMP path | ExifIFD tag | Value contract | Flag |
|---|---|---|---|
| SubjectDistance | 0x9206 | Meters; zero unknown; wire numerator UINT32_MAX infinity | subject_distance_to_exif |
| DigitalZoomRatio | 0xA404 | Nonnegative ratio; zero means digital zoom unused | digital_zoom_ratio_to_exif |
| ExposureIndex | 0xA215 | Positive exposure index | exposure_index_to_exif |
| FlashEnergy | 0xA20B | Nonnegative BCPS energy; no Flash-state inference | flash_energy_to_exif |
Sources accept scalar nonnegative integers, unsigned rationals, and the existing exact integer/decimal/scientific/fraction text grammar, including a leading plus. Signed rational and floating-point values, arrays, whitespace, unit suffixes, zero denominators, malformed numbers, and unsupported precision fail. Text must use Ascii, Utf8, or Unknown encoding. Checked uint64 parsing precedes reduction; the reduced numerator and denominator must fit uint32. No rounding occurs.
SubjectDistance adds exact Unknown and Infinity labels. Integer/fraction text
and typed integer/unsigned-rational inputs with numerator UINT32_MAX select
infinity before reduction; their denominator must be positive and fit uint32.
New unknown/infinity values use 0/1 and UINT32_MAX/1. Equivalent existing sentinel
encodings retain their denominator. Decimal/scientific text denotes a finite
number; if its reduced numerator would equal UINT32_MAX, translation fails to
avoid encoding a finite distance as infinity. Other finite inputs that only
reduce to that reserved numerator also fail. Native sentinel equivalence is
checked before ordinary rational cross-multiplication.
Defaults are DirtyOnly/FailOnConflict with all fields enabled. Missing sources retain native fields; eligible duplicates fail. PreserveExisting retains native values, FailOnConflict requires typed equivalence, and ReplaceExisting repairs values/types/duplicates or removes a dirty source tombstone's native field. All selected fields commit atomically, including source/output aliasing and owned provenance. Limits are four additions, 1024 operations, 128 source text bytes per property, and 512 total text bytes. Preparation may allocate.
The canonical EXIF tags 0xA215 and 0xA20B are the only ExposureIndex/FlashEnergy
targets; older TIFF/EP aliases remain untouched. No APEX conversion, unit
conversion, version creation/upgrade, or geographic/flash-state inference occurs.
Existing numeric and settings option layouts remain unchanged. In 0.5.5,
primary native DigitalZoomRatio output retains exact fractions, including zero.
Source spelling can still differ from generated equivalent numeric values.
Use xmp_include_existing=True and XmpConflictPolicy.ExistingWins to retain
authoritative source spelling; choose CanonicalizeManaged when generated
values must replace managed source aliases. These are explicit authority choices.
translate_xmp_flash_metadata(...) and Python
Document.translate_flash_metadata(...) use MetadataFlashTranslationOptions,
contract version 1, to write one ExifIFD Flash (0x9209) SHORT. Select either an
exact scalar exif:Flash or all five children in the EXIF namespace:
Flash/Fired: bit 0, Boolean.Flash/Return: bits 1..2, codes 0, 2, or 3; code 1 is reserved.Flash/Mode: bits 3..4, codes 0..3.Flash/Function: bit 5, Boolean; True means no flash function.Flash/RedEyeMode: bit 6, Boolean.
Children also accept the Flash/exif:Name spelling. Boolean values accept
integer 0/1 or exact text True, False, 0, 1. Mode and Return accept
nonnegative scalar integers or unsigned decimal integer text. Scalar Flash
accepts those integer forms or exact OpenMeta Flash labels. Bits 7 and above,
reserved Return code 1, floats, rationals, arrays, whitespace, signs in text,
case changes, and unknown labels fail. Text encodings are Ascii, Utf8, or Unknown.
All 96 combinations of defined bits are accepted; physical consistency is not
inferred from other capture fields.
Defaults are DirtyOnly/FailOnConflict. One dirty child selects the complete structure, including its clean companions. A partial structure fails without borrowing missing bits from native EXIF. Duplicate child aliases, scalar plus structured sources, qualifiers, nested children, and indexed forms fail. Clean unselected sources are retained. A dirty scalar tombstone or all five dirty child tombstones removes native Flash under ReplaceExisting; partial child removal fails. Missing sources retain native Flash. FlashEnergy is independent.
Native equivalence requires one SHORT with the same bits. PreserveExisting keeps native fields, FailOnConflict protects non-equivalent values, and ReplaceExisting repairs types/duplicates. All operations commit atomically, including aliased source/output and owned provenance. Limits are one added entry, 1024 operations, 16 inspected Flash source properties, 128 text bytes per active property, and 640 total text bytes. Limits may be lowered; preparation may allocate. Existing capture option layouts remain unchanged. EXIF version fields are retained.
For portable XMP readback, use include_existing_xmp=True and
conflict_policy=XmpConflictPolicy.ExistingWins to retain a complete source
structure or scalar spelling. Snapshot transfer uses xmp_include_existing
and xmp_conflict_policy for the same choices. Native-only portable output
continues to use the existing Flash projection.
translate_xmp_light_source_metadata(...) and Python
Document.translate_light_source_metadata(...) use
MetadataLightSourceTranslationOptions, contract version 1, to write one
ExifIFD LightSource (0x9208) SHORT from exact exif:LightSource.
Accepted codes are 0..4, 9..34, and 255: 32 values from the existing
OpenMeta name table. This includes unknown (0), other (255), warm white
fluorescent (16), and the newer light-source/LED codes 25..34. Undefined codes
fail. No source means no change; the API does not create a default Unknown.
Sources accept nonnegative signed or unsigned scalar integers, unsigned decimal
integer text, and exact unique OpenMeta labels. Explicit aliases Tungsten and
Cloudy weather map to 3 and 10. Numeric 1 and 25 remain distinct, while
text Daylight returns AmbiguousSource because both codes use that display
label. Native values, conflict policies, EXIF versions, white balance, Flash,
DNG calibration illuminants and color temperature never resolve this ambiguity.
Callers must supply an explicit numeric code when the source label is ambiguous.
Text must use Ascii, Utf8, or Unknown encoding. Signs in text, whitespace,
case changes, floating-point/scientific notation, fractions, arrays, unsupported
encodings and unknown labels fail. Eligible indexed, qualified or nested
LightSource paths return UnsupportedSourceShape. Foreign namespaces and other
property names are ignored. Clean sources are ignored in default DirtyOnly mode;
All selects active sources. Duplicate eligible sources fail.
Native equivalence requires one SHORT with the selected code. Default FailOnConflict protects different values and wrong types. PreserveExisting retains native data; ReplaceExisting repairs types and duplicates. A dirty source tombstone removes native LightSource under ReplaceExisting. Tombstone contents are not parsed, so an ambiguous old label can still be explicitly removed. Clean tombstones are ignored. Source omission retains native metadata.
Limits are one addition, 1024 operations, 128 text bytes per active property, and 128 total text bytes. Limits may be lowered; invalid settings fail. All changes share the existing atomic capture transaction, including aliased source/output and owned provenance. Preparation may allocate. Existing capture option layouts and EXIF version metadata are retained. This field operation does not claim full EXIF-version conformance or infer other capture metadata.
Portable native-to-XMP output now emits numeric 1 and 25 for these two
LightSource values, preserving their distinction. Other defined values retain
the existing unique labels. Every defined native code survives portable dump,
XMP decode and this reverse API. Existing XMP retained with ExistingWins keeps
its original spelling, including ambiguous Daylight; previously lost numeric
identity cannot be recovered from that text alone. Human-readable native names
and DNG calibration-illuminant display names remain unchanged.
translate_xmp_image_geometry(...) projects edited XMP image geometry only
when it agrees with a host-supplied TransferTargetImageSpec:
| XMP source | Native EXIF destination | Required target fact |
|---|---|---|
tiff:Orientation |
IFD0 Orientation as one SHORT |
has_orientation and the same EXIF index in 1..8 |
tiff:ImageWidth, exif:ExifImageWidth, or exif:PixelXDimension plus a matching height alias |
IFD0 ImageWidth/ImageLength and ExifIFD PixelXDimension/PixelYDimension, each as one LONG |
has_dimensions and the same nonzero stored width/height |
Height aliases are tiff:ImageLength, portable tiff:ImageHeight,
exif:ExifImageHeight, and exif:PixelYDimension. Standard and portable
aliases may coexist when all values agree. Duplicate exact properties or
contradictory aliases fail instead of selecting one.
Width and height always describe the stored target raster. They are not
post-orientation display dimensions and are never swapped for orientation
indices 5..8. For example, a stored 640x480 raster with orientation 6
must use target width 640, target height 480, and orientation 6; a host
that physically rotates the pixels must instead provide the new stored
dimensions and the orientation that applies to those output pixels.
An active source without the corresponding target fact returns
TargetImageSpecRequired. A source value that disagrees with the target, or a
dirty deletion while the target still declares that fact, returns
TargetImageSpecMismatch. Width and height are one complete group: mixed
active/deleted state or only one axis returns IncompleteSourceGroup.
| XMP source | Native IPTC-IIM destination | Maximum encoded bytes |
|---|---|---|
dc:title[@xml:lang=x-default] |
ObjectName (2:5) |
64 |
dc:description[@xml:lang=x-default] |
Caption-Abstract (2:120) |
2000 |
dc:creator[n] |
repeated By-line (2:80) |
32 per value |
dc:subject[n] |
repeated Keywords (2:25) |
64 per value |
dc:rights[@xml:lang=x-default] |
CopyrightNotice (2:116) |
128 |
photoshop:Credit |
Credit (2:110) |
32 |
photoshop:Source |
Source (2:115) |
32 |
The default-language and indexed paths must match exactly. Creator and keyword items retain numeric XMP index order. Duplicate singleton properties or duplicate active indexes are ambiguous and fail rather than selecting a value. IPTC-IIM limits are byte limits; text is never truncated.
Non-ASCII values remain UTF-8. Translation emits IPTC CodedCharacterSet
(1:90) with ESC % G when the marker is absent and every existing active IPTC
value is ASCII or will be replaced by the same transaction. An incompatible
charset marker or unrelated legacy high-bit data returns
NativeEncodingConflict without modifying the output.
translate_xmp_location_metadata(...) accepts
MetadataLocationTranslationOptions and returns
MetadataDescriptiveTranslationResult. It uses the descriptive source modes,
conflict policies, statuses, and mapping diagnostics. The existing descriptive
API still selects only its original seven mappings.
| Exact XMP source | Native IPTC-IIM destination | Maximum encoded bytes |
|---|---|---|
photoshop:City |
City (2:90) |
32 |
Iptc4xmpCore:Location |
Sub-location (2:92) |
32 |
photoshop:State |
Province-State (2:95) |
32 |
photoshop:Country |
Country-PrimaryLocationName (2:101) |
64 |
Iptc4xmpCore:CountryCode |
Country-PrimaryLocationCode (2:100) |
3 |
These are the flat legacy location mappings in the IPTC Photo Metadata Standard. Each mapping is an independent singleton. Namespace URIs and property paths must match exactly; duplicate active sources are rejected even when equal. Empty values, invalid UTF-8/XML text, embedded NULs, and excess bytes fail without changing the output. Removal uses a dirty tombstone.
Country codes require exactly two or three uppercase ASCII letters and are
preserved byte for byte. Code membership, conversion between two and three
letters, and agreement with the country name belong to the caller. Structured
LocationCreated/LocationShown properties, indexed or qualified forms, and
GPS are not aliases and are not selected by this API. The operation does not
infer whether a flat location describes the camera or the depicted subject.
UTF-8 charset promotion follows the descriptive policy above, including rejection of incompatible markers and unrelated legacy high-bit IPTC values. Limits are 1024 matched source properties, 4096 operations, 8 MiB of inspected text/charset-safety bytes, and six added entries (five datasets plus one charset marker). Options can lower these limits. New entries copy source provenance; updates preserve the existing native entry's provenance. Translation is a preparation operation that may allocate; it is not an allocation-free replay API.
translate_xmp_structured_location_metadata(...) accepts
MetadataStructuredLocationTranslationOptions and returns the shared
MetadataDescriptiveTranslationResult. Contract version 1 selects one
structured location and reconciles its five supported text fields into both
flat legacy XMP and native IPTC-IIM. Existing flat-location and combined IPTC
APIs keep their original mapping sets.
The root namespace must be http://iptc.org/std/Iptc4xmpExt/2008-02-29/.
location_kind defaults to MetadataStructuredLocationKind::Shown;
Created requires an explicit choice. Shown and Created never fall back to
each other. These properties distinguish the depicted place from the place
where the camera was. Choosing Created authorizes copying that camera-location
description into the legacy fields whose historical semantics are ambiguous.
See the IPTC Photo Metadata specification.
| Structured child | Flat XMP destination | Native IPTC | Maximum UTF-8 bytes |
|---|---|---|---|
City |
photoshop:City |
2:90 | 32 |
Sublocation |
Iptc4xmpCore:Location |
2:92 | 32 |
ProvinceState |
photoshop:State |
2:95 | 32 |
CountryName |
photoshop:Country |
2:101 | 64 |
CountryCode |
Iptc4xmpCore:CountryCode |
2:100 | 3 |
Paths are LocationShown[n]/City, LocationCreated[n]/City, and the equivalent
paths for the other four children. The existing scalar LocationCreated/City
resource form is accepted as record index 1. Mixed scalar and indexed Created
forms fail. Indexes must be positive decimal uint32 values without leading
zeros. location_index == 0 selects the sole represented record when present;
an absent root kind is a no-op, and multiple records fail with AmbiguousLocation.
A positive option selects that exact index
and fails with LocationNotFound if absent. It never selects the first record
implicitly or merges records. Any represented child, including an unsupported
child, counts for record selection. Selection precedes DirtyOnly filtering.
XML decoding uses one-based RDF order; authored stores can have sparse indexes.
Canonical unqualified child names and Iptc4xmpExt:-qualified equivalents are
accepted. Duplicate field sources are ambiguous, including equal values,
qualified/unqualified aliases, or mixed active and dirty-deleted entries.
Other namespaces, nested Address children, language/index qualifiers, location
names and IDs, WorldRegion, and structured GPS fields are retained without
projection. Nonstandard scalar LocationShown and malformed indexed paths fail
with UnsupportedSourceShape. The API operates on decoded paths and does not
infer a missing RDF container type.
Defaults are DirtyOnly and FailOnConflict. Only the selected structured leaf flags trigger a field in DirtyOnly mode; dirty flat/native destinations do not. All selects clean active leaves too. Each flat XMP/native IPTC pair is one conflict group:
- PreserveExisting retains both destinations if either already exists.
- FailOnConflict requires each existing destination to match and fills a missing counterpart. Mismatches and duplicate destinations fail atomically.
- ReplaceExisting updates both destinations and removes duplicate entries. A dirty structured leaf tombstone removes both corresponding destinations.
Missing structured fields preserve existing destination fields. Clean tombstones and whole-structure/array tombstones do not request field deletion. This is a field patch operation; it does not establish geographic consistency between supplied fields and retained fields. Text must be nonempty valid UTF-8/XML text. Byte limits never truncate. Country codes require two or three uppercase ASCII letters; registry membership and name/code agreement remain caller responsibilities.
Limits are 1024 inspected structured properties across the chosen root kind,
4096 destination operations, 8 MiB of source text and charset-safety inspection,
and 11 added entries: five flat XMP properties, five IPTC datasets, and a possible
UTF-8 marker. Existing destination entries also count against the operation
inspection limit. Each limit can be lowered. The shared IPTC charset policy
rejects unsafe promotion of retained legacy high-bit values. Result entry
counters include both destination families; group counters count five fields.
NativeConflict covers either destination family.
The source stays immutable. Failure leaves the output unchanged; output/source aliasing is supported. New entries own copied source provenance, while updates retain existing destination provenance. Preparation may allocate.
translated = document.translate_structured_location_metadata(
location_kind=openmeta.MetadataStructuredLocationKind.Shown,
location_index=2,
source_mode=openmeta.MetadataDescriptiveTranslationSourceMode.All,
conflict_policy=openmeta.MetadataDescriptiveTranslationConflictPolicy.ReplaceExisting,
)The returned document is detached. Persist both XMP and IPTC from its snapshot
to keep both destinations synchronized. For Python transfer_snapshot_file,
set xmp_include_existing=True: that transfer API defaults to projecting native
metadata only. This option retains the reconciled flat XMP and structured
records. GPS translation is a separate explicit contract; structured location
coordinates are not aliases for primary EXIF GPS.
translate_xmp_location_to_structured_metadata(...) and Python
Document.translate_location_to_structured_metadata(...) copy the five flat
legacy XMP location fields into one selected IPTC Extension record.
MetadataLocationCreationTranslationOptions has contract version 1.
| Exact flat source | Selected structured child |
|---|---|
| photoshop:City | City |
| Iptc4xmpCore:Location | Sublocation |
| photoshop:State | ProvinceState |
| photoshop:Country | CountryName |
| Iptc4xmpCore:CountryCode | CountryCode |
The caller must choose both location_kind (Shown or Created) and a positive
location_index. C++ defaults to Unspecified/zero and rejects those values;
Python requires both arguments. Created permits index one only. Shown permits
an existing record or append at the next index. Existing indexes must be dense
from one, with a maximum of 1024. Sparse indexes, mixed scalar/indexed Created
records, opaque root/record placeholders, and competing nested or language-qualified shapes
for a selected field fail. Other namespaces and nested Address fields are not
aliases. No Created/Shown selection or geographic information is inferred.
New records use indexed LocationShown[n] or LocationCreated[1] paths with
IPTC Extension children. Existing scalar Created records retain their resource
shape. Portable XMP emits indexed locations as RDF Bags. The decoder omits
redundant child prefixes within the same namespace; record order and field
values are preserved. RDF indexes identify current order, not stable record IDs.
City, sublocation, and state use the existing 32-byte flat-location limit; country uses 64 bytes. CountryCode requires two or three uppercase ASCII letters, without checking country membership. Other fields require nonempty valid UTF-8 text, with Ascii/Utf8/Unknown source encoding. Existing flat location text validation applies. There is no normalization or truncation.
Defaults are DirtyOnly/FailOnConflict with five enabled flags. Missing source fields retain destinations. Duplicate selected sources fail. Each destination leaf is a conflict group; PreserveExisting keeps it, FailOnConflict requires equivalence, and ReplaceExisting replaces it and removes duplicate aliases. A dirty flat-field tombstone removes that selected structured leaf. Unrelated fields and records remain untouched. Removal of an entire non-last record fails to prevent renumbering another record during serialization. Removing the last record is allowed. All selected changes commit atomically, including aliased source/output calls. Preparation may allocate.
Limits are five additions, 1024 operations, 1024 total inspected destination
properties plus selected source candidates, and the existing descriptive total
text budget. Native IPTC fields and charset markers are not modified or used
as implicit sources. Native-only callers must explicitly prepare flat XMP first.
To persist the constructed XMP, transfer requires xmp_include_existing=True
and xmp_conflict_policy=openmeta.XmpConflictPolicy.ExistingWins.
Python example:
created = document.translate_location_to_structured_metadata(
openmeta.MetadataStructuredLocationKind.Shown, 1,
source_mode=openmeta.MetadataDescriptiveTranslationSourceMode.All,
)translate_xmp_editorial_metadata(...) accepts
MetadataEditorialTranslationOptions and returns
MetadataDescriptiveTranslationResult. Each mapping has an independent flag;
DirtyOnly and FailOnConflict are the defaults. Descriptive and location
calls retain their existing mapping sets.
| Exact XMP source | Native IPTC-IIM destination | Maximum encoded bytes |
|---|---|---|
photoshop:Headline |
Headline (2:105) |
256 |
photoshop:Instructions |
SpecialInstructions (2:40) |
256 |
photoshop:TransmissionReference |
OriginalTransmissionReference (2:103) |
32 |
The mappings and limits follow the
IPTC Photo Metadata Standard 2025.1.
Sources must be exact singleton properties in the Photoshop namespace
http://ns.adobe.com/photoshop/1.0/. Title, description, rights usage terms,
and other job identifiers are not aliases. Empty text, invalid UTF-8/XML,
embedded NULs, duplicate active singletons, and excess encoded bytes fail
transactionally. Values are never truncated. Dirty tombstones request removal
under ReplaceExisting.
The shared IPTC transaction enforces charset safety, preserves unrelated native data, copies source provenance for new entries, and retains native provenance for updates. Limits are 1024 matched source properties, four added entries (three datasets plus one UTF-8 charset marker), 4096 operations, and 8 MiB of inspected text/charset-safety bytes. Options may lower these limits; disabling every mapping is invalid. This preparation operation may allocate.
Python exposes the same operation as Document.translate_editorial_metadata,
with headline_to_iptc, instructions_to_iptc, and
transmission_reference_to_iptc flags. It returns a detached document and raises
ValueError with mapping diagnostics on failure. For clean metadata read from
a file, explicitly select MetadataDescriptiveTranslationSourceMode.All.
translate_xmp_iptc_metadata(...) accepts MetadataIptcTranslationOptions
and returns MetadataDescriptiveTranslationResult. It selects the seven
descriptive, five location, and three editorial groups above, plus the five
workflow mappings below. All 20 text/priority groups share one transaction,
including charset promotion and resource accounting. The existing subgroup
APIs retain their original options and mappings. Date/time fields remain in
translate_xmp_creation_dates(...); composing the two calls does not make
them one transaction.
| Exact Photoshop XMP source | Native IPTC-IIM destination | Contract |
|---|---|---|
AuthorsPosition |
By-lineTitle (2:85) |
Singleton text, at most 32 UTF-8 bytes |
CaptionWriter |
Writer-Editor (2:122) |
Singleton text, at most 32 UTF-8 bytes |
Category |
Category (2:15) |
One to three ASCII letters, case preserved |
SupplementalCategories[n] |
Repeated SupplementalCategories (2:20) |
Text, at most 32 UTF-8 bytes per value |
Urgency |
Urgency (2:10) |
One text digit from 1 to 8, or a signed/unsigned integer scalar in that range |
These contracts use the Adobe Photoshop XMP namespace, IPTC IIM 4.2, and IPTC Photo Metadata 2025.1. Category, Supplemental Categories, and Urgency are legacy IIM fields. This API supports their explicit writeback; it does not convert them to modern taxonomy. Category does not consult a provider registry. The caller must supply a creator and associate AuthorsPosition with the first creator. Translation neither infers that association nor creates a missing creator.
Supplemental Categories use exact indexed paths with positive numeric indexes.
Gaps are accepted. Values are written in index order, and equal values at
different indexes remain separate datasets. Duplicate active indexes fail.
New repeated native entries receive placement ranks that preserve that order;
source block and wire provenance remain owned by the output. Updates retain
the existing native ranks.
An unindexed scalar or empty Bag is not a removal request; remove the indexed
entries with dirty tombstones. Urgency emits an ASCII digit, never a binary
integer. Values 0 and 9, floats, rationals, arrays, and alternative textual
spellings such as 05, +5, or 5.0 are rejected. IPTC-to-portable-XMP
projection now also carries native Urgency.
All mappings default to enabled, with DirtyOnly/FailOnConflict policies. Each has an independent boolean flag. Disabling every mapping is invalid. Limits across the whole call are 1024 matched source properties, 1025 added entries (up to 1024 values plus one charset marker), 4096 operations, and 8 MiB of inspected text/charset-safety bytes. Callers may lower these limits.
A failure in any selected group leaves the source and output unchanged. This also permits a non-ASCII replacement when another selected group removes the legacy IPTC bytes that would otherwise block UTF-8 promotion. Preparation may allocate; it is not an allocation-free replay operation.
Python exposes Document.translate_iptc_metadata(...) with the same flags,
policies, limits, detached output, and ValueError diagnostics. For example:
translated = document.translate_iptc_metadata(
source_mode=openmeta.MetadataDescriptiveTranslationSourceMode.All,
conflict_policy=openmeta.MetadataDescriptiveTranslationConflictPolicy.ReplaceExisting,
category_to_iptc=False,
urgency_to_iptc=False,
)The combined API and existing date API cover the 24 active IPTC destination
assignments in ExifTool's
xmp2iptc.args inventory
checked on 2026-09-09. This is a field inventory, not semantic or behavioral
parity: the two commented taxonomy mappings, Photoshop IPTCDigest, arbitrary
IPTC datasets, and ExifTool's conversion/overwrite conventions are excluded.
translate_xmp_gps_metadata(...) accepts MetadataGpsTranslationOptions
and returns MetadataGpsTranslationResult. Contract version 1 covers three
primary EXIF GPS groups, each with an independent flag. It does not select
structured IPTC locations, destination coordinates, GPS time, navigation data,
or nonstandard standalone XMP latitude/longitude reference properties.
Exact http://ns.adobe.com/exif/1.0/ XMP source |
Native gpsifd fields |
Flag |
|---|---|---|
GPSLatitude |
LatitudeRef (1), Latitude (2) | latitude_to_exif |
GPSLongitude |
LongitudeRef (3), Longitude (4) | longitude_to_exif |
GPSAltitude and GPSAltitudeRef |
AltitudeRef (5), Altitude (6) | altitude_to_exif |
Coordinates accept unsigned integer degrees followed by decimal minutes, or
integer minutes and decimal seconds: 35,48.125N or 139,34,55.25W.
Separators are commas; the final character must be uppercase N/S for latitude
or E/W for longitude. Signs, whitespace, decimal degrees, exponent notation,
and rational components are rejected. Latitude is bounded by 90 degrees and
longitude by 180; minutes and seconds must be less than 60. At the maximum
degree, all remaining components must be zero. Hemisphere is preserved at zero.
The parser uses integer arithmetic and emits normalized integer degrees, integer minutes, and reduced rational seconds as RATIONAL[3], with an ASCII reference. Fractions must fit exact unsigned 32-bit numerator/denominator components; unsupported precision fails without rounding. Trailing decimal zeros do not consume precision. Parsing uses checked 64-bit intermediates; overflow also returns UnsupportedPrecision, even if further algebraic reduction could fit the native fields. Native equivalence compares typed components as rational numbers; it does not rearrange a different native DMS tuple into an equivalent whole angle. Use ReplaceExisting to canonicalize such tuples.
Altitude uses a nonnegative unsigned rational, nonnegative integer scalar, or
exact unsigned text (123.45, 2469/20, or 0). Floats, signed text, exponent
notation, units, arrays, and zero denominators are rejected. A separate
GPSAltitudeRef is mandatory. This contract uses the legacy Adobe XMP sea-level
convention: text 0/1, or an integer scalar 0/1. It does not infer a missing
reference from the sign or from existing native metadata.
GPSVersionID is structural companion metadata. A missing version becomes BYTE[4]
2.3.0.0 when a selected active result needs it. An existing version must be one
well-formed BYTE[4] entry; malformed or duplicate active versions fail. Existing
version bytes are retained. Altitude supports 2.0.0.0 through 2.4.0.0:
legacy versions use native sea-level references 0/1; version 2.4 uses 2/3 for the
same meaning. Unknown versions fail altitude translation. No geoid, ellipsoid,
horizontal datum, or unit conversion is performed, and native GPSVersionID is
not upgraded. The XMP GPSVersionID property is not copied or used to infer an
altitude convention. Callers with modern ellipsoidal-height XMP must not pass
it under this legacy sea-level contract.
Coordinate syntax and legacy XMP altitude semantics follow the Adobe EXIF namespace and CIPA's EXIF/XMP mapping. The version-2.4 native reference distinction follows EXIF 3.0 section 4.6.7.1.6.
Defaults are DirtyOnly and FailOnConflict. One dirty altitude member selects both active XMP members, including a clean companion. Missing, duplicate, or mixed active/deleted altitude members fail. A complete dirty altitude tombstone pair removes both native fields under ReplaceExisting; one coordinate tombstone removes its native value/reference pair. PreserveExisting preserves the existing native group as a unit when either field exists, even when the group is partial. FailOnConflict rejects mismatched or partial existing groups. ReplaceExisting updates fields and removes duplicates. If deletion removes the last GPS value, its version tag is also removed; unrelated GPS fields keep the version.
The whole call is atomic. It leaves source and output unchanged on failure and copies provenance into output-owned storage. Calls may allocate during preparation. Limits are seven added entries including GPSVersionID, 1024 native operations, 128 text bytes per selected active source, and 512 total text bytes. Coordinates and altitude can be disabled independently; disabling every group is invalid. Existing translation APIs and their default mapping sets are unchanged.
Python exposes the same C++ policy and diagnostics:
translated = document.translate_gps_metadata(
source_mode=openmeta.MetadataGpsTranslationSourceMode.All,
conflict_policy=openmeta.MetadataGpsTranslationConflictPolicy.ReplaceExisting,
)Pass the detached result to transfer preparation to persist it. Portable XMP now writes all four GPSVersionID components. Its existing coordinate/altitude formatters can round native rational values; this reverse API cannot recover precision already lost before the XMP source was created. This contract covers primary position writeback, not all GPS metadata or complete ExifTool parity.
translate_xmp_gps_navigation_metadata(...) uses independent
MetadataGpsNavigationTranslationOptions and the shared GPS result, source
mode, conflict policy, and diagnostic enums. Contract version 1 adds four
atomic groups. The existing primary GPS API retains its options and mapping set.
| Exact EXIF XMP source | Native gpsifd fields |
Flag |
|---|---|---|
GPSTimeStamp |
TimeStamp (7) and DateStamp (29) | timestamp_to_exif |
GPSSpeedRef and GPSSpeed |
SpeedRef (12) and Speed (13) | speed_to_exif |
GPSTrackRef and GPSTrack |
TrackRef (14) and Track (15) | track_to_exif |
GPSImgDirectionRef and GPSImgDirection |
ImgDirectionRef (16) and ImgDirection (17) | image_direction_to_exif |
Sources must use the exact http://ns.adobe.com/exif/1.0/ namespace and
unindexed, unqualified paths. XMP combines the native GPS date and time into
one GPSTimeStamp property. Split GPSDateStamp/time, GPSDateTime aliases,
capture-date fallback, receiver status/quality, destination GPS, and structured
IPTC locations are outside this contract. Field identities and unit/reference
codes follow the Adobe EXIF namespace
and CIPA EXIF/XMP mapping.
GPSTimeStamp requires text YYYY-MM-DDTHH:MM:SS[.fraction]Z or the same complete
date/time followed by +HH:MM or -HH:MM. The timezone is mandatory. Numeric
offsets through 23:59 normalize to UTC, including day/month/year rollover;
both zero-offset signs mean UTC. Years must remain 0001 through 9999 after
normalization. Gregorian calendar rules apply. Hours are 00-23, minutes 00-59,
and seconds are less than 60. Leap seconds, partial dates/times, lowercase
separators, whitespace, and inferred timezones are rejected.
Output contains an ASCII YYYY:MM:DD date and an unsigned RATIONAL[3] time:
integer UTC hours, integer UTC minutes, and reduced exact rational seconds.
Fractions never round. Reduced numerator and denominator must each fit uint32;
checked uint64 parser intermediates can also reject unsupported precision.
Trailing decimal zeros do not consume precision. A supported fractional input
can still fail when its whole-second numerator cannot fit uint32 after reduction.
Speed and angles accept nonnegative integer scalars, unsigned rational scalars, or exact unsigned integer/decimal/fraction text, matching primary GPS altitude syntax. Floats, signed text, exponents, units inside numeric values, arrays, and zero denominators are rejected. Speed has no additional bound beyond the native rational capacity. Track and image direction are bounded by 359.99 inclusive, without rounding or modulo reduction.
Reference properties are text. Speed accepts K, M, or N, plus the exact
existing OpenMeta portable aliases km/h, mph, and knots. Track and image
direction accept T or M, plus True North and Magnetic North. Output uses
the compact ASCII letter code. Case, spelling, and whitespace are exact; no
localized names or other synonyms are accepted. Units and north references
are preserved without conversion or inference from native metadata.
All flags default to enabled, with DirtyOnly/FailOnConflict. A dirty reference or numeric member selects its complete active XMP pair, including a clean companion. Missing, duplicate, or mixed active/deleted members fail. A dirty timestamp tombstone removes both date and time; removing a navigation pair requires both members to be dirty tombstones. Clean tombstones are ignored. Missing groups preserve native fields.
Each native pair uses the primary GPS conflict rules: PreserveExisting keeps the whole group if either member exists; FailOnConflict requires a complete matching pair; ReplaceExisting repairs partial pairs and removes duplicates. Native rational equivalence is exact by component. Native text may include its one trailing wire NUL. Existing equivalent values retain their encoding and provenance. All selected groups share one atomic transaction.
An active timestamp requires native GPSVersionID 2.2.0.0 through 2.4.0.0 under this versioned contract. Older and unknown versions fail without an upgrade. Speed/track/direction retain any well-formed BYTE[4] version. Missing versions become 2.3.0.0 when selected active output needs one. Malformed or duplicate active versions fail. Removing the last GPS value also removes its version; unrelated GPS fields retain it. Source XMP GPSVersionID is not copied.
Limits are nine added entries including GPSVersionID, 1024 native operations, 128 text bytes per selected active property, and 896 total text bytes. Limits can be lowered. Failure leaves source and output unchanged, including when they alias. New entries own copied source provenance; preparation may allocate.
translated = document.translate_gps_navigation_metadata(
source_mode=openmeta.MetadataGpsTranslationSourceMode.All,
conflict_policy=openmeta.MetadataGpsTranslationConflictPolicy.ReplaceExisting,
)Persist the detached result through a transfer snapshot with EXIF enabled.
When retaining source XMP too, set xmp_include_existing=True and
xmp_conflict_policy=openmeta.XmpConflictPolicy.ExistingWins in Python transfer
helpers. Inclusion alone keeps the historical precedence of generated EXIF
values, which can change reference spelling or timestamp formatting. ExistingWins
preserves supplied XMP values over generated counterparts. The portable writer's
native-only decimal formatting is not an exact arbitrary-rational round trip.
translate_xmp_gps_destination_metadata(...) uses independent
MetadataGpsDestinationTranslationOptions and the shared GPS result, source
mode, conflict policy, and diagnostic enums. Contract version 1 adds four
atomic groups; the primary GPS and navigation APIs keep their existing scopes.
| Exact EXIF XMP source | Native gpsifd fields |
Flag |
|---|---|---|
GPSDestLatitude |
DestLatitudeRef (19) and DestLatitude (20) | latitude_to_exif |
GPSDestLongitude |
DestLongitudeRef (21) and DestLongitude (22) | longitude_to_exif |
GPSDestBearingRef and GPSDestBearing |
DestBearingRef (23) and DestBearing (24) | bearing_to_exif |
GPSDestDistanceRef and GPSDestDistance |
DestDistanceRef (25) and DestDistance (26) | distance_to_exif |
Sources use exact unindexed, unqualified paths in
http://ns.adobe.com/exif/1.0/. Coordinates use the primary GPS text syntax:
degrees,decimalMinutesN or degrees,minutes,decimalSecondsN, with the
appropriate N/S or E/W hemisphere. Bounds are 90/180 degrees and minutes or
seconds below 60; components after a maximum degree must be zero. Output is
ASCII hemisphere plus exact reduced unsigned RATIONAL[3], including the
hemisphere of zero. Unsupported precision fails without rounding.
Bearing and distance use the navigation exact nonnegative integer/rational
scalar or unsigned integer/decimal/fraction text syntax. Bearing is in
[0, 359.99], with T/M or exact True North/Magnetic North aliases.
Distance accepts K/M/N for kilometers, miles, and nautical miles, plus
exact Kilometers, Miles, and Nautical miles aliases. These distance
units follow CIPA DC-X010-2017, section 8.3.
Portable XMP now emits Nautical miles for destination unit N; it previously
emitted the incorrect label Knots. The reverse API accepts that historical
capitalized spelling as N distance without converting the number. Lowercase
knots, km/h, and mph are speed references and are rejected for distance.
No bearing, distance, primary position, datum, north reference, or unit is
inferred or converted. Numeric precision follows the primary GPS parser limits.
Defaults are DirtyOnly/FailOnConflict with all four groups enabled. One dirty bearing/distance member selects the complete active pair, including its clean companion. Missing, duplicate, and mixed deleted/active members fail. A dirty coordinate tombstone removes both native coordinate fields; bearing/distance removal requires both XMP members to be dirty tombstones. Clean tombstones are ignored. Missing groups preserve their native values.
Native pairs follow the primary GPS conflict policy. PreserveExisting retains a whole group when either native member exists. FailOnConflict requires a complete equivalent pair. ReplaceExisting repairs partial or malformed groups and removes duplicates. Equivalence compares typed rational components exactly; text may include its one wire NUL. A well-formed native BYTE[4] GPSVersionID is retained without interpreting or upgrading it. Missing versions default to 2.3.0.0 when active output needs one. Malformed or duplicate active versions fail. Removing the final GPS value removes its version; unrelated GPS retains it.
Limits are nine added entries including the version, 1024 operations, 128 text bytes per selected active source, and 768 total text bytes. Limits may be lowered. All selected groups share one transaction; failures leave source and output unchanged, including aliasing. New entries own their provenance. Preparation may allocate. Python returns a detached document:
destination = document.translate_gps_destination_metadata(
source_mode=openmeta.MetadataGpsTranslationSourceMode.All,
conflict_policy=openmeta.MetadataGpsTranslationConflictPolicy.ReplaceExisting,
)Persist the result through transfer preparation. To retain original XMP values
and spelling, Python transfer requires both xmp_include_existing=True and
xmp_conflict_policy=openmeta.XmpConflictPolicy.ExistingWins. Native-only
portable coordinate/rational formatting can be approximate; reverse translation
cannot recover precision already lost from its input. Receiver status/quality,
DOP, and geographic computation remain outside this contract.
translate_xmp_gps_quality_metadata(...) and Python
Document.translate_gps_quality_metadata(...) add five independent singleton
mappings with MetadataGpsQualityTranslationOptions, contract version 1.
Exact unindexed paths use http://ns.adobe.com/exif/1.0/.
| XMP source | Native tag/type | Flag |
|---|---|---|
GPSStatus |
9 / ASCII | status_to_exif |
GPSMeasureMode |
10 / ASCII | measure_mode_to_exif |
GPSDOP |
11 / RATIONAL | dop_to_exif |
GPSDifferential |
30 / SHORT | differential_to_exif |
GPSHPositioningError |
31 / RATIONAL | horizontal_error_to_exif |
Status accepts exact A/V or Measurement Active/Measurement Void text. Measure
mode accepts nonnegative integer 2/3 or exact text 2/3. Differential accepts
integer 0/1, exact text 0/1, or No Correction/Differential Corrected. No other
enum spellings, decimal/fraction coercion, or unknown codes are accepted.
DOP and horizontal error use the existing exact nonnegative rational parser;
floats, signed text, exponents, zero denominators, and unsupported precision fail.
DOP is retained without calculating an accuracy estimate. Horizontal error is
in meters; no unit conversion or receiver-state inference occurs. The field
identities follow CIPA EXIF/XMP mapping.
The v1 version contract accepts GPS 2.2 through 2.4 for differential correction and GPS 2.3 through 2.4 for horizontal error. Earlier or unknown versions fail when the corresponding active field is selected. Other quality fields retain any well-formed native BYTE[4] version. Versions are never upgraded. A missing native version defaults to 2.3.0.0; source XMP GPSVersionID is not copied.
Defaults are DirtyOnly/FailOnConflict and five enabled flags. One dirty source tombstone removes its native singleton. Missing fields and clean tombstones retain native data. Duplicate sources fail. PreserveExisting retains existing native values; FailOnConflict requires typed equivalence; ReplaceExisting repairs malformed values and duplicates. Native ASCII permits one trailing wire NUL; rational equivalence is exact. All five selected fields share one atomic transaction. Removing the last GPS field also removes its version, while unrelated GPS retains it. New entries own their provenance, and failures preserve source/output even when they alias.
Limits are six added entries including version, 1024 operations, 128 text bytes
per selected active source, and 640 total text bytes. Limits can be lowered.
Preparation may allocate. Existing primary, navigation, and destination APIs
keep their scopes. Python returns a detached document for transfer preparation.
Source XMP preservation still requires both xmp_include_existing=True and
xmp_conflict_policy=openmeta.XmpConflictPolicy.ExistingWins during transfer.
translate_xmp_gps_text_metadata(...) and Python
Document.translate_gps_text_metadata(...) add four independent singleton
mappings with MetadataGpsTextTranslationOptions, contract version 1.
Exact unindexed paths use http://ns.adobe.com/exif/1.0/.
| XMP source | Native tag/type | Flag |
|---|---|---|
GPSSatellites |
8 / ASCII | satellites_to_exif |
GPSMapDatum |
18 / ASCII | map_datum_to_exif |
GPSProcessingMethod |
27 / UNDEFINED encoded text | processing_method_to_exif |
GPSAreaInformation |
28 / UNDEFINED encoded text | area_information_to_exif |
Satellites and datum require ASCII. Processing method and area accept valid UTF-8, including supplementary Unicode characters. Source Text values must use Ascii, Utf8, or Unknown encoding; Unknown is interpreted as UTF-8. Empty text is an active value, and spaces are retained exactly. NUL, C0/C1 controls, malformed UTF-8, and unsupported source types/encodings fail. There is no truncation, Unicode normalization, datum conversion, receiver-state inference, or parsing of satellite lists or processing-method names.
Method and area are stored as UNDEFINED bytes with an eight-byte character-code identifier. ASCII content uses the ASCII prefix; non-ASCII content uses the UNICODE prefix followed by a UTF-16LE byte-order marker and exact UTF-16 payload. Supplementary characters use surrogate pairs. New encoded payloads omit the optional terminator; ordinary ASCII fields receive their wire terminator during serialization. Native equivalent text accepts the ASCII prefix or UNICODE with an explicit LE/BE byte-order marker and at most one trailing terminator. JIS, unknown identifiers, malformed text, and Unicode without a byte-order marker are non-equivalent. FailOnConflict protects these native values; PreserveExisting keeps them, while ReplaceExisting replaces them with canonical new output. Portable XMP recognizes the same supported encoded forms and emits decoded text. Unsupported forms are omitted from native-to-XMP projection without modifying the original native entries.
The v1 method/area version contract accepts GPS 2.2 through 2.4; earlier or unknown versions fail when an active encoded field is selected. Satellites and datum retain any well-formed native BYTE[4] version. Missing versions default to 2.3.0.0, with no upgrade or source-XMP version copy.
Defaults are DirtyOnly/FailOnConflict and four enabled flags. Selection, singleton conflicts, duplicate repair, dirty tombstone removal, version cleanup, owned provenance, and source/output aliasing follow the quality GPS contract. All selected fields commit atomically. Empty strings do not request deletion. Limits are five added entries including version, 1024 native operations, 4096 source text bytes per selected active property, and 16384 total text bytes. Encoded output is bounded by twice the source byte limit plus its prefix/BOM; encoding overhead does not consume the source-text budget. Limits may be lowered. Preparation may allocate.
Python returns a detached document. Persist it through transfer preparation;
retain source XMP using both xmp_include_existing=True and
xmp_conflict_policy=openmeta.XmpConflictPolicy.ExistingWins. The five GPS
translation APIs now cover the repository's 32 standard native GPS tag IDs,
including paired companions and automatically maintained GPSVersionID. This
is bounded writeback coverage; it does not imply support for every possible
encoding, version, or arbitrary metadata synchronization policy.
All translation APIs apply the following behaviors to each complete native group. Location and editorial translation reuse the descriptive conflict-policy enum.
| Policy | Behavior |
|---|---|
PreserveExisting |
Keep the complete native group when any member already exists. |
FailOnConflict |
Require the group to be absent or already exactly equivalent. This is the default. |
ReplaceExisting |
Replace the group, tombstone duplicates, and tombstone stale companion fields. |
A dirty deleted XMP source is propagated only with ReplaceExisting; the
corresponding native group is tombstoned. Duplicate eligible XMP source
properties are rejected as ambiguous rather than choosing one occurrence.
The operation is transactional. The source is immutable and the output store
is replaced only after all selected mappings parse, reconcile, and finalize.
New native entries retain the source XMP block and wire provenance, with any
referenced wire-type name copied into output-owned storage.
Date max_added_entries and max_operations may lower the public hard limits
of 16 added entries and 1024 native operations. Technical translation has
separate hard limits of 6 added entries, 1024 operations, 4096 bytes per text
property, and 16 KiB total source text. Calls keep no global state and are safe
when each concurrent call owns its output store.
Capture translation separately permits at most 5 added entries, 1024
operations, 128 bytes per text property, and 640 total source text bytes. Its
tag-specific conversion always emits scalar RATIONAL, SHORT, or
SRATIONAL values, so generic writer type inference cannot select a different
TIFF type.
Geometry translation permits at most 5 added entries, 1024 operations, 32
bytes per textual property, and 160 total source text bytes. One dirty width or
height member makes the complete active dimension group eligible in
DirtyOnly mode. A complete dirty width/height tombstone pair may remove the
native group under ReplaceExisting only when the target spec does not still
declare dimensions; orientation follows the same target-contradiction rule.
Descriptive translation separately bounds matched source properties, added
entries, operations, and total text bytes. In DirtyOnly mode, one dirty member
makes the complete repeated creator/keyword group eligible, so unchanged active
members are retained while dirty tombstones can remove native values under
ReplaceExisting.
#include "openmeta/metadata_transfer.h"
#include "openmeta/metadata_translation.h"
openmeta::MetadataDateTranslationOptions options;
options.conflict_policy
= openmeta::MetadataDateTranslationConflictPolicy::ReplaceExisting;
openmeta::MetaStore translated;
const openmeta::MetadataDateTranslationResult result
= openmeta::translate_xmp_creation_dates(edited, options, &translated);
if (result.status != openmeta::MetadataDateTranslationStatus::Ok) {
// translated is unchanged.
}
openmeta::MetadataTechnicalTranslationOptions technical_options;
technical_options.conflict_policy
= openmeta::MetadataTechnicalTranslationConflictPolicy::ReplaceExisting;
openmeta::MetaStore technical;
const auto technical_result = openmeta::translate_xmp_technical_metadata(
translated, technical_options, &technical);
openmeta::MetadataCaptureTranslationOptions capture_options;
capture_options.conflict_policy
= openmeta::MetadataCaptureTranslationConflictPolicy::ReplaceExisting;
openmeta::MetaStore capture;
const auto capture_result = openmeta::translate_xmp_capture_metadata(
technical, capture_options, &capture);
openmeta::TransferTargetImageSpec target;
target.has_dimensions = true;
target.width = output_width;
target.height = output_height;
target.has_orientation = true;
target.orientation = output_orientation;
openmeta::MetaStore geometry;
const auto geometry_result = openmeta::translate_xmp_image_geometry(
capture, target, openmeta::MetadataGeometryTranslationOptions {},
&geometry);
openmeta::MetadataDescriptiveTranslationOptions descriptive_options;
descriptive_options.conflict_policy
= openmeta::MetadataDescriptiveTranslationConflictPolicy::ReplaceExisting;
openmeta::MetaStore descriptive;
const auto descriptive_result = openmeta::translate_xmp_descriptive_metadata(
geometry, descriptive_options, &descriptive);
openmeta::MetadataLocationTranslationOptions location_options;
location_options.conflict_policy
= openmeta::MetadataDescriptiveTranslationConflictPolicy::ReplaceExisting;
openmeta::MetaStore location;
const auto location_result = openmeta::translate_xmp_location_metadata(
descriptive, location_options, &location);Python calls the same C++ transaction and returns a detached Document:
translated = edited.translate_creation_dates(
conflict_policy=openmeta.MetadataDateTranslationConflictPolicy.ReplaceExisting,
)
translated = translated.translate_technical_metadata(
conflict_policy=openmeta.MetadataTechnicalTranslationConflictPolicy.ReplaceExisting,
)
translated = translated.translate_capture_metadata(
conflict_policy=openmeta.MetadataCaptureTranslationConflictPolicy.ReplaceExisting,
)
translated = translated.translate_image_geometry(target_image_spec)
translated = translated.translate_descriptive_metadata(
conflict_policy=openmeta.MetadataDescriptiveTranslationConflictPolicy.ReplaceExisting,
)
translated = translated.translate_location_metadata(
conflict_policy=openmeta.MetadataDescriptiveTranslationConflictPolicy.ReplaceExisting,
)Invalid or lossy requests raise ValueError containing the C++ status,
mapping, and source entry ID. The original Document is not mutated.
This milestone is intentionally limited to exact creation-date, common technical and capture EXIF, target-bound orientation/stored dimensions, primary GPS coordinates/time/navigation, combined descriptive/editorial/workflow IPTC, flat IPTC Core locations, capture settings, and explicit structured-location reconciliation/construction. It does not yet provide arbitrary EXIF/IPTC/XMP translation, broader target image-layout/storage projection, multilingual-alternative selection, timezone inference, numeric approximation or value repair, or automatic synchronization during transfer.
translate_xmp_sensitivity_metadata(...) and Python
Document.translate_sensitivity_metadata(...) use independent
MetadataSensitivityTranslationOptions, contract version 1. The seven ExifIFD
fields form one transaction and one result group:
XMP property in http://cipa.jp/exif/1.0/ (exifEX) |
Native tag | Type/range |
|---|---|---|
| PhotographicSensitivity | 0x8827 | SHORT, 1..65535 |
| SensitivityType | 0x8830 | SHORT, 0..7 |
| StandardOutputSensitivity | 0x8831 | LONG, 1..4294967295 |
| RecommendedExposureIndex | 0x8832 | LONG, 1..4294967295 |
| ISOSpeed | 0x8833 | LONG, 1..4294967295 |
| ISOSpeedLatitudeyyy | 0x8834 | LONG, 1..4294967295 |
| ISOSpeedLatitudezzz | 0x8835 | LONG, 1..4294967295 |
Input values are unsigned scalar integers or decimal integer text with optional
leading +. Signed scalars, fractions, labels, zero sensitivity values, overflow,
and malformed/indexed/qualified fields fail. This positive-value restriction is
OpenMeta's bounded writeback policy. Exact namespaces are required. Legacy
OpenMeta exif:ISO, exif:ISOSpeedRatings, and exif:ISOSpeedRatings[1] are
base aliases; the six companion names in the older exif namespace are also
accepted for historical portable packets. Duplicate properties or aliases fail
even when equal. The existing ISO-only API retains its 1..65535 contract.
An active group requires both base sensitivity and type. Type 0 means unknown; 1 selects SOS, 2 REI, 3 ISO speed, 4 SOS+REI, 5 SOS+ISO, 6 REI+ISO, and 7 all three. Any supplied selected parameters must equal the base value below 65535, or use base 65535 for values at or above that limit. Plural selected parameters must equal each other as full LONG values, including above the SHORT limit. Unselected parameters may differ. Optional selected parameters may be absent; a marker without its extended value remains a marker. No type or missing high-range value is inferred. Latitude values require both latitude fields and ISOSpeed. EXIF versions and unrelated exposure fields are retained.
Defaults are DirtyOnly/FailOnConflict. One dirty recognized member selects the whole group, including clean active companions. All selects active sources and dirty tombstones. No selected source retains the native group. Once selected, missing or deleted optional sources specify absent target fields. A dirty base tombstone with no active group members requests complete removal; an orphan companion tombstone or partial required pair fails. Source XMP entries remain available for caller-controlled serialization.
PreserveExisting retains the complete native group if any member exists. FailOnConflict requires the entire group to be absent or exactly equivalent, including presence, SHORT/LONG types and duplicate counts. ReplaceExisting updates the group and removes stale/duplicate native members. Validation runs before all policies, and failures preserve separate or aliased output. Limits are seven added entries, 1024 operations, 128 text bytes per property and 896 text bytes in total; callers may lower them. Preparation may allocate.
Portable native output now uses exifEX for the six companions. A base
with native SensitivityType uses exifEX:PhotographicSensitivity; an
ISO-only base retains exif:ISO. These seven properties participate in
the existing CanonicalizeManaged namespace policy. Complete generated
groups round-trip through this API, including the full unsigned LONG range.
Existing source XMP can be
retained with ExistingWins; independently authored duplicate base aliases still
require caller resolution. Snapshot persistence covers JPEG, Classic TIFF and
BigTIFF creation, replacement and removal.
The wire and companion rules follow the CIPA EXIF 2.32 English translation (section 4.6.6 and Annex G) and EXIF 2.31 XMP mapping tables. This bounded API is not a claim of complete current EXIF conformance or automatic reconciliation with every legacy ISO-array convention.
References: CIPA EXIF translation, CIPA EXIF metadata for XMP.
translate_xmp_camera_text_metadata(...) and Python
Document.translate_camera_text_metadata(...) use independent
MetadataCameraTextTranslationOptions and the existing technical source-mode,
conflict-policy, result and diagnostic enums. Contract version 1 adds six
independent native targets in one transaction.
| Exact XMP property | Native ExifIFD tag | Option |
|---|---|---|
exif:SpectralSensitivity |
0x8824 ASCII |
spectral_sensitivity_to_exif |
exifEX:CameraOwnerName |
0xa430 ASCII |
camera_owner_name_to_exif |
exifEX:BodySerialNumber |
0xa431 ASCII |
body_serial_number_to_exif |
exifEX:LensMake |
0xa433 ASCII |
lens_make_to_exif |
exifEX:LensModel |
0xa434 ASCII |
lens_model_to_exif |
exifEX:LensSerialNumber |
0xa435 ASCII |
lens_serial_number_to_exif |
Here exif is http://ns.adobe.com/exif/1.0/ and exifEX is
http://cipa.jp/exif/1.0/. The five identity fields also accept the same property
names in the historical OpenMeta exif namespace. Prefix spelling is irrelevant;
namespace URI and property path must match. aux:Lens, aux:SerialNumber,
OwnerName, generic Lens aliases and MakerNote identity inference are excluded.
The tag types follow Exif 2.32;
the canonical XMP identity mappings follow
CIPA metadata interchange guidance.
An active value must be nonempty Text with Ascii or Utf8 encoding, containing
only printable ASCII bytes 0x20..0x7e. Leading/trailing spaces, punctuation and
serial-number leading zeros are preserved. XMP decoding retains their boundary
whitespace in description attributes, resource values and element text. Empty values, other value kinds or
encodings fail with InvalidSourceValue; NUL, controls, DEL and non-ASCII bytes
fail with NonAsciiSource. The bounded printable subset permits lossless
portable XML round trips. No trimming, transliteration, Unicode conversion or
ASTM spectral-curve grammar validation is performed. The latter remains the
caller's responsibility; this API transports the supplied text.
All six switches default to true. DirtyOnly selects each dirty property
individually. All also selects active clean entries; deleted entries still
require Dirty. Missing or disabled properties retain native metadata. Eligible
duplicates, including canonical/legacy alias pairs with equal values, fail with
AmbiguousSource. Indexed, qualified or nested forms of a selected property fail
with UnsupportedSourceShape. A selected dirty tombstone ignores its payload and
removes only that native field under ReplaceExisting.
Each field has its own conflict decision and result group. FailOnConflict
rejects a mismatch, PreserveExisting retains the conflicting field while other
fields can translate, and ReplaceExisting updates the singleton and removes
native duplicates. Native equivalence requires Text with Ascii or Utf8
encoding and matching bytes after native terminal NUL bytes are removed; byte
blobs, numbers and other encodings are conflicts. New values use native ASCII.
All source validation, conflict decisions and resource checks finish before one
commit. Failure leaves separate or aliased output unchanged. Source provenance
and text ownership survive the source store's lifetime.
Limits are 6 added entries, 1024 edit operations, 4096 bytes per property and 24576 total source-text bytes. Limits must be nonzero and at most those ceilings. At least one switch must be enabled. Existing technical and capture option layouts and existing enum values are unchanged.
Portable native identity fields now use canonical exifEX names. That namespace
is declared only when needed. Spectral sensitivity remains in exif.
CanonicalizeManaged recognizes all six fields. If callers retain legacy source
XMP along with generated canonical identity properties, both aliases can remain
under ordinary preservation policies; reverse translation rejects their
ambiguity. Use CanonicalizeManaged when replacing those managed aliases.
Shared tests cover all six fields, conflict and removal transactions, source shapes and budgets, portable XML and namespace policies, serialized snapshots through JPEG/Classic TIFF/BigTIFF, the Python wrapper and an installed shared library consumer. Related fields are implemented and reviewed together, with focused checks during development and one final platform matrix per stable batch. Lens specification and image identity use the separate contract below. APEX and focal-plane/subject arrays are covered by the combined batches below.
translate_xmp_identity_metadata(...) and Python
Document.translate_identity_metadata(...) use
MetadataIdentityTranslationOptions, contract version 1, and the existing
capture source modes, conflict policies, statuses and result counters. The two
fields have independent switches and conflict decisions, followed by one commit.
| Source | Native ExifIFD field | Option |
|---|---|---|
exifEX:LensSpecification |
0xA432, four unsigned RATIONALs |
lens_specification_to_exif |
exif:ImageUniqueID |
0xA420, ASCII, 33 bytes on wire |
image_unique_id_to_exif |
Lens specification also accepts the historical OpenMeta exif namespace alias.
The four ordered components are minimum focal length, maximum focal length,
minimum F-number at the minimum focal length, and minimum F-number at the maximum
focal length. Focal lengths must be positive, with minimum <= maximum. Apertures
must be positive or exactly 0/0, the EXIF unknown-aperture marker. The API does
not infer missing values, constrain one aperture relative to the other, or parse
lens model descriptions.
Accepted lens shapes are one URational array of exactly four elements, or four
scalar properties named LensSpecification[1] through [4]. Indexed values
accept unsigned integer/rational scalars or ASCII/UTF-8 numeric text: integer,
decimal, scientific notation or numerator/denominator. Conversion is exact and
reduced to unsigned 32-bit components; unrepresentable values fail. Floating
point scalars, units, opaque lists, sparse/noncanonical indexes, nested fields,
root/index mixtures and mixed or duplicate namespace aliases fail. The store
retains flattened indexes, not the original RDF Seq/Bag container kind; hosts
must supply the four components in the documented order. Portable output uses a
standard exifEX RDF Seq with exact fraction text and preserves 0/0.
Under DirtyOnly, any dirty lens member selects the whole lens group, including
clean companions. A root tombstone must stand alone. Four dirty indexed
tombstones also remove the group; partial deletion fails. Missing sources and
clean tombstones do nothing. All selects clean active sources too. Image ID
selection is independent and accepts only the exact scalar property.
Image IDs require exactly 32 hexadecimal ASCII characters (0-9, a-f, A-F),
stored as ASCII/UTF-8 Text. No trimming, case folding, hyphen removal or ID
creation occurs. Leading zeros and letter case survive. A native equivalent must
be ASCII/UTF-8 Text with those same bytes, optionally followed by one NUL. The
serializer writes one terminator for the required 33-byte wire representation.
Generic validation and validated authoring allow 0/0 only in the exact
unsigned lens aperture slots (native array, XMP root array or scalar [3]/[4]).
Other zero-denominator values remain errors. The translation contract also checks
positive values, focal ordering and completeness. Native lens equivalence
requires a four-element URational array with equal
rational values and exact unknown markers; equivalent unreduced values remain.
Malformed native types or duplicate native tags are conflicts.
FailOnConflict rejects differences, PreserveExisting retains existing native
fields, and ReplaceExisting applies updates, duplicate repair and requested
removals. Every source check and budget check precedes mutation of either field.
Failure retains both a separate output and an aliased input/output unchanged.
Preparation and commit may allocate; synchronization of shared objects is the
host's responsibility. The API has no internal synchronization or ID allocator.
Limits are 2 added native entries, 1024 edit operations, 128 text bytes per source member, and 544 total text bytes (four lens members plus a 32-byte image ID). Callers can lower these positive limits. Typed lens arrays are fixed at 32 bytes and do not consume the text budget. Source properties count actual selected entries; successful translations count at most two groups.
Portable native output skips malformed lens/ID values. CanonicalizeManaged
recognizes the canonical lens array and removes managed source copies before
native projection. Preservation policies may retain a historical alias alongside
a generated canonical value, which reverse translation rejects as ambiguous.
XMP decoding preserves boundary whitespace for exif:ImageUniqueID, so invalid
padded IDs are rejected consistently for attributes, resources and element text.
The structural contract follows the Exif 2.3 field definitions and CIPA Exif/XMP mappings. It does not validate UUID generation/version, global uniqueness, or an application's capture-time identity retention policy. Hosts decide whether an explicit replacement or removal is appropriate for their workflow and Exif version. No existing EXIF version is upgraded implicitly.
Combined tests cover C++, Python, installed shared consumers, exact portable round trips, and serialized snapshots through JPEG, Classic TIFF and BigTIFF. The APEX and focal-plane/subject batches below extend this coverage.
translate_xmp_apex_metadata(source, MetadataApexTranslationOptions{}, &output)
writes five scalar properties in one transaction. Python exposes the same
contract as Document.translate_apex_metadata(...). The result, source modes,
conflict policies and diagnostics use the existing capture types.
Exact source in http://ns.adobe.com/exif/1.0/ |
ExifIFD target | Native type | Independent option |
|---|---|---|---|
ShutterSpeedValue |
0x9201 |
SRATIONAL, count 1 |
shutter_speed_value_to_exif |
ApertureValue |
0x9202 |
RATIONAL, count 1 |
aperture_value_to_exif |
BrightnessValue |
0x9203 |
SRATIONAL, count 1 |
brightness_value_to_exif |
ExposureBiasValue or ExposureCompensation |
0x9204 |
SRATIONAL, count 1 |
exposure_bias_value_to_exif |
MaxApertureValue |
0x9205 |
RATIONAL, count 1 |
max_aperture_value_to_exif |
All inputs are direct APEX values. There is no conversion from seconds or f-numbers, inference from ExposureTime/FNumber, exposure-equation check, version upgrade, or consistency check between the five fields. The two aperture fields accept zero and positive values. The signed fields accept either sign. The usual brightness/bias range of -99.99 to 99.99 is not a hard limit.
The structural types and brightness sentinel follow CIPA DC-008-2012, camera information tags and Annex C. The XMP property names follow CIPA DC-X010-2017, Exif mappings. This contract does not claim full conformance to every tag or later Exif revision.
Sources may be scalar signed/unsigned integers, the target rational type, or
ASCII/UTF-8/Unknown-encoding text containing an integer, decimal, scientific
number or numerator/denominator. Signed integers for aperture fields must be
nonnegative. Signed rational fields require SRational, and unsigned rational
fields require URational. Floating-point, arrays, structured children,
qualifiers, units, whitespace in numeric text and zero/negative denominators
fail. XMP decoding retains its existing whitespace normalization; this batch
does not change reader behavior. Exact fractions reduce to 32-bit components;
numeric text parsing uses bounded 64-bit intermediates. Overflow fails without
approximation. Denominators are positive, at most INT32_MAX for signed fields
or UINT32_MAX for unsigned fields.
Brightness reserves a wire numerator of -1 (0xffffffff) for unknown,
regardless of its positive denominator. Explicit fraction text -1/n, a typed
SRational{-1, n}, or exact text Unknown selects this sentinel before
reduction and writes -1/1. Sentinel fraction denominators must fit INT32_MAX.
Integer and decimal/scientific inputs are finite: -1, -1.0 and a signed
integer -1 write -2/2. A finite -0.5 or explicit -2/4 writes -2/4.
Reduced finite fractions with numerator -1 use -2/(2n); if the doubled
denominator cannot fit, translation returns ValueOutOfRange. Unknown and
finite native brightness values never compare equal during conflict handling.
Positive-denominator native unknown fractions compare equivalent to each other.
All five switches default to true. At least one must be enabled. DirtyOnly selects eligible dirty scalar sources; All also selects active clean sources. Deleted sources require Dirty in either mode. Missing sources retain native values. Each field reconciles independently with PreserveExisting, FailOnConflict or ReplaceExisting, and duplicate eligible aliases fail even when their values agree. Indexed/structured shapes of enabled properties fail. Unrelated names, namespaces and disabled mappings are ignored.
Native equivalence requires the correct scalar type/count and equal rational value, with the brightness sentinel rule above. ReplaceExisting repairs wrong types and duplicate native entries. A dirty tombstone removes all native instances under ReplaceExisting. Parsing, conflicts and shared limits complete before one commit. Failure preserves both separate and aliased output; success owns its values and provenance. Preparation may allocate. The host synchronizes conflicting access to shared stores.
Default and hard maximum budgets are five added entries, 1024 edit operations,
128 bytes per text property and 640 total text bytes. Duplicate repair and
removal consume the operation budget. Lower positive caller limits are allowed.
The exposure-bias mapping and its diagnostic XmpExposureCompensation are
shared with the older capture API; that API's options and input behavior remain
unchanged.
Generated portable XMP now emits exact fractions for all five native APEX tags.
For example, native shutter 6/1 emits ShutterSpeedValue=6/1; native aperture
4/1 emits ApertureValue=4/1. Previous releases emitted seconds (1/64) and
f-numbers (4.0) under these names. Old generated packets cannot be identified
reliably from their values; regenerate them from native EXIF before reverse
translation. No heuristic migration is performed.
Brightness and exposure compensation also use fractions instead of rounded
floating-point text. Existing correctly typed scalar XMP APEX rationals also
retain their exact wire fractions, including noncanonical unknown brightness. The existing portable ExposureCompensation alias remains.
Unknown brightness emits -1/1; finite wire fractions retain their numerator
so the sentinel distinction survives. Wrong native types/counts and nonpositive
denominators are omitted. Large representable APEX values remain exact without
exponentiation or a physical aperture limit. Default existing-XMP precedence
remains unchanged; CanonicalizeManaged permits valid native values to replace
managed source properties. Host FlatHost/Spec projections are separate and
unchanged.
The APEX batch added four native targets, bringing capture-related coverage at 0.5.3 to 41 targets across nine APIs. The spatial batch below extends this. OIIO/iRAW application acceptance remains a separate downstream task.
translate_xmp_capture_spatial_metadata, MetadataCaptureSpatialTranslationOptions
and Python Document.translate_capture_spatial_metadata provide three logical
groups in one transaction. Sources use http://ns.adobe.com/exif/1.0/.
| XMP property | ExifIFD target | Native shape | Group switch |
|---|---|---|---|
FocalPlaneXResolution |
0xa20e |
RATIONAL, count 1 | focal_plane_to_exif |
FocalPlaneYResolution |
0xa20f |
RATIONAL, count 1 | focal_plane_to_exif |
FocalPlaneResolutionUnit |
0xa210 |
SHORT, count 1 | focal_plane_to_exif |
SubjectArea |
0x9214 |
SHORT array, count 2, 3 or 4 | subject_area_to_exif |
SubjectLocation |
0xa214 |
SHORT array, count 2 | subject_location_to_exif |
Focal-plane X/Y values are pixels per explicit unit for the recorded image.
All three source fields must be present together. Positive scalar signed or
unsigned integers, URational, or ASCII/UTF-8 integer, decimal, scientific and
fraction text are accepted for X/Y. Fractions reduce exactly using bounded
64-bit intermediates and must fit unsigned 32-bit components. Zero, negative
values, zero denominators, floating point and approximation are rejected.
The unit accepts integer 2 or 3, integer text with an optional leading plus,
or the exact portable labels inches and cm. Units 1, 4 and 5 and labels
mm/um are outside this writeback contract. There is no default or conversion.
SubjectArea holds X/Y for a point; a circle adds diameter; a rectangle adds width and height. SubjectLocation holds X/Y independently. Values are preserved as supplied relative to the upper-left origin before orientation processing. No rotation, crop, image-dimension check, positive-size rule or consistency relation between the two arrays is inferred. Every element may be 0..65535.
Sources can be one root U16 array or dense indexed members [1] through [N].
Indexed members accept scalar signed/unsigned integers in range or ASCII/UTF-8
integer text, optionally prefixed with +. They reject decimal/fraction text,
float/rational values, nested arrays, qualifiers, duplicate or sparse indexes,
leading-zero indexes and mixed root/indexed forms. SubjectArea requires 2..4
members; SubjectLocation requires exactly two. The store flattens RDF arrays,
so callers must supply the documented order; Seq/Bag provenance is not retained.
All three switches default to true, and at least one must remain enabled. DirtyOnly selects a group when any member is dirty; All also selects active clean sources. Selected groups include all active clean companions and dirty tombstones. Clean tombstones are ignored. Duplicate active/dirty sources fail, even when values agree. Disabled groups and unrelated namespaces are ignored.
Every selected group must be wholly present or wholly deleted. An array root tombstone, or a complete dense deleted source array, removes the native array under ReplaceExisting. Focal-plane removal requires all three tombstones. Partial deletion fails instead of changing an array's meaning or leaving units behind. A missing group retains native values. PreserveExisting preserves the entire group if any native member exists; FailOnConflict requires all native members to match; ReplaceExisting repairs partial groups, wrong types and duplicates. Equality checks exact native type/count, rational value and ordered array contents. Results count up to three logical groups and five native entries.
Parsing, conflicts and shared limits precede one commit, preserving separate and aliased output on failure. Success owns array storage and provenance. Default and hard bounds are five added entries, 1024 edit operations, 128 text bytes per property and 1152 total text bytes. Positive lower limits are allowed. Preparation may allocate; hosts synchronize conflicting shared-object access.
In 0.5.4, native and existing typed XMP focal-plane rationals emit exact fractions. Earlier rounded decimal packets cannot recover the original exact fraction; regenerate them from native EXIF when exact recovery is required. Subject arrays emit ordered sequences and participate in managed replacement as arrays. Invalid native spatial types/counts/spans and nonpositive resolutions are omitted before claiming a property. Valid existing XMP remains available. Existing unit read labels, including mm/um, and default existing-XMP precedence remain. Host FlatHost/Spec and reader decode paths are unchanged.
The field types and coordinate meanings follow CIPA DC-008-2012, camera information tags; the XMP namespace and ordered-array mappings follow CIPA DC-X010-2017. Requiring explicit complete groups and positive resolutions is this bounded OpenMeta contract. This is not full Exif conformance or downstream acceptance. The five new targets bring capture-related coverage to 46 targets across ten APIs.
Two explicit, bounded APIs extend the capture family to twelve calls and 55
distinct ExifIFD targets. Both reuse capture source modes, conflict policies,
dirty tombstones, failure details and transaction rules. Python provides
Document.translate_capture_additional_metadata(...) and
Document.translate_environment_metadata(...) with the same defaults and
limits. Each call publishes independently; hosts that need an entire multi-call
workflow transaction should keep the candidate detached until every call succeeds.
| API / property | Native tag and shape | Accepted meaning |
|---|---|---|
| Additional: exif:FocalLengthIn35mmFilm | A405 SHORT, 1 | Millimetres, 0 unknown, maximum 65535. FocalLengthIn35mmFormat is an explicit alias and remains the portable output spelling. |
| Additional: exif:FileSource | A300 UNDEFINED, 1 | 0 other, 1 transparent scanner, 2 reflex scanner, 3 digital still camera. |
| Additional: exif:SceneType | A301 UNDEFINED, 1 | 1 directly photographed image. |
| Environment: exifEX:Temperature | 9400 SRATIONAL, 1 | Degrees Celsius. |
| Environment: exifEX:Humidity | 9401 RATIONAL, 1 | Percent. No additional physical range is imposed. |
| Environment: exifEX:Pressure | 9402 RATIONAL, 1 | hPa. |
| Environment: exifEX:WaterDepth | 9403 SRATIONAL, 1 | Metres; negative above water level. |
| Environment: exifEX:Acceleration | 9404 RATIONAL, 1 | mGal (10^-5 m/s²). |
| Environment: exifEX:CameraElevationAngle | 9405 SRATIONAL, 1 | Degrees, finite values in [-180, 180). |
Additional capture inputs accept scalar integer values and decimal integer
text, with no labels, units, rounding or sensor-size inference. Native
FileSource and SceneType remain one-byte MetaValueKind::Bytes, and portable
XMP emits integer text. Environment inputs accept the matching typed rational,
integers and exact decimal/scientific/fraction text. Floating-point values and
unit suffixes are rejected. The CIPA namespace URI is
http://cipa.jp/exif/1.0/; legacy EXIF namespace environment properties are
accepted as aliases. Duplicate eligible aliases, indexed/structured paths and
malformed scalar shapes fail before mutation.
For every environment tag, raw denominator 0xffffffff means unknown.
SRATIONAL represents those bits as -1. Recognition precedes reduction, and
unknown numerators are preserved. Typed SRational{-7, -1} and text -7/-1
or -7/4294967295 retain the same unknown value. Unsigned 7/4294967295 also
retains its numerator. Text Unknown uses numerator zero. Other denominators
must be positive; a finite fraction that reduces to the reserved denominator
is rejected. Finite fractions reduce exactly within their native integer width.
Portable output uses exact numerator/denominator text for these typed values.
CanonicalizeManaged removes legacy namespace aliases only when a valid native
replacement will be emitted. Invalid native shapes, codes and finite elevation
ranges leave existing XMP available. PreserveAll retains explicit aliases,
which can subsequently fail reverse translation as ambiguous.
The per-call defaults are 3 or 6 added entries, 1024 primitive edit operations, 128 text bytes per property, and 384 or 768 total text bytes. Options may lower these bounds. Typed editing before translation is described in editing.md.
Contracts follow the native definitions in CIPA Exif 2.32 and the namespace/property mappings in CIPA Exif metadata for XMP.
C++ translate_xmp_image_encoding_metadata and Python
Document.translate_image_encoding_metadata cover Gamma (A500),
CompressedBitsPerPixel (9102), and ComponentsConfiguration (9101). Gamma uses
exifEX (http://cipa.jp/exif/1.0/); legacy exif is an explicit alternative.
The other two use exif (http://ns.adobe.com/exif/1.0/). Gamma and compressed
bits per pixel accept finite nonnegative unsigned rationals, including zero.
Explicit fractions retain their numerator and denominator. Unit suffixes and
physical interpretation are not accepted. ComponentsConfiguration is exactly
four codes in 0..6, supplied as a typed unsigned array or dense [1]..[4]
properties. Native storage is UNDEFINED count four. No component order or
uniqueness is inferred.
C++ translate_xmp_composite_metadata and Python
Document.translate_composite_metadata reconcile three native fields as one
transaction:
| Native tag | Contract |
|---|---|
| CompositeImage A460 | SHORT count one: 0 unknown, 1 non-composite, 2 general composite, 3 composite captured while shooting. Companions require code 2 or 3; code 3 requires both. |
| SourceImageNumberOfCompositeImage A461 | SHORT count two: total >=2; used is 0 (unavailable) or 2..total. |
| SourceExposureTimesOfCompositeImage A462 | Bounded UNDEFINED structure with seven summary rationals and an optional exposure list, described below. |
Canonical XMP uses the full property names in exifEX. Explicit alternatives
are the legacy Adobe exif namespace and ExifTool's short roots
CompositeImageCount and CompositeImageExposureTimes. Mixing roots or
namespaces within one property is ambiguous. The abandoned CIPA /2.32/
namespace is not an implicit alias.
The exposure structure has seven unsigned rational members, in this order:
TotalExposurePeriod, SumOfExposureTimesOfAll, SumOfExposureTimesOfUsed,
MaxExposureTimesOfAll, MaxExposureTimesOfUsed, MinExposureTimesOfAll, and
MinExposureTimesOfUsed. Each finite value is nonnegative seconds; 0/0 means
unavailable. Native offsets 0..55 contain these seven rationals. A SHORT
NumberOfSequences (m) follows at offset 56. If m is zero, the payload ends at
58 bytes and has no n or Values. Otherwise a positive SHORT
NumberOfImagesInSequences (n) follows at offset 58, and exactly m*n rationals
follow at offset 60. XMP Values is an ordered sequence or typed URational
array. Each list value has a positive denominator, including when its numerator
is zero. The list contains at least two values and agrees with the total source
count when that companion exists. Summary totals and extrema are not computed
or cross-checked against the list.
Both APIs use the capture source modes, conflict policies, result statuses and
per-call transaction rules. Any selected composite member selects its complete
source group, including clean companions. ReplaceExisting also removes
missing native companions. Deleting CompositeImage together with its companions
removes the group. Dirty tombstones can remove n and Values when changing m to
zero. Duplicate, sparse, qualified, incomplete and mixed root/indexed structures
fail without publishing the candidate. Limits include three added entries per
API, the existing 1024-operation ceiling, 128 text bytes per property, 768 total
text bytes for encoding and 1 MiB for composite capture. Composite translation
has a configurable limit of at most 4096 exposure values.
Typed native editing and validation enforce the same individual and group constraints. Portable output validates a native group before replacing source XMP, emits exact fractions and canonical structured fields, and can remove stale managed companions when a valid native code 0 or 1 replaces a composite group. Invalid native metadata does not suppress valid source XMP.
New A462 payloads are little-endian. Decoding a big-endian TIFF retains the raw
bytes and sets EntryFlags::ValueBigEndian. Typed Set and low-level
SetValueWithWire clear this flag for new little-endian data; plain low-level
SetValue retains it, so its caller must preserve the existing byte order.
Canonical EXIF serialization writes little-endian values. Transfer converts the
embedded structure to the destination TIFF byte order. Snapshot v1 layout and
existing golden bytes are unchanged; snapshots using the new flag require
OpenMeta 0.5.7 or later. Host-facing raw values remain intact.
Target transfer continues to filter all three source encoding fields, including canonical CIPA Gamma, because these properties can depend on the source pixels. Direct EXIF serialization retains all six fields. The combined fixture therefore has 61 native tags before transfer and 58 retained source tags afterward. No codec processing, pixel rewrite or automatic image calibration is added.
The binary layout follows CIPA DC-008-Translation-2023-E, section 4.6.6.7.49, Figure 25. XMP uses CIPA DC-010-2024, section 6.5 and Table A.5: EXIF specification, EXIF/XMP mapping.
translate_xmp_structured_capture_metadata(source, options, &output) adds four
independent field groups in one transaction. Python exposes
Document.translate_structured_capture_metadata(...) and returns a new document
with the same policies and limits. Defaults select dirty sources and fail on
native conflicts; preparation may allocate. The host synchronizes conflicting
access to shared objects.
| EXIF field | Tag | Canonical exif: structure |
|---|---|---|
| OECF | 8828 | Columns, Rows, Names, Values (signed rational pairs) |
| SpatialFrequencyResponse | A20C | Columns, Rows, Names, Values (unsigned rational pairs) |
| CFAPattern | A302 | Columns, Rows, Values (color codes 0 through 6) |
| DeviceSettingDescription | A40B | Columns, Rows, Values (ordered setting strings) |
The namespace is http://ns.adobe.com/exif/1.0/. Dimensions are positive SHORT
values. Tables require one NUL-free ASCII name per column and exactly
Columns times Rows values. CFA requires the same number of color codes.
Device dimensions describe the display; they do not determine the number of
settings. At least one setting is required; individual strings may be empty.
No dimensions, photographic relationships or CFA byte-order heuristics are
inferred. Legacy SpatialFrequencyResponse 920C is outside this contract.
Table values preserve explicit numerator/denominator pairs without reduction. OECF accepts signed 32-bit components, including negative denominators; SpatialFrequencyResponse accepts unsigned 32-bit components. Zero denominators are rejected. Device settings accept valid UTF-8 representable in XML 1.0. Embedded NULs, invalid Unicode and disallowed XML characters are rejected. Spaces, tabs, CR/LF, DEL and supplementary characters retain their values.
The canonical spelling is Columns. The published CIPA table spelling
Columus is an explicit OECF/SFR input alias. Mixed aliases, qualifiers,
sparse indices and root/child mixtures fail. Text lists use dense one-based
paths such as OECF/Names[1]. Numeric Values may instead be typed rational
arrays or unsigned integer arrays for CFA. A root Bytes value is an alternative
validated little-endian native payload. Any selected member selects the whole
structure, including clean companions. All-deleted source members remove the
field; partially populated active structures fail. PreserveExisting,
FailOnConflict and ReplaceExisting act independently on the four fields, but
any error rolls back the entire call.
Default hard limits are 256 columns, 4096 values per field, 4096 bytes per text property, 1 MiB total source text per call, 1 MiB native payload per field, four added entries and 1024 edit operations. Options may lower these limits. They cannot increase them. Typed native editing validates the same wire layout and character rules; raw decoding retains malformed data for inspection. Portable output skips malformed native structures without suppressing valid existing XMP.
Native UNDEFINED payloads use two SHORT dimensions. Tables then contain
NUL-terminated ASCII names and rational pairs; CFA contains byte codes.
Each device string has its own UTF-16 BOM and terminator. Newly authored
strings use UTF-16LE; decoded strings may mix BOM byte orders.
EntryFlags::ValueBigEndian records the order of native headers and table
rational components without changing the raw bytes. EXIF serialization and
transfer convert those numbers to the destination order while preserving
string bytes. Typed replacement supplies a new little-endian payload and
clears the provenance flag. Snapshot v1 has no layout change, but snapshots
carrying big-endian values for these four tags require OpenMeta 0.5.8 or later.
Older 0.5.7 readers only understand this flag for composite exposure data.
Snapshots produced before 0.5.8 can lack byte-order provenance for these four
fields. Reopen the original container with 0.5.8 when recovering those
big-endian values; the old raw payload alone does not establish its byte order.
Compatible-file transfer retains these four fields. Rendered-image transfer removes CFAPattern from both EXIF and XMP under the existing calibration policy. It does not transform image samples or infer a replacement pattern.
The combined fixture now covers 65 distinct ExifIFD tags across fifteen APIs; target transfer retains 62 after its existing three encoding-field exclusions. The structured batch includes JPEG, classic TIFF and BigTIFF, both TIFF byte orders, snapshot persistence, source fallback, native-only and retained-XMP round trips, Unicode and transactional failures. This is a supported-profile inventory, not a claim of complete EXIF or arbitrary RDF editing.
Layout references: CIPA DC-008-Translation-2023-E, Figures 16, 20, 21 and 23; CIPA DC-010-2024-E, corresponding EXIF/XMP structure mappings. Source PDFs and independent encoded-file qualification remain in OpenMeta-internal.
translate_xmp_exif_text_metadata and Python
Document.translate_exif_text_metadata add one bounded transaction for the
following properties. Exact canonical namespaces are required.
| XMP property | Native ExifIFD tag | Value contract |
|---|---|---|
| exif:ExifVersion | 9000 | Four ASCII digits in UNDEFINED count 4; no NUL. |
| exif:FlashpixVersion | A000 | 0100 in UNDEFINED count 4; no NUL. |
| exif:UserComment | 9286 | Character-set prefix plus text; XMP scalar or [@xml:lang=x-default]. |
| exifEX:ImageTitle | A436 | ASCII or UTF-8 text; empty means unknown. |
| exifEX:Photographer, ImageEditor | A437, A438 | ASCII or UTF-8 text; require explicit native IFD0 Artist. |
| exifEX:CameraFirmware, RAWDevelopingSoftware, ImageEditingSoftware, MetadataEditingSoftware | A439–A43C | ASCII or UTF-8 text; require explicit native IFD0 Software. |
| exifEX:CameraOwnerName, LensMake, LensModel | A430, A433, A434 | ASCII or UTF-8 text. These overlap the earlier camera-text API. |
Versions use four digits exactly. The API validates their syntax; accepting a
future EXIF version number does not establish conformance with that future
specification. FlashpixVersion is restricted to the defined 0100 value.
Text accepts ASCII or UTF-8 MetaValue text with valid XML 1.0 characters.
Whitespace, empty strings, carriage returns and supplementary Unicode characters
are preserved. A leading BOM, embedded NUL, invalid UTF-8, UTF-16 source text or
numeric source fails. Native type 2 and type 129 counts include one terminal
NUL. Type 129 carries UTF-8 without a BOM. Plain ASCII output uses type 2.
Decoded type 129 is retained on serialization, including ASCII-only payloads.
The seven new tags and non-ASCII owner/lens text require an explicit effective
ExifVersion of 0300 or later. The effective version is the native version after
this call's selected conflict policy. The library does not upgrade it silently.
Artist and Software must already exist as nonempty native text, with exactly one
active entry. The caller supplies those values through authoring/editing; the
translator does not infer them from photographer, editor or software details.
General store validation checks field shapes and encodings. It is not a complete
EXIF mandatory-tag/profile validator. These version and companion requirements
belong to this translation contract.
UserComment authoring uses ASCII\0\0\0 for ASCII. Non-ASCII uses
UNICODE\0 followed by UTF-8 for EXIF 3, or UTF-16LE with a BOM for older or
missing versions. UNDEFINED comments do not require a terminal NUL. Native
projection recognizes explicit UTF-16 BOMs in either version family. Without a
BOM, UNICODE follows the declared EXIF version: UTF-8 for EXIF 3 and the TIFF
byte order for legacy UTF-16. The existing nonstandard UTF8 marker is accepted
for reading only. JIS and undefined character sets remain opaque; no guessed
conversion or new charset dependency is introduced. Raw snapshots preserve the
original bytes. Semantic translation preserves text, not padding or the original
character-set choice.
Version changes cannot reinterpret retained comments or downgrade retained
EXIF 3 text. Supply the comment source with ReplaceExisting, or a dirty
comment tombstone, when changing its encoding family. An explicit UTF-16 BOM
keeps legacy comments readable independently of the version and TIFF byte order.
The decoder records ValueBigEndian for native UserComment. Snapshot v1 layout
and ABI 3 remain unchanged; BOM-less big-endian comment snapshots require a
0.5.9 reader. Older snapshots may lack provenance; reopen their original files
with 0.5.9 to recover it.
Portable output emits UserComment as one rdf:Alt with x-default. Other
language alternatives remain in XMP, including under CanonicalizeManaged.
The reverse API chooses only the scalar/default source. It rejects eligible
scalar/default duplicates and unsupported structured/indexed shapes. No locale
is selected automatically. Invalid native comments do not hide valid source XMP.
Defaults follow the capture APIs: DirtyOnly and FailOnConflict.
PreserveExisting, ReplaceExisting, dirty tombstones, independent field
switches, aliased source/output and complete failure rollback are supported.
One call can add at most 13 entries, record 1,024 operations, read 65,536 text
bytes per property and 1,048,576 total text bytes. Preparation may allocate;
this is separate from prepared allocation-free patch application. Conflicting
shared-object access remains the host's responsibility; no atomics or mutexes
are added.
Serializer type-129 support also covers the EXIF-permitted IFD0 text fields: ImageDescription, Make, Model, Software and Artist. Copyright remains outside this extension because its two-part NUL-separated form needs a separate contract. The older camera-text translator keeps its printable-ASCII contract. SpectralSensitivity, BodySerialNumber and LensSerialNumber remain outside the UTF-8 extension.
The combined capture fixture covers 75 distinct ExifIFD tags across sixteen APIs. Compatible-file transfer retains 72 after the existing three encoding filters. IFD0 companion fields are separate from that inventory.
Compatibility qualification found that the installed OIIO reader aborts with
std::bad_alloc on type-129 JPEG metadata. An isolated diagnostic changed only
those type codes and read successfully. This is an external reader limitation;
valid EXIF 3 output keeps type 129. The installed ExifTool also interprets
UNICODE as legacy UTF-16 regardless of ExifVersion. Direct TIFF byte checks
qualify EXIF 3 text, ExifTool checks XMP, and FFmpeg verifies unchanged decoded
pixels. Downstream application acceptance remains on hold.
The wire rules follow CIPA EXIF 3.0 Table 4 and the type-129/tag definitions, and CIPA DC-010-2024 EXIF/XMP mappings. The original PDFs and the character-code table image remain in the private qualification evidence.
translate_xmp_development_correction_metadata and Python
Document.translate_development_correction_metadata project six EXIF 3.1
ExifIFD fields in one bounded transaction. The canonical XMP namespace is
http://cipa.jp/exif/1.0/ (normally exifEX).
| XMP property | Native tag | Native value |
|---|---|---|
exifEX:DevelopmentType |
A40D | One SHORT. The high and low bytes are each 1, 2 or 4. |
exifEX:DevelopmentTypeDescription |
A40E | UTF-8 text, TIFF type 129 even when the text is ASCII. |
exifEX:DistortionCorrection |
A40F | SHORT 0 or 1. |
exifEX:ChromaticAberrationCorrection |
A410 | SHORT 0 or 1. |
exifEX:ShadingCorrection |
A411 | SHORT 0 or 1. |
exifEX:NoiseReduction |
A412 | SHORT 0 through 3. |
DevelopmentType is an RDF resource with exactly the published children
DevelopmentCharacterstic and FactoryDefault. The spelling of
DevelopmentCharacterstic is part of the wire contract. Root values,
additional children, indexed forms, duplicate eligible sources and partial
deletion fail without publishing a candidate. The description requires valid
UTF-8 XML text; whitespace is preserved and no BOM or character-set conversion
is inferred.
The caller supplies exif_version as an explicit host policy. A selected
source requires exactly 300 or 310; 0 returns IncompleteSource. The
translator never creates, upgrades, downgrades or otherwise changes native
ExifVersion. This records the DC-008 0300 and DC-010 0310 source
discrepancy without silently choosing one edition. Defaults remain DirtyOnly
and FailOnConflict. PreserveExisting, ReplaceExisting, dirty tombstones,
per-field switches and resource limits follow the capture translation contract.
The call can add at most six entries, read 65,536 bytes per property and
1,048,576 total source text bytes. Preparation may allocate; the commit is one
transaction and conflicting shared-object access remains host-owned.
Native validation, typed editing, canonical portable XMP and TIFF serialization share the same value checks. Portable output emits the packed A40D value as the two-child resource and emits A40E with type 129. Compatible-file and rendered- image transfer retain these standard processing-history fields when they are present. OpenMeta does not infer correction history from RAW or MakerNote data, and it does not add a value when the source has none.
The combined capture inventory is now 81 distinct ExifIFD tags across seventeen APIs; the six fields are new and do not overlap the prior 75-tag set. The profile and LearningOptOutIn contracts below are separate from the capture inventory and do not impose a complete-file profile.
translate_xmp_learning_opt_out_in_metadata and
Document.translate_learning_opt_out_in_metadata project one exact
http://cipa.jp/exif/1.0/ structure into ExifIFD tag 0x9287 (TIFF type 7).
The selected source has LearningOptOutIn/NumberOfSets and either a typed
LearningOptOutIn/Values array or dense scalar leaves
LearningOptOutIn/Values[1] through [2*n]. Each pair is usage/intention:
the first usage is zero, usage values are unique, usage is 0..4 and intention
is 0..2. Root values, sparse indexes, duplicate sources and partial deletion
fail atomically. A dirty tombstone removes the native tag only with
ReplaceExisting.
The caller supplies exif_version as 300 or 310 when a source is selected;
the translator never creates or changes ExifVersion. The default policy is
DirtyOnly plus FailOnConflict; PreserveExisting, ReplaceExisting,
bounded set/text/operation limits and host-owned synchronization follow the
other translation contracts. Native type-7 bytes retain ValueBigEndian
provenance, while new payloads use canonical little-endian words. Portable XMP
emits the same structure as an rdf:Seq.
translate_xmp_profile_metadata and
Document.translate_xmp_profile_metadata provide one bounded transaction for
the following exact properties:
| XMP source | Native destination | Requirements |
|---|---|---|
tiff:ImageDescription |
IFD0 0x010E |
Non-empty valid EXIF text; ASCII uses TIFF type 2 and UTF-8 uses type 129. |
tiff:Artist |
IFD0 0x013B |
Same text contract as ImageDescription. |
tiff:Copyright |
IFD0 0x8298 |
One scalar native part; an existing two-part NUL value is preserved or conflicts under the selected policy. |
exif:ColorSpace |
ExifIFD 0xA001 |
Numeric 1 or 65535, or the labels sRGB and Uncalibrated. |
exif:RelatedSoundFile |
ExifIFD 0xA004 |
Printable ASCII 8.3 filename without path separators. |
Namespaces and paths must match exactly. No ICC profile, image dimension,
color agreement, audio-file association or companion inference is performed.
Each selected field reconciles independently; duplicate eligible sources,
invalid values, limits and conflicts fail before publication. Dirty tombstones
remove native values only with ReplaceExisting. Preparation may allocate and
the commit is transactional; callers synchronize conflicting access to a
shared MetaStore.
The existing JP2 writer path also accepts boxed JPH brands. It replaces selected
Exif/XML carriers, preserves unknown UUID boxes and the codestream, and updates
an existing jp2h/colr ICC child without synthesizing a missing jp2h. Focused
tests cover ordinary and extended boxes, zero-length terminal boxes, positional
scans that skip codestream bytes, and both jp2 and jph brands. Real-file
ICC/read-back and OIIO/iRAW consumer acceptance remain external qualification
gates.