| import re |
| import pandas as pd |
| import gradio as gr |
|
|
| |
| CSV_FILE = "game_data.csv" |
|
|
| df = pd.read_csv(CSV_FILE) |
| TOTAL_STAGES = len(df) |
|
|
| |
| def extract_start_seconds(url_str: str, default_seconds: int = 78) -> int: |
| """ |
| Try to extract start time from: |
| - t=78 |
| - start=78 |
| If not found, fall back to default_seconds (78). |
| Supports URLs like: ...&t=78 or ...?t=78 |
| """ |
| if not url_str: |
| return default_seconds |
|
|
| s = str(url_str) |
|
|
| |
| m = re.search(r"(?:[?&](?:t|start))=(\d+)", s) |
| if m: |
| return int(m.group(1)) |
|
|
| return default_seconds |
|
|
|
|
| def get_embed_url(youtube_url: str, start_seconds: int) -> str: |
| """ |
| Converts a standard YouTube watch/share link into an embed link. |
| Adds start time using ?start=... |
| """ |
| url_str = str(youtube_url).strip() |
| if not url_str: |
| return "" |
|
|
| video_id = "" |
|
|
| if "youtu.be/" in url_str: |
| video_id = url_str.split("youtu.be/")[1].split("?")[0].split("&")[0] |
| elif "v=" in url_str: |
| video_id = url_str.split("v=")[1].split("&")[0].split("?")[0] |
| elif "embed/" in url_str: |
| |
| |
| base = url_str.split("?")[0] |
| return f"{base}?start={start_seconds}" |
|
|
| else: |
| return "" |
|
|
| return f"https://www.youtube.com/embed/{video_id}?start={start_seconds}" |
|
|
|
|
| def generate_iframe_html(embed_url: str) -> str: |
| """Wraps an embed link inside a responsive HTML iframe.""" |
| if not embed_url: |
| return "<p style='color:red;'>⚠️ Invalid YouTube link found in CSV!</p>" |
|
|
| return f""" |
| <div style="display: flex; justify-content: center; margin-bottom: 15px;"> |
| <iframe |
| width="100%" |
| height="315" |
| src="{embed_url}" |
| title="YouTube video player" |
| frameborder="0" |
| allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" |
| allowfullscreen> |
| </iframe> |
| </div> |
| """ |
|
|
|
|
| def get_stage_data(stage_index: int): |
| if stage_index < 0 or stage_index >= TOTAL_STAGES: |
| return None |
|
|
| row = df.iloc[stage_index] |
|
|
| raw_url = row.get("youtube_link", "") |
| start_seconds = extract_start_seconds(raw_url, default_seconds=78) |
| embed_url = get_embed_url(raw_url, start_seconds=start_seconds) |
| iframe_code = generate_iframe_html(embed_url) |
|
|
| title = row.get("title", f"Stage {stage_index + 1}") |
|
|
| return { |
| "stage_no": row.get("stage_no", stage_index + 1), |
| "title": title, |
| "song_name": str(row["song_name"]).strip(), |
| "iframe_html": iframe_code, |
| "lyrics": row.get("lyrics", "No lyrics available."), |
| } |
|
|
|
|
| |
| def start_game(): |
| """ |
| Initializes stage but hides the YouTube iframe until user clicks Play Clip. |
| """ |
| stage = get_stage_data(0) |
| if stage is None: |
| return ( |
| 0, |
| 0, |
| "", |
| f"", |
| "", |
| "", |
| "", |
| gr.update(visible=False), |
| gr.update(visible=True), |
| gr.update(visible=False), |
| gr.update(visible=True), |
| gr.update(visible=False), |
| ) |
|
|
| return ( |
| 0, |
| 0, |
| "", |
| f"## 🎵 {stage['title']}", |
| stage["lyrics"], |
| "", |
| "", |
| gr.update(visible=False), |
| gr.update(visible=True), |
| gr.update(visible=False), |
| gr.update(visible=True), |
| gr.update(visible=False), |
| ) |
|
|
|
|
| def play_clip(current_idx: int): |
| """ |
| Generates and shows the iframe for the current stage. |
| """ |
| stage = get_stage_data(current_idx) |
| if not stage: |
| return ( |
| "", |
| gr.update(visible=False), |
| ) |
|
|
| return ( |
| stage["iframe_html"], |
| gr.update(visible=True), |
| ) |
|
|
|
|
| def show_lyrics(): |
| return gr.update(visible=True) |
|
|
|
|
| def check_guess(guess, current_idx, score): |
| stage = get_stage_data(current_idx) |
| if not stage: |
| return ( |
| score, |
| "Error: invalid stage.", |
| gr.update(visible=False), |
| gr.update(visible=False), |
| ) |
|
|
| is_correct = guess.strip().lower() == stage["song_name"].lower() |
|
|
| if is_correct: |
| score += 1 |
| feedback = "🎉 **Correct!**" |
| else: |
| feedback = f"❌ **Incorrect.** Correct answer: **{stage['song_name']}**" |
|
|
| next_idx = current_idx + 1 |
| is_last = next_idx >= TOTAL_STAGES |
|
|
| return ( |
| score, |
| feedback, |
| gr.update(visible=not is_last), |
| gr.update(visible=is_last), |
| ) |
|
|
|
|
| def next_stage(current_idx): |
| """ |
| Advances to next stage, hides iframe again, and shows Play button. |
| """ |
| next_idx = current_idx + 1 |
| stage = get_stage_data(next_idx) |
|
|
| if not stage: |
| |
| return ( |
| current_idx, |
| "", |
| f"", |
| "", |
| "", |
| "", |
| gr.update(visible=False), |
| gr.update(visible=False), |
| ) |
|
|
| return ( |
| next_idx, |
| "", |
| f"## 🎵 {stage['title']}", |
| stage["lyrics"], |
| "", |
| "", |
| gr.update(visible=False), |
| gr.update(visible=False), |
| ) |
|
|
|
|
| def end_game_summary(score): |
| return f"## 🏆 Game Over!\n\nYou scored **{score}/{TOTAL_STAGES}**!" |
|
|
|
|
| |
| with gr.Blocks() as demo: |
| gr.Markdown("# 🎧 Ultimate YouTube Music Guessing Game") |
| gr.Markdown("Click ▶️ Play Clip to start. Then guess the song name!") |
|
|
| current_stage_idx = gr.State(0) |
| current_score = gr.State(0) |
|
|
| with gr.Column(visible=True) as game_box: |
| stage_title_ui = gr.Markdown() |
|
|
| with gr.Row(): |
| with gr.Column(): |
| |
| with gr.Column(visible=False) as video_box: |
| video_html_ui = gr.HTML() |
|
|
| play_btn = gr.Button("▶️ Play Clip", variant="secondary") |
|
|
| lyrics_btn = gr.Button("💡 Show Lyrics") |
| lyrics_ui = gr.Markdown(visible=False) |
|
|
| with gr.Column(): |
| score_display = gr.Markdown("### Score: 0") |
| guess_input = gr.Textbox(label="Your Guess", placeholder="Enter song name...") |
| submit_btn = gr.Button("Submit", variant="primary") |
| feedback_ui = gr.Markdown() |
| next_btn = gr.Button("Next Stage ➡️", visible=False) |
|
|
| with gr.Column(visible=False) as game_over_box: |
| summary_ui = gr.Markdown() |
| restart_btn = gr.Button("Play Again 🔄") |
|
|
| |
| demo.load( |
| fn=start_game, |
| outputs=[ |
| current_stage_idx, |
| current_score, |
| video_html_ui, |
| stage_title_ui, |
| lyrics_ui, |
| guess_input, |
| feedback_ui, |
| video_box, |
| game_box, |
| game_over_box, |
| play_btn, |
| next_btn, |
| ], |
| ) |
|
|
| |
| play_btn.click( |
| fn=play_clip, |
| inputs=[current_stage_idx], |
| outputs=[video_html_ui, video_box], |
| ) |
|
|
| |
| lyrics_btn.click( |
| fn=show_lyrics, |
| outputs=lyrics_ui |
| ) |
|
|
| |
| submit_btn.click( |
| fn=check_guess, |
| inputs=[guess_input, current_stage_idx, current_score], |
| outputs=[current_score, feedback_ui, next_btn, game_over_box], |
| ).then( |
| fn=lambda s: f"### Score: {s}", |
| inputs=current_score, |
| outputs=score_display |
| ).then( |
| fn=end_game_summary, |
| inputs=current_score, |
| outputs=summary_ui |
| ) |
|
|
| |
| next_btn.click( |
| fn=next_stage, |
| inputs=[current_stage_idx], |
| outputs=[ |
| current_stage_idx, |
| video_html_ui, |
| stage_title_ui, |
| lyrics_ui, |
| guess_input, |
| feedback_ui, |
| video_box, |
| next_btn, |
| ], |
| ).then( |
| fn=lambda: gr.update(visible=True), |
| inputs=[], |
| outputs=[play_btn] |
| ) |
|
|
| |
| restart_btn.click( |
| fn=start_game, |
| outputs=[ |
| current_stage_idx, |
| current_score, |
| video_html_ui, |
| stage_title_ui, |
| lyrics_ui, |
| guess_input, |
| feedback_ui, |
| video_box, |
| game_box, |
| game_over_box, |
| play_btn, |
| next_btn, |
| ], |
| ).then( |
| fn=lambda s: f"### Score: {s}", |
| inputs=current_score, |
| outputs=score_display |
| ) |
|
|
| demo.launch() |