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