improve image minification logic

This commit is contained in:
Jared Dueck 2025-08-03 12:29:52 -05:00
commit b073758bf7

View file

@ -71,33 +71,41 @@ class ABCMinifier(ABC):
class PillowMinifier(ABCMinifier): class PillowMinifier(ABCMinifier):
@staticmethod @staticmethod
def _convert_image( def _convert_image(
image_file: Path, image_format: ImageFormat, dest: Path | None = None, quality: int = 100 image_file: Path | None = None, image_format: ImageFormat, dest: Path | None = None, quality: int = 100, img: Image.Image | None = None
) -> Path: ) -> Path:
""" """
Converts an image to the specified format in-place. The original image is not Converts an image to the specified format in-place. The original image is not
removed. By default, the quality is set to 100. removed. By default, the quality is set to 100.
""" """
if img is None:
if image_file is None:
raise ValueError("Must provide either image_file or img.")
img = Image.open(image_file)
img = Image.open(image_file)
img = ImageOps.exif_transpose(img) img = ImageOps.exif_transpose(img)
if img.mode not in image_format.modes: if img.mode not in image_format.modes:
img = img.convert(image_format.modes[0]) img = img.convert(image_format.modes[0])
dest = dest or image_file.with_suffix(image_format.suffix) if dest is None:
if image_file is None:
raise ValueError("If dest is not provided, image_file must be.")
dest = image_file.with_suffix(image_format.suffix)
img.save(dest, image_format.format, quality=quality) img.save(dest, image_format.format, quality=quality)
return dest return dest
@staticmethod @staticmethod
def to_jpg(image_file: Path, dest: Path | None = None, quality: int = 100) -> Path: def to_jpg(image_file_path: Path | None = None, dest_path: Path | None = None, quality: int = 100, img: Image.Image | None = None) -> Path:
return PillowMinifier._convert_image(image_file, JPG, dest, quality) return PillowMinifier._convert_image(image_file_path, JPG, dest_path, quality, img)
@staticmethod @staticmethod
def to_webp(image_file: Path, dest: Path | None = None, quality: int = 100) -> Path: def to_webp(image_file_path: Path | None = None, dest_path: Path | None = None, quality: int = 100, img: Image.Image | None = None) -> Path:
return PillowMinifier._convert_image(image_file, WEBP, dest, quality) return PillowMinifier._convert_image(image_file_path, WEBP, dest_path, quality, img)
@staticmethod @staticmethod
def crop_center(pil_img: Image, crop_width=300, crop_height=300): def crop_center(pil_img: Image, crop_width=300, crop_height=300) -> Image.Image:
img_width, img_height = pil_img.size img_width, img_height = pil_img.size
return pil_img.crop( return pil_img.crop(
( (
@ -117,36 +125,44 @@ class PillowMinifier(ABCMinifier):
tiny_dest = image_file.parent.joinpath("tiny-original.webp") tiny_dest = image_file.parent.joinpath("tiny-original.webp")
if not force and min_dest.exists() and tiny_dest.exists() and org_dest.exists(): if not force and min_dest.exists() and tiny_dest.exists() and org_dest.exists():
self._logger.info(f"{image_file.name} already minified") self._logger.info(f"{image_file.name} already exists in all formats")
return return
success = False success = False
if self._opts.original: try:
if not force and org_dest.exists(): with Image.open(image_file) as img:
self._logger.info(f"{image_file.name} already minified")
else:
PillowMinifier.to_webp(image_file, org_dest, quality=70)
success = True
if self._opts.miniature: if self._opts.original:
if not force and min_dest.exists(): if not force and org_dest.exists():
self._logger.info(f"{image_file.name} already minified") self._logger.info(f"{org_dest} already minified")
else: else:
PillowMinifier.to_webp(image_file, min_dest, quality=70) result_path = PillowMinifier.to_webp(dest_path=org_dest, quality=80, img=img.copy())
self._logger.info(f"{image_file.name} minified") self._logger.info(f"{result_path} created")
success = True success = True
if self._opts.tiny: if self._opts.miniature:
if not force and tiny_dest.exists(): if not force and min_dest.exists():
self._logger.info(f"{image_file.name} already minified") self._logger.info(f"{min_dest} already minified")
else: else:
img = Image.open(image_file) mini = img.copy()
img = ImageOps.exif_transpose(img) mini.thumbnail((1024, 1024), Image.LANCZOS)
tiny_image = PillowMinifier.crop_center(img) result_path = PillowMinifier.to_webp(dest_path=min_dest, quality=70, img=mini)
tiny_image.save(tiny_dest, WEBP.format, quality=70) self._logger.info(f"{result_path} created")
self._logger.info("Tiny image saved") success = True
success = True
if self._opts.tiny:
if not force and tiny_dest.exists():
self._logger.info(f"{tiny_dest} already minified")
else:
tiny_image = PillowMinifier.crop_center(ImageOps.exif_transpose(img.copy()))
result_path = PillowMinifier.to_webp(dest_path=tiny_dest, quality=70, img=tiny_image)
self._logger.info(f"{result_path} created")
success = True
except Exception as e:
self._logger.error(f"[ERROR] Failed to minify {image_file.name}. Error: {e}")
raise
if self._purge and success: if self._purge and success:
self.purge(image_file) self.purge(image_file)