Reading XMP Sidecar Files With Python and Libxmp
Reading XMP sidecar files with Python and libxmp
This is a copy from Medium: https://medium.com/@gullevek/reading-xmp-sidecar-files-with-python-and-libxmp-a4c52e9955bb
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.