""" Asset transformation functionality for Issue #144. This module provides asset transformation and thumbnail generation capabilities. """ from pathlib import Path from typing import List, Dict, Any, Optional, Tuple from dataclasses import dataclass from PIL import Image import io @dataclass class TransformationResult: """Result of an asset transformation operation.""" success: bool source_path: Path output_path: Path original_size: int transformed_size: int transformation_type: str error_message: Optional[str] = None class AssetTransformer: """Transforms assets between formats and sizes.""" def __init__(self): """Initialize the asset transformer.""" self.supported_formats = { 'image': ['.jpg', '.jpeg', '.png', '.gif', '.bmp', '.webp'], 'document': ['.pdf', '.docx', '.txt', '.md'], } def transform_image(self, source_path: Path, output_path: Path, width: Optional[int] = None, height: Optional[int] = None, format: Optional[str] = None, quality: int = 85) -> TransformationResult: """Transform an image file.""" try: with Image.open(source_path) as img: original_size = source_path.stat().st_size # Resize if dimensions provided if width or height: img = img.resize((width or img.width, height or img.height), Image.Resampling.LANCZOS) # Save with specified format or keep original save_format = format or img.format img.save(output_path, format=save_format, quality=quality) transformed_size = output_path.stat().st_size return TransformationResult( success=True, source_path=source_path, output_path=output_path, original_size=original_size, transformed_size=transformed_size, transformation_type=f"resize_{width}x{height}" if (width or height) else "format_conversion" ) except Exception as e: return TransformationResult( success=False, source_path=source_path, output_path=output_path, original_size=0, transformed_size=0, transformation_type="failed", error_message=str(e) ) def generate_thumbnail(self, source_path: Path, output_path: Path, size: Optional[Tuple[int, int]] = None) -> TransformationResult: """Generate a thumbnail for the given asset.""" size = size or (150, 150) return self.transform_image( source_path, output_path, width=size[0], height=size[1], format='JPEG', quality=80 ) def generate_resolution_variants(self, source_path: Path, output_dir: Path, sizes: Optional[List[Tuple[int, int]]] = None) -> List[TransformationResult]: """Generate multiple resolution variants of an image.""" if sizes is None: sizes = [(150, 150), (300, 300), (600, 600), (1200, 1200)] results = [] output_dir.mkdir(parents=True, exist_ok=True) for size in sizes: variant_name = f"{source_path.stem}_{size[0]}x{size[1]}{source_path.suffix}" output_path = output_dir / variant_name result = self.transform_image(source_path, output_path, width=size[0], height=size[1]) results.append(result) return results class ThumbnailGenerator: """Generates thumbnails for various asset types.""" def __init__(self, default_size: Tuple[int, int] = (150, 150)): """Initialize thumbnail generator.""" self.default_size = default_size self._transformer = None @property def transformer(self): if self._transformer is None: self._transformer = AssetTransformer() return self._transformer def generate_thumbnail(self, source_path: Path, output_path: Path, size: Optional[Tuple[int, int]] = None) -> TransformationResult: """Generate a thumbnail for the given asset.""" size = size or self.default_size return self.transformer.transform_image( source_path, output_path, width=size[0], height=size[1], format='JPEG', quality=80 ) def generate_thumbnails_batch(self, source_paths: List[Path], output_dir: Path, size: Optional[Tuple[int, int]] = None) -> List[TransformationResult]: """Generate thumbnails for multiple assets.""" results = [] output_dir.mkdir(parents=True, exist_ok=True) for source_path in source_paths: output_path = output_dir / f"{source_path.stem}_thumb.jpg" result = self.generate_thumbnail(source_path, output_path, size) results.append(result) return results