typecast-python 0.1.8__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 +82 -7
- typecast/client.py +83 -7
- typecast/models/__init__.py +9 -2
- typecast/models/subscription.py +35 -0
- typecast/models/tts.py +38 -0
- {typecast_python-0.1.8.dist-info → typecast_python-0.2.0.dist-info}/METADATA +4 -3
- typecast_python-0.2.0.dist-info/RECORD +15 -0
- typecast/models/tts_wss.py +0 -7
- typecast/sse.py +0 -35
- typecast/websocket.py +0 -47
- typecast_python-0.1.8.dist-info/RECORD +0 -17
- {typecast_python-0.1.8.dist-info → typecast_python-0.2.0.dist-info}/WHEEL +0 -0
- {typecast_python-0.1.8.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
|
|
|
@@ -196,12 +247,11 @@ class AsyncTypecast:
|
|
|
196
247
|
params = {}
|
|
197
248
|
if filter:
|
|
198
249
|
filter_dict = filter.model_dump(exclude_none=True)
|
|
199
|
-
# Convert enum values to
|
|
250
|
+
# Convert enum values to their underlying str representation.
|
|
251
|
+
# Every VoicesV2Filter field is an Optional[Enum], so getattr
|
|
252
|
+
# falls back only if a future non-enum field is added.
|
|
200
253
|
for key, value in filter_dict.items():
|
|
201
|
-
|
|
202
|
-
params[key] = value.value
|
|
203
|
-
else:
|
|
204
|
-
params[key] = value
|
|
254
|
+
params[key] = getattr(value, "value", value)
|
|
205
255
|
|
|
206
256
|
async with self.session.get(
|
|
207
257
|
f"{self.host}{endpoint}", params=params
|
|
@@ -213,6 +263,31 @@ class AsyncTypecast:
|
|
|
213
263
|
data = await response.json()
|
|
214
264
|
return [VoiceV2Response.model_validate(item) for item in data]
|
|
215
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
|
+
|
|
216
291
|
async def voice_v2(self, voice_id: str) -> VoiceV2Response:
|
|
217
292
|
"""Get a specific voice by ID with enhanced metadata (V2 API)
|
|
218
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
|
|
|
@@ -173,12 +230,11 @@ class Typecast:
|
|
|
173
230
|
params = {}
|
|
174
231
|
if filter:
|
|
175
232
|
filter_dict = filter.model_dump(exclude_none=True)
|
|
176
|
-
# Convert enum values to
|
|
233
|
+
# Convert enum values to their underlying str representation.
|
|
234
|
+
# Every VoicesV2Filter field is an Optional[Enum], so getattr
|
|
235
|
+
# falls back only if a future non-enum field is added.
|
|
177
236
|
for key, value in filter_dict.items():
|
|
178
|
-
|
|
179
|
-
params[key] = value.value
|
|
180
|
-
else:
|
|
181
|
-
params[key] = value
|
|
237
|
+
params[key] = getattr(value, "value", value)
|
|
182
238
|
|
|
183
239
|
response = self.session.get(f"{self.host}{endpoint}", params=params)
|
|
184
240
|
|
|
@@ -187,6 +243,26 @@ class Typecast:
|
|
|
187
243
|
|
|
188
244
|
return [VoiceV2Response.model_validate(item) for item in response.json()]
|
|
189
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
|
+
|
|
190
266
|
def voice_v2(self, voice_id: str) -> VoiceV2Response:
|
|
191
267
|
"""Get a specific voice by ID with enhanced metadata (V2 API)
|
|
192
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,15 +226,15 @@ 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
|
|
231
|
+
Requires-Dist: aioresponses>=0.7.6; extra == 'dev'
|
|
233
232
|
Requires-Dist: black>=23.0.0; extra == 'dev'
|
|
234
233
|
Requires-Dist: flake8>=6.0.0; extra == 'dev'
|
|
235
234
|
Requires-Dist: isort>=5.0.0; extra == 'dev'
|
|
236
235
|
Requires-Dist: mypy>=1.0.0; extra == 'dev'
|
|
237
236
|
Requires-Dist: pytest-asyncio>=0.21.0; extra == 'dev'
|
|
237
|
+
Requires-Dist: pytest-cov>=7.0.0; extra == 'dev'
|
|
238
238
|
Requires-Dist: pytest-mock>=3.14.0; extra == 'dev'
|
|
239
239
|
Requires-Dist: pytest>=7.0.0; extra == 'dev'
|
|
240
240
|
Description-Content-Type: text/markdown
|
|
@@ -248,6 +248,7 @@ Description-Content-Type: text/markdown
|
|
|
248
248
|
Convert text to lifelike speech using AI-powered voices
|
|
249
249
|
|
|
250
250
|
[](https://pypi.org/project/typecast-python/)
|
|
251
|
+
[](../docs/coverage-policy.md)
|
|
251
252
|
[](LICENSE)
|
|
252
253
|
[](https://www.python.org/)
|
|
253
254
|
|
|
@@ -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,35 +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
|
-
SSE_URL = f"{conf.get_host()}/v1/text-to-speech/sse"
|
|
11
|
-
|
|
12
|
-
def __init__(self, api_key: str):
|
|
13
|
-
self.api_key = conf.get_api_key(api_key)
|
|
14
|
-
self.session: Optional[aiohttp.ClientSession] = None
|
|
15
|
-
|
|
16
|
-
async def connect(self, endpoint: str) -> AsyncIterator[str]:
|
|
17
|
-
if self.session:
|
|
18
|
-
await self.session.close()
|
|
19
|
-
|
|
20
|
-
self.session = aiohttp.ClientSession(
|
|
21
|
-
headers={"X-API-KEY": self.api_key, "Accept": "text/event-stream"}
|
|
22
|
-
)
|
|
23
|
-
|
|
24
|
-
async with self.session.get(f"{self.SSE_URL}/{endpoint}") as response:
|
|
25
|
-
if response.status != 200:
|
|
26
|
-
raise TypecastError(f"SSE connection failed: {response.status}")
|
|
27
|
-
|
|
28
|
-
async for line in response.content:
|
|
29
|
-
decoded_line = line.decode("utf-8").strip()
|
|
30
|
-
if decoded_line.startswith("data: "):
|
|
31
|
-
yield decoded_line[6:]
|
|
32
|
-
|
|
33
|
-
async def close(self):
|
|
34
|
-
if self.session:
|
|
35
|
-
await self.session.close()
|
typecast/websocket.py
DELETED
|
@@ -1,47 +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
|
-
WS_URL = "wss://api.typecast.ai/v1/ws"
|
|
13
|
-
|
|
14
|
-
def __init__(self, api_key: str):
|
|
15
|
-
self.api_key = api_key
|
|
16
|
-
self.ws: Optional[websockets.WebSocketClientProtocol] = None
|
|
17
|
-
self.callbacks: dict[str, Callable] = {}
|
|
18
|
-
|
|
19
|
-
async def connect(self):
|
|
20
|
-
self.ws = await websockets.connect(f"{self.WS_URL}?token={self.api_key}")
|
|
21
|
-
|
|
22
|
-
# Start message handler
|
|
23
|
-
asyncio.create_task(self._message_handler())
|
|
24
|
-
|
|
25
|
-
async def _message_handler(self):
|
|
26
|
-
if not self.ws:
|
|
27
|
-
return
|
|
28
|
-
|
|
29
|
-
async for message in self.ws:
|
|
30
|
-
data = json.loads(message)
|
|
31
|
-
msg = WebSocketMessage(**data)
|
|
32
|
-
|
|
33
|
-
if msg.type in self.callbacks:
|
|
34
|
-
await self.callbacks[msg.type](msg.payload)
|
|
35
|
-
|
|
36
|
-
def on(self, event_type: str, callback: Callable):
|
|
37
|
-
"""Register event callback"""
|
|
38
|
-
self.callbacks[event_type] = callback
|
|
39
|
-
|
|
40
|
-
async def send(self, message: WebSocketMessage):
|
|
41
|
-
if not self.ws:
|
|
42
|
-
raise TypecastError("WebSocket not connected")
|
|
43
|
-
await self.ws.send(message.model_dump_json())
|
|
44
|
-
|
|
45
|
-
async def close(self):
|
|
46
|
-
if self.ws:
|
|
47
|
-
await self.ws.close()
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
typecast/__init__.py,sha256=8xdL3GnADTIsi2KuqPY938opQtN6GbHWnJI0XwjqHPc,871
|
|
2
|
-
typecast/async_client.py,sha256=_v-YHussPMAG4az8XnWdXwb6uh5gcXAhWcpW1Scuf0o,9004
|
|
3
|
-
typecast/client.py,sha256=Kj_IqZ_RgDGaPTylsUsYAQfRO_1PG7NZdEuc8nZ0a4A,7431
|
|
4
|
-
typecast/conf.py,sha256=Fn_T4XW7BaHRnj0tP11BT5at3Y-db7oGcbBA_E1fmF0,479
|
|
5
|
-
typecast/exceptions.py,sha256=Y0ZzYebe8zOSOSAHbXfKR0G_RJgdmZXxi15Z7ZxPLIk,1568
|
|
6
|
-
typecast/sse.py,sha256=yYO2h5E_1-uiIID0TZM03eRZfvgkCStxu-0J6ej4Bpo,1109
|
|
7
|
-
typecast/utils.py,sha256=XuNuX7gW8_CGKqZ-cv_tKlPVMPBluAYJBw2clwmjIMI,708
|
|
8
|
-
typecast/websocket.py,sha256=SwFmk2efGtMLY3UgEtZljZAhULl5HG6KaA-rqfJ0Jaw,1336
|
|
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.8.dist-info/METADATA,sha256=MEpG51YSCui99kpJQlUOdoRIBd7Ao6auv58Ybmtj2I0,22726
|
|
15
|
-
typecast_python-0.1.8.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
|
|
16
|
-
typecast_python-0.1.8.dist-info/licenses/LICENSE,sha256=HvtJ-S89uUkuYmt-OvVk4MRxmzwtbn84__qJtSrGU2Q,11348
|
|
17
|
-
typecast_python-0.1.8.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|