-
-
Notifications
You must be signed in to change notification settings - Fork 2.4k
Description
What did you do?
When converting a TIF to a JPG, if the image contains XMP data that uses a <?xpacket> wrapper, conversion fails with an exception bubbled up from re module.
What did you expect to happen?
Conversion should happen
What actually happened?
Exception generated
TypeError ("expected string or bytes-like object, got 'tuple'",)
Traceback (most recent call last):
File "<ipython-input-64-92aa2f62a20c>", line 5, in <module>
jpeg_image = img.convert("RGB")
^^^^^^^^^^^^^^^^^^
File "/Users/graeme/.local/share/virtualenvs/sandbox-0NfWatfK/lib/python3.12/site-packages/PIL/Image.py", line 984, in convert
self.load()
File "/Users/graeme/.local/share/virtualenvs/sandbox-0NfWatfK/lib/python3.12/site-packages/PIL/TiffImagePlugin.py", line 1292, in load
return self._load_libtiff()
^^^^^^^^^^^^^^^^^^^^
File "/Users/graeme/.local/share/virtualenvs/sandbox-0NfWatfK/lib/python3.12/site-packages/PIL/TiffImagePlugin.py", line 1402, in _load_libtiff
self.load_end()
File "/Users/graeme/.local/share/virtualenvs/sandbox-0NfWatfK/lib/python3.12/site-packages/PIL/TiffImagePlugin.py", line 1314, in load_end
ImageOps.exif_transpose(self, in_place=True)
File "/Users/graeme/.local/share/virtualenvs/sandbox-0NfWatfK/lib/python3.12/site-packages/PIL/ImageOps.py", line 725, in exif_transpose
else re.sub(pattern.encode(), b"", value)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/opt/homebrew/Cellar/[email protected]/3.12.8/Frameworks/Python.framework/Versions/3.12/lib/python3.12/re/__init__.py", line 186, in sub
return _compile(pattern, flags).sub(repl, string, count)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
TypeError: expected string or bytes-like object, got 'tuple'What are your OS, Python and Pillow versions?
- OS: MacOS Sequoia
- Python: 3.12.8
- Pillow: 11.1.0 (via PIL.version)
% pipenv run python3 -m PIL.report
--------------------------------------------------------------------
Pillow 11.1.0
Python 3.12.8 (main, Dec 3 2024, 18:42:41) [Clang 16.0.0 (clang-1600.0.26.4)]
--------------------------------------------------------------------
Python executable is /Users/graeme/.local/share/virtualenvs/sandbox-0NfWatfK/bin/python3
Environment Python files loaded from /Users/graeme/.local/share/virtualenvs/sandbox-0NfWatfK
System Python files loaded from /opt/homebrew/opt/[email protected]/Frameworks/Python.framework/Versions/3.12
--------------------------------------------------------------------
Python Pillow modules loaded from /Users/graeme/.local/share/virtualenvs/sandbox-0NfWatfK/lib/python3.12/site-packages/PIL
Binary Pillow modules loaded from /Users/graeme/.local/share/virtualenvs/sandbox-0NfWatfK/lib/python3.12/site-packages/PIL
--------------------------------------------------------------------
--- PIL CORE support ok, compiled for 11.1.0
*** TKINTER support not installed
--- FREETYPE2 support ok, loaded 2.13.2
--- LITTLECMS2 support ok, loaded 2.16
--- WEBP support ok, loaded 1.5.0
--- JPEG support ok, compiled for libjpeg-turbo 3.1.0
--- OPENJPEG (JPEG2000) support ok, loaded 2.5.3
--- ZLIB (PNG/ZIP) support ok, loaded 1.3.1.zlib-ng, compiled for zlib-ng 2.2.2
--- LIBTIFF support ok, loaded 4.6.0
--- RAQM (Bidirectional Text) support ok, loaded 0.10.1, fribidi 1.0.16, harfbuzz 10.1.0
*** LIBIMAGEQUANT (Quantization method) support not installed
--- XCB (X protocol) support ok
--------------------------------------------------------------------
Reproduction steps
I encountered this issue on a Mac. I've been struggling to isolate a more independant example. It's probably me struggling with exiftool
I can't attach tif files. Please obtain from this Google Drive folder
- Download no_xmp.tif and xmp.tif and run the following
from PIL import Image
img = Image.open('xmp.tif')
new_img = img.convert("RGB")Conversion should be successful
-
Repeat with xmp_xpacket.tif. Exception will be produced
-
We can break out the specific logic causing the exception from ImageOps.exif_transpose()
def test_xmp(img):
if 'xmp' in img.info:
problematic_value = img.info['xmp']
pattern = r'tiff:Orientation="([0-9])'
if isinstance(problematic_value, str):
re.sub(pattern, "", problematic_value)
else:
if isinstance(problematic_value, tuple):
print("Tuple found. Not good")
else:
print("No tuple found. Good")
re.sub(pattern.encode(), b"", problematic_value)
else:
print("Didn't find xmp info")
img_noxmp = Image.open("no_xmp.tif")
test_xml(img_noxmp)Will return "Didn't find xmp info"
img_xmp = Image.open("xmp.tif")
test_xml(img_xmp)Will return "No tuple found. Good"
Finally
img_xpacket = Image.open("xmp_xpacket.tif")
test_xml(img_xpacket)Will return "Tuple found. Not good"
- To preview the XMP in these images run
exiftool -XMP -b xmp_xpacket.tifand you'll observe the <?xpacket> field
How did xpacket get in there?
I discovered that this is due to image rotation on a Mac. Specifically Quick Actions vs Preview app
- Start with no_xmp.tif. Run
exiftool -XMP -b no_xmp.tif - Open 'Preview' and rotate the image (cmd-L for shortcut)
- Run exiftool again and observe some XMP data has been added to the image
- In the Mac finder, select 'no_xmp.tif' and either
- Tap space to open quicklook and press cmd-L, or click the 'rotate left' button
- or right click on the image and select "Quick Actions -> Rotate left'
This will insert the xpacket tag
Summary
So one could say there's an inconsistency in Mac on how XMP fragments are added depending on how you rotate the image.
Looking at part 1 of the XMP spec, specifically section 7.3.2 it does appear that the XMP is valid if those xpacket wrappers are present.
I suspect that ImageOps.exif_transpose() needs to be adjusted