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.
@@ -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 = DEFAULT_API_CONNECT_OPTIONS,
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 = DEFAULT_API_CONNECT_OPTIONS
196
+ self, *, conn_options: Optional[APIConnectOptions] = None
170
197
  ) -> "SynthesizeStream":
171
- return SynthesizeStream(
198
+ stream = SynthesizeStream(
172
199
  tts=self,
173
- conn_options=conn_options,
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
- "https://api.cartesia.ai/tts/bytes",
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
- session: aiohttp.ClientSession,
286
+ pool: utils.ConnectionPool[aiohttp.ClientWebSocketResponse],
253
287
  ):
254
- super().__init__(tts=tts, conn_options=conn_options)
255
- self._opts, self._session = opts, session
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, close the connection
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
- url = f"wss://api.cartesia.ai/tts/websocket?api_key={self._opts.api_key}&cartesia_version={API_VERSION}"
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]:
@@ -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.7"
15
+ __version__ = "0.4.8"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: livekit-plugins-cartesia
3
- Version: 0.4.7
3
+ Version: 0.4.8
4
4
  Summary: LiveKit Agents Plugin for Cartesia
5
5
  Home-page: https://github.com/livekit/agents
6
6
  License: Apache-2.0
@@ -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,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (75.8.0)
2
+ Generator: setuptools (75.8.2)
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=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,,