Files
foldsite/src/server/image_optimizer.py
Tanishq Dubey ad81d7f3db
All checks were successful
Datadog Software Composition Analysis / Datadog SBOM Generation and Upload (push) Successful in 52s
Datadog Secrets Scanning / Datadog Static Analyzer (push) Successful in 1m1s
Datadog Static Analysis / Datadog Static Analyzer (push) Successful in 5m50s
docs refactor
2025-10-09 18:21:23 -04:00

204 lines
7.4 KiB
Python

"""
Image optimization utilities for foldsite
Follows grug principles: simple, focused functionality
"""
import os
from PIL import Image, ExifTags
from pathlib import Path
class ImageOptimizer:
"""Simple image optimization following grug principles - does one thing well"""
def __init__(self, max_width=2048, max_height=2048, quality=85):
self.max_width = max_width
self.max_height = max_height
self.quality = quality
def optimize_image(self, input_path, output_path=None, preserve_exif=True):
"""
Optimize a single image - grug-simple approach
"""
if output_path is None:
output_path = input_path
try:
with Image.open(input_path) as img:
# Handle EXIF orientation
if preserve_exif and hasattr(img, '_getexif'):
exif = img._getexif()
if exif is not None:
orientation = exif.get(0x0112, 1)
if orientation == 3:
img = img.rotate(180, expand=True)
elif orientation == 6:
img = img.rotate(270, expand=True)
elif orientation == 8:
img = img.rotate(90, expand=True)
# Resize if too large
if img.width > self.max_width or img.height > self.max_height:
img.thumbnail((self.max_width, self.max_height), Image.Resampling.LANCZOS)
# Convert to RGB if needed (for JPEG output)
if img.mode in ('RGBA', 'P'):
rgb_img = Image.new('RGB', img.size, (255, 255, 255))
rgb_img.paste(img, mask=img.split()[-1] if img.mode == 'RGBA' else None)
img = rgb_img
# Save optimized image
save_kwargs = {'quality': self.quality, 'optimize': True}
# Preserve some EXIF if possible
if preserve_exif and hasattr(img, '_getexif'):
exif_dict = img._getexif()
if exif_dict:
# Keep important EXIF data
save_kwargs['exif'] = img.info.get('exif', b'')
img.save(output_path, **save_kwargs)
return True
except Exception as e:
print(f"Error optimizing {input_path}: {e}")
return False
def optimize_folder(self, folder_path, backup=True):
"""
Optimize all images in a folder - simple batch processing
"""
folder_path = Path(folder_path)
results = {'optimized': 0, 'errors': 0, 'skipped': 0}
image_extensions = {'.jpg', '.jpeg', '.png', '.gif'}
for file_path in folder_path.rglob('*'):
if file_path.suffix.lower() in image_extensions:
try:
# Create backup if requested
if backup:
backup_path = file_path.with_suffix(f'{file_path.suffix}.backup')
if not backup_path.exists():
file_path.rename(backup_path)
source_path = backup_path
else:
source_path = file_path
else:
source_path = file_path
# Optimize
if self.optimize_image(source_path, file_path):
results['optimized'] += 1
else:
results['errors'] += 1
except Exception:
results['errors'] += 1
return results
def generate_thumbnails(self, image_path, thumbnail_dir, sizes=[150, 300, 600]):
"""
Generate thumbnails in multiple sizes - simple and useful
"""
image_path = Path(image_path)
thumbnail_dir = Path(thumbnail_dir)
thumbnail_dir.mkdir(exist_ok=True)
base_name = image_path.stem
extension = image_path.suffix
generated = []
try:
with Image.open(image_path) as img:
for size in sizes:
# Create thumbnail
thumb = img.copy()
thumb.thumbnail((size, size), Image.Resampling.LANCZOS)
# Save thumbnail
thumb_name = f"{base_name}_{size}px{extension}"
thumb_path = thumbnail_dir / thumb_name
# Convert to RGB for JPEG if needed
if thumb.mode in ('RGBA', 'P') and extension.lower() in ['.jpg', '.jpeg']:
rgb_thumb = Image.new('RGB', thumb.size, (255, 255, 255))
rgb_thumb.paste(thumb, mask=thumb.split()[-1] if thumb.mode == 'RGBA' else None)
thumb = rgb_thumb
thumb.save(thumb_path, quality=self.quality, optimize=True)
generated.append(thumb_path)
except Exception as e:
print(f"Error generating thumbnails for {image_path}: {e}")
return generated
def get_image_info(self, image_path):
"""
Get basic image information - simple and fast
"""
try:
with Image.open(image_path) as img:
info = {
'width': img.width,
'height': img.height,
'format': img.format,
'mode': img.mode,
'size_kb': os.path.getsize(image_path) // 1024
}
# Get EXIF data if available
if hasattr(img, '_getexif'):
exif = img._getexif()
if exif:
exif_data = {}
for key, value in exif.items():
tag = ExifTags.TAGS.get(key, key)
exif_data[tag] = value
info['exif'] = exif_data
return info
except Exception as e:
return {'error': str(e)}
def bulk_optimize_images(content_dir, max_width=2048, quality=85):
"""
Utility function for bulk optimization - grug simple
"""
optimizer = ImageOptimizer(max_width=max_width, quality=quality)
return optimizer.optimize_folder(content_dir, backup=True)
def create_image_gallery_data(folder_path):
"""
Create gallery data structure for templates - simple and useful
"""
folder_path = Path(folder_path)
images = []
image_extensions = {'.jpg', '.jpeg', '.png', '.gif'}
for file_path in folder_path.iterdir():
if file_path.suffix.lower() in image_extensions and file_path.is_file():
optimizer = ImageOptimizer()
info = optimizer.get_image_info(file_path)
if 'error' not in info:
images.append({
'filename': file_path.name,
'path': str(file_path.relative_to(folder_path.parent)),
'width': info['width'],
'height': info['height'],
'size_kb': info['size_kb'],
'exif': info.get('exif', {}),
'date_taken': info.get('exif', {}).get('DateTimeOriginal', ''),
'camera': info.get('exif', {}).get('Model', '')
})
# Sort by date taken or filename
images.sort(key=lambda x: x.get('date_taken') or x['filename'])
return images