{
    "file_diffs": [
        {
            "old_file_content": "import warnings\nfrom io import BytesIO\n\nimport pytest\n\nfrom PIL import Image\n\nfrom .helper import (\n    assert_image_equal,\n    assert_image_similar,\n    is_pypy,\n    skip_unless_feature,\n)\n\ntest_files = [\"Tests/images/sugarshack.mpo\", \"Tests/images/frozenpond.mpo\"]\n\npytestmark = skip_unless_feature(\"jpg\")\n\n\ndef roundtrip(im, **options):\n    out = BytesIO()\n    im.save(out, \"MPO\", **options)\n    test_bytes = out.tell()\n    out.seek(0)\n    im = Image.open(out)\n    im.bytes = test_bytes  # for testing only\n    return im\n\n\n@pytest.mark.parametrize(\"test_file\", test_files)\ndef test_sanity(test_file):\n    with Image.open(test_file) as im:\n        im.load()\n        assert im.mode == \"RGB\"\n        assert im.size == (640, 480)\n        assert im.format == \"MPO\"\n\n\n@pytest.mark.skipif(is_pypy(), reason=\"Requires CPython\")\ndef test_unclosed_file():\n    def open():\n        im = Image.open(test_files[0])\n        im.load()\n\n    pytest.warns(ResourceWarning, open)\n\n\ndef test_closed_file():\n    with warnings.catch_warnings():\n        im = Image.open(test_files[0])\n        im.load()\n        im.close()\n\n\ndef test_seek_after_close():\n    im = Image.open(test_files[0])\n    im.close()\n\n    with pytest.raises(ValueError):\n        im.seek(1)\n\n\ndef test_context_manager():\n    with warnings.catch_warnings():\n        with Image.open(test_files[0]) as im:\n            im.load()\n\n\n@pytest.mark.parametrize(\"test_file\", test_files)\ndef test_app(test_file):\n    # Test APP/COM reader (@PIL135)\n    with Image.open(test_file) as im:\n        assert im.applist[0][0] == \"APP1\"\n        assert im.applist[1][0] == \"APP2\"\n        assert (\n            im.applist[1][1][:16] == b\"MPF\\x00MM\\x00*\\x00\\x00\\x00\\x08\\x00\\x03\\xb0\\x00\"\n        )\n        assert len(im.applist) == 2\n\n\n@pytest.mark.parametrize(\"test_file\", test_files)\ndef test_exif(test_file):\n    with Image.open(test_file) as im:\n        info = im._getexif()\n        assert info[272] == \"Nintendo 3DS\"\n        assert info[296] == 2\n        assert info[34665] == 188\n\n\ndef test_frame_size():\n    # This image has been hexedited to contain a different size\n    # in the EXIF data of the second frame\n    with Image.open(\"Tests/images/sugarshack_frame_size.mpo\") as im:\n        assert im.size == (640, 480)\n\n        im.seek(1)\n        assert im.size == (680, 480)\n\n        im.seek(0)\n        assert im.size == (640, 480)\n\n\ndef test_ignore_frame_size():\n    # Ignore the different size of the second frame\n    # since this is not a \"Large Thumbnail\" image\n    with Image.open(\"Tests/images/ignore_frame_size.mpo\") as im:\n        assert im.size == (64, 64)\n\n        im.seek(1)\n        assert (\n            im.mpinfo[0xB002][1][\"Attribute\"][\"MPType\"]\n            == \"Multi-Frame Image: (Disparity)\"\n        )\n        assert im.size == (64, 64)\n\n\ndef test_parallax():\n    # Nintendo\n    with Image.open(\"Tests/images/sugarshack.mpo\") as im:\n        exif = im.getexif()\n        assert exif.get_ifd(0x927C)[0x1101][\"Parallax\"] == -44.798187255859375\n\n    # Fujifilm\n    with Image.open(\"Tests/images/fujifilm.mpo\") as im:\n        im.seek(1)\n        exif = im.getexif()\n        assert exif.get_ifd(0x927C)[0xB211] == -3.125\n\n\ndef test_reload_exif_after_seek():\n    with Image.open(\"Tests/images/sugarshack.mpo\") as im:\n        exif = im.getexif()\n        del exif[296]\n\n        im.seek(1)\n        assert 296 in exif\n\n\n@pytest.mark.parametrize(\"test_file\", test_files)\ndef test_mp(test_file):\n    with Image.open(test_file) as im:\n        mpinfo = im._getmp()\n        assert mpinfo[45056] == b\"0100\"\n        assert mpinfo[45057] == 2\n\n\ndef test_mp_offset():\n    # This image has been manually hexedited to have an IFD offset of 10\n    # in APP2 data, in contrast to normal 8\n    with Image.open(\"Tests/images/sugarshack_ifd_offset.mpo\") as im:\n        mpinfo = im._getmp()\n        assert mpinfo[45056] == b\"0100\"\n        assert mpinfo[45057] == 2\n\n\ndef test_mp_no_data():\n    # This image has been manually hexedited to have the second frame\n    # beyond the end of the file\n    with Image.open(\"Tests/images/sugarshack_no_data.mpo\") as im:\n        with pytest.raises(ValueError):\n            im.seek(1)\n\n\n@pytest.mark.parametrize(\"test_file\", test_files)\ndef test_mp_attribute(test_file):\n    with Image.open(test_file) as im:\n        mpinfo = im._getmp()\n    frame_number = 0\n    for mpentry in mpinfo[0xB002]:\n        mpattr = mpentry[\"Attribute\"]\n        if frame_number:\n            assert not mpattr[\"RepresentativeImageFlag\"]\n        else:\n            assert mpattr[\"RepresentativeImageFlag\"]\n        assert not mpattr[\"DependentParentImageFlag\"]\n        assert not mpattr[\"DependentChildImageFlag\"]\n        assert mpattr[\"ImageDataFormat\"] == \"JPEG\"\n        assert mpattr[\"MPType\"] == \"Multi-Frame Image: (Disparity)\"\n        assert mpattr[\"Reserved\"] == 0\n        frame_number += 1\n\n\n@pytest.mark.parametrize(\"test_file\", test_files)\ndef test_seek(test_file):\n    with Image.open(test_file) as im:\n        assert im.tell() == 0\n        # prior to first image raises an error, both blatant and borderline\n        with pytest.raises(EOFError):\n            im.seek(-1)\n        with pytest.raises(EOFError):\n            im.seek(-523)\n        # after the final image raises an error,\n        # both blatant and borderline\n        with pytest.raises(EOFError):\n            im.seek(2)\n        with pytest.raises(EOFError):\n            im.seek(523)\n        # bad calls shouldn't change the frame\n        assert im.tell() == 0\n        # this one will work\n        im.seek(1)\n        assert im.tell() == 1\n        # and this one, too\n        im.seek(0)\n        assert im.tell() == 0\n\n\ndef test_n_frames():\n    with Image.open(\"Tests/images/sugarshack.mpo\") as im:\n        assert im.n_frames == 2\n        assert im.is_animated\n\n\ndef test_eoferror():\n    with Image.open(\"Tests/images/sugarshack.mpo\") as im:\n        n_frames = im.n_frames\n\n        # Test seeking past the last frame\n        with pytest.raises(EOFError):\n            im.seek(n_frames)\n        assert im.tell() < n_frames\n\n        # Test that seeking to the last frame does not raise an error\n        im.seek(n_frames - 1)\n\n\n@pytest.mark.parametrize(\"test_file\", test_files)\ndef test_image_grab(test_file):\n    with Image.open(test_file) as im:\n        assert im.tell() == 0\n        im0 = im.tobytes()\n        im.seek(1)\n        assert im.tell() == 1\n        im1 = im.tobytes()\n        im.seek(0)\n        assert im.tell() == 0\n        im02 = im.tobytes()\n        assert im0 == im02\n        assert im0 != im1\n\n\n@pytest.mark.parametrize(\"test_file\", test_files)\ndef test_save(test_file):\n    with Image.open(test_file) as im:\n        assert im.tell() == 0\n        jpg0 = roundtrip(im)\n        assert_image_similar(im, jpg0, 30)\n        im.seek(1)\n        assert im.tell() == 1\n        jpg1 = roundtrip(im)\n        assert_image_similar(im, jpg1, 30)\n\n\ndef test_save_all():\n    for test_file in test_files:\n        with Image.open(test_file) as im:\n            im_reloaded = roundtrip(im, save_all=True)\n\n            im.seek(0)\n            assert_image_similar(im, im_reloaded, 30)\n\n            im.seek(1)\n            im_reloaded.seek(1)\n            assert_image_similar(im, im_reloaded, 30)\n\n    im = Image.new(\"RGB\", (1, 1))\n    im2 = Image.new(\"RGB\", (1, 1), \"#f00\")\n    im_reloaded = roundtrip(im, save_all=True, append_images=[im2])\n\n    assert_image_equal(im, im_reloaded)\n\n    im_reloaded.seek(1)\n    assert_image_similar(im2, im_reloaded, 1)\n\n    # Test that a single frame image will not be saved as an MPO\n    jpg = roundtrip(im, save_all=True)\n    assert \"mp\" not in jpg.info\n",
            "new_file_content": "import warnings\nfrom io import BytesIO\n\nimport pytest\n\nfrom PIL import Image\n\nfrom .helper import (\n    assert_image_equal,\n    assert_image_similar,\n    is_pypy,\n    skip_unless_feature,\n)\n\ntest_files = [\"Tests/images/sugarshack.mpo\", \"Tests/images/frozenpond.mpo\"]\n\npytestmark = skip_unless_feature(\"jpg\")\n\n\ndef roundtrip(im, **options):\n    out = BytesIO()\n    im.save(out, \"MPO\", **options)\n    test_bytes = out.tell()\n    out.seek(0)\n    im = Image.open(out)\n    im.bytes = test_bytes  # for testing only\n    return im\n\n\n@pytest.mark.parametrize(\"test_file\", test_files)\ndef test_sanity(test_file):\n    with Image.open(test_file) as im:\n        im.load()\n        assert im.mode == \"RGB\"\n        assert im.size == (640, 480)\n        assert im.format == \"MPO\"\n\n\n@pytest.mark.skipif(is_pypy(), reason=\"Requires CPython\")\ndef test_unclosed_file():\n    def open():\n        im = Image.open(test_files[0])\n        im.load()\n\n    pytest.warns(ResourceWarning, open)\n\n\ndef test_closed_file():\n    with warnings.catch_warnings():\n        im = Image.open(test_files[0])\n        im.load()\n        im.close()\n\n\ndef test_seek_after_close():\n    im = Image.open(test_files[0])\n    im.close()\n\n    with pytest.raises(ValueError):\n        im.seek(1)\n\n\ndef test_context_manager():\n    with warnings.catch_warnings():\n        with Image.open(test_files[0]) as im:\n            im.load()\n\n\n@pytest.mark.parametrize(\"test_file\", test_files)\ndef test_app(test_file):\n    # Test APP/COM reader (@PIL135)\n    with Image.open(test_file) as im:\n        assert im.applist[0][0] == \"APP1\"\n        assert im.applist[1][0] == \"APP2\"\n        assert (\n            im.applist[1][1][:16] == b\"MPF\\x00MM\\x00*\\x00\\x00\\x00\\x08\\x00\\x03\\xb0\\x00\"\n        )\n        assert len(im.applist) == 2\n\n\n@pytest.mark.parametrize(\"test_file\", test_files)\ndef test_exif(test_file):\n    with Image.open(test_file) as im:\n        info = im._getexif()\n        assert info[272] == \"Nintendo 3DS\"\n        assert info[296] == 2\n        assert info[34665] == 188\n\n\ndef test_frame_size():\n    # This image has been hexedited to contain a different size\n    # in the EXIF data of the second frame\n    with Image.open(\"Tests/images/sugarshack_frame_size.mpo\") as im:\n        assert im.size == (640, 480)\n\n        im.seek(1)\n        assert im.size == (680, 480)\n\n        im.seek(0)\n        assert im.size == (640, 480)\n\n\ndef test_ignore_frame_size():\n    # Ignore the different size of the second frame\n    # since this is not a \"Large Thumbnail\" image\n    with Image.open(\"Tests/images/ignore_frame_size.mpo\") as im:\n        assert im.size == (64, 64)\n\n        im.seek(1)\n        assert (\n            im.mpinfo[0xB002][1][\"Attribute\"][\"MPType\"]\n            == \"Multi-Frame Image: (Disparity)\"\n        )\n        assert im.size == (64, 64)\n\n\ndef test_parallax():\n    # Nintendo\n    with Image.open(\"Tests/images/sugarshack.mpo\") as im:\n        exif = im.getexif()\n        assert exif.get_ifd(0x927C)[0x1101][\"Parallax\"] == -44.798187255859375\n\n    # Fujifilm\n    with Image.open(\"Tests/images/fujifilm.mpo\") as im:\n        im.seek(1)\n        exif = im.getexif()\n        assert exif.get_ifd(0x927C)[0xB211] == -3.125\n\n\ndef test_reload_exif_after_seek():\n    with Image.open(\"Tests/images/sugarshack.mpo\") as im:\n        exif = im.getexif()\n        del exif[296]\n\n        im.seek(1)\n        assert 296 in exif\n\n\n@pytest.mark.parametrize(\"test_file\", test_files)\ndef test_mp(test_file):\n    with Image.open(test_file) as im:\n        mpinfo = im._getmp()\n        assert mpinfo[45056] == b\"0100\"\n        assert mpinfo[45057] == 2\n\n\ndef test_mp_offset():\n    # This image has been manually hexedited to have an IFD offset of 10\n    # in APP2 data, in contrast to normal 8\n    with Image.open(\"Tests/images/sugarshack_ifd_offset.mpo\") as im:\n        mpinfo = im._getmp()\n        assert mpinfo[45056] == b\"0100\"\n        assert mpinfo[45057] == 2\n\n\ndef test_mp_no_data():\n    # This image has been manually hexedited to have the second frame\n    # beyond the end of the file\n    with Image.open(\"Tests/images/sugarshack_no_data.mpo\") as im:\n        with pytest.raises(ValueError):\n            im.seek(1)\n\n\n@pytest.mark.parametrize(\"test_file\", test_files)\ndef test_mp_attribute(test_file):\n    with Image.open(test_file) as im:\n        mpinfo = im._getmp()\n    frame_number = 0\n    for mpentry in mpinfo[0xB002]:\n        mpattr = mpentry[\"Attribute\"]\n        if frame_number:\n            assert not mpattr[\"RepresentativeImageFlag\"]\n        else:\n            assert mpattr[\"RepresentativeImageFlag\"]\n        assert not mpattr[\"DependentParentImageFlag\"]\n        assert not mpattr[\"DependentChildImageFlag\"]\n        assert mpattr[\"ImageDataFormat\"] == \"JPEG\"\n        assert mpattr[\"MPType\"] == \"Multi-Frame Image: (Disparity)\"\n        assert mpattr[\"Reserved\"] == 0\n        frame_number += 1\n\n\n@pytest.mark.parametrize(\"test_file\", test_files)\ndef test_seek(test_file):\n    with Image.open(test_file) as im:\n        assert im.tell() == 0\n        # prior to first image raises an error, both blatant and borderline\n        with pytest.raises(EOFError):\n            im.seek(-1)\n        with pytest.raises(EOFError):\n            im.seek(-523)\n        # after the final image raises an error,\n        # both blatant and borderline\n        with pytest.raises(EOFError):\n            im.seek(2)\n        with pytest.raises(EOFError):\n            im.seek(523)\n        # bad calls shouldn't change the frame\n        assert im.tell() == 0\n        # this one will work\n        im.seek(1)\n        assert im.tell() == 1\n        # and this one, too\n        im.seek(0)\n        assert im.tell() == 0\n\n\ndef test_n_frames():\n    with Image.open(\"Tests/images/sugarshack.mpo\") as im:\n        assert im.n_frames == 2\n        assert im.is_animated\n\n\ndef test_eoferror():\n    with Image.open(\"Tests/images/sugarshack.mpo\") as im:\n        n_frames = im.n_frames\n\n        # Test seeking past the last frame\n        with pytest.raises(EOFError):\n            im.seek(n_frames)\n        assert im.tell() < n_frames\n\n        # Test that seeking to the last frame does not raise an error\n        im.seek(n_frames - 1)\n\n\n@pytest.mark.parametrize(\"test_file\", test_files)\ndef test_image_grab(test_file):\n    with Image.open(test_file) as im:\n        assert im.tell() == 0\n        im0 = im.tobytes()\n        im.seek(1)\n        assert im.tell() == 1\n        im1 = im.tobytes()\n        im.seek(0)\n        assert im.tell() == 0\n        im02 = im.tobytes()\n        assert im0 == im02\n        assert im0 != im1\n\n\n@pytest.mark.parametrize(\"test_file\", test_files)\ndef test_save(test_file):\n    with Image.open(test_file) as im:\n        assert im.tell() == 0\n        jpg0 = roundtrip(im)\n        assert_image_similar(im, jpg0, 30)\n        im.seek(1)\n        assert im.tell() == 1\n        jpg1 = roundtrip(im)\n        assert_image_similar(im, jpg1, 30)\n\n\ndef test_save_all():\n    for test_file in test_files:\n        with Image.open(test_file) as im:\n            im_reloaded = roundtrip(im, save_all=True)\n\n            im.seek(0)\n            assert_image_similar(im, im_reloaded, 30)\n\n            im.seek(1)\n            im_reloaded.seek(1)\n            assert_image_similar(im, im_reloaded, 30)\n\n    im = Image.new(\"RGB\", (1, 1))\n    im2 = Image.new(\"RGB\", (1, 1), \"#f00\")\n    im_reloaded = roundtrip(im, save_all=True, append_images=[im2])\n\n    assert_image_equal(im, im_reloaded)\n    assert im_reloaded.mpinfo[45056] == b\"0100\"\n\n    im_reloaded.seek(1)\n    assert_image_similar(im2, im_reloaded, 1)\n\n    # Test that a single frame image will not be saved as an MPO\n    jpg = roundtrip(im, save_all=True)\n    assert \"mp\" not in jpg.info\n",
            "header": {
                "file": {
                    "path": "Tests/test_file_mpo.py"
                },
                "misc_line": null
            },
            "index_line": {
                "old_commit_hash": "d94bdaa96",
                "new_commit_hash": "dba1ec1b1",
                "mode": "100644"
            },
            "is_binary_file": false,
            "binary_line": null,
            "minus_file": {
                "path": "a/Tests/test_file_mpo.py"
            },
            "plus_file": {
                "path": "b/Tests/test_file_mpo.py"
            },
            "hunks": [
                {
                    "descriptor": {
                        "old_range": {
                            "start": 268,
                            "length": 6
                        },
                        "new_range": {
                            "start": 268,
                            "length": 7
                        },
                        "section": "def test_save_all():"
                    },
                    "line_group": {
                        "all_lines": [
                            {
                                "content": "    im_reloaded = roundtrip(im, save_all=True, append_images=[im2])",
                                "type": "context"
                            },
                            {
                                "content": "",
                                "type": "context"
                            },
                            {
                                "content": "    assert_image_equal(im, im_reloaded)",
                                "type": "context"
                            },
                            {
                                "content": "    assert im_reloaded.mpinfo[45056] == b\"0100\"",
                                "type": "added"
                            },
                            {
                                "content": "",
                                "type": "context"
                            },
                            {
                                "content": "    im_reloaded.seek(1)",
                                "type": "context"
                            },
                            {
                                "content": "    assert_image_similar(im2, im_reloaded, 1)",
                                "type": "context"
                            }
                        ]
                    },
                    "modified_entities": [
                        {
                            "file_name": "Tests/test_file_mpo.py",
                            "type": "function",
                            "name": "test_save_all",
                            "content": "def test_save_all():\n    for test_file in test_files:\n        with Image.open(test_file) as im:\n            im_reloaded = roundtrip(im, save_all=True)\n\n            im.seek(0)\n            assert_image_similar(im, im_reloaded, 30)\n\n            im.seek(1)\n            im_reloaded.seek(1)\n            assert_image_similar(im, im_reloaded, 30)\n\n    im = Image.new(\"RGB\", (1, 1))\n    im2 = Image.new(\"RGB\", (1, 1), \"#f00\")\n    im_reloaded = roundtrip(im, save_all=True, append_images=[im2])\n\n    assert_image_equal(im, im_reloaded)\n    assert im_reloaded.mpinfo[45056] == b\"0100\"\n\n    im_reloaded.seek(1)\n    assert_image_similar(im2, im_reloaded, 1)\n\n    # Test that a single frame image will not be saved as an MPO\n    jpg = roundtrip(im, save_all=True)\n    assert \"mp\" not in jpg.info",
                            "ast_type_str": "FunctionDef",
                            "start_lineno": 254,
                            "end_lineno": 278,
                            "parent": null
                        }
                    ],
                    "added_entities": [],
                    "deleted_entities": []
                }
            ]
        },
        {
            "old_file_content": "#\n# The Python Imaging Library.\n# $Id$\n#\n# MPO file handling\n#\n# See \"Multi-Picture Format\" (CIPA DC-007-Translation 2009, Standard of the\n# Camera & Imaging Products Association)\n#\n# The multi-picture object combines multiple JPEG images (with a modified EXIF\n# data format) into a single file. While it can theoretically be used much like\n# a GIF animation, it is commonly used to represent 3D photographs and is (as\n# of this writing) the most commonly used format by 3D cameras.\n#\n# History:\n# 2014-03-13 Feneric   Created\n#\n# See the README file for information on usage and redistribution.\n#\n\nimport itertools\nimport os\nimport struct\n\nfrom . import Image, ImageFile, ImageSequence, JpegImagePlugin, TiffImagePlugin\nfrom ._binary import i16be as i16\nfrom ._binary import o32le\n\n# def _accept(prefix):\n#     return JpegImagePlugin._accept(prefix)\n\n\ndef _save(im, fp, filename):\n    JpegImagePlugin._save(im, fp, filename)\n\n\ndef _save_all(im, fp, filename):\n    append_images = im.encoderinfo.get(\"append_images\", [])\n    if not append_images:\n        try:\n            animated = im.is_animated\n        except AttributeError:\n            animated = False\n        if not animated:\n            _save(im, fp, filename)\n            return\n\n    offsets = []\n    for imSequence in itertools.chain([im], append_images):\n        for im_frame in ImageSequence.Iterator(imSequence):\n            if not offsets:\n                # APP2 marker\n                im.encoderinfo[\"extra\"] = (\n                    b\"\\xFF\\xE2\" + struct.pack(\">H\", 6 + 70) + b\"MPF\\0\" + b\" \" * 70\n                )\n                JpegImagePlugin._save(im_frame, fp, filename)\n                offsets.append(fp.tell())\n            else:\n                im_frame.save(fp, \"JPEG\")\n                offsets.append(fp.tell() - offsets[-1])\n\n    ifd = TiffImagePlugin.ImageFileDirectory_v2()\n    ifd[0xB001] = len(offsets)\n\n    mpentries = b\"\"\n    data_offset = 0\n    for i, size in enumerate(offsets):\n        if i == 0:\n            mptype = 0x030000  # Baseline MP Primary Image\n        else:\n            mptype = 0x000000  # Undefined\n        mpentries += struct.pack(\"<LLLHH\", mptype, size, data_offset, 0, 0)\n        if i == 0:\n            data_offset -= 28\n        data_offset += size\n    ifd[0xB002] = mpentries\n\n    fp.seek(28)\n    fp.write(b\"II\\x2A\\x00\" + o32le(8) + ifd.tobytes(8))\n    fp.seek(0, os.SEEK_END)\n\n\n##\n# Image plugin for MPO images.\n\n\nclass MpoImageFile(JpegImagePlugin.JpegImageFile):\n\n    format = \"MPO\"\n    format_description = \"MPO (CIPA DC-007)\"\n    _close_exclusive_fp_after_loading = False\n\n    def _open(self):\n        self.fp.seek(0)  # prep the fp in order to pass the JPEG test\n        JpegImagePlugin.JpegImageFile._open(self)\n        self._after_jpeg_open()\n\n    def _after_jpeg_open(self, mpheader=None):\n        self._initial_size = self.size\n        self.mpinfo = mpheader if mpheader is not None else self._getmp()\n        self.n_frames = self.mpinfo[0xB001]\n        self.__mpoffsets = [\n            mpent[\"DataOffset\"] + self.info[\"mpoffset\"] for mpent in self.mpinfo[0xB002]\n        ]\n        self.__mpoffsets[0] = 0\n        # Note that the following assertion will only be invalid if something\n        # gets broken within JpegImagePlugin.\n        assert self.n_frames == len(self.__mpoffsets)\n        del self.info[\"mpoffset\"]  # no longer needed\n        self.is_animated = self.n_frames > 1\n        self._fp = self.fp  # FIXME: hack\n        self._fp.seek(self.__mpoffsets[0])  # get ready to read first frame\n        self.__frame = 0\n        self.offset = 0\n        # for now we can only handle reading and individual frame extraction\n        self.readonly = 1\n\n    def load_seek(self, pos):\n        self._fp.seek(pos)\n\n    def seek(self, frame):\n        if not self._seek_check(frame):\n            return\n        self.fp = self._fp\n        self.offset = self.__mpoffsets[frame]\n\n        self.fp.seek(self.offset + 2)  # skip SOI marker\n        segment = self.fp.read(2)\n        if not segment:\n            raise ValueError(\"No data found for frame\")\n        self._size = self._initial_size\n        if i16(segment) == 0xFFE1:  # APP1\n            n = i16(self.fp.read(2)) - 2\n            self.info[\"exif\"] = ImageFile._safe_read(self.fp, n)\n            self._reload_exif()\n\n            mptype = self.mpinfo[0xB002][frame][\"Attribute\"][\"MPType\"]\n            if mptype.startswith(\"Large Thumbnail\"):\n                exif = self.getexif().get_ifd(0x8769)\n                if 40962 in exif and 40963 in exif:\n                    self._size = (exif[40962], exif[40963])\n        elif \"exif\" in self.info:\n            del self.info[\"exif\"]\n            self._reload_exif()\n\n        self.tile = [(\"jpeg\", (0, 0) + self.size, self.offset, (self.mode, \"\"))]\n        self.__frame = frame\n\n    def tell(self):\n        return self.__frame\n\n    @staticmethod\n    def adopt(jpeg_instance, mpheader=None):\n        \"\"\"\n        Transform the instance of JpegImageFile into\n        an instance of MpoImageFile.\n        After the call, the JpegImageFile is extended\n        to be an MpoImageFile.\n\n        This is essentially useful when opening a JPEG\n        file that reveals itself as an MPO, to avoid\n        double call to _open.\n        \"\"\"\n        jpeg_instance.__class__ = MpoImageFile\n        jpeg_instance._after_jpeg_open(mpheader)\n        return jpeg_instance\n\n\n# ---------------------------------------------------------------------\n# Registry stuff\n\n# Note that since MPO shares a factory with JPEG, we do not need to do a\n# separate registration for it here.\n# Image.register_open(MpoImageFile.format,\n#                     JpegImagePlugin.jpeg_factory, _accept)\nImage.register_save(MpoImageFile.format, _save)\nImage.register_save_all(MpoImageFile.format, _save_all)\n\nImage.register_extension(MpoImageFile.format, \".mpo\")\n\nImage.register_mime(MpoImageFile.format, \"image/mpo\")\n",
            "new_file_content": "#\n# The Python Imaging Library.\n# $Id$\n#\n# MPO file handling\n#\n# See \"Multi-Picture Format\" (CIPA DC-007-Translation 2009, Standard of the\n# Camera & Imaging Products Association)\n#\n# The multi-picture object combines multiple JPEG images (with a modified EXIF\n# data format) into a single file. While it can theoretically be used much like\n# a GIF animation, it is commonly used to represent 3D photographs and is (as\n# of this writing) the most commonly used format by 3D cameras.\n#\n# History:\n# 2014-03-13 Feneric   Created\n#\n# See the README file for information on usage and redistribution.\n#\n\nimport itertools\nimport os\nimport struct\n\nfrom . import Image, ImageFile, ImageSequence, JpegImagePlugin, TiffImagePlugin\nfrom ._binary import i16be as i16\nfrom ._binary import o32le\n\n# def _accept(prefix):\n#     return JpegImagePlugin._accept(prefix)\n\n\ndef _save(im, fp, filename):\n    JpegImagePlugin._save(im, fp, filename)\n\n\ndef _save_all(im, fp, filename):\n    append_images = im.encoderinfo.get(\"append_images\", [])\n    if not append_images:\n        try:\n            animated = im.is_animated\n        except AttributeError:\n            animated = False\n        if not animated:\n            _save(im, fp, filename)\n            return\n\n    offsets = []\n    for imSequence in itertools.chain([im], append_images):\n        for im_frame in ImageSequence.Iterator(imSequence):\n            if not offsets:\n                # APP2 marker\n                im.encoderinfo[\"extra\"] = (\n                    b\"\\xFF\\xE2\" + struct.pack(\">H\", 6 + 82) + b\"MPF\\0\" + b\" \" * 82\n                )\n                JpegImagePlugin._save(im_frame, fp, filename)\n                offsets.append(fp.tell())\n            else:\n                im_frame.save(fp, \"JPEG\")\n                offsets.append(fp.tell() - offsets[-1])\n\n    ifd = TiffImagePlugin.ImageFileDirectory_v2()\n    ifd[0xB000] = b\"0100\"\n    ifd[0xB001] = len(offsets)\n\n    mpentries = b\"\"\n    data_offset = 0\n    for i, size in enumerate(offsets):\n        if i == 0:\n            mptype = 0x030000  # Baseline MP Primary Image\n        else:\n            mptype = 0x000000  # Undefined\n        mpentries += struct.pack(\"<LLLHH\", mptype, size, data_offset, 0, 0)\n        if i == 0:\n            data_offset -= 28\n        data_offset += size\n    ifd[0xB002] = mpentries\n\n    fp.seek(28)\n    fp.write(b\"II\\x2A\\x00\" + o32le(8) + ifd.tobytes(8))\n    fp.seek(0, os.SEEK_END)\n\n\n##\n# Image plugin for MPO images.\n\n\nclass MpoImageFile(JpegImagePlugin.JpegImageFile):\n\n    format = \"MPO\"\n    format_description = \"MPO (CIPA DC-007)\"\n    _close_exclusive_fp_after_loading = False\n\n    def _open(self):\n        self.fp.seek(0)  # prep the fp in order to pass the JPEG test\n        JpegImagePlugin.JpegImageFile._open(self)\n        self._after_jpeg_open()\n\n    def _after_jpeg_open(self, mpheader=None):\n        self._initial_size = self.size\n        self.mpinfo = mpheader if mpheader is not None else self._getmp()\n        self.n_frames = self.mpinfo[0xB001]\n        self.__mpoffsets = [\n            mpent[\"DataOffset\"] + self.info[\"mpoffset\"] for mpent in self.mpinfo[0xB002]\n        ]\n        self.__mpoffsets[0] = 0\n        # Note that the following assertion will only be invalid if something\n        # gets broken within JpegImagePlugin.\n        assert self.n_frames == len(self.__mpoffsets)\n        del self.info[\"mpoffset\"]  # no longer needed\n        self.is_animated = self.n_frames > 1\n        self._fp = self.fp  # FIXME: hack\n        self._fp.seek(self.__mpoffsets[0])  # get ready to read first frame\n        self.__frame = 0\n        self.offset = 0\n        # for now we can only handle reading and individual frame extraction\n        self.readonly = 1\n\n    def load_seek(self, pos):\n        self._fp.seek(pos)\n\n    def seek(self, frame):\n        if not self._seek_check(frame):\n            return\n        self.fp = self._fp\n        self.offset = self.__mpoffsets[frame]\n\n        self.fp.seek(self.offset + 2)  # skip SOI marker\n        segment = self.fp.read(2)\n        if not segment:\n            raise ValueError(\"No data found for frame\")\n        self._size = self._initial_size\n        if i16(segment) == 0xFFE1:  # APP1\n            n = i16(self.fp.read(2)) - 2\n            self.info[\"exif\"] = ImageFile._safe_read(self.fp, n)\n            self._reload_exif()\n\n            mptype = self.mpinfo[0xB002][frame][\"Attribute\"][\"MPType\"]\n            if mptype.startswith(\"Large Thumbnail\"):\n                exif = self.getexif().get_ifd(0x8769)\n                if 40962 in exif and 40963 in exif:\n                    self._size = (exif[40962], exif[40963])\n        elif \"exif\" in self.info:\n            del self.info[\"exif\"]\n            self._reload_exif()\n\n        self.tile = [(\"jpeg\", (0, 0) + self.size, self.offset, (self.mode, \"\"))]\n        self.__frame = frame\n\n    def tell(self):\n        return self.__frame\n\n    @staticmethod\n    def adopt(jpeg_instance, mpheader=None):\n        \"\"\"\n        Transform the instance of JpegImageFile into\n        an instance of MpoImageFile.\n        After the call, the JpegImageFile is extended\n        to be an MpoImageFile.\n\n        This is essentially useful when opening a JPEG\n        file that reveals itself as an MPO, to avoid\n        double call to _open.\n        \"\"\"\n        jpeg_instance.__class__ = MpoImageFile\n        jpeg_instance._after_jpeg_open(mpheader)\n        return jpeg_instance\n\n\n# ---------------------------------------------------------------------\n# Registry stuff\n\n# Note that since MPO shares a factory with JPEG, we do not need to do a\n# separate registration for it here.\n# Image.register_open(MpoImageFile.format,\n#                     JpegImagePlugin.jpeg_factory, _accept)\nImage.register_save(MpoImageFile.format, _save)\nImage.register_save_all(MpoImageFile.format, _save_all)\n\nImage.register_extension(MpoImageFile.format, \".mpo\")\n\nImage.register_mime(MpoImageFile.format, \"image/mpo\")\n",
            "header": {
                "file": {
                    "path": "src/PIL/MpoImagePlugin.py"
                },
                "misc_line": null
            },
            "index_line": {
                "old_commit_hash": "5bfd8efc1",
                "new_commit_hash": "92d288f2f",
                "mode": "100644"
            },
            "is_binary_file": false,
            "binary_line": null,
            "minus_file": {
                "path": "a/src/PIL/MpoImagePlugin.py"
            },
            "plus_file": {
                "path": "b/src/PIL/MpoImagePlugin.py"
            },
            "hunks": [
                {
                    "descriptor": {
                        "old_range": {
                            "start": 51,
                            "length": 7
                        },
                        "new_range": {
                            "start": 51,
                            "length": 7
                        },
                        "section": "def _save_all(im, fp, filename):"
                    },
                    "line_group": {
                        "all_lines": [
                            {
                                "content": "            if not offsets:",
                                "type": "context"
                            },
                            {
                                "content": "                # APP2 marker",
                                "type": "context"
                            },
                            {
                                "content": "                im.encoderinfo[\"extra\"] = (",
                                "type": "context"
                            },
                            {
                                "content": "                    b\"\\xFF\\xE2\" + struct.pack(\">H\", 6 + 70) + b\"MPF\\0\" + b\" \" * 70",
                                "type": "deleted"
                            },
                            {
                                "content": "                    b\"\\xFF\\xE2\" + struct.pack(\">H\", 6 + 82) + b\"MPF\\0\" + b\" \" * 82",
                                "type": "added"
                            },
                            {
                                "content": "                )",
                                "type": "context"
                            },
                            {
                                "content": "                JpegImagePlugin._save(im_frame, fp, filename)",
                                "type": "context"
                            },
                            {
                                "content": "                offsets.append(fp.tell())",
                                "type": "context"
                            }
                        ]
                    },
                    "modified_entities": [
                        {
                            "file_name": "src/PIL/MpoImagePlugin.py",
                            "type": "function",
                            "name": "_save_all",
                            "content": "def _save_all(im, fp, filename):\n    append_images = im.encoderinfo.get(\"append_images\", [])\n    if not append_images:\n        try:\n            animated = im.is_animated\n        except AttributeError:\n            animated = False\n        if not animated:\n            _save(im, fp, filename)\n            return\n\n    offsets = []\n    for imSequence in itertools.chain([im], append_images):\n        for im_frame in ImageSequence.Iterator(imSequence):\n            if not offsets:\n                # APP2 marker\n                im.encoderinfo[\"extra\"] = (\n                    b\"\\xFF\\xE2\" + struct.pack(\">H\", 6 + 70) + b\"MPF\\0\" + b\" \" * 70\n                )\n                JpegImagePlugin._save(im_frame, fp, filename)\n                offsets.append(fp.tell())\n            else:\n                im_frame.save(fp, \"JPEG\")\n                offsets.append(fp.tell() - offsets[-1])\n\n    ifd = TiffImagePlugin.ImageFileDirectory_v2()\n    ifd[0xB001] = len(offsets)\n\n    mpentries = b\"\"\n    data_offset = 0\n    for i, size in enumerate(offsets):\n        if i == 0:\n            mptype = 0x030000  # Baseline MP Primary Image\n        else:\n            mptype = 0x000000  # Undefined\n        mpentries += struct.pack(\"<LLLHH\", mptype, size, data_offset, 0, 0)\n        if i == 0:\n            data_offset -= 28\n        data_offset += size\n    ifd[0xB002] = mpentries\n\n    fp.seek(28)\n    fp.write(b\"II\\x2A\\x00\" + o32le(8) + ifd.tobytes(8))\n    fp.seek(0, os.SEEK_END)",
                            "ast_type_str": "FunctionDef",
                            "start_lineno": 37,
                            "end_lineno": 80,
                            "parent": null
                        }
                    ],
                    "added_entities": [],
                    "deleted_entities": []
                },
                {
                    "descriptor": {
                        "old_range": {
                            "start": 60,
                            "length": 6
                        },
                        "new_range": {
                            "start": 60,
                            "length": 7
                        },
                        "section": "def _save_all(im, fp, filename):"
                    },
                    "line_group": {
                        "all_lines": [
                            {
                                "content": "                offsets.append(fp.tell() - offsets[-1])",
                                "type": "context"
                            },
                            {
                                "content": "",
                                "type": "context"
                            },
                            {
                                "content": "    ifd = TiffImagePlugin.ImageFileDirectory_v2()",
                                "type": "context"
                            },
                            {
                                "content": "    ifd[0xB000] = b\"0100\"",
                                "type": "added"
                            },
                            {
                                "content": "    ifd[0xB001] = len(offsets)",
                                "type": "context"
                            },
                            {
                                "content": "",
                                "type": "context"
                            },
                            {
                                "content": "    mpentries = b\"\"",
                                "type": "context"
                            },
                            {
                                "content": "",
                                "type": "context"
                            }
                        ]
                    },
                    "modified_entities": [
                        {
                            "file_name": "src/PIL/MpoImagePlugin.py",
                            "type": "function",
                            "name": "_save_all",
                            "content": "def _save_all(im, fp, filename):\n    append_images = im.encoderinfo.get(\"append_images\", [])\n    if not append_images:\n        try:\n            animated = im.is_animated\n        except AttributeError:\n            animated = False\n        if not animated:\n            _save(im, fp, filename)\n            return\n\n    offsets = []\n    for imSequence in itertools.chain([im], append_images):\n        for im_frame in ImageSequence.Iterator(imSequence):\n            if not offsets:\n                # APP2 marker\n                im.encoderinfo[\"extra\"] = (\n                    b\"\\xFF\\xE2\" + struct.pack(\">H\", 6 + 82) + b\"MPF\\0\" + b\" \" * 82\n                )\n                JpegImagePlugin._save(im_frame, fp, filename)\n                offsets.append(fp.tell())\n            else:\n                im_frame.save(fp, \"JPEG\")\n                offsets.append(fp.tell() - offsets[-1])\n\n    ifd = TiffImagePlugin.ImageFileDirectory_v2()\n    ifd[0xB000] = b\"0100\"\n    ifd[0xB001] = len(offsets)\n\n    mpentries = b\"\"\n    data_offset = 0\n    for i, size in enumerate(offsets):\n        if i == 0:\n            mptype = 0x030000  # Baseline MP Primary Image\n        else:\n            mptype = 0x000000  # Undefined\n        mpentries += struct.pack(\"<LLLHH\", mptype, size, data_offset, 0, 0)\n        if i == 0:\n            data_offset -= 28\n        data_offset += size\n    ifd[0xB002] = mpentries\n\n    fp.seek(28)\n    fp.write(b\"II\\x2A\\x00\" + o32le(8) + ifd.tobytes(8))\n    fp.seek(0, os.SEEK_END)",
                            "ast_type_str": "FunctionDef",
                            "start_lineno": 37,
                            "end_lineno": 81,
                            "parent": null
                        }
                    ],
                    "added_entities": [],
                    "deleted_entities": []
                }
            ]
        }
    ],
    "old_commit_hash": "af317a696d181bfdbfc28aed44496915a26bc90f^",
    "new_commit_hash": "af317a696d181bfdbfc28aed44496915a26bc90f",
    "commit_message": "Added MP Format Version when saving",
    "commit_date": "2022-11-13T08:00:20+11:00",
    "metadata": {}
}
{
    "repo_name": "pillow",
    "new_commit_hash": "af317a696d181bfdbfc28aed44496915a26bc90f",
    "test_file_codes": [
        "import warnings\nfrom io import BytesIO\n\nimport pytest\n\nfrom PIL import Image\n\nfrom .helper import (\n    assert_image_equal,\n    assert_image_similar,\n    is_pypy,\n    skip_unless_feature,\n)\n\ntest_files = [\"Tests/images/sugarshack.mpo\", \"Tests/images/frozenpond.mpo\"]\n\npytestmark = skip_unless_feature(\"jpg\")\n\n\ndef roundtrip(im, **options):\n    out = BytesIO()\n    im.save(out, \"MPO\", **options)\n    test_bytes = out.tell()\n    out.seek(0)\n    im = Image.open(out)\n    im.bytes = test_bytes  # for testing only\n    return im\n\n\n@pytest.mark.parametrize(\"test_file\", test_files)\ndef test_sanity(test_file):\n    with Image.open(test_file) as im:\n        im.load()\n        assert im.mode == \"RGB\"\n        assert im.size == (640, 480)\n        assert im.format == \"MPO\"\n\n\n@pytest.mark.skipif(is_pypy(), reason=\"Requires CPython\")\ndef test_unclosed_file():\n    def open():\n        im = Image.open(test_files[0])\n        im.load()\n\n    pytest.warns(ResourceWarning, open)\n\n\ndef test_closed_file():\n    with warnings.catch_warnings():\n        im = Image.open(test_files[0])\n        im.load()\n        im.close()\n\n\ndef test_seek_after_close():\n    im = Image.open(test_files[0])\n    im.close()\n\n    with pytest.raises(ValueError):\n        im.seek(1)\n\n\ndef test_context_manager():\n    with warnings.catch_warnings():\n        with Image.open(test_files[0]) as im:\n            im.load()\n\n\n@pytest.mark.parametrize(\"test_file\", test_files)\ndef test_app(test_file):\n    # Test APP/COM reader (@PIL135)\n    with Image.open(test_file) as im:\n        assert im.applist[0][0] == \"APP1\"\n        assert im.applist[1][0] == \"APP2\"\n        assert (\n            im.applist[1][1][:16] == b\"MPF\\x00MM\\x00*\\x00\\x00\\x00\\x08\\x00\\x03\\xb0\\x00\"\n        )\n        assert len(im.applist) == 2\n\n\n@pytest.mark.parametrize(\"test_file\", test_files)\ndef test_exif(test_file):\n    with Image.open(test_file) as im:\n        info = im._getexif()\n        assert info[272] == \"Nintendo 3DS\"\n        assert info[296] == 2\n        assert info[34665] == 188\n\n\ndef test_frame_size():\n    # This image has been hexedited to contain a different size\n    # in the EXIF data of the second frame\n    with Image.open(\"Tests/images/sugarshack_frame_size.mpo\") as im:\n        assert im.size == (640, 480)\n\n        im.seek(1)\n        assert im.size == (680, 480)\n\n        im.seek(0)\n        assert im.size == (640, 480)\n\n\ndef test_ignore_frame_size():\n    # Ignore the different size of the second frame\n    # since this is not a \"Large Thumbnail\" image\n    with Image.open(\"Tests/images/ignore_frame_size.mpo\") as im:\n        assert im.size == (64, 64)\n\n        im.seek(1)\n        assert (\n            im.mpinfo[0xB002][1][\"Attribute\"][\"MPType\"]\n            == \"Multi-Frame Image: (Disparity)\"\n        )\n        assert im.size == (64, 64)\n\n\ndef test_parallax():\n    # Nintendo\n    with Image.open(\"Tests/images/sugarshack.mpo\") as im:\n        exif = im.getexif()\n        assert exif.get_ifd(0x927C)[0x1101][\"Parallax\"] == -44.798187255859375\n\n    # Fujifilm\n    with Image.open(\"Tests/images/fujifilm.mpo\") as im:\n        im.seek(1)\n        exif = im.getexif()\n        assert exif.get_ifd(0x927C)[0xB211] == -3.125\n\n\ndef test_reload_exif_after_seek():\n    with Image.open(\"Tests/images/sugarshack.mpo\") as im:\n        exif = im.getexif()\n        del exif[296]\n\n        im.seek(1)\n        assert 296 in exif\n\n\n@pytest.mark.parametrize(\"test_file\", test_files)\ndef test_mp(test_file):\n    with Image.open(test_file) as im:\n        mpinfo = im._getmp()\n        assert mpinfo[45056] == b\"0100\"\n        assert mpinfo[45057] == 2\n\n\ndef test_mp_offset():\n    # This image has been manually hexedited to have an IFD offset of 10\n    # in APP2 data, in contrast to normal 8\n    with Image.open(\"Tests/images/sugarshack_ifd_offset.mpo\") as im:\n        mpinfo = im._getmp()\n        assert mpinfo[45056] == b\"0100\"\n        assert mpinfo[45057] == 2\n\n\ndef test_mp_no_data():\n    # This image has been manually hexedited to have the second frame\n    # beyond the end of the file\n    with Image.open(\"Tests/images/sugarshack_no_data.mpo\") as im:\n        with pytest.raises(ValueError):\n            im.seek(1)\n\n\n@pytest.mark.parametrize(\"test_file\", test_files)\ndef test_mp_attribute(test_file):\n    with Image.open(test_file) as im:\n        mpinfo = im._getmp()\n    frame_number = 0\n    for mpentry in mpinfo[0xB002]:\n        mpattr = mpentry[\"Attribute\"]\n        if frame_number:\n            assert not mpattr[\"RepresentativeImageFlag\"]\n        else:\n            assert mpattr[\"RepresentativeImageFlag\"]\n        assert not mpattr[\"DependentParentImageFlag\"]\n        assert not mpattr[\"DependentChildImageFlag\"]\n        assert mpattr[\"ImageDataFormat\"] == \"JPEG\"\n        assert mpattr[\"MPType\"] == \"Multi-Frame Image: (Disparity)\"\n        assert mpattr[\"Reserved\"] == 0\n        frame_number += 1\n\n\n@pytest.mark.parametrize(\"test_file\", test_files)\ndef test_seek(test_file):\n    with Image.open(test_file) as im:\n        assert im.tell() == 0\n        # prior to first image raises an error, both blatant and borderline\n        with pytest.raises(EOFError):\n            im.seek(-1)\n        with pytest.raises(EOFError):\n            im.seek(-523)\n        # after the final image raises an error,\n        # both blatant and borderline\n        with pytest.raises(EOFError):\n            im.seek(2)\n        with pytest.raises(EOFError):\n            im.seek(523)\n        # bad calls shouldn't change the frame\n        assert im.tell() == 0\n        # this one will work\n        im.seek(1)\n        assert im.tell() == 1\n        # and this one, too\n        im.seek(0)\n        assert im.tell() == 0\n\n\ndef test_n_frames():\n    with Image.open(\"Tests/images/sugarshack.mpo\") as im:\n        assert im.n_frames == 2\n        assert im.is_animated\n\n\ndef test_eoferror():\n    with Image.open(\"Tests/images/sugarshack.mpo\") as im:\n        n_frames = im.n_frames\n\n        # Test seeking past the last frame\n        with pytest.raises(EOFError):\n            im.seek(n_frames)\n        assert im.tell() < n_frames\n\n        # Test that seeking to the last frame does not raise an error\n        im.seek(n_frames - 1)\n\n\n@pytest.mark.parametrize(\"test_file\", test_files)\ndef test_image_grab(test_file):\n    with Image.open(test_file) as im:\n        assert im.tell() == 0\n        im0 = im.tobytes()\n        im.seek(1)\n        assert im.tell() == 1\n        im1 = im.tobytes()\n        im.seek(0)\n        assert im.tell() == 0\n        im02 = im.tobytes()\n        assert im0 == im02\n        assert im0 != im1\n\n\n@pytest.mark.parametrize(\"test_file\", test_files)\ndef test_save(test_file):\n    with Image.open(test_file) as im:\n        assert im.tell() == 0\n        jpg0 = roundtrip(im)\n        assert_image_similar(im, jpg0, 30)\n        im.seek(1)\n        assert im.tell() == 1\n        jpg1 = roundtrip(im)\n        assert_image_similar(im, jpg1, 30)\n\n\ndef test_save_all():\n    for test_file in test_files:\n        with Image.open(test_file) as im:\n            im_reloaded = roundtrip(im, save_all=True)\n\n            im.seek(0)\n            assert_image_similar(im, im_reloaded, 30)\n\n            im.seek(1)\n            im_reloaded.seek(1)\n            assert_image_similar(im, im_reloaded, 30)\n\n    im = Image.new(\"RGB\", (1, 1))\n    im2 = Image.new(\"RGB\", (1, 1), \"#f00\")\n    im_reloaded = roundtrip(im, save_all=True, append_images=[im2])\n\n    assert_image_equal(im, im_reloaded)\n    assert im_reloaded.mpinfo[45056] == b\"0100\"\n\n    im_reloaded.seek(1)\n    assert_image_similar(im2, im_reloaded, 1)\n\n    # Test that a single frame image will not be saved as an MPO\n    jpg = roundtrip(im, save_all=True)\n    assert \"mp\" not in jpg.info\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 14ms\nInstalled 12 packages in 289ms\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 751ms\nPrepared 1 package in 32.87s\nInstalled 1 package in 0.84ms\n + pillow==9.4.0.dev0 (from file:///home/gcpuser/buckets/local_repoeval_bucket/repos/pillow_af317a696d181bfdbfc28aed44496915a26bc90f)\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_af317a696d181bfdbfc28aed44496915a26bc90f\nconfigfile: setup.cfg\nplugins: cov-6.0.0\ncollected 29 items\n\nr2e_tests/test_1.py \u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\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_sanity[Tests/images/sugarshack.mpo]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_sanity[Tests/images/frozenpond.mpo]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_unclosed_file\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_closed_file\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_seek_after_close\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_context_manager\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_app[Tests/images/sugarshack.mpo]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_app[Tests/images/frozenpond.mpo]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_exif[Tests/images/sugarshack.mpo]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_exif[Tests/images/frozenpond.mpo]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_frame_size\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_ignore_frame_size\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_parallax\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_reload_exif_after_seek\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_mp[Tests/images/sugarshack.mpo]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_mp[Tests/images/frozenpond.mpo]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_mp_offset\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_mp_no_data\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_mp_attribute[Tests/images/sugarshack.mpo]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_mp_attribute[Tests/images/frozenpond.mpo]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_seek[Tests/images/sugarshack.mpo]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_seek[Tests/images/frozenpond.mpo]\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_eoferror\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_image_grab[Tests/images/sugarshack.mpo]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_image_grab[Tests/images/frozenpond.mpo]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_save[Tests/images/sugarshack.mpo]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_save[Tests/images/frozenpond.mpo]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_save_all\u001b[0m\n\u001b[32m============================== \u001b[32m\u001b[1m29 passed\u001b[0m\u001b[32m in 0.30s\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_af317a696d181bfdbfc28aed44496915a26bc90f\nconfigfile: setup.cfg\nplugins: cov-6.0.0\ncollected 29 items\n\nr2e_tests/test_1.py \u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[31mF\u001b[0m\u001b[31m                        [100%]\u001b[0m\n\n=================================== FAILURES ===================================\n\u001b[31m\u001b[1m________________________________ test_save_all _________________________________\u001b[0m\n\n    def test_save_all():\n        for test_file in test_files:\n            with Image.open(test_file) as im:\n                im_reloaded = roundtrip(im, save_all=True)\n    \n                im.seek(0)\n                assert_image_similar(im, im_reloaded, 30)\n    \n                im.seek(1)\n                im_reloaded.seek(1)\n                assert_image_similar(im, im_reloaded, 30)\n    \n        im = Image.new(\"RGB\", (1, 1))\n        im2 = Image.new(\"RGB\", (1, 1), \"#f00\")\n        im_reloaded = roundtrip(im, save_all=True, append_images=[im2])\n    \n        assert_image_equal(im, im_reloaded)\n>       assert im_reloaded.mpinfo[45056] == b\"0100\"\n\u001b[1m\u001b[31mE       KeyError: 45056\u001b[0m\n\n\u001b[1m\u001b[31mr2e_tests/test_1.py\u001b[0m:271: KeyError\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_sanity[Tests/images/sugarshack.mpo]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_sanity[Tests/images/frozenpond.mpo]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_unclosed_file\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_closed_file\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_seek_after_close\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_context_manager\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_app[Tests/images/sugarshack.mpo]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_app[Tests/images/frozenpond.mpo]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_exif[Tests/images/sugarshack.mpo]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_exif[Tests/images/frozenpond.mpo]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_frame_size\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_ignore_frame_size\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_parallax\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_reload_exif_after_seek\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_mp[Tests/images/sugarshack.mpo]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_mp[Tests/images/frozenpond.mpo]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_mp_offset\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_mp_no_data\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_mp_attribute[Tests/images/sugarshack.mpo]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_mp_attribute[Tests/images/frozenpond.mpo]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_seek[Tests/images/sugarshack.mpo]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_seek[Tests/images/frozenpond.mpo]\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_eoferror\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_image_grab[Tests/images/sugarshack.mpo]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_image_grab[Tests/images/frozenpond.mpo]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_save[Tests/images/sugarshack.mpo]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_save[Tests/images/frozenpond.mpo]\u001b[0m\n\u001b[31mFAILED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_save_all\u001b[0m - KeyError: 45056\n\u001b[31m========================= \u001b[31m\u001b[1m1 failed\u001b[0m, \u001b[32m28 passed\u001b[0m\u001b[31m in 0.24s\u001b[0m\u001b[31m =========================\u001b[0m\n",
    "old_commit_res_stderr": ""
}
Tests/test_file_mpo.py src/PIL/MpoImagePlugin.py
We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
[ISSUE]
**Title:** Missing MP Format Version in MPO Files Causes KeyError When Accessing Metadata

**Description:**
When saving MPO (Multi-Picture Object) images using the `PIL` library, the MP Format Version metadata is not included in the saved file. This omission leads to a `KeyError` when attempting to access the MP Format Version using the `mpinfo` attribute.

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

# Open an existing MPO image
with Image.open('image.mpo') as im:
    # Save the MPO image with additional frames
    im.save('saved.mpo', save_all=True, append_images=[im.copy()])
    
    # Reload the saved MPO image
    with Image.open('saved.mpo') as im_reloaded:
        # Attempt to access the MP Format Version
        version = im_reloaded.mpinfo[45056]  # This raises KeyError
```

**Expected Behavior:**
The saved MPO image should include the MP Format Version in its metadata. Accessing `mpinfo[45056]` should successfully return the version information without raising an error.

**Actual Behavior:**
The MP Format Version is missing from the metadata of the saved MPO image, resulting in a `KeyError` when trying to access `mpinfo[45056]`.
[/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/MpoImagePlugin.py
#
# The Python Imaging Library.
# $Id$
#
# MPO file handling
#
# See "Multi-Picture Format" (CIPA DC-007-Translation 2009, Standard of the
# Camera & Imaging Products Association)
#
# The multi-picture object combines multiple JPEG images (with a modified EXIF
# data format) into a single file. While it can theoretically be used much like
# a GIF animation, it is commonly used to represent 3D photographs and is (as
# of this writing) the most commonly used format by 3D cameras.
#
# History:
# 2014-03-13 Feneric   Created
#
# See the README file for information on usage and redistribution.
#

import itertools
import os
import struct

from . import Image, ImageFile, ImageSequence, JpegImagePlugin, TiffImagePlugin
from ._binary import i16be as i16
from ._binary import o32le

# def _accept(prefix):
#     return JpegImagePlugin._accept(prefix)


def _save(im, fp, filename):
    JpegImagePlugin._save(im, fp, filename)


def _save_all(im, fp, filename):
    append_images = im.encoderinfo.get("append_images", [])
    if not append_images:
        try:
            animated = im.is_animated
        except AttributeError:
            animated = False
        if not animated:
            _save(im, fp, filename)
            return

    offsets = []
    for imSequence in itertools.chain([im], append_images):
        for im_frame in ImageSequence.Iterator(imSequence):
            if not offsets:
                # APP2 marker
                im.encoderinfo["extra"] = (
                    b"\xFF\xE2" + struct.pack(">H", 6 + 70) + b"MPF\0" + b" " * 70
                )
                JpegImagePlugin._save(im_frame, fp, filename)
                offsets.append(fp.tell())
            else:
                im_frame.save(fp, "JPEG")
                offsets.append(fp.tell() - offsets[-1])

    ifd = TiffImagePlugin.ImageFileDirectory_v2()
    ifd[0xB001] = len(offsets)

    mpentries = b""
    data_offset = 0
    for i, size in enumerate(offsets):
        if i == 0:
            mptype = 0x030000  # Baseline MP Primary Image
        else:
            mptype = 0x000000  # Undefined
        mpentries += struct.pack("<LLLHH", mptype, size, data_offset, 0, 0)
        if i == 0:
            data_offset -= 28
        data_offset += size
    ifd[0xB002] = mpentries

    fp.seek(28)
    fp.write(b"II\x2A\x00" + o32le(8) + ifd.tobytes(8))
    fp.seek(0, os.SEEK_END)


##
# Image plugin for MPO images.


class MpoImageFile(JpegImagePlugin.JpegImageFile):

    format = "MPO"
    format_description = "MPO (CIPA DC-007)"
    _close_exclusive_fp_after_loading = False

    def _open(self):
        self.fp.seek(0)  # prep the fp in order to pass the JPEG test
        JpegImagePlugin.JpegImageFile._open(self)
        self._after_jpeg_open()

    def _after_jpeg_open(self, mpheader=None):
        self._initial_size = self.size
        self.mpinfo = mpheader if mpheader is not None else self._getmp()
        self.n_frames = self.mpinfo[0xB001]
        self.__mpoffsets = [
            mpent["DataOffset"] + self.info["mpoffset"] for mpent in self.mpinfo[0xB002]
        ]
        self.__mpoffsets[0] = 0
        # Note that the following assertion will only be invalid if something
        # gets broken within JpegImagePlugin.
        assert self.n_frames == len(self.__mpoffsets)
        del self.info["mpoffset"]  # no longer needed
        self.is_animated = self.n_frames > 1
        self._fp = self.fp  # FIXME: hack
        self._fp.seek(self.__mpoffsets[0])  # get ready to read first frame
        self.__frame = 0
        self.offset = 0
        # for now we can only handle reading and individual frame extraction
        self.readonly = 1

    def load_seek(self, pos):
        self._fp.seek(pos)

    def seek(self, frame):
        if not self._seek_check(frame):
            return
        self.fp = self._fp
        self.offset = self.__mpoffsets[frame]

        self.fp.seek(self.offset + 2)  # skip SOI marker
        segment = self.fp.read(2)
        if not segment:
            raise ValueError("No data found for frame")
        self._size = self._initial_size
        if i16(segment) == 0xFFE1:  # APP1
            n = i16(self.fp.read(2)) - 2
            self.info["exif"] = ImageFile._safe_read(self.fp, n)
            self._reload_exif()

            mptype = self.mpinfo[0xB002][frame]["Attribute"]["MPType"]
            if mptype.startswith("Large Thumbnail"):
                exif = self.getexif().get_ifd(0x8769)
                if 40962 in exif and 40963 in exif:
                    self._size = (exif[40962], exif[40963])
        elif "exif" in self.info:
            del self.info["exif"]
            self._reload_exif()

        self.tile = [("jpeg", (0, 0) + self.size, self.offset, (self.mode, ""))]
        self.__frame = frame

    def tell(self):
        return self.__frame

    @staticmethod
    def adopt(jpeg_instance, mpheader=None):
        """
        Transform the instance of JpegImageFile into
        an instance of MpoImageFile.
        After the call, the JpegImageFile is extended
        to be an MpoImageFile.

        This is essentially useful when opening a JPEG
        file that reveals itself as an MPO, to avoid
        double call to _open.
        """
        jpeg_instance.__class__ = MpoImageFile
        jpeg_instance._after_jpeg_open(mpheader)
        return jpeg_instance


# ---------------------------------------------------------------------
# Registry stuff

# Note that since MPO shares a factory with JPEG, we do not need to do a
# separate registration for it here.
# Image.register_open(MpoImageFile.format,
#                     JpegImagePlugin.jpeg_factory, _accept)
Image.register_save(MpoImageFile.format, _save)
Image.register_save_all(MpoImageFile.format, _save_all)

Image.register_extension(MpoImageFile.format, ".mpo")

Image.register_mime(MpoImageFile.format, "image/mpo")

```
--- 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:** Missing MP Format Version in MPO Files Causes KeyError When Accessing Metadata

**Description:**
When saving MPO (Multi-Picture Object) images using the `PIL` library, the MP Format Version metadata is not included in the saved file. This omission leads to a `KeyError` when attempting to access the MP Format Version using the `mpinfo` attribute.

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

# Open an existing MPO image
with Image.open('image.mpo') as im:
    # Save the MPO image with additional frames
    im.save('saved.mpo', save_all=True, append_images=[im.copy()])
    
    # Reload the saved MPO image
    with Image.open('saved.mpo') as im_reloaded:
        # Attempt to access the MP Format Version
        version = im_reloaded.mpinfo[45056]  # This raises KeyError
```

**Expected Behavior:**
The saved MPO image should include the MP Format Version in its metadata. Accessing `mpinfo[45056]` should successfully return the version information without raising an error.

**Actual Behavior:**
The MP Format Version is missing from the metadata of the saved MPO image, resulting in a `KeyError` when trying to access `mpinfo[45056]`.
[/ISSUE]
{
    "\u001b[1mtest_sanity[Tests/images/sugarshack.mpo]\u001b[0m": "PASSED",
    "\u001b[1mtest_sanity[Tests/images/frozenpond.mpo]\u001b[0m": "PASSED",
    "\u001b[1mtest_unclosed_file\u001b[0m": "PASSED",
    "\u001b[1mtest_closed_file\u001b[0m": "PASSED",
    "\u001b[1mtest_seek_after_close\u001b[0m": "PASSED",
    "\u001b[1mtest_context_manager\u001b[0m": "PASSED",
    "\u001b[1mtest_app[Tests/images/sugarshack.mpo]\u001b[0m": "PASSED",
    "\u001b[1mtest_app[Tests/images/frozenpond.mpo]\u001b[0m": "PASSED",
    "\u001b[1mtest_exif[Tests/images/sugarshack.mpo]\u001b[0m": "PASSED",
    "\u001b[1mtest_exif[Tests/images/frozenpond.mpo]\u001b[0m": "PASSED",
    "\u001b[1mtest_frame_size\u001b[0m": "PASSED",
    "\u001b[1mtest_ignore_frame_size\u001b[0m": "PASSED",
    "\u001b[1mtest_parallax\u001b[0m": "PASSED",
    "\u001b[1mtest_reload_exif_after_seek\u001b[0m": "PASSED",
    "\u001b[1mtest_mp[Tests/images/sugarshack.mpo]\u001b[0m": "PASSED",
    "\u001b[1mtest_mp[Tests/images/frozenpond.mpo]\u001b[0m": "PASSED",
    "\u001b[1mtest_mp_offset\u001b[0m": "PASSED",
    "\u001b[1mtest_mp_no_data\u001b[0m": "PASSED",
    "\u001b[1mtest_mp_attribute[Tests/images/sugarshack.mpo]\u001b[0m": "PASSED",
    "\u001b[1mtest_mp_attribute[Tests/images/frozenpond.mpo]\u001b[0m": "PASSED",
    "\u001b[1mtest_seek[Tests/images/sugarshack.mpo]\u001b[0m": "PASSED",
    "\u001b[1mtest_seek[Tests/images/frozenpond.mpo]\u001b[0m": "PASSED",
    "\u001b[1mtest_n_frames\u001b[0m": "PASSED",
    "\u001b[1mtest_eoferror\u001b[0m": "PASSED",
    "\u001b[1mtest_image_grab[Tests/images/sugarshack.mpo]\u001b[0m": "PASSED",
    "\u001b[1mtest_image_grab[Tests/images/frozenpond.mpo]\u001b[0m": "PASSED",
    "\u001b[1mtest_save[Tests/images/sugarshack.mpo]\u001b[0m": "PASSED",
    "\u001b[1mtest_save[Tests/images/frozenpond.mpo]\u001b[0m": "PASSED",
    "\u001b[1mtest_save_all\u001b[0m": "PASSED"
}
diff --git a/src/PIL/MpoImagePlugin.py b/src/PIL/MpoImagePlugin.py
index 5bfd8efc1..92d288f2f 100644
--- a/src/PIL/MpoImagePlugin.py
+++ b/src/PIL/MpoImagePlugin.py
@@ -51,7 +51,7 @@ def _save_all(im, fp, filename):
             if not offsets:
                 # APP2 marker
                 im.encoderinfo["extra"] = (
-                    b"\xFF\xE2" + struct.pack(">H", 6 + 70) + b"MPF\0" + b" " * 70
+                    b"\xFF\xE2" + struct.pack(">H", 6 + 82) + b"MPF\0" + b" " * 82
                 )
                 JpegImagePlugin._save(im_frame, fp, filename)
                 offsets.append(fp.tell())
@@ -60,6 +60,7 @@ def _save_all(im, fp, filename):
                 offsets.append(fp.tell() - offsets[-1])
 
     ifd = TiffImagePlugin.ImageFileDirectory_v2()
+    ifd[0xB000] = b"0100"
     ifd[0xB001] = len(offsets)
 
     mpentries = b""
 

{"src/PIL/MpoImagePlugin.py": "#\n# The Python Imaging Library.\n# $Id$\n#\n# MPO file handling\n#\n# See \"Multi-Picture Format\" (CIPA DC-007-Translation 2009, Standard of the\n# Camera & Imaging Products Association)\n#\n# The multi-picture object combines multiple JPEG images (with a modified EXIF\n# data format) into a single file. While it can theoretically be used much like\n# a GIF animation, it is commonly used to represent 3D photographs and is (as\n# of this writing) the most commonly used format by 3D cameras.\n#\n# History:\n# 2014-03-13 Feneric   Created\n#\n# See the README file for information on usage and redistribution.\n#\n\nimport itertools\nimport os\nimport struct\n\nfrom . import Image, ImageFile, ImageSequence, JpegImagePlugin, TiffImagePlugin\nfrom ._binary import i16be as i16\nfrom ._binary import o32le\n\n# def _accept(prefix):\n#     return JpegImagePlugin._accept(prefix)\n\n\ndef _save(im, fp, filename):\n    JpegImagePlugin._save(im, fp, filename)\n\n\ndef _save_all(im, fp, filename):\n    append_images = im.encoderinfo.get(\"append_images\", [])\n    if not append_images:\n        try:\n            animated = im.is_animated\n        except AttributeError:\n            animated = False\n        if not animated:\n            _save(im, fp, filename)\n            return\n\n    offsets = []\n    for imSequence in itertools.chain([im], append_images):\n        for im_frame in ImageSequence.Iterator(imSequence):\n            if not offsets:\n                # APP2 marker\n                im.encoderinfo[\"extra\"] = (\n                    b\"\\xFF\\xE2\" + struct.pack(\">H\", 6 + 70) + b\"MPF\\0\" + b\" \" * 70\n                )\n                JpegImagePlugin._save(im_frame, fp, filename)\n                offsets.append(fp.tell())\n            else:\n                im_frame.save(fp, \"JPEG\")\n                offsets.append(fp.tell() - offsets[-1])\n\n    ifd = TiffImagePlugin.ImageFileDirectory_v2()\n    ifd[0xB001] = len(offsets)\n\n    mpentries = b\"\"\n    data_offset = 0\n    for i, size in enumerate(offsets):\n        if i == 0:\n            mptype = 0x030000  # Baseline MP Primary Image\n        else:\n            mptype = 0x000000  # Undefined\n        mpentries += struct.pack(\"<LLLHH\", mptype, size, data_offset, 0, 0)\n        if i == 0:\n            data_offset -= 28\n        data_offset += size\n    ifd[0xB002] = mpentries\n\n    fp.seek(28)\n    fp.write(b\"II\\x2A\\x00\" + o32le(8) + ifd.tobytes(8))\n    fp.seek(0, os.SEEK_END)\n\n\n##\n# Image plugin for MPO images.\n\n\nclass MpoImageFile(JpegImagePlugin.JpegImageFile):\n\n    format = \"MPO\"\n    format_description = \"MPO (CIPA DC-007)\"\n    _close_exclusive_fp_after_loading = False\n\n    def _open(self):\n        self.fp.seek(0)  # prep the fp in order to pass the JPEG test\n        JpegImagePlugin.JpegImageFile._open(self)\n        self._after_jpeg_open()\n\n    def _after_jpeg_open(self, mpheader=None):\n        self._initial_size = self.size\n        self.mpinfo = mpheader if mpheader is not None else self._getmp()\n        self.n_frames = self.mpinfo[0xB001]\n        self.__mpoffsets = [\n            mpent[\"DataOffset\"] + self.info[\"mpoffset\"] for mpent in self.mpinfo[0xB002]\n        ]\n        self.__mpoffsets[0] = 0\n        # Note that the following assertion will only be invalid if something\n        # gets broken within JpegImagePlugin.\n        assert self.n_frames == len(self.__mpoffsets)\n        del self.info[\"mpoffset\"]  # no longer needed\n        self.is_animated = self.n_frames > 1\n        self._fp = self.fp  # FIXME: hack\n        self._fp.seek(self.__mpoffsets[0])  # get ready to read first frame\n        self.__frame = 0\n        self.offset = 0\n        # for now we can only handle reading and individual frame extraction\n        self.readonly = 1\n\n    def load_seek(self, pos):\n        self._fp.seek(pos)\n\n    def seek(self, frame):\n        if not self._seek_check(frame):\n            return\n        self.fp = self._fp\n        self.offset = self.__mpoffsets[frame]\n\n        self.fp.seek(self.offset + 2)  # skip SOI marker\n        segment = self.fp.read(2)\n        if not segment:\n            raise ValueError(\"No data found for frame\")\n        self._size = self._initial_size\n        if i16(segment) == 0xFFE1:  # APP1\n            n = i16(self.fp.read(2)) - 2\n            self.info[\"exif\"] = ImageFile._safe_read(self.fp, n)\n            self._reload_exif()\n\n            mptype = self.mpinfo[0xB002][frame][\"Attribute\"][\"MPType\"]\n            if mptype.startswith(\"Large Thumbnail\"):\n                exif = self.getexif().get_ifd(0x8769)\n                if 40962 in exif and 40963 in exif:\n                    self._size = (exif[40962], exif[40963])\n        elif \"exif\" in self.info:\n            del self.info[\"exif\"]\n            self._reload_exif()\n\n        self.tile = [(\"jpeg\", (0, 0) + self.size, self.offset, (self.mode, \"\"))]\n        self.__frame = frame\n\n    def tell(self):\n        return self.__frame\n\n    @staticmethod\n    def adopt(jpeg_instance, mpheader=None):\n        \"\"\"\n        Transform the instance of JpegImageFile into\n        an instance of MpoImageFile.\n        After the call, the JpegImageFile is extended\n        to be an MpoImageFile.\n\n        This is essentially useful when opening a JPEG\n        file that reveals itself as an MPO, to avoid\n        double call to _open.\n        \"\"\"\n        jpeg_instance.__class__ = MpoImageFile\n        jpeg_instance._after_jpeg_open(mpheader)\n        return jpeg_instance\n\n\n# ---------------------------------------------------------------------\n# Registry stuff\n\n# Note that since MPO shares a factory with JPEG, we do not need to do a\n# separate registration for it here.\n# Image.register_open(MpoImageFile.format,\n#                     JpegImagePlugin.jpeg_factory, _accept)\nImage.register_save(MpoImageFile.format, _save)\nImage.register_save_all(MpoImageFile.format, _save_all)\n\nImage.register_extension(MpoImageFile.format, \".mpo\")\n\nImage.register_mime(MpoImageFile.format, \"image/mpo\")\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:** Missing MP Format Version in MPO Files Causes KeyError When Accessing Metadata

**Description:**
When saving MPO (Multi-Picture Object) images using the `PIL` library, the MP Format Version metadata is not included in the saved file. This omission leads to a `KeyError` when attempting to access the MP Format Version using the `mpinfo` attribute.

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

# Open an existing MPO image
with Image.open('image.mpo') as im:
    # Save the MPO image with additional frames
    im.save('saved.mpo', save_all=True, append_images=[im.copy()])
    
    # Reload the saved MPO image
    with Image.open('saved.mpo') as im_reloaded:
        # Attempt to access the MP Format Version
        version = im_reloaded.mpinfo[45056]  # This raises KeyError
```

**Expected Behavior:**
The saved MPO image should include the MP Format Version in its metadata. Accessing `mpinfo[45056]` should successfully return the version information without raising an error.

**Actual Behavior:**
The MP Format Version is missing from the metadata of the saved MPO image, resulting in a `KeyError` when trying to access `mpinfo[45056]`.
[/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/MpoImagePlugin.py
#
# The Python Imaging Library.
# $Id$
#
# MPO file handling
#
# See "Multi-Picture Format" (CIPA DC-007-Translation 2009, Standard of the
# Camera & Imaging Products Association)
#
# The multi-picture object combines multiple JPEG images (with a modified EXIF
# data format) into a single file. While it can theoretically be used much like
# a GIF animation, it is commonly used to represent 3D photographs and is (as
# of this writing) the most commonly used format by 3D cameras.
#
# History:
# 2014-03-13 Feneric   Created
#
# See the README file for information on usage and redistribution.
#

import itertools
import os
import struct

from . import Image, ImageFile, ImageSequence, JpegImagePlugin, TiffImagePlugin
from ._binary import i16be as i16
from ._binary import o32le

# def _accept(prefix):
#     return JpegImagePlugin._accept(prefix)


def _save(im, fp, filename):
    JpegImagePlugin._save(im, fp, filename)


def _save_all(im, fp, filename):
    append_images = im.encoderinfo.get("append_images", [])
    if not append_images:
        try:
            animated = im.is_animated
        except AttributeError:
            animated = False
        if not animated:
            _save(im, fp, filename)
            return

    offsets = []
    for imSequence in itertools.chain([im], append_images):
        for im_frame in ImageSequence.Iterator(imSequence):
            if not offsets:
                # APP2 marker
                im.encoderinfo["extra"] = (
                    b"\xFF\xE2" + struct.pack(">H", 6 + 70) + b"MPF\0" + b" " * 70
                )
                JpegImagePlugin._save(im_frame, fp, filename)
                offsets.append(fp.tell())
            else:
                im_frame.save(fp, "JPEG")
                offsets.append(fp.tell() - offsets[-1])

    ifd = TiffImagePlugin.ImageFileDirectory_v2()
    ifd[0xB001] = len(offsets)

    mpentries = b""
    data_offset = 0
    for i, size in enumerate(offsets):
        if i == 0:
            mptype = 0x030000  # Baseline MP Primary Image
        else:
            mptype = 0x000000  # Undefined
        mpentries += struct.pack("<LLLHH", mptype, size, data_offset, 0, 0)
        if i == 0:
            data_offset -= 28
        data_offset += size
    ifd[0xB002] = mpentries

    fp.seek(28)
    fp.write(b"II\x2A\x00" + o32le(8) + ifd.tobytes(8))
    fp.seek(0, os.SEEK_END)


##
# Image plugin for MPO images.


class MpoImageFile(JpegImagePlugin.JpegImageFile):

    format = "MPO"
    format_description = "MPO (CIPA DC-007)"
    _close_exclusive_fp_after_loading = False

    def _open(self):
        self.fp.seek(0)  # prep the fp in order to pass the JPEG test
        JpegImagePlugin.JpegImageFile._open(self)
        self._after_jpeg_open()

    def _after_jpeg_open(self, mpheader=None):
        self._initial_size = self.size
        self.mpinfo = mpheader if mpheader is not None else self._getmp()
        self.n_frames = self.mpinfo[0xB001]
        self.__mpoffsets = [
            mpent["DataOffset"] + self.info["mpoffset"] for mpent in self.mpinfo[0xB002]
        ]
        self.__mpoffsets[0] = 0
        # Note that the following assertion will only be invalid if something
        # gets broken within JpegImagePlugin.
        assert self.n_frames == len(self.__mpoffsets)
        del self.info["mpoffset"]  # no longer needed
        self.is_animated = self.n_frames > 1
        self._fp = self.fp  # FIXME: hack
        self._fp.seek(self.__mpoffsets[0])  # get ready to read first frame
        self.__frame = 0
        self.offset = 0
        # for now we can only handle reading and individual frame extraction
        self.readonly = 1

    def load_seek(self, pos):
        self._fp.seek(pos)

    def seek(self, frame):
        if not self._seek_check(frame):
            return
        self.fp = self._fp
        self.offset = self.__mpoffsets[frame]

        self.fp.seek(self.offset + 2)  # skip SOI marker
        segment = self.fp.read(2)
        if not segment:
            raise ValueError("No data found for frame")
        self._size = self._initial_size
        if i16(segment) == 0xFFE1:  # APP1
            n = i16(self.fp.read(2)) - 2
            self.info["exif"] = ImageFile._safe_read(self.fp, n)
            self._reload_exif()

            mptype = self.mpinfo[0xB002][frame]["Attribute"]["MPType"]
            if mptype.startswith("Large Thumbnail"):
                exif = self.getexif().get_ifd(0x8769)
                if 40962 in exif and 40963 in exif:
                    self._size = (exif[40962], exif[40963])
        elif "exif" in self.info:
            del self.info["exif"]
            self._reload_exif()

        self.tile = [("jpeg", (0, 0) + self.size, self.offset, (self.mode, ""))]
        self.__frame = frame

    def tell(self):
        return self.__frame

    @staticmethod
    def adopt(jpeg_instance, mpheader=None):
        """
        Transform the instance of JpegImageFile into
        an instance of MpoImageFile.
        After the call, the JpegImageFile is extended
        to be an MpoImageFile.

        This is essentially useful when opening a JPEG
        file that reveals itself as an MPO, to avoid
        double call to _open.
        """
        jpeg_instance.__class__ = MpoImageFile
        jpeg_instance._after_jpeg_open(mpheader)
        return jpeg_instance


# ---------------------------------------------------------------------
# Registry stuff

# Note that since MPO shares a factory with JPEG, we do not need to do a
# separate registration for it here.
# Image.register_open(MpoImageFile.format,
#                     JpegImagePlugin.jpeg_factory, _accept)
Image.register_save(MpoImageFile.format, _save)
Image.register_save_all(MpoImageFile.format, _save_all)

Image.register_extension(MpoImageFile.format, ".mpo")

Image.register_mime(MpoImageFile.format, "image/mpo")

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