suprimedev commited on
Commit
39512db
·
verified ·
1 Parent(s): 901e3e9

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +77 -80
app.py CHANGED
@@ -7,23 +7,17 @@ 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
- from fastapi import FastAPI
15
- from fastapi.responses import JSONResponse
16
 
17
  warnings.filterwarnings("ignore")
18
 
19
- # ذخیره‌سازی نتایج (متن همیشه ذخیره می‌شود)
20
  results_cache = {}
21
  cache_lock = threading.Lock()
22
 
23
- # FastAPI instance برای API endpoints
24
- app = FastAPI()
25
-
26
- # تابع برای پاکسازی cache قدیمی (بهبود: فقط event_idهای کامل‌شده و قدیمی را پاک کن)
27
  def cleanup_old_cache():
28
  while True:
29
  time.sleep(3600) # هر ساعت
@@ -40,7 +34,8 @@ def cleanup_old_cache():
40
  except:
41
  pass
42
  for event_id in keys_to_remove:
43
- del results_cache[event_id]
 
44
  if keys_to_remove:
45
  print(f"[CACHE] {len(keys_to_remove)} event_id قدیمی پاک شد.")
46
 
@@ -48,12 +43,12 @@ cleanup_thread = threading.Thread(target=cleanup_old_cache, daemon=True)
48
  cleanup_thread.start()
49
 
50
  def get_event_id(video_url, language):
51
- """ایجاد event_id یکتا (قبلاً cache_key)"""
52
  return hashlib.md5(f"{video_url}_{language}".encode()).hexdigest()
53
 
54
  def save_result_to_cache(event_id, video_url, language, mp3_path, text, status_msg, processing=False):
55
  """ذخیره نتیجه در cache (متن همیشه ذخیره می‌شود)"""
56
- # کپی فایل MP3 به مکان دائمی اگر موجود باشد
57
  cache_mp3_path = None
58
  if mp3_path and os.path.exists(mp3_path):
59
  cache_mp3_path = f"cache_{event_id}.mp3"
@@ -63,7 +58,7 @@ def save_result_to_cache(event_id, video_url, language, mp3_path, text, status_m
63
  results_cache[event_id] = {
64
  'timestamp': datetime.now(),
65
  'mp3_path': cache_mp3_path,
66
- 'text': text, # متن همیشه ذخیره می‌شود (حتی اگر None باشد)
67
  'status_msg': status_msg,
68
  'video_url': video_url,
69
  'language': language,
@@ -82,8 +77,30 @@ def get_result_from_cache(event_id):
82
  return result
83
  return None
84
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
85
  def convert_to_mp3_and_transcribe_wrapper(video_url, language):
86
- """Wrapper function که event_id را در ابتدا برمی‌گرداند"""
87
  if not video_url:
88
  return None, None, "لینک ویدیو را وارد کنید.", None
89
 
@@ -105,7 +122,7 @@ def convert_to_mp3_and_transcribe_wrapper(video_url, language):
105
  # ثبت شروع پردازش
106
  save_result_to_cache(event_id, video_url, language, None, None, "در حال پردازش...", processing=True)
107
 
108
- # پردازش در background (برای سادگی، همینجا انجام می‌دهیم - در production async کنید)
109
  mp3_path, text, status_msg = convert_to_mp3_and_transcribe(video_url, language)
110
  save_result_to_cache(event_id, video_url, language, mp3_path, text, status_msg, processing=False)
111
 
@@ -120,7 +137,7 @@ def convert_to_mp3_and_transcribe(video_url, language):
120
 
121
  progress(0, desc="شروع دانلود...")
122
 
123
- # دانلود و تبدیل به MP3
124
  ydl_opts = {
125
  'format': 'bestaudio[ext=m4a]/bestaudio/best',
126
  'postprocessors': [{
@@ -149,7 +166,7 @@ def convert_to_mp3_and_transcribe(video_url, language):
149
 
150
  progress(0.3, desc="دانلود کامل. کپی MP3...")
151
 
152
- # کپی به موقت
153
  temp_mp3 = tempfile.NamedTemporaryFile(suffix='.mp3', delete=False)
154
  temp_mp3.close()
155
  shutil.copy2(mp3_file, temp_mp3.name)
@@ -177,7 +194,7 @@ def language_display(lang_code):
177
  return "پارسی" if lang_code == 'fa-IR' else "انگلیسی" if lang_code == 'en-US' else lang_code
178
 
179
  def transcribe_audio(mp3_path, progress, language, chunk_length_ms=60000, overlap_ms=5000):
180
- """استخراج متن - کد قبلی شما"""
181
  recognizer = sr.Recognizer()
182
  recognizer.energy_threshold = 300
183
  recognizer.dynamic_energy_threshold = True
@@ -274,74 +291,54 @@ def transcribe_audio(mp3_path, progress, language, chunk_length_ms=60000, overla
274
 
275
  return final_text, ""
276
 
277
- # API endpoints (بهبود: event_id به جای cache_key، و endpoint جدید برای لیست)
278
- @app.get("/api/check_result/{event_id}")
279
- async def check_result_api(event_id: str):
280
- """API endpoint برای بررسی وضعیت نتیجه با event_id (متن اگر کامل شده برگردانده می‌شود)"""
281
- result = get_result_from_cache(event_id)
282
-
283
- if not result:
284
- return JSONResponse({"status": 0, "message": "Event ID not found"})
285
-
286
- if result['processing']:
287
- return JSONResponse({"status": 0, "message": "Processing..."})
288
 
289
- if result['completed']:
290
- return JSONResponse({
291
- "status": 1,
292
- "event_id": event_id,
293
- "text": result['text'], # متن استخراج‌شده همیشه برگردانده می‌شود
294
- "mp3_available": result['mp3_path'] is not None and os.path.exists(result['mp3_path']),
295
- "status_message": result['status_msg'],
296
- "timestamp": result['timestamp'].isoformat()
297
- })
 
 
 
 
 
 
 
 
298
 
299
- return JSONResponse({"status": 0, "message": "Unknown status"})
300
-
301
- @app.get("/api/list_events")
302
- async def list_events_api(limit: int = 10):
303
- """API endpoint جدید: لیست event_idهای اخیر (برای مدیریت)"""
304
- with cache_lock:
305
- recent_events = sorted(
306
- [(eid, data['timestamp']) for eid, data in results_cache.items() if data['completed']],
307
- key=lambda x: x[1],
308
- reverse=True
309
- )[:limit]
310
- return JSONResponse({
311
- "events": [{"event_id": eid, "timestamp": ts.isoformat()} for eid, ts in recent_events]
312
- })
313
-
314
- # رابط Gradio (بهبود: event_id برجسته‌تر)
315
- iface = gr.Interface(
316
- fn=convert_to_mp3_and_transcribe_wrapper,
317
- inputs=[
318
- gr.Textbox(
319
- label="لینک ویدیو",
320
- placeholder="https://www.youtube.com/watch?v=..."
321
- ),
322
- gr.Dropdown(
323
- choices=[("پارسی", "fa-IR"), ("انگلیسی", "en-US")],
324
- value="fa-IR",
325
- label="زبان"
326
  )
327
- ],
328
- outputs=[
329
- gr.File(label="دانلود MP3"),
330
- gr.Textbox(label="متن استخراج‌شده", lines=10),
331
- gr.Textbox(label="وضعیت"),
332
- gr.Textbox(label="Event ID (برای استعلام API)", visible=True, info="این ID را برای چک کردن نتیجه از API استفاده کنید: /api/check_result/{event_id}")
333
- ],
334
- title="تبدیل ویدیو به MP3 و متن (با ذخیره event_id)",
335
  examples=[
336
  ["https://www.youtube.com/watch?v=5qap5aO4i9A", "fa-IR"],
337
  ["https://www.youtube.com/watch?v=dQw4w9WgXcQ", "en-US"]
338
- ]
 
 
 
339
  )
340
 
341
- # Mount FastAPI to Gradio
342
- from gradio import mount_gradio_app
343
- mount_gradio_app(app, iface, path="/")
344
-
345
- if __name__ == "__main__":
346
- import uvicorn
347
- uvicorn.run(app, host="0.0.0.0", port=7860)
 
7
  from pydub import AudioSegment
8
  import time
9
  import warnings
 
10
  from datetime import datetime, timedelta
11
  import threading
12
  import hashlib
 
 
13
 
14
  warnings.filterwarnings("ignore")
15
 
16
+ # ذخیره‌سازی نتایج (متن همیشه ذخیره می‌شود - در حافظه Spaces موقت)
17
  results_cache = {}
18
  cache_lock = threading.Lock()
19
 
20
+ # تابع برای پاکسازی cache قدیمی (هر ساعت، فقط event_idهای کامل‌شده قدیمی رو پاک کن)
 
 
 
21
  def cleanup_old_cache():
22
  while True:
23
  time.sleep(3600) # هر ساعت
 
34
  except:
35
  pass
36
  for event_id in keys_to_remove:
37
+ if event_id in results_cache:
38
+ del results_cache[event_id]
39
  if keys_to_remove:
40
  print(f"[CACHE] {len(keys_to_remove)} event_id قدیمی پاک شد.")
41
 
 
43
  cleanup_thread.start()
44
 
45
  def get_event_id(video_url, language):
46
+ """ایجاد event_id یکتا"""
47
  return hashlib.md5(f"{video_url}_{language}".encode()).hexdigest()
48
 
49
  def save_result_to_cache(event_id, video_url, language, mp3_path, text, status_msg, processing=False):
50
  """ذخیره نتیجه در cache (متن همیشه ذخیره می‌شود)"""
51
+ # کپی فایل MP3 به مکان دائمی اگر موجود باشد (در Spaces، فایل‌ها موقت هستن)
52
  cache_mp3_path = None
53
  if mp3_path and os.path.exists(mp3_path):
54
  cache_mp3_path = f"cache_{event_id}.mp3"
 
58
  results_cache[event_id] = {
59
  'timestamp': datetime.now(),
60
  'mp3_path': cache_mp3_path,
61
+ 'text': text, # متن استخراج‌شده همیشه ذخیره می‌شه (حتی None)
62
  'status_msg': status_msg,
63
  'video_url': video_url,
64
  'language': language,
 
77
  return result
78
  return None
79
 
80
+ def check_event_id(event_id):
81
+ """تابع جدید: استعلام event_id و برگرداندن متن/وضعیت (برای Gradio و API)"""
82
+ if not event_id:
83
+ return "Event ID را وارد کنید.", None, "نامشخص"
84
+
85
+ cached_result = get_result_from_cache(event_id)
86
+ if not cached_result:
87
+ return "Event ID یافت نشد.", None, "Not found"
88
+
89
+ if cached_result['processing']:
90
+ return "در حال پردازش... (صبر کنید)", None, "Processing"
91
+
92
+ if cached_result['completed']:
93
+ text = cached_result['text']
94
+ status = cached_result['status_msg']
95
+ mp3_path = cached_result['mp3_path'] if cached_result['mp3_path'] and os.path.exists(cached_result['mp3_path']) else None
96
+ return (text if text else "متن استخراج نشد.",
97
+ mp3_path,
98
+ f"کامل: {status} (زمان: {cached_result['timestamp'].strftime('%Y-%m-%d %H:%M')})")
99
+
100
+ return "وضعیت نامشخص.", None, "Unknown"
101
+
102
  def convert_to_mp3_and_transcribe_wrapper(video_url, language):
103
+ """Wrapper اصلی: پردازش ویدیو و برگرداندن event_id"""
104
  if not video_url:
105
  return None, None, "لینک ویدیو را وارد کنید.", None
106
 
 
122
  # ثبت شروع پردازش
123
  save_result_to_cache(event_id, video_url, language, None, None, "در حال پردازش...", processing=True)
124
 
125
+ # پردازش (در Spaces، sync باشه چون async سخت‌تره)
126
  mp3_path, text, status_msg = convert_to_mp3_and_transcribe(video_url, language)
127
  save_result_to_cache(event_id, video_url, language, mp3_path, text, status_msg, processing=False)
128
 
 
137
 
138
  progress(0, desc="شروع دانلود...")
139
 
140
+ # دانلود و تبدیل به MP3 (در Spaces، yt_dlp کار می‌کنه اما محدودیت حجم داره)
141
  ydl_opts = {
142
  'format': 'bestaudio[ext=m4a]/bestaudio/best',
143
  'postprocessors': [{
 
166
 
167
  progress(0.3, desc="دانلود کامل. کپی MP3...")
168
 
169
+ # کپی به موقت (در Spaces، tempfile خوبه)
170
  temp_mp3 = tempfile.NamedTemporaryFile(suffix='.mp3', delete=False)
171
  temp_mp3.close()
172
  shutil.copy2(mp3_file, temp_mp3.name)
 
194
  return "پارسی" if lang_code == 'fa-IR' else "انگلیسی" if lang_code == 'en-US' else lang_code
195
 
196
  def transcribe_audio(mp3_path, progress, language, chunk_length_ms=60000, overlap_ms=5000):
197
+ """استخراج متن - بدون تغییر"""
198
  recognizer = sr.Recognizer()
199
  recognizer.energy_threshold = 300
200
  recognizer.dynamic_energy_threshold = True
 
291
 
292
  return final_text, ""
293
 
294
+ # رابط Gradio با دو تب (برای Spaces بهینه)
295
+ with gr.Blocks(title="تبدیل ویدیو به MP3 و متن (Hugging Face Spaces)") as demo:
296
+ gr.Markdown("# تبدیل ویدیو یوتیوب به MP3 و متن استخراج‌شده\n\n- **تب 1**: پردازش ویدیو جدید (event_id تولید می‌شه).\n- **تب 2**: استعلام event_id برای گرفتن متن ذخیره‌شده.")
 
 
 
 
 
 
 
 
297
 
298
+ with gr.Tab("پردازش ویدیو"):
299
+ with gr.Row():
300
+ video_input = gr.Textbox(label="لینک ویدیو", placeholder="https://www.youtube.com/watch?v=...")
301
+ lang_input = gr.Dropdown(choices=[("پارسی", "fa-IR"), ("انگلیسی", "en-US")], value="fa-IR", label="زبان")
302
+ process_btn = gr.Button("پردازش")
303
+
304
+ with gr.Row():
305
+ mp3_output = gr.File(label="دانلود MP3")
306
+ text_output = gr.Textbox(label="متن استخراج‌شده", lines=10)
307
+ status_output = gr.Textbox(label="وضعیت")
308
+ event_id_output = gr.Textbox(label="Event ID (کپی کنید برای استعلام)", info="این ID رو در تب دوم وارد کنید یا از API استفاده کنید.")
309
+
310
+ process_btn.click(
311
+ fn=convert_to_mp3_and_transcribe_wrapper,
312
+ inputs=[video_input, lang_input],
313
+ outputs=[mp3_output, text_output, status_output, event_id_output]
314
+ )
315
 
316
+ with gr.Tab("استعلام Event ID"):
317
+ event_id_input = gr.Textbox(label="Event ID را وارد کنید", placeholder="مثال: a1b2c3d4e5f6...")
318
+ check_btn = gr.Button("چک کن")
319
+
320
+ check_text = gr.Textbox(label="متن استخراج‌شده", lines=10)
321
+ check_mp3 = gr.File(label="دانلود MP3 (اگر موجود)")
322
+ check_status = gr.Textbox(label="وضعیت")
323
+
324
+ check_btn.click(
325
+ fn=check_event_id,
326
+ inputs=[event_id_input],
327
+ outputs=[check_text, check_mp3, check_status]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
328
  )
329
+
330
+ gr.Markdown("### API Usage (از خارج Spaces):\n- **پردازش ویدیو**: `POST /api/predict` با payload: `{'data': ['https://youtube.com/...', 'fa-IR']}` → خروجی شامل event_id.\n- **چک event_id**: `POST /api/predict` با payload: `{'fn_index': 1, 'data': ['event_id_here']}` → خروجی متن/وضعیت.\nمثال با curl: `curl -X POST https://your-space.hf.space/api/predict -H 'Content-Type: application/json' -d '{"data": ["url", "lang"]}'`")
331
+
332
+ # Examples برای تب اول
333
+ examples = gr.Examples(
 
 
 
334
  examples=[
335
  ["https://www.youtube.com/watch?v=5qap5aO4i9A", "fa-IR"],
336
  ["https://www.youtube.com/watch?v=dQw4w9WgXcQ", "en-US"]
337
+ ],
338
+ inputs=[video_input, lang_input],
339
+ outputs=[mp3_output, text_output, status_output, event_id_output],
340
+ fn=convert_to_mp3_and_transcribe_wrapper
341
  )
342
 
343
+ # در Spaces، demo رو launch نکن؛ Spaces خودش handle می‌کنه
344
+ # demo.launch(share=True) # فقط برای local، در Spaces کامنت کن