Pillow Resizing images (thumbnail) on Amazon S3 – Django

Question:

I am try resize an image when this is saved.
Specifically, my images are stored on Amazon S3, then I use the django-storages and boto3 third party applications

When a image is saved, this is stored in my Amazon S3 bucket, having a acces url such as follow:

https://s3-sa-east-1.amazonaws.com/ihost-project/media/studyoffer_images/algoritmos-para-ensenanza/15061122523583.jpg

The code to save and resize the image is this:

class UploadStudyOffer(models.Model):
    study_offer = models.ForeignKey(StudiesOffert, related_name='uploadsstudyoffer')
    image = models.ImageField(upload_to=get_image_path)
    # images folder per object

    def save(self, *args, **kwargs):
        super(UploadStudyOffer, self).save(*args, **kwargs)
        # We first check to make sure an image exists
        if self.image:
            # Open image and check their size
            image = Image.open(self.image)
            i_width, i_height = image.size
            max_size = (100,100)

            # We resize the image if it's too large
            if i_width > 1000:
                image.thumbnail(max_size, Image.ANTIALIAS)
                image.save(self.image.path)

When I upload an image, I get this message:

Exception Type: NotImplementedError at /host/study-offer/algoritmos-para-ensenanza/edit/images/
Exception Value: This backend doesn't support absolute paths.

And I am not sure, if the error is manage at storages or boto backends or in Pillow.
Then at level of Pillow I found the following options in the moment of save the image, such as follow:

I change the section code:

image.save(self.image.path)

to:

image.save(self.image.name)

And I get this error:

File "/home/bgarcial/workspace/hostayni_platform/hosts/models.py" in save
  542.                 image.save(self.image.name) #[Errno 2] No such file or directory: 'studyoffer_images/ingenieria-de-sistemas/15061122523583.jpg'

File "/home/bgarcial/.virtualenvs/hostayni/lib/python3.6/site-packages/PIL/Image.py" in save
  1725.             fp = builtins.open(filename, "w+b")

Exception Type: FileNotFoundError at /host/study-offer/algoritmos-para-ensenanza/edit/images/
Exception Value: [Errno 2] No such file or directory: 'studyoffer_images/algoritmos-para-ensenanza/1900-X-1080-Wallpapers-022.jpg'

Of course, my image is stored om Amazon S3 and not locally in my project or hard disk, then I use the url parameter of this way:

I change

image.save(self.image.name)

to

image.save(self.image.url)

And I get this error:

Exception Type: FileNotFoundError at /host/study-offer/algoritmos-para-ensenanza/edit/images/
Exception Value: [Errno 2] No such file or directory: 'https://s3-sa-east-1.amazonaws.com/ihost-project/media/studyoffer_images/algoritmos-para-ensenanza/15061122523583.jpg'

Getting the amazon s3 image URL does not works, even though the url is a valid url https://s3-sa-east-1.amazonaws.com/ihost-project/media/studyoffer_images/algoritmos-para-ensenanza/15061122523583.jpg

Then I change

image.save(self.image.url)

to:

image.save(self.image.file)

And my image is uploaded without any errors, but is not resized and is uploaded as its original format.

How to can I process a image uploaded from my application and their result was saved on Amazon S3 to after use them?

Asked By: bgarcial

||

Answers:

You can make it easier and use easy_thumbnails app.

If you want to crop the image on save then you can do it with:

from easy_thumbnails.fields import ThumbnailerImageField

CROP_SETTINGS = {'size': (1000, 500), 'crop': 'smart'}

class UploadStudyOffer(models.Model):

    image =ThumbnailerImageField(upload_to=get_image_path,
                                         resize_source=CROP_SETTINGS)

Or you can manually specify the size of the image in the template:

{% load thumbnail %}

<img src="{% thumbnail offer.image 1000x500 crop %}" alt="" />
Answered By: Alexander Tyapkov

Fairly new to Django/Python, but this is how I solved it. Save large file to AWS, open it with Pillow, then resize and save in memory. Then push to AWS using default_storage just as the django-storages help docs suggest. Note that img.thumbnail will retain aspect with just the longer edge of the image set to 1000 pixels. image is the Django model ImageField.

from django.core.files.storage import default_storage
from io import BytesIO

..

def save(self, *args, **kwargs):
    #run save of parent class above to save original image to disk
    super().save(*args, **kwargs)

    memfile = BytesIO()

    img = Image.open(self.image)
    if img.height > 1000 or img.width > 1000:
        output_size = (1000, 1000)
        img.thumbnail(output_size, Image.ANTIALIAS)
        img.save(memfile, 'JPEG', quality=95)
        default_storage.save(self.image.name, memfile)
        memfile.close()
        img.close()
Answered By: user11967783

Most of what I found online suggested removing the Pillow resize and writing an AWS Lambda function to handle the resize on upload. I initially tried that approach, but according to the AWS docs you shouldn’t use the same bucket for input and output, meaning I had to create a second S3 bucket just for resized images. I couldn’t figure out how to get that setup working with django-storages.

A second approach I found mentioned using a buffer to save the resized image into, and then saving that to AWS. The examples of this that I found were either incomplete or used old versions of python. Here is what actually worked for me.

user/models.py

from app.utils import image_resize

class Profile(models.Model):
    user = models.OneToOneField(User, on_delete=models.CASCADE)
    image = models.ImageField(default="profile-default.png", 
    upload_to="profile_pics")

    def __str__(self):
        return f"{self.user.username} Profile"

    def save(self, *args, **kwargs):
        image_resize(self.image, 512, 512)
        super().save(*args, **kwargs)

app/utils.py

from django.core.files import File
from pathlib import Path
from PIL import Image
from io import BytesIO

image_types = {
    "jpg": "JPEG",
    "jpeg": "JPEG",
    "png": "PNG",
    "gif": "GIF",
    "tif": "TIFF",
    "tiff": "TIFF",
}


def image_resize(image, width, height):
    # Open the image using Pillow
    img = Image.open(image)
    # check if either the width or height is greater than the max
    if img.width > width or img.height > height:
        output_size = (width, height)
        # Create a new resized “thumbnail” version of the image with Pillow
        img.thumbnail(output_size)
        # Find the file name of the image
        img_filename = Path(image.file.name).name
        # Spilt the filename on “.” to get the file extension only
        img_suffix = Path(image.file.name).name.split(".")[-1]
        # Use the file extension to determine the file type from the image_types dictionary
        img_format = image_types[img_suffix]
        # Save the resized image into the buffer, noting the correct file type
        buffer = BytesIO()
        img.save(buffer, format=img_format)
        # Wrap the buffer in File object
        file_object = File(buffer)
        # Save the new resized file as usual, which will save to S3 using django-storages
        image.save(img_filename, file_object)

I’m overriding the save method still, and calling a function I’ve placed in utils.py of my main application. The following happens in the image_resize function:

The image_function checks if the image is too wide or tall and, if it is, saves a resized version first to a memory buffer and then to S3. Back in the save method we call super().save() to save the remaining fields. The super().save() needs to be called after the image.save() or both the original and the resized images will get uploaded to S3.

I hope that was helpful to someone.

You can Visit https://blog.soards.me/posts/resize-image-on-save-in-django-before-sending-to-amazon-s3/ for more details

Answered By: olorunshola matins
Categories: questions Tags: , , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.