vocalstack 1.0.0a2__tar.gz → 1.0.0a3__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: vocalstack
3
- Version: 1.0.0a2
3
+ Version: 1.0.0a3
4
4
  Summary: VocalStack speech API SDK (asyncio): one client grouped by resource, typed models from the API contract, realtime WebSocket streaming.
5
5
  Project-URL: Homepage, https://vocalstack.com
6
6
  License-Expression: MIT
@@ -48,7 +48,6 @@ pip install vocalstack
48
48
  - [Quickstart](https://github.com/vocalstack/python-sdk/blob/main/docs/quickstart.md): An API key, the SDK, and your first transcript in a dozen lines.
49
49
  - [Transcribe a file](https://github.com/vocalstack/python-sdk/blob/main/docs/transcribe-a-file.md): Upload a file, wait for its job, read the transcript and export it. Asynchronous; nothing to keep open.
50
50
  - [Transcribe a link](https://github.com/vocalstack/python-sdk/blob/main/docs/transcribe-a-link.md): The server fetches a public URL, a YouTube video or a recording on a video or audio site, then transcribes it like a file. Asynchronous, with a fetch phase first.
51
- - [Transcribe a batch of files](https://github.com/vocalstack/python-sdk/blob/main/docs/transcribe-a-batch.md): Send many files or links as one batch, start it once, and read every transcript when it is done. Cheaper than one transcription per file.
52
51
  - [Search your transcriptions](https://github.com/vocalstack/python-sdk/blob/main/docs/search-transcriptions.md): Find transcriptions by words in their name or link, site, language, kind, tag and date, keep the shared ones, and play each with its share code.
53
52
  - [Stream a microphone](https://github.com/vocalstack/python-sdk/blob/main/docs/stream-a-microphone.md): Live audio in, transcript events out, over one WebSocket. Drafts replace the live tail; commits append.
54
53
  - [Stream a file at full speed](https://github.com/vocalstack/python-sdk/blob/main/docs/stream-a-file.md): Send a file's bytes through a realtime session and read the transcript as fast as the server makes it. Nothing is stored.
@@ -69,7 +68,6 @@ pip install vocalstack
69
68
  | transcription | a file, link, microphone or stream, transcribed and stored | `vs.transcriptions` |
70
69
  | recording | one take inside a broadcast or a transcription | `vs.recordings` |
71
70
  | job | a file or link recording's transcription work | `vs.jobs` |
72
- | batch | many files transcribed together, for less than one transcription each | `vs.batches` |
73
71
 
74
72
  The other groups: `vs.corrections`, `vs.share_links`, `vs.voices`,
75
73
  `vs.search`, `vs.me`, `vs.languages`, `vs.rtmp`, `vs.events`, `vs.meta`.
@@ -35,7 +35,6 @@ pip install vocalstack
35
35
  - [Quickstart](https://github.com/vocalstack/python-sdk/blob/main/docs/quickstart.md): An API key, the SDK, and your first transcript in a dozen lines.
36
36
  - [Transcribe a file](https://github.com/vocalstack/python-sdk/blob/main/docs/transcribe-a-file.md): Upload a file, wait for its job, read the transcript and export it. Asynchronous; nothing to keep open.
37
37
  - [Transcribe a link](https://github.com/vocalstack/python-sdk/blob/main/docs/transcribe-a-link.md): The server fetches a public URL, a YouTube video or a recording on a video or audio site, then transcribes it like a file. Asynchronous, with a fetch phase first.
38
- - [Transcribe a batch of files](https://github.com/vocalstack/python-sdk/blob/main/docs/transcribe-a-batch.md): Send many files or links as one batch, start it once, and read every transcript when it is done. Cheaper than one transcription per file.
39
38
  - [Search your transcriptions](https://github.com/vocalstack/python-sdk/blob/main/docs/search-transcriptions.md): Find transcriptions by words in their name or link, site, language, kind, tag and date, keep the shared ones, and play each with its share code.
40
39
  - [Stream a microphone](https://github.com/vocalstack/python-sdk/blob/main/docs/stream-a-microphone.md): Live audio in, transcript events out, over one WebSocket. Drafts replace the live tail; commits append.
41
40
  - [Stream a file at full speed](https://github.com/vocalstack/python-sdk/blob/main/docs/stream-a-file.md): Send a file's bytes through a realtime session and read the transcript as fast as the server makes it. Nothing is stored.
@@ -56,7 +55,6 @@ pip install vocalstack
56
55
  | transcription | a file, link, microphone or stream, transcribed and stored | `vs.transcriptions` |
57
56
  | recording | one take inside a broadcast or a transcription | `vs.recordings` |
58
57
  | job | a file or link recording's transcription work | `vs.jobs` |
59
- | batch | many files transcribed together, for less than one transcription each | `vs.batches` |
60
58
 
61
59
  The other groups: `vs.corrections`, `vs.share_links`, `vs.voices`,
62
60
  `vs.search`, `vs.me`, `vs.languages`, `vs.rtmp`, `vs.events`, `vs.meta`.
@@ -20,9 +20,6 @@ labels and translation. The `vocalstack` package has one asyncio client,
20
20
  - A **job** is a file or link recording's transcription work:
21
21
  `await vs.jobs.wait(recording_id)` polls it until it is `done` or `failed`
22
22
  (`on_progress=lambda job: ...` sees each read: `job.state`, `job.progress.pct`).
23
- - A **batch** is many files transcribed together for less than one
24
- transcription each: `vs.batches.create()`, `add_file`/`add_url` per file,
25
- `start`, then `wait` (section 6).
26
23
  - A **broadcast** is a link listeners open to follow a live recording.
27
24
  - A **realtime session** (`vs.realtime`) is a socket you stream audio into and
28
25
  read the transcript from. Nothing is stored. "Session" means only this.
@@ -196,32 +193,6 @@ for h in res.hits:
196
193
  print(f"{h.similarity:.2f}", h.passage.media_start, h.passage.text)
197
194
  ```
198
195
 
199
- ## 6. A batch of files
200
-
201
- Many files at once cost less as a batch: one create, one minimum charge for
202
- the whole batch. Its files get transcripts, exports and speaker labels (when
203
- `settings.speakerLabels` asks), never a summary, title, share link or voices.
204
-
205
- ```python
206
- import asyncio, os, sys
207
- from vocalstack import VocalStack
208
-
209
- async def main(paths):
210
- async with VocalStack(os.environ.get("VOCALSTACK_BASE_URL"), api_key=os.environ["VOCALSTACK_API_KEY"]) as vs:
211
- batch = await vs.batches.create(name="Interviews", settings={"spokenLanguage": "en"})
212
- for path in paths:
213
- await vs.batches.add_file(batch.id, file=path) # or add_url(batch.id, url)
214
- await vs.batches.start(batch.id)
215
- done = await vs.batches.wait(batch.id) # done (or stopped); check done.counts.failed
216
- for tx in done.files or []:
217
- if tx.job and tx.job.state == "done":
218
- snap = await vs.recordings.transcript(tx.job.recording_id)
219
- print(tx.name, " ".join(s.text for s in snap.sentences))
220
- # vs.batches.export(batch.id, "srt") is every finished transcript in one zip.
221
-
222
- asyncio.run(main(sys.argv[1:]))
223
- ```
224
-
225
196
  ## Errors
226
197
 
227
198
  A failed call raises `vocalstack.VocalStackError` with `.code`, `.message`,
@@ -257,8 +228,7 @@ except VocalStackError as e:
257
228
  - The other groups: `vs.transcriptions` (`list`, `get`, `update`, `archive`,
258
229
  `restore`, `purge`, `list_share_links`, `create_share_link`), `vs.recordings`
259
230
  (`get`, `summarize`, `summary`, `translation`, `audio_url`, `subscribe`),
260
- `vs.batches` (`create`, `add_file`, `add_url`, `start`, `wait`, `get`, `list`,
261
- `export`, `stop`, `delete`), `vs.corrections`, `vs.share_links`, `vs.voices`, `vs.broadcasts`,
231
+ `vs.corrections`, `vs.share_links`, `vs.voices`, `vs.broadcasts`,
262
232
  `vs.me` (`get`, `usage`), `vs.languages`, `vs.rtmp`, `vs.events`, `vs.meta`.
263
233
  Each method is its operation's name in the OpenAPI document, in snake_case
264
234
  (`transcriptions.create` is `POST /v1/transcriptions`).
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "vocalstack"
7
- version = "1.0.0a2"
7
+ version = "1.0.0a3"
8
8
  description = "VocalStack speech API SDK (asyncio): one client grouped by resource, typed models from the API contract, realtime WebSocket streaming."
9
9
  readme = "README.md"
10
10
  license = "MIT"
@@ -20,9 +20,6 @@ labels and translation. The `vocalstack` package has one asyncio client,
20
20
  - A **job** is a file or link recording's transcription work:
21
21
  `await vs.jobs.wait(recording_id)` polls it until it is `done` or `failed`
22
22
  (`on_progress=lambda job: ...` sees each read: `job.state`, `job.progress.pct`).
23
- - A **batch** is many files transcribed together for less than one
24
- transcription each: `vs.batches.create()`, `add_file`/`add_url` per file,
25
- `start`, then `wait` (section 6).
26
23
  - A **broadcast** is a link listeners open to follow a live recording.
27
24
  - A **realtime session** (`vs.realtime`) is a socket you stream audio into and
28
25
  read the transcript from. Nothing is stored. "Session" means only this.
@@ -196,32 +193,6 @@ for h in res.hits:
196
193
  print(f"{h.similarity:.2f}", h.passage.media_start, h.passage.text)
197
194
  ```
198
195
 
199
- ## 6. A batch of files
200
-
201
- Many files at once cost less as a batch: one create, one minimum charge for
202
- the whole batch. Its files get transcripts, exports and speaker labels (when
203
- `settings.speakerLabels` asks), never a summary, title, share link or voices.
204
-
205
- ```python
206
- import asyncio, os, sys
207
- from vocalstack import VocalStack
208
-
209
- async def main(paths):
210
- async with VocalStack(os.environ.get("VOCALSTACK_BASE_URL"), api_key=os.environ["VOCALSTACK_API_KEY"]) as vs:
211
- batch = await vs.batches.create(name="Interviews", settings={"spokenLanguage": "en"})
212
- for path in paths:
213
- await vs.batches.add_file(batch.id, file=path) # or add_url(batch.id, url)
214
- await vs.batches.start(batch.id)
215
- done = await vs.batches.wait(batch.id) # done (or stopped); check done.counts.failed
216
- for tx in done.files or []:
217
- if tx.job and tx.job.state == "done":
218
- snap = await vs.recordings.transcript(tx.job.recording_id)
219
- print(tx.name, " ".join(s.text for s in snap.sentences))
220
- # vs.batches.export(batch.id, "srt") is every finished transcript in one zip.
221
-
222
- asyncio.run(main(sys.argv[1:]))
223
- ```
224
-
225
196
  ## Errors
226
197
 
227
198
  A failed call raises `vocalstack.VocalStackError` with `.code`, `.message`,
@@ -257,8 +228,7 @@ except VocalStackError as e:
257
228
  - The other groups: `vs.transcriptions` (`list`, `get`, `update`, `archive`,
258
229
  `restore`, `purge`, `list_share_links`, `create_share_link`), `vs.recordings`
259
230
  (`get`, `summarize`, `summary`, `translation`, `audio_url`, `subscribe`),
260
- `vs.batches` (`create`, `add_file`, `add_url`, `start`, `wait`, `get`, `list`,
261
- `export`, `stop`, `delete`), `vs.corrections`, `vs.share_links`, `vs.voices`, `vs.broadcasts`,
231
+ `vs.corrections`, `vs.share_links`, `vs.voices`, `vs.broadcasts`,
262
232
  `vs.me` (`get`, `usage`), `vs.languages`, `vs.rtmp`, `vs.events`, `vs.meta`.
263
233
  Each method is its operation's name in the OpenAPI document, in snake_case
264
234
  (`transcriptions.create` is `POST /v1/transcriptions`).
@@ -30,7 +30,7 @@ from .errors import VocalStackError
30
30
  from .realtime import RealtimeTranscript
31
31
  from .realtime import RealtimeSession, pace_realtime
32
32
 
33
- __version__ = "1.0.0a2"
33
+ __version__ = "1.0.0a3"
34
34
 
35
35
  __all__ = [
36
36
  "DEFAULT_BASE_URL",
@@ -26,12 +26,6 @@ AudioSourceState = Literal["ready", "error"]
26
26
 
27
27
  RecordingState = Literal["pending", "live", "paused", "processing", "ended", "finalized"]
28
28
 
29
- QueueReason = Literal["concurrency"]
30
-
31
- SourceKind = Literal["file", "url"]
32
-
33
- JobState = Literal["queued", "fetching", "transcribing", "done", "failed", "cropping", "stopped"]
34
-
35
29
  Lifecycle = Literal["active", "archived", "purged"]
36
30
 
37
31
  LatencyMode = Literal["auto", "fast", "accuracy"]
@@ -58,6 +52,10 @@ IngestState = Literal["connecting", "streaming", "reconnecting", "stopped", "fai
58
52
 
59
53
  InputKind = Literal["live", "file", "url", "stream"]
60
54
 
55
+ QueueReason = Literal["concurrency"]
56
+
57
+ JobState = Literal["queued", "fetching", "transcribing", "done", "failed", "cropping", "stopped"]
58
+
61
59
  LanguageStatusState = Literal["matches", "mismatch", "unknown", "change_unsupported"]
62
60
 
63
61
  LinkKind = Literal["live", "file", "unknown"]
@@ -68,6 +66,8 @@ LoadingStage = Literal["download", "model"]
68
66
 
69
67
  RecordingKind = Literal["live", "upload"]
70
68
 
69
+ SourceKind = Literal["file", "url"]
70
+
71
71
  PullState = Literal["connecting", "streaming", "reconnecting", "failed"]
72
72
 
73
73
  SentenceStatus = Literal["open", "closed", "dropped"]
@@ -236,225 +236,6 @@ class AutoTopUpView(_Base):
236
236
  threshold: Optional[float] = None
237
237
 
238
238
 
239
- class BatchCounts(_Base):
240
- """How many of a batch's files are in each state."""
241
-
242
- done: int
243
- failed: int
244
- queued: int
245
- running: int
246
- stopped: int
247
-
248
-
249
- class Blocked(_Base):
250
- at: str
251
- contact: str
252
- reason: str
253
-
254
-
255
- class BroadcastRef(_Base):
256
- """A broadcast a live transcription feeds (`Transcription.broadcasts`)."""
257
-
258
- access: Access
259
- code: str
260
- id: str
261
- name: str
262
- share_url: str = Field(alias="shareUrl")
263
-
264
-
265
- class CurrentRecording(_Base):
266
- """The recording in flight (`currentRecording`): on a transcription, its
267
- open recording; on a broadcast, the open recording
268
- of a live transcription feeding it, which is what its listeners are sent."""
269
-
270
- recording_id: str = Field(alias="recordingId")
271
- started_at: Optional[str] = Field(default=None, alias="startedAt")
272
- state: RecordingState
273
- transcription_id: Optional[str] = Field(default=None, alias="transcriptionId")
274
-
275
-
276
- class CropWindow(_Base):
277
- """A window of the source, in seconds into it. `end_s` absent = to the end."""
278
-
279
- end_s: Optional[float] = Field(default=None, alias="endS")
280
- start_s: float = Field(alias="startS")
281
-
282
-
283
- class JobProgress(_Base):
284
- fetch_total_bytes: Optional[int] = Field(default=None, alias="fetchTotalBytes")
285
- fetched_bytes: Optional[int] = Field(default=None, alias="fetchedBytes")
286
- pct: int
287
- phase: Optional[str] = None
288
- phase_pct: Optional[int] = Field(default=None, alias="phasePct")
289
- processed_s: float = Field(alias="processedS")
290
- segments_done: int = Field(alias="segmentsDone")
291
- segments_total: Optional[int] = Field(default=None, alias="segmentsTotal")
292
- total_s: Optional[float] = Field(default=None, alias="totalS")
293
-
294
-
295
- class JobQueue(_Base):
296
- """Why a `queued` job is waiting, and where it stands (`Job.queue`)."""
297
-
298
- position: int
299
- reason: QueueReason
300
-
301
-
302
- class SourceInfo(_Base):
303
- """What the recording was made from (design `j.meta`: "M4A · 34.2 MB",
304
- "Video URL")."""
305
-
306
- bytes: Optional[int] = None
307
- duration_s: Optional[float] = Field(default=None, alias="durationS")
308
- embed_id: Optional[str] = Field(default=None, alias="embedId")
309
- format: Optional[str] = None
310
- kind: SourceKind
311
- mime: Optional[str] = None
312
- name: str
313
- site: Optional[str] = None
314
- thumbnail_url: Optional[str] = Field(default=None, alias="thumbnailUrl")
315
- url: Optional[str] = None
316
-
317
-
318
- class TranscriptionJob(_Base):
319
- attempts: int
320
- created_at: str = Field(alias="createdAt")
321
- crop: Optional["CropWindow"] = None
322
- error: Optional[str] = None
323
- error_code: Optional[str] = Field(default=None, alias="errorCode")
324
- finished_at: Optional[str] = Field(default=None, alias="finishedAt")
325
- has_audio: bool = Field(alias="hasAudio")
326
- has_video: Optional[bool] = Field(default=None, alias="hasVideo")
327
- peaks: Optional[List[float]] = None
328
- preview: Optional[bool] = None
329
- progress: "JobProgress"
330
- queue: Optional["JobQueue"] = None
331
- recording_id: str = Field(alias="recordingId")
332
- source: Optional["SourceInfo"] = None
333
- started_at: Optional[str] = Field(default=None, alias="startedAt")
334
- state: JobState
335
- transcription_id: str = Field(alias="transcriptionId")
336
-
337
-
338
- class LastRecording(_Base):
339
- """A live transcription's newest recording (`lastRecording`): what
340
- its row in a list reads from — how long it ran, when, and whether it is
341
- still running — and the recording a tap on that row opens."""
342
-
343
- duration_s: Optional[float] = Field(default=None, alias="durationS")
344
- recording_id: str = Field(alias="recordingId")
345
- started_at: Optional[str] = Field(default=None, alias="startedAt")
346
- state: RecordingState
347
- title: Optional[str] = None
348
- transcription_id: Optional[str] = Field(default=None, alias="transcriptionId")
349
-
350
-
351
- class ConfidencePolicy(_Base):
352
- """Env defaults, overridden per broadcast, transcription or recording."""
353
-
354
- hide_below: Optional[float] = Field(default=None, alias="hideBelow")
355
- low_threshold: Optional[float] = Field(default=None, alias="lowThreshold")
356
- suppress_count: Optional[int] = Field(default=None, alias="suppressCount")
357
-
358
-
359
- class Settings(_Base):
360
- """A broadcast's, transcription's or recording's capture settings (the
361
- design's settings sheet): what a recording made in it starts with."""
362
-
363
- confidence: Optional["ConfidencePolicy"] = None
364
- latency_mode: Optional[LatencyMode] = Field(default=None, alias="latencyMode")
365
- max_duration_s: Optional[int] = Field(default=None, alias="maxDurationS")
366
- max_pause_s: Optional[int] = Field(default=None, alias="maxPauseS")
367
- silence_auto_pause_s: Optional[int] = Field(default=None, alias="silenceAutoPauseS")
368
- speaker_labels: Optional[SpeakerLabels] = Field(default=None, alias="speakerLabels")
369
- spoken_language: Optional[str] = Field(default=None, alias="spokenLanguage")
370
- summary: Optional[bool] = None
371
- summary_translations: Optional[bool] = Field(default=None, alias="summaryTranslations")
372
- title: Optional[bool] = None
373
- voice: Optional[bool] = None
374
- voice_by_lang: Optional[Dict[str, str]] = Field(default=None, alias="voiceByLang")
375
- voice_cloning: Optional[bool] = Field(default=None, alias="voiceCloning")
376
- voice_cloning_consent: Optional[bool] = Field(default=None, alias="voiceCloningConsent")
377
-
378
-
379
- class TagRef(_Base):
380
- """A tag as a transcription lists it."""
381
-
382
- color: Literal["coral", "amber", "lime", "green", "teal", "blue", "violet", "pink"]
383
- id: str
384
- name: str
385
-
386
-
387
- class Transcription(_Base):
388
- """A transcription: a file, a link (with its `transcription` job), or
389
- something captured as it is spoken (`live: true`: a microphone, a
390
- stream, an RTMP encoder), which may feed broadcasts (`broadcasts`)."""
391
-
392
- archived_at: Optional[str] = Field(default=None, alias="archivedAt")
393
- batch_id: Optional[str] = Field(default=None, alias="batchId")
394
- blocked: Optional["Blocked"] = None
395
- broadcasts: Optional[List["BroadcastRef"]] = None
396
- code: str
397
- created_at: str = Field(alias="createdAt")
398
- created_by_api_key_id: Optional[str] = Field(default=None, alias="createdByApiKeyId")
399
- current_recording: Optional["CurrentRecording"] = Field(default=None, alias="currentRecording")
400
- id: str
401
- job: Optional["TranscriptionJob"] = None
402
- kind: Literal["transcription"] = "transcription"
403
- last_recorded_at: Optional[str] = Field(default=None, alias="lastRecordedAt")
404
- last_recording: Optional["LastRecording"] = Field(default=None, alias="lastRecording")
405
- lifecycle: Lifecycle
406
- live: Optional[bool] = None
407
- locked: Optional[bool] = None
408
- name: str
409
- origin: Literal["dashboard", "api", "extension", "desktop_app", "obs", "wordpress", "payload", "strapi", "sanity", "contentful", "mcp", "batch_api", "broadcast", "unknown"]
410
- owner_principal_id: str = Field(alias="ownerPrincipalId")
411
- purge_after: Optional[str] = Field(default=None, alias="purgeAfter")
412
- recording_count: int = Field(alias="recordingCount")
413
- settings: "Settings"
414
- share_code: Optional[str] = Field(default=None, alias="shareCode")
415
- share_url: str = Field(alias="shareUrl")
416
- source: "CaptureSource"
417
- tags: Optional[List["TagRef"]] = None
418
- updated_at: str = Field(alias="updatedAt")
419
-
420
-
421
- class BatchSettings(_Base):
422
- """What every file of a batch runs with. Summary, title, voices and the
423
- share link are never made for a batch file, so they are not here.
424
- Voice cloning is (design 120: the New transcription form asks it for
425
- every file it starts, one file or several)."""
426
-
427
- latency_mode: Optional[LatencyMode] = Field(default=None, alias="latencyMode")
428
- speaker_labels: Optional[SpeakerLabels] = Field(default=None, alias="speakerLabels")
429
- spoken_language: Optional[str] = Field(default=None, alias="spokenLanguage")
430
- voice_cloning: Optional[bool] = Field(default=None, alias="voiceCloning")
431
- voice_cloning_consent: Optional[bool] = Field(default=None, alias="voiceCloningConsent")
432
-
433
-
434
- class Batch(_Base):
435
- """A batch of files."""
436
-
437
- counts: "BatchCounts"
438
- created_at: str = Field(alias="createdAt")
439
- created_by_api_key_id: Optional[str] = Field(default=None, alias="createdByApiKeyId")
440
- file_count: int = Field(alias="fileCount")
441
- files: Optional[List["Transcription"]] = None
442
- files_per_batch: Optional[int] = Field(default=None, alias="filesPerBatch")
443
- finished_at: Optional[str] = Field(default=None, alias="finishedAt")
444
- id: str
445
- name: str
446
- settings: "BatchSettings"
447
- started_at: Optional[str] = Field(default=None, alias="startedAt")
448
- state: str
449
-
450
-
451
- class BatchPage(_Base):
452
- """`GET /v1/batches`: one page, newest first."""
453
-
454
- batches: List["Batch"]
455
- next_cursor: Optional[str] = Field(default=None, alias="nextCursor")
456
-
457
-
458
239
  class BillingDetails(_Base):
459
240
  """`GET / PUT /v1/billing/details`."""
460
241
 
@@ -488,7 +269,6 @@ class Limits2(_Base):
488
269
  concurrent_transcriptions: Optional[int] = Field(default=None, alias="concurrentTranscriptions")
489
270
  creates_per_hour: Optional[int] = Field(default=None, alias="createsPerHour")
490
271
  creates_per_hour_unpaid: Optional[int] = Field(default=None, alias="createsPerHourUnpaid")
491
- files_per_batch: Optional[int] = Field(default=None, alias="filesPerBatch")
492
272
  max_file_bytes: Optional[int] = Field(default=None, alias="maxFileBytes")
493
273
  max_live_seconds: Optional[int] = Field(default=None, alias="maxLiveSeconds")
494
274
  questions_per_day: Optional[int] = Field(default=None, alias="questionsPerDay")
@@ -533,7 +313,6 @@ class Rates(_Base):
533
313
  """The rate card of a version: dollars per hour, and the least a
534
314
  transcription is billed."""
535
315
 
536
- batch_file_minimum_seconds: Optional[int] = Field(default=None, alias="batchFileMinimumSeconds")
537
316
  file_per_hour: float = Field(alias="filePerHour")
538
317
  live_per_hour: float = Field(alias="livePerHour")
539
318
  minimum_seconds: int = Field(alias="minimumSeconds")
@@ -646,6 +425,64 @@ running transcriptions go on below zero, never how far."""
646
425
  top_up_url: str = Field(alias="topUpUrl")
647
426
 
648
427
 
428
+ class Blocked(_Base):
429
+ at: str
430
+ contact: str
431
+ reason: str
432
+
433
+
434
+ class CurrentRecording(_Base):
435
+ """The recording in flight (`currentRecording`): on a transcription, its
436
+ open recording; on a broadcast, the open recording
437
+ of a live transcription feeding it, which is what its listeners are sent."""
438
+
439
+ recording_id: str = Field(alias="recordingId")
440
+ started_at: Optional[str] = Field(default=None, alias="startedAt")
441
+ state: RecordingState
442
+ transcription_id: Optional[str] = Field(default=None, alias="transcriptionId")
443
+
444
+
445
+ class LastRecording(_Base):
446
+ """A live transcription's newest recording (`lastRecording`): what
447
+ its row in a list reads from — how long it ran, when, and whether it is
448
+ still running — and the recording a tap on that row opens."""
449
+
450
+ duration_s: Optional[float] = Field(default=None, alias="durationS")
451
+ recording_id: str = Field(alias="recordingId")
452
+ started_at: Optional[str] = Field(default=None, alias="startedAt")
453
+ state: RecordingState
454
+ title: Optional[str] = None
455
+ transcription_id: Optional[str] = Field(default=None, alias="transcriptionId")
456
+
457
+
458
+ class ConfidencePolicy(_Base):
459
+ """Env defaults, overridden per broadcast, transcription or recording."""
460
+
461
+ hide_below: Optional[float] = Field(default=None, alias="hideBelow")
462
+ low_threshold: Optional[float] = Field(default=None, alias="lowThreshold")
463
+ suppress_count: Optional[int] = Field(default=None, alias="suppressCount")
464
+
465
+
466
+ class Settings(_Base):
467
+ """A broadcast's, transcription's or recording's capture settings (the
468
+ design's settings sheet): what a recording made in it starts with."""
469
+
470
+ confidence: Optional["ConfidencePolicy"] = None
471
+ latency_mode: Optional[LatencyMode] = Field(default=None, alias="latencyMode")
472
+ max_duration_s: Optional[int] = Field(default=None, alias="maxDurationS")
473
+ max_pause_s: Optional[int] = Field(default=None, alias="maxPauseS")
474
+ silence_auto_pause_s: Optional[int] = Field(default=None, alias="silenceAutoPauseS")
475
+ speaker_labels: Optional[SpeakerLabels] = Field(default=None, alias="speakerLabels")
476
+ spoken_language: Optional[str] = Field(default=None, alias="spokenLanguage")
477
+ summary: Optional[bool] = None
478
+ summary_translations: Optional[bool] = Field(default=None, alias="summaryTranslations")
479
+ title: Optional[bool] = None
480
+ voice: Optional[bool] = None
481
+ voice_by_lang: Optional[Dict[str, str]] = Field(default=None, alias="voiceByLang")
482
+ voice_cloning: Optional[bool] = Field(default=None, alias="voiceCloning")
483
+ voice_cloning_consent: Optional[bool] = Field(default=None, alias="voiceCloningConsent")
484
+
485
+
649
486
  class TranscriptionRef(_Base):
650
487
  """A live transcription feeding a broadcast (`Broadcast.transcriptions`)."""
651
488
 
@@ -686,6 +523,16 @@ those live transcriptions."""
686
523
  updated_at: str = Field(alias="updatedAt")
687
524
 
688
525
 
526
+ class BroadcastRef(_Base):
527
+ """A broadcast a live transcription feeds (`Transcription.broadcasts`)."""
528
+
529
+ access: Access
530
+ code: str
531
+ id: str
532
+ name: str
533
+ share_url: str = Field(alias="shareUrl")
534
+
535
+
689
536
  class Limits(_Base):
690
537
  audio_buffer_s: int = Field(alias="audioBufferS")
691
538
  max_duration_s: int = Field(alias="maxDurationS")
@@ -862,13 +709,6 @@ class CreateAudioSource(_Base):
862
709
  url: str
863
710
 
864
711
 
865
- class CreateBatch(_Base):
866
- """`POST /v1/batches`."""
867
-
868
- name: Optional[str] = None
869
- settings: Optional["BatchSettings"] = None
870
-
871
-
872
712
  class CreateBroadcast(_Base):
873
713
  """`POST /v1/broadcasts`."""
874
714
 
@@ -999,6 +839,13 @@ class CropRange(_Base):
999
839
  start_s: Optional[float] = Field(default=None, alias="startS")
1000
840
 
1001
841
 
842
+ class CropWindow(_Base):
843
+ """A window of the source, in seconds into it. `end_s` absent = to the end."""
844
+
845
+ end_s: Optional[float] = Field(default=None, alias="endS")
846
+ start_s: float = Field(alias="startS")
847
+
848
+
1002
849
  class DraftSegment(_Base):
1003
850
  speaker: int
1004
851
  start: float
@@ -1031,6 +878,25 @@ class InputOptions(_Base):
1031
878
  url: Optional[str] = None
1032
879
 
1033
880
 
881
+ class JobProgress(_Base):
882
+ fetch_total_bytes: Optional[int] = Field(default=None, alias="fetchTotalBytes")
883
+ fetched_bytes: Optional[int] = Field(default=None, alias="fetchedBytes")
884
+ pct: int
885
+ phase: Optional[str] = None
886
+ phase_pct: Optional[int] = Field(default=None, alias="phasePct")
887
+ processed_s: float = Field(alias="processedS")
888
+ segments_done: int = Field(alias="segmentsDone")
889
+ segments_total: Optional[int] = Field(default=None, alias="segmentsTotal")
890
+ total_s: Optional[float] = Field(default=None, alias="totalS")
891
+
892
+
893
+ class JobQueue(_Base):
894
+ """Why a `queued` job is waiting, and where it stands (`Job.queue`)."""
895
+
896
+ position: int
897
+ reason: QueueReason
898
+
899
+
1034
900
  class KnownVoice(_Base):
1035
901
  """One voice a realtime session may recognise a speaker as.
1036
902
 
@@ -1309,6 +1175,22 @@ class PublisherInfo(_Base):
1309
1175
  machine_id: Optional[str] = Field(default=None, alias="machineId")
1310
1176
 
1311
1177
 
1178
+ class SourceInfo(_Base):
1179
+ """What the recording was made from (design `j.meta`: "M4A · 34.2 MB",
1180
+ "Video URL")."""
1181
+
1182
+ bytes: Optional[int] = None
1183
+ duration_s: Optional[float] = Field(default=None, alias="durationS")
1184
+ embed_id: Optional[str] = Field(default=None, alias="embedId")
1185
+ format: Optional[str] = None
1186
+ kind: SourceKind
1187
+ mime: Optional[str] = None
1188
+ name: str
1189
+ site: Optional[str] = None
1190
+ thumbnail_url: Optional[str] = Field(default=None, alias="thumbnailUrl")
1191
+ url: Optional[str] = None
1192
+
1193
+
1312
1194
  class Recording(_Base):
1313
1195
  created_at: str = Field(alias="createdAt")
1314
1196
  end_reason: Optional[str] = Field(default=None, alias="endReason")
@@ -1551,6 +1433,26 @@ Flattened into its owner."""
1551
1433
  views: int
1552
1434
 
1553
1435
 
1436
+ class TranscriptionJob(_Base):
1437
+ attempts: int
1438
+ created_at: str = Field(alias="createdAt")
1439
+ crop: Optional["CropWindow"] = None
1440
+ error: Optional[str] = None
1441
+ error_code: Optional[str] = Field(default=None, alias="errorCode")
1442
+ finished_at: Optional[str] = Field(default=None, alias="finishedAt")
1443
+ has_audio: bool = Field(alias="hasAudio")
1444
+ has_video: Optional[bool] = Field(default=None, alias="hasVideo")
1445
+ peaks: Optional[List[float]] = None
1446
+ preview: Optional[bool] = None
1447
+ progress: "JobProgress"
1448
+ queue: Optional["JobQueue"] = None
1449
+ recording_id: str = Field(alias="recordingId")
1450
+ source: Optional["SourceInfo"] = None
1451
+ started_at: Optional[str] = Field(default=None, alias="startedAt")
1452
+ state: JobState
1453
+ transcription_id: str = Field(alias="transcriptionId")
1454
+
1455
+
1554
1456
  class SharedTranscript(_Base):
1555
1457
  """What the public page learns from a code: the transcript's name, who
1556
1458
  shared it (display name only), what the link allows, and the recording
@@ -1624,6 +1526,14 @@ class TagList(_Base):
1624
1526
  tags: List["Tag"]
1625
1527
 
1626
1528
 
1529
+ class TagRef(_Base):
1530
+ """A tag as a transcription lists it."""
1531
+
1532
+ color: Literal["coral", "amber", "lime", "green", "teal", "blue", "violet", "pink"]
1533
+ id: str
1534
+ name: str
1535
+
1536
+
1627
1537
  class TagTranscriptions(_Base):
1628
1538
  """Transcriptions to put the tag on and to take it off, by id."""
1629
1539
 
@@ -1644,6 +1554,39 @@ class TranscriptSnapshot(_Base):
1644
1554
  translation_pending: Optional[bool] = Field(default=None, alias="translationPending")
1645
1555
 
1646
1556
 
1557
+ class Transcription(_Base):
1558
+ """A transcription: a file, a link (with its `transcription` job), or
1559
+ something captured as it is spoken (`live: true`: a microphone, a
1560
+ stream, an RTMP encoder), which may feed broadcasts (`broadcasts`)."""
1561
+
1562
+ archived_at: Optional[str] = Field(default=None, alias="archivedAt")
1563
+ blocked: Optional["Blocked"] = None
1564
+ broadcasts: Optional[List["BroadcastRef"]] = None
1565
+ code: str
1566
+ created_at: str = Field(alias="createdAt")
1567
+ created_by_api_key_id: Optional[str] = Field(default=None, alias="createdByApiKeyId")
1568
+ current_recording: Optional["CurrentRecording"] = Field(default=None, alias="currentRecording")
1569
+ id: str
1570
+ job: Optional["TranscriptionJob"] = None
1571
+ kind: Literal["transcription"] = "transcription"
1572
+ last_recorded_at: Optional[str] = Field(default=None, alias="lastRecordedAt")
1573
+ last_recording: Optional["LastRecording"] = Field(default=None, alias="lastRecording")
1574
+ lifecycle: Lifecycle
1575
+ live: Optional[bool] = None
1576
+ locked: Optional[bool] = None
1577
+ name: str
1578
+ origin: Literal["dashboard", "api", "extension", "desktop_app", "obs", "wordpress", "payload", "strapi", "sanity", "contentful", "mcp", "broadcast", "unknown"]
1579
+ owner_principal_id: str = Field(alias="ownerPrincipalId")
1580
+ purge_after: Optional[str] = Field(default=None, alias="purgeAfter")
1581
+ recording_count: int = Field(alias="recordingCount")
1582
+ settings: "Settings"
1583
+ share_code: Optional[str] = Field(default=None, alias="shareCode")
1584
+ share_url: str = Field(alias="shareUrl")
1585
+ source: "CaptureSource"
1586
+ tags: Optional[List["TagRef"]] = None
1587
+ updated_at: str = Field(alias="updatedAt")
1588
+
1589
+
1647
1590
  class TranscriptionLookup(_Base):
1648
1591
  transcription: Optional["Transcription"] = None
1649
1592
 
@@ -1902,6 +1845,14 @@ class SessionLoading(_Base):
1902
1845
  type: Literal["session.loading"] = "session.loading"
1903
1846
 
1904
1847
 
1848
+ class SessionQueued(_Base):
1849
+ buffered_s: Optional[float] = Field(default=None, alias="bufferedS")
1850
+ dropped_s: Optional[float] = Field(default=None, alias="droppedS")
1851
+ position: Optional[int] = None
1852
+ type: Literal["session.queued"] = "session.queued"
1853
+ waited_s: float = Field(alias="waitedS")
1854
+
1855
+
1905
1856
  class SessionReady(_Base):
1906
1857
  type: Literal["session.ready"] = "session.ready"
1907
1858
 
@@ -2080,7 +2031,7 @@ class ErrorFrame(_Base):
2080
2031
  type: Literal["error"] = "error"
2081
2032
 
2082
2033
 
2083
- ServerFrame = Annotated[Union[SessionLoading, SessionReady, TranscriptCommitted, TranscriptDraft, TranscriptWords, TranscriptAmended, TranscriptLatency, TranscriptSpeaker, TranscriptSpeakerGender, TranscriptSpeakerIdentified, TranscriptSpeakerNumbers, TranscriptSentence, TranscriptTranslated, TranscriptVoice, TranscriptVoiceStatus, TranscriptLanguageStatus, TranscriptRetracted, TranscriptHeld, TranscriptProgress, StreamPull, SourceProgress, SourcePrepared, SessionDone, ErrorFrame], Field(discriminator="type")]
2034
+ ServerFrame = Annotated[Union[SessionLoading, SessionQueued, SessionReady, TranscriptCommitted, TranscriptDraft, TranscriptWords, TranscriptAmended, TranscriptLatency, TranscriptSpeaker, TranscriptSpeakerGender, TranscriptSpeakerIdentified, TranscriptSpeakerNumbers, TranscriptSentence, TranscriptTranslated, TranscriptVoice, TranscriptVoiceStatus, TranscriptLanguageStatus, TranscriptRetracted, TranscriptHeld, TranscriptProgress, StreamPull, SourceProgress, SourcePrepared, SessionDone, ErrorFrame], Field(discriminator="type")]
2084
2035
 
2085
2036
  class SubscribeUpdate(_Base):
2086
2037
  audio: Optional[bool] = None
@@ -2226,7 +2177,7 @@ class SubscribeGap(_Base):
2226
2177
 
2227
2178
  SubscriberFrame = Annotated[Union[SubscribeReady, RecordingState, RecordingSettings, RecordingSilence, RecordingExpiring, TranscriptCorrected, TranscriptSpeakerGender, TranscriptSpeakerIdentified, TranscriptSpeakerNamed, TranscriptSpeakerNumbers, RecordingStarted, JobProgress, StreamIngest, RecordingEnded, Audience, SubscribeGap], Field(discriminator="type")]
2228
2179
 
2229
- SERVER_FRAME_TYPES = frozenset(["session.loading", "session.ready", "transcript.committed", "transcript.draft", "transcript.words", "transcript.amended", "transcript.latency", "transcript.speaker", "transcript.speakerGender", "transcript.speakerIdentified", "transcript.speakerNumbers", "transcript.sentence", "transcript.translated", "transcript.voice", "transcript.voiceStatus", "transcript.languageStatus", "transcript.retracted", "transcript.held", "transcript.progress", "stream.pull", "source.progress", "source.prepared", "session.done", "error"])
2180
+ SERVER_FRAME_TYPES = frozenset(["session.loading", "session.queued", "session.ready", "transcript.committed", "transcript.draft", "transcript.words", "transcript.amended", "transcript.latency", "transcript.speaker", "transcript.speakerGender", "transcript.speakerIdentified", "transcript.speakerNumbers", "transcript.sentence", "transcript.translated", "transcript.voice", "transcript.voiceStatus", "transcript.languageStatus", "transcript.retracted", "transcript.held", "transcript.progress", "stream.pull", "source.progress", "source.prepared", "session.done", "error"])
2230
2181
 
2231
2182
  _server_frame_adapter: TypeAdapter[ServerFrame] = TypeAdapter(ServerFrame)
2232
2183
 
@@ -28,7 +28,6 @@ import httpx
28
28
  from ._http import DEFAULT_BASE_URL, Transport
29
29
  from .realtime import Realtime
30
30
  from .resources import (
31
- Batches,
32
31
  Billing,
33
32
  Broadcasts,
34
33
  Connect,
@@ -84,7 +83,6 @@ class VocalStack:
84
83
  self.realtime = Realtime(t, ready_deadline_s)
85
84
  self.broadcasts = Broadcasts(t)
86
85
  self.transcriptions = Transcriptions(t)
87
- self.batches = Batches(t)
88
86
  self.recordings = Recordings(t, ready_deadline_s)
89
87
  self.jobs = Jobs(t)
90
88
  self.corrections = Corrections(t)
@@ -382,7 +382,7 @@ async def pace_realtime(
382
382
  speed: float = 1.0,
383
383
  ) -> None:
384
384
  """Feed a whole PCM buffer at ``speed``× realtime, then ``eof()``.
385
- Convenience for file/batch sources against the realtime API."""
385
+ Convenience for file sources against the realtime API."""
386
386
  bytes_per_second = 16000 * 4
387
387
  chunk = max(4, int(bytes_per_second * chunk_seconds) & ~3)
388
388
  start = time.monotonic()
@@ -349,115 +349,6 @@ class Transcriptions(_ParentResource):
349
349
  return _one(m.Transcription, data)
350
350
 
351
351
 
352
- class Batches(_Group):
353
- """``vs.batches``: several files transcribed together, for less than a
354
- transcription each (one create, one minimum charge). Create a batch, add
355
- each file or link with ``add_file``, then ``start`` it; ``get`` shows its
356
- files (each a transcription with its job) and their counts. A batch's
357
- files get no summary, title, share link or spoken translations."""
358
-
359
- @staticmethod
360
- def _path(id: str, rest: str = "") -> str:
361
- return f"/v1/batches/{_p(id)}{rest}"
362
-
363
- async def create(self, *, idempotency_key: Optional[str] = None, **fields: Any) -> m.Batch:
364
- """An open batch, ready for files (``name`` defaults to "Batch of
365
- <date>"; ``settings``: ``spoken_language``, ``speaker_labels``,
366
- ``latency_mode`` for every file)."""
367
- body = _body(m.CreateBatch, fields)
368
- return _one(m.Batch, await self._t.json("POST", "/v1/batches", json_body=body, idempotency_key=idempotency_key))
369
-
370
- async def list(self) -> List[m.Batch]:
371
- """Every batch, newest first (every page of ``list_page``); each with its counts, not its files."""
372
- items = await _all_pages(lambda c: self._t.json("GET", "/v1/batches", query={"cursor": c}), "batches")
373
- return [m.Batch.model_validate(x) for x in items]
374
-
375
- async def list_page(self, *, cursor: Optional[str] = None, limit: Optional[int] = None) -> m.BatchPage:
376
- """One page of batches, newest first; ``cursor`` is the previous page's ``next_cursor``."""
377
- return _one(m.BatchPage, await self._t.json("GET", "/v1/batches", query={"cursor": cursor, "limit": limit}))
378
-
379
- async def get(self, id: str) -> m.Batch:
380
- """A batch with its files (``files``, each a transcription with its job), in the order they were added."""
381
- return _one(m.Batch, await self._t.json("GET", self._path(id)))
382
-
383
- async def delete(self, id: str) -> None:
384
- """Discard a batch that was never started, with its files (nothing was charged)."""
385
- await self._t.json("DELETE", self._path(id))
386
-
387
- async def add_file(
388
- self,
389
- id: str,
390
- *,
391
- file: Optional[Upload] = None,
392
- file_name: Optional[str] = None,
393
- idempotency_key: Optional[str] = None,
394
- **fields: Any,
395
- ) -> m.Transcription:
396
- """Add one file (``file``: a path, bytes or an open binary file) or
397
- public link (``url=...``) to an open batch: a transcription of its
398
- own (``batch_id``), waiting for the batch's ``start``. Of the
399
- transcription options it takes ``name``, ``start_s``, ``end_s`` and
400
- ``duration_s`` (the settings are the batch's)."""
401
- options = _body(m.CreateTranscription, fields)
402
- if file is None:
403
- data = await self._t.json("POST", self._path(id, "/files"), json_body=options, idempotency_key=idempotency_key)
404
- else:
405
- part = await asyncio.to_thread(_file_part, file, file_name, "upload")
406
- data = await self._t.json(
407
- "POST",
408
- self._path(id, "/files"),
409
- data={"options": json.dumps(options)},
410
- files={"file": part},
411
- idempotency_key=idempotency_key,
412
- )
413
- return _one(m.Transcription, data)
414
-
415
- async def add_url(self, id: str, url: str, **fields: Any) -> m.Transcription:
416
- """``add_file(id, url=url, ...)``."""
417
- return await self.add_file(id, url=url, **fields)
418
-
419
- async def start(self, id: str, *, idempotency_key: Optional[str] = None) -> m.Batch:
420
- """Seal the batch and queue its files, longest first; ``wait`` follows it to the end."""
421
- return _one(m.Batch, await self._t.json("POST", self._path(id, "/start"), idempotency_key=idempotency_key))
422
-
423
- async def export(self, id: str, format: str) -> bytes:
424
- """Every finished file's transcript in one ``format`` (``json``,
425
- ``srt``, ``xlsx`` or ``pdf``, as ``recordings.export`` makes one), in
426
- one zip: its bytes. Files still running or failed are left out."""
427
- resp = await self._t.raw("GET", self._path(id, "/export"), headers={"accept": "*/*"}, query={"format": format})
428
- return resp.content
429
-
430
- async def stop(self, id: str) -> m.Batch:
431
- """Stop every file not finished yet: queued files never start, a
432
- running one is cancelled and billed for what it transcribed."""
433
- return _one(m.Batch, await self._t.json("POST", self._path(id, "/stop")))
434
-
435
- async def wait(
436
- self,
437
- id: str,
438
- *,
439
- interval_s: float = 5.0,
440
- timeout_s: Optional[float] = None,
441
- on_progress: Optional[Callable[[m.Batch], None]] = None,
442
- ) -> m.Batch:
443
- """Poll a started batch until it is ``done`` (every file finished or
444
- failed) or ``stopped``, and return it with its files. It does not
445
- raise on a failed file: check ``counts.failed`` and each file's
446
- ``job.state``. ``on_progress`` sees every read, the last one included."""
447
- deadline = None if timeout_s is None else time.monotonic() + timeout_s
448
- while True:
449
- batch = await self.get(id)
450
- if on_progress is not None:
451
- on_progress(batch)
452
- if batch.state in ("done", "stopped") and batch.finished_at:
453
- return batch
454
- if batch.state == "open":
455
- raise VocalStackError("invalid_state", "the batch is open: start it before waiting for it")
456
- if deadline is not None and time.monotonic() + interval_s > deadline:
457
- raise VocalStackError("timeout", f"batch still {batch.state} after {timeout_s}s", retryable=True)
458
- await asyncio.sleep(interval_s)
459
-
460
-
461
352
  class Recordings(_Group):
462
353
  """``vs.recordings``: one take, live or uploaded."""
463
354
 
File without changes
File without changes