livekit-plugins-cartesia 0.4.7__py3-none-any.whl → 0.4.8__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- livekit/plugins/cartesia/tts.py +49 -28
- livekit/plugins/cartesia/version.py +1 -1
- {livekit_plugins_cartesia-0.4.7.dist-info → livekit_plugins_cartesia-0.4.8.dist-info}/METADATA +1 -1
- livekit_plugins_cartesia-0.4.8.dist-info/RECORD +10 -0
- {livekit_plugins_cartesia-0.4.7.dist-info → livekit_plugins_cartesia-0.4.8.dist-info}/WHEEL +1 -1
- livekit_plugins_cartesia-0.4.7.dist-info/RECORD +0 -10
- {livekit_plugins_cartesia-0.4.7.dist-info → livekit_plugins_cartesia-0.4.8.dist-info}/top_level.txt +0 -0
livekit/plugins/cartesia/tts.py
CHANGED
@@ -18,13 +18,13 @@ import asyncio
|
|
18
18
|
import base64
|
19
19
|
import json
|
20
20
|
import os
|
21
|
+
import weakref
|
21
22
|
from dataclasses import dataclass
|
22
|
-
from typing import Any
|
23
|
+
from typing import Any, Optional
|
23
24
|
|
24
25
|
import aiohttp
|
25
26
|
from livekit import rtc
|
26
27
|
from livekit.agents import (
|
27
|
-
DEFAULT_API_CONNECT_OPTIONS,
|
28
28
|
APIConnectionError,
|
29
29
|
APIConnectOptions,
|
30
30
|
APIStatusError,
|
@@ -61,6 +61,13 @@ class _TTSOptions:
|
|
61
61
|
emotion: list[TTSVoiceEmotion | str] | None
|
62
62
|
api_key: str
|
63
63
|
language: str
|
64
|
+
base_url: str
|
65
|
+
|
66
|
+
def get_http_url(self, path: str) -> str:
|
67
|
+
return f"{self.base_url}{path}"
|
68
|
+
|
69
|
+
def get_ws_url(self, path: str) -> str:
|
70
|
+
return f"{self.base_url.replace('http', 'ws', 1)}{path}"
|
64
71
|
|
65
72
|
|
66
73
|
class TTS(tts.TTS):
|
@@ -76,6 +83,7 @@ class TTS(tts.TTS):
|
|
76
83
|
sample_rate: int = 24000,
|
77
84
|
api_key: str | None = None,
|
78
85
|
http_session: aiohttp.ClientSession | None = None,
|
86
|
+
base_url: str = "https://api.cartesia.ai",
|
79
87
|
) -> None:
|
80
88
|
"""
|
81
89
|
Create a new instance of Cartesia TTS.
|
@@ -92,6 +100,7 @@ class TTS(tts.TTS):
|
|
92
100
|
sample_rate (int, optional): The audio sample rate in Hz. Defaults to 24000.
|
93
101
|
api_key (str, optional): The Cartesia API key. If not provided, it will be read from the CARTESIA_API_KEY environment variable.
|
94
102
|
http_session (aiohttp.ClientSession | None, optional): An existing aiohttp ClientSession to use. If not provided, a new session will be created.
|
103
|
+
base_url (str, optional): The base URL for the Cartesia API. Defaults to "https://api.cartesia.ai".
|
95
104
|
"""
|
96
105
|
|
97
106
|
super().__init__(
|
@@ -113,8 +122,26 @@ class TTS(tts.TTS):
|
|
113
122
|
speed=speed,
|
114
123
|
emotion=emotion,
|
115
124
|
api_key=api_key,
|
125
|
+
base_url=base_url,
|
116
126
|
)
|
117
127
|
self._session = http_session
|
128
|
+
self._pool = utils.ConnectionPool[aiohttp.ClientWebSocketResponse](
|
129
|
+
connect_cb=self._connect_ws,
|
130
|
+
close_cb=self._close_ws,
|
131
|
+
)
|
132
|
+
self._streams = weakref.WeakSet[SynthesizeStream]()
|
133
|
+
|
134
|
+
async def _connect_ws(self) -> aiohttp.ClientWebSocketResponse:
|
135
|
+
session = self._ensure_session()
|
136
|
+
url = self._opts.get_ws_url(
|
137
|
+
f"/tts/websocket?api_key={self._opts.api_key}&cartesia_version={API_VERSION}"
|
138
|
+
)
|
139
|
+
return await asyncio.wait_for(
|
140
|
+
session.ws_connect(url), self._conn_options.timeout
|
141
|
+
)
|
142
|
+
|
143
|
+
async def _close_ws(self, ws: aiohttp.ClientWebSocketResponse):
|
144
|
+
await ws.close()
|
118
145
|
|
119
146
|
def _ensure_session(self) -> aiohttp.ClientSession:
|
120
147
|
if not self._session:
|
@@ -155,7 +182,7 @@ class TTS(tts.TTS):
|
|
155
182
|
self,
|
156
183
|
text: str,
|
157
184
|
*,
|
158
|
-
conn_options: APIConnectOptions =
|
185
|
+
conn_options: Optional[APIConnectOptions] = None,
|
159
186
|
) -> ChunkedStream:
|
160
187
|
return ChunkedStream(
|
161
188
|
tts=self,
|
@@ -166,14 +193,22 @@ class TTS(tts.TTS):
|
|
166
193
|
)
|
167
194
|
|
168
195
|
def stream(
|
169
|
-
self, *, conn_options: APIConnectOptions =
|
196
|
+
self, *, conn_options: Optional[APIConnectOptions] = None
|
170
197
|
) -> "SynthesizeStream":
|
171
|
-
|
198
|
+
stream = SynthesizeStream(
|
172
199
|
tts=self,
|
173
|
-
|
200
|
+
pool=self._pool,
|
174
201
|
opts=self._opts,
|
175
|
-
session=self._ensure_session(),
|
176
202
|
)
|
203
|
+
self._streams.add(stream)
|
204
|
+
return stream
|
205
|
+
|
206
|
+
async def aclose(self) -> None:
|
207
|
+
for stream in list(self._streams):
|
208
|
+
await stream.aclose()
|
209
|
+
self._streams.clear()
|
210
|
+
await self._pool.aclose()
|
211
|
+
await super().aclose()
|
177
212
|
|
178
213
|
|
179
214
|
class ChunkedStream(tts.ChunkedStream):
|
@@ -184,9 +219,9 @@ class ChunkedStream(tts.ChunkedStream):
|
|
184
219
|
*,
|
185
220
|
tts: TTS,
|
186
221
|
input_text: str,
|
187
|
-
conn_options: APIConnectOptions,
|
188
222
|
opts: _TTSOptions,
|
189
223
|
session: aiohttp.ClientSession,
|
224
|
+
conn_options: Optional[APIConnectOptions] = None,
|
190
225
|
) -> None:
|
191
226
|
super().__init__(tts=tts, input_text=input_text, conn_options=conn_options)
|
192
227
|
self._opts, self._session = opts, session
|
@@ -207,7 +242,7 @@ class ChunkedStream(tts.ChunkedStream):
|
|
207
242
|
|
208
243
|
try:
|
209
244
|
async with self._session.post(
|
210
|
-
|
245
|
+
self._opts.get_http_url("/tts/bytes"),
|
211
246
|
headers=headers,
|
212
247
|
json=json,
|
213
248
|
timeout=aiohttp.ClientTimeout(
|
@@ -247,12 +282,11 @@ class SynthesizeStream(tts.SynthesizeStream):
|
|
247
282
|
self,
|
248
283
|
*,
|
249
284
|
tts: TTS,
|
250
|
-
conn_options: APIConnectOptions,
|
251
285
|
opts: _TTSOptions,
|
252
|
-
|
286
|
+
pool: utils.ConnectionPool[aiohttp.ClientWebSocketResponse],
|
253
287
|
):
|
254
|
-
super().__init__(tts=tts
|
255
|
-
self._opts, self.
|
288
|
+
super().__init__(tts=tts)
|
289
|
+
self._opts, self._pool = opts, pool
|
256
290
|
self._sent_tokenizer_stream = tokenize.basic.SentenceTokenizer(
|
257
291
|
min_sentence_len=BUFFERED_WORDS_COUNT
|
258
292
|
).stream()
|
@@ -336,23 +370,13 @@ class SynthesizeStream(tts.SynthesizeStream):
|
|
336
370
|
last_frame = frame
|
337
371
|
|
338
372
|
_send_last_frame(segment_id=segment_id, is_final=True)
|
339
|
-
|
340
373
|
if segment_id == request_id:
|
341
|
-
# we're not going to receive more frames,
|
342
|
-
await ws.close()
|
374
|
+
# we're not going to receive more frames, end stream
|
343
375
|
break
|
344
376
|
else:
|
345
377
|
logger.error("unexpected Cartesia message %s", data)
|
346
378
|
|
347
|
-
|
348
|
-
|
349
|
-
ws: aiohttp.ClientWebSocketResponse | None = None
|
350
|
-
|
351
|
-
try:
|
352
|
-
ws = await asyncio.wait_for(
|
353
|
-
self._session.ws_connect(url), self._conn_options.timeout
|
354
|
-
)
|
355
|
-
|
379
|
+
async with self._pool.connection() as ws:
|
356
380
|
tasks = [
|
357
381
|
asyncio.create_task(_input_task()),
|
358
382
|
asyncio.create_task(_sentence_stream_task(ws)),
|
@@ -363,9 +387,6 @@ class SynthesizeStream(tts.SynthesizeStream):
|
|
363
387
|
await asyncio.gather(*tasks)
|
364
388
|
finally:
|
365
389
|
await utils.aio.gracefully_cancel(*tasks)
|
366
|
-
finally:
|
367
|
-
if ws is not None:
|
368
|
-
await ws.close()
|
369
390
|
|
370
391
|
|
371
392
|
def _to_cartesia_options(opts: _TTSOptions) -> dict[str, Any]:
|
@@ -0,0 +1,10 @@
|
|
1
|
+
livekit/plugins/cartesia/__init__.py,sha256=UTa6Q7IxhRBCwPftowHEUDvmBg99J_UjGS_yxTzKD7g,1095
|
2
|
+
livekit/plugins/cartesia/log.py,sha256=4Mnhjng_DU1dIWP9IWjIQGZ67EV3LnQhWMWCHVudJbo,71
|
3
|
+
livekit/plugins/cartesia/models.py,sha256=56CJgo7my-w-vpedir_ImV_aqKASeLihE5DbcCCgGJI,950
|
4
|
+
livekit/plugins/cartesia/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
5
|
+
livekit/plugins/cartesia/tts.py,sha256=UUnGrqizmnyHhA-rmbtVD6-pYCMEWozbZxlVRe0HFw4,14747
|
6
|
+
livekit/plugins/cartesia/version.py,sha256=1PUjLiYcL1-nwBUG1EXqZsTfF_WQI8KxpzCeCIZHeLc,600
|
7
|
+
livekit_plugins_cartesia-0.4.8.dist-info/METADATA,sha256=sJRr9rLGGOUcOonnwQX6xxbKu1Ag-DFWrxjBf4uhDtw,1463
|
8
|
+
livekit_plugins_cartesia-0.4.8.dist-info/WHEEL,sha256=jB7zZ3N9hIM9adW7qlTAyycLYW9npaWKLRzaoVcLKcM,91
|
9
|
+
livekit_plugins_cartesia-0.4.8.dist-info/top_level.txt,sha256=OoDok3xUmXbZRvOrfvvXB-Juu4DX79dlq188E19YHoo,8
|
10
|
+
livekit_plugins_cartesia-0.4.8.dist-info/RECORD,,
|
@@ -1,10 +0,0 @@
|
|
1
|
-
livekit/plugins/cartesia/__init__.py,sha256=UTa6Q7IxhRBCwPftowHEUDvmBg99J_UjGS_yxTzKD7g,1095
|
2
|
-
livekit/plugins/cartesia/log.py,sha256=4Mnhjng_DU1dIWP9IWjIQGZ67EV3LnQhWMWCHVudJbo,71
|
3
|
-
livekit/plugins/cartesia/models.py,sha256=56CJgo7my-w-vpedir_ImV_aqKASeLihE5DbcCCgGJI,950
|
4
|
-
livekit/plugins/cartesia/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
5
|
-
livekit/plugins/cartesia/tts.py,sha256=MKMLQBLc9A9XBkfwbFFt7PogUiWUHl-HozUR9mBmtMI,13906
|
6
|
-
livekit/plugins/cartesia/version.py,sha256=85hVEOZ--XQ1Y7ngd1qGTZPpeywK2do8-2uhP_kdeyA,600
|
7
|
-
livekit_plugins_cartesia-0.4.7.dist-info/METADATA,sha256=gZFU-QvodV6JmJiLDZ_OluuXviq8Zef-pYxhNEvxHho,1463
|
8
|
-
livekit_plugins_cartesia-0.4.7.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
|
9
|
-
livekit_plugins_cartesia-0.4.7.dist-info/top_level.txt,sha256=OoDok3xUmXbZRvOrfvvXB-Juu4DX79dlq188E19YHoo,8
|
10
|
-
livekit_plugins_cartesia-0.4.7.dist-info/RECORD,,
|
{livekit_plugins_cartesia-0.4.7.dist-info → livekit_plugins_cartesia-0.4.8.dist-info}/top_level.txt
RENAMED
File without changes
|