mirror of https://gitlab.com/litecord/litecord.git
attachments: add gif resize support
- images: strip IconManager.resize_gif into its own function
This commit is contained in:
parent
35967cb714
commit
1555a4717e
|
|
@ -22,15 +22,34 @@ from pathlib import Path
|
|||
from quart import Blueprint, send_file, current_app as app, request
|
||||
from PIL import Image
|
||||
|
||||
from litecord.images import resize_gif
|
||||
|
||||
bp = Blueprint('attachments', __name__)
|
||||
ATTACHMENTS = Path.cwd() / 'attachments'
|
||||
|
||||
|
||||
async def _resize(image, attach_id: str, ext: str,
|
||||
async def _resize_gif(attach_id: int, resized_path: Path,
|
||||
width: int, height: int) -> str:
|
||||
"""Resize a GIF attachment."""
|
||||
|
||||
# get original gif bytes
|
||||
orig_path = ATTACHMENTS / f'{attach_id}.gif'
|
||||
orig_bytes = orig_path.read_bytes()
|
||||
|
||||
# give them and the target size to the
|
||||
# image module's resize_gif
|
||||
|
||||
_data_fd, raw_data = await resize_gif(orig_bytes, (width, height))
|
||||
|
||||
# write raw_data to the destination
|
||||
resized_path.write_bytes(raw_data)
|
||||
|
||||
return str(resized_path)
|
||||
|
||||
|
||||
async def _resize(image, attach_id: int, ext: str,
|
||||
width: int, height: int) -> str:
|
||||
"""Resize an image."""
|
||||
# TODO: gif support
|
||||
|
||||
# check if we have it on the folder
|
||||
resized_path = ATTACHMENTS / f'{attach_id}_{width}_{height}.{ext}'
|
||||
|
||||
|
|
@ -44,6 +63,11 @@ async def _resize(image, attach_id: str, ext: str,
|
|||
# if we dont, we need to generate it off the
|
||||
# given image instance.
|
||||
|
||||
# the process is different for gif files because we need
|
||||
# gifsicle. doing it manually is too troublesome.
|
||||
if ext == 'gif':
|
||||
return await _resize_gif(attach_id, resized_path, width, height)
|
||||
|
||||
# NOTE: this is the same resize mode for icons.
|
||||
resized = image.resize((width, height), resample=Image.LANCZOS)
|
||||
resized.save(resized_path_s, format=ext)
|
||||
|
|
|
|||
|
|
@ -194,6 +194,65 @@ def _try_unlink(path: str):
|
|||
pass
|
||||
|
||||
|
||||
async def resize_gif(raw_data: bytes, target: tuple) -> tuple:
|
||||
"""Resize a GIF image."""
|
||||
# generate a temporary file to call gifsticle to and from.
|
||||
input_fd, input_path = tempfile.mkstemp(suffix='.gif')
|
||||
_, output_path = tempfile.mkstemp(suffix='.gif')
|
||||
|
||||
input_handler = os.fdopen(input_fd, 'wb')
|
||||
|
||||
# make sure its valid image data
|
||||
data_fd = BytesIO(raw_data)
|
||||
image = Image.open(data_fd)
|
||||
image.close()
|
||||
|
||||
log.info('resizing a GIF from {} to {}',
|
||||
image.size, target)
|
||||
|
||||
# insert image info on input_handler
|
||||
# close it to make it ready for consumption by gifsicle
|
||||
input_handler.write(raw_data)
|
||||
input_handler.close()
|
||||
|
||||
# call gifsicle under subprocess
|
||||
log.debug('input: {}', input_path)
|
||||
log.debug('output: {}', output_path)
|
||||
|
||||
process = await asyncio.create_subprocess_shell(
|
||||
f'gifsicle --resize {target[0]}x{target[1]} '
|
||||
f'{input_path} > {output_path}',
|
||||
stdout=asyncio.subprocess.PIPE,
|
||||
stderr=asyncio.subprocess.PIPE,
|
||||
)
|
||||
|
||||
# run it, etc.
|
||||
out, err = await process.communicate()
|
||||
|
||||
log.debug('out + err from gifsicle: {}', out + err)
|
||||
|
||||
# write over an empty data_fd
|
||||
data_fd = BytesIO()
|
||||
output_handler = open(output_path, 'rb')
|
||||
data_fd.write(output_handler.read())
|
||||
|
||||
# close unused handlers
|
||||
output_handler.close()
|
||||
|
||||
# delete the files we created with mkstemp
|
||||
_try_unlink(input_path)
|
||||
_try_unlink(output_path)
|
||||
|
||||
# reseek, save to raw_data, reseek again.
|
||||
# TODO: remove raw_data altogether as its inefficient
|
||||
# to have two representations of the same bytes
|
||||
data_fd.seek(0)
|
||||
raw_data = data_fd.read()
|
||||
data_fd.seek(0)
|
||||
|
||||
return data_fd, raw_data
|
||||
|
||||
|
||||
class IconManager:
|
||||
"""Main icon manager."""
|
||||
def __init__(self, app):
|
||||
|
|
@ -257,65 +316,6 @@ class IconManager:
|
|||
return await self.generic_get(
|
||||
'guild', guild_id, icon_hash, **kwargs)
|
||||
|
||||
async def _resize_gif(self, scope: str, key,
|
||||
raw_data: bytes, target: tuple) -> tuple:
|
||||
"""Resize a GIF image."""
|
||||
# generate a temporary file to call gifsticle to and from.
|
||||
input_fd, input_path = tempfile.mkstemp(suffix='.gif')
|
||||
_, output_path = tempfile.mkstemp(suffix='.gif')
|
||||
|
||||
input_handler = os.fdopen(input_fd, 'wb')
|
||||
|
||||
# make sure its valid image data
|
||||
data_fd = BytesIO(raw_data)
|
||||
image = Image.open(data_fd)
|
||||
image.close()
|
||||
|
||||
log.info('resizing a GIF from {} to {}',
|
||||
image.size, target)
|
||||
|
||||
# insert image info on input_handler
|
||||
# close it to make it ready for consumption by gifsicle
|
||||
input_handler.write(raw_data)
|
||||
input_handler.close()
|
||||
|
||||
# call gifsicle under subprocess
|
||||
log.debug('input: {}', input_path)
|
||||
log.debug('output: {}', output_path)
|
||||
|
||||
process = await asyncio.create_subprocess_shell(
|
||||
f'gifsicle --resize {target[0]}x{target[1]} '
|
||||
f'{input_path} > {output_path}',
|
||||
stdout=asyncio.subprocess.PIPE,
|
||||
stderr=asyncio.subprocess.PIPE,
|
||||
)
|
||||
|
||||
# run it, etc.
|
||||
await process.communicate()
|
||||
|
||||
# write over an empty data_fd
|
||||
data_fd = BytesIO()
|
||||
output_handler = open(output_path, 'rb')
|
||||
data_fd.write(output_handler.read())
|
||||
|
||||
# close unused handlers
|
||||
output_handler.close()
|
||||
|
||||
# delete the files we created with mkstemp
|
||||
_try_unlink(input_path)
|
||||
_try_unlink(output_path)
|
||||
|
||||
# reseek, save to raw_data, reseek again.
|
||||
# TODO: remove raw_data altogether as its inefficient
|
||||
# to have two representations of the same bytes
|
||||
data_fd.seek(0)
|
||||
raw_data = data_fd.read()
|
||||
data_fd.seek(0)
|
||||
|
||||
|
||||
return data_fd, raw_data
|
||||
|
||||
|
||||
async def put(self, scope: str, key: str,
|
||||
b64_data: str, **kwargs) -> Icon:
|
||||
"""Insert an icon."""
|
||||
|
|
@ -336,8 +336,7 @@ class IconManager:
|
|||
# size management is different for gif files
|
||||
# as they're composed of multiple frames.
|
||||
if 'size' in kwargs and mime == 'image/gif':
|
||||
data_fd, raw_data = await self._resize_gif(
|
||||
scope, key, raw_data, kwargs['size'])
|
||||
data_fd, raw_data = await resize_gif(raw_data, kwargs['size'])
|
||||
elif 'size' in kwargs:
|
||||
image = Image.open(data_fd)
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue