vocalstack 1.0.0a0__tar.gz → 1.0.0a2__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.
- vocalstack-1.0.0a2/.gitignore +8 -0
- vocalstack-1.0.0a2/LICENSE +21 -0
- {vocalstack-1.0.0a0 → vocalstack-1.0.0a2}/PKG-INFO +16 -7
- {vocalstack-1.0.0a0 → vocalstack-1.0.0a2}/README.md +13 -5
- {vocalstack-1.0.0a0/skills/vocalstack → vocalstack-1.0.0a2}/SKILL.md +49 -16
- {vocalstack-1.0.0a0 → vocalstack-1.0.0a2}/pyproject.toml +5 -7
- {vocalstack-1.0.0a0 → vocalstack-1.0.0a2/skills/vocalstack}/SKILL.md +49 -16
- {vocalstack-1.0.0a0 → vocalstack-1.0.0a2}/src/vocalstack/__init__.py +1 -1
- {vocalstack-1.0.0a0 → vocalstack-1.0.0a2}/src/vocalstack/_http.py +24 -15
- {vocalstack-1.0.0a0 → vocalstack-1.0.0a2}/src/vocalstack/_models.py +652 -154
- {vocalstack-1.0.0a0 → vocalstack-1.0.0a2}/src/vocalstack/client.py +6 -0
- {vocalstack-1.0.0a0 → vocalstack-1.0.0a2}/src/vocalstack/errors.py +14 -0
- {vocalstack-1.0.0a0 → vocalstack-1.0.0a2}/src/vocalstack/realtime.py +116 -24
- {vocalstack-1.0.0a0 → vocalstack-1.0.0a2}/src/vocalstack/resources.py +440 -32
- vocalstack-1.0.0a0/.gitignore +0 -9
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 VocalStack
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: vocalstack
|
|
3
|
-
Version: 1.0.
|
|
3
|
+
Version: 1.0.0a2
|
|
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
|
-
License: MIT
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
License-File: LICENSE
|
|
7
8
|
Requires-Python: >=3.10
|
|
8
9
|
Requires-Dist: httpx>=0.27
|
|
9
10
|
Requires-Dist: pydantic>=2.7
|
|
@@ -17,6 +18,8 @@ The VocalStack speech API for Python (3.10 or newer, asyncio): one client,
|
|
|
17
18
|
are pydantic models generated from the API's one OpenAPI document
|
|
18
19
|
(`/v1/openapi.json`), with snake_case attributes.
|
|
19
20
|
|
|
21
|
+
Read more about the [VocalStack Python SDK](https://vocalstack.com/#developers) and our other SDKs on the [VocalStack website](https://vocalstack.com/).
|
|
22
|
+
|
|
20
23
|
```python
|
|
21
24
|
import asyncio, os
|
|
22
25
|
from vocalstack import VocalStack
|
|
@@ -32,22 +35,27 @@ async def main():
|
|
|
32
35
|
asyncio.run(main())
|
|
33
36
|
```
|
|
34
37
|
|
|
35
|
-
<!-- sdk-docs: generated
|
|
38
|
+
<!-- sdk-docs: generated from the VocalStack API guides; edits here are overwritten -->
|
|
36
39
|
|
|
37
40
|
## Install
|
|
38
41
|
|
|
39
|
-
|
|
42
|
+
```sh
|
|
43
|
+
pip install vocalstack
|
|
44
|
+
```
|
|
40
45
|
|
|
41
46
|
## Guides
|
|
42
47
|
|
|
43
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.
|
|
44
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.
|
|
45
|
-
- [Transcribe a link](https://github.com/vocalstack/python-sdk/blob/main/docs/transcribe-a-link.md): The server fetches a public URL or a
|
|
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
|
+
- [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.
|
|
46
53
|
- [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.
|
|
47
54
|
- [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.
|
|
48
55
|
- [Go live](https://github.com/vocalstack/python-sdk/blob/main/docs/go-live.md): Publish live audio to a stored transcription, and put it on a broadcast link listeners open. Control is HTTP; audio is one WebSocket.
|
|
49
56
|
- [Listen](https://github.com/vocalstack/python-sdk/blob/main/docs/listen.md): Follow a recording as it happens, in any language: subscribe, catch up from a snapshot, then apply live frames in order.
|
|
50
57
|
- [Live translation and spoken translations](https://github.com/vocalstack/python-sdk/blob/main/docs/translation-and-voices.md): Translate a stream as it is spoken, and have each translated sentence spoken back in a voice. Text frames and audio clips, over the same socket.
|
|
58
|
+
- [Connect an app with one click](https://github.com/vocalstack/python-sdk/blob/main/docs/connect-an-app.md): Let the people who use your app connect their VocalStack account with one Authenticate click — OAuth 2.0 authorization code with PKCE, where the access token is an API key — instead of pasting a key.
|
|
51
59
|
- [Errors, retries and limits](https://github.com/vocalstack/python-sdk/blob/main/docs/errors-and-limits.md): One error envelope everywhere; what to retry (429, 503 unavailable) and how to make a retry safe (Idempotency-Key); credit, plan limits and the 402s.
|
|
52
60
|
|
|
53
61
|
<!-- /sdk-docs -->
|
|
@@ -61,6 +69,7 @@ Not on PyPI yet. The API serves its own builds to every account: [https://api.vo
|
|
|
61
69
|
| transcription | a file, link, microphone or stream, transcribed and stored | `vs.transcriptions` |
|
|
62
70
|
| recording | one take inside a broadcast or a transcription | `vs.recordings` |
|
|
63
71
|
| 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` |
|
|
64
73
|
|
|
65
74
|
The other groups: `vs.corrections`, `vs.share_links`, `vs.voices`,
|
|
66
75
|
`vs.search`, `vs.me`, `vs.languages`, `vs.rtmp`, `vs.events`, `vs.meta`.
|
|
@@ -113,5 +122,5 @@ async with VocalStack(api_key=key) as vs:
|
|
|
113
122
|
`status`, `retryable`, `details` (quota numbers, code suggestions), `headers`
|
|
114
123
|
and `retry_after`.
|
|
115
124
|
|
|
116
|
-
Verified against
|
|
117
|
-
and against a running API
|
|
125
|
+
Verified against a language-agnostic conformance suite shared by every
|
|
126
|
+
VocalStack SDK and against a running API. For agents: `SKILL.md`.
|
|
@@ -5,6 +5,8 @@ The VocalStack speech API for Python (3.10 or newer, asyncio): one client,
|
|
|
5
5
|
are pydantic models generated from the API's one OpenAPI document
|
|
6
6
|
(`/v1/openapi.json`), with snake_case attributes.
|
|
7
7
|
|
|
8
|
+
Read more about the [VocalStack Python SDK](https://vocalstack.com/#developers) and our other SDKs on the [VocalStack website](https://vocalstack.com/).
|
|
9
|
+
|
|
8
10
|
```python
|
|
9
11
|
import asyncio, os
|
|
10
12
|
from vocalstack import VocalStack
|
|
@@ -20,22 +22,27 @@ async def main():
|
|
|
20
22
|
asyncio.run(main())
|
|
21
23
|
```
|
|
22
24
|
|
|
23
|
-
<!-- sdk-docs: generated
|
|
25
|
+
<!-- sdk-docs: generated from the VocalStack API guides; edits here are overwritten -->
|
|
24
26
|
|
|
25
27
|
## Install
|
|
26
28
|
|
|
27
|
-
|
|
29
|
+
```sh
|
|
30
|
+
pip install vocalstack
|
|
31
|
+
```
|
|
28
32
|
|
|
29
33
|
## Guides
|
|
30
34
|
|
|
31
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.
|
|
32
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.
|
|
33
|
-
- [Transcribe a link](https://github.com/vocalstack/python-sdk/blob/main/docs/transcribe-a-link.md): The server fetches a public URL or a
|
|
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
|
+
- [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.
|
|
34
40
|
- [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.
|
|
35
41
|
- [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.
|
|
36
42
|
- [Go live](https://github.com/vocalstack/python-sdk/blob/main/docs/go-live.md): Publish live audio to a stored transcription, and put it on a broadcast link listeners open. Control is HTTP; audio is one WebSocket.
|
|
37
43
|
- [Listen](https://github.com/vocalstack/python-sdk/blob/main/docs/listen.md): Follow a recording as it happens, in any language: subscribe, catch up from a snapshot, then apply live frames in order.
|
|
38
44
|
- [Live translation and spoken translations](https://github.com/vocalstack/python-sdk/blob/main/docs/translation-and-voices.md): Translate a stream as it is spoken, and have each translated sentence spoken back in a voice. Text frames and audio clips, over the same socket.
|
|
45
|
+
- [Connect an app with one click](https://github.com/vocalstack/python-sdk/blob/main/docs/connect-an-app.md): Let the people who use your app connect their VocalStack account with one Authenticate click — OAuth 2.0 authorization code with PKCE, where the access token is an API key — instead of pasting a key.
|
|
39
46
|
- [Errors, retries and limits](https://github.com/vocalstack/python-sdk/blob/main/docs/errors-and-limits.md): One error envelope everywhere; what to retry (429, 503 unavailable) and how to make a retry safe (Idempotency-Key); credit, plan limits and the 402s.
|
|
40
47
|
|
|
41
48
|
<!-- /sdk-docs -->
|
|
@@ -49,6 +56,7 @@ Not on PyPI yet. The API serves its own builds to every account: [https://api.vo
|
|
|
49
56
|
| transcription | a file, link, microphone or stream, transcribed and stored | `vs.transcriptions` |
|
|
50
57
|
| recording | one take inside a broadcast or a transcription | `vs.recordings` |
|
|
51
58
|
| 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` |
|
|
52
60
|
|
|
53
61
|
The other groups: `vs.corrections`, `vs.share_links`, `vs.voices`,
|
|
54
62
|
`vs.search`, `vs.me`, `vs.languages`, `vs.rtmp`, `vs.events`, `vs.meta`.
|
|
@@ -101,5 +109,5 @@ async with VocalStack(api_key=key) as vs:
|
|
|
101
109
|
`status`, `retryable`, `details` (quota numbers, code suggestions), `headers`
|
|
102
110
|
and `retry_after`.
|
|
103
111
|
|
|
104
|
-
Verified against
|
|
105
|
-
and against a running API
|
|
112
|
+
Verified against a language-agnostic conformance suite shared by every
|
|
113
|
+
VocalStack SDK and against a running API. For agents: `SKILL.md`.
|
|
@@ -20,6 +20,9 @@ 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).
|
|
23
26
|
- A **broadcast** is a link listeners open to follow a live recording.
|
|
24
27
|
- A **realtime session** (`vs.realtime`) is a socket you stream audio into and
|
|
25
28
|
read the transcript from. Nothing is stored. "Session" means only this.
|
|
@@ -31,16 +34,6 @@ python3 -m venv .venv && . .venv/bin/activate # system Pythons refuse a bare p
|
|
|
31
34
|
pip install vocalstack # Python 3.10 or newer
|
|
32
35
|
```
|
|
33
36
|
|
|
34
|
-
Before the package is published, any account installs
|
|
35
|
-
the private build its environment serves: the API reference's Install section
|
|
36
|
-
(`<base>/docs`, or Developers -> API reference in the app) and `<base>/sdk/latest`
|
|
37
|
-
give the exact commands, which send `VOCALSTACK_API_KEY` to that host only.
|
|
38
|
-
Or install it from a local checkout of the repository:
|
|
39
|
-
|
|
40
|
-
```sh
|
|
41
|
-
pip install -e /absolute/path/to/transcribe/sdks/python
|
|
42
|
-
```
|
|
43
|
-
|
|
44
37
|
Live transcription (program 4) also needs `ffmpeg` on PATH.
|
|
45
38
|
|
|
46
39
|
Speaker numbers start at 0. A speaker of `-1` means speaker labels were off
|
|
@@ -57,8 +50,7 @@ for that transcription, so print the text without a speaker.
|
|
|
57
50
|
- Scopes: `read` (read transcripts, search, export), `write` and
|
|
58
51
|
`recordings:publish` (create transcriptions, stream live audio), or `*`.
|
|
59
52
|
- Base URL: `https://api.vocalstack.com` (the default). Override it with
|
|
60
|
-
`VOCALSTACK_BASE_URL
|
|
61
|
-
`https://api.e2e.vocalstack.com` for e2e.
|
|
53
|
+
`VOCALSTACK_BASE_URL` (for example, a local or staging server).
|
|
62
54
|
|
|
63
55
|
Every program below opens the client the same way, inside `async def main()`
|
|
64
56
|
run with `asyncio.run(main())` (a `None` base URL means the default):
|
|
@@ -105,7 +97,9 @@ async def main(path): # mp3, wav, flac, ogg, m4a, mp4 ...
|
|
|
105
97
|
async with VocalStack(os.environ.get("VOCALSTACK_BASE_URL"), api_key=os.environ["VOCALSTACK_API_KEY"]) as vs:
|
|
106
98
|
tx = await vs.transcriptions.create(
|
|
107
99
|
file=path, # a path, bytes, or an open binary file
|
|
108
|
-
settings={"spokenLanguage": "en", "speakerLabels": "on"}, # both optional; "auto" | "on" | "off"
|
|
100
|
+
settings={"spokenLanguage": "en", "speakerLabels": "on"}, # both optional; "auto" (default) | "on" | "off"
|
|
101
|
+
# Off unless asked for: settings summary, title, summaryTranslations,
|
|
102
|
+
# voice (spoken translations), and share_link=True (a public share link).
|
|
109
103
|
)
|
|
110
104
|
job = await vs.jobs.wait(tx.job.recording_id)
|
|
111
105
|
if job.state != "done":
|
|
@@ -191,6 +185,43 @@ async def main(q): # 2+ characters
|
|
|
191
185
|
asyncio.run(main(sys.argv[1]))
|
|
192
186
|
```
|
|
193
187
|
|
|
188
|
+
By meaning, in any language, even with none of the words (vector search). Each
|
|
189
|
+
hit's `passage` is its whole paragraph: give the passages to your own model as
|
|
190
|
+
context to answer questions about what was said (RAG). Pay as you go counts 60
|
|
191
|
+
an hour (`allowance`); Premium and Enterprise have no limit.
|
|
192
|
+
|
|
193
|
+
```python
|
|
194
|
+
res = await vs.search.vector("how much does housing cost")
|
|
195
|
+
for h in res.hits:
|
|
196
|
+
print(f"{h.similarity:.2f}", h.passage.media_start, h.passage.text)
|
|
197
|
+
```
|
|
198
|
+
|
|
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
|
+
|
|
194
225
|
## Errors
|
|
195
226
|
|
|
196
227
|
A failed call raises `vocalstack.VocalStackError` with `.code`, `.message`,
|
|
@@ -203,8 +234,9 @@ its `Retry-After`, twice by default (`max_retries` on the client).
|
|
|
203
234
|
|---|---|---|
|
|
204
235
|
| 401 | `invalid_token`, `key_expired`, `key_revoked` | Check `VOCALSTACK_API_KEY`. Use the replacement key, or create a new one. |
|
|
205
236
|
| 403 | `forbidden` | Usually a missing scope: the message names it, and `e.headers["www-authenticate"]` lists the scopes the route needs. A suspended key or account gets `suspended`. |
|
|
206
|
-
|
|
|
207
|
-
|
|
|
237
|
+
| 403 | `terms_acceptance_required` | The account has not accepted the current Terms of Service and Privacy Policy: someone signs in to the Dashboard and accepts them (an organisation's keys work once an owner or admin has). Not retryable until then. |
|
|
238
|
+
| 402 | `insufficient_credit` | The account's credit is out, or does not cover the transcription: every transcription is billed at least the plan's minimum (`rates.minimumSeconds`, 5 minutes). `details`: `balance`, `plan`, `topUpUrl`, and `required` when the cost was known up front. Add credit; `await vs.billing.get()` shows the balance. |
|
|
239
|
+
| 409 | `payment_method_required` | Auto top-up (`vs.billing.set_auto_top_up`) and the portal (`vs.billing.portal()`) need a payment first (`vs.billing.create_checkout(kind="topup", amount=25, accept_terms=True)` opens one; a signed-in person's token, never an API key). |
|
|
208
240
|
| 402 | `quota_exceeded` | The plan's transcriptions-at-once limit is reached. `details`: `metric` (`concurrent_transcriptions`), `limit`, `used`, `plan`, `topUpUrl`. |
|
|
209
241
|
| 429 | `rate_limited` | Retried automatically; if it still fails, wait `e.retry_after` seconds. Every response carries `RateLimit-Limit`, `RateLimit-Remaining` and `RateLimit-Reset`. |
|
|
210
242
|
| 503 | `unavailable` | The service is waking (its database was asleep). Retried automatically after `Retry-After` (2 s) on calls that are safe to repeat: GET, HEAD, DELETE, or any call with `idempotency_key=`. A create without a key is not retried: retry it yourself, with an `idempotency_key` so it cannot run twice. A realtime socket refused this way raises `unavailable`; a bare 503 there is `capacity_exceeded`. |
|
|
@@ -225,7 +257,8 @@ except VocalStackError as e:
|
|
|
225
257
|
- The other groups: `vs.transcriptions` (`list`, `get`, `update`, `archive`,
|
|
226
258
|
`restore`, `purge`, `list_share_links`, `create_share_link`), `vs.recordings`
|
|
227
259
|
(`get`, `summarize`, `summary`, `translation`, `audio_url`, `subscribe`),
|
|
228
|
-
`vs.
|
|
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`,
|
|
229
262
|
`vs.me` (`get`, `usage`), `vs.languages`, `vs.rtmp`, `vs.events`, `vs.meta`.
|
|
230
263
|
Each method is its operation's name in the OpenAPI document, in snake_case
|
|
231
264
|
(`transcriptions.create` is `POST /v1/transcriptions`).
|
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
[build-system]
|
|
2
|
-
requires = ["hatchling"]
|
|
2
|
+
requires = ["hatchling>=1.27"]
|
|
3
3
|
build-backend = "hatchling.build"
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "vocalstack"
|
|
7
|
-
version = "1.0.
|
|
7
|
+
version = "1.0.0a2"
|
|
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
|
-
license =
|
|
10
|
+
license = "MIT"
|
|
11
|
+
license-files = ["LICENSE"]
|
|
11
12
|
requires-python = ">=3.10"
|
|
12
13
|
dependencies = [
|
|
13
14
|
"httpx>=0.27",
|
|
@@ -21,11 +22,8 @@ Homepage = "https://vocalstack.com"
|
|
|
21
22
|
[tool.hatch.build.targets.wheel]
|
|
22
23
|
packages = ["src/vocalstack"]
|
|
23
24
|
|
|
24
|
-
# The Agent Skill ships inside the package (site-packages/vocalstack/SKILL.md)
|
|
25
|
-
# so an agent can find it next to the code.
|
|
26
25
|
[tool.hatch.build.targets.wheel.force-include]
|
|
27
26
|
"SKILL.md" = "vocalstack/SKILL.md"
|
|
28
27
|
|
|
29
|
-
# An explicit list: nothing else from the repository goes into the sdist.
|
|
30
28
|
[tool.hatch.build.targets.sdist]
|
|
31
|
-
only-include = ["src/vocalstack", "README.md", "SKILL.md", "skills", "pyproject.toml"]
|
|
29
|
+
only-include = ["src/vocalstack", "LICENSE", "README.md", "SKILL.md", "skills", "pyproject.toml"]
|
|
@@ -20,6 +20,9 @@ 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).
|
|
23
26
|
- A **broadcast** is a link listeners open to follow a live recording.
|
|
24
27
|
- A **realtime session** (`vs.realtime`) is a socket you stream audio into and
|
|
25
28
|
read the transcript from. Nothing is stored. "Session" means only this.
|
|
@@ -31,16 +34,6 @@ python3 -m venv .venv && . .venv/bin/activate # system Pythons refuse a bare p
|
|
|
31
34
|
pip install vocalstack # Python 3.10 or newer
|
|
32
35
|
```
|
|
33
36
|
|
|
34
|
-
Before the package is published, any account installs
|
|
35
|
-
the private build its environment serves: the API reference's Install section
|
|
36
|
-
(`<base>/docs`, or Developers -> API reference in the app) and `<base>/sdk/latest`
|
|
37
|
-
give the exact commands, which send `VOCALSTACK_API_KEY` to that host only.
|
|
38
|
-
Or install it from a local checkout of the repository:
|
|
39
|
-
|
|
40
|
-
```sh
|
|
41
|
-
pip install -e /absolute/path/to/transcribe/sdks/python
|
|
42
|
-
```
|
|
43
|
-
|
|
44
37
|
Live transcription (program 4) also needs `ffmpeg` on PATH.
|
|
45
38
|
|
|
46
39
|
Speaker numbers start at 0. A speaker of `-1` means speaker labels were off
|
|
@@ -57,8 +50,7 @@ for that transcription, so print the text without a speaker.
|
|
|
57
50
|
- Scopes: `read` (read transcripts, search, export), `write` and
|
|
58
51
|
`recordings:publish` (create transcriptions, stream live audio), or `*`.
|
|
59
52
|
- Base URL: `https://api.vocalstack.com` (the default). Override it with
|
|
60
|
-
`VOCALSTACK_BASE_URL
|
|
61
|
-
`https://api.e2e.vocalstack.com` for e2e.
|
|
53
|
+
`VOCALSTACK_BASE_URL` (for example, a local or staging server).
|
|
62
54
|
|
|
63
55
|
Every program below opens the client the same way, inside `async def main()`
|
|
64
56
|
run with `asyncio.run(main())` (a `None` base URL means the default):
|
|
@@ -105,7 +97,9 @@ async def main(path): # mp3, wav, flac, ogg, m4a, mp4 ...
|
|
|
105
97
|
async with VocalStack(os.environ.get("VOCALSTACK_BASE_URL"), api_key=os.environ["VOCALSTACK_API_KEY"]) as vs:
|
|
106
98
|
tx = await vs.transcriptions.create(
|
|
107
99
|
file=path, # a path, bytes, or an open binary file
|
|
108
|
-
settings={"spokenLanguage": "en", "speakerLabels": "on"}, # both optional; "auto" | "on" | "off"
|
|
100
|
+
settings={"spokenLanguage": "en", "speakerLabels": "on"}, # both optional; "auto" (default) | "on" | "off"
|
|
101
|
+
# Off unless asked for: settings summary, title, summaryTranslations,
|
|
102
|
+
# voice (spoken translations), and share_link=True (a public share link).
|
|
109
103
|
)
|
|
110
104
|
job = await vs.jobs.wait(tx.job.recording_id)
|
|
111
105
|
if job.state != "done":
|
|
@@ -191,6 +185,43 @@ async def main(q): # 2+ characters
|
|
|
191
185
|
asyncio.run(main(sys.argv[1]))
|
|
192
186
|
```
|
|
193
187
|
|
|
188
|
+
By meaning, in any language, even with none of the words (vector search). Each
|
|
189
|
+
hit's `passage` is its whole paragraph: give the passages to your own model as
|
|
190
|
+
context to answer questions about what was said (RAG). Pay as you go counts 60
|
|
191
|
+
an hour (`allowance`); Premium and Enterprise have no limit.
|
|
192
|
+
|
|
193
|
+
```python
|
|
194
|
+
res = await vs.search.vector("how much does housing cost")
|
|
195
|
+
for h in res.hits:
|
|
196
|
+
print(f"{h.similarity:.2f}", h.passage.media_start, h.passage.text)
|
|
197
|
+
```
|
|
198
|
+
|
|
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
|
+
|
|
194
225
|
## Errors
|
|
195
226
|
|
|
196
227
|
A failed call raises `vocalstack.VocalStackError` with `.code`, `.message`,
|
|
@@ -203,8 +234,9 @@ its `Retry-After`, twice by default (`max_retries` on the client).
|
|
|
203
234
|
|---|---|---|
|
|
204
235
|
| 401 | `invalid_token`, `key_expired`, `key_revoked` | Check `VOCALSTACK_API_KEY`. Use the replacement key, or create a new one. |
|
|
205
236
|
| 403 | `forbidden` | Usually a missing scope: the message names it, and `e.headers["www-authenticate"]` lists the scopes the route needs. A suspended key or account gets `suspended`. |
|
|
206
|
-
|
|
|
207
|
-
|
|
|
237
|
+
| 403 | `terms_acceptance_required` | The account has not accepted the current Terms of Service and Privacy Policy: someone signs in to the Dashboard and accepts them (an organisation's keys work once an owner or admin has). Not retryable until then. |
|
|
238
|
+
| 402 | `insufficient_credit` | The account's credit is out, or does not cover the transcription: every transcription is billed at least the plan's minimum (`rates.minimumSeconds`, 5 minutes). `details`: `balance`, `plan`, `topUpUrl`, and `required` when the cost was known up front. Add credit; `await vs.billing.get()` shows the balance. |
|
|
239
|
+
| 409 | `payment_method_required` | Auto top-up (`vs.billing.set_auto_top_up`) and the portal (`vs.billing.portal()`) need a payment first (`vs.billing.create_checkout(kind="topup", amount=25, accept_terms=True)` opens one; a signed-in person's token, never an API key). |
|
|
208
240
|
| 402 | `quota_exceeded` | The plan's transcriptions-at-once limit is reached. `details`: `metric` (`concurrent_transcriptions`), `limit`, `used`, `plan`, `topUpUrl`. |
|
|
209
241
|
| 429 | `rate_limited` | Retried automatically; if it still fails, wait `e.retry_after` seconds. Every response carries `RateLimit-Limit`, `RateLimit-Remaining` and `RateLimit-Reset`. |
|
|
210
242
|
| 503 | `unavailable` | The service is waking (its database was asleep). Retried automatically after `Retry-After` (2 s) on calls that are safe to repeat: GET, HEAD, DELETE, or any call with `idempotency_key=`. A create without a key is not retried: retry it yourself, with an `idempotency_key` so it cannot run twice. A realtime socket refused this way raises `unavailable`; a bare 503 there is `capacity_exceeded`. |
|
|
@@ -225,7 +257,8 @@ except VocalStackError as e:
|
|
|
225
257
|
- The other groups: `vs.transcriptions` (`list`, `get`, `update`, `archive`,
|
|
226
258
|
`restore`, `purge`, `list_share_links`, `create_share_link`), `vs.recordings`
|
|
227
259
|
(`get`, `summarize`, `summary`, `translation`, `audio_url`, `subscribe`),
|
|
228
|
-
`vs.
|
|
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`,
|
|
229
262
|
`vs.me` (`get`, `usage`), `vs.languages`, `vs.rtmp`, `vs.events`, `vs.meta`.
|
|
230
263
|
Each method is its operation's name in the OpenAPI document, in snake_case
|
|
231
264
|
(`transcriptions.create` is `POST /v1/transcriptions`).
|
|
@@ -8,7 +8,7 @@ from typing import Any, Callable, Dict, Mapping, Optional, Union
|
|
|
8
8
|
|
|
9
9
|
import httpx
|
|
10
10
|
|
|
11
|
-
from .errors import from_envelope
|
|
11
|
+
from .errors import VocalStackError, from_envelope, from_oauth
|
|
12
12
|
|
|
13
13
|
DEFAULT_BASE_URL = "https://api.vocalstack.com"
|
|
14
14
|
|
|
@@ -30,7 +30,6 @@ def listener_query(listener_token: Optional[str], share_code: Optional[str]) ->
|
|
|
30
30
|
return {"listenerToken": listener_token, "shareCode": share_code}
|
|
31
31
|
|
|
32
32
|
|
|
33
|
-
#: The wait before retrying ``503 unavailable`` when it names none.
|
|
34
33
|
UNAVAILABLE_WAIT_S = 2.0
|
|
35
34
|
|
|
36
35
|
|
|
@@ -103,6 +102,8 @@ class Transport:
|
|
|
103
102
|
headers: Optional[Mapping[str, str]] = None,
|
|
104
103
|
idempotency_key: Optional[str] = None,
|
|
105
104
|
anonymous: bool = False,
|
|
105
|
+
no_retry: bool = False,
|
|
106
|
+
oauth_errors: bool = False,
|
|
106
107
|
) -> httpx.Response:
|
|
107
108
|
h: Dict[str, str] = {"accept": "application/json"}
|
|
108
109
|
if headers:
|
|
@@ -118,16 +119,19 @@ class Transport:
|
|
|
118
119
|
h["idempotency-key"] = idempotency_key
|
|
119
120
|
attempt = 0
|
|
120
121
|
while True:
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
122
|
+
try:
|
|
123
|
+
resp = await self.http.request(
|
|
124
|
+
method,
|
|
125
|
+
self.url(path),
|
|
126
|
+
params=_clean(query),
|
|
127
|
+
json=json_body,
|
|
128
|
+
files=files,
|
|
129
|
+
data=data,
|
|
130
|
+
headers=h,
|
|
131
|
+
follow_redirects=False,
|
|
132
|
+
)
|
|
133
|
+
except httpx.TransportError as e:
|
|
134
|
+
raise VocalStackError("unavailable", "could not reach the VocalStack API", retryable=True) from e
|
|
131
135
|
if resp.status_code < 400:
|
|
132
136
|
return resp
|
|
133
137
|
body = None
|
|
@@ -135,8 +139,8 @@ class Transport:
|
|
|
135
139
|
body = resp.json()
|
|
136
140
|
except (json.JSONDecodeError, ValueError):
|
|
137
141
|
pass
|
|
138
|
-
err = from_envelope(body, resp.status_code, resp.headers)
|
|
139
|
-
if retries(resp.status_code, err.code, method, idempotency_key) and attempt < self.max_retries:
|
|
142
|
+
err = (from_oauth if oauth_errors else from_envelope)(body, resp.status_code, resp.headers)
|
|
143
|
+
if not no_retry and retries(resp.status_code, err.code, method, idempotency_key) and attempt < self.max_retries:
|
|
140
144
|
wait = err.retry_after
|
|
141
145
|
if wait is None and err.code == "unavailable":
|
|
142
146
|
wait = UNAVAILABLE_WAIT_S
|
|
@@ -150,7 +154,12 @@ class Transport:
|
|
|
150
154
|
resp = await self.raw(method, path, **kw)
|
|
151
155
|
if 300 <= resp.status_code < 400:
|
|
152
156
|
return {"location": resp.headers.get("location")}
|
|
153
|
-
|
|
157
|
+
if not resp.content:
|
|
158
|
+
return None
|
|
159
|
+
try:
|
|
160
|
+
return resp.json()
|
|
161
|
+
except (json.JSONDecodeError, ValueError) as e:
|
|
162
|
+
raise VocalStackError("internal", "the API answered with a body that is not JSON", status=resp.status_code, headers=resp.headers) from e
|
|
154
163
|
|
|
155
164
|
async def aclose(self) -> None:
|
|
156
165
|
if self.own_http:
|