+
    i                        R t ^ RIt^ RIt^ RIt^ RIt^ RIt^ RIt^ RIt^ RIt^ RI	t	^ RI
t
^ RIt^ RIt^ RIHt ^ RIHtHtHtHt ^ RIHt ]P,                  ! ]4      t^ RIHt ^ RIHtHt R tR tR	 t R
 t!Rt"Rt#Rt$Rt%Rt&Rt'Rt(Rt)Rt*Rt+Rt,R R lt-]-! 4       t.Rt/R R lt0R R lt1R R lt2R R  lt3R! R" lt4R# R$ lt5R% R& lt6R' R( lt7R) R* lt8R+ R, lt9R- R. lt:R/ R0 lt;RwR1 R2 llt<R3 R4 lt=R5 R6 lt>R7 R8 lt?]P                  ! R94      tA]P                  ! R:4      tB]P                  ! R;4      tC]P                  ! R<4      tD]P                  ! R=4      tE]P                  ! R>4      tF]P                  ! R?4      tG]P                  ! R@]P                  RA7      tI]P                  ! RB]P                  RA7      tJ]P                  ! RC4      tK]P                  ! RD4      tLRE RF ltMRwRG RH lltN]RI8X  Ed   ]O! RJ4       ]O! Rx4       RK tP]O! RL4       ]O! RM]P! ]R4      '       d   RNMRO 24       ]O! RP]P! ]RQ4      '       d   RNMRR 24       ]O! RS]P                  ! RT4      '       d   RUMRV 24       ]O! RW]P! ] RX4      '       d   RNMRY 24       ]O! RS]! 4       '       d   RUMRZ 24       ]O! R[]P                  ! R\4      '       d   R]MR^ 24       ]O! R_]2! 4       '       d   R`MRa 24       ]O! Rb]. 24       ]0! 4       tR]1! ]R4      tS]O! Rc]S 24       ^ RdITHUtU ReRfRgRhRiRjRkRlRmRjRnRgRo/RpRjRnRgRq//RrRm.//tV]UP                  ! RfRs]VRt ]=RuRv7       R# )ya?  
Text-to-Speech Tool Module

Supports five 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
- MiniMax TTS: High-quality with voice cloning, needs MINIMAX_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                     ^ RI p V # )z?Lazy import edge_tts. Returns the module or raises ImportError.Nedge_ttsr   s    +/home/ubuntu/hermes-agent/tools/tts_tool.py_import_edge_ttsr   2   s
    O    c                     ^ RI Hp  V # )zGLazy import ElevenLabs client. Returns the class or raises ImportError.
ElevenLabs)elevenlabs.clientr   r   s    r   _import_elevenlabsr   7   s    ,r   c                     ^ RI Hp  V # )zCLazy import OpenAI client. Returns the class or raises ImportError.)OpenAI)openair   )OpenAIClients    r   _import_openai_clientr   <   s    -r   c                     ^ RI p V # )zJLazy import sounddevice. Returns the module or raises ImportError/OSError.N)sounddevice)sds    r   _import_sounddevicer   A   s
    Ir   edgezen-US-AriaNeuralpNInz6obpgDQGcFmaJgBeleven_multilingual_v2eleven_flash_v2_5zgpt-4o-mini-ttsalloyzhttps://api.openai.com/v1zspeech-2.8-hdEnglish_Graceful_Ladyz https://api.minimax.io/v1/t2a_v2c                $    V ^8  d   QhR\         /#    returnstr)formats   "r   __annotate__r,   V   s     = = =r   c                  2    ^ RI Hp  \        V ! RR4      4      # )    get_hermes_dirzcache/audioaudio_cache)hermes_constantsr0   r*   r/   s    r   _get_default_output_dirr3   V   s    /~m];<<r   i  c                F    V ^8  d   QhR\         \        \        3,          /# r&   r   r*   r   )r+   s   "r   r,   r,   a   s      $sCx. r   c                      ^ RI Hp  V ! 4       pVP                  R/ 4      #   \         d    \        P                  R4       / u # \         d%   p\        P                  RTRR7       / u Rp?# Rp?ii ; i)z
Load TTS configuration from ~/.hermes/config.yaml.

Returns a dict with provider settings. Falls back to defaults
for any missing fields.
)load_configttsz9hermes_cli.config not available, using default TTS configzFailed to load TTS config: %sTexc_infoN)hermes_cli.configr7   getImportErrorloggerdebug	Exceptionwarning)r7   configes      r   _load_tts_configrD   a   sd    	1zz%$$ PQ	 6DI	s'   ! "A3A3A3A.(A3.A3c                R    V ^8  d   QhR\         \        \        3,          R\        /# )r'   
tts_configr(   r5   )r+   s   "r   r,   r,   t   s&     L Ld38n L Lr   c                x    V P                  R4      ;'       g    \        P                  4       P                  4       # )z%Get the configured TTS provider name.provider)r<   DEFAULT_PROVIDERlowerstrip)rF   s   &r   _get_providerrL   t   s,    NN:&::*:AACIIKKr   c                $    V ^8  d   QhR\         /# r&   bool)r+   s   "r   r,   r,   |   s     . .T .r   c                 2    \         P                  ! R4      RJ# )z+Check if ffmpeg is available on the system.ffmpegN)shutilwhich r   r   _has_ffmpegrU   |   s    <<!--r   c                F    V ^8  d   QhR\         R\        \         ,          /# )r'   mp3_pathr(   r*   r   )r+   s   "r   r,   r,      s        s  x}  r   c                   \        4       '       g   R# V P                  R^4      ^ ,          R,           p \        P                  ! RRV RRRR	R
RRRVR.R^R7      pVP                  ^ 8w  dF   \
        P                  RVP                  VP                  P                  RRR7      R,          4       R# \        P                  P                  V4      '       d'   \        P                  P                  V4      ^ 8  d   V# R#   \        P                   d    \
        P                  R4        R# \         d    \
        P                  R4        R# \         d$   p\
        P                  RTRR7        Rp?R# Rp?ii ; i)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..oggrQ   -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:N   Nz)ffmpeg OGG conversion timed out after 30szffmpeg not found in PATHz ffmpeg OGG conversion failed: %sr9   )rU   rsplit
subprocessrun
returncoder>   rA   stderrdecodeospathexistsgetsizeTimeoutExpiredFileNotFoundErrorr@   )rW   ogg_pathresultrC   s   &   r   _convert_to_opusru      sI    ==sA&q)F2HMtXy)CxG

 !NNM ++V]]-A-A'RZ-A-[\`-ac77>>(##(AA(EO  $$ DBC
 	  312   M91tLLMs<   A:C= 0$C= %C= =*E9*E93E9E9E9E44E9c                j    V ^8  d   QhR\         R\         R\        \         \        3,          R\         /# r'   textoutput_pathrF   r(   r*   r   r   )r+   s   "r   r,   r,      s2      3 S d3PS8n Y\ r   c                   "   \        4       pVP                  R/ 4      pVP                  R\        4      pVP                  W4      pVP	                  V4      G Rj  xL
  V#  L5i)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   r<   DEFAULT_EDGE_VOICECommunicatesave)rx   ry   rF   	_edge_ttsedge_configr|   communicates   &&&    r   _generate_edge_ttsr      s_      !"I..,KOOG%78E''4K


;
''' (s   AA#A!A#c                j    V ^8  d   QhR\         R\         R\        \         \        3,          R\         /# rw   rz   )r+   s   "r   r,   r,      s1     ( (s ( ($sCx. (UX (r   c                   \         P                  ! RR4      pV'       g   \        R4      hVP                  R/ 4      pVP                  R\        4      pVP                  R\
        4      pVP                  R4      '       d   RpMR	p\        4       pV! VR
7      p	V	P                  P                  V VVVR7      p
\        VR4      ;_uu_ 4       pV
 F  pVP                  V4       K  	  RRR4       V#   + '       g   i     T# ; i)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_idr[   opus_48000_64mp3_44100_128api_keyrx   r   r   output_formatwbN)rm   getenv
ValueErrorr<   DEFAULT_ELEVENLABS_VOICE_IDDEFAULT_ELEVENLABS_MODEL_IDendswithr   text_to_speechconvertopenwrite)rx   ry   rF   r   	el_configr   r   r   r   clientaudio_generatorfchunks   &&&          r   _generate_elevenlabsr      s     ii,b1GXYY|R0I}}Z)DEH}}Z)DEH F##''#%J(F++33#	 4 O 
k4	 	 A$EGGEN % 
! 	 
!	  s   C00D	c                j    V ^8  d   QhR\         R\         R\        \         \        3,          R\         /# rw   rz   )r+   s   "r   r,   r,      s1     ) )s ) )$sCx. )UX )r   c                r   \        4       w  r4VP                  R/ 4      pVP                  R\        4      pVP                  R\        4      pVP                  RV4      pVP	                  R4      '       d   RpMRp\        4       p	V	! W4R7      p
 V
P                  P                  P                  VVV VR	\        \        P                  ! 4       4      /R
7      pVP                  V4       T\        V
RR4      p\        V4      '       d	   V! 4        # #   \        T
RR4      p\        T4      '       d	   T! 4        i i ; i)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   modelr|   base_urlr[   opusmp3)r   r   zx-idempotency-key)r   r|   inputresponse_formatextra_headerscloseN)#_resolve_openai_audio_client_configr<   DEFAULT_OPENAI_MODELDEFAULT_OPENAI_VOICEr   r   audiospeechcreater*   uuiduuid4stream_to_filegetattrcallable)rx   ry   rF   r   r   
oai_configr   r|   r   r   r   responser   s   &&&          r   _generate_openai_ttsr      s    <=G"-JNN7$89ENN7$89E~~j(3H F## (*L'=F<<&&--+.DJJL0AB . 
 	,.E??G  .E??G s   AD (D6c                j    V ^8  d   QhR\         R\         R\        \         \        3,          R\         /# rw   rz   )r+   s   "r   r,   r,     s7     Q Q Q# Q4S> QVY Qr   c                H   ^ RI p\        P                  ! RR4      pV'       g   \        R4      hVP	                  R/ 4      pVP	                  R\
        4      pVP	                  R\        4      pVP	                  R^4      pVP	                  R	^4      p	VP	                  R
^ 4      p
VP	                  R\        4      pVP                  R4      '       d   RpMVP                  R4      '       d   RpMRpRVRV RRRRVRVR	V	R
V
/RRRRRRVR^//pRRRRV 2/pVP                  WV^<R 7      pVP                  4        VP                  4       pVP	                  R!/ 4      pVP	                  R"R+4      pV^ 8w  d$   VP	                  R#R$4      p\        R%V R&V 24      hVP	                  R'/ 4      P	                  R(R4      pV'       g   \        R)4      h\        P                  V4      p\        VR*4      ;_uu_ 4       pVP!                  V4       RRR4       V#   + '       g   i     T# ; i),ao  
Generate audio using MiniMax TTS API.

MiniMax returns hex-encoded audio data. Supports streaming (SSE) and
non-streaming modes. This implementation uses non-streaming for simplicity.

Args:
    text: Text to convert (max 10,000 characters).
    output_path: Where to save the audio file.
    tts_config: TTS config dict.

Returns:
    Path to the saved audio file.
NMINIMAX_API_KEYr   z@MINIMAX_API_KEY not set. Get one at https://platform.minimax.io/minimaxr   r   speedvolpitchr   .wavwavz.flacflacr   rx   streamFvoice_settingaudio_settingsample_ratei }  bitratei  r+   channelzContent-Typezapplication/jsonAuthorizationzBearer )jsonheadersrc   	base_respstatus_code
status_msgunknown errorzMiniMax TTS API error (code ): datar   z%MiniMax TTS returned empty audio datar   )requestsrm   r   r   r<   DEFAULT_MINIMAX_MODELDEFAULT_MINIMAX_VOICE_IDDEFAULT_MINIMAX_BASE_URLr   postraise_for_statusr   RuntimeErrorbytesfromhexr   r   )rx   ry   rF   r   r   	mm_configr   r   r   r   r   r   audio_formatpayloadr   r   rt   r   r   r   	hex_audioaudio_bytesr   s   &&&                    r   _generate_minimax_ttsr     s-    ii)2.G[\\y"-IMM'#89E}}Z)ABHMM'1%E
--q
!CMM'1%E}}Z)ABH F##			g	&	& 	%U3U	
 	5vlq	
G& 	*77),G
 }}XWb}QH]]_F

;+I--r2Ka]]<A
9+c*VWW

62&**7B7IBCC --	*K	k4	 	 A	 
!  
!	  s   4HH!	c                $    V ^8  d   QhR\         /# r&   rN   )r+   s   "r   r,   r,   t  s       r   c                 j     ^ RI p V P                  P                  R4      RJ#   \         d     R# i ; i)z=Check if the neutts engine is importable (installed locally).NneuttsF)importlib.utilutil	find_specr@   )	importlibs    r   _check_neutts_availabler   t  s6    ~~''1== s    # 22c                $    V ^8  d   QhR\         /# r&   r)   )r+   s   "r   r,   r,   }  s     D D3 Dr   c                 b    \        \        \        4      P                  R,          R,          4      # )z9Return path to the bundled default voice reference audio.neutts_sampleszjo.wavr*   r   __file__parentrT   r   r   _default_neutts_ref_audior   }  "    tH~$$'77(BCCr   c                $    V ^8  d   QhR\         /# r&   r)   )r+   s   "r   r,   r,     s     D D# Dr   c                 b    \        \        \        4      P                  R,          R,          4      # )z>Return path to the bundled default voice reference transcript.r   zjo.txtr   rT   r   r   _default_neutts_ref_textr     r   r   c                j    V ^8  d   QhR\         R\         R\        \         \        3,          R\         /# rw   rz   )r+   s   "r   r,   r,     s1     2 23 2S 2d38n 2QT 2r   c                L   ^ RI pVP                  R/ 4      pVP                  RR4      ;'       g    \        4       pVP                  RR4      ;'       g    \        4       pVP                  RR4      pVP                  RR	4      pTp	VP	                  R
4      '       g!   VP                  R^4      ^ ,          R
,           p	\        \        \        4      P                  R,          4      p
VP                  V
RV RV	RVRVRVRV.p\        P                  ! VRR^xR7      pVP                  ^ 8w  d   VP                  P                  4       pVP!                  4        Uu. uF  qP#                  R4      '       d   K  VNK  	  pp\%        R\'        ^
4      P)                  V4      ;'       g    R 24      hW8w  do   \*        P,                  ! R4      pV'       d;   VRV	RRRV.p\        P                  ! VR^R7       \.        P0                  ! V	4       V# \.        P2                  ! W4       V# u upi )zGenerate 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.
Nr   	ref_audior   ref_textr   zneuphonic/neutts-air-q4-ggufdevicecpur   rZ   zneutts_synth.pyz--textz--outz--ref-audioz
--ref-textz--modelz--deviceT)rb   rx   rc   zOK:zNeuTTS synthesis failed: r   rQ   r\   ra   z	-loglevelerror)checkrc   )sysr<   r   r   r   rg   r*   r   r   r   
executablerh   ri   rj   rk   rK   
splitlines
startswithr   chrjoinrR   rS   rm   removerename)rx   ry   rF   r   neutts_configr   r   r   r   wav_pathsynth_scriptcmdrt   rk   lerror_linesrQ   conv_cmds   &&&               r   _generate_neuttsr    s    NN8R0M!!+r2QQ6O6QI  R0NN4L4NHg'EFEx/F H''%%c1-a069tH~,,/@@AL$yh5FC ^^C4MFA$$&"("3"3"5Q"5Q\\%=Pqq"5Q6s2w||K7P7c7cTc6deff h'hk7KXHNN84<IIh
  IIh, Rs   H!3H!c                R    V ^8  d   QhR\         R\        \         ,          R\         /# )r'   rx   ry   r(   rX   )r+   s   "r   r,   r,     s4     nV nV
nV#nV 	nVr   c                H  a aa S '       d   S P                  4       '       g   \        P                  ! RRRR/RR7      # \        S 4      \        8  d.   \
        P                  R\        S 4      \        4       S R\         o \        4       o\        S4      p\        P                  ! RR	4      P                  4       pVR
8H  pV'       d   \        V4      P                  4       pMy\        P                  P                  4       P!                  R4      p\        \"        4      pVP%                  RRR7       V'       d   VR29   d   VRV R2,          pMVRV R2,          pVP&                  P%                  RRR7       \)        V4      o VR8X  d0    \+        4        \
        P/                  R4       \1        S SS4       EMVR8X  d0    \3        4        \
        P/                  R4       \5        S SS4       EMkVR8X  d%   \
        P/                  R4       \7        S SS4       EM@VR8X  dP   \9        4       '       g   \        P                  ! RRRR/RR7      # \
        P/                  R4       \;        S SS4       MRp \=        4        V'       d   \
        P/                  R4        \>        P@                  ! 4       p	^ RI!p
V
PD                  PG                  ^R7      ;_uu_ 4       pVPI                  VV V3R l4      PK                  ^<R7       RRR4       MQ\9        4       '       d&   \
        P/                  R 4       Rp\;        S SS4       M\        P                  ! RRRR!/RR7      # \        PR                  PU                  S4      '       d%   \        PR                  PW                  S4      ^ 8X  d!   \        P                  ! RRRR"V R#2/RR7      # RpVR39   d0   SPY                  R4      '       g   \[        S4      pV'       d   VoRpMVR49   d   SPY                  R4      p\        PR                  PW                  S4      p\
        P/                  R$SVR% V4       R&S 2pV'       d   R'V 2p\        P                  ! RRR(SR)VR*VR+V/RR7      #   \,         d     \        P                  ! RRRR/RR7      u # i ; i  \,         d     \        P                  ! RRRR/RR7      u # i ; i  \,         d    Rp ELmi ; i  + '       g   i     EL; i  \L         d&    \>        PN                  ! \Q        S SS4      4        ELi ; i  \\         dE   pR,T R-T 2p\
        P_                  R.T4       \        P                  ! RRRT/RR7      u Rp?# Rp?i\`         dG   pR/T R-T 2p\
        P_                  R.TRR07       \        P                  ! RRRT/RR7      u Rp?# Rp?i\b         dG   pR1T R-T 2p\
        P_                  R.TRR07       \        P                  ! RRRT/RR7      u Rp?# Rp?ii ; i)5a3  
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.
successFr   zText is required)ensure_asciiz.TTS text too long (%d chars), truncating to %dNHERMES_SESSION_PLATFORMr   telegramz%Y%m%d_%H%M%ST)parentsexist_okr   r   tts_r[   z.mp3z`ElevenLabs provider selected but 'elevenlabs' package not installed. Run: pip install elevenlabsz$Generating speech with ElevenLabs...z<OpenAI provider selected but 'openai' package not installed.z$Generating speech with OpenAI TTS...r   z%Generating speech with MiniMax 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...)max_workersc                  F   < \         P                  ! \        SS S4      4      # N)asynciori   r   )file_strrx   rF   s   r   <lambda>%text_to_speech_tool.<locals>.<lambda>-  s    GKK0B4S]0^$_r   rc   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: )z,TTS audio saved: %s (%s bytes, provider: %s),zMEDIA:z[[audio_as_voice]]
	file_path	media_tagrH   voice_compatiblezTTS configuration error (r   z%szTTS dependency missing (r9   zTTS generation failed ()r   r   )r   r   r   )r   r   )2rK   r   dumpslenMAX_TEXT_LENGTHr>   rA   rD   rL   rm   r   rJ   r   
expanduserdatetimenowstrftimeDEFAULT_OUTPUT_DIRmkdirr   r*   r   r=   infor   r   r   r   r   r  r   r  get_running_loopconcurrent.futuresfuturesThreadPoolExecutorsubmitrt   r   ri   r   rn   ro   rp   r   ru   r   r   rr   r@   )rx   ry   rH   platform	want_opusr   	timestampout_diredge_availableloop
concurrentpoolr"  	opus_path	file_sizer!  rC   	error_msgr  rF   s   f&                @@r   text_to_speech_toolr=    s   * tzz||zz9eW6HIX]^^ 4y?"GTTcd$_%!#JZ(H yy2B7==?HZ'I %002	%%))+44_E	)*dT2 %==D4"88ID4"88I 4$79~HuV|#'"$ KK>? x<!'%' KK>? x<"KK?@!$*=!*,,zzu F# !&	' '
 KKBCT8Z8 "N' " @AP"335D-#++>>1>MMQU_ &&, NM )**WX# x<zzu E# !&	' ' ww~~h''277??8+D+I::5H
RST "# # !44X=N=Nv=V=V(2I$#' 11'008GGOOH-	BHQZ[\P]`hi XJ'	.yk:Izzt 0
  	u  'zzu# !&' ''  'zzu[# !&' ''6  '!&' NMM $ PKK 24: NOP`  V/zQC@	T9%zz9eWi@uUU V.xjA3?	T9t4zz9eWi@uUU V-hZs1#>	T9t4zz9eWi@uUU	Vs0  T4 

Q? +T4  
R, 
BT4 %T4 2
S <T4 T4 =T (S-?T AT4 $T4 >AT4 T4 "A5T4 &T4 ?'R)&T4 (R))T4 ,'ST4 ST4 S*&T4 )S**T4 -S>	8T <T4 >T ,T1-T4 0T11T4 4X!?9U>8X!>X!X!;WX!X!X!;XX!X!c                $    V ^8  d   QhR\         /# r&   rN   )r+   s   "r   r,   r,   s  s       r   c                 v    \        4        R#   \         d     Mi ; i \        4        \        P                  ! R4      '       d   R# M  \         d     Mi ; i \        4        \        4       '       d   R# M  \         d     Mi ; i\        P                  ! R4      '       d   R# \        4       '       d   R# R# )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.
Tr   r   F)r   r=   r   rm   r   r   _has_openai_audio_backendr   rT   r   r   check_tts_requirementsrA  s  s     99)** + $&& ' 	yy"##  s/   
 &A	 	AAA8 8BBc                F    V ^8  d   QhR\         \        \        3,          /# r&   )tupler*   )r+   s   "r   r,   r,     s      U38_ r   c                    \        4       p V '       d	   V \        3# \        R4      pVf'   Rp\        4       '       d
   VR,          p\	        V4      hVP
                  \        VP                  P                  R4       R2R4      3# )z@Return direct OpenAI audio config or a managed gateway fallback.openai-audioz8Neither 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     s    13N6662>BOL%''NNG!!**G))005
6a8$-  r   c                $    V ^8  d   QhR\         /# r&   rN   )r+   s   "r   r,   r,     s     ` `4 `r   c                 N    \        \        4       ;'       g    \        R4      4      # )zPReturn True when OpenAI audio can use direct credentials or the managed gateway.rE  )rO   r
   r   rT   r   r   r@  r@    s    ,.^^2N~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                0    V ^8  d   QhR\         R\         /# )r'   rx   r(   r)   )r+   s   "r   r,   r,     s      # # r   c                   \         P                  RV 4      p \        P                  RV 4      p \        P                  RV 4      p \        P                  RV 4      p \
        P                  RV 4      p \        P                  RV 4      p \        P                  RV 4      p \        P                  RV 4      p \        P                  RV 4      p \        P                  RV 4      p V P                  4       # )z:Remove markdown formatting that shouldn't be spoken aloud. z\1r   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_NLrK   )rx   s   &r   _strip_markdown_for_ttsra    s    c4(D<<t$D;;r4 D<<t$D>>%&Dud+D>>"d#DR&D::b$DVT*D::<r   c                    V ^8  d   QhR\         P                  R\        P                  R\        P                  R\        \
        \        .R3,          ,          /# )r'   
text_queue
stop_eventtts_done_eventdisplay_callbackN)queueQueue	threadingEventr   r   r*   )r+   s   "r   r,   r,     sR     F FFF OOF xt45	Fr   c           	       aaaaaaaa VP                  4         RoRo\        o\        o\        4       pVP	                  R/ 4      pVP	                  RS4      oVP	                  RVP	                  RS4      4      o\
        P                  ! RR4      pV'       g   \        P                  R4       MG \        4       pV! VR	7      oSe0    \        4       pVP                  R^RR7      oSP                  4        Rp
^p^dpRp. o\"        P$                  ! R\"        P&                  R7      pR VVVVVVVV3R llpR oSP)                  4       '       g    V P	                  VR7      pTf2   TP1                  RT
4      p
T
P3                  4       '       d	   T! T
4       MT
T,          p
TP1                  RT
4      p
RT
9   d
   RT
9  d   K   \4        P7                  T
4      pTf   K  TP9                  4       pT
RT pT
TR p
\/        TP3                  4       4      T8  d   TT
,           p
K  T! T4       Ki    V P;                  4        K    \         d    \        P                  R
4        ELi ; i  \        \        3 d$   p	\        P                  RT	4       Ro Rp	?	ELRp	?	i\          d$   p	\        P                  RT	4       Ro Rp	?	ELRp	?	ii ; i  \*        P,                   d     \/        T
4      T8  d   T! T
4       Rp
 EK  i ; i  \*        P,                   d     M2i ; i  \          d!   p	\        P                  RT	4        Rp	?	MRp	?	ii ; iSe4    SP=                  4        SP?                  4        M  \          d     Mi ; iTPA                  4        R#   Se4    SP=                  4        SP?                  4        M  \          d     Mi ; iTPA                  4        i ; i)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.
Nr   r   streaming_model_idr   r   r   z8ELEVENLABS_API_KEY not set; streaming TTS audio disabledr   z8elevenlabs package not installed; streaming TTS disabled]  int16)
sampleratechannelsdtypezsounddevice not available: %sz#sounddevice OutputStream failed: %sg      ?z<think[\s>].*?</think>rQ  c                $    V ^8  d   QhR\         /# )r'   sentencer)   )r+   s   "r   r,   +stream_tts_to_speaker.<locals>.__annotate__  s     (	I (	Ic (	Ir   c                  < SP                  4       '       d   R# \        V 4      P                  4       pV'       g   R# VP                  4       P	                  R4      pS
 F*  pVP                  4       P	                  R4      V8X  g   K)   R# 	  S
P                  V4       Se	   S! V 4       Sf   R# \        V4      \        8  d
   VR\         p SP                  P                  VSSRR7      pSef   V F]  pSP                  4       '       d    R# ^ RI
pVP                  WVP                  R7      pSP                  VP                  R^4      4       K_  	  R# S	! VS4       R#   \         d"   p\         P#                  RT4        Rp?R# Rp?ii ; i)z6Display sentence and optionally generate + play audio.Nz.!,	pcm_24000r   )rq  z!Streaming TTS sentence failed: %sr   )is_setra  rK   rJ   rK  appendr$  r%  r   r   numpy
frombufferrn  r   reshaper@   r>   rA   )rs  cleanedcleaned_lowerprev
audio_iterr   _npaudio_arrayexc_play_via_tempfile_spoken_sentencesr   rf  r   output_streamrd  r   s   &        r   _speak_sentence.stream_tts_to_speaker.<locals>._speak_sentence  s\     ""-h7==?G#MMO2259M)::<&&u-> * $$W-+ *~7|o-!"2?3I#22:: %%"-	 ; 
 !,!+%,,..!+&)nnU))n&L%++K,?,?A,FG ", 'z:> IBCHHIs%   ?E  AE  	E   F+FFc                   Rp ^ RI p\        P                  ! RRR7      pVP                  pVP	                  VR4      ;_uu_ 4       pVP                  ^4       VP                  ^4       VP                  R4       V  F,  pVP                  4       '       d    MVP                  V4       K.  	  RRR4       ^ RI
Hp V! V4       V'       d    \        P                   ! V4       R# R#   + '       g   i     LA; i  \         d!   p\        P                  RT4        Rp?L\Rp?ii ; i  \"         d     R# i ; i  T'       d*    \        P                   ! T4       i   \"         d     i i ; ii ; i)	z0Write PCM chunks to a temp WAV file and play it.Nr   F)suffixdeleter   rm  )play_audio_filez!Temp-file TTS fallback failed: %s)wavetempfileNamedTemporaryFilenamer   setnchannelssetsampwidthsetframeraterw  writeframestools.voice_moder  r@   r>   rA   rm   unlinkOSError)	r  stop_evttmp_pathr  tmpwfr   r  r  s	   &&       r   r  1stream_tts_to_speaker.<locals>._play_via_tempfile;  s#   H11N88YYsD))ROOA&OOA&OOE*!+#??,,!u- ",	 * =) 		(+  *)  IBCHHI #  		(+"  sy   AC8 A&C%,C8 D& %C5	0C8 8D#DD8 D##D8 &D54D58	E+EE+E'$E+&E''E+r  z<thinkz</think>z Streaming TTS pipeline error: %s)!clearr   %DEFAULT_ELEVENLABS_STREAMING_MODEL_IDrD   r<   rm   r   r>   rA   r   r=   r   OutputStreamstartr  r?   r@   recompileDOTALLrw  rg  Emptyr$  rW  rK   _SENTENCE_BOUNDARY_REsearchend
get_nowaitstopr   set)rc  rd  re  rf  rF   r   r   r   r   r  sentence_bufmin_sentence_lenlong_flush_lenqueue_timeout_think_block_rer  deltamend_posrs  r  r  r   r   r  r   s   &f&f                @@@@@@r   stream_tts_to_speakerr    s   " s.8%'
NN<4	==X6==!5!*z8!DF ))0"5NNUV[/1
#G4 !),.B$&OO#(1G %4 %M "'') ')**%>biiP(	I (	IT	4 ##%%"}= }.222|D%%''#L1E!L
 +..r<@L <'Jl,J )00>9%%''1+GH5x~~'(+;;#+l#:L) %%'_  [YZ[ $W- )LL!@#F$(M  )NN#H#N$(M)h ;; |$~5#L1#%LX ;; 
  @93??@ $""$##%  $""$##% s  BL 0H? L .I& 6AL K	 "+L BL -L  =L ? I#L "I##L &K7JL K"K#K;L KL 	/K=8L <K==L  LL N LL M&M<N MN  M/ /M=<M=O N98O9OOOO__main__u   🔊 Text-to-Speech Tool Modulec                 :     V ! 4        R #   \          d     R# i ; i)TF)r=   )importerlabels   &&r   _checkr    s!    	J 		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:  r   r  znot setz  OpenAI:     oaiznot installedz2not set (VOICE_TOOLS_OPENAI_KEY or OPENAI_API_KEY)z  MiniMax:    r   zAPI key setznot set (MINIMAX_API_KEY)z  ffmpeg:     u	   ✅ foundu(   ❌ not found (needed for Telegram Opus)z
  Output dir: z  Configured provider: )registryr  r   descriptiona  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.
parameterstypeobject
propertiesrx   stringz:The text to convert to speech. Keep under 4000 characters.ry   z^Optional custom file path to save the audio. Defaults to ~/.hermes/audio_cache/<timestamp>.mp3requiredr8   c                 Z    \        V P                  R R4      V P                  R4      R7      # )rx   r   ry   )rx   ry   )r=  r<   )argskws   &,r   r  r    s$    2XXfb!HH]+ -r   u   🔊)r  toolsetschemahandlercheck_fnemojir  z2==================================================)X__doc__r  r'  r   loggingrm   rg  r  rR   rh   r  ri  r   pathlibr   typingr   r   r   r   urllib.parser   	getLogger__name__r>   tools.managed_tool_gatewayr   tools.tool_backend_helpersr	   r
   r   r   r   r   rI   r}   r   r   r  r   r   rH  r   r   r   r3   r*  r%  rD   rL   rU   ru   r   r   r   r   r   r   r   r  r=  rA  r   r@  r  r  rV  rX  rY  rZ  r[  r\  	MULTILINEr]  r^  r_  r`  ra  r  printr  r   rB   rH   tools.registryr  
TTS_SCHEMAregisterrT   r   r   <module>r     s  .     	  	       0 0  			8	$ C _


  ' 4 6 (; %(  5 ' 2 = = -. &L.
 L0(\)^QpD
D
2pnVhD$` 

#BC  -.::./
**_
%::&'ZZ%
**Z(ZZ	6


>>	G	

9%FX z	
+,	(O 

$%	N&1A6*J*J;Pvw
xy	N&1CT*J*J;Pxy
z{	NBII.B$C$C5S
TU	N&1F*N*N;Tcd
ef	02258l
m	o 
NBII6G,H,H=Nij
kl	N+--;=gh
ij	/0
12FV$H	#H:
./ $   n[ 	
 	VH
& 	  	- $
	r   