diff --git a/pixelle_video/utils/template_util.py b/pixelle_video/utils/template_util.py index 372bf55..fdbf22c 100644 --- a/pixelle_video/utils/template_util.py +++ b/pixelle_video/utils/template_util.py @@ -4,7 +4,8 @@ Template utility functions for size parsing and template management import os from pathlib import Path -from typing import List, Tuple, Optional +from typing import List, Tuple, Optional, Literal +from pydantic import BaseModel, Field def parse_template_size(template_path: str) -> Tuple[int, int]: @@ -154,6 +155,113 @@ def get_template_full_path(size: str, template_name: str) -> str: 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 resolve_template_path(template_input: Optional[str]) -> str: """ Resolve template input to full path with validation diff --git a/web/app.py b/web/app.py index acf5eaf..a0ca382 100644 --- a/web/app.py +++ b/web/app.py @@ -667,47 +667,52 @@ def main(): st.markdown(tr("template.how")) # Import template utilities - from pixelle_video.utils.template_util import list_available_sizes, list_templates_for_size + from pixelle_video.utils.template_util import get_all_templates_with_info - # Step 1: Select video size - VIDEO_SIZE_OPTIONS = { - "📱 竖屏视频 (1080×1920)": "1080x1920", - "🖥 横屏视频 (1920×1080)": "1920x1080", - "⬜ 方形视频 (1080×1080)": "1080x1080", - } + # Get all templates with their info + all_templates = get_all_templates_with_info() - # Filter available sizes (only show sizes that exist) - available_sizes = list_available_sizes() - available_size_options = {k: v for k, v in VIDEO_SIZE_OPTIONS.items() if v in available_sizes} - - if not available_size_options: - st.error("No template sizes found. Please ensure templates are in correct directory structure.") + if not all_templates: + st.error("No templates found. Please ensure templates are in templates/ directory with proper structure (e.g., templates/1080x1920/default.html).") st.stop() - selected_size_label = st.selectbox( - tr("template.video_size"), - list(available_size_options.keys()), - label_visibility="collapsed" - ) - selected_size = available_size_options[selected_size_label] + # Build display names with i18n + ORIENTATION_I18N = { + 'portrait': tr('orientation.portrait'), + 'landscape': tr('orientation.landscape'), + 'square': tr('orientation.square') + } - # Step 2: Select template for the chosen size - template_files = list_templates_for_size(selected_size) + display_options = {} + for item in all_templates: + info = item.display_info + name = info.name + orientation = ORIENTATION_I18N.get(info.orientation, info.orientation) + + # Always show dimensions for standardization + display_name = f"{name} - {orientation}({info.width}×{info.height})" + + display_options[display_name] = item.template_path - # Default to default.html if exists, otherwise first option - default_template_index = 0 - if "default.html" in template_files: - default_template_index = template_files.index("default.html") + # Default to "default" portrait if exists + display_names = list(display_options.keys()) + default_index = 0 + for idx, name in enumerate(display_names): + if "default" in name.lower() and tr('orientation.portrait') in name: + default_index = idx + break - template_name = st.selectbox( - tr("template.style"), - template_files if template_files else ["default.html"], - index=default_template_index, - label_visibility="collapsed" + # Single dropdown with formatted names + selected_display_name = st.selectbox( + tr("template.select"), + display_names, + index=default_index, + label_visibility="collapsed", + help=tr("template.select_help") ) - # Combine size and template name to get full path - frame_template = f"{selected_size}/{template_name}" + # Get full template path + frame_template = display_options[selected_display_name] # Template preview expander with st.expander(tr("template.preview_title"), expanded=False): diff --git a/web/i18n/locales/en_US.json b/web/i18n/locales/en_US.json index 6f505f3..af4c4fd 100644 --- a/web/i18n/locales/en_US.json +++ b/web/i18n/locales/en_US.json @@ -66,11 +66,18 @@ "style.generated_prompt": "Generated prompt: {prompt}", "template.selector": "Template Selection", + "template.select": "Select Template", + "template.select_help": "Select template and video size", "template.default": "Default", "template.modern": "Modern", "template.neon": "Neon", "template.what": "Controls the visual layout and design style of each frame (title, text, image arrangement)", "template.how": "Place .html template files in the templates/ folder for automatic detection. Supports custom CSS styles", + "template.size_info": "Template Size", + + "orientation.portrait": "Portrait", + "orientation.landscape": "Landscape", + "orientation.square": "Square", "template.preview_title": "Preview Template", "template.preview_param_title": "Title", "template.preview_param_text": "Text", diff --git a/web/i18n/locales/zh_CN.json b/web/i18n/locales/zh_CN.json index 4247398..32c97f2 100644 --- a/web/i18n/locales/zh_CN.json +++ b/web/i18n/locales/zh_CN.json @@ -66,11 +66,18 @@ "style.generated_prompt": "生成的提示词:{prompt}", "template.selector": "模板选择", + "template.select": "选择模板", + "template.select_help": "选择模板和视频尺寸", "template.default": "默认", "template.modern": "现代", "template.neon": "霓虹", "template.what": "控制视频每一帧的视觉布局和设计风格(标题、文本、图片的排版样式)", "template.how": "将 .html 模板文件放入 templates/ 文件夹即可自动识别。支持自定义 CSS 样式", + "template.size_info": "模板尺寸", + + "orientation.portrait": "竖屏", + "orientation.landscape": "横屏", + "orientation.square": "方形", "template.preview_title": "预览模板", "template.preview_param_title": "标题", "template.preview_param_text": "文本",