smallestai 4.0.1__py3-none-any.whl → 4.1.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.

@@ -6,8 +6,8 @@ import aiofiles
6
6
  import requests
7
7
  from typing import Optional, Union, List
8
8
 
9
- from smallestai.waves.exceptions import TTSError, APIError
10
- from smallestai.waves.utils import (TTSOptions, validate_input,
9
+ from smallestai.waves.exceptions import InvalidError, APIError
10
+ from smallestai.waves.utils import (TTSOptions, validate_input, validate_asr_input,
11
11
  get_smallest_languages, get_smallest_models, ALLOWED_AUDIO_EXTENSIONS, API_BASE_URL)
12
12
 
13
13
 
@@ -52,7 +52,7 @@ class AsyncWavesClient:
52
52
  """
53
53
  self.api_key = api_key or os.environ.get("SMALLEST_API_KEY")
54
54
  if not self.api_key:
55
- raise TTSError()
55
+ raise InvalidError()
56
56
  if model == "lightning-large" and voice_id is None:
57
57
  voice_id = "lakshya"
58
58
 
@@ -150,7 +150,7 @@ class AsyncWavesClient:
150
150
  - Otherwise, returns the synthesized audio content as bytes.
151
151
 
152
152
  Raises:
153
- - TTSError: If the provided file name does not have a .wav or .mp3 extension when `save_as` is specified.
153
+ - InvalidError: If the provided file name does not have a .wav or .mp3 extension when `save_as` is specified.
154
154
  - APIError: If the API request fails or returns an error.
155
155
  - ValueError: If an unexpected parameter is passed in `kwargs`.
156
156
  """
@@ -223,17 +223,17 @@ class AsyncWavesClient:
223
223
  - str: The response from the API as a formatted JSON string.
224
224
 
225
225
  Raises:
226
- - TTSError: If the file does not exist or is not a valid audio file.
226
+ - InvalidError: If the file does not exist or is not a valid audio file.
227
227
  - APIError: If the API request fails or returns an error.
228
228
  """
229
229
  url = f"{API_BASE_URL}/lightning-large/add_voice"
230
230
 
231
231
  if not os.path.exists(file_path):
232
- raise TTSError("Invalid file path. File does not exist.")
232
+ raise InvalidError("Invalid file path. File does not exist.")
233
233
 
234
234
  file_extension = os.path.splitext(file_path)[1].lower()
235
235
  if file_extension not in ALLOWED_AUDIO_EXTENSIONS:
236
- raise TTSError(f"Invalid file type. Supported formats are: {ALLOWED_AUDIO_EXTENSIONS}")
236
+ raise InvalidError(f"Invalid file type. Supported formats are: {ALLOWED_AUDIO_EXTENSIONS}")
237
237
 
238
238
  headers = {
239
239
  'Authorization': f"Bearer {self.api_key}",
@@ -255,7 +255,7 @@ class AsyncWavesClient:
255
255
  if res.status != 200:
256
256
  raise APIError(f"Failed to add voice: {await res.text()}. For more information, visit https://waves.smallest.ai/")
257
257
 
258
- return json.dumps(await res.json(), indent=4, ensure_ascii=False)
258
+ return await res.json()
259
259
 
260
260
  finally:
261
261
  if should_cleanup and self.session:
@@ -290,8 +290,61 @@ class AsyncWavesClient:
290
290
  if res.status != 200:
291
291
  raise APIError(f"Failed to delete voice: {await res.text()}. For more information, visit https://waves.smallest.ai/")
292
292
 
293
- return json.dumps(await res.json(), indent=4, ensure_ascii=False)
293
+ return await res.json()
294
294
  finally:
295
295
  if should_cleanup and self.session:
296
296
  await self.session.close()
297
- self.session = None
297
+ self.session = None
298
+
299
+ async def transcribe(
300
+ self,
301
+ file_path: str,
302
+ language: Optional[str] = "en",
303
+ word_timestamps: Optional[bool] = False,
304
+ age_detection: Optional[bool] = False,
305
+ gender_detection: Optional[bool] = False,
306
+ emotion_detection: Optional[bool] = False,
307
+ model: Optional[str] = "lightning"
308
+ ) -> dict:
309
+ validate_asr_input(file_path, model, language)
310
+
311
+ url = f"{API_BASE_URL}/speech-to-text"
312
+ headers = {
313
+ 'Authorization': f"Bearer {self.api_key}",
314
+ }
315
+
316
+ should_cleanup = await self._ensure_session()
317
+
318
+ try:
319
+ file_extension = os.path.splitext(file_path)[1].lower()
320
+ content_type = f"audio/{file_extension[1:]}" if file_extension else "application/octet-stream"
321
+
322
+ async with aiofiles.open(file_path, 'rb') as f:
323
+ file_data = await f.read()
324
+
325
+ form = aiohttp.FormData()
326
+ form.add_field(
327
+ 'file',
328
+ file_data,
329
+ filename=os.path.basename(file_path),
330
+ content_type=content_type
331
+ )
332
+ # Send options as multipart form fields (not query params)
333
+ form.add_field('model', model)
334
+ form.add_field('language', language)
335
+ form.add_field('word_timestamps', str(bool(word_timestamps)).lower())
336
+ form.add_field('age_detection', str(bool(age_detection)).lower())
337
+ form.add_field('gender_detection', str(bool(gender_detection)).lower())
338
+ form.add_field('emotion_detection', str(bool(emotion_detection)).lower())
339
+
340
+ async with self.session.post(url, headers=headers, data=form) as res:
341
+ if res.status != 200:
342
+ raise APIError(
343
+ f"Failed to transcribe audio: {await res.text()}. "
344
+ "For more information, visit https://waves-docs.smallest.ai/v4.0.0/content/api-references/asr-post-api"
345
+ )
346
+ return await res.json()
347
+ finally:
348
+ if should_cleanup and self.session:
349
+ await self.session.close()
350
+ self.session = None
@@ -1,18 +1,18 @@
1
- class TTSError(Exception):
1
+ class InvalidError(Exception):
2
2
  """Base exception for TTS SDK"""
3
3
  default_message = "API key is required. Please set the `SMALLEST_API_KEY` environment variable or visit https://waves.smallest.ai/ to obtain your API key."
4
4
 
5
5
  def __init__(self, message=None):
6
6
  super().__init__(message or self.default_message)
7
7
 
8
- class APIError(TTSError):
8
+ class APIError(InvalidError):
9
9
  """Raised when the API returns an error"""
10
10
  pass
11
11
 
12
- class ValidationError(TTSError):
12
+ class ValidationError(InvalidError):
13
13
  """Raised when input validation fails"""
14
14
  pass
15
15
 
16
- class AuthenticationError(TTSError):
16
+ class AuthenticationError(InvalidError):
17
17
  """Raised when authentication fails"""
18
18
  pass
@@ -6,3 +6,11 @@ TTSModels = [
6
6
  "lightning-large",
7
7
  "lightning-v2"
8
8
  ]
9
+ ASRLanguages_lightning = [
10
+ "it", "es", "en", "pt", "hi", "de", "fr", "uk", "ru", "kn", "ml", "pl",
11
+ "mr", "gu", "cs", "sk", "te", "or", "nl", "bn", "lv", "et", "ro", "pa",
12
+ "fi", "sv", "bg", "ta", "hu", "da", "lt", "mt", "multi"
13
+ ]
14
+ ASRModels = [
15
+ "lightning"
16
+ ]
smallestai/waves/utils.py CHANGED
@@ -1,9 +1,10 @@
1
+ import os
1
2
  from typing import List
2
3
  from typing import Optional
3
4
  from dataclasses import dataclass
4
5
 
5
6
  from smallestai.waves.exceptions import ValidationError
6
- from smallestai.waves.models import TTSModels, TTSLanguages_lightning, TTSLanguages_lightning_large, TTSLanguages_lightning_v2
7
+ from smallestai.waves.models import TTSModels, TTSLanguages_lightning, TTSLanguages_lightning_large, TTSLanguages_lightning_v2, ASRModels, ASRLanguages_lightning
7
8
 
8
9
 
9
10
  API_BASE_URL = "https://waves-api.smallest.ai/api/v1"
@@ -25,7 +26,24 @@ class TTSOptions:
25
26
  enhancement: int
26
27
  language: str
27
28
  output_format: str
29
+
30
+ @dataclass
31
+ class ASROptions:
32
+ model: str
33
+ api_key: str
34
+ language: str
35
+ word_timestamps: bool
36
+ age_detection: bool
37
+ gender_detection: bool
38
+ emotion_detection: bool
28
39
 
40
+ def validate_asr_input(file_path: str, model: str, language: str):
41
+ if not os.path.isfile(file_path):
42
+ raise ValidationError("Invalid file path. File does not exist.")
43
+ if model not in ASRModels:
44
+ raise ValidationError(f"Invalid model: {model}. Must be one of {ASRModels}")
45
+ if language not in ASRLanguages_lightning:
46
+ raise ValidationError(f"Invalid language: {language}. Must be one of {ASRLanguages_lightning}")
29
47
 
30
48
  def validate_input(text: str, model: str, sample_rate: int, speed: float, consistency: Optional[float] = None, similarity: Optional[float] = None, enhancement: Optional[int] = None):
31
49
  if not text:
@@ -4,8 +4,8 @@ import copy
4
4
  import requests
5
5
  from typing import Optional, Union, List
6
6
 
7
- from smallestai.waves.exceptions import TTSError, APIError
8
- from smallestai.waves.utils import (TTSOptions, validate_input,
7
+ from smallestai.waves.exceptions import InvalidError, APIError
8
+ from smallestai.waves.utils import (TTSOptions, validate_input, validate_asr_input,
9
9
  get_smallest_languages, get_smallest_models, ALLOWED_AUDIO_EXTENSIONS, API_BASE_URL)
10
10
 
11
11
  class WavesClient:
@@ -48,7 +48,7 @@ class WavesClient:
48
48
  """
49
49
  self.api_key = api_key or os.environ.get("SMALLEST_API_KEY")
50
50
  if not self.api_key:
51
- raise TTSError()
51
+ raise InvalidError()
52
52
  if model == "lightning-large" and voice_id is None:
53
53
  voice_id = "lakshya"
54
54
 
@@ -125,7 +125,7 @@ class WavesClient:
125
125
  - Otherwise, returns the synthesized audio content as bytes.
126
126
 
127
127
  Raises:
128
- - TTSError: If the provided file name does not have a .wav or .mp3 extension when `save_as` is specified.
128
+ - InvalidError: If the provided file name does not have a .wav or .mp3 extension when `save_as` is specified.
129
129
  - APIError: If the API request fails or returns an error.
130
130
  """
131
131
  opts = copy.deepcopy(self.opts)
@@ -184,15 +184,15 @@ class WavesClient:
184
184
  - str: The response from the API as a formatted JSON string.
185
185
 
186
186
  Raises:
187
- - TTSError: If the file does not exist or is not a valid audio file.
187
+ - InvalidError: If the file does not exist or is not a valid audio file.
188
188
  - APIError: If the API request fails or returns an error.
189
189
  """
190
190
  if not os.path.isfile(file_path):
191
- raise TTSError("Invalid file path. File does not exist.")
191
+ raise InvalidError("Invalid file path. File does not exist.")
192
192
 
193
193
  file_extension = os.path.splitext(file_path)[1].lower()
194
194
  if file_extension not in ALLOWED_AUDIO_EXTENSIONS:
195
- raise TTSError(f"Invalid file type. Supported formats are: {ALLOWED_AUDIO_EXTENSIONS}")
195
+ raise InvalidError(f"Invalid file type. Supported formats are: {ALLOWED_AUDIO_EXTENSIONS}")
196
196
 
197
197
  url = f"{API_BASE_URL}/lightning-large/add_voice"
198
198
  payload = {'displayName': display_name}
@@ -206,8 +206,8 @@ class WavesClient:
206
206
  response = requests.post(url, headers=headers, data=payload, files=files)
207
207
  if response.status_code != 200:
208
208
  raise APIError(f"Failed to add voice: {response.text}. For more information, visit https://waves.smallest.ai/")
209
-
210
- return json.dumps(response.json(), indent=4, ensure_ascii=False)
209
+
210
+ return response.json()
211
211
 
212
212
 
213
213
  def delete_voice(self, voice_id: str) -> str:
@@ -234,4 +234,41 @@ class WavesClient:
234
234
  if response.status_code != 200:
235
235
  raise APIError(f"Failed to delete voice: {response.text}. For more information, visit https://waves.smallest.ai/")
236
236
 
237
- return json.dumps(response.json(), indent=4, ensure_ascii=False)
237
+ return response.json()
238
+
239
+ def transcribe(
240
+ self,
241
+ file_path: str,
242
+ language: Optional[str] = "en",
243
+ word_timestamps: Optional[bool] = False,
244
+ age_detection: Optional[bool] = False,
245
+ gender_detection: Optional[bool] = False,
246
+ emotion_detection: Optional[bool] = False,
247
+ model: Optional[str] = "lightning"
248
+ ) -> dict:
249
+ validate_asr_input(file_path, model, language)
250
+
251
+ url = f"{API_BASE_URL}/speech-to-text"
252
+ headers = {
253
+ 'Authorization': f"Bearer {self.api_key}",
254
+ }
255
+ payload = {
256
+ 'model': model,
257
+ 'language': language,
258
+ 'word_timestamps': str(bool(word_timestamps)).lower(),
259
+ 'age_detection': str(bool(age_detection)).lower(),
260
+ 'gender_detection': str(bool(gender_detection)).lower(),
261
+ 'emotion_detection': str(bool(emotion_detection)).lower()
262
+ }
263
+
264
+ file_extension = os.path.splitext(file_path)[1].lower()
265
+ content_type = f"audio/{file_extension[1:]}" if file_extension else "application/octet-stream"
266
+
267
+ with open(file_path, 'rb') as f:
268
+ files = {'file': (os.path.basename(file_path), f, content_type)}
269
+ response = requests.post(url, headers=headers, files=files, data=payload)
270
+
271
+ if response.status_code != 200:
272
+ raise APIError(f"Failed to transcribe audio: {response.text}. For more information, visit https://waves-docs.smallest.ai/v4.0.0/content/api-references/asr-post-api")
273
+
274
+ return response.json()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: smallestai
3
- Version: 4.0.1
3
+ Version: 4.1.0
4
4
  Summary: Official Python client for the Smallest AI API
5
5
  Author-email: Smallest <support@smallest.ai>
6
6
  License: MIT
@@ -134,14 +134,14 @@ smallestai/atoms/models/webhook_post_request_events_inner.py,sha256=xrJ41CgtZMEX
134
134
  smallestai/atoms/models/webhook_subscription.py,sha256=tU_IthL9wOlEqL0WlhUTaLjNheHV0SORiJxBNcqPsUs,3908
135
135
  smallestai/atoms/models/webhook_subscription_populated.py,sha256=3mfU3cvtpOnWWsSqUv7m2NFP7lAjNUrXRmeLKrelAlc,4207
136
136
  smallestai/waves/__init__.py,sha256=hxyqisgFiKiroxupuZeNXpXFIbnivmdgPrid3CnLhh0,268
137
- smallestai/waves/async_waves_client.py,sha256=BgiSqd2UjwECCPwuh2dyhLSBP0inIsbPUEbduWTJrmI,11704
138
- smallestai/waves/exceptions.py,sha256=nY6I8fCXe2By54CytQ0-i3hFiYtt8TYAKj0g6OYsCjc,585
139
- smallestai/waves/models.py,sha256=FaMVkOFyNCVpWvyMCmqkv3t1wmnfCs1HIULxLr1L8XE,283
137
+ smallestai/waves/async_waves_client.py,sha256=1eRgJV5ZuyqH0gTMYpijjKoqI3epSgyvTRLP3-Fex5s,13865
138
+ smallestai/waves/exceptions.py,sha256=zJ_erDTHcVVW7UKCfMCnyAZfvTypHBek28LJqxsbH50,601
139
+ smallestai/waves/models.py,sha256=RaCy9Wfg_HiVF0FBh_WE4C8C5t_06zLoXTDciIjI8GI,556
140
140
  smallestai/waves/stream_tts.py,sha256=c9r8mZuuFjbyWsUrlZ1jb0WNX7-lR39EXDUqyF-5g14,6792
141
- smallestai/waves/utils.py,sha256=sqDpfa5SC60C_kJZo4MKxlDfkX7RRzO6aJ2hKpNMemE,2273
142
- smallestai/waves/waves_client.py,sha256=U6aqClYL49cTtYisvpUVhas2miGZiCfqwTU0eDUY548,9770
143
- smallestai-4.0.1.dist-info/licenses/LICENSE,sha256=kK3HNKhN7luQhkjkNWIvy9_gizbEDUM4mSv_HWq9uuM,1068
144
- smallestai-4.0.1.dist-info/METADATA,sha256=c9DX-VrtU8V0Lh4CWp7FfSY-pQT6CbQLFg9O3dmSAYY,20421
145
- smallestai-4.0.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
146
- smallestai-4.0.1.dist-info/top_level.txt,sha256=pdJzm1VC2J6RxoobATz45L9U3cki4AFLigsfvETz7Io,11
147
- smallestai-4.0.1.dist-info/RECORD,,
141
+ smallestai/waves/utils.py,sha256=f88r6uqDELWPos1Kmee3W53Ec0lrcT8CWnkyLPHl17E,2952
142
+ smallestai/waves/waves_client.py,sha256=7kHgLyHpduYoQr_zQ6edyF2a_FGKcrEvICnz6_OFoBo,11252
143
+ smallestai-4.1.0.dist-info/licenses/LICENSE,sha256=kK3HNKhN7luQhkjkNWIvy9_gizbEDUM4mSv_HWq9uuM,1068
144
+ smallestai-4.1.0.dist-info/METADATA,sha256=Dg58pZfqEBnles1XTwBruWfeuYodezYXw-xqR3r5GxE,20421
145
+ smallestai-4.1.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
146
+ smallestai-4.1.0.dist-info/top_level.txt,sha256=pdJzm1VC2J6RxoobATz45L9U3cki4AFLigsfvETz7Io,11
147
+ smallestai-4.1.0.dist-info/RECORD,,