Reading XMP sidecar files with Python and libxmp

呉
2 min readFeb 21, 2018

--

I just wanted to read some XMP sidecar files, change some data and write that back to the file.

A quick google search brought me to the libxml library (github page) and it looked like exactly what I needed. Sadly lacking documentation made something that should have taken 10 minutes take around 3 hours.

To make everyone elses journey easier I write this simple help file

  • you use Python
  • you want to read XMP sidecar files (not XMP embedded in files)

First: Installing on macOS

pip install works fine. If you have MacPorts you can use the exempi tools from there, no need to manually install it. But if you do it, you need to use the configure command below

./configure — with-darwinports — prefix=/opt/local

The /opt/local prefix is very important or the python library will no find the exempi tools

path = ctypes.util.find_library(‘exempi’)
if path is None:
if platform.system().startswith(‘Darwin’):
if os.path.exists(‘/opt/local/lib/libexempi.dylib’):
# MacPorts starndard location.
path = ‘/opt/local/lib/libexempi.dylib’

Yeah, it will very like look in /opt/local

Second: Reading data or lacking documentation

But the main problem was the very lacking documentation about how to read XMP files and what XMP file types it can read. The code sample from the documentation

>>> from libxmp.utils import file_to_dict
>>> xmp = file_to_dict( "test/samples/BlueSquare.xmp" )

or

>>> from libxmp import XMPFiles, consts
>>> xmpfile = XMPFiles( file_path="test/samples/BlueSquare.jpg", open_forupdate=True )

Will only work with embedded XMP data or XMP data that has the <?xpacket tag around. Without this the reading needs to follow the following flow

from libxmp import XMPMetawith open('sample_sidecar.xmp', 'r') as fptr:
strbuffer = fptr.read()
xmp = XMPMeta()
xmp.parse_from_str(strbuffer)

This will end you up with a proper XMP data stream in the xmp variable.

Third: Reading entries or missing documentation

Again with this darn documentation. It lacks to describe what are the constants are for actually reading data. Because entries like ‘GPSLatitude’, ‘City’ or ‘Location’ are in three different namespaces as defined on top of the XMP file

<rdf:Description rdf:about=""
xmlns:tiff="http://ns.adobe.com/tiff/1.0/"
xmlns:exif="http://ns.adobe.com/exif/1.0/"
xmlns:xmp="http://ns.adobe.com/xap/1.0/"
xmlns:aux="http://ns.adobe.com/exif/1.0/aux/"
xmlns:exifEX="http://cipa.jp/exif/1.0/"
xmlns:photoshop="http://ns.adobe.com/photoshop/1.0/"
xmlns:xmpMM="http://ns.adobe.com/xap/1.0/mm/"
xmlns:stEvt="http://ns.adobe.com/xap/1.0/sType/ResourceEvent#"
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:Iptc4xmpCore="http://iptc.org/std/Iptc4xmpCore/1.0/xmlns/"
xmlns:crs="http://ns.adobe.com/camera-raw-settings/1.0/"

The various namespaces are listed above. But which are which constant? They are defined here and below is a list for some of them

XMP_NS_Photoshop = "http://ns.adobe.com/photoshop/1.0/"
...
XMP_NS_EXIF = "http://ns.adobe.com/exif/1.0/"
...
XMP_NS_IPTCCore = "http://iptc.org/std/Iptc4xmpCore/1.0/xmlns/"
...

So to read some basic data see the code blow (still sidecar based XMP file)

from libxmp import XMPMeta, constwith open('sample_sidecar.xmp', 'r') as fptr:
strbuffer = fptr.read()
xmp = XMPMeta()
xmp.parse_from_str(strbuffer)
# print out one for exif, photoshop, iptc core
print("Exif:GPSLatitude = {}".format(xmp.get_property(consts.XMP_NS_EXIF, 'GPSLatitude')))
print("photoshop:City = {}".format(xmp.get_property(consts.XMP_NS_Photoshop, 'City')))
print("Iptc4xmpCore:Location = {}".format(xmp.get_property(consts.XMP_NS_IPTCCore, 'Location')))

Forth: Writing data or again missing documentation

To change an entry you simple use the same constants and entry but add the new value

xmp.set_property(consts.XMP_NS_IPTCCore, 'Location', 'New Town Square')

And to finally write that all back to a proper XMP sidecar file you need to remember that the sidecar file does not have the <?xpacket tag

with open('save_sidecar.xmp', 'w') as fptr:
fptr.write(xmp.serialize_to_str(omit_packet_wrapper=True))

That’s it. Handling XMP sidecar files in perl is pretty easy if the documentation is all right.

--

--

呉

日本で住んでいる。写真大好き。焼酎も大好き。ヤバイ生活!