sarvamai 0.1.11a3__py3-none-any.whl → 0.1.11a5__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.
@@ -23,10 +23,10 @@ class BaseClientWrapper:
23
23
 
24
24
  def get_headers(self) -> typing.Dict[str, str]:
25
25
  headers: typing.Dict[str, str] = {
26
- "User-Agent": "sarvamai/0.1.11a3",
26
+ "User-Agent": "sarvamai/0.1.11a5",
27
27
  "X-Fern-Language": "Python",
28
28
  "X-Fern-SDK-Name": "sarvamai",
29
- "X-Fern-SDK-Version": "0.1.11a3",
29
+ "X-Fern-SDK-Version": "0.1.11a5",
30
30
  **(self.get_custom_headers() or {}),
31
31
  }
32
32
  headers["api-subscription-key"] = self.api_subscription_key
@@ -234,12 +234,53 @@ class SpeechToTextJobClient:
234
234
 
235
235
  def create_job(
236
236
  self,
237
- job_parameters: SpeechToTextJobParametersParams,
237
+ language_code: typing.Optional[str] = None,
238
+ model: typing.Optional[str] = "saarika:v2.5",
239
+ num_speakers: typing.Optional[int] = None,
240
+ with_diarization: typing.Optional[bool] = False,
241
+ with_timestamps: typing.Optional[bool] = False,
238
242
  callback: typing.Optional[BulkJobCallbackParams] = OMIT,
239
243
  request_options: typing.Optional[RequestOptions] = None,
240
244
  ) -> SpeechToTextJob:
245
+ """
246
+ Create a new Speech-to-Text bulk job.
247
+
248
+ Parameters
249
+ ----------
250
+ language_code : typing.Optional[str], default=None
251
+ The language code of the input audio (e.g., "en", "hi").
252
+
253
+ model : typing.Optional[str], default="saarika:v2.5"
254
+ The model to use for transcription.
255
+
256
+ num_speakers : typing.Optional[int], default=None
257
+ The number of distinct speakers in the audio, if known.
258
+
259
+ with_diarization : typing.Optional[bool], default=False
260
+ Whether to enable speaker diarization (distinguishing who said what).
261
+
262
+ with_timestamps : typing.Optional[bool], default=False
263
+ Whether to include word-level timestamps in the transcription output.
264
+
265
+ callback : typing.Optional[BulkJobCallbackParams], default=OMIT
266
+ Optional callback configuration to receive job completion events.
267
+
268
+ request_options : typing.Optional[RequestOptions], default=None
269
+ Request-specific configuration.
270
+
271
+ Returns
272
+ -------
273
+ SpeechToTextJob
274
+ A handle to the newly created Speech-to-Text job.
275
+ """
241
276
  response = self.initialise(
242
- job_parameters=job_parameters,
277
+ job_parameters=SpeechToTextJobParametersParams(
278
+ language_code=language_code,
279
+ model=model,
280
+ num_speakers=num_speakers, # type: ignore[typeddict-item]
281
+ with_diarization=with_diarization, # type: ignore[typeddict-item]
282
+ with_timestamps=with_timestamps, # type: ignore[typeddict-item]
283
+ ),
243
284
  callback=callback,
244
285
  request_options=request_options,
245
286
  )
@@ -247,7 +288,17 @@ class SpeechToTextJobClient:
247
288
 
248
289
  def get_job(self, job_id: str) -> SpeechToTextJob:
249
290
  """
250
- Return a job handle for an existing Speech-to-Text job.
291
+ Get an existing Speech-to-Text job handle by job ID.
292
+
293
+ Parameters
294
+ ----------
295
+ job_id : str
296
+ The job ID of the previously created Speech-to-Text job.
297
+
298
+ Returns
299
+ -------
300
+ SpeechToTextJob
301
+ A job handle which can be used to check status or retrieve results.
251
302
  """
252
303
  return SpeechToTextJob(job_id=job_id, client=self)
253
304
 
@@ -511,12 +562,53 @@ class AsyncSpeechToTextJobClient:
511
562
 
512
563
  async def create_job(
513
564
  self,
514
- job_parameters: SpeechToTextJobParametersParams,
565
+ language_code: typing.Optional[str] = None,
566
+ model: typing.Optional[str] = "saarika:v2.5",
567
+ num_speakers: typing.Optional[int] = None,
568
+ with_diarization: typing.Optional[bool] = False,
569
+ with_timestamps: typing.Optional[bool] = False,
515
570
  callback: typing.Optional[BulkJobCallbackParams] = OMIT,
516
571
  request_options: typing.Optional[RequestOptions] = None,
517
572
  ) -> "AsyncSpeechToTextJob":
573
+ """
574
+ Create a new Speech-to-Text bulk job.
575
+
576
+ Parameters
577
+ ----------
578
+ language_code : typing.Optional[str], default=None
579
+ The language code of the input audio (e.g., "en", "hi").
580
+
581
+ model : typing.Optional[str], default="saarika:v2.5"
582
+ The model to use for transcription.
583
+
584
+ num_speakers : typing.Optional[int], default=None
585
+ The number of distinct speakers in the audio, if known.
586
+
587
+ with_diarization : typing.Optional[bool], default=False
588
+ Whether to enable speaker diarization (distinguishing who said what).
589
+
590
+ with_timestamps : typing.Optional[bool], default=False
591
+ Whether to include word-level timestamps in the transcription output.
592
+
593
+ callback : typing.Optional[BulkJobCallbackParams], default=OMIT
594
+ Optional callback configuration to receive job completion events.
595
+
596
+ request_options : typing.Optional[RequestOptions], default=None
597
+ Request-specific configuration.
598
+
599
+ Returns
600
+ -------
601
+ AsyncSpeechToTextJob
602
+ A handle to the newly created job.
603
+ """
518
604
  response = await self.initialise(
519
- job_parameters=job_parameters,
605
+ job_parameters=SpeechToTextJobParametersParams(
606
+ language_code=language_code,
607
+ model=model,
608
+ num_speakers=num_speakers, # type: ignore[typeddict-item]
609
+ with_diarization=with_diarization, # type: ignore[typeddict-item]
610
+ with_timestamps=with_timestamps, # type: ignore[typeddict-item]
611
+ ),
520
612
  callback=callback,
521
613
  request_options=request_options,
522
614
  )
@@ -524,6 +616,16 @@ class AsyncSpeechToTextJobClient:
524
616
 
525
617
  async def get_job(self, job_id: str) -> "AsyncSpeechToTextJob":
526
618
  """
527
- Return a job handle for an existing Speech-to-Text job.
619
+ Get an existing Speech-to-Text job handle by job ID.
620
+
621
+ Parameters
622
+ ----------
623
+ job_id : str
624
+ The job ID of the previously created speech-to-text job.
625
+
626
+ Returns
627
+ -------
628
+ AsyncSpeechToTextJob
629
+ A job handle which can be used to check status or retrieve results.
528
630
  """
529
631
  return AsyncSpeechToTextJob(job_id=job_id, client=self)
@@ -1,4 +1,5 @@
1
1
  import asyncio
2
+ import mimetypes
2
3
  import os
3
4
  import time
4
5
  import typing
@@ -66,12 +67,15 @@ class AsyncSpeechToTextJob:
66
67
  file_name = os.path.basename(path)
67
68
  url = upload_links.upload_urls[file_name].file_url
68
69
  with open(path, "rb") as f:
70
+ content_type, _ = mimetypes.guess_type(path)
71
+ if content_type is None:
72
+ content_type = "audio/wav"
69
73
  response = await session.put(
70
74
  url,
71
75
  content=f.read(),
72
76
  headers={
73
77
  "x-ms-blob-type": "BlockBlob",
74
- "Content-Type": "audio/wav",
78
+ "Content-Type": content_type,
75
79
  },
76
80
  )
77
81
  if response.status_code != 201:
@@ -5,12 +5,18 @@ import typing
5
5
  from ..core.client_wrapper import AsyncClientWrapper, SyncClientWrapper
6
6
  from ..core.request_options import RequestOptions
7
7
  from ..requests.bulk_job_callback import BulkJobCallbackParams
8
- from ..requests.speech_to_text_translate_job_parameters import SpeechToTextTranslateJobParametersParams
8
+ from ..requests.speech_to_text_translate_job_parameters import (
9
+ SpeechToTextTranslateJobParametersParams,
10
+ )
9
11
  from ..types.bulk_job_init_response_v_1 import BulkJobInitResponseV1
10
12
  from ..types.files_download_response import FilesDownloadResponse
11
13
  from ..types.files_upload_response import FilesUploadResponse
12
14
  from ..types.job_status_v_1 import JobStatusV1
13
- from .raw_client import AsyncRawSpeechToTextTranslateJobClient, RawSpeechToTextTranslateJobClient
15
+ from .job import AsyncSpeechToTextTranslateJob, SpeechToTextTranslateJob
16
+ from .raw_client import (
17
+ AsyncRawSpeechToTextTranslateJobClient,
18
+ RawSpeechToTextTranslateJobClient,
19
+ )
14
20
 
15
21
  # this is used as the default value for optional parameters
16
22
  OMIT = typing.cast(typing.Any, ...)
@@ -18,7 +24,9 @@ OMIT = typing.cast(typing.Any, ...)
18
24
 
19
25
  class SpeechToTextTranslateJobClient:
20
26
  def __init__(self, *, client_wrapper: SyncClientWrapper):
21
- self._raw_client = RawSpeechToTextTranslateJobClient(client_wrapper=client_wrapper)
27
+ self._raw_client = RawSpeechToTextTranslateJobClient(
28
+ client_wrapper=client_wrapper
29
+ )
22
30
 
23
31
  @property
24
32
  def with_raw_response(self) -> RawSpeechToTextTranslateJobClient:
@@ -72,11 +80,16 @@ class SpeechToTextTranslateJobClient:
72
80
  )
73
81
  """
74
82
  _response = self._raw_client.initialise(
75
- job_parameters=job_parameters, ptu_id=ptu_id, callback=callback, request_options=request_options
83
+ job_parameters=job_parameters,
84
+ ptu_id=ptu_id,
85
+ callback=callback,
86
+ request_options=request_options,
76
87
  )
77
88
  return _response.data
78
89
 
79
- def get_status(self, job_id: str, *, request_options: typing.Optional[RequestOptions] = None) -> JobStatusV1:
90
+ def get_status(
91
+ self, job_id: str, *, request_options: typing.Optional[RequestOptions] = None
92
+ ) -> JobStatusV1:
80
93
  """
81
94
  Get the status of a speech to text translate bulk job V1
82
95
 
@@ -143,7 +156,9 @@ class SpeechToTextTranslateJobClient:
143
156
  job_id="job_id",
144
157
  )
145
158
  """
146
- _response = self._raw_client.start(job_id, ptu_id=ptu_id, request_options=request_options)
159
+ _response = self._raw_client.start(
160
+ job_id, ptu_id=ptu_id, request_options=request_options
161
+ )
147
162
  return _response.data
148
163
 
149
164
  def get_upload_links(
@@ -234,10 +249,77 @@ class SpeechToTextTranslateJobClient:
234
249
  )
235
250
  return _response.data
236
251
 
252
+ def create_job(
253
+ self,
254
+ prompt: typing.Optional[str] = None,
255
+ model: typing.Optional[str] = "saaras:v2.5",
256
+ num_speakers: typing.Optional[int] = None,
257
+ with_diarization: typing.Optional[bool] = False,
258
+ callback: typing.Optional[BulkJobCallbackParams] = OMIT,
259
+ request_options: typing.Optional[RequestOptions] = None,
260
+ ) -> SpeechToTextTranslateJob:
261
+ """
262
+ Create a new Speech-to-Text-Translate bulk job.
263
+
264
+ Parameters
265
+ ----------
266
+ prompt : typing.Optional[str], default=None
267
+ An optional prompt to guide the transcription and translation model.
268
+
269
+ model : typing.Optional[str], default="saaras:v2.5"
270
+ The model to use for speech-to-text translation.
271
+
272
+ num_speakers : typing.Optional[int], default=None
273
+ The number of distinct speakers in the input audio, if known.
274
+
275
+ with_diarization : typing.Optional[bool], default=False
276
+ Whether to enable speaker diarization (i.e., distinguishing who is speaking).
277
+
278
+ callback : typing.Optional[BulkJobCallbackParams], default=OMIT
279
+ Optional callback configuration to receive job completion events via webhook.
280
+
281
+ request_options : typing.Optional[RequestOptions], default=None
282
+ Optional configuration for request timeout, retries, etc.
283
+
284
+ Returns
285
+ -------
286
+ SpeechToTextTranslateJob
287
+ A handle to the newly created Speech-to-Text-Translate job.
288
+ """
289
+ response = self.initialise(
290
+ job_parameters=SpeechToTextTranslateJobParametersParams(
291
+ prompt=prompt, # type: ignore[typeddict-item]
292
+ model=model,
293
+ with_diarization=with_diarization, # type: ignore[typeddict-item]
294
+ num_speakers=num_speakers, # type: ignore[typeddict-item]
295
+ ),
296
+ callback=callback,
297
+ request_options=request_options,
298
+ )
299
+ return SpeechToTextTranslateJob(job_id=response.job_id, client=self)
300
+
301
+ def get_job(self, job_id: str) -> SpeechToTextTranslateJob:
302
+ """
303
+ Get an existing Speech-to-Text-Translate job handle by job ID.
304
+
305
+ Parameters
306
+ ----------
307
+ job_id : str
308
+ The job ID of the previously created Speech-to-Text-Translate job.
309
+
310
+ Returns
311
+ -------
312
+ SpeechToTextTranslateJob
313
+ A job handle which can be used to check status or retrieve results.
314
+ """
315
+ return SpeechToTextTranslateJob(job_id=job_id, client=self)
316
+
237
317
 
238
318
  class AsyncSpeechToTextTranslateJobClient:
239
319
  def __init__(self, *, client_wrapper: AsyncClientWrapper):
240
- self._raw_client = AsyncRawSpeechToTextTranslateJobClient(client_wrapper=client_wrapper)
320
+ self._raw_client = AsyncRawSpeechToTextTranslateJobClient(
321
+ client_wrapper=client_wrapper
322
+ )
241
323
 
242
324
  @property
243
325
  def with_raw_response(self) -> AsyncRawSpeechToTextTranslateJobClient:
@@ -299,11 +381,16 @@ class AsyncSpeechToTextTranslateJobClient:
299
381
  asyncio.run(main())
300
382
  """
301
383
  _response = await self._raw_client.initialise(
302
- job_parameters=job_parameters, ptu_id=ptu_id, callback=callback, request_options=request_options
384
+ job_parameters=job_parameters,
385
+ ptu_id=ptu_id,
386
+ callback=callback,
387
+ request_options=request_options,
303
388
  )
304
389
  return _response.data
305
390
 
306
- async def get_status(self, job_id: str, *, request_options: typing.Optional[RequestOptions] = None) -> JobStatusV1:
391
+ async def get_status(
392
+ self, job_id: str, *, request_options: typing.Optional[RequestOptions] = None
393
+ ) -> JobStatusV1:
307
394
  """
308
395
  Get the status of a speech to text translate bulk job V1
309
396
 
@@ -339,7 +426,9 @@ class AsyncSpeechToTextTranslateJobClient:
339
426
 
340
427
  asyncio.run(main())
341
428
  """
342
- _response = await self._raw_client.get_status(job_id, request_options=request_options)
429
+ _response = await self._raw_client.get_status(
430
+ job_id, request_options=request_options
431
+ )
343
432
  return _response.data
344
433
 
345
434
  async def start(
@@ -386,7 +475,9 @@ class AsyncSpeechToTextTranslateJobClient:
386
475
 
387
476
  asyncio.run(main())
388
477
  """
389
- _response = await self._raw_client.start(job_id, ptu_id=ptu_id, request_options=request_options)
478
+ _response = await self._raw_client.start(
479
+ job_id, ptu_id=ptu_id, request_options=request_options
480
+ )
390
481
  return _response.data
391
482
 
392
483
  async def get_upload_links(
@@ -492,3 +583,68 @@ class AsyncSpeechToTextTranslateJobClient:
492
583
  job_id=job_id, files=files, ptu_id=ptu_id, request_options=request_options
493
584
  )
494
585
  return _response.data
586
+
587
+ async def create_job(
588
+ self,
589
+ prompt: typing.Optional[str] = None,
590
+ model: typing.Optional[str] = "saaras:v2.5",
591
+ num_speakers: typing.Optional[int] = None,
592
+ with_diarization: typing.Optional[bool] = False,
593
+ callback: typing.Optional[BulkJobCallbackParams] = OMIT,
594
+ request_options: typing.Optional[RequestOptions] = None,
595
+ ) -> "AsyncSpeechToTextTranslateJob":
596
+ """
597
+ Create a new Speech-to-Text-Translate bulk job.
598
+
599
+ Parameters
600
+ ----------
601
+ prompt : typing.Optional[str], default=None
602
+ An optional prompt to guide the transcription and translation model.
603
+
604
+ model : typing.Optional[str], default="saaras:v2.5"
605
+ The model to use for speech-to-text translation.
606
+
607
+ num_speakers : typing.Optional[int], default=None
608
+ The number of distinct speakers in the input audio, if known.
609
+
610
+ with_diarization : typing.Optional[bool], default=False
611
+ Whether to enable speaker diarization (i.e., distinguishing who is speaking).
612
+
613
+ callback : typing.Optional[BulkJobCallbackParams], default=OMIT
614
+ Optional callback configuration to receive job completion events via webhook.
615
+
616
+ request_options : typing.Optional[RequestOptions], default=None
617
+ Optional configuration for request timeout, retries, etc.
618
+
619
+ Returns
620
+ -------
621
+ AsyncSpeechToTextTranslateJob
622
+ A handle to the newly created job.
623
+ """
624
+ response = await self.initialise(
625
+ job_parameters=SpeechToTextTranslateJobParametersParams(
626
+ prompt=prompt, # type: ignore[typeddict-item]
627
+ model=model,
628
+ with_diarization=with_diarization, # type: ignore[typeddict-item]
629
+ num_speakers=num_speakers, # type: ignore[typeddict-item]
630
+ ),
631
+ callback=callback,
632
+ request_options=request_options,
633
+ )
634
+ return AsyncSpeechToTextTranslateJob(job_id=response.job_id, client=self)
635
+
636
+ async def get_job(self, job_id: str) -> "AsyncSpeechToTextTranslateJob":
637
+ """
638
+ Get an existing Speech-to-Text-Translate job handle by job ID.
639
+
640
+ Parameters
641
+ ----------
642
+ job_id : str
643
+ The job ID of the previously created speech-to-text-translate job.
644
+
645
+ Returns
646
+ -------
647
+ AsyncSpeechToTextTranslateJob
648
+ A job handle which can be used to check status or retrieve results.
649
+ """
650
+ return AsyncSpeechToTextTranslateJob(job_id=job_id, client=self)
@@ -0,0 +1,479 @@
1
+ import asyncio
2
+ import mimetypes
3
+ import os
4
+ import time
5
+ import typing
6
+ import httpx
7
+
8
+ from ..types import JobStatusV1
9
+
10
+ if typing.TYPE_CHECKING:
11
+ from .client import (
12
+ AsyncSpeechToTextTranslateJobClient,
13
+ SpeechToTextTranslateJobClient,
14
+ )
15
+
16
+
17
+ class AsyncSpeechToTextTranslateJob:
18
+ def __init__(
19
+ self,
20
+ job_id: str,
21
+ client: "AsyncSpeechToTextTranslateJobClient",
22
+ ):
23
+ """
24
+ Initialise the asynchronous speech-to-text-translate-translate job.
25
+
26
+ Parameters
27
+ ----------
28
+ job_id : str
29
+ The unique job identifier returned from a previous job initialisation.
30
+
31
+ client : AsyncSpeechToTextTranslateJobClient
32
+ The async client instance used to create the job.
33
+
34
+ !!! important
35
+ This must be the **same client instance** that was used to initialise
36
+ the job originally, as it contains the subscription key and configuration
37
+ required to authenticate and manage the job.
38
+
39
+ """
40
+ self._job_id = job_id
41
+ self._client = client
42
+
43
+ @property
44
+ def job_id(self) -> str:
45
+ """
46
+ Returns the job ID associated with this job instance.
47
+
48
+ Returns
49
+ -------
50
+ str
51
+ """
52
+ return self._job_id
53
+
54
+ async def upload_files(self, file_paths: typing.Sequence[str]) -> bool:
55
+ """
56
+ Upload input audio files for the speech-to-text-translate job.
57
+
58
+ Parameters
59
+ ----------
60
+ file_paths : Sequence[str]
61
+ List of full paths to local audio files.
62
+
63
+ Returns
64
+ -------
65
+ bool
66
+ True if all files are uploaded successfully.
67
+ """
68
+ upload_links = await self._client.get_upload_links(
69
+ job_id=self._job_id,
70
+ files=[os.path.basename(p) for p in file_paths],
71
+ )
72
+ async with httpx.AsyncClient() as session:
73
+ for path in file_paths:
74
+ file_name = os.path.basename(path)
75
+ url = upload_links.upload_urls[file_name].file_url
76
+ with open(path, "rb") as f:
77
+ response = await session.put(
78
+ url,
79
+ content=f.read(),
80
+ headers={
81
+ "x-ms-blob-type": "BlockBlob",
82
+ "Content-Type": "audio/wav",
83
+ },
84
+ )
85
+ if response.status_code != 201:
86
+ raise RuntimeError(
87
+ f"Upload failed for {file_name}: {response.status_code}"
88
+ )
89
+ return True
90
+
91
+ async def wait_until_complete(
92
+ self, poll_interval: int = 5, timeout: int = 600
93
+ ) -> JobStatusV1:
94
+ """
95
+ Polls job status until it completes or fails.
96
+
97
+ Parameters
98
+ ----------
99
+ poll_interval : int, optional
100
+ Time in seconds between polling attempts (default is 5).
101
+
102
+ timeout : int, optional
103
+ Maximum time to wait for completion in seconds (default is 600).
104
+
105
+ Returns
106
+ -------
107
+ JobStatusV1
108
+ Final job status.
109
+
110
+ Raises
111
+ ------
112
+ TimeoutError
113
+ If the job does not complete within the given timeout.
114
+ """
115
+ start = asyncio.get_event_loop().time()
116
+ while True:
117
+ status = await self.get_status()
118
+ state = status.job_state.lower()
119
+ if state in {"completed", "failed"}:
120
+ return status
121
+ if asyncio.get_event_loop().time() - start > timeout:
122
+ raise TimeoutError(
123
+ f"Job {self._job_id} did not complete within {timeout} seconds."
124
+ )
125
+ await asyncio.sleep(poll_interval)
126
+
127
+ async def get_output_mappings(self) -> typing.List[typing.Dict[str, str]]:
128
+ """
129
+ Get the mapping of input files to their corresponding output files.
130
+
131
+ Returns
132
+ -------
133
+ List[Dict[str, str]]
134
+ List of mappings with keys 'input_file' and 'output_file'.
135
+ """
136
+ job_status = await self.get_status()
137
+ return [
138
+ {
139
+ "input_file": detail.inputs[0].file_name,
140
+ "output_file": detail.outputs[0].file_name,
141
+ }
142
+ for detail in (job_status.job_details or [])
143
+ if detail.inputs and detail.outputs
144
+ ]
145
+
146
+ async def download_outputs(self, output_dir: str) -> bool:
147
+ """
148
+ Download output files to the specified directory.
149
+
150
+ Parameters
151
+ ----------
152
+ output_dir : str
153
+ Local directory where outputs will be saved.
154
+
155
+ Returns
156
+ -------
157
+ bool
158
+ True if all files downloaded successfully.
159
+
160
+ Raises
161
+ ------
162
+ RuntimeError
163
+ If a file fails to download.
164
+ """
165
+ mappings = await self.get_output_mappings()
166
+ file_names = [m["output_file"] for m in mappings]
167
+ download_links = await self._client.get_download_links(
168
+ job_id=self._job_id, files=file_names
169
+ )
170
+
171
+ os.makedirs(output_dir, exist_ok=True)
172
+ async with httpx.AsyncClient() as session:
173
+ for m in mappings:
174
+ url = download_links.download_urls[m["output_file"]].file_url
175
+ response = await session.get(url)
176
+ if response.status_code != 200:
177
+ raise RuntimeError(
178
+ f"Download failed for {m['output_file']}: {response.status_code}"
179
+ )
180
+ output_path = os.path.join(output_dir, f"{m['input_file']}.json")
181
+ with open(output_path, "wb") as f:
182
+ f.write(response.content)
183
+ return True
184
+
185
+ async def get_status(self) -> JobStatusV1:
186
+ """
187
+ Retrieve the current status of the job.
188
+
189
+ Returns
190
+ -------
191
+ JobStatusV1
192
+ """
193
+ return await self._client.get_status(self._job_id)
194
+
195
+ async def start(self) -> JobStatusV1:
196
+ """
197
+ Start the speech-to-text-translate job processing.
198
+
199
+ Returns
200
+ -------
201
+ JobStatusV1
202
+ """
203
+ return await self._client.start(job_id=self._job_id)
204
+
205
+ async def exists(self) -> bool:
206
+ """
207
+ Check if the job exists in the system.
208
+
209
+ Returns
210
+ -------
211
+ bool
212
+ """
213
+ try:
214
+ await self.get_status()
215
+ return True
216
+ except httpx.HTTPStatusError:
217
+ return False
218
+
219
+ async def is_complete(self) -> bool:
220
+ """
221
+ Check if the job is either completed or failed.
222
+
223
+ Returns
224
+ -------
225
+ bool
226
+ """
227
+ state = (await self.get_status()).job_state.lower()
228
+ return state in {"completed", "failed"}
229
+
230
+ async def is_successful(self) -> bool:
231
+ """
232
+ Check if the job completed successfully.
233
+
234
+ Returns
235
+ -------
236
+ bool
237
+ """
238
+ return (await self.get_status()).job_state.lower() == "completed"
239
+
240
+ async def is_failed(self) -> bool:
241
+ """
242
+ Check if the job has failed.
243
+
244
+ Returns
245
+ -------
246
+ bool
247
+ """
248
+ return (await self.get_status()).job_state.lower() == "failed"
249
+
250
+
251
+ class SpeechToTextTranslateJob:
252
+ def __init__(self, job_id: str, client: "SpeechToTextTranslateJobClient"):
253
+ """
254
+ Initialise the synchronous speech-to-text-translate job.
255
+
256
+ Parameters
257
+ ----------
258
+ job_id : str
259
+ The unique job identifier returned from a previous job initialisation.
260
+
261
+ client : SpeechToTextTranslateJobClient
262
+ The client instance used to create the job.
263
+
264
+ !!! important
265
+ This must be the **same client instance** that was used to initialise
266
+ the job originally, as it contains the subscription key and configuration
267
+ required to authenticate and manage the job.
268
+
269
+ """
270
+ self._job_id = job_id
271
+ self._client = client
272
+
273
+ @property
274
+ def job_id(self) -> str:
275
+ """
276
+ Returns the job ID associated with this job instance.
277
+
278
+ Returns
279
+ -------
280
+ str
281
+ """
282
+ return self._job_id
283
+
284
+ def upload_files(self, file_paths: typing.Sequence[str]) -> bool:
285
+ """
286
+ Upload input audio files for the speech-to-text-translate job.
287
+
288
+ Parameters
289
+ ----------
290
+ file_paths : Sequence[str]
291
+ List of full paths to local audio files.
292
+
293
+ Returns
294
+ -------
295
+ bool
296
+ True if all files are uploaded successfully.
297
+ """
298
+ upload_links = self._client.get_upload_links(
299
+ job_id=self._job_id, files=[os.path.basename(p) for p in file_paths]
300
+ )
301
+ with httpx.Client() as client:
302
+ for path in file_paths:
303
+ file_name = os.path.basename(path)
304
+ url = upload_links.upload_urls[file_name].file_url
305
+ content_type, _ = mimetypes.guess_type(path)
306
+ if content_type is None:
307
+ content_type = "audio/wav"
308
+ with open(path, "rb") as f:
309
+ response = client.put(
310
+ url,
311
+ content=f,
312
+ headers={
313
+ "x-ms-blob-type": "BlockBlob",
314
+ "Content-Type": content_type,
315
+ },
316
+ )
317
+ if response.status_code != 201:
318
+ raise RuntimeError(
319
+ f"Upload failed for {file_name}: {response.status_code}"
320
+ )
321
+ return True
322
+
323
+ def wait_until_complete(
324
+ self, poll_interval: int = 5, timeout: int = 600
325
+ ) -> JobStatusV1:
326
+ """
327
+ Polls job status until it completes or fails.
328
+
329
+ Parameters
330
+ ----------
331
+ poll_interval : int, optional
332
+ Time in seconds between polling attempts (default is 5).
333
+
334
+ timeout : int, optional
335
+ Maximum time to wait for completion in seconds (default is 600).
336
+
337
+ Returns
338
+ -------
339
+ JobStatusV1
340
+ Final job status.
341
+
342
+ Raises
343
+ ------
344
+ TimeoutError
345
+ If the job does not complete within the given timeout.
346
+ """
347
+ start = time.monotonic()
348
+ while True:
349
+ status = self.get_status()
350
+ state = status.job_state.lower()
351
+ if state in {"completed", "failed"}:
352
+ return status
353
+ if time.monotonic() - start > timeout:
354
+ raise TimeoutError(
355
+ f"Job {self._job_id} did not complete within {timeout} seconds."
356
+ )
357
+ time.sleep(poll_interval)
358
+
359
+ def get_output_mappings(self) -> typing.List[typing.Dict[str, str]]:
360
+ """
361
+ Get the mapping of input files to their corresponding output files.
362
+
363
+ Returns
364
+ -------
365
+ List[Dict[str, str]]
366
+ List of mappings with keys 'input_file' and 'output_file'.
367
+ """
368
+ job_status = self.get_status()
369
+ return [
370
+ {
371
+ "input_file": detail.inputs[0].file_name,
372
+ "output_file": detail.outputs[0].file_name,
373
+ }
374
+ for detail in (job_status.job_details or [])
375
+ if detail.inputs and detail.outputs
376
+ ]
377
+
378
+ def download_outputs(self, output_dir: str) -> bool:
379
+ """
380
+ Download output files to the specified directory.
381
+
382
+ Parameters
383
+ ----------
384
+ output_dir : str
385
+ Local directory where outputs will be saved.
386
+
387
+ Returns
388
+ -------
389
+ bool
390
+ True if all files downloaded successfully.
391
+
392
+ Raises
393
+ ------
394
+ RuntimeError
395
+ If a file fails to download.
396
+ """
397
+ mappings = self.get_output_mappings()
398
+ file_names = [m["output_file"] for m in mappings]
399
+ download_links = self._client.get_download_links(
400
+ job_id=self._job_id, files=file_names
401
+ )
402
+
403
+ os.makedirs(output_dir, exist_ok=True)
404
+ with httpx.Client() as client:
405
+ for m in mappings:
406
+ url = download_links.download_urls[m["output_file"]].file_url
407
+ response = client.get(url)
408
+ if response.status_code != 200:
409
+ raise RuntimeError(
410
+ f"Download failed for {m['output_file']}: {response.status_code}"
411
+ )
412
+ output_path = os.path.join(output_dir, f"{m['input_file']}.json")
413
+ with open(output_path, "wb") as f:
414
+ f.write(response.content)
415
+ return True
416
+
417
+ def get_status(self) -> JobStatusV1:
418
+ """
419
+ Retrieve the current status of the job.
420
+
421
+ Returns
422
+ -------
423
+ JobStatusV1
424
+ """
425
+ return self._client.get_status(self._job_id)
426
+
427
+ def start(self) -> JobStatusV1:
428
+ """
429
+ Start the speech-to-text-translate job processing.
430
+
431
+ Returns
432
+ -------
433
+ JobStatusV1
434
+ """
435
+ return self._client.start(job_id=self._job_id)
436
+
437
+ def exists(self) -> bool:
438
+ """
439
+ Check if the job exists in the system.
440
+
441
+ Returns
442
+ -------
443
+ bool
444
+ """
445
+ try:
446
+ self.get_status()
447
+ return True
448
+ except httpx.HTTPStatusError:
449
+ return False
450
+
451
+ def is_complete(self) -> bool:
452
+ """
453
+ Check if the job is either completed or failed.
454
+
455
+ Returns
456
+ -------
457
+ bool
458
+ """
459
+ return self.get_status().job_state.lower() in {"completed", "failed"}
460
+
461
+ def is_successful(self) -> bool:
462
+ """
463
+ Check if the job completed successfully.
464
+
465
+ Returns
466
+ -------
467
+ bool
468
+ """
469
+ return self.get_status().job_state.lower() == "completed"
470
+
471
+ def is_failed(self) -> bool:
472
+ """
473
+ Check if the job has failed.
474
+
475
+ Returns
476
+ -------
477
+ bool
478
+ """
479
+ return self.get_status().job_state.lower() == "failed"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: sarvamai
3
- Version: 0.1.11a3
3
+ Version: 0.1.11a5
4
4
  Summary:
5
5
  Requires-Python: >=3.8,<4.0
6
6
  Classifier: Intended Audience :: Developers
@@ -5,7 +5,7 @@ sarvamai/chat/raw_client.py,sha256=A2kRuZcVWlJhyYCD7YKgqNkZEp3cYa1731KhRkhirU0,1
5
5
  sarvamai/client.py,sha256=J30X_os1lPf8Wml0KDFEf6p8VGHhgF_lf3nw1T2D3qo,8207
6
6
  sarvamai/core/__init__.py,sha256=YE2CtXeASe1RAbaI39twKWYKCuT4tW5is9HWHhJjR_g,1653
7
7
  sarvamai/core/api_error.py,sha256=44vPoTyWN59gonCIZMdzw7M1uspygiLnr3GNFOoVL2Q,614
8
- sarvamai/core/client_wrapper.py,sha256=eyHeN1B7fvk8dfMZfv6DdCgRHHv13fEABklg1DOLkhA,2570
8
+ sarvamai/core/client_wrapper.py,sha256=0HB_aO3OF0Lw-ffD7erBfQjWFP6612DsoW2iqaTi4Jg,2570
9
9
  sarvamai/core/datetime_utils.py,sha256=nBys2IsYrhPdszxGKCNRPSOCwa-5DWOHG95FB8G9PKo,1047
10
10
  sarvamai/core/events.py,sha256=j7VWXgMpOsjCXdzY22wIhI7Q-v5InZ4WchRzA88x_Sk,856
11
11
  sarvamai/core/file.py,sha256=d4NNbX8XvXP32z8KpK2Xovv33nFfruIrpz0QWxlgpZk,2663
@@ -87,8 +87,8 @@ sarvamai/speech_to_text/__init__.py,sha256=_VhToAyIt_5axN6CLJwtxg3-CO7THa_23pbUz
87
87
  sarvamai/speech_to_text/client.py,sha256=lp2G2fI9SUbeOBBE1S5tjcp-Xb8wIhAuVadLKwXveh8,11003
88
88
  sarvamai/speech_to_text/raw_client.py,sha256=A_56vEVeJdyttVJRiFxTMJ4n-s4l_PS8rI1DiLZlOmc,25331
89
89
  sarvamai/speech_to_text_job/__init__.py,sha256=_VhToAyIt_5axN6CLJwtxg3-CO7THa_23pbUzqhXJa4,85
90
- sarvamai/speech_to_text_job/client.py,sha256=DJhCmYQ1EYbmrhLalsqEv_3rZYJWTsT-W2wQrU6IaXU,14275
91
- sarvamai/speech_to_text_job/job.py,sha256=6h9hrpsKX21ql-umxEThtx8gU38cZj25WuBY9iBdnbI,13744
90
+ sarvamai/speech_to_text_job/client.py,sha256=VKhfSR6FPvXspNH8T8m5TRf9xS46X_QKtsnHCoIv_uc,18220
91
+ sarvamai/speech_to_text_job/job.py,sha256=LD47DCehRg5vHF8mc9C-gcVPiXRE-vpVMT8gGlrzK04,13923
92
92
  sarvamai/speech_to_text_job/raw_client.py,sha256=v14drcQLAmpqozRUNKmw1F9j3omieMPC8R88Th1BID8,48055
93
93
  sarvamai/speech_to_text_streaming/__init__.py,sha256=q7QygMmZCHJ-4FMhhL_6XNV_dsqlIFRCO1iSxoyxaaY,437
94
94
  sarvamai/speech_to_text_streaming/client.py,sha256=WdkzZxKMdnQ2hHv9hzJlfSNggRJLKFljRiC7695Jcog,8224
@@ -100,7 +100,8 @@ sarvamai/speech_to_text_streaming/types/speech_to_text_streaming_language_code.p
100
100
  sarvamai/speech_to_text_streaming/types/speech_to_text_streaming_model.py,sha256=b6F4ymgz4got6KVDqrweYvkET8itze63wUwWyjqDlO4,180
101
101
  sarvamai/speech_to_text_streaming/types/speech_to_text_streaming_vad_signals.py,sha256=8wiFOB7WDMbYCcMTYgNFJaIjEytYeXpJLwr_O_mH0TI,172
102
102
  sarvamai/speech_to_text_translate_job/__init__.py,sha256=_VhToAyIt_5axN6CLJwtxg3-CO7THa_23pbUzqhXJa4,85
103
- sarvamai/speech_to_text_translate_job/client.py,sha256=Qo0Uc-O6Y7JzUlrOH4RpFk46gdIdDWb6rHajbJzpxYc,13565
103
+ sarvamai/speech_to_text_translate_job/client.py,sha256=u2ir1s6YxOVWAbyPlykzgGjijkLs7WPUizVT8n0otqE,19015
104
+ sarvamai/speech_to_text_translate_job/job.py,sha256=-DRzK4ZI3Jupf4H3GUX1ShIqDSTxGb59lIYGY9cQa7U,14109
104
105
  sarvamai/speech_to_text_translate_job/raw_client.py,sha256=g-xk7H8ZwjmPSuJSgblVSH7kqGh_5wAkYUy5PdwTm-U,49362
105
106
  sarvamai/speech_to_text_translate_streaming/__init__.py,sha256=_hmlce1Zs1grylysZhBUdtKfkaUROwVydtwz6l-1qqg,411
106
107
  sarvamai/speech_to_text_translate_streaming/client.py,sha256=TnHCcspbbYFaimcEk8km3QNrNkm8JlX7e2ydpeHL9EE,8068
@@ -208,6 +209,6 @@ sarvamai/types/transliterate_mode.py,sha256=1jSEMlGcoLkWuk12TgoOpSgwifa4rThGKZ1h
208
209
  sarvamai/types/transliterate_source_language.py,sha256=bSY9wJszF0sg-Cgg6F-YcWC8ly1mIlj9rqa15-jBtx8,283
209
210
  sarvamai/types/transliteration_response.py,sha256=yt-lzTbDeJ_ZL4I8kQa6oESxA9ebeJJY7LfFHpdEsmM,815
210
211
  sarvamai/version.py,sha256=Qkp3Ee9YH-O9RTix90e0i7iNrFAGN-QDt2AFwGA4n8k,75
211
- sarvamai-0.1.11a3.dist-info/METADATA,sha256=c6e6mRlKnUEgebTQ0LhrFajaqvE_z3xT-i0sOLBTB-o,26753
212
- sarvamai-0.1.11a3.dist-info/WHEEL,sha256=Zb28QaM1gQi8f4VCBhsUklF61CTlNYfs9YAZn-TOGFk,88
213
- sarvamai-0.1.11a3.dist-info/RECORD,,
212
+ sarvamai-0.1.11a5.dist-info/METADATA,sha256=z2aP1-cQBG0P5JYqkU9KLNgB5oSRgJ8e5TyuAzE65U8,26753
213
+ sarvamai-0.1.11a5.dist-info/WHEEL,sha256=Zb28QaM1gQi8f4VCBhsUklF61CTlNYfs9YAZn-TOGFk,88
214
+ sarvamai-0.1.11a5.dist-info/RECORD,,