core: added imageCheck and imageResize methods:
imageCheck will give a report on image, notably it will tell if it's too big and needs to be resized before a transfer. imageResize will create a new image with the requested size and return a path to it.
This commit is contained in:
parent
fb2dc9b7a0
commit
93a4e488ea
7 changed files with 197 additions and 0 deletions
|
|
@ -909,3 +909,24 @@ sig_in=
|
|||
sig_out=a{ss}
|
||||
doc=Get a dict to short name => whole namespaces
|
||||
doc_return=namespaces mapping
|
||||
|
||||
[imageCheck]
|
||||
type=method
|
||||
category=core
|
||||
sig_in=s
|
||||
sig_out=s
|
||||
doc=Analyze an image a return a report
|
||||
doc_return=serialized report
|
||||
|
||||
[imageResize]
|
||||
async=
|
||||
type=method
|
||||
category=core
|
||||
sig_in=sii
|
||||
sig_out=s
|
||||
doc=Create a new image with desired size
|
||||
doc_param_0=image_path: path of the image to resize
|
||||
doc_param_1=width: width of the new image
|
||||
doc_param_2=height: height of the new image
|
||||
doc_return=path of the new image with desired size
|
||||
the image must be deleted once not needed anymore
|
||||
|
|
|
|||
|
|
@ -384,6 +384,18 @@ class DbusObject(dbus.service.Object):
|
|||
def historyGet(self, from_jid, to_jid, limit, between=True, filters='', profile="@NONE@", callback=None, errback=None):
|
||||
return self._callback("historyGet", str(from_jid), str(to_jid), limit, between, filters, str(profile), callback=callback, errback=errback)
|
||||
|
||||
@dbus.service.method(const_INT_PREFIX+const_CORE_SUFFIX,
|
||||
in_signature='s', out_signature='s',
|
||||
async_callbacks=None)
|
||||
def imageCheck(self, arg_0):
|
||||
return self._callback("imageCheck", str(arg_0))
|
||||
|
||||
@dbus.service.method(const_INT_PREFIX+const_CORE_SUFFIX,
|
||||
in_signature='sii', out_signature='s',
|
||||
async_callbacks=('callback', 'errback'))
|
||||
def imageResize(self, image_path, width, height, callback=None, errback=None):
|
||||
return self._callback("imageResize", str(image_path), width, height, callback=callback, errback=errback)
|
||||
|
||||
@dbus.service.method(const_INT_PREFIX+const_CORE_SUFFIX,
|
||||
in_signature='s', out_signature='b',
|
||||
async_callbacks=None)
|
||||
|
|
|
|||
|
|
@ -361,6 +361,13 @@ class Const(object):
|
|||
# internationalisation
|
||||
DEFAULT_LOCALE = "en_GB"
|
||||
|
||||
# Contexts
|
||||
# context indicate how things are done (e.g. to who/what a file is transfered)
|
||||
# this is useful to make some decision (e.g. image size)
|
||||
|
||||
# something is done in instant messaging
|
||||
CONTEXT_CHAT = "CONTEXT_CHAT"
|
||||
|
||||
## Misc ##
|
||||
SAVEFILE_DATABASE = APP_NAME_FILE + ".db"
|
||||
IQ_SET = '/iq[@type="set"]'
|
||||
|
|
|
|||
|
|
@ -40,8 +40,10 @@ from sat.memory import cache
|
|||
from sat.memory import encryption
|
||||
from sat.tools import async_trigger as trigger
|
||||
from sat.tools import utils
|
||||
from sat.tools import images
|
||||
from sat.tools.common import dynamic_import
|
||||
from sat.tools.common import regex
|
||||
from sat.tools.common import data_format
|
||||
from sat.stdui import ui_contact_list, ui_profile_manager
|
||||
import sat.plugins
|
||||
|
||||
|
|
@ -161,6 +163,8 @@ class SAT(service.Service):
|
|||
self.bridge.register_method("loadParamsTemplate", self.memory.load_xml)
|
||||
self.bridge.register_method("sessionInfosGet", self.getSessionInfos)
|
||||
self.bridge.register_method("namespacesGet", self.getNamespaces)
|
||||
self.bridge.register_method("imageCheck", self._imageCheck)
|
||||
self.bridge.register_method("imageResize", self._imageResize)
|
||||
|
||||
self.memory.initialized.addCallback(self._postMemoryInit)
|
||||
|
||||
|
|
@ -632,6 +636,17 @@ class SAT(service.Service):
|
|||
}
|
||||
return defer.succeed(data)
|
||||
|
||||
# images
|
||||
|
||||
def _imageCheck(self, path):
|
||||
report = images.checkImage(self, path)
|
||||
return data_format.serialise(report)
|
||||
|
||||
def _imageResize(self, path, width, height):
|
||||
d = images.resizeImage(path, (width, height))
|
||||
d.addCallback(lambda new_image_path: str(new_image_path))
|
||||
return d
|
||||
|
||||
# local dirs
|
||||
|
||||
def getLocalPath(self, client, dir_name, *extra_path, **kwargs):
|
||||
|
|
|
|||
77
sat/tools/images.py
Normal file
77
sat/tools/images.py
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# SAT: a jabber client
|
||||
# Copyright (C) 2009-2019 Jérôme Poisson (goffi@goffi.org)
|
||||
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
"""Methods to manipulate images"""
|
||||
|
||||
import tempfile
|
||||
from PIL import Image
|
||||
from sat.core.constants import Const as C
|
||||
from pathlib import Path
|
||||
from twisted.internet import threads
|
||||
|
||||
|
||||
def checkImage(host, path, context=C.CONTEXT_CHAT):
|
||||
"""Analyze image and return a report
|
||||
|
||||
report will indicate if image is too large, and the recommended new size if this is
|
||||
the case
|
||||
@param host: SàT instance
|
||||
@param path(str, pathlib.Path): image to open
|
||||
@param context(str): context in which the image is transfered
|
||||
@return dict: report on image, with following keys:
|
||||
- too_large: true if image is oversized
|
||||
- recommended_size: if too_large is True, recommended size to use
|
||||
"""
|
||||
# TODO: context is not used yet
|
||||
report = {}
|
||||
image = Image.open(path)
|
||||
max_size = tuple(host.memory.getConfig(None, "image_max", (1200, 720)))
|
||||
if image.size > max_size:
|
||||
report['too_large'] = True
|
||||
if image.size[0] > max_size[0]:
|
||||
factor = max_size[0] / image.size[0]
|
||||
if image.size[1] * factor > max_size[1]:
|
||||
factor = max_size[1] / image.size[1]
|
||||
else:
|
||||
factor = max_size[1] / image.size[1]
|
||||
report['recommended_size'] = [image.width*factor, image.height*factor]
|
||||
else:
|
||||
report['too_large'] = False
|
||||
|
||||
return report
|
||||
|
||||
|
||||
def _resizeImageBlocking(image_path, new_size):
|
||||
im_path = Path(image_path)
|
||||
im = Image.open(im_path)
|
||||
resized = im.resize(new_size, Image.LANCZOS)
|
||||
with tempfile.NamedTemporaryFile(suffix=im_path.suffix, delete=False) as f:
|
||||
resized.save(f, format=im.format)
|
||||
return Path(f.name)
|
||||
|
||||
|
||||
def resizeImage(image_path, new_size):
|
||||
"""Resize an image to a new temporary file, and return it path
|
||||
|
||||
@param image_path(str, Path): path of the original image
|
||||
@param new_size(tuple[int, int]): size to use for new image
|
||||
@return (Path): path of the resized file. The image at this path must be deleted
|
||||
after use
|
||||
"""
|
||||
return threads.deferToThread(_resizeImageBlocking, image_path, new_size)
|
||||
|
|
@ -469,6 +469,29 @@ class Bridge:
|
|||
error_handler = lambda err:errback(dbus_to_bridge_exception(err))
|
||||
return self.db_core_iface.historyGet(from_jid, to_jid, limit, between, filters, profile, timeout=const_TIMEOUT, reply_handler=callback, error_handler=error_handler)
|
||||
|
||||
def imageCheck(self, arg_0, callback=None, errback=None):
|
||||
if callback is None:
|
||||
error_handler = None
|
||||
else:
|
||||
if errback is None:
|
||||
errback = log.error
|
||||
error_handler = lambda err:errback(dbus_to_bridge_exception(err))
|
||||
kwargs={}
|
||||
if callback is not None:
|
||||
kwargs['timeout'] = const_TIMEOUT
|
||||
kwargs['reply_handler'] = callback
|
||||
kwargs['error_handler'] = error_handler
|
||||
return str(self.db_core_iface.imageCheck(arg_0, **kwargs))
|
||||
|
||||
def imageResize(self, image_path, width, height, callback=None, errback=None):
|
||||
if callback is None:
|
||||
error_handler = None
|
||||
else:
|
||||
if errback is None:
|
||||
errback = log.error
|
||||
error_handler = lambda err:errback(dbus_to_bridge_exception(err))
|
||||
return str(self.db_core_iface.imageResize(image_path, width, height, timeout=const_TIMEOUT, reply_handler=callback, error_handler=error_handler))
|
||||
|
||||
def isConnected(self, profile_key="@DEFAULT@", callback=None, errback=None):
|
||||
if callback is None:
|
||||
error_handler = None
|
||||
|
|
@ -1085,6 +1108,22 @@ class AIOBridge(Bridge):
|
|||
self.db_core_iface.historyGet(from_jid, to_jid, limit, between, filters, profile, timeout=const_TIMEOUT, reply_handler=reply_handler, error_handler=error_handler)
|
||||
return fut
|
||||
|
||||
def imageCheck(self, arg_0):
|
||||
loop = asyncio.get_running_loop()
|
||||
fut = loop.create_future()
|
||||
reply_handler = lambda ret=None: loop.call_soon_threadsafe(fut.set_result, ret)
|
||||
error_handler = lambda err: loop.call_soon_threadsafe(fut.set_exception, dbus_to_bridge_exception(err))
|
||||
self.db_core_iface.imageCheck(arg_0, timeout=const_TIMEOUT, reply_handler=reply_handler, error_handler=error_handler)
|
||||
return fut
|
||||
|
||||
def imageResize(self, image_path, width, height):
|
||||
loop = asyncio.get_running_loop()
|
||||
fut = loop.create_future()
|
||||
reply_handler = lambda ret=None: loop.call_soon_threadsafe(fut.set_result, ret)
|
||||
error_handler = lambda err: loop.call_soon_threadsafe(fut.set_exception, dbus_to_bridge_exception(err))
|
||||
self.db_core_iface.imageResize(image_path, width, height, timeout=const_TIMEOUT, reply_handler=reply_handler, error_handler=error_handler)
|
||||
return fut
|
||||
|
||||
def isConnected(self, profile_key="@DEFAULT@"):
|
||||
loop = asyncio.get_running_loop()
|
||||
fut = loop.create_future()
|
||||
|
|
|
|||
|
|
@ -375,6 +375,22 @@ class Bridge(object):
|
|||
errback = self._generic_errback
|
||||
d.addErrback(errback)
|
||||
|
||||
def imageCheck(self, arg_0, callback=None, errback=None):
|
||||
d = self.root.callRemote("imageCheck", arg_0)
|
||||
if callback is not None:
|
||||
d.addCallback(callback)
|
||||
if errback is None:
|
||||
errback = self._generic_errback
|
||||
d.addErrback(errback)
|
||||
|
||||
def imageResize(self, image_path, width, height, callback=None, errback=None):
|
||||
d = self.root.callRemote("imageResize", image_path, width, height)
|
||||
if callback is not None:
|
||||
d.addCallback(callback)
|
||||
if errback is None:
|
||||
errback = self._generic_errback
|
||||
d.addErrback(errback)
|
||||
|
||||
def isConnected(self, profile_key="@DEFAULT@", callback=None, errback=None):
|
||||
d = self.root.callRemote("isConnected", profile_key)
|
||||
if callback is not None:
|
||||
|
|
@ -773,6 +789,16 @@ class AIOBridge(Bridge):
|
|||
d.addErrback(self._errback)
|
||||
return d.asFuture(asyncio.get_event_loop())
|
||||
|
||||
def imageCheck(self, arg_0):
|
||||
d = self.root.callRemote("imageCheck", arg_0)
|
||||
d.addErrback(self._errback)
|
||||
return d.asFuture(asyncio.get_event_loop())
|
||||
|
||||
def imageResize(self, image_path, width, height):
|
||||
d = self.root.callRemote("imageResize", image_path, width, height)
|
||||
d.addErrback(self._errback)
|
||||
return d.asFuture(asyncio.get_event_loop())
|
||||
|
||||
def isConnected(self, profile_key="@DEFAULT@"):
|
||||
d = self.root.callRemote("isConnected", profile_key)
|
||||
d.addErrback(self._errback)
|
||||
|
|
|
|||
Loading…
Reference in a new issue