feat: Add comprehensive timeline editor with frame editing and regeneration capabilities
This commit is contained in:
@@ -69,8 +69,25 @@ def render_single_output(pixelle_video, video_params):
|
||||
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):
|
||||
# Mode selection: sync vs async
|
||||
col_mode, col_btn = st.columns([1, 2])
|
||||
|
||||
with col_mode:
|
||||
async_mode = st.checkbox(
|
||||
"🚀 后台生成",
|
||||
value=False,
|
||||
help="勾选后任务将提交到后台,可以继续创作其他视频"
|
||||
)
|
||||
|
||||
with col_btn:
|
||||
generate_clicked = st.button(
|
||||
tr("btn.generate") + (" (后台)" if async_mode else ""),
|
||||
type="primary",
|
||||
use_container_width=True
|
||||
)
|
||||
|
||||
# Generate Button clicked
|
||||
if generate_clicked:
|
||||
# Validate system configuration
|
||||
if not config_manager.validate():
|
||||
st.error(tr("settings.not_configured"))
|
||||
@@ -81,6 +98,76 @@ def render_single_output(pixelle_video, video_params):
|
||||
st.error(tr("error.input_required"))
|
||||
st.stop()
|
||||
|
||||
# ============================================
|
||||
# Async Mode: Submit to background queue
|
||||
# ============================================
|
||||
if async_mode:
|
||||
import requests
|
||||
|
||||
try:
|
||||
# Prepare request payload
|
||||
api_payload = {
|
||||
"text": text,
|
||||
"mode": mode,
|
||||
"title": title if title else None,
|
||||
"n_scenes": n_scenes,
|
||||
"split_mode": split_mode,
|
||||
"media_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,
|
||||
"tts_inference_mode": tts_mode,
|
||||
}
|
||||
|
||||
# Add TTS parameters
|
||||
if tts_mode == "local":
|
||||
api_payload["tts_voice"] = selected_voice
|
||||
api_payload["tts_speed"] = tts_speed
|
||||
else:
|
||||
api_payload["tts_workflow"] = tts_workflow_key
|
||||
if ref_audio_path:
|
||||
api_payload["ref_audio"] = str(ref_audio_path)
|
||||
|
||||
# Add template params
|
||||
if custom_values_for_video:
|
||||
api_payload["template_params"] = custom_values_for_video
|
||||
|
||||
# Submit to async API
|
||||
response = requests.post(
|
||||
"http://localhost:8000/api/video/generate/async",
|
||||
json=api_payload,
|
||||
timeout=30
|
||||
)
|
||||
|
||||
if response.status_code == 200:
|
||||
result = response.json()
|
||||
task_id = result.get("task_id", "unknown")
|
||||
|
||||
# Show success dialog
|
||||
st.success(f"✅ 任务已提交!任务ID: `{task_id}`")
|
||||
st.info("📋 可以在「任务中心」查看进度")
|
||||
|
||||
# Clear form button
|
||||
if st.button("🆕 开始新创作", type="primary", use_container_width=True):
|
||||
# Clear session state for form fields
|
||||
for key in ["input_text", "video_title"]:
|
||||
if key in st.session_state:
|
||||
del st.session_state[key]
|
||||
st.rerun()
|
||||
else:
|
||||
st.error(f"提交失败: {response.text}")
|
||||
|
||||
except requests.exceptions.ConnectionError:
|
||||
st.error("❌ 无法连接到 API 服务器,请确保后端已启动")
|
||||
except Exception as e:
|
||||
st.error(f"❌ 提交失败: {e}")
|
||||
|
||||
st.stop()
|
||||
|
||||
# ============================================
|
||||
# Sync Mode: Original blocking generation
|
||||
# ============================================
|
||||
# Show progress
|
||||
progress_bar = st.progress(0)
|
||||
status_text = st.empty()
|
||||
@@ -139,6 +226,8 @@ def render_single_output(pixelle_video, video_params):
|
||||
"progress_callback": update_progress,
|
||||
"media_width": st.session_state.get('template_media_width'),
|
||||
"media_height": st.session_state.get('template_media_height'),
|
||||
# Character memory for visual consistency
|
||||
"character_memory": st.session_state.get('character_memory'),
|
||||
}
|
||||
|
||||
# Add TTS parameters based on mode
|
||||
@@ -190,17 +279,57 @@ def render_single_output(pixelle_video, video_params):
|
||||
if os.path.exists(result.video_path):
|
||||
st.video(result.video_path)
|
||||
|
||||
# Buttons row
|
||||
col_download, col_editor = st.columns(2)
|
||||
|
||||
# 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
|
||||
)
|
||||
with col_download:
|
||||
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
|
||||
)
|
||||
|
||||
# Open in Editor button
|
||||
with col_editor:
|
||||
# Get task_id from result path (format: output/{task_id}/...)
|
||||
task_id = None
|
||||
try:
|
||||
path_parts = Path(result.video_path).parts
|
||||
if "output" in path_parts:
|
||||
output_idx = path_parts.index("output")
|
||||
if output_idx + 1 < len(path_parts):
|
||||
task_id = path_parts[output_idx + 1]
|
||||
except:
|
||||
pass
|
||||
|
||||
if task_id:
|
||||
editor_url = f"http://localhost:3001/editor?storyboard_id={task_id}"
|
||||
st.markdown(
|
||||
f'''
|
||||
<a href="{editor_url}" target="_blank" style="text-decoration: none;">
|
||||
<button style="
|
||||
width: 100%;
|
||||
padding: 0.5rem 1rem;
|
||||
background-color: #262730;
|
||||
color: white;
|
||||
border: 1px solid #262730;
|
||||
border-radius: 0.5rem;
|
||||
cursor: pointer;
|
||||
font-size: 0.875rem;
|
||||
font-weight: 400;
|
||||
">
|
||||
✏️ {"在编辑器中打开" if get_language() == "zh_CN" else "Open in Editor"}
|
||||
</button>
|
||||
</a>
|
||||
''',
|
||||
unsafe_allow_html=True
|
||||
)
|
||||
else:
|
||||
st.error(tr("status.video_not_found", path=result.video_path))
|
||||
|
||||
|
||||
Reference in New Issue
Block a user