{
    "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\n    if webp_exif:\n        assert webp_exif == expected_exif, \"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\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",
            "header": {
                "file": {
                    "path": "Tests/test_file_webp_metadata.py"
                },
                "misc_line": null
            },
            "index_line": {
                "old_commit_hash": "e6d6fc63f",
                "new_commit_hash": "f77a245c0",
                "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": 55,
                            "length": 9
                        },
                        "new_range": {
                            "start": 55,
                            "length": 7
                        },
                        "section": "def test_write_exif_metadata():"
                    },
                    "line_group": {
                        "all_lines": [
                            {
                                "content": "    test_buffer.seek(0)",
                                "type": "context"
                            },
                            {
                                "content": "    with Image.open(test_buffer) as webp_image:",
                                "type": "context"
                            },
                            {
                                "content": "        webp_exif = webp_image.info.get(\"exif\", None)",
                                "type": "context"
                            },
                            {
                                "content": "    assert webp_exif",
                                "type": "deleted"
                            },
                            {
                                "content": "    if webp_exif:",
                                "type": "deleted"
                            },
                            {
                                "content": "        assert webp_exif == expected_exif, \"WebP EXIF didn't match\"",
                                "type": "deleted"
                            },
                            {
                                "content": "    assert webp_exif == expected_exif[6:], \"WebP EXIF didn't match\"",
                                "type": "added"
                            },
                            {
                                "content": "",
                                "type": "context"
                            },
                            {
                                "content": "",
                                "type": "context"
                            },
                            {
                                "content": "def test_read_icc_profile():",
                                "type": "context"
                            }
                        ]
                    },
                    "modified_entities": [
                        {
                            "file_name": "Tests/test_file_webp_metadata.py",
                            "type": "function",
                            "name": "test_write_exif_metadata",
                            "content": "def 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\n    if webp_exif:\n        assert webp_exif == expected_exif, \"WebP EXIF didn't match\"",
                            "ast_type_str": "FunctionDef",
                            "start_lineno": 47,
                            "end_lineno": 60,
                            "parent": null
                        }
                    ],
                    "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\", \"\")\n    if isinstance(exif, Image.Exif):\n        exif = exif.tobytes()\n    xmp = im.encoderinfo.get(\"xmp\", \"\")\n    method = im.encoderinfo.get(\"method\", 4)\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        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 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\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        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": "c1f4b730f",
                "new_commit_hash": "5eaeb10cc",
                "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": 311,
                            "length": 9
                        },
                        "new_range": {
                            "start": 311,
                            "length": 11
                        },
                        "section": "def _save(im, fp, filename):"
                    },
                    "line_group": {
                        "all_lines": [
                            {
                                "content": "    lossless = im.encoderinfo.get(\"lossless\", False)",
                                "type": "context"
                            },
                            {
                                "content": "    quality = im.encoderinfo.get(\"quality\", 80)",
                                "type": "context"
                            },
                            {
                                "content": "    icc_profile = im.encoderinfo.get(\"icc_profile\") or \"\"",
                                "type": "context"
                            },
                            {
                                "content": "    exif = im.encoderinfo.get(\"exif\", \"\")",
                                "type": "deleted"
                            },
                            {
                                "content": "    exif = im.encoderinfo.get(\"exif\", b\"\")",
                                "type": "added"
                            },
                            {
                                "content": "    if isinstance(exif, Image.Exif):",
                                "type": "context"
                            },
                            {
                                "content": "        exif = exif.tobytes()",
                                "type": "context"
                            },
                            {
                                "content": "    if exif.startswith(b\"Exif\\x00\\x00\"):",
                                "type": "added"
                            },
                            {
                                "content": "        exif = exif[6:]",
                                "type": "added"
                            },
                            {
                                "content": "    xmp = im.encoderinfo.get(\"xmp\", \"\")",
                                "type": "context"
                            },
                            {
                                "content": "    method = im.encoderinfo.get(\"method\", 4)",
                                "type": "context"
                            },
                            {
                                "content": "",
                                "type": "context"
                            },
                            {
                                "content": "",
                                "type": "context"
                            }
                        ]
                    },
                    "modified_entities": [
                        {
                            "file_name": "src/PIL/WebPImagePlugin.py",
                            "type": "function",
                            "name": "_save",
                            "content": "def _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\", \"\")\n    if isinstance(exif, Image.Exif):\n        exif = exif.tobytes()\n    xmp = im.encoderinfo.get(\"xmp\", \"\")\n    method = im.encoderinfo.get(\"method\", 4)\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        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)",
                            "ast_type_str": "FunctionDef",
                            "start_lineno": 310,
                            "end_lineno": 343,
                            "parent": null
                        }
                    ],
                    "added_entities": [],
                    "deleted_entities": []
                }
            ]
        }
    ],
    "old_commit_hash": "4995d045dc94e57c1527576d725b6a12fd878a2e^",
    "new_commit_hash": "4995d045dc94e57c1527576d725b6a12fd878a2e",
    "commit_message": "Merge pull request #6582 from radarhere/webp_exif\n\nRemoved EXIF prefix when saving WebP",
    "commit_date": "2022-10-11T16:48:28+02:00",
    "metadata": {}
}
{
    "repo_name": "pillow",
    "new_commit_hash": "4995d045dc94e57c1527576d725b6a12fd878a2e",
    "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\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"
    ],
    "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 12ms\nInstalled 12 packages in 231ms\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 941ms\nPrepared 1 package in 33.57s\nInstalled 1 package in 1ms\n + pillow==9.3.0.dev0 (from file:///home/gcpuser/buckets/local_repoeval_bucket/repos/pillow_4995d045dc94e57c1527576d725b6a12fd878a2e)\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_4995d045dc94e57c1527576d725b6a12fd878a2e\nconfigfile: setup.cfg\nplugins: cov-6.0.0\ncollected 7 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                                              [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_write_animated_metadata\u001b[0m\n\u001b[32m============================== \u001b[32m\u001b[1m7 passed\u001b[0m\u001b[32m in 0.23s\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_4995d045dc94e57c1527576d725b6a12fd878a2e\nconfigfile: setup.cfg\nplugins: cov-6.0.0\ncollected 7 items\n\nr2e_tests/test_1.py \u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[31mF\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[31m                                              [100%]\u001b[0m\n\n=================================== FAILURES ===================================\n\u001b[31m\u001b[1m___________________________ test_write_exif_metadata ___________________________\u001b[0m\n\n    @mark_if_feature_version(\n        pytest.mark.valgrind_known_error, \"libjpeg_turbo\", \"2.0\", reason=\"Known Failing\"\n    )\n    def 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\u001b[1m\u001b[31mE       AssertionError: WebP EXIF didn't match\u001b[0m\n\u001b[1m\u001b[31mE       assert b'Exif\\x00\\x0...c\\xba\\xff\\xd8' == b'II*\\x00\\x08...c\\xba\\xff\\xd8'\u001b[0m\n\u001b[1m\u001b[31mE         \u001b[0m\n\u001b[1m\u001b[31mE         At index 0 diff: b'E' != b'I'\u001b[0m\n\u001b[1m\u001b[31mE         Use -v to get more diff\u001b[0m\n\n\u001b[1m\u001b[31mr2e_tests/test_1.py\u001b[0m:58: AssertionError\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_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_write_exif_metadata\u001b[0m - AssertionError: WebP EXIF didn't match\n\u001b[31m========================= \u001b[31m\u001b[1m1 failed\u001b[0m, \u001b[32m6 passed\u001b[0m\u001b[31m in 0.15s\u001b[0m\u001b[31m ==========================\u001b[0m\n",
    "old_commit_res_stderr": ""
}
Tests/test_file_webp_metadata.py src/PIL/WebPImagePlugin.py
We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
[ISSUE]
**Title:** WebP Images Include EXIF Prefix Causing Metadata Mismatch

**Description:**
When saving images in WebP format, the EXIF metadata retains an "Exif" prefix. This leads to discrepancies when comparing the EXIF data of the saved WebP image with the original image's EXIF data.

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

def save_webp_image(input_path, output_path):
    with Image.open(input_path) as img:
        exif_data = img.info.get("exif", b"")
        img.save(output_path, format="WEBP", exif=exif_data)

# After saving, the EXIF data in 'output.webp' starts with b'Exif\x00\x00',
# which causes mismatches when compared to the original EXIF data.
```

**Expected Behavior:**
Saving an image as WebP should preserve the original EXIF metadata without adding any additional prefixes. The EXIF data of the saved WebP image should exactly match the original image's EXIF data.

**Actual Behavior:**
The EXIF metadata in the saved WebP image includes an "Exif" prefix (`b'Exif\x00\x00'`), resulting in a mismatch when compared to the original EXIF data.

[/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", "")
    if isinstance(exif, Image.Exif):
        exif = exif.tobytes()
    xmp = im.encoderinfo.get("xmp", "")
    method = im.encoderinfo.get("method", 4)

    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,
        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:** WebP Images Include EXIF Prefix Causing Metadata Mismatch

**Description:**
When saving images in WebP format, the EXIF metadata retains an "Exif" prefix. This leads to discrepancies when comparing the EXIF data of the saved WebP image with the original image's EXIF data.

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

def save_webp_image(input_path, output_path):
    with Image.open(input_path) as img:
        exif_data = img.info.get("exif", b"")
        img.save(output_path, format="WEBP", exif=exif_data)

# After saving, the EXIF data in 'output.webp' starts with b'Exif\x00\x00',
# which causes mismatches when compared to the original EXIF data.
```

**Expected Behavior:**
Saving an image as WebP should preserve the original EXIF metadata without adding any additional prefixes. The EXIF data of the saved WebP image should exactly match the original image's EXIF data.

**Actual Behavior:**
The EXIF metadata in the saved WebP image includes an "Exif" prefix (`b'Exif\x00\x00'`), resulting in a mismatch when compared to the original EXIF data.

[/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_write_animated_metadata\u001b[0m": "PASSED"
}
diff --git a/src/PIL/WebPImagePlugin.py b/src/PIL/WebPImagePlugin.py
index c1f4b730f..5eaeb10cc 100644
--- a/src/PIL/WebPImagePlugin.py
+++ b/src/PIL/WebPImagePlugin.py
@@ -311,9 +311,11 @@ 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", "")
+    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)
 
 

{"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\", \"\")\n    if isinstance(exif, Image.Exif):\n        exif = exif.tobytes()\n    xmp = im.encoderinfo.get(\"xmp\", \"\")\n    method = im.encoderinfo.get(\"method\", 4)\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        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:** WebP Images Include EXIF Prefix Causing Metadata Mismatch

**Description:**
When saving images in WebP format, the EXIF metadata retains an "Exif" prefix. This leads to discrepancies when comparing the EXIF data of the saved WebP image with the original image's EXIF data.

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

def save_webp_image(input_path, output_path):
    with Image.open(input_path) as img:
        exif_data = img.info.get("exif", b"")
        img.save(output_path, format="WEBP", exif=exif_data)

# After saving, the EXIF data in 'output.webp' starts with b'Exif\x00\x00',
# which causes mismatches when compared to the original EXIF data.
```

**Expected Behavior:**
Saving an image as WebP should preserve the original EXIF metadata without adding any additional prefixes. The EXIF data of the saved WebP image should exactly match the original image's EXIF data.

**Actual Behavior:**
The EXIF metadata in the saved WebP image includes an "Exif" prefix (`b'Exif\x00\x00'`), resulting in a mismatch when compared to the original EXIF data.

[/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", "")
    if isinstance(exif, Image.Exif):
        exif = exif.tobytes()
    xmp = im.encoderinfo.get("xmp", "")
    method = im.encoderinfo.get("method", 4)

    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,
        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