Spaces:
Running
Running
File size: 1,542 Bytes
64276f2 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 | import base64
import io
import logging
from typing import Optional, Tuple
from PIL import Image, ImageOps
logger = logging.getLogger(__name__)
MAX_SIDE = 768 # sisi terpanjang setelah resize
JPEG_QUALITIES = (72, 60, 50)
MAX_OUTPUT_BYTES = 200_000
MAX_PIXELS = 40_000_000 # tolak gambar raksasa (decompression bomb)
Image.MAX_IMAGE_PIXELS = MAX_PIXELS
def compress_image_to_data_url(path: Optional[str]) -> Tuple[Optional[str], str]:
"""Return (data_url, error). Semua di memori; tidak ada file baru ditulis."""
if not path:
return None, ""
try:
with Image.open(path) as src:
if src.width * src.height > MAX_PIXELS:
return None, "Resolusi foto terlalu besar."
src.draft("RGB", (MAX_SIDE * 2, MAX_SIDE * 2)) # percepat decode JPEG
img = ImageOps.exif_transpose(src) # perbaiki rotasi HP
img = img.convert("RGB")
img.thumbnail((MAX_SIDE, MAX_SIDE), Image.Resampling.LANCZOS)
data = b""
for q in JPEG_QUALITIES:
buf = io.BytesIO()
img.save(buf, "JPEG", quality=q, optimize=True) # EXIF/GPS tidak ikut
data = buf.getvalue()
if len(data) <= MAX_OUTPUT_BYTES:
break
b64 = base64.b64encode(data).decode("ascii")
return f"data:image/jpeg;base64,{b64}", ""
except Exception as e:
logger.warning(f"Image compress failed: {e}")
return None, "Foto tidak dapat diproses." |