Dmitriy Lunev
Dmitriy Lunev

Reputation: 227

Django convert image to webp

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?

Upvotes: 2

Views: 4936

Answers (3)

Rinshan Kolayil
Rinshan Kolayil

Reputation: 1139

I just added the following arguments to make it easier to work with other formats

 media_path = ResizedImageField(
        other_formats=['pdf'], #NEW ARGUMENT
        upload_to=document_upload_to,
        null=True,
        blank=True,
        quality=75,
        force_format='WEBP',
    )

Updates in the ResizedImageFieldFile class of following file venv/lib/python3.10/site-packages/django_resized/forms.py

class ResizedImageFieldFile(ImageField.attr_class):

    def save(self, name, content, save=True):
        #NEW LINES

        if content.name.lower().endswith(tuple(self.field.other_formats)):
            super().save(name, content, save)
            return

        #NEW LINES END

Updates in the ResizedImageField class of the file venv/lib/python3.10/site-packages/django_resized/forms.py

class ResizedImageField(ImageField):

    attr_class = ResizedImageFieldFile

    #ADDED other_formats IN THE INIT METHOD

    def __init__(self, verbose_name=None, name=None,other_formats=[], **kwargs):
        # migrate from 0.2.x
        for argname in ('max_width', 'max_height', 'use_thumbnail_aspect_ratio', 'background_color'):
            if argname in kwargs:
                warnings.warn(
                    f'Error: Keyword argument {argname} is deprecated for ResizedImageField, '
                    'see README https://github.com/un1t/django-resized',
                    DeprecationWarning,
                )
                del kwargs[argname]
        self.other_formats = other_formats #NEW LINE

Upvotes: 0

John Solly
John Solly

Reputation: 466

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!

Upvotes: 6

Dmitriy Lunev
Dmitriy Lunev

Reputation: 227

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.

Upvotes: 1

Related Questions