1+ from dataclasses import dataclass
12from fastapi import APIRouter , Depends , FastAPI , HTTPException , UploadFile , File , Request
23from fastapi .concurrency import run_in_threadpool
34from fastapi .responses import StreamingResponse
1112import sys
1213import shutil
1314import tempfile
14- from deepgram import DeepgramClient , SpeakOptions
15- from deepgram .clients . common . v1 . errors import DeepgramApiError
15+ from deepgram import DeepgramClient
16+ from deepgram .core . api_error import ApiError
1617from langchain_text_splitters import RecursiveCharacterTextSplitter
1718
1819
129130 "aura-zeus-en" ,
130131]
131132
133+
134+ @dataclass (slots = True )
135+ class TTSOptions :
136+ model : str | None = None
137+ sample_rate : int | None = None
138+ bit_rate : int | None = None
139+ encoding : str | None = None
140+ container : str | None = None
141+ callback : str | None = None
142+ callback_method : str | None = None
143+ mip_opt_out : bool | None = None
144+ tag : str | list [str ] | None = None
145+
146+ def to_dict (self ) -> dict :
147+ return {
148+ key : value
149+ for key , value in {
150+ "model" : self .model ,
151+ "sample_rate" : self .sample_rate ,
152+ "bit_rate" : self .bit_rate ,
153+ "encoding" : self .encoding ,
154+ "container" : self .container ,
155+ "callback" : self .callback ,
156+ "callback_method" : self .callback_method ,
157+ "mip_opt_out" : self .mip_opt_out ,
158+ "tag" : self .tag ,
159+ }.items ()
160+ if value is not None
161+ }
162+
132163def _iter_bytes (data : bytes , * , chunk_size : int ):
133164 for i in range (0 , len (data ), chunk_size ):
134165 yield data [i : i + chunk_size ]
@@ -186,17 +217,19 @@ async def _concat_and_boost_mp3_ffmpeg(chunks: list[bytes], gain: float = 8.0) -
186217 return await run_in_threadpool (_concat_and_boost_mp3_ffmpeg_sync , chunks , gain )
187218
188219
189- def _tts_chunk_to_bytes_sync (text : str , options : SpeakOptions ) -> bytes :
190- """Synthesize a single text chunk via Deepgram SDK and return audio bytes."""
191- deepgram = DeepgramClient (DEEPGRAM_API_KEY )
192- timeout = httpx .Timeout (connect = 10.0 , read = 300.0 , write = 300.0 , pool = 10.0 )
193- response = deepgram .speak .rest .v ("1" ).stream_memory (
194- {"text" : text }, options , timeout = timeout
220+ def _tts_chunk_to_bytes_sync (text : str , options : TTSOptions ) -> bytes :
221+ """Synthesize a single text chunk via Deepgram SDK v6 and return audio bytes."""
222+ deepgram = DeepgramClient (api_key = DEEPGRAM_API_KEY , timeout = 300.0 )
223+ return b"" .join (
224+ deepgram .speak .v1 .audio .generate (
225+ text = text ,
226+ request_options = {"timeout_in_seconds" : 300 , "chunk_size" : 65536 },
227+ ** options .to_dict (),
228+ )
195229 )
196- return response .stream_memory .read ()
197230
198231
199- def _require_api_token_if_configured (request : Request ) -> None :
232+ async def _require_api_token_if_configured (request : Request ) -> None :
200233 configured_token = (os .getenv ("API_TOKEN" ) or "" ).strip ()
201234 if not configured_token :
202235 return
@@ -317,7 +350,7 @@ async def get_speech(request: Request):
317350 raise HTTPException (status_code = 400 , detail = f"No TTS model available for language: { language } " )
318351 input_params ["model" ] = models [0 ]
319352
320- # Build SpeakOptions with valid TTS parameters
353+ # Build TTS options with only Deepgram-supported TTS parameters.
321354 speak_kwargs = {}
322355 if input_params .get ("model" ):
323356 speak_kwargs ["model" ] = input_params ["model" ]
@@ -326,8 +359,16 @@ async def get_speech(request: Request):
326359 if input_params .get ("bit_rate" ):
327360 speak_kwargs ["bit_rate" ] = int (input_params ["bit_rate" ])
328361 speak_kwargs ["encoding" ] = "mp3"
329-
330- options = SpeakOptions (** speak_kwargs )
362+ if input_params .get ("callback" ):
363+ speak_kwargs ["callback" ] = input_params ["callback" ]
364+ if input_params .get ("callback_method" ):
365+ speak_kwargs ["callback_method" ] = input_params ["callback_method" ]
366+ if input_params .get ("mip_opt_out" ):
367+ speak_kwargs ["mip_opt_out" ] = input_params ["mip_opt_out" ].strip ().lower () in ("1" , "true" , "yes" )
368+ if input_params .get ("tag" ):
369+ speak_kwargs ["tag" ] = input_params ["tag" ]
370+
371+ options = TTSOptions (** speak_kwargs )
331372 logger .debug ("Deepgram TTS options: %s" , speak_kwargs )
332373
333374 audio_parts : list [bytes ] = []
@@ -341,12 +382,13 @@ async def get_speech(request: Request):
341382 idx , len (chunks ), len (audio_data ),
342383 )
343384 audio_parts .append (audio_data )
344- except DeepgramApiError as e :
345- status = int (e .status ) if e .status else 502
346- logger .error ("Deepgram TTS API error: status=%s message=%s" , e .status , e .message )
385+ except ApiError as e :
386+ status = int (e .status_code ) if e .status_code else 502
387+ message = e .body if isinstance (e .body , str ) else str (e .body )
388+ logger .error ("Deepgram TTS API error: status=%s message=%s" , e .status_code , message )
347389 raise HTTPException (
348390 status_code = status ,
349- detail = f"Deepgram API error: { e . message } " ,
391+ detail = f"Deepgram API error: { message } " ,
350392 )
351393 except httpx .TimeoutException :
352394 logger .warning ("Deepgram TTS request timed out" )
0 commit comments