o
    i]                  	   @   s(  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m	Z	 ddl
mZ ddlmZ ddlmZmZmZmZmZmZmZmZ ddlmZm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"m#Z#m$Z$ d	dl%m&Z& d	dl'm(Z( d	dl)m*Z*m+Z+m,Z,m-Z- d	dlm.Z.m/Z/ ej0e1 dZ2de3de4deee3e3f e3f fddZ5dee6e3f de6fddZ7de6fddZ8de3de4de4fddZ9de3de4fdd Z:de3d!e4de4fd"d#Z;dee6e3f d$e4dee3ddf fd%d&Z<d'e&d(ee6e3f de6fd)d*Z=de6fd+d,Z>d-e6d.e6d/e6de6fd0d1Z?G d2d3 d3Z@dS )4zCommunicate with the service. Only the Communicate class should be used by
end-users. The other classes and functions are for internal use only.    N)nullcontext)TextIOWrapper)Queue)AsyncGeneratorContextManagerDict	GeneratorListOptionalTupleUnion)escapeunescape)Literal   )DEFAULT_VOICEMP3_BITRATE_BPSSEC_MS_GEC_VERSIONTICKS_PER_SECONDWSS_HEADERSWSS_URL)	TTSConfig)DRM)NoAudioReceivedUnexpectedResponseUnknownResponseWebSocketError)CommunicateStateTTSChunk)cafiledataheader_lengthreturnc                 C   sZ   t | ts	tdi }| d| dD ]}|dd\}}|||< q|| |d d fS )z
    Returns the headers and data from the given data.

    Args:
        data (bytes): The data to be parsed.
        header_length (int): The length of the header.

    Returns:
        tuple: The headers and data to be used in the request.
    zdata must be bytesNs   
   :r      )
isinstancebytes	TypeErrorsplit)r    r!   headerslinekeyvalue r-   N/home/kim/smarthome/.venv/lib/python3.10/site-packages/edge_tts/communicate.pyget_headers_and_data2   s   

r/   stringc                 C   s   t | tr
| d} t | tstdt| }t|D ].\}}t|}d|  kr-dksEn d|  kr8dksEn d|  krCdkrIn qd	||< qd
|S )aS  
    The service does not support a couple character ranges.
    Most important being the vertical tab character which is
    commonly present in OCR-ed PDFs. Not doing this will
    result in an error from the service.

    Args:
        string (str or bytes): The string to be cleaned.

    Returns:
        str: The cleaned string.
    utf-8zstring must be str or bytesr                    )	r%   r&   decodestrr'   list	enumerateordjoin)r0   charsidxcharcoder-   r-   r.   remove_incompatible_charactersJ   s   


D
rC   c                   C   s
   t  jS )zZ
    Returns a UUID without dashes.

    Returns:
        str: A UUID without dashes.
    )uuiduuid4hexr-   r-   r-   r.   
connect_idf   s   
rG   textlimitc                 C   s(   |  dd|}|dk r|  dd|}|S )a  
    Finds the index of the rightmost preferred split character (newline or space)
    within the initial `limit` bytes of the text.

    This helps find a natural word or sentence boundary for splitting, prioritizing
    newlines over spaces.

    Args:
        text (bytes): The byte string to search within.
        limit (int): The maximum index (exclusive) to search up to.

    Returns:
        int: The index of the last found newline or space within the limit,
             or -1 if neither is found in that range.
       
r       )rfind)rH   rI   split_atr-   r-   r.   (_find_last_newline_or_space_within_limitp   s   rN   text_segmentc                 C   sP   t | }|dkr&z| d| d |W S  ty!   |d8 }Y nw |dks|S )a  
    Finds the rightmost possible byte index such that the
    segment `text_segment[:index]` is a valid UTF-8 sequence.

    This prevents splitting in the middle of a multi-byte UTF-8 character.

    Args:
        text_segment (bytes): The byte segment being considered for splitting.

    Returns:
        int: The index of the safe split point. Returns 0 if no valid split
             point is found (e.g., if the first byte is part of a multi-byte
             sequence longer than the limit allows).
    r   Nr1   r   )lenr9   UnicodeDecodeError)rO   rM   r-   r-   r.   _find_safe_utf8_split_point   s   	rR   rM   c                 C   s^   |dkr-d| d| v r-|  dd|}| d||dkr	 |S |}|dkr-d| d| v s|S )a  
    Adjusts a proposed split point backward to prevent splitting inside an XML entity.

    For example, if `text` is `b"this &amp; that"` and `split_at` falls between
    `&` and `;`, this function moves `split_at` to the index before `&`.

    Args:
        text (bytes): The text segment being considered.
        split_at (int): The proposed split point index, determined by whitespace
                        or UTF-8 safety.

    Returns:
        int: The adjusted split point index. It will be moved to the '&'
             if an unterminated entity is detected right before the original `split_at`.
             Otherwise, the original `split_at` is returned.
    r      &N   ;)rindexfind)rH   rM   Zampersand_indexr-   r-   r.   "_adjust_split_point_for_xml_entity   s   
rX   byte_lengthc                 c   s    t | tr| d} t | tstd|dkrtdt| |kr[t| |}|dk r/t| }t	| |}|dk r<td| d| 
 }|rI|V  | |dkrP|ndd } t| |ks"| 
 }|rf|V  dS dS )a  
    Splits text into chunks, each not exceeding a maximum byte length.

    This function prioritizes splitting at natural boundaries (newlines, spaces)
    while ensuring that:
    1. No chunk exceeds `byte_length` bytes.
    2. Chunks do not end with an incomplete UTF-8 multi-byte character.
    3. Chunks do not split XML entities (like `&amp;`) in the middle.

    Args:
        text (str or bytes): The input text. If str, it's encoded to UTF-8.
        byte_length (int): The maximum allowed byte length for any yielded chunk.
                           Must be positive.

    Yields:
        bytes: Text chunks (UTF-8 encoded, stripped of leading/trailing whitespace)
               that conform to the byte length and integrity constraints.

    Raises:
        TypeError: If `text` is not str or bytes.
        ValueError: If `byte_length` is not positive, or if a split point
                    cannot be determined (e.g., due to extremely small byte_length
                    relative to character/entity sizes).
    r1   ztext must be str or bytesr   z"byte_length must be greater than 0zTMaximum byte length is too small or invalid text structure near '&' or invalid UTF-8Nr   )r%   r:   encoder&   r'   
ValueErrorrP   rN   rR   rX   strip)rH   rY   rM   chunkZremaining_chunkr-   r-   r.   split_text_by_byte_length   s2   





r^   tcescaped_textc                 C   s@   t |tr
|d}d| j d| j d| j d| j d| dS )z
    Creates a SSML string from the given parameters.

    Args:
        tc (TTSConfig): The TTS configuration.
        escaped_text (str or bytes): The escaped text. If bytes, it must be UTF-8 encoded.

    Returns:
        str: The SSML string.
    r1   z_<speak version='1.0' xmlns='http://www.w3.org/2001/10/synthesis' xml:lang='en-US'><voice name='z'><prosody pitch='z' rate='z
' volume='z'>z</prosody></voice></speak>)r%   r&   r9   voicepitchratevolume)r_   r`   r-   r-   r.   mkssml  s   

re   c                   C   s   t dt  S )zg
    Return Javascript-style date string.

    Returns:
        str: Javascript-style date string.
    z:%a %b %d %Y %H:%M:%S GMT+0000 (Coordinated Universal Time))timestrftimegmtimer-   r-   r-   r.   date_to_string   s   ri   
request_id	timestampssmlc                 C   s   d|  d| d| S )z
    Returns the headers and data to be used in the request.

    Returns:
        str: The headers and data to be used in the request.
    zX-RequestId:z1
Content-Type:application/ssml+xml
X-Timestamp:zZ
Path:ssml

r-   )rj   rk   rl   r-   r-   r.   ssml_headers_plus_data0  s   	rm   c                   @   s,  e Zd ZdZefdddddddddd	ed
ededededed deej	 dee dee
 dee
 fddZdedefddZd(ddZdeedf fddZdeedf fddZ	d)d eeef d!eeeef  ddfd"d#Zdeeddf fd$d%Z	d)d eeef d!eeeef  ddfd&d'ZdS )*Communicatez'
    Communicate with the service.
    z+0%z+0HzSentenceBoundaryN
   <   )rc   rd   rb   boundary	connectorproxyconnect_timeoutreceive_timeoutrH   ra   rc   rd   rb   rr   WordBoundaryro   rs   rt   ru   rv   c                C   s   t |||||| _t|tstdttt|d| _|d ur)t|ts)td|| _	t|	t
s5tdt|
t
s>tdtjd d |	|
d| _|d urVt|tjsVtd|| _dd	d	d
d	d	d| _d S )Nztext must be stri   zproxy must be strzconnect_timeout must be intzreceive_timeout must be int)totalconnectZsock_connectZ	sock_readz'connector must be aiohttp.BaseConnector    r   F)partial_textoffset_compensationlast_duration_offsetstream_was_calledchunk_audio_bytescumulative_audio_bytes)r   
tts_configr%   r:   r'   r^   r   rC   textsrt   intaiohttpZClientTimeoutsession_timeoutBaseConnectorrs   state)selfrH   ra   rc   rd   rb   rr   rs   rt   ru   rv   r-   r-   r.   __init__G  s<   



zCommunicate.__init__r    r"   c                 C   s   t |d D ]7}|d }|dv r3|d d | jd  }|d d }|||t|d d d	 d
  S |dv r8qtd| td)NZMetadataTyperw   ZDataZOffsetr}   ZDurationrH   Text)typeoffsetdurationrH   )Z
SessionEndzUnknown metadata type: zNo WordBoundary metadata found)jsonloadsr   r   r   r   )r   r    Zmeta_objZ	meta_typecurrent_offsetZcurrent_durationr-   r-   r.   Z__parse_metadata  s   
zCommunicate.__parse_metadatac                 C   sB   | j d  | j d 7  < | j d d t t | j d< d| j d< dS )a  Update inter-chunk offset_compensation from cumulative CBR audio bytes.

        The output format is audio-24khz-48kbitrate-mono-mp3 (48 kbps CBR).
        For any CBR stream the byte-to-tick conversion is exact integer
        arithmetic:  ticks = total_bytes * 8 * 10_000_000 // 48_000.

        This replaces the previous metadata-based accumulation which drifted
        on long texts due to variable AI silence and Microsoft's integer
        overflow in reported offsets.
        r   r   r2   r}   r   N)r   r   r   r   r-   r-   r.   Z__compensate_offset  s   zCommunicate.__compensate_offsetc              
     s  d) fdd}d) fdd}d}t j jd jd4 I d H 4}|jt d	t  d
t  dt	 d j
tttd4 I d H | I d H  | I d H  2 z3 d H W }|jt jjkr|jd}t||d\}}|dd }	|	dkr |}
|
V  |
d |
d   jd< qR|	dkr    n|	dvrtdqR|jt jjkrt|jdk rtdt|jd d d}|t|jkrtdt|j|\}}|ddkrtd|dd }|dvrtd |d u rt|d!krqRtd"t|d!krtd#d} jd$  t|7  < d%|d&V  qR|jt jjkr,t |jr)|jd'qR6 |s5t!d(W d   I d H  n1 I d H sFw   Y  W d   I d H  d S 1 I d H s]w   Y  d S )*Nr"   c               	      sP    j jdk} | rdnd}| sdnd}dt  d| d| dI dH  dS )	z)Sends the command request to the service.rx   truefalsezX-Timestamp:z
Content-Type:application/json; charset=utf-8
Path:speech.config

{"context":{"synthesis":{"audio":{"metadataoptions":{"sentenceBoundaryEnabled":"z","wordBoundaryEnabled":"z9"},"outputFormat":"audio-24khz-48kbitrate-mono-mp3"}}}}
N)r   rr   send_strri   )Zword_boundarywdsqr   Z	websocketr-   r.   send_command_request  s   
z2Communicate.__stream.<locals>.send_command_requestc                	      s0    tt t t j jd I dH  dS )z&Sends the SSML request to the service.r|   N)r   rm   rG   ri   re   r   r   r-   r   r-   r.   send_ssml_request  s   z/Communicate.__stream.<locals>.send_ssml_requestFT)rs   	trust_envtimeoutz&ConnectionId=z&Sec-MS-GEC=z&Sec-MS-GEC-Version=   )compressrt   r)   sslr1   s   

s   Paths   audio.metadatar   r   r~   s   turn.end)s   responses
   turn.startzUnknown path receivedr$   zBWe received a binary message, but it is missing the header length.bigz9The header length is greater than the length of the data.s   audioz3Received binary message, but the path is not audio.s   Content-Type)s
   audio/mpegNz=Received binary message, but with an unexpected Content-Type.r   z<Received binary message with no Content-Type, but with data.z:Received binary message, but it is missing the audio data.r   audio)r   r    zUnknown errorzFNo audio was received. Please verify that your parameters are correct.r"   N)"r   ZClientSessionrs   r   Z
ws_connectr   rG   r   Zgenerate_sec_ms_gecr   rt   Zheaders_with_muidr   _SSL_CTXr   Z	WSMsgTypeZTEXTr    rZ   r/   rW   get_Communicate__parse_metadatar   _Communicate__compensate_offsetr   ZBINARYrP   r   r   
from_bytesERRORr   r   )r   r   r   Zaudio_was_receivedsessionZreceivedZencoded_data
parametersr    pathZparsed_metadatar!   content_typer-   r   r.   Z__stream  s   



S\zCommunicate.__streamc                 C  s   | j d r
tdd| j d< | jD ]M| j d< d| j d< z|  2 z	3 dH W }|V  q!6 W q tjy_ } z%|jdkr< t| d| j d< |  2 z	3 dH W }|V  qJ6 W Y d}~qd}~ww dS )	au  
        Streams audio and metadata from the service.

        Raises:
            NoAudioReceived: If no audio is received from the service.
            UnexpectedResponse: If the response from the service is unexpected.
            UnknownResponse: If the response from the service is unknown.
            WebSocketError: If there is an error with the websocket.
        r   zstream can only be called once.Tr|   r   r   Ni  )	r   RuntimeErrorr   _Communicate__streamr   ZClientResponseErrorstatusr   Zhandle_client_response_error)r   messageer-   r-   r.   stream6  s*   





zCommunicate.streamaudio_fnamemetadata_fnamec              	      s   |durt |dddnt }|V t |d8}|  2 z*3 dH W }|d dkr0||d  qt|trF|d d	v rFt|| |d
 q6 W d   n1 sRw   Y  W d   dS W d   dS 1 sjw   Y  dS )zE
        Save the audio and metadata to the specified files.
        Nwr1   )encodingwbr   r   r    rw   
)openr   r   writer%   r   r   dump)r   r   r   metadatar   r   r-   r-   r.   saveW  s   

PzCommunicate.savec                 #   sv    dt ddf fdd}t  }tj }||| 	 | }|du r%n|V  qW d   dS 1 s4w   Y  dS )z-Synchronous interface for async stream methodqueuer"   Nc                    s:   d fdd}t  }t | ||  |  d S )Nr"   c                     s2     2 z3 d H W }  |  q6  d  d S N)r   put)item)r   r   r-   r.   	get_itemss  s
   zECommunicate.stream_sync.<locals>.fetch_async_items.<locals>.get_itemsr   )asyncioZnew_event_loopZset_event_loopZrun_until_completeclose)r   r   loopr   )r   r.   fetch_async_itemsr  s
   
z2Communicate.stream_sync.<locals>.fetch_async_items)r   
concurrentfuturesThreadPoolExecutorsubmitr   )r   r   r   executorr   r-   r   r.   stream_synco  s   "zCommunicate.stream_syncc                 C   sN   t j }|tj| ||}|  W d   dS 1 s w   Y  dS )z,Synchronous interface for async save method.N)r   r   r   r   r   runr   result)r   r   r   r   futurer-   r-   r.   	save_sync  s   
"zCommunicate.save_syncr   r   )__name__
__module____qualname____doc__r   r:   r   r
   r   r   r   r   r&   r   r   r   r   r   r   r   r   r   r   r   r-   r-   r-   r.   rn   A  sr    	

;
 

$


rn   )Ar   r   concurrent.futuresr   r   r   rf   rD   
contextlibr   ior   r   r   typingr   r   r   r   r	   r
   r   r   Zxml.sax.saxutilsr   r   r   certifiZtyping_extensionsr   	constantsr   r   r   r   r   r   Zdata_classesr   Zdrmr   
exceptionsr   r   r   r   r   r   create_default_contextwherer   r&   r   r/   r:   rC   rG   rN   rR   rX   r^   re   ri   rm   rn   r-   r-   r-   r.   <module>   sX    (
 



E