انتقل إلى المحتوى الرئيسي

الوحدة 2 — مكوّنات النصّ والصورة والصوت والفيديو

أظهرت الوحدة السابقة أنّ Gradio يترجم أنواع Python إلى مكوّنات واجهة. لكنّ التصنيف وحده لا يكفي: كلّ مكوّن يستقبل صيغة محدّدة، ويُرجع صيغة محدّدة، وخطأ في هذا يُنتج استثناءات صامتة تظهر بعد أوّل نقرة من المستخدم. هذه الوحدة تفكّك ما تحصل عليه دالّتك عندما يرفع المستخدم صورة أو ملفّ صوت، وما يجب عليها إرجاعه ليظهر شيء صحيح على الشاشة.

مكوّن gr.Textbox: أبسط ما يمكن، وأخطر ما يمكن

النصّ يبدو حالة تافهة، ثمّ نكتشف أنّه المصدر الأوّل للأخطاء في العروض الحقيقيّة. المستخدم يُدخل نصًّا فارغًا، أو نصًّا يحوي رموزًا خاصّة، أو نصًّا بعشرات آلاف الأحرف. المكوّن يقبل خصائص للحدّ من ذلك:

gr.Textbox(
label="سؤالك",
placeholder="اكتب سؤالًا قصيرًا…",
lines=3,
max_lines=10,
max_length=1000,
rtl=True,
)

rtl=True مفيد جدًّا للواجهة العربيّة، فهو يجعل النصّ يتدفّق من اليمين إلى اليسار. أمّا max_length فيمنع المستخدم من إرسال نصّ طويل جدًّا يُطيل زمن الاستجابة أو يستنزف نافذة السياق. ما يُهمّ هو أنّ Gradio لا يتحقّق تلقائيًّا من محتوى النصّ: يجب أن تعالج الدالّة الفراغ والحدود المنطقيّة صراحةً، لأنّ العرض يُشغَّل غالبًا بمستخدمين لا يعرفون قاعدة استعمال النموذج.

مكوّن gr.Image وصيغه الثلاث

gr.Image هو أكثر مكوّن يُخطئ فيه المبتدئون، لأنّ type تُغيّر شكل ما تستقبله الدالّة تمامًا:

typeما تستقبله الدالّةمتى تختاره
"numpy"np.ndarray بشكل (H, W, 3) بقيم uint8 من 0 إلى 255معالجة يدويّة بـOpenCV أو NumPy
"pil"كائن PIL.Image.Image بنمط RGBنماذج PyTorch/torchvision التي تقبل PIL
"filepath"سلسلة مسار مؤقّت على القرصمكتبات تفتح الملفّ بنفسها (ffmpeg، exiftool…)

الصيغة الافتراضيّة (type="numpy") هي أكثر ما يُتوقَّع، لكنّها ليست دائمًا الأنسب. مثال بسيط:

def negatif(image):
return 255 - image # image = np.ndarray (H, W, 3)

gr.Interface(
fn=negatif,
inputs=gr.Image(type="numpy", label="ارفع صورة"),
outputs=gr.Image(label="النتيجة"),
).launch()

أمّا للإرجاع، يقبل gr.Image مصفوفة NumPy أو كائن PIL أو مسار ملفّ بلا تمييز. للفيديو والصور المتحرّكة تُستعمل gr.Video وgr.Gallery.

مكوّن gr.Audio وتردّد أخذ العيّنات

الصوت أكثر تعقيدًا لأنّه يحمل معلومتين: العيّنات وتردّد أخذ العيّنات. صيغة الاستقبال الافتراضيّة زوج:

def duree(audio):
sr, x = audio # sr = تردّد أخذ العيّنات (Hz), x = np.ndarray بأشكال (N,) أو (N, 2)
return f"المدّة: {len(x) / sr:.2f} ثانية عند {sr} هرتز."

gr.Interface(
fn=duree,
inputs=gr.Audio(sources=["microphone", "upload"], type="numpy"),
outputs=gr.Textbox(label="النتيجة"),
).launch()

نسيان أنّ x قد يكون قناتَين (stereo) مصدرٌ شائع للأخطاء عند تمرير الصوت إلى نموذج نسخ يقبل قناة واحدة فقط. الحلّ الآمن هو تحويلها إلى قناة واحدة بمعدّل:

if x.ndim == 2:
x = x.mean(axis=1) # stereo -> mono

كذلك يجب إعادة أخذ العيّنات إلى تردّد النموذج (16000 هرتز شائع لنماذج النسخ) بمكتبة مثل librosa.resample قبل أيّ استدلال.

مكوّن gr.Label: عرض الاحتمالات بشكل صحيح

عند إرجاع تصنيف، تُرجع الدالّة قاموسًا {etiquette: probability} ويتكفّل gr.Label بعرضه في قائمة مرتّبة مع أشرطة تقدّم:

def classer(image):
return {"قطّة": 0.72, "كلب": 0.18, "أرنب": 0.05, "طائر": 0.03, "أخرى": 0.02}

gr.Interface(
fn=classer,
inputs=gr.Image(type="pil"),
outputs=gr.Label(num_top_classes=3),
).launch()

num_top_classes=3 يعرض أعلى ثلاثة فقط، وهو ما يجب أن يفعله كلّ عرض تصنيف: قائمة طويلة من الاحتمالات الصغيرة تُشتّت المستخدم وتُخفي ما يهمّ.

مثال نسخ صوتيّ متكامل

نبني الآن عرضًا يستقبل صوتًا وينسخه إلى نصّ باستعمال نموذج مفتوح صغير:

import gradio as gr, numpy as np, librosa
from transformers import pipeline

nsx = pipeline("automatic-speech-recognition", model="openai/whisper-small")

def transcrire(audio):
if audio is None:
return "لم يُرفَع أيّ ملفّ صوت."
sr, x = audio
if x.ndim == 2:
x = x.mean(axis=1)
x = librosa.resample(x.astype(np.float32) / 32768.0, orig_sr=sr, target_sr=16000)
return nsx({"array": x, "sampling_rate": 16000})["text"]

gr.Interface(
fn=transcrire,
inputs=gr.Audio(sources=["microphone", "upload"], type="numpy", label="سجّل أو ارفع"),
outputs=gr.Textbox(label="النصّ المكتوب", rtl=True),
title="نسخ صوتيّ",
description="سجّل بضع ثوانٍ من الميكروفون أو ارفع ملفًّا واحصل على النصّ.",
).launch()

المهمّ هنا هو معالجة الحالات الحديّة صراحةً: audio is None عندما لا يرفع المستخدم شيئًا، ndim == 2 للصوت الاستيريو، القسمة على 32768.0 لتحويل int16 إلى float32. تخطّي أيّ منها يُنتج استثناء يُخيف المستخدم ويُوقف الجلسة.

حجم الملفّ والزمن

Gradio لا يفرض حدًّا افتراضيًّا صارمًا على حجم الملفّ. مستخدم يرفع ساعة كاملة من الصوت يُشعل معالجة تستغرق دقائق ويحبس قائمة الانتظار. الوحدة 7 ستُقدّم concurrency_limit وmax_batch_size، لكن الحلّ الأبسط هنا هو الاعتراض المبكّر: if len(x) > sr * 60: return "الرجاء تحديد المدّة بدقيقة.".

الخلاصة

  • gr.Textbox أبسط مكوّن لكنّه أكثر مصادر الأخطاء؛ استعمل max_length وrtl=True وتحقّق من الفراغ صراحةً.
  • gr.Image يتغيّر شكل ما تستقبله الدالّة تمامًا بحسب type: مصفوفة NumPy، أو PIL، أو مسار ملفّ.
  • gr.Audio يُرجع زوج (sample_rate, np.ndarray)؛ عالج قناة الاستيريو وإعادة أخذ العيّنات قبل تمريره إلى النموذج.
  • gr.Label يعرض الاحتمالات بأشرطة تقدّم؛ حدّ num_top_classes كي لا يُشتّت المستخدم.

الوحدة التالية: gr.Blocks لبناء واجهات متعدّدة الخطوات، حيث نربط نسخ الصوت بتلخيصه في تدفّق واحد.