
    )j                    P   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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 ddlmZ  ej        e          ZddZddlmZ dd	l m!Z!m"Z"m#Z#m$Z$ dd
l%m&Z& d Z'd Z(d Z)d Z*d Z+d Z,d Z-dZ.dZ/dZ0dZ1dZ2dZ3dZ4dZ5dZ6dZ7dZ8dZ9dZ:dZ;d Z<d!Z=d"Z>d#Z?d$Z@d%ZAd&ZBd'ZCd(ZDd)ZEd*ZFd&ZGd+ZHd$ZId,ZJd-ZKd.eLfd/ZM eM            ZNd0d1d2d3d4d5d3d6d6d0d7
ZOeeLePf         eQd8<   d0d0d3d3d3d3d9d:d;ZReeLePf         eQd<<   dd=ed>eSd.eSfd?ZTd4ZUeUZV	 dd@eeL         dAeeeLef                  d.ePfdBZWd.eeLef         fdCZXdAeeLef         d.eLfdDZY eZh dE          Z[dFZ\dGZ] eZh dH          Z^d0Z_dAeeLef         dIeLd.eeLef         fdJZ`dAeeLef         dIeLd.eeLef         fdKZadLeeLef         d.eSfdMZbd@eLdAeeLef         d.eeeLef                  fdNZcdOeLdPeLd@eLdAeeLef         d.eeL         f
dQZdd@eLd.eSfdRZedAeeLef         fdSZfdLeeLef         d.egfdTZh	 ddLeeLef         dPeeL         d.eLfdUZidLeeLef         d.eSfdVZjdWeLdXePd.eeL         fdYZkd=eLdZeeL         d.eLfd[ZldWeLd\eeLeLf         d.eLfd]Zmd^ejn        d.dfd_Zod`eLdaegd.ejp        fdbZqdcedLeeLef         d.efddZrdOeLdPeLdeeLdLeeLef         dAeeLef         d.eLfdfZsddAeeeLef                  d.eSfdgZtd.eSfdhZudieLd.eeL         fdjZvdOeLdPeLdAeeLef         d.eLfdkZwdOeLdPeLdAeeLef         d.eLfdlZxdOeLdPeLdAeeLef         d.eLfdmZydnZzdoZ{ ej|        dpdq}                    ez          z   drz   dq}                    e{          z   dsz   ej~        t          Z ej|        duej        t          Zdd=ed>eSd.eSfdvZdOeLd.eLfdwZdOeLdPeLdAeeLef         d.eLfdxZdOeLdPeLdAeeLef         d.eLfdyZdOeLdPeLdAeeLef         d.eLfdzZeIeJeKfd{ed|ePd}ePd~ePd.ef
dZdeeLef         d.ee         fdZdeeLef         d.eLfdZdeLd.eSfdZdeeLef         deLd.eSfdZdeLd.eLfdZded.eLfdZddOeLdeLd.eLfdZ	 ddOeLdeeLef         deeL         d.eLfdZdOeLdPeLdAeeLef         d.eLfdZd.eSfdZd.eSfdZd.eLfdZd.eLfdZdOeLdPeLdAeeLef         d.eLfdZi aeeLef         eQd<   d.eSfdZd.efdZdeLded.eLfdZdOeLdPeLdAeeLef         d.eLfdZi aeeLef         eQd<   dOeLdPeLdAeeLef         d.eLfdZ	 ddOeLdPeeL         d.eLfdZd.eSfdZd.eeLeLf         fdZd.eSfdZ ej|        d          Z ej|        d          Z ej|        d          Z ej|        d          Z ej|        d          Z ej|        d          Z ej|        d          Z ej|        dej        t          Z ej|        dej        t          Z ej|        d          Z ej|        d          ZdOeLd.eLfdZ	 ddej        dej        dej        deeeLgdf                  fdZedk    r- ed            ed           d Z ed            ed ee'd          rdnd             ed ee(d          rdnd             ed ed          rdnd             ed ee)d¦          rdndÛ             ed e$            rdndě             ed edƦ          rdndț             ed e            rdndʛ             ed eu            rdnd͛             edeN             eX            Z eYe          Z ede            ddlmZmZ dddddd֜dd e             d؝d֜dٜdOgdڜdۜZ ej        dded݄ edެߦ           dS )a  
Text-to-Speech Tool Module

Built-in 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
- Mistral (Voxtral TTS): Multilingual, native Opus, needs MISTRAL_API_KEY
- Google Gemini TTS: Controllable, 30 prebuilt voices, needs GEMINI_API_KEY
- xAI TTS: Grok voices, uses xAI Grok OAuth credentials or XAI_API_KEY
- NeuTTS (local, free, no API key): On-device TTS via neutts
- KittenTTS (local, free, no API key): On-device 25MB model
- Piper (local, free, no API key): OHF-Voice/piper1-gpl neural VITS, 44 languages

Custom command providers:
- Users can declare any number of named providers with ``type: command``
  under ``tts.providers.<name>`` in ``~/.hermes/config.yaml``. Hermes
  writes the input text to a temp file and runs the configured shell
  command, which must produce the audio file at the expected path.
  See the Local Command section of ``website/docs/user-guide/features/tts.md``.

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)display_hermes_homec                 ~    	 ddl m} n%# t          $ r t          j        | |          cY S w xY w ||           }||n|S )a  Read env values through the live config module.

    Tests may monkeypatch and later restore ``hermes_cli.config.get_env_value``
    before this module is imported. Resolve the helper at call time so TTS does
    not keep a stale imported function for the rest of the test process.
    r   )get_env_value)hermes_cli.configr   ImportErrorosgetenv)namedefault_get_env_valuevalues       3/home/ubuntu/.hermes/hermes-agent/tools/tts_tool.pyr   r   :   sl    (EEEEEEE ( ( (yw'''''(N4  Em77.s   	 ++)resolve_managed_tool_gateway)managed_nous_tools_enabled%nous_tool_gateway_unavailable_messageprefers_gatewayresolve_openai_audio_api_key)hermes_xai_user_agentc                      	 ddl m}   | dd           n9# t          $ r Y n-t          $ r!}t          t	          |                    d}~ww xY wddl}|S )z?Lazy import edge_tts. Returns the module or raises ImportError.r   ensureztts.edgeFpromptN)tools.lazy_depsr   r   	Exceptionstredge_tts)_lazy_ensureer#   s      r   _import_edge_ttsr&   U   s    "::::::Z.....    " " "#a&&!!!"OOOO    
A	AAAc                      	 ddl m} m}  |dd           n9# t          $ r Y n-t          $ r!}t          t          |                    d}~ww xY wddlm} |S )a  Lazy import ElevenLabs client. Returns the class or raises ImportError.

    Calls :func:`tools.lazy_deps.ensure` first so the SDK gets installed on
    demand if the user picked ElevenLabs as their TTS provider but never ran
    the post-setup hook (e.g. enabled it by editing config.yaml directly).
    Raises ``ImportError`` on lazy-install failure so existing callers'
    error-handling paths keep working.
    r   )FeatureUnavailabler   ztts.elevenlabsFr   N)
ElevenLabs)r    r)   r   r   r!   r"   elevenlabs.clientr*   )r)   r   r%   r*   s       r   _import_elevenlabsr,   a   s    ">>>>>>>>.....    	 " " "#a&&!!!",,,,,,s    
A	AA		Ac                      ddl m}  | S )zCLazy import OpenAI client. Returns the class or raises ImportError.r   )OpenAI)openair.   )OpenAIClients    r   _import_openai_clientr1   v   s    ------    c                      	 ddl m}   | dd           n9# t          $ r Y n-t          $ r!}t          t	          |                    d}~ww xY wddlm} |S )aj  Lazy import Mistral client. Returns the class or raises ImportError.

    Calls :func:`tools.lazy_deps.ensure` first so the ``mistralai`` SDK gets
    installed on demand if the user picked Mistral as their STT/TTS provider
    but never ran the post-setup hook (e.g. enabled it by editing config.yaml
    directly). Mirrors the ElevenLabs lazy-import path.
    r   r   ztts.mistralFr   N)Mistral)r    r   r   r!   r"   mistralai.clientr4   )r   r%   r4   s      r   _import_mistral_clientr6   {   s    "******}U+++++    " " "#a&&!!!"((((((Nr'   c                      ddl } | S )zJLazy import sounddevice. Returns the module or raises ImportError/OSError.r   N)sounddevice)sds    r   _import_sounddevicer:      s    Ir2   c                      ddl m}  | S )z?Lazy import KittenTTS. Returns the class or raises ImportError.r   	KittenTTS)	kittenttsr=   r<   s    r   _import_kittenttsr?      s    ######r2   c                      ddl m}  | S )at  Lazy import Piper. Returns the PiperVoice class or raises ImportError.

    Piper is an optional, fully-local neural TTS engine (Home Assistant /
    Open Home Foundation). ``pip install piper-tts`` provides cross-platform
    wheels (Linux / macOS / Windows, x86_64 + ARM64) with embedded espeak-ng.
    Voice models (.onnx + .onnx.json) are downloaded on first use.
    r   
PiperVoice)piperrB   rA   s    r   _import_piperrD      s     !     r2   edgezen-US-AriaNeuralpNInz6obpgDQGcFmaJgBeleven_multilingual_v2eleven_flash_v2_5zgpt-4o-mini-ttsz!KittenML/kitten-tts-nano-0.8-int8Jasperzen_US-lessac-mediumalloyzhttps://api.openai.com/v1zspeech-02-hdEnglish_expressive_narratorz https://api.minimax.io/v1/t2a_v2zvoxtral-mini-tts-2603z$c69964a6-ab8b-4f8a-9465-ec0925096ec8eveen]    Fzhttps://api.x.ai/v1zgemini-2.5-flash-preview-ttsKorez0https://generativelanguage.googleapis.com/v1betatts_audio_tags      returnc                  @    ddl m}  t           | dd                    S )Nr   get_hermes_dirzcache/audioaudio_cache)hermes_constantsrW   r"   rV   s    r   _get_default_output_dirrZ      s.    //////~~m];;<<<r2   i  i   i:  i'  i   }  i  )
rE   r/   xaiminimaxmistralgemini
elevenlabsneuttsr>   rC   PROVIDER_MAX_TEXT_LENGTHi0u  i@  )	eleven_v3eleven_ttv_v3rG   eleven_multilingual_v1eleven_english_sts_v2eleven_english_sts_v1eleven_flash_v2rH    ELEVENLABS_MODEL_MAX_TEXT_LENGTHr   r   c                     t          | t                    r| S | |S t          | t          t          f          rt          |           S t          | t                    r2|                                                                 }|dv rdS |dv rdS |S )zNCoerce common YAML/env bool spellings without treating random strings as true.N>   1onyestrueenabledT>   0noofffalsedisabledF)
isinstanceboolintfloatr"   striplower)r   r   
normalizeds      r   _config_boolr|      s    % }%#u&& E{{% [[]]((**
>>>4@@@5Nr2   provider
tts_configc                 l   | st           S |                                                                 }|pi }t          |                    |          t
                    r|                    |          ni }|r|                    d          nd}t          |t                    rd}t          |t                    r|dk    r|S |dk    r[|pi                     d          pt          }t                              t          |                                                    }|r|S |t          v rt          |         S |t          vrot          ||          }t          |          rP|                    d          }	t          |	t                    rd}	t          |	t                    r|	dk    r|	S t          S t           S )a  Return the input-character cap for *provider*.

    Resolution order:
      1. ``tts.<provider>.max_text_length`` (user override in config.yaml)
      2. ``tts.providers.<provider>.max_text_length`` for user-declared
         command providers
      3. ElevenLabs model-aware table (keyed on configured ``model_id``)
      4. ``PROVIDER_MAX_TEXT_LENGTH`` default
      5. ``DEFAULT_COMMAND_TTS_MAX_TEXT_LENGTH`` when the provider is a
         command-type user provider without an explicit cap
      6. ``FALLBACK_MAX_TEXT_LENGTH`` (4000)

    Non-positive or non-integer overrides fall through to the default so a
    broken config can't accidentally disable truncation entirely.
    max_text_lengthNr   r`   model_id)FALLBACK_MAX_TEXT_LENGTHrz   ry   ru   getdictrv   rw   DEFAULT_ELEVENLABS_MODEL_IDri   r"   rb   BUILTIN_TTS_PROVIDERS_get_named_provider_config_is_command_provider_config#DEFAULT_COMMAND_TTS_MAX_TEXT_LENGTH)
r}   r~   keycfgprov_cfgoverrider   mappednamednamed_overrides
             r   _resolve_max_text_lengthr     s   &  (''
..


 
 
"
"C

C  *#''#,,==Eswws|||2H2:Dx||-...H(D!! (C   X\\
lN''
33R7R155c(mm6I6I6K6KLL 	M
&&&',, '''*344&u-- 	7"YY'899N.$// &!%.#.. &>A3E3E%%66##r2   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)r   r   r   r   loggerdebugr!   warning)r   configr%   s      r   _load_tts_configr   A  s    	111111zz%$$$   PQQQ			   6DIII						s!   %( &B	BA=7B=Bc                     |                      d          pt                                                                          S )z%Get the configured TTS provider name.r}   )r   DEFAULT_PROVIDERrz   ry   )r~   s    r   _get_providerr   T  s2    NN:&&:*:AACCIIKKKr2   >
   r\   rE   rC   r_   ra   r/   r]   r^   r>   r`   x   mp3>   r   oggwavflacr   c                     t          | t                    si S |                     |          }t          |t                    r|ni S )zBReturn a provider config block if it's a dict, else an empty dict.)ru   r   r   )r~   r   sections      r   _get_provider_sectionr     sC    j$'' 	nnT""G $//777R7r2   c                 
   t          | d          }t          |t                    r|                    |          nd}t          |t                    r|S |                                t
          vrt          | |          }|r|S i S )a  Return the config dict for a user-declared provider.

    Looks up ``tts.providers.<name>`` first (the canonical location), and
    falls back to ``tts.<name>`` so users who followed the built-in layout
    still work. Returns an empty dict when the provider is not declared.
    	providersN)r   ru   r   r   rz   r   )r~   r   r   r   legacys        r   r   r     s     &j+>>I%/	4%@%@JimmD!!!dG'4    zz||000&z488 	MIr2   r   c                 j   t          | t                    sdS t          |                     d          pd                                                                          }|r|dk    rdS |                     d          }t          |t                    o t          |                                          S )z;Return True when *config* declares a command-type provider.Ftype command)ru   r   r"   r   ry   rz   rv   )r   ptyper   s      r   r   r     s    fd## u

6""(b))//117799E )##ujj##Ggs##=W]]__(=(==r2   c                     | sdS |                                                                  }|t          v rdS t          ||          }t	          |          r|S dS )zReturn the provider config if *provider* resolves to a command type.

    Built-in provider names are rejected (they have native handlers).
    Returns None when the name is a built-in, unknown, or not a command
    type.
    N)rz   ry   r   r   r   )r}   r~   r   r   s       r    _resolve_command_provider_configr     sf      t
..


 
 
"
"C
###t'
C88F"6** 4r2   textoutput_pathc                    |sdS |                                                                 }|t          v rdS t          t	          ||                    rdS 	 ddlm} ddlm}  |              ||          }| |d            ||          }n3# t          $ r&}t                              d|           Y d}~dS d}~ww xY w|dS t          |t                    r|                    d          nd}	t          |t                    r|                    d	          nd}
t          |t                    r|                    d
          nd}t          |t                    r|                    dt                    nt          }t                              d|           |                    | |t          |	t$                    r|	r|	ndt          |
t$                    r|
r|
ndt          |t&          t(          f          rt)          |          nd|r!t%          |                                           nd          }t          |t$                    r|r|n|S )u  Route the call to a plugin-registered TTS provider, or return None.

    Returns the path to the written audio file on dispatch, or ``None``
    to fall through to the next resolution layer (built-in dispatch or
    Edge TTS default).

    Resolution invariants enforced here (matches issue #30398):

    1. Built-in provider names short-circuit — never reach the plugin
       registry. The caller is responsible for the elif chain that
       handles ``edge``/``openai``/etc.; this function explicitly
       rejects those names defensively.
    2. Command-type providers declared under
       ``tts.providers.<name>: type: command`` (PR #17843) win over a
       plugin with the same name. The caller passes us only when its
       own command-provider check returned None — we re-verify here so
       a refactor of the caller can't silently break the invariant.
    3. Plugin dispatch fires only when ``provider`` matches a registered
       :class:`TTSProvider` whose ``name`` equals the configured value.
       Unknown names return None (caller falls through to Edge default).

    Plugin exceptions are caught and re-raised — the outer
    ``text_to_speech_tool`` try/except converts them to the standard
    error envelope, matching how command-provider failures surface.
    Nr   get_provider)_ensure_plugins_discoveredT)forcez2tts plugin dispatch skipped (discovery failed): %svoicemodelspeedoutput_formatz2Generating speech with plugin TTS provider '%s'...r   )r   r   r   format)rz   ry   r   r   r   agent.tts_registryr   hermes_cli.pluginsr   r!   r   r   ru   r   r   !DEFAULT_COMMAND_TTS_OUTPUT_FORMATinfo
synthesizer"   rw   rx   )r   r   r}   r~   r   r   r   plugin_providerexcr   r   r   fmtwrittens                 r   _dispatch_to_plugin_providerr     s   >  t
..


 
 
"
"C
###t ##=j##N#NOO t333333AAAAAA""$$$&,s++" '&T2222*l3//O   I3OOOttttt t (2*d'C'CMJNN7###E'1*d'C'CMJNN7###E'1*d'C'CMJNN7###E j$''	/
(IJJJ.  KK<c   ((!%--A%AeeT!%--A%AeeT(e==GeElll4#&1s3xx~~E )  G !#..K7K77Ks   :B 
CB<<Cc                 (   | sdS |                                                                  }|t          v rdS 	 ddlm}  ||          }|dS t          |j                  S # t          $ r'}t          	                    d||           Y d}~dS d}~ww xY w)a  Return True when the registered plugin provider opts into voice
    bubble delivery via its ``voice_compatible`` property.

    Defensive: any registry or property access failure means False
    (matches the safe default for the command-provider path).
    Fr   r   Nz5tts plugin voice_compatible check failed for '%s': %s)
rz   ry   r   r   r   rv   voice_compatibler!   r   r   )r}   r   r   r   r   s        r   $_plugin_provider_is_voice_compatibler   &  s      u
..


 
 
"
"C
###u333333&,s++"5O4555   CS#	
 	
 	
 uuuuu	s   A  A   
B*BBc              #     K   t          | t                    sdS t          | d          }|pi                                 D ]J\  }}t          |t                    r0|                                t          vrt          |          r||fV  KdS )zDYield (name, config) pairs for every declared command-type provider.Nr   )ru   r   r   itemsr"   rz   r   r   )r~   r   r   r   s       r   _iter_command_providersr   @  s      j$'' %j+>>Io2,,..    	cdC   	 TZZ\\9N%N%N*3//  Ci   r2   c                    |                      d|                      dt                              }	 t          |          }n+# t          t          f$ r t          t                    cY S w xY w|dk    rt          t                    S |S )z5Return timeout in seconds, falling back when invalid.timeouttimeout_secondsr   )r   #DEFAULT_COMMAND_TTS_TIMEOUT_SECONDSrx   	TypeError
ValueError)r   rawr   s      r   _get_command_tts_timeoutr   K  s    
**Y

+<>a b b
c
cC:c

z" : : :899999:zz8999Ls   A %A)(A)c                    |rVt          |          j                                                                                            d          }|t
          v r|S |                     d          p|                     d          pt          }t          |                                                                                              d          }|t
          v r|nt          S )z6Return the validated output format (mp3/wav/ogg/flac)..r   r   )	r   suffixrz   ry   lstripCOMMAND_TTS_OUTPUT_FORMATSr   r   r"   )r   r   r   r   r   s        r   _get_command_tts_output_formatr   W  s    
  k"")//117799@@EE///M

8 	-::o&&	-, 
 c((..


 
 
"
"
)
)#
.
.C333339ZZr2   c                     |                      dd          }t          |t                    r(|                                                                dv S t          |          S )zEReturn True only when the user explicitly opted in to voice delivery.r   F>   rk   rl   rm   rn   )r   ru   r"   ry   rz   rv   )r   r   s     r    _is_command_tts_voice_compatibler   i  sU    JJ)511E% C{{}}""$$(BBB;;r2   command_templatepositionc                     d}d}d}||k     r\| |         }|dk    r	|dk    rd}n:|dk    r|rd}n/|dk    rd}n&|dk    rd}n|dk    rd}n|dk    rd}n|dk    r|dz  }|dz  }||k     \|S )	zReturn the shell quote character active right before *position*.

    Returns ``"'"`` / ``'"'`` when inside a single- / double-quoted region
    of the template, ``None`` for bare context.
    NFr   '"\TrR    )r   r   quoteescapedichars         r   _shell_quote_contextr   q  s      EG	A
h,,"C<<s{{c\\ S[[EES[[EET\\FA	Q% h,,& Lr2   quote_contextc                 \   |dk    r|                      dd          S |dk    rR|                      dd                               dd                               dd                               d	d
          S t          j        dk    rt          j        | g          S t          j        |           S )zGQuote a placeholder value for its position in a shell command template.r   z'\''r   r   z\\z\"$z\$`z\`nt)replacer   r   
subprocesslist2cmdlineshlexr   )r   r   s     r   _quote_command_tts_placeholderr     s    }}S'***WT6""WS%  WS%  WS%  	
 
w$&w///;ur2   placeholdersc                     d                     d D                       }t          j        d| d| d          }g dt          j        t                   dt          f fd}|                    |           }|                    d	d
                              dd          }D ]\  }}|                    ||          }|S )z@Replace supported placeholders while preserving ``{{`` / ``}}``.|c              3   >   K   | ]}t          j        |          V  d S N)reescape).0r   s     r   	<genexpr>z/_render_command_tts_template.<locals>.<genexpr>  s*      >>RYt__>>>>>>r2   z(?<!\$)(?:\{\{(?P<double>z)\}\}|\{(?P<single>z)\})matchrT   c                    |                      d          p|                      d          }dt                     d}                    |t          |         t	          |                                                     f           |S )Ndoublesingle__HERMES_TTS_PLACEHOLDER___)grouplenappendr   r   start)r   r   tokenr   r   replacementss      r   replace_matchz3_render_command_tts_template.<locals>.replace_match  s    {{8$$=H(=(=AC,=,=AAA*T"$%5u{{}}EE 
 	 	 	 r2   z{{{z}}})joinr   compileMatchr"   subr   )	r   r   namespatternr  renderedr  r   r  s	   ``      @r   _render_command_tts_templater    s    
 HH>>>>>>>EjPuPPEPPP G +-L
RXc] 
s 
 
 
 
 
 
 
 
 {{=*:;;Hc**224==H$ 2 2u##E511Or2   procc           	         |                                  dS t          j        dk    rv	 t          j        ddddt          | j                  gt          j        t          j        dt          j                   n$# t          $ r | 	                                 Y nw xY wdS d	dl
}	 |                    | j                  }|                    d
          D ]'}	 |                                 # |j        $ r Y $w xY w|                                 n0# |j        $ r Y dS t          $ r |                                  Y nw xY w	 |                     d           dS # t          j        $ r Y nw xY w	 |                    | j                  }|                    d
          D ]'}	 |	                                 # |j        $ r Y $w xY w|	                                 dS # |j        $ r Y dS t          $ r | 	                                 Y dS w xY w)zCBest-effort termination of a shell process and all of its children.Nr   taskkillz/Fz/Tz/PID   )stdoutstderrr   stdinr   T)	recursiverS   r   )pollr   r   r   runr"   pidDEVNULLr!   killpsutilProcesschildren	terminateNoSuchProcesswaitTimeoutExpired)r  r!  parentchilds       r   #_terminate_command_tts_process_treer*    s   yy{{	w$		NT4TX?!)!) (      	 	 	IIKKKKK	MMM))__t_44 	 	E!!!!'         		!	$   ))__t_44 	 	E

'         		s   AA7 7BB"2D C*)D *
C74D 6C77D 
D<D<;D< E E*)E*.2G !F65G 6
G G GG 
H
)H
	H
r   r   c                    dt           j        t           j        dd}t          j        dk    rt	          t           dd          |d<   nd|d<   t          j        | fi |dt           j        i}	 |                    |	          \  }}n# t           j        $ rz}t          |           	 |                    d
	          \  }}n2# t          $ r% t	          |dd          }t	          |dd          }Y nw xY wt          j        | |||          |d}~ww xY w|j        rt          j        |j        | ||          t          j        | |j        ||          S )zGRun a command-provider shell command with process-tree timeout cleanup.T)shellr  r  r   r   CREATE_NEW_PROCESS_GROUPr   creationflagsstart_new_sessionr  r  rR   outputNr  )r0  r  )r   PIPEr   r   getattrPopenr  communicater'  r*  r!   
returncodeCalledProcessErrorCompletedProcess)r   r   popen_kwargsr  r  r  r   s          r   _run_command_ttsr9    s    //	$ $L 
w$(/
<VXY(Z(Z_%%,0()GNN|NN:;MNNND))')::$   +D111	2!--a-88NFFF 	2 	2 	2S(D11FS(D11FFF	2 '	
 
 

 	  
+O	
 
 
 	
 &wPPPs<   ,B DD
%B?>D
?,C.+D
-C..D

Dpathc                 P    t          |          }|                     d|           S )zKReturn an output path whose extension matches the provider's output_format.r   )r   with_suffix)r:  r   r   s      r   #_configured_command_tts_output_pathr=    s)    
(
0
0CIII&&&r2   provider_namec                 n   t          |                    d          pd                                          }|st          d| d          t	          |                                          }|j                            dd           |                                r|	                                 t          |          }t          |t          |                    }|                    d|                    dd                    }	t          j                    5 }
t	          |
          dz  }|                    | d	
           t          |          t          |          t          |          |t          |                    dd                    t          |                    dd                    t          |	          d}t          ||          }	 t!          ||           n# t"          j        $ r}t'          d| d|dd          |d}~wt"          j        $ r}g }|j        r/|                    d|j                                                    |j        r/|                    d|j                                                    d                    |          pd}t'          d| d|j         d|           |d}~ww xY w	 ddd           n# 1 swxY w Y   |                                r|                                j        dk    rt'          d| d|           t          |          S )a
  Generate speech by running a user-configured shell command.

    Returns the absolute path of the audio file the command wrote.
    Raises ``ValueError`` when the provider config is invalid, and
    ``RuntimeError`` for timeouts / non-zero exits / empty output.
    r   r   ztts.providers.z.command is not configuredTparentsexist_okr   z	input.txtutf-8encodingr   r   )
input_path	text_pathr   r   r   r   r   zTTS provider 'z' timed out after gsNzstderr: zstdout: z; zno command outputz' exited with code z: r   z' produced no output at )r"   r   ry   r   r   
expanduserr(  mkdirexistsunlinkr   r   tempfileTemporaryDirectory
write_textr  r9  r   r'  RuntimeErrorr6  r  r  r  r  r5  statst_size)r   r   r>  r   r~   r   r0  r   r   r   tmpdirrG  r   r   r   detail_partsdetails                    r   _generate_command_ttsrW  !  s    6::i006B77==?? 
F]FFF
 
 	
 +))++F
Mt444}} &v..G263v;;GGMJJw
w ; ;<<E		$	&	& &LL;.	TG444 i..Yv;;#GR0011GR0011ZZ
 
 //?NN	Wg....( 	 	 	NNN'NNNN  , 
	 
	 
	Lz E##$Csz/?/?/A/A$C$CDDDz E##$Csz/?/?/A/A$C$CDDDYY|,,C0CF. . .>. .%+. .  
	 /!              B ==?? 
fkkmm3q88L]LLFLL
 
 	
 v;;sD   B:KG&%K&K5HKB#KKKKKc                 T    | t                      } t          |           D ]\  }} dS dS )z=Return True when any command-type TTS provider is configured.NTF)r   r   )r~   _name_cfgs      r   _has_any_command_tts_providerr[  e  s:    %''
.z::  ttt5r2   c                  .    t          j        d          duS )z+Check if ffmpeg is available on the system.ffmpegN)shutilwhichr   r2   r   _has_ffmpegr`  q  s    <!!--r2   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t          j                  }|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.
    Nr   rR   r   .oggr]  -i-acodeclibopus-acrk   -b:a64k-vbrrr   -yT   capture_outputr   r  z0ffmpeg conversion failed with return code %d: %srC  ignoreerrors   z)ffmpeg OGG conversion timed out after 30szffmpeg not found in PATHz ffmpeg OGG conversion failed: %sr   )r`  rsplitr   r  r  r5  r   r   r  decoder   r:  rL  getsizer'  FileNotFoundErrorr!   )ra  ogg_pathresultr%   s       r   _convert_to_opusry  v  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+   A=C5 0AC5 5)E5 #E5	E5E00E5c           	        K   t                      }|                    di           }|                    dt                    }t          |                    d|                    dd                              }d|i}|dk    rt	          |dz
  dz            }|dd|d<    |j        | fi |}	|	                    |           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.
    rE   r   r         ?d   z+d%rateN)r&   r   DEFAULT_EDGE_VOICErx   roundCommunicatesave)
r   r   r~   	_edge_ttsedge_configr   r   kwargspctr4  s
             r   _generate_edge_ttsr    s       !""I..,,KOOG%788E+//':>>'3+G+GHHIIEuF||US[C'((v')'7777K


;
'
''''''''r2   c                    t          d          p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_KEYr   z=ELEVENLABS_API_KEY not set. Get one at https://elevenlabs.io/r`   voice_idr   rc  opus_48000_64mp3_44100_128api_keyr   r  r   r   wbN)r   r   r   DEFAULT_ELEVENLABS_VOICE_IDr   endswithr,   text_to_speechconvertopenwrite)r   r   r~   r  	el_configr  r   r   r*   clientaudio_generatorfchunks                r   _generate_elevenlabsr    se    1228bG 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   C99C= C=c           	      r   t                      \  }}|                    di           }|                    dt                    }|                    dt                    }|                    d|          }t	          |                    d|                    dd                              }|                    d          rd}	nd	}	t                      }
 |
||
          }	 ||| |	dt          t          j	                              id}|dk    r!t          dt          d|                    |d<    |j        j        j        d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/   r   r   base_urlr   r{  rc  opusr   )r  r  zx-idempotency-key)r   r   inputresponse_formatextra_headersg      ?g      @closeNr   )#_resolve_openai_audio_client_configr   DEFAULT_OPENAI_MODELDEFAULT_OPENAI_VOICErx   r  r1   r"   uuiduuid4maxminaudiospeechcreatestream_to_filer2  callable)r   r   r~   r  r  
oai_configr   r   r   r  r0   r  create_kwargsresponser  s                  r   _generate_openai_ttsr    s    <==GX"--JNN7$899ENN7$899E~~j(33H*..*..#*F*FGGHHE F##   (**L\'H===F.13tz||3D3DE
 
 C<<%(s3%?%?M'"-6<&->>>>,,,..E?? 	EGGGG	 ..E?? 	EGGGG	s   !A<F	 	-F6)pausez
long-pausezhum-tunelaughchucklegigglecrytskztongue-clickz	lip-smackbreathinhaleexhalesigh)softwhisperloudzbuild-intensityzdecrease-intensityzhigher-pitchzlower-pitchslowfastz	sing-songsingingzlaugh-speakemphasisz(\[(?:r   z
)\]|</?(?:z)>)flagsu   ^(.{12,120}?[.!?…])\s+(?=\S)c                 $    t          | |          S )Nr   )r|   )r   r   s     r   _xai_bool_configr  B  s    w////r2   c                    |                                  }|rt                              |          r| S t          j        dd|          }t          j        dd|          }t                              |          st
                              d|d          }t          j        dd|                                           }|S )	zAdd light xAI speech tags for more natural voice-mode replies.

    The transform is intentionally conservative: it only inserts pauses. It
    never fabricates laughter or whispering, and it leaves explicit user/model
    speech tags untouched.
    z\n\s*\n+z	 [pause] z\s*\n\s* z\1 [pause] rR   )countz\s{2,})ry   _XAI_SPEECH_TAG_REsearchr   r  _XAI_FIRST_SENTENCE_RE)r   cleans     r   _apply_xai_auto_speech_tagsr  F  s     JJLLE &--e44 F;U33EF;U++E$$U++ K&**>5*JJF9c5))//11ELr2   c                    ddl }ddlm}  |            }t          |                    d          pd                                          }|st          d          |                    di           }t          |                    dt                                                              pt          }t          |                    d	t                                                              pt          }	t          |                    d
t                              }
t          |                    dt                              }t          |                    d|                    d                    t                    }|rt          |           } t          |                    d          p*|                    d          pt          d          pt                                                                         d          }|                    d          rdnd}| ||	d}|dk    s|
t          k    s|dk    r(|t          k    rd|i}|
r|
|d
<   |dk    r|r||d<   ||d<   |                    | dd| dt)                      d|d          }|                                 t-          |d          5 }|                    |j                   ddd           n# 1 swxY w Y   |S )z
    Generate audio using xAI TTS.

    xAI exposes a dedicated /v1/tts endpoint instead of the OpenAI audio.speech
    API shape, so this is implemented as a separate backend.
    r   Nresolve_xai_http_credentialsr  r   zSNo xAI credentials found. Configure xAI OAuth in `hermes model` or set XAI_API_KEY.r\   r  languagesample_ratebit_rateauto_speech_tagsspeech_tagsr  XAI_BASE_URL/.wavr   r   )r   r  r  codecr   z/ttsBearer application/json)AuthorizationContent-Typez
User-Agent<   )headersjsonr   r  )requeststools.xai_httpr  r"   r   ry   r   DEFAULT_XAI_VOICE_IDDEFAULT_XAI_LANGUAGErw   DEFAULT_XAI_SAMPLE_RATEDEFAULT_XAI_BIT_RATEr  DEFAULT_XAI_AUTO_SPEECH_TAGSr  r   DEFAULT_XAI_BASE_URLrstripr  postr   raise_for_statusr  r  content)r   r   r~   r  r  credsr  
xai_configr  r  r  r  r  r  r  payloadr   r  r  s                      r   _generate_xai_ttsr  Y  s=    OOO;;;;;;((**E%))I&&,"--3355G pnooor**J:>>*.BCCDDJJLLdPdH:>>*.BCCDDJJLLdPdHjnn]4KLLMMK:>>*.BCCDDH'):>>-+H+HII$   1*400z"" 	 99Z  	 ((	   	 
 eggffSkk  !))&11<EEuE G 	111UNNx+???)0%(8 	7+6M-(E>>h>(0M*%#0 }}0w00./11
 

   	 	H 	k4	 	  "A	 !!!" " " " " " " " " " " " " " " s   4KK"Kc           	         ddl }t          d          pd}|st          d          |                    di           }|                    dt                    }|                    dt
                    }|                    d	t                    }|                    d
d          }	|                    dd          }
|                    dd          }|                    dd          }|                    dd          }|                    dd          }t          |                    d          pd                                          p"t          d          pd                                }|rd|vrd|v rdnd}| | d| }dd| d}d|v }|r|| ||	|
||d||ddd d!}n|| |d"}|	                    |||d#$          }|r|
                                 |                                }|                    d%i           }|                    d&d'          }|dk    r+|                    d(d)          }t          d*| d+|           |                    d,i                               d-d          }|st          d.          t                              |          }t          |d/          5 }|                    |           ddd           n# 1 swxY w Y   |S |j                            d0d          }d1|v rDt          |d/          5 }|                    |j                   ddd           n# 1 swxY w Y   |S 	 |                                }|                    d%i           }|                    d&d'          }|dk    r+|                    d(d)          }t          d*| d+|           nJ# t&          $ r= |
                                 t          d2| d3t)          |j                   d4          w xY wt          d5          )6a  
    Generate audio using MiniMax TTS API.

    Supports two endpoints:
    - v1/text_to_speech: simple payload, returns raw audio (Content-Type: audio/mpeg)
    - v1/t2a_v2: nested voice_setting/audio_setting, returns JSON with hex-encoded audio

    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.
    r   NMINIMAX_API_KEYr   z@MINIMAX_API_KEY not set. Get one at https://platform.minimax.io/r]   r   r  r  r   r{  volpitchemotionneutralr  r[   bitraterO   group_idMINIMAX_GROUP_IDzGroupId=?&r  r  )r  r  t2a_v2)r  r   r  r  r  r   rR   )r  r  r   channel)r   r   voice_settingaudio_setting)r   r   r  r  )r  r  r   	base_respstatus_code
status_msgunknown errorzMiniMax TTS API error (code ): datar  z%MiniMax TTS returned empty audio datar  r  zaudio/z.MiniMax TTS returned unexpected Content-Type 'z' (z bytes)z"MiniMax TTS returned no audio data)r  r   r   r   DEFAULT_MINIMAX_MODELDEFAULT_MINIMAX_VOICE_IDDEFAULT_MINIMAX_BASE_URLr"   ry   r  r  r  rQ  bytesfromhexr  r  r  r  r!   r  )r   r   r~   r  r  	mm_configr   r  r  r   r  r  r  r  r  r  sepr  	is_t2a_v2r  r  rx  r  r  r  	hex_audioaudio_bytesr  content_types                                r   _generate_minimax_ttsr    s     OOO.//52G ][\\\y"--IMM'#899E}}Z)ABBH}}Z)ABBHMM'3''E
--s
#
#CMM'1%%EmmIy11G--u55KmmIv..G 	IMM*%%+,,2244 	=,--3::<<   8Jh..H__cc#777X77 +,7,, G H$I 
 $"   +"	 
 
(  
 
 }}XGWb}QQH ,A!!###JJ{B//	mmM266!"|_EEJZkZZjZZ[[[JJvr**..w;;	 	HFGGGmmI..+t$$ 	!GGK   	! 	! 	! 	! 	! 	! 	! 	! 	! 	! 	! 	! 	! 	! 	!  '++NB??|##k4(( *A()))* * * * * * * * * * * * * * *	]]__F

;33I#--r::Ka&]]<II
"#^+#^#^R\#^#^___    	 	 	%%'''3 3 3())3 3 3  	 ?@@@s2   /KKKL44L8;L8A1N4 4AO;c                    t          d          pd}|st          d          |                    di           }|                    dt                    }|                    d          pt          }|                    d          rd}n2|                    d	          rd
}n|                    d          rd}nd}t                      }	  ||          5 }	|	j        j        	                    || ||          }
t          j        |
j                  }ddd           n# 1 swxY w Y   n^# t          $ r  t          $ rG}t                              d|d           t!          dt#          |          j                   |d}~ww xY wt'          |d          5 }|                    |           ddd           n# 1 swxY w Y   |S )zGenerate audio using Mistral Voxtral TTS API.

    The API returns base64-encoded audio; this function decodes it
    and writes the raw bytes to *output_path*.
    Supports native Opus output for Telegram voice bubbles.
    MISTRAL_API_KEYr   z?MISTRAL_API_KEY not set. Get one at https://console.mistral.ai/r^   r   r  rc  r  r  r   z.flacr   r   r  )r   r  r  r  NzMistral TTS failed: %sTr   zMistral TTS failed: r  )r   r   r   DEFAULT_MISTRAL_TTS_MODELDEFAULT_MISTRAL_TTS_VOICE_IDr  r6   r  r  completebase64	b64decode
audio_datar!   r   errorrQ  r   __name__r  r  )r   r   r~   r  	mi_configr   r  r  r4   r  r  r  r%   r  s                 r   _generate_mistral_ttsr"  (  s    .//52G \Z[[[y"--IMM'#<==E}}Z((H,HHF##   			f	%	%  			g	&	&   $&&GMWW%%% 	@|*33! /	 4  H !*8+>??K	@ 	@ 	@ 	@ 	@ 	@ 	@ 	@ 	@ 	@ 	@ 	@ 	@ 	@ 	@     M M M-q4@@@D$q''2BDDEE1LM 
k4	 	  A	               sO   	D* =DD* D""D* %D"&D* *F>AF  FF::F>F>	pcm_bytesr  channelssample_widthc                 @   ddl }||z  |z  }||z  }t          |           }|                    dddd|||||dz  	  	        }|                    dd	|          }	d
t          |          z   t          |	          z   |z   }
|                    dd|
d          }||z   |	z   | z   S )a  Wrap raw signed-little-endian PCM with a standard WAV RIFF header.

    Gemini TTS returns audio/L16;codec=pcm;rate=24000 -- raw PCM samples with
    no container. We add a minimal WAV header so the file is playable and
    ffmpeg can re-encode it to MP3/Opus downstream.
    r   Nz
<4sIHHIIHHs   fmt    rR      z<4sIs   data   z<4sI4ss   RIFFs   WAVE)structr  pack)r#  r  r$  r%  r*  	byte_rateblock_align	data_size	fmt_chunkdata_chunk_header	riff_sizeriff_headers               r   _wrap_pcm_as_wavr3  Y  s     MMMh&5I\)KII
	q
 
I FGY??C	NN"S):%;%;;iGI++hGDDK"%66BBr2   gemini_configc                    |                      d          }t          |t                    r|                                sdS t          j                            |                                          }t          |                                          }|	                                s;	 ddl
m}  |            |z  }n&# t          $ r t          j                    |z  }Y nw xY w|S )z7Return the configured persona prompt file path, if any.persona_prompt_fileNr   )get_hermes_home)r   ru   r"   ry   r   r:  
expandvarsr   rJ  is_absoluterY   r7  r!   cwd)r4  r   expandedr:  r7  s        r   #_resolve_gemini_persona_prompt_pathr<  {  s    


1
2
2Cc3 syy{{ tw!!#))++..H>>$$&&D %	%888888"?$$t+DD 	% 	% 	%8::$DDD	%Ks   (B< < CCc                     t          |           }|dS 	 |                    d                                          S # t          t          f$ r'}t
                              d||           Y d}~dS d}~ww xY w)zERead the Gemini persona prompt file, failing soft on config mistakes.Nr   rC  rD  z4Gemini TTS persona prompt file unavailable at %s: %s)r<  	read_textry   OSErrorUnicodeDecodeErrorr   r   )r4  r:  r   s      r   _read_gemini_persona_promptrA    s    .}==D|r~~w~//55777'(   B	
 	
 	

 rrrrrs   '= A5A00A5r   c                     | pd                                                                                     dd          d         }d|v od|v S )zIReturn True for Gemini TTS models known to support expressive audio tags.r   r  rR   r  z
gemini-3.1r   )ry   rz   rs  )r   r{   s     r   !_gemini_model_supports_audio_tagsrC    sM    +2$$&&,,..55c1==bAJ:%=%:*==r2   c                    |                      d          }t          |t                    r|                     d          }t          |t                    }|sdS t          |          st                              d|           dS dS )N
audio_tagsro   r  FzrGemini TTS audio_tags enabled, but model %s is not known to support Gemini audio tags; skipping hidden tag rewriteT)r   ru   r   r|   DEFAULT_GEMINI_AUDIO_TAGSrC  r   r   )r4  r   r   ro   s       r   _gemini_audio_tags_enabledrG    s    


L
)
)C#t !ggi  3(ABBBG u,U33 =	
 	
 	

 u4r2   r  c                     | pd                                 }t          j        d|t          j                  }|r'|                    d                                           }|S )Nr   z$```(?:[A-Za-z0-9_-]+)?\s*(.*?)\s*```r  rR   )ry   r   	fullmatchDOTALLr  )r  r  fences      r   _clean_gemini_audio_tag_rewriterL    sX    ]!!##EL@%ryYYYE 'A$$&&Lr2   r  c                    	 | j         d         }t          |dd           }t          |t                    r$t	          |                    d          pd          S t	          t          |dd          pd          S # t          $ r Y dS w xY w)Nr   messager  r   )choicesr2  ru   r   r"   r   r!   )r  choicerN  s      r   "_extract_auxiliary_message_contentrQ    s    !!$&)T22gt$$ 	5w{{9--344477Ir228b999   rrs   AA9 A9 9
BBr   persona_promptc                 ^   |                                  }|s| S d}|                                 pd}d| d| }	 ddlm}  |t          d|dd	|dgd
          }t	          t          |                    }|p| S # t          $ r'}	t                              d|	           | cY d}	~	S d}	~	ww xY w)z?Use the configured auxiliary model to insert Gemini audio tags.a  You rewrite transcripts for Gemini 3.1 Flash TTS by inserting expressive audio tags.

Audio tags are inline square-bracket modifiers such as [whispers], [excitedly], [very slow], [sarcastically], [laughs], [sighs], or [gasp]. There is no fixed allowlist. Use creative freeform tags generously but naturally to control tone, pace, emotional vibe, emphasis, section-level delivery, and non-verbal sounds. Use English audio tags even when the spoken transcript is not English.

Rules:
- Preserve the spoken words, order, and meaning.
- Do not add new spoken sentences or remove existing spoken words.
- Use square brackets for every audio tag.
- Do not use SSML or XML tags.
- Do not explain or comment.
- Return only the tagged TTS script.z(none)zPERSONA AND DIRECTOR CONTEXT:
z

TRANSCRIPT TO TAG:
r   )call_llmsystem)roler  usergffffff?)taskmessagestemperaturez<Gemini TTS audio tag rewrite failed; using untagged text: %sN)	ry   agent.auxiliary_clientrT  GEMINI_AUDIO_TAG_REWRITE_TASKrL  rQ  r!   r   r   )
r   rR  
transcriptsystem_promptcontextuser_promptrT  r  taggedr   s
             r   _rewrite_gemini_tts_audio_tagsrb    s   J 	/ " ""$$0G		 	 	 	 3333338.!m<<K88 
 
 
 11ST\1]1]^^~   UWZ[[[s   A A; ;
B,B'!B,'B,c                    |                                  }|t          |          }|s|S d}t          j        dt          j                  t          j        dt          j                  f}|}|D ]H}|                    |          r1|                    ||          }| d|                                  c S I| d| d|                                  S )zHBuild the Gemini prompt from persona direction plus the live transcript.NzSynthesize speech from the TRANSCRIPT only. Treat AUDIO PROFILE, SCENE, DIRECTOR'S NOTES, and SAMPLE CONTEXT as performance direction; do not speak those sections aloud.z\{\{\s*transcript\s*\}\}r  z\{\s*transcript\s*\}

z

#### TRANSCRIPT
)ry   rA  r   r  
IGNORECASEr  r  )r   r4  rR  r]  preambleplaceholder_patternsr   r  s           r   _compose_gemini_tts_promptrh    s    J4]CC 	-  	
.bmDDD

*"-@@@ F' 5 5>>&!! 	5[[V44F,,F,,2244444	5 MMNMMMMSSUUUr2   c                 ^   ddl }t          d          pt          d          pd                                }|st          d          |                    di           }t          |t                    r|ni }t          |                    dt                                                              pt          }t          |                    d	t                                                              pt          }t          |                    d
          pt          d          pt                                                                        d          }	t          |          }
| }t          ||          rt          | |
          }t          |||
          }t!          d|          }t#          |          |k    r3t$                              dt#          |          |           |d|         }dd|igigdgddd|iiidd}|	 d| d}|                    |d|iddi|d          }|j        dk    r	 |                                                    di           }|                    d           p|j        dd!         }n# t0          $ r |j        dd!         }Y nw xY wt3          d"|j         d#|           	 |                                }|d$         d         d%         d         }t5          d& |D             d          }|t3          d'          |                    d(          p|                    d)          pi }|                    d*d          }n2# t6          t8          t:          f$ r}t3          d+|           |d}~ww xY w|st3          d,          t=          j        |          }tA          |          }|!                                "                    d-          r?tG          |d.          5 }|$                    |           ddd           n# 1 swxY w Y   |S tK          j&        d-d/0          5 }|$                    |           |j'        }ddd           n# 1 swxY w Y   	 tQ          j)        d1          }|r|!                                "                    d2          r|d3|d4d5d6d7d8d9d:d;d<d=d|g}n	|d3|d<d=d|g}tU          j+        |d>d?tT          j,        @          } | j-        dk    r6| j.        /                    dAdBC          dd!         }!t3          dD|!           n0t$                              dE|           tQ          j0        ||           	 tc          j2        |           n:# tf          $ r Y n.w xY w# 	 tc          j2        |           w # tf          $ r Y w w xY wxY w|S )Fa  Generate audio using Google Gemini TTS.

    Gemini's generateContent endpoint with responseModalities=["AUDIO"] returns
    raw 24kHz mono 16-bit PCM (L16) as base64. We wrap it with a WAV RIFF
    header to produce a playable file, then ffmpeg-convert to MP3 / Opus if
    the caller requested those formats (same pattern as NeuTTS).

    Args:
        text: Text to convert (prompt-style; supports inline direction like
              "Say cheerfully:" and audio tags like [whispers]).
        output_path: Where to save the audio file (.wav, .mp3, or .ogg).
        tts_config: TTS config dict.

    Returns:
        Path to the saved audio file.
    r   NGEMINI_API_KEYGOOGLE_API_KEYr   zIGEMINI_API_KEY not set. Get one at https://aistudio.google.com/app/apikeyr_   r   r   r  GEMINI_BASE_URLr  )rR  z@Gemini TTS composed prompt too long (%d chars), truncating to %dpartsr   AUDIOvoiceConfigprebuiltVoiceConfig	voiceName)responseModalitiesspeechConfig)contentsgenerationConfigz/models/z:generateContentr   r  r  r  )paramsr  r  r   rr  r  rN  ,  zGemini TTS API error (HTTP r	  
candidatesr  c              3   *   K   | ]}d |v sd|v 
|V  dS )
inlineDatainline_dataNr   )r   ps     r   r   z'_generate_gemini_tts.<locals>.<genexpr>j  s7      WW|q/@/@MUVDVDV1DVDVDVDVWWr2   z+Gemini TTS response contained no audio datarz  r{  r
  z#Gemini TTS response was malformed: z$Gemini TTS returned empty audio datar  r  Fr   deleter]  rc  rd  re  rf  rg  rk   rh  ri  rj  rr   rk  	-loglevelTrl  rm  rC  ro  rp  zffmpeg conversion failed: zEffmpeg not found; writing raw WAV to %s (extension may be misleading))4r  r   ry   r   r   ru   r   r"   DEFAULT_GEMINI_TTS_MODELDEFAULT_GEMINI_TTS_VOICEDEFAULT_GEMINI_TTS_BASE_URLr  rA  rG  rb  rh  r   r  r   r   r  r  r  r   r!   rQ  nextKeyError
IndexErrorr   r  r  r3  rz   r  r  r  rN  NamedTemporaryFiler   r^  r_  r   r  r  r5  r  rt  copyfiler   remover?  )"r   r   r~   r  r  raw_gemini_configr4  r   r   r  rR  
tts_scriptprompt_textmax_lenr  endpointr  errrV  r
  rm  
audio_partinline	audio_b64r%   r#  	wav_bytesr  tmpwav_pathr]  cmdrx  r  s"                                     r   _generate_gemini_ttsr    s   " OOO-..W-@P2Q2QWUW^^``G 
W
 
 	
 #x44)34Et)L)LT%%RTM!!'+CDDEEKKMMiQiE!!'+CDDEEKKMMiQiE*%% 	'*++	'&  eggffSkk	 
 1??NJ!-77 Y3DXXX
,%  K
 'x<<G
;'!!Ng	
 	
 	
 "(7(+  5678#*))K+? 
 

 
G ;;E;;;H}}w!34   H s""	)--//%%gr22CWWY''>8=#+>FF 	) 	) 	)]4C4(FFF	)K(*>KK6KK
 
 	
	M}}\"1%i09WWeWWWY]^^
LMMM--T1N1NTRTJJvr**		j), M M MDDDEE1LM  CABBB ++I ++I ##F++ +t$$ 	GGI	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 
	$F5	A	A	A S		)8              h'' 	3   ""++F33 	XD(y%E65+w tXt[';W^CbPZPbcccF A%%--gh-GGM"#H#H#HIII & NNW   OHk222	Ih 	 	 	D		Ih 	 	 	D	 s   7AJ J J >BM N	1NN	7PP P<Q%%Q),Q)1C,V U3 3
V ?V V*VV*
V'$V*&V''V*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   Nra   Fimportlib.utilutil	find_specr!   	importlibs    r   _check_neutts_availabler    sP    ~''11==   uu   " 
00c                  f    	 ddl } | j                            d          duS # t          $ r Y dS w xY w)z@Check if the kittentts engine is importable (installed locally).r   Nr>   Fr  r  s    r   _check_kittentts_availabler    sP    ~''44D@@   uur  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__r(  r   r2   r   _default_neutts_ref_audior    $    tH~~$'77(BCCCr2   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  r   r2   r   _default_neutts_ref_textr    r  r2   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t          j                  }|j        dk    rk|j                                        }d |                                D             }t%          dt'          d                              |          pd           |	|k    rkt+          j        d          }|r@|d|	ddd |g}t          j        |dd!t          j        "           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   Nra   	ref_audior   ref_textr   zneuphonic/neutts-air-q4-ggufdevicecpur  r   rR   zneutts_synth.pyz--textz--outz--ref-audioz
--ref-textz--modelz--deviceTr   rn  r   r   r  c                 <    g | ]}|                     d           |S )zOK:)
startswith)r   ls     r   
<listcomp>z$_generate_neutts.<locals>.<listcomp>  s)    QQQQQ\\%=P=PQqQQQr2   zNeuTTS synthesis failed: 
   r  r]  rd  rk  r  r  rl  checkr   r  )sysr   r  r  r  rs  r"   r   r  r(  
executabler   r  r  r5  r  ry   
splitlinesrQ  chrr  r^  r_  r   r  rename)r   r   r~   r  neutts_configr  r  r   r  r  synth_scriptr  rx  r  error_linesr]  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T^TfgggFA$$&&QQ&"3"3"5"5QQQes2ww||K7P7P7cTceefff ;h'' 	-hk7KXHN84:CUVVVVIh Ih,,,r2   _piper_voice_cachec                  f    	 ddl } | j                            d          duS # t          $ r Y dS w xY w)z2Check whether the piper-tts package is importable.r   NrC   Fr  r  s    r   _check_piper_availabler    sP    ~''00<<   uur  c                  r    ddl m}  t           | dd                    }|                    dd           |S )zReturn the directory where Hermes caches Piper voice models.

    Resolves to ``~/.hermes/cache/piper-voices/`` under the active
    HERMES_HOME so voice downloads follow profile boundaries.
    r   rV   zcache/piper-voicespiper_voices_cacheTr@  )rY   rW   r   rK  )rW   roots     r   _get_piper_voices_dirr    sL     0/////35IJJKKDJJtdJ+++Kr2   r   download_dirc           
      f   | st           } t          |                                           }|j                                        dk    r#|                                rt          |          S ||  dz  }|                                r)||  dz                                  rt          |          S ddl}t          	                    d| |           	 t          j        |j        dd| dt          |          gd	d	d
t          j                  }n+# t          j        $ r}t          d|  d          |d}~ww xY w|j        dk    r:|j        pd                                pd}t          d|  d|dd                    |                                st          d| d          t          |          S )a}  Resolve *voice* (a model name or path) to a concrete .onnx file path.

    Accepts any of:
      - Absolute / expanded path to an .onnx file the user already has
      - A voice *name* like ``en_US-lessac-medium`` (downloads to
        ``download_dir`` on first use via ``python -m piper.download_voices``)

    Raises RuntimeError if the model can't be located or downloaded.
    z.onnxz
.onnx.jsonr   Nz0[Piper] Downloading voice '%s' to %s (first use)z-mzpiper.download_voicesz--download-dirTrw  r  z/Piper voice download timed out after 300s for 'r   r   zno stderr outputz!Piper voice download failed for 'z': i  z#Piper voice download completed but uh    is missing — check voice name (see: https://github.com/OHF-Voice/piper1-gpl/blob/main/docs/VOICES.md))DEFAULT_PIPER_VOICEr   rJ  r   rz   rL  r"   r  r   r   r   r  r  r  r'  rQ  r5  r  ry   )r   r  	candidatecached_sysrx  r   r  s           r   _resolve_piper_voice_pathr    s     $# U&&((I7**y/?/?/A/A*9~~ uOOO+F}} Le+?+?+??GGII 6{{ 
KKBE<XXX
_d$;Us<002dC$	
 
 
 $   FeFFF
 
	
 A-%2,,..D2DHHH&#,HH
 
 	
 ==?? 
)& ) ) )
 
 	

 v;;s   ;D D5D00D5c                    t                      }ddl}t          |t                    r|                    di           ni                     d          pt
          }t                              d          pt                                                                }|	                    dd           t                              dd	                    }t          ||          }| d
| }	|	t          vrTt                              d|           |                    ||          t          |	<   t                              d           t          |	         }
d}t!          fddD                       }|r	 ddlm}  |t'                              dd                    t'                              dd                    t'                              dd                    t'                              dd                    t                              dd                              }n*# t(          $ r t                              d           Y nw xY w|}|                    d          s|                    dd          d         dz   }|                    |d          5 }||
                    | ||           n|
                    | |           ddd           n# 1 swxY w Y   ||k    r|t5          j        d          }|rQ|d |d!d"d#|g}t9          j        |dd$t8          j        %           	 t?          j         |           n%# tB          $ r Y nw xY wt?          j"        ||           |S )&zGenerate speech using the local Piper engine.

    Loads the voice model once per process (cached by absolute path) and
    writes a WAV file. Caller is responsible for converting to MP3/Opus
    via ffmpeg when a different output format is required.
    r   NrC   r   
voices_dirTr@  use_cudaFz::cuda=z[Piper] Loading voice: %s)r  z[Piper] Voice loadedc              3       K   | ]}|v V  	d S r   r   )r   kpiper_configs     r   r   z&_generate_piper_tts.<locals>.<genexpr>p  s<         	
\     r2   )length_scalenoise_scalenoise_w_scalevolumenormalize_audio)SynthesisConfigr  r{  r  gMbX?r  g?r  r  uZ   [Piper] SynthesisConfig not available in this piper-tts version — advanced knobs ignoredr  r   rR   r  )
syn_configr]  rd  rk  r  r  rl  r  )#rD   waveru   r   r   r  r   r  rJ  rK  rv   r  r  r   r   loadanyrC   r  rx   r   r   r  rs  r  synthesize_wavr^  r_  r   r  r  r   r  r?  r  )r   r   r~   rB   r  
voice_namer  r  
model_path	cache_keyr   r  has_advancedr  r  wav_filer]  r  r  s                     @r   _generate_piper_ttsr  R  s    JKKK2<Z2N2NV:>>'2...TVL!!'**A.AJ((66Q:O:Q:QRR]]__Ltd333L$$Z7788H*:|DDJ00h00I***/<<<(2
X(V(V9%*+++y)E
 J    ^    L  	------("<#3#3NC#H#HII!,"2"2=%"H"HII#L$4$4_c$J$JKK\--h<<== $\%5%56G%N%N O O  JJ  	 	 	NN5    	 H'' :%%c1--a069	8T	"	" 1h!  xJ GGGG  x000	1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 ;h'' 		-hk7KXHN84:CUVVVV	(####    Ih,,,s7   :B;H6 6$II,2K**K.1K.=M 
MM_kittentts_model_cachec                    t                      }|                    di           }|                    dt                    }|                    dt                    }|                    dd          }|                    dd          }|t          vrHt
                              d|            ||          t          |<   t
                              d	           t          |         }	|	                    | |||
          }
ddl}|}|	                    d          s|
                    dd          d         dz   }|                    ||
d           ||k    rkt          j        d          }|r@|d|ddd|g}t          j        |ddt          j                   t#          j        |           nt#          j        ||           |S )at  Generate speech using KittenTTS local ONNX model.

    KittenTTS is a lightweight TTS engine (25-80MB models) that runs
    entirely on CPU without requiring a GPU or API key.

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

    Returns:
        Path to the saved audio file.
    r>   r   r   r   r{  
clean_textTz[KittenTTS] Loading model: %sz%[KittenTTS] Model loaded successfully)r   r   r  r   Nr  r   rR   rN   r]  rd  rk  r  r  rl  r  )r?   r   DEFAULT_KITTENTTS_MODELDEFAULT_KITTENTTS_VOICEr  r   r   generate	soundfiler  rs  r  r^  r_  r   r  r  r   r  r  )r   r   r~   r=   	kt_config
model_namer   r   r  r   r  sfr  r]  r  s                  r   _generate_kittenttsr    s    "##I{B//Iw(?@@JMM'#:;;EMM'3''E|T22J ///3Z@@@-6Yz-B-Bz*;<<<":.E NN4uEjNQQE H'' :%%c1--a069HHXue$$$ ;h'' 	-hk7KXHN84:CUVVVVIh Ih,,,r2   c                 D     r                                  st          dd          S t                      t                    }t	          |          }t          |          }t                     |k    r4t                              d|t                     |            d|          ddl	m
}  |dd	                                          }|d
k    }|rbddlm}  ||          rt          j        dd| ddd          S t!          |                                          }	|t%          |	|          }	nt&          j                                                            d          }
t!          t,                    }|                    dd           |t1          |          }|d|
 d| z  }	n|r|dv r
|d|
 dz  }	n	|d|
 dz  }	|	j                            dd           t5          |	          	 |0t                              d|           t9           ||          n`|t:          vrt=           |          x}	 |n>|dk    rf	 t?                       n)# t@          $ r t          j        dddd          cY S w xY wt                              d           tC                      n|dk    rf	 tE                       n)# t@          $ r t          j        dddd          cY S w xY wt                              d           tG                      nf|dk    r-t                              d            tI                      n3|d!k    r-t                              d"           tK                      n |d#k    rf	 tM                       n)# t@          $ r t          j        dd$dd          cY S w xY wt                              d%           tO                      n|d&k    r-t                              d'           tQ                      na|d(k    rTtS                      st          j        dd)dd          S t                              d*           tU                      n|d+k    rf	 tW                       n)# t@          $ r t          j        dd,dd          cY S w xY wt                              d-           tY                      n|d.k    rf	 t[                       n)# t@          $ r t          j        dd/dd          cY S w xY wt                              d0           t]                      n/d}	 t_                       n# t@          $ r d}Y nw xY w|rt                              d1           	 ddl0}|j1        2                    d23          5 }|3                     fd4          4                    d56           ddd           n# 1 swxY w Y   n# tj          $ r& tm          j7        tq                                Y nYw xY wtS                      r.t                              d7           d(}tU                      nt          j        dd8dd          S tr          j:        ;                              r#tr          j:        <                              dk    rt          j        dd9| d:dd          S d}|Mt{          |          r=>                    d          st                    }|r|>                    d          }n|t:          vrOt          |          }|r=>                    d          st                    }|r|>                    d          }nL|r/|d;v r+>                    d          st                    }|r|d}n|dv r|o>                    d          }tr          j:        <                              }t                              d<|d=|           d> }|rd?| }t          j        d|||d@d          S # t          $ r>}dA| dB| }t          B                    dC|           t          |d          cY d}~S d}~wt          $ r@}dD| dB| }t          B                    dC|dE           t          |d          cY d}~S d}~wt          $ r@}dF| dB| }t          B                    dC|dE           t          |d          cY d}~S d}~ww xY w)Gac  
    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.
    zText is requiredF)successz>TTS text too long for provider %s (%d chars), truncating to %dNr   )get_session_envHERMES_SESSION_PLATFORMr   telegram)has_traversal_componentz/output_path contains '..' traversal component: zM. Use an absolute path or one relative to the current directory without '..'.)r  r  )ensure_asciiz%Y%m%d_%H%M%STr@  tts_r   >   r_   r/   r^   r`   rc  z.mp3z3Generating speech with command TTS provider '%s'...r`   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]   z%Generating speech with MiniMax TTS...r\   z!Generating speech with xAI TTS...r^   ziMistral provider selected but 'mistralai' package not installed. Run: pip install 'hermes-agent[mistral]'z-Generating speech with Mistral Voxtral TTS...r_   z+Generating speech with Google Gemini TTS...ra   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)...r>   zKittenTTS provider selected but 'kittentts' package not installed. Run 'hermes setup tts' and choose KittenTTS, or install manually: pip install https://github.com/KittenML/KittenTTS/releases/download/0.8.1/kittentts-0.8.1-py3-none-any.whlz2Generating speech with KittenTTS (local, ~25MB)...rC   zPiper provider selected but 'piper-tts' package not installed. Run 'hermes tools' and select Piper under TTS, or install manually: pip install piper-ttsz'Generating speech with Piper (local)...z"Generating speech with Edge TTS...rR   )max_workersc                  J    t          j        t                               S r   )asyncior  r  )file_strr   r~   s   r   <lambda>z%text_to_speech_tool.<locals>.<lambda>  s    GK0B4S]0^0^$_$_ r2   r  r  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\   rE   rC   ra   r]   r>   z,TTS audio saved: %s (%s bytes, provider: %s),zMEDIA:z[[audio_as_voice]]
)r  	file_path	media_tagr}   r   zTTS configuration error (r	  z%szTTS dependency missing (r   zTTS generation failed ()Ery   
tool_errorr   r   r   r   r  r   r   gateway.session_contextr  rz   tools.path_securityr  r  dumpsr   rJ  r=  datetimenowstrftimeDEFAULT_OUTPUT_DIRrK  r   r(  r"   r   rW  r   r   r,   r   r  r1   r  r  r  r6   r"  r  r  r  r?   r  rD   r  r&   concurrent.futuresfuturesThreadPoolExecutorsubmitrx  rQ  r  r  r  r   r:  rL  ru  r   r  ry  r   r   r  rv  r!   )r   r   r}   command_provider_configr  r  platform	want_opusr  r   	timestampout_dirr   _plugin_pathedge_available
concurrentpoolr   	opus_pathplugin_voice_compatible	file_sizer  r%   	error_msgr  r~   s   `                       @@r   text_to_speech_toolr    sU   *  =tzz|| =,e<<<<!##JZ((H ?xTT 'x<<G
4yy7Lc$ii	
 	
 	
 HWH~ 8777778"==CCEEHZ'I  '9 	@?????"";// 	#: ="= = =  "# # # # %%0022	". <2 I %))++44_EE	)**dT222".01HIIC":":":S":"::II  	98'TTT"8"8"8"88II"8"8"8"88I 4$7779~~HT4".KKEx   -h*A: HH 2228h*  L 	8
 $HH%%'"$$$$ ' ' 'z$# # !&' ' ' ' ' ''
 KK>??? x<<<<!!'%'''' ' ' 'z$[# # !&' ' ' ' ' ''
 KK>??? x<<<<""KK?@@@!$*====KK;<<<dHj9999""'&(((( ' ' 'z$H# # !&	' ' ' ' ' '' KKGHHH!$*====!!KKEFFF x<<<<!!*,, 'z$F# # !&	' ' ' '
 KKBCCCT8Z8888$$'!#### ' ' 'z$J# #
 !&' ' ' ' ' '' KKLMMMh
;;;;  ' ' ' 'z$5# #
 !&' ' ' ' ' '' KKABBBh
;;;; "N' """" ' ' '!&'  '@AAAP----#+>>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  "# # # # !". 00GHH =((00 - 0 : :I  -#,#+#4#4V#<#< 222
 'K8&T&T#& =((00 - 0 : :I  -#,#+#4#4V#<#< 
	GVVV%%f-- W )22I ($#' FFF(FX->->v-F-FGOOH--	BHQZN^N^`hiii (X''	 	;:y::Iz!"  0
 
    	  4 4 4@@@Q@@	T9%%%)U333333333 4 4 4?x??A??	T9t444)U333333333 4 4 4>h>>1>>	T9t444)U333333333	4sz  ;Aa I% $a %#Ja 
J6a K a #K74a 6K77Ba N# "a ##O	a O		Ba 3a R a #S?a S6a 9T a #T.+a -T..2a !U0 /a 0U?<a >U??a X >/W9-X 9W==X  W=X a -X52a 4X55Aa Aa ,Ea 
d3b
d
d5cdd5dddc                     t                      rdS 	 t                       dS # t          $ r Y nw xY w	 t                       t	          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	          d          rdS 	 ddlm}   |             	                    d          rdS n# t          $ r Y nw xY wt	          d          st	          d          rdS 	 t                       t	          d	          rdS n# t          $ r Y nw xY wt                      rdS t                      rdS t                      rdS d
S )a,  
    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. A user-declared command provider
    also satisfies the requirement.

    Returns:
        bool: True if at least one provider can work.
    Tr  r  r   r  r  rj  rk  r  F)r[  r&   r   r,   r   r1   _has_openai_audio_backendr  r  r   r!   r6   r  r  r  r  s    r   check_tts_requirementsr  	  s    %&& tt   -.. 	4	   $&& 	4	   &'' t??????''))--i88 	4	   %&& -8H*I*I t   *++ 	4	      t!## t t5sS   " 
//A 
A A $B 
BB%#C 
CC<D 
D)(D)c                  ^   t                      } | rt          d          s	| t          fS t          d          }|Cd}t	                      st          d          r|dt          d          z   z  }t          |          |j        t          |j	        
                    d           dd          fS )	zReturn direct OpenAI audio config or a managed gateway fallback.

    When ``tts.use_gateway`` is set in config, the Tool Gateway is preferred
    even if direct OpenAI credentials are present.
    r   openai-audioNz8Neither VOICE_TOOLS_OPENAI_KEY nor OPENAI_API_KEY is setz. zmanaged OpenAI audio for TTSr  v1)r   r   DEFAULT_OPENAI_BASE_URLr   r   r   r   nous_user_tokenr   gateway_originr  )direct_api_keymanaged_gatewayrN  s      r   r  r  V	  s     233N 7oe44 76662>BBOL%'' 	?5+A+A 	72 G !!!*G)0055888$- -  r2   c                  V    t          t                      pt          d                    S )zPReturn True when OpenAI audio can use direct credentials or the managed gateway.r   )rv   r   r   r   r2   r   r  r  q	  s%    ,..^2N~2^2^___r2   z(?<=[.!?])(?:\s|\n)|(?:\n\n)z```[\s\S]*?```z\[([^\]]+)\]\([^)]+\)zhttps?://\S+z\*\*(.+?)\*\*z	\*(.+?)\*z`(.+?)`z^#+\s*z^\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.r  z\1r   rd  )_MD_CODE_BLOCKr  _MD_LINK_MD_URL_MD_BOLD
_MD_ITALIC_MD_INLINE_CODE
_MD_HEADER_MD_LIST_ITEM_MD_HR_MD_EXCESS_NLry   )r   s    r   _strip_markdown_for_ttsr3  	  s    c4((D<<t$$D;;r4  D<<t$$D>>%&&Dud++D>>"d##DR&&D::b$DVT**D::<<r2   
text_queue
stop_eventtts_done_eventdisplay_callbackc           
         |                                  	 ddt          t          t                      }|                    di           }|                    d          |                    d|                    d                    t          di |di |dii          t          d          p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.
    Nr`   r  streaming_model_idr   r  r   z8ELEVENLABS_API_KEY not set; streaming TTS audio disabledr  z8elevenlabs package not installed; streaming TTS disabledrN   rR   int16)
samplerater$  dtypezsounddevice not available: %sz#sounddevice OutputStream failed: %s   r|  g      ?z<think[\s>].*?</think>r  sentencec                 H  	                                  rdS t          |                                           }|sdS |                                                    d          }
D ]0}|                                                    d          |k    r dS 1
                    |            |            dS t          |          k    r
|d         }	 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   )r<  r  rR   z!Streaming TTS sentence failed: %s)is_setr3  ry   rz   r  r  r  r  r  numpy
frombufferr:  r  reshaper!   r   r   )r>  cleanedcleaned_lowerprev
audio_iterr  _npaudio_arrayr   _play_via_tempfile_spoken_sentencesr  r7  r   output_streamr5  stream_max_lenr  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||n,,!/>/2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E1 #E1 1
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  Fr}  r  rR   rS   rN   )play_audio_filez!Temp-file TTS fallback failed: %s)r  rN  r  r   r  setnchannelssetsampwidthsetframeraterA  writeframestools.voice_moderQ  r!   r   r   r   rM  r?  )	rH  stop_evttmp_pathr  r  wfr  rQ  r   s	            r   rK  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   r   r   r   r   r   r,   r   r:   OutputStreamr  r?  r   r!   r   r  rJ  r"   rA  queueEmptyr  r  ry   _SENTENCE_BOUNDARY_REr  end
get_nowaitstopr  set)r4  r5  r6  r7  r~   r  r  r*   r9   r   sentence_bufmin_sentence_lenlong_flush_lenqueue_timeout_think_block_rerO  deltamend_posr>  rK  rL  r  r   rM  rN  r  s    ` `                @@@@@@@r   stream_tts_to_speakerrl  	  s   " {.8%''
NN<44	==X66==!5!*z8!D!DF F 2MzM<)LI)Lz8)L)LMM
 

 !!566<" 	)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 (	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  C N  C; :N ;$D"N !D""N (:E# "N #G
4FN G
#G N G

AN %H< ;N </I.+N -I..C2N !M6 5N 6NN NN P 
N<N72P 7N<<P (O+ +
O87O8Q#(P>=Q#>
QQ#
QQ#__main__u   🔊 Text-to-Speech Tool Modulez2==================================================c                 >    	  |              dS # t           $ r Y dS w xY w)NTF)r   )importerlabels     r   _checkrq  p
  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:  r  rc  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  Piper:      z%not installed (pip install piper-tts)z  ffmpeg:     u	   ✅ foundu(   ❌ not found (needed for Telegram Opus)z
  Output dir: z  Configured provider: )registryr  r  a  Convert text to speech audio. Returns a MEDIA: path that the platform delivers as native audio. Compatible providers render as a voice bubble on Telegram; otherwise audio is sent as a regular attachment. In CLI mode, saves to ~/voice-memos/. Voice and provider are user-configured (built-in providers like edge/openai or custom command providers under tts.providers.<name>), not model-selected.objectstringzThe text to convert to speech. Provider-specific character caps apply and are enforced automatically (OpenAI 4096, xAI 15000, MiniMax 10000, ElevenLabs 5k-40k depending on model); over-long input is truncated.)r   descriptionz9Optional custom file path to save the audio. Defaults to z/audio_cache/<timestamp>.mp3r   r   )r   
propertiesrequired)r   rx  
parametersr   c                 r    t          |                     dd          |                     d                    S )Nr   r   r   ry  )r  r   )argskws     r   r  r  
  s6    2XXfb!!HH]++ -  -  - r2   u   🔊)r   toolsetschemahandlercheck_fnemojir   )F)r   )__doc__r  r  r  r  loggingr   r]  r   r   r^  r   rN  	threadingr  pathlibr   typingr   r   r   r   urllib.parser   rY   r	   	getLoggerr   r   r   tools.managed_tool_gatewayr   tools.tool_backend_helpersr   r   r   r   r  r   r&   r,   r1   r6   r:   r?   rD   r   r  r  r   r[  r  r  r  r  r  r"  r  r  r  r  r  r  r  r  r  r  r  r  r  r  rF  r\  GEMINI_TTS_SAMPLE_RATEGEMINI_TTS_CHANNELSGEMINI_TTS_SAMPLE_WIDTHr"   rZ   r	  rb   rw   __annotations__ri   rv   r|   r   MAX_TEXT_LENGTHr   r   r   	frozensetr   r   r   r   r   r   r   r   r   r   r   r   rx   r   r   r   r   r   r  r3  r*  r7  r9  r=  rW  r[  r`  ry  r  r  r  _XAI_INLINE_SPEECH_TAGS_XAI_WRAPPING_SPEECH_TAGSr  r  re  r  rJ  r  r  r  r  r  r"  r  r3  r<  rA  rC  rG  rL  rQ  rb  rh  r  r  r  r  r  r  r  r  r  r  r  r  r  r  r  tupler  r  r_  r)  r*  r+  r,  r-  r.  	MULTILINEr/  r0  r1  r2  r3  QueueEventrl  printrq  r   r}   tools.registryru  r  
TTS_SCHEMAregisterr   r2   r   <module>r     s  ! ! !F      				  				                   0 0 0 0 0 0 0 0 0 0 0 0             0 0 0 0 0 0		8	$	$/ / / / D C C C C C            1 0 0 0 0 0
 
 
  *  
  $    	 	 	  ' 4 6 (; %( = " +  5 & 8 = 3 E     $ , 9 ! P !  0    = = = = = -,..  , , $sCx.    ##""	4 	4  $sCx. 	 	 	  d t    "    +
 ,05$ 5$sm5$c3h(5$ 	5$ 5$ 5$ 5$v$sCx.    &Ld38n L L L L LH "	 # # #    '* #$) !&Y'D'D'DEE &* #8d38n 8C 8DcN 8 8 8 8S#X
 
#s(^   0>S#X >4 > > > >S#X d38n   *XL
XLXL XL S#X	XL
 c]XL XL XL XLv3 4    4 S#X        	T#s(^ 	 	 	 	 	 "&[ [cN[#[ 	[ [ [ [$T#s(^     3 # (3-    ># hsm PS    "sCx. 	   <1j.> 14 1 1 1 1h%Qc %QE %Qj6Q %Q %Q %Q %QP'd 'DcN 't ' ' ' 'A
AA A cN	A
 S#XA 	A A A AH htCH~.F RV    .T . . . .
!s !x} ! ! ! !N3 S d3PS8n Y\    <(s ( ($sCx. (UX ( ( ( (\-s - -$sCx. -UX - - - -f     RZ0111MACHHMfDgDggjpp
-    $$ERYWWW 0 0C 0$ 04 0 0 0 0c c    &EC Ec EtCH~ ERU E E E EV~A ~A# ~A4S> ~AVY ~A ~A ~A ~AH+ +# +4S> +VY + + + +f .'/	C CCC C 	C
 C C C CDtCH~ (SW.    "tCH~ #     >S >T > > > >d38n S T    "S S         - - -c -3 - - - -f %)V V
VS>V SMV 		V V V V>Qs Q Q$sCx. QUX Q Q Q Qp    D    D3 D D D D
D# D D D D
23 2S 2d38n 2QT 2 2 2 2x &( DcN ' ' '    	t 	 	 	 	2S 2 2 2 2 2 2jKc K Kc3h KTW K K K Kf *, S#X + + +4c 4 4c3h 4TW 4 4 4 4x "&u4 u4
u4#u4 	u4 u4 u4 u4v	6 6 6 6 6rU38_    6`4 ` ` ` ` #
#BCC  -..2:.//
"*_
%
%2:&''RZ%%
"*Z((RZ	666

>>>>	G		
9%%# #    & 9=	N NNN ON xt45	N N N Nh 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
XMM2F$G$GV55Y
X
XYYY	E
e&&1F*N*Nc;;Tc
e
efff	E	o0022l558l	o 	o   
E
oMM:K,L,Lm==Rm
o
oppp	E
q*@*@*B*Bo;;Ho
q
qrrr	E
i++--g;;=g
i
ijjj	E
1/
1
1222F}V$$H	E
.H
.
./// 0 / / / / / / /  ` !  s 
 !  O[n[n[p[p   O   O   O 	
 	
 H  
&  	- - $
	 	 	 	 	 	r2   