vinowhisper 0.3.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.
Files changed (38) hide show
  1. vinowhisper-0.3.0/LICENSE +21 -0
  2. vinowhisper-0.3.0/PKG-INFO +160 -0
  3. vinowhisper-0.3.0/README.md +125 -0
  4. vinowhisper-0.3.0/pyproject.toml +204 -0
  5. vinowhisper-0.3.0/setup.cfg +4 -0
  6. vinowhisper-0.3.0/tests/test_audio.py +101 -0
  7. vinowhisper-0.3.0/tests/test_capture.py +181 -0
  8. vinowhisper-0.3.0/tests/test_devices.py +195 -0
  9. vinowhisper-0.3.0/tests/test_distro.py +75 -0
  10. vinowhisper-0.3.0/tests/test_doctor.py +140 -0
  11. vinowhisper-0.3.0/tests/test_session.py +91 -0
  12. vinowhisper-0.3.0/tests/test_stitch.py +136 -0
  13. vinowhisper-0.3.0/tests/test_ui.py +156 -0
  14. vinowhisper-0.3.0/tests/test_wizard.py +129 -0
  15. vinowhisper-0.3.0/vinowhisper/__init__.py +10 -0
  16. vinowhisper-0.3.0/vinowhisper/audio.py +114 -0
  17. vinowhisper-0.3.0/vinowhisper/caption.py +353 -0
  18. vinowhisper-0.3.0/vinowhisper/capture.py +305 -0
  19. vinowhisper-0.3.0/vinowhisper/client.py +70 -0
  20. vinowhisper-0.3.0/vinowhisper/config.py +133 -0
  21. vinowhisper-0.3.0/vinowhisper/devices.py +445 -0
  22. vinowhisper-0.3.0/vinowhisper/distro.py +330 -0
  23. vinowhisper-0.3.0/vinowhisper/doctor.py +445 -0
  24. vinowhisper-0.3.0/vinowhisper/events.py +71 -0
  25. vinowhisper-0.3.0/vinowhisper/recorder.py +161 -0
  26. vinowhisper-0.3.0/vinowhisper/replay.py +184 -0
  27. vinowhisper-0.3.0/vinowhisper/server.py +175 -0
  28. vinowhisper-0.3.0/vinowhisper/session.py +80 -0
  29. vinowhisper-0.3.0/vinowhisper/stitch.py +215 -0
  30. vinowhisper-0.3.0/vinowhisper/transcriber.py +171 -0
  31. vinowhisper-0.3.0/vinowhisper/ui.py +404 -0
  32. vinowhisper-0.3.0/vinowhisper/wizard.py +427 -0
  33. vinowhisper-0.3.0/vinowhisper.egg-info/PKG-INFO +160 -0
  34. vinowhisper-0.3.0/vinowhisper.egg-info/SOURCES.txt +36 -0
  35. vinowhisper-0.3.0/vinowhisper.egg-info/dependency_links.txt +1 -0
  36. vinowhisper-0.3.0/vinowhisper.egg-info/entry_points.txt +6 -0
  37. vinowhisper-0.3.0/vinowhisper.egg-info/requires.txt +9 -0
  38. vinowhisper-0.3.0/vinowhisper.egg-info/top_level.txt +1 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Karan Shukla
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,160 @@
1
+ Metadata-Version: 2.4
2
+ Name: vinowhisper
3
+ Version: 0.3.0
4
+ Summary: NPU-accelerated local live captioning for Linux, via OpenVINO GenAI Whisper
5
+ Author: Karan Shukla
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/karanshukla/vinoWhisper
8
+ Project-URL: Repository, https://github.com/karanshukla/vinoWhisper
9
+ Project-URL: Issues, https://github.com/karanshukla/vinoWhisper/issues
10
+ Project-URL: Changelog, https://github.com/karanshukla/vinoWhisper/blob/main/CHANGELOG.md
11
+ Keywords: whisper,openvino,npu,captions,speech-to-text,pipewire,linux
12
+ Classifier: Development Status :: 4 - Beta
13
+ Classifier: Environment :: Console
14
+ Classifier: Intended Audience :: End Users/Desktop
15
+ Classifier: Operating System :: POSIX :: Linux
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Programming Language :: Python :: 3.13
20
+ Classifier: Topic :: Multimedia :: Sound/Audio :: Speech
21
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
22
+ Requires-Python: <3.14,>=3.11
23
+ Description-Content-Type: text/markdown
24
+ License-File: LICENSE
25
+ Requires-Dist: openvino>=2026.3.1
26
+ Requires-Dist: openvino-genai>=2026.3.1.0
27
+ Requires-Dist: openvino-tokenizers>=2026.3.1.0
28
+ Requires-Dist: optimum[openvino]
29
+ Requires-Dist: flask
30
+ Requires-Dist: werkzeug
31
+ Requires-Dist: requests
32
+ Requires-Dist: numpy
33
+ Requires-Dist: rich
34
+ Dynamic: license-file
35
+
36
+ # vinoWhisper
37
+
38
+ [![CI](https://github.com/karanshukla/vinoWhisper/actions/workflows/ci.yml/badge.svg)](https://github.com/karanshukla/vinoWhisper/actions/workflows/ci.yml)
39
+ [![License](https://img.shields.io/badge/license-MIT-blue)](https://github.com/karanshukla/vinoWhisper/blob/main/LICENSE)
40
+ [![Python](https://img.shields.io/badge/python-3.11%20|%203.12%20|%203.13-blue)](https://github.com/karanshukla/vinoWhisper/blob/main/pyproject.toml)
41
+ [![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff)
42
+
43
+ NPU-accelerated local live captioning for Linux, using OpenVINO GenAI's
44
+ `WhisperPipeline` on an Intel NPU. Named after
45
+ [vinoAuthFace](https://github.com/karanshukla/vinoAuthFace), same idea of
46
+ OpenVINO doing the NPU work, different feature.
47
+
48
+ Point it at whatever is playing and it captions in your terminal. Nothing
49
+ leaves the machine. Start it and it goes; there is nothing to interact with.
50
+
51
+ <img width="1237" height="530" alt="image" src="https://github.com/user-attachments/assets/f263eabf-f1f4-4ab2-9b68-bc50eaf92ea0" />
52
+
53
+ The transcript scrolls above that bar in your terminal's own scrollback, so it
54
+ is still there after you quit and your terminal's selection and search still
55
+ work on it. `hearing…` is the words heard once but still waiting on a second
56
+ cycle to agree, which is the two-cycle commit delay made visible rather than
57
+ felt as a freeze.
58
+
59
+ ## Install
60
+
61
+ ```bash
62
+ curl -fsSL https://raw.githubusercontent.com/karanshukla/vinoWhisper/main/scripts/install.sh | bash
63
+ ```
64
+
65
+ That installs [uv](https://docs.astral.sh/uv/), clones the repo, builds the
66
+ environment, and hands over to `vinowhisper-setup`, which is where every
67
+ machine-specific decision happens: your capture tool, your NPU driver, the
68
+ model export your device needs, and systemd units generated against the paths
69
+ that actually exist. It prints every command before running it and asks first.
70
+
71
+ From a checkout, or to see what it would do without doing it:
72
+
73
+ ```bash
74
+ git clone https://github.com/karanshukla/vinoWhisper && cd vinoWhisper
75
+ uv sync
76
+ uv run vinowhisper-setup --dry-run # the whole plan, nothing changed
77
+ uv run vinowhisper-setup # for real, one prompt per step
78
+ ```
79
+
80
+ Or from PyPI, if you would rather wire up the machine yourself:
81
+
82
+ ```bash
83
+ pip install vinowhisper # needs Python 3.11-3.13
84
+ vinowhisper-setup # still worth running: NPU driver, model export, units
85
+ ```
86
+
87
+ `pip install` gets you the five commands and the Python dependencies. It cannot
88
+ get you an NPU driver, a model export or systemd units, which is what
89
+ `vinowhisper-setup` is for either way. See
90
+ [docs/install.md](https://github.com/karanshukla/vinoWhisper/blob/main/docs/install.md) for the OpenVINO version
91
+ floor and why this could not be a pip install until 2026-08-31.
92
+
93
+ ## Commands
94
+
95
+ ```
96
+ vinowhisper-caption # caption system audio
97
+ vinowhisper-caption --source mic # caption yourself
98
+ vinowhisper-caption --list-targets # capture one app instead of the whole sink
99
+ vinowhisper-caption --debug # per-cycle timings, levels, raw transcript
100
+ vinowhisper-caption --record ~/sess # save the session for replay
101
+ vinowhisper-caption --plain > out.txt # no status bar (implied when piping)
102
+
103
+ vinowhisper-setup # guided install; re-runnable, idempotent
104
+ vinowhisper-setup --dry-run # print the plan, change nothing
105
+ vinowhisper-setup --print-units # the systemd units it would generate
106
+
107
+ vinowhisper-doctor # devices, model, audio, live levels
108
+ vinowhisper-doctor --json # the same, for a bug report
109
+ vinowhisper-doctor --no-probe # skip the 2s-per-target level capture
110
+
111
+ vinowhisper-replay ~/sess --restitch # re-run the merge logic offline
112
+ vinowhisper-replay ~/sess --sweep 8,12,20 # measure what --window actually costs
113
+ ```
114
+
115
+ ## Hardware
116
+
117
+ The NPU is the point. Everything below it exists so a broken driver degrades
118
+ the tool instead of bricking it.
119
+
120
+ | Device | Selected | Model export | What you get |
121
+ |---|---|---|---|
122
+ | **NPU** (`Intel(R) AI Boost`) | first | `--disable-stateful` | ~1.19s per 30s window, measured 2026-08-03 |
123
+ | **GPU** (Arc / Xe) | second | stateful | Untested here. Works in principle; watch the lag figure |
124
+ | **CPU** | last resort | stateful | Runs. Competes with everything else on the machine, and lags |
125
+
126
+ Selection is automatic and a fallback is never silent: it shows up in the
127
+ server journal, in `/health`, in `vinowhisper-doctor`, and on the status bar as
128
+ a red border. The two model exports are not interchangeable, and the NPU needs
129
+ a userspace driver half that no distro packages completely.
130
+ [docs/hardware.md](https://github.com/karanshukla/vinoWhisper/blob/main/docs/hardware.md) covers all of it, including what to do
131
+ when the NPU does not show up.
132
+
133
+ Audio capture works on PipeWire (`pw-record`) or PulseAudio (`parec`), picked
134
+ automatically, and package names for eight distro families live in one table in
135
+ [`vinowhisper/distro.py`](https://github.com/karanshukla/vinoWhisper/blob/main/vinowhisper/distro.py). **If a name is wrong for your
136
+ distro, that is expected, and it is the fastest thing here to fix.**
137
+
138
+ ## Docs
139
+
140
+ | | |
141
+ |---|---|
142
+ | [Installing](https://github.com/karanshukla/vinoWhisper/blob/main/docs/install.md) | What the installer does, the OpenVINO version floor and why, pinning the window on top |
143
+ | [Hardware](https://github.com/karanshukla/vinoWhisper/blob/main/docs/hardware.md) | Device selection, the two model exports, and every way the NPU fails to appear |
144
+ | [Audio capture](https://github.com/karanshukla/vinoWhisper/blob/main/docs/audio.md) | PipeWire vs PulseAudio, distro coverage, and what actually silences a capture (it is not the mute button) |
145
+ | [Latency](https://github.com/karanshukla/vinoWhisper/blob/main/docs/latency.md) | Why captions trail the audio, the one knob that changes it, and why the wording drifts |
146
+ | [Debugging](https://github.com/karanshukla/vinoWhisper/blob/main/docs/debugging.md) | `--record`, offline replay, and what `vinowhisper-doctor` measures |
147
+ | [Architecture](https://github.com/karanshukla/vinoWhisper/blob/main/docs/architecture.md) | Socket activation and scale-to-zero, and how to stop it |
148
+
149
+ ## More
150
+
151
+ - Design doc, benchmarks, and the three export bugs hit getting to a working
152
+ NPU pipeline:
153
+ [wildcat-lake-linux/input/f5-voice-typing.md](https://github.com/karanshukla/wildcat-lake-linux/blob/main/input/f5-voice-typing.md)
154
+ - [CONTRIBUTING.md](https://github.com/karanshukla/vinoWhisper/blob/main/CONTRIBUTING.md), where the useful contributions are distro
155
+ corrections and reports from hardware that isn't this laptop
156
+ - [SECURITY.md](https://github.com/karanshukla/vinoWhisper/blob/main/SECURITY.md), what stays on the machine and what the loopback
157
+ server's trust boundary actually is
158
+ - [CHANGELOG.md](https://github.com/karanshukla/vinoWhisper/blob/main/CHANGELOG.md)
159
+
160
+ MIT licensed.
@@ -0,0 +1,125 @@
1
+ # vinoWhisper
2
+
3
+ [![CI](https://github.com/karanshukla/vinoWhisper/actions/workflows/ci.yml/badge.svg)](https://github.com/karanshukla/vinoWhisper/actions/workflows/ci.yml)
4
+ [![License](https://img.shields.io/badge/license-MIT-blue)](https://github.com/karanshukla/vinoWhisper/blob/main/LICENSE)
5
+ [![Python](https://img.shields.io/badge/python-3.11%20|%203.12%20|%203.13-blue)](https://github.com/karanshukla/vinoWhisper/blob/main/pyproject.toml)
6
+ [![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff)
7
+
8
+ NPU-accelerated local live captioning for Linux, using OpenVINO GenAI's
9
+ `WhisperPipeline` on an Intel NPU. Named after
10
+ [vinoAuthFace](https://github.com/karanshukla/vinoAuthFace), same idea of
11
+ OpenVINO doing the NPU work, different feature.
12
+
13
+ Point it at whatever is playing and it captions in your terminal. Nothing
14
+ leaves the machine. Start it and it goes; there is nothing to interact with.
15
+
16
+ <img width="1237" height="530" alt="image" src="https://github.com/user-attachments/assets/f263eabf-f1f4-4ab2-9b68-bc50eaf92ea0" />
17
+
18
+ The transcript scrolls above that bar in your terminal's own scrollback, so it
19
+ is still there after you quit and your terminal's selection and search still
20
+ work on it. `hearing…` is the words heard once but still waiting on a second
21
+ cycle to agree, which is the two-cycle commit delay made visible rather than
22
+ felt as a freeze.
23
+
24
+ ## Install
25
+
26
+ ```bash
27
+ curl -fsSL https://raw.githubusercontent.com/karanshukla/vinoWhisper/main/scripts/install.sh | bash
28
+ ```
29
+
30
+ That installs [uv](https://docs.astral.sh/uv/), clones the repo, builds the
31
+ environment, and hands over to `vinowhisper-setup`, which is where every
32
+ machine-specific decision happens: your capture tool, your NPU driver, the
33
+ model export your device needs, and systemd units generated against the paths
34
+ that actually exist. It prints every command before running it and asks first.
35
+
36
+ From a checkout, or to see what it would do without doing it:
37
+
38
+ ```bash
39
+ git clone https://github.com/karanshukla/vinoWhisper && cd vinoWhisper
40
+ uv sync
41
+ uv run vinowhisper-setup --dry-run # the whole plan, nothing changed
42
+ uv run vinowhisper-setup # for real, one prompt per step
43
+ ```
44
+
45
+ Or from PyPI, if you would rather wire up the machine yourself:
46
+
47
+ ```bash
48
+ pip install vinowhisper # needs Python 3.11-3.13
49
+ vinowhisper-setup # still worth running: NPU driver, model export, units
50
+ ```
51
+
52
+ `pip install` gets you the five commands and the Python dependencies. It cannot
53
+ get you an NPU driver, a model export or systemd units, which is what
54
+ `vinowhisper-setup` is for either way. See
55
+ [docs/install.md](https://github.com/karanshukla/vinoWhisper/blob/main/docs/install.md) for the OpenVINO version
56
+ floor and why this could not be a pip install until 2026-08-31.
57
+
58
+ ## Commands
59
+
60
+ ```
61
+ vinowhisper-caption # caption system audio
62
+ vinowhisper-caption --source mic # caption yourself
63
+ vinowhisper-caption --list-targets # capture one app instead of the whole sink
64
+ vinowhisper-caption --debug # per-cycle timings, levels, raw transcript
65
+ vinowhisper-caption --record ~/sess # save the session for replay
66
+ vinowhisper-caption --plain > out.txt # no status bar (implied when piping)
67
+
68
+ vinowhisper-setup # guided install; re-runnable, idempotent
69
+ vinowhisper-setup --dry-run # print the plan, change nothing
70
+ vinowhisper-setup --print-units # the systemd units it would generate
71
+
72
+ vinowhisper-doctor # devices, model, audio, live levels
73
+ vinowhisper-doctor --json # the same, for a bug report
74
+ vinowhisper-doctor --no-probe # skip the 2s-per-target level capture
75
+
76
+ vinowhisper-replay ~/sess --restitch # re-run the merge logic offline
77
+ vinowhisper-replay ~/sess --sweep 8,12,20 # measure what --window actually costs
78
+ ```
79
+
80
+ ## Hardware
81
+
82
+ The NPU is the point. Everything below it exists so a broken driver degrades
83
+ the tool instead of bricking it.
84
+
85
+ | Device | Selected | Model export | What you get |
86
+ |---|---|---|---|
87
+ | **NPU** (`Intel(R) AI Boost`) | first | `--disable-stateful` | ~1.19s per 30s window, measured 2026-08-03 |
88
+ | **GPU** (Arc / Xe) | second | stateful | Untested here. Works in principle; watch the lag figure |
89
+ | **CPU** | last resort | stateful | Runs. Competes with everything else on the machine, and lags |
90
+
91
+ Selection is automatic and a fallback is never silent: it shows up in the
92
+ server journal, in `/health`, in `vinowhisper-doctor`, and on the status bar as
93
+ a red border. The two model exports are not interchangeable, and the NPU needs
94
+ a userspace driver half that no distro packages completely.
95
+ [docs/hardware.md](https://github.com/karanshukla/vinoWhisper/blob/main/docs/hardware.md) covers all of it, including what to do
96
+ when the NPU does not show up.
97
+
98
+ Audio capture works on PipeWire (`pw-record`) or PulseAudio (`parec`), picked
99
+ automatically, and package names for eight distro families live in one table in
100
+ [`vinowhisper/distro.py`](https://github.com/karanshukla/vinoWhisper/blob/main/vinowhisper/distro.py). **If a name is wrong for your
101
+ distro, that is expected, and it is the fastest thing here to fix.**
102
+
103
+ ## Docs
104
+
105
+ | | |
106
+ |---|---|
107
+ | [Installing](https://github.com/karanshukla/vinoWhisper/blob/main/docs/install.md) | What the installer does, the OpenVINO version floor and why, pinning the window on top |
108
+ | [Hardware](https://github.com/karanshukla/vinoWhisper/blob/main/docs/hardware.md) | Device selection, the two model exports, and every way the NPU fails to appear |
109
+ | [Audio capture](https://github.com/karanshukla/vinoWhisper/blob/main/docs/audio.md) | PipeWire vs PulseAudio, distro coverage, and what actually silences a capture (it is not the mute button) |
110
+ | [Latency](https://github.com/karanshukla/vinoWhisper/blob/main/docs/latency.md) | Why captions trail the audio, the one knob that changes it, and why the wording drifts |
111
+ | [Debugging](https://github.com/karanshukla/vinoWhisper/blob/main/docs/debugging.md) | `--record`, offline replay, and what `vinowhisper-doctor` measures |
112
+ | [Architecture](https://github.com/karanshukla/vinoWhisper/blob/main/docs/architecture.md) | Socket activation and scale-to-zero, and how to stop it |
113
+
114
+ ## More
115
+
116
+ - Design doc, benchmarks, and the three export bugs hit getting to a working
117
+ NPU pipeline:
118
+ [wildcat-lake-linux/input/f5-voice-typing.md](https://github.com/karanshukla/wildcat-lake-linux/blob/main/input/f5-voice-typing.md)
119
+ - [CONTRIBUTING.md](https://github.com/karanshukla/vinoWhisper/blob/main/CONTRIBUTING.md), where the useful contributions are distro
120
+ corrections and reports from hardware that isn't this laptop
121
+ - [SECURITY.md](https://github.com/karanshukla/vinoWhisper/blob/main/SECURITY.md), what stays on the machine and what the loopback
122
+ server's trust boundary actually is
123
+ - [CHANGELOG.md](https://github.com/karanshukla/vinoWhisper/blob/main/CHANGELOG.md)
124
+
125
+ MIT licensed.
@@ -0,0 +1,204 @@
1
+ [project]
2
+ name = "vinowhisper"
3
+ version = "0.3.0"
4
+ description = "NPU-accelerated local live captioning for Linux, via OpenVINO GenAI Whisper"
5
+ readme = "README.md"
6
+ license = "MIT"
7
+ license-files = ["LICENSE"]
8
+ authors = [{ name = "Karan Shukla" }]
9
+ keywords = ["whisper", "openvino", "npu", "captions", "speech-to-text", "pipewire", "linux"]
10
+ classifiers = [
11
+ "Development Status :: 4 - Beta",
12
+ "Environment :: Console",
13
+ "Intended Audience :: End Users/Desktop",
14
+ "Operating System :: POSIX :: Linux",
15
+ "Programming Language :: Python :: 3",
16
+ "Programming Language :: Python :: 3.11",
17
+ "Programming Language :: Python :: 3.12",
18
+ "Programming Language :: Python :: 3.13",
19
+ "Topic :: Multimedia :: Sound/Audio :: Speech",
20
+ "Topic :: Scientific/Engineering :: Artificial Intelligence",
21
+ ]
22
+ # Python 3.14 is unusable here: it made functools.partial a descriptor, which
23
+ # breaks optimum's `NORMALIZED_CONFIG_CLASS = SomeConfig.with_args(...)` class-
24
+ # attribute idiom (self gets auto-bound as an extra positional arg). Confirmed
25
+ # 2026-08-03 across every optimum/optimum-intel/transformers version tried —
26
+ # root cause, not a version-pairing issue. Use 3.13 until upstream fixes it.
27
+ requires-python = ">=3.11,<3.14"
28
+ dependencies = [
29
+ # 2026.3.1 is the floor because it is the first *stable* release that can
30
+ # build the NPU static Whisper pipeline. Stable 2026.2.1 could not: its
31
+ # pipeline_static.cpp pattern-matcher did not recognize the optimum-intel
32
+ # export's SDPA attention-mask node shape
33
+ # (OPENVINO_ASSERT(!self_attn_nodes.empty())), which is why this project
34
+ # pinned the nightly wheel index from 2026-08-03 until 2026-08-31.
35
+ # Re-measured 2026-08-31 on the Wildcat Lake NPU against the same
36
+ # --disable-stateful export, WhisperPipeline(..., STATIC_PIPELINE=True):
37
+ # 2026.3.1 stable builds in 2.0s and decodes, as do nightly 2026.4.0.dev-
38
+ # 20260805 and 2026.5.0.dev20260831. Stable caught up, so the nightly
39
+ # index, the global prerelease policy and the weekly resolve canary are
40
+ # all gone and these resolve from PyPI like anything else.
41
+ "openvino>=2026.3.1",
42
+ "openvino-genai>=2026.3.1.0",
43
+ # Pulled in transitively by openvino-genai and still listed directly: the
44
+ # NPU pipeline hard-depends on it, and a transitive-only dependency is one
45
+ # upstream packaging change away from disappearing silently.
46
+ "openvino-tokenizers>=2026.3.1.0",
47
+ "optimum[openvino]",
48
+ "flask",
49
+ "werkzeug",
50
+ "requests",
51
+ "numpy",
52
+ # Status bar only. Rich rather than Textual because the transcript is
53
+ # append-only (see stitch.py), so it belongs in the terminal's own
54
+ # scrollback and only the status line needs to redraw.
55
+ "rich",
56
+ ]
57
+
58
+ [project.urls]
59
+ Homepage = "https://github.com/karanshukla/vinoWhisper"
60
+ Repository = "https://github.com/karanshukla/vinoWhisper"
61
+ Issues = "https://github.com/karanshukla/vinoWhisper/issues"
62
+ Changelog = "https://github.com/karanshukla/vinoWhisper/blob/main/CHANGELOG.md"
63
+
64
+ [project.scripts]
65
+ vinowhisper-caption = "vinowhisper.caption:main"
66
+ vinowhisper-server = "vinowhisper.server:main"
67
+ vinowhisper-replay = "vinowhisper.replay:main"
68
+ vinowhisper-doctor = "vinowhisper.doctor:main"
69
+ vinowhisper-setup = "vinowhisper.wizard:main"
70
+
71
+ [build-system]
72
+ requires = ["setuptools>=77"] # PEP 639 license expressions
73
+ build-backend = "setuptools.build_meta"
74
+
75
+ [tool.setuptools]
76
+ # Without this, setuptools' flat-layout auto-discovery sees the top-level
77
+ # systemd/ directory (unit files, not a package) and refuses to build,
78
+ # thinking it found two packages. Confirmed failing 2026-08-03.
79
+ packages = ["vinowhisper"]
80
+
81
+ # ── uv ────────────────────────────────────────────────────────────────────────
82
+
83
+ [dependency-groups]
84
+ # Deliberately installable on its own (`uv sync --only-group dev`): CI has no
85
+ # NPU, no audio server and no reason to download ~400MB of OpenVINO to run
86
+ # tests that are forbidden from importing it. Nothing under tests/ imports
87
+ # openvino, transcriber or server, which is what keeps this split honest.
88
+ dev = [
89
+ "pytest>=8.0",
90
+ "pytest-cov>=5.0",
91
+ "ruff>=0.6",
92
+ "mypy>=1.10",
93
+ "poethepoet>=0.27",
94
+ "bump-my-version>=0.24",
95
+ "types-requests",
96
+ "numpy",
97
+ "rich",
98
+ ]
99
+
100
+ # ── Tasks ─────────────────────────────────────────────────────────────────────
101
+
102
+ [tool.poe.tasks]
103
+ lint = "ruff check ."
104
+ fmt = "ruff format --check ."
105
+ types = "mypy vinowhisper/"
106
+ test = "pytest -v --tb=short"
107
+ fix.sequence = [{ cmd = "ruff check --fix ." }, { cmd = "ruff format ." }]
108
+ security.cmd = "uvx --from bandit[toml] bandit -c pyproject.toml -r vinowhisper/"
109
+ security.help = "the same Bandit scan the Bandit workflow runs"
110
+ check.sequence = ["lint", "fmt", "types", "test"]
111
+ check.help = "everything CI runs, locally"
112
+ doctor = "python -m vinowhisper.doctor"
113
+
114
+ # ── Ruff ──────────────────────────────────────────────────────────────────────
115
+
116
+ [tool.ruff]
117
+ line-length = 100
118
+ target-version = "py311"
119
+
120
+ [tool.ruff.lint]
121
+ select = ["E", "F", "I", "UP", "B", "SIM"]
122
+ ignore = [
123
+ "E501", # line length is the formatter's job
124
+ "SIM108", # ternary-instead-of-if is often less readable
125
+ ]
126
+
127
+ [tool.ruff.lint.isort]
128
+ known-first-party = ["vinowhisper"]
129
+
130
+ # ── Mypy ──────────────────────────────────────────────────────────────────────
131
+
132
+ [tool.mypy]
133
+ # Deliberately not pinned to python_version = "3.11". Numpy's own stubs use
134
+ # 3.12-only syntax (`type X = ...`), and mypy parses them against the target
135
+ # version, so pinning 3.11 while running on 3.12 fails inside numpy with a
136
+ # syntax error before it checks any of this project's code. The floor is
137
+ # enforced where it belongs anyway: requires-python and ruff's target-version.
138
+ #
139
+ # openvino_genai ships no stubs, and CI type-checks without it installed at all
140
+ # (see the dev group note above).
141
+ ignore_missing_imports = true
142
+ warn_unused_ignores = false
143
+
144
+ # ── Pytest ────────────────────────────────────────────────────────────────────
145
+
146
+ [tool.pytest.ini_options]
147
+ testpaths = ["tests"]
148
+ python_files = ["test_*.py"]
149
+ addopts = "-q"
150
+ # The package is imported from the source tree, not from an install: CI never
151
+ # installs the project itself, because that would drag in the OpenVINO nightly
152
+ # stack it deliberately does without.
153
+ pythonpath = ["."]
154
+
155
+ # ── Coverage ──────────────────────────────────────────────────────────────────
156
+
157
+ [tool.coverage.run]
158
+ source = ["vinowhisper"]
159
+ # Not covered by design: these need an NPU, an audio server, or a live socket.
160
+ omit = ["vinowhisper/transcriber.py", "vinowhisper/server.py", "vinowhisper/replay.py"]
161
+
162
+ [tool.coverage.report]
163
+ show_missing = true
164
+
165
+ # ── Bandit ────────────────────────────────────────────────────────────────────
166
+
167
+ # Read with `bandit -c pyproject.toml` (needs the `toml` extra). The Bandit
168
+ # workflow passes the same flag, so CI and `poe security` agree.
169
+ [tool.bandit]
170
+ exclude_dirs = ["tests", ".venv", ".git", "build", "dist"]
171
+ # Three blanket skips, each because the finding is this tool's entire job
172
+ # rather than a defect in it. B602 (shell=True) stays ON, and is the one that
173
+ # would actually matter: there is no shell anywhere in this codebase, and if
174
+ # one ever appears it should fail this scan.
175
+ skips = [
176
+ # blacklist — flags the `import subprocess` line itself, in a program whose
177
+ # purpose is to drive pw-record, parec, pactl, optimum-cli and systemctl.
178
+ "B404",
179
+ # subprocess_without_shell_equals_true. Every call site builds a literal
180
+ # argv list and passes it without a shell, so there is nothing to inject
181
+ # into: the variable parts are PipeWire node names read from pw-dump and a
182
+ # --target the user typed, both of which arrive as single argv elements.
183
+ "B603",
184
+ # start_process_with_partial_path. Deliberate: pw-record, parec and pactl
185
+ # live in different places on different distros, and hardcoding /usr/bin
186
+ # would break the fallbacks this release exists to add. PATH resolution is
187
+ # the portable behaviour here, and the tools are located with shutil.which
188
+ # before use.
189
+ "B607",
190
+ ]
191
+
192
+ # ── bump-my-version ───────────────────────────────────────────────────────────
193
+
194
+ [tool.bumpversion]
195
+ current_version = "0.3.0"
196
+ commit = true
197
+ tag = true
198
+ tag_name = "v{new_version}"
199
+ pre_commit_hooks = ["uv lock", "git add uv.lock"]
200
+
201
+ [[tool.bumpversion.files]]
202
+ filename = "pyproject.toml"
203
+ search = 'version = "{current_version}"'
204
+ replace = 'version = "{new_version}"'
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,101 @@
1
+ """RingBuffer and level helpers.
2
+
3
+ The ring buffer replaced a `np.concatenate([buf, chunk])[-cap:]` that copied
4
+ ~1.9MB twice per 100ms read. The interesting cases are all about the wrap:
5
+ writing across the end, reading across it, and a chunk larger than the whole
6
+ buffer, which must not corrupt the write index.
7
+ """
8
+
9
+ import numpy as np
10
+ import pytest
11
+
12
+ from vinowhisper.audio import RingBuffer, normalize, rms
13
+
14
+
15
+ def ramp(count: int, start: int = 0) -> np.ndarray:
16
+ return np.arange(start, start + count, dtype=np.float32)
17
+
18
+
19
+ def test_capacity_must_be_positive():
20
+ with pytest.raises(ValueError):
21
+ RingBuffer(0)
22
+
23
+
24
+ def test_reads_back_what_was_written():
25
+ buffer = RingBuffer(10)
26
+ buffer.write(ramp(4))
27
+ assert list(buffer.read_last(4)) == [0, 1, 2, 3]
28
+ assert buffer.total_written == 4
29
+
30
+
31
+ def test_read_more_than_written_returns_what_there_is():
32
+ buffer = RingBuffer(10)
33
+ buffer.write(ramp(3))
34
+ assert list(buffer.read_last(100)) == [0, 1, 2]
35
+ assert buffer.read_last(0).size == 0
36
+
37
+
38
+ def test_keeps_the_newest_samples_when_it_wraps():
39
+ buffer = RingBuffer(5)
40
+ buffer.write(ramp(8)) # 0..7, so 3..7 survive
41
+ assert list(buffer.read_last(5)) == [3, 4, 5, 6, 7]
42
+ # total_written is monotonic — the caption loop measures new audio with it.
43
+ assert buffer.total_written == 8
44
+
45
+
46
+ def test_read_spanning_the_wrap_point_stays_in_order():
47
+ buffer = RingBuffer(5)
48
+ buffer.write(ramp(3))
49
+ buffer.write(ramp(4, start=3)) # writes 3,4,5,6 across the boundary
50
+ assert list(buffer.read_last(5)) == [2, 3, 4, 5, 6]
51
+
52
+
53
+ def test_chunk_larger_than_capacity_keeps_the_tail_and_the_count():
54
+ buffer = RingBuffer(4)
55
+ buffer.write(ramp(10))
56
+ assert list(buffer.read_last(4)) == [6, 7, 8, 9]
57
+ assert buffer.total_written == 10
58
+ # The write index must still be consistent afterwards.
59
+ buffer.write(ramp(2, start=10))
60
+ assert list(buffer.read_last(4)) == [8, 9, 10, 11]
61
+
62
+
63
+ def test_empty_write_is_a_noop():
64
+ buffer = RingBuffer(4)
65
+ buffer.write(np.zeros(0, dtype=np.float32))
66
+ assert buffer.total_written == 0
67
+
68
+
69
+ def test_rms_of_silence_and_of_a_constant():
70
+ assert rms(np.zeros(0, dtype=np.float32)) == 0.0
71
+ assert rms(np.zeros(100, dtype=np.float32)) == 0.0
72
+ assert rms(np.full(100, 0.5, dtype=np.float32)) == pytest.approx(0.5)
73
+
74
+
75
+ def test_normalize_boosts_quiet_audio_towards_the_target():
76
+ quiet = np.full(100, 0.01, dtype=np.float32)
77
+ boosted, gain = normalize(quiet, target_rms=0.05, max_gain=20.0)
78
+ assert gain == pytest.approx(5.0)
79
+ assert rms(boosted) == pytest.approx(0.05, rel=1e-3)
80
+
81
+
82
+ def test_normalize_never_attenuates():
83
+ loud = np.full(100, 0.5, dtype=np.float32)
84
+ same, gain = normalize(loud, target_rms=0.05, max_gain=20.0)
85
+ assert gain == 1.0
86
+ assert np.array_equal(same, loud)
87
+
88
+
89
+ def test_normalize_respects_max_gain_and_clips():
90
+ very_quiet = np.full(100, 0.001, dtype=np.float32)
91
+ boosted, gain = normalize(very_quiet, target_rms=0.05, max_gain=20.0)
92
+ assert gain == 20.0
93
+ assert boosted.dtype == np.float32
94
+ assert boosted.max() <= 1.0
95
+
96
+
97
+ def test_normalize_leaves_digital_silence_alone():
98
+ silence = np.zeros(100, dtype=np.float32)
99
+ same, gain = normalize(silence, target_rms=0.05, max_gain=20.0)
100
+ assert gain == 1.0
101
+ assert np.array_equal(same, silence)