Example #1
0
def make_thumbnail(ctx, source_image, source_url_path, width, height=None):
    """Helper method that can create thumbnails from within the build process
    of an artifact.
    """
    suffix = get_suffix(width, height)
    dst_url_path = get_dependent_url(source_url_path,
                                     suffix,
                                     ext=get_thumbnail_ext(source_image))
    quality = get_quality(source_image)

    im = find_imagemagick(ctx.build_state.config['IMAGEMAGICK_EXECUTABLE'])

    @ctx.sub_artifact(artifact_name=dst_url_path, sources=[source_image])
    def build_thumbnail_artifact(artifact):
        resize_key = str(width)
        if height is not None:
            resize_key += 'x' + str(height)
        artifact.ensure_dir()

        cmdline = [
            im, source_image, '-resize', resize_key, '-auto-orient',
            '-quality',
            str(quality), artifact.dst_filename
        ]

        reporter.report_debug_info('imagemagick cmd line', cmdline)
        portable_popen(cmdline).wait()

    return Thumbnail(dst_url_path, width, height)
Example #2
0
def make_thumbnail(ctx, source_image, source_url_path, width, height=None,
                   crop=False):
    """Helper method that can create thumbnails from within the build process
    of an artifact.
    """
    with open(source_image, 'rb') as f:
        format, source_width, source_height = get_image_info(f)
        if format == 'unknown':
            raise RuntimeError('Cannot process unknown images')

    suffix = get_suffix(width, height, crop=crop)
    dst_url_path = get_dependent_url(source_url_path, suffix,
                                     ext=get_thumbnail_ext(source_image))

    if height is None:
        # we can only crop if a height is specified
        crop = False
        height = computed_height(source_image, width, source_width,
                                 source_height)

    # If we are dealing with an actual svg image, we do not actually
    # resize anything, we just return it.  This is not ideal but it's
    # better than outright failing.
    if format == 'svg':
        return Thumbnail(source_url_path, width, height)

    @ctx.sub_artifact(artifact_name=dst_url_path, sources=[source_image])
    def build_thumbnail_artifact(artifact):
        artifact.ensure_dir()
        process_image(ctx, source_image, artifact.dst_filename,
                      width, height, crop=crop)

    return Thumbnail(dst_url_path, width, height)
Example #3
0
def make_thumbnail(ctx, source_image, source_url_path, width, height=None):
    """Helper method that can create thumbnails from within the build process
    of an artifact.
    """
    suffix = get_suffix(width, height)
    dst_url_path = get_dependent_url(source_url_path, suffix,
                                     ext=get_thumbnail_ext(source_image))
    quality = get_quality(source_image)

    im = find_imagemagick(
        ctx.build_state.config['IMAGEMAGICK_EXECUTABLE'])

    @ctx.sub_artifact(artifact_name=dst_url_path, sources=[source_image])
    def build_thumbnail_artifact(artifact):
        resize_key = str(width)
        if height is not None:
            resize_key += 'x' + str(height)
        artifact.ensure_dir()

        cmdline = [im, source_image, '-resize', resize_key,
                   '-auto-orient', '-quality', str(quality),
                   artifact.dst_filename]

        reporter.report_debug_info('imagemagick cmd line', cmdline)
        portable_popen(cmdline).wait()

    return Thumbnail(dst_url_path, width, height)
Example #4
0
def make_thumbnail(ctx, source_image, source_url_path, width, height=None,
                   crop=False, quality=None):
    """Helper method that can create thumbnails from within the build process
    of an artifact.
    """
    with open(source_image, 'rb') as f:
        format, source_width, source_height = get_image_info(f)
        if format == 'unknown':
            raise RuntimeError('Cannot process unknown images')

    suffix = get_suffix(width, height, crop=crop, quality=quality)
    dst_url_path = get_dependent_url(source_url_path, suffix,
                                     ext=get_thumbnail_ext(source_image))
    report_height = height

    if height is None:
        # we can only crop if a height is specified
        crop = False
        report_height = computed_height(source_image, format, width,
                                        source_width, source_height)

    # If we are dealing with an actual svg image, we do not actually
    # resize anything, we just return it.  This is not ideal but it's
    # better than outright failing.
    if format == 'svg':
        return Thumbnail(source_url_path, width, height)

    @ctx.sub_artifact(artifact_name=dst_url_path, sources=[source_image])
    def build_thumbnail_artifact(artifact):
        artifact.ensure_dir()
        process_image(ctx, source_image, artifact.dst_filename,
                      width, height, crop=crop, quality=quality)

    return Thumbnail(dst_url_path, width, report_height)
Example #5
0
def make_thumbnail(ctx,
                   source_image,
                   source_url_path,
                   width,
                   height=None,
                   crop=False):
    """Helper method that can create thumbnails from within the build process
    of an artifact.
    """
    suffix = get_suffix(width, height, crop=crop)
    dst_url_path = get_dependent_url(source_url_path,
                                     suffix,
                                     ext=get_thumbnail_ext(source_image))

    if height is None:
        # we can only crop if a height is specified
        crop = False
        with open(source_image, 'rb') as f:
            _, w, h = get_image_info(f)
        height = computed_height(source_image, width, w, h)

    @ctx.sub_artifact(artifact_name=dst_url_path, sources=[source_image])
    def build_thumbnail_artifact(artifact):
        artifact.ensure_dir()
        process_image(ctx,
                      source_image,
                      artifact.dst_filename,
                      width,
                      height,
                      crop=crop)

    return Thumbnail(dst_url_path, width, height)
Example #6
0
def make_thumbnail(ctx, source_image, source_url_path, width, height=None):
    """Helper method that can create thumbnails from within the build process
    of an artifact.
    """
    suffix = get_suffix(width, height)
    dst_url_path = get_dependent_url(source_url_path, suffix,
                                     ext=get_thumbnail_ext(source_image))

    if height is None:
        with open(source_image, 'rb') as f:
            _, w, h = get_image_info(f)
        height = computed_height(source_image, width, w, h)

    @ctx.sub_artifact(artifact_name=dst_url_path, sources=[source_image])
    def build_thumbnail_artifact(artifact):
        artifact.ensure_dir()
        process_image(ctx, source_image, artifact.dst_filename, width, height)

    return Thumbnail(dst_url_path, width, height)
Example #7
0
def make_image_thumbnail(
    ctx,
    source_image,
    source_url_path,
    width=None,
    height=None,
    mode=ThumbnailMode.DEFAULT,
    upscale=None,
    quality=None,
):
    """Helper method that can create thumbnails from within the build process
    of an artifact.
    """
    if width is None and height is None:
        raise ValueError("Must specify at least one of width or height.")

    # temporarily fallback to "fit" in case of erroneous arguments
    # to preserve backward-compatibility.
    # this needs to change to an exception in the future.
    if mode != ThumbnailMode.FIT and (width is None or height is None):
        warnings.warn(
            f'"{mode.value}" mode requires both `width` and `height` '
            'to be specified. Falling back to "fit" mode.'
        )
        mode = ThumbnailMode.FIT

    if upscale is None and mode in (ThumbnailMode.CROP, ThumbnailMode.STRETCH):
        upscale = True

    with open(source_image, "rb") as f:
        format, source_width, source_height = get_image_info(f)

    if format is None:
        raise RuntimeError("Cannot process unknown images")

    # If we are dealing with an actual svg image, we do not actually
    # resize anything, we just return it. This is not ideal but it's
    # better than outright failing.
    if format == "svg":
        return Thumbnail(source_url_path, width, height)

    if mode == ThumbnailMode.FIT:
        computed_width, computed_height = compute_dimensions(
            width, height, source_width, source_height
        )
    else:
        computed_width, computed_height = width, height

    would_upscale = computed_width > source_width or computed_height > source_height

    # this part needs to be removed once backward-compatibility period passes
    if would_upscale and upscale is None:
        warnings.warn(
            "Your image is being scaled up since the requested thumbnail "
            "size is larger than the source. This default will change "
            "in the future. If you want to preserve the current behaviour, "
            "use `upscale=True`."
        )
        upscale = True

    if would_upscale and not upscale:
        return Thumbnail(source_url_path, source_width, source_height)

    suffix = get_suffix(width, height, mode, quality=quality)
    dst_url_path = get_dependent_url(
        source_url_path, suffix, ext=get_thumbnail_ext(source_image)
    )

    def build_thumbnail_artifact(artifact):
        artifact.ensure_dir()
        process_image(
            ctx,
            source_image,
            artifact.dst_filename,
            width,
            height,
            mode,
            quality=quality,
        )

    ctx.sub_artifact(artifact_name=dst_url_path, sources=[source_image])(
        build_thumbnail_artifact
    )

    return Thumbnail(dst_url_path, computed_width, computed_height)
Example #8
0
def make_video_thumbnail(
    ctx,
    source_video,
    source_url_path,
    seek,
    width=None,
    height=None,
    mode=ThumbnailMode.DEFAULT,
    upscale=None,
    quality=None,
    format=None,
):
    if mode != ThumbnailMode.FIT and (width is None or height is None):
        msg = '"%s" mode requires both `width` and `height` to be defined.'
        raise ValueError(msg % mode.label)

    if upscale is None:
        upscale = {
            ThumbnailMode.FIT: False,
            ThumbnailMode.CROP: True,
            ThumbnailMode.STRETCH: True,
        }[mode]

    if format is None:
        format = "jpg"
    if format not in THUMBNAIL_FORMATS:
        raise ValueError('Invalid thumbnail format "%s"' % format)

    if quality is not None and format != "jpg":
        raise ValueError(
            "The quality parameter is only supported for jpeg images")

    if seek < timedelta(0):
        raise ValueError("Seek must not be negative")

    ffmpeg = locate_executable("ffmpeg")
    if ffmpeg is None:
        raise RuntimeError("Failed to locate ffmpeg")
    info = get_video_info(source_video)

    source_dim = Dimensions(info["width"], info["height"])
    resize_dim, crop_dim = source_dim.resize(width, height, mode, upscale)

    # Construct a filename suffix unique to the given parameters
    suffix = get_suffix(seek, width, height, mode=mode, quality=quality)
    dst_url_path = get_dependent_url(source_url_path,
                                     suffix,
                                     ext=".{}".format(format))

    if quality is None and format == "jpg":
        quality = 95

    def build_thumbnail_artifact(artifact):
        artifact.ensure_dir()

        vfilter = "thumbnail,scale={rw}:{rh},crop={tw}:{th}".format(
            rw=resize_dim.width,
            rh=resize_dim.height,
            tw=crop_dim.width,
            th=crop_dim.height,
        )

        cmdline = [
            ffmpeg,
            "-loglevel",
            "-8",
            "-ss",
            get_timecode(seek),  # Input seeking since it's faster
            "-i",
            source_video,
            "-vf",
            vfilter,
            "-frames:v",
            "1",
            "-qscale:v",
            str(get_ffmpeg_quality(quality)),
            artifact.dst_filename,
        ]

        reporter.report_debug_info("ffmpeg cmd line", cmdline)
        proc = portable_popen(cmdline)
        if proc.wait() != 0:
            raise RuntimeError("ffmpeg exited with code {}".format(
                proc.returncode))

        if not os.path.exists(artifact.dst_filename):
            msg = ("Unable to create video thumbnail for {!r}. Maybe the seek "
                   "is outside of the video duration?")
            raise RuntimeError(msg.format(source_video))

    ctx.sub_artifact(artifact_name=dst_url_path,
                     sources=[source_video])(build_thumbnail_artifact)

    return Thumbnail(dst_url_path, crop_dim.width, crop_dim.height)
Example #9
0
def make_image_thumbnail(ctx,
                         source_image,
                         source_url_path,
                         width=None,
                         height=None,
                         mode=ThumbnailMode.DEFAULT,
                         upscale=None,
                         quality=None,
                         offset=None,
                         zoom=None,
                         ext=None):
    """Helper method that can create thumbnails from within the build process
    of an artifact.
    """
    if width is None and height is None:
        raise ValueError("Must specify at least one of width or height.")

    # this part needs to be removed once backward-compatibility period passes
    if upscale is None and mode == ThumbnailMode.FIT:
        warnings.warn(
            "Your images are currently scaled up when the thumbnail requested "
            "is larger than the source. This default will change in the future. "
            "If you want to preserve the current behaviour, use `upscale=True`."
        )
        upscale = True

    if upscale is None:
        upscale = {
            ThumbnailMode.FIT: False,
            ThumbnailMode.CROP: True,
            ThumbnailMode.STRETCH: True,
        }[mode]

    # temporarily fallback to "fit" in case of erroneous arguments
    # to preserve backward-compatibility.
    # this needs to change to an exception in the future.
    if mode != ThumbnailMode.FIT and (width is None or height is None):
        warnings.warn(
            '"%s" mode requires both `width` and `height` to be defined. '
            'Falling back to "fit" mode.`' % mode.label)
        mode = ThumbnailMode.FIT

    with open(source_image, "rb") as f:
        format, source_width, source_height = get_image_info(f)

    if format is None:
        raise RuntimeError("Cannot process unknown images")

    # If we are dealing with an actual svg image, we do not actually
    # resize anything, we just return it. This is not ideal but it's
    # better than outright failing.
    if format == "svg":
        return Thumbnail(source_url_path, width, height)

    if not upscale:

        def _original():
            return Thumbnail(source_url_path, source_width, source_height)

        if height is None:
            if source_width < width:
                return _original()
        elif width is None:
            if source_height < height:
                return _original()
        else:
            if source_width < width and source_height < height:
                return _original()

    suffix = get_suffix(width,
                        height,
                        mode,
                        quality=quality,
                        offset=offset,
                        zoom=zoom)
    dst_url_path = get_dependent_url(
        source_url_path,
        suffix,
        ext='.%s' % ext if ext else get_thumbnail_ext(source_image))

    if mode == ThumbnailMode.FIT:
        computed_width, computed_height = compute_dimensions(
            width, height, source_width, source_height)
    else:
        computed_width, computed_height = width, height

    @ctx.sub_artifact(artifact_name=dst_url_path, sources=[source_image])
    def build_thumbnail_artifact(artifact):
        artifact.ensure_dir()
        process_image(ctx,
                      source_image,
                      artifact.dst_filename,
                      width,
                      height,
                      mode,
                      quality=quality,
                      offset=offset,
                      zoom=zoom)

    return Thumbnail(dst_url_path, computed_width, computed_height)