mirror of
https://github.com/Ethan930717/siliconflow_tts.git
synced 2026-07-21 14:44:05 +08:00
First upload
This commit is contained in:
@@ -0,0 +1,20 @@
|
||||
name: Validate
|
||||
|
||||
on:
|
||||
push:
|
||||
pull_request:
|
||||
schedule:
|
||||
- cron: "0 0 * * *"
|
||||
|
||||
jobs:
|
||||
validate:
|
||||
runs-on: "ubuntu-latest"
|
||||
name: Validate
|
||||
steps:
|
||||
- name: Check out repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: HACS validation
|
||||
uses: hacs/action@main
|
||||
with:
|
||||
category: "integration"
|
||||
@@ -0,0 +1,250 @@
|
||||
# SiliconFlow TTS 组件
|
||||
|
||||
这是一个基于硅基流动API的Home Assistant TTS组件,支持CosyVoice2-0.5B模型。
|
||||
|
||||
## 功能特性
|
||||
|
||||
- 🎭 **动态音色配置** - 在 `configuration.yaml` 中自定义音色,无需修改代码
|
||||
- 🎵 内置音色:阿狸、露露、索拉卡、扎克、吴楠
|
||||
- 🎚️ **前端可视化配置** - 在 Home Assistant 界面中实时调整参数
|
||||
- 🎧 支持多种音频格式:mp3, wav, opus, pcm
|
||||
- ⚡ 支持语速调节(0.25-4.0倍速)
|
||||
- 🔊 支持音量增益调节(-10到+10 dB)
|
||||
- 😊 支持情感表达
|
||||
- 📊 支持多种采样率(最高44.1kHz)
|
||||
- 🔄 **无需重启** - 配置更改立即生效
|
||||
- 🛠️ **兼容性强** - 支持媒体界面和服务调用
|
||||
|
||||
## 安装
|
||||
|
||||
1. 将 `siliconflow_tts` 文件夹复制到你的 `custom_components` 目录
|
||||
2. 重启 Home Assistant
|
||||
3. 在配置文件中添加以下配置:
|
||||
|
||||
```yaml
|
||||
# configuration.yaml
|
||||
siliconflow_tts:
|
||||
api_key: "your_api_key_here"
|
||||
# 可选:自定义音色选项
|
||||
custom_voices:
|
||||
"speech:Dahu-HA-MyVoice:qc10fwc040:customid123": "我的专属音色"
|
||||
"speech:Dahu-HA-BossVoice:qc10fwc040:customid456": "老板音色"
|
||||
"speech:your-custom-voice-id": "自定义名称"
|
||||
```
|
||||
|
||||
## 配置选项
|
||||
|
||||
| 参数 | 类型 | 必需 | 描述 |
|
||||
|------|------|------|------|
|
||||
| `api_key` | string | 是 | 硅基流动API密钥 |
|
||||
| `custom_voices` | dict | 否 | 自定义音色选项(音色ID: 显示名称) |
|
||||
|
||||
## 🎭 自定义音色配置
|
||||
|
||||
您可以在 `configuration.yaml` 中添加自定义音色,这样就不需要修改代码:
|
||||
|
||||
### 基础配置
|
||||
```yaml
|
||||
siliconflow_tts:
|
||||
custom_voices:
|
||||
"speech:Dahu-HA-MyCustom:qc10fwc040:uniqueid123": "我的音色"
|
||||
"speech:Dahu-HA-Family:qc10fwc040:uniqueid456": "家人音色"
|
||||
```
|
||||
|
||||
### 完整配置示例
|
||||
```yaml
|
||||
siliconflow_tts:
|
||||
custom_voices:
|
||||
# 专业音色
|
||||
"speech:Dahu-HA-Professional:qc10fwc040:prof001": "专业播音"
|
||||
"speech:Dahu-HA-News:qc10fwc040:news001": "新闻主播"
|
||||
|
||||
# 个人音色
|
||||
"speech:Dahu-HA-Dad:qc10fwc040:family001": "爸爸的声音"
|
||||
"speech:Dahu-HA-Mom:qc10fwc040:family002": "妈妈的声音"
|
||||
|
||||
# 角色音色
|
||||
"speech:Dahu-HA-Robot:qc10fwc040:robot001": "机器人音色"
|
||||
"speech:Dahu-HA-Child:qc10fwc040:child001": "儿童音色"
|
||||
```
|
||||
|
||||
### 使用自定义音色
|
||||
配置后,您可以在前端配置界面中选择这些音色,也可以在服务调用中使用:
|
||||
|
||||
```yaml
|
||||
service: tts.speak
|
||||
data:
|
||||
entity_id: tts.siliconflow_tts
|
||||
message: "你好,我是专业播音音色"
|
||||
voice: "speech:Dahu-HA-Professional:qc10fwc040:prof001"
|
||||
```
|
||||
|
||||
## 使用方法
|
||||
|
||||
### 在自动化中使用
|
||||
|
||||
```yaml
|
||||
automation:
|
||||
- alias: "TTS测试"
|
||||
trigger:
|
||||
platform: state
|
||||
entity_id: input_boolean.tts_test
|
||||
action:
|
||||
- service: tts.siliconflow_say
|
||||
data:
|
||||
message: "你好,这是TTS测试"
|
||||
voice: "anna"
|
||||
speed: 1.2
|
||||
gain: 2
|
||||
```
|
||||
|
||||
### 在脚本中使用
|
||||
|
||||
```yaml
|
||||
script:
|
||||
tts_demo:
|
||||
sequence:
|
||||
- service: tts.siliconflow_say
|
||||
data:
|
||||
message: "今天天气很好"
|
||||
voice: "bella"
|
||||
emotion: "happy"
|
||||
speed: 1.0
|
||||
gain: 0
|
||||
```
|
||||
|
||||
### 使用自定义音色
|
||||
|
||||
```yaml
|
||||
# 使用预设语音
|
||||
script:
|
||||
preset_voice_demo:
|
||||
sequence:
|
||||
- service: tts.siliconflow_say
|
||||
data:
|
||||
message: "你能用高兴的情感说吗?<|endofprompt|>今天真是太开心了!"
|
||||
voice: "FunAudioLLM/CosyVoice2-0.5B:anna"
|
||||
speed: 1.0
|
||||
gain: 0
|
||||
|
||||
# 使用自定义音色URI
|
||||
script:
|
||||
custom_voice_demo:
|
||||
sequence:
|
||||
- service: tts.siliconflow_say
|
||||
data:
|
||||
message: "你能用高兴的情感说吗?<|endofprompt|>这是使用自定义音色的测试!"
|
||||
reference_audio: "speech:Dahu-HA-Ari:qc10fwc040:jxlvonagnnzdvbotrmrr"
|
||||
speed: 1.0
|
||||
gain: 0
|
||||
```
|
||||
|
||||
## 支持的参数
|
||||
|
||||
### voice(预设语音)
|
||||
- `FunAudioLLM/CosyVoice2-0.5B:alex` - Alex (男声)
|
||||
- `FunAudioLLM/CosyVoice2-0.5B:anna` - Anna (女声)
|
||||
- `FunAudioLLM/CosyVoice2-0.5B:bella` - Bella (女声)
|
||||
- `FunAudioLLM/CosyVoice2-0.5B:benjamin` - Benjamin (男声)
|
||||
- `FunAudioLLM/CosyVoice2-0.5B:charles` - Charles (男声)
|
||||
- `FunAudioLLM/CosyVoice2-0.5B:claire` - Claire (女声)
|
||||
- `FunAudioLLM/CosyVoice2-0.5B:david` - David (男声)
|
||||
- `FunAudioLLM/CosyVoice2-0.5B:diana` - Diana (女声)
|
||||
|
||||
### reference_audio(自定义音色)
|
||||
- 类型:string(URI格式)
|
||||
- 描述:使用上传后获得的URI作为自定义音色
|
||||
- 格式:speech:custom-name:model-id:unique-id
|
||||
- 注意:与voice参数互斥,同时使用时会优先使用reference_audio
|
||||
- 推荐:使用upload_voice.py脚本先上传音频文件获取URI
|
||||
|
||||
### response_format(音频格式)
|
||||
- `mp3` - MP3格式(默认)
|
||||
- `wav` - WAV格式
|
||||
- `opus` - Opus格式
|
||||
- `pcm` - PCM格式
|
||||
|
||||
### speed(语速)
|
||||
- 范围:0.25 - 4.0
|
||||
- 默认:1.0
|
||||
|
||||
### gain(音量增益)
|
||||
- 范围:-10 - +10
|
||||
- 默认:0
|
||||
|
||||
### sample_rate(采样率)
|
||||
- mp3: 32000, 44100 Hz
|
||||
- wav/pcm: 8000, 16000, 24000, 32000, 44100 Hz
|
||||
- opus: 48000 Hz
|
||||
|
||||
### emotion(情感)
|
||||
- 支持各种情感描述,如:happy, sad, angry, excited等
|
||||
- 会自动添加API所需的特殊标记格式
|
||||
|
||||
## 自定义音色使用指南
|
||||
|
||||
### 方法一:使用本地音频文件
|
||||
|
||||
#### 音频文件要求
|
||||
1. **格式**:支持mp3, wav, opus, pcm格式
|
||||
2. **时长**:建议5-30秒,太短可能影响音色提取效果
|
||||
3. **质量**:建议使用清晰、无噪音的音频
|
||||
4. **内容**:建议包含完整的句子,避免背景音乐
|
||||
|
||||
#### 文件路径
|
||||
- 可以使用绝对路径:`/config/custom_components/siliconflow_tts/Ari.mp3`
|
||||
- 也可以使用相对路径(相对于Home Assistant配置目录)
|
||||
|
||||
### 方法二:使用上传的URI参考音频
|
||||
|
||||
#### 上传步骤
|
||||
1. 使用提供的`upload_voice.py`脚本上传音频文件
|
||||
2. 脚本会返回一个URI格式的字符串
|
||||
3. 在TTS配置中直接使用这个URI
|
||||
|
||||
#### 使用示例
|
||||
```bash
|
||||
# 运行上传脚本
|
||||
python3 upload_voice.py
|
||||
|
||||
# 脚本会输出类似这样的URI:
|
||||
# speech:Dahu-HA-Ari:qc10fwc040:jxlvonagnnzdvbotrmrr
|
||||
```
|
||||
|
||||
### 使用示例
|
||||
|
||||
```yaml
|
||||
# 使用预设语音
|
||||
- service: tts.siliconflow_say
|
||||
data:
|
||||
message: "你能用高兴的情感说吗?<|endofprompt|>使用预设语音测试!"
|
||||
voice: "FunAudioLLM/CosyVoice2-0.5B:anna"
|
||||
|
||||
# 使用自定义音色URI
|
||||
- service: tts.siliconflow_say
|
||||
data:
|
||||
message: "你能用高兴的情感说吗?<|endofprompt|>使用自定义音色测试!"
|
||||
reference_audio: "speech:Dahu-HA-Ari:qc10fwc040:jxlvonagnnzdvbotrmrr"
|
||||
```
|
||||
|
||||
## 故障排除
|
||||
|
||||
1. **API密钥错误**:确保API密钥正确且有效
|
||||
2. **网络连接问题**:检查网络连接是否正常
|
||||
3. **参数范围错误**:确保speed和gain参数在有效范围内
|
||||
4. **音频格式不支持**:确保选择的音频格式和采样率组合有效
|
||||
5. **自定义音色文件不存在**:检查音频文件路径是否正确
|
||||
6. **自定义音色编码失败**:检查音频文件是否损坏或格式不支持
|
||||
|
||||
## 日志
|
||||
|
||||
组件会记录详细的日志信息,可以在Home Assistant的开发者工具中查看:
|
||||
|
||||
```yaml
|
||||
logger:
|
||||
custom_components.siliconflow_tts: debug
|
||||
```
|
||||
|
||||
## 许可证
|
||||
|
||||
MIT License
|
||||
@@ -0,0 +1,306 @@
|
||||
# SiliconFlow TTS for Home Assistant
|
||||
|
||||
🎙️ 一个功能强大的 Home Assistant 文本转语音集成,基于 [SiliconFlow](https://siliconflow.cn) 的 CosyVoice2-0.5B 模型。
|
||||
|
||||
[](https://github.com/your-username/siliconflow-tts-ha/releases)
|
||||
[](LICENSE)
|
||||
[](https://www.home-assistant.io/)
|
||||
[](README_OPENSOURCE.md)
|
||||
|
||||
## ✨ 功能特性
|
||||
|
||||
- 🎭 **动态音色配置** - 在 `configuration.yaml` 中自定义音色,无需修改代码
|
||||
- 🎵 **内置精品音色** - 阿狸、露露、索拉卡、扎克等精选音色
|
||||
- 🎚️ **可视化配置界面** - 在 Home Assistant 前端实时调整所有参数
|
||||
- 🎧 **多种音频格式** - 支持 mp3, wav, opus, pcm
|
||||
- ⚡ **语速调节** - 0.25-4.0倍速,满足不同场景需求
|
||||
- 🔊 **音量增益控制** - -10到+10 dB精确调节
|
||||
- 😊 **情感表达支持** - 让语音更生动自然
|
||||
- 📊 **高音质采样** - 最高支持 44.1kHz 采样率
|
||||
- 🔄 **即时生效** - 配置更改无需重启,立即生效
|
||||
- 🛠️ **完美兼容** - 支持媒体界面和所有服务调用方式
|
||||
- 🎤 **自定义音色** - 支持上传个人音色文件
|
||||
|
||||
## 📦 安装方式
|
||||
|
||||
### 方法一:HACS 安装(推荐)
|
||||
|
||||
1. 确保已安装 [HACS](https://hacs.xyz/)
|
||||
2. 进入 HACS → 集成
|
||||
3. 点击右上角 ⋮ → 自定义存储库
|
||||
4. 添加此仓库 URL:`https://github.com/your-username/siliconflow-tts-ha`
|
||||
5. 类别选择"集成"
|
||||
6. 搜索 "SiliconFlow TTS" 并安装
|
||||
7. 重启 Home Assistant
|
||||
|
||||
### 方法二:手动安装
|
||||
|
||||
1. 下载最新版本的压缩包
|
||||
2. 解压到 `custom_components/siliconflow_tts/`
|
||||
3. 重启 Home Assistant
|
||||
|
||||
## 🚀 快速开始
|
||||
|
||||
### 1. 获取 API 密钥
|
||||
|
||||
前往 [SiliconFlow 官网](https://cloud.siliconflow.cn/) 注册账号并获取 API 密钥。
|
||||
|
||||
### 2. 基础配置
|
||||
|
||||
在 Home Assistant 中:
|
||||
1. 进入 **设置** → **设备与服务**
|
||||
2. 点击 **添加集成**
|
||||
3. 搜索 **SiliconFlow TTS**
|
||||
4. 输入您的 API 密钥
|
||||
|
||||
### 3. 使用测试
|
||||
|
||||
```yaml
|
||||
# 在开发者工具中测试
|
||||
service: tts.speak
|
||||
data:
|
||||
entity_id: tts.siliconflow_tts
|
||||
message: "你好,欢迎使用 SiliconFlow TTS"
|
||||
```
|
||||
|
||||
## ⚙️ 高级配置
|
||||
|
||||
### 🎭 自定义音色配置
|
||||
|
||||
在 `configuration.yaml` 中添加自定义音色:
|
||||
|
||||
```yaml
|
||||
siliconflow_tts:
|
||||
custom_voices:
|
||||
# 专业音色
|
||||
"speech:Dahu-HA-Professional:qc10fwc040:prof001": "专业播音"
|
||||
"speech:Dahu-HA-News:qc10fwc040:news001": "新闻主播"
|
||||
|
||||
# 个人音色
|
||||
"speech:Dahu-HA-Dad:qc10fwc040:family001": "爸爸的声音"
|
||||
"speech:Dahu-HA-Mom:qc10fwc040:family002": "妈妈的声音"
|
||||
|
||||
# 角色音色
|
||||
"speech:Dahu-HA-Robot:qc10fwc040:robot001": "机器人音色"
|
||||
"speech:Dahu-HA-Child:qc10fwc040:child001": "儿童音色"
|
||||
```
|
||||
|
||||
配置后重启 Home Assistant,即可在前端配置界面中选择这些音色。
|
||||
|
||||
### 🎚️ 前端参数调整
|
||||
|
||||
1. 进入 **设置** → **设备与服务**
|
||||
2. 找到 **SiliconFlow TTS** 集成
|
||||
3. 点击 **配置** 按钮
|
||||
4. 在界面中调整:
|
||||
- 🎭 选择音色
|
||||
- 🚀 调整语速 (0.25-4.0)
|
||||
- 🎵 选择音频格式
|
||||
- 📊 设置采样率
|
||||
- 🔊 调整音量增益
|
||||
|
||||
**💡 提示**:所有配置更改立即生效,无需重启!
|
||||
|
||||
## 🎤 上传自定义音色教程
|
||||
|
||||
### 准备工作
|
||||
|
||||
1. **获取 API 密钥**:确保已从 [SiliconFlow](https://cloud.siliconflow.cn/) 获取有效的 API 密钥
|
||||
2. **准备音频文件**:
|
||||
- 格式:wav, mp3, m4a 等常见格式
|
||||
- 时长:建议 10-30 秒
|
||||
- 内容:清晰的语音,最好包含多种音调
|
||||
- 质量:无背景噪音,语音清晰
|
||||
|
||||
### 方法一:使用 cURL 命令(推荐)
|
||||
|
||||
打开终端,执行以下命令:
|
||||
|
||||
```bash
|
||||
curl --request POST \
|
||||
--url https://api.siliconflow.cn/v1/uploads/audio/voice \
|
||||
--header 'Authorization: Bearer YOUR_API_KEY' \
|
||||
--header 'Content-Type: multipart/form-data' \
|
||||
--form model=FunAudioLLM/CosyVoice2-0.5B \
|
||||
--form customName=my-custom-voice \
|
||||
--form 'text=在一无所知中,梦里的一天结束了,一个新的轮回便会开始' \
|
||||
--form file=@/path/to/your/audio.wav
|
||||
```
|
||||
|
||||
**参数说明**:
|
||||
- `YOUR_API_KEY`:替换为您的 SiliconFlow API 密钥
|
||||
- `customName`:自定义音色名称(英文,无空格)
|
||||
- `text`:音频对应的文本内容
|
||||
- `file=@/path/to/your/audio.wav`:音频文件路径
|
||||
|
||||
### 方法二:使用 Python 脚本
|
||||
|
||||
```python
|
||||
import requests
|
||||
|
||||
def upload_voice(api_key, audio_file_path, custom_name, text):
|
||||
url = "https://api.siliconflow.cn/v1/uploads/audio/voice"
|
||||
|
||||
headers = {
|
||||
'Authorization': f'Bearer {api_key}'
|
||||
}
|
||||
|
||||
files = {
|
||||
'file': open(audio_file_path, 'rb')
|
||||
}
|
||||
|
||||
data = {
|
||||
'model': 'FunAudioLLM/CosyVoice2-0.5B',
|
||||
'customName': custom_name,
|
||||
'text': text
|
||||
}
|
||||
|
||||
response = requests.post(url, headers=headers, files=files, data=data)
|
||||
|
||||
if response.status_code == 200:
|
||||
result = response.json()
|
||||
print(f"上传成功!音色 URI: {result['uri']}")
|
||||
return result['uri']
|
||||
else:
|
||||
print(f"上传失败:{response.status_code} - {response.text}")
|
||||
return None
|
||||
|
||||
# 使用示例
|
||||
api_key = "your_siliconflow_api_key"
|
||||
audio_path = "/path/to/your/voice.wav"
|
||||
voice_name = "my-voice"
|
||||
voice_text = "这是我的自定义音色测试"
|
||||
|
||||
uri = upload_voice(api_key, audio_path, voice_name, voice_text)
|
||||
```
|
||||
|
||||
### 上传成功后的配置
|
||||
|
||||
上传成功后,您会收到类似这样的响应:
|
||||
|
||||
```json
|
||||
{
|
||||
"uri": "speech:my-custom-voice:qc10fwc040:abc123def456"
|
||||
}
|
||||
```
|
||||
|
||||
将这个 URI 添加到您的 `configuration.yaml`:
|
||||
|
||||
```yaml
|
||||
siliconflow_tts:
|
||||
custom_voices:
|
||||
"speech:my-custom-voice:qc10fwc040:abc123def456": "我的专属音色"
|
||||
```
|
||||
|
||||
重启 Home Assistant 后,您就可以在配置界面中选择这个音色了!
|
||||
|
||||
### 🔧 故障排除
|
||||
|
||||
**上传失败常见原因**:
|
||||
1. **API 密钥错误**:检查密钥是否正确复制
|
||||
2. **音频格式不支持**:尝试转换为 wav 格式
|
||||
3. **文件过大**:建议音频文件小于 10MB
|
||||
4. **网络问题**:检查网络连接
|
||||
|
||||
**音色使用问题**:
|
||||
1. **音色不显示**:确保已重启 Home Assistant
|
||||
2. **音色不生效**:检查 URI 是否正确复制
|
||||
3. **音质不佳**:尝试使用更高质量的源音频
|
||||
|
||||
## 📖 使用示例
|
||||
|
||||
### 在自动化中使用
|
||||
|
||||
```yaml
|
||||
automation:
|
||||
- alias: "门铃提醒"
|
||||
trigger:
|
||||
platform: state
|
||||
entity_id: binary_sensor.doorbell
|
||||
to: "on"
|
||||
action:
|
||||
- service: tts.speak
|
||||
data:
|
||||
entity_id: tts.siliconflow_tts
|
||||
message: "有客人来访,请注意查看"
|
||||
# 使用自定义音色
|
||||
voice: "speech:my-custom-voice:qc10fwc040:abc123def456"
|
||||
```
|
||||
|
||||
### 在脚本中使用
|
||||
|
||||
```yaml
|
||||
script:
|
||||
welcome_home:
|
||||
sequence:
|
||||
- service: tts.speak
|
||||
data:
|
||||
entity_id: tts.siliconflow_tts
|
||||
message: "欢迎回家,{{ states('sensor.family_member') }}!"
|
||||
speed: 1.2
|
||||
gain: 2
|
||||
```
|
||||
|
||||
### 在 Node-RED 中使用
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "tts_node",
|
||||
"type": "api-call-service",
|
||||
"name": "TTS 播报",
|
||||
"server": "home_assistant_server",
|
||||
"service_domain": "tts",
|
||||
"service": "speak",
|
||||
"data": {
|
||||
"entity_id": "tts.siliconflow_tts",
|
||||
"message": "这是来自 Node-RED 的消息"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 🔧 配置选项
|
||||
|
||||
| 参数 | 类型 | 默认值 | 描述 |
|
||||
|------|------|--------|------|
|
||||
| `voice` | string | 阿狸 | 选择的音色 |
|
||||
| `speed` | float | 1.0 | 语速 (0.25-4.0) |
|
||||
| `response_format` | string | mp3 | 音频格式 |
|
||||
| `sample_rate` | int | 44100 | 采样率 (Hz) |
|
||||
| `gain` | int | -2 | 音量增益 (dB) |
|
||||
|
||||
## 🐛 问题反馈
|
||||
|
||||
遇到问题?请通过以下方式反馈:
|
||||
|
||||
1. [GitHub Issues](https://github.com/your-username/siliconflow-tts-ha/issues)
|
||||
2. [Home Assistant 社区论坛](https://bbs.hassbian.com/)
|
||||
|
||||
## 🤝 贡献代码
|
||||
|
||||
欢迎提交 Pull Request!请确保:
|
||||
|
||||
1. 代码符合 Python PEP 8 规范
|
||||
2. 添加必要的测试用例
|
||||
3. 更新相关文档
|
||||
|
||||
## 📄 开源协议
|
||||
|
||||
本项目基于 [MIT License](LICENSE) 开源。
|
||||
|
||||
## 🙏 致谢
|
||||
|
||||
- [SiliconFlow](https://siliconflow.cn/) - 提供优秀的语音合成 API
|
||||
- [Home Assistant](https://www.home-assistant.io/) - 开源智能家居平台
|
||||
- 所有贡献者和用户的支持
|
||||
|
||||
## 📚 相关资源
|
||||
|
||||
- [SiliconFlow API 文档](https://docs.siliconflow.cn/)
|
||||
- [Home Assistant TTS 文档](https://www.home-assistant.io/integrations/tts/)
|
||||
- [CosyVoice 项目](https://github.com/FunAudioLLM/CosyVoice)
|
||||
|
||||
---
|
||||
|
||||
⭐ 如果这个项目对您有帮助,请给我们一个 Star!
|
||||
|
||||
🔗 **项目链接**: https://github.com/your-username/siliconflow-tts-ha
|
||||
@@ -0,0 +1,95 @@
|
||||
# SiliconFlow TTS/STT 组件安装指南
|
||||
|
||||
## 安装步骤
|
||||
|
||||
### 1. 重启Home Assistant
|
||||
修改了组件文件后,需要重启Home Assistant以加载新的组件。
|
||||
|
||||
### 2. 添加集成
|
||||
重启后,按以下步骤添加集成:
|
||||
|
||||
1. 进入 **设置** > **设备与服务**
|
||||
2. 点击右下角的 **添加集成** 按钮
|
||||
3. 搜索 **SiliconFlow**
|
||||
4. 你应该能看到两个集成:
|
||||
- **SiliconFlow TTS**
|
||||
- **SiliconFlow STT**
|
||||
|
||||
### 3. 配置TTS
|
||||
1. 选择 **SiliconFlow TTS**
|
||||
2. 输入你的硅基流动API密钥
|
||||
3. 点击提交
|
||||
|
||||
### 4. 配置STT
|
||||
1. 选择 **SiliconFlow STT**
|
||||
2. 输入你的硅基流动API密钥
|
||||
3. 点击提交
|
||||
|
||||
### 5. 配置语音助手
|
||||
配置完成后:
|
||||
|
||||
1. 进入 **设置** > **语音助手**
|
||||
2. 在 **文本转语音** 中选择 **SiliconFlow TTS**
|
||||
3. 在 **语音转文本** 中选择 **SiliconFlow STT**
|
||||
|
||||
## 故障排除
|
||||
|
||||
### 找不到集成
|
||||
如果在添加集成页面找不到SiliconFlow:
|
||||
1. 确保已重启Home Assistant
|
||||
2. 检查日志是否有错误信息
|
||||
3. 确认文件结构正确
|
||||
|
||||
### API密钥验证失败
|
||||
如果API密钥验证失败:
|
||||
1. 检查API密钥是否正确
|
||||
2. 确认网络连接正常
|
||||
3. 检查硅基流动服务状态
|
||||
|
||||
### 语音助手中找不到选项
|
||||
如果在语音助手设置中找不到对应选项:
|
||||
1. 确保两个集成都已成功配置
|
||||
2. 重新加载集成
|
||||
3. 重启Home Assistant
|
||||
|
||||
## 使用方法
|
||||
|
||||
配置完成后,你可以:
|
||||
|
||||
### 在自动化中使用TTS
|
||||
```yaml
|
||||
action:
|
||||
- service: tts.speak
|
||||
target:
|
||||
entity_id: tts.siliconflow_tts
|
||||
data:
|
||||
message: "你能用高兴的情感说吗?<|endofprompt|>Hello World!"
|
||||
```
|
||||
|
||||
### 在脚本中使用
|
||||
```yaml
|
||||
script:
|
||||
test_tts:
|
||||
sequence:
|
||||
- service: tts.speak
|
||||
target:
|
||||
entity_id: tts.siliconflow_tts
|
||||
data:
|
||||
message: "测试消息"
|
||||
options:
|
||||
voice: "FunAudioLLM/CosyVoice2-0.5B:anna"
|
||||
```
|
||||
|
||||
### 使用自定义音色
|
||||
```yaml
|
||||
script:
|
||||
custom_voice:
|
||||
sequence:
|
||||
- service: tts.speak
|
||||
target:
|
||||
entity_id: tts.siliconflow_tts
|
||||
data:
|
||||
message: "自定义音色测试"
|
||||
options:
|
||||
reference_audio: "speech:your-voice-uri"
|
||||
```
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
"""硅基流动TTS组件初始化文件"""
|
||||
import logging
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
|
||||
from .const import DOMAIN, CONF_CUSTOM_VOICES, update_voice_options
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
# configuration.yaml 配置架构
|
||||
CONFIG_SCHEMA = vol.Schema(
|
||||
{
|
||||
DOMAIN: vol.Schema(
|
||||
{
|
||||
vol.Optional(CONF_CUSTOM_VOICES, default={}): vol.Schema(
|
||||
{cv.string: cv.string}
|
||||
)
|
||||
}
|
||||
)
|
||||
},
|
||||
extra=vol.ALLOW_EXTRA,
|
||||
)
|
||||
|
||||
async def async_setup(hass: HomeAssistant, config: dict):
|
||||
"""设置组件"""
|
||||
# 读取用户自定义音色配置
|
||||
domain_config = config.get(DOMAIN, {})
|
||||
custom_voices = domain_config.get(CONF_CUSTOM_VOICES, {})
|
||||
|
||||
if custom_voices:
|
||||
_LOGGER.info(f"🎭 加载了 {len(custom_voices)} 个自定义音色: {list(custom_voices.values())}")
|
||||
update_voice_options(custom_voices)
|
||||
else:
|
||||
_LOGGER.debug("未发现自定义音色配置,使用默认音色")
|
||||
|
||||
return True
|
||||
|
||||
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry):
|
||||
"""从配置条目设置组件"""
|
||||
await hass.config_entries.async_forward_entry_setups(entry, ["tts"])
|
||||
return True
|
||||
|
||||
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry):
|
||||
"""卸载配置条目"""
|
||||
return await hass.config_entries.async_unload_platforms(entry, ["tts"])
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+189
@@ -0,0 +1,189 @@
|
||||
"""SiliconFlow TTS配置流程"""
|
||||
import asyncio
|
||||
import logging
|
||||
from typing import Any
|
||||
|
||||
import aiohttp
|
||||
import async_timeout
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant import config_entries
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
|
||||
from .const import (
|
||||
DOMAIN,
|
||||
DEFAULT_VOICE,
|
||||
DEFAULT_SPEED,
|
||||
DEFAULT_SAMPLE_RATE,
|
||||
DEFAULT_RESPONSE_FORMAT,
|
||||
DEFAULT_GAIN,
|
||||
MIN_SPEED,
|
||||
MAX_SPEED,
|
||||
MIN_GAIN,
|
||||
MAX_GAIN,
|
||||
SUPPORTED_FORMATS,
|
||||
SUPPORTED_SAMPLE_RATES,
|
||||
get_voice_options,
|
||||
CONF_VOICE,
|
||||
CONF_SPEED,
|
||||
CONF_SAMPLE_RATE,
|
||||
CONF_RESPONSE_FORMAT,
|
||||
CONF_GAIN,
|
||||
)
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
STEP_USER_DATA_SCHEMA = vol.Schema(
|
||||
{
|
||||
vol.Required("api_key"): str,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
async def validate_input(hass: HomeAssistant, data: dict[str, Any]) -> dict[str, Any]:
|
||||
"""Validate the user input allows us to connect.
|
||||
|
||||
Data has the keys from STEP_USER_DATA_SCHEMA with values provided by the user.
|
||||
"""
|
||||
api_key = data["api_key"]
|
||||
|
||||
headers = {
|
||||
"Authorization": f"Bearer {api_key}",
|
||||
"Content-Type": "application/json",
|
||||
}
|
||||
|
||||
payload = {
|
||||
"model": "FunAudioLLM/CosyVoice2-0.5B",
|
||||
"input": "测试",
|
||||
"voice": "FunAudioLLM/CosyVoice2-0.5B:anna",
|
||||
"response_format": "mp3",
|
||||
}
|
||||
|
||||
try:
|
||||
async with aiohttp.ClientSession() as session:
|
||||
async with async_timeout.timeout(10):
|
||||
async with session.post(
|
||||
"https://api.siliconflow.cn/v1/audio/speech",
|
||||
headers=headers,
|
||||
json=payload
|
||||
) as response:
|
||||
_LOGGER.debug(f"TTS API验证响应状态: {response.status}")
|
||||
if response.status == 401:
|
||||
_LOGGER.error("TTS API密钥无效")
|
||||
raise InvalidAuth
|
||||
elif response.status == 403:
|
||||
_LOGGER.error("TTS API密钥权限不足")
|
||||
raise InvalidAuth
|
||||
elif response.status >= 500:
|
||||
_LOGGER.error(f"TTS服务器错误: {response.status}")
|
||||
raise CannotConnect
|
||||
# 200, 400等其他状态码都表示API密钥有效
|
||||
_LOGGER.info("TTS API密钥验证成功")
|
||||
except aiohttp.ClientError as e:
|
||||
_LOGGER.error(f"TTS网络连接错误: {e}")
|
||||
raise CannotConnect
|
||||
except asyncio.TimeoutError:
|
||||
_LOGGER.error("TTS API请求超时")
|
||||
raise CannotConnect
|
||||
except Exception as e:
|
||||
_LOGGER.error(f"TTS验证过程中出现未知错误: {e}")
|
||||
raise CannotConnect
|
||||
|
||||
# Return info that you want to store in the config entry.
|
||||
return {"title": "SiliconFlow TTS"}
|
||||
|
||||
|
||||
class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
||||
"""Handle a config flow for SiliconFlow TTS."""
|
||||
|
||||
VERSION = 1
|
||||
|
||||
@staticmethod
|
||||
@callback
|
||||
def async_get_options_flow(config_entry):
|
||||
"""Get the options flow for this handler."""
|
||||
return OptionsFlowHandler(config_entry)
|
||||
|
||||
async def async_step_user(
|
||||
self, user_input: dict[str, Any] | None = None
|
||||
) -> config_entries.FlowResult:
|
||||
"""Handle the initial step."""
|
||||
errors: dict[str, str] = {}
|
||||
if user_input is not None:
|
||||
_LOGGER.info(f"开始验证TTS配置,API密钥长度: {len(user_input.get('api_key', ''))}")
|
||||
try:
|
||||
info = await validate_input(self.hass, user_input)
|
||||
_LOGGER.info("TTS配置验证成功")
|
||||
except CannotConnect:
|
||||
_LOGGER.error("TTS配置验证失败: 无法连接")
|
||||
errors["base"] = "cannot_connect"
|
||||
except InvalidAuth:
|
||||
_LOGGER.error("TTS配置验证失败: API密钥无效")
|
||||
errors["base"] = "invalid_auth"
|
||||
except Exception as e: # pylint: disable=broad-except
|
||||
_LOGGER.exception(f"TTS配置验证时出现未知异常: {e}")
|
||||
errors["base"] = "unknown"
|
||||
else:
|
||||
_LOGGER.info(f"TTS集成配置成功: {info['title']}")
|
||||
return self.async_create_entry(title=info["title"], data=user_input)
|
||||
|
||||
return self.async_show_form(
|
||||
step_id="user", data_schema=STEP_USER_DATA_SCHEMA, errors=errors
|
||||
)
|
||||
|
||||
|
||||
class OptionsFlowHandler(config_entries.OptionsFlow):
|
||||
"""Handle options for SiliconFlow TTS."""
|
||||
|
||||
def __init__(self, config_entry: config_entries.ConfigEntry) -> None:
|
||||
"""Initialize options flow."""
|
||||
self._config_entry = config_entry
|
||||
|
||||
async def async_step_init(
|
||||
self, user_input: dict[str, Any] | None = None
|
||||
) -> config_entries.FlowResult:
|
||||
"""Manage the options."""
|
||||
if user_input is not None:
|
||||
return self.async_create_entry(title="", data=user_input)
|
||||
|
||||
# 获取当前配置的值,如果没有则使用默认值
|
||||
current_voice = self._config_entry.options.get(CONF_VOICE, DEFAULT_VOICE)
|
||||
current_speed = self._config_entry.options.get(CONF_SPEED, DEFAULT_SPEED)
|
||||
current_sample_rate = self._config_entry.options.get(CONF_SAMPLE_RATE, DEFAULT_SAMPLE_RATE)
|
||||
current_response_format = self._config_entry.options.get(CONF_RESPONSE_FORMAT, DEFAULT_RESPONSE_FORMAT)
|
||||
current_gain = self._config_entry.options.get(CONF_GAIN, DEFAULT_GAIN)
|
||||
|
||||
# 根据选择的格式获取支持的采样率
|
||||
available_sample_rates = SUPPORTED_SAMPLE_RATES.get(current_response_format, [DEFAULT_SAMPLE_RATE])
|
||||
|
||||
# 获取当前可用的音色选项(默认 + 用户自定义)
|
||||
voice_options = get_voice_options()
|
||||
_LOGGER.debug(f"当前可用音色选项: {len(voice_options)} 个")
|
||||
|
||||
options_schema = vol.Schema(
|
||||
{
|
||||
vol.Optional(CONF_VOICE, default=current_voice): vol.In(voice_options),
|
||||
vol.Optional(CONF_SPEED, default=current_speed): vol.All(
|
||||
vol.Coerce(float), vol.Range(min=MIN_SPEED, max=MAX_SPEED)
|
||||
),
|
||||
vol.Optional(CONF_RESPONSE_FORMAT, default=current_response_format): vol.In(SUPPORTED_FORMATS),
|
||||
vol.Optional(CONF_SAMPLE_RATE, default=current_sample_rate): vol.In(available_sample_rates),
|
||||
vol.Optional(CONF_GAIN, default=current_gain): vol.All(
|
||||
vol.Coerce(int), vol.Range(min=MIN_GAIN, max=MAX_GAIN)
|
||||
),
|
||||
}
|
||||
)
|
||||
|
||||
return self.async_show_form(
|
||||
step_id="init",
|
||||
data_schema=options_schema,
|
||||
)
|
||||
|
||||
|
||||
class CannotConnect(HomeAssistantError):
|
||||
"""Error to indicate we cannot connect."""
|
||||
|
||||
|
||||
class InvalidAuth(HomeAssistantError):
|
||||
"""Error to indicate there is invalid auth."""
|
||||
@@ -0,0 +1,68 @@
|
||||
"""硅基流动TTS组件常量定义"""
|
||||
|
||||
DOMAIN = "siliconflow_tts"
|
||||
DEFAULT_NAME = "SiliconFlow TTS"
|
||||
|
||||
# API配置
|
||||
API_BASE_URL = "https://api.siliconflow.cn/v1"
|
||||
API_ENDPOINT = f"{API_BASE_URL}/audio/speech"
|
||||
|
||||
# 默认配置
|
||||
DEFAULT_MODEL = "FunAudioLLM/CosyVoice2-0.5B"
|
||||
DEFAULT_VOICE = "speech:Dahu-HA-Ari:qc10fwc040:jxlvonagnnzdvbotrmrr"
|
||||
DEFAULT_RESPONSE_FORMAT = "mp3"
|
||||
DEFAULT_SAMPLE_RATE = 44100 # 更高的采样率,减少切断问题
|
||||
DEFAULT_SPEED = 1.0 # 正常语速,避免因语速过快导致切断
|
||||
DEFAULT_GAIN = -2 # 稍微降低增益,避免音频削峰导致的切断
|
||||
DEFAULT_STREAM = False # 不使用流式传输,确保最快响应
|
||||
|
||||
# 支持的音频格式
|
||||
SUPPORTED_FORMATS = ["mp3", "wav", "opus", "pcm"]
|
||||
|
||||
# 支持的采样率
|
||||
SUPPORTED_SAMPLE_RATES = {
|
||||
"opus": [48000],
|
||||
"wav": [8000, 16000, 24000, 32000, 44100],
|
||||
"pcm": [8000, 16000, 24000, 32000, 44100],
|
||||
"mp3": [32000, 44100]
|
||||
}
|
||||
|
||||
# 速度范围
|
||||
MIN_SPEED = 0.25
|
||||
MAX_SPEED = 4.0
|
||||
|
||||
# 增益范围
|
||||
MIN_GAIN = -10
|
||||
MAX_GAIN = 10
|
||||
|
||||
# 默认音色选项(内置)
|
||||
DEFAULT_VOICE_OPTIONS = {
|
||||
"speech:Dahu-HA-Ari:qc10fwc040:jxlvonagnnzdvbotrmrr": "阿狸",
|
||||
"speech:Dahu-HA-Lulu:qc10fwc040:ugmvnfqyesexvqjuneyf": "露露",
|
||||
"speech:Dahu-HA-Soraka:qc10fwc040:ezxwqvmnazypoekujait": "索拉卡",
|
||||
"speech:Dahu-HA-Zac:qc10fwc040:smplyqhoxcxwepjavhci": "扎克"
|
||||
}
|
||||
|
||||
# 全局变量存储合并后的音色选项(默认 + 用户自定义)
|
||||
_VOICE_OPTIONS = DEFAULT_VOICE_OPTIONS.copy()
|
||||
|
||||
def get_voice_options():
|
||||
"""获取当前可用的所有音色选项(默认 + 用户自定义)"""
|
||||
return _VOICE_OPTIONS.copy()
|
||||
|
||||
def update_voice_options(custom_voices: dict):
|
||||
"""更新音色选项,将用户自定义音色与默认音色合并"""
|
||||
global _VOICE_OPTIONS
|
||||
_VOICE_OPTIONS = DEFAULT_VOICE_OPTIONS.copy()
|
||||
if custom_voices:
|
||||
_VOICE_OPTIONS.update(custom_voices)
|
||||
|
||||
# configuration.yaml 配置键名
|
||||
CONF_CUSTOM_VOICES = "custom_voices"
|
||||
|
||||
# 配置选项的键名
|
||||
CONF_VOICE = "voice"
|
||||
CONF_SPEED = "speed"
|
||||
CONF_SAMPLE_RATE = "sample_rate"
|
||||
CONF_RESPONSE_FORMAT = "response_format"
|
||||
CONF_GAIN = "gain"
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"name": "SiliconFlow TTS",
|
||||
"hacs": "1.6.0",
|
||||
"domains": ["tts"],
|
||||
"country": ["CN"],
|
||||
"homeassistant": "2023.1.0",
|
||||
"iot_class": "Cloud Polling",
|
||||
"render_readme": true
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
# SiliconFlow TTS
|
||||
|
||||
基于硅基流动 CosyVoice2-0.5B 模型的高质量文本转语音集成。
|
||||
|
||||
## 功能特性
|
||||
|
||||
- 🎭 动态音色配置 - 支持自定义音色
|
||||
- 🎚️ 前端可视化配置界面
|
||||
- 🔄 配置实时生效,无需重启
|
||||
- 🎵 多种内置精品音色
|
||||
- ⚡ 语速、音量、格式全面可调
|
||||
|
||||
## 快速开始
|
||||
|
||||
1. 获取 [SiliconFlow](https://cloud.siliconflow.cn/) API 密钥
|
||||
2. 在集成配置中输入 API 密钥
|
||||
3. 在配置界面调整音色和参数
|
||||
4. 开始使用高质量语音合成
|
||||
|
||||
## 自定义音色
|
||||
|
||||
支持上传个人音色文件,实现个性化语音输出。
|
||||
|
||||
详细使用说明请查看 [GitHub 仓库](https://github.com/your-username/siliconflow-tts-ha)。
|
||||
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"domain": "siliconflow_tts",
|
||||
"name": "SiliconFlow TTS",
|
||||
"version": "1.0.0",
|
||||
"documentation": "https://github.com/your-username/siliconflow-tts-ha",
|
||||
"issue_tracker": "https://github.com/your-username/siliconflow-tts-ha/issues",
|
||||
"requirements": ["aiohttp>=3.8.0", "async_timeout>=4.0.0"],
|
||||
"codeowners": ["@your-username"],
|
||||
"config_flow": true,
|
||||
"dependencies": [],
|
||||
"after_dependencies": [],
|
||||
"iot_class": "cloud_polling"
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
{
|
||||
"config": {
|
||||
"step": {
|
||||
"user": {
|
||||
"title": "设置SiliconFlow TTS",
|
||||
"description": "请输入您的SiliconFlow API密钥",
|
||||
"data": {
|
||||
"api_key": "API密钥"
|
||||
}
|
||||
}
|
||||
},
|
||||
"error": {
|
||||
"cannot_connect": "无法连接到SiliconFlow服务,请检查网络连接",
|
||||
"invalid_auth": "API密钥无效,请检查密钥是否正确",
|
||||
"unknown": "发生未知错误,请重试"
|
||||
},
|
||||
"abort": {
|
||||
"already_configured": "该API密钥已经配置过了"
|
||||
}
|
||||
},
|
||||
"options": {
|
||||
"step": {
|
||||
"init": {
|
||||
"title": "配置SiliconFlow TTS选项",
|
||||
"description": "调整语音合成参数,更改后将立即生效,无需重启",
|
||||
"data": {
|
||||
"voice": "音色",
|
||||
"speed": "语速 (0.25-4.0)",
|
||||
"response_format": "音频格式",
|
||||
"sample_rate": "采样率 (Hz)",
|
||||
"gain": "音量增益 (-10到10 dB)"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,246 @@
|
||||
"""硅基流动TTS组件"""
|
||||
import asyncio
|
||||
import logging
|
||||
import aiohttp
|
||||
import async_timeout
|
||||
import base64
|
||||
import os
|
||||
from typing import Any
|
||||
|
||||
from homeassistant.components.tts import (
|
||||
TextToSpeechEntity,
|
||||
Voice,
|
||||
TtsAudioType,
|
||||
)
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
|
||||
from .const import (
|
||||
API_ENDPOINT,
|
||||
DEFAULT_MODEL,
|
||||
DEFAULT_VOICE,
|
||||
DEFAULT_RESPONSE_FORMAT,
|
||||
DEFAULT_SAMPLE_RATE,
|
||||
DEFAULT_SPEED,
|
||||
DEFAULT_GAIN,
|
||||
DEFAULT_STREAM,
|
||||
SUPPORTED_FORMATS,
|
||||
MIN_SPEED,
|
||||
MAX_SPEED,
|
||||
MIN_GAIN,
|
||||
MAX_GAIN,
|
||||
get_voice_options,
|
||||
CONF_VOICE,
|
||||
CONF_SPEED,
|
||||
CONF_SAMPLE_RATE,
|
||||
CONF_RESPONSE_FORMAT,
|
||||
CONF_GAIN,
|
||||
)
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
# 支持的语音列表(根据CosyVoice2-0.5B模型的实际语音)
|
||||
SUPPORTED_VOICES = {
|
||||
"zh": [
|
||||
Voice("speech:Dahu-HA-Lulu:qc10fwc040:ugmvnfqyesexvqjuneyf", "Lulu"),
|
||||
],
|
||||
"en": [
|
||||
Voice("speech:Dahu-HA-Lulu:qc10fwc040:ugmvnfqyesexvqjuneyf", "Lulu"),
|
||||
]
|
||||
}
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
config_entry: ConfigEntry,
|
||||
async_add_entities: AddEntitiesCallback,
|
||||
) -> None:
|
||||
"""设置TTS实体"""
|
||||
async_add_entities([SiliconFlowTTSEntity(config_entry)])
|
||||
|
||||
class SiliconFlowTTSEntity(TextToSpeechEntity):
|
||||
"""硅基流动TTS实体"""
|
||||
|
||||
def __init__(self, config_entry: ConfigEntry) -> None:
|
||||
"""初始化TTS实体"""
|
||||
self._config_entry = config_entry
|
||||
self._attr_name = "SiliconFlow TTS"
|
||||
self._attr_unique_id = f"siliconflow_tts_{config_entry.entry_id}"
|
||||
|
||||
@property
|
||||
def supported_languages(self) -> list[str]:
|
||||
"""返回支持的语言列表"""
|
||||
return list(SUPPORTED_VOICES.keys())
|
||||
|
||||
def get_supported_voices(self, language: str) -> list[Voice] | None:
|
||||
"""获取指定语言支持的语音(动态加载所有可用音色)"""
|
||||
# 从动态音色选项中生成Voice对象列表
|
||||
voice_options = get_voice_options()
|
||||
voices = []
|
||||
for voice_id, voice_name in voice_options.items():
|
||||
voices.append(Voice(voice_id, voice_name))
|
||||
_LOGGER.debug(f"为语言 '{language}' 提供 {len(voices)} 个音色选项")
|
||||
return voices
|
||||
|
||||
@property
|
||||
def default_language(self) -> str:
|
||||
"""返回默认语言"""
|
||||
return "zh"
|
||||
|
||||
@property
|
||||
def supported_audio_codecs(self) -> list[str]:
|
||||
"""返回支持的音频编解码器"""
|
||||
return SUPPORTED_FORMATS
|
||||
|
||||
def _encode_audio_file(self, file_path: str) -> str | None:
|
||||
"""将音频文件编码为base64字符串"""
|
||||
try:
|
||||
if not os.path.exists(file_path):
|
||||
_LOGGER.error(f"音频文件不存在: {file_path}")
|
||||
return None
|
||||
|
||||
with open(file_path, 'rb') as audio_file:
|
||||
audio_data = audio_file.read()
|
||||
|
||||
# 检查文件大小,如果太大则截取前100KB
|
||||
if len(audio_data) > 100 * 1024: # 100KB
|
||||
_LOGGER.warning(f"音频文件较大 ({len(audio_data)} bytes),截取前100KB")
|
||||
audio_data = audio_data[:100 * 1024]
|
||||
|
||||
encoded_audio = base64.b64encode(audio_data).decode('utf-8')
|
||||
_LOGGER.debug(f"成功编码音频文件: {file_path}, 大小: {len(audio_data)} bytes")
|
||||
return encoded_audio
|
||||
except Exception as e:
|
||||
_LOGGER.error(f"编码音频文件失败: {e}")
|
||||
return None
|
||||
|
||||
def _is_uri_reference(self, reference: str) -> bool:
|
||||
"""检查是否为URI格式的参考音频"""
|
||||
return reference.startswith("speech:")
|
||||
|
||||
async def async_get_tts_audio(
|
||||
self,
|
||||
message: str,
|
||||
language: str,
|
||||
options: dict[str, Any] | None = None,
|
||||
) -> TtsAudioType:
|
||||
"""获取TTS音频"""
|
||||
|
||||
api_key = self._config_entry.data.get("api_key")
|
||||
if not api_key:
|
||||
_LOGGER.error("未找到API密钥")
|
||||
return None
|
||||
|
||||
try:
|
||||
headers = {
|
||||
"Authorization": f"Bearer {api_key}",
|
||||
"Content-Type": "application/json",
|
||||
}
|
||||
|
||||
# 从配置条目的选项中获取参数,优先级:service call options > config entry options > defaults
|
||||
config_options = self._config_entry.options
|
||||
_LOGGER.debug(f"配置选项: {config_options}")
|
||||
_LOGGER.debug(f"服务调用选项: {options}")
|
||||
|
||||
# 优化参数获取逻辑,确保正确处理None值
|
||||
response_format = (
|
||||
options.get("response_format") if options and "response_format" in options
|
||||
else config_options.get(CONF_RESPONSE_FORMAT, DEFAULT_RESPONSE_FORMAT)
|
||||
)
|
||||
speed = (
|
||||
options.get("speed") if options and "speed" in options
|
||||
else config_options.get(CONF_SPEED, DEFAULT_SPEED)
|
||||
)
|
||||
sample_rate = (
|
||||
options.get("sample_rate") if options and "sample_rate" in options
|
||||
else config_options.get(CONF_SAMPLE_RATE, DEFAULT_SAMPLE_RATE)
|
||||
)
|
||||
gain = (
|
||||
options.get("gain") if options and "gain" in options
|
||||
else config_options.get(CONF_GAIN, DEFAULT_GAIN)
|
||||
)
|
||||
|
||||
# 验证参数范围
|
||||
speed = max(MIN_SPEED, min(MAX_SPEED, speed))
|
||||
gain = max(MIN_GAIN, min(MAX_GAIN, gain))
|
||||
|
||||
# 构建输入文本,根据API文档添加特殊标记,并解决语音切断问题
|
||||
input_text = message.strip()
|
||||
|
||||
# 解决语音切断问题:在文本末尾添加适当的标点符号和停顿
|
||||
if not input_text.endswith(('.', '。', '!', '!', '?', '?', ',', ',', '、')):
|
||||
input_text += "。" # 添加句号确保语音完整
|
||||
|
||||
# 添加轻微的停顿来避免切断(使用标点符号)
|
||||
input_text += " " # 两个空格提供自然的停顿
|
||||
|
||||
if options and "emotion" in options:
|
||||
emotion = options["emotion"]
|
||||
input_text = f"Can you say it with a {emotion} emotion? <|endofprompt|>{input_text}"
|
||||
|
||||
_LOGGER.debug(f"处理后的文本: '{input_text}'")
|
||||
|
||||
# 构建payload
|
||||
payload = {
|
||||
"model": DEFAULT_MODEL,
|
||||
"input": input_text,
|
||||
"response_format": response_format,
|
||||
"speed": speed,
|
||||
"sample_rate": sample_rate,
|
||||
"gain": gain,
|
||||
"stream": DEFAULT_STREAM, # 显式设置为false,确保最快响应
|
||||
}
|
||||
|
||||
# 确定使用的语音,优先级:service call options > config entry options > defaults
|
||||
voice = DEFAULT_VOICE
|
||||
|
||||
# 检查是否在service call中指定了语音(包括预设语音和自定义音色)
|
||||
if options and "voice" in options:
|
||||
voice = options["voice"]
|
||||
_LOGGER.info(f"使用service call指定的语音: {voice}")
|
||||
elif options and "reference_audio" in options:
|
||||
# 为了兼容性,仍然支持reference_audio参数
|
||||
reference_audio = options["reference_audio"]
|
||||
if self._is_uri_reference(reference_audio):
|
||||
voice = reference_audio
|
||||
_LOGGER.info(f"使用reference_audio参数指定的URI音色: {reference_audio}")
|
||||
else:
|
||||
_LOGGER.warning(f"本地文件参考音频暂不支持,请先上传获取URI: {reference_audio}")
|
||||
elif config_options.get(CONF_VOICE):
|
||||
# 使用配置的音色
|
||||
voice = config_options.get(CONF_VOICE)
|
||||
_LOGGER.info(f"使用配置的语音: {voice}")
|
||||
else:
|
||||
# 使用全局默认语音
|
||||
_LOGGER.info(f"🎭 使用全局默认语音: {voice}")
|
||||
|
||||
# 验证选择的音色是否在可用音色列表中
|
||||
voice_options = get_voice_options()
|
||||
if voice not in voice_options:
|
||||
_LOGGER.warning(f"选择的音色 '{voice}' 不在可用列表中,使用默认音色")
|
||||
voice = DEFAULT_VOICE
|
||||
|
||||
payload["voice"] = voice
|
||||
|
||||
_LOGGER.info(f"TTS配置 - 音色:{voice[-10:]}..., 格式:{response_format}, 采样率:{sample_rate}Hz, 语速:{speed}, 增益:{gain}dB")
|
||||
_LOGGER.debug(f"完整音色ID: {voice}")
|
||||
_LOGGER.debug(f"完整payload: {payload}")
|
||||
|
||||
async with aiohttp.ClientSession() as session:
|
||||
async with async_timeout.timeout(30):
|
||||
async with session.post(API_ENDPOINT, headers=headers, json=payload) as response:
|
||||
if response.status == 200:
|
||||
audio_data = await response.read()
|
||||
_LOGGER.debug(f"TTS成功,音频大小: {len(audio_data)} bytes")
|
||||
return (response_format, audio_data)
|
||||
else:
|
||||
error_text = await response.text()
|
||||
_LOGGER.error(f"TTS API错误 {response.status}: {error_text}")
|
||||
return None
|
||||
|
||||
except asyncio.TimeoutError:
|
||||
_LOGGER.error("TTS请求超时")
|
||||
return None
|
||||
except Exception as e:
|
||||
_LOGGER.error(f"TTS处理错误: {e}")
|
||||
return None
|
||||
Reference in New Issue
Block a user