rinabuoy's picture
labubu
3ba82ff
import gradio as gr
import os
import random
from pathlib import Path
# Build a mapping of audio clips to their corresponding images
def build_clip_mapping(audio_dir):
"""Map each audio file to its corresponding image based on matching base names"""
audio_files = sorted([f for f in os.listdir(audio_dir) if f.endswith('.mp3')])
image_files = sorted([f for f in os.listdir(audio_dir) if f.endswith('.png')])
print(f"Found {len(audio_files)} audio files in {audio_dir}")
print(f"Found {len(image_files)} image files in {audio_dir}")
mapping = {}
for audio_file in audio_files:
audio_base = os.path.splitext(audio_file)[0]
for image_file in image_files:
image_base = os.path.splitext(image_file)[0]
if audio_base == image_base:
mapping[audio_file] = image_file
break
print(f"Successfully mapped {len(mapping)} audio-image pairs")
return mapping
class QuizApp:
def __init__(self):
self.current_index = 0
self.score = 0
self.selected_image = None
self.current_correct_index = None
self.audio_dir = None
self.clip_to_image = {}
self.shuffled_clips = []
self.total_questions = 0
def start_quiz(self, audio_set):
"""Initialize quiz with selected audio set"""
self.audio_dir = "audio_clips" if audio_set == "Khmer Script (αžαŸ’αž˜αŸ‚αžš)" else "audio_clips_ko"
self.clip_to_image = build_clip_mapping(self.audio_dir)
valid_clips = list(self.clip_to_image.keys())
self.shuffled_clips = valid_clips.copy()
random.shuffle(self.shuffled_clips)
self.total_questions = len(self.shuffled_clips)
self.current_index = 0
self.score = 0
self.selected_image = None
clip_path, question, image_paths, _ = self.get_current_clip()
image_updates = []
for i in range(4):
if i < len(image_paths):
image_updates.append(gr.Image(value=image_paths[i], visible=True))
else:
image_updates.append(gr.Image(visible=False))
return (
gr.update(visible=False), # Hide start screen
gr.update(visible=True), # Show quiz screen
clip_path,
gr.Markdown(f"### {question}"),
*image_updates,
""
)
def get_current_clip(self):
if self.current_index < len(self.shuffled_clips):
clip_name = self.shuffled_clips[self.current_index]
clip_path = os.path.join(self.audio_dir, clip_name)
correct_image = self.clip_to_image[clip_name]
all_images = [img for audio, img in self.clip_to_image.items() if audio != clip_name]
incorrect_images = random.sample(all_images, min(3, len(all_images)))
all_options = [correct_image] + incorrect_images
random.shuffle(all_options)
correct_index = all_options.index(correct_image)
self.current_correct_index = correct_index
image_paths = [os.path.join(self.audio_dir, img) for img in all_options]
question = f"What did you hear? ({self.current_index + 1}/{self.total_questions})"
return clip_path, question, image_paths, correct_index
self.current_correct_index = None
return None, "Quiz Complete!", [], None
def select_image(self, img_index):
self.selected_image = img_index
return f"βœ“ Selected option {img_index + 1}"
def check_answer(self):
if self.selected_image is None:
return (
"⚠️ Please select an answer first!",
gr.update(), gr.update(),
*[gr.update() for _ in range(4)],
""
)
correct_index = self.current_correct_index
if correct_index is None:
final_msg = f"# πŸŽ‰ Quiz Complete!\n## Final Score: {self.score}/{self.total_questions}\n\n![Labubu](https://media1.tenor.com/m/l-eWQvZW4LgAAAAC/labubu-cute.gif)\n\nGreat work! 🌟"
return (
final_msg,
gr.update(), gr.update(),
*[gr.update() for _ in range(4)],
""
)
is_correct = self.selected_image == correct_index
if is_correct:
self.score += 1
feedback = f"# πŸŽ‰ Correct! πŸŽ‰\n\n![Labubu](https://media1.tenor.com/m/l-eWQvZW4LgAAAAC/labubu-cute.gif)\n\nAwesome job! Score: {self.score}/{self.total_questions}"
else:
feedback = f"# 😊 Oops! Try Again!\n\nDon't worry, you'll get the next one! Score: {self.score}/{self.total_questions}"
# Move to next question
self.current_index += 1
self.selected_image = None
if self.current_index >= self.total_questions:
final_msg = f"# πŸŽ‰ Quiz Complete!\n## Final Score: {self.score}/{self.total_questions}\n\n![Labubu](https://i.pinimg.com/originals/09/b4/f1/09b4f1c4090cbd1ab8165eec92e501ab.gif)\n\nGreat work! 🌟"
return (
final_msg, None, gr.Markdown(""),
*[gr.Image(visible=False) for _ in range(4)],
""
)
next_clip, next_question, next_images, _ = self.get_current_clip()
image_updates = []
for i in range(4):
if i < len(next_images):
image_updates.append(gr.Image(value=next_images[i], visible=True))
else:
image_updates.append(gr.Image(visible=False))
return (
feedback, next_clip, gr.Markdown(f"### {next_question}"),
*image_updates,
""
)
def reset(self):
"""Reset quiz to start screen"""
self.current_index = 0
self.score = 0
self.selected_image = None
self.audio_dir = None
self.clip_to_image = {}
self.shuffled_clips = []
return (
gr.update(visible=True), # Show start screen
gr.update(visible=False), # Hide quiz screen
None,
gr.Markdown(""),
*[gr.Image(visible=False) for _ in range(4)],
"",
""
)
quiz = QuizApp()
def make_select_handler(index):
def handler():
return quiz.select_image(index)
return handler
custom_css = """
.clickable-image {
cursor: pointer !important;
border: 3px solid transparent !important;
border-radius: 8px !important;
transition: all 0.3s ease !important;
}
.clickable-image:hover {
border-color: #2563eb !important;
transform: scale(1.05) !important;
box-shadow: 0 4px 12px rgba(37, 99, 235, 0.3) !important;
}
.feedback-box {
padding: 1.5rem !important;
border-radius: 0.75rem !important;
margin: 1rem 0 !important;
text-align: center !important;
font-size: 1.2rem !important;
}
.labubu-celebration {
text-align: center !important;
margin: 1rem auto !important;
max-width: 300px !important;
}
"""
with gr.Blocks(title="Khmer Learning Quiz", theme=gr.themes.Base(), css=custom_css) as demo:
gr.Markdown("# 🎡 Khmer Audio Learning Quiz")
# Start Screen
with gr.Column(visible=True) as start_screen:
gr.Markdown("## Welcome! πŸ‘‹")
gr.Markdown("### Choose your learning mode:")
audio_set_choice = gr.Radio(
choices=["Khmer Script (αžαŸ’αž˜αŸ‚αžš)", "Korean (ν•œκΈ€/ko)"],
value="Khmer Script (αžαŸ’αž˜αŸ‚αžš)",
label="Select Audio Set",
info="Choose which set of audio clips you want to practice with"
)
start_btn = gr.Button("πŸš€ Start Quiz", variant="primary", size="lg")
# Quiz Screen
with gr.Column(visible=False) as quiz_screen:
gr.Markdown("Listen to each audio clip and select the correct image!")
audio_player = gr.Audio(label="πŸ”Š Listen to the audio clip", autoplay=False)
question_text = gr.Markdown("")
gr.Markdown("### Click on an image to select:")
with gr.Row():
image_options = []
for i in range(4):
img = gr.Image(show_label=False, interactive=False, height=200,
visible=False, elem_classes="clickable-image")
image_options.append(img)
selection_status = gr.Textbox(label="Your Selection", value="", interactive=False)
with gr.Row():
submit_btn = gr.Button("βœ“ Submit Answer", variant="primary", size="lg")
reset_btn = gr.Button("πŸ”„ Back to Start", variant="secondary")
feedback_text = gr.Markdown("", elem_classes="feedback-box")
# Event handlers
start_btn.click(
fn=quiz.start_quiz,
inputs=[audio_set_choice],
outputs=[start_screen, quiz_screen, audio_player, question_text] + image_options + [selection_status]
)
for i in range(4):
image_options[i].select(fn=make_select_handler(i), inputs=[], outputs=[selection_status])
submit_btn.click(fn=quiz.check_answer, inputs=[],
outputs=[feedback_text, audio_player, question_text] + image_options + [selection_status])
reset_btn.click(fn=quiz.reset, inputs=[],
outputs=[start_screen, quiz_screen, audio_player, question_text] + image_options + [selection_status, feedback_text])
if __name__ == "__main__":
demo.launch()