Alifbaataa / app.py
NazishHasan's picture
Update app.py
ab23395 verified
import gradio as gr
import os
from transformers import pipeline # Import for ASR (Speech-to-Text) and TTS (Text-to-Speech)
import torchaudio # Required by some TTS models for audio handling
# Define base paths for assets - IMPORTANT: You need to create these folders and place your files here
# For Hugging Face Spaces, these paths are relative to your app.py file
ASSETS_DIR = "./assets"
IMAGE_DIR = os.path.join(ASSETS_DIR, "images")
AUDIO_DIR = os.path.join(ASSETS_DIR, "audio") # This will also be used for dynamically generated audio
# Create asset directories if they don't exist (for local testing, Hugging Face handles this with git lfs)
# On Hugging Face Spaces, you'll upload these folders with your files.
os.makedirs(IMAGE_DIR, exist_ok=True)
os.makedirs(AUDIO_DIR, exist_ok=True)
# Initialize ASR pipeline for pronunciation check
# Using a small, multilingual Whisper model for demonstration.
# For better Arabic-specific performance, consider a model fine-tuned on Arabic speech.
# device=-1 will try to use GPU if available, otherwise CPU.
try:
asr_pipeline = pipeline("automatic-speech-recognition", model="openai/whisper-small", device=-1)
except Exception as e:
print(f"Warning: Could not load ASR model. Pronunciation check will be a placeholder. Error: {e}")
asr_pipeline = None # Set to None if model loading fails
# Initialize TTS pipeline for audio feedback
# Using a specific Arabic TTS model.
try:
# Ensure this model is suitable for your Hugging Face Space's resources.
# facebook/mms-tts-ara is a good general Arabic TTS model.
tts_pipeline = pipeline("text-to-speech", model="facebook/mms-tts-ara", device=-1)
except Exception as e:
print(f"Warning: Could not load TTS model. Audio feedback will not be generated. Error: {e}")
tts_pipeline = None # Set to None if model loading fails
# Data for Arabic Letters
# Updated paths for word_image and word_audio based on the provided "alphabets pictures names.JPG"
arabic_letters_data = [
{
"letter": "أ",
"image": os.path.join(IMAGE_DIR, "alif.jpg"),
"audio": os.path.join(AUDIO_DIR, "alif.wav"),
"word_example": "أرنب (Arnab - Rabbit)",
"word_image": os.path.join(IMAGE_DIR, "arnab.jpg"),
"word_audio": os.path.join(AUDIO_DIR, "arnab.wav"),
},
{
"letter": "ب",
"image": os.path.join(IMAGE_DIR, "ba.jpg"),
"audio": os.path.join(AUDIO_DIR, "ba.wav"),
"word_example": "بطة (Batta - Duck)",
"word_image": os.path.join(IMAGE_DIR, "batta.jpg"),
"word_audio": os.path.join(AUDIO_DIR, "batta.wav"),
},
{
"letter": "ت",
"image": os.path.join(IMAGE_DIR, "taa.jpg"),
"audio": os.path.join(AUDIO_DIR, "taa.wav"),
"word_example": "تفاح (Tuffah - Apple)",
"word_image": os.path.join(IMAGE_DIR, "tuffaha.jpg"), # Corrected from 'placeholder_word_taa.jpg'
"word_audio": os.path.join(AUDIO_DIR, "tuffaha.wav"), # Corrected from 'placeholder_word_taa.wav'
},
{
"letter": "ث",
"image": os.path.join(IMAGE_DIR, "tha.jpg"),
"audio": os.path.join(AUDIO_DIR, "tha.wav"),
"word_example": "ثعلب (Tha'lab - Fox)",
"word_image": os.path.join(IMAGE_DIR, "thalab.jpg"), # Corrected
"word_audio": os.path.join(AUDIO_DIR, "thalab.wav"), # Corrected
},
{
"letter": "ج",
"image": os.path.join(IMAGE_DIR, "jim.jpg"),
"audio": os.path.join(AUDIO_DIR, "jim.wav"),
"word_example": "جمل (Jamal - Camel)",
"word_image": os.path.join(IMAGE_DIR, "jamal.jpg"), # Corrected
"word_audio": os.path.join(AUDIO_DIR, "jamal.wav"), # Corrected
},
{
"letter": "ح",
"image": os.path.join(IMAGE_DIR, "ha.jpg"),
"audio": os.path.join(AUDIO_DIR, "ha.wav"),
"word_example": "حصان (Hisan - Horse)",
"word_image": os.path.join(IMAGE_DIR, "hisan.jpg"), # Corrected
"word_audio": os.path.join(AUDIO_DIR, "hisan.wav"), # Corrected
},
{
"letter": "خ",
"image": os.path.join(IMAGE_DIR, "kha.jpg"),
"audio": os.path.join(AUDIO_DIR, "kha.wav"),
"word_example": "خروف (Kharouf - Sheep)",
"word_image": os.path.join(IMAGE_DIR, "kharouf.jpg"), # Corrected
"word_audio": os.path.join(AUDIO_DIR, "kharouf.wav"), # Corrected
},
{
"letter": "د",
"image": os.path.join(IMAGE_DIR, "dal.jpg"),
"audio": os.path.join(AUDIO_DIR, "dal.wav"),
"word_example": "دجاجة (Dajaja - Chicken)",
"word_image": os.path.join(IMAGE_DIR, "dajaja.jpg"), # Corrected
"word_audio": os.path.join(AUDIO_DIR, "dajaja.wav"), # Corrected
},
{
"letter": "ذ",
"image": os.path.join(IMAGE_DIR, "dhal.jpg"),
"audio": os.path.join(AUDIO_DIR, "dhal.wav"),
"word_example": "ذرة (Dhurah - Corn)",
"word_image": os.path.join(IMAGE_DIR, "dhurah.jpg"), # Corrected
"word_audio": os.path.join(AUDIO_DIR, "dhurah.wav"), # Corrected
},
{
"letter": "ر",
"image": os.path.join(IMAGE_DIR, "ra.jpg"),
"audio": os.path.join(AUDIO_DIR, "ra.wav"),
"word_example": "رمان (Rumman - Pomegranate)",
"word_image": os.path.join(IMAGE_DIR, "rumman.jpg"), # Corrected
"word_audio": os.path.join(AUDIO_DIR, "rumman.wav"), # Corrected
},
{
"letter": "ز",
"image": os.path.join(IMAGE_DIR, "za.jpg"),
"audio": os.path.join(AUDIO_DIR, "za.wav"),
"word_example": "زرافة (Zarafa - Giraffe)",
"word_image": os.path.join(IMAGE_DIR, "zarafa.jpg"), # Corrected
"word_audio": os.path.join(AUDIO_DIR, "zarafa.wav"), # Corrected
},
{
"letter": "س",
"image": os.path.join(IMAGE_DIR, "sin.jpg"),
"audio": os.path.join(AUDIO_DIR, "sin.wav"),
"word_example": "سمكة (Samaka - Fish)",
"word_image": os.path.join(IMAGE_DIR, "samaka.jpg"), # Corrected
"word_audio": os.path.join(AUDIO_DIR, "samaka.wav"), # Corrected
},
{
"letter": "ش",
"image": os.path.join(IMAGE_DIR, "shin.jpg"),
"audio": os.path.join(AUDIO_DIR, "shin.wav"),
"word_example": "شجرة (Shajara - Tree)",
"word_image": os.path.join(IMAGE_DIR, "shajara.jpg"), # Corrected
"word_audio": os.path.join(AUDIO_DIR, "shajara.wav"), # Corrected
},
{
"letter": "ص",
"image": os.path.join(IMAGE_DIR, "sad.jpg"),
"audio": os.path.join(AUDIO_DIR, "sad.wav"),
"word_example": "صابون (Saboon - Soap)",
"word_image": os.path.join(IMAGE_DIR, "saboon.jpg"), # Corrected
"word_audio": os.path.join(AUDIO_DIR, "saboon.wav"), # Corrected
},
{
"letter": "ض",
"image": os.path.join(IMAGE_DIR, "dad.jpg"),
"audio": os.path.join(AUDIO_DIR, "dad.wav"),
"word_example": "ضفدع (Difda - Frog)",
"word_image": os.path.join(IMAGE_DIR, "difda.jpg"), # Corrected
"word_audio": os.path.join(AUDIO_DIR, "difda.wav"), # Corrected
},
{
"letter": "ط",
"image": os.path.join(IMAGE_DIR, "ta.jpg"),
"audio": os.path.join(AUDIO_DIR, "ta.wav"),
"word_example": "طائرة (Ta'ira - Airplane)",
"word_image": os.path.join(IMAGE_DIR, "tairah.jpg"), # Corrected
"word_audio": os.path.join(AUDIO_DIR, "tairah.wav"), # Corrected
},
{
"letter": "ظ",
"image": os.path.join(IMAGE_DIR, "zay.jpg"), # Using zay.jpg for ظ based on previous mapping, ensure this image is correct for ظ
"audio": os.path.join(AUDIO_DIR, "zay.wav"), # Using zay.wav for ظ, ensure this audio is correct for ظ
"word_example": "ظرف (Zarf - Envelope)",
"word_image": os.path.join(IMAGE_DIR, "zarf.jpg"), # Corrected
"word_audio": os.path.join(AUDIO_DIR, "zarf.wav"), # Corrected
},
{
"letter": "ع",
"image": os.path.join(IMAGE_DIR, "ayn.jpg"),
"audio": os.path.join(AUDIO_DIR, "ayn.wav"),
"word_example": "عين (Ayn - Eye)",
"word_image": os.path.join(IMAGE_DIR, "ain.jpg"), # Corrected
"word_audio": os.path.join(AUDIO_DIR, "ain.wav"), # Corrected
},
{
"letter": "غ",
"image": os.path.join(IMAGE_DIR, "ghayn.jpg"),
"audio": os.path.join(AUDIO_DIR, "ghayn.wav"),
"word_example": "غواصة (Ghawwasa - Submarine)",
"word_image": os.path.join(IMAGE_DIR, "ghawwasa.jpg"), # Corrected
"word_audio": os.path.join(AUDIO_DIR, "ghawwasa.wav"), # Corrected
},
{
"letter": "ف",
"image": os.path.join(IMAGE_DIR, "fa.jpg"),
"audio": os.path.join(AUDIO_DIR, "fa.wav"),
"word_example": "فراشة (Farasha - Butterfly)",
"word_image": os.path.join(IMAGE_DIR, "farasha.jpg"), # Corrected
"word_audio": os.path.join(AUDIO_DIR, "farasha.wav"), # Corrected
},
{
"letter": "ق",
"image": os.path.join(IMAGE_DIR, "qaf.jpg"),
"audio": os.path.join(AUDIO_DIR, "qaf.wav"),
"word_example": "قرد (Qird - Monkey)",
"word_image": os.path.join(IMAGE_DIR, "qird.jpg"), # Corrected
"word_audio": os.path.join(AUDIO_DIR, "qird.wav"), # Corrected
},
{
"letter": "ك",
"image": os.path.join(IMAGE_DIR, "kaf.jpg"),
"audio": os.path.join(AUDIO_DIR, "kaf.wav"),
"word_example": "كلب (Kalb - Dog)",
"word_image": os.path.join(IMAGE_DIR, "kalb.jpg"), # Corrected
"word_audio": os.path.join(AUDIO_DIR, "kalb.wav"), # Corrected
},
{
"letter": "ل",
"image": os.path.join(IMAGE_DIR, "lam.jpg"),
"audio": os.path.join(AUDIO_DIR, "lam.wav"),
"word_example": "ليمونة (Laymuna - Lemon)",
"word_image": os.path.join(IMAGE_DIR, "laymuna.jpg"), # Corrected
"word_audio": os.path.join(AUDIO_DIR, "laymuna.wav"), # Corrected
},
{
"letter": "م",
"image": os.path.join(IMAGE_DIR, "mim.jpg"),
"audio": os.path.join(AUDIO_DIR, "mim.wav"),
"word_example": "موزة (Mawzah - Banana)",
"word_image": os.path.join(IMAGE_DIR, "mawzah.jpg"), # Corrected from 'mauzah.jpg'
"word_audio": os.path.join(AUDIO_DIR, "mawzah.wav"), # Corrected from 'mauzah.wav'
},
{
"letter": "ن",
"image": os.path.join(IMAGE_DIR, "nun.jpg"),
"audio": os.path.join(AUDIO_DIR, "nun.wav"),
"word_example": "نمر (Namir - Tiger)",
"word_image": os.path.join(IMAGE_DIR, "namir.jpg"), # Corrected
"word_audio": os.path.join(AUDIO_DIR, "namir.wav"), # Corrected
},
{
"letter": "ه",
"image": os.path.join(IMAGE_DIR, "ha.jpg"),
"audio": os.path.join(AUDIO_DIR, "ha.wav"),
"word_example": "هاتف (Hatif - Telephone)",
"word_image": os.path.join(IMAGE_DIR, "hatif.jpg"), # Corrected
"word_audio": os.path.join(AUDIO_DIR, "hatif.wav"), # Corrected
},
{
"letter": "و",
"image": os.path.join(IMAGE_DIR, "waw.jpg"),
"audio": os.path.join(AUDIO_DIR, "waw.wav"),
"word_example": "وردة (Warda - Rose)",
"word_image": os.path.join(IMAGE_DIR, "warda.jpg"), # Corrected
"word_audio": os.path.join(AUDIO_DIR, "warda.wav"), # Corrected
},
{
"letter": "ي",
"image": os.path.join(IMAGE_DIR, "ya.jpg"),
"audio": os.path.join(AUDIO_DIR, "ya.wav"),
"word_example": "يد (Yad - Hand)",
"word_image": os.path.join(IMAGE_DIR, "yad.jpg"), # Corrected
"word_audio": os.path.join(AUDIO_DIR, "yad.wav"), # Corrected
}
]
# Placeholder data for Arabic Stories
# YOU MUST REPLACE 'placeholder_story_image_pageX.jpg' and 'placeholder_story_audio.wav'
# with actual paths to your story images and audio files within the 'assets/' directory.
arabic_stories_data = [
{
"title": "The Gingerbread Man: رجل خبز الزنجبيل (Rajul Khubz Al-Zanjabeel)",
"pages": [
{
"text": "في يوم من الأيام، خرج أرنب صغير يلعب في الحديقة الخضراء. كان الجو جميلاً والشمس مشرقة.",
"image": os.path.join(IMAGE_DIR, "story1_page1.JPG"), # e.g., 'assets/images/story1_page1.jpg'
"audio": os.path.join(AUDIO_DIR, "story1_page1.wav"), # e.g., 'assets/audio/story1_page1.wav'
},
{
"text": "فجأة، رأى الأرنب زهرة حمراء جميلة. 'ما أجمل هذه الزهرة!' قال الأرنب بسعادة.",
"image": os.path.join(IMAGE_DIR, "story1_page2.JPG"), # e.g., 'assets/images/story1_page2.jpg'
"audio": os.path.join(AUDIO_DIR, "story1_page2.wav"), # e.g., 'assets/audio/story1_page2.wav'
},
{
"text": "فجأة، رأى الأرنب زهرة حمراء جميلة. 'ما أجمل هذه الزهرة!' قال الأرنب بسعادة.",
"image": os.path.join(IMAGE_DIR, "story1_page3.JPG"), # e.g., 'assets/images/story1_page3.jpg'
"audio": os.path.join(AUDIO_DIR, "story1_page3.wav"), # e.g., 'assets/audio/story1_page3.wav'
}, # Add more pages for this story. Each page needs text, an image, and corresponding audio.
]
},
{
"title": "الأسد والفأر (The Lion and the Mouse)",
"pages": [
{
"text": "كان أسد نائماً تحت شجرة كبيرة في الغابة. جاء فأر صغير وركض فوق الأسد.",
"image": os.path.join(IMAGE_DIR, "story2_page1.jpg"), # e.g., 'assets/images/story2_page1.jpg'
"audio": os.path.join(AUDIO_DIR, "story2_page1.wav"), # e.g., 'assets/audio/story2_page1.wav'
},
{
"text": "استيقظ الأسد غاضباً وأمسك بالفأر. توسل الفأر إليه ليتركه، ووعده بمساعدته يوماً ما.",
"image": os.path.join(IMAGE_DIR, "story2_page2.jpg"), # e.g., 'assets/images/story2_page2.jpg'
"audio": os.path.join(AUDIO_DIR, "story2_page2.wav"), # e.g., 'assets/audio/story2_page2.wav'
},
# Add more pages for this story.
]
}
]
# Global state variables for navigation (do not modify directly outside functions)
current_letter_idx = 0
current_story_idx = 0
current_story_page_idx = 0
# --- Functions for Learning Arabic Letters Section ---
def get_current_letter_content():
"""
Retrieves the current letter's data (image, audio, word example, word image, word audio)
based on the global `current_letter_idx`. The letter text is no longer returned directly.
"""
if not arabic_letters_data:
# Handle case where no letter data is available
return None, None, None, None # Adjusted return for consistency
data = arabic_letters_data[current_letter_idx]
# Return (letter_image, letter_audio, word_image, word_audio)
# word_example is no longer returned as an output to Gradio components
return (
data["image"],
data["audio"], # This audio is for the letter sound itself
data["word_image"],
data["word_audio"] # This audio is for the word example sound
)
def next_letter_func():
"""
Advances to the next Arabic letter in the `arabic_letters_data` list.
If at the end, it loops back to the first letter.
"""
global current_letter_idx
if current_letter_idx < len(arabic_letters_data) - 1:
current_letter_idx += 1
else:
current_letter_idx = 0 # Loop back to the beginning
return get_current_letter_content()
def prev_letter_func():
"""
Goes back to the previous Arabic letter in the `arabic_letters_data` list.
If at the beginning, it loops to the last letter.
"""
global current_letter_idx
if current_letter_idx > 0:
current_letter_idx -= 1
else:
current_letter_idx = len(arabic_letters_data) - 1 # Loop to the end
return get_current_letter_content()
def play_audio(audio_path):
"""
Plays an audio file. This function is called by Gradio's Audio component.
It checks if the file exists before returning the path.
"""
if os.path.exists(audio_path):
return audio_path
print(f"Error: Audio file not found at {audio_path}")
# Returning None for audio will result in an error message in Gradio's console
return None
def generate_tts_audio(text_to_speak, filename="temp_feedback_audio.wav"):
"""
Generates audio from text using the TTS pipeline and saves it to a file.
Returns the file path.
"""
if tts_pipeline is None:
print("TTS pipeline not loaded, cannot generate audio.")
return None
try:
# Generate speech
# For 'facebook/mms-tts-ara', the audio output might need specific handling.
# This is a common pattern for Hugging Face TTS pipelines.
output = tts_pipeline(text_to_speak)
# Assuming output is a dictionary like {'audio': numpy_array, 'sampling_rate': int}
audio_array = output['audio']
sampling_rate = output['sampling_rate']
# Save the audio to a temporary file
output_path = os.path.join(AUDIO_DIR, filename)
torchaudio.save(output_path, audio_array.unsqueeze(0), sampling_rate)
return output_path
except Exception as e:
print(f"Error generating TTS audio for '{text_to_speak}': {e}")
return None
def check_pronunciation(audio_input):
"""
Performs pronunciation check using ASR.
Compares transcribed text to the expected Arabic letter.
Returns text feedback and an audio file for the feedback.
"""
feedback_text = ""
feedback_audio_path = None
if audio_input is None:
feedback_text = "من فضلك سجل صوتك أولاً. (Please record your voice first.)"
feedback_audio_path = generate_tts_audio("من فضلك سجل صوتك أولاً.")
return feedback_text, feedback_audio_path
if asr_pipeline is None:
feedback_text = "وظيفة التحقق من النطق غير متوفرة. (Pronunciation check not available. ASR model failed to load.)"
feedback_audio_path = generate_tts_audio("وظيفة التحقق من النطق غير متوفرة.")
return feedback_text, feedback_audio_path
try:
# Transcribe the audio input
# Ensure the language is explicitly set to Arabic for better results with Whisper
transcription_result = asr_pipeline(audio_input, generate_kwargs={"language": "ar"})
transcribed_text = transcription_result["text"].strip().lower()
# Get the expected Arabic letter for comparison
expected_letter = arabic_letters_data[current_letter_idx]["letter"].lower()
# Simple check: does the transcription contain the expected letter?
if expected_letter in transcribed_text:
feedback_text = f"أحسنت! (Excellent!) لقد قلت: '{transcribed_text}'"
feedback_audio_path = generate_tts_audio("أحسنت!")
else:
feedback_text = f"حاول مرة أخرى. (Try again.) لقد قلت: '{transcribed_text}'" \
f" كان المتوقع: '{arabic_letters_data[current_letter_idx]['letter']}'"
feedback_audio_path = generate_tts_audio("حاول مرة أخرى.")
return feedback_text, feedback_audio_path
except Exception as e:
feedback_text = f"حدث خطأ أثناء التحقق من النطق: {e}. (An error occurred during pronunciation check.)"
feedback_audio_path = generate_tts_audio("حدث خطأ أثناء التحقق من النطق.") # Generic error audio
return feedback_text, feedback_audio_path
# --- Functions for Arabic Storytelling Section ---
def get_story_titles():
"""
Returns a list of story titles to populate the dropdown menu.
"""
return [story["title"] for story in arabic_stories_data]
def select_story_func(title):
"""
Selects a story based on its title from the dropdown.
Resets the page index to 0 for the newly selected story.
"""
global current_story_idx, current_story_page_idx
for i, story in enumerate(arabic_stories_data):
if story["title"] == title:
current_story_idx = i
current_story_page_idx = 0 # Reset to the first page of the new story
break
# After selecting, return the content of the first page of the chosen story
return get_current_story_page_content()
def get_current_story_page_content():
"""
Retrieves the content (title, text, image, audio) for the current page
of the currently selected story.
"""
if not arabic_stories_data:
# Handle case where no story data is available
return "لا توجد قصص متاحة", None, None, None
story = arabic_stories_data[current_story_idx]
if current_story_page_idx < len(story["pages"]):
# Return content for the current page
page = story["pages"][current_story_page_idx]
return story["title"], page["text"], page["image"], page["audio"]
else:
# If past the last page, indicate the end of the story
return story["title"], "انتهت القصة! (End of story!)", None, None # No image/audio at end
def next_story_page_func():
"""
Advances to the next page of the current story.
If at the end of the story, it stays on the last page.
"""
global current_story_page_idx
story = arabic_stories_data[current_story_idx]
if current_story_page_idx < len(story["pages"]) - 1:
current_story_page_idx += 1
# If at the last page, do nothing (stay on the last page)
return get_current_story_page_content()
def prev_story_page_func():
"""
Goes back to the previous page of the current story.
If at the beginning of the story, it loops to the last letter.
"""
global current_story_page_idx
if current_story_page_idx > 0:
current_story_page_idx -= 1
# If at the first page, do nothing (stay on the first page)
return get_current_story_page_content()
# --- Gradio UI Definition using gr.Blocks for advanced layout and styling ---
with gr.Blocks(
theme=gr.themes.Soft(), # A soft, pleasant theme
# Custom CSS for better aesthetics and responsiveness
css="""
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700&display=swap');
body {
font-family: 'Inter', sans-serif;
}
.gradio-container {
font-family: 'Inter', sans-serif;
background-color: #f0f8ff; /* Light blue background for the entire app */
border-radius: 15px;
box-shadow: 0 8px 16px rgba(0,0,0,0.2); /* Enhanced shadow */
padding: 25px;
max-width: 950px; /* Slightly wider max-width */
margin: 30px auto;
border: 2px solid #aaddff; /* Light blue border */
}
h1, h2, h3 {
color: #2c3e50; /* Darker blue-grey for headings */
text-align: center;
margin-bottom: 25px;
font-weight: 700; /* Bold headings */
}
.gr-tab-item {
border-radius: 12px 12px 0 0 !important; /* Rounded top corners for tabs */
background-color: #e6f7ff; /* Lighter tab background */
font-size: 1.15em; /* Larger tab text */
padding: 12px 20px;
transition: background-color 0.3s ease;
}
.gr-tab-item.selected {
background-color: #ffffff !important;
border-bottom: none !important;
color: #007bff !important; /* Vibrant blue for selected tab */
font-weight: bold;
box-shadow: 0 -3px 8px rgba(0,0,0,0.1); /* Shadow for selected tab */
}
.gr-button {
background-color: #007bff; /* Primary blue button */
color: white;
border-radius: 12px; /* More rounded buttons */
padding: 12px 25px;
font-size: 1.15em; /* Larger button text */
margin: 8px;
box-shadow: 0 4px 8px rgba(0,0,0,0.25); /* More prominent shadow */
transition: background-color 0.3s ease, transform 0.2s ease, box-shadow 0.3s ease;
border: none; /* No default border */
cursor: pointer;
}
.gr-button:hover {
background-color: #0056b3; /* Darker blue on hover */
transform: translateY(-3px); /* Lift effect on hover */
box-shadow: 0 6px 12px rgba(0,0,0,0.3); /* Larger shadow on hover */
}
.gr-image {
border-radius: 18px; /* More rounded images */
overflow: hidden;
border: 3px solid #aaddff; /* Thicker light blue border */
box-shadow: 0 4px 10px rgba(0,0,0,0.15);
background-color: #ffffff;
display: block; /* Ensure image is a block element for centering */
margin: 15px auto; /* Center images */
}
.gr-audio {
border-radius: 12px;
border: 2px solid #cceeff;
background-color: #ffffff;
padding: 10px;
margin: 10px 0;
}
.gr-markdown {
background-color: #ffffff;
border-radius: 12px;
padding: 20px;
margin-top: 15px;
border: 2px solid #cceeff;
text-align: center;
font-size: 1.3em; /* Larger text for readability for kids */
color: #333;
line-height: 1.6; /* Improve line spacing */
box-shadow: 0 2px 6px rgba(0,0,0,0.08);
}
.gr-dropdown {
border-radius: 12px;
padding: 8px;
font-size: 1.1em;
border: 1px solid #cceeff;
box-shadow: inset 0 1px 3px rgba(0,0,0,0.05);
}
.feedback-message {
color: #28a745; /* Green for success/positive feedback */
font-weight: bold;
text-align: center;
margin-top: 20px;
font-size: 1.2em;
}
.error-message {
color: #dc3545; /* Red for error messages */
font-weight: bold;
text-align: center;
margin-top: 20px;
font-size: 1.2em;
}
hr {
border: 0;
height: 1px;
background-image: linear-gradient(to right, rgba(0, 0, 0, 0), rgba(0, 123, 255, 0.75), rgba(0, 0, 0, 0));
margin: 30px 0;
}
[dir="rtl"] .gr-button, [dir="rtl"] .gr-markdown, [dir="rtl"] .gr-dropdown {
text-align: right; /* Ensure RTL alignment for text within components */
}
"""
) as demo:
# Main application title and welcoming message
gr.Markdown("# <span style='color:#007bff;'>تطبيق تعلم العربية للأطفال</span>", rtl=True)
# Add logo image
gr.Image(
value=os.path.join(IMAGE_DIR, "Applogo.png"), # Path to your logo image
label="شعار التطبيق (App Logo)",
width=300,
height=150,
interactive=False,
elem_classes=["gr-image"]
)
gr.Markdown("## <span style='color:#34495e;'>مرحباً يا أبطالنا الصغار! (Welcome, Little Heroes!)</span>", rtl=True)
# Tabs for different learning modes
with gr.Tabs():
# --- Arabic Letters Learning Tab ---
with gr.TabItem("تعلم الحروف العربية (Learn Arabic Letters)"):
with gr.Column(
scale=1,
min_width=400,
elem_id="letter-learning-section",
variant="panel"
):
gr.Markdown("### <span style='color:#28a745;'>تعلم حرفاً جديداً كل يوم! (Learn a new letter every day!)</span>", rtl=True)
# The letter image will now be the primary display for the letter.
letter_image = gr.Image(
label="صورة الحرف (Letter Image)",
width=250, height=250,
interactive=False,
elem_classes=["gr-image"]
)
# Hidden audio component to play the letter sound
letter_audio_output = gr.Audio(
label="صوت الحرف (Letter Sound)",
autoplay=True,
type="filepath",
visible=False
)
# Navigation buttons for letters
with gr.Row():
prev_letter_btn = gr.Button("الحرف السابق (Previous Letter)", elem_classes=["gr-button"])
play_letter_btn = gr.Button("استمع للحرف (Listen to Letter)", elem_classes=["gr-button"])
next_letter_btn = gr.Button("الحرف التالي (Next Letter)", elem_classes=["gr-button"])
#gr.Markdown("<hr>", elem_classes=["gr-markdown"])
# --- Word Example Section for the current letter ---
gr.Markdown("### <span style='color:#ffc107;'>كلمات تبدأ بهذا الحرف (Words starting with this letter)</span>", rtl=True)
# Display image for the example word
word_image = gr.Image(
label="صورة الكلمة (Word Image)",
width=250, height=250,
interactive=False,
elem_classes=["gr-image"]
)
# Hidden audio component to play the word sound
word_audio_output = gr.Audio(
label="صوت الكلمة (Word Sound)",
autoplay=True,
type="filepath",
visible=False
)
# Button to play the word audio
play_word_btn = gr.Button("استمع للكلمة (Listen to Word)", elem_classes=["gr-button"])
gr.Markdown("<hr>", elem_classes=["gr-markdown"])
# --- Pronunciation Practice Section ---
gr.Markdown("### <span style='color:#17a2b8;'>تدرب على النطق (Practice Pronunciation)</span>", rtl=True)
# Microphone input for user to record pronunciation
pronunciation_input = gr.Audio(
sources=["microphone"],
type="filepath",
label="سجل صوتك وأنت تنطق الحرف/الكلمة (Record your voice saying the letter/word)",
elem_classes=["gr-audio"]
)
# Markdown to display feedback on pronunciation
pronunciation_feedback = gr.Markdown(
label="التقييم (Feedback)",
rtl=True,
elem_classes=["gr-markdown"]
)
# New Audio component to play pronunciation feedback
pronunciation_feedback_audio = gr.Audio(
label="صوت التقييم (Feedback Audio)",
autoplay=True,
type="filepath",
visible=False # Keep it hidden, as we only trigger playback
)
# Button to trigger the pronunciation check
check_pronunciation_btn = gr.Button("تحقق من النطق (Check Pronunciation)", elem_classes=["gr-button"])
# --- Arabic Storytelling Tab ---
with gr.TabItem("قصص عربية (Arabic Storytelling)"):
with gr.Column(
scale=1,
min_width=400,
elem_id="storytelling-section",
variant="panel"
):
gr.Markdown("### <span style='color:#fd7e14;'>استمتع بقصصنا الشيقة! (Enjoy our exciting stories!)</span>", rtl=True)
# Dropdown to select a story
story_selection_dropdown = gr.Dropdown(
choices=get_story_titles(),
label="اختر قصة (Choose a Story)",
value=get_story_titles()[0] if get_story_titles() else None,
elem_classes=["gr-dropdown"]
)
# Display area for story title
story_title_display = gr.Markdown(
value="",
label="عنوان القصة (Story Title)",
rtl=True,
elem_classes=["gr-markdown"]
)
# Display area for story text
story_text_display = gr.Markdown(
value="",
label="نص القصة (Story Text)",
rtl=True,
elem_classes=["gr-markdown"]
)
# Display image for the current story page
story_image_display = gr.Image(
label="صورة القصة (Story Image)",
width=450, height=350,
interactive=False,
elem_classes=["gr-image"]
)
# Hidden audio component to play story page audio
story_audio_output = gr.Audio(
label="صوت القصة (Story Audio)",
autoplay=True,
type="filepath",
visible=False
)
# Navigation buttons for story pages
with gr.Row():
prev_story_page_btn = gr.Button("الصفحة السابقة (Previous Page)", elem_classes=["gr-button"])
play_story_btn = gr.Button("استمع للقصة (Listen to Story)", elem_classes=["gr-button"])
next_story_page_btn = gr.Button("الصفحة التالية (Next Page)", elem_classes=["gr-button"])
# --- Event Handlers (Logic for button clicks and component changes) ---
# --- Event Handlers for Learning Arabic Letters Tab ---
# When the demo loads, display the content of the first letter
demo.load(
get_current_letter_content,
inputs=None,
outputs=[
letter_image,
letter_audio_output,
word_image,
word_audio_output
],
queue=False
)
# Connect navigation buttons to their respective functions
next_letter_btn.click(
next_letter_func,
inputs=None,
outputs=[
letter_image,
letter_audio_output,
word_image,
word_audio_output
]
)
prev_letter_btn.click(
prev_letter_func,
inputs=None,
outputs=[
letter_image,
letter_audio_output,
word_image,
word_audio_output
]
)
# Connect play audio buttons. Using lambda to pass the current audio path dynamically.
play_letter_btn.click(
lambda: play_audio(arabic_letters_data[current_letter_idx]["audio"]),
inputs=None,
outputs=letter_audio_output
)
play_word_btn.click(
lambda: play_audio(arabic_letters_data[current_letter_idx]["word_audio"]),
inputs=None,
outputs=word_audio_output
)
# Connect pronunciation check button
check_pronunciation_btn.click(
check_pronunciation,
inputs=pronunciation_input,
outputs=[pronunciation_feedback, pronunciation_feedback_audio]
)
# --- Event Handlers for Arabic Storytelling Tab ---
# When a new story is selected from the dropdown
story_selection_dropdown.change(
select_story_func,
inputs=story_selection_dropdown,
outputs=[story_title_display, story_text_display, story_image_display, story_audio_output]
)
# Connect navigation buttons for story pages
next_story_page_btn.click(
next_story_page_func,
inputs=None,
outputs=[story_title_display, story_text_display, story_image_display, story_audio_output]
)
prev_story_page_btn.click(
prev_story_page_func,
inputs=None,
outputs=[story_title_display, story_text_display, story_image_display, story_audio_output]
)
# Connect button to play the current story page's audio
play_story_btn.click(
lambda: play_audio(arabic_stories_data[current_story_idx]["pages"][current_story_page_idx]["audio"]),
inputs=None,
outputs=story_audio_output
)
# Launch the Gradio application
demo.launch()