import re import pandas as pd import gradio as gr # ====================== CONFIG ====================== CSV_FILE = "game_data.csv" df = pd.read_csv(CSV_FILE) TOTAL_STAGES = len(df) # ====================== HELPER ====================== 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) # Common patterns: t=78 or start=78 (allow ?t=78 or &t=78) 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: # If it's already an embed link, keep it but normalize start # (best effort: do not try to re-parse ID) 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 "

⚠️ Invalid YouTube link found in CSV!

" return f"""
""" 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."), } # ====================== GAME LOGIC ====================== 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, # current_stage_idx 0, # current_score "", # video_html_ui (empty -> hidden) f"", # stage_title_ui "", # lyrics_ui (content) "", # guess_input value "", # feedback_ui gr.update(visible=False), # video_box visible gr.update(visible=True), # game_box visible gr.update(visible=False), # game_over_box visible gr.update(visible=True), # play_btn visible gr.update(visible=False), # next_btn visible ) return ( 0, 0, "", # hide video until play f"## 🎵 {stage['title']}", stage["lyrics"], "", "", gr.update(visible=False), # video_box hidden gr.update(visible=True), # game_box visible gr.update(visible=False), # game_over_box hidden gr.update(visible=True), # show Play button gr.update(visible=False), # next hidden ) 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 ( "", # video_html_ui gr.update(visible=False), # video_box ) return ( stage["iframe_html"], gr.update(visible=True), # show video ) 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), # next_btn gr.update(visible=False), # game_over_box ) 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), # show next if not last gr.update(visible=is_last), # show game over if 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: # End: keep iframe hidden, hide next return ( current_idx, "", # video_html_ui cleared f"", "", # lyrics_ui content "", "", gr.update(visible=False), # video_box hidden gr.update(visible=False), # next_btn hidden ) return ( next_idx, "", # clear video_html_ui f"## 🎵 {stage['title']}", stage["lyrics"], "", "", gr.update(visible=False), # hide video again gr.update(visible=False), # next_btn hidden until correct ) def end_game_summary(score): return f"## 🏆 Game Over!\n\nYou scored **{score}/{TOTAL_STAGES}**!" # ====================== UI ====================== 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(): # Video area (we toggle visibility) 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 🔄") # Start/load demo.load( fn=start_game, outputs=[ current_stage_idx, current_score, video_html_ui, # hidden (empty) stage_title_ui, lyrics_ui, # lyrics content guess_input, feedback_ui, video_box, # hide video game_box, # show/hide game panel game_over_box, # show/hide over panel play_btn, # show play button next_btn, # next hidden ], ) # Play button (show iframe) play_btn.click( fn=play_clip, inputs=[current_stage_idx], outputs=[video_html_ui, video_box], ) # Lyrics visibility lyrics_btn.click( fn=show_lyrics, outputs=lyrics_ui ) # Submit guess 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 stage next_btn.click( fn=next_stage, inputs=[current_stage_idx], outputs=[ current_stage_idx, # stage idx video_html_ui, # clear video html stage_title_ui, # update title lyrics_ui, # update lyrics content (still hidden unless user clicks) guess_input, # clear guess feedback_ui, # clear feedback video_box, # hide video again next_btn, # keep next hidden until correct guess ], ).then( fn=lambda: gr.update(visible=True), # show play btn again inputs=[], outputs=[play_btn] ) # Restart 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()