Spaces:
Runtime error
Runtime error
Upload 9 files
Browse files- app.py +209 -90
- athletic_performance.py +96 -0
app.py
CHANGED
|
@@ -4,7 +4,8 @@ import os
|
|
| 4 |
from athletic_performance import (
|
| 5 |
analyze_youtube_video, analyze_video_file, get_performance_insights,
|
| 6 |
get_ai_sports_coaching_analysis, test_gemini_api_connection,
|
| 7 |
-
generate_annotated_video_from_youtube, generate_annotated_video_from_file
|
|
|
|
| 8 |
)
|
| 9 |
|
| 10 |
def analyze_jump_from_youtube(youtube_url, user_height_cm, user_weight_kg, progress=gr.Progress()):
|
|
@@ -62,7 +63,7 @@ def analyze_jump_from_youtube(youtube_url, user_height_cm, user_weight_kg, progr
|
|
| 62 |
|
| 63 |
### 📈 Performance Insights
|
| 64 |
"""
|
| 65 |
-
|
| 66 |
# Add performance insights using the new function
|
| 67 |
insights = get_performance_insights(result)
|
| 68 |
for insight in insights:
|
|
@@ -99,11 +100,11 @@ def analyze_jump_from_file(video_file, user_height_cm, user_weight_kg, progress=
|
|
| 99 |
# Handle errors
|
| 100 |
if "error" in result:
|
| 101 |
return f"❌ {result['error']}", None, None
|
| 102 |
-
|
| 103 |
-
|
| 104 |
-
|
| 105 |
-
|
| 106 |
-
|
| 107 |
# Handle potential None values safely
|
| 108 |
jump_height = result.get('jump_height_cm', 0) or 0
|
| 109 |
flight_time = result.get('flight_time_s', 0) or 0
|
|
@@ -141,7 +142,7 @@ def analyze_jump_from_file(video_file, user_height_cm, user_weight_kg, progress=
|
|
| 141 |
|
| 142 |
### 📈 Performance Insights
|
| 143 |
"""
|
| 144 |
-
|
| 145 |
# Add performance insights using the new function
|
| 146 |
insights = get_performance_insights(result)
|
| 147 |
for insight in insights:
|
|
@@ -396,6 +397,139 @@ Your annotated video is ready for download!
|
|
| 396 |
return results_text, summary_df, video_path
|
| 397 |
|
| 398 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 399 |
# Create Gradio interface
|
| 400 |
def create_interface():
|
| 401 |
with gr.Blocks(title="🏃♂️ Athletic Ability Analysis") as app:
|
|
@@ -411,25 +545,25 @@ def create_interface():
|
|
| 411 |
- **🎬 Annotated Videos**: Generate training videos with pose tracking and performance overlays
|
| 412 |
- **⚠️ Technique Analysis**: Real-time knee strain detection and form corrections
|
| 413 |
- **🎯 Performance Insights**: Professional-grade analysis and training suggestions
|
|
|
|
| 414 |
|
| 415 |
## 📋 Instructions
|
| 416 |
1. Enter your height in centimeters and weight in kilograms
|
| 417 |
2. Choose your analysis type:
|
| 418 |
-
- **📊 Standard Analysis**: Get detailed biomechanical metrics
|
| 419 |
-
- **
|
| 420 |
-
- **🎬 Video Generation**: Create annotated training videos with visual overlays
|
| 421 |
3. Provide a video (YouTube URL or file upload)
|
| 422 |
-
4. Get comprehensive results
|
| 423 |
""")
|
| 424 |
|
| 425 |
with gr.Row():
|
| 426 |
with gr.Column():
|
| 427 |
user_height = gr.Number(
|
| 428 |
-
|
| 429 |
-
|
| 430 |
-
|
| 431 |
-
|
| 432 |
-
|
| 433 |
with gr.Column():
|
| 434 |
user_weight = gr.Number(
|
| 435 |
label="Your Weight (kg)",
|
|
@@ -458,34 +592,48 @@ def create_interface():
|
|
| 458 |
)
|
| 459 |
file_btn = gr.Button("🚀 Analyze Uploaded Video", variant="primary")
|
| 460 |
|
| 461 |
-
#
|
| 462 |
-
with gr.TabItem("
|
| 463 |
gr.Markdown("""
|
| 464 |
-
##
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 465 |
|
| 466 |
-
|
|
|
|
|
|
|
|
|
|
| 467 |
|
| 468 |
-
|
| 469 |
-
-
|
| 470 |
-
-
|
| 471 |
-
-
|
| 472 |
""")
|
| 473 |
|
| 474 |
with gr.Row():
|
| 475 |
with gr.Column():
|
| 476 |
-
|
| 477 |
choices=["Male", "Female"],
|
| 478 |
label="Gender",
|
| 479 |
value="Male"
|
| 480 |
)
|
|
|
|
|
|
|
| 481 |
# Check if API key is available in environment
|
| 482 |
default_api_key = os.getenv("GEMINI_API_KEY", "")
|
| 483 |
-
|
| 484 |
-
label="Gemini API Key",
|
| 485 |
-
placeholder="Enter your Google Gemini API key" if not default_api_key else "API key loaded from environment",
|
| 486 |
type="password",
|
| 487 |
value=default_api_key
|
| 488 |
)
|
|
|
|
| 489 |
with gr.Row():
|
| 490 |
test_api_btn = gr.Button("🧪 Test API Key", size="sm")
|
| 491 |
|
|
@@ -499,60 +647,21 @@ def create_interface():
|
|
| 499 |
gr.Markdown("""
|
| 500 |
💡 **Get your free API key**: [Google AI Studio](https://aistudio.google.com/app/apikey)
|
| 501 |
|
| 502 |
-
|
| 503 |
""")
|
| 504 |
|
| 505 |
with gr.Column():
|
| 506 |
-
|
| 507 |
-
label="YouTube URL (Optional)",
|
| 508 |
-
placeholder="https://youtube.com/watch?v=..."
|
| 509 |
-
)
|
| 510 |
-
ai_video_file = gr.File(
|
| 511 |
-
label="Upload Video File (Optional)",
|
| 512 |
-
file_types=[".mp4", ".avi", ".mov", ".mkv", ".webm"]
|
| 513 |
-
)
|
| 514 |
-
gr.Markdown("*Provide either a YouTube URL or upload a video file*")
|
| 515 |
-
|
| 516 |
-
ai_coaching_btn = gr.Button("🤖 Get AI Coaching Analysis", variant="primary", size="lg")
|
| 517 |
-
|
| 518 |
-
# Video Generation Tab
|
| 519 |
-
with gr.TabItem("🎬 Annotated Video"):
|
| 520 |
-
gr.Markdown("""
|
| 521 |
-
## 🎬 Generate Annotated Training Video
|
| 522 |
-
|
| 523 |
-
Create a professional training video with visual overlays including:
|
| 524 |
-
- **🦴 Pose Tracking**: Real-time skeleton visualization
|
| 525 |
-
- **📏 Performance Lines**: Average vs Professional jump heights
|
| 526 |
-
- **⚠️ Knee Strain Detection**: Red warnings for poor form
|
| 527 |
-
- **📊 Live Metrics**: Frame-by-frame jump analysis
|
| 528 |
-
|
| 529 |
-
Perfect for coaches, athletes, and performance analysis!
|
| 530 |
-
""")
|
| 531 |
-
|
| 532 |
-
with gr.Row():
|
| 533 |
-
with gr.Column():
|
| 534 |
-
video_gender = gr.Radio(
|
| 535 |
-
choices=["Male", "Female"],
|
| 536 |
-
label="Gender (for performance references)",
|
| 537 |
-
value="Male"
|
| 538 |
-
)
|
| 539 |
-
gr.Markdown("*Used to set appropriate average/pro jump height lines*")
|
| 540 |
-
|
| 541 |
-
with gr.Column():
|
| 542 |
-
gr.Markdown("### Video Input Options")
|
| 543 |
-
video_youtube_url = gr.Textbox(
|
| 544 |
label="YouTube URL (Option 1)",
|
| 545 |
placeholder="https://youtube.com/watch?v=..."
|
| 546 |
)
|
| 547 |
-
|
| 548 |
label="Upload Video File (Option 2)",
|
| 549 |
file_types=[".mp4", ".avi", ".mov", ".mkv", ".webm"]
|
| 550 |
)
|
| 551 |
gr.Markdown("*Provide either a YouTube URL or upload a video file*")
|
| 552 |
|
| 553 |
-
|
| 554 |
-
video_youtube_btn = gr.Button("🎬 Generate from YouTube", variant="primary", size="lg")
|
| 555 |
-
video_file_btn = gr.Button("🎬 Generate from Upload", variant="primary", size="lg")
|
| 556 |
|
| 557 |
# Results section
|
| 558 |
gr.Markdown("## 📊 Analysis Results")
|
|
@@ -567,6 +676,14 @@ def create_interface():
|
|
| 567 |
datatype=["str", "str"]
|
| 568 |
)
|
| 569 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 570 |
status_message = gr.Textbox(label="Status", interactive=False)
|
| 571 |
|
| 572 |
# Video requirements
|
|
@@ -609,10 +726,25 @@ def create_interface():
|
|
| 609 |
outputs=[results_text, results_table, status_message]
|
| 610 |
)
|
| 611 |
|
| 612 |
-
|
| 613 |
-
|
| 614 |
-
|
| 615 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 616 |
)
|
| 617 |
|
| 618 |
# API key test handler
|
|
@@ -622,23 +754,10 @@ def create_interface():
|
|
| 622 |
|
| 623 |
test_api_btn.click(
|
| 624 |
fn=test_and_show_result,
|
| 625 |
-
inputs=[
|
| 626 |
outputs=[api_test_result]
|
| 627 |
)
|
| 628 |
|
| 629 |
-
# Video generation event handlers
|
| 630 |
-
video_youtube_btn.click(
|
| 631 |
-
fn=generate_video_from_youtube,
|
| 632 |
-
inputs=[video_youtube_url, user_height, user_weight, video_gender],
|
| 633 |
-
outputs=[results_text, results_table, gr.File(label="Download Video")]
|
| 634 |
-
)
|
| 635 |
-
|
| 636 |
-
video_file_btn.click(
|
| 637 |
-
fn=generate_video_from_file,
|
| 638 |
-
inputs=[video_file_upload, user_height, user_weight, video_gender],
|
| 639 |
-
outputs=[results_text, results_table, gr.File(label="Download Video")]
|
| 640 |
-
)
|
| 641 |
-
|
| 642 |
# Example section
|
| 643 |
gr.Examples(
|
| 644 |
examples=[
|
|
|
|
| 4 |
from athletic_performance import (
|
| 5 |
analyze_youtube_video, analyze_video_file, get_performance_insights,
|
| 6 |
get_ai_sports_coaching_analysis, test_gemini_api_connection,
|
| 7 |
+
generate_annotated_video_from_youtube, generate_annotated_video_from_file,
|
| 8 |
+
generate_complete_analysis_with_video
|
| 9 |
)
|
| 10 |
|
| 11 |
def analyze_jump_from_youtube(youtube_url, user_height_cm, user_weight_kg, progress=gr.Progress()):
|
|
|
|
| 63 |
|
| 64 |
### 📈 Performance Insights
|
| 65 |
"""
|
| 66 |
+
|
| 67 |
# Add performance insights using the new function
|
| 68 |
insights = get_performance_insights(result)
|
| 69 |
for insight in insights:
|
|
|
|
| 100 |
# Handle errors
|
| 101 |
if "error" in result:
|
| 102 |
return f"❌ {result['error']}", None, None
|
| 103 |
+
|
| 104 |
+
if result is None:
|
| 105 |
+
return "⚠️ Could not analyze jump. Make sure the video shows a person clearly performing a vertical jump.", None, None
|
| 106 |
+
|
| 107 |
+
# Format results (same as YouTube function)
|
| 108 |
# Handle potential None values safely
|
| 109 |
jump_height = result.get('jump_height_cm', 0) or 0
|
| 110 |
flight_time = result.get('flight_time_s', 0) or 0
|
|
|
|
| 142 |
|
| 143 |
### 📈 Performance Insights
|
| 144 |
"""
|
| 145 |
+
|
| 146 |
# Add performance insights using the new function
|
| 147 |
insights = get_performance_insights(result)
|
| 148 |
for insight in insights:
|
|
|
|
| 397 |
return results_text, summary_df, video_path
|
| 398 |
|
| 399 |
|
| 400 |
+
def complete_analysis_with_video_stream(youtube_url, video_file, user_height_cm, user_weight_kg, gender, gemini_api_key, progress=gr.Progress()):
|
| 401 |
+
"""Complete analysis with AI coaching and video streaming."""
|
| 402 |
+
|
| 403 |
+
# Validate inputs
|
| 404 |
+
if not user_height_cm or user_height_cm <= 0:
|
| 405 |
+
return "❌ Please provide a valid height", None, None, None
|
| 406 |
+
|
| 407 |
+
if not user_weight_kg or user_weight_kg <= 0:
|
| 408 |
+
return "❌ Please provide a valid weight", None, None, None
|
| 409 |
+
|
| 410 |
+
if not gender:
|
| 411 |
+
return "❌ Please select your gender", None, None, None
|
| 412 |
+
|
| 413 |
+
# Determine video source and path
|
| 414 |
+
video_source = None
|
| 415 |
+
video_path_or_url = None
|
| 416 |
+
|
| 417 |
+
if youtube_url and youtube_url.strip():
|
| 418 |
+
video_source = "youtube"
|
| 419 |
+
video_path_or_url = youtube_url.strip()
|
| 420 |
+
progress(0.05, desc="Processing YouTube video...")
|
| 421 |
+
elif video_file:
|
| 422 |
+
video_source = "file"
|
| 423 |
+
video_path_or_url = video_file.name
|
| 424 |
+
progress(0.05, desc="Processing uploaded video...")
|
| 425 |
+
else:
|
| 426 |
+
return "❌ Please provide either a YouTube URL or upload a video file", None, None, None
|
| 427 |
+
|
| 428 |
+
try:
|
| 429 |
+
# Create progress callback
|
| 430 |
+
def progress_callback(prog, desc):
|
| 431 |
+
progress(prog, desc=desc)
|
| 432 |
+
|
| 433 |
+
# Run complete analysis
|
| 434 |
+
result = generate_complete_analysis_with_video(
|
| 435 |
+
video_source=video_source,
|
| 436 |
+
video_path_or_url=video_path_or_url,
|
| 437 |
+
user_height_cm=user_height_cm,
|
| 438 |
+
user_weight_kg=user_weight_kg,
|
| 439 |
+
gender=gender,
|
| 440 |
+
api_key=gemini_api_key.strip() if gemini_api_key else None,
|
| 441 |
+
progress_callback=progress_callback
|
| 442 |
+
)
|
| 443 |
+
|
| 444 |
+
# Handle errors
|
| 445 |
+
if "error" in result:
|
| 446 |
+
return f"❌ Analysis failed: {result['error']}", None, None, None
|
| 447 |
+
|
| 448 |
+
if not result.get("success"):
|
| 449 |
+
return "❌ Analysis failed. Please ensure the video shows a clear vertical jump.", None, None, None
|
| 450 |
+
|
| 451 |
+
# Extract results
|
| 452 |
+
video_path = result.get("video_path")
|
| 453 |
+
jump_metrics = result.get("jump_metrics", {})
|
| 454 |
+
jump_references = result.get("jump_references", {})
|
| 455 |
+
ai_coaching = result.get("ai_coaching")
|
| 456 |
+
|
| 457 |
+
# Handle potential None values safely
|
| 458 |
+
jump_height = jump_metrics.get('jump_height_cm', 0) or 0
|
| 459 |
+
flight_time = jump_metrics.get('flight_time_s', 0) or 0
|
| 460 |
+
peak_power = jump_metrics.get('peak_power_watts', 0) or 0
|
| 461 |
+
rfd = jump_metrics.get('rate_of_force_development', 0) or 0
|
| 462 |
+
|
| 463 |
+
# Format comprehensive results
|
| 464 |
+
results_text = f"""
|
| 465 |
+
# 🎬 Complete Athletic Performance Analysis
|
| 466 |
+
|
| 467 |
+
## 📊 Performance Metrics
|
| 468 |
+
- **Jump Height**: {jump_height:.2f} cm
|
| 469 |
+
- **Relative Jump**: {(jump_height/user_height_cm*100):.1f}% of body height
|
| 470 |
+
- **Flight Time**: {flight_time:.3f} seconds
|
| 471 |
+
- **Peak Power**: {peak_power:.0f} watts
|
| 472 |
+
- **Rate of Force Development**: {rfd:.2f}
|
| 473 |
+
|
| 474 |
+
## 📏 Performance Comparison
|
| 475 |
+
- **Your Performance**: {jump_height:.1f} cm
|
| 476 |
+
- **Average for {gender}**: {jump_references.get('average', 0):.1f} cm
|
| 477 |
+
- **Professional Level**: {jump_references.get('professional', 0):.1f} cm
|
| 478 |
+
|
| 479 |
+
## 🎥 Video Analysis Features
|
| 480 |
+
- ✅ **Real-time Pose Tracking**: Skeleton overlay throughout jump
|
| 481 |
+
- ✅ **Performance Reference Lines**: Average vs Professional benchmarks
|
| 482 |
+
- ✅ **Knee Strain Detection**: Automatic form analysis with warnings
|
| 483 |
+
- ✅ **Live Metrics Display**: Frame-by-frame jump height tracking
|
| 484 |
+
|
| 485 |
+
"""
|
| 486 |
+
|
| 487 |
+
# Add AI coaching analysis if available
|
| 488 |
+
if ai_coaching and ai_coaching.get("success"):
|
| 489 |
+
results_text += f"""
|
| 490 |
+
## 🤖 AI Sports Coach Analysis
|
| 491 |
+
|
| 492 |
+
{ai_coaching.get('analysis', 'AI analysis not available')}
|
| 493 |
+
|
| 494 |
+
---
|
| 495 |
+
*Analysis powered by Google Gemini AI*
|
| 496 |
+
"""
|
| 497 |
+
elif gemini_api_key:
|
| 498 |
+
results_text += """
|
| 499 |
+
## 🤖 AI Sports Coach Analysis
|
| 500 |
+
|
| 501 |
+
❌ AI coaching analysis failed. Please check your API key and try again.
|
| 502 |
+
"""
|
| 503 |
+
else:
|
| 504 |
+
results_text += """
|
| 505 |
+
## 🤖 AI Sports Coach Analysis
|
| 506 |
+
|
| 507 |
+
💡 **Provide a Gemini API key to get personalized sports recommendations and technique improvements!**
|
| 508 |
+
|
| 509 |
+
Get your free API key at: [Google AI Studio](https://aistudio.google.com/app/apikey)
|
| 510 |
+
"""
|
| 511 |
+
|
| 512 |
+
# Create comprehensive summary dataframe
|
| 513 |
+
summary_data = [
|
| 514 |
+
["Jump Height", f"{jump_height:.2f} cm"],
|
| 515 |
+
["Flight Time", f"{flight_time:.3f} seconds"],
|
| 516 |
+
["Peak Power", f"{peak_power:.0f} watts"],
|
| 517 |
+
["Rate of Force Development", f"{rfd:.2f}"],
|
| 518 |
+
["Performance vs Average", f"{((jump_height/jump_references.get('average', 1))*100):.0f}%"],
|
| 519 |
+
["Performance vs Pro", f"{((jump_height/jump_references.get('professional', 1))*100):.0f}%"],
|
| 520 |
+
["Video Features", "Pose + References + Strain Detection"],
|
| 521 |
+
["AI Coaching", "✅ Included" if ai_coaching and ai_coaching.get("success") else "❌ Not Available"],
|
| 522 |
+
]
|
| 523 |
+
|
| 524 |
+
summary_df = pd.DataFrame(summary_data, columns=["Metric", "Value"])
|
| 525 |
+
|
| 526 |
+
# Return results with video for streaming
|
| 527 |
+
return results_text, summary_df, video_path, "✅ Complete analysis ready!"
|
| 528 |
+
|
| 529 |
+
except Exception as e:
|
| 530 |
+
return f"❌ Unexpected error: {str(e)}", None, None, None
|
| 531 |
+
|
| 532 |
+
|
| 533 |
# Create Gradio interface
|
| 534 |
def create_interface():
|
| 535 |
with gr.Blocks(title="🏃♂️ Athletic Ability Analysis") as app:
|
|
|
|
| 545 |
- **🎬 Annotated Videos**: Generate training videos with pose tracking and performance overlays
|
| 546 |
- **⚠️ Technique Analysis**: Real-time knee strain detection and form corrections
|
| 547 |
- **🎯 Performance Insights**: Professional-grade analysis and training suggestions
|
| 548 |
+
- **📺 Video Streaming**: Watch your annotated analysis video directly in the browser
|
| 549 |
|
| 550 |
## 📋 Instructions
|
| 551 |
1. Enter your height in centimeters and weight in kilograms
|
| 552 |
2. Choose your analysis type:
|
| 553 |
+
- **📊 Standard Analysis**: Get detailed biomechanical metrics only
|
| 554 |
+
- **🎬 Complete Analysis**: Get annotated video + AI coaching + streaming (Recommended!)
|
|
|
|
| 555 |
3. Provide a video (YouTube URL or file upload)
|
| 556 |
+
4. Get comprehensive results with video streaming and AI coaching insights
|
| 557 |
""")
|
| 558 |
|
| 559 |
with gr.Row():
|
| 560 |
with gr.Column():
|
| 561 |
user_height = gr.Number(
|
| 562 |
+
label="Your Height (cm)",
|
| 563 |
+
value=175,
|
| 564 |
+
minimum=100,
|
| 565 |
+
maximum=250
|
| 566 |
+
)
|
| 567 |
with gr.Column():
|
| 568 |
user_weight = gr.Number(
|
| 569 |
label="Your Weight (kg)",
|
|
|
|
| 592 |
)
|
| 593 |
file_btn = gr.Button("🚀 Analyze Uploaded Video", variant="primary")
|
| 594 |
|
| 595 |
+
# Complete Analysis Tab
|
| 596 |
+
with gr.TabItem("🎬 Complete Analysis + AI Coach"):
|
| 597 |
gr.Markdown("""
|
| 598 |
+
## 🎬 Complete Athletic Performance Analysis
|
| 599 |
+
|
| 600 |
+
Get the ultimate training analysis combining:
|
| 601 |
+
|
| 602 |
+
### 🎥 Annotated Training Video
|
| 603 |
+
- **🦴 Real-time Pose Tracking**: Skeleton overlay throughout jump
|
| 604 |
+
- **📏 Performance Reference Lines**: Average vs Professional benchmarks
|
| 605 |
+
- **⚠�� Knee Strain Detection**: Automatic form analysis with red warnings
|
| 606 |
+
- **📊 Live Metrics Display**: Frame-by-frame jump height tracking
|
| 607 |
|
| 608 |
+
### 🤖 AI Sports Coach Analysis
|
| 609 |
+
- **🏆 Sport Recommendations**: Top 3 sports matching your athletic profile
|
| 610 |
+
- **🎯 Technique Improvements**: Specific jump form corrections
|
| 611 |
+
- **📈 Training Insights**: Personalized performance enhancement tips
|
| 612 |
|
| 613 |
+
### 📺 Video Streaming
|
| 614 |
+
- **Watch directly in browser**: No downloads required
|
| 615 |
+
- **Professional annotations**: Training-ready video output
|
| 616 |
+
- **Shareable results**: Perfect for coaches and athletes
|
| 617 |
""")
|
| 618 |
|
| 619 |
with gr.Row():
|
| 620 |
with gr.Column():
|
| 621 |
+
complete_gender = gr.Radio(
|
| 622 |
choices=["Male", "Female"],
|
| 623 |
label="Gender",
|
| 624 |
value="Male"
|
| 625 |
)
|
| 626 |
+
gr.Markdown("*Used for performance references and AI analysis*")
|
| 627 |
+
|
| 628 |
# Check if API key is available in environment
|
| 629 |
default_api_key = os.getenv("GEMINI_API_KEY", "")
|
| 630 |
+
complete_gemini_key = gr.Textbox(
|
| 631 |
+
label="Gemini API Key (Optional for AI Coaching)",
|
| 632 |
+
placeholder="Enter your Google Gemini API key for AI coaching" if not default_api_key else "API key loaded from environment",
|
| 633 |
type="password",
|
| 634 |
value=default_api_key
|
| 635 |
)
|
| 636 |
+
|
| 637 |
with gr.Row():
|
| 638 |
test_api_btn = gr.Button("🧪 Test API Key", size="sm")
|
| 639 |
|
|
|
|
| 647 |
gr.Markdown("""
|
| 648 |
💡 **Get your free API key**: [Google AI Studio](https://aistudio.google.com/app/apikey)
|
| 649 |
|
| 650 |
+
⚠️ **Note**: Video generation works without API key, but AI coaching requires one
|
| 651 |
""")
|
| 652 |
|
| 653 |
with gr.Column():
|
| 654 |
+
complete_youtube_url = gr.Textbox(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 655 |
label="YouTube URL (Option 1)",
|
| 656 |
placeholder="https://youtube.com/watch?v=..."
|
| 657 |
)
|
| 658 |
+
complete_video_file = gr.File(
|
| 659 |
label="Upload Video File (Option 2)",
|
| 660 |
file_types=[".mp4", ".avi", ".mov", ".mkv", ".webm"]
|
| 661 |
)
|
| 662 |
gr.Markdown("*Provide either a YouTube URL or upload a video file*")
|
| 663 |
|
| 664 |
+
complete_analysis_btn = gr.Button("🚀 Get Complete Analysis + Video", variant="primary", size="lg")
|
|
|
|
|
|
|
| 665 |
|
| 666 |
# Results section
|
| 667 |
gr.Markdown("## 📊 Analysis Results")
|
|
|
|
| 676 |
datatype=["str", "str"]
|
| 677 |
)
|
| 678 |
|
| 679 |
+
# Video streaming section
|
| 680 |
+
with gr.Row():
|
| 681 |
+
with gr.Column():
|
| 682 |
+
analysis_video = gr.Video(
|
| 683 |
+
label="📺 Annotated Training Video",
|
| 684 |
+
visible=False
|
| 685 |
+
)
|
| 686 |
+
|
| 687 |
status_message = gr.Textbox(label="Status", interactive=False)
|
| 688 |
|
| 689 |
# Video requirements
|
|
|
|
| 726 |
outputs=[results_text, results_table, status_message]
|
| 727 |
)
|
| 728 |
|
| 729 |
+
# Complete analysis with video streaming
|
| 730 |
+
def complete_analysis_with_video_display(youtube_url, video_file, user_height_cm, user_weight_kg, gender, gemini_api_key, progress=gr.Progress()):
|
| 731 |
+
# Get the complete analysis
|
| 732 |
+
results_text_output, summary_df, video_path, status = complete_analysis_with_video_stream(
|
| 733 |
+
youtube_url, video_file, user_height_cm, user_weight_kg, gender, gemini_api_key, progress
|
| 734 |
+
)
|
| 735 |
+
|
| 736 |
+
# Update video component visibility and content
|
| 737 |
+
if video_path and os.path.exists(video_path):
|
| 738 |
+
video_update = gr.update(value=video_path, visible=True)
|
| 739 |
+
else:
|
| 740 |
+
video_update = gr.update(visible=False)
|
| 741 |
+
|
| 742 |
+
return results_text_output, summary_df, video_update, status
|
| 743 |
+
|
| 744 |
+
complete_analysis_btn.click(
|
| 745 |
+
fn=complete_analysis_with_video_display,
|
| 746 |
+
inputs=[complete_youtube_url, complete_video_file, user_height, user_weight, complete_gender, complete_gemini_key],
|
| 747 |
+
outputs=[results_text, results_table, analysis_video, status_message]
|
| 748 |
)
|
| 749 |
|
| 750 |
# API key test handler
|
|
|
|
| 754 |
|
| 755 |
test_api_btn.click(
|
| 756 |
fn=test_and_show_result,
|
| 757 |
+
inputs=[complete_gemini_key],
|
| 758 |
outputs=[api_test_result]
|
| 759 |
)
|
| 760 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 761 |
# Example section
|
| 762 |
gr.Examples(
|
| 763 |
examples=[
|
athletic_performance.py
CHANGED
|
@@ -826,6 +826,102 @@ def generate_annotated_video_from_file(video_file_path, user_height_cm, user_wei
|
|
| 826 |
return {"error": f"Error during video generation: {str(e)}"}
|
| 827 |
|
| 828 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 829 |
def get_performance_insights(result_dict):
|
| 830 |
"""Generate performance insights based on comprehensive jump metrics.
|
| 831 |
|
|
|
|
| 826 |
return {"error": f"Error during video generation: {str(e)}"}
|
| 827 |
|
| 828 |
|
| 829 |
+
def generate_complete_analysis_with_video(video_source, video_path_or_url, user_height_cm, user_weight_kg, gender, api_key=None, progress_callback=None):
|
| 830 |
+
"""
|
| 831 |
+
Complete analysis combining annotated video generation + AI sports coaching.
|
| 832 |
+
|
| 833 |
+
Args:
|
| 834 |
+
video_source (str): "youtube" or "file"
|
| 835 |
+
video_path_or_url (str): YouTube URL or file path
|
| 836 |
+
user_height_cm (float): User's height in centimeters
|
| 837 |
+
user_weight_kg (float): User's weight in kilograms
|
| 838 |
+
gender (str): "Male" or "Female"
|
| 839 |
+
api_key (str, optional): Gemini API key for AI coaching
|
| 840 |
+
progress_callback (callable, optional): Progress tracking function
|
| 841 |
+
|
| 842 |
+
Returns:
|
| 843 |
+
dict: Complete analysis with video path, AI coaching, and metrics
|
| 844 |
+
"""
|
| 845 |
+
|
| 846 |
+
try:
|
| 847 |
+
# Phase 1: Generate annotated video (50% of progress)
|
| 848 |
+
if progress_callback:
|
| 849 |
+
progress_callback(0.1, "Starting comprehensive analysis...")
|
| 850 |
+
|
| 851 |
+
def video_progress(prog, desc):
|
| 852 |
+
if progress_callback:
|
| 853 |
+
progress_callback(0.1 + (prog * 0.4), f"Video: {desc}")
|
| 854 |
+
|
| 855 |
+
# Generate annotated video
|
| 856 |
+
if video_source == "youtube":
|
| 857 |
+
video_result = generate_annotated_video_from_youtube(
|
| 858 |
+
video_path_or_url, user_height_cm, user_weight_kg, gender, video_progress
|
| 859 |
+
)
|
| 860 |
+
else:
|
| 861 |
+
video_result = generate_annotated_video_from_file(
|
| 862 |
+
video_path_or_url, user_height_cm, user_weight_kg, gender, video_progress
|
| 863 |
+
)
|
| 864 |
+
|
| 865 |
+
if "error" in video_result:
|
| 866 |
+
return video_result
|
| 867 |
+
|
| 868 |
+
# Phase 2: AI Sports Coaching Analysis (30% of progress)
|
| 869 |
+
if progress_callback:
|
| 870 |
+
progress_callback(0.5, "Generating AI sports coaching analysis...")
|
| 871 |
+
|
| 872 |
+
ai_result = None
|
| 873 |
+
if api_key:
|
| 874 |
+
jump_metrics = video_result.get("jump_metrics", {})
|
| 875 |
+
|
| 876 |
+
def ai_progress(prog, desc):
|
| 877 |
+
if progress_callback:
|
| 878 |
+
progress_callback(0.5 + (prog * 0.3), f"AI: {desc}")
|
| 879 |
+
|
| 880 |
+
ai_result = get_ai_sports_coaching_analysis(
|
| 881 |
+
jump_height_cm=jump_metrics.get('jump_height_cm', 0),
|
| 882 |
+
user_height_cm=user_height_cm,
|
| 883 |
+
gender=gender,
|
| 884 |
+
peak_power_watts=jump_metrics.get('peak_power_watts'),
|
| 885 |
+
flight_time_s=jump_metrics.get('flight_time_s'),
|
| 886 |
+
rfd=jump_metrics.get('rate_of_force_development'),
|
| 887 |
+
api_key=api_key
|
| 888 |
+
)
|
| 889 |
+
|
| 890 |
+
if progress_callback:
|
| 891 |
+
progress_callback(0.8, "AI analysis complete!")
|
| 892 |
+
|
| 893 |
+
# Phase 3: Combine results (20% of progress)
|
| 894 |
+
if progress_callback:
|
| 895 |
+
progress_callback(0.9, "Finalizing comprehensive analysis...")
|
| 896 |
+
|
| 897 |
+
jump_metrics = video_result.get("jump_metrics", {})
|
| 898 |
+
jump_references = video_result.get("jump_references", {})
|
| 899 |
+
|
| 900 |
+
# Create comprehensive result
|
| 901 |
+
result = {
|
| 902 |
+
"success": True,
|
| 903 |
+
"video_path": video_result.get("output_video_path"),
|
| 904 |
+
"jump_metrics": jump_metrics,
|
| 905 |
+
"jump_references": jump_references,
|
| 906 |
+
"ai_coaching": ai_result if ai_result and "error" not in ai_result else None,
|
| 907 |
+
"video_features": {
|
| 908 |
+
"pose_tracking": True,
|
| 909 |
+
"reference_lines": True,
|
| 910 |
+
"knee_strain_detection": True,
|
| 911 |
+
"live_metrics": True
|
| 912 |
+
},
|
| 913 |
+
"analysis_type": "complete_with_video"
|
| 914 |
+
}
|
| 915 |
+
|
| 916 |
+
if progress_callback:
|
| 917 |
+
progress_callback(1.0, "Complete analysis ready!")
|
| 918 |
+
|
| 919 |
+
return result
|
| 920 |
+
|
| 921 |
+
except Exception as e:
|
| 922 |
+
return {"error": f"Error during complete analysis: {str(e)}"}
|
| 923 |
+
|
| 924 |
+
|
| 925 |
def get_performance_insights(result_dict):
|
| 926 |
"""Generate performance insights based on comprehensive jump metrics.
|
| 927 |
|