Spaces:
Runtime error
Runtime error
| 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) | |