197 lines
8.3 KiB
Python
197 lines
8.3 KiB
Python
# Copyright (C) 2025 AIDC-AI
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
# you may not use this file except in compliance with the License.
|
|
# You may obtain a copy of the License at
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
# See the License for the specific language governing permissions and
|
|
# limitations under the License.
|
|
|
|
"""
|
|
Output preview components for web UI (right column)
|
|
"""
|
|
|
|
import base64
|
|
import os
|
|
from pathlib import Path
|
|
|
|
import streamlit as st
|
|
from loguru import logger
|
|
|
|
from web.i18n import tr, get_language
|
|
from web.utils.async_helpers import run_async
|
|
from pixelle_video.models.progress import ProgressEvent
|
|
from pixelle_video.config import config_manager
|
|
|
|
|
|
def render_output_preview(pixelle_video, video_params):
|
|
"""Render output preview section (right column)"""
|
|
# Extract parameters from video_params dict
|
|
text = video_params.get("text", "")
|
|
mode = video_params.get("mode", "generate")
|
|
title = video_params.get("title")
|
|
n_scenes = video_params.get("n_scenes", 5)
|
|
bgm_path = video_params.get("bgm_path")
|
|
bgm_volume = video_params.get("bgm_volume", 0.2)
|
|
|
|
tts_mode = video_params.get("tts_inference_mode", "local")
|
|
selected_voice = video_params.get("tts_voice")
|
|
tts_speed = video_params.get("tts_speed")
|
|
tts_workflow_key = video_params.get("tts_workflow")
|
|
ref_audio_path = video_params.get("ref_audio")
|
|
|
|
frame_template = video_params.get("frame_template")
|
|
custom_values_for_video = video_params.get("template_params", {})
|
|
workflow_key = video_params.get("image_workflow")
|
|
prompt_prefix = video_params.get("prompt_prefix", "")
|
|
|
|
with st.container(border=True):
|
|
st.markdown(f"**{tr('section.video_generation')}**")
|
|
|
|
# Check if system is configured
|
|
if not config_manager.validate():
|
|
st.warning(tr("settings.not_configured"))
|
|
|
|
# Generate Button
|
|
if st.button(tr("btn.generate"), type="primary", use_container_width=True):
|
|
# Validate system configuration
|
|
if not config_manager.validate():
|
|
st.error(tr("settings.not_configured"))
|
|
st.stop()
|
|
|
|
# Validate input
|
|
if not text:
|
|
st.error(tr("error.input_required"))
|
|
st.stop()
|
|
|
|
# Show progress
|
|
progress_bar = st.progress(0)
|
|
status_text = st.empty()
|
|
|
|
# Record start time for generation
|
|
import time
|
|
start_time = time.time()
|
|
|
|
try:
|
|
# Progress callback to update UI
|
|
def update_progress(event: ProgressEvent):
|
|
"""Update progress bar and status text from ProgressEvent"""
|
|
# Translate event to user-facing message
|
|
if event.event_type == "frame_step":
|
|
# Frame step: "分镜 3/5 - 步骤 2/4: 生成插图"
|
|
action_key = f"progress.step_{event.action}"
|
|
action_text = tr(action_key)
|
|
message = tr(
|
|
"progress.frame_step",
|
|
current=event.frame_current,
|
|
total=event.frame_total,
|
|
step=event.step,
|
|
action=action_text
|
|
)
|
|
elif event.event_type == "processing_frame":
|
|
# Processing frame: "分镜 3/5"
|
|
message = tr(
|
|
"progress.frame",
|
|
current=event.frame_current,
|
|
total=event.frame_total
|
|
)
|
|
else:
|
|
# Simple events: use i18n key directly
|
|
message = tr(f"progress.{event.event_type}")
|
|
|
|
# Append extra_info if available (e.g., batch progress)
|
|
if event.extra_info:
|
|
message = f"{message} - {event.extra_info}"
|
|
|
|
status_text.text(message)
|
|
progress_bar.progress(min(int(event.progress * 100), 99)) # Cap at 99% until complete
|
|
|
|
# Generate video (directly pass parameters)
|
|
# Note: image_width and image_height are now auto-determined from template
|
|
video_params = {
|
|
"text": text,
|
|
"mode": mode,
|
|
"title": title if title else None,
|
|
"n_scenes": n_scenes,
|
|
"image_workflow": workflow_key,
|
|
"frame_template": frame_template,
|
|
"prompt_prefix": prompt_prefix,
|
|
"bgm_path": bgm_path,
|
|
"bgm_volume": bgm_volume if bgm_path else 0.2,
|
|
"progress_callback": update_progress,
|
|
}
|
|
|
|
# Add TTS parameters based on mode
|
|
video_params["tts_inference_mode"] = tts_mode
|
|
if tts_mode == "local":
|
|
video_params["tts_voice"] = selected_voice
|
|
video_params["tts_speed"] = tts_speed
|
|
else: # comfyui
|
|
video_params["tts_workflow"] = tts_workflow_key
|
|
if ref_audio_path:
|
|
video_params["ref_audio"] = str(ref_audio_path)
|
|
|
|
# Add custom template parameters if any
|
|
if custom_values_for_video:
|
|
video_params["template_params"] = custom_values_for_video
|
|
|
|
result = run_async(pixelle_video.generate_video(**video_params))
|
|
|
|
# Calculate total generation time
|
|
total_generation_time = time.time() - start_time
|
|
|
|
progress_bar.progress(100)
|
|
status_text.text(tr("status.success"))
|
|
|
|
# Display success message
|
|
st.success(tr("status.video_generated", path=result.video_path))
|
|
|
|
st.markdown("---")
|
|
|
|
# Video information (compact display)
|
|
file_size_mb = result.file_size / (1024 * 1024)
|
|
|
|
# Parse video size from template path
|
|
from pixelle_video.utils.template_util import parse_template_size, resolve_template_path
|
|
template_path = resolve_template_path(result.storyboard.config.frame_template)
|
|
video_width, video_height = parse_template_size(template_path)
|
|
|
|
info_text = (
|
|
f"⏱️ {tr('info.generation_time')} {total_generation_time:.1f}s "
|
|
f"📦 {file_size_mb:.2f}MB "
|
|
f"🎬 {len(result.storyboard.frames)}{tr('info.scenes_unit')} "
|
|
f"📐 {video_width}x{video_height}"
|
|
)
|
|
st.caption(info_text)
|
|
|
|
st.markdown("---")
|
|
|
|
# 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))
|
|
|
|
except Exception as e:
|
|
status_text.text("")
|
|
progress_bar.empty()
|
|
st.error(tr("status.error", error=str(e)))
|
|
logger.exception(e)
|
|
st.stop()
|
|
|