Skip to content

Commit 473ceea

Browse files
committed
added avif support to sorl-thumbnail
1 parent 168e879 commit 473ceea

File tree

5 files changed

+107
-0
lines changed

5 files changed

+107
-0
lines changed

sorl_thumbnail_avif/__init__.py

Whitespace-only changes.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
from sorl_thumbnail_avif.thumbnail.base import AvifThumbnail
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
from sorl.thumbnail.base import ThumbnailBackend
2+
from sorl.thumbnail.conf import settings
3+
from sorl.thumbnail.conf import defaults as default_settings
4+
from sorl.thumbnail.helpers import serialize, tokey
5+
6+
7+
EXTENSIONS = {
8+
"JPEG": "jpg",
9+
"PNG": "png",
10+
"GIF": "gif",
11+
"WEBP": "webp",
12+
"AVIF": "avif",
13+
}
14+
15+
16+
class AvifThumbnail(ThumbnailBackend):
17+
def _get_format(self, source):
18+
file_extension = self.file_extension(source)
19+
20+
if file_extension == ".avif":
21+
return "AVIF"
22+
elif file_extension == ".jpg" or file_extension == ".jpeg":
23+
return "JPEG"
24+
elif file_extension == ".png":
25+
return "PNG"
26+
elif file_extension == ".gif":
27+
return "GIF"
28+
elif file_extension == ".webp":
29+
return "WEBP"
30+
else:
31+
from django.conf import settings
32+
33+
return getattr(
34+
settings, "THUMBNAIL_FORMAT", default_settings.THUMBNAIL_FORMAT
35+
)
36+
37+
def _get_thumbnail_filename(self, source, geometry_string, options):
38+
key = tokey(source.key, geometry_string, serialize(options))
39+
path = f"{key[:2]}{key[2:4]}{key}"
40+
return f"{settings.THUMBNAIL_PREFIX}{path}.{EXTENSIONS[options["format"]]}"
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
from sorl_thumbnail_avif.thumbnail.engines.pil_engine import AvifEngine
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
from io import BytesIO
2+
from sorl.thumbnail.engines.pil_engine import Engine
3+
4+
from PIL import Image, ImageFile
5+
import pillow_avif # noqa: F401
6+
7+
8+
class AvifEngine(Engine):
9+
def get_image(self, source):
10+
buffer = BytesIO(source.read())
11+
return Image.open(buffer)
12+
13+
def is_valid_image(self, raw_data):
14+
buffer = BytesIO(raw_data)
15+
try:
16+
trial_image = Image.open(buffer)
17+
trial_image.verify()
18+
except Exception:
19+
return False
20+
return True
21+
22+
def _padding(self, image, geometry, options):
23+
x_image, y_image = self.get_image_size(image)
24+
left = int((geometry[0] - x_image) / 2)
25+
top = int((geometry[1] - y_image) / 2)
26+
color = options.get("padding_color")
27+
im = Image.new(image.mode, geometry, color)
28+
im.paste(image, (left, top))
29+
return im
30+
31+
def _get_raw_data(
32+
self, image, format_, quality, image_info=None, progressive=False
33+
):
34+
# Increase (but never decrease) PIL buffer size
35+
ImageFile.MAXBLOCK = max(ImageFile.MAXBLOCK, image.size[0] * image.size[1])
36+
bf = BytesIO()
37+
38+
params = {
39+
"format": format_,
40+
"quality": quality,
41+
"optimize": 1,
42+
}
43+
44+
# keeps icc_profile
45+
if "icc_profile" in image_info:
46+
params["icc_profile"] = image_info["icc_profile"]
47+
48+
raw_data = None
49+
50+
if format_ == "JPEG" and progressive:
51+
params["progressive"] = True
52+
try:
53+
# Do not save unnecessary exif data for smaller thumbnail size
54+
params.pop("exif", {})
55+
image.save(bf, **params)
56+
except OSError:
57+
# Try without optimization.
58+
params.pop("optimize")
59+
image.save(bf, **params)
60+
else:
61+
raw_data = bf.getvalue()
62+
finally:
63+
bf.close()
64+
65+
return raw_data

0 commit comments

Comments
 (0)