Exemplo n.º 1
0
    def distributed_collage(self, callback, orientation, alignment, urls):
        logger.debug('filters.distributed_collage: distributed_collage invoked')
        self.storage = self.context.modules.storage

        self.callback = callback
        self.orientation = orientation
        self.alignment = alignment
        self.urls = urls.split('|')
        self.images = {}

        total = len(self.urls)
        if total > self.MAX_IMAGES:
            logger.error('filters.distributed_collage: Too many images to join')
            callback()
        elif total == 0:
            logger.error('filters.distributed_collage: No images to join')
            callback()
        else:
            self.urls = self.urls[:self.MAX_IMAGES]
            for url in self.urls:
                self.images[url] = Picture(url, self)

            # second loop needed to ensure that all images are in self.images
            # otherwise, self.on_image_fetch can call the self.assembly()
            # without that all images had being loaded
            for url in self.urls:
                buffer = yield tornado.gen.maybe_future(self.storage.get(url))
                pic = self.images[url]
                if buffer is not None:
                    pic.fill_buffer(buffer)
                    self.on_image_fetch()
                else:
                    pic.request()
Exemplo n.º 2
0
    def dispatch(self, file_key):
        """ Callback method for getObject from s3 """
        if not file_key or 'Error' in file_key or 'Body' not in file_key:

            logger.error(
                "ERROR retrieving image from S3 {0}: {1}".
                format(self.key, str(file_key)))

            # If we got here, there was a failure.
            # We will return 404 if S3 returned a 404, otherwise 502.
            result = LoaderResult()
            result.successful = False

            if not file_key:
                result.error = LoaderResult.ERROR_UPSTREAM
                self.callback(result)
                return

            response_metadata = file_key.get('ResponseMetadata', {})
            status_code = response_metadata.get('HTTPStatusCode')

            if status_code == 404:
                result.error = LoaderResult.ERROR_NOT_FOUND
                self.callback(result)
                return

            if self.retries_counter < self.max_retry:
                self.__increment_retry_counter()
                self.bucket_loader.get(self.key,
                                       callback=self.dispatch)
            else:
                result.error = LoaderResult.ERROR_UPSTREAM
                self.callback(result)
        else:
            self.callback(file_key['Body'].read())
Exemplo n.º 3
0
    def load(self, buffer, extension):
        self.extension = extension

        if extension is None:
            mime = self.get_mimetype(buffer)
            self.extension = EXTENSION.get(mime, '.jpg')

        if self.extension == '.svg':
            buffer = self.convert_svg_to_png(buffer)

        image_or_frames = self.create_image(buffer)

        if METADATA_AVAILABLE:
            try:
                self.metadata = ImageMetadata.from_buffer(buffer)
                self.metadata.read()
            except Exception as e:
                logger.error('Error reading image metadata: %s' % e)

        if self.context.config.ALLOW_ANIMATED_GIFS and isinstance(
                image_or_frames, (list, tuple)):
            self.image = image_or_frames[0]
            if len(image_or_frames) > 1:
                self.multiple_engine = MultipleEngine(self)
                for frame in image_or_frames:
                    self.multiple_engine.add_frame(frame)
                self.wrap(self.multiple_engine)
        else:
            self.image = image_or_frames

        if self.source_width is None:
            self.source_width = self.size[0]
        if self.source_height is None:
            self.source_height = self.size[1]
    def command(
        cls,
        context,
        pre=[],
        post=[],
        buffer='',
        input_temp_file=None
    ):
        if not input_temp_file:
            input_temp_file = NamedTemporaryFile()
            input_temp_file.write(buffer)
            input_temp_file.flush()

        command = [context.config.EXIFTOOL_PATH]
        command += pre
        command.append(input_temp_file.name)
        command += post

        logger.debug('[ExiftoolRunner] command: %r' % command, extra=log_extra(context))

        code, stderr, stdout = ShellRunner.command(command, context)

        input_temp_file.close()

        if stderr:
            logger.error('[ExiftoolRunner] error: %r' % stderr, extra=log_extra(context))

        return stdout
Exemplo n.º 5
0
    def post(self, **kwargs):
        self.should_return_image = False

        # URL can be passed as a URL argument or in the body
        url = kwargs['url'] if 'url' in kwargs else kwargs['key']

        if not url:
            logger.error("Couldn't find url param in body or key in URL...")
            raise tornado.web.HTTPError(404)

        options = RequestParser.path_to_parameters(url)

        yield self.check_image(options)

        # We check the status code, if != 200 the image is incorrect, and we shouldn't store the key
        if self.get_status() == 200:
            logger.debug("Image is checked, clearing the response before trying to store...")
            self.clear()
            try:
                shortener = Shortener(self.context)
                key = shortener.generate(url)
                shortener.put(key, url)

                self.write(json.dumps({'key': key}))
                self.set_header("Content-Type", "application/json")
            except Exception as e:
                logger.error("An error occurred while trying to store shortened URL: {error}.".format(error=e.message))
                self.set_status(500)
                self.write(json.dumps({'error': e.message}))
Exemplo n.º 6
0
    def get_image(self):
        try:
            result = yield self._fetch(self.context.request.image_url)

            if not result.successful:
                if result.loader_error == LoaderResult.ERROR_NOT_FOUND:
                    self._error(404)
                    return
                elif result.loader_error == LoaderResult.ERROR_UPSTREAM:
                    # Return a Bad Gateway status if the error came from upstream
                    self._error(502)
                    return
                elif result.loader_error == LoaderResult.ERROR_TIMEOUT:
                    # Return a Gateway Timeout status if upstream timed out (i.e. 599)
                    self._error(504)
                    return
                else:
                    self._error(500)
                    return

        except Exception as e:
            msg = "[BaseHandler] get_image failed for url `{url}`. error: `{error}`".format(
                url=self.context.request.image_url, error=e
            )

            self.log_exception(*sys.exc_info())

            if "cannot identify image file" in e.message:
                logger.warning(msg)
                self._error(400)
            else:
                logger.error(msg)
                self._error(500)
            return

        normalized = result.normalized
        buffer = result.buffer
        engine = result.engine

        req = self.context.request

        if engine is None:
            if buffer is None:
                self._error(504)
                return

            engine = self.context.request.engine
            engine.load(buffer, self.context.request.extension)

        def transform():
            self.normalize_crops(normalized, req, engine)

            if req.meta:
                self.context.request.engine = JSONEngine(engine, req.image_url, req.meta_callback)

            after_transform_cb = functools.partial(self.after_transform, self.context)
            Transformer(self.context).transform(after_transform_cb)

        self.filters_runner.apply_filters(thumbor.filters.PHASE_AFTER_LOAD, transform)
Exemplo n.º 7
0
    def __init__(self, context):
        super(Optimizer, self).__init__(context)

        self.runnable = True
        self.pngcrush_path = self.context.config.PNGCRUSH_PATH
        if not (os.path.isfile(self.pngcrush_path) and os.access(self.pngcrush_path, os.X_OK)):
            logger.error("ERROR pngcrush path '{0}' is not accessible".format(self.pngcrush_path))
            self.runnable = False
Exemplo n.º 8
0
    def __init__(self, context):
        super(Optimizer, self).__init__(context)

        self.runnable = True
        self.imgmin_path = self.context.config.IMGMIN_PATH

        if not ( os.path.isfile(self.imgmin_path) ):
            logger.error("ERROR path '{0}' is not accessible".format(self.imgmin_path))
            self.runnable = False
Exemplo n.º 9
0
    def __init__(self, context):
        super(Optimizer, self).__init__(context)

        self.runnable = True
        self.zopflipng_path = self.context.config.ZOPFLIPNG_PATH

        if not (os.path.isfile(self.zopflipng_path) and os.access(self.zopflipng_path, os.X_OK)):
            logger.error("ERROR zopflipng path '{0}' is not accessible".format(self.zopflipng_path))
            self.runnable = False
Exemplo n.º 10
0
def return_contents(response, url, callback):
    if response.error:
        logger.error("ERROR retrieving image {0}: {1}".format(url, str(response.error)))
        callback(None)
    elif response.body is None or len(response.body) == 0:
        logger.error("ERROR retrieving image {0}: Empty response.".format(url))
        callback(None)
    else:
        callback(response.body)
Exemplo n.º 11
0
    def __init__(self, context):
        super(Optimizer, self).__init__(context)

        self.runnable = True
        self.jpegrecompress_path = self.context.config.JPEGRECOMPRESS_PATH

        if not (os.path.isfile(self.jpegrecompress_path) and os.access(self.jpegrecompress_path, os.X_OK)):
            logger.error("ERROR jpeg-recompress path '{0}' is not accessible".format(self.jpegrecompress_path))
            self.runnable = False
Exemplo n.º 12
0
    def __init__(self, context):
        super(Optimizer, self).__init__(context)

        self.runnable = True
        self.mozjpeg_path = self.context.config.MOZJPEG_PATH
        self.mozjpeg_level = self.context.config.MOZJPEG_QUALITY or '75'

        if not (os.path.isfile(self.mozjpeg_path) and os.access(self.mozjpeg_path, os.X_OK)):
            logger.error("ERROR mozjpeg path '{0}' is not accessible".format(self.mozjpeg_path))
            self.runnable = False
 def put(self, bytes):
     normalized_path = self.normalize_path(self.context.request.url)
     uri = self.context.config.get('RESULT_STORAGE_WEBDAV_URI') + normalized_path
     logger.debug("[RESULT_STORAGE] Making PUT request to: %s", uri)
     http_client = HTTPClient()
     try:
         response = http_client.fetch(uri, method='PUT', body=bytes)
         logger.debug("[RESULT_STORAGE] Success on PUT request!")
     except HTTPError as e:
         logger.error("[RESULT_STORAGE] Error on PUT request: %s", e)
    def handle_error(self, context, handler, exception):
        ex_type, value, tb = exception

        ex_msg = traceback.format_exception_only(ex_type, value)
        tb_msg = traceback.format_tb(tb)

        extra = log_extra(context)
        extra['traceback'] = ''.join(tb_msg)

        logger.error(''.join(ex_msg), extra=extra)
Exemplo n.º 15
0
    def validate(self, path):
        if not hasattr(self.loader, 'validate'):
            return True

        is_valid = self.loader.validate(path)

        if not is_valid:
            logger.error('Request denied because the specified path "%s" was not identified by the loader as a valid path' % path)

        return is_valid
Exemplo n.º 16
0
    def __init__(self, context):
        super(Optimizer, self).__init__(context)

        self.runnable = True
        self.pngquant_path = self.context.config.PNGQUANT_PATH
        self.pngquant_quality = self.context.config.PNGQUANT_QUALITY or '65-80'
        self.pngquant_speed = self.context.config.PNGQUANT_SPEED or '3'

        if not (os.path.isfile(self.pngquant_path) and os.access(self.pngquant_path, os.X_OK)):
            logger.error("ERROR pnqquant path '{0}' is not accessible".format(self.pngquant_path))
            self.runnable = False
Exemplo n.º 17
0
    def _handle_request_exception(self, e):
        try:
            exc_info = sys.exc_info()
            msg = traceback.format_exception(exc_info[0], exc_info[1], exc_info[2])

            if self.context.config.USE_CUSTOM_ERROR_HANDLING:
                self.context.modules.importer.error_handler.handle_error(context=self.context, handler=self, exception=exc_info)

        finally:
            del exc_info
            logger.error('ERROR: %s' % "".join(msg))
            self.send_error(500)
Exemplo n.º 18
0
    def convert_svg_to_png(self, buffer):
        if not cairosvg:
            msg = """[BaseEngine] convert_svg_to_png failed cairosvg not
            imported (if you want svg conversion to png please install cairosvg)
            """
            logger.error(msg)
            return buffer

        buffer = cairosvg.svg2png(bytestring=buffer, dpi=self.context.config.SVG_DPI)
        mime = self.get_mimetype(buffer)
        self.extension = EXTENSION.get(mime, '.jpg')
        return buffer
Exemplo n.º 19
0
    def convert_tif_to_png(self, buffer):
        if not numpy:
            msg = """[BaseEngin] convert_tif_to_png failed numpy not imported"""
            logger.error(msg)
            return buffer
        if not cv2:
            msg = """[BaseEngin] convert_tif_to_png failed opencv not imported"""
            logger.error(msg)
            return buffer

        img = cv2.imdecode(numpy.fromstring(buffer), -1)
        img_str = cv2.imencode('.png', img)[1].tostring()
        return img_str
Exemplo n.º 20
0
    def log_exception(self, *exc_info):
        if isinstance(exc_info[1], tornado.web.HTTPError):
            # Delegate HTTPError's to the base class
            # We don't want these through normal exception handling
            return super(ContextHandler, self).log_exception(*exc_info)

        msg = traceback.format_exception(*exc_info)

        try:
            if self.context.config.USE_CUSTOM_ERROR_HANDLING:
                self.context.modules.importer.error_handler.handle_error(context=self.context, handler=self, exception=exc_info)
        finally:
            del exc_info
            logger.error('ERROR: %s' % "".join(msg))
Exemplo n.º 21
0
    def _read(self, update_image=True):
        buffer = self.flush_operations(update_image)

        # Make sure gifsicle produced a valid gif.
        try:
            with BytesIO(buffer) as buff:
                Image.open(buff).verify()
        except Exception:
            self.context.metrics.incr('gif_engine.no_output')
            logger.error("[GIF_ENGINE] invalid gif engine result for url `{url}`.".format(
                url=self.context.request.url
            ))
            raise

        return buffer
Exemplo n.º 22
0
    def read(self, extension=None, quality=None):
        self.flush_operations()

        # Make sure gifsicle produced a valid gif.
        try:
            with Image.open(BytesIO(self.buffer)) as image:
                image.verify()
        except Exception:
            self.context.statsd_client.incr('gif_engine.no_output')
            logger.error("[GIF_ENGINE] invalid gif engine result for url `{url}`.".format(
                url=self.context.request.url
            ))
            raise

        return self.buffer
Exemplo n.º 23
0
    def convert_tif_to_png(self, buffer):
        if not cv2:
            msg = """[PILEngine] convert_tif_to_png failed: opencv not imported"""
            logger.error(msg)
            return buffer
        if not numpy:
            msg = """[PILEngine] convert_tif_to_png failed: opencv not imported"""
            logger.error(msg)
            return buffer

        img = cv2.imdecode(numpy.fromstring(buffer, dtype='uint16'), -1)
        buffer = cv2.imencode('.png', img)[1].tostring()

        mime = self.get_mimetype(buffer)
        self.extension = EXTENSION.get(mime, '.jpg')
        return buffer
Exemplo n.º 24
0
    def detect(self, callback):
        self.context.request.prevent_result_storage = True
        try:
            if not self.pyremotecv:
                self.pyremotecv = PyRemoteCV(host=self.context.config.REDIS_QUEUE_SERVER_HOST,
                                             port=self.context.config.REDIS_QUEUE_SERVER_PORT,
                                             db=self.context.config.REDIS_QUEUE_SERVER_DB,
                                             password=self.context.config.REDIS_QUEUE_SERVER_PASSWORD)

            self.pyremotecv.async_detect('remotecv.pyres_tasks.DetectTask', 'Detect',
                    args=[self.detection_type, self.context.request.image_url],
                    key=self.context.request.image_url)
        except RedisError:
            self.context.request.detection_error = True
            logger.error(traceback.format_exc())
        finally:
            callback([])
Exemplo n.º 25
0
    def save_on_disc(self):
        if self.fetched:
            try:
                self.engine.load(self.buffer, self.extension)
            except Exception as err:
                self.failed = True
                logger.exception(err)

            try:
                self.thumbor_filter.storage.put(self.url, self.engine.read())
                self.thumbor_filter.storage.put_crypto(self.url)
            except Exception as err:
                self.failed = True
                logger.exception(err)
        else:
            self.failed = True
            logger.error("filters.distributed_collage: Can't save unfetched image")
Exemplo n.º 26
0
    def convert_tif_to_png(self, buffer):
        if not cv:
            msg = """[PILEngine] convert_tif_to_png failed: opencv not imported"""
            logger.error(msg)
            return buffer

        # can not use cv2 here, because ubuntu precise shipped with python-opencv 2.3 which has bug with imencode
        # requires 3rd parameter buf which could not be created in python. Could be replaced with these lines:
        # img = cv2.imdecode(numpy.fromstring(buffer, dtype='uint16'), -1)
        # buffer = cv2.imencode('.png', img)[1].tostring()
        mat_data = cv.CreateMatHeader(1, len(buffer), cv.CV_8UC1)
        cv.SetData(mat_data, buffer, len(buffer))
        img = cv.DecodeImage(mat_data, -1)
        buffer = cv.EncodeImage(".png", img).tostring()

        mime = self.get_mimetype(buffer)
        self.extension = EXTENSION.get(mime, '.jpg')
        return buffer
Exemplo n.º 27
0
    def __init__(self, context):
        super(Optimizer, self).__init__(context)

        self.runnable = True
        self.imgmin_path = self.context.config.IMGMIN_PATH

        # All defaults are from imgmin unless noted otherwise
        self.error_threshold = self.context.config.AUTO_ERROR_THRESHOLD or "1.0"
        self.color_density_ratio = self.context.config.AUTO_COLOR_DENSITY_RATIO or "0.11"
        self.min_unique_colors = self.context.config.AUTO_MIN_UNIQUE_COLORS or "4096"
        self.quality_out_max = self.context.config.AUTO_QUALITY_OUT_MAX or "100"  # imgming default 95
        self.quality_out_min = self.context.config.AUTO_QUALITY_OUT_MIN or "95"  # imgming default 70
        self.quality_in_min = self.context.config.AUTO_QUALITY_IN_MIN or "82"
        self.max_steps = self.context.config.AUTO_MAX_STEPS or "5"

        if not (os.path.isfile(self.imgmin_path)):
            logger.error("ERROR path '{0}' or '{1}' is not accessible".format(self.imgmin_path))
            self.runnable = False
Exemplo n.º 28
0
    def run_gifsicle(self, command):
        p = Popen([self.context.server.gifsicle_path] + command.split(' '), stdout=PIPE, stdin=PIPE, stderr=PIPE)
        stdout_data, stderr_data = p.communicate(input=self.buffer)
        if p.returncode != 0:
            logger.error(stderr_data)

        if stdout_data is None:
            raise GifSicleError(
                'gifsicle command returned errorlevel {0} for command "{1}" (image maybe corrupted?)'.format(
                    p.returncode, ' '.join(
                        [self.context.server.gifsicle_path] +
                        command.split(' ') +
                        [self.context.request.url]
                    )
                )
            )

        return stdout_data
Exemplo n.º 29
0
    def normalize_color_to_hex(self, color_string):
        try:
            return webcolors.normalize_hex("#" + color_string)
        except ValueError:
            pass

        try:
            return webcolors.name_to_hex(color_string)
        except ValueError:
            pass

        try:
            return webcolors.normalize_hex(color_string)
        except ValueError:
            pass

        if color_string:
            logger.error('background_color value could not be parsed')
Exemplo n.º 30
0
    def put(self, path, bytes):
        start = datetime.datetime.now()
        normalized_path = self._normalize_path(path)
        content_type = 'text/plain'
        if bytes:
            try:
                mime = BaseEngine.get_mimetype(bytes)
                content_type = mime
            except:
                logger.error("[GoogleCloudStorage] Couldn't determine mimetype for %s"
                              % path)

        blob = self._get_bucket().blob(normalized_path)
        blob.upload_from_string(bytes, content_type=content_type)

        finish = datetime.datetime.now()
        self.context.metrics.timing('gcs.put.{0}'.format(normalized_path),
                                    (finish - start).total_seconds()
                                    * 1000)
def _process_done(
        callback,
        process,
        context,
        normalized_url,
        seek,
        output_file,
        status
        ):
    # T183907 Sometimes ffmpeg returns status 0 and actually fails to
    # generate a thumbnail. We double-check the existence of the thumbnail
    # in case of apparent success
    if status == 0:
        if os.stat(output_file.name).st_size == 0:
            status = -1

    # If rendering the desired frame fails, attempt to render the
    # first frame instead
    if status != 0 and seek > 0:
        seek_and_screenshot(callback, context, normalized_url, 0)
        return

    result = LoaderResult()

    if status != 0:  # pragma: no cover
        _http_code_from_stderr(context, process, result, normalized_url)
    else:
        result.successful = True
        result.buffer = output_file.read()

    process.stdout.close()
    process.stderr.close()

    output_file.close()

    try:
        os.unlink(output_file.name)
    except OSError as e:  # pragma: no cover
        if e.errno != errno.ENOENT:
            logger.error('[Video] Unable to unlink output file', extra=log_extra(context))
            raise

    callback(result)
Exemplo n.º 32
0
    def on_redis_error(self, fname, exc_type, exc_value):
        '''Callback executed when there is a redis error.
        :param string fname: Function name that was being called.
        :param type exc_type: Exception type
        :param Exception exc_value: The current exception
        :returns: Default value or raise the current exception
        '''

        if self.shared_client:
            Storage.storage = None
        else:
            self.storage = None

        if self.context.config.REDIS_RESULT_STORAGE_IGNORE_ERRORS is True:
            logger.error("Redis result storage failure: %s" % exc_value)

            return None
        else:
            raise exc_value
Exemplo n.º 33
0
    def reorientate(self, override_exif=True):
        """
        Rotates the image in the buffer so that it is oriented correctly.
        If override_exif is True (default) then the metadata
        orientation is adjusted as well.
        :param override_exif: If the metadata should be adjusted as well.
        :type override_exif: Boolean
        """
        exif = self._get_exif_object()
        if exif is None:
            return None

        orientation = exif.get_orientation()

        if orientation is None:
            return

        if orientation == 2:
            self.flip_horizontally()
        elif orientation == 3:
            self.rotate(180)
        elif orientation == 4:
            self.flip_vertically()
        elif orientation == 5:
            # Horizontal Mirror + Rotation 270 CCW
            self.flip_vertically()
            self.rotate(270)
        elif orientation == 6:
            self.rotate(270)
        elif orientation == 7:
            # Vertical Mirror + Rotation 270 CCW
            self.flip_horizontally()
            self.rotate(270)
        elif orientation == 8:
            self.rotate(90)

        if orientation != 1 and override_exif:
            try:
                exif.set_orientation(1)
                self.exif = exif.tobytes()
            except Exception as e:
                msg = """[exif] %s""" % e
                logger.error(msg)
Exemplo n.º 34
0
def load(context, path, callback):
    result = LoaderResult()

    for idx, next_dir in enumerate(context.config.TC_MULTIDIR_PATHS):

        file_path = join(next_dir.rstrip('/'), path.lstrip('/'))
        file_path = abspath(file_path)

        inside_root_path = file_path.startswith(abspath(next_dir))

        if inside_root_path:
            
            # keep backwards compatibility, try the actual path first
            # if not found, unquote it and try again
            found = exists(file_path)
            if not found:
                file_path = unquote(file_path)
                found = exists(file_path)

            if found:
                with open(file_path, 'rb') as f:
                    stats = fstat(f.fileno())

                    result.successful = True
                    result.buffer = f.read()

                    result.metadata.update(
                        size=stats.st_size,
                        updated_at=datetime.utcfromtimestamp(stats.st_mtime))
                callback(result)
                return

        logger.debug('TC_MULTIDIR: File {0} not found in {1}'.format(path, next_dir))
        # else loop and try next directory
    
    if not context.config.TC_MULTIDIR_PATHS:
        logger.error('TC_MULTIDIR: No paths set in configuration TC_MULTIDIR_PATHS')

    # no file found
    result.error = LoaderResult.ERROR_NOT_FOUND
    result.successful = False
    callback(result)
Exemplo n.º 35
0
    def convert_svg_to_png(self, buffer):
        if not cairosvg:
            msg = """[BaseEngine] convert_svg_to_png failed cairosvg not
            imported (if you want svg conversion to png please install cairosvg)
            """
            logger.error(msg)
            return buffer

        try:
            buffer = cairosvg.svg2png(bytestring=buffer, dpi=self.context.config.SVG_DPI)
            mime = self.get_mimetype(buffer)
            self.extension = EXTENSION.get(mime, '.jpg')
        except ParseError:
            mime = self.get_mimetype(buffer)
            extension = EXTENSION.get(mime)
            if extension is None or extension == '.svg':
                raise
            self.extension = extension

        return buffer
Exemplo n.º 36
0
    def detect(self, callback):
        self.context.request.prevent_result_storage = True
        try:
            if not self.pyremotecv:
                self.pyremotecv = PyRemoteCV(
                    host=self.context.config.REDIS_QUEUE_SERVER_HOST,
                    port=self.context.config.REDIS_QUEUE_SERVER_PORT,
                    db=self.context.config.REDIS_QUEUE_SERVER_DB,
                    password=self.context.config.REDIS_QUEUE_SERVER_PASSWORD)

            self.pyremotecv.async_detect(
                'remotecv.pyres_tasks.DetectTask',
                'Detect',
                args=[self.detection_type, self.context.request.image_url],
                key=self.context.request.image_url)
        except RedisError:
            self.context.request.detection_error = True
            logger.error(traceback.format_exc())
        finally:
            callback([])
Exemplo n.º 37
0
    def reorientate(self, override_exif=True):
        """
        Rotates the image in the buffer so that it is oriented correctly.
        If override_exif is True (default) then the metadata
        orientation is adjusted as well.
        :param override_exif: If the metadata should be adjusted as well.
        :type override_exif: Boolean
        """
        orientation = self.get_orientation()

        if orientation is None:
            return

        if orientation == 2:
            self.flip_horizontally()
        elif orientation == 3:
            self.rotate(180)
        elif orientation == 4:
            self.flip_vertically()
        elif orientation == 5:
            # Horizontal Mirror + Rotation 270 CCW
            self.flip_vertically()
            self.rotate(270)
        elif orientation == 6:
            self.rotate(270)
        elif orientation == 7:
            # Vertical Mirror + Rotation 270 CCW
            self.flip_horizontally()
            self.rotate(270)
        elif orientation == 8:
            self.rotate(90)

        if orientation != 1 and override_exif:
            exif_dict = self._get_exif_segment()
            if exif_dict and piexif.ImageIFD.Orientation in exif_dict["0th"]:
                exif_dict["0th"][piexif.ImageIFD.Orientation] = 1
                try:
                    self.exif = piexif.dump(exif_dict)
                except Exception as e:
                    msg = """[piexif] %s""" % e
                    logger.error(msg)
Exemplo n.º 38
0
    def get_image(self):
        try:
            normalized, buffer, engine = yield self._fetch(
                self.context.request.image_url)
        except Exception as e:
            msg = '[BaseHandler] get_image failed for url `{url}`. error: `{error}`'.format(
                url=self.context.request.image_url, error=e)

            self.log_exception(*sys.exc_info())

            if 'cannot identify image file' in e.message:
                logger.warning(msg)
                self._error(400)
            else:
                logger.error(msg)
                self._error(500)
            return

        req = self.context.request

        if engine is None:
            if buffer is None:
                self._error(504)
                return

            engine = self.context.request.engine
            engine.load(buffer, self.context.request.extension)

        def transform():
            self.normalize_crops(normalized, req, engine)

            if req.meta:
                self.context.request.engine = JSONEngine(
                    engine, req.image_url, req.meta_callback)

            after_transform_cb = functools.partial(self.after_transform,
                                                   self.context)
            Transformer(self.context).transform(after_transform_cb)

        self.filters_runner.apply_filters(thumbor.filters.PHASE_AFTER_LOAD,
                                          transform)
Exemplo n.º 39
0
    def run_gifsicle(self, command):
        process = Popen(
            [self.context.server.gifsicle_path] + command.split(" "),
            stdout=PIPE,
            stdin=PIPE,
            stderr=PIPE,
        )
        stdout_data, stderr_data = process.communicate(input=self.buffer)
        if process.returncode != 0:
            logger.error(stderr_data)

        if stdout_data is None:
            raise GifSicleError(
                ("gifsicle command returned errorlevel {0} for "
                 'command "{1}" (image maybe corrupted?)').format(
                     process.returncode,
                     " ".join([self.context.server.gifsicle_path] +
                              command.split(" ") + [self.context.request.url]),
                 ))

        return stdout_data
Exemplo n.º 40
0
    def _get(self, key, col):

        try:
            key = md5(key).hexdigest() + '-' + key
        except UnicodeEncodeError:
            key = md5(
                key.encode('utf-8')).hexdigest() + '-' + key.encode('utf-8')

        if self.storage is None:
            self._connect()

        try:
            r = self.storage.get(self.table, key, self.data_fam + ':' + col)[0]
        except IndexError:
            r = None
        except:
            r = None
            logger.error("Error retrieving image from HBase; key " + key)
            self.hbase_server_offset = self.hbase_server_offset + 1

        return r
Exemplo n.º 41
0
    def load(self, buffer, extension):
        self.extension = extension

        if extension is None:
            mime = self.get_mimetype(buffer)
            self.extension = EXTENSION.get(mime, '.jpg')

        if self.extension == '.svg':
            buffer = self.convert_svg_to_png(buffer)

        # added by zhaorong, if it is a tif picture, convert it to png
        if self.extension == '.tif':
            buffer = self.convert_tif_to_png(buffer)

        image_or_frames = self.create_image(buffer)
        if image_or_frames is None:
            return

        if METADATA_AVAILABLE:
            try:
                self.metadata = ImageMetadata.from_buffer(buffer)
                self.metadata.read()
            except Exception as e:
                logger.error('Error reading image metadata: %s' % e)

        if self.context.config.ALLOW_ANIMATED_GIFS and isinstance(
                image_or_frames, (list, tuple)):
            self.image = image_or_frames[0]
            if len(image_or_frames) > 1:
                self.multiple_engine = MultipleEngine(self)
                for frame in image_or_frames:
                    self.multiple_engine.add_frame(frame)
                self.wrap(self.multiple_engine)
        else:
            self.image = image_or_frames

        if self.source_width is None:
            self.source_width = self.size[0]
        if self.source_height is None:
            self.source_height = self.size[1]
Exemplo n.º 42
0
    def post(self, **kwargs):
        self.should_return_image = False

        content_type = self.request.headers.get("Content-Type", '')

        if 'key' in kwargs and kwargs['key']:
            url = kwargs['key']
        elif content_type.startswith("application/json"):
            data = json.loads(self.request.body)
            url = data['url'] if 'url' in data else None
        else:
            url = self.get_body_argument('url', None)

        if not url:
            logger.error("Couldn't find url param in body or key in URL...")
            raise tornado.web.HTTPError(400)

        options = RequestParser.path_to_parameters(url)

        yield self.check_image(options)

        # We check the status code, if != 200 the image is incorrect, and we shouldn't store the key
        if self.get_status() == 200:
            logger.debug(
                "Image is checked, clearing the response before trying to store..."
            )
            self.clear()
            try:
                shortener = Shortener(self.context)
                key = shortener.generate(url)
                shortener.put(key, url)

                self.write(json.dumps({'key': key}))
                self.set_header("Content-Type", "application/json")
            except Exception as e:
                logger.error(
                    "An error occurred while trying to store shortened URL: {error}."
                    .format(error=e.message))
                self.set_status(500)
                self.write(json.dumps({'error': e.message}))
Exemplo n.º 43
0
    def put(self, path, bytes):
        file_abspath = self._normalize_path(path)
        logger.debug("[STORAGE] putting at %s" % file_abspath)
        bucket = self._get_bucket()

        blob = bucket.blob(file_abspath)
        blob.upload_from_string(bytes)

        max_age = self.context.config.CLOUD_STORAGE_MAX_AGE
        blob.cache_control = "public,max-age=%s" % max_age

        if bytes:
            try:
                mime = BaseEngine.get_mimetype(bytes)
                blob.content_type = mime
            except Exception as ex:
                logger.debug("[STORAGE] Couldn't determine mimetype: %s" % ex)

        try:
            blob.patch()
        except Exception as ex:
            logger.error("[STORAGE] Couldn't patch blob: %s" % ex)
Exemplo n.º 44
0
    async def distributed_collage(self, orientation, alignment, urls):
        self.orientation = orientation
        self.alignment = alignment
        self.urls = urls.split("|")
        self.images = {}

        total = len(self.urls)
        if total > self.MAX_IMAGES:
            logger.error("filters.distributed_collage: Too many images to join")
            return
        elif total == 0:
            logger.error("filters.distributed_collage: No images to join")
            return
        else:
            self.urls = self.urls[: self.MAX_IMAGES]

            self.max_age = self.context.config.MAX_AGE

            self._calculate_dimensions()
            await self._fetch_images()

            self.context.request.max_age = self.max_age
Exemplo n.º 45
0
    def load(self, buffer, extension):
        self.extension = extension

        if extension is None:
            mime = self.get_mimetype(buffer)
            self.extension = EXTENSION.get(mime, ".jpg")

        if self.extension == ".svg":
            buffer = self.convert_svg_to_png(buffer)

        image_or_frames = self.create_image(buffer)
        if image_or_frames is None:
            return

        if METADATA_AVAILABLE:
            try:
                self.metadata = ImageMetadata.from_buffer(buffer)
                self.metadata.read()
            except Exception as error:  # pylint: disable=broad-except
                logger.error("Error reading image metadata: %s", error)

        if self.context.config.ALLOW_ANIMATED_GIFS and isinstance(
            image_or_frames, (list, tuple)
        ):
            self.image = image_or_frames[0]
            if len(image_or_frames) > 1:
                self.multiple_engine = MultipleEngine(self)
                for frame in image_or_frames:
                    self.multiple_engine.add_frame(frame)
                self.wrap(self.multiple_engine)
        else:
            self.image = image_or_frames

        if self.source_width is None:
            self.source_width = self.size[0]
        if self.source_height is None:
            self.source_height = self.size[1]
Exemplo n.º 46
0
def get_pdf_page(context, file_path):
    """
    A context manager that extracts a single page out of a pdf file and 
    stores it in a temporary file. Returns the path of the temporary file
    or None in case of failure.
    """
    import subprocess
    import tempfile
    import os

    gspath = '/usr/bin/gs'

    # Fail nicely when ffmpeg cannot be found
    if not os.path.exists(gspath):
        logger.error('%s does not exist, please configure Ghostscript', gspath)
        yield None
        return
    # Prepare temporary file
    f, image_path = tempfile.mkstemp('.jpg')
    os.close(f)
    # Extract image
    try:
        cmd = [
            gspath, '-sDEVICE=jpeg', '-dDOINTERPOLATE', '-dCOLORSCREEN',
            '-dFirstPage=1', '-dLastPage=1', '-dPDFFitPage', '-dBATCH',
            '-dJPEGQ=85', '-r120x120', '-dNOPAUSE',
            '-sOutputFile="' + image_path + '"', file_path
        ]
        subprocess.check_call(cmd)
        yield image_path
    except:
        logger.exception('Cannot extract image frame from %s', file_path)
        yield None
    finally:
        # Cleanup
        try_to_delete(image_path)
    def distributed_collage(self, callback, orientation, alignment, urls):
        self.callback = callback
        self.orientation = orientation
        self.alignment = alignment
        self.urls = urls.split('|')
        self.images = {}

        total = len(self.urls)
        if total > self.MAX_IMAGES:
            logger.error(
                'filters.distributed_collage: Too many images to join')
            callback()
        elif total == 0:
            logger.error('filters.distributed_collage: No images to join')
            callback()
        else:
            self.urls = self.urls[:self.MAX_IMAGES]

            self.max_age = self.context.config.MAX_AGE

            self._calculate_dimensions()
            yield self._fetch_images()

            self.context.request.max_age = self.max_age
Exemplo n.º 48
0
    def create_preview(self, url, resolution=200):
        out_io = BytesIO()
        try:
            http_client = httpclient.AsyncHTTPClient()
            response = yield http_client.fetch(url)
            if not response.error:
                try:
                    with (Image(blob=response.body,
                                resolution=resolution)) as source:
                        single_image = source.sequence[
                            0]  # Just work on first page
                        with Image(single_image) as i:
                            i.format = 'png'
                            i.background_color = Color(
                                'white')  # Set white background.
                            i.alpha_channel = 'remove'  # Remove transparency and replace with bg.
                            i.save(file=out_io)
                            raise gen.Return(out_io.getvalue())

                except WandException as e:
                    logger.exception('[PDFHander.create_preview] %s', e)
                    raise gen.Return(None)
            else:
                logger.error('STATUS: %s - Failed to get pdf from url %s' %
                             (str(400), url))
                raise tornado.web.HTTPError(400)
        except httpclient.HTTPError as e:
            logger.error('STATUS: %s - Failed to get pdf from url %s' %
                         (str(e.code), url))
            valid_status_code = httputil.responses.get(e.code)
            if valid_status_code:
                self._error(e.code)
            else:
                raise tornado.web.HTTPError(400)
        finally:
            out_io.close()
Exemplo n.º 49
0
def get_video_frame(context, url, normalize_url_func):
    """
    A context manager that extracts a single frame out of a video file and
    stores it in a temporary file. Returns the path of the temporary file
    or None in case of failure.
    Depends on FFMPEG_PATH from Thumbor's configuration.
    """
    url = normalize_url_func(url)
    # Fail nicely when ffmpeg cannot be found
    if not os.path.exists(context.config.FFMPEG_PATH):
        logger.error('%s does not exist, please configure FFMPEG_PATH', context.config.FFMPEG_PATH)
        yield None
        return
    # Prepare temporary file
    f, image_path = tempfile.mkstemp('.jpg')
    os.close(f)
    # Extract image
    try:
        cmd = [
            context.config.FFMPEG_PATH,
            '-i', url,
            '-ss', '00:00:01.000',
            '-vframes', '1',
            '-y',
            '-nostats',
            '-loglevel', 'error',
            image_path
        ]
        subprocess.check_call(cmd)
        yield image_path
    except:
        logger.exception('Cannot extract image frame from %s', url)
        yield None
    finally:
        # Cleanup
        try_to_delete(image_path)
Exemplo n.º 50
0
    def __init__(self, context, importer):
        '''
        :param context:
        :param importer:
        '''

        ThumborContextImporter.__init__(self, context, importer)

        # Dynamically load registered modules
        for name in self._community_modules:
            if hasattr(importer, name):
                init = getattr(importer, name)
                if not hasattr(init, '__call__'):
                    logger.error(
                        "Attr {attr} of object {obj} is not callable".format(
                            attr=name,
                            obj=importer,
                        ))
                instance = getattr(importer, name)(context)
                setattr(self, name, instance)
            else:
                logger.warning("Module {name} is not configured.".format(
                    name=name.upper()))
                setattr(self, name, None)
Exemplo n.º 51
0
 def on_image_fetch(self):
     if (self.is_any_failed()):
         logger.error('filters.distributed_collage: Some images failed')
         self.callback()
     elif self.is_all_fetched():
         self.assembly()
Exemplo n.º 52
0
    def get_image(self):
        """
        This function is called after the PRE_LOAD filters have been applied.
        It applies the AFTER_LOAD filters on the result, then crops the image.
        """
        try:
            result = yield self._fetch(self.context.request.image_url)

            if not result.successful:
                if result.loader_error == LoaderResult.ERROR_NOT_FOUND:
                    self._error(404)
                    return
                elif result.loader_error == LoaderResult.ERROR_UPSTREAM:
                    # Return a Bad Gateway status if the error came from upstream
                    self._error(502)
                    return
                elif result.loader_error == LoaderResult.ERROR_TIMEOUT:
                    # Return a Gateway Timeout status if upstream timed out (i.e. 599)
                    self._error(504)
                    return
                else:
                    self._error(500)
                    return

        except Exception as e:
            msg = '[BaseHandler] get_image failed for url `{url}`. error: `{error}`'.format(
                url=self.context.request.image_url, error=e)

            self.log_exception(*sys.exc_info())

            if 'cannot identify image file' in e.message:
                logger.warning(msg)
                self._error(400)
            else:
                logger.error(msg)
                self._error(500)
            return

        normalized = result.normalized
        buffer = result.buffer
        engine = result.engine

        req = self.context.request

        if engine is None:
            if buffer is None:
                self._error(504)
                return

            engine = self.context.request.engine
            try:
                engine.load(buffer, self.context.request.extension)
            except Exception:
                self._error(504)
                return

        def transform():
            self.normalize_crops(normalized, req, engine)

            if req.meta:
                self.context.request.engine = JSONEngine(
                    engine, req.image_url, req.meta_callback)

            after_transform_cb = functools.partial(self.after_transform,
                                                   self.context)
            Transformer(self.context).transform(after_transform_cb)

        self.filters_runner.apply_filters(thumbor.filters.PHASE_AFTER_LOAD,
                                          transform)
Exemplo n.º 53
0
    async def _fetch_images(self):
        crypto = CryptoURL(key=self.context.server.security_key)

        image_ops = []
        if not hasattr(self.context.config, "DISTRIBUTED_COLLAGE_FILTER_HTTP_LOADER"):
            self.context.config.DISTRIBUTED_COLLAGE_FILTER_HTTP_LOADER = (
                "thumbor.loaders.http_loader"
            )
        self.context.modules.importer.import_item(
            "DISTRIBUTED_COLLAGE_FILTER_HTTP_LOADER"
        )
        loader = self.context.modules.importer.distributed_collage_filter_http_loader

        for i, url in enumerate(self.urls):
            width = (
                self.image_width if i < len(self.urls) - 1 else self.last_image_width
            )
            height = (
                self.context.request.height
                or self.context.transformer.get_target_dimensions()[1]
            )
            params = {
                "width": int(width),
                "height": int(height),
                "image_url": url,
                "smart": True,
                "halign": "center",
                "valign": "middle",
                "filters": ["quality(100)"],
            }
            thumbor_host = getattr(
                self.context.config,
                "DISTRIBUTED_COLLAGE_FILTER_THUMBOR_SERVER_URL",
                "%s://%s"
                % (
                    self.context.request_handler.request.protocol,
                    self.context.request_handler.request.host,
                ),
            )
            encrypted_url = "%s%s" % (thumbor_host, crypto.generate(**params))
            image_ops.append(await loader.load(self.context, encrypted_url))

        successful = all([image.successful for image in image_ops])
        if not successful:
            logger.error(
                "Retrieving at least one of the collaged images failed: %s"
                % (
                    ", ".join(
                        [image.error for image in image_ops if not image.successful]
                    ),
                )
            )
            return

        max_age = min(
            [
                self.get_max_age(image.metadata.get("Cache-Control"), self.max_age)
                for image in image_ops
            ]
        )
        self.assembly_images(image_ops)
Exemplo n.º 54
0
 def _error(self, status, msg=None):
     self.set_status(status)
     if msg is not None:
         logger.error(msg)
     self.finish()
Exemplo n.º 55
0
    def run_and_check_ssim_and_size(
        self,
        url,
        mediawiki_reference_thumbnail,
        perfect_reference_thumbnail,
        expected_width,
        expected_height,
        expected_ssim,
        size_tolerance,
    ):
        """Request URL and check ssim and size.

        Arguments:
        url -- thumbnail URL
        mediawiki_reference_thumbnail -- reference thumbnail file
        expected_width -- expected thumbnail width
        expected_height -- expected thumbnail height
        expected_ssim -- minimum SSIM score
        size_tolerance -- maximum file size ratio between reference and result
        perfect_reference_thumbnail -- perfect lossless version of the target thumbnail, for visual comparison
        """
        try:
            result = self.fetch(url)
        except Exception as e:
            assert False, 'Exception occured: %r' % e

        assert result is not None, 'No result'
        assert result.code == 200, 'Response code: %s' % result.code

        result.buffer.seek(0)

        generated = Image.open(result.buffer)

        expected_path = os.path.join(
            os.path.dirname(__file__),
            'thumbnails',
            mediawiki_reference_thumbnail
        )

        visual_expected_path = os.path.join(
            os.path.dirname(__file__),
            'thumbnails',
            perfect_reference_thumbnail
        )
        visual_expected = Image.open(visual_expected_path).convert(generated.mode)

        assert generated.size[0] == expected_width, \
            'Width differs: %d (should be == %d)\n' % (generated.size[0], expected_width)

        assert generated.size[1] == expected_height, \
            'Height differs: %d (should be == %d)\n' % (generated.size[1], expected_height)

        ssim = compute_ssim(generated, visual_expected)

        try:
            assert ssim >= expected_ssim, 'Images too dissimilar: %f (should be >= %f)\n' % (ssim, expected_ssim)
        except AssertionError as e:
            output_file = NamedTemporaryFile(delete=False)
            output_file.write(result.buffer.getvalue())
            output_file.close()
            logger.error('Dumped generated test image for debugging purposes: %s' % output_file.name)
            raise e

        expected_filesize = float(os.path.getsize(expected_path))
        generated_filesize = float(len(result.buffer.getvalue()))

        ratio = generated_filesize / expected_filesize
        assert ratio <= size_tolerance, \
            'Generated file bigger than size tolerance: %f (should be <= %f)' % (ratio, size_tolerance)

        return result.buffer
Exemplo n.º 56
0
    async def get_image(self):
        """
        This function is called after the PRE_LOAD filters have been applied.
        It applies the AFTER_LOAD filters on the result, then crops the image.
        """
        try:
            result = await self._fetch(self.context.request.image_url)

            if not result.successful:
                if result.loader_error == LoaderResult.ERROR_NOT_FOUND:
                    self._error(404)
                    return

                if result.loader_error == LoaderResult.ERROR_UPSTREAM:
                    # Return a Bad Gateway status if the error
                    # came from upstream
                    self._error(502)
                    return

                if result.loader_error == LoaderResult.ERROR_TIMEOUT:
                    # Return a Gateway Timeout status if upstream
                    # timed out (i.e. 599)
                    self._error(504)
                    return

                if isinstance(result.loader_error, int):
                    self._error(result.loader_error)
                    return

                if (hasattr(result, "engine_error") and result.engine_error
                        == EngineResult.COULD_NOT_LOAD_IMAGE):
                    self._error(400)
                    return

                self._error(500)
                return

        except Exception as error:
            msg = (
                "[BaseHandler] get_image failed for url `{url}`. error: `{error}`"
            ).format(url=self.context.request.image_url, error=error)

            self.log_exception(*sys.exc_info())

            if "cannot identify image file" in str(error):
                logger.warning(msg)
                self._error(400)
            else:
                logger.error(msg)
                self._error(500)
            return

        normalized = result.normalized
        buffer = result.buffer
        engine = result.engine

        req = self.context.request

        if engine is None:
            if buffer is None:
                self._error(504)
                return

            engine = self.context.request.engine
            try:
                engine.load(buffer, self.context.request.extension)
            except Exception as error:
                logger.exception("Loading image failed with %s", error)
                self._error(504)
                return

        self.context.transformer = Transformer(self.context)

        await self.filters_runner.apply_filters(
            thumbor.filters.PHASE_AFTER_LOAD)
        self.normalize_crops(normalized, req, engine)

        if req.meta:
            self.context.transformer.engine = self.context.request.engine = JSONEngine(
                engine, req.image_url, req.meta_callback)

        await self.context.transformer.transform()
        await self.after_transform()
Exemplo n.º 57
0
    def get_image(self):
        """
        This function is called after the PRE_LOAD filters have been applied.
        It applies the AFTER_LOAD filters on the result, then crops the image.
        """
        if self.context.request.mgnlogin:
            mgnl_auth = ""
            if self.context.config.MGNLOGIN_USER and self.context.config.MGNLOGIN_PASS:
                mgnl_auth = "?mgnlUserId=%s&mgnlUserPSWD=%s" % (
                    self.context.config.MGNLOGIN_USER,
                    self.context.config.MGNLOGIN_PASS)
            req_image_url = self.context.request.image_url + mgnl_auth
        else:
            req_image_url = self.context.request.image_url

        try:
            result = yield self._fetch(req_image_url)

            if not result.successful:
                if result.loader_error == LoaderResult.ERROR_NOT_FOUND:
                    self._error(404)
                    return
                elif result.loader_error == LoaderResult.ERROR_UPSTREAM:
                    # Return a Bad Gateway status if the error came from upstream
                    self._error(502)
                    return
                elif result.loader_error == LoaderResult.ERROR_TIMEOUT:
                    # Return a Gateway Timeout status if upstream timed out (i.e. 599)
                    self._error(504)
                    return
                elif isinstance(result.loader_error, int):
                    self._error(result.loader_error)
                    return
                elif hasattr(
                        result, 'engine_error'
                ) and result.engine_error == EngineResult.COULD_NOT_LOAD_IMAGE:
                    self._error(400)
                    return
                else:
                    self._error(500)
                    return

        except Exception as e:
            msg = '[BaseHandler] get_image failed for url `{url}`. error: `{error}`'.format(
                url=self.context.request.image_url, error=e)

            self.log_exception(*sys.exc_info())

            if 'cannot identify image file' in e.message:
                logger.warning(msg)
                self._error(400)
            else:
                logger.error(msg)
                self._error(500)
            return

        normalized = result.normalized
        buffer = result.buffer
        engine = result.engine

        req = self.context.request

        if engine is None:
            if buffer is None:
                self._error(504)
                return

            engine = self.context.request.engine
            try:
                engine.load(buffer, self.context.request.extension)
            except Exception:
                self._error(504)
                return

        self.context.transformer = Transformer(self.context)

        def transform():
            self.normalize_crops(normalized, req, engine)

            if req.meta:
                self.context.transformer.engine = \
                    self.context.request.engine = \
                    JSONEngine(engine, req.image_url, req.meta_callback)

            self.context.transformer.transform(self.after_transform)

        self.filters_runner.apply_filters(thumbor.filters.PHASE_AFTER_LOAD,
                                          transform)
Exemplo n.º 58
0
    async def red_eye(self) -> None:
        if not OPENCV_AVAILABLE:
            logger.error(
                "Can't use red eye removal filter if OpenCV and NumPy are not available."
            )

            return

        faces = [
            face for face in self.context.request.focal_points
            if face.origin == "Face Detection"
        ]

        if not faces:
            return

        mode, data = self.engine.image_data_as_rgb()
        mode = mode.lower()
        size = self.engine.size

        image = np.ndarray(
            shape=(size[1], size[0], 4 if mode == "rgba" else 3),
            dtype="|u1",
            buffer=data,
        ).copy()

        for face in faces:
            face_x = int(face.x - face.width / 2)
            face_y = int(face.y - face.height / 2)

            face_image = image[face_y:face_y + face.height,
                               face_x:face_x + face.width]

            eye_rects = self.cascade.detectMultiScale(
                face_image,
                scaleFactor=HAAR_SCALE,
                minNeighbors=MIN_NEIGHBORS,
                minSize=MIN_SIZE,
            )

            for pos_x, pos_y, width, height in eye_rects:
                # Crop the eye region
                eye_image = face_image[pos_y:pos_y + height,
                                       pos_x:pos_x + width]

                # split the images into 3 channels
                red, green, blue = cv2.split(eye_image)

                # Add blue and green channels
                blue_green = cv2.add(blue, green)
                mean = blue_green // 2
                # threshold the mask based on red color and combination of blue and green color
                mask = ((red > RED_THRESHOLD * mean) &
                        (red > 60)).astype(np.uint8) * 255
                # Some extra region may also get detected , we find the largest region
                # find all contours
                contours_return = cv2.findContours(
                    mask.copy(), cv2.RETR_EXTERNAL,
                    cv2.CHAIN_APPROX_NONE)  # It return contours and Hierarchy

                if len(contours_return) == 2:
                    contours, _ = contours_return
                else:
                    _, contours, _ = contours_return

                # find contour with max area
                max_area = 0
                max_cont = None

                for cont in contours:
                    area = cv2.contourArea(cont)

                    if area > max_area:
                        max_area = area
                        max_cont = cont

                if max_cont is None:
                    continue

                mask = mask * 0  # Reset the mask image to complete black image
                # draw the biggest contour on mask
                cv2.drawContours(mask, [max_cont], 0, (255), -1)
                # Close the holes to make a smooth region
                mask = cv2.morphologyEx(
                    mask,
                    cv2.MORPH_CLOSE,
                    cv2.getStructuringElement(cv2.MORPH_DILATE, (5, 5)),
                )
                mask = cv2.dilate(mask, (3, 3), iterations=3)

                # The information of only red color is lost,
                # So we fill the mean of blue and green color in all
                # three channels(BGR) to maintain the texture
                # Fill this black mean value to masked image
                mean = cv2.bitwise_and(mean, mask)  # mask the mean image
                mean = cv2.cvtColor(
                    mean, cv2.COLOR_GRAY2RGB)  # convert mean to 3 channel
                mask = cv2.cvtColor(
                    mask, cv2.COLOR_GRAY2RGB)  # convert mask to 3 channel
                eye = (cv2.bitwise_and(~mask, eye_image) + mean
                       )  # Copy the mean color to masked region to color image
                face_image[pos_y:pos_y + height, pos_x:pos_x + width] = eye

        self.engine.set_image_data(image.tobytes())
Exemplo n.º 59
0
    def get_image(self):
        """
        This function is called after the PRE_LOAD filters have been applied.
        It applies the AFTER_LOAD filters on the result, then crops the image.
        """
        try:
            result = yield self._fetch(
                self.context.request.image_url
            )

            if not result.successful:
                if result.loader_error == LoaderResult.ERROR_NOT_FOUND:
                    self._error(404)
                    return
                elif result.loader_error == LoaderResult.ERROR_FORBIDDEN:
                    blacklist = yield self.get_blacklist_contents()
                    blacklist += self.context.request.image_url + "\n"
                    logger.warning('403 Adding to blacklist: %s' % self.context.request.image_url)
                    self.context.modules.storage.put('blacklist.txt', blacklist)
                    self._error(404)
                    return
                elif result.loader_error == LoaderResult.ERROR_UPSTREAM:
                    # Return a Bad Gateway status if the error came from upstream
                    self._error(502)
                    return
                elif result.loader_error == LoaderResult.ERROR_TIMEOUT:
                    # Return a Gateway Timeout status if upstream timed out (i.e. 599)
                    self._error(504)
                    return
                elif isinstance(result.loader_error, int):
                    self._error(result.loader_error)
                    return
                elif hasattr(result, 'engine_error') and result.engine_error == EngineResult.COULD_NOT_LOAD_IMAGE:
                    self._error(400)
                    return
                else:
                    self._error(500)
                    return

        except Exception as e:
            url = self.context.request.image_url if self.context.request else '-|-'
            msg = '[BaseHandler] get_image failed for url `{url}`. error: `{error}`'.format(
                url=url,
                error=e
            )

            self.log_exception(*sys.exc_info())

            if 'cannot identify image file' in e.message:
                logger.warning(msg)
                self._error(400)
            else:
                logger.error(msg)
                self._error(500)
            return

        normalized = result.normalized
        buffer = result.buffer
        engine = result.engine

        req = self.context.request

        if engine is None:
            if buffer is None:
                self._error(504)
                return

            engine = self.context.request.engine
            try:
                engine.load(buffer, self.context.request.extension)
            except Exception:
                self._error(504)
                return

        self.context.transformer = Transformer(self.context)

        def transform():
            self.normalize_crops(normalized, req, engine)

            if req.meta:
                self.context.transformer.engine = \
                    self.context.request.engine = \
                    JSONEngine(engine, req.image_url, req.meta_callback)

            self.context.transformer.transform(self.after_transform)

        self.filters_runner.apply_filters(thumbor.filters.PHASE_AFTER_LOAD, transform)