KIMOSSINO commited on
Commit
618c56b
·
verified ·
1 Parent(s): aaf291f

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +226 -174
app.py CHANGED
@@ -10,13 +10,8 @@ import tempfile
10
  from datetime import datetime
11
  import logging
12
  import sys
13
- from pydub import AudioSegment
14
- import time
15
- import torch
16
- from concurrent.futures import ThreadPoolExecutor
17
- from functools import lru_cache
18
 
19
- # تكوين التسجيل
20
  logging.basicConfig(
21
  level=logging.INFO,
22
  format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
@@ -27,16 +22,6 @@ logging.basicConfig(
27
  )
28
  logger = logging.getLogger(__name__)
29
 
30
- # تحديد الجهاز المستخدم (GPU إذا كان متاحاً)
31
- DEVICE = "cuda" if torch.cuda.is_available() else "cpu"
32
- logger.info(f"Using device: {DEVICE}")
33
-
34
- # حجم النموذج - يمكن تغييره حسب الحاجة
35
- MODEL_SIZE = "base"
36
-
37
- # تخزين مؤقت للنماذج
38
- _models = {}
39
-
40
  # قائمة اللغات المدعومة
41
  SUPPORTED_LANGUAGES = {
42
  'ar': 'العربية',
@@ -48,146 +33,156 @@ SUPPORTED_LANGUAGES = {
48
 
49
  # تعيين أصوات لكل لغة
50
  VOICE_MAPPINGS = {
51
- 'ar': {
52
- 'male': 'ar-EG-ShakirNeural',
53
- 'female': 'ar-EG-SalmaNeural'
54
- },
55
- 'en': {
56
- 'male': 'en-US-ChristopherNeural',
57
- 'female': 'en-US-JennyNeural'
58
- },
59
- 'fr': {
60
- 'male': 'fr-FR-HenriNeural',
61
- 'female': 'fr-FR-DeniseNeural'
62
- },
63
- 'es': {
64
- 'male': 'es-ES-AlvaroNeural',
65
- 'female': 'es-ES-ElviraNeural'
66
- },
67
- 'de': {
68
- 'male': 'de-DE-ConradNeural',
69
- 'female': 'de-DE-KatjaNeural'
70
- }
71
  }
72
 
 
73
  RTL_LANGUAGES = ['ar']
74
 
75
- def get_whisper_model():
76
- """الحصول على نموذج Whisper مع التخزين المؤقت"""
77
- if 'whisper' not in _models:
78
- _models['whisper'] = whisper.load_model(MODEL_SIZE, device=DEVICE)
79
- return _models['whisper']
80
-
81
- @lru_cache(maxsize=100)
82
- async def generate_speech_cached(text, voice):
83
- """توليد الصوت مع التخزين المؤقت"""
84
  try:
 
85
  communicate = edge_tts.Communicate(text, voice)
 
86
  audio_path = tempfile.mktemp(suffix='.mp3')
87
  await communicate.save(audio_path)
88
- return audio_path if os.path.exists(audio_path) and os.path.getsize(audio_path) > 0 else None
 
 
 
 
 
 
 
 
89
  except Exception as e:
90
  logger.error(f"خطأ في توليد الصوت: {str(e)}")
91
  return None
92
 
93
- def text_to_speech(text, lang, voice_gender='male', progress=gr.Progress()):
94
- """تحسين تحويل النص إلى صوت"""
95
  if not text:
 
96
  return None
97
-
98
  try:
99
  progress(0.2, desc="جاري تجهيز الصوت...")
100
- voice = VOICE_MAPPINGS.get(lang, {}).get(voice_gender, VOICE_MAPPINGS['en']['male'])
101
 
102
- # تقسيم النص إلى أجزاء أصغر للمعالجة المتوازية
103
- chunk_size = 500
104
- chunks = [text[i:i+chunk_size] for i in range(0, len(text), chunk_size)]
105
 
 
106
  audio_files = []
107
- with ThreadPoolExecutor() as executor:
108
- futures = []
109
- for chunk in chunks:
110
- future = asyncio.run(generate_speech_cached(chunk, voice))
111
- futures.append(future)
112
-
113
- for i, future in enumerate(futures):
114
- progress((i + 1) / len(futures), desc=f"معالجة الجزء {i+1} من {len(futures)}...")
115
- if future:
116
- audio_files.append(future)
117
-
118
  if not audio_files:
 
119
  return None
120
-
 
121
  if len(audio_files) == 1:
122
  return audio_files[0]
123
-
124
- # دمج الملفات الصوتية
 
 
125
  final_audio = AudioSegment.from_mp3(audio_files[0])
126
  for audio_file in audio_files[1:]:
127
  final_audio += AudioSegment.from_mp3(audio_file)
128
-
129
  final_path = tempfile.mktemp(suffix='.mp3')
130
  final_audio.export(final_path, format="mp3")
131
-
132
  # تنظيف الملفات المؤقتة
133
  for file in audio_files:
134
  try:
135
  os.remove(file)
136
  except:
137
  pass
138
-
 
139
  return final_path
140
-
141
  except Exception as e:
142
  logger.error(f"خطأ في تحويل النص إلى صوت: {str(e)}")
143
  return None
144
 
145
- @lru_cache(maxsize=50)
146
- def translate_text_cached(text, source_lang, target_lang):
147
- """ترجمة النص مع التخزين المؤقت"""
148
- if source_lang == target_lang:
149
- return text
150
-
151
  try:
152
- translator = GoogleTranslator(source=source_lang, target=target_lang)
153
- return translator.translate(text)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
154
  except Exception as e:
155
- logger.error(f"خطأ في الترجمة: {str(e)}")
156
- return f"خطأ في الترجمة: {str(e)}"
157
 
158
- def translate_text(text, source_lang, target_lang, progress=None):
159
- """تحسين الترجمة باستخدام المعالجة المتوازية"""
160
  if source_lang == target_lang:
161
  return text
162
-
163
  try:
164
- if progress:
165
- progress(0.3, desc="جاري الترجمة...")
166
-
167
- # تقسيم النص إلى أجزاء أصغر
168
- chunk_size = 1000
169
- chunks = [text[i:i+chunk_size] for i in range(0, len(text), chunk_size)]
170
-
171
- translated_chunks = []
172
- with ThreadPoolExecutor() as executor:
173
- futures = []
174
- for chunk in chunks:
175
- future = executor.submit(translate_text_cached, chunk, source_lang, target_lang)
176
- futures.append(future)
177
-
178
- for i, future in enumerate(futures):
179
- if progress:
180
- progress((i + 1) / len(futures), desc=f"ترجمة الجزء {i+1} من {len(futures)}...")
181
- translated_chunks.append(future.result())
182
-
183
- return ' '.join(translated_chunks)
184
-
185
  except Exception as e:
186
  logger.error(f"خطأ في الترجمة: {str(e)}")
187
  return f"خطأ في الترجمة: {str(e)}"
188
 
189
- def process_video(video, source_lang="en", target_lang="ar", progress=None):
190
- """تحسين معالجة الفيديو"""
191
  if video is None:
192
  return {
193
  "error": "الرجاء رفع ملف فيديو",
@@ -195,50 +190,39 @@ def process_video(video, source_lang="en", target_lang="ar", progress=None):
195
  "translated": "",
196
  "document": None
197
  }
198
-
199
  try:
200
- if progress:
201
- progress(0.1, desc="جاري تحميل الفيديو...")
202
  temp_path = video.name
 
203
 
204
  # تحميل نموذج Whisper
205
- if progress:
206
- progress(0.3, desc="جاري تحميل نموذج التعرف على الكلام...")
207
- model = get_whisper_model()
208
-
209
- # استخراج النص مع التقسيم
210
- if progress:
211
- progress(0.5, desc="جاري استخراج النص من الفيديو...")
212
- result = model.transcribe(
213
- temp_path,
214
- language=source_lang,
215
- fp16=torch.cuda.is_available(), # استخدام FP16 إذا كان متاحاً
216
- task="transcribe"
217
- )
218
-
219
- transcribed_text = ""
220
- for segment in result["segments"]:
221
- start_time = format_timestamp(segment["start"])
222
- text = segment["text"].strip()
223
- transcribed_text += f"[{start_time}] {text}\n"
224
-
225
  # ترجمة النص
226
- if progress:
227
- progress(0.7, desc="جاري ترجمة النص...")
228
- translated_text = translate_text(transcribed_text, source_lang, target_lang, progress)
229
-
230
- # إنشاء المستند
231
- if progress:
232
- progress(0.9, desc="جاري إنشاء المستند...")
233
  doc_path = create_document(transcribed_text, translated_text, source_lang, target_lang)
234
-
 
235
  return {
236
  "error": None,
237
  "original": transcribed_text,
238
  "translated": translated_text,
239
  "document": doc_path
240
  }
241
-
242
  except Exception as e:
243
  logger.error(f"خطأ في معالجة الفيديو: {str(e)}")
244
  return {
@@ -248,40 +232,108 @@ def process_video(video, source_lang="en", target_lang="ar", progress=None):
248
  "document": None
249
  }
250
 
251
- def format_timestamp(seconds):
252
- """تنسيق الوقت بالثواني إلى الشكل HH:MM:SS"""
253
- hours = int(seconds // 3600)
254
- minutes = int((seconds % 3600) // 60)
255
- seconds = int(seconds % 60)
256
- return f"{hours:02d}:{minutes:02d}:{seconds:02d}"
257
-
258
- def create_document(original_text, translated_text, source_lang, target_lang):
259
- """إنشاء مستند DOCX يحتوي على النص الأصلي والترجمة"""
260
- doc = Document()
261
- doc.add_heading(f"النص الأصلي ({SUPPORTED_LANGUAGES.get(source_lang, 'غير معروف')})")
262
- doc.add_paragraph(original_text)
263
- doc.add_heading(f"الترجمة ({SUPPORTED_LANGUAGES.get(target_lang, 'غير معروف')})")
264
- doc.add_paragraph(translated_text)
265
- temp_path = tempfile.mktemp(suffix='.docx')
266
- doc.save(temp_path)
267
- return temp_path
268
-
269
- # إنشاء واجهة المستخدم
270
- demo = gr.Interface(
271
- process_video,
272
- [
273
- gr.File(label="تحميل ملف الفيديو"),
274
- gr.Radio(label="لغة المصدر", choices=list(SUPPORTED_LANGUAGES.keys()), value="en"),
275
- gr.Radio(label="لغة الهدف", choices=list(SUPPORTED_LANGUAGES.keys()), value="ar")
276
- ],
277
- [
278
- gr.Textbox(label="النص الأصلي"),
279
- gr.Textbox(label="الترجمة"),
280
- gr.File(label="المستند")
281
- ],
282
- title="تحويل الفيديو إلى نص وترجمة",
283
- description="استخدم هذا الأداة لتحويل الفيديوهات إلى نصوص وترجمتها إلى لغات مختلفة."
284
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
285
 
286
  if __name__ == "__main__":
287
- demo.launch()
 
 
 
 
 
 
10
  from datetime import datetime
11
  import logging
12
  import sys
 
 
 
 
 
13
 
14
+ # إعداد التسجيل
15
  logging.basicConfig(
16
  level=logging.INFO,
17
  format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
 
22
  )
23
  logger = logging.getLogger(__name__)
24
 
 
 
 
 
 
 
 
 
 
 
25
  # قائمة اللغات المدعومة
26
  SUPPORTED_LANGUAGES = {
27
  'ar': 'العربية',
 
33
 
34
  # تعيين أصوات لكل لغة
35
  VOICE_MAPPINGS = {
36
+ 'ar': 'ar-EG-ShakirNeural',
37
+ 'en': 'en-US-EricNeural',
38
+ 'fr': 'fr-FR-HenriNeural',
39
+ 'es': 'es-ES-AlvaroNeural',
40
+ 'de': 'de-DE-ConradNeural'
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
41
  }
42
 
43
+ # تحديد اللغات RTL
44
  RTL_LANGUAGES = ['ar']
45
 
46
+ async def generate_speech(text, lang):
47
+ """توليد الصوت باستخدام edge-tts"""
 
 
 
 
 
 
 
48
  try:
49
+ voice = VOICE_MAPPINGS.get(lang, 'en-US-EricNeural')
50
  communicate = edge_tts.Communicate(text, voice)
51
+
52
  audio_path = tempfile.mktemp(suffix='.mp3')
53
  await communicate.save(audio_path)
54
+
55
+ # التحقق من وجود الملف وحجمه
56
+ if os.path.exists(audio_path) and os.path.getsize(audio_path) > 0:
57
+ logger.info(f"تم إنشاء ملف صوتي: {audio_path}")
58
+ return audio_path
59
+ else:
60
+ logger.error("فشل إنشاء ملف صوتي صالح")
61
+ return None
62
+
63
  except Exception as e:
64
  logger.error(f"خطأ في توليد الصوت: {str(e)}")
65
  return None
66
 
67
+ def text_to_speech(text, lang, progress=gr.Progress()):
68
+ """واجهة لتحويل النص إلى صوت"""
69
  if not text:
70
+ logger.warning("لم يتم تقديم نص للتحويل إلى صوت")
71
  return None
72
+
73
  try:
74
  progress(0.2, desc="جاري تجهيز الصوت...")
75
+ logger.info(f"بدء تحويل النص إلى صوت باللغة: {lang}")
76
 
77
+ # تقسيم النص إلى أجزاء إذا كان طويلاً
78
+ max_length = 1000
79
+ text_parts = [text[i:i+max_length] for i in range(0, len(text), max_length)]
80
 
81
+ # إنشاء ملف صوتي لكل جزء
82
  audio_files = []
83
+ for i, part in enumerate(text_parts):
84
+ progress((i + 1) / len(text_parts), desc=f"معالجة الجزء {i+1} من {len(text_parts)}...")
85
+ audio_path = asyncio.run(generate_speech(part, lang))
86
+ if audio_path:
87
+ audio_files.append(audio_path)
88
+
 
 
 
 
 
89
  if not audio_files:
90
+ logger.error("لم يتم إنشاء أي ملفات صوتية")
91
  return None
92
+
93
+ # إذا كان هناك جزء واحد فقط
94
  if len(audio_files) == 1:
95
  return audio_files[0]
96
+
97
+ # دمج الملفات الصوتية إذا كان هناك أكثر من جزء
98
+ from pydub import AudioSegment
99
+
100
  final_audio = AudioSegment.from_mp3(audio_files[0])
101
  for audio_file in audio_files[1:]:
102
  final_audio += AudioSegment.from_mp3(audio_file)
103
+
104
  final_path = tempfile.mktemp(suffix='.mp3')
105
  final_audio.export(final_path, format="mp3")
106
+
107
  # تنظيف الملفات المؤقتة
108
  for file in audio_files:
109
  try:
110
  os.remove(file)
111
  except:
112
  pass
113
+
114
+ progress(1.0, desc="تم إنشاء الصوت بنجاح!")
115
  return final_path
116
+
117
  except Exception as e:
118
  logger.error(f"خطأ في تحويل النص إلى صوت: {str(e)}")
119
  return None
120
 
121
+ def create_document(original_text, translated_text, source_lang, target_lang, progress=gr.Progress()):
122
+ """إنشاء ملف Word يحتوي على النص الأصلي والترجمة"""
 
 
 
 
123
  try:
124
+ progress(0, desc="جاري إنشاء المستند...")
125
+ doc = Document()
126
+ doc.add_heading('النص الأصلي والترجمة', 0)
127
+
128
+ progress(0.3, desc="جاري إضافة المحتوى...")
129
+ # إضافة التاريخ والوقت
130
+ doc.add_paragraph(f'تم الإنشاء في: {datetime.now().strftime("%Y-%m-%d %H:%M:%S")}')
131
+
132
+ # إضافة النص الأصلي
133
+ doc.add_heading(f'النص الأصلي ({SUPPORTED_LANGUAGES[source_lang]})', level=1)
134
+ doc.add_paragraph(original_text)
135
+
136
+ progress(0.6, desc="جاري إضافة الترجمة...")
137
+ # إضافة الترجمة
138
+ doc.add_heading(f'الترجمة ({SUPPORTED_LANGUAGES[target_lang]})', level=1)
139
+ doc.add_paragraph(translated_text)
140
+
141
+ # حفظ الملف
142
+ progress(0.9, desc="جاري حفظ المستند...")
143
+ temp_path = tempfile.mktemp(suffix='.docx')
144
+ doc.save(temp_path)
145
+
146
+ progress(1.0, desc="تم إنشاء المستند بنجاح!")
147
+ logger.info(f"تم إنشاء مستند Word: {temp_path}")
148
+ return temp_path
149
+
150
  except Exception as e:
151
+ logger.error(f"خطأ في إنشاء المستند: {str(e)}")
152
+ return None
153
 
154
+ def translate_text(text, source_lang, target_lang, progress=gr.Progress()):
155
+ """ترجمة النص باستخدام deep-translator"""
156
  if source_lang == target_lang:
157
  return text
158
+
159
  try:
160
+ progress(0.3, desc="جاري الترجمة...")
161
+ logger.info(f"بدء الترجمة من {source_lang} إلى {target_lang}")
162
+
163
+ translator = GoogleTranslator(source=source_lang, target=target_lang)
164
+
165
+ # تقسيم النص إلى أجزاء إذا كان طويلاً
166
+ max_length = 5000
167
+ text_parts = [text[i:i+max_length] for i in range(0, len(text), max_length)]
168
+
169
+ translated_parts = []
170
+ for i, part in enumerate(text_parts):
171
+ progress((i + 1) / len(text_parts), desc=f"ترجمة الجزء {i+1} من {len(text_parts)}...")
172
+ translated_part = translator.translate(part)
173
+ translated_parts.append(translated_part)
174
+
175
+ translated_text = ' '.join(translated_parts)
176
+ progress(1.0, desc="تمت الترجمة بنجاح!")
177
+
178
+ return translated_text
179
+
 
180
  except Exception as e:
181
  logger.error(f"خطأ في الترجمة: {str(e)}")
182
  return f"خطأ في الترجمة: {str(e)}"
183
 
184
+ def process_video(video, source_lang="en", target_lang="ar", progress=gr.Progress()):
185
+ """معالجة الفيديو واستخراج النص وترجمته"""
186
  if video is None:
187
  return {
188
  "error": "الرجاء رفع ملف فيديو",
 
190
  "translated": "",
191
  "document": None
192
  }
193
+
194
  try:
195
+ # حفظ الفيديو مؤقتاً
196
+ progress(0.1, desc="جاري تحميل الفيديو...")
197
  temp_path = video.name
198
+ logger.info(f"تم استلام ملف فيديو: {temp_path}")
199
 
200
  # تحميل نموذج Whisper
201
+ progress(0.3, desc="جاري تحميل نموذج التعرف على الكلام...")
202
+ model = whisper.load_model("base")
203
+
204
+ # استخراج النص
205
+ progress(0.5, desc="جاري استخراج النص من الفيديو...")
206
+ result = model.transcribe(temp_path, language=source_lang)
207
+ transcribed_text = result["text"]
208
+ logger.info("تم استخراج النص بنجاح")
209
+
 
 
 
 
 
 
 
 
 
 
 
210
  # ترجمة النص
211
+ progress(0.7, desc="جاري ترجمة النص...")
212
+ translated_text = translate_text(transcribed_text, source_lang, target_lang)
213
+
214
+ # إنشاء ملف Word
215
+ progress(0.9, desc="جاري إنشاء المستند...")
 
 
216
  doc_path = create_document(transcribed_text, translated_text, source_lang, target_lang)
217
+
218
+ progress(1.0, desc="تمت المعالجة بنجاح!")
219
  return {
220
  "error": None,
221
  "original": transcribed_text,
222
  "translated": translated_text,
223
  "document": doc_path
224
  }
225
+
226
  except Exception as e:
227
  logger.error(f"خطأ في معالجة الفيديو: {str(e)}")
228
  return {
 
232
  "document": None
233
  }
234
 
235
+ def create_ui():
236
+ """إنشاء واجهة المستخدم"""
237
+ with gr.Blocks(theme=gr.themes.Soft(
238
+ primary_hue="blue",
239
+ secondary_hue="indigo",
240
+ )) as demo:
241
+ gr.Markdown(
242
+ """
243
+ # 🎥 منصة تحويل الفيديو إلى نص مع الترجمة
244
+ ### قم برفع فيديو للحصول على النص والترجمة مع إمكانية تحويل النص إلى صوت
245
+ """
246
+ )
247
+
248
+ with gr.Row():
249
+ with gr.Column(scale=2):
250
+ video_input = gr.File(
251
+ label="📁 رفع فيديو",
252
+ file_types=["video"],
253
+ elem_id="video_input"
254
+ )
255
+ with gr.Column(scale=1):
256
+ source_lang = gr.Dropdown(
257
+ choices=list(SUPPORTED_LANGUAGES.keys()),
258
+ value="en",
259
+ label="🗣️ لغة الفيديو الأصلية"
260
+ )
261
+ target_lang = gr.Dropdown(
262
+ choices=list(SUPPORTED_LANGUAGES.keys()),
263
+ value="ar",
264
+ label="🌐 لغة الترجمة"
265
+ )
266
+ process_btn = gr.Button("🎯 معالجة الفيديو", variant="primary")
267
+
268
+ with gr.Row():
269
+ error_output = gr.Textbox(label="⚠️ الأخطاء", visible=False)
270
+
271
+ with gr.Tabs():
272
+ with gr.TabItem("📝 النص الأصلي"):
273
+ original_text = gr.Textbox(
274
+ label="النص المستخرج من الفيديو",
275
+ lines=10,
276
+ elem_classes=["ltr"]
277
+ )
278
+ with gr.Row():
279
+ generate_original_audio = gr.Button("🔊 توليد الصوت", variant="secondary")
280
+ original_audio = gr.Audio(label="الصوت", visible=True)
281
+
282
+ with gr.TabItem("🔄 النص المترجم"):
283
+ translated_text = gr.Textbox(
284
+ label="النص المترجم",
285
+ lines=10,
286
+ elem_classes=["rtl"]
287
+ )
288
+ with gr.Row():
289
+ generate_translated_audio = gr.Button("🔊 توليد الصوت", variant="secondary")
290
+ translated_audio = gr.Audio(label="الصوت", visible=True)
291
+
292
+ with gr.Row():
293
+ download_btn = gr.File(
294
+ label="📥 تحميل المستند (Word)",
295
+ interactive=False
296
+ )
297
+
298
+ def update_ui(video, src_lang, tgt_lang):
299
+ result = process_video(video, src_lang, tgt_lang)
300
+
301
+ # تحديث اتجاه النص
302
+ original_classes = "rtl" if src_lang in RTL_LANGUAGES else "ltr"
303
+ translated_classes = "rtl" if tgt_lang in RTL_LANGUAGES else "ltr"
304
+
305
+ return {
306
+ error_output: gr.update(value=result["error"], visible=bool(result["error"])),
307
+ original_text: gr.update(value=result["original"], elem_classes=[original_classes]),
308
+ translated_text: gr.update(value=result["translated"], elem_classes=[translated_classes]),
309
+ download_btn: result["document"]
310
+ }
311
+
312
+ # ربط الأحداث
313
+ process_btn.click(
314
+ fn=update_ui,
315
+ inputs=[video_input, source_lang, target_lang],
316
+ outputs=[error_output, original_text, translated_text, download_btn]
317
+ )
318
+
319
+ generate_original_audio.click(
320
+ fn=text_to_speech,
321
+ inputs=[original_text, source_lang],
322
+ outputs=[original_audio]
323
+ )
324
+
325
+ generate_translated_audio.click(
326
+ fn=text_to_speech,
327
+ inputs=[translated_text, target_lang],
328
+ outputs=[translated_audio]
329
+ )
330
+
331
+ return demo
332
 
333
  if __name__ == "__main__":
334
+ try:
335
+ logger.info("بدء تشغيل التطبيق")
336
+ demo = create_ui()
337
+ demo.launch()
338
+ except Exception as e:
339
+ logger.error(f"خطأ في تشغيل التطبيق: {str(e)}")