Ë
    ãß²j4%  ã                  óD  — U d Z ddlm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mZmZmZ  ej$                  d«      ZdZd	Zd
Z ee«      j0                  dz  ZdZi Zded<   i Zded<   eedœZded<   dd„Zdd„Z dd„Z!d d„Z"d!d„Z#d"d„Z$dZ%dd„Z&d"d„Z'dd„Z(y)#aÀ  Local on-device TTS engines for ``tools.tts_tool``: NeuTTS, Piper, KittenTTS.

All three synthesize WAV natively; :func:`_finalize_wav_output` converts/renames to the requested
container. Piper and KittenTTS keep loaded models in small LRU caches registered in
``_LOCAL_TTS_MODEL_CACHES`` so warm/release can pre-load or drop them. ``_import_piper`` /
``_import_kittentts`` are resolved through the origin module at call time (test monkeypatches).
é    )ÚannotationsN)ÚPath)ÚAnyÚCallableÚDictÚTuple)Ú_finalize_wav_outputÚ_originÚ_sectionÚ_wav_sidecar_pathztools.tts_toolz!KittenML/kitten-tts-nano-0.8-int8ÚJasperzen_US-lessac-mediumÚneutts_samplesé   úDict[str, Any]Ú_piper_voice_cacheÚ_kittentts_model_cache)ÚpiperÚ	kittenttszDict[str, Dict[str, Any]]Ú_LOCAL_TTS_MODEL_CACHESc                óê   — || v r| j                  |«      | |<   | |   S  |«       }|| |<   t        | «      t        kD  r7| j                  t        t	        | «      «      d«       t        | «      t        kD  rŒ7|S )z¶Get ``key`` from ``cache`` or load it, LRU-bounded at ``_TTS_MODEL_CACHE_MAX`` (a hit refreshes
    recency via pop + reinsert; eviction only releases the slot, not live references).N)ÚpopÚlenÚ_TTS_MODEL_CACHE_MAXÚnextÚiter)ÚcacheÚkeyÚloadÚvalues       ú9/home/ubuntu/.hermes/hermes-agent/tools/tts_tool_local.pyÚ_tts_cache_get_or_loadr!   (   sn   € ð ˆe�|Ø—Y‘Y˜s“^ˆˆc‰
Ø�S‰zÐÙ‹F€EØ€Eˆ#�JÜ
ˆe‹*Ô+Ò
+Ø�	‰	”$”t˜E“{Ó# TÔ*ô ˆe‹*Ô+Ó
+à€Ló    c           	     óV   — t        j                  | dddd|t         j                  ¬«      S )NTzutf-8Úreplace)Úcapture_outputÚtextÚencodingÚerrorsÚtimeoutÚstdin)Ú
subprocessÚrunÚDEVNULL)Úcmdr)   s     r    Ú_run_helperr/   5   s*   € Ü�>‰>Ø˜D t°gÀiÐY`Ôhr×hzÑhzôð r"   c                óÎ  — |j                  d«      xs i }t        |«      }t        j                  t	        t        t        «      j                  dz  «      d| d|d|j                  dd«      xs t	        t        dz  «      d	|j                  d
d«      xs t	        t        dz  «      d|j                  dd«      d|j                  dd«      g}t        |d«      }|j                  dk7  rq|j                  j                  «       j                  «       D �cg c]  }|j                  d«      rŒ|‘Œ }}t        dt!        d«      j#                  |«      xs d› �«      ‚t%        ||«      S c c}w )NÚneuttszneutts_synth.pyz--textz--outz--ref-audioÚ	ref_audioÚ zjo.wavz
--ref-textÚref_textzjo.txtz--modelÚmodelzneuphonic/neutts-air-q4-ggufz--deviceÚdeviceÚcpuéx   r   zOK:zNeuTTS synthesis failed: é
   zunknown error)Úgetr   ÚsysÚ
executableÚstrr   Ú__file__ÚparentÚ_NEUTTS_SAMPLESr/   Ú
returncodeÚstderrÚstripÚ
splitlinesÚ
startswithÚRuntimeErrorÚchrÚjoinr	   )	r&   Úoutput_pathÚ
tts_configÚneutts_configÚwav_pathr.   ÚresultÚlÚerror_liness	            r    Ú_generate_neuttsrP   <   sB  € Ø—N‘N 8Ó,Ò2°€MÜ  Ó-€Hä�‰œœD¤›N×1Ñ1Ð4EÑEÓFØ�$Ø�Ø�}×(Ñ(¨°bÓ9Ò\¼SÄÐS[ÑA[Ó=\Ø�m×'Ñ'¨
°BÓ7ÒZ¼3¼ÐQYÑ?YÓ;ZØ�=×$Ñ$ WÐ.LÓMØ�M×%Ñ% h°Ó6ð8€Cô ˜˜cÓ"€FØ×Ñ˜AÒØ"(§-¡-×"5Ñ"5Ó"7×"BÑ"BÓ"DÖ`˜QÈAÏLÉLÐY^ÕL_’qÐ`ˆÐ`ÜÐ6´s¸2³w·|±|ÀKÓ7PÒ7cÐTcÐ6dÐeÓfÐfÜ ¨+Ó6Ð6ùò as   ÄE"Ä&E"c                 ó\   — ddl m}  t         | dd«      «      }|j                  dd¬«       |S )zS``<HERMES_HOME>/cache/piper-voices/`` so voice downloads follow profile boundaries.r   )Úget_hermes_dirzcache/piper-voicesÚpiper_voices_cacheT©ÚparentsÚexist_ok)Úhermes_constantsrR   r   Úmkdir)rR   Úroots     r    Ú_get_piper_voices_dirrZ   O   s-   € å/Ü‘Ð3Ð5IÓJÓK€DØ‡J�J�t d€JÔ+Ø€Kr"   c           
     óà  — | xs t         } t        | «      j                  «       }|j                  j	                  «       dk(  r|j                  «       rt        |«      S || › d�z  }|j                  «       r!|| › d�z  j                  «       rt        |«      S t        j                  d| |«       	 t        t        j                  dd| dt        |«      gd«      }|j                  dk7  r6|j                   xs dj#                  «       xs d}t        d| › d|d
d › �«      ‚|j                  «       st        d|› d�«      ‚t        |«      S # t        j                  $ r}t        d| › d	�«      |‚d
}~ww xY w)z°Resolve *voice* (an .onnx path or a name like ``en_US-lessac-medium``, downloaded into
    *download_dir* on first use) to a concrete .onnx file; RuntimeError when it can't be.z.onnxz
.onnx.jsonz0[Piper] Downloading voice '%s' to %s (first use)z-mzpiper.download_voicesz--download-diri,  z/Piper voice download timed out after 300s for 'ú'Nr   r3   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   Ú
expanduserÚsuffixÚlowerÚexistsr=   ÚloggerÚinfor/   r;   r<   r+   ÚTimeoutExpiredrF   rA   rB   rC   )ÚvoiceÚdownload_dirÚ	candidateÚcachedrM   ÚexcrB   s          r    Ú_resolve_piper_voice_pathrj   W   s|  € ð Ò(Ô(€EÜ�U“×&Ñ&Ó(€IØ×Ñ×ÑÓ 7Ò*¨y×/?Ñ/?Ô/AÜ�9‹~ÐØ˜u˜g U˜OÑ+€FØ‡}�}„˜L¨e¨W°JÐ+?Ñ?×GÑGÔIÜ�6‹{ÐÜ
‡K�KÐBÀEÈ<ÔXð`ÜÜ�^‰^˜TÐ#:¸EÐCSÔUXÐYeÓUfÐgÐiló
ˆð
 ×Ñ˜AÒØ—-‘-Ò% 2×,Ñ,Ó.ÒDÐ2DˆÜÐ>¸u¸gÀSÈÐPTÐQTÈÈÐWÓXÐXØ�=‰=Œ?ÜØ1°&°ð :(ð )ó*ð 	*ô ˆv‹;Ðøô ×$Ñ$ò `ÜÐLÈUÈGÐSTÐUÓVÐ\_Ð_ûð`ús   Â-)E ÅE-ÅE(Å(E-c                ó   ‡‡‡— t        «       j                  «       Št        | d«      }|j                  d«      xs t        }t        |j                  d«      xs
 t        «       «      j                  «       }|j                  dd¬«       t        |j                  dd«      «      Št        ||«      Šˆˆˆfd„}‰› d	‰› �}t        t        ||«      |fS )
zÁResolve + load (or fetch from cache) the selected Piper voice -> ``(voice, piper_config)``.
    Shared by synthesis and ``warm_tts_provider`` so a warm-up fills exactly the slot synthesis hits.r   re   Ú
voices_dirTrT   Úuse_cudaFc                 ó„   •— t         j                  d‰«       ‰j                  ‰‰¬«      } t         j                  d«       | S )Nz[Piper] Loading voice: %s)rm   z[Piper] Voice loaded)rb   rc   r   )ÚvÚ
PiperVoiceÚ
model_pathrm   s    €€€r    Ú_load_piper_voicez7_load_piper_voice_for_config.<locals>._load_piper_voice~   s6   ø€ Ü�‰Ð/°Ô<Ø�O‰O˜J°ˆOÓ:ˆÜ�‰Ð*Ô+Øˆr"   z::cuda=)r
   Ú_import_piperr   r:   r]   r   rZ   r^   rX   Úboolrj   r!   r   )	rJ   Úpiper_configÚ
voice_namerf   rr   Ú	cache_keyrp   rq   rm   s	         @@@r    Ú_load_piper_voice_for_configrx   s   sÂ   ú€ ô “×(Ñ(Ó*€JÜ˜J¨Ó0€LØ×!Ñ! 'Ó*ÒAÔ.A€JÜ˜×(Ñ(¨Ó6ÒQÔ:OÓ:QÓR×]Ñ]Ó_€LØ×Ñ˜t¨dÐÔ3Ü�L×$Ñ$ Z°Ó7Ó8€HÜ*¨:°|ÓD€Jöð �,˜g h ZÐ0€IÜ!Ô"4°iÐARÓSÐUaÐaÐar"   ©Úlength_scaleÚnoise_scaleÚnoise_w_scaleÚvolumeÚnormalize_audioÚ
speaker_idc                óä  ‡— dd l }t        |«      \  }Š‰j                  dd«      }t        |«      t        u r|nd}d }t        ˆfd„t        D «       «      r’	 ddlm}  |t        ‰j                  dd«      «      t        ‰j                  dd«      «      t        ‰j                  d	d
«      «      t        ‰j                  dd«      «      t        ‰j                  dd«      «      |¬«      }t        |«      }	|j                  |	d«      5 }
|�|j!                  | |
|¬«       n|j!                  | |
«       d d d «       t#        |	|«      S # t        $ r t        j                  d«       Y Œ{w xY w# 1 sw Y   Œ9xY w)Nr   r   c              3  ó&   •K  — | ]  }|‰v –— Œ
 y ­w)N© )Ú.0Úkru   s     €r    ú	<genexpr>z&_generate_piper_tts.<locals>.<genexpr>•   s   øè ø€ Ò
< ˆ1�ÔÑ
<ùs   ƒ)ÚSynthesisConfigrz   ç      ð?r{   gòÒMbXå?r|   gš™™™™™é?r}   r~   Try   uZ   [Piper] SynthesisConfig not available in this piper-tts version â€” advanced knobs ignoredÚwb)Ú
syn_config)Úwaverx   r:   ÚtypeÚintÚanyÚ_PIPER_ADVANCED_KNOBSr   r†   Úfloatrt   ÚImportErrorrb   Úwarningr   ÚopenÚsynthesize_wavr	   )r&   rI   rJ   rŠ   re   Ú_raw_speakerr   r‰   r†   rL   Úwav_fileru   s              @r    Ú_generate_piper_ttsr–   Œ   s[  ø€ ÛÜ6°zÓBÑ€Eˆ<à×#Ñ# L°!Ó4€LÜ!% lÓ!3´sÑ!:‘À€Jð €JÜ
Ó
<Ô&;Ô
<Ô<ð
	yÝ-Ù(Ü" <×#3Ñ#3°NÀCÓ#HÓIÜ! ,×"2Ñ"2°=À%Ó"HÓIÜ# L×$4Ñ$4°_ÀcÓ$JÓKÜ˜\×-Ñ-¨h¸Ó<Ó=Ü $ \×%5Ñ%5Ð6GÈÓ%NÓ OØ%ô'ˆJô ! Ó-€HØ	�‰�8˜TÓ	"ð 1 hØÐ!Ø× Ñ   x¸JÐ ÕGà× Ñ   xÔ0÷	1ô
   ¨+Ó6Ð6øô ò 	yÜ�N‰NÐwÖxð	yú÷1ð 1ús   ÁBE Ä*E&ÅE#Å"E#Å&E/c                ó¬   ‡‡— t        «       j                  «       Št        | d«      }|j                  dt        «      Šˆˆfd„}t        t        ‰|«      |fS )zVLoad (or fetch from cache) the KittenTTS model; returns ``(model, kittentts_config)``.r   r5   c                 ón   •— t         j                  d‰«        ‰‰«      } t         j                  d«       | S )Nz[KittenTTS] Loading model: %sz%[KittenTTS] Model loaded successfully)rb   rc   )ÚmÚ	KittenTTSÚ
model_names    €€r    Ú_load_kittentts_modelz?_load_kittentts_model_for_config.<locals>._load_kittentts_model±   s.   ø€ Ü�‰Ð3°ZÔ@Ù�jÓ!ˆÜ�‰Ð;Ô<Øˆr"   )r
   Ú_import_kittenttsr   r:   ÚDEFAULT_KITTENTTS_MODELr!   r   )rJ   Ú	kt_configrœ   rš   r›   s      @@r    Ú _load_kittentts_model_for_configr    «   sN   ù€ ä“	×+Ñ+Ó-€IÜ˜ [Ó1€IØ—‘˜wÔ(?Ó@€Jõô "Ô"8¸*ÐF[Ó\Ð^gÐgÐgr"   c           	     ó  — t        |«      \  }}|j                  | |j                  dt        «      |j                  dd«      |j                  dd«      ¬«      }dd l}t        |«      }|j                  ||d«       t        ||«      S )	Nre   Úspeedr‡   Ú
clean_textT)re   r¢   r£   r   iÀ]  )r    Úgenerater:   ÚDEFAULT_KITTENTTS_VOICEÚ	soundfiler   Úwriter	   )r&   rI   rJ   r5   rŸ   ÚaudioÚsfrL   s           r    Ú_generate_kittenttsrª   º   s~   € Ü7¸
ÓCÑ€Eˆ9Ø�N‰NØ�I—M‘M 'Ô+BÓCØ�m‰m˜G SÓ)°i·m±mÀLÐRVÓ6Wð ó Y€Eó Ü  Ó-€HØ‡H�HˆX�u˜eÔ$Ü ¨+Ó6Ð6r"   )r   r   r   r=   r   zCallable[[], Any]Úreturnr   )r.   Úlistr)   rŒ   r«   zsubprocess.CompletedProcess)r&   r=   rI   r=   rJ   r   r«   r=   )r«   r   )re   r=   rf   r   r«   r=   )rJ   r   r«   zTuple[Any, Dict[str, Any]]))Ú__doc__Ú
__future__r   Úloggingr+   r;   Úpathlibr   Útypingr   r   r   r   Útools.tts_tool_deliveryr	   r
   r   r   Ú	getLoggerrb   rž   r¥   r]   r>   r?   r@   r   r   Ú__annotations__r   r   r!   r/   rP   rZ   rj   rx   rŽ   r–   r    rª   r‚   r"   r    ú<module>rµ      sÑ   ðòõ #ã Û Û 
Ý ß -Ó -ç ^Ó ^à	ˆ×	Ñ	Ð+Ó	,€à=Ð Ø"Ð Ø+Ð Ù�x“.×'Ñ'Ð*:Ñ:€ð
 Ð ð
 &(Ð �NÓ 'Ø)+Ð ˜Ó +àÐ.Dñ6FÐ Ð2ó Fó
óó7ó&óó8bð, tÐ ó7ó>hô7r"   