feat(webui): 完善国际化多语言适配

将webui所有页面的硬编码中文提示文本替换为多语言翻译调用,为相关函数添加兼容的tr参数,同时补充zh.json和en.json中的对应翻译词条。
This commit is contained in:
viccy 2026-06-05 16:36:03 +08:00
parent 35477a933d
commit 89eebb8b41
10 changed files with 847 additions and 310 deletions

View File

@ -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 持久化)

View File

@ -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):

View File

@ -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 特殊配置

View File

@ -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) # 给一点时间让用户看到成功消息

View File

@ -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:
# 其他引擎正常显示字幕选项

View File

@ -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..."
}
}
}

View File

@ -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...": "整理输出..."
}
}
}

View File

@ -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)

View File

@ -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()}")

View File

@ -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)