RealTimeSTT 0.3.5__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.
- {realtimestt-0.3.5 → realtimestt-0.3.6}/PKG-INFO +2 -2
- {realtimestt-0.3.5 → realtimestt-0.3.6}/README.md +1 -1
- {realtimestt-0.3.5 → realtimestt-0.3.6}/RealtimeSTT/audio_recorder.py +42 -12
- {realtimestt-0.3.5 → realtimestt-0.3.6}/RealtimeSTT.egg-info/PKG-INFO +2 -2
- {realtimestt-0.3.5 → realtimestt-0.3.6}/RealtimeSTT_server/stt_cli_client.py +129 -34
- {realtimestt-0.3.5 → realtimestt-0.3.6}/RealtimeSTT_server/stt_server.py +111 -42
- {realtimestt-0.3.5 → realtimestt-0.3.6}/setup.py +1 -1
- {realtimestt-0.3.5 → realtimestt-0.3.6}/LICENSE +0 -0
- {realtimestt-0.3.5 → realtimestt-0.3.6}/RealtimeSTT/__init__.py +0 -0
- {realtimestt-0.3.5 → realtimestt-0.3.6}/RealtimeSTT/audio_recorder_client.py +0 -0
- {realtimestt-0.3.5 → realtimestt-0.3.6}/RealtimeSTT.egg-info/SOURCES.txt +0 -0
- {realtimestt-0.3.5 → realtimestt-0.3.6}/RealtimeSTT.egg-info/dependency_links.txt +0 -0
- {realtimestt-0.3.5 → realtimestt-0.3.6}/RealtimeSTT.egg-info/entry_points.txt +0 -0
- {realtimestt-0.3.5 → realtimestt-0.3.6}/RealtimeSTT.egg-info/requires.txt +0 -0
- {realtimestt-0.3.5 → realtimestt-0.3.6}/RealtimeSTT.egg-info/top_level.txt +0 -0
- {realtimestt-0.3.5 → realtimestt-0.3.6}/RealtimeSTT_server/__init__.py +0 -0
- {realtimestt-0.3.5 → realtimestt-0.3.6}/RealtimeSTT_server/install_packages.py +0 -0
- {realtimestt-0.3.5 → realtimestt-0.3.6}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: RealtimeSTT
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.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.
|
|
58
|
+
Latest Version: v0.3.6
|
|
59
59
|
|
|
60
60
|
See [release history](https://github.com/KoljaB/RealtimeSTT/releases).
|
|
61
61
|
|
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 >
|
|
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
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
58
|
+
Latest Version: v0.3.6
|
|
59
59
|
|
|
60
60
|
See [release history](https://github.com/KoljaB/RealtimeSTT/releases).
|
|
61
61
|
|
|
@@ -1,3 +1,21 @@
|
|
|
1
|
+
"""
|
|
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.
|
|
17
|
+
"""
|
|
18
|
+
|
|
1
19
|
from urllib.parse import urlparse
|
|
2
20
|
from scipy import signal
|
|
3
21
|
from queue import Queue
|
|
@@ -7,13 +25,13 @@ import threading
|
|
|
7
25
|
import websocket
|
|
8
26
|
import argparse
|
|
9
27
|
import pyaudio
|
|
10
|
-
import logging
|
|
11
28
|
import struct
|
|
12
29
|
import socket
|
|
13
30
|
import shutil
|
|
14
31
|
import queue
|
|
15
32
|
import json
|
|
16
33
|
import time
|
|
34
|
+
import wave
|
|
17
35
|
import sys
|
|
18
36
|
import os
|
|
19
37
|
|
|
@@ -35,7 +53,7 @@ init()
|
|
|
35
53
|
websocket.enableTrace(False)
|
|
36
54
|
|
|
37
55
|
class STTWebSocketClient:
|
|
38
|
-
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):
|
|
39
57
|
self.control_url = control_url
|
|
40
58
|
self.data_url = data_url
|
|
41
59
|
self.control_ws = None
|
|
@@ -52,6 +70,16 @@ class STTWebSocketClient:
|
|
|
52
70
|
self.message_queue = Queue()
|
|
53
71
|
self.commands = Queue()
|
|
54
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}")
|
|
55
83
|
|
|
56
84
|
# Audio attributes
|
|
57
85
|
self.audio_interface = None
|
|
@@ -64,9 +92,12 @@ class STTWebSocketClient:
|
|
|
64
92
|
self.data_ws_thread = None
|
|
65
93
|
self.recording_thread = None
|
|
66
94
|
|
|
95
|
+
|
|
67
96
|
def debug_print(self, message):
|
|
68
97
|
if self.debug:
|
|
69
|
-
|
|
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)
|
|
70
101
|
|
|
71
102
|
def connect(self):
|
|
72
103
|
if not self.ensure_server_running():
|
|
@@ -74,7 +105,10 @@ class STTWebSocketClient:
|
|
|
74
105
|
return False
|
|
75
106
|
|
|
76
107
|
try:
|
|
108
|
+
self.debug_print("Attempting to establish WebSocket connections...")
|
|
109
|
+
|
|
77
110
|
# Connect to control WebSocket
|
|
111
|
+
self.debug_print(f"Connecting to control WebSocket at {self.control_url}")
|
|
78
112
|
self.control_ws = websocket.WebSocketApp(self.control_url,
|
|
79
113
|
on_message=self.on_control_message,
|
|
80
114
|
on_error=self.on_error,
|
|
@@ -82,10 +116,12 @@ class STTWebSocketClient:
|
|
|
82
116
|
on_open=self.on_control_open)
|
|
83
117
|
|
|
84
118
|
self.control_ws_thread = threading.Thread(target=self.control_ws.run_forever)
|
|
85
|
-
self.control_ws_thread.daemon = False
|
|
119
|
+
self.control_ws_thread.daemon = False
|
|
120
|
+
self.debug_print("Starting control WebSocket thread")
|
|
86
121
|
self.control_ws_thread.start()
|
|
87
122
|
|
|
88
123
|
# Connect to data WebSocket
|
|
124
|
+
self.debug_print(f"Connecting to data WebSocket at {self.data_url}")
|
|
89
125
|
self.data_ws_app = websocket.WebSocketApp(self.data_url,
|
|
90
126
|
on_message=self.on_data_message,
|
|
91
127
|
on_error=self.on_error,
|
|
@@ -93,10 +129,11 @@ class STTWebSocketClient:
|
|
|
93
129
|
on_open=self.on_data_open)
|
|
94
130
|
|
|
95
131
|
self.data_ws_thread = threading.Thread(target=self.data_ws_app.run_forever)
|
|
96
|
-
self.data_ws_thread.daemon = False
|
|
132
|
+
self.data_ws_thread.daemon = False
|
|
133
|
+
self.debug_print("Starting data WebSocket thread")
|
|
97
134
|
self.data_ws_thread.start()
|
|
98
135
|
|
|
99
|
-
|
|
136
|
+
self.debug_print("Waiting for connections to be established...")
|
|
100
137
|
if not self.connection_established.wait(timeout=10):
|
|
101
138
|
self.debug_print("Timeout while connecting to the server.")
|
|
102
139
|
return False
|
|
@@ -104,24 +141,30 @@ class STTWebSocketClient:
|
|
|
104
141
|
self.debug_print("WebSocket connections established successfully.")
|
|
105
142
|
return True
|
|
106
143
|
except Exception as e:
|
|
107
|
-
self.debug_print(f"Error while connecting to the server: {e}")
|
|
144
|
+
self.debug_print(f"Error while connecting to the server: {str(e)}")
|
|
108
145
|
return False
|
|
109
146
|
|
|
147
|
+
|
|
110
148
|
def on_control_open(self, ws):
|
|
111
|
-
self.debug_print("Control WebSocket connection opened
|
|
149
|
+
self.debug_print("Control WebSocket connection opened successfully")
|
|
112
150
|
self.connection_established.set()
|
|
113
151
|
self.start_command_processor()
|
|
114
152
|
|
|
115
153
|
def on_data_open(self, ws):
|
|
116
|
-
self.debug_print("Data WebSocket connection opened
|
|
117
|
-
self.data_ws_connected = ws
|
|
154
|
+
self.debug_print("Data WebSocket connection opened successfully")
|
|
155
|
+
self.data_ws_connected = ws
|
|
118
156
|
self.start_recording()
|
|
119
157
|
|
|
120
158
|
def on_error(self, ws, error):
|
|
121
|
-
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)}")
|
|
122
162
|
|
|
123
163
|
def on_close(self, ws, close_status_code, close_msg):
|
|
124
|
-
self.debug_print(f"WebSocket connection closed
|
|
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}")
|
|
125
168
|
self.is_running = False
|
|
126
169
|
self.stop_event.set()
|
|
127
170
|
|
|
@@ -129,8 +172,11 @@ class STTWebSocketClient:
|
|
|
129
172
|
parsed_url = urlparse(self.control_url)
|
|
130
173
|
host = parsed_url.hostname
|
|
131
174
|
port = parsed_url.port or 80
|
|
175
|
+
self.debug_print(f"Checking if server is running at {host}:{port}")
|
|
132
176
|
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
|
|
133
|
-
|
|
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
|
|
134
180
|
|
|
135
181
|
def ask_to_start_server(self):
|
|
136
182
|
response = input("Would you like to start the STT server now? (y/n): ").strip().lower()
|
|
@@ -213,37 +259,47 @@ class STTWebSocketClient:
|
|
|
213
259
|
|
|
214
260
|
def on_control_message(self, ws, message):
|
|
215
261
|
try:
|
|
262
|
+
self.debug_print(f"Received control message: {message}")
|
|
216
263
|
data = json.loads(message)
|
|
217
264
|
if 'status' in data:
|
|
265
|
+
self.debug_print(f"Message status: {data['status']}")
|
|
218
266
|
if data['status'] == 'success':
|
|
219
267
|
if 'parameter' in data and 'value' in data:
|
|
268
|
+
self.debug_print(f"Parameter update: {data['parameter']} = {data['value']}")
|
|
220
269
|
print(f"Parameter {data['parameter']} = {data['value']}")
|
|
221
270
|
elif data['status'] == 'error':
|
|
271
|
+
self.debug_print(f"Server error received: {data.get('message', '')}")
|
|
222
272
|
print(f"Server Error: {data.get('message', '')}")
|
|
223
273
|
else:
|
|
224
274
|
self.debug_print(f"Unknown control message format: {data}")
|
|
225
275
|
except json.JSONDecodeError:
|
|
226
|
-
self.debug_print(f"
|
|
276
|
+
self.debug_print(f"Failed to decode JSON control message: {message}")
|
|
227
277
|
except Exception as e:
|
|
228
|
-
self.debug_print(f"Error processing control message: {e}")
|
|
278
|
+
self.debug_print(f"Error processing control message: {str(e)}")
|
|
229
279
|
|
|
230
280
|
def on_data_message(self, ws, message):
|
|
231
281
|
try:
|
|
282
|
+
self.debug_print(f"Received data message: {message}")
|
|
232
283
|
data = json.loads(message)
|
|
233
284
|
message_type = data.get('type')
|
|
285
|
+
self.debug_print(f"Message type: {message_type}")
|
|
286
|
+
|
|
234
287
|
if message_type == 'realtime':
|
|
235
288
|
if data['text'] != self.last_text:
|
|
289
|
+
self.debug_print(f"New realtime text received: {data['text']}")
|
|
236
290
|
self.last_text = data['text']
|
|
237
291
|
if not self.norealtime:
|
|
238
292
|
self.update_progress_bar(self.last_text)
|
|
239
293
|
elif message_type == 'fullSentence':
|
|
294
|
+
self.debug_print(f"Full sentence received: {data['text']}")
|
|
240
295
|
if self.file_output:
|
|
296
|
+
self.debug_print("Writing to file output")
|
|
241
297
|
sys.stderr.write('\r\033[K')
|
|
242
298
|
sys.stderr.write(data['text'])
|
|
243
299
|
sys.stderr.write('\n')
|
|
244
300
|
sys.stderr.flush()
|
|
245
301
|
print(data['text'], file=self.file_output)
|
|
246
|
-
self.file_output.flush()
|
|
302
|
+
self.file_output.flush()
|
|
247
303
|
else:
|
|
248
304
|
self.finish_progress_bar()
|
|
249
305
|
print(f"{data['text']}")
|
|
@@ -258,14 +314,13 @@ class STTWebSocketClient:
|
|
|
258
314
|
'wakeword_detection_end',
|
|
259
315
|
'transcription_start'}:
|
|
260
316
|
pass # Known message types, no action needed
|
|
261
|
-
|
|
262
317
|
else:
|
|
263
|
-
self.debug_print(f"
|
|
318
|
+
self.debug_print(f"Other message type received: {message_type}")
|
|
264
319
|
|
|
265
320
|
except json.JSONDecodeError:
|
|
266
|
-
self.debug_print(f"
|
|
321
|
+
self.debug_print(f"Failed to decode JSON data message: {message}")
|
|
267
322
|
except Exception as e:
|
|
268
|
-
self.debug_print(f"Error processing data message: {e}")
|
|
323
|
+
self.debug_print(f"Error processing data message: {str(e)}")
|
|
269
324
|
|
|
270
325
|
def show_initial_indicator(self):
|
|
271
326
|
if self.norealtime:
|
|
@@ -331,40 +386,67 @@ class STTWebSocketClient:
|
|
|
331
386
|
def record_and_send_audio(self):
|
|
332
387
|
try:
|
|
333
388
|
if not self.setup_audio():
|
|
389
|
+
self.debug_print("Failed to set up audio recording")
|
|
334
390
|
raise Exception("Failed to set up audio recording.")
|
|
335
391
|
|
|
336
|
-
|
|
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")
|
|
337
400
|
self.show_initial_indicator()
|
|
338
401
|
|
|
339
402
|
while self.is_running:
|
|
340
403
|
try:
|
|
341
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
|
+
|
|
342
416
|
metadata = {"sampleRate": self.device_sample_rate}
|
|
343
417
|
metadata_json = json.dumps(metadata)
|
|
344
418
|
metadata_length = len(metadata_json)
|
|
345
419
|
message = struct.pack('<I', metadata_length) + metadata_json.encode('utf-8') + audio_data
|
|
420
|
+
|
|
421
|
+
self.debug_print(f"Sending audio chunk {self.chunks_sent}: {len(audio_data)} bytes, metadata: {metadata_json}")
|
|
346
422
|
self.data_ws_connected.send(message, opcode=websocket.ABNF.OPCODE_BINARY)
|
|
423
|
+
self.last_chunk_time = current_time
|
|
424
|
+
|
|
347
425
|
except Exception as e:
|
|
348
|
-
self.debug_print(f"Error sending audio data: {e}")
|
|
349
|
-
break
|
|
426
|
+
self.debug_print(f"Error sending audio data: {str(e)}")
|
|
427
|
+
break
|
|
350
428
|
|
|
351
429
|
except Exception as e:
|
|
352
|
-
self.debug_print(f"Error in record_and_send_audio: {e}")
|
|
430
|
+
self.debug_print(f"Error in record_and_send_audio: {str(e)}")
|
|
353
431
|
finally:
|
|
354
432
|
self.cleanup_audio()
|
|
355
433
|
|
|
356
434
|
def setup_audio(self):
|
|
357
435
|
try:
|
|
436
|
+
self.debug_print("Initializing PyAudio interface")
|
|
358
437
|
self.audio_interface = pyaudio.PyAudio()
|
|
359
438
|
self.input_device_index = None
|
|
439
|
+
|
|
360
440
|
try:
|
|
361
441
|
default_device = self.audio_interface.get_default_input_device_info()
|
|
362
442
|
self.input_device_index = default_device['index']
|
|
443
|
+
self.debug_print(f"Default input device found: {default_device}")
|
|
363
444
|
except OSError as e:
|
|
364
|
-
self.debug_print(f"No default input device found: {e}")
|
|
445
|
+
self.debug_print(f"No default input device found: {str(e)}")
|
|
365
446
|
return False
|
|
366
447
|
|
|
367
|
-
self.device_sample_rate = 16000
|
|
448
|
+
self.device_sample_rate = 16000
|
|
449
|
+
self.debug_print(f"Attempting to open audio stream with sample rate {self.device_sample_rate} Hz")
|
|
368
450
|
|
|
369
451
|
try:
|
|
370
452
|
self.stream = self.audio_interface.open(
|
|
@@ -375,29 +457,36 @@ class STTWebSocketClient:
|
|
|
375
457
|
frames_per_buffer=CHUNK,
|
|
376
458
|
input_device_index=self.input_device_index,
|
|
377
459
|
)
|
|
378
|
-
self.debug_print(f"Audio
|
|
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}")
|
|
379
462
|
return True
|
|
380
463
|
except Exception as e:
|
|
381
|
-
self.debug_print(f"Failed to initialize audio stream
|
|
464
|
+
self.debug_print(f"Failed to initialize audio stream: {str(e)}")
|
|
382
465
|
return False
|
|
383
466
|
|
|
384
467
|
except Exception as e:
|
|
385
|
-
self.debug_print(f"Error
|
|
468
|
+
self.debug_print(f"Error in setup_audio: {str(e)}")
|
|
386
469
|
if self.audio_interface:
|
|
387
470
|
self.audio_interface.terminate()
|
|
388
471
|
return False
|
|
389
472
|
|
|
390
473
|
def cleanup_audio(self):
|
|
474
|
+
self.debug_print("Cleaning up audio resources")
|
|
391
475
|
try:
|
|
392
476
|
if self.stream:
|
|
477
|
+
self.debug_print("Stopping and closing audio stream")
|
|
393
478
|
self.stream.stop_stream()
|
|
394
479
|
self.stream.close()
|
|
395
480
|
self.stream = None
|
|
396
481
|
if self.audio_interface:
|
|
482
|
+
self.debug_print("Terminating PyAudio interface")
|
|
397
483
|
self.audio_interface.terminate()
|
|
398
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()
|
|
399
488
|
except Exception as e:
|
|
400
|
-
self.debug_print(f"Error
|
|
489
|
+
self.debug_print(f"Error during audio cleanup: {str(e)}")
|
|
401
490
|
|
|
402
491
|
def set_parameter(self, parameter, value):
|
|
403
492
|
command = {
|
|
@@ -428,23 +517,28 @@ class STTWebSocketClient:
|
|
|
428
517
|
self.command_thread.daemon = False # Ensure it is not a daemon thread
|
|
429
518
|
self.command_thread.start()
|
|
430
519
|
|
|
520
|
+
|
|
431
521
|
def command_processor(self):
|
|
432
|
-
self.debug_print(
|
|
522
|
+
self.debug_print("Starting command processor thread")
|
|
433
523
|
while not self.stop_event.is_set():
|
|
434
524
|
try:
|
|
435
525
|
command = self.commands.get(timeout=0.1)
|
|
526
|
+
self.debug_print(f"Processing command: {command}")
|
|
436
527
|
if command['type'] == 'set_parameter':
|
|
528
|
+
self.debug_print(f"Setting parameter: {command['parameter']} = {command['value']}")
|
|
437
529
|
self.set_parameter(command['parameter'], command['value'])
|
|
438
530
|
elif command['type'] == 'get_parameter':
|
|
531
|
+
self.debug_print(f"Getting parameter: {command['parameter']}")
|
|
439
532
|
self.get_parameter(command['parameter'])
|
|
440
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')}")
|
|
441
535
|
self.call_method(command['method'], command.get('args'), command.get('kwargs'))
|
|
442
536
|
except queue.Empty:
|
|
443
537
|
continue
|
|
444
538
|
except Exception as e:
|
|
445
|
-
self.debug_print(f"Error in command processor: {e}")
|
|
539
|
+
self.debug_print(f"Error in command processor: {str(e)}")
|
|
446
540
|
|
|
447
|
-
self.debug_print(
|
|
541
|
+
self.debug_print("Command processor thread stopping")
|
|
448
542
|
|
|
449
543
|
def add_command(self, command):
|
|
450
544
|
self.commands.put(command)
|
|
@@ -455,6 +549,7 @@ def main():
|
|
|
455
549
|
parser.add_argument("--data-url", default=DEFAULT_DATA_URL, help="STT Data WebSocket URL")
|
|
456
550
|
parser.add_argument("--debug", action="store_true", help="Enable debug mode")
|
|
457
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")
|
|
458
553
|
parser.add_argument("--set-param", nargs=2, metavar=('PARAM', 'VALUE'), action='append',
|
|
459
554
|
help="Set a recorder parameter. Can be used multiple times.")
|
|
460
555
|
parser.add_argument("--call-method", nargs='+', metavar='METHOD', action='append',
|
|
@@ -469,7 +564,7 @@ def main():
|
|
|
469
564
|
else:
|
|
470
565
|
file_output = None
|
|
471
566
|
|
|
472
|
-
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)
|
|
473
568
|
|
|
474
569
|
def signal_handler(sig, frame):
|
|
475
570
|
client.stop()
|
|
@@ -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
|
-
|
|
76
|
+
debug_logging = False
|
|
77
|
+
extended_logging = False
|
|
70
78
|
send_recorded_chunk = False
|
|
71
|
-
log_incoming_chunks =
|
|
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
|
-
|
|
121
|
-
import
|
|
122
|
-
|
|
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
|
|
|
@@ -279,6 +301,8 @@ def on_transcription_start(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
|
|
|
@@ -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
|
|
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}")
|
|
@@ -622,47 +683,55 @@ async def main_async():
|
|
|
622
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
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
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
|
-
|
|
633
|
-
|
|
696
|
+
# Start the broadcast and recorder threads
|
|
697
|
+
broadcast_task = asyncio.create_task(broadcast_audio_messages())
|
|
634
698
|
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
699
|
+
recorder_thread = threading.Thread(target=_recorder_thread, args=(loop,))
|
|
700
|
+
recorder_thread.start()
|
|
701
|
+
recorder_ready.wait()
|
|
638
702
|
|
|
639
|
-
|
|
703
|
+
print(f"{bcolors.OKGREEN}Server started. Press Ctrl+C to stop the server.{bcolors.ENDC}")
|
|
640
704
|
|
|
641
|
-
|
|
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}
|
|
710
|
+
print(f"{bcolors.WARNING}Server interrupted by user, shutting down...{bcolors.ENDC}")
|
|
645
711
|
finally:
|
|
646
|
-
#
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
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
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
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:
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|