diff --git a/main/manager-api/src/main/resources/db/changelog/202602271137.sql b/main/manager-api/src/main/resources/db/changelog/202602271137.sql new file mode 100644 index 00000000..010acd8f --- /dev/null +++ b/main/manager-api/src/main/resources/db/changelog/202602271137.sql @@ -0,0 +1,42 @@ +-- 更新腾讯TTS供应器配置,增加speed、volume和format参数 +UPDATE `ai_model_provider` +SET fields = '[{"key":"appid","label":"应用ID","type":"string"},{"key":"secret_id","label":"Secret ID","type":"string"},{"key":"secret_key","label":"Secret Key","type":"string"},{"key":"format","label":"音频格式","type":"string"},{"key":"speed","label":"语速","type":"number"},{"key":"volume","label":"音量","type":"number"},{"key":"output_dir","label":"输出目录","type":"string"},{"key":"voice","label":"音色ID","type":"string"},{"key":"region","label":"区域","type":"string"}]' +WHERE id = 'SYSTEM_TTS_TencentTTS'; + +-- 更新腾讯TTS模型配置,增加speed和volume参数,补充参数说明 +UPDATE `ai_model_config` SET + `config_json` = JSON_SET(`config_json`, '$.speed', 0, '$.volume', 0), + `remark` = '腾讯TTS配置说明: +1. 需要在腾讯云平台开通智能语音交互服务 +2. 支持多种音色,当前配置使用101001 +3. 需要网络连接 +4. 输出文件保存在tmp/目录 +申请步骤: +1. 访问 https://console.cloud.tencent.com/cam/capi 获取密钥 +2. 访问 https://console.cloud.tencent.com/tts/resourcebundle 领取免费资源 +3. 创建新应用 +4. 获取appid、secret_id和secret_key +5. 填入配置文件中 +音频参数: +- format: 音频格式,支持pcm、wav、mp3 +- speed: 语速,范围-2~6,默认0 +- volume: 音量,范围-10~10,默认0' +WHERE `id` = 'TTS_TencentTTS'; + +-- 更新CozeCnTTS供应器配置,增加speed和loudness_rate参数 +UPDATE `ai_model_provider` +SET fields = '[{"key":"voice","label":"音色","type":"string"},{"key":"access_token","label":"访问令牌","type":"string"},{"key":"speed","label":"语速","type":"number"},{"key":"loudness_rate","label":"音量增益","type":"number"},{"key":"output_dir","label":"输出目录","type":"string"},{"key":"response_format","label":"响应格式","type":"string"}]' +WHERE id = 'SYSTEM_TTS_cozecn'; + +-- 更新CozeCnTTS模型配置,增加speed和loudness_rate参数,补充参数说明 +UPDATE `ai_model_config` SET + `config_json` = JSON_SET(`config_json`, '$.speed', 1, '$.loudness_rate', 0), + `remark` = 'Coze中文语音合成配置说明: +1. 访问 https://www.coze.cn/ 注册并登录 +2. 创建应用并获取access_token +3. 选择合适的音色ID +音频参数: +- response_format: 音频格式,支持pcm、wav、mp3 +- speed: 语速,范围0.5~2,默认1 +- loudness_rate: 音量增益,范围-50~100,默认0' +WHERE `id` = 'TTS_CozeCnTTS'; diff --git a/main/manager-api/src/main/resources/db/changelog/db.changelog-master.yaml b/main/manager-api/src/main/resources/db/changelog/db.changelog-master.yaml index e2ce2412..606cefa7 100755 --- a/main/manager-api/src/main/resources/db/changelog/db.changelog-master.yaml +++ b/main/manager-api/src/main/resources/db/changelog/db.changelog-master.yaml @@ -536,3 +536,10 @@ databaseChangeLog: - sqlFile: encoding: utf8 path: classpath:db/changelog/202602061650.sql + - changeSet: + id: 202602271137 + author: RanChen + changes: + - sqlFile: + encoding: utf8 + path: classpath:db/changelog/202602271137.sql diff --git a/main/xiaozhi-server/config.yaml b/main/xiaozhi-server/config.yaml index 81c5c0b1..c4662afa 100644 --- a/main/xiaozhi-server/config.yaml +++ b/main/xiaozhi-server/config.yaml @@ -813,6 +813,9 @@ TTS: access_token: 你的coze web key response_format: wav # language: "中文" # 指定输出语种,如:中文、英语、日语、韩语等,请根据所选音色支持的语言进行设置,不填则默认为中文 + # 以下可不用设置,使用默认设置 + # speed: 1 # 语速:-0.5到2 + # loudness_rate: 0 # 音量:-50到100 VolcesAiGatewayTTS: type: openai # 火山引擎 - 边缘大模型网关 @@ -984,7 +987,10 @@ TTS: region: ap-guangzhou voice: 101001 # language: "中文" # 指定输出语种,如:中文、英语、日语、韩语等,请根据所选音色支持的语言进行设置,不填则默认为中文 - + # 以下可不用设置,使用默认设置 + # format: wav # 音频格式:pcm、wav、mp3 + # volume: 0 # 音量:-10到10 + # speech_rate: 0 # 语速:-2到6 TTS302AI: # 302AI语音合成服务,需要先在302平台创建账户充值,并获取密钥信息 # 添加 302.ai TTS 配置 diff --git a/main/xiaozhi-server/core/providers/tts/aliyun.py b/main/xiaozhi-server/core/providers/tts/aliyun.py index 05ea2031..2dc39a9d 100644 --- a/main/xiaozhi-server/core/providers/tts/aliyun.py +++ b/main/xiaozhi-server/core/providers/tts/aliyun.py @@ -100,7 +100,6 @@ class TTSProvider(TTSProviderBase): self.access_key_secret = config.get("access_key_secret") self.appkey = config.get("appkey") - self.format = config.get("format", "wav") self.audio_file_type = config.get("format", "wav") if config.get("private_voice"): @@ -179,7 +178,7 @@ class TTSProvider(TTSProviderBase): "appkey": self.appkey, "token": self.token, "text": text, - "format": self.format, + "format": self.audio_file_type, "sample_rate": self.conn.sample_rate, "voice": self.voice, "volume": self.volume, diff --git a/main/xiaozhi-server/core/providers/tts/aliyun_stream.py b/main/xiaozhi-server/core/providers/tts/aliyun_stream.py index 8fd20084..0e17a229 100644 --- a/main/xiaozhi-server/core/providers/tts/aliyun_stream.py +++ b/main/xiaozhi-server/core/providers/tts/aliyun_stream.py @@ -103,7 +103,6 @@ class TTSProvider(TTSProviderBase): self.access_key_secret = config.get("access_key_secret") self.appkey = config.get("appkey") self.format = config.get("format", "pcm") - self.audio_file_type = config.get("format", "pcm") # 音色配置 - CosyVoice大模型音色 if config.get("private_voice"): diff --git a/main/xiaozhi-server/core/providers/tts/cozecn.py b/main/xiaozhi-server/core/providers/tts/cozecn.py index 6c5d0547..1d727a9d 100644 --- a/main/xiaozhi-server/core/providers/tts/cozecn.py +++ b/main/xiaozhi-server/core/providers/tts/cozecn.py @@ -3,6 +3,11 @@ from core.providers.tts.base import TTSProviderBase class TTSProvider(TTSProviderBase): + TTS_PARAM_CONFIG = [ + ("speed", "speed", 0.5, 2, 1, lambda v: round(float(v), 1)), + ("loudness_rate", "loudness_rate", -50, 100, 0, int), + ] + def __init__(self, config, delete_audio_file): super().__init__(config, delete_audio_file) self.model = config.get("model") @@ -11,17 +16,29 @@ class TTSProvider(TTSProviderBase): self.voice = config.get("private_voice") else: self.voice = config.get("voice") - self.response_format = config.get("response_format", "wav") self.audio_file_type = config.get("response_format", "wav") self.host = "api.coze.cn" self.api_url = f"https://{self.host}/v1/audio/speech" + # 音频参数配置 + speed = config.get("speed", "0") + self.speed = int(speed) if speed else 0 + + loudness_rate = config.get("loudness_rate", "0") + self.loudness_rate = int(loudness_rate) if loudness_rate else 0 + + # 应用百分比调整(如果存在),否则使用公有化配置 + self._apply_percentage_params(config) + async def text_to_speak(self, text, output_file): request_json = { "model": self.model, "input": text, "voice_id": self.voice, - "response_format": self.response_format, + "response_format": self.audio_file_type, + "sample_rate": self.conn.sample_rate, + "speed": self.speed, + "loudness_rate": self.loudness_rate, } headers = { "Authorization": f"Bearer {self.access_token}", diff --git a/main/xiaozhi-server/core/providers/tts/custom.py b/main/xiaozhi-server/core/providers/tts/custom.py index b417825e..5e6a43ac 100644 --- a/main/xiaozhi-server/core/providers/tts/custom.py +++ b/main/xiaozhi-server/core/providers/tts/custom.py @@ -15,7 +15,6 @@ class TTSProvider(TTSProviderBase): self.url = config.get("url") self.method = config.get("method", "GET") self.headers = config.get("headers", {}) - self.format = config.get("format", "wav") self.audio_file_type = config.get("format", "wav") self.output_file = config.get("output_dir", "tmp/") self.params = config.get("params") @@ -29,7 +28,7 @@ class TTSProvider(TTSProviderBase): raise TypeError("Custom TTS配置参数出错, 请参考配置说明") def generate_filename(self): - return os.path.join(self.output_file, f"tts-{datetime.now().date()}@{uuid.uuid4().hex}.{self.format}") + return os.path.join(self.output_file, f"tts-{datetime.now().date()}@{uuid.uuid4().hex}.{self.audio_file_type}") async def text_to_speak(self, text, output_file): request_params = {} diff --git a/main/xiaozhi-server/core/providers/tts/fishspeech.py b/main/xiaozhi-server/core/providers/tts/fishspeech.py index 83b3bc3f..163ba2c1 100644 --- a/main/xiaozhi-server/core/providers/tts/fishspeech.py +++ b/main/xiaozhi-server/core/providers/tts/fishspeech.py @@ -91,7 +91,6 @@ class TTSProvider(TTSProviderBase): self.reference_text = parse_string_to_list( config.get('ref_text')if config.get('ref_text') else config.get("reference_text") ) - self.format = config.get("response_format", "wav") self.audio_file_type = config.get("response_format", "wav") self.api_key = config.get("api_key", "YOUR_API_KEY") model_key_msg = check_model_key("FishSpeech TTS", self.api_key) @@ -148,7 +147,7 @@ class TTSProvider(TTSProviderBase): ], "reference_id": self.reference_id, "normalize": self.normalize, - "format": self.format, + "format": self.audio_file_type, "max_new_tokens": self.max_new_tokens, "chunk_length": self.chunk_length, "top_p": self.top_p, diff --git a/main/xiaozhi-server/core/providers/tts/openai.py b/main/xiaozhi-server/core/providers/tts/openai.py index 20bf9a96..40a0134e 100644 --- a/main/xiaozhi-server/core/providers/tts/openai.py +++ b/main/xiaozhi-server/core/providers/tts/openai.py @@ -8,6 +8,10 @@ logger = setup_logging() class TTSProvider(TTSProviderBase): + TTS_PARAM_CONFIG = [ + ("ttsRate", "speed", 0.25, 4, 1, lambda v: round(float(v), 2)), + ] + def __init__(self, config, delete_audio_file): super().__init__(config, delete_audio_file) self.api_key = config.get("api_key") @@ -17,13 +21,15 @@ class TTSProvider(TTSProviderBase): self.voice = config.get("private_voice") else: self.voice = config.get("voice", "alloy") - self.response_format = config.get("format", "wav") self.audio_file_type = config.get("format", "wav") # 处理空字符串的情况 speed = config.get("speed", "1.0") self.speed = float(speed) if speed else 1.0 + # 应用百分比调整(如果存在),否则使用公有化配置 + self._apply_percentage_params(config) + self.output_file = config.get("output_dir", "tmp/") model_key_msg = check_model_key("TTS", self.api_key) if model_key_msg: @@ -38,7 +44,7 @@ class TTSProvider(TTSProviderBase): "model": self.model, "input": text, "voice": self.voice, - "response_format": "wav", + "response_format": self.audio_file_type, "speed": self.speed, } response = requests.post(self.api_url, json=data, headers=headers) diff --git a/main/xiaozhi-server/core/providers/tts/siliconflow.py b/main/xiaozhi-server/core/providers/tts/siliconflow.py index 48b709d6..b5c618ec 100644 --- a/main/xiaozhi-server/core/providers/tts/siliconflow.py +++ b/main/xiaozhi-server/core/providers/tts/siliconflow.py @@ -5,7 +5,7 @@ from core.providers.tts.base import TTSProviderBase class TTSProvider(TTSProviderBase): TTS_PARAM_CONFIG = [ ("ttsVolume", "gain", -10, 10, 0, int), - ("ttsRate", "speed", 0.25, 4, 1, lambda v: round(float(v), 1)), + ("ttsRate", "speed", 0.25, 4, 1, lambda v: round(float(v), 2)), ] def __init__(self, config, delete_audio_file): @@ -16,7 +16,6 @@ class TTSProvider(TTSProviderBase): self.voice = config.get("private_voice") else: self.voice = config.get("voice") - self.response_format = config.get("response_format", "mp3") self.audio_file_type = config.get("response_format", "mp3") self.speed = float(config.get("speed", 1.0)) self.gain = config.get("gain") @@ -32,7 +31,7 @@ class TTSProvider(TTSProviderBase): "model": self.model, "input": text, "voice": self.voice, - "response_format": self.response_format, + "response_format": self.audio_file_type, } headers = { "Authorization": f"Bearer {self.access_token}", diff --git a/main/xiaozhi-server/core/providers/tts/tencent.py b/main/xiaozhi-server/core/providers/tts/tencent.py index a2493632..50964d33 100644 --- a/main/xiaozhi-server/core/providers/tts/tencent.py +++ b/main/xiaozhi-server/core/providers/tts/tencent.py @@ -10,6 +10,11 @@ from core.providers.tts.base import TTSProviderBase class TTSProvider(TTSProviderBase): + TTS_PARAM_CONFIG = [ + ("ttsVolume", "volume", -10, 10, 0, lambda v: round(float(v), 1)), + ("ttsRate", "speed", -2, 6, 0, lambda v: round(float(v), 2)), + ] + def __init__(self, config, delete_audio_file): super().__init__(config, delete_audio_file) self.appid = config.get("appid") @@ -24,6 +29,16 @@ class TTSProvider(TTSProviderBase): self.output_file = config.get("output_dir") self.audio_file_type = config.get("format", "wav") + # 音频参数配置 + speed = config.get("speed", "0") + self.speed = int(speed) if speed else 0 + + volume = config.get("volume", "0") + self.volume = int(volume) if volume else 0 + + # 应用百分比调整(如果存在),否则使用公有化配置 + self._apply_percentage_params(config) + def _get_auth_headers(self, request_body): """生成鉴权请求头""" # 获取当前UTC时间戳 @@ -127,6 +142,10 @@ class TTSProvider(TTSProviderBase): "Text": text, # 合成语音的源文本 "SessionId": str(uuid.uuid4()), # 会话ID,随机生成 "VoiceType": int(self.voice), # 音色 + "Codec": self.audio_file_type, # 音频编码格式 + "Volume": self.volume, # 音量 + "Speed": self.speed, # 语速 + "SampleRate": self.conn.sample_rate, # 采样率部分支持24000 } try: diff --git a/main/xiaozhi-server/core/providers/tts/ttson.py b/main/xiaozhi-server/core/providers/tts/ttson.py index 0a9f8261..2f54a5c5 100644 --- a/main/xiaozhi-server/core/providers/tts/ttson.py +++ b/main/xiaozhi-server/core/providers/tts/ttson.py @@ -35,7 +35,6 @@ class TTSProvider(TTSProviderBase): self.stream = str(config.get("stream", False)).lower() in ("true", "1", "yes") self.output_file = config.get("output_dir") self.pitch_factor = int(config.get("pitch_factor", 0)) - self.format = config.get("format", "mp3") self.audio_file_type = config.get("format", "mp3") self.emotion = int(config.get("emotion", 1)) self.header = {"Content-Type": "application/json"} @@ -57,7 +56,7 @@ class TTSProvider(TTSProviderBase): "to_lang": self.to_lang, "text": text, "emotion": self.emotion, - "format": self.format, + "format": self.audio_file_type, "volume_change_dB": self.volume_change_dB, "voice_id": self.voice, "pitch_factor": self.pitch_factor, diff --git a/main/xiaozhi-server/core/utils/tts.py b/main/xiaozhi-server/core/utils/tts.py index a0ac3c6b..df3e90b4 100644 --- a/main/xiaozhi-server/core/utils/tts.py +++ b/main/xiaozhi-server/core/utils/tts.py @@ -156,10 +156,9 @@ def convert_percentage_to_range(percentage, min_val, max_val, base_val=None): Returns: 转换后的值 """ - if base_val is None: - base_val = (min_val + max_val) / 2 + percentage, min_val, max_val = float(percentage), float(min_val), float(max_val) + base_val = float(base_val) if base_val is not None else (min_val + max_val) / 2 - # 百分比 -100 对应 min_val, 0 对应 base_val, 100 对应 max_val if percentage < 0: # 负百分比:从 base_val 向 min_val 线性插值 result = base_val + (base_val - min_val) * (percentage / 100)