typecast-python 0.1.9__py3-none-any.whl → 0.2.0__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.
- typecast/__init__.py +12 -2
- typecast/async_client.py +78 -2
- typecast/client.py +79 -2
- typecast/models/__init__.py +9 -2
- typecast/models/subscription.py +35 -0
- typecast/models/tts.py +38 -0
- {typecast_python-0.1.9.dist-info → typecast_python-0.2.0.dist-info}/METADATA +1 -3
- typecast_python-0.2.0.dist-info/RECORD +15 -0
- typecast/models/tts_wss.py +0 -7
- typecast/sse.py +0 -58
- typecast/websocket.py +0 -48
- typecast_python-0.1.9.dist-info/RECORD +0 -17
- {typecast_python-0.1.9.dist-info → typecast_python-0.2.0.dist-info}/WHEEL +0 -0
- {typecast_python-0.1.9.dist-info → typecast_python-0.2.0.dist-info}/licenses/LICENSE +0 -0
typecast/__init__.py
CHANGED
|
@@ -11,14 +11,19 @@ from .exceptions import (
|
|
|
11
11
|
UnprocessableEntityError,
|
|
12
12
|
)
|
|
13
13
|
from .models import (
|
|
14
|
+
Credits,
|
|
14
15
|
Error,
|
|
15
16
|
LanguageCode,
|
|
17
|
+
Limits,
|
|
16
18
|
Output,
|
|
19
|
+
OutputStream,
|
|
20
|
+
PlanTier,
|
|
17
21
|
Prompt,
|
|
22
|
+
SubscriptionResponse,
|
|
18
23
|
TTSRequest,
|
|
24
|
+
TTSRequestStream,
|
|
19
25
|
TTSResponse,
|
|
20
26
|
VoicesResponse,
|
|
21
|
-
WebSocketMessage,
|
|
22
27
|
)
|
|
23
28
|
|
|
24
29
|
__all__ = [
|
|
@@ -35,12 +40,17 @@ __all__ = [
|
|
|
35
40
|
"UnauthorizedError",
|
|
36
41
|
"UnprocessableEntityError",
|
|
37
42
|
# Models
|
|
43
|
+
"Credits",
|
|
38
44
|
"Error",
|
|
39
45
|
"LanguageCode",
|
|
46
|
+
"Limits",
|
|
40
47
|
"Output",
|
|
48
|
+
"OutputStream",
|
|
49
|
+
"PlanTier",
|
|
41
50
|
"Prompt",
|
|
51
|
+
"SubscriptionResponse",
|
|
42
52
|
"TTSRequest",
|
|
53
|
+
"TTSRequestStream",
|
|
43
54
|
"TTSResponse",
|
|
44
55
|
"VoicesResponse",
|
|
45
|
-
"WebSocketMessage",
|
|
46
56
|
]
|
typecast/async_client.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
from typing import Optional
|
|
1
|
+
from typing import AsyncIterator, Optional
|
|
2
2
|
|
|
3
3
|
import aiohttp
|
|
4
4
|
|
|
@@ -13,7 +13,15 @@ from .exceptions import (
|
|
|
13
13
|
UnauthorizedError,
|
|
14
14
|
UnprocessableEntityError,
|
|
15
15
|
)
|
|
16
|
-
from .models import
|
|
16
|
+
from .models import (
|
|
17
|
+
SubscriptionResponse,
|
|
18
|
+
TTSRequest,
|
|
19
|
+
TTSRequestStream,
|
|
20
|
+
TTSResponse,
|
|
21
|
+
VoicesResponse,
|
|
22
|
+
VoicesV2Filter,
|
|
23
|
+
VoiceV2Response,
|
|
24
|
+
)
|
|
17
25
|
|
|
18
26
|
|
|
19
27
|
class AsyncTypecast:
|
|
@@ -117,6 +125,49 @@ class AsyncTypecast:
|
|
|
117
125
|
format=response.headers.get("Content-Type", "audio/wav").split("/")[-1],
|
|
118
126
|
)
|
|
119
127
|
|
|
128
|
+
async def text_to_speech_stream(
|
|
129
|
+
self, request: TTSRequestStream, chunk_size: int = 8192
|
|
130
|
+
) -> AsyncIterator[bytes]:
|
|
131
|
+
"""Stream synthesized audio from `POST /v1/text-to-speech/stream`.
|
|
132
|
+
|
|
133
|
+
Async generator that yields audio chunks as the server emits them.
|
|
134
|
+
For WAV the first chunk contains the WAV header (declared with size
|
|
135
|
+
0xFFFFFFFF for streaming) followed by PCM data; subsequent chunks are
|
|
136
|
+
PCM only. For MP3 each chunk contains independently-decodable frames.
|
|
137
|
+
|
|
138
|
+
Args:
|
|
139
|
+
request: Streaming TTS request. Uses `OutputStream`, which omits
|
|
140
|
+
`volume` and `target_lufs` (not supported by the streaming
|
|
141
|
+
endpoint).
|
|
142
|
+
chunk_size: Maximum bytes returned per yielded chunk.
|
|
143
|
+
|
|
144
|
+
Yields:
|
|
145
|
+
Audio chunk bytes in the order produced by the server.
|
|
146
|
+
|
|
147
|
+
Raises:
|
|
148
|
+
TypecastError: If the client session is not initialized.
|
|
149
|
+
BadRequestError, UnauthorizedError, PaymentRequiredError,
|
|
150
|
+
NotFoundError, UnprocessableEntityError, RateLimitError,
|
|
151
|
+
InternalServerError, TypecastError: depending on response status.
|
|
152
|
+
"""
|
|
153
|
+
if not isinstance(chunk_size, int) or isinstance(chunk_size, bool) or chunk_size < 1:
|
|
154
|
+
raise ValueError("chunk_size must be a positive integer")
|
|
155
|
+
if not self.session:
|
|
156
|
+
raise TypecastError("Client session not initialized. Use async with.")
|
|
157
|
+
endpoint = "/v1/text-to-speech/stream"
|
|
158
|
+
stream_timeout = aiohttp.ClientTimeout(sock_connect=10, sock_read=300)
|
|
159
|
+
async with self.session.post(
|
|
160
|
+
f"{self.host}{endpoint}",
|
|
161
|
+
json=request.model_dump(exclude_none=True),
|
|
162
|
+
timeout=stream_timeout,
|
|
163
|
+
) as response:
|
|
164
|
+
if response.status != 200:
|
|
165
|
+
error_text = await response.text()
|
|
166
|
+
self._handle_error(response.status, error_text)
|
|
167
|
+
|
|
168
|
+
async for chunk in response.content.iter_chunked(chunk_size):
|
|
169
|
+
yield chunk
|
|
170
|
+
|
|
120
171
|
async def voices(self, model: Optional[str] = None) -> list[VoicesResponse]:
|
|
121
172
|
"""Get available voices (V1 API) asynchronously.
|
|
122
173
|
|
|
@@ -212,6 +263,31 @@ class AsyncTypecast:
|
|
|
212
263
|
data = await response.json()
|
|
213
264
|
return [VoiceV2Response.model_validate(item) for item in data]
|
|
214
265
|
|
|
266
|
+
async def get_my_subscription(self) -> SubscriptionResponse:
|
|
267
|
+
"""Get the authenticated user's current subscription asynchronously.
|
|
268
|
+
|
|
269
|
+
Returns plan tier, credit usage, and concurrency limits. Use this to
|
|
270
|
+
check remaining credits or verify your plan before making TTS calls.
|
|
271
|
+
|
|
272
|
+
Returns:
|
|
273
|
+
SubscriptionResponse with plan, credits, and limits.
|
|
274
|
+
|
|
275
|
+
Raises:
|
|
276
|
+
TypecastError: If the client session is not initialized.
|
|
277
|
+
UnauthorizedError: If the API key is invalid.
|
|
278
|
+
RateLimitError: If the rate limit was exceeded.
|
|
279
|
+
InternalServerError: On server-side failures.
|
|
280
|
+
"""
|
|
281
|
+
if not self.session:
|
|
282
|
+
raise TypecastError("Client session not initialized. Use async with.")
|
|
283
|
+
endpoint = "/v1/users/me/subscription"
|
|
284
|
+
async with self.session.get(f"{self.host}{endpoint}") as response:
|
|
285
|
+
if response.status != 200:
|
|
286
|
+
error_text = await response.text()
|
|
287
|
+
self._handle_error(response.status, error_text)
|
|
288
|
+
data = await response.json()
|
|
289
|
+
return SubscriptionResponse.model_validate(data)
|
|
290
|
+
|
|
215
291
|
async def voice_v2(self, voice_id: str) -> VoiceV2Response:
|
|
216
292
|
"""Get a specific voice by ID with enhanced metadata (V2 API)
|
|
217
293
|
|
typecast/client.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
from typing import Optional
|
|
1
|
+
from typing import Iterator, Optional
|
|
2
2
|
|
|
3
3
|
import requests
|
|
4
4
|
|
|
@@ -13,7 +13,15 @@ from .exceptions import (
|
|
|
13
13
|
UnauthorizedError,
|
|
14
14
|
UnprocessableEntityError,
|
|
15
15
|
)
|
|
16
|
-
from .models import
|
|
16
|
+
from .models import (
|
|
17
|
+
SubscriptionResponse,
|
|
18
|
+
TTSRequest,
|
|
19
|
+
TTSRequestStream,
|
|
20
|
+
TTSResponse,
|
|
21
|
+
VoicesResponse,
|
|
22
|
+
VoicesV2Filter,
|
|
23
|
+
VoiceV2Response,
|
|
24
|
+
)
|
|
17
25
|
|
|
18
26
|
|
|
19
27
|
class Typecast:
|
|
@@ -104,6 +112,55 @@ class Typecast:
|
|
|
104
112
|
format=response.headers.get("Content-Type", "audio/wav").split("/")[-1],
|
|
105
113
|
)
|
|
106
114
|
|
|
115
|
+
def text_to_speech_stream(
|
|
116
|
+
self, request: TTSRequestStream, chunk_size: int = 8192
|
|
117
|
+
) -> Iterator[bytes]:
|
|
118
|
+
"""Stream synthesized audio from `POST /v1/text-to-speech/stream`.
|
|
119
|
+
|
|
120
|
+
Yields raw audio chunks as the server produces them. For WAV the
|
|
121
|
+
first chunk contains the WAV header (declared with size 0xFFFFFFFF
|
|
122
|
+
for streaming) followed by PCM data; subsequent chunks are PCM only.
|
|
123
|
+
For MP3 each chunk contains independently-decodable MP3 frames.
|
|
124
|
+
|
|
125
|
+
The HTTP response is held open until the iterator is exhausted or
|
|
126
|
+
garbage-collected, so callers should consume the iterator promptly
|
|
127
|
+
(e.g. inside a `for` loop or by writing chunks to disk).
|
|
128
|
+
|
|
129
|
+
Args:
|
|
130
|
+
request: Streaming TTS request. Uses `OutputStream`, which omits
|
|
131
|
+
`volume` and `target_lufs` (not supported by the streaming
|
|
132
|
+
endpoint).
|
|
133
|
+
chunk_size: Maximum bytes returned per yielded chunk.
|
|
134
|
+
|
|
135
|
+
Yields:
|
|
136
|
+
Audio chunk bytes in the order produced by the server.
|
|
137
|
+
|
|
138
|
+
Raises:
|
|
139
|
+
BadRequestError, UnauthorizedError, PaymentRequiredError,
|
|
140
|
+
NotFoundError, UnprocessableEntityError, RateLimitError,
|
|
141
|
+
InternalServerError, TypecastError: depending on response status.
|
|
142
|
+
"""
|
|
143
|
+
if not isinstance(chunk_size, int) or isinstance(chunk_size, bool) or chunk_size < 1:
|
|
144
|
+
raise ValueError("chunk_size must be a positive integer")
|
|
145
|
+
endpoint = "/v1/text-to-speech/stream"
|
|
146
|
+
response = self.session.post(
|
|
147
|
+
f"{self.host}{endpoint}",
|
|
148
|
+
json=request.model_dump(exclude_none=True),
|
|
149
|
+
stream=True,
|
|
150
|
+
timeout=(10, 300),
|
|
151
|
+
)
|
|
152
|
+
if response.status_code != 200:
|
|
153
|
+
error_text = response.text
|
|
154
|
+
response.close()
|
|
155
|
+
self._handle_error(response.status_code, error_text)
|
|
156
|
+
|
|
157
|
+
try:
|
|
158
|
+
for chunk in response.iter_content(chunk_size=chunk_size):
|
|
159
|
+
if chunk:
|
|
160
|
+
yield chunk
|
|
161
|
+
finally:
|
|
162
|
+
response.close()
|
|
163
|
+
|
|
107
164
|
def voices(self, model: Optional[str] = None) -> list[VoicesResponse]:
|
|
108
165
|
"""Get available voices (V1 API).
|
|
109
166
|
|
|
@@ -186,6 +243,26 @@ class Typecast:
|
|
|
186
243
|
|
|
187
244
|
return [VoiceV2Response.model_validate(item) for item in response.json()]
|
|
188
245
|
|
|
246
|
+
def get_my_subscription(self) -> SubscriptionResponse:
|
|
247
|
+
"""Get the authenticated user's current subscription.
|
|
248
|
+
|
|
249
|
+
Returns plan tier, credit usage, and concurrency limits. Use this to
|
|
250
|
+
check remaining credits or verify your plan before making TTS calls.
|
|
251
|
+
|
|
252
|
+
Returns:
|
|
253
|
+
SubscriptionResponse with plan, credits, and limits.
|
|
254
|
+
|
|
255
|
+
Raises:
|
|
256
|
+
UnauthorizedError: If the API key is invalid.
|
|
257
|
+
RateLimitError: If the rate limit was exceeded.
|
|
258
|
+
InternalServerError: On server-side failures.
|
|
259
|
+
"""
|
|
260
|
+
endpoint = "/v1/users/me/subscription"
|
|
261
|
+
response = self.session.get(f"{self.host}{endpoint}")
|
|
262
|
+
if response.status_code != 200:
|
|
263
|
+
self._handle_error(response.status_code, response.text)
|
|
264
|
+
return SubscriptionResponse.model_validate(response.json())
|
|
265
|
+
|
|
189
266
|
def voice_v2(self, voice_id: str) -> VoiceV2Response:
|
|
190
267
|
"""Get a specific voice by ID with enhanced metadata (V2 API)
|
|
191
268
|
|
typecast/models/__init__.py
CHANGED
|
@@ -1,17 +1,19 @@
|
|
|
1
1
|
from .error import Error
|
|
2
|
+
from .subscription import Credits, Limits, PlanTier, SubscriptionResponse
|
|
2
3
|
from .tts import (
|
|
3
4
|
EmotionPreset,
|
|
4
5
|
LanguageCode,
|
|
5
6
|
Output,
|
|
7
|
+
OutputStream,
|
|
6
8
|
PresetPrompt,
|
|
7
9
|
Prompt,
|
|
8
10
|
SmartPrompt,
|
|
9
11
|
TTSModel,
|
|
10
12
|
TTSPrompt,
|
|
11
13
|
TTSRequest,
|
|
14
|
+
TTSRequestStream,
|
|
12
15
|
TTSResponse,
|
|
13
16
|
)
|
|
14
|
-
from .tts_wss import WebSocketMessage
|
|
15
17
|
from .voices import (
|
|
16
18
|
AgeEnum,
|
|
17
19
|
GenderEnum,
|
|
@@ -24,6 +26,7 @@ from .voices import (
|
|
|
24
26
|
|
|
25
27
|
__all__ = [
|
|
26
28
|
"TTSRequest",
|
|
29
|
+
"TTSRequestStream",
|
|
27
30
|
"TTSModel",
|
|
28
31
|
"TTSPrompt",
|
|
29
32
|
"Prompt",
|
|
@@ -31,6 +34,7 @@ __all__ = [
|
|
|
31
34
|
"SmartPrompt",
|
|
32
35
|
"EmotionPreset",
|
|
33
36
|
"Output",
|
|
37
|
+
"OutputStream",
|
|
34
38
|
"TTSResponse",
|
|
35
39
|
"VoicesResponse",
|
|
36
40
|
"VoiceV2Response",
|
|
@@ -40,6 +44,9 @@ __all__ = [
|
|
|
40
44
|
"AgeEnum",
|
|
41
45
|
"UseCaseEnum",
|
|
42
46
|
"Error",
|
|
43
|
-
"WebSocketMessage",
|
|
44
47
|
"LanguageCode",
|
|
48
|
+
"PlanTier",
|
|
49
|
+
"Credits",
|
|
50
|
+
"Limits",
|
|
51
|
+
"SubscriptionResponse",
|
|
45
52
|
]
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
from enum import Enum
|
|
2
|
+
|
|
3
|
+
from pydantic import BaseModel, Field
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class PlanTier(str, Enum):
|
|
7
|
+
"""Subscription plan tier."""
|
|
8
|
+
|
|
9
|
+
FREE = "free"
|
|
10
|
+
LITE = "lite"
|
|
11
|
+
PLUS = "plus"
|
|
12
|
+
CUSTOM = "custom"
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class Credits(BaseModel):
|
|
16
|
+
"""Credit usage information."""
|
|
17
|
+
|
|
18
|
+
plan_credits: int = Field(description="Total credits provided by the plan")
|
|
19
|
+
used_credits: int = Field(description="Number of credits used")
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class Limits(BaseModel):
|
|
23
|
+
"""Usage limit information."""
|
|
24
|
+
|
|
25
|
+
concurrency_limit: int = Field(
|
|
26
|
+
description="Maximum number of concurrent requests allowed"
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class SubscriptionResponse(BaseModel):
|
|
31
|
+
"""Response from `GET /v1/users/me/subscription`."""
|
|
32
|
+
|
|
33
|
+
plan: PlanTier = Field(description="Current subscription plan tier")
|
|
34
|
+
credits: Credits = Field(description="Credit usage information")
|
|
35
|
+
limits: Limits = Field(description="Usage limit information")
|
typecast/models/tts.py
CHANGED
|
@@ -174,3 +174,41 @@ class TTSResponse(BaseModel):
|
|
|
174
174
|
audio_data: bytes
|
|
175
175
|
duration: float
|
|
176
176
|
format: str = "wav"
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
class OutputStream(BaseModel):
|
|
180
|
+
"""Audio output settings for streaming mode.
|
|
181
|
+
|
|
182
|
+
Streaming mode does not support `volume` or `target_lufs` because the
|
|
183
|
+
server has to commit each chunk before the full waveform is known.
|
|
184
|
+
Passing either field raises a validation error so misuse fails fast.
|
|
185
|
+
"""
|
|
186
|
+
|
|
187
|
+
model_config = ConfigDict(extra="forbid")
|
|
188
|
+
|
|
189
|
+
audio_pitch: Optional[int] = Field(default=0, ge=-12, le=12)
|
|
190
|
+
audio_tempo: Optional[float] = Field(default=1.0, ge=0.5, le=2.0)
|
|
191
|
+
audio_format: Optional[str] = Field(
|
|
192
|
+
default="wav", description="Audio format", examples=["wav", "mp3"]
|
|
193
|
+
)
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
class TTSRequestStream(BaseModel):
|
|
197
|
+
"""Request body for `POST /v1/text-to-speech/stream`.
|
|
198
|
+
|
|
199
|
+
Mirrors `TTSRequest` but uses `OutputStream` (no volume / target_lufs).
|
|
200
|
+
"""
|
|
201
|
+
|
|
202
|
+
model_config = ConfigDict(json_schema_extra={"exclude_none": True})
|
|
203
|
+
|
|
204
|
+
voice_id: str = Field(
|
|
205
|
+
description="Voice ID", examples=["tc_62a8975e695ad26f7fb514d1"]
|
|
206
|
+
)
|
|
207
|
+
text: str = Field(description="Text", examples=["Hello. How are you?"])
|
|
208
|
+
model: TTSModel = Field(description="Voice model name", examples=["ssfm-v21"])
|
|
209
|
+
language: Optional[Union[LanguageCode, str]] = Field(
|
|
210
|
+
None, description="Language code (ISO 639-3)", examples=["eng"]
|
|
211
|
+
)
|
|
212
|
+
prompt: Optional[TTSPrompt] = None
|
|
213
|
+
output: Optional[OutputStream] = None
|
|
214
|
+
seed: Optional[int] = None
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: typecast-python
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.2.0
|
|
4
4
|
Summary: Official Typecast Python SDK - Convert text to lifelike speech using AI-powered voices
|
|
5
5
|
Project-URL: Homepage, https://typecast.ai
|
|
6
6
|
Project-URL: Documentation, https://typecast.ai/docs/overview
|
|
@@ -226,9 +226,7 @@ Requires-Python: >=3.11
|
|
|
226
226
|
Requires-Dist: aiohttp>=3.8.0
|
|
227
227
|
Requires-Dist: pydantic>=2.0.0
|
|
228
228
|
Requires-Dist: requests>=2.28.0
|
|
229
|
-
Requires-Dist: sseclient-py>=1.7.2
|
|
230
229
|
Requires-Dist: typing-extensions>=4.0.0
|
|
231
|
-
Requires-Dist: websockets>=10.0
|
|
232
230
|
Provides-Extra: dev
|
|
233
231
|
Requires-Dist: aioresponses>=0.7.6; extra == 'dev'
|
|
234
232
|
Requires-Dist: black>=23.0.0; extra == 'dev'
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
typecast/__init__.py,sha256=3pdJqNkXCZ7svzqab4sBR_qwyoM5E2sPjfuci1g1Ub8,1047
|
|
2
|
+
typecast/async_client.py,sha256=NJY2CWuCD3CVcqHuNwezIk39X8YTnkpQeJGdfhv6bqw,12341
|
|
3
|
+
typecast/client.py,sha256=HzCSGDglsCz880x8lXesHFXQIcivc7KXImWiXehWvQc,10514
|
|
4
|
+
typecast/conf.py,sha256=Fn_T4XW7BaHRnj0tP11BT5at3Y-db7oGcbBA_E1fmF0,479
|
|
5
|
+
typecast/exceptions.py,sha256=Y0ZzYebe8zOSOSAHbXfKR0G_RJgdmZXxi15Z7ZxPLIk,1568
|
|
6
|
+
typecast/utils.py,sha256=XuNuX7gW8_CGKqZ-cv_tKlPVMPBluAYJBw2clwmjIMI,708
|
|
7
|
+
typecast/models/__init__.py,sha256=uvDWzZPo01mjHreUtlHBMYiuZ84Tb02LzU3ZpszYg1I,923
|
|
8
|
+
typecast/models/error.py,sha256=XomIjx7jvlCjItqzJuCAT4mXC9jwTjxR8lLDUk6P8KA,152
|
|
9
|
+
typecast/models/subscription.py,sha256=EIaAAo3cCRw8LYT_O6D9AVwxqIHrWCijzl4UTx7FZB8,894
|
|
10
|
+
typecast/models/tts.py,sha256=clwrqky7CtcKjGtLY1h1hnyn2MXXJjXEaZ8iVZXK85c,6739
|
|
11
|
+
typecast/models/voices.py,sha256=VwmK_Ts17QccAaClGYqqstIGJ5RT9Qgj2kxqEoDr6z4,1659
|
|
12
|
+
typecast_python-0.2.0.dist-info/METADATA,sha256=ske3xiM-FStIUYiyDP3k7rYLU538pbTm35pyhll5y3w,22881
|
|
13
|
+
typecast_python-0.2.0.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
|
|
14
|
+
typecast_python-0.2.0.dist-info/licenses/LICENSE,sha256=HvtJ-S89uUkuYmt-OvVk4MRxmzwtbn84__qJtSrGU2Q,11348
|
|
15
|
+
typecast_python-0.2.0.dist-info/RECORD,,
|
typecast/models/tts_wss.py
DELETED
typecast/sse.py
DELETED
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
from typing import AsyncIterator, Optional
|
|
2
|
-
|
|
3
|
-
import aiohttp
|
|
4
|
-
|
|
5
|
-
from . import conf
|
|
6
|
-
from .exceptions import TypecastError
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
class TypecastSSE:
|
|
10
|
-
"""Server-Sent Events client for Typecast streaming endpoints."""
|
|
11
|
-
|
|
12
|
-
def __init__(
|
|
13
|
-
self,
|
|
14
|
-
api_key: Optional[str] = None,
|
|
15
|
-
host: Optional[str] = None,
|
|
16
|
-
sse_url: Optional[str] = None,
|
|
17
|
-
):
|
|
18
|
-
"""Initialize the SSE client.
|
|
19
|
-
|
|
20
|
-
Args:
|
|
21
|
-
api_key: API key. Defaults to TYPECAST_API_KEY env var.
|
|
22
|
-
host: API host. Defaults to TYPECAST_API_HOST env var or
|
|
23
|
-
'https://api.typecast.ai'. Used to derive sse_url when
|
|
24
|
-
sse_url is not provided.
|
|
25
|
-
sse_url: Full SSE base URL override (escape hatch for tests).
|
|
26
|
-
When provided, takes precedence over host.
|
|
27
|
-
"""
|
|
28
|
-
self.api_key = conf.get_api_key(api_key)
|
|
29
|
-
self.host = conf.get_host(host)
|
|
30
|
-
self._sse_url_override = sse_url
|
|
31
|
-
self.session: Optional[aiohttp.ClientSession] = None
|
|
32
|
-
|
|
33
|
-
@property
|
|
34
|
-
def sse_url(self) -> str:
|
|
35
|
-
if self._sse_url_override is not None:
|
|
36
|
-
return self._sse_url_override
|
|
37
|
-
return f"{self.host}/v1/text-to-speech/sse"
|
|
38
|
-
|
|
39
|
-
async def connect(self, endpoint: str) -> AsyncIterator[str]:
|
|
40
|
-
if self.session:
|
|
41
|
-
await self.session.close()
|
|
42
|
-
|
|
43
|
-
self.session = aiohttp.ClientSession(
|
|
44
|
-
headers={"X-API-KEY": self.api_key, "Accept": "text/event-stream"}
|
|
45
|
-
)
|
|
46
|
-
|
|
47
|
-
async with self.session.get(f"{self.sse_url}/{endpoint}") as response:
|
|
48
|
-
if response.status != 200:
|
|
49
|
-
raise TypecastError(f"SSE connection failed: {response.status}")
|
|
50
|
-
|
|
51
|
-
async for line in response.content:
|
|
52
|
-
decoded_line = line.decode("utf-8").strip()
|
|
53
|
-
if decoded_line.startswith("data: "):
|
|
54
|
-
yield decoded_line[6:]
|
|
55
|
-
|
|
56
|
-
async def close(self):
|
|
57
|
-
if self.session:
|
|
58
|
-
await self.session.close()
|
typecast/websocket.py
DELETED
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
import asyncio
|
|
2
|
-
import json
|
|
3
|
-
from typing import Callable, Optional
|
|
4
|
-
|
|
5
|
-
import websockets
|
|
6
|
-
|
|
7
|
-
from .exceptions import TypecastError
|
|
8
|
-
from .models import WebSocketMessage
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
class TypecastWebSocket:
|
|
12
|
-
"""WebSocket client for Typecast streaming TTS."""
|
|
13
|
-
|
|
14
|
-
DEFAULT_WS_URL = "wss://api.typecast.ai/v1/ws"
|
|
15
|
-
|
|
16
|
-
def __init__(self, api_key: str, ws_url: Optional[str] = None):
|
|
17
|
-
self.api_key = api_key
|
|
18
|
-
self.ws_url = ws_url or self.DEFAULT_WS_URL
|
|
19
|
-
self.ws: Optional[websockets.WebSocketClientProtocol] = None
|
|
20
|
-
self.callbacks: dict[str, Callable] = {}
|
|
21
|
-
|
|
22
|
-
async def connect(self):
|
|
23
|
-
self.ws = await websockets.connect(f"{self.ws_url}?token={self.api_key}")
|
|
24
|
-
asyncio.create_task(self._message_handler())
|
|
25
|
-
|
|
26
|
-
async def _message_handler(self):
|
|
27
|
-
if not self.ws:
|
|
28
|
-
return
|
|
29
|
-
|
|
30
|
-
async for message in self.ws:
|
|
31
|
-
data = json.loads(message)
|
|
32
|
-
msg = WebSocketMessage(**data)
|
|
33
|
-
|
|
34
|
-
if msg.type in self.callbacks:
|
|
35
|
-
await self.callbacks[msg.type](msg.payload)
|
|
36
|
-
|
|
37
|
-
def on(self, event_type: str, callback: Callable):
|
|
38
|
-
"""Register event callback."""
|
|
39
|
-
self.callbacks[event_type] = callback
|
|
40
|
-
|
|
41
|
-
async def send(self, message: WebSocketMessage):
|
|
42
|
-
if not self.ws:
|
|
43
|
-
raise TypecastError("WebSocket not connected")
|
|
44
|
-
await self.ws.send(message.model_dump_json())
|
|
45
|
-
|
|
46
|
-
async def close(self):
|
|
47
|
-
if self.ws:
|
|
48
|
-
await self.ws.close()
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
typecast/__init__.py,sha256=8xdL3GnADTIsi2KuqPY938opQtN6GbHWnJI0XwjqHPc,871
|
|
2
|
-
typecast/async_client.py,sha256=gSsp8pGxX7twCfnDDohFxazMV0G63UcHqgMRFKv5V_U,9083
|
|
3
|
-
typecast/client.py,sha256=B5EgmAJ6rWGfSsrWaiVoRlB98IW1lKrf2Un8yXE5uUA,7510
|
|
4
|
-
typecast/conf.py,sha256=Fn_T4XW7BaHRnj0tP11BT5at3Y-db7oGcbBA_E1fmF0,479
|
|
5
|
-
typecast/exceptions.py,sha256=Y0ZzYebe8zOSOSAHbXfKR0G_RJgdmZXxi15Z7ZxPLIk,1568
|
|
6
|
-
typecast/sse.py,sha256=3qpNaS7iPU97QIWRFRUoiQFinHcIwloEim8Z_LtD05c,1951
|
|
7
|
-
typecast/utils.py,sha256=XuNuX7gW8_CGKqZ-cv_tKlPVMPBluAYJBw2clwmjIMI,708
|
|
8
|
-
typecast/websocket.py,sha256=3m5klCYykEOyC_j28rLR2zAt7LHWO4k3-2W-L2rlaGY,1450
|
|
9
|
-
typecast/models/__init__.py,sha256=sF_0YSHXOPebNpNi_RiYAy-GnoG1D7ugHskszQYDsxk,754
|
|
10
|
-
typecast/models/error.py,sha256=XomIjx7jvlCjItqzJuCAT4mXC9jwTjxR8lLDUk6P8KA,152
|
|
11
|
-
typecast/models/tts.py,sha256=51TeFsiCZMzhMWdvP6Q6bKm4KCMjk0OyLPahOPig-yk,5358
|
|
12
|
-
typecast/models/tts_wss.py,sha256=zYfP3oNs5VuOkxI7bu0uSCHDYfSbQWaenqg0lLLqaSc,101
|
|
13
|
-
typecast/models/voices.py,sha256=VwmK_Ts17QccAaClGYqqstIGJ5RT9Qgj2kxqEoDr6z4,1659
|
|
14
|
-
typecast_python-0.1.9.dist-info/METADATA,sha256=cYVtKWj-dNPOMqWS_9uBeP0hbWk-CMa2TUM016GAEgE,22948
|
|
15
|
-
typecast_python-0.1.9.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
|
|
16
|
-
typecast_python-0.1.9.dist-info/licenses/LICENSE,sha256=HvtJ-S89uUkuYmt-OvVk4MRxmzwtbn84__qJtSrGU2Q,11348
|
|
17
|
-
typecast_python-0.1.9.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|