Fix bilingual initial greeting playback
This commit is contained in:
+35
-15
@@ -1840,30 +1840,50 @@ class CallSession:
|
|||||||
) -> None:
|
) -> None:
|
||||||
first_audio_seen = False
|
first_audio_seen = False
|
||||||
started_monotonic = time.perf_counter()
|
started_monotonic = time.perf_counter()
|
||||||
cached_greeting = (
|
|
||||||
self._filler_audio.pick("initial_greeting", allow_fallback=False)
|
|
||||||
if self._filler_audio is not None
|
|
||||||
else None
|
|
||||||
)
|
|
||||||
if cached_greeting:
|
|
||||||
await self._play_cached_initial_greeting(
|
|
||||||
epoch=epoch,
|
|
||||||
greeting_text=greeting_text,
|
|
||||||
pcm_audio=cached_greeting,
|
|
||||||
started_monotonic=started_monotonic,
|
|
||||||
)
|
|
||||||
return
|
|
||||||
try:
|
try:
|
||||||
audio_chunk_count = 0
|
audio_chunk_count = 0
|
||||||
audio_byte_count = 0
|
audio_byte_count = 0
|
||||||
for segment_text, language_code in greeting_segments:
|
for segment_index, (segment_text, language_code) in enumerate(greeting_segments, start=1):
|
||||||
|
cached_segment = (
|
||||||
|
self._filler_audio.pick(
|
||||||
|
f"initial_greeting_{segment_index}_{language_code or 'default'}",
|
||||||
|
allow_fallback=False,
|
||||||
|
)
|
||||||
|
if self._filler_audio is not None
|
||||||
|
else None
|
||||||
|
)
|
||||||
|
if cached_segment:
|
||||||
|
self._ensure_generation(epoch)
|
||||||
|
audio_chunk_count += 1
|
||||||
|
audio_byte_count += len(cached_segment)
|
||||||
|
if not first_audio_seen:
|
||||||
|
first_audio_seen = True
|
||||||
|
self._log_latency(
|
||||||
|
"initial_greeting_cached_ttfa",
|
||||||
|
started_monotonic,
|
||||||
|
epoch=epoch,
|
||||||
|
message="session start -> cached initial greeting first audio",
|
||||||
|
)
|
||||||
|
self._set_state(SessionState.ASSISTANT_SPEAKING, reason="cached initial greeting started")
|
||||||
|
LOGGER.info(
|
||||||
|
"realtime session %s initial greeting cached segment: epoch=%s index=%s language=%s bytes=%s",
|
||||||
|
self.session_id,
|
||||||
|
epoch,
|
||||||
|
segment_index,
|
||||||
|
language_code,
|
||||||
|
len(cached_segment),
|
||||||
|
)
|
||||||
|
await self.transport.send_audio(cached_segment)
|
||||||
|
continue
|
||||||
|
|
||||||
async def one_shot_text_stream() -> AsyncGenerator[str, None]:
|
async def one_shot_text_stream() -> AsyncGenerator[str, None]:
|
||||||
yield segment_text
|
yield segment_text
|
||||||
|
|
||||||
LOGGER.info(
|
LOGGER.info(
|
||||||
"realtime session %s initial greeting segment start: epoch=%s language=%s chars=%s text=%r",
|
"realtime session %s initial greeting segment start: epoch=%s index=%s language=%s chars=%s text=%r",
|
||||||
self.session_id,
|
self.session_id,
|
||||||
epoch,
|
epoch,
|
||||||
|
segment_index,
|
||||||
language_code,
|
language_code,
|
||||||
len(segment_text),
|
len(segment_text),
|
||||||
_preview_text(segment_text),
|
_preview_text(segment_text),
|
||||||
|
|||||||
@@ -97,25 +97,32 @@ def _single_initial_greeting_enabled() -> bool:
|
|||||||
return raw in {"1", "true", "yes", "on"}
|
return raw in {"1", "true", "yes", "on"}
|
||||||
|
|
||||||
|
|
||||||
|
def _env_text_or_default(name: str, default: str) -> str:
|
||||||
|
value = os.getenv(name)
|
||||||
|
if value is None or not str(value).strip():
|
||||||
|
return default
|
||||||
|
return str(value).strip()
|
||||||
|
|
||||||
|
|
||||||
def _initial_greeting_segments() -> tuple[tuple[str, str | None], ...]:
|
def _initial_greeting_segments() -> tuple[tuple[str, str | None], ...]:
|
||||||
raw = str(os.getenv("REALTIME_VOICE_INITIAL_GREETING_TEXT", "")).strip()
|
raw = str(os.getenv("REALTIME_VOICE_INITIAL_GREETING_TEXT", "")).strip()
|
||||||
if raw and _single_initial_greeting_enabled():
|
if raw and _single_initial_greeting_enabled():
|
||||||
language = str(os.getenv("REALTIME_VOICE_INITIAL_GREETING_LANGUAGE_CODE", "")).strip() or None
|
language = str(os.getenv("REALTIME_VOICE_INITIAL_GREETING_LANGUAGE_CODE", "")).strip() or None
|
||||||
return ((raw, language),)
|
return ((raw, language),)
|
||||||
|
|
||||||
ru_text = (
|
ru_text = _env_text_or_default(
|
||||||
str(os.getenv("REALTIME_VOICE_INITIAL_GREETING_RU_TEXT") or "").strip()
|
"REALTIME_VOICE_INITIAL_GREETING_RU_TEXT",
|
||||||
or (
|
(
|
||||||
"Здравствуйте! Меня зовут Айнур, я эй-ай-ассистент компании ДиджиОпс. "
|
"Здравствуйте! Меня зовут Айнур, я эй-ай-ассистент компании ДиджиОпс. "
|
||||||
"Как я могу к вам обращаться и на каком языке вам удобнее продолжить: на русском или на казахском?"
|
"Как я могу к вам обращаться и на каком языке вам удобнее продолжить: на русском или на казахском?"
|
||||||
)
|
),
|
||||||
)
|
)
|
||||||
kk_text = (
|
kk_text = _env_text_or_default(
|
||||||
str(os.getenv("REALTIME_VOICE_INITIAL_GREETING_KK_TEXT") or "").strip()
|
"REALTIME_VOICE_INITIAL_GREETING_KK_TEXT",
|
||||||
or (
|
(
|
||||||
"Сәлеметсіз бе! Менің атым Айнұр, мен ДиджиОпс компаниясының эй-ай ассистентімін. "
|
"Сәлеметсіз бе! Менің атым Айнұр, мен ДиджиОпс компаниясының эй-ай ассистентімін. "
|
||||||
"Сізге қалай жүгінсем болады және қай тілде сөйлескен ыңғайлы: орысша ма, қазақша ма?"
|
"Сізге қалай жүгінсем болады және қай тілде сөйлескен ыңғайлы: орысша ма, қазақша ма?"
|
||||||
)
|
),
|
||||||
)
|
)
|
||||||
segments: list[tuple[str, str | None]] = []
|
segments: list[tuple[str, str | None]] = []
|
||||||
if ru_text:
|
if ru_text:
|
||||||
@@ -213,12 +220,32 @@ class RealtimeVoiceService:
|
|||||||
len(greeting_text),
|
len(greeting_text),
|
||||||
greeting_text[:120],
|
greeting_text[:120],
|
||||||
)
|
)
|
||||||
clip = await self._filler_audio.synthesize_segments(self._tts, greeting_segments)
|
total_bytes = 0
|
||||||
if not clip:
|
for index, (segment_text, language_code) in enumerate(greeting_segments, start=1):
|
||||||
LOGGER.warning("initial greeting audio preload produced empty clip")
|
segment_clip = await self._filler_audio.synthesize_segments(
|
||||||
|
self._tts,
|
||||||
|
((segment_text, language_code),),
|
||||||
|
)
|
||||||
|
if not segment_clip:
|
||||||
|
LOGGER.warning(
|
||||||
|
"initial greeting segment preload produced empty clip: index=%s language=%s",
|
||||||
|
index,
|
||||||
|
language_code,
|
||||||
|
)
|
||||||
|
continue
|
||||||
|
clip_key = f"initial_greeting_{index}_{language_code or 'default'}"
|
||||||
|
self._filler_audio.add_clip(clip_key, segment_clip)
|
||||||
|
total_bytes += len(segment_clip)
|
||||||
|
LOGGER.info(
|
||||||
|
"initial greeting segment preload done: key=%s language=%s bytes=%s",
|
||||||
|
clip_key,
|
||||||
|
language_code,
|
||||||
|
len(segment_clip),
|
||||||
|
)
|
||||||
|
if total_bytes <= 0:
|
||||||
|
LOGGER.warning("initial greeting audio preload produced no segment clips")
|
||||||
return
|
return
|
||||||
self._filler_audio.add_clip("initial_greeting", clip)
|
LOGGER.info("initial greeting audio preload done: bytes=%s", total_bytes)
|
||||||
LOGGER.info("initial greeting audio preload done: bytes=%s", len(clip))
|
|
||||||
except Exception:
|
except Exception:
|
||||||
LOGGER.exception("failed to preload initial greeting audio")
|
LOGGER.exception("failed to preload initial greeting audio")
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user