smallestai 2.1.0__py3-none-any.whl → 3.0.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.
Potentially problematic release.
This version of smallestai might be problematic. Click here for more details.
- smallestai/__init__.py +95 -0
- smallestai/atoms/__init__.py +182 -0
- smallestai/atoms/api/__init__.py +12 -0
- smallestai/atoms/api/agent_templates_api.py +573 -0
- smallestai/atoms/api/agents_api.py +1465 -0
- smallestai/atoms/api/calls_api.py +320 -0
- smallestai/atoms/api/campaigns_api.py +1689 -0
- smallestai/atoms/api/knowledge_base_api.py +2271 -0
- smallestai/atoms/api/logs_api.py +305 -0
- smallestai/atoms/api/organization_api.py +285 -0
- smallestai/atoms/api/user_api.py +285 -0
- smallestai/atoms/api_client.py +797 -0
- smallestai/atoms/api_response.py +21 -0
- smallestai/atoms/atoms_client.py +560 -0
- smallestai/atoms/configuration.py +582 -0
- smallestai/atoms/exceptions.py +216 -0
- smallestai/atoms/models/__init__.py +72 -0
- smallestai/atoms/models/agent_dto.py +130 -0
- smallestai/atoms/models/agent_dto_language.py +91 -0
- smallestai/atoms/models/agent_dto_synthesizer.py +99 -0
- smallestai/atoms/models/agent_dto_synthesizer_voice_config.py +111 -0
- smallestai/atoms/models/api_response.py +89 -0
- smallestai/atoms/models/bad_request_error_response.py +89 -0
- smallestai/atoms/models/create_agent_from_template200_response.py +89 -0
- smallestai/atoms/models/create_agent_from_template_request.py +91 -0
- smallestai/atoms/models/create_agent_request.py +113 -0
- smallestai/atoms/models/create_agent_request_language.py +124 -0
- smallestai/atoms/models/create_agent_request_language_synthesizer.py +110 -0
- smallestai/atoms/models/create_agent_request_language_synthesizer_voice_config.py +137 -0
- smallestai/atoms/models/create_campaign200_response.py +93 -0
- smallestai/atoms/models/create_campaign200_response_data.py +106 -0
- smallestai/atoms/models/create_campaign200_response_inner.py +106 -0
- smallestai/atoms/models/create_campaign201_response.py +93 -0
- smallestai/atoms/models/create_campaign201_response_data.py +104 -0
- smallestai/atoms/models/create_campaign_request.py +93 -0
- smallestai/atoms/models/create_knowledge_base201_response.py +89 -0
- smallestai/atoms/models/create_knowledge_base_request.py +89 -0
- smallestai/atoms/models/delete_agent200_response.py +87 -0
- smallestai/atoms/models/get_agent_by_id200_response.py +93 -0
- smallestai/atoms/models/get_agent_templates200_response.py +97 -0
- smallestai/atoms/models/get_agent_templates200_response_data_inner.py +97 -0
- smallestai/atoms/models/get_agents200_response.py +93 -0
- smallestai/atoms/models/get_agents200_response_data.py +101 -0
- smallestai/atoms/models/get_campaign_by_id200_response.py +93 -0
- smallestai/atoms/models/get_campaign_by_id200_response_data.py +114 -0
- smallestai/atoms/models/get_campaigns200_response.py +97 -0
- smallestai/atoms/models/get_campaigns200_response_data_inner.py +118 -0
- smallestai/atoms/models/get_campaigns200_response_data_inner_agent.py +89 -0
- smallestai/atoms/models/get_campaigns200_response_data_inner_audience.py +89 -0
- smallestai/atoms/models/get_campaigns_request.py +89 -0
- smallestai/atoms/models/get_conversation200_response.py +93 -0
- smallestai/atoms/models/get_conversation200_response_data.py +125 -0
- smallestai/atoms/models/get_conversation_logs200_response.py +93 -0
- smallestai/atoms/models/get_conversation_logs200_response_data.py +125 -0
- smallestai/atoms/models/get_current_user200_response.py +93 -0
- smallestai/atoms/models/get_current_user200_response_data.py +99 -0
- smallestai/atoms/models/get_knowledge_base_by_id200_response.py +93 -0
- smallestai/atoms/models/get_knowledge_base_items200_response.py +97 -0
- smallestai/atoms/models/get_knowledge_bases200_response.py +97 -0
- smallestai/atoms/models/get_organization200_response.py +93 -0
- smallestai/atoms/models/get_organization200_response_data.py +105 -0
- smallestai/atoms/models/get_organization200_response_data_members_inner.py +89 -0
- smallestai/atoms/models/get_organization200_response_data_subscription.py +87 -0
- smallestai/atoms/models/internal_server_error_response.py +89 -0
- smallestai/atoms/models/knowledge_base_dto.py +93 -0
- smallestai/atoms/models/knowledge_base_item_dto.py +124 -0
- smallestai/atoms/models/start_outbound_call200_response.py +93 -0
- smallestai/atoms/models/start_outbound_call200_response_data.py +87 -0
- smallestai/atoms/models/start_outbound_call_request.py +89 -0
- smallestai/atoms/models/unauthorized_error_reponse.py +89 -0
- smallestai/atoms/models/update_agent200_response.py +89 -0
- smallestai/atoms/models/update_agent_request.py +119 -0
- smallestai/atoms/models/update_agent_request_language.py +99 -0
- smallestai/atoms/models/update_agent_request_synthesizer.py +110 -0
- smallestai/atoms/models/update_agent_request_synthesizer_voice_config.py +137 -0
- smallestai/atoms/models/update_agent_request_synthesizer_voice_config_one_of.py +111 -0
- smallestai/atoms/models/update_agent_request_synthesizer_voice_config_one_of1.py +99 -0
- smallestai/atoms/models/upload_text_to_knowledge_base_request.py +89 -0
- smallestai/atoms/py.typed +0 -0
- smallestai/atoms/rest.py +258 -0
- smallestai/waves/__init__.py +5 -0
- smallest/async_tts.py → smallestai/waves/async_waves_client.py +60 -47
- smallestai/waves/stream_tts.py +272 -0
- {smallest → smallestai/waves}/utils.py +8 -8
- smallest/tts.py → smallestai/waves/waves_client.py +58 -46
- {smallestai-2.1.0.dist-info → smallestai-3.0.0.dist-info}/METADATA +194 -43
- smallestai-3.0.0.dist-info/RECORD +92 -0
- {smallestai-2.1.0.dist-info → smallestai-3.0.0.dist-info}/WHEEL +1 -1
- smallestai-3.0.0.dist-info/top_level.txt +1 -0
- smallest/__init__.py +0 -5
- smallest/stream_tts.py +0 -161
- smallestai-2.1.0.dist-info/RECORD +0 -12
- smallestai-2.1.0.dist-info/top_level.txt +0 -1
- {smallest → smallestai/waves}/exceptions.py +0 -0
- {smallest → smallestai/waves}/models.py +0 -0
- {smallestai-2.1.0.dist-info → smallestai-3.0.0.dist-info/licenses}/LICENSE +0 -0
smallestai/atoms/rest.py
ADDED
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
# coding: utf-8
|
|
2
|
+
|
|
3
|
+
"""
|
|
4
|
+
Agent Management API
|
|
5
|
+
|
|
6
|
+
API for managing agents, their templates, and call logs
|
|
7
|
+
|
|
8
|
+
The version of the OpenAPI document: 1.0.0
|
|
9
|
+
Generated by OpenAPI Generator (https://openapi-generator.tech)
|
|
10
|
+
|
|
11
|
+
Do not edit the class manually.
|
|
12
|
+
""" # noqa: E501
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
import io
|
|
16
|
+
import json
|
|
17
|
+
import re
|
|
18
|
+
import ssl
|
|
19
|
+
|
|
20
|
+
import urllib3
|
|
21
|
+
|
|
22
|
+
from smallestai.atoms.exceptions import ApiException, ApiValueError
|
|
23
|
+
|
|
24
|
+
SUPPORTED_SOCKS_PROXIES = {"socks5", "socks5h", "socks4", "socks4a"}
|
|
25
|
+
RESTResponseType = urllib3.HTTPResponse
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def is_socks_proxy_url(url):
|
|
29
|
+
if url is None:
|
|
30
|
+
return False
|
|
31
|
+
split_section = url.split("://")
|
|
32
|
+
if len(split_section) < 2:
|
|
33
|
+
return False
|
|
34
|
+
else:
|
|
35
|
+
return split_section[0].lower() in SUPPORTED_SOCKS_PROXIES
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class RESTResponse(io.IOBase):
|
|
39
|
+
|
|
40
|
+
def __init__(self, resp) -> None:
|
|
41
|
+
self.response = resp
|
|
42
|
+
self.status = resp.status
|
|
43
|
+
self.reason = resp.reason
|
|
44
|
+
self.data = None
|
|
45
|
+
|
|
46
|
+
def read(self):
|
|
47
|
+
if self.data is None:
|
|
48
|
+
self.data = self.response.data
|
|
49
|
+
return self.data
|
|
50
|
+
|
|
51
|
+
def getheaders(self):
|
|
52
|
+
"""Returns a dictionary of the response headers."""
|
|
53
|
+
return self.response.headers
|
|
54
|
+
|
|
55
|
+
def getheader(self, name, default=None):
|
|
56
|
+
"""Returns a given response header."""
|
|
57
|
+
return self.response.headers.get(name, default)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
class RESTClientObject:
|
|
61
|
+
|
|
62
|
+
def __init__(self, configuration) -> None:
|
|
63
|
+
# urllib3.PoolManager will pass all kw parameters to connectionpool
|
|
64
|
+
# https://github.com/shazow/urllib3/blob/f9409436f83aeb79fbaf090181cd81b784f1b8ce/urllib3/poolmanager.py#L75 # noqa: E501
|
|
65
|
+
# https://github.com/shazow/urllib3/blob/f9409436f83aeb79fbaf090181cd81b784f1b8ce/urllib3/connectionpool.py#L680 # noqa: E501
|
|
66
|
+
# Custom SSL certificates and client certificates: http://urllib3.readthedocs.io/en/latest/advanced-usage.html # noqa: E501
|
|
67
|
+
|
|
68
|
+
# cert_reqs
|
|
69
|
+
if configuration.verify_ssl:
|
|
70
|
+
cert_reqs = ssl.CERT_REQUIRED
|
|
71
|
+
else:
|
|
72
|
+
cert_reqs = ssl.CERT_NONE
|
|
73
|
+
|
|
74
|
+
pool_args = {
|
|
75
|
+
"cert_reqs": cert_reqs,
|
|
76
|
+
"ca_certs": configuration.ssl_ca_cert,
|
|
77
|
+
"cert_file": configuration.cert_file,
|
|
78
|
+
"key_file": configuration.key_file,
|
|
79
|
+
"ca_cert_data": configuration.ca_cert_data,
|
|
80
|
+
}
|
|
81
|
+
if configuration.assert_hostname is not None:
|
|
82
|
+
pool_args['assert_hostname'] = (
|
|
83
|
+
configuration.assert_hostname
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
if configuration.retries is not None:
|
|
87
|
+
pool_args['retries'] = configuration.retries
|
|
88
|
+
|
|
89
|
+
if configuration.tls_server_name:
|
|
90
|
+
pool_args['server_hostname'] = configuration.tls_server_name
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
if configuration.socket_options is not None:
|
|
94
|
+
pool_args['socket_options'] = configuration.socket_options
|
|
95
|
+
|
|
96
|
+
if configuration.connection_pool_maxsize is not None:
|
|
97
|
+
pool_args['maxsize'] = configuration.connection_pool_maxsize
|
|
98
|
+
|
|
99
|
+
# https pool manager
|
|
100
|
+
self.pool_manager: urllib3.PoolManager
|
|
101
|
+
|
|
102
|
+
if configuration.proxy:
|
|
103
|
+
if is_socks_proxy_url(configuration.proxy):
|
|
104
|
+
from urllib3.contrib.socks import SOCKSProxyManager
|
|
105
|
+
pool_args["proxy_url"] = configuration.proxy
|
|
106
|
+
pool_args["headers"] = configuration.proxy_headers
|
|
107
|
+
self.pool_manager = SOCKSProxyManager(**pool_args)
|
|
108
|
+
else:
|
|
109
|
+
pool_args["proxy_url"] = configuration.proxy
|
|
110
|
+
pool_args["proxy_headers"] = configuration.proxy_headers
|
|
111
|
+
self.pool_manager = urllib3.ProxyManager(**pool_args)
|
|
112
|
+
else:
|
|
113
|
+
self.pool_manager = urllib3.PoolManager(**pool_args)
|
|
114
|
+
|
|
115
|
+
def request(
|
|
116
|
+
self,
|
|
117
|
+
method,
|
|
118
|
+
url,
|
|
119
|
+
headers=None,
|
|
120
|
+
body=None,
|
|
121
|
+
post_params=None,
|
|
122
|
+
_request_timeout=None
|
|
123
|
+
):
|
|
124
|
+
"""Perform requests.
|
|
125
|
+
|
|
126
|
+
:param method: http request method
|
|
127
|
+
:param url: http request url
|
|
128
|
+
:param headers: http request headers
|
|
129
|
+
:param body: request json body, for `application/json`
|
|
130
|
+
:param post_params: request post parameters,
|
|
131
|
+
`application/x-www-form-urlencoded`
|
|
132
|
+
and `multipart/form-data`
|
|
133
|
+
:param _request_timeout: timeout setting for this request. If one
|
|
134
|
+
number provided, it will be total request
|
|
135
|
+
timeout. It can also be a pair (tuple) of
|
|
136
|
+
(connection, read) timeouts.
|
|
137
|
+
"""
|
|
138
|
+
method = method.upper()
|
|
139
|
+
assert method in [
|
|
140
|
+
'GET',
|
|
141
|
+
'HEAD',
|
|
142
|
+
'DELETE',
|
|
143
|
+
'POST',
|
|
144
|
+
'PUT',
|
|
145
|
+
'PATCH',
|
|
146
|
+
'OPTIONS'
|
|
147
|
+
]
|
|
148
|
+
|
|
149
|
+
if post_params and body:
|
|
150
|
+
raise ApiValueError(
|
|
151
|
+
"body parameter cannot be used with post_params parameter."
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
post_params = post_params or {}
|
|
155
|
+
headers = headers or {}
|
|
156
|
+
|
|
157
|
+
timeout = None
|
|
158
|
+
if _request_timeout:
|
|
159
|
+
if isinstance(_request_timeout, (int, float)):
|
|
160
|
+
timeout = urllib3.Timeout(total=_request_timeout)
|
|
161
|
+
elif (
|
|
162
|
+
isinstance(_request_timeout, tuple)
|
|
163
|
+
and len(_request_timeout) == 2
|
|
164
|
+
):
|
|
165
|
+
timeout = urllib3.Timeout(
|
|
166
|
+
connect=_request_timeout[0],
|
|
167
|
+
read=_request_timeout[1]
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
try:
|
|
171
|
+
# For `POST`, `PUT`, `PATCH`, `OPTIONS`, `DELETE`
|
|
172
|
+
if method in ['POST', 'PUT', 'PATCH', 'OPTIONS', 'DELETE']:
|
|
173
|
+
|
|
174
|
+
# no content type provided or payload is json
|
|
175
|
+
content_type = headers.get('Content-Type')
|
|
176
|
+
if (
|
|
177
|
+
not content_type
|
|
178
|
+
or re.search('json', content_type, re.IGNORECASE)
|
|
179
|
+
):
|
|
180
|
+
request_body = None
|
|
181
|
+
if body is not None:
|
|
182
|
+
request_body = json.dumps(body)
|
|
183
|
+
r = self.pool_manager.request(
|
|
184
|
+
method,
|
|
185
|
+
url,
|
|
186
|
+
body=request_body,
|
|
187
|
+
timeout=timeout,
|
|
188
|
+
headers=headers,
|
|
189
|
+
preload_content=False
|
|
190
|
+
)
|
|
191
|
+
elif content_type == 'application/x-www-form-urlencoded':
|
|
192
|
+
r = self.pool_manager.request(
|
|
193
|
+
method,
|
|
194
|
+
url,
|
|
195
|
+
fields=post_params,
|
|
196
|
+
encode_multipart=False,
|
|
197
|
+
timeout=timeout,
|
|
198
|
+
headers=headers,
|
|
199
|
+
preload_content=False
|
|
200
|
+
)
|
|
201
|
+
elif content_type == 'multipart/form-data':
|
|
202
|
+
# must del headers['Content-Type'], or the correct
|
|
203
|
+
# Content-Type which generated by urllib3 will be
|
|
204
|
+
# overwritten.
|
|
205
|
+
del headers['Content-Type']
|
|
206
|
+
# Ensures that dict objects are serialized
|
|
207
|
+
post_params = [(a, json.dumps(b)) if isinstance(b, dict) else (a,b) for a, b in post_params]
|
|
208
|
+
r = self.pool_manager.request(
|
|
209
|
+
method,
|
|
210
|
+
url,
|
|
211
|
+
fields=post_params,
|
|
212
|
+
encode_multipart=True,
|
|
213
|
+
timeout=timeout,
|
|
214
|
+
headers=headers,
|
|
215
|
+
preload_content=False
|
|
216
|
+
)
|
|
217
|
+
# Pass a `string` parameter directly in the body to support
|
|
218
|
+
# other content types than JSON when `body` argument is
|
|
219
|
+
# provided in serialized form.
|
|
220
|
+
elif isinstance(body, str) or isinstance(body, bytes):
|
|
221
|
+
r = self.pool_manager.request(
|
|
222
|
+
method,
|
|
223
|
+
url,
|
|
224
|
+
body=body,
|
|
225
|
+
timeout=timeout,
|
|
226
|
+
headers=headers,
|
|
227
|
+
preload_content=False
|
|
228
|
+
)
|
|
229
|
+
elif headers['Content-Type'].startswith('text/') and isinstance(body, bool):
|
|
230
|
+
request_body = "true" if body else "false"
|
|
231
|
+
r = self.pool_manager.request(
|
|
232
|
+
method,
|
|
233
|
+
url,
|
|
234
|
+
body=request_body,
|
|
235
|
+
preload_content=False,
|
|
236
|
+
timeout=timeout,
|
|
237
|
+
headers=headers)
|
|
238
|
+
else:
|
|
239
|
+
# Cannot generate the request from given parameters
|
|
240
|
+
msg = """Cannot prepare a request message for provided
|
|
241
|
+
arguments. Please check that your arguments match
|
|
242
|
+
declared content type."""
|
|
243
|
+
raise ApiException(status=0, reason=msg)
|
|
244
|
+
# For `GET`, `HEAD`
|
|
245
|
+
else:
|
|
246
|
+
r = self.pool_manager.request(
|
|
247
|
+
method,
|
|
248
|
+
url,
|
|
249
|
+
fields={},
|
|
250
|
+
timeout=timeout,
|
|
251
|
+
headers=headers,
|
|
252
|
+
preload_content=False
|
|
253
|
+
)
|
|
254
|
+
except urllib3.exceptions.SSLError as e:
|
|
255
|
+
msg = "\n".join([type(e).__name__, str(e)])
|
|
256
|
+
raise ApiException(status=0, reason=msg)
|
|
257
|
+
|
|
258
|
+
return RESTResponse(r)
|
|
@@ -4,14 +4,14 @@ import json
|
|
|
4
4
|
import aiohttp
|
|
5
5
|
import aiofiles
|
|
6
6
|
import requests
|
|
7
|
-
from typing import Optional, Union, List
|
|
7
|
+
from typing import Optional, Union, List, AsyncIterator
|
|
8
8
|
|
|
9
|
-
from
|
|
10
|
-
from
|
|
9
|
+
from smallestai.waves.exceptions import TTSError, APIError
|
|
10
|
+
from smallestai.waves.utils import (TTSOptions, validate_input, preprocess_text, add_wav_header, chunk_text,
|
|
11
11
|
get_smallest_languages, get_smallest_models, ALLOWED_AUDIO_EXTENSIONS, API_BASE_URL)
|
|
12
12
|
|
|
13
13
|
|
|
14
|
-
class
|
|
14
|
+
class AsyncWavesClient:
|
|
15
15
|
def __init__(
|
|
16
16
|
self,
|
|
17
17
|
api_key: str = None,
|
|
@@ -19,6 +19,9 @@ class AsyncSmallest:
|
|
|
19
19
|
sample_rate: Optional[int] = 24000,
|
|
20
20
|
voice_id: Optional[str] = "emily",
|
|
21
21
|
speed: Optional[float] = 1.0,
|
|
22
|
+
consistency: Optional[float] = 0.5,
|
|
23
|
+
similarity: Optional[float] = 0.0,
|
|
24
|
+
enhancement: Optional[int] = 1,
|
|
22
25
|
add_wav_header: Optional[bool] = True
|
|
23
26
|
) -> None:
|
|
24
27
|
"""
|
|
@@ -34,6 +37,9 @@ class AsyncSmallest:
|
|
|
34
37
|
- sample_rate (int): The sample rate for the audio output.
|
|
35
38
|
- voice_id (TTSVoices): The voice to be used for synthesis.
|
|
36
39
|
- speed (float): The speed of the speech synthesis.
|
|
40
|
+
- consistency (float): This parameter controls word repetition and skipping. Decrease it to prevent skipped words, and increase it to prevent repetition. Only supported in `lightning-large` model. Range - [0, 1]
|
|
41
|
+
- similarity (float): This parameter controls the similarity between the synthesized audio and the reference audio. Increase it to make the speech more similar to the reference audio. Only supported in `lightning-large` model. Range - [0, 1]
|
|
42
|
+
- enhancement (int): Enhances speech quality at the cost of increased latency. Only supported in `lightning-large` model. Range - [0, 2].
|
|
37
43
|
- add_wav_header (bool): Whether to add a WAV header to the output audio.
|
|
38
44
|
|
|
39
45
|
Methods:
|
|
@@ -45,7 +51,7 @@ class AsyncSmallest:
|
|
|
45
51
|
self.api_key = api_key or os.environ.get("SMALLEST_API_KEY")
|
|
46
52
|
if not self.api_key:
|
|
47
53
|
raise TTSError()
|
|
48
|
-
if model == "lightning-large":
|
|
54
|
+
if model == "lightning-large" and voice_id is None:
|
|
49
55
|
voice_id = "lakshya"
|
|
50
56
|
|
|
51
57
|
self.chunk_size = 250
|
|
@@ -56,7 +62,10 @@ class AsyncSmallest:
|
|
|
56
62
|
voice_id=voice_id,
|
|
57
63
|
api_key=self.api_key,
|
|
58
64
|
add_wav_header=add_wav_header,
|
|
59
|
-
speed=speed
|
|
65
|
+
speed=speed,
|
|
66
|
+
consistency=consistency,
|
|
67
|
+
similarity=similarity,
|
|
68
|
+
enhancement=enhancement
|
|
60
69
|
)
|
|
61
70
|
self.session = None
|
|
62
71
|
|
|
@@ -121,27 +130,25 @@ class AsyncSmallest:
|
|
|
121
130
|
async def synthesize(
|
|
122
131
|
self,
|
|
123
132
|
text: str,
|
|
124
|
-
|
|
125
|
-
similarity: Optional[float] = 0,
|
|
126
|
-
enhancement: Optional[bool] = False,
|
|
133
|
+
stream: Optional[bool] = False,
|
|
127
134
|
save_as: Optional[str] = None,
|
|
128
135
|
**kwargs
|
|
129
|
-
) -> Union[bytes, None]:
|
|
136
|
+
) -> Union[bytes, None, AsyncIterator[bytes]]:
|
|
130
137
|
"""
|
|
131
138
|
Asynchronously synthesize speech from the provided text.
|
|
132
139
|
|
|
133
140
|
Args:
|
|
134
141
|
- text (str): The text to be converted to speech.
|
|
142
|
+
- stream (Optional[bool]): If True, returns an iterator yielding audio chunks instead of a full byte array.
|
|
135
143
|
- save_as (Optional[str]): If provided, the synthesized audio will be saved to this file path.
|
|
136
144
|
The file must have a .wav extension.
|
|
137
|
-
- consistency (Optional[float]): This parameter controls word repetition and skipping. Decrease it to prevent skipped words, and increase it to prevent repetition. Only supported in `lightning-large` model.
|
|
138
|
-
- similarity (Optional[float]): This parameter controls the similarity between the synthesized audio and the reference audio. Increase it to make the speech more similar to the reference audio. Only supported in `lightning-large` model.
|
|
139
|
-
- enhancement (Optional[bool]): Enhances speech quality at the cost of increased latency. Only supported in `lightning-large` model.
|
|
140
145
|
- kwargs: Additional optional parameters to override `__init__` options for this call.
|
|
141
146
|
|
|
142
147
|
Returns:
|
|
143
|
-
- Union[bytes, None]:
|
|
144
|
-
|
|
148
|
+
- Union[bytes, None, Iterator[bytes]]:
|
|
149
|
+
- If `stream=True`, returns an iterator yielding audio chunks.
|
|
150
|
+
- If `save_as` is provided, saves the file and returns None.
|
|
151
|
+
- Otherwise, returns the synthesized audio content as bytes.
|
|
145
152
|
|
|
146
153
|
Raises:
|
|
147
154
|
- TTSError: If the provided file name does not have a .wav extension when `save_as` is specified.
|
|
@@ -165,44 +172,50 @@ class AsyncSmallest:
|
|
|
165
172
|
for key, value in kwargs.items():
|
|
166
173
|
setattr(opts, key, value)
|
|
167
174
|
|
|
168
|
-
|
|
175
|
+
text = preprocess_text(text)
|
|
176
|
+
validate_input(text, opts.model, opts.sample_rate, opts.speed, opts.consistency, opts.similarity, opts.enhancement)
|
|
169
177
|
|
|
170
178
|
self.chunk_size = 250
|
|
171
179
|
if opts.model == 'lightning-large':
|
|
172
180
|
self.chunk_size = 140
|
|
173
181
|
|
|
174
182
|
chunks = chunk_text(text, self.chunk_size)
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
183
|
+
|
|
184
|
+
async def audio_stream():
|
|
185
|
+
for chunk in chunks:
|
|
186
|
+
payload = {
|
|
187
|
+
"text": chunk,
|
|
188
|
+
"sample_rate": opts.sample_rate,
|
|
189
|
+
"voice_id": opts.voice_id,
|
|
190
|
+
"add_wav_header": False,
|
|
191
|
+
"speed": opts.speed,
|
|
192
|
+
"model": opts.model
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
if opts.model == "lightning-large":
|
|
196
|
+
if opts.consistency is not None:
|
|
197
|
+
payload["consistency"] = opts.consistency
|
|
198
|
+
if opts.similarity is not None:
|
|
199
|
+
payload["similarity"] = opts.similarity
|
|
200
|
+
if opts.enhancement is not None:
|
|
201
|
+
payload["enhancement"] = opts.enhancement
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
headers = {
|
|
205
|
+
"Authorization": f"Bearer {self.api_key}",
|
|
206
|
+
"Content-Type": "application/json",
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
async with self.session.post(f"{API_BASE_URL}/{opts.model}/get_speech", json=payload, headers=headers) as res:
|
|
210
|
+
if res.status != 200:
|
|
211
|
+
raise APIError(f"Failed to synthesize speech: {await res.text()}. For more information, visit https://waves.smallest.ai/")
|
|
212
|
+
|
|
213
|
+
yield await res.read()
|
|
214
|
+
|
|
215
|
+
if stream:
|
|
216
|
+
return audio_stream()
|
|
217
|
+
|
|
218
|
+
audio_content = b"".join([chunk async for chunk in audio_stream()])
|
|
206
219
|
|
|
207
220
|
if save_as:
|
|
208
221
|
if not save_as.endswith(".wav"):
|