|
|
import shutil |
|
|
import gradio as gr |
|
|
from supabase import create_client, Client |
|
|
from supabase.client import ClientOptions |
|
|
import random |
|
|
import os |
|
|
import torch |
|
|
|
|
|
from supabase_auth.errors import ( |
|
|
AuthWeakPasswordError, |
|
|
AuthInvalidCredentialsError, |
|
|
AuthApiError, |
|
|
) |
|
|
|
|
|
url = os.environ['url'] |
|
|
key = os.environ['key'] |
|
|
supabase = create_client(url, key) |
|
|
user_country = "السعودية" |
|
|
UNIVERSITIES = [ |
|
|
"جامعة طيبة", |
|
|
"جامعة الملك فهد للبترول والمعادن", |
|
|
"أخرى" |
|
|
] |
|
|
COUNTRY = [ |
|
|
"السعودية", |
|
|
"مصر" |
|
|
] |
|
|
CITIES = saudi_regions = [ |
|
|
"منطقة الرياض", |
|
|
"منطقة مكة المكرمة", |
|
|
"منطقة المدينة المنورة", |
|
|
"المنطقة الشرقية", |
|
|
"منطقة القصيم", |
|
|
"منطقة عسير", |
|
|
"منطقة تبوك", |
|
|
"منطقة حائل", |
|
|
"منطقة الحدود الشمالية", |
|
|
"منطقة جازان", |
|
|
"منطقة نجران", |
|
|
"منطقة الباحة", |
|
|
"منطقة الجوف" |
|
|
] |
|
|
|
|
|
|
|
|
DIALECTS = [ |
|
|
"حجازية", |
|
|
"حجازية بدوية", |
|
|
"جنوبية", |
|
|
"تهامية", |
|
|
"نجدية", |
|
|
"نجدية بدوية", |
|
|
"قصيمية", |
|
|
"الشمال", |
|
|
"حساوية", |
|
|
"قطيفية", |
|
|
"سيهاتية" |
|
|
] |
|
|
|
|
|
GENDER = [ |
|
|
"ذكر", |
|
|
"أنثى" |
|
|
] |
|
|
AGE = [ |
|
|
"18-24", |
|
|
"25-34", |
|
|
"35-44", |
|
|
"45-54", |
|
|
"55-64", |
|
|
"65+" |
|
|
] |
|
|
|
|
|
SENTENCES = [ |
|
|
file.strip() for file in open("sentences.txt", "r", encoding="utf-8").readlines() |
|
|
] |
|
|
idx = random.randint(0, len(SENTENCES)) |
|
|
USER_ID = "" |
|
|
|
|
|
INSTRUCTIONS = """ |
|
|
|
|
|
<article dir="rtl" lang="ar"> |
|
|
<section> |
|
|
<ul> |
|
|
<li>سجّل بمكان هادي وما فيه إزعاج.</li> |
|
|
<li>خل المايك أو الجوال قريب من وجهك (٢٠ – ٣٠ سم تقريبًا).</li> |
|
|
<li>تكلم بصوت واضح وطبيعي، زي ما تتكلم عادة.</li> |
|
|
<li>حاول يكون الصوت ثابت، لا تعلي مرة ولا توطي مرة.</li> |
|
|
<li>إذا غلطت بكلمة، اقفل التسجيل وأعده من جديد.</li> |
|
|
<li>لو في جملة ما ودك تسجلها، تقدر تختار «تخطي».</li> |
|
|
<li> |
|
|
إذا واجهتك أي مشكلة أو عندك استفسار، تواصل معنا على الإيميل: |
|
|
<a href="mailto:a.a.elghawas@gmail.com">a.a.elghawas@gmail.com</a> |
|
|
</li> |
|
|
</ul> |
|
|
</section> |
|
|
</article> |
|
|
|
|
|
""" |
|
|
|
|
|
TERMS = """<article dir="rtl" lang="ar"> |
|
|
<header> |
|
|
<h1>اتفاقية المشاركة وجمع البيانات</h1> |
|
|
<p> |
|
|
الاتفاقية هذي بينك كمشارك وبين فريق البحث من جامعة الملك فهد للبترول والمعادن و جامعة طيبة |
|
|
(اللي بنسميهم هنا «الجامعتين»). |
|
|
</p> |
|
|
<p> |
|
|
الغرض منها تنظيم عملية جمع واستخدام وتوزيع التسجيلات الصوتية اللي بتفيد أبحاث كشف الأصوات المزوّرة (Deepfake) |
|
|
وغيرها من الأبحاث العلمية غير التجارية. |
|
|
</p> |
|
|
</header> |
|
|
|
|
|
<section> |
|
|
<h2>١. هدف جمع البيانات</h2> |
|
|
<p> |
|
|
الفريق يجمع تسجيلات صوتية علشان يبني مجموعة بيانات (Dataset) للكشف عن الأصوات المصنوعة بالذكاء الاصطناعي، |
|
|
زي تحويل النص لصوت (TTS) أو تقليد الأصوات (Voice Conversion). ويمكن استخدامها في غيرها من الابحاث الغير تجارية. |
|
|
</p> |
|
|
</section> |
|
|
|
|
|
<section> |
|
|
<h2>٢. نوعية البيانات اللي تُجمع</h2> |
|
|
<ul> |
|
|
<li>تسجيلات بصوتك الطبيعي من خلال نصوص أو جمل نطلب منك تقراها.</li> |
|
|
<li>بيانات اختيارية زي: النوع (ذكر/أنثى)، العمر، اللهجة...</li> |
|
|
<li>موافقتك إن الصوت ممكن يُعدل أو يتغير بأساليب صناعية.</li> |
|
|
</ul> |
|
|
</section> |
|
|
|
|
|
<section> |
|
|
<h2>٣. الحقوق الممنوحة</h2> |
|
|
<p>المشارك يعطي الفريق الحق:</p> |
|
|
<ul> |
|
|
<li>يسجل ويعالج ويستخدم الصوت الطبيعي والنسخ المصنوعة منه.</li> |
|
|
<li>يوزع البيانات للباحثين حول العالم للأغراض الأكاديمية غير التجارية.</li> |
|
|
<li>ينشر عينات صوتية على منصات أكاديمية/مهنية (LinkedIn، Twitter/X، YouTube) لرفع الوعي حول أبحاث الديب فيك.</li> |
|
|
</ul> |
|
|
</section> |
|
|
|
|
|
<section> |
|
|
<h2>٤. إتاحة البيانات</h2> |
|
|
<p> |
|
|
المجموعة الصوتية تُنشر بترخيص مفتوح <strong>Creative Commons Attribution 4.0</strong>، |
|
|
اللي يسمح لأي باحث يستخدمها ويشاركها للأغراض الأكاديمية غير التجارية. |
|
|
</p> |
|
|
</section> |
|
|
|
|
|
<section> |
|
|
<h2>٥. الخصوصية والسرية</h2> |
|
|
<ul> |
|
|
<li>ما راح يُنشر اسمك أو أي بيانات شخصية إلا بموافقة مكتوبة منك.</li> |
|
|
<li>بيكون لك ID مجهول داخل مجموعة البيانات.</li> |
|
|
</ul> |
|
|
</section> |
|
|
|
|
|
<section> |
|
|
<h2>٦. المشاركة والانضمام</h2> |
|
|
<ul> |
|
|
<li>المشاركة اختيارية بالكامل.</li> |
|
|
<li>لك الحق تنسحب أو تطلب حذف تسجيلاتك قبل نشر مجموعة البيانات.</li> |
|
|
<li>بعد النشر العام، ما نقدر نحذف التسجيلات بسبب طريقة التوزيع.</li> |
|
|
</ul> |
|
|
</section> |
|
|
|
|
|
<section> |
|
|
<h2>٧. التعويض</h2> |
|
|
<p> |
|
|
المشارك عارف إن ما فيه أي مقابل مادي مقابل تسجيلاته، والمشاركة هدفها دعم وتطوير البحث العلمي فقط. |
|
|
</p> |
|
|
</section> |
|
|
|
|
|
<footer> |
|
|
<p> |
|
|
وبمجرد تسجيلك بالموقع، معناته إنك موافق على جميع الشروط المذكورة أعلاه. |
|
|
</p> |
|
|
<p> |
|
|
لأي استفسار أو توضيح: |
|
|
<a href="mailto:a.a.elghawas@gmail.com">a.a.elghawas@gmail.com</a> |
|
|
</p> |
|
|
</footer> |
|
|
</article> |
|
|
|
|
|
""" |
|
|
|
|
|
def sign_up(su_name, su_email, su_pw, su_pw2, university, country, city, dialect, gender, age): |
|
|
global supabase |
|
|
if(su_pw == su_pw2): |
|
|
try: |
|
|
response = supabase.auth.sign_up( |
|
|
{ |
|
|
"email": su_email, |
|
|
"password": su_pw, |
|
|
} |
|
|
) |
|
|
print(response.user.id) |
|
|
|
|
|
uuid = response.user.id |
|
|
data = { |
|
|
"id": uuid, |
|
|
"university": university, |
|
|
"country": country, |
|
|
"city": city, |
|
|
"dialect": dialect, |
|
|
"gender": gender, |
|
|
"age": age, |
|
|
"idx": idx |
|
|
} |
|
|
print("HEERER") |
|
|
try: |
|
|
session = response.session |
|
|
supabase.postgrest.auth(session.access_token) |
|
|
supabase.table("users").insert(data).execute() |
|
|
except Exception as e: |
|
|
print("Error inserting user data:", e) |
|
|
|
|
|
|
|
|
return( |
|
|
"✅ تم إنشاء الحساب بنجاح, يمكنك تسجيل الدخول الآن" |
|
|
) |
|
|
except AuthWeakPasswordError as e: |
|
|
return( |
|
|
"⚠️ كلمة السر غير مقبولة. استخدم علي الأقل 6 أرقام" |
|
|
) |
|
|
except Exception as e: |
|
|
if(str(e) == "User already registered"): |
|
|
return( |
|
|
"⚠️ هذا المستخدم موجود بالفعل, قم بتسجيل الدخول علي حسابك" |
|
|
) |
|
|
elif(str(e) == "Unable to validate email address: invalid format"): |
|
|
return( |
|
|
"⚠️ يوجد خطأ بالإيميل, يرجي إعادة الإدخال مرة أخري بصورة صحيحة" |
|
|
) |
|
|
else: |
|
|
return( |
|
|
"⚠️ كلمة السر غير متطابقة" |
|
|
) |
|
|
|
|
|
def sign_in(si_email, si_pw): |
|
|
global supabase, USER_ID, idx |
|
|
try: |
|
|
response = supabase.auth.sign_in_with_password( |
|
|
{ |
|
|
"email": si_email, |
|
|
"password": si_pw, |
|
|
} |
|
|
) |
|
|
USER_ID = response.user.id |
|
|
session = response.session |
|
|
supabase.postgrest.auth(session.access_token) |
|
|
idx = supabase.table("users").select("idx").eq('id', USER_ID).execute().data[0]["idx"] |
|
|
print("IDDXXXXXXXX",idx) |
|
|
return( |
|
|
gr.update(visible=False), |
|
|
gr.update(visible=True), |
|
|
f"✅ هلا {si_email}!", |
|
|
gr.update(value=SENTENCES[idx]) |
|
|
) |
|
|
except Exception as e: |
|
|
print(e) |
|
|
return( |
|
|
gr.update(visible=True), |
|
|
gr.update(visible=False), |
|
|
"⚠️ هناك خطأ بالإيميل أو كلمة السر, في حالة عدم وجود حساب يرجي إنشاء حساب أولاً", |
|
|
gr.update(value="") |
|
|
) |
|
|
|
|
|
|
|
|
def save_audio(audio): |
|
|
print(audio) |
|
|
|
|
|
|
|
|
response = ( |
|
|
supabase.table("users") |
|
|
.select("*") |
|
|
.eq('id', USER_ID) |
|
|
.execute() |
|
|
) |
|
|
print(response) |
|
|
idx = response.data[0]["idx"] |
|
|
save_path = f"recordings/{USER_ID}/audio_{idx}.wav" |
|
|
|
|
|
try: |
|
|
with open(audio, "rb") as f: |
|
|
response = ( |
|
|
supabase.storage |
|
|
.from_("files") |
|
|
.upload( |
|
|
file=f, |
|
|
path=save_path, |
|
|
file_options={"cache-control": "3600", "upsert": "false"} |
|
|
) |
|
|
) |
|
|
idx = (idx + 1) % len(SENTENCES) |
|
|
|
|
|
response = ( |
|
|
supabase.table("users") |
|
|
.update({"idx": idx}) |
|
|
.eq("id", USER_ID) |
|
|
.execute() |
|
|
) |
|
|
|
|
|
return ( |
|
|
"🎉 تم التسجيل بنجاح.. استمر!", |
|
|
None, |
|
|
SENTENCES[idx], |
|
|
gr.update(open=False) |
|
|
) |
|
|
except Exception as e: |
|
|
print(e) |
|
|
return ( |
|
|
"⚠️ هناك مشكلة في حفظ التسجيل, حاول مرة أخري", |
|
|
None, |
|
|
SENTENCES[idx], |
|
|
gr.update(open=False) |
|
|
) |
|
|
|
|
|
def skip_audio(): |
|
|
response = ( |
|
|
supabase.table("users") |
|
|
.select("*") |
|
|
.eq('id', USER_ID) |
|
|
.execute() |
|
|
) |
|
|
print(response) |
|
|
idx = response.data[0]["idx"] |
|
|
idx = (idx + 1) % len(SENTENCES) |
|
|
|
|
|
response = ( |
|
|
supabase.table("users") |
|
|
.update({"idx": idx}) |
|
|
.eq("id", USER_ID) |
|
|
.execute() |
|
|
) |
|
|
return ( |
|
|
"⏭️ تم تخطي التسجيل.. استمر!", |
|
|
None, |
|
|
SENTENCES[idx], |
|
|
gr.update(open=False) |
|
|
) |
|
|
|
|
|
rtl_css = """ |
|
|
|
|
|
* { |
|
|
direction: rtl; |
|
|
text-align: right; |
|
|
} |
|
|
|
|
|
/* Add right padding so text in dropdowns isn’t hidden */ |
|
|
|
|
|
|
|
|
.svelte-1hfxrpf{ |
|
|
padding-right: 0.8rem !important; |
|
|
} |
|
|
|
|
|
""" |
|
|
|
|
|
with gr.Blocks(css=rtl_css) as demo: |
|
|
msg = gr.Markdown("", visible=False) |
|
|
|
|
|
|
|
|
with gr.Group(visible=True) as auth_box: |
|
|
with gr.Tabs(): |
|
|
with gr.Tab("تسجيل الدخول"): |
|
|
with gr.Group(): |
|
|
si_email = gr.Textbox(label="الإيميل", placeholder="you@example.com") |
|
|
si_pw = gr.Textbox(label="كلمة السر", type="password", placeholder="••••••••") |
|
|
si_btn = gr.Button("تسجيل الدخول", variant="primary") |
|
|
si_msg = gr.Markdown("") |
|
|
|
|
|
|
|
|
with gr.Tab("إنشاء حساب"): |
|
|
with gr.Group(): |
|
|
su_name = gr.Textbox(label="الاسم", placeholder="Your name") |
|
|
su_email = gr.Textbox(label="الإيميل", placeholder="you@example.com") |
|
|
su_pw = gr.Textbox(label="كلمة السر", type="password") |
|
|
su_pw2 = gr.Textbox(label="تأكيد كلمة السر", type="password") |
|
|
university = gr.Dropdown(choices=UNIVERSITIES, label="اختر الجامعة", elem_classes="dropdw") |
|
|
country = gr.Dropdown(choices=COUNTRY, label="اختر الدولة", value="السعودية", interactive=False, elem_classes="dropdw") |
|
|
city = gr.Dropdown(choices=CITIES, label="اختر المنطقة", elem_classes="dropdw") |
|
|
dialect = gr.Dropdown(choices=DIALECTS, label="اختر اللهجة", elem_classes="dropdw") |
|
|
gender = gr.Dropdown(choices=GENDER, label="اختر النوع", value="ذكر", elem_classes="dropdw") |
|
|
age = gr.Dropdown(choices=AGE, label="اختر الفئة العمرية", value="25-34",elem_classes="dropdw") |
|
|
with gr.Accordion(label="⚠️ سياسة تجميع البيانات والاستخدام", open=True): |
|
|
gr.Markdown(TERMS) |
|
|
su_btn = gr.Button("إنشاء حساب", variant="primary") |
|
|
su_msg = gr.Markdown("") |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
with gr.Group(visible=False) as app_box: |
|
|
gr.Markdown("## 🇸🇦🎤 تسجيل الجمل الصوتية بلهجتك") |
|
|
with gr.Accordion("تعليمات تسجيل الصوت", open=True) as instruct: |
|
|
gr.Markdown(INSTRUCTIONS) |
|
|
text_sample = gr.Textbox(label="الجملة المطلوب تسجيلها", value=SENTENCES[idx], lines=4, interactive=True) |
|
|
audio = gr.Audio(sources="microphone", type="filepath") |
|
|
output = gr.Textbox(label="الحالة", value="") |
|
|
with gr.Row(): |
|
|
btn_save = gr.Button("حفظ التسجيل", variant="primary") |
|
|
btn_skip = gr.Button("تخطي", variant="secondary") |
|
|
|
|
|
|
|
|
su_btn.click(sign_up, [su_name, su_email, su_pw, su_pw2, university, country, city, dialect, gender, age], [su_msg]) |
|
|
btn_save.click(save_audio,[audio],[output, audio, text_sample, instruct]) |
|
|
si_btn.click(sign_in, [si_email, si_pw], [auth_box,app_box,si_msg, text_sample]) |
|
|
btn_skip.click(skip_audio, [], [output, audio, text_sample, instruct]) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__": |
|
|
demo.queue().launch() |