2

I have service in my Django project's app, that upload images, and I need to convert all images to webp to optimize further work with these files on the frontend side.

Draft of _convert_to_webp method:

# imports
from pathlib import Path

from django.core.files import temp as tempfile
from django.core.files.uploadedfile import InMemoryUploadedFile
from PIL import Image


# some service class
...
    def _convert_to_webp(self, f_object: InMemoryUploadedFile):
        new_file_name = str(Path(f_object._name).with_suffix('.webp'))
        
        temp_file = tempfile.NamedTemporaryFile(suffix='.temp.webp')
        # FIXME: on other OS may cause FileNotFoundError
        with open(temp_file 'wb') as f:
            for line in f_object.file.readlines():
                ... # will it works good?
        new_file = ...
        
        new_f_object = InMemoryUploadedFile(
            new_file,
            f_object.field_name,
            new_file_name,
            f_object.content_type,
            f_object.size,
            f_object.charset,
            f_object.content_type_extra
        )
        
        return new_file_name, new_f_object
...

f_object is InMemoryUploadedFile instance from POST request body (Django automatically create it).

My idea is to create a temporary file, write data from f_object.file.readlines() to it, open this file with PIL.Image.open and save with format="webp". Is this idea a good one or there is another way to make file converting?

Dmitriy Lunev
  • 177
  • 2
  • 12
  • Upd: this new file is not needed for further work on server (that is why I use tempfile). After model instance create and save, file will actually be uploaded to remote storage. – Dmitriy Lunev Jul 06 '22 at 14:18

2 Answers2

4

I found a pretty clean way to do this using the django-resized package.

After pip installing, I just needed to swap out the imageField for a ResizedImageField

    img = ResizedImageField(force_format="WEBP", quality=75, upload_to="post_imgs/")

All image uploads are automatically converted to .webp!

John Solly
  • 326
  • 2
  • 7
1

The solution was pretty simple. PIL.Image can be opened using file instance, so I just opened it using f_object.file and then saved it in BytesIO instance with optimization and compression.

Correctly working code:

# imports
from pathlib import Path

from django.core.files.uploadedfile import InMemoryUploadedFile
from PIL import Image


# some service class
...
    def _convert_to_webp(self, f_object: InMemoryUploadedFile):
        suffix = Path(f_object._name).suffix
        if suffix == ".webp":
            return f_object._name, f_object
        
        new_file_name = str(Path(f_object._name).with_suffix('.webp'))
        image = Image.open(f_object.file)
        thumb_io = io.BytesIO()
        image.save(thumb_io, 'webp', optimize=True, quality=95)
    
        new_f_object = InMemoryUploadedFile(
            thumb_io,
            f_object.field_name,
            new_file_name,
            f_object.content_type,
            f_object.size,
            f_object.charset,
            f_object.content_type_extra
        )
        
        return new_file_name, new_f_object

95% was chosen as balanced parameter. There was very bad quality with quality=80 or quality=90.

Dmitriy Lunev
  • 177
  • 2
  • 12