Automatically transpose images using EXIF data (#6975)

* Automatically transpose images using EXIF data

* Added test

---------

Co-authored-by: Andrew Murray <radarhere@users.noreply.github.com>
This commit is contained in:
Andrew Murray 2025-10-24 22:08:23 +11:00 committed by GitHub
parent eb496470f1
commit 5afc9b0221
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 26 additions and 4 deletions

View File

@ -63,9 +63,10 @@ class ImagesPipeline(FilesPipeline):
crawler: Crawler | None = None,
):
try:
from PIL import Image # noqa: PLC0415
from PIL import Image, ImageOps # noqa: PLC0415
self._Image = Image
self._ImageOps = ImageOps
except ImportError:
raise NotConfigured(
"ImagesPipeline requires installing Pillow 8.0.0 or later"
@ -180,8 +181,9 @@ class ImagesPipeline(FilesPipeline):
) -> Iterable[tuple[str, Image.Image, BytesIO]]:
path = self.file_path(request, response=response, info=info, item=item)
orig_image = self._Image.open(BytesIO(response.body))
transposed_image = self._ImageOps.exif_transpose(orig_image)
width, height = orig_image.size
width, height = transposed_image.size
if width < self.min_width or height < self.min_height:
raise ImageException(
"Image too small "
@ -190,7 +192,7 @@ class ImagesPipeline(FilesPipeline):
)
image, buf = self.convert_image(
orig_image, response_body=BytesIO(response.body)
transposed_image, response_body=BytesIO(response.body)
)
yield path, image, buf

View File

@ -169,13 +169,33 @@ class TestImagesPipeline:
path, new_im, new_buf = next(get_images_gen)
assert path == "full/3fd165099d8e71b8a48b2683946e64dbfad8b52d.jpg"
assert orig_im == new_im
assert orig_im.copy() == new_im
assert buf.getvalue() == new_buf.getvalue()
thumb_path, thumb_img, thumb_buf = next(get_images_gen)
assert thumb_path == "thumbs/small/3fd165099d8e71b8a48b2683946e64dbfad8b52d.jpg"
assert orig_thumb_buf.getvalue() == thumb_buf.getvalue()
def test_get_transposed_images(self):
orig_im = Image.new("RGB", (2, 2), (0, 0, 0))
orig_im.putpixel((1, 1), (255, 0, 0))
exif = orig_im.getexif()
exif[274] = 3
buf = io.BytesIO()
orig_im.save(buf, "PNG", exif=exif)
buf.seek(0)
resp = Response(url="https://dev.mydeco.com/mydeco.gif", body=buf.getvalue())
req = Request(url="https://dev.mydeco.com/mydeco.gif")
get_images_gen = self.pipeline.get_images(
response=resp, request=req, info=object()
)
path, new_im, _ = next(get_images_gen)
assert path == "full/3fd165099d8e71b8a48b2683946e64dbfad8b52d.jpg"
assert new_im.getpixel((0, 0)) == (255, 0, 0)
def test_convert_image(self):
SIZE = (100, 100)
# straight forward case: RGB and JPEG