Source code for ciowarehouse.handlers.handler_image

"""A file handler for images."""

from os import makedirs
from os.path import relpath, exists, splitext, join, dirname
from shutil import rmtree
from warnings import catch_warnings, simplefilter

from PIL import Image

from chrysalio.lib.utils import execute
from chrysalio.helpers.literal import Literal
from chrysalio.helpers.tags import image as tag_image
from ..lib.i18n import _
from ..lib.utils import THUMBNAIL_LARGE, THUMBNAIL_SMALL, thumbnail_create
from ..lib.handler import Handler


# =============================================================================
[docs]def includeme(configurator): """Function to include CioWarehouse a handler. :type configurator: pyramid.config.Configurator :param configurator: Object used to do configuration declaration within the application. """ Handler.register(configurator, HandlerImage)
# =============================================================================
[docs]class HandlerImage(Handler): """Class to manage an image.""" uid = 'image' label = _('Generic image file handling') extensions = ( '.png', '.jpg', '.jpeg', '.svg', '.gif', '.ico', '.tiff', '.tif', '.bmp', '.eps', '.nef', '.psd', '.ai') extensions_web = ('.png', '.jpg', '.jpeg', '.svg', '.gif', '.ico') viewings = ( {'name': 'default', 'label': _('Default'), 'template': 'ciowarehouse:Templates/handler_layout_view.pt', 'css': ('/ciowarehouse/css/handler_image.css',)},) # -------------------------------------------------------------------------
[docs] def thumbnails( self, warehouse, abs_file, thumb_dir, request=None, registry=None): """Create the small and large thumbnails representing the file. See: :meth:`.lib.handler.Handler.thumbnails` """ if not warehouse.lock(abs_file): return # PIL images ext = splitext(abs_file)[1].lower() if ext in ('.png', '.jpg', '.jpeg', 'gif', '.ico'): self._thumbnails_pil(warehouse, abs_file, thumb_dir, request) warehouse.unlock(abs_file) return # Inkscape images if ext == '.svg': self._thumbnails_inkscape(warehouse, abs_file, thumb_dir, request) warehouse.unlock(abs_file) return # Poppler images if ext == '.ai': self._thumbnails_poppler(warehouse, abs_file, thumb_dir, request) warehouse.unlock(abs_file) return # ImageMagick images self._thumbnails_im(warehouse, abs_file, thumb_dir, request) warehouse.unlock(abs_file)
# ------------------------------------------------------------------------- def _thumbnails_pil(self, warehouse, abs_file, thumb_dir, request=None): """Create the thumbnail with Pillow library. See: :meth:`.lib.handler.Handler.thumbnail` """ # Read image = None try: with catch_warnings(): simplefilter('ignore') image = Image.open(abs_file) except (IOError, ValueError) as error: self._log_error(_( 'Cannot read image ${w}/${i}: ${e}', { 'w': warehouse.uid, 'i': relpath(abs_file, warehouse.root), 'e': error}), request) return except Image.DecompressionBombError as error: # pragma: nocover self._log_warning( _('${i}: ${e}', {'i': relpath(abs_file, warehouse.root), 'e': error}), request) self._thumbnails_im(warehouse, abs_file, thumb_dir, request) return # Manage transparency if image.mode in ('RGBA', 'LA') or ( image.mode == 'P' and 'transparency' in image.info): abs_thumb = join(thumb_dir, '{0}.png'.format(THUMBNAIL_LARGE)) else: abs_thumb = join(thumb_dir, '{0}.jpg'.format(THUMBNAIL_LARGE)) # Create thumbnails if not thumbnail_create(image, warehouse.thumbnail_sizes, abs_thumb): self._thumbnails_im( warehouse, abs_file, thumb_dir, request) # pragma: nocover # ------------------------------------------------------------------------- def _thumbnails_inkscape(self, warehouse, abs_file, thumb_dir, request): """Create the thumbnail with Inkscape. See: :meth:`.handlers.HandlerImage.thumbnail` """ # Large thumbnail if exists(thumb_dir): rmtree(thumb_dir) makedirs(thumb_dir, exist_ok=True) abs_thumb = join(thumb_dir, '{0}.png'.format(THUMBNAIL_LARGE)) if warehouse.inkscape92: cmd = [ 'nice', 'inkscape', '--without-gui', '--export-dpi', '200', '--export-text-to-path', '--export-png', abs_thumb, abs_file] else: cmd = [ 'nice', 'inkscape', '--pdf-poppler', '--export-dpi', '200', '--export-type', 'png', '--export-filename', abs_thumb, abs_file] error = execute(cmd, cwd=dirname(abs_file))[1] if error: self._log_error( _('Cannot create large thumbnail for image ${w}/${i}', { 'w': warehouse.uid, 'i': relpath(abs_file, warehouse.root)}), request) rmtree(thumb_dir) return cmd = [ 'nice', 'mogrify', '-geometry', '{0}x{1}>'.format(*warehouse.thumbnail_sizes[0]), abs_thumb] execute(cmd) # Small thumbnail cmd = [ 'nice', 'convert', abs_thumb, '-geometry', '{0}x{1}>'.format(*warehouse.thumbnail_sizes[1]), join(thumb_dir, '{0}.{1}'.format( THUMBNAIL_SMALL, abs_thumb[-3:]))] execute(cmd) # ------------------------------------------------------------------------- def _thumbnails_poppler(self, warehouse, abs_file, thumb_dir, request): """Create the thumbnail with pdftocairo. See: :meth:`.handlers.HandlerImage.thumbnail` """ # Large thumbnail if exists(thumb_dir): rmtree(thumb_dir) makedirs(thumb_dir, exist_ok=True) error = execute( ['nice', 'pdftocairo', '-r', '200', '-singlefile', '-jpeg', '-scale-to', str(warehouse.thumbnail_sizes[0][0]), abs_file, join(thumb_dir, THUMBNAIL_LARGE)], cwd=dirname(abs_file))[1] if error: self._log_error( _('Cannot create large thumbnail for image ${w}/${i}', { 'w': warehouse.uid, 'i': relpath(abs_file, warehouse.root)}), request) rmtree(thumb_dir) return # Small thumbnail execute([ 'nice', 'convert', join(thumb_dir, '{0}.jpg'.format(THUMBNAIL_LARGE)), '-geometry', '{0}x{1}>'.format(*warehouse.thumbnail_sizes[1]), join(thumb_dir, '{0}.jpg'.format(THUMBNAIL_SMALL))]) # ------------------------------------------------------------------------- def _thumbnails_im(self, warehouse, abs_file, thumb_dir, request): """Create the thumbnail with ImageMagick. See: :meth:`.handlers.HandlerImage.thumbnail` """ cmd = ['nice', 'convert'] if splitext(abs_file)[1].lower() in ('.png', '.gif', '.svg', '.ai'): cmd += ['-background', 'none'] abs_thumb = join(thumb_dir, '{0}.png'.format(THUMBNAIL_LARGE)) else: abs_thumb = join(thumb_dir, '{0}.jpg'.format(THUMBNAIL_LARGE)) # Large thumbnail if exists(thumb_dir): rmtree(thumb_dir) makedirs(thumb_dir, exist_ok=True) cmd += [ '-colorspace', 'RGB', '-density', '200', '-strip', '{0}[0]'.format(abs_file), '-layers', 'flatten', '-geometry', '{0}x{1}>'.format(*warehouse.thumbnail_sizes[0]), abs_thumb] error = execute(cmd, cwd=dirname(abs_file))[1] if error: self._log_error( _('Cannot create large thumbnail for image ${w}/${i}', { 'w': warehouse.uid, 'i': relpath(abs_file, warehouse.root)}), request) rmtree(thumb_dir) return # Small thumbnail cmd = [ 'nice', 'convert', abs_thumb, '-geometry', '{0}x{1}>'.format(*warehouse.thumbnail_sizes[1]), join(thumb_dir, '{0}.{1}'.format( THUMBNAIL_SMALL, abs_thumb[-3:]))] execute(cmd) # -------------------------------------------------------------------------
[docs] def view(self, request, warehouse, content=None, ts_factory=None): """Return a string containing HTML to display the file. See: :meth:`.lib.handler.Handler.view` """ if splitext(request.matchdict['path'][-1])[1].lower() \ not in self.extensions_web: return None return self._chameleon_render( request, warehouse, self.viewings[0], ts_factory or _, { 'content': Literal('<div class="cioImage">{0}</div>'.format( tag_image( request.route_path( 'file_download', warehouse_id=warehouse.uid, path=join(*request.matchdict['path'])), alt=request.matchdict['path'][-1], title=join( warehouse.uid, *request.matchdict['path']))))})
# ------------------------------------------------------------------------- def _routes_around( self, request, warehouse, file_id, extensions_subset=None): """Return a tuple of URL to go to the previous and the next file according to the file type. See: :meth:`.lib.handler.Handler._routes_around` """ return super(HandlerImage, self)._routes_around( request, warehouse, file_id, extensions_subset=extensions_subset or self.extensions_web)