akarsh999's picture
Upload 11 files
3ce655a verified
Raw
History Blame Contribute Delete
19.2 kB
from flask import Flask, request, jsonify, send_file
from flask_cors import CORS
import pandas as pd
import os
import tempfile
import json
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
)
app = Flask(__name__)
CORS(app) # Enable CORS for all routes
@app.route('/health', methods=['GET'])
def health_check():
"""Health check endpoint"""
return jsonify({"status": "healthy", "message": "Athletic Performance API is running"})
@app.route('/analyze/youtube', methods=['POST'])
def analyze_youtube():
"""Analyze jump from YouTube URL"""
try:
data = request.get_json()
# Validate required fields
required_fields = ['youtube_url', 'user_height_cm', 'user_weight_kg']
for field in required_fields:
if field not in data:
return jsonify({"error": f"Missing required field: {field}"}), 400
youtube_url = data['youtube_url']
user_height_cm = data['user_height_cm']
user_weight_kg = data['user_weight_kg']
# Validate input values
if not youtube_url or not youtube_url.strip():
return jsonify({"error": "YouTube URL cannot be empty"}), 400
if user_height_cm <= 0 or user_weight_kg <= 0:
return jsonify({"error": "Height and weight must be positive values"}), 400
# Create a simple progress callback (no-op for API)
def progress_callback(prog, desc):
pass
# 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 jsonify({"error": result['error']}), 400
if result is None:
return jsonify({"error": "Could not analyze jump. Make sure the video shows a person clearly performing a vertical jump."}), 400
# Get performance insights
insights = get_performance_insights(result)
# Format response
response = {
"success": True,
"analysis": {
"jump_height_cm": result.get('jump_height_cm', 0),
"flight_time_s": result.get('flight_time_s', 0),
"normalized_rise": result.get('normalized_rise', 0),
"peak_power_watts": result.get('peak_power_watts', 0),
"peak_force_n": result.get('peak_force_n', 0),
"impulse_ns": result.get('impulse_ns', 0),
"rate_of_force_development": result.get('rate_of_force_development', 0),
"takeoff_phase_duration_s": result.get('takeoff_phase_duration_s', 0),
"ground_contact_time_s": result.get('ground_contact_time_s', 0),
"frames": result.get('frames', 0),
"fps": result.get('fps', 0),
"video": result.get('video', ''),
"user_weight_kg": result.get('user_weight_kg', user_weight_kg)
},
"insights": insights,
"relative_jump_height": (result.get('jump_height_cm', 0) / user_height_cm * 100) if user_height_cm > 0 else 0
}
return jsonify(response)
except Exception as e:
return jsonify({"error": f"Unexpected error: {str(e)}"}), 500
@app.route('/analyze/file', methods=['POST'])
def analyze_file():
"""Analyze jump from uploaded video file"""
try:
# Check if file is uploaded
if 'video_file' not in request.files:
return jsonify({"error": "No video file uploaded"}), 400
file = request.files['video_file']
if file.filename == '':
return jsonify({"error": "No file selected"}), 400
# Get form data
user_height_cm = request.form.get('user_height_cm', type=float)
user_weight_kg = request.form.get('user_weight_kg', type=float)
# Validate required fields
if not user_height_cm or not user_weight_kg:
return jsonify({"error": "Missing required fields: user_height_cm and user_weight_kg"}), 400
if user_height_cm <= 0 or user_weight_kg <= 0:
return jsonify({"error": "Height and weight must be positive values"}), 400
# Save uploaded file temporarily
with tempfile.NamedTemporaryFile(delete=False, suffix='.mp4') as temp_file:
file.save(temp_file.name)
temp_file_path = temp_file.name
try:
# Create a simple progress callback (no-op for API)
def progress_callback(prog, desc):
pass
# Call the core analysis function
result = analyze_video_file(temp_file_path, user_height_cm, user_weight_kg, progress_callback)
# Handle errors
if "error" in result:
return jsonify({"error": result['error']}), 400
if result is None:
return jsonify({"error": "Could not analyze jump. Make sure the video shows a person clearly performing a vertical jump."}), 400
# Get performance insights
insights = get_performance_insights(result)
# Format response
response = {
"success": True,
"analysis": {
"jump_height_cm": result.get('jump_height_cm', 0),
"flight_time_s": result.get('flight_time_s', 0),
"normalized_rise": result.get('normalized_rise', 0),
"peak_power_watts": result.get('peak_power_watts', 0),
"peak_force_n": result.get('peak_force_n', 0),
"impulse_ns": result.get('impulse_ns', 0),
"rate_of_force_development": result.get('rate_of_force_development', 0),
"takeoff_phase_duration_s": result.get('takeoff_phase_duration_s', 0),
"ground_contact_time_s": result.get('ground_contact_time_s', 0),
"frames": result.get('frames', 0),
"fps": result.get('fps', 0),
"video": result.get('video', ''),
"user_weight_kg": result.get('user_weight_kg', user_weight_kg)
},
"insights": insights,
"relative_jump_height": (result.get('jump_height_cm', 0) / user_height_cm * 100) if user_height_cm > 0 else 0
}
return jsonify(response)
finally:
# Clean up temporary file
if os.path.exists(temp_file_path):
os.unlink(temp_file_path)
except Exception as e:
return jsonify({"error": f"Unexpected error: {str(e)}"}), 500
@app.route('/ai-coaching', methods=['POST'])
def ai_coaching():
"""Get AI-powered sports coaching recommendations"""
try:
data = request.get_json()
# Validate required fields
required_fields = ['user_height_cm', 'user_weight_kg', 'gender', 'gemini_api_key', 'favorite_sports']
for field in required_fields:
if field not in data:
return jsonify({"error": f"Missing required field: {field}"}), 400
user_height_cm = data['user_height_cm']
user_weight_kg = data['user_weight_kg']
gender = data['gender']
gemini_api_key = data['gemini_api_key']
favorite_sports = data['favorite_sports']
# Validate inputs
if not gemini_api_key or not gemini_api_key.strip():
return jsonify({"error": "Gemini API key is required"}), 400
if not gender:
return jsonify({"error": "Gender is required"}), 400
if user_height_cm <= 0 or user_weight_kg <= 0:
return jsonify({"error": "Height and weight must be positive values"}), 400
# Validate favorite_sports
if not favorite_sports or not isinstance(favorite_sports, list):
return jsonify({"error": "favorite_sports must be a non-empty list"}), 400
if len(favorite_sports) > 5:
return jsonify({"error": "Maximum 5 favorite sports allowed"}), 400
# Clean and validate sport names
cleaned_sports = []
for sport in favorite_sports:
if isinstance(sport, str) and sport.strip():
cleaned_sports.append(sport.strip())
if not cleaned_sports:
return jsonify({"error": "Please provide valid sport names"}), 400
# Determine video source
youtube_url = data.get('youtube_url', '')
video_file_data = data.get('video_file_data', '') # Base64 encoded video data
if not youtube_url and not video_file_data:
return jsonify({"error": "Either YouTube URL or video file data is required"}), 400
# First, get the jump analysis
def progress_callback(prog, desc):
pass
if youtube_url:
result = analyze_youtube_video(youtube_url, user_height_cm, user_weight_kg, progress_callback)
else:
# Handle base64 video data
import base64
video_data = base64.b64decode(video_file_data)
with tempfile.NamedTemporaryFile(delete=False, suffix='.mp4') as temp_file:
temp_file.write(video_data)
temp_file_path = temp_file.name
try:
result = analyze_video_file(temp_file_path, user_height_cm, user_weight_kg, progress_callback)
finally:
if os.path.exists(temp_file_path):
os.unlink(temp_file_path)
# Handle analysis errors
if "error" in result:
return jsonify({"error": f"Video analysis failed: {result['error']}"}), 400
if result is None:
return jsonify({"error": "Could not analyze jump. Please ensure the video shows a clear vertical jump."}), 400
# 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=cleaned_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()
)
if "error" in ai_result:
return jsonify({"error": f"AI analysis failed: {ai_result['error']}"}), 400
# Format response
response = {
"success": True,
"performance_summary": {
"jump_height_cm": result.get('jump_height_cm', 0),
"relative_jump_height": (result.get('jump_height_cm', 0) / user_height_cm * 100) if user_height_cm > 0 else 0,
"flight_time_s": result.get('flight_time_s', 0),
"peak_power_watts": result.get('peak_power_watts', 0),
"gender": gender,
"height_cm": user_height_cm,
"weight_kg": user_weight_kg
},
"ai_coaching_analysis": ai_result['analysis']
}
return jsonify(response)
except Exception as e:
return jsonify({"error": f"Unexpected error: {str(e)}"}), 500
@app.route('/test-api-key', methods=['POST'])
def test_api_key():
"""Test Gemini API key connection"""
try:
data = request.get_json()
if 'api_key' not in data:
return jsonify({"error": "API key is required"}), 400
api_key = data['api_key']
if not api_key or not api_key.strip():
return jsonify({"error": "API key cannot be empty"}), 400
result = test_gemini_api_connection(api_key.strip())
response = {
"success": result["success"],
"status_code": result["status_code"],
"response_text": result["response_text"],
"error": result.get("error", None)
}
return jsonify(response)
except Exception as e:
return jsonify({"error": f"Unexpected error: {str(e)}"}), 500
@app.route('/generate-video/youtube', methods=['POST'])
def generate_video_youtube():
"""Generate annotated video from YouTube URL"""
try:
data = request.get_json()
# Validate required fields
required_fields = ['youtube_url', 'user_height_cm', 'user_weight_kg', 'gender']
for field in required_fields:
if field not in data:
return jsonify({"error": f"Missing required field: {field}"}), 400
youtube_url = data['youtube_url']
user_height_cm = data['user_height_cm']
user_weight_kg = data['user_weight_kg']
gender = data['gender']
# Validate inputs
if not youtube_url or not youtube_url.strip():
return jsonify({"error": "YouTube URL cannot be empty"}), 400
if not gender:
return jsonify({"error": "Gender is required"}), 400
if user_height_cm <= 0 or user_weight_kg <= 0:
return jsonify({"error": "Height and weight must be positive values"}), 400
# Create progress callback
def progress_callback(prog, desc):
pass
# 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 jsonify({"error": f"Video generation failed: {result['error']}"}), 400
if result is None:
return jsonify({"error": "Could not generate video. Please ensure the video shows a clear vertical jump."}), 400
# Check if video file exists
video_path = result.get("output_video_path", "")
if not video_path or not os.path.exists(video_path):
return jsonify({"error": "Generated video file not found"}), 500
# Return video file
return send_file(
video_path,
as_attachment=True,
download_name=f"annotated_jump_analysis_{user_height_cm}cm_{user_weight_kg}kg.mp4",
mimetype='video/mp4'
)
except Exception as e:
return jsonify({"error": f"Unexpected error: {str(e)}"}), 500
@app.route('/generate-video/file', methods=['POST'])
def generate_video_file():
"""Generate annotated video from uploaded file"""
try:
# Check if file is uploaded
if 'video_file' not in request.files:
return jsonify({"error": "No video file uploaded"}), 400
file = request.files['video_file']
if file.filename == '':
return jsonify({"error": "No file selected"}), 400
# Get form data
user_height_cm = request.form.get('user_height_cm', type=float)
user_weight_kg = request.form.get('user_weight_kg', type=float)
gender = request.form.get('gender')
# Validate required fields
if not user_height_cm or not user_weight_kg or not gender:
return jsonify({"error": "Missing required fields: user_height_cm, user_weight_kg, and gender"}), 400
if user_height_cm <= 0 or user_weight_kg <= 0:
return jsonify({"error": "Height and weight must be positive values"}), 400
# Save uploaded file temporarily
with tempfile.NamedTemporaryFile(delete=False, suffix='.mp4') as temp_file:
file.save(temp_file.name)
temp_file_path = temp_file.name
try:
# Create progress callback
def progress_callback(prog, desc):
pass
# Call the video generation function
result = generate_annotated_video_from_file(
temp_file_path, user_height_cm, user_weight_kg, gender, progress_callback
)
# Handle errors
if "error" in result:
return jsonify({"error": f"Video generation failed: {result['error']}"}), 400
if result is None:
return jsonify({"error": "Could not generate video. Please ensure the video shows a clear vertical jump."}), 400
# Check if video file exists
video_path = result.get("output_video_path", "")
if not video_path or not os.path.exists(video_path):
return jsonify({"error": "Generated video file not found"}), 500
# Return video file
return send_file(
video_path,
as_attachment=True,
download_name=f"annotated_jump_analysis_{user_height_cm}cm_{user_weight_kg}kg.mp4",
mimetype='video/mp4'
)
finally:
# Clean up temporary file
if os.path.exists(temp_file_path):
os.unlink(temp_file_path)
except Exception as e:
return jsonify({"error": f"Unexpected error: {str(e)}"}), 500
@app.route('/metrics', methods=['GET'])
def get_metrics():
"""Get available metrics and their descriptions"""
metrics_info = {
"jump_height_cm": "Vertical jump height in centimeters",
"flight_time_s": "Time spent in the air during the jump",
"normalized_rise": "Jump height as a percentage of body height",
"peak_power_watts": "Maximum power output during takeoff",
"peak_force_n": "Maximum force generated during takeoff",
"impulse_ns": "Total impulse (force × time) during takeoff",
"rate_of_force_development": "Speed of force generation (explosiveness)",
"takeoff_phase_duration_s": "Time from crouch position to launch",
"ground_contact_time_s": "Time spent in contact with ground during takeoff",
"frames": "Total number of video frames processed",
"fps": "Video frame rate (frames per second)"
}
return jsonify({
"success": True,
"metrics": metrics_info,
"description": "Available biomechanical metrics for jump analysis"
})
@app.errorhandler(404)
def not_found(error):
return jsonify({"error": "Endpoint not found"}), 404
@app.errorhandler(500)
def internal_error(error):
return jsonify({"error": "Internal server error"}), 500
if __name__ == '__main__':
# Get port from environment variable or use default
port = int(os.environ.get('PORT', 5000))
# Run the Flask app
app.run(host='0.0.0.0', port=port, debug=True)