分镜支持视频功能
This commit is contained in:
156
web/app.py
156
web/app.py
@@ -782,10 +782,29 @@ def main():
|
||||
generator_for_params = HTMLFrameGenerator(template_path_for_params)
|
||||
custom_params_for_video = generator_for_params.parse_template_parameters()
|
||||
|
||||
# Detect if template requires image generation
|
||||
template_requires_image = generator_for_params.requires_image()
|
||||
# Store in session state for Image Section to read
|
||||
st.session_state['template_requires_image'] = template_requires_image
|
||||
# Detect template media type
|
||||
from pathlib import Path
|
||||
template_name = Path(frame_template).name
|
||||
|
||||
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
|
||||
|
||||
# Store in session state for workflow filtering
|
||||
st.session_state['template_media_type'] = template_media_type
|
||||
st.session_state['template_requires_media'] = template_requires_media
|
||||
|
||||
# Backward compatibility
|
||||
st.session_state['template_requires_image'] = (template_media_type == "image")
|
||||
|
||||
custom_values_for_video = {}
|
||||
if custom_params_for_video:
|
||||
@@ -928,25 +947,51 @@ def main():
|
||||
logger.exception(e)
|
||||
|
||||
# ====================================================================
|
||||
# Image Generation Section (conditional based on template)
|
||||
# Media Generation Section (conditional based on template)
|
||||
# ====================================================================
|
||||
# Check if current template requires image generation
|
||||
if st.session_state.get('template_requires_image', True):
|
||||
# Template requires images - show full Image Section
|
||||
# Check if current template requires media generation
|
||||
template_media_type = st.session_state.get('template_media_type', 'image')
|
||||
template_requires_media = st.session_state.get('template_requires_media', True)
|
||||
|
||||
if template_requires_media:
|
||||
# Template requires media - show Media Generation Section
|
||||
with st.container(border=True):
|
||||
st.markdown(f"**{tr('section.image')}**")
|
||||
# Dynamic section title based on template type
|
||||
if template_media_type == "video":
|
||||
section_title = tr('section.video')
|
||||
else:
|
||||
section_title = tr('section.image')
|
||||
|
||||
st.markdown(f"**{section_title}**")
|
||||
|
||||
# 1. ComfyUI Workflow selection
|
||||
with st.expander(tr("help.feature_description"), expanded=False):
|
||||
st.markdown(f"**{tr('help.what')}**")
|
||||
st.markdown(tr("style.workflow_what"))
|
||||
if template_media_type == "video":
|
||||
st.markdown(tr('style.video_workflow_what'))
|
||||
else:
|
||||
st.markdown(tr("style.workflow_what"))
|
||||
st.markdown(f"**{tr('help.how')}**")
|
||||
st.markdown(tr("style.workflow_how"))
|
||||
if template_media_type == "video":
|
||||
st.markdown(tr('style.video_workflow_how'))
|
||||
else:
|
||||
st.markdown(tr("style.workflow_how"))
|
||||
st.markdown(f"**{tr('help.note')}**")
|
||||
st.markdown(tr("style.image_size_note"))
|
||||
if template_media_type == "video":
|
||||
st.markdown(tr('style.video_size_note'))
|
||||
else:
|
||||
st.markdown(tr("style.image_size_note"))
|
||||
|
||||
# Get available workflows from pixelle_video (with source info)
|
||||
workflows = pixelle_video.image.list_workflows()
|
||||
# Get available workflows and filter by template type
|
||||
all_workflows = pixelle_video.media.list_workflows()
|
||||
|
||||
# Filter workflows based on template media type
|
||||
if template_media_type == "video":
|
||||
# Only show video_ workflows
|
||||
workflows = [wf for wf in all_workflows if "video_" in wf["key"].lower()]
|
||||
else:
|
||||
# Only show image_ workflows (exclude video_)
|
||||
workflows = [wf for wf in all_workflows if "video_" not in wf["key"].lower()]
|
||||
|
||||
# Build options for selectbox
|
||||
# Display: "image_flux.json - Runninghub"
|
||||
@@ -979,25 +1024,39 @@ def main():
|
||||
workflow_key = "runninghub/image_flux.json" # fallback
|
||||
|
||||
|
||||
# 2. Image size input
|
||||
# 2. Media size input
|
||||
col1, col2 = st.columns(2)
|
||||
with col1:
|
||||
if template_media_type == "video":
|
||||
width_label = tr('style.video_width')
|
||||
width_help = tr('style.video_width_help')
|
||||
else:
|
||||
width_label = tr('style.image_width')
|
||||
width_help = tr('style.image_width_help')
|
||||
|
||||
image_width = st.number_input(
|
||||
tr('style.image_width'),
|
||||
width_label,
|
||||
min_value=128,
|
||||
value=1024,
|
||||
step=1,
|
||||
label_visibility="visible",
|
||||
help=tr('style.image_width_help')
|
||||
help=width_help
|
||||
)
|
||||
with col2:
|
||||
if template_media_type == "video":
|
||||
height_label = tr('style.video_height')
|
||||
height_help = tr('style.video_height_help')
|
||||
else:
|
||||
height_label = tr('style.image_height')
|
||||
height_help = tr('style.image_height_help')
|
||||
|
||||
image_height = st.number_input(
|
||||
tr('style.image_height'),
|
||||
height_label,
|
||||
min_value=128,
|
||||
value=1024,
|
||||
step=1,
|
||||
label_visibility="visible",
|
||||
help=tr('style.image_height_help')
|
||||
help=height_help
|
||||
)
|
||||
|
||||
# 3. Prompt prefix input
|
||||
@@ -1014,54 +1073,71 @@ def main():
|
||||
help=tr("style.prompt_prefix_help")
|
||||
)
|
||||
|
||||
# Style preview expander (similar to template preview)
|
||||
with st.expander(tr("style.preview_title"), expanded=False):
|
||||
# Media preview expander
|
||||
preview_title = tr("style.video_preview_title") if template_media_type == "video" else tr("style.preview_title")
|
||||
with st.expander(preview_title, expanded=False):
|
||||
# Test prompt input
|
||||
if template_media_type == "video":
|
||||
test_prompt_label = tr("style.test_video_prompt")
|
||||
test_prompt_value = "a dog running in the park"
|
||||
else:
|
||||
test_prompt_label = tr("style.test_prompt")
|
||||
test_prompt_value = "a dog"
|
||||
|
||||
test_prompt = st.text_input(
|
||||
tr("style.test_prompt"),
|
||||
value="a dog",
|
||||
test_prompt_label,
|
||||
value=test_prompt_value,
|
||||
help=tr("style.test_prompt_help"),
|
||||
key="style_test_prompt"
|
||||
)
|
||||
|
||||
# Preview button
|
||||
if st.button(tr("style.preview"), key="preview_style", use_container_width=True):
|
||||
with st.spinner(tr("style.previewing")):
|
||||
preview_button_label = tr("style.video_preview") if template_media_type == "video" else tr("style.preview")
|
||||
if st.button(preview_button_label, key="preview_style", use_container_width=True):
|
||||
previewing_text = tr("style.video_previewing") if template_media_type == "video" else tr("style.previewing")
|
||||
with st.spinner(previewing_text):
|
||||
try:
|
||||
from pixelle_video.utils.prompt_helper import build_image_prompt
|
||||
|
||||
# Build final prompt with prefix
|
||||
final_prompt = build_image_prompt(test_prompt, prompt_prefix)
|
||||
|
||||
# Generate preview image (use user-specified size)
|
||||
preview_image_path = run_async(pixelle_video.image(
|
||||
# Generate preview media (use user-specified size and media type)
|
||||
media_result = run_async(pixelle_video.media(
|
||||
prompt=final_prompt,
|
||||
workflow=workflow_key,
|
||||
media_type=template_media_type,
|
||||
width=int(image_width),
|
||||
height=int(image_height)
|
||||
))
|
||||
preview_media_path = media_result.url
|
||||
|
||||
# Display preview (support both URL and local path)
|
||||
if preview_image_path:
|
||||
st.success(tr("style.preview_success"))
|
||||
if preview_media_path:
|
||||
success_text = tr("style.video_preview_success") if template_media_type == "video" else tr("style.preview_success")
|
||||
st.success(success_text)
|
||||
|
||||
# Read and encode image
|
||||
if preview_image_path.startswith('http'):
|
||||
# URL - use directly
|
||||
img_html = f'<div class="preview-image"><img src="{preview_image_path}" alt="Style Preview"/></div>'
|
||||
if template_media_type == "video":
|
||||
# Display video
|
||||
st.video(preview_media_path)
|
||||
else:
|
||||
# Local file - encode as base64
|
||||
with open(preview_image_path, 'rb') as f:
|
||||
img_data = base64.b64encode(f.read()).decode()
|
||||
img_html = f'<div class="preview-image"><img src="data:image/png;base64,{img_data}" alt="Style Preview"/></div>'
|
||||
|
||||
st.markdown(img_html, unsafe_allow_html=True)
|
||||
# Display image
|
||||
if preview_media_path.startswith('http'):
|
||||
# URL - use directly
|
||||
img_html = f'<div class="preview-image"><img src="{preview_media_path}" alt="Style Preview"/></div>'
|
||||
else:
|
||||
# Local file - encode as base64
|
||||
with open(preview_media_path, 'rb') as f:
|
||||
img_data = base64.b64encode(f.read()).decode()
|
||||
img_html = f'<div class="preview-image"><img src="data:image/png;base64,{img_data}" alt="Style Preview"/></div>'
|
||||
|
||||
st.markdown(img_html, unsafe_allow_html=True)
|
||||
|
||||
# Show the final prompt used
|
||||
st.info(f"**{tr('style.final_prompt_label')}**\n{final_prompt}")
|
||||
|
||||
# Show file path
|
||||
st.caption(f"📁 {preview_image_path}")
|
||||
st.caption(f"📁 {preview_media_path}")
|
||||
else:
|
||||
st.error(tr("style.preview_failed_general"))
|
||||
except Exception as e:
|
||||
|
||||
@@ -8,6 +8,8 @@
|
||||
"section.bgm": "🎵 Background Music",
|
||||
"section.tts": "🎤 Voiceover",
|
||||
"section.image": "🎨 Image Generation",
|
||||
"section.video": "🎬 Video Generation",
|
||||
"section.media": "🎨 Media Generation",
|
||||
"section.template": "📐 Storyboard Template",
|
||||
"section.video_generation": "🎬 Generate Video",
|
||||
|
||||
@@ -45,12 +47,19 @@
|
||||
"style.workflow": "Workflow Selection",
|
||||
"style.workflow_what": "Determines how each frame's illustration is generated and its effect (e.g., using FLUX, SD models)",
|
||||
"style.workflow_how": "Place the exported image_xxx.json workflow file(API format) into the workflows/selfhost/ folder (for local ComfyUI) or the workflows/runninghub/ folder (for cloud)",
|
||||
"style.video_workflow_what": "Determines how each frame's video clip is generated and its effect (e.g., using different video generation models)",
|
||||
"style.video_workflow_how": "Place the exported video_xxx.json workflow file(API format) into the workflows/selfhost/ folder (for local ComfyUI) or the workflows/runninghub/ folder (for cloud)",
|
||||
"style.image_size": "Image Size",
|
||||
"style.image_width": "Width",
|
||||
"style.image_height": "Height",
|
||||
"style.image_width_help": "Width of AI-generated images (Note: This is the image size, not the final video size. Video size is determined by the template)",
|
||||
"style.image_height_help": "Height of AI-generated images (Note: This is the image size, not the final video size. Video size is determined by the template)",
|
||||
"style.video_width": "Video Width",
|
||||
"style.video_height": "Video Height",
|
||||
"style.video_width_help": "Width of AI-generated video (Note: This is the video clip size, will auto-adapt to template size)",
|
||||
"style.video_height_help": "Height of AI-generated video (Note: This is the video clip size, will auto-adapt to template size)",
|
||||
"style.image_size_note": "Image size controls the dimensions of AI-generated illustrations, and does not affect the final video size. Video size is determined by the Storyboard Template below.",
|
||||
"style.video_size_note": "Video size will automatically adapt to the template size, no manual adjustment needed.",
|
||||
"style.prompt_prefix": "Prompt Prefix",
|
||||
"style.prompt_prefix_what": "Automatically added before all image prompts to control the illustration style uniformly (e.g., cartoon, realistic)",
|
||||
"style.prompt_prefix_how": "Enter style description in the input box below. To save permanently, edit the config.yaml file",
|
||||
@@ -60,11 +69,16 @@
|
||||
"style.description": "Style Description",
|
||||
"style.description_placeholder": "Describe the illustration style you want (any language)...",
|
||||
"style.preview_title": "Preview Style",
|
||||
"style.video_preview_title": "Preview Video",
|
||||
"style.test_prompt": "Test Prompt",
|
||||
"style.test_video_prompt": "Test Video Prompt",
|
||||
"style.test_prompt_help": "Enter test prompt to preview style effect",
|
||||
"style.preview": "🖼️ Generate Preview",
|
||||
"style.video_preview": "🎬 Generate Video Preview",
|
||||
"style.previewing": "Generating style preview...",
|
||||
"style.video_previewing": "Generating video preview...",
|
||||
"style.preview_success": "✅ Preview generated successfully!",
|
||||
"style.video_preview_success": "✅ Video preview generated successfully!",
|
||||
"style.preview_caption": "Style Preview",
|
||||
"style.preview_failed": "Preview failed: {error}",
|
||||
"style.preview_failed_general": "Failed to generate preview image",
|
||||
@@ -140,12 +154,16 @@
|
||||
"progress.generating_narrations": "Generating narrations...",
|
||||
"progress.splitting_script": "Splitting script...",
|
||||
"progress.generating_image_prompts": "Generating image prompts...",
|
||||
"progress.generating_video_prompts": "Generating video prompts...",
|
||||
"progress.preparing_frames": "Preparing frames...",
|
||||
"progress.frame": "Frame {current}/{total}",
|
||||
"progress.frame_step": "Frame {current}/{total} - Step {step}/4: {action}",
|
||||
"progress.step_audio": "Generating audio...",
|
||||
"progress.step_image": "Generating image...",
|
||||
"progress.step_compose": "Composing frame...",
|
||||
"progress.step_video": "Creating video segment...",
|
||||
"progress.processing_frame": "Processing frame {current}/{total}...",
|
||||
"progress.step_audio": "Generating audio",
|
||||
"progress.step_image": "Generating image",
|
||||
"progress.step_media": "Generating media",
|
||||
"progress.step_compose": "Composing frame",
|
||||
"progress.step_video": "Creating video segment",
|
||||
"progress.concatenating": "Concatenating video...",
|
||||
"progress.finalizing": "Finalizing...",
|
||||
"progress.completed": "✅ Completed",
|
||||
|
||||
@@ -8,6 +8,8 @@
|
||||
"section.bgm": "🎵 背景音乐",
|
||||
"section.tts": "🎤 配音合成",
|
||||
"section.image": "🎨 插图生成",
|
||||
"section.video": "🎬 视频生成",
|
||||
"section.media": "🎨 媒体生成",
|
||||
"section.template": "📐 分镜模板",
|
||||
"section.video_generation": "🎬 生成视频",
|
||||
|
||||
@@ -45,12 +47,19 @@
|
||||
"style.workflow": "工作流选择",
|
||||
"style.workflow_what": "决定视频中每帧插图的生成方式和效果(如使用 FLUX、SD 等模型)",
|
||||
"style.workflow_how": "将导出的 image_xxx.json 工作流文件(API格式)放入 workflows/selfhost/(本地 ComfyUI)或 workflows/runninghub/(云端)文件夹",
|
||||
"style.video_workflow_what": "决定视频中每帧视频片段的生成方式和效果(如使用不同的视频生成模型)",
|
||||
"style.video_workflow_how": "将导出的 video_xxx.json 工作流文件(API格式)放入 workflows/selfhost/(本地 ComfyUI)或 workflows/runninghub/(云端)文件夹",
|
||||
"style.image_size": "图片尺寸",
|
||||
"style.image_width": "宽度",
|
||||
"style.image_height": "高度",
|
||||
"style.image_width_help": "AI 生成插图的宽度(注意:这是插图尺寸,不是最终视频尺寸。视频尺寸由模板决定)",
|
||||
"style.image_height_help": "AI 生成插图的高度(注意:这是插图尺寸,不是最终视频尺寸。视频尺寸由模板决定)",
|
||||
"style.video_width": "视频宽度",
|
||||
"style.video_height": "视频高度",
|
||||
"style.video_width_help": "AI 生成视频的宽度(注意:这是视频片段尺寸,会自适应模板尺寸)",
|
||||
"style.video_height_help": "AI 生成视频的高度(注意:这是视频片段尺寸,会自适应模板尺寸)",
|
||||
"style.image_size_note": "图片尺寸控制 AI 生成的插图大小,不影响最终视频尺寸。视频尺寸由下方的「📐 分镜模板」决定。",
|
||||
"style.video_size_note": "视频尺寸会自动适配模板尺寸,无需手动调整。",
|
||||
"style.prompt_prefix": "提示词前缀",
|
||||
"style.prompt_prefix_what": "自动添加到所有图片提示词前面,统一控制插图风格(如:卡通风格、写实风格等)",
|
||||
"style.prompt_prefix_how": "直接在下方输入框填写风格描述。若要永久保存,需编辑 config.yaml 文件",
|
||||
@@ -60,11 +69,16 @@
|
||||
"style.description": "风格描述",
|
||||
"style.description_placeholder": "描述您想要的插图风格(任何语言)...",
|
||||
"style.preview_title": "预览风格",
|
||||
"style.video_preview_title": "预览视频",
|
||||
"style.test_prompt": "测试提示词",
|
||||
"style.test_video_prompt": "测试视频提示词",
|
||||
"style.test_prompt_help": "输入测试提示词来预览风格效果",
|
||||
"style.preview": "🖼️ 生成预览",
|
||||
"style.video_preview": "🎬 生成视频预览",
|
||||
"style.previewing": "正在生成风格预览...",
|
||||
"style.video_previewing": "正在生成视频预览...",
|
||||
"style.preview_success": "✅ 预览生成成功!",
|
||||
"style.video_preview_success": "✅ 视频预览生成成功!",
|
||||
"style.preview_caption": "风格预览",
|
||||
"style.preview_failed": "预览失败:{error}",
|
||||
"style.preview_failed_general": "预览图片生成失败",
|
||||
@@ -140,12 +154,16 @@
|
||||
"progress.generating_narrations": "生成旁白...",
|
||||
"progress.splitting_script": "切分脚本...",
|
||||
"progress.generating_image_prompts": "生成图片提示词...",
|
||||
"progress.generating_video_prompts": "生成视频提示词...",
|
||||
"progress.preparing_frames": "准备分镜...",
|
||||
"progress.frame": "分镜 {current}/{total}",
|
||||
"progress.frame_step": "分镜 {current}/{total} - 步骤 {step}/4: {action}",
|
||||
"progress.step_audio": "生成语音...",
|
||||
"progress.step_image": "生成插图...",
|
||||
"progress.step_compose": "合成画面...",
|
||||
"progress.step_video": "创建视频片段...",
|
||||
"progress.processing_frame": "处理分镜 {current}/{total}...",
|
||||
"progress.step_audio": "生成语音",
|
||||
"progress.step_image": "生成插图",
|
||||
"progress.step_media": "生成媒体",
|
||||
"progress.step_compose": "合成画面",
|
||||
"progress.step_video": "创建视频片段",
|
||||
"progress.concatenating": "正在拼接视频...",
|
||||
"progress.finalizing": "完成中...",
|
||||
"progress.completed": "✅ 生成完成",
|
||||
|
||||
Reference in New Issue
Block a user