RealTimeSTT 0.3.6__tar.gz → 0.3.8__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 (21) hide show
  1. {realtimestt-0.3.6 → RealtimeSTT-0.3.8}/PKG-INFO +4 -14
  2. {realtimestt-0.3.6 → RealtimeSTT-0.3.8}/README.md +3 -1
  3. RealtimeSTT-0.3.8/RealtimeSTT/__init__.py +3 -0
  4. RealtimeSTT-0.3.8/RealtimeSTT/audio_input.py +153 -0
  5. {realtimestt-0.3.6 → RealtimeSTT-0.3.8}/RealtimeSTT/audio_recorder_client.py +73 -72
  6. {realtimestt-0.3.6 → RealtimeSTT-0.3.8}/RealtimeSTT.egg-info/PKG-INFO +4 -14
  7. {realtimestt-0.3.6 → RealtimeSTT-0.3.8}/RealtimeSTT.egg-info/SOURCES.txt +1 -0
  8. {realtimestt-0.3.6 → RealtimeSTT-0.3.8}/RealtimeSTT.egg-info/requires.txt +1 -0
  9. {realtimestt-0.3.6 → RealtimeSTT-0.3.8}/RealtimeSTT_server/install_packages.py +0 -50
  10. RealtimeSTT-0.3.8/RealtimeSTT_server/stt_cli_client.py +283 -0
  11. {realtimestt-0.3.6 → RealtimeSTT-0.3.8}/RealtimeSTT_server/stt_server.py +132 -76
  12. {realtimestt-0.3.6 → RealtimeSTT-0.3.8}/setup.py +1 -1
  13. realtimestt-0.3.6/RealtimeSTT/__init__.py +0 -2
  14. realtimestt-0.3.6/RealtimeSTT_server/stt_cli_client.py +0 -631
  15. {realtimestt-0.3.6 → RealtimeSTT-0.3.8}/LICENSE +0 -0
  16. {realtimestt-0.3.6 → RealtimeSTT-0.3.8}/RealtimeSTT/audio_recorder.py +0 -0
  17. {realtimestt-0.3.6 → RealtimeSTT-0.3.8}/RealtimeSTT.egg-info/dependency_links.txt +0 -0
  18. {realtimestt-0.3.6 → RealtimeSTT-0.3.8}/RealtimeSTT.egg-info/entry_points.txt +0 -0
  19. {realtimestt-0.3.6 → RealtimeSTT-0.3.8}/RealtimeSTT.egg-info/top_level.txt +0 -0
  20. {realtimestt-0.3.6 → RealtimeSTT-0.3.8}/RealtimeSTT_server/__init__.py +0 -0
  21. {realtimestt-0.3.6 → RealtimeSTT-0.3.8}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: RealtimeSTT
3
- Version: 0.3.6
3
+ Version: 0.3.8
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
@@ -12,18 +12,6 @@ Classifier: Operating System :: OS Independent
12
12
  Requires-Python: >=3.6
13
13
  Description-Content-Type: text/markdown
14
14
  License-File: LICENSE
15
- Requires-Dist: PyAudio==0.2.14
16
- Requires-Dist: faster-whisper==1.0.3
17
- Requires-Dist: pvporcupine==1.9.5
18
- Requires-Dist: webrtcvad-wheels==2.0.14
19
- Requires-Dist: halo==0.0.31
20
- Requires-Dist: torch
21
- Requires-Dist: torchaudio
22
- Requires-Dist: scipy==1.14.1
23
- Requires-Dist: websockets==v12.0
24
- Requires-Dist: websocket-client==1.8.0
25
- Requires-Dist: openwakeword>=0.4.0
26
- Requires-Dist: numpy<2.0.0
27
15
 
28
16
  # RealtimeSTT
29
17
  [![PyPI](https://img.shields.io/pypi/v/RealtimeSTT)](https://pypi.org/project/RealtimeSTT/)
@@ -55,7 +43,7 @@ https://github.com/user-attachments/assets/797e6552-27cd-41b1-a7f3-e5cbc72094f5
55
43
 
56
44
  ### Updates
57
45
 
58
- Latest Version: v0.3.6
46
+ Latest Version: v0.3.8
59
47
 
60
48
  See [release history](https://github.com/KoljaB/RealtimeSTT/releases).
61
49
 
@@ -462,6 +450,8 @@ When you initialize the `AudioToTextRecorder` class, you have various options to
462
450
 
463
451
  - **level** (int, default=logging.WARNING): Logging level.
464
452
 
453
+ - **init_logging** (bool, default=True): Whether to initialize the logging framework. Set to False to manage this yourself.
454
+
465
455
  - **handle_buffer_overflow** (bool, default=True): If set, the system will log a warning when an input overflow occurs during recording and remove the data from the buffer.
466
456
 
467
457
  - **beam_size** (int, default=5): The beam size to use for beam search decoding.
@@ -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.6
31
+ Latest Version: v0.3.8
32
32
 
33
33
  See [release history](https://github.com/KoljaB/RealtimeSTT/releases).
34
34
 
@@ -435,6 +435,8 @@ When you initialize the `AudioToTextRecorder` class, you have various options to
435
435
 
436
436
  - **level** (int, default=logging.WARNING): Logging level.
437
437
 
438
+ - **init_logging** (bool, default=True): Whether to initialize the logging framework. Set to False to manage this yourself.
439
+
438
440
  - **handle_buffer_overflow** (bool, default=True): If set, the system will log a warning when an input overflow occurs during recording and remove the data from the buffer.
439
441
 
440
442
  - **beam_size** (int, default=5): The beam size to use for beam search decoding.
@@ -0,0 +1,3 @@
1
+ from .audio_recorder import AudioToTextRecorder
2
+ from .audio_recorder_client import AudioToTextRecorderClient
3
+ from .audio_input import AudioInput
@@ -0,0 +1,153 @@
1
+ from colorama import init, Fore, Style
2
+ import pyaudio
3
+ import logging
4
+ import time
5
+
6
+
7
+ class AudioInput:
8
+ def __init__(self, input_device_index=None, debug_mode=False):
9
+ self.input_device_index = input_device_index
10
+ self.debug_mode = debug_mode
11
+ self.audio_interface = None
12
+ self.stream = None
13
+ 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
20
+
21
+ def get_supported_sample_rates(self, device_index):
22
+ """Test which standard sample rates are supported by the specified device."""
23
+ standard_rates = [8000, 9600, 11025, 12000, 16000, 22050, 24000, 32000, 44100, 48000]
24
+ supported_rates = []
25
+
26
+ device_info = self.audio_interface.get_device_info_by_index(device_index)
27
+ max_channels = device_info.get('maxInputChannels') # Changed from maxOutputChannels
28
+
29
+ for rate in standard_rates:
30
+ try:
31
+ if self.audio_interface.is_format_supported(
32
+ rate,
33
+ 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
36
+ ):
37
+ supported_rates.append(rate)
38
+ except:
39
+ continue
40
+ return supported_rates
41
+
42
+ def _get_best_sample_rate(self, actual_device_index, desired_rate):
43
+ """Determines the best available sample rate for the device."""
44
+ try:
45
+ device_info = self.audio_interface.get_device_info_by_index(actual_device_index)
46
+ supported_rates = self.get_supported_sample_rates(actual_device_index)
47
+
48
+ if desired_rate in supported_rates:
49
+ return desired_rate
50
+
51
+ return max(supported_rates)
52
+
53
+ # lower_rates = [r for r in supported_rates if r <= desired_rate]
54
+ # if lower_rates:
55
+ # return max(lower_rates)
56
+
57
+ # higher_rates = [r for r in supported_rates if r > desired_rate]
58
+ # if higher_rates:
59
+ # return min(higher_rates)
60
+
61
+ return int(device_info.get('defaultSampleRate', 44100))
62
+
63
+ except Exception as e:
64
+ logging.warning(f"Error determining sample rate: {e}")
65
+ return 44100 # Safe fallback
66
+
67
+ def list_devices(self):
68
+ """List all available audio input devices with supported sample rates."""
69
+ try:
70
+ init() # Initialize colorama
71
+ self.audio_interface = pyaudio.PyAudio()
72
+ device_count = self.audio_interface.get_device_count()
73
+
74
+ print(f"Available audio input devices:")
75
+ #print(f"{Fore.LIGHTBLUE_EX}Available audio input devices:{Style.RESET_ALL}")
76
+ for i in range(device_count):
77
+ device_info = self.audio_interface.get_device_info_by_index(i)
78
+ device_name = device_info.get('name')
79
+ max_input_channels = device_info.get('maxInputChannels', 0)
80
+
81
+ if max_input_channels > 0: # Only consider devices with input capabilities
82
+ supported_rates = self.get_supported_sample_rates(i)
83
+ print(f"{Fore.LIGHTGREEN_EX}Device {Style.RESET_ALL}{i}{Fore.LIGHTGREEN_EX}: {device_name}{Style.RESET_ALL}")
84
+
85
+ # Format each rate in cyan
86
+ if supported_rates:
87
+ rates_formatted = ", ".join([f"{Fore.CYAN}{rate}{Style.RESET_ALL}" for rate in supported_rates])
88
+ print(f" {Fore.YELLOW}Supported sample rates: {rates_formatted}{Style.RESET_ALL}")
89
+ else:
90
+ print(f" {Fore.YELLOW}Supported sample rates: None{Style.RESET_ALL}")
91
+
92
+ except Exception as e:
93
+ print(f"Error listing devices: {e}")
94
+ finally:
95
+ if self.audio_interface:
96
+ self.audio_interface.terminate()
97
+
98
+ def setup(self):
99
+ """Initialize audio interface and open stream"""
100
+ try:
101
+ self.audio_interface = pyaudio.PyAudio()
102
+
103
+ if self.debug_mode:
104
+ print(f"Input device index: {self.input_device_index}")
105
+ actual_device_index = (self.input_device_index if self.input_device_index is not None
106
+ else self.audio_interface.get_default_input_device_info()['index'])
107
+
108
+ if self.debug_mode:
109
+ print(f"Actual selected device index: {actual_device_index}")
110
+ self.input_device_index = actual_device_index
111
+ self.device_sample_rate = self._get_best_sample_rate(actual_device_index, self.DESIRED_RATE)
112
+
113
+ if self.debug_mode:
114
+ print(f"Setting up audio on device {self.input_device_index} with sample rate {self.device_sample_rate}")
115
+
116
+ try:
117
+ self.stream = self.audio_interface.open(
118
+ format=self.FORMAT,
119
+ channels=self.CHANNELS,
120
+ rate=self.device_sample_rate,
121
+ input=True,
122
+ frames_per_buffer=self.CHUNK,
123
+ input_device_index=self.input_device_index,
124
+ )
125
+ if self.debug_mode:
126
+ print(f"Audio recording initialized successfully at {self.device_sample_rate} Hz")
127
+ return True
128
+ except Exception as e:
129
+ print(f"Failed to initialize audio stream at {self.device_sample_rate} Hz: {e}")
130
+ return False
131
+
132
+ except Exception as e:
133
+ print(f"Error initializing audio recording: {e}")
134
+ if self.audio_interface:
135
+ self.audio_interface.terminate()
136
+ return False
137
+
138
+ def read_chunk(self):
139
+ """Read a chunk of audio data"""
140
+ return self.stream.read(self.CHUNK, exception_on_overflow=False)
141
+
142
+ def cleanup(self):
143
+ """Clean up audio resources"""
144
+ try:
145
+ if self.stream:
146
+ self.stream.stop_stream()
147
+ self.stream.close()
148
+ self.stream = None
149
+ if self.audio_interface:
150
+ self.audio_interface.terminate()
151
+ self.audio_interface = None
152
+ except Exception as e:
153
+ print(f"Error cleaning up audio resources: {e}")
@@ -9,15 +9,18 @@ import websocket
9
9
  import threading
10
10
  import platform
11
11
  import logging
12
- import pyaudio
13
12
  import socket
14
13
  import struct
15
14
  import signal
15
+ import wave
16
16
  import json
17
17
  import time
18
18
  import sys
19
19
  import os
20
20
 
21
+ # Import the AudioInput class
22
+ from .audio_input import AudioInput
23
+
21
24
  DEFAULT_CONTROL_URL = "ws://127.0.0.1:8011"
22
25
  DEFAULT_DATA_URL = "ws://127.0.0.1:8012"
23
26
 
@@ -36,11 +39,8 @@ INIT_WAKE_WORD_TIMEOUT = 5.0
36
39
  INIT_WAKE_WORD_BUFFER_DURATION = 0.1
37
40
  ALLOWED_LATENCY_LIMIT = 100
38
41
 
39
- CHUNK = 1024
40
- FORMAT = pyaudio.paInt16
41
- CHANNELS = 1
42
- SAMPLE_RATE = 16000
43
42
  BUFFER_SIZE = 512
43
+ SAMPLE_RATE = 16000
44
44
 
45
45
  INIT_HANDLE_BUFFER_OVERFLOW = False
46
46
  if platform.system() != 'Darwin':
@@ -143,6 +143,7 @@ class AudioToTextRecorderClient:
143
143
  control_url: str = DEFAULT_CONTROL_URL,
144
144
  data_url: str = DEFAULT_DATA_URL,
145
145
  autostart_server: bool = True,
146
+ output_wav_file: str = None,
146
147
  ):
147
148
 
148
149
  # Set instance variables from constructor parameters
@@ -213,6 +214,7 @@ class AudioToTextRecorderClient:
213
214
  self.control_url = control_url
214
215
  self.data_url = data_url
215
216
  self.autostart_server = autostart_server
217
+ self.output_wav_file = output_wav_file
216
218
 
217
219
  # Instance variables
218
220
  self.muted = False
@@ -223,6 +225,9 @@ class AudioToTextRecorderClient:
223
225
  self.final_text_ready = threading.Event()
224
226
  self.realtime_text = ""
225
227
  self.final_text = ""
228
+ self._recording = False
229
+ self.server_already_running = False
230
+ self.wav_file = None
226
231
 
227
232
  self.request_counter = 0
228
233
  self.pending_requests = {} # Map from request_id to threading.Event and value
@@ -238,6 +243,13 @@ class AudioToTextRecorderClient:
238
243
  if self.use_microphone:
239
244
  self.start_recording()
240
245
 
246
+
247
+ if self.server_already_running:
248
+ self.set_parameter("language", self.language)
249
+ # self.set_parameter("model", self.model)
250
+
251
+
252
+
241
253
  def text(self, on_transcription_finished=None):
242
254
  self.realtime_text = ""
243
255
  self.submitted_realtime_text = ""
@@ -251,17 +263,15 @@ class AudioToTextRecorderClient:
251
263
  wait_interval = 0.02 # Wait in small intervals, e.g., 100ms
252
264
  max_wait_time = 60 # Timeout after 60 seconds
253
265
 
254
- while total_wait_time < max_wait_time:
266
+ while total_wait_time < max_wait_time and self.is_running and self._recording:
255
267
  if self.final_text_ready.wait(timeout=wait_interval):
256
268
  break # Break if transcription is ready
257
-
258
- # if not self.realtime_text == self.submitted_realtime_text:
259
- # if self.on_realtime_transcription_update:
260
- # self.on_realtime_transcription_update(self.realtime_text)
261
- # self.submitted_realtime_text = self.realtime_text
262
269
 
263
- total_wait_time += wait_interval
270
+ if not self.is_running or not self._recording:
271
+ break
264
272
 
273
+ total_wait_time += wait_interval
274
+
265
275
  # Check if a manual interrupt has occurred
266
276
  if total_wait_time >= max_wait_time:
267
277
  if self.debug_mode:
@@ -273,6 +283,9 @@ class AudioToTextRecorderClient:
273
283
 
274
284
  self.recording_start.clear()
275
285
 
286
+ if not self.is_running or not self._recording:
287
+ return ""
288
+
276
289
  if on_transcription_finished:
277
290
  threading.Thread(target=on_transcription_finished, args=(self.final_text,)).start()
278
291
 
@@ -280,7 +293,7 @@ class AudioToTextRecorderClient:
280
293
 
281
294
  except KeyboardInterrupt:
282
295
  if self.debug_mode:
283
- print("KeyboardInterrupt in record_and_send_audio, exiting...")
296
+ print("KeyboardInterrupt in text(), exiting...")
284
297
  raise KeyboardInterrupt
285
298
 
286
299
  except Exception as e:
@@ -318,7 +331,7 @@ class AudioToTextRecorderClient:
318
331
  if not self.ensure_server_running():
319
332
  print("Cannot start STT server. Exiting.")
320
333
  return False
321
-
334
+
322
335
  try:
323
336
  # Connect to control WebSocket
324
337
  self.control_ws = websocket.WebSocketApp(self.control_url,
@@ -438,7 +451,7 @@ class AudioToTextRecorderClient:
438
451
  if not self.is_server_running():
439
452
  if self.debug_mode:
440
453
  print("STT server is not running.", file=sys.stderr)
441
- if self.autostart_server or self.ask_to_start_server():
454
+ if self.autostart_server:
442
455
  self.start_server()
443
456
  if self.debug_mode:
444
457
  print("Waiting for STT server to start...", file=sys.stderr)
@@ -454,7 +467,16 @@ class AudioToTextRecorderClient:
454
467
  else:
455
468
  print("STT server is required. Please start it manually.", file=sys.stderr)
456
469
  return False
470
+
471
+ else:
472
+ self.server_already_running = True
473
+
457
474
  return True
475
+
476
+ def list_devices(self):
477
+ """List all available audio input devices."""
478
+ audio = AudioInput(debug_mode=self.debug_mode)
479
+ audio.list_devices()
458
480
 
459
481
  def start_recording(self):
460
482
  self.recording_thread = threading.Thread(target=self.record_and_send_audio)
@@ -462,45 +484,29 @@ class AudioToTextRecorderClient:
462
484
  self.recording_thread.start()
463
485
 
464
486
  def setup_audio(self):
465
- try:
466
- self.audio_interface = pyaudio.PyAudio()
467
- self.input_device_index = None
468
- try:
469
- default_device = self.audio_interface.get_default_input_device_info()
470
- self.input_device_index = default_device['index']
471
- except OSError as e:
472
- print(f"No default input device found: {e}")
473
- return False
474
-
475
- self.device_sample_rate = 16000 # Try 16000 Hz first
476
-
477
- try:
478
- self.stream = self.audio_interface.open(
479
- format=FORMAT,
480
- channels=CHANNELS,
481
- rate=self.device_sample_rate,
482
- input=True,
483
- frames_per_buffer=CHUNK,
484
- input_device_index=self.input_device_index,
485
- )
486
- if self.debug_mode:
487
- print(f"Audio recording initialized successfully at {self.device_sample_rate} Hz")
488
- return True
489
- except Exception as e:
490
- print(f"Failed to initialize audio stream at {self.device_sample_rate} Hz: {e}")
491
- return False
492
-
493
- except Exception as e:
494
- print(f"Error initializing audio recording: {e}")
495
- if self.audio_interface:
496
- self.audio_interface.terminate()
497
- return False
487
+ """Initialize audio input"""
488
+ self.audio_input = AudioInput(
489
+ input_device_index=self.input_device_index,
490
+ debug_mode=self.debug_mode
491
+ )
492
+ return self.audio_input.setup()
498
493
 
499
494
  def record_and_send_audio(self):
495
+ """Record and stream audio data"""
496
+ self._recording = True
497
+
500
498
  try:
501
499
  if not self.setup_audio():
502
500
  raise Exception("Failed to set up audio recording.")
503
501
 
502
+ # Initialize WAV file writer if output_wav_file is provided
503
+ if self.output_wav_file:
504
+ self.wav_file = wave.open(self.output_wav_file, 'wb')
505
+ self.wav_file.setnchannels(1)
506
+ self.wav_file.setsampwidth(2)
507
+ self.wav_file.setframerate(self.audio_input.device_sample_rate) # Use self.device_sample_rate
508
+
509
+
504
510
  if self.debug_mode:
505
511
  print("Recording and sending audio...")
506
512
 
@@ -510,7 +516,10 @@ class AudioToTextRecorderClient:
510
516
  continue
511
517
 
512
518
  try:
513
- audio_data = self.stream.read(CHUNK)
519
+ audio_data = self.audio_input.read_chunk()
520
+
521
+ if self.wav_file:
522
+ self.wav_file.writeframes(audio_data)
514
523
 
515
524
  if self.on_recorded_chunk:
516
525
  self.on_recorded_chunk(audio_data)
@@ -519,7 +528,7 @@ class AudioToTextRecorderClient:
519
528
  continue
520
529
 
521
530
  if self.recording_start.is_set():
522
- metadata = {"sampleRate": self.device_sample_rate}
531
+ metadata = {"sampleRate": self.audio_input.device_sample_rate}
523
532
  metadata_json = json.dumps(metadata)
524
533
  metadata_length = len(metadata_json)
525
534
  message = struct.pack('<I', metadata_length) + metadata_json.encode('utf-8') + audio_data
@@ -528,30 +537,26 @@ class AudioToTextRecorderClient:
528
537
  if log_outgoing_chunks:
529
538
  print(".", flush=True, end='')
530
539
  self.data_ws.send(message, opcode=websocket.ABNF.OPCODE_BINARY)
531
- except KeyboardInterrupt: # handle manual interruption (Ctrl+C)
540
+ except KeyboardInterrupt:
532
541
  if self.debug_mode:
533
542
  print("KeyboardInterrupt in record_and_send_audio, exiting...")
534
543
  break
535
544
  except Exception as e:
536
545
  print(f"Error sending audio data: {e}")
537
- break # Exit the recording loop
546
+ break
538
547
 
539
548
  except Exception as e:
540
- print(f"Error in record_and_send_audio: {e}")
549
+ print(f"Error in record_and_send_audio: {e}", file=sys.stderr)
541
550
  finally:
542
551
  self.cleanup_audio()
552
+ self.final_text_ready.set() # fake final text to stop the text() method
553
+ self.is_running = False
554
+ self._recording = False
543
555
 
544
556
  def cleanup_audio(self):
545
- try:
546
- if self.stream:
547
- self.stream.stop_stream()
548
- self.stream.close()
549
- self.stream = None
550
- if self.audio_interface:
551
- self.audio_interface.terminate()
552
- self.audio_interface = None
553
- except Exception as e:
554
- print(f"Error cleaning up audio resources: {e}")
557
+ """Clean up audio resources"""
558
+ if hasattr(self, 'audio_input'):
559
+ self.audio_input.cleanup()
555
560
 
556
561
  def on_control_message(self, ws, message):
557
562
  try:
@@ -585,7 +590,7 @@ class AudioToTextRecorderClient:
585
590
  self.realtime_text = data['text']
586
591
 
587
592
  timestamp = datetime.now().strftime('%H:%M:%S.%f')[:-3]
588
- print(f"Realtime text [{timestamp}]: {bcolors.OKCYAN}{self.realtime_text}{bcolors.ENDC}")
593
+ # print(f"Realtime text [{timestamp}]: {bcolors.OKCYAN}{self.realtime_text}{bcolors.ENDC}")
589
594
 
590
595
  if self.on_realtime_transcription_update:
591
596
  # Call the callback in a new thread to avoid blocking
@@ -704,14 +709,14 @@ class AudioToTextRecorderClient:
704
709
  self.control_ws.send(json.dumps(command))
705
710
 
706
711
  def shutdown(self):
712
+ """Shutdown all resources"""
707
713
  self.is_running = False
708
- #self.stop_event.set()
709
714
  if self.control_ws:
710
715
  self.control_ws.close()
711
716
  if self.data_ws:
712
717
  self.data_ws.close()
713
718
 
714
- # Join threads to ensure they finish before exiting
719
+ # Join threads
715
720
  if self.control_ws_thread:
716
721
  self.control_ws_thread.join()
717
722
  if self.data_ws_thread:
@@ -719,12 +724,8 @@ class AudioToTextRecorderClient:
719
724
  if self.recording_thread:
720
725
  self.recording_thread.join()
721
726
 
722
- # Clean up audio resources
723
- if self.stream:
724
- self.stream.stop_stream()
725
- self.stream.close()
726
- if self.audio_interface:
727
- self.audio_interface.terminate()
727
+ # Clean up audio
728
+ self.cleanup_audio()
728
729
 
729
730
  def __enter__(self):
730
731
  """
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: RealtimeSTT
3
- Version: 0.3.6
3
+ Version: 0.3.8
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
@@ -12,18 +12,6 @@ Classifier: Operating System :: OS Independent
12
12
  Requires-Python: >=3.6
13
13
  Description-Content-Type: text/markdown
14
14
  License-File: LICENSE
15
- Requires-Dist: PyAudio==0.2.14
16
- Requires-Dist: faster-whisper==1.0.3
17
- Requires-Dist: pvporcupine==1.9.5
18
- Requires-Dist: webrtcvad-wheels==2.0.14
19
- Requires-Dist: halo==0.0.31
20
- Requires-Dist: torch
21
- Requires-Dist: torchaudio
22
- Requires-Dist: scipy==1.14.1
23
- Requires-Dist: websockets==v12.0
24
- Requires-Dist: websocket-client==1.8.0
25
- Requires-Dist: openwakeword>=0.4.0
26
- Requires-Dist: numpy<2.0.0
27
15
 
28
16
  # RealtimeSTT
29
17
  [![PyPI](https://img.shields.io/pypi/v/RealtimeSTT)](https://pypi.org/project/RealtimeSTT/)
@@ -55,7 +43,7 @@ https://github.com/user-attachments/assets/797e6552-27cd-41b1-a7f3-e5cbc72094f5
55
43
 
56
44
  ### Updates
57
45
 
58
- Latest Version: v0.3.6
46
+ Latest Version: v0.3.8
59
47
 
60
48
  See [release history](https://github.com/KoljaB/RealtimeSTT/releases).
61
49
 
@@ -462,6 +450,8 @@ When you initialize the `AudioToTextRecorder` class, you have various options to
462
450
 
463
451
  - **level** (int, default=logging.WARNING): Logging level.
464
452
 
453
+ - **init_logging** (bool, default=True): Whether to initialize the logging framework. Set to False to manage this yourself.
454
+
465
455
  - **handle_buffer_overflow** (bool, default=True): If set, the system will log a warning when an input overflow occurs during recording and remove the data from the buffer.
466
456
 
467
457
  - **beam_size** (int, default=5): The beam size to use for beam search decoding.
@@ -2,6 +2,7 @@ LICENSE
2
2
  README.md
3
3
  setup.py
4
4
  RealtimeSTT/__init__.py
5
+ RealtimeSTT/audio_input.py
5
6
  RealtimeSTT/audio_recorder.py
6
7
  RealtimeSTT/audio_recorder_client.py
7
8
  RealtimeSTT.egg-info/PKG-INFO
@@ -6,6 +6,7 @@ halo==0.0.31
6
6
  torch
7
7
  torchaudio
8
8
  scipy==1.14.1
9
+ websocket==0.2.1
9
10
  websockets==v12.0
10
11
  websocket-client==1.8.0
11
12
  openwakeword>=0.4.0
@@ -53,53 +53,3 @@ def check_and_install_packages(packages):
53
53
  else:
54
54
  print(f"The program requires '{install_name}' to run. Exiting...")
55
55
  sys.exit(1)
56
-
57
-
58
- # import subprocess
59
- # import sys
60
-
61
- # def check_and_install_packages(packages):
62
- # """
63
- # Checks if the specified packages are installed, and if not, prompts the user
64
- # to install them.
65
-
66
- # Parameters:
67
- # - packages: A list of dictionaries, each containing:
68
- # - 'import_name': The name used in the import statement.
69
- # - 'install_name': (Optional) The name used in the pip install command.
70
- # Defaults to 'import_name' if not provided.
71
- # - 'version': (Optional) Version constraint for the package.
72
- # """
73
- # for package in packages:
74
- # import_name = package['import_name']
75
- # install_name = package.get('install_name', import_name)
76
- # version = package.get('version', '')
77
-
78
- # try:
79
- # print(f"import {import_name}")
80
- # __import__(import_name)
81
- # print(f"imported {import_name}")
82
- # except ImportError:
83
- # user_input = input(
84
- # f"This program requires the '{import_name}' library, which is not installed.\n"
85
- # f"Do you want to install it now? (y/n): "
86
- # )
87
- # if user_input.strip().lower() == 'y':
88
- # try:
89
- # # Build the pip install command
90
- # install_command = [sys.executable, "-m", "pip", "install"]
91
- # if version:
92
- # install_command.append(f"{install_name}{version}")
93
- # else:
94
- # install_command.append(install_name)
95
-
96
- # subprocess.check_call(install_command)
97
- # __import__(import_name)
98
- # print(f"Successfully installed '{install_name}'.")
99
- # except Exception as e:
100
- # print(f"An error occurred while installing '{install_name}': {e}")
101
- # sys.exit(1)
102
- # else:
103
- # print(f"The program requires the '{import_name}' library to run. Exiting...")
104
- # sys.exit(1)
105
-