13 Commits
Author SHA1 Message Date
chliny 1a05335b3c feat: add ASR filtering options 2026-07-19 22:50:02 +08:00
chliny 667a735fe4 ci: publish release on version tags 2026-07-19 22:24:59 +08:00
chliny 40b15e6e9a feat: support more ASR engine models 2026-07-19 22:19:24 +08:00
chliny f1cd7fff72 chore: bump version 2025-05-20 11:39:13 +08:00
chliny 17fb066906 doc: update README badge 2025-05-18 21:54:54 +08:00
chliny dc331904f2 fix: update support laguage code 2025-05-18 21:24:57 +08:00
chliny bf4fa5b25c fix: update model translation 2025-05-18 21:19:58 +08:00
chliny 51e2b0a23a feat: support reconfig model 2025-05-17 21:29:26 +08:00
chliny 1af6717403 doc: README typo fix 2025-05-12 10:22:55 +08:00
chliny 4e3959f6a5 doc: udpate README 2025-05-12 10:20:57 +08:00
chliny 2cef7baa1a typo 2025-05-12 10:19:54 +08:00
chliny 1cc7277816 doc: update README 2025-05-12 10:19:16 +08:00
chliny 6cba77700d chore: udpate name 2025-05-12 10:06:03 +08:00
10 changed files with 357 additions and 56 deletions
+18 -11
View File
@@ -1,25 +1,32 @@
name: Release
on:
release:
types: [published]
push:
tags:
- "v*"
permissions:
contents: write
jobs:
release-zip:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Checkout code
uses: actions/checkout@v4
- name: ZIP Component Dir
run: |
cd ${{ github.workspace }}/custom_components/tencentcloud_asr
zip -r tencentcloud_asr.zip ./
zip -r ${{ github.workspace }}/tencentcloud_asr.zip ./
- name: Upload zip to release
uses: svenstaro/upload-release-action@v2
- name: Create Release
uses: softprops/action-gh-release@v2
with:
repo_token: ${{ secrets.GITHUB_TOKEN }}
file: ${{ github.workspace }}/custom_components/tencentcloud_asr/tencentcloud_asr.zip
asset_name: tencentcloud_asr.zip
tag: ${{ github.ref }}
overwrite: true
tag_name: ${{ github.ref_name }}
name: Release ${{ github.ref_name }}
draft: false
prerelease: false
files: tencentcloud_asr.zip
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+47 -5
View File
@@ -1,6 +1,10 @@
# hass-tencentcloud-asr
腾讯云语音识别服务 homeassistantstt集成
腾讯云语音识别服务 homeassistant stt集成
[![GitHub release][releases-shield]][releases]
[![Github downloads][downloads-shield]][releases]
[![HACS][hacs-shield]][hacs]
## 安装
@@ -8,8 +12,46 @@
- HACS -> 集成 -> 右上解选项 -> 自定义存储库
- 存储库: https://github.com/chliny/hass-tencentcloud-asr
- 类别: 集成
- 浏览并下载存储库 -> 搜索 Tencent Cloud ASR 并下载
- 重启Homeassistant
- Homeassistant 添加集成
- 设置 -> 设备与服务 -> 添加集成 -> 搜索 Tencent Cloud ASR -> 填入腾讯云API密钥和Secret
- 浏览并下载存储库 -> 搜索 TencentCloud ASR 并下载
- 重启homeassistant
- homeassistant 添加集成
- 设置 -> 设备与服务 -> 添加集成 -> 搜索 TencentCloud ASR -> 填入腾讯云API密钥和Secret
- 提交后稍等片刻,即可看到`tencentcloud_asr`实体
## 支持模型
| 模型 | 说明 |
|------|------|
|8k_zh|中文电话通用|
|8k_en|英文电话通用|
|16k_zh| 中文通用|
|16k_zh-PY|中英粤|
|16k_zh_medical|中文医疗|
|16k_en|英语|
|16k_yue|粤语|
|16k_ja|日语|
|16k_ko|韩语|
|16k_vi|越南语|
|16k_ms|马来语|
|16k_id|印度尼西亚语|
|16k_fil|菲律宾语|
|16k_th|泰语|
|16k_pt|葡萄牙语|
|16k_tr|土耳其语|
|16k_ar|阿拉伯语|
|16k_es|西班牙语|
|16k_hi|印地语|
|16k_fr|法语|
|16k_de|德语|
|16k_zh_dialect|多方言,支持23种方言(上海话、四川话、武汉话、贵阳话、昆明话、西安话、郑州话、太原话、兰州话、银川话、西宁话、南京话、合肥话、南昌话、长沙话、苏州话、杭州话、济南话、天津话、石家庄话、黑龙江话、吉林话、辽宁话)|
- 更多模型详细说明,请参考[腾讯云文档](https://cloud.tencent.com/document/product/1093/35646)
- 默认模型: `16k_zh`
[releases-shield]: https://img.shields.io/github/v/release/chliny/hass-tencentcloud-asr?style=flat-square
[downloads-shield]: https://img.shields.io/github/downloads/chliny/hass-tencentcloud-asr/total?style=flat-square
[releases]: https://github.com/chliny/hass-tencentcloud-asr/releases
[hacs-shield]: https://img.shields.io/badge/HACS-Default-41BDF5.svg?style=flat-square
[hacs]: https://github.com/hacs/integration
@@ -1,21 +1,40 @@
import logging
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import Platform
from homeassistant.core import HomeAssistant
from .tencentcloud_api import DefaultModel
DOMAIN = "tencentcloud_asr"
PLATFORMS = (Platform.STT,)
_LOGGER = logging.getLogger(__name__)
# const key
SecretIdKey = "secretid"
SecretKeyKey = "secretkey"
ModelKey = "model"
FilterDirtyKey = "filter_dirty"
FilterModalKey = "filter_modal"
FilterPuncKey = "filter_punc"
ConvertNumModeKey = "convert_num_mode"
async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry):
if not config_entry.update_listeners:
config_entry.add_update_listener(async_update_options)
config_data = {**config_entry.data, **config_entry.options}
model = config_data.get(ModelKey, DefaultModel)
hass.data[ModelKey] = model
await hass.config_entries.async_forward_entry_setups(config_entry, PLATFORMS)
return True
async def async_update_options(hass: HomeAssistant, config_entry: ConfigEntry):
config_data = {**config_entry.data, **config_entry.options}
model = config_data.get(ModelKey, DefaultModel)
_LOGGER.debug("update model:%s", model)
hass.data[ModelKey] = model
async def async_unload_entry(hass: HomeAssistant, config_entry: ConfigEntry):
return await hass.config_entries.async_unload_platforms(config_entry, PLATFORMS)
@@ -1,17 +1,52 @@
import voluptuous as vol
from homeassistant.config_entries import ConfigFlow, ConfigFlowResult
from typing import Any, Dict
import logging
from .tencentcloud_api import Modules, DefaultModel
import voluptuous as vol
from typing import Any, Dict
from homeassistant.core import callback
from homeassistant.config_entries import ConfigFlow, ConfigFlowResult, OptionsFlow, ConfigEntry
from homeassistant.helpers.selector import (
BooleanSelector,
SelectOptionDict,
SelectSelector,
SelectSelectorConfig,
)
from .tencentcloud_api import ModuleSupportLanguage, DefaultModel
from .tencentcloud_api import TencentCloudAsrAPi
from . import SecretIdKey, SecretKeyKey, ModelKey
from . import (
ConvertNumModeKey,
FilterDirtyKey,
FilterModalKey,
FilterPuncKey,
ModelKey,
SecretIdKey,
SecretKeyKey,
)
from . import DOMAIN
_LOGGER = logging.getLogger(__name__)
modelSelector = SelectSelector(
SelectSelectorConfig(
options=[
SelectOptionDict(value=model_id, label=model_id) for model_id in ModuleSupportLanguage
],
translation_key="model_descriptions",
)
)
filterDirtySelector = SelectSelector(
SelectSelectorConfig(
options=[
SelectOptionDict(value="0", label="0"),
SelectOptionDict(value="1", label="1"),
SelectOptionDict(value="2", label="2"),
],
translation_key="filter_dirty_descriptions",
)
)
class ConfigFlowHandler(ConfigFlow, domain=DOMAIN):
async def async_step_user(self, user_input: dict[str, Any] | None = None) -> ConfigFlowResult:
errors: Dict[str, str] = {}
@@ -27,14 +62,55 @@ class ConfigFlowHandler(ConfigFlow, domain=DOMAIN):
if not errors:
return self.async_create_entry(title=DOMAIN, data=user_input)
return self.async_show_form(
step_id="user",
data_schema=vol.Schema(
{
vol.Required(SecretIdKey): str,
vol.Required(SecretKeyKey): str,
vol.Optional(ModelKey, default=DefaultModel): vol.In(Modules.keys()),
},
),
errors=errors
)
config_schema = vol.Schema(
{
vol.Required(SecretIdKey): str,
vol.Required(SecretKeyKey): str,
vol.Required(ModelKey, default=DefaultModel): modelSelector,
vol.Required(FilterDirtyKey, default="0"): filterDirtySelector,
vol.Required(FilterModalKey, default=False): BooleanSelector(),
vol.Required(FilterPuncKey, default=False): BooleanSelector(),
vol.Required(ConvertNumModeKey, default=True): BooleanSelector(),
})
return self.async_show_form(step_id="user", data_schema=config_schema, errors=errors)
@staticmethod
@callback
def async_get_options_flow(config_entry: ConfigEntry):
return TencentCloudAsrOptionFlow(config_entry)
class TencentCloudAsrOptionFlow(OptionsFlow):
def __init__(self, config_entry: ConfigEntry):
self._config_entry = config_entry
async def async_step_init(self, user_input=None):
return await self.async_step_user()
async def async_step_user(self, user_input=None):
if not user_input:
user_input = {**self._config_entry.data, **self._config_entry.options}
else:
return self.async_create_entry(title=DOMAIN, data=user_input)
model = user_input.get(ModelKey, DefaultModel)
config_schema = vol.Schema({
vol.Optional(ModelKey, default=model): modelSelector,
vol.Required(
FilterDirtyKey,
default=user_input.get(FilterDirtyKey, "0"),
): filterDirtySelector,
vol.Required(
FilterModalKey,
default=user_input.get(FilterModalKey, False),
): BooleanSelector(),
vol.Required(
FilterPuncKey,
default=user_input.get(FilterPuncKey, False),
): BooleanSelector(),
vol.Required(
ConvertNumModeKey,
default=user_input.get(ConvertNumModeKey, True),
): BooleanSelector(),
})
return self.async_show_form(step_id="user", data_schema=config_schema, errors={})
@@ -16,5 +16,5 @@
"loggers": [
"tencentcloud_asr"
],
"version": "0.0.1"
"version": "0.1.1"
}
+34 -6
View File
@@ -10,7 +10,15 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback
from .tencentcloud_api import TencentCloudAsrAPi
from .tencentcloud_api import ModuleSupportLanguage
from .tencentcloud_api import DefaultModel
from . import SecretIdKey, SecretKeyKey, ModelKey
from . import (
ConvertNumModeKey,
FilterDirtyKey,
FilterModalKey,
FilterPuncKey,
ModelKey,
SecretIdKey,
SecretKeyKey,
)
_LOGGER = logging.getLogger(__name__)
@@ -29,15 +37,15 @@ class ASRSTT(stt.SpeechToTextEntity):
secretKey = config_entry.data.get(SecretKeyKey, "")
self.tencentCloudApi = TencentCloudAsrAPi(secretId, secretKey)
self.model: str = config_entry.data.get(ModelKey, DefaultModel)
# self._attr_name = f"TencentCloud Asr id: ({secretId})"
self._attr_unique_id = f"tencentcloud_asr_stt"
self._attr_name = f"TencentCloud Asr STT"
self.hass = hass
self.config_entry = config_entry
@property
def supported_languages(self) -> list[str]:
return ModuleSupportLanguage.get(self.model, ["zh"])
model: str = self.hass.data.get(ModelKey, DefaultModel)
return ModuleSupportLanguage.get(model, ["zh"])
@property
def supported_formats(self) -> list[stt.AudioFormats]:
@@ -53,6 +61,9 @@ class ASRSTT(stt.SpeechToTextEntity):
@property
def supported_sample_rates(self) -> list[stt.AudioSampleRates]:
model: str = self.hass.data.get(ModelKey, DefaultModel)
if model.startswith("8k_"):
return [stt.AudioSampleRates.SAMPLERATE_8000]
return [stt.AudioSampleRates.SAMPLERATE_16000]
@property
@@ -69,8 +80,25 @@ class ASRSTT(stt.SpeechToTextEntity):
audio += chunk
data = base64.b64encode(audio).decode("utf8", "ignore").strip()
_LOGGER.debug(f"process_audio_stream transcribe: {data}")
ret, result = await self.hass.async_add_executor_job(partial(self.tencentCloudApi.SentenceRecognition, self.model, data))
config = {**self.config_entry.data, **self.config_entry.options}
model: str = config.get(ModelKey, DefaultModel)
_LOGGER.debug(
"process_audio_stream transcribe: audio_bytes=%s model=%s",
len(audio),
model,
)
ret, result = await self.hass.async_add_executor_job(
partial(
self.tencentCloudApi.SentenceRecognition,
model,
data,
len(audio),
config.get(FilterDirtyKey, "0"),
config.get(FilterModalKey, False),
config.get(FilterPuncKey, False),
config.get(ConvertNumModeKey, True),
)
)
if not ret:
return stt.SpeechResult(result, stt.SpeechResultState.ERROR)
if not result:
@@ -10,18 +10,28 @@ _LOGGER = logging.getLogger(__name__)
SentenceRecognitionSourceTypePost = 1
SentenceRecognitionSourceTypeURL = 0
Modules = {
"16k_zh": "中文",
"16k_zh-PY": "中英粤",
"16k_en": "英语",
"16k_yue": "粤语",
"16k_zh_dialect": "多方言",
}
ModuleSupportLanguage = {
"16k_zh": ["zh"],
"16k_zh-PY": ["zh", "en", "yue"],
"8k_zh": ["zh-cn"],
"8k_en": ["en"],
"16k_zh": ["zh-cn"],
"16k_zh-PY": ["zh-cn", "en", "zh-hk"],
"16k_zh_medical": ["zh-cn"],
"16k_en": ["en"],
"16k_yue": ["yue"],
"16k_yue": ["zh-hk"],
"16k_ja": ["ja"],
"16k_ko": ["ko"],
"16k_vi": ["vi"],
"16k_ms": ["ms"],
"16k_id": ["id"],
"16k_fil": ["fil"],
"16k_th": ["th"],
"16k_pt": ["pt"],
"16k_tr": ["tr"],
"16k_ar": ["ar"],
"16k_es": ["es"],
"16k_hi": ["hi"],
"16k_fr": ["fr"],
"16k_de": ["de"],
"16k_zh_dialect": ["zh"],
}
DefaultModel = "16k_zh"
@@ -33,7 +43,16 @@ class TencentCloudAsrAPi(object):
region = ""
self.asrClient = AsrClient(cred, region)
def SentenceRecognition(self, engSerViceType, data: str) -> (tuple[bool, str | None]):
def SentenceRecognition(
self,
engSerViceType,
data: str,
data_len: int,
filter_dirty: str,
filter_modal: bool,
filter_punc: bool,
convert_num_mode: bool,
) -> tuple[bool, str | None]:
req = SentenceRecognitionRequest()
req.EngSerViceType = engSerViceType
req.SourceType = SentenceRecognitionSourceTypePost
@@ -42,7 +61,11 @@ class TencentCloudAsrAPi(object):
req.SubServiceType = 2
req.UsrAudioKey = ""
req.Data = data
req.DataLen = len(data)
req.DataLen = data_len
req.FilterDirty = int(filter_dirty)
req.FilterModal = int(filter_modal)
req.FilterPunc = int(filter_punc)
req.ConvertNumMode = int(convert_num_mode)
try:
res: SentenceRecognitionResponse = self.asrClient.SentenceRecognition(req)
return True, res.Result
@@ -7,9 +7,62 @@
"data": {
"secretid": "tencentcloud asr secretid",
"secretkey": "tencnetcloud asr secretkey",
"model": "model"
"model": "model",
"filter_dirty": "Dirty word handling",
"filter_modal": "Filter filler words such as um and uh",
"filter_punc": "Filter sentence-ending punctuation",
"convert_num_mode": "Smart Arabic numeral conversion"
},
"description": "The integration is based on [Tencent Cloud API](https://cloud.tencent.com/document/product/1093/35646)."
}
}
},
"options": {
"step": {
"user": {
"data": {
"model": "model",
"filter_dirty": "Dirty word handling",
"filter_modal": "Filter filler words such as um and uh",
"filter_punc": "Filter sentence-ending punctuation",
"convert_num_mode": "Smart Arabic numeral conversion"
}
}
}
},
"selector": {
"filter_dirty_descriptions": {
"options": {
"0": "Do not filter dirty words",
"1": "Filter dirty words",
"2": "Replace dirty words with *"
}
},
"model_descriptions": {
"options": {
"8k_zh": "8k_zh - Chinese telephone audio",
"8k_en": "8k_en - English telephone audio",
"16k_zh": "16k_zh - Chinese",
"16k_zh-PY": "16k_zh-PY - Chinese, English, and Cantonese",
"16k_zh_medical": "16k_zh_medical - Medical Chinese",
"16k_en": "16k_en - English",
"16k_yue": "16k_yue - Cantonese",
"16k_ja": "16k_ja - Japanese",
"16k_ko": "16k_ko - Korean",
"16k_vi": "16k_vi - Vietnamese",
"16k_ms": "16k_ms - Malay",
"16k_id": "16k_id - Indonesian",
"16k_fil": "16k_fil - Filipino",
"16k_th": "16k_th - Thai",
"16k_pt": "16k_pt - Portuguese",
"16k_tr": "16k_tr - Turkish",
"16k_ar": "16k_ar - Arabic",
"16k_es": "16k_es - Spanish",
"16k_hi": "16k_hi - Hindi",
"16k_fr": "16k_fr - French",
"16k_de": "16k_de - German",
"16k_zh_dialect": "16k_zh_dialect - Chinese dialects"
}
}
}
}
@@ -7,9 +7,62 @@
"data": {
"secretid": "请输入腾讯云ASR服务secretid",
"secretkey": "请输入腾讯云ASR服务secretkey",
"model": "模型"
"model": "模型",
"filter_dirty": "脏词处理方式",
"filter_modal": "过滤“嗯、啊”等语气词",
"filter_punc": "过滤句末标点",
"convert_num_mode": "阿拉伯数字智能转换"
},
"description": "此集成基于腾讯云API, 具体见[腾讯云文档](https://cloud.tencent.com/document/product/1093/35646)"
}
}
},
"options": {
"step": {
"user": {
"data": {
"model": "模型",
"filter_dirty": "脏词处理方式",
"filter_modal": "过滤“嗯、啊”等语气词",
"filter_punc": "过滤句末标点",
"convert_num_mode": "阿拉伯数字智能转换"
}
}
}
},
"selector": {
"filter_dirty_descriptions": {
"options": {
"0": "不过滤脏词",
"1": "过滤脏词",
"2": "将脏词替换为 *"
}
},
"model_descriptions": {
"options": {
"8k_zh": "8k_zh - 中文电话通用",
"8k_en": "8k_en - 英文电话通用",
"16k_zh": "16k_zh - 中文通用",
"16k_zh-PY": "16k_zh-PY - 中英粤",
"16k_zh_medical": "16k_zh_medical - 中文医疗",
"16k_en": "16k_en - 英语",
"16k_yue": "16k_yue - 粤语",
"16k_ja": "16k_ja - 日语",
"16k_ko": "16k_ko - 韩语",
"16k_vi": "16k_vi - 越南语",
"16k_ms": "16k_ms - 马来语",
"16k_id": "16k_id - 印度尼西亚语",
"16k_fil": "16k_fil - 菲律宾语",
"16k_th": "16k_th - 泰语",
"16k_pt": "16k_pt - 葡萄牙语",
"16k_tr": "16k_tr - 土耳其语",
"16k_ar": "16k_ar - 阿拉伯语",
"16k_es": "16k_es - 西班牙语",
"16k_hi": "16k_hi - 印地语",
"16k_fr": "16k_fr - 法语",
"16k_de": "16k_de - 德语",
"16k_zh_dialect": "16k_zh_dialect - 中文多方言"
}
}
}
}
+1 -1
View File
@@ -1,5 +1,5 @@
{
"name": "TencentCloud Asr",
"name": "TencentCloud ASR",
"domains": [
"tencentcloud_asr"
],