
    ia              	          U d Z 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ZddlZddl	Z	ddl
Z
ddlZddlmZmZmZmZ  ej        e          Zd ZdefdZddlmZ defdZdee         fd	Zdefd
ZdefdZdefdZ dZ!dZ"dZ#dZ$dZ%dZ&ej'        (                     ej)                    d          Z*d/de+de,de+ddfdZ- G d d          Z. G d d          Z/de/e.z  fdZ0h dZ1 ej2        d ej3        !          Z4d"edefd#Z5d0d$ed%ee         deeef         fd&Z6da7eej8                 e9d'<    e	j:                    Z;d1d(Z<d)edefd*Z=deeef         fd+Z>d2d-e+de+fd.Z?dS )3aV  Voice Mode -- Push-to-talk audio recording and playback for the CLI.

Provides audio capture via sounddevice, WAV encoding via stdlib wave,
STT dispatch via tools.transcription_tools, and TTS playback via
sounddevice or system audio players.

Dependencies (optional):
    pip install sounddevice numpy
    or: pip install hermes-agent[voice]
    N)AnyDictListOptionalc                      ddl } ddl}| |fS )zLazy-import sounddevice and numpy.  Returns (sd, np).

    Raises ImportError or OSError if the libraries are not available
    (e.g. PortAudio missing on headless servers).
    r   N)sounddevicenumpy)sdnps     5/home/ubuntu/.hermes/hermes-agent/tools/voice_mode.py_import_audior       s%     r6M    returnc                  T    	 t                       dS # t          t          f$ r Y dS w xY w)z/Return True if audio libraries can be imported.TF)r   ImportErrorOSError r   r   _audio_availabler   +   s<    t!   uus    '')	is_termuxc                  &    t                      rdS dS )NzGpkg install python-numpy portaudio && python -m pip install sounddevicezpip install sounddevice numpy)_is_termux_environmentr   r   r   _voice_capture_install_hintr   7   s     YXX**r   c                  J    t                      sd S t          j        d          S )Nztermux-microphone-record)r   shutilwhichr   r   r   _termux_microphone_commandr   =   s&    !## t<2333r   c                      t                      sdS 	 t          j        g ddddd          } d| j        pdv S # t          $ r Y dS w xY w)NF)pmlistpackageszcom.termux.apiT   capture_outputtexttimeoutcheckzpackage:com.termux.api )r   
subprocessrunstdout	Exception)results    r   _termux_api_app_installedr-   D   s{    !## u
888
 
 
 (FM,?R@@   uus   %8 
AAc                  >    t                      d uot                      S N)r   r-   r   r   r   _termux_voice_capture_availabler0   T   s    %''t3S8Q8S8SSr   c                     g } g }t                      }t                      }t          |o|          }t          d dD                       r|                     d           ddlm}  |            r|                     d           	 t          dd          5 }d	|                                	                                v rJt          j                            d
          r|                    d           n|                     d           ddd           n# 1 swxY w Y   n# t          t          t          f$ r Y nw xY w	 t!                      \  }}	 |                                }	|	s-|r|                    d           n|                     d           nr# t$          $ re t          j                            d
          r|                    d           n-|r|                    d           n|                     d           Y nw xY wn# t&          $ rZ |r|                    d           n?|r|s|                     d           n%|                     dt)                       d           Y nzt          $ rn |r|                    d           nS|r|s|                     d           n9t+                      r|                     d           n|                     d           Y nw xY w|  | |dS )zDetect if the current environment supports audio I/O.

    Returns dict with 'available' (bool), 'warnings' (list of hard-fail
    reasons that block voice mode), and 'notices' (list of informational
    messages that do NOT block voice mode).
    c              3   T   K   | ]#}t           j                            |          V  $d S r/   )osenvironget).0vs     r   	<genexpr>z+detect_audio_environment.<locals>.<genexpr>f   s0      
R
R2:>>!
R
R
R
R
R
Rr   )
SSH_CLIENTSSH_TTYSSH_CONNECTIONz.Running over SSH -- no audio devices availabler   )is_containerz3Running inside Docker container -- no audio devicesz/proc/versionr	microsoftPULSE_SERVERz%Running in WSL with PulseAudio bridgezRunning in WSL -- audio requires PulseAudio bridge.
  1. Set PULSE_SERVER=unix:/mnt/wslg/PulseServer
  2. Create ~/.asoundrc pointing ALSA at PulseAudio
  3. Verify with: arecord -d 3 /tmp/test.wav && aplay /tmp/test.wavNzMNo PortAudio devices detected, but Termux:API microphone capture is availablez&No audio input/output devices detectedz?Audio device query failed but PULSE_SERVER is set -- continuingzMPortAudio device query failed, but Termux:API microphone capture is availablez6Audio subsystem error (PortAudio cannot query devices)zDTermux:API microphone recording available (sounddevice not required)zkTermux:API Android app is not installed. Install/update the Termux:API app to use termux-microphone-record.zAudio libraries not installed ()zBTermux:API microphone recording available (PortAudio not required)zmPortAudio system library not found -- install it first:
  Termux: pkg install portaudio
Then retry /voice on.zPortAudio system library not found -- install it first:
  Linux:  sudo apt-get install libportaudio2
  macOS:  brew install portaudio
Then retry /voice on.)	availablewarningsnotices)r   r-   boolanyappendhermes_constantsr<   openreadlowerr3   r4   r5   FileNotFoundErrorPermissionErrorr   r   query_devicesr+   r   r   r   )
rB   rC   termux_mic_cmdtermux_app_installedtermux_capturer<   fr
   _devicess
             r   detect_audio_environmentrT   X   s    HG/11N466.A-ABBN 
R
R&Q
R
R
RRR JHIII .-----|~~ OMNNN/3'' 
	1affhhnn....:>>.11 NN#JKKKKOO^  
	 
	 
	 
	 
	 
	 
	 
	 
	 
	 
	 
	 
	 
	 
	 8   .A	Z&&((G N! NNN#rssssOO$LMMM 	Z 	Z 	Z z~~n-- Z`aaaa Znoooo XYYY	Z  ` ` ` 	`NNabbbb 	`$8 	`OO}    OO^>Y>[>[^^^___    	NN_```` 	$8 	OO}    $%% 	OO(    OO(  , "\  sw   D! A3D	D! DD! DD! !D;:D;?H AF H A,HH HH A!K"+A4K"!K"i>     int16            @hermes_voicep  Q?	frequencydurationcountc           	      z   	 t                      \  }}n# t          t          f$ r Y dS w xY w	 d}t          t          |z            }t          t          |z            }g }t          |          D ]!}	|                    d||d          }
|                    d|j        z  | z  |
z            }t          t          t          dz            |dz            }|d|xx         |                    dd	|          z  cc<   || dxx         |                    d	d|          z  cc<   |
                    |d
z  dz                      |j                             |	|d	z
  k     r/|
                    |                    ||j                             #|                    |          }|                    |t                     t!          j                    dz   }|                                r|                                j        rot!          j                    |k     rXt!          j        d           |                                r0|                                j        rt!          j                    |k     X|                                 dS # t,          $ r&}t.                              d|           Y d}~dS d}~ww xY w)zPlay a short beep tone using numpy + sounddevice.

    Args:
        frequency: Tone frequency in Hz (default 880 = A5).
        duration: Duration of each beep in seconds.
        count: Number of beeps to play (with short gap between).
    NgQ?r   F)endpointrW   {Gz?   rU   333333?i  dtype
samplerate       @zBeep playback failed: %s)r   r   r   intSAMPLE_RATErangelinspacesinpiminrF   astyperV   zerosconcatenateplaytime	monotonic
get_streamactivesleepstopr+   loggerdebug)r]   r^   r_   r
   r   gapsamples_per_beepsamples_per_gappartsittonefade_lenaudiodeadlinees                   r   	play_beepr      s   BB!   4{X566kC/00u 		H 		HAAx)9EJJA66!be)i/!344D3{T1224D4IJJH(OOOr{{1a:::OOO(Aq( ; ;;LL$*u,44RX>>???519}}RXXoRXXFFGGGu%%
+... >##c)mmoo 	"--//"8 	T^=M=MPX=X=XJt mmoo 	"--//"8 	T^=M=MPX=X=X
					 4 4 4/3333333334s$    ))IJ
 

J:J55J:c                       e Zd ZdZdZddZedefd            Zede	fd            Z
edefd            Zddd	Zdd
Zdee         fdZddZddZdS )TermuxAudioRecorderzBRecorder backend that uses Termux:API microphone capture commands.Fr   Nc                 n    t          j                    | _        d| _        d| _        d | _        d| _        d S )NF        r   )	threadingLock_lock
_recording_start_time_recording_path_current_rmsselfs    r   __init__zTermuxAudioRecorder.__init__   s6    ^%%
.2r   c                     | j         S r/   r   r   s    r   is_recordingz TermuxAudioRecorder.is_recording  s
    r   c                 J    | j         sdS t          j                    | j        z
  S Nr   r   ru   rv   r   r   s    r   elapsed_secondsz#TermuxAudioRecorder.elapsed_seconds  '     	3~$"222r   c                     | j         S r/   r   r   s    r   current_rmszTermuxAudioRecorder.current_rms  s      r   c                    ~t                      }|st          d          t                      st          d          | j        5  | j        r	 d d d            d S t          j        t          d           t          j	        d          }t
          j
                            t          d| d          | _        d d d            n# 1 swxY w Y   |d| j        d	d
dddt          t                    dt          t                    g}	 t!          j        |dddd           ny# t           j        $ rG}|j        p|j        pt          |                                          }t          d|           |d }~wt,          $ r}t          d|           |d }~ww xY w| j        5  t          j                    | _        d| _        d| _        d d d            n# 1 swxY w Y   t4                              d           d S )NzTermux voice capture requires the termux-api package and app.
Install with: pkg install termux-api
Then install/update the Termux:API Android app.zrTermux voice capture requires the Termux:API Android app.
Install/update the Termux:API app, then retry /voice on.Texist_ok%Y%m%d_%H%M%S
recording_z.aacz-fz-l0z-eaacz-rz-c   r"   z Termux microphone start failed: r   zTermux voice recording started)r   RuntimeErrorr-   r   r   r3   makedirs	_TEMP_DIRru   strftimepathjoinr   strrk   CHANNELSr(   r)   CalledProcessErrorstderrr*   stripr+   rv   r   r   r{   info)r   on_silence_stopmic_cmd	timestampcommandr   detailss          r   startzTermuxAudioRecorder.start  s   ,.. 	B  
 )** 	K  
 Z 	Y 	Y 	Y 	Y 	Y 	Y 	Y 	Y 	Y 	Y K	D1111o66I#%7<<	;W	;W;W;W#X#XD 	Y 	Y 	Y 	Y 	Y 	Y 	Y 	Y 	Y 	Y 	Y 	Y 	Y 	Y 	Y $&#%#k""#h--
	NN74dBVZ[[[[[, 	T 	T 	Tx5185s1vv<<>>GK'KKLLRSS 	N 	N 	NE!EEFFAM	N Z 	" 	"#~//D"DO !D	" 	" 	" 	" 	" 	" 	" 	" 	" 	" 	" 	" 	" 	" 	" 	455555sP   	CACCCD   F/AE11F>FF 'GGGc                 `    t                      }|sd S t          j        |dgdddd           d S )N-qTr   Fr"   )r   r(   r)   )r   r   s     r   _stop_termux_recordingz*TermuxAudioRecorder._stop_termux_recording;  sA    ,.. 	Ft$PRZ_``````r   c                 r   | j         5  | j        s	 d d d            d S d| _        | j        }d | _        | j        }d| _        d d d            n# 1 swxY w Y   |                                  |rt          j                            |          sd S t          j
                    |z
  dk     r(	 t          j        |           n# t          $ r Y nw xY wd S t          j                            |          dk    r(	 t          j        |           n# t          $ r Y nw xY wd S t                              d|           |S )NFr   rd   z"Termux voice recording stopped: %s)r   r   r   r   r   r   r3   r   isfileru   rv   unlinkr   getsizer{   r   )r   r   
started_ats      r   rz   zTermuxAudioRecorder.stopA  s   Z 	" 	"? 	" 	" 	" 	" 	" 	" 	" 	" $DO'D#'D )J !D	" 	" 	" 	" 	" 	" 	" 	" 	" 	" 	" 	" 	" 	" 	" 	##%%% 	27>>$// 	4>j(3..	$   47??4  A%%	$   48$???s:   	A#AAA*B? ?
CC5D
 

DDc                    | j         5  | j        }d| _        d | _        d| _        d d d            n# 1 swxY w Y   	 |                                  n# t
          $ r Y nw xY w|rEt          j                            |          r&	 t          j	        |           n# t          $ r Y nw xY wt                              d           d S )NFr   z Termux voice recording cancelled)r   r   r   r   r   r+   r3   r   r   r   r   r{   r   )r   r   s     r   cancelzTermuxAudioRecorder.cancel]  s"   Z 	" 	"'D#DO#'D  !D		" 	" 	" 	" 	" 	" 	" 	" 	" 	" 	" 	" 	" 	" 	"
	'')))) 	 	 	D	 	BGNN4(( 		$   677777s/   155A 
AAB 
B&%B&c                 .    |                                   d S r/   )r   r   s    r   shutdownzTermuxAudioRecorder.shutdownn  s    r   r   Nr/   )__name__
__module____qualname____doc__supports_silence_autostopr   propertyrD   r   floatr   rj   r   r   r   r   r   rz   r   r   r   r   r   r   r      s"       LL %    d    X 3 3 3 3 X3
 !S ! ! ! X!*6 *6 *6 *6 *6Xa a a ahsm    88 8 8 8"     r   r   c                       e Zd ZdZdZddZedefd            Zede	fd            Z
edefd            Zdd	Zddd
ZddeddfdZdee         fdZddZddZedefd            ZdS )AudioRecordera  Thread-safe audio recorder using sounddevice.InputStream.

    Usage::

        recorder = AudioRecorder()
        recorder.start(on_silence_stop=my_callback)
        # ... user speaks ...
        wav_path = recorder.stop()   # returns path to WAV file
        # or
        recorder.cancel()            # discard without saving

    If ``on_silence_stop`` is provided, recording automatically stops when
    the user is silent for ``silence_duration`` seconds and calls the callback.
    Tr   Nc                 F   t          j                    | _        d | _        g | _        d| _        d| _        d| _        d| _        d| _	        d| _
        d| _        d| _        d| _        d| _        d | _        t           | _        t$          | _        d| _        d| _        d| _        d S )NFr   rd   g      .@r   )r   r   r   _stream_framesr   r   _has_spoken_speech_start
_dip_start_min_speech_duration_max_dip_tolerance_silence_start_resume_start_resume_dip_start_on_silence_stopSILENCE_RMS_THRESHOLD_silence_thresholdSILENCE_DURATION_SECONDS_silence_duration	_max_wait	_peak_rmsr   r   s    r   r   zAudioRecorder.__init__  s    ^%%
 "$"% $'!$+.!),%($'(+ $'<(@ $!"r   c                 J    | j         sdS t          j                    | j        z
  S r   r   r   s    r   r   zAudioRecorder.elapsed_seconds  r   r   c                     | j         S )zBCurrent audio input RMS level (0-32767). Updated each audio chunk.r   r   s    r   r   zAudioRecorder.current_rms  s       r   c                     | j         S )z,Whether audio recording is currently active.r   r   s    r   r   zAudioRecorder.is_recording  s     r   c                 t     j         dS t                      \  } fd}d}	 |                    t          t          t
          |          }|                                 nN# t          $ rA}|&	 |                                 n# t          $ r Y nw xY wt          d| d          |d}~ww xY w| _         dS )a_  Create the audio InputStream once and keep it alive.

        The stream stays open for the lifetime of the recorder.  Between
        recordings the callback simply discards audio chunks (``_recording``
        is ``False``).  This avoids the CoreAudio bug where closing and
        re-opening an ``InputStream`` hangs indefinitely on macOS.
        Nc           	      n  	 |rt                               d|           j        sd S j                            |                                            t          
                    
                    | 	                    
j
                  dz                                }|_        |j        k    r|_        j        kt          j                    }|j        z
  }|j        k    rd_        j        dk    r|_        nDj        s=|j        z
  j        k    r*d_        t                               d|j        z
             j        sd_        nd_        j        dk    r|_        n|j        z
  j        k    rd_        d_        nj        r@j        dk    r4j        dk    r|_        n|j        z
  j        k    rd_        d_        nbj        dk    rWj        dk    r|_        nD|j        z
  j        k    r1t                               d|j        z
             d_        d_        d}j        rT|j        k    rIj        dk    r|_        nj|j        z
  j        k    r"t                               d	j                   d}n4j        s-|j        k    r"t                               d
j                   d}|r`j        5  j        	d _        d d d            n# 1 swxY w Y   	r3	fd}t=          j        |d                                            d S d S d S d S )Nzsounddevice status: %srW   r   Tz(Speech confirmed (%.2fs above threshold)r   z'Speech attempt reset (dip lasted %.2fs)Fz'Silence detected (%.1fs), auto-stoppingz%No speech within %.0fs, auto-stoppingc                      	               d S # t           $ r(} t                              d| d           Y d } ~ d S d } ~ ww xY w)NzSilence callback failed: %sT)exc_info)r+   r{   error)r   cbs    r   _safe_cbzAAudioRecorder._ensure_stream.<locals>._callback.<locals>._safe_cb  sg    ^ "#, ^ ^ ^ &-JAX\ ] ] ] ] ] ] ] ] ]^s   
 
A<Atargetdaemon)!r{   r|   r   r   rF   copyrj   sqrtmeanrq   float64r   r   r   ru   rv   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r   Threadr   )indataframes	time_infostatusrmsnowelapsedshould_firer   r   r   r   s            @r   	_callbackz/AudioRecorder._ensure_stream.<locals>._callback  s    ?5v>>>? L... bggbggfmmBJ&?&?1&DEEFFGGC #DT^##!$ $0n&& 00000&)DO)S00-0**!- ?#8J2JdNg2g2g+/(%O%(4+=%=? ? ?
  + 5.1++ 25.-4414D.. 4#559RRR25D/14D.% . )A--1S8858D22 4#99T=TTT14D.58D2'!++ #--*-t.$2III%N%(4?%:< < <-0**-
 $# 't/F(F(F*c11.1++t22d6LLL$M$($:< < <&*) 'g.G.GKK G $0 0 0"&K 
O 5 5!204-5 5 5 5 5 5 5 5 5 5 5 5 5 5 5  O^ ^ ^ ^ ^
 "(FFFLLNNNNN_ 10J
O 
OO Os   K55K9<K9)rh   channelsrf   callbackz#Failed to open audio input stream: z6. Check that a microphone is connected and accessible.)
r   r   InputStreamrk   r   DTYPEr   r+   closer   )r   r
   r   streamr   r   s   `    @r   _ensure_streamzAudioRecorder._ensure_stream  s#    <#FB^	O ^	O ^	O ^	O ^	O ^	OB 	^^&!"	 $  F LLNNNN 		 		 		!LLNNNN    DGa G G G  		 s;   <A# #
B.-B)0BB)
BB)BB))B.c                 x   	 t                       n7# t          t          f$ r#}t          dt          j         d          |d}~ww xY w| j        5  | j        r	 ddd           dS g | _        t          j
                    | _        d| _        d| _        d| _        d| _        d| _        d| _        d| _        d| _        || _        ddd           n# 1 swxY w Y   |                                  | j        5  d| _        ddd           n# 1 swxY w Y   t,                              dt0          t2                     dS )	a}  Start capturing audio from the default input device.

        The underlying InputStream is created once and kept alive across
        recordings.  Subsequent calls simply reset detection state and
        toggle frame collection via ``_recording``.

        Args:
            on_silence_stop: Optional callback invoked (in a daemon thread) when
                silence is detected after speech. The callback receives no arguments.
                Use this to auto-stop recording and trigger transcription.

        Raises ``RuntimeError`` if sounddevice/numpy are not installed
        or if a recording is already in progress.
        z9Voice mode requires sounddevice and numpy.
Install with: z! -m pip install sounddevice numpyNFr   r   Tz.Voice recording started (rate=%d, channels=%d))r   r   r   r   sys
executabler   r   r   ru   rv   r   r   r   r   r   r   r   r   r   r   r  r{   r   rk   r   )r   r   r   s      r   r   zAudioRecorder.start6  s   	OOOOW% 	 	 	S!$S S S  	 Z 	4 	4 	4 	4 	4 	4 	4 	4 	4 	4 DL#~//D$D!$D!DO"%D!$D%(D"DN !D$3D!	4 	4 	4 	4 	4 	4 	4 	4 	4 	4 	4 	4 	4 	4 	4" 	Z 	# 	#"DO	# 	# 	# 	# 	# 	# 	# 	# 	# 	# 	# 	# 	# 	# 	#DkS[\\\\\s?    AA  A	C%ACCC5D		DDrY   r%   c                 N   | j         dS | j         d| _         fd}t          j        |d          }|                                 t	          d                                          |z   }|                                rtt	          d                                          |k     rO|                    d           |                                r%t	          d                                          |k     O|                                rt          	                    d|           dS dS )	zAClose the audio stream with a timeout to prevent CoreAudio hangs.Nc                  |    	                                                                      d S # t          $ r Y d S w xY wr/   )rz   r  r+   )r  s   r   	_do_closez;AudioRecorder._close_stream_with_timeout.<locals>._do_closel  sH       s   (- 
;;Tr   ru   g?r%   u:   Audio stream close timed out after %.1fs — forcing ahead)
r   r   r   r   
__import__rv   is_aliver   r{   warning)r   r%   r  r   r   r  s        @r   _close_stream_with_timeoutz(AudioRecorder._close_stream_with_timeoutd  s$   <F	 	 	 	 	 Id;;;				f%%//11G;jjll 	 z&11;;==HHFF3F jjll 	 z&11;;==HH::<< 	bNNWY`aaaaa	b 	br   c                    | j         5  | j        s	 ddd           dS d| _        d| _        | j        s	 ddd           dS t	                      \  }}|                    | j        d          }g | _        t          j                    | j        z
  }t          
                    d|t          |                     t          t          dz            }t          |          |k     r6t                              dt          |                     	 ddd           dS | j        t           k     r4t          
                    d| j        t                      	 ddd           dS |                     |          cddd           S # 1 swxY w Y   dS )	u   Stop recording and write captured audio to a WAV file.

        The underlying stream is kept alive for reuse — only frame
        collection is stopped.

        Returns:
            Path to the WAV file, or ``None`` if no audio was captured.
        NFr   )axisz+Voice recording stopped (%.1fs, %d samples)rd   z,Recording too short (%d samples), discardingz2Recording too quiet (peak RMS=%d < %d), discarding)r   r   r   r   r   rs   ru   rv   r   r{   r   lenrj   rk   r|   r   r   
_write_wav)r   rR   r   
audio_datar   min_sampless         r   rz   zAudioRecorder.stop|  s.    Z  	/  	/?  	/  	/  	/  	/  	/  	/  	/  	/ $DO !D <  	/  	/  	/  	/  	/  	/  	/  	/ "OOEAr1==JDLn&&)99GKKEwPST^P_P_``` kC/00K:,,KSQ[__]]]/ 	/  	/  	/  	/  	/  	/  	/  	/6 ~ 555P N,AC C C= 	/  	/  	/  	/  	/  	/  	/  	/@ ??:..A 	/  	/  	/  	/  	/  	/  	/  	/  	/  	/  	/  	/  	/  	/  	/  	/  	/  	/s)   	E>E>CE>7E>E>>FFc                     | j         5  d| _        g | _        d| _        d| _        ddd           n# 1 swxY w Y   t
                              d           dS )zoStop recording and discard all captured audio.

        The underlying stream is kept alive for reuse.
        FNr   zVoice recording cancelled)r   r   r   r   r   r{   r   r   s    r   r   zAudioRecorder.cancel  s    
 Z 	" 	"#DODL$(D! !D		" 	" 	" 	" 	" 	" 	" 	" 	" 	" 	" 	" 	" 	" 	"
 	/00000s   155c                     | j         5  d| _        g | _        d| _        ddd           n# 1 swxY w Y   |                                  t
                              d           dS )z<Release the audio stream.  Call when voice mode is disabled.FNzAudioRecorder shut down)r   r   r   r   r  r{   r   r   s    r   r   zAudioRecorder.shutdown  s    Z 	) 	)#DODL$(D!	) 	) 	) 	) 	) 	) 	) 	) 	) 	) 	) 	) 	) 	) 	)
 	'')))-.....s   *..c                 p   t          j        t          d           t          j        d          }t           j                            t          d| d          }t          j        |d          5 }|	                    t                     |                    t                     |                    t                     |                    |                                            ddd           n# 1 swxY w Y   t           j                            |          }t$                              d||           |S )	zTWrite numpy int16 audio data to a WAV file.

        Returns the file path.
        Tr   r   r   .wavwbNzWAV written: %s (%d bytes))r3   r   r   ru   r   r   r   waverH   setnchannelsr   setsampwidthSAMPLE_WIDTHsetframeraterk   writeframestobytesr   r{   r   )r  r   wav_pathwf	file_sizes        r   r  zAudioRecorder._write_wav  s2    	I----M/22	7<<	+G	+G+G+GHHYx&& 	1"OOH%%%OOL)))OOK(((NN:--//000		1 	1 	1 	1 	1 	1 	1 	1 	1 	1 	1 	1 	1 	1 	1 GOOH--	0(IFFFs   .A6C00C47C4r   r/   )rY   )r   r   r   r   r   r   r   r   r   rj   r   rD   r   r  r   r  r   r   rz   r   r   staticmethodr  r   r   r   r   r   u  s         !%# # # #4 3 3 3 3 X3
 !S ! ! ! X! d    XA A A AF,] ,] ,] ,] ,]\b b% b$ b b b b0)/hsm )/ )/ )/ )/V
1 
1 
1 
1/ / / / #    \  r   r   c                  V    t                      rt                      S t                      S )z=Return the best recorder backend for the current environment.)r0   r   r   r   r   r   create_audio_recorderr(    s&    &(( %"$$$??r   >   %   продолжение следует(   продолжение следует...*   ご視聴ありがとうございました,   sottotitoli creati dalla comunità amara.org5   sous-titres réalisés par la communauté d'amara.orgbye.the endthe end.	amara.org	thank you
thank you.sous-titreswww.mooji.orgplease subscribeplease subscribe.like and subscribelike and subscribe.thanks for watchingthanks for watching.thank you for watchingsubscribe to my channelthank you for watching.subscribe to my channel.untertitel von stephanie geigesbyeyouz9^(?:thank you|thanks|bye|you|ok|okay|the end|\.|\s|,|!)+$)flags
transcriptc                     |                                                                  }|sdS |                    d          t          v s	|t          v rdS t                              |          rdS dS )zBCheck if a transcript is a known Whisper hallucination on silence.Tz.!F)r   rJ   rstripWHISPER_HALLUCINATIONS_HALLUCINATION_REPEAT_REmatch)rD  cleaneds     r   is_whisper_hallucinationrK    st      &&((G t~~d555DZ9Z9Zt%%g.. t5r   r#  modelc                     ddl m}  || |          }|                    d          rJt          |                    dd                    r't                              d|d                    dddd	S |S )
a  Transcribe a WAV recording using the existing Whisper pipeline.

    Delegates to ``tools.transcription_tools.transcribe_audio()``.
    Filters out known Whisper hallucinations on silent audio.

    Args:
        wav_path: Path to the WAV file.
        model: Whisper model name (default: from config or ``whisper-1``).

    Returns:
        Dict with ``success``, ``transcript``, and optionally ``error``.
    r   )transcribe_audio)rL  successrD  r'   z"Filtered Whisper hallucination: %rT)rO  rD  filtered)tools.transcription_toolsrN  r5   rK  r{   r   )r#  rL  rN  r,   s       r   transcribe_recordingrR    s     ;:::::he444F zz) E!9&**\SU:V:V!W!W E8&:NOOOrtDDDMr   _active_playbackc                  p   t           5  t          } daddd           n# 1 swxY w Y   | rT|                                 @	 |                                  t                              d           n# t          $ r Y nw xY w	 t                      \  }}|                                 dS # t          $ r Y dS w xY w)z/Interrupt the currently playing audio (if any).NzAudio playback interrupted)	_playback_lockrS  poll	terminater{   r   r+   r   rz   )procr
   rR   s      r   stop_playbackrY  7  s    
                                   		#	NNKK45555 	 	 	D	A
					   s0   
"" .A/ /
A<;A< %B' '
B54B5	file_pathc                 T   t           j                            |           st                              d|            dS |                     d          r	 t                      \  }}t          j        | d          5 }|	                    |
                                          }|                    ||j                  }|                                }ddd           n# 1 swxY w Y   |                    ||           t          |          |z  }t!          j                    |z   dz   }|                                r|                                j        rot!          j                    |k     rXt!          j        d	           |                                r0|                                j        rt!          j                    |k     X|                                 d
S # t,          t.          f$ r Y n1t0          $ r%}	t                              d|	           Y d}	~	nd}	~	ww xY wt5          j                    }
g }|
dk    r|                    d| g           |                    ddddd| g           |
dk    r|                    dd| g           |D ]}}t;          j        |d                   }|r]	 t?          j         |t>          j!        t>          j!                  }tD          5  |a#ddd           n# 1 swxY w Y   |$                    d           tD          5  da#ddd           n# 1 swxY w Y    d
S # t>          j%        $ rn t                              d|d                    |&                                 |$                                 tD          5  da#ddd           n# 1 swxY w Y   Y #t0          $ rN}	t                              d|d         |	           tD          5  da#ddd           n# 1 swxY w Y   Y d}	~	ud}	~	ww xY wt                              d|            dS )ay  Play an audio file through the default output device.

    Strategy:
    1. WAV files via ``sounddevice.play()`` when available.
    2. System commands: ``afplay`` (macOS), ``ffplay`` (cross-platform),
       ``aplay`` (Linux ALSA).

    Playback can be interrupted by calling ``stop_playback()``.

    Returns:
        ``True`` if playback succeeded, ``False`` otherwise.
    zAudio file not found: %sFr  rbre   Nrg   ri   rb   Tzsounddevice playback failed: %sDarwinafplayffplayz-nodispz	-autoexitz	-loglevelquietLinuxaplayr   r   )r*   r   i,  r  z+System player %s timed out, killing processzSystem player %s failed: %sz No audio player available for %s)'r3   r   r   r{   r  endswithr   r  rH   
readframes
getnframes
frombufferrV   getframeratert   r  ru   rv   rw   rx   ry   rz   r   r   r+   r|   platformsystemrF   r   r   r(   PopenDEVNULLrU  rS  waitTimeoutExpiredkill)rZ  r
   r   r$  r   r  sample_rateduration_secsr   r   ri  playerscmdexerX  s                  r   play_audio_filert  K  s>    7>>)$$ 19===u &!! ?	?"__FB9d++ 0rr}}77]]6]BB
 oo//0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
 GGJ;G777  
OOk9M~''-7#=H--// !bmmoo&< !AQAQT\A\A\
4    --// !bmmoo&< !AQAQT\A\A\GGIII4W% 	 	 	D 	? 	? 	?LL:A>>>>>>>>	? _FG),---NNHik7IVWWWy1222 , ,l3q6"" 	,,!'J4FzOabbb# , ,'+$, , , , , , , , , , , , , , ,		#	&&&# , ,'+$, , , , , , , , , , , , , , ,tt, , , ,LcRSfUUU				# , ,'+$, , , , , , , , , , , , , , , , , ,:CFAFFF# , ,'+$, , , , , , , , , , , , , , ,,	,( NN5yAAA5s   &G :ACG C""G %C"&C5G H0	H9HH/2L5!K0$L50K4	4L57K4	8 L5L'L5'L+	+L5.L+	/L55APN#P#N''P*N'+P1	P:)P#O2&P2O66P9O6:PPc                     ddl m} m}m}  |            } ||          } | |          }|o|dk    }g }t	                      }t                      p|}	|	s|                    ddg           t                      }
|	o	|o|
d         }g }|r|                    d           n=|	r|                    d           n%|                    d	t                       d
           |s|                    d           ni|dk    r|                    d           nM|dk    r|                    d           n1|dk    r|                    d           n|                    d           |
d         D ]}|                    d|            |

                    dg           D ]}|                    d|            ||	||d                    |          |
dS )zCheck if all voice mode requirements are met.

    Returns:
        Dict with ``available``, ``audio_available``, ``stt_available``,
        ``missing_packages``, and ``details``.
    r   )_get_provider_load_stt_configis_stt_enablednoner   r	   rA   z)Audio capture: OK (Termux:API microphone)zAudio capture: OKzAudio capture: MISSING (r@   z5STT provider: DISABLED in config (stt.enabled: false)localz'STT provider: OK (local faster-whisper)groqzSTT provider: OK (Groq)openaizSTT provider: OK (OpenAI)z`STT provider: MISSING (pip install faster-whisper, or set GROQ_API_KEY / VOICE_TOOLS_OPENAI_KEY)rB   zEnvironment: rC   
)rA   audio_availablestt_availablemissing_packagesr   environment)rQ  rv  rw  rx  r0   r   extendrT   rF   r   r5   r   )rv  rw  rx  
stt_configstt_enabledstt_providerr  missingrP   	has_audio	env_checkrA   details_partsr  notices                  r   check_voice_requirementsr    st    ZYYYYYYYYY!!##J .,,K =,,L:LF$:MG466N ""4nI 1w/000 )**IFmF	+0FIM ZHIIII	 Z01111X8S8U8UXXXYYY 
TUUUU		 	 FGGGG			67777		!	!89999<	
 	
 	

 Z( 8 86W667777--	2.. 7 75V556666 $&#99]++   r     max_age_secondsc                 "   t           j                            t                    sdS d}t	          j                    }t          j        t                    D ]}|                                r|j                            d          rl|j        	                    d          rR	 ||
                                j        z
  }|| k    rt          j        |j                   |dz  }# t          $ r Y w xY w|rt                              d|           |S )zRemove old temporary voice recording files.

    Args:
        max_age_seconds: Delete files older than this (default: 1 hour).

    Returns:
        Number of files deleted.
    r   r   r  rU   z"Cleaned up %d old voice recordings)r3   r   isdirr   ru   scandiris_filename
startswithrc  statst_mtimer   r   r{   r|   )r  deletedr   entryages        r   cleanup_temp_recordingsr    s    7==## qG
)++CI&&  ==?? 	uz44\BB 	uzGZGZ[aGbGb 	EJJLL11((Iej)))qLG     D97CCCNs    A C!!
C.-C.)r[   r\   rU   r/   r   )r  )@r   loggingr3   rh  rer   r(   r  tempfiler   ru   r  typingr   r   r   r   	getLoggerr   r{   r   rD   r   rG   r   r   r   r   r   r-   r0   dictrT   rk   r   r  r  r   r   r   r   
gettempdirr   rj   r   r   r   r   r(  rG  compile
IGNORECASErH  rK  rR  rS  rj  __annotations__r   rU  rY  rt  r  r  r   r   r   <module>r     s  	 	 	  				  				      



        , , , , , , , , , , , ,		8	$	$  $     A @ @ @ @ @+S + + + +4HSM 4 4 4 44     T T T T T\$ \ \ \ \B     GLL,,..??	&4 &4 &4e &43 &4t &4 &4 &4 &4Xz z z z z z z z@\ \ \ \ \ \ \ \~
}/BB       > &2:@
-        " 3 x} SRUX    < 04 (:+, 3 3 3!!   (Ks Kt K K K Kb<$sCx. < < < <D S C      r   