352 lines
11 KiB
Python
352 lines
11 KiB
Python
"""
|
|
Template utility functions for size parsing and template management
|
|
"""
|
|
|
|
import os
|
|
from pathlib import Path
|
|
from typing import List, Tuple, Optional, Literal
|
|
from pydantic import BaseModel, Field
|
|
|
|
|
|
def parse_template_size(template_path: str) -> Tuple[int, int]:
|
|
"""
|
|
Parse video size from template path
|
|
|
|
Args:
|
|
template_path: Template path like "templates/1080x1920/default.html"
|
|
or "1080x1920/default.html"
|
|
|
|
Returns:
|
|
Tuple of (width, height) in pixels
|
|
|
|
Raises:
|
|
ValueError: If template path format is invalid
|
|
|
|
Examples:
|
|
>>> parse_template_size("templates/1080x1920/default.html")
|
|
(1080, 1920)
|
|
>>> parse_template_size("1920x1080/modern.html")
|
|
(1920, 1080)
|
|
"""
|
|
path = Path(template_path)
|
|
|
|
# Get parent directory name (should be like "1080x1920")
|
|
dir_name = path.parent.name
|
|
|
|
# Special case: if parent is "templates", go up one more level
|
|
if dir_name == "templates":
|
|
# This shouldn't happen in new structure, but handle it
|
|
raise ValueError(
|
|
f"Invalid template path format: {template_path}. "
|
|
f"Expected format: 'WIDTHxHEIGHT/template.html' or 'templates/WIDTHxHEIGHT/template.html'"
|
|
)
|
|
|
|
# Parse size from directory name
|
|
if 'x' not in dir_name:
|
|
raise ValueError(
|
|
f"Invalid size format in path: {template_path}. "
|
|
f"Directory name should be 'WIDTHxHEIGHT' (e.g., '1080x1920')"
|
|
)
|
|
|
|
try:
|
|
width_str, height_str = dir_name.split('x')
|
|
width = int(width_str)
|
|
height = int(height_str)
|
|
|
|
# Sanity check
|
|
if width < 100 or height < 100 or width > 10000 or height > 10000:
|
|
raise ValueError(f"Invalid size dimensions: {width}x{height}")
|
|
|
|
return (width, height)
|
|
except ValueError as e:
|
|
raise ValueError(
|
|
f"Failed to parse size from path: {template_path}. "
|
|
f"Expected format: 'WIDTHxHEIGHT/template.html' (e.g., '1080x1920/default.html'). "
|
|
f"Error: {e}"
|
|
)
|
|
|
|
|
|
def list_available_sizes() -> List[str]:
|
|
"""
|
|
List all available video sizes
|
|
|
|
Returns:
|
|
List of size strings like ["1080x1920", "1920x1080", "1080x1080"]
|
|
|
|
Examples:
|
|
>>> list_available_sizes()
|
|
['1080x1920', '1920x1080', '1080x1080']
|
|
"""
|
|
templates_dir = Path("templates")
|
|
|
|
if not templates_dir.exists():
|
|
return []
|
|
|
|
sizes = []
|
|
for item in templates_dir.iterdir():
|
|
if item.is_dir() and 'x' in item.name:
|
|
# Validate it's a proper size format
|
|
try:
|
|
width, height = item.name.split('x')
|
|
int(width)
|
|
int(height)
|
|
sizes.append(item.name)
|
|
except (ValueError, AttributeError):
|
|
# Skip invalid directories
|
|
continue
|
|
|
|
return sorted(sizes)
|
|
|
|
|
|
def list_templates_for_size(size: str) -> List[str]:
|
|
"""
|
|
List all templates available for a given size
|
|
|
|
Args:
|
|
size: Size string like "1080x1920"
|
|
|
|
Returns:
|
|
List of template filenames (without path) like ["default.html", "modern.html"]
|
|
|
|
Examples:
|
|
>>> list_templates_for_size("1080x1920")
|
|
['cartoon.html', 'default.html', 'elegant.html', 'modern.html', ...]
|
|
"""
|
|
size_dir = Path("templates") / size
|
|
|
|
if not size_dir.exists() or not size_dir.is_dir():
|
|
return []
|
|
|
|
templates = []
|
|
for item in size_dir.iterdir():
|
|
if item.is_file() and item.suffix == '.html':
|
|
templates.append(item.name)
|
|
|
|
return sorted(templates)
|
|
|
|
|
|
def get_template_full_path(size: str, template_name: str) -> str:
|
|
"""
|
|
Get full template path from size and template name
|
|
|
|
Args:
|
|
size: Size string like "1080x1920"
|
|
template_name: Template filename like "default.html"
|
|
|
|
Returns:
|
|
Full path like "templates/1080x1920/default.html"
|
|
|
|
Raises:
|
|
FileNotFoundError: If template file doesn't exist
|
|
|
|
Examples:
|
|
>>> get_template_full_path("1080x1920", "default.html")
|
|
'templates/1080x1920/default.html'
|
|
"""
|
|
template_path = Path("templates") / size / template_name
|
|
|
|
if not template_path.exists():
|
|
available_templates = list_templates_for_size(size)
|
|
raise FileNotFoundError(
|
|
f"Template not found: {template_path}\n"
|
|
f"Available templates for size {size}: {available_templates}"
|
|
)
|
|
|
|
return str(template_path)
|
|
|
|
|
|
class TemplateDisplayInfo(BaseModel):
|
|
"""Template display information for UI layer"""
|
|
|
|
name: str = Field(..., description="Template name without extension")
|
|
size: str = Field(..., description="Size string like '1080x1920'")
|
|
width: int = Field(..., description="Width in pixels")
|
|
height: int = Field(..., description="Height in pixels")
|
|
orientation: Literal['portrait', 'landscape', 'square'] = Field(
|
|
...,
|
|
description="Video orientation"
|
|
)
|
|
is_standard: bool = Field(
|
|
...,
|
|
description="True only for standard sizes: 1080x1920, 1920x1080, 1080x1080"
|
|
)
|
|
|
|
|
|
class TemplateInfo(BaseModel):
|
|
"""Complete template information with path and display info"""
|
|
|
|
template_path: str = Field(..., description="Full template path like '1080x1920/default.html'")
|
|
display_info: TemplateDisplayInfo = Field(..., description="Display information")
|
|
|
|
|
|
def format_template_display_info(template_name: str, size: str) -> TemplateDisplayInfo:
|
|
"""
|
|
Format template display information for UI
|
|
|
|
Returns structured data for UI layer to handle display and i18n.
|
|
|
|
Args:
|
|
template_name: Template filename like "default.html"
|
|
size: Size string like "1080x1920"
|
|
|
|
Returns:
|
|
TemplateDisplayInfo object with name, size, dimensions, orientation, and standard flag
|
|
|
|
Examples:
|
|
>>> info = format_template_display_info("default.html", "1080x1920")
|
|
>>> info.name
|
|
'default'
|
|
>>> info.is_standard
|
|
True
|
|
|
|
>>> info = format_template_display_info("custom.html", "1080x1921")
|
|
>>> info.orientation
|
|
'portrait'
|
|
>>> info.is_standard
|
|
False
|
|
"""
|
|
# Keep full template name with .html extension
|
|
name = template_name
|
|
|
|
# Parse size
|
|
width, height = map(int, size.split('x'))
|
|
|
|
# Detect orientation
|
|
if height > width:
|
|
orientation = 'portrait'
|
|
elif width > height:
|
|
orientation = 'landscape'
|
|
else:
|
|
orientation = 'square'
|
|
|
|
# Check if it's a standard size (only these three)
|
|
is_standard = (width, height) in [(1080, 1920), (1920, 1080), (1080, 1080)]
|
|
|
|
return TemplateDisplayInfo(
|
|
name=name,
|
|
size=size,
|
|
width=width,
|
|
height=height,
|
|
orientation=orientation,
|
|
is_standard=is_standard
|
|
)
|
|
|
|
|
|
def get_all_templates_with_info() -> List[TemplateInfo]:
|
|
"""
|
|
Get all templates with their display information
|
|
|
|
Returns:
|
|
List of TemplateInfo objects
|
|
|
|
Example:
|
|
>>> templates = get_all_templates_with_info()
|
|
>>> for t in templates:
|
|
... print(f"{t.display_info.name} - {t.display_info.orientation}")
|
|
... print(f" Path: {t.template_path}")
|
|
... print(f" Standard: {t.display_info.is_standard}")
|
|
"""
|
|
result = []
|
|
sizes = list_available_sizes()
|
|
|
|
for size in sizes:
|
|
templates = list_templates_for_size(size)
|
|
for template in templates:
|
|
display_info = format_template_display_info(template, size)
|
|
full_path = f"{size}/{template}"
|
|
result.append(TemplateInfo(
|
|
template_path=full_path,
|
|
display_info=display_info
|
|
))
|
|
|
|
return result
|
|
|
|
|
|
def get_templates_grouped_by_size() -> dict:
|
|
"""
|
|
Get templates grouped by size
|
|
|
|
Returns:
|
|
Dict with size as key, list of TemplateInfo as value
|
|
Ordered by orientation priority: portrait > landscape > square
|
|
|
|
Example:
|
|
>>> grouped = get_templates_grouped_by_size()
|
|
>>> for size, templates in grouped.items():
|
|
... print(f"Size: {size}")
|
|
... for t in templates:
|
|
... print(f" - {t.display_info.name}")
|
|
"""
|
|
from collections import defaultdict
|
|
|
|
templates = get_all_templates_with_info()
|
|
grouped = defaultdict(list)
|
|
|
|
for t in templates:
|
|
grouped[t.display_info.size].append(t)
|
|
|
|
# Sort groups by orientation priority: portrait > landscape > square
|
|
orientation_priority = {'portrait': 0, 'landscape': 1, 'square': 2}
|
|
|
|
sorted_grouped = {}
|
|
for size in sorted(grouped.keys(), key=lambda s: (
|
|
orientation_priority.get(grouped[s][0].display_info.orientation, 3),
|
|
s
|
|
)):
|
|
sorted_grouped[size] = sorted(grouped[size], key=lambda t: t.display_info.name)
|
|
|
|
return sorted_grouped
|
|
|
|
|
|
def resolve_template_path(template_input: Optional[str]) -> str:
|
|
"""
|
|
Resolve template input to full path with validation
|
|
|
|
Args:
|
|
template_input: Can be:
|
|
- None: Use default "1080x1920/default.html"
|
|
- "template.html": Use default size + this template
|
|
- "1080x1920/template.html": Full relative path
|
|
- "templates/1080x1920/template.html": Absolute-ish path
|
|
|
|
Returns:
|
|
Resolved full path like "templates/1080x1920/default.html"
|
|
|
|
Raises:
|
|
FileNotFoundError: If template doesn't exist
|
|
|
|
Examples:
|
|
>>> resolve_template_path(None)
|
|
'templates/1080x1920/default.html'
|
|
>>> resolve_template_path("modern.html")
|
|
'templates/1080x1920/modern.html'
|
|
>>> resolve_template_path("1920x1080/default.html")
|
|
'templates/1920x1080/default.html'
|
|
"""
|
|
# Default case
|
|
if template_input is None:
|
|
template_input = "1080x1920/default.html"
|
|
|
|
# If already starts with "templates/", use as-is
|
|
if template_input.startswith("templates/"):
|
|
template_path = Path(template_input)
|
|
# If contains size directory (e.g., "1080x1920/default.html")
|
|
elif '/' in template_input and 'x' in template_input.split('/')[0]:
|
|
template_path = Path("templates") / template_input
|
|
# Just template name (e.g., "default.html")
|
|
else:
|
|
# Use default size
|
|
template_path = Path("templates") / "1080x1920" / template_input
|
|
|
|
# Validate existence
|
|
if not template_path.exists():
|
|
available_sizes = list_available_sizes()
|
|
raise FileNotFoundError(
|
|
f"Template not found: {template_path}\n"
|
|
f"Available sizes: {available_sizes}\n"
|
|
f"Hint: Use format 'SIZExSIZE/template.html' (e.g., '1080x1920/default.html')"
|
|
)
|
|
|
|
return str(template_path)
|
|
|