import gradio as gr import pandas as pd import os from athletic_performance import ( analyze_youtube_video, analyze_video_file, get_performance_insights, get_ai_sports_coaching_analysis, test_gemini_api_connection, generate_annotated_video_from_youtube, generate_annotated_video_from_file ) def analyze_jump_from_youtube(youtube_url, user_height_cm, user_weight_kg, progress=gr.Progress()): """Main analysis function for Gradio interface.""" # Create progress callback for the athletic_performance module def progress_callback(prog, desc): progress(prog, desc=desc) # Call the core analysis function result = analyze_youtube_video(youtube_url, user_height_cm, user_weight_kg, progress_callback) # Handle errors if "error" in result: return f"โŒ {result['error']}", None, None if result is None: return "โš ๏ธ Could not analyze jump. Make sure the video shows a person clearly performing a vertical jump.", None, None # Format results for display # Handle potential None values safely jump_height = result.get('jump_height_cm', 0) or 0 flight_time = result.get('flight_time_s', 0) or 0 normalized_rise = result.get('normalized_rise', 0) or 0 peak_power = result.get('peak_power_watts', 0) or 0 peak_force = result.get('peak_force_n', 0) or 0 impulse = result.get('impulse_ns', 0) or 0 rfd = result.get('rate_of_force_development', 0) or 0 takeoff_duration = result.get('takeoff_phase_duration_s', 0) or 0 ground_contact = result.get('ground_contact_time_s', 0) or 0 results_text = f""" ## ๐ŸŽ‰ Comprehensive Jump Analysis Results ### ๐Ÿ“Š Core Performance Metrics - **Jump Height**: {jump_height:.2f} cm - **Flight Time**: {flight_time:.3f} seconds - **Normalized Rise**: {normalized_rise:.3f} ({normalized_rise*100:.1f}%) ### โšก Power & Force Metrics - **Peak Power Output**: {peak_power:.0f} watts - **Peak Force**: {peak_force:.0f} N - **Impulse**: {impulse:.2f} Nโ‹…s ### ๐Ÿš€ Explosiveness Metrics - **Rate of Force Development**: {rfd:.2f} - **Takeoff Phase Duration**: {takeoff_duration:.3f} seconds - **Ground Contact Time**: {ground_contact:.3f} seconds ### ๐Ÿ“น Video Information - **Total Frames**: {result['frames']} - **Frame Rate**: {result['fps']:.2f} FPS - **Video File**: {result['video']} - **Subject Weight**: {result.get('user_weight_kg', 'N/A')} kg ### ๐Ÿ“ˆ Performance Insights """ # Add performance insights using the new function insights = get_performance_insights(result) for insight in insights: results_text += f"{insight}\n" # Create a comprehensive results dataframe for the table results_df = pd.DataFrame([ ["Jump Height", f"{jump_height:.2f} cm"], ["Flight Time", f"{flight_time:.3f} seconds"], ["Peak Power", f"{peak_power:.0f} watts"], ["Peak Force", f"{peak_force:.0f} N"], ["Rate of Force Development", f"{rfd:.2f}"], ["Ground Contact Time", f"{ground_contact:.3f} seconds"], ["Impulse", f"{impulse:.2f} Nโ‹…s"], ["Takeoff Duration", f"{takeoff_duration:.3f} seconds"], ["Normalized Rise", f"{normalized_rise*100:.1f}%"], ["Video Frames", f"{result.get('frames', 0)}"], ["Frame Rate", f"{result.get('fps', 0):.2f} FPS"], ], columns=["Metric", "Value"]) return results_text, results_df, "โœ… Analysis completed successfully!" def analyze_jump_from_file(video_file, user_height_cm, user_weight_kg, progress=gr.Progress()): """Analysis function for uploaded video files.""" # Create progress callback for the athletic_performance module def progress_callback(prog, desc): progress(prog, desc=desc) # Call the core analysis function video_path = video_file.name if video_file else None result = analyze_video_file(video_path, user_height_cm, user_weight_kg, progress_callback) # Handle errors if "error" in result: return f"โŒ {result['error']}", None, None if result is None: return "โš ๏ธ Could not analyze jump. Make sure the video shows a person clearly performing a vertical jump.", None, None # Format results (same as YouTube function) # Handle potential None values safely jump_height = result.get('jump_height_cm', 0) or 0 flight_time = result.get('flight_time_s', 0) or 0 normalized_rise = result.get('normalized_rise', 0) or 0 peak_power = result.get('peak_power_watts', 0) or 0 peak_force = result.get('peak_force_n', 0) or 0 impulse = result.get('impulse_ns', 0) or 0 rfd = result.get('rate_of_force_development', 0) or 0 takeoff_duration = result.get('takeoff_phase_duration_s', 0) or 0 ground_contact = result.get('ground_contact_time_s', 0) or 0 results_text = f""" ## ๐ŸŽ‰ Comprehensive Jump Analysis Results ### ๐Ÿ“Š Core Performance Metrics - **Jump Height**: {jump_height:.2f} cm - **Flight Time**: {flight_time:.3f} seconds - **Normalized Rise**: {normalized_rise:.3f} ({normalized_rise*100:.1f}%) ### โšก Power & Force Metrics - **Peak Power Output**: {peak_power:.0f} watts - **Peak Force**: {peak_force:.0f} N - **Impulse**: {impulse:.2f} Nโ‹…s ### ๐Ÿš€ Explosiveness Metrics - **Rate of Force Development**: {rfd:.2f} - **Takeoff Phase Duration**: {takeoff_duration:.3f} seconds - **Ground Contact Time**: {ground_contact:.3f} seconds ### ๐Ÿ“น Video Information - **Total Frames**: {result['frames']} - **Frame Rate**: {result['fps']:.2f} FPS - **Video File**: {result['video']} - **Subject Weight**: {result.get('user_weight_kg', 'N/A')} kg ### ๐Ÿ“ˆ Performance Insights """ # Add performance insights using the new function insights = get_performance_insights(result) for insight in insights: results_text += f"{insight}\n" # Create a comprehensive results dataframe for the table results_df = pd.DataFrame([ ["Jump Height", f"{jump_height:.2f} cm"], ["Flight Time", f"{flight_time:.3f} seconds"], ["Peak Power", f"{peak_power:.0f} watts"], ["Peak Force", f"{peak_force:.0f} N"], ["Rate of Force Development", f"{rfd:.2f}"], ["Ground Contact Time", f"{ground_contact:.3f} seconds"], ["Impulse", f"{impulse:.2f} Nโ‹…s"], ["Takeoff Duration", f"{takeoff_duration:.3f} seconds"], ["Normalized Rise", f"{normalized_rise*100:.1f}%"], ["Video Frames", f"{result.get('frames', 0)}"], ["Frame Rate", f"{result.get('fps', 0):.2f} FPS"], ], columns=["Metric", "Value"]) return results_text, results_df, "โœ… Analysis completed successfully!" def get_ai_coaching_recommendations(youtube_url, video_file, user_height_cm, user_weight_kg, gender, favorite_sports, gemini_api_key, progress=gr.Progress()): """Get AI-powered sports coaching recommendations.""" # Validate inputs if not gemini_api_key or not gemini_api_key.strip(): return "โŒ Please provide your Gemini API key", None, None if not gender: return "โŒ Please select your gender", None, None if not user_height_cm or user_height_cm <= 0: return "โŒ Please provide a valid height", None, None # Validate favorite sports if not favorite_sports or len(favorite_sports) == 0: return "โŒ Please select at least one favorite sport", None, None if len(favorite_sports) > 5: return "โŒ Please select maximum 5 favorite sports", None, None # Determine which video source to use video_source = None if youtube_url and youtube_url.strip(): video_source = "youtube" progress(0.1, desc="Analyzing YouTube video...") elif video_file: video_source = "file" progress(0.1, desc="Analyzing uploaded video...") else: return "โŒ Please provide either a YouTube URL or upload a video file", None, None try: # First, get the jump analysis progress(0.2, desc="Performing biomechanical analysis...") def progress_callback(prog, desc): progress(0.2 + (prog * 0.5), desc=desc) if video_source == "youtube": result = analyze_youtube_video(youtube_url, user_height_cm, user_weight_kg, progress_callback) else: video_path = video_file.name if video_file else None result = analyze_video_file(video_path, user_height_cm, user_weight_kg, progress_callback) # Handle analysis errors if "error" in result: return f"โŒ Video analysis failed: {result['error']}", None, None if result is None: return "โŒ Could not analyze jump. Please ensure the video shows a clear vertical jump.", None, None progress(0.7, desc="Getting AI coaching analysis...") # Get AI coaching analysis ai_result = get_ai_sports_coaching_analysis( jump_height_cm=result['jump_height_cm'], user_height_cm=user_height_cm, gender=gender, favorite_sports=favorite_sports, peak_power_watts=result.get('peak_power_watts'), flight_time_s=result.get('flight_time_s'), rfd=result.get('rate_of_force_development'), api_key=gemini_api_key.strip() ) progress(0.9, desc="Formatting results...") if "error" in ai_result: return f"โŒ AI analysis failed: {ai_result['error']}", None, None # Format the comprehensive results # Handle potential None values safely jump_height = result.get('jump_height_cm', 0) or 0 flight_time = result.get('flight_time_s', 0) or 0 peak_power = result.get('peak_power_watts', 0) or 0 # Format favorite sports list for display sports_display = ", ".join(favorite_sports) results_text = f""" # ๐Ÿค– AI Sports Coaching Analysis ## ๐Ÿ“Š Performance Summary - **Jump Height**: {jump_height:.2f} cm - **Relative Jump**: {(jump_height/user_height_cm*100):.1f}% of body height - **Flight Time**: {flight_time:.3f} seconds - **Peak Power**: {peak_power:.0f} watts - **Gender**: {gender} - **Height**: {user_height_cm} cm - **Favorite Sports**: {sports_display} ## ๐Ÿ† AI Expert Coaching Analysis **๐Ÿ“Š Performance Percentiles:** {chr(10).join([f"- **{sport}**: {percentile}th percentile" for sport, percentile in ai_result.get('analysis', {}).get('sports', {}).items()])} **๐Ÿ’ก Improvement Tips:** {chr(10).join([f"{i+1}. {tip}" for i, tip in enumerate(ai_result.get('analysis', {}).get('tips', []))])} --- *Analysis powered by Google Gemini AI* """ # Create summary dataframe rfd = result.get('rate_of_force_development', 0) or 0 summary_df = pd.DataFrame([ ["Jump Height", f"{jump_height:.2f} cm"], ["Relative Jump Height", f"{(jump_height/user_height_cm*100):.1f}%"], ["Flight Time", f"{flight_time:.3f} seconds"], ["Peak Power", f"{peak_power:.0f} watts"], ["Rate of Force Development", f"{rfd:.2f}"], ["Gender", gender], ["Height", f"{user_height_cm} cm"], ["Weight", f"{user_weight_kg} kg"], ["Favorite Sports", sports_display], ], columns=["Metric", "Value"]) progress(1.0, desc="AI coaching analysis complete!") return results_text, summary_df, "โœ… AI coaching analysis completed!" except Exception as e: return f"โŒ Unexpected error: {str(e)}", None, None def test_api_key(api_key): """Test the API key connection.""" if not api_key or not api_key.strip(): return "โŒ Please provide an API key to test" result = test_gemini_api_connection(api_key.strip()) if result["success"]: return f"โœ… API Key is working! Status: {result['status_code']}\n\nResponse preview: {result['response_text'][:100]}..." else: return f"โŒ API Key test failed!\n\nStatus Code: {result['status_code']}\nError: {result['error']}\n\nResponse: {result['response_text']}" def generate_video_from_youtube(youtube_url, user_height_cm, user_weight_kg, gender, progress=gr.Progress()): """Generate annotated video from YouTube URL.""" # Create progress callback def progress_callback(prog, desc): progress(prog, desc=desc) # Call the video generation function result = generate_annotated_video_from_youtube( youtube_url, user_height_cm, user_weight_kg, gender, progress_callback ) # Handle errors if "error" in result: return f"โŒ Video generation failed: {result['error']}", None, None if result is None: return "โŒ Could not generate video. Please ensure the video shows a clear vertical jump.", None, None # Format results video_path = result.get("output_video_path", "") jump_metrics = result.get("jump_metrics", {}) results_text = f""" # ๐ŸŽฌ Annotated Video Generated! ## ๐Ÿ“Š Jump Analysis Summary - **Jump Height**: {jump_metrics.get('jump_height_cm', 0):.2f} cm - **Flight Time**: {jump_metrics.get('flight_time_s', 0):.3f} seconds - **Peak Power**: {jump_metrics.get('peak_power_watts', 0):.0f} watts - **Frames Processed**: {result.get('total_frames_processed', 0)} ## ๐ŸŽฅ Video Features Added - โœ… **Pose Tracking**: Real-time skeleton overlay - โœ… **Jump Reference Lines**: Average vs Professional heights - โœ… **Knee Strain Detection**: Red markers for poor form - โœ… **Performance Metrics**: Live jump height tracking ## ๐Ÿ“ฅ Download Your annotated video is ready for download! """ # Create summary dataframe summary_df = pd.DataFrame([ ["Jump Height", f"{jump_metrics.get('jump_height_cm', 0):.2f} cm"], ["Flight Time", f"{jump_metrics.get('flight_time_s', 0):.3f} seconds"], ["Peak Power", f"{jump_metrics.get('peak_power_watts', 0):.0f} watts"], ["Video Features", "Pose + References + Strain Detection"], ["Output Format", "MP4 Video"], ["Status", "โœ… Ready for Download"], ], columns=["Metric", "Value"]) return results_text, summary_df, video_path def generate_video_from_file(video_file, user_height_cm, user_weight_kg, gender, progress=gr.Progress()): """Generate annotated video from uploaded file.""" # Create progress callback def progress_callback(prog, desc): progress(prog, desc=desc) # Call the video generation function video_path = video_file.name if video_file else None result = generate_annotated_video_from_file( video_path, user_height_cm, user_weight_kg, gender, progress_callback ) # Handle errors if "error" in result: return f"โŒ Video generation failed: {result['error']}", None, None if result is None: return "โŒ Could not generate video. Please ensure the video shows a clear vertical jump.", None, None # Format results (same as YouTube function) video_path = result.get("output_video_path", "") jump_metrics = result.get("jump_metrics", {}) results_text = f""" # ๐ŸŽฌ Annotated Video Generated! ## ๐Ÿ“Š Jump Analysis Summary - **Jump Height**: {jump_metrics.get('jump_height_cm', 0):.2f} cm - **Flight Time**: {jump_metrics.get('flight_time_s', 0):.3f} seconds - **Peak Power**: {jump_metrics.get('peak_power_watts', 0):.0f} watts - **Frames Processed**: {result.get('total_frames_processed', 0)} ## ๐ŸŽฅ Video Features Added - โœ… **Pose Tracking**: Real-time skeleton overlay - โœ… **Jump Reference Lines**: Average vs Professional heights - โœ… **Knee Strain Detection**: Red markers for poor form - โœ… **Performance Metrics**: Live jump height tracking ## ๐Ÿ“ฅ Download Your annotated video is ready for download! """ # Create summary dataframe summary_df = pd.DataFrame([ ["Jump Height", f"{jump_metrics.get('jump_height_cm', 0):.2f} cm"], ["Flight Time", f"{jump_metrics.get('flight_time_s', 0):.3f} seconds"], ["Peak Power", f"{jump_metrics.get('peak_power_watts', 0):.0f} watts"], ["Video Features", "Pose + References + Strain Detection"], ["Output Format", "MP4 Video"], ["Status", "โœ… Ready for Download"], ], columns=["Metric", "Value"]) return results_text, summary_df, video_path # Create Gradio interface def create_interface(): with gr.Blocks(title="๐Ÿƒโ€โ™‚๏ธ Athletic Ability Analysis") as app: gr.Markdown(""" # ๐Ÿƒโ€โ™‚๏ธ Athletic Ability Analysis & AI Sports Coach Analyze jumping performance from videos using computer vision and get AI-powered sports coaching recommendations. Upload a video or provide a YouTube URL to get detailed metrics and personalized coaching insights. ## ๐Ÿš€ Features - **๐Ÿ“Š Biomechanical Analysis**: Comprehensive jump metrics (height, power, force, RFD) - **๐Ÿค– AI Sports Coach**: Personalized sport recommendations and technique improvements - **๐ŸŽฌ Annotated Videos**: Generate training videos with pose tracking and performance overlays - **โš ๏ธ Technique Analysis**: Real-time knee strain detection and form corrections - **๐ŸŽฏ Performance Insights**: Professional-grade analysis and training suggestions ## ๐Ÿ“‹ Instructions 1. Enter your height in centimeters and weight in kilograms 2. Choose your analysis type: - **๐Ÿ“Š Standard Analysis**: Get detailed biomechanical metrics - **๐Ÿค– AI Sports Coach**: Personalized recommendations and sport suggestions - **๐ŸŽฌ Video Generation**: Create annotated training videos with visual overlays 3. Provide a video (YouTube URL or file upload) 4. Get comprehensive results, actionable insights, or downloadable training videos """) with gr.Row(): with gr.Column(): user_height = gr.Number( label="Your Height (cm)", value=175, minimum=100, maximum=250 ) with gr.Column(): user_weight = gr.Number( label="Your Weight (kg)", value=75, minimum=30, maximum=200 ) gr.Markdown("๐Ÿ’ก *Enter your height and weight for accurate biomechanical calculations*") with gr.Tabs(): # YouTube URL Tab with gr.TabItem("๐ŸŽฅ YouTube Video"): gr.Markdown("๐Ÿ“บ *Paste a YouTube URL containing a video of someone jumping*") youtube_url = gr.Textbox( label="YouTube URL", placeholder="https://youtube.com/watch?v=..." ) youtube_btn = gr.Button("๐Ÿš€ Analyze YouTube Video", variant="primary") # File Upload Tab with gr.TabItem("๐Ÿ“ Upload Video"): gr.Markdown("๐Ÿ“ *Upload a video file showing someone performing a jump*") video_file = gr.File( label="Upload Video File", file_types=[".mp4", ".avi", ".mov", ".mkv", ".webm"] ) file_btn = gr.Button("๐Ÿš€ Analyze Uploaded Video", variant="primary") # AI Coaching Tab with gr.TabItem("๐Ÿค– AI Sports Coach"): gr.Markdown(""" ## ๐Ÿค– AI-Powered Sports Coaching Analysis Get personalized performance analysis for your favorite sports and targeted improvement suggestions from our AI sports coach powered by Google Gemini. **What you'll get:** - ๐Ÿ“Š **Percentile Rankings** across your favorite sports based on your performance - ๐ŸŽฏ **Combined Performance Improvement** recommendations (3-4 key pointers) - ๐Ÿ“ˆ **Sport-Specific Analysis** tailored to your athletic interests """) with gr.Row(): with gr.Column(): ai_gender = gr.Radio( choices=["Male", "Female"], label="Gender", value="Male" ) favorite_sports = gr.CheckboxGroup( choices=[ "Basketball", "Volleyball", "Track and Field", "Football", "Soccer", "Tennis", "Badminton", "Swimming", "Gymnastics", "Boxing", "Wrestling", "Baseball", "Hockey", "Rugby", "Cricket", "Martial Arts", "Rock Climbing", "Parkour", "Dancing", "CrossFit" ], label="Favorite Sports (Select 1-5)", value=["Basketball"] ) gr.Markdown("๐Ÿ’ก *Select your favorite sports to get percentile rankings showing how your jump performance compares to typical athletes in each sport*") # Check if API key is available in environment default_api_key = os.getenv("GEMINI_API_KEY", "") ai_gemini_key = gr.Textbox( label="Gemini API Key", placeholder="Enter your Google Gemini API key" if not default_api_key else "API key loaded from environment", type="password", value=default_api_key ) with gr.Row(): test_api_btn = gr.Button("๐Ÿงช Test API Key", size="sm") api_test_result = gr.Textbox( label="API Test Result", lines=3, interactive=False, visible=False ) gr.Markdown(""" ๐Ÿ’ก **Get your free API key**: [Google AI Studio](https://aistudio.google.com/app/apikey) ๐Ÿ“ฑ **Privacy**: Your API key is only used for this analysis and not stored. """) with gr.Column(): ai_youtube_url = gr.Textbox( label="YouTube URL (Optional)", placeholder="https://youtube.com/watch?v=..." ) ai_video_file = gr.File( label="Upload Video File (Optional)", file_types=[".mp4", ".avi", ".mov", ".mkv", ".webm"] ) gr.Markdown("*Provide either a YouTube URL or upload a video file*") ai_coaching_btn = gr.Button("๐Ÿค– Get AI Coaching Analysis", variant="primary", size="lg") # Video Generation Tab with gr.TabItem("๐ŸŽฌ Annotated Video"): gr.Markdown(""" ## ๐ŸŽฌ Generate Annotated Training Video Create a professional training video with visual overlays including: - **๐Ÿฆด Pose Tracking**: Real-time skeleton visualization - **๐Ÿ“ Performance Lines**: Average vs Professional jump heights - **โš ๏ธ Knee Strain Detection**: Red warnings for poor form - **๐Ÿ“Š Live Metrics**: Frame-by-frame jump analysis Perfect for coaches, athletes, and performance analysis! """) with gr.Row(): with gr.Column(): video_gender = gr.Radio( choices=["Male", "Female"], label="Gender (for performance references)", value="Male" ) gr.Markdown("*Used to set appropriate average/pro jump height lines*") with gr.Column(): gr.Markdown("### Video Input Options") video_youtube_url = gr.Textbox( label="YouTube URL (Option 1)", placeholder="https://youtube.com/watch?v=..." ) video_file_upload = gr.File( label="Upload Video File (Option 2)", file_types=[".mp4", ".avi", ".mov", ".mkv", ".webm"] ) gr.Markdown("*Provide either a YouTube URL or upload a video file*") with gr.Row(): video_youtube_btn = gr.Button("๐ŸŽฌ Generate from YouTube", variant="primary", size="lg") video_file_btn = gr.Button("๐ŸŽฌ Generate from Upload", variant="primary", size="lg") # Results section gr.Markdown("## ๐Ÿ“Š Analysis Results") with gr.Row(): with gr.Column(scale=2): results_text = gr.Markdown(label="Results") with gr.Column(scale=1): results_table = gr.Dataframe( label="Metrics Summary", headers=["Metric", "Value"], datatype=["str", "str"] ) status_message = gr.Textbox(label="Status", interactive=False) # Video requirements gr.Markdown(""" ## ๐Ÿ“ Video Requirements For best results, ensure your videos meet these criteria: - **Full body visible**: The person should be completely visible in the frame - **Clear movement**: Good lighting and minimal background clutter - **Vertical jumps**: Works best with straight vertical jumps - **Duration**: 3-30 seconds is optimal - **Quality**: Higher quality videos produce better results - **Public videos**: For YouTube, ensure the video is not private ## ๐Ÿ”ฌ How it Works 1. **Pose Detection**: Uses Google's MediaPipe to detect human pose landmarks 2. **Hip Tracking**: Tracks the midpoint between left and right hip joints 3. **Biomechanical Analysis**: Calculates comprehensive metrics based on hip trajectory: - **Jump Height**: Relative to your body size - **Flight Time**: Duration in the air - **Peak Power Output**: Maximum power generated during takeoff - **Rate of Force Development (RFD)**: Speed of force generation - **Ground Contact Time**: Efficiency in stretch-shortening cycle - **Impulse & Peak Force**: Force characteristics during takeoff - **Takeoff Phase Duration**: Time from crouch to launch """) # Event handlers youtube_btn.click( fn=analyze_jump_from_youtube, inputs=[youtube_url, user_height, user_weight], outputs=[results_text, results_table, status_message] ) file_btn.click( fn=analyze_jump_from_file, inputs=[video_file, user_height, user_weight], outputs=[results_text, results_table, status_message] ) ai_coaching_btn.click( fn=get_ai_coaching_recommendations, inputs=[ai_youtube_url, ai_video_file, user_height, user_weight, ai_gender, favorite_sports, ai_gemini_key], outputs=[results_text, results_table, status_message] ) # API key test handler def test_and_show_result(api_key): result = test_api_key(api_key) return gr.update(value=result, visible=True) test_api_btn.click( fn=test_and_show_result, inputs=[ai_gemini_key], outputs=[api_test_result] ) # Video generation event handlers video_youtube_btn.click( fn=generate_video_from_youtube, inputs=[video_youtube_url, user_height, user_weight, video_gender], outputs=[results_text, results_table, gr.File(label="Download Video")] ) video_file_btn.click( fn=generate_video_from_file, inputs=[video_file_upload, user_height, user_weight, video_gender], outputs=[results_text, results_table, gr.File(label="Download Video")] ) # Example section gr.Examples( examples=[ ["https://www.youtube.com/watch?v=dQw4w9WgXcQ", 175, 75], # This is just a placeholder ], inputs=[youtube_url, user_height, user_weight], label="๐Ÿ“š Example (Replace with actual jump video URLs)" ) return app if __name__ == "__main__": app = create_interface() app.launch(debug=True, share=True)