-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathimage_utils.py
More file actions
83 lines (66 loc) · 2.84 KB
/
Copy pathimage_utils.py
File metadata and controls
83 lines (66 loc) · 2.84 KB
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
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
"""
Image encoding utilities: base64 encoding with optional resize and format conversion.
"""
from __future__ import annotations
import base64
import io
import logging
import os
import time
from PIL import Image
logger = logging.getLogger(__name__)
def encode_image(image_path: str) -> str | None:
"""Simple base64 encode of a file."""
if not image_path:
return None
with open(image_path, "rb") as f:
return base64.b64encode(f.read()).decode("utf-8")
def encode_image_with_resize(
image_path: str, max_dim: int | None = None, quality: int = 80
) -> tuple[str | None, int]:
"""
Encode image to base64 with optional downscaling and JPEG conversion.
Supports HEIC via pillow-heif (register_heif_opener must be called at startup).
Returns (b64_string, byte_size) or (None, 0) on failure.
"""
if not image_path or not os.path.exists(image_path):
logger.error(f"[ImageOpt] File not found: {image_path}")
return None, 0
ext = os.path.splitext(image_path)[1].lower()
start_time = time.time()
try:
logger.debug(
f"[ImageOpt] 🛠️ Processing {os.path.basename(image_path)} "
f"(Ext: {ext}, Target Max: {max_dim}px, Qual: {quality})"
)
img: Image.Image = Image.open(image_path) # type: ignore[assignment]
orig_w, orig_h = img.size
orig_format = img.format or "UNKNOWN"
logger.debug(f"[ImageOpt] 📥 Opened {orig_format}: {orig_w}x{orig_h}, Mode: {img.mode}")
if img.mode != "RGB":
logger.debug(f"[ImageOpt] 🔄 Converting {img.mode} -> RGB")
img = img.convert("RGB")
if max_dim and (orig_w > max_dim or orig_h > max_dim):
img.thumbnail((max_dim, max_dim), Image.Resampling.LANCZOS)
logger.debug(
f"[ImageOpt] 📏 Resized from {orig_w}x{orig_h} to {img.size[0]}x{img.size[1]}"
)
buffer = io.BytesIO()
img.save(buffer, format="JPEG", quality=quality, optimize=True)
img_bytes = buffer.getvalue()
duration = (time.time() - start_time) * 1000
conv_msg = f"Converted {orig_format} -> JPEG" if orig_format != "JPEG" else "Optimized JPEG"
logger.info(
f"[ImageOpt] ✅ {conv_msg} | {orig_w}x{orig_h} -> {img.size[0]}x{img.size[1]} "
f"| {len(img_bytes) / 1024:.1f} KB | {duration:.1f}ms"
)
return base64.b64encode(img_bytes).decode("utf-8"), len(img_bytes)
except Exception as e:
logger.error(f"[ImageOpt] Error processing {image_path}: {e}")
try:
with open(image_path, "rb") as f:
data = f.read()
logger.warning(f"[ImageOpt] Fallback to raw binary for {os.path.basename(image_path)}")
return base64.b64encode(data).decode("utf-8"), len(data)
except Exception:
return None, 0