src/pyams_file/thumbnail.py
changeset 175 ab573883a5c7
parent 147 015342e304c8
child 201 50a5119ee57d
equal deleted inserted replaced
174:d49bcf382187 175:ab573883a5c7
    10 # FOR A PARTICULAR PURPOSE.
    10 # FOR A PARTICULAR PURPOSE.
    11 #
    11 #
    12 
    12 
    13 __docformat__ = 'restructuredtext'
    13 __docformat__ = 'restructuredtext'
    14 
    14 
       
    15 import logging
    15 import re
    16 import re
    16 
    17 
    17 import transaction
    18 import transaction
    18 from BTrees import OOBTree
    19 from BTrees import OOBTree
    19 from persistent.dict import PersistentDict
    20 from persistent.dict import PersistentDict
    20 from pyramid.events import subscriber
    21 from pyramid.events import subscriber
    21 from pyramid.threadlocal import get_current_registry
    22 from pyramid.threadlocal import get_current_registry
    22 from zope.interface import Interface, alsoProvides
    23 from zope.interface import Interface, alsoProvides
    23 from zope.lifecycleevent import ObjectAddedEvent, ObjectCreatedEvent, ObjectRemovedEvent
    24 from zope.lifecycleevent import IObjectRemovedEvent, ObjectAddedEvent, ObjectCreatedEvent, ObjectRemovedEvent
    24 from zope.location import locate
    25 from zope.location import locate
    25 from zope.traversing.interfaces import ITraversable
    26 from zope.traversing.interfaces import ITraversable
    26 
    27 
    27 from pyams_file.file import FileFactory
    28 from pyams_file.file import FileFactory
    28 from pyams_file.interfaces import IFileModifiedEvent, IImage, IThumbnailFile, IThumbnailer, IThumbnails, IWatermarker
    29 from pyams_file.interfaces import IFileModifiedEvent, IImage, IMediaFile, IThumbnailFile, IThumbnailer, IThumbnails, \
       
    30     IWatermarker
    29 from pyams_file.zmi.image import render_image
    31 from pyams_file.zmi.image import render_image
    30 from pyams_utils.adapter import ContextAdapter, ContextRequestViewAdapter, adapter_config, get_annotation_adapter
    32 from pyams_utils.adapter import ContextAdapter, ContextRequestViewAdapter, adapter_config, get_annotation_adapter
    31 from pyams_utils.interfaces.tales import ITALESExtension
    33 from pyams_utils.interfaces.tales import ITALESExtension
    32 from pyams_utils.registry import query_utility
    34 from pyams_utils.registry import query_utility
    33 from pyams_utils.request import check_request
    35 from pyams_utils.request import check_request
       
    36 
       
    37 
       
    38 logger = logging.getLogger('PyAMS (file)')
    34 
    39 
    35 
    40 
    36 THUMBNAIL_ANNOTATIONS_KEY = 'pyams_file.image.thumbnails'
    41 THUMBNAIL_ANNOTATIONS_KEY = 'pyams_file.image.thumbnails'
    37 THUMBNAIL_GEOMETRY_KEY = 'pyams_file.image.geometry'
    42 THUMBNAIL_GEOMETRY_KEY = 'pyams_file.image.geometry'
    38 
    43 
   121                 return '{0}x{1}'.format(*size)
   126                 return '{0}x{1}'.format(*size)
   122         else:
   127         else:
   123             return None, None
   128             return None, None
   124 
   129 
   125     def get_selection(self, selection_name, format=None):
   130     def get_selection(self, selection_name, format=None):
       
   131         logger.debug(">>> Requested thumbnail selection: {}".format(selection_name))
   126         if selection_name in self.thumbnails:
   132         if selection_name in self.thumbnails:
   127             return self.thumbnails[selection_name]
   133             return self.thumbnails[selection_name]
   128         geometry = self.get_geometry(selection_name)
   134         geometry = self.get_geometry(selection_name)
   129         registry = get_current_registry()
   135         if geometry == IThumbnailer(self.image).get_default_geometry():
   130         thumbnailer = registry.queryAdapter(self.image, IThumbnailer, name=selection_name)
   136             return self.image
   131         if thumbnailer is not None:
   137         else:
   132             selection = thumbnailer.create_thumbnail(geometry, format)
   138             registry = get_current_registry()
   133             if selection is not None:
   139             thumbnailer = registry.queryAdapter(self.image, IThumbnailer, name=selection_name)
   134                 if isinstance(selection, tuple):
   140             if thumbnailer is not None:
   135                     selection, format = selection
   141                 selection = thumbnailer.create_thumbnail(geometry, format)
   136                 else:
   142                 if selection is not None:
   137                     format = 'jpeg'
   143                     if isinstance(selection, tuple):
   138                 selection = FileFactory(selection)
   144                         selection, format = selection
   139                 alsoProvides(selection, IThumbnailFile)
   145                     else:
   140                 registry.notify(ObjectCreatedEvent(selection))
   146                         format = 'jpeg'
   141                 self.thumbnails[selection_name] = selection
   147                     selection = FileFactory(selection)
   142                 selection_size = selection.get_image_size()
   148                     alsoProvides(selection, IThumbnailFile)
   143                 locate(selection, self.image,
   149                     registry.notify(ObjectCreatedEvent(selection))
   144                        '++thumb++{0}:{1}x{2}.{3}'.format(selection_name,
   150                     self.thumbnails[selection_name] = selection
   145                                                          selection_size[0],
   151                     selection_size = selection.get_image_size()
   146                                                          selection_size[1],
   152                     locate(selection, self.image,
   147                                                          format))
   153                            '++thumb++{0}:{1}x{2}.{3}'.format(selection_name,
   148                 registry.notify(ObjectAddedEvent(selection))
   154                                                              selection_size[0],
   149                 return selection
   155                                                              selection_size[1],
       
   156                                                              format))
       
   157                     logger.debug("  > Generated thumbnail selection: {}".format(selection.__name__))
       
   158                     registry.notify(ObjectAddedEvent(selection))
       
   159                     return selection
   150 
   160 
   151     def get_thumbnail(self, thumbnail_name, format=None, watermark=None):
   161     def get_thumbnail(self, thumbnail_name, format=None, watermark=None):
       
   162         logger.debug(">>> Requested thumbnail: {}".format(thumbnail_name))
   152         # check for existing thumbnail
   163         # check for existing thumbnail
   153         if thumbnail_name in self.thumbnails:
   164         if thumbnail_name in self.thumbnails:
   154             return self.thumbnails[thumbnail_name]
   165             return self.thumbnails[thumbnail_name]
   155         # check for selection thumbnail, in "selection:size" format
   166         # check for selection thumbnail, in "selection:size" format
   156         if ':' in thumbnail_name:
   167         if ':' in thumbnail_name:
   202                        '++thumb++{0}{1}{2}x{3}.{4}'.format(thumbnailer_name,
   213                        '++thumb++{0}{1}{2}x{3}.{4}'.format(thumbnailer_name,
   203                                                            ':' if thumbnailer_name else '',
   214                                                            ':' if thumbnailer_name else '',
   204                                                            thumbnail_size[0],
   215                                                            thumbnail_size[0],
   205                                                            thumbnail_size[1],
   216                                                            thumbnail_size[1],
   206                                                            format))
   217                                                            format))
       
   218                 logger.debug("  < Generated thumbnail: {}".format(thumbnail_image.__name__))
   207                 registry.notify(ObjectAddedEvent(thumbnail_image))
   219                 registry.notify(ObjectAddedEvent(thumbnail_image))
   208                 return thumbnail_image
   220                 return thumbnail_image
   209 
   221 
   210     def delete_thumbnail(self, thumbnail_name):
   222     def delete_thumbnail(self, thumbnail_name):
   211         if thumbnail_name in self.thumbnails:
   223         if thumbnail_name in self.thumbnails:
   212             thumbnail_image = self.thumbnails[thumbnail_name]
   224             thumbnail_image = self.thumbnails[thumbnail_name]
   213             registry = check_request().registry
   225             registry = get_current_registry()
   214             registry.notify(ObjectRemovedEvent(thumbnail_image))
   226             registry.notify(ObjectRemovedEvent(thumbnail_image))
   215             del self.thumbnails[thumbnail_name]
   227             del self.thumbnails[thumbnail_name]
       
   228             logger.debug(">>> Removed thumbnail: {}".format(thumbnail_name))
   216 
   229 
   217     def clear_thumbnails(self):
   230     def clear_thumbnails(self):
   218         [self.delete_thumbnail(thumbnail_name) for thumbnail_name in list(self.thumbnails.keys())]
   231         [self.delete_thumbnail(thumbnail_name) for thumbnail_name in list(self.thumbnails.keys())]
   219 
   232 
   220 
   233 
   221 @subscriber(IFileModifiedEvent, context_selector=IImage)
   234 @subscriber(IFileModifiedEvent, context_selector=IMediaFile)
   222 def handle_modified_file(event):
   235 @subscriber(IObjectRemovedEvent, context_selector=IMediaFile)
   223     thumbnail = IThumbnails(event.object)
   236 def handle_modified_image(event):
   224     thumbnail.clear_geometries()
   237     """Clear thumbnails when an image is updated or removed"""
   225     thumbnail.clear_thumbnails()
   238     thumbnails = IThumbnails(event.object, None)
       
   239     if thumbnails is not None:
       
   240         thumbnails.clear_geometries()
       
   241         thumbnails.clear_thumbnails()
   226 
   242 
   227 
   243 
   228 @adapter_config(name='thumb', context=IImage, provides=ITraversable)
   244 @adapter_config(name='thumb', context=IImage, provides=ITraversable)
   229 class ThumbnailTraverser(ContextAdapter):
   245 class ThumbnailTraverser(ContextAdapter):
   230     """++thumb++ namespace traverser"""
   246     """++thumb++ namespace traverser"""
   259         return IThumbnails(context, None)
   275         return IThumbnails(context, None)
   260 
   276 
   261 
   277 
   262 @adapter_config(name='thumbnail', context=(Interface, Interface, Interface), provides=ITALESExtension)
   278 @adapter_config(name='thumbnail', context=(Interface, Interface, Interface), provides=ITALESExtension)
   263 class ThumbnailExtension(ContextRequestViewAdapter):
   279 class ThumbnailExtension(ContextRequestViewAdapter):
   264     """extension:thumbnail(image, width, height) TALES extension
   280     """extension:thumbnail(image, width, height, css_class, img_class) TALES extension
   265 
   281 
   266     This TALES extension doesn't return an adapter but HTML code matching given image and dimensions.
   282     This TALES extension doesn't return an adapter but HTML code matching given image and dimensions.
   267     If image is a classic image, an "img" tag with source to thumbnail of required size is returned.
   283     If image is a classic image, an "img" tag with source to thumbnail of required size is returned.
   268     If image in an SVG image, a "div" is returned containing whole SVG data of given image.
   284     If image in an SVG image, a "div" is returned containing whole SVG data of given image.
   269     """
   285     """