voxora 1.0.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.
voxora-1.0.0/LICENSE ADDED
@@ -0,0 +1,19 @@
1
+ Copyright (c) 2026 Voxora contributors
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in all
11
+ copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19
+ SOFTWARE.
voxora-1.0.0/PKG-INFO ADDED
@@ -0,0 +1,158 @@
1
+ Metadata-Version: 2.4
2
+ Name: voxora
3
+ Version: 1.0.0
4
+ Summary: A lightweight, microphone-based speech-to-text SDK for Python.
5
+ Author: Prakhar Parikh
6
+ License-Expression: MIT
7
+ Keywords: speech-to-text,speech-recognition,voice-activity-detection,audio,microphone,groq,whisper,python-sdk
8
+ Classifier: Development Status :: 4 - Beta
9
+ Classifier: Intended Audience :: Developers
10
+ Classifier: Topic :: Multimedia :: Sound/Audio :: Speech
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: Programming Language :: Python :: 3 :: Only
13
+ Classifier: Programming Language :: Python :: 3.10
14
+ Requires-Python: >=3.10
15
+ Description-Content-Type: text/markdown
16
+ License-File: LICENSE
17
+ Requires-Dist: PyAudio>=0.2.13
18
+ Requires-Dist: webrtcvad-wheels>=2.0.14.post1
19
+ Requires-Dist: groq>=0.31.0
20
+ Requires-Dist: openai>=2.31.0
21
+ Provides-Extra: dev
22
+ Requires-Dist: pytest>=7; extra == "dev"
23
+ Dynamic: license-file
24
+
25
+ # Voxora
26
+
27
+ **A lightweight, microphone-based speech-to-text SDK for Python.**
28
+
29
+ Voxora makes it easy to add speech recognition to Python applications with automatic voice detection, manual recording controls, and optional AI-powered transcript formatting.
30
+
31
+ ## Features
32
+
33
+ - **Automatic recording:** Detect speech and automatically stop after silence.
34
+ - **Manual recording:** Control recording with `start()` and `stop()`.
35
+ - **Speech-to-text:** Transcribe audio using Groq Whisper.
36
+ - **Transcript formatting:** Optionally improve punctuation, capitalization, and paragraph structure.
37
+ - **In-memory processing:** Audio is processed in memory without writing recordings to disk.
38
+ - **Simple API:** A single `STT` class for both recording modes.
39
+
40
+ ## Installation
41
+
42
+ ```bash
43
+ pip install voxora
44
+ ```
45
+
46
+ Set your Groq API key:
47
+
48
+ ```bash
49
+ export GROQ_API_KEY="your-groq-api-key"
50
+ ```
51
+
52
+ Voxora requires Python 3.10 or newer and microphone/audio dependencies. PyAudio may require platform-specific system libraries.
53
+
54
+ ## Quick Start
55
+
56
+ ### Automatic recording (VAD)
57
+
58
+ Voxora automatically detects speech and stops recording after the configured silence duration.
59
+
60
+ ```python
61
+ from voxora import STT
62
+
63
+ stt = STT(
64
+ api_key="YOUR_GROQ_API_KEY",
65
+ vad=True
66
+ )
67
+
68
+ text = stt.listen()
69
+
70
+ print(text)
71
+ ```
72
+
73
+ ### Manual recording
74
+
75
+ For applications that need explicit recording controls:
76
+
77
+ ```python
78
+ from voxora import STT
79
+
80
+ stt = STT(
81
+ api_key="YOUR_GROQ_API_KEY",
82
+ vad=False
83
+ )
84
+
85
+ stt.start()
86
+
87
+ input("Press Enter to stop recording...")
88
+
89
+ text = stt.stop()
90
+
91
+ print(text)
92
+ ```
93
+
94
+ ## Transcript Formatting
95
+
96
+ Voxora can optionally format transcribed text using a compatible chat-completions API.
97
+
98
+ Formatting improves punctuation, capitalization, and paragraph breaks while preserving the original spoken words and meaning.
99
+
100
+ ```python
101
+ from voxora import STT
102
+
103
+ stt = STT(
104
+ api_key="YOUR_GROQ_API_KEY",
105
+ vad=True,
106
+ format=True,
107
+ format_model="openai/gpt-oss-120b",
108
+ format_base_url="https://api.groq.com/openai/v1"
109
+ )
110
+
111
+ text = stt.listen()
112
+
113
+ print(text)
114
+ ```
115
+
116
+ Formatting is disabled by default. The formatter receives the transcript after speech recognition; it does not receive the audio.
117
+
118
+ ## Configuration
119
+
120
+ | Parameter | Description |
121
+ |---|---|
122
+ | `api_key` | Groq API key. Defaults to `GROQ_API_KEY`. |
123
+ | `stt_model` | Groq transcription model. |
124
+ | `language` | Optional ISO-639-1 language hint. |
125
+ | `vad` | Enables automatic voice activity detection. |
126
+ | `vad_mode` | WebRTC VAD aggressiveness (0–3). |
127
+ | `silence_duration` | Silence duration before automatic recording stops. |
128
+ | `pre_speech_duration` | Audio retained before speech detection. |
129
+ | `max_recording_duration` | Maximum recording duration. |
130
+ | `speech_start_timeout` | Maximum wait for speech to begin. |
131
+ | `format` | Enables optional transcript formatting. |
132
+ | `format_base_url` | Compatible formatting API endpoint. |
133
+ | `format_model` | Formatting model. |
134
+ | `format_api_key` | Formatting API key; defaults to `api_key`. |
135
+
136
+ ## Error Handling
137
+
138
+ Voxora exposes exceptions for configuration, recording, transcription, and formatting failures.
139
+
140
+ Provider authentication, network, rate-limit, and other API errors may propagate from the underlying SDK.
141
+
142
+ ## Requirements
143
+
144
+ - Python 3.10+
145
+ - PyAudio
146
+ - WebRTC VAD
147
+ - Groq API key
148
+ - Internet connection for transcription and optional formatting
149
+
150
+ Voxora uses Groq's cloud transcription API and is not an offline speech recognition engine.
151
+
152
+ ## License
153
+
154
+ See [LICENSE](LICENSE).
155
+
156
+ ---
157
+
158
+ **Voxora** — Speech recognition, made simple.
voxora-1.0.0/README.md ADDED
@@ -0,0 +1,134 @@
1
+ # Voxora
2
+
3
+ **A lightweight, microphone-based speech-to-text SDK for Python.**
4
+
5
+ Voxora makes it easy to add speech recognition to Python applications with automatic voice detection, manual recording controls, and optional AI-powered transcript formatting.
6
+
7
+ ## Features
8
+
9
+ - **Automatic recording:** Detect speech and automatically stop after silence.
10
+ - **Manual recording:** Control recording with `start()` and `stop()`.
11
+ - **Speech-to-text:** Transcribe audio using Groq Whisper.
12
+ - **Transcript formatting:** Optionally improve punctuation, capitalization, and paragraph structure.
13
+ - **In-memory processing:** Audio is processed in memory without writing recordings to disk.
14
+ - **Simple API:** A single `STT` class for both recording modes.
15
+
16
+ ## Installation
17
+
18
+ ```bash
19
+ pip install voxora
20
+ ```
21
+
22
+ Set your Groq API key:
23
+
24
+ ```bash
25
+ export GROQ_API_KEY="your-groq-api-key"
26
+ ```
27
+
28
+ Voxora requires Python 3.10 or newer and microphone/audio dependencies. PyAudio may require platform-specific system libraries.
29
+
30
+ ## Quick Start
31
+
32
+ ### Automatic recording (VAD)
33
+
34
+ Voxora automatically detects speech and stops recording after the configured silence duration.
35
+
36
+ ```python
37
+ from voxora import STT
38
+
39
+ stt = STT(
40
+ api_key="YOUR_GROQ_API_KEY",
41
+ vad=True
42
+ )
43
+
44
+ text = stt.listen()
45
+
46
+ print(text)
47
+ ```
48
+
49
+ ### Manual recording
50
+
51
+ For applications that need explicit recording controls:
52
+
53
+ ```python
54
+ from voxora import STT
55
+
56
+ stt = STT(
57
+ api_key="YOUR_GROQ_API_KEY",
58
+ vad=False
59
+ )
60
+
61
+ stt.start()
62
+
63
+ input("Press Enter to stop recording...")
64
+
65
+ text = stt.stop()
66
+
67
+ print(text)
68
+ ```
69
+
70
+ ## Transcript Formatting
71
+
72
+ Voxora can optionally format transcribed text using a compatible chat-completions API.
73
+
74
+ Formatting improves punctuation, capitalization, and paragraph breaks while preserving the original spoken words and meaning.
75
+
76
+ ```python
77
+ from voxora import STT
78
+
79
+ stt = STT(
80
+ api_key="YOUR_GROQ_API_KEY",
81
+ vad=True,
82
+ format=True,
83
+ format_model="openai/gpt-oss-120b",
84
+ format_base_url="https://api.groq.com/openai/v1"
85
+ )
86
+
87
+ text = stt.listen()
88
+
89
+ print(text)
90
+ ```
91
+
92
+ Formatting is disabled by default. The formatter receives the transcript after speech recognition; it does not receive the audio.
93
+
94
+ ## Configuration
95
+
96
+ | Parameter | Description |
97
+ |---|---|
98
+ | `api_key` | Groq API key. Defaults to `GROQ_API_KEY`. |
99
+ | `stt_model` | Groq transcription model. |
100
+ | `language` | Optional ISO-639-1 language hint. |
101
+ | `vad` | Enables automatic voice activity detection. |
102
+ | `vad_mode` | WebRTC VAD aggressiveness (0–3). |
103
+ | `silence_duration` | Silence duration before automatic recording stops. |
104
+ | `pre_speech_duration` | Audio retained before speech detection. |
105
+ | `max_recording_duration` | Maximum recording duration. |
106
+ | `speech_start_timeout` | Maximum wait for speech to begin. |
107
+ | `format` | Enables optional transcript formatting. |
108
+ | `format_base_url` | Compatible formatting API endpoint. |
109
+ | `format_model` | Formatting model. |
110
+ | `format_api_key` | Formatting API key; defaults to `api_key`. |
111
+
112
+ ## Error Handling
113
+
114
+ Voxora exposes exceptions for configuration, recording, transcription, and formatting failures.
115
+
116
+ Provider authentication, network, rate-limit, and other API errors may propagate from the underlying SDK.
117
+
118
+ ## Requirements
119
+
120
+ - Python 3.10+
121
+ - PyAudio
122
+ - WebRTC VAD
123
+ - Groq API key
124
+ - Internet connection for transcription and optional formatting
125
+
126
+ Voxora uses Groq's cloud transcription API and is not an offline speech recognition engine.
127
+
128
+ ## License
129
+
130
+ See [LICENSE](LICENSE).
131
+
132
+ ---
133
+
134
+ **Voxora** — Speech recognition, made simple.
@@ -0,0 +1,54 @@
1
+ [build-system]
2
+ requires = ["setuptools>=77"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "voxora"
7
+ version = "1.0.0"
8
+ description = "A lightweight, microphone-based speech-to-text SDK for Python."
9
+ readme = "README.md"
10
+ requires-python = ">=3.10"
11
+ license = "MIT"
12
+ license-files = ["LICENSE"]
13
+ authors = [
14
+ { name = "Prakhar Parikh" }
15
+ ]
16
+ keywords = [
17
+ "speech-to-text",
18
+ "speech-recognition",
19
+ "voice-activity-detection",
20
+ "audio",
21
+ "microphone",
22
+ "groq",
23
+ "whisper",
24
+ "python-sdk"
25
+ ]
26
+ classifiers = [
27
+ "Development Status :: 4 - Beta",
28
+ "Intended Audience :: Developers",
29
+ "Topic :: Multimedia :: Sound/Audio :: Speech",
30
+ "Programming Language :: Python :: 3",
31
+ "Programming Language :: Python :: 3 :: Only",
32
+ "Programming Language :: Python :: 3.10",
33
+ ]
34
+ dependencies = [
35
+ "PyAudio>=0.2.13",
36
+ "webrtcvad-wheels>=2.0.14.post1",
37
+ "groq>=0.31.0",
38
+ "openai>=2.31.0",
39
+ ]
40
+
41
+ [project.optional-dependencies]
42
+ dev = [
43
+ "pytest>=7",
44
+ ]
45
+
46
+ [tool.setuptools]
47
+ package-dir = { "" = "src" }
48
+
49
+ [tool.setuptools.packages.find]
50
+ where = ["src"]
51
+
52
+ [tool.pytest.ini_options]
53
+ testpaths = ["tests"]
54
+ addopts = "-q"
voxora-1.0.0/setup.cfg ADDED
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,25 @@
1
+ """Public package interface for Voxora."""
2
+
3
+ from .exceptions import (
4
+ AudioCaptureError,
5
+ ConfigurationError,
6
+ FormattingError,
7
+ NoSpeechError,
8
+ RecordingStateError,
9
+ RecordingTimeoutError,
10
+ TranscriptionError,
11
+ VoxoraError,
12
+ )
13
+ from .stt import STT
14
+
15
+ __all__ = [
16
+ "STT",
17
+ "VoxoraError",
18
+ "ConfigurationError",
19
+ "RecordingStateError",
20
+ "AudioCaptureError",
21
+ "NoSpeechError",
22
+ "RecordingTimeoutError",
23
+ "FormattingError",
24
+ "TranscriptionError",
25
+ ]
@@ -0,0 +1,198 @@
1
+ """Internal microphone capture, buffering, and WebRTC VAD."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import math
6
+ import threading
7
+ import time
8
+ from collections import deque
9
+ from dataclasses import dataclass
10
+ from typing import Callable, Deque, List
11
+
12
+ from .exceptions import AudioCaptureError, NoSpeechError
13
+
14
+
15
+ @dataclass(frozen=True)
16
+ class AudioResult:
17
+ """Internal PCM recording result for the future transcription layer."""
18
+
19
+ pcm_data: bytes
20
+ sample_rate: int
21
+ channels: int = 1
22
+ sample_width: int = 2
23
+
24
+
25
+ class AudioRecorder:
26
+ """Capture mono 16-bit PCM from the default microphone."""
27
+
28
+ def __init__(self, config, logger, *, pyaudio_module=None, vad_factory=None, clock: Callable[[], float] = time.monotonic):
29
+ self.config = config
30
+ self.logger = logger
31
+ self._pyaudio_module = pyaudio_module
32
+ self._vad_factory = vad_factory
33
+ self._clock = clock
34
+ self._audio = None
35
+ self._stream = None
36
+ self.samples_per_frame = config.sample_rate * config.frame_duration_ms // 1000
37
+ if self.samples_per_frame * 1000 != config.sample_rate * config.frame_duration_ms:
38
+ raise AudioCaptureError("frame duration does not produce a whole number of samples")
39
+ self.frame_bytes = self.samples_per_frame * 2
40
+
41
+ def _load_pyaudio(self):
42
+ if self._pyaudio_module is None:
43
+ try:
44
+ import pyaudio
45
+ except Exception as exc:
46
+ raise AudioCaptureError("PyAudio is required for microphone recording") from exc
47
+ self._pyaudio_module = pyaudio
48
+ return self._pyaudio_module
49
+
50
+ def _load_vad(self):
51
+ if self._vad_factory is None:
52
+ try:
53
+ import webrtcvad
54
+ except Exception as exc:
55
+ raise AudioCaptureError("WebRTC VAD is required for VAD recording") from exc
56
+ self._vad_factory = webrtcvad.Vad
57
+ return self._vad_factory
58
+
59
+ def open(self) -> None:
60
+ if self._stream is not None:
61
+ raise AudioCaptureError("microphone stream is already open")
62
+ pyaudio = self._load_pyaudio()
63
+ try:
64
+ self._audio = pyaudio.PyAudio()
65
+ self._stream = self._audio.open(
66
+ format=pyaudio.paInt16,
67
+ channels=1,
68
+ rate=self.config.sample_rate,
69
+ input=True,
70
+ frames_per_buffer=self.samples_per_frame,
71
+ )
72
+ self.logger.debug("microphone initialized")
73
+ except Exception as exc:
74
+ self.close()
75
+ raise AudioCaptureError("could not initialize microphone") from exc
76
+
77
+ def close(self) -> None:
78
+ stream, audio = self._stream, self._audio
79
+ self._stream = None
80
+ self._audio = None
81
+ cleanup_errors = []
82
+ try:
83
+ if stream is not None:
84
+ try:
85
+ stream.stop_stream()
86
+ except Exception as exc:
87
+ cleanup_errors.append(exc)
88
+ try:
89
+ stream.close()
90
+ except Exception as exc:
91
+ cleanup_errors.append(exc)
92
+ finally:
93
+ if audio is not None:
94
+ try:
95
+ audio.terminate()
96
+ except Exception as exc:
97
+ cleanup_errors.append(exc)
98
+ self.logger.debug("microphone resources cleaned up")
99
+ if cleanup_errors:
100
+ self.logger.error("microphone cleanup encountered an error")
101
+
102
+ def _read_frame(self) -> bytes:
103
+ if self._stream is None:
104
+ raise AudioCaptureError("microphone stream is not open")
105
+ try:
106
+ frame = self._stream.read(self.samples_per_frame, exception_on_overflow=False)
107
+ except Exception as exc:
108
+ raise AudioCaptureError("microphone read failed") from exc
109
+ if not isinstance(frame, bytes) or len(frame) != self.frame_bytes:
110
+ raise AudioCaptureError("microphone returned an invalid audio frame")
111
+ return frame
112
+
113
+ def _new_vad(self):
114
+ try:
115
+ return self._load_vad()(self.config.vad_mode)
116
+ except Exception as exc:
117
+ raise AudioCaptureError("invalid WebRTC VAD configuration") from exc
118
+
119
+ def record_vad(self) -> AudioResult:
120
+ """Record until speech followed by configured silence is detected."""
121
+ self.open()
122
+ frames: List[bytes] = []
123
+ pre_speech_frames: Deque[bytes] = deque(
124
+ maxlen=max(0, math.ceil(self.config.pre_speech_duration * 1000 / self.config.frame_duration_ms))
125
+ )
126
+ frame_seconds = self.config.frame_duration_ms / 1000.0
127
+ silence_frames_needed = max(1, math.ceil(self.config.silence_duration / frame_seconds))
128
+ speech_started = False
129
+ consecutive_silence = 0
130
+ started_at = self._clock()
131
+ vad = None
132
+ try:
133
+ vad = self._new_vad()
134
+ self.logger.debug("recording started with VAD")
135
+ while True:
136
+ frame = self._read_frame()
137
+ elapsed = self._clock() - started_at
138
+ if self.config.max_recording_duration is not None and elapsed >= self.config.max_recording_duration:
139
+ self.logger.debug("maximum recording duration reached")
140
+ if not speech_started:
141
+ raise NoSpeechError("no speech detected before maximum recording duration")
142
+ break
143
+
144
+ if not speech_started and (
145
+ self.config.speech_start_timeout is not None
146
+ and elapsed >= self.config.speech_start_timeout
147
+ ):
148
+ raise NoSpeechError("no speech detected before speech_start_timeout")
149
+
150
+ try:
151
+ speaking = vad.is_speech(frame, self.config.sample_rate)
152
+ except Exception as exc:
153
+ raise AudioCaptureError("WebRTC VAD processing failed") from exc
154
+ if not speech_started:
155
+ if speaking:
156
+ speech_started = True
157
+ frames.extend(pre_speech_frames)
158
+ frames.append(frame)
159
+ self.logger.debug("speech detected")
160
+ else:
161
+ pre_speech_frames.append(frame)
162
+ continue
163
+
164
+ frames.append(frame)
165
+ if speaking:
166
+ consecutive_silence = 0
167
+ else:
168
+ consecutive_silence += 1
169
+ if consecutive_silence >= silence_frames_needed:
170
+ self.logger.debug("silence detected; recording stopped")
171
+ break
172
+
173
+ if not frames:
174
+ raise NoSpeechError("no usable audio was captured")
175
+ return AudioResult(b"".join(frames), self.config.sample_rate)
176
+ finally:
177
+ self.close()
178
+ self.logger.debug("audio preparation completed")
179
+
180
+ def record_manual(self, stop_event: threading.Event) -> AudioResult:
181
+ """Record until stopped or the configured maximum duration is reached."""
182
+ if self._stream is None:
183
+ raise AudioCaptureError("microphone stream is not open")
184
+ frames: List[bytes] = []
185
+ started_at = self._clock()
186
+ self.logger.debug("manual recording started")
187
+ try:
188
+ while not stop_event.is_set():
189
+ if self.config.max_recording_duration is not None and self._clock() - started_at >= self.config.max_recording_duration:
190
+ self.logger.debug("maximum recording duration reached")
191
+ break
192
+ frames.append(self._read_frame())
193
+ if not frames:
194
+ raise NoSpeechError("no usable audio was captured")
195
+ return AudioResult(b"".join(frames), self.config.sample_rate)
196
+ finally:
197
+ self.close()
198
+ self.logger.debug("manual recording stopped")