{
    "file_diffs": [
        {
            "old_file_content": "from io import BytesIO\n\nimport pytest\n\nfrom PIL import Image\n\nfrom .helper import mark_if_feature_version, skip_unless_feature\n\npytestmark = [\n    skip_unless_feature(\"webp\"),\n    skip_unless_feature(\"webp_mux\"),\n]\n\n\ndef test_read_exif_metadata():\n\n    file_path = \"Tests/images/flower.webp\"\n    with Image.open(file_path) as image:\n\n        assert image.format == \"WEBP\"\n        exif_data = image.info.get(\"exif\", None)\n        assert exif_data\n\n        exif = image._getexif()\n\n        # Camera make\n        assert exif[271] == \"Canon\"\n\n        with Image.open(\"Tests/images/flower.jpg\") as jpeg_image:\n            expected_exif = jpeg_image.info[\"exif\"]\n\n            assert exif_data == expected_exif\n\n\ndef test_read_exif_metadata_without_prefix():\n    with Image.open(\"Tests/images/flower2.webp\") as im:\n        # Assert prefix is not present\n        assert im.info[\"exif\"][:6] != b\"Exif\\x00\\x00\"\n\n        exif = im.getexif()\n        assert exif[305] == \"Adobe Photoshop CS6 (Macintosh)\"\n\n\n@mark_if_feature_version(\n    pytest.mark.valgrind_known_error, \"libjpeg_turbo\", \"2.0\", reason=\"Known Failing\"\n)\ndef test_write_exif_metadata():\n    file_path = \"Tests/images/flower.jpg\"\n    test_buffer = BytesIO()\n    with Image.open(file_path) as image:\n        expected_exif = image.info[\"exif\"]\n\n        image.save(test_buffer, \"webp\", exif=expected_exif)\n\n    test_buffer.seek(0)\n    with Image.open(test_buffer) as webp_image:\n        webp_exif = webp_image.info.get(\"exif\", None)\n    assert webp_exif == expected_exif[6:], \"WebP EXIF didn't match\"\n\n\ndef test_read_icc_profile():\n\n    file_path = \"Tests/images/flower2.webp\"\n    with Image.open(file_path) as image:\n\n        assert image.format == \"WEBP\"\n        assert image.info.get(\"icc_profile\", None)\n\n        icc = image.info[\"icc_profile\"]\n\n        with Image.open(\"Tests/images/flower2.jpg\") as jpeg_image:\n            expected_icc = jpeg_image.info[\"icc_profile\"]\n\n            assert icc == expected_icc\n\n\n@mark_if_feature_version(\n    pytest.mark.valgrind_known_error, \"libjpeg_turbo\", \"2.0\", reason=\"Known Failing\"\n)\ndef test_write_icc_metadata():\n    file_path = \"Tests/images/flower2.jpg\"\n    test_buffer = BytesIO()\n    with Image.open(file_path) as image:\n        expected_icc_profile = image.info[\"icc_profile\"]\n\n        image.save(test_buffer, \"webp\", icc_profile=expected_icc_profile)\n\n    test_buffer.seek(0)\n    with Image.open(test_buffer) as webp_image:\n        webp_icc_profile = webp_image.info.get(\"icc_profile\", None)\n\n    assert webp_icc_profile\n    if webp_icc_profile:\n        assert webp_icc_profile == expected_icc_profile, \"Webp ICC didn't match\"\n\n\n@mark_if_feature_version(\n    pytest.mark.valgrind_known_error, \"libjpeg_turbo\", \"2.0\", reason=\"Known Failing\"\n)\ndef test_read_no_exif():\n    file_path = \"Tests/images/flower.jpg\"\n    test_buffer = BytesIO()\n    with Image.open(file_path) as image:\n        assert \"exif\" in image.info\n\n        image.save(test_buffer, \"webp\")\n\n    test_buffer.seek(0)\n    with Image.open(test_buffer) as webp_image:\n        assert not webp_image._getexif()\n\n\n@skip_unless_feature(\"webp_anim\")\ndef test_write_animated_metadata(tmp_path):\n    iccp_data = b\"<iccp_data>\"\n    exif_data = b\"<exif_data>\"\n    xmp_data = b\"<xmp_data>\"\n\n    temp_file = str(tmp_path / \"temp.webp\")\n    with Image.open(\"Tests/images/anim_frame1.webp\") as frame1:\n        with Image.open(\"Tests/images/anim_frame2.webp\") as frame2:\n            frame1.save(\n                temp_file,\n                save_all=True,\n                append_images=[frame2, frame1, frame2],\n                icc_profile=iccp_data,\n                exif=exif_data,\n                xmp=xmp_data,\n            )\n\n    with Image.open(temp_file) as image:\n        assert \"icc_profile\" in image.info\n        assert \"exif\" in image.info\n        assert \"xmp\" in image.info\n        assert iccp_data == image.info.get(\"icc_profile\", None)\n        assert exif_data == image.info.get(\"exif\", None)\n        assert xmp_data == image.info.get(\"xmp\", None)\n",
            "new_file_content": "from io import BytesIO\n\nimport pytest\n\nfrom PIL import Image\n\nfrom .helper import mark_if_feature_version, skip_unless_feature\n\npytestmark = [\n    skip_unless_feature(\"webp\"),\n    skip_unless_feature(\"webp_mux\"),\n]\n\ntry:\n    from defusedxml import ElementTree\nexcept ImportError:\n    ElementTree = None\n\n\ndef test_read_exif_metadata():\n\n    file_path = \"Tests/images/flower.webp\"\n    with Image.open(file_path) as image:\n\n        assert image.format == \"WEBP\"\n        exif_data = image.info.get(\"exif\", None)\n        assert exif_data\n\n        exif = image._getexif()\n\n        # Camera make\n        assert exif[271] == \"Canon\"\n\n        with Image.open(\"Tests/images/flower.jpg\") as jpeg_image:\n            expected_exif = jpeg_image.info[\"exif\"]\n\n            assert exif_data == expected_exif\n\n\ndef test_read_exif_metadata_without_prefix():\n    with Image.open(\"Tests/images/flower2.webp\") as im:\n        # Assert prefix is not present\n        assert im.info[\"exif\"][:6] != b\"Exif\\x00\\x00\"\n\n        exif = im.getexif()\n        assert exif[305] == \"Adobe Photoshop CS6 (Macintosh)\"\n\n\n@mark_if_feature_version(\n    pytest.mark.valgrind_known_error, \"libjpeg_turbo\", \"2.0\", reason=\"Known Failing\"\n)\ndef test_write_exif_metadata():\n    file_path = \"Tests/images/flower.jpg\"\n    test_buffer = BytesIO()\n    with Image.open(file_path) as image:\n        expected_exif = image.info[\"exif\"]\n\n        image.save(test_buffer, \"webp\", exif=expected_exif)\n\n    test_buffer.seek(0)\n    with Image.open(test_buffer) as webp_image:\n        webp_exif = webp_image.info.get(\"exif\", None)\n    assert webp_exif == expected_exif[6:], \"WebP EXIF didn't match\"\n\n\ndef test_read_icc_profile():\n\n    file_path = \"Tests/images/flower2.webp\"\n    with Image.open(file_path) as image:\n\n        assert image.format == \"WEBP\"\n        assert image.info.get(\"icc_profile\", None)\n\n        icc = image.info[\"icc_profile\"]\n\n        with Image.open(\"Tests/images/flower2.jpg\") as jpeg_image:\n            expected_icc = jpeg_image.info[\"icc_profile\"]\n\n            assert icc == expected_icc\n\n\n@mark_if_feature_version(\n    pytest.mark.valgrind_known_error, \"libjpeg_turbo\", \"2.0\", reason=\"Known Failing\"\n)\ndef test_write_icc_metadata():\n    file_path = \"Tests/images/flower2.jpg\"\n    test_buffer = BytesIO()\n    with Image.open(file_path) as image:\n        expected_icc_profile = image.info[\"icc_profile\"]\n\n        image.save(test_buffer, \"webp\", icc_profile=expected_icc_profile)\n\n    test_buffer.seek(0)\n    with Image.open(test_buffer) as webp_image:\n        webp_icc_profile = webp_image.info.get(\"icc_profile\", None)\n\n    assert webp_icc_profile\n    if webp_icc_profile:\n        assert webp_icc_profile == expected_icc_profile, \"Webp ICC didn't match\"\n\n\n@mark_if_feature_version(\n    pytest.mark.valgrind_known_error, \"libjpeg_turbo\", \"2.0\", reason=\"Known Failing\"\n)\ndef test_read_no_exif():\n    file_path = \"Tests/images/flower.jpg\"\n    test_buffer = BytesIO()\n    with Image.open(file_path) as image:\n        assert \"exif\" in image.info\n\n        image.save(test_buffer, \"webp\")\n\n    test_buffer.seek(0)\n    with Image.open(test_buffer) as webp_image:\n        assert not webp_image._getexif()\n\n\ndef test_getxmp():\n    with Image.open(\"Tests/images/flower.webp\") as im:\n        assert \"xmp\" not in im.info\n        assert im.getxmp() == {}\n\n    with Image.open(\"Tests/images/flower2.webp\") as im:\n        if ElementTree is None:\n            with pytest.warns(UserWarning):\n                assert im.getxmp() == {}\n        else:\n            assert (\n                im.getxmp()[\"xmpmeta\"][\"xmptk\"]\n                == \"Adobe XMP Core 5.3-c011 66.145661, 2012/02/06-14:56:27        \"\n            )\n\n\n@skip_unless_feature(\"webp_anim\")\ndef test_write_animated_metadata(tmp_path):\n    iccp_data = b\"<iccp_data>\"\n    exif_data = b\"<exif_data>\"\n    xmp_data = b\"<xmp_data>\"\n\n    temp_file = str(tmp_path / \"temp.webp\")\n    with Image.open(\"Tests/images/anim_frame1.webp\") as frame1:\n        with Image.open(\"Tests/images/anim_frame2.webp\") as frame2:\n            frame1.save(\n                temp_file,\n                save_all=True,\n                append_images=[frame2, frame1, frame2],\n                icc_profile=iccp_data,\n                exif=exif_data,\n                xmp=xmp_data,\n            )\n\n    with Image.open(temp_file) as image:\n        assert \"icc_profile\" in image.info\n        assert \"exif\" in image.info\n        assert \"xmp\" in image.info\n        assert iccp_data == image.info.get(\"icc_profile\", None)\n        assert exif_data == image.info.get(\"exif\", None)\n        assert xmp_data == image.info.get(\"xmp\", None)\n",
            "header": {
                "file": {
                    "path": "Tests/test_file_webp_metadata.py"
                },
                "misc_line": null
            },
            "index_line": {
                "old_commit_hash": "f77a245c0",
                "new_commit_hash": "4f513d82b",
                "mode": "100644"
            },
            "is_binary_file": false,
            "binary_line": null,
            "minus_file": {
                "path": "a/Tests/test_file_webp_metadata.py"
            },
            "plus_file": {
                "path": "b/Tests/test_file_webp_metadata.py"
            },
            "hunks": [
                {
                    "descriptor": {
                        "old_range": {
                            "start": 11,
                            "length": 6
                        },
                        "new_range": {
                            "start": 11,
                            "length": 11
                        },
                        "section": "pytestmark = ["
                    },
                    "line_group": {
                        "all_lines": [
                            {
                                "content": "    skip_unless_feature(\"webp_mux\"),",
                                "type": "context"
                            },
                            {
                                "content": "]",
                                "type": "context"
                            },
                            {
                                "content": "",
                                "type": "context"
                            },
                            {
                                "content": "try:",
                                "type": "added"
                            },
                            {
                                "content": "    from defusedxml import ElementTree",
                                "type": "added"
                            },
                            {
                                "content": "except ImportError:",
                                "type": "added"
                            },
                            {
                                "content": "    ElementTree = None",
                                "type": "added"
                            },
                            {
                                "content": "",
                                "type": "added"
                            },
                            {
                                "content": "",
                                "type": "context"
                            },
                            {
                                "content": "def test_read_exif_metadata():",
                                "type": "context"
                            },
                            {
                                "content": "",
                                "type": "context"
                            }
                        ]
                    },
                    "modified_entities": [],
                    "added_entities": [
                        {
                            "file_name": "Tests/test_file_webp_metadata.py",
                            "type": "statement",
                            "name": "",
                            "content": "try:\n    from defusedxml import ElementTree\nexcept ImportError:\n    ElementTree = None",
                            "ast_type_str": "Try",
                            "start_lineno": 14,
                            "end_lineno": 17,
                            "parent": null
                        }
                    ],
                    "deleted_entities": []
                },
                {
                    "descriptor": {
                        "old_range": {
                            "start": 110,
                            "length": 6
                        },
                        "new_range": {
                            "start": 115,
                            "length": 22
                        },
                        "section": "def test_read_no_exif():"
                    },
                    "line_group": {
                        "all_lines": [
                            {
                                "content": "        assert not webp_image._getexif()",
                                "type": "context"
                            },
                            {
                                "content": "",
                                "type": "context"
                            },
                            {
                                "content": "",
                                "type": "context"
                            },
                            {
                                "content": "def test_getxmp():",
                                "type": "added"
                            },
                            {
                                "content": "    with Image.open(\"Tests/images/flower.webp\") as im:",
                                "type": "added"
                            },
                            {
                                "content": "        assert \"xmp\" not in im.info",
                                "type": "added"
                            },
                            {
                                "content": "        assert im.getxmp() == {}",
                                "type": "added"
                            },
                            {
                                "content": "",
                                "type": "added"
                            },
                            {
                                "content": "    with Image.open(\"Tests/images/flower2.webp\") as im:",
                                "type": "added"
                            },
                            {
                                "content": "        if ElementTree is None:",
                                "type": "added"
                            },
                            {
                                "content": "            with pytest.warns(UserWarning):",
                                "type": "added"
                            },
                            {
                                "content": "                assert im.getxmp() == {}",
                                "type": "added"
                            },
                            {
                                "content": "        else:",
                                "type": "added"
                            },
                            {
                                "content": "            assert (",
                                "type": "added"
                            },
                            {
                                "content": "                im.getxmp()[\"xmpmeta\"][\"xmptk\"]",
                                "type": "added"
                            },
                            {
                                "content": "                == \"Adobe XMP Core 5.3-c011 66.145661, 2012/02/06-14:56:27        \"",
                                "type": "added"
                            },
                            {
                                "content": "            )",
                                "type": "added"
                            },
                            {
                                "content": "",
                                "type": "added"
                            },
                            {
                                "content": "",
                                "type": "added"
                            },
                            {
                                "content": "@skip_unless_feature(\"webp_anim\")",
                                "type": "context"
                            },
                            {
                                "content": "def test_write_animated_metadata(tmp_path):",
                                "type": "context"
                            },
                            {
                                "content": "    iccp_data = b\"<iccp_data>\"",
                                "type": "context"
                            }
                        ]
                    },
                    "modified_entities": [],
                    "added_entities": [
                        {
                            "file_name": "Tests/test_file_webp_metadata.py",
                            "type": "function",
                            "name": "test_getxmp",
                            "content": "def test_getxmp():\n    with Image.open(\"Tests/images/flower.webp\") as im:\n        assert \"xmp\" not in im.info\n        assert im.getxmp() == {}\n\n    with Image.open(\"Tests/images/flower2.webp\") as im:\n        if ElementTree is None:\n            with pytest.warns(UserWarning):\n                assert im.getxmp() == {}\n        else:\n            assert (\n                im.getxmp()[\"xmpmeta\"][\"xmptk\"]\n                == \"Adobe XMP Core 5.3-c011 66.145661, 2012/02/06-14:56:27        \"\n            )",
                            "ast_type_str": "FunctionDef",
                            "start_lineno": 118,
                            "end_lineno": 131,
                            "parent": null
                        }
                    ],
                    "deleted_entities": []
                }
            ]
        },
        {
            "old_file_content": "9.4.0\n-----\n\nBackwards Incompatible Changes\n==============================\n\nTODO\n^^^^\n\nTODO\n\nDeprecations\n============\n\nTODO\n^^^^\n\nTODO\n\nAPI Changes\n===========\n\nTODO\n^^^^\n\nTODO\n\nAPI Additions\n=============\n\nAdded start position for getmask and getmask2\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nText may render differently when starting at fractional coordinates, so\n:py:meth:`.FreeTypeFont.getmask` and :py:meth:`.FreeTypeFont.getmask2` now\nsupport a ``start`` argument. This tuple of horizontal and vertical offset\nwill be used internally by :py:meth:`.ImageDraw.text` to more accurately place\ntext at the ``xy`` coordinates.\n\nAdded the ``exact`` encoding option for WebP\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nThe ``exact`` encoding option for WebP is now supported. The WebP encoder\nremoves the hidden RGB values for better compression by default in libwebp 0.5\nor later. By setting this option to ``True``, the encoder will keep the hidden\nRGB values.\n\nSecurity\n========\n\nTODO\n^^^^\n\nTODO\n\nOther Changes\n=============\n\nTODO\n^^^^\n\nTODO\n",
            "new_file_content": "9.4.0\n-----\n\nBackwards Incompatible Changes\n==============================\n\nTODO\n^^^^\n\nTODO\n\nDeprecations\n============\n\nTODO\n^^^^\n\nTODO\n\nAPI Changes\n===========\n\nTODO\n^^^^\n\nTODO\n\nAPI Additions\n=============\n\nAdded start position for getmask and getmask2\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nText may render differently when starting at fractional coordinates, so\n:py:meth:`.FreeTypeFont.getmask` and :py:meth:`.FreeTypeFont.getmask2` now\nsupport a ``start`` argument. This tuple of horizontal and vertical offset\nwill be used internally by :py:meth:`.ImageDraw.text` to more accurately place\ntext at the ``xy`` coordinates.\n\nAdded the ``exact`` encoding option for WebP\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nThe ``exact`` encoding option for WebP is now supported. The WebP encoder\nremoves the hidden RGB values for better compression by default in libwebp 0.5\nor later. By setting this option to ``True``, the encoder will keep the hidden\nRGB values.\n\ngetxmp()\n^^^^^^^^\n\n`XMP data <https://en.wikipedia.org/wiki/Extensible_Metadata_Platform>`_ can now be\ndecoded for WEBP images through ``getxmp()``.\n\nSecurity\n========\n\nTODO\n^^^^\n\nTODO\n\nOther Changes\n=============\n\nTODO\n^^^^\n\nTODO\n",
            "header": {
                "file": {
                    "path": "docs/releasenotes/9.4.0.rst"
                },
                "misc_line": null
            },
            "index_line": {
                "old_commit_hash": "0f47f5ad6",
                "new_commit_hash": "f2b50fa5b",
                "mode": "100644"
            },
            "is_binary_file": false,
            "binary_line": null,
            "minus_file": {
                "path": "a/docs/releasenotes/9.4.0.rst"
            },
            "plus_file": {
                "path": "b/docs/releasenotes/9.4.0.rst"
            },
            "hunks": [
                {
                    "descriptor": {
                        "old_range": {
                            "start": 45,
                            "length": 6
                        },
                        "new_range": {
                            "start": 45,
                            "length": 12
                        },
                        "section": "removes the hidden RGB values for better compression by default in libwebp 0.5"
                    },
                    "line_group": {
                        "all_lines": [
                            {
                                "content": "or later. By setting this option to ``True``, the encoder will keep the hidden",
                                "type": "context"
                            },
                            {
                                "content": "RGB values.",
                                "type": "context"
                            },
                            {
                                "content": "",
                                "type": "context"
                            },
                            {
                                "content": "getxmp()",
                                "type": "added"
                            },
                            {
                                "content": "^^^^^^^^",
                                "type": "added"
                            },
                            {
                                "content": "",
                                "type": "added"
                            },
                            {
                                "content": "`XMP data <https://en.wikipedia.org/wiki/Extensible_Metadata_Platform>`_ can now be",
                                "type": "added"
                            },
                            {
                                "content": "decoded for WEBP images through ``getxmp()``.",
                                "type": "added"
                            },
                            {
                                "content": "",
                                "type": "added"
                            },
                            {
                                "content": "Security",
                                "type": "context"
                            },
                            {
                                "content": "========",
                                "type": "context"
                            },
                            {
                                "content": "",
                                "type": "context"
                            }
                        ]
                    },
                    "modified_entities": [],
                    "added_entities": [],
                    "deleted_entities": []
                }
            ]
        },
        {
            "old_file_content": "from io import BytesIO\n\nfrom . import Image, ImageFile\n\ntry:\n    from . import _webp\n\n    SUPPORTED = True\nexcept ImportError:\n    SUPPORTED = False\n\n\n_VALID_WEBP_MODES = {\"RGBX\": True, \"RGBA\": True, \"RGB\": True}\n\n_VALID_WEBP_LEGACY_MODES = {\"RGB\": True, \"RGBA\": True}\n\n_VP8_MODES_BY_IDENTIFIER = {\n    b\"VP8 \": \"RGB\",\n    b\"VP8X\": \"RGBA\",\n    b\"VP8L\": \"RGBA\",  # lossless\n}\n\n\ndef _accept(prefix):\n    is_riff_file_format = prefix[:4] == b\"RIFF\"\n    is_webp_file = prefix[8:12] == b\"WEBP\"\n    is_valid_vp8_mode = prefix[12:16] in _VP8_MODES_BY_IDENTIFIER\n\n    if is_riff_file_format and is_webp_file and is_valid_vp8_mode:\n        if not SUPPORTED:\n            return (\n                \"image file could not be identified because WEBP support not installed\"\n            )\n        return True\n\n\nclass WebPImageFile(ImageFile.ImageFile):\n\n    format = \"WEBP\"\n    format_description = \"WebP image\"\n    __loaded = 0\n    __logical_frame = 0\n\n    def _open(self):\n        if not _webp.HAVE_WEBPANIM:\n            # Legacy mode\n            data, width, height, self.mode, icc_profile, exif = _webp.WebPDecode(\n                self.fp.read()\n            )\n            if icc_profile:\n                self.info[\"icc_profile\"] = icc_profile\n            if exif:\n                self.info[\"exif\"] = exif\n            self._size = width, height\n            self.fp = BytesIO(data)\n            self.tile = [(\"raw\", (0, 0) + self.size, 0, self.mode)]\n            self.n_frames = 1\n            self.is_animated = False\n            return\n\n        # Use the newer AnimDecoder API to parse the (possibly) animated file,\n        # and access muxed chunks like ICC/EXIF/XMP.\n        self._decoder = _webp.WebPAnimDecoder(self.fp.read())\n\n        # Get info from decoder\n        width, height, loop_count, bgcolor, frame_count, mode = self._decoder.get_info()\n        self._size = width, height\n        self.info[\"loop\"] = loop_count\n        bg_a, bg_r, bg_g, bg_b = (\n            (bgcolor >> 24) & 0xFF,\n            (bgcolor >> 16) & 0xFF,\n            (bgcolor >> 8) & 0xFF,\n            bgcolor & 0xFF,\n        )\n        self.info[\"background\"] = (bg_r, bg_g, bg_b, bg_a)\n        self.n_frames = frame_count\n        self.is_animated = self.n_frames > 1\n        self.mode = \"RGB\" if mode == \"RGBX\" else mode\n        self.rawmode = mode\n        self.tile = []\n\n        # Attempt to read ICC / EXIF / XMP chunks from file\n        icc_profile = self._decoder.get_chunk(\"ICCP\")\n        exif = self._decoder.get_chunk(\"EXIF\")\n        xmp = self._decoder.get_chunk(\"XMP \")\n        if icc_profile:\n            self.info[\"icc_profile\"] = icc_profile\n        if exif:\n            self.info[\"exif\"] = exif\n        if xmp:\n            self.info[\"xmp\"] = xmp\n\n        # Initialize seek state\n        self._reset(reset=False)\n\n    def _getexif(self):\n        if \"exif\" not in self.info:\n            return None\n        return self.getexif()._get_merged_dict()\n\n    def seek(self, frame):\n        if not self._seek_check(frame):\n            return\n\n        # Set logical frame to requested position\n        self.__logical_frame = frame\n\n    def _reset(self, reset=True):\n        if reset:\n            self._decoder.reset()\n        self.__physical_frame = 0\n        self.__loaded = -1\n        self.__timestamp = 0\n\n    def _get_next(self):\n        # Get next frame\n        ret = self._decoder.get_next()\n        self.__physical_frame += 1\n\n        # Check if an error occurred\n        if ret is None:\n            self._reset()  # Reset just to be safe\n            self.seek(0)\n            raise EOFError(\"failed to decode next frame in WebP file\")\n\n        # Compute duration\n        data, timestamp = ret\n        duration = timestamp - self.__timestamp\n        self.__timestamp = timestamp\n\n        # libwebp gives frame end, adjust to start of frame\n        timestamp -= duration\n        return data, timestamp, duration\n\n    def _seek(self, frame):\n        if self.__physical_frame == frame:\n            return  # Nothing to do\n        if frame < self.__physical_frame:\n            self._reset()  # Rewind to beginning\n        while self.__physical_frame < frame:\n            self._get_next()  # Advance to the requested frame\n\n    def load(self):\n        if _webp.HAVE_WEBPANIM:\n            if self.__loaded != self.__logical_frame:\n                self._seek(self.__logical_frame)\n\n                # We need to load the image data for this frame\n                data, timestamp, duration = self._get_next()\n                self.info[\"timestamp\"] = timestamp\n                self.info[\"duration\"] = duration\n                self.__loaded = self.__logical_frame\n\n                # Set tile\n                if self.fp and self._exclusive_fp:\n                    self.fp.close()\n                self.fp = BytesIO(data)\n                self.tile = [(\"raw\", (0, 0) + self.size, 0, self.rawmode)]\n\n        return super().load()\n\n    def tell(self):\n        if not _webp.HAVE_WEBPANIM:\n            return super().tell()\n\n        return self.__logical_frame\n\n\ndef _save_all(im, fp, filename):\n    encoderinfo = im.encoderinfo.copy()\n    append_images = list(encoderinfo.get(\"append_images\", []))\n\n    # If total frame count is 1, then save using the legacy API, which\n    # will preserve non-alpha modes\n    total = 0\n    for ims in [im] + append_images:\n        total += getattr(ims, \"n_frames\", 1)\n    if total == 1:\n        _save(im, fp, filename)\n        return\n\n    background = (0, 0, 0, 0)\n    if \"background\" in encoderinfo:\n        background = encoderinfo[\"background\"]\n    elif \"background\" in im.info:\n        background = im.info[\"background\"]\n        if isinstance(background, int):\n            # GifImagePlugin stores a global color table index in\n            # info[\"background\"]. So it must be converted to an RGBA value\n            palette = im.getpalette()\n            if palette:\n                r, g, b = palette[background * 3 : (background + 1) * 3]\n                background = (r, g, b, 255)\n            else:\n                background = (background, background, background, 255)\n\n    duration = im.encoderinfo.get(\"duration\", im.info.get(\"duration\", 0))\n    loop = im.encoderinfo.get(\"loop\", 0)\n    minimize_size = im.encoderinfo.get(\"minimize_size\", False)\n    kmin = im.encoderinfo.get(\"kmin\", None)\n    kmax = im.encoderinfo.get(\"kmax\", None)\n    allow_mixed = im.encoderinfo.get(\"allow_mixed\", False)\n    verbose = False\n    lossless = im.encoderinfo.get(\"lossless\", False)\n    quality = im.encoderinfo.get(\"quality\", 80)\n    method = im.encoderinfo.get(\"method\", 0)\n    icc_profile = im.encoderinfo.get(\"icc_profile\") or \"\"\n    exif = im.encoderinfo.get(\"exif\", \"\")\n    if isinstance(exif, Image.Exif):\n        exif = exif.tobytes()\n    xmp = im.encoderinfo.get(\"xmp\", \"\")\n    if allow_mixed:\n        lossless = False\n\n    # Sensible keyframe defaults are from gif2webp.c script\n    if kmin is None:\n        kmin = 9 if lossless else 3\n    if kmax is None:\n        kmax = 17 if lossless else 5\n\n    # Validate background color\n    if (\n        not isinstance(background, (list, tuple))\n        or len(background) != 4\n        or not all(0 <= v < 256 for v in background)\n    ):\n        raise OSError(\n            f\"Background color is not an RGBA tuple clamped to (0-255): {background}\"\n        )\n\n    # Convert to packed uint\n    bg_r, bg_g, bg_b, bg_a = background\n    background = (bg_a << 24) | (bg_r << 16) | (bg_g << 8) | (bg_b << 0)\n\n    # Setup the WebP animation encoder\n    enc = _webp.WebPAnimEncoder(\n        im.size[0],\n        im.size[1],\n        background,\n        loop,\n        minimize_size,\n        kmin,\n        kmax,\n        allow_mixed,\n        verbose,\n    )\n\n    # Add each frame\n    frame_idx = 0\n    timestamp = 0\n    cur_idx = im.tell()\n    try:\n        for ims in [im] + append_images:\n            # Get # of frames in this image\n            nfr = getattr(ims, \"n_frames\", 1)\n\n            for idx in range(nfr):\n                ims.seek(idx)\n                ims.load()\n\n                # Make sure image mode is supported\n                frame = ims\n                rawmode = ims.mode\n                if ims.mode not in _VALID_WEBP_MODES:\n                    alpha = (\n                        \"A\" in ims.mode\n                        or \"a\" in ims.mode\n                        or (ims.mode == \"P\" and \"A\" in ims.im.getpalettemode())\n                    )\n                    rawmode = \"RGBA\" if alpha else \"RGB\"\n                    frame = ims.convert(rawmode)\n\n                if rawmode == \"RGB\":\n                    # For faster conversion, use RGBX\n                    rawmode = \"RGBX\"\n\n                # Append the frame to the animation encoder\n                enc.add(\n                    frame.tobytes(\"raw\", rawmode),\n                    timestamp,\n                    frame.size[0],\n                    frame.size[1],\n                    rawmode,\n                    lossless,\n                    quality,\n                    method,\n                )\n\n                # Update timestamp and frame index\n                if isinstance(duration, (list, tuple)):\n                    timestamp += duration[frame_idx]\n                else:\n                    timestamp += duration\n                frame_idx += 1\n\n    finally:\n        im.seek(cur_idx)\n\n    # Force encoder to flush frames\n    enc.add(None, timestamp, 0, 0, \"\", lossless, quality, 0)\n\n    # Get the final output from the encoder\n    data = enc.assemble(icc_profile, exif, xmp)\n    if data is None:\n        raise OSError(\"cannot write file as WebP (encoder returned None)\")\n\n    fp.write(data)\n\n\ndef _save(im, fp, filename):\n    lossless = im.encoderinfo.get(\"lossless\", False)\n    quality = im.encoderinfo.get(\"quality\", 80)\n    icc_profile = im.encoderinfo.get(\"icc_profile\") or \"\"\n    exif = im.encoderinfo.get(\"exif\", b\"\")\n    if isinstance(exif, Image.Exif):\n        exif = exif.tobytes()\n    if exif.startswith(b\"Exif\\x00\\x00\"):\n        exif = exif[6:]\n    xmp = im.encoderinfo.get(\"xmp\", \"\")\n    method = im.encoderinfo.get(\"method\", 4)\n    exact = 1 if im.encoderinfo.get(\"exact\") else 0\n\n    if im.mode not in _VALID_WEBP_LEGACY_MODES:\n        alpha = (\n            \"A\" in im.mode\n            or \"a\" in im.mode\n            or (im.mode == \"P\" and \"transparency\" in im.info)\n        )\n        im = im.convert(\"RGBA\" if alpha else \"RGB\")\n\n    data = _webp.WebPEncode(\n        im.tobytes(),\n        im.size[0],\n        im.size[1],\n        lossless,\n        float(quality),\n        im.mode,\n        icc_profile,\n        method,\n        exact,\n        exif,\n        xmp,\n    )\n    if data is None:\n        raise OSError(\"cannot write file as WebP (encoder returned None)\")\n\n    fp.write(data)\n\n\nImage.register_open(WebPImageFile.format, WebPImageFile, _accept)\nif SUPPORTED:\n    Image.register_save(WebPImageFile.format, _save)\n    if _webp.HAVE_WEBPANIM:\n        Image.register_save_all(WebPImageFile.format, _save_all)\n    Image.register_extension(WebPImageFile.format, \".webp\")\n    Image.register_mime(WebPImageFile.format, \"image/webp\")\n",
            "new_file_content": "from io import BytesIO\n\nfrom . import Image, ImageFile\n\ntry:\n    from . import _webp\n\n    SUPPORTED = True\nexcept ImportError:\n    SUPPORTED = False\n\n\n_VALID_WEBP_MODES = {\"RGBX\": True, \"RGBA\": True, \"RGB\": True}\n\n_VALID_WEBP_LEGACY_MODES = {\"RGB\": True, \"RGBA\": True}\n\n_VP8_MODES_BY_IDENTIFIER = {\n    b\"VP8 \": \"RGB\",\n    b\"VP8X\": \"RGBA\",\n    b\"VP8L\": \"RGBA\",  # lossless\n}\n\n\ndef _accept(prefix):\n    is_riff_file_format = prefix[:4] == b\"RIFF\"\n    is_webp_file = prefix[8:12] == b\"WEBP\"\n    is_valid_vp8_mode = prefix[12:16] in _VP8_MODES_BY_IDENTIFIER\n\n    if is_riff_file_format and is_webp_file and is_valid_vp8_mode:\n        if not SUPPORTED:\n            return (\n                \"image file could not be identified because WEBP support not installed\"\n            )\n        return True\n\n\nclass WebPImageFile(ImageFile.ImageFile):\n\n    format = \"WEBP\"\n    format_description = \"WebP image\"\n    __loaded = 0\n    __logical_frame = 0\n\n    def _open(self):\n        if not _webp.HAVE_WEBPANIM:\n            # Legacy mode\n            data, width, height, self.mode, icc_profile, exif = _webp.WebPDecode(\n                self.fp.read()\n            )\n            if icc_profile:\n                self.info[\"icc_profile\"] = icc_profile\n            if exif:\n                self.info[\"exif\"] = exif\n            self._size = width, height\n            self.fp = BytesIO(data)\n            self.tile = [(\"raw\", (0, 0) + self.size, 0, self.mode)]\n            self.n_frames = 1\n            self.is_animated = False\n            return\n\n        # Use the newer AnimDecoder API to parse the (possibly) animated file,\n        # and access muxed chunks like ICC/EXIF/XMP.\n        self._decoder = _webp.WebPAnimDecoder(self.fp.read())\n\n        # Get info from decoder\n        width, height, loop_count, bgcolor, frame_count, mode = self._decoder.get_info()\n        self._size = width, height\n        self.info[\"loop\"] = loop_count\n        bg_a, bg_r, bg_g, bg_b = (\n            (bgcolor >> 24) & 0xFF,\n            (bgcolor >> 16) & 0xFF,\n            (bgcolor >> 8) & 0xFF,\n            bgcolor & 0xFF,\n        )\n        self.info[\"background\"] = (bg_r, bg_g, bg_b, bg_a)\n        self.n_frames = frame_count\n        self.is_animated = self.n_frames > 1\n        self.mode = \"RGB\" if mode == \"RGBX\" else mode\n        self.rawmode = mode\n        self.tile = []\n\n        # Attempt to read ICC / EXIF / XMP chunks from file\n        icc_profile = self._decoder.get_chunk(\"ICCP\")\n        exif = self._decoder.get_chunk(\"EXIF\")\n        xmp = self._decoder.get_chunk(\"XMP \")\n        if icc_profile:\n            self.info[\"icc_profile\"] = icc_profile\n        if exif:\n            self.info[\"exif\"] = exif\n        if xmp:\n            self.info[\"xmp\"] = xmp\n\n        # Initialize seek state\n        self._reset(reset=False)\n\n    def _getexif(self):\n        if \"exif\" not in self.info:\n            return None\n        return self.getexif()._get_merged_dict()\n\n    def getxmp(self):\n        \"\"\"\n        Returns a dictionary containing the XMP tags.\n        Requires defusedxml to be installed.\n\n        :returns: XMP tags in a dictionary.\n        \"\"\"\n        return self._getxmp(self.info[\"xmp\"]) if \"xmp\" in self.info else {}\n\n    def seek(self, frame):\n        if not self._seek_check(frame):\n            return\n\n        # Set logical frame to requested position\n        self.__logical_frame = frame\n\n    def _reset(self, reset=True):\n        if reset:\n            self._decoder.reset()\n        self.__physical_frame = 0\n        self.__loaded = -1\n        self.__timestamp = 0\n\n    def _get_next(self):\n        # Get next frame\n        ret = self._decoder.get_next()\n        self.__physical_frame += 1\n\n        # Check if an error occurred\n        if ret is None:\n            self._reset()  # Reset just to be safe\n            self.seek(0)\n            raise EOFError(\"failed to decode next frame in WebP file\")\n\n        # Compute duration\n        data, timestamp = ret\n        duration = timestamp - self.__timestamp\n        self.__timestamp = timestamp\n\n        # libwebp gives frame end, adjust to start of frame\n        timestamp -= duration\n        return data, timestamp, duration\n\n    def _seek(self, frame):\n        if self.__physical_frame == frame:\n            return  # Nothing to do\n        if frame < self.__physical_frame:\n            self._reset()  # Rewind to beginning\n        while self.__physical_frame < frame:\n            self._get_next()  # Advance to the requested frame\n\n    def load(self):\n        if _webp.HAVE_WEBPANIM:\n            if self.__loaded != self.__logical_frame:\n                self._seek(self.__logical_frame)\n\n                # We need to load the image data for this frame\n                data, timestamp, duration = self._get_next()\n                self.info[\"timestamp\"] = timestamp\n                self.info[\"duration\"] = duration\n                self.__loaded = self.__logical_frame\n\n                # Set tile\n                if self.fp and self._exclusive_fp:\n                    self.fp.close()\n                self.fp = BytesIO(data)\n                self.tile = [(\"raw\", (0, 0) + self.size, 0, self.rawmode)]\n\n        return super().load()\n\n    def tell(self):\n        if not _webp.HAVE_WEBPANIM:\n            return super().tell()\n\n        return self.__logical_frame\n\n\ndef _save_all(im, fp, filename):\n    encoderinfo = im.encoderinfo.copy()\n    append_images = list(encoderinfo.get(\"append_images\", []))\n\n    # If total frame count is 1, then save using the legacy API, which\n    # will preserve non-alpha modes\n    total = 0\n    for ims in [im] + append_images:\n        total += getattr(ims, \"n_frames\", 1)\n    if total == 1:\n        _save(im, fp, filename)\n        return\n\n    background = (0, 0, 0, 0)\n    if \"background\" in encoderinfo:\n        background = encoderinfo[\"background\"]\n    elif \"background\" in im.info:\n        background = im.info[\"background\"]\n        if isinstance(background, int):\n            # GifImagePlugin stores a global color table index in\n            # info[\"background\"]. So it must be converted to an RGBA value\n            palette = im.getpalette()\n            if palette:\n                r, g, b = palette[background * 3 : (background + 1) * 3]\n                background = (r, g, b, 255)\n            else:\n                background = (background, background, background, 255)\n\n    duration = im.encoderinfo.get(\"duration\", im.info.get(\"duration\", 0))\n    loop = im.encoderinfo.get(\"loop\", 0)\n    minimize_size = im.encoderinfo.get(\"minimize_size\", False)\n    kmin = im.encoderinfo.get(\"kmin\", None)\n    kmax = im.encoderinfo.get(\"kmax\", None)\n    allow_mixed = im.encoderinfo.get(\"allow_mixed\", False)\n    verbose = False\n    lossless = im.encoderinfo.get(\"lossless\", False)\n    quality = im.encoderinfo.get(\"quality\", 80)\n    method = im.encoderinfo.get(\"method\", 0)\n    icc_profile = im.encoderinfo.get(\"icc_profile\") or \"\"\n    exif = im.encoderinfo.get(\"exif\", \"\")\n    if isinstance(exif, Image.Exif):\n        exif = exif.tobytes()\n    xmp = im.encoderinfo.get(\"xmp\", \"\")\n    if allow_mixed:\n        lossless = False\n\n    # Sensible keyframe defaults are from gif2webp.c script\n    if kmin is None:\n        kmin = 9 if lossless else 3\n    if kmax is None:\n        kmax = 17 if lossless else 5\n\n    # Validate background color\n    if (\n        not isinstance(background, (list, tuple))\n        or len(background) != 4\n        or not all(0 <= v < 256 for v in background)\n    ):\n        raise OSError(\n            f\"Background color is not an RGBA tuple clamped to (0-255): {background}\"\n        )\n\n    # Convert to packed uint\n    bg_r, bg_g, bg_b, bg_a = background\n    background = (bg_a << 24) | (bg_r << 16) | (bg_g << 8) | (bg_b << 0)\n\n    # Setup the WebP animation encoder\n    enc = _webp.WebPAnimEncoder(\n        im.size[0],\n        im.size[1],\n        background,\n        loop,\n        minimize_size,\n        kmin,\n        kmax,\n        allow_mixed,\n        verbose,\n    )\n\n    # Add each frame\n    frame_idx = 0\n    timestamp = 0\n    cur_idx = im.tell()\n    try:\n        for ims in [im] + append_images:\n            # Get # of frames in this image\n            nfr = getattr(ims, \"n_frames\", 1)\n\n            for idx in range(nfr):\n                ims.seek(idx)\n                ims.load()\n\n                # Make sure image mode is supported\n                frame = ims\n                rawmode = ims.mode\n                if ims.mode not in _VALID_WEBP_MODES:\n                    alpha = (\n                        \"A\" in ims.mode\n                        or \"a\" in ims.mode\n                        or (ims.mode == \"P\" and \"A\" in ims.im.getpalettemode())\n                    )\n                    rawmode = \"RGBA\" if alpha else \"RGB\"\n                    frame = ims.convert(rawmode)\n\n                if rawmode == \"RGB\":\n                    # For faster conversion, use RGBX\n                    rawmode = \"RGBX\"\n\n                # Append the frame to the animation encoder\n                enc.add(\n                    frame.tobytes(\"raw\", rawmode),\n                    timestamp,\n                    frame.size[0],\n                    frame.size[1],\n                    rawmode,\n                    lossless,\n                    quality,\n                    method,\n                )\n\n                # Update timestamp and frame index\n                if isinstance(duration, (list, tuple)):\n                    timestamp += duration[frame_idx]\n                else:\n                    timestamp += duration\n                frame_idx += 1\n\n    finally:\n        im.seek(cur_idx)\n\n    # Force encoder to flush frames\n    enc.add(None, timestamp, 0, 0, \"\", lossless, quality, 0)\n\n    # Get the final output from the encoder\n    data = enc.assemble(icc_profile, exif, xmp)\n    if data is None:\n        raise OSError(\"cannot write file as WebP (encoder returned None)\")\n\n    fp.write(data)\n\n\ndef _save(im, fp, filename):\n    lossless = im.encoderinfo.get(\"lossless\", False)\n    quality = im.encoderinfo.get(\"quality\", 80)\n    icc_profile = im.encoderinfo.get(\"icc_profile\") or \"\"\n    exif = im.encoderinfo.get(\"exif\", b\"\")\n    if isinstance(exif, Image.Exif):\n        exif = exif.tobytes()\n    if exif.startswith(b\"Exif\\x00\\x00\"):\n        exif = exif[6:]\n    xmp = im.encoderinfo.get(\"xmp\", \"\")\n    method = im.encoderinfo.get(\"method\", 4)\n    exact = 1 if im.encoderinfo.get(\"exact\") else 0\n\n    if im.mode not in _VALID_WEBP_LEGACY_MODES:\n        alpha = (\n            \"A\" in im.mode\n            or \"a\" in im.mode\n            or (im.mode == \"P\" and \"transparency\" in im.info)\n        )\n        im = im.convert(\"RGBA\" if alpha else \"RGB\")\n\n    data = _webp.WebPEncode(\n        im.tobytes(),\n        im.size[0],\n        im.size[1],\n        lossless,\n        float(quality),\n        im.mode,\n        icc_profile,\n        method,\n        exact,\n        exif,\n        xmp,\n    )\n    if data is None:\n        raise OSError(\"cannot write file as WebP (encoder returned None)\")\n\n    fp.write(data)\n\n\nImage.register_open(WebPImageFile.format, WebPImageFile, _accept)\nif SUPPORTED:\n    Image.register_save(WebPImageFile.format, _save)\n    if _webp.HAVE_WEBPANIM:\n        Image.register_save_all(WebPImageFile.format, _save_all)\n    Image.register_extension(WebPImageFile.format, \".webp\")\n    Image.register_mime(WebPImageFile.format, \"image/webp\")\n",
            "header": {
                "file": {
                    "path": "src/PIL/WebPImagePlugin.py"
                },
                "misc_line": null
            },
            "index_line": {
                "old_commit_hash": "e3c19db3d",
                "new_commit_hash": "81ed550d9",
                "mode": "100644"
            },
            "is_binary_file": false,
            "binary_line": null,
            "minus_file": {
                "path": "a/src/PIL/WebPImagePlugin.py"
            },
            "plus_file": {
                "path": "b/src/PIL/WebPImagePlugin.py"
            },
            "hunks": [
                {
                    "descriptor": {
                        "old_range": {
                            "start": 98,
                            "length": 6
                        },
                        "new_range": {
                            "start": 98,
                            "length": 15
                        },
                        "section": "class WebPImageFile(ImageFile.ImageFile):"
                    },
                    "line_group": {
                        "all_lines": [
                            {
                                "content": "            return None",
                                "type": "context"
                            },
                            {
                                "content": "        return self.getexif()._get_merged_dict()",
                                "type": "context"
                            },
                            {
                                "content": "",
                                "type": "context"
                            },
                            {
                                "content": "    def getxmp(self):",
                                "type": "added"
                            },
                            {
                                "content": "        \"\"\"",
                                "type": "added"
                            },
                            {
                                "content": "        Returns a dictionary containing the XMP tags.",
                                "type": "added"
                            },
                            {
                                "content": "        Requires defusedxml to be installed.",
                                "type": "added"
                            },
                            {
                                "content": "",
                                "type": "added"
                            },
                            {
                                "content": "        :returns: XMP tags in a dictionary.",
                                "type": "added"
                            },
                            {
                                "content": "        \"\"\"",
                                "type": "added"
                            },
                            {
                                "content": "        return self._getxmp(self.info[\"xmp\"]) if \"xmp\" in self.info else {}",
                                "type": "added"
                            },
                            {
                                "content": "",
                                "type": "added"
                            },
                            {
                                "content": "    def seek(self, frame):",
                                "type": "context"
                            },
                            {
                                "content": "        if not self._seek_check(frame):",
                                "type": "context"
                            },
                            {
                                "content": "            return",
                                "type": "context"
                            },
                            {
                                "content": "",
                                "type": "context"
                            }
                        ]
                    },
                    "modified_entities": [
                        {
                            "file_name": "src/PIL/WebPImagePlugin.py",
                            "type": "class",
                            "name": "WebPImageFile",
                            "content": "class WebPImageFile(ImageFile.ImageFile):\n\n    format = \"WEBP\"\n    format_description = \"WebP image\"\n    __loaded = 0\n    __logical_frame = 0\n\n    def _open(self):\n        if not _webp.HAVE_WEBPANIM:\n            # Legacy mode\n            data, width, height, self.mode, icc_profile, exif = _webp.WebPDecode(\n                self.fp.read()\n            )\n            if icc_profile:\n                self.info[\"icc_profile\"] = icc_profile\n            if exif:\n                self.info[\"exif\"] = exif\n            self._size = width, height\n            self.fp = BytesIO(data)\n            self.tile = [(\"raw\", (0, 0) + self.size, 0, self.mode)]\n            self.n_frames = 1\n            self.is_animated = False\n            return\n\n        # Use the newer AnimDecoder API to parse the (possibly) animated file,\n        # and access muxed chunks like ICC/EXIF/XMP.\n        self._decoder = _webp.WebPAnimDecoder(self.fp.read())\n\n        # Get info from decoder\n        width, height, loop_count, bgcolor, frame_count, mode = self._decoder.get_info()\n        self._size = width, height\n        self.info[\"loop\"] = loop_count\n        bg_a, bg_r, bg_g, bg_b = (\n            (bgcolor >> 24) & 0xFF,\n            (bgcolor >> 16) & 0xFF,\n            (bgcolor >> 8) & 0xFF,\n            bgcolor & 0xFF,\n        )\n        self.info[\"background\"] = (bg_r, bg_g, bg_b, bg_a)\n        self.n_frames = frame_count\n        self.is_animated = self.n_frames > 1\n        self.mode = \"RGB\" if mode == \"RGBX\" else mode\n        self.rawmode = mode\n        self.tile = []\n\n        # Attempt to read ICC / EXIF / XMP chunks from file\n        icc_profile = self._decoder.get_chunk(\"ICCP\")\n        exif = self._decoder.get_chunk(\"EXIF\")\n        xmp = self._decoder.get_chunk(\"XMP \")\n        if icc_profile:\n            self.info[\"icc_profile\"] = icc_profile\n        if exif:\n            self.info[\"exif\"] = exif\n        if xmp:\n            self.info[\"xmp\"] = xmp\n\n        # Initialize seek state\n        self._reset(reset=False)\n\n    def _getexif(self):\n        if \"exif\" not in self.info:\n            return None\n        return self.getexif()._get_merged_dict()\n\n    def getxmp(self):\n        \"\"\"\n        Returns a dictionary containing the XMP tags.\n        Requires defusedxml to be installed.\n\n        :returns: XMP tags in a dictionary.\n        \"\"\"\n        return self._getxmp(self.info[\"xmp\"]) if \"xmp\" in self.info else {}\n\n    def seek(self, frame):\n        if not self._seek_check(frame):\n            return\n\n        # Set logical frame to requested position\n        self.__logical_frame = frame\n\n    def _reset(self, reset=True):\n        if reset:\n            self._decoder.reset()\n        self.__physical_frame = 0\n        self.__loaded = -1\n        self.__timestamp = 0\n\n    def _get_next(self):\n        # Get next frame\n        ret = self._decoder.get_next()\n        self.__physical_frame += 1\n\n        # Check if an error occurred\n        if ret is None:\n            self._reset()  # Reset just to be safe\n            self.seek(0)\n            raise EOFError(\"failed to decode next frame in WebP file\")\n\n        # Compute duration\n        data, timestamp = ret\n        duration = timestamp - self.__timestamp\n        self.__timestamp = timestamp\n\n        # libwebp gives frame end, adjust to start of frame\n        timestamp -= duration\n        return data, timestamp, duration\n\n    def _seek(self, frame):\n        if self.__physical_frame == frame:\n            return  # Nothing to do\n        if frame < self.__physical_frame:\n            self._reset()  # Rewind to beginning\n        while self.__physical_frame < frame:\n            self._get_next()  # Advance to the requested frame\n\n    def load(self):\n        if _webp.HAVE_WEBPANIM:\n            if self.__loaded != self.__logical_frame:\n                self._seek(self.__logical_frame)\n\n                # We need to load the image data for this frame\n                data, timestamp, duration = self._get_next()\n                self.info[\"timestamp\"] = timestamp\n                self.info[\"duration\"] = duration\n                self.__loaded = self.__logical_frame\n\n                # Set tile\n                if self.fp and self._exclusive_fp:\n                    self.fp.close()\n                self.fp = BytesIO(data)\n                self.tile = [(\"raw\", (0, 0) + self.size, 0, self.rawmode)]\n\n        return super().load()\n\n    def tell(self):\n        if not _webp.HAVE_WEBPANIM:\n            return super().tell()\n\n        return self.__logical_frame",
                            "ast_type_str": "ClassDef",
                            "start_lineno": 37,
                            "end_lineno": 175,
                            "parent": null
                        }
                    ],
                    "added_entities": [
                        {
                            "file_name": "src/PIL/WebPImagePlugin.py",
                            "type": "method",
                            "name": "WebPImageFile.getxmp",
                            "content": "class WebPImageFile(ImageFile.ImageFile):\n\n    format = \"WEBP\"\n    format_description = \"WebP image\"\n    __loaded = 0\n    __logical_frame = 0\n\n    def _open(self):\n        if not _webp.HAVE_WEBPANIM:\n            # Legacy mode\n            data, width, height, self.mode, icc_profile, exif = _webp.WebPDecode(\n                self.fp.read()\n            )\n            if icc_profile:\n                self.info[\"icc_profile\"] = icc_profile\n            if exif:\n                self.info[\"exif\"] = exif\n            self._size = width, height\n            self.fp = BytesIO(data)\n            self.tile = [(\"raw\", (0, 0) + self.size, 0, self.mode)]\n            self.n_frames = 1\n            self.is_animated = False\n            return\n\n        # Use the newer AnimDecoder API to parse the (possibly) animated file,\n        # and access muxed chunks like ICC/EXIF/XMP.\n        self._decoder = _webp.WebPAnimDecoder(self.fp.read())\n\n        # Get info from decoder\n        width, height, loop_count, bgcolor, frame_count, mode = self._decoder.get_info()\n        self._size = width, height\n        self.info[\"loop\"] = loop_count\n        bg_a, bg_r, bg_g, bg_b = (\n            (bgcolor >> 24) & 0xFF,\n            (bgcolor >> 16) & 0xFF,\n            (bgcolor >> 8) & 0xFF,\n            bgcolor & 0xFF,\n        )\n        self.info[\"background\"] = (bg_r, bg_g, bg_b, bg_a)\n        self.n_frames = frame_count\n        self.is_animated = self.n_frames > 1\n        self.mode = \"RGB\" if mode == \"RGBX\" else mode\n        self.rawmode = mode\n        self.tile = []\n\n        # Attempt to read ICC / EXIF / XMP chunks from file\n        icc_profile = self._decoder.get_chunk(\"ICCP\")\n        exif = self._decoder.get_chunk(\"EXIF\")\n        xmp = self._decoder.get_chunk(\"XMP \")\n        if icc_profile:\n            self.info[\"icc_profile\"] = icc_profile\n        if exif:\n            self.info[\"exif\"] = exif\n        if xmp:\n            self.info[\"xmp\"] = xmp\n\n        # Initialize seek state\n        self._reset(reset=False)\n\n    def _getexif(self):\n        if \"exif\" not in self.info:\n            return None\n        return self.getexif()._get_merged_dict()\n\n    def getxmp(self):\n        \"\"\"\n        Returns a dictionary containing the XMP tags.\n        Requires defusedxml to be installed.\n\n        :returns: XMP tags in a dictionary.\n        \"\"\"\n        return self._getxmp(self.info[\"xmp\"]) if \"xmp\" in self.info else {}\n\n    def seek(self, frame):\n        if not self._seek_check(frame):\n            return\n\n        # Set logical frame to requested position\n        self.__logical_frame = frame\n\n    def _reset(self, reset=True):\n        if reset:\n            self._decoder.reset()\n        self.__physical_frame = 0\n        self.__loaded = -1\n        self.__timestamp = 0\n\n    def _get_next(self):\n        # Get next frame\n        ret = self._decoder.get_next()\n        self.__physical_frame += 1\n\n        # Check if an error occurred\n        if ret is None:\n            self._reset()  # Reset just to be safe\n            self.seek(0)\n            raise EOFError(\"failed to decode next frame in WebP file\")\n\n        # Compute duration\n        data, timestamp = ret\n        duration = timestamp - self.__timestamp\n        self.__timestamp = timestamp\n\n        # libwebp gives frame end, adjust to start of frame\n        timestamp -= duration\n        return data, timestamp, duration\n\n    def _seek(self, frame):\n        if self.__physical_frame == frame:\n            return  # Nothing to do\n        if frame < self.__physical_frame:\n            self._reset()  # Rewind to beginning\n        while self.__physical_frame < frame:\n            self._get_next()  # Advance to the requested frame\n\n    def load(self):\n        if _webp.HAVE_WEBPANIM:\n            if self.__loaded != self.__logical_frame:\n                self._seek(self.__logical_frame)\n\n                # We need to load the image data for this frame\n                data, timestamp, duration = self._get_next()\n                self.info[\"timestamp\"] = timestamp\n                self.info[\"duration\"] = duration\n                self.__loaded = self.__logical_frame\n\n                # Set tile\n                if self.fp and self._exclusive_fp:\n                    self.fp.close()\n                self.fp = BytesIO(data)\n                self.tile = [(\"raw\", (0, 0) + self.size, 0, self.rawmode)]\n\n        return super().load()\n\n    def tell(self):\n        if not _webp.HAVE_WEBPANIM:\n            return super().tell()\n\n        return self.__logical_frame",
                            "ast_type_str": "ClassDef",
                            "start_lineno": 101,
                            "end_lineno": 108,
                            "parent": {
                                "file_name": "src/PIL/WebPImagePlugin.py",
                                "type": "class",
                                "name": "WebPImageFile",
                                "content": "class WebPImageFile(ImageFile.ImageFile):\n\n    format = \"WEBP\"\n    format_description = \"WebP image\"\n    __loaded = 0\n    __logical_frame = 0\n\n    def _open(self):\n        if not _webp.HAVE_WEBPANIM:\n            # Legacy mode\n            data, width, height, self.mode, icc_profile, exif = _webp.WebPDecode(\n                self.fp.read()\n            )\n            if icc_profile:\n                self.info[\"icc_profile\"] = icc_profile\n            if exif:\n                self.info[\"exif\"] = exif\n            self._size = width, height\n            self.fp = BytesIO(data)\n            self.tile = [(\"raw\", (0, 0) + self.size, 0, self.mode)]\n            self.n_frames = 1\n            self.is_animated = False\n            return\n\n        # Use the newer AnimDecoder API to parse the (possibly) animated file,\n        # and access muxed chunks like ICC/EXIF/XMP.\n        self._decoder = _webp.WebPAnimDecoder(self.fp.read())\n\n        # Get info from decoder\n        width, height, loop_count, bgcolor, frame_count, mode = self._decoder.get_info()\n        self._size = width, height\n        self.info[\"loop\"] = loop_count\n        bg_a, bg_r, bg_g, bg_b = (\n            (bgcolor >> 24) & 0xFF,\n            (bgcolor >> 16) & 0xFF,\n            (bgcolor >> 8) & 0xFF,\n            bgcolor & 0xFF,\n        )\n        self.info[\"background\"] = (bg_r, bg_g, bg_b, bg_a)\n        self.n_frames = frame_count\n        self.is_animated = self.n_frames > 1\n        self.mode = \"RGB\" if mode == \"RGBX\" else mode\n        self.rawmode = mode\n        self.tile = []\n\n        # Attempt to read ICC / EXIF / XMP chunks from file\n        icc_profile = self._decoder.get_chunk(\"ICCP\")\n        exif = self._decoder.get_chunk(\"EXIF\")\n        xmp = self._decoder.get_chunk(\"XMP \")\n        if icc_profile:\n            self.info[\"icc_profile\"] = icc_profile\n        if exif:\n            self.info[\"exif\"] = exif\n        if xmp:\n            self.info[\"xmp\"] = xmp\n\n        # Initialize seek state\n        self._reset(reset=False)\n\n    def _getexif(self):\n        if \"exif\" not in self.info:\n            return None\n        return self.getexif()._get_merged_dict()\n\n    def getxmp(self):\n        \"\"\"\n        Returns a dictionary containing the XMP tags.\n        Requires defusedxml to be installed.\n\n        :returns: XMP tags in a dictionary.\n        \"\"\"\n        return self._getxmp(self.info[\"xmp\"]) if \"xmp\" in self.info else {}\n\n    def seek(self, frame):\n        if not self._seek_check(frame):\n            return\n\n        # Set logical frame to requested position\n        self.__logical_frame = frame\n\n    def _reset(self, reset=True):\n        if reset:\n            self._decoder.reset()\n        self.__physical_frame = 0\n        self.__loaded = -1\n        self.__timestamp = 0\n\n    def _get_next(self):\n        # Get next frame\n        ret = self._decoder.get_next()\n        self.__physical_frame += 1\n\n        # Check if an error occurred\n        if ret is None:\n            self._reset()  # Reset just to be safe\n            self.seek(0)\n            raise EOFError(\"failed to decode next frame in WebP file\")\n\n        # Compute duration\n        data, timestamp = ret\n        duration = timestamp - self.__timestamp\n        self.__timestamp = timestamp\n\n        # libwebp gives frame end, adjust to start of frame\n        timestamp -= duration\n        return data, timestamp, duration\n\n    def _seek(self, frame):\n        if self.__physical_frame == frame:\n            return  # Nothing to do\n        if frame < self.__physical_frame:\n            self._reset()  # Rewind to beginning\n        while self.__physical_frame < frame:\n            self._get_next()  # Advance to the requested frame\n\n    def load(self):\n        if _webp.HAVE_WEBPANIM:\n            if self.__loaded != self.__logical_frame:\n                self._seek(self.__logical_frame)\n\n                # We need to load the image data for this frame\n                data, timestamp, duration = self._get_next()\n                self.info[\"timestamp\"] = timestamp\n                self.info[\"duration\"] = duration\n                self.__loaded = self.__logical_frame\n\n                # Set tile\n                if self.fp and self._exclusive_fp:\n                    self.fp.close()\n                self.fp = BytesIO(data)\n                self.tile = [(\"raw\", (0, 0) + self.size, 0, self.rawmode)]\n\n        return super().load()\n\n    def tell(self):\n        if not _webp.HAVE_WEBPANIM:\n            return super().tell()\n\n        return self.__logical_frame",
                                "ast_type_str": "ClassDef",
                                "start_lineno": 37,
                                "end_lineno": 175,
                                "parent": null
                            }
                        }
                    ],
                    "deleted_entities": []
                }
            ]
        }
    ],
    "old_commit_hash": "066c3ab73bff25d514d01478aa9b64799c6447ed^",
    "new_commit_hash": "066c3ab73bff25d514d01478aa9b64799c6447ed",
    "commit_message": "Merge pull request #6758 from radarhere/webp_getxmp\n\nResolves https://github.com/python-pillow/Pillow/issues/6757",
    "commit_date": "2022-11-26T11:37:46+02:00",
    "metadata": {}
}
{
    "repo_name": "pillow",
    "new_commit_hash": "066c3ab73bff25d514d01478aa9b64799c6447ed",
    "test_file_codes": [
        "from io import BytesIO\n\nimport pytest\n\nfrom PIL import Image\n\nfrom .helper import mark_if_feature_version, skip_unless_feature\n\npytestmark = [\n    skip_unless_feature(\"webp\"),\n    skip_unless_feature(\"webp_mux\"),\n]\n\ntry:\n    from defusedxml import ElementTree\nexcept ImportError:\n    ElementTree = None\n\n\ndef test_read_exif_metadata():\n\n    file_path = \"Tests/images/flower.webp\"\n    with Image.open(file_path) as image:\n\n        assert image.format == \"WEBP\"\n        exif_data = image.info.get(\"exif\", None)\n        assert exif_data\n\n        exif = image._getexif()\n\n        # Camera make\n        assert exif[271] == \"Canon\"\n\n        with Image.open(\"Tests/images/flower.jpg\") as jpeg_image:\n            expected_exif = jpeg_image.info[\"exif\"]\n\n            assert exif_data == expected_exif\n\n\ndef test_read_exif_metadata_without_prefix():\n    with Image.open(\"Tests/images/flower2.webp\") as im:\n        # Assert prefix is not present\n        assert im.info[\"exif\"][:6] != b\"Exif\\x00\\x00\"\n\n        exif = im.getexif()\n        assert exif[305] == \"Adobe Photoshop CS6 (Macintosh)\"\n\n\n@mark_if_feature_version(\n    pytest.mark.valgrind_known_error, \"libjpeg_turbo\", \"2.0\", reason=\"Known Failing\"\n)\ndef test_write_exif_metadata():\n    file_path = \"Tests/images/flower.jpg\"\n    test_buffer = BytesIO()\n    with Image.open(file_path) as image:\n        expected_exif = image.info[\"exif\"]\n\n        image.save(test_buffer, \"webp\", exif=expected_exif)\n\n    test_buffer.seek(0)\n    with Image.open(test_buffer) as webp_image:\n        webp_exif = webp_image.info.get(\"exif\", None)\n    assert webp_exif == expected_exif[6:], \"WebP EXIF didn't match\"\n\n\ndef test_read_icc_profile():\n\n    file_path = \"Tests/images/flower2.webp\"\n    with Image.open(file_path) as image:\n\n        assert image.format == \"WEBP\"\n        assert image.info.get(\"icc_profile\", None)\n\n        icc = image.info[\"icc_profile\"]\n\n        with Image.open(\"Tests/images/flower2.jpg\") as jpeg_image:\n            expected_icc = jpeg_image.info[\"icc_profile\"]\n\n            assert icc == expected_icc\n\n\n@mark_if_feature_version(\n    pytest.mark.valgrind_known_error, \"libjpeg_turbo\", \"2.0\", reason=\"Known Failing\"\n)\ndef test_write_icc_metadata():\n    file_path = \"Tests/images/flower2.jpg\"\n    test_buffer = BytesIO()\n    with Image.open(file_path) as image:\n        expected_icc_profile = image.info[\"icc_profile\"]\n\n        image.save(test_buffer, \"webp\", icc_profile=expected_icc_profile)\n\n    test_buffer.seek(0)\n    with Image.open(test_buffer) as webp_image:\n        webp_icc_profile = webp_image.info.get(\"icc_profile\", None)\n\n    assert webp_icc_profile\n    if webp_icc_profile:\n        assert webp_icc_profile == expected_icc_profile, \"Webp ICC didn't match\"\n\n\n@mark_if_feature_version(\n    pytest.mark.valgrind_known_error, \"libjpeg_turbo\", \"2.0\", reason=\"Known Failing\"\n)\ndef test_read_no_exif():\n    file_path = \"Tests/images/flower.jpg\"\n    test_buffer = BytesIO()\n    with Image.open(file_path) as image:\n        assert \"exif\" in image.info\n\n        image.save(test_buffer, \"webp\")\n\n    test_buffer.seek(0)\n    with Image.open(test_buffer) as webp_image:\n        assert not webp_image._getexif()\n\n\ndef test_getxmp():\n    with Image.open(\"Tests/images/flower.webp\") as im:\n        assert \"xmp\" not in im.info\n        assert im.getxmp() == {}\n\n    with Image.open(\"Tests/images/flower2.webp\") as im:\n        if ElementTree is None:\n            with pytest.warns(UserWarning):\n                assert im.getxmp() == {}\n        else:\n            assert (\n                im.getxmp()[\"xmpmeta\"][\"xmptk\"]\n                == \"Adobe XMP Core 5.3-c011 66.145661, 2012/02/06-14:56:27        \"\n            )\n\n\n@skip_unless_feature(\"webp_anim\")\ndef test_write_animated_metadata(tmp_path):\n    iccp_data = b\"<iccp_data>\"\n    exif_data = b\"<exif_data>\"\n    xmp_data = b\"<xmp_data>\"\n\n    temp_file = str(tmp_path / \"temp.webp\")\n    with Image.open(\"Tests/images/anim_frame1.webp\") as frame1:\n        with Image.open(\"Tests/images/anim_frame2.webp\") as frame2:\n            frame1.save(\n                temp_file,\n                save_all=True,\n                append_images=[frame2, frame1, frame2],\n                icc_profile=iccp_data,\n                exif=exif_data,\n                xmp=xmp_data,\n            )\n\n    with Image.open(temp_file) as image:\n        assert \"icc_profile\" in image.info\n        assert \"exif\" in image.info\n        assert \"xmp\" in image.info\n        assert iccp_data == image.info.get(\"icc_profile\", None)\n        assert exif_data == image.info.get(\"exif\", None)\n        assert xmp_data == image.info.get(\"xmp\", None)\n"
    ],
    "test_file_names": [
        "test_1.py"
    ],
    "setup_res_code": 0,
    "setup_res_stdout": "Starting Pillow installation attempts...\nVerifying Pillow installation...\n✅ Pillow installation successful!\nSuccessfully installed Pillow\n",
    "setup_res_stderr": "Using CPython 3.9.21\nCreating virtual environment at: .venv\nActivate with: source .venv/bin/activate\nResolved 12 packages in 15ms\nInstalled 12 packages in 126ms\n + coverage==7.6.10\n + exceptiongroup==1.2.2\n + iniconfig==2.0.0\n + packaging==24.2\n + pluggy==1.5.0\n + pyqt5==5.15.11\n + pyqt5-qt5==5.15.16\n + pyqt5-sip==12.16.1\n + pytest==8.3.4\n + pytest-cov==6.0.0\n + setuptools==75.8.0\n + tomli==2.2.1\nResolved 1 package in 809ms\nPrepared 1 package in 32.31s\nInstalled 1 package in 7ms\n + pillow==9.4.0.dev0 (from file:///home/gcpuser/buckets/local_repoeval_bucket/repos/pillow_066c3ab73bff25d514d01478aa9b64799c6447ed)\n",
    "new_commit_res_code": 0,
    "new_commit_res_stdout": "\u001b[1m============================= test session starts ==============================\u001b[0m\nplatform linux -- Python 3.9.21, pytest-8.3.4, pluggy-1.5.0\nrootdir: /home/gcpuser/buckets/local_repoeval_bucket/repos/pillow_066c3ab73bff25d514d01478aa9b64799c6447ed\nconfigfile: setup.cfg\nplugins: cov-6.0.0\ncollected 8 items\n\nr2e_tests/test_1.py \u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m                                             [100%]\u001b[0m\n\n==================================== PASSES ====================================\n\u001b[36m\u001b[1m=========================== short test summary info ============================\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_read_exif_metadata\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_read_exif_metadata_without_prefix\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_write_exif_metadata\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_read_icc_profile\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_write_icc_metadata\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_read_no_exif\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_getxmp\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_write_animated_metadata\u001b[0m\n\u001b[32m============================== \u001b[32m\u001b[1m8 passed\u001b[0m\u001b[32m in 0.26s\u001b[0m\u001b[32m ===============================\u001b[0m\n",
    "new_commit_res_stderr": "",
    "old_commit_res_code": 1,
    "old_commit_res_stdout": "\u001b[1m============================= test session starts ==============================\u001b[0m\nplatform linux -- Python 3.9.21, pytest-8.3.4, pluggy-1.5.0\nrootdir: /home/gcpuser/buckets/local_repoeval_bucket/repos/pillow_066c3ab73bff25d514d01478aa9b64799c6447ed\nconfigfile: setup.cfg\nplugins: cov-6.0.0\ncollected 8 items\n\nr2e_tests/test_1.py \u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[31mF\u001b[0m\u001b[32m.\u001b[0m\u001b[31m                                             [100%]\u001b[0m\n\n=================================== FAILURES ===================================\n\u001b[31m\u001b[1m_________________________________ test_getxmp __________________________________\u001b[0m\n\n    def test_getxmp():\n        with Image.open(\"Tests/images/flower.webp\") as im:\n            assert \"xmp\" not in im.info\n>           assert im.getxmp() == {}\n\n\u001b[1m\u001b[31mr2e_tests/test_1.py\u001b[0m:121: \n_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ \n\nself = <PIL.WebPImagePlugin.WebPImageFile image mode=RGB size=480x360 at 0x79F998FFF4C0>\nname = 'getxmp'\n\n    def __getattr__(self, name):\n        if name == \"category\":\n            deprecate(\"Image categories\", 10, \"is_animated\", plural=True)\n            return self._category\n>       raise AttributeError(name)\n\u001b[1m\u001b[31mE       AttributeError: getxmp\u001b[0m\n\n\u001b[1m\u001b[31msrc/PIL/Image.py\u001b[0m:517: AttributeError\n==================================== PASSES ====================================\n\u001b[36m\u001b[1m=========================== short test summary info ============================\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_read_exif_metadata\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_read_exif_metadata_without_prefix\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_write_exif_metadata\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_read_icc_profile\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_write_icc_metadata\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_read_no_exif\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_write_animated_metadata\u001b[0m\n\u001b[31mFAILED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_getxmp\u001b[0m - AttributeError: getxmp\n\u001b[31m========================= \u001b[31m\u001b[1m1 failed\u001b[0m, \u001b[32m7 passed\u001b[0m\u001b[31m in 0.22s\u001b[0m\u001b[31m ==========================\u001b[0m\n",
    "old_commit_res_stderr": ""
}
Tests/test_file_webp_metadata.py docs/releasenotes/9.4.0.rst src/PIL/WebPImagePlugin.py
We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
[ISSUE]
**Title:** `WebPImageFile` Missing `getxmp` Method Causes AttributeError When Accessing XMP Metadata

**Description:**
When attempting to retrieve XMP metadata from a WebP image, the `WebPImageFile` class does not have a `getxmp` method. This results in an `AttributeError` when the method is called.

**Example Code:**
```python
from PIL import Image

with Image.open("flower.webp") as im:
    xmp_data = im.getxmp()
```

**Expected Behavior:**
The `getxmp()` method should return a dictionary containing the XMP tags if they are present in the image. If no XMP data is available, it should return an empty dictionary.

**Actual Behavior:**
Calling `getxmp` on a `WebPImageFile` instance raises the following error:
```
AttributeError: 'WebPImageFile' object has no attribute 'getxmp'
```
This prevents developers from accessing XMP metadata in WebP images using the current implementation.

[/ISSUE]
--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.

--- BEGIN FILE ---
```
### src/PIL/WebPImagePlugin.py
from io import BytesIO

from . import Image, ImageFile

try:
    from . import _webp

    SUPPORTED = True
except ImportError:
    SUPPORTED = False


_VALID_WEBP_MODES = {"RGBX": True, "RGBA": True, "RGB": True}

_VALID_WEBP_LEGACY_MODES = {"RGB": True, "RGBA": True}

_VP8_MODES_BY_IDENTIFIER = {
    b"VP8 ": "RGB",
    b"VP8X": "RGBA",
    b"VP8L": "RGBA",  # lossless
}


def _accept(prefix):
    is_riff_file_format = prefix[:4] == b"RIFF"
    is_webp_file = prefix[8:12] == b"WEBP"
    is_valid_vp8_mode = prefix[12:16] in _VP8_MODES_BY_IDENTIFIER

    if is_riff_file_format and is_webp_file and is_valid_vp8_mode:
        if not SUPPORTED:
            return (
                "image file could not be identified because WEBP support not installed"
            )
        return True


class WebPImageFile(ImageFile.ImageFile):

    format = "WEBP"
    format_description = "WebP image"
    __loaded = 0
    __logical_frame = 0

    def _open(self):
        if not _webp.HAVE_WEBPANIM:
            # Legacy mode
            data, width, height, self.mode, icc_profile, exif = _webp.WebPDecode(
                self.fp.read()
            )
            if icc_profile:
                self.info["icc_profile"] = icc_profile
            if exif:
                self.info["exif"] = exif
            self._size = width, height
            self.fp = BytesIO(data)
            self.tile = [("raw", (0, 0) + self.size, 0, self.mode)]
            self.n_frames = 1
            self.is_animated = False
            return

        # Use the newer AnimDecoder API to parse the (possibly) animated file,
        # and access muxed chunks like ICC/EXIF/XMP.
        self._decoder = _webp.WebPAnimDecoder(self.fp.read())

        # Get info from decoder
        width, height, loop_count, bgcolor, frame_count, mode = self._decoder.get_info()
        self._size = width, height
        self.info["loop"] = loop_count
        bg_a, bg_r, bg_g, bg_b = (
            (bgcolor >> 24) & 0xFF,
            (bgcolor >> 16) & 0xFF,
            (bgcolor >> 8) & 0xFF,
            bgcolor & 0xFF,
        )
        self.info["background"] = (bg_r, bg_g, bg_b, bg_a)
        self.n_frames = frame_count
        self.is_animated = self.n_frames > 1
        self.mode = "RGB" if mode == "RGBX" else mode
        self.rawmode = mode
        self.tile = []

        # Attempt to read ICC / EXIF / XMP chunks from file
        icc_profile = self._decoder.get_chunk("ICCP")
        exif = self._decoder.get_chunk("EXIF")
        xmp = self._decoder.get_chunk("XMP ")
        if icc_profile:
            self.info["icc_profile"] = icc_profile
        if exif:
            self.info["exif"] = exif
        if xmp:
            self.info["xmp"] = xmp

        # Initialize seek state
        self._reset(reset=False)

    def _getexif(self):
        if "exif" not in self.info:
            return None
        return self.getexif()._get_merged_dict()

    def seek(self, frame):
        if not self._seek_check(frame):
            return

        # Set logical frame to requested position
        self.__logical_frame = frame

    def _reset(self, reset=True):
        if reset:
            self._decoder.reset()
        self.__physical_frame = 0
        self.__loaded = -1
        self.__timestamp = 0

    def _get_next(self):
        # Get next frame
        ret = self._decoder.get_next()
        self.__physical_frame += 1

        # Check if an error occurred
        if ret is None:
            self._reset()  # Reset just to be safe
            self.seek(0)
            raise EOFError("failed to decode next frame in WebP file")

        # Compute duration
        data, timestamp = ret
        duration = timestamp - self.__timestamp
        self.__timestamp = timestamp

        # libwebp gives frame end, adjust to start of frame
        timestamp -= duration
        return data, timestamp, duration

    def _seek(self, frame):
        if self.__physical_frame == frame:
            return  # Nothing to do
        if frame < self.__physical_frame:
            self._reset()  # Rewind to beginning
        while self.__physical_frame < frame:
            self._get_next()  # Advance to the requested frame

    def load(self):
        if _webp.HAVE_WEBPANIM:
            if self.__loaded != self.__logical_frame:
                self._seek(self.__logical_frame)

                # We need to load the image data for this frame
                data, timestamp, duration = self._get_next()
                self.info["timestamp"] = timestamp
                self.info["duration"] = duration
                self.__loaded = self.__logical_frame

                # Set tile
                if self.fp and self._exclusive_fp:
                    self.fp.close()
                self.fp = BytesIO(data)
                self.tile = [("raw", (0, 0) + self.size, 0, self.rawmode)]

        return super().load()

    def tell(self):
        if not _webp.HAVE_WEBPANIM:
            return super().tell()

        return self.__logical_frame


def _save_all(im, fp, filename):
    encoderinfo = im.encoderinfo.copy()
    append_images = list(encoderinfo.get("append_images", []))

    # If total frame count is 1, then save using the legacy API, which
    # will preserve non-alpha modes
    total = 0
    for ims in [im] + append_images:
        total += getattr(ims, "n_frames", 1)
    if total == 1:
        _save(im, fp, filename)
        return

    background = (0, 0, 0, 0)
    if "background" in encoderinfo:
        background = encoderinfo["background"]
    elif "background" in im.info:
        background = im.info["background"]
        if isinstance(background, int):
            # GifImagePlugin stores a global color table index in
            # info["background"]. So it must be converted to an RGBA value
            palette = im.getpalette()
            if palette:
                r, g, b = palette[background * 3 : (background + 1) * 3]
                background = (r, g, b, 255)
            else:
                background = (background, background, background, 255)

    duration = im.encoderinfo.get("duration", im.info.get("duration", 0))
    loop = im.encoderinfo.get("loop", 0)
    minimize_size = im.encoderinfo.get("minimize_size", False)
    kmin = im.encoderinfo.get("kmin", None)
    kmax = im.encoderinfo.get("kmax", None)
    allow_mixed = im.encoderinfo.get("allow_mixed", False)
    verbose = False
    lossless = im.encoderinfo.get("lossless", False)
    quality = im.encoderinfo.get("quality", 80)
    method = im.encoderinfo.get("method", 0)
    icc_profile = im.encoderinfo.get("icc_profile") or ""
    exif = im.encoderinfo.get("exif", "")
    if isinstance(exif, Image.Exif):
        exif = exif.tobytes()
    xmp = im.encoderinfo.get("xmp", "")
    if allow_mixed:
        lossless = False

    # Sensible keyframe defaults are from gif2webp.c script
    if kmin is None:
        kmin = 9 if lossless else 3
    if kmax is None:
        kmax = 17 if lossless else 5

    # Validate background color
    if (
        not isinstance(background, (list, tuple))
        or len(background) != 4
        or not all(0 <= v < 256 for v in background)
    ):
        raise OSError(
            f"Background color is not an RGBA tuple clamped to (0-255): {background}"
        )

    # Convert to packed uint
    bg_r, bg_g, bg_b, bg_a = background
    background = (bg_a << 24) | (bg_r << 16) | (bg_g << 8) | (bg_b << 0)

    # Setup the WebP animation encoder
    enc = _webp.WebPAnimEncoder(
        im.size[0],
        im.size[1],
        background,
        loop,
        minimize_size,
        kmin,
        kmax,
        allow_mixed,
        verbose,
    )

    # Add each frame
    frame_idx = 0
    timestamp = 0
    cur_idx = im.tell()
    try:
        for ims in [im] + append_images:
            # Get # of frames in this image
            nfr = getattr(ims, "n_frames", 1)

            for idx in range(nfr):
                ims.seek(idx)
                ims.load()

                # Make sure image mode is supported
                frame = ims
                rawmode = ims.mode
                if ims.mode not in _VALID_WEBP_MODES:
                    alpha = (
                        "A" in ims.mode
                        or "a" in ims.mode
                        or (ims.mode == "P" and "A" in ims.im.getpalettemode())
                    )
                    rawmode = "RGBA" if alpha else "RGB"
                    frame = ims.convert(rawmode)

                if rawmode == "RGB":
                    # For faster conversion, use RGBX
                    rawmode = "RGBX"

                # Append the frame to the animation encoder
                enc.add(
                    frame.tobytes("raw", rawmode),
                    timestamp,
                    frame.size[0],
                    frame.size[1],
                    rawmode,
                    lossless,
                    quality,
                    method,
                )

                # Update timestamp and frame index
                if isinstance(duration, (list, tuple)):
                    timestamp += duration[frame_idx]
                else:
                    timestamp += duration
                frame_idx += 1

    finally:
        im.seek(cur_idx)

    # Force encoder to flush frames
    enc.add(None, timestamp, 0, 0, "", lossless, quality, 0)

    # Get the final output from the encoder
    data = enc.assemble(icc_profile, exif, xmp)
    if data is None:
        raise OSError("cannot write file as WebP (encoder returned None)")

    fp.write(data)


def _save(im, fp, filename):
    lossless = im.encoderinfo.get("lossless", False)
    quality = im.encoderinfo.get("quality", 80)
    icc_profile = im.encoderinfo.get("icc_profile") or ""
    exif = im.encoderinfo.get("exif", b"")
    if isinstance(exif, Image.Exif):
        exif = exif.tobytes()
    if exif.startswith(b"Exif\x00\x00"):
        exif = exif[6:]
    xmp = im.encoderinfo.get("xmp", "")
    method = im.encoderinfo.get("method", 4)
    exact = 1 if im.encoderinfo.get("exact") else 0

    if im.mode not in _VALID_WEBP_LEGACY_MODES:
        alpha = (
            "A" in im.mode
            or "a" in im.mode
            or (im.mode == "P" and "transparency" in im.info)
        )
        im = im.convert("RGBA" if alpha else "RGB")

    data = _webp.WebPEncode(
        im.tobytes(),
        im.size[0],
        im.size[1],
        lossless,
        float(quality),
        im.mode,
        icc_profile,
        method,
        exact,
        exif,
        xmp,
    )
    if data is None:
        raise OSError("cannot write file as WebP (encoder returned None)")

    fp.write(data)


Image.register_open(WebPImageFile.format, WebPImageFile, _accept)
if SUPPORTED:
    Image.register_save(WebPImageFile.format, _save)
    if _webp.HAVE_WEBPANIM:
        Image.register_save_all(WebPImageFile.format, _save_all)
    Image.register_extension(WebPImageFile.format, ".webp")
    Image.register_mime(WebPImageFile.format, "image/webp")

```
--- END FILE ---

Please first localize the bug based on the issue statement, and then generate *SEARCH/REPLACE* edits to fix the issue.

Every *SEARCH/REPLACE* edit must use this format:
1. The file path
2. The start of search block: <<<<<<< SEARCH
3. A contiguous chunk of lines to search for in the existing source code
4. The dividing line: =======
5. The lines to replace into the source code
6. The end of the replace block: >>>>>>> REPLACE

Here is an example:

```python
### mathweb/flask/app.py
<<<<<<< SEARCH
from flask import Flask
=======
import math
from flask import Flask
>>>>>>> REPLACE
```
</solution>

Please note that the *SEARCH/REPLACE* edit REQUIRES PROPER INDENTATION. If you would like to add the line '        print(x)', you must fully write that out, with all those spaces before the code!
Wrap each *SEARCH/REPLACE* edit in a code block as shown in the example above. If you have multiple *SEARCH/REPLACE* edits, use a separate code block for each one.
[ISSUE]
**Title:** `WebPImageFile` Missing `getxmp` Method Causes AttributeError When Accessing XMP Metadata

**Description:**
When attempting to retrieve XMP metadata from a WebP image, the `WebPImageFile` class does not have a `getxmp` method. This results in an `AttributeError` when the method is called.

**Example Code:**
```python
from PIL import Image

with Image.open("flower.webp") as im:
    xmp_data = im.getxmp()
```

**Expected Behavior:**
The `getxmp()` method should return a dictionary containing the XMP tags if they are present in the image. If no XMP data is available, it should return an empty dictionary.

**Actual Behavior:**
Calling `getxmp` on a `WebPImageFile` instance raises the following error:
```
AttributeError: 'WebPImageFile' object has no attribute 'getxmp'
```
This prevents developers from accessing XMP metadata in WebP images using the current implementation.

[/ISSUE]
{
    "\u001b[1mtest_read_exif_metadata\u001b[0m": "PASSED",
    "\u001b[1mtest_read_exif_metadata_without_prefix\u001b[0m": "PASSED",
    "\u001b[1mtest_write_exif_metadata\u001b[0m": "PASSED",
    "\u001b[1mtest_read_icc_profile\u001b[0m": "PASSED",
    "\u001b[1mtest_write_icc_metadata\u001b[0m": "PASSED",
    "\u001b[1mtest_read_no_exif\u001b[0m": "PASSED",
    "\u001b[1mtest_getxmp\u001b[0m": "PASSED",
    "\u001b[1mtest_write_animated_metadata\u001b[0m": "PASSED"
}
diff --git a/src/PIL/WebPImagePlugin.py b/src/PIL/WebPImagePlugin.py
index e3c19db3d..81ed550d9 100644
--- a/src/PIL/WebPImagePlugin.py
+++ b/src/PIL/WebPImagePlugin.py
@@ -98,6 +98,15 @@ class WebPImageFile(ImageFile.ImageFile):
             return None
         return self.getexif()._get_merged_dict()
 
+    def getxmp(self):
+        """
+        Returns a dictionary containing the XMP tags.
+        Requires defusedxml to be installed.
+
+        :returns: XMP tags in a dictionary.
+        """
+        return self._getxmp(self.info["xmp"]) if "xmp" in self.info else {}
+
     def seek(self, frame):
         if not self._seek_check(frame):
             return
 

{"src/PIL/WebPImagePlugin.py": "from io import BytesIO\n\nfrom . import Image, ImageFile\n\ntry:\n    from . import _webp\n\n    SUPPORTED = True\nexcept ImportError:\n    SUPPORTED = False\n\n\n_VALID_WEBP_MODES = {\"RGBX\": True, \"RGBA\": True, \"RGB\": True}\n\n_VALID_WEBP_LEGACY_MODES = {\"RGB\": True, \"RGBA\": True}\n\n_VP8_MODES_BY_IDENTIFIER = {\n    b\"VP8 \": \"RGB\",\n    b\"VP8X\": \"RGBA\",\n    b\"VP8L\": \"RGBA\",  # lossless\n}\n\n\ndef _accept(prefix):\n    is_riff_file_format = prefix[:4] == b\"RIFF\"\n    is_webp_file = prefix[8:12] == b\"WEBP\"\n    is_valid_vp8_mode = prefix[12:16] in _VP8_MODES_BY_IDENTIFIER\n\n    if is_riff_file_format and is_webp_file and is_valid_vp8_mode:\n        if not SUPPORTED:\n            return (\n                \"image file could not be identified because WEBP support not installed\"\n            )\n        return True\n\n\nclass WebPImageFile(ImageFile.ImageFile):\n\n    format = \"WEBP\"\n    format_description = \"WebP image\"\n    __loaded = 0\n    __logical_frame = 0\n\n    def _open(self):\n        if not _webp.HAVE_WEBPANIM:\n            # Legacy mode\n            data, width, height, self.mode, icc_profile, exif = _webp.WebPDecode(\n                self.fp.read()\n            )\n            if icc_profile:\n                self.info[\"icc_profile\"] = icc_profile\n            if exif:\n                self.info[\"exif\"] = exif\n            self._size = width, height\n            self.fp = BytesIO(data)\n            self.tile = [(\"raw\", (0, 0) + self.size, 0, self.mode)]\n            self.n_frames = 1\n            self.is_animated = False\n            return\n\n        # Use the newer AnimDecoder API to parse the (possibly) animated file,\n        # and access muxed chunks like ICC/EXIF/XMP.\n        self._decoder = _webp.WebPAnimDecoder(self.fp.read())\n\n        # Get info from decoder\n        width, height, loop_count, bgcolor, frame_count, mode = self._decoder.get_info()\n        self._size = width, height\n        self.info[\"loop\"] = loop_count\n        bg_a, bg_r, bg_g, bg_b = (\n            (bgcolor >> 24) & 0xFF,\n            (bgcolor >> 16) & 0xFF,\n            (bgcolor >> 8) & 0xFF,\n            bgcolor & 0xFF,\n        )\n        self.info[\"background\"] = (bg_r, bg_g, bg_b, bg_a)\n        self.n_frames = frame_count\n        self.is_animated = self.n_frames > 1\n        self.mode = \"RGB\" if mode == \"RGBX\" else mode\n        self.rawmode = mode\n        self.tile = []\n\n        # Attempt to read ICC / EXIF / XMP chunks from file\n        icc_profile = self._decoder.get_chunk(\"ICCP\")\n        exif = self._decoder.get_chunk(\"EXIF\")\n        xmp = self._decoder.get_chunk(\"XMP \")\n        if icc_profile:\n            self.info[\"icc_profile\"] = icc_profile\n        if exif:\n            self.info[\"exif\"] = exif\n        if xmp:\n            self.info[\"xmp\"] = xmp\n\n        # Initialize seek state\n        self._reset(reset=False)\n\n    def _getexif(self):\n        if \"exif\" not in self.info:\n            return None\n        return self.getexif()._get_merged_dict()\n\n    def seek(self, frame):\n        if not self._seek_check(frame):\n            return\n\n        # Set logical frame to requested position\n        self.__logical_frame = frame\n\n    def _reset(self, reset=True):\n        if reset:\n            self._decoder.reset()\n        self.__physical_frame = 0\n        self.__loaded = -1\n        self.__timestamp = 0\n\n    def _get_next(self):\n        # Get next frame\n        ret = self._decoder.get_next()\n        self.__physical_frame += 1\n\n        # Check if an error occurred\n        if ret is None:\n            self._reset()  # Reset just to be safe\n            self.seek(0)\n            raise EOFError(\"failed to decode next frame in WebP file\")\n\n        # Compute duration\n        data, timestamp = ret\n        duration = timestamp - self.__timestamp\n        self.__timestamp = timestamp\n\n        # libwebp gives frame end, adjust to start of frame\n        timestamp -= duration\n        return data, timestamp, duration\n\n    def _seek(self, frame):\n        if self.__physical_frame == frame:\n            return  # Nothing to do\n        if frame < self.__physical_frame:\n            self._reset()  # Rewind to beginning\n        while self.__physical_frame < frame:\n            self._get_next()  # Advance to the requested frame\n\n    def load(self):\n        if _webp.HAVE_WEBPANIM:\n            if self.__loaded != self.__logical_frame:\n                self._seek(self.__logical_frame)\n\n                # We need to load the image data for this frame\n                data, timestamp, duration = self._get_next()\n                self.info[\"timestamp\"] = timestamp\n                self.info[\"duration\"] = duration\n                self.__loaded = self.__logical_frame\n\n                # Set tile\n                if self.fp and self._exclusive_fp:\n                    self.fp.close()\n                self.fp = BytesIO(data)\n                self.tile = [(\"raw\", (0, 0) + self.size, 0, self.rawmode)]\n\n        return super().load()\n\n    def tell(self):\n        if not _webp.HAVE_WEBPANIM:\n            return super().tell()\n\n        return self.__logical_frame\n\n\ndef _save_all(im, fp, filename):\n    encoderinfo = im.encoderinfo.copy()\n    append_images = list(encoderinfo.get(\"append_images\", []))\n\n    # If total frame count is 1, then save using the legacy API, which\n    # will preserve non-alpha modes\n    total = 0\n    for ims in [im] + append_images:\n        total += getattr(ims, \"n_frames\", 1)\n    if total == 1:\n        _save(im, fp, filename)\n        return\n\n    background = (0, 0, 0, 0)\n    if \"background\" in encoderinfo:\n        background = encoderinfo[\"background\"]\n    elif \"background\" in im.info:\n        background = im.info[\"background\"]\n        if isinstance(background, int):\n            # GifImagePlugin stores a global color table index in\n            # info[\"background\"]. So it must be converted to an RGBA value\n            palette = im.getpalette()\n            if palette:\n                r, g, b = palette[background * 3 : (background + 1) * 3]\n                background = (r, g, b, 255)\n            else:\n                background = (background, background, background, 255)\n\n    duration = im.encoderinfo.get(\"duration\", im.info.get(\"duration\", 0))\n    loop = im.encoderinfo.get(\"loop\", 0)\n    minimize_size = im.encoderinfo.get(\"minimize_size\", False)\n    kmin = im.encoderinfo.get(\"kmin\", None)\n    kmax = im.encoderinfo.get(\"kmax\", None)\n    allow_mixed = im.encoderinfo.get(\"allow_mixed\", False)\n    verbose = False\n    lossless = im.encoderinfo.get(\"lossless\", False)\n    quality = im.encoderinfo.get(\"quality\", 80)\n    method = im.encoderinfo.get(\"method\", 0)\n    icc_profile = im.encoderinfo.get(\"icc_profile\") or \"\"\n    exif = im.encoderinfo.get(\"exif\", \"\")\n    if isinstance(exif, Image.Exif):\n        exif = exif.tobytes()\n    xmp = im.encoderinfo.get(\"xmp\", \"\")\n    if allow_mixed:\n        lossless = False\n\n    # Sensible keyframe defaults are from gif2webp.c script\n    if kmin is None:\n        kmin = 9 if lossless else 3\n    if kmax is None:\n        kmax = 17 if lossless else 5\n\n    # Validate background color\n    if (\n        not isinstance(background, (list, tuple))\n        or len(background) != 4\n        or not all(0 <= v < 256 for v in background)\n    ):\n        raise OSError(\n            f\"Background color is not an RGBA tuple clamped to (0-255): {background}\"\n        )\n\n    # Convert to packed uint\n    bg_r, bg_g, bg_b, bg_a = background\n    background = (bg_a << 24) | (bg_r << 16) | (bg_g << 8) | (bg_b << 0)\n\n    # Setup the WebP animation encoder\n    enc = _webp.WebPAnimEncoder(\n        im.size[0],\n        im.size[1],\n        background,\n        loop,\n        minimize_size,\n        kmin,\n        kmax,\n        allow_mixed,\n        verbose,\n    )\n\n    # Add each frame\n    frame_idx = 0\n    timestamp = 0\n    cur_idx = im.tell()\n    try:\n        for ims in [im] + append_images:\n            # Get # of frames in this image\n            nfr = getattr(ims, \"n_frames\", 1)\n\n            for idx in range(nfr):\n                ims.seek(idx)\n                ims.load()\n\n                # Make sure image mode is supported\n                frame = ims\n                rawmode = ims.mode\n                if ims.mode not in _VALID_WEBP_MODES:\n                    alpha = (\n                        \"A\" in ims.mode\n                        or \"a\" in ims.mode\n                        or (ims.mode == \"P\" and \"A\" in ims.im.getpalettemode())\n                    )\n                    rawmode = \"RGBA\" if alpha else \"RGB\"\n                    frame = ims.convert(rawmode)\n\n                if rawmode == \"RGB\":\n                    # For faster conversion, use RGBX\n                    rawmode = \"RGBX\"\n\n                # Append the frame to the animation encoder\n                enc.add(\n                    frame.tobytes(\"raw\", rawmode),\n                    timestamp,\n                    frame.size[0],\n                    frame.size[1],\n                    rawmode,\n                    lossless,\n                    quality,\n                    method,\n                )\n\n                # Update timestamp and frame index\n                if isinstance(duration, (list, tuple)):\n                    timestamp += duration[frame_idx]\n                else:\n                    timestamp += duration\n                frame_idx += 1\n\n    finally:\n        im.seek(cur_idx)\n\n    # Force encoder to flush frames\n    enc.add(None, timestamp, 0, 0, \"\", lossless, quality, 0)\n\n    # Get the final output from the encoder\n    data = enc.assemble(icc_profile, exif, xmp)\n    if data is None:\n        raise OSError(\"cannot write file as WebP (encoder returned None)\")\n\n    fp.write(data)\n\n\ndef _save(im, fp, filename):\n    lossless = im.encoderinfo.get(\"lossless\", False)\n    quality = im.encoderinfo.get(\"quality\", 80)\n    icc_profile = im.encoderinfo.get(\"icc_profile\") or \"\"\n    exif = im.encoderinfo.get(\"exif\", b\"\")\n    if isinstance(exif, Image.Exif):\n        exif = exif.tobytes()\n    if exif.startswith(b\"Exif\\x00\\x00\"):\n        exif = exif[6:]\n    xmp = im.encoderinfo.get(\"xmp\", \"\")\n    method = im.encoderinfo.get(\"method\", 4)\n    exact = 1 if im.encoderinfo.get(\"exact\") else 0\n\n    if im.mode not in _VALID_WEBP_LEGACY_MODES:\n        alpha = (\n            \"A\" in im.mode\n            or \"a\" in im.mode\n            or (im.mode == \"P\" and \"transparency\" in im.info)\n        )\n        im = im.convert(\"RGBA\" if alpha else \"RGB\")\n\n    data = _webp.WebPEncode(\n        im.tobytes(),\n        im.size[0],\n        im.size[1],\n        lossless,\n        float(quality),\n        im.mode,\n        icc_profile,\n        method,\n        exact,\n        exif,\n        xmp,\n    )\n    if data is None:\n        raise OSError(\"cannot write file as WebP (encoder returned None)\")\n\n    fp.write(data)\n\n\nImage.register_open(WebPImageFile.format, WebPImageFile, _accept)\nif SUPPORTED:\n    Image.register_save(WebPImageFile.format, _save)\n    if _webp.HAVE_WEBPANIM:\n        Image.register_save_all(WebPImageFile.format, _save_all)\n    Image.register_extension(WebPImageFile.format, \".webp\")\n    Image.register_mime(WebPImageFile.format, \"image/webp\")\n"}
A user will ask you to solve a task. You should first draft your thinking process (inner monologue). Then, generate the solution.

Your response format must follow the template below:
<think>
Your thoughts or/and draft, like working through an exercise on scratch paper. Be as casual and as long as you want until you are confident to generate a correct solution.
</think>
<solution>
Final solution presented to the user.
</solution>
system
We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
[ISSUE]
**Title:** `WebPImageFile` Missing `getxmp` Method Causes AttributeError When Accessing XMP Metadata

**Description:**
When attempting to retrieve XMP metadata from a WebP image, the `WebPImageFile` class does not have a `getxmp` method. This results in an `AttributeError` when the method is called.

**Example Code:**
```python
from PIL import Image

with Image.open("flower.webp") as im:
    xmp_data = im.getxmp()
```

**Expected Behavior:**
The `getxmp()` method should return a dictionary containing the XMP tags if they are present in the image. If no XMP data is available, it should return an empty dictionary.

**Actual Behavior:**
Calling `getxmp` on a `WebPImageFile` instance raises the following error:
```
AttributeError: 'WebPImageFile' object has no attribute 'getxmp'
```
This prevents developers from accessing XMP metadata in WebP images using the current implementation.

[/ISSUE]
--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.

--- BEGIN FILE ---
```
### src/PIL/WebPImagePlugin.py
from io import BytesIO

from . import Image, ImageFile

try:
    from . import _webp

    SUPPORTED = True
except ImportError:
    SUPPORTED = False


_VALID_WEBP_MODES = {"RGBX": True, "RGBA": True, "RGB": True}

_VALID_WEBP_LEGACY_MODES = {"RGB": True, "RGBA": True}

_VP8_MODES_BY_IDENTIFIER = {
    b"VP8 ": "RGB",
    b"VP8X": "RGBA",
    b"VP8L": "RGBA",  # lossless
}


def _accept(prefix):
    is_riff_file_format = prefix[:4] == b"RIFF"
    is_webp_file = prefix[8:12] == b"WEBP"
    is_valid_vp8_mode = prefix[12:16] in _VP8_MODES_BY_IDENTIFIER

    if is_riff_file_format and is_webp_file and is_valid_vp8_mode:
        if not SUPPORTED:
            return (
                "image file could not be identified because WEBP support not installed"
            )
        return True


class WebPImageFile(ImageFile.ImageFile):

    format = "WEBP"
    format_description = "WebP image"
    __loaded = 0
    __logical_frame = 0

    def _open(self):
        if not _webp.HAVE_WEBPANIM:
            # Legacy mode
            data, width, height, self.mode, icc_profile, exif = _webp.WebPDecode(
                self.fp.read()
            )
            if icc_profile:
                self.info["icc_profile"] = icc_profile
            if exif:
                self.info["exif"] = exif
            self._size = width, height
            self.fp = BytesIO(data)
            self.tile = [("raw", (0, 0) + self.size, 0, self.mode)]
            self.n_frames = 1
            self.is_animated = False
            return

        # Use the newer AnimDecoder API to parse the (possibly) animated file,
        # and access muxed chunks like ICC/EXIF/XMP.
        self._decoder = _webp.WebPAnimDecoder(self.fp.read())

        # Get info from decoder
        width, height, loop_count, bgcolor, frame_count, mode = self._decoder.get_info()
        self._size = width, height
        self.info["loop"] = loop_count
        bg_a, bg_r, bg_g, bg_b = (
            (bgcolor >> 24) & 0xFF,
            (bgcolor >> 16) & 0xFF,
            (bgcolor >> 8) & 0xFF,
            bgcolor & 0xFF,
        )
        self.info["background"] = (bg_r, bg_g, bg_b, bg_a)
        self.n_frames = frame_count
        self.is_animated = self.n_frames > 1
        self.mode = "RGB" if mode == "RGBX" else mode
        self.rawmode = mode
        self.tile = []

        # Attempt to read ICC / EXIF / XMP chunks from file
        icc_profile = self._decoder.get_chunk("ICCP")
        exif = self._decoder.get_chunk("EXIF")
        xmp = self._decoder.get_chunk("XMP ")
        if icc_profile:
            self.info["icc_profile"] = icc_profile
        if exif:
            self.info["exif"] = exif
        if xmp:
            self.info["xmp"] = xmp

        # Initialize seek state
        self._reset(reset=False)

    def _getexif(self):
        if "exif" not in self.info:
            return None
        return self.getexif()._get_merged_dict()

    def seek(self, frame):
        if not self._seek_check(frame):
            return

        # Set logical frame to requested position
        self.__logical_frame = frame

    def _reset(self, reset=True):
        if reset:
            self._decoder.reset()
        self.__physical_frame = 0
        self.__loaded = -1
        self.__timestamp = 0

    def _get_next(self):
        # Get next frame
        ret = self._decoder.get_next()
        self.__physical_frame += 1

        # Check if an error occurred
        if ret is None:
            self._reset()  # Reset just to be safe
            self.seek(0)
            raise EOFError("failed to decode next frame in WebP file")

        # Compute duration
        data, timestamp = ret
        duration = timestamp - self.__timestamp
        self.__timestamp = timestamp

        # libwebp gives frame end, adjust to start of frame
        timestamp -= duration
        return data, timestamp, duration

    def _seek(self, frame):
        if self.__physical_frame == frame:
            return  # Nothing to do
        if frame < self.__physical_frame:
            self._reset()  # Rewind to beginning
        while self.__physical_frame < frame:
            self._get_next()  # Advance to the requested frame

    def load(self):
        if _webp.HAVE_WEBPANIM:
            if self.__loaded != self.__logical_frame:
                self._seek(self.__logical_frame)

                # We need to load the image data for this frame
                data, timestamp, duration = self._get_next()
                self.info["timestamp"] = timestamp
                self.info["duration"] = duration
                self.__loaded = self.__logical_frame

                # Set tile
                if self.fp and self._exclusive_fp:
                    self.fp.close()
                self.fp = BytesIO(data)
                self.tile = [("raw", (0, 0) + self.size, 0, self.rawmode)]

        return super().load()

    def tell(self):
        if not _webp.HAVE_WEBPANIM:
            return super().tell()

        return self.__logical_frame


def _save_all(im, fp, filename):
    encoderinfo = im.encoderinfo.copy()
    append_images = list(encoderinfo.get("append_images", []))

    # If total frame count is 1, then save using the legacy API, which
    # will preserve non-alpha modes
    total = 0
    for ims in [im] + append_images:
        total += getattr(ims, "n_frames", 1)
    if total == 1:
        _save(im, fp, filename)
        return

    background = (0, 0, 0, 0)
    if "background" in encoderinfo:
        background = encoderinfo["background"]
    elif "background" in im.info:
        background = im.info["background"]
        if isinstance(background, int):
            # GifImagePlugin stores a global color table index in
            # info["background"]. So it must be converted to an RGBA value
            palette = im.getpalette()
            if palette:
                r, g, b = palette[background * 3 : (background + 1) * 3]
                background = (r, g, b, 255)
            else:
                background = (background, background, background, 255)

    duration = im.encoderinfo.get("duration", im.info.get("duration", 0))
    loop = im.encoderinfo.get("loop", 0)
    minimize_size = im.encoderinfo.get("minimize_size", False)
    kmin = im.encoderinfo.get("kmin", None)
    kmax = im.encoderinfo.get("kmax", None)
    allow_mixed = im.encoderinfo.get("allow_mixed", False)
    verbose = False
    lossless = im.encoderinfo.get("lossless", False)
    quality = im.encoderinfo.get("quality", 80)
    method = im.encoderinfo.get("method", 0)
    icc_profile = im.encoderinfo.get("icc_profile") or ""
    exif = im.encoderinfo.get("exif", "")
    if isinstance(exif, Image.Exif):
        exif = exif.tobytes()
    xmp = im.encoderinfo.get("xmp", "")
    if allow_mixed:
        lossless = False

    # Sensible keyframe defaults are from gif2webp.c script
    if kmin is None:
        kmin = 9 if lossless else 3
    if kmax is None:
        kmax = 17 if lossless else 5

    # Validate background color
    if (
        not isinstance(background, (list, tuple))
        or len(background) != 4
        or not all(0 <= v < 256 for v in background)
    ):
        raise OSError(
            f"Background color is not an RGBA tuple clamped to (0-255): {background}"
        )

    # Convert to packed uint
    bg_r, bg_g, bg_b, bg_a = background
    background = (bg_a << 24) | (bg_r << 16) | (bg_g << 8) | (bg_b << 0)

    # Setup the WebP animation encoder
    enc = _webp.WebPAnimEncoder(
        im.size[0],
        im.size[1],
        background,
        loop,
        minimize_size,
        kmin,
        kmax,
        allow_mixed,
        verbose,
    )

    # Add each frame
    frame_idx = 0
    timestamp = 0
    cur_idx = im.tell()
    try:
        for ims in [im] + append_images:
            # Get # of frames in this image
            nfr = getattr(ims, "n_frames", 1)

            for idx in range(nfr):
                ims.seek(idx)
                ims.load()

                # Make sure image mode is supported
                frame = ims
                rawmode = ims.mode
                if ims.mode not in _VALID_WEBP_MODES:
                    alpha = (
                        "A" in ims.mode
                        or "a" in ims.mode
                        or (ims.mode == "P" and "A" in ims.im.getpalettemode())
                    )
                    rawmode = "RGBA" if alpha else "RGB"
                    frame = ims.convert(rawmode)

                if rawmode == "RGB":
                    # For faster conversion, use RGBX
                    rawmode = "RGBX"

                # Append the frame to the animation encoder
                enc.add(
                    frame.tobytes("raw", rawmode),
                    timestamp,
                    frame.size[0],
                    frame.size[1],
                    rawmode,
                    lossless,
                    quality,
                    method,
                )

                # Update timestamp and frame index
                if isinstance(duration, (list, tuple)):
                    timestamp += duration[frame_idx]
                else:
                    timestamp += duration
                frame_idx += 1

    finally:
        im.seek(cur_idx)

    # Force encoder to flush frames
    enc.add(None, timestamp, 0, 0, "", lossless, quality, 0)

    # Get the final output from the encoder
    data = enc.assemble(icc_profile, exif, xmp)
    if data is None:
        raise OSError("cannot write file as WebP (encoder returned None)")

    fp.write(data)


def _save(im, fp, filename):
    lossless = im.encoderinfo.get("lossless", False)
    quality = im.encoderinfo.get("quality", 80)
    icc_profile = im.encoderinfo.get("icc_profile") or ""
    exif = im.encoderinfo.get("exif", b"")
    if isinstance(exif, Image.Exif):
        exif = exif.tobytes()
    if exif.startswith(b"Exif\x00\x00"):
        exif = exif[6:]
    xmp = im.encoderinfo.get("xmp", "")
    method = im.encoderinfo.get("method", 4)
    exact = 1 if im.encoderinfo.get("exact") else 0

    if im.mode not in _VALID_WEBP_LEGACY_MODES:
        alpha = (
            "A" in im.mode
            or "a" in im.mode
            or (im.mode == "P" and "transparency" in im.info)
        )
        im = im.convert("RGBA" if alpha else "RGB")

    data = _webp.WebPEncode(
        im.tobytes(),
        im.size[0],
        im.size[1],
        lossless,
        float(quality),
        im.mode,
        icc_profile,
        method,
        exact,
        exif,
        xmp,
    )
    if data is None:
        raise OSError("cannot write file as WebP (encoder returned None)")

    fp.write(data)


Image.register_open(WebPImageFile.format, WebPImageFile, _accept)
if SUPPORTED:
    Image.register_save(WebPImageFile.format, _save)
    if _webp.HAVE_WEBPANIM:
        Image.register_save_all(WebPImageFile.format, _save_all)
    Image.register_extension(WebPImageFile.format, ".webp")
    Image.register_mime(WebPImageFile.format, "image/webp")

```
--- END FILE ---

Please first localize the bug based on the issue statement, and then generate *SEARCH/REPLACE* edits to fix the issue.

Every *SEARCH/REPLACE* edit must use this format:
1. The file path
2. The start of search block: <<<<<<< SEARCH
3. A contiguous chunk of lines to search for in the existing source code
4. The dividing line: =======
5. The lines to replace into the source code
6. The end of the replace block: >>>>>>> REPLACE

Here is an example:

```python
### mathweb/flask/app.py
<<<<<<< SEARCH
from flask import Flask
=======
import math
from flask import Flask
>>>>>>> REPLACE
```
</solution>

Please note that the *SEARCH/REPLACE* edit REQUIRES PROPER INDENTATION. If you would like to add the line '        print(x)', you must fully write that out, with all those spaces before the code!
Wrap each *SEARCH/REPLACE* edit in a code block as shown in the example above. If you have multiple *SEARCH/REPLACE* edits, use a separate code block for each one.
user