From 4bc0b67cd7df435e5a6c1bb082b223766ba9f319 Mon Sep 17 00:00:00 2001 From: FAN-yeB <1442100690@qq.com> Date: Thu, 7 May 2026 10:20:48 +0800 Subject: [PATCH 1/5] =?UTF-8?q?update:=E5=8E=BB=E9=99=A4=E6=97=A0=E7=94=A8?= =?UTF-8?q?=E7=9A=84.claude=E7=9B=AE=E5=BD=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- main/xiaozhi-server/.claude/settings.local.json | 11 ----------- 1 file changed, 11 deletions(-) delete mode 100644 main/xiaozhi-server/.claude/settings.local.json diff --git a/main/xiaozhi-server/.claude/settings.local.json b/main/xiaozhi-server/.claude/settings.local.json deleted file mode 100644 index 32b9ba94..00000000 --- a/main/xiaozhi-server/.claude/settings.local.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "permissions": { - "allow": [ - "Bash(tree:*)", - "Bash(find:*)", - "Bash(python3:*)" - ], - "deny": [], - "ask": [] - } -} From 05c22cdf2a777eb70dad2b6fefb3fb1a6d20ba61 Mon Sep 17 00:00:00 2001 From: FAN-yeB <1442100690@qq.com> Date: Thu, 7 May 2026 10:23:02 +0800 Subject: [PATCH 2/5] =?UTF-8?q?update:=E6=9B=B4=E6=96=B0=20.gitignore?= =?UTF-8?q?=E5=BF=BD=E7=95=A5.claude=E7=9B=AE=E5=BD=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index c5d58f2b..7b0cac5e 100644 --- a/.gitignore +++ b/.gitignore @@ -149,6 +149,7 @@ tmp .history .DS_Store main/xiaozhi-server/data +main/xiaozhi-server/.claude main/manager-web/node_modules .config.yaml From 9ed66ec0137a0473f977088721d99ac0325d3ddd Mon Sep 17 00:00:00 2001 From: FAN-yeB <1442100690@qq.com> Date: Thu, 7 May 2026 10:54:03 +0800 Subject: [PATCH 3/5] =?UTF-8?q?update:TTS=5FACGNTTS=20=E6=B5=B7=E8=B1=9A?= =?UTF-8?q?=E9=85=8D=E9=9F=B3=E6=88=AA=E6=AD=A25=E6=9C=887=E6=97=A5?= =?UTF-8?q?=E6=9C=AA=E6=89=BE=E5=88=B0token=E5=85=A5=E5=8F=A3=EF=BC=8C?= =?UTF-8?q?=E5=85=88=E6=9A=82=E6=97=B6=E4=B8=8B=E6=9E=B6=E8=BF=99=E4=B8=AA?= =?UTF-8?q?=E4=BE=9B=E5=BA=94=E5=99=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../resources/db/changelog/202605071037.sql | 5 + .../db/changelog/db.changelog-master.yaml | 7 ++ main/xiaozhi-server/config.yaml | 18 ---- .../core/providers/tts/ttson.py | 96 ------------------- 4 files changed, 12 insertions(+), 114 deletions(-) create mode 100644 main/manager-api/src/main/resources/db/changelog/202605071037.sql delete mode 100644 main/xiaozhi-server/core/providers/tts/ttson.py diff --git a/main/manager-api/src/main/resources/db/changelog/202605071037.sql b/main/manager-api/src/main/resources/db/changelog/202605071037.sql new file mode 100644 index 00000000..97586d77 --- /dev/null +++ b/main/manager-api/src/main/resources/db/changelog/202605071037.sql @@ -0,0 +1,5 @@ +-- 删除provider_code为ttson的供应器 +DELETE FROM `ai_model_provider` WHERE `provider_code` = 'ttson'; + +-- 删除model_code为ACGNTTS的配置 +DELETE FROM `ai_model_config` WHERE `model_code` = 'ACGNTTS'; 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 d7967b21..ca9f7ca8 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 @@ -641,3 +641,10 @@ databaseChangeLog: - sqlFile: encoding: utf8 path: classpath:db/changelog/202604300930.sql + - changeSet: + id: 202605071037 + author: fyb + changes: + - sqlFile: + encoding: utf8 + path: classpath:db/changelog/202605071037.sql diff --git a/main/xiaozhi-server/config.yaml b/main/xiaozhi-server/config.yaml index e82d15f0..3d9b5c7b 100644 --- a/main/xiaozhi-server/config.yaml +++ b/main/xiaozhi-server/config.yaml @@ -1065,24 +1065,6 @@ TTS: output_dir: tmp/ access_token: "你的机智云API key" # language: "中文" # 指定输出语种,如:中文、英语、日语、韩语等,请根据所选音色支持的语言进行设置,不填则默认为中文 - ACGNTTS: - #在线网址:https://acgn.ttson.cn/ - #token购买:www.ttson.cn - #开发相关疑问请提交至网站上的qq - #角色id获取地址:ctrl+f快速检索角色——网站管理者不允许发布,可询问网站管理者 - #各参数意义见开发文档:https://www.yuque.com/alexuh/skmti9/wm6taqislegb02gd?singleDoc# - type: ttson - token: your_token - voice_id: 1695 - speed_factor: 1 - pitch_factor: 0 - volume_change_dB: 0 - to_lang: ZH - url: https://u95167-bd74-2aef8085.westx.seetacloud.com:8443/flashsummary/tts?token= - format: mp3 - output_dir: tmp/ - emotion: 1 - # language: "中文" # 指定输出语种,如:中文、英语、日语、韩语等,请根据所选音色支持的语言进行设置,不填则默认为中文 OpenAITTS: # openai官方文本转语音服务,可支持全球大多数语种 type: openai diff --git a/main/xiaozhi-server/core/providers/tts/ttson.py b/main/xiaozhi-server/core/providers/tts/ttson.py deleted file mode 100644 index 2f54a5c5..00000000 --- a/main/xiaozhi-server/core/providers/tts/ttson.py +++ /dev/null @@ -1,96 +0,0 @@ -import os -import uuid -import json -import requests -import shutil -from datetime import datetime -from core.providers.tts.base import TTSProviderBase -from config.logger import setup_logging - -TAG = __name__ -logger = setup_logging() - - -class TTSProvider(TTSProviderBase): - TTS_PARAM_CONFIG = [ - ("ttsVolume", "volume_change_dB", -10, 10, 0, int), - ("ttsRate", "speed_factor", 0.5, 2, 0, lambda v: round(float(v), 1)), - ("ttsPitch", "pitch_factor", -8, 8, 0, lambda v: round(float(v), 1)), - ] - - def __init__(self, config, delete_audio_file): - super().__init__(config, delete_audio_file) - self.url = config.get( - "url", - "https://u95167-bd74-2aef8085.westx.seetacloud.com:8443/flashsummary/tts?token=", - ) - if config.get("private_voice"): - self.voice = int(config.get("private_voice")) - else: - self.voice = int(config.get("voice_id", 1695)) - self.token = config.get("token") - self.to_lang = config.get("to_lang") - self.volume_change_dB = int(config.get("volume_change_dB", 0)) - self.speed_factor = int(config.get("speed_factor", 1)) - 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.audio_file_type = config.get("format", "mp3") - self.emotion = int(config.get("emotion", 1)) - self.header = {"Content-Type": "application/json"} - - # 应用百分比调整(如果存在),否则使用公有化配置 - self._apply_percentage_params(config) - - def generate_filename(self, extension=".mp3"): - 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): - url = f"{self.url}{self.token}" - result = "firefly" - payload = json.dumps( - { - "to_lang": self.to_lang, - "text": text, - "emotion": self.emotion, - "format": self.audio_file_type, - "volume_change_dB": self.volume_change_dB, - "voice_id": self.voice, - "pitch_factor": self.pitch_factor, - "speed_factor": self.speed_factor, - "token": self.token, - } - ) - - resp = requests.request("POST", url, data=payload) - if resp.status_code != 200: - logger.bind(tag=TAG).error(f"TTSON 请求失败: {resp.text}") - raise Exception(f"{__name__}: TTS请求失败") - resp_json = resp.json() - try: - result = ( - resp_json["url"] - + ":" - + str(resp_json["port"]) - + "/flashsummary/retrieveFileData?stream=True&token=" - + self.token - + "&voice_audio_path=" - + resp_json["voice_path"] - ) - - audio_content = requests.get(result) - if output_file: - with open(output_file, "wb") as f: - f.write(audio_content.content) - else: - return audio_content.content - voice_path = resp_json.get("voice_path") - des_path = output_file - shutil.move(voice_path, des_path) - - except Exception as e: - print("error:", e) - raise Exception(f"{__name__}: TTS请求失败") From 8b7f16b9c2745fffdeb155e93bcee2d023a01e8b Mon Sep 17 00:00:00 2001 From: FAN-yeB <1442100690@qq.com> Date: Thu, 7 May 2026 11:27:32 +0800 Subject: [PATCH 4/5] =?UTF-8?q?update:TTS=5FGizwitsTTS=20=E8=BF=91?= =?UTF-8?q?=E6=9C=9F=E6=9C=BA=E6=99=BA=E4=BA=91=E8=AF=AD=E9=9F=B3=E5=90=88?= =?UTF-8?q?=E6=88=90=E4=B8=8D=E6=88=90=E5=8A=9F=EF=BC=8C=E5=85=88=E6=9A=82?= =?UTF-8?q?=E6=97=B6=E4=B8=8B=E6=9E=B6=E8=BF=99=E4=B8=AA=E4=BE=9B=E5=BA=94?= =?UTF-8?q?=E5=99=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/resources/db/changelog/202605071120.sql | 6 ++++++ .../resources/db/changelog/db.changelog-master.yaml | 7 +++++++ main/xiaozhi-server/config.yaml | 12 ------------ 3 files changed, 13 insertions(+), 12 deletions(-) create mode 100644 main/manager-api/src/main/resources/db/changelog/202605071120.sql diff --git a/main/manager-api/src/main/resources/db/changelog/202605071120.sql b/main/manager-api/src/main/resources/db/changelog/202605071120.sql new file mode 100644 index 00000000..c3329170 --- /dev/null +++ b/main/manager-api/src/main/resources/db/changelog/202605071120.sql @@ -0,0 +1,6 @@ +-- 删除model_code为GizwitsTTS的配置 +DELETE FROM `ai_model_config` WHERE `model_code` = 'GizwitsTTS'; + +-- 删除关联的TTS音色记录 +DELETE FROM `ai_tts_voice` WHERE `tts_model_id` = 'TTS_GizwitsTTS'; +DELETE FROM `ai_tts_voice` WHERE `tts_model_id` = 'TTS_ACGNTTS'; 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 ca9f7ca8..2c29b49c 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 @@ -648,3 +648,10 @@ databaseChangeLog: - sqlFile: encoding: utf8 path: classpath:db/changelog/202605071037.sql + - changeSet: + id: 202605071120 + author: fyb + changes: + - sqlFile: + encoding: utf8 + path: classpath:db/changelog/202605071120.sql diff --git a/main/xiaozhi-server/config.yaml b/main/xiaozhi-server/config.yaml index 3d9b5c7b..b25a05e0 100644 --- a/main/xiaozhi-server/config.yaml +++ b/main/xiaozhi-server/config.yaml @@ -1053,18 +1053,6 @@ TTS: output_dir: tmp/ access_token: "你的302API密钥" # language: "中文" # 指定输出语种,如:中文、英语、日语、韩语等,请根据所选音色支持的语言进行设置,不填则默认为中文 - GizwitsTTS: - type: doubao - # 火山引擎作为基座,可以完全使用企业级火山引擎语音合成服务 - # 前一万名注册的用户,将送5元体验金额 - # 获取API Key地址:https://agentrouter.gizwitsapi.com/panel/token - api_url: https://bytedance.gizwitsapi.com/api/v1/tts - authorization: "Bearer " - # 湾湾小何音色 - voice: "zh_female_wanwanxiaohe_moon_bigtts" - output_dir: tmp/ - access_token: "你的机智云API key" - # language: "中文" # 指定输出语种,如:中文、英语、日语、韩语等,请根据所选音色支持的语言进行设置,不填则默认为中文 OpenAITTS: # openai官方文本转语音服务,可支持全球大多数语种 type: openai From c693ff15641df9cbca4fe594bb8be0e31e71b307 Mon Sep 17 00:00:00 2001 From: FAN-yeB <1442100690@qq.com> Date: Fri, 8 May 2026 10:18:26 +0800 Subject: [PATCH 5/5] =?UTF-8?q?update:linkerai=20=E8=BF=91=E6=9C=9F?= =?UTF-8?q?=E5=A4=9A=E6=AC=A1=E6=B5=8B=E8=AF=95=EF=BC=8Clinkerai=E6=8E=A5?= =?UTF-8?q?=E5=8F=A3=E6=97=A0=E6=B3=95=E6=8F=90=E4=BE=9B=E7=A8=B3=E5=AE=9A?= =?UTF-8?q?=E6=9C=8D=E5=8A=A1=EF=BC=8C=E5=85=88=E6=9A=82=E6=97=B6=E4=B8=8B?= =?UTF-8?q?=E6=9E=B6=E8=BF=99=E4=B8=AA=E4=BE=9B=E5=BA=94=E5=99=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../resources/db/changelog/202605081008.sql | 8 + .../db/changelog/db.changelog-master.yaml | 7 + main/xiaozhi-server/config.yaml | 12 - .../core/providers/tts/linkerai.py | 267 ------------------ 4 files changed, 15 insertions(+), 279 deletions(-) create mode 100644 main/manager-api/src/main/resources/db/changelog/202605081008.sql delete mode 100644 main/xiaozhi-server/core/providers/tts/linkerai.py diff --git a/main/manager-api/src/main/resources/db/changelog/202605081008.sql b/main/manager-api/src/main/resources/db/changelog/202605081008.sql new file mode 100644 index 00000000..91a14a19 --- /dev/null +++ b/main/manager-api/src/main/resources/db/changelog/202605081008.sql @@ -0,0 +1,8 @@ +-- 删除provider_code为linkerai的供应商配置 +DELETE FROM `ai_model_provider` WHERE `provider_code` = 'linkerai'; + +-- 删除model_code为LinkeraiTTS的模型配置 +DELETE FROM `ai_model_config` WHERE `model_code` = 'LinkeraiTTS'; + +-- 删除LinkeraiTTS关联的TTS音色记录 +DELETE FROM `ai_tts_voice` WHERE `tts_model_id` = 'TTS_LinkeraiTTS'; 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 2c29b49c..c645c111 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 @@ -655,3 +655,10 @@ databaseChangeLog: - sqlFile: encoding: utf8 path: classpath:db/changelog/202605071120.sql + - changeSet: + id: 202605081008 + author: fyb + changes: + - sqlFile: + encoding: utf8 + path: classpath:db/changelog/202605081008.sql diff --git a/main/xiaozhi-server/config.yaml b/main/xiaozhi-server/config.yaml index b25a05e0..d593892f 100644 --- a/main/xiaozhi-server/config.yaml +++ b/main/xiaozhi-server/config.yaml @@ -1092,18 +1092,6 @@ TTS: format: mp3 # 接口返回的音频格式 output_dir: tmp/ # language: "中文" # 指定输出语种,如:中文、英语、日语、韩语等,请根据所选音色支持的语言进行设置,不填则默认为中文 - LinkeraiTTS: - type: linkerai - api_url: https://tts.linkerai.cn/tts - audio_format: "pcm" - # 默认的access_token供大家测试时免费使用的,此access_token请勿用于商业用途 - # 如果效果不错,可自行申请token,申请地址:https://linkerai.cn - # 各参数意义见开发文档:https://tts.linkerai.cn/docs - # 支持声音克隆,可自行上传音频,填入voice参数,voice参数为空时,使用默认声音 - access_token: "U4YdYXVfpwWnk2t5Gp822zWPCuORyeJL" - voice: "OUeAo1mhq6IBExi" - output_dir: tmp/ - # language: "中文" # 指定输出语种,如:中文、英语、日语、韩语等,请根据所选音色支持的语言进行设置,不填则默认为中文 PaddleSpeechTTS: #百度飞浆 PaddleSpeech 支持本地离线部署 支持模型训练 #框架地址 https://www.paddlepaddle.org.cn/ diff --git a/main/xiaozhi-server/core/providers/tts/linkerai.py b/main/xiaozhi-server/core/providers/tts/linkerai.py deleted file mode 100644 index ecfb8b38..00000000 --- a/main/xiaozhi-server/core/providers/tts/linkerai.py +++ /dev/null @@ -1,267 +0,0 @@ -import os -import time -import queue -import aiohttp -import asyncio -import requests -import traceback -from config.logger import setup_logging -from core.utils.tts import MarkdownCleaner -from core.providers.tts.base import TTSProviderBase -from core.utils import opus_encoder_utils, textUtils -from core.providers.tts.dto.dto import SentenceType, ContentType, InterfaceType - -TAG = __name__ -logger = setup_logging() - - -class TTSProvider(TTSProviderBase): - def __init__(self, config, delete_audio_file): - super().__init__(config, delete_audio_file) - self.interface_type = InterfaceType.SINGLE_STREAM - self.access_token = config.get("access_token") - self.voice = config.get("voice") - self.api_url = config.get("api_url") - self.audio_format = "pcm" - self.before_stop_play_files = [] - - # PCM缓冲区 - self.pcm_buffer = bytearray() - - 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: - # 初始化参数 - self.tts_stop_request = False - self.processed_chars = 0 - self.tts_text_buff = [] - self.before_stop_play_files.clear() - elif ContentType.TEXT == message.content_type: - self.tts_text_buff.append(message.content_detail) - segment_text = self._get_segment_text() - if segment_text: - self.to_tts_single_stream(segment_text) - - elif ContentType.FILE == message.content_type: - logger.bind(tag=TAG).info( - f"添加音频文件到待播放列表: {message.content_file}" - ) - if message.content_file and os.path.exists(message.content_file): - # 先处理文件音频数据 - self._process_audio_file_stream(message.content_file, callback=lambda audio_data: self.handle_audio_file(audio_data, message.content_detail)) - if message.sentence_type == SentenceType.LAST: - # 处理剩余的文本 - self._process_remaining_text_stream(True) - - except queue.Empty: - continue - except Exception as e: - logger.bind(tag=TAG).error( - f"处理TTS文本失败: {str(e)}, 类型: {type(e).__name__}, 堆栈: {traceback.format_exc()}" - ) - - def _process_remaining_text_stream(self, is_last=False): - """处理剩余的文本并生成语音 - - Returns: - bool: 是否成功处理了文本 - """ - full_text = "".join(self.tts_text_buff) - remaining_text = full_text[self.processed_chars :] - if remaining_text: - segment_text = textUtils.get_string_no_punctuation_or_emoji(remaining_text) - if segment_text: - self.to_tts_single_stream(segment_text, is_last) - self.processed_chars += len(full_text) - else: - self._process_before_stop_play_files() - else: - self._process_before_stop_play_files() - - def to_tts_single_stream(self, text, is_last=False): - try: - max_repeat_time = 5 - original_text = text - text = MarkdownCleaner.clean_markdown(text) - if self._correct_words_pattern: - text = self._correct_words_pattern.sub(lambda m: self.correct_words[m.group(0)], text) - try: - asyncio.run(self.text_to_speak(text, is_last)) - except Exception as e: - logger.bind(tag=TAG).warning( - f"语音生成失败{5 - max_repeat_time + 1}次: {original_text},错误: {e}" - ) - max_repeat_time -= 1 - - if max_repeat_time > 0: - logger.bind(tag=TAG).info( - f"语音生成成功: {original_text},重试{5 - max_repeat_time}次" - ) - else: - logger.bind(tag=TAG).error( - f"语音生成失败: {original_text},请检查网络或服务是否正常" - ) - except Exception as e: - logger.bind(tag=TAG).error(f"Failed to generate TTS file: {e}") - finally: - return None - - async def text_to_speak(self, text, is_last): - """流式处理TTS音频,每句只推送一次音频列表""" - await self._tts_request(text, is_last) - - async def close(self): - """资源清理""" - await super().close() - if hasattr(self, "opus_encoder"): - self.opus_encoder.close() - - async def _tts_request(self, text: str, is_last: bool) -> None: - params = { - "tts_text": text, - "spk_id": self.voice, - "frame_durition": 60, - "stream": "true", - "target_sr": self.conn.sample_rate, - "audio_format": "pcm", - "instruct_text": "请生成一段自然流畅的语音", - } - headers = { - "Authorization": f"Bearer {self.access_token}", - "Content-Type": "application/json", - } - - # 一帧 PCM 所需字节数:60 ms × sample_rate × 1 ch × 2 B - frame_bytes = int( - self.opus_encoder.sample_rate - * self.opus_encoder.channels # 1 - * self.opus_encoder.frame_size_ms - / 1000 - * 2 - ) # 16-bit = 2 bytes - - try: - async with aiohttp.ClientSession() as session: - async with session.get( - self.api_url, params=params, headers=headers, timeout=10 - ) as resp: - - if resp.status != 200: - logger.bind(tag=TAG).error( - f"TTS请求失败: {resp.status}, {await resp.text()}" - ) - self.tts_audio_queue.put((SentenceType.LAST, [], None)) - return - - self.pcm_buffer.clear() - self.tts_audio_queue.put((SentenceType.FIRST, [], text)) - - # 兼容 iter_chunked / iter_chunks / iter_any - async for chunk in resp.content.iter_any(): - data = chunk[0] if isinstance(chunk, (list, tuple)) else chunk - if not data: - continue - - # 拼到 buffer - self.pcm_buffer.extend(data) - - # 够一帧就编码 - while len(self.pcm_buffer) >= frame_bytes: - frame = bytes(self.pcm_buffer[:frame_bytes]) - del self.pcm_buffer[:frame_bytes] - - self.opus_encoder.encode_pcm_to_opus_stream( - frame, - end_of_stream=False, - callback=self.handle_opus - ) - - # flush 剩余不足一帧的数据 - if self.pcm_buffer: - self.opus_encoder.encode_pcm_to_opus_stream( - bytes(self.pcm_buffer), - end_of_stream=True, - callback=self.handle_opus - ) - self.pcm_buffer.clear() - - # 如果是最后一段,输出音频获取完毕 - if is_last: - self._process_before_stop_play_files() - - except Exception as e: - logger.bind(tag=TAG).error(f"TTS请求异常: {e}") - self.tts_audio_queue.put((SentenceType.LAST, [], None)) - - def to_tts(self, text: str) -> list: - """非流式TTS处理,用于测试及保存音频文件的场景 - Args: - text: 要转换的文本 - Returns: - list: 返回opus编码后的音频数据列表 - """ - start_time = time.time() - text = MarkdownCleaner.clean_markdown(text) - if self._correct_words_pattern: - text = self._correct_words_pattern.sub(lambda m: self.correct_words[m.group(0)], text) - - params = { - "tts_text": text, - "spk_id": self.voice, - "frame_duration": 60, - "stream": False, - "target_sr": self.conn.sample_rate, - "audio_format": self.audio_format, - "instruct_text": "请生成一段自然流畅的语音", - } - headers = { - "Authorization": f"Bearer {self.access_token}", - "Content-Type": "application/json", - } - - try: - with requests.get( - self.api_url, params=params, headers=headers, timeout=5 - ) as response: - if response.status_code != 200: - logger.bind(tag=TAG).error( - f"TTS请求失败: {response.status_code}, {response.text}" - ) - return [] - - logger.info(f"TTS请求成功: {text}, 耗时: {time.time() - start_time}秒") - - # 使用opus编码器处理PCM数据 - opus_datas = [] - pcm_data = response.content - - # 计算每帧的字节数 - frame_bytes = int( - self.opus_encoder.sample_rate - * self.opus_encoder.channels - * self.opus_encoder.frame_size_ms - / 1000 - * 2 - ) - - # 分帧处理PCM数据 - for i in range(0, len(pcm_data), frame_bytes): - frame = pcm_data[i : i + frame_bytes] - if len(frame) < frame_bytes: - # 最后一帧可能不足,用0填充 - frame = frame + b"\x00" * (frame_bytes - len(frame)) - - self.opus_encoder.encode_pcm_to_opus_stream( - frame, - end_of_stream=(i + frame_bytes >= len(pcm_data)), - callback=lambda opus: opus_datas.append(opus) - ) - - return opus_datas - - except Exception as e: - logger.bind(tag=TAG).error(f"TTS请求异常: {e}") - return [] \ No newline at end of file