mirror of
https://github.com/linyqh/NarratoAI.git
synced 2026-07-29 09:25:52 +00:00
feat(webui): 完善国际化多语言适配
将webui所有页面的硬编码中文提示文本替换为多语言翻译调用,为相关函数添加兼容的tr参数,同时补充zh.json和en.json中的对应翻译词条。
This commit is contained in:
parent
35477a933d
commit
89eebb8b41
42
webui.py
42
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 持久化)
|
||||
|
||||
@ -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):
|
||||
|
||||
@ -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 特殊配置
|
||||
|
||||
@ -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) # 给一点时间让用户看到成功消息
|
||||
|
||||
@ -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:
|
||||
# 其他引擎正常显示字幕选项
|
||||
|
||||
@ -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..."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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...": "整理输出..."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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()}")
|
||||
|
||||
@ -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)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user