mirror of
https://github.com/xinnan-tech/xiaozhi-esp32-server.git
synced 2026-07-28 19:13:52 +08:00
@@ -7,6 +7,7 @@ from core.ota_server import SimpleOtaServer
|
|||||||
from core.utils.util import check_ffmpeg_installed
|
from core.utils.util import check_ffmpeg_installed
|
||||||
from config.logger import setup_logging
|
from config.logger import setup_logging
|
||||||
from core.utils.util import get_local_ip
|
from core.utils.util import get_local_ip
|
||||||
|
from aioconsole import ainput
|
||||||
|
|
||||||
TAG = __name__
|
TAG = __name__
|
||||||
logger = setup_logging()
|
logger = setup_logging()
|
||||||
@@ -34,10 +35,19 @@ async def wait_for_exit() -> None:
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
async def monitor_stdin():
|
||||||
|
"""监控标准输入,消费回车键"""
|
||||||
|
while True:
|
||||||
|
await ainput() # 异步等待输入,消费回车
|
||||||
|
|
||||||
|
|
||||||
async def main():
|
async def main():
|
||||||
check_ffmpeg_installed()
|
check_ffmpeg_installed()
|
||||||
config = load_config()
|
config = load_config()
|
||||||
|
|
||||||
|
# 添加 stdin 监控任务
|
||||||
|
stdin_task = asyncio.create_task(monitor_stdin())
|
||||||
|
|
||||||
# 启动 WebSocket 服务器
|
# 启动 WebSocket 服务器
|
||||||
ws_server = WebSocketServer(config)
|
ws_server = WebSocketServer(config)
|
||||||
ws_task = asyncio.create_task(ws_server.start())
|
ws_task = asyncio.create_task(ws_server.start())
|
||||||
@@ -78,19 +88,22 @@ async def main():
|
|||||||
)
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
await wait_for_exit() # 监听退出信号
|
await wait_for_exit() # 阻塞直到收到退出信号
|
||||||
except asyncio.CancelledError:
|
except asyncio.CancelledError:
|
||||||
print("任务被取消,清理资源中...")
|
print("任务被取消,清理资源中...")
|
||||||
finally:
|
finally:
|
||||||
|
# 取消所有任务(关键修复点)
|
||||||
|
stdin_task.cancel()
|
||||||
ws_task.cancel()
|
ws_task.cancel()
|
||||||
if ota_task:
|
if ota_task:
|
||||||
ota_task.cancel()
|
ota_task.cancel()
|
||||||
try:
|
|
||||||
await ws_task
|
# 等待任务终止(必须加超时)
|
||||||
if ota_task:
|
await asyncio.wait(
|
||||||
await ota_task
|
[stdin_task, ws_task, ota_task] if ota_task else [stdin_task, ws_task],
|
||||||
except asyncio.CancelledError:
|
timeout=3.0,
|
||||||
pass
|
return_when=asyncio.ALL_COMPLETED
|
||||||
|
)
|
||||||
print("服务器已关闭,程序退出。")
|
print("服务器已关闭,程序退出。")
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
import os
|
import os
|
||||||
import copy
|
import copy
|
||||||
import json
|
import json
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
import uuid
|
import uuid
|
||||||
import time
|
import time
|
||||||
import queue
|
import queue
|
||||||
@@ -241,6 +243,44 @@ class ConnectionHandler:
|
|||||||
elif isinstance(message, bytes):
|
elif isinstance(message, bytes):
|
||||||
await handleAudioMessage(self, message)
|
await handleAudioMessage(self, message)
|
||||||
|
|
||||||
|
async def handle_restart(self, message):
|
||||||
|
"""处理服务器重启请求"""
|
||||||
|
try:
|
||||||
|
|
||||||
|
self.logger.bind(tag=TAG).info("收到服务器重启指令,准备执行...")
|
||||||
|
|
||||||
|
# 发送确认响应
|
||||||
|
await self.websocket.send(json.dumps({
|
||||||
|
"type": "server_response",
|
||||||
|
"status": "success",
|
||||||
|
"message": "服务器重启中..."
|
||||||
|
}))
|
||||||
|
|
||||||
|
# 异步执行重启操作
|
||||||
|
def restart_server():
|
||||||
|
"""实际执行重启的方法"""
|
||||||
|
time.sleep(1)
|
||||||
|
self.logger.bind(tag=TAG).info("执行服务器重启...")
|
||||||
|
subprocess.Popen(
|
||||||
|
[sys.executable, "app.py"],
|
||||||
|
stdin=sys.stdin,
|
||||||
|
stdout=sys.stdout,
|
||||||
|
stderr=sys.stderr,
|
||||||
|
start_new_session=True
|
||||||
|
)
|
||||||
|
os._exit(0)
|
||||||
|
|
||||||
|
# 使用线程执行重启避免阻塞事件循环
|
||||||
|
threading.Thread(target=restart_server, daemon=True).start()
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
self.logger.bind(tag=TAG).error(f"重启失败: {str(e)}")
|
||||||
|
await self.websocket.send(json.dumps({
|
||||||
|
"type": "server_response",
|
||||||
|
"status": "error",
|
||||||
|
"message": f"Restart failed: {str(e)}"
|
||||||
|
}))
|
||||||
|
|
||||||
def _initialize_components(self):
|
def _initialize_components(self):
|
||||||
"""初始化组件"""
|
"""初始化组件"""
|
||||||
if self.config.get("prompt") is not None:
|
if self.config.get("prompt") is not None:
|
||||||
|
|||||||
@@ -136,5 +136,8 @@ async def handleTextMessage(conn, message):
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
# 重启服务器
|
||||||
|
elif msg_json["action"] == "restart":
|
||||||
|
await conn.handle_restart(msg_json)
|
||||||
except json.JSONDecodeError:
|
except json.JSONDecodeError:
|
||||||
await conn.websocket.send(message)
|
await conn.websocket.send(message)
|
||||||
|
|||||||
@@ -38,9 +38,13 @@ class TTSProvider(TTSProviderBase):
|
|||||||
"Authorization": f"Bearer {self.access_token}",
|
"Authorization": f"Bearer {self.access_token}",
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
}
|
}
|
||||||
response = requests.request(
|
|
||||||
"POST", self.api_url, json=request_json, headers=headers
|
try:
|
||||||
)
|
response = requests.request(
|
||||||
data = response.content
|
"POST", self.api_url, json=request_json, headers=headers
|
||||||
file_to_save = open(output_file, "wb")
|
)
|
||||||
file_to_save.write(data)
|
data = response.content
|
||||||
|
file_to_save = open(output_file, "wb")
|
||||||
|
file_to_save.write(data)
|
||||||
|
except Exception as e:
|
||||||
|
raise Exception(f"{__name__} error: {e}")
|
||||||
@@ -32,4 +32,6 @@ class TTSProvider(TTSProviderBase):
|
|||||||
with open(output_file, "wb") as file:
|
with open(output_file, "wb") as file:
|
||||||
file.write(resp.content)
|
file.write(resp.content)
|
||||||
else:
|
else:
|
||||||
logger.bind(tag=TAG).error(f"Custom TTS请求失败: {resp.status_code} - {resp.text}")
|
error_msg = f"Custom TTS请求失败: {resp.status_code} - {resp.text}"
|
||||||
|
logger.bind(tag=TAG).error(error_msg)
|
||||||
|
raise Exception(error_msg) # 抛出异常,让调用方捕获
|
||||||
|
|||||||
@@ -20,14 +20,18 @@ class TTSProvider(TTSProviderBase):
|
|||||||
)
|
)
|
||||||
|
|
||||||
async def text_to_speak(self, text, output_file):
|
async def text_to_speak(self, text, output_file):
|
||||||
communicate = edge_tts.Communicate(text, voice=self.voice)
|
try:
|
||||||
# 确保目录存在并创建空文件
|
communicate = edge_tts.Communicate(text, voice=self.voice)
|
||||||
os.makedirs(os.path.dirname(output_file), exist_ok=True)
|
# 确保目录存在并创建空文件
|
||||||
with open(output_file, "wb") as f:
|
os.makedirs(os.path.dirname(output_file), exist_ok=True)
|
||||||
pass
|
with open(output_file, "wb") as f:
|
||||||
|
pass
|
||||||
|
|
||||||
# 流式写入音频数据
|
# 流式写入音频数据
|
||||||
with open(output_file, "ab") as f: # 改为追加模式避免覆盖
|
with open(output_file, "ab") as f: # 改为追加模式避免覆盖
|
||||||
async for chunk in communicate.stream():
|
async for chunk in communicate.stream():
|
||||||
if chunk["type"] == "audio": # 只处理音频数据块
|
if chunk["type"] == "audio": # 只处理音频数据块
|
||||||
f.write(chunk["data"])
|
f.write(chunk["data"])
|
||||||
|
except Exception as e:
|
||||||
|
error_msg = f"Edge TTS请求失败: {e}"
|
||||||
|
raise Exception(error_msg) # 抛出异常,让调用方捕获
|
||||||
@@ -177,5 +177,7 @@ class TTSProvider(TTSProviderBase):
|
|||||||
audio_file.write(audio_content)
|
audio_file.write(audio_content)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
print(f"Request failed with status code {response.status_code}")
|
error_msg = f"Request failed with status code {response.status_code}"
|
||||||
|
print(error_msg)
|
||||||
print(response.json())
|
print(response.json())
|
||||||
|
raise Exception(error_msg)
|
||||||
|
|||||||
@@ -105,6 +105,6 @@ class TTSProvider(TTSProviderBase):
|
|||||||
with open(output_file, "wb") as file:
|
with open(output_file, "wb") as file:
|
||||||
file.write(resp.content)
|
file.write(resp.content)
|
||||||
else:
|
else:
|
||||||
logger.bind(tag=TAG).error(
|
error_msg = f"GPT_SoVITS_V2 TTS请求失败: {resp.status_code} - {resp.text}"
|
||||||
f"GPT_SoVITS_V2 TTS请求失败: {resp.status_code} - {resp.text}"
|
logger.bind(tag=TAG).error(error_msg)
|
||||||
)
|
raise Exception(error_msg)
|
||||||
|
|||||||
@@ -64,6 +64,7 @@ class TTSProvider(TTSProviderBase):
|
|||||||
with open(output_file, "wb") as file:
|
with open(output_file, "wb") as file:
|
||||||
file.write(resp.content)
|
file.write(resp.content)
|
||||||
else:
|
else:
|
||||||
logger.bind(tag=TAG).error(
|
error_msg = f"GPT_SoVITS_V3 TTS请求失败: {resp.status_code} - {resp.text}"
|
||||||
f"GPT_SoVITS_V3 TTS请求失败: {resp.status_code} - {resp.text}"
|
logger.bind(tag=TAG).error(error_msg)
|
||||||
)
|
raise Exception(error_msg)
|
||||||
|
|
||||||
|
|||||||
@@ -39,9 +39,12 @@ class TTSProvider(TTSProviderBase):
|
|||||||
"Authorization": f"Bearer {self.access_token}",
|
"Authorization": f"Bearer {self.access_token}",
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
}
|
}
|
||||||
response = requests.request(
|
try:
|
||||||
"POST", self.api_url, json=request_json, headers=headers
|
response = requests.request(
|
||||||
)
|
"POST", self.api_url, json=request_json, headers=headers
|
||||||
data = response.content
|
)
|
||||||
file_to_save = open(output_file, "wb")
|
data = response.content
|
||||||
file_to_save.write(data)
|
file_to_save = open(output_file, "wb")
|
||||||
|
file_to_save.write(data)
|
||||||
|
except Exception as e:
|
||||||
|
raise Exception(f"{__name__} error: {e}")
|
||||||
@@ -58,8 +58,8 @@ class TTSProvider(TTSProviderBase):
|
|||||||
|
|
||||||
resp = requests.request("POST", url, data=payload)
|
resp = requests.request("POST", url, data=payload)
|
||||||
if resp.status_code != 200:
|
if resp.status_code != 200:
|
||||||
logger.bind(tag=TAG).error(f"TTS请求失败: {resp.text}")
|
logger.bind(tag=TAG).error(f"TTSON 请求失败: {resp.text}")
|
||||||
return None
|
raise Exception(f"{__name__}: TTS请求失败")
|
||||||
resp_json = resp.json()
|
resp_json = resp.json()
|
||||||
try:
|
try:
|
||||||
result = (
|
result = (
|
||||||
@@ -71,13 +71,15 @@ class TTSProvider(TTSProviderBase):
|
|||||||
+ "&voice_audio_path="
|
+ "&voice_audio_path="
|
||||||
+ resp_json["voice_path"]
|
+ resp_json["voice_path"]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
audio_content = requests.get(result)
|
||||||
|
with open(output_file, "wb") as f:
|
||||||
|
f.write(audio_content.content)
|
||||||
|
return True
|
||||||
|
voice_path = resp_json.get("voice_path")
|
||||||
|
des_path = output_file
|
||||||
|
shutil.move(voice_path, des_path)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print("error:", e)
|
print("error:", e)
|
||||||
|
raise Exception(f"{__name__}: TTS请求失败")
|
||||||
audio_content = requests.get(result)
|
|
||||||
with open(output_file, "wb") as f:
|
|
||||||
f.write(audio_content.content)
|
|
||||||
return True
|
|
||||||
voice_path = resp_json.get("voice_path")
|
|
||||||
des_path = output_file
|
|
||||||
shutil.move(voice_path, des_path)
|
|
||||||
@@ -28,4 +28,5 @@ PySocks==1.7.1
|
|||||||
dashscope==1.23.1
|
dashscope==1.23.1
|
||||||
baidu-aip==4.16.13
|
baidu-aip==4.16.13
|
||||||
chardet==5.2.0
|
chardet==5.2.0
|
||||||
|
aioconsole==0.8.1
|
||||||
markitdown==0.1.1
|
markitdown==0.1.1
|
||||||
Reference in New Issue
Block a user