chasqui-translate 0.2.0__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.
- chasqui_translate-0.2.0/LICENSE +21 -0
- chasqui_translate-0.2.0/PKG-INFO +263 -0
- chasqui_translate-0.2.0/README.md +229 -0
- chasqui_translate-0.2.0/chasqui/__init__.py +17 -0
- chasqui_translate-0.2.0/chasqui/__main__.py +6 -0
- chasqui_translate-0.2.0/chasqui/adapters/__init__.py +60 -0
- chasqui_translate-0.2.0/chasqui/adapters/base.py +155 -0
- chasqui_translate-0.2.0/chasqui/adapters/bridge.py +166 -0
- chasqui_translate-0.2.0/chasqui/adapters/device.py +47 -0
- chasqui_translate-0.2.0/chasqui/adapters/headset.py +171 -0
- chasqui_translate-0.2.0/chasqui/adapters/media.py +108 -0
- chasqui_translate-0.2.0/chasqui/adapters/meet.py +20 -0
- chasqui_translate-0.2.0/chasqui/adapters/text.py +67 -0
- chasqui_translate-0.2.0/chasqui/adapters/virtual_mic.py +80 -0
- chasqui_translate-0.2.0/chasqui/cli.py +392 -0
- chasqui_translate-0.2.0/chasqui/core/__init__.py +0 -0
- chasqui_translate-0.2.0/chasqui/core/audio.py +281 -0
- chasqui_translate-0.2.0/chasqui/core/config.py +169 -0
- chasqui_translate-0.2.0/chasqui/core/constants.py +25 -0
- chasqui_translate-0.2.0/chasqui/core/pipeline.py +404 -0
- chasqui_translate-0.2.0/chasqui/core/pricing.py +148 -0
- chasqui_translate-0.2.0/chasqui/core/selftest.py +131 -0
- chasqui_translate-0.2.0/chasqui/core/session.py +71 -0
- chasqui_translate-0.2.0/chasqui/core/windevice.py +68 -0
- chasqui_translate-0.2.0/chasqui/ui/__init__.py +9 -0
- chasqui_translate-0.2.0/chasqui/ui/app.py +1096 -0
- chasqui_translate-0.2.0/chasqui/ui/icon.py +233 -0
- chasqui_translate-0.2.0/chasqui/ui/runner.py +287 -0
- chasqui_translate-0.2.0/chasqui_translate.egg-info/PKG-INFO +263 -0
- chasqui_translate-0.2.0/chasqui_translate.egg-info/SOURCES.txt +47 -0
- chasqui_translate-0.2.0/chasqui_translate.egg-info/dependency_links.txt +1 -0
- chasqui_translate-0.2.0/chasqui_translate.egg-info/entry_points.txt +5 -0
- chasqui_translate-0.2.0/chasqui_translate.egg-info/requires.txt +13 -0
- chasqui_translate-0.2.0/chasqui_translate.egg-info/top_level.txt +1 -0
- chasqui_translate-0.2.0/pyproject.toml +66 -0
- chasqui_translate-0.2.0/setup.cfg +4 -0
- chasqui_translate-0.2.0/tests/test_adapter_control.py +181 -0
- chasqui_translate-0.2.0/tests/test_audio.py +69 -0
- chasqui_translate-0.2.0/tests/test_cli_streams.py +62 -0
- chasqui_translate-0.2.0/tests/test_config.py +50 -0
- chasqui_translate-0.2.0/tests/test_entry_points.py +78 -0
- chasqui_translate-0.2.0/tests/test_headset_wiring.py +210 -0
- chasqui_translate-0.2.0/tests/test_icon.py +258 -0
- chasqui_translate-0.2.0/tests/test_media_wiring.py +168 -0
- chasqui_translate-0.2.0/tests/test_pipeline.py +54 -0
- chasqui_translate-0.2.0/tests/test_pricing.py +109 -0
- chasqui_translate-0.2.0/tests/test_recv_flush.py +74 -0
- chasqui_translate-0.2.0/tests/test_session.py +22 -0
- chasqui_translate-0.2.0/tests/test_ui_runner.py +324 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 kvothesson
|
|
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.
|
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: chasqui-translate
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: Real-time simultaneous translation. Speak your language. Be understood.
|
|
5
|
+
Author: kvothesson
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/kvothesson/chasqui
|
|
8
|
+
Project-URL: Issues, https://github.com/kvothesson/chasqui/issues
|
|
9
|
+
Keywords: translation,real-time,speech,ai,interpreter,realtime-api
|
|
10
|
+
Classifier: Development Status :: 3 - Alpha
|
|
11
|
+
Classifier: Intended Audience :: End Users/Desktop
|
|
12
|
+
Classifier: Environment :: Win32 (MS Windows)
|
|
13
|
+
Classifier: Operating System :: Microsoft :: Windows
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
17
|
+
Classifier: Topic :: Multimedia :: Sound/Audio :: Speech
|
|
18
|
+
Requires-Python: >=3.11
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
20
|
+
License-File: LICENSE
|
|
21
|
+
Requires-Dist: openai>=1.40.0
|
|
22
|
+
Requires-Dist: websockets>=12.0
|
|
23
|
+
Requires-Dist: sounddevice>=0.4.6
|
|
24
|
+
Requires-Dist: numpy>=1.24.0
|
|
25
|
+
Requires-Dist: click>=8.1.0
|
|
26
|
+
Requires-Dist: pyyaml>=6.0
|
|
27
|
+
Requires-Dist: python-dotenv>=1.0.0
|
|
28
|
+
Provides-Extra: dev
|
|
29
|
+
Requires-Dist: pytest; extra == "dev"
|
|
30
|
+
Requires-Dist: pytest-asyncio; extra == "dev"
|
|
31
|
+
Requires-Dist: black; extra == "dev"
|
|
32
|
+
Requires-Dist: ruff; extra == "dev"
|
|
33
|
+
Dynamic: license-file
|
|
34
|
+
|
|
35
|
+
# chasqui
|
|
36
|
+
|
|
37
|
+
> *"El chasqui corría cientos de kilómetros llevando un mensaje que no podía leer.*
|
|
38
|
+
> *Transmitía sin entender. Conectaba sin pertenecer."*
|
|
39
|
+
|
|
40
|
+
Real-time simultaneous translation. Speak your language. Be understood.
|
|
41
|
+
|
|
42
|
+
*[Leer en español →](https://github.com/kvothesson/chasqui/blob/master/README.es.md)*
|
|
43
|
+
|
|
44
|
+
<p align="center">
|
|
45
|
+
<img src="https://raw.githubusercontent.com/kvothesson/chasqui/master/docs/img/ui-gadget.png" alt="The chasqui window: two language pickers, a mode, and Start" width="402">
|
|
46
|
+
</p>
|
|
47
|
+
|
|
48
|
+
---
|
|
49
|
+
|
|
50
|
+
## Install
|
|
51
|
+
|
|
52
|
+
**You need:** Python 3.11+, an OpenAI API key, and — for the modes that put
|
|
53
|
+
translated audio into another app — [VB-Audio Cable](https://vb-audio.com/Cable/)
|
|
54
|
+
on Windows.
|
|
55
|
+
|
|
56
|
+
The package is `chasqui-translate` (plain `chasqui` on PyPI belongs to an
|
|
57
|
+
unrelated project). It is not published yet, so install from source:
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
git clone https://github.com/kvothesson/chasqui && cd chasqui
|
|
61
|
+
pip install -e .
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Give it a key, either as an environment variable:
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
export OPENAI_API_KEY=sk-... # Windows: setx OPENAI_API_KEY sk-...
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
...or in a `chasqui.yaml` next to your project (`chasqui init` writes a starter
|
|
71
|
+
file). Then open the window:
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
chasqui ui
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### A desktop launcher
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
chasqui icon --shortcut
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
That writes the icon — drawn in code, there is no image file in the repo — and
|
|
84
|
+
puts a shortcut on your desktop. The shortcut runs `chasqui-ui`, a windowed
|
|
85
|
+
launcher. `chasqui ui` does the same thing but is a console script: fine from a
|
|
86
|
+
terminal, and from a double-click it parks a black console window behind the app
|
|
87
|
+
for the whole session.
|
|
88
|
+
|
|
89
|
+
### Linux note
|
|
90
|
+
|
|
91
|
+
Tkinter ships with Python, so the UI adds no dependency. On Debian/Ubuntu it
|
|
92
|
+
lives in a separate package:
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
sudo apt install python3-tk
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
The audio adapters are Windows-first and honest about it: `headset` and `media`
|
|
99
|
+
switch your Windows default devices through PowerShell. The `text` adapter runs
|
|
100
|
+
anywhere.
|
|
101
|
+
|
|
102
|
+
---
|
|
103
|
+
|
|
104
|
+
## Using the interface
|
|
105
|
+
|
|
106
|
+
The window has two shapes. The one above is the default: small enough to park
|
|
107
|
+
beside a call, and it shows only what you need mid-conversation.
|
|
108
|
+
|
|
109
|
+
### The two language pickers
|
|
110
|
+
|
|
111
|
+
**Your language is always the left box.** The captions above them change with
|
|
112
|
+
the mode and say whose language each box holds, so you never have to work it
|
|
113
|
+
out:
|
|
114
|
+
|
|
115
|
+
| Mode | Left box | Right box |
|
|
116
|
+
|---|---|---|
|
|
117
|
+
| `headset`, `meet`, `bridge` | I SPEAK | THEY SPEAK |
|
|
118
|
+
| `virtual-mic`, `device` | I SPEAK | THEY HEAR |
|
|
119
|
+
| `media` | I HEAR | THEY SPEAK |
|
|
120
|
+
|
|
121
|
+
The arrow between them tells you which way the translation travels: **⇄** in the
|
|
122
|
+
bidirectional modes, **→** when you are being broadcast one way, **←** in media
|
|
123
|
+
mode when audio is coming at you. Click it to swap the two languages.
|
|
124
|
+
|
|
125
|
+
Below them sits the mode picker. `Headset — bidirectional, any app` is the one
|
|
126
|
+
to start with.
|
|
127
|
+
|
|
128
|
+
### Start, Ping, and the state light
|
|
129
|
+
|
|
130
|
+
**START** opens the session. **PING** runs the same round-trip smoke test as
|
|
131
|
+
`chasqui check`: no mic, no speakers, just "is the pipeline alive". The hexagon
|
|
132
|
+
in the header is the node's state:
|
|
133
|
+
|
|
134
|
+
| Light | What is happening |
|
|
135
|
+
|---|---|
|
|
136
|
+
| `LATENT` | Idle. Nothing is running. |
|
|
137
|
+
| `COMPILING` | Starting up: opening the socket, taking over your default devices. |
|
|
138
|
+
| `ON ROUTE` | Live. Audio is being translated in both directions if the mode is bidirectional. |
|
|
139
|
+
| `UNWINDING` | Stopping, and handing your Windows devices back. |
|
|
140
|
+
| `PING` | Running the round-trip check. |
|
|
141
|
+
| `GLITCH` | Something failed. Open BUFFER — the reason is in there. |
|
|
142
|
+
|
|
143
|
+
**Stopping matters more than it looks.** The `headset` and `media` modes take
|
|
144
|
+
over your Windows default mic and output while they run and hand them back on
|
|
145
|
+
exit, so use STOP, or just close the window — it waits up to 15 seconds for the
|
|
146
|
+
handover — instead of killing the process.
|
|
147
|
+
|
|
148
|
+
The hexagon at the top right (**⬢** / **⬡**) keeps the window above everything
|
|
149
|
+
else. Filled means held on top, which is what you want beside a call.
|
|
150
|
+
|
|
151
|
+
### What it is costing you
|
|
152
|
+
|
|
153
|
+
The line under the buttons is a running cost, and it stays visible in the small
|
|
154
|
+
shape on purpose. `gpt-realtime-translate` bills by the minute, so chasqui
|
|
155
|
+
counts the audio it streams and prices it — and a bidirectional session pays for
|
|
156
|
+
two directions at once, which is worth seeing before a long call. Set
|
|
157
|
+
`pricing.budget` in `chasqui.yaml` to get "$X left" alongside it.
|
|
158
|
+
|
|
159
|
+
> OpenAI exposes no API for your remaining credit balance, so that budget is
|
|
160
|
+
> your own number, tracked locally — not a reading of your account. The `~`
|
|
161
|
+
> in front of the figure is deliberate: see `core/pricing.py` for what is
|
|
162
|
+
> measured and what is assumed.
|
|
163
|
+
|
|
164
|
+
### BUFFER — the full panel
|
|
165
|
+
|
|
166
|
+
Click **▸ BUFFER** and the window grows into the second shape: the voice picker,
|
|
167
|
+
the audio devices, and the log.
|
|
168
|
+
|
|
169
|
+
<p align="center">
|
|
170
|
+
<img src="https://raw.githubusercontent.com/kvothesson/chasqui/master/docs/img/ui-transcript.png" alt="The expanded window with a live transcript" width="402">
|
|
171
|
+
</p>
|
|
172
|
+
|
|
173
|
+
The log is where the conversation lands as it happens. Each turn shows the
|
|
174
|
+
source line in grey, the translation under it, and the round-trip latency:
|
|
175
|
+
**ice for what goes out, violet for what comes back**, so in a bidirectional
|
|
176
|
+
call you can see at a glance which way a line travelled. It also holds the
|
|
177
|
+
startup lines, the rate chasqui is pricing you at, and anything that went wrong.
|
|
178
|
+
|
|
179
|
+
Lines accumulate whether the panel is open or not, so opening it after a failure
|
|
180
|
+
shows you everything from the beginning. **FLUSH** clears it. The footer keeps
|
|
181
|
+
the model, the turn count and the average latency.
|
|
182
|
+
|
|
183
|
+
### Audio devices
|
|
184
|
+
|
|
185
|
+
Devices stay on **Auto**, and Auto is right almost always. Open
|
|
186
|
+
**▸ AUDIO DEVICES** only when autodetection picks the wrong cable.
|
|
187
|
+
|
|
188
|
+
<p align="center">
|
|
189
|
+
<img src="https://raw.githubusercontent.com/kvothesson/chasqui/master/docs/img/ui-devices.png" alt="The audio devices panel, four pickers on Auto" width="402">
|
|
190
|
+
</p>
|
|
191
|
+
|
|
192
|
+
Each field names both ends of the wire, and the pickers you do not need in the
|
|
193
|
+
current mode are greyed out:
|
|
194
|
+
|
|
195
|
+
| Field | What it is |
|
|
196
|
+
|---|---|
|
|
197
|
+
| **Your mic** | Where your voice goes in |
|
|
198
|
+
| **To the app** | The virtual cable Meet/Zoom/Discord listens to |
|
|
199
|
+
| **You hear** | Where the translation reaches you |
|
|
200
|
+
| **From the app** | Capture of what the app is saying |
|
|
201
|
+
|
|
202
|
+
**RESCAN** re-reads the device list — press it after plugging something in.
|
|
203
|
+
Pick devices by name, never by index: Windows renumbers indices whenever a
|
|
204
|
+
default changes, and chasqui's own adapters change defaults at startup.
|
|
205
|
+
|
|
206
|
+
Your languages, mode, voice, devices, window shape and running spend are all
|
|
207
|
+
remembered in `~/.chasqui/ui.json`.
|
|
208
|
+
|
|
209
|
+
---
|
|
210
|
+
|
|
211
|
+
## Adapters
|
|
212
|
+
|
|
213
|
+
| Adapter | Use case | Status |
|
|
214
|
+
|---|---|---|
|
|
215
|
+
| `text` | Keyboard in, terminal out — silent mode, accessibility, testing | ✅ Working |
|
|
216
|
+
| `virtual-mic` | Google Meet, Zoom, Teams, ChatGPT Voice (one-way: your voice, translated) | ✅ Working — validated end-to-end with ChatGPT Voice |
|
|
217
|
+
| `bridge` / `meet` | Bidirectional translation for any voice app (needs VB-Audio Cable) | OUT ✅ / IN ✅ (same plumbing as headset, validated there) |
|
|
218
|
+
| `headset` | Bidirectional for any app — Stereo Mix or Hi-Fi Cable for IN capture | ✅ **Validated end-to-end with ChatGPT Voice (2026-07-05), both directions.** Auto-switches default mic, restores on exit; separate mic/headphone via `--mic` / `--earphone` |
|
|
219
|
+
| `media` | YouTube, videos, any system audio → your language | ✅ Working — auto-switches Windows audio output to VB-Cable on start, restores on exit |
|
|
220
|
+
| `device` | Physical pendant (mic + speaker) — the **eco** hardware | Awaiting hardware |
|
|
221
|
+
|
|
222
|
+
## Commands
|
|
223
|
+
|
|
224
|
+
```bash
|
|
225
|
+
chasqui ui # desktop interface
|
|
226
|
+
chasqui icon --shortcut # desktop launcher + icon
|
|
227
|
+
chasqui translate "¿Dónde está la estación?" # one-shot text translation
|
|
228
|
+
chasqui listen --adapter device # live audio translation
|
|
229
|
+
chasqui listen --adapter headset --mic "USB Micro" --earphone "Auriculares"
|
|
230
|
+
# separate mic + headphones
|
|
231
|
+
chasqui devices # list audio devices, detect virtual mics
|
|
232
|
+
chasqui langs # supported language codes
|
|
233
|
+
chasqui init # generate chasqui.yaml starter config
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
## Configuration
|
|
237
|
+
|
|
238
|
+
Generate a starter config with `chasqui init`. Models are configurable per mode:
|
|
239
|
+
|
|
240
|
+
```yaml
|
|
241
|
+
api:
|
|
242
|
+
key: ${OPENAI_API_KEY}
|
|
243
|
+
model: gpt-realtime-translate # realtime model for audio adapters
|
|
244
|
+
text_model: gpt-4o-mini # chat model for the text adapter
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
> **403 `model_not_found`?** Your OpenAI project doesn't have access to the
|
|
248
|
+
> configured model. Check which models are enabled for your project at
|
|
249
|
+
> [platform.openai.com](https://platform.openai.com/settings) and set
|
|
250
|
+
> `api.model` / `api.text_model` accordingly.
|
|
251
|
+
|
|
252
|
+
## Development
|
|
253
|
+
|
|
254
|
+
```bash
|
|
255
|
+
pip install -e ".[dev]"
|
|
256
|
+
pytest
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
See [SPEC.md](https://github.com/kvothesson/chasqui/blob/master/SPEC.md) for full architecture and [AGENTS.md](https://github.com/kvothesson/chasqui/blob/master/AGENTS.md) for contributor/agent notes.
|
|
260
|
+
|
|
261
|
+
## License
|
|
262
|
+
|
|
263
|
+
MIT
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
# chasqui
|
|
2
|
+
|
|
3
|
+
> *"El chasqui corría cientos de kilómetros llevando un mensaje que no podía leer.*
|
|
4
|
+
> *Transmitía sin entender. Conectaba sin pertenecer."*
|
|
5
|
+
|
|
6
|
+
Real-time simultaneous translation. Speak your language. Be understood.
|
|
7
|
+
|
|
8
|
+
*[Leer en español →](https://github.com/kvothesson/chasqui/blob/master/README.es.md)*
|
|
9
|
+
|
|
10
|
+
<p align="center">
|
|
11
|
+
<img src="https://raw.githubusercontent.com/kvothesson/chasqui/master/docs/img/ui-gadget.png" alt="The chasqui window: two language pickers, a mode, and Start" width="402">
|
|
12
|
+
</p>
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## Install
|
|
17
|
+
|
|
18
|
+
**You need:** Python 3.11+, an OpenAI API key, and — for the modes that put
|
|
19
|
+
translated audio into another app — [VB-Audio Cable](https://vb-audio.com/Cable/)
|
|
20
|
+
on Windows.
|
|
21
|
+
|
|
22
|
+
The package is `chasqui-translate` (plain `chasqui` on PyPI belongs to an
|
|
23
|
+
unrelated project). It is not published yet, so install from source:
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
git clone https://github.com/kvothesson/chasqui && cd chasqui
|
|
27
|
+
pip install -e .
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Give it a key, either as an environment variable:
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
export OPENAI_API_KEY=sk-... # Windows: setx OPENAI_API_KEY sk-...
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
...or in a `chasqui.yaml` next to your project (`chasqui init` writes a starter
|
|
37
|
+
file). Then open the window:
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
chasqui ui
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### A desktop launcher
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
chasqui icon --shortcut
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
That writes the icon — drawn in code, there is no image file in the repo — and
|
|
50
|
+
puts a shortcut on your desktop. The shortcut runs `chasqui-ui`, a windowed
|
|
51
|
+
launcher. `chasqui ui` does the same thing but is a console script: fine from a
|
|
52
|
+
terminal, and from a double-click it parks a black console window behind the app
|
|
53
|
+
for the whole session.
|
|
54
|
+
|
|
55
|
+
### Linux note
|
|
56
|
+
|
|
57
|
+
Tkinter ships with Python, so the UI adds no dependency. On Debian/Ubuntu it
|
|
58
|
+
lives in a separate package:
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
sudo apt install python3-tk
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
The audio adapters are Windows-first and honest about it: `headset` and `media`
|
|
65
|
+
switch your Windows default devices through PowerShell. The `text` adapter runs
|
|
66
|
+
anywhere.
|
|
67
|
+
|
|
68
|
+
---
|
|
69
|
+
|
|
70
|
+
## Using the interface
|
|
71
|
+
|
|
72
|
+
The window has two shapes. The one above is the default: small enough to park
|
|
73
|
+
beside a call, and it shows only what you need mid-conversation.
|
|
74
|
+
|
|
75
|
+
### The two language pickers
|
|
76
|
+
|
|
77
|
+
**Your language is always the left box.** The captions above them change with
|
|
78
|
+
the mode and say whose language each box holds, so you never have to work it
|
|
79
|
+
out:
|
|
80
|
+
|
|
81
|
+
| Mode | Left box | Right box |
|
|
82
|
+
|---|---|---|
|
|
83
|
+
| `headset`, `meet`, `bridge` | I SPEAK | THEY SPEAK |
|
|
84
|
+
| `virtual-mic`, `device` | I SPEAK | THEY HEAR |
|
|
85
|
+
| `media` | I HEAR | THEY SPEAK |
|
|
86
|
+
|
|
87
|
+
The arrow between them tells you which way the translation travels: **⇄** in the
|
|
88
|
+
bidirectional modes, **→** when you are being broadcast one way, **←** in media
|
|
89
|
+
mode when audio is coming at you. Click it to swap the two languages.
|
|
90
|
+
|
|
91
|
+
Below them sits the mode picker. `Headset — bidirectional, any app` is the one
|
|
92
|
+
to start with.
|
|
93
|
+
|
|
94
|
+
### Start, Ping, and the state light
|
|
95
|
+
|
|
96
|
+
**START** opens the session. **PING** runs the same round-trip smoke test as
|
|
97
|
+
`chasqui check`: no mic, no speakers, just "is the pipeline alive". The hexagon
|
|
98
|
+
in the header is the node's state:
|
|
99
|
+
|
|
100
|
+
| Light | What is happening |
|
|
101
|
+
|---|---|
|
|
102
|
+
| `LATENT` | Idle. Nothing is running. |
|
|
103
|
+
| `COMPILING` | Starting up: opening the socket, taking over your default devices. |
|
|
104
|
+
| `ON ROUTE` | Live. Audio is being translated in both directions if the mode is bidirectional. |
|
|
105
|
+
| `UNWINDING` | Stopping, and handing your Windows devices back. |
|
|
106
|
+
| `PING` | Running the round-trip check. |
|
|
107
|
+
| `GLITCH` | Something failed. Open BUFFER — the reason is in there. |
|
|
108
|
+
|
|
109
|
+
**Stopping matters more than it looks.** The `headset` and `media` modes take
|
|
110
|
+
over your Windows default mic and output while they run and hand them back on
|
|
111
|
+
exit, so use STOP, or just close the window — it waits up to 15 seconds for the
|
|
112
|
+
handover — instead of killing the process.
|
|
113
|
+
|
|
114
|
+
The hexagon at the top right (**⬢** / **⬡**) keeps the window above everything
|
|
115
|
+
else. Filled means held on top, which is what you want beside a call.
|
|
116
|
+
|
|
117
|
+
### What it is costing you
|
|
118
|
+
|
|
119
|
+
The line under the buttons is a running cost, and it stays visible in the small
|
|
120
|
+
shape on purpose. `gpt-realtime-translate` bills by the minute, so chasqui
|
|
121
|
+
counts the audio it streams and prices it — and a bidirectional session pays for
|
|
122
|
+
two directions at once, which is worth seeing before a long call. Set
|
|
123
|
+
`pricing.budget` in `chasqui.yaml` to get "$X left" alongside it.
|
|
124
|
+
|
|
125
|
+
> OpenAI exposes no API for your remaining credit balance, so that budget is
|
|
126
|
+
> your own number, tracked locally — not a reading of your account. The `~`
|
|
127
|
+
> in front of the figure is deliberate: see `core/pricing.py` for what is
|
|
128
|
+
> measured and what is assumed.
|
|
129
|
+
|
|
130
|
+
### BUFFER — the full panel
|
|
131
|
+
|
|
132
|
+
Click **▸ BUFFER** and the window grows into the second shape: the voice picker,
|
|
133
|
+
the audio devices, and the log.
|
|
134
|
+
|
|
135
|
+
<p align="center">
|
|
136
|
+
<img src="https://raw.githubusercontent.com/kvothesson/chasqui/master/docs/img/ui-transcript.png" alt="The expanded window with a live transcript" width="402">
|
|
137
|
+
</p>
|
|
138
|
+
|
|
139
|
+
The log is where the conversation lands as it happens. Each turn shows the
|
|
140
|
+
source line in grey, the translation under it, and the round-trip latency:
|
|
141
|
+
**ice for what goes out, violet for what comes back**, so in a bidirectional
|
|
142
|
+
call you can see at a glance which way a line travelled. It also holds the
|
|
143
|
+
startup lines, the rate chasqui is pricing you at, and anything that went wrong.
|
|
144
|
+
|
|
145
|
+
Lines accumulate whether the panel is open or not, so opening it after a failure
|
|
146
|
+
shows you everything from the beginning. **FLUSH** clears it. The footer keeps
|
|
147
|
+
the model, the turn count and the average latency.
|
|
148
|
+
|
|
149
|
+
### Audio devices
|
|
150
|
+
|
|
151
|
+
Devices stay on **Auto**, and Auto is right almost always. Open
|
|
152
|
+
**▸ AUDIO DEVICES** only when autodetection picks the wrong cable.
|
|
153
|
+
|
|
154
|
+
<p align="center">
|
|
155
|
+
<img src="https://raw.githubusercontent.com/kvothesson/chasqui/master/docs/img/ui-devices.png" alt="The audio devices panel, four pickers on Auto" width="402">
|
|
156
|
+
</p>
|
|
157
|
+
|
|
158
|
+
Each field names both ends of the wire, and the pickers you do not need in the
|
|
159
|
+
current mode are greyed out:
|
|
160
|
+
|
|
161
|
+
| Field | What it is |
|
|
162
|
+
|---|---|
|
|
163
|
+
| **Your mic** | Where your voice goes in |
|
|
164
|
+
| **To the app** | The virtual cable Meet/Zoom/Discord listens to |
|
|
165
|
+
| **You hear** | Where the translation reaches you |
|
|
166
|
+
| **From the app** | Capture of what the app is saying |
|
|
167
|
+
|
|
168
|
+
**RESCAN** re-reads the device list — press it after plugging something in.
|
|
169
|
+
Pick devices by name, never by index: Windows renumbers indices whenever a
|
|
170
|
+
default changes, and chasqui's own adapters change defaults at startup.
|
|
171
|
+
|
|
172
|
+
Your languages, mode, voice, devices, window shape and running spend are all
|
|
173
|
+
remembered in `~/.chasqui/ui.json`.
|
|
174
|
+
|
|
175
|
+
---
|
|
176
|
+
|
|
177
|
+
## Adapters
|
|
178
|
+
|
|
179
|
+
| Adapter | Use case | Status |
|
|
180
|
+
|---|---|---|
|
|
181
|
+
| `text` | Keyboard in, terminal out — silent mode, accessibility, testing | ✅ Working |
|
|
182
|
+
| `virtual-mic` | Google Meet, Zoom, Teams, ChatGPT Voice (one-way: your voice, translated) | ✅ Working — validated end-to-end with ChatGPT Voice |
|
|
183
|
+
| `bridge` / `meet` | Bidirectional translation for any voice app (needs VB-Audio Cable) | OUT ✅ / IN ✅ (same plumbing as headset, validated there) |
|
|
184
|
+
| `headset` | Bidirectional for any app — Stereo Mix or Hi-Fi Cable for IN capture | ✅ **Validated end-to-end with ChatGPT Voice (2026-07-05), both directions.** Auto-switches default mic, restores on exit; separate mic/headphone via `--mic` / `--earphone` |
|
|
185
|
+
| `media` | YouTube, videos, any system audio → your language | ✅ Working — auto-switches Windows audio output to VB-Cable on start, restores on exit |
|
|
186
|
+
| `device` | Physical pendant (mic + speaker) — the **eco** hardware | Awaiting hardware |
|
|
187
|
+
|
|
188
|
+
## Commands
|
|
189
|
+
|
|
190
|
+
```bash
|
|
191
|
+
chasqui ui # desktop interface
|
|
192
|
+
chasqui icon --shortcut # desktop launcher + icon
|
|
193
|
+
chasqui translate "¿Dónde está la estación?" # one-shot text translation
|
|
194
|
+
chasqui listen --adapter device # live audio translation
|
|
195
|
+
chasqui listen --adapter headset --mic "USB Micro" --earphone "Auriculares"
|
|
196
|
+
# separate mic + headphones
|
|
197
|
+
chasqui devices # list audio devices, detect virtual mics
|
|
198
|
+
chasqui langs # supported language codes
|
|
199
|
+
chasqui init # generate chasqui.yaml starter config
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
## Configuration
|
|
203
|
+
|
|
204
|
+
Generate a starter config with `chasqui init`. Models are configurable per mode:
|
|
205
|
+
|
|
206
|
+
```yaml
|
|
207
|
+
api:
|
|
208
|
+
key: ${OPENAI_API_KEY}
|
|
209
|
+
model: gpt-realtime-translate # realtime model for audio adapters
|
|
210
|
+
text_model: gpt-4o-mini # chat model for the text adapter
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
> **403 `model_not_found`?** Your OpenAI project doesn't have access to the
|
|
214
|
+
> configured model. Check which models are enabled for your project at
|
|
215
|
+
> [platform.openai.com](https://platform.openai.com/settings) and set
|
|
216
|
+
> `api.model` / `api.text_model` accordingly.
|
|
217
|
+
|
|
218
|
+
## Development
|
|
219
|
+
|
|
220
|
+
```bash
|
|
221
|
+
pip install -e ".[dev]"
|
|
222
|
+
pytest
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
See [SPEC.md](https://github.com/kvothesson/chasqui/blob/master/SPEC.md) for full architecture and [AGENTS.md](https://github.com/kvothesson/chasqui/blob/master/AGENTS.md) for contributor/agent notes.
|
|
226
|
+
|
|
227
|
+
## License
|
|
228
|
+
|
|
229
|
+
MIT
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"""Chasqui — Real-time simultaneous translation."""
|
|
2
|
+
|
|
3
|
+
__version__ = "0.2.0"
|
|
4
|
+
|
|
5
|
+
from .core.config import Config
|
|
6
|
+
from .core.pipeline import Pipeline, TranslationEvent
|
|
7
|
+
from .core.audio import AudioStream, MicStream, SpeakerStream, NullStream
|
|
8
|
+
|
|
9
|
+
__all__ = [
|
|
10
|
+
"Config",
|
|
11
|
+
"Pipeline",
|
|
12
|
+
"TranslationEvent",
|
|
13
|
+
"AudioStream",
|
|
14
|
+
"MicStream",
|
|
15
|
+
"SpeakerStream",
|
|
16
|
+
"NullStream",
|
|
17
|
+
]
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
"""Adapter registry — the single place that maps adapter names to classes.
|
|
2
|
+
|
|
3
|
+
Imports stay lazy: `chasqui devices` and `chasqui langs` must not pay for
|
|
4
|
+
sounddevice/openai import time, and a missing optional dependency should only
|
|
5
|
+
blow up for the adapter that actually needs it.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import importlib
|
|
9
|
+
from typing import TYPE_CHECKING
|
|
10
|
+
|
|
11
|
+
if TYPE_CHECKING: # pragma: no cover - typing only
|
|
12
|
+
from ..core.config import Config
|
|
13
|
+
from .base import BaseAdapter
|
|
14
|
+
|
|
15
|
+
ADAPTER_PATHS: dict[str, str] = {
|
|
16
|
+
"text": "chasqui.adapters.text.TextAdapter",
|
|
17
|
+
"device": "chasqui.adapters.device.DeviceAdapter",
|
|
18
|
+
"virtual-mic": "chasqui.adapters.virtual_mic.VirtualMicAdapter",
|
|
19
|
+
"media": "chasqui.adapters.media.MediaAdapter",
|
|
20
|
+
"bridge": "chasqui.adapters.bridge.BridgeAdapter",
|
|
21
|
+
"meet": "chasqui.adapters.meet.MeetAdapter",
|
|
22
|
+
"headset": "chasqui.adapters.headset.HeadsetAdapter",
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
ADAPTER_NAMES: list[str] = list(ADAPTER_PATHS)
|
|
26
|
+
|
|
27
|
+
# One-line descriptions, shown in the UI's mode picker.
|
|
28
|
+
ADAPTER_LABELS: dict[str, str] = {
|
|
29
|
+
"headset": "Headset — bidirectional, any app (recommended)",
|
|
30
|
+
"meet": "Meet / Zoom / Teams — bidirectional, two cables",
|
|
31
|
+
"bridge": "Bridge — bidirectional, two cables",
|
|
32
|
+
"virtual-mic": "Virtual mic — one way, your voice into any app",
|
|
33
|
+
"media": "Media — translate what your system is playing",
|
|
34
|
+
"device": "Device — physical mic + speaker pendant",
|
|
35
|
+
"text": "Text — keyboard in, terminal out",
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
# Adapters the GUI can drive. The text adapter is a blocking stdin REPL, so it
|
|
39
|
+
# has nothing to read from in a windowed session.
|
|
40
|
+
UI_ADAPTER_NAMES: list[str] = [
|
|
41
|
+
"headset", "meet", "bridge", "virtual-mic", "media", "device",
|
|
42
|
+
]
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def get_adapter_class(name: str) -> type["BaseAdapter"]:
|
|
46
|
+
"""Resolve an adapter name to its class. Raises KeyError if unknown."""
|
|
47
|
+
try:
|
|
48
|
+
path = ADAPTER_PATHS[name]
|
|
49
|
+
except KeyError:
|
|
50
|
+
raise KeyError(
|
|
51
|
+
f"Unknown adapter {name!r}. Available: {', '.join(ADAPTER_NAMES)}"
|
|
52
|
+
) from None
|
|
53
|
+
|
|
54
|
+
module_path, class_name = path.rsplit(".", 1)
|
|
55
|
+
return getattr(importlib.import_module(module_path), class_name)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def build_adapter(config: "Config") -> "BaseAdapter":
|
|
59
|
+
"""Instantiate the adapter named by `config.adapter`."""
|
|
60
|
+
return get_adapter_class(config.adapter)(config)
|