File size: 4,788 Bytes
ed85b4d f76d838 ed85b4d 56af65e ed85b4d 56af65e ed85b4d f912c03 ed85b4d f912c03 ed85b4d 56af65e ed85b4d 56af65e ed85b4d 56af65e ed85b4d fc0bc36 ed85b4d 56af65e ed85b4d | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 | """
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'
}) |