RealTimeSTT 0.3.93__tar.gz → 0.3.95__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 (20) hide show
  1. {realtimestt-0.3.93 → realtimestt-0.3.95}/PKG-INFO +7 -7
  2. {realtimestt-0.3.93 → realtimestt-0.3.95}/README.md +2 -3
  3. {realtimestt-0.3.93 → realtimestt-0.3.95}/RealtimeSTT/audio_input.py +81 -14
  4. {realtimestt-0.3.93 → realtimestt-0.3.95}/RealtimeSTT/audio_recorder.py +137 -36
  5. realtimestt-0.3.95/RealtimeSTT/warmup_audio.wav +0 -0
  6. {realtimestt-0.3.93 → realtimestt-0.3.95}/RealtimeSTT.egg-info/PKG-INFO +7 -7
  7. {realtimestt-0.3.93 → realtimestt-0.3.95}/RealtimeSTT.egg-info/SOURCES.txt +1 -0
  8. {realtimestt-0.3.93 → realtimestt-0.3.95}/RealtimeSTT.egg-info/requires.txt +4 -3
  9. {realtimestt-0.3.93 → realtimestt-0.3.95}/setup.py +3 -1
  10. {realtimestt-0.3.93 → realtimestt-0.3.95}/LICENSE +0 -0
  11. {realtimestt-0.3.93 → realtimestt-0.3.95}/RealtimeSTT/__init__.py +0 -0
  12. {realtimestt-0.3.93 → realtimestt-0.3.95}/RealtimeSTT/audio_recorder_client.py +0 -0
  13. {realtimestt-0.3.93 → realtimestt-0.3.95}/RealtimeSTT.egg-info/dependency_links.txt +0 -0
  14. {realtimestt-0.3.93 → realtimestt-0.3.95}/RealtimeSTT.egg-info/entry_points.txt +0 -0
  15. {realtimestt-0.3.93 → realtimestt-0.3.95}/RealtimeSTT.egg-info/top_level.txt +0 -0
  16. {realtimestt-0.3.93 → realtimestt-0.3.95}/RealtimeSTT_server/__init__.py +0 -0
  17. {realtimestt-0.3.93 → realtimestt-0.3.95}/RealtimeSTT_server/install_packages.py +0 -0
  18. {realtimestt-0.3.93 → realtimestt-0.3.95}/RealtimeSTT_server/stt_cli_client.py +0 -0
  19. {realtimestt-0.3.93 → realtimestt-0.3.95}/RealtimeSTT_server/stt_server.py +0 -0
  20. {realtimestt-0.3.93 → realtimestt-0.3.95}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: RealtimeSTT
3
- Version: 0.3.93
3
+ Version: 0.3.95
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,21 +13,22 @@ 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.0
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.14.1
22
+ Requires-Dist: scipy==1.15.1
23
23
  Requires-Dist: openwakeword>=0.4.0
24
- Requires-Dist: websockets==14.1
24
+ Requires-Dist: websockets==14.2
25
25
  Requires-Dist: websocket-client==1.8.0
26
26
  Requires-Dist: numpy<2.0.0
27
+ Requires-Dist: soundfile==0.13.1
27
28
 
28
29
  # RealtimeSTT
29
30
  [![PyPI](https://img.shields.io/pypi/v/RealtimeSTT)](https://pypi.org/project/RealtimeSTT/)
30
- [![Downloads](https://static.pepy.tech/badge/RealtimeSTT)](https://pepy.tech/project/KoljaB/RealtimeSTT)
31
+ [![Downloads](https://static.pepy.tech/badge/RealtimeSTT)](https://www.pepy.tech/projects/realtimestt)
31
32
  [![GitHub release](https://img.shields.io/github/release/KoljaB/RealtimeSTT.svg)](https://GitHub.com/KoljaB/RealtimeSTT/releases/)
32
33
  [![GitHub commits](https://badgen.net/github/commits/KoljaB/RealtimeSTT)](https://GitHub.com/Naereen/KoljaB/RealtimeSTT/commit/)
33
34
  [![GitHub forks](https://img.shields.io/github/forks/KoljaB/RealtimeSTT.svg?style=social&label=Fork&maxAge=2592000)](https://GitHub.com/KoljaB/RealtimeSTT/network/)
@@ -55,7 +56,7 @@ https://github.com/user-attachments/assets/797e6552-27cd-41b1-a7f3-e5cbc72094f5
55
56
 
56
57
  ### Updates
57
58
 
58
- Latest Version: v0.3.92
59
+ Latest Version: v0.3.95
59
60
 
60
61
  See [release history](https://github.com/KoljaB/RealtimeSTT/releases).
61
62
 
@@ -67,7 +68,6 @@ See [release history](https://github.com/KoljaB/RealtimeSTT/releases).
67
68
 
68
69
  ```python
69
70
  from RealtimeSTT import AudioToTextRecorder
70
- import pyautogui
71
71
 
72
72
  def process_text(text):
73
73
  print(text)
@@ -1,6 +1,6 @@
1
1
  # RealtimeSTT
2
2
  [![PyPI](https://img.shields.io/pypi/v/RealtimeSTT)](https://pypi.org/project/RealtimeSTT/)
3
- [![Downloads](https://static.pepy.tech/badge/RealtimeSTT)](https://pepy.tech/project/KoljaB/RealtimeSTT)
3
+ [![Downloads](https://static.pepy.tech/badge/RealtimeSTT)](https://www.pepy.tech/projects/realtimestt)
4
4
  [![GitHub release](https://img.shields.io/github/release/KoljaB/RealtimeSTT.svg)](https://GitHub.com/KoljaB/RealtimeSTT/releases/)
5
5
  [![GitHub commits](https://badgen.net/github/commits/KoljaB/RealtimeSTT)](https://GitHub.com/Naereen/KoljaB/RealtimeSTT/commit/)
6
6
  [![GitHub forks](https://img.shields.io/github/forks/KoljaB/RealtimeSTT.svg?style=social&label=Fork&maxAge=2592000)](https://GitHub.com/KoljaB/RealtimeSTT/network/)
@@ -28,7 +28,7 @@ https://github.com/user-attachments/assets/797e6552-27cd-41b1-a7f3-e5cbc72094f5
28
28
 
29
29
  ### Updates
30
30
 
31
- Latest Version: v0.3.92
31
+ Latest Version: v0.3.95
32
32
 
33
33
  See [release history](https://github.com/KoljaB/RealtimeSTT/releases).
34
34
 
@@ -40,7 +40,6 @@ See [release history](https://github.com/KoljaB/RealtimeSTT/releases).
40
40
 
41
41
  ```python
42
42
  from RealtimeSTT import AudioToTextRecorder
43
- import pyautogui
44
43
 
45
44
  def process_text(text):
46
45
  print(text)
@@ -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__(self, input_device_index=None, debug_mode=False):
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
- # PyAudio constants
16
- self.CHUNK = 1024
17
- self.FORMAT = pyaudio.paInt16
18
- self.CHANNELS = 1
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.FORMAT, # Changed to input_format
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.DESIRED_RATE)
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.FORMAT,
119
- channels=self.CHANNELS,
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.CHUNK,
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)
@@ -26,15 +26,15 @@ Author: Kolja Beigel
26
26
 
27
27
  """
28
28
 
29
+ from faster_whisper import WhisperModel, BatchedInferencePipeline
29
30
  from typing import Iterable, List, Optional, Union
30
- import torch.multiprocessing as mp
31
- import torch
32
- from ctypes import c_bool
33
31
  from openwakeword.model import Model
32
+ import torch.multiprocessing as mp
34
33
  from scipy.signal import resample
35
- from scipy import signal
36
34
  import signal as system_signal
37
- from faster_whisper import WhisperModel, BatchedInferencePipeline
35
+ from ctypes import c_bool
36
+ from scipy import signal
37
+ import soundfile as sf
38
38
  import faster_whisper
39
39
  import openwakeword
40
40
  import collections
@@ -43,14 +43,13 @@ import pvporcupine
43
43
  import traceback
44
44
  import threading
45
45
  import webrtcvad
46
- import itertools
47
46
  import datetime
48
47
  import platform
49
- import pyaudio
50
48
  import logging
51
49
  import struct
52
50
  import base64
53
51
  import queue
52
+ import torch
54
53
  import halo
55
54
  import time
56
55
  import copy
@@ -120,7 +119,7 @@ class TranscriptionWorker:
120
119
  data = self.conn.recv()
121
120
  self.queue.put(data)
122
121
  except Exception as e:
123
- logging.error(f"Error receiving data from connection: {e}")
122
+ logging.error(f"Error receiving data from connection: {e}", exc_info=True)
124
123
  else:
125
124
  time.sleep(TIME_SLEEP)
126
125
 
@@ -144,8 +143,13 @@ class TranscriptionWorker:
144
143
  model = BatchedInferencePipeline(model=model)
145
144
 
146
145
  # Run a warm-up transcription
147
- dummy_audio = np.zeros(16000, dtype=np.float32)
148
- model.transcribe(dummy_audio, language="en", beam_size=1)
146
+ current_dir = os.path.dirname(os.path.realpath(__file__))
147
+ warmup_audio_path = os.path.join(
148
+ current_dir, "warmup_audio.wav"
149
+ )
150
+ warmup_audio_data, _ = sf.read(warmup_audio_path, dtype="float32")
151
+ segments, info = model.transcribe(warmup_audio_data, language="en", beam_size=1)
152
+ model_warmup_transcription = " ".join(segment.text for segment in segments)
149
153
  except Exception as e:
150
154
  logging.exception(f"Error initializing main faster_whisper transcription model: {e}")
151
155
  raise
@@ -162,6 +166,7 @@ class TranscriptionWorker:
162
166
  try:
163
167
  audio, language = self.queue.get(timeout=0.1)
164
168
  try:
169
+ logging.debug(f"Transcribing audio with language {language}")
165
170
  if self.batch_size > 0:
166
171
  segments, info = model.transcribe(
167
172
  audio,
@@ -184,7 +189,7 @@ class TranscriptionWorker:
184
189
  logging.debug(f"Final text detected with main model: {transcription}")
185
190
  self.conn.send(('success', (transcription, info)))
186
191
  except Exception as e:
187
- logging.error(f"General error in transcription: {e}")
192
+ logging.error(f"General error in transcription: {e}", exc_info=True)
188
193
  self.conn.send(('error', str(e)))
189
194
  except queue.Empty:
190
195
  continue
@@ -193,7 +198,7 @@ class TranscriptionWorker:
193
198
  logging.debug("Transcription worker process finished due to KeyboardInterrupt")
194
199
  break
195
200
  except Exception as e:
196
- logging.error(f"General error in processing queue item: {e}")
201
+ logging.error(f"General error in processing queue item: {e}", exc_info=True)
197
202
  finally:
198
203
  __builtins__['print'] = print # Restore the original print function
199
204
  self.conn.close()
@@ -567,6 +572,8 @@ class AudioToTextRecorder:
567
572
  self.sample_rate = sample_rate
568
573
  self.recording_start_time = 0
569
574
  self.recording_stop_time = 0
575
+ self.last_recording_start_time = 0
576
+ self.last_recording_stop_time = 0
570
577
  self.wake_word_detect_time = 0
571
578
  self.silero_check_time = 0
572
579
  self.silero_working = False
@@ -590,6 +597,8 @@ class AudioToTextRecorder:
590
597
  self.stream = None
591
598
  self.start_recording_event = threading.Event()
592
599
  self.stop_recording_event = threading.Event()
600
+ self.backdate_stop_seconds = 0.0
601
+ self.backdate_resume_seconds = 0.0
593
602
  self.last_transcription_bytes = None
594
603
  self.last_transcription_bytes_b64 = None
595
604
  self.initial_prompt = initial_prompt
@@ -723,8 +732,15 @@ class AudioToTextRecorder:
723
732
  )
724
733
  if self.realtime_batch_size > 0:
725
734
  self.realtime_model_type = BatchedInferencePipeline(model=self.realtime_model_type)
726
- dummy_audio = np.zeros(16000, dtype=np.float32)
727
- self.realtime_model_type.transcribe(dummy_audio, language="en", beam_size=1)
735
+
736
+ # Run a warm-up transcription
737
+ current_dir = os.path.dirname(os.path.realpath(__file__))
738
+ warmup_audio_path = os.path.join(
739
+ current_dir, "warmup_audio.wav"
740
+ )
741
+ warmup_audio_data, _ = sf.read(warmup_audio_path, dtype="float32")
742
+ segments, info = self.realtime_model_type.transcribe(warmup_audio_data, language="en", beam_size=1)
743
+ model_warmup_transcription = " ".join(segment.text for segment in segments)
728
744
  except Exception as e:
729
745
  logging.exception("Error initializing faster_whisper "
730
746
  f"realtime transcription model: {e}"
@@ -860,6 +876,7 @@ class AudioToTextRecorder:
860
876
  0.3)
861
877
  )
862
878
  self.frames = []
879
+ self.last_frames = []
863
880
 
864
881
  # Recording control flags
865
882
  self.is_recording = False
@@ -925,7 +942,7 @@ class AudioToTextRecorder:
925
942
  logging.info("KeyboardInterrupt in read from stdout detected, exiting...")
926
943
  break
927
944
  except Exception as e:
928
- logging.error(f"Unexpected error in read from stdout: {e}")
945
+ logging.error(f"Unexpected error in read from stdout: {e}", exc_info=True)
929
946
  logging.error(traceback.format_exc()) # Log the full traceback here
930
947
  break
931
948
  time.sleep(0.1)
@@ -1074,7 +1091,7 @@ class AudioToTextRecorder:
1074
1091
  return stream
1075
1092
 
1076
1093
  except Exception as e:
1077
- logging.error(f"Microphone connection failed: {e}. Retrying...")
1094
+ logging.error(f"Microphone connection failed: {e}. Retrying...", exc_info=True)
1078
1095
  input_device_index = None
1079
1096
  time.sleep(3) # Wait before retrying
1080
1097
  continue
@@ -1190,7 +1207,7 @@ class AudioToTextRecorder:
1190
1207
  if e.errno == pyaudio.paInputOverflowed:
1191
1208
  logging.warning("Input overflowed. Frame dropped.")
1192
1209
  else:
1193
- logging.error(f"OSError during recording: {e}")
1210
+ logging.error(f"OSError during recording: {e}", exc_info=True)
1194
1211
  # Attempt to reinitialize the stream
1195
1212
  logging.error("Attempting to reinitialize the audio stream...")
1196
1213
 
@@ -1259,12 +1276,17 @@ class AudioToTextRecorder:
1259
1276
  self.listen_start = time.time()
1260
1277
 
1261
1278
  def abort(self):
1279
+ state = self.state
1262
1280
  self.start_recording_on_voice_activity = False
1263
1281
  self.stop_recording_on_voice_deactivity = False
1264
- self._set_state("inactive")
1265
1282
  self.interrupt_stop_event.set()
1266
- self.was_interrupted.wait()
1283
+ if self.state != "inactive": # if inactive, was_interrupted will never be set
1284
+ self.was_interrupted.wait()
1285
+ self._set_state("transcribing")
1267
1286
  self.was_interrupted.clear()
1287
+ if self.is_recording: # if recording, make sure to stop the recorder
1288
+ self.stop()
1289
+
1268
1290
 
1269
1291
  def wait_audio(self):
1270
1292
  """
@@ -1308,13 +1330,60 @@ class AudioToTextRecorder:
1308
1330
  if (self.stop_recording_event.wait(timeout=0.02)):
1309
1331
  break
1310
1332
 
1311
- # Convert recorded frames to the appropriate audio format.
1312
- audio_array = np.frombuffer(b''.join(self.frames), dtype=np.int16)
1313
- self.audio = audio_array.astype(np.float32) / INT16_MAX_ABS_VALUE
1333
+ frames = self.frames
1334
+ if len(frames) == 0:
1335
+ frames = self.last_frames
1336
+
1337
+ # Calculate samples needed for backdating resume
1338
+ samples_to_keep = int(self.sample_rate * self.backdate_resume_seconds)
1339
+
1340
+ # First convert all current frames to audio array
1341
+ full_audio_array = np.frombuffer(b''.join(frames), dtype=np.int16)
1342
+ full_audio = full_audio_array.astype(np.float32) / INT16_MAX_ABS_VALUE
1343
+
1344
+ # Calculate how many samples we need to keep for backdating resume
1345
+ if samples_to_keep > 0:
1346
+ samples_to_keep = min(samples_to_keep, len(full_audio))
1347
+ # Keep the last N samples for backdating resume
1348
+ frames_to_read_audio = full_audio[-samples_to_keep:]
1349
+
1350
+ # Convert the audio back to int16 bytes for frames
1351
+ frames_to_read_int16 = (frames_to_read_audio * INT16_MAX_ABS_VALUE).astype(np.int16)
1352
+ frame_bytes = frames_to_read_int16.tobytes()
1353
+
1354
+ # Split into appropriate frame sizes (assuming standard frame size)
1355
+ FRAME_SIZE = 2048 # Typical frame size
1356
+ frames_to_read = []
1357
+ for i in range(0, len(frame_bytes), FRAME_SIZE):
1358
+ frame = frame_bytes[i:i + FRAME_SIZE]
1359
+ if frame: # Only add non-empty frames
1360
+ frames_to_read.append(frame)
1361
+ else:
1362
+ frames_to_read = []
1363
+
1364
+ # Process backdate stop seconds
1365
+ samples_to_remove = int(self.sample_rate * self.backdate_stop_seconds)
1366
+
1367
+ if samples_to_remove > 0:
1368
+ if samples_to_remove < len(full_audio):
1369
+ self.audio = full_audio[:-samples_to_remove]
1370
+ logging.debug(f"Removed {samples_to_remove} samples "
1371
+ f"({samples_to_remove/self.sample_rate:.3f}s) from end of audio")
1372
+ else:
1373
+ self.audio = np.array([], dtype=np.float32)
1374
+ logging.debug("Cleared audio (samples_to_remove >= audio length)")
1375
+ else:
1376
+ self.audio = full_audio
1377
+ logging.debug(f"No samples removed, final audio length: {len(self.audio)}")
1378
+
1314
1379
  self.frames.clear()
1380
+ self.last_frames.clear()
1381
+ self.frames.extend(frames_to_read)
1382
+
1383
+ # Reset backdating parameters
1384
+ self.backdate_stop_seconds = 0.0
1385
+ self.backdate_resume_seconds = 0.0
1315
1386
 
1316
- # Reset recording-related timestamps
1317
- self.recording_stop_time = 0
1318
1387
  self.listen_start = 0
1319
1388
 
1320
1389
  self._set_state("inactive")
@@ -1363,6 +1432,12 @@ class AudioToTextRecorder:
1363
1432
 
1364
1433
  while self.transcribe_count > 0:
1365
1434
  logging.debug(F"Receive from parent_transcription_pipe after sendiung transcription request, transcribe_count: {self.transcribe_count}")
1435
+ if not self.parent_transcription_pipe.poll(0.1): # check if transcription done
1436
+ if self.interrupt_stop_event.is_set(): # check if interrupted
1437
+ self.was_interrupted.set()
1438
+ self._set_state("inactive")
1439
+ return "" # return empty string if interrupted
1440
+ continue
1366
1441
  status, result = self.parent_transcription_pipe.recv()
1367
1442
  self.transcribe_count -= 1
1368
1443
 
@@ -1383,12 +1458,12 @@ class AudioToTextRecorder:
1383
1458
  print(f"Model {self.main_model_type} completed transcription in {transcription_time:.2f} seconds")
1384
1459
  else:
1385
1460
  logging.debug(f"Model {self.main_model_type} completed transcription in {transcription_time:.2f} seconds")
1386
- return transcription
1461
+ return "" if self.interrupt_stop_event.is_set() else transcription # if interrupted return empty string
1387
1462
  else:
1388
1463
  logging.error(f"Transcription error: {result}")
1389
1464
  raise Exception(result)
1390
1465
  except Exception as e:
1391
- logging.error(f"Error during transcription: {str(e)}")
1466
+ logging.error(f"Error during transcription: {str(e)}", exc_info=True)
1392
1467
  raise e
1393
1468
 
1394
1469
  def _process_wakeword(self, data):
@@ -1476,7 +1551,16 @@ class AudioToTextRecorder:
1476
1551
  return self.transcribe()
1477
1552
 
1478
1553
 
1479
- def start(self):
1554
+ def format_number(self, num):
1555
+ # Convert the number to a string
1556
+ num_str = f"{num:.10f}" # Ensure precision is sufficient
1557
+ # Split the number into integer and decimal parts
1558
+ integer_part, decimal_part = num_str.split('.')
1559
+ # Take the last two digits of the integer part and the first two digits of the decimal part
1560
+ result = f"{integer_part[-2:]}.{decimal_part[:2]}"
1561
+ return result
1562
+
1563
+ def start(self, frames = None):
1480
1564
  """
1481
1565
  Starts recording audio directly without waiting for voice activity.
1482
1566
  """
@@ -1498,7 +1582,10 @@ class AudioToTextRecorder:
1498
1582
  self.wakeword_detected = False
1499
1583
  self.wake_word_detect_time = 0
1500
1584
  self.frames = []
1585
+ if frames:
1586
+ self.frames = frames
1501
1587
  self.is_recording = True
1588
+
1502
1589
  self.recording_start_time = time.time()
1503
1590
  self.is_silero_speech_active = False
1504
1591
  self.is_webrtc_speech_active = False
@@ -1510,9 +1597,19 @@ class AudioToTextRecorder:
1510
1597
 
1511
1598
  return self
1512
1599
 
1513
- def stop(self):
1600
+ def stop(self,
1601
+ backdate_stop_seconds: float = 0.0,
1602
+ backdate_resume_seconds: float = 0.0,
1603
+ ):
1514
1604
  """
1515
1605
  Stops recording audio.
1606
+
1607
+ Args:
1608
+ - backdate_stop_seconds (float, default="0.0"): Specifies the number of
1609
+ seconds to backdate the stop time. This is useful when the stop
1610
+ command is issued after the actual stop time.
1611
+ - backdate_resume_seconds (float, default="0.0"): Specifies the number
1612
+ of seconds to backdate the time relistening is initiated.
1516
1613
  """
1517
1614
 
1518
1615
  # Ensure there's a minimum interval
@@ -1525,6 +1622,9 @@ class AudioToTextRecorder:
1525
1622
  return self
1526
1623
 
1527
1624
  logging.info("recording stopped")
1625
+ self.last_frames = copy.deepcopy(self.frames)
1626
+ self.backdate_stop_seconds = backdate_stop_seconds
1627
+ self.backdate_resume_seconds = backdate_resume_seconds
1528
1628
  self.is_recording = False
1529
1629
  self.recording_stop_time = time.time()
1530
1630
  self.is_silero_speech_active = False
@@ -1533,6 +1633,9 @@ class AudioToTextRecorder:
1533
1633
  self.start_recording_event.clear()
1534
1634
  self.stop_recording_event.set()
1535
1635
 
1636
+ self.last_recording_start_time = self.recording_start_time
1637
+ self.last_recording_stop_time = self.recording_stop_time
1638
+
1536
1639
  if self.on_recording_stop:
1537
1640
  self.on_recording_stop()
1538
1641
 
@@ -1607,7 +1710,6 @@ class AudioToTextRecorder:
1607
1710
  return
1608
1711
 
1609
1712
  print("\033[91mRealtimeSTT shutting down\033[0m")
1610
- # logging.debug("RealtimeSTT shutting down")
1611
1713
 
1612
1714
  # Force wait_audio() and text() to exit
1613
1715
  self.is_shut_down = True
@@ -1736,7 +1838,7 @@ class AudioToTextRecorder:
1736
1838
  data = self.audio_queue.get()
1737
1839
 
1738
1840
  except BrokenPipeError:
1739
- logging.error("BrokenPipeError _recording_worker")
1841
+ logging.error("BrokenPipeError _recording_worker", exc_info=True)
1740
1842
  self.is_running = False
1741
1843
  break
1742
1844
 
@@ -1813,11 +1915,11 @@ class AudioToTextRecorder:
1813
1915
 
1814
1916
  except struct.error:
1815
1917
  logging.error("Error unpacking audio data "
1816
- "for wake word processing.")
1918
+ "for wake word processing.", exc_info=True)
1817
1919
  continue
1818
1920
 
1819
1921
  except Exception as e:
1820
- logging.error(f"Wake word processing error: {e}")
1922
+ logging.error(f"Wake word processing error: {e}", exc_info=True)
1821
1923
  continue
1822
1924
 
1823
1925
  if self.use_extended_logging:
@@ -2044,7 +2146,7 @@ class AudioToTextRecorder:
2044
2146
  except Exception as e:
2045
2147
  logging.debug('Debug: Caught exception in main try block')
2046
2148
  if not self.interrupt_stop_event.is_set():
2047
- logging.error(f"Unhandled exeption in _recording_worker: {e}")
2149
+ logging.error(f"Unhandled exeption in _recording_worker: {e}", exc_info=True)
2048
2150
  raise
2049
2151
 
2050
2152
  if self.use_extended_logging:
@@ -2111,7 +2213,7 @@ class AudioToTextRecorder:
2111
2213
  logging.warning("Realtime transcription timed out")
2112
2214
  continue
2113
2215
  except Exception as e:
2114
- logging.error(f"Error in realtime transcription: {str(e)}")
2216
+ logging.error(f"Error in realtime transcription: {str(e)}", exc_info=True)
2115
2217
  continue
2116
2218
  else:
2117
2219
  # Perform transcription and assemble the text
@@ -2145,7 +2247,6 @@ class AudioToTextRecorder:
2145
2247
  if self.is_recording and time.time() - \
2146
2248
  self.recording_start_time > self.init_realtime_after_seconds:
2147
2249
 
2148
- # logging.debug('Starting realtime transcription')
2149
2250
  self.realtime_transcription_text = realtime_text
2150
2251
  self.realtime_transcription_text = \
2151
2252
  self.realtime_transcription_text.strip()
@@ -2225,7 +2326,7 @@ class AudioToTextRecorder:
2225
2326
  time.sleep(TIME_SLEEP)
2226
2327
 
2227
2328
  except Exception as e:
2228
- logging.error(f"Unhandled exeption in _realtime_worker: {e}")
2329
+ logging.error(f"Unhandled exeption in _realtime_worker: {e}", exc_info=True)
2229
2330
  raise
2230
2331
 
2231
2332
  def _is_silero_speech(self, chunk):
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: RealtimeSTT
3
- Version: 0.3.93
3
+ Version: 0.3.95
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,21 +13,22 @@ 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.0
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.14.1
22
+ Requires-Dist: scipy==1.15.1
23
23
  Requires-Dist: openwakeword>=0.4.0
24
- Requires-Dist: websockets==14.1
24
+ Requires-Dist: websockets==14.2
25
25
  Requires-Dist: websocket-client==1.8.0
26
26
  Requires-Dist: numpy<2.0.0
27
+ Requires-Dist: soundfile==0.13.1
27
28
 
28
29
  # RealtimeSTT
29
30
  [![PyPI](https://img.shields.io/pypi/v/RealtimeSTT)](https://pypi.org/project/RealtimeSTT/)
30
- [![Downloads](https://static.pepy.tech/badge/RealtimeSTT)](https://pepy.tech/project/KoljaB/RealtimeSTT)
31
+ [![Downloads](https://static.pepy.tech/badge/RealtimeSTT)](https://www.pepy.tech/projects/realtimestt)
31
32
  [![GitHub release](https://img.shields.io/github/release/KoljaB/RealtimeSTT.svg)](https://GitHub.com/KoljaB/RealtimeSTT/releases/)
32
33
  [![GitHub commits](https://badgen.net/github/commits/KoljaB/RealtimeSTT)](https://GitHub.com/Naereen/KoljaB/RealtimeSTT/commit/)
33
34
  [![GitHub forks](https://img.shields.io/github/forks/KoljaB/RealtimeSTT.svg?style=social&label=Fork&maxAge=2592000)](https://GitHub.com/KoljaB/RealtimeSTT/network/)
@@ -55,7 +56,7 @@ https://github.com/user-attachments/assets/797e6552-27cd-41b1-a7f3-e5cbc72094f5
55
56
 
56
57
  ### Updates
57
58
 
58
- Latest Version: v0.3.92
59
+ Latest Version: v0.3.95
59
60
 
60
61
  See [release history](https://github.com/KoljaB/RealtimeSTT/releases).
61
62
 
@@ -67,7 +68,6 @@ See [release history](https://github.com/KoljaB/RealtimeSTT/releases).
67
68
 
68
69
  ```python
69
70
  from RealtimeSTT import AudioToTextRecorder
70
- import pyautogui
71
71
 
72
72
  def process_text(text):
73
73
  print(text)
@@ -5,6 +5,7 @@ RealtimeSTT/__init__.py
5
5
  RealtimeSTT/audio_input.py
6
6
  RealtimeSTT/audio_recorder.py
7
7
  RealtimeSTT/audio_recorder_client.py
8
+ RealtimeSTT/warmup_audio.wav
8
9
  RealtimeSTT.egg-info/PKG-INFO
9
10
  RealtimeSTT.egg-info/SOURCES.txt
10
11
  RealtimeSTT.egg-info/dependency_links.txt
@@ -1,12 +1,13 @@
1
1
  PyAudio==0.2.14
2
- faster-whisper==1.1.0
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.14.1
8
+ scipy==1.15.1
9
9
  openwakeword>=0.4.0
10
- websockets==14.1
10
+ websockets==14.2
11
11
  websocket-client==1.8.0
12
12
  numpy<2.0.0
13
+ soundfile==0.13.1
@@ -9,7 +9,7 @@ with open('requirements.txt') as f:
9
9
 
10
10
  setuptools.setup(
11
11
  name="RealtimeSTT",
12
- version="0.3.93",
12
+ version="0.3.95",
13
13
  author="Kolja Beigel",
14
14
  author_email="kolja.beigel@web.de",
15
15
  description="A fast Voice Activity Detection and Transcription System",
@@ -25,6 +25,8 @@ setuptools.setup(
25
25
  python_requires='>=3.6',
26
26
  install_requires=requirements,
27
27
  keywords="real-time, audio, transcription, speech-to-text, voice-activity-detection, VAD, real-time-transcription, ambient-noise-detection, microphone-input, faster_whisper, speech-recognition, voice-assistants, audio-processing, buffered-transcription, pyaudio, ambient-noise-level, voice-deactivity",
28
+ package_data={"RealtimeSTT": ["warmup_audio.wav"]},
29
+ include_package_data=True,
28
30
  entry_points={
29
31
  'console_scripts': [
30
32
  'stt-server=RealtimeSTT_server.stt_server:main',
File without changes
File without changes