RealTimeSTT 0.3.92__tar.gz → 0.3.94__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.
- {realtimestt-0.3.92 → realtimestt-0.3.94}/PKG-INFO +4 -4
- {realtimestt-0.3.92 → realtimestt-0.3.94}/RealtimeSTT/audio_input.py +81 -14
- {realtimestt-0.3.92 → realtimestt-0.3.94}/RealtimeSTT/audio_recorder.py +128 -33
- {realtimestt-0.3.92 → realtimestt-0.3.94}/RealtimeSTT/audio_recorder_client.py +48 -0
- {realtimestt-0.3.92 → realtimestt-0.3.94}/RealtimeSTT.egg-info/PKG-INFO +4 -4
- {realtimestt-0.3.92 → realtimestt-0.3.94}/RealtimeSTT.egg-info/requires.txt +3 -3
- {realtimestt-0.3.92 → realtimestt-0.3.94}/RealtimeSTT_server/stt_server.py +61 -6
- {realtimestt-0.3.92 → realtimestt-0.3.94}/setup.py +1 -1
- {realtimestt-0.3.92 → realtimestt-0.3.94}/LICENSE +0 -0
- {realtimestt-0.3.92 → realtimestt-0.3.94}/README.md +0 -0
- {realtimestt-0.3.92 → realtimestt-0.3.94}/RealtimeSTT/__init__.py +0 -0
- {realtimestt-0.3.92 → realtimestt-0.3.94}/RealtimeSTT.egg-info/SOURCES.txt +0 -0
- {realtimestt-0.3.92 → realtimestt-0.3.94}/RealtimeSTT.egg-info/dependency_links.txt +0 -0
- {realtimestt-0.3.92 → realtimestt-0.3.94}/RealtimeSTT.egg-info/entry_points.txt +0 -0
- {realtimestt-0.3.92 → realtimestt-0.3.94}/RealtimeSTT.egg-info/top_level.txt +0 -0
- {realtimestt-0.3.92 → realtimestt-0.3.94}/RealtimeSTT_server/__init__.py +0 -0
- {realtimestt-0.3.92 → realtimestt-0.3.94}/RealtimeSTT_server/install_packages.py +0 -0
- {realtimestt-0.3.92 → realtimestt-0.3.94}/RealtimeSTT_server/stt_cli_client.py +0 -0
- {realtimestt-0.3.92 → realtimestt-0.3.94}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: RealtimeSTT
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.94
|
|
4
4
|
Summary: A fast Voice Activity Detection and Transcription System
|
|
5
5
|
Home-page: https://github.com/KoljaB/RealTimeSTT
|
|
6
6
|
Author: Kolja Beigel
|
|
@@ -13,15 +13,15 @@ Requires-Python: >=3.6
|
|
|
13
13
|
Description-Content-Type: text/markdown
|
|
14
14
|
License-File: LICENSE
|
|
15
15
|
Requires-Dist: PyAudio==0.2.14
|
|
16
|
-
Requires-Dist: faster-whisper==1.1.
|
|
16
|
+
Requires-Dist: faster-whisper==1.1.1
|
|
17
17
|
Requires-Dist: pvporcupine==1.9.5
|
|
18
18
|
Requires-Dist: webrtcvad-wheels==2.0.14
|
|
19
19
|
Requires-Dist: halo==0.0.31
|
|
20
20
|
Requires-Dist: torch
|
|
21
21
|
Requires-Dist: torchaudio
|
|
22
|
-
Requires-Dist: scipy==1.
|
|
22
|
+
Requires-Dist: scipy==1.15.1
|
|
23
23
|
Requires-Dist: openwakeword>=0.4.0
|
|
24
|
-
Requires-Dist: websockets==14.
|
|
24
|
+
Requires-Dist: websockets==14.2
|
|
25
25
|
Requires-Dist: websocket-client==1.8.0
|
|
26
26
|
Requires-Dist: numpy<2.0.0
|
|
27
27
|
|
|
@@ -1,22 +1,35 @@
|
|
|
1
1
|
from colorama import init, Fore, Style
|
|
2
|
+
from scipy.signal import butter, filtfilt, resample_poly
|
|
2
3
|
import pyaudio
|
|
3
4
|
import logging
|
|
4
|
-
import time
|
|
5
5
|
|
|
6
|
+
DESIRED_RATE = 16000
|
|
7
|
+
CHUNK_SIZE = 1024
|
|
8
|
+
AUDIO_FORMAT = pyaudio.paInt16
|
|
9
|
+
CHANNELS = 1
|
|
6
10
|
|
|
7
11
|
class AudioInput:
|
|
8
|
-
def __init__(
|
|
12
|
+
def __init__(
|
|
13
|
+
self,
|
|
14
|
+
input_device_index: int = None,
|
|
15
|
+
debug_mode: bool = False,
|
|
16
|
+
target_samplerate: int = DESIRED_RATE,
|
|
17
|
+
chunk_size: int = CHUNK_SIZE,
|
|
18
|
+
audio_format: int = AUDIO_FORMAT,
|
|
19
|
+
channels: int = CHANNELS,
|
|
20
|
+
resample_to_target: bool = True,
|
|
21
|
+
):
|
|
22
|
+
|
|
9
23
|
self.input_device_index = input_device_index
|
|
10
24
|
self.debug_mode = debug_mode
|
|
11
25
|
self.audio_interface = None
|
|
12
26
|
self.stream = None
|
|
13
27
|
self.device_sample_rate = None
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
self.
|
|
17
|
-
self.
|
|
18
|
-
self.
|
|
19
|
-
self.DESIRED_RATE = 16000
|
|
28
|
+
self.target_samplerate = target_samplerate
|
|
29
|
+
self.chunk_size = chunk_size
|
|
30
|
+
self.audio_format = audio_format
|
|
31
|
+
self.channels = channels
|
|
32
|
+
self.resample_to_target = resample_to_target
|
|
20
33
|
|
|
21
34
|
def get_supported_sample_rates(self, device_index):
|
|
22
35
|
"""Test which standard sample rates are supported by the specified device."""
|
|
@@ -31,8 +44,8 @@ class AudioInput:
|
|
|
31
44
|
if self.audio_interface.is_format_supported(
|
|
32
45
|
rate,
|
|
33
46
|
input_device=device_index, # Changed to input_device
|
|
34
|
-
input_channels=max_channels, # Changed to input_channels
|
|
35
|
-
input_format=self.
|
|
47
|
+
input_channels=max_channels, # Changed to input_channels
|
|
48
|
+
input_format=self.audio_format, # Changed to input_format
|
|
36
49
|
):
|
|
37
50
|
supported_rates.append(rate)
|
|
38
51
|
except:
|
|
@@ -108,18 +121,18 @@ class AudioInput:
|
|
|
108
121
|
if self.debug_mode:
|
|
109
122
|
print(f"Actual selected device index: {actual_device_index}")
|
|
110
123
|
self.input_device_index = actual_device_index
|
|
111
|
-
self.device_sample_rate = self._get_best_sample_rate(actual_device_index, self.
|
|
124
|
+
self.device_sample_rate = self._get_best_sample_rate(actual_device_index, self.target_samplerate)
|
|
112
125
|
|
|
113
126
|
if self.debug_mode:
|
|
114
127
|
print(f"Setting up audio on device {self.input_device_index} with sample rate {self.device_sample_rate}")
|
|
115
128
|
|
|
116
129
|
try:
|
|
117
130
|
self.stream = self.audio_interface.open(
|
|
118
|
-
format=self.
|
|
119
|
-
channels=self.
|
|
131
|
+
format=self.audio_format,
|
|
132
|
+
channels=self.channels,
|
|
120
133
|
rate=self.device_sample_rate,
|
|
121
134
|
input=True,
|
|
122
|
-
frames_per_buffer=self.
|
|
135
|
+
frames_per_buffer=self.chunk_size,
|
|
123
136
|
input_device_index=self.input_device_index,
|
|
124
137
|
)
|
|
125
138
|
if self.debug_mode:
|
|
@@ -135,6 +148,60 @@ class AudioInput:
|
|
|
135
148
|
self.audio_interface.terminate()
|
|
136
149
|
return False
|
|
137
150
|
|
|
151
|
+
def lowpass_filter(self, signal, cutoff_freq, sample_rate):
|
|
152
|
+
"""
|
|
153
|
+
Apply a low-pass Butterworth filter to prevent aliasing in the signal.
|
|
154
|
+
|
|
155
|
+
Args:
|
|
156
|
+
signal (np.ndarray): Input audio signal to filter
|
|
157
|
+
cutoff_freq (float): Cutoff frequency in Hz
|
|
158
|
+
sample_rate (float): Sampling rate of the input signal in Hz
|
|
159
|
+
|
|
160
|
+
Returns:
|
|
161
|
+
np.ndarray: Filtered audio signal
|
|
162
|
+
|
|
163
|
+
Notes:
|
|
164
|
+
- Uses a 5th order Butterworth filter
|
|
165
|
+
- Applies zero-phase filtering using filtfilt
|
|
166
|
+
"""
|
|
167
|
+
# Calculate the Nyquist frequency (half the sample rate)
|
|
168
|
+
nyquist_rate = sample_rate / 2.0
|
|
169
|
+
|
|
170
|
+
# Normalize cutoff frequency to Nyquist rate (required by butter())
|
|
171
|
+
normal_cutoff = cutoff_freq / nyquist_rate
|
|
172
|
+
|
|
173
|
+
# Design the Butterworth filter
|
|
174
|
+
b, a = butter(5, normal_cutoff, btype='low', analog=False)
|
|
175
|
+
|
|
176
|
+
# Apply zero-phase filtering (forward and backward)
|
|
177
|
+
filtered_signal = filtfilt(b, a, signal)
|
|
178
|
+
return filtered_signal
|
|
179
|
+
|
|
180
|
+
def resample_audio(self, pcm_data, target_sample_rate, original_sample_rate):
|
|
181
|
+
"""
|
|
182
|
+
Filter and resample audio data to a target sample rate.
|
|
183
|
+
|
|
184
|
+
Args:
|
|
185
|
+
pcm_data (np.ndarray): Input audio data
|
|
186
|
+
target_sample_rate (int): Desired output sample rate in Hz
|
|
187
|
+
original_sample_rate (int): Original sample rate of input in Hz
|
|
188
|
+
|
|
189
|
+
Returns:
|
|
190
|
+
np.ndarray: Resampled audio data
|
|
191
|
+
|
|
192
|
+
Notes:
|
|
193
|
+
- Applies anti-aliasing filter before resampling
|
|
194
|
+
- Uses polyphase filtering for high-quality resampling
|
|
195
|
+
"""
|
|
196
|
+
if target_sample_rate < original_sample_rate:
|
|
197
|
+
# Downsampling with low-pass filter
|
|
198
|
+
pcm_filtered = self.lowpass_filter(pcm_data, target_sample_rate / 2, original_sample_rate)
|
|
199
|
+
resampled = resample_poly(pcm_filtered, target_sample_rate, original_sample_rate)
|
|
200
|
+
else:
|
|
201
|
+
# Upsampling without low-pass filter
|
|
202
|
+
resampled = resample_poly(pcm_data, target_sample_rate, original_sample_rate)
|
|
203
|
+
return resampled
|
|
204
|
+
|
|
138
205
|
def read_chunk(self):
|
|
139
206
|
"""Read a chunk of audio data"""
|
|
140
207
|
return self.stream.read(self.CHUNK, exception_on_overflow=False)
|
|
@@ -120,7 +120,7 @@ class TranscriptionWorker:
|
|
|
120
120
|
data = self.conn.recv()
|
|
121
121
|
self.queue.put(data)
|
|
122
122
|
except Exception as e:
|
|
123
|
-
logging.error(f"Error receiving data from connection: {e}")
|
|
123
|
+
logging.error(f"Error receiving data from connection: {e}", exc_info=True)
|
|
124
124
|
else:
|
|
125
125
|
time.sleep(TIME_SLEEP)
|
|
126
126
|
|
|
@@ -139,8 +139,13 @@ class TranscriptionWorker:
|
|
|
139
139
|
device_index=self.gpu_device_index,
|
|
140
140
|
download_root=self.download_root,
|
|
141
141
|
)
|
|
142
|
+
# Create a short dummy audio array, for example 1 second of silence at 16 kHz
|
|
142
143
|
if self.batch_size > 0:
|
|
143
144
|
model = BatchedInferencePipeline(model=model)
|
|
145
|
+
|
|
146
|
+
# Run a warm-up transcription
|
|
147
|
+
dummy_audio = np.zeros(16000, dtype=np.float32)
|
|
148
|
+
model.transcribe(dummy_audio, language="en", beam_size=1)
|
|
144
149
|
except Exception as e:
|
|
145
150
|
logging.exception(f"Error initializing main faster_whisper transcription model: {e}")
|
|
146
151
|
raise
|
|
@@ -157,6 +162,7 @@ class TranscriptionWorker:
|
|
|
157
162
|
try:
|
|
158
163
|
audio, language = self.queue.get(timeout=0.1)
|
|
159
164
|
try:
|
|
165
|
+
logging.debug(f"Transcribing audio with language {language}")
|
|
160
166
|
if self.batch_size > 0:
|
|
161
167
|
segments, info = model.transcribe(
|
|
162
168
|
audio,
|
|
@@ -179,7 +185,7 @@ class TranscriptionWorker:
|
|
|
179
185
|
logging.debug(f"Final text detected with main model: {transcription}")
|
|
180
186
|
self.conn.send(('success', (transcription, info)))
|
|
181
187
|
except Exception as e:
|
|
182
|
-
logging.error(f"General error in transcription: {e}")
|
|
188
|
+
logging.error(f"General error in transcription: {e}", exc_info=True)
|
|
183
189
|
self.conn.send(('error', str(e)))
|
|
184
190
|
except queue.Empty:
|
|
185
191
|
continue
|
|
@@ -188,7 +194,7 @@ class TranscriptionWorker:
|
|
|
188
194
|
logging.debug("Transcription worker process finished due to KeyboardInterrupt")
|
|
189
195
|
break
|
|
190
196
|
except Exception as e:
|
|
191
|
-
logging.error(f"General error in processing queue item: {e}")
|
|
197
|
+
logging.error(f"General error in processing queue item: {e}", exc_info=True)
|
|
192
198
|
finally:
|
|
193
199
|
__builtins__['print'] = print # Restore the original print function
|
|
194
200
|
self.conn.close()
|
|
@@ -281,6 +287,7 @@ class AudioToTextRecorder:
|
|
|
281
287
|
buffer_size: int = BUFFER_SIZE,
|
|
282
288
|
sample_rate: int = SAMPLE_RATE,
|
|
283
289
|
initial_prompt: Optional[Union[str, Iterable[int]]] = None,
|
|
290
|
+
initial_prompt_realtime: Optional[Union[str, Iterable[int]]] = None,
|
|
284
291
|
suppress_tokens: Optional[List[int]] = [-1],
|
|
285
292
|
print_transcription_time: bool = False,
|
|
286
293
|
early_transcription_on_silence: int = 0,
|
|
@@ -294,12 +301,12 @@ class AudioToTextRecorder:
|
|
|
294
301
|
|
|
295
302
|
Args:
|
|
296
303
|
- model (str, default="tiny"): Specifies the size of the transcription
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
304
|
+
model to use or the path to a converted model directory.
|
|
305
|
+
Valid options are 'tiny', 'tiny.en', 'base', 'base.en',
|
|
306
|
+
'small', 'small.en', 'medium', 'medium.en', 'large-v1',
|
|
307
|
+
'large-v2'.
|
|
308
|
+
If a specific size is provided, the model is downloaded
|
|
309
|
+
from the Hugging Face Hub.
|
|
303
310
|
- download_root (str, default=None): Specifies the root path were the Whisper models
|
|
304
311
|
are downloaded to. When empty, the default is used.
|
|
305
312
|
- language (str, default=""): Language code for speech-to-text engine.
|
|
@@ -472,7 +479,9 @@ class AudioToTextRecorder:
|
|
|
472
479
|
recording. Changing this will very probably functionality (as the
|
|
473
480
|
WebRTC VAD model is very sensitive towards the sample rate).
|
|
474
481
|
- initial_prompt (str or iterable of int, default=None): Initial
|
|
475
|
-
prompt to be fed to the transcription
|
|
482
|
+
prompt to be fed to the main transcription model.
|
|
483
|
+
- initial_prompt_realtime (str or iterable of int, default=None):
|
|
484
|
+
Initial prompt to be fed to the real-time transcription model.
|
|
476
485
|
- suppress_tokens (list of int, default=[-1]): Tokens to be suppressed
|
|
477
486
|
from the transcription output.
|
|
478
487
|
- print_transcription_time (bool, default=False): Logs processing time
|
|
@@ -533,6 +542,8 @@ class AudioToTextRecorder:
|
|
|
533
542
|
self.enable_realtime_transcription = enable_realtime_transcription
|
|
534
543
|
self.use_main_model_for_realtime = use_main_model_for_realtime
|
|
535
544
|
self.main_model_type = model
|
|
545
|
+
if not download_root:
|
|
546
|
+
download_root = None
|
|
536
547
|
self.download_root = download_root
|
|
537
548
|
self.realtime_model_type = realtime_model_type
|
|
538
549
|
self.realtime_processing_pause = realtime_processing_pause
|
|
@@ -557,6 +568,8 @@ class AudioToTextRecorder:
|
|
|
557
568
|
self.sample_rate = sample_rate
|
|
558
569
|
self.recording_start_time = 0
|
|
559
570
|
self.recording_stop_time = 0
|
|
571
|
+
self.last_recording_start_time = 0
|
|
572
|
+
self.last_recording_stop_time = 0
|
|
560
573
|
self.wake_word_detect_time = 0
|
|
561
574
|
self.silero_check_time = 0
|
|
562
575
|
self.silero_working = False
|
|
@@ -580,9 +593,12 @@ class AudioToTextRecorder:
|
|
|
580
593
|
self.stream = None
|
|
581
594
|
self.start_recording_event = threading.Event()
|
|
582
595
|
self.stop_recording_event = threading.Event()
|
|
596
|
+
self.backdate_stop_seconds = 0.0
|
|
597
|
+
self.backdate_resume_seconds = 0.0
|
|
583
598
|
self.last_transcription_bytes = None
|
|
584
599
|
self.last_transcription_bytes_b64 = None
|
|
585
600
|
self.initial_prompt = initial_prompt
|
|
601
|
+
self.initial_prompt_realtime = initial_prompt_realtime
|
|
586
602
|
self.suppress_tokens = suppress_tokens
|
|
587
603
|
self.use_wake_words = wake_words or wakeword_backend in {'oww', 'openwakeword', 'openwakewords'}
|
|
588
604
|
self.detected_language = None
|
|
@@ -697,7 +713,11 @@ class AudioToTextRecorder:
|
|
|
697
713
|
if self.enable_realtime_transcription and not self.use_main_model_for_realtime:
|
|
698
714
|
try:
|
|
699
715
|
logging.info("Initializing faster_whisper realtime "
|
|
700
|
-
f"transcription model {self.realtime_model_type}"
|
|
716
|
+
f"transcription model {self.realtime_model_type}, "
|
|
717
|
+
f"default device: {self.device}, "
|
|
718
|
+
f"compute type: {self.compute_type}, "
|
|
719
|
+
f"device index: {self.gpu_device_index}, "
|
|
720
|
+
f"download root: {self.download_root}"
|
|
701
721
|
)
|
|
702
722
|
self.realtime_model_type = faster_whisper.WhisperModel(
|
|
703
723
|
model_size_or_path=self.realtime_model_type,
|
|
@@ -708,7 +728,8 @@ class AudioToTextRecorder:
|
|
|
708
728
|
)
|
|
709
729
|
if self.realtime_batch_size > 0:
|
|
710
730
|
self.realtime_model_type = BatchedInferencePipeline(model=self.realtime_model_type)
|
|
711
|
-
|
|
731
|
+
dummy_audio = np.zeros(16000, dtype=np.float32)
|
|
732
|
+
self.realtime_model_type.transcribe(dummy_audio, language="en", beam_size=1)
|
|
712
733
|
except Exception as e:
|
|
713
734
|
logging.exception("Error initializing faster_whisper "
|
|
714
735
|
f"realtime transcription model: {e}"
|
|
@@ -844,6 +865,7 @@ class AudioToTextRecorder:
|
|
|
844
865
|
0.3)
|
|
845
866
|
)
|
|
846
867
|
self.frames = []
|
|
868
|
+
self.last_frames = []
|
|
847
869
|
|
|
848
870
|
# Recording control flags
|
|
849
871
|
self.is_recording = False
|
|
@@ -909,7 +931,7 @@ class AudioToTextRecorder:
|
|
|
909
931
|
logging.info("KeyboardInterrupt in read from stdout detected, exiting...")
|
|
910
932
|
break
|
|
911
933
|
except Exception as e:
|
|
912
|
-
logging.error(f"Unexpected error in read from stdout: {e}")
|
|
934
|
+
logging.error(f"Unexpected error in read from stdout: {e}", exc_info=True)
|
|
913
935
|
logging.error(traceback.format_exc()) # Log the full traceback here
|
|
914
936
|
break
|
|
915
937
|
time.sleep(0.1)
|
|
@@ -1058,7 +1080,7 @@ class AudioToTextRecorder:
|
|
|
1058
1080
|
return stream
|
|
1059
1081
|
|
|
1060
1082
|
except Exception as e:
|
|
1061
|
-
logging.error(f"Microphone connection failed: {e}. Retrying...")
|
|
1083
|
+
logging.error(f"Microphone connection failed: {e}. Retrying...", exc_info=True)
|
|
1062
1084
|
input_device_index = None
|
|
1063
1085
|
time.sleep(3) # Wait before retrying
|
|
1064
1086
|
continue
|
|
@@ -1174,7 +1196,7 @@ class AudioToTextRecorder:
|
|
|
1174
1196
|
if e.errno == pyaudio.paInputOverflowed:
|
|
1175
1197
|
logging.warning("Input overflowed. Frame dropped.")
|
|
1176
1198
|
else:
|
|
1177
|
-
logging.error(f"OSError during recording: {e}")
|
|
1199
|
+
logging.error(f"OSError during recording: {e}", exc_info=True)
|
|
1178
1200
|
# Attempt to reinitialize the stream
|
|
1179
1201
|
logging.error("Attempting to reinitialize the audio stream...")
|
|
1180
1202
|
|
|
@@ -1292,13 +1314,60 @@ class AudioToTextRecorder:
|
|
|
1292
1314
|
if (self.stop_recording_event.wait(timeout=0.02)):
|
|
1293
1315
|
break
|
|
1294
1316
|
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1317
|
+
frames = self.frames
|
|
1318
|
+
if len(frames) == 0:
|
|
1319
|
+
frames = self.last_frames
|
|
1320
|
+
|
|
1321
|
+
# Calculate samples needed for backdating resume
|
|
1322
|
+
samples_to_keep = int(self.sample_rate * self.backdate_resume_seconds)
|
|
1323
|
+
|
|
1324
|
+
# First convert all current frames to audio array
|
|
1325
|
+
full_audio_array = np.frombuffer(b''.join(frames), dtype=np.int16)
|
|
1326
|
+
full_audio = full_audio_array.astype(np.float32) / INT16_MAX_ABS_VALUE
|
|
1327
|
+
|
|
1328
|
+
# Calculate how many samples we need to keep for backdating resume
|
|
1329
|
+
if samples_to_keep > 0:
|
|
1330
|
+
samples_to_keep = min(samples_to_keep, len(full_audio))
|
|
1331
|
+
# Keep the last N samples for backdating resume
|
|
1332
|
+
frames_to_read_audio = full_audio[-samples_to_keep:]
|
|
1333
|
+
|
|
1334
|
+
# Convert the audio back to int16 bytes for frames
|
|
1335
|
+
frames_to_read_int16 = (frames_to_read_audio * INT16_MAX_ABS_VALUE).astype(np.int16)
|
|
1336
|
+
frame_bytes = frames_to_read_int16.tobytes()
|
|
1337
|
+
|
|
1338
|
+
# Split into appropriate frame sizes (assuming standard frame size)
|
|
1339
|
+
FRAME_SIZE = 2048 # Typical frame size
|
|
1340
|
+
frames_to_read = []
|
|
1341
|
+
for i in range(0, len(frame_bytes), FRAME_SIZE):
|
|
1342
|
+
frame = frame_bytes[i:i + FRAME_SIZE]
|
|
1343
|
+
if frame: # Only add non-empty frames
|
|
1344
|
+
frames_to_read.append(frame)
|
|
1345
|
+
else:
|
|
1346
|
+
frames_to_read = []
|
|
1347
|
+
|
|
1348
|
+
# Process backdate stop seconds
|
|
1349
|
+
samples_to_remove = int(self.sample_rate * self.backdate_stop_seconds)
|
|
1350
|
+
|
|
1351
|
+
if samples_to_remove > 0:
|
|
1352
|
+
if samples_to_remove < len(full_audio):
|
|
1353
|
+
self.audio = full_audio[:-samples_to_remove]
|
|
1354
|
+
logging.debug(f"Removed {samples_to_remove} samples "
|
|
1355
|
+
f"({samples_to_remove/self.sample_rate:.3f}s) from end of audio")
|
|
1356
|
+
else:
|
|
1357
|
+
self.audio = np.array([], dtype=np.float32)
|
|
1358
|
+
logging.debug("Cleared audio (samples_to_remove >= audio length)")
|
|
1359
|
+
else:
|
|
1360
|
+
self.audio = full_audio
|
|
1361
|
+
logging.debug(f"No samples removed, final audio length: {len(self.audio)}")
|
|
1362
|
+
|
|
1298
1363
|
self.frames.clear()
|
|
1364
|
+
self.last_frames.clear()
|
|
1365
|
+
self.frames.extend(frames_to_read)
|
|
1366
|
+
|
|
1367
|
+
# Reset backdating parameters
|
|
1368
|
+
self.backdate_stop_seconds = 0.0
|
|
1369
|
+
self.backdate_resume_seconds = 0.0
|
|
1299
1370
|
|
|
1300
|
-
# Reset recording-related timestamps
|
|
1301
|
-
self.recording_stop_time = 0
|
|
1302
1371
|
self.listen_start = 0
|
|
1303
1372
|
|
|
1304
1373
|
self._set_state("inactive")
|
|
@@ -1372,7 +1441,7 @@ class AudioToTextRecorder:
|
|
|
1372
1441
|
logging.error(f"Transcription error: {result}")
|
|
1373
1442
|
raise Exception(result)
|
|
1374
1443
|
except Exception as e:
|
|
1375
|
-
logging.error(f"Error during transcription: {str(e)}")
|
|
1444
|
+
logging.error(f"Error during transcription: {str(e)}", exc_info=True)
|
|
1376
1445
|
raise e
|
|
1377
1446
|
|
|
1378
1447
|
def _process_wakeword(self, data):
|
|
@@ -1460,7 +1529,16 @@ class AudioToTextRecorder:
|
|
|
1460
1529
|
return self.transcribe()
|
|
1461
1530
|
|
|
1462
1531
|
|
|
1463
|
-
def
|
|
1532
|
+
def format_number(self, num):
|
|
1533
|
+
# Convert the number to a string
|
|
1534
|
+
num_str = f"{num:.10f}" # Ensure precision is sufficient
|
|
1535
|
+
# Split the number into integer and decimal parts
|
|
1536
|
+
integer_part, decimal_part = num_str.split('.')
|
|
1537
|
+
# Take the last two digits of the integer part and the first two digits of the decimal part
|
|
1538
|
+
result = f"{integer_part[-2:]}.{decimal_part[:2]}"
|
|
1539
|
+
return result
|
|
1540
|
+
|
|
1541
|
+
def start(self, frames = None):
|
|
1464
1542
|
"""
|
|
1465
1543
|
Starts recording audio directly without waiting for voice activity.
|
|
1466
1544
|
"""
|
|
@@ -1482,7 +1560,10 @@ class AudioToTextRecorder:
|
|
|
1482
1560
|
self.wakeword_detected = False
|
|
1483
1561
|
self.wake_word_detect_time = 0
|
|
1484
1562
|
self.frames = []
|
|
1563
|
+
if frames:
|
|
1564
|
+
self.frames = frames
|
|
1485
1565
|
self.is_recording = True
|
|
1566
|
+
|
|
1486
1567
|
self.recording_start_time = time.time()
|
|
1487
1568
|
self.is_silero_speech_active = False
|
|
1488
1569
|
self.is_webrtc_speech_active = False
|
|
@@ -1494,9 +1575,19 @@ class AudioToTextRecorder:
|
|
|
1494
1575
|
|
|
1495
1576
|
return self
|
|
1496
1577
|
|
|
1497
|
-
def stop(self
|
|
1578
|
+
def stop(self,
|
|
1579
|
+
backdate_stop_seconds: float = 0.0,
|
|
1580
|
+
backdate_resume_seconds: float = 0.0,
|
|
1581
|
+
):
|
|
1498
1582
|
"""
|
|
1499
1583
|
Stops recording audio.
|
|
1584
|
+
|
|
1585
|
+
Args:
|
|
1586
|
+
- backdate_stop_seconds (float, default="0.0"): Specifies the number of
|
|
1587
|
+
seconds to backdate the stop time. This is useful when the stop
|
|
1588
|
+
command is issued after the actual stop time.
|
|
1589
|
+
- backdate_resume_seconds (float, default="0.0"): Specifies the number
|
|
1590
|
+
of seconds to backdate the time relistening is initiated.
|
|
1500
1591
|
"""
|
|
1501
1592
|
|
|
1502
1593
|
# Ensure there's a minimum interval
|
|
@@ -1509,6 +1600,9 @@ class AudioToTextRecorder:
|
|
|
1509
1600
|
return self
|
|
1510
1601
|
|
|
1511
1602
|
logging.info("recording stopped")
|
|
1603
|
+
self.last_frames = copy.deepcopy(self.frames)
|
|
1604
|
+
self.backdate_stop_seconds = backdate_stop_seconds
|
|
1605
|
+
self.backdate_resume_seconds = backdate_resume_seconds
|
|
1512
1606
|
self.is_recording = False
|
|
1513
1607
|
self.recording_stop_time = time.time()
|
|
1514
1608
|
self.is_silero_speech_active = False
|
|
@@ -1517,6 +1611,9 @@ class AudioToTextRecorder:
|
|
|
1517
1611
|
self.start_recording_event.clear()
|
|
1518
1612
|
self.stop_recording_event.set()
|
|
1519
1613
|
|
|
1614
|
+
self.last_recording_start_time = self.recording_start_time
|
|
1615
|
+
self.last_recording_stop_time = self.recording_stop_time
|
|
1616
|
+
|
|
1520
1617
|
if self.on_recording_stop:
|
|
1521
1618
|
self.on_recording_stop()
|
|
1522
1619
|
|
|
@@ -1591,7 +1688,6 @@ class AudioToTextRecorder:
|
|
|
1591
1688
|
return
|
|
1592
1689
|
|
|
1593
1690
|
print("\033[91mRealtimeSTT shutting down\033[0m")
|
|
1594
|
-
# logging.debug("RealtimeSTT shutting down")
|
|
1595
1691
|
|
|
1596
1692
|
# Force wait_audio() and text() to exit
|
|
1597
1693
|
self.is_shut_down = True
|
|
@@ -1720,7 +1816,7 @@ class AudioToTextRecorder:
|
|
|
1720
1816
|
data = self.audio_queue.get()
|
|
1721
1817
|
|
|
1722
1818
|
except BrokenPipeError:
|
|
1723
|
-
logging.error("BrokenPipeError _recording_worker")
|
|
1819
|
+
logging.error("BrokenPipeError _recording_worker", exc_info=True)
|
|
1724
1820
|
self.is_running = False
|
|
1725
1821
|
break
|
|
1726
1822
|
|
|
@@ -1797,11 +1893,11 @@ class AudioToTextRecorder:
|
|
|
1797
1893
|
|
|
1798
1894
|
except struct.error:
|
|
1799
1895
|
logging.error("Error unpacking audio data "
|
|
1800
|
-
"for wake word processing.")
|
|
1896
|
+
"for wake word processing.", exc_info=True)
|
|
1801
1897
|
continue
|
|
1802
1898
|
|
|
1803
1899
|
except Exception as e:
|
|
1804
|
-
logging.error(f"Wake word processing error: {e}")
|
|
1900
|
+
logging.error(f"Wake word processing error: {e}", exc_info=True)
|
|
1805
1901
|
continue
|
|
1806
1902
|
|
|
1807
1903
|
if self.use_extended_logging:
|
|
@@ -2028,7 +2124,7 @@ class AudioToTextRecorder:
|
|
|
2028
2124
|
except Exception as e:
|
|
2029
2125
|
logging.debug('Debug: Caught exception in main try block')
|
|
2030
2126
|
if not self.interrupt_stop_event.is_set():
|
|
2031
|
-
logging.error(f"Unhandled exeption in _recording_worker: {e}")
|
|
2127
|
+
logging.error(f"Unhandled exeption in _recording_worker: {e}", exc_info=True)
|
|
2032
2128
|
raise
|
|
2033
2129
|
|
|
2034
2130
|
if self.use_extended_logging:
|
|
@@ -2095,7 +2191,7 @@ class AudioToTextRecorder:
|
|
|
2095
2191
|
logging.warning("Realtime transcription timed out")
|
|
2096
2192
|
continue
|
|
2097
2193
|
except Exception as e:
|
|
2098
|
-
logging.error(f"Error in realtime transcription: {str(e)}")
|
|
2194
|
+
logging.error(f"Error in realtime transcription: {str(e)}", exc_info=True)
|
|
2099
2195
|
continue
|
|
2100
2196
|
else:
|
|
2101
2197
|
# Perform transcription and assemble the text
|
|
@@ -2104,7 +2200,7 @@ class AudioToTextRecorder:
|
|
|
2104
2200
|
audio_array,
|
|
2105
2201
|
language=self.language if self.language else None,
|
|
2106
2202
|
beam_size=self.beam_size_realtime,
|
|
2107
|
-
initial_prompt=self.
|
|
2203
|
+
initial_prompt=self.initial_prompt_realtime,
|
|
2108
2204
|
suppress_tokens=self.suppress_tokens,
|
|
2109
2205
|
batch_size=self.realtime_batch_size
|
|
2110
2206
|
)
|
|
@@ -2113,7 +2209,7 @@ class AudioToTextRecorder:
|
|
|
2113
2209
|
audio_array,
|
|
2114
2210
|
language=self.language if self.language else None,
|
|
2115
2211
|
beam_size=self.beam_size_realtime,
|
|
2116
|
-
initial_prompt=self.
|
|
2212
|
+
initial_prompt=self.initial_prompt_realtime,
|
|
2117
2213
|
suppress_tokens=self.suppress_tokens
|
|
2118
2214
|
)
|
|
2119
2215
|
|
|
@@ -2129,7 +2225,6 @@ class AudioToTextRecorder:
|
|
|
2129
2225
|
if self.is_recording and time.time() - \
|
|
2130
2226
|
self.recording_start_time > self.init_realtime_after_seconds:
|
|
2131
2227
|
|
|
2132
|
-
# logging.debug('Starting realtime transcription')
|
|
2133
2228
|
self.realtime_transcription_text = realtime_text
|
|
2134
2229
|
self.realtime_transcription_text = \
|
|
2135
2230
|
self.realtime_transcription_text.strip()
|
|
@@ -2209,7 +2304,7 @@ class AudioToTextRecorder:
|
|
|
2209
2304
|
time.sleep(TIME_SLEEP)
|
|
2210
2305
|
|
|
2211
2306
|
except Exception as e:
|
|
2212
|
-
logging.error(f"Unhandled exeption in _realtime_worker: {e}")
|
|
2307
|
+
logging.error(f"Unhandled exeption in _realtime_worker: {e}", exc_info=True)
|
|
2213
2308
|
raise
|
|
2214
2309
|
|
|
2215
2310
|
def _is_silero_speech(self, chunk):
|
|
@@ -28,6 +28,7 @@ DEFAULT_DATA_URL = "ws://127.0.0.1:8012"
|
|
|
28
28
|
INIT_MODEL_TRANSCRIPTION = "tiny"
|
|
29
29
|
INIT_MODEL_TRANSCRIPTION_REALTIME = "tiny"
|
|
30
30
|
INIT_REALTIME_PROCESSING_PAUSE = 0.2
|
|
31
|
+
INIT_REALTIME_INITIAL_PAUSE = 0.2
|
|
31
32
|
INIT_SILERO_SENSITIVITY = 0.4
|
|
32
33
|
INIT_WEBRTC_SENSITIVITY = 3
|
|
33
34
|
INIT_POST_SPEECH_SILENCE_DURATION = 0.6
|
|
@@ -68,6 +69,7 @@ class AudioToTextRecorderClient:
|
|
|
68
69
|
|
|
69
70
|
def __init__(self,
|
|
70
71
|
model: str = INIT_MODEL_TRANSCRIPTION,
|
|
72
|
+
download_root: str = None,
|
|
71
73
|
language: str = "",
|
|
72
74
|
compute_type: str = "default",
|
|
73
75
|
input_device_index: int = None,
|
|
@@ -81,14 +83,17 @@ class AudioToTextRecorderClient:
|
|
|
81
83
|
use_microphone=True,
|
|
82
84
|
spinner=True,
|
|
83
85
|
level=logging.WARNING,
|
|
86
|
+
batch_size: int = 16,
|
|
84
87
|
|
|
85
88
|
# Realtime transcription parameters
|
|
86
89
|
enable_realtime_transcription=False,
|
|
87
90
|
use_main_model_for_realtime=False,
|
|
88
91
|
realtime_model_type=INIT_MODEL_TRANSCRIPTION_REALTIME,
|
|
89
92
|
realtime_processing_pause=INIT_REALTIME_PROCESSING_PAUSE,
|
|
93
|
+
init_realtime_after_seconds=INIT_REALTIME_INITIAL_PAUSE,
|
|
90
94
|
on_realtime_transcription_update=None,
|
|
91
95
|
on_realtime_transcription_stabilized=None,
|
|
96
|
+
realtime_batch_size: int = 16,
|
|
92
97
|
|
|
93
98
|
# Voice activation parameters
|
|
94
99
|
silero_sensitivity: float = INIT_SILERO_SENSITIVITY,
|
|
@@ -133,6 +138,7 @@ class AudioToTextRecorderClient:
|
|
|
133
138
|
buffer_size: int = BUFFER_SIZE,
|
|
134
139
|
sample_rate: int = SAMPLE_RATE,
|
|
135
140
|
initial_prompt: Optional[Union[str, Iterable[int]]] = None,
|
|
141
|
+
initial_prompt_realtime: Optional[Union[str, Iterable[int]]] = None,
|
|
136
142
|
suppress_tokens: Optional[List[int]] = [-1],
|
|
137
143
|
print_transcription_time: bool = False,
|
|
138
144
|
early_transcription_on_silence: int = 0,
|
|
@@ -162,10 +168,14 @@ class AudioToTextRecorderClient:
|
|
|
162
168
|
self.use_microphone = use_microphone
|
|
163
169
|
self.spinner = spinner
|
|
164
170
|
self.level = level
|
|
171
|
+
self.batch_size = batch_size
|
|
172
|
+
self.init_realtime_after_seconds = init_realtime_after_seconds
|
|
173
|
+
self.realtime_batch_size = realtime_batch_size
|
|
165
174
|
|
|
166
175
|
# Real-time transcription parameters
|
|
167
176
|
self.enable_realtime_transcription = enable_realtime_transcription
|
|
168
177
|
self.use_main_model_for_realtime = use_main_model_for_realtime
|
|
178
|
+
self.download_root = download_root
|
|
169
179
|
self.realtime_model_type = realtime_model_type
|
|
170
180
|
self.realtime_processing_pause = realtime_processing_pause
|
|
171
181
|
self.on_realtime_transcription_update = on_realtime_transcription_update
|
|
@@ -204,6 +214,7 @@ class AudioToTextRecorderClient:
|
|
|
204
214
|
self.buffer_size = buffer_size
|
|
205
215
|
self.sample_rate = sample_rate
|
|
206
216
|
self.initial_prompt = initial_prompt
|
|
217
|
+
self.initial_prompt_realtime = initial_prompt_realtime
|
|
207
218
|
self.suppress_tokens = suppress_tokens
|
|
208
219
|
self.print_transcription_time = print_transcription_time
|
|
209
220
|
self.early_transcription_on_silence = early_transcription_on_silence
|
|
@@ -376,6 +387,43 @@ class AudioToTextRecorderClient:
|
|
|
376
387
|
args += ['--model', self.model]
|
|
377
388
|
if self.realtime_model_type:
|
|
378
389
|
args += ['--realtime_model_type', self.realtime_model_type]
|
|
390
|
+
if self.download_root:
|
|
391
|
+
args += ['--root', self.download_root]
|
|
392
|
+
if self.batch_size is not None:
|
|
393
|
+
args += ['--batch', str(self.batch_size)]
|
|
394
|
+
if self.realtime_batch_size is not None:
|
|
395
|
+
args += ['--realtime_batch_size', str(self.realtime_batch_size)]
|
|
396
|
+
if self.init_realtime_after_seconds is not None:
|
|
397
|
+
args += ['--init_realtime_after_seconds', str(self.init_realtime_after_seconds)]
|
|
398
|
+
if self.initial_prompt_realtime:
|
|
399
|
+
sanitized_prompt = self.initial_prompt_realtime.replace("\n", "\\n")
|
|
400
|
+
args += ['--initial_prompt_realtime', sanitized_prompt]
|
|
401
|
+
|
|
402
|
+
# if self.compute_type:
|
|
403
|
+
# args += ['--compute_type', self.compute_type]
|
|
404
|
+
# if self.input_device_index is not None:
|
|
405
|
+
# args += ['--input_device_index', str(self.input_device_index)]
|
|
406
|
+
# if self.gpu_device_index is not None:
|
|
407
|
+
# args += ['--gpu_device_index', str(self.gpu_device_index)]
|
|
408
|
+
# if self.device:
|
|
409
|
+
# args += ['--device', self.device]
|
|
410
|
+
# if self.spinner:
|
|
411
|
+
# args.append('--spinner') # flag, no need for True/False
|
|
412
|
+
# if self.enable_realtime_transcription:
|
|
413
|
+
# args.append('--enable_realtime_transcription') # flag, no need for True/False
|
|
414
|
+
# if self.handle_buffer_overflow:
|
|
415
|
+
# args.append('--handle_buffer_overflow') # flag, no need for True/False
|
|
416
|
+
# if self.suppress_tokens:
|
|
417
|
+
# args += ['--suppress_tokens', str(self.suppress_tokens)]
|
|
418
|
+
# if self.print_transcription_time:
|
|
419
|
+
# args.append('--print_transcription_time') # flag, no need for True/False
|
|
420
|
+
# if self.allowed_latency_limit is not None:
|
|
421
|
+
# args += ['--allowed_latency_limit', str(self.allowed_latency_limit)]
|
|
422
|
+
# if self.no_log_file:
|
|
423
|
+
# args.append('--no_log_file') # flag, no need for True
|
|
424
|
+
# if self.debug_mode:
|
|
425
|
+
# args.append('--debug') # flag, no need for True/False
|
|
426
|
+
|
|
379
427
|
if self.language:
|
|
380
428
|
args += ['--language', self.language]
|
|
381
429
|
if self.silero_sensitivity is not None:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: RealtimeSTT
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.94
|
|
4
4
|
Summary: A fast Voice Activity Detection and Transcription System
|
|
5
5
|
Home-page: https://github.com/KoljaB/RealTimeSTT
|
|
6
6
|
Author: Kolja Beigel
|
|
@@ -13,15 +13,15 @@ Requires-Python: >=3.6
|
|
|
13
13
|
Description-Content-Type: text/markdown
|
|
14
14
|
License-File: LICENSE
|
|
15
15
|
Requires-Dist: PyAudio==0.2.14
|
|
16
|
-
Requires-Dist: faster-whisper==1.1.
|
|
16
|
+
Requires-Dist: faster-whisper==1.1.1
|
|
17
17
|
Requires-Dist: pvporcupine==1.9.5
|
|
18
18
|
Requires-Dist: webrtcvad-wheels==2.0.14
|
|
19
19
|
Requires-Dist: halo==0.0.31
|
|
20
20
|
Requires-Dist: torch
|
|
21
21
|
Requires-Dist: torchaudio
|
|
22
|
-
Requires-Dist: scipy==1.
|
|
22
|
+
Requires-Dist: scipy==1.15.1
|
|
23
23
|
Requires-Dist: openwakeword>=0.4.0
|
|
24
|
-
Requires-Dist: websockets==14.
|
|
24
|
+
Requires-Dist: websockets==14.2
|
|
25
25
|
Requires-Dist: websocket-client==1.8.0
|
|
26
26
|
Requires-Dist: numpy<2.0.0
|
|
27
27
|
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
PyAudio==0.2.14
|
|
2
|
-
faster-whisper==1.1.
|
|
2
|
+
faster-whisper==1.1.1
|
|
3
3
|
pvporcupine==1.9.5
|
|
4
4
|
webrtcvad-wheels==2.0.14
|
|
5
5
|
halo==0.0.31
|
|
6
6
|
torch
|
|
7
7
|
torchaudio
|
|
8
|
-
scipy==1.
|
|
8
|
+
scipy==1.15.1
|
|
9
9
|
openwakeword>=0.4.0
|
|
10
|
-
websockets==14.
|
|
10
|
+
websockets==14.2
|
|
11
11
|
websocket-client==1.8.0
|
|
12
12
|
numpy<2.0.0
|
|
@@ -27,6 +27,8 @@ stt-server [OPTIONS]
|
|
|
27
27
|
- `-D, --debug`: Enable debug logging.
|
|
28
28
|
- `-W, --write`: Save audio to WAV file.
|
|
29
29
|
- `-s, --silence_timing`: Enable dynamic silence duration for sentence detection; default True.
|
|
30
|
+
- `-b, --batch, --batch_size`: Batch size for inference; default 16.
|
|
31
|
+
- `--root, --download_root`: Specifies the root path were the Whisper models are downloaded to.
|
|
30
32
|
- `--silero_sensitivity`: Silero VAD sensitivity (0-1); default 0.05.
|
|
31
33
|
- `--silero_use_onnx`: Use Silero ONNX model; default False.
|
|
32
34
|
- `--webrtc_sensitivity`: WebRTC VAD sensitivity (0-3); default 3.
|
|
@@ -38,7 +40,10 @@ stt-server [OPTIONS]
|
|
|
38
40
|
- `--early_transcription_on_silence`: Start transcription after silence in seconds; default 0.2.
|
|
39
41
|
- `--beam_size`: Beam size for main model; default 5.
|
|
40
42
|
- `--beam_size_realtime`: Beam size for real-time model; default 3.
|
|
41
|
-
- `--
|
|
43
|
+
- `--init_realtime_after_seconds`: Initial waiting time for realtime transcription; default 0.2.
|
|
44
|
+
- `--realtime_batch_size`: Batch size for the real-time transcription model; default 16.
|
|
45
|
+
- `--initial_prompt`: Initial main transcription guidance prompt.
|
|
46
|
+
- `--initial_prompt_realtime`: Initial realtime transcription guidance prompt.
|
|
42
47
|
- `--end_of_sentence_detection_pause`: Silence duration for sentence end detection; default 0.45.
|
|
43
48
|
- `--unknown_sentence_detection_pause`: Pause duration for incomplete sentence detection; default 0.7.
|
|
44
49
|
- `--mid_sentence_detection_pause`: Pause for mid-sentence break; default 2.0.
|
|
@@ -52,6 +57,14 @@ stt-server [OPTIONS]
|
|
|
52
57
|
- `--use_main_model_for_realtime`: Use main model for real-time transcription.
|
|
53
58
|
- `--use_extended_logging`: Enable extensive log messages.
|
|
54
59
|
- `--logchunks`: Log incoming audio chunks.
|
|
60
|
+
- `--compute_type`: Type of computation to use.
|
|
61
|
+
- `--input_device_index`: Index of the audio input device.
|
|
62
|
+
- `--gpu_device_index`: Index of the GPU device.
|
|
63
|
+
- `--device`: Device to use for computation.
|
|
64
|
+
- `--handle_buffer_overflow`: Handle buffer overflow during transcription.
|
|
65
|
+
- `--suppress_tokens`: Suppress tokens during transcription.
|
|
66
|
+
- `--allowed_latency_limit`: Allowed latency limit for real-time transcription.
|
|
67
|
+
|
|
55
68
|
|
|
56
69
|
### WebSocket Interface:
|
|
57
70
|
The server supports two WebSocket connections:
|
|
@@ -364,7 +377,7 @@ def parse_arguments():
|
|
|
364
377
|
parser.add_argument('-l', '--lang', '--language', type=str, default='en',
|
|
365
378
|
help='Language code for the STT model to transcribe in a specific language. Leave this empty for auto-detection based on input audio. Default is en. List of supported language codes: https://github.com/openai/whisper/blob/main/whisper/tokenizer.py#L11-L110')
|
|
366
379
|
|
|
367
|
-
parser.add_argument('-i', '--input-device', '--
|
|
380
|
+
parser.add_argument('-i', '--input-device', '--input-device-index', type=int, default=1,
|
|
368
381
|
help='Index of the audio input device to use. Use this option to specify a particular microphone or audio input device based on your system. Default is 1.')
|
|
369
382
|
|
|
370
383
|
parser.add_argument('-c', '--control', '--control_port', type=int, default=8011,
|
|
@@ -378,12 +391,23 @@ def parse_arguments():
|
|
|
378
391
|
|
|
379
392
|
parser.add_argument('-D', '--debug', action='store_true', help='Enable debug logging for detailed server operations')
|
|
380
393
|
|
|
381
|
-
parser.add_argument(
|
|
382
|
-
|
|
394
|
+
parser.add_argument('-W', '--write', metavar='FILE', help='Save received audio to a WAV file')
|
|
395
|
+
|
|
396
|
+
parser.add_argument('-b', '--batch', '--batch_size', type=int, default=16, help='Batch size for inference. This parameter controls the number of audio chunks processed in parallel during transcription. Default is 16.')
|
|
397
|
+
|
|
398
|
+
parser.add_argument('--root', '--download_root', type=str,default=None, help='Specifies the root path where the Whisper models are downloaded to. Default is None.')
|
|
383
399
|
|
|
384
400
|
parser.add_argument('-s', '--silence_timing', action='store_true', default=True,
|
|
385
401
|
help='Enable dynamic adjustment of silence duration for sentence detection. Adjusts post-speech silence duration based on detected sentence structure and punctuation. Default is False.')
|
|
386
402
|
|
|
403
|
+
parser.add_argument('--init_realtime_after_seconds', type=float, default=0.2,
|
|
404
|
+
help='The initial waiting time in seconds before real-time transcription starts. This delay helps prevent false positives at the beginning of a session. Default is 0.2 seconds.')
|
|
405
|
+
|
|
406
|
+
parser.add_argument('--realtime_batch_size', type=int, default=16,
|
|
407
|
+
help='Batch size for the real-time transcription model. This parameter controls the number of audio chunks processed in parallel during real-time transcription. Default is 16.')
|
|
408
|
+
|
|
409
|
+
parser.add_argument('--initial_prompt_realtime', type=str, default="", help='Initial prompt that guides the real-time transcription model to produce transcriptions in a particular style or format.')
|
|
410
|
+
|
|
387
411
|
parser.add_argument('--silero_sensitivity', type=float, default=0.05,
|
|
388
412
|
help='Sensitivity level for Silero Voice Activity Detection (VAD), with a range from 0 to 1. Lower values make the model less sensitive, useful for noisy environments. Default is 0.05.')
|
|
389
413
|
|
|
@@ -457,6 +481,23 @@ def parse_arguments():
|
|
|
457
481
|
parser.add_argument('--use_extended_logging', action='store_true',
|
|
458
482
|
help='Writes extensive log messages for the recording worker, that processes the audio chunks.')
|
|
459
483
|
|
|
484
|
+
parser.add_argument('--compute_type', type=str, default='default',
|
|
485
|
+
help='Type of computation to use. See https://opennmt.net/CTranslate2/quantization.html')
|
|
486
|
+
|
|
487
|
+
parser.add_argument('--gpu_device_index', type=int, default=0,
|
|
488
|
+
help='Index of the GPU device to use. Default is None.')
|
|
489
|
+
|
|
490
|
+
parser.add_argument('--device', type=str, default='cuda',
|
|
491
|
+
help='Device for model to use. Can either be "cuda" or "cpu". Default is cuda.')
|
|
492
|
+
|
|
493
|
+
parser.add_argument('--handle_buffer_overflow', action='store_true',
|
|
494
|
+
help='Handle buffer overflow during transcription. Default is False.')
|
|
495
|
+
|
|
496
|
+
parser.add_argument('--suppress_tokens', type=int, default=[-1], nargs='*', help='Suppress tokens during transcription. Default is [-1].')
|
|
497
|
+
|
|
498
|
+
parser.add_argument('--allowed_latency_limit', type=int, default=100,
|
|
499
|
+
help='Maximal amount of chunks that can be unprocessed in queue before discarding chunks.. Default is 100.')
|
|
500
|
+
|
|
460
501
|
parser.add_argument('--logchunks', action='store_true', help='Enable logging of incoming audio chunks (periods)')
|
|
461
502
|
|
|
462
503
|
# Parse arguments
|
|
@@ -479,6 +520,9 @@ def parse_arguments():
|
|
|
479
520
|
if args.initial_prompt:
|
|
480
521
|
args.initial_prompt = args.initial_prompt.replace("\\n", "\n")
|
|
481
522
|
|
|
523
|
+
if args.initial_prompt_realtime:
|
|
524
|
+
args.initial_prompt_realtime = args.initial_prompt_realtime.replace("\\n", "\n")
|
|
525
|
+
|
|
482
526
|
return args
|
|
483
527
|
|
|
484
528
|
def _recorder_thread(loop):
|
|
@@ -534,7 +578,7 @@ def decode_and_resample(
|
|
|
534
578
|
|
|
535
579
|
return resampled_audio.astype(np.int16).tobytes()
|
|
536
580
|
|
|
537
|
-
async def control_handler(websocket
|
|
581
|
+
async def control_handler(websocket):
|
|
538
582
|
debug_print(f"New control connection from {websocket.remote_address}")
|
|
539
583
|
print(f"{bcolors.OKGREEN}Control client connected{bcolors.ENDC}")
|
|
540
584
|
global recorder
|
|
@@ -629,7 +673,7 @@ async def control_handler(websocket, path):
|
|
|
629
673
|
finally:
|
|
630
674
|
control_connections.remove(websocket)
|
|
631
675
|
|
|
632
|
-
async def data_handler(websocket
|
|
676
|
+
async def data_handler(websocket):
|
|
633
677
|
global writechunks, wav_file
|
|
634
678
|
print(f"{bcolors.OKGREEN}Data client connected{bcolors.ENDC}")
|
|
635
679
|
data_connections.add(websocket)
|
|
@@ -700,8 +744,13 @@ async def main_async():
|
|
|
700
744
|
|
|
701
745
|
recorder_config = {
|
|
702
746
|
'model': args.model,
|
|
747
|
+
'download_root': args.root,
|
|
703
748
|
'realtime_model_type': args.rt_model,
|
|
704
749
|
'language': args.lang,
|
|
750
|
+
'batch_size': args.batch,
|
|
751
|
+
'init_realtime_after_seconds': args.init_realtime_after_seconds,
|
|
752
|
+
'realtime_batch_size': args.realtime_batch_size,
|
|
753
|
+
'initial_prompt_realtime': args.initial_prompt_realtime,
|
|
705
754
|
'input_device_index': args.input_device,
|
|
706
755
|
'silero_sensitivity': args.silero_sensitivity,
|
|
707
756
|
'silero_use_onnx': args.silero_use_onnx,
|
|
@@ -740,6 +789,12 @@ async def main_async():
|
|
|
740
789
|
'no_log_file': True, # Disable logging to file
|
|
741
790
|
'use_extended_logging': args.use_extended_logging,
|
|
742
791
|
'level': loglevel,
|
|
792
|
+
'compute_type': args.compute_type,
|
|
793
|
+
'gpu_device_index': args.gpu_device_index,
|
|
794
|
+
'device': args.device,
|
|
795
|
+
'handle_buffer_overflow': args.handle_buffer_overflow,
|
|
796
|
+
'suppress_tokens': args.suppress_tokens,
|
|
797
|
+
'allowed_latency_limit': args.allowed_latency_limit,
|
|
743
798
|
}
|
|
744
799
|
|
|
745
800
|
try:
|
|
@@ -9,7 +9,7 @@ with open('requirements.txt') as f:
|
|
|
9
9
|
|
|
10
10
|
setuptools.setup(
|
|
11
11
|
name="RealtimeSTT",
|
|
12
|
-
version="0.3.
|
|
12
|
+
version="0.3.94",
|
|
13
13
|
author="Kolja Beigel",
|
|
14
14
|
author_email="kolja.beigel@web.de",
|
|
15
15
|
description="A fast Voice Activity Detection and Transcription System",
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|