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."