o
    4ѹgL                  	   @   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 ddlZddlZdd	l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* de+de,deee+e+f e+f fddZ-dee.e+f de.fddZ/de.fddZ0dee.e+f de,dee+ddf fddZ1de!dee.e+f de.fddZ2de.fd d!Z3d"e.d#e.d$e.de.fd%d&Z4d'e!de,fd(d)Z5G d*d+ d+Z6dS ),zCommunicate 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   )DEFAULT_VOICESEC_MS_GEC_VERSIONWSS_HEADERSWSS_URL)	TTSConfig)DRM)NoAudioReceivedUnexpectedResponseUnknownResponseWebSocketError)CommunicateStateTTSChunk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(   X/Users/admin/.pyenv/versions/3.10.0/lib/python3.10/site-packages/edge_tts/communicate.pyget_headers_and_data(   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                    N)	r    r!   decodestrr"   list	enumerateordjoin)r+   charsidxcharcoder(   r(   r)   remove_incompatible_characters@   s   


D
r>   c                   C   s   t t ddS )zZ
    Returns a UUID without dashes.

    Returns:
        str: A UUID without dashes.
    -r3   N)r5   uuiduuid4replacer(   r(   r(   r)   
connect_id\   s   rC   textbyte_lengthc                 c   s   t | tr| d} t | tstd|dkrtdt| |kr| dd|}|dkr/|n|}d| d| v rc| dd|}| 	d	||dkrJqc|d
 }|dk rVtd|dkr[qcd| d| v s9| d| 
 }|rp|V  |dkrvd
}| |d } t| |ks"| 
 }|r|V  dS dS )a  
    Splits a string into a list of strings of a given byte length
    while attempting to keep words together. This function assumes
    text will be inside of an XML tag.

    Args:
        text (str or bytes): The string to be split. If bytes, it must be UTF-8 encoded.
        byte_length (int): The maximum byte length of each string in the list.

    Yield:
        bytes: The next string in the list.
    r,   ztext must be str or bytesr   z"byte_length must be greater than 0       &N   ;r   z0Maximum byte length is too small or invalid text)r    r5   encoder!   r"   
ValueErrorlenrfindrindexfindstrip)rD   rE   Zsplit_atZampersand_indexZnew_textr(   r(   r)   split_text_by_byte_lengthf   s>   



rQ   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.
    r,   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>N)r    r!   r4   voicepitchratevolume)rR   rS   r(   r(   r)   mkssml   s   

rX   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)N)timestrftimegmtimer(   r(   r(   r)   date_to_string   s   r\   
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

Nr(   )r]   r^   r_   r(   r(   r)   ssml_headers_plus_data   s   	r`   
tts_configc                 C   s*   d}t tt t t| dd }|| S )zCalculates the maximum message size for the given voice, rate, and volume.

    Returns:
        int: The maximum message size.
    i   r3   2   N)rL   r`   rC   r\   rX   )ra   Zwebsocket_max_sizeZoverhead_per_messager(   r(   r)   calc_max_mesg_size   s   
rc   c                   @   s  e Zd ZdZefddddddddded	ed
edede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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+0HzN
   <   )rV   rW   rU   	connectorproxyconnect_timeoutreceive_timeoutrD   rT   rV   rW   rU   rg   rh   ri   rj   c          
      C   s   t ||||| _t|tstdttt|t| j| _	|d ur+t|ts+td|| _
t|ts7tdt|	ts@tdtjd d ||	d| _|d urXt|tjsXtd|| _dddd	d
| _d S )Nztext must be strzproxy must be strzconnect_timeout must be intzreceive_timeout must be int)totalconnectsock_connectZ	sock_readz'connector must be aiohttp.BaseConnector    r   F)partial_textoffset_compensationlast_duration_offsetstream_was_called)r   ra   r    r5   r"   rQ   r   r>   rc   textsrh   intaiohttpZClientTimeoutsession_timeoutBaseConnectorrg   state)
selfrD   rT   rV   rW   rU   rg   rh   ri   rj   r(   r(   r)   __init__   s8   



zCommunicate.__init__r   r   c                 C   s   t |d D ]5}|d }|dkr1|d d | jd  }|d d }||||d d d	 d
  S |dv r6qtd| td)NZMetadataTypeWordBoundaryZDataZOffsetrp   ZDurationrD   Text)typeoffsetdurationrD   )Z
SessionEndzUnknown metadata type: zNo WordBoundary metadata found)jsonloadsrx   r   r   )ry   r   Zmeta_objZ	meta_typecurrent_offsetZcurrent_durationr(   r(   r)   Z__parse_metadata(  s   
zCommunicate.__parse_metadatac              
     s  d+fdd}d+ fdd}d}t jt d}tj jd jd	4 I d H 8}|jt	 d
t
  dt dt  d j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< qW|
dkr jd  jd<  jd  d7  <  q8|
dvrtdqW|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 rt|	d$kr
qWtd%t|	d$krtd&d}d'|	d(V  qW|jtjj kr6t!|jr3|jd)qW6 |s?t"d*W d   I d H  qV1 I d H sQw   Y  W d   I d H  d S 1 I d H shw   Y  d S ),Nr   c                      s      dt  dI dH  dS )z)Sends the command request to the service.zX-Timestamp:z
Content-Type:application/json; charset=utf-8
Path:speech.config

{"context":{"synthesis":{"audio":{"metadataoptions":{"sentenceBoundaryEnabled":"false","wordBoundaryEnabled":"true"},"outputFormat":"audio-24khz-48kbitrate-mono-mp3"}}}}
N)send_strr\   r(   )	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.ro   N)r   r`   rC   r\   rX   ra   rx   r(   ry   r   r(   r)   send_ssml_requestH  s   z/Communicate.__stream.<locals>.send_ssml_requestF)cafileT)rg   	trust_envtimeoutz&Sec-MS-GEC=z&Sec-MS-GEC-Version=z&ConnectionId=   )compressrh   r$   sslr,   s   

s   Paths   audio.metadatar   r   rq   s   turn.endrp   i )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.audio)r~   r   zUnknown errorzFNo audio was received. Please verify that your parameters are correct.r   N)#r   create_default_contextcertifiwhereru   ZClientSessionrg   rv   Z
ws_connectr   r   Zgenerate_sec_ms_gecr   rC   rh   r   r~   Z	WSMsgTypeZTEXTr   rJ   r*   rO   get_Communicate__parse_metadatarx   r   BINARYrL   r   rt   
from_bytesERRORr   r   )ry   r   r   Zaudio_was_receivedZssl_ctxsessionZreceivedZencoded_data
parametersr   pathZparsed_metadatar   content_typer(   r   r)   Z__stream;  s   

J
	7
\^zCommunicate.__streamc                 C  s   | j d r
tdd| j d< | jD ]C| j d< z|  2 z	3 dH W }|V  q6 W q tjyU } z |jdkr7 t| |  2 z	3 dH W }|V  q@6 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.
        rr   zstream can only be called once.Tro   Ni  )	rx   RuntimeErrorrs   _Communicate__streamru   ZClientResponseErrorstatusr   Zhandle_client_response_error)ry   messageer(   r(   r)   stream  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	k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wr,   )encodingwbr~   r   r   r|   
)openr   r   writer    r   r   dump)ry   r   r   metadatar   r   r(   r(   r)   save  s$   


PzCommunicate.savec                 #   sv    dt ddf fdd}t  }tj }||| 	 | }|du r%q)|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   ry   r(   r)   	get_items	  s
   zECommunicate.stream_sync.<locals>.fetch_async_items.<locals>.get_itemsr   )asyncionew_event_loopset_event_looprun_until_completeclose)r   r   loopry   )r   r)   fetch_async_items  s
   
z2Communicate.stream_sync.<locals>.fetch_async_itemsT)r   
concurrentfuturesThreadPoolExecutorsubmitr   )ry   r   r   executorr   r(   r   r)   stream_sync  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)ry   r   r   r   futurer(   r(   r)   	save_sync  s   
"zCommunicate.save_syncr   )__name__
__module____qualname____doc__r   r5   r
   ru   rw   rt   rz   r!   r   r   r   r   r   r   r   r   r   r   r(   r(   r(   r)   rd      sj    	

8 

"


rd   )7r   r   concurrent.futuresr   r   r   rY   r@   
contextlibr   ior   r   r   typingr   r   r   r   r	   r
   r   r   xml.sax.saxutilsr   ru   r   	constantsr   r   r   r   Zdata_classesr   Zdrmr   
exceptionsr   r   r   r   r   r   r!   rt   r*   r5   r>   rC   rQ   rX   r\   r`   rc   rd   r(   r(   r(   r)   <module>   sP    (




7