diff --git a/webui.py b/webui.py index 3b9c12b..7c70ad7 100644 --- a/webui.py +++ b/webui.py @@ -143,10 +143,10 @@ def render_generate_button(): # 移除task_id检查 - 现在使用统一裁剪策略,不再需要预裁剪 # 直接检查必要的文件是否存在 if not st.session_state.get('video_clip_json_path'): - st.error(tr("脚本文件不能为空")) + st.error(tr("Script file cannot be empty")) return if not st.session_state.get('video_origin_path'): - st.error(tr("视频文件不能为空")) + st.error(tr("Video file cannot be empty")) return # 获取所有参数 @@ -199,7 +199,7 @@ def render_generate_button(): status_text.text(f"Processing... {progress}%") if state == const.TASK_STATE_COMPLETE: - status_text.text(tr("视频生成完成")) + status_text.text(tr("Video Generation Completed")) progress_bar.progress(1.0) # 显示结果 @@ -212,11 +212,11 @@ def render_generate_button(): except Exception as e: logger.error(f"播放视频失败: {e}") - st.success(tr("视频生成完成")) + st.success(tr("Video Generation Completed")) break elif state == const.TASK_STATE_FAILED: - st.error(f"任务失败: {task.get('message', 'Unknown error')}") + st.error(f"{tr('Task failed')}: {task.get('message', 'Unknown error')}") break time.sleep(0.5) @@ -291,23 +291,23 @@ def render_export_jianying_button(): if 'jianying_export_error' not in st.session_state: st.session_state['jianying_export_error'] = None - if st.button("📤 导出到剪映草稿", use_container_width=True, type="secondary"): + if st.button(tr("Export to Jianying Draft"), use_container_width=True, type="secondary"): config.save_config() if not st.session_state.get('video_clip_json_path'): - st.error("脚本文件不能为空") + st.error(tr("Script file cannot be empty")) return if not st.session_state.get('video_origin_path'): - st.error("视频文件不能为空") + st.error(tr("Video file cannot be empty")) return jianying_draft_path = config.ui.get("jianying_draft_path", "") if not jianying_draft_path: - st.error("请在基础设置中配置剪映草稿地址") + st.error(tr("Please configure Jianying draft folder in basic settings")) return if not os.path.exists(jianying_draft_path): - st.error(f"剪映草稿文件夹不存在: {jianying_draft_path}") + st.error(tr("Jianying draft folder does not exist").format(path=jianying_draft_path)) return # 显示导出表单 @@ -318,17 +318,17 @@ def render_export_jianying_button(): # 显示导出表单 if st.session_state['show_jianying_export_form']: st.markdown("---") - st.subheader("导出到剪映草稿") + st.subheader(tr("Export to Jianying Draft")) draft_name = st.text_input( - "请输入剪映草稿名称", + tr("Please enter Jianying draft name"), value=f"NarratoAI_{int(time.time())}", key="draft_name_input" ) - if st.button("确认导出", key="confirm_export"): + if st.button(tr("Confirm Export"), key="confirm_export"): if not draft_name: - st.error("请输入草稿名称") + st.error(tr("Please enter draft name")) return # 创建任务ID @@ -340,10 +340,10 @@ def render_export_jianying_button(): params = get_jianying_export_params() except Exception as e: logger.error(f"构建参数失败: {e}") - st.error(f"参数构建失败: {e}") + st.error(f"{tr('Failed to build parameters')}: {e}") return - with st.spinner("正在导出到剪映草稿,请稍候..."): + with st.spinner(tr("Exporting to Jianying draft...")): try: from app.services import jianying_task @@ -359,17 +359,17 @@ def render_export_jianying_button(): st.session_state['jianying_export_error'] = None st.session_state['show_jianying_export_form'] = False - st.success(f"✅ 成功导出到剪映草稿: {result['draft_name']}") - st.info(f"📁 草稿已保存到: {result['draft_path']}") + st.success(tr("Jianying draft exported successfully").format(name=result['draft_name'])) + st.info(tr("Draft saved to").format(path=result['draft_path'])) except Exception as e: logger.error(f"导出到剪映草稿失败: {e}") import traceback logger.error(f"错误详情: {traceback.format_exc()}") st.session_state['jianying_export_error'] = str(e) st.session_state['jianying_export_result'] = None - st.error(f"❌ 导出到剪映草稿失败: {e}") + st.error(f"{tr('Failed to export Jianying draft')}: {e}") - if st.button("取消", key="cancel_export"): + if st.button(tr("Cancel"), key="cancel_export"): st.session_state['show_jianying_export_form'] = False st.session_state['jianying_export_result'] = None st.session_state['jianying_export_error'] = None @@ -394,7 +394,7 @@ def main(): logger.error(f"❌ LLM 提供商注册失败: {str(e)}") import traceback logger.error(traceback.format_exc()) - st.error(f"⚠️ LLM 初始化失败: {str(e)}\n\n请检查配置文件和依赖是否正确安装。") + st.error(tr("LLM initialization failed").format(error=str(e))) # 不抛出异常,允许应用继续运行(但 LLM 功能不可用) # 检测FFmpeg硬件加速,但只打印一次日志(使用 session_state 持久化) diff --git a/webui/components/audio_settings.py b/webui/components/audio_settings.py index ce4aa8e..862457d 100644 --- a/webui/components/audio_settings.py +++ b/webui/components/audio_settings.py @@ -19,55 +19,55 @@ def get_soulvoice_voices(): return ["soulvoice:custom"] -def get_tts_engine_options(): +def get_tts_engine_options(tr=lambda key: key): """获取TTS引擎选项""" return { "edge_tts": "Edge TTS", "azure_speech": "Azure Speech Services", - "tencent_tts": "腾讯云 TTS", - "qwen3_tts": "通义千问 Qwen3 TTS", - "indextts2": "IndexTTS2 语音克隆", - "doubaotts": "豆包语音 TTS" + "tencent_tts": tr("Tencent Cloud TTS"), + "qwen3_tts": tr("Tongyi Qwen3 TTS"), + "indextts2": tr("IndexTTS2 Voice Clone"), + "doubaotts": tr("Doubao TTS") } -def get_tts_engine_descriptions(): +def get_tts_engine_descriptions(tr=lambda key: key): """获取TTS引擎详细描述""" return { "edge_tts": { "title": "Edge TTS", - "features": "完全免费,但服务稳定性一般,不支持语音克隆功能", - "use_case": "测试和轻量级使用", + "features": tr("Edge TTS features"), + "use_case": tr("Edge TTS use case"), "registration": None }, "azure_speech": { "title": "Azure Speech Services", - "features": "提供一定免费额度,超出后按量付费,需要绑定海外信用卡", - "use_case": "企业级应用,需要稳定服务", + "features": tr("Azure Speech Services features"), + "use_case": tr("Azure Speech Services use case"), "registration": "https://portal.azure.com/#view/Microsoft_Azure_ProjectOxford/CognitiveServicesHub/~/SpeechServices" }, "tencent_tts": { - "title": "腾讯云 TTS", - "features": "提供免费额度,音质优秀,支持多种音色,国内访问速度快", - "use_case": "个人和企业用户,需要稳定的中文语音合成", + "title": tr("Tencent Cloud TTS"), + "features": tr("Tencent Cloud TTS features"), + "use_case": tr("Tencent Cloud TTS use case"), "registration": "https://console.cloud.tencent.com/tts" }, "qwen3_tts": { - "title": "通义千问 Qwen3 TTS", - "features": "阿里云通义千问语音合成,音质优秀,支持多种音色", - "use_case": "需要高质量中文语音合成的用户", + "title": tr("Tongyi Qwen3 TTS"), + "features": tr("Tongyi Qwen3 TTS features"), + "use_case": tr("High-quality Chinese speech synthesis use case"), "registration": "https://dashscope.aliyuncs.com/" }, "indextts2": { - "title": "IndexTTS2 语音克隆", - "features": "零样本语音克隆,上传参考音频即可合成相同音色的语音,需要本地或私有部署", - "use_case": "下载地址:https://pan.quark.cn/s/0767c9bcefd5", + "title": tr("IndexTTS2 Voice Clone"), + "features": tr("IndexTTS2 features"), + "use_case": tr("IndexTTS2 download link"), "registration": None }, "doubaotts": { - "title": "豆包语音 TTS", - "features": "火山引擎豆包语音合成,支持多种音色和情感,国内访问速度快", - "use_case": "需要高质量中文语音合成的用户", + "title": tr("Doubao TTS"), + "features": tr("Doubao TTS features"), + "use_case": tr("High-quality Chinese speech synthesis use case"), "registration": "https://www.volcengine.com/product/voice-tech" } } @@ -105,8 +105,8 @@ def render_tts_settings(tr): # 1. TTS引擎选择器 # st.subheader("🎤 TTS引擎选择") - engine_options = get_tts_engine_options() - engine_descriptions = get_tts_engine_descriptions() + engine_options = get_tts_engine_options(tr) + engine_descriptions = get_tts_engine_descriptions(tr) # 获取保存的TTS引擎设置 saved_tts_engine = config.ui.get("tts_engine", "edge_tts") @@ -117,11 +117,11 @@ def render_tts_settings(tr): # TTS引擎选择下拉框 selected_engine = st.selectbox( - "选择TTS引擎", + tr("Select TTS Engine"), options=list(engine_options.keys()), format_func=lambda x: engine_options[x], index=list(engine_options.keys()).index(saved_tts_engine), - help="选择您要使用的文本转语音引擎" + help=tr("Select TTS Engine Help") ) # 保存TTS引擎选择 @@ -132,12 +132,12 @@ def render_tts_settings(tr): if selected_engine in engine_descriptions: desc = engine_descriptions[selected_engine] - with st.expander(f"📋 {desc['title']} 详细说明", expanded=True): - st.markdown(f"**特点:** {desc['features']}") - st.markdown(f"**适用场景:** {desc['use_case']}") + with st.expander(tr("TTS Engine Details").format(engine=desc['title']), expanded=True): + st.markdown(f"**{tr('Features')}:** {desc['features']}") + st.markdown(f"**{tr('Use Case')}:** {desc['use_case']}") if desc['registration']: - st.markdown(f"**注册地址:** [{desc['registration']}]({desc['registration']})") + st.markdown(f"**{tr('Registration URL')}:** [{desc['registration']}]({desc['registration']})") # 3. 根据选择的引擎渲染对应的配置界面 # st.subheader("⚙️ 引擎配置") @@ -187,10 +187,10 @@ def render_edge_tts_settings(tr): # 音色选择下拉框 selected_friendly_name = st.selectbox( - "音色选择", + tr("Voice Selection"), options=list(friendly_names.values()), index=list(friendly_names.keys()).index(saved_voice_name) if saved_voice_name in friendly_names else 0, - help="选择Edge TTS音色" + help=tr("Select Edge TTS Voice") ) # 获取实际的语音名称 @@ -199,10 +199,10 @@ def render_edge_tts_settings(tr): ] # 显示音色信息 - with st.expander("💡 Edge TTS 音色说明", expanded=False): - st.write(f"已加载 {len(edge_voices)} 个音色") + with st.expander(tr("Edge TTS Voice Description"), expanded=False): + st.write(tr("Loaded voice count").format(count=len(edge_voices))) for v in edge_voices: - gender = "女声" if "Female" in v else "男声" + gender = tr("Female Voice") if "Female" in v else tr("Male Voice") name = v.replace("-Female", "").replace("-Male", "").replace("Neural", "") st.write(f"• {name} ({gender})") @@ -211,36 +211,36 @@ def render_edge_tts_settings(tr): # 音量调节 voice_volume = st.slider( - "音量调节", + tr("Voice Volume"), min_value=0, max_value=100, value=int(config.ui.get("edge_volume", 80)), step=1, - help="调节语音音量 (0-100)" + help=tr("Voice Volume Help Percent") ) config.ui["edge_volume"] = voice_volume st.session_state['voice_volume'] = voice_volume / 100.0 # 语速调节 voice_rate = st.slider( - "语速调节", + tr("Voice Rate"), min_value=0.5, max_value=2.0, value=config.ui.get("edge_rate", 1.0), step=0.1, - help="调节语音速度 (0.5-2.0倍速)" + help=tr("Voice Rate Help 0.5-2.0") ) config.ui["edge_rate"] = voice_rate st.session_state['voice_rate'] = voice_rate # 语调调节 voice_pitch = st.slider( - "语调调节", + tr("Voice Pitch"), min_value=-50, max_value=50, value=int(config.ui.get("edge_pitch", 0)), step=5, - help="调节语音音调 (-50%到+50%)" + help=tr("Voice Pitch Help Percent") ) config.ui["edge_pitch"] = voice_pitch # 转换为比例值 @@ -251,10 +251,10 @@ def render_azure_speech_settings(tr): """渲染 Azure Speech Services 引擎设置""" # 服务区域配置 azure_speech_region = st.text_input( - "服务区域", + tr("Service Region"), value=config.azure.get("speech_region", ""), - placeholder="例如:eastus", - help="Azure Speech Services 服务区域,如:eastus, westus2, eastasia 等" + placeholder=tr("Service Region Placeholder"), + help=tr("Azure Service Region Help") ) # API Key配置 @@ -262,7 +262,7 @@ def render_azure_speech_settings(tr): "API Key", value=config.azure.get("speech_key", ""), type="password", - help="Azure Speech Services API 密钥" + help=tr("Azure Speech Key Help") ) # 保存Azure配置 @@ -274,41 +274,41 @@ def render_azure_speech_settings(tr): # 音色名称输入 voice_name = st.text_input( - "音色名称", + tr("Voice Name"), value=saved_voice_name, - help="输入Azure Speech Services音色名称,直接使用官方音色名称即可。例如:zh-CN-YunzeNeural", + help=tr("Azure Voice Name Help"), placeholder="zh-CN-YunzeNeural" ) # 显示常用音色示例 - with st.expander("💡 常用音色参考", expanded=False): - st.write("**中文音色:**") - st.write("• zh-CN-XiaoxiaoMultilingualNeural (女声,多语言)") - st.write("• zh-CN-YunzeNeural (男声)") - st.write("• zh-CN-YunxiNeural (男声)") - st.write("• zh-CN-XiaochenNeural (女声)") + with st.expander(tr("Common Voice Reference"), expanded=False): + st.write(f"**{tr('Chinese Voices')}:**") + st.write(f"• zh-CN-XiaoxiaoMultilingualNeural ({tr('Female Voice')}, {tr('Multilingual')})") + st.write(f"• zh-CN-YunzeNeural ({tr('Male Voice')})") + st.write(f"• zh-CN-YunxiNeural ({tr('Male Voice')})") + st.write(f"• zh-CN-XiaochenNeural ({tr('Female Voice')})") st.write("") - st.write("**英文音色:**") - st.write("• en-US-AndrewMultilingualNeural (男声,多语言)") - st.write("• en-US-AvaMultilingualNeural (女声,多语言)") - st.write("• en-US-BrianMultilingualNeural (男声,多语言)") - st.write("• en-US-EmmaMultilingualNeural (女声,多语言)") + st.write(f"**{tr('English Voices')}:**") + st.write(f"• en-US-AndrewMultilingualNeural ({tr('Male Voice')}, {tr('Multilingual')})") + st.write(f"• en-US-AvaMultilingualNeural ({tr('Female Voice')}, {tr('Multilingual')})") + st.write(f"• en-US-BrianMultilingualNeural ({tr('Male Voice')}, {tr('Multilingual')})") + st.write(f"• en-US-EmmaMultilingualNeural ({tr('Female Voice')}, {tr('Multilingual')})") st.write("") - st.info("💡 更多音色请参考 [Azure Speech Services 官方文档](https://docs.microsoft.com/en-us/azure/cognitive-services/speech-service/language-support)") + st.info(tr("Azure Voices Docs Notice")) # 快速选择按钮 - st.write("**快速选择:**") + st.write(f"**{tr('Quick Select')}:**") cols = st.columns(3) with cols[0]: - if st.button("中文女声", help="zh-CN-XiaoxiaoMultilingualNeural"): + if st.button(tr("Chinese Female Voice"), help="zh-CN-XiaoxiaoMultilingualNeural"): voice_name = "zh-CN-XiaoxiaoMultilingualNeural" st.rerun() with cols[1]: - if st.button("中文男声", help="zh-CN-YunzeNeural"): + if st.button(tr("Chinese Male Voice"), help="zh-CN-YunzeNeural"): voice_name = "zh-CN-YunzeNeural" st.rerun() with cols[2]: - if st.button("英文女声", help="en-US-AvaMultilingualNeural"): + if st.button(tr("English Female Voice"), help="en-US-AvaMultilingualNeural"): voice_name = "en-US-AvaMultilingualNeural" st.rerun() @@ -316,10 +316,10 @@ def render_azure_speech_settings(tr): if voice_name.strip(): # 检查是否为有效的Azure音色格式 if is_valid_azure_voice_name(voice_name): - st.success(f"✅ 音色名称有效: {voice_name}") + st.success(tr("Voice name valid").format(voice=voice_name)) else: - st.warning(f"⚠️ 音色名称格式可能不正确: {voice_name}") - st.info("💡 Azure音色名称通常格式为: [语言]-[地区]-[名称]Neural") + st.warning(tr("Voice name format may be invalid").format(voice=voice_name)) + st.info(tr("Azure voice name format notice")) # 保存配置 config.ui["azure_voice_name"] = voice_name @@ -327,36 +327,36 @@ def render_azure_speech_settings(tr): # 音量调节 voice_volume = st.slider( - "音量调节", + tr("Voice Volume"), min_value=0, max_value=100, value=int(config.ui.get("azure_volume", 80)), step=1, - help="调节语音音量 (0-100)" + help=tr("Voice Volume Help Percent") ) config.ui["azure_volume"] = voice_volume st.session_state['voice_volume'] = voice_volume / 100.0 # 语速调节 voice_rate = st.slider( - "语速调节", + tr("Voice Rate"), min_value=0.5, max_value=2.0, value=config.ui.get("azure_rate", 1.0), step=0.1, - help="调节语音速度 (0.5-2.0倍速)" + help=tr("Voice Rate Help 0.5-2.0") ) config.ui["azure_rate"] = voice_rate st.session_state['voice_rate'] = voice_rate # 语调调节 voice_pitch = st.slider( - "语调调节", + tr("Voice Pitch"), min_value=-50, max_value=50, value=int(config.ui.get("azure_pitch", 0)), step=5, - help="调节语音音调 (-50%到+50%)" + help=tr("Voice Pitch Help Percent") ) config.ui["azure_pitch"] = voice_pitch # 转换为比例值 @@ -364,11 +364,11 @@ def render_azure_speech_settings(tr): # 显示配置状态 if azure_speech_region and azure_speech_key: - st.success("✅ Azure Speech Services 配置已设置") + st.success(tr("Azure Speech Services configured")) elif not azure_speech_region: - st.warning("⚠️ 请配置服务区域") + st.warning(tr("Please configure service region")) elif not azure_speech_key: - st.warning("⚠️ 请配置 API Key") + st.warning(tr("Please configure API Key")) def render_tencent_tts_settings(tr): @@ -377,7 +377,7 @@ def render_tencent_tts_settings(tr): secret_id = st.text_input( "Secret ID", value=config.tencent.get("secret_id", ""), - help="请输入您的腾讯云 Secret ID" + help=tr("Tencent Secret ID Help") ) # Secret Key 输入 @@ -385,7 +385,7 @@ def render_tencent_tts_settings(tr): "Secret Key", value=config.tencent.get("secret_key", ""), type="password", - help="请输入您的腾讯云 Secret Key" + help=tr("Tencent Secret Key Help") ) # 地域选择 @@ -404,10 +404,10 @@ def render_tencent_tts_settings(tr): region_options.append(saved_region) region = st.selectbox( - "服务地域", + tr("Service Region"), options=region_options, index=region_options.index(saved_region), - help="选择腾讯云 TTS 服务地域" + help=tr("Tencent Service Region Help") ) # 音色选择 @@ -434,13 +434,13 @@ def render_tencent_tts_settings(tr): saved_voice_type = config.ui.get("tencent_voice_type", "101001") if saved_voice_type not in voice_type_options: - voice_type_options[saved_voice_type] = f"自定义音色 ({saved_voice_type})" + voice_type_options[saved_voice_type] = f"{tr('Custom Voice')} ({saved_voice_type})" selected_voice_display = st.selectbox( - "音色选择", + tr("Voice Selection"), options=list(voice_type_options.values()), index=list(voice_type_options.keys()).index(saved_voice_type), - help="选择腾讯云 TTS 音色" + help=tr("Select Tencent TTS Voice") ) # 获取实际的音色ID @@ -450,31 +450,31 @@ def render_tencent_tts_settings(tr): # 语速调节 voice_rate = st.slider( - "语速调节", + tr("Voice Rate"), min_value=0.5, max_value=2.0, value=config.ui.get("tencent_rate", 1.0), step=0.1, - help="调节语音速度 (0.5-2.0)" + help=tr("Voice Rate Help 0.5-2.0") ) config.ui["voice_name"] = saved_voice_type # 兼容性 # 显示音色说明 - with st.expander("💡 腾讯云 TTS 音色说明", expanded=False): - st.write("**女声音色:**") + with st.expander(tr("Tencent Cloud TTS Voice Description"), expanded=False): + st.write(f"**{tr('Female Voices')}:**") female_voices = [(k, v) for k, v in voice_type_options.items() if "女声" in v] for voice_id, voice_desc in female_voices[:6]: # 显示前6个 st.write(f"• {voice_desc} (ID: {voice_id})") st.write("") - st.write("**男声音色:**") + st.write(f"**{tr('Male Voices')}:**") male_voices = [(k, v) for k, v in voice_type_options.items() if "男声" in v] for voice_id, voice_desc in male_voices: st.write(f"• {voice_desc} (ID: {voice_id})") st.write("") - st.info("💡 更多音色请参考腾讯云官方文档") + st.info(tr("Tencent More Voices Notice")) # 保存配置 config.tencent["secret_id"] = secret_id @@ -491,13 +491,13 @@ def render_qwen3_tts_settings(tr): "API Key", value=config.tts_qwen.get("api_key", ""), type="password", - help="通义千问 DashScope API Key" + help=tr("Qwen DashScope API Key Help") ) model_name = st.text_input( - "模型名称", + tr("TTS Model Name"), value=config.tts_qwen.get("model_name", "qwen3-tts-flash"), - help="Qwen TTS 模型名,例如 qwen3-tts-flash" + help=tr("Qwen TTS Model Help") ) # Qwen3 TTS 音色选项 - 中文名: 英文参数 @@ -538,22 +538,22 @@ def render_qwen3_tts_settings(tr): voice_options[saved_display_name] = saved_voice_param selected_display_name = st.selectbox( - "音色选择", + tr("Voice Selection"), options=display_names, index=display_names.index(saved_display_name) if saved_display_name in display_names else 0, - help="选择Qwen3 TTS音色" + help=tr("Select Qwen3 TTS Voice") ) # 获取对应的英文参数 voice_type = voice_options.get(selected_display_name, "Cherry") voice_rate = st.slider( - "语速调节", + tr("Voice Rate"), min_value=0.5, max_value=2.0, value=1.0, step=0.1, - help="调节语音速度 (0.5-2.0)" + help=tr("Voice Rate Help 0.5-2.0") ) # 保存配置 @@ -570,23 +570,23 @@ def render_indextts2_tts_settings(tr): # API 地址配置 api_url = st.text_input( - "API 地址", + tr("API URL"), value=config.indextts2.get("api_url", "http://127.0.0.1:8081/tts"), - help="IndexTTS2 API 服务地址" + help=tr("IndexTTS2 API URL Help") ) # 参考音频文件路径 reference_audio = st.text_input( - "参考音频路径", + tr("Reference Audio Path"), value=config.indextts2.get("reference_audio", ""), - help="用于语音克隆的参考音频文件路径(WAV 格式,建议 3-10 秒)" + help=tr("Reference Audio Path Help") ) # 文件上传功能 uploaded_file = st.file_uploader( - "或上传参考音频文件", + tr("Upload Reference Audio File"), type=["wav", "mp3"], - help="上传一段清晰的音频用于语音克隆" + help=tr("Upload Reference Audio Help") ) if uploaded_file is not None: @@ -597,28 +597,34 @@ def render_indextts2_tts_settings(tr): with open(audio_path, "wb") as f: f.write(uploaded_file.getbuffer()) reference_audio = audio_path - st.success(f"✅ 音频已上传: {audio_path}") + st.success(tr("Audio uploaded").format(path=audio_path)) # 推理模式 - infer_mode = st.selectbox( - "推理模式", - options=["普通推理", "快速推理"], - index=0 if config.indextts2.get("infer_mode", "普通推理") == "普通推理" else 1, - help="普通推理质量更高但速度较慢,快速推理速度更快但质量略低" - ) + infer_mode_options = [ + ("普通推理", tr("Standard Inference")), + ("快速推理", tr("Fast Inference")), + ] + infer_mode_index = 0 if config.indextts2.get("infer_mode", "普通推理") == "普通推理" else 1 + infer_mode = infer_mode_options[st.selectbox( + tr("Inference Mode"), + options=range(len(infer_mode_options)), + index=infer_mode_index, + format_func=lambda x: infer_mode_options[x][1], + help=tr("Inference Mode Help") + )][0] # 高级参数折叠面板 - with st.expander("🔧 高级参数", expanded=False): + with st.expander(tr("Advanced Parameters"), expanded=False): col1, col2 = st.columns(2) with col1: temperature = st.slider( - "采样温度 (Temperature)", + tr("Sampling Temperature"), min_value=0.1, max_value=2.0, value=float(config.indextts2.get("temperature", 1.0)), step=0.1, - help="控制随机性,值越高输出越随机,值越低越确定" + help=tr("Sampling Temperature Help") ) top_p = st.slider( @@ -627,7 +633,7 @@ def render_indextts2_tts_settings(tr): max_value=1.0, value=float(config.indextts2.get("top_p", 0.8)), step=0.05, - help="nucleus 采样的概率阈值,值越小结果越确定" + help=tr("Top P Help") ) top_k = st.slider( @@ -636,49 +642,37 @@ def render_indextts2_tts_settings(tr): max_value=100, value=int(config.indextts2.get("top_k", 30)), step=5, - help="top-k 采样的 k 值,0 表示不使用 top-k" + help=tr("Top K Help") ) with col2: num_beams = st.slider( - "束搜索 (Num Beams)", + tr("Num Beams"), min_value=1, max_value=10, value=int(config.indextts2.get("num_beams", 3)), step=1, - help="束搜索的 beam 数量,值越大质量可能越好但速度越慢" + help=tr("Num Beams Help") ) repetition_penalty = st.slider( - "重复惩罚 (Repetition Penalty)", + tr("Repetition Penalty"), min_value=1.0, max_value=20.0, value=float(config.indextts2.get("repetition_penalty", 10.0)), step=0.5, - help="值越大越能避免重复,但过大可能导致不自然" + help=tr("Repetition Penalty Help") ) do_sample = st.checkbox( - "启用采样", + tr("Enable Sampling"), value=config.indextts2.get("do_sample", True), - help="启用采样可以获得更自然的语音" + help=tr("Enable Sampling Help") ) # 显示使用说明 - with st.expander("💡 IndexTTS2 使用说明", expanded=False): - st.markdown(""" - **零样本语音克隆** - - 1. **准备参考音频**:上传或指定一段清晰的音频文件(建议 3-10 秒) - 2. **设置 API 地址**:确保 IndexTTS2 服务正常运行 - 3. **开始合成**:系统会自动使用参考音频的音色合成新语音 - - **注意事项**: - - 参考音频质量直接影响合成效果 - - 建议使用无背景噪音的清晰音频 - - 文本长度建议控制在合理范围内 - - 首次合成可能需要较长时间 - """) + with st.expander(tr("IndexTTS2 Usage Instructions Title"), expanded=False): + st.markdown(tr("IndexTTS2 Usage Instructions")) # 保存配置 config.indextts2["api_url"] = api_url @@ -702,7 +696,7 @@ def render_doubaotts_settings(tr): ak = st.text_input( "Access Key", value=config.doubaotts.get("ak", ""), - help="火山引擎 Access Key" + help=tr("Volcengine Access Key Help") ) # SK 输入 @@ -710,14 +704,14 @@ def render_doubaotts_settings(tr): "Secret Key", value=config.doubaotts.get("sk", ""), type="password", - help="火山引擎 Secret Key" + help=tr("Volcengine Secret Key Help") ) # AppID 输入 appid = st.text_input( "AppID", value=config.doubaotts.get("appid", ""), - help="豆包语音应用 AppID" + help=tr("Doubao AppID Help") ) # Token 输入 @@ -725,14 +719,14 @@ def render_doubaotts_settings(tr): "Token", value=config.doubaotts.get("token", ""), type="password", - help="豆包语音应用 Token" + help=tr("Doubao Token Help") ) # 集群配置 cluster = st.text_input( - "集群", + tr("Cluster"), value=config.doubaotts.get("cluster", "volcano_tts"), - help="业务集群,标准音色使用 volcano_tts" + help=tr("Doubao Cluster Help") ) # 音色选择 @@ -836,13 +830,13 @@ def render_doubaotts_settings(tr): saved_voice_type = config.ui.get("doubaotts_voice_type", "BV700_streaming") if saved_voice_type not in voice_options: - voice_options[saved_voice_type] = f"自定义音色 ({saved_voice_type})" + voice_options[saved_voice_type] = f"{tr('Custom Voice')} ({saved_voice_type})" selected_voice_display = st.selectbox( - "音色选择", + tr("Voice Selection"), options=list(voice_options.values()), index=list(voice_options.keys()).index(saved_voice_type) if saved_voice_type in voice_options else 0, - help="选择豆包语音 TTS 音色" + help=tr("Select Doubao TTS Voice") ) # 获取实际的音色ID @@ -851,63 +845,63 @@ def render_doubaotts_settings(tr): ] # 高级参数折叠面板 - with st.expander("🔧 高级参数", expanded=False): + with st.expander(tr("Advanced Parameters"), expanded=False): col1, col2 = st.columns(2) with col1: # 语速调节 voice_rate = st.slider( - "语速调节", + tr("Voice Rate"), min_value=0.2, max_value=3.0, value=config.ui.get("doubaotts_rate", 1.0), step=0.1, - help="调节语音速度 (0.2-3.0)" + help=tr("Voice Rate Help 0.2-3.0") ) # 音量调节 voice_volume = st.slider( - "音量调节", + tr("Voice Volume"), min_value=0.1, max_value=2.0, value=config.doubaotts.get("volume", 1.0), step=0.1, - help="调节语音音量 (0.1-2.0)" + help=tr("Voice Volume Help 0.1-2.0") ) with col2: # 音高调节 voice_pitch = st.slider( - "音高调节", + tr("Voice Pitch"), min_value=0.5, max_value=1.5, value=config.doubaotts.get("pitch", 1.0), step=0.1, - help="调节语音音高 (0.5-1.5)" + help=tr("Voice Pitch Help 0.5-1.5") ) # 句尾静音时长 silence_duration = st.slider( - "句尾静音时长 (秒)", + tr("Sentence Silence Duration"), min_value=0.0, max_value=2.0, value=config.doubaotts.get("silence_duration", 0.125), step=0.05, - help="调节句尾静音时长 (0.0-2.0秒)" + help=tr("Sentence Silence Duration Help") ) # 显示API Key申请流程 - with st.expander("💡 豆包语音 TTS API Key申请流程", expanded=False): - st.write("**申请步骤:**") - st.write("1. 打开 [https://console.volcengine.com/iam/keymanage](https://console.volcengine.com/iam/keymanage)") - st.write("2. 新建 Access Key 和 Secret Key") - st.write("3. 打开 [https://www.volcengine.com/product/voice-tech](https://www.volcengine.com/product/voice-tech)") - st.write("4. 点击立即使用") - st.write("5. 在最左边的API服务中心找到音频生成下面的语音合成(注意:是语音合成,不是语音合成大模型)") - st.write("6. 翻到最下面获取 APPID 和 Access Token") + with st.expander(tr("Doubao TTS API Key Application Process"), expanded=False): + st.write(f"**{tr('Application Steps')}:**") + st.write(tr("Doubao TTS Step 1")) + st.write(tr("Doubao TTS Step 2")) + st.write(tr("Doubao TTS Step 3")) + st.write(tr("Doubao TTS Step 4")) + st.write(tr("Doubao TTS Step 5")) + st.write(tr("Doubao TTS Step 6")) st.write("") - st.info("💡 请将获取到的 Access Key、Secret Key、AppID 和 Token 填写到上方的配置中") + st.info(tr("Doubao TTS Fill Credentials Notice")) # 保存配置 config.doubaotts["ak"] = ak @@ -925,7 +919,7 @@ def render_doubaotts_settings(tr): # 显示配置状态 if ak and sk and appid and token: - st.success("✅ 豆包语音 TTS 配置已设置") + st.success(tr("Doubao TTS configured")) else: missing = [] if not ak: @@ -937,13 +931,13 @@ def render_doubaotts_settings(tr): if not token: missing.append("Token") if missing: - st.warning(f"⚠️ 请配置: {', '.join(missing)}") + st.warning(tr("Please configure missing fields").format(fields=', '.join(missing))) def render_voice_preview_new(tr, selected_engine): """渲染新的语音试听功能""" - if st.button("🎵 试听语音合成", use_container_width=True): - play_content = "感谢关注 NarratoAI,有任何问题或建议,可以关注微信公众号,求助或讨论" + if st.button(tr("Preview Voice Synthesis"), use_container_width=True): + play_content = tr("Voice Preview Sample") # 根据选择的引擎获取对应的语音配置 voice_name = "" @@ -990,10 +984,10 @@ def render_voice_preview_new(tr, selected_engine): voice_pitch = 1.0 # 豆包语音 TTS 不支持音调调节 if not voice_name: - st.error("请先配置语音设置") + st.error(tr("Please configure voice settings first")) return - with st.spinner("正在合成语音..."): + with st.spinner(tr("Synthesizing Voice")): temp_dir = utils.storage_dir("temp", create=True) audio_file = os.path.join(temp_dir, f"tmp-voice-{str(uuid4())}.mp3") @@ -1007,7 +1001,7 @@ def render_voice_preview_new(tr, selected_engine): ) if sub_maker and os.path.exists(audio_file): - st.success("✅ 语音合成成功!") + st.success(tr("Voice synthesis successful")) # 播放音频 with open(audio_file, 'rb') as audio_file_obj: @@ -1020,7 +1014,7 @@ def render_voice_preview_new(tr, selected_engine): except: pass else: - st.error("❌ 语音合成失败,请检查配置") + st.error(tr("Voice synthesis failed")) def render_azure_v2_settings(tr): @@ -1089,7 +1083,7 @@ def render_voice_parameters(tr, voice_name): else: # SoulVoice 不支持音调调节,设置默认值 st.session_state['voice_pitch'] = 1.0 - st.info("ℹ️ SoulVoice 引擎不支持音调调节") + st.info(tr("SoulVoice pitch not supported")) def render_voice_preview(tr, voice_name): diff --git a/webui/components/basic_settings.py b/webui/components/basic_settings.py index 95db275..842a500 100644 --- a/webui/components/basic_settings.py +++ b/webui/components/basic_settings.py @@ -26,7 +26,7 @@ OPENAI_COMPATIBLE_GATEWAY_BASE_URLS = { } -def build_base_url_help(provider: str, model_type: str) -> tuple[str, bool, str]: +def build_base_url_help(provider: str, model_type: str, tr=lambda key: key) -> tuple[str, bool, str]: """ 根据 provider 返回 Base URL 的帮助文案 @@ -35,14 +35,14 @@ def build_base_url_help(provider: str, model_type: str) -> tuple[str, bool, str] requires_base: 是否强制提示必须填写 Base URL placeholder: 推荐的默认值(可为空字符串) """ - default_help = "自定义 API 端点(可选),当使用自建或第三方代理时需要填写" + default_help = tr("Custom API endpoint help") provider_key = (provider or "").lower() example_url = OPENAI_COMPATIBLE_GATEWAY_BASE_URLS.get(provider_key) if example_url is not None: - extra = f"\n推荐接口地址: {example_url}" if example_url else "" + extra = f"\n{tr('Recommended API endpoint')}: {example_url}" if example_url else "" help_text = ( - f"{model_type} 选择的提供商基于 OpenAI 兼容网关,必须填写完整的接口地址。" + f"{tr('OpenAI compatible gateway help').format(model_type=model_type)}" f"{extra}" ) return help_text, True, example_url @@ -227,11 +227,11 @@ def render_proxy_settings(tr): config.proxy["https"] = "" # 剪映草稿地址设置 - st.subheader("剪映草稿设置") + st.subheader(tr("Jianying Draft Settings")) jianying_draft_path = st.text_input( - "剪映草稿文件夹路径", + tr("Jianying Draft Folder Path"), value=config.ui.get("jianying_draft_path", ""), - help="剪映草稿文件夹路径,例如:C:\\Users\\用户名\\Documents\\JianyingPro Drafts" + help=tr("Jianying Draft Folder Path Help") ) config.ui["jianying_draft_path"] = jianying_draft_path @@ -479,13 +479,15 @@ def render_vision_llm_settings(tr): model_name_input = st.text_input( tr("Vision Model Name"), value=current_model, - help="输入完整模型名称\n\n" - "常用示例:\n" - "• Qwen/Qwen3.5-122B-A10B\n" - "• gemini/gemini-2.0-flash-lite\n" - "• gpt-4o\n" - "• Qwen/Qwen2.5-VL-32B-Instruct (SiliconFlow)\n\n" - "支持常见 OpenAI 兼容网关(如 OpenAI/DeepSeek/OpenRouter/SiliconFlow)", + help=( + tr("Model Name Input Help") + + "\n\n" + + "• Qwen/Qwen3.5-122B-A10B\n" + + "• gemini/gemini-2.0-flash-lite\n" + + "• gpt-4o\n" + + "• Qwen/Qwen2.5-VL-32B-Instruct (SiliconFlow)\n\n" + + tr("OpenAI compatible providers help") + ), key="vision_model_input" ) @@ -496,16 +498,18 @@ def render_vision_llm_settings(tr): tr("Vision API Key"), value=vision_api_key, type="password", - help="对应 provider 的 API 密钥\n\n" - "获取地址:\n" - "• Gemini: https://makersuite.google.com/app/apikey\n" - "• OpenAI: https://platform.openai.com/api-keys\n" - "• Qwen: https://bailian.console.aliyun.com/\n" - "• SiliconFlow: https://cloud.siliconflow.cn/account/ak" + help=( + tr("Provider API Key Help") + + "\n\n" + + "• Gemini: https://makersuite.google.com/app/apikey\n" + + "• OpenAI: https://platform.openai.com/api-keys\n" + + "• Qwen: https://bailian.console.aliyun.com/\n" + + "• SiliconFlow: https://cloud.siliconflow.cn/account/ak" + ) ) vision_base_help, vision_base_required, vision_placeholder = build_base_url_help( - selected_provider, "视频分析模型" + selected_provider, tr("Vision model"), tr ) st_vision_base_url = st.text_input( tr("Vision Base URL"), @@ -515,15 +519,15 @@ def render_vision_llm_settings(tr): ) if vision_base_required and not st_vision_base_url: info_example = vision_placeholder or "https://your-openai-compatible-endpoint/v1" - st.info(f"请在上方填写 OpenAI 兼容网关地址,例如:{info_example}") + st.info(tr("Please fill OpenAI compatible gateway").format(example=info_example)) # 添加测试连接按钮 if st.button(tr("Test Connection"), key="test_vision_connection"): test_errors = [] if not st_vision_api_key: - test_errors.append("请先输入 API 密钥") + test_errors.append(tr("Please enter API key")) if not model_name_input: - test_errors.append("请先输入模型名称") + test_errors.append(tr("Please enter model name")) if test_errors: for error in test_errors: @@ -543,7 +547,7 @@ def render_vision_llm_settings(tr): else: st.error(message) except Exception as e: - st.error(f"测试连接时发生错误: {str(e)}") + st.error(f"{tr('Connection test error')}: {str(e)}") logger.error(f"OpenAI 兼容 视频分析模型连接测试失败: {str(e)}") # 验证和保存配置 @@ -597,9 +601,9 @@ def render_vision_llm_settings(tr): # 清除缓存,确保下次使用新配置 UnifiedLLMService.clear_cache() if st_vision_api_key or st_vision_base_url or st_vision_model_name: - st.success(f"视频分析模型配置已保存(OpenAI 兼容)") + st.success(tr("Vision model config saved")) except Exception as e: - st.error(f"保存配置失败: {str(e)}") + st.error(f"{tr('Failed to save config')}: {str(e)}") logger.error(f"保存视频分析配置失败: {str(e)}") @@ -742,13 +746,15 @@ def render_text_llm_settings(tr): model_name_input = st.text_input( tr("Text Model Name"), value=current_model, - help="输入完整模型名称\n\n" - "常用示例:\n" - "• Pro/zai-org/GLM-5\n" - "• deepseek/deepseek-chat\n" - "• gpt-4o\n" - "• deepseek-ai/DeepSeek-R1 (SiliconFlow)\n\n" - "支持常见 OpenAI 兼容网关(如 OpenAI/DeepSeek/OpenRouter/SiliconFlow)", + help=( + tr("Model Name Input Help") + + "\n\n" + + "• Pro/zai-org/GLM-5\n" + + "• deepseek/deepseek-chat\n" + + "• gpt-4o\n" + + "• deepseek-ai/DeepSeek-R1 (SiliconFlow)\n\n" + + tr("OpenAI compatible providers help") + ), key="text_model_input" ) @@ -759,18 +765,20 @@ def render_text_llm_settings(tr): tr("Text API Key"), value=text_api_key, type="password", - help="对应 provider 的 API 密钥\n\n" - "获取地址:\n" - "• DeepSeek: https://platform.deepseek.com/api_keys\n" - "• Gemini: https://makersuite.google.com/app/apikey\n" - "• OpenAI: https://platform.openai.com/api-keys\n" - "• Qwen: https://bailian.console.aliyun.com/\n" - "• SiliconFlow: https://cloud.siliconflow.cn/account/ak\n" - "• Moonshot: https://platform.moonshot.cn/console/api-keys" + help=( + tr("Provider API Key Help") + + "\n\n" + + "• DeepSeek: https://platform.deepseek.com/api_keys\n" + + "• Gemini: https://makersuite.google.com/app/apikey\n" + + "• OpenAI: https://platform.openai.com/api-keys\n" + + "• Qwen: https://bailian.console.aliyun.com/\n" + + "• SiliconFlow: https://cloud.siliconflow.cn/account/ak\n" + + "• Moonshot: https://platform.moonshot.cn/console/api-keys" + ) ) text_base_help, text_base_required, text_placeholder = build_base_url_help( - selected_provider, "文案生成模型" + selected_provider, tr("Text model"), tr ) st_text_base_url = st.text_input( tr("Text Base URL"), @@ -780,15 +788,15 @@ def render_text_llm_settings(tr): ) if text_base_required and not st_text_base_url: info_example = text_placeholder or "https://your-openai-compatible-endpoint/v1" - st.info(f"请在上方填写 OpenAI 兼容网关地址,例如:{info_example}") + st.info(tr("Please fill OpenAI compatible gateway").format(example=info_example)) # 添加测试连接按钮 if st.button(tr("Test Connection"), key="test_text_connection"): test_errors = [] if not st_text_api_key: - test_errors.append("请先输入 API 密钥") + test_errors.append(tr("Please enter API key")) if not model_name_input: - test_errors.append("请先输入模型名称") + test_errors.append(tr("Please enter model name")) if test_errors: for error in test_errors: @@ -808,7 +816,7 @@ def render_text_llm_settings(tr): else: st.error(message) except Exception as e: - st.error(f"测试连接时发生错误: {str(e)}") + st.error(f"{tr('Connection test error')}: {str(e)}") logger.error(f"OpenAI 兼容 文案生成模型连接测试失败: {str(e)}") # 验证和保存配置 @@ -861,9 +869,9 @@ def render_text_llm_settings(tr): # 清除缓存,确保下次使用新配置 UnifiedLLMService.clear_cache() if st_text_api_key or st_text_base_url or st_text_model_name: - st.success(f"文案生成模型配置已保存(OpenAI 兼容)") + st.success(tr("Text model config saved")) except Exception as e: - st.error(f"保存配置失败: {str(e)}") + st.error(f"{tr('Failed to save config')}: {str(e)}") logger.error(f"保存文案生成配置失败: {str(e)}") # # Cloudflare 特殊配置 diff --git a/webui/components/script_settings.py b/webui/components/script_settings.py index 4ac6f2b..772906b 100644 --- a/webui/components/script_settings.py +++ b/webui/components/script_settings.py @@ -346,7 +346,7 @@ def short_drama_summary(tr): # 显示当前已上传的字幕文件路径 if 'subtitle_path' in st.session_state and st.session_state['subtitle_path']: - st.info(f"已上传字幕: {os.path.basename(st.session_state['subtitle_path'])}") + st.info(tr("Uploaded subtitle").format(file=os.path.basename(st.session_state['subtitle_path']))) if st.button(tr("清除已上传字幕")): st.session_state['subtitle_path'] = None st.session_state['subtitle_content'] = None @@ -388,8 +388,8 @@ def short_drama_summary(tr): # 更新状态 st.success( f"{tr('字幕上传成功')} " - f"(编码: {detected_encoding.upper()}, " - f"大小: {len(script_content)} 字符)" + f"({tr('Encoding')}: {detected_encoding.upper()}, " + f"{tr('Size')}: {len(script_content)} {tr('Characters')})" ) st.session_state['subtitle_path'] = script_file_path st.session_state['subtitle_content'] = script_content @@ -417,23 +417,23 @@ def render_fun_asr_transcription(tr): st.session_state['subtitle_content'] = None st.session_state['subtitle_file_processed'] = False - with st.expander("阿里百炼 Fun-ASR 字幕转录", expanded=False): - st.caption("上传本地音频/视频后,将自动上传到阿里百炼临时存储并通过 fun-asr 生成 SRT 字幕。") + with st.expander(tr("Ali Bailian Fun-ASR Subtitle Transcription"), expanded=False): + st.caption(tr("Fun-ASR upload caption")) st.markdown( - "API Key 获取地址:" + f"{tr('API Key URL')}: " "[https://bailian.console.aliyun.com/?tab=model#/api-key]" "(https://bailian.console.aliyun.com/?tab=model#/api-key)" ) api_key = st.text_input( - "阿里百炼 API Key", + tr("Ali Bailian API Key"), value=config.fun_asr.get("api_key", ""), type="password", - help="请输入你自己的阿里百炼 API Key;保存配置后会写入本地 config.toml", + help=tr("Ali Bailian API Key Help"), key="fun_asr_api_key", ) uploaded_media = st.file_uploader( - "上传需要转录的音频/视频", + tr("Upload media to transcribe"), type=[ "aac", "amr", "avi", "flac", "flv", "m4a", "mkv", "mov", "mp3", "mp4", "mpeg", "ogg", "opus", "wav", "webm", "wma", "wmv", @@ -442,14 +442,14 @@ def render_fun_asr_transcription(tr): key="fun_asr_media_uploader", ) - if st.button("转写生成字幕", key="fun_asr_transcribe"): + if st.button(tr("Transcribe subtitles"), key="fun_asr_transcribe"): if not api_key.strip(): clear_fun_asr_subtitle_state() - st.error("请先输入阿里百炼 API Key") + st.error(tr("Please enter Ali Bailian API Key")) return if uploaded_media is None: clear_fun_asr_subtitle_state() - st.error("请先上传需要转录的音频或视频文件") + st.error(tr("Please upload media to transcribe")) return try: @@ -474,7 +474,7 @@ def render_fun_asr_transcription(tr): subtitle_name = f"{os.path.splitext(os.path.basename(media_path))[0]}_fun_asr.srt" subtitle_path = os.path.join(utils.subtitle_dir(), subtitle_name) - with st.spinner("正在使用阿里百炼 Fun-ASR 转写字幕,请稍候..."): + with st.spinner(tr("Transcribing with Fun-ASR...")): generated_path = fun_asr_subtitle.create_with_fun_asr( local_file=media_path, subtitle_file=subtitle_path, @@ -483,7 +483,7 @@ def render_fun_asr_transcription(tr): if not generated_path or not os.path.exists(generated_path): clear_fun_asr_subtitle_state() - st.error("Fun-ASR 转写失败:未生成字幕文件") + st.error(tr("Fun-ASR failed without subtitle file")) return with open(generated_path, "r", encoding="utf-8") as f: @@ -492,11 +492,11 @@ def render_fun_asr_transcription(tr): st.session_state['subtitle_path'] = generated_path st.session_state['subtitle_content'] = subtitle_content st.session_state['subtitle_file_processed'] = True - st.success(f"字幕转写成功: {os.path.basename(generated_path)}") + st.success(tr("Subtitle transcription succeeded").format(file=os.path.basename(generated_path))) except Exception as e: clear_fun_asr_subtitle_state() logger.error(f"Fun-ASR 字幕转写失败: {traceback.format_exc()}") - st.error(f"Fun-ASR 字幕转写失败: {str(e)}") + st.error(f"{tr('Fun-ASR transcription failed')}: {str(e)}") def render_script_buttons(tr, params): @@ -519,7 +519,7 @@ def render_script_buttons(tr, params): if st.button(button_name, key="script_action", disabled=not script_path): if script_path == "auto": # 执行纪录片视频脚本生成(视频无字幕无配音) - generate_script_docu(params) + generate_script_docu(params, tr) elif script_path == "short": # 执行 短剧混剪 脚本生成 custom_clips = st.session_state.get('custom_clips') @@ -529,7 +529,7 @@ def render_script_buttons(tr, params): subtitle_path = st.session_state.get('subtitle_path') video_theme = st.session_state.get('video_theme') temperature = st.session_state.get('temperature') - generate_script_short_sunmmary(params, subtitle_path, video_theme, temperature) + generate_script_short_sunmmary(params, subtitle_path, video_theme, temperature, tr) else: load_script(tr, script_path) @@ -566,7 +566,7 @@ def save_script_with_validation(tr, video_clip_json_details): st.stop() # 第一步:格式验证 - with st.spinner("正在验证脚本格式..."): + with st.spinner(tr("Validating script format...")): try: result = check_script.check_format(video_clip_json_details) if not result.get('success'): @@ -574,13 +574,13 @@ def save_script_with_validation(tr, video_clip_json_details): error_message = result.get('message', '未知错误') error_details = result.get('details', '') - st.error(f"**脚本格式验证失败**") - st.error(f"**错误信息:** {error_message}") + st.error(f"**{tr('Script format validation failed')}**") + st.error(f"**{tr('Error Message')}:** {error_message}") if error_details: - st.error(f"**详细说明:** {error_details}") + st.error(f"**{tr('Details')}:** {error_details}") # 显示正确格式示例 - st.info("**正确的脚本格式示例:**") + st.info(f"**{tr('Correct script format example')}:**") example_script = [ { "_id": 1, @@ -601,7 +601,7 @@ def save_script_with_validation(tr, video_clip_json_details): st.stop() except Exception as e: - st.error(f"格式验证过程中发生错误: {str(e)}") + st.error(f"{tr('Script format validation error')}: {str(e)}") st.stop() # 第二步:保存脚本 @@ -624,7 +624,7 @@ def save_script_with_validation(tr, video_clip_json_details): config.app["video_clip_json_path"] = save_path # 显示成功消息 - st.success("✅ 脚本格式验证通过,保存成功!") + st.success(tr("Script validated and saved successfully")) # 强制重新加载页面更新选择框 time.sleep(0.5) # 给一点时间让用户看到成功消息 diff --git a/webui/components/subtitle_settings.py b/webui/components/subtitle_settings.py index ed1141a..41fac95 100644 --- a/webui/components/subtitle_settings.py +++ b/webui/components/subtitle_settings.py @@ -10,7 +10,7 @@ def render_subtitle_panel(tr): """渲染字幕设置面板""" with st.container(border=True): st.write(tr("Subtitle Settings")) - st.info("💡 提示:目前仅 **edge-tts** 引擎支持自动生成字幕,其他 TTS 引擎暂不支持。") + st.info(tr("Subtitle TTS support notice")) # 检查是否选择了 SoulVoice qwen3_tts引擎 from app.services import voice @@ -20,8 +20,8 @@ def render_subtitle_panel(tr): if is_disabled_subtitle: # SoulVoice 引擎时显示禁用提示 - st.warning(f"⚠️ {tts_engine}不支持精确字幕生成") - st.info("💡 建议使用专业剪辑工具(如剪映、PR等)手动添加字幕") + st.warning(tr("TTS engine does not support precise subtitles").format(engine=tts_engine)) + st.info(tr("Manual subtitle editing recommendation")) # 强制禁用字幕 st.session_state['subtitle_enabled'] = False @@ -31,7 +31,7 @@ def render_subtitle_panel(tr): tr("Enable Subtitles"), value=False, disabled=True, - help="SoulVoice 引擎不支持字幕生成,请使用其他 TTS 引擎" + help=tr("Disabled subtitles help") ) else: # 其他引擎正常显示字幕选项 diff --git a/webui/i18n/en.json b/webui/i18n/en.json index 3a69807..bfab52e 100644 --- a/webui/i18n/en.json +++ b/webui/i18n/en.json @@ -86,6 +86,314 @@ "Hide Log": "Hide Log", "Upload Local Files": "Upload Local Files", "File Uploaded Successfully": "File Uploaded Successfully", - "Frame Interval (seconds)": "Frame Interval (seconds) (More keyframes consume more tokens)" + "Frame Interval (seconds)": "Frame Interval (seconds)", + "Generate Video Script": "Generate Video Script", + "Video Theme": "Video Theme", + "Generation Prompt": "Custom Prompt", + "Video LLM Provider": "Video Analysis Model", + "timestamp": "Timestamp", + "Picture description": "Picture Description", + "Narration": "Narration", + "Rebuild": "Regenerate", + "Load Video Script": "Load Video Script", + "Speech Pitch": "Speech Pitch", + "Please Select Script File": "Please Select Script File", + "Check Format": "Check Format", + "Script Loaded Successfully": "Script Loaded Successfully", + "Script loaded successfully": "Script loaded successfully", + "Script format check passed": "Script format check passed", + "Script format check failed": "Script format check failed", + "Failed to Load Script": "Failed to Load Script", + "Failed to load script": "Failed to load script", + "Failed to Save Script": "Failed to Save Script", + "Failed to save script": "Failed to save script", + "Script saved successfully": "Script saved successfully", + "Video Quality": "Video Quality", + "Custom prompt for LLM, leave empty to use default prompt": "Custom prompt for LLM. Leave empty to use the default prompt.", + "Proxy Settings": "Proxy Settings", + "HTTP_PROXY": "HTTP Proxy", + "HTTPs_PROXY": "HTTPS Proxy", + "Vision Model Settings": "Vision Model Settings", + "Vision Model Provider": "Vision Model Provider", + "Vision API Key": "Vision API Key", + "Vision Base URL": "Vision Base URL", + "Vision Model Name": "Vision Model Name", + "Text Generation Model Settings": "Text Generation Model Settings", + "LLM Model Name": "LLM Model Name", + "LLM Model API Key": "LLM Model API Key", + "Text Model Provider": "Text Model Provider", + "Text API Key": "Text API Key", + "Text Base URL": "Text Base URL", + "Text Model Name": "Text Model Name", + "Skip the first few seconds": "Skip the first few seconds", + "Difference threshold": "Difference Threshold", + "Vision processing batch size": "Vision Processing Batch Size", + "Test Connection": "Test Connection", + "Testing connection...": "Testing connection...", + "gemini model is available": "Gemini model is available", + "gemini model is not available": "Gemini model is not available", + "Unsupported provider": "Unsupported provider", + "0: Keep the audio only, 1: Keep the original sound only, 2: Keep the original sound and audio": "0: Keep the narration only, 1: Keep the original sound only, 2: Keep both original sound and narration", + "Text model is not available": "Text model is not available", + "Text model is available": "Text model is available", + "Upload Script": "Upload Script", + "Upload Script File": "Upload Script File", + "Script Uploaded Successfully": "Script Uploaded Successfully", + "Invalid JSON format": "Invalid JSON format", + "Upload failed": "Upload failed", + "Enable Proxy": "Enable Proxy", + "QwenVL model is available": "QwenVL model is available", + "QwenVL model is not available": "QwenVL model is not available", + "QwenVL model returned invalid response": "QwenVL model returned an invalid response", + "System settings": "System Settings", + "Clear Cache": "Clear Cache", + "Cache cleared": "Cache cleared", + "storage directory does not exist": "Storage directory does not exist", + "Failed to clear cache": "Failed to clear cache", + "Clear frames": "Clear frames", + "Clear clip videos": "Clear clip videos", + "Clear tasks": "Clear tasks", + "Directory cleared": "Directory cleared", + "Directory does not exist": "Directory does not exist", + "Failed to clear directory": "Failed to clear directory", + "Subtitle Preview": "Subtitle Preview", + "One-Click Transcribe": "One-Click Transcribe", + "Transcribing...": "Transcribing...", + "Transcription Complete!": "Transcription Complete!", + "Transcription Failed. Please try again.": "Transcription failed. Please try again.", + "API rate limit exceeded. Please wait about an hour and try again.": "API rate limit exceeded. Please wait about an hour and try again.", + "Resources exhausted. Please try again later.": "Resources exhausted. Please try again later.", + "Transcription Failed": "Transcription Failed", + "Short Generate": "Short Drama Mix", + "Generate Short Video Script": "Generate Short Video Script", + "Adjust the volume of the original audio": "Adjust the volume of the original audio", + "Original Volume": "Original Volume", + "Frame Interval (seconds) (More keyframes consume more tokens)": "Frame Interval (seconds) (More keyframes consume more tokens)", + "Batch Size": "Batch Size", + "Batch Size (More keyframes consume more tokens)": "Batch Size (smaller batches consume more tokens)", + "Short Drama Summary": "Short Drama Summary", + "Video Type": "Video Type", + "Select/Upload Script": "Select/Upload Script", + "原生Gemini模型连接成功": "Native Gemini model connection succeeded", + "原生Gemini模型连接失败": "Native Gemini model connection failed", + "OpenAI兼容Gemini代理连接成功": "OpenAI-compatible Gemini proxy connection succeeded", + "OpenAI兼容Gemini代理连接失败": "OpenAI-compatible Gemini proxy connection failed", + "Connection failed": "Connection failed", + "自定义片段": "Custom Clips", + "设置需要生成的短视频片段数量": "Set the number of short video clips to generate", + "上传字幕文件": "Upload Subtitle File", + "清除已上传字幕": "Clear Uploaded Subtitle", + "无法读取字幕文件,请检查文件编码(支持 UTF-8、UTF-16、GBK、GB2312)": "Unable to read the subtitle file. Please check the file encoding. Supported encodings: UTF-8, UTF-16, GBK, GB2312.", + "字幕文件内容似乎为空,请检查文件": "The subtitle file appears to be empty. Please check the file.", + "字幕上传成功": "Subtitle uploaded successfully", + "短剧名称": "Short Drama Name", + "生成短剧解说脚本": "Generate Short Drama Narration Script", + "请输入视频脚本": "Please enter the video script", + "Subtitle TTS support notice": "💡 Note: currently only the **edge-tts** engine supports automatic subtitle generation. Other TTS engines are not supported yet.", + "TTS engine does not support precise subtitles": "⚠️ {engine} does not support precise subtitle generation", + "Manual subtitle editing recommendation": "💡 We recommend adding subtitles manually in a professional editor such as CapCut or Premiere Pro.", + "Disabled subtitles help": "This TTS engine does not support subtitle generation. Please use another TTS engine.", + "Tencent Cloud TTS": "Tencent Cloud TTS", + "Tongyi Qwen3 TTS": "Tongyi Qwen3 TTS", + "IndexTTS2 Voice Clone": "IndexTTS2 Voice Clone", + "Doubao TTS": "Doubao TTS", + "Edge TTS features": "Completely free, but service stability can vary and voice cloning is not supported.", + "Edge TTS use case": "Testing and lightweight use", + "Azure Speech Services features": "Includes a free quota, then pay-as-you-go billing. An overseas credit card may be required.", + "Azure Speech Services use case": "Enterprise use cases that need a stable service", + "Tencent Cloud TTS features": "Includes a free quota, good voice quality, multiple voices, and fast access in mainland China.", + "Tencent Cloud TTS use case": "Personal and enterprise users who need stable Chinese speech synthesis", + "Tongyi Qwen3 TTS features": "Alibaba Cloud Tongyi Qwen speech synthesis with high-quality voices and multiple voice options.", + "High-quality Chinese speech synthesis use case": "Users who need high-quality Chinese speech synthesis", + "IndexTTS2 features": "Zero-shot voice cloning. Upload a reference audio file to synthesize speech with a matching voice. Requires local or private deployment.", + "IndexTTS2 download link": "Download link: https://pan.quark.cn/s/0767c9bcefd5", + "Doubao TTS features": "Volcengine Doubao speech synthesis with multiple voices and emotions, plus fast access in mainland China.", + "Select TTS Engine": "Select TTS Engine", + "Select TTS Engine Help": "Choose the text-to-speech engine you want to use.", + "TTS Engine Details": "📋 {engine} Details", + "Features": "Features", + "Use Case": "Use Case", + "Registration URL": "Registration URL", + "Voice Selection": "Voice Selection", + "Select Edge TTS Voice": "Select an Edge TTS voice", + "Edge TTS Voice Description": "💡 Edge TTS Voice Notes", + "Loaded voice count": "Loaded {count} voices", + "Female Voice": "Female voice", + "Male Voice": "Male voice", + "Voice Volume": "Voice Volume", + "Voice Volume Help Percent": "Adjust voice volume (0-100)", + "Voice Rate": "Voice Rate", + "Voice Rate Help 0.5-2.0": "Adjust voice speed (0.5-2.0x)", + "Voice Pitch": "Voice Pitch", + "Voice Pitch Help Percent": "Adjust voice pitch (-50% to +50%)", + "Service Region": "Service Region", + "Service Region Placeholder": "e.g. eastus", + "Azure Service Region Help": "Azure Speech Services region, such as eastus, westus2, or eastasia.", + "Azure Speech Key Help": "Azure Speech Services API key", + "Voice Name": "Voice Name", + "Azure Voice Name Help": "Enter an Azure Speech Services voice name. You can use the official voice name directly, such as zh-CN-YunzeNeural.", + "Common Voice Reference": "💡 Common Voice Reference", + "Chinese Voices": "Chinese Voices", + "English Voices": "English Voices", + "Multilingual": "multilingual", + "Azure Voices Docs Notice": "💡 For more voices, see the [Azure Speech Services documentation](https://docs.microsoft.com/en-us/azure/cognitive-services/speech-service/language-support).", + "Quick Select": "Quick Select", + "Chinese Female Voice": "Chinese Female Voice", + "Chinese Male Voice": "Chinese Male Voice", + "English Female Voice": "English Female Voice", + "Voice name valid": "✅ Voice name is valid: {voice}", + "Voice name format may be invalid": "⚠️ Voice name format may be incorrect: {voice}", + "Azure voice name format notice": "💡 Azure voice names usually follow this format: [language]-[region]-[name]Neural", + "Azure Speech Services configured": "✅ Azure Speech Services is configured", + "Please configure service region": "⚠️ Please configure the service region", + "Please configure API Key": "⚠️ Please configure the API Key", + "Task failed": "Task failed", + "Script file cannot be empty": "Script file cannot be empty", + "Video file cannot be empty": "Video file cannot be empty", + "Export to Jianying Draft": "📤 Export to Jianying Draft", + "Please configure Jianying draft folder in basic settings": "Please configure the Jianying draft folder in Basic Settings", + "Jianying draft folder does not exist": "Jianying draft folder does not exist: {path}", + "Please enter Jianying draft name": "Please enter the Jianying draft name", + "Confirm Export": "Confirm Export", + "Please enter draft name": "Please enter a draft name", + "Failed to build parameters": "Failed to build parameters", + "Exporting to Jianying draft...": "Exporting to Jianying draft, please wait...", + "Jianying draft exported successfully": "✅ Successfully exported to Jianying draft: {name}", + "Draft saved to": "📁 Draft saved to: {path}", + "Failed to export Jianying draft": "❌ Failed to export Jianying draft", + "Cancel": "Cancel", + "LLM initialization failed": "⚠️ LLM initialization failed: {error}\n\nPlease check whether the configuration file and dependencies are installed correctly.", + "Jianying Draft Settings": "Jianying Draft Settings", + "Jianying Draft Folder Path": "Jianying Draft Folder Path", + "Jianying Draft Folder Path Help": "Jianying draft folder path, for example: C:\\Users\\Username\\Documents\\JianyingPro Drafts", + "Custom API endpoint help": "Custom API endpoint (optional). Required when using a self-hosted or third-party proxy.", + "Recommended API endpoint": "Recommended endpoint", + "OpenAI compatible gateway help": "{model_type} uses an OpenAI-compatible gateway provider, so a complete endpoint URL is required.", + "Vision model": "Vision model", + "Text model": "Text model", + "Model Name Input Help": "Enter the full model name.\n\nCommon examples:", + "OpenAI compatible providers help": "Supports common OpenAI-compatible gateways such as OpenAI, DeepSeek, OpenRouter, and SiliconFlow.", + "Provider API Key Help": "API key for the selected provider.\n\nWhere to get one:", + "Please fill OpenAI compatible gateway": "Please fill in the OpenAI-compatible gateway URL above, for example: {example}", + "Please enter API key": "Please enter the API key first", + "Please enter model name": "Please enter the model name first", + "Connection test error": "An error occurred while testing the connection", + "Vision model config saved": "Vision model configuration saved (OpenAI compatible)", + "Text model config saved": "Text generation model configuration saved (OpenAI compatible)", + "Failed to save config": "Failed to save configuration", + "Custom Position (% from top)": "Custom Position (% from top)", + "Please enter a value between 0 and 100": "Please enter a value between 0 and 100", + "Please enter a valid number": "Please enter a valid number", + "None": "None", + "Uploaded subtitle": "Uploaded subtitle: {file}", + "Encoding": "Encoding", + "Size": "Size", + "Characters": "characters", + "Ali Bailian Fun-ASR Subtitle Transcription": "Ali Bailian Fun-ASR Subtitle Transcription", + "Fun-ASR upload caption": "After uploading a local audio/video file, it will be uploaded to temporary Ali Bailian storage and converted to SRT subtitles with fun-asr.", + "API Key URL": "API Key URL", + "Ali Bailian API Key": "Ali Bailian API Key", + "Ali Bailian API Key Help": "Enter your Ali Bailian API Key. After saving, it will be written to the local config.toml file.", + "Upload media to transcribe": "Upload audio/video to transcribe", + "Transcribe subtitles": "Transcribe Subtitles", + "Please enter Ali Bailian API Key": "Please enter the Ali Bailian API Key first", + "Please upload media to transcribe": "Please upload the audio or video file to transcribe first", + "Transcribing with Fun-ASR...": "Transcribing subtitles with Ali Bailian Fun-ASR, please wait...", + "Fun-ASR failed without subtitle file": "Fun-ASR transcription failed: no subtitle file was generated", + "Subtitle transcription succeeded": "Subtitle transcription succeeded: {file}", + "Fun-ASR transcription failed": "Fun-ASR transcription failed", + "Validating script format...": "Validating script format...", + "Script format validation failed": "Script format validation failed", + "Error Message": "Error Message", + "Details": "Details", + "Correct script format example": "Correct script format example", + "Script format validation error": "An error occurred during script format validation", + "Script validated and saved successfully": "✅ Script format validated and saved successfully!", + "Tencent Secret ID Help": "Enter your Tencent Cloud Secret ID", + "Tencent Secret Key Help": "Enter your Tencent Cloud Secret Key", + "Tencent Service Region Help": "Select the Tencent Cloud TTS service region", + "Custom Voice": "Custom Voice", + "Select Tencent TTS Voice": "Select a Tencent Cloud TTS voice", + "Tencent Cloud TTS Voice Description": "💡 Tencent Cloud TTS Voice Notes", + "Female Voices": "Female Voices", + "Male Voices": "Male Voices", + "Tencent More Voices Notice": "💡 See the official Tencent Cloud documentation for more voices.", + "Qwen DashScope API Key Help": "Tongyi Qwen DashScope API Key", + "TTS Model Name": "TTS Model Name", + "Qwen TTS Model Help": "Qwen TTS model name, for example qwen3-tts-flash", + "Select Qwen3 TTS Voice": "Select a Qwen3 TTS voice", + "API URL": "API URL", + "IndexTTS2 API URL Help": "IndexTTS2 API service URL", + "Reference Audio Path": "Reference Audio Path", + "Reference Audio Path Help": "Reference audio file path for voice cloning (WAV format, 3-10 seconds recommended)", + "Upload Reference Audio File": "Or Upload Reference Audio File", + "Upload Reference Audio Help": "Upload a clear audio clip for voice cloning", + "Audio uploaded": "✅ Audio uploaded: {path}", + "Inference Mode": "Inference Mode", + "Standard Inference": "Standard Inference", + "Fast Inference": "Fast Inference", + "Inference Mode Help": "Standard inference has higher quality but is slower. Fast inference is faster with slightly lower quality.", + "Advanced Parameters": "🔧 Advanced Parameters", + "Sampling Temperature": "Sampling Temperature", + "Sampling Temperature Help": "Controls randomness. Higher values are more random; lower values are more deterministic.", + "Top P Help": "Probability threshold for nucleus sampling. Smaller values make results more deterministic.", + "Top K Help": "The k value for top-k sampling. 0 disables top-k.", + "Num Beams": "Num Beams", + "Num Beams Help": "Number of beams for beam search. Higher values may improve quality but slow generation.", + "Repetition Penalty": "Repetition Penalty", + "Repetition Penalty Help": "Higher values reduce repetition, but overly high values may sound unnatural.", + "Enable Sampling": "Enable Sampling", + "Enable Sampling Help": "Enable sampling for more natural speech.", + "IndexTTS2 Usage Instructions Title": "💡 IndexTTS2 Usage Instructions", + "IndexTTS2 Usage Instructions": "**Zero-shot voice cloning**\n\n1. **Prepare reference audio**: upload or specify a clear audio file (3-10 seconds recommended)\n2. **Set API URL**: make sure the IndexTTS2 service is running\n3. **Start synthesis**: the system will use the reference voice to synthesize new speech\n\n**Notes**:\n- Reference audio quality directly affects synthesis quality\n- Use clean audio without background noise when possible\n- Keep text length within a reasonable range\n- The first synthesis may take longer", + "Volcengine Access Key Help": "Volcengine Access Key", + "Volcengine Secret Key Help": "Volcengine Secret Key", + "Doubao AppID Help": "Doubao TTS application AppID", + "Doubao Token Help": "Doubao TTS application Token", + "Cluster": "Cluster", + "Doubao Cluster Help": "Business cluster. Standard voices use volcano_tts.", + "Select Doubao TTS Voice": "Select a Doubao TTS voice", + "Voice Rate Help 0.2-3.0": "Adjust voice speed (0.2-3.0)", + "Voice Volume Help 0.1-2.0": "Adjust voice volume (0.1-2.0)", + "Voice Pitch Help 0.5-1.5": "Adjust voice pitch (0.5-1.5)", + "Sentence Silence Duration": "Sentence-end Silence Duration (seconds)", + "Sentence Silence Duration Help": "Adjust sentence-end silence duration (0.0-2.0 seconds)", + "Doubao TTS API Key Application Process": "💡 Doubao TTS API Key Application Process", + "Application Steps": "Application Steps", + "Doubao TTS Step 1": "1. Open [https://console.volcengine.com/iam/keymanage](https://console.volcengine.com/iam/keymanage)", + "Doubao TTS Step 2": "2. Create a new Access Key and Secret Key", + "Doubao TTS Step 3": "3. Open [https://www.volcengine.com/product/voice-tech](https://www.volcengine.com/product/voice-tech)", + "Doubao TTS Step 4": "4. Click Start Now", + "Doubao TTS Step 5": "5. In the left API Service Center, find Speech Synthesis under Audio Generation (note: Speech Synthesis, not the speech synthesis large model)", + "Doubao TTS Step 6": "6. Scroll to the bottom to get the APPID and Access Token", + "Doubao TTS Fill Credentials Notice": "💡 Fill the Access Key, Secret Key, AppID, and Token above.", + "Doubao TTS configured": "✅ Doubao TTS is configured", + "Please configure missing fields": "⚠️ Please configure: {fields}", + "Preview Voice Synthesis": "🎵 Preview Voice Synthesis", + "Voice Preview Sample": "Thanks for using NarratoAI. If you have any questions or suggestions, please join the community for help and discussion.", + "Please configure voice settings first": "Please configure voice settings first", + "Voice synthesis successful": "✅ Voice synthesis successful!", + "Voice synthesis failed": "❌ Voice synthesis failed. Please check your configuration.", + "SoulVoice pitch not supported": "ℹ️ SoulVoice does not support pitch adjustment", + "Progress": "Progress", + "Generating script...": "Generating script...", + "Please select video file first": "Please select a video file first", + "Extracting keyframes...": "Extracting keyframes...", + "Script generation completed": "Script generation completed", + "Script generation completed!": "Script generation completed!", + "Video script generated successfully": "✅ Video script generated successfully!", + "Generation error": "❌ An error occurred during generation", + "Please upload subtitle file first": "Please upload a subtitle file first", + "Video": "Video", + "Subtitle": "Subtitle", + "Preparing script generation": "Preparing script generation", + "Script generation failed check logs": "Script generation failed. Please check the logs.", + "Parsing subtitles...": "Parsing subtitles...", + "Subtitle file does not exist": "Subtitle file does not exist", + "Subtitle file is empty or unreadable": "Subtitle file is empty or unreadable", + "Generating narration copy...": "Generating narration copy...", + "Generated narration JSON parse failed": "The generated narration format is invalid and could not be parsed as JSON", + "Generated narration missing items field": "The generated narration is missing the required 'items' field", + "Preparing output...": "Preparing output..." } -} \ No newline at end of file +} diff --git a/webui/i18n/zh.json b/webui/i18n/zh.json index f9a2da1..4c732cc 100644 --- a/webui/i18n/zh.json +++ b/webui/i18n/zh.json @@ -163,6 +163,233 @@ "Batch Size (More keyframes consume more tokens)": "批处理大小, 每批处理越少消耗 token 越多", "Short Drama Summary": "短剧解说", "Video Type": "视频类型", - "Select/Upload Script": "选择/上传脚本" + "Select/Upload Script": "选择/上传脚本", + "Script loaded successfully": "脚本加载成功", + "Failed to load script": "加载脚本失败", + "Failed to save script": "保存脚本失败", + "QwenVL model returned invalid response": "QwenVL 模型返回了无效响应", + "Testing connection...": "正在测试连接...", + "Connection failed": "连接失败", + "Subtitle TTS support notice": "💡 提示:目前仅 **edge-tts** 引擎支持自动生成字幕,其他 TTS 引擎暂不支持。", + "TTS engine does not support precise subtitles": "⚠️ {engine} 不支持精确字幕生成", + "Manual subtitle editing recommendation": "💡 建议使用专业剪辑工具(如剪映、PR 等)手动添加字幕", + "Disabled subtitles help": "当前 TTS 引擎不支持字幕生成,请使用其他 TTS 引擎", + "Tencent Cloud TTS": "腾讯云 TTS", + "Tongyi Qwen3 TTS": "通义千问 Qwen3 TTS", + "IndexTTS2 Voice Clone": "IndexTTS2 语音克隆", + "Doubao TTS": "豆包语音 TTS", + "Edge TTS features": "完全免费,但服务稳定性一般,不支持语音克隆功能", + "Edge TTS use case": "测试和轻量级使用", + "Azure Speech Services features": "提供一定免费额度,超出后按量付费,需要绑定海外信用卡", + "Azure Speech Services use case": "企业级应用,需要稳定服务", + "Tencent Cloud TTS features": "提供免费额度,音质优秀,支持多种音色,国内访问速度快", + "Tencent Cloud TTS use case": "个人和企业用户,需要稳定的中文语音合成", + "Tongyi Qwen3 TTS features": "阿里云通义千问语音合成,音质优秀,支持多种音色", + "High-quality Chinese speech synthesis use case": "需要高质量中文语音合成的用户", + "IndexTTS2 features": "零样本语音克隆,上传参考音频即可合成相同音色的语音,需要本地或私有部署", + "IndexTTS2 download link": "下载地址:https://pan.quark.cn/s/0767c9bcefd5", + "Doubao TTS features": "火山引擎豆包语音合成,支持多种音色和情感,国内访问速度快", + "Select TTS Engine": "选择 TTS 引擎", + "Select TTS Engine Help": "选择您要使用的文本转语音引擎", + "TTS Engine Details": "📋 {engine} 详细说明", + "Features": "特点", + "Use Case": "适用场景", + "Registration URL": "注册地址", + "Voice Selection": "音色选择", + "Select Edge TTS Voice": "选择 Edge TTS 音色", + "Edge TTS Voice Description": "💡 Edge TTS 音色说明", + "Loaded voice count": "已加载 {count} 个音色", + "Female Voice": "女声", + "Male Voice": "男声", + "Voice Volume": "音量调节", + "Voice Volume Help Percent": "调节语音音量 (0-100)", + "Voice Rate": "语速调节", + "Voice Rate Help 0.5-2.0": "调节语音速度 (0.5-2.0 倍速)", + "Voice Pitch": "语调调节", + "Voice Pitch Help Percent": "调节语音音调 (-50% 到 +50%)", + "Service Region": "服务区域", + "Service Region Placeholder": "例如:eastus", + "Azure Service Region Help": "Azure Speech Services 服务区域,如:eastus、westus2、eastasia 等", + "Azure Speech Key Help": "Azure Speech Services API 密钥", + "Voice Name": "音色名称", + "Azure Voice Name Help": "输入 Azure Speech Services 音色名称,直接使用官方音色名称即可。例如:zh-CN-YunzeNeural", + "Common Voice Reference": "💡 常用音色参考", + "Chinese Voices": "中文音色", + "English Voices": "英文音色", + "Multilingual": "多语言", + "Azure Voices Docs Notice": "💡 更多音色请参考 [Azure Speech Services 官方文档](https://docs.microsoft.com/en-us/azure/cognitive-services/speech-service/language-support)", + "Quick Select": "快速选择", + "Chinese Female Voice": "中文女声", + "Chinese Male Voice": "中文男声", + "English Female Voice": "英文女声", + "Voice name valid": "✅ 音色名称有效: {voice}", + "Voice name format may be invalid": "⚠️ 音色名称格式可能不正确: {voice}", + "Azure voice name format notice": "💡 Azure 音色名称通常格式为: [语言]-[地区]-[名称]Neural", + "Azure Speech Services configured": "✅ Azure Speech Services 配置已设置", + "Please configure service region": "⚠️ 请配置服务区域", + "Please configure API Key": "⚠️ 请配置 API Key", + "Language": "界面语言", + "Task failed": "任务失败", + "Script file cannot be empty": "脚本文件不能为空", + "Video file cannot be empty": "视频文件不能为空", + "Export to Jianying Draft": "📤 导出到剪映草稿", + "Please configure Jianying draft folder in basic settings": "请在基础设置中配置剪映草稿地址", + "Jianying draft folder does not exist": "剪映草稿文件夹不存在: {path}", + "Please enter Jianying draft name": "请输入剪映草稿名称", + "Confirm Export": "确认导出", + "Please enter draft name": "请输入草稿名称", + "Failed to build parameters": "参数构建失败", + "Exporting to Jianying draft...": "正在导出到剪映草稿,请稍候...", + "Jianying draft exported successfully": "✅ 成功导出到剪映草稿: {name}", + "Draft saved to": "📁 草稿已保存到: {path}", + "Failed to export Jianying draft": "❌ 导出到剪映草稿失败", + "Cancel": "取消", + "LLM initialization failed": "⚠️ LLM 初始化失败: {error}\n\n请检查配置文件和依赖是否正确安装。", + "Jianying Draft Settings": "剪映草稿设置", + "Jianying Draft Folder Path": "剪映草稿文件夹路径", + "Jianying Draft Folder Path Help": "剪映草稿文件夹路径,例如:C:\\Users\\用户名\\Documents\\JianyingPro Drafts", + "Custom API endpoint help": "自定义 API 端点(可选),当使用自建或第三方代理时需要填写", + "Recommended API endpoint": "推荐接口地址", + "OpenAI compatible gateway help": "{model_type} 选择的提供商基于 OpenAI 兼容网关,必须填写完整的接口地址。", + "Vision model": "视频分析模型", + "Text model": "文案生成模型", + "Model Name Input Help": "输入完整模型名称\n\n常用示例:", + "OpenAI compatible providers help": "支持常见 OpenAI 兼容网关(如 OpenAI/DeepSeek/OpenRouter/SiliconFlow)", + "Provider API Key Help": "对应 provider 的 API 密钥\n\n获取地址:", + "Please fill OpenAI compatible gateway": "请在上方填写 OpenAI 兼容网关地址,例如:{example}", + "Please enter API key": "请先输入 API 密钥", + "Please enter model name": "请先输入模型名称", + "Connection test error": "测试连接时发生错误", + "Vision model config saved": "视频分析模型配置已保存(OpenAI 兼容)", + "Text model config saved": "文案生成模型配置已保存(OpenAI 兼容)", + "Failed to save config": "保存配置失败", + "Custom Position (% from top)": "自定义位置(距顶部百分比)", + "Please enter a value between 0 and 100": "请输入 0 到 100 之间的值", + "Please enter a valid number": "请输入有效数字", + "None": "无", + "Uploaded subtitle": "已上传字幕: {file}", + "Encoding": "编码", + "Size": "大小", + "Characters": "字符", + "Ali Bailian Fun-ASR Subtitle Transcription": "阿里百炼 Fun-ASR 字幕转录", + "Fun-ASR upload caption": "上传本地音频/视频后,将自动上传到阿里百炼临时存储并通过 fun-asr 生成 SRT 字幕。", + "API Key URL": "API Key 获取地址", + "Ali Bailian API Key": "阿里百炼 API Key", + "Ali Bailian API Key Help": "请输入你自己的阿里百炼 API Key;保存配置后会写入本地 config.toml", + "Upload media to transcribe": "上传需要转录的音频/视频", + "Transcribe subtitles": "转写生成字幕", + "Please enter Ali Bailian API Key": "请先输入阿里百炼 API Key", + "Please upload media to transcribe": "请先上传需要转录的音频或视频文件", + "Transcribing with Fun-ASR...": "正在使用阿里百炼 Fun-ASR 转写字幕,请稍候...", + "Fun-ASR failed without subtitle file": "Fun-ASR 转写失败:未生成字幕文件", + "Subtitle transcription succeeded": "字幕转写成功: {file}", + "Fun-ASR transcription failed": "Fun-ASR 字幕转写失败", + "Validating script format...": "正在验证脚本格式...", + "Script format validation failed": "脚本格式验证失败", + "Error Message": "错误信息", + "Details": "详细说明", + "Correct script format example": "正确的脚本格式示例", + "Script format validation error": "格式验证过程中发生错误", + "Script validated and saved successfully": "✅ 脚本格式验证通过,保存成功!", + "Tencent Secret ID Help": "请输入您的腾讯云 Secret ID", + "Tencent Secret Key Help": "请输入您的腾讯云 Secret Key", + "Tencent Service Region Help": "选择腾讯云 TTS 服务地域", + "Custom Voice": "自定义音色", + "Select Tencent TTS Voice": "选择腾讯云 TTS 音色", + "Tencent Cloud TTS Voice Description": "💡 腾讯云 TTS 音色说明", + "Female Voices": "女声音色", + "Male Voices": "男声音色", + "Tencent More Voices Notice": "💡 更多音色请参考腾讯云官方文档", + "Qwen DashScope API Key Help": "通义千问 DashScope API Key", + "TTS Model Name": "模型名称", + "Qwen TTS Model Help": "Qwen TTS 模型名,例如 qwen3-tts-flash", + "Select Qwen3 TTS Voice": "选择 Qwen3 TTS 音色", + "API URL": "API 地址", + "IndexTTS2 API URL Help": "IndexTTS2 API 服务地址", + "Reference Audio Path": "参考音频路径", + "Reference Audio Path Help": "用于语音克隆的参考音频文件路径(WAV 格式,建议 3-10 秒)", + "Upload Reference Audio File": "或上传参考音频文件", + "Upload Reference Audio Help": "上传一段清晰的音频用于语音克隆", + "Audio uploaded": "✅ 音频已上传: {path}", + "Inference Mode": "推理模式", + "Standard Inference": "普通推理", + "Fast Inference": "快速推理", + "Inference Mode Help": "普通推理质量更高但速度较慢,快速推理速度更快但质量略低", + "Advanced Parameters": "🔧 高级参数", + "Sampling Temperature": "采样温度 (Temperature)", + "Sampling Temperature Help": "控制随机性,值越高输出越随机,值越低越确定", + "Top P Help": "nucleus 采样的概率阈值,值越小结果越确定", + "Top K Help": "top-k 采样的 k 值,0 表示不使用 top-k", + "Num Beams": "束搜索 (Num Beams)", + "Num Beams Help": "束搜索的 beam 数量,值越大质量可能越好但速度越慢", + "Repetition Penalty": "重复惩罚 (Repetition Penalty)", + "Repetition Penalty Help": "值越大越能避免重复,但过大可能导致不自然", + "Enable Sampling": "启用采样", + "Enable Sampling Help": "启用采样可以获得更自然的语音", + "IndexTTS2 Usage Instructions Title": "💡 IndexTTS2 使用说明", + "IndexTTS2 Usage Instructions": "**零样本语音克隆**\n\n1. **准备参考音频**:上传或指定一段清晰的音频文件(建议 3-10 秒)\n2. **设置 API 地址**:确保 IndexTTS2 服务正常运行\n3. **开始合成**:系统会自动使用参考音频的音色合成新语音\n\n**注意事项**:\n- 参考音频质量直接影响合成效果\n- 建议使用无背景噪音的清晰音频\n- 文本长度建议控制在合理范围内\n- 首次合成可能需要较长时间", + "Volcengine Access Key Help": "火山引擎 Access Key", + "Volcengine Secret Key Help": "火山引擎 Secret Key", + "Doubao AppID Help": "豆包语音应用 AppID", + "Doubao Token Help": "豆包语音应用 Token", + "Cluster": "集群", + "Doubao Cluster Help": "业务集群,标准音色使用 volcano_tts", + "Select Doubao TTS Voice": "选择豆包语音 TTS 音色", + "Voice Rate Help 0.2-3.0": "调节语音速度 (0.2-3.0)", + "Voice Volume Help 0.1-2.0": "调节语音音量 (0.1-2.0)", + "Voice Pitch Help 0.5-1.5": "调节语音音高 (0.5-1.5)", + "Sentence Silence Duration": "句尾静音时长 (秒)", + "Sentence Silence Duration Help": "调节句尾静音时长 (0.0-2.0 秒)", + "Doubao TTS API Key Application Process": "💡 豆包语音 TTS API Key申请流程", + "Application Steps": "申请步骤", + "Doubao TTS Step 1": "1. 打开 [https://console.volcengine.com/iam/keymanage](https://console.volcengine.com/iam/keymanage)", + "Doubao TTS Step 2": "2. 新建 Access Key 和 Secret Key", + "Doubao TTS Step 3": "3. 打开 [https://www.volcengine.com/product/voice-tech](https://www.volcengine.com/product/voice-tech)", + "Doubao TTS Step 4": "4. 点击立即使用", + "Doubao TTS Step 5": "5. 在最左边的 API 服务中心找到音频生成下面的语音合成(注意:是语音合成,不是语音合成大模型)", + "Doubao TTS Step 6": "6. 翻到最下面获取 APPID 和 Access Token", + "Doubao TTS Fill Credentials Notice": "💡 请将获取到的 Access Key、Secret Key、AppID 和 Token 填写到上方的配置中", + "Doubao TTS configured": "✅ 豆包语音 TTS 配置已设置", + "Please configure missing fields": "⚠️ 请配置: {fields}", + "Preview Voice Synthesis": "🎵 试听语音合成", + "Voice Preview Sample": "感谢关注 NarratoAI,有任何问题或建议,可以加入社区频道求助或讨论", + "Please configure voice settings first": "请先配置语音设置", + "Voice synthesis successful": "✅ 语音合成成功!", + "Voice synthesis failed": "❌ 语音合成失败,请检查配置", + "SoulVoice pitch not supported": "ℹ️ SoulVoice 引擎不支持音调调节", + "上传字幕文件": "上传字幕文件", + "清除已上传字幕": "清除已上传字幕", + "无法读取字幕文件,请检查文件编码(支持 UTF-8、UTF-16、GBK、GB2312)": "无法读取字幕文件,请检查文件编码(支持 UTF-8、UTF-16、GBK、GB2312)", + "字幕文件内容似乎为空,请检查文件": "字幕文件内容似乎为空,请检查文件", + "字幕上传成功": "字幕上传成功", + "短剧名称": "短剧名称", + "生成短剧解说脚本": "生成短剧解说脚本", + "请输入视频脚本": "请输入视频脚本", + "自定义片段": "自定义片段", + "设置需要生成的短视频片段数量": "设置需要生成的短视频片段数量", + "原生Gemini模型连接成功": "原生 Gemini 模型连接成功", + "原生Gemini模型连接失败": "原生 Gemini 模型连接失败", + "OpenAI兼容Gemini代理连接成功": "OpenAI 兼容 Gemini 代理连接成功", + "OpenAI兼容Gemini代理连接失败": "OpenAI 兼容 Gemini 代理连接失败", + "Progress": "进度", + "Generating script...": "正在生成脚本...", + "Please select video file first": "请先选择视频文件", + "Extracting keyframes...": "正在提取关键帧...", + "Script generation completed": "脚本生成完成", + "Script generation completed!": "🎉 脚本生成完成!", + "Video script generated successfully": "✅ 视频脚本生成成功!", + "Generation error": "❌ 生成过程中发生错误", + "Please upload subtitle file first": "请先上传字幕文件", + "Video": "视频", + "Subtitle": "字幕", + "Preparing script generation": "开始准备生成脚本", + "Script generation failed check logs": "生成脚本失败,请检查日志", + "Parsing subtitles...": "正在解析字幕...", + "Subtitle file does not exist": "字幕文件不存在", + "Subtitle file is empty or unreadable": "字幕文件内容为空或无法读取", + "Generating narration copy...": "正在生成文案...", + "Generated narration JSON parse failed": "生成的解说文案格式错误,无法解析为 JSON", + "Generated narration missing items field": "生成的解说文案缺少必要的 'items' 字段", + "Preparing output...": "整理输出..." } -} \ No newline at end of file +} diff --git a/webui/tools/generate_script_docu.py b/webui/tools/generate_script_docu.py index b366156..9f6052b 100644 --- a/webui/tools/generate_script_docu.py +++ b/webui/tools/generate_script_docu.py @@ -24,7 +24,7 @@ def _normalize_progress_value(progress: float | int) -> int: return max(0, min(100, int(round(value)))) -def generate_script_docu(params): +def generate_script_docu(params, tr=lambda key: key): """ 生成纪录片视频脚本。 要求: 原视频无字幕无配音 @@ -39,12 +39,12 @@ def generate_script_docu(params): if message: status_text.text(f"🎬 {message}") else: - status_text.text(f"📊 进度: {normalized_progress}%") + status_text.text(f"📊 {tr('Progress')}: {normalized_progress}%") try: - with st.spinner("正在生成脚本..."): + with st.spinner(tr("Generating script...")): if not params.video_origin_path: - st.error("请先选择视频文件") + st.error(tr("Please select video file first")) return vision_llm_provider = ( @@ -76,7 +76,7 @@ def generate_script_docu(params): "vision_max_concurrency", 2 ) - update_progress(10, "正在提取关键帧...") + update_progress(10, tr("Extracting keyframes...")) service = DocumentaryFrameAnalysisService() script_items = asyncio.run( service.generate_documentary_script( @@ -100,15 +100,15 @@ def generate_script_docu(params): st.session_state["video_clip_json"] = script elif isinstance(script, str): st.session_state["video_clip_json"] = json.loads(script) - update_progress(100, "脚本生成完成") + update_progress(100, tr("Script generation completed")) time.sleep(0.1) progress_bar.progress(100) - status_text.text("🎉 脚本生成完成!") - st.success("✅ 视频脚本生成成功!") + status_text.text(tr("Script generation completed!")) + st.success(tr("Video script generated successfully")) except Exception as err: - st.error(f"❌ 生成过程中发生错误: {str(err)}") + st.error(f"{tr('Generation error')}: {str(err)}") logger.exception(f"生成脚本时发生错误\n{traceback.format_exc()}") finally: time.sleep(2) diff --git a/webui/tools/generate_script_short.py b/webui/tools/generate_script_short.py index 2f6ef9b..5c7b43d 100644 --- a/webui/tools/generate_script_short.py +++ b/webui/tools/generate_script_short.py @@ -27,21 +27,21 @@ def generate_script_short(tr, params, custom_clips=5): if message: status_text.text(f"{progress}% - {message}") else: - status_text.text(f"进度: {progress}%") + status_text.text(f"{tr('Progress')}: {progress}%") try: - with st.spinner("正在生成脚本..."): + with st.spinner(tr("Generating script...")): # ========== 严格验证:必须上传视频和字幕(与短剧解说保持一致)========== # 1. 验证视频文件 video_path = getattr(params, "video_origin_path", None) if not video_path or not str(video_path).strip(): - st.error("请先选择视频文件") + st.error(tr("Please select video file first")) st.stop() try: ensure_existing_file( str(video_path), - label="视频", + label=tr("Video"), allowed_exts=(".mp4", ".mov", ".avi", ".flv", ".mkv"), ) except InputValidationError as e: @@ -51,13 +51,13 @@ def generate_script_short(tr, params, custom_clips=5): # 2. 验证字幕文件(移除推断逻辑,必须上传) subtitle_path = st.session_state.get("subtitle_path") if not subtitle_path or not str(subtitle_path).strip(): - st.error("请先上传字幕文件") + st.error(tr("Please upload subtitle file first")) st.stop() try: subtitle_path = ensure_existing_file( str(subtitle_path), - label="字幕", + label=tr("Subtitle"), allowed_exts=(".srt",), ) except InputValidationError as e: @@ -78,7 +78,7 @@ def generate_script_short(tr, params, custom_clips=5): vision_model = st.session_state.get(f'vision_{vision_llm_provider}_model_name') or config.app.get(f'vision_{vision_llm_provider}_model_name', "") vision_base_url = st.session_state.get(f'vision_{vision_llm_provider}_base_url') or config.app.get(f'vision_{vision_llm_provider}_base_url', "") - update_progress(20, "开始准备生成脚本") + update_progress(20, tr("Preparing script generation")) # ========== 调用后端生成脚本 ========== from app.services.SDP.generate_script_short import generate_script_result @@ -103,7 +103,7 @@ def generate_script_short(tr, params, custom_clips=5): ) if result.get("status") != "success": - st.error(result.get("message", "生成脚本失败,请检查日志")) + st.error(result.get("message", tr("Script generation failed check logs"))) st.stop() script = result.get("script") @@ -114,14 +114,14 @@ def generate_script_short(tr, params, custom_clips=5): elif isinstance(script, str): st.session_state['video_clip_json'] = json.loads(script) - update_progress(80, "脚本生成完成") + update_progress(80, tr("Script generation completed")) time.sleep(0.1) progress_bar.progress(100) - status_text.text("脚本生成完成!") - st.success("视频脚本生成成功!") + status_text.text(tr("Script generation completed!")) + st.success(tr("Video script generated successfully")) except Exception as err: progress_bar.progress(100) - st.error(f"生成过程中发生错误: {str(err)}") + st.error(f"{tr('Generation error')}: {str(err)}") logger.exception(f"生成脚本时发生错误\n{traceback.format_exc()}") diff --git a/webui/tools/generate_short_summary.py b/webui/tools/generate_short_summary.py index 0ead867..fe2d223 100644 --- a/webui/tools/generate_short_summary.py +++ b/webui/tools/generate_short_summary.py @@ -135,7 +135,7 @@ def parse_and_fix_json(json_string): return None -def generate_script_short_sunmmary(params, subtitle_path, video_theme, temperature): +def generate_script_short_sunmmary(params, subtitle_path, video_theme, temperature, tr=lambda key: key): """ 生成 短剧解说 视频脚本 要求: 提供高质量短剧字幕 @@ -149,20 +149,20 @@ def generate_script_short_sunmmary(params, subtitle_path, video_theme, temperatu if message: status_text.text(f"{progress}% - {message}") else: - status_text.text(f"进度: {progress}%") + status_text.text(f"{tr('Progress')}: {progress}%") try: - with st.spinner("正在生成脚本..."): + with st.spinner(tr("Generating script...")): if not params.video_origin_path: - st.error("请先选择视频文件") + st.error(tr("Please select video file first")) return """ 1. 获取字幕 """ - update_progress(30, "正在解析字幕...") + update_progress(30, tr("Parsing subtitles...")) # 判断字幕文件是否存在 if not os.path.exists(subtitle_path): - st.error("字幕文件不存在") + st.error(tr("Subtitle file does not exist")) return """ @@ -176,7 +176,7 @@ def generate_script_short_sunmmary(params, subtitle_path, video_theme, temperatu # 读取字幕文件内容(无论使用哪种实现都需要) subtitle_content = read_subtitle_text(subtitle_path).text if not subtitle_content: - st.error("字幕文件内容为空或无法读取") + st.error(tr("Subtitle file is empty or unreadable")) return try: @@ -203,7 +203,7 @@ def generate_script_short_sunmmary(params, subtitle_path, video_theme, temperatu """ if analysis_result["status"] == "success": logger.info("字幕分析成功!") - update_progress(60, "正在生成文案...") + update_progress(60, tr("Generating narration copy...")) # 根据剧情生成解说文案 - 使用新的LLM服务架构 try: @@ -235,11 +235,11 @@ def generate_script_short_sunmmary(params, subtitle_path, video_theme, temperatu logger.info(narration_result["narration_script"]) else: logger.info(f"\n解说文案生成失败: {narration_result['message']}") - st.error("生成脚本失败,请检查日志") + st.error(tr("Script generation failed check logs")) st.stop() else: logger.error(f"分析失败: {analysis_result['message']}") - st.error("生成脚本失败,请检查日志") + st.error(tr("Script generation failed check logs")) st.stop() """ @@ -253,35 +253,35 @@ def generate_script_short_sunmmary(params, subtitle_path, video_theme, temperatu # 增强JSON解析,包含错误处理和修复 narration_dict = parse_and_fix_json(narration_script) if narration_dict is None: - st.error("生成的解说文案格式错误,无法解析为JSON") + st.error(tr("Generated narration JSON parse failed")) logger.error(f"JSON解析失败,原始内容: {narration_script}") st.stop() # 验证JSON结构 if 'items' not in narration_dict: - st.error("生成的解说文案缺少必要的'items'字段") + st.error(tr("Generated narration missing items field")) logger.error(f"JSON结构错误,缺少items字段: {narration_dict}") st.stop() script = json.dumps(narration_dict['items'], ensure_ascii=False, indent=2) if script is None: - st.error("生成脚本失败,请检查日志") + st.error(tr("Script generation failed check logs")) st.stop() logger.success(f"剪辑脚本生成完成") if isinstance(script, list): st.session_state['video_clip_json'] = script elif isinstance(script, str): st.session_state['video_clip_json'] = json.loads(script) - update_progress(90, "整理输出...") + update_progress(90, tr("Preparing output...")) time.sleep(0.1) progress_bar.progress(100) - status_text.text("脚本生成完成!") - st.success("视频脚本生成成功!") + status_text.text(tr("Script generation completed!")) + st.success(tr("Video script generated successfully")) except Exception as err: - st.error(f"生成过程中发生错误: {str(err)}") + st.error(f"{tr('Generation error')}: {str(err)}") logger.exception(f"生成脚本时发生错误\n{traceback.format_exc()}") finally: time.sleep(2)