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 quart import Blueprint, send_file, current_app as app, request
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
|
|
||||||
|
from litecord.images import resize_gif
|
||||||
|
|
||||||
bp = Blueprint('attachments', __name__)
|
bp = Blueprint('attachments', __name__)
|
||||||
ATTACHMENTS = Path.cwd() / 'attachments'
|
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:
|
width: int, height: int) -> str:
|
||||||
"""Resize an image."""
|
"""Resize an image."""
|
||||||
# TODO: gif support
|
|
||||||
|
|
||||||
# check if we have it on the folder
|
# check if we have it on the folder
|
||||||
resized_path = ATTACHMENTS / f'{attach_id}_{width}_{height}.{ext}'
|
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
|
# if we dont, we need to generate it off the
|
||||||
# given image instance.
|
# 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.
|
# NOTE: this is the same resize mode for icons.
|
||||||
resized = image.resize((width, height), resample=Image.LANCZOS)
|
resized = image.resize((width, height), resample=Image.LANCZOS)
|
||||||
resized.save(resized_path_s, format=ext)
|
resized.save(resized_path_s, format=ext)
|
||||||
|
|
|
||||||
|
|
@ -194,6 +194,65 @@ def _try_unlink(path: str):
|
||||||
pass
|
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:
|
class IconManager:
|
||||||
"""Main icon manager."""
|
"""Main icon manager."""
|
||||||
def __init__(self, app):
|
def __init__(self, app):
|
||||||
|
|
@ -257,65 +316,6 @@ class IconManager:
|
||||||
return await self.generic_get(
|
return await self.generic_get(
|
||||||
'guild', guild_id, icon_hash, **kwargs)
|
'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,
|
async def put(self, scope: str, key: str,
|
||||||
b64_data: str, **kwargs) -> Icon:
|
b64_data: str, **kwargs) -> Icon:
|
||||||
"""Insert an icon."""
|
"""Insert an icon."""
|
||||||
|
|
@ -336,8 +336,7 @@ class IconManager:
|
||||||
# size management is different for gif files
|
# size management is different for gif files
|
||||||
# as they're composed of multiple frames.
|
# as they're composed of multiple frames.
|
||||||
if 'size' in kwargs and mime == 'image/gif':
|
if 'size' in kwargs and mime == 'image/gif':
|
||||||
data_fd, raw_data = await self._resize_gif(
|
data_fd, raw_data = await resize_gif(raw_data, kwargs['size'])
|
||||||
scope, key, raw_data, kwargs['size'])
|
|
||||||
elif 'size' in kwargs:
|
elif 'size' in kwargs:
|
||||||
image = Image.open(data_fd)
|
image = Image.open(data_fd)
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue