Skip to content

Commit 847abc5

Browse files
committed
bug fixes, translation fixes
1 parent 7e67729 commit 847abc5

File tree

11 files changed

+237
-109
lines changed

11 files changed

+237
-109
lines changed

custom_components/openai_tts/config_flow.py

Lines changed: 24 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
CONF_NORMALIZE_AUDIO,
3131
CONF_INSTRUCTIONS,
3232
CONF_VOLUME_RESTORE,
33+
CONF_PAUSE_PLAYBACK,
3334
)
3435

3536
_LOGGER = logging.getLogger(__name__)
@@ -119,40 +120,51 @@ async def async_step_user(self, user_input: dict[str, Any] | None = None):
119120

120121
@staticmethod
121122
def async_get_options_flow(config_entry):
122-
return OpenAITTSOptionsFlow()
123+
return OpenAITTSOptionsFlow(config_entry)
123124

124125
class OpenAITTSOptionsFlow(OptionsFlow):
125126
"""Handle options flow for OpenAI TTS."""
127+
128+
def __init__(self, config_entry):
129+
"""Initialize options flow."""
130+
self._config_entry = config_entry
131+
126132
async def async_step_init(self, user_input: dict | None = None):
127133
if user_input is not None:
128134
return self.async_create_entry(title="", data=user_input)
129135

130136
chime_opts = await self.hass.async_add_executor_job(get_chime_options)
131137
options_schema = vol.Schema({
132138
vol.Optional(
133-
CONF_CHIME_ENABLE,
134-
default=self.config_entry.options.get(CONF_CHIME_ENABLE, self.config_entry.data.get(CONF_CHIME_ENABLE, False)),
139+
"chime", # Use strings directly here, not constants
140+
default=self._config_entry.options.get(CONF_CHIME_ENABLE, self._config_entry.data.get(CONF_CHIME_ENABLE, False)),
135141
): selector({"boolean": {}}),
136142

137143
vol.Optional(
138-
CONF_CHIME_SOUND,
139-
default=self.config_entry.options.get(CONF_CHIME_SOUND, self.config_entry.data.get(CONF_CHIME_SOUND, "threetone.mp3")),
144+
"chime_sound", # Use strings directly
145+
default=self._config_entry.options.get(CONF_CHIME_SOUND, self._config_entry.data.get(CONF_CHIME_SOUND, "threetone.mp3")),
140146
): selector({"select": {"options": chime_opts}}),
141147

142148
vol.Optional(
143-
CONF_NORMALIZE_AUDIO,
144-
default=self.config_entry.options.get(CONF_NORMALIZE_AUDIO, self.config_entry.data.get(CONF_NORMALIZE_AUDIO, False)),
149+
"normalize_audio", # Use strings directly
150+
default=self._config_entry.options.get(CONF_NORMALIZE_AUDIO, self._config_entry.data.get(CONF_NORMALIZE_AUDIO, False)),
145151
): selector({"boolean": {}}),
146152

147153
vol.Optional(
148-
CONF_INSTRUCTIONS,
149-
default=self.config_entry.options.get(CONF_INSTRUCTIONS, self.config_entry.data.get(CONF_INSTRUCTIONS, "")),
154+
"instructions", # Use strings directly
155+
default=self._config_entry.options.get(CONF_INSTRUCTIONS, self._config_entry.data.get(CONF_INSTRUCTIONS, "")),
150156
): TextSelector(TextSelectorConfig(type=TextSelectorType.TEXT, multiline=True)),
151157

152158
vol.Optional(
153-
CONF_VOLUME_RESTORE,
154-
default=self.config_entry.options.get(CONF_VOLUME_RESTORE, self.config_entry.data.get(CONF_VOLUME_RESTORE, False)),
159+
"volume_restore", # Use strings directly
160+
default=self._config_entry.options.get(CONF_VOLUME_RESTORE, self._config_entry.data.get(CONF_VOLUME_RESTORE, False)),
161+
): selector({"boolean": {}}),
162+
163+
# Use string directly for pause_playback
164+
vol.Optional(
165+
"pause_playback", # Must match exactly with translation key
166+
default=self._config_entry.options.get(CONF_PAUSE_PLAYBACK, self._config_entry.data.get(CONF_PAUSE_PLAYBACK, False)),
155167
): selector({"boolean": {}}),
156168
})
157169

158-
return self.async_show_form(step_id="init", data_schema=options_schema)
170+
return self.async_show_form(step_id="init", data_schema=options_schema)

custom_components/openai_tts/const.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,5 +21,5 @@
2121
# Toggle to snapshot & restore volumes
2222
CONF_VOLUME_RESTORE = "volume_restore"
2323

24-
# NEW: Toggle to pause/resume media playback
25-
CONF_PAUSE_PLAYBACK = "pause_playback"
24+
# Toggle to pause/resume media playback
25+
CONF_PAUSE_PLAYBACK = "pause_playback"

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": "3.4b0"
13+
"version": "3.4b1"
1414
}

custom_components/openai_tts/services.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ say:
6868
mode: slider
6969
pause_playback:
7070
name: Pause Playback
71-
description: Pause any currently playing media (only works with Sonos speakers).
71+
description: Pause any currently playing media during announcement (only works with Sonos speakers).
7272
required: false
7373
default: false
7474
selector:

custom_components/openai_tts/strings.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,10 @@
2929
"voice": "Voice",
3030
"instructions": "Instructions (check https://www.openai.fm/ )",
3131
"normalize_audio": "Enable loudness for generated audio (uses more CPU)",
32-
"volume_restore": "Restore speaker volumes after TTS playback"
32+
"volume_restore": "Restore speaker volumes after TTS playback",
33+
"pause_playback": "Pause media during TTS (Sonos only)"
3334
}
3435
}
3536
}
3637
}
37-
}
38+
}

custom_components/openai_tts/translations/cs.json

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,14 @@
2525
"chime": "Povolte zvukový signál před řečí (užitečné pro oznámení)",
2626
"chime_sound": "Zvuk zvukového signálu",
2727
"speed": "Rychlost (0,25 až 4,0)",
28+
"model": "Model",
2829
"voice": "Hlas",
29-
"normalize_audio": "Povolte zvýšení hlasitosti generovaného audia (vyžaduje více CPU)"
30+
"instructions": "Pokyny (viz https://www.openai.fm/)",
31+
"normalize_audio": "Povolte zvýšení hlasitosti generovaného audia (vyžaduje více CPU)",
32+
"volume_restore": "Obnovit hlasitost reproduktorů po přehrávání TTS",
33+
"pause_playback": "Pozastavit přehrávání médií během TTS (pouze Sonos)"
3034
}
3135
}
3236
}
3337
}
34-
}
38+
}

custom_components/openai_tts/translations/de.json

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,14 @@
2525
"chime": "Aktivieren Sie den Signalton vor der Sprache (nützlich für Ansagen)",
2626
"chime_sound": "Signalton",
2727
"speed": "Geschwindigkeit (0,25 bis 4,0)",
28+
"model": "Modell",
2829
"voice": "Stimme",
29-
"normalize_audio": "Aktivieren Sie die Lautstärkeerhöhung für das erzeugte Audio (verwendet mehr CPU)"
30+
"instructions": "Anweisungen (siehe https://www.openai.fm/)",
31+
"normalize_audio": "Aktivieren Sie die Lautstärkeerhöhung für das erzeugte Audio (verwendet mehr CPU)",
32+
"volume_restore": "Lautstärke der Lautsprecher nach der TTS-Wiedergabe wiederherstellen",
33+
"pause_playback": "Medienwiedergabe während TTS pausieren (nur Sonos)"
3034
}
3135
}
3236
}
3337
}
34-
}
38+
}

custom_components/openai_tts/translations/el.json

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
"speed": "Ταχύτητα (0.25 έως 4.0, όπου το 1.0 είναι προεπιλογή)",
1010
"model": "Μοντέλο",
1111
"voice": "Φωνή",
12-
"url": "Εισάγετε ένα endpoint συμβατό με OpenAI (προαιρετικά συμπεριλάβετε αριθμό θύρας)."
12+
"url": "Εισάγετε ένα endpoint συμβατό με OpenAI (προαιρετικά συμπεριλάβετε την πόρτα)."
1313
}
1414
}
1515
},
@@ -22,13 +22,17 @@
2222
"init": {
2323
"title": "Διαμορφώστε τις επιλογές TTS",
2424
"data": {
25-
"chime": "Ενεργοποιήστε τον ήχο του ηχητικού σήματος πριν από την ομιλία (χρήσιμο για ανακοινώσεις)",
26-
"chime_sound": "Ήχος ηχητικού σήματος",
25+
"chime": "Ενεργοποιήστε τον ηχο ανακοινωσεων πριν από την ομιλία",
26+
"chime_sound": "Ήχος ανακοινώσεων",
2727
"speed": "Ταχύτητα (0.25 έως 4.0)",
28+
"model": "Μοντέλο",
2829
"voice": "Φωνή",
29-
"normalize_audio": "Ενεργοποιήστε την αύξηση της έντασης για τον παραγόμενο ήχο (χρησιμοποιεί περισσότερη CPU)"
30+
"instructions": "Οδηγίες (δείτε https://www.openai.fm/)",
31+
"normalize_audio": "Ενεργοποιήστε την αύξηση της έντασης για τον παραγόμενο ήχο (χρησιμοποιεί περισσότερη CPU)",
32+
"volume_restore": "Επαναφορά έντασης μετά την αναπαραγωγή TTS",
33+
"pause_playback": "Παύση πολυμέσων κατά τη διάρκεια TTS (μόνο για Sonos)"
3034
}
3135
}
3236
}
3337
}
34-
}
38+
}

custom_components/openai_tts/translations/en.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,8 @@
2929
"voice": "Voice",
3030
"instructions": "Instructions",
3131
"normalize_audio": "Enable loudness for generated audio (uses more CPU)",
32-
"volume_restore": "Restore speaker volumes after TTS playback"
32+
"volume_restore": "Restore speaker volumes after TTS playback",
33+
"pause_playback": "Pause media during TTS (Sonos only)"
3334
}
3435
}
3536
}

custom_components/openai_tts/tts.py

Lines changed: 57 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515
from homeassistant.core import HomeAssistant
1616
from homeassistant.helpers.entity_platform import AddEntitiesCallback
1717
from homeassistant.helpers.entity import generate_entity_id
18+
from homeassistant.helpers.restore_state import RestoreEntity
19+
1820
from .const import (
1921
CONF_API_KEY,
2022
CONF_MODEL,
@@ -73,7 +75,7 @@ async def async_setup_entry(
7375
async_add_entities([OpenAITTSEntity(hass, config_entry, engine)])
7476

7577

76-
class OpenAITTSEntity(TextToSpeechEntity):
78+
class OpenAITTSEntity(TextToSpeechEntity, RestoreEntity):
7779
_attr_has_entity_name = True
7880
_attr_should_poll = False
7981

@@ -93,6 +95,41 @@ def __init__(self, hass: HomeAssistant, config: ConfigEntry, engine: OpenAITTSEn
9395
self._last_total_time = None
9496
self._last_media_duration_ms = None # Store in milliseconds
9597

98+
async def async_added_to_hass(self) -> None:
99+
"""When entity is added to hass, restore previous state."""
100+
await super().async_added_to_hass()
101+
102+
# Restore previous state if it exists
103+
last_state = await self.async_get_last_state()
104+
105+
if last_state is not None and last_state.attributes:
106+
# Restore from attributes
107+
self._engine_active = last_state.attributes.get("engine_active", False)
108+
109+
# Restore time values
110+
api_time_str = last_state.attributes.get("last_api_time")
111+
if api_time_str and " msec" in api_time_str:
112+
self._last_api_time = int(api_time_str.replace(" msec", ""))
113+
114+
ffmpeg_time_str = last_state.attributes.get("last_ffmpeg_time")
115+
if ffmpeg_time_str and " msec" in ffmpeg_time_str:
116+
self._last_ffmpeg_time = int(ffmpeg_time_str.replace(" msec", ""))
117+
118+
total_time_str = last_state.attributes.get("last_total_time")
119+
if total_time_str and " msec" in total_time_str:
120+
self._last_total_time = int(total_time_str.replace(" msec", ""))
121+
122+
# Restore media duration directly (stored as raw milliseconds)
123+
self._last_media_duration_ms = last_state.attributes.get("media_duration")
124+
125+
_LOGGER.debug(
126+
"Restored OpenAI TTS entity state: api_time=%s, ffmpeg_time=%s, total_time=%s, media_duration=%s",
127+
self._last_api_time,
128+
self._last_ffmpeg_time,
129+
self._last_total_time,
130+
self._last_media_duration_ms
131+
)
132+
96133
@property
97134
def default_language(self) -> str:
98135
return "en"
@@ -266,6 +303,10 @@ def get_tts_audio(
266303
# Compute media duration in milliseconds before cleaning up.
267304
duration_seconds = get_media_duration(merged_output_path)
268305
self._last_media_duration_ms = int(duration_seconds * 1000)
306+
307+
# DO NOT call self.async_write_ha_state() here - thread safety issue
308+
# It will be called from the async_get_tts_audio method
309+
269310
# Cleanup temporary files.
270311
try:
271312
os.remove(tts_path)
@@ -306,6 +347,10 @@ def get_tts_audio(
306347
# Compute media duration in milliseconds for the normalized file.
307348
duration_seconds = get_media_duration(norm_output_path)
308349
self._last_media_duration_ms = int(duration_seconds * 1000)
350+
351+
# DO NOT call self.async_write_ha_state() here - thread safety issue
352+
# It will be called from the async_get_tts_audio method
353+
309354
try:
310355
os.remove(norm_input_path)
311356
os.remove(norm_output_path)
@@ -328,6 +373,10 @@ def get_tts_audio(
328373
_LOGGER.debug("Overall TTS processing time: %.2f ms", overall_duration)
329374
self._last_total_time = overall_duration
330375
self._last_ffmpeg_time = 0 # No ffmpeg processing used.
376+
377+
# DO NOT call self.async_write_ha_state() here - thread safety issue
378+
# It will be called from the async_get_tts_audio method
379+
331380
return "mp3", audio_content
332381

333382
except CancelledError as ce:
@@ -347,11 +396,17 @@ async def async_get_tts_audio(
347396
try:
348397
self._engine_active = True
349398
self.async_write_ha_state()
350-
return await asyncio.shield(
399+
400+
result = await asyncio.shield(
351401
self.hass.async_add_executor_job(
352402
partial(self.get_tts_audio, message, language, options=options)
353403
)
354404
)
405+
406+
# Update the entity state from within the event loop
407+
self.async_write_ha_state()
408+
409+
return result
355410
except asyncio.CancelledError:
356411
_LOGGER.exception("async_get_tts_audio cancelled")
357412
raise

0 commit comments

Comments
 (0)