"""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)