import os import glob import json import time import streamlit as st from app.config import config from app.models.schema import VideoClipParams from app.utils import utils, check_script from webui.tools.generate_script_docu import generate_script_docu def render_script_panel(tr): """渲染脚本配置面板""" with st.container(border=True): st.write(tr("Video Script Configuration")) params = VideoClipParams() # 渲染脚本文件选择 render_script_file(tr, params) # 渲染视频文件选择 render_video_file(tr, params) # 渲染视频主题和提示词 render_video_details(tr) # 渲染脚本操作按钮 render_script_buttons(tr, params) def render_script_file(tr, params): """渲染脚本文件选择""" script_list = [ (tr("None"), ""), (tr("Auto Generate"), "auto"), (tr("Upload Script"), "upload_script") # 新增上传脚本选项 ] # 获取已有脚本文件 suffix = "*.json" script_dir = utils.script_dir() files = glob.glob(os.path.join(script_dir, suffix)) file_list = [] for file in files: file_list.append({ "name": os.path.basename(file), "file": file, "ctime": os.path.getctime(file) }) file_list.sort(key=lambda x: x["ctime"], reverse=True) for file in file_list: display_name = file['file'].replace(config.root_dir, "") script_list.append((display_name, file['file'])) # 找到保存的脚本文件在列表中的索引 saved_script_path = st.session_state.get('video_clip_json_path', '') selected_index = 0 for i, (_, path) in enumerate(script_list): if path == saved_script_path: selected_index = i break selected_script_index = st.selectbox( tr("Script Files"), index=selected_index, options=range(len(script_list)), format_func=lambda x: script_list[x][0] ) script_path = script_list[selected_script_index][1] st.session_state['video_clip_json_path'] = script_path params.video_clip_json_path = script_path # 处理脚本上传 if script_path == "upload_script": uploaded_file = st.file_uploader( tr("Upload Script File"), type=["json"], accept_multiple_files=False, ) if uploaded_file is not None: try: # 读取上传的JSON内容并验证格式 script_content = uploaded_file.read().decode('utf-8') json_data = json.loads(script_content) # 保存到脚本目录 script_file_path = os.path.join(script_dir, uploaded_file.name) file_name, file_extension = os.path.splitext(uploaded_file.name) # 如果文件已存在,添加时间戳 if os.path.exists(script_file_path): timestamp = time.strftime("%Y%m%d%H%M%S") file_name_with_timestamp = f"{file_name}_{timestamp}" script_file_path = os.path.join(script_dir, file_name_with_timestamp + file_extension) # 写入文件 with open(script_file_path, "w", encoding='utf-8') as f: json.dump(json_data, f, ensure_ascii=False, indent=2) # 更新状态 st.success(tr("Script Uploaded Successfully")) st.session_state['video_clip_json_path'] = script_file_path params.video_clip_json_path = script_file_path time.sleep(1) st.rerun() except json.JSONDecodeError: st.error(tr("Invalid JSON format")) except Exception as e: st.error(f"{tr('Upload failed')}: {str(e)}") def render_video_file(tr, params): """渲染视频文件选择""" video_list = [(tr("None"), ""), (tr("Upload Local Files"), "upload_local")] # 获取已有视频文件 for suffix in ["*.mp4", "*.mov", "*.avi", "*.mkv"]: video_files = glob.glob(os.path.join(utils.video_dir(), suffix)) for file in video_files: display_name = file.replace(config.root_dir, "") video_list.append((display_name, file)) selected_video_index = st.selectbox( tr("Video File"), index=0, options=range(len(video_list)), format_func=lambda x: video_list[x][0] ) video_path = video_list[selected_video_index][1] st.session_state['video_origin_path'] = video_path params.video_origin_path = video_path if video_path == "upload_local": uploaded_file = st.file_uploader( tr("Upload Local Files"), type=["mp4", "mov", "avi", "flv", "mkv"], accept_multiple_files=False, ) if uploaded_file is not None: video_file_path = os.path.join(utils.video_dir(), uploaded_file.name) file_name, file_extension = os.path.splitext(uploaded_file.name) if os.path.exists(video_file_path): timestamp = time.strftime("%Y%m%d%H%M%S") file_name_with_timestamp = f"{file_name}_{timestamp}" video_file_path = os.path.join(utils.video_dir(), file_name_with_timestamp + file_extension) with open(video_file_path, "wb") as f: f.write(uploaded_file.read()) st.success(tr("File Uploaded Successfully")) st.session_state['video_origin_path'] = video_file_path params.video_origin_path = video_file_path time.sleep(1) st.rerun() def render_video_details(tr): """渲染视频主题和提示词""" video_theme = st.text_input(tr("Video Theme")) custom_prompt = st.text_area( tr("Generation Prompt"), value=st.session_state.get('video_plot', ''), help=tr("Custom prompt for LLM, leave empty to use default prompt"), height=180 ) st.session_state['video_theme'] = video_theme st.session_state['custom_prompt'] = custom_prompt return video_theme, custom_prompt def render_script_buttons(tr, params): """渲染脚本操作按钮""" # 新增三个输入框,放在同一行 input_cols = st.columns(3) with input_cols[0]: skip_seconds = st.number_input( "skip_seconds", min_value=0, value=st.session_state.get('skip_seconds', config.frames.get('skip_seconds', 0)), help=tr("Skip the first few seconds"), key="skip_seconds_input" ) st.session_state['skip_seconds'] = skip_seconds with input_cols[1]: threshold = st.number_input( "threshold", min_value=0, value=st.session_state.get('threshold', config.frames.get('threshold', 30)), help=tr("Difference threshold"), key="threshold_input" ) st.session_state['threshold'] = threshold with input_cols[2]: vision_batch_size = st.number_input( "vision_batch_size", min_value=1, max_value=20, value=st.session_state.get('vision_batch_size', config.frames.get('vision_batch_size', 5)), help=tr("Vision processing batch size"), key="vision_batch_size_input" ) st.session_state['vision_batch_size'] = vision_batch_size # 生成/加载按钮 script_path = st.session_state.get('video_clip_json_path', '') if script_path == "auto": button_name = tr("Generate Video Script") elif script_path: button_name = tr("Load Video Script") else: button_name = tr("Please Select Script File") if st.button(button_name, key="script_action", disabled=not script_path): if script_path == "auto": generate_script_docu(tr, params) else: load_script(tr, script_path) # 视频脚本编辑区 video_clip_json_details = st.text_area( tr("Video Script"), value=json.dumps(st.session_state.get('video_clip_json', []), indent=2, ensure_ascii=False), height=180 ) # 操作按钮行 button_cols = st.columns(3) with button_cols[0]: if st.button(tr("Check Format"), key="check_format", use_container_width=True): check_script_format(tr, video_clip_json_details) with button_cols[1]: if st.button(tr("Save Script"), key="save_script", use_container_width=True): save_script(tr, video_clip_json_details) with button_cols[2]: script_valid = st.session_state.get('script_format_valid', False) if st.button(tr("Crop Video"), key="crop_video", disabled=not script_valid, use_container_width=True): crop_video(tr, params) def check_script_format(tr, script_content): """检查脚本格式""" try: result = check_script.check_format(script_content) if result.get('success'): st.success(tr("Script format check passed")) st.session_state['script_format_valid'] = True else: st.error(f"{tr('Script format check failed')}: {result.get('message')}") st.session_state['script_format_valid'] = False except Exception as e: st.error(f"{tr('Script format check error')}: {str(e)}") st.session_state['script_format_valid'] = False def load_script(tr, script_path): """加载脚本文件""" try: with open(script_path, 'r', encoding='utf-8') as f: script = f.read() script = utils.clean_model_output(script) st.session_state['video_clip_json'] = json.loads(script) st.success(tr("Script loaded successfully")) st.rerun() except Exception as e: st.error(f"{tr('Failed to load script')}: {str(e)}") def save_script(tr, video_clip_json_details): """保存视频脚本""" if not video_clip_json_details: st.error(tr("请输入视频脚本")) st.stop() with st.spinner(tr("Save Script")): script_dir = utils.script_dir() timestamp = time.strftime("%Y-%m%d-%H%M%S") save_path = os.path.join(script_dir, f"{timestamp}.json") try: data = json.loads(video_clip_json_details) with open(save_path, 'w', encoding='utf-8') as file: json.dump(data, file, ensure_ascii=False, indent=4) st.session_state['video_clip_json'] = data st.session_state['video_clip_json_path'] = save_path # 更新配置 config.app["video_clip_json_path"] = save_path # 显示成功消息 st.success(tr("Script saved successfully")) # 强制重新加载页面更新选择框 time.sleep(0.5) # 给一点时间让用户看到成功消息 st.rerun() except Exception as err: st.error(f"{tr('Failed to save script')}: {str(err)}") st.stop() def crop_video(tr, params): """裁剪视频""" progress_bar = st.progress(0) status_text = st.empty() def update_progress(progress): progress_bar.progress(progress) status_text.text(f"剪辑进度: {progress}%") try: utils.cut_video(params, update_progress) time.sleep(0.5) progress_bar.progress(100) status_text.text("剪完成!") st.success("视频剪辑成功完成!") except Exception as e: st.error(f"剪辑过程中发生错误: {str(e)}") finally: time.sleep(2) progress_bar.empty() status_text.empty()