typecast-python 0.3.12__tar.gz → 0.3.14__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.12 → typecast_python-0.3.14}/PKG-INFO +1 -1
- {typecast_python-0.3.12 → typecast_python-0.3.14}/pyproject.toml +1 -1
- {typecast_python-0.3.12 → typecast_python-0.3.14}/src/typecast/_user_agent.py +4 -2
- {typecast_python-0.3.12 → typecast_python-0.3.14}/src/typecast/_voice_clone.py +6 -0
- {typecast_python-0.3.12 → typecast_python-0.3.14}/src/typecast/async_client.py +88 -8
- {typecast_python-0.3.12 → typecast_python-0.3.14}/src/typecast/client.py +78 -5
- {typecast_python-0.3.12 → typecast_python-0.3.14}/src/typecast/models/__init__.py +4 -0
- {typecast_python-0.3.12 → typecast_python-0.3.14}/src/typecast/models/subscription.py +5 -0
- {typecast_python-0.3.12 → typecast_python-0.3.14}/src/typecast/models/voices.py +25 -1
- {typecast_python-0.3.12 → typecast_python-0.3.14}/.gitignore +0 -0
- {typecast_python-0.3.12 → typecast_python-0.3.14}/LICENSE +0 -0
- {typecast_python-0.3.12 → typecast_python-0.3.14}/README.md +0 -0
- {typecast_python-0.3.12 → typecast_python-0.3.14}/src/typecast/__init__.py +0 -0
- {typecast_python-0.3.12 → typecast_python-0.3.14}/src/typecast/_httpx_compat.py +0 -0
- {typecast_python-0.3.12 → typecast_python-0.3.14}/src/typecast/composer.py +0 -0
- {typecast_python-0.3.12 → typecast_python-0.3.14}/src/typecast/conf.py +0 -0
- {typecast_python-0.3.12 → typecast_python-0.3.14}/src/typecast/exceptions.py +0 -0
- {typecast_python-0.3.12 → typecast_python-0.3.14}/src/typecast/models/error.py +0 -0
- {typecast_python-0.3.12 → typecast_python-0.3.14}/src/typecast/models/tts.py +0 -0
- {typecast_python-0.3.12 → typecast_python-0.3.14}/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.14
|
|
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
|
|
@@ -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.14"
|
|
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"}
|
|
@@ -19,10 +19,12 @@ def attribution_suffix(
|
|
|
19
19
|
if (
|
|
20
20
|
not isinstance(source, str)
|
|
21
21
|
or not isinstance(generated_by, str)
|
|
22
|
-
or source not in {"llms", "skill"}
|
|
22
|
+
or source not in {"llms", "skill", "api-page", "api-docs"}
|
|
23
23
|
or not generated_by
|
|
24
24
|
):
|
|
25
|
-
raise ValueError(
|
|
25
|
+
raise ValueError(
|
|
26
|
+
"source (llms, skill, api-page, or api-docs) and generated_by must be provided together"
|
|
27
|
+
)
|
|
26
28
|
if not _GENERATED_BY_PATTERN.fullmatch(generated_by):
|
|
27
29
|
raise ValueError("generated_by must be a lowercase token of at most 32 characters")
|
|
28
30
|
return f" typecast-integration/1 (source={source}; generated_by={generated_by})"
|
|
@@ -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
|
|
|
@@ -90,7 +94,7 @@ class AsyncTypecast:
|
|
|
90
94
|
__aenter__ will not create a new session and __aexit__ will not close it
|
|
91
95
|
(the caller owns its lifecycle). Auth headers (`X-API-KEY`, `User-Agent`)
|
|
92
96
|
are attached per-request via `_request_headers()`.
|
|
93
|
-
source: Integration source,
|
|
97
|
+
source: Integration source: 'llms', 'skill', 'api-page', or 'api-docs'.
|
|
94
98
|
generated_by: Lowercase token identifying the coding agent.
|
|
95
99
|
|
|
96
100
|
Raises:
|
|
@@ -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
|
|
|
@@ -124,7 +126,7 @@ class Typecast:
|
|
|
124
126
|
the client will not create a new session nor close it; auth headers
|
|
125
127
|
(`X-API-KEY`, `User-Agent`) are attached per-request via
|
|
126
128
|
`_request_headers()`.
|
|
127
|
-
source: Integration source,
|
|
129
|
+
source: Integration source: 'llms', 'skill', 'api-page', or 'api-docs'.
|
|
128
130
|
generated_by: Lowercase token identifying the coding agent.
|
|
129
131
|
|
|
130
132
|
Raises:
|
|
@@ -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):
|
|
@@ -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
|
|
File without changes
|