First upload

This commit is contained in:
Ethan930717
2025-08-01 23:02:17 +08:00
parent ca47eee6b5
commit 01e7f3c264
16 changed files with 1304 additions and 0 deletions
+20
View File
@@ -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"
+250
View File
@@ -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(自定义音色)
- 类型:stringURI格式)
- 描述:使用上传后获得的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
+306
View File
@@ -0,0 +1,306 @@
# SiliconFlow TTS for Home Assistant
🎙️ 一个功能强大的 Home Assistant 文本转语音集成,基于 [SiliconFlow](https://siliconflow.cn) 的 CosyVoice2-0.5B 模型。
[![GitHub Release](https://img.shields.io/github/release/your-username/siliconflow-tts-ha)](https://github.com/your-username/siliconflow-tts-ha/releases)
[![License](https://img.shields.io/github/license/your-username/siliconflow-tts-ha)](LICENSE)
[![Home Assistant](https://img.shields.io/badge/Home%20Assistant-compatible-brightgreen)](https://www.home-assistant.io/)
[![中文文档](https://img.shields.io/badge/文档-中文-blue)](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
+95
View File
@@ -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
View File
@@ -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
View File
@@ -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."""
+68
View File
@@ -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"
+9
View File
@@ -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
}
+24
View File
@@ -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)。
+13
View File
@@ -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"
}
+36
View File
@@ -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)"
}
}
}
}
}
+246
View File
@@ -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