livekit-plugins-cartesia 0.4.3__py3-none-any.whl → 0.4.6__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/models.py +1 -1
- livekit/plugins/cartesia/tts.py +68 -48
- livekit/plugins/cartesia/version.py +1 -1
- {livekit_plugins_cartesia-0.4.3.dist-info → livekit_plugins_cartesia-0.4.6.dist-info}/METADATA +13 -3
- livekit_plugins_cartesia-0.4.6.dist-info/RECORD +10 -0
- {livekit_plugins_cartesia-0.4.3.dist-info → livekit_plugins_cartesia-0.4.6.dist-info}/WHEEL +1 -1
- livekit_plugins_cartesia-0.4.3.dist-info/RECORD +0 -10
- {livekit_plugins_cartesia-0.4.3.dist-info → livekit_plugins_cartesia-0.4.6.dist-info}/top_level.txt +0 -0
@@ -10,7 +10,7 @@ TTSEncoding = Literal[
|
|
10
10
|
|
11
11
|
TTSModels = Literal["sonic-english", "sonic-multilingual"]
|
12
12
|
TTSLanguages = Literal["en", "es", "fr", "de", "pt", "zh", "ja"]
|
13
|
-
TTSDefaultVoiceId = "
|
13
|
+
TTSDefaultVoiceId = "794f9389-aac1-45b6-b726-9d9369183238"
|
14
14
|
TTSVoiceSpeed = Literal["fastest", "fast", "normal", "slow", "slowest"]
|
15
15
|
TTSVoiceEmotion = Literal[
|
16
16
|
"anger:lowest",
|
livekit/plugins/cartesia/tts.py
CHANGED
@@ -24,7 +24,9 @@ from typing import Any
|
|
24
24
|
import aiohttp
|
25
25
|
from livekit import rtc
|
26
26
|
from livekit.agents import (
|
27
|
+
DEFAULT_API_CONNECT_OPTIONS,
|
27
28
|
APIConnectionError,
|
29
|
+
APIConnectOptions,
|
28
30
|
APIStatusError,
|
29
31
|
APITimeoutError,
|
30
32
|
tokenize,
|
@@ -65,7 +67,7 @@ class TTS(tts.TTS):
|
|
65
67
|
def __init__(
|
66
68
|
self,
|
67
69
|
*,
|
68
|
-
model: TTSModels | str = "sonic
|
70
|
+
model: TTSModels | str = "sonic",
|
69
71
|
language: str = "en",
|
70
72
|
encoding: TTSEncoding = "pcm_s16le",
|
71
73
|
voice: str | list[float] = TTSDefaultVoiceId,
|
@@ -149,23 +151,47 @@ class TTS(tts.TTS):
|
|
149
151
|
if emotion is not None:
|
150
152
|
self._opts.emotion = emotion
|
151
153
|
|
152
|
-
def synthesize(
|
153
|
-
|
154
|
+
def synthesize(
|
155
|
+
self,
|
156
|
+
text: str,
|
157
|
+
*,
|
158
|
+
conn_options: APIConnectOptions = DEFAULT_API_CONNECT_OPTIONS,
|
159
|
+
) -> ChunkedStream:
|
160
|
+
return ChunkedStream(
|
161
|
+
tts=self,
|
162
|
+
input_text=text,
|
163
|
+
conn_options=conn_options,
|
164
|
+
opts=self._opts,
|
165
|
+
session=self._ensure_session(),
|
166
|
+
)
|
154
167
|
|
155
|
-
def stream(
|
156
|
-
|
168
|
+
def stream(
|
169
|
+
self, *, conn_options: APIConnectOptions = DEFAULT_API_CONNECT_OPTIONS
|
170
|
+
) -> "SynthesizeStream":
|
171
|
+
return SynthesizeStream(
|
172
|
+
tts=self,
|
173
|
+
conn_options=conn_options,
|
174
|
+
opts=self._opts,
|
175
|
+
session=self._ensure_session(),
|
176
|
+
)
|
157
177
|
|
158
178
|
|
159
179
|
class ChunkedStream(tts.ChunkedStream):
|
160
180
|
"""Synthesize chunked text using the bytes endpoint"""
|
161
181
|
|
162
182
|
def __init__(
|
163
|
-
self,
|
183
|
+
self,
|
184
|
+
*,
|
185
|
+
tts: TTS,
|
186
|
+
input_text: str,
|
187
|
+
conn_options: APIConnectOptions,
|
188
|
+
opts: _TTSOptions,
|
189
|
+
session: aiohttp.ClientSession,
|
164
190
|
) -> None:
|
165
|
-
super().__init__(tts,
|
191
|
+
super().__init__(tts=tts, input_text=input_text, conn_options=conn_options)
|
166
192
|
self._opts, self._session = opts, session
|
167
193
|
|
168
|
-
async def
|
194
|
+
async def _run(self) -> None:
|
169
195
|
request_id = utils.shortuuid()
|
170
196
|
bstream = utils.audio.AudioByteStream(
|
171
197
|
sample_rate=self._opts.sample_rate, num_channels=NUM_CHANNELS
|
@@ -184,6 +210,10 @@ class ChunkedStream(tts.ChunkedStream):
|
|
184
210
|
"https://api.cartesia.ai/tts/bytes",
|
185
211
|
headers=headers,
|
186
212
|
json=json,
|
213
|
+
timeout=aiohttp.ClientTimeout(
|
214
|
+
total=30,
|
215
|
+
sock_connect=self._conn_options.timeout,
|
216
|
+
),
|
187
217
|
) as resp:
|
188
218
|
resp.raise_for_status()
|
189
219
|
async for data, _ in resp.content.iter_chunks():
|
@@ -215,47 +245,22 @@ class ChunkedStream(tts.ChunkedStream):
|
|
215
245
|
class SynthesizeStream(tts.SynthesizeStream):
|
216
246
|
def __init__(
|
217
247
|
self,
|
248
|
+
*,
|
218
249
|
tts: TTS,
|
250
|
+
conn_options: APIConnectOptions,
|
219
251
|
opts: _TTSOptions,
|
220
252
|
session: aiohttp.ClientSession,
|
221
253
|
):
|
222
|
-
super().__init__(tts)
|
254
|
+
super().__init__(tts=tts, conn_options=conn_options)
|
223
255
|
self._opts, self._session = opts, session
|
224
256
|
self._sent_tokenizer_stream = tokenize.basic.SentenceTokenizer(
|
225
257
|
min_sentence_len=BUFFERED_WORDS_COUNT
|
226
258
|
).stream()
|
227
259
|
|
228
|
-
|
229
|
-
async def _main_task(self) -> None:
|
230
|
-
retry_count = 0
|
231
|
-
max_retry = 3
|
232
|
-
while self._input_ch.qsize() or not self._input_ch.closed:
|
233
|
-
try:
|
234
|
-
url = f"wss://api.cartesia.ai/tts/websocket?api_key={self._opts.api_key}&cartesia_version={API_VERSION}"
|
235
|
-
ws = await self._session.ws_connect(url)
|
236
|
-
retry_count = 0 # connected successfully, reset the retry_count
|
237
|
-
|
238
|
-
await self._run_ws(ws)
|
239
|
-
except Exception as e:
|
240
|
-
if retry_count >= max_retry:
|
241
|
-
logger.exception(
|
242
|
-
f"failed to connect to Cartesia after {max_retry} tries"
|
243
|
-
)
|
244
|
-
break
|
245
|
-
|
246
|
-
retry_delay = min(retry_count * 2, 10) # max 10s
|
247
|
-
retry_count += 1
|
248
|
-
|
249
|
-
logger.warning(
|
250
|
-
f"Cartesia connection failed, retrying in {retry_delay}s",
|
251
|
-
exc_info=e,
|
252
|
-
)
|
253
|
-
await asyncio.sleep(retry_delay)
|
254
|
-
|
255
|
-
async def _run_ws(self, ws: aiohttp.ClientWebSocketResponse) -> None:
|
260
|
+
async def _run(self) -> None:
|
256
261
|
request_id = utils.shortuuid()
|
257
262
|
|
258
|
-
async def
|
263
|
+
async def _sentence_stream_task(ws: aiohttp.ClientWebSocketResponse):
|
259
264
|
base_pkt = _to_cartesia_options(self._opts)
|
260
265
|
async for ev in self._sent_tokenizer_stream:
|
261
266
|
token_pkt = base_pkt.copy()
|
@@ -270,7 +275,7 @@ class SynthesizeStream(tts.SynthesizeStream):
|
|
270
275
|
end_pkt["continue"] = False
|
271
276
|
await ws.send_str(json.dumps(end_pkt))
|
272
277
|
|
273
|
-
async def
|
278
|
+
async def _input_task():
|
274
279
|
async for data in self._input_ch:
|
275
280
|
if isinstance(data, self._FlushSentinel):
|
276
281
|
self._sent_tokenizer_stream.flush()
|
@@ -278,7 +283,7 @@ class SynthesizeStream(tts.SynthesizeStream):
|
|
278
283
|
self._sent_tokenizer_stream.push_text(data)
|
279
284
|
self._sent_tokenizer_stream.end_input()
|
280
285
|
|
281
|
-
async def
|
286
|
+
async def _recv_task(ws: aiohttp.ClientWebSocketResponse):
|
282
287
|
audio_bstream = utils.audio.AudioByteStream(
|
283
288
|
sample_rate=self._opts.sample_rate,
|
284
289
|
num_channels=NUM_CHANNELS,
|
@@ -307,7 +312,10 @@ class SynthesizeStream(tts.SynthesizeStream):
|
|
307
312
|
aiohttp.WSMsgType.CLOSE,
|
308
313
|
aiohttp.WSMsgType.CLOSING,
|
309
314
|
):
|
310
|
-
raise
|
315
|
+
raise APIStatusError(
|
316
|
+
"Cartesia connection closed unexpectedly",
|
317
|
+
request_id=request_id,
|
318
|
+
)
|
311
319
|
|
312
320
|
if msg.type != aiohttp.WSMsgType.TEXT:
|
313
321
|
logger.warning("unexpected Cartesia message type %s", msg.type)
|
@@ -335,16 +343,28 @@ class SynthesizeStream(tts.SynthesizeStream):
|
|
335
343
|
else:
|
336
344
|
logger.error("unexpected Cartesia message %s", data)
|
337
345
|
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
asyncio.create_task(recv_task()),
|
342
|
-
]
|
346
|
+
url = f"wss://api.cartesia.ai/tts/websocket?api_key={self._opts.api_key}&cartesia_version={API_VERSION}"
|
347
|
+
|
348
|
+
ws: aiohttp.ClientWebSocketResponse | None = None
|
343
349
|
|
344
350
|
try:
|
345
|
-
await asyncio.
|
351
|
+
ws = await asyncio.wait_for(
|
352
|
+
self._session.ws_connect(url), self._conn_options.timeout
|
353
|
+
)
|
354
|
+
|
355
|
+
tasks = [
|
356
|
+
asyncio.create_task(_input_task()),
|
357
|
+
asyncio.create_task(_sentence_stream_task(ws)),
|
358
|
+
asyncio.create_task(_recv_task(ws)),
|
359
|
+
]
|
360
|
+
|
361
|
+
try:
|
362
|
+
await asyncio.gather(*tasks)
|
363
|
+
finally:
|
364
|
+
await utils.aio.gracefully_cancel(*tasks)
|
346
365
|
finally:
|
347
|
-
|
366
|
+
if ws is not None:
|
367
|
+
await ws.close()
|
348
368
|
|
349
369
|
|
350
370
|
def _to_cartesia_options(opts: _TTSOptions) -> dict[str, Any]:
|
{livekit_plugins_cartesia-0.4.3.dist-info → livekit_plugins_cartesia-0.4.6.dist-info}/METADATA
RENAMED
@@ -1,6 +1,6 @@
|
|
1
|
-
Metadata-Version: 2.
|
1
|
+
Metadata-Version: 2.2
|
2
2
|
Name: livekit-plugins-cartesia
|
3
|
-
Version: 0.4.
|
3
|
+
Version: 0.4.6
|
4
4
|
Summary: LiveKit Agents Plugin for Cartesia
|
5
5
|
Home-page: https://github.com/livekit/agents
|
6
6
|
License: Apache-2.0
|
@@ -19,7 +19,17 @@ Classifier: Programming Language :: Python :: 3.10
|
|
19
19
|
Classifier: Programming Language :: Python :: 3 :: Only
|
20
20
|
Requires-Python: >=3.9.0
|
21
21
|
Description-Content-Type: text/markdown
|
22
|
-
Requires-Dist: livekit-agents
|
22
|
+
Requires-Dist: livekit-agents>=0.12.3
|
23
|
+
Dynamic: classifier
|
24
|
+
Dynamic: description
|
25
|
+
Dynamic: description-content-type
|
26
|
+
Dynamic: home-page
|
27
|
+
Dynamic: keywords
|
28
|
+
Dynamic: license
|
29
|
+
Dynamic: project-url
|
30
|
+
Dynamic: requires-dist
|
31
|
+
Dynamic: requires-python
|
32
|
+
Dynamic: summary
|
23
33
|
|
24
34
|
# LiveKit Plugins Cartesia
|
25
35
|
|
@@ -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=M4Bxxu0cTyhA3QahgFubZcpREENw9C5GZjaepN4tPmc,13869
|
6
|
+
livekit/plugins/cartesia/version.py,sha256=D_Vma_D7upABLhXhjK1P5nahb5uh4zpuIiqYDq9sgJ0,600
|
7
|
+
livekit_plugins_cartesia-0.4.6.dist-info/METADATA,sha256=HGMhsernokDVPky0lA5zT6eNgK9MHHbiFi5eVoFKsPA,1462
|
8
|
+
livekit_plugins_cartesia-0.4.6.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
|
9
|
+
livekit_plugins_cartesia-0.4.6.dist-info/top_level.txt,sha256=OoDok3xUmXbZRvOrfvvXB-Juu4DX79dlq188E19YHoo,8
|
10
|
+
livekit_plugins_cartesia-0.4.6.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=fOO276Vzw3OkDUWUVcw7PH95ctFy38rj3q9I6_mYQ7M,950
|
4
|
-
livekit/plugins/cartesia/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
5
|
-
livekit/plugins/cartesia/tts.py,sha256=2xwWOIjwLDOF4TbHlDibrZpUju9If8WrNpHQ2JMuBC0,13533
|
6
|
-
livekit/plugins/cartesia/version.py,sha256=u7PSD5TBbPRIhE8vJkBVJzq_eGqYfg6RP5c3VKNlKGk,600
|
7
|
-
livekit_plugins_cartesia-0.4.3.dist-info/METADATA,sha256=w5q0oz6rdHDL5cxAyT5hWbHqhZnOPnZYGl3aUKsr3z4,1246
|
8
|
-
livekit_plugins_cartesia-0.4.3.dist-info/WHEEL,sha256=P9jw-gEje8ByB7_hXoICnHtVCrEwMQh-630tKvQWehc,91
|
9
|
-
livekit_plugins_cartesia-0.4.3.dist-info/top_level.txt,sha256=OoDok3xUmXbZRvOrfvvXB-Juu4DX79dlq188E19YHoo,8
|
10
|
-
livekit_plugins_cartesia-0.4.3.dist-info/RECORD,,
|
{livekit_plugins_cartesia-0.4.3.dist-info → livekit_plugins_cartesia-0.4.6.dist-info}/top_level.txt
RENAMED
File without changes
|