# -*- coding: utf-8 -*-
#
# This file is part of Flask-IIIF
# Copyright (C) 2014, 2015, 2016, 2017 CERN.
# Copyright (C) 2020 data-futures.
#
# Flask-IIIF is free software; you can redistribute it and/or modify
# it under the terms of the Revised BSD License; see LICENSE file for
# more details.
"""Multimedia IIIF Image API."""
import datetime
from email.utils import parsedate
from io import BytesIO
from flask import Response, current_app, jsonify, redirect, request, \
send_file, url_for
from flask_restful import Resource
from flask_restful.utils import cors
from werkzeug.local import LocalProxy
from werkzeug.utils import secure_filename
from .api import IIIFImageAPIWrapper
from .decorators import api_decorator, error_handler
from .signals import iiif_after_info_request, iiif_after_process_request, \
iiif_before_info_request, iiif_before_process_request
from .utils import should_cache
current_iiif = LocalProxy(lambda: current_app.extensions['iiif'])
[docs]class IIIFImageBase(Resource):
"""IIIF Image Base."""
[docs] def get(self, version, uuid):
"""Get IIIF Image Base.
.. note::
It will redirect to ``iiifimageinfo`` endpoint with status code
303.
"""
return redirect(
url_for('iiifimageinfo', version=version, uuid=uuid), code=303
)
[docs]class IIIFImageInfo(Resource):
"""IIIF Image Info."""
method_decorators = [
error_handler,
api_decorator,
]
[docs] @cors.crossdomain(origin='*', methods='GET')
def get(self, version, uuid):
"""Get IIIF Image Info."""
# Trigger event before proccess the api request
iiif_before_info_request.send(self, version=version, uuid=uuid)
# build the image key
key = u"iiif:info:{0}/{1}".format(
version, uuid
).encode('utf8')
# Check if its cached
try:
cached = current_iiif.cache.get(key)
except Exception:
if current_app.config.get('IIIF_CACHE_IGNORE_ERRORS', False):
cached = None
else:
raise
# If the image size is cached loaded from cache
if cached:
width, height = map(int, cached.split(','))
else:
data = current_iiif.uuid_to_image_opener(uuid)
image = IIIFImageAPIWrapper.open_image(data)
width, height = image.size()
if should_cache(request.args):
try:
current_iiif.cache.set(
key, "{0},{1}".format(width, height))
except Exception:
if not current_app.config.get(
'IIIF_CACHE_IGNORE_ERRORS', False):
raise
data = current_app.config['IIIF_API_INFO_RESPONSE_SKELETON'][version]
base_uri = url_for(
'iiifimagebase',
uuid=uuid,
version=version,
_external=True
)
data["@id"] = base_uri
data["width"] = width
data["height"] = height
# Trigger event after proccess the api request
iiif_after_info_request.send(self, **data)
resp = jsonify(data)
if 'application/ld+json' in request.headers.get('Accept', ''):
resp.mimetype = 'application/ld+json'
return resp
[docs]class IIIFImageAPI(Resource):
"""IIIF API Implementation.
.. note::
* IIF IMAGE API v1.0
* For more infos please visit <http://iiif.io/api/image/>.
* IIIF Image API v2.0
* For more infos please visit <http://iiif.io/api/image/2.0/>.
* The API works only for GET requests
* The image process must follow strictly the following workflow:
* Region
* Size
* Rotation
* Quality
* Format
"""
method_decorators = [
error_handler,
api_decorator,
]
[docs] def get(self, version, uuid, region, size, rotation, quality,
image_format):
"""Run IIIF Image API workflow."""
api_parameters = dict(
version=version,
uuid=uuid,
region=region,
size=size,
rotation=rotation,
quality=quality,
image_format=image_format
)
# Trigger event before proccess the api request
iiif_before_process_request.send(self, **api_parameters)
# Validate IIIF parameters
IIIFImageAPIWrapper.validate_api(**api_parameters)
# build the image key
key = u'iiif:{0}/{1}/{2}/{3}/{4}.{5}'.format(
uuid, region, size, quality, rotation, image_format
).encode('utf8')
# Check if its cached
try:
cached = current_iiif.cache.get(key)
except Exception:
if current_app.config.get('IIIF_CACHE_IGNORE_ERRORS', False):
cached = None
else:
raise
# If the image is cached loaded from cache
if cached:
to_serve = BytesIO(cached)
to_serve.seek(0)
# Otherwise create the image
else:
data = current_iiif.uuid_to_image_opener(uuid)
image = IIIFImageAPIWrapper.open_image(data)
image.apply_api(
version=version,
region=region,
size=size,
rotation=rotation,
quality=quality
)
# prepare image to be serve
to_serve = image.serve(image_format=image_format)
# to_serve = image.serve(image_format=image_format)
if should_cache(request.args):
try:
current_iiif.cache.set(key, to_serve.getvalue())
except Exception:
if not current_app.config.get(
'IIIF_CACHE_IGNORE_ERRORS', False):
raise
try:
last_modified = current_iiif.cache.get_last_modification(key)
except Exception:
if not current_app.config.get('IIIF_CACHE_IGNORE_ERRORS', False):
raise
last_modified = None
# decide the mime_type from the requested image_format
mimetype = current_app.config['IIIF_FORMATS'].get(
image_format, 'image/jpeg'
)
# Built the after request parameters
api_after_request_parameters = dict(
mimetype=mimetype,
image=to_serve
)
# Trigger event after proccess the api request
iiif_after_process_request.send(self, **api_after_request_parameters)
send_file_kwargs = {'mimetype': mimetype}
# last_modified is not supported before flask 0.12
additional_headers = []
if last_modified:
send_file_kwargs.update(last_modified=last_modified)
if 'dl' in request.args:
filename = secure_filename(request.args.get('dl', ''))
if filename.lower() in {'', '1', 'true'}:
filename = u'{0}-{1}-{2}-{3}-{4}.{5}'.format(
uuid, region, size, quality, rotation, image_format
)
send_file_kwargs.update(
as_attachment=True,
attachment_filename=secure_filename(filename),
)
if_modified_since_raw = request.headers.get('If-Modified-Since')
if if_modified_since_raw:
if_modified_since = datetime.datetime(
*parsedate(if_modified_since_raw)[:6]
)
if if_modified_since and if_modified_since >= last_modified:
return Response(status=304)
response = send_file(to_serve, **send_file_kwargs)
if additional_headers:
response.headers.extend(additional_headers)
return response