typecast-python 0.3.2__py3-none-any.whl → 0.3.4__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/async_client.py +40 -0
- typecast/client.py +70 -3
- typecast/conf.py +13 -4
- typecast/models/tts.py +15 -3
- {typecast_python-0.3.2.dist-info → typecast_python-0.3.4.dist-info}/METADATA +1 -1
- {typecast_python-0.3.2.dist-info → typecast_python-0.3.4.dist-info}/RECORD +8 -8
- {typecast_python-0.3.2.dist-info → typecast_python-0.3.4.dist-info}/WHEEL +0 -0
- {typecast_python-0.3.2.dist-info → typecast_python-0.3.4.dist-info}/licenses/LICENSE +0 -0
typecast/async_client.py
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import asyncio
|
|
1
2
|
from pathlib import Path
|
|
2
3
|
from typing import AsyncIterator, BinaryIO, Optional, Union
|
|
3
4
|
from urllib.parse import quote
|
|
@@ -11,6 +12,8 @@ from ._voice_clone import (
|
|
|
11
12
|
validate_custom_voice_id,
|
|
12
13
|
)
|
|
13
14
|
from .client import _guess_audio_mime
|
|
15
|
+
from .client import _output_with_inferred_format
|
|
16
|
+
from .client import _validate_output_path
|
|
14
17
|
from .exceptions import (
|
|
15
18
|
BadRequestError,
|
|
16
19
|
InternalServerError,
|
|
@@ -23,8 +26,11 @@ from .exceptions import (
|
|
|
23
26
|
)
|
|
24
27
|
from .models import (
|
|
25
28
|
CustomVoice,
|
|
29
|
+
LanguageCode,
|
|
30
|
+
Output,
|
|
26
31
|
SubscriptionResponse,
|
|
27
32
|
TTSModel,
|
|
33
|
+
TTSPrompt,
|
|
28
34
|
TTSRequest,
|
|
29
35
|
TTSRequestStream,
|
|
30
36
|
TTSRequestWithTimestamps,
|
|
@@ -68,6 +74,8 @@ class AsyncTypecast:
|
|
|
68
74
|
"""
|
|
69
75
|
self.host = conf.get_host(host)
|
|
70
76
|
self.api_key = conf.get_api_key(api_key)
|
|
77
|
+
if not self.api_key and conf.is_default_host(self.host):
|
|
78
|
+
raise ValueError("API key is required for the default Typecast API host")
|
|
71
79
|
self.session: Optional[aiohttp.ClientSession] = None
|
|
72
80
|
|
|
73
81
|
async def __aenter__(self):
|
|
@@ -139,6 +147,38 @@ class AsyncTypecast:
|
|
|
139
147
|
format=response.headers.get("Content-Type", "audio/wav").split("/")[-1],
|
|
140
148
|
)
|
|
141
149
|
|
|
150
|
+
async def generate_to_file(
|
|
151
|
+
self,
|
|
152
|
+
path: Union[str, Path],
|
|
153
|
+
*,
|
|
154
|
+
text: str,
|
|
155
|
+
voice_id: str,
|
|
156
|
+
model: TTSModel = TTSModel.SSFM_V30,
|
|
157
|
+
language: Optional[Union[LanguageCode, str]] = None,
|
|
158
|
+
prompt: Optional[TTSPrompt] = None,
|
|
159
|
+
output: Optional[Output] = None,
|
|
160
|
+
seed: Optional[int] = None,
|
|
161
|
+
) -> TTSResponse:
|
|
162
|
+
"""Convert text to speech and write the audio bytes to a file.
|
|
163
|
+
|
|
164
|
+
Browse available API voices at
|
|
165
|
+
``https://typecast.ai/developers/api/voices``.
|
|
166
|
+
"""
|
|
167
|
+
output_path = _validate_output_path(path)
|
|
168
|
+
response = await self.text_to_speech(
|
|
169
|
+
TTSRequest(
|
|
170
|
+
text=text,
|
|
171
|
+
voice_id=voice_id,
|
|
172
|
+
model=model,
|
|
173
|
+
language=language,
|
|
174
|
+
prompt=prompt,
|
|
175
|
+
output=_output_with_inferred_format(output, path),
|
|
176
|
+
seed=seed,
|
|
177
|
+
)
|
|
178
|
+
)
|
|
179
|
+
await asyncio.to_thread(output_path.write_bytes, response.audio_data)
|
|
180
|
+
return response
|
|
181
|
+
|
|
142
182
|
async def text_to_speech_stream(
|
|
143
183
|
self, request: TTSRequestStream, chunk_size: int = 8192
|
|
144
184
|
) -> AsyncIterator[bytes]:
|
typecast/client.py
CHANGED
|
@@ -22,8 +22,11 @@ from .exceptions import (
|
|
|
22
22
|
)
|
|
23
23
|
from .models import (
|
|
24
24
|
CustomVoice,
|
|
25
|
+
LanguageCode,
|
|
26
|
+
Output,
|
|
25
27
|
SubscriptionResponse,
|
|
26
28
|
TTSModel,
|
|
29
|
+
TTSPrompt,
|
|
27
30
|
TTSRequest,
|
|
28
31
|
TTSRequestStream,
|
|
29
32
|
TTSRequestWithTimestamps,
|
|
@@ -35,6 +38,33 @@ from .models import (
|
|
|
35
38
|
)
|
|
36
39
|
|
|
37
40
|
|
|
41
|
+
def _infer_audio_format_from_path(path: Union[str, Path]) -> Optional[str]:
|
|
42
|
+
suffix = Path(path).suffix.lower()
|
|
43
|
+
if suffix == ".mp3":
|
|
44
|
+
return "mp3"
|
|
45
|
+
if suffix == ".wav":
|
|
46
|
+
return "wav"
|
|
47
|
+
return None
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def _output_with_inferred_format(
|
|
51
|
+
output: Optional[Output],
|
|
52
|
+
path: Union[str, Path],
|
|
53
|
+
) -> Optional[Output]:
|
|
54
|
+
audio_format = _infer_audio_format_from_path(path)
|
|
55
|
+
if audio_format is None or (output is not None and output.audio_format is not None):
|
|
56
|
+
return output
|
|
57
|
+
if output is None:
|
|
58
|
+
return Output(audio_format=audio_format)
|
|
59
|
+
return output.model_copy(update={"audio_format": audio_format})
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def _validate_output_path(path: Union[str, Path]) -> Path:
|
|
63
|
+
if isinstance(path, str) and not path.strip():
|
|
64
|
+
raise ValueError("path cannot be empty")
|
|
65
|
+
return Path(path)
|
|
66
|
+
|
|
67
|
+
|
|
38
68
|
def _guess_audio_mime(filename: str) -> str:
|
|
39
69
|
"""Guess audio MIME type from filename extension; fall back to octet-stream."""
|
|
40
70
|
lower = filename.lower()
|
|
@@ -77,10 +107,13 @@ class Typecast:
|
|
|
77
107
|
"""
|
|
78
108
|
self.host = conf.get_host(host)
|
|
79
109
|
self.api_key = conf.get_api_key(api_key)
|
|
110
|
+
if not self.api_key and conf.is_default_host(self.host):
|
|
111
|
+
raise ValueError("API key is required for the default Typecast API host")
|
|
80
112
|
self.session = requests.Session()
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
113
|
+
headers = {"Content-Type": "application/json"}
|
|
114
|
+
if self.api_key:
|
|
115
|
+
headers["X-API-KEY"] = self.api_key
|
|
116
|
+
self.session.headers.update(headers)
|
|
84
117
|
|
|
85
118
|
def _handle_error(self, status_code: int, response_text: str):
|
|
86
119
|
"""Handle HTTP error responses with specific exception types."""
|
|
@@ -133,6 +166,40 @@ class Typecast:
|
|
|
133
166
|
format=response.headers.get("Content-Type", "audio/wav").split("/")[-1],
|
|
134
167
|
)
|
|
135
168
|
|
|
169
|
+
def generate_to_file(
|
|
170
|
+
self,
|
|
171
|
+
path: Union[str, Path],
|
|
172
|
+
*,
|
|
173
|
+
text: str,
|
|
174
|
+
voice_id: str,
|
|
175
|
+
model: TTSModel = TTSModel.SSFM_V30,
|
|
176
|
+
language: Optional[Union[LanguageCode, str]] = None,
|
|
177
|
+
prompt: Optional[TTSPrompt] = None,
|
|
178
|
+
output: Optional[Output] = None,
|
|
179
|
+
seed: Optional[int] = None,
|
|
180
|
+
) -> TTSResponse:
|
|
181
|
+
"""Convert text to speech and write the audio bytes to a file.
|
|
182
|
+
|
|
183
|
+
``model`` defaults to ``ssfm-v30``. If ``output.audio_format`` is not
|
|
184
|
+
set, the format is inferred from a ``.mp3`` or ``.wav`` extension.
|
|
185
|
+
Browse available API voices at
|
|
186
|
+
``https://typecast.ai/developers/api/voices``.
|
|
187
|
+
"""
|
|
188
|
+
output_path = _validate_output_path(path)
|
|
189
|
+
response = self.text_to_speech(
|
|
190
|
+
TTSRequest(
|
|
191
|
+
text=text,
|
|
192
|
+
voice_id=voice_id,
|
|
193
|
+
model=model,
|
|
194
|
+
language=language,
|
|
195
|
+
prompt=prompt,
|
|
196
|
+
output=_output_with_inferred_format(output, path),
|
|
197
|
+
seed=seed,
|
|
198
|
+
)
|
|
199
|
+
)
|
|
200
|
+
output_path.write_bytes(response.audio_data)
|
|
201
|
+
return response
|
|
202
|
+
|
|
136
203
|
def text_to_speech_stream(
|
|
137
204
|
self, request: TTSRequestStream, chunk_size: int = 8192
|
|
138
205
|
) -> Iterator[bytes]:
|
typecast/conf.py
CHANGED
|
@@ -5,12 +5,21 @@ TYPECAST_API_HOST = "https://api.typecast.ai"
|
|
|
5
5
|
|
|
6
6
|
def get_host(host=None):
|
|
7
7
|
if host: # Parameter takes priority
|
|
8
|
-
return host
|
|
8
|
+
return normalize_host(host)
|
|
9
9
|
env_host = os.getenv("TYPECAST_API_HOST") # Check environment variable
|
|
10
|
-
return env_host if env_host else TYPECAST_API_HOST # Use default if not set
|
|
10
|
+
return normalize_host(env_host) if env_host else TYPECAST_API_HOST # Use default if not set
|
|
11
11
|
|
|
12
12
|
|
|
13
13
|
def get_api_key(api_key=None):
|
|
14
14
|
if api_key: # Parameter takes priority
|
|
15
|
-
return api_key
|
|
16
|
-
|
|
15
|
+
return api_key.strip()
|
|
16
|
+
env_key = os.getenv("TYPECAST_API_KEY") # Return from environment variable
|
|
17
|
+
return env_key.strip() if env_key else None
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def normalize_host(host):
|
|
21
|
+
return host.strip().rstrip("/")
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def is_default_host(host):
|
|
25
|
+
return normalize_host(host).lower() == TYPECAST_API_HOST
|
typecast/models/tts.py
CHANGED
|
@@ -158,7 +158,11 @@ class TTSRequest(BaseModel):
|
|
|
158
158
|
model_config = ConfigDict(json_schema_extra={"exclude_none": True})
|
|
159
159
|
|
|
160
160
|
voice_id: str = Field(
|
|
161
|
-
description=
|
|
161
|
+
description=(
|
|
162
|
+
"Voice ID. Browse available API voices at "
|
|
163
|
+
"https://typecast.ai/developers/api/voices."
|
|
164
|
+
),
|
|
165
|
+
examples=["tc_62a8975e695ad26f7fb514d1"],
|
|
162
166
|
)
|
|
163
167
|
text: str = Field(description="Text", examples=["Hello. How are you?"])
|
|
164
168
|
model: TTSModel = Field(description="Voice model name", examples=["ssfm-v21"])
|
|
@@ -202,7 +206,11 @@ class TTSRequestStream(BaseModel):
|
|
|
202
206
|
model_config = ConfigDict(json_schema_extra={"exclude_none": True})
|
|
203
207
|
|
|
204
208
|
voice_id: str = Field(
|
|
205
|
-
description=
|
|
209
|
+
description=(
|
|
210
|
+
"Voice ID. Browse available API voices at "
|
|
211
|
+
"https://typecast.ai/developers/api/voices."
|
|
212
|
+
),
|
|
213
|
+
examples=["tc_62a8975e695ad26f7fb514d1"],
|
|
206
214
|
)
|
|
207
215
|
text: str = Field(description="Text", examples=["Hello. How are you?"])
|
|
208
216
|
model: TTSModel = Field(description="Voice model name", examples=["ssfm-v21"])
|
|
@@ -253,7 +261,11 @@ class TTSRequestWithTimestamps(BaseModel):
|
|
|
253
261
|
model_config = ConfigDict(json_schema_extra={"exclude_none": True})
|
|
254
262
|
|
|
255
263
|
voice_id: str = Field(
|
|
256
|
-
description=
|
|
264
|
+
description=(
|
|
265
|
+
"Voice ID. Browse available API voices at "
|
|
266
|
+
"https://typecast.ai/developers/api/voices."
|
|
267
|
+
),
|
|
268
|
+
examples=["tc_62a8975e695ad26f7fb514d1"],
|
|
257
269
|
)
|
|
258
270
|
text: str = Field(description="Text", examples=["Hello. How are you?"])
|
|
259
271
|
model: TTSModel = Field(description="Voice model name", examples=["ssfm-v30"])
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: typecast-python
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.4
|
|
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
|
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
typecast/__init__.py,sha256=3pdJqNkXCZ7svzqab4sBR_qwyoM5E2sPjfuci1g1Ub8,1047
|
|
2
2
|
typecast/_voice_clone.py,sha256=TN2tbB3b5lC5uFCBnbERhz37bNJlbv6VZ-vj70EfTs4,3464
|
|
3
|
-
typecast/async_client.py,sha256=
|
|
4
|
-
typecast/client.py,sha256=
|
|
5
|
-
typecast/conf.py,sha256=
|
|
3
|
+
typecast/async_client.py,sha256=5Tz6j2waj4XHgHhoaxM__W3YcJxUS7r8Wc3RkZA-LBc,18645
|
|
4
|
+
typecast/client.py,sha256=oMjYK6NEYivY5ICSst-ZKH13ORnbzfGGlC_ZLhxk_PM,17461
|
|
5
|
+
typecast/conf.py,sha256=yfOHYZDvIshWWMriN5wE0GFPR8AX3zv4Rg2655OvA9Q,724
|
|
6
6
|
typecast/exceptions.py,sha256=Y0ZzYebe8zOSOSAHbXfKR0G_RJgdmZXxi15Z7ZxPLIk,1568
|
|
7
7
|
typecast/utils.py,sha256=XuNuX7gW8_CGKqZ-cv_tKlPVMPBluAYJBw2clwmjIMI,708
|
|
8
8
|
typecast/models/__init__.py,sha256=UEPUjg86fpCMXUvAzcNgxSPPhuPwCY9aQbzK3w90Fj0,1203
|
|
9
9
|
typecast/models/error.py,sha256=XomIjx7jvlCjItqzJuCAT4mXC9jwTjxR8lLDUk6P8KA,152
|
|
10
10
|
typecast/models/subscription.py,sha256=EIaAAo3cCRw8LYT_O6D9AVwxqIHrWCijzl4UTx7FZB8,894
|
|
11
|
-
typecast/models/tts.py,sha256=
|
|
11
|
+
typecast/models/tts.py,sha256=uZ8QEevpAnRF-W4_hoEx4EA1WP1TN0ZJIak1OYE1ThQ,16370
|
|
12
12
|
typecast/models/voices.py,sha256=-EXP35jDy7_G30k5bDnVrFJHp6svEDTA5jJ8oHAgXNQ,2310
|
|
13
|
-
typecast_python-0.3.
|
|
14
|
-
typecast_python-0.3.
|
|
15
|
-
typecast_python-0.3.
|
|
16
|
-
typecast_python-0.3.
|
|
13
|
+
typecast_python-0.3.4.dist-info/METADATA,sha256=TS2HvMhG4jAK_Y7r_dQK2Kiun3gz696PXUsuGDWZgyA,25530
|
|
14
|
+
typecast_python-0.3.4.dist-info/WHEEL,sha256=mffPy8wBnZQn2VnJUU5jE99KsxaSfiyMHV9Yt0aLVxs,87
|
|
15
|
+
typecast_python-0.3.4.dist-info/licenses/LICENSE,sha256=HvtJ-S89uUkuYmt-OvVk4MRxmzwtbn84__qJtSrGU2Q,11348
|
|
16
|
+
typecast_python-0.3.4.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|