Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Automatically generate Amazon Polly list of voices and regions #119198

Merged
merged 11 commits into from
Aug 22, 2024
121 changes: 9 additions & 112 deletions homeassistant/components/amazon_polly/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,135 +8,32 @@
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[list[str]] = ["mp3", "ogg_vorbis", "pcm"]
SUPPORTED_OUTPUT_FORMATS: Final[set[str]] = {"mp3", "ogg_vorbis", "pcm"}

SUPPORTED_ENGINES: Final[list[str]] = ["neural", "standard"]
SUPPORTED_SAMPLE_RATES: Final[set[str]] = {"8000", "16000", "22050", "24000"}

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",
"audio/ogg": "ogg",
"audio/pcm": "pcm",
}

DEFAULT_REGION: Final = "us-east-1"

DEFAULT_ENGINE: Final = "standard"
DEFAULT_VOICE: Final = "Joanna"
DEFAULT_OUTPUT_FORMAT: Final = "mp3"
Expand Down
8 changes: 5 additions & 3 deletions homeassistant/components/amazon_polly/tts.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,11 @@
)
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

Expand All @@ -38,13 +43,10 @@
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__)
Expand Down
137 changes: 137 additions & 0 deletions homeassistant/generated/amazon_polly.py
Original file line number Diff line number Diff line change
@@ -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",
}
70 changes: 70 additions & 0 deletions script/amazon_polly.py
Original file line number Diff line number Diff line change
@@ -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",
)
)