diff --git a/Tests/images/comment.jp2 b/Tests/images/comment.jp2 new file mode 100644 index 00000000000..4bdf91760e1 Binary files /dev/null and b/Tests/images/comment.jp2 differ diff --git a/Tests/test_file_jpeg2k.py b/Tests/test_file_jpeg2k.py index de622c47897..81795d54c91 100644 --- a/Tests/test_file_jpeg2k.py +++ b/Tests/test_file_jpeg2k.py @@ -353,6 +353,17 @@ def test_subsampling_decode(name): assert_image_similar(im, expected, epsilon) +def test_comment(): + with Image.open("Tests/images/comment.jp2") as im: + assert im.info["comment"] == b"Created by OpenJPEG version 2.5.0" + + # Test an image that is truncated partway through a codestream + with open("Tests/images/comment.jp2", "rb") as fp: + b = BytesIO(fp.read(130)) + with Image.open(b) as im: + pass + + @pytest.mark.parametrize( "test_file", [ diff --git a/docs/releasenotes/9.5.0.rst b/docs/releasenotes/9.5.0.rst index 13c99313a22..074931671b0 100644 --- a/docs/releasenotes/9.5.0.rst +++ b/docs/releasenotes/9.5.0.rst @@ -48,6 +48,12 @@ Added ``corners`` argument to ``ImageDraw.rounded_rectangle()`` ``corners``. This a tuple of Booleans, specifying whether to round each corner, ``(top_left, top_right, bottom_right, bottom_left)``. +Reading JPEG comments +^^^^^^^^^^^^^^^^^^^^^ + +When opening a JPEG2000 image, the comment may now be read into +:py:attr:`~PIL.Image.Image.info`. + Security ======== diff --git a/src/PIL/Jpeg2KImagePlugin.py b/src/PIL/Jpeg2KImagePlugin.py index 7457874c1ec..1dec2f84aa1 100644 --- a/src/PIL/Jpeg2KImagePlugin.py +++ b/src/PIL/Jpeg2KImagePlugin.py @@ -218,6 +218,8 @@ def _open(self): self._size, self.mode, self.custom_mimetype, dpi = header if dpi is not None: self.info["dpi"] = dpi + if self.fp.read(12).endswith(b"jp2c\xff\x4f\xff\x51"): + self._parse_comment() else: msg = "not a JPEG 2000 file" raise SyntaxError(msg) @@ -254,6 +256,28 @@ def _open(self): ) ] + def _parse_comment(self): + hdr = self.fp.read(2) + length = struct.unpack(">H", hdr)[0] + self.fp.seek(length - 2, os.SEEK_CUR) + + while True: + marker = self.fp.read(2) + if not marker: + break + typ = marker[1] + if typ in (0x90, 0xD9): + # Start of tile or end of codestream + break + hdr = self.fp.read(2) + length = struct.unpack(">H", hdr)[0] + if typ == 0x64: + # Comment + self.info["comment"] = self.fp.read(length - 2)[2:] + break + else: + self.fp.seek(length - 2, os.SEEK_CUR) + @property def reduce(self): # https://github.com/python-pillow/Pillow/issues/4343 found that the