
    KiA                        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ZddlmZ ddlmZmZmZmZ ddlmZ  ej        e          ZddlmZ ddlmZmZ d Zd	 Zd
 Z d Z!dZ"dZ#dZ$dZ%dZ&dZ'dZ(dZ)de*fdZ+ e+            Z,dZ-dee*ef         fdZ.dee*ef         de*fdZ/de0fdZ1de*dee*         fdZ2de*de*dee*ef         de*fdZ3de*de*dee*ef         de*fd Z4de*de*dee*ef         de*fd!Z5de0fd"Z6de*fd#Z7de*fd$Z8de*de*dee*ef         de*fd%Z9	 dcde*dee*         de*fd&Z:de0fd'Z;de<e*e*f         fd(Z=de0fd)Z> ej?        d*          Z@ ej?        d+          ZA ej?        d,          ZB ej?        d-          ZC ej?        d.          ZD ej?        d/          ZE ej?        d0          ZF ej?        d1ejG        2          ZH ej?        d3ejG        2          ZI ej?        d4          ZJ ej?        d5          ZKde*de*fd6ZL	 dcd7ejM        d8ejN        d9ejN        d:eee*gdf                  fd;ZOed<k    r ePd=            ePd>           d? ZQ ePd@            ePdA eQed          rdBndC             ePdD eQedE          rdBndF             ePdG ejR        dH          rdIndJ             ePdK eQe dL          rdBndM             ePdG e            rdIndN             ePdO e1            rdPndQ             ePdRe,             e.            ZS e/eS          ZT ePdSeT            ddTlUmVZV dUdVdWdXdYdZdXd[dZd\dgd]d^ZW eVjX        dUd_eWd` e;dab           dS )da  
Text-to-Speech Tool Module

Supports four TTS providers:
- Edge TTS (default, free, no API key): Microsoft Edge neural voices
- ElevenLabs (premium): High-quality voices, needs ELEVENLABS_API_KEY
- OpenAI TTS: Good quality, needs OPENAI_API_KEY
- NeuTTS (local, free, no API key): On-device TTS via neutts_cli, needs neutts installed

Output formats:
- Opus (.ogg) for Telegram voice bubbles (requires ffmpeg for Edge TTS)
- MP3 (.mp3) for everything else (CLI, Discord, WhatsApp)

Configuration is loaded from ~/.hermes/config.yaml under the 'tts:' key.
The user chooses the provider and voice; the model just sends text.

Usage:
    from tools.tts_tool import text_to_speech_tool, check_tts_requirements

    result = text_to_speech_tool(text="Hello world")
    N)Path)CallableDictAnyOptional)urljoin)resolve_managed_tool_gateway)managed_nous_tools_enabledresolve_openai_audio_api_keyc                      ddl } | S )z?Lazy import edge_tts. Returns the module or raises ImportError.r   Nedge_ttsr   s    +/home/ubuntu/hermes-agent/tools/tts_tool.py_import_edge_ttsr   1   s    OOOO    c                      ddl m}  | S )zGLazy import ElevenLabs client. Returns the class or raises ImportError.r   
ElevenLabs)elevenlabs.clientr   r   s    r   _import_elevenlabsr   6   s    ,,,,,,r   c                      ddl m}  | S )zCLazy import OpenAI client. Returns the class or raises ImportError.r   )OpenAI)openair   )OpenAIClients    r   _import_openai_clientr   ;   s    ------r   c                      ddl } | S )zJLazy import sounddevice. Returns the module or raises ImportError/OSError.r   N)sounddevice)sds    r   _import_sounddevicer   @   s    Ir   edgezen-US-AriaNeuralpNInz6obpgDQGcFmaJgBeleven_multilingual_v2eleven_flash_v2_5zgpt-4o-mini-ttsalloyzhttps://api.openai.com/v1returnc                  @    ddl m}  t           | dd                    S )Nr   get_hermes_dirzcache/audioaudio_cache)hermes_constantsr(   strr'   s    r   _get_default_output_dirr,   R   s.    //////~~m];;<<<r   i  c                  
   	 ddl m}   |             }|                    di           S # t          $ r t                              d           i cY S t          $ r)}t                              d|d           i cY d}~S d}~ww xY w)	z
    Load TTS configuration from ~/.hermes/config.yaml.

    Returns a dict with provider settings. Falls back to defaults
    for any missing fields.
    r   )load_configttsz9hermes_cli.config not available, using default TTS configzFailed to load TTS config: %sTexc_infoN)hermes_cli.configr.   getImportErrorloggerdebug	Exceptionwarning)r.   configes      r   _load_tts_configr;   ]   s    	111111zz%$$$   PQQQ			   6DIII						s!   %( &B	BA=7B=B
tts_configc                     |                      d          pt                                                                          S )z%Get the configured TTS provider name.provider)r3   DEFAULT_PROVIDERlowerstrip)r<   s    r   _get_providerrB   p   s2    NN:&&:*:AACCIIKKKr   c                  .    t          j        d          duS )z+Check if ffmpeg is available on the system.ffmpegN)shutilwhich r   r   _has_ffmpegrH   x   s    <!!--r   mp3_pathc                    t                      sdS |                     dd          d         dz   }	 t          j        dd| dd	d
ddddd|dgdd          }|j        dk    rEt
                              d|j        |j                            dd          dd                    dS t          j
                            |          r%t          j
                            |          dk    r|S n# t          j        $ r t
                              d           Y nXt          $ r t
                              d           Y n3t          $ r'}t
                              d|d           Y d}~nd}~ww xY wdS )z
    Convert an MP3 file to OGG Opus format for Telegram voice bubbles.

    Args:
        mp3_path: Path to the input MP3 file.

    Returns:
        Path to the .ogg file, or None if conversion fails.
    N.   r   .oggrD   -iz-acodeclibopusz-ac1z-b:a64kz-vbroff-yT   )capture_outputtimeoutz0ffmpeg conversion failed with return code %d: %szutf-8ignore)errors   z)ffmpeg OGG conversion timed out after 30szffmpeg not found in PATHz ffmpeg OGG conversion failed: %sr0   )rH   rsplit
subprocessrun
returncoder5   r8   stderrdecodeospathexistsgetsizeTimeoutExpiredFileNotFoundErrorr7   )rI   ogg_pathresultr:   s       r   _convert_to_opusrh   }   s    == tsA&&q)F2HMtXy)CxG
 
 

 !!NNM +V]-A-A'RZ-A-[-[\`]`\`-ac c c47>>(## 	(A(AA(E(EO$ D D DBCCCCC 3 3 3122222 M M M91tLLLLLLLLM4s+   A2C* %AC* *)E*#E*:	E*E%%E*textoutput_pathc                    K   t                      }|                    di           }|                    dt                    }|                    | |          }|                    |           d{V  |S )z
    Generate audio using Edge TTS.

    Args:
        text: Text to convert.
        output_path: Where to save the MP3 file.
        tts_config: TTS config dict.

    Returns:
        Path to the saved audio file.
    r    voiceN)r   r3   DEFAULT_EDGE_VOICECommunicatesave)ri   rj   r<   	_edge_ttsedge_configrl   communicates          r   _generate_edge_ttsrs      s{       !""I..,,KOOG%788E''e44K


;
'
''''''''r   c                    t          j        dd          }|st          d          |                    di           }|                    dt                    }|                    dt
                    }|                    d          rd}nd	}t                      } ||
          }	|	j        	                    | |||          }
t          |d          5 }|
D ]}|                    |           	 ddd           n# 1 swxY w Y   |S )z
    Generate audio using ElevenLabs.

    Args:
        text: Text to convert.
        output_path: Where to save the audio file.
        tts_config: TTS config dict.

    Returns:
        Path to the saved audio file.
    ELEVENLABS_API_KEY z=ELEVENLABS_API_KEY not set. Get one at https://elevenlabs.io/
elevenlabsvoice_idmodel_idrM   opus_48000_64mp3_44100_128api_keyri   rx   ry   output_formatwbN)r`   getenv
ValueErrorr3   DEFAULT_ELEVENLABS_VOICE_IDDEFAULT_ELEVENLABS_MODEL_IDendswithr   text_to_speechconvertopenwrite)ri   rj   r<   r}   	el_configrx   ry   r   r   clientaudio_generatorfchunks                r   _generate_elevenlabsr      sd    i,b11G ZXYYY|R00I}}Z)DEEH}}Z)DEEH F## (''#%%JZ(((F+33#	 4  O 
k4	 	  A$ 	 	EGGENNNN	               s   C==DDc                    t                      \  }}|                    di           }|                    dt                    }|                    dt                    }|                    d|          }|                    d          rd}nd}t                      }	 |	||          }
	 |
j        j                            ||| |d	t          t          j                              i
          }|                    |           |t          |
dd          }t          |          r |             S S # t          |
dd          }t          |          r |             w w xY w)z
    Generate audio using OpenAI TTS.

    Args:
        text: Text to convert.
        output_path: Where to save the audio file.
        tts_config: TTS config dict.

    Returns:
        Path to the saved audio file.
    r   modelrl   base_urlrM   opusmp3)r}   r   zx-idempotency-key)r   rl   inputresponse_formatextra_headerscloseN)#_resolve_openai_audio_client_configr3   DEFAULT_OPENAI_MODELDEFAULT_OPENAI_VOICEr   r   audiospeechcreater+   uuiduuid4stream_to_filegetattrcallable)ri   rj   r<   r}   r   
oai_configr   rl   r   r   r   responser   s                r   _generate_openai_ttsr      sl    <==GX"--JNN7$899ENN7$899E~~j(33H F##   (**L\'H===F<&--+.DJLL0A0AB . 
 
 	,,,..E?? 	EGGGG	 ..E?? 	EGGGG	s   *AD0 0-Ec                  f    	 ddl } | j                            d          duS # t          $ r Y dS w xY w)z=Check if the neutts engine is importable (installed locally).r   NneuttsF)importlib.utilutil	find_specr7   )	importlibs    r   _check_neutts_availabler     sP    ~''11==   uus   " 
00c                  Z    t          t          t                    j        dz  dz            S )z9Return path to the bundled default voice reference audio.neutts_sampleszjo.wavr+   r   __file__parentrG   r   r   _default_neutts_ref_audior   "  $    tH~~$'77(BCCCr   c                  Z    t          t          t                    j        dz  dz            S )z>Return path to the bundled default voice reference transcript.r   zjo.txtr   rG   r   r   _default_neutts_ref_textr   '  r   r   c                    ddl }|                    di           }|                    dd          pt                      }|                    dd          pt                      }|                    dd          }|                    d	d
          }|}	|                    d          s|                    dd          d         dz   }	t          t          t                    j	        dz            }
|j
        |
d| d|	d|d|d|d|g}t          j        |ddd          }|j        dk    rk|j                                        }d |                                D             }t#          dt%          d                              |          pd           |	|k    r`t)          j        d          }|r5|d|	ddd |g}t          j        |dd!"           t-          j        |	           nt-          j        |	|           |S )#a  Generate speech using the local NeuTTS engine.

    Runs synthesis in a subprocess via tools/neutts_synth.py to keep the
    ~500MB model in a separate process that exits after synthesis.
    Outputs WAV; the caller handles conversion for Telegram if needed.
    r   Nr   	ref_audiorv   ref_textr   zneuphonic/neutts-air-q4-ggufdevicecpu.wavrK   rL   zneutts_synth.pyz--textz--outz--ref-audioz
--ref-textz--modelz--deviceTx   )rU   ri   rV   c                 <    g | ]}|                     d           |S )zOK:)
startswith).0ls     r   
<listcomp>z$_generate_neutts.<locals>.<listcomp>P  s)    QQQQQ\\%=P=PQqQQQr   zNeuTTS synthesis failed: 
   zunknown errorrD   rN   rS   z	-loglevelerrorrT   )checkrV   )sysr3   r   r   r   rZ   r+   r   r   r   
executabler[   r\   r]   r^   rA   
splitlinesRuntimeErrorchrjoinrE   rF   r`   removerename)ri   rj   r<   r   neutts_configr   r   r   r   wav_pathsynth_scriptcmdrg   r^   error_linesrD   conv_cmds                    r   _generate_neuttsr   ,  s    JJJNN8R00M!!+r22Q6O6Q6QI  R00N4L4N4NHg'EFFEx//F H'' :%%c1--a069tH~~,/@@AAL$yh5FC ^C4MMMFA$$&&QQ&"3"3"5"5QQQes2ww||K7P7P7cTceefff ;h'' 	-hk7KXHN84<<<<Ih Ih,,,r   c                      r                                  st          j        dddd          S t                     t          k    r=t
                              dt                     t                      dt                    t                      t                    }t          j
        dd                                          }|d	k    }|r"t          |                                          }nut          j                                                            d
          }t          t"                    }|                    dd           |r|dv r
|d| dz  }n	|d| dz  }|j                            dd           t)          |          	 |dk    rf	 t+                       n)# t,          $ r t          j        dddd          cY S w xY wt
                              d           t1                      n|dk    rf	 t3                       n)# t,          $ r t          j        dddd          cY S w xY wt
                              d           t5                      n|dk    rTt7                      st          j        dddd          S t
                              d           t9                      nBd}	 t;                       n# t,          $ r d}Y nw xY w|rt
                              d           	 t=          j                    }	ddl }
|
j!        "                    d          5 }|#                     fd          $                    d            ddd           n# 1 swxY w Y   n# tJ          $ r& t=          j&        tO                                Y nYw xY wt7                      r.t
                              d!           d}t9                      nt          j        dd"dd          S t          j(        )                              r#t          j(        *                              dk    rt          j        dd#| d$dd          S d}|d%v r++                    d          stY                    }|r|d}n|d&v r+                    d          }t          j(        *                              }t
                              d'|d(|           d) }|rd*| }t          j        d|||d+d          S # tZ          $ rF}d,| d-| }t
          .                    d.|           t          j        d|dd          cY d}~S d}~wt^          $ rH}d/| d-| }t
          .                    d.|d0           t          j        d|dd          cY d}~S d}~wt`          $ rH}d1| d-| }t
          .                    d.|d0           t          j        d|dd          cY d}~S d}~ww xY w)2ac  
    Convert text to speech audio.

    Reads provider/voice config from ~/.hermes/config.yaml (tts: section).
    The model sends text; the user configures voice and provider.

    On messaging platforms, the returned MEDIA:<path> tag is intercepted
    by the send pipeline and delivered as a native voice message.
    In CLI mode, the file is saved to ~/voice-memos/.

    Args:
        text: The text to convert to speech.
        output_path: Optional custom save path. Defaults to ~/voice-memos/<timestamp>.mp3

    Returns:
        str: JSON result with success, file_path, and optionally MEDIA tag.
    FzText is required)successr   )ensure_asciiz.TTS text too long (%d chars), truncating to %dNHERMES_SESSION_PLATFORMrv   telegramz%Y%m%d_%H%M%ST)parentsexist_ok)r   rw   tts_rM   z.mp3rw   z`ElevenLabs provider selected but 'elevenlabs' package not installed. Run: pip install elevenlabsz$Generating speech with ElevenLabs...r   z<OpenAI provider selected but 'openai' package not installed.z$Generating speech with OpenAI TTS...r   zNeuTTS provider selected but neutts is not installed. Run hermes setup and choose NeuTTS, or install espeak-ng and run python -m pip install -U neutts[all].z(Generating speech with NeuTTS (local)...z"Generating speech with Edge TTS...r   rL   )max_workersc                  J    t          j        t                               S N)asyncior\   rs   )file_strri   r<   s   r   <lambda>z%text_to_speech_tool.<locals>.<lambda>  s    GK0B4S]0^0^$_$_ r   <   rV   z9Edge TTS not available, falling back to NeuTTS (local)...zhNo TTS provider available. Install edge-tts (pip install edge-tts) or set up NeuTTS for local synthesis.z-TTS generation produced no output (provider: ))r    r   )rw   r   z,TTS audio saved: %s (%s bytes, provider: %s),zMEDIA:z[[audio_as_voice]]
)r   	file_path	media_tagr>   voice_compatiblezTTS configuration error (z): z%szTTS dependency missing (r0   zTTS generation failed ()1rA   jsondumpslenMAX_TEXT_LENGTHr5   r8   r;   rB   r`   r   r@   r   
expanduserdatetimenowstrftimeDEFAULT_OUTPUT_DIRmkdirr   r+   r   r4   infor   r   r   r   r   r   r   get_running_loopconcurrent.futuresfuturesThreadPoolExecutorsubmitrg   r   r\   rs   ra   rb   rc   r   rh   r   r   re   r7   )ri   rj   r>   platform	want_opusr   	timestampout_diredge_availableloop
concurrentpoolr   	opus_path	file_sizer   r:   	error_msgr   r<   s   `                 @@r   text_to_speech_toolr  d  s   *  _tzz|| _ze6HIIX]^^^^ 4yy?""GTTcddd$_$%!##JZ((H y2B77==??HZ'I  9%%0022		%))++44_EE	)**dT222  	9%==="8"8"8"88II"8"8"8"88I 4$7779~~HqV|##'"$$$$ ' ' 'z$# # !&' ' ' ' ' ''
 KK>??? x<<<<!!'%'''' ' ' 'z$[# # !&' ' ' ' ' ''
 KK>??? x<<<<!!*,, 'z$F# # !&	' ' ' '
 KKBCCCT8Z8888 "N' """" ' ' '!&'  '@AAAP"355D----#+>>1>MM -QU______  &&,,,- - - - - - - - - - - - - - - $ P P PK 24: N NOOOOOP(** 	'WXXX# x<<<<z$E# # !&	' ' ' ' w~~h'' 	#27??8+D+D+I+I: TTTT  "# # # # !)))(2C2CF2K2K)(22I ($#' 111'0088GOOH--	BHQZN^N^`hiii (X''	 	;:y::Iz!"  0
 
    	  V V V@@@Q@@	T9%%%zei@@uUUUUUUUUU V V V?x??A??	T9t444zei@@uUUUUUUUUU V V V>h>>1>>	T9t444zei@@uUUUUUUUUU	Vs  T F- ,T -#GT G6T 
H T #H?<T >H??AT /T K T K*'T )K**T 
2N </M7+N 7M;;N >M;?N T -N30T 2N33AT AT *B/T 
X
$;U%X
%X
2=V5/X
5X
=X?X
X
c                  4   	 t                       dS # t          $ r Y nw xY w	 t                       t          j        d          rdS n# t          $ r Y nw xY w	 t                       t                      rdS n# t          $ r Y nw xY wt                      rdS dS )z
    Check if at least one TTS provider is available.

    Edge TTS needs no API key and is the default, so if the package
    is installed, TTS is available.

    Returns:
        bool: True if at least one provider can work.
    Tru   F)r   r4   r   r`   r   r   _has_openai_audio_backendr   rG   r   r   check_tts_requirementsr    s    t   9)** 	4	   $&& 	4	      t5s/    
"A 
AAA8 8
BBc                     t                      } | r	| t          fS t          d          }|$d}t                      r|dz  }t	          |          |j        t          |j                            d           dd          fS )z@Return direct OpenAI audio config or a managed gateway fallback.openai-audioNz8Neither VOICE_TOOLS_OPENAI_KEY nor OPENAI_API_KEY is setz5, and the managed OpenAI audio gateway is unavailable/v1)	r   DEFAULT_OPENAI_BASE_URLr	   r
   r   nous_user_tokenr   gateway_originrstrip)direct_api_keymanaged_gatewaymessages      r   r   r   4  s    133N 76662>BBOL%'' 	ONNG!!!*G)0055888$- -  r   c                  V    t          t                      pt          d                    S )zPReturn True when OpenAI audio can use direct credentials or the managed gateway.r
  )boolr   r	   rG   r   r   r  r  F  s%    ,..^2N~2^2^___r   z(?<=[.!?])(?:\s|\n)|(?:\n\n)z```[\s\S]*?```z\[([^\]]+)\]\([^)]+\)zhttps?://\S+z\*\*(.+?)\*\*z	\*(.+?)\*z`(.+?)`z^#+\s*flagsz^\s*[-*]\s+z---+z\n{3,}c                 F   t                               d|           } t                              d|           } t                              d|           } t                              d|           } t
                              d|           } t                              d|           } t                              d|           } t                              d|           } t                              d|           } t                              d|           } |                                 S )z:Remove markdown formatting that shouldn't be spoken aloud. z\1rv   z

)_MD_CODE_BLOCKsub_MD_LINK_MD_URL_MD_BOLD
_MD_ITALIC_MD_INLINE_CODE
_MD_HEADER_MD_LIST_ITEM_MD_HR_MD_EXCESS_NLrA   )ri   s    r   _strip_markdown_for_ttsr%  ^  s    c4((D<<t$$D;;r4  D<<t$$D>>%&&Dud++D>>"d##DR&&D::b$DVT**D::<<r   
text_queue
stop_eventtts_done_eventdisplay_callbackc           	         |                                  	 ddt          t          t                      }|                    di           }|                    d          |                    d|                    d                    t          j        dd          }|st                              d           n	 t                      } ||	          n*# t          $ r t                              d
           Y nw xY w	 t                      }|                    ddd                                           nj# t          t          f$ r'}	t                              d|	           dY d}	~	n7d}	~	wt           $ r'}	t                              d|	           dY d}	~	nd}	~	ww xY wd}
d}d}d}g t#          j        dt"          j                  }dt(          ffd}d                                 s;	 |                     |          }n5# t,          j        $ r# t1          |
          |k    r ||
           d}
Y ^w xY w|6|                    d|
          }
|
                                r ||
           n|
|z  }
|                    d|
          }
d|
v rd|
vr	 t6                              |
          }|n_|                                }|
d|         }|
|d         }
t1          |                                          |k     r||
z   }
n ||           |                                ;	 	 |                                  n# t,          j        $ r Y nw xY w,n2# t           $ r%}	t                              d|	           Y d}	~	nd}	~	ww xY w:	                                                                    n# t           $ r Y nw xY w|!                                 dS # :	                                                                    n# t           $ r Y nw xY w|!                                 w xY w)a  Consume text deltas from *text_queue*, buffer them into sentences,
    and stream each sentence through ElevenLabs TTS to the speaker in
    real-time.

    Protocol:
        * The producer puts ``str`` deltas onto *text_queue*.
        * A ``None`` sentinel signals end-of-text (flush remaining buffer).
        * *stop_event* can be set to abort early (e.g. user interrupt).
        * *tts_done_event* is **set** in the ``finally`` block so callers
          waiting on it (continuous voice mode) know playback is finished.
    Nrw   rx   streaming_model_idry   ru   rv   z8ELEVENLABS_API_KEY not set; streaming TTS audio disabledr|   z8elevenlabs package not installed; streaming TTS disabled]  rL   int16)
sampleratechannelsdtypezsounddevice not available: %sz#sounddevice OutputStream failed: %s   d   g      ?z<think[\s>].*?</think>r  sentencec                 \                                    rdS t          |                                           }|sdS |                                                    d          }
D ]0}|                                                    d          |k    r dS 1
                    |            |            dS t          |          t          k    r|dt                   }	 j        	                    |d          }h|D ]a}                                 r nLddl
}|                    ||j                  }                    |                    dd                     bdS dS  	|           dS # t          $ r&}t                               d	|           Y d}~dS d}~ww xY w)
z6Display sentence and optionally generate + play audio.Nz.!,	pcm_24000r~   r   )r0  rL   z!Streaming TTS sentence failed: %s)is_setr%  rA   r@   r  appendr   r   r   r   numpy
frombufferr-  r   reshaper7   r5   r8   )r3  cleanedcleaned_lowerprev
audio_iterr   _npaudio_arrayexc_play_via_tempfile_spoken_sentencesr   r)  ry   output_streamr'  rx   s            r   _speak_sentencez.stream_tts_to_speaker.<locals>._speak_sentence  s     "" -h77==??G #MMOO22599M)  ::<<&&u-->>FF ?$$W---+  ***~7||o--!"2?"23I#2:: %%"-	 ;  
 !,!+ H H%,,.. "!E++++&)nnU#)n&L&L%++K,?,?A,F,FGGGGH H!E '&z:>>>>> I I IBCHHHHHHHHHIs   %BE; -E; ;
F+F&&F+c                    d}	 ddl }t          j        dd          }|j        }|                    |d          5 }|                    d           |                    d           |                    d	           | D ]-}|                                r n|	                    |           .ddd           n# 1 swxY w Y   dd
l
m}  ||           n2# t          $ r%}t                              d|           Y d}~nd}~ww xY w|r(	 t          j        |           dS # t"          $ r Y dS w xY wdS # |r&	 t          j        |           w # t"          $ r Y w w xY ww xY w)z0Write PCM chunks to a temp WAV file and play it.Nr   r   F)suffixdeleter   rL      r,  )play_audio_filez!Temp-file TTS fallback failed: %s)wavetempfileNamedTemporaryFilenamer   setnchannelssetsampwidthsetframerater7  writeframestools.voice_moderK  r7   r5   r8   r`   unlinkOSError)	r?  stop_evttmp_pathrL  tmpwfr   rK  rB  s	            r   rC  z1stream_tts_to_speaker.<locals>._play_via_tempfile  s   H1NNN8YYsD)) .ROOA&&&OOA&&&OOE***!+ . .#??,, "!Eu----. . . . . . . . . . . . . . . =<<<<<)))) I I IBCHHHHHHHHI  	(+++++"    8 	(++++"   s   7C A0B7+C 7B;;C >B;?C D2 
DC>9D2 >DD2 	D 
D-,D-2E6E
E
EEEEr   z<thinkz</think>Tz Streaming TTS pipeline error: %s)"clearr   %DEFAULT_ELEVENLABS_STREAMING_MODEL_IDr;   r3   r`   r   r5   r8   r   r4   r   OutputStreamstartrV  r6   r7   recompileDOTALLr+   r7  queueEmptyr   r  rA   _SENTENCE_BOUNDARY_REsearchend
get_nowaitstopr   set)r&  r'  r(  r)  r<   r   r}   r   r   rB  sentence_bufmin_sentence_lenlong_flush_lenqueue_timeout_think_block_rerF  deltamend_posr3  rC  rD  r   ry   rE  rx   s    ` `                @@@@@@r   stream_tts_to_speakerrr  m  s   " s.8%''
NN<44	==X66==!5!*z8!D!DF F )0"55 	)NNUVVVV[/11
#G444 [ [ [YZZZZZ[ !),..B$&OO#(1G %4 % %M "''))))#W- ) ) )LL!@#FFF$(MMMMMM  ) ) )NN#H#NNN$(MMMMMM) ')*%>biPPP(	Ic (	I (	I (	I (	I (	I (	I (	I (	I (	I (	I (	I (	I (	IT	 	 	4 ##%% *	*"}==;   |$$~55#OL111#%L }.222|DD%%'' 2#OL111E!L
 +..r<@@L <''Jl,J,J*)00>>9%%'''1+GHH5x~~''((+;;;#+l#:L)))*? ##%% *	*Z	%%'''';   	   @ @ @93????????@ $""$$$##%%%%    $""$$$##%%%%   s  B(M3 C" !M3 "$D	M3 D		M3 :E
 	M3 
F1E=8M3 =F1
F,'M3 ,F11AM3 H" !M3 "/IM3 IC2M3 M M3 M.+M3 -M..M3 2O7 3
N"=NO7 N""O7 ((O 
OO7Q	;(P$#Q	$
P1.Q	0P11Q	__main__u   🔊 Text-to-Speech Tool Modulez2==================================================c                 >    	  |              dS # t           $ r Y dS w xY w)NTF)r4   )importerlabels     r   _checkrw  =  s9    	HJJJ4 	 	 	55	s   
 
z
Provider availability:z  Edge TTS:   	installedz$not installed (pip install edge-tts)z  ElevenLabs: elz&not installed (pip install elevenlabs)z    API Key:  ru   ri  znot setz  OpenAI:     oaiznot installedz2not set (VOICE_TOOLS_OPENAI_KEY or OPENAI_API_KEY)z  ffmpeg:     u	   ✅ foundu(   ❌ not found (needed for Telegram Opus)z
  Output dir: z  Configured provider: )registryr   a  Convert text to speech audio. Returns a MEDIA: path that the platform delivers as a voice message. On Telegram it plays as a voice bubble, on Discord/WhatsApp as an audio attachment. In CLI mode, saves to ~/voice-memos/. Voice and provider are user-configured, not model-selected.objectstringz:The text to convert to speech. Keep under 4000 characters.)typedescriptionz^Optional custom file path to save the audio. Defaults to ~/.hermes/audio_cache/<timestamp>.mp3ri   rj   )r~  
propertiesrequired)rO  r  
parametersr/   c                 r    t          |                     dd          |                     d                    S )Nri   rv   rj   r  )r  r3   )argskws     r   r   r   q  s6    2XXfb!!HH]++ -  -  - r   u   🔊)rO  toolsetschemahandlercheck_fnemojir   )Y__doc__r   r   r   loggingr`   rb  r_  rE   r[   rM  	threadingr   pathlibr   typingr   r   r   r   urllib.parser   	getLogger__name__r5   tools.managed_tool_gatewayr	   tools.tool_backend_helpersr
   r   r   r   r   r   r?   rm   r   r   r\  r   r   r  r+   r,   r   r   r;   rB   r  rH   rh   rs   r   r   r   r   r   r   r  r  tupler   r  r`  rd  r  r  r  r  r  r   	MULTILINEr!  r"  r#  r$  r%  QueueEventrr  printrw  r   r9   r>   tools.registryr{  
TTS_SCHEMAregisterrG   r   r   <module>r     s   ,     				  				                  0 0 0 0 0 0 0 0 0 0 0 0            		8	$	$ C C C C C C _ _ _ _ _ _ _ _  
  
  
    ' 4 6 (; %(  5 = = = = = -,.. $sCx.    &Ld38n L L L L L.T . . . .
 s  x}        L3 S d3PS8n Y\    0(s ( ($sCx. (UX ( ( ( (\)s ) )$sCx. )UX ) ) ) )`    D3 D D D D
D# D D D D
23 2S 2d38n 2QT 2 2 2 2t "&jV jV
jV#jV 	jV jV jV jV`    @U38_    $`4 ` ` ` ` #
#BCC  -..2:.//
"*_
%
%2:&''RZ%%
"*Z((RZ	666

>>>>	G		
9%%# #    & 9=	F FFF OF xt45	F F F FX z	E
+,,,	E(OOO   
E
$%%%	E
x&&1A6*J*Jv;;Pv
x
xyyy	E
z&&1CT*J*Jx;;Px
z
z{{{	E
TIBI.B$C$CR55
T
TUUU	E
e&&1F*N*Nc;;Tc
e
efff	E	o0022l558l	o 	o   
E
i++--g;;=g
i
ijjj	E
1/
1
1222F}V$$H	E
.H
.
./// $ # # # # #  n ![ 
 ! 	
 	
 H  
&  	- - $
	 	 	 	 	 	r   