mirror of
https://github.com/xinnan-tech/xiaozhi-esp32-server.git
synced 2026-07-21 10:55:38 +00:00
🎈 perf: 更新文档+优雅的退出 (#292)
* 🎈 perf: 更新文档+优雅的退出 * update:gsv代码可以使用gpt_sovits_v2适配器代码拓展,不需要单独。如果需要定义模型名称,可以在gpt_sovits_v2代码中添加扩展 --------- Co-authored-by: hrz <1710360675@qq.com>
This commit is contained in:
+3
-19
@@ -117,7 +117,7 @@ docker run -it --name xiaozhi-env --restart always --security-opt seccomp:unconf
|
||||
-p 8000:8000 \
|
||||
-p 8002:8002 \
|
||||
-v ./:/app \
|
||||
kalicyh/poetry:v3.10_xiaozhi
|
||||
kalicyh/python:xiaozhi
|
||||
```
|
||||
|
||||
然后就和正常开发一样了
|
||||
@@ -127,29 +127,13 @@ docker run -it --name xiaozhi-env --restart always --security-opt seccomp:unconf
|
||||
在刚刚的打开的终端运行
|
||||
|
||||
```sh
|
||||
poetry install --no-root
|
||||
```
|
||||
|
||||
```sh
|
||||
apt-get update
|
||||
apt-get install -y --no-install-recommends libopus0 ffmpeg
|
||||
```
|
||||
|
||||
速度慢可以尝试使用清华镜像
|
||||
|
||||
```sh
|
||||
echo "deb https://mirrors.tuna.tsinghua.edu.cn/debian/ bookworm main contrib non-free non-free-firmware" > /etc/apt/sources.list
|
||||
echo "deb https://mirrors.tuna.tsinghua.edu.cn/debian/ bookworm-updates main contrib non-free non-free-firmware" >> /etc/apt/sources.list
|
||||
echo "deb https://mirrors.tuna.tsinghua.edu.cn/debian/ bookworm-backports main contrib non-free non-free-firmware" >> /etc/apt/sources.list
|
||||
echo "deb https://mirrors.tuna.tsinghua.edu.cn/debian-security bookworm-security main contrib non-free non-free-firmware" >> /etc/apt/sources.list
|
||||
apt-get update
|
||||
apt-get install -y --no-install-recommends libopus0 ffmpeg
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
|
||||
## 6.运行项目
|
||||
|
||||
```sh
|
||||
poetry run python app.py
|
||||
python app.py
|
||||
```
|
||||
|
||||
# 方式三:本地源码运行
|
||||
|
||||
@@ -8,6 +8,6 @@ public class AdminApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(AdminApplication.class, args);
|
||||
System.out.println("http://localhost:8002/xiaozhi-esp32-api/doc.html");
|
||||
System.out.println("http://localhost:8002/xiaozhi-esp32-api/api/v1/doc.html");
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,27 @@
|
||||
import asyncio
|
||||
import sys
|
||||
import signal
|
||||
from config.settings import load_config, check_config_file
|
||||
from core.websocket_server import WebSocketServer
|
||||
from core.utils.util import check_ffmpeg_installed
|
||||
|
||||
TAG = __name__
|
||||
|
||||
async def wait_for_exit():
|
||||
"""Windows 和 Linux 兼容的退出监听"""
|
||||
loop = asyncio.get_running_loop()
|
||||
stop_event = asyncio.Event()
|
||||
|
||||
if sys.platform == "win32":
|
||||
# Windows: 用 sys.stdin.read() 监听 Ctrl + C
|
||||
await loop.run_in_executor(None, sys.stdin.read)
|
||||
else:
|
||||
# Linux/macOS: 用 signal 监听 Ctrl + C
|
||||
def stop():
|
||||
stop_event.set()
|
||||
loop.add_signal_handler(signal.SIGINT, stop)
|
||||
loop.add_signal_handler(signal.SIGTERM, stop) # 支持 kill 进程
|
||||
await stop_event.wait()
|
||||
|
||||
async def main():
|
||||
check_config_file()
|
||||
@@ -16,11 +33,19 @@ async def main():
|
||||
ws_task = asyncio.create_task(ws_server.start())
|
||||
|
||||
try:
|
||||
# 等待 WebSocket 服务器运行
|
||||
await ws_task
|
||||
await wait_for_exit() # 监听退出信号
|
||||
except asyncio.CancelledError:
|
||||
print("任务被取消,清理资源中...")
|
||||
finally:
|
||||
ws_task.cancel()
|
||||
|
||||
try:
|
||||
await ws_task
|
||||
except asyncio.CancelledError:
|
||||
pass
|
||||
print("服务器已关闭,程序退出。")
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
try:
|
||||
asyncio.run(main())
|
||||
except KeyboardInterrupt:
|
||||
print("手动中断,程序终止。")
|
||||
|
||||
@@ -311,36 +311,6 @@ TTS:
|
||||
media_type: "wav"
|
||||
streaming_mode: false
|
||||
threshold: 30
|
||||
GSV:
|
||||
# GSV原神本地语音合成
|
||||
# 整合包下载:https://pan.ai-hobbyist.com/Infer%20Packs/TTS
|
||||
# 国内镜像:https://alist.hancat.work/d/cdn/GPT-Sovits%E8%AF%AD%E9%9F%B3%E5%90%88%E6%88%90%E6%95%B4%E5%90%88%E5%8C%85%EF%BC%8C%E6%94%AF%E6%8C%81%E5%A4%9A%E4%BA%BA%E5%AF%B9%E8%AF%9D%E3%80%82%E6%B0%B8%E4%B9%85%E5%85%8D%E8%B4%B9%EF%BC%8C%E4%B8%A5%E7%A6%81%E5%80%92%E5%8D%96%EF%BC%81.7z
|
||||
# 本TTS需要配合模型来使用,模型可以去这里下载:
|
||||
# 二次元 & 游戏角色 GPT-Sovits V2 语音合成模型:https://www.modelscope.cn/models/aihobbyist/Anime_GPT-Sovits_Models
|
||||
# 下载后将其解压到model文件夹
|
||||
# URL需要修改批处理文件才可生效,默认WebUI端口号为8080,API端口号为WebUI端口号+1
|
||||
url: "http://127.0.0.1:9881/infer_single"
|
||||
app_key: ""
|
||||
audio_dl_url: "http://127.0.0.1:9881"
|
||||
model_name: "【原神】须弥"
|
||||
speaker_name: "纳西妲"
|
||||
prompt_text_lang: "中文"
|
||||
emocton: "中立_neutral"
|
||||
text_lang: "中文"
|
||||
top_k: 10
|
||||
top_p: 1
|
||||
temperature: 1
|
||||
text_split_method: "按标点符号切"
|
||||
batch_size: 10
|
||||
batch_size: 0.75
|
||||
split_bucket: true
|
||||
speed_facter: 1
|
||||
fragment_interval: 0.3
|
||||
media_type: "wav"
|
||||
parallel_infer: true
|
||||
repetition_penalty: 1.35
|
||||
seed: -1
|
||||
output_file: tmp/
|
||||
MinimaxTTS:
|
||||
# Minimax语音合成服务,需要先在minimax平台创建账户充值,并获取登录信息
|
||||
# 平台地址:https://platform.minimaxi.com/
|
||||
|
||||
@@ -1,123 +0,0 @@
|
||||
import os
|
||||
import uuid
|
||||
import requests
|
||||
from config.logger import setup_logging
|
||||
from datetime import datetime
|
||||
from core.providers.tts.base import TTSProviderBase
|
||||
|
||||
TAG = __name__
|
||||
logger = setup_logging()
|
||||
|
||||
class TTSProvider(TTSProviderBase):
|
||||
def __init__(self, config, delete_audio_file):
|
||||
super().__init__(config, delete_audio_file)
|
||||
# POST请求地址
|
||||
self.url = config.get("url")
|
||||
# 从配置中加载所有参数
|
||||
self.app_key = config.get("app_key")
|
||||
self.audio_dl_url = config.get("audio_dl_url")
|
||||
self.model_name = config.get("model_name")
|
||||
self.speaker_name = config.get("speaker_name")
|
||||
self.prompt_text_lang = config.get("prompt_text_lang")
|
||||
self.emotion = config.get("emotion")
|
||||
self.text_lang = config.get("text_lang")
|
||||
self.top_k = config.get("top_k")
|
||||
self.top_p = config.get("top_p")
|
||||
self.temperature = config.get("temperature")
|
||||
self.text_split_method = config.get("text_split_method")
|
||||
self.batch_size = config.get("batch_size")
|
||||
self.batch_threshold = config.get("batch_size")
|
||||
self.split_bucket = config.get("split_bucket")
|
||||
self.speed_facter = config.get("speed_facter")
|
||||
self.fragment_interval = config.get("fragment_interval")
|
||||
self.media_type = config.get("media_type")
|
||||
self.parallel_infer = config.get("parallel_infer")
|
||||
self.repetition_penalty = config.get("repetition_penalty")
|
||||
self.seed = config.get("seed")
|
||||
|
||||
def generate_filename(self):
|
||||
"""根据媒体类型生成文件名"""
|
||||
extension = f".{self.media_type}"
|
||||
return os.path.join(
|
||||
self.output_file,
|
||||
f"tts-{datetime.now().date()}@{uuid.uuid4().hex}{extension}"
|
||||
)
|
||||
|
||||
async def text_to_speak(self, text, output_file):
|
||||
"""文本转语音并保存音频文件"""
|
||||
# 构造请求体
|
||||
request_body = {
|
||||
"app_key": self.app_key,
|
||||
"audio_dl_url": self.audio_dl_url,
|
||||
"model_name": self.model_name,
|
||||
"speaker_name": self.speaker_name,
|
||||
"prompt_text_lang": self.prompt_text_lang,
|
||||
"emotion": self.emotion,
|
||||
"text": text,
|
||||
"text_lang": self.text_lang,
|
||||
"top_k": self.top_k,
|
||||
"top_p": self.top_p,
|
||||
"temperature": self.temperature,
|
||||
"text_split_method": self.text_split_method,
|
||||
"batch_size": self.batch_size,
|
||||
"batch_threshold": self.batch_threshold,
|
||||
"split_bucket": self.split_bucket,
|
||||
"speed_facter": self.speed_facter,
|
||||
"fragment_interval": self.fragment_interval,
|
||||
"media_type": self.media_type,
|
||||
"parallel_infer": self.parallel_infer,
|
||||
"repetition_penalty": self.repetition_penalty,
|
||||
"seed": self.seed
|
||||
}
|
||||
|
||||
body = {
|
||||
"app_key": "",
|
||||
"audio_dl_url": "http://127.0.0.1:9881",
|
||||
"model_name": "【原神】须弥",
|
||||
"speaker_name": "纳西妲",
|
||||
"prompt_text_lang": "中文",
|
||||
"emotion": "中立_neutral",
|
||||
"text": text,
|
||||
"text_lang": "中文",
|
||||
"top_k": 10,
|
||||
"top_p": 1,
|
||||
"temperature": 1,
|
||||
"text_split_method": "按标点符号切",
|
||||
"batch_size": 10,
|
||||
"batch_threshold": 0.75,
|
||||
"split_bucket": True,
|
||||
"speed_facter": 1,
|
||||
"fragment_interval": 0.3,
|
||||
"media_type": "wav",
|
||||
"parallel_infer": True,
|
||||
"repetition_penalty": 1.35,
|
||||
"seed": -1
|
||||
}
|
||||
|
||||
try:
|
||||
# 发送POST请求
|
||||
resp = requests.post(self.url, json=body)
|
||||
resp.raise_for_status()
|
||||
|
||||
# 解析响应
|
||||
result = resp.json()
|
||||
if "audio_url" not in result:
|
||||
logger.bind(tag=TAG).error("响应中缺少音频地址")
|
||||
return
|
||||
|
||||
# 下载音频文件
|
||||
audio_resp = requests.get(result["audio_url"])
|
||||
audio_resp.raise_for_status()
|
||||
|
||||
# 保存文件
|
||||
with open(output_file, "wb") as f:
|
||||
f.write(audio_resp.content)
|
||||
|
||||
logger.bind(tag=TAG).info(f"音频文件已保存至: {output_file}")
|
||||
|
||||
except requests.exceptions.HTTPError as e:
|
||||
logger.bind(tag=TAG).error(f"HTTP请求失败: {str(e)}")
|
||||
except requests.exceptions.JSONDecodeError:
|
||||
logger.bind(tag=TAG).error("响应解析失败,无效的JSON格式")
|
||||
except Exception as e:
|
||||
logger.bind(tag=TAG).error(f"语音合成失败: {str(e)}")
|
||||
Reference in New Issue
Block a user