diff --git a/core/filler_audio.py b/core/filler_audio.py index c857a61..fc648cd 100644 --- a/core/filler_audio.py +++ b/core/filler_audio.py @@ -107,10 +107,28 @@ class FillerAudioLibrary: segments: Iterable[tuple[str, str | None]], ) -> bytes: clip = bytearray() - for text, language_code in segments: + for i, (text, language_code) in enumerate(segments): + LOGGER.info( + "synthesize_segments: starting segment %s language=%s text=%r", + i, + language_code, + text[:60], + ) segment = await self._synthesize_clip(tts, text, language_code=language_code) if segment: clip.extend(segment) + LOGGER.info( + "synthesize_segments: completed segment %s bytes=%s total_bytes=%s", + i, + len(segment), + len(clip), + ) + else: + LOGGER.warning( + "synthesize_segments: segment %s produced NO audio; language=%s", + i, + language_code, + ) return bytes(clip) def _load_from_files(self) -> None: diff --git a/core/session.py b/core/session.py index b2971e6..9631865 100644 --- a/core/session.py +++ b/core/session.py @@ -1868,6 +1868,7 @@ class CallSession: len(segment_text), _preview_text(segment_text), ) + audio_segment_chunk_count = 0 async for audio_chunk in self._tts.synthesize_stream( one_shot_text_stream(), language_code=language_code, @@ -1875,6 +1876,7 @@ class CallSession: self._ensure_generation(epoch) if not audio_chunk: continue + audio_segment_chunk_count += 1 audio_chunk_count += 1 audio_byte_count += len(audio_chunk) if not first_audio_seen: @@ -1887,6 +1889,20 @@ class CallSession: ) self._set_state(SessionState.ASSISTANT_SPEAKING, reason="initial greeting started") await self.transport.send_audio(audio_chunk) + + if audio_segment_chunk_count == 0: + LOGGER.warning( + "realtime session %s initial greeting segment produced NO audio: language=%s", + self.session_id, + language_code, + ) + else: + LOGGER.info( + "realtime session %s initial greeting segment completed: language=%s chunks=%s", + self.session_id, + language_code, + audio_segment_chunk_count, + ) await self.transport.flush_audio() self._ensure_generation(epoch) LOGGER.info( diff --git a/main.py b/main.py index 12e7d93..3c922c4 100644 --- a/main.py +++ b/main.py @@ -103,24 +103,20 @@ def _initial_greeting_segments() -> tuple[tuple[str, str | None], ...]: language = str(os.getenv("REALTIME_VOICE_INITIAL_GREETING_LANGUAGE_CODE", "")).strip() or None return ((raw, language),) - ru_text = str( - os.getenv( - "REALTIME_VOICE_INITIAL_GREETING_RU_TEXT", - ( - "Здравствуйте! Меня зовут Айнур, я эй-ай-ассистент компании ДиджиОпс. " - "Как я могу к вам обращаться и на каком языке вам удобнее продолжить: на русском или на казахском?" - ), + ru_text = ( + str(os.getenv("REALTIME_VOICE_INITIAL_GREETING_RU_TEXT") or "").strip() + or ( + "Здравствуйте! Меня зовут Айнур, я эй-ай-ассистент компании ДиджиОпс. " + "Как я могу к вам обращаться и на каком языке вам удобнее продолжить: на русском или на казахском?" ) - ).strip() - kk_text = str( - os.getenv( - "REALTIME_VOICE_INITIAL_GREETING_KK_TEXT", - ( - "Сәлеметсіз бе! Менің атым Айнұр, мен ДиджиОпс компаниясының эй-ай ассистентімін. " - "Сізге қалай жүгінсем болады және қай тілде сөйлескен ыңғайлы: орысша ма, қазақша ма?" - ), + ) + kk_text = ( + str(os.getenv("REALTIME_VOICE_INITIAL_GREETING_KK_TEXT") or "").strip() + or ( + "Сәлеметсіз бе! Менің атым Айнұр, мен ДиджиОпс компаниясының эй-ай ассистентімін. " + "Сізге қалай жүгінсем болады және қай тілде сөйлескен ыңғайлы: орысша ма, қазақша ма?" ) - ).strip() + ) segments: list[tuple[str, str | None]] = [] if ru_text: segments.append((ru_text, "ru")) diff --git a/providers/tts.py b/providers/tts.py index 99ff053..001e283 100644 --- a/providers/tts.py +++ b/providers/tts.py @@ -192,7 +192,11 @@ class ElevenLabsTTS(BaseTTS): if not self._voice_id: raise RuntimeError("ELEVENLABS_TTS_VOICE_ID is required for ElevenLabs TTS") - effective_language_code = (str(language_code).strip() if language_code else "") or self._language_code + # If language_code is explicitly provided, use it. + # If it's None, use the instance default self._language_code. + # To trigger auto-detection, language_code should be an empty string or the default should be None. + effective_language_code = language_code if language_code is not None else self._language_code + if self._resolved_transport_mode() == "http_stream": async for audio_chunk in self._synthesize_http_stream( text_stream, @@ -234,7 +238,7 @@ class ElevenLabsTTS(BaseTTS): self._output_format, self._provider_sample_rate_hz, self._target_sample_rate_hz, - language_code, + language_code or "auto", self._auto_mode, ) try: @@ -297,13 +301,14 @@ class ElevenLabsTTS(BaseTTS): if audio_chunk: yield audio_chunk if self._is_error_payload(payload): + error_detail = self._format_error_payload(payload) LOGGER.error( - "ElevenLabs TTS error payload: voice_id=%s model=%s payload=%s", + "ElevenLabs TTS error payload: voice_id=%s model=%s error=%s", self._voice_id, self._model_id, - self._format_error_payload(payload), + error_detail, ) - raise RuntimeError(self._format_error_payload(payload)) + raise RuntimeError(error_detail) if bool(payload.get("isFinal")) or bool(payload.get("is_final")): LOGGER.info( "ElevenLabs TTS final payload: voice_id=%s model=%s chunks=%s " @@ -327,9 +332,9 @@ class ElevenLabsTTS(BaseTTS): except asyncio.TimeoutError as exc: raise RuntimeError("ElevenLabs TTS WebSocket request timed out") from exc except WebSocketException as exc: - raise RuntimeError("ElevenLabs TTS WebSocket stream failed") from exc + raise RuntimeError(f"ElevenLabs TTS WebSocket stream failed: {exc}") from exc except OSError as exc: - raise RuntimeError("ElevenLabs TTS WebSocket connection failed") from exc + raise RuntimeError(f"ElevenLabs TTS WebSocket connection failed: {exc}") from exc async def _synthesize_http_stream( self, @@ -364,7 +369,7 @@ class ElevenLabsTTS(BaseTTS): self._output_format, self._provider_sample_rate_hz, self._target_sample_rate_hz, - language_code, + language_code or "auto", ) try: @@ -389,9 +394,9 @@ class ElevenLabsTTS(BaseTTS): async with session.post(url, headers=headers, json=payload) as response: if response.status >= 400: payload_text = await response.text() - raise RuntimeError( - f"ElevenLabs TTS HTTP {response.status}: {payload_text[:300]}" - ) + error_msg = f"ElevenLabs TTS HTTP {response.status}: {payload_text[:300]}" + LOGGER.error(error_msg) + raise RuntimeError(error_msg) async for audio_chunk in response.content.iter_chunked(4096): if not audio_chunk: continue @@ -439,7 +444,7 @@ class ElevenLabsTTS(BaseTTS): except asyncio.TimeoutError as exc: raise RuntimeError("ElevenLabs TTS HTTP stream timed out") from exc except aiohttp.ClientError as exc: - raise RuntimeError("ElevenLabs TTS HTTP stream failed") from exc + raise RuntimeError(f"ElevenLabs TTS HTTP stream failed: {exc}") from exc LOGGER.info( "ElevenLabs TTS HTTP stream completed: voice_id=%s model=%s requests=%s chunks=%s " @@ -462,9 +467,9 @@ class ElevenLabsTTS(BaseTTS): "sync_alignment": "false", "apply_text_normalization": self._apply_text_normalization, } - effective_language = (str(language_code).strip() if language_code else "") or self._language_code - if effective_language: - query["language_code"] = effective_language + # Use provided language_code or fallback to None for auto-detection + if language_code: + query["language_code"] = language_code encoded_voice_id = quote(self._voice_id, safe="") return f"{self._ws_base}/v1/text-to-speech/{encoded_voice_id}/stream-input?{urlencode(query)}" @@ -482,9 +487,9 @@ class ElevenLabsTTS(BaseTTS): "voice_settings": self._voice_settings, "apply_text_normalization": self._apply_text_normalization, } - effective_language = (str(language_code).strip() if language_code else "") or self._language_code - if effective_language: - payload["language_code"] = effective_language + # Use provided language_code or fallback to None for auto-detection + if language_code: + payload["language_code"] = language_code return payload def _resolved_transport_mode(self) -> str: