|
from PIL import Image |
|
import os |
|
from typing import Literal, Optional |
|
from io import BytesIO |
|
|
|
def compress_image_file( |
|
input_path: str, |
|
output_path: str, |
|
quality: int = 85, |
|
format: Literal["JPEG", "PNG", "WEBP"] = "JPEG", |
|
max_width: Optional[int] = None, |
|
max_height: Optional[int] = None |
|
) -> str: |
|
""" |
|
Compress an image file from disk. |
|
""" |
|
try: |
|
if not os.path.splitext(output_path)[1]: |
|
extension_map = {"JPEG": ".jpg", "PNG": ".png", "WEBP": ".webp"} |
|
output_path = output_path + extension_map[format] |
|
|
|
with Image.open(input_path) as img: |
|
if format == "JPEG" and img.mode in ("RGBA", "P"): |
|
img = img.convert("RGB") |
|
|
|
if max_width or max_height: |
|
img.thumbnail((max_width or img.width, max_height or img.height), Image.Resampling.LANCZOS) |
|
|
|
save_kwargs = {"format": format, "optimize": True} |
|
if format in ["JPEG", "WEBP"]: |
|
save_kwargs["quality"] = quality |
|
|
|
img.save(output_path, **save_kwargs) |
|
|
|
original_size = os.path.getsize(input_path) / 1024 / 1024 |
|
compressed_size = os.path.getsize(output_path) / 1024 / 1024 |
|
reduction = (1 - compressed_size/original_size) * 100 |
|
|
|
return f"✅ Compressed successfully!\nOriginal: {original_size:.2f}MB → Compressed: {compressed_size:.2f}MB\nReduction: {reduction:.1f}%" |
|
|
|
except Exception as e: |
|
return f"❌ Error: {str(e)}" |
|
|
|
def compress_image_memory(image: Image.Image, quality: int = 80, format: str = "JPEG") -> Image.Image: |
|
""" |
|
Compress an image in memory and return the compressed image. |
|
""" |
|
if format == "JPEG" and image.mode in ("RGBA", "P"): |
|
image = image.convert("RGB") |
|
|
|
output = BytesIO() |
|
save_kwargs = {"format": format, "optimize": True} |
|
|
|
if format in ["JPEG", "WEBP"]: |
|
save_kwargs["quality"] = quality |
|
|
|
image.save(output, **save_kwargs) |
|
output.seek(0) |
|
|
|
return Image.open(output) |
|
|