""" Image and video processing utilities for thumbnail generation """ import os import subprocess from PIL import Image from django.conf import settings def create_thumbnail(image_path, size=(300, 300), quality=85, format='WEBP'): """ Create a thumbnail for an uploaded image Args: image_path: Absolute path to original image size: Tuple of (width, height) for thumbnail max dimensions quality: Image quality (1-95 for JPEG, 1-100 for WebP) format: Output format ('WEBP', 'JPEG', 'PNG') Returns: Absolute path to thumbnail file Raises: ValueError: If image cannot be processed FileNotFoundError: If original image doesn't exist """ if not os.path.exists(image_path): raise FileNotFoundError(f"Original image not found: {image_path}") try: # Open and process image img = Image.open(image_path) # Convert RGBA to RGB if needed (for JPEG/WebP compatibility) if img.mode in ('RGBA', 'LA', 'P'): background = Image.new('RGB', img.size, (255, 255, 255)) if img.mode == 'RGBA': background.paste(img, mask=img.split()[-1]) else: background.paste(img) img = background # Create thumbnail maintaining aspect ratio img.thumbnail(size, Image.Resampling.LANCZOS) # Generate thumbnail path base, ext = os.path.splitext(image_path) # Choose extension based on format if format.upper() == 'WEBP': thumb_path = f"{base}_thumb.webp" img.save(thumb_path, 'WEBP', quality=quality, method=6) elif format.upper() == 'JPEG': thumb_path = f"{base}_thumb.jpg" img.save(thumb_path, 'JPEG', quality=quality, optimize=True) elif format.upper() == 'PNG': thumb_path = f"{base}_thumb.png" img.save(thumb_path, 'PNG', optimize=True) else: # Default to WebP thumb_path = f"{base}_thumb.webp" img.save(thumb_path, 'WEBP', quality=quality, method=6) return thumb_path except Exception as e: raise ValueError(f"Failed to create thumbnail: {str(e)}") def get_image_dimensions(image_path): """ Get dimensions of an image Args: image_path: Path to image file Returns: Tuple of (width, height) or None if error """ try: with Image.open(image_path) as img: return img.size except Exception: return None def is_image_file(file_path): """ Check if a file is an image based on extension and content Args: file_path: Path to file Returns: Boolean indicating if file is an image """ image_extensions = {'.jpg', '.jpeg', '.png', '.gif', '.webp', '.bmp', '.tiff'} ext = os.path.splitext(file_path)[1].lower() if ext not in image_extensions: return False try: with Image.open(file_path) as img: img.verify() return True except Exception: return False def is_video_file(file_path): """ Check if a file is a video based on extension Args: file_path: Path to file Returns: Boolean indicating if file is a video """ video_extensions = {'.mp4', '.avi', '.mov', '.mkv', '.flv', '.wmv', '.webm', '.m4v'} ext = os.path.splitext(file_path)[1].lower() return ext in video_extensions def extract_video_thumbnail(video_path, time_offset='00:00:01', size=(300, 300), quality=85): """ Extract a frame from video as thumbnail using FFmpeg Args: video_path: Absolute path to video file time_offset: Time position to extract frame (default: 1 second) size: Tuple of (width, height) for thumbnail max dimensions quality: JPEG quality (1-31 for FFmpeg, lower is better, we convert to FFmpeg scale) Returns: Absolute path to thumbnail file or None if failed Raises: FileNotFoundError: If video file doesn't exist ValueError: If FFmpeg is not installed or extraction fails """ if not os.path.exists(video_path): raise FileNotFoundError(f"Video file not found: {video_path}") # Check if FFmpeg is available try: subprocess.run(['ffmpeg', '-version'], stdout=subprocess.PIPE, stderr=subprocess.PIPE, timeout=5) except (subprocess.TimeoutExpired, FileNotFoundError): raise ValueError("FFmpeg is not installed or not available in PATH") try: # Generate output path base, ext = os.path.splitext(video_path) output_path = f"{base}_thumb.jpg" # Convert quality (85 -> 2 in FFmpeg scale) # FFmpeg uses 1-31 where lower is better, opposite of our 0-100 scale ffmpeg_quality = max(1, min(31, int((100 - quality) * 31 / 100))) # FFmpeg command to extract frame cmd = [ 'ffmpeg', '-ss', time_offset, # Seek to time position (before input for speed) '-i', video_path, # Input video '-vframes', '1', # Extract 1 frame '-vf', f'scale={size[0]}:-1', # Scale to width, maintain aspect ratio '-q:v', str(ffmpeg_quality), # Quality '-y', # Overwrite output output_path ] result = subprocess.run( cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, timeout=15 # 15 second timeout ) if result.returncode == 0 and os.path.exists(output_path): return output_path else: error_msg = result.stderr.decode('utf-8', errors='ignore') raise ValueError(f"FFmpeg extraction failed: {error_msg[:200]}") except subprocess.TimeoutExpired: raise ValueError("Video thumbnail extraction timed out (>15s)") except Exception as e: raise ValueError(f"Failed to extract video thumbnail: {str(e)}")