├── hacs.json ├── custom_components └── rhvoice │ ├── __init__.py │ ├── manifest.json │ ├── const.py │ └── tts.py ├── .markdownlint.json ├── .github └── workflows │ ├── black.yml │ ├── hassfest.yaml │ └── hacs.yaml ├── LICENSE ├── .gitignore └── README.md /hacs.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "RHVoice", 3 | "render_readme": true 4 | } 5 | -------------------------------------------------------------------------------- /custom_components/rhvoice/__init__.py: -------------------------------------------------------------------------------- 1 | """Support for RHVoice integration.""" 2 | -------------------------------------------------------------------------------- /.markdownlint.json: -------------------------------------------------------------------------------- 1 | { 2 | "default": true, 3 | "MD013": false, 4 | "MD033": { 5 | "allowed_elements": [ 6 | "a" 7 | ] 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /.github/workflows/black.yml: -------------------------------------------------------------------------------- 1 | name: Lint 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | lint: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: actions/checkout@v2 10 | - uses: actions/setup-python@v2 11 | - uses: psf/black@stable 12 | -------------------------------------------------------------------------------- /.github/workflows/hassfest.yaml: -------------------------------------------------------------------------------- 1 | name: Validate with hassfest 2 | 3 | on: 4 | push: 5 | pull_request: 6 | schedule: 7 | - cron: "0 0 * * *" 8 | 9 | jobs: 10 | validate: 11 | runs-on: "ubuntu-latest" 12 | steps: 13 | - uses: "actions/checkout@v2" 14 | - uses: home-assistant/actions/hassfest@master 15 | -------------------------------------------------------------------------------- /custom_components/rhvoice/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "domain": "rhvoice", 3 | "name": "RHVoice", 4 | "codeowners": ["@definitio"], 5 | "documentation": "https://github.com/definitio/ha-rhvoice/blob/master/README.md", 6 | "iot_class": "local_push", 7 | "issue_tracker": "https://github.com/definitio/ha-rhvoice/issues", 8 | "version": "1.3.3" 9 | } 10 | -------------------------------------------------------------------------------- /.github/workflows/hacs.yaml: -------------------------------------------------------------------------------- 1 | name: HACS Action 2 | 3 | on: 4 | push: 5 | pull_request: 6 | schedule: 7 | - cron: "0 0 * * *" 8 | 9 | jobs: 10 | hacs: 11 | name: HACS Action 12 | runs-on: "ubuntu-latest" 13 | steps: 14 | - name: HACS Action 15 | uses: "hacs/action@main" 16 | with: 17 | category: "integration" 18 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 definitio 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /custom_components/rhvoice/const.py: -------------------------------------------------------------------------------- 1 | """Constants for the RHVoice tts service.""" 2 | 3 | CONF_FORMAT = "format" 4 | CONF_PITCH = "pitch" 5 | CONF_RATE = "rate" 6 | CONF_VOICE = "voice" 7 | CONF_VOLUME = "volume" 8 | 9 | SUPPORTED_FORMATS = ["flac", "mp3", "opus", "wav"] 10 | SUPPORTED_OPTIONS = [CONF_FORMAT, CONF_PITCH, CONF_RATE, CONF_VOICE, CONF_VOLUME] 11 | SUPPORTED_LANGUAGES = { 12 | "cs-CZ": ("zdenek",), 13 | "en-US": ( 14 | "alan", 15 | "bdl", 16 | "clb", 17 | "evgeniy-eng", 18 | "lyubov", 19 | "slt", 20 | ), 21 | "eo": ("spomenka",), 22 | "ka-GE": ("natia",), 23 | "ky-KG": ("azamat", "nazgul"), 24 | "mk": ( 25 | "kiko", 26 | "suze", 27 | ), 28 | "pl-PL": ( 29 | "alicja", 30 | "cezary", 31 | "magda", 32 | "michal", 33 | "natan", 34 | ), 35 | "pt-BR": ("letícia-f123",), 36 | "ru-RU": ( 37 | "aleksandr", 38 | "aleksandr-hq", 39 | "anna", 40 | "arina", 41 | "artemiy", 42 | "elena", 43 | "evgeniy-rus", 44 | "irina", 45 | "mikhail", 46 | "pavel", 47 | "tatiana", 48 | "timofey", 49 | "umka", 50 | "victoria", 51 | "vitaliy", 52 | "vitaliy-ng", 53 | "vsevolod", 54 | "yuriy", 55 | ), 56 | "sk-SK": ("ondro",), 57 | "sq-AL": ("hana",), 58 | "tt-RU": ("talgat",), 59 | "uk-UA": ( 60 | "anatol", 61 | "marianna", 62 | "natalia", 63 | "volodymyr", 64 | ), 65 | } 66 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | pip-wheel-metadata/ 24 | share/python-wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .nox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *.cover 50 | *.py,cover 51 | .hypothesis/ 52 | .pytest_cache/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | target/ 76 | 77 | # Jupyter Notebook 78 | .ipynb_checkpoints 79 | 80 | # IPython 81 | profile_default/ 82 | ipython_config.py 83 | 84 | # pyenv 85 | .python-version 86 | 87 | # pipenv 88 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 89 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 90 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 91 | # install all needed dependencies. 92 | #Pipfile.lock 93 | 94 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 95 | __pypackages__/ 96 | 97 | # Celery stuff 98 | celerybeat-schedule 99 | celerybeat.pid 100 | 101 | # SageMath parsed files 102 | *.sage.py 103 | 104 | # Environments 105 | .env 106 | .venv 107 | env/ 108 | venv/ 109 | ENV/ 110 | env.bak/ 111 | venv.bak/ 112 | 113 | # Spyder project settings 114 | .spyderproject 115 | .spyproject 116 | 117 | # Rope project settings 118 | .ropeproject 119 | 120 | # mkdocs documentation 121 | /site 122 | 123 | # mypy 124 | .mypy_cache/ 125 | .dmypy.json 126 | dmypy.json 127 | 128 | # Pyre type checker 129 | .pyre/ 130 | 131 | # Directory configuration 132 | .directory 133 | -------------------------------------------------------------------------------- /custom_components/rhvoice/tts.py: -------------------------------------------------------------------------------- 1 | """Support for the RHVoice tts service.""" 2 | 3 | import logging 4 | from asyncio import TimeoutError as aioTimeoutError 5 | from http import HTTPStatus 6 | from itertools import chain 7 | 8 | import async_timeout 9 | import voluptuous as vol 10 | from aiohttp import ClientError 11 | from homeassistant.components.tts import PLATFORM_SCHEMA, Provider, Voice, callback 12 | from homeassistant.const import ( 13 | CONF_HOST, 14 | CONF_PORT, 15 | CONF_SSL, 16 | CONF_TIMEOUT, 17 | CONF_VERIFY_SSL, 18 | ) 19 | from homeassistant.helpers import config_validation as cv 20 | from homeassistant.helpers.aiohttp_client import async_get_clientsession 21 | 22 | from .const import ( 23 | CONF_FORMAT, 24 | CONF_PITCH, 25 | CONF_RATE, 26 | CONF_VOICE, 27 | CONF_VOLUME, 28 | SUPPORTED_FORMATS, 29 | SUPPORTED_OPTIONS, 30 | SUPPORTED_LANGUAGES, 31 | ) 32 | 33 | _LOGGER = logging.getLogger(__name__) 34 | 35 | DEFAULT_PORT = 8080 36 | 37 | DEFAULT_FORMAT = "mp3" 38 | DEFAULT_PITCH = 50 39 | DEFAULT_RATE = 50 40 | DEFAULT_VOICE = "anna" 41 | DEFAULT_VOLUME = 50 42 | 43 | FORMAT_SCHEMA = vol.All(cv.string, vol.In(SUPPORTED_FORMATS)) 44 | PITCH_SCHEMA = vol.All(vol.Coerce(int), vol.Range(0, 100)) 45 | RATE_SCHEMA = vol.All(vol.Coerce(int), vol.Range(0, 100)) 46 | VOICE_SCHEMA = vol.All(cv.string, vol.In(list(chain(*SUPPORTED_LANGUAGES.values())))) 47 | VOLUME_SCHEMA = vol.All(vol.Coerce(int), vol.Range(0, 100)) 48 | 49 | PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( 50 | { 51 | vol.Required(CONF_HOST): cv.string, 52 | vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port, 53 | vol.Optional(CONF_FORMAT, default=DEFAULT_FORMAT): FORMAT_SCHEMA, 54 | vol.Optional(CONF_PITCH, default=DEFAULT_PITCH): PITCH_SCHEMA, 55 | vol.Optional(CONF_RATE, default=DEFAULT_RATE): RATE_SCHEMA, 56 | vol.Optional(CONF_SSL, default=False): cv.boolean, 57 | vol.Optional(CONF_VERIFY_SSL, default=True): cv.boolean, 58 | vol.Optional(CONF_VOICE, default=DEFAULT_VOICE): VOICE_SCHEMA, 59 | vol.Optional(CONF_VOLUME, default=DEFAULT_VOLUME): VOLUME_SCHEMA, 60 | } 61 | ) 62 | 63 | 64 | async def async_get_engine(hass, config, discovery_info=None): 65 | """Set up RHVoice speech component.""" 66 | return RHVoiceProvider(hass, config) 67 | 68 | 69 | class RHVoiceProvider(Provider): 70 | """RHVoice speech api provider.""" 71 | 72 | def __init__(self, hass, conf): 73 | """Init RHVoice TTS service.""" 74 | self.name = "RHVoice" 75 | self.hass = hass 76 | host, port, ssl = conf.get(CONF_HOST), conf.get(CONF_PORT), conf.get(CONF_SSL) 77 | self._url = f"http{'s' if ssl else ''}://{host}:{port}/say" 78 | self._verify_ssl = conf.get(CONF_VERIFY_SSL) 79 | 80 | self._encoding = conf.get(CONF_FORMAT) 81 | self._pitch = conf.get(CONF_PITCH) 82 | self._rate = conf.get(CONF_RATE) 83 | self._voice = conf.get(CONF_VOICE) 84 | self._volume = conf.get(CONF_VOLUME) 85 | self._timeout = conf.get(CONF_TIMEOUT) 86 | 87 | for language, voices in SUPPORTED_LANGUAGES.items(): 88 | if self._voice in voices: 89 | self._language = language 90 | break 91 | 92 | @property 93 | def default_language(self): 94 | """Return the default language.""" 95 | return self._language 96 | 97 | @property 98 | def supported_languages(self): 99 | """Return list of supported languages.""" 100 | return list(SUPPORTED_LANGUAGES.keys()) 101 | 102 | @property 103 | def supported_options(self): 104 | """Return list of supported options.""" 105 | return SUPPORTED_OPTIONS 106 | 107 | @callback 108 | def async_get_supported_voices(self, language: str) -> list[Voice] | None: 109 | """Return a list of supported voices for a language.""" 110 | return [Voice(voice, voice) for voice in SUPPORTED_LANGUAGES[language]] 111 | 112 | async def async_get_tts_audio(self, message, language, options=None): 113 | """Load TTS from RHVoice.""" 114 | websession = async_get_clientsession(self.hass) 115 | options_schema = vol.Schema( 116 | { 117 | vol.Optional(CONF_FORMAT, default=self._encoding): FORMAT_SCHEMA, 118 | vol.Optional(CONF_PITCH, default=self._pitch): PITCH_SCHEMA, 119 | vol.Optional(CONF_RATE, default=self._rate): RATE_SCHEMA, 120 | vol.Optional(CONF_VOICE, default=self._voice): VOICE_SCHEMA, 121 | vol.Optional(CONF_VOLUME, default=self._volume): VOLUME_SCHEMA, 122 | } 123 | ) 124 | options = options_schema(options or {}) 125 | options.update({"text": message}) 126 | 127 | if not message: 128 | _LOGGER.warning("The message text can't be empty") 129 | message = " " 130 | 131 | try: 132 | with async_timeout.timeout(self._timeout): 133 | request = await websession.get( 134 | self._url, params=options, verify_ssl=self._verify_ssl 135 | ) 136 | 137 | if request.status != HTTPStatus.OK: 138 | _LOGGER.error( 139 | "Error %d on load URL %s", request.status, request.url 140 | ) 141 | return None, None 142 | data = await request.read() 143 | 144 | except (aioTimeoutError, ClientError): 145 | _LOGGER.error("Timeout for RHVoice API") 146 | return None, None 147 | 148 | return self._encoding, data 149 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # RHVoice component for Home Assistant 2 | 3 | [![hacs_badge](https://img.shields.io/badge/HACS-Default-orange.svg)](https://github.com/custom-components/hacs) 4 | [![Validate with hassfest](https://github.com/definitio/ha-rhvoice/actions/workflows/hassfest.yaml/badge.svg)](https://github.com/definitio/ha-rhvoice/actions/workflows/hassfest.yaml) 5 | [![Validate with HACS action](https://github.com/definitio/ha-rhvoice/actions/workflows/hacs.yaml/badge.svg)](https://github.com/definitio/ha-rhvoice/actions/workflows/hacs.yaml) 6 | 7 | The `rhvoice` integration uses [RHVoice](https://github.com/RHVoice/RHVoice) Text-to-Speech (TTS) engine to read a text with natural sounding voices. 8 | 9 | ## Installation 10 | 11 | 1. Run [rhvoice-rest](https://github.com/Aculeasis/rhvoice-rest) Docker container (choose your CPU architecture): 12 | 13 | aarch64: `docker run -d -p 8080:8080 ghcr.io/aculeasis/rhvoice-rest:arm64v8` 14 | 15 | armv7l: `docker run -d -p 8080:8080 ghcr.io/aculeasis/rhvoice-rest:arm32v7` 16 | 17 | x86_64: `docker run -d -p 8080:8080 ghcr.io/aculeasis/rhvoice-rest:amd64` 18 | 19 | Or use [RHVoice add-on](https://github.com/definitio/ha-rhvoice-addon). Only `aarch64` and `x64` are supported. 20 | 21 | 2. Install the integration to Home Assistant: use [HACS](https://hacs.xyz/) or copy the contents of `custom_components/rhvoice/` to `/custom_components/rhvoice/`. 22 | 3. Configure in the Home Assistant `configuration.yaml` (See the [Configuration](#configuration) and [Configuration Options](#configuration-options) sections below) 23 | 4. Restart Home Assistant. 24 | 25 | ## Configuration 26 | 27 | To enable text-to-speech with RHVoice, add at a minimum the following lines to your Home Assistant's `configuration.yaml` file: 28 | 29 | ```yaml 30 | tts: 31 | - platform: rhvoice 32 | host: 33 | port: 8080 34 | ``` 35 | 36 | Full configuration example: 37 | 38 | ```yaml 39 | tts: 40 | - platform: rhvoice 41 | host: 42 | port: 8080 43 | format: 'mp3' 44 | pitch: 50 45 | rate: 50 46 | voice: 'anna' 47 | volume: 50 48 | ``` 49 | 50 | ## Configuration Options 51 | 52 | - **host:** *(string) (Required)* 53 | 54 | This is the hostname, domain name or IP address that the `rhvoice-rest` container can be reached at. If you use domain name that is reachable on the Internet for Home Assistant, enter that here. 55 | Use `localhost` for [RHVoice add-on](https://github.com/definitio/ha-rhvoice-addon). 56 | 57 | - **port:** *(string) (Optional)* 58 | 59 | This is the port that the rhvoice-rest container can be reached at. 60 | 61 | *Default value: `8080`* 62 | 63 | - **ssl:** *(boolean) (Optional)* 64 | 65 | Use HTTPS instead of HTTP to connect. 66 | 67 | *Default value: `false`* 68 | 69 | - **verify_ssl:** *(boolean) (Optional)* 70 | 71 | Enable or disable SSL certificate verification. Set to false if you have a self-signed SSL certificate and haven't installed the CA certificate to enable verification. 72 | 73 | *Default value: `true`* 74 | 75 | - **format:** *(string) (Optional)* 76 | 77 | This is the file format used for the TTS files created. 78 | 79 | *Default value: `mp3`* 80 | 81 | *Allowed values: `wav|mp3|opus|flac`* 82 | 83 | - **pitch:** *(string) (Optional)* 84 | 85 | This adjust the sound frequency of the TTS voice, lower or higher. 86 | 87 | *Default value: `50`* 88 | 89 | *Allowed values: `0 to 100`* 90 | 91 | - **rate:** *(string) (Optional)* 92 | 93 | This adjust the talking speed of the TTS voice, slower or faster. 94 | 95 | *Default value: `50`* 96 | 97 | *Allowed values: `0 to 100`* 98 | 99 | - **voice:** *(string) (Optional)* 100 | 101 | This is the voice that is used to create the TTS files. Voices are connected with a language. For best results select a voice for the text language you will use. 102 | 103 | *Default value: `anna` (russian)* 104 | 105 | *Allowed values:* 106 | 107 | | Language | Voices | 108 | | -------------------- | ---------------------------------------------------------------------------------------------------------- | 109 | | Albanian | `hana` | 110 | | American English | `alan`, `bdl`, `clb`, `evgeniy-eng`, `lyubov`, `slt` | 111 | | Czech | `zdenek` | 112 | | Esperanto | `spomenka` | 113 | | Georgian | `natia` | 114 | | Kyrgyz | `azamat`, `nazgul` | 115 | | Macedonian | `kiko`, `suze` | 116 | | Polish | `alicja`, `cezary`, `magda`, `michal`, `natan` | 117 | | Brazilian Portuguese | `letícia-f123` | 118 | | Russian | `aleksandr`, `aleksandr-hq`, `anna`, `arina`, `artemiy`, `elena`,
`evgeniy-rus`, `irina`, `mikhail`, `pavel`, `tatiana`, `timofey`,
`umka`, `victoria`, `vitaliy`, `vitaliy-ng`, `vsevolod`, `yuriy` | 119 | | Slovak | `ondro` | 120 | | Tatar | `talgat` | 121 | | Ukrainian | `anatol`, `marianna`, `natalia`, `volodymyr` | 122 | | Uzbek | `sevinch` | 123 | 124 | - **volume:** *(string) (Optional)* 125 | 126 | This adjusts the volume of the voice in TTS files created, softer or louder. 127 | 128 | *Default value: `50`* 129 | 130 | *Allowed values: `0 to 100`* 131 | 132 | ## Tips 133 | 134 | ### You can override configuration with service options 135 | 136 | ```yaml 137 | service: tts.rhvoice_say 138 | data: 139 | entity_id: media_player.main 140 | message: The cake is a lie 141 | options: 142 | format: mp3 143 | pitch: 32 144 | rate: 64 145 | voice: slt 146 | volume: 16 147 | ``` 148 | 149 | ## Other 150 | 151 | You can buy me a coffee via Bitcoin donation: `bc1qd6khey9xkss6vgd6fqpqdyq4lehtepajkcf256` 152 | --------------------------------------------------------------------------------