""" Common utilities and constants Stable functions that rarely change """ import os import time from pathlib import Path from typing import Dict, Any, List, Set # Constants IMG_EXTS: Set[str] = {'.jpg', '.jpeg', '.png', '.bmp', '.webp'} VID_EXTS: Set[str] = {'.mp4', '.avi', '.mov', '.mkv'} ASPECT: Dict[str, str] = { 'youtube': '1920:1080', 'reels': '1080:1920', 'tiktok': '1080:1920', 'square': '1080:1080', 'widescreen': '1920:1080' } QUALITY: Dict[str, Dict[str, str]] = { 'fast': {'preset': 'ultrafast', 'crf': '28'}, 'balanced': {'preset': 'fast', 'crf': '23'}, 'quality': {'preset': 'medium', 'crf': '20'}, 'best': {'preset': 'slow', 'crf': '18'} } # Utility functions def is_image(file_path: str) -> bool: """Check if file is supported image format""" return Path(file_path).suffix.lower() in IMG_EXTS def is_video(file_path: str) -> bool: """Check if file is supported video format""" return Path(file_path).suffix.lower() in VID_EXTS def is_media(file_path: str) -> bool: """Check if file is supported media format""" return is_image(file_path) or is_video(file_path) def size_mb(file_path: str) -> float: """Get file size in MB""" if not os.path.exists(file_path): return 0.0 return round(os.path.getsize(file_path) / (1024 * 1024), 2) def unique_name(prefix: str, extension: str = '.mp4') -> str: """Generate unique filename with timestamp and PID""" return f"{prefix}_{int(time.time())}_{os.getpid()}{extension}" def safe_filename(name: str) -> str: """Make filename safe for filesystem""" invalid_chars = '<>:"/\\|?*' for char in invalid_chars: name = name.replace(char, '_') return name[:100] # Limit length def format_duration(seconds: int) -> str: """Format seconds to human readable duration""" if seconds < 60: return f"{seconds}s" elif seconds < 3600: return f"{seconds // 60}m {seconds % 60}s" else: h = seconds // 3600 m = (seconds % 3600) // 60 return f"{h}h {m}m" def estimate_output(input_files: List[str], duration_per_file: int = 4) -> Dict[str, Any]: """Estimate output video characteristics""" total_files = len(input_files) total_duration = total_files * duration_per_file # Rough size estimation (varies greatly by content) estimated_size_mb = total_duration * 2 # ~2MB per second for balanced quality return { 'files': total_files, 'duration': total_duration, 'duration_fmt': format_duration(total_duration), 'size_mb': round(estimated_size_mb, 1) } def valid_template(template: Dict[str, Any]) -> bool: """Validate motion template structure""" required_fields = ['name', 'scale', 'pan', 'rotate', 'duration'] if not all(field in template for field in required_fields): return False # Validate data types and ranges if not isinstance(template['duration'], (int, float)) or template['duration'] <= 0: return False if not (isinstance(template['scale'], list) and len(template['scale']) == 2): return False if not (isinstance(template['pan'], list) and len(template['pan']) == 4): return False if not (isinstance(template['rotate'], list) and len(template['rotate']) == 2): return False return True def get_platform_info(aspect_key: str) -> Dict[str, str]: """Get platform information and recommendations""" platform_info = { 'youtube': { 'name': 'YouTube', 'ratio': '16:9', 'optimal_duration': '15-60s', 'description': 'Horizontal format for YouTube Shorts and regular videos' }, 'reels': { 'name': 'Instagram Reels', 'ratio': '9:16', 'optimal_duration': '15-30s', 'description': 'Vertical format optimized for mobile viewing' }, 'tiktok': { 'name': 'TikTok', 'ratio': '9:16', 'optimal_duration': '15-60s', 'description': 'Vertical format for TikTok content' }, 'square': { 'name': 'Instagram Post', 'ratio': '1:1', 'optimal_duration': '15-30s', 'description': 'Square format for Instagram feed posts' }, 'widescreen': { 'name': 'Widescreen', 'ratio': '16:9', 'optimal_duration': '30-120s', 'description': 'Standard widescreen format for presentations' } } return platform_info.get(aspect_key, { 'name': 'Custom', 'ratio': 'Custom', 'optimal_duration': 'Variable', 'description': 'Custom aspect ratio' })