"""음성 캐스케이드 — OpenAI STT(전사) + TTS(멀티보이스) 어댑터. MASTERPLAN '음성 필수'(한신대 요구) / DESIGN_CONCEPT §5.2(음성 오브 4상태) / §4.3(립싱크 RMS): STT : OpenAI /v1/audio/transcriptions (gpt-4o-transcribe | whisper-1). 학습자 음성 → 텍스트. TTS : OpenAI /v1/audio/speech (gpt-4o-mini-tts | tts-1). 내담자 텍스트 → 음성(페르소나 voice). 설계 원칙(이 모듈의 경계): - 순수 어댑터: httpx 로 OpenAI 음성 엔드포인트만 호출한다. 상담 로직(orchestrator)·상태머신은 호출부(routes/voice.py)가 조립한다. 여기는 "오디오↔텍스트" 변환 + voice preset 매핑만. - API 키 없으면 명확히 degraded: is_available()=False, 호출 시 VoiceUnavailable. 절대 크래시·무한대기 금지(라우트가 503/close 로 변환). - 립싱크 힌트: TTS 오디오 청크를 흘리며 RMS(진폭) 힌트를 같이 산출(설계 §4.3 — viseme 정밀 매칭 안 함, RMS 1채널). PCM 디코딩 의존성 없이 바이트 에너지 근사로 임시 RMS 추정. 페르소나 voice preset(persona/*.json voice.preset, 설계 §4.6) → OpenAI voice 매핑은 PRESET_TO_OPENAI_VOICE 테이블이 흡수. 새 preset 추가는 이 테이블만 손대면 된다. """ from __future__ import annotations import re from dataclasses import dataclass from pathlib import Path from typing import AsyncIterator, Optional import httpx from ..config import settings # ════════════════════════════════════════════════════════════════════════════ # OpenAI 음성 엔드포인트/모델 상수 # ════════════════════════════════════════════════════════════════════════════ OPENAI_BASE_URL = "https://api.openai.com/v1" STT_ENDPOINT = "/audio/transcriptions" TTS_ENDPOINT = "/audio/speech" # STT 모델: gpt-4o-transcribe(고품질) — 미가용 폴백은 whisper-1. STT_MODEL = "gpt-4o-transcribe" STT_MODEL_FALLBACK = "whisper-1" # TTS 모델: gpt-4o-mini-tts(저지연·표현력) — 폴백 tts-1. TTS_MODEL = "gpt-4o-mini-tts" TTS_MODEL_FALLBACK = "tts-1" # 전사 언어 힌트(상담은 한국어). OpenAI 는 ISO-639-1. STT_LANGUAGE = "ko" # TTS 출력 포맷: 브라우저 MediaSource/