RealTimeSTT 0.3.4__tar.gz → 0.3.6__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: RealtimeSTT
3
- Version: 0.3.4
3
+ Version: 0.3.6
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
@@ -55,7 +55,7 @@ https://github.com/user-attachments/assets/797e6552-27cd-41b1-a7f3-e5cbc72094f5
55
55
 
56
56
  ### Updates
57
57
 
58
- Latest Version: v0.3.4
58
+ Latest Version: v0.3.6
59
59
 
60
60
  See [release history](https://github.com/KoljaB/RealtimeSTT/releases).
61
61
 
@@ -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.4
31
+ Latest Version: v0.3.6
32
32
 
33
33
  See [release history](https://github.com/KoljaB/RealtimeSTT/releases).
34
34
 
@@ -63,6 +63,7 @@ os.environ['KMP_DUPLICATE_LIB_OK'] = 'TRUE'
63
63
  INIT_MODEL_TRANSCRIPTION = "tiny"
64
64
  INIT_MODEL_TRANSCRIPTION_REALTIME = "tiny"
65
65
  INIT_REALTIME_PROCESSING_PAUSE = 0.2
66
+ INIT_REALTIME_INITIAL_PAUSE = 0.2
66
67
  INIT_SILERO_SENSITIVITY = 0.4
67
68
  INIT_WEBRTC_SENSITIVITY = 3
68
69
  INIT_POST_SPEECH_SILENCE_DURATION = 0.6
@@ -179,6 +180,12 @@ class TranscriptionWorker:
179
180
  polling_thread.join() # Wait for the polling thread to finish
180
181
 
181
182
 
183
+ class bcolors:
184
+ OKGREEN = '\033[92m' # Green for active speech detection
185
+ WARNING = '\033[93m' # Yellow for silence detection
186
+ ENDC = '\033[0m' # Reset to default color
187
+
188
+
182
189
  class AudioToTextRecorder:
183
190
  """
184
191
  A class responsible for capturing audio from the microphone, detecting
@@ -207,6 +214,7 @@ class AudioToTextRecorder:
207
214
  use_main_model_for_realtime=False,
208
215
  realtime_model_type=INIT_MODEL_TRANSCRIPTION_REALTIME,
209
216
  realtime_processing_pause=INIT_REALTIME_PROCESSING_PAUSE,
217
+ init_realtime_after_seconds=INIT_REALTIME_INITIAL_PAUSE,
210
218
  on_realtime_transcription_update=None,
211
219
  on_realtime_transcription_stabilized=None,
212
220
 
@@ -326,6 +334,9 @@ class AudioToTextRecorder:
326
334
  interval in seconds after a chunk of audio gets transcribed. Lower
327
335
  values will result in more "real-time" (frequent) transcription
328
336
  updates but may increase computational load.
337
+ - init_realtime_after_seconds (float, default=0.2): Specifies the
338
+ initial waiting time after the recording was initiated before
339
+ yielding the first realtime transcription
329
340
  - on_realtime_transcription_update = A callback function that is
330
341
  triggered whenever there's an update in the real-time
331
342
  transcription. The function is called with the newly transcribed
@@ -499,6 +510,7 @@ class AudioToTextRecorder:
499
510
  self.main_model_type = model
500
511
  self.realtime_model_type = realtime_model_type
501
512
  self.realtime_processing_pause = realtime_processing_pause
513
+ self.init_realtime_after_seconds = init_realtime_after_seconds
502
514
  self.on_realtime_transcription_update = (
503
515
  on_realtime_transcription_update
504
516
  )
@@ -1619,8 +1631,8 @@ class AudioToTextRecorder:
1619
1631
  # Continuously monitor audio for voice activity
1620
1632
  while self.is_running:
1621
1633
 
1622
- if self.use_extended_logging:
1623
- logging.debug('Debug: Entering inner try block')
1634
+ # if self.use_extended_logging:
1635
+ # logging.debug('Debug: Entering inner try block')
1624
1636
  if last_inner_try_time:
1625
1637
  last_processing_time = time.time() - last_inner_try_time
1626
1638
  if last_processing_time > 0.1:
@@ -1628,20 +1640,20 @@ class AudioToTextRecorder:
1628
1640
  logging.warning('### WARNING: PROCESSING TOOK TOO LONG')
1629
1641
  last_inner_try_time = time.time()
1630
1642
  try:
1631
- if self.use_extended_logging:
1632
- logging.debug('Debug: Trying to get data from audio queue')
1643
+ # if self.use_extended_logging:
1644
+ # logging.debug('Debug: Trying to get data from audio queue')
1633
1645
  try:
1634
1646
  data = self.audio_queue.get(timeout=0.01)
1635
1647
  self.last_words_buffer.append(data)
1636
1648
  except queue.Empty:
1637
- if self.use_extended_logging:
1638
- logging.debug('Debug: Queue is empty, checking if still running')
1649
+ # if self.use_extended_logging:
1650
+ # logging.debug('Debug: Queue is empty, checking if still running')
1639
1651
  if not self.is_running:
1640
1652
  if self.use_extended_logging:
1641
1653
  logging.debug('Debug: Not running, breaking loop')
1642
1654
  break
1643
- if self.use_extended_logging:
1644
- logging.debug('Debug: Continuing to next iteration')
1655
+ # if self.use_extended_logging:
1656
+ # logging.debug('Debug: Continuing to next iteration')
1645
1657
  continue
1646
1658
 
1647
1659
  if self.use_extended_logging:
@@ -2072,7 +2084,7 @@ class AudioToTextRecorder:
2072
2084
  # double check recording state
2073
2085
  # because it could have changed mid-transcription
2074
2086
  if self.is_recording and time.time() - \
2075
- self.recording_start_time > 0.5:
2087
+ self.recording_start_time > self.init_realtime_after_seconds:
2076
2088
 
2077
2089
  # logging.debug('Starting realtime transcription')
2078
2090
  self.realtime_transcription_text = realtime_text
@@ -2179,7 +2191,11 @@ class AudioToTextRecorder:
2179
2191
  SAMPLE_RATE).item()
2180
2192
  is_silero_speech_active = vad_prob > (1 - self.silero_sensitivity)
2181
2193
  if is_silero_speech_active:
2182
- self.is_silero_speech_active = True
2194
+ if not self.is_silero_speech_active and self.use_extended_logging:
2195
+ logging.info(f"{bcolors.OKGREEN}Silero VAD detected speech{bcolors.ENDC}")
2196
+ elif self.is_silero_speech_active and self.use_extended_logging:
2197
+ logging.info(f"{bcolors.WARNING}Silero VAD detected silence{bcolors.ENDC}")
2198
+ self.is_silero_speech_active = is_silero_speech_active
2183
2199
  self.silero_working = False
2184
2200
  return is_silero_speech_active
2185
2201
 
@@ -2191,6 +2207,8 @@ class AudioToTextRecorder:
2191
2207
  data (bytes): raw bytes of audio data (1024 raw bytes with
2192
2208
  16000 sample rate and 16 bits per sample)
2193
2209
  """
2210
+ speech_str = f"{bcolors.OKGREEN}WebRTC VAD detected speech{bcolors.ENDC}"
2211
+ silence_str = f"{bcolors.WARNING}WebRTC VAD detected silence{bcolors.ENDC}"
2194
2212
  if self.sample_rate != 16000:
2195
2213
  pcm_data = np.frombuffer(chunk, dtype=np.int16)
2196
2214
  data_16000 = signal.resample_poly(
@@ -2212,6 +2230,9 @@ class AudioToTextRecorder:
2212
2230
  if self.debug_mode:
2213
2231
  logging.info(f"Speech detected in frame {i + 1}"
2214
2232
  f" of {num_frames}")
2233
+ if not self.is_webrtc_speech_active and self.use_extended_logging:
2234
+ logging.info(speech_str)
2235
+ self.is_webrtc_speech_active = True
2215
2236
  return True
2216
2237
  if all_frames_must_be_true:
2217
2238
  if self.debug_mode and speech_frames == num_frames:
@@ -2219,10 +2240,19 @@ class AudioToTextRecorder:
2219
2240
  f"{num_frames} frames")
2220
2241
  elif self.debug_mode:
2221
2242
  logging.info(f"Speech not detected in all {num_frames} frames")
2222
- return speech_frames == num_frames
2243
+ speech_detected = speech_frames == num_frames
2244
+ if speech_detected and not self.is_webrtc_speech_active and self.use_extended_logging:
2245
+ logging.info(speech_str)
2246
+ elif not speech_detected and self.is_webrtc_speech_active and self.use_extended_logging:
2247
+ logging.info(silence_str)
2248
+ self.is_webrtc_speech_active = speech_detected
2249
+ return speech_detected
2223
2250
  else:
2224
2251
  if self.debug_mode:
2225
2252
  logging.info(f"Speech not detected in any of {num_frames} frames")
2253
+ if self.is_webrtc_speech_active and self.use_extended_logging:
2254
+ logging.info(silence_str)
2255
+ self.is_webrtc_speech_active = False
2226
2256
  return False
2227
2257
 
2228
2258
  def _check_voice_activity(self, data):
@@ -2232,7 +2262,7 @@ class AudioToTextRecorder:
2232
2262
  Args:
2233
2263
  data: The audio data to be checked for voice activity.
2234
2264
  """
2235
- self.is_webrtc_speech_active = self._is_webrtc_speech(data)
2265
+ self._is_webrtc_speech(data)
2236
2266
 
2237
2267
  # First quick performing check for voice activity using WebRTC
2238
2268
  if self.is_webrtc_speech_active:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: RealtimeSTT
3
- Version: 0.3.4
3
+ Version: 0.3.6
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
@@ -55,7 +55,7 @@ https://github.com/user-attachments/assets/797e6552-27cd-41b1-a7f3-e5cbc72094f5
55
55
 
56
56
  ### Updates
57
57
 
58
- Latest Version: v0.3.4
58
+ Latest Version: v0.3.6
59
59
 
60
60
  See [release history](https://github.com/KoljaB/RealtimeSTT/releases).
61
61
 
@@ -10,7 +10,7 @@ RealtimeSTT.egg-info/dependency_links.txt
10
10
  RealtimeSTT.egg-info/entry_points.txt
11
11
  RealtimeSTT.egg-info/requires.txt
12
12
  RealtimeSTT.egg-info/top_level.txt
13
- server/__init__.py
14
- server/install_packages.py
15
- server/stt_cli_client.py
16
- server/stt_server.py
13
+ RealtimeSTT_server/__init__.py
14
+ RealtimeSTT_server/install_packages.py
15
+ RealtimeSTT_server/stt_cli_client.py
16
+ RealtimeSTT_server/stt_server.py
@@ -0,0 +1,3 @@
1
+ [console_scripts]
2
+ stt = RealtimeSTT_server.stt_cli_client:main
3
+ stt-server = RealtimeSTT_server.stt_server:main
@@ -0,0 +1,2 @@
1
+ RealtimeSTT
2
+ RealtimeSTT_server
@@ -1,90 +1,39 @@
1
1
  """
2
- Speech-to-Text (STT) Client CLI for WebSocket Server Interaction
3
-
4
- This command-line interface (CLI) allows interaction with the Speech-to-Text (STT) WebSocket server. It connects to the server via control and data WebSocket URLs to facilitate real-time speech transcription, control the server, and manage various parameters related to the STT process.
5
-
6
- The client can be used to start recording audio, set or retrieve STT parameters, and interact with the server using commands. Additionally, the CLI can disable real-time updates or run in debug mode for detailed output.
7
-
8
- ### Features:
9
- - Connects to STT WebSocket server for real-time transcription and control.
10
- - Supports setting and retrieving parameters via the command line.
11
- - Allows calling server methods (e.g., start/stop recording).
12
- - Option to disable real-time updates during transcription.
13
- - Debug mode available for verbose logging.
14
-
15
- ### Starting the Client:
16
- You can start the client using the command `stt` and optionally pass configuration options or commands for interacting with the server.
17
-
18
- ```bash
19
- stt [OPTIONS]
20
- ```
21
-
22
- ### Available Parameters:
23
- - `--control-url` (default: "ws://localhost:8011"): The WebSocket URL for server control commands.
24
-
25
- - `--data-url` (default: "ws://localhost:8012"): The WebSocket URL for sending audio data and receiving transcription updates.
26
-
27
- - `--debug`: Enable debug mode, which prints detailed logs to stderr.
28
-
29
- - `--nort` or `--norealtime`: Disable real-time output of transcription results.
30
-
31
- - `--set-param PARAM VALUE`: Set a recorder parameter (e.g., silero_sensitivity, beam_size, etc.). This option can be used multiple times to set different parameters.
32
-
33
- - `--get-param PARAM`: Retrieve the value of a specific recorder parameter. This option can be used multiple times to retrieve different parameters.
34
-
35
- - `--call-method METHOD [ARGS]`: Call a method on the recorder with optional arguments. This option can be used multiple times for different methods.
36
-
37
- ### Example Usage:
38
- 1. **Start the client with default settings:**
39
- ```bash
40
- stt
41
- ```
42
-
43
- 2. **Set a recorder parameter (e.g., set Silero sensitivity to 0.1):**
44
- ```bash
45
- stt --set-param silero_sensitivity 0.1
46
- ```
47
-
48
- 3. **Retrieve the value of a recorder parameter (e.g., get the current Silero sensitivity):**
49
- ```bash
50
- stt --get-param silero_sensitivity
51
- ```
52
-
53
- 4. **Call a method on the recorder (e.g., start the microphone input):**
54
- ```bash
55
- stt --call-method set_microphone
56
- ```
57
-
58
- 5. **Run in debug mode:**
59
- ```bash
60
- stt --debug
61
- ```
62
-
63
- ### WebSocket Interface:
64
- - **Control WebSocket**: Used for sending control commands like setting parameters or invoking methods.
65
- - **Data WebSocket**: Used for sending audio data for real-time transcription and receiving transcription results.
66
-
67
- The client can be used to send audio data to the server for transcription and to control the behavior of the server remotely.
2
+ This is a command-line client for the Speech-to-Text (STT) server.
3
+ It records audio from the default input device and sends it to the server for speech recognition.
4
+ It can also process commands to set parameters, get parameter values, or call methods on the server.
5
+
6
+ Usage:
7
+ stt [--control-url CONTROL_URL] [--data-url DATA_URL] [--debug] [--norealtime] [--set-param PARAM VALUE] [--call-method METHOD [ARGS ...]] [--get-param PARAM]
8
+
9
+ Options:
10
+ --control-url CONTROL_URL STT Control WebSocket URL
11
+ --data-url DATA_URL STT Data WebSocket URL
12
+ --debug Enable debug mode
13
+ --norealtime Disable real-time output
14
+ --set-param PARAM VALUE Set a recorder parameter. Can be used multiple times.
15
+ --call-method METHOD [ARGS ...] Call a recorder method with optional arguments.
16
+ --get-param PARAM Get the value of a recorder parameter. Can be used multiple times.
68
17
  """
69
18
 
70
- import os
71
- import sys
72
- import pyaudio
73
- import numpy as np
19
+ from urllib.parse import urlparse
74
20
  from scipy import signal
75
- import logging
21
+ from queue import Queue
22
+ import numpy as np
23
+ import subprocess
24
+ import threading
76
25
  import websocket
77
26
  import argparse
78
- import json
79
- import threading
80
- import time
27
+ import pyaudio
81
28
  import struct
82
29
  import socket
83
- import subprocess
84
30
  import shutil
85
- from urllib.parse import urlparse
86
31
  import queue
87
- from queue import Queue
32
+ import json
33
+ import time
34
+ import wave
35
+ import sys
36
+ import os
88
37
 
89
38
  os.environ['ALSA_LOG_LEVEL'] = 'none'
90
39
 
@@ -104,12 +53,13 @@ init()
104
53
  websocket.enableTrace(False)
105
54
 
106
55
  class STTWebSocketClient:
107
- def __init__(self, control_url, data_url, debug=False, file_output=None, norealtime=False):
56
+ def __init__(self, control_url, data_url, debug=False, file_output=None, norealtime=False, writechunks=None):
108
57
  self.control_url = control_url
109
58
  self.data_url = data_url
110
59
  self.control_ws = None
111
- self.data_ws = None
112
- self.is_running = False
60
+ self.data_ws_app = None
61
+ self.data_ws_connected = None # WebSocket object that will be used for sending
62
+ self.is_running = True
113
63
  self.debug = debug
114
64
  self.file_output = file_output
115
65
  self.last_text = ""
@@ -120,6 +70,16 @@ class STTWebSocketClient:
120
70
  self.message_queue = Queue()
121
71
  self.commands = Queue()
122
72
  self.stop_event = threading.Event()
73
+ self.chunks_sent = 0
74
+ self.last_chunk_time = time.time()
75
+ self.writechunks = writechunks # Add this to store the file name for writing audio chunks
76
+
77
+ self.debug_print("Initializing STT WebSocket Client")
78
+ self.debug_print(f"Control URL: {control_url}")
79
+ self.debug_print(f"Data URL: {data_url}")
80
+ self.debug_print(f"File Output: {file_output}")
81
+ self.debug_print(f"No Realtime: {norealtime}")
82
+ self.debug_print(f"Write Chunks: {writechunks}")
123
83
 
124
84
  # Audio attributes
125
85
  self.audio_interface = None
@@ -132,9 +92,12 @@ class STTWebSocketClient:
132
92
  self.data_ws_thread = None
133
93
  self.recording_thread = None
134
94
 
95
+
135
96
  def debug_print(self, message):
136
97
  if self.debug:
137
- print(message, file=sys.stderr)
98
+ timestamp = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
99
+ thread_name = threading.current_thread().name
100
+ print(f"{Fore.CYAN}[DEBUG][{timestamp}][{thread_name}] {message}{Style.RESET_ALL}", file=sys.stderr)
138
101
 
139
102
  def connect(self):
140
103
  if not self.ensure_server_running():
@@ -142,7 +105,10 @@ class STTWebSocketClient:
142
105
  return False
143
106
 
144
107
  try:
108
+ self.debug_print("Attempting to establish WebSocket connections...")
109
+
145
110
  # Connect to control WebSocket
111
+ self.debug_print(f"Connecting to control WebSocket at {self.control_url}")
146
112
  self.control_ws = websocket.WebSocketApp(self.control_url,
147
113
  on_message=self.on_control_message,
148
114
  on_error=self.on_error,
@@ -150,21 +116,24 @@ class STTWebSocketClient:
150
116
  on_open=self.on_control_open)
151
117
 
152
118
  self.control_ws_thread = threading.Thread(target=self.control_ws.run_forever)
153
- self.control_ws_thread.daemon = False # Set to False to ensure proper shutdown
119
+ self.control_ws_thread.daemon = False
120
+ self.debug_print("Starting control WebSocket thread")
154
121
  self.control_ws_thread.start()
155
122
 
156
123
  # Connect to data WebSocket
157
- self.data_ws = websocket.WebSocketApp(self.data_url,
158
- on_message=self.on_data_message,
159
- on_error=self.on_error,
160
- on_close=self.on_close,
161
- on_open=self.on_data_open)
162
-
163
- self.data_ws_thread = threading.Thread(target=self.data_ws.run_forever)
164
- self.data_ws_thread.daemon = False # Set to False to ensure proper shutdown
124
+ self.debug_print(f"Connecting to data WebSocket at {self.data_url}")
125
+ self.data_ws_app = websocket.WebSocketApp(self.data_url,
126
+ on_message=self.on_data_message,
127
+ on_error=self.on_error,
128
+ on_close=self.on_close,
129
+ on_open=self.on_data_open)
130
+
131
+ self.data_ws_thread = threading.Thread(target=self.data_ws_app.run_forever)
132
+ self.data_ws_thread.daemon = False
133
+ self.debug_print("Starting data WebSocket thread")
165
134
  self.data_ws_thread.start()
166
135
 
167
- # Wait for the connections to be established
136
+ self.debug_print("Waiting for connections to be established...")
168
137
  if not self.connection_established.wait(timeout=10):
169
138
  self.debug_print("Timeout while connecting to the server.")
170
139
  return False
@@ -172,24 +141,30 @@ class STTWebSocketClient:
172
141
  self.debug_print("WebSocket connections established successfully.")
173
142
  return True
174
143
  except Exception as e:
175
- self.debug_print(f"Error while connecting to the server: {e}")
144
+ self.debug_print(f"Error while connecting to the server: {str(e)}")
176
145
  return False
177
146
 
147
+
178
148
  def on_control_open(self, ws):
179
- self.debug_print("Control WebSocket connection opened.")
149
+ self.debug_print("Control WebSocket connection opened successfully")
180
150
  self.connection_established.set()
181
151
  self.start_command_processor()
182
152
 
183
153
  def on_data_open(self, ws):
184
- self.debug_print("Data WebSocket connection opened.")
185
- self.is_running = True
154
+ self.debug_print("Data WebSocket connection opened successfully")
155
+ self.data_ws_connected = ws
186
156
  self.start_recording()
187
157
 
188
158
  def on_error(self, ws, error):
189
- self.debug_print(f"WebSocket error: {error}")
159
+ self.debug_print(f"WebSocket error occurred: {str(error)}")
160
+ self.debug_print(f"WebSocket object: {ws}")
161
+ self.debug_print(f"Error type: {type(error)}")
190
162
 
191
163
  def on_close(self, ws, close_status_code, close_msg):
192
- self.debug_print(f"WebSocket connection closed: {close_status_code} - {close_msg}")
164
+ self.debug_print(f"WebSocket connection closed")
165
+ self.debug_print(f"Close status code: {close_status_code}")
166
+ self.debug_print(f"Close message: {close_msg}")
167
+ self.debug_print(f"WebSocket object: {ws}")
193
168
  self.is_running = False
194
169
  self.stop_event.set()
195
170
 
@@ -197,8 +172,11 @@ class STTWebSocketClient:
197
172
  parsed_url = urlparse(self.control_url)
198
173
  host = parsed_url.hostname
199
174
  port = parsed_url.port or 80
175
+ self.debug_print(f"Checking if server is running at {host}:{port}")
200
176
  with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
201
- return s.connect_ex((host, port)) == 0
177
+ result = s.connect_ex((host, port)) == 0
178
+ self.debug_print(f"Server status check result: {'running' if result else 'not running'}")
179
+ return result
202
180
 
203
181
  def ask_to_start_server(self):
204
182
  response = input("Would you like to start the STT server now? (y/n): ").strip().lower()
@@ -208,8 +186,57 @@ class STTWebSocketClient:
208
186
  if os.name == 'nt': # Windows
209
187
  subprocess.Popen('start /min cmd /c stt-server', shell=True)
210
188
  else: # Unix-like systems
211
- subprocess.Popen(['stt-server'], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, start_new_session=True)
212
- print("STT server start command issued. Please wait a moment for it to initialize.", file=sys.stderr)
189
+ terminal_emulators = [
190
+ 'gnome-terminal',
191
+ 'x-terminal-emulator',
192
+ 'konsole',
193
+ 'xfce4-terminal',
194
+ 'lxterminal',
195
+ 'xterm',
196
+ 'mate-terminal',
197
+ 'terminator',
198
+ 'tilix',
199
+ 'alacritty',
200
+ 'urxvt',
201
+ 'eterm',
202
+ 'rxvt',
203
+ 'kitty',
204
+ 'hyper'
205
+ ]
206
+
207
+ terminal = None
208
+ for term in terminal_emulators:
209
+ if shutil.which(term):
210
+ terminal = term
211
+ break
212
+
213
+ if terminal:
214
+ terminal_exec_options = {
215
+ 'x-terminal-emulator': ['--'],
216
+ 'gnome-terminal': ['--'],
217
+ 'mate-terminal': ['--'],
218
+ 'terminator': ['--'],
219
+ 'tilix': ['--'],
220
+ 'konsole': ['-e'],
221
+ 'xfce4-terminal': ['-e'],
222
+ 'lxterminal': ['-e'],
223
+ 'alacritty': ['-e'],
224
+ 'xterm': ['-e'],
225
+ 'rxvt': ['-e'],
226
+ 'urxvt': ['-e'],
227
+ 'eterm': ['-e'],
228
+ 'kitty': [],
229
+ 'hyper': ['--command']
230
+ }
231
+
232
+ exec_option = terminal_exec_options.get(terminal, None)
233
+ if exec_option is not None:
234
+ subprocess.Popen([terminal] + exec_option + ['stt-server'], start_new_session=True)
235
+ print(f"STT server started in a new terminal window using {terminal}.", file=sys.stderr)
236
+ else:
237
+ print(f"Unsupported terminal emulator '{terminal}'. Please start the STT server manually.", file=sys.stderr)
238
+ else:
239
+ print("No supported terminal emulator found. Please start the STT server manually.", file=sys.stderr)
213
240
 
214
241
  def ensure_server_running(self):
215
242
  if not self.is_server_running():
@@ -230,57 +257,70 @@ class STTWebSocketClient:
230
257
  return False
231
258
  return True
232
259
 
233
- # Handle control messages like set_parameter, get_parameter, etc.
234
260
  def on_control_message(self, ws, message):
235
261
  try:
262
+ self.debug_print(f"Received control message: {message}")
236
263
  data = json.loads(message)
237
- # Handle server response with status
238
264
  if 'status' in data:
265
+ self.debug_print(f"Message status: {data['status']}")
239
266
  if data['status'] == 'success':
240
- # print(f"Server Response: {data.get('message', '')}")
241
267
  if 'parameter' in data and 'value' in data:
268
+ self.debug_print(f"Parameter update: {data['parameter']} = {data['value']}")
242
269
  print(f"Parameter {data['parameter']} = {data['value']}")
243
270
  elif data['status'] == 'error':
271
+ self.debug_print(f"Server error received: {data.get('message', '')}")
244
272
  print(f"Server Error: {data.get('message', '')}")
245
273
  else:
246
274
  self.debug_print(f"Unknown control message format: {data}")
247
275
  except json.JSONDecodeError:
248
- self.debug_print(f"Received non-JSON control message: {message}")
276
+ self.debug_print(f"Failed to decode JSON control message: {message}")
249
277
  except Exception as e:
250
- self.debug_print(f"Error processing control message: {e}")
278
+ self.debug_print(f"Error processing control message: {str(e)}")
251
279
 
252
- # Handle real-time transcription and full sentence updates
253
280
  def on_data_message(self, ws, message):
254
281
  try:
282
+ self.debug_print(f"Received data message: {message}")
255
283
  data = json.loads(message)
256
- # Handle real-time transcription updates
257
- if data.get('type') == 'realtime':
284
+ message_type = data.get('type')
285
+ self.debug_print(f"Message type: {message_type}")
286
+
287
+ if message_type == 'realtime':
258
288
  if data['text'] != self.last_text:
289
+ self.debug_print(f"New realtime text received: {data['text']}")
259
290
  self.last_text = data['text']
260
291
  if not self.norealtime:
261
292
  self.update_progress_bar(self.last_text)
262
-
263
- # Handle full sentences
264
- elif data.get('type') == 'fullSentence':
293
+ elif message_type == 'fullSentence':
294
+ self.debug_print(f"Full sentence received: {data['text']}")
265
295
  if self.file_output:
296
+ self.debug_print("Writing to file output")
266
297
  sys.stderr.write('\r\033[K')
267
298
  sys.stderr.write(data['text'])
268
299
  sys.stderr.write('\n')
269
300
  sys.stderr.flush()
270
301
  print(data['text'], file=self.file_output)
271
- self.file_output.flush() # Ensure it's written immediately
302
+ self.file_output.flush()
272
303
  else:
273
304
  self.finish_progress_bar()
274
305
  print(f"{data['text']}")
275
306
  self.stop()
276
-
307
+ elif message_type in {
308
+ 'vad_detect_start',
309
+ 'vad_detect_stop',
310
+ 'recording_start',
311
+ 'recording_stop',
312
+ 'wakeword_detected',
313
+ 'wakeword_detection_start',
314
+ 'wakeword_detection_end',
315
+ 'transcription_start'}:
316
+ pass # Known message types, no action needed
277
317
  else:
278
- self.debug_print(f"Unknown data message format: {data}")
318
+ self.debug_print(f"Other message type received: {message_type}")
279
319
 
280
320
  except json.JSONDecodeError:
281
- self.debug_print(f"Received non-JSON data message: {message}")
321
+ self.debug_print(f"Failed to decode JSON data message: {message}")
282
322
  except Exception as e:
283
- self.debug_print(f"Error processing data message: {e}")
323
+ self.debug_print(f"Error processing data message: {str(e)}")
284
324
 
285
325
  def show_initial_indicator(self):
286
326
  if self.norealtime:
@@ -317,10 +357,11 @@ class STTWebSocketClient:
317
357
  self.finish_progress_bar()
318
358
  self.is_running = False
319
359
  self.stop_event.set()
360
+ self.debug_print("Stopping client and cleaning up resources.")
320
361
  if self.control_ws:
321
362
  self.control_ws.close()
322
- if self.data_ws:
323
- self.data_ws.close()
363
+ if self.data_ws_connected:
364
+ self.data_ws_connected.close()
324
365
 
325
366
  # Join threads to ensure they finish before exiting
326
367
  if self.control_ws_thread:
@@ -345,40 +386,67 @@ class STTWebSocketClient:
345
386
  def record_and_send_audio(self):
346
387
  try:
347
388
  if not self.setup_audio():
389
+ self.debug_print("Failed to set up audio recording")
348
390
  raise Exception("Failed to set up audio recording.")
349
391
 
350
- self.debug_print("Recording and sending audio...")
392
+ # Initialize WAV file writer if writechunks is provided
393
+ if self.writechunks:
394
+ self.wav_file = wave.open(self.writechunks, 'wb')
395
+ self.wav_file.setnchannels(CHANNELS)
396
+ self.wav_file.setsampwidth(pyaudio.get_sample_size(FORMAT))
397
+ self.wav_file.setframerate(self.device_sample_rate) # Use self.device_sample_rate
398
+
399
+ self.debug_print("Starting audio recording and transmission")
351
400
  self.show_initial_indicator()
352
401
 
353
402
  while self.is_running:
354
403
  try:
355
404
  audio_data = self.stream.read(CHUNK)
405
+ self.chunks_sent += 1
406
+ current_time = time.time()
407
+ elapsed = current_time - self.last_chunk_time
408
+
409
+ # Write to WAV file if enabled
410
+ if self.writechunks:
411
+ self.wav_file.writeframes(audio_data)
412
+
413
+ if self.chunks_sent % 100 == 0: # Log every 100 chunks
414
+ self.debug_print(f"Sent {self.chunks_sent} chunks. Last chunk took {elapsed:.3f}s")
415
+
356
416
  metadata = {"sampleRate": self.device_sample_rate}
357
417
  metadata_json = json.dumps(metadata)
358
418
  metadata_length = len(metadata_json)
359
419
  message = struct.pack('<I', metadata_length) + metadata_json.encode('utf-8') + audio_data
360
- self.data_ws.send(message, opcode=websocket.ABNF.OPCODE_BINARY)
420
+
421
+ self.debug_print(f"Sending audio chunk {self.chunks_sent}: {len(audio_data)} bytes, metadata: {metadata_json}")
422
+ self.data_ws_connected.send(message, opcode=websocket.ABNF.OPCODE_BINARY)
423
+ self.last_chunk_time = current_time
424
+
361
425
  except Exception as e:
362
- self.debug_print(f"Error sending audio data: {e}")
363
- break # Exit the recording loop
426
+ self.debug_print(f"Error sending audio data: {str(e)}")
427
+ break
364
428
 
365
429
  except Exception as e:
366
- self.debug_print(f"Error in record_and_send_audio: {e}")
430
+ self.debug_print(f"Error in record_and_send_audio: {str(e)}")
367
431
  finally:
368
432
  self.cleanup_audio()
369
433
 
370
434
  def setup_audio(self):
371
435
  try:
436
+ self.debug_print("Initializing PyAudio interface")
372
437
  self.audio_interface = pyaudio.PyAudio()
373
438
  self.input_device_index = None
439
+
374
440
  try:
375
441
  default_device = self.audio_interface.get_default_input_device_info()
376
442
  self.input_device_index = default_device['index']
443
+ self.debug_print(f"Default input device found: {default_device}")
377
444
  except OSError as e:
378
- self.debug_print(f"No default input device found: {e}")
445
+ self.debug_print(f"No default input device found: {str(e)}")
379
446
  return False
380
447
 
381
- self.device_sample_rate = 16000 # Try 16000 Hz first
448
+ self.device_sample_rate = 16000
449
+ self.debug_print(f"Attempting to open audio stream with sample rate {self.device_sample_rate} Hz")
382
450
 
383
451
  try:
384
452
  self.stream = self.audio_interface.open(
@@ -389,29 +457,36 @@ class STTWebSocketClient:
389
457
  frames_per_buffer=CHUNK,
390
458
  input_device_index=self.input_device_index,
391
459
  )
392
- self.debug_print(f"Audio recording initialized successfully at {self.device_sample_rate} Hz")
460
+ self.debug_print(f"Audio stream initialized successfully")
461
+ self.debug_print(f"Audio parameters: rate={self.device_sample_rate}, channels={CHANNELS}, format={FORMAT}, chunk={CHUNK}")
393
462
  return True
394
463
  except Exception as e:
395
- self.debug_print(f"Failed to initialize audio stream at {self.device_sample_rate} Hz: {e}")
464
+ self.debug_print(f"Failed to initialize audio stream: {str(e)}")
396
465
  return False
397
466
 
398
467
  except Exception as e:
399
- self.debug_print(f"Error initializing audio recording: {e}")
468
+ self.debug_print(f"Error in setup_audio: {str(e)}")
400
469
  if self.audio_interface:
401
470
  self.audio_interface.terminate()
402
471
  return False
403
472
 
404
473
  def cleanup_audio(self):
474
+ self.debug_print("Cleaning up audio resources")
405
475
  try:
406
476
  if self.stream:
477
+ self.debug_print("Stopping and closing audio stream")
407
478
  self.stream.stop_stream()
408
479
  self.stream.close()
409
480
  self.stream = None
410
481
  if self.audio_interface:
482
+ self.debug_print("Terminating PyAudio interface")
411
483
  self.audio_interface.terminate()
412
484
  self.audio_interface = None
485
+ if self.writechunks and self.wav_file:
486
+ self.debug_print("Closing WAV file")
487
+ self.wav_file.close()
413
488
  except Exception as e:
414
- self.debug_print(f"Error cleaning up audio resources: {e}")
489
+ self.debug_print(f"Error during audio cleanup: {str(e)}")
415
490
 
416
491
  def set_parameter(self, parameter, value):
417
492
  command = {
@@ -442,27 +517,28 @@ class STTWebSocketClient:
442
517
  self.command_thread.daemon = False # Ensure it is not a daemon thread
443
518
  self.command_thread.start()
444
519
 
520
+
445
521
  def command_processor(self):
446
- # print(f"Starting command processor")
447
- self.debug_print(f"Starting command processor")
448
- #while self.is_running and not self.stop_event.is_set():
522
+ self.debug_print("Starting command processor thread")
449
523
  while not self.stop_event.is_set():
450
524
  try:
451
525
  command = self.commands.get(timeout=0.1)
526
+ self.debug_print(f"Processing command: {command}")
452
527
  if command['type'] == 'set_parameter':
528
+ self.debug_print(f"Setting parameter: {command['parameter']} = {command['value']}")
453
529
  self.set_parameter(command['parameter'], command['value'])
454
530
  elif command['type'] == 'get_parameter':
531
+ self.debug_print(f"Getting parameter: {command['parameter']}")
455
532
  self.get_parameter(command['parameter'])
456
533
  elif command['type'] == 'call_method':
534
+ self.debug_print(f"Calling method: {command['method']} with args: {command.get('args')} and kwargs: {command.get('kwargs')}")
457
535
  self.call_method(command['method'], command.get('args'), command.get('kwargs'))
458
- except queue.Empty: # Use queue.Empty instead of Queue.Empty
459
- continue # Queue was empty, just loop again
536
+ except queue.Empty:
537
+ continue
460
538
  except Exception as e:
461
- self.debug_print(f"Error in command processor: {e}")
462
- # finally:
463
- #print(f"Leaving command processor")
464
- self.debug_print(f"Leaving command processor")
539
+ self.debug_print(f"Error in command processor: {str(e)}")
465
540
 
541
+ self.debug_print("Command processor thread stopping")
466
542
 
467
543
  def add_command(self, command):
468
544
  self.commands.put(command)
@@ -473,6 +549,7 @@ def main():
473
549
  parser.add_argument("--data-url", default=DEFAULT_DATA_URL, help="STT Data WebSocket URL")
474
550
  parser.add_argument("--debug", action="store_true", help="Enable debug mode")
475
551
  parser.add_argument("-nort", "--norealtime", action="store_true", help="Disable real-time output")
552
+ parser.add_argument("--writechunks", metavar="FILE", help="Save recorded audio chunks to a WAV file")
476
553
  parser.add_argument("--set-param", nargs=2, metavar=('PARAM', 'VALUE'), action='append',
477
554
  help="Set a recorder parameter. Can be used multiple times.")
478
555
  parser.add_argument("--call-method", nargs='+', metavar='METHOD', action='append',
@@ -487,7 +564,7 @@ def main():
487
564
  else:
488
565
  file_output = None
489
566
 
490
- client = STTWebSocketClient(args.control_url, args.data_url, args.debug, file_output, args.norealtime)
567
+ client = STTWebSocketClient(args.control_url, args.data_url, args.debug, file_output, args.norealtime, args.writechunks)
491
568
 
492
569
  def signal_handler(sig, frame):
493
570
  client.stop()
@@ -502,7 +579,6 @@ def main():
502
579
  if args.set_param:
503
580
  for param, value in args.set_param:
504
581
  try:
505
- # Attempt to parse the value to the appropriate type
506
582
  if '.' in value:
507
583
  value = float(value)
508
584
  else:
@@ -552,3 +628,4 @@ def main():
552
628
 
553
629
  if __name__ == "__main__":
554
630
  main()
631
+
@@ -65,18 +65,26 @@ The server supports two WebSocket connections:
65
65
  The server will broadcast real-time transcription updates to all connected clients on the data WebSocket.
66
66
  """
67
67
 
68
+ from .install_packages import check_and_install_packages
69
+ from datetime import datetime
70
+ import logging
71
+ import asyncio
72
+ import pyaudio
73
+ import sys
74
+
68
75
 
69
- extended_logging = True
76
+ debug_logging = False
77
+ extended_logging = False
70
78
  send_recorded_chunk = False
71
79
  log_incoming_chunks = False
72
80
  stt_optimizations = False
81
+ writechunks = False#
82
+ wav_file = None
83
+ loglevel = logging.WARNING
73
84
 
85
+ FORMAT = pyaudio.paInt16
86
+ CHANNELS = 1
74
87
 
75
- from .install_packages import check_and_install_packages
76
- from datetime import datetime
77
- import asyncio
78
- import base64
79
- import sys
80
88
 
81
89
  if sys.platform == 'win32':
82
90
  asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())
@@ -117,17 +125,25 @@ class bcolors:
117
125
 
118
126
  print(f"{bcolors.BOLD}{bcolors.OKCYAN}Starting server, please wait...{bcolors.ENDC}")
119
127
 
120
- import threading
121
- import json
122
- import websockets
128
+ # Initialize colorama
129
+ from colorama import init, Fore, Style
130
+ init()
131
+
123
132
  from RealtimeSTT import AudioToTextRecorder
124
- import numpy as np
125
133
  from scipy.signal import resample
134
+ import numpy as np
135
+ import websockets
136
+ import threading
137
+ import logging
138
+ import wave
139
+ import json
140
+ import time
126
141
 
127
142
  global_args = None
128
143
  recorder = None
129
144
  recorder_config = {}
130
145
  recorder_ready = threading.Event()
146
+ recorder_thread = None
131
147
  stop_recorder = False
132
148
  prev_text = ""
133
149
 
@@ -174,6 +190,12 @@ def preprocess_text(text):
174
190
 
175
191
  return text
176
192
 
193
+ def debug_print(message):
194
+ if debug_logging:
195
+ timestamp = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
196
+ thread_name = threading.current_thread().name
197
+ print(f"{Fore.CYAN}[DEBUG][{timestamp}][{thread_name}] {message}{Style.RESET_ALL}", file=sys.stderr)
198
+
177
199
  def text_detected(text, loop):
178
200
  global prev_text
179
201
 
@@ -259,26 +281,28 @@ def on_transcription_start(loop):
259
281
  })
260
282
  asyncio.run_coroutine_threadsafe(audio_queue.put(message), loop)
261
283
 
262
- def on_realtime_transcription_update(text, loop):
263
- # Send real-time transcription updates to the client
264
- text = preprocess_text(text)
265
- message = json.dumps({
266
- 'type': 'realtime_update',
267
- 'text': text
268
- })
269
- asyncio.run_coroutine_threadsafe(audio_queue.put(message), loop)
270
-
271
- def on_recorded_chunk(chunk, loop):
272
- if send_recorded_chunk:
273
- bytes_b64 = base64.b64encode(chunk.tobytes()).decode('utf-8')
274
- message = json.dumps({
275
- 'type': 'recorded_chunk',
276
- 'bytes': bytes_b64
277
- })
278
- asyncio.run_coroutine_threadsafe(audio_queue.put(message), loop)
284
+ # def on_realtime_transcription_update(text, loop):
285
+ # # Send real-time transcription updates to the client
286
+ # text = preprocess_text(text)
287
+ # message = json.dumps({
288
+ # 'type': 'realtime_update',
289
+ # 'text': text
290
+ # })
291
+ # asyncio.run_coroutine_threadsafe(audio_queue.put(message), loop)
292
+
293
+ # def on_recorded_chunk(chunk, loop):
294
+ # if send_recorded_chunk:
295
+ # bytes_b64 = base64.b64encode(chunk.tobytes()).decode('utf-8')
296
+ # message = json.dumps({
297
+ # 'type': 'recorded_chunk',
298
+ # 'bytes': bytes_b64
299
+ # })
300
+ # asyncio.run_coroutine_threadsafe(audio_queue.put(message), loop)
279
301
 
280
302
  # Define the server's arguments
281
303
  def parse_arguments():
304
+ global debug_logging, extended_logging, loglevel, writechunks, log_incoming_chunks
305
+
282
306
  import argparse
283
307
  parser = argparse.ArgumentParser(description='Start the Speech-to-Text (STT) server with various configuration options.')
284
308
 
@@ -346,7 +370,7 @@ def parse_arguments():
346
370
  parser.add_argument('--data_port', type=int, default=8012,
347
371
  help='The port number used for the data WebSocket connection. Data connections are used to send audio data and receive transcription updates in real time. Default is port 8012.')
348
372
 
349
- parser.add_argument('--wake_words', type=str, default="Jarvis",
373
+ parser.add_argument('--wake_words', type=str, default="",
350
374
  help='Specify the wake word(s) that will trigger the server to start listening. For example, setting this to "Jarvis" will make the system start transcribing when it detects the wake word "Jarvis". Default is "Jarvis".')
351
375
 
352
376
  parser.add_argument('--wake_words_sensitivity', type=float, default=0.5,
@@ -358,7 +382,7 @@ def parse_arguments():
358
382
  parser.add_argument('--wake_word_activation_delay', type=float, default=20,
359
383
  help='The delay in seconds before the wake word detection is activated after the system starts listening. This prevents false positives during the start of a session. Default is 0.5 seconds.')
360
384
 
361
- parser.add_argument('--wakeword_backend', type=str, default='pvporcupine',
385
+ parser.add_argument('--wakeword_backend', type=str, default='none',
362
386
  help='The backend used for wake word detection. You can specify different backends such as "default" or any custom implementations depending on your setup. Default is "pvporcupine".')
363
387
 
364
388
  parser.add_argument('--openwakeword_model_paths', type=str, nargs='*',
@@ -376,9 +400,27 @@ def parse_arguments():
376
400
  parser.add_argument('--use_extended_logging', action='store_true',
377
401
  help='Writes extensive log messages for the recording worker, that processes the audio chunks.')
378
402
 
403
+ parser.add_argument('--debug', action='store_true', help='Enable debug logging for detailed server operations')
404
+
405
+ parser.add_argument('--logchunks', action='store_true', help='Enable logging of incoming audio chunks (periods)')
406
+
407
+ parser.add_argument("--writechunks", metavar="FILE", help="Save received audio chunks to a WAV file")
408
+
379
409
  # Parse arguments
380
410
  args = parser.parse_args()
381
411
 
412
+ debug_logging = args.debug
413
+ extended_logging = args.use_extended_logging
414
+ writechunks = args.writechunks
415
+ log_incoming_chunks = args.logchunks
416
+
417
+ if debug_logging:
418
+ loglevel = logging.DEBUG
419
+ logging.basicConfig(level=loglevel, format='[%(asctime)s] %(levelname)s - %(message)s', datefmt='%Y-%m-%d %H:%M:%S')
420
+ else:
421
+ loglevel = logging.WARNING
422
+
423
+
382
424
  # Replace escaped newlines with actual newlines in initial_prompt
383
425
  if args.initial_prompt:
384
426
  args.initial_prompt = args.initial_prompt.replace("\\n", "\n")
@@ -437,11 +479,13 @@ def decode_and_resample(
437
479
  return resampled_audio.astype(np.int16).tobytes()
438
480
 
439
481
  async def control_handler(websocket, path):
482
+ debug_print(f"New control connection from {websocket.remote_address}")
440
483
  print(f"{bcolors.OKGREEN}Control client connected{bcolors.ENDC}")
441
484
  global recorder
442
485
  control_connections.add(websocket)
443
486
  try:
444
487
  async for message in websocket:
488
+ debug_print(f"Received control message: {message[:200]}...")
445
489
  if not recorder_ready.is_set():
446
490
  print(f"{bcolors.WARNING}Recorder not ready{bcolors.ENDC}")
447
491
  continue
@@ -530,21 +574,38 @@ async def control_handler(websocket, path):
530
574
  control_connections.remove(websocket)
531
575
 
532
576
  async def data_handler(websocket, path):
577
+ global writechunks, wav_file
533
578
  print(f"{bcolors.OKGREEN}Data client connected{bcolors.ENDC}")
534
579
  data_connections.add(websocket)
535
580
  try:
536
581
  while True:
537
582
  message = await websocket.recv()
538
583
  if isinstance(message, bytes):
539
- if log_incoming_chunks:
584
+ if debug_logging:
585
+ debug_print(f"Received audio chunk (size: {len(message)} bytes)")
586
+ elif log_incoming_chunks:
540
587
  print(".", end='', flush=True)
541
588
  # Handle binary message (audio data)
542
589
  metadata_length = int.from_bytes(message[:4], byteorder='little')
543
590
  metadata_json = message[4:4+metadata_length].decode('utf-8')
544
591
  metadata = json.loads(metadata_json)
545
592
  sample_rate = metadata['sampleRate']
593
+
594
+ debug_print(f"Processing audio chunk with sample rate {sample_rate}")
546
595
  chunk = message[4+metadata_length:]
596
+
597
+ if writechunks:
598
+ if not wav_file:
599
+ wav_file = wave.open(writechunks, 'wb')
600
+ wav_file.setnchannels(CHANNELS)
601
+ wav_file.setsampwidth(pyaudio.get_sample_size(FORMAT))
602
+ wav_file.setframerate(sample_rate)
603
+
604
+ wav_file.writeframes(chunk)
605
+
547
606
  resampled_chunk = decode_and_resample(chunk, sample_rate, 16000)
607
+
608
+ debug_print(f"Resampled chunk size: {len(resampled_chunk)} bytes")
548
609
  recorder.feed_audio(resampled_chunk)
549
610
  else:
550
611
  print(f"{bcolors.WARNING}Received non-binary message on data connection{bcolors.ENDC}")
@@ -619,50 +680,58 @@ async def main_async():
619
680
  'on_wakeword_detection_start': make_callback(loop, on_wakeword_detection_start),
620
681
  'on_wakeword_detection_end': make_callback(loop, on_wakeword_detection_end),
621
682
  'on_transcription_start': make_callback(loop, on_transcription_start),
622
- 'on_recorded_chunk': make_callback(loop, on_recorded_chunk),
683
+ # 'on_recorded_chunk': make_callback(loop, on_recorded_chunk),
623
684
  'no_log_file': True, # Disable logging to file
624
685
  'use_extended_logging': args.use_extended_logging,
686
+ 'level': loglevel,
625
687
  }
626
688
 
627
- control_server = await websockets.serve(control_handler, "localhost", args.control_port)
628
- data_server = await websockets.serve(data_handler, "localhost", args.data_port)
629
- print(f"{bcolors.OKGREEN}Control server started on {bcolors.OKBLUE}ws://localhost:{args.control_port}{bcolors.ENDC}")
630
- print(f"{bcolors.OKGREEN}Data server started on {bcolors.OKBLUE}ws://localhost:{args.data_port}{bcolors.ENDC}")
689
+ try:
690
+ # Attempt to start control and data servers
691
+ control_server = await websockets.serve(control_handler, "localhost", args.control_port)
692
+ data_server = await websockets.serve(data_handler, "localhost", args.data_port)
693
+ print(f"{bcolors.OKGREEN}Control server started on {bcolors.OKBLUE}ws://localhost:{args.control_port}{bcolors.ENDC}")
694
+ print(f"{bcolors.OKGREEN}Data server started on {bcolors.OKBLUE}ws://localhost:{args.data_port}{bcolors.ENDC}")
631
695
 
632
- # Task to broadcast audio messages
633
- broadcast_task = asyncio.create_task(broadcast_audio_messages())
696
+ # Start the broadcast and recorder threads
697
+ broadcast_task = asyncio.create_task(broadcast_audio_messages())
634
698
 
635
- recorder_thread = threading.Thread(target=_recorder_thread, args=(loop,))
636
- recorder_thread.start()
637
- recorder_ready.wait()
699
+ recorder_thread = threading.Thread(target=_recorder_thread, args=(loop,))
700
+ recorder_thread.start()
701
+ recorder_ready.wait()
638
702
 
639
- print(f"{bcolors.OKGREEN}Server started. Press Ctrl+C to stop the server.{bcolors.ENDC}")
703
+ print(f"{bcolors.OKGREEN}Server started. Press Ctrl+C to stop the server.{bcolors.ENDC}")
640
704
 
641
- try:
705
+ # Run server tasks
642
706
  await asyncio.gather(control_server.wait_closed(), data_server.wait_closed(), broadcast_task)
707
+ except OSError as e:
708
+ print(f"{bcolors.FAIL}Error: Could not start server on specified ports. It’s possible another instance of the server is already running, or the ports are being used by another application.{bcolors.ENDC}")
643
709
  except KeyboardInterrupt:
644
- print(f"{bcolors.WARNING}{bcolors.BOLD}Shutting down gracefully...{bcolors.ENDC}")
710
+ print(f"{bcolors.WARNING}Server interrupted by user, shutting down...{bcolors.ENDC}")
645
711
  finally:
646
- # Shut down the recorder
647
- if recorder:
648
- stop_recorder = True
649
- recorder.abort()
650
- recorder.stop()
651
- recorder.shutdown()
652
- print(f"{bcolors.OKGREEN}Recorder shut down{bcolors.ENDC}")
653
-
712
+ # Shutdown procedures for recorder and server threads
713
+ await shutdown_procedure()
714
+ print(f"{bcolors.OKGREEN}Server shutdown complete.{bcolors.ENDC}")
715
+
716
+ async def shutdown_procedure():
717
+ global stop_recorder, recorder_thread
718
+ if recorder:
719
+ stop_recorder = True
720
+ recorder.abort()
721
+ recorder.stop()
722
+ recorder.shutdown()
723
+ print(f"{bcolors.OKGREEN}Recorder shut down{bcolors.ENDC}")
724
+
725
+ if recorder_thread:
654
726
  recorder_thread.join()
655
727
  print(f"{bcolors.OKGREEN}Recorder thread finished{bcolors.ENDC}")
656
-
657
- # Cancel all active tasks in the event loop
658
- tasks = [t for t in asyncio.all_tasks() if t is not asyncio.current_task()]
659
- for task in tasks:
660
- task.cancel()
661
-
662
- # Run pending tasks and handle cancellation
663
- await asyncio.gather(*tasks, return_exceptions=True)
664
-
665
- print(f"{bcolors.OKGREEN}All tasks cancelled, closing event loop now.{bcolors.ENDC}")
728
+
729
+ tasks = [t for t in asyncio.all_tasks() if t is not asyncio.current_task()]
730
+ for task in tasks:
731
+ task.cancel()
732
+ await asyncio.gather(*tasks, return_exceptions=True)
733
+
734
+ print(f"{bcolors.OKGREEN}All tasks cancelled, closing event loop now.{bcolors.ENDC}")
666
735
 
667
736
  def main():
668
737
  try:
@@ -1,34 +1,34 @@
1
- import setuptools
2
-
3
- with open("README.md", "r") as fh:
4
- long_description = fh.read()
5
-
6
- # Read requirements.txt
7
- with open('requirements.txt') as f:
8
- requirements = f.read().splitlines()
9
-
10
- setuptools.setup(
11
- name="RealtimeSTT",
12
- version="0.3.4",
13
- author="Kolja Beigel",
14
- author_email="kolja.beigel@web.de",
15
- description="A fast Voice Activity Detection and Transcription System",
16
- long_description=long_description,
17
- long_description_content_type="text/markdown",
18
- url="https://github.com/KoljaB/RealTimeSTT",
19
- packages=setuptools.find_packages(),
20
- classifiers=[
21
- "Programming Language :: Python :: 3",
22
- "License :: OSI Approved :: MIT License",
23
- "Operating System :: OS Independent",
24
- ],
25
- python_requires='>=3.6',
26
- install_requires=requirements,
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
- entry_points={
29
- 'console_scripts': [
30
- 'stt-server=server.stt_server:main',
31
- 'stt=server.stt_cli_client:main',
32
- ],
33
- },
34
- )
1
+ import setuptools
2
+
3
+ with open("README.md", "r") as fh:
4
+ long_description = fh.read()
5
+
6
+ # Read requirements.txt
7
+ with open('requirements.txt') as f:
8
+ requirements = f.read().splitlines()
9
+
10
+ setuptools.setup(
11
+ name="RealtimeSTT",
12
+ version="0.3.6",
13
+ author="Kolja Beigel",
14
+ author_email="kolja.beigel@web.de",
15
+ description="A fast Voice Activity Detection and Transcription System",
16
+ long_description=long_description,
17
+ long_description_content_type="text/markdown",
18
+ url="https://github.com/KoljaB/RealTimeSTT",
19
+ packages=setuptools.find_packages(include=["RealtimeSTT", "RealtimeSTT_server"]),
20
+ classifiers=[
21
+ "Programming Language :: Python :: 3",
22
+ "License :: OSI Approved :: MIT License",
23
+ "Operating System :: OS Independent",
24
+ ],
25
+ python_requires='>=3.6',
26
+ install_requires=requirements,
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
+ entry_points={
29
+ 'console_scripts': [
30
+ 'stt-server=RealtimeSTT_server.stt_server:main',
31
+ 'stt=RealtimeSTT_server.stt_cli_client:main',
32
+ ],
33
+ },
34
+ )
@@ -1,3 +0,0 @@
1
- [console_scripts]
2
- stt = server.stt_cli_client:main
3
- stt-server = server.stt_server:main
@@ -1,2 +0,0 @@
1
- RealtimeSTT
2
- server
File without changes
File without changes