Changing exif data without re-compressing JPEG image - python-3.x

I write a python 3 CLI tool to fix creation dates of photos in a library (see here.
I use Pillow to load and save the image and piexif to handle exif data retrieval/modification.
The problem I have is that I only want to change the EXIF data in the pictures and not recompress the whole image. It seems that Pillow save can't do that.
My question is:
Any better exif library I could use to only play with the exif data (so far I tried py3exiv2, pexif and piexif) ?
If not, is there a way to indicate to Pillow to only change the exif of the image without recompressing when saving ?
Thanks !
Here is the code I use to change the creation date so far:
# Get original exif data
try:
exif_dict = piexif.load(obj.path)
except (KeyError, piexif._exceptions.InvalidImageDataError):
logger.debug('No exif data for {}'.format(obj.path))
return
# Change creation date in exif_dict
date = obj.decided_stamp.strftime('%Y:%m:%d %H:%M:%S').encode('ascii')
try:
exif_dict['Exif'][EXIF_TAKE_TIME_ORIG] = date
except (KeyError, piexif._exceptions.InvalidImageDataError):
return
exif_bytes = piexif.dump(exif_dict)
# Save new exif
im = Image.open(obj.path)
im.save(obj.path, 'jpeg', exif=exif_bytes)

In your case, I think that no need to use Pillow.
exif_bytes = piexif.dump(exif_dict)
piexif.insert(exif_bytes, obj.path)

Related

Decrypting Image Headers for WebP, JPEG XL, HEIF, AVIF image formats using Python

I want to decode and understand the structure of various latest image formats (namely WebP, JPEG XL, HEIF and AVIF) and compare header information with respect to image data to see which format is header information heavy with respect to image data. I was wondering how I can do this using Python.
Is there a simple way to decode various image file formats of the same image in either HEX or Binary and learn how much of the file contents is the image data and how much else is the header information? Better yet, is there a way to learn the breakdown of header information for various file formats??
So far I got to a point where I got a file to open in binary mode (Below is the sample code I have for the AVIF test image) but I'm not sure how I can decrypt, read and understand the structure. Googling around, I found some information for JPEG format (like the information shown on this page for JPEG https://yasoob.me/posts/understanding-and-writing-jpeg-decoder-in-python/) but none for WebP, HEIF, and AVIF on how I can read the binary format.
image = 'test.avif'
with open(image, 'rb') as image_file:
content = image_file.read()
Content
I wish to know how exactly can we extract the header information from the images while compression.

Can't remove exif data with Node: seeing exif data written into the buffer in two different formats

Here’s what I’m trying to do:
Download a tiny placeholder image axios.get(imgUrl, { responseType: "arraybuffer" }).
Remove exif data (ICC profile). This is the tricky bit.
BASE64 encode it Buffer.from(img.data, 'binary').toString('base64').
Cache for later use.
Some images have an Adobe ICC profile attached to them which adds significantly to the size, and thus I’d like to remove it before encoding.
I’m able to work with the exif data on the original image. But if I use query params on the URL to change its size ?w=20 I can’t access the Exif data anymore, I’m just reminded that there is no exif data. I use node-exif. The exif data clearly is there still, since the created BASE64 string is as large as before.
If I look into the buffers Buffer.from(img.data, "binary").toString("utf8", 0, 370) I see they look very different. This is the original image which I can read the exif from:
����;ExifMMb(1!r2��i��-��'-��'Adobe Photoshop 21.1 (Macintosh)2020:03:25 10:35:31������"HH����
Adobe_CM��Adobed����
Here's the resized image that claims to contain no exif data:
����JFIF,,��`ICC_PROFILEPapplmntrRGB XYZ �
acspAPPLAPPL���-appldescPbdscm�6cprt�#wtptrXYZ$gXYZ8bXYZLrTRC`
aargl vcgt�0ndin�>chad�,mmod((bTRC`
gTRC`
aabgl aaggl
What do you think is happening when the image is resized?
Here's a demo of the problem: https://codesandbox.io/s/goofy-sea-68eny?file=/src/index.js

Is there a way to ignore EXIF orientation data when loading an image with PIL?

I'm getting some unwanted rotation when loading images using PIL. I'm loading image samples and their binary mask, so this is causing issues. I'm attempting to convert the code to use openCV instead, but this is proving sticky. I haven't seen any arguments in the documentation under Image.load(), but I'm hoping there's a workaround I just haven't found...
There is, but I haven't written it all up. Basically, if you load an image with EXIF "Orientation" field set, you can get that parameter.
First, a quick test using this image from the PIL GitHub source Pillow-7.1.2/Tests/images/hopper_orientation_6.jpg and run jhead on it you can see the EXIF orientation is 6:
jhead /Users/mark/StackOverflow/PillowBuild/Pillow-7.1.2/Tests/images/hopper_orientation_6.jpg
File name : /Users/mark/StackOverflow/PillowBuild/Pillow-7.1.2/Tests/images/hopper_orientation_6.jpg
File size : 4951 bytes
File date : 2020:04:24 14:00:09
Resolution : 128 x 128
Orientation : rotate 90 <--- see here
JPEG Quality : 75
Now do that in PIL:
from PIL import Image
# Load that image
im = Image.open('/Users/mark/StackOverflow/PillowBuild/Pillow-7.1.2/Tests/images/hopper_orientation_6.jpg')
# Get all EXIF data
e = im.getexif()
# Specifically get orientation
e.get(0x0112)
# prints 6
Now click on the source and you can work out how your image has been rotated and undo it.
Or, you could be completely unprofessional ;-) and create a function called SneakilyRemoveOrientationWhileNooneIsLooking(filename) and shell out (subprocess) to exiftool and remove the orientation with:
exiftool -Orientation= image.jpg
Author's "much simpler solution" detailed in above comment is misleading so I just wanna clear that up.
Pillow does not automatically apply EXIF orientation transformation when reading an image. However, it has a method to do so: PIL.ImageOps.exif_transpose(image)
OpenCV automatically applies EXIF orientation when reading an image. You can disable this behavior by using the IMREAD_IGNORE_ORIENTATION flag.
I believe the author's true intention was to apply the EXIF orientation rather than ignore it, which is exactly what his solution accomplished.

How do I save my files at 300 dpi using Pillow(PIL)?

I opening an image file using the pillow(PIL) library and saving it again under a different name. But when I save the image under the different name it takes my original 300 DPI file and makes it a 72 DPI file. I tried adding dpi=(300, 300) But still no success.
See code
from PIL import Image
image = Image.open('image-1.jpg')
image.save('image-2.jpg' , dpi=(300, 300))
My original file(image-1.jpg)
https://www.dropbox.com/s/x7xj6hyoemv3t94/image_info_1.jpg?raw=1
My copied file(image-2.jpg)
https://www.dropbox.com/s/dpcnkfozefobopn/image_info_2.jpg?raw=1
Notice how they still have the same image size: 8.45.
Thanks to #HansHirse explaining that the meta data was missing AKA exif information I saved the image with the exif info and it worked
from PIL import Image
image = Image.open('image-1.jpg')
exif = image.info['exif']
image.save('image-2.jpg' , exif=exif)

How to supress UserWarings in Openpyxl

I get the UserWarning 'C:\Users...\Anaconda3\lib\site-packages\openpyxl\reader\drawings.py:58: UserWarning: wmf image format is not supported so the image is being dropped warn(msg)'
How can I suppress this warning message?
If you only need to read the file, the warning can be suppressed by setting the read_only keyword argument to True:
wb = load_workbook(filename=<your file>, read_only=True)
stovfl answered this question:
To add WMF read or write support to your application, use PIL.WmfImagePlugin.register_handler() to register a WMF handler. You have to patch openpyxl as well, dropping WMF is hardcoded, see OpenPyXL - find_images
answer
Pillow supports wmf image file:
https://pillow.readthedocs.io/en/stable/handbook/image-file-formats.html#wmf-emf
By default, PIL.open() will load the image at 72 dpi.
So, if you want to modify excel file with wmf image(s),
what you need to do is convert wmf image to supported formats (e.g. "png")
when loading image(s) in the _import_image(img) function of
"C:\Users\%username%\AppData\Local\Programs\Python\Python310\Lib\site-packages\openpyxl\drawing\image.py":
def _import_image(img):
if not PILImage:
raise ImportError('You must install Pillow to fetch image objects')
if not isinstance(img, PILImage.Image):
img = PILImage.open(img)
# This is the part you have to add (Start)
try:
if (img.format.lower() == "wmf"):
fp = BytesIO()
img.save(fp, format="png")
img = PILImage.open(fp)
except:
None
# This is the part you have to add (End)
return img
This will allows you to modify the excel file with wmf image(s).
But note that the quality of the image might be visibly different compared to original image(s), since image format conversion takes place.

Resources