|
@@ -1,284 +0,0 @@
|
1
|
|
-from django.conf import settings
|
2
|
|
-import fstools
|
3
|
|
-import io
|
4
|
|
-import logging
|
5
|
|
-import mimetypes
|
6
|
|
-from ..models import get_item_type, TYPE_IMAGE, TYPE_VIDEO
|
7
|
|
-import os
|
8
|
|
-from PIL import Image, ImageEnhance, ExifTags
|
9
|
|
-import platform
|
10
|
|
-import pygal
|
11
|
|
-import subprocess
|
12
|
|
-
|
13
|
|
-# Get a logger instance
|
14
|
|
-logger = logging.getLogger("CACHING")
|
15
|
|
-
|
16
|
|
-
|
17
|
|
-def get_image_class(full_path):
|
18
|
|
- return {
|
19
|
|
- TYPE_IMAGE: image,
|
20
|
|
- TYPE_VIDEO: video,
|
21
|
|
- }.get(get_item_type(full_path), other)
|
22
|
|
-
|
23
|
|
-
|
24
|
|
-def get_image_instance(full_path, request):
|
25
|
|
- return get_image_class(full_path)(full_path, request)
|
26
|
|
-
|
27
|
|
-
|
28
|
|
-class mm_image(object):
|
29
|
|
- JOIN_TOP_LEFT = 1
|
30
|
|
- JOIN_TOP_RIGHT = 2
|
31
|
|
- JOIN_BOT_LEFT = 3
|
32
|
|
- JOIN_BOT_RIGHT = 4
|
33
|
|
- JOIN_CENTER = 5
|
34
|
|
-
|
35
|
|
- def __init__(self, imagepath_handle_image):
|
36
|
|
- self.imagepath_handle_image = imagepath_handle_image
|
37
|
|
- self.__image__ = None
|
38
|
|
- #
|
39
|
|
-
|
40
|
|
- def __init_image__(self):
|
41
|
|
- if self.__image__ is None:
|
42
|
|
- if type(self.imagepath_handle_image) is mm_image:
|
43
|
|
- self.__image__ = self.imagepath_handle_image.__image__
|
44
|
|
- elif type(self.imagepath_handle_image) is Image.Image:
|
45
|
|
- self.__image__ = self.imagepath_handle_image
|
46
|
|
- else:
|
47
|
|
- self.__image__ = Image.open(self.imagepath_handle_image)
|
48
|
|
-
|
49
|
|
- def orientation(self):
|
50
|
|
- self.__init_image__()
|
51
|
|
- exif_tags = dict((v, k) for k, v in ExifTags.TAGS.items())
|
52
|
|
- try:
|
53
|
|
- return dict(self.__image__._getexif().items())[exif_tags['Orientation']]
|
54
|
|
- except AttributeError:
|
55
|
|
- return 1
|
56
|
|
- except KeyError:
|
57
|
|
- return 1
|
58
|
|
-
|
59
|
|
- def copy(self):
|
60
|
|
- self.__init_image__()
|
61
|
|
- return mm_image(self.__image__.copy())
|
62
|
|
-
|
63
|
|
- def save(self, *args, **kwargs):
|
64
|
|
- self.__init_image__()
|
65
|
|
- im = self.__image__.copy().convert('RGB')
|
66
|
|
- im.save(*args, **kwargs)
|
67
|
|
-
|
68
|
|
- def resize(self, max_size):
|
69
|
|
- self.__init_image__()
|
70
|
|
- #
|
71
|
|
- # resize
|
72
|
|
- #
|
73
|
|
- x, y = self.__image__.size
|
74
|
|
- xy_max = max(x, y)
|
75
|
|
- self.__image__ = self.__image__.resize((int(x * float(max_size) / xy_max), int(y * float(max_size) / xy_max)), Image.NEAREST).rotate(0)
|
76
|
|
-
|
77
|
|
- def rotate_by_orientation(self, orientation):
|
78
|
|
- self.__init_image__()
|
79
|
|
- #
|
80
|
|
- # rotate
|
81
|
|
- #
|
82
|
|
- angle = {3: 180, 6: 270, 8: 90}.get(orientation)
|
83
|
|
- if angle is not None:
|
84
|
|
- self.__image__ = self.__image__.rotate(angle, expand=True)
|
85
|
|
-
|
86
|
|
- def __rgba_copy__(self):
|
87
|
|
- self.__init_image__()
|
88
|
|
- if self.__image__.mode != 'RGBA':
|
89
|
|
- return self.__image__.convert('RGBA')
|
90
|
|
- else:
|
91
|
|
- return self.__image__.copy()
|
92
|
|
-
|
93
|
|
- def join(self, image, joint_pos=JOIN_TOP_RIGHT, opacity=0.7):
|
94
|
|
- """
|
95
|
|
- This joins another picture to this one.
|
96
|
|
-
|
97
|
|
- :param picture_edit picture: The picture to be joint.
|
98
|
|
- :param joint_pos: The position of picture in this picture. See also self.JOIN_*
|
99
|
|
- :param float opacity: The opacity of picture when joint (value between 0 and 1).
|
100
|
|
-
|
101
|
|
- .. note::
|
102
|
|
- joint_pos makes only sense if picture is smaller than this picture.
|
103
|
|
- """
|
104
|
|
- self.__init_image__()
|
105
|
|
- #
|
106
|
|
- im2 = image.__rgba_copy__()
|
107
|
|
- # change opacity of im2
|
108
|
|
- alpha = im2.split()[3]
|
109
|
|
- alpha = ImageEnhance.Brightness(alpha).enhance(opacity)
|
110
|
|
- im2.putalpha(alpha)
|
111
|
|
-
|
112
|
|
- self.__image__ = self.__rgba_copy__()
|
113
|
|
-
|
114
|
|
- # create a transparent layer
|
115
|
|
- layer = Image.new('RGBA', self.__image__.size, (0, 0, 0, 0))
|
116
|
|
- # draw im2 in layer
|
117
|
|
- if joint_pos == self.JOIN_TOP_LEFT:
|
118
|
|
- layer.paste(im2, (0, 0))
|
119
|
|
- elif joint_pos == self.JOIN_TOP_RIGHT:
|
120
|
|
- layer.paste(im2, ((self.__image__.size[0] - im2.size[0]), 0))
|
121
|
|
- elif joint_pos == self.JOIN_BOT_LEFT:
|
122
|
|
- layer.paste(im2, (0, (self.__image__.size[1] - im2.size[1])))
|
123
|
|
- elif joint_pos == self.JOIN_BOT_RIGHT:
|
124
|
|
- layer.paste(im2, ((self.__image__.size[0] - im2.size[0]), (self.__image__.size[1] - im2.size[1])))
|
125
|
|
- elif joint_pos == self.JOIN_CENTER:
|
126
|
|
- layer.paste(im2, (int((self.__image__.size[0] - im2.size[0]) / 2), int((self.__image__.size[1] - im2.size[1]) / 2)))
|
127
|
|
-
|
128
|
|
- self.__image__ = Image.composite(layer, self.__image__, layer)
|
129
|
|
-
|
130
|
|
- def image_data(self):
|
131
|
|
- self.__init_image__()
|
132
|
|
- #
|
133
|
|
- # create return value
|
134
|
|
- #
|
135
|
|
- im = self.__image__.copy().convert('RGB')
|
136
|
|
- output = io.BytesIO()
|
137
|
|
- im.save(output, format='JPEG')
|
138
|
|
- return output.getvalue()
|
139
|
|
-
|
140
|
|
-
|
141
|
|
-class mm_video(object):
|
142
|
|
- def __init__(self, full_path):
|
143
|
|
- self.full_path = full_path
|
144
|
|
-
|
145
|
|
- def image(self):
|
146
|
|
- if platform.system() == 'Linux':
|
147
|
|
- cmd = 'ffmpeg -ss 0.5 -i "' + self.full_path + '" -vframes 1 -f image2pipe pipe:1 2> /dev/null'
|
148
|
|
- else:
|
149
|
|
- cmd = 'ffmpeg -ss 0.5 -i "' + self.full_path + '" -vframes 1 -f image2pipe pipe:1 2> NULL'
|
150
|
|
- data = subprocess.check_output(cmd, shell=True)
|
151
|
|
- ffmpeg_handle = io.BytesIO(data)
|
152
|
|
- im = Image.open(ffmpeg_handle)
|
153
|
|
- return mm_image(im.copy())
|
154
|
|
-
|
155
|
|
-
|
156
|
|
-class base_item(object):
|
157
|
|
- MIME_TYPES = {
|
158
|
|
- '.ada': 'text/x/adasrc',
|
159
|
|
- '.hex': 'text/x/hex',
|
160
|
|
- '.jpg': 'image/x/generic',
|
161
|
|
- '.jpeg': 'image/x/generic',
|
162
|
|
- '.jpe': 'image/x/generic',
|
163
|
|
- '.png': 'image/x/generic',
|
164
|
|
- '.tif': 'image/x/generic',
|
165
|
|
- '.tiff': 'image/x/generic',
|
166
|
|
- '.gif': 'image/x/generic',
|
167
|
|
- '.avi': 'video/x/generic',
|
168
|
|
- '.mpg': 'video/x/generic',
|
169
|
|
- '.mpeg': 'video/x/generic',
|
170
|
|
- '.mpe': 'video/x/generic',
|
171
|
|
- '.mov': 'video/x/generic',
|
172
|
|
- '.qt': 'video/x/generic',
|
173
|
|
- '.mp4': 'video/x/generic',
|
174
|
|
- '.webm': 'video/x/generic',
|
175
|
|
- '.ogv': 'video/x/generic',
|
176
|
|
- '.flv': 'video/x/generic',
|
177
|
|
- '.3gp': 'video/x/generic',
|
178
|
|
- }
|
179
|
|
-
|
180
|
|
- def __init__(self, full_path, request):
|
181
|
|
- self.full_path = full_path
|
182
|
|
- self.request = request
|
183
|
|
- self.rel_path = pygal.get_rel_path(full_path)
|
184
|
|
- #
|
185
|
|
- ext = os.path.splitext(self.full_path)[1].lower()
|
186
|
|
- self.mime_type = self.MIME_TYPES.get(ext, mimetypes.types_map.get(ext, 'unknown'))
|
187
|
|
-
|
188
|
|
- def __cache_image_folder__(self, rel_path):
|
189
|
|
- return os.path.join(settings.XNAIL_ROOT, rel_path.replace('_', '__').replace('/', '_'))
|
190
|
|
-
|
191
|
|
- def __cache_image_name__(self, max_size):
|
192
|
|
- filename = '%04d_%02x_%s.jpg' % (max_size, self.XNAIL_VERSION_NUMBER, fstools.uid(self.full_path, None))
|
193
|
|
- return os.path.join(self.__cache_image_folder__(self.rel_path), filename)
|
194
|
|
-
|
195
|
|
- def __delete_cache_image__(self, max_size):
|
196
|
|
- for fn in fstools.filelist(self.__cache_image_folder__(self.rel_path), '%04d*' % max_size):
|
197
|
|
- try:
|
198
|
|
- os.remove(fn)
|
199
|
|
- except OSError:
|
200
|
|
- pass # possibly file is already removed by another process
|
201
|
|
-
|
202
|
|
-
|
203
|
|
-class image(base_item, mm_image):
|
204
|
|
- XNAIL_VERSION_NUMBER = 1
|
205
|
|
-
|
206
|
|
- def __init__(self, *args, **kwargs):
|
207
|
|
- base_item.__init__(self, *args, **kwargs)
|
208
|
|
- mm_image.__init__(self, self.full_path)
|
209
|
|
- self.mime_type_xnails = mimetypes.types_map.get('.jpg', 'unknown')
|
210
|
|
-
|
211
|
|
- def get_resized_image_data(self, max_size):
|
212
|
|
- cache_filename = self.__cache_image_name__(max_size)
|
213
|
|
- if not os.path.exists(cache_filename):
|
214
|
|
- logger.info('Creating xnail-%d for %s', max_size, self.rel_path)
|
215
|
|
- self.__delete_cache_image__(max_size)
|
216
|
|
- im = self.copy()
|
217
|
|
- im.resize(max_size)
|
218
|
|
- im.rotate_by_orientation(self.orientation())
|
219
|
|
- #
|
220
|
|
- # create cache file
|
221
|
|
- #
|
222
|
|
- fstools.mkdir(os.path.dirname(cache_filename))
|
223
|
|
- with open(cache_filename, 'wb') as fh:
|
224
|
|
- im.save(fh, format='JPEG')
|
225
|
|
- #
|
226
|
|
- return im.image_data()
|
227
|
|
- else:
|
228
|
|
- return open(cache_filename, 'rb').read()
|
229
|
|
-
|
230
|
|
- def thumbnail_picture(self):
|
231
|
|
- return self.get_resized_image_data(pygal.get_thumbnail_max_size(self.request))
|
232
|
|
-
|
233
|
|
- def webnail_picture(self):
|
234
|
|
- return self.get_resized_image_data(pygal.get_webnail_size(self.request))
|
235
|
|
-
|
236
|
|
-
|
237
|
|
-class video(base_item, mm_video):
|
238
|
|
- XNAIL_VERSION_NUMBER = 1
|
239
|
|
-
|
240
|
|
- def __init__(self, *args, **kwargs):
|
241
|
|
- base_item.__init__(self, *args, **kwargs)
|
242
|
|
- mm_video.__init__(self, self.full_path)
|
243
|
|
- self.mime_type_xnails = mimetypes.types_map.get('.jpg', 'unknown')
|
244
|
|
-
|
245
|
|
- def get_resized_image_data(self, max_size):
|
246
|
|
- cache_filename = self.__cache_image_name__(max_size)
|
247
|
|
- if not os.path.exists(cache_filename):
|
248
|
|
- logger.info('Creating xnail-%d for %s', max_size, self.rel_path)
|
249
|
|
- self.__delete_cache_image__(max_size)
|
250
|
|
- im = self.image()
|
251
|
|
- im.resize(max_size)
|
252
|
|
- overlay = mm_image(os.path.join(os.path.dirname(__file__), 'video.png'))
|
253
|
|
- im.join(overlay)
|
254
|
|
- #
|
255
|
|
- # create cache file
|
256
|
|
- #
|
257
|
|
- fstools.mkdir(os.path.dirname(cache_filename))
|
258
|
|
- with open(cache_filename, 'wb') as fh:
|
259
|
|
- im.save(fh, format='JPEG')
|
260
|
|
- #
|
261
|
|
- return im.image_data()
|
262
|
|
- else:
|
263
|
|
- return open(cache_filename, 'rb').read()
|
264
|
|
-
|
265
|
|
- def thumbnail_picture(self):
|
266
|
|
- return image.thumbnail_picture(self)
|
267
|
|
-
|
268
|
|
- def webnail_picture(self):
|
269
|
|
- return image.webnail_picture(self)
|
270
|
|
-
|
271
|
|
-
|
272
|
|
-class other(base_item):
|
273
|
|
- def __init__(self, *args, **kwargs):
|
274
|
|
- base_item.__init__(self, *args, **kwargs)
|
275
|
|
- self.mime_type_xnails = mimetypes.types_map.get('.png', 'unknown')
|
276
|
|
-
|
277
|
|
- def thumbnail_picture(self):
|
278
|
|
- fn = os.path.join(os.path.abspath(os.path.dirname(__file__)), 'mimetype_icons', '%s.png' % (self.mime_type).replace('/', '-'))
|
279
|
|
- if not os.path.exists(fn):
|
280
|
|
- fn = os.path.join(os.path.abspath(os.path.dirname(__file__)), 'mimetype_icons', 'unknown.png')
|
281
|
|
- return open(fn, 'rb').read()
|
282
|
|
-
|
283
|
|
- def webnail_picture(self):
|
284
|
|
- return self.thumbnail_picture()
|