openlp-core team mailing list archive
-
openlp-core team
-
Mailing list archive
-
Message #10333
[Merge] lp:~googol-hush/openlp/thumb-creation into lp:openlp
Andreas Preikschat has proposed merging lp:~googol-hush/openlp/thumb-creation into lp:openlp with lp:~trb143/openlp/b1 as a prerequisite.
Requested reviews:
OpenLP Core (openlp-core)
For more details, see:
https://code.launchpad.net/~googol-hush/openlp/thumb-creation/+merge/65515
Hello,
1) I moved the create_thumb and validate_thumb functions to core/lib/init, thus to be able to use them (from) everywhere.
2) I changed the image manager in two ways:
- the resize_image is now done in the thread. Now you can add images to the service manager without delay and the thread resizes and creates the bytes streams in the background)
- implemented an 'intelligent' image queue to possible reduce the possibility that we are generating data (e. g. the byte stream) when it is not needed.
--
https://code.launchpad.net/~googol-hush/openlp/thumb-creation/+merge/65515
Your team OpenLP Core is requested to review the proposed merge of lp:~googol-hush/openlp/thumb-creation into lp:openlp.
=== modified file 'openlp/core/lib/__init__.py'
--- openlp/core/lib/__init__.py 2011-06-12 16:02:52 +0000
+++ openlp/core/lib/__init__.py 2011-06-22 15:13:52 +0000
@@ -137,6 +137,58 @@
# convert to base64 encoding so does not get missed!
return byte_array.toBase64()
+def create_thumb(image_path, thumb_path, return_icon=True, size=None):
+ """
+ Create a thumbnail from the given image path and depending on
+ ``return_icon`` it returns an icon from this thumb.
+
+ ``image_path``
+ The image file to create the icon from.
+
+ ``thumb_path``
+ The filename to save the thumbnail to.
+
+ ``return_icon``
+ States if an icon should be build and returned from the thumb. Defaults
+ to ``True``.
+
+ ``size``
+ Defaults to ``None``.
+ """
+ ext = os.path.splitext(thumb_path)[1].lower()
+ reader = QtGui.QImageReader(image_path)
+ if size is None:
+ ratio = float(reader.size().width()) / float(reader.size().height())
+ reader.setScaledSize(QtCore.QSize(int(ratio * 88), 88))
+ else:
+ reader.setScaledSize(size)
+ thumb = reader.read()
+ thumb.save(thumb_path, ext[1:])
+ if not return_icon:
+ return
+ if os.path.exists(thumb_path):
+ return build_icon(unicode(thumb_path))
+ # Fallback for files with animation support.
+ return build_icon(unicode(image_path))
+
+def validate_thumb(file_path, thumb_path):
+ """
+ Validates whether an file's thumb still exists and if is up to date.
+ **Note**, you must **not** call this function, before checking the
+ existence of the file.
+
+ ``file_path``
+ The path to the file. The file **must** exist!
+
+ ``thumb_path``
+ The path to the thumb.
+ """
+ if not os.path.exists(unicode(thumb_path)):
+ return False
+ image_date = os.stat(unicode(file_path)).st_mtime
+ thumb_date = os.stat(unicode(thumb_path)).st_mtime
+ return image_date <= thumb_date
+
def resize_image(image_path, width, height, background=QtCore.Qt.black):
"""
Resize an image to fit on the current screen.
@@ -151,7 +203,7 @@
The new image height.
``background``
- The background colour defaults to black.
+ The background colour. Defaults to ``QtCore.Qt.black``.
"""
log.debug(u'resize_image - start')
reader = QtGui.QImageReader(image_path)
=== modified file 'openlp/core/lib/imagemanager.py'
--- openlp/core/lib/imagemanager.py 2011-06-12 16:02:52 +0000
+++ openlp/core/lib/imagemanager.py 2011-06-22 15:13:52 +0000
@@ -32,6 +32,7 @@
"""
import logging
import time
+import Queue
from PyQt4 import QtCore
@@ -53,15 +54,59 @@
"""
Run the thread.
"""
- self.imageManager.process()
+ self.imageManager._process()
+
+
+class Priority(object):
+ """
+ Enumeration class for different priorities.
+
+ ``Low``
+ Only the image's byte stream has to be generated. Neither the QImage nor
+ the byte stream has been requested yet.
+
+ ``Normal``
+ The image's byte stream as well as the image has to be generated.
+ Neither the QImage nor the byte stream has been requested yet.
+
+ ``High``
+ The image's byte stream as well as the image has to be generated. The
+ QImage for this image has been requested.
+
+ ``Urgent``
+ The image's byte stream as well as the image has to be generated. The
+ byte stream for this image has been requested.
+ """
+ Low = 3
+ Normal = 2
+ High = 1
+ Urgent = 0
class Image(object):
- name = ''
- path = ''
- dirty = True
- image = None
- image_bytes = None
+ def __init__(self, name='', path=''):
+ self.name = name
+ self.path = path
+ self.image = None
+ self.image_bytes = None
+ self.priority = Priority.Normal
+
+
+class PriorityQueue(Queue.PriorityQueue):
+ """
+ Customised ``Queue.PriorityQueue``.
+ """
+ def remove(self, item):
+ """
+ Removes the given ``item`` from the queue.
+
+ ``item``
+ The item to remove. This should be a tuple::
+
+ ``(Priority, Image)``
+ """
+ if item in self.queue:
+ self.queue.remove(item)
class ImageManager(QtCore.QObject):
@@ -76,46 +121,63 @@
self.width = current_screen[u'size'].width()
self.height = current_screen[u'size'].height()
self._cache = {}
- self._thread_running = False
self._cache_dirty = False
- self.image_thread = ImageThread(self)
+ self._image_thread = ImageThread(self)
+ self._clean_queue = PriorityQueue()
def update_display(self):
"""
- Screen has changed size so rebuild the cache to new size
+ Screen has changed size so rebuild the cache to new size.
"""
log.debug(u'update_display')
current_screen = ScreenList.get_instance().current
self.width = current_screen[u'size'].width()
self.height = current_screen[u'size'].height()
- # mark the images as dirty for a rebuild
+ # Mark the images as dirty for a rebuild.
+ self._clean_queue = PriorityQueue()
for key in self._cache.keys():
image = self._cache[key]
- image.dirty = True
- image.image = resize_image(image.path, self.width, self.height)
+ image.priority = Priority.Normal
+ image.image = None
+ image.image_bytes = None
+ self._clean_queue.put((image.priority, image))
self._cache_dirty = True
- # only one thread please
- if not self._thread_running:
- self.image_thread.start()
+ # We want only one thread.
+ if not self._image_thread.isRunning():
+ self._image_thread.start()
def get_image(self, name):
"""
- Return the Qimage from the cache
+ Return the Qimage from the cache.
"""
+ print u'get_image:', name
log.debug(u'get_image %s' % name)
- return self._cache[name].image
+ image = self._cache[name]
+ if image.image is None:
+ self._clean_queue.remove((image.priority, image))
+ image.priority = Priority.High
+ self._clean_queue.put((image.priority, image))
+ while image.image is None:
+ log.debug(u'get_image - waiting')
+ time.sleep(0.1)
+ return image.image
def get_image_bytes(self, name):
"""
- Returns the byte string for an image
- If not present wait for the background thread to process it.
+ Returns the byte string for an image. If not present wait for the
+ background thread to process it.
"""
+ print u'get_image_bytes:', name
log.debug(u'get_image_bytes %s' % name)
- if not self._cache[name].image_bytes:
- while self._cache[name].dirty:
+ image = self._cache[name]
+ if image.image_bytes is None:
+ self._clean_queue.remove((image.priority, image))
+ image.priority = Priority.Urgent
+ self._clean_queue.put((image.priority, image))
+ while image.image_bytes is None:
log.debug(u'get_image_bytes - waiting')
time.sleep(0.1)
- return self._cache[name].image_bytes
+ return image.image_bytes
def del_image(self, name):
"""
@@ -131,41 +193,45 @@
"""
log.debug(u'add_image %s:%s' % (name, path))
if not name in self._cache:
- image = Image()
- image.name = name
- image.path = path
- image.image = resize_image(path, self.width, self.height)
+ image = Image(name, path)
self._cache[name] = image
+ self._clean_queue.put((image.priority, image))
else:
log.debug(u'Image in cache %s:%s' % (name, path))
self._cache_dirty = True
- # only one thread please
- if not self._thread_running:
- self.image_thread.start()
+ # We want only one thread.
+ if not self._image_thread.isRunning():
+ self._image_thread.start()
- def process(self):
- """
- Controls the processing called from a QThread
- """
- log.debug(u'process - started')
- self._thread_running = True
- self.clean_cache()
- # data loaded since we started ?
+ def _process(self):
+ """
+ Controls the processing called from a ``QtCore.QThread``.
+ """
+ log.debug(u'_process - started')
while self._cache_dirty:
- log.debug(u'process - recycle')
- self.clean_cache()
- self._thread_running = False
- log.debug(u'process - ended')
+ log.debug(u'_process - recycle')
+ self._clean_cache()
+ log.debug(u'_process - ended')
- def clean_cache(self):
+ def _clean_cache(self):
"""
Actually does the work.
"""
- log.debug(u'clean_cache')
- # we will clean the cache now
- self._cache_dirty = False
- for key in self._cache.keys():
- image = self._cache[key]
- if image.dirty:
- image.image_bytes = image_to_byte(image.image)
- image.dirty = False
+ log.debug(u'_clean_cache')
+ if self._clean_queue.empty():
+ print u'empty'
+ self._cache_dirty = False
+ return
+ image = self._clean_queue.get()[1]
+ if image.image is None:
+ print u'processing (image):', image.name, image.priority
+ image.image = resize_image(image.path, self.width, self.height)
+ if image.priority != Priority.Urgent:
+ self._clean_queue.task_done()
+ image.priority = Priority.Low
+ self._clean_queue.put((image.priority, image))
+ return
+ if image.image_bytes is None:
+ print u'processing (bytes):', image.name, image.priority
+ image.image_bytes = image_to_byte(image.image)
+ self._clean_queue.task_done()
=== modified file 'openlp/core/lib/mediamanageritem.py'
--- openlp/core/lib/mediamanageritem.py 2011-06-12 16:02:52 +0000
+++ openlp/core/lib/mediamanageritem.py 2011-06-22 15:13:52 +0000
@@ -374,44 +374,6 @@
count += 1
return filelist
- def validate(self, image, thumb):
- """
- Validates whether an image still exists and, if it does, is the
- thumbnail representation of the image up to date.
- """
- if not os.path.exists(unicode(image)):
- return False
- if os.path.exists(thumb):
- imageDate = os.stat(unicode(image)).st_mtime
- thumbDate = os.stat(unicode(thumb)).st_mtime
- # If image has been updated rebuild icon
- if imageDate > thumbDate:
- self.iconFromFile(image, thumb)
- else:
- self.iconFromFile(image, thumb)
- return True
-
- def iconFromFile(self, image_path, thumb_path):
- """
- Create a thumbnail icon from a given image.
-
- ``image_path``
- The image file to create the icon from.
-
- ``thumb_path``
- The filename to save the thumbnail to.
- """
- ext = os.path.splitext(thumb_path)[1].lower()
- reader = QtGui.QImageReader(image_path)
- ratio = float(reader.size().width()) / float(reader.size().height())
- reader.setScaledSize(QtCore.QSize(int(ratio * 88), 88))
- thumb = reader.read()
- thumb.save(thumb_path, ext[1:])
- if os.path.exists(thumb_path):
- return build_icon(unicode(thumb_path))
- # Fallback for files with animation support.
- return build_icon(unicode(image_path))
-
def loadList(self, list):
raise NotImplementedError(u'MediaManagerItem.loadList needs to be '
u'defined by the plugin')
=== modified file 'openlp/core/ui/slidecontroller.py'
--- openlp/core/ui/slidecontroller.py 2011-06-16 22:03:59 +0000
+++ openlp/core/ui/slidecontroller.py 2011-06-22 15:13:52 +0000
@@ -582,6 +582,9 @@
Loads a ServiceItem into the system from ServiceManager
Display the slide number passed
"""
+ import time
+ import datetime
+ start = time.time()
log.debug(u'processManagerItem live = %s' % self.isLive)
self.onStopLoop()
old_item = self.serviceItem
@@ -625,14 +628,14 @@
label.setMargin(4)
label.setScaledContents(True)
if self.serviceItem.is_command():
- image = QtGui.QImage(frame[u'image'])
+ label.setPixmap(QtGui.QPixmap(frame[u'image']))
else:
# If current slide set background to image
if framenumber == slideno:
self.serviceItem.bg_image_bytes = \
self.imageManager.get_image_bytes(frame[u'title'])
image = self.imageManager.get_image(frame[u'title'])
- label.setPixmap(QtGui.QPixmap.fromImage(image))
+ label.setPixmap(QtGui.QPixmap.fromImage(image))
self.previewListWidget.setCellWidget(framenumber, 0, label)
slideHeight = width * self.parent().renderer.screen_ratio
row += 1
@@ -668,6 +671,7 @@
self.onMediaClose()
Receiver.send_message(u'slidecontroller_%s_started' % self.typePrefix,
[serviceItem])
+ print unicode(datetime.timedelta(seconds=time.time() - start))
def __updatePreviewSelection(self, slideno):
"""
=== modified file 'openlp/core/ui/thememanager.py'
--- openlp/core/ui/thememanager.py 2011-06-12 16:02:52 +0000
+++ openlp/core/ui/thememanager.py 2011-06-22 15:13:52 +0000
@@ -36,7 +36,7 @@
from openlp.core.lib import OpenLPToolbar, get_text_file_string, build_icon, \
Receiver, SettingsManager, translate, check_item_selected, \
- check_directory_exists
+ check_directory_exists, create_thumb, validate_thumb
from openlp.core.lib.theme import ThemeXML, BackgroundType, VerticalType, \
BackgroundGradientType
from openlp.core.lib.ui import UiStrings, critical_error_message_box
@@ -364,7 +364,7 @@
The theme to delete.
"""
self.themelist.remove(theme)
- thumb = theme + u'.png'
+ thumb = u'%s.png' % theme
delete_file(os.path.join(self.path, thumb))
delete_file(os.path.join(self.thumbPath, thumb))
try:
@@ -478,15 +478,12 @@
name = textName
thumb = os.path.join(self.thumbPath, u'%s.png' % textName)
item_name = QtGui.QListWidgetItem(name)
- if os.path.exists(thumb):
+ if validate_thumb(theme, thumb):
icon = build_icon(thumb)
else:
- icon = build_icon(theme)
- pixmap = icon.pixmap(QtCore.QSize(88, 50))
- pixmap.save(thumb, u'png')
+ icon = create_thumb(theme, thumb)
item_name.setIcon(icon)
- item_name.setData(QtCore.Qt.UserRole,
- QtCore.QVariant(textName))
+ item_name.setData(QtCore.Qt.UserRole, QtCore.QVariant(textName))
self.themeListWidget.addItem(item_name)
self.themelist.append(textName)
self._pushThemes()
@@ -658,9 +655,7 @@
os.unlink(samplepathname)
frame.save(samplepathname, u'png')
thumb = os.path.join(self.thumbPath, u'%s.png' % name)
- icon = build_icon(frame)
- pixmap = icon.pixmap(QtCore.QSize(88, 50))
- pixmap.save(thumb, u'png')
+ create_thumb(samplepathname, thumb, False)
log.debug(u'Theme image written to %s', samplepathname)
def updatePreviewImages(self):
=== modified file 'openlp/plugins/images/lib/mediaitem.py'
--- openlp/plugins/images/lib/mediaitem.py 2011-06-12 17:56:11 +0000
+++ openlp/plugins/images/lib/mediaitem.py 2011-06-22 15:13:52 +0000
@@ -33,7 +33,7 @@
from openlp.core.lib import MediaManagerItem, build_icon, ItemCapabilities, \
SettingsManager, translate, check_item_selected, check_directory_exists, \
- Receiver
+ Receiver, create_thumb, validate_thumb
from openlp.core.lib.ui import UiStrings, critical_error_message_box
from openlp.core.utils import AppLocation, delete_file, get_images_filter
@@ -122,13 +122,13 @@
self.plugin.formparent.incrementProgressBar()
filename = os.path.split(unicode(imageFile))[1]
thumb = os.path.join(self.servicePath, filename)
- if os.path.exists(thumb):
- if self.validate(imageFile, thumb):
+ if not os.path.exists(imageFile):
+ icon = build_icon(u':/general/general_delete.png')
+ else:
+ if validate_thumb(imageFile, thumb):
icon = build_icon(thumb)
else:
- icon = build_icon(u':/general/general_delete.png')
- else:
- icon = self.iconFromFile(imageFile, thumb)
+ icon = create_thumb(imageFile, thumb)
item_name = QtGui.QListWidgetItem(filename)
item_name.setIcon(icon)
item_name.setData(QtCore.Qt.UserRole, QtCore.QVariant(imageFile))
=== modified file 'openlp/plugins/presentations/lib/mediaitem.py'
--- openlp/plugins/presentations/lib/mediaitem.py 2011-06-12 17:56:11 +0000
+++ openlp/plugins/presentations/lib/mediaitem.py 2011-06-22 15:13:52 +0000
@@ -32,7 +32,8 @@
from PyQt4 import QtCore, QtGui
from openlp.core.lib import MediaManagerItem, build_icon, SettingsManager, \
- translate, check_item_selected, Receiver, ItemCapabilities
+ translate, check_item_selected, Receiver, ItemCapabilities, create_thumb, \
+ validate_thumb
from openlp.core.lib.ui import UiStrings, critical_error_message_box, \
media_item_combo_box
from openlp.plugins.presentations.lib import MessageListener
@@ -190,10 +191,13 @@
doc.load_presentation()
preview = doc.get_thumbnail_path(1, True)
doc.close_presentation()
- if preview and self.validate(preview, thumb):
- icon = build_icon(thumb)
+ if not (preview and os.path.exists(preview)):
+ icon = build_icon(u':/general/general_delete.png')
else:
- icon = build_icon(u':/general/general_delete.png')
+ if validate_thumb(preview, thumb):
+ icon = build_icon(thumb)
+ else:
+ icon = create_thumb(preview, thumb)
else:
if initialLoad:
icon = build_icon(u':/general/general_delete.png')
=== modified file 'openlp/plugins/presentations/lib/presentationcontroller.py'
--- openlp/plugins/presentations/lib/presentationcontroller.py 2011-06-12 16:02:52 +0000
+++ openlp/plugins/presentations/lib/presentationcontroller.py 2011-06-22 15:13:52 +0000
@@ -31,7 +31,7 @@
from PyQt4 import QtCore
-from openlp.core.lib import Receiver, resize_image
+from openlp.core.lib import Receiver, create_thumb, resize_image, validate_thumb
from openlp.core.utils import AppLocation
log = logging.getLogger(__name__)
@@ -145,15 +145,13 @@
def check_thumbnails(self):
"""
- Returns true if the thumbnail images look to exist and are more
- recent than the powerpoint
+ Returns ``True`` if the thumbnail images exist and are more recent than
+ the powerpoint file.
"""
lastimage = self.get_thumbnail_path(self.get_slide_count(), True)
if not (lastimage and os.path.isfile(lastimage)):
return False
- imgdate = os.stat(lastimage).st_mtime
- pptdate = os.stat(self.filepath).st_mtime
- return imgdate >= pptdate
+ return validate_thumb(self.filepath, lastimage)
def close_presentation(self):
"""
@@ -246,8 +244,8 @@
if self.check_thumbnails():
return
if os.path.isfile(file):
- img = resize_image(file, 320, 240)
- img.save(self.get_thumbnail_path(idx, False))
+ thumb_path = self.get_thumbnail_path(idx, False)
+ create_thumb(file, thumb_path, False, QtCore.QSize(320, 240))
def get_thumbnail_path(self, slide_no, check_exists):
"""
Follow ups