suprimedev commited on
Commit
4881f89
·
verified ·
1 Parent(s): 5abdca1

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +267 -219
app.py CHANGED
@@ -1,304 +1,352 @@
1
- # ─────────────────────────────────────────────────────────────────────
2
- # File: app.py
3
- # ─────────────────────────────────────────────────────────────────────
4
- # این اسکریپت دو نقش دارد
5
- # 1. یک UI Gradio برای تبدیل ویدیو به MP3 و استخراج متن
6
- # 2. یک API ساده (Flask) برای دریافت وضعیت و نهایی‌سازی خروجی
7
- # 3. نگه‌داری یک دیکشنری `session_map` به‌صورت thread‑safe
8
- # تا `token` تولید‌شده در PHP را با `session_hash` Gradio مطابقت دهیم
9
- # ─────────────────────────────────────────────────────────────────────
10
  import os
11
- import uuid
12
- import json
13
  import shutil
14
- import tempfile
15
- import warnings
16
- import time
17
- import threading
18
- import yt_dlp
19
  import speech_recognition as sr
20
  from pydub import AudioSegment
21
- from typing import Tuple, Optional
22
-
23
- from flask import Flask, request, jsonify
24
- import gradio as gr
 
 
25
 
26
  warnings.filterwarnings("ignore")
27
 
28
- # ------------------------------------------------------------
29
- # ۱. نگه‌داری mapping token ↔︎ session_hash
30
- # ------------------------------------------------------------
31
- session_map = {} # {token : session_hash}
32
- session_lock = threading.Lock()
33
-
34
- # ------------------------------------------------------------
35
- # ۲. توابع اصلی تبدیل/تبدیل‌تکست
36
- # ------------------------------------------------------------
37
- def language_display(lang_code: str) -> str:
38
- return {"fa-IR":"پارسی","en-US":"انگلیسی"}.get(lang_code, lang_code)
39
-
40
-
41
- def transcribe_audio(mp3_path, progress, language,
42
- chunk_length_ms=60000, overlap_ms=5000) -> Tuple[Optional[str], str]:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
43
  recognizer = sr.Recognizer()
44
  recognizer.energy_threshold = 300
45
  recognizer.dynamic_energy_threshold = True
46
  recognizer.pause_threshold = 0.8
47
-
48
  full_text = []
49
  bad_chunks = 0
50
  total_chunks = 0
51
  temp_wav_dir = tempfile.mkdtemp()
52
-
53
  audio = AudioSegment.from_mp3(mp3_path)
54
  duration_ms = len(audio)
55
-
56
  if duration_ms == 0:
 
57
  return None, "فایل صوتی خالی یا بدون صدا."
58
-
59
  step_size = chunk_length_ms - overlap_ms
60
  if step_size <= 0:
61
  step_size = chunk_length_ms // 2
62
  num_chunks = max(1, (duration_ms // step_size) + 1)
63
-
64
- progress(0.5, desc=f"تقسیم به {num_chunks} chunk 60s ")
65
-
 
 
66
  i = 0
67
  chunk_idx = 1
68
  while i < duration_ms:
69
  end_pos = min(i + chunk_length_ms, duration_ms)
70
  chunk = audio[i:end_pos]
71
-
72
- if len(chunk) < 3000: # کوتاه‌تر از 3 ثانیه
 
73
  break
74
-
75
  temp_wav = os.path.join(temp_wav_dir, f"chunk_{i}.wav")
76
-
77
  try:
78
  chunk.export(temp_wav, format="wav")
79
-
 
80
  text_chunk = None
81
  retry_count = 0
82
  max_retries = 3
 
83
  while retry_count < max_retries:
84
  try:
85
- progress(0.5 + (i / duration_ms) * 0.5,
86
- desc=f"Chunk {chunk_idx}/{num_chunks} ({i/1000:.0f}-{end_pos/1000:.0f}s) …")
87
-
88
  with sr.AudioFile(temp_wav) as source:
89
  recognizer.adjust_for_ambient_noise(source, duration=0.5)
90
  audio_data = recognizer.record(source, duration=None)
91
-
92
  text = recognizer.recognize_google(audio_data, language=language)
93
  if text.strip():
94
  text_chunk = text
 
95
  break
96
  else:
97
  text_chunk = "[سکوت]"
 
98
  break
99
-
100
  except sr.UnknownValueError:
101
  text_chunk = "[نامشخص]"
 
102
  break
103
-
104
  except sr.RequestError as e:
105
  retry_count += 1
106
- time.sleep(2)
107
- if retry_count == max_retries:
108
- text_chunk = f"[خطا rate limit: {str(e)[:30]}…]"
 
 
109
  bad_chunks += 1
110
-
 
 
111
  if text_chunk:
112
  full_text.append(text_chunk)
113
  if "[نامشخص" in text_chunk or "[خطا" in text_chunk:
114
  bad_chunks += 1
115
-
116
  total_chunks += 1
117
-
118
  except Exception as chunk_e:
119
- text_chunk = f"[خطا chunk: {str(chunk_e)[:30]}…]"
 
120
  full_text.append(text_chunk)
121
  bad_chunks += 1
122
  total_chunks += 1
123
-
124
- finally:
125
- if os.path.exists(temp_wav):
126
- os.remove(temp_wav)
127
-
128
  i += step_size
129
  chunk_idx += 1
130
-
131
  shutil.rmtree(temp_wav_dir, ignore_errors=True)
132
-
133
  final_text = ' '.join(full_text).strip()
 
 
134
  if not final_text:
135
- return None, "هیچ chunk موفقی نبود."
136
-
137
- bad_ratio = bad_chunks / total_chunks if total_chunks else 1
 
138
  if bad_ratio > 0.7:
139
- return None, f"بیش از 70% chunkها fail ({bad_ratio*100:.0f}%)"
140
-
141
- return final_text, ""
142
-
143
-
144
- def convert_to_mp3_and_transcribe(video_url: str, language: str,
145
- progress=gr.Progress()) -> Tuple[Optional[str], Optional[str], str]:
146
- if not video_url:
147
- return None, None, "لینک ویدیو را وارد کنید."
148
-
149
- progress(0, desc="شروع دانلود…")
150
-
151
- # ۱. دانلود
152
- ydl_opts = {
153
- 'format': 'bestaudio[ext=m4a]/bestaudio/best',
154
- 'postprocessors': [{
155
- 'key': 'FFmpegExtractAudio',
156
- 'preferredcodec': 'mp3',
157
- 'preferredquality': '192',
158
- }],
159
- 'outtmpl': 'temp.%(ext)s',
160
- 'quiet': True,
161
  }
162
- try:
163
- with yt_dlp.YoutubeDL(ydl_opts) as ydl:
164
- ydl.download([video_url])
165
- except Exception as e:
166
- return None, None, f"خطا در دانلود: {e}"
167
 
168
- # ۲. پیدا کردن فایل MP3
169
- mp3_file = None
170
- for f in os.listdir('.'):
171
- if f.startswith('temp.') and f.endswith('.mp3'):
172
- mp3_file = f
173
- break
174
- if not mp3_file:
175
- return None, None, "فایل mp3 پیدا نشد."
176
-
177
- # ۳. copy to temp
178
- temp_mp3 = tempfile.NamedTemporaryFile(suffix='.mp3', delete=False)
179
- temp_mp3.close()
180
- shutil.copy2(mp3_file, temp_mp3.name)
181
- os.remove(mp3_file)
182
-
183
- progress(0.5, desc="MP3 آماده… شروع استخراج متن")
184
- text, error_msg = transcribe_audio(temp_mp3.name, progress, language)
185
-
186
- if text is None:
187
- return temp_mp3.name, None, f"MP3 آماده است، ولی استخراج متن شکست خورد: {error_msg}"
188
-
189
- progress(1.0, desc="استخراج متن کامل شد")
190
- return temp_mp3.name, text, f"موفق! زبان: {language_display(language)}. {len(text.split())} کلمه استخراج شد."
191
-
192
-
193
- # ------------------------------------------------------------
194
- # ۳. Gradio Interface
195
- # ------------------------------------------------------------
196
  iface = gr.Interface(
197
  fn=convert_to_mp3_and_transcribe,
198
  inputs=[
199
- gr.Textbox(label="لینک ویدیو (یوتیوب یا MP4 مستقیم)",
200
- placeholder="https://www.youtube.com/watch?v=…"),
201
- gr.Dropdown(choices=[("پارسی","fa-IR"),("انگلیسی","en-US")],
202
- value="fa-IR", label="زبان")
 
 
 
 
 
 
 
 
 
 
 
 
 
203
  ],
204
  outputs=[
205
  gr.File(label="دانلود MP3"),
206
- gr.Textbox(label="متن استخراج‌شده", lines=10),
207
- gr.Textbox(label="پیام وضعیت")
 
208
  ],
209
- title="تبدیل ویدیو به MP3 و استخراج متن",
210
- description="لینک ویدیو را وارد کنید. برای پارسی: fa-IR، برای انگلیسی: en-US.",
211
  examples=[
212
- ["https://www.youtube.com/watch?v=5qap5aO4i9A","fa-IR"],
213
- ["https://www.youtube.com/watch?v=dQw4w9WgXcQ","en-US"]
214
  ],
215
  allow_flagging="never",
216
  cache_examples=False
217
  )
218
 
219
- # ------------------------------------------------------------
220
- # ۴. API (Flask) برای گرفتن status بر مبنای token
221
- # ------------------------------------------------------------
222
- app = Flask(__name__)
223
-
224
- @app.route('/api/status', methods=['POST'])
225
- def api_status():
226
- """
227
- ورودی: {"token":"<uuid>"}
228
- خروجی: {status:0/1, transcript:"..."(در صورت تکمیل)}
229
- """
230
- data = request.get_json()
231
- if not data or "token" not in data:
232
- return jsonify({"status":0,"error":"token missing"}),400
233
-
234
- token = data["token"]
235
- with session_lock:
236
- session_hash = session_map.get(token)
237
-
238
- if not session_hash:
239
- return jsonify({"status":0,"error":"unknown token"}),400
240
-
241
- # query the gradio queue
242
- import requests
243
- sse_url = f"https://suprimedev-hfvd.hf.space/gradio_api/queue/data?session_hash={session_hash}"
244
- resp = requests.get(sse_url, headers={
245
- "Accept":"text/event-stream",
246
- "Connection":"keep-alive"
247
- })
248
- if resp.status_code != 200:
249
- return jsonify({"status":0,"error":"failed to fetch"}),500
250
-
251
- frames = parse_sse(resp.text)
252
-
253
- for frame in frames:
254
- if frame.get('data',{}).get('msg') == 'process_completed':
255
- out = frame['data'].get('output',{}).get('data')
256
- if isinstance(out, list) and len(out)>=2:
257
- transcript = out[1]
258
- # Update DB outside – not done here (PHP handles)
259
- return jsonify({"status":1,"transcript":transcript})
260
- return jsonify({"status":0,"error":"not finished yet"})
261
 
262
- def parse_sse(sse_text: str):
263
- frames = []
264
- current = {}
265
- for line in sse_text.splitlines():
266
- if line == '':
267
- if current:
268
- frames.append(current)
269
- current={}
270
- continue
271
- if line.startswith('event:'):
272
- current['event']=line[6:].strip()
273
- elif line.startswith('id:'):
274
- current['id']=line[3:].strip()
275
- elif line.startswith('data:'):
276
- current['data']=line[5:].strip()
277
- if current:
278
- frames.append(current)
279
-
280
- for frame in frames:
281
- if 'data' in frame:
282
- try:
283
- frame['data']=json.loads(frame['data'])
284
- except:
285
- pass
286
- return frames
287
-
288
- # ------------------------------------------------------------
289
- # ۵. راه‌اندازی Gradio + API
290
- # ------------------------------------------------------------
291
  if __name__ == "__main__":
292
- # در اینجا 2 سرویس به هم‌زمان اجرا می‌شوند
293
- # 1. Gradio (به‌صورت normal)
294
- # 2. Flask (در پورت 5001)
295
- from multiprocessing import Process
296
-
297
- def run_gradio():
298
- iface.launch(server_name="0.0.0.0", server_port=7860, share=False)
299
-
300
- p1 = Process(target=run_gradio)
301
- p1.start()
302
-
303
- # Flask
304
- app.run(host="0.0.0.0", port=5001)
 
1
+ import gradio as gr
2
+ import yt_dlp
3
+ import tempfile
 
 
 
 
 
 
4
  import os
 
 
5
  import shutil
 
 
 
 
 
6
  import speech_recognition as sr
7
  from pydub import AudioSegment
8
+ import time
9
+ import warnings
10
+ import json
11
+ from datetime import datetime, timedelta
12
+ import threading
13
+ import hashlib
14
 
15
  warnings.filterwarnings("ignore")
16
 
17
+ # ذخیره‌سازی نتایج برای جلوگیری از مشکل session
18
+ results_cache = {}
19
+ cache_lock = threading.Lock()
20
+
21
+ # تابع برای پاکسازی cache قدیمی (بیش از 24 ساعت)
22
+ def cleanup_old_cache():
23
+ while True:
24
+ time.sleep(3600) # هر ساعت چک کن
25
+ with cache_lock:
26
+ current_time = datetime.now()
27
+ keys_to_remove = []
28
+ for key, value in results_cache.items():
29
+ if current_time - value['timestamp'] > timedelta(hours=24):
30
+ keys_to_remove.append(key)
31
+ for key in keys_to_remove:
32
+ del results_cache[key]
33
+ if keys_to_remove:
34
+ print(f"[CACHE] {len(keys_to_remove)} نتیجه قدیمی پاک شد.")
35
+
36
+ # شروع thread پاکسازی
37
+ cleanup_thread = threading.Thread(target=cleanup_old_cache, daemon=True)
38
+ cleanup_thread.start()
39
+
40
+ def get_cache_key(video_url, language):
41
+ """ایجاد کلید یکتا برای cache بر اساس URL و زبان"""
42
+ return hashlib.md5(f"{video_url}_{language}".encode()).hexdigest()
43
+
44
+ def save_result_to_cache(video_url, language, mp3_path, text, status_msg):
45
+ """ذخیره نتیجه در cache"""
46
+ cache_key = get_cache_key(video_url, language)
47
+
48
+ # کپی فایل MP3 به مکان دائمی
49
+ if mp3_path and os.path.exists(mp3_path):
50
+ cache_mp3_path = f"cache_{cache_key}.mp3"
51
+ shutil.copy2(mp3_path, cache_mp3_path)
52
+ else:
53
+ cache_mp3_path = None
54
+
55
+ with cache_lock:
56
+ results_cache[cache_key] = {
57
+ 'timestamp': datetime.now(),
58
+ 'mp3_path': cache_mp3_path,
59
+ 'text': text,
60
+ 'status_msg': status_msg,
61
+ 'video_url': video_url,
62
+ 'language': language
63
+ }
64
+
65
+ print(f"[CACHE] نتیجه ذخیره شد: {cache_key}")
66
+ return cache_key
67
+
68
+ def get_result_from_cache(cache_key):
69
+ """دریافت نتیجه از cache"""
70
+ with cache_lock:
71
+ if cache_key in results_cache:
72
+ result = results_cache[cache_key]
73
+ print(f"[CACHE] نتیجه یافت شد: {cache_key}")
74
+ return result['mp3_path'], result['text'], result['status_msg']
75
+ return None, None, None
76
+
77
+ def convert_to_mp3_and_transcribe(video_url, language, use_cache=True):
78
+ """
79
+ دانلود ویدیو/صوت از لینک، تبدیل به MP3، و استخراج متن با Google Speech Recognition.
80
+ """
81
+ if not video_url:
82
+ return None, None, "لینک ویدیو را وارد کنید.", None
83
+
84
+ # چک کردن cache
85
+ cache_key = get_cache_key(video_url, language)
86
+ if use_cache:
87
+ cached_mp3, cached_text, cached_status = get_result_from_cache(cache_key)
88
+ if cached_mp3 is not None or cached_text is not None:
89
+ return cached_mp3, cached_text, f"[از حافظه] {cached_status}", cache_key
90
+
91
+ progress = gr.Progress(track_tqdm=False)
92
+
93
+ try:
94
+ print(f"[DEBUG] شروع پردازش لینک: {video_url} (زبان: {language})")
95
+
96
+ progress(0, desc="شروع دانلود...")
97
+
98
+ # دانلود و تبدیل به MP3
99
+ ydl_opts = {
100
+ 'format': 'bestaudio[ext=m4a]/bestaudio/best',
101
+ 'postprocessors': [{
102
+ 'key': 'FFmpegExtractAudio',
103
+ 'preferredcodec': 'mp3',
104
+ 'preferredquality': '192',
105
+ }],
106
+ 'outtmpl': 'temp.%(ext)s',
107
+ 'quiet': True,
108
+ }
109
+
110
+ with yt_dlp.YoutubeDL(ydl_opts) as ydl:
111
+ ydl.download([video_url])
112
+
113
+ print("[DEBUG] دانلود کامل شد.")
114
+
115
+ # پیدا کردن MP3
116
+ mp3_file = None
117
+ for file in os.listdir('.'):
118
+ if file.startswith('temp.') and file.endswith('.mp3'):
119
+ mp3_file = file
120
+ break
121
+
122
+ if not mp3_file:
123
+ print("[DEBUG] هیچ MP3 پیدا نشد.")
124
+ status_msg = "خطا در دانلود یا تبدیل. مطمئن شوید لینک معتبر است."
125
+ save_result_to_cache(video_url, language, None, None, status_msg)
126
+ return None, None, status_msg, cache_key
127
+
128
+ progress(0.3, desc="دانلود کامل. کپی MP3...")
129
+
130
+ # کپی به موقت
131
+ temp_mp3 = tempfile.NamedTemporaryFile(suffix='.mp3', delete=False)
132
+ temp_mp3.close()
133
+ shutil.copy2(mp3_file, temp_mp3.name)
134
+ if os.path.exists(mp3_file):
135
+ os.remove(mp3_file)
136
+
137
+ print(f"[DEBUG] MP3 آماده: {temp_mp3.name}, اندازه: {os.path.getsize(temp_mp3.name)/1024:.1f} KB")
138
+
139
+ progress(0.5, desc="MP3 آماده. شروع استخراج متن با Google...")
140
+
141
+ # استخراج متن
142
+ text, error_msg = transcribe_audio(temp_mp3.name, progress, language)
143
+
144
+ if text is None:
145
+ status_msg = f"MP3 آماده است، اما استخراج متن fail شد. {error_msg}"
146
+ save_result_to_cache(video_url, language, temp_mp3.name, None, status_msg)
147
+ return temp_mp3.name, None, status_msg, cache_key
148
+
149
+ progress(1.0, desc="استخراج متن کامل شد!")
150
+
151
+ print(f"[DEBUG] متن نهایی (اولین 100 کاراکتر): {text[:100]}...")
152
+
153
+ status_msg = f"موفق! زبان: {language_display(language)}. {len(text.split())} کلمه استخراج شد."
154
+ save_result_to_cache(video_url, language, temp_mp3.name, text, status_msg)
155
+
156
+ return temp_mp3.name, text, status_msg, cache_key
157
+
158
+ except Exception as e:
159
+ print(f"[ERROR] خطای کلی: {str(e)}")
160
+ status_msg = f"خطا کلی: {str(e)}"
161
+ save_result_to_cache(video_url, language, None, None, status_msg)
162
+ return None, None, status_msg, cache_key
163
+
164
+ def language_display(lang_code):
165
+ """نمایش نام زبان"""
166
+ if lang_code == 'fa-IR':
167
+ return "پارسی"
168
+ elif lang_code == 'en-US':
169
+ return "انگلیسی"
170
+ else:
171
+ return lang_code
172
+
173
+ def transcribe_audio(mp3_path, progress, language, chunk_length_ms=60000, overlap_ms=5000):
174
+ """
175
+ استخراج متن با Google STT + retry برای rate limit. chunk 60s.
176
+ """
177
  recognizer = sr.Recognizer()
178
  recognizer.energy_threshold = 300
179
  recognizer.dynamic_energy_threshold = True
180
  recognizer.pause_threshold = 0.8
181
+
182
  full_text = []
183
  bad_chunks = 0
184
  total_chunks = 0
185
  temp_wav_dir = tempfile.mkdtemp()
 
186
  audio = AudioSegment.from_mp3(mp3_path)
187
  duration_ms = len(audio)
 
188
  if duration_ms == 0:
189
+ print("[DEBUG] فایل صوتی خالی!")
190
  return None, "فایل صوتی خالی یا بدون صدا."
191
+
192
  step_size = chunk_length_ms - overlap_ms
193
  if step_size <= 0:
194
  step_size = chunk_length_ms // 2
195
  num_chunks = max(1, (duration_ms // step_size) + 1)
196
+
197
+ print(f"[DEBUG] مدت: {duration_ms/1000:.1f}s, chunkها: {num_chunks}, گام: {step_size/1000:.1f}s")
198
+
199
+ progress(0.5, desc=f"تقسیم به {num_chunks} chunk 60s (زبان: {language_display(language)})...")
200
+
201
  i = 0
202
  chunk_idx = 1
203
  while i < duration_ms:
204
  end_pos = min(i + chunk_length_ms, duration_ms)
205
  chunk = audio[i:end_pos]
206
+
207
+ if len(chunk) < 3000:
208
+ print(f"[DEBUG] Chunk {chunk_idx} خیلی کوتاه ({len(chunk)/1000}s), رد شد.")
209
  break
210
+
211
  temp_wav = os.path.join(temp_wav_dir, f"chunk_{i}.wav")
212
+
213
  try:
214
  chunk.export(temp_wav, format="wav")
215
+ print(f"[DEBUG] Chunk {chunk_idx} export شد: {temp_wav}")
216
+
217
  text_chunk = None
218
  retry_count = 0
219
  max_retries = 3
220
+
221
  while retry_count < max_retries:
222
  try:
223
+ progress(0.5 + (i / duration_ms) * 0.5, desc=f"Chunk {chunk_idx}/{num_chunks} ({(i/1000):.0f}-{end_pos/1000:.0f}s, retry {retry_count+1})...")
224
+
 
225
  with sr.AudioFile(temp_wav) as source:
226
  recognizer.adjust_for_ambient_noise(source, duration=0.5)
227
  audio_data = recognizer.record(source, duration=None)
228
+
229
  text = recognizer.recognize_google(audio_data, language=language)
230
  if text.strip():
231
  text_chunk = text
232
+ print(f"[DEBUG] Chunk {chunk_idx} موفق: {text[:50]}...")
233
  break
234
  else:
235
  text_chunk = "[سکوت]"
236
+ print(f"[DEBUG] Chunk {chunk_idx} سکوت.")
237
  break
238
+
239
  except sr.UnknownValueError:
240
  text_chunk = "[نامشخص]"
241
+ print(f"[DEBUG] Chunk {chunk_idx} نامشخص (نویز/سکوت).")
242
  break
243
+
244
  except sr.RequestError as e:
245
  retry_count += 1
246
+ print(f"[DEBUG] Chunk {chunk_idx} RequestError (rate limit?): {str(e)}. Retry {retry_count}/{max_retries}")
247
+ if retry_count < max_retries:
248
+ time.sleep(2)
249
+ else:
250
+ text_chunk = f"[خطا rate limit: {str(e)[:30]}...]"
251
  bad_chunks += 1
252
+ print(f"[DEBUG] Chunk {chunk_idx} fail پس از retryها.")
253
+ break
254
+
255
  if text_chunk:
256
  full_text.append(text_chunk)
257
  if "[نامشخص" in text_chunk or "[خطا" in text_chunk:
258
  bad_chunks += 1
259
+
260
  total_chunks += 1
261
+
262
  except Exception as chunk_e:
263
+ print(f"[ERROR] Chunk {chunk_idx} (خطای کلی): {str(chunk_e)}")
264
+ text_chunk = f"[خطا chunk: {str(chunk_e)[:30]}...]"
265
  full_text.append(text_chunk)
266
  bad_chunks += 1
267
  total_chunks += 1
268
+
269
+ if os.path.exists(temp_wav):
270
+ os.remove(temp_wav)
271
+
 
272
  i += step_size
273
  chunk_idx += 1
274
+
275
  shutil.rmtree(temp_wav_dir, ignore_errors=True)
276
+
277
  final_text = ' '.join(full_text).strip()
278
+ error_msg = ""
279
+
280
  if not final_text:
281
+ error_msg = "هیچ chunk موفقی نبود."
282
+ return None, error_msg
283
+
284
+ bad_ratio = bad_chunks / total_chunks if total_chunks > 0 else 1
285
  if bad_ratio > 0.7:
286
+ error_msg = f"بیش از 70% chunkها fail ({bad_ratio*100:.0f}%). ممکن است نویز باشد، rate limit گوگل، یا زبان اشتباه انتخاب شده."
287
+ return None, error_msg
288
+
289
+ print(f"[DEBUG] {total_chunks} chunk پردازش شد, {bad_chunks} بد.")
290
+
291
+ return final_text, error_msg
292
+
293
+ # API endpoint برای دریافت نتیجه با cache key
294
+ def get_cached_result(cache_key):
295
+ """API برای دریافت نتیجه ذخیره شده"""
296
+ mp3, text, status = get_result_from_cache(cache_key)
297
+ return {
298
+ "cache_key": cache_key,
299
+ "mp3_available": mp3 is not None,
300
+ "text": text,
301
+ "status": status
 
 
 
 
 
 
302
  }
 
 
 
 
 
303
 
304
+ # رابط Gradio با خروجی cache key
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
305
  iface = gr.Interface(
306
  fn=convert_to_mp3_and_transcribe,
307
  inputs=[
308
+ gr.Textbox(
309
+ label="لینک ویدیو (یوتیوب یا MP4 مستقیم)",
310
+ placeholder="https://www.youtube.com/watch?v=... یا https://example.com/video.mp4"
311
+ ),
312
+ gr.Dropdown(
313
+ choices=[
314
+ ("پارسی", "fa-IR"),
315
+ ("انگلیسی", "en-US"),
316
+ ],
317
+ value="fa-IR",
318
+ label="زبان متن"
319
+ ),
320
+ gr.Checkbox(
321
+ label="استفاده از حافظه cache",
322
+ value=True,
323
+ visible=False # مخفی کنیم چون همیشه فعال است
324
+ )
325
  ],
326
  outputs=[
327
  gr.File(label="دانلود MP3"),
328
+ gr.Textbox(label="متن استخراج‌شده (Google STT)", lines=10),
329
+ gr.Textbox(label="پیام وضعیت"),
330
+ gr.Textbox(label="Cache Key (برای دریافت مجدد نتیجه)", visible=True)
331
  ],
332
+ title="تبدیل ویدیو به MP3 و استخراج متن (Google STT) - با Cache",
333
+ description="لینک ویدیو را وارد کنید و زبان را انتخاب کنید. نتایج برای 24 ساعت ذخیره می‌شوند.",
334
  examples=[
335
+ ["https://www.youtube.com/watch?v=5qap5aO4i9A", "fa-IR"],
336
+ ["https://www.youtube.com/watch?v=dQw4w9WgXcQ", "en-US"]
337
  ],
338
  allow_flagging="never",
339
  cache_examples=False
340
  )
341
 
342
+ # اضافه کردن API route برای دسترسی به cache
343
+ with gr.Blocks() as demo:
344
+ iface.render()
345
+
346
+ # API endpoint مخفی
347
+ @gr.route("/api/get_result/{cache_key}")
348
+ def api_get_result(cache_key):
349
+ return get_cached_result(cache_key)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
350
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
351
  if __name__ == "__main__":
352
+ demo.launch(server_name="0.0.0.0", server_port=7860, share=False)