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) |