typecast-python 0.3.13__tar.gz → 0.3.15__tar.gz
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_python-0.3.13 → typecast_python-0.3.15}/PKG-INFO +4 -4
- {typecast_python-0.3.13 → typecast_python-0.3.15}/README.md +3 -3
- {typecast_python-0.3.13 → typecast_python-0.3.15}/pyproject.toml +1 -1
- {typecast_python-0.3.13 → typecast_python-0.3.15}/src/typecast/_voice_clone.py +6 -0
- {typecast_python-0.3.13 → typecast_python-0.3.15}/src/typecast/async_client.py +87 -7
- {typecast_python-0.3.13 → typecast_python-0.3.15}/src/typecast/client.py +77 -4
- {typecast_python-0.3.13 → typecast_python-0.3.15}/src/typecast/models/__init__.py +4 -0
- {typecast_python-0.3.13 → typecast_python-0.3.15}/src/typecast/models/subscription.py +5 -0
- {typecast_python-0.3.13 → typecast_python-0.3.15}/src/typecast/models/tts.py +22 -2
- {typecast_python-0.3.13 → typecast_python-0.3.15}/src/typecast/models/voices.py +25 -1
- {typecast_python-0.3.13 → typecast_python-0.3.15}/.gitignore +0 -0
- {typecast_python-0.3.13 → typecast_python-0.3.15}/LICENSE +0 -0
- {typecast_python-0.3.13 → typecast_python-0.3.15}/src/typecast/__init__.py +0 -0
- {typecast_python-0.3.13 → typecast_python-0.3.15}/src/typecast/_httpx_compat.py +0 -0
- {typecast_python-0.3.13 → typecast_python-0.3.15}/src/typecast/_user_agent.py +0 -0
- {typecast_python-0.3.13 → typecast_python-0.3.15}/src/typecast/composer.py +0 -0
- {typecast_python-0.3.13 → typecast_python-0.3.15}/src/typecast/conf.py +0 -0
- {typecast_python-0.3.13 → typecast_python-0.3.15}/src/typecast/exceptions.py +0 -0
- {typecast_python-0.3.13 → typecast_python-0.3.15}/src/typecast/models/error.py +0 -0
- {typecast_python-0.3.13 → typecast_python-0.3.15}/src/typecast/utils.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: typecast-python
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.15
|
|
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
|
|
@@ -383,11 +383,11 @@ response = client.text_to_speech(TTSRequest(
|
|
|
383
383
|
```python
|
|
384
384
|
from typecast.models import VoicesV2Filter, TTSModel, GenderEnum, AgeEnum
|
|
385
385
|
|
|
386
|
-
# Get all voices (
|
|
387
|
-
voices = client.
|
|
386
|
+
# Get all voices (V3 API)
|
|
387
|
+
voices = client.voices_v3()
|
|
388
388
|
|
|
389
389
|
# Filter by criteria
|
|
390
|
-
filtered = client.
|
|
390
|
+
filtered = client.voices_v3(VoicesV2Filter(
|
|
391
391
|
model=TTSModel.SSFM_V30,
|
|
392
392
|
gender=GenderEnum.FEMALE,
|
|
393
393
|
age=AgeEnum.YOUNG_ADULT
|
|
@@ -136,11 +136,11 @@ response = client.text_to_speech(TTSRequest(
|
|
|
136
136
|
```python
|
|
137
137
|
from typecast.models import VoicesV2Filter, TTSModel, GenderEnum, AgeEnum
|
|
138
138
|
|
|
139
|
-
# Get all voices (
|
|
140
|
-
voices = client.
|
|
139
|
+
# Get all voices (V3 API)
|
|
140
|
+
voices = client.voices_v3()
|
|
141
141
|
|
|
142
142
|
# Filter by criteria
|
|
143
|
-
filtered = client.
|
|
143
|
+
filtered = client.voices_v3(VoicesV2Filter(
|
|
144
144
|
model=TTSModel.SSFM_V30,
|
|
145
145
|
gender=GenderEnum.FEMALE,
|
|
146
146
|
age=AgeEnum.YOUNG_ADULT
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "typecast-python"
|
|
7
|
-
version = "0.3.
|
|
7
|
+
version = "0.3.15"
|
|
8
8
|
description = "Official Typecast Python SDK - Convert text to lifelike speech using AI-powered voices"
|
|
9
9
|
authors = [
|
|
10
10
|
{name = "Neosapience", email = "help@typecast.ai"}
|
|
@@ -36,6 +36,12 @@ def validate_custom_voice_id(voice_id: str) -> None:
|
|
|
36
36
|
)
|
|
37
37
|
|
|
38
38
|
|
|
39
|
+
def validate_voice_id(voice_id: str) -> None:
|
|
40
|
+
"""Reject blank voice IDs before constructing a detail URL."""
|
|
41
|
+
if not isinstance(voice_id, str) or not voice_id.strip():
|
|
42
|
+
raise ValueError("voice_id must not be blank")
|
|
43
|
+
|
|
44
|
+
|
|
39
45
|
def validate_clone_inputs(audio: AudioInput, name: str) -> tuple[bytes, str]:
|
|
40
46
|
"""Pre-validate `clone_voice` inputs and return (audio_bytes, filename).
|
|
41
47
|
|
|
@@ -12,18 +12,21 @@ else: # pragma: no cover
|
|
|
12
12
|
aiohttp = None # type: ignore[assignment]
|
|
13
13
|
|
|
14
14
|
from . import conf
|
|
15
|
+
from ._user_agent import aiohttp_user_agent, attribution_suffix, httpx_user_agent
|
|
15
16
|
from ._voice_clone import (
|
|
16
17
|
normalize_clone_model,
|
|
17
18
|
validate_clone_inputs,
|
|
18
19
|
validate_custom_voice_id,
|
|
20
|
+
validate_voice_id,
|
|
19
21
|
)
|
|
20
|
-
from ._user_agent import aiohttp_user_agent, attribution_suffix, httpx_user_agent
|
|
21
22
|
|
|
22
23
|
if TYPE_CHECKING or sys.version_info < (3, 10): # pragma: no cover
|
|
23
24
|
from ._httpx_compat import AiohttpCompatSession, ClientTimeout, FormData
|
|
24
|
-
from .client import
|
|
25
|
-
|
|
26
|
-
|
|
25
|
+
from .client import (
|
|
26
|
+
_guess_audio_mime,
|
|
27
|
+
_output_with_inferred_format,
|
|
28
|
+
_validate_output_path,
|
|
29
|
+
)
|
|
27
30
|
from .exceptions import (
|
|
28
31
|
BadRequestError,
|
|
29
32
|
InternalServerError,
|
|
@@ -50,6 +53,7 @@ from .models import (
|
|
|
50
53
|
VoicesResponse,
|
|
51
54
|
VoicesV2Filter,
|
|
52
55
|
VoiceV2Response,
|
|
56
|
+
VoiceV3Response,
|
|
53
57
|
)
|
|
54
58
|
|
|
55
59
|
|
|
@@ -388,12 +392,12 @@ class AsyncTypecast:
|
|
|
388
392
|
else ClientTimeout(total=300, connect=10)
|
|
389
393
|
)
|
|
390
394
|
async with self.session.post(
|
|
391
|
-
f"{self.host}/v1/voices/clone",
|
|
395
|
+
f"{self.host}/v1/custom-voices/instant-clone",
|
|
392
396
|
data=form,
|
|
393
397
|
timeout=timeout,
|
|
394
398
|
headers=self._request_headers(),
|
|
395
399
|
) as response:
|
|
396
|
-
if response.status
|
|
400
|
+
if response.status not in (200, 201):
|
|
397
401
|
text = await response.text()
|
|
398
402
|
self._handle_error(response.status, text)
|
|
399
403
|
body = await response.json()
|
|
@@ -418,7 +422,7 @@ class AsyncTypecast:
|
|
|
418
422
|
else ClientTimeout(total=60, connect=10)
|
|
419
423
|
)
|
|
420
424
|
async with self.session.delete(
|
|
421
|
-
f"{self.host}/v1/voices/{quote(voice_id, safe='')}",
|
|
425
|
+
f"{self.host}/v1/custom-voices/{quote(voice_id, safe='')}",
|
|
422
426
|
timeout=timeout,
|
|
423
427
|
headers=self._request_headers(),
|
|
424
428
|
) as response:
|
|
@@ -426,6 +430,52 @@ class AsyncTypecast:
|
|
|
426
430
|
text = await response.text()
|
|
427
431
|
self._handle_error(response.status, text)
|
|
428
432
|
|
|
433
|
+
async def create_professional_voice(
|
|
434
|
+
self,
|
|
435
|
+
audio: Union[str, Path, bytes, BinaryIO],
|
|
436
|
+
name: str,
|
|
437
|
+
language: Union[str, LanguageCode],
|
|
438
|
+
model: Union[str, "TTSModel"],
|
|
439
|
+
) -> CustomVoice:
|
|
440
|
+
"""Start an asynchronous professional custom-voice clone."""
|
|
441
|
+
if self.session is None:
|
|
442
|
+
raise TypecastError("Client session not initialized; use 'async with'.")
|
|
443
|
+
audio_bytes, filename = validate_clone_inputs(audio, name)
|
|
444
|
+
form: Any = aiohttp.FormData() if aiohttp else FormData()
|
|
445
|
+
form.add_field("name", name)
|
|
446
|
+
form.add_field("language", str(language.value if hasattr(language, "value") else language))
|
|
447
|
+
form.add_field("model", normalize_clone_model(model))
|
|
448
|
+
form.add_field("files", audio_bytes, filename=filename, content_type=_guess_audio_mime(filename))
|
|
449
|
+
timeout = aiohttp.ClientTimeout(total=300, connect=10) if aiohttp else ClientTimeout(total=300, connect=10)
|
|
450
|
+
async with self.session.post(
|
|
451
|
+
f"{self.host}/v1/custom-voices/professional-clone",
|
|
452
|
+
data=form, timeout=timeout, headers=self._request_headers(),
|
|
453
|
+
) as response:
|
|
454
|
+
if response.status != 202:
|
|
455
|
+
self._handle_error(response.status, await response.text())
|
|
456
|
+
return CustomVoice.model_validate(await response.json())
|
|
457
|
+
|
|
458
|
+
async def get_custom_voices(self) -> list[CustomVoice]:
|
|
459
|
+
"""List custom voices owned by the authenticated user."""
|
|
460
|
+
if self.session is None:
|
|
461
|
+
raise TypecastError("Client session not initialized; use 'async with'.")
|
|
462
|
+
async with self.session.get(f"{self.host}/v1/custom-voices", headers=self._request_headers()) as response:
|
|
463
|
+
if response.status != 200:
|
|
464
|
+
self._handle_error(response.status, await response.text())
|
|
465
|
+
return [CustomVoice.model_validate(item) for item in await response.json()]
|
|
466
|
+
|
|
467
|
+
async def get_custom_voice(self, voice_id: str) -> CustomVoice:
|
|
468
|
+
"""Get a custom voice, including professional-clone status."""
|
|
469
|
+
if self.session is None:
|
|
470
|
+
raise TypecastError("Client session not initialized; use 'async with'.")
|
|
471
|
+
validate_custom_voice_id(voice_id)
|
|
472
|
+
async with self.session.get(
|
|
473
|
+
f"{self.host}/v1/custom-voices/{quote(voice_id, safe='')}", headers=self._request_headers()
|
|
474
|
+
) as response:
|
|
475
|
+
if response.status != 200:
|
|
476
|
+
self._handle_error(response.status, await response.text())
|
|
477
|
+
return CustomVoice.model_validate(await response.json())
|
|
478
|
+
|
|
429
479
|
async def voices(self, model: Optional[str] = None) -> list[VoicesResponse]:
|
|
430
480
|
"""Get available voices (V1 API) asynchronously.
|
|
431
481
|
|
|
@@ -580,6 +630,36 @@ class AsyncTypecast:
|
|
|
580
630
|
data = await response.json()
|
|
581
631
|
return VoiceV2Response.model_validate(data)
|
|
582
632
|
|
|
633
|
+
async def voices_v3(
|
|
634
|
+
self, filter: Optional[VoicesV2Filter] = None
|
|
635
|
+
) -> list[VoiceV3Response]:
|
|
636
|
+
"""Get voices using the current V3 Voice API asynchronously."""
|
|
637
|
+
if not self.session:
|
|
638
|
+
raise TypecastError("Client session not initialized. Use async with.")
|
|
639
|
+
params = {}
|
|
640
|
+
if filter:
|
|
641
|
+
for key, value in filter.model_dump(exclude_none=True).items():
|
|
642
|
+
params[key] = getattr(value, "value", value)
|
|
643
|
+
async with self.session.get(
|
|
644
|
+
f"{self.host}/v3/voices", params=params, headers=self._request_headers()
|
|
645
|
+
) as response:
|
|
646
|
+
if response.status != 200:
|
|
647
|
+
self._handle_error(response.status, await response.text())
|
|
648
|
+
return [VoiceV3Response.model_validate(item) for item in await response.json()]
|
|
649
|
+
|
|
650
|
+
async def voice_v3(self, voice_id: str) -> VoiceV3Response:
|
|
651
|
+
"""Get a voice by ID using the current V3 Voice API asynchronously."""
|
|
652
|
+
validate_voice_id(voice_id)
|
|
653
|
+
if not self.session:
|
|
654
|
+
raise TypecastError("Client session not initialized. Use async with.")
|
|
655
|
+
async with self.session.get(
|
|
656
|
+
f"{self.host}/v3/voices/{quote(voice_id, safe='')}",
|
|
657
|
+
headers=self._request_headers(),
|
|
658
|
+
) as response:
|
|
659
|
+
if response.status != 200:
|
|
660
|
+
self._handle_error(response.status, await response.text())
|
|
661
|
+
return VoiceV3Response.model_validate(await response.json())
|
|
662
|
+
|
|
583
663
|
async def recommend_voices(
|
|
584
664
|
self, query: str, count: int = 5
|
|
585
665
|
) -> list[RecommendedVoice]:
|
|
@@ -11,12 +11,13 @@ else: # pragma: no cover
|
|
|
11
11
|
requests = None # type: ignore[assignment]
|
|
12
12
|
|
|
13
13
|
from . import conf
|
|
14
|
+
from ._user_agent import attribution_suffix, httpx_user_agent, requests_user_agent
|
|
14
15
|
from ._voice_clone import (
|
|
15
16
|
normalize_clone_model,
|
|
16
17
|
validate_clone_inputs,
|
|
17
18
|
validate_custom_voice_id,
|
|
19
|
+
validate_voice_id,
|
|
18
20
|
)
|
|
19
|
-
from ._user_agent import attribution_suffix, httpx_user_agent, requests_user_agent
|
|
20
21
|
|
|
21
22
|
if TYPE_CHECKING or sys.version_info < (3, 10): # pragma: no cover
|
|
22
23
|
from ._httpx_compat import RequestsCompatSession
|
|
@@ -47,6 +48,7 @@ from .models import (
|
|
|
47
48
|
VoicesResponse,
|
|
48
49
|
VoicesV2Filter,
|
|
49
50
|
VoiceV2Response,
|
|
51
|
+
VoiceV3Response,
|
|
50
52
|
)
|
|
51
53
|
|
|
52
54
|
|
|
@@ -418,13 +420,13 @@ class Typecast:
|
|
|
418
420
|
if per_request:
|
|
419
421
|
headers.update(per_request)
|
|
420
422
|
response = self.session.post(
|
|
421
|
-
f"{self.host}/v1/voices/clone",
|
|
423
|
+
f"{self.host}/v1/custom-voices/instant-clone",
|
|
422
424
|
files=files,
|
|
423
425
|
data=data,
|
|
424
426
|
headers=headers,
|
|
425
427
|
timeout=(10, 300),
|
|
426
428
|
)
|
|
427
|
-
if response.status_code
|
|
429
|
+
if response.status_code not in (200, 201):
|
|
428
430
|
self._handle_error(response.status_code, response.text)
|
|
429
431
|
return CustomVoice.model_validate(response.json())
|
|
430
432
|
|
|
@@ -440,13 +442,58 @@ class Typecast:
|
|
|
440
442
|
"""
|
|
441
443
|
validate_custom_voice_id(voice_id)
|
|
442
444
|
response = self.session.delete(
|
|
443
|
-
f"{self.host}/v1/voices/{quote(voice_id, safe='')}",
|
|
445
|
+
f"{self.host}/v1/custom-voices/{quote(voice_id, safe='')}",
|
|
444
446
|
timeout=(10, 60),
|
|
445
447
|
headers=self._request_headers(),
|
|
446
448
|
)
|
|
447
449
|
if response.status_code not in (200, 204):
|
|
448
450
|
self._handle_error(response.status_code, response.text)
|
|
449
451
|
|
|
452
|
+
def create_professional_voice(
|
|
453
|
+
self,
|
|
454
|
+
audio: Union[str, Path, bytes, BinaryIO],
|
|
455
|
+
name: str,
|
|
456
|
+
language: Union[str, LanguageCode],
|
|
457
|
+
model: Union[str, "TTSModel"],
|
|
458
|
+
) -> CustomVoice:
|
|
459
|
+
"""Start an asynchronous professional custom-voice clone.
|
|
460
|
+
|
|
461
|
+
Poll :meth:`get_custom_voice` until the returned status is
|
|
462
|
+
``"completed"`` or ``"failed"``.
|
|
463
|
+
"""
|
|
464
|
+
audio_bytes, filename = validate_clone_inputs(audio, name)
|
|
465
|
+
model_str = normalize_clone_model(model)
|
|
466
|
+
response = self.session.post(
|
|
467
|
+
f"{self.host}/v1/custom-voices/professional-clone",
|
|
468
|
+
files={"files": (filename, audio_bytes, _guess_audio_mime(filename))},
|
|
469
|
+
data={"name": name, "language": str(language.value if hasattr(language, "value") else language), "model": model_str},
|
|
470
|
+
headers={"Content-Type": None, **(self._request_headers() or {})},
|
|
471
|
+
timeout=(10, 300),
|
|
472
|
+
)
|
|
473
|
+
if response.status_code != 202:
|
|
474
|
+
self._handle_error(response.status_code, response.text)
|
|
475
|
+
return CustomVoice.model_validate(response.json())
|
|
476
|
+
|
|
477
|
+
def get_custom_voices(self) -> list[CustomVoice]:
|
|
478
|
+
"""List custom voices owned by the authenticated user."""
|
|
479
|
+
response = self.session.get(
|
|
480
|
+
f"{self.host}/v1/custom-voices", headers=self._request_headers()
|
|
481
|
+
)
|
|
482
|
+
if response.status_code != 200:
|
|
483
|
+
self._handle_error(response.status_code, response.text)
|
|
484
|
+
return [CustomVoice.model_validate(item) for item in response.json()]
|
|
485
|
+
|
|
486
|
+
def get_custom_voice(self, voice_id: str) -> CustomVoice:
|
|
487
|
+
"""Get a custom voice, including professional-clone status."""
|
|
488
|
+
validate_custom_voice_id(voice_id)
|
|
489
|
+
response = self.session.get(
|
|
490
|
+
f"{self.host}/v1/custom-voices/{quote(voice_id, safe='')}",
|
|
491
|
+
headers=self._request_headers(),
|
|
492
|
+
)
|
|
493
|
+
if response.status_code != 200:
|
|
494
|
+
self._handle_error(response.status_code, response.text)
|
|
495
|
+
return CustomVoice.model_validate(response.json())
|
|
496
|
+
|
|
450
497
|
def voices(self, model: Optional[str] = None) -> list[VoicesResponse]:
|
|
451
498
|
"""Get available voices (V1 API).
|
|
452
499
|
|
|
@@ -579,6 +626,32 @@ class Typecast:
|
|
|
579
626
|
|
|
580
627
|
return VoiceV2Response.model_validate(response.json())
|
|
581
628
|
|
|
629
|
+
def voices_v3(
|
|
630
|
+
self, filter: Optional[VoicesV2Filter] = None
|
|
631
|
+
) -> list[VoiceV3Response]:
|
|
632
|
+
"""Get voices using the current V3 Voice API."""
|
|
633
|
+
params = {}
|
|
634
|
+
if filter:
|
|
635
|
+
for key, value in filter.model_dump(exclude_none=True).items():
|
|
636
|
+
params[key] = getattr(value, "value", value)
|
|
637
|
+
response = self.session.get(
|
|
638
|
+
f"{self.host}/v3/voices", params=params, headers=self._request_headers()
|
|
639
|
+
)
|
|
640
|
+
if response.status_code != 200:
|
|
641
|
+
self._handle_error(response.status_code, response.text)
|
|
642
|
+
return [VoiceV3Response.model_validate(item) for item in response.json()]
|
|
643
|
+
|
|
644
|
+
def voice_v3(self, voice_id: str) -> VoiceV3Response:
|
|
645
|
+
"""Get a voice by ID using the current V3 Voice API."""
|
|
646
|
+
validate_voice_id(voice_id)
|
|
647
|
+
response = self.session.get(
|
|
648
|
+
f"{self.host}/v3/voices/{quote(voice_id, safe='')}",
|
|
649
|
+
headers=self._request_headers(),
|
|
650
|
+
)
|
|
651
|
+
if response.status_code != 200:
|
|
652
|
+
self._handle_error(response.status_code, response.text)
|
|
653
|
+
return VoiceV3Response.model_validate(response.json())
|
|
654
|
+
|
|
582
655
|
def recommend_voices(self, query: str, count: int = 5) -> list[RecommendedVoice]:
|
|
583
656
|
"""Recommend voices from a text description.
|
|
584
657
|
|
|
@@ -22,12 +22,14 @@ from .voices import (
|
|
|
22
22
|
AgeEnum,
|
|
23
23
|
CustomVoice,
|
|
24
24
|
GenderEnum,
|
|
25
|
+
LocalizedVoiceName,
|
|
25
26
|
ModelInfo,
|
|
26
27
|
RecommendedVoice,
|
|
27
28
|
UseCaseEnum,
|
|
28
29
|
VoicesResponse,
|
|
29
30
|
VoicesV2Filter,
|
|
30
31
|
VoiceV2Response,
|
|
32
|
+
VoiceV3Response,
|
|
31
33
|
)
|
|
32
34
|
|
|
33
35
|
__all__ = [
|
|
@@ -40,6 +42,7 @@ __all__ = [
|
|
|
40
42
|
"Error",
|
|
41
43
|
"GenderEnum",
|
|
42
44
|
"LanguageCode",
|
|
45
|
+
"LocalizedVoiceName",
|
|
43
46
|
"Limits",
|
|
44
47
|
"ModelInfo",
|
|
45
48
|
"Output",
|
|
@@ -59,6 +62,7 @@ __all__ = [
|
|
|
59
62
|
"TTSWithTimestampsResponse",
|
|
60
63
|
"UseCaseEnum",
|
|
61
64
|
"VoiceV2Response",
|
|
65
|
+
"VoiceV3Response",
|
|
62
66
|
"VoicesResponse",
|
|
63
67
|
"VoicesV2Filter",
|
|
64
68
|
]
|
|
@@ -25,6 +25,11 @@ class Limits(BaseModel):
|
|
|
25
25
|
concurrency_limit: int = Field(
|
|
26
26
|
description="Maximum number of concurrent requests allowed"
|
|
27
27
|
)
|
|
28
|
+
custom_voice_slot: int = Field(
|
|
29
|
+
default=0,
|
|
30
|
+
ge=0,
|
|
31
|
+
description="Maximum active custom voices allowed by the current plan",
|
|
32
|
+
)
|
|
28
33
|
|
|
29
34
|
|
|
30
35
|
class SubscriptionResponse(BaseModel):
|
|
@@ -124,6 +124,13 @@ TTSPrompt = Union[Prompt, PresetPrompt, SmartPrompt]
|
|
|
124
124
|
|
|
125
125
|
|
|
126
126
|
class Output(BaseModel):
|
|
127
|
+
remove_silence_ms: Optional[int] = Field(
|
|
128
|
+
default=None,
|
|
129
|
+
strict=True,
|
|
130
|
+
ge=0,
|
|
131
|
+
le=1000,
|
|
132
|
+
description="Remaining detected silence in milliseconds. 0 removes silence; None disables this processing.",
|
|
133
|
+
)
|
|
127
134
|
volume: Optional[int] = Field(
|
|
128
135
|
default=100,
|
|
129
136
|
ge=0,
|
|
@@ -191,6 +198,14 @@ class OutputStream(BaseModel):
|
|
|
191
198
|
|
|
192
199
|
model_config = ConfigDict(extra="forbid")
|
|
193
200
|
|
|
201
|
+
remove_silence_ms: Optional[int] = Field(
|
|
202
|
+
default=None,
|
|
203
|
+
strict=True,
|
|
204
|
+
ge=0,
|
|
205
|
+
le=1000,
|
|
206
|
+
description="Remaining detected silence in milliseconds. 0 removes silence; None disables this processing.",
|
|
207
|
+
)
|
|
208
|
+
|
|
194
209
|
audio_pitch: Optional[int] = Field(default=0, ge=-12, le=12)
|
|
195
210
|
audio_tempo: Optional[float] = Field(default=1.0, ge=0.5, le=2.0)
|
|
196
211
|
audio_format: Optional[str] = Field(
|
|
@@ -419,6 +434,7 @@ class TTSWithTimestampsResponse(BaseModel):
|
|
|
419
434
|
def audio_bytes(self) -> bytes:
|
|
420
435
|
"""Return decoded audio bytes from the base64 `audio` field."""
|
|
421
436
|
import base64
|
|
437
|
+
|
|
422
438
|
return base64.b64decode(self.audio, validate=True)
|
|
423
439
|
|
|
424
440
|
def save_audio(self, path: str) -> None:
|
|
@@ -436,7 +452,9 @@ class TTSWithTimestampsResponse(BaseModel):
|
|
|
436
452
|
subtitle guidelines (7.0s / 42 chars).
|
|
437
453
|
"""
|
|
438
454
|
segments, word_mode = _segments_for_captioning(self.words, self.characters)
|
|
439
|
-
cues = _group_into_cues(
|
|
455
|
+
cues = _group_into_cues(
|
|
456
|
+
segments, word_mode=word_mode, max_seconds=max_seconds, max_chars=max_chars
|
|
457
|
+
)
|
|
440
458
|
if not cues:
|
|
441
459
|
raise ValueError("no alignment segments to caption from")
|
|
442
460
|
lines = []
|
|
@@ -457,7 +475,9 @@ class TTSWithTimestampsResponse(BaseModel):
|
|
|
457
475
|
subtitle guidelines (7.0s / 42 chars).
|
|
458
476
|
"""
|
|
459
477
|
segments, word_mode = _segments_for_captioning(self.words, self.characters)
|
|
460
|
-
cues = _group_into_cues(
|
|
478
|
+
cues = _group_into_cues(
|
|
479
|
+
segments, word_mode=word_mode, max_seconds=max_seconds, max_chars=max_chars
|
|
480
|
+
)
|
|
461
481
|
if not cues:
|
|
462
482
|
raise ValueError("no alignment segments to caption from")
|
|
463
483
|
lines = ["WEBVTT", ""]
|
|
@@ -70,6 +70,26 @@ class VoiceV2Response(BaseModel):
|
|
|
70
70
|
use_cases: Optional[List[str]] = None
|
|
71
71
|
|
|
72
72
|
|
|
73
|
+
class LocalizedVoiceName(BaseModel):
|
|
74
|
+
"""English and Korean display names returned by the V3 Voice API."""
|
|
75
|
+
|
|
76
|
+
eng: str
|
|
77
|
+
kor: str
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
class VoiceV3Response(BaseModel):
|
|
81
|
+
"""V3 Voice response with localized name and preview metadata."""
|
|
82
|
+
|
|
83
|
+
voice_id: str
|
|
84
|
+
voice_name: LocalizedVoiceName
|
|
85
|
+
models: List[ModelInfo]
|
|
86
|
+
voice_type: str
|
|
87
|
+
gender: Optional[GenderEnum] = None
|
|
88
|
+
age: Optional[AgeEnum] = None
|
|
89
|
+
use_cases: Optional[List[str]] = None
|
|
90
|
+
preview_url: Optional[str] = None
|
|
91
|
+
|
|
92
|
+
|
|
73
93
|
class RecommendedVoice(BaseModel):
|
|
74
94
|
"""Recommended voice result.
|
|
75
95
|
|
|
@@ -93,7 +113,7 @@ class VoicesV2Filter(BaseModel):
|
|
|
93
113
|
|
|
94
114
|
|
|
95
115
|
class CustomVoice(BaseModel):
|
|
96
|
-
"""
|
|
116
|
+
"""Custom voice returned by the Custom Voice API.
|
|
97
117
|
|
|
98
118
|
Attributes:
|
|
99
119
|
voice_id: Custom voice identifier with `uc_` prefix.
|
|
@@ -105,3 +125,7 @@ class CustomVoice(BaseModel):
|
|
|
105
125
|
voice_id: str = Field(..., description="Custom voice identifier (uc_ prefix)")
|
|
106
126
|
name: str = Field(..., description="Human-readable voice name")
|
|
107
127
|
model: str = Field(..., description="Engine model: ssfm-v21 or ssfm-v30")
|
|
128
|
+
source: Optional[str] = Field(default=None, description="instant or professional")
|
|
129
|
+
status: Optional[str] = Field(default=None, description="Cloning status")
|
|
130
|
+
error: Optional[str] = Field(default=None, description="Safe failure reason when status is failed")
|
|
131
|
+
created_at: Optional[str] = Field(default=None, description="UTC creation timestamp")
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|