Update engine.py

This commit is contained in:
2026-01-20 12:35:01 +01:00
parent 8528ea73bf
commit 77aaae6491

View File

@@ -1,10 +1,7 @@
import os import os
import shutil import shutil
import sqlite3 import sqlite3
import threading
import hashlib
from contextlib import contextmanager from contextlib import contextmanager
from functools import lru_cache
from PIL import Image from PIL import Image
from io import BytesIO from io import BytesIO
from typing import Dict, List, Optional, Tuple from typing import Dict, List, Optional, Tuple
@@ -285,33 +282,12 @@ class SorterEngine:
return fid return fid
# ========================================== # ==========================================
# IMAGE COMPRESSION (OPTIMIZED WITH CACHING) # IMAGE COMPRESSION
# ========================================== # ==========================================
# Simple in-memory cache for thumbnails
_thumbnail_cache: Dict[str, bytes] = {}
_cache_max_items = 500
_cache_lock = threading.Lock()
@classmethod
def _get_cache_key(cls, path: str, quality: int, target_size: Optional[int]) -> str:
"""Generate cache key including file modification time."""
try:
mtime = os.path.getmtime(path)
except OSError:
mtime = 0
return hashlib.md5(f"{path}:{quality}:{target_size}:{mtime}".encode()).hexdigest()
@classmethod @classmethod
def compress_for_web(cls, path: str, quality: int, target_size: Optional[int] = None) -> Optional[bytes]: def compress_for_web(cls, path: str, quality: int, target_size: Optional[int] = None) -> Optional[bytes]:
"""Loads image, resizes smart, and saves as WebP with caching.""" """Loads image, resizes smart, and saves as WebP."""
cache_key = cls._get_cache_key(path, quality, target_size)
# Check cache first
with cls._cache_lock:
if cache_key in cls._thumbnail_cache:
return cls._thumbnail_cache[cache_key]
try: try:
with Image.open(path) as img: with Image.open(path) as img:
# Keep RGBA for WebP support, only convert unusual modes # Keep RGBA for WebP support, only convert unusual modes
@@ -326,19 +302,8 @@ class SorterEngine:
# Save as WebP # Save as WebP
buf = BytesIO() buf = BytesIO()
img.save(buf, format="WEBP", quality=quality, method=4) # method=4 is faster img.save(buf, format="WEBP", quality=quality)
result = buf.getvalue() return buf.getvalue()
# Cache the result
with cls._cache_lock:
if len(cls._thumbnail_cache) >= cls._cache_max_items:
# Simple eviction: remove oldest 20%
keys_to_remove = list(cls._thumbnail_cache.keys())[:cls._cache_max_items // 5]
for k in keys_to_remove:
del cls._thumbnail_cache[k]
cls._thumbnail_cache[cache_key] = result
return result
except Exception: except Exception:
return None return None
@@ -350,7 +315,8 @@ class SorterEngine:
def process_one(path): def process_one(path):
return path, SorterEngine.compress_for_web(path, quality) return path, SorterEngine.compress_for_web(path, quality)
with concurrent.futures.ThreadPoolExecutor(max_workers=8) as executor: # Reduced from 8 to 2 workers to prevent memory issues
with concurrent.futures.ThreadPoolExecutor(max_workers=2) as executor:
future_to_path = {executor.submit(process_one, p): p for p in image_paths} future_to_path = {executor.submit(process_one, p): p for p in image_paths}
for future in concurrent.futures.as_completed(future_to_path): for future in concurrent.futures.as_completed(future_to_path):
try: try: