editor / src /utils.py
Vo Hoang Minh
u
fc0bc36
"""
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'
})