
    E|jA                        d dl Z d dlZd dlZd dlZd dlZd dlZd dlmZ d dlm	Z	 d dl
mZmZ d dlmZ  ej                  e      ZdZdZdZd	Zd
ZdddddZ ej0                         ZdZdZdZdZdZdZdZ dZ! eejD                        Z# G d d      Z$efde%de&de%fdZ'efde%de(de(de)d e&de)fd!Z*d.de%de(de(d#e(de+e(e(f   f
d$Z,d"efde%de(de(d#e(d%e&de+e-e)   e(f   fd&Z.d.d'e(d(e(de(fd)Z/d'e(d*e(de(fd+Z0d'e(d,e(de%fd-Z1y)/    N)AsyncSarvamAI).AsyncSpeechToTextRealtimeStreamingSocketClient)RealtimeAudioInputRealtimeFlush)settingsz$https://api.sarvam.ai/speech-to-textz+https://api.sarvam.ai/speech-to-text/job/v1zhttps://api.sarvam.ai/translatez$https://api.sarvam.ai/text-to-speech)te-INhi-INta-INen-INzbn-INzgu-INzkn-INzml-INzmr-INzpa-INzod-INanushkamanishavidya)r   r	   r
   r   <   g      ?      i>  )g      ?g       @g      @)api_subscription_keyc                   T    e Zd ZdZdefdedefdZddZd	e	ddfd
Z
ddZd ZddZy)SarvamStreamSessionuE  One persistent Sarvam speech-to-text-realtime WebSocket connection (saaras:v3-realtime),
    carrying one conversation-mode session's live audio. Unlike the batch path above, this
    endpoint does not support diarization — every entry comes back as a single speaker
    stream, so callers treat this as speakerId "0" throughout.

    Runs with endpointing="vad" (the default) so Sarvam's own VAD marks utterance
    boundaries — flush() and speech_start/speech_end are no-ops in that mode; they only
    take effect under endpointing="manual", which this session does not use.r   languagesample_ratec                 <    || _         || _        d | _        d | _        y N)	_language_sample_rate_ctx_ws)selfr   r   s      8/var/www/html/ai-nims-dev/backend/app/services/sarvam.py__init__zSarvamStreamSession.__init__F   s!    !' 	JN    returnNc                    K   t         j                  j                  | j                  ddt	        | j
                        dd      | _        | j                  j                          d {   | _        y 7 
w)Nzsaaras:v3-realtime	translatelinear16vad)language_codemodelmoder   encodingendpointing)	_sarvam_client!speech_to_text_realtime_streamingconnectr   strr   r   
__aenter__r   r   s    r   r-   zSarvamStreamSession.connectO   sa     "DDLL..&D--. M 
	 --///s   A%A4'A2(A4	pcm_bytesc                    K   | j                   t        d      | j                   j                  t        t	        j
                  |      j                  d                   d {    y 7 w)Nz6SarvamStreamSession.send_audio called before connect()ascii)audio)r   RuntimeErrorsend_realtime_audio_inputr   base64	b64encodedecode)r   r1   s     r   
send_audiozSarvamStreamSession.send_audioZ   sV     88WXXhh00V%5%5i%@%G%G%PQ
 	
 	
s   A"A,$A*%A,c                    K   | j                   ,| j                   j                  t                      d {    y y 7 wr   )r   send_realtime_flushr   r0   s    r   flushzSarvamStreamSession.flusha   s4      88((..}???  ?s   3><>c                   K   | j                   t        d      | j                   2 3 d {   }|j                          7 6 y w)Nz2SarvamStreamSession.events called before connect())r   r5   dict)r   responses     r   eventszSarvamStreamSession.eventsh   sC     88STT"hh 	" 	"(--/!	"hs%   $AAAAAAAc                    K   | j                   4| j                   j                  d d d        d {    d | _         d | _        y y 7 wr   )r   	__aexit__r   r0   s    r   closezSarvamStreamSession.closen   sB     99 ))%%dD$777DIDH !7s   -AAA)r!   N)__name__
__module____qualname____doc__STREAM_SAMPLE_RATEr.   intr   r-   bytesr:   r=   rA   rD    r    r   r   r   <   sN    P (/CU O OS O	0
% 
D 
@"r    r   r1   r   r!   c                     d}d}||z  |z  dz  }||z  dz  }t        j                  dddt        |       z   dddd|||||d	t        |             }|| z   S )
u   Wraps raw 16-bit mono PCM in a WAV header — used to submit audio buffered during a
    Sarvam streaming outage through the batch STT path above, which expects a file.         z<4sI4s4sIHHIIHH4sIs   RIFF$   s   WAVEs   fmt s   data)structpacklen)r1   r   channelsbits_per_sample	byte_rateblock_alignheaders          r   pcm_to_wav_bytesrZ   u   sy     HOh&8A=I_,1K[[c)n$gQ+y+Y	F Ir    audio_bytesfilename	mime_typejob_parametersmax_poll_attemptsc                   K   t         j                  dd}t        j                  t        |d|i       d{   }t
        j                  d|j                  |j                  dd        |j                          |j                         }|j                  d      xs |j                  d	      }|st        d
|       t
        j                  d|       t        j                  t         d|||gd       d{   }	t
        j                  d|	j                  |	j                  dd        |	j                          |	j                         }
|
j                  d|
      }|j                  |      xs# t        t        |j                               d      }|st        d| d|
       t!        |t"              r|d   n|}|j%                  d      d   j'                         }t        j)                  || |ddd       d{   }t
        j                  d|j                         |j                          t        j                  t         d| d|i        d{   }t
        j                  d|j                  |j                  dd        |j                          i }d}t+        |      D ]  }t-        j.                  t0               d{    t        j                  t         d| ddt         j                  i        d{   }|j                          |j                         }|j                  d!|j                  d"d#            }|d$z  dk(  rt
        j                  d%||       |d&v r%t
        j                  d'|t3        |      dd         nS|d(v rt        d)|       |d*v r|d+z  }|t4        k\  st        d,| d-      d}! t        d.|t0        z  d/d0|       t
        j                  d1t3        |      dd2        |j                  d3      xs |j                  d4      xs g }|rWt!        |t6              rG|d   j                  d5      xs g }|r*|d   j                  d6      xs |d   j                  d7      nd}nd}|s0|j9                  d8d+      d   }| d9}t
        j                  d:|       t        j                  t         d;|||gd       d{   }t
        j                  d<|j                  |j                  dd        |j                          |j                         }|j                  d=|      }|j                  |      xs# t        t        |j                               d      }|st        d>| d|       t!        |t"              r8|j                  d      xs |j                  d?      xs d#}|st        d@|       t3        |      }|j;                  dA      st        dB|      t        j                  |dC       d{   }t
        j                  dD|j                  |j<                  j                  dE      |j                  dd        |j                          |j                  j'                         }	 t        j>                  |      } t!        | t6              r	| r| d   ni } t
        j                  dF|t3        |       dd2        t!        | t"              r| S dGt3        |       j'                         iS 7 7 7 7 7 T7 7 B7 # t        j@                  $ r |} Y w xY ww)Huj  Runs the full Sarvam batch STT job lifecycle (create → upload → start → poll →
    download → fetch) and returns the parsed file_result (dict or list, depending on
    whether diarization was requested). Shared by transcribe_audio and
    transcribe_audio_diarized — only job_parameters, max_poll_attempts, and the caller's
    result parsing differ.application/jsonapi-subscription-keyContent-Typer^   headersjsonNzSTT step1 create %d: %si,  job_ididzNo job_id in create response: zSTT job created: %sz/upload-files)rh   fileszSTT step2 upload-files %d: %supload_urlszNo presigned upload URL for z: file_url;r   	BlockBlob)rd   zx-ms-blob-typeg      >@)contentrf   timeoutzSTT step3 upload audio %d/z/startzSTT step4 start %d: %s   z/statusrc   )rf   	job_statestatus    zSTT poll #%d state=%s)	Completed	completedSUCCESSsuccessz&STT step5 completed after %d polls: %s)FailedfailedERRORerrorzBatch STT job failed: )PendingpendingrN   z#Batch STT job stuck in Pending for u)    polls — Sarvam queue may be backloggedzBatch STT timed out after z.0fzs, last state: zSTT step6 status_data: %si  job_detailsdetailsoutputs	file_namer\   .z.jsonz4STT step6 no output filename in status, guessing: %sz/download-fileszSTT step7 download-files %d: %sdownload_urlszNo download URL for urlz-download_urls entry has no file_url/url key: httpz+presigned_download_url is not a valid URL: )rp   z*STT step8 fetch %d content-type=%s body=%szcontent-typeu#   STT job %s done — file_result: %s
transcript)!r   SARVAM_API_KEY_http_clientpostSARVAM_BATCH_BASE_URLloggerwarningstatus_codetextraise_for_statusrg   getr5   nextitervalues
isinstancer?   splitstripputrangeasynciosleepPOLL_INTERVAL_Sr.   MAX_PENDING_POLLSlistrsplit
startswithrf   loadsJSONDecodeError)!r[   r\   r]   r^   r_   rf   create_respcreate_datarh   upload_respupload_datark   	url_entrypresigned_url	base_mimeput_resp
start_respstatus_datapending_pollsattemptstatus_resprs   r   r   output_filenamebasedownload_respdownload_resultr   presigned_download_url
fetch_resprawfile_results!                                    r   _run_batch_stt_jobr      sB     (0'>'>PbcG %))/ *  K
 NN,k.E.E{GWGWX\Y\G]^  """$K__X&?+//$*?F;K=IJJ
NN(&1 %)) 
!/(4 *  K
 NN2K4K4K[M]M]^b_bMcd  """$K//-=K)ST${7I7I7K2Ld-SI9(2k]STT-7	4-HIj)iM $Q'--/I!%%!*kJ	 &  H NN.0D0DE $(( 
!6(&1 )  J
 NN+Z-C-CZ__UYVYEZ[! KM*+ mmO,,,(,,$%Qvhg6+X-D-DE - 
 
 	$$&!&&(OOK21NO	R<1NN2GYGHHNNCWcR]N^_c`cNde>>!7}EFF..QM 11"%H  XA  $B  C  CM+. 78IO8[\_7``op{o|}~~ NN.K0@#0FG//-0TKOOI4NTRTKz+t4a.$$Y/52W^'!*..5S
9SdhsA&q)!F%.M_ '++ 
!1/):; ,  M
 NN4m6O6OQ^QcQcdhehQij""$#((*O#''IM!!/2^d4@T@T@V;WY]6^I1/1B"_DUVWW)T"!*z!:!XimmE>R!XVX%!NykZ[[!$Y!,,V4HI_Hbcdd $''(>'MMJ
NN?AWAWYcYkYkYoYop~Y  BL  BQ  BQ  RV  SV  BW  X!
//


!Cjjo +t$(3k!n
NN8&#kBRSWTWBXY$[$7;elCP[L\LbLbLd=eeA  	-
N, N  s   5]\C]:\;D] \A]\A1]\ 8]\#B2]:D*]$\&%D9]\)A6]\, +A*]]]] ]#]&])],]]]]r   r   c           	         K   t        | ||dddd       d {   }|j                  dd      }|j                  d      xs |}t        j                  d||       ||fS 7 Fw)	Nunknown	saaras:v3
transcribe)r&   r'   r(   r   ru   r&   u)   STT done — detected: %s, transcript: %r)r   r   r   r   )r[   r\   r]   r   r   r   detected_langs          r   transcribe_audior     sl     *Xy#k<P K r2JOOO4@M
NN>zZ}$$s   A!AAA!num_speakersc                   K   t        | ||dddd|dt               d{   }|j                  d      xs i j                  d	      xs g }|D cg c]d  }|j                  d
      xs dj                         r?t	        |j                  dd            |j                  d
      xs dj                         df }}|j                  d      xs |}	t
        j                  d|	|       ||	fS 7 c c}w w)u  Same batch pipeline as transcribe_audio, but with Sarvam speaker diarization enabled —
    used by free-conversation intake mode, where one audio segment can contain more than one
    speaker's turn (doctor + patient, or patient + an accompanying family member). Returns a
    list of {"speakerId": ..., "text": ...} entries in chronological order, plus the detected
    language for the segment as a whole (diarization does not give a language per entry).r   r   r   T)r&   r'   r(   with_diarizationr   )r_   Ndiarized_transcriptentriesr   ru   
speaker_id)	speakerIdr   r&   u/   Diarized STT done — detected: %s, entries: %r)r   DIARIZED_MAX_POLL_ATTEMPTSr   r   r.   r   r   )
r[   r\   r]   r   r   r   raw_entrieser   r   s
             r   transcribe_audio_diarizedr     s      +Xy&l $l	
 5 K ??#89?RDDYOUSUK EE,%2,,. !%%b12QUU<=P=VTV<]<]<_`G 
  OOO4@M
NNDmU\]M!!!s"   C3C,0C3A)C.:3C3.C3r   source_language_codec                 D  K   | j                         s| S t        j                  t        t        j
                  dd| |dddddd	       d {   }|j                          |j                         }|j                  d
|       }t        j                  d|| |       |S 7 Pw)Nra   rb   r   Femaleformal	mayura:v1Finputr   target_language_codespeaker_genderr(   r'   enable_preprocessingre   translated_textu   Translate %s → en: %r → %rr   r   r   SARVAM_TRANSLATE_URLr   r   r   rg   r   r   r   )r   r   r@   data
translateds        r   translate_to_englishr   :  s     ::<!&&$,$;$;.

 $8$+& $)
 '  H  ==?D+T2J
NN35I4Q[\)s   AB BAB r   c                 N  K   | j                         r|dk(  r| S t        j                  t        t        j
                  dd| d|ddddd	       d {   }|j                          |j                         }|j                  d
|       }t        j                  d|| |       |S 7 Pw)Nr   ra   rb   r   r   r   Fr   re   r   u   Translate en → %s: %r → %rr   )r   r   r@   r   r   s        r   translate_to_languager   T  s     ::</7:!&&$,$;$;.

 $+$8& $)
 '  H  ==?D+T2J
NN35I4Q[\)s   AB%B#AB%r&   c           	        K   t         j                  |d      }t        j                  t        t
        j                  dd| g||ddd       d {   }|j                  dk7  r+t        j                  d	|j                  |j                         |j                          |j                         }|d
   d   }t        j                  |      S 7 {w)Nr   ra   rb   z	bulbul:v2)inputsr   speakerr'   g      .@)rf   rg   rp   rr   zSarvam TTS error %d: %saudiosr   )_LANGUAGE_SPEAKERr   r   r   SARVAM_TTS_URLr   r   r   r   r~   r   r   rg   r7   	b64decode)r   r&   r   r@   r   	audio_b64s         r   text_to_speechr   n  s     ##M9=G!&&$,$;$;.

 f$1 	
  '  H s".0D0DhmmT==?DXq!II&&'s   ACCA<C)r   )2r   r7   rg   loggingrR   httpxsarvamair   8sarvamai.speech_to_text_realtime_streaming.socket_clientr   sarvamai.typesr   r   app.core.configr   	getLoggerrE   r   SARVAM_STT_URLr   r   r   SUPPORTED_LANGUAGESr   AsyncClientr   MAX_POLL_ATTEMPTSr   r   r   CONVERSATION_NUM_SPEAKERSrI   STREAM_RECONNECT_ATTEMPTSSTREAM_RECONNECT_BACKOFF_Sr   r+   r   rK   rJ   rZ   r.   r?   r   tupler   r   r   r   r   r   rL   r    r   <module>r      s         " s < $			8	$7E 8 7  	  !u  "   ! 
    ,  H4K4KL6 6r ;M  C QV $ /LfLf"%Lf25LfGKLfLf 
Lf^% % % %X[ %jopsuxpxjy % HOd}"""%"25"AD"^a"
4:s?"6S  RU 4c   4's '3 '5 'r    