Exemplo n.º 1
0
 def test02_derive(self):
     m = IIIFManipulator()
     r = IIIFRequest()
     r.parse_url('id1/full/full/0/default')
     tmp = tempfile.mkdtemp()
     outfile = os.path.join(tmp,'testout.png')
     try:
         m.derive(srcfile='testimages/test1.png',
                  request=r, outfile=outfile);
         self.assertTrue( os.path.isfile(outfile) )
         self.assertEqual( os.path.getsize(outfile), 65810 )
     finally:
         shutil.rmtree(tmp) 
     # and where path to outfile must be created
     outfile = os.path.join(tmp,'a','b','testout.png')
     try:
         m.derive(srcfile='testimages/test1.png',
                  request=r, outfile=outfile);
         self.assertTrue( os.path.isfile(outfile) )
         self.assertEqual( os.path.getsize(outfile), 65810 )
     finally:
         shutil.rmtree(tmp) 
Exemplo n.º 2
0
 def test02_derive(self):
     """Test derive method by running complete conversions."""
     m = IIIFManipulator()
     r = IIIFRequest()
     r.parse_url('id1/full/full/0/default')
     tmp = tempfile.mkdtemp()
     outfile = os.path.join(tmp, 'testout.png')
     try:
         m.derive(srcfile='testimages/test1.png',
                  request=r, outfile=outfile)
         self.assertTrue(os.path.isfile(outfile))
         self.assertEqual(os.path.getsize(outfile), 65810)
     finally:
         shutil.rmtree(tmp)
     # and where path to outfile must be created
     outfile = os.path.join(tmp, 'a', 'b', 'testout.png')
     try:
         m.derive(srcfile='testimages/test1.png',
                  request=r, outfile=outfile)
         self.assertTrue(os.path.isfile(outfile))
         self.assertEqual(os.path.getsize(outfile), 65810)
     finally:
         shutil.rmtree(tmp)
Exemplo n.º 3
0
class IIIFHandler(object):
    """IIIFHandler class."""
    def __init__(self, prefix, identifier, config, klass, auth):
        """Initialize IIIFHandler setting key configurations.

        Positional parameters:
        prefix -- URI prefix (without leading or trailing slashes)
        identifier -- identifier of image
        config -- instance of Config class
        klass -- IIIFManipulator sub-class to do manipulations
        auth -- IIIFAuth sub-class for auth
        """
        self.prefix = prefix
        self.identifier = identifier
        self.config = config
        self.klass = klass
        self.api_version = config.api_version
        self.auth = auth
        self.degraded = False
        self.logger = logging.getLogger('IIIFHandler')
        #
        # Create objects to process request
        self.iiif = IIIFRequest(api_version=self.api_version,
                                identifier=self.identifier)
        self.manipulator = klass(api_version=self.api_version)
        #
        # Set up auth object with locations if not already done
        if (self.auth and not self.auth.login_uri):
            self.auth.login_uri = self.server_and_prefix + '/login'
            if (self.auth.logout_handler is not None):
                self.auth.logout_uri = self.server_and_prefix + '/logout'
            self.auth.access_token_uri = self.server_and_prefix + '/token'
        #
        # Response headers
        # -- All responses should have CORS header
        self.headers = {'Access-control-allow-origin': '*'}

    @property
    def server_and_prefix(self):
        """Server and prefix from config."""
        return (host_port_prefix(self.config.host, self.config.port,
                                 self.prefix))

    @property
    def json_mime_type(self):
        """Return the MIME type for a JSON response.

        For version 2.0+ the server must return json-ld MIME type if that
        format is requested. Implement for 1.1 also.
        http://iiif.io/api/image/2.1/#information-request
        """
        mime_type = "application/json"
        if (self.api_version >= '1.1' and 'Accept' in request.headers):
            mime_type = do_conneg(request.headers['Accept'],
                                  ['application/ld+json']) or mime_type
        return mime_type

    @property
    def file(self):
        """Filename property for the source image for the current identifier."""
        file = None
        if (self.config.klass_name == 'gen'):
            for ext in ['.py']:
                file = os.path.join(self.config.generator_dir,
                                    self.identifier + ext)
                if (os.path.isfile(file)):
                    return file
        else:
            for ext in ['.jpg', '.png', '.tif']:
                file = os.path.join(self.config.image_dir,
                                    self.identifier + ext)
                if (os.path.isfile(file)):
                    return file
        # failed, show list of available identifiers as error
        available = "\n ".join(identifiers(self.config))
        raise IIIFError(code=404,
                        parameter="identifier",
                        text="Image resource '" + self.identifier +
                        "' not found. Local resources available:" + available +
                        "\n")

    def add_compliance_header(self):
        """Add IIIF Compliance level header to response."""
        if (self.manipulator.compliance_uri is not None):
            self.headers['Link'] = '<' + \
                self.manipulator.compliance_uri + '>;rel="profile"'

    def make_response(self, content, code=200, headers=None):
        """Wrapper around Flask.make_response which also adds any local headers."""
        if headers:
            for header in headers:
                self.headers[header] = headers[header]
        return make_response(content, code, self.headers)

    def image_information_response(self):
        """Parse image information request and create response."""
        dr = degraded_request(self.identifier)
        if (dr):
            self.logger.info("image_information: degraded %s -> %s" %
                             (self.identifier, dr))
            self.degraded = self.identifier
            self.identifier = dr
        else:
            self.logger.info("image_information: %s" % (self.identifier))
        # get size
        self.manipulator.srcfile = self.file
        self.manipulator.do_first()
        # most of info.json comes from config, a few things specific to image
        info = {
            'tile_height': self.config.tile_height,
            'tile_width': self.config.tile_width,
            'scale_factors': self.config.scale_factors
        }
        # calculate scale factors if not hard-coded
        if ('auto' in self.config.scale_factors):
            info['scale_factors'] = self.manipulator.scale_factors(
                self.config.tile_width, self.config.tile_height)
        i = IIIFInfo(conf=info, api_version=self.api_version)
        i.server_and_prefix = self.server_and_prefix
        i.identifier = self.iiif.identifier
        i.width = self.manipulator.width
        i.height = self.manipulator.height
        if (self.api_version >= '2.0'):
            # FIXME - should come from manipulator
            i.qualities = ["default", "color", "gray"]
        else:
            # FIXME - should come from manipulator
            i.qualities = ["native", "color", "gray"]
        i.formats = ["jpg", "png"]  # FIXME - should come from manipulator
        if (self.auth):
            self.auth.add_services(i)
        return self.make_response(
            i.as_json(), headers={"Content-Type": self.json_mime_type})

    def image_request_response(self, path):
        """Parse image request and create response."""
        # Parse the request in path
        if (len(path) > 1024):
            raise IIIFError(code=414,
                            text="URI Too Long: Max 1024 chars, got %d\n" %
                            len(path))
        # print "GET " + path
        try:
            self.iiif.identifier = self.identifier
            self.iiif.parse_url(path)
        except IIIFRequestPathError as e:
            # Reraise as IIIFError with code=404 because we can't tell
            # whether there was an encoded slash in the identifier or
            # whether there was a bad number of path segments.
            raise IIIFError(code=404, text=e.text)
        except IIIFRequestBaseURI as e:
            info_uri = self.server_and_prefix + '/' + \
                urlquote(self.iiif.identifier) + '/info.json'
            raise IIIFError(code=303, headers={'Location': info_uri})
        except IIIFError as e:
            # Pass through
            raise e
        except Exception as e:
            # Something completely unexpected => 500
            raise IIIFError(
                code=500,
                text=
                "Internal Server Error: unexpected exception parsing request ("
                + str(e) + ")")
        dr = degraded_request(self.identifier)
        if (dr):
            self.logger.info("image_request: degraded %s -> %s" %
                             (self.identifier, dr))
            self.degraded = self.identifier
            self.identifier = dr
            self.iiif.quality = 'gray'
        else:
            # Parsed request OK, attempt to fulfill
            self.logger.info("image_request: %s" % (self.identifier))
        file = self.file
        self.manipulator.srcfile = file
        self.manipulator.do_first()
        if (self.api_version < '2.0' and self.iiif.format is None
                and 'Accept' in request.headers):
            # In 1.0 and 1.1 conneg was specified as an alternative to format, see:
            # http://iiif.io/api/image/1.0/#format
            # http://iiif.io/api/image/1.1/#parameters-format
            formats = {
                'image/jpeg': 'jpg',
                'image/tiff': 'tif',
                'image/png': 'png',
                'image/gif': 'gif',
                'image/jp2': 'jps',
                'application/pdf': 'pdf'
            }
            accept = do_conneg(request.headers['Accept'], list(formats.keys()))
            # Ignore Accept header if not recognized, should this be an error
            # instead?
            if (accept in formats):
                self.iiif.format = formats[accept]
        (outfile, mime_type) = self.manipulator.derive(file, self.iiif)
        # FIXME - find efficient way to serve file with headers
        self.add_compliance_header()
        return send_file(outfile, mimetype=mime_type)

    def error_response(self, e):
        """Make response for an IIIFError e.

        Also add compliance header.
        """
        self.add_compliance_header()
        return self.make_response(*e.image_server_response(self.api_version))
Exemplo n.º 4
0
 def test03_is_scaled_full_image(self):
     """Test check for whether request is scaled full image."""
     r = IIIFRequest(identifier='dummy',
                     api_version='2.1')
     r.parse_url('full/full/0/default.jpg')
     self.assertFalse(r.is_scaled_full_image())
     r.parse_url('full/!10,10/0/default.jpg')
     self.assertFalse(r.is_scaled_full_image())
     r.parse_url('10,10,90,90/10,10/0/default.jpg')
     self.assertFalse(r.is_scaled_full_image())
     r.parse_url('full/10,10/1/default.jpg')
     self.assertFalse(r.is_scaled_full_image())
     r.parse_url('full/10,10/0/default.png')
     self.assertFalse(r.is_scaled_full_image())
     r.parse_url('full/10,10/0/color.jpg')
     self.assertFalse(r.is_scaled_full_image())
     #
     r = IIIFRequest(identifier='dummy',
                     api_version='2.1')
     r.parse_url('full/10,10/0/default.jpg')
     self.assertTrue(r.is_scaled_full_image())
Exemplo n.º 5
0
class IIIFHandler(object):
    """IIIFHandler class."""

    def __init__(self, prefix, identifier, config, klass, auth):
        """Initialize IIIFHandler setting key configurations.

        Positional parameters:
        prefix -- URI prefix (without leading or trailing slashes)
        identifier -- identifier of image
        config -- instance of Config class
        klass -- IIIFManipulator sub-class to do manipulations
        auth -- IIIFAuth sub-class instance for auth or None
        """
        self.prefix = prefix
        self.identifier = identifier
        self.config = config
        self.klass = klass
        self.api_version = config.api_version
        self.auth = auth
        self.degraded = False
        self.logger = logging.getLogger('IIIFHandler')
        #
        # Create objects to process request
        self.iiif = IIIFRequest(api_version=self.api_version,
                                identifier=self.identifier)
        self.manipulator = klass(api_version=self.api_version)
        #
        # Set up auth object with locations if not already done
        if (self.auth and not self.auth.login_uri):
            self.auth.login_uri = self.server_and_prefix + '/login'
            if (self.auth.logout_handler is not None):
                self.auth.logout_uri = self.server_and_prefix + '/logout'
            self.auth.access_token_uri = self.server_and_prefix + '/token'
        #
        # Response headers
        # -- All responses should have CORS header
        self.headers = {'Access-control-allow-origin': '*'}

    @property
    def server_and_prefix(self):
        """Server and prefix from config."""
        return(host_port_prefix(self.config.host, self.config.port, self.prefix))

    @property
    def json_mime_type(self):
        """Return the MIME type for a JSON response.

        For version 2.0+ the server must return json-ld MIME type if that
        format is requested. Implement for 1.1 also.
        http://iiif.io/api/image/2.1/#information-request
        """
        mime_type = "application/json"
        if (self.api_version >= '1.1' and 'Accept' in request.headers):
            mime_type = do_conneg(request.headers['Accept'], [
                                  'application/ld+json']) or mime_type
        return mime_type

    @property
    def file(self):
        """Filename property for the source image for the current identifier."""
        file = None
        if (self.config.klass_name == 'gen'):
            for ext in ['.py']:
                file = os.path.join(
                    self.config.generator_dir, self.identifier + ext)
                if (os.path.isfile(file)):
                    return file
        else:
            for ext in ['.jpg', '.png', '.tif']:
                file = os.path.join(self.config.image_dir,
                                    self.identifier + ext)
                if (os.path.isfile(file)):
                    return file
        # failed, show list of available identifiers as error
        available = "\n ".join(identifiers(self.config))
        raise IIIFError(code=404, parameter="identifier",
                        text="Image resource '" + self.identifier + "' not found. Local resources available:" + available + "\n")

    def add_compliance_header(self):
        """Add IIIF Compliance level header to response."""
        if (self.manipulator.compliance_uri is not None):
            self.headers['Link'] = '<' + \
                self.manipulator.compliance_uri + '>;rel="profile"'

    def make_response(self, content, code=200, headers=None):
        """Wrapper around Flask.make_response which also adds any local headers."""
        if headers:
            for header in headers:
                self.headers[header] = headers[header]
        return make_response(content, code, self.headers)

    def image_information_response(self):
        """Parse image information request and create response."""
        dr = degraded_request(self.identifier)
        if (dr):
            self.logger.info("image_information: degraded %s -> %s" %
                             (self.identifier, dr))
            self.degraded = self.identifier
            self.identifier = dr
        else:
            self.logger.info("image_information: %s" % (self.identifier))
        # get size
        self.manipulator.srcfile = self.file
        self.manipulator.do_first()
        # most of info.json comes from config, a few things specific to image
        info = {'tile_height': self.config.tile_height,
                'tile_width': self.config.tile_width,
                'scale_factors': self.config.scale_factors
                }
        # calculate scale factors if not hard-coded
        if ('auto' in self.config.scale_factors):
            info['scale_factors'] = self.manipulator.scale_factors(
                self.config.tile_width, self.config.tile_height)
        i = IIIFInfo(conf=info, api_version=self.api_version)
        i.server_and_prefix = self.server_and_prefix
        i.identifier = self.iiif.identifier
        i.width = self.manipulator.width
        i.height = self.manipulator.height
        if (self.api_version >= '2.0'):
            # FIXME - should come from manipulator
            i.qualities = ["default", "color", "gray"]
        else:
            # FIXME - should come from manipulator
            i.qualities = ["native", "color", "gray"]
        i.formats = ["jpg", "png"]  # FIXME - should come from manipulator
        if (self.auth):
            self.auth.add_services(i)
        return self.make_response(i.as_json(),
                                  headers={"Content-Type": self.json_mime_type})

    def image_request_response(self, path):
        """Parse image request and create response."""
        # Parse the request in path
        if (len(path) > 1024):
            raise IIIFError(code=414,
                            text="URI Too Long: Max 1024 chars, got %d\n" % len(path))
        try:
            self.iiif.identifier = self.identifier
            self.iiif.parse_url(path)
        except IIIFRequestPathError as e:
            # Reraise as IIIFError with code=404 because we can't tell
            # whether there was an encoded slash in the identifier or
            # whether there was a bad number of path segments.
            raise IIIFError(code=404, text=e.text)
        except IIIFError as e:
            # Pass through
            raise e
        except Exception as e:
            # Something completely unexpected => 500
            raise IIIFError(code=500,
                            text="Internal Server Error: unexpected exception parsing request (" + str(e) + ")")
        dr = degraded_request(self.identifier)
        if (dr):
            self.logger.info("image_request: degraded %s -> %s" %
                             (self.identifier, dr))
            self.degraded = self.identifier
            self.identifier = dr
            self.iiif.quality = 'gray'
        else:
            # Parsed request OK, attempt to fulfill
            self.logger.info("image_request: %s" % (self.identifier))
        file = self.file
        self.manipulator.srcfile = file
        self.manipulator.do_first()
        if (self.api_version < '2.0' and
                self.iiif.format is None and
                'Accept' in request.headers):
            # In 1.0 and 1.1 conneg was specified as an alternative to format, see:
            # http://iiif.io/api/image/1.0/#format
            # http://iiif.io/api/image/1.1/#parameters-format
            formats = {'image/jpeg': 'jpg', 'image/tiff': 'tif',
                       'image/png': 'png', 'image/gif': 'gif',
                       'image/jp2': 'jps', 'application/pdf': 'pdf'}
            accept = do_conneg(request.headers['Accept'], list(formats.keys()))
            # Ignore Accept header if not recognized, should this be an error
            # instead?
            if (accept in formats):
                self.iiif.format = formats[accept]
        (outfile, mime_type) = self.manipulator.derive(file, self.iiif)
        # FIXME - find efficient way to serve file with headers
        self.add_compliance_header()
        return send_file(outfile, mimetype=mime_type)

    def error_response(self, e):
        """Make response for an IIIFError e.

        Also add compliance header.
        """
        self.add_compliance_header()
        return self.make_response(*e.image_server_response(self.api_version))
Exemplo n.º 6
0
 def test03_is_scaled_full_image(self):
     """Test check for whether request is scaled full image."""
     r = IIIFRequest(identifier='dummy', api_version='2.1')
     r.parse_url('full/full/0/default.jpg')
     self.assertFalse(r.is_scaled_full_image())
     r.parse_url('full/!10,10/0/default.jpg')
     self.assertFalse(r.is_scaled_full_image())
     r.parse_url('10,10,90,90/10,10/0/default.jpg')
     self.assertFalse(r.is_scaled_full_image())
     r.parse_url('full/10,10/1/default.jpg')
     self.assertFalse(r.is_scaled_full_image())
     r.parse_url('full/10,10/0/default.png')
     self.assertFalse(r.is_scaled_full_image())
     r.parse_url('full/10,10/0/color.jpg')
     self.assertFalse(r.is_scaled_full_image())
     #
     r = IIIFRequest(identifier='dummy', api_version='2.1')
     r.parse_url('full/10,10/0/default.jpg')
     self.assertTrue(r.is_scaled_full_image())