feat(edge-tts, webui): 新增动态获取音色列表功能,更新内置列表并简化UI

- 新增`get_all_edge_voices()`工具函数链,支持缓存与代理,拉取失败时回退到内置音色列表
- 更新内置音色文档与列表,修正部分命名并新增多语言神经音色及因纽特语相关音色
- 简化WebUI的Edge TTS设置界面,不再硬编码筛选音色,直接展示所有已加载的音色
This commit is contained in:
viccy 2026-06-05 15:59:54 +08:00
parent a1b434fbda
commit 35477a933d
3 changed files with 109 additions and 30 deletions

View File

@ -6,6 +6,7 @@ import edge_tts
import asyncio
import requests
import uuid
from functools import lru_cache
from loguru import logger
from typing import List, Union, Tuple
from datetime import datetime
@ -282,7 +283,7 @@ Gender: Male
Name: en-AU-NatashaNeural
Gender: Female
Name: en-AU-WilliamNeural
Name: en-AU-WilliamMultilingualNeural
Gender: Male
Name: en-CA-ClaraNeural
@ -369,21 +370,33 @@ Gender: Female
Name: en-US-AndrewNeural
Gender: Male
Name: en-US-AndrewMultilingualNeural
Gender: Male
Name: en-US-AriaNeural
Gender: Female
Name: en-US-AvaNeural
Gender: Female
Name: en-US-AvaMultilingualNeural
Gender: Female
Name: en-US-BrianNeural
Gender: Male
Name: en-US-BrianMultilingualNeural
Gender: Male
Name: en-US-ChristopherNeural
Gender: Male
Name: en-US-EmmaNeural
Gender: Female
Name: en-US-EmmaMultilingualNeural
Gender: Female
Name: en-US-EricNeural
Gender: Male
@ -666,12 +679,24 @@ Gender: Male
Name: it-IT-ElsaNeural
Gender: Female
Name: it-IT-GiuseppeNeural
Name: it-IT-GiuseppeMultilingualNeural
Gender: Male
Name: it-IT-IsabellaNeural
Gender: Female
Name: iu-Cans-CA-SiqiniqNeural
Gender: Female
Name: iu-Cans-CA-TaqqiqNeural
Gender: Male
Name: iu-Latn-CA-SiqiniqNeural
Gender: Female
Name: iu-Latn-CA-TaqqiqNeural
Gender: Male
Name: ja-JP-KeitaNeural
Gender: Male
@ -708,7 +733,7 @@ Gender: Male
Name: kn-IN-SapnaNeural
Gender: Female
Name: ko-KR-HyunsuNeural
Name: ko-KR-HyunsuMultilingualNeural
Gender: Male
Name: ko-KR-InJoonNeural
@ -822,7 +847,7 @@ Gender: Male
Name: pt-BR-FranciscaNeural
Gender: Female
Name: pt-BR-ThalitaNeural
Name: pt-BR-ThalitaMultilingualNeural
Gender: Female
Name: pt-PT-DuarteNeural
@ -1306,6 +1331,52 @@ def get_edge_tts_proxy() -> str | None:
return proxy_url or None
def _run_async_safely(coro_func, *args, **kwargs):
"""在同步代码里安全运行异步 edge_tts 调用。"""
def run_in_new_loop():
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
try:
return loop.run_until_complete(coro_func(*args, **kwargs))
finally:
loop.close()
asyncio.set_event_loop(None)
try:
asyncio.get_running_loop()
except RuntimeError:
return run_in_new_loop()
import concurrent.futures
with concurrent.futures.ThreadPoolExecutor(max_workers=1) as executor:
return executor.submit(run_in_new_loop).result()
@lru_cache(maxsize=8)
def _get_all_edge_voices_cached(proxy: str | None) -> list[str]:
async def _list_voices():
return await edge_tts.list_voices(proxy=proxy)
voices = []
for item in _run_async_safely(_list_voices):
name = item.get("ShortName", "").strip()
gender = item.get("Gender", "").strip()
if name and gender:
voices.append(f"{name}-{gender}")
voices.sort()
return voices
def get_all_edge_voices() -> list[str]:
"""获取 Edge TTS 当前支持的全部语言和音色,失败时回退到内置列表。"""
try:
return _get_all_edge_voices_cached(get_edge_tts_proxy())
except Exception as e:
logger.warning(f"获取 Edge TTS 在线音色列表失败,使用内置音色列表: {e}")
return [v for v in get_all_azure_voices(filter_locals=[]) if "-V2" not in v]
def azure_tts_v1(
text: str, voice_name: str, voice_rate: float, voice_pitch: float, voice_file: str
) -> Union[SubMaker, None]:

View File

@ -199,7 +199,7 @@ Gender: Male
Name: en-AU-NatashaNeural
Gender: Female
Name: en-AU-WilliamNeural
Name: en-AU-WilliamMultilingualNeural
Gender: Male
Name: en-CA-ClaraNeural
@ -286,21 +286,33 @@ Gender: Female
Name: en-US-AndrewNeural
Gender: Male
Name: en-US-AndrewMultilingualNeural
Gender: Male
Name: en-US-AriaNeural
Gender: Female
Name: en-US-AvaNeural
Gender: Female
Name: en-US-AvaMultilingualNeural
Gender: Female
Name: en-US-BrianNeural
Gender: Male
Name: en-US-BrianMultilingualNeural
Gender: Male
Name: en-US-ChristopherNeural
Gender: Male
Name: en-US-EmmaNeural
Gender: Female
Name: en-US-EmmaMultilingualNeural
Gender: Female
Name: en-US-EricNeural
Gender: Male
@ -583,12 +595,24 @@ Gender: Male
Name: it-IT-ElsaNeural
Gender: Female
Name: it-IT-GiuseppeNeural
Name: it-IT-GiuseppeMultilingualNeural
Gender: Male
Name: it-IT-IsabellaNeural
Gender: Female
Name: iu-Cans-CA-SiqiniqNeural
Gender: Female
Name: iu-Cans-CA-TaqqiqNeural
Gender: Male
Name: iu-Latn-CA-SiqiniqNeural
Gender: Female
Name: iu-Latn-CA-TaqqiqNeural
Gender: Male
Name: ja-JP-KeitaNeural
Gender: Male
@ -625,7 +649,7 @@ Gender: Male
Name: kn-IN-SapnaNeural
Gender: Female
Name: ko-KR-HyunsuNeural
Name: ko-KR-HyunsuMultilingualNeural
Gender: Male
Name: ko-KR-InJoonNeural
@ -739,7 +763,7 @@ Gender: Male
Name: pt-BR-FranciscaNeural
Gender: Female
Name: pt-BR-ThalitaNeural
Name: pt-BR-ThalitaMultilingualNeural
Gender: Female
Name: pt-PT-DuarteNeural

View File

@ -163,12 +163,8 @@ def render_tts_settings(tr):
def render_edge_tts_settings(tr):
"""渲染 Edge TTS 引擎设置"""
# 获取支持的语音列表
support_locales = ["zh-CN", "en-US"]
all_voices = voice.get_all_azure_voices(filter_locals=support_locales)
# 只保留标准版本的语音Edge TTS专用不包含V2
edge_voices = [v for v in all_voices if "-V2" not in v]
# 获取 Edge TTS 支持的全部语言和音色
edge_voices = voice.get_all_edge_voices()
# 创建友好的显示名称
friendly_names = {}
@ -189,7 +185,7 @@ def render_edge_tts_settings(tr):
# 如果没找到匹配的,使用第一个
saved_voice_name = edge_voices[0] if edge_voices else ""
# 音色选择下拉框Edge TTS音色相对较少保留下拉框
# 音色选择下拉框
selected_friendly_name = st.selectbox(
"音色选择",
options=list(friendly_names.values()),
@ -204,24 +200,12 @@ def render_edge_tts_settings(tr):
# 显示音色信息
with st.expander("💡 Edge TTS 音色说明", expanded=False):
st.write("**中文音色:**")
zh_voices = [v for v in edge_voices if v.startswith("zh-CN")]
for v in zh_voices:
st.write(f"已加载 {len(edge_voices)} 个音色")
for v in edge_voices:
gender = "女声" if "Female" in v else "男声"
name = v.replace("-Female", "").replace("-Male", "").replace("zh-CN-", "").replace("Neural", "")
name = v.replace("-Female", "").replace("-Male", "").replace("Neural", "")
st.write(f"{name} ({gender})")
st.write("")
st.write("**英文音色:**")
en_voices = [v for v in edge_voices if v.startswith("en-US")][:5] # 只显示前5个
for v in en_voices:
gender = "女声" if "Female" in v else "男声"
name = v.replace("-Female", "").replace("-Male", "").replace("en-US-", "").replace("Neural", "")
st.write(f"{name} ({gender})")
if len([v for v in edge_voices if v.startswith("en-US")]) > 5:
st.write("• ... 更多英文音色")
config.ui["edge_voice_name"] = voice_name
config.ui["voice_name"] = voice_name # 兼容性