pocketstation 0.1.2__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.
- pocketstation-0.1.2/PKG-INFO +312 -0
- pocketstation-0.1.2/README.md +281 -0
- pocketstation-0.1.2/RELEASE_NOTES.md +126 -0
- pocketstation-0.1.2/docs/README.md +47 -0
- pocketstation-0.1.2/docs/concepts/session-and-bounds.md +50 -0
- pocketstation-0.1.2/docs/getting-started/capture.md +80 -0
- pocketstation-0.1.2/docs/guides/application-audio.md +66 -0
- pocketstation-0.1.2/docs/guides/integrations.md +171 -0
- pocketstation-0.1.2/docs/guides/record-and-observe.md +69 -0
- pocketstation-0.1.2/docs/guides/relay.md +58 -0
- pocketstation-0.1.2/docs/guides/voice.md +90 -0
- pocketstation-0.1.2/docs/operations/platform-support.md +125 -0
- pocketstation-0.1.2/docs/reference/api-map.md +68 -0
- pocketstation-0.1.2/docs/troubleshooting.md +76 -0
- pocketstation-0.1.2/examples/README.md +123 -0
- pocketstation-0.1.2/examples/debug_voice_ai.py +50 -0
- pocketstation-0.1.2/examples/send_audio_to_websocket.py +33 -0
- pocketstation-0.1.2/examples/stream_any_app_audio.py +29 -0
- pocketstation-0.1.2/examples/transcribe_voice_app.py +23 -0
- pocketstation-0.1.2/native/Cargo.lock +2816 -0
- pocketstation-0.1.2/native/Cargo.toml +25 -0
- pocketstation-0.1.2/native/build.rs +73 -0
- pocketstation-0.1.2/native/src/audio_input.rs +289 -0
- pocketstation-0.1.2/native/src/connector/driver.rs +636 -0
- pocketstation-0.1.2/native/src/connector/mod.rs +17 -0
- pocketstation-0.1.2/native/src/connector/observations.rs +181 -0
- pocketstation-0.1.2/native/src/connector/values.rs +409 -0
- pocketstation-0.1.2/native/src/connector/worker.rs +580 -0
- pocketstation-0.1.2/native/src/endpoint_authoring.rs +767 -0
- pocketstation-0.1.2/native/src/errors.rs +131 -0
- pocketstation-0.1.2/native/src/extensions.rs +416 -0
- pocketstation-0.1.2/native/src/graph.rs +1363 -0
- pocketstation-0.1.2/native/src/lib.rs +38 -0
- pocketstation-0.1.2/native/src/observations.rs +2385 -0
- pocketstation-0.1.2/native/src/operator_authoring/driver.rs +533 -0
- pocketstation-0.1.2/native/src/operator_authoring/mod.rs +15 -0
- pocketstation-0.1.2/native/src/operator_authoring/values.rs +229 -0
- pocketstation-0.1.2/native/src/relay.rs +141 -0
- pocketstation-0.1.2/native/src/session.rs +1436 -0
- pocketstation-0.1.2/native/src/sidecar.rs +485 -0
- pocketstation-0.1.2/native/src/signals.rs +1004 -0
- pocketstation-0.1.2/native/src/source_authoring/driver.rs +299 -0
- pocketstation-0.1.2/native/src/source_authoring/mod.rs +17 -0
- pocketstation-0.1.2/native/src/source_authoring/values.rs +221 -0
- pocketstation-0.1.2/native/src/sources.rs +692 -0
- pocketstation-0.1.2/native/src/streams.rs +399 -0
- pocketstation-0.1.2/pyproject.toml +76 -0
- pocketstation-0.1.2/python/pocketstation/__init__.py +41 -0
- pocketstation-0.1.2/python/pocketstation/_api.py +617 -0
- pocketstation-0.1.2/python/pocketstation/_native.pyi +1354 -0
- pocketstation-0.1.2/python/pocketstation/aio/__init__.py +23 -0
- pocketstation-0.1.2/python/pocketstation/aio/_api.py +226 -0
- pocketstation-0.1.2/python/pocketstation/aio/audio_input.py +108 -0
- pocketstation-0.1.2/python/pocketstation/aio/capture.py +205 -0
- pocketstation-0.1.2/python/pocketstation/aio/connector.py +811 -0
- pocketstation-0.1.2/python/pocketstation/aio/control.py +264 -0
- pocketstation-0.1.2/python/pocketstation/aio/conversation.py +15 -0
- pocketstation-0.1.2/python/pocketstation/aio/endpoint_authoring.py +290 -0
- pocketstation-0.1.2/python/pocketstation/aio/event_input.py +161 -0
- pocketstation-0.1.2/python/pocketstation/aio/extensions.py +21 -0
- pocketstation-0.1.2/python/pocketstation/aio/observations.py +77 -0
- pocketstation-0.1.2/python/pocketstation/aio/operator_authoring.py +302 -0
- pocketstation-0.1.2/python/pocketstation/aio/relay.py +278 -0
- pocketstation-0.1.2/python/pocketstation/aio/session.py +697 -0
- pocketstation-0.1.2/python/pocketstation/aio/sidecar.py +146 -0
- pocketstation-0.1.2/python/pocketstation/aio/source_authoring.py +300 -0
- pocketstation-0.1.2/python/pocketstation/aio/sources.py +42 -0
- pocketstation-0.1.2/python/pocketstation/aio/streams.py +268 -0
- pocketstation-0.1.2/python/pocketstation/audio_input.py +213 -0
- pocketstation-0.1.2/python/pocketstation/capture.py +201 -0
- pocketstation-0.1.2/python/pocketstation/compatibility.py +30 -0
- pocketstation-0.1.2/python/pocketstation/connector.py +1357 -0
- pocketstation-0.1.2/python/pocketstation/control.py +614 -0
- pocketstation-0.1.2/python/pocketstation/conversation.py +35 -0
- pocketstation-0.1.2/python/pocketstation/endpoint_authoring.py +476 -0
- pocketstation-0.1.2/python/pocketstation/errors.py +333 -0
- pocketstation-0.1.2/python/pocketstation/extensions.py +167 -0
- pocketstation-0.1.2/python/pocketstation/graph.py +1146 -0
- pocketstation-0.1.2/python/pocketstation/identity.py +40 -0
- pocketstation-0.1.2/python/pocketstation/observations.py +1274 -0
- pocketstation-0.1.2/python/pocketstation/operator_authoring.py +327 -0
- pocketstation-0.1.2/python/pocketstation/py.typed +0 -0
- pocketstation-0.1.2/python/pocketstation/relay.py +419 -0
- pocketstation-0.1.2/python/pocketstation/session.py +496 -0
- pocketstation-0.1.2/python/pocketstation/sidecar.py +398 -0
- pocketstation-0.1.2/python/pocketstation/signal.py +277 -0
- pocketstation-0.1.2/python/pocketstation/source_authoring.py +349 -0
- pocketstation-0.1.2/python/pocketstation/sources.py +637 -0
- pocketstation-0.1.2/python/pocketstation/streams.py +355 -0
- pocketstation-0.1.2/python/pocketstation/voice/__init__.py +106 -0
- pocketstation-0.1.2/python/pocketstation/voice/capabilities.py +158 -0
- pocketstation-0.1.2/python/pocketstation/voice/configuration.py +265 -0
- pocketstation-0.1.2/python/pocketstation/voice/conversation.py +1396 -0
- pocketstation-0.1.2/python/pocketstation/voice/duplex.py +58 -0
- pocketstation-0.1.2/python/pocketstation/voice/errors.py +59 -0
- pocketstation-0.1.2/python/pocketstation/voice/events.py +40 -0
- pocketstation-0.1.2/python/pocketstation/voice/response.py +105 -0
- pocketstation-0.1.2/python/pocketstation/voice/speech_detection.py +63 -0
- pocketstation-0.1.2/python/pocketstation/voice/synthesis.py +74 -0
- pocketstation-0.1.2/python/pocketstation/voice/transcription.py +120 -0
- pocketstation-0.1.2/python/pocketstation/voice/turns.py +81 -0
- pocketstation-0.1.2/python/pocketstation_demo/__init__.py +15 -0
- pocketstation-0.1.2/python/pocketstation_demo/audio_windows.py +192 -0
- pocketstation-0.1.2/python/pocketstation_demo/demo.py +41 -0
- pocketstation-0.1.2/python/pocketstation_demo/faster_whisper.py +456 -0
- pocketstation-0.1.2/python/pocketstation_demo/openai_realtime.py +1244 -0
- pocketstation-0.1.2/python/pocketstation_demo/relay.py +27 -0
- pocketstation-0.1.2/python/pocketstation_demo/transcript.py +42 -0
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pocketstation
|
|
3
|
+
Version: 0.1.2
|
|
4
|
+
Classifier: Development Status :: 3 - Alpha
|
|
5
|
+
Classifier: Intended Audience :: Developers
|
|
6
|
+
Classifier: Programming Language :: Python :: 3
|
|
7
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
8
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
9
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
10
|
+
Classifier: Programming Language :: Rust
|
|
11
|
+
Classifier: Typing :: Typed
|
|
12
|
+
Requires-Dist: httpx>=0.27
|
|
13
|
+
Requires-Dist: pytest>=8.0 ; extra == 'dev'
|
|
14
|
+
Requires-Dist: pytest-asyncio>=0.23 ; extra == 'dev'
|
|
15
|
+
Requires-Dist: mypy>=1.15 ; extra == 'dev'
|
|
16
|
+
Requires-Dist: ruff>=0.11 ; extra == 'dev'
|
|
17
|
+
Requires-Dist: faster-whisper>=1.2.1,<2.0 ; extra == 'transcription'
|
|
18
|
+
Requires-Dist: websockets>=17.0,<18 ; extra == 'voice-agent-debug'
|
|
19
|
+
Provides-Extra: dev
|
|
20
|
+
Provides-Extra: transcription
|
|
21
|
+
Provides-Extra: voice-agent-debug
|
|
22
|
+
Summary: Source-aware live audio capture, processing, and routing for Python
|
|
23
|
+
License: MIT
|
|
24
|
+
Requires-Python: >=3.11
|
|
25
|
+
Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
|
|
26
|
+
Project-URL: Documentation, https://github.com/pocketstation-io/sdk-python/tree/main/docs
|
|
27
|
+
Project-URL: Issues, https://github.com/pocketstation-io/sdk-python/issues
|
|
28
|
+
Project-URL: Release notes, https://github.com/pocketstation-io/sdk-python/blob/main/RELEASE_NOTES.md
|
|
29
|
+
Project-URL: Repository, https://github.com/pocketstation-io/sdk-python
|
|
30
|
+
|
|
31
|
+
# PocketStation for Python
|
|
32
|
+
|
|
33
|
+
PocketStation captures one desktop application and an optional microphone as
|
|
34
|
+
separate live audio stems. A single native Session can send those stems to
|
|
35
|
+
Python model code, a remote browser, and a multistem recording without mixing
|
|
36
|
+
their source identities.
|
|
37
|
+
|
|
38
|
+
The Python package uses the PocketStation Rust engine for capture, routing,
|
|
39
|
+
timing, recording, and Relay transport. Your Python code owns the model and
|
|
40
|
+
application logic.
|
|
41
|
+
|
|
42
|
+
## Capture a desktop application
|
|
43
|
+
|
|
44
|
+
Install PocketStation:
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
python -m pip install pocketstation
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Capture one application without opening a microphone or writing files:
|
|
51
|
+
|
|
52
|
+
```python
|
|
53
|
+
import pocketstation
|
|
54
|
+
|
|
55
|
+
with pocketstation.capture(application="Spotify") as live:
|
|
56
|
+
for frame in live.audio:
|
|
57
|
+
print(frame.source_id, frame.stem_id)
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Add `microphone=True` when you need the default microphone as a second
|
|
61
|
+
independent stem. Add `record_to="recordings"` when you want each selected stem
|
|
62
|
+
recorded. Both behaviors are off by default.
|
|
63
|
+
|
|
64
|
+
## Handle permissions and source changes
|
|
65
|
+
|
|
66
|
+
PocketStation does not prompt during import or discovery. Check microphone
|
|
67
|
+
permission without prompting, then let Source opening report the authoritative
|
|
68
|
+
result. Application capture and microphone capture use separate permissions.
|
|
69
|
+
|
|
70
|
+
When an application or device disappears, PocketStation reports the change and
|
|
71
|
+
does not switch to another Source. Stop the current Session, discover again,
|
|
72
|
+
confirm any changed selection, and create a new Session. Store a discovered
|
|
73
|
+
identity only for its reported persistence scope. Keep fallback and provider
|
|
74
|
+
retry policy explicit and finite.
|
|
75
|
+
|
|
76
|
+
See [Prepare and qualify each Python platform](docs/operations/platform-support.md)
|
|
77
|
+
for the permission states, persistence scopes, and recovery sequence.
|
|
78
|
+
|
|
79
|
+
## Debug a voice interruption
|
|
80
|
+
|
|
81
|
+
[`examples/debug_voice_ai.py`](examples/debug_voice_ai.py) sends a physical
|
|
82
|
+
microphone to OpenAI Realtime without another voice framework. PocketStation
|
|
83
|
+
keeps microphone input, generated assistant audio, and the selected browser's
|
|
84
|
+
output as independent recorded stems. Provider events and media events share
|
|
85
|
+
one monotonic timeline, so you can see whether delay occurred before the model,
|
|
86
|
+
inside the provider, in local output, or after Relay delivery.
|
|
87
|
+
|
|
88
|
+
See the [voice-agent debugger instructions](examples/README.md#debug-a-voice-agent-interruption-from-the-media-boundary).
|
|
89
|
+
Run repository examples from a source checkout or source archive. The installed
|
|
90
|
+
`pocketstation-demo` command is the packaged application-and-microphone demo.
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
python -m pip install 'pocketstation[transcription]'
|
|
94
|
+
pocketstation-demo
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
## Transcribe both sides of a voice application
|
|
98
|
+
|
|
99
|
+
Install the transcription extra, then run the example:
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
python -m pip install 'pocketstation[transcription]'
|
|
103
|
+
python examples/transcribe_voice_app.py
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
The program asks which desktop voice application to inspect. It declares one
|
|
107
|
+
faster-whisper Operator, connects both stems to its audio input, and prints each
|
|
108
|
+
transcript with its original source identity. It does not start Relay or write a
|
|
109
|
+
recording.
|
|
110
|
+
|
|
111
|
+
The Session runs this path concurrently:
|
|
112
|
+
|
|
113
|
+
```text
|
|
114
|
+
voice application ─┐
|
|
115
|
+
├─ one bounded faster-whisper Operator ─ transcripts
|
|
116
|
+
physical microphone┘
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
The complete composition is visible in
|
|
120
|
+
[`examples/transcribe_voice_app.py`](examples/transcribe_voice_app.py). The
|
|
121
|
+
example adapter imports `faster_whisper.WhisperModel` when the Operator starts;
|
|
122
|
+
the provider is not part of the `pocketstation` namespace.
|
|
123
|
+
|
|
124
|
+
This example does not debug turn handling, interruption, agent latency, or
|
|
125
|
+
browser playout. PocketStation does not receive those events in this program.
|
|
126
|
+
|
|
127
|
+
## Stream any application audio to a browser
|
|
128
|
+
|
|
129
|
+
Run the Relay example when you want another person to listen in a browser:
|
|
130
|
+
|
|
131
|
+
```bash
|
|
132
|
+
python examples/stream_any_app_audio.py
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
Choose any running application that is producing audio. The example publishes
|
|
136
|
+
that application as one named AudioBus, waits for Relay readiness, and prints a
|
|
137
|
+
single-use word code and browser URL. It does not open the microphone or record
|
|
138
|
+
audio.
|
|
139
|
+
|
|
140
|
+
The example uses PocketStation's small, rate-limited demo service unless you set
|
|
141
|
+
`POCKETSTATION_CONTROL_URL` and `POCKETSTATION_RELAY_URL` to services you
|
|
142
|
+
operate. The shared URLs live in `pocketstation_demo`; application code does
|
|
143
|
+
not contain service credentials.
|
|
144
|
+
|
|
145
|
+
## Read application and microphone audio
|
|
146
|
+
|
|
147
|
+
Set the optional microphone and recording parameters when the workflow needs
|
|
148
|
+
both sides:
|
|
149
|
+
|
|
150
|
+
```python
|
|
151
|
+
import pocketstation
|
|
152
|
+
|
|
153
|
+
with pocketstation.capture(
|
|
154
|
+
application="Zoom",
|
|
155
|
+
microphone=True,
|
|
156
|
+
record_to="recordings",
|
|
157
|
+
) as live:
|
|
158
|
+
for frame in live.audio:
|
|
159
|
+
print(frame.source_id, frame.stem_id)
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
The iterator reads a bounded native endpoint. A slow Python consumer produces
|
|
163
|
+
observable pressure and discontinuities; it does not create an unbounded Python
|
|
164
|
+
audio queue.
|
|
165
|
+
|
|
166
|
+
## Send application-owned audio into a Session
|
|
167
|
+
|
|
168
|
+
Use `audio_input()` when your application already owns PCM, such as generated
|
|
169
|
+
speech or audio received from a call provider:
|
|
170
|
+
|
|
171
|
+
```python
|
|
172
|
+
session = pocketstation.Session(recording_root="recordings")
|
|
173
|
+
agent = session.audio_input("agent-output")
|
|
174
|
+
agent.output.record("agent")
|
|
175
|
+
|
|
176
|
+
with session.start():
|
|
177
|
+
agent.write(samples)
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
The input uses finite preallocated Core buffers. Writes report full, closed,
|
|
181
|
+
cancelled, and invalid-buffer outcomes explicitly.
|
|
182
|
+
|
|
183
|
+
## Create an integration
|
|
184
|
+
|
|
185
|
+
PocketStation uses four open boundaries:
|
|
186
|
+
|
|
187
|
+
| Boundary | Use it when |
|
|
188
|
+
|---|---|
|
|
189
|
+
| `Source` | Media or signals enter the Session. |
|
|
190
|
+
| `Operator` | Work transforms media or emits typed signals. |
|
|
191
|
+
| `Connector` | Media or signals leave for an external system. |
|
|
192
|
+
| `Endpoint` | You need the lower-level outbound execution contract. |
|
|
193
|
+
|
|
194
|
+
Pass one function when the destination is already open:
|
|
195
|
+
|
|
196
|
+
```python
|
|
197
|
+
import pocketstation as pks
|
|
198
|
+
import pocketstation.aio as pks_aio
|
|
199
|
+
|
|
200
|
+
async def send_audio(frame: pks.AudioFrame) -> None:
|
|
201
|
+
await socket.send(frame.samples)
|
|
202
|
+
|
|
203
|
+
destination = pks_aio.Connector(send=send_audio)
|
|
204
|
+
application.send_to(destination)
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
Subclass the synchronous or asyncio contract when the provider opens and
|
|
208
|
+
closes resources. The provider class owns its connection; the Session owns
|
|
209
|
+
bounded delivery, lineage, observations, drain, abort, and joined shutdown:
|
|
210
|
+
|
|
211
|
+
```python
|
|
212
|
+
class WebSocketConnector(pks_aio.Connector):
|
|
213
|
+
def __init__(self, url, token):
|
|
214
|
+
self.url, self.token = url, token
|
|
215
|
+
|
|
216
|
+
async def start(self):
|
|
217
|
+
self.socket = await connect(self.url, token=self.token)
|
|
218
|
+
|
|
219
|
+
async def send(self, frame: pks.AudioFrame):
|
|
220
|
+
await self.socket.send(frame.samples)
|
|
221
|
+
|
|
222
|
+
async def stop(self):
|
|
223
|
+
await self.socket.close()
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
Attach one configured object to one or more stems:
|
|
227
|
+
|
|
228
|
+
```python
|
|
229
|
+
destination = WebSocketConnector(url, token)
|
|
230
|
+
application.send_to(destination)
|
|
231
|
+
microphone.send_to(destination)
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
PocketStation calls `start()` once, interleaves both source-aware stems through
|
|
235
|
+
`send()`, and calls `stop()` once. A second Connector object creates a separate
|
|
236
|
+
destination. See [Create an integration](docs/guides/integrations.md) for
|
|
237
|
+
deadlines, failures, and the advanced SPI.
|
|
238
|
+
|
|
239
|
+
Python provider callbacks execute on bounded off-realtime workers. They cannot
|
|
240
|
+
be used as native capture callbacks. Compiled native extensions remain the path
|
|
241
|
+
for native provider code, and process sidecars remain available when crash
|
|
242
|
+
isolation is required.
|
|
243
|
+
|
|
244
|
+
## Use Relay from Python
|
|
245
|
+
|
|
246
|
+
Python creates and deletes RelaySessions through the typed HTTP control client.
|
|
247
|
+
The shared Rust `pocketstation-relay` connector publishes media. The Go Relay
|
|
248
|
+
service forwards WebRTC audio. Python does not encode Opus, write RTP, or own a
|
|
249
|
+
second media plane.
|
|
250
|
+
|
|
251
|
+
The control client uses finite request deadlines, bounded response bodies,
|
|
252
|
+
redacted secrets, and matching synchronous and asyncio APIs.
|
|
253
|
+
|
|
254
|
+
## Sync and asyncio
|
|
255
|
+
|
|
256
|
+
`pocketstation` and `pocketstation.aio` operate the same native Session. The
|
|
257
|
+
asyncio namespace provides awaitable lifecycle, stream, Relay, provider, and
|
|
258
|
+
audio-input operations without creating another audio queue.
|
|
259
|
+
|
|
260
|
+
Python callbacks still cross the interpreter boundary. Capture, routing,
|
|
261
|
+
recording, and Relay transport remain native-speed; arbitrary Python model code
|
|
262
|
+
does not have the same execution cost as Rust.
|
|
263
|
+
|
|
264
|
+
## Platform support
|
|
265
|
+
|
|
266
|
+
| Area | Support |
|
|
267
|
+
|---|---|
|
|
268
|
+
| Python | 3.11 and newer |
|
|
269
|
+
| macOS Apple silicon | Installed wheel, application capture, physical microphone, 10 ms voice path, Relay, Chromium, and multistem recording tested |
|
|
270
|
+
| Linux | Core application selection and 10 ms capture tested; installed Python distribution qualification in progress |
|
|
271
|
+
| Windows 11 ARM64 | Core application selection and 10 ms capture tested in a VM; installed Python distribution and physical-device qualification in progress |
|
|
272
|
+
| WAN and TURN | Not yet qualified |
|
|
273
|
+
|
|
274
|
+
The native binding uses PocketStation Core `1.1.4` and the shared Relay
|
|
275
|
+
Connector `0.1.2`.
|
|
276
|
+
|
|
277
|
+
The Rust-to-Python audio read currently copies native samples into Python-owned
|
|
278
|
+
bytes before exposing a `memoryview`. The view avoids another Python-side copy;
|
|
279
|
+
the complete boundary is not zero-copy.
|
|
280
|
+
|
|
281
|
+
## Develop the SDK
|
|
282
|
+
|
|
283
|
+
```bash
|
|
284
|
+
uv sync --extra transcription
|
|
285
|
+
uv run pytest -q
|
|
286
|
+
uv run ruff check python tests examples
|
|
287
|
+
uv run ruff format --check python tests examples
|
|
288
|
+
uv run mypy python tests/qualification/typing_contract.py examples
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
## Reference
|
|
292
|
+
|
|
293
|
+
- [`RELEASE_NOTES.md`](RELEASE_NOTES.md) — user-visible changes and upgrade
|
|
294
|
+
guidance.
|
|
295
|
+
- [`docs/README.md`](docs/README.md) — task guides, concepts, operations, and
|
|
296
|
+
API ownership.
|
|
297
|
+
- [Write application-owned audio](docs/guides/application-audio.md) — bounded
|
|
298
|
+
PCM input and selective output cancellation.
|
|
299
|
+
- [Record and observe a Session](docs/guides/record-and-observe.md) — multistem
|
|
300
|
+
outcomes, route metrics, and lifecycle events.
|
|
301
|
+
- [Prepare each platform](docs/operations/platform-support.md) — permissions,
|
|
302
|
+
source persistence, explicit rediscovery, and fallback policy.
|
|
303
|
+
- [`examples/README.md`](examples/README.md) — runnable examples and
|
|
304
|
+
prerequisites.
|
|
305
|
+
- `pocketstation.capture` — concise application and microphone capture.
|
|
306
|
+
- `pocketstation.session` — Session declarations and lifecycle.
|
|
307
|
+
- `pocketstation.graph` — stems, ports, routes, and signal contracts.
|
|
308
|
+
- `pocketstation.connector` — outbound provider authoring.
|
|
309
|
+
- `pocketstation.operator_authoring` — computation authoring.
|
|
310
|
+
- `pocketstation.source_authoring` — inbound provider authoring.
|
|
311
|
+
- `pocketstation.aio` — asyncio projection of the same engine.
|
|
312
|
+
|
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
# PocketStation for Python
|
|
2
|
+
|
|
3
|
+
PocketStation captures one desktop application and an optional microphone as
|
|
4
|
+
separate live audio stems. A single native Session can send those stems to
|
|
5
|
+
Python model code, a remote browser, and a multistem recording without mixing
|
|
6
|
+
their source identities.
|
|
7
|
+
|
|
8
|
+
The Python package uses the PocketStation Rust engine for capture, routing,
|
|
9
|
+
timing, recording, and Relay transport. Your Python code owns the model and
|
|
10
|
+
application logic.
|
|
11
|
+
|
|
12
|
+
## Capture a desktop application
|
|
13
|
+
|
|
14
|
+
Install PocketStation:
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
python -m pip install pocketstation
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
Capture one application without opening a microphone or writing files:
|
|
21
|
+
|
|
22
|
+
```python
|
|
23
|
+
import pocketstation
|
|
24
|
+
|
|
25
|
+
with pocketstation.capture(application="Spotify") as live:
|
|
26
|
+
for frame in live.audio:
|
|
27
|
+
print(frame.source_id, frame.stem_id)
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Add `microphone=True` when you need the default microphone as a second
|
|
31
|
+
independent stem. Add `record_to="recordings"` when you want each selected stem
|
|
32
|
+
recorded. Both behaviors are off by default.
|
|
33
|
+
|
|
34
|
+
## Handle permissions and source changes
|
|
35
|
+
|
|
36
|
+
PocketStation does not prompt during import or discovery. Check microphone
|
|
37
|
+
permission without prompting, then let Source opening report the authoritative
|
|
38
|
+
result. Application capture and microphone capture use separate permissions.
|
|
39
|
+
|
|
40
|
+
When an application or device disappears, PocketStation reports the change and
|
|
41
|
+
does not switch to another Source. Stop the current Session, discover again,
|
|
42
|
+
confirm any changed selection, and create a new Session. Store a discovered
|
|
43
|
+
identity only for its reported persistence scope. Keep fallback and provider
|
|
44
|
+
retry policy explicit and finite.
|
|
45
|
+
|
|
46
|
+
See [Prepare and qualify each Python platform](docs/operations/platform-support.md)
|
|
47
|
+
for the permission states, persistence scopes, and recovery sequence.
|
|
48
|
+
|
|
49
|
+
## Debug a voice interruption
|
|
50
|
+
|
|
51
|
+
[`examples/debug_voice_ai.py`](examples/debug_voice_ai.py) sends a physical
|
|
52
|
+
microphone to OpenAI Realtime without another voice framework. PocketStation
|
|
53
|
+
keeps microphone input, generated assistant audio, and the selected browser's
|
|
54
|
+
output as independent recorded stems. Provider events and media events share
|
|
55
|
+
one monotonic timeline, so you can see whether delay occurred before the model,
|
|
56
|
+
inside the provider, in local output, or after Relay delivery.
|
|
57
|
+
|
|
58
|
+
See the [voice-agent debugger instructions](examples/README.md#debug-a-voice-agent-interruption-from-the-media-boundary).
|
|
59
|
+
Run repository examples from a source checkout or source archive. The installed
|
|
60
|
+
`pocketstation-demo` command is the packaged application-and-microphone demo.
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
python -m pip install 'pocketstation[transcription]'
|
|
64
|
+
pocketstation-demo
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Transcribe both sides of a voice application
|
|
68
|
+
|
|
69
|
+
Install the transcription extra, then run the example:
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
python -m pip install 'pocketstation[transcription]'
|
|
73
|
+
python examples/transcribe_voice_app.py
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
The program asks which desktop voice application to inspect. It declares one
|
|
77
|
+
faster-whisper Operator, connects both stems to its audio input, and prints each
|
|
78
|
+
transcript with its original source identity. It does not start Relay or write a
|
|
79
|
+
recording.
|
|
80
|
+
|
|
81
|
+
The Session runs this path concurrently:
|
|
82
|
+
|
|
83
|
+
```text
|
|
84
|
+
voice application ─┐
|
|
85
|
+
├─ one bounded faster-whisper Operator ─ transcripts
|
|
86
|
+
physical microphone┘
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
The complete composition is visible in
|
|
90
|
+
[`examples/transcribe_voice_app.py`](examples/transcribe_voice_app.py). The
|
|
91
|
+
example adapter imports `faster_whisper.WhisperModel` when the Operator starts;
|
|
92
|
+
the provider is not part of the `pocketstation` namespace.
|
|
93
|
+
|
|
94
|
+
This example does not debug turn handling, interruption, agent latency, or
|
|
95
|
+
browser playout. PocketStation does not receive those events in this program.
|
|
96
|
+
|
|
97
|
+
## Stream any application audio to a browser
|
|
98
|
+
|
|
99
|
+
Run the Relay example when you want another person to listen in a browser:
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
python examples/stream_any_app_audio.py
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
Choose any running application that is producing audio. The example publishes
|
|
106
|
+
that application as one named AudioBus, waits for Relay readiness, and prints a
|
|
107
|
+
single-use word code and browser URL. It does not open the microphone or record
|
|
108
|
+
audio.
|
|
109
|
+
|
|
110
|
+
The example uses PocketStation's small, rate-limited demo service unless you set
|
|
111
|
+
`POCKETSTATION_CONTROL_URL` and `POCKETSTATION_RELAY_URL` to services you
|
|
112
|
+
operate. The shared URLs live in `pocketstation_demo`; application code does
|
|
113
|
+
not contain service credentials.
|
|
114
|
+
|
|
115
|
+
## Read application and microphone audio
|
|
116
|
+
|
|
117
|
+
Set the optional microphone and recording parameters when the workflow needs
|
|
118
|
+
both sides:
|
|
119
|
+
|
|
120
|
+
```python
|
|
121
|
+
import pocketstation
|
|
122
|
+
|
|
123
|
+
with pocketstation.capture(
|
|
124
|
+
application="Zoom",
|
|
125
|
+
microphone=True,
|
|
126
|
+
record_to="recordings",
|
|
127
|
+
) as live:
|
|
128
|
+
for frame in live.audio:
|
|
129
|
+
print(frame.source_id, frame.stem_id)
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
The iterator reads a bounded native endpoint. A slow Python consumer produces
|
|
133
|
+
observable pressure and discontinuities; it does not create an unbounded Python
|
|
134
|
+
audio queue.
|
|
135
|
+
|
|
136
|
+
## Send application-owned audio into a Session
|
|
137
|
+
|
|
138
|
+
Use `audio_input()` when your application already owns PCM, such as generated
|
|
139
|
+
speech or audio received from a call provider:
|
|
140
|
+
|
|
141
|
+
```python
|
|
142
|
+
session = pocketstation.Session(recording_root="recordings")
|
|
143
|
+
agent = session.audio_input("agent-output")
|
|
144
|
+
agent.output.record("agent")
|
|
145
|
+
|
|
146
|
+
with session.start():
|
|
147
|
+
agent.write(samples)
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
The input uses finite preallocated Core buffers. Writes report full, closed,
|
|
151
|
+
cancelled, and invalid-buffer outcomes explicitly.
|
|
152
|
+
|
|
153
|
+
## Create an integration
|
|
154
|
+
|
|
155
|
+
PocketStation uses four open boundaries:
|
|
156
|
+
|
|
157
|
+
| Boundary | Use it when |
|
|
158
|
+
|---|---|
|
|
159
|
+
| `Source` | Media or signals enter the Session. |
|
|
160
|
+
| `Operator` | Work transforms media or emits typed signals. |
|
|
161
|
+
| `Connector` | Media or signals leave for an external system. |
|
|
162
|
+
| `Endpoint` | You need the lower-level outbound execution contract. |
|
|
163
|
+
|
|
164
|
+
Pass one function when the destination is already open:
|
|
165
|
+
|
|
166
|
+
```python
|
|
167
|
+
import pocketstation as pks
|
|
168
|
+
import pocketstation.aio as pks_aio
|
|
169
|
+
|
|
170
|
+
async def send_audio(frame: pks.AudioFrame) -> None:
|
|
171
|
+
await socket.send(frame.samples)
|
|
172
|
+
|
|
173
|
+
destination = pks_aio.Connector(send=send_audio)
|
|
174
|
+
application.send_to(destination)
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
Subclass the synchronous or asyncio contract when the provider opens and
|
|
178
|
+
closes resources. The provider class owns its connection; the Session owns
|
|
179
|
+
bounded delivery, lineage, observations, drain, abort, and joined shutdown:
|
|
180
|
+
|
|
181
|
+
```python
|
|
182
|
+
class WebSocketConnector(pks_aio.Connector):
|
|
183
|
+
def __init__(self, url, token):
|
|
184
|
+
self.url, self.token = url, token
|
|
185
|
+
|
|
186
|
+
async def start(self):
|
|
187
|
+
self.socket = await connect(self.url, token=self.token)
|
|
188
|
+
|
|
189
|
+
async def send(self, frame: pks.AudioFrame):
|
|
190
|
+
await self.socket.send(frame.samples)
|
|
191
|
+
|
|
192
|
+
async def stop(self):
|
|
193
|
+
await self.socket.close()
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
Attach one configured object to one or more stems:
|
|
197
|
+
|
|
198
|
+
```python
|
|
199
|
+
destination = WebSocketConnector(url, token)
|
|
200
|
+
application.send_to(destination)
|
|
201
|
+
microphone.send_to(destination)
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
PocketStation calls `start()` once, interleaves both source-aware stems through
|
|
205
|
+
`send()`, and calls `stop()` once. A second Connector object creates a separate
|
|
206
|
+
destination. See [Create an integration](docs/guides/integrations.md) for
|
|
207
|
+
deadlines, failures, and the advanced SPI.
|
|
208
|
+
|
|
209
|
+
Python provider callbacks execute on bounded off-realtime workers. They cannot
|
|
210
|
+
be used as native capture callbacks. Compiled native extensions remain the path
|
|
211
|
+
for native provider code, and process sidecars remain available when crash
|
|
212
|
+
isolation is required.
|
|
213
|
+
|
|
214
|
+
## Use Relay from Python
|
|
215
|
+
|
|
216
|
+
Python creates and deletes RelaySessions through the typed HTTP control client.
|
|
217
|
+
The shared Rust `pocketstation-relay` connector publishes media. The Go Relay
|
|
218
|
+
service forwards WebRTC audio. Python does not encode Opus, write RTP, or own a
|
|
219
|
+
second media plane.
|
|
220
|
+
|
|
221
|
+
The control client uses finite request deadlines, bounded response bodies,
|
|
222
|
+
redacted secrets, and matching synchronous and asyncio APIs.
|
|
223
|
+
|
|
224
|
+
## Sync and asyncio
|
|
225
|
+
|
|
226
|
+
`pocketstation` and `pocketstation.aio` operate the same native Session. The
|
|
227
|
+
asyncio namespace provides awaitable lifecycle, stream, Relay, provider, and
|
|
228
|
+
audio-input operations without creating another audio queue.
|
|
229
|
+
|
|
230
|
+
Python callbacks still cross the interpreter boundary. Capture, routing,
|
|
231
|
+
recording, and Relay transport remain native-speed; arbitrary Python model code
|
|
232
|
+
does not have the same execution cost as Rust.
|
|
233
|
+
|
|
234
|
+
## Platform support
|
|
235
|
+
|
|
236
|
+
| Area | Support |
|
|
237
|
+
|---|---|
|
|
238
|
+
| Python | 3.11 and newer |
|
|
239
|
+
| macOS Apple silicon | Installed wheel, application capture, physical microphone, 10 ms voice path, Relay, Chromium, and multistem recording tested |
|
|
240
|
+
| Linux | Core application selection and 10 ms capture tested; installed Python distribution qualification in progress |
|
|
241
|
+
| Windows 11 ARM64 | Core application selection and 10 ms capture tested in a VM; installed Python distribution and physical-device qualification in progress |
|
|
242
|
+
| WAN and TURN | Not yet qualified |
|
|
243
|
+
|
|
244
|
+
The native binding uses PocketStation Core `1.1.4` and the shared Relay
|
|
245
|
+
Connector `0.1.2`.
|
|
246
|
+
|
|
247
|
+
The Rust-to-Python audio read currently copies native samples into Python-owned
|
|
248
|
+
bytes before exposing a `memoryview`. The view avoids another Python-side copy;
|
|
249
|
+
the complete boundary is not zero-copy.
|
|
250
|
+
|
|
251
|
+
## Develop the SDK
|
|
252
|
+
|
|
253
|
+
```bash
|
|
254
|
+
uv sync --extra transcription
|
|
255
|
+
uv run pytest -q
|
|
256
|
+
uv run ruff check python tests examples
|
|
257
|
+
uv run ruff format --check python tests examples
|
|
258
|
+
uv run mypy python tests/qualification/typing_contract.py examples
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
## Reference
|
|
262
|
+
|
|
263
|
+
- [`RELEASE_NOTES.md`](RELEASE_NOTES.md) — user-visible changes and upgrade
|
|
264
|
+
guidance.
|
|
265
|
+
- [`docs/README.md`](docs/README.md) — task guides, concepts, operations, and
|
|
266
|
+
API ownership.
|
|
267
|
+
- [Write application-owned audio](docs/guides/application-audio.md) — bounded
|
|
268
|
+
PCM input and selective output cancellation.
|
|
269
|
+
- [Record and observe a Session](docs/guides/record-and-observe.md) — multistem
|
|
270
|
+
outcomes, route metrics, and lifecycle events.
|
|
271
|
+
- [Prepare each platform](docs/operations/platform-support.md) — permissions,
|
|
272
|
+
source persistence, explicit rediscovery, and fallback policy.
|
|
273
|
+
- [`examples/README.md`](examples/README.md) — runnable examples and
|
|
274
|
+
prerequisites.
|
|
275
|
+
- `pocketstation.capture` — concise application and microphone capture.
|
|
276
|
+
- `pocketstation.session` — Session declarations and lifecycle.
|
|
277
|
+
- `pocketstation.graph` — stems, ports, routes, and signal contracts.
|
|
278
|
+
- `pocketstation.connector` — outbound provider authoring.
|
|
279
|
+
- `pocketstation.operator_authoring` — computation authoring.
|
|
280
|
+
- `pocketstation.source_authoring` — inbound provider authoring.
|
|
281
|
+
- `pocketstation.aio` — asyncio projection of the same engine.
|