Spaces:
Sleeping
Sleeping
| from __future__ import annotations | |
| import logging | |
| import time | |
| from dataclasses import dataclass | |
| from pathlib import Path | |
| from typing import Optional | |
| from PIL import Image | |
| from config import Config | |
| logger = logging.getLogger("image_processor") | |
| class OptimizationError(Exception): | |
| """Raised when saving an optimized image fails.""" | |
| class OptimizationResult: | |
| output_path: Path | |
| final_width: int | |
| final_height: int | |
| file_size_bytes: int | |
| elapsed_seconds: float | |
| class Optimizer: | |
| """Resize (optional) and write JPEG with metadata stripped.""" | |
| def __init__(self, config: Config) -> None: | |
| self.config = config | |
| def optimize_and_save(self, image: Image.Image, output_path: Path) -> OptimizationResult: | |
| start = time.perf_counter() | |
| resized = self._maybe_resize(image) | |
| save_kwargs: dict[str, object] = { | |
| "format": "JPEG", | |
| "quality": int(self.config.jpeg_quality), | |
| "optimize": True, | |
| "progressive": True, | |
| } | |
| if not self.config.strip_metadata: | |
| exif = image.info.get("exif") | |
| if exif: | |
| save_kwargs["exif"] = exif | |
| try: | |
| output_path.parent.mkdir(parents=True, exist_ok=True) | |
| resized.save(output_path, **save_kwargs) | |
| except PermissionError as exc: | |
| raise OptimizationError(f"Permission denied writing {output_path}: {exc}") from exc | |
| except OSError as exc: | |
| raise OptimizationError(f"Failed to save {output_path}: {exc}") from exc | |
| elapsed = time.perf_counter() - start | |
| return OptimizationResult( | |
| output_path=output_path, | |
| final_width=resized.width, | |
| final_height=resized.height, | |
| file_size_bytes=output_path.stat().st_size, | |
| elapsed_seconds=elapsed, | |
| ) | |
| def _maybe_resize(self, image: Image.Image) -> Image.Image: | |
| max_w = self.config.max_width | |
| max_h = self.config.max_height | |
| if not max_w and not max_h: | |
| return image | |
| width, height = image.size | |
| scale = 1.0 | |
| if max_w and width > max_w: | |
| scale = min(scale, max_w / width) | |
| if max_h and height > max_h: | |
| scale = min(scale, max_h / height) | |
| if scale >= 1.0: | |
| return image | |
| new_size = (max(1, int(width * scale)), max(1, int(height * scale))) | |
| return image.resize(new_size, Image.Resampling.LANCZOS) | |