suprimedev commited on
Commit
6556fb7
·
verified ·
1 Parent(s): 39512db

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +187 -107
app.py CHANGED
@@ -7,58 +7,59 @@ import speech_recognition as sr
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) # هر ساعت
24
  with cache_lock:
25
  current_time = datetime.now()
26
  keys_to_remove = []
27
- for event_id, value in results_cache.items():
28
- if value['completed'] and (current_time - value['timestamp'] > timedelta(hours=24)):
29
- keys_to_remove.append(event_id)
30
- # پاک کردن فایل MP3 اگر وجود دارد
31
- if value['mp3_path'] and os.path.exists(value['mp3_path']):
32
- try:
33
- os.remove(value['mp3_path'])
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
 
42
  cleanup_thread = threading.Thread(target=cleanup_old_cache, daemon=True)
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"
55
  shutil.copy2(mp3_path, cache_mp3_path)
56
 
57
  with cache_lock:
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,
@@ -66,67 +67,81 @@ def save_result_to_cache(event_id, video_url, language, mp3_path, text, status_m
66
  'completed': not processing and (text is not None or status_msg.startswith("خطا"))
67
  }
68
 
69
- print(f"[CACHE] نتیجه ذخیره شد: {event_id} (processing: {processing}, text_length: {len(text) if text else 0})")
70
 
71
- def get_result_from_cache(event_id):
72
- """دریافت نتیجه از cache با event_id"""
73
  with cache_lock:
74
- if event_id in results_cache:
75
- result = results_cache[event_id]
76
- print(f"[CACHE] نتیجه یافت شد: {event_id}")
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
 
107
- # ایجاد event_id
108
- event_id = get_event_id(video_url, language)
 
109
 
110
  # چک کردن cache
111
- cached_result = get_result_from_cache(event_id)
112
  if cached_result and cached_result['completed']:
113
  return (cached_result['mp3_path'],
114
  cached_result['text'],
115
  f"[از حافظه] {cached_result['status_msg']}",
116
- event_id)
 
117
 
118
  # اگر در حال پردازش است
119
  if cached_result and cached_result['processing']:
120
- return None, None, "در حال پردازش... لطفاً صبر کنید.", event_id
121
 
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
 
129
- return mp3_path, text, status_msg, event_id
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
130
 
131
  def convert_to_mp3_and_transcribe(video_url, language):
132
  """تابع اصلی پردازش (بدون cache)"""
@@ -137,7 +152,7 @@ def convert_to_mp3_and_transcribe(video_url, language):
137
 
138
  progress(0, desc="شروع دانلود...")
139
 
140
- # دانلود و تبدیل به MP3 (در Spaces، yt_dlp کار می‌کنه اما محدودیت حجم داره)
141
  ydl_opts = {
142
  'format': 'bestaudio[ext=m4a]/bestaudio/best',
143
  'postprocessors': [{
@@ -166,7 +181,7 @@ def convert_to_mp3_and_transcribe(video_url, language):
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,7 +209,7 @@ def language_display(lang_code):
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,54 +306,119 @@ def transcribe_audio(mp3_path, progress, language, chunk_length_ms=60000, overla
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 کامنت کن
 
 
 
 
 
 
 
 
 
 
 
 
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
+ import uuid
15
+ from fastapi import FastAPI
16
+ from fastapi.responses import JSONResponse
17
 
18
  warnings.filterwarnings("ignore")
19
 
20
+ # ذخیره‌سازی نتایج
21
  results_cache = {}
22
  cache_lock = threading.Lock()
23
 
24
+ # FastAPI instance برای API endpoints
25
+ app = FastAPI()
26
+
27
+ # تابع برای پاکسازی cache قدیمی
28
  def cleanup_old_cache():
29
  while True:
30
  time.sleep(3600) # هر ساعت
31
  with cache_lock:
32
  current_time = datetime.now()
33
  keys_to_remove = []
34
+ for key, value in results_cache.items():
35
+ if current_time - value['timestamp'] > timedelta(hours=24):
36
+ keys_to_remove.append(key)
37
+ for key in keys_to_remove:
38
+ del results_cache[key]
 
 
 
 
 
 
 
39
  if keys_to_remove:
40
+ print(f"[CACHE] {len(keys_to_remove)} نتیجه قدیمی پاک شد.")
41
 
42
  cleanup_thread = threading.Thread(target=cleanup_old_cache, daemon=True)
43
  cleanup_thread.start()
44
 
45
+ def get_cache_key(video_url, language):
46
+ """ایجاد کلید یکتا برای cache"""
47
  return hashlib.md5(f"{video_url}_{language}".encode()).hexdigest()
48
 
49
+ def save_result_to_cache(cache_key, event_id, video_url, language, mp3_path, text, status_msg, processing=False):
50
+ """ذخیره نتیجه در cache"""
51
+ # کپی فایل MP3 به مکان دائمی
52
  cache_mp3_path = None
53
  if mp3_path and os.path.exists(mp3_path):
54
+ cache_mp3_path = f"cache_{cache_key}.mp3"
55
  shutil.copy2(mp3_path, cache_mp3_path)
56
 
57
  with cache_lock:
58
+ results_cache[cache_key] = {
59
+ 'event_id': event_id,
60
  'timestamp': datetime.now(),
61
  'mp3_path': cache_mp3_path,
62
+ 'text': text,
63
  'status_msg': status_msg,
64
  'video_url': video_url,
65
  'language': language,
 
67
  'completed': not processing and (text is not None or status_msg.startswith("خطا"))
68
  }
69
 
70
+ print(f"[CACHE] نتیجه ذخیره شد: {cache_key} (processing: {processing})")
71
 
72
+ def get_result_from_cache(cache_key):
73
+ """دریافت نتیجه از cache"""
74
  with cache_lock:
75
+ if cache_key in results_cache:
76
+ result = results_cache[cache_key]
77
+ print(f"[CACHE] نتیجه یافت شد: {cache_key}")
78
  return result
79
  return None
80
 
81
+ def get_result_by_event_id_internal(event_id):
82
+ """دریافت نتیجه بر اساس event_id - برای استفاده داخلی"""
83
+ with cache_lock:
84
+ for key, value in results_cache.items():
85
+ if value.get('event_id') == event_id:
86
+ return value
87
+ return None
 
 
 
 
 
 
 
 
 
 
 
 
 
 
88
 
89
  def convert_to_mp3_and_transcribe_wrapper(video_url, language):
90
+ """Wrapper function که cache_key را در ابتدا برمی‌گرداند"""
91
  if not video_url:
92
+ return None, None, "لینک ویدیو را وارد کنید.", None, None
93
 
94
+ # ایجاد cache key
95
+ cache_key = get_cache_key(video_url, language)
96
+ event_id = str(uuid.uuid4())
97
 
98
  # چک کردن cache
99
+ cached_result = get_result_from_cache(cache_key)
100
  if cached_result and cached_result['completed']:
101
  return (cached_result['mp3_path'],
102
  cached_result['text'],
103
  f"[از حافظه] {cached_result['status_msg']}",
104
+ cache_key,
105
+ cached_result['event_id'])
106
 
107
  # اگر در حال پردازش است
108
  if cached_result and cached_result['processing']:
109
+ return None, None, "در حال پردازش... لطفاً صبر کنید.", cache_key, event_id
110
 
111
  # ثبت شروع پردازش
112
+ save_result_to_cache(cache_key, event_id, video_url, language, None, None, "در حال پردازش...", processing=True)
113
+
114
+ # پردازش در background
115
+ def process_async():
116
+ try:
117
+ mp3_path, text, status_msg = convert_to_mp3_and_transcribe(video_url, language)
118
+ save_result_to_cache(cache_key, event_id, video_url, language, mp3_path, text, status_msg, processing=False)
119
+ except Exception as e:
120
+ save_result_to_cache(cache_key, event_id, video_url, language, None, None, f"خطا: {str(e)}", processing=False)
121
 
122
+ # شروع پردازش (برای سادگی، همینجا انجام می‌دهیم - در production باید async باشد)
123
  mp3_path, text, status_msg = convert_to_mp3_and_transcribe(video_url, language)
124
+ save_result_to_cache(cache_key, event_id, video_url, language, mp3_path, text, status_msg, processing=False)
125
 
126
+ return mp3_path, text, status_msg, cache_key, event_id
127
+
128
+ def check_result_by_event_id(event_id):
129
+ """تابع برای بررسی نتیجه بر اساس Event ID"""
130
+ if not event_id:
131
+ return None, "", "Event ID را وارد کنید."
132
+
133
+ result = get_result_by_event_id_internal(event_id)
134
+
135
+ if not result:
136
+ return None, "", "نتیجه‌ای با این Event ID یافت نشد."
137
+
138
+ if result['processing']:
139
+ return None, "", "در حال پردازش... لطفاً صبر کنید."
140
+
141
+ if result['completed']:
142
+ return result.get('mp3_path'), result.get('text', ''), result.get('status_msg', 'تکمیل شد.')
143
+
144
+ return None, "", "وضعیت نامشخص."
145
 
146
  def convert_to_mp3_and_transcribe(video_url, language):
147
  """تابع اصلی پردازش (بدون cache)"""
 
152
 
153
  progress(0, desc="شروع دانلود...")
154
 
155
+ # دانلود و تبدیل به MP3
156
  ydl_opts = {
157
  'format': 'bestaudio[ext=m4a]/bestaudio/best',
158
  'postprocessors': [{
 
181
 
182
  progress(0.3, desc="دانلود کامل. کپی MP3...")
183
 
184
+ # کپی به موقت
185
  temp_mp3 = tempfile.NamedTemporaryFile(suffix='.mp3', delete=False)
186
  temp_mp3.close()
187
  shutil.copy2(mp3_file, temp_mp3.name)
 
209
  return "پارسی" if lang_code == 'fa-IR' else "انگلیسی" if lang_code == 'en-US' else lang_code
210
 
211
  def transcribe_audio(mp3_path, progress, language, chunk_length_ms=60000, overlap_ms=5000):
212
+ """استخراج متن - کد قبلی شما"""
213
  recognizer = sr.Recognizer()
214
  recognizer.energy_threshold = 300
215
  recognizer.dynamic_energy_threshold = True
 
306
 
307
  return final_text, ""
308
 
309
+ # API endpoint
310
+ @app.get("/api/check_result/{cache_key}")
311
+ async def check_result_api(cache_key: str):
312
+ """API endpoint برای بررسی وضعیت نتیجه"""
313
+ result = get_result_from_cache(cache_key)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
314
 
315
+ if not result:
316
+ return JSONResponse({"status": 0, "message": "Not found"})
317
+
318
+ if result['processing']:
319
+ return JSONResponse({"status": 0, "message": "Processing"})
320
+
321
+ if result['completed']:
322
+ return JSONResponse({
323
+ "status": 1,
324
+ "text": result['text'],
325
+ "mp3_available": result['mp3_path'] is not None,
326
+ "status_message": result['status_msg']
327
+ })
328
+
329
+ return JSONResponse({"status": 0, "message": "Unknown status"})
330
+
331
+ # Endpoint جدید برای استعلام با event_id
332
+ @app.get("/api/get_result_by_event_id/{event_id}")
333
+ async def get_result_by_event_id(event_id: str):
334
+ """دریافت نتیجه بر اساس event_id"""
335
+ result = get_result_by_event_id_internal(event_id)
336
+
337
+ if not result:
338
+ return JSONResponse({
339
+ "status": 0,
340
+ "event_id": event_id,
341
+ "message": "Event ID not found"
342
+ })
343
+
344
+ if result['processing']:
345
+ return JSONResponse({
346
+ "status": 0,
347
+ "event_id": event_id,
348
+ "message": "Processing"
349
+ })
350
+
351
+ if result['completed']:
352
+ return JSONResponse({
353
+ "status": 1,
354
+ "event_id": event_id,
355
+ "text": result['text'],
356
+ "mp3_available": result['mp3_path'] is not None,
357
+ "status_message": result['status_msg']
358
+ })
359
+
360
+ return JSONResponse({
361
+ "status": 0,
362
+ "event_id": event_id,
363
+ "message": "Failed or unknown status"
364
+ })
365
 
366
+ # رابط Gradio اصلی برای پردازش ویدیو
367
+ iface_main = gr.Interface(
368
+ fn=convert_to_mp3_and_transcribe_wrapper,
369
+ inputs=[
370
+ gr.Textbox(
371
+ label="لینک ویدیو",
372
+ placeholder="https://www.youtube.com/watch?v=..."
373
+ ),
374
+ gr.Dropdown(
375
+ choices=[("پارسی", "fa-IR"), ("انگلیسی", "en-US")],
376
+ value="fa-IR",
377
+ label="زبان"
378
+ )
379
+ ],
380
+ outputs=[
381
+ gr.File(label="دانلود MP3"),
382
+ gr.Textbox(label="متن استخراج‌شده", lines=10),
383
+ gr.Textbox(label="وضعیت"),
384
+ gr.Textbox(label="Cache Key", visible=False),
385
+ gr.Textbox(label="Event ID")
386
+ ],
387
+ title="تبدیل ویدیو به MP3 و متن",
388
  examples=[
389
  ["https://www.youtube.com/watch?v=5qap5aO4i9A", "fa-IR"],
390
  ["https://www.youtube.com/watch?v=dQw4w9WgXcQ", "en-US"]
391
+ ]
392
+ )
393
+
394
+ # رابط Gradio برای استعلام نتایج بر اساس Event ID
395
+ iface_query = gr.Interface(
396
+ fn=check_result_by_event_id,
397
+ inputs=[
398
+ gr.Textbox(
399
+ label="Event ID",
400
+ placeholder="وارد کردن Event ID..."
401
+ )
402
+ ],
403
+ outputs=[
404
+ gr.File(label="دانلود MP3"),
405
+ gr.Textbox(label="متن استخراج‌شده", lines=10),
406
+ gr.Textbox(label="وضعیت")
407
  ],
408
+ title="استعلام نتایج بر اساس Event ID",
409
+ description="با وارد کردن Event ID می‌توانید نتایج پردازش قبلی را مشاهده کنید."
 
410
  )
411
 
412
+ # Tabbed interface
413
+ iface = gr.TabbedInterface(
414
+ [iface_main, iface_query],
415
+ tab_names=["پردازش ویدیو", "استعلام نتایج"]
416
+ )
417
+
418
+ # Mount FastAPI to Gradio
419
+ from gradio import mount_gradio_app
420
+ mount_gradio_app(app, iface, path="/")
421
+
422
+ if __name__ == "__main__":
423
+ import uvicorn
424
+ uvicorn.run(app, host="0.0.0.0", port=7860)