Skip to content

Instantly share code, notes, and snippets.

@maiagripp
Created January 5, 2023 20:26
Show Gist options
  • Save maiagripp/5f25b0ec28d323cb170279672e29f893 to your computer and use it in GitHub Desktop.
Save maiagripp/5f25b0ec28d323cb170279672e29f893 to your computer and use it in GitHub Desktop.
Engine to process gifs in a faster way
# 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