├── .gitignore ├── hacs.json ├── pic ├── 1.png ├── 2.png ├── 3.png ├── 4.png ├── 5.png └── 6.png ├── custom_components └── aliyun_bailian_tts │ ├── const.py │ ├── manifest.json │ ├── __init__.py │ ├── config_flow.py │ └── tts.py ├── README.md ├── README-en.md └── LICENSE /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | *.mp3 -------------------------------------------------------------------------------- /hacs.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Aliyun BaiLian TTS", 3 | "render_readme": true 4 | } -------------------------------------------------------------------------------- /pic/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/itning/hass-aliyun_bailian_tts/main/pic/1.png -------------------------------------------------------------------------------- /pic/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/itning/hass-aliyun_bailian_tts/main/pic/2.png -------------------------------------------------------------------------------- /pic/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/itning/hass-aliyun_bailian_tts/main/pic/3.png -------------------------------------------------------------------------------- /pic/4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/itning/hass-aliyun_bailian_tts/main/pic/4.png -------------------------------------------------------------------------------- /pic/5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/itning/hass-aliyun_bailian_tts/main/pic/5.png -------------------------------------------------------------------------------- /pic/6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/itning/hass-aliyun_bailian_tts/main/pic/6.png -------------------------------------------------------------------------------- /custom_components/aliyun_bailian_tts/const.py: -------------------------------------------------------------------------------- 1 | DOMAIN = "aliyun_bailian_tts" 2 | CONF_TOKEN = "token" 3 | CONF_MODEL = "model" 4 | CONF_VOICE = "voice" -------------------------------------------------------------------------------- /custom_components/aliyun_bailian_tts/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "domain": "aliyun_bailian_tts", 3 | "name": "Aliyun BaiLian TTS", 4 | "codeowners": ["@itning"], 5 | "documentation": "https://github.com/itning/hass-aliyun_bailian_tts", 6 | "iot_class": "cloud_polling", 7 | "issue_tracker": "https://github.com/hasscc/hass-aliyun_bailian_tts/issues", 8 | "requirements": ["dashscope>=1.24.6"], 9 | "integration_type": "service", 10 | "single_config_entry": true, 11 | "loggers": ["aliyun_bailian_tts"], 12 | "config_flow": true, 13 | "version": "1.1.3" 14 | } -------------------------------------------------------------------------------- /custom_components/aliyun_bailian_tts/__init__.py: -------------------------------------------------------------------------------- 1 | from homeassistant.components.tts import DOMAIN as TTS_DOMAIN 2 | from homeassistant.helpers.discovery import async_load_platform 3 | 4 | from .const import DOMAIN 5 | 6 | 7 | async def async_setup(hass, config): 8 | """Set up the Aliyun BaiLian TTS component from configuration.yaml.""" 9 | if config.get(DOMAIN): 10 | # 如果配置来自 configuration.yaml,建议将其迁移到 config_entry 11 | hass.async_create_task( 12 | hass.config_entries.flow.async_init( 13 | DOMAIN, context={"source": "import"}, data=config[DOMAIN] 14 | ) 15 | ) 16 | return True 17 | 18 | 19 | async def async_setup_entry(hass, entry): 20 | """Set up the Aliyun BaiLian TTS component from a config entry.""" 21 | # 将配置存储到 hass.data 22 | hass.data.setdefault(DOMAIN, {}) 23 | hass.data[DOMAIN][entry.entry_id] = entry 24 | 25 | # 加载 TTS 平台 26 | await async_load_platform(hass, TTS_DOMAIN, DOMAIN, entry.data, {}) 27 | 28 | # 监听选项更新事件 29 | entry.async_on_unload(entry.add_update_listener(async_update_options)) 30 | 31 | return True 32 | 33 | 34 | async def async_unload_entry(hass, entry): 35 | """Unload the Aliyun BaiLian TTS component.""" 36 | # 清理存储的数据 37 | if DOMAIN in hass.data: 38 | hass.data[DOMAIN].pop(entry.entry_id, None) 39 | if not hass.data[DOMAIN]: 40 | hass.data.pop(DOMAIN, None) 41 | return True 42 | 43 | 44 | async def async_update_options(hass, config_entry): 45 | """Update options when they are changed.""" 46 | # 当选项更新时,重新加载集成 47 | await hass.config_entries.async_reload(config_entry.entry_id) 48 | -------------------------------------------------------------------------------- /custom_components/aliyun_bailian_tts/config_flow.py: -------------------------------------------------------------------------------- 1 | from typing import Any 2 | 3 | import voluptuous as vol 4 | from homeassistant import config_entries 5 | from homeassistant.core import callback 6 | from homeassistant.data_entry_flow import FlowResult 7 | 8 | from .const import DOMAIN, CONF_TOKEN, CONF_MODEL, CONF_VOICE 9 | 10 | OPTIONS_SCHEMA = vol.Schema( 11 | { 12 | vol.Required(CONF_TOKEN): str, 13 | vol.Optional(CONF_MODEL, default="qwen3-tts-flash"): str, 14 | vol.Optional(CONF_VOICE, default="Cherry"): str, 15 | } 16 | ) 17 | 18 | 19 | class AliyunBaiLianTTSConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): 20 | """Aliyun BaiLian TTS config flow.""" 21 | 22 | VERSION = 1 23 | 24 | async def async_step_user(self, user_input: dict[str, Any] | None = None) -> FlowResult: 25 | """Handle a flow initiated by the user.""" 26 | # 检查是否已经有配置条目 27 | if self._async_current_entries(): 28 | return self.async_abort(reason="single_instance_allowed") 29 | 30 | errors = {} 31 | 32 | # 如果用户提供了输入,验证并创建配置条目 33 | if user_input is not None: 34 | # 基本验证 35 | if not user_input.get(CONF_TOKEN): 36 | errors[CONF_TOKEN] = "invalid_token" 37 | 38 | if not errors: 39 | # 创建配置条目,同时将数据保存到 options 中 40 | return self.async_create_entry( 41 | title="Aliyun BaiLian TTS", 42 | data=user_input, 43 | options=user_input # 同时保存到 options 中 44 | ) 45 | 46 | # 显示表单让用户输入配置 47 | return self.async_show_form( 48 | step_id="user", 49 | data_schema=OPTIONS_SCHEMA, 50 | errors=errors 51 | ) 52 | 53 | async def async_step_import(self, import_config: dict[str, Any]) -> FlowResult: 54 | """Handle import from configuration.yaml.""" 55 | # 检查是否已经有配置条目 56 | if self._async_current_entries(): 57 | return self.async_abort(reason="single_instance_allowed") 58 | 59 | # 从 configuration.yaml 导入时也同时保存到 options 60 | return self.async_create_entry( 61 | title="Aliyun BaiLian TTS", 62 | data=import_config, 63 | options=import_config 64 | ) 65 | 66 | @staticmethod 67 | @callback 68 | def async_get_options_flow(config_entry): 69 | """Get the options flow for this handler.""" 70 | return AliyunBaiLianTTSOptionsFlowHandler(config_entry) 71 | 72 | 73 | class AliyunBaiLianTTSOptionsFlowHandler(config_entries.OptionsFlow): 74 | """Handle an options flow for Aliyun BaiLian TTS.""" 75 | 76 | def __init__(self, config_entry: config_entries.ConfigEntry): 77 | """Initialize options flow.""" 78 | self._config_entry = config_entry 79 | 80 | async def async_step_init(self, user_input: dict[str, Any] | None = None) -> FlowResult: 81 | """Manage the options.""" 82 | errors = {} 83 | 84 | if user_input is not None: 85 | # 验证输入 86 | if not user_input.get(CONF_TOKEN): 87 | errors[CONF_TOKEN] = "invalid_token" 88 | 89 | if not errors: 90 | return self.async_create_entry(title="", data=user_input) 91 | 92 | # 获取当前的配置值,优先从 options 读取,如果没有则从 data 读取 93 | current_config = {} 94 | if self.config_entry.options: 95 | current_config = self.config_entry.options 96 | else: 97 | # 第一次打开设置页面,从 data 中读取 98 | current_config = self.config_entry.data 99 | 100 | # 显示表单,用当前配置作为默认值 101 | return self.async_show_form( 102 | step_id="init", 103 | data_schema=self.add_suggested_values_to_schema( 104 | OPTIONS_SCHEMA, current_config 105 | ), 106 | errors=errors 107 | ) 108 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

阿里云百炼平台TTS与Home Assistant集成

2 |
3 | 4 | [![GitHub stars](https://img.shields.io/github/stars/itning/hass-aliyun_bailian_tts.svg?style=social&label=Stars)](https://github.com/itning/hass-aliyun_bailian_tts/stargazers) 5 | [![GitHub forks](https://img.shields.io/github/forks/itning/hass-aliyun_bailian_tts.svg?style=social&label=Fork)](https://github.com/itning/hass-aliyun_bailian_tts/network/members) 6 | [![GitHub watchers](https://img.shields.io/github/watchers/itning/hass-aliyun_bailian_tts.svg?style=social&label=Watch)](https://github.com/itning/hass-aliyun_bailian_tts/watchers) 7 | [![GitHub followers](https://img.shields.io/github/followers/itning.svg?style=social&label=Follow)](https://github.com/itning?tab=followers) 8 | 9 | 10 |
11 | 12 |
13 | 14 | [![GitHub issues](https://img.shields.io/github/issues/itning/hass-aliyun_bailian_tts.svg)](https://github.com/itning/hass-aliyun_bailian_tts/issues) 15 | [![GitHub license](https://img.shields.io/github/license/itning/hass-aliyun_bailian_tts.svg)](https://github.com/itning/hass-aliyun_bailian_tts/blob/master/LICENSE) 16 | [![GitHub last commit](https://img.shields.io/github/last-commit/itning/hass-aliyun_bailian_tts.svg)](https://github.com/itning/hass-aliyun_bailian_tts/commits) 17 | [![GitHub repo size in bytes](https://img.shields.io/github/repo-size/itning/hass-aliyun_bailian_tts.svg)](https://github.com/itning/hass-aliyun_bailian_tts) 18 | [![Hits](https://hitcount.itning.com?u=itning&r=hass-aliyun_bailian_tts)](https://github.com/itning/hit-count) 19 | 20 |
21 | 22 | --- 23 | 24 | [ENGLISH README](https://github.com/itning/hass-aliyun_bailian_tts/blob/main/README-en.md) 25 | 26 | ## 简介 27 | Home Assistant TTS发音使用阿里云[百炼平台](https://bailian.console.aliyun.com/)的语音合成大模型 28 | 29 | 目前支持以下两种语音合成模型: 30 | 1. `CosyVoice` - 语音合成CosyVoice大模型,具体可以查看[阿里云文档](https://help.aliyun.com/zh/model-studio/developer-reference/cosyvoice-large-model-for-speech-synthesis/) 31 | 2. `Qwen3-TTS` `Qwen-TTS` - 通义千问系列的语音合成模型,支持输入中文、英文、中英混合的文本,并流式输出音频,具体可以查看[阿里云文档](https://help.aliyun.com/zh/model-studio/qwen-tts) 32 | 33 | ## 安装 34 | 35 | ### 通过HACS安装 36 | 37 | 1. [![Open your Home Assistant instance and open the hass-aliyun_bailian_tts integration inside the Home Assistant Community Store.](https://my.home-assistant.io/badges/hacs_repository.svg)](https://my.home-assistant.io/redirect/hacs_repository/?owner=itning&repository=hass-aliyun_bailian_tts&category=integration) 前往HACS面板,点击右上角菜单,添加自定义集成 38 | 39 | ![](https://raw.githubusercontent.com/itning/hass-aliyun_bailian_tts/refs/heads/main/pic/1.png) 40 | 41 | 2. 在弹出的面板中, 42 | 43 | Repository填写`https://github.com/itning/hass-aliyun_bailian_tts.git` 44 | 45 | type选择:`Integration` 46 | 47 | ![](https://raw.githubusercontent.com/itning/hass-aliyun_bailian_tts/refs/heads/main/pic/2.png) 48 | 49 | 3. 点击ADD按钮进行保存。 50 | 51 | 4. 在HACS中搜索刚刚添加的集成:`Aliyun BaiLian TTS` 并在右侧菜单中点击`Download`按钮 52 | 53 | ![](https://raw.githubusercontent.com/itning/hass-aliyun_bailian_tts/refs/heads/main/pic/3.png) 54 | 55 | 5. 下载完成后按照提示重启Home Assistant 56 | 57 | 6. 重启后在设置中点击设备与服务,然后点击右下角按钮添加集成,搜索`Aliyun BaiLian TTS` 并添加。 58 | 59 | ![](https://raw.githubusercontent.com/itning/hass-aliyun_bailian_tts/refs/heads/main/pic/4.png) 60 | 61 | 7. 点击添加的`Aliyun BaiLian TTS`进入配置页面 62 | 63 | 8. 点击配置按钮,输入百炼平台Token (https://bailian.console.aliyun.com/?tab=model#/api-key) 64 | 65 | 音色(Voice)列表: 66 | - CosyVoice: https://help.aliyun.com/zh/model-studio/text-to-speech 67 | - Qwen-TTS: https://help.aliyun.com/zh/model-studio/qwen-tts 68 | 69 | 模型(Model)列表: 70 | - CosyVoice模型:`cosyvoice-v1`等 71 | - Qwen-TTS模型:`qwen-tts`、`qwen-tts-latest`、`qwen-tts-2025-05-22`、`qwen-tts-2025-04-10` 72 | - Qwen3-TTS模型:`qwen3-tts-flash`、`qwen3-tts-flash-2025-09-18` 73 | 74 | 输入完成后,点击提交。 75 | 76 | ![](https://raw.githubusercontent.com/itning/hass-aliyun_bailian_tts/refs/heads/main/pic/5.png) 77 | 78 | 9. 点击设置,语音助手,修改助手,将引擎改成`Aliyun BaiLian TTS` 79 | 80 | ![](https://raw.githubusercontent.com/itning/hass-aliyun_bailian_tts/refs/heads/main/pic/6.png) 81 | 82 | 10. 完成。 83 | 84 | ## 结尾 85 | 86 | 此项目前期大部分代码是由大模型[qwen-max-2025-01-25](https://bailian.console.aliyun.com/model-market/detail/qwen-max-2025-01-25#/model-market/detail/qwen-max-2025-01-25)生成的,后期使用Claude Sonnet 4。 87 | 88 | ## 感谢 89 | 90 | ![JetBrains Logo (Main) logo](https://resources.jetbrains.com/storage/products/company/brand/logos/jb_beam.svg) 91 | -------------------------------------------------------------------------------- /README-en.md: -------------------------------------------------------------------------------- 1 |

Integration of Aliyun BaiLian Platform TTS with Home Assistant

2 |
3 | 4 | [![GitHub stars](https://img.shields.io/github/stars/itning/hass-aliyun_bailian_tts.svg?style=social&label=Stars)](https://github.com/itning/hass-aliyun_bailian_tts/stargazers) 5 | [![GitHub forks](https://img.shields.io/github/forks/itning/hass-aliyun_bailian_tts.svg?style=social&label=Fork)](https://github.com/itning/hass-aliyun_bailian_tts/network/members) 6 | [![GitHub watchers](https://img.shields.io/github/watchers/itning/hass-aliyun_bailian_tts.svg?style=social&label=Watch)](https://github.com/itning/hass-aliyun_bailian_tts/watchers) 7 | [![GitHub followers](https://img.shields.io/github/followers/itning.svg?style=social&label=Follow)](https://github.com/itning?tab=followers) 8 | 9 | 10 |
11 | 12 |
13 | 14 | [![GitHub issues](https://img.shields.io/github/issues/itning/hass-aliyun_bailian_tts.svg)](https://github.com/itning/hass-aliyun_bailian_tts/issues) 15 | [![GitHub license](https://img.shields.io/github/license/itning/hass-aliyun_bailian_tts.svg)](https://github.com/itning/hass-aliyun_bailian_tts/blob/master/LICENSE) 16 | [![GitHub last commit](https://img.shields.io/github/last-commit/itning/hass-aliyun_bailian_tts.svg)](https://github.com/itning/hass-aliyun_bailian_tts/commits) 17 | [![GitHub repo size in bytes](https://img.shields.io/github/repo-size/itning/hass-aliyun_bailian_tts.svg)](https://github.com/itning/hass-aliyun_bailian_tts) 18 | [![Hits](https://hitcount.itning.com?u=itning&r=hass-aliyun_bailian_tts)](https://github.com/itning/hit-count) 19 | 20 |
21 | 22 | --- 23 | 24 | ## Introduction 25 | The Home Assistant TTS (Text-to-Speech) feature leverages the voice synthesis capabilities of large language models from the [Aliyun BaiLian Platform](https://bailian.console.aliyun.com/). 26 | 27 | Currently supports the following two speech synthesis models: 28 | 1. `CosyVoice` - CosyVoice large model for speech synthesis. For more information, please refer to the [Aliyun Documentation](https://help.aliyun.com/zh/model-studio/developer-reference/cosyvoice-large-model-for-speech-synthesis/) 29 | 2. `Qwen3-TTS` `Qwen-TTS` - Speech synthesis model of the Qwen series, which supports inputting Chinese, English, and mixed Chinese-English text, and streamingly outputs audio. For more information, please refer to the [Aliyun Documentation](https://help.aliyun.com/zh/model-studio/qwen-tts) 30 | 31 | ## Installation 32 | 33 | ### Installation via HACS 34 | 35 | 1. [![Open your Home Assistant instance and open the hass-aliyun_bailian_tts integration inside the Home Assistant Community Store.](https://my.home-assistant.io/badges/hacs_repository.svg)](https://my.home-assistant.io/redirect/hacs_repository/?owner=itning&repository=hass-aliyun_bailian_tts&category=integration) Go to the HACS panel, click the menu button in the top-right corner, and add a custom integration. 36 | 37 | ![](https://raw.githubusercontent.com/itning/hass-aliyun_bailian_tts/refs/heads/main/pic/1.png) 38 | 39 | 2. In the pop-up panel: 40 | 41 | - **Repository**: Enter `https://github.com/itning/hass-aliyun_bailian_tts.git` 42 | - **Type**: Select `Integration` 43 | 44 | ![](https://raw.githubusercontent.com/itning/hass-aliyun_bailian_tts/refs/heads/main/pic/2.png) 45 | 46 | 3. Click the **ADD** button to save. 47 | 48 | 4. In HACS, search for the newly added integration: `Aliyun BaiLian TTS`. Click the **Download** button on the right side of the menu. 49 | 50 | ![](https://raw.githubusercontent.com/itning/hass-aliyun_bailian_tts/refs/heads/main/pic/3.png) 51 | 52 | 5. After the download is complete, restart Home Assistant as prompted. 53 | 54 | 6. After restarting, go to **Settings** > **Devices & Services**, then click the button in the bottom-right corner to add an integration. Search for `Aliyun BaiLian TTS` and add it. 55 | 56 | ![](https://raw.githubusercontent.com/itning/hass-aliyun_bailian_tts/refs/heads/main/pic/4.png) 57 | 58 | 7. Click on the added `Aliyun BaiLian TTS` to enter the configuration page. 59 | 60 | 8. Click the **Configure** button, and enter your BaiLian Platform Token (https://bailian.console.aliyun.com/?tab=model#/api-key). 61 | 62 | Voice list: 63 | - CosyVoice: https://help.aliyun.com/zh/model-studio/developer-reference/timbre-list 64 | - Qwen-TTS: https://help.aliyun.com/zh/model-studio/qwen-tts 65 | 66 | Model list: 67 | - CosyVoice models: `cosyvoice-v1`, etc. 68 | - Qwen-TTS models: `qwen-tts`, `qwen-tts-latest`, `qwen-tts-2025-05-22`, `qwen-tts-2025-04-10` 69 | - Qwen3-TTS models: `qwen3-tts-flash`, `qwen3-tts-flash-2025-09-18` 70 | 71 | After entering the information, click **Submit**. 72 | 73 | ![](https://raw.githubusercontent.com/itning/hass-aliyun_bailian_tts/refs/heads/main/pic/5.png) 74 | 75 | 9. Click **Settings** > **Voice Assistant** > **Modify Assistant**, and change the engine to `Aliyun BaiLian TTS`. 76 | 77 | ![](https://raw.githubusercontent.com/itning/hass-aliyun_bailian_tts/refs/heads/main/pic/6.png) 78 | 79 | 10. You're all set! 80 | 81 | ## Conclusion 82 | 83 | Most of the code for this project was initially generated by the large model [qwen-max-2025-01-25](https://bailian.console.aliyun.com/model-market/detail/qwen-max-2025-01-25#/model-market/detail/qwen-max-2025-01-25), and later by Claude Sonnet 4. 84 | 85 | ## Acknowledgments 86 | 87 | ![JetBrains Logo (Main) logo](https://resources.jetbrains.com/storage/products/company/brand/logos/jb_beam.svg) 88 | -------------------------------------------------------------------------------- /custom_components/aliyun_bailian_tts/tts.py: -------------------------------------------------------------------------------- 1 | import base64 2 | import logging 3 | import struct 4 | import time 5 | 6 | import dashscope 7 | from homeassistant.components.tts import Provider, TtsAudioType 8 | from homeassistant.exceptions import HomeAssistantError 9 | 10 | from .const import CONF_TOKEN, CONF_MODEL, CONF_VOICE, DOMAIN 11 | 12 | _LOGGER = logging.getLogger(__name__) 13 | 14 | 15 | class AliyunBaiLianTTSProvider(Provider): 16 | def __init__(self, hass, config): 17 | self.hass = hass 18 | self.name = "Aliyun Bailian TTS" 19 | 20 | @property 21 | def default_language(self): 22 | return "zh" 23 | 24 | @property 25 | def supported_languages(self): 26 | return ["en", "zh"] 27 | 28 | def _get_current_config(self): 29 | """获取当前的配置,优先使用 options""" 30 | config_entries = self.hass.config_entries.async_entries(DOMAIN) 31 | if config_entries: 32 | config_entry = config_entries[0] 33 | # 优先使用 options,如果没有则使用 data 34 | if config_entry.options: 35 | return config_entry.options 36 | else: 37 | return config_entry.data 38 | 39 | # 回退到旧的存储方式 40 | return self.hass.data.get(DOMAIN, {}) 41 | 42 | @staticmethod 43 | def _process_qwen_tts(model: str, voice: str, message: str) -> bytes: 44 | def create_wav_header(data_size, channels=1, sample_rate=24000, bits_per_sample=16): 45 | """创建WAV文件头""" 46 | riff = b'RIFF' 47 | filesize = 36 + data_size 48 | wave_fmt = b'WAVE' 49 | fmt = b'fmt ' 50 | subchunk1_size = 16 51 | audio_format = 1 # PCM 52 | byte_rate = sample_rate * channels * bits_per_sample // 8 53 | block_align = channels * bits_per_sample // 8 54 | subchunk2_id = b'data' 55 | 56 | header = riff + struct.pack(' bytes: 101 | start_time = time.perf_counter() 102 | synthesizer = dashscope.audio.tts_v2.SpeechSynthesizer(model=model, voice=voice) 103 | audio_data = synthesizer.call(message) 104 | end_time = time.perf_counter() 105 | elapsed_time = (end_time - start_time) * 1000 106 | _LOGGER.info( 107 | '[Metric] requestId: %s, first package delay ms: %s, elapsed_time: %sms', 108 | synthesizer.get_last_request_id(), 109 | synthesizer.get_first_package_delay(), 110 | elapsed_time 111 | ) 112 | return audio_data 113 | 114 | async def async_get_tts_audio(self, message: str, language: str, options=None) -> TtsAudioType: 115 | """Generate TTS audio.""" 116 | try: 117 | # 获取当前配置 118 | config = self._get_current_config() 119 | 120 | dashscope.api_key = config.get(CONF_TOKEN) 121 | if not dashscope.api_key: 122 | raise ValueError("API Key is not set in configuration") 123 | 124 | model = config.get(CONF_MODEL, "qwen3-tts-flash") 125 | voice = config.get(CONF_VOICE, "Cherry") 126 | 127 | if model.startswith("qwen"): 128 | audio_data = await self.hass.async_add_executor_job(self._process_qwen_tts, model, voice, message) 129 | audio_format = "wav" 130 | elif model.startswith("cosyvoice"): 131 | audio_data = await self.hass.async_add_executor_job(self._process_cosyvoice_tts, model, voice, message) 132 | audio_format = "mp3" 133 | else: 134 | _LOGGER.error("not supported model: %s", model) 135 | raise HomeAssistantError("not supported model: " + model) 136 | 137 | if audio_data is None or len(audio_data) == 0: 138 | _LOGGER.error("SpeechSynthesizer returned None or empty audio for message: %s model: %s voice: %s", 139 | message, model, voice) 140 | raise HomeAssistantError("SpeechSynthesizer returned None or empty audio") 141 | 142 | return audio_format, audio_data 143 | 144 | except Exception as e: 145 | _LOGGER.error("Error generating TTS audio: %s", e, exc_info=True) 146 | raise HomeAssistantError(f"Error generating TTS audio: {e}") 147 | 148 | 149 | async def async_get_engine(hass, config, discovery_info=None): 150 | """Set up the Aliyun BaiLian TTS platform.""" 151 | return AliyunBaiLianTTSProvider(hass, config) 152 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | --------------------------------------------------------------------------------