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.
@@ -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 = "c2ac25f9-ecc4-4f56-9095-651354df60c0"
13
+ TTSDefaultVoiceId = "794f9389-aac1-45b6-b726-9d9369183238"
14
14
  TTSVoiceSpeed = Literal["fastest", "fast", "normal", "slow", "slowest"]
15
15
  TTSVoiceEmotion = Literal[
16
16
  "anger:lowest",
@@ -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-english",
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(self, text: str) -> "ChunkedStream":
153
- return ChunkedStream(self, text, self._opts, self._ensure_session())
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(self) -> "SynthesizeStream":
156
- return SynthesizeStream(self, self._opts, self._ensure_session())
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, tts: TTS, text: str, opts: _TTSOptions, session: aiohttp.ClientSession
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, text)
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 _main_task(self) -> None:
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
- @utils.log_exceptions(logger=logger)
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 sentence_stream_task():
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 input_task():
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 recv_task():
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 Exception("Cartesia connection closed unexpectedly")
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
- tasks = [
339
- asyncio.create_task(input_task()),
340
- asyncio.create_task(sentence_stream_task()),
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.gather(*tasks)
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
- await utils.aio.gracefully_cancel(*tasks)
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]:
@@ -12,4 +12,4 @@
12
12
  # See the License for the specific language governing permissions and
13
13
  # limitations under the License.
14
14
 
15
- __version__ = "0.4.3"
15
+ __version__ = "0.4.6"
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.2
2
2
  Name: livekit-plugins-cartesia
3
- Version: 0.4.3
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 >=0.11
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,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (75.3.0)
2
+ Generator: setuptools (75.8.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -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,,