
    )jP              
       >   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e fdZ!dZ"dZ#dZ$dZ%dZ&dZ'ej(        )                     ej*                    d          Z+d4de,de-de,ddfdZ. G d d          Z/ G d d          Z0de0e/z  fdZ1h d Z2 ej3        d!ej4        "          Z5d#edefd$Z6d5d%ed&ee         deeef         fd'Z7d(ed)e,defd*Z8d%ed&ee         d)e,deeef         fd+Z9d%ed)e,dee         fd,Z:da;eej<                 e=d-<    e	j>                    Z?d6d.Z@d(edefd/ZAdeeef         fd0ZBd7d2e,de,fd3ZCdS )8aV  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     7/home/wildlama/.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t          j                  } 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checkstdinzpackage:com.termux.api )r   
subprocessrunDEVNULLstdout	Exception)results    r   _termux_api_app_installedr/   D   s    !## u888$
 
 
 (FM,?R@@   uus   0A 
AAc                  >    t                      d uot                      S N)r   r/   r   r   r   _termux_voice_capture_availabler2   U   s    %''t3S8Q8S8SSr   c                     ddl } ddl}g }t          j                            dd          }|                    d          D ]U}|                                }|                    d          r*|                    |t          d          d                    Vt          j                            d          }|r3|                    t          j
                            |d                     t          j                            d	          }|rg|                    t          j
                            |d
d                     |                    t          j
                            |d                     |D ]}|s	 |                    t          j        |          j                  s3n# t          $ r Y @w xY w|                      | j        | j                  }	 |                    d           |                    |           	 |                                  dS # t          $ r Y |                                 w xY w# |                                 w xY wdS )a  Return True if a PulseAudio/PipeWire socket is reachable on disk.

    Covers the common case where a sound server runs locally (e.g. on a
    remote SSH host) without ``PULSE_SERVER``/``PIPEWIRE_REMOTE`` being set --
    the client just connects to the default socket under the runtime dir.
    We look at ``PULSE_SERVER`` unix paths, ``PULSE_RUNTIME_PATH``, and
    ``XDG_RUNTIME_DIR`` for a ``pulse/native`` or ``pipewire-0`` socket
    (issue #35622).
    r   NPULSE_SERVERr(   ;zunix:PULSE_RUNTIME_PATHnativeXDG_RUNTIME_DIRpulsez
pipewire-0g      ?TF)socketstatosenvirongetsplitstrip
startswithappendlenpathjoinS_ISSOCKst_moder   AF_UNIXSOCK_STREAM
settimeoutconnectclose)	r:   r;   
candidatespulse_serverpartpulse_runtimexdg_runtimerD   socks	            r   _pulse_socket_reachablerS   Y   sD    MMMKKKJ:>>."55L""3'' 3 3zz||??7## 	3d3w<<==1222JNN#788M A"',,}h??@@@*..!233K C"',,{GXFFGGG"',,{LAABBB   		==!677  	 	 	H	 }}V^V-?@@	OOC   LL JJLLLLLL  	 	 	JJLLLL	 JJLLLL5s6   :,F((
F54F5*H
H<%H?;H<<H??Ic                     g } g }t                      }t                      }t          |o|          }t          t          j                            d          p,t          j                            d          pt                                }t          d dD                       r-|r|                    d           n|                     d           ddl	m
}  |            r-|r|                    d	           n|                     d
           	 t          d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#                      \  }}		 |                                }
|
sE|r|                    d           n-|r|                    d           n|                     d           nU# t&          $ rH |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).
    r4   PIPEWIRE_REMOTEc              3   T   K   | ]#}t           j                            |          V  $d S r1   )r<   r=   r>   ).0vs     r   	<genexpr>z+detect_audio_environment.<locals>.<genexpr>   s0      
R
R2:>>!
R
R
R
R
R
Rr   )
SSH_CLIENTSSH_TTYSSH_CONNECTIONzBRunning over SSH with a reachable PulseAudio/PipeWire sound serverzRunning over SSH -- no audio devices available.
  If a sound server (PulseAudio/PipeWire) is running on this host,
  point Hermes at it, e.g.:
    export XDG_RUNTIME_DIR=/run/user/$(id -u)
    # or: export PULSE_SERVER=unix:$XDG_RUNTIME_DIR/pulse/nativer   )is_containerzGRunning inside container (Docker/Podman/LXC) with host audio forwardinga  Running inside container (Docker/Podman/LXC) -- no audio devices.
  Forward host audio with one of (substitute $XDG_RUNTIME_DIR for your runtime dir,
  typically /run/user/$UID):
    PulseAudio:  -v $XDG_RUNTIME_DIR/pulse/native:$XDG_RUNTIME_DIR/pulse/native \
                 -e PULSE_SERVER=unix:$XDG_RUNTIME_DIR/pulse/native
    PipeWire:    -e PIPEWIRE_REMOTE=$XDG_RUNTIME_DIR/pipewire-0z/proc/versionrzutf-8)encoding	microsoftz%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.wavNzSNo PortAudio devices detected but host audio forwarding is configured -- continuingzMNo PortAudio devices detected, but Termux:API microphone capture is availablez&No audio input/output devices detectedzOAudio device query failed but host audio forwarding is configured -- 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/   boolr<   r=   r>   rS   anyrB   hermes_constantsr]   openreadlowerFileNotFoundErrorPermissionErrorr   r   query_devicesr-   r   r   r   )rc   rd   termux_mic_cmdtermux_app_installedtermux_capturehas_forwarded_audior]   fr
   _devicess              r   detect_audio_environmentru      s    HG/11N466.A-ABBN

~&& 	%:>>+,,	%"$$  
R
R&Q
R
R
RRR 
 		NN_````OOS   .-----|~~  
	NNdeeeeOOR  /3999 
	Qaffhhnn....:>>.11 NN#JKKKKOO^  
	 
	 
	 
	 
	 
	 
	 
	 
	 
	 
	 
	 
	 
	 
	 8   5A	Z&&((G N& NNNm    $ NNN#rssssOO$LMMM 	Z 	Z 	Z # Ze      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   F, !A3F F,  F$$F, 'F$(F, ,GG
J AH8 7J 8AJ
J 	J

J A!M(1A4M('M(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)endpointrx   {Gz?   rv   333333?i  dtype
samplerate       @zBeep playback failed: %s)r   r   r   intSAMPLE_RATErangelinspacesinpiminrB   astyperw   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__T  s6    ^%%
.2r   c                     | j         S r1   r   r   s    r   is_recordingz TermuxAudioRecorder.is_recording[  s
    r   c                 J    | j         sdS t          j                    | j        z
  S Nr   r   r   r   r   r   s    r   elapsed_secondsz#TermuxAudioRecorder.elapsed_seconds_  '     	3~$"222r   c                     | j         S r1   r   r   s    r   current_rmszTermuxAudioRecorder.current_rmse  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t           j                   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   t6                              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   r<   makedirs	_TEMP_DIRr   strftimerD   rE   r   strr   CHANNELSr)   r*   r+   CalledProcessErrorstderrr,   r@   r-   r   r   r   r   info)r   on_silence_stopmic_cmd	timestampcommandr   detailss          r   startzTermuxAudioRecorder.starti  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N74dBVZblbtuuuuu, 	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E<<F!	FF!+'GG"%G"c                 v    t                      }|sd S t          j        |dgddddt          j                   d S )N-qTr   Fr"   )r   r)   r*   r+   )r   r   s     r   _stop_termux_recordingz*TermuxAudioRecorder._stop_termux_recording  sG    ,.. 	Ft$PRZ_gqgyzzzzzzr   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   r   z"Termux voice recording stopped: %s)r   r   r   r   r   r   r<   rD   isfiler   r   unlinkr   getsizer   r   )r   rD   
started_ats      r   r   zTermuxAudioRecorder.stop  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-   r<   rD   r   r   r   r   r   )r   rD   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 r1   )r   r   s    r   shutdownzTermuxAudioRecorder.shutdown  s    r   r   Nr1   )__name__
__module____qualname____doc__supports_silence_autostopr   propertyre   r   floatr   r   r   r   r   r   r   r   r   r   r   r   r   r   r   O  s"       LL %    d    X 3 3 3 3 X3
 !S ! ! ! X!*6 *6 *6 *6 *6X{ { { {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   r   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           	      ~  	 |rt                               d|           j        sd S j                            |                                            t          
                    
                    | 	                    
j
                  dz                                }|_        t          j        |          _        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: %srx   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_cbr  sg    ^ "#, ^ ^ ^ &-JAX\ ] ] ] ] ] ] ] ] ]^s   
 
A<Atargetdaemon)"r   r   r   r   rB   copyr   sqrtmeanr   float64r   maxr  r   r   r   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 55DN $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   "K==LL)r   channelsr   callbackz#Failed to open audio input stream: z6. Check that a microphone is connected and accessible.)
r   r   InputStreamr   r   DTYPEr   r-   rL   r   )r   r
   r  streamr   r   s   `    @r   _ensure_streamzAudioRecorder._ensure_stream  s#    <#FB]	O ]	O ]	O ]	O ]	O ]	O@ 	^^&!"	 $  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   r   r   r   r   r   r   r   r   r   r  r   r   r$  r   r   r   r   )r   r   r   s      r   r   zAudioRecorder.start  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rz   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r1   )r   rL   r-   )r#  s   r   	_do_closez;AudioRecorder._close_stream_with_timeout.<locals>._do_close  sH       s   (- 
;;Tr  r   g?r%   u:   Audio stream close timed out after %.1fs — forcing ahead)
r   r   r  r   
__import__r   is_aliverE   r   warning)r   r%   r*  r   r   r#  s        @r   _close_stream_with_timeoutz(AudioRecorder._close_stream_with_timeout  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)r   z,Recording too short (%d samples), discardingz2Recording too quiet (peak RMS=%d < %d), discarding)r   r   r   r   r   r   r   r   r   r   r   rC   r   r   r   r  r   
_write_wav)r   rs   r   
audio_datar  min_sampless         r   r   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))r<   r   r   r   r   rD   rE   waverh   setnchannelsr   setsampwidthSAMPLE_WIDTHsetframerater   writeframestobytesr   r   r   )r3  r   wav_pathwf	file_sizes        r   r2  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   r1   )rz   )r   r   r   r   r   r   r   r   r   r   r   re   r   r$  r   r/  r   r   r   r   r   staticmethodr2  r   r   r   r   r     s         !%# # # #4 3 3 3 3 X3
 !S ! ! ! X! d    X@ @ @ @D,] ,] ,] ,] ,]\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.)r2   r   r   r   r   r   create_audio_recorderrF  -  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)rb  cleaneds     r   is_whisper_hallucinationri  ]  st      &&((G t~~d555DZ9Z9Zt%%g.. t5r   rA  modelc                 4   ddl m}m} t          | |          rt	          | ||          }n || |          }|                    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   )MAX_FILE_SIZEtranscribe_audio)rj  max_file_sizerj  successrb  r(   z"Filtered Whisper hallucination: %rT)rp  rb  filtered)	tools.transcription_toolsrl  rm  _should_chunk_for_transcription_transcribe_wav_in_chunksr>   ri  r   r   )rA  rj  rl  rm  r.   s        r   transcribe_recordingru  n  s     JIIIIIII&x?? 9*85P]^^^!!(%888 zz) E!9&**\SU:V:V!W!W E8&:NOOOrtDDDMr   	file_pathrn  c                     |                                                      d          sdS 	 t          j                            |           |k    S # t
          $ r Y dS w xY w)z@Return whether a CLI WAV recording needs to be split before STT.r8  F)rj   endswithr<   rD   r   r   )rv  rn  s     r   rs  rs    sg    ??%%f-- uwy))M99   uus   "A 
AAc          	      
   ddl m} g }g }	 t          | |          }|sOdddd|D ]F}	 t          j                            |          rt          j        |           7# t          $ r Y Cw xY wS t          	                    dt          |          |            t          |d	
          D ]\  }} |||          }|                    d          s}|                    dd          }	ddd| dt          |           d|	 dc |D ]F}	 t          j                            |          rt          j        |           7# t          $ r Y Cw xY wS |                    dd                                          }
|
r$t          |
          s|                    |
           dd                    |                                          |                    d          t          |          d|D ]F}	 t          j                            |          rt          j        |           7# t          $ r Y Cw xY wS # t"          $ rz}t                              d| |d           ddd| dcY d}~|D ]F}	 t          j                            |          rt          j        |           7# t          $ r Y Cw xY wS d}~ww xY w# |D ]F}	 t          j                            |          rt          j        |           7# t          $ r Y Cw xY ww xY w)zGSplit an oversized WAV into provider-sized chunks and join transcripts.r   )rm  )rn  Fr(   zNo audio chunks were created)rp  rb  r
  z+Transcribing oversized WAV in %d chunks: %srv   )r   ro  rp  r
  zUnknown transcription errorzChunk /z	 failed: rb  T provider)rp  rb  r|  chunksz'Chunked transcription failed for %s: %sr  zChunked transcription failed: N)rr  rm  _split_wav_for_transcriptionr<   rD   r   r   r   r   r   rC   	enumerater>   r@   ri  rB   rE   r-   r
  )rA  rj  rn  rm  chunk_pathstranscripts
chunk_pathindexr.   r
  rb  r   s               r   rt  rt    s    ;:::::KK#28=YYY 	a$BA_``6 & 	 	J7>>*-- *Ij)))   		3 	A3{CSCSU]^^^!*;a!@!@!@ 	/ 	/E:%%j>>>F::i(( 

7,IJJ$"$PePPc+.>.>PPPP   ( & 	 	J7>>*-- *Ij)))   		  L"55;;==J /"::"F"F /"":... ((;//5577

:..+&&	
 
 & 	 	J7>>*-- *Ij)))   			  c c c>!VZ[[[ =a^_=a=abbbbbbb% 	 	J7>>*-- *Ij)))   			c & 	 	J7>>*-- *Ij)))   		s   H0 3A
A*)A*.BH0 3D;;
EEBH0 +3H
H,+H,0
J4:&J/ J4!J7 *3J
J+*J+/J44J7 7L=3K10L1
K>	;L=K>	>Lc                   t          j        t          d           g }d}t          j        | d          5 }|                                }t          d|j        |j        z            }||z
  }||k     rt          d          t          d||z            }d}		 |
                    |          }
|
sn|	dz  }	t          j        t           j                            t           j                            |                     d          d|	d	d
dt          d          }|j        }|                                 	 t          j        |d          5 }|                    |j                   |                    |j                   |                    |j                   |                    |j        |j                   |                    |
           ddd           n# 1 swxY w Y   |                    |           n5# t6          $ r( 	 t          j        |           n# t:          $ r Y nw xY w w xY w	 ddd           n# 1 swxY w Y   |S )zDWrite WAV chunks small enough to pass the shared STT file-size gate.Tr   i   rbrv   z/STT max_file_size is too small for WAV chunkingr   _chunk03drs   r8  F)prefixsuffixdirdeleter9  N)r<   r   r   r:  rh   	getparamsr  	nchannels	sampwidth
ValueError
readframestempfileNamedTemporaryFilerD   splitextbasenamenamerL   r;  r<  r>  	frameratesetcomptypecomptypecompnamer?  rB   r-   r   r   )rA  rn  r  header_reservesourceparamsblock_alignmax_data_bytesframes_per_chunkr  r  tempr  chunks                 r   r~  r~    s   K	D))))KN	8T	"	" %f!!##!V-0@@AA&7K''NOOOq.K"?@@	&&'788F QJE.'**27+;+;H+E+EFFqI]]QV]]]]	  D JJJLLLYz400 .E&&v'7888&&v'7888&&v'7888%%fovGGG%%f---. . . . . . . . . . . . . . . "":....   Ij))))   D/	 % % % % % % % % % % % % % % %N ss   C?H?5G;
BGG;G	G;!G	"G;:H?;
H-HH-
H(%H-'H((H--H??II_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_lockr  poll	terminater   r   r-   r   r   )procr
   rs   s      r   stop_playbackr    s    
                                   		#	NNKK45555 	 	 	D	A
					   s0   
"" .A/ /
A<;A< %B' '
B54B5c                 j   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                   }|rh	 t?          j         |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}	~	d}	~	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: %sFr8  r  r   Nr   r   r   Tzsounddevice playback failed: %sDarwinafplayffplayz-nodispz	-autoexitz	-loglevelquietLinuxaplayr   r   )r,   r   r'   i,  r+  z+System player %s timed out, killing processzSystem player %s failed: %sz No audio player available for %s)'r<   rD   r   r   r.  rx  r   r:  rh   r  
getnframes
frombufferrw   getframerater   rC   r   r   r   r   r   r   r   r   r-   r   platformsystemrB   r   r   r)   Popenr+   r  r  waitTimeoutExpiredkill)rv  r
   r   rB  r  r3  sample_rateduration_secsr   r   r  playerscmdexer  s                  r   play_audio_filer    sD    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"" 	,,!'J4FzOaisi{|||# , ,'+$, , , , , , , , , , , , , , ,		#	&&&# , ,'+$, , , , , , , , , , , , , , ,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/=M ,K;/M ;K?	?M K?	 M #L2&M 2L6	6M 9L6	:M  APN."P.N22P5N26P<	P)P.O=1P=PPP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	   rb   z)Audio capture: OK (Termux:API microphone)zAudio capture: OKzAudio capture: MISSING (ra   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)u   STT provider: MISSING (uv pip install faster-whisper — `pip install faster-whisper` also works if pip is on PATH, or set GROQ_API_KEY / VOICE_TOOLS_OPENAI_KEY)rc   zEnvironment: rd   
)rb   audio_availablestt_availablemissing_packagesr   environment)rr  r  r  r  r2   r   extendru   rB   r   r>   rE   )r  r  r  
stt_configstt_enabledstt_providerr  missingrp   	has_audio	env_checkrb   details_partsr.  notices                  r   check_voice_requirementsr  d  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   r8  rv   z"Cleaned up %d old voice recordings)r<   rD   isdirr   r   scandiris_filer  rA   rx  r;   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}   rv   r1   r   )r  )Dr   loggingr<   r  rer   r)   r&  r  r   r   r:  typingr   r   r   r   	getLoggerr   r   r   re   r   rg   r   r   r   r   r   r/   r2   rS   dictru   r   r   r"  r=  r   r   rD   rE   
gettempdirr   r   r   r   r   r   rF  re  compile
IGNORECASErf  ri  ru  rs  rt  r~  r  r  __annotations__r   r  r  r  r  r  r   r   r   <module>r     s  	 	 	  				  				      



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