From 77552c9e80efff8e0957e2bfdacfdfcbff6ed558 Mon Sep 17 00:00:00 2001 From: puke <1129090915@qq.com> Date: Mon, 3 Nov 2025 11:45:20 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E6=A8=A1=E6=9D=BF=E5=88=86?= =?UTF-8?q?=E7=BB=84=E6=98=BE=E7=A4=BA=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pixelle_video/utils/template_util.py | 36 ++++++++++++++ web/app.py | 70 ++++++++++++++++++---------- web/i18n/locales/en_US.json | 3 +- web/i18n/locales/zh_CN.json | 3 +- 4 files changed, 85 insertions(+), 27 deletions(-) diff --git a/pixelle_video/utils/template_util.py b/pixelle_video/utils/template_util.py index fdbf22c..accea07 100644 --- a/pixelle_video/utils/template_util.py +++ b/pixelle_video/utils/template_util.py @@ -262,6 +262,42 @@ def get_all_templates_with_info() -> List[TemplateInfo]: 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 diff --git a/web/app.py b/web/app.py index a0ca382..75132b4 100644 --- a/web/app.py +++ b/web/app.py @@ -667,52 +667,72 @@ def main(): st.markdown(tr("template.how")) # Import template utilities - from pixelle_video.utils.template_util import get_all_templates_with_info + from pixelle_video.utils.template_util import get_templates_grouped_by_size - # Get all templates with their info - all_templates = get_all_templates_with_info() + # Get templates grouped by size + grouped_templates = get_templates_grouped_by_size() - if not all_templates: + if not grouped_templates: st.error("No templates found. Please ensure templates are in templates/ directory with proper structure (e.g., templates/1080x1920/default.html).") st.stop() - # Build display names with i18n + # Build display options with group separators ORIENTATION_I18N = { 'portrait': tr('orientation.portrait'), 'landscape': tr('orientation.landscape'), 'square': tr('orientation.square') } - 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" portrait if exists - display_names = list(display_options.keys()) + display_options = [] + template_path_map = {} 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 + current_index = 0 - # Single dropdown with formatted names + for size, templates in grouped_templates.items(): + if not templates: + continue + + # Get orientation from first template in group + orientation = ORIENTATION_I18N.get( + templates[0].display_info.orientation, + templates[0].display_info.orientation + ) + width = templates[0].display_info.width + height = templates[0].display_info.height + + # Add group separator + separator = f"─── {orientation} {width}×{height} ───" + display_options.append(separator) + current_index += 1 + + # Add templates in this group + for t in templates: + display_name = f" {t.display_info.name}" + display_options.append(display_name) + template_path_map[display_name] = t.template_path + + # Set default to first "default.html" in portrait orientation + if default_index == 0 and "default.html" in t.display_info.name and t.display_info.orientation == 'portrait': + default_index = current_index + + current_index += 1 + + # Dropdown with grouped display selected_display_name = st.selectbox( tr("template.select"), - display_names, + display_options, index=default_index, label_visibility="collapsed", help=tr("template.select_help") ) + # Check if separator is selected (shouldn't happen, but handle it) + if selected_display_name.startswith("───"): + st.warning(tr("template.separator_selected")) + st.stop() + # Get full template path - frame_template = display_options[selected_display_name] + frame_template = template_path_map.get(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 af4c4fd..c97a50c 100644 --- a/web/i18n/locales/en_US.json +++ b/web/i18n/locales/en_US.json @@ -68,11 +68,12 @@ "template.selector": "Template Selection", "template.select": "Select Template", "template.select_help": "Select template and video size", + "template.separator_selected": "Please select a specific template, not the group header", "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.how": "Place .html template files in templates/SIZE/ directories (e.g., templates/1080x1920/). Templates are automatically grouped by size. Custom CSS styles are supported", "template.size_info": "Template Size", "orientation.portrait": "Portrait", diff --git a/web/i18n/locales/zh_CN.json b/web/i18n/locales/zh_CN.json index 32c97f2..cf81087 100644 --- a/web/i18n/locales/zh_CN.json +++ b/web/i18n/locales/zh_CN.json @@ -68,11 +68,12 @@ "template.selector": "模板选择", "template.select": "选择模板", "template.select_help": "选择模板和视频尺寸", + "template.separator_selected": "请选择具体的模板,而不是分组标题", "template.default": "默认", "template.modern": "现代", "template.neon": "霓虹", "template.what": "控制视频每一帧的视觉布局和设计风格(标题、文本、图片的排版样式)", - "template.how": "将 .html 模板文件放入 templates/ 文件夹即可自动识别。支持自定义 CSS 样式", + "template.how": "将 .html 模板文件放入 templates/尺寸/ 目录(如 templates/1080x1920/),系统会自动按尺寸分组。支持自定义 CSS 样式", "template.size_info": "模板尺寸", "orientation.portrait": "竖屏",