daihui.zhang commited on
Commit
2a2d4ba
·
1 Parent(s): 7289e30

add frontend demo

Browse files
.gitignore CHANGED
@@ -3,7 +3,7 @@ __pycache__/
3
  *$py.class
4
  # C extensions
5
  *.so
6
-
7
  # Distribution / packaging
8
  .Python
9
  build/
 
3
  *$py.class
4
  # C extensions
5
  *.so
6
+ .DS_Store
7
  # Distribution / packaging
8
  .Python
9
  build/
api_model.py ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from pydantic import BaseModel, Field
2
+ from uuid import UUID
3
+
4
+ class TransResult(BaseModel):
5
+ # trans_pattern: str
6
+ seg_id: int
7
+ context: str
8
+ from_: str = Field(alias="from")
9
+ to: str
10
+ tran_content: str = Field(alias="tranContent")
11
+ partial: bool = True
12
+
13
+ class Config:
14
+ populate_by_name = True
15
+
16
+
17
+
18
+
19
+ class Message(BaseModel):
20
+ result: TransResult = {}
21
+ # action: str = "recognition"
22
+ error_code: int = 0
23
+ request_id: UUID
config.py CHANGED
@@ -9,7 +9,7 @@ PAUSE_END_MARKERS = [',', ',', '、']
9
 
10
  # whisper推理参数
11
  WHISPER_PROMPT_ZH = "以下是简体中文普通话的句子。"
12
- MAX_LENTH_ZH = 3
13
 
14
  WHISPER_PROMPT_EN = "The following is an English sentence."
15
  MAX_LENGTH_EN= 1
 
9
 
10
  # whisper推理参数
11
  WHISPER_PROMPT_ZH = "以下是简体中文普通话的句子。"
12
+ MAX_LENTH_ZH = 4
13
 
14
  WHISPER_PROMPT_EN = "The following is an English sentence."
15
  MAX_LENGTH_EN= 1
frontend/index.html ADDED
@@ -0,0 +1,169 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="zh">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <title>试试翻译</title>
6
+ <style>
7
+ body {
8
+ background-color: #f9f9fc;
9
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", sans-serif;
10
+ margin: 0;
11
+ padding: 2rem;
12
+ }
13
+ .translation-box {
14
+ background: #f2f2f8;
15
+ border-radius: 12px;
16
+ padding: 1.5rem;
17
+ max-width: 800px;
18
+ margin: 0 auto;
19
+ min-height: 200px;
20
+ }
21
+ .entry {
22
+ margin-bottom: 1.5rem;
23
+ }
24
+ .timestamp {
25
+ font-size: 0.75rem;
26
+ color: #999;
27
+ }
28
+ .original {
29
+ font-size: 1rem;
30
+ color: #333;
31
+ }
32
+ .translation {
33
+ font-size: 1rem;
34
+ font-weight: bold;
35
+ color: #000;
36
+ }
37
+ .footer {
38
+ display: flex;
39
+ justify-content: space-between;
40
+ align-items: center;
41
+ margin-top: 2rem;
42
+ }
43
+ .lang-select {
44
+ background: white;
45
+ border-radius: 9999px;
46
+ padding: 0.4rem 1rem;
47
+ border: none;
48
+ font-size: 1rem;
49
+ box-shadow: 0 0 0 1px #ddd;
50
+ }
51
+ .record-button {
52
+ background-color: #1e40af;
53
+ color: white;
54
+ border: none;
55
+ padding: 0.6rem 1.2rem;
56
+ border-radius: 9999px;
57
+ font-size: 1rem;
58
+ cursor: pointer;
59
+ }
60
+ </style>
61
+ </head>
62
+ <body>
63
+ <div class="translation-box" id="translationBox">
64
+ <!-- 实时内容将插入这里 -->
65
+ </div>
66
+
67
+ <div class="footer">
68
+ <select class="lang-select">
69
+ <option>中文 » 英语</option>
70
+ </select>
71
+ <button class="record-button" onclick="startRecording()">🎤 录音</button>
72
+ </div>
73
+
74
+ <script>
75
+ let ws;
76
+ let mediaRecorder;
77
+
78
+ function formatTimestamp(ms) {
79
+ const sec = ms / 1000;
80
+ const min = Math.floor(sec / 60);
81
+ const s = (sec % 60).toFixed(1);
82
+ return `${String(min).padStart(2, '0')}:${s.padStart(4, '0')}`;
83
+ }
84
+
85
+ let lastSegId = null; // 用于存储上一个 seg_id
86
+
87
+ function addTranslation(result) {
88
+ const box = document.getElementById('translationBox');
89
+
90
+ // 创建一个新的 div 来显示翻译
91
+ const entry = document.createElement('div');
92
+ entry.className = 'entry';
93
+
94
+ console.log(result);
95
+
96
+ const start = formatTimestamp(result.bg);
97
+ const end = formatTimestamp(result.ed);
98
+
99
+ // 判断是否是新的一行
100
+ if (result.seg_id === lastSegId) {
101
+ // 如果 seg_id 相同,更新该行内容
102
+ const existingEntry = box.querySelector(`.entry[data-seg-id="${result.seg_id}"]`);
103
+ if (existingEntry) {
104
+ const translationDiv = existingEntry.querySelector('.translation');
105
+ translationDiv.innerHTML = result.tranContent;
106
+ }
107
+ } else {
108
+ // 如果 seg_id 不同,表示是新的行,添加新行
109
+ entry.setAttribute('data-seg-id', result.seg_id); // 设置 seg_id
110
+ entry.innerHTML = `
111
+ <div class="original">${result.context}</div>
112
+ <div class="translation">${result.tranContent}</div>
113
+ `;
114
+ box.appendChild(entry);
115
+ }
116
+
117
+ // 更新 lastSegId 以便下一次判断
118
+ lastSegId = result.seg_id;
119
+ }
120
+
121
+
122
+
123
+ async function startRecording() {
124
+ const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
125
+ const audioContext = new AudioContext({ sampleRate: 16000 });
126
+ const source = audioContext.createMediaStreamSource(stream);
127
+ const processor = audioContext.createScriptProcessor(4096, 1, 1);
128
+
129
+ const wsUrl = "ws://localhost:9090?from=zh&to=en";
130
+ ws = new WebSocket(wsUrl);
131
+
132
+ ws.binaryType = "arraybuffer";
133
+
134
+ ws.onopen = () => {
135
+ console.log("WebSocket opened");
136
+ source.connect(processor);
137
+ processor.connect(audioContext.destination);
138
+
139
+ processor.onaudioprocess = (e) => {
140
+ const input = e.inputBuffer.getChannelData(0);
141
+ const buffer = new Int16Array(input.length);
142
+ for (let i = 0; i < input.length; i++) {
143
+ buffer[i] = Math.max(-1, Math.min(1, input[i])) * 0x7FFF;
144
+ }
145
+ ws.send(buffer);
146
+ };
147
+ };
148
+
149
+ ws.onmessage = (event) => {
150
+ try {
151
+ const msg = JSON.parse(event.data);
152
+ if (msg.result) {
153
+ addTranslation(msg.result);
154
+ }
155
+ } catch (e) {
156
+ console.error("Parse error:", e);
157
+ }
158
+ };
159
+
160
+ ws.onerror = (e) => console.error("WebSocket error:", e);
161
+ ws.onclose = () => {
162
+ console.log("WebSocket closed");
163
+ processor.disconnect();
164
+ source.disconnect();
165
+ };
166
+ }
167
+ </script>
168
+ </body>
169
+ </html>
pyproject.toml CHANGED
@@ -11,6 +11,7 @@ dependencies = [
11
  "numpy>=2.1.3",
12
  "onnxruntime>=1.21.0",
13
  "pyaudio>=0.2.14",
 
14
  "pywhispercpp>=1.3.0",
15
  "setuptools>=78.1.0",
16
  "soundfile>=0.13.1",
 
11
  "numpy>=2.1.3",
12
  "onnxruntime>=1.21.0",
13
  "pyaudio>=0.2.14",
14
+ "pydantic>=2.11.2",
15
  "pywhispercpp>=1.3.0",
16
  "setuptools>=78.1.0",
17
  "soundfile>=0.13.1",
transcribe/transcription.py CHANGED
@@ -13,7 +13,7 @@ from .vad import VoiceActivityDetector
13
  from urllib.parse import urlparse, parse_qsl
14
  from websockets.exceptions import ConnectionClosed
15
  from websockets.sync.server import serve
16
-
17
  logging.basicConfig(level=logging.INFO)
18
 
19
 
@@ -174,14 +174,19 @@ class TranscriptionServer:
174
  frame_data = websocket.recv()
175
  if frame_data == b"END_OF_AUDIO":
176
  return False
177
- return np.frombuffer(frame_data, dtype=np.float32)
 
178
 
179
  def handle_new_connection(self, websocket):
 
 
 
 
180
  try:
181
  logging.info("New client connected")
182
- options = websocket.recv()
183
- options = json.loads(options)
184
-
185
  if self.client_manager is None:
186
  max_clients = options.get('max_clients', 4)
187
  max_connection_time = options.get('max_connection_time', 600)
@@ -195,7 +200,9 @@ class TranscriptionServer:
195
  self.vad_detector = VoiceActivityDetector(frame_rate=self.RATE)
196
 
197
  self.initialize_client(websocket, options)
198
-
 
 
199
  return True
200
  except json.JSONDecodeError:
201
  logging.error("Failed to decode JSON from client")
@@ -240,12 +247,7 @@ class TranscriptionServer:
240
  self.backend = backend
241
  if not self.handle_new_connection(websocket):
242
  return
243
- query_parameters_dict = dict(parse_qsl(urlparse(websocket.request.path).query))
244
- from_lang, to_lang = query_parameters_dict.get('from'), query_parameters_dict.get('to')
245
-
246
- if from_lang and to_lang:
247
- self.set_lang(websocket, from_lang, to_lang)
248
- logging.info(f"Source lange: {from_lang} -> Dst lange: {to_lang}")
249
 
250
  try:
251
  while not self.client_manager.is_client_timeout(websocket):
 
13
  from urllib.parse import urlparse, parse_qsl
14
  from websockets.exceptions import ConnectionClosed
15
  from websockets.sync.server import serve
16
+ from uuid import uuid1
17
  logging.basicConfig(level=logging.INFO)
18
 
19
 
 
174
  frame_data = websocket.recv()
175
  if frame_data == b"END_OF_AUDIO":
176
  return False
177
+ return np.frombuffer(frame_data, dtype=np.int16).astype(np.float32) / 32768.0
178
+
179
 
180
  def handle_new_connection(self, websocket):
181
+ query_parameters_dict = dict(parse_qsl(urlparse(websocket.request.path).query))
182
+ from_lang, to_lang = query_parameters_dict.get('from'), query_parameters_dict.get('to')
183
+
184
+
185
  try:
186
  logging.info("New client connected")
187
+ # options = websocket.recv()
188
+ # options = json.loads(options)
189
+ options = {"language": from_lang, "uid": str(uuid1())}
190
  if self.client_manager is None:
191
  max_clients = options.get('max_clients', 4)
192
  max_connection_time = options.get('max_connection_time', 600)
 
200
  self.vad_detector = VoiceActivityDetector(frame_rate=self.RATE)
201
 
202
  self.initialize_client(websocket, options)
203
+ if from_lang and to_lang:
204
+ self.set_lang(websocket, from_lang, to_lang)
205
+ logging.info(f"Source lange: {from_lang} -> Dst lange: {to_lang}")
206
  return True
207
  except json.JSONDecodeError:
208
  logging.error("Failed to decode JSON from client")
 
247
  self.backend = backend
248
  if not self.handle_new_connection(websocket):
249
  return
250
+
 
 
 
 
 
251
 
252
  try:
253
  while not self.client_manager.is_client_timeout(websocket):
transcribe/translator.py CHANGED
@@ -28,7 +28,7 @@ class QwenTranslator:
28
  message = self.to_message(prompt, src_lang, dst_lang)
29
  start_time = time.monotonic()
30
  output = self.llm.create_chat_completion(messages=message, temperature=0.9)
31
- logger.info(f"LLM inference time: {time.monotonic() - start_time:.2f}s.")
32
  return output['choices'][0]['message']['content']
33
 
34
  def __call__(self, prompt,*args, **kwargs):
 
28
  message = self.to_message(prompt, src_lang, dst_lang)
29
  start_time = time.monotonic()
30
  output = self.llm.create_chat_completion(messages=message, temperature=0.9)
31
+ logger.info(f"Translate time: {time.monotonic() - start_time:.2f}s.")
32
  return output['choices'][0]['message']['content']
33
 
34
  def __call__(self, prompt,*args, **kwargs):
transcribe/whisper_llm_serve.py CHANGED
@@ -1,7 +1,7 @@
1
 
2
 
3
  import soundfile
4
- from concurrent.futures import ProcessPoolExecutor as Pool
5
  import multiprocessing as mp
6
  import numpy as np
7
  from logging import getLogger
@@ -14,10 +14,17 @@ import threading
14
  from functools import partial
15
  from .server import ServeClientBase
16
  from .translator import QwenTranslator
 
17
  from pywhispercpp.model import Model
 
 
 
 
18
 
19
  logger = getLogger(__name__)
20
 
 
 
21
 
22
  class TripleTextBuffer:
23
  def __init__(self, size=2):
@@ -108,6 +115,7 @@ class SegmentManager:
108
  class PywhisperInference:
109
  whisper_model = None
110
  llm_model = None
 
111
 
112
  @classmethod
113
  def initializer(cls, event:mp.Event, warmup=True):
@@ -124,6 +132,7 @@ class PywhisperInference:
124
 
125
  # init llamacpp
126
  cls.llm_model = QwenTranslator(config.LLM_MODEL_PATH, config.LLM_SYS_PROMPT)
 
127
  event.set()
128
 
129
  @classmethod
@@ -155,6 +164,12 @@ class PywhisperInference:
155
  @classmethod
156
  def translate(cls, context: str, src_lang, dst_lang):
157
  return cls.llm_model.translate(context, src_lang, dst_lang)
 
 
 
 
 
 
158
 
159
 
160
  class PyWhiperCppServe(ServeClientBase):
@@ -171,16 +186,21 @@ class PyWhiperCppServe(ServeClientBase):
171
  self.lock = threading.Lock()
172
  self.frames_np = None
173
  self.sample_rate = 16000
174
-
 
175
  self._ready_state = mp.Event()
176
- self._pool = Pool(
177
  max_workers=1, initializer=partial(PywhisperInference.initializer, event=self._ready_state))
178
-
179
  logger.info('Create a process to process audio.')
 
 
 
 
 
180
  self.trans_thread = threading.Thread(target=self.speech_to_text)
181
  self.trans_thread.daemon = True
182
  self.trans_thread.start()
183
- self.send_ready_state()
184
 
185
  def send_ready_state(self):
186
  while not self._ready_state:
@@ -196,13 +216,29 @@ class PyWhiperCppServe(ServeClientBase):
196
  self.language = src_lang
197
  self.dst_lang = dst_lang
198
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
199
  def add_frames(self, frame_np):
 
200
  with self.lock:
201
  if self.frames_np is None:
202
  self.frames_np = frame_np.copy()
203
  else:
204
  self.frames_np = np.append(self.frames_np,frame_np)
205
-
 
206
 
207
  def update_audio_buffer(self, last_offset):
208
  with self.lock:
@@ -273,6 +309,7 @@ class PyWhiperCppServe(ServeClientBase):
273
  return None, left_watch_string, right_watch_string, is_end_sentence
274
 
275
  def speech_to_text(self):
 
276
  while True:
277
  if self.exit:
278
  logger.info("Exiting speech to text thread")
@@ -284,13 +321,17 @@ class PyWhiperCppServe(ServeClientBase):
284
  continue
285
 
286
  audio_buffer = self.get_audio_chunk_for_processing()
287
- # logger.info(f"[pywhispercpp:] Processing audio with duration: {len(audio_buffer)}")
288
  # segments = self.transcribe_audio(audio_buffer)
 
 
 
289
  try:
290
  logger.info(f"Processing audio with duration: {len(audio_buffer) / self.sample_rate:.2f}s")
291
  segments = self.transcribe_audio(audio_buffer)
292
- for item in self.handle_transcription_output(segments, audio_buffer):
293
- print(item)
 
294
  except KeyboardInterrupt:
295
  break
296
  except Exception as e:
@@ -300,6 +341,8 @@ class PyWhiperCppServe(ServeClientBase):
300
 
301
  def handle_transcription_output(self, segments, audio_buffer):
302
  texts = "".join(i.text for i in segments)
 
 
303
  self._segment_manager.handle(texts)
304
  # 分析句子
305
  last_cut_index, left_string, right_string, is_end_sentence = self.analysis_segments(segments, audio_buffer)
@@ -313,26 +356,45 @@ class PyWhiperCppServe(ServeClientBase):
313
  if is_end_sentence and last_cut_index:
314
  message = self._segment_manager.segment
315
  seg_id = self._segment_manager.get_seg_id() - 1
316
- yield (seg_id, message, self.translate_text(message))
 
 
 
 
 
 
 
 
317
  if self._segment_manager.string.strip():
318
- yield (seg_id + 1, self._segment_manager.string, self.translate_text(self._segment_manager.string))
 
 
 
 
 
 
 
319
 
320
  else:
321
  seg_id = self._segment_manager.get_seg_id()
322
  message = self._segment_manager.short_sentence + self._segment_manager.string
323
- # print(self._segment_manager.__dict__)
324
- # elapsed_time = time.time() - start_time
325
- # formatted_time = f"{int(elapsed_time // 60):02}:{int(elapsed_time % 60):02}:{(elapsed_time % 1) * 1000:03.0f}"
326
- yield (seg_id, message,self.translate_text(message))
 
 
 
 
327
 
328
- def send_to_client(self, data_dict):
329
- content = {
330
- "uid": self.client_uid,
331
- **data_dict
332
- }
333
  try:
334
  self.websocket.send(
335
- json.dumps(content)
336
  )
337
  except Exception as e:
338
  logger.error(f"Sending data to client: {e}")
 
1
 
2
 
3
  import soundfile
4
+ from concurrent.futures import ProcessPoolExecutor as PPool
5
  import multiprocessing as mp
6
  import numpy as np
7
  from logging import getLogger
 
14
  from functools import partial
15
  from .server import ServeClientBase
16
  from .translator import QwenTranslator
17
+ from .vad import VoiceActivityDetector
18
  from pywhispercpp.model import Model
19
+ from queue import Queue
20
+ from scipy.io.wavfile import write
21
+ from api_model import TransResult, Message
22
+
23
 
24
  logger = getLogger(__name__)
25
 
26
+ def save_to_wave(filename, data:np.ndarray, sample_rate=16000):
27
+ write(filename, sample_rate, data)
28
 
29
  class TripleTextBuffer:
30
  def __init__(self, size=2):
 
115
  class PywhisperInference:
116
  whisper_model = None
117
  llm_model = None
118
+ vad_model = None
119
 
120
  @classmethod
121
  def initializer(cls, event:mp.Event, warmup=True):
 
132
 
133
  # init llamacpp
134
  cls.llm_model = QwenTranslator(config.LLM_MODEL_PATH, config.LLM_SYS_PROMPT)
135
+ cls.vad_model = VoiceActivityDetector()
136
  event.set()
137
 
138
  @classmethod
 
164
  @classmethod
165
  def translate(cls, context: str, src_lang, dst_lang):
166
  return cls.llm_model.translate(context, src_lang, dst_lang)
167
+
168
+ @classmethod
169
+ def voice_detect(cls, audio_buffer):
170
+ audio_buffer = np.frombuffer(audio_buffer, dtype=np.float32)
171
+ return cls.vad_model(audio_buffer)
172
+
173
 
174
 
175
  class PyWhiperCppServe(ServeClientBase):
 
186
  self.lock = threading.Lock()
187
  self.frames_np = None
188
  self.sample_rate = 16000
189
+ # self._audio_queue = Queue()
190
+ # 进程初始化后再开始收音频
191
  self._ready_state = mp.Event()
192
+ self._pool = PPool(
193
  max_workers=1, initializer=partial(PywhisperInference.initializer, event=self._ready_state))
194
+
195
  logger.info('Create a process to process audio.')
196
+ self.send_ready_state()
197
+ # self.load_frame_thread = threading.Thread(target=self.load_frame_from_queue)
198
+ # self.load_frame_thread.daemon = True
199
+ # self.load_frame_thread.start()
200
+
201
  self.trans_thread = threading.Thread(target=self.speech_to_text)
202
  self.trans_thread.daemon = True
203
  self.trans_thread.start()
 
204
 
205
  def send_ready_state(self):
206
  while not self._ready_state:
 
216
  self.language = src_lang
217
  self.dst_lang = dst_lang
218
 
219
+ # def load_frame_from_queue(self):
220
+ # while True:
221
+ # frame_np = self._audio_queue.get()
222
+ # fut = self._pool.submit(PywhisperInference.voice_detect, frame_np.tobytes())
223
+ # output = fut.result()
224
+ # logger.info(f"VAD: {output}")
225
+ # if output == True:
226
+ # with self.lock:
227
+ # if self.frames_np is None:
228
+ # self.frames_np = frame_np.copy()
229
+ # else:
230
+ # self.frames_np = np.append(self.frames_np,frame_np)
231
+
232
+
233
  def add_frames(self, frame_np):
234
+ # self._audio_queue.put(frame_np)
235
  with self.lock:
236
  if self.frames_np is None:
237
  self.frames_np = frame_np.copy()
238
  else:
239
  self.frames_np = np.append(self.frames_np,frame_np)
240
+
241
+
242
 
243
  def update_audio_buffer(self, last_offset):
244
  with self.lock:
 
309
  return None, left_watch_string, right_watch_string, is_end_sentence
310
 
311
  def speech_to_text(self):
312
+ # c = 0
313
  while True:
314
  if self.exit:
315
  logger.info("Exiting speech to text thread")
 
321
  continue
322
 
323
  audio_buffer = self.get_audio_chunk_for_processing()
324
+ logger.info(f"Processing audio with duration: {len(audio_buffer)}")
325
  # segments = self.transcribe_audio(audio_buffer)
326
+ # c+= 1
327
+ # name = f"dev-{c}.wav"
328
+ # save_to_wave(name, audio_buffer)
329
  try:
330
  logger.info(f"Processing audio with duration: {len(audio_buffer) / self.sample_rate:.2f}s")
331
  segments = self.transcribe_audio(audio_buffer)
332
+ for tran_result in self.handle_transcription_output(segments, audio_buffer):
333
+ self.send_to_client(tran_result)
334
+
335
  except KeyboardInterrupt:
336
  break
337
  except Exception as e:
 
341
 
342
  def handle_transcription_output(self, segments, audio_buffer):
343
  texts = "".join(i.text for i in segments)
344
+ if not len(texts):
345
+ return
346
  self._segment_manager.handle(texts)
347
  # 分析句子
348
  last_cut_index, left_string, right_string, is_end_sentence = self.analysis_segments(segments, audio_buffer)
 
356
  if is_end_sentence and last_cut_index:
357
  message = self._segment_manager.segment
358
  seg_id = self._segment_manager.get_seg_id() - 1
359
+ logger.info(f"{seg_id}, {message}")
360
+ yield TransResult(
361
+ seg_id=seg_id,
362
+ context=message,
363
+ from_=self.language,
364
+ to=self.dst_lang,
365
+ tran_content="this is english sample",
366
+ partial=False
367
+ )
368
  if self._segment_manager.string.strip():
369
+ logger.info(f"{seg_id + 1}, {self._segment_manager.string}")
370
+ yield TransResult(
371
+ seg_id=seg_id+1,
372
+ context=self._segment_manager.string,
373
+ from_=self.language,
374
+ to=self.dst_lang,
375
+ tran_content="this is english sample",
376
+ )
377
 
378
  else:
379
  seg_id = self._segment_manager.get_seg_id()
380
  message = self._segment_manager.short_sentence + self._segment_manager.string
381
+ logger.info(f"{seg_id}, {message}")
382
+ yield TransResult(
383
+ seg_id=seg_id,
384
+ context=message,
385
+ from_=self.language,
386
+ to=self.dst_lang,
387
+ tran_content="this is english sample",
388
+ )
389
 
390
+ def send_to_client(self, data:TransResult):
391
+ # content = {
392
+ # "uid": self.client_uid,
393
+ # **data_dict
394
+ # }
395
  try:
396
  self.websocket.send(
397
+ Message(result=data, request_id=self.client_uid).model_dump_json(by_alias=True)
398
  )
399
  except Exception as e:
400
  logger.error(f"Sending data to client: {e}")
uv.lock CHANGED
@@ -7,6 +7,15 @@ resolution-markers = [
7
  "python_full_version < '3.12'",
8
  ]
9
 
 
 
 
 
 
 
 
 
 
10
  [[package]]
11
  name = "audioop-lts"
12
  version = "0.2.1"
@@ -763,6 +772,86 @@ wheels = [
763
  { url = "https://files.pythonhosted.org/packages/13/a3/a812df4e2dd5696d1f351d58b8fe16a405b234ad2886a0dab9183fb78109/pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc", size = 117552 },
764
  ]
765
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
766
  [[package]]
767
  name = "pyreadline3"
768
  version = "3.5.4"
@@ -1067,6 +1156,7 @@ dependencies = [
1067
  { name = "numpy" },
1068
  { name = "onnxruntime" },
1069
  { name = "pyaudio" },
 
1070
  { name = "pywhispercpp" },
1071
  { name = "setuptools" },
1072
  { name = "soundfile" },
@@ -1084,6 +1174,7 @@ requires-dist = [
1084
  { name = "numpy", specifier = ">=2.1.3" },
1085
  { name = "onnxruntime", specifier = ">=1.21.0" },
1086
  { name = "pyaudio", specifier = ">=0.2.14" },
 
1087
  { name = "pywhispercpp", specifier = ">=1.3.0" },
1088
  { name = "setuptools", specifier = ">=78.1.0" },
1089
  { name = "soundfile", specifier = ">=0.13.1" },
@@ -1112,6 +1203,18 @@ wheels = [
1112
  { url = "https://files.pythonhosted.org/packages/e0/86/39b65d676ec5732de17b7e3c476e45bb80ec64eb50737a8dce1a4178aba1/typing_extensions-4.13.0-py3-none-any.whl", hash = "sha256:c8dd92cc0d6425a97c18fbb9d1954e5ff92c1ca881a309c45f06ebc0b79058e5", size = 45683 },
1113
  ]
1114
 
 
 
 
 
 
 
 
 
 
 
 
 
1115
  [[package]]
1116
  name = "urllib3"
1117
  version = "2.3.0"
 
7
  "python_full_version < '3.12'",
8
  ]
9
 
10
+ [[package]]
11
+ name = "annotated-types"
12
+ version = "0.7.0"
13
+ source = { registry = "https://pypi.org/simple" }
14
+ sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081 }
15
+ wheels = [
16
+ { url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643 },
17
+ ]
18
+
19
  [[package]]
20
  name = "audioop-lts"
21
  version = "0.2.1"
 
772
  { url = "https://files.pythonhosted.org/packages/13/a3/a812df4e2dd5696d1f351d58b8fe16a405b234ad2886a0dab9183fb78109/pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc", size = 117552 },
773
  ]
774
 
775
+ [[package]]
776
+ name = "pydantic"
777
+ version = "2.11.2"
778
+ source = { registry = "https://pypi.org/simple" }
779
+ dependencies = [
780
+ { name = "annotated-types" },
781
+ { name = "pydantic-core" },
782
+ { name = "typing-extensions" },
783
+ { name = "typing-inspection" },
784
+ ]
785
+ sdist = { url = "https://files.pythonhosted.org/packages/b0/41/832125a41fe098b58d1fdd04ae819b4dc6b34d6b09ed78304fd93d4bc051/pydantic-2.11.2.tar.gz", hash = "sha256:2138628e050bd7a1e70b91d4bf4a91167f4ad76fdb83209b107c8d84b854917e", size = 784742 }
786
+ wheels = [
787
+ { url = "https://files.pythonhosted.org/packages/bf/c2/0f3baea344d0b15e35cb3e04ad5b953fa05106b76efbf4c782a3f47f22f5/pydantic-2.11.2-py3-none-any.whl", hash = "sha256:7f17d25846bcdf89b670a86cdfe7b29a9f1c9ca23dee154221c9aa81845cfca7", size = 443295 },
788
+ ]
789
+
790
+ [[package]]
791
+ name = "pydantic-core"
792
+ version = "2.33.1"
793
+ source = { registry = "https://pypi.org/simple" }
794
+ dependencies = [
795
+ { name = "typing-extensions" },
796
+ ]
797
+ sdist = { url = "https://files.pythonhosted.org/packages/17/19/ed6a078a5287aea7922de6841ef4c06157931622c89c2a47940837b5eecd/pydantic_core-2.33.1.tar.gz", hash = "sha256:bcc9c6fdb0ced789245b02b7d6603e17d1563064ddcfc36f046b61c0c05dd9df", size = 434395 }
798
+ wheels = [
799
+ { url = "https://files.pythonhosted.org/packages/d6/7f/c6298830cb780c46b4f46bb24298d01019ffa4d21769f39b908cd14bbd50/pydantic_core-2.33.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:6e966fc3caaf9f1d96b349b0341c70c8d6573bf1bac7261f7b0ba88f96c56c24", size = 2044224 },
800
+ { url = "https://files.pythonhosted.org/packages/a8/65/6ab3a536776cad5343f625245bd38165d6663256ad43f3a200e5936afd6c/pydantic_core-2.33.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:bfd0adeee563d59c598ceabddf2c92eec77abcb3f4a391b19aa7366170bd9e30", size = 1858845 },
801
+ { url = "https://files.pythonhosted.org/packages/e9/15/9a22fd26ba5ee8c669d4b8c9c244238e940cd5d818649603ca81d1c69861/pydantic_core-2.33.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:91815221101ad3c6b507804178a7bb5cb7b2ead9ecd600041669c8d805ebd595", size = 1910029 },
802
+ { url = "https://files.pythonhosted.org/packages/d5/33/8cb1a62818974045086f55f604044bf35b9342900318f9a2a029a1bec460/pydantic_core-2.33.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9fea9c1869bb4742d174a57b4700c6dadea951df8b06de40c2fedb4f02931c2e", size = 1997784 },
803
+ { url = "https://files.pythonhosted.org/packages/c0/ca/49958e4df7715c71773e1ea5be1c74544923d10319173264e6db122543f9/pydantic_core-2.33.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1d20eb4861329bb2484c021b9d9a977566ab16d84000a57e28061151c62b349a", size = 2141075 },
804
+ { url = "https://files.pythonhosted.org/packages/7b/a6/0b3a167a9773c79ba834b959b4e18c3ae9216b8319bd8422792abc8a41b1/pydantic_core-2.33.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0fb935c5591573ae3201640579f30128ccc10739b45663f93c06796854405505", size = 2745849 },
805
+ { url = "https://files.pythonhosted.org/packages/0b/60/516484135173aa9e5861d7a0663dce82e4746d2e7f803627d8c25dfa5578/pydantic_core-2.33.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c964fd24e6166420d18fb53996d8c9fd6eac9bf5ae3ec3d03015be4414ce497f", size = 2005794 },
806
+ { url = "https://files.pythonhosted.org/packages/86/70/05b1eb77459ad47de00cf78ee003016da0cedf8b9170260488d7c21e9181/pydantic_core-2.33.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:681d65e9011f7392db5aa002b7423cc442d6a673c635668c227c6c8d0e5a4f77", size = 2123237 },
807
+ { url = "https://files.pythonhosted.org/packages/c7/57/12667a1409c04ae7dc95d3b43158948eb0368e9c790be8b095cb60611459/pydantic_core-2.33.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e100c52f7355a48413e2999bfb4e139d2977a904495441b374f3d4fb4a170961", size = 2086351 },
808
+ { url = "https://files.pythonhosted.org/packages/57/61/cc6d1d1c1664b58fdd6ecc64c84366c34ec9b606aeb66cafab6f4088974c/pydantic_core-2.33.1-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:048831bd363490be79acdd3232f74a0e9951b11b2b4cc058aeb72b22fdc3abe1", size = 2258914 },
809
+ { url = "https://files.pythonhosted.org/packages/d1/0a/edb137176a1f5419b2ddee8bde6a0a548cfa3c74f657f63e56232df8de88/pydantic_core-2.33.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:bdc84017d28459c00db6f918a7272a5190bec3090058334e43a76afb279eac7c", size = 2257385 },
810
+ { url = "https://files.pythonhosted.org/packages/26/3c/48ca982d50e4b0e1d9954919c887bdc1c2b462801bf408613ccc641b3daa/pydantic_core-2.33.1-cp311-cp311-win32.whl", hash = "sha256:32cd11c5914d1179df70406427097c7dcde19fddf1418c787540f4b730289896", size = 1923765 },
811
+ { url = "https://files.pythonhosted.org/packages/33/cd/7ab70b99e5e21559f5de38a0928ea84e6f23fdef2b0d16a6feaf942b003c/pydantic_core-2.33.1-cp311-cp311-win_amd64.whl", hash = "sha256:2ea62419ba8c397e7da28a9170a16219d310d2cf4970dbc65c32faf20d828c83", size = 1950688 },
812
+ { url = "https://files.pythonhosted.org/packages/4b/ae/db1fc237b82e2cacd379f63e3335748ab88b5adde98bf7544a1b1bd10a84/pydantic_core-2.33.1-cp311-cp311-win_arm64.whl", hash = "sha256:fc903512177361e868bc1f5b80ac8c8a6e05fcdd574a5fb5ffeac5a9982b9e89", size = 1908185 },
813
+ { url = "https://files.pythonhosted.org/packages/c8/ce/3cb22b07c29938f97ff5f5bb27521f95e2ebec399b882392deb68d6c440e/pydantic_core-2.33.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:1293d7febb995e9d3ec3ea09caf1a26214eec45b0f29f6074abb004723fc1de8", size = 2026640 },
814
+ { url = "https://files.pythonhosted.org/packages/19/78/f381d643b12378fee782a72126ec5d793081ef03791c28a0fd542a5bee64/pydantic_core-2.33.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:99b56acd433386c8f20be5c4000786d1e7ca0523c8eefc995d14d79c7a081498", size = 1852649 },
815
+ { url = "https://files.pythonhosted.org/packages/9d/2b/98a37b80b15aac9eb2c6cfc6dbd35e5058a352891c5cce3a8472d77665a6/pydantic_core-2.33.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:35a5ec3fa8c2fe6c53e1b2ccc2454398f95d5393ab398478f53e1afbbeb4d939", size = 1892472 },
816
+ { url = "https://files.pythonhosted.org/packages/4e/d4/3c59514e0f55a161004792b9ff3039da52448f43f5834f905abef9db6e4a/pydantic_core-2.33.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b172f7b9d2f3abc0efd12e3386f7e48b576ef309544ac3a63e5e9cdd2e24585d", size = 1977509 },
817
+ { url = "https://files.pythonhosted.org/packages/a9/b6/c2c7946ef70576f79a25db59a576bce088bdc5952d1b93c9789b091df716/pydantic_core-2.33.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9097b9f17f91eea659b9ec58148c0747ec354a42f7389b9d50701610d86f812e", size = 2128702 },
818
+ { url = "https://files.pythonhosted.org/packages/88/fe/65a880f81e3f2a974312b61f82a03d85528f89a010ce21ad92f109d94deb/pydantic_core-2.33.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cc77ec5b7e2118b152b0d886c7514a4653bcb58c6b1d760134a9fab915f777b3", size = 2679428 },
819
+ { url = "https://files.pythonhosted.org/packages/6f/ff/4459e4146afd0462fb483bb98aa2436d69c484737feaceba1341615fb0ac/pydantic_core-2.33.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d5e3d15245b08fa4a84cefc6c9222e6f37c98111c8679fbd94aa145f9a0ae23d", size = 2008753 },
820
+ { url = "https://files.pythonhosted.org/packages/7c/76/1c42e384e8d78452ededac8b583fe2550c84abfef83a0552e0e7478ccbc3/pydantic_core-2.33.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ef99779001d7ac2e2461d8ab55d3373fe7315caefdbecd8ced75304ae5a6fc6b", size = 2114849 },
821
+ { url = "https://files.pythonhosted.org/packages/00/72/7d0cf05095c15f7ffe0eb78914b166d591c0eed72f294da68378da205101/pydantic_core-2.33.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:fc6bf8869e193855e8d91d91f6bf59699a5cdfaa47a404e278e776dd7f168b39", size = 2069541 },
822
+ { url = "https://files.pythonhosted.org/packages/b3/69/94a514066bb7d8be499aa764926937409d2389c09be0b5107a970286ef81/pydantic_core-2.33.1-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:b1caa0bc2741b043db7823843e1bde8aaa58a55a58fda06083b0569f8b45693a", size = 2239225 },
823
+ { url = "https://files.pythonhosted.org/packages/84/b0/e390071eadb44b41f4f54c3cef64d8bf5f9612c92686c9299eaa09e267e2/pydantic_core-2.33.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:ec259f62538e8bf364903a7d0d0239447059f9434b284f5536e8402b7dd198db", size = 2248373 },
824
+ { url = "https://files.pythonhosted.org/packages/d6/b2/288b3579ffc07e92af66e2f1a11be3b056fe1214aab314748461f21a31c3/pydantic_core-2.33.1-cp312-cp312-win32.whl", hash = "sha256:e14f369c98a7c15772b9da98987f58e2b509a93235582838bd0d1d8c08b68fda", size = 1907034 },
825
+ { url = "https://files.pythonhosted.org/packages/02/28/58442ad1c22b5b6742b992ba9518420235adced665513868f99a1c2638a5/pydantic_core-2.33.1-cp312-cp312-win_amd64.whl", hash = "sha256:1c607801d85e2e123357b3893f82c97a42856192997b95b4d8325deb1cd0c5f4", size = 1956848 },
826
+ { url = "https://files.pythonhosted.org/packages/a1/eb/f54809b51c7e2a1d9f439f158b8dd94359321abcc98767e16fc48ae5a77e/pydantic_core-2.33.1-cp312-cp312-win_arm64.whl", hash = "sha256:8d13f0276806ee722e70a1c93da19748594f19ac4299c7e41237fc791d1861ea", size = 1903986 },
827
+ { url = "https://files.pythonhosted.org/packages/7a/24/eed3466a4308d79155f1cdd5c7432c80ddcc4530ba8623b79d5ced021641/pydantic_core-2.33.1-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:70af6a21237b53d1fe7b9325b20e65cbf2f0a848cf77bed492b029139701e66a", size = 2033551 },
828
+ { url = "https://files.pythonhosted.org/packages/ab/14/df54b1a0bc9b6ded9b758b73139d2c11b4e8eb43e8ab9c5847c0a2913ada/pydantic_core-2.33.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:282b3fe1bbbe5ae35224a0dbd05aed9ccabccd241e8e6b60370484234b456266", size = 1852785 },
829
+ { url = "https://files.pythonhosted.org/packages/fa/96/e275f15ff3d34bb04b0125d9bc8848bf69f25d784d92a63676112451bfb9/pydantic_core-2.33.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4b315e596282bbb5822d0c7ee9d255595bd7506d1cb20c2911a4da0b970187d3", size = 1897758 },
830
+ { url = "https://files.pythonhosted.org/packages/b7/d8/96bc536e975b69e3a924b507d2a19aedbf50b24e08c80fb00e35f9baaed8/pydantic_core-2.33.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:1dfae24cf9921875ca0ca6a8ecb4bb2f13c855794ed0d468d6abbec6e6dcd44a", size = 1986109 },
831
+ { url = "https://files.pythonhosted.org/packages/90/72/ab58e43ce7e900b88cb571ed057b2fcd0e95b708a2e0bed475b10130393e/pydantic_core-2.33.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6dd8ecfde08d8bfadaea669e83c63939af76f4cf5538a72597016edfa3fad516", size = 2129159 },
832
+ { url = "https://files.pythonhosted.org/packages/dc/3f/52d85781406886c6870ac995ec0ba7ccc028b530b0798c9080531b409fdb/pydantic_core-2.33.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2f593494876eae852dc98c43c6f260f45abdbfeec9e4324e31a481d948214764", size = 2680222 },
833
+ { url = "https://files.pythonhosted.org/packages/f4/56/6e2ef42f363a0eec0fd92f74a91e0ac48cd2e49b695aac1509ad81eee86a/pydantic_core-2.33.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:948b73114f47fd7016088e5186d13faf5e1b2fe83f5e320e371f035557fd264d", size = 2006980 },
834
+ { url = "https://files.pythonhosted.org/packages/4c/c0/604536c4379cc78359f9ee0aa319f4aedf6b652ec2854953f5a14fc38c5a/pydantic_core-2.33.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e11f3864eb516af21b01e25fac915a82e9ddad3bb0fb9e95a246067398b435a4", size = 2120840 },
835
+ { url = "https://files.pythonhosted.org/packages/1f/46/9eb764814f508f0edfb291a0f75d10854d78113fa13900ce13729aaec3ae/pydantic_core-2.33.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:549150be302428b56fdad0c23c2741dcdb5572413776826c965619a25d9c6bde", size = 2072518 },
836
+ { url = "https://files.pythonhosted.org/packages/42/e3/fb6b2a732b82d1666fa6bf53e3627867ea3131c5f39f98ce92141e3e3dc1/pydantic_core-2.33.1-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:495bc156026efafd9ef2d82372bd38afce78ddd82bf28ef5276c469e57c0c83e", size = 2248025 },
837
+ { url = "https://files.pythonhosted.org/packages/5c/9d/fbe8fe9d1aa4dac88723f10a921bc7418bd3378a567cb5e21193a3c48b43/pydantic_core-2.33.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:ec79de2a8680b1a67a07490bddf9636d5c2fab609ba8c57597e855fa5fa4dacd", size = 2254991 },
838
+ { url = "https://files.pythonhosted.org/packages/aa/99/07e2237b8a66438d9b26482332cda99a9acccb58d284af7bc7c946a42fd3/pydantic_core-2.33.1-cp313-cp313-win32.whl", hash = "sha256:ee12a7be1742f81b8a65b36c6921022301d466b82d80315d215c4c691724986f", size = 1915262 },
839
+ { url = "https://files.pythonhosted.org/packages/8a/f4/e457a7849beeed1e5defbcf5051c6f7b3c91a0624dd31543a64fc9adcf52/pydantic_core-2.33.1-cp313-cp313-win_amd64.whl", hash = "sha256:ede9b407e39949d2afc46385ce6bd6e11588660c26f80576c11c958e6647bc40", size = 1956626 },
840
+ { url = "https://files.pythonhosted.org/packages/20/d0/e8d567a7cff7b04e017ae164d98011f1e1894269fe8e90ea187a3cbfb562/pydantic_core-2.33.1-cp313-cp313-win_arm64.whl", hash = "sha256:aa687a23d4b7871a00e03ca96a09cad0f28f443690d300500603bd0adba4b523", size = 1909590 },
841
+ { url = "https://files.pythonhosted.org/packages/ef/fd/24ea4302d7a527d672c5be06e17df16aabfb4e9fdc6e0b345c21580f3d2a/pydantic_core-2.33.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:401d7b76e1000d0dd5538e6381d28febdcacb097c8d340dde7d7fc6e13e9f95d", size = 1812963 },
842
+ { url = "https://files.pythonhosted.org/packages/5f/95/4fbc2ecdeb5c1c53f1175a32d870250194eb2fdf6291b795ab08c8646d5d/pydantic_core-2.33.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7aeb055a42d734c0255c9e489ac67e75397d59c6fbe60d155851e9782f276a9c", size = 1986896 },
843
+ { url = "https://files.pythonhosted.org/packages/71/ae/fe31e7f4a62431222d8f65a3bd02e3fa7e6026d154a00818e6d30520ea77/pydantic_core-2.33.1-cp313-cp313t-win_amd64.whl", hash = "sha256:338ea9b73e6e109f15ab439e62cb3b78aa752c7fd9536794112e14bee02c8d18", size = 1931810 },
844
+ { url = "https://files.pythonhosted.org/packages/0b/76/1794e440c1801ed35415238d2c728f26cd12695df9057154ad768b7b991c/pydantic_core-2.33.1-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:3a371dc00282c4b84246509a5ddc808e61b9864aa1eae9ecc92bb1268b82db4a", size = 2042858 },
845
+ { url = "https://files.pythonhosted.org/packages/73/b4/9cd7b081fb0b1b4f8150507cd59d27b275c3e22ad60b35cb19ea0977d9b9/pydantic_core-2.33.1-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:f59295ecc75a1788af8ba92f2e8c6eeaa5a94c22fc4d151e8d9638814f85c8fc", size = 1873745 },
846
+ { url = "https://files.pythonhosted.org/packages/e1/d7/9ddb7575d4321e40d0363903c2576c8c0c3280ebea137777e5ab58d723e3/pydantic_core-2.33.1-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:08530b8ac922003033f399128505f513e30ca770527cc8bbacf75a84fcc2c74b", size = 1904188 },
847
+ { url = "https://files.pythonhosted.org/packages/d1/a8/3194ccfe461bb08da19377ebec8cb4f13c9bd82e13baebc53c5c7c39a029/pydantic_core-2.33.1-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bae370459da6a5466978c0eacf90690cb57ec9d533f8e63e564ef3822bfa04fe", size = 2083479 },
848
+ { url = "https://files.pythonhosted.org/packages/42/c7/84cb569555d7179ca0b3f838cef08f66f7089b54432f5b8599aac6e9533e/pydantic_core-2.33.1-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e3de2777e3b9f4d603112f78006f4ae0acb936e95f06da6cb1a45fbad6bdb4b5", size = 2118415 },
849
+ { url = "https://files.pythonhosted.org/packages/3b/67/72abb8c73e0837716afbb58a59cc9e3ae43d1aa8677f3b4bc72c16142716/pydantic_core-2.33.1-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:3a64e81e8cba118e108d7126362ea30e021291b7805d47e4896e52c791be2761", size = 2079623 },
850
+ { url = "https://files.pythonhosted.org/packages/0b/cd/c59707e35a47ba4cbbf153c3f7c56420c58653b5801b055dc52cccc8e2dc/pydantic_core-2.33.1-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:52928d8c1b6bda03cc6d811e8923dffc87a2d3c8b3bfd2ce16471c7147a24850", size = 2250175 },
851
+ { url = "https://files.pythonhosted.org/packages/84/32/e4325a6676b0bed32d5b084566ec86ed7fd1e9bcbfc49c578b1755bde920/pydantic_core-2.33.1-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:1b30d92c9412beb5ac6b10a3eb7ef92ccb14e3f2a8d7732e2d739f58b3aa7544", size = 2254674 },
852
+ { url = "https://files.pythonhosted.org/packages/12/6f/5596dc418f2e292ffc661d21931ab34591952e2843e7168ea5a52591f6ff/pydantic_core-2.33.1-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:f995719707e0e29f0f41a8aa3bcea6e761a36c9136104d3189eafb83f5cec5e5", size = 2080951 },
853
+ ]
854
+
855
  [[package]]
856
  name = "pyreadline3"
857
  version = "3.5.4"
 
1156
  { name = "numpy" },
1157
  { name = "onnxruntime" },
1158
  { name = "pyaudio" },
1159
+ { name = "pydantic" },
1160
  { name = "pywhispercpp" },
1161
  { name = "setuptools" },
1162
  { name = "soundfile" },
 
1174
  { name = "numpy", specifier = ">=2.1.3" },
1175
  { name = "onnxruntime", specifier = ">=1.21.0" },
1176
  { name = "pyaudio", specifier = ">=0.2.14" },
1177
+ { name = "pydantic", specifier = ">=2.11.2" },
1178
  { name = "pywhispercpp", specifier = ">=1.3.0" },
1179
  { name = "setuptools", specifier = ">=78.1.0" },
1180
  { name = "soundfile", specifier = ">=0.13.1" },
 
1203
  { url = "https://files.pythonhosted.org/packages/e0/86/39b65d676ec5732de17b7e3c476e45bb80ec64eb50737a8dce1a4178aba1/typing_extensions-4.13.0-py3-none-any.whl", hash = "sha256:c8dd92cc0d6425a97c18fbb9d1954e5ff92c1ca881a309c45f06ebc0b79058e5", size = 45683 },
1204
  ]
1205
 
1206
+ [[package]]
1207
+ name = "typing-inspection"
1208
+ version = "0.4.0"
1209
+ source = { registry = "https://pypi.org/simple" }
1210
+ dependencies = [
1211
+ { name = "typing-extensions" },
1212
+ ]
1213
+ sdist = { url = "https://files.pythonhosted.org/packages/82/5c/e6082df02e215b846b4b8c0b887a64d7d08ffaba30605502639d44c06b82/typing_inspection-0.4.0.tar.gz", hash = "sha256:9765c87de36671694a67904bf2c96e395be9c6439bb6c87b5142569dcdd65122", size = 76222 }
1214
+ wheels = [
1215
+ { url = "https://files.pythonhosted.org/packages/31/08/aa4fdfb71f7de5176385bd9e90852eaf6b5d622735020ad600f2bab54385/typing_inspection-0.4.0-py3-none-any.whl", hash = "sha256:50e72559fcd2a6367a19f7a7e610e6afcb9fac940c650290eed893d61386832f", size = 14125 },
1216
+ ]
1217
+
1218
  [[package]]
1219
  name = "urllib3"
1220
  version = "2.3.0"