Block docker web (#248)

* update:docker智控台设置提示

* 修复启动报错问题 (#247)

修复启动时SyntaxWarning: invalid escape sequence '\]'
  half_width_punctuations = '!"#$%&\'()*+,-./:;<=>?@[\]^_`{|}~'的报错问题

* 2025-3-8-iot状态更新 (#243)

* 添加 GPTSoVits-V3 TTS (#234)

---------

Co-authored-by: hrz <1710360675@qq.com>
Co-authored-by: vonweller <154695311+vonweller@users.noreply.github.com>
Co-authored-by: Jiao Haoyang <108573524+XuSenfeng@users.noreply.github.com>
Co-authored-by: Echo <yowfung@163.com>
This commit is contained in:
欣南科技
2025-03-09 01:02:37 +08:00
committed by GitHub
co-authored by hrz vonweller Jiao Haoyang Echo
parent baff979ab4
commit 9c2b2a2dcc
6 changed files with 129 additions and 17 deletions
+17 -1
View File
@@ -37,7 +37,8 @@ log:
data_dir: data data_dir: data
iot: iot:
Speaker: Speaker:
volume: 100 # 设置esp32的音量,范围0-100
volume: 80
xiaozhi: xiaozhi:
type: hello type: hello
version: 1 version: 1
@@ -258,6 +259,21 @@ TTS:
parallel_infer: true parallel_infer: true
repetition_penalty: 1.35 repetition_penalty: 1.35
aux_ref_audio_paths: [] aux_ref_audio_paths: []
GPT_SOVITS_V3:
type: gpt_sovits_v3
url: "http://127.0.0.1:9880/tts"
output_file: tmp/
text_lang: "auto"
ref_audio_path: "caixukun.wav"
prompt_lang: "zh"
prompt_text: ""
top_k: 5
top_p: 1
temperature: 1
sample_steps: 16
media_type: "wav"
streaming_mode: false
threshold: 30
MinimaxTTS: MinimaxTTS:
# Minimax语音合成服务,需要先在minimax平台创建账户充值,并获取登录信息 # Minimax语音合成服务,需要先在minimax平台创建账户充值,并获取登录信息
# 平台地址:https://platform.minimaxi.com/ # 平台地址:https://platform.minimaxi.com/
@@ -109,7 +109,48 @@ async def handleIotDescriptors(conn, descriptors):
default_iot_volume = conn.config["iot"]["Speaker"]["volume"] default_iot_volume = conn.config["iot"]["Speaker"]["volume"]
logger.bind(tag=TAG).info(f"服务端设置音量为{default_iot_volume}") logger.bind(tag=TAG).info(f"服务端设置音量为{default_iot_volume}")
await send_iot_conn(conn, "Speaker", "SetVolume", {"volume": default_iot_volume}) await send_iot_conn(conn, "Speaker", "SetVolume", {"volume": default_iot_volume})
async def handleIotStatus(conn, states):
"""
处理物联网状态
示例: [{
"name":"Speaker",
"state":{
"volume":100
}
}]
states: 状态列表
"""
for state in states:
for key, value in conn.iot_descriptors.items():
if key == state["name"]:
for property_item in value.properties:
# properties为字典列表, 记录各种属性
for k, v in state["state"].items():
# state为字典, 记录各种属性的值, 是需要记录的信息
if property_item["name"] == k:
# 检查一下属性是不是相同的
if type(v) != type(property_item["value"]):
logger.bind(tag=TAG).error(f"属性{property_item['name']}的值类型不匹配")
break
else:
property_item["value"] = v
logger.bind(tag=TAG).info(f"物联网状态更新: {key} , {property_item['name']} = {v}")
break
break
async def get_iot_status(conn, name, property_name):
"""
获取物联网状态
name: 设备名称 "Speaker"
property_name: 属性名称 "volume"
返回值: 属性值, 实际的属性有int, bool和str三种类型
"""
for key, value in conn.iot_descriptors.items():
if key == name:
for property_item in value.properties:
if property_item["name"] == property_name:
return property_item["value"]
return None
async def send_iot_conn(conn, name, method_name, parameters): async def send_iot_conn(conn, name, method_name, parameters):
""" """
@@ -3,7 +3,7 @@ import json
from core.handle.abortHandle import handleAbortMessage from core.handle.abortHandle import handleAbortMessage
from core.handle.helloHandle import handleHelloMessage from core.handle.helloHandle import handleHelloMessage
from core.handle.receiveAudioHandle import startToChat from core.handle.receiveAudioHandle import startToChat
from core.handle.iotHandle import handleIotDescriptors from core.handle.iotHandle import handleIotDescriptors, handleIotStatus
TAG = __name__ TAG = __name__
logger = setup_logging() logger = setup_logging()
@@ -40,5 +40,7 @@ async def handleTextMessage(conn, message):
elif msg_json["type"] == "iot": elif msg_json["type"] == "iot":
if "descriptors" in msg_json: if "descriptors" in msg_json:
await handleIotDescriptors(conn, msg_json["descriptors"]) await handleIotDescriptors(conn, msg_json["descriptors"])
if "states" in msg_json:
await handleIotStatus(conn, msg_json["states"])
except json.JSONDecodeError: except json.JSONDecodeError:
await conn.websocket.send(message) await conn.websocket.send(message)
@@ -0,0 +1,52 @@
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)
self.url = config.get("url")
self.text_lang = config.get("text_lang", "audo")
self.ref_audio_path = config.get("ref_audio_path")
self.prompt_lang = config.get("prompt_lang")
self.prompt_text = config.get("prompt_text")
self.top_k = config.get("top_k", 5)
self.top_p = config.get("top_p", 1)
self.temperature = config.get("temperature", 1)
self.sample_steps = config.get("sample_steps", 16)
self.media_type = config.get("media_type", "wav")
self.streaming_mode = config.get("streaming_mode", False)
self.threshold = config.get("threshold", 30)
def generate_filename(self, extension=".wav"):
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_params = {
"text": text,
"text_lang": self.text_lang,
"ref_audio_path": self.ref_audio_path,
"prompt_lang": self.prompt_lang,
"prompt_text": self.prompt_text,
"top_k": self.top_k,
"top_p": self.top_p,
"temperature": self.temperature,
"sample_steps": self.sample_steps,
"media_type": self.media_type,
"streaming_mode": self.streaming_mode,
"threshold": self.threshold,
}
resp = requests.get(self.url, params=request_params)
if resp.status_code == 200:
with open(output_file, "wb") as file:
file.write(resp.content)
else:
logger.bind(tag=TAG).error(f"GPT_SoVITS_V3 TTS请求失败: {resp.status_code} - {resp.text}")
+1 -1
View File
@@ -75,7 +75,7 @@ def get_string_no_punctuation_or_emoji(s):
def remove_punctuation_and_length(text): def remove_punctuation_and_length(text):
# 全角符号和半角符号的Unicode范围 # 全角符号和半角符号的Unicode范围
full_width_punctuations = '!"#$%&'()*+,-。/:;<=>?@[\]^_`{|}~' full_width_punctuations = '!"#$%&'()*+,-。/:;<=>?@[\]^_`{|}~'
half_width_punctuations = '!"#$%&\'()*+,-./:;<=>?@[\]^_`{|}~' half_width_punctuations = r'!"#$%&\'()*+,-./:;<=>?@[\]^_`{|}~'
space = ' ' # 半角空格 space = ' ' # 半角空格
full_width_space = ' ' # 全角空格 full_width_space = ' ' # 全角空格
+15 -14
View File
@@ -21,17 +21,18 @@ services:
- ./data:/opt/xiaozhi-esp32-server/data - ./data:/opt/xiaozhi-esp32-server/data
# 模型文件挂接,很重要 # 模型文件挂接,很重要
- ./models/SenseVoiceSmall/model.pt:/opt/xiaozhi-esp32-server/models/SenseVoiceSmall/model.pt - ./models/SenseVoiceSmall/model.pt:/opt/xiaozhi-esp32-server/models/SenseVoiceSmall/model.pt
xiaozhi-esp32-server-web: # #智控台还没开发好,还不能完全使用,会报很多错误,如果是非技术人员,请不要启用智控台服务
image: ghcr.nju.edu.cn/xinnan-tech/xiaozhi-esp32-server:web_latest # xiaozhi-esp32-server-web:
container_name: xiaozhi-esp32-server-web # image: ghcr.nju.edu.cn/xinnan-tech/xiaozhi-esp32-server:web_latest
restart: always # container_name: xiaozhi-esp32-server-web
ports: # restart: always
- "8002:8002" # ports:
environment: # - "8002:8002"
- TZ=Asia/Shanghai # environment:
##记得改mysql和redis IP 密码 # - TZ=Asia/Shanghai
- SPRING_DATASOURCE_DRUID_URL=jdbc:mysql://192.168.1.20:3306/xiaozhi_esp32_server?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai # ##记得改mysql和redis IP 密码
- SPRING_DATASOURCE_DRUID_USERNAME=root # - SPRING_DATASOURCE_DRUID_URL=jdbc:mysql://192.168.1.20:3306/xiaozhi_esp32_server?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
- SPRING_DATASOURCE_DRUID_PASSWORD=123456 # - SPRING_DATASOURCE_DRUID_USERNAME=root
- SPRING_DATA_REDIS_HOST=192.168.1.20 # - SPRING_DATASOURCE_DRUID_PASSWORD=123456
- SPRING_DATA_REDIS_PORT=6379 # - SPRING_DATA_REDIS_HOST=192.168.1.20
# - SPRING_DATA_REDIS_PORT=6379