o
    ҵi                     @  s  d Z ddlm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 ddlmZmZmZ zddlZddlmZ W n eyY   edw dd	lmZmZmZmZ d
ZdZ dZ!dZ"dZ#dZ$	dXdYddZ%dZddZ&d[d!d"Z'd\d#d$Z(d]d&d'Z)d^d)d*Z*d_d.d/Z+d`d2d3Z,dad7d8Z-e$dd9dbd=d>Z.e"e$dd?d@dcdCdDZ/dddFdGZ0dedHdIZ1dfdKdLZ2dMZ3dNZ4dOZ5G dPdQ dQZ6G dRdS dSZ7G dTdU dUZ8G dVdW dWZ9dS )gz:Orpheus TTS Client - Stream speech from the Orpheus model.    )annotationsN)Path)AsyncIteratorIteratorOptional)quoteurlparse
urlunparse)ConnectionClosedzEwebsockets package is required. Install with: pip install orpheus-tts)AuthenticationErrorConnectionErrorOrpheusErrorStreamingError   ORPHEUS_TTS_MULTIPLEX_WS_URLdemozhttp://35.232.65.178i@        $@urlstrheadersOptional[dict[str, str]]c              
     s   t ddddd}z8|r5ztj| fd|i|I dH W W S  ty4   tj| fd|i|I dH  Y W S w tj| fi |I dH W S  tyu } z(t|}|d	v rZtd
| d||dkrctd||durptd| d| d}~ww )zBCreate websocket connection across websockets version differences.i      
      )max_sizeping_intervalping_timeoutclose_timeoutadditional_headersNextra_headersi  i  zOAuthentication failed while connecting to the Orpheus streaming endpoint (HTTP z). Please verify your API key.  zdThe Orpheus streaming endpoint was not found (HTTP 404). Please verify the configured websocket URL.zGThe Orpheus streaming endpoint rejected the websocket connection (HTTP z:). Please verify endpoint availability and service health.)dict
websocketsconnect	TypeError	Exception_extract_websocket_status_coder   r   )r   r   kwargsexcstatus_code r+   F/home/ubuntu/.local/lib/python3.10/site-packages/orpheus_tts/client.py_connect_websocket(   sT    "r-   voicevoice_endpoint_mapdict[str, str]returnc                 C  sL   |    }z|| W S  ty%   dt| }td|  d| w )zyResolve a voice name to its WebSocket endpoint URL.

    Raises:
        ValueError: If the voice is not recognized.
    z, zUnknown voice 'z'. Valid voices: )striplowerKeyErrorjoinsortedkeys
ValueError)r.   r/   normalized_voicevalidr+   r+   r,   _get_endpointN   s   
r;   datar"   local_config_pathr   Nonec                 C  s.   |j jddd |jtj| ddddd d S )NT)parentsexist_ok   )indent	sort_keysutf-8encoding)parentmkdir
write_textjsondumps)r<   r=   r+   r+   r,   _write_local_config^   s   rL   c                 C  s   |   std|  t| jddpd}t|tr(t|dtr(|d }n|}t|ts7td|  di }| D ]\}}t	|
  }t	|
 }|rS|sTq=|||< q=|sbtd|  |S )	NzLocal config file not found: rD   rE   {}r/   zInvalid local config at z$: expected voice mapping JSON objectz$No voice endpoint mappings found in )existsr8   rJ   loads	read_text
isinstancer"   getitemsr   r2   r3   )r=   r<   r/   
normalized
voice_nameendpoint_url	voice_keyendpoint_valuer+   r+   r,   *_load_voice_endpoint_map_from_local_configc   s(   



rY   config_hostc                 C  s   |   }|s
tdd|vrd| }t|}|js"td|  d|j}|s/td|  dd|v r>|ds>d| d	}|jd urF|jn| dt }|j	d
}t
|jpXd||dddfS )NzConfig host must not be emptyz://zhttp://zInvalid config host 'z/'. Expected something like http://35.232.65.178':[]/http )r2   r8   r   netlochostname
startswithport_DEFAULT_CONFIG_SERVICE_PORTpathrstripr	   scheme)rZ   valueparsedrc   rb   	base_pathr+   r+   r,   _normalize_config_host~   s$   

rm   providerc                 C  s8   t | }ttdd}t| dd}| d| d| S )Nra   )safer_   )rm   r   _DEFAULT_CLIENT_NAMEr2   )rZ   rn   base_urlclient_segmentprovider_segmentr+   r+   r,   _build_config_service_url   s   rt   r*   intservice_urlc                 C  s@   | dv rd|  d| dS | dkrd| dS d| d	|  d
S )Nr    z[Unable to load voice configuration from the config service because access was denied (HTTP z) for provider zH. Please verify your SDK credentials and service authorization settings.r!   z/Voice configuration was not found for provider zZ (HTTP 404). Please verify the provider value and confirm this configuration is published.z0Unable to load voice configuration for provider z (HTTP z=). Please try again or contact support if the issue persists.r+   r*   rv   rn   r+   r+   r,   !_format_config_service_http_error   s   
rx   ws_urlwebsocket_countc                 C  s   d| dS )NzIUnable to establish a connection to the Orpheus streaming endpoint using z[ websocket(s). Please verify your API key, endpoint availability, and network connectivity.r+   ry   rz   r+   r+   r,   "_format_multiplex_connection_error   s   r|   r)   r&   Optional[int]c              	   C  s|   t | dd}t |dd}t|tr|S t | dd}t|tr |S t | dd}|dur<zt|W S  ttfy;   Y dS w dS )zKBest-effort extraction of an HTTP status from websocket handshake failures.responseNr*   status)getattrrQ   ru   r%   r8   )r)   r~   response_statusr*   r   r+   r+   r,   r'      s   


r'   )timeout_secondsr=   r   floatOptional[str]c              
   C  s  t || }tjj|ddid}z!tjj||d}| d}W d    n1 s*w   Y  W n. tjjyI } zt	t
|j|| d|d }~w tjjy^ } zt	d|  |d }~ww z	t|ped}	W n tjy~ } z	t	d	| d
|d }~ww t|	tst	d	| di }
|	 D ]\}}t|  }t| }|r|sq||
|< q|
st	d	| d|rt|  }td|
i| t|S |
S )NAcceptzapplication/json)r   timeoutrD   rw   z4Failed to download voice configuration for provider rM   zConfig service at z did not return valid JSONz must return a JSON objectz$ returned no voice endpoint mappingsr/   )rt   urllibrequestRequesturlopenreaddecodeerror	HTTPErrorr8   rx   codeURLErrorrJ   rO   JSONDecodeErrorrQ   r"   rS   r   r2   r3   r   
expanduserresolverL   rY   )rn   rZ   r   r=   rv   r   r~   payloadr)   r<   rT   rU   rV   rW   rX   resolved_local_pathr+   r+   r,   ,_load_voice_endpoint_map_from_config_service   sl   




r   F)rZ   r   r=   refreshr   boolc                 C  sJ   |rt |  }| r|st|S t| ||t|dS t| ||dS )N)rn   rZ   r   r=   )rn   rZ   r   )r   r   r   rN   rY   r   r   )rn   rZ   r   r=   r   r   r+   r+   r,   $_load_or_download_voice_endpoint_map  s   r   endpointc                 C  s0   t | }|jdkrdnd}t||jddddfS )z4Convert a WSS endpoint URL to its HTTPS /health URL.wsshttpsr`   z/healthra   )r   ri   r	   rb   )r   rk   ri   r+   r+   r,   _get_health_url#  s   r   c                 C  s,   zt jj| dd W dS  ty   Y dS w )zHSend a fire-and-forget GET to the health endpoint (runs in thread pool).r   r   N)r   r   r   r&   )r   r+   r+   r,   _fire_health_ping*  s
   r   r   c                 C  s   t | }|S )z5Serialize and print an outbound JSON request payload.)rJ   rK   )r   payload_jsonr+   r+   r,   _log_request_json2  s   
r   i  rA      c                   @  s   e Zd ZdZdddZdS )_MultiplexRequestStatez'Tracks one in-flight multiplex request.
request_idr   c                 C  s   || _ t | _d | _d S N)r   asyncioQueuequeuer   )selfr   r+   r+   r,   __init__A  s   

z_MultiplexRequestState.__init__N)r   r   )__name__
__module____qualname____doc__r   r+   r+   r+   r,   r   >  s    r   c                   @  sb   e Zd ZdZd%d&ddZd'ddZd'ddZd'ddZd(ddZd)ddZ	d*ddZ
d+d#d$ZdS ),_MultiplexedWebSocketConnectionz=Single websocket that can carry many concurrent TTS requests.Nry   r   r   r   c                 C  sH   || _ || _d | _d| _d| _t | _t | _d | _	i | _
i | _d S )NFr   )ry   r   ws
_connected_request_counterr   Lock
_send_lock_state_lock_receiver_task_states_server_to_client)r   ry   r   r+   r+   r,   r   J  s   


z(_MultiplexedWebSocketConnection.__init__r1   r>   c                   s2   t | j| jI d H | _d| _t|  | _d S )NT)	r-   ry   r   r   r   r   create_task_receive_loopr   r   r+   r+   r,   r$   V  s   z'_MultiplexedWebSocketConnection.connectc                   s   d| _ | jd ur$| j  z| jI d H  W n
 tjy    Y nw d | _| jd urBz
| j I d H  W n	 ty<   Y nw d | _d S d S NF)r   r   cancelr   CancelledErrorr   closer&   r   r+   r+   r,   r   [  s$   



z%_MultiplexedWebSocketConnection.closec              
     s   z$| j 2 z3 d H W }t|tr| |I d H  q| |I d H  q6 W d S  tjy.     tyM } z| t	d| I d H  W Y d }~d S d }~ww )NzMultiplex receive failed: )
r   rQ   bytes_handle_binary_message_handle_json_messager   r   r&   _fail_all_requestsr   )r   messageer+   r+   r,   r   k  s   
(z-_MultiplexedWebSocketConnection._receive_loopr   r   c              	     s   t |dk r	d S t|d d d}|dd  }|sd S | j4 I d H = | j|}|d u rKt| j }| j	 D ]}||vrJ|}|| j|<  nq;|d urU| j|nd }W d   I d H  n1 I d H sgw   Y  |d ur{|j
|I d H  d S d S )N   big)lenru   
from_bytesr   r   rR   setvaluesr   r7   r   put)r   r   server_request_idaudior   mapped_request_idscandidate_request_idstater+   r+   r,   r   w  s,   
(z6_MultiplexedWebSocketConnection._handle_binary_messagec           	   	     s8  zt |}W n t jy   Y d S w |d}|d}|d}|d u r4t|dtr4|d}|dkri|d uri|d uri| j4 I d H  t|| jt|< W d   I d H  d S 1 I d H sbw   Y  d S d }|d urtt|}n7t|dtr|d}n)|d ur| j4 I d H  | jt|}W d   I d H  n1 I d H sw   Y  |d u rd S | j4 I d H  | j	|}W d   I d H  n1 I d H sw   Y  |d u rd S |dr	t|d}d|
 v sd|
 v rt||_nt||_|jd I d H  d S |d	r|jd I d H  d S d S )
Ntyper   client_request_idr   acceptedr   authkeydone)rJ   rO   r   rR   rQ   ru   r   r   r   r   r3   r   r   r   r   r   )	r   r   r<   msg_typer   r   r   r   error_messager+   r+   r,   r     sX   




((
z4_MultiplexedWebSocketConnection._handle_json_messager   r&   c              	     sz   | j 4 I d H  t| j }W d   I d H  n1 I d H s w   Y  |D ]}|jd u r1||_|jd I d H  q'd S r   )r   listr   r   r   r   r   )r   r   statesr   r+   r+   r,   r     s   (
z2_MultiplexedWebSocketConnection._fail_all_requeststextr.   
max_tokensru   temperaturer   repetition_penaltyAsyncIterator[bytes]c                  s  | j r	| jd u rtd| j4 I d H  dt j  t }|| j < W d   I d H  n1 I d H s6w   Y   |||||d}z| j	4 I d H  | j
t|I d H  W d   I d H  n1 I d H shw   Y  	 |j I d H }|d u r{n|V  qn|jd ur|jW | j4 I d H + | j d   fdd| j D }	|	D ]	}
| j|
d  qW d   I d H  d S 1 I d H sw   Y  d S | j4 I d H * | j d   fdd| j D }	|	D ]	}
| j|
d  qW d   I d H  w 1 I d H sw   Y  w )Nz$Multiplex websocket is not connectedreq_)r   promptr.   r   r   r   Tc                   s   g | ]
\}}| kr|qS r+   r+   ).0	server_id	client_idr   r+   r,   
<listcomp>  s
    zB_MultiplexedWebSocketConnection.stream_request.<locals>.<listcomp>)r   r   r   r   uuiduuid4hexr   r   r   sendr   r   rR   r   popr   rS   )r   r   r.   r   r   r   r   r   itemstale_server_idsr   r+   r   r,   stream_request  sZ   (	(

>
*z._MultiplexedWebSocketConnection.stream_requestr   )ry   r   r   r   r1   r>   )r   r   r1   r>   )r   r   r1   r>   )r   r&   r1   r>   r   r   r.   r   r   ru   r   r   r   r   r1   r   )r   r   r   r   r   r$   r   r   r   r   r   r   r+   r+   r+   r,   r   G  s    





-r   c                   @  s\   e Zd ZdZ		d%d&ddZd'ddZd'ddZd(ddZd)ddZd*ddZ	d+d#d$Z
dS ),_MultiplexWebSocketPoolz?Pool of multiplex-capable websockets with load-aware selection.Nr   ry   r   rz   ru   r   r   max_inflight_per_socketc                 C  s>   || _ td|| _|| _td|| _g | _i | _t | _	d S )Nr   r   )
ry   maxrz   r   r   _clients	_inflightr   	Condition_cond)r   ry   rz   r   r   r+   r+   r,   r     s   z _MultiplexWebSocketPool.__init__r1   r>   c                   s   fdd t j fddtjD  I d H }d }|D ]\}}|d ur1j| dj|< q|d ur;|d u r;|}qjsO|d urE|ttj	jdd S )Nc               
     sV   t  j j} z|  I d H  | d fW S  ty* } z
d |fW  Y d }~S d }~ww r   )r   ry   r   r$   r&   )clientr)   r   r+   r,   _create_one  s   
z7_MultiplexWebSocketPool.initialize.<locals>._create_onec                   s   g | ]}  qS r+   r+   )r   _)r   r+   r,   r     s    z6_MultiplexWebSocketPool.initialize.<locals>.<listcomp>r   r{   )
r   gatherrangerz   r   appendr   r   r|   ry   )r   createdfirst_errorr   r   r+   )r   r   r,   
initialize  s*   $z"_MultiplexWebSocketPool.initializec              	     s   | j 4 I d H  t| j}| j  | j  | j   W d   I d H  n1 I d H s-w   Y  |D ]	}| I d H  q4d S r   )r   r   r   clearr   
notify_allr   )r   clientsr   r+   r+   r,   r   !  s   


(z_MultiplexWebSocketPool.closer   c                 C  s
   t | jS )z3Return whether at least one websocket is connected.)r   r   r   r+   r+   r,   has_connections*  s   
z'_MultiplexWebSocketPool.has_connectionsr   c              	     s    j 4 I d H H 	  jstdt j fddd}|D ](} j|d} jdkr2| jkr2q|d  j|< |  W  d   I d H  S  j  I d H  q
1 I d H sVw   Y  d S )NTz,No multiplex websocket connections availablec                   s    j | dS Nr   )r   rR   )cr   r+   r,   <lambda>6  s    z9_MultiplexWebSocketPool._acquire_client.<locals>.<lambda>)r   r   r   )r   r   r   r6   r   rR   r   wait)r   
candidatesr   inflightr+   r   r,   _acquire_client.  s&   
z'_MultiplexWebSocketPool._acquire_clientr   c              	     sp   | j 4 I d H # || jv rtd| j| d | j|< | j   W d   I d H  d S 1 I d H s1w   Y  d S )Nr   r   )r   r   r   notify)r   r   r+   r+   r,   _release_client@  s   
.z'_MultiplexWebSocketPool._release_clientr   r.   r   r   r   r   r   c              	   C sd   |   I d H }z |j|||||d2 z	3 d H W }|V  q6 W | |I d H  d S | |I d H  w )Nr   r.   r   r   r   )r  r   r  )r   r   r.   r   r   r   r   chunkr+   r+   r,   r   F  s   &	z&_MultiplexWebSocketPool.stream_requestr  )ry   r   rz   ru   r   r   r   ru   r   )r1   r   )r1   r   )r   r   r1   r>   r   )r   r   r   r   r   r  r   r  r  r  r   r+   r+   r+   r,   r     s    


	

r   c                   @  s  e Zd ZdZ				dJddddKddZdLddZefddddddMdd ZdNd!d"ZdOd$d%Z	dPd'd(Z
dQd)d*Zd+d, Zd-d. Zd/d0 Zd1d2 ZdRd6d7Z	8dSdddd9dTd;d<ZdTd=d>ZdUd?d@Z	8dSdddd9dRdAdBZdVdCdDZ	8dSdWdFdGZ	8dSdWdHdIZdS )XOrpheusClienta-  
    Client for streaming speech from the Orpheus TTS model.

    Example:
        >>> from orpheus_tts import OrpheusClient
        >>>
        >>> # Option 1: Auto-connect (includes connection time in first request)
        >>> client = OrpheusClient(provider="PROVIDER_NAME")
        >>> for chunk in client.stream("Hello!", voice="josh"):
        ...     audio_player.write(chunk)
        >>>
        >>> # Option 2: Pre-connect for lowest latency (recommended)
        >>> client = OrpheusClient(provider="PROVIDER_NAME")
        >>> client.connect()  # Pool of 16 connections per endpoint
        >>> for chunk in client.stream("Hello!", voice="josh"):
        ...     audio_player.write(chunk)  # TTFA excludes handshake!
        >>> client.close()
        >>>
        >>> # Option 3: Async usage (same pool, same connect)
        >>> client = OrpheusClient(provider="PROVIDER_NAME")
        >>> client.connect()
        >>> async for chunk in client.stream_async("Hello!", voice="josh"):
        ...     audio_player.write(chunk)
        >>> client.close()
    N        ?皙?)rn   r/   api_keyr   r   ru   r   r   r   rn   r/   r   c                C  s   || _ || _|| _|| _|durdd | D | _n|r$t|d| _ntdi | _g | _	d| _
d| _d| _d| _d| _d| _d| _dS )a  
        Initialize the Orpheus TTS client.

        Args:
            api_key: API key for authentication (optional for now).
            max_tokens: Maximum tokens to generate (default: 3000).
            temperature: Sampling temperature (default: 1.0).
            repetition_penalty: Repetition penalty (default: 1.1).
            provider: Provider used to load the voice configuration
                (for example "PROVIDER_NAME").
            voice_endpoint_map: Explicit voice->url mapping override.
        Nc                 S  s&   i | ]\}}t |  t |qS r+   )r   r2   r3   )r   kvr+   r+   r,   
<dictcomp>  s    z*OrpheusClient.__init__.<locals>.<dictcomp>)rn   z=A provider is required unless voice_endpoint_map is provided.r   F)r  r   r   r   rS   _voice_endpoint_mapr   r8   _pools_all_connections_multiplex_pool_multiplex_ws_url_websocket_count_max_inflight_per_socket_loop_threadr   )r   r  r   r   r   rn   r/   r+   r+   r,   r   w  s0   


zOrpheusClient.__init__r1   c                 C  s   | j sdS dd| j  iS )z;Build websocket auth headers when an API key is configured.NAuthorizationzApi-Key )r  r   r+   r+   r,   _get_auth_headers  s   zOrpheusClient._get_auth_headersr   )ry   r.   rz   r   	pool_sizery   r.   rz   r}   r   r>   c             
     sV   j rdS |dur|durtd|}|du r!|dur!t| j}|du r*tt}| _td|dur5|n| _	td| _
t  _ fdd}tj|dd _ j  t j|d	 j}z#|jd
d t j}	 jduow j }
|	s|
stdd _ W dS  ttfy        ty } z   td| |d}~ww )aY  
        Pre-establish WebSocket connections for lowest latency.

        Multiplex mode (recommended):
            - Pass ws_url, or pass voice, or set ORPHEUS_TTS_MULTIPLEX_WS_URL.
            - Creates websocket_count multiplex sockets to a single endpoint.
            - A single socket can serve many concurrent requests.

        Legacy mode (compat):
            - If ws_url/voice/env are not provided, uses per-voice endpoint pools.
            - Creates pool_size connections per unique endpoint.

        Args:
            pool_size: Legacy per-endpoint pool size.
            ws_url: Multiplex gateway websocket URL.
            voice: Voice name to resolve to a multiplex endpoint URL.
            websocket_count: Number of multiplex sockets (default: pool_size).
            max_inflight_per_socket: Optional cap per socket (0 = unlimited).

        Example:
            >>> client = OrpheusClient(provider="PROVIDER_NAME")
            >>> client.connect()  # 16 connections per endpoint
            >>> for chunk in client.stream("Hello!", voice="josh"):
            ...     process(chunk)
        Nz3Pass either ws_url or voice to connect(), not both.r   r   c                     s   t  j  j  d S r   )r   set_event_loopr   run_foreverr+   r   r+   r,   run_loop  s   z'OrpheusClient.connect.<locals>.run_loopTtargetdaemon)r$  g      >@r   z!Failed to connect to any endpointzFailed to connect: )r   r8   r;   r  osgetenv_DEFAULT_MULTIPLEX_WS_ENVr  r   r  r  r   new_event_loopr   	threadingThreadr!  startrun_coroutine_threadsafe_create_poolsresultr   r  r  r  r   r   r   r&   )r   r$  ry   r.   rz   r   resolved_ws_urlr'  futurehas_legacy_connectionshas_multiplex_connectionsr   r+   r   r,   r$     sF   "



zOrpheusClient.connectc                   s   j r  }t j  j| jd _ j I dH  dS t j	 }d	 fdd}|D ]	}t
  j|< q.g }|D ]}t|D ]
}|||| qBq<t
j| I dH }d}	|D ]%\}}
}|
duru j|
  j| |
I dH  qZ|dur|	du r|}	qZ js|	dur|	dS dS )
z0Internal: create multiplex pool or legacy pools.)ry   rz   r   r   Nr   r   idxru   c              
     sR   zt |   I d H }| |d fW S  ty( } z| d |fW  Y d }~S d }~ww r   )r-   r#  r&   )r   r9  r   r)   r   r+   r,   create_connection  s   z6OrpheusClient._create_pools.<locals>.create_connection)r   r   r9  ru   )r  r#  r   r  r  r  r  r   r  r   r   r   r  r   r   r   r  r   )r   r$  r   	endpointsr:  r   tasksiresultsr   r   r   r+   r   r,   r3     s@   zOrpheusClient._create_poolsr   c                   sH   t || j}| j|}|du rtd| d| I dH }||fS )z7Acquire a connection from the pool for the given voice.NzNo connection pool for voice 'z,'. Call connect() first or check voice name.)r;   r  r  rR   r   )r   r.   r   poolr   r+   r+   r,   _acquire-  s   
zOrpheusClient._acquirer   c                   s   zt |dr|j n|j}W n ty   d}Y nw | j|}|du r'dS |r3||I dH  dS zt||  I dH }||I dH  W dS  tyR   Y dS w )z<Release a connection back to the pool, replacing it if dead.closedFN)	hasattrrA  openr&   r  rR   r   r-   r#  )r   r   r   is_openr?  new_wsr+   r+   r,   _release9  s"   zOrpheusClient._releasec                   s    j dur@ js jdur@ fdd}zt|  j }|jdd W n	 ty,   Y nw  j   j  d _	d _
d _ j dur_ j  j j  jdurY jjdd d _ d _d _dS )	z,Close all connections and cleanup resources.Nc               	     s    j d ur j  I d H  d  _  j D ]&} |  s<z|  }| jvr, j| W n
 tj	y7   Y nw |  rq jD ]}ztj
| ddI d H  W q@ tyY   Y q@w d S )Nr  r   )r  r   r  r   empty
get_nowaitr  r   r   
QueueEmptywait_forr&   )r?  r   r   r+   r,   
_close_allQ  s.   


	z'OrpheusClient.close.<locals>._close_allr   r   r   g       @F)r   r  r  r   r2  r4  r&   r  r  r  r  r  call_soon_threadsafestopr!  r5   r   )r   rK  r6  r+   r   r,   r   N  s(   




zOrpheusClient.closec                 C  s   | S r   r+   r   r+   r+   r,   	__enter__|  s   zOrpheusClient.__enter__c                 C  s   |    dS r   r   r   exc_typeexc_valexc_tbr+   r+   r,   __exit__  s   zOrpheusClient.__exit__c                   s   | S r   r+   r   r+   r+   r,   
__aenter__  s   zOrpheusClient.__aenter__c                   s   |    dS r   rO  rP  r+   r+   r,   	__aexit__  s   zOrpheusClient.__aexit__r   Optional[float]r   c              	   C s   |||dur	|n| j |dur|n| j|dur|n| jd}|t|I dH  ztt|| j}t	 
dt| W n	 tyD   Y nw |2 zE3 dH W }	t|	trV|	V  qGz,t|	}
d|
v rx|
d }d| v spd| v rtt|t||
drW  dS W qG tjy   Y qGw 6 dS )z;Internal: stream audio on an existing WebSocket connection.N)r   r.   r   r   r   r   r   r   r   )r   r   r   r   r   r   r;   r  r   get_running_looprun_in_executorr   r&   rQ   r   rJ   rO   r3   r   r   rR   r   )r   r   r   r.   r   r   r   request_body
health_urlr   r<   	error_msgr+   r+   r,   _stream_on_connection  sB   



z#OrpheusClient._stream_on_connectionjosh)r   r   r   Iterator[bytes]c                c  s    |   }t|| j}| jr7| jdur7| jdur(| |||||E dH  dS | ||||||E dH  dS | 	||||||E dH  dS )aV  
        Stream audio synchronously from the Orpheus TTS model.

        If connect() was called first, uses a pooled connection for lowest
        latency. Otherwise, creates a new connection per request.

        Args:
            text: The text to convert to speech.
            voice: Voice to use. See orpheus_tts.VOICES for options.
            max_tokens: Override default max_tokens.
            temperature: Override default temperature.
            repetition_penalty: Override default repetition_penalty.

        Yields:
            bytes: Raw PCM audio chunks (int16, 48kHz, mono).

        Raises:
            ValueError: If voice is not recognized.
            AuthenticationError: If API key is invalid.
            ConnectionError: If connection to service fails.
            StreamingError: If an error occurs during streaming.
        N)
r2   r3   r;   r  r   r   r  _stream_from_multiplex_pool_stream_from_pool_stream_with_new_connection)r   r   r.   r   r   r   r   r+   r+   r,   stream  s   

zOrpheusClient.streamc           
   
   #  s    t  g fdd  fdd}t| j}	  }|du r-n|V  q$z|jdd W n tyR }	 zsH|	 W Y d}	~	nd}	~	ww rYd	 dS )
z*Stream using the multiplex websocket pool.c                    sr   j d u r
tdj jd urnjd urnjd ur%njd2 z3 d H W }  |  q*6 d S )Nz!Multiplex pool is not initializedr  )r  r   r   r   r   r   r   )r  )chunk_queuer   r   r   r   r   r.   r+   r,   _run  s   
z7OrpheusClient._stream_from_multiplex_pool.<locals>._runc               
     p   z0z  I d H  W n t y! }  z|  W Y d } ~ nd } ~ ww W d  d S W d  d S d  w r   r&   r   r   r   re  rd  error_holderr+   r,   _run_wrapper     z?OrpheusClient._stream_from_multiplex_pool.<locals>._run_wrapperTN     r@r   r   	r   r   r   r2  r   rR   r4  r&   r   )
r   r   r.   r   r   r   rk  r6  r  r   r+   )	re  rd  rj  r   r   r   r   r   r.   r,   r`    s,   	
z)OrpheusClient._stream_from_multiplex_poolc              
   #  s    t  g 	fdd  fdd}t| j}	  }	|	du r.n|	V  q%z|jdd W n tyS }
 zsI|
 W Y d}
~
nd}
~
ww rZd	 dS )
z2Stream using a pooled connection (lowest latency).c               	     s   d} t | D ]j}d }z.I d H \}}|2 z3 d H W } | q 6 |I d H  W  d S  ty]   |d urM|I d H  || d k rUY qtd|  d tyq   |d urp|I d H   w d S )N   r   %Connection closed unexpectedly after  retries)r   r@  r]  r   rF  r
   r   r&   )max_retriesattemptr   r   r  rd  r   r   r   r   r   r   r.   r+   r,   re  )  s:   z-OrpheusClient._stream_from_pool.<locals>._runc               
     rf  r   rg  rh  ri  r+   r,   rk  D  rl  z5OrpheusClient._stream_from_pool.<locals>._run_wrapperTNrm  r   r   rn  )r   r   r   r.   r   r   r   rk  r6  r  r   r+   
re  rd  r   rj  r   r   r   r   r   r.   r,   ra    s,   

zOrpheusClient._stream_from_poolc                 s0  	   	t	jjrzjdurzt g 	fdd  fdd}t	| j}t
 }	 |djI dH }	|	du rLn|	V  q=z|jdd W n tyq }
 zsg|
 W Y d}
~
nd}
~
ww rxd	 dS d}zz!t I dH }|	2 z	3 dH W }	|	V  q6 W nC ty }
 ztd
|
 |
d}
~
w ty }
 ztd|
 |
d}
~
w ttfy     ty }
 ztd|
 |
d}
~
ww W |durz
| I dH  W dS  ty   Y dS w dS |durz	| I dH  W w  ty   Y w w w )aW  
        Stream audio asynchronously from the Orpheus TTS model.

        If connect() was called first, uses a pooled connection for lowest
        latency. Otherwise, creates a new connection per request.

        Args:
            text: The text to convert to speech.
            voice: Voice to use. See orpheus_tts.VOICES for options.
            max_tokens: Override default max_tokens.
            temperature: Override default temperature.
            repetition_penalty: Override default repetition_penalty.

        Yields:
            bytes: Raw PCM audio chunks (int16, 48kHz, mono).

        Raises:
            ValueError: If voice is not recognized.
            AuthenticationError: If API key is invalid.
            ConnectionError: If connection to service fails.
            StreamingError: If an error occurs during streaming.
        Nc               	     sP  j d ur5j jd urnjd urnjd ur!njd2 z3 d H W }  |  q&6 d S d}t|D ]j}d }z.I d H \}}|2 z3 d H W }  |  qT6 	|I d H  W  d S  t
y   |d ur	|I d H  ||d k rY q;td| d ty   |d ur	|I d H   w d S )Nr  ro  r   rp  rq  )r  r   r   r   r   r   r   r@  r]  rF  r
   r   r&   )r  rr  rs  r   r   rt  r+   r,   re    sV   

z(OrpheusClient.stream_async.<locals>._runc               
     rf  r   rg  rh  ri  r+   r,   rk    rl  z0OrpheusClient.stream_async.<locals>._run_wrapperT      @r   r   z Connection closed unexpectedly: z"Failed to connect to TTS service: zUnexpected error: )r2   r3   r;   r  r   r   r   r   r   r2  rX  rY  rR   r4  r&   r   r-   r#  r]  r
   r   OSErrorr   r   r   r   )r   r   r.   r   r   r   rk  r6  loopr  r   r   r+   ru  r,   stream_async]  s   +

zOrpheusClient.stream_asyncc           
   	   #  s    t   g  	f	ddfdd}tj|dd}|  	   }	|	du r0n|	V  q'|jdd	 r@d
 dS )z?Stream with a fresh connection (includes handshake in latency).c                    s   d } zZz#t  I d H } | 2 z3 d H W } | q6 W n ty> } z| W Y d }~nd }~ww W | d urWz	|  I d H  W n	 tyV   Y nw  d  d S | d uruz	|  I d H  W n	 tyt   Y nw  d  w r   )r-   r#  r]  r   r&   r   r   )r   r  r   )	rd  r   rj  r   r   r   r   r   r.   r+   r,   collect  s:   
z:OrpheusClient._stream_with_new_connection.<locals>.collectc               
     sT   t  } t |  z|    W | |   |   d S | |   |   w r   )r   r.  r%  run_until_completeshutdown_asyncgensr   )rx  )rz  r+   r,   	run_async  s   

z<OrpheusClient._stream_with_new_connection.<locals>.run_asyncTr(  Nrv  r   r   )r   r   r/  r0  r1  rR   r5   )
r   r   r.   r   r   r   r   r}  threadr  r+   )
rd  rz  r   rj  r   r   r   r   r   r.   r,   rb    s"   
	z)OrpheusClient._stream_with_new_connectionr   c                 K  s"   t | j||fi |}d|S )ak  
        Generate complete audio and return as bytes.

        Convenience method that collects all streamed chunks.

        Args:
            text: The text to convert to speech.
            voice: Voice to use.
            **kwargs: Additional arguments passed to stream().

        Returns:
            bytes: Complete PCM audio (int16, 48kHz, mono).
            )r   rc  r5   )r   r   r.   r(   chunksr+   r+   r,   stream_to_bytes  s   
zOrpheusClient.stream_to_bytesc                   s>   g }| j ||fi |2 z3 dH W }|| q6 d|S )aA  
        Generate complete audio asynchronously and return as bytes.

        Args:
            text: The text to convert to speech.
            voice: Voice to use.
            **kwargs: Additional arguments passed to stream_async().

        Returns:
            bytes: Complete PCM audio (int16, 48kHz, mono).
        Nr  )ry  r   r5   )r   r   r.   r(   r  r  r+   r+   r,   stream_to_bytes_async3  s    
z#OrpheusClient.stream_to_bytes_async)Nr  r  r  )r  r   r   ru   r   r   r   r   rn   r   r/   r   )r1   r   )r$  ru   ry   r   r.   r   rz   r}   r   ru   r1   r>   )r$  ru   r1   r>   )r.   r   )r   r   r   )r   r   r.   r   r   r}   r   rW  r   rW  r1   r   )r^  )r   r   r.   r   r   r}   r   rW  r   rW  r1   r_  )r   r   r   r   r.   r   r   r}   r   rW  r   rW  r1   r_  )r   r   r.   r   r   r   r   r}   r   rW  r   rW  r1   r_  )r   r   r.   r   r1   r   )r   r   r   r   r   r#  _DEFAULT_POOL_SIZEr$   r3  r@  rF  r   rN  rT  rU  rV  r]  rc  r`  ra  ry  rb  r  r  r+   r+   r+   r,   r  \  s^    
3
P
-

.
0
0
5D 

:r  r   )r   r   r   r   )r.   r   r/   r0   r1   r   )r<   r"   r=   r   r1   r>   )r=   r   r1   r0   )rZ   r   r1   r   )rZ   r   rn   r   r1   r   )r*   ru   rv   r   rn   r   r1   r   )ry   r   rz   ru   r1   r   )r)   r&   r1   r}   )
rn   r   rZ   r   r   r   r=   r   r1   r0   )rn   r   rZ   r   r   r   r=   r   r   r   r1   r0   )r   r   r1   r   )r   r   r1   r>   )r   r"   r1   r   ):r   
__future__r   r   rJ   r+  r   r/  urllib.errorr   urllib.requestr   pathlibr   typingr   r   r   urllib.parser   r   r	   r#   websockets.exceptionsr
   ImportErrororpheus_tts.exceptionsr   r   r   r   r  r-  rp   _DEFAULT_CONFIG_SERVICE_HOSTrf   '_DEFAULT_CONFIG_REQUEST_TIMEOUT_SECONDSr-   r;   rL   rY   rm   rt   rx   r|   r'   r   r   r   r   r   SAMPLE_RATESAMPLE_WIDTHCHANNELSr   r   r   r  r+   r+   r+   r,   <module>   sr    
&






9


	 .h