我的 Django 项目应用程序中有上传图像的服务,我需要将所有图像转换为 webp,以优化前端对这些文件的进一步处理。
_convert_to_webp
方法草案:
# 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
是来自 POST 请求正文的 InMemoryUploadedFile
实例(Django 自动创建它)。
我的想法是创建一个临时文件,将数据从
f_object.file.readlines()
写入其中,用PIL.Image.open
打开该文件并用format="webp"
保存。这个想法是好的还是有另一种方法来进行文件转换?
我找到了一种非常干净的方法来使用 django-resized 包来做到这一点。
pip 安装后,我只需要把
imageField
换成 ResizedImageField
img = ResizedImageField(force_format="WEBP", quality=75, upload_to="post_imgs/")
所有上传的图片都会自动转换为.webp!
解决方案非常简单。
PIL.Image
可以使用文件实例打开,所以我只是使用f_object.file
打开它,然后将其保存在经过优化和压缩的BytesIO实例中。
正确工作的代码:
# 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%
作为平衡参数。 quality=80
或 quality=90
的质量非常差。
我刚刚添加了以下参数,以便更轻松地使用其他格式
media_path = ResizedImageField(
other_formats=['pdf'], #NEW ARGUMENT
upload_to=document_upload_to,
null=True,
blank=True,
quality=75,
force_format='WEBP',
)
以下文件的 ResizedImageFieldFile 类中的更新
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
文件的 ResizedImageField 类中的更新
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