1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 __maintainer__ = 'Florian Boucault <florian@fluendo.com>'
19 __maintainer2__ = 'Benjamin Kampmann <benjamin@fluendo.com>'
20
21 from elisa.core import common
22
23 from elisa.extern.log import log
24
25 from twisted.internet import defer, reactor, threads
26 from twisted.python.failure import Failure
27
28 import PIL
29 from PIL import PngImagePlugin
30 import StringIO
31
32 import gst
33
34 import gobject
35 from mutex import mutex
36 import sys, os
37 import time
38 import Queue
39 import md5
40 import Image, ImageStat
41
42 BORING_IMAGE_VARIANCE=2000
43
44 HOLES_SIZE= (9, 35)
45 HOLES_DATA='\x00\x00\x00\xa6\x00\x00\x00\xa6\x00\x00\x00\xa6\x00\x00\x00\xa6\x00\x00\x00\xa6\x00\x00\x00\xa6\x00\x00\x00\xa6\x00\x00\x00\xa6\x00\x00\x00\xa6\x00\x00\x00\xa6\x00\x00\x00\xa6\x00\x00\x00\xa6\x00\x00\x00\xa6\x00\x00\x00\xa6\x00\x00\x00\xa6\x00\x00\x00\xa6\x00\x00\x00\xa6\x00\x00\x00\xa6\x00\x00\x00\xa6\x00\x00\x00\xa6\x00\x00\x00\xa6\x9a\x9a\x9a\xa6\xff\xff\xff\xa6\xff\xff\xff\xa6\xff\xff\xff\xa6\x91\x91\x91\xa6\x00\x00\x00\xa6\x00\x00\x00\xa6\x00\x00\x00\xa6\x00\x00\x00\xa6\xff\xff\xff\xa6\xff\xff\xff\xa6\xff\xff\xff\xa6\xff\xff\xff\xa6\xff\xff\xff\xa6\x00\x00\x00\xa6\x00\x00\x00\xa6\x00\x00\x00\xa6\x00\x00\x00\xa6\x91\x91\x91\xa6\xff\xff\xff\xa6\xff\xff\xff\xa6\xff\xff\xff\xa6\x91\x91\x91\xa6\x00\x00\x00\xa6\x00\x00\x00\xa6\x00\x00\x00\xa6\x00\x00\x00\xa6\x00\x00\x00\xa6\x00\x00\x00\xa6\x00\x00\x00\xa6\x00\x00\x00\xa6\x00\x00\x00\xa6\x00\x00\x00\xa6\x00\x00\x00\xa6\x00\x00\x00\xa6\x00\x00\x00\xa6\x00\x00\x00\xa6\x00\x00\x00\xa6\x00\x00\x00\xa6\x00\x00\x00\xa6\x00\x00\x00\xa6\x00\x00\x00\xa6\x00\x00\x00\xa6\x00\x00\x00\xa6\x00\x00\x00\xa6\x00\x00\x00\xa6\x00\x00\x00\xa6\x00\x00\x00\xa6\x00\x00\x00\xa6\x00\x00\x00\xa6\x00\x00\x00\xa6\x00\x00\x00\xa6\x00\x00\x00\xa6\x00\x00\x00\xa6\x00\x00\x00\xa6\x00\x00\x00\xa6\x00\x00\x00\xa6\x00\x00\x00\xa6\x00\x00\x00\xa6\x00\x00\x00\xa6\x00\x00\x00\xa6\x00\x00\x00\xa6\x00\x00\x00\xa6\x9a\x9a\x9a\xa6\xff\xff\xff\xa6\xff\xff\xff\xa6\xff\xff\xff\xa6\x91\x91\x91\xa6\x00\x00\x00\xa6\x00\x00\x00\xa6\x00\x00\x00\xa6\x00\x00\x00\xa6\xff\xff\xff\xa6\xff\xff\xff\xa6\xff\xff\xff\xa6\xff\xff\xff\xa6\xff\xff\xff\xa6\x00\x00\x00\xa6\x00\x00\x00\xa6\x00\x00\x00\xa6\x00\x00\x00\xa6\x91\x91\x91\xa6\xff\xff\xff\xa6\xff\xff\xff\xa6\xff\xff\xff\xa6\x91\x91\x91\xa6\x00\x00\x00\xa6\x00\x00\x00\xa6\x00\x00\x00\xa6\x00\x00\x00\xa6\x00\x00\x00\xa6\x00\x00\x00\xa6\x00\x00\x00\xa6\x00\x00\x00\xa6\x00\x00\x00\xa6\x00\x00\x00\xa6\x00\x00\x00\xa6\x00\x00\x00\xa6\x00\x00\x00\xa6\x00\x00\x00\xa6\x00\x00\x00\xa6\x00\x00\x00\xa6\x00\x00\x00\xa6\x00\x00\x00\xa6\x00\x00\x00\xa6\x00\x00\x00\xa6\x00\x00\x00\xa6\x00\x00\x00\xa6\x00\x00\x00\xa6\x00\x00\x00\xa6\x00\x00\x00\xa6\x00\x00\x00\xa6\x00\x00\x00\xa6\x00\x00\x00\xa6\x00\x00\x00\xa6\x00\x00\x00\xa6\x00\x00\x00\xa6\x00\x00\x00\xa6\x00\x00\x00\xa6\x00\x00\x00\xa6\x00\x00\x00\xa6\x00\x00\x00\xa6\x00\x00\x00\xa6\x00\x00\x00\xa6\x00\x00\x00\xa6\x00\x00\x00\xa6\x9a\x9a\x9a\xa6\xff\xff\xff\xa6\xff\xff\xff\xa6\xff\xff\xff\xa6\x91\x91\x91\xa6\x00\x00\x00\xa6\x00\x00\x00\xa6\x00\x00\x00\xa6\x00\x00\x00\xa6\xff\xff\xff\xa6\xff\xff\xff\xa6\xff\xff\xff\xa6\xff\xff\xff\xa6\xff\xff\xff\xa6\x00\x00\x00\xa6\x00\x00\x00\xa6\x00\x00\x00\xa6\x00\x00\x00\xa6\x91\x91\x91\xa6\xff\xff\xff\xa6\xff\xff\xff\xa6\xff\xff\xff\xa6\x91\x91\x91\xa6\x00\x00\x00\xa6\x00\x00\x00\xa6\x00\x00\x00\xa6\x00\x00\x00\xa6\x00\x00\x00\xa6\x00\x00\x00\xa6\x00\x00\x00\xa6\x00\x00\x00\xa6\x00\x00\x00\xa6\x00\x00\x00\xa6\x00\x00\x00\xa6\x00\x00\x00\xa6\x00\x00\x00\xa6\x00\x00\x00\xa6\x00\x00\x00\xa6\x00\x00\x00\xa6\x00\x00\x00\xa6\x00\x00\x00\xa6\x00\x00\x00\xa6\x00\x00\x00\xa6\x00\x00\x00\xa6\x00\x00\x00\xa6\x00\x00\x00\xa6\x00\x00\x00\xa6\x00\x00\x00\xa6\x00\x00\x00\xa6\x00\x00\x00\xa6\x00\x00\x00\xa6\x00\x00\x00\xa6\x00\x00\x00\xa6\x00\x00\x00\xa6\x00\x00\x00\xa6\x00\x00\x00\xa6\x00\x00\x00\xa6\x00\x00\x00\xa6\x00\x00\x00\xa6\x00\x00\x00\xa6\x00\x00\x00\xa6\x00\x00\x00\xa6\x00\x00\x00\xa6\x9a\x9a\x9a\xa6\xff\xff\xff\xa6\xff\xff\xff\xa6\xff\xff\xff\xa6\x91\x91\x91\xa6\x00\x00\x00\xa6\x00\x00\x00\xa6\x00\x00\x00\xa6\x00\x00\x00\xa6\xff\xff\xff\xa6\xff\xff\xff\xa6\xff\xff\xff\xa6\xff\xff\xff\xa6\xff\xff\xff\xa6\x00\x00\x00\xa6\x00\x00\x00\xa6\x00\x00\x00\xa6\x00\x00\x00\xa6\x91\x91\x91\xa6\xff\xff\xff\xa6\xff\xff\xff\xa6\xff\xff\xff\xa6\x91\x91\x91\xa6\x00\x00\x00\xa6\x00\x00\x00\xa6\x00\x00\x00\xa6\x00\x00\x00\xa6\x00\x00\x00\xa6\x00\x00\x00\xa6\x00\x00\x00\xa6\x00\x00\x00\xa6\x00\x00\x00\xa6\x00\x00\x00\xa6\x00\x00\x00\xa6\x00\x00\x00\xa6\x00\x00\x00\xa6\x00\x00\x00\xa6\x00\x00\x00\xa6\x00\x00\x00\xa6\x00\x00\x00\xa6\x00\x00\x00\xa6\x00\x00\x00\xa6\x00\x00\x00\xa6\x00\x00\x00\xa6\x00\x00\x00\xa6\x00\x00\x00\xa6\x00\x00\x00\xa6\x00\x00\x00\xa6\x00\x00\x00\xa6\x00\x00\x00\xa6\x00\x00\x00\xa6\x00\x00\x00\xa6\x00\x00\x00\xa6\x00\x00\x00\xa6\x00\x00\x00\xa6\x00\x00\x00\xa6\x00\x00\x00\xa6\x00\x00\x00\xa6\x00\x00\x00\xa6\x00\x00\x00\xa6\x00\x00\x00\xa6\x00\x00\x00\xa6\x00\x00\x00\xa6\x9a\x9a\x9a\xa6\xff\xff\xff\xa6\xff\xff\xff\xa6\xff\xff\xff\xa6\x91\x91\x91\xa6\x00\x00\x00\xa6\x00\x00\x00\xa6\x00\x00\x00\xa6\x00\x00\x00\xa6\xff\xff\xff\xa6\xff\xff\xff\xa6\xff\xff\xff\xa6\xff\xff\xff\xa6\xff\xff\xff\xa6\x00\x00\x00\xa6\x00\x00\x00\xa6\x00\x00\x00\xa6\x00\x00\x00\xa6\x91\x91\x91\xa6\xff\xff\xff\xa6\xff\xff\xff\xa6\xff\xff\xff\xa6\x91\x91\x91\xa6\x00\x00\x00\xa6\x00\x00\x00\xa6\x00\x00\x00\xa6\x00\x00\x00\xa6\x00\x00\x00\xa6\x00\x00\x00\xa6\x00\x00\x00\xa6\x00\x00\x00\xa6\x00\x00\x00\xa6\x00\x00\x00\xa6\x00\x00\x00\xa6\x00\x00\x00\xa6\x00\x00\x00\xa6\x00\x00\x00\xa6\x00\x00\x00\xa6\x00\x00\x00\xa6\x00\x00\x00\xa6\x00\x00\x00\xa6\x00\x00\x00\xa6'
46
48
49 - def __init__(self, uri, error_message=None):
50 if error_message == None:
51 output = "Failed thumbnailing %r" % uri
52 else:
53 output = "Failed thumbnailing %r: %r" % (uri, error_message)
54
55 Exception.__init__(self, output)
56 self.uri = uri
57 self.error_message = error_message
58
61
63
65 self.reset()
66 gst.Bin.__init__(self)
67 self._capsfilter = gst.element_factory_make('capsfilter', 'capsfilter')
68
69 self.set_caps(needed_caps)
70 self.add(self._capsfilter)
71
72 fakesink = gst.element_factory_make('fakesink', 'fakesink')
73 fakesink.set_property("sync", False)
74 self.add(fakesink)
75 self._capsfilter.link(fakesink)
76
77 pad = self._capsfilter.get_pad("sink")
78 ghostpad = gst.GhostPad("sink", pad)
79
80 pad2probe = fakesink.get_pad("sink")
81 pad2probe.add_buffer_probe(self.buffer_probe)
82
83 self.add_pad(ghostpad)
84 self.sink = self._capsfilter
85
87 self._current_frame = value
88
90 gst_caps = gst.caps_from_string(caps)
91 self._capsfilter.set_property("caps", gst_caps)
92
94 frame = self._current_frame
95 self._current_frame = None
96 return frame
97
99 caps = buffer.caps
100 if caps != None:
101 s = caps[0]
102 self.width = s['width']
103 self.height = s['height']
104 if self.width != None and self.height != None and buffer != None:
105 self.set_current_frame(buffer.data)
106 return True
107
112
113
114 gobject.type_register(VideoSinkBin)
115
116 from threading import Event
117
119
120 logCategory = "thumbnailer"
121
123 self._pipeline = gst.element_factory_make('playbin', 'playbin')
124 caps = "video/x-raw-rgb,bpp=24,depth=24"
125
126 self._sink = VideoSinkBin(caps)
127 self._blocker = Event()
128
129 self._pipeline.set_property("video-sink", self._sink)
130 self._pipeline.set_property('volume', 0)
131
132
136
138 holes = self.get_holes_img()
139 holes_h = holes.size[1]
140 remain = img.size[1] % holes_h
141
142 i = 0
143 nbands = 0
144 while i < (img.size[1] - remain):
145 left_box = (0, i, holes.size[0], (nbands+1) * holes.size[1])
146 img.paste(holes, left_box)
147
148 right_box = (img.size[0] - holes.size[0], i,
149 img.size[0], (nbands+1) * holes.size[1])
150 img.paste(holes, right_box)
151
152 i += holes_h
153 nbands += 1
154
155 remain_holes = holes.crop((0, 0, holes.size[0], remain))
156 remain_holes.load()
157 img.paste(remain_holes, (0, i, holes.size[0], img.size[1]))
158 img.paste(remain_holes, (img.size[0] - holes.size[0], i,
159 img.size[0], img.size[1]))
160 return img
161
165
167 status = pipeline.set_state(state)
168 if status == gst.STATE_CHANGE_ASYNC:
169 self.debug("Waiting for state change completion to %s..." % state)
170
171 result = [False]
172 max_try = 100
173 nb_try = 0
174 while not result[0] == gst.STATE_CHANGE_SUCCESS:
175 if nb_try > max_try:
176 self.debug("State change failed: %s" % result[0])
177 return False
178 nb_try += 1
179 result = pipeline.get_state(50*gst.MSECOND)
180
181 self.debug("State change completed.")
182 return True
183 elif status == gst.STATE_CHANGE_SUCCESS:
184 self.debug("State change completed.")
185 return True
186 else:
187 self.debug("State change failed")
188 return False
189
190
192 """
193 Try to generate a thumbnail for the video located at video_uri,
194 of size width.
195
196 @param video_uri: URI to make a thumbnail from
197 @type video_uri: L{elisa.core.media_uri.MediaUri}
198 @param size: size of the thumbnail in pixels
199 @type size: int
200
201 @raise ThumbnailerError: if an error occurs when generating the thumbnail
202 """
203
204 self.set_state_blocking(self._pipeline, gst.STATE_NULL)
205 self.debug("Generating thumbnail for file: %s" % video_uri)
206 self._pipeline.set_property('uri', video_uri)
207
208 """
209 def bus_event(bus, message, pipeline):
210 t = message.type
211 if t == gst.MESSAGE_EOS:
212 self.debug("End of Stream")
213 pass
214 elif t == gst.MESSAGE_ERROR:
215 err, debug = message.parse_error()
216 self.debug("Error: %s %s" % (err, debug))
217 self.set_state_blocking(pipeline, gst.STATE_NULL)
218 return True
219
220 self._pipeline.get_bus().add_watch(bus_event, self._pipeline)
221 """
222
223
224 if not self.set_state_blocking(self._pipeline, gst.STATE_PAUSED):
225 self.debug("Cannot start the pipeline")
226 self.set_state_blocking(self._pipeline, gst.STATE_NULL)
227 raise ThumbnailerError(video_uri)
228
229 if self._sink.width == None or self._sink.height == None:
230 self.debug("Cannot determine media size")
231 self.set_state_blocking(self._pipeline, gst.STATE_NULL)
232 raise ThumbnailerError(video_uri)
233 sink_size = (self._sink.width, self._sink.height)
234
235 self.debug("width: %s; height: %s" % (self._sink.width, self._sink.height))
236 try:
237 duration, format = self._pipeline.query_duration(gst.FORMAT_TIME)
238 except Exception, e:
239
240 self.debug("Gstreamer cannot determine the media duration."
241 " using playing-thumbnailing for %s" % video_uri)
242 self.set_state_blocking(self._pipeline, gst.STATE_NULL)
243 img = self._play_for_thumb(sink_size, size, 0)
244 self.debug("play found %s" % img)
245 if img:
246 return img
247 else:
248 duration /= gst.NSECOND
249 self.debug("duration: %s" % duration)
250 try:
251 img = self._seek_for_thumb(video_uri, duration, sink_size, size)
252 self.debug("seek found %s" % img)
253 if img:
254 return img
255 except ThumbnailerError, e:
256
257 self.debug("Using Fallback: play_for_thumb")
258 self.set_state_blocking(self._pipeline, gst.STATE_NULL)
259 img = self._play_for_thumb(sink_size, size, duration)
260 self.debug("Fallback-Play found %s" % img)
261 if img:
262 return img
263
264 self.set_state_blocking(self._pipeline, gst.STATE_NULL)
265 raise ThumbnailerError(video_uri)
266
267
269
270 self.debug("Doing play_for_thumb!")
271 self.debug(duration)
272 id = None
273 self._img = None
274
275 if duration >= 250000:
276 self._every = 25
277 elif duration >= 200000:
278 self._every = 15
279 elif duration >= 10000:
280 self._every = 10
281 elif duration >= 5000:
282 self._every = 5
283 else:
284 self._every = 1
285
286 self.debug("Setting every-frame to %s" % self._every)
287
288 self._every_co = self._every
289
290
291 self._counter = 5
292
293 def buffer_probe(pad, buffer):
294
295 if self._every_co < self._every:
296 self._every_co += 1
297 return
298 self._every_co = 0
299 self.debug("Proceeding a Frame")
300
301 try:
302 img = Image.frombuffer("RGB", sink_size, buffer,
303 "raw", "RGB",0, 1)
304 except Exception, e:
305 self.debug("Invalid frame")
306 else:
307 self.debug("Found Frame")
308 self._img = img
309 if self.interesting_image(self._img):
310 self.debug("Intresting image found")
311
312 self._img.thumbnail((size, size), Image.BILINEAR)
313 if img.mode != 'RGBA':
314 img = img.convert(mode='RGBA')
315
316 self.debug("releasing %s" % self._img)
317 self._sink.reset()
318 pad.remove_buffer_probe(id)
319 self._blocker.set()
320 return
321
322 self._counter -= 1
323 if self._counter <= 0:
324 self.debug("Counter off, resetting blocker!")
325
326 if self._img:
327 self._img.thumbnail((size, size), Image.BILINEAR)
328 if img.mode != 'RGBA':
329 img = img.convert(mode='RGBA')
330
331
332 self._sink.reset()
333 pad.remove_buffer_probe(id)
334 self._blocker.set()
335
336
337 self.debug("Setting Pipeline")
338 self.set_state_blocking(self._pipeline, gst.STATE_PLAYING)
339
340 pad = self._sink.get_pad('sink')
341 id = pad.add_buffer_probe(buffer_probe)
342 self.debug("Wait")
343 self._blocker.wait()
344 self.debug("Going on")
345 self._pipeline.set_state(gst.STATE_NULL)
346 self.debug("returning %s" % self._img)
347 return self._img
348
350 frame_locations = [ 1.0 / 3.0, 2.0 / 3.0, 0.1, 0.9, 0.5 ]
351 self.debug('seeking')
352
353 for location in frame_locations:
354 abs_location = int(location * duration)
355 self.debug("location: rel %s, abs %s" % (location, abs_location))
356
357 if abs_location == 0:
358 raise ThumbnailerError(video_uri, "Empty media")
359
360 event = self._pipeline.seek(1.0, gst.FORMAT_TIME,
361 gst.SEEK_FLAG_FLUSH | gst.SEEK_FLAG_KEY_UNIT,
362 gst.SEEK_TYPE_SET, abs_location,
363 gst.SEEK_TYPE_NONE, 0)
364 if not event:
365 raise ThumbnailerError(video_uri, "Not Seekable")
366
367 if not self.set_state_blocking(self._pipeline, gst.STATE_PAUSED):
368 raise ThumbnailerError(video_uri, "Not Pausable")
369
370 frame = self._sink.get_current_frame()
371
372 try:
373 img = Image.frombuffer("RGB", sink_size, frame, "raw", "RGB", 0, 1)
374 except:
375 self.debug("Invalid frame")
376 continue
377
378 if self.interesting_image(img):
379 self.debug("Interesting image found")
380 break
381 else:
382 self.debug("Image not interesting")
383 pass
384
385 self._sink.reset()
386
387 if img:
388 img.thumbnail((size, size), Image.BILINEAR)
389 if img.mode != 'RGBA':
390 img = img.convert(mode='RGBA')
391 self.set_state_blocking(self._pipeline, gst.STATE_NULL)
392 return img
393
394
396
397 logCategory = "thumbnailer"
398
399 - def __init__(self, thumbnail_dir=None):
400 """
401
402 @param thumbnail_dir: abolute path to a directory where to store thumbnails
403 @type thumbnail_dir: string
404 """
405
406 if not thumbnail_dir:
407 thumbnail_dir = os.path.join(os.path.expanduser("~"), ".thumbnails")
408 self._thumbnail_dir = thumbnail_dir
409
410 self._video_thumbnailer = VideoThumbnailer()
411 self._queue = Queue.Queue()
412 self._running = False
413 self._delayed_call = None
414
416 if self._delayed_call and self._delayed_call.active():
417 self._delayed_call.cancel()
418
419
421 """
422 Save a thumbnail of an URI at a given location.
423
424 @param file_uri: URI the thumbnail was generated from
425 @type file_uri: L{elisa.core.media_uri.MediaUri}
426 @param thumbnail: Thumbnail instance
427 @type thumbnail: L{PIL.Image}
428 @param thumbnail_filename: filename to save to
429 @type thumbnail_filename: string
430 @raises ThumbnailerError: if the thumbnail couldn't be saved
431 """
432
433
434 directory = os.path.dirname(thumbnail_filename)
435 if not os.path.exists(directory):
436 try:
437 os.makedirs(directory, 0700)
438 except OSError, e:
439 msg = "Could not make directory %r: %s. Thumbnail not saved." % (directory, e)
440 self.warning(msg)
441 raise ThumbnailerError(file_uri, msg)
442
443 info = PngImagePlugin.PngInfo()
444
445
446 info.add_text("Thumb::URI", str(file_uri))
447
448
449
450
451
452
453
454 thumbnail.save(thumbnail_filename, "png", pnginfo=info)
455
457 """
458 Return the thumbnail's location of a file for a particular size.
459
460 @param file_uri: URI of the file
461 @type file_uri: L{elisa.core.media_uri.MediaUri}
462 @param size: Size of the thumbnail (in pixels)
463 @type size: int
464 @rtype: tuple (path to thumbnail, maximum thumbnail size)
465
466 @raise Exception: Raise an exception if size is superior to 512
467 """
468
469
470 if size <= 128:
471 thumbnail_dir_size = "normal"
472 thumbnail_max_size = 128
473 elif size <= 256:
474 thumbnail_dir_size = "large"
475 thumbnail_max_size = 256
476 elif size <= 512:
477
478 thumbnail_dir_size = "extra_large"
479 thumbnail_max_size = 512
480 else:
481 raise Exception("ThumbnailManager._get_thumbnail_location(): size \
482 given too large: %d" % size)
483
484 thumbnail_filename = md5.new(str(file_uri)).hexdigest() + ".png"
485
486 return (os.path.join(self._thumbnail_dir, thumbnail_dir_size,
487 thumbnail_filename),
488 thumbnail_max_size)
489
490
492 """
493 Retrieve a thumbnail from a given URI.
494
495 @param file_uri: URI to get the thumbnail from
496 @param file_uri: L{elisa.core.media_uri.MediaUri}
497 @param size: size of the thumbnail in pixels
498 @type size: int
499
500 @rtype: L{PIL.Image} or None
501
502 @raise: Exception raise if an error occurs while loading
503 """
504 thumbnail_path, size = self._get_thumbnail_location(file_uri, size)
505
506 if os.path.exists(thumbnail_path):
507
508 try:
509 thumbnail = image.Image(thumbnail_path)
510 return thumbnail
511 except IOError, error:
512 raise Exception("Error loading %s: %s" % (thumbnail_path, error))
513 else:
514 return None
515
516
518 """
519 @raise ThumbnailerError: if an error occurs when generating the
520 thumbnail
521 @raise NoThumbnailerFound: when it is not possible to generate the
522 thumbnail
523 """
524 self.debug("Generating thumbnail for %s of %s type" % (uri, media_type))
525 if media_type == 'image':
526 dfr = defer.Deferred()
527 self._queue.put( (uri, size, dfr) )
528 if not self._running:
529 self._running = True
530 self._delayed_call = reactor.callLater(0.01,
531 self._process_next)
532 return dfr
533
534
535
536 else:
537 return defer.fail(NoThumbnailerFound(
538 "No thumbnailer available for type %s\
539 (%s)" % (media_type, uri)))
540
556
557 def thumbnail_done(result, thumbnail_filename, thumbnail_max_size):
558 self.debug('returning (%s, %s)' % (thumbnail_filename,
559 thumbnail_max_size))
560 item_dfr.callback((thumbnail_filename, thumbnail_max_size))
561 self._delayed_call = reactor.callLater(0.01, self._process_next)
562
563 def got_error(failure):
564 self._delayed_call = reactor.callLater(0.01, self._process_next)
565 self.debug('Error while processing %s:%s' % (uri, failure))
566
567 def got_readed_data(data):
568 self.debug('got media data')
569 thumbnail_filename, thumbnail_max_size = self._get_thumbnail_location(uri, size)
570 dfr = threads.deferToThread(do_thumbnail,
571 data, thumbnail_filename, thumbnail_max_size)
572 dfr.addCallback(thumbnail_done,
573 thumbnail_filename, thumbnail_max_size)
574
575 return dfr
576
577 def open_done(media_file):
578 if media_file is None:
579 self.debug("Didn't get a media_file :(")
580 raise Exception("Didn't get a media_file, reading failed")
581 self.debug('got media file %s' % media_file)
582 dfr = media_file.read()
583 dfr.addCallback(got_readed_data)
584 dfr.addErrback(got_error)
585 return dfr
586
587 dfr = media_manager.open(uri)
588 dfr.addCallback(open_done)
589 dfr.addErrback(got_error)
590
592 """
593 Creates a thumbnail of uri on a local location.
594 Returns a deferred, it's callback is called with
595 a tuple containing the local filename of the thumbnail
596 and its size as an int.
597 Calls the deferred error callback if an error occured.
598
599 @param uri: URI to generate a thumbnail from
600 @type uri: L{elisa.core.media_uri.MediaUri}
601 @param size: size of the desired thumbnail
602 @type size: int
603 @rtype: L{twisted.internet.defer.Deferred}
604 """
605 self.debug("Requesting thumbnail for %s of %s type" \
606 % (uri, media_type))
607 uri = common.application.media_manager.get_real_uri(uri)
608 f, size = self._get_thumbnail_location(uri, size)
609
610
611 if os.path.exists(f):
612 self.debug("Returning already existing thumbnail for %s" % uri)
613 return defer.succeed( (f, size) )
614
615 return self._retrieve_thumbnail(uri, size,media_type)
616
618 """
619 Adds to the thumbnail cache a thumbnail created by
620 a 3rd party component (like a View)
621
622 @param uri: URI the thumbnail has been generated from
623 @type uri: L{elisa.core.media_uri.MediaUri}
624 @param size: size of the thumbnail
625 @type size: int
626 @param thumbnail: the Image
627 @type thumbnail: L{PIL.Image}
628 """
629
630 if thumbnail:
631 thumbnail_filename, thumbnail_max_size = self._get_thumbnail_location(uri, size)
632 self._save_thumbnail_as(uri, thumbnail, thumbnail_filename)
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675