mirror of
https://github.com/xinnan-tech/xiaozhi-esp32-server.git
synced 2026-07-22 23:23:55 +08:00
+2








dd5fecdf5a
* 新增openai tts (#137) On branch openai_tts Changes to be committed: modified: config.yaml new file: core/providers/tts/openai_tts.py Co-authored-by: 欣南科技 <huangrongzhuang@xin-nan.com> * Update config.yaml (#201) * 修改了coze的实现方式,使用了v3接口,修复了只能对话一次的bug * 使用mem0ai api实现记忆功能 * Fix docker space (#179) * update:调试docker编译缓存 * update:调试docker编译 * update:调试docker编译容量 * update:调试docker编译 --------- Co-authored-by: hrz <1710360675@qq.com> * TTS处理增加index信息,完善TTS出错时的处理 (#177) * TTS处理增加index信息,完善TTS出错时的处理 * 第一句语音index处理,歌曲播放index处理 * update:去掉了无用变量,修复continuebug --------- Co-authored-by: hrz <1710360675@qq.com> * update:更新demo (#180) Co-authored-by: hrz <1710360675@qq.com> * 优化chat中异步调用方式 * 记忆增加时间,以便大模型判断先后关系 * 记忆对时间戳排序,便于梳理前后关系 * add: 不跟踪music下的文件的更改 (#187) * Mem0ai (#199) * update:增加mem0ai包依赖 * update:文档增加mem0ai的描述 --------- Co-authored-by: hrz <1710360675@qq.com> * Update config.yaml * Update config.yaml * Update config.yaml * Update config.yaml --------- Co-authored-by: strix214 <a1425699467@gmail.com> Co-authored-by: 玄凤科技 <eric230308@gmail.com> Co-authored-by: HonestQiao <honestqiao@163.com> Co-authored-by: 欣南科技 <huangrongzhuang@xin-nan.com> Co-authored-by: hrz <1710360675@qq.com> Co-authored-by: TOM88812 <ttangxs0808@163.com> Co-authored-by: 香草味的纳西妲 <151599587+NyaOH-Nahida@users.noreply.github.com> * update:添加忽略文件 * feat: 增加fastgpt适配:支持think、variables参数 (#213) * 修改了coze的实现方式,使用了v3接口,修复了只能对话一次的bug * 使用mem0ai api实现记忆功能 * Fix docker space (#179) * update:调试docker编译缓存 * update:调试docker编译 * update:调试docker编译容量 * update:调试docker编译 --------- Co-authored-by: hrz <1710360675@qq.com> * TTS处理增加index信息,完善TTS出错时的处理 (#177) * TTS处理增加index信息,完善TTS出错时的处理 * 第一句语音index处理,歌曲播放index处理 * update:去掉了无用变量,修复continuebug --------- Co-authored-by: hrz <1710360675@qq.com> * update:更新demo (#180) Co-authored-by: hrz <1710360675@qq.com> * 优化chat中异步调用方式 * 记忆增加时间,以便大模型判断先后关系 * 记忆对时间戳排序,便于梳理前后关系 * add: 不跟踪music下的文件的更改 (#187) * Mem0ai (#199) * update:增加mem0ai包依赖 * update:文档增加mem0ai的描述 --------- Co-authored-by: hrz <1710360675@qq.com> * feat: 增加fastgpt适配:支持think、variables参数 --------- Co-authored-by: strix214 <a1425699467@gmail.com> Co-authored-by: 玄凤科技 <eric230308@gmail.com> Co-authored-by: HonestQiao <honestqiao@163.com> Co-authored-by: 欣南科技 <huangrongzhuang@xin-nan.com> Co-authored-by: hrz <1710360675@qq.com> Co-authored-by: TOM88812 <ttangxs0808@163.com> Co-authored-by: 香草味的纳西妲 <151599587+NyaOH-Nahida@users.noreply.github.com> Co-authored-by: pt <ptsghs@163.com> * update:合并最新代码 * update:合并群友pr * update:openaitts 测试通过 * update:ACGNTTS测试完成 --------- Co-authored-by: Chris <119588753+Chris-websketch@users.noreply.github.com> Co-authored-by: ikun441 <3497689533@qq.com> Co-authored-by: strix214 <a1425699467@gmail.com> Co-authored-by: 玄凤科技 <eric230308@gmail.com> Co-authored-by: HonestQiao <honestqiao@163.com> Co-authored-by: hrz <1710360675@qq.com> Co-authored-by: TOM88812 <ttangxs0808@163.com> Co-authored-by: 香草味的纳西妲 <151599587+NyaOH-Nahida@users.noreply.github.com> Co-authored-by: ptisnoob <49125305+ptisnoob@users.noreply.github.com> Co-authored-by: pt <ptsghs@163.com>
160 lines
5.6 KiB
Python
160 lines
5.6 KiB
Python
import base64
|
|
import os
|
|
import uuid
|
|
import requests
|
|
import ormsgpack
|
|
from pathlib import Path
|
|
from pydantic import BaseModel, Field, conint, model_validator
|
|
from typing_extensions import Annotated
|
|
from datetime import datetime
|
|
from typing import Literal
|
|
from core.utils.util import check_model_key
|
|
from core.providers.tts.base import TTSProviderBase
|
|
from config.logger import setup_logging
|
|
|
|
TAG = __name__
|
|
logger = setup_logging()
|
|
|
|
|
|
class ServeReferenceAudio(BaseModel):
|
|
audio: bytes
|
|
text: str
|
|
|
|
@model_validator(mode="before")
|
|
def decode_audio(cls, values):
|
|
audio = values.get("audio")
|
|
if (
|
|
isinstance(audio, str) and len(audio) > 255
|
|
): # Check if audio is a string (Base64)
|
|
try:
|
|
values["audio"] = base64.b64decode(audio)
|
|
except Exception as e:
|
|
# If the audio is not a valid base64 string, we will just ignore it and let the server handle it
|
|
pass
|
|
return values
|
|
|
|
def __repr__(self) -> str:
|
|
return f"ServeReferenceAudio(text={self.text!r}, audio_size={len(self.audio)})"
|
|
|
|
|
|
class ServeTTSRequest(BaseModel):
|
|
text: str
|
|
chunk_length: Annotated[int, conint(ge=100, le=300, strict=True)] = 200
|
|
# Audio format
|
|
format: Literal["wav", "pcm", "mp3"] = "wav"
|
|
# References audios for in-context learning
|
|
references: list[ServeReferenceAudio] = []
|
|
# Reference id
|
|
# For example, if you want use https://fish.audio/m/7f92f8afb8ec43bf81429cc1c9199cb1/
|
|
# Just pass 7f92f8afb8ec43bf81429cc1c9199cb1
|
|
reference_id: str | None = None
|
|
seed: int | None = None
|
|
use_memory_cache: Literal["on", "off"] = "off"
|
|
# Normalize text for en & zh, this increase stability for numbers
|
|
normalize: bool = True
|
|
# not usually used below
|
|
streaming: bool = False
|
|
max_new_tokens: int = 1024
|
|
top_p: Annotated[float, Field(ge=0.1, le=1.0, strict=True)] = 0.7
|
|
repetition_penalty: Annotated[float, Field(ge=0.9, le=2.0, strict=True)] = 1.2
|
|
temperature: Annotated[float, Field(ge=0.1, le=1.0, strict=True)] = 0.7
|
|
|
|
class Config:
|
|
# Allow arbitrary types for pytorch related types
|
|
arbitrary_types_allowed = True
|
|
|
|
|
|
def audio_to_bytes(file_path):
|
|
if not file_path or not Path(file_path).exists():
|
|
return None
|
|
with open(file_path, "rb") as wav_file:
|
|
wav = wav_file.read()
|
|
return wav
|
|
|
|
|
|
def read_ref_text(ref_text):
|
|
path = Path(ref_text)
|
|
if path.exists() and path.is_file():
|
|
with path.open("r", encoding="utf-8") as file:
|
|
return file.read()
|
|
return ref_text
|
|
|
|
|
|
class TTSProvider(TTSProviderBase):
|
|
|
|
def __init__(self, config, delete_audio_file):
|
|
super().__init__(config, delete_audio_file)
|
|
|
|
self.reference_id = config.get("reference_id")
|
|
self.reference_audio = config.get("reference_audio", [])
|
|
self.reference_text = config.get("reference_text", [])
|
|
self.format = config.get("format", "wav")
|
|
self.channels = config.get("channels", 1)
|
|
self.rate = config.get("rate", 44100)
|
|
self.api_key = config.get("api_key", "YOUR_API_KEY")
|
|
have_key = check_model_key("FishSpeech TTS", self.api_key)
|
|
if not have_key:
|
|
return
|
|
self.normalize = config.get("normalize", True)
|
|
self.max_new_tokens = config.get("max_new_tokens", 1024)
|
|
self.chunk_length = config.get("chunk_length", 200)
|
|
self.top_p = config.get("top_p", 0.7)
|
|
self.repetition_penalty = config.get("repetition_penalty", 1.2)
|
|
self.temperature = config.get("temperature", 0.7)
|
|
self.streaming = config.get("streaming", False)
|
|
self.use_memory_cache = config.get("use_memory_cache", "on")
|
|
self.seed = config.get("seed")
|
|
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):
|
|
# Prepare reference data
|
|
byte_audios = [audio_to_bytes(ref_audio) for ref_audio in self.reference_audio]
|
|
ref_texts = [read_ref_text(ref_text) for ref_text in self.reference_text]
|
|
|
|
data = {
|
|
"text": text,
|
|
"references": [
|
|
ServeReferenceAudio(
|
|
audio=audio if audio else b"", text=text
|
|
)
|
|
for text, audio in zip(ref_texts, byte_audios)
|
|
],
|
|
"reference_id": self.reference_id,
|
|
"normalize": self.normalize,
|
|
"format": self.format,
|
|
"max_new_tokens": self.max_new_tokens,
|
|
"chunk_length": self.chunk_length,
|
|
"top_p": self.top_p,
|
|
"repetition_penalty": self.repetition_penalty,
|
|
"temperature": self.temperature,
|
|
"streaming": self.streaming,
|
|
"use_memory_cache": self.use_memory_cache,
|
|
"seed": self.seed,
|
|
}
|
|
|
|
pydantic_data = ServeTTSRequest(**data)
|
|
|
|
response = requests.post(
|
|
self.api_url,
|
|
data=ormsgpack.packb(pydantic_data, option=ormsgpack.OPT_SERIALIZE_PYDANTIC),
|
|
headers={
|
|
"Authorization": f"Bearer {self.api_key}",
|
|
"Content-Type": "application/msgpack",
|
|
},
|
|
)
|
|
|
|
if response.status_code == 200:
|
|
audio_content = response.content
|
|
|
|
with open(output_file, "wb") as audio_file:
|
|
audio_file.write(audio_content)
|
|
|
|
|
|
|
|
else:
|
|
print(f"Request failed with status code {response.status_code}")
|
|
print(response.json())
|