Created
January 5, 2023 20:26
-
-
Save maiagripp/5f25b0ec28d323cb170279672e29f893 to your computer and use it in GitHub Desktop.
Engine to process gifs in a faster way
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# thumbor imaging service | |
# https://github.com/thumbor/thumbor/wiki | |
# Licensed under the MIT license: | |
# http://www.opensource.org/licenses/mit-license | |
# Copyright (c) 2022, globo.com <thumbor@g.globo> | |
from io import BytesIO | |
from PIL import Image, ImageSequence | |
from thumbor.engines.pil import Engine as PILEngine | |
from thumbor.utils import logger | |
class Engine(PILEngine): | |
@property | |
def size(self): | |
return self.image_size | |
def is_multiple(self): | |
return self.frame_count > 1 | |
def update_image_info(self): | |
self.gif = self._get_gif() | |
self.image_size = self.gif.size | |
self.frame_count = self.gif.n_frames | |
def load(self, buffer, extension): | |
self.extension = extension | |
self.buffer = buffer | |
self.image = "" | |
self.operations = {} | |
self.update_image_info() | |
def flush_operations(self): | |
if not self.operations: | |
return self.buffer | |
frames = [] | |
for frame in ImageSequence.Iterator(self.gif): | |
if "resize" in self.operations: | |
frame = frame.resize( | |
self.operations["resize"], Image.Resampling.NEAREST | |
) | |
if "crop" in self.operations: | |
frame = frame.crop(self.operations["crop"]) | |
if "flip_vertically" in self.operations: | |
frame = frame.transpose(Image.Transpose.FLIP_TOP_BOTTOM) | |
if "flip_horizontally" in self.operations: | |
frame = frame.transpose(Image.Transpose.FLIP_LEFT_RIGHT) | |
frames.append(frame) | |
self.operations = {} | |
self.save_buffer(frames) | |
self.update_image_info() | |
return self.buffer | |
def draw_rectangle(self, x, y, width, height): | |
raise NotImplementedError() | |
def resize(self, width, height): | |
if width == 0 and height == 0: | |
return | |
self.operations["resize"] = (int(width), int(height)) | |
def crop(self, left, top, right, bottom): | |
self.operations["crop"] = (int(left), int(top), int(right), int(bottom)) | |
self.flush_operations() | |
self.update_image_info() | |
def rotate(self, degrees): | |
pass | |
def flip_vertically(self): | |
self.operations["flip_vertically"] = True | |
def flip_horizontally(self): | |
self.operations["flip_horizontally"] = True | |
def extract_cover(self): | |
cover = ImageSequence.Iterator(self._get_gif())[0] | |
self.save_cover(cover) | |
self.update_image_info() | |
def read(self, extension=None, quality=None): | |
return self._read() | |
def _read(self, update_image=True): | |
buffer = self.flush_operations() | |
try: | |
with BytesIO(buffer) as buff: | |
Image.open(buff).verify() | |
except Exception: | |
self.context.metrics.incr("gif_engine.no_output") | |
logger.error( | |
"[GIF_ENGINE] invalid gif engine result for url `%s`.", | |
self.context.request.url, | |
) | |
raise | |
return buffer | |
def _get_gif(self): | |
if hasattr(self, "gif"): | |
return self.gif | |
return Image.open(BytesIO(self.buffer)) | |
def convert_to_grayscale(self, update_image=True, alpha=True): | |
pass | |
# gif have no exif data and thus can't be auto oriented | |
def reorientate(self, override_exif=True): | |
pass | |
def save_buffer(self, frames): | |
buffer = BytesIO() | |
output = frames[0] | |
output.save( | |
buffer, | |
format="GIF", | |
save_all=True, | |
append_images=frames[1:], | |
disposal=self.gif.disposal_method, | |
**self.gif.info, | |
optimize=False, | |
) | |
self.buffer = buffer.getvalue() | |
self.gif = Image.open(BytesIO(self.buffer)) | |
buffer.close() | |
def save_cover(self, cover): | |
buffer = BytesIO() | |
cover.save( | |
buffer, | |
format="GIF", | |
disposal=self.gif.disposal_method, | |
**self.gif.info, | |
optimize=False, | |
) | |
self.buffer = buffer.getvalue() | |
self.gif = Image.open(BytesIO(self.buffer)) | |
buffer.close() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment