comparison src/plugins/plugin_xep_0264.py @ 2513:2d3c9dcec384

plugin XEP-0264: thumbnails handling implementation
author Goffi <goffi@goffi.org>
date Fri, 02 Mar 2018 17:53:31 +0100
parents
children 327bbbe793ce
comparison
equal deleted inserted replaced
2512:025afb04c10b 2513:2d3c9dcec384
1 #!/usr/bin/env python2
2 # -*- coding: utf-8 -*-
3
4 # SAT plugin for managing xep-0264
5 # Copyright (C) 2009-2018 Jérôme Poisson (goffi@goffi.org)
6 # Copyright (C) 2014 Emmanuel Gil Peyrot (linkmauve@linkmauve.fr)
7
8 # This program is free software: you can redistribute it and/or modify
9 # it under the terms of the GNU Affero General Public License as published by
10 # the Free Software Foundation, either version 3 of the License, or
11 # (at your option) any later version.
12
13 # This program is distributed in the hope that it will be useful,
14 # but WITHOUT ANY WARRANTY; without even the implied warranty of
15 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 # GNU Affero General Public License for more details.
17
18 # You should have received a copy of the GNU Affero General Public License
19 # along with this program. If not, see <http://www.gnu.org/licenses/>.
20
21 from sat.core.i18n import _
22 from sat.core.constants import Const as C
23 from sat.core.log import getLogger
24 log = getLogger(__name__)
25 from twisted.internet import threads
26 from twisted.python.failure import Failure
27
28 from zope.interface import implements
29
30 from wokkel import disco, iwokkel
31
32 from sat.core import exceptions
33 import hashlib
34 try:
35 from PIL import Image
36 except:
37 raise exceptions.MissingModule(u"Missing module pillow, please download/install it from https://python-pillow.github.io")
38
39 try:
40 from twisted.words.protocols.xmlstream import XMPPHandler
41 except ImportError:
42 from wokkel.subprotocols import XMPPHandler
43
44
45 MIME_TYPE = u'image/jpeg'
46 SAVE_FORMAT = u'JPEG' # (cf. Pillow documentation)
47
48 NS_THUMBS = 'urn:xmpp:thumbs:1'
49
50 PLUGIN_INFO = {
51 C.PI_NAME: "XEP-0264",
52 C.PI_IMPORT_NAME: "XEP-0264",
53 C.PI_TYPE: "XEP",
54 C.PI_MODES: C.PLUG_MODE_BOTH,
55 C.PI_PROTOCOLS: ["XEP-0264"],
56 C.PI_DEPENDENCIES: ["XEP-0234"],
57 C.PI_MAIN: "XEP_0264",
58 C.PI_HANDLER: "yes",
59 C.PI_DESCRIPTION: _("""Thumbnails handling""")
60 }
61
62
63 class XEP_0264(object):
64 SIZE_SMALL = (250, 250)
65 SIZE_MEDIUM = (1024, 1024)
66
67 def __init__(self, host):
68 log.info(_(u"Plugin XEP_0264 initialization"))
69 self.host = host
70 host.trigger.add("XEP-0234_buildFileElement", self._addFileThumbnails)
71 host.trigger.add("XEP-0234_parseFileElement", self._getFileThumbnails)
72
73 def getHandler(self, client):
74 return XEP_0264_handler()
75
76 ## triggers ##
77
78 def _addFileThumbnails(self, file_elt, extra_args):
79 try:
80 thumbnails = extra_args[u'extra'][C.KEY_THUMBNAILS]
81 except KeyError:
82 return
83 for thumbnail in thumbnails:
84 thumbnail_elt = file_elt.addElement((NS_THUMBS, u'thumbnail'))
85 thumbnail_elt['uri'] = u'cid:' + thumbnail['id']
86 thumbnail_elt['media-type'] = MIME_TYPE
87 width, height = thumbnail['size']
88 thumbnail_elt['width'] = unicode(width)
89 thumbnail_elt['height'] = unicode(height)
90
91 def _getFileThumbnails(self, file_elt, file_data):
92 thumbnails = []
93 for thumbnail_elt in file_elt.elements(NS_THUMBS, u'thumbnail'):
94 uri = thumbnail_elt['uri']
95 if uri.startswith ('cid:'):
96 thumbnail = {'id': uri[4:]}
97 width = thumbnail_elt.getAttribute('width')
98 height = thumbnail_elt.getAttribute('height')
99 if width and height:
100 try:
101 thumbnail['size'] = int(width), int(height)
102 except ValueError:
103 pass
104 try:
105 thumbnail['mime_type'] = thumbnail_elt['media-type']
106 except KeyError:
107 pass
108 thumbnails.append(thumbnail)
109
110 if thumbnails:
111 file_data.setdefault('extra', {})[C.KEY_THUMBNAILS] = thumbnails
112
113 ## thumbnails generation ##
114
115 def getThumbId(self, image_uid, size):
116 """return an ID unique for image/size combination
117
118 @param image_uid(unicode): unique id of the image
119 can be a hash
120 @param size(tuple(int)): requested size of thumbnail
121 @return (unicode): unique id for this image/size
122 """
123 return hashlib.sha256(repr((image_uid, size))).hexdigest()
124
125 def _blockingGenThumb(self, source_path, size=None, max_age=None, image_uid=None):
126 """Generate a thumbnail for image
127
128 This is a blocking method and must be executed in a thread
129 params are the same as for [generateThumbnail]
130 """
131 if size is None:
132 size = self.SIZE_SMALL
133 try:
134 img = Image.open(source_path)
135 except IOError:
136 return Failure(exceptions.DataError(u"Can't open image"))
137
138 img.thumbnail(size)
139 uid = self.getThumbId(image_uid or source_path, size)
140
141 with self.host.common_cache.cacheData(
142 PLUGIN_INFO[C.PI_IMPORT_NAME],
143 uid,
144 MIME_TYPE,
145 max_age,
146 ) as f:
147 img.save(f, SAVE_FORMAT)
148
149 return img.size, uid
150
151 def generateThumbnail(self, source_path, size=None, max_age=None, image_uid=None):
152 """Generate a thumbnail of image
153
154 @param source_path(unicode): absolute path to source image
155 @param size(int, None): max size of the thumbnail
156 can be one of self.SIZE_*
157 None to use default value (i.e. self.SIZE_SMALL)
158 @param max_age(int, None): same as for [memory.cache.Cache.cacheData])
159 @param image_uid(unicode, None): unique ID to identify the image
160 use hash whenever possible
161 if None, source_path will be used
162 @return D(tuple[tuple[int,int], unicode]): tuple with:
163 - size of the thumbnail
164 - unique Id of the thumbnail
165 """
166
167 d = threads.deferToThread(
168 self._blockingGenThumb, source_path, size, max_age, image_uid=image_uid)
169 d.addErrback(lambda failure_:
170 log.error(u"thumbnail generation error: {}".format(failure_)))
171 return d
172
173
174 class XEP_0264_handler(XMPPHandler):
175 implements(iwokkel.IDisco)
176
177 def getDiscoInfo(self, requestor, target, nodeIdentifier=''):
178 return [disco.DiscoFeature(NS_THUMBS)]
179
180 def getDiscoItems(self, requestor, target, nodeIdentifier=''):
181 return []