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.

Files changed (96) hide show
  1. smallestai/__init__.py +95 -0
  2. smallestai/atoms/__init__.py +182 -0
  3. smallestai/atoms/api/__init__.py +12 -0
  4. smallestai/atoms/api/agent_templates_api.py +573 -0
  5. smallestai/atoms/api/agents_api.py +1465 -0
  6. smallestai/atoms/api/calls_api.py +320 -0
  7. smallestai/atoms/api/campaigns_api.py +1689 -0
  8. smallestai/atoms/api/knowledge_base_api.py +2271 -0
  9. smallestai/atoms/api/logs_api.py +305 -0
  10. smallestai/atoms/api/organization_api.py +285 -0
  11. smallestai/atoms/api/user_api.py +285 -0
  12. smallestai/atoms/api_client.py +797 -0
  13. smallestai/atoms/api_response.py +21 -0
  14. smallestai/atoms/atoms_client.py +560 -0
  15. smallestai/atoms/configuration.py +582 -0
  16. smallestai/atoms/exceptions.py +216 -0
  17. smallestai/atoms/models/__init__.py +72 -0
  18. smallestai/atoms/models/agent_dto.py +130 -0
  19. smallestai/atoms/models/agent_dto_language.py +91 -0
  20. smallestai/atoms/models/agent_dto_synthesizer.py +99 -0
  21. smallestai/atoms/models/agent_dto_synthesizer_voice_config.py +111 -0
  22. smallestai/atoms/models/api_response.py +89 -0
  23. smallestai/atoms/models/bad_request_error_response.py +89 -0
  24. smallestai/atoms/models/create_agent_from_template200_response.py +89 -0
  25. smallestai/atoms/models/create_agent_from_template_request.py +91 -0
  26. smallestai/atoms/models/create_agent_request.py +113 -0
  27. smallestai/atoms/models/create_agent_request_language.py +124 -0
  28. smallestai/atoms/models/create_agent_request_language_synthesizer.py +110 -0
  29. smallestai/atoms/models/create_agent_request_language_synthesizer_voice_config.py +137 -0
  30. smallestai/atoms/models/create_campaign200_response.py +93 -0
  31. smallestai/atoms/models/create_campaign200_response_data.py +106 -0
  32. smallestai/atoms/models/create_campaign200_response_inner.py +106 -0
  33. smallestai/atoms/models/create_campaign201_response.py +93 -0
  34. smallestai/atoms/models/create_campaign201_response_data.py +104 -0
  35. smallestai/atoms/models/create_campaign_request.py +93 -0
  36. smallestai/atoms/models/create_knowledge_base201_response.py +89 -0
  37. smallestai/atoms/models/create_knowledge_base_request.py +89 -0
  38. smallestai/atoms/models/delete_agent200_response.py +87 -0
  39. smallestai/atoms/models/get_agent_by_id200_response.py +93 -0
  40. smallestai/atoms/models/get_agent_templates200_response.py +97 -0
  41. smallestai/atoms/models/get_agent_templates200_response_data_inner.py +97 -0
  42. smallestai/atoms/models/get_agents200_response.py +93 -0
  43. smallestai/atoms/models/get_agents200_response_data.py +101 -0
  44. smallestai/atoms/models/get_campaign_by_id200_response.py +93 -0
  45. smallestai/atoms/models/get_campaign_by_id200_response_data.py +114 -0
  46. smallestai/atoms/models/get_campaigns200_response.py +97 -0
  47. smallestai/atoms/models/get_campaigns200_response_data_inner.py +118 -0
  48. smallestai/atoms/models/get_campaigns200_response_data_inner_agent.py +89 -0
  49. smallestai/atoms/models/get_campaigns200_response_data_inner_audience.py +89 -0
  50. smallestai/atoms/models/get_campaigns_request.py +89 -0
  51. smallestai/atoms/models/get_conversation200_response.py +93 -0
  52. smallestai/atoms/models/get_conversation200_response_data.py +125 -0
  53. smallestai/atoms/models/get_conversation_logs200_response.py +93 -0
  54. smallestai/atoms/models/get_conversation_logs200_response_data.py +125 -0
  55. smallestai/atoms/models/get_current_user200_response.py +93 -0
  56. smallestai/atoms/models/get_current_user200_response_data.py +99 -0
  57. smallestai/atoms/models/get_knowledge_base_by_id200_response.py +93 -0
  58. smallestai/atoms/models/get_knowledge_base_items200_response.py +97 -0
  59. smallestai/atoms/models/get_knowledge_bases200_response.py +97 -0
  60. smallestai/atoms/models/get_organization200_response.py +93 -0
  61. smallestai/atoms/models/get_organization200_response_data.py +105 -0
  62. smallestai/atoms/models/get_organization200_response_data_members_inner.py +89 -0
  63. smallestai/atoms/models/get_organization200_response_data_subscription.py +87 -0
  64. smallestai/atoms/models/internal_server_error_response.py +89 -0
  65. smallestai/atoms/models/knowledge_base_dto.py +93 -0
  66. smallestai/atoms/models/knowledge_base_item_dto.py +124 -0
  67. smallestai/atoms/models/start_outbound_call200_response.py +93 -0
  68. smallestai/atoms/models/start_outbound_call200_response_data.py +87 -0
  69. smallestai/atoms/models/start_outbound_call_request.py +89 -0
  70. smallestai/atoms/models/unauthorized_error_reponse.py +89 -0
  71. smallestai/atoms/models/update_agent200_response.py +89 -0
  72. smallestai/atoms/models/update_agent_request.py +119 -0
  73. smallestai/atoms/models/update_agent_request_language.py +99 -0
  74. smallestai/atoms/models/update_agent_request_synthesizer.py +110 -0
  75. smallestai/atoms/models/update_agent_request_synthesizer_voice_config.py +137 -0
  76. smallestai/atoms/models/update_agent_request_synthesizer_voice_config_one_of.py +111 -0
  77. smallestai/atoms/models/update_agent_request_synthesizer_voice_config_one_of1.py +99 -0
  78. smallestai/atoms/models/upload_text_to_knowledge_base_request.py +89 -0
  79. smallestai/atoms/py.typed +0 -0
  80. smallestai/atoms/rest.py +258 -0
  81. smallestai/waves/__init__.py +5 -0
  82. smallest/async_tts.py → smallestai/waves/async_waves_client.py +60 -47
  83. smallestai/waves/stream_tts.py +272 -0
  84. {smallest → smallestai/waves}/utils.py +8 -8
  85. smallest/tts.py → smallestai/waves/waves_client.py +58 -46
  86. {smallestai-2.1.0.dist-info → smallestai-3.0.0.dist-info}/METADATA +194 -43
  87. smallestai-3.0.0.dist-info/RECORD +92 -0
  88. {smallestai-2.1.0.dist-info → smallestai-3.0.0.dist-info}/WHEEL +1 -1
  89. smallestai-3.0.0.dist-info/top_level.txt +1 -0
  90. smallest/__init__.py +0 -5
  91. smallest/stream_tts.py +0 -161
  92. smallestai-2.1.0.dist-info/RECORD +0 -12
  93. smallestai-2.1.0.dist-info/top_level.txt +0 -1
  94. {smallest → smallestai/waves}/exceptions.py +0 -0
  95. {smallest → smallestai/waves}/models.py +0 -0
  96. {smallestai-2.1.0.dist-info → smallestai-3.0.0.dist-info/licenses}/LICENSE +0 -0
@@ -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)
@@ -0,0 +1,5 @@
1
+ from smallestai.waves.waves_client import WavesClient
2
+ from smallestai.waves.async_waves_client import AsyncWavesClient
3
+ from smallestai.waves.stream_tts import TextToAudioStream
4
+
5
+ __all__ = ["WavesClient", "AsyncWavesClient", "TextToAudioStream"]
@@ -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 smallest.exceptions import TTSError, APIError
10
- from smallest.utils import (TTSOptions, validate_input, preprocess_text, add_wav_header, chunk_text,
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 AsyncSmallest:
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
- consistency: Optional[float] = 0.5,
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]: The synthesized audio content in bytes if `save_as` is not specified;
144
- otherwise, returns None after saving the audio to the specified file.
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
- validate_input(preprocess_text(text), opts.model, opts.sample_rate, opts.speed, consistency, similarity, enhancement)
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
- audio_content = b""
176
-
177
- for chunk in chunks:
178
- payload = {
179
- "text": preprocess_text(chunk),
180
- "sample_rate": opts.sample_rate,
181
- "voice_id": opts.voice_id,
182
- "add_wav_header": False,
183
- "speed": opts.speed,
184
- "model": opts.model
185
- }
186
-
187
- if opts.model == "lightning-large":
188
- if consistency:
189
- payload["consistency"] = consistency
190
- if similarity:
191
- payload["similarity"] = similarity
192
- if enhancement:
193
- payload["enhancement"] = enhancement
194
-
195
-
196
- headers = {
197
- "Authorization": f"Bearer {self.api_key}",
198
- "Content-Type": "application/json",
199
- }
200
-
201
- async with self.session.post(f"{API_BASE_URL}/{opts.model}/get_speech", json=payload, headers=headers) as res:
202
- if res.status != 200:
203
- raise APIError(f"Failed to synthesize speech: {await res.text()}. For more information, visit https://waves.smallest.ai/")
204
-
205
- audio_content += await res.read()
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"):