changeset b6ea6f5bdd96 in /home/hg/repos/gajim-plugins
author: Linus Heckemann <[email protected]>
branches: gtk3
details:gajim-plugins?cmd=changeset;node=b6ea6f5bdd96
description: httpupload: thumbnailing improvements
- Move thumbnailing code into separate module
- Port GdkPixbuf thumbnailer to gtk3
- Make GdkPixbuf thumbnailer more efficient
- Better exception logging
- Some code reformatting
diffstat:
httpupload/httpupload.py | 125 ++++++++--------------------------------------
httpupload/thumbnail.py | 90 +++++++++++++++++++++++++++++++++
2 files changed, 112 insertions(+), 103 deletions(-)
diffs (truncated from 304 to 300 lines):
diff -r 748cc7c1cb4e -r b6ea6f5bdd96 httpupload/httpupload.py
--- a/httpupload/httpupload.py Thu Aug 18 20:00:57 2016 +0200
+++ b/httpupload/httpupload.py Sat Aug 20 00:09:14 2016 +0200
@@ -4,20 +4,11 @@
from gi.repository import GObject, Gtk
import os
import time
-import base64
-import tempfile
from urllib.request import Request, urlopen
-from urllib.parse import quote as urlquote
import mimetypes # better use the magic packet, but that's not a
standard lib
import gtkgui_helpers
+import logging
from queue import Queue
-try:
- from PIL import Image
- pil_available = True
-except:
- pil_available = False
-from io import BytesIO
-import base64
import binascii
from common import gajim
@@ -25,10 +16,11 @@
import chat_control
from plugins import GajimPlugin
from plugins.helpers import log_calls
-import logging
from dialogs import FileChooserDialog, ImageChooserDialog, ErrorDialog
import nbxmpp
+from .thumbnail import thumbnail
+
log = logging.getLogger('gajim.plugin_system.httpupload')
if os.name != 'nt':
@@ -52,8 +44,6 @@
jid_to_servers = {}
iq_ids_to_callbacks = {}
last_info_query = {}
-max_thumbnail_size = 2048
-max_thumbnail_dimension = 160
class HttpuploadPlugin(GajimPlugin):
@@ -125,10 +115,11 @@
#pass
# query info at most every 60 seconds in case something goes wrong
- if (not chat_control.account in last_info_query or \
- last_info_query[chat_control.account] + 60 < time.time()) and \
- not gajim.get_jid_from_account(chat_control.account) in
jid_to_servers and \
- gajim.account_is_connected(chat_control.account):
+ if ((not chat_control.account in last_info_query or
+ last_info_query[chat_control.account] + 60 < time.time())
+ and not gajim.get_jid_from_account(chat_control.account) in
jid_to_servers
+ and gajim.account_is_connected(chat_control.account)
+ ):
log.info("Account %s: Using dicovery to find jid of httpupload
component" % chat_control.account)
id_ = gajim.get_an_id()
iq = nbxmpp.Iq(
@@ -289,11 +280,11 @@
progress_window.close_dialog()
error = stanza.getTag("error")
if error and error.getTag("text"):
- ErrorDialog(_('Could not request upload slot'),
+ ErrorDialog(_('Could not request upload slot'),
_('Got unexpected response from server: %s') %
str(error.getTagData("text")),
transient_for=self.chat_control.parent_win.window)
else:
- ErrorDialog(_('Could not request upload slot'),
+ ErrorDialog(_('Could not request upload slot'),
_('Got unexpected response from server
(protocol mismatch??)'),
transient_for=self.chat_control.parent_win.window)
return
@@ -313,7 +304,7 @@
except:
log.error("Could not open file")
progress_window.close_dialog()
- ErrorDialog(_('Could not open file'),
+ ErrorDialog(_('Could not open file'),
_('Exception raised while opening file (see error
log for more information)'),
transient_for=self.chat_control.parent_win.window)
raise # fill error log with useful information
@@ -323,7 +314,7 @@
if not put or not get:
log.error("got unexpected stanza: " + str(stanza))
progress_window.close_dialog()
- ErrorDialog(_('Could not request upload slot'),
+ ErrorDialog(_('Could not request upload slot'),
_('Got unexpected response from server (protocol
mismatch??)'),
transient_for=self.chat_control.parent_win.window)
return
@@ -335,69 +326,17 @@
log.info("Upload completed successfully")
xhtml = None
is_image = mime_type.split('/', 1)[0] == 'image'
- if (not isinstance(self.chat_control,
chat_control.ChatControl) or not self.chat_control.gpg_is_active) and \
- self.dialog_type == 'image' and is_image and not
self.encrypted_upload:
-
+ if ((not isinstance(self.chat_control,
chat_control.ChatControl)
+ or not self.chat_control.gpg_is_active)
+ and self.dialog_type == 'image'
+ and is_image
+ and not self.encrypted_upload
+ ):
progress_messages.put(_('Calculating (possible) image
thumbnail...'))
- thumb = None
- quality_steps = (100, 80, 60, 50, 40, 35, 30, 25, 23,
20, 18, 15, 13, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1)
- with open(path_to_file, 'rb') as content_file:
- thumb =
urlquote(base64.standard_b64encode(content_file.read()), '')
- if thumb and len(thumb) < max_thumbnail_size:
- quality = 100
- log.info("Image small enough (%d bytes), not
resampling" % len(thumb))
- elif pil_available:
- log.info("PIL available, using it for image
downsampling")
- try:
- for quality in quality_steps:
- thumb = Image.open(path_to_file)
- thumb.thumbnail((max_thumbnail_dimension,
max_thumbnail_dimension), Image.ANTIALIAS)
- output = BytesIO()
- thumb.save(output, format='JPEG',
quality=quality, optimize=True)
- thumb = output.getvalue()
- output.close()
- thumb =
urlquote(base64.standard_b64encode(thumb), '')
- log.debug("pil thumbnail jpeg quality %d
produces an image of size %d..." % (quality, len(thumb)))
- if len(thumb) < max_thumbnail_size:
- break
- except:
- thumb = None
- else:
- thumb = None
- if not thumb:
- log.info("PIL not available, using GTK for image
downsampling")
- temp_file = None
- try:
- with open(path_to_file, 'rb') as content_file:
- thumb = content_file.read()
- loader = Gtk.gdk.PixbufLoader()
- loader.write(thumb)
- loader.close()
- pixbuf = loader.get_pixbuf()
- scaled_pb = self.get_pixbuf_of_size(pixbuf,
max_thumbnail_dimension)
- handle, temp_file =
tempfile.mkstemp(suffix='.jpeg', prefix='gajim_httpupload_scaled_tmp',
dir=gajim.TMP)
- log.debug("Saving temporary jpeg image to
'%s'..." % temp_file)
- os.close(handle)
- for quality in quality_steps:
- scaled_pb.save(temp_file, "jpeg",
{"quality": str(quality)})
- with open(temp_file, 'rb') as content_file:
- thumb = content_file.read()
- thumb =
urlquote(base64.standard_b64encode(thumb), '')
- log.debug("gtk thumbnail jpeg quality %d
produces an image of size %d..." % (quality, len(thumb)))
- if len(thumb) < max_thumbnail_size:
- break
- except:
- thumb = None
- finally:
- if temp_file:
- os.unlink(temp_file)
+ thumb = thumbnail(path_to_file)
if thumb:
- if len(thumb) > max_thumbnail_size:
- log.info("Couldn't compress image enough, not
sending any thumbnail")
- else:
- log.info("Using thumbnail jpeg quality %d
(image size: %d bytes)" % (quality, len(thumb)))
- xhtml = '<body><br/><a href="%s"> <img
alt="%s" src="data:image/png;base64,%s"/> </a></body>' % \
- (get.getData(), get.getData(), thumb)
+ xhtml = '<body><br/><a href="%s"><img alt="%s"
src="data:image/jpeg;base64,%s"/></a></body>' % \
+ (get.getData(), get.getData(), thumb)
progress_window.close_dialog()
id_ = gajim.get_an_id()
def add_oob_tag():
@@ -414,7 +353,7 @@
ErrorDialog(_('Could not upload file'),
_('Got unexpected http response code from
server: ') + str(response_code),
transient_for=self.chat_control.parent_win.window)
-
+
def uploader():
progress_messages.put(_('Uploading file via HTTP...'))
try:
@@ -495,26 +434,6 @@
self.dialog_type = 'image'
self.dlg = ImageChooserDialog(on_response_ok=self.on_file_dialog_ok,
on_response_cancel=None)
- def get_pixbuf_of_size(self, pixbuf, size):
- # Creates a pixbuf that fits in the specified square of sizexsize
- # while preserving the aspect ratio
- # Returns scaled_pixbuf
- image_width = pixbuf.get_width()
- image_height = pixbuf.get_height()
-
- if image_width > image_height:
- if image_width > size:
- image_height = int(size / float(image_width) * image_height)
- image_width = int(size)
- else:
- if image_height > size:
- image_width = int(size / float(image_height) * image_width)
- image_height = int(size)
-
- crop_pixbuf = pixbuf.scale_simple(image_width, image_height,
- Gtk.gdk.INTERP_BILINEAR)
- return crop_pixbuf
-
class StreamFileWithProgress:
def __init__(self, path, mode, callback=None,
diff -r 748cc7c1cb4e -r b6ea6f5bdd96 httpupload/thumbnail.py
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/httpupload/thumbnail.py Sat Aug 20 00:09:14 2016 +0200
@@ -0,0 +1,90 @@
+from gi.repository import GdkPixbuf
+import base64
+from io import BytesIO
+import os
+import sys
+import logging
+from urllib.parse import quote as urlquote
+try:
+ from PIL import Image
+ pil_available = True
+except:
+ pil_available = False
+
+log = logging.getLogger('gajim.plugin_system.httpupload.thumbnail')
+
+def scale_down_to(pixbuf, size):
+ # Creates a pixbuf that fits in the specified square of sizexsize
+ # while preserving the aspect ratio
+ # Returns scaled_pixbuf
+ image_width = pixbuf.get_width()
+ image_height = pixbuf.get_height()
+
+ if image_width > image_height:
+ if image_width > size:
+ image_height = int(size / float(image_width) * image_height)
+ image_width = int(size)
+ else:
+ if image_height > size:
+ image_width = int(size / float(image_height) * image_width)
+ image_height = int(size)
+
+ crop_pixbuf = pixbuf.scale_simple(image_width, image_height,
GdkPixbuf.InterpType.BILINEAR)
+ return crop_pixbuf
+
+
+max_thumbnail_size = 2048
+max_thumbnail_dimension = 160
+base64_size_factor = 4/3
+def thumbnail(path_to_file):
+ """
+ Generates a JPEG thumbnail and base64-encodes, ensuring that the encoded
+ size is less than max_thumbnail_size bytes. If this is not possible,
returns
+ None.
+ """
+ thumb = None
+ quality_steps = (100, 80, 60, 50, 40, 35, 30, 25, 23, 20, 18, 15, 13, 10,
9, 8, 7, 6, 5, 4, 3, 2, 1)
+ # If the whole file is small enough, we'll just use that as a thumbnail
+ # without downsampling.
+ if os.path.getsize(path_to_file) * base64_size_factor < max_thumbnail_size:
+ with open(path_to_file, 'rb') as content_file:
+ thumb = urlquote(base64.standard_b64encode(content_file.read()),
'')
+ log.info("Image small enough (%d bytes), not resampling" %
len(thumb))
+ return thumb
+ elif pil_available:
+ log.info("PIL available, using it for image downsampling")
+ try:
+ for quality in quality_steps:
+ thumb = Image.open(path_to_file)
+ thumb.thumbnail((max_thumbnail_dimension,
max_thumbnail_dimension), Image.ANTIALIAS)
+ output = BytesIO()
+ thumb.save(output, format='JPEG', quality=quality,
optimize=True)
+ thumb = output.getvalue()
+ output.close()
+ thumb = urlquote(base64.standard_b64encode(thumb), '')
+ log.debug("pil thumbnail jpeg quality %d produces an image of
size %d...", quality, len(thumb))
+ if len(thumb) < max_thumbnail_size:
+ log.debug("Size is acceptable.")
+ return thumb
+ except:
+ log.info("Exception occurred during PIL downsampling",
exc_info=sys.exc_info())
+ thumb = None
+ # If we haven't returned by now we couldn't use PIL for one reason or
+ # another, so let's pass on to GdkPixbuf
+ log.info("using GdkPixBuf for image downsampling")
+ temp_file = None
+ try:
+ pixbuf = GdkPixbuf.Pixbuf.new_from_file(path_to_file)
+ scaled_pb = scale_down_to(pixbuf, max_thumbnail_dimension)
+ for quality in quality_steps:
+ success, thumb_raw = scaled_pb.save_to_bufferv("jpeg",
["quality"], [str(quality)])
+ log.debug("gdkpixbuf thumbnail jpeg quality %d produces an image
of size %d...",
+ quality,
+ len(thumb_raw) * base64_size_factor)
+ if len(thumb_raw) * base64_size_factor < max_thumbnail_size:
+ log.debug("Size is acceptable.")
+ return urlquote(base64.standard_b64encode(thumb_raw))
_______________________________________________
Commits mailing list
[email protected]
https://lists.gajim.org/cgi-bin/listinfo/commits