From 22b06a4149a30e69bb608b16fbda4dce3de71a77 Mon Sep 17 00:00:00 2001 From: Olivier <16240457+TiTidom-RC@users.noreply.github.com> Date: Wed, 27 May 2026 19:42:48 +0200 Subject: [PATCH 1/2] Enhance TTS logging with context and formatting Add richer contextual information to TTS/GenAI logging and standardize formatting. Many logging calls in resources/ttscastd/ttscastd.py now include metadata such as language, voice, engine/model, chunk counts and a safe repr-truncated text excerpt (first 80 chars) to improve diagnostics; several f-strings were replaced with logging format args. Also normalize a couple of GroupVol error logs and add "Echec" to .vscode/settings.json. These changes improve debugging and traceability of TTS/GenAI failures without changing functional behavior. --- .vscode/settings.json | 1 + resources/ttscastd/ttscastd.py | 50 +++++++++++++++++----------------- 2 files changed, 26 insertions(+), 25 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 1671ab6..341e50c 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -34,6 +34,7 @@ "customtext", "defaultpromptresult", "Deutsch", + "Echec", "ehthumbs", "Eiwc", "ENXIO", diff --git a/resources/ttscastd/ttscastd.py b/resources/ttscastd/ttscastd.py index 87dae67..91ec258 100644 --- a/resources/ttscastd/ttscastd.py +++ b/resources/ttscastd/ttscastd.py @@ -342,7 +342,7 @@ def jeedomTTS(ttsText, ttsLang): filecontent = fc.read() fc.close() except Exception as e: - logging.error('[DAEMON][JeedomTTS] Error while retrieving TTS file :: %s', e) + logging.error('[DAEMON][JeedomTTS] Error while retrieving TTS file :: %s | lang : %s | extrait : %s', e, ttsLang, repr(ttsText[:80])) logging.debug(traceback.format_exc()) filecontent = None return filecontent @@ -384,7 +384,7 @@ def voiceRSS(ttsText, ttsLang, ttsSpeed='0', ttsSSML=False): filecontent = fc.read() fc.close() """ except Exception as e: - logging.error('[DAEMON][VoiceRSS] Error while retrieving TTS file :: %s', e) + logging.error('[DAEMON][VoiceRSS] Error while retrieving TTS file :: %s | voix : %s | extrait : %s', e, ttsVoiceName, repr(ttsText[:80])) logging.debug(traceback.format_exc()) filecontent = None return filecontent @@ -547,7 +547,7 @@ def generateTestTTS(ttsText, ttsGoogleName, ttsVoiceName, ttsRSSVoiceName, ttsGe os.remove(filepath) except OSError: pass - logging.warning('[DAEMON][TestTTS] Google Translate API ERROR :: %s', e) + logging.warning('[DAEMON][TestTTS] Google Translate API ERROR :: %s | lang : %s | extrait : %s', e, ttsLang, repr(ttsText[:80])) else: logging.debug('[DAEMON][TestTTS] Le fichier TTS existe déjà dans le cache :: %s', filepath) @@ -581,7 +581,7 @@ def generateTestTTS(ttsText, ttsGoogleName, ttsVoiceName, ttsRSSVoiceName, ttsGe with open(filepath, 'wb') as f: f.write(ttsResult) else: - logging.warning('[DAEMON][TestTTS] JeedomTTS Error :: Incorrect Output — lang : %s — extrait : "%.80s"', ttsLang, ttsText) + logging.warning('[DAEMON][TestTTS] JeedomTTS Error :: Incorrect Output — lang : %s — extrait : %s', ttsLang, repr(ttsText[:80])) else: logging.debug('[DAEMON][TestTTS] Le fichier TTS existe déjà dans le cache :: %s', filepath) @@ -616,7 +616,7 @@ def generateTestTTS(ttsText, ttsGoogleName, ttsVoiceName, ttsRSSVoiceName, ttsGe with open(filepath, 'wb') as f: f.write(ttsResult) else: - logging.warning('[DAEMON][TestTTS] VoiceRSS Error :: Incorrect Output — voix : %s — extrait : "%.80s"', ttsRSSVoiceName, ttsText) + logging.warning('[DAEMON][TestTTS] VoiceRSS Error :: Incorrect Output — voix : %s — extrait : %s', ttsRSSVoiceName, repr(ttsText[:80])) else: logging.debug('[DAEMON][TestTTS] Le fichier TTS existe déjà dans le cache :: %s', filepath) @@ -661,7 +661,7 @@ def generateTestTTS(ttsText, ttsGoogleName, ttsVoiceName, ttsRSSVoiceName, ttsGe logging.info('[TIMING][GeminiStream] t0_start :: %.3f (%s)', _t, datetime.datetime.fromtimestamp(_t).strftime('%H:%M:%S.') + f'{int((_t % 1) * 1000):03d}') prefetch = TTSCast.geminiTTS(_textToSynth, ttsGeminiVoiceName, _effectiveStyle, streaming=True) if prefetch is None: - logging.error('[DAEMON][TestTTS] GeminiTTS streaming :: échec de la pré-lecture') + logging.error('[DAEMON][TestTTS] GeminiTTS streaming :: échec de la pré-lecture | voix : %s | extrait : %s', ttsGeminiVoiceName, repr(_textToSynth[:80])) return streamIter, firstChunkBytes, sampleRate, channels, streamClient = prefetch streamMimeType = 'audio/wav' # Le proxy envoie un stream WAV RIFF (PCM LE 16-bit) @@ -683,7 +683,7 @@ def generateTestTTS(ttsText, ttsGoogleName, ttsVoiceName, ttsRSSVoiceName, ttsGe out.write(audioBytes) logging.debug('[DAEMON][TestTTS] Fichier TTS Gemini généré :: %s', filepath) else: - logging.error('[DAEMON][TestTTS] Echec de la génération Gemini TTS — voix : %s — extrait : "%.80s"', ttsGeminiVoiceName, _textToSynth) + logging.error('[DAEMON][TestTTS] Echec de la génération Gemini TTS — voix : %s — extrait : %s', ttsGeminiVoiceName, repr(_textToSynth[:80])) return urlFileToPlay = f'{ttsSrvWeb}{filename}' logging.debug('[DAEMON][TestTTS] URL du fichier TTS à diffuser :: %s', urlFileToPlay) @@ -861,7 +861,7 @@ def generateTTS(ttsText, ttsFile, ttsVoiceName, ttsRSSVoiceName, ttsGeminiVoiceN os.remove(filepath) except OSError: pass - logging.warning('[DAEMON][GenerateTTS] Google Translate API ERROR :: %s', e) + logging.warning('[DAEMON][GenerateTTS] Google Translate API ERROR :: %s | lang : %s | extrait : %s', e, ttsLang, repr(ttsText[:80])) else: logging.debug('[DAEMON][GenerateTTS] Le fichier TTS existe déjà dans le cache :: %s', filepath) @@ -886,7 +886,7 @@ def generateTTS(ttsText, ttsFile, ttsVoiceName, ttsRSSVoiceName, ttsGeminiVoiceN f.write(ttsResult) logging.debug('[DAEMON][GenerateTTS] Fichier TTS généré :: %s', filepath) else: - logging.warning('[DAEMON][GenerateTTS] VoiceRSS Error :: Incorrect Output — voix : %s — extrait : "%.80s"', ttsRSSVoiceName, ttsText) + logging.warning('[DAEMON][GenerateTTS] VoiceRSS Error :: Incorrect Output — voix : %s — extrait : %s', ttsRSSVoiceName, repr(ttsText[:80])) else: logging.debug('[DAEMON][GenerateTTS] Le fichier TTS existe déjà dans le cache :: %s', filepath) else: @@ -913,13 +913,13 @@ def generateTTS(ttsText, ttsFile, ttsVoiceName, ttsRSSVoiceName, ttsGeminiVoiceN f.write(audioBytes) logging.debug('[DAEMON][GenerateTTS] Fichier TTS Gemini généré :: %s', filepath) else: - logging.warning('[DAEMON][GenerateTTS][GEMINI] Réponse invalide de l\'API Gemini TTS — voix : %s — extrait : "%.80s"', ttsGeminiVoiceName, _textToSynth) + logging.warning('[DAEMON][GenerateTTS][GEMINI] Réponse invalide de l\'API Gemini TTS — voix : %s — extrait : %s', ttsGeminiVoiceName, repr(_textToSynth[:80])) else: logging.error('[DAEMON][GenerateTTS] Moteur TTS inconnu ou non supporté : "%s". Valeurs acceptées : gcloudtts, gtranslatetts, jeedomtts, voicersstts, geminitts.', ttsEngine) except Exception as e: - logging.error('[DAEMON][GenerateTTS] Exception on TTS :: %s', e) + logging.error('[DAEMON][GenerateTTS] Exception on TTS :: %s | moteur : %s | extrait : %s', e, ttsEngine, repr(ttsText[:80])) logging.debug(traceback.format_exc()) @staticmethod @@ -1155,7 +1155,7 @@ def getTTS(ttsText, ttsGoogleUUID, ttsVoiceName, ttsRSSVoiceName, ttsGeminiVoice os.remove(filepath) except OSError: pass - logging.warning('[DAEMON][TTS] Google Translate API ERROR :: %s', e) + logging.warning('[DAEMON][TTS] Google Translate API ERROR :: %s | lang : %s | extrait : %s', e, ttsLang, repr(ttsText[:80])) else: logging.info('[DAEMON][TTS] Le fichier TTS existe déjà dans le cache :: %s', filepath) @@ -1189,7 +1189,7 @@ def getTTS(ttsText, ttsGoogleUUID, ttsVoiceName, ttsRSSVoiceName, ttsGeminiVoice with open(filepath, 'wb') as f: f.write(ttsResult) else: - logging.warning('[DAEMON][TTS] JeedomTTS Error :: Incorrect Output — lang : %s — extrait : "%.80s"', ttsLang, ttsText) + logging.warning('[DAEMON][TTS] JeedomTTS Error :: Incorrect Output — lang : %s — extrait : %s', ttsLang, repr(ttsText[:80])) else: logging.info('[DAEMON][TTS] Le fichier TTS existe déjà dans le cache :: %s', filepath) @@ -1224,7 +1224,7 @@ def getTTS(ttsText, ttsGoogleUUID, ttsVoiceName, ttsRSSVoiceName, ttsGeminiVoice with open(filepath, 'wb') as f: f.write(ttsResult) else: - logging.warning('[DAEMON][TTS] VoiceRSS Error :: Incorrect Output — voix : %s — extrait : "%.80s"', ttsRSSVoiceName, ttsText) + logging.warning('[DAEMON][TTS] VoiceRSS Error :: Incorrect Output — voix : %s — extrait : %s', ttsRSSVoiceName, repr(ttsText[:80])) else: logging.info('[DAEMON][TTS] Le fichier TTS existe déjà dans le cache :: %s', filepath) @@ -1265,7 +1265,7 @@ def getTTS(ttsText, ttsGoogleUUID, ttsVoiceName, ttsRSSVoiceName, ttsGeminiVoice logging.info('[TIMING][GeminiStream] t0_start :: %.3f (%s)', _t, datetime.datetime.fromtimestamp(_t).strftime('%H:%M:%S.') + f'{int((_t % 1) * 1000):03d}') prefetch = TTSCast.geminiTTS(_textToSynth, ttsGeminiVoiceName, _ttsGeminiStyle, streaming=True) if prefetch is None: - logging.error('[DAEMON][TTS] GeminiTTS streaming :: échec de la pré-lecture') + logging.error('[DAEMON][TTS] GeminiTTS streaming :: échec de la pré-lecture | voix : %s | extrait : %s', ttsGeminiVoiceName, repr(_textToSynth[:80])) return False streamIter, firstChunkBytes, sampleRate, channels, streamClient = prefetch mimeType = 'audio/wav' # Le proxy envoie un stream WAV RIFF (PCM LE 16-bit) @@ -1291,7 +1291,7 @@ def getTTS(ttsText, ttsGoogleUUID, ttsVoiceName, ttsRSSVoiceName, ttsGeminiVoice f.write(audioBytes) logging.info('[DAEMON][TTS] Fichier TTS Gemini généré :: %s', filepath) else: - logging.error('[DAEMON][TTS] GeminiTTS Error :: Incorrect Output — voix : %s — extrait : "%.80s"', ttsGeminiVoiceName, _textToSynth) + logging.error('[DAEMON][TTS] GeminiTTS Error :: Incorrect Output — voix : %s — extrait : %s', ttsGeminiVoiceName, repr(_textToSynth[:80])) return False urlFileToPlay = f'{ttsSrvWeb}{filename}' logging.debug('[DAEMON][TTS] URL du fichier TTS à diffuser :: %s', urlFileToPlay) @@ -1303,7 +1303,7 @@ def getTTS(ttsText, ttsGoogleUUID, ttsVoiceName, ttsRSSVoiceName, ttsGeminiVoice logging.error('[DAEMON][TTS] Moteur TTS inconnu ou non supporté : "%s". Valeurs acceptées : gcloudtts, gtranslatetts, jeedomtts, voicersstts, geminitts.', ttsEngine) except Exception as e: - logging.error('[DAEMON][TTS] Exception on TTS :: %s', e) + logging.error('[DAEMON][TTS] Exception on TTS :: %s | moteur : %s | extrait : %s', e, ttsEngine, repr(ttsText[:80])) logging.debug(traceback.format_exc()) @staticmethod @@ -1678,7 +1678,7 @@ def genAI(_aiPrompt, _aiCustomSysPrompt=None, _aiCustomTone=None, _aiCustomTemp= logging.warning('[DAEMON][GenAI][TOKENS] Aucun token reçu (input=0) - stats non envoyées à Jeedom') if not response.text: - logging.warning('[DAEMON][GenAI] Aucune réponse générée par Gemini — extrait : "%.80s"', _aiPrompt) + logging.warning('[DAEMON][GenAI] Aucune réponse générée par Gemini — extrait : %s', repr(_aiPrompt[:80])) return None else: raw_text = response.text.strip() @@ -1690,7 +1690,7 @@ def genAI(_aiPrompt, _aiCustomSysPrompt=None, _aiCustomTone=None, _aiCustomTemp= logging.warning('[DAEMON][GenAI] Clé (JSON ou Api) et/ou ID de projet Google invalide :: %s, %s, %s', myConfig.gCloudApiKey, "***" if myConfig.aiApiKey else "N/A", myConfig.aiProjectID) return None except Exception as e: - logging.error('[DAEMON][GenAI] Erreur: %s', e) + logging.error('[DAEMON][GenAI] Erreur :: %s | modèle : %s | extrait : %s', e, myConfig.aiModel, repr(_aiPrompt[:80])) return None @staticmethod @@ -1759,7 +1759,7 @@ def geminiTTS(ttsText: str, voiceName: str, style: str = '', streaming: bool = F channels = int(channels_match.group(1)) if channels_match else 1 logging.debug('[DAEMON][GeminiTTS] Premier chunk stream :: rate=%d | channels=%d', sampleRate, channels) return streamIterator, blob.data, sampleRate, channels, client - logging.error('[DAEMON][GeminiTTS] Streaming :: aucun chunk audio reçu.') + logging.error('[DAEMON][GeminiTTS] Streaming :: aucun chunk audio reçu | voix : %s | extrait : %s', voiceName, repr(ttsText[:80])) return None else: @@ -1802,11 +1802,11 @@ def geminiTTS(ttsText: str, voiceName: str, style: str = '', streaming: bool = F else: logging.debug('[DAEMON][GeminiTTS] Audio WAV natif retourné :: %d bytes', len(audio_data)) return audio_data - logging.warning('[DAEMON][GeminiTTS] Aucune donnée audio dans la réponse.') + logging.warning('[DAEMON][GeminiTTS] Aucune donnée audio dans la réponse | voix : %s | extrait : %s', voiceName, repr(ttsText[:80])) return None except Exception as e: - logging.error('[DAEMON][GeminiTTS] Exception :: %s', e) + logging.error('[DAEMON][GeminiTTS] Exception :: %s | Modèle : %s | Voix : %s | Mode : %s | Extrait : %s', e, myConfig.geminiTTSModel, voiceName, 'stream' if streaming else 'buffered', repr(ttsText[:80])) logging.debug(traceback.format_exc()) return None @@ -1894,7 +1894,7 @@ def geminiTTSStream(streamIterator, firstChunkBytes: bytes, sampleRate: int, cha logging.warning('[DAEMON][GeminiTTSStream] Client déconnecté avant la fin du stream (Broken pipe)') return None except Exception as e: - logging.error('[DAEMON][GeminiTTSStream] Exception :: %s', e) + logging.error('[DAEMON][GeminiTTSStream] Exception :: %s | chunks reçus : %d', e, len(pcmBuffer)) logging.debug(traceback.format_exc()) return None finally: @@ -2089,7 +2089,7 @@ def _setVol(uuid_dev, vol): dev.set_volume(vol) logging.debug(f'[DAEMON][GroupVol] Set {vol} on {dev.name}') except Exception as ex: - logging.error(f'[DAEMON][GroupVol] Error on {uuid_dev}: {ex}') + logging.error('[DAEMON][GroupVol] Error on %s :: %s', uuid_dev, ex) threads = [] for member_uuid in membersUUIDs: @@ -2114,7 +2114,7 @@ def _resVol(uuid_dev, vol): dev.set_volume(vol) logging.debug(f'[DAEMON][GroupVol] Restored {vol} on {dev.name}') except Exception as ex: - logging.error(f'[DAEMON][GroupVol] Error restoring {uuid_dev}: {ex}') + logging.error('[DAEMON][GroupVol] Error restoring %s :: %s', uuid_dev, ex) threads = [] for m_uuid, m_vol in volumeSnapshot.items(): From b1571f9f27bdf3255c3dad38cbad39f3e94042b1 Mon Sep 17 00:00:00 2001 From: Olivier <16240457+TiTidom-RC@users.noreply.github.com> Date: Wed, 27 May 2026 19:53:28 +0200 Subject: [PATCH 2/2] Bump ttscast plugin version to 1.9.5 Update plugin_info/info.json to increment the ttscast pluginVersion from 1.9.4 to 1.9.5. This change reflects a new plugin release and contains no other functional modifications. --- plugin_info/info.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugin_info/info.json b/plugin_info/info.json index a11cca0..13f0fb2 100644 --- a/plugin_info/info.json +++ b/plugin_info/info.json @@ -1,7 +1,7 @@ { "id": "ttscast", "name": "TTS Cast", - "pluginVersion": "1.9.4", + "pluginVersion": "1.9.5", "description": { "fr_FR": "Plugin pour gérer ses équipements Google, type Google Home, Nest Mini, Nest Hub (Max), Chromecast. Il permet de générer des notifications TTS (Text To Speech) et de les diffuser sur les équipements Google. Il permet également de diffuser des sons (mp3), des vidéos YouTube, une page Web, ou encore une radio en streaming sur ces mêmes équipements.", "en_US": "Plugin to manage Google equipment, such as Google Home, Nest Mini, Nest Hub (Max), Chromecast. It allows you to generate TTS (Text To Speech) notifications and broadcast them to Google devices. It also allows you to broadcast sounds (mp3), YouTube videos, a web page, or even streaming radio on the same equipment.",