mirror of
https://github.com/xinnan-tech/xiaozhi-esp32-server.git
synced 2026-07-28 01:53:53 +08:00
update:优化公共方法
This commit is contained in:
@@ -155,12 +155,6 @@ class ASRProvider(ASRProviderBase):
|
|||||||
# f"剩余 {remaining:.2f}秒")
|
# f"剩余 {remaining:.2f}秒")
|
||||||
return time.time() > self.expire_time
|
return time.time() > self.expire_time
|
||||||
|
|
||||||
def generate_filename(self, extension=".wav"):
|
|
||||||
return os.path.join(
|
|
||||||
self.output_file,
|
|
||||||
f"tts-{__name__}{datetime.now().date()}@{uuid.uuid4().hex}{extension}",
|
|
||||||
)
|
|
||||||
|
|
||||||
def _construct_request_url(self) -> str:
|
def _construct_request_url(self) -> str:
|
||||||
"""构造请求URL,包含参数"""
|
"""构造请求URL,包含参数"""
|
||||||
request = f"{self.base_url}?appkey={self.app_key}"
|
request = f"{self.base_url}?appkey={self.app_key}"
|
||||||
|
|||||||
@@ -8,61 +8,74 @@ import requests
|
|||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from core.providers.tts.base import TTSProviderBase
|
from core.providers.tts.base import TTSProviderBase
|
||||||
|
|
||||||
import http.client
|
|
||||||
import urllib.parse
|
|
||||||
import time
|
import time
|
||||||
import uuid
|
import uuid
|
||||||
from urllib import parse
|
from urllib import parse
|
||||||
|
|
||||||
|
|
||||||
class AccessToken:
|
class AccessToken:
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _encode_text(text):
|
def _encode_text(text):
|
||||||
encoded_text = parse.quote_plus(text)
|
encoded_text = parse.quote_plus(text)
|
||||||
return encoded_text.replace('+', '%20').replace('*', '%2A').replace('%7E', '~')
|
return encoded_text.replace("+", "%20").replace("*", "%2A").replace("%7E", "~")
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _encode_dict(dic):
|
def _encode_dict(dic):
|
||||||
keys = dic.keys()
|
keys = dic.keys()
|
||||||
dic_sorted = [(key, dic[key]) for key in sorted(keys)]
|
dic_sorted = [(key, dic[key]) for key in sorted(keys)]
|
||||||
encoded_text = parse.urlencode(dic_sorted)
|
encoded_text = parse.urlencode(dic_sorted)
|
||||||
return encoded_text.replace('+', '%20').replace('*', '%2A').replace('%7E', '~')
|
return encoded_text.replace("+", "%20").replace("*", "%2A").replace("%7E", "~")
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def create_token(access_key_id, access_key_secret):
|
def create_token(access_key_id, access_key_secret):
|
||||||
parameters = {'AccessKeyId': access_key_id,
|
parameters = {
|
||||||
'Action': 'CreateToken',
|
"AccessKeyId": access_key_id,
|
||||||
'Format': 'JSON',
|
"Action": "CreateToken",
|
||||||
'RegionId': 'cn-shanghai',
|
"Format": "JSON",
|
||||||
'SignatureMethod': 'HMAC-SHA1',
|
"RegionId": "cn-shanghai",
|
||||||
'SignatureNonce': str(uuid.uuid1()),
|
"SignatureMethod": "HMAC-SHA1",
|
||||||
'SignatureVersion': '1.0',
|
"SignatureNonce": str(uuid.uuid1()),
|
||||||
'Timestamp': time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()),
|
"SignatureVersion": "1.0",
|
||||||
'Version': '2019-02-28'}
|
"Timestamp": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()),
|
||||||
|
"Version": "2019-02-28",
|
||||||
|
}
|
||||||
# 构造规范化的请求字符串
|
# 构造规范化的请求字符串
|
||||||
query_string = AccessToken._encode_dict(parameters)
|
query_string = AccessToken._encode_dict(parameters)
|
||||||
# print('规范化的请求字符串: %s' % query_string)
|
# print('规范化的请求字符串: %s' % query_string)
|
||||||
# 构造待签名字符串
|
# 构造待签名字符串
|
||||||
string_to_sign = 'GET' + '&' + AccessToken._encode_text('/') + '&' + AccessToken._encode_text(query_string)
|
string_to_sign = (
|
||||||
|
"GET"
|
||||||
|
+ "&"
|
||||||
|
+ AccessToken._encode_text("/")
|
||||||
|
+ "&"
|
||||||
|
+ AccessToken._encode_text(query_string)
|
||||||
|
)
|
||||||
# print('待签名的字符串: %s' % string_to_sign)
|
# print('待签名的字符串: %s' % string_to_sign)
|
||||||
# 计算签名
|
# 计算签名
|
||||||
secreted_string = hmac.new(bytes(access_key_secret + '&', encoding='utf-8'),
|
secreted_string = hmac.new(
|
||||||
bytes(string_to_sign, encoding='utf-8'),
|
bytes(access_key_secret + "&", encoding="utf-8"),
|
||||||
hashlib.sha1).digest()
|
bytes(string_to_sign, encoding="utf-8"),
|
||||||
|
hashlib.sha1,
|
||||||
|
).digest()
|
||||||
signature = base64.b64encode(secreted_string)
|
signature = base64.b64encode(secreted_string)
|
||||||
# print('签名: %s' % signature)
|
# print('签名: %s' % signature)
|
||||||
# 进行URL编码
|
# 进行URL编码
|
||||||
signature = AccessToken._encode_text(signature)
|
signature = AccessToken._encode_text(signature)
|
||||||
# print('URL编码后的签名: %s' % signature)
|
# print('URL编码后的签名: %s' % signature)
|
||||||
# 调用服务
|
# 调用服务
|
||||||
full_url = 'http://nls-meta.cn-shanghai.aliyuncs.com/?Signature=%s&%s' % (signature, query_string)
|
full_url = "http://nls-meta.cn-shanghai.aliyuncs.com/?Signature=%s&%s" % (
|
||||||
|
signature,
|
||||||
|
query_string,
|
||||||
|
)
|
||||||
# print('url: %s' % full_url)
|
# print('url: %s' % full_url)
|
||||||
# 提交HTTP GET请求
|
# 提交HTTP GET请求
|
||||||
response = requests.get(full_url)
|
response = requests.get(full_url)
|
||||||
if response.ok:
|
if response.ok:
|
||||||
root_obj = response.json()
|
root_obj = response.json()
|
||||||
key = 'Token'
|
key = "Token"
|
||||||
if key in root_obj:
|
if key in root_obj:
|
||||||
token = root_obj[key]['Id']
|
token = root_obj[key]["Id"]
|
||||||
expire_time = root_obj[key]['ExpireTime']
|
expire_time = root_obj[key]["ExpireTime"]
|
||||||
return token, expire_time
|
return token, expire_time
|
||||||
# print(response.text)
|
# print(response.text)
|
||||||
return None, None
|
return None, None
|
||||||
@@ -70,10 +83,9 @@ class AccessToken:
|
|||||||
|
|
||||||
class TTSProvider(TTSProviderBase):
|
class TTSProvider(TTSProviderBase):
|
||||||
|
|
||||||
|
|
||||||
def __init__(self, config, delete_audio_file):
|
def __init__(self, config, delete_audio_file):
|
||||||
super().__init__(config, delete_audio_file)
|
super().__init__(config, delete_audio_file)
|
||||||
|
|
||||||
# 新增空值判断逻辑
|
# 新增空值判断逻辑
|
||||||
self.access_key_id = config.get("access_key_id")
|
self.access_key_id = config.get("access_key_id")
|
||||||
self.access_key_secret = config.get("access_key_secret")
|
self.access_key_secret = config.get("access_key_secret")
|
||||||
@@ -87,9 +99,7 @@ class TTSProvider(TTSProviderBase):
|
|||||||
self.pitch_rate = config.get("pitch_rate", 0)
|
self.pitch_rate = config.get("pitch_rate", 0)
|
||||||
self.host = config.get("host", "nls-gateway-cn-shanghai.aliyuncs.com")
|
self.host = config.get("host", "nls-gateway-cn-shanghai.aliyuncs.com")
|
||||||
self.api_url = f"https://{self.host}/stream/v1/tts"
|
self.api_url = f"https://{self.host}/stream/v1/tts"
|
||||||
self.header = {
|
self.header = {"Content-Type": "application/json"}
|
||||||
"Content-Type": "application/json"
|
|
||||||
}
|
|
||||||
|
|
||||||
if self.access_key_id and self.access_key_secret:
|
if self.access_key_id and self.access_key_secret:
|
||||||
# 使用密钥对生成临时token
|
# 使用密钥对生成临时token
|
||||||
@@ -99,35 +109,30 @@ class TTSProvider(TTSProviderBase):
|
|||||||
self.token = config.get("token")
|
self.token = config.get("token")
|
||||||
self.expire_time = None
|
self.expire_time = None
|
||||||
|
|
||||||
|
|
||||||
def _refresh_token(self):
|
def _refresh_token(self):
|
||||||
"""刷新Token并记录过期时间"""
|
"""刷新Token并记录过期时间"""
|
||||||
if self.access_key_id and self.access_key_secret:
|
if self.access_key_id and self.access_key_secret:
|
||||||
self.token, expire_time_str = AccessToken.create_token(
|
self.token, expire_time_str = AccessToken.create_token(
|
||||||
self.access_key_id,
|
self.access_key_id, self.access_key_secret
|
||||||
self.access_key_secret
|
|
||||||
)
|
)
|
||||||
if not expire_time_str:
|
if not expire_time_str:
|
||||||
raise ValueError("无法获取有效的Token过期时间")
|
raise ValueError("无法获取有效的Token过期时间")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
#统一转换为字符串处理
|
# 统一转换为字符串处理
|
||||||
expire_str = str(expire_time_str).strip()
|
expire_str = str(expire_time_str).strip()
|
||||||
|
|
||||||
if expire_str.isdigit():
|
if expire_str.isdigit():
|
||||||
expire_time = datetime.fromtimestamp(int(expire_str))
|
expire_time = datetime.fromtimestamp(int(expire_str))
|
||||||
else:
|
else:
|
||||||
expire_time = datetime.strptime(
|
expire_time = datetime.strptime(expire_str, "%Y-%m-%dT%H:%M:%SZ")
|
||||||
expire_str,
|
|
||||||
"%Y-%m-%dT%H:%M:%SZ"
|
|
||||||
)
|
|
||||||
self.expire_time = expire_time.timestamp() - 60
|
self.expire_time = expire_time.timestamp() - 60
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise ValueError(f"无效的过期时间格式: {expire_str}") from e
|
raise ValueError(f"无效的过期时间格式: {expire_str}") from e
|
||||||
|
|
||||||
else:
|
else:
|
||||||
self.expire_time = None
|
self.expire_time = None
|
||||||
|
|
||||||
if not self.token:
|
if not self.token:
|
||||||
raise ValueError("无法获取有效的访问Token")
|
raise ValueError("无法获取有效的访问Token")
|
||||||
|
|
||||||
@@ -142,8 +147,6 @@ class TTSProvider(TTSProviderBase):
|
|||||||
# f"过期时间 {datetime.fromtimestamp(self.expire_time)} | "
|
# f"过期时间 {datetime.fromtimestamp(self.expire_time)} | "
|
||||||
# f"剩余 {remaining:.2f}秒")
|
# f"剩余 {remaining:.2f}秒")
|
||||||
return time.time() > self.expire_time
|
return time.time() > self.expire_time
|
||||||
def generate_filename(self, extension=".wav"):
|
|
||||||
return os.path.join(self.output_file, f"tts-{__name__}{datetime.now().date()}@{uuid.uuid4().hex}{extension}")
|
|
||||||
|
|
||||||
async def text_to_speak(self, text, output_file):
|
async def text_to_speak(self, text, output_file):
|
||||||
if self._is_token_expired():
|
if self._is_token_expired():
|
||||||
@@ -158,21 +161,27 @@ class TTSProvider(TTSProviderBase):
|
|||||||
"voice": self.voice,
|
"voice": self.voice,
|
||||||
"volume": self.volume,
|
"volume": self.volume,
|
||||||
"speech_rate": self.speech_rate,
|
"speech_rate": self.speech_rate,
|
||||||
"pitch_rate": self.pitch_rate
|
"pitch_rate": self.pitch_rate,
|
||||||
}
|
}
|
||||||
|
|
||||||
# print(self.api_url, json.dumps(request_json, ensure_ascii=False))
|
# print(self.api_url, json.dumps(request_json, ensure_ascii=False))
|
||||||
try:
|
try:
|
||||||
resp = requests.post(self.api_url, json.dumps(request_json), headers=self.header)
|
resp = requests.post(
|
||||||
|
self.api_url, json.dumps(request_json), headers=self.header
|
||||||
|
)
|
||||||
if resp.status_code == 401: # Token过期特殊处理
|
if resp.status_code == 401: # Token过期特殊处理
|
||||||
self._refresh_token()
|
self._refresh_token()
|
||||||
resp = requests.post(self.api_url, json.dumps(request_json), headers=self.header)
|
resp = requests.post(
|
||||||
|
self.api_url, json.dumps(request_json), headers=self.header
|
||||||
|
)
|
||||||
# 检查返回请求数据的mime类型是否是audio/***,是则保存到指定路径下;返回的是binary格式的
|
# 检查返回请求数据的mime类型是否是audio/***,是则保存到指定路径下;返回的是binary格式的
|
||||||
if resp.headers['Content-Type'].startswith('audio/'):
|
if resp.headers["Content-Type"].startswith("audio/"):
|
||||||
with open(output_file, 'wb') as f:
|
with open(output_file, "wb") as f:
|
||||||
f.write(resp.content)
|
f.write(resp.content)
|
||||||
return output_file
|
return output_file
|
||||||
else:
|
else:
|
||||||
raise Exception(f"{__name__} status_code: {resp.status_code} response: {resp.content}")
|
raise Exception(
|
||||||
|
f"{__name__} status_code: {resp.status_code} response: {resp.content}"
|
||||||
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise Exception(f"{__name__} error: {e}")
|
raise Exception(f"{__name__} error: {e}")
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ from config.logger import setup_logging
|
|||||||
import queue
|
import queue
|
||||||
import os
|
import os
|
||||||
import uuid
|
import uuid
|
||||||
|
import datetime
|
||||||
import threading
|
import threading
|
||||||
from core.utils import p3
|
from core.utils import p3
|
||||||
from core.utils import textUtils
|
from core.utils import textUtils
|
||||||
@@ -57,9 +58,11 @@ class TTSProviderBase(ABC):
|
|||||||
self.processed_chars = 0
|
self.processed_chars = 0
|
||||||
self.is_first_sentence = True
|
self.is_first_sentence = True
|
||||||
|
|
||||||
@abstractmethod
|
def generate_filename(self, extension=".wav"):
|
||||||
def generate_filename(self):
|
return os.path.join(
|
||||||
pass
|
self.output_file,
|
||||||
|
f"tts-{datetime.now().date()}@{uuid.uuid4().hex}{extension}",
|
||||||
|
)
|
||||||
|
|
||||||
def to_tts(self, text):
|
def to_tts(self, text):
|
||||||
tmp_file = self.generate_filename()
|
tmp_file = self.generate_filename()
|
||||||
@@ -148,7 +151,7 @@ class TTSProviderBase(ABC):
|
|||||||
self.tts_timeout = conn.config.get("tts_timeout", 10)
|
self.tts_timeout = conn.config.get("tts_timeout", 10)
|
||||||
# tts 消化线程
|
# tts 消化线程
|
||||||
self.tts_priority_thread = threading.Thread(
|
self.tts_priority_thread = threading.Thread(
|
||||||
target=self._tts_text_priority_thread, daemon=True
|
target=self.tts_text_priority_thread, daemon=True
|
||||||
)
|
)
|
||||||
self.tts_priority_thread.start()
|
self.tts_priority_thread.start()
|
||||||
|
|
||||||
@@ -160,7 +163,7 @@ class TTSProviderBase(ABC):
|
|||||||
|
|
||||||
# 这里默认是非流式的处理方式
|
# 这里默认是非流式的处理方式
|
||||||
# 流式处理方式请在子类中重写
|
# 流式处理方式请在子类中重写
|
||||||
def _tts_text_priority_thread(self):
|
def tts_text_priority_thread(self):
|
||||||
while not self.conn.stop_event.is_set():
|
while not self.conn.stop_event.is_set():
|
||||||
try:
|
try:
|
||||||
message = self.tts_text_queue.get(timeout=1)
|
message = self.tts_text_queue.get(timeout=1)
|
||||||
@@ -219,9 +222,6 @@ class TTSProviderBase(ABC):
|
|||||||
self.conn.loop,
|
self.conn.loop,
|
||||||
)
|
)
|
||||||
result = future.result()
|
result = future.result()
|
||||||
logger.bind(tag=TAG).info(
|
|
||||||
f"audio_play_priority result: {text} {result}"
|
|
||||||
)
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.bind(tag=TAG).error(
|
logger.bind(tag=TAG).error(
|
||||||
f"audio_play_priority priority_thread: {text} {e}"
|
f"audio_play_priority priority_thread: {text} {e}"
|
||||||
|
|||||||
@@ -21,12 +21,6 @@ class TTSProvider(TTSProviderBase):
|
|||||||
self.host = "api.coze.cn"
|
self.host = "api.coze.cn"
|
||||||
self.api_url = f"https://{self.host}/v1/audio/speech"
|
self.api_url = f"https://{self.host}/v1/audio/speech"
|
||||||
|
|
||||||
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):
|
async def text_to_speak(self, text, output_file):
|
||||||
request_json = {
|
request_json = {
|
||||||
"model": self.model,
|
"model": self.model,
|
||||||
@@ -47,4 +41,4 @@ class TTSProvider(TTSProviderBase):
|
|||||||
file_to_save = open(output_file, "wb")
|
file_to_save = open(output_file, "wb")
|
||||||
file_to_save.write(data)
|
file_to_save.write(data)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise Exception(f"{__name__} error: {e}")
|
raise Exception(f"{__name__} error: {e}")
|
||||||
|
|||||||
@@ -41,12 +41,6 @@ class TTSProvider(TTSProviderBase):
|
|||||||
self.header = {"Authorization": f"{self.authorization}{self.access_token}"}
|
self.header = {"Authorization": f"{self.authorization}{self.access_token}"}
|
||||||
check_model_key("TTS", self.access_token)
|
check_model_key("TTS", self.access_token)
|
||||||
|
|
||||||
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):
|
async def text_to_speak(self, text, output_file):
|
||||||
request_json = {
|
request_json = {
|
||||||
"app": {
|
"app": {
|
||||||
|
|||||||
@@ -133,12 +133,6 @@ class TTSProvider(TTSProviderBase):
|
|||||||
self.seed = int(config.get("seed")) if config.get("seed") else None
|
self.seed = int(config.get("seed")) if config.get("seed") else None
|
||||||
self.api_url = config.get("api_url", "http://127.0.0.1:8080/v1/tts")
|
self.api_url = config.get("api_url", "http://127.0.0.1:8080/v1/tts")
|
||||||
|
|
||||||
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):
|
async def text_to_speak(self, text, output_file):
|
||||||
# Prepare reference data
|
# Prepare reference data
|
||||||
byte_audios = [audio_to_bytes(ref_audio) for ref_audio in self.reference_audio]
|
byte_audios = [audio_to_bytes(ref_audio) for ref_audio in self.reference_audio]
|
||||||
|
|||||||
@@ -71,12 +71,6 @@ class TTSProvider(TTSProviderBase):
|
|||||||
config.get("aux_ref_audio_paths")
|
config.get("aux_ref_audio_paths")
|
||||||
)
|
)
|
||||||
|
|
||||||
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):
|
async def text_to_speak(self, text, output_file):
|
||||||
request_json = {
|
request_json = {
|
||||||
"text": text,
|
"text": text,
|
||||||
|
|||||||
@@ -36,12 +36,6 @@ class TTSProvider(TTSProviderBase):
|
|||||||
self.inp_refs = parse_string_to_list(config.get("inp_refs"))
|
self.inp_refs = parse_string_to_list(config.get("inp_refs"))
|
||||||
self.if_sr = str(config.get("if_sr", False)).lower() in ("true", "1", "yes")
|
self.if_sr = str(config.get("if_sr", False)).lower() in ("true", "1", "yes")
|
||||||
|
|
||||||
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):
|
async def text_to_speak(self, text, output_file):
|
||||||
request_params = {
|
request_params = {
|
||||||
"refer_wav_path": self.refer_wav_path,
|
"refer_wav_path": self.refer_wav_path,
|
||||||
@@ -67,4 +61,3 @@ class TTSProvider(TTSProviderBase):
|
|||||||
error_msg = f"GPT_SoVITS_V3 TTS请求失败: {resp.status_code} - {resp.text}"
|
error_msg = f"GPT_SoVITS_V3 TTS请求失败: {resp.status_code} - {resp.text}"
|
||||||
logger.bind(tag=TAG).error(error_msg)
|
logger.bind(tag=TAG).error(error_msg)
|
||||||
raise Exception(error_msg)
|
raise Exception(error_msg)
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import websockets
|
|||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from config.logger import setup_logging
|
from config.logger import setup_logging
|
||||||
from core.providers.tts.base import TTSProviderBase
|
from core.providers.tts.base import TTSProviderBase
|
||||||
|
from core.providers.tts.dto.dto import TTSMessageDTO, SentenceType, ContentType
|
||||||
from core.utils.util import pcm_to_data
|
from core.utils.util import pcm_to_data
|
||||||
|
|
||||||
TAG = __name__
|
TAG = __name__
|
||||||
@@ -149,43 +150,6 @@ class TTSProvider(TTSProviderBase):
|
|||||||
self.start_connection_flag = False
|
self.start_connection_flag = False
|
||||||
self.tts_text = ""
|
self.tts_text = ""
|
||||||
|
|
||||||
async def open_audio_channels(self, conn):
|
|
||||||
await super().open_audio_channels(conn)
|
|
||||||
ws_header = {
|
|
||||||
"X-Api-App-Key": self.appId,
|
|
||||||
"X-Api-Access-Key": self.access_token,
|
|
||||||
"X-Api-Resource-Id": self.resource_id,
|
|
||||||
"X-Api-Connect-Id": uuid.uuid4(),
|
|
||||||
}
|
|
||||||
self.ws = await websockets.connect(
|
|
||||||
self.ws_url, additional_headers=ws_header, max_size=1000000000
|
|
||||||
)
|
|
||||||
tts_priority = threading.Thread(
|
|
||||||
target=self._start_monitor_tts_response_thread, daemon=True
|
|
||||||
)
|
|
||||||
tts_priority.start()
|
|
||||||
|
|
||||||
def generate_filename(self, extension=".wav"):
|
|
||||||
return os.path.join(
|
|
||||||
self.output_file,
|
|
||||||
f"tts-{datetime.now().date()}@{uuid.uuid4().hex}{extension}",
|
|
||||||
)
|
|
||||||
|
|
||||||
def to_tts(self, text):
|
|
||||||
future = asyncio.run_coroutine_threadsafe(
|
|
||||||
self.start_session(self.conn.sentence_id), loop=self.conn.loop
|
|
||||||
)
|
|
||||||
future.result()
|
|
||||||
future = asyncio.run_coroutine_threadsafe(
|
|
||||||
self.text_to_speak(text, None), loop=self.conn.loop
|
|
||||||
)
|
|
||||||
future.result()
|
|
||||||
future = asyncio.run_coroutine_threadsafe(
|
|
||||||
self.finish_session(self.conn.sentence_id), loop=self.conn.loop
|
|
||||||
)
|
|
||||||
future.result()
|
|
||||||
return self.interface_type.value
|
|
||||||
|
|
||||||
async def send_event(
|
async def send_event(
|
||||||
self, header: bytes, optional: bytes | None = None, payload: bytes = None
|
self, header: bytes, optional: bytes | None = None, payload: bytes = None
|
||||||
):
|
):
|
||||||
@@ -360,14 +324,65 @@ class TTSProvider(TTSProviderBase):
|
|||||||
self.start_connection_flag = False
|
self.start_connection_flag = False
|
||||||
await self.start_connection()
|
await self.start_connection()
|
||||||
self.start_connection_flag = True
|
self.start_connection_flag = True
|
||||||
await super().reset()
|
|
||||||
|
|
||||||
async def close(self):
|
async def close(self):
|
||||||
super().close()
|
|
||||||
"""资源清理方法"""
|
"""资源清理方法"""
|
||||||
await self.finish_connection()
|
await self.finish_connection()
|
||||||
await self.ws.close()
|
await self.ws.close()
|
||||||
|
|
||||||
|
###################################################################################
|
||||||
|
# 以下是火山双流式TTS重写父类的方法
|
||||||
|
###################################################################################
|
||||||
|
|
||||||
|
async def open_audio_channels(self, conn):
|
||||||
|
await super().open_audio_channels(conn)
|
||||||
|
ws_header = {
|
||||||
|
"X-Api-App-Key": self.appId,
|
||||||
|
"X-Api-Access-Key": self.access_token,
|
||||||
|
"X-Api-Resource-Id": self.resource_id,
|
||||||
|
"X-Api-Connect-Id": uuid.uuid4(),
|
||||||
|
}
|
||||||
|
self.ws = await websockets.connect(
|
||||||
|
self.ws_url, additional_headers=ws_header, max_size=1000000000
|
||||||
|
)
|
||||||
|
tts_priority = threading.Thread(
|
||||||
|
target=self._start_monitor_tts_response_thread, daemon=True
|
||||||
|
)
|
||||||
|
tts_priority.start()
|
||||||
|
|
||||||
|
def tts_text_priority_thread(self):
|
||||||
|
while not self.conn.stop_event.is_set():
|
||||||
|
try:
|
||||||
|
message = self.tts_text_queue.get(timeout=1)
|
||||||
|
if message.sentence_type == SentenceType.FIRST:
|
||||||
|
# 初始化参数
|
||||||
|
future = asyncio.run_coroutine_threadsafe(
|
||||||
|
self.start_session(self.conn.sentence_id), loop=self.conn.loop
|
||||||
|
)
|
||||||
|
future.result()
|
||||||
|
elif ContentType.TEXT == message.content_type:
|
||||||
|
if message.content_detail:
|
||||||
|
future = asyncio.run_coroutine_threadsafe(
|
||||||
|
self.text_to_speak(message.content_detail, None),
|
||||||
|
loop=self.conn.loop,
|
||||||
|
)
|
||||||
|
future.result()
|
||||||
|
elif ContentType.FILE == message.content_type:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if message.sentence_type == SentenceType.LAST:
|
||||||
|
future = asyncio.run_coroutine_threadsafe(
|
||||||
|
self.finish_session(self.conn.sentence_id), loop=self.conn.loop
|
||||||
|
)
|
||||||
|
future.result()
|
||||||
|
|
||||||
|
except queue.Empty:
|
||||||
|
continue
|
||||||
|
except Exception as e:
|
||||||
|
logger.bind(tag=TAG).error(
|
||||||
|
f"处理TTS文本失败: {str(e)}, 类型: {type(e).__name__}, 堆栈: {traceback.format_exc()}"
|
||||||
|
)
|
||||||
|
|
||||||
async def text_to_speak(self, text, _):
|
async def text_to_speak(self, text, _):
|
||||||
# 发送文本
|
# 发送文本
|
||||||
await self.send_text(self.speaker, text, self.conn.sentence_id)
|
await self.send_text(self.speaker, text, self.conn.sentence_id)
|
||||||
|
|||||||
@@ -29,12 +29,6 @@ class TTSProvider(TTSProviderBase):
|
|||||||
self.output_file = config.get("output_dir", "tmp/")
|
self.output_file = config.get("output_dir", "tmp/")
|
||||||
check_model_key("TTS", self.api_key)
|
check_model_key("TTS", self.api_key)
|
||||||
|
|
||||||
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):
|
async def text_to_speak(self, text, output_file):
|
||||||
headers = {
|
headers = {
|
||||||
"Authorization": f"Bearer {self.api_key}",
|
"Authorization": f"Bearer {self.api_key}",
|
||||||
|
|||||||
@@ -22,12 +22,6 @@ class TTSProvider(TTSProviderBase):
|
|||||||
self.host = "api.siliconflow.cn"
|
self.host = "api.siliconflow.cn"
|
||||||
self.api_url = f"https://{self.host}/v1/audio/speech"
|
self.api_url = f"https://{self.host}/v1/audio/speech"
|
||||||
|
|
||||||
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):
|
async def text_to_speak(self, text, output_file):
|
||||||
request_json = {
|
request_json = {
|
||||||
"model": self.model,
|
"model": self.model,
|
||||||
@@ -47,4 +41,4 @@ class TTSProvider(TTSProviderBase):
|
|||||||
file_to_save = open(output_file, "wb")
|
file_to_save = open(output_file, "wb")
|
||||||
file_to_save.write(data)
|
file_to_save.write(data)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise Exception(f"{__name__} error: {e}")
|
raise Exception(f"{__name__} error: {e}")
|
||||||
|
|||||||
@@ -121,12 +121,6 @@ class TTSProvider(TTSProviderBase):
|
|||||||
msg = msg.encode("utf-8")
|
msg = msg.encode("utf-8")
|
||||||
return hmac.new(key, msg, hashlib.sha256).digest()
|
return hmac.new(key, msg, hashlib.sha256).digest()
|
||||||
|
|
||||||
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):
|
async def text_to_speak(self, text, output_file):
|
||||||
# 构建请求体
|
# 构建请求体
|
||||||
request_json = {
|
request_json = {
|
||||||
|
|||||||
Reference in New Issue
Block a user