Metadata Translation¶
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
transfer or writing.
The APIs are experimental and versioned by
kMetadataDateTranslationContractVersion == 2 and
kMetadataTechnicalTranslationContractVersion == 2 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
kMetadataDevelopmentCorrectionTranslationContractVersion == 2 and
kMetadataLearningOptOutInTranslationContractVersion == 2 and
kMetadataProfileTranslationContractVersion == 1.
Development/correction and learning lifecycle (0.8.11)¶
The six existing development/correction fields A40D-A412 and LearningOptOutIn 9287 now use translation contract 2. Exact valid clean native matches gain Dirty while retaining values, wire hints/counts, origin and endian provenance. Dirty matches remain unchanged. Complete ReplaceExisting deletion retains typed native intent even when a key is absent; clean tombstones are promoted and dirty ones reused. Promotions and deletion intents consume operation limits, and new intents also consume entry limits. Conflict, source-mask and rollback rules remain.
Selected sources still require explicit host exif_version 300 or 310; translation never infers or changes ExifVersion or processing history. These APIs do not validate a complete destination EXIF profile. All seven fields use selective TIFF/BigTIFF/DNG merge and removal in both safety modes, including restored snapshots. The shared native allowlist grows from 77 to 84. Existing/template DNG version retention, source-pixel filters, ABI 4, snapshot v1 and host-owned synchronization remain unchanged.
LearningOptOutIn uses checked 16-bit word conversion from native provenance to canonical little endian and then to the target TIFF byte order. Unrelated UNDEFINED payloads remain opaque. Canonical encoding validates learning values before copying them.
For native TIFF type-129 text, a counted payload may retain one explicit wire NUL. Validation and serialization both use the logical UTF-8 byte length plus one terminator. Ordinary authored text and the reader’s normalized text omit that NUL from their payload. Embedded NULs and mismatched wire counts still fail.
Workflow¶
See Capture Synchronization Milestone for the seventeen capture APIs, 81 distinct native targets, shared-target composition and the portable round-trip fixes. Primary ExifIFD FNumber, FocalLength and DigitalZoomRatio emit exact fractions; FocalLength is in millimeters without the display suffix `` mm``. Invalid native types/counts and values leave valid existing XMP available. Regenerate older rounded packets from native EXIF when original precision is needed.
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.
Date mappings¶
XMP source |
Native destination |
Precision requirements |
|---|---|---|
|
EXIF |
Time is required. Timezone and up to nine fractional digits are preserved in companion tags. |
|
IPTC |
Date-only or whole seconds are accepted. Fractional seconds are rejected. |
|
IPTC |
Date-only or whole seconds are accepted. Fractional seconds are rejected. |
XMP |
EXIF |
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.
Technical EXIF mappings¶
In 0.6.2, dirty tombstones for tiff:Make, tiff:Model and
xmp:CreatorTool translated with ReplaceExisting can remove the existing
native IFD0 tags through TIFF/BigTIFF edit writers. Preparation preserves the
explicit removal requests through snapshots. Source omission preserves the
destination. In 0.7.0 all three timestamp owners control the base, offset and
subsecond family. Missing companions produce explicit native dirty tombstones
even when absent from the source store. Dirty owner deletion removes all three
members; omission preserves values. Synthesized intents count against added
entry and operation limits and are reused on repeat calls. TIFF edits merge
unspecified destination entries for dirty timestamps, preserving other groups
and native records. Date and technical calls remain separate transactions.
See Migrating to OpenMeta 0.7 for contract version 2 and the unfrozen ABI-4 policy.
XMP source |
Native EXIF destination |
Requirements |
|---|---|---|
|
IFD0 |
Time is required. Timezone and up to nine fractional digits are preserved in companion tags. |
|
IFD0 |
Non-empty 7-bit ASCII without embedded NUL bytes. |
|
IFD0 |
Non-empty 7-bit ASCII without embedded NUL bytes. |
|
IFD0 |
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.
Capture EXIF mappings¶
XMP source |
Native EXIF destination |
Required native type |
|---|---|---|
|
ExifIFD |
One unsigned |
|
ExifIFD |
One unsigned |
|
ExifIFD |
One |
|
ExifIFD |
One unsigned |
|
ExifIFD |
One signed |
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.
Capture settings writeback¶
translate_xmp_capture_settings_metadata(...) and Python
Document.translate_capture_settings_metadata(...) add twelve independent
SHORT singleton mappings with MetadataCaptureSettingsTranslationOptions,
contract version 2. 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.
Exact capture rational writeback¶
translate_xmp_capture_rational_metadata(...) and Python
Document.translate_capture_rational_metadata(...) use
MetadataCaptureRationalTranslationOptions, contract version 2, 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.
Accepted exact clean native values gain Dirty while retaining native components and wire provenance. Complete eligible deletion creates or reuses Dirty|Deleted native intent even when the native key was absent. PreserveExisting keeps an active owner without promotion; omitted or ineligible sources preserve the destination. Intent, promotions and duplicate removals consume the existing preflight budgets. Repeated calls reuse intent; failure preserves source and output. The host synchronizes shared objects.
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 equivalent generated values. Use
xmp_include_existing=True and XmpConflictPolicy.ExistingWins for
authoritative source spelling; choose CanonicalizeManaged when generated
values must replace managed source aliases. These are explicit authority choices.
Complete Flash writeback¶
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.
LightSource writeback¶
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.
Target-bound image geometry¶
translate_xmp_image_geometry(...) projects tiff:Orientation and
complete XMP width/height pairs only when they agree with a caller-provided
TransferTargetImageSpec. Orientation becomes one IFD0 SHORT. Stored
dimensions become IFD0 ImageWidth/ImageLength and ExifIFD
PixelXDimension/PixelYDimension LONG values.
Width aliases are tiff:ImageWidth, exif:ExifImageWidth, and
exif:PixelXDimension. Height aliases are tiff:ImageLength, portable
tiff:ImageHeight, exif:ExifImageHeight, and
exif:PixelYDimension. Aliases may coexist only when all values agree.
Dimensions describe the stored raster and are never swapped for orientation indices 5 through 8. Missing target facts, target mismatches, contradictory aliases, incomplete pairs, and mixed active/deleted pairs fail transactionally.
Descriptive mappings¶
XMP source |
Native IPTC-IIM destination |
Maximum encoded bytes |
|---|---|---|
|
|
64 |
|
|
2000 |
|
repeated |
32 per value |
|
repeated |
64 per value |
|
|
128 |
|
|
32 |
|
|
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.
Location mappings¶
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 bytes |
|---|---|---|
|
|
32 |
|
|
32 |
|
|
32 |
|
|
64 |
|
|
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.
Structured location reconciliation¶
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 |
|---|---|---|---|
|
|
2:90 |
32 |
|
|
2:92 |
32 |
|
|
2:95 |
32 |
|
|
2:101 |
64 |
|
|
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.
Structured location construction¶
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,
)
Editorial mappings¶
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 |
|---|---|---|
|
|
256 |
|
|
256 |
|
|
32 |
The mappings and limits follow the
IPTC Photo Metadata Standard 2025.1 (see the location specification link above).
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.
Combined IPTC writeback¶
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 |
|---|---|---|
|
By-lineTitle (2:85) |
Singleton text, at most 32 UTF-8 bytes |
|
Writer-Editor (2:122) |
Singleton text, at most 32 UTF-8 bytes |
|
Category (2:15) |
One to three ASCII letters, case preserved |
|
Repeated SupplementalCategories (2:20) |
Text, at most 32 UTF-8 bytes per value |
|
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.
Primary GPS writeback¶
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 EXIF XMP source |
Native gpsifd fields |
Flag |
|---|---|---|
|
LatitudeRef (1), Latitude (2) |
|
|
LongitudeRef (3), Longitude (4) |
|
|
AltitudeRef (5), Altitude (6) |
|
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.
Destination GPS writeback¶
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 |
Flag |
|---|---|---|
|
DestLatitudeRef (19) and DestLatitude (20) |
|
|
DestLongitudeRef (21) and DestLongitude (22) |
|
|
DestBearingRef (23) and DestBearing (24) |
|
|
DestDistanceRef (25) and DestDistance (26) |
|
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.
GPS receiver quality writeback¶
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 |
|---|---|---|
|
9 / ASCII |
|
|
10 / ASCII |
|
|
11 / RATIONAL |
|
|
30 / SHORT |
|
|
31 / RATIONAL |
|
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.
GPS text writeback¶
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 |
|---|---|---|
|
8 / ASCII |
|
|
18 / ASCII |
|
|
27 / UNDEFINED encoded text |
|
|
28 / UNDEFINED encoded text |
|
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.
Conflict and removal 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 |
|---|---|
|
Keep the complete native group when any member already exists. |
|
Require the group to be absent or already exactly equivalent. This is the default. |
|
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 text property, and 160 total source text bytes. One dirty dimension
makes the complete active pair eligible. Dirty geometry tombstones may remove
native fields under ReplaceExisting only when the target spec does not
still declare the same fact.
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.
C++ example¶
#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¶
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.
Scope¶
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, and selected structured-location reconciliation. It does not yet provide arbitrary EXIF/IPTC/XMP translation, broader target layout/storage projection, multilingual-alternative selection, timezone inference, numeric approximation or value repair, or automatic synchronization during transfer.
Sensitivity companion writeback¶
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:
exifEX property |
Native tag/type |
|---|---|
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.
Camera, lens and spectral text writeback¶
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 |
|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 batches below.
Lens specification and image identity writeback¶
translate_xmp_identity_metadata(...) and Python
Document.translate_identity_metadata(...) use
MetadataIdentityTranslationOptions, contract version 2. Independent
lens_specification_to_exif and image_unique_id_to_exif flags select two
ExifIFD fields, with capture conflict policies and one transactional commit.
exifEX:LensSpecification(legacyexifaccepted) maps to0xA432as four unsigned RATIONALs: minimum/maximum focal length, then minimum F-number at each endpoint. Positive focal lengths must be ordered. Apertures are positive or exactly0/0for unknown. One typed URational array or four dense[1]through[4]scalar properties are accepted. Numeric text converts exactly to unsigned 32-bit fractions; floating scalars, units, incomplete/mixed shapes and duplicate aliases fail.exif:ImageUniqueIDmaps to0xA420as 32 ASCII hex characters plus the serializer’s terminal NUL. Case and leading zeros are preserved. No trimming, case folding, UUID generation, inference or hyphen removal is performed.
Under DirtyOnly, a dirty lens member selects clean companions too. A lone
root tombstone or four dirty indexed tombstones removes the lens field; partial
member deletion fails. Image ID uses its own eligible scalar source/tombstone.
Native equivalence requires the correct shape and value. Rational equivalence
retains unreduced native fractions; ID equivalence allows one native terminal
NUL. All validation, conflicts and limits precede changes, including aliased
input/output. Preparation may allocate; the host synchronizes shared objects.
Limits are 2 added entries, 1024 operations, 128 bytes per text member and 544
aggregate text bytes. Fixed four-element typed arrays do not consume text budget.
Portable output emits a canonical exifEX RDF Seq with exact fraction text,
including 0/0. Managed canonicalization recognizes its indexed shape.
Generic validation accepts the exact unsigned lens aperture 0/0 slots,
including validated authoring inputs; unrelated zero denominators remain errors.
Malformed native lens/ID values are skipped. Decoding preserves ImageUniqueID
boundary whitespace so malformed padded IDs remain detectable.
This is a structural Exif 2.3 / CIPA XMP translation contract. It does not check
UUID version, global uniqueness or capture-time identity retention policy and
never upgrades ExifVersion. Hosts supply lens components in documented order:
the store flattens RDF arrays and does not retain Seq/Bag container kind.
See docs/translation.md for exact contracts, standards references and the
focal-plane/subject batch below. Combined C++, Python, shared
consumer and JPEG/Classic TIFF/BigTIFF snapshot checks cover both fields.
Accepted exact clean natives gain Dirty while retaining lens fractions/unknown markers, ID case/NUL and wire provenance. Complete eligible deletion creates or reuses Dirty|Deleted intent even for absent keys. PreserveExisting retains an active owner without promotion; omitted or ineligible sources preserve the destination. Intent, promotions and duplicate removals consume existing budgets. Repeated calls reuse intent; failure retains source and output unchanged.
APEX writeback (contract version 2)¶
translate_xmp_apex_metadata and Python
Document.translate_apex_metadata translate five independent scalar fields
in one transaction. MetadataApexTranslationOptions uses the capture source
modes, conflict policies, result and status types.
XMP source |
ExifIFD target |
Native type, count 1 |
|---|---|---|
|
|
SRATIONAL |
|
|
RATIONAL |
|
|
SRATIONAL |
|
|
SRATIONAL |
|
|
RATIONAL |
The namespace must be http://ns.adobe.com/exif/1.0/. All five independent
*_value_to_exif switches default to true; at least one must remain enabled.
The exact switch names are shutter_speed_value_to_exif,
aperture_value_to_exif, brightness_value_to_exif,
exposure_bias_value_to_exif and max_aperture_value_to_exif.
Inputs are direct APEX values. No seconds/f-number conversion, exposure
inference, cross-field consistency check or ExifVersion upgrade is performed.
Scalar integers, the target rational type and ASCII/UTF-8/Unknown-encoding integer, decimal, scientific or fraction text are supported. Aperture fields accept nonnegative values, including zero; the other fields accept either sign. Denominators must be positive. Exact reduction uses bounded 64-bit intermediates and must fit 32-bit numerator/denominator components. Floating point, arrays, structured children, qualifiers, unit suffixes and invalid numeric text fail. Existing XMP decode whitespace normalization is unchanged. The usual brightness/bias range of -99.99 to 99.99 is not a hard limit.
A brightness wire numerator of -1 denotes unknown before reduction. Typed
SRational{-1,n}, explicit fraction text -1/n or exact text Unknown
creates or replaces the value with -1/1; the positive denominator must fit
INT32_MAX. Equivalent native unknown scalars retain their original encoding
when gaining Dirty authority. Integer or
single-number text inputs are finite: -1 and -1.0 write -2/2;
-0.5 and -2/4 write -2/4. Finite reduced fractions with numerator -1
use -2/(2n) or fail with ValueOutOfRange if the doubled denominator cannot
fit. Conflict comparison keeps unknown and finite values distinct.
Missing sources retain native fields. Since 0.7.2, accepted dirty tombstones carry native removal intent even for absent keys; accepted exact clean native values gain Dirty while retaining scalar and wire provenance. Duplicate eligible aliases fail. Native equivalence requires the correct scalar type/count and rational value, with sentinel handling. Replacement repairs wrong types and duplicate entries. Parsing, conflicts and budgets finish before one commit, including aliased output. Preparation may allocate; the host synchronizes conflicting access to shared objects.
Default/hard bounds are five added entries, 1024 operations, 128 text bytes per
property and 640 total text bytes. Positive lower limits are supported.
Exposure bias reuses the existing capture mapping and
XmpExposureCompensation diagnostic. Option layouts and parsing stay the same;
0.7.2 extends native lifecycle authority in both bias entry points.
In 0.5.3 generated portable XMP emits exact APEX fractions for all five tags.
Native shutter 6/1 now emits 6/1; earlier output emitted seconds
1/64. Native aperture 4/1 now emits 4/1; earlier output emitted
f-number text 4.0. Regenerate old packets from native EXIF before reverse
translation; their units cannot be inferred reliably from the values alone.
Brightness and the retained ExposureCompensation alias also emit exact
fractions. Existing correctly typed scalar XMP APEX rationals retain exact wire
fractions as well. Unknown brightness emits -1/1. Wrong native types/counts and
nonpositive denominators are omitted; large representable APEX values remain
exact. Existing-XMP precedence remains unchanged; CanonicalizeManaged allows
valid native replacements. Host FlatHost/Spec projections are unchanged.
The structural definitions follow CIPA DC-008-2012 and XMP names follow CIPA DC-X010-2017. This is not a full conformance claim for all Exif tags or later revisions. The batch adds four native targets, bringing capture-related coverage to 41 targets across nine APIs at 0.5.3. The spatial batch below extends this coverage; downstream application acceptance is separate.
Focal-plane and subject writeback (contract version 2)¶
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 |
|---|---|---|---|
|
|
RATIONAL, count 1 |
|
|
|
RATIONAL, count 1 |
|
|
|
SHORT, count 1 |
|
|
|
SHORT array, count 2, 3 or 4 |
|
|
|
SHORT array, count 2 |
|
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.
Since 0.8.4, accepted clean exact native fields gain Dirty authority through same-value updates retaining their original rational components, ordered arrays and wire provenance. Complete accepted deletion carries Dirty+Deleted intent for every owned native tag, even when it is absent from the source. Repeat calls reuse that intent. PreserveExisting retains the entire active native group without promotion. New markers, promotions and duplicate removals count against the existing limits before publication.
Prepared TIFF/DNG bundles use the existing native ExifIFD merge and removal fields for all five spatial tags. Unselected destination entries, MakerNotes and media ranges remain preserved. The bounded ExifIFD allowlist now has 44 tags. Dirty native owners select merge mode. Live entries included by ordinary EXIF preparation, including clean entries, supply destination values. Destination entries absent from the prepared EXIF payload retain their existing values. Source snapshot version 1 retains the flags needed to regenerate these intents. Fresh emission and non-edit consumers reject removal intent they cannot express. Signatures, options and bundle layout remain unchanged; spatial contract version 2 identifies the extended lifecycle. Development ABI remains unfrozen 4.
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 spatial milestone brought coverage to 46 targets across ten APIs; 0.5.8 extends it to 65 targets across fifteen APIs.
Additional capture and environment fields (0.5.6)¶
translate_xmp_capture_additional_metadata adds FocalLengthIn35mmFilm A405
(SHORT count 1, 0 unknown), FileSource A300 (UNDEFINED count 1, codes 0..3),
and SceneType A301 (UNDEFINED count 1, code 1). The explicit focal-length alias
FocalLengthIn35mmFormat remains the portable spelling. XMP inputs accept
integer scalars and decimal integer text without labels, units or inference.
translate_xmp_environment_metadata accepts the CIPA namespace
http://cipa.jp/exif/1.0/ and explicit legacy EXIF namespace aliases.
Duplicate eligible aliases and indexed/structured paths fail transactionally.
Temperature 9400 (degrees Celsius), WaterDepth 9403 (metres), and
CameraElevationAngle 9405 (degrees in [-180, 180)) use SRATIONAL count 1.
Humidity 9401 (percent), Pressure 9402 (hPa), and Acceleration 9404
(mGal, 10^-5 m/s²) use RATIONAL count 1. No other physical ranges are imposed.
Environment inputs accept matching typed rationals, integers and exact
fraction/decimal/scientific text. Raw denominator 0xffffffff means unknown;
signed fields represent it as -1. Recognition precedes reduction and preserves
the numerator. Signed sentinel text accepts n/-1 or n/4294967295, unsigned text
accepts n/4294967295, and Unknown uses numerator zero. Other denominators
must be positive. Finite fractions that reduce to the reserved denominator
fail. Portable output retains exact rational pairs. No float approximation or
unit-suffix conversion is performed.
Each call is bounded and transactional, including aliased output, dirty
removals and native conflicts. Defaults are 3/6 added entries, 1024 primitive
operations, 128 text bytes per property and 384/768 total text bytes. Python
Document.translate_capture_additional_metadata and
Document.translate_environment_metadata use the same policy. Multi-call
publication and conflicting shared-object access remain host responsibilities.
CanonicalizeManaged removes legacy environment aliases only when a valid
native replacement is emitted; invalid native values preserve existing XMP.
References: CIPA Exif 2.32 and CIPA Exif metadata for XMP.
In 0.8.6, both contracts are version 2. Accepted exact clean native values gain
Dirty while retaining native fractions, unknown numerator/denominator bits,
one-byte UNDEFINED codes and wire provenance. Native byte spans copy into the
edit arena before publication. Complete eligible deletion creates or reuses
Dirty|Deleted native intent even for absent keys. PreserveExisting keeps
an active owner without promotion; omitted and ineligible sources preserve the
destination. Existing preflight budgets include intent, promotion and duplicate
removal. Repeated calls reuse intent and failure retains source/output unchanged.
The shared ExifIFD merge/removal allowlist grows from 50 to 59 using existing
prepared fields and snapshot v1. Signatures, options and bundle layout stay the
same; development ABI 4 is unfrozen. Hosts synchronize shared objects.
Image Encoding and Composite Capture (0.5.7)¶
translate_xmp_image_encoding_metadata / Document.translate_image_encoding_metadata
cover Gamma, CompressedBitsPerPixel and ComponentsConfiguration. Rationals allow
zero with a positive denominator and preserve exact fractions. Components are
four codes in 0..6. Gamma uses CIPA exifEX with explicit legacy exif;
the other two use exif.
translate_xmp_composite_metadata / Document.translate_composite_metadata
reconcile CompositeImage and its two companions as one bounded transaction.
Codes are 0..3; companions require 2 or 3, and code 3 requires both. Source
counts are total >=2 and used 0 (unavailable) or 2..total. The exposure payload
has seven summary rationals, then SHORT m at offset 56. Summary 0/0 means
unavailable. When m is zero the payload ends at 58 bytes. Otherwise SHORT n at
58 is positive and exactly m*n rational values follow at offset 60. The list
contains at least two values, agrees with the total count, and has at most
4096 values. Finite seconds are nonnegative; totals and extrema are not inferred.
Canonical XMP uses the full native names in http://cipa.jp/exif/1.0/ and
structured summary members with ordered Values. ExifTool short roots and legacy
Adobe exif are explicit alternatives. Mixed aliases, sparse values and invalid
group dependencies fail transactionally. Any selected member selects its complete
source group; dirty tombstones can remove the list when m becomes zero.
Native validation, typed editing and portable output share these contracts.
New payloads are little-endian. Decoded big-endian bytes stay raw with
EntryFlags::ValueBigEndian; typed Set clears this flag for new little-endian
bytes. Serialization and transfer convert the embedded fields. Snapshot v1
layout is unchanged; snapshots using the new flag require 0.5.7 or later.
Direct EXIF serialization retains all six fields. Target transfer filters the three source encoding fields because they may not describe destination pixels. Preparation can allocate. Conflicting shared-object access remains host owned. See the detailed field and binary-layout contracts.
In 0.8.8, both translation contracts are version 2. Accepted exact clean
native values gain Dirty without replacing their stored fractions, byte spans,
byte-order flags or wire provenance. Complete eligible deletion creates or reuses
typed Dirty|Deleted intent, including absent native fields. Under
ReplaceExisting, a selected valid composite group also records removal of
omitted companions: source-only codes 0/1/2 can clear a destination’s old group.
PreserveExisting keeps an active native group without promotion; omitted and
ineligible source properties leave the store unchanged. Promotion, deletion
intent and duplicate removal consume the existing entry/operation budgets.
Preflight failure leaves source and output unchanged, and repeated calls reuse
intent.
The shared ExifIFD merge/removal allowlist grows from 59 to 62 with A460-A462.
Existing TIFF/BigTIFF/DNG preparation, snapshot v1 and vector/stream/package paths
carry eligible composite edits. The three encoding fields remain excluded from
target transfer, even when dirty or deleted: Gamma, compressed bits per pixel and
component configuration describe source pixels. Direct canonical EXIF serialization
retains them; callers own their correspondence to encoded pixels. Destination
encoding metadata is preserved during composite merge. For an existing DNG,
retain its original DNGVersion in the source metadata. DNG preparation
synthesizes a version when the source lacks one; it cannot infer target context
during preparation. No pixel transformation,
RAW encoding or private-offset repair is performed. Append-based TIFF writing
preserves retained image data; it does not promise whole-file identity after
repeated edits. Public signatures, options and prepared-bundle/snapshot layouts
remain unchanged; ABI 4 remains unfrozen and hosts synchronize shared objects.
Structured Capture Data (0.5.8)¶
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.
OECF (8828): Columns, Rows, Names and signed rational Values.
SpatialFrequencyResponse (A20C): Columns, Rows, Names and unsigned rational Values.
CFAPattern (A302): Columns, Rows and Values with color codes 0 through 6.
DeviceSettingDescription (A40B): Columns, Rows and ordered setting-string Values.
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.
In 0.8.9, the structured translation contract is version 2. Accepted exact
clean native values gain Dirty while retaining their raw bytes, byte-order
flags and wire provenance. Complete eligible deletion creates or reuses typed
Dirty|Deleted native intent, including absent fields. PreserveExisting
retains active native owners; omission and ineligible source mappings leave
them unchanged. Existing entry, operation and payload budgets apply before
publication. Partial deletion, malformed source or preflight failure leaves
the source and output unchanged. Repeated calls reuse native intent.
The shared TIFF ExifIFD merge/removal allowlist grows from 62 to 66 with these four tags. Native edits use the existing TIFF/BigTIFF/DNG, snapshot v1 and vector/stream/package paths. Byte-order conversion changes dimension and table rational words while preserving ASCII names and each UTF-16 string’s BOM and bytes. Public signatures, options and storage layouts remain unchanged; ABI 4 remains unfrozen and hosts synchronize shared objects.
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. This filtering applies to prepared source metadata. During a selective TIFF merge, a pre-existing destination CFAPattern remains when no removal intent selects it. An explicit dirty native CFAPattern deletion removes it in either transfer safety mode. Filtering alone does not create destination deletion intent. For compatible-file editing of an existing DNG, retain its original DNGVersion as source context, as described for the encoding/composite lifecycle above. Rendered-image filtering also removes this source version; DNG preparation then generates a version. To preserve an existing DNG container during these rendered-source metadata probes, prepare through the TIFF native edit route. This does not qualify rendered DNG pixels or DNG scaffold generation.
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.
EXIF Text and Version Metadata (0.5.9)¶
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 |
|
exif:UserComment |
9286 |
Character-set prefix plus text; XMP scalar or |
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. An explicit semantic replacement preserves text rather than padding or the
original character-set choice. Exact clean promotion in 0.8.10 retains both.
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.
The 0.8.10 native lifecycle uses EXIF text translation contract 2. Under FailOnConflict or ReplaceExisting, an exact clean valid native match gains Dirty while keeping raw bytes, encoding, endian flags and wire provenance. This includes comment prefixes, explicit BOMs and padding, and ASCII stored as type 129 under EXIF 3. Dirty exact matches remain unchanged; PreserveExisting retains active values. Explicit replacements use the authoring encodings above. A ReplaceExisting version-family change re-encodes a selected matching comment; a legacy downgrade also rewrites selected ASCII owner/lens type 129 as type 2. Unselected type 129 and EXIF 3-only tags still block a legacy downgrade. Artist/Software companion requirements apply to promotions too.
Complete selected deletion preserves active native values under PreserveExisting, conflicts under FailOnConflict, and removes them under ReplaceExisting. Without an active value it promotes a clean tombstone, reuses a dirty tombstone, or adds an empty typed Dirty|Deleted marker: Bytes/U8 for versions and UserComment, Text for the other ten fields. Promotions and new markers consume an operation; new markers also consume an addition. Omitted or disabled sources retain native state, and all preflight failures leave output unchanged. All 13 fields now support native TIFF/BigTIFF/DNG selective merge/removal, including snapshot v1. The shared ExifIFD allowlist grows from 66 to 77; no public layout changes.
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 81 distinct ExifIFD tags across seventeen APIs. Compatible-file transfer retains the six development/correction fields and still filters the three source-encoding fields for rendered-image output. 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.
EXIF 3.1 Development and Correction Data (0.5.11)¶
translate_xmp_development_correction_metadata and
Document.translate_development_correction_metadata add one bounded
transaction for A40D-A412. DevelopmentType is a SHORT with high and low
bytes chosen from 1, 2, and 4. Its XMP resource uses the published
DevelopmentCharacterstic and FactoryDefault children. A40E is UTF-8
TIFF type 129 even for ASCII text. A40F-A411 accept 0/1 and A412 accepts 0..3.
The caller must provide exif_version 300 or 310 for a selected source.
OpenMeta never changes or infers ExifVersion. Root or indexed forms,
partial resources, duplicate sources and invalid values fail atomically.
Native validation, typed editing, portable XMP and TIFF serialization share the
same rules. Compatible-file and rendered-image transfer retain present values;
RAW and MakerNote processing history is not inferred. LearningOptOutIn and
profile authoring are covered by the 0.5.12 sections below; complete-file
profile validation remains separate.
EXIF 3.1 LearningOptOutIn Data (0.5.12)¶
translate_xmp_learning_opt_out_in_metadata and its Python counterpart
project one exact http://cipa.jp/exif/1.0/ structure into ExifIFD tag
0x9287 (TIFF type 7). The source has NumberOfSets and either a typed
Values array or dense scalar leaves Values[1] through [2*n].
Usage/intention pairs require first usage zero, unique usage values 0..4 and
intention values 0..2. Root values, sparse indexes, duplicate sources and
partial deletion fail atomically. A dirty tombstone removes the native value
only with ReplaceExisting.
The caller supplies exif_version 300 or 310 for selected sources. New
payloads use canonical little-endian words; decoded big-endian values retain
ValueBigEndian. Defaults remain DirtyOnly and FailOnConflict and
preparation may allocate while the commit is transactional.
JP2 and JPH Qualification Boundary¶
The existing JP2 writer path accepts boxed JPH brands, replaces selected
Exif/XML carriers, preserves unknown UUID boxes and the codestream, and updates
an existing jp2h/colr child without synthesizing jp2h. Focused tests
cover ordinary and extended boxes, terminal zero-length 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.
Capture and lens lifecycle (0.7.1)¶
Capture, Flash, sensitivity and camera-text contracts are version 2. Accepted ExposureTime, FNumber, base ISO, Flash, FocalLength and lens make/model/serial owners create native Dirty+Deleted intent even when native keys are missing. Accepted clean exact values receive same-value Dirty updates that preserve bytes and wire provenance. Omission and conflict policies remain; intent and update costs are checked before transactional publication and repeat calls reuse intents. Hosts stage the separate calls before publishing an aggregate result.
Full sensitivity owns 8827 and 8830–8835 as one validated group;
optional absence becomes removal intent after complete validation. Basic ISO
owns only 8827 and preserves companions; disable it when full sensitivity
owns the source. Lens fields are independent. In 0.7.1, ExposureBiasValue,
LensSpecification and camera-owner/body-serial paths retained their prior behavior.
The 0.7.2 section below extends ExposureBiasValue.
TIFF preparation merges unspecified destination ExifIFD records for these dirty
edits. Bounded removal also accepts 829A, 829D, 8827, 8830–8835,
9209, 920A and A433–A435. MakerNote bytes and standard pointers
are preserved as opaque records; private relocation or checksum repair is not
provided. See Migrating to OpenMeta 0.7 for the unfrozen development ABI-4 policy.
APEX and capture-settings lifecycle (0.7.2)¶
APEX and capture-settings contracts are version 2; general capture is version 3 because its ExposureCompensation mapping shares the APEX bias owner. Each of five APEX fields and twelve closed settings is independent. Accepted dirty owner deletion carries exact native Dirty+Deleted intent even when native keys are missing. Accepted clean exact values gain Dirty with original scalar and wire provenance. Omitted or ineligible owners preserve destination values.
Existing conflict policies, exact APEX units, brightness unknown sentinel, accepted enum codes, aliases and source shapes remain. Added intent, clean-marker promotion and same-value authority updates consume preflight budgets. Repeat calls reuse intent; each translator publishes once or leaves output unchanged. Hosts stage separate calls before aggregate publication and disable one shared ExposureBias mapping when composing APEX and general capture. No inference is performed.
TIFF/BigTIFF uses the existing removal list and merge flag for the added tags:
8822, 9201–9205, 9207, A217, A401–A403,
A406–A40A and A40C. The allowlist now has 39 tags. Unselected native
records, opaque MakerNote bytes/original offsets, standard pointers and media
remain preserved. Snapshot v1, bundle/API layouts, ABI 4, package-minor policy
and unsupported-consumer rejection remain unchanged.
GPS destination lifecycle (0.8.0)¶
All five GPS translators use contract version 2. Accepted dirty deletion carries native intent even for absent members; accepted exact clean members gain Dirty without changing native encoding. Omitted sources preserve the destination. Intent and authority updates consume existing transactional limits; repeated calls reuse them. Bounded TIFF/BigTIFF merges retain unselected destination GPS. Version cleanup depends on surviving destination values, and conflicting active versions fail before output. See Migrating to OpenMeta 0.8 for ownership and rebuilds.