Alide21 commited on
Commit
e7b1c1e
·
verified ·
1 Parent(s): 4294fb4

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +121 -479
app.py CHANGED
@@ -5,396 +5,137 @@ import random
5
  import os
6
  import uuid
7
  from huggingface_hub import HfApi
 
8
 
9
- # --- الإعدادات ---
10
  HF_TOKEN = os.getenv("HF_TOKEN")
11
  REPO_ID = "Alide21/speech_quran"
12
  api = HfApi()
13
 
14
- SURAH_NAMES = ["الفاتحة", "البقرة", "آل عمران", "النساء", "المائدة", "الأنعام", "الأعراف", "الأنفال", "التوبة", "يونس", "هود", "يوسف", "الرعد", "إبراهيم", "الحجر", "النحل", "الإسراء", "الكهف", "مريم", "طه", "الأنبياء", "الحج", "المؤمنون", "النور", "الفرقان", "الشعراء", "النمل", "القصص", "العنكبوت", "الروم", "لقمان", "السجدة", "الأحزاب", "سبأ", "فاطر", "يس", "الصافات", "ص", "الزمر", "غافر", "فصلت", "الشورى", "الزخرف", "الدخان", "الجاثية", "الأحقاف", "محمد", "الفتح", "الحجرات", "ق", "الذاريات", "الطور", "النجم", "القمر", "الرحمن", "الواقعة", "الحديد", "المجادلة", "الحشر", "الممتحنة", "الصف", "الجمعة", "المنافقون", "التغابن", "الطلاق", "التحريم", "الملك", "القلم", "الحاقة", "المعارج", "نوح", "الجن", "المزمل", "المدثر", "القيامة", "الإنسان", "المرسلات", "النبأ", "النازعات", "عبس", "التكوير", "الانفطار", "المطففين", "الانشقاق", "البروج", "الطارق", "الأعلى", "الغاشية", "الفجر", "البلد", "الشمس", "الليل", "الضحى", "الشرح", "التين", "العلق", "القدر", "البينة", "الزلزلة", "العاديات", "القارعة", "التكاثر", "العصر", "الهمزة", "الفيل", "قريش", "الماعون", "الكوثر", "الكافرون", "النصر", "المسد", "الإخلاص", "الفلق", "الناس"]
15
 
16
- # تحسين CSS لتكون أكثر جمالية
17
  custom_css = """
18
- @import url('https://fonts.googleapis.com/css2?family=Amiri:wght@400;700&family=Reem+Kufi:wght@400;500;600;700&family=Scheherazade+New:wght@400;700&display=swap');
19
 
20
  :root {
21
- --primary-color: #1a5f7a;
22
- --secondary-color: #57c5b6;
23
- --accent-color: #159895;
24
- --gold-color: #d4af37;
25
- --light-bg: #f8f9fa;
26
- --dark-bg: #1a252f;
27
- --success-color: #28a745;
28
- --warning-color: #ffc107;
29
  }
30
 
31
- * {
32
- font-family: 'Reem Kufi', sans-serif;
33
- }
34
-
35
- body {
36
- direction: rtl;
37
- background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
38
- min-height: 100vh;
39
- margin: 0;
40
- padding: 0;
41
  }
42
 
 
43
  .gradio-container {
44
- max-width: 1400px !important;
45
- margin: 0 auto !important;
46
- padding: 20px !important;
47
  }
48
 
49
- /* الهيدر */
50
  #header-section {
51
- background: linear-gradient(135deg, var(--primary-color) 0%, var(--accent-color) 100%);
52
- min-height: 40vh;
53
- display: flex;
54
- flex-direction: column;
55
- justify-content: center;
56
- align-items: center;
57
  text-align: center;
58
- padding: 40px 20px;
59
- border-radius: 0 0 60px 60px;
60
- position: relative;
61
- overflow: hidden;
62
- box-shadow: 0 10px 30px rgba(0,0,0,0.1);
63
- margin-bottom: 30px;
64
- }
65
-
66
- #header-section::before {
67
- content: '';
68
- position: absolute;
69
- top: 0;
70
- left: 0;
71
- right: 0;
72
- bottom: 0;
73
- background: url('https://images.unsplash.com/photo-1541250628459-d8f2f0157289?q=80&w=2070&auto=format&fit=crop') center/cover;
74
- opacity: 0.15;
75
- }
76
-
77
- .main-title {
78
- font-family: 'Reem Kufi', sans-serif;
79
- font-size: 3.5rem;
80
  color: white;
81
- margin-bottom: 10px;
82
- text-shadow: 3px 3px 6px rgba(0,0,0,0.3);
83
- position: relative;
84
- z-index: 1;
85
- }
86
-
87
- .subtitle {
88
- font-family: 'Scheherazade New', serif;
89
- font-size: 1.5rem;
90
- color: var(--gold-color);
91
- margin-bottom: 20px;
92
- position: relative;
93
- z-index: 1;
94
- font-weight: 700;
95
- }
96
-
97
- .logo-container {
98
- background: white;
99
- width: 100px;
100
- height: 100px;
101
- border-radius: 50%;
102
- display: flex;
103
- align-items: center;
104
- justify-content: center;
105
- margin-bottom: 20px;
106
- box-shadow: 0 8px 25px rgba(0,0,0,0.2);
107
- position: relative;
108
- z-index: 1;
109
- border: 5px solid var(--gold-color);
110
  }
111
 
112
- .logo-text {
113
- font-family: 'Reem Kufi', sans-serif;
114
- font-size: 2.2rem;
115
- color: var(--primary-color);
116
  font-weight: 700;
 
117
  }
118
 
119
- /* البطاقات */
120
- .card-box {
121
  background: white;
122
- border-radius: 20px;
123
- padding: 25px;
124
- box-shadow: 0 15px 35px rgba(0,0,0,0.08);
125
- margin-bottom: 20px;
126
- border-top: 6px solid var(--secondary-color);
127
- transition: transform 0.3s ease, box-shadow 0.3s ease;
128
- position: relative;
129
- overflow: hidden;
130
- }
131
-
132
- .card-box:hover {
133
- transform: translateY(-5px);
134
- box-shadow: 0 20px 40px rgba(0,0,0,0.12);
135
- }
136
-
137
- .card-box::before {
138
- content: '';
139
- position: absolute;
140
- top: 0;
141
- left: 0;
142
- right: 0;
143
- height: 4px;
144
- background: linear-gradient(90deg, var(--primary-color), var(--secondary-color));
145
- }
146
-
147
- /* نص الآية */
148
- .ayah-container {
149
- background: linear-gradient(135deg, #fff8e1 0%, #fef9e7 100%);
150
- border-radius: 15px;
151
- padding: 30px;
152
- margin: 15px 0;
153
- border-right: 6px solid var(--gold-color);
154
- position: relative;
155
- }
156
-
157
- .ayah-container::before {
158
- content: "﷽";
159
- position: absolute;
160
- top: 10px;
161
- left: 20px;
162
- font-size: 1.8rem;
163
- color: rgba(0,0,0,0.1);
164
- font-family: 'Amiri', serif;
165
- }
166
-
167
- .ayah-text {
168
- font-family: 'Amiri', serif;
169
- font-size: 2.4rem;
170
- line-height: 1.8;
171
- color: #2c3e50;
172
- text-align: center;
173
- font-weight: 700;
174
- text-shadow: 1px 1px 2px rgba(0,0,0,0.05);
175
- padding: 15px;
176
- }
177
-
178
- .ayah-info {
179
- font-family: 'Reem Kufi', sans-serif;
180
- font-size: 1.3rem;
181
- color: var(--primary-color);
182
- text-align: center;
183
- margin-top: 15px;
184
- padding: 12px;
185
- background: rgba(87, 197, 182, 0.1);
186
- border-radius: 10px;
187
- border-right: 3px solid var(--secondary-color);
188
- }
189
-
190
- /* الأزرار */
191
- button {
192
- font-family: 'Reem Kufi', sans-serif !important;
193
- font-size: 1rem !important;
194
- border-radius: 10px !important;
195
- padding: 12px 25px !important;
196
- transition: all 0.3s ease !important;
197
- border: none !important;
198
- margin: 5px !important;
199
- }
200
-
201
- button[data-variant="primary"] {
202
- background: linear-gradient(135deg, var(--primary-color) 0%, var(--accent-color) 100%) !important;
203
- color: white !important;
204
- box-shadow: 0 5px 15px rgba(26, 95, 122, 0.3) !important;
205
- }
206
-
207
- button[data-variant="primary"]:hover {
208
- transform: translateY(-3px);
209
- box-shadow: 0 8px 20px rgba(26, 95, 122, 0.4) !important;
210
- }
211
-
212
- button[data-variant="secondary"] {
213
- background: linear-gradient(135deg, #ffc107 0%, #ff9800 100%) !important;
214
- color: white !important;
215
- box-shadow: 0 5px 15px rgba(255, 193, 7, 0.3) !important;
216
- }
217
-
218
- /* قسم التسجيل */
219
- .recording-section {
220
- background: linear-gradient(135deg, var(--dark-bg) 0%, #2c3e50 100%);
221
- border-radius: 20px;
222
- padding: 25px;
223
- color: white;
224
- margin-top: 20px;
225
- }
226
-
227
- .recording-title {
228
- color: var(--gold-color);
229
- font-size: 1.5rem;
230
- margin-bottom: 15px;
231
  text-align: center;
 
232
  }
233
 
234
- /* الإحصائيات */
235
- .stats-card {
236
- background: linear-gradient(135deg, #ffffff 0%, #f8f9fa 100%);
237
- border-radius: 15px;
 
 
238
  padding: 20px;
239
- text-align: center;
240
- border: 2px solid var(--secondary-color);
241
- margin: 15px 0;
242
- }
243
-
244
- .stats-number {
245
- font-size: 2.5rem;
246
- color: var(--primary-color);
247
- font-weight: 700;
248
- margin: 10px 0;
249
  }
250
 
251
- .stats-label {
252
- font-size: 1.1rem;
253
- color: #666;
 
 
254
  }
 
255
 
256
  /* التنبيهات */
257
  .alert-box {
258
  padding: 15px;
259
  border-radius: 12px;
260
- margin: 15px 0;
261
- text-align: center;
262
  font-weight: bold;
263
- font-size: 1rem;
264
- animation: slideIn 0.5s ease;
265
- }
266
-
267
- @keyframes slideIn {
268
- from { transform: translateY(-20px); opacity: 0; }
269
- to { transform: translateY(0); opacity: 1; }
270
- }
271
-
272
- .alert-success {
273
- background: linear-gradient(135deg, #d4edda 0%, #c3e6cb 100%);
274
- color: #155724;
275
- border-right: 4px solid var(--success-color);
276
- }
277
-
278
- .alert-error {
279
- background: linear-gradient(135deg, #f8d7da 0%, #f5c6cb 100%);
280
- color: #721c24;
281
- border-right: 4px solid #dc3545;
282
- }
283
-
284
- /* البحث السريع */
285
- .search-section {
286
- background: white;
287
- border-radius: 15px;
288
- padding: 20px;
289
- box-shadow: 0 10px 25px rgba(0,0,0,0.05);
290
- margin-top: 15px;
291
- }
292
-
293
- /* تخصيص العناصر */
294
- .gr-box {
295
- border-radius: 15px !important;
296
- border: 2px solid #e9ecef !important;
297
- }
298
-
299
- .gr-input {
300
- border-radius: 10px !important;
301
- }
302
-
303
- /* تذييل الصفحة */
304
- footer {
305
- text-align: center;
306
- padding: 25px;
307
- margin-top: 40px;
308
- background: var(--dark-bg);
309
- color: white;
310
- border-radius: 30px 30px 0 0;
311
- }
312
-
313
- .footer-text {
314
- color: var(--gold-color);
315
- font-size: 1rem;
316
- }
317
-
318
- /* تأثيرات إضافية */
319
- .pulse {
320
- animation: pulse 2s infinite;
321
- }
322
-
323
- @keyframes pulse {
324
- 0% { transform: scale(1); }
325
- 50% { transform: scale(1.03); }
326
- 100% { transform: scale(1); }
327
  }
 
 
328
 
329
- .glow {
330
- text-shadow: 0 0 10px rgba(212, 175, 55, 0.7);
331
- }
332
-
333
- /* تعديلات Gradio */
334
- .gr-form {
335
- background: transparent !important;
336
- }
337
-
338
- .gr-button {
339
- cursor: pointer !important;
340
- }
341
-
342
- /* تعديلات الـ Audio */
343
- .gr-audio {
344
- background: rgba(255, 255, 255, 0.9) !important;
345
- border-radius: 10px !important;
346
- padding: 10px !important;
347
- }
348
-
349
- /* تحسين الـ Dropdown */
350
- .gr-dropdown {
351
- border-radius: 10px !important;
352
- }
353
-
354
- /* تحسين الـ Number */
355
- .gr-number {
356
- border-radius: 10px !important;
357
- }
358
  """
359
 
 
360
  def clean_text(text):
361
  return re.sub(r'<[^>]*>', '', text).strip()
362
 
 
 
 
 
 
 
 
 
363
  def load_quran_data():
364
  all_verses = []
365
- # لتحسين السرعة، سنحمل عينة من السور أولاً
366
- for s in range(1, 115):
367
- try:
368
- url = f"https://raw.githubusercontent.com/CheeseWithSauce/TheHolyQuranJSONFormat/refs/heads/main/tajweedsurahs/{s:03}.json"
369
- res = requests.get(url, timeout=5).json()
370
- for v in res["verses"]:
371
- txt = clean_text(v.get("text_ar", ""))
372
- all_verses.append({"surah": s, "ayah": v["ayah"], "text": txt, "surah_name": SURAH_NAMES[s-1]})
373
- except:
374
- continue
375
  return all_verses
376
 
377
  ALL_AYAT = load_quran_data()
378
 
379
- def get_stats_html(user_session_count):
 
380
  return f"""
381
- <div class="stats-card">
382
- <div class="stats-number">{user_session_count}</div>
383
- <div class="stats-label">عدد تلاواتك في هذه الجلسة</div>
384
- <div style="margin-top: 15px; font-size: 0.9rem; color: #888;">
385
- كل تلاوة تساهم في بناء أكبر قاعدة بيانات صوتية للقرآن الكريم
386
- </div>
387
  </div>
388
  """
389
 
390
  def on_submit(audio, current_idx, session_count):
391
  if not audio:
392
- return "<div class='alert-box alert-error'>⚠️ يرجى تسجيل الصوت أولاً</div>", gr.update(), gr.update(), current_idx, session_count, gr.update()
393
 
394
  v = ALL_AYAT[current_idx]
395
  unique_id = str(uuid.uuid4())[:8]
396
- ext = audio.split('.')[-1] if '.' in audio else "wav"
397
- file_name = f"s{v['surah']}_a{v['ayah']}_{unique_id}.{ext}"
398
 
399
  try:
400
  api.upload_file(
@@ -405,175 +146,76 @@ def on_submit(audio, current_idx, session_count):
405
  token=HF_TOKEN
406
  )
407
  new_sess = session_count + 1
408
- next_idx = (current_idx + 1) % len(ALL_AYAT)
409
  nv = ALL_AYAT[next_idx]
410
 
411
- success_msg = f"""
412
- <div class='alert-box alert-success'>
413
- <div style="font-size: 1.2rem; margin-bottom: 10px;">✅ تم الحفظ بنجاح</div>
414
- <div>تم رفع تلاوتك للآية {v['ayah']} من سورة {v['surah_name']}</div>
415
- <div style="margin-top: 10px; font-size: 0.9rem;">
416
- المعرف الفريد: {unique_id}
417
- </div>
418
- </div>
419
- """
420
-
421
- return success_msg, f"<div class='ayah-text'>{nv['text']}</div>", f"### سورة {nv['surah_name']} - آية {nv['ayah']}", next_idx, new_sess, get_stats_html(new_sess)
422
  except Exception as e:
423
- return f"<div class='alert-box alert-error'>❌ خطأ في الرفع: {str(e)}</div>", gr.update(), gr.update(), current_idx, session_count, gr.update()
424
 
425
- def jump_logic(s_name, a_num):
426
  try:
427
  s_idx = SURAH_NAMES.index(s_name) + 1
428
- t_idx = next((i for i, v in enumerate(ALL_AYAT) if v["surah"] == s_idx and v["ayah"] == int(a_num)), 0)
429
- target = ALL_AYAT[t_idx]
430
- return "", f"<div class='ayah-text'>{target['text']}</div>", f"### سورة {target['surah_name']} - آية {target['ayah']}", t_idx
 
 
431
  except:
432
- return "<div class='alert-box alert-error'>⚠️ لم يتم العثور على الآية المحددة</div>", gr.update(), gr.update(), 0
433
 
434
- def random_ayah():
435
- ni = random.randint(0, len(ALL_AYAT)-1)
436
- v = ALL_AYAT[ni]
437
- return f"<div class='ayah-text'>{v['text']}</div>", f"### سورة {v['surah_name']} - آية {v['ayah']}", ni, ""
438
-
439
- # --- بناء الواجهة المحسنة ---
440
- with gr.Blocks(title="مِنَصَّة التِّلَاوَة - Quran Recitation Platform") as demo:
441
  idx_state = gr.State(random.randint(0, len(ALL_AYAT)-1))
442
  sess_state = gr.State(0)
443
-
444
- # الهيدر الرئيسي
445
  gr.HTML("""
446
  <div id="header-section">
447
- <div class="logo-container">
448
- <div class="logo-text">ق</div>
449
- </div>
450
- <h1 class="main-title glow">مِنَصَّة التِّلَاوَة</h1>
451
- <p class="subtitle">مشروع جماعي لجمع وتوثيق تلاوات القرآن الكريم</p>
452
- <p style="color: rgba(255,255,255,0.9); font-size: 1.1rem; max-width: 800px; position: relative; z-index: 1;">
453
- ساهم معنا في بناء أكبر قاعدة بيانات صوتية للقرآن الكريم من خلال تسجيل تلاوتك للآيات الكريمة
454
- </p>
455
  </div>
456
  """)
457
-
458
- with gr.Column():
459
- # قسم الإحصائيات
460
- with gr.Row():
461
- with gr.Column(scale=1):
462
- stats_display = gr.HTML(get_stats_html(0))
463
- with gr.Column(scale=2):
464
- gr.HTML(f"""
465
- <div class="card-box">
466
- <h3 style="color: var(--primary-color); margin-bottom: 15px;">🎯 الهدف من المنصة</h3>
467
- <p style="line-height: 1.6;">
468
- هذه المنصة تهدف إلى جمع تسجيلات صوتية متنوعة لتلاوات القرآن الكريم
469
- من مختلف القراء والمستويات، لاستخدامها في تطوير نماذج الذكاء الاصطناعي
470
- لفهم وتحليل التلاوة القرآنية.
471
- </p>
472
- <div style="margin-top: 20px; padding: 15px; background: #f8f9fa; border-radius: 10px;">
473
- <div style="display: flex; justify-content: space-between; margin-bottom: 10px;">
474
- <span>📖 عدد الآيات المتاحة:</span>
475
- <span style="font-weight: bold;">{len(ALL_AYAT)}</span>
476
- </div>
477
- <div style="display: flex; justify-content: space-between;">
478
- <span>🎙️ السور المتاحة:</span>
479
- <span style="font-weight: bold;">114</span>
480
- </div>
481
- </div>
482
- </div>
483
- """)
484
-
485
- # قسم العرض الرئيسي
486
- with gr.Row():
487
- with gr.Column(scale=2):
488
- with gr.Group():
489
- gr.HTML("""
490
- <div style="text-align: center; margin-bottom: 15px;">
491
- <h2 style="color: var(--primary-color);">📖 الآية الحالية</h2>
492
- <p style="color: #666;">تلاوة ثم تسجيل صوتك للآية المعروضة</p>
493
- </div>
494
- """)
495
-
496
- curr_v = ALL_AYAT[idx_state.value]
497
- info_text = gr.Markdown(f"### سورة {curr_v['surah_name']} - آية {curr_v['ayah']}")
498
- text_html = gr.HTML(f"<div class='ayah-text'>{curr_v['text']}</div>")
499
-
500
- with gr.Row():
501
- with gr.Column():
502
- send_btn = gr.Button("🎤 إرسال التلاوة", variant="primary")
503
- with gr.Column():
504
- rnd_btn = gr.Button("🔄 آية عشوائية", variant="secondary")
505
-
506
- status_msg = gr.HTML("")
507
 
508
- with gr.Column(scale=1):
509
- with gr.Group():
510
- gr.HTML("""
511
- <div class="recording-section">
512
- <h3 class="recording-title">🎙️ استوديو التسجيل</h3>
513
- <p style="text-align: center; opacity: 0.9; margin-bottom: 15px;">
514
- سجل تلاوتك للآية الحالية بجودة عالية
515
- </p>
516
- </div>
517
- """)
518
- # إزالة المعلمات غير المدعومة في Gradio
519
- audio_ui = gr.Audio(
520
- sources=["microphone"],
521
- type="filepath",
522
- label=None
523
- )
524
 
525
- with gr.Accordion("🔍 البحث عن آية محددة", open=False):
526
- with gr.Group():
527
- s_drop = gr.Dropdown(
528
- choices=SURAH_NAMES,
529
- label="اختر السورة",
530
- value="الفاتحة"
531
- )
532
- a_num = gr.Number(
533
- label="رقم الآية",
534
- value=1,
535
- precision=0,
536
- minimum=1
537
- )
538
- go_btn = gr.Button("🔍 انتقل إلى الآية", variant="primary")
 
 
539
 
540
  # التذييل
541
  gr.HTML("""
542
- <footer>
543
- <div class="footer-text"ِنَصَّة التِّلَاوَة - Quran Recitation Platform</div>
544
- <p style="margin-top: 10px; opacity: 0.8; font-size: 0.95rem;">
545
- مشروع مفتوح المصدر يساهم في خدمة القرآن الكريم وتقنيات الذكاء الاصطناعي
546
- </p>
547
- <div style="margin-top: 20px; font-size: 0.85rem; opacity: 0.7;">
548
- © 2024 جميع الحقوق محفوظة - للمساهمة تواصل معنا
549
- </div>
550
- </footer>
551
  """)
552
 
553
- # التعامل مع الأحداث
554
- rnd_btn.click(
555
- random_ayah,
556
- None,
557
- [text_html, info_text, idx_state, status_msg]
558
- )
559
-
560
- go_btn.click(
561
- jump_logic,
562
- [s_drop, a_num],
563
- [status_msg, text_html, info_text, idx_state]
564
- )
565
-
566
- send_btn.click(
567
- on_submit,
568
- [audio_ui, idx_state, sess_state],
569
- [status_msg, text_html, info_text, idx_state, sess_state, stats_display]
570
- )
571
 
572
- # تشغيل التطبيق
573
  if __name__ == "__main__":
574
- demo.launch(
575
- server_name="0.0.0.0",
576
- server_port=7860,
577
- share=False,
578
- css=custom_css
579
- )
 
5
  import os
6
  import uuid
7
  from huggingface_hub import HfApi
8
+ from concurrent.futures import ThreadPoolExecutor
9
 
10
+ # --- الإعدادات والمصادر ---
11
  HF_TOKEN = os.getenv("HF_TOKEN")
12
  REPO_ID = "Alide21/speech_quran"
13
  api = HfApi()
14
 
15
+ SURAH_NAMES = ["الفاتحة", "البقرة", "آل عمران", "النساء", "المائدة", "الأنعام", "الأعراف", "الأنفال", "التوبة", "يونس", "هود", "يوسف", "الرعد", "إبراهيم", "الحجر", "النحل", "الإسراء", "الكهف", "مريم", "طه", "الأنبياء", "الحج", "المؤمنون", "النور", "الفرقان", "الشعراء", "النمل", "القصص", "العنكبوت", "الروم", "لقمان", "السجدة", "الأحزاب", "سبأ", "فاطر", "يس", "الصافات", "ص", "الزمر", "غافر", "فصلت", "الشورى", "الزخرف", "الدخان", "الجاثية", "الأحقاف", "محمد", "الفتح", "الحجرات", "ق", "الذاريات", "الطور", "النجم", "القمر", "الرحمن", "الواقعة", "الحديد", "المجادلة", "الحشر", "الممتحنة", "الصف", "الجمعة", "المنافقون", "التغابن", "الطلاق", "التحريم", "الملك", "اللم", "الحاقة", "المعارج", "نوح", "الجن", "المزمل", "المدثر", "القيامة", "الإنسان", "المرسلات", "النبأ", "النازعات", "عبس", "التكوير", "الانفطار", "المطففين", "الانشقاق", "البروج", "الطارق", "الأعلى", "الغاشية", "الفجر", "البلد", "الشمس", "الليل", "الضحى", "الشرح", "التين", "العلق", "القدر", "البينة", "الزلزلة", "العاديات", "القارعة", "التكاثر", "العصر", "الهمزة", "الفيل", "قريش", "الماعون", "الكوثر", "الكافرون", "النصر", "المسد", "الإخلاص", "الفلق", "الناس"]
16
 
17
+ # --- تحسين CSS ---
18
  custom_css = """
19
+ @import url('https://fonts.googleapis.com/css2?family=Amiri:wght@400;700&family=Reem+Kufi:wght@400;700&family=Scheherazade+New:wght@400;700&display=swap');
20
 
21
  :root {
22
+ --primary: #064635;
23
+ --secondary: #519259;
24
+ --accent: #F4BB44;
25
+ --text-dark: #2C3333;
26
+ --bg-light: #F0F2F5;
 
 
 
27
  }
28
 
29
+ body {
30
+ direction: rtl;
31
+ background-color: var(--bg-light);
 
 
 
 
 
 
 
32
  }
33
 
34
+ /* تحسين شكل الحاوية الرئيسية */
35
  .gradio-container {
36
+ font-family: 'Reem Kufi', sans-serif !important;
 
 
37
  }
38
 
39
+ /* الهيدر الأنيق */
40
  #header-section {
41
+ background: linear-gradient(135deg, var(--primary) 0%, #0a6b51 100%);
42
+ padding: 50px 20px;
43
+ border-radius: 0 0 50px 50px;
 
 
 
44
  text-align: center;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
45
  color: white;
46
+ box-shadow: 0 10px 25px rgba(0,0,0,0.2);
47
+ margin-bottom: 30px;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
48
  }
49
 
50
+ .main-title {
51
+ font-size: 3rem !important;
52
+ margin-bottom: 10px;
 
53
  font-weight: 700;
54
+ text-shadow: 2px 2px 4px rgba(0,0,0,0.3);
55
  }
56
 
57
+ /* بطاقة الآية */
58
+ .ayah-card {
59
  background: white;
60
+ border-radius: 25px;
61
+ padding: 40px;
62
+ box-shadow: 0 15px 35px rgba(0,0,0,0.05);
63
+ border: 1px solid rgba(0,0,0,0.05);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
64
  text-align: center;
65
+ transition: all 0.3s ease;
66
  }
67
 
68
+ .ayah-text {
69
+ font-family: 'Amiri', serif !important;
70
+ font-size: 2.8rem !important;
71
+ line-height: 1.8 !important;
72
+ color: var(--primary);
73
+ margin: 20px 0;
74
  padding: 20px;
 
 
 
 
 
 
 
 
 
 
75
  }
76
 
77
+ /* الأزرار */
78
+ .custom-btn {
79
+ border-radius: 12px !important;
80
+ font-weight: bold !important;
81
+ transition: transform 0.2s !important;
82
  }
83
+ .custom-btn:hover { transform: scale(1.02); }
84
 
85
  /* التنبيهات */
86
  .alert-box {
87
  padding: 15px;
88
  border-radius: 12px;
89
+ margin: 10px 0;
 
90
  font-weight: bold;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
91
  }
92
+ .alert-success { background: #d4edda; color: #155724; border-right: 5px solid #28a745; }
93
+ .alert-error { background: #f8d7da; color: #721c24; border-right: 5px solid #dc3545; }
94
 
95
+ /* تخصيص المسافات */
96
+ .gap-20 { gap: 20px; }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
97
  """
98
 
99
+ # --- منطق البيانات ---
100
  def clean_text(text):
101
  return re.sub(r'<[^>]*>', '', text).strip()
102
 
103
+ def fetch_surah(s_idx):
104
+ try:
105
+ url = f"https://raw.githubusercontent.com/CheeseWithSauce/TheHolyQuranJSONFormat/refs/heads/main/tajweedsurahs/{s_idx:03}.json"
106
+ res = requests.get(url, timeout=5).json()
107
+ return [{"surah": s_idx, "ayah": v["ayah"], "text": clean_text(v.get("text_ar", "")), "surah_name": SURAH_NAMES[s_idx-1]} for v in res["verses"]]
108
+ except:
109
+ return []
110
+
111
  def load_quran_data():
112
  all_verses = []
113
+ # استخدام الـ Threading لتحميل البيانات بسرعة بدلاً من الانتظار التسلسلي
114
+ with ThreadPoolExecutor(max_workers=10) as executor:
115
+ results = list(executor.map(fetch_surah, range(1, 115)))
116
+ for r in results:
117
+ all_verses.extend(r)
 
 
 
 
 
118
  return all_verses
119
 
120
  ALL_AYAT = load_quran_data()
121
 
122
+ # --- الوظائف المساعدة ---
123
+ def get_stats_html(count):
124
  return f"""
125
+ <div style="background: white; padding: 20px; border-radius: 15px; text-align: center; box-shadow: 0 4px 12px rgba(0,0,0,0.05);">
126
+ <div style="font-size: 0.9rem; color: #666;">تلاواتك في هذه الجلسة</div>
127
+ <div style="font-size: 2.5rem; color: var(--secondary); font-weight: bold;">{count}</div>
128
+ <div style="font-size: 0.8rem; color: #aaa; margin-top: 5px;">جزاك الله خيراً على مساهمتك</div>
 
 
129
  </div>
130
  """
131
 
132
  def on_submit(audio, current_idx, session_count):
133
  if not audio:
134
+ return "<div class='alert-box alert-error'>⚠️ يرجى تسجيل تلاوتك أولاً</div>", gr.update(), gr.update(), current_idx, session_count, gr.update()
135
 
136
  v = ALL_AYAT[current_idx]
137
  unique_id = str(uuid.uuid4())[:8]
138
+ file_name = f"s{v['surah']}_a{v['ayah']}_{unique_id}.wav"
 
139
 
140
  try:
141
  api.upload_file(
 
146
  token=HF_TOKEN
147
  )
148
  new_sess = session_count + 1
149
+ next_idx = random.randint(0, len(ALL_AYAT)-1) # الانتقال لآية عشوائية بعد النجاح
150
  nv = ALL_AYAT[next_idx]
151
 
152
+ success_msg = f"<div class='alert-box alert-success'>✅ تم حفظ التلاوة بنجاح! (آية {v['ayah']} - {v['surah_name']})</div>"
153
+ return success_msg, f"<div class='ayah-text'>{nv['text']}</div>", f"### {nv['surah_name']} | آية {nv['ayah']}", next_idx, new_sess, get_stats_html(new_sess)
 
 
 
 
 
 
 
 
 
154
  except Exception as e:
155
+ return f"<div class='alert-box alert-error'>❌ فشل الرفع: {str(e)}</div>", gr.update(), gr.update(), current_idx, session_count, gr.update()
156
 
157
+ def jump_to_ayah(s_name, a_num):
158
  try:
159
  s_idx = SURAH_NAMES.index(s_name) + 1
160
+ t_idx = next((i for i, v in enumerate(ALL_AYAT) if v["surah"] == s_idx and v["ayah"] == int(a_num)), None)
161
+ if t_idx is not None:
162
+ target = ALL_AYAT[t_idx]
163
+ return "", f"<div class='ayah-text'>{target['text']}</div>", f"### {target['surah_name']} | آية {target['ayah']}", t_idx
164
+ return "<div class='alert-box alert-error'>⚠️ لم نجد هذه الآية في السورة المختارة</div>", gr.update(), gr.update(), 0
165
  except:
166
+ return "<div class='alert-box alert-error'>⚠️ حدث خطأ في البحث</div>", gr.update(), gr.update(), 0
167
 
168
+ # --- بناء الواجهة ---
169
+ with gr.Blocks(css=custom_css, title="منصة تلاوة") as demo:
 
 
 
 
 
170
  idx_state = gr.State(random.randint(0, len(ALL_AYAT)-1))
171
  sess_state = gr.State(0)
172
+
173
+ # الهيدر
174
  gr.HTML("""
175
  <div id="header-section">
176
+ <h1 class="main-title">مِنَصَّة التِّلَاوَة</h1>
177
+ <p style="font-size: 1.2rem; opacity: 0.9;">ساهم في بناء أول قاعدة بيانات صوتية مفتوحة المصدر للقرآن الكريم</p>
 
 
 
 
 
 
178
  </div>
179
  """)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
180
 
181
+ with gr.Row(elem_classes="gap-20"):
182
+ # العمود الأيمن: العرض
183
+ with gr.Column(scale=3):
184
+ with gr.Div(elem_classes="ayah-card"):
185
+ curr_v = ALL_AYAT[idx_state.value]
186
+ info_text = gr.Markdown(f"### {curr_v['surah_name']} | آية {curr_v['ayah']}")
187
+ text_html = gr.HTML(f"<div class='ayah-text'>{curr_v['text']}</div>")
188
+
189
+ status_msg = gr.HTML("")
 
 
 
 
 
 
 
190
 
191
+ with gr.Row():
192
+ send_btn = gr.Button("🎤 إرسال التلاوة الآن", variant="primary", elem_classes="custom-btn")
193
+ rnd_btn = gr.Button("🔄 آية أخرى", variant="secondary", elem_classes="custom-btn")
194
+
195
+ # العمود الأيسر: التحكم والإحصائيات
196
+ with gr.Column(scale=1):
197
+ stats_display = gr.HTML(get_stats_html(0))
198
+
199
+ with gr.Group():
200
+ gr.Markdown("### 🎙️ استوديو التسجيل")
201
+ audio_ui = gr.Audio(sources=["microphone"], type="filepath", label=None)
202
+
203
+ with gr.Accordion("🔍 انتقل لآية محددة", open=False):
204
+ s_drop = gr.Dropdown(choices=SURAH_NAMES, label="السورة", value="الفاتحة")
205
+ a_num = gr.Number(label="رقم الآية", value=1, precision=0)
206
+ go_btn = gr.Button("انتقال", variant="secondary")
207
 
208
  # التذييل
209
  gr.HTML("""
210
+ <div style="text-align: center; margin-top: 50px; padding: 20px; color: #666; border-top: 1px solid #ddd;">
211
+ <p>© 2026 مشروع تلاوة - صدقة جارية لكل من ساهم بقراءته</p>
212
+ </div>
 
 
 
 
 
 
213
  """)
214
 
215
+ # الأحداث
216
+ rnd_btn.click(lambda: (f"<div class='ayah-text'>{ALL_AYAT[i:=random.randint(0,len(ALL_AYAT)-1)]['text']}</div>", f"### {ALL_AYAT[i]['surah_name']} | آية {ALL_AYAT[i]['ayah']}", i, ""), None, [text_html, info_text, idx_state, status_msg])
217
+ go_btn.click(jump_to_ayah, [s_drop, a_num], [status_msg, text_html, info_text, idx_state])
218
+ send_btn.click(on_submit, [audio_ui, idx_state, sess_state], [status_msg, text_html, info_text, idx_state, sess_state, stats_display])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
219
 
 
220
  if __name__ == "__main__":
221
+ demo.launch()