File size: 10,105 Bytes
f8bdf4c
 
 
 
 
829af70
f8bdf4c
e7b1c1e
f8bdf4c
4033e26
f8bdf4c
 
 
 
a386a7b
319712f
e71dc85
f8bdf4c
f2185fc
1a3680a
f2185fc
4033e26
0a5f182
1a3680a
4033e26
0a5f182
 
 
4033e26
 
f2185fc
e71dc85
f2185fc
0a5f182
e71dc85
 
 
 
 
 
 
 
0a5f182
1a3680a
5c5e4d4
e71dc85
 
0a5f182
 
a858113
4033e26
e71dc85
f2185fc
0a5f182
 
 
4033e26
0a5f182
4033e26
e71dc85
f2185fc
0a5f182
f2185fc
 
e71dc85
f2185fc
0a5f182
 
f2185fc
e71dc85
 
 
f8bdf4c
 
e71dc85
 
 
 
 
 
 
0a5f182
5c5e4d4
e7b1c1e
 
 
 
5c5e4d4
a386a7b
f8bdf4c
a386a7b
 
 
f8bdf4c
0a5f182
4033e26
0a5f182
e71dc85
 
18efb04
 
e71dc85
 
15e9b93
f278b50
 
 
 
 
 
e71dc85
 
 
 
 
 
f278b50
e71dc85
 
 
 
 
 
 
 
 
 
 
4033e26
e71dc85
 
0a5f182
1a3680a
 
e71dc85
 
0a5f182
e71dc85
 
f2185fc
 
 
 
0a5f182
1a3680a
 
e71dc85
 
 
1a3680a
 
 
f2185fc
1a3680a
0a5f182
e71dc85
 
 
 
 
 
 
 
 
 
f2185fc
e71dc85
5c5e4d4
f2185fc
 
0a5f182
a858113
5c5e4d4
 
0a5f182
a858113
4033e26
e71dc85
4033e26
f2185fc
e71dc85
 
 
 
f2185fc
e71dc85
 
 
1a3680a
f2185fc
 
 
1a3680a
 
 
f2185fc
 
 
1a3680a
 
 
 
 
 
 
f8f92ea
e71dc85
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
import gradio as gr
import requests
import re
import random
import os
import uuid
from huggingface_hub import HfApi
from concurrent.futures import ThreadPoolExecutor

# --- الإعدادات ---
HF_TOKEN = os.getenv("HF_TOKEN")
REPO_ID = "Alide21/speech_quran"
api = HfApi()

SURAH_NAMES = ["الفاتحة", "البقرة", "آل عمران", "النساء", "المائدة", "الأنعام", "الأعراف", "الأنفال", "التوبة", "يونس", "هود", "يوسف", "الرعد", "إبراهيم", "الحجر", "النحل", "الإسراء", "الكهف", "مريم", "طه", "الأنبياء", "الحج", "المؤمنون", "النور", "الفرقان", "الشعراء", "النمل", "القصص", "العنكبوت", "الروم", "لقمان", "السجدة", "الأحزاب", "سبأ", "فاطر", "يس", "الصافات", "ص", "الزمر", "غافر", "فصلت", "الشورى", "الزخرف", "الدخان", "الجاثية", "الأحقاف", "محمد", "الفتح", "الحجرات", "ق", "الذاريات", "الطور", "النجم", "القمر", "الرحمن", "الواقعة", "الحديد", "المجادلة", "الحشر", "الممتحنة", "الصف", "الجمعة", "المنافقون", "التغابن", "الطلاق", "التحريم", "الملك", "القلم", "الحاقة", "المعارج", "نوح", "الجن", "المزمل", "المدثر", "القيامة", "الإنسان", "المرسلات", "النبأ", "النازعات", "عبس", "التكوير", "الانفطار", "المطففين", "الانشقاق", "البروج", "الطارق", "الأعلى", "الغاشية", "الفجر", "البلد", "الشمس", "الليل", "الضحى", "الشرح", "التين", "العلق", "القدر", "البينة", "الزلزلة", "العاديات", "القارعة", "التكاثر", "العصر", "الهمزة", "الفيل", "قريش", "الماعون", "الكوثر", "الكافرون", "النصر", "المسد", "الإخلاص", "الفلق", "الناس"]

# --- التنسيق البصري ---
custom_css = """
@import url('https://fonts.googleapis.com/css2?family=Amiri:wght@400;700&family=Reem+Kufi:wght@400;700&display=swap');
:root { --gold: #D4AF37; --dark-green: #064635; }

body { direction: rtl; background-color: #f4f6f9; }

.hero-header {
    background: linear-gradient(rgba(6, 70, 53, 0.9), rgba(6, 70, 53, 0.9)), 
                url('https://images.unsplash.com/photo-1609599006353-e629aaabfeae?q=80&w=2070&auto=format&fit=crop');
    background-size: cover;
    background-position: center;
    padding: 50px 20px;
    border-radius: 0 0 40px 40px;
    text-align: center;
    color: white !important;
}

.hero-header h1, .hero-header p, .hero-header span { color: white !important; }

.real-count { 
    font-size: 4rem; 
    color: #FFFFFF !important; 
    font-weight: bold; 
    text-shadow: 2px 2px 8px rgba(0,0,0,0.3);
}

.instruction-box {
    background: #fff9e6;
    border-right: 6px solid var(--gold);
    padding: 20px;
    border-radius: 15px;
    margin: 20px 0;
    text-align: right;
    direction: rtl;
    box-shadow: 0 4px 6px rgba(0,0,0,0.05);
}

.custom-card {
    background: white !important;
    border-radius: 20px !important;
    padding: 25px !important;
    box-shadow: 0 8px 20px rgba(0,0,0,0.06) !important;
    margin-bottom: 20px !important;
}

.ayah-text {
    font-family: 'Amiri', serif !important;
    font-size: 2.3rem !important;
    color: var(--dark-green);
    line-height: 1.8 !important;
    text-align: center;
}

/* ضبط اتجاه رسائل الحالة */
.status-msg { direction: rtl !important; text-align: right !important; }
"""

# --- المنطق الخلفي ---
def get_real_total():
    try:
        files = api.list_repo_files(repo_id=REPO_ID, repo_type="dataset", token=HF_TOKEN)
        return len([f for f in files if f.startswith("data/")])
    except: return 0

def clean_text(text): return re.sub(r'<[^>]*>', '', text).strip()

def fetch_surah(s_idx):
    try:
        url = f"https://raw.githubusercontent.com/CheeseWithSauce/TheHolyQuranJSONFormat/refs/heads/main/tajweedsurahs/{s_idx:03}.json"
        res = requests.get(url, timeout=5).json()
        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"]]
    except: return []

with ThreadPoolExecutor(max_workers=10) as executor:
    results = list(executor.map(fetch_surah, range(1, 115)))
ALL_AYAT = [item for sublist in results for item in sublist]

def validate_ayah(v):
    return len(v['text']) <= 200

# دالة لاختيار آية عشوائية صالحة
def get_random_valid_ayah():
    while True:
        idx = random.randint(0, len(ALL_AYAT)-1)
        if validate_ayah(ALL_AYAT[idx]):
            return idx

def jump_to_ayah(s_name, a_num):
    try:
        target_num = int(a_num)
        for i, v in enumerate(ALL_AYAT):
            if v['surah_name'] == s_name and v['ayah'] == target_num:
                if not validate_ayah(v):
                    return "⚠️ الآية المختارة طويلة جداً (تتجاوز 200 رمز).", gr.update(), gr.update(), gr.update()
                # الرسالة هنا RTL
                return "✅ تم العثور على الآية بنجاح", f"<div class='ayah-text'>{v['text']}</div>", f"### {v['surah_name']} | آية {v['ayah']}", i
        return "⚠️ الآية غير موجودة في هذه السورة.", gr.update(), gr.update(), gr.update()
    except:
        return "⚠️ يرجى التأكد من رقم الآية.", gr.update(), gr.update(), gr.update()

def on_submit(audio, current_idx):
    if not audio: return "⚠️ سجل صوتك أولاً", gr.update(), gr.update(), current_idx
    v = ALL_AYAT[current_idx]
    try:
        api.upload_file(path_or_fileobj=audio, path_in_repo=f"data/s{v['surah']}_a{v['ayah']}_{uuid.uuid4().hex[:6]}.wav", repo_id=REPO_ID, repo_type="dataset", token=HF_TOKEN)
        next_idx = get_random_valid_ayah()
        nv = ALL_AYAT[next_idx]
        return "✅ تم الحفظ بنجاح، جزاك الله خيراً", f"<div class='ayah-text'>{nv['text']}</div>", f"### {nv['surah_name']} | آية {nv['ayah']}", next_idx
    except Exception as e: return f"❌ فشل الرفع: {str(e)}", gr.update(), gr.update(), current_idx

# --- بناء الواجهة ---
with gr.Blocks(title="منصة تلاوة") as demo:
    # الآن سيتم اختيار آية جديدة في كل مرة يتم فيها بناء الـ Blocks (أي عند التحديث)
    idx_state = gr.State(get_random_valid_ayah())
    
    gr.HTML(f"""
        <div class="hero-header">
            <h1>مِنَصَّة التِّلَاوَة</h1>
            <p>بناء قاعدة بيانات صوتية لتدريب الذكاء الاصطناعي على تصحيح التلاوة</p>
            <div style="margin-top:20px;">
                <span style="font-size: 1.3rem;">مساهمات الأمة</span>
                <div class="real-count">{get_real_total()}</div>
            </div>
        </div>
    """)

    with gr.Column(elem_classes="gradio-container"):
        gr.HTML("""
            <div class="instruction-box">
                <strong>💡 تنبيه للجودة:</strong> 
                يرجى قراءة الآية بنفس واحد. 
                <br>نقبل الآيات التي لا تتجاوز 200 رمز لضمان دقة التحليل.
            </div>
        """)

        with gr.Row():
            with gr.Column(scale=2):
                with gr.Column(elem_classes="custom-card"):
                    # نستخدم دالة لتهيئة العرض الأول بناءً على idx_state
                    def load_initial_ayah(idx):
                        v = ALL_AYAT[idx]
                        return f"<div class='ayah-text'>{v['text']}</div>", f"### {v['surah_name']} | آية {v['ayah']}"
                    
                    # إنشاء المكونات بقيم فارغة ثم تعبئتها
                    info_text = gr.Markdown()
                    text_html = gr.HTML()
                    status_msg = gr.Markdown(elem_classes="status-msg")
                    
                    with gr.Row():
                        send_btn = gr.Button("🎤 إرسال التسجيل", variant="primary")
                        rnd_btn = gr.Button("🔄 آية عشوائية", variant="secondary")

            with gr.Column(scale=1):
                with gr.Column(elem_classes="custom-card"):
                    gr.Markdown("### 🎙️ التسجيل")
                    audio_ui = gr.Audio(sources=["microphone"], type="filepath", label=None)
                
                with gr.Column(elem_classes="custom-card"):
                    gr.Markdown("### 🔍 اختيار آية")
                    s_drop = gr.Dropdown(choices=SURAH_NAMES, label="السورة", value="الفاتحة")
                    a_num = gr.Number(label="رقم الآية", value=1, precision=0)
                    go_btn = gr.Button("انتقال")

    # حدث لتحميل الآية عند فتح الصفحة لأول مرة
    demo.load(load_initial_ayah, inputs=[idx_state], outputs=[text_html, info_text])

    # أحداث الأزرار
    rnd_btn.click(
        lambda: (idx := get_random_valid_ayah()) and (v := ALL_AYAT[idx]) and 
                (f"<div class='ayah-text'>{v['text']}</div>", f"### {v['surah_name']} | آية {v['ayah']}", idx, ""),
        outputs=[text_html, info_text, idx_state, status_msg],
        show_progress="hidden"
    )

    go_btn.click(
        jump_to_ayah,
        inputs=[s_drop, a_num],
        outputs=[status_msg, text_html, info_text, idx_state],
        show_progress="hidden"
    )

    send_btn.click(
        on_submit,
        inputs=[audio_ui, idx_state],
        outputs=[status_msg, text_html, info_text, idx_state],
        show_progress="minimal"
    )

if __name__ == "__main__":
    demo.launch(css=custom_css)