Spaces:
Runtime error
Runtime error
Upload 10 files
Browse files- app.py +90 -209
- athletic_performance.py +0 -96
app.py
CHANGED
|
@@ -4,8 +4,7 @@ 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 |
-
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,7 +62,7 @@ def analyze_jump_from_youtube(youtube_url, user_height_cm, user_weight_kg, progr
|
|
| 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,11 +99,11 @@ def analyze_jump_from_file(video_file, user_height_cm, user_weight_kg, progress=
|
|
| 100 |
# Handle errors
|
| 101 |
if "error" in result:
|
| 102 |
return f"❌ {result['error']}", None, None
|
| 103 |
-
|
| 104 |
-
|
| 105 |
-
|
| 106 |
-
|
| 107 |
-
|
| 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,7 +141,7 @@ def analyze_jump_from_file(video_file, user_height_cm, user_weight_kg, progress=
|
|
| 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,139 +396,6 @@ Your annotated video is ready for download!
|
|
| 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,25 +411,25 @@ def create_interface():
|
|
| 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
|
| 554 |
-
- **
|
|
|
|
| 555 |
3. Provide a video (YouTube URL or file upload)
|
| 556 |
-
4. Get comprehensive results
|
| 557 |
""")
|
| 558 |
|
| 559 |
with gr.Row():
|
| 560 |
with gr.Column():
|
| 561 |
user_height = gr.Number(
|
| 562 |
-
|
| 563 |
-
|
| 564 |
-
|
| 565 |
-
|
| 566 |
-
|
| 567 |
with gr.Column():
|
| 568 |
user_weight = gr.Number(
|
| 569 |
label="Your Weight (kg)",
|
|
@@ -592,48 +458,34 @@ def create_interface():
|
|
| 592 |
)
|
| 593 |
file_btn = gr.Button("🚀 Analyze Uploaded Video", variant="primary")
|
| 594 |
|
| 595 |
-
#
|
| 596 |
-
with gr.TabItem("
|
| 597 |
gr.Markdown("""
|
| 598 |
-
##
|
| 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 |
-
|
| 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 |
-
|
| 614 |
-
- **
|
| 615 |
-
- **
|
| 616 |
-
-
|
| 617 |
""")
|
| 618 |
|
| 619 |
with gr.Row():
|
| 620 |
with gr.Column():
|
| 621 |
-
|
| 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 |
-
|
| 631 |
-
label="Gemini API Key
|
| 632 |
-
placeholder="Enter your Google Gemini API key
|
| 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,21 +499,60 @@ def create_interface():
|
|
| 647 |
gr.Markdown("""
|
| 648 |
💡 **Get your free API key**: [Google AI Studio](https://aistudio.google.com/app/apikey)
|
| 649 |
|
| 650 |
-
|
| 651 |
""")
|
| 652 |
|
| 653 |
with gr.Column():
|
| 654 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 655 |
label="YouTube URL (Option 1)",
|
| 656 |
placeholder="https://youtube.com/watch?v=..."
|
| 657 |
)
|
| 658 |
-
|
| 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 |
-
|
|
|
|
|
|
|
| 665 |
|
| 666 |
# Results section
|
| 667 |
gr.Markdown("## 📊 Analysis Results")
|
|
@@ -676,14 +567,6 @@ def create_interface():
|
|
| 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,25 +609,10 @@ def create_interface():
|
|
| 726 |
outputs=[results_text, results_table, status_message]
|
| 727 |
)
|
| 728 |
|
| 729 |
-
|
| 730 |
-
|
| 731 |
-
|
| 732 |
-
|
| 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,10 +622,23 @@ def create_interface():
|
|
| 754 |
|
| 755 |
test_api_btn.click(
|
| 756 |
fn=test_and_show_result,
|
| 757 |
-
inputs=[
|
| 758 |
outputs=[api_test_result]
|
| 759 |
)
|
| 760 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 761 |
# Example section
|
| 762 |
gr.Examples(
|
| 763 |
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 |
)
|
| 9 |
|
| 10 |
def analyze_jump_from_youtube(youtube_url, user_height_cm, user_weight_kg, progress=gr.Progress()):
|
|
|
|
| 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 |
# Handle errors
|
| 100 |
if "error" in result:
|
| 101 |
return f"❌ {result['error']}", None, None
|
| 102 |
+
|
| 103 |
+
if result is None:
|
| 104 |
+
return "⚠️ Could not analyze jump. Make sure the video shows a person clearly performing a vertical jump.", None, None
|
| 105 |
+
|
| 106 |
+
# Format results (same as YouTube function)
|
| 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 |
|
| 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 |
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 |
- **🎬 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 |
+
- **🤖 AI Sports Coach**: Personalized recommendations and sport suggestions
|
| 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, actionable insights, or downloadable training videos
|
| 423 |
""")
|
| 424 |
|
| 425 |
with gr.Row():
|
| 426 |
with gr.Column():
|
| 427 |
user_height = gr.Number(
|
| 428 |
+
label="Your Height (cm)",
|
| 429 |
+
value=175,
|
| 430 |
+
minimum=100,
|
| 431 |
+
maximum=250
|
| 432 |
+
)
|
| 433 |
with gr.Column():
|
| 434 |
user_weight = gr.Number(
|
| 435 |
label="Your Weight (kg)",
|
|
|
|
| 458 |
)
|
| 459 |
file_btn = gr.Button("🚀 Analyze Uploaded Video", variant="primary")
|
| 460 |
|
| 461 |
+
# AI Coaching Tab
|
| 462 |
+
with gr.TabItem("🤖 AI Sports Coach"):
|
| 463 |
gr.Markdown("""
|
| 464 |
+
## 🤖 AI-Powered Sports Coaching Analysis
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 465 |
|
| 466 |
+
Get personalized sports recommendations and jump technique improvement suggestions from our AI sports coach powered by Google Gemini.
|
|
|
|
|
|
|
|
|
|
| 467 |
|
| 468 |
+
**What you'll get:**
|
| 469 |
+
- 🏆 **Recommended Sports** (top 3 that match your athletic profile)
|
| 470 |
+
- 🎯 **Jump Technique Analysis** with specific improvement suggestions
|
| 471 |
+
- 📊 **Personalized Training Recommendations**
|
| 472 |
""")
|
| 473 |
|
| 474 |
with gr.Row():
|
| 475 |
with gr.Column():
|
| 476 |
+
ai_gender = gr.Radio(
|
| 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 |
+
ai_gemini_key = gr.Textbox(
|
| 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 |
gr.Markdown("""
|
| 500 |
💡 **Get your free API key**: [Google AI Studio](https://aistudio.google.com/app/apikey)
|
| 501 |
|
| 502 |
+
📱 **Privacy**: Your API key is only used for this analysis and not stored.
|
| 503 |
""")
|
| 504 |
|
| 505 |
with gr.Column():
|
| 506 |
+
ai_youtube_url = gr.Textbox(
|
| 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 |
+
video_file_upload = gr.File(
|
| 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 |
+
with gr.Row():
|
| 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 |
datatype=["str", "str"]
|
| 568 |
)
|
| 569 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 570 |
status_message = gr.Textbox(label="Status", interactive=False)
|
| 571 |
|
| 572 |
# Video requirements
|
|
|
|
| 609 |
outputs=[results_text, results_table, status_message]
|
| 610 |
)
|
| 611 |
|
| 612 |
+
ai_coaching_btn.click(
|
| 613 |
+
fn=get_ai_coaching_recommendations,
|
| 614 |
+
inputs=[ai_youtube_url, ai_video_file, user_height, user_weight, ai_gender, ai_gemini_key],
|
| 615 |
+
outputs=[results_text, results_table, status_message]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 616 |
)
|
| 617 |
|
| 618 |
# API key test handler
|
|
|
|
| 622 |
|
| 623 |
test_api_btn.click(
|
| 624 |
fn=test_and_show_result,
|
| 625 |
+
inputs=[ai_gemini_key],
|
| 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=[
|
athletic_performance.py
CHANGED
|
@@ -826,102 +826,6 @@ 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 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 |
|
|
|
|
| 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 |
|