webui适配视频功能; 统一模板命名规范;
This commit is contained in:
@@ -37,15 +37,29 @@ comfyui:
|
||||
|
||||
# Image prompt prefix (optional)
|
||||
prompt_prefix: "Minimalist black-and-white matchstick figure style illustration, clean lines, simple sketch style"
|
||||
|
||||
# Video-specific configuration
|
||||
video:
|
||||
# Required: Default workflow to use (no fallback)
|
||||
# Options: runninghub/video_wan2.1_fusionx.json (recommended, no local setup)
|
||||
# selfhost/video_wan2.1_fusionx.json (requires local ComfyUI)
|
||||
default_workflow: runninghub/video_wan2.1_fusionx.json
|
||||
|
||||
# Video prompt prefix (optional)
|
||||
prompt_prefix: "Minimalist black-and-white matchstick figure style illustration, clean lines, simple sketch style"
|
||||
|
||||
# ==================== Template Configuration ====================
|
||||
# Configure default template for video generation
|
||||
template:
|
||||
# Default frame template to use when not explicitly specified
|
||||
# Determines video aspect ratio and layout style
|
||||
# Template naming convention:
|
||||
# - static_*.html: Static style templates (no AI-generated media)
|
||||
# - image_*.html: Templates requiring AI-generated images
|
||||
# - video_*.html: Templates requiring AI-generated videos
|
||||
# Options:
|
||||
# - 1080x1920 (vertical/portrait): default.html, modern.html, elegant.html, etc.
|
||||
# - 1080x1080 (square): minimal_framed.html, magazine_cover.html, etc.
|
||||
# - 1920x1080 (horizontal/landscape): film.html, full.html, etc.
|
||||
# - 1080x1920 (vertical/portrait): image_default.html, image_modern.html, image_elegant.html, static_simple.html, etc.
|
||||
# - 1080x1080 (square): image_minimal_framed.html, etc.
|
||||
# - 1920x1080 (horizontal/landscape): image_film.html, image_full.html, etc.
|
||||
# See templates/ directory for all available templates
|
||||
default_template: "1080x1920/default.html"
|
||||
default_template: "1080x1920/image_default.html"
|
||||
|
||||
@@ -117,9 +117,10 @@ class CustomPipeline(BasePipeline):
|
||||
VideoGenerationResult
|
||||
|
||||
Image Generation Logic:
|
||||
- If template has {{image}} → automatically generates images
|
||||
- If template has no {{image}} → skips image generation (faster, cheaper)
|
||||
- To customize: Override the template_requires_image logic in your subclass
|
||||
- image_*.html templates → automatically generates images
|
||||
- video_*.html templates → automatically generates videos
|
||||
- static_*.html templates → skips media generation (faster, cheaper)
|
||||
- To customize: Override the template type detection logic in your subclass
|
||||
"""
|
||||
logger.info("Starting CustomPipeline")
|
||||
logger.info(f"Input text length: {len(text)} chars")
|
||||
@@ -151,23 +152,27 @@ class CustomPipeline(BasePipeline):
|
||||
frame_template = template_config.get("default_template", "1080x1920/default.html")
|
||||
|
||||
# ========== Step 0.5: Check template requirements ==========
|
||||
# Detect if template requires {{image}} parameter
|
||||
# This allows skipping the entire image generation pipeline for text-only templates
|
||||
# Detect template type by filename prefix
|
||||
from pathlib import Path
|
||||
from pixelle_video.services.frame_html import HTMLFrameGenerator
|
||||
from pixelle_video.utils.template_util import resolve_template_path
|
||||
from pixelle_video.utils.template_util import resolve_template_path, get_template_type
|
||||
|
||||
template_path = resolve_template_path(frame_template)
|
||||
generator = HTMLFrameGenerator(template_path)
|
||||
template_requires_image = generator.requires_image()
|
||||
template_name = Path(frame_template).name
|
||||
template_type = get_template_type(template_name)
|
||||
template_requires_image = (template_type == "image")
|
||||
|
||||
# Read media size from template meta tags
|
||||
template_path = resolve_template_path(frame_template)
|
||||
generator = HTMLFrameGenerator(template_path)
|
||||
image_width, image_height = generator.get_media_size()
|
||||
logger.info(f"📐 Media size from template: {image_width}x{image_height}")
|
||||
|
||||
if template_requires_image:
|
||||
if template_type == "image":
|
||||
logger.info(f"📸 Template requires image generation")
|
||||
else:
|
||||
logger.info(f"⚡ Template does not require images - skipping image generation pipeline")
|
||||
elif template_type == "video":
|
||||
logger.info(f"🎬 Template requires video generation")
|
||||
else: # static
|
||||
logger.info(f"⚡ Static template - skipping media generation pipeline")
|
||||
logger.info(f" 💡 Benefits: Faster generation + Lower cost + No ComfyUI dependency")
|
||||
|
||||
# ========== Step 1: Process content (CUSTOMIZE THIS) ==========
|
||||
@@ -197,8 +202,8 @@ class CustomPipeline(BasePipeline):
|
||||
# ========== Step 2: Generate image prompts (CONDITIONAL - CUSTOMIZE THIS) ==========
|
||||
self._report_progress(progress_callback, "generating_image_prompts", 0.25)
|
||||
|
||||
# IMPORTANT: Check if template actually needs images
|
||||
# If your template doesn't use {{image}}, you can skip this entire step!
|
||||
# IMPORTANT: Check if template is image type
|
||||
# If your template is static_*.html, you can skip this entire step!
|
||||
if template_requires_image:
|
||||
# Template requires images - generate image prompts using LLM
|
||||
from pixelle_video.utils.content_generators import generate_image_prompts
|
||||
|
||||
@@ -282,8 +282,8 @@ class StandardPipeline(BasePipeline):
|
||||
logger.info(f"🎬 Template requires video generation")
|
||||
elif template_media_type == "image":
|
||||
logger.info(f"📸 Template requires image generation")
|
||||
else: # text
|
||||
logger.info(f"⚡ Template does not require media - skipping media generation pipeline")
|
||||
else: # static
|
||||
logger.info(f"⚡ Static template - skipping media generation pipeline")
|
||||
logger.info(f" 💡 Benefits: Faster generation + Lower cost + No ComfyUI dependency")
|
||||
|
||||
try:
|
||||
@@ -525,35 +525,23 @@ class StandardPipeline(BasePipeline):
|
||||
- Media generation API calls
|
||||
- ComfyUI dependency
|
||||
|
||||
Template naming rules:
|
||||
Template naming convention:
|
||||
- static_*.html: Static style template (returns "static")
|
||||
- image_*.html: Image template (returns "image")
|
||||
- video_*.html: Video template (returns "video")
|
||||
- Other templates with {{image}}: Image template (returns "image")
|
||||
- Other templates without {{image}}: Text-only template (returns "text")
|
||||
|
||||
Args:
|
||||
frame_template: Template path (e.g., "1080x1920/default.html" or "1080x1920/video_default.html")
|
||||
frame_template: Template path (e.g., "1080x1920/image_default.html" or "1080x1920/video_default.html")
|
||||
|
||||
Returns:
|
||||
"video", "image", or "text"
|
||||
"static", "image", or "video"
|
||||
"""
|
||||
from pixelle_video.services.frame_html import HTMLFrameGenerator
|
||||
from pixelle_video.utils.template_util import resolve_template_path
|
||||
from pixelle_video.utils.template_util import get_template_type
|
||||
|
||||
# Check if template name starts with video_
|
||||
# Determine type by template filename prefix
|
||||
template_name = Path(frame_template).name
|
||||
if template_name.startswith("video_"):
|
||||
logger.debug(f"Template '{frame_template}' is video template (video_ prefix)")
|
||||
return "video"
|
||||
template_type = get_template_type(template_name)
|
||||
|
||||
# Check if template contains {{image}}
|
||||
template_path = resolve_template_path(frame_template)
|
||||
generator = HTMLFrameGenerator(template_path)
|
||||
|
||||
requires_image = generator.requires_image()
|
||||
if requires_image:
|
||||
logger.debug(f"Template '{frame_template}' is image template (has {{image}})")
|
||||
return "image"
|
||||
else:
|
||||
logger.debug(f"Template '{frame_template}' is text-only template")
|
||||
return "text"
|
||||
logger.debug(f"Template '{frame_template}' is {template_type} template")
|
||||
return template_type
|
||||
|
||||
|
||||
@@ -77,21 +77,6 @@ class HTMLFrameGenerator:
|
||||
self._check_linux_dependencies()
|
||||
logger.debug(f"Loaded HTML template: {template_path} (size: {self.width}x{self.height})")
|
||||
|
||||
def requires_image(self) -> bool:
|
||||
"""
|
||||
Detect if template requires {{image}} parameter
|
||||
|
||||
This method checks if the template uses the {{image}} variable.
|
||||
If the template doesn't use images, the entire image generation
|
||||
pipeline can be skipped, significantly improving:
|
||||
- Generation speed (no image generation API calls)
|
||||
- Cost efficiency (no LLM calls for image prompts)
|
||||
- Dependency requirements (no ComfyUI needed)
|
||||
|
||||
Returns:
|
||||
True if template contains {{image}}, False otherwise
|
||||
"""
|
||||
return '{{image}}' in self.template
|
||||
|
||||
def _check_linux_dependencies(self):
|
||||
"""Check Linux system dependencies and warn if missing"""
|
||||
|
||||
@@ -18,6 +18,7 @@ import os
|
||||
from pathlib import Path
|
||||
from typing import List, Tuple, Optional, Literal
|
||||
from pydantic import BaseModel, Field
|
||||
import logging
|
||||
|
||||
from pixelle_video.utils.os_util import (
|
||||
get_resource_path,
|
||||
@@ -26,6 +27,8 @@ from pixelle_video.utils.os_util import (
|
||||
resource_exists
|
||||
)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def parse_template_size(template_path: str) -> Tuple[int, int]:
|
||||
"""
|
||||
@@ -316,7 +319,7 @@ def resolve_template_path(template_input: Optional[str]) -> str:
|
||||
|
||||
Args:
|
||||
template_input: Can be:
|
||||
- None: Use default "1080x1920/default.html"
|
||||
- None: Use default "1080x1920/image_default.html"
|
||||
- "template.html": Use default size + this template
|
||||
- "1080x1920/template.html": Full relative path
|
||||
- "templates/1080x1920/template.html": Absolute-ish path (legacy)
|
||||
@@ -330,15 +333,15 @@ def resolve_template_path(template_input: Optional[str]) -> str:
|
||||
|
||||
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'
|
||||
'templates/1080x1920/image_default.html'
|
||||
>>> resolve_template_path("image_modern.html")
|
||||
'templates/1080x1920/image_modern.html'
|
||||
>>> resolve_template_path("1920x1080/image_default.html")
|
||||
'templates/1920x1080/image_default.html'
|
||||
"""
|
||||
# Default case
|
||||
if template_input is None:
|
||||
template_input = "1080x1920/default.html"
|
||||
template_input = "1080x1920/image_default.html"
|
||||
|
||||
# Parse input to extract size and template name
|
||||
size = None
|
||||
@@ -359,6 +362,18 @@ def resolve_template_path(template_input: Optional[str]) -> str:
|
||||
size = "1080x1920"
|
||||
template_name = template_input
|
||||
|
||||
# Backward compatibility: migrate "default.html" to "image_default.html"
|
||||
if template_name == "default.html":
|
||||
migrated_name = "image_default.html"
|
||||
try:
|
||||
# Try migrated name first
|
||||
path = get_resource_path("templates", size, migrated_name)
|
||||
logger.info(f"Backward compatibility: migrated '{template_input}' to '{size}/{migrated_name}'")
|
||||
return path
|
||||
except FileNotFoundError:
|
||||
# Fall through to try original name
|
||||
logger.warning(f"Migrated template '{size}/{migrated_name}' not found, trying original name")
|
||||
|
||||
# Use resource API to resolve path (custom > default)
|
||||
try:
|
||||
return get_resource_path("templates", size, template_name)
|
||||
@@ -367,6 +382,120 @@ def resolve_template_path(template_input: Optional[str]) -> str:
|
||||
raise FileNotFoundError(
|
||||
f"Template not found: {size}/{template_name}\n"
|
||||
f"Available sizes: {available_sizes}\n"
|
||||
f"Hint: Use format 'SIZExSIZE/template.html' (e.g., '1080x1920/default.html')"
|
||||
f"Hint: Use format 'SIZExSIZE/template.html' (e.g., '1080x1920/image_default.html')"
|
||||
)
|
||||
|
||||
|
||||
def get_template_type(template_name: str) -> Literal['static', 'image', 'video']:
|
||||
"""
|
||||
Detect template type from template filename
|
||||
|
||||
Template naming convention:
|
||||
- static_*.html: Static style templates (no AI-generated media)
|
||||
- image_*.html: Templates requiring AI-generated images
|
||||
- video_*.html: Templates requiring AI-generated videos
|
||||
|
||||
Args:
|
||||
template_name: Template filename like "image_default.html" or "video_simple.html"
|
||||
|
||||
Returns:
|
||||
Template type: 'static', 'image', or 'video'
|
||||
|
||||
Examples:
|
||||
>>> get_template_type("static_simple.html")
|
||||
'static'
|
||||
>>> get_template_type("image_default.html")
|
||||
'image'
|
||||
>>> get_template_type("video_simple.html")
|
||||
'video'
|
||||
"""
|
||||
name = Path(template_name).name
|
||||
|
||||
if name.startswith("static_"):
|
||||
return "static"
|
||||
elif name.startswith("video_"):
|
||||
return "video"
|
||||
elif name.startswith("image_"):
|
||||
return "image"
|
||||
else:
|
||||
# Fallback: try to detect from legacy names
|
||||
logger.warning(
|
||||
f"Template '{template_name}' doesn't follow naming convention (static_/image_/video_). "
|
||||
f"Defaulting to 'image' type."
|
||||
)
|
||||
return "image"
|
||||
|
||||
|
||||
def filter_templates_by_type(
|
||||
templates: List[TemplateInfo],
|
||||
template_type: Literal['static', 'image', 'video']
|
||||
) -> List[TemplateInfo]:
|
||||
"""
|
||||
Filter templates by type
|
||||
|
||||
Args:
|
||||
templates: List of TemplateInfo objects
|
||||
template_type: Type to filter by ('static', 'image', or 'video')
|
||||
|
||||
Returns:
|
||||
Filtered list of TemplateInfo objects
|
||||
|
||||
Examples:
|
||||
>>> all_templates = get_all_templates_with_info()
|
||||
>>> image_templates = filter_templates_by_type(all_templates, 'image')
|
||||
>>> len(image_templates) > 0
|
||||
True
|
||||
"""
|
||||
filtered = []
|
||||
for t in templates:
|
||||
template_name = t.display_info.name
|
||||
if get_template_type(template_name) == template_type:
|
||||
filtered.append(t)
|
||||
return filtered
|
||||
|
||||
|
||||
def get_templates_grouped_by_size_and_type(
|
||||
template_type: Optional[Literal['static', 'image', 'video']] = None
|
||||
) -> dict:
|
||||
"""
|
||||
Get templates grouped by size, optionally filtered by type
|
||||
|
||||
Args:
|
||||
template_type: Optional type filter ('static', 'image', or 'video')
|
||||
|
||||
Returns:
|
||||
Dict with size as key, list of TemplateInfo as value
|
||||
Ordered by orientation priority: portrait > landscape > square
|
||||
|
||||
Examples:
|
||||
>>> # Get all templates
|
||||
>>> all_grouped = get_templates_grouped_by_size_and_type()
|
||||
|
||||
>>> # Get only image templates
|
||||
>>> image_grouped = get_templates_grouped_by_size_and_type('image')
|
||||
"""
|
||||
from collections import defaultdict
|
||||
|
||||
templates = get_all_templates_with_info()
|
||||
|
||||
# Filter by type if specified
|
||||
if template_type is not None:
|
||||
templates = filter_templates_by_type(templates, template_type)
|
||||
|
||||
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
|
||||
|
||||
|
||||
93
web/app.py
93
web/app.py
@@ -684,13 +684,41 @@ def main():
|
||||
st.markdown(f"🔗 [{tr('template.preview_link')}]({template_docs_url})")
|
||||
|
||||
# Import template utilities
|
||||
from pixelle_video.utils.template_util import get_templates_grouped_by_size
|
||||
from pixelle_video.utils.template_util import get_templates_grouped_by_size_and_type, get_template_type
|
||||
|
||||
# Get templates grouped by size
|
||||
grouped_templates = get_templates_grouped_by_size()
|
||||
# Template type selector
|
||||
st.markdown(f"**{tr('template.type_selector')}**")
|
||||
|
||||
template_type_options = {
|
||||
'static': tr('template.type.static'),
|
||||
'image': tr('template.type.image'),
|
||||
'video': tr('template.type.video')
|
||||
}
|
||||
|
||||
# Radio buttons in horizontal layout
|
||||
selected_template_type = st.radio(
|
||||
tr('template.type_selector'),
|
||||
options=list(template_type_options.keys()),
|
||||
format_func=lambda x: template_type_options[x],
|
||||
index=1, # Default to 'image'
|
||||
key="template_type_selector",
|
||||
label_visibility="collapsed",
|
||||
horizontal=True
|
||||
)
|
||||
|
||||
# Display hint based on selected type (below radio buttons)
|
||||
if selected_template_type == 'static':
|
||||
st.info(tr('template.type.static_hint'))
|
||||
elif selected_template_type == 'image':
|
||||
st.info(tr('template.type.image_hint'))
|
||||
elif selected_template_type == 'video':
|
||||
st.info(tr('template.type.video_hint'))
|
||||
|
||||
# Get templates grouped by size, filtered by selected type
|
||||
grouped_templates = get_templates_grouped_by_size_and_type(selected_template_type)
|
||||
|
||||
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.warning(f"No {template_type_options[selected_template_type]} templates found. Please select a different type or add templates.")
|
||||
st.stop()
|
||||
|
||||
# Build display options with group separators
|
||||
@@ -707,7 +735,19 @@ def main():
|
||||
|
||||
# Get default template from config
|
||||
template_config = pixelle_video.config.get("template", {})
|
||||
config_default_template = template_config.get("default_template", "1080x1920/default.html")
|
||||
config_default_template = template_config.get("default_template", "1080x1920/image_default.html")
|
||||
|
||||
# Backward compatibility
|
||||
if config_default_template == "1080x1920/default.html":
|
||||
config_default_template = "1080x1920/image_default.html"
|
||||
|
||||
# Determine type-specific default template
|
||||
type_default_templates = {
|
||||
'static': '1080x1920/static_default.html',
|
||||
'image': '1080x1920/image_default.html',
|
||||
'video': '1080x1920/video_default.html'
|
||||
}
|
||||
type_specific_default = type_default_templates.get(selected_template_type, config_default_template)
|
||||
|
||||
for size, templates in grouped_templates.items():
|
||||
if not templates:
|
||||
@@ -733,10 +773,12 @@ def main():
|
||||
display_options.append(display_name)
|
||||
template_paths_ordered.append(t.template_path) # Add to ordered list
|
||||
|
||||
# Set default based on config (priority: config > first default.html in portrait)
|
||||
# Set default: priority is config > type-specific default > first in portrait
|
||||
if t.template_path == config_default_template:
|
||||
default_index = current_index
|
||||
elif default_index == 0 and "default.html" in t.display_info.name and t.display_info.orientation == 'portrait':
|
||||
elif default_index == 0 and t.template_path == type_specific_default:
|
||||
default_index = current_index
|
||||
elif default_index == 0 and t.display_info.orientation == 'portrait':
|
||||
default_index = current_index
|
||||
|
||||
current_index += 1
|
||||
@@ -789,20 +831,11 @@ def main():
|
||||
|
||||
# Detect template media type
|
||||
from pathlib import Path
|
||||
template_name = Path(frame_template).name
|
||||
from pixelle_video.utils.template_util import get_template_type
|
||||
|
||||
if template_name.startswith("video_"):
|
||||
# Video template
|
||||
template_media_type = "video"
|
||||
template_requires_media = True
|
||||
elif generator_for_params.requires_image():
|
||||
# Image template
|
||||
template_media_type = "image"
|
||||
template_requires_media = True
|
||||
else:
|
||||
# Text-only template
|
||||
template_media_type = "text"
|
||||
template_requires_media = False
|
||||
template_name = Path(frame_template).name
|
||||
template_media_type = get_template_type(template_name)
|
||||
template_requires_media = (template_media_type in ["image", "video"])
|
||||
|
||||
# Store in session state for workflow filtering
|
||||
st.session_state['template_media_type'] = template_media_type
|
||||
@@ -1009,7 +1042,9 @@ def main():
|
||||
|
||||
# If user has a saved preference in config, try to match it
|
||||
comfyui_config = config_manager.get_comfyui_config()
|
||||
saved_workflow = comfyui_config["image"]["default_workflow"]
|
||||
# Select config based on template type (image or video)
|
||||
media_config_key = "video" if template_media_type == "video" else "image"
|
||||
saved_workflow = comfyui_config.get(media_config_key, {}).get("default_workflow", "")
|
||||
if saved_workflow and saved_workflow in workflow_keys:
|
||||
default_workflow_index = workflow_keys.index(saved_workflow)
|
||||
|
||||
@@ -1040,8 +1075,8 @@ def main():
|
||||
st.info(f"📐 {size_info_text}")
|
||||
|
||||
# Prompt prefix input
|
||||
# Get current prompt_prefix from config
|
||||
current_prefix = comfyui_config["image"]["prompt_prefix"]
|
||||
# Get current prompt_prefix from config (based on media type)
|
||||
current_prefix = comfyui_config.get(media_config_key, {}).get("prompt_prefix", "")
|
||||
|
||||
# Prompt prefix input (temporary, not saved to config)
|
||||
prompt_prefix = st.text_area(
|
||||
@@ -1268,6 +1303,18 @@ def main():
|
||||
# Video preview
|
||||
if os.path.exists(result.video_path):
|
||||
st.video(result.video_path)
|
||||
|
||||
# Download button
|
||||
with open(result.video_path, "rb") as video_file:
|
||||
video_bytes = video_file.read()
|
||||
video_filename = os.path.basename(result.video_path)
|
||||
st.download_button(
|
||||
label="⬇️ 下载视频" if get_language() == "zh_CN" else "⬇️ Download Video",
|
||||
data=video_bytes,
|
||||
file_name=video_filename,
|
||||
mime="video/mp4",
|
||||
use_container_width=True
|
||||
)
|
||||
else:
|
||||
st.error(tr("status.video_not_found", path=result.video_path))
|
||||
|
||||
|
||||
@@ -86,8 +86,15 @@
|
||||
"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 templates/SIZE/ directories (e.g., templates/1080x1920/). Templates are automatically grouped by size. Custom CSS styles are supported.\n\n**Note**\n\nAt least one of the following browsers must be installed on your computer for proper operation:\n1. Google Chrome (Windows, macOS)\n2. Chromium Browser (Linux)\n3. Microsoft Edge",
|
||||
"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.\n\n**Template Naming Convention**\n\n- `static_*.html` → Static style templates (no AI-generated media)\n- `image_*.html` → Image generation templates (AI-generated images)\n- `video_*.html` → Video generation templates (AI-generated videos)\n\n**Note**\n\nAt least one of the following browsers must be installed on your computer for proper operation:\n1. Google Chrome (Windows, macOS)\n2. Chromium Browser (Linux)\n3. Microsoft Edge",
|
||||
"template.size_info": "Template Size",
|
||||
"template.type_selector": "Template Type",
|
||||
"template.type.static": "📄 Static Style",
|
||||
"template.type.image": "🖼️ Generate Images",
|
||||
"template.type.video": "🎬 Generate Videos",
|
||||
"template.type.static_hint": "Uses template's built-in styles, no AI-generated media required. You can customize background images and other parameters in the template.",
|
||||
"template.type.image_hint": "AI automatically generates illustrations matching the narration content. Image size is determined by the template.",
|
||||
"template.type.video_hint": "AI automatically generates video clips matching the narration content. Video size is determined by the template.",
|
||||
|
||||
"orientation.portrait": "Portrait",
|
||||
"orientation.landscape": "Landscape",
|
||||
|
||||
@@ -86,8 +86,15 @@
|
||||
"template.modern": "现代",
|
||||
"template.neon": "霓虹",
|
||||
"template.what": "控制视频每一帧的视觉布局和设计风格(标题、文本、图片的排版样式)",
|
||||
"template.how": "将 .html 模板文件放入 templates/尺寸/ 目录(如 templates/1080x1920/),系统会自动按尺寸分组。支持自定义 CSS 样式。\n\n**注意**\n\n您的计算机上必须安装以下至少一种浏览器才能正常运行:\n1. Google Chrome(Windows、MacOS)\n2. Chromium 浏览器(Linux)\n3. Microsoft Edge",
|
||||
"template.how": "将 .html 模板文件放入 templates/尺寸/ 目录(如 templates/1080x1920/),系统会自动按尺寸分组。支持自定义 CSS 样式。\n\n**模板命名规范**\n\n- `static_*.html` → 静态样式模板(无需AI生成媒体)\n- `image_*.html` → 生成插图模板(AI生成图片)\n- `video_*.html` → 生成视频模板(AI生成视频)\n\n**注意**\n\n您的计算机上必须安装以下至少一种浏览器才能正常运行:\n1. Google Chrome(Windows、MacOS)\n2. Chromium 浏览器(Linux)\n3. Microsoft Edge",
|
||||
"template.size_info": "模板尺寸",
|
||||
"template.type_selector": "分镜类型",
|
||||
"template.type.static": "📄 静态样式",
|
||||
"template.type.image": "🖼️ 生成插图",
|
||||
"template.type.video": "🎬 生成视频",
|
||||
"template.type.static_hint": "使用模板自带样式,无需AI生成媒体。可在模板中自定义背景图片等参数。",
|
||||
"template.type.image_hint": "AI自动根据文案内容生成与之匹配的插图,插图尺寸由模板决定。",
|
||||
"template.type.video_hint": "AI自动根据文案内容生成与之匹配的视频片段,视频尺寸由模板决定。",
|
||||
|
||||
"orientation.portrait": "竖屏",
|
||||
"orientation.landscape": "横屏",
|
||||
|
||||
Reference in New Issue
Block a user