Repository: python-pillow/Pillow

Issue:
Maybe not an issue but a need for support
Hello, gif similarly to png with itxt or ztxt can contains arbitrary plain text metadata _(comment extension)_.

However, looking at the source code and the documentation of Pillow, I wasn’t able to find any relevant function for reading or writing them.



Discussion:
Hi. This does look like a feature request, that Pillow add support for the comment extension to the GIF format. Which sounds like a reasonable idea.

Quick question, what is 'exim'?

Okay, I've created a PR to add support for the GIF comment extension.


Resolving patch:
diff --git a/PIL/GifImagePlugin.py b/PIL/GifImagePlugin.py
--- a/PIL/GifImagePlugin.py
+++ b/PIL/GifImagePlugin.py
@@ -198,6 +198,11 @@ def _seek(self, frame):
                         # correct, but it seems to prevent the last
                         # frame from looking odd for some animations
                         self.disposal_method = dispose_bits
+                elif i8(s) == 254:
+                    #
+                    # comment extension
+                    #
+                    self.info["comment"] = block
                 elif i8(s) == 255:
                     #
                     # application extension
@@ -455,6 +460,12 @@ def _get_local_header(fp, im, offset, flags):
                  o8(transparency) +       # transparency index
                  o8(0))
 
+    if "comment" in im.encoderinfo and 1 <= len(im.encoderinfo["comment"]) <= 255:
+        fp.write(b"!" +
+                 o8(254) +                # extension intro
+                 o8(len(im.encoderinfo["comment"])) +
+                 im.encoderinfo["comment"] +
+                 o8(0))
     if "loop" in im.encoderinfo:
         number_of_loops = im.encoderinfo["loop"]
         fp.write(b"!" +
@@ -547,9 +558,11 @@ def getheader(im, palette=None, info=None):
     # http://www.matthewflickinger.com/lab/whatsinagif/bits_and_bytes.asp
 
     version = b"87a"
-    for extensionKey in ["transparency", "duration", "loop"]:
-        if info and extensionKey in info and \
-                not (extensionKey == "duration" and info[extensionKey] == 0):
+    for extensionKey in ["transparency", "duration", "loop", "comment"]:
+        if info and extensionKey in info:
+            if ((extensionKey == "duration" and info[extensionKey] == 0) or
+                (extensionKey == "comment" and not (1 <= len(info[extensionKey]) <= 255))):
+                continue
             version = b"89a"
             break
     else:


Test patch:
diff --git a/Tests/test_file_gif.py b/Tests/test_file_gif.py
--- a/Tests/test_file_gif.py
+++ b/Tests/test_file_gif.py
@@ -267,6 +267,18 @@ def test_background(self):
 
         self.assertEqual(reread.info['background'], im.info['background'])
 
+    def test_comment(self):
+        im = Image.open(TEST_GIF)
+        self.assertEqual(im.info['comment'], b"File written by Adobe Photoshop\xa8 4.0")
+
+        out = self.tempfile('temp.gif')
+        im = Image.new('L', (100, 100), '#000')
+        im.info['comment'] = b"Test comment text"
+        im.save(out)
+        reread = Image.open(out)
+
+        self.assertEqual(reread.info['comment'], im.info['comment'])
+
     def test_version(self):
         out = self.tempfile('temp.gif')
 
@@ -283,7 +295,7 @@ def test_version(self):
         self.assertEqual(reread.info["version"], b"GIF89a")
 
         # Test that a GIF87a image is also saved in that format
-        im = Image.open(TEST_GIF)
+        im = Image.open("Tests/images/test.colors.gif")
         im.save(out)
         reread = Image.open(out)
         self.assertEqual(reread.info["version"], b"GIF87a")