{
    "file_diffs": [
        {
            "old_file_content": "import pytest\nfrom packaging.version import parse as parse_version\n\nfrom PIL import Image, features\n\nfrom .helper import (\n    assert_image_equal,\n    assert_image_similar,\n    is_big_endian,\n    skip_unless_feature,\n)\n\npytestmark = [\n    skip_unless_feature(\"webp\"),\n    skip_unless_feature(\"webp_anim\"),\n]\n\n\ndef test_n_frames():\n    \"\"\"Ensure that WebP format sets n_frames and is_animated attributes correctly.\"\"\"\n\n    with Image.open(\"Tests/images/hopper.webp\") as im:\n        assert im.n_frames == 1\n        assert not im.is_animated\n\n    with Image.open(\"Tests/images/iss634.webp\") as im:\n        assert im.n_frames == 42\n        assert im.is_animated\n\n\ndef test_write_animation_L(tmp_path):\n    \"\"\"\n    Convert an animated GIF to animated WebP, then compare the frame count, and first\n    and last frames to ensure they're visually similar.\n    \"\"\"\n\n    with Image.open(\"Tests/images/iss634.gif\") as orig:\n        assert orig.n_frames > 1\n\n        temp_file = str(tmp_path / \"temp.webp\")\n        orig.save(temp_file, save_all=True)\n        with Image.open(temp_file) as im:\n            assert im.n_frames == orig.n_frames\n\n            # Compare first and last frames to the original animated GIF\n            orig.load()\n            im.load()\n            assert_image_similar(im, orig.convert(\"RGBA\"), 32.9)\n\n            if is_big_endian():\n                webp = parse_version(features.version_module(\"webp\"))\n                if webp < parse_version(\"1.2.2\"):\n                    pytest.skip(\"Fails with libwebp earlier than 1.2.2\")\n            orig.seek(orig.n_frames - 1)\n            im.seek(im.n_frames - 1)\n            orig.load()\n            im.load()\n            assert_image_similar(im, orig.convert(\"RGBA\"), 32.9)\n\n\ndef test_write_animation_RGB(tmp_path):\n    \"\"\"\n    Write an animated WebP from RGB frames, and ensure the frames\n    are visually similar to the originals.\n    \"\"\"\n\n    def check(temp_file):\n        with Image.open(temp_file) as im:\n            assert im.n_frames == 2\n\n            # Compare first frame to original\n            im.load()\n            assert_image_equal(im, frame1.convert(\"RGBA\"))\n\n            # Compare second frame to original\n            if is_big_endian():\n                webp = parse_version(features.version_module(\"webp\"))\n                if webp < parse_version(\"1.2.2\"):\n                    pytest.skip(\"Fails with libwebp earlier than 1.2.2\")\n            im.seek(1)\n            im.load()\n            assert_image_equal(im, frame2.convert(\"RGBA\"))\n\n    with Image.open(\"Tests/images/anim_frame1.webp\") as frame1:\n        with Image.open(\"Tests/images/anim_frame2.webp\") as frame2:\n            temp_file1 = str(tmp_path / \"temp.webp\")\n            frame1.copy().save(\n                temp_file1, save_all=True, append_images=[frame2], lossless=True\n            )\n            check(temp_file1)\n\n            # Tests appending using a generator\n            def im_generator(ims):\n                yield from ims\n\n            temp_file2 = str(tmp_path / \"temp_generator.webp\")\n            frame1.copy().save(\n                temp_file2,\n                save_all=True,\n                append_images=im_generator([frame2]),\n                lossless=True,\n            )\n            check(temp_file2)\n\n\ndef test_timestamp_and_duration(tmp_path):\n    \"\"\"\n    Try passing a list of durations, and make sure the encoded\n    timestamps and durations are correct.\n    \"\"\"\n\n    durations = [0, 10, 20, 30, 40]\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, frame1],\n                duration=durations,\n            )\n\n    with Image.open(temp_file) as im:\n        assert im.n_frames == 5\n        assert im.is_animated\n\n        # Check that timestamps and durations match original values specified\n        ts = 0\n        for frame in range(im.n_frames):\n            im.seek(frame)\n            im.load()\n            assert im.info[\"duration\"] == durations[frame]\n            assert im.info[\"timestamp\"] == ts\n            ts += durations[frame]\n\n\ndef test_seeking(tmp_path):\n    \"\"\"\n    Create an animated WebP file, and then try seeking through frames in reverse-order,\n    verifying the timestamps and durations are correct.\n    \"\"\"\n\n    dur = 33\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, frame1],\n                duration=dur,\n            )\n\n    with Image.open(temp_file) as im:\n        assert im.n_frames == 5\n        assert im.is_animated\n\n        # Traverse frames in reverse, checking timestamps and durations\n        ts = dur * (im.n_frames - 1)\n        for frame in reversed(range(im.n_frames)):\n            im.seek(frame)\n            im.load()\n            assert im.info[\"duration\"] == dur\n            assert im.info[\"timestamp\"] == ts\n            ts -= dur\n\n\ndef test_seek_errors():\n    with Image.open(\"Tests/images/iss634.webp\") as im:\n        with pytest.raises(EOFError):\n            im.seek(-1)\n\n        with pytest.raises(EOFError):\n            im.seek(42)\n",
            "new_file_content": "import pytest\nfrom packaging.version import parse as parse_version\n\nfrom PIL import Image, features\n\nfrom .helper import (\n    assert_image_equal,\n    assert_image_similar,\n    is_big_endian,\n    skip_unless_feature,\n)\n\npytestmark = [\n    skip_unless_feature(\"webp\"),\n    skip_unless_feature(\"webp_anim\"),\n]\n\n\ndef test_n_frames():\n    \"\"\"Ensure that WebP format sets n_frames and is_animated attributes correctly.\"\"\"\n\n    with Image.open(\"Tests/images/hopper.webp\") as im:\n        assert im.n_frames == 1\n        assert not im.is_animated\n\n    with Image.open(\"Tests/images/iss634.webp\") as im:\n        assert im.n_frames == 42\n        assert im.is_animated\n\n\ndef test_write_animation_L(tmp_path):\n    \"\"\"\n    Convert an animated GIF to animated WebP, then compare the frame count, and first\n    and last frames to ensure they're visually similar.\n    \"\"\"\n\n    with Image.open(\"Tests/images/iss634.gif\") as orig:\n        assert orig.n_frames > 1\n\n        temp_file = str(tmp_path / \"temp.webp\")\n        orig.save(temp_file, save_all=True)\n        with Image.open(temp_file) as im:\n            assert im.n_frames == orig.n_frames\n\n            # Compare first and last frames to the original animated GIF\n            orig.load()\n            im.load()\n            assert_image_similar(im, orig.convert(\"RGBA\"), 32.9)\n\n            if is_big_endian():\n                webp = parse_version(features.version_module(\"webp\"))\n                if webp < parse_version(\"1.2.2\"):\n                    pytest.skip(\"Fails with libwebp earlier than 1.2.2\")\n            orig.seek(orig.n_frames - 1)\n            im.seek(im.n_frames - 1)\n            orig.load()\n            im.load()\n            assert_image_similar(im, orig.convert(\"RGBA\"), 32.9)\n\n\ndef test_write_animation_RGB(tmp_path):\n    \"\"\"\n    Write an animated WebP from RGB frames, and ensure the frames\n    are visually similar to the originals.\n    \"\"\"\n\n    def check(temp_file):\n        with Image.open(temp_file) as im:\n            assert im.n_frames == 2\n\n            # Compare first frame to original\n            im.load()\n            assert_image_equal(im, frame1.convert(\"RGBA\"))\n\n            # Compare second frame to original\n            if is_big_endian():\n                webp = parse_version(features.version_module(\"webp\"))\n                if webp < parse_version(\"1.2.2\"):\n                    pytest.skip(\"Fails with libwebp earlier than 1.2.2\")\n            im.seek(1)\n            im.load()\n            assert_image_equal(im, frame2.convert(\"RGBA\"))\n\n    with Image.open(\"Tests/images/anim_frame1.webp\") as frame1:\n        with Image.open(\"Tests/images/anim_frame2.webp\") as frame2:\n            temp_file1 = str(tmp_path / \"temp.webp\")\n            frame1.copy().save(\n                temp_file1, save_all=True, append_images=[frame2], lossless=True\n            )\n            check(temp_file1)\n\n            # Tests appending using a generator\n            def im_generator(ims):\n                yield from ims\n\n            temp_file2 = str(tmp_path / \"temp_generator.webp\")\n            frame1.copy().save(\n                temp_file2,\n                save_all=True,\n                append_images=im_generator([frame2]),\n                lossless=True,\n            )\n            check(temp_file2)\n\n\ndef test_timestamp_and_duration(tmp_path):\n    \"\"\"\n    Try passing a list of durations, and make sure the encoded\n    timestamps and durations are correct.\n    \"\"\"\n\n    durations = [0, 10, 20, 30, 40]\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, frame1],\n                duration=durations,\n            )\n\n    with Image.open(temp_file) as im:\n        assert im.n_frames == 5\n        assert im.is_animated\n\n        # Check that timestamps and durations match original values specified\n        ts = 0\n        for frame in range(im.n_frames):\n            im.seek(frame)\n            im.load()\n            assert im.info[\"duration\"] == durations[frame]\n            assert im.info[\"timestamp\"] == ts\n            ts += durations[frame]\n\n\ndef test_float_duration(tmp_path):\n    temp_file = str(tmp_path / \"temp.webp\")\n    with Image.open(\"Tests/images/iss634.apng\") as im:\n        assert im.info[\"duration\"] == 70.0\n\n        im.save(temp_file, save_all=True)\n\n    with Image.open(temp_file) as reloaded:\n        reloaded.load()\n        assert reloaded.info[\"duration\"] == 70\n\n\ndef test_seeking(tmp_path):\n    \"\"\"\n    Create an animated WebP file, and then try seeking through frames in reverse-order,\n    verifying the timestamps and durations are correct.\n    \"\"\"\n\n    dur = 33\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, frame1],\n                duration=dur,\n            )\n\n    with Image.open(temp_file) as im:\n        assert im.n_frames == 5\n        assert im.is_animated\n\n        # Traverse frames in reverse, checking timestamps and durations\n        ts = dur * (im.n_frames - 1)\n        for frame in reversed(range(im.n_frames)):\n            im.seek(frame)\n            im.load()\n            assert im.info[\"duration\"] == dur\n            assert im.info[\"timestamp\"] == ts\n            ts -= dur\n\n\ndef test_seek_errors():\n    with Image.open(\"Tests/images/iss634.webp\") as im:\n        with pytest.raises(EOFError):\n            im.seek(-1)\n\n        with pytest.raises(EOFError):\n            im.seek(42)\n",
            "header": {
                "file": {
                    "path": "Tests/test_file_webp_animated.py"
                },
                "misc_line": null
            },
            "index_line": {
                "old_commit_hash": "c621df0d9",
                "new_commit_hash": "2fd5e5484",
                "mode": "100644"
            },
            "is_binary_file": false,
            "binary_line": null,
            "minus_file": {
                "path": "a/Tests/test_file_webp_animated.py"
            },
            "plus_file": {
                "path": "b/Tests/test_file_webp_animated.py"
            },
            "hunks": [
                {
                    "descriptor": {
                        "old_range": {
                            "start": 134,
                            "length": 6
                        },
                        "new_range": {
                            "start": 134,
                            "length": 18
                        },
                        "section": "def test_timestamp_and_duration(tmp_path):"
                    },
                    "line_group": {
                        "all_lines": [
                            {
                                "content": "            ts += durations[frame]",
                                "type": "context"
                            },
                            {
                                "content": "",
                                "type": "context"
                            },
                            {
                                "content": "",
                                "type": "context"
                            },
                            {
                                "content": "def test_float_duration(tmp_path):",
                                "type": "added"
                            },
                            {
                                "content": "    temp_file = str(tmp_path / \"temp.webp\")",
                                "type": "added"
                            },
                            {
                                "content": "    with Image.open(\"Tests/images/iss634.apng\") as im:",
                                "type": "added"
                            },
                            {
                                "content": "        assert im.info[\"duration\"] == 70.0",
                                "type": "added"
                            },
                            {
                                "content": "",
                                "type": "added"
                            },
                            {
                                "content": "        im.save(temp_file, save_all=True)",
                                "type": "added"
                            },
                            {
                                "content": "",
                                "type": "added"
                            },
                            {
                                "content": "    with Image.open(temp_file) as reloaded:",
                                "type": "added"
                            },
                            {
                                "content": "        reloaded.load()",
                                "type": "added"
                            },
                            {
                                "content": "        assert reloaded.info[\"duration\"] == 70",
                                "type": "added"
                            },
                            {
                                "content": "",
                                "type": "added"
                            },
                            {
                                "content": "",
                                "type": "added"
                            },
                            {
                                "content": "def test_seeking(tmp_path):",
                                "type": "context"
                            },
                            {
                                "content": "    \"\"\"",
                                "type": "context"
                            },
                            {
                                "content": "    Create an animated WebP file, and then try seeking through frames in reverse-order,",
                                "type": "context"
                            }
                        ]
                    },
                    "modified_entities": [],
                    "added_entities": [
                        {
                            "file_name": "Tests/test_file_webp_animated.py",
                            "type": "function",
                            "name": "test_float_duration",
                            "content": "def test_float_duration(tmp_path):\n    temp_file = str(tmp_path / \"temp.webp\")\n    with Image.open(\"Tests/images/iss634.apng\") as im:\n        assert im.info[\"duration\"] == 70.0\n\n        im.save(temp_file, save_all=True)\n\n    with Image.open(temp_file) as reloaded:\n        reloaded.load()\n        assert reloaded.info[\"duration\"] == 70",
                            "ast_type_str": "FunctionDef",
                            "start_lineno": 137,
                            "end_lineno": 146,
                            "parent": null
                        }
                    ],
                    "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    format = \"WEBP\"\n    format_description = \"WebP image\"\n    __loaded = 0\n    __logical_frame = 0\n\n    def _open(self):\n        if not _webp.HAVE_WEBPANIM:\n            # Legacy mode\n            data, width, height, self.mode, icc_profile, exif = _webp.WebPDecode(\n                self.fp.read()\n            )\n            if icc_profile:\n                self.info[\"icc_profile\"] = icc_profile\n            if exif:\n                self.info[\"exif\"] = exif\n            self._size = width, height\n            self.fp = BytesIO(data)\n            self.tile = [(\"raw\", (0, 0) + self.size, 0, self.mode)]\n            self.n_frames = 1\n            self.is_animated = False\n            return\n\n        # Use the newer AnimDecoder API to parse the (possibly) animated file,\n        # and access muxed chunks like ICC/EXIF/XMP.\n        self._decoder = _webp.WebPAnimDecoder(self.fp.read())\n\n        # Get info from decoder\n        width, height, loop_count, bgcolor, frame_count, mode = self._decoder.get_info()\n        self._size = width, height\n        self.info[\"loop\"] = loop_count\n        bg_a, bg_r, bg_g, bg_b = (\n            (bgcolor >> 24) & 0xFF,\n            (bgcolor >> 16) & 0xFF,\n            (bgcolor >> 8) & 0xFF,\n            bgcolor & 0xFF,\n        )\n        self.info[\"background\"] = (bg_r, bg_g, bg_b, bg_a)\n        self.n_frames = frame_count\n        self.is_animated = self.n_frames > 1\n        self.mode = \"RGB\" if mode == \"RGBX\" else mode\n        self.rawmode = mode\n        self.tile = []\n\n        # Attempt to read ICC / EXIF / XMP chunks from file\n        icc_profile = self._decoder.get_chunk(\"ICCP\")\n        exif = self._decoder.get_chunk(\"EXIF\")\n        xmp = self._decoder.get_chunk(\"XMP \")\n        if icc_profile:\n            self.info[\"icc_profile\"] = icc_profile\n        if exif:\n            self.info[\"exif\"] = exif\n        if xmp:\n            self.info[\"xmp\"] = xmp\n\n        # Initialize seek state\n        self._reset(reset=False)\n\n    def _getexif(self):\n        if \"exif\" not in self.info:\n            return None\n        return self.getexif()._get_merged_dict()\n\n    def getxmp(self):\n        \"\"\"\n        Returns a dictionary containing the XMP tags.\n        Requires defusedxml to be installed.\n\n        :returns: XMP tags in a dictionary.\n        \"\"\"\n        return self._getxmp(self.info[\"xmp\"]) if \"xmp\" in self.info else {}\n\n    def seek(self, frame):\n        if not self._seek_check(frame):\n            return\n\n        # Set logical frame to requested position\n        self.__logical_frame = frame\n\n    def _reset(self, reset=True):\n        if reset:\n            self._decoder.reset()\n        self.__physical_frame = 0\n        self.__loaded = -1\n        self.__timestamp = 0\n\n    def _get_next(self):\n        # Get next frame\n        ret = self._decoder.get_next()\n        self.__physical_frame += 1\n\n        # Check if an error occurred\n        if ret is None:\n            self._reset()  # Reset just to be safe\n            self.seek(0)\n            msg = \"failed to decode next frame in WebP file\"\n            raise EOFError(msg)\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        msg = f\"Background color is not an RGBA tuple clamped to (0-255): {background}\"\n        raise OSError(msg)\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        msg = \"cannot write file as WebP (encoder returned None)\"\n        raise OSError(msg)\n\n    fp.write(data)\n\n\ndef _save(im, fp, filename):\n    lossless = im.encoderinfo.get(\"lossless\", False)\n    quality = im.encoderinfo.get(\"quality\", 80)\n    icc_profile = im.encoderinfo.get(\"icc_profile\") or \"\"\n    exif = im.encoderinfo.get(\"exif\", b\"\")\n    if isinstance(exif, Image.Exif):\n        exif = exif.tobytes()\n    if exif.startswith(b\"Exif\\x00\\x00\"):\n        exif = exif[6:]\n    xmp = im.encoderinfo.get(\"xmp\", \"\")\n    method = im.encoderinfo.get(\"method\", 4)\n    exact = 1 if im.encoderinfo.get(\"exact\") else 0\n\n    if im.mode not in _VALID_WEBP_LEGACY_MODES:\n        alpha = (\n            \"A\" in im.mode\n            or \"a\" in im.mode\n            or (im.mode == \"P\" and \"transparency\" in im.info)\n        )\n        im = im.convert(\"RGBA\" if alpha else \"RGB\")\n\n    data = _webp.WebPEncode(\n        im.tobytes(),\n        im.size[0],\n        im.size[1],\n        lossless,\n        float(quality),\n        im.mode,\n        icc_profile,\n        method,\n        exact,\n        exif,\n        xmp,\n    )\n    if data is None:\n        msg = \"cannot write file as WebP (encoder returned None)\"\n        raise OSError(msg)\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    format = \"WEBP\"\n    format_description = \"WebP image\"\n    __loaded = 0\n    __logical_frame = 0\n\n    def _open(self):\n        if not _webp.HAVE_WEBPANIM:\n            # Legacy mode\n            data, width, height, self.mode, icc_profile, exif = _webp.WebPDecode(\n                self.fp.read()\n            )\n            if icc_profile:\n                self.info[\"icc_profile\"] = icc_profile\n            if exif:\n                self.info[\"exif\"] = exif\n            self._size = width, height\n            self.fp = BytesIO(data)\n            self.tile = [(\"raw\", (0, 0) + self.size, 0, self.mode)]\n            self.n_frames = 1\n            self.is_animated = False\n            return\n\n        # Use the newer AnimDecoder API to parse the (possibly) animated file,\n        # and access muxed chunks like ICC/EXIF/XMP.\n        self._decoder = _webp.WebPAnimDecoder(self.fp.read())\n\n        # Get info from decoder\n        width, height, loop_count, bgcolor, frame_count, mode = self._decoder.get_info()\n        self._size = width, height\n        self.info[\"loop\"] = loop_count\n        bg_a, bg_r, bg_g, bg_b = (\n            (bgcolor >> 24) & 0xFF,\n            (bgcolor >> 16) & 0xFF,\n            (bgcolor >> 8) & 0xFF,\n            bgcolor & 0xFF,\n        )\n        self.info[\"background\"] = (bg_r, bg_g, bg_b, bg_a)\n        self.n_frames = frame_count\n        self.is_animated = self.n_frames > 1\n        self.mode = \"RGB\" if mode == \"RGBX\" else mode\n        self.rawmode = mode\n        self.tile = []\n\n        # Attempt to read ICC / EXIF / XMP chunks from file\n        icc_profile = self._decoder.get_chunk(\"ICCP\")\n        exif = self._decoder.get_chunk(\"EXIF\")\n        xmp = self._decoder.get_chunk(\"XMP \")\n        if icc_profile:\n            self.info[\"icc_profile\"] = icc_profile\n        if exif:\n            self.info[\"exif\"] = exif\n        if xmp:\n            self.info[\"xmp\"] = xmp\n\n        # Initialize seek state\n        self._reset(reset=False)\n\n    def _getexif(self):\n        if \"exif\" not in self.info:\n            return None\n        return self.getexif()._get_merged_dict()\n\n    def getxmp(self):\n        \"\"\"\n        Returns a dictionary containing the XMP tags.\n        Requires defusedxml to be installed.\n\n        :returns: XMP tags in a dictionary.\n        \"\"\"\n        return self._getxmp(self.info[\"xmp\"]) if \"xmp\" in self.info else {}\n\n    def seek(self, frame):\n        if not self._seek_check(frame):\n            return\n\n        # Set logical frame to requested position\n        self.__logical_frame = frame\n\n    def _reset(self, reset=True):\n        if reset:\n            self._decoder.reset()\n        self.__physical_frame = 0\n        self.__loaded = -1\n        self.__timestamp = 0\n\n    def _get_next(self):\n        # Get next frame\n        ret = self._decoder.get_next()\n        self.__physical_frame += 1\n\n        # Check if an error occurred\n        if ret is None:\n            self._reset()  # Reset just to be safe\n            self.seek(0)\n            msg = \"failed to decode next frame in WebP file\"\n            raise EOFError(msg)\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        msg = f\"Background color is not an RGBA tuple clamped to (0-255): {background}\"\n        raise OSError(msg)\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                    round(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, round(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        msg = \"cannot write file as WebP (encoder returned None)\"\n        raise OSError(msg)\n\n    fp.write(data)\n\n\ndef _save(im, fp, filename):\n    lossless = im.encoderinfo.get(\"lossless\", False)\n    quality = im.encoderinfo.get(\"quality\", 80)\n    icc_profile = im.encoderinfo.get(\"icc_profile\") or \"\"\n    exif = im.encoderinfo.get(\"exif\", b\"\")\n    if isinstance(exif, Image.Exif):\n        exif = exif.tobytes()\n    if exif.startswith(b\"Exif\\x00\\x00\"):\n        exif = exif[6:]\n    xmp = im.encoderinfo.get(\"xmp\", \"\")\n    method = im.encoderinfo.get(\"method\", 4)\n    exact = 1 if im.encoderinfo.get(\"exact\") else 0\n\n    if im.mode not in _VALID_WEBP_LEGACY_MODES:\n        alpha = (\n            \"A\" in im.mode\n            or \"a\" in im.mode\n            or (im.mode == \"P\" and \"transparency\" in im.info)\n        )\n        im = im.convert(\"RGBA\" if alpha else \"RGB\")\n\n    data = _webp.WebPEncode(\n        im.tobytes(),\n        im.size[0],\n        im.size[1],\n        lossless,\n        float(quality),\n        im.mode,\n        icc_profile,\n        method,\n        exact,\n        exif,\n        xmp,\n    )\n    if data is None:\n        msg = \"cannot write file as WebP (encoder returned None)\"\n        raise OSError(msg)\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": "d060dd4b8",
                "new_commit_hash": "ce8e05fcb",
                "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": 285,
                            "length": 7
                        },
                        "new_range": {
                            "start": 285,
                            "length": 7
                        },
                        "section": "def _save_all(im, fp, filename):"
                    },
                    "line_group": {
                        "all_lines": [
                            {
                                "content": "                # Append the frame to the animation encoder",
                                "type": "context"
                            },
                            {
                                "content": "                enc.add(",
                                "type": "context"
                            },
                            {
                                "content": "                    frame.tobytes(\"raw\", rawmode),",
                                "type": "context"
                            },
                            {
                                "content": "                    timestamp,",
                                "type": "deleted"
                            },
                            {
                                "content": "                    round(timestamp),",
                                "type": "added"
                            },
                            {
                                "content": "                    frame.size[0],",
                                "type": "context"
                            },
                            {
                                "content": "                    frame.size[1],",
                                "type": "context"
                            },
                            {
                                "content": "                    rawmode,",
                                "type": "context"
                            }
                        ]
                    },
                    "modified_entities": [
                        {
                            "file_name": "src/PIL/WebPImagePlugin.py",
                            "type": "function",
                            "name": "_save_all",
                            "content": "def _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        msg = f\"Background color is not an RGBA tuple clamped to (0-255): {background}\"\n        raise OSError(msg)\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        msg = \"cannot write file as WebP (encoder returned None)\"\n        raise OSError(msg)\n\n    fp.write(data)",
                            "ast_type_str": "FunctionDef",
                            "start_lineno": 178,
                            "end_lineno": 316,
                            "parent": null
                        }
                    ],
                    "added_entities": [],
                    "deleted_entities": []
                },
                {
                    "descriptor": {
                        "old_range": {
                            "start": 305,
                            "length": 7
                        },
                        "new_range": {
                            "start": 305,
                            "length": 7
                        },
                        "section": "def _save_all(im, fp, filename):"
                    },
                    "line_group": {
                        "all_lines": [
                            {
                                "content": "        im.seek(cur_idx)",
                                "type": "context"
                            },
                            {
                                "content": "",
                                "type": "context"
                            },
                            {
                                "content": "    # Force encoder to flush frames",
                                "type": "context"
                            },
                            {
                                "content": "    enc.add(None, timestamp, 0, 0, \"\", lossless, quality, 0)",
                                "type": "deleted"
                            },
                            {
                                "content": "    enc.add(None, round(timestamp), 0, 0, \"\", lossless, quality, 0)",
                                "type": "added"
                            },
                            {
                                "content": "",
                                "type": "context"
                            },
                            {
                                "content": "    # Get the final output from the encoder",
                                "type": "context"
                            },
                            {
                                "content": "    data = enc.assemble(icc_profile, exif, xmp)",
                                "type": "context"
                            },
                            {
                                "content": "",
                                "type": "context"
                            }
                        ]
                    },
                    "modified_entities": [
                        {
                            "file_name": "src/PIL/WebPImagePlugin.py",
                            "type": "function",
                            "name": "_save_all",
                            "content": "def _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        msg = f\"Background color is not an RGBA tuple clamped to (0-255): {background}\"\n        raise OSError(msg)\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        msg = \"cannot write file as WebP (encoder returned None)\"\n        raise OSError(msg)\n\n    fp.write(data)",
                            "ast_type_str": "FunctionDef",
                            "start_lineno": 178,
                            "end_lineno": 316,
                            "parent": null
                        }
                    ],
                    "added_entities": [],
                    "deleted_entities": []
                }
            ]
        }
    ],
    "old_commit_hash": "459f0d8352f1bf0394e99b19fd09d529aa928d73^",
    "new_commit_hash": "459f0d8352f1bf0394e99b19fd09d529aa928d73",
    "commit_message": "Round duration when saving animated WebP",
    "commit_date": "2023-03-07T18:07:46+11:00",
    "metadata": {}
}
{
    "repo_name": "pillow",
    "new_commit_hash": "459f0d8352f1bf0394e99b19fd09d529aa928d73",
    "test_file_codes": [
        "import pytest\nfrom packaging.version import parse as parse_version\n\nfrom PIL import Image, features\n\nfrom .helper import (\n    assert_image_equal,\n    assert_image_similar,\n    is_big_endian,\n    skip_unless_feature,\n)\n\npytestmark = [\n    skip_unless_feature(\"webp\"),\n    skip_unless_feature(\"webp_anim\"),\n]\n\n\ndef test_n_frames():\n    \"\"\"Ensure that WebP format sets n_frames and is_animated attributes correctly.\"\"\"\n\n    with Image.open(\"Tests/images/hopper.webp\") as im:\n        assert im.n_frames == 1\n        assert not im.is_animated\n\n    with Image.open(\"Tests/images/iss634.webp\") as im:\n        assert im.n_frames == 42\n        assert im.is_animated\n\n\ndef test_write_animation_L(tmp_path):\n    \"\"\"\n    Convert an animated GIF to animated WebP, then compare the frame count, and first\n    and last frames to ensure they're visually similar.\n    \"\"\"\n\n    with Image.open(\"Tests/images/iss634.gif\") as orig:\n        assert orig.n_frames > 1\n\n        temp_file = str(tmp_path / \"temp.webp\")\n        orig.save(temp_file, save_all=True)\n        with Image.open(temp_file) as im:\n            assert im.n_frames == orig.n_frames\n\n            # Compare first and last frames to the original animated GIF\n            orig.load()\n            im.load()\n            assert_image_similar(im, orig.convert(\"RGBA\"), 32.9)\n\n            if is_big_endian():\n                webp = parse_version(features.version_module(\"webp\"))\n                if webp < parse_version(\"1.2.2\"):\n                    pytest.skip(\"Fails with libwebp earlier than 1.2.2\")\n            orig.seek(orig.n_frames - 1)\n            im.seek(im.n_frames - 1)\n            orig.load()\n            im.load()\n            assert_image_similar(im, orig.convert(\"RGBA\"), 32.9)\n\n\ndef test_write_animation_RGB(tmp_path):\n    \"\"\"\n    Write an animated WebP from RGB frames, and ensure the frames\n    are visually similar to the originals.\n    \"\"\"\n\n    def check(temp_file):\n        with Image.open(temp_file) as im:\n            assert im.n_frames == 2\n\n            # Compare first frame to original\n            im.load()\n            assert_image_equal(im, frame1.convert(\"RGBA\"))\n\n            # Compare second frame to original\n            if is_big_endian():\n                webp = parse_version(features.version_module(\"webp\"))\n                if webp < parse_version(\"1.2.2\"):\n                    pytest.skip(\"Fails with libwebp earlier than 1.2.2\")\n            im.seek(1)\n            im.load()\n            assert_image_equal(im, frame2.convert(\"RGBA\"))\n\n    with Image.open(\"Tests/images/anim_frame1.webp\") as frame1:\n        with Image.open(\"Tests/images/anim_frame2.webp\") as frame2:\n            temp_file1 = str(tmp_path / \"temp.webp\")\n            frame1.copy().save(\n                temp_file1, save_all=True, append_images=[frame2], lossless=True\n            )\n            check(temp_file1)\n\n            # Tests appending using a generator\n            def im_generator(ims):\n                yield from ims\n\n            temp_file2 = str(tmp_path / \"temp_generator.webp\")\n            frame1.copy().save(\n                temp_file2,\n                save_all=True,\n                append_images=im_generator([frame2]),\n                lossless=True,\n            )\n            check(temp_file2)\n\n\ndef test_timestamp_and_duration(tmp_path):\n    \"\"\"\n    Try passing a list of durations, and make sure the encoded\n    timestamps and durations are correct.\n    \"\"\"\n\n    durations = [0, 10, 20, 30, 40]\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, frame1],\n                duration=durations,\n            )\n\n    with Image.open(temp_file) as im:\n        assert im.n_frames == 5\n        assert im.is_animated\n\n        # Check that timestamps and durations match original values specified\n        ts = 0\n        for frame in range(im.n_frames):\n            im.seek(frame)\n            im.load()\n            assert im.info[\"duration\"] == durations[frame]\n            assert im.info[\"timestamp\"] == ts\n            ts += durations[frame]\n\n\ndef test_float_duration(tmp_path):\n    temp_file = str(tmp_path / \"temp.webp\")\n    with Image.open(\"Tests/images/iss634.apng\") as im:\n        assert im.info[\"duration\"] == 70.0\n\n        im.save(temp_file, save_all=True)\n\n    with Image.open(temp_file) as reloaded:\n        reloaded.load()\n        assert reloaded.info[\"duration\"] == 70\n\n\ndef test_seeking(tmp_path):\n    \"\"\"\n    Create an animated WebP file, and then try seeking through frames in reverse-order,\n    verifying the timestamps and durations are correct.\n    \"\"\"\n\n    dur = 33\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, frame1],\n                duration=dur,\n            )\n\n    with Image.open(temp_file) as im:\n        assert im.n_frames == 5\n        assert im.is_animated\n\n        # Traverse frames in reverse, checking timestamps and durations\n        ts = dur * (im.n_frames - 1)\n        for frame in reversed(range(im.n_frames)):\n            im.seek(frame)\n            im.load()\n            assert im.info[\"duration\"] == dur\n            assert im.info[\"timestamp\"] == ts\n            ts -= dur\n\n\ndef test_seek_errors():\n    with Image.open(\"Tests/images/iss634.webp\") as im:\n        with pytest.raises(EOFError):\n            im.seek(-1)\n\n        with pytest.raises(EOFError):\n            im.seek(42)\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 11ms\nInstalled 12 packages in 228ms\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 934ms\nPrepared 1 package in 35.10s\nInstalled 1 package in 2ms\n + pillow==9.5.0.dev0 (from file:///home/gcpuser/buckets/local_repoeval_bucket/repos/pillow_459f0d8352f1bf0394e99b19fd09d529aa928d73)\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_459f0d8352f1bf0394e99b19fd09d529aa928d73\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_n_frames\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_write_animation_L\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_write_animation_RGB\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_timestamp_and_duration\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_float_duration\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_seeking\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_seek_errors\u001b[0m\n\u001b[32m============================== \u001b[32m\u001b[1m7 passed\u001b[0m\u001b[32m in 0.50s\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_459f0d8352f1bf0394e99b19fd09d529aa928d73\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[31mF\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[31m                                              [100%]\u001b[0m\n\n=================================== FAILURES ===================================\n\u001b[31m\u001b[1m_____________________________ test_float_duration ______________________________\u001b[0m\n\ntmp_path = PosixPath('/tmp/pytest-of-gcpuser/pytest-238/test_float_duration0')\n\n    def test_float_duration(tmp_path):\n        temp_file = str(tmp_path / \"temp.webp\")\n        with Image.open(\"Tests/images/iss634.apng\") as im:\n            assert im.info[\"duration\"] == 70.0\n    \n>           im.save(temp_file, save_all=True)\n\n\u001b[1m\u001b[31mr2e_tests/test_1.py\u001b[0m:142: \n_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ \n\u001b[1m\u001b[31msrc/PIL/Image.py\u001b[0m:2435: in save\n    save_handler(self, fp, filename)\n_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ \n\nim = <PIL.PngImagePlugin.PngImageFile image mode=RGBA size=245x245 at 0x7BABF3662130>\nfp = <_io.BufferedRandom name='/tmp/pytest-of-gcpuser/pytest-238/test_float_duration0/temp.webp'>\nfilename = '/tmp/pytest-of-gcpuser/pytest-238/test_float_duration0/temp.webp'\n\n    def _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            msg = f\"Background color is not an RGBA tuple clamped to (0-255): {background}\"\n            raise OSError(msg)\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\u001b[1m\u001b[31mE                   TypeError: integer argument expected, got float\u001b[0m\n\n\u001b[1m\u001b[31msrc/PIL/WebPImagePlugin.py\u001b[0m:286: TypeError\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_n_frames\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_write_animation_L\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_write_animation_RGB\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_timestamp_and_duration\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_seeking\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_seek_errors\u001b[0m\n\u001b[31mFAILED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_float_duration\u001b[0m - TypeError: integer argument expected, got float\n\u001b[31m========================= \u001b[31m\u001b[1m1 failed\u001b[0m, \u001b[32m6 passed\u001b[0m\u001b[31m in 0.35s\u001b[0m\u001b[31m ==========================\u001b[0m\n",
    "old_commit_res_stderr": ""
}
Tests/test_file_webp_animated.py src/PIL/WebPImagePlugin.py
We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
[ISSUE]
**Title:** TypeError When Saving Animated WebP with Float Duration

**Description:**
When attempting to save an animated WebP image with a float duration value, a `TypeError` is raised because an integer is expected. This issue occurs because the duration timestamp is not properly rounded before being passed to the encoder.

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

def save_animated_webp():
    with Image.open("Tests/images/iss634.apng") as im:
        im.info["duration"] = 70.0  # Float duration
        im.save("temp.webp", save_all=True)

save_animated_webp()
```

**Expected Behavior:**
The animated WebP should be saved successfully with the duration rounded to the nearest integer, allowing the animation to proceed without errors.

**Actual Behavior:**
Saving the animated WebP with a float duration results in the following error:
```
TypeError: integer argument expected, got float
```
This error occurs because the encoder receives a float instead of the required integer for the duration timestamp.
[/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 getxmp(self):
        """
        Returns a dictionary containing the XMP tags.
        Requires defusedxml to be installed.

        :returns: XMP tags in a dictionary.
        """
        return self._getxmp(self.info["xmp"]) if "xmp" in self.info else {}

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

        # 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)
            msg = "failed to decode next frame in WebP file"
            raise EOFError(msg)

        # 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)
    ):
        msg = f"Background color is not an RGBA tuple clamped to (0-255): {background}"
        raise OSError(msg)

    # 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:
        msg = "cannot write file as WebP (encoder returned None)"
        raise OSError(msg)

    fp.write(data)


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

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

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

    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:** TypeError When Saving Animated WebP with Float Duration

**Description:**
When attempting to save an animated WebP image with a float duration value, a `TypeError` is raised because an integer is expected. This issue occurs because the duration timestamp is not properly rounded before being passed to the encoder.

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

def save_animated_webp():
    with Image.open("Tests/images/iss634.apng") as im:
        im.info["duration"] = 70.0  # Float duration
        im.save("temp.webp", save_all=True)

save_animated_webp()
```

**Expected Behavior:**
The animated WebP should be saved successfully with the duration rounded to the nearest integer, allowing the animation to proceed without errors.

**Actual Behavior:**
Saving the animated WebP with a float duration results in the following error:
```
TypeError: integer argument expected, got float
```
This error occurs because the encoder receives a float instead of the required integer for the duration timestamp.
[/ISSUE]
{
    "\u001b[1mtest_n_frames\u001b[0m": "PASSED",
    "\u001b[1mtest_write_animation_L\u001b[0m": "PASSED",
    "\u001b[1mtest_write_animation_RGB\u001b[0m": "PASSED",
    "\u001b[1mtest_timestamp_and_duration\u001b[0m": "PASSED",
    "\u001b[1mtest_float_duration\u001b[0m": "PASSED",
    "\u001b[1mtest_seeking\u001b[0m": "PASSED",
    "\u001b[1mtest_seek_errors\u001b[0m": "PASSED"
}
diff --git a/src/PIL/WebPImagePlugin.py b/src/PIL/WebPImagePlugin.py
index d060dd4b8..ce8e05fcb 100644
--- a/src/PIL/WebPImagePlugin.py
+++ b/src/PIL/WebPImagePlugin.py
@@ -285,7 +285,7 @@ def _save_all(im, fp, filename):
                 # Append the frame to the animation encoder
                 enc.add(
                     frame.tobytes("raw", rawmode),
-                    timestamp,
+                    round(timestamp),
                     frame.size[0],
                     frame.size[1],
                     rawmode,
@@ -305,7 +305,7 @@ def _save_all(im, fp, filename):
         im.seek(cur_idx)
 
     # Force encoder to flush frames
-    enc.add(None, timestamp, 0, 0, "", lossless, quality, 0)
+    enc.add(None, round(timestamp), 0, 0, "", lossless, quality, 0)
 
     # Get the final output from the encoder
     data = enc.assemble(icc_profile, exif, xmp)
 

{"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    format = \"WEBP\"\n    format_description = \"WebP image\"\n    __loaded = 0\n    __logical_frame = 0\n\n    def _open(self):\n        if not _webp.HAVE_WEBPANIM:\n            # Legacy mode\n            data, width, height, self.mode, icc_profile, exif = _webp.WebPDecode(\n                self.fp.read()\n            )\n            if icc_profile:\n                self.info[\"icc_profile\"] = icc_profile\n            if exif:\n                self.info[\"exif\"] = exif\n            self._size = width, height\n            self.fp = BytesIO(data)\n            self.tile = [(\"raw\", (0, 0) + self.size, 0, self.mode)]\n            self.n_frames = 1\n            self.is_animated = False\n            return\n\n        # Use the newer AnimDecoder API to parse the (possibly) animated file,\n        # and access muxed chunks like ICC/EXIF/XMP.\n        self._decoder = _webp.WebPAnimDecoder(self.fp.read())\n\n        # Get info from decoder\n        width, height, loop_count, bgcolor, frame_count, mode = self._decoder.get_info()\n        self._size = width, height\n        self.info[\"loop\"] = loop_count\n        bg_a, bg_r, bg_g, bg_b = (\n            (bgcolor >> 24) & 0xFF,\n            (bgcolor >> 16) & 0xFF,\n            (bgcolor >> 8) & 0xFF,\n            bgcolor & 0xFF,\n        )\n        self.info[\"background\"] = (bg_r, bg_g, bg_b, bg_a)\n        self.n_frames = frame_count\n        self.is_animated = self.n_frames > 1\n        self.mode = \"RGB\" if mode == \"RGBX\" else mode\n        self.rawmode = mode\n        self.tile = []\n\n        # Attempt to read ICC / EXIF / XMP chunks from file\n        icc_profile = self._decoder.get_chunk(\"ICCP\")\n        exif = self._decoder.get_chunk(\"EXIF\")\n        xmp = self._decoder.get_chunk(\"XMP \")\n        if icc_profile:\n            self.info[\"icc_profile\"] = icc_profile\n        if exif:\n            self.info[\"exif\"] = exif\n        if xmp:\n            self.info[\"xmp\"] = xmp\n\n        # Initialize seek state\n        self._reset(reset=False)\n\n    def _getexif(self):\n        if \"exif\" not in self.info:\n            return None\n        return self.getexif()._get_merged_dict()\n\n    def getxmp(self):\n        \"\"\"\n        Returns a dictionary containing the XMP tags.\n        Requires defusedxml to be installed.\n\n        :returns: XMP tags in a dictionary.\n        \"\"\"\n        return self._getxmp(self.info[\"xmp\"]) if \"xmp\" in self.info else {}\n\n    def seek(self, frame):\n        if not self._seek_check(frame):\n            return\n\n        # Set logical frame to requested position\n        self.__logical_frame = frame\n\n    def _reset(self, reset=True):\n        if reset:\n            self._decoder.reset()\n        self.__physical_frame = 0\n        self.__loaded = -1\n        self.__timestamp = 0\n\n    def _get_next(self):\n        # Get next frame\n        ret = self._decoder.get_next()\n        self.__physical_frame += 1\n\n        # Check if an error occurred\n        if ret is None:\n            self._reset()  # Reset just to be safe\n            self.seek(0)\n            msg = \"failed to decode next frame in WebP file\"\n            raise EOFError(msg)\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        msg = f\"Background color is not an RGBA tuple clamped to (0-255): {background}\"\n        raise OSError(msg)\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        msg = \"cannot write file as WebP (encoder returned None)\"\n        raise OSError(msg)\n\n    fp.write(data)\n\n\ndef _save(im, fp, filename):\n    lossless = im.encoderinfo.get(\"lossless\", False)\n    quality = im.encoderinfo.get(\"quality\", 80)\n    icc_profile = im.encoderinfo.get(\"icc_profile\") or \"\"\n    exif = im.encoderinfo.get(\"exif\", b\"\")\n    if isinstance(exif, Image.Exif):\n        exif = exif.tobytes()\n    if exif.startswith(b\"Exif\\x00\\x00\"):\n        exif = exif[6:]\n    xmp = im.encoderinfo.get(\"xmp\", \"\")\n    method = im.encoderinfo.get(\"method\", 4)\n    exact = 1 if im.encoderinfo.get(\"exact\") else 0\n\n    if im.mode not in _VALID_WEBP_LEGACY_MODES:\n        alpha = (\n            \"A\" in im.mode\n            or \"a\" in im.mode\n            or (im.mode == \"P\" and \"transparency\" in im.info)\n        )\n        im = im.convert(\"RGBA\" if alpha else \"RGB\")\n\n    data = _webp.WebPEncode(\n        im.tobytes(),\n        im.size[0],\n        im.size[1],\n        lossless,\n        float(quality),\n        im.mode,\n        icc_profile,\n        method,\n        exact,\n        exif,\n        xmp,\n    )\n    if data is None:\n        msg = \"cannot write file as WebP (encoder returned None)\"\n        raise OSError(msg)\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:** TypeError When Saving Animated WebP with Float Duration

**Description:**
When attempting to save an animated WebP image with a float duration value, a `TypeError` is raised because an integer is expected. This issue occurs because the duration timestamp is not properly rounded before being passed to the encoder.

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

def save_animated_webp():
    with Image.open("Tests/images/iss634.apng") as im:
        im.info["duration"] = 70.0  # Float duration
        im.save("temp.webp", save_all=True)

save_animated_webp()
```

**Expected Behavior:**
The animated WebP should be saved successfully with the duration rounded to the nearest integer, allowing the animation to proceed without errors.

**Actual Behavior:**
Saving the animated WebP with a float duration results in the following error:
```
TypeError: integer argument expected, got float
```
This error occurs because the encoder receives a float instead of the required integer for the duration timestamp.
[/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 getxmp(self):
        """
        Returns a dictionary containing the XMP tags.
        Requires defusedxml to be installed.

        :returns: XMP tags in a dictionary.
        """
        return self._getxmp(self.info["xmp"]) if "xmp" in self.info else {}

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

        # 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)
            msg = "failed to decode next frame in WebP file"
            raise EOFError(msg)

        # 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)
    ):
        msg = f"Background color is not an RGBA tuple clamped to (0-255): {background}"
        raise OSError(msg)

    # 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:
        msg = "cannot write file as WebP (encoder returned None)"
        raise OSError(msg)

    fp.write(data)


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

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

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

    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