Repository: python-pillow/Pillow

Issue:
Exif information not saved in JPEG Plugin unless explicitly added in save call
<!--
Thank you for reporting an issue.

Follow these guidelines to ensure your issue is handled properly.

If you have a ...

1.  General question: consider asking the question on Stack Overflow
    with the python-imaging-library tag:

    * https://stackoverflow.com/questions/tagged/python-imaging-library

    Do not ask a question in both places.

    If you think you have found a bug or have an unexplained exception
    then file a bug report here.

2.  Bug report: include a self-contained, copy-pastable example that
    generates the issue if possible. Be concise with code posted.
    Guidelines on how to provide a good bug report:

    * https://stackoverflow.com/help/mcve

    Bug reports which follow these guidelines are easier to diagnose,
    and are often handled much more quickly.

3.  Feature request: do a quick search of existing issues
    to make sure this has not been asked before.

We know asking good questions takes effort, and we appreciate your time.
Thank you.
-->

### What did you do?
Open an image, edit it's exif information, save the image and check the metadata.

### What did you expect to happen?
I expected the modified exif information to be written. This is exactly what happend when saving the image as a `png`.

### What actually happened?
The exif information was not saved when using the `jpg` format. It works when explicitly adding the `exif` parameter to the `save` call but this requires adding the exif information everywhere `save` is called (potentially third party code).

This also means that exif information contained in the original image will be removed by loading and saving a JPEG image using PIL. If this is desired the PNG plugin should probably be modified to handle exif information in the same manner as the JPEG plugin, but for my usecase I prefer the way it is handled in PNG.

### What are your OS, Python and Pillow versions?

* OS: Arch Linux with Kernel 6.0.6
* Python: 3.10.8
* Pillow: 9.4.0.dev0 (current main branch)

```python
from PIL import Image
from PIL import ExifTags

desc = "My image description"
exif_tag = ExifTags.Base.ImageDescription

img = Image.open("hopper.jpg")


exif = Image.Exif()
exif[exif_tag] = "My image description"

img.info["exif"] = exif

# png plugin does save exif information
img.save("hopper_out.png")
png_exif = Image.open("hopper_out.png").getexif()
assert png_exif != {}
assert png_exif[exif_tag] == desc

# jpeg plugin does not save exif information
img.save("hopper_out.jpg")
jpg_exif = Image.open("hopper_out.jpg").getexif()
assert jpg_exif != {}
assert jpg_exif[exif_tag] == desc
```



Discussion:

Resolving patch:
diff --git a/src/PIL/PngImagePlugin.py b/src/PIL/PngImagePlugin.py
--- a/src/PIL/PngImagePlugin.py
+++ b/src/PIL/PngImagePlugin.py
@@ -1383,7 +1383,7 @@ def _save(im, fp, filename, chunk=putchunk, save_all=False):
                 chunks.remove(cid)
                 chunk(fp, cid, data)
 
-    exif = im.encoderinfo.get("exif", im.info.get("exif"))
+    exif = im.encoderinfo.get("exif")
     if exif:
         if isinstance(exif, Image.Exif):
             exif = exif.tobytes(8)


Test patch:
diff --git a/Tests/test_file_png.py b/Tests/test_file_png.py
--- a/Tests/test_file_png.py
+++ b/Tests/test_file_png.py
@@ -706,10 +706,18 @@ def test_exif(self):
         assert exif[274] == 3
 
     def test_exif_save(self, tmp_path):
+        # Test exif is not saved from info
+        test_file = str(tmp_path / "temp.png")
         with Image.open("Tests/images/exif.png") as im:
-            test_file = str(tmp_path / "temp.png")
             im.save(test_file)
 
+        with Image.open(test_file) as reloaded:
+            assert reloaded._getexif() is None
+
+        # Test passing in exif
+        with Image.open("Tests/images/exif.png") as im:
+            im.save(test_file, exif=im.getexif())
+
         with Image.open(test_file) as reloaded:
             exif = reloaded._getexif()
         assert exif[274] == 1
@@ -720,7 +728,7 @@ def test_exif_save(self, tmp_path):
     def test_exif_from_jpg(self, tmp_path):
         with Image.open("Tests/images/pil_sample_rgb.jpg") as im:
             test_file = str(tmp_path / "temp.png")
-            im.save(test_file)
+            im.save(test_file, exif=im.getexif())
 
         with Image.open(test_file) as reloaded:
             exif = reloaded._getexif()