buildsol 0.1.1__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.
- buildsol-0.1.1/.gitignore +15 -0
- buildsol-0.1.1/CHANGELOG.md +15 -0
- buildsol-0.1.1/LICENSE +21 -0
- buildsol-0.1.1/PKG-INFO +134 -0
- buildsol-0.1.1/README.md +107 -0
- buildsol-0.1.1/pyproject.toml +25 -0
- buildsol-0.1.1/src/buildsol/__init__.py +37 -0
- buildsol-0.1.1/src/buildsol/_async_resources.py +254 -0
- buildsol-0.1.1/src/buildsol/_resources.py +224 -0
- buildsol-0.1.1/src/buildsol/_transport.py +286 -0
- buildsol-0.1.1/src/buildsol/audio.py +72 -0
- buildsol-0.1.1/src/buildsol/client.py +78 -0
- buildsol-0.1.1/src/buildsol/errors.py +69 -0
- buildsol-0.1.1/src/buildsol/live.py +171 -0
- buildsol-0.1.1/src/buildsol/py.typed +0 -0
- buildsol-0.1.1/src/buildsol/types.py +106 -0
- buildsol-0.1.1/tests/test_sdk.py +424 -0
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.1.1
|
|
4
|
+
|
|
5
|
+
- Prepare npm and PyPI distribution with standard registry installation instructions.
|
|
6
|
+
- No public API or runtime behavior changes from 0.1.0.
|
|
7
|
+
|
|
8
|
+
## 0.1.0
|
|
9
|
+
|
|
10
|
+
Initial Python and JavaScript/TypeScript SDK release:
|
|
11
|
+
- Incremental TTS streaming and file downloads, with audio controls preserved.
|
|
12
|
+
- Voice catalogue, consent-required cloning, deletion, controls, dialects and balance.
|
|
13
|
+
- File transcription (JSON/SSE), live WebSocket transcription and translation.
|
|
14
|
+
- Python sync/async clients; Node ESM/CommonJS and TypeScript declarations.
|
|
15
|
+
- Typed API errors, timeouts, cancellation, safe retries and explicit idempotency.
|
buildsol-0.1.1/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Sol
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
buildsol-0.1.1/PKG-INFO
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: buildsol
|
|
3
|
+
Version: 0.1.1
|
|
4
|
+
Summary: Official Sol SDK: streaming speech, voices, transcription and translation.
|
|
5
|
+
Project-URL: Homepage, https://www.buildsol.ai
|
|
6
|
+
Project-URL: Documentation, https://www.buildsol.ai/docs#sdks
|
|
7
|
+
Project-URL: Repository, https://github.com/hectordiazgomez/sol-sdks
|
|
8
|
+
Project-URL: Issues, https://github.com/hectordiazgomez/sol-sdks/issues
|
|
9
|
+
Author: Sol
|
|
10
|
+
License-Expression: MIT
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Classifier: Operating System :: OS Independent
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Typing :: Typed
|
|
15
|
+
Requires-Python: >=3.10
|
|
16
|
+
Requires-Dist: httpx<1,>=0.27
|
|
17
|
+
Requires-Dist: typing-extensions>=4.12
|
|
18
|
+
Requires-Dist: websockets<18,>=14
|
|
19
|
+
Provides-Extra: dev
|
|
20
|
+
Requires-Dist: build>=1.2; extra == 'dev'
|
|
21
|
+
Requires-Dist: mypy>=1.14; extra == 'dev'
|
|
22
|
+
Requires-Dist: pytest-asyncio>=0.24; extra == 'dev'
|
|
23
|
+
Requires-Dist: pytest>=8; extra == 'dev'
|
|
24
|
+
Requires-Dist: ruff>=0.9; extra == 'dev'
|
|
25
|
+
Requires-Dist: twine>=6; extra == 'dev'
|
|
26
|
+
Description-Content-Type: text/markdown
|
|
27
|
+
|
|
28
|
+
# Sol Python SDK
|
|
29
|
+
|
|
30
|
+
Official synchronous and asynchronous Python client. Requires Python 3.10+.
|
|
31
|
+
|
|
32
|
+
## Install
|
|
33
|
+
|
|
34
|
+
```sh
|
|
35
|
+
python -m pip install buildsol
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Install the official `buildsol` package from [PyPI](https://pypi.org/project/buildsol/). For development, clone this repository and run `pip install -e './python[dev]'`.
|
|
39
|
+
|
|
40
|
+
Set `SOL_API_KEY` on your server, then:
|
|
41
|
+
|
|
42
|
+
```python
|
|
43
|
+
from buildsol import Sol
|
|
44
|
+
|
|
45
|
+
with Sol() as sol:
|
|
46
|
+
voices = sol.voices.list(country="PE")
|
|
47
|
+
voice_id = voices["voices"][0]["id"] # handle an empty catalogue in your app
|
|
48
|
+
with sol.speech.stream(
|
|
49
|
+
text="Hola, tu pedido llega mañana.", voice_id=voice_id, speed=1.0
|
|
50
|
+
) as audio:
|
|
51
|
+
print(audio.request_id)
|
|
52
|
+
audio.save("speech.mp3")
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
`AudioStream` is an iterator of unchanged `bytes`. Always use its context manager so an early exit closes the connection. `speech.create()` returns `Audio` with `.content`, `.headers`, `.request_id` and `.save(path)` after downloading the full result. `reference_file=` supports multipart references; the JSON `reference_audio=` field is base64 text. Do not combine references with `voice_id`.
|
|
56
|
+
|
|
57
|
+
## Async and incremental bytes
|
|
58
|
+
|
|
59
|
+
```python
|
|
60
|
+
import asyncio
|
|
61
|
+
from buildsol import AsyncSol
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
async def main():
|
|
65
|
+
async with AsyncSol() as sol:
|
|
66
|
+
async with sol.speech.stream(text="Buenos días.", output_format="pcm") as audio:
|
|
67
|
+
async for chunk in audio:
|
|
68
|
+
print(len(chunk)) # forward to your own compatible playback pipeline
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
asyncio.run(main())
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## Transcription, translation and voices
|
|
75
|
+
|
|
76
|
+
```python
|
|
77
|
+
from buildsol import Sol
|
|
78
|
+
|
|
79
|
+
with Sol() as sol:
|
|
80
|
+
transcript = sol.transcriptions.create(
|
|
81
|
+
file="recording.wav",
|
|
82
|
+
language="es",
|
|
83
|
+
diarize=True,
|
|
84
|
+
transcription_style="verbatim",
|
|
85
|
+
idempotency_key="recording-2026-001",
|
|
86
|
+
)
|
|
87
|
+
print(transcript["text"], transcript["billing"])
|
|
88
|
+
translated = sol.translations.create(
|
|
89
|
+
text="Buenos días.",
|
|
90
|
+
source_language="es",
|
|
91
|
+
target_language="pt",
|
|
92
|
+
idempotency_key="translation-2026-001",
|
|
93
|
+
)
|
|
94
|
+
print(translated["translation"])
|
|
95
|
+
print(sol.controls.get(), sol.dialects.list(), sol.balance.get())
|
|
96
|
+
|
|
97
|
+
# Only with the speaker's permission; creates an account voice:
|
|
98
|
+
# voice = sol.voices.create(name="Mi voz", audio="reference.wav", consent=True)
|
|
99
|
+
# sol.voices.get(voice["voice"]["id"])
|
|
100
|
+
# sol.voices.delete(voice["voice"]["id"]) # irreversible
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
File inputs accept a path, `Path`, bytes (default filename `audio.wav`), an open binary file, `(filename, bytes)` or `(filename, bytes, content_type)`. Uploads are read into memory once, allowing safe idempotent replay of accepted small uploads. The SDK does not close caller-owned file objects.
|
|
104
|
+
|
|
105
|
+
File SSE:
|
|
106
|
+
|
|
107
|
+
```python
|
|
108
|
+
with Sol() as sol:
|
|
109
|
+
with sol.transcriptions.stream(file="recording.wav", language="es") as events:
|
|
110
|
+
for event in events:
|
|
111
|
+
print(event) # segment, metadata, completed; an error raises APIError
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
This does not make file transcription real-time. Use a live session for microphone input. See [live_python.py](https://github.com/hectordiazgomez/sol-sdks/blob/main/examples/live_python.py) for concurrent sending, real-time pacing and finalization. `LiveConnection` and `AsyncLiveConnection` can also consume a pre-issued single-use session. Live receive timeout defaults to 30 seconds; close the connection when done. No token is reused or reconnected automatically.
|
|
115
|
+
|
|
116
|
+
## Configuration and errors
|
|
117
|
+
|
|
118
|
+
```python
|
|
119
|
+
from buildsol import Sol, APIError, ConnectionError, TimeoutError
|
|
120
|
+
|
|
121
|
+
with Sol(timeout=120.0, max_retries=2) as sol:
|
|
122
|
+
try:
|
|
123
|
+
balance = sol.balance.get(timeout=15.0)
|
|
124
|
+
except APIError as error:
|
|
125
|
+
print(error.status_code, error.code, error.request_id)
|
|
126
|
+
except TimeoutError:
|
|
127
|
+
print("Request timed out")
|
|
128
|
+
except ConnectionError:
|
|
129
|
+
print("Connection interrupted")
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
Optional `api_key=`, `base_url=` and `http_client=` configure the client. HTTPX's timeout is a per-network-operation timeout (connect/read/write/pool), in seconds. Cancel an async task normally; `async with` releases resources. `AuthenticationError`, `PermissionDeniedError`, `NotFoundError` and `RateLimitError` subclass `APIError`; `.details` and `.headers` retain server information. `ProtocolError` reports malformed server events/JSON. Never log secrets or full audio.
|
|
133
|
+
|
|
134
|
+
Retries are limited to GET or explicit-idempotency transcription/translation requests; **TTS is never automatically retried**. Partial-stream failures preserve the partial file and raise an error; the caller should remove or label incomplete files. See the repository README for full API coverage, limits, token rules and billing distinctions.
|
buildsol-0.1.1/README.md
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
# Sol Python SDK
|
|
2
|
+
|
|
3
|
+
Official synchronous and asynchronous Python client. Requires Python 3.10+.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```sh
|
|
8
|
+
python -m pip install buildsol
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Install the official `buildsol` package from [PyPI](https://pypi.org/project/buildsol/). For development, clone this repository and run `pip install -e './python[dev]'`.
|
|
12
|
+
|
|
13
|
+
Set `SOL_API_KEY` on your server, then:
|
|
14
|
+
|
|
15
|
+
```python
|
|
16
|
+
from buildsol import Sol
|
|
17
|
+
|
|
18
|
+
with Sol() as sol:
|
|
19
|
+
voices = sol.voices.list(country="PE")
|
|
20
|
+
voice_id = voices["voices"][0]["id"] # handle an empty catalogue in your app
|
|
21
|
+
with sol.speech.stream(
|
|
22
|
+
text="Hola, tu pedido llega mañana.", voice_id=voice_id, speed=1.0
|
|
23
|
+
) as audio:
|
|
24
|
+
print(audio.request_id)
|
|
25
|
+
audio.save("speech.mp3")
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
`AudioStream` is an iterator of unchanged `bytes`. Always use its context manager so an early exit closes the connection. `speech.create()` returns `Audio` with `.content`, `.headers`, `.request_id` and `.save(path)` after downloading the full result. `reference_file=` supports multipart references; the JSON `reference_audio=` field is base64 text. Do not combine references with `voice_id`.
|
|
29
|
+
|
|
30
|
+
## Async and incremental bytes
|
|
31
|
+
|
|
32
|
+
```python
|
|
33
|
+
import asyncio
|
|
34
|
+
from buildsol import AsyncSol
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
async def main():
|
|
38
|
+
async with AsyncSol() as sol:
|
|
39
|
+
async with sol.speech.stream(text="Buenos días.", output_format="pcm") as audio:
|
|
40
|
+
async for chunk in audio:
|
|
41
|
+
print(len(chunk)) # forward to your own compatible playback pipeline
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
asyncio.run(main())
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Transcription, translation and voices
|
|
48
|
+
|
|
49
|
+
```python
|
|
50
|
+
from buildsol import Sol
|
|
51
|
+
|
|
52
|
+
with Sol() as sol:
|
|
53
|
+
transcript = sol.transcriptions.create(
|
|
54
|
+
file="recording.wav",
|
|
55
|
+
language="es",
|
|
56
|
+
diarize=True,
|
|
57
|
+
transcription_style="verbatim",
|
|
58
|
+
idempotency_key="recording-2026-001",
|
|
59
|
+
)
|
|
60
|
+
print(transcript["text"], transcript["billing"])
|
|
61
|
+
translated = sol.translations.create(
|
|
62
|
+
text="Buenos días.",
|
|
63
|
+
source_language="es",
|
|
64
|
+
target_language="pt",
|
|
65
|
+
idempotency_key="translation-2026-001",
|
|
66
|
+
)
|
|
67
|
+
print(translated["translation"])
|
|
68
|
+
print(sol.controls.get(), sol.dialects.list(), sol.balance.get())
|
|
69
|
+
|
|
70
|
+
# Only with the speaker's permission; creates an account voice:
|
|
71
|
+
# voice = sol.voices.create(name="Mi voz", audio="reference.wav", consent=True)
|
|
72
|
+
# sol.voices.get(voice["voice"]["id"])
|
|
73
|
+
# sol.voices.delete(voice["voice"]["id"]) # irreversible
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
File inputs accept a path, `Path`, bytes (default filename `audio.wav`), an open binary file, `(filename, bytes)` or `(filename, bytes, content_type)`. Uploads are read into memory once, allowing safe idempotent replay of accepted small uploads. The SDK does not close caller-owned file objects.
|
|
77
|
+
|
|
78
|
+
File SSE:
|
|
79
|
+
|
|
80
|
+
```python
|
|
81
|
+
with Sol() as sol:
|
|
82
|
+
with sol.transcriptions.stream(file="recording.wav", language="es") as events:
|
|
83
|
+
for event in events:
|
|
84
|
+
print(event) # segment, metadata, completed; an error raises APIError
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
This does not make file transcription real-time. Use a live session for microphone input. See [live_python.py](https://github.com/hectordiazgomez/sol-sdks/blob/main/examples/live_python.py) for concurrent sending, real-time pacing and finalization. `LiveConnection` and `AsyncLiveConnection` can also consume a pre-issued single-use session. Live receive timeout defaults to 30 seconds; close the connection when done. No token is reused or reconnected automatically.
|
|
88
|
+
|
|
89
|
+
## Configuration and errors
|
|
90
|
+
|
|
91
|
+
```python
|
|
92
|
+
from buildsol import Sol, APIError, ConnectionError, TimeoutError
|
|
93
|
+
|
|
94
|
+
with Sol(timeout=120.0, max_retries=2) as sol:
|
|
95
|
+
try:
|
|
96
|
+
balance = sol.balance.get(timeout=15.0)
|
|
97
|
+
except APIError as error:
|
|
98
|
+
print(error.status_code, error.code, error.request_id)
|
|
99
|
+
except TimeoutError:
|
|
100
|
+
print("Request timed out")
|
|
101
|
+
except ConnectionError:
|
|
102
|
+
print("Connection interrupted")
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
Optional `api_key=`, `base_url=` and `http_client=` configure the client. HTTPX's timeout is a per-network-operation timeout (connect/read/write/pool), in seconds. Cancel an async task normally; `async with` releases resources. `AuthenticationError`, `PermissionDeniedError`, `NotFoundError` and `RateLimitError` subclass `APIError`; `.details` and `.headers` retain server information. `ProtocolError` reports malformed server events/JSON. Never log secrets or full audio.
|
|
106
|
+
|
|
107
|
+
Retries are limited to GET or explicit-idempotency transcription/translation requests; **TTS is never automatically retried**. Partial-stream failures preserve the partial file and raise an error; the caller should remove or label incomplete files. See the repository README for full API coverage, limits, token rules and billing distinctions.
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling>=1.26"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
[project]
|
|
5
|
+
name = "buildsol"
|
|
6
|
+
version = "0.1.1"
|
|
7
|
+
description = "Official Sol SDK: streaming speech, voices, transcription and translation."
|
|
8
|
+
readme = "README.md"
|
|
9
|
+
requires-python = ">=3.10"
|
|
10
|
+
license = "MIT"
|
|
11
|
+
authors = [{name = "Sol"}]
|
|
12
|
+
dependencies = ["httpx>=0.27,<1", "websockets>=14,<18", "typing-extensions>=4.12"]
|
|
13
|
+
classifiers = ["Programming Language :: Python :: 3", "Typing :: Typed", "Operating System :: OS Independent"]
|
|
14
|
+
[project.urls]
|
|
15
|
+
Homepage = "https://www.buildsol.ai"
|
|
16
|
+
Documentation = "https://www.buildsol.ai/docs#sdks"
|
|
17
|
+
Repository = "https://github.com/hectordiazgomez/sol-sdks"
|
|
18
|
+
Issues = "https://github.com/hectordiazgomez/sol-sdks/issues"
|
|
19
|
+
[tool.hatch.build.targets.wheel]
|
|
20
|
+
packages = ["src/buildsol"]
|
|
21
|
+
[project.optional-dependencies]
|
|
22
|
+
dev = ["pytest>=8", "pytest-asyncio>=0.24", "build>=1.2", "twine>=6", "ruff>=0.9", "mypy>=1.14"]
|
|
23
|
+
[tool.pytest.ini_options]
|
|
24
|
+
asyncio_mode = "auto"
|
|
25
|
+
asyncio_default_fixture_loop_scope = "function"
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"""Official Sol Python SDK. Keep your API key on your server."""
|
|
2
|
+
|
|
3
|
+
from ._transport import VERSION as __version__
|
|
4
|
+
from .audio import AsyncAudioStream, Audio, AudioStream
|
|
5
|
+
from .client import AsyncSol, Sol
|
|
6
|
+
from .errors import (
|
|
7
|
+
APIError,
|
|
8
|
+
AuthenticationError,
|
|
9
|
+
ConnectionError,
|
|
10
|
+
NotFoundError,
|
|
11
|
+
PermissionDeniedError,
|
|
12
|
+
ProtocolError,
|
|
13
|
+
RateLimitError,
|
|
14
|
+
SolError,
|
|
15
|
+
TimeoutError,
|
|
16
|
+
)
|
|
17
|
+
from .live import AsyncLiveConnection, LiveConnection
|
|
18
|
+
|
|
19
|
+
__all__ = [
|
|
20
|
+
"Sol",
|
|
21
|
+
"AsyncSol",
|
|
22
|
+
"Audio",
|
|
23
|
+
"AudioStream",
|
|
24
|
+
"AsyncAudioStream",
|
|
25
|
+
"SolError",
|
|
26
|
+
"APIError",
|
|
27
|
+
"AuthenticationError",
|
|
28
|
+
"PermissionDeniedError",
|
|
29
|
+
"NotFoundError",
|
|
30
|
+
"RateLimitError",
|
|
31
|
+
"ConnectionError",
|
|
32
|
+
"TimeoutError",
|
|
33
|
+
"ProtocolError",
|
|
34
|
+
"LiveConnection",
|
|
35
|
+
"AsyncLiveConnection",
|
|
36
|
+
"__version__",
|
|
37
|
+
]
|
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
from contextlib import asynccontextmanager
|
|
2
|
+
from typing import Any
|
|
3
|
+
from urllib.parse import quote
|
|
4
|
+
|
|
5
|
+
import httpx
|
|
6
|
+
from typing_extensions import Unpack
|
|
7
|
+
|
|
8
|
+
from ._transport import Transport, event, form, transport_error, upload
|
|
9
|
+
from .audio import AsyncAudioStream, Audio
|
|
10
|
+
from .types import (
|
|
11
|
+
Balance,
|
|
12
|
+
LiveSession,
|
|
13
|
+
SpeechParams,
|
|
14
|
+
Transcription,
|
|
15
|
+
TranscriptionParams,
|
|
16
|
+
Translation,
|
|
17
|
+
Upload,
|
|
18
|
+
VoiceList,
|
|
19
|
+
VoiceParams,
|
|
20
|
+
VoiceResult,
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class Resource:
|
|
25
|
+
def __init__(self, transport: Transport):
|
|
26
|
+
self._t = transport
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class Speech(Resource):
|
|
30
|
+
@asynccontextmanager
|
|
31
|
+
async def stream(
|
|
32
|
+
self,
|
|
33
|
+
*,
|
|
34
|
+
reference_file: Upload | None = None,
|
|
35
|
+
timeout: float | None = None,
|
|
36
|
+
**params: Unpack[SpeechParams],
|
|
37
|
+
):
|
|
38
|
+
kwargs = (
|
|
39
|
+
{"json": params}
|
|
40
|
+
if reference_file is None
|
|
41
|
+
else {
|
|
42
|
+
"data": form(params),
|
|
43
|
+
"files": {"reference_audio": upload(reference_file)},
|
|
44
|
+
}
|
|
45
|
+
)
|
|
46
|
+
response = await self._t.arequest(
|
|
47
|
+
"POST", "/v1/text-to-speech", timeout=timeout, **kwargs
|
|
48
|
+
)
|
|
49
|
+
try:
|
|
50
|
+
yield AsyncAudioStream(response)
|
|
51
|
+
finally:
|
|
52
|
+
await response.aclose()
|
|
53
|
+
|
|
54
|
+
async def create(
|
|
55
|
+
self,
|
|
56
|
+
*,
|
|
57
|
+
reference_file: Upload | None = None,
|
|
58
|
+
timeout: float | None = None,
|
|
59
|
+
**params: Unpack[SpeechParams],
|
|
60
|
+
) -> Audio:
|
|
61
|
+
async with self.stream(
|
|
62
|
+
reference_file=reference_file, timeout=timeout, **params
|
|
63
|
+
) as stream:
|
|
64
|
+
content = b"".join([chunk async for chunk in stream])
|
|
65
|
+
return Audio(content, stream.headers)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
class Voices(Resource):
|
|
69
|
+
async def list(
|
|
70
|
+
self,
|
|
71
|
+
*,
|
|
72
|
+
country: str | None = None,
|
|
73
|
+
gender: str | None = None,
|
|
74
|
+
age: str | None = None,
|
|
75
|
+
category: str | None = None,
|
|
76
|
+
timeout: float | None = None,
|
|
77
|
+
) -> VoiceList:
|
|
78
|
+
return await self._t.ajson(
|
|
79
|
+
"GET",
|
|
80
|
+
"/v1/voices",
|
|
81
|
+
params=form(
|
|
82
|
+
{"country": country, "gender": gender, "age": age, "category": category}
|
|
83
|
+
),
|
|
84
|
+
timeout=timeout,
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
async def get(self, voice_id: str, *, timeout: float | None = None) -> VoiceResult:
|
|
88
|
+
return await self._t.ajson(
|
|
89
|
+
"GET", "/v1/voices/" + quote(voice_id, safe=""), timeout=timeout
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
async def create(
|
|
93
|
+
self,
|
|
94
|
+
*,
|
|
95
|
+
name: str,
|
|
96
|
+
audio: Upload,
|
|
97
|
+
consent: bool,
|
|
98
|
+
timeout: float | None = None,
|
|
99
|
+
**params: Unpack[VoiceParams],
|
|
100
|
+
) -> VoiceResult:
|
|
101
|
+
return await self._t.ajson(
|
|
102
|
+
"POST",
|
|
103
|
+
"/v1/voices",
|
|
104
|
+
data=form({"name": name, "consent": consent, **params}),
|
|
105
|
+
files={"audio": upload(audio)},
|
|
106
|
+
timeout=timeout,
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
async def delete(
|
|
110
|
+
self, voice_id: str, *, timeout: float | None = None
|
|
111
|
+
) -> dict[str, bool]:
|
|
112
|
+
return await self._t.ajson(
|
|
113
|
+
"DELETE", "/v1/voices/" + quote(voice_id, safe=""), timeout=timeout
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
class Controls(Resource):
|
|
118
|
+
async def get(self, *, timeout: float | None = None) -> dict[str, Any]:
|
|
119
|
+
return await self._t.ajson("GET", "/v1/tts/controls", timeout=timeout)
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
class Dialects(Resource):
|
|
123
|
+
async def list(self, *, timeout: float | None = None) -> dict[str, Any]:
|
|
124
|
+
return await self._t.ajson("GET", "/v1/dialects", timeout=timeout)
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
class BalanceResource(Resource):
|
|
128
|
+
async def get(self, *, timeout: float | None = None) -> Balance:
|
|
129
|
+
return await self._t.ajson("GET", "/v1/balance", timeout=timeout)
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
class Transcriptions(Resource):
|
|
133
|
+
async def create(
|
|
134
|
+
self,
|
|
135
|
+
*,
|
|
136
|
+
file: Upload,
|
|
137
|
+
idempotency_key: str | None = None,
|
|
138
|
+
timeout: float | None = None,
|
|
139
|
+
**params: Unpack[TranscriptionParams],
|
|
140
|
+
) -> Transcription:
|
|
141
|
+
return await self._t.ajson(
|
|
142
|
+
"POST",
|
|
143
|
+
"/v1/audio/transcriptions",
|
|
144
|
+
data=form(params),
|
|
145
|
+
files={"file": upload(file)},
|
|
146
|
+
idempotency_key=idempotency_key,
|
|
147
|
+
timeout=timeout,
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
@asynccontextmanager
|
|
151
|
+
async def stream(
|
|
152
|
+
self,
|
|
153
|
+
*,
|
|
154
|
+
file: Upload,
|
|
155
|
+
idempotency_key: str | None = None,
|
|
156
|
+
timeout: float | None = None,
|
|
157
|
+
**params: Unpack[TranscriptionParams],
|
|
158
|
+
):
|
|
159
|
+
"""SSE transport for a processed file; use live.connect for real-time audio."""
|
|
160
|
+
response = await self._t.arequest(
|
|
161
|
+
"POST",
|
|
162
|
+
"/v1/audio/transcriptions",
|
|
163
|
+
data=form({**params, "stream": True}),
|
|
164
|
+
files={"file": upload(file)},
|
|
165
|
+
idempotency_key=idempotency_key,
|
|
166
|
+
timeout=timeout,
|
|
167
|
+
)
|
|
168
|
+
try:
|
|
169
|
+
yield _sse(response)
|
|
170
|
+
finally:
|
|
171
|
+
await response.aclose()
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
class Translations(Resource):
|
|
175
|
+
async def create(
|
|
176
|
+
self,
|
|
177
|
+
*,
|
|
178
|
+
text: str,
|
|
179
|
+
source_language: str,
|
|
180
|
+
target_language: str,
|
|
181
|
+
model: str = "sol-v1-preview-translate",
|
|
182
|
+
idempotency_key: str | None = None,
|
|
183
|
+
timeout: float | None = None,
|
|
184
|
+
) -> Translation:
|
|
185
|
+
return await self._t.ajson(
|
|
186
|
+
"POST",
|
|
187
|
+
"/v1/translate",
|
|
188
|
+
json={
|
|
189
|
+
"text": text,
|
|
190
|
+
"source_language": source_language,
|
|
191
|
+
"target_language": target_language,
|
|
192
|
+
"model": model,
|
|
193
|
+
},
|
|
194
|
+
idempotency_key=idempotency_key,
|
|
195
|
+
timeout=timeout,
|
|
196
|
+
)
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
class Live(Resource):
|
|
200
|
+
async def create_session(
|
|
201
|
+
self,
|
|
202
|
+
*,
|
|
203
|
+
language: str | None = None,
|
|
204
|
+
model: str = "sol-transcribe-v1",
|
|
205
|
+
timeout: float | None = None,
|
|
206
|
+
) -> LiveSession:
|
|
207
|
+
return await self._t.ajson(
|
|
208
|
+
"POST",
|
|
209
|
+
"/v1/audio/transcription-sessions",
|
|
210
|
+
json=form({"model": model, "language": language}),
|
|
211
|
+
timeout=timeout,
|
|
212
|
+
)
|
|
213
|
+
|
|
214
|
+
@asynccontextmanager
|
|
215
|
+
async def connect(
|
|
216
|
+
self,
|
|
217
|
+
*,
|
|
218
|
+
session: LiveSession | None = None,
|
|
219
|
+
language: str | None = None,
|
|
220
|
+
timeout: float = 30,
|
|
221
|
+
):
|
|
222
|
+
from .live import AsyncLiveConnection
|
|
223
|
+
|
|
224
|
+
session = session or await self.create_session(
|
|
225
|
+
language=language, timeout=timeout
|
|
226
|
+
)
|
|
227
|
+
connection = AsyncLiveConnection(session, timeout=timeout)
|
|
228
|
+
async with connection:
|
|
229
|
+
yield connection
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
async def _sse(response):
|
|
233
|
+
data = []
|
|
234
|
+
completed = False
|
|
235
|
+
try:
|
|
236
|
+
async for line in response.aiter_lines():
|
|
237
|
+
if line == "":
|
|
238
|
+
if data:
|
|
239
|
+
value = event("\n".join(data))
|
|
240
|
+
completed |= value.get("type") == "completed"
|
|
241
|
+
yield value
|
|
242
|
+
data = []
|
|
243
|
+
elif line.startswith("data:"):
|
|
244
|
+
data.append(line[5:].removeprefix(" "))
|
|
245
|
+
if data:
|
|
246
|
+
value = event("\n".join(data))
|
|
247
|
+
completed |= value.get("type") == "completed"
|
|
248
|
+
yield value
|
|
249
|
+
if not completed:
|
|
250
|
+
from .errors import ProtocolError
|
|
251
|
+
|
|
252
|
+
raise ProtocolError("Transcription stream ended without a completed event")
|
|
253
|
+
except httpx.TransportError as exc:
|
|
254
|
+
raise transport_error(exc) from exc
|