From c541cb5cbaf1ddb59e977436ffdc9e8a3a492364 Mon Sep 17 00:00:00 2001 From: Jakob Schlyter Date: Thu, 22 Aug 2024 15:36:11 +0200 Subject: [PATCH] Automatically generate Amazon Polly list of voices and regions (#119198) * Automatically generate list of voices and regions. Requires AWS credentials. * add missing commit * replace pydantic with dataclass * dictionary values are strings or list of strings * also generated set of supported engines * use sets for amazon polly parameters * move default for readability --- .../components/amazon_polly/const.py | 121 ++-------------- homeassistant/components/amazon_polly/tts.py | 8 +- homeassistant/generated/amazon_polly.py | 137 ++++++++++++++++++ script/amazon_polly.py | 70 +++++++++ 4 files changed, 221 insertions(+), 115 deletions(-) create mode 100644 homeassistant/generated/amazon_polly.py create mode 100644 script/amazon_polly.py diff --git a/homeassistant/components/amazon_polly/const.py b/homeassistant/components/amazon_polly/const.py index bb196544fc3..40b1bba3ddd 100644 --- a/homeassistant/components/amazon_polly/const.py +++ b/homeassistant/components/amazon_polly/const.py @@ -8,128 +8,23 @@ CONF_REGION: Final = "region_name" CONF_ACCESS_KEY_ID: Final = "aws_access_key_id" CONF_SECRET_ACCESS_KEY: Final = "aws_secret_access_key" -DEFAULT_REGION: Final = "us-east-1" -SUPPORTED_REGIONS: Final[list[str]] = [ - "us-east-1", - "us-east-2", - "us-west-1", - "us-west-2", - "ca-central-1", - "eu-west-1", - "eu-central-1", - "eu-west-2", - "eu-west-3", - "ap-southeast-1", - "ap-southeast-2", - "ap-northeast-2", - "ap-northeast-1", - "ap-south-1", - "sa-east-1", -] - CONF_ENGINE: Final = "engine" CONF_VOICE: Final = "voice" CONF_OUTPUT_FORMAT: Final = "output_format" CONF_SAMPLE_RATE: Final = "sample_rate" CONF_TEXT_TYPE: Final = "text_type" -SUPPORTED_VOICES: Final[list[str]] = [ - "Aditi", # Hindi - "Amy", # English (British) - "Aria", # English (New Zealand), Neural - "Arlet", # Catalan, Neural - "Arthur", # English, Neural - "Astrid", # Swedish - "Ayanda", # English (South African), Neural - "Bianca", # Italian - "Brian", # English (British) - "Camila", # Portuguese, Brazilian - "Carla", # Italian - "Carmen", # Romanian - "Celine", # French - "Chantal", # French Canadian - "Conchita", # Spanish (European) - "Cristiano", # Portuguese (European) - "Daniel", # German, Neural - "Dora", # Icelandic - "Elin", # Swedish, Neural - "Emma", # English - "Enrique", # Spanish (European) - "Ewa", # Polish - "Filiz", # Turkish - "Gabrielle", # French (Canadian) - "Geraint", # English Welsh - "Giorgio", # Italian - "Gwyneth", # Welsh - "Hala", # Arabic (Gulf), Neural - "Hannah", # German (Austrian), Neural - "Hans", # German - "Hiujin", # Chinese (Cantonese), Neural - "Ida", # Norwegian, Neural - "Ines", # Portuguese, European # codespell:ignore ines - "Ivy", # English - "Jacek", # Polish - "Jan", # Polish - "Joanna", # English - "Joey", # English - "Justin", # English - "Kajal", # English (Indian)/Hindi (Bilingual ), Neural - "Karl", # Icelandic - "Kendra", # English - "Kevin", # English, Neural - "Kimberly", # English - "Laura", # Dutch, Neural - "Lea", # French - "Liam", # Canadian French, Neural - "Liv", # Norwegian - "Lotte", # Dutch - "Lucia", # Spanish European - "Lupe", # Spanish US - "Mads", # Danish - "Maja", # Polish - "Marlene", # German - "Mathieu", # French - "Matthew", # English - "Maxim", # Russian - "Mia", # Spanish Mexican - "Miguel", # Spanish US - "Mizuki", # Japanese - "Naja", # Danish - "Nicole", # English Australian - "Ola", # Polish, Neural - "Olivia", # Female, Australian, Neural - "Penelope", # Spanish US - "Pedro", # Spanish US, Neural - "Raveena", # English, Indian - "Ricardo", # Portuguese (Brazilian) - "Ruben", # Dutch - "Russell", # English (Australian) - "Ruth", # English, Neural - "Salli", # English - "Seoyeon", # Korean - "Stephen", # English, Neural - "Suvi", # Finnish - "Takumi", # Japanese - "Tatyana", # Russian - "Vicki", # German - "Vitoria", # Portuguese, Brazilian - "Zeina", # Arabic - "Zhiyu", # Chinese -] +SUPPORTED_OUTPUT_FORMATS: Final[set[str]] = {"mp3", "ogg_vorbis", "pcm"} -SUPPORTED_OUTPUT_FORMATS: Final[list[str]] = ["mp3", "ogg_vorbis", "pcm"] +SUPPORTED_SAMPLE_RATES: Final[set[str]] = {"8000", "16000", "22050", "24000"} -SUPPORTED_ENGINES: Final[list[str]] = ["neural", "standard"] - -SUPPORTED_SAMPLE_RATES: Final[list[str]] = ["8000", "16000", "22050", "24000"] - -SUPPORTED_SAMPLE_RATES_MAP: Final[dict[str, list[str]]] = { - "mp3": ["8000", "16000", "22050", "24000"], - "ogg_vorbis": ["8000", "16000", "22050"], - "pcm": ["8000", "16000"], +SUPPORTED_SAMPLE_RATES_MAP: Final[dict[str, set[str]]] = { + "mp3": {"8000", "16000", "22050", "24000"}, + "ogg_vorbis": {"8000", "16000", "22050"}, + "pcm": {"8000", "16000"}, } -SUPPORTED_TEXT_TYPES: Final[list[str]] = ["text", "ssml"] +SUPPORTED_TEXT_TYPES: Final[set[str]] = {"text", "ssml"} CONTENT_TYPE_EXTENSIONS: Final[dict[str, str]] = { "audio/mpeg": "mp3", @@ -137,6 +32,8 @@ CONTENT_TYPE_EXTENSIONS: Final[dict[str, str]] = { "audio/pcm": "pcm", } +DEFAULT_REGION: Final = "us-east-1" + DEFAULT_ENGINE: Final = "standard" DEFAULT_VOICE: Final = "Joanna" DEFAULT_OUTPUT_FORMAT: Final = "mp3" diff --git a/homeassistant/components/amazon_polly/tts.py b/homeassistant/components/amazon_polly/tts.py index d5cb7092fe3..1fc972fa3a1 100644 --- a/homeassistant/components/amazon_polly/tts.py +++ b/homeassistant/components/amazon_polly/tts.py @@ -16,6 +16,11 @@ from homeassistant.components.tts import ( ) from homeassistant.const import ATTR_CREDENTIALS, CONF_PROFILE_NAME from homeassistant.core import HomeAssistant +from homeassistant.generated.amazon_polly import ( + SUPPORTED_ENGINES, + SUPPORTED_REGIONS, + SUPPORTED_VOICES, +) import homeassistant.helpers.config_validation as cv from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType @@ -38,13 +43,10 @@ from .const import ( DEFAULT_SAMPLE_RATES, DEFAULT_TEXT_TYPE, DEFAULT_VOICE, - SUPPORTED_ENGINES, SUPPORTED_OUTPUT_FORMATS, - SUPPORTED_REGIONS, SUPPORTED_SAMPLE_RATES, SUPPORTED_SAMPLE_RATES_MAP, SUPPORTED_TEXT_TYPES, - SUPPORTED_VOICES, ) _LOGGER: Final = logging.getLogger(__name__) diff --git a/homeassistant/generated/amazon_polly.py b/homeassistant/generated/amazon_polly.py new file mode 100644 index 00000000000..1d870bf6c92 --- /dev/null +++ b/homeassistant/generated/amazon_polly.py @@ -0,0 +1,137 @@ +"""Automatically generated file. + +To update, run python3 -m script.amazon_polly +""" + +from __future__ import annotations + +from typing import Final + +SUPPORTED_ENGINES: Final[set[str]] = { + "generative", + "long-form", + "neural", + "standard", +} + +SUPPORTED_REGIONS: Final[set[str]] = { + "af-south-1", + "ap-east-1", + "ap-northeast-1", + "ap-northeast-2", + "ap-northeast-3", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "ca-central-1", + "eu-central-1", + "eu-north-1", + "eu-west-1", + "eu-west-2", + "eu-west-3", + "me-south-1", + "sa-east-1", + "us-east-1", + "us-east-2", + "us-west-1", + "us-west-2", +} + +SUPPORTED_VOICES: Final[set[str]] = { + "Aditi", + "Adriano", + "Amy", + "Andres", + "Aria", + "Arlet", + "Arthur", + "Astrid", + "Ayanda", + "Bianca", + "Brian", + "Burcu", + "Camila", + "Carla", + "Carmen", + "Celine", + "Chantal", + "Conchita", + "Cristiano", + "Daniel", + "Danielle", + "Dora", + "Elin", + "Emma", + "Enrique", + "Ewa", + "Filiz", + "Gabrielle", + "Geraint", + "Giorgio", + "Gregory", + "Gwyneth", + "Hala", + "Hannah", + "Hans", + "Hiujin", + "Ida", + "Ines", + "Isabelle", + "Ivy", + "Jacek", + "Jan", + "Joanna", + "Joey", + "Justin", + "Kajal", + "Karl", + "Kazuha", + "Kendra", + "Kevin", + "Kimberly", + "Laura", + "Lea", + "Liam", + "Lisa", + "Liv", + "Lotte", + "Lucia", + "Lupe", + "Mads", + "Maja", + "Marlene", + "Mathieu", + "Matthew", + "Maxim", + "Mia", + "Miguel", + "Mizuki", + "Naja", + "Niamh", + "Nicole", + "Ola", + "Olivia", + "Pedro", + "Penelope", + "Raveena", + "Remi", + "Ricardo", + "Ruben", + "Russell", + "Ruth", + "Salli", + "Seoyeon", + "Sergio", + "Sofie", + "Stephen", + "Suvi", + "Takumi", + "Tatyana", + "Thiago", + "Tomoko", + "Vicki", + "Vitoria", + "Zayd", + "Zeina", + "Zhiyu", +} diff --git a/script/amazon_polly.py b/script/amazon_polly.py new file mode 100644 index 00000000000..fcb0a4b7987 --- /dev/null +++ b/script/amazon_polly.py @@ -0,0 +1,70 @@ +"""Helper script to update supported languages for Amazone Polly text-to-speech (TTS). + +N.B. This script requires AWS credentials. +""" + +from dataclasses import dataclass +from pathlib import Path +from typing import Self + +import boto3 + +from .hassfest.serializer import format_python_namespace + + +@dataclass(frozen=True) +class AmazonPollyVoice: + """Amazon Polly Voice.""" + + id: str + name: str + gender: str + language_name: str + language_code: str + supported_engines: set[str] + additional_language_codes: set[str] + + @classmethod + def validate(cls, model: dict[str, str | list[str]]) -> Self: + """Validate data model.""" + return cls( + id=model["Id"], + name=model["Name"], + gender=model["Gender"], + language_name=model["LanguageName"], + language_code=model["LanguageCode"], + supported_engines=set(model["SupportedEngines"]), + additional_language_codes=set(model.get("AdditionalLanguageCodes", [])), + ) + + +def get_all_voices(client: boto3.client) -> list[AmazonPollyVoice]: + """Get list of all supported voices from Amazon Polly.""" + response = client.describe_voices() + return [AmazonPollyVoice.validate(voice) for voice in response["Voices"]] + + +supported_regions = set( + boto3.session.Session().get_available_regions(service_name="polly") +) + +polly_client = boto3.client(service_name="polly", region_name="us-east-1") +voices = get_all_voices(polly_client) +supported_voices = set({v.id for v in voices}) +supported_engines = set().union(*[v.supported_engines for v in voices]) + +Path("homeassistant/generated/amazon_polly.py").write_text( + format_python_namespace( + { + "SUPPORTED_VOICES": supported_voices, + "SUPPORTED_REGIONS": supported_regions, + "SUPPORTED_ENGINES": supported_engines, + }, + annotations={ + "SUPPORTED_VOICES": "Final[set[str]]", + "SUPPORTED_REGIONS": "Final[set[str]]", + "SUPPORTED_ENGINES": "Final[set[str]]", + }, + generator="script.amazon_polly", + ) +)