
XMP metadata parsing, manipulation and serialization with a compatible API, supports parsing from strings, configurable serialization options, and easy migration from original SDK imports.
This library is a port of Adobe's XMP SDK to Kotlin Multiplatform.
implementation("de.stefan-oltmann:xmpcore:2.0.0")
Version 2.0.0 contains a few breaking changes:
getDateTimeOriginal() returns an XmpDate and setDateTimeOriginal() takes an
XmpDate, so values keep their seconds and fractions and no longer need manual parsing and
formatting.getFaces()/setFaces() are replaced by getFaceRegions()/setFaceRegions()
working on a List<XmpFaceRegion>, because the region list of the XMP can hold several regions
with the same name.SerializeOptions is immutable. The setters return modified copies instead of mutating the
receiver, so one shared instance can be reused from concurrent writers.XMPUtils batch operations removeProperties, appendProperties, separateArrayItems and
catenateArrayItems are now available.The library has been designed as a drop-in replacement for users who previously used XMP Core Java. Therefore, all the documentation applicable to the Java SDK also pertains to this library. However, please note that we have made the decision to remove the functionality for reading from and writing to ByteArray and InputStreams, as I believe it is unnecessary.
val originalXmp: String = "... your XMP ..."
val xmpMeta: XMPMeta = XMPMetaFactory.parseFromString(originalXmp)
val xmpSerializeOptions =
SerializeOptions()
.setOmitXmpMetaElement(false)
.setOmitPacketWrapper(false)
.setUseCompactFormat(true)
.setSort(true)
val newXmp = XMPMetaFactory.serializeToString(xmpMeta, xmpSerializeOptions)
Check out the Kotlin JVM example project.
For usage in Java projects check out the Java example project.
Also see the unit tests ReadXmpTest and WriteXmpTest to learn more about reading and
manipulating data.
If you have previously used the official XMP Core Java library available on Maven Central, please
make sure to update your imports from com.adobe.internal.xmp
to de.stefan_oltmann.xmp.
Namespaces discovered while parsing are registered permanently in a process-global schema registry (like Adobe's XMP Core). Parsing a file with unknown namespaces therefore leaves small permanent entries behind. Applications that parse very large numbers of files with many changing namespaces over long uptime should keep this in mind.
This port aims to behave like the Adobe original. A few deliberate deviations remain:
delete* methods and returns false from the doesPropertyExist* methods when the arguments
are invalid or a namespace is unknown. This port throws XMPException in those cases, because
treating invalid input as a no-op masks programming errors.REQUIRE_XMP_META into a filter. This port throws
XMPException instead, so callers keep control over the fallback; use parseOrCreate for the
"nothing there" case.XMPDateTime is replaced by XmpDate, which parses strictly without silently
clamping values and always renders the seconds of a time.String input and output exists. The ByteArray/InputStream/
OutputStream API of the original, including the exactPacketLength and thumbnail padding
options, is not ported.Beyond these, the port fixes defects that the Java 5.1.3 original carries and that behave like the Adobe C++ original here:
ns:bag[?ns:qual='value'] never examined the last array item in
the Java original; this port finds it.NullPointerException; this port reports an XMPException.XMPIterator.skipSubtree() was declared but never evaluated by the Java original; this port
implements the documented behavior.XMPUtils batch operations had several defects in the Java original, among them an
inverted form comparison that disabled array merging, array items appended to the schema
instead of the array, and a quote scan reading the wrong position; this port implements the
intended semantics.A few edge cases are handled differently on purpose:
rdf:_ element are
rejected, while the numbered rdf:_N item form is accepted, following the RDF specification
more closely than the Java original.Where a drop-in replacement is affected, the remaining API-shape differences:
getNamespace() falls back to the base namespace for array items, where the
Adobe original returns null. An array item belongs to its schema's namespace even though its
node name is the prefix-less [], so the null of the original is an artifact of looking up
that synthetic name as a prefix; the port reports the namespace the item was found under
instead.Contributions to this project are welcome! If you encounter any issues, have suggestions for improvements, or would like to contribute new features, please feel free to submit a pull request.
The same BSD license applies to this project as to Adobe's open source XMP SDK, from which it is derived. See NOTICE.md for attributions of the original work and the bundled third-party libraries.
Note: The original license page went offline, but you can still find it on archive.org.
This library is a port of Adobe's XMP SDK to Kotlin Multiplatform.
implementation("de.stefan-oltmann:xmpcore:2.0.0")
Version 2.0.0 contains a few breaking changes:
getDateTimeOriginal() returns an XmpDate and setDateTimeOriginal() takes an
XmpDate, so values keep their seconds and fractions and no longer need manual parsing and
formatting.getFaces()/setFaces() are replaced by getFaceRegions()/setFaceRegions()
working on a List<XmpFaceRegion>, because the region list of the XMP can hold several regions
with the same name.SerializeOptions is immutable. The setters return modified copies instead of mutating the
receiver, so one shared instance can be reused from concurrent writers.XMPUtils batch operations removeProperties, appendProperties, separateArrayItems and
catenateArrayItems are now available.The library has been designed as a drop-in replacement for users who previously used XMP Core Java. Therefore, all the documentation applicable to the Java SDK also pertains to this library. However, please note that we have made the decision to remove the functionality for reading from and writing to ByteArray and InputStreams, as I believe it is unnecessary.
val originalXmp: String = "... your XMP ..."
val xmpMeta: XMPMeta = XMPMetaFactory.parseFromString(originalXmp)
val xmpSerializeOptions =
SerializeOptions()
.setOmitXmpMetaElement(false)
.setOmitPacketWrapper(false)
.setUseCompactFormat(true)
.setSort(true)
val newXmp = XMPMetaFactory.serializeToString(xmpMeta, xmpSerializeOptions)
Check out the Kotlin JVM example project.
For usage in Java projects check out the Java example project.
Also see the unit tests ReadXmpTest and WriteXmpTest to learn more about reading and
manipulating data.
If you have previously used the official XMP Core Java library available on Maven Central, please
make sure to update your imports from com.adobe.internal.xmp
to de.stefan_oltmann.xmp.
Namespaces discovered while parsing are registered permanently in a process-global schema registry (like Adobe's XMP Core). Parsing a file with unknown namespaces therefore leaves small permanent entries behind. Applications that parse very large numbers of files with many changing namespaces over long uptime should keep this in mind.
This port aims to behave like the Adobe original. A few deliberate deviations remain:
delete* methods and returns false from the doesPropertyExist* methods when the arguments
are invalid or a namespace is unknown. This port throws XMPException in those cases, because
treating invalid input as a no-op masks programming errors.REQUIRE_XMP_META into a filter. This port throws
XMPException instead, so callers keep control over the fallback; use parseOrCreate for the
"nothing there" case.XMPDateTime is replaced by XmpDate, which parses strictly without silently
clamping values and always renders the seconds of a time.String input and output exists. The ByteArray/InputStream/
OutputStream API of the original, including the exactPacketLength and thumbnail padding
options, is not ported.Beyond these, the port fixes defects that the Java 5.1.3 original carries and that behave like the Adobe C++ original here:
ns:bag[?ns:qual='value'] never examined the last array item in
the Java original; this port finds it.NullPointerException; this port reports an XMPException.XMPIterator.skipSubtree() was declared but never evaluated by the Java original; this port
implements the documented behavior.XMPUtils batch operations had several defects in the Java original, among them an
inverted form comparison that disabled array merging, array items appended to the schema
instead of the array, and a quote scan reading the wrong position; this port implements the
intended semantics.A few edge cases are handled differently on purpose:
rdf:_ element are
rejected, while the numbered rdf:_N item form is accepted, following the RDF specification
more closely than the Java original.Where a drop-in replacement is affected, the remaining API-shape differences:
getNamespace() falls back to the base namespace for array items, where the
Adobe original returns null. An array item belongs to its schema's namespace even though its
node name is the prefix-less [], so the null of the original is an artifact of looking up
that synthetic name as a prefix; the port reports the namespace the item was found under
instead.Contributions to this project are welcome! If you encounter any issues, have suggestions for improvements, or would like to contribute new features, please feel free to submit a pull request.
The same BSD license applies to this project as to Adobe's open source XMP SDK, from which it is derived. See NOTICE.md for attributions of the original work and the bundled third-party libraries.
Note: The original license page went offline, but you can still find it on archive.org.