Skip to content

Commit 8f761ea

Browse files
committed
语音播报基础功能
1 parent 16ed754 commit 8f761ea

5 files changed

Lines changed: 212 additions & 218 deletions

File tree

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,3 +191,4 @@ cython_debug/
191191
/data/history
192192
/data/list
193193
/data/audio
194+
/data/TEMP

app/Language/modules/voice_settings.py

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,6 @@
1212
"description": "选择语音合成引擎类型",
1313
},
1414
"volume_group": {"name": "音量设置", "description": "调整语音播报音量大小"},
15-
"system_volume_group": {
16-
"name": "系统音量控制",
17-
"description": "选择要控制的系统音量类型",
18-
},
1915
"voice_engine": {
2016
"name": "语音引擎",
2117
"description": "选择语音合成引擎类型",
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
# ==================================================
2+
# 导入库
3+
# ==================================================
4+
import asyncio
5+
import edge_tts
6+
from loguru import logger
7+
from PySide6.QtCore import QThread, Signal
8+
9+
10+
class EdgeTTSWorker(QThread):
11+
"""Edge TTS语音列表获取线程"""
12+
13+
voices_fetched = Signal(list)
14+
error_occurred = Signal(str)
15+
16+
def run(self):
17+
"""运行线程,获取Edge TTS语音列表"""
18+
try:
19+
# 获取语音列表
20+
voices = self.get_voices()
21+
self.voices_fetched.emit(voices)
22+
except Exception as e:
23+
logger.error(f"获取Edge TTS语音列表失败: {e}")
24+
self.error_occurred.emit(str(e))
25+
26+
def get_voices(self):
27+
"""获取Edge TTS语音列表"""
28+
try:
29+
# 同步获取语音列表
30+
loop = asyncio.new_event_loop()
31+
asyncio.set_event_loop(loop)
32+
voices = loop.run_until_complete(edge_tts.list_voices())
33+
loop.close()
34+
35+
logger.debug(f"Edge TTS API返回原始语音数量: {len(voices)}")
36+
37+
# 调试:打印前几个语音的结构
38+
if voices:
39+
logger.debug(f"第一个语音的结构: {voices[0]}")
40+
logger.debug(f"第一个语音的键: {list(voices[0].keys())}")
41+
42+
# 简化过滤逻辑
43+
filtered_voices = []
44+
total_processed = 0
45+
passed_filter = 0
46+
47+
for v in voices:
48+
try:
49+
total_processed += 1
50+
# 检查必要字段 - 注意:API返回的是Name而不是FriendlyName
51+
if "Name" in v and "ShortName" in v and "Locale" in v:
52+
passed_filter += 1
53+
name = v["Name"] # 使用Name字段
54+
display_name = v.get("DisplayName", name) # 优先使用DisplayName
55+
short_name = v["ShortName"]
56+
locale = v["Locale"]
57+
gender = v.get("Gender", "Unknown")
58+
voice_type = v.get("VoiceType", "Unknown")
59+
60+
# 直接使用short_name作为ID
61+
voice_id = short_name
62+
63+
filtered_voices.append(
64+
{
65+
"name": display_name, # 使用更友好的显示名称
66+
"id": voice_id,
67+
"languages": locale.replace("_", "-"),
68+
"full_info": f"{gender} | {locale} | Type: {voice_type}",
69+
}
70+
)
71+
except Exception as e:
72+
logger.warning(f"处理语音 {v} 时出错: {e}")
73+
if total_processed <= 5: # 只显示前5个出错的语音
74+
logger.debug(f"出错语音结构: {v}")
75+
continue
76+
77+
logger.debug(f"成功获取Edge TTS语音列表,共{len(filtered_voices)}个语音")
78+
logger.debug(
79+
f"处理统计: 总共{total_processed}个, 通过过滤{passed_filter}个, 最终{len(filtered_voices)}个"
80+
)
81+
82+
# 如果过滤后列表为空,返回默认语音列表
83+
if not filtered_voices:
84+
logger.warning("过滤后语音列表为空,返回默认语音")
85+
return self.get_default_voices()
86+
87+
# 按语言排序,优先显示中文语音
88+
filtered_voices.sort(
89+
key=lambda x: ("zh-CN" not in x["languages"], x["name"])
90+
)
91+
logger.debug(
92+
f"语音列表已排序,中文语音优先显示,共{len(filtered_voices)}个语音"
93+
)
94+
95+
return filtered_voices
96+
except Exception as e:
97+
logger.error(f"获取Edge TTS语音列表失败: {e}")
98+
# 返回默认语音列表
99+
return self.get_default_voices()
100+
101+
def get_default_voices(self):
102+
"""获取默认语音列表"""
103+
default_voices = [
104+
{
105+
"name": "Xiaoxiao (Chinese Female)",
106+
"id": "zh-CN-XiaoxiaoNeural",
107+
"languages": "zh-CN",
108+
"full_info": "Female | zh-CN | Type: Neural",
109+
},
110+
{
111+
"name": "Yunxi (Chinese Male)",
112+
"id": "zh-CN-YunxiNeural",
113+
"languages": "zh-CN",
114+
"full_info": "Male | zh-CN | Type: Neural",
115+
},
116+
{
117+
"name": "Xiaoyi (Chinese Female)",
118+
"id": "zh-CN-XiaoyiNeural",
119+
"languages": "zh-CN",
120+
"full_info": "Female | zh-CN | Type: Neural",
121+
},
122+
{
123+
"name": "Jenny (English Female)",
124+
"id": "en-US-JennyNeural",
125+
"languages": "en-US",
126+
"full_info": "Female | en-US | Type: Neural",
127+
},
128+
{
129+
"name": "Guy (English Male)",
130+
"id": "en-US-GuyNeural",
131+
"languages": "en-US",
132+
"full_info": "Male | en-US | Type: Neural",
133+
},
134+
]
135+
logger.info("使用默认语音列表")
136+
return default_voices

app/common/voice/voice.py

Lines changed: 7 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -96,38 +96,19 @@ def start(self) -> None:
9696

9797
def _playback_worker(self) -> None:
9898
"""播放线程主循环"""
99-
last_queue_adjust_time = 0
100-
queue_adjust_interval = 5.0 # 队列大小调整间隔,单位:秒
101-
idle_interval = 60.0 # 队列为空时的检测间隔,单位:秒
102-
10399
while not self._stop_flag.is_set():
104100
try:
105-
current_time = time.time()
106-
is_queue_empty = self.play_queue.empty()
107-
108-
# 根据队列状态调整检测频率
109-
if is_queue_empty:
110-
# 队列为空时,降低检测频率
111-
adjust_interval = idle_interval
112-
else:
113-
# 队列不为空时,正常检测频率
114-
adjust_interval = queue_adjust_interval
115-
116-
# 只有当满足时间间隔或队列不为空时,才调整队列大小
117-
if current_time - last_queue_adjust_time >= adjust_interval:
118-
# 动态调整队列大小
119-
new_queue_size = self._load_balancer.get_optimal_queue_size()
120-
if self.play_queue.maxsize != new_queue_size:
121-
self.play_queue.maxsize = new_queue_size
122-
logger.debug(f"队列大小调整为: {new_queue_size}")
123-
last_queue_adjust_time = current_time
124-
125101
# 非阻塞获取任务,设置超时时间避免无限等待
126-
# 根据队列状态调整超时时间
127-
timeout = 0.1 if is_queue_empty else 1.0
102+
timeout = 0.1 # 短暂超时,平衡响应速度和CPU占用
128103
task = self.play_queue.get(timeout=timeout)
129104
logger.debug(f"获取到播放任务: {type(task).__name__}")
130105

106+
# 只有在有实际播放任务时,才进行系统负载检测和队列大小调整
107+
new_queue_size = self._load_balancer.get_optimal_queue_size()
108+
if self.play_queue.maxsize != new_queue_size:
109+
self.play_queue.maxsize = new_queue_size
110+
logger.debug(f"队列大小调整为: {new_queue_size}")
111+
131112
# 处理两种任务格式:文件路径或内存数据
132113
if isinstance(task, tuple): # 内存数据
133114
data, fs = task

0 commit comments

Comments
 (0)