Skip to content

Commit 9911a79

Browse files
committed
v3.2
1 parent 2e8ebec commit 9911a79

File tree

16 files changed

+300
-212
lines changed

16 files changed

+300
-212
lines changed

README.md

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,16 @@ The OpenAI TTS component for Home Assistant makes it possible to use the OpenAI
1111
visit: (https://platform.openai.com/docs/pricing)
1212

1313

14-
## Features
14+
## Features
15+
16+
- 🗣️ **Text-to-Speech** conversion using OpenAI's API
17+
- 🌍 **Support for multiple languages and voices** No special configuration is needed. The AI model will auto-recognize the language.
18+
- ⭐🔔 **(New!) Chime option** – Useful for announcements on speakers. (See Devices --> OpenAI TTS --> CONNFIGURE button)
19+
- ⭐🔔 **(New!) User configurable chime sounds** – Drop your own chime sound into config/custom_components/openai_tts/chime folder (mp3).
20+
- ⭐🎛️ **(New!) Audio normalization option** – Uses more CPU but provides better audible sound on mobile phones and small speakers. (See Devices --> OpenAI TTS --> CONNFIGURE button)
21+
- 🎙️ **Customizable speech model** ([Check supported voices and models](https://platform.openai.com/docs/guides/text-to-speech))
22+
- 🏡 **Integration with Home Assistant** – Works with assistant, automations, and scripts.
1523

16-
- Text-to-Speech conversion using OpenAI's API
17-
- Support for multiple languages and voices
18-
- Chime option. Usefull for announcements.
19-
- Customizable speech model (check https://platform.openai.com/docs/guides/text-to-speech for supported voices and models)
20-
- Integration with Home Assistant's assistant, automations and scripts
2124

2225
## YouTube sample video (its not a tutorial!)
2326

5.51 KB
Binary file not shown.
8.54 KB
Binary file not shown.
8.1 KB
Binary file not shown.
7.54 KB
Binary file not shown.
8.34 KB
Binary file not shown.

custom_components/openai_tts/config_flow.py

Lines changed: 58 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
"""
44
from __future__ import annotations
55
from typing import Any
6+
import os
67
import voluptuous as vol
78
import logging
89
from urllib.parse import urlparse
@@ -13,7 +14,20 @@
1314
from homeassistant.helpers.selector import selector
1415
from homeassistant.exceptions import HomeAssistantError
1516

16-
from .const import CONF_API_KEY, CONF_MODEL, CONF_VOICE, CONF_SPEED, CONF_URL, DOMAIN, MODELS, VOICES, UNIQUE_ID
17+
from .const import (
18+
CONF_API_KEY,
19+
CONF_MODEL,
20+
CONF_VOICE,
21+
CONF_SPEED,
22+
CONF_URL,
23+
DOMAIN,
24+
MODELS,
25+
VOICES,
26+
UNIQUE_ID,
27+
CONF_CHIME_ENABLE, # Use constant for chime enable toggle
28+
CONF_CHIME_SOUND,
29+
CONF_NORMALIZE_AUDIO,
30+
)
1731

1832
_LOGGER = logging.getLogger(__name__)
1933

@@ -26,6 +40,26 @@ async def validate_user_input(user_input: dict):
2640
if user_input.get(CONF_VOICE) is None:
2741
raise ValueError("Voice is required")
2842

43+
def get_chime_options() -> list[dict[str, str]]:
44+
"""
45+
Scans the "chime" folder (located in the same directory as this file)
46+
and returns a list of options for the dropdown selector.
47+
Each option is a dict with 'value' (the file name) and 'label' (the file name without extension).
48+
"""
49+
chime_folder = os.path.join(os.path.dirname(__file__), "chime")
50+
try:
51+
files = os.listdir(chime_folder)
52+
except Exception as err:
53+
_LOGGER.error("Error listing chime folder: %s", err)
54+
files = []
55+
options = []
56+
for file in files:
57+
if file.lower().endswith(".mp3"):
58+
label = os.path.splitext(file)[0].title() # e.g. "Signal1.mp3" -> "Signal1"
59+
options.append({"value": file, "label": label})
60+
options.sort(key=lambda x: x["label"])
61+
return options
62+
2963
class OpenAITTSConfigFlow(ConfigFlow, domain=DOMAIN):
3064
"""Handle a config flow for OpenAI TTS."""
3165
VERSION = 1
@@ -63,7 +97,6 @@ async def async_step_user(self, user_input: dict[str, Any] | None = None):
6397
if user_input is not None:
6498
try:
6599
await validate_user_input(user_input)
66-
# Generate a random unique id so multiple integrations can be added.
67100
entry_id = generate_entry_id()
68101
user_input[UNIQUE_ID] = entry_id
69102
await self.async_set_unique_id(entry_id)
@@ -99,11 +132,24 @@ class OpenAITTSOptionsFlow(OptionsFlow):
99132
async def async_step_init(self, user_input: dict | None = None):
100133
if user_input is not None:
101134
return self.async_create_entry(title="", data=user_input)
135+
# Retrieve chime options using the executor to avoid blocking the event loop.
136+
chime_options = await self.hass.async_add_executor_job(get_chime_options)
102137
options_schema = vol.Schema({
138+
# Use constant for chime enable toggle so the label comes from strings.json
103139
vol.Optional(
104-
"chime",
105-
default=self.config_entry.options.get("chime", self.config_entry.data.get("chime", False))
140+
CONF_CHIME_ENABLE,
141+
default=self.config_entry.options.get(CONF_CHIME_ENABLE, self.config_entry.data.get(CONF_CHIME_ENABLE, False))
106142
): selector({"boolean": {}}),
143+
144+
vol.Optional(
145+
CONF_CHIME_SOUND,
146+
default=self.config_entry.options.get(CONF_CHIME_SOUND, self.config_entry.data.get(CONF_CHIME_SOUND, "threetone.mp3"))
147+
): selector({
148+
"select": {
149+
"options": chime_options
150+
}
151+
}),
152+
107153
vol.Optional(
108154
CONF_SPEED,
109155
default=self.config_entry.options.get(CONF_SPEED, self.config_entry.data.get(CONF_SPEED, 1.0))
@@ -115,6 +161,7 @@ async def async_step_init(self, user_input: dict | None = None):
115161
"mode": "slider"
116162
}
117163
}),
164+
118165
vol.Optional(
119166
CONF_VOICE,
120167
default=self.config_entry.options.get(CONF_VOICE, self.config_entry.data.get(CONF_VOICE, "shimmer"))
@@ -125,6 +172,12 @@ async def async_step_init(self, user_input: dict | None = None):
125172
"sort": True,
126173
"custom_value": True
127174
}
128-
})
175+
}),
176+
177+
# Normalization toggle using its constant; label will be picked from strings.json.
178+
vol.Optional(
179+
CONF_NORMALIZE_AUDIO,
180+
default=self.config_entry.options.get(CONF_NORMALIZE_AUDIO, self.config_entry.data.get(CONF_NORMALIZE_AUDIO, False))
181+
): selector({"boolean": {}})
129182
})
130183
return self.async_show_form(step_id="init", data_schema=options_schema)

custom_components/openai_tts/const.py

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,18 @@
1-
""" Constants for OpenAI TTS custom component"""
1+
"""
2+
Constants for OpenAI TTS custom component
3+
"""
24

35
DOMAIN = "openai_tts"
4-
CONF_API_KEY = 'api_key'
5-
CONF_MODEL = 'model'
6-
CONF_VOICE = 'voice'
7-
CONF_SPEED = 'speed'
8-
CONF_URL = 'url'
9-
UNIQUE_ID = 'unique_id'
6+
CONF_API_KEY = "api_key"
7+
CONF_MODEL = "model"
8+
CONF_VOICE = "voice"
9+
CONF_SPEED = "speed"
10+
CONF_URL = "url"
11+
UNIQUE_ID = "unique_id"
12+
1013
MODELS = ["tts-1", "tts-1-hd"]
1114
VOICES = ["alloy", "ash", "coral", "echo", "fable", "onyx", "nova", "sage", "shimmer"]
15+
16+
CONF_CHIME_ENABLE = "chime"
17+
CONF_CHIME_SOUND = "chime_sound"
18+
CONF_NORMALIZE_AUDIO = "normalize_audio"

custom_components/openai_tts/manifest.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,5 +10,5 @@
1010
"iot_class": "cloud_polling",
1111
"issue_tracker": "https://github.com/sfortis/openai_tts/issues",
1212
"requirements": [],
13-
"version": "0.3.1b0"
13+
"version": "0.3.2"
1414
}

custom_components/openai_tts/openaitts_engine.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ def __init__(self, api_key: str, voice: str, model: str, speed: float, url: str)
2626
self._url = url
2727

2828
def get_tts(self, text: str, speed: float = None, voice: str = None) -> AudioResponse:
29-
"""Synchronous TTS request using urllib.request
29+
"""Synchronous TTS request using urllib.request.
3030
If the API call fails, waits for 1 second and retries once.
3131
"""
3232
if speed is None:
@@ -42,7 +42,7 @@ def get_tts(self, text: str, speed: float = None, voice: str = None) -> AudioRes
4242
"model": self._model,
4343
"input": text,
4444
"voice": voice,
45-
"response_format": "wav",
45+
"response_format": "mp3",
4646
"speed": speed
4747
}
4848

custom_components/openai_tts/strings.json

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,28 +7,26 @@
77
"data": {
88
"api_key": "Enter OpenAI API key",
99
"speed": "Speed (0.25 to 4.0, where 1.0 is default)",
10-
"model": "Select model",
11-
"voice": "Select voice",
12-
"url": "Enter the OpenAI-compatible endpoint. Optionally include a port number."
10+
"model": "Model",
11+
"voice": "Voice",
12+
"url": "Enter an OpenAI-compatible endpoint (optionally include a port number)."
1313
}
1414
}
1515
},
1616
"error": {
17-
"wrong_api_key": "Invalid API key. Please enter a valid API key.",
18-
"already_configured": "This voice and endpoint are already configured."
19-
},
20-
"abort": {
21-
"already_configured": "This voice and endpoint are already configured."
17+
"wrong_api_key": "Invalid API key. Please enter a valid API key."
2218
}
2319
},
2420
"options": {
2521
"step": {
2622
"init": {
2723
"title": "Configure TTS options",
2824
"data": {
29-
"chime": "Enable chime before speech (useful for announcements)",
30-
"speed": "Set speed (0.25 to 4.0)",
31-
"voice": "Select voice"
25+
"chime": "Enable chime sound prior speech (useful for announcements)",
26+
"chime_sound": "Chime sound",
27+
"speed": "Speed (0.25 to 4.0)",
28+
"voice": "Voice",
29+
"normalize_audio": "Enable loudness for generated audio (uses more CPU)"
3230
}
3331
}
3432
}

custom_components/openai_tts/translations/cs.json

Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -7,28 +7,26 @@
77
"data": {
88
"api_key": "Zadejte OpenAI API klíč",
99
"speed": "Rychlost (0,25 až 4,0, kde 1,0 je výchozí)",
10-
"model": "Vyberte model",
11-
"voice": "Vyberte hlas",
12-
"url": "Zadejte OpenAI-kompatibilní endpoint (Volitelně uveďte číslo portu)"
10+
"model": "Model",
11+
"voice": "Hlas",
12+
"url": "Zadejte endpoint kompatibilní s OpenAI (volitelně uveďte číslo portu)."
1313
}
1414
}
1515
},
1616
"error": {
17-
"wrong_api_key": "Neplatný API klíč. Zadejte prosím platný API klíč.",
18-
"already_configured": "Tento hlas a tento endpoint jsou již nakonfigurovány."
19-
},
20-
"abort": {
21-
"already_configured": "Tento hlas a tento endpoint jsou již nakonfigurovány."
17+
"wrong_api_key": "Neplatný API klíč. Zadejte prosím platný API klíč."
2218
}
2319
},
2420
"options": {
2521
"step": {
2622
"init": {
27-
"title": "Nastavení TTS možností",
23+
"title": "Nastavte TTS možnosti",
2824
"data": {
29-
"chime": "Povolit zvukový signál před řečí (užitečné pro oznámení)",
30-
"speed": "Nastavit rychlost (0,25 až 4,0)",
31-
"voice": "Vyberte hlas"
25+
"chime": "Povolte zvukový signál před řečí (užitečné pro oznámení)",
26+
"chime_sound": "Zvuk zvukového signálu",
27+
"speed": "Rychlost (0,25 až 4,0)",
28+
"voice": "Hlas",
29+
"normalize_audio": "Povolte zvýšení hlasitosti generovaného audia (vyžaduje více CPU)"
3230
}
3331
}
3432
}

custom_components/openai_tts/translations/de.json

Lines changed: 11 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -6,29 +6,27 @@
66
"description": "Weitere Informationen finden Sie in der Dokumentation.",
77
"data": {
88
"api_key": "Geben Sie den OpenAI API-Schlüssel ein",
9-
"speed": "Geschwindigkeit (0.25 bis 4.0, wobei 1.0 Standard ist)",
10-
"model": "Wählen Sie das Modell aus",
11-
"voice": "Wählen Sie die Stimme aus",
12-
"url": "Geben Sie den OpenAI-kompatiblen Endpunkt ein (Optional können Sie eine Portnummer angeben)"
9+
"speed": "Geschwindigkeit (0,25 bis 4,0, wobei 1,0 Standard ist)",
10+
"model": "Modell",
11+
"voice": "Stimme",
12+
"url": "Geben Sie einen OpenAI-kompatiblen Endpunkt ein (optional können Sie eine Portnummer angeben)."
1313
}
1414
}
1515
},
1616
"error": {
17-
"wrong_api_key": "Ungültiger API-Schlüssel. Bitte geben Sie einen gültigen API-Schlüssel ein.",
18-
"already_configured": "Diese Stimme und dieser Endpunkt sind bereits konfiguriert."
19-
},
20-
"abort": {
21-
"already_configured": "Diese Stimme und dieser Endpunkt sind bereits konfiguriert."
17+
"wrong_api_key": "Ungültiger API-Schlüssel. Bitte geben Sie einen gültigen API-Schlüssel ein."
2218
}
2319
},
2420
"options": {
2521
"step": {
2622
"init": {
27-
"title": "TTS-Optionen konfigurieren",
23+
"title": "Konfigurieren Sie die TTS-Optionen",
2824
"data": {
29-
"chime": "Chime vor der Sprache aktivieren (nützlich für Ansagen)",
30-
"speed": "Geschwindigkeit einstellen (0.25 bis 4.0)",
31-
"voice": "Stimme auswählen"
25+
"chime": "Aktivieren Sie den Signalton vor der Sprache (nützlich für Ansagen)",
26+
"chime_sound": "Signalton",
27+
"speed": "Geschwindigkeit (0,25 bis 4,0)",
28+
"voice": "Stimme",
29+
"normalize_audio": "Aktivieren Sie die Lautstärkeerhöhung für das erzeugte Audio (verwendet mehr CPU)"
3230
}
3331
}
3432
}

custom_components/openai_tts/translations/el.json

Lines changed: 11 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3,32 +3,30 @@
33
"step": {
44
"user": {
55
"title": "Προσθήκη μετατροπής κειμένου σε ομιλία",
6-
"description": "Δείτε την τεκμηρίωση για περισσότερες πληροφορίες.",
6+
"description": "Δείτε την τεκμηρίωση για περαιτέρω πληροφορίες.",
77
"data": {
88
"api_key": "Εισάγετε το API key του OpenAI",
9-
"speed": "Ταχύτητα (0.25 έως 4.0, όπου το 1.0 είναι η προεπιλογή)",
10-
"model": "Επιλέξτε μοντέλο",
11-
"voice": "Επιλέξτε φωνή",
12-
"url": "Εισάγετε το custom API endpoint, συμβατό με OpenAI ( Προαιρετικά συμπεριλάβετε τον αριθμό της πόρτας)"
9+
"speed": "Ταχύτητα (0.25 έως 4.0, όπου το 1.0 είναι προεπιλογή)",
10+
"model": "Μοντέλο",
11+
"voice": "Φωνή",
12+
"url": "Εισάγετε ένα endpoint συμβατό με OpenAI (προαιρετικά συμπεριλάβετε αριθμό θύρας)."
1313
}
1414
}
1515
},
1616
"error": {
17-
"wrong_api_key": "Μη έγκυρο κλειδί API. Παρακαλώ εισάγετε ένα έγκυρο κλειδί API.",
18-
"already_configured": "Αυτή η φωνή και το σημείο τερματισμού είναι ήδη διαμορφωμένα."
19-
},
20-
"abort": {
21-
"already_configured": "Αυτή η φωνή και το σημείο τερματισμού είναι ήδη διαμορφωμένα."
17+
"wrong_api_key": "Μη έγκυρο API key. Εισάγετε ένα έγκυρο API key."
2218
}
2319
},
2420
"options": {
2521
"step": {
2622
"init": {
2723
"title": "Διαμορφώστε τις επιλογές TTS",
2824
"data": {
29-
"chime": "Ενεργοποιήστε το ηχητικό σήμα πριν από την ομιλία (χρήσιμο για ανακοινώσεις)",
30-
"speed": "Ρυθμίστε την ταχύτητα (0.25 έως 4.0)",
31-
"voice": "Επιλέξτε φωνή"
25+
"chime": "Ενεργοποιήστε τον ήχο του ηχητικού σήματος πριν από την ομιλία (χρήσιμο για ανακοινώσεις)",
26+
"chime_sound": "Ήχος ηχητικού σήματος",
27+
"speed": "Ταχύτητα (0.25 έως 4.0)",
28+
"voice": "Φωνή",
29+
"normalize_audio": "Ενεργοποιήστε την αύξηση της έντασης για τον παραγόμενο ήχο (χρησιμοποιεί περισσότερη CPU)"
3230
}
3331
}
3432
}

custom_components/openai_tts/translations/en.json

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,28 +7,26 @@
77
"data": {
88
"api_key": "Enter OpenAI API key",
99
"speed": "Speed (0.25 to 4.0, where 1.0 is default)",
10-
"model": "Select model",
11-
"voice": "Select voice",
12-
"url": "Enter the OpenAI-compatible endpoint. Optionally include a port number."
10+
"model": "Model",
11+
"voice": "Voice",
12+
"url": "Enter an OpenAI-compatible endpoint (optionally include a port number)."
1313
}
1414
}
1515
},
1616
"error": {
17-
"wrong_api_key": "Invalid API key. Please enter a valid API key.",
18-
"already_configured": "This voice and endpoint are already configured."
19-
},
20-
"abort": {
21-
"already_configured": "This voice and endpoint are already configured."
17+
"wrong_api_key": "Invalid API key. Please enter a valid API key."
2218
}
2319
},
2420
"options": {
2521
"step": {
2622
"init": {
2723
"title": "Configure TTS options",
2824
"data": {
29-
"chime": "Enable chime before speech (useful for announcements)",
30-
"speed": "Set speed (0.25 to 4.0)",
31-
"voice": "Select voice"
25+
"chime": "Enable chime sound prior speech (useful for announcements)",
26+
"chime_sound": "Chime sound",
27+
"speed": "Speed (0.25 to 4.0)",
28+
"voice": "Voice",
29+
"normalize_audio": "Enable loudness for generated audio (uses more CPU)"
3230
}
3331
}
3432
}

0 commit comments

Comments
 (0)