suprimedev commited on
Commit
b2d26db
·
verified ·
1 Parent(s): e92b5fd

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +108 -43
app.py CHANGED
@@ -14,17 +14,79 @@ import time
14
  recognizer = sr.Recognizer()
15
  recognizer.energy_threshold = 300
16
  recognizer.dynamic_energy_threshold = True
 
17
 
18
- # صف برای پردازش real-time
19
- audio_queue = queue.Queue()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
20
 
21
  def numpy_to_audio_segment(audio_data, sample_rate):
22
- """تبدیل numpy array به AudioSegment"""
23
- if audio_data is None:
24
  return None
25
 
26
- # نرمال‌سازی و تبدیل به int16
27
  if audio_data.dtype == np.float32 or audio_data.dtype == np.float64:
 
28
  audio_data = (audio_data * 32767).astype(np.int16)
29
 
30
  # ذخیره در buffer موقت
@@ -39,16 +101,20 @@ def numpy_to_audio_segment(audio_data, sample_rate):
39
  return AudioSegment.from_wav(buffer)
40
 
41
  def process_audio_chunk(audio_tuple):
42
- """پردازش یک قطعه صوتی با Google Speech Recognition"""
43
  try:
44
  if audio_tuple is None:
45
  return ""
46
 
47
  sample_rate, audio_data = audio_tuple
 
 
 
 
48
 
49
  # تبدیل به AudioSegment
50
  audio_segment = numpy_to_audio_segment(audio_data, sample_rate)
51
- if audio_segment is None:
52
  return ""
53
 
54
  # ذخیره موقت برای speech recognition
@@ -56,8 +122,9 @@ def process_audio_chunk(audio_tuple):
56
  audio_segment.export(tmp_file.name, format="wav")
57
  tmp_path = tmp_file.name
58
 
59
- # استفاده از speech recognition
60
  with sr.AudioFile(tmp_path) as source:
 
61
  audio = recognizer.record(source)
62
 
63
  # تشخیص با اولویت فارسی
@@ -69,55 +136,53 @@ def process_audio_chunk(audio_tuple):
69
  try:
70
  text = recognizer.recognize_google(audio, language='en-US')
71
  except sr.UnknownValueError:
72
- text = ""
73
- except sr.RequestError:
 
74
  text = "[خطا در اتصال]"
 
75
 
76
  # پاک کردن فایل موقت
77
  if os.path.exists(tmp_path):
78
  os.unlink(tmp_path)
79
 
80
- return text
81
 
82
  except Exception as e:
83
  print(f"خطا در پردازش: {e}")
84
  return ""
85
 
86
- # متغیر global برای ذخیره متن real-time
87
- current_transcript = ""
88
- transcript_lock = threading.Lock()
89
-
90
  def transcribe_realtime(audio):
91
- """تبدیل real-time صوت به متن"""
92
  global current_transcript
93
 
94
  if audio is None:
95
  return current_transcript
96
 
97
- # پردازش در thread جداگانه برای جلوگیری از blocking
98
- def process_async():
99
- global current_transcript
100
- text = process_audio_chunk(audio)
101
- if text and text != "[خطا در اتصال]":
102
- with transcript_lock:
103
- if current_transcript:
104
- current_transcript += " " + text
105
- else:
106
- current_transcript = text
107
-
108
- thread = threading.Thread(target=process_async)
109
- thread.daemon = True
110
- thread.start()
111
 
112
- return current_transcript
 
 
113
 
114
  def clear_realtime():
115
  """پاک کردن متن real-time"""
116
  global current_transcript
117
  with transcript_lock:
118
  current_transcript = ""
 
 
 
 
 
 
119
  return ""
120
 
 
121
  def transcribe_file(audio_file, chunk_duration=30):
122
  """تبدیل فایل صوتی به متن با تقسیم به بخش‌ها"""
123
  if audio_file is None:
@@ -198,7 +263,7 @@ def save_text(text):
198
 
199
  return temp_file.name
200
 
201
- # رابط کاربری Gradio
202
  with gr.Blocks(
203
  title="تبدیل گفتار به متن",
204
  theme=gr.themes.Soft(),
@@ -248,14 +313,14 @@ with gr.Blocks(
248
 
249
  realtime_output = gr.Textbox(
250
  label="متن تشخیص داده شده",
251
- placeholder="شروع به صحبت کنید و متن اینجا ظاهر می‌شود...",
252
  lines=12,
253
  elem_classes="rtl",
254
  rtl=True,
255
  show_copy_button=True
256
  )
257
 
258
- # اتصال برای real-time transcription
259
  audio_input.stream(
260
  transcribe_realtime,
261
  inputs=[audio_input],
@@ -268,7 +333,7 @@ with gr.Blocks(
268
  outputs=[realtime_output]
269
  )
270
 
271
- # تب فایل صوتی
272
  with gr.TabItem("📁 فایل صوتی"):
273
  gr.Markdown("### فایل صوتی خود را انتخاب کنید", elem_classes="rtl")
274
 
@@ -349,16 +414,16 @@ with gr.Blocks(
349
  outputs=[file_output, progress_label]
350
  )
351
 
352
- # بخش راهنما
353
  with gr.Accordion("📖 راهنمای استفاده", open=False, elem_classes="rtl"):
354
  gr.Markdown("""
355
  ### نحوه استفاده:
356
 
357
- **برای ضبط مستقیم:**
358
  1. به تب "ضبط مستقیم" بروید
359
  2. اجازه دسترسی به میکروفون را بدهید
360
- 3. شروع به صحبت کنید
361
- 4. متن به صورت خودکار نمایش داده می‌شود
362
 
363
  **برای فایل صوتی:**
364
  1. به تب "فایل صوتی" بروید
@@ -373,23 +438,23 @@ with gr.Blocks(
373
  ### نکات مهم:
374
  - 🎯 برای دقت بیشتر، از فایل‌های با کیفیت بالا استفاده کنید
375
  - 🔇 نویز پس‌زمینه را به حداقل برسانید
376
- - 🗣️ واضح و شمرده صحبت کنید
377
  - 🌐 اتصال اینترنت پایدار داشته باشید
 
378
  """, elem_classes="rtl")
379
 
380
  # فوتر
381
  gr.HTML("""
382
  <div style="text-align: center; margin-top: 2em; padding: 1em; background-color: #f8f9fa;">
383
  <p style="color: #666;">
384
- ساخته شده با ❤️ | نسخه 2.0
385
  </p>
386
  </div>
387
  """)
388
 
389
-
390
  # اجرای برنامه
391
  if __name__ == "__main__":
392
  demo.queue().launch(
393
  share=True,
394
  show_error=True
395
- )
 
14
  recognizer = sr.Recognizer()
15
  recognizer.energy_threshold = 300
16
  recognizer.dynamic_energy_threshold = True
17
+ recognizer.pause_threshold = 0.8 # اضافه: pause detection بهتر برای real-time
18
 
19
+ # صف برای accumulate کردن chunk‌های real-time (هر chunk کوچک ~0.5-1s)
20
+ audio_queue = queue.Queue(maxsize=20) # محدود برای جلوگیری از overflow
21
+ CHUNK_PROCESS_INTERVAL = 3.0 # ثانیه: هر چند ثانیه chunk‌ها را process کنیم
22
+ MIN_AUDIO_DURATION = 2.0 # ثانیه: حداقل طول صوت برای process
23
+
24
+ # متغیر global برای ذخیره متن real-time
25
+ current_transcript = ""
26
+ transcript_lock = threading.Lock()
27
+
28
+ # Thread background برای پردازش مداوم queue
29
+ def background_processor():
30
+ """Thread جداگانه برای polling queue و process chunk‌های accumulate‌شده"""
31
+ accumulated_audio = [] # لیست برای جمع chunk‌ها
32
+ last_process_time = time.time()
33
+
34
+ while True:
35
+ try:
36
+ # اگر queue خالی نیست، chunk جدید اضافه کن
37
+ if not audio_queue.empty():
38
+ audio_tuple = audio_queue.get(timeout=0.1)
39
+ if audio_tuple:
40
+ sample_rate, audio_data = audio_tuple
41
+ # Normalize و append به accumulated
42
+ if audio_data is not None and len(audio_data) > 0:
43
+ # Clip به [-1, 1] برای جلوگیری از overflow
44
+ audio_data = np.clip(audio_data, -1.0, 1.0)
45
+ accumulated_audio.append((sample_rate, audio_data))
46
+ print(f"Chunk accumulated: {len(audio_data)/sample_rate:.2f}s") # log
47
+
48
+ # هر INTERVAL، اگر طول accumulated کافی باشد، process کن
49
+ current_time = time.time()
50
+ total_duration = sum(len(data)/rate for rate, data in accumulated_audio) if accumulated_audio else 0
51
+ if (current_time - last_process_time >= CHUNK_PROCESS_INTERVAL) and total_duration >= MIN_AUDIO_DURATION:
52
+ # Merge chunk‌ها به یک audio واحد
53
+ if len(accumulated_audio) > 0:
54
+ merged_rate = accumulated_audio[0][0] # فرض sample_rate ثابت
55
+ merged_data = np.concatenate([data for _, data in accumulated_audio])
56
+
57
+ text = process_audio_chunk((merged_rate, merged_data))
58
+ if text and text != "[خطا در اتصال]":
59
+ with transcript_lock:
60
+ if current_transcript:
61
+ current_transcript += " " + text
62
+ else:
63
+ current_transcript = text
64
+ print(f"Processed: {text}") # log
65
+
66
+ # Reset accumulated
67
+ accumulated_audio = []
68
+ last_process_time = current_time
69
+
70
+ time.sleep(0.1) # poll هر 100ms
71
+
72
+ except queue.Empty:
73
+ continue
74
+ except Exception as e:
75
+ print(f"Error in background processor: {e}")
76
+ time.sleep(0.5)
77
+
78
+ # شروع thread background (یک بار)
79
+ processor_thread = threading.Thread(target=background_processor, daemon=True)
80
+ processor_thread.start()
81
 
82
  def numpy_to_audio_segment(audio_data, sample_rate):
83
+ """تبدیل numpy array به AudioSegment (بهبود: clip و handling بهتر)"""
84
+ if audio_data is None or len(audio_data) == 0:
85
  return None
86
 
87
+ # نرمال‌سازی و clip به int16
88
  if audio_data.dtype == np.float32 or audio_data.dtype == np.float64:
89
+ audio_data = np.clip(audio_data, -1.0, 1.0) # clip برای جلوگیری از distortion
90
  audio_data = (audio_data * 32767).astype(np.int16)
91
 
92
  # ذخیره در buffer موقت
 
101
  return AudioSegment.from_wav(buffer)
102
 
103
  def process_audio_chunk(audio_tuple):
104
+ """پردازش یک قطعه صوتی با Google Speech Recognition (بهبود: handling chunk کوتاه)"""
105
  try:
106
  if audio_tuple is None:
107
  return ""
108
 
109
  sample_rate, audio_data = audio_tuple
110
+ duration = len(audio_data) / sample_rate if sample_rate > 0 else 0
111
+
112
+ if duration < MIN_AUDIO_DURATION:
113
+ return "" # skip chunk‌های خیلی کوتاه
114
 
115
  # تبدیل به AudioSegment
116
  audio_segment = numpy_to_audio_segment(audio_data, sample_rate)
117
+ if audio_segment is None or len(audio_segment) < MIN_AUDIO_DURATION * 1000:
118
  return ""
119
 
120
  # ذخیره موقت برای speech recognition
 
122
  audio_segment.export(tmp_file.name, format="wav")
123
  tmp_path = tmp_file.name
124
 
125
+ # استفاده از speech recognition با adjust برای chunk
126
  with sr.AudioFile(tmp_path) as source:
127
+ recognizer.adjust_for_ambient_noise(source, duration=0.5) # adjust noise برای real-time
128
  audio = recognizer.record(source)
129
 
130
  # تشخیص با اولویت فارسی
 
136
  try:
137
  text = recognizer.recognize_google(audio, language='en-US')
138
  except sr.UnknownValueError:
139
+ text = "" # بهبود: لاگ
140
+ print("No speech detected in chunk")
141
+ except sr.RequestError as e:
142
  text = "[خطا در اتصال]"
143
+ print(f"Google API error: {e}")
144
 
145
  # پاک کردن فایل موقت
146
  if os.path.exists(tmp_path):
147
  os.unlink(tmp_path)
148
 
149
+ return text.strip()
150
 
151
  except Exception as e:
152
  print(f"خطا در پردازش: {e}")
153
  return ""
154
 
 
 
 
 
155
  def transcribe_realtime(audio):
156
+ """تبدیل real-time صوت به متن (بهبود: queue chunk و return current)"""
157
  global current_transcript
158
 
159
  if audio is None:
160
  return current_transcript
161
 
162
+ # Add to queue (non-blocking)
163
+ try:
164
+ audio_queue.put(audio, block=False)
165
+ except queue.Full:
166
+ print("Queue full, skipping chunk") # log
 
 
 
 
 
 
 
 
 
167
 
168
+ # Return current transcript برای آپدیت UI
169
+ with transcript_lock:
170
+ return current_transcript
171
 
172
  def clear_realtime():
173
  """پاک کردن متن real-time"""
174
  global current_transcript
175
  with transcript_lock:
176
  current_transcript = ""
177
+ # Clear queue هم
178
+ while not audio_queue.empty():
179
+ try:
180
+ audio_queue.get_nowait()
181
+ except queue.Empty:
182
+ break
183
  return ""
184
 
185
+ # توابع file و UI بدون تغییر (برای اختصار، اما کامل هستند)
186
  def transcribe_file(audio_file, chunk_duration=30):
187
  """تبدیل فایل صوتی به متن با تقسیم به بخش‌ها"""
188
  if audio_file is None:
 
263
 
264
  return temp_file.name
265
 
266
+ # رابط کاربری Gradio (بدون تغییر، اما اتصال real-time بهبود یافته)
267
  with gr.Blocks(
268
  title="تبدیل گفتار به متن",
269
  theme=gr.themes.Soft(),
 
313
 
314
  realtime_output = gr.Textbox(
315
  label="متن تشخیص داده شده",
316
+ placeholder="شروع به صحبت کنید و متن اینجا ظاهر می‌شود... (پس از 3-5 ثانیه)",
317
  lines=12,
318
  elem_classes="rtl",
319
  rtl=True,
320
  show_copy_button=True
321
  )
322
 
323
+ # اتصال برای real-time transcription (بهبود: stream هر chunk به queue)
324
  audio_input.stream(
325
  transcribe_realtime,
326
  inputs=[audio_input],
 
333
  outputs=[realtime_output]
334
  )
335
 
336
+ # تب فایل صوتی (بدون تغییر)
337
  with gr.TabItem("📁 فایل صوتی"):
338
  gr.Markdown("### فایل صوتی خود را انتخاب کنید", elem_classes="rtl")
339
 
 
414
  outputs=[file_output, progress_label]
415
  )
416
 
417
+ # بخش راهنما (بهبود: نکته real-time)
418
  with gr.Accordion("📖 راهنمای استفاده", open=False, elem_classes="rtl"):
419
  gr.Markdown("""
420
  ### نحوه استفاده:
421
 
422
+ **برای ضبط مستقیم (بهبود یافته):**
423
  1. به تب "ضبط مستقیم" بروید
424
  2. اجازه دسترسی به میکروفون را بدهید
425
+ 3. حداقل 3-5 ثانیه واضح صحبت کنید (chunk‌ها جمع می‌شوند)
426
+ 4. متن هر 3 ثانیه آپدیت می‌شود
427
 
428
  **برای فایل صوتی:**
429
  1. به تب "فایل صوتی" بروید
 
438
  ### نکات مهم:
439
  - 🎯 برای دقت بیشتر، از فایل‌های با کیفیت بالا استفاده کنید
440
  - 🔇 نویز پس‌زمینه را به حداقل برسانید
441
+ - 🗣️ واضح و شمرده صحبت کنید (حداقل 3 ثانیه)
442
  - 🌐 اتصال اینترنت پایدار داشته باشید
443
+ - ⚠️ Real-time ممکن است 1-2 ثانیه تاخیر داشته باشد
444
  """, elem_classes="rtl")
445
 
446
  # فوتر
447
  gr.HTML("""
448
  <div style="text-align: center; margin-top: 2em; padding: 1em; background-color: #f8f9fa;">
449
  <p style="color: #666;">
450
+ ساخته شده با ❤️ | نسخه 2.1 (بهبود real-time)
451
  </p>
452
  </div>
453
  """)
454
 
 
455
  # اجرای برنامه
456
  if __name__ == "__main__":
457
  demo.queue().launch(
458
  share=True,
459
  show_error=True
460
+ )