75 lines
2.3 KiB
Python
75 lines
2.3 KiB
Python
"""Image processing operations"""
|
|
import io
|
|
import hashlib
|
|
from datetime import datetime
|
|
from PIL import Image
|
|
|
|
from config import (
|
|
ALLOWED_FORMATS, MAX_DIMENSION, MAX_FILE_SIZE,
|
|
RESIZE_TARGET, THUMBNAIL_TARGET
|
|
)
|
|
|
|
|
|
def validate_image(img_data: bytes) -> tuple[Image.Image, str]:
|
|
"""Validate and open image data"""
|
|
if len(img_data) > MAX_FILE_SIZE:
|
|
raise ValueError(f'File too large: {len(img_data)} bytes')
|
|
|
|
img_hash = hashlib.sha256(img_data).hexdigest()
|
|
img = Image.open(io.BytesIO(img_data))
|
|
|
|
if img.format not in ALLOWED_FORMATS:
|
|
raise ValueError(f'Invalid format: {img.format}')
|
|
|
|
if img.width * img.height > MAX_DIMENSION ** 2:
|
|
raise ValueError(f'Image too large: {img.width}x{img.height}')
|
|
|
|
return img, img_hash
|
|
|
|
|
|
def determine_processing(filename: str) -> tuple[tuple[int, int], str]:
|
|
"""Determine processing type based on filename"""
|
|
fn = filename.lower()
|
|
if '_thumb' in fn:
|
|
return THUMBNAIL_TARGET, 'resize_200x200'
|
|
elif '_grayscale' in fn:
|
|
return None, 'grayscale'
|
|
else:
|
|
return RESIZE_TARGET, 'resize_1024x1024'
|
|
|
|
|
|
def process_image(img: Image.Image, target: tuple[int, int], ptype: str) -> Image.Image:
|
|
"""Apply image processing transformations"""
|
|
if ptype == 'grayscale':
|
|
return img.convert('L')
|
|
elif target:
|
|
img.thumbnail(target, Image.Resampling.LANCZOS)
|
|
return img
|
|
|
|
|
|
def save_image(img: Image.Image, original_format: str) -> tuple[bytes, str]:
|
|
"""Save processed image to bytes"""
|
|
output = io.BytesIO()
|
|
fmt = original_format or 'JPEG'
|
|
img.save(output, format=fmt, quality=85)
|
|
return output.getvalue(), f'image/{fmt.lower()}'
|
|
|
|
|
|
def get_processed_key(original_key: str) -> str:
|
|
"""Generate processed object key"""
|
|
return original_key.replace('uploads/', 'processed/')
|
|
|
|
|
|
def build_result(original_key: str, processed_key: str, orig_size: tuple,
|
|
img: Image.Image, ptype: str, img_hash: str) -> dict:
|
|
"""Build processing result dictionary"""
|
|
return {
|
|
'status': 'success',
|
|
'original_size': orig_size,
|
|
'processed_size': img.size,
|
|
'processing_type': ptype,
|
|
'processed_key': processed_key,
|
|
'timestamp': datetime.utcnow().isoformat(),
|
|
'hash': img_hash
|
|
}
|