Spaces:
Sleeping
Sleeping
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Bodybuilding Pose Analyzer</title> | |
| <link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/tailwind.min.css" rel="stylesheet"> | |
| </head> | |
| <body class="bg-gray-100 min-h-screen"> | |
| <div class="container mx-auto px-4 py-8"> | |
| <h1 class="text-4xl font-bold text-center mb-8">Bodybuilding Pose Analyzer</h1> | |
| <div class="max-w-2xl mx-auto bg-white rounded-lg shadow-lg p-6"> | |
| <div class="mb-6"> | |
| <h2 class="text-2xl font-semibold mb-4">Upload Video</h2> | |
| <form id="uploadForm" class="space-y-4"> | |
| <div class="border-2 border-dashed border-gray-300 rounded-lg p-6 text-center"> | |
| <input type="file" id="videoInput" accept="video/*" class="hidden"> | |
| <label for="videoInput" class="cursor-pointer"> | |
| <div class="text-gray-600"> | |
| <svg class="mx-auto h-12 w-12" stroke="currentColor" fill="none" viewBox="0 0 48 48"> | |
| <path d="M28 8H12a4 4 0 00-4 4v20m32-12v8m0 0v8a4 4 0 01-4 4H12a4 4 0 01-4-4v-4m32-4l-3.172-3.172a4 4 0 00-5.656 0L28 28M8 32l9.172-9.172a4 4 0 015.656 0L28 28m0 0l4 4m4-24h8m-4-4v8m-12 4h.02" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" /> | |
| </svg> | |
| <p class="mt-1">Click to upload a video</p> | |
| <p id="fileName" class="text-sm text-gray-500 mt-1"></p> | |
| </div> | |
| </label> | |
| </div> | |
| <div> | |
| <label class="block text-sm font-medium text-gray-700">Choose Model:</label> | |
| <div class="mt-1 flex rounded-md shadow-sm"> | |
| <div class="relative flex items-stretch flex-grow focus-within:z-10"> | |
| <label class="inline-flex items-center"> | |
| <input type="radio" class="form-radio" name="model_choice" value="movenet" checked> | |
| <span class="ml-2">Gladiator BB</span> | |
| </label> | |
| <label class="inline-flex items-center ml-6"> | |
| <input type="radio" class="form-radio" name="model_choice" value="Gladiator SupaDot"> | |
| <span class="ml-2">Gladiator SupaDot</span> | |
| </label> | |
| </div> | |
| </div> | |
| </div> | |
| <div id="gladiatorBBOptions" class="space-y-4"> | |
| <div> | |
| <label class="block text-sm font-medium text-gray-700">Gladiator BB Variant:</label> | |
| <div class="mt-1 flex rounded-md shadow-sm"> | |
| <div class="relative flex items-stretch flex-grow focus-within:z-10"> | |
| <label class="inline-flex items-center"> | |
| <input type="radio" class="form-radio" name="movenet_variant" value="lightning" checked> | |
| <span class="ml-2">Lightning (Faster, Less Accurate)</span> | |
| </label> | |
| <label class="inline-flex items-center ml-6"> | |
| <input type="radio" class="form-radio" name="movenet_variant" value="thunder"> | |
| <span class="ml-2">Thunder (Slower, More Accurate)</span> | |
| </label> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <button type="submit" class="w-full bg-blue-500 text-white py-2 px-4 rounded-lg hover:bg-blue-600 transition duration-200"> | |
| Process Video | |
| </button> | |
| </form> | |
| </div> | |
| <div id="result" class="hidden"> | |
| <h2 class="text-2xl font-semibold mb-4">Results</h2> | |
| <div class="aspect-w-16 aspect-h-9"> | |
| <video id="outputVideo" controls class="w-full rounded-lg"></video> | |
| </div> | |
| </div> | |
| <div id="loading" class="hidden"> | |
| <div class="flex items-center justify-center"> | |
| <div class="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-500"></div> | |
| </div> | |
| <p class="text-center mt-4">Processing video...</p> | |
| </div> | |
| </div> | |
| </div> | |
| <script> | |
| document.getElementById('videoInput').addEventListener('change', function() { | |
| const fileName = this.files[0] ? this.files[0].name : 'No file selected'; | |
| document.getElementById('fileName').textContent = fileName; | |
| }); | |
| document.querySelectorAll('input[name="model_choice"]').forEach(radio => { | |
| radio.addEventListener('change', function() { | |
| const gladiatorBBOptions = document.getElementById('gladiatorBBOptions'); | |
| if (this.value === 'movenet') { | |
| gladiatorBBOptions.classList.remove('hidden'); | |
| } else { | |
| gladiatorBBOptions.classList.add('hidden'); | |
| } | |
| }); | |
| }); | |
| // Trigger change event on page load for the initially checked model_choice | |
| document.querySelector('input[name="model_choice"]:checked').dispatchEvent(new Event('change')); | |
| document.getElementById('uploadForm').addEventListener('submit', async (e) => { | |
| e.preventDefault(); | |
| const fileInput = document.getElementById('videoInput'); | |
| const file = fileInput.files[0]; | |
| if (!file) { | |
| alert('Please select a video file'); | |
| return; | |
| } | |
| const formData = new FormData(); | |
| formData.append('video', file); | |
| const modelChoice = document.querySelector('input[name="model_choice"]:checked').value; | |
| formData.append('model_choice', modelChoice); | |
| if (modelChoice === 'movenet') { | |
| const movenetVariant = document.querySelector('input[name="movenet_variant"]:checked').value; | |
| formData.append('movenet_variant', movenetVariant); | |
| } | |
| // Show loading | |
| document.getElementById('loading').classList.remove('hidden'); | |
| document.getElementById('result').classList.add('hidden'); | |
| try { | |
| const response = await fetch('/upload', { | |
| method: 'POST', | |
| body: formData | |
| }); | |
| console.log('[CLIENT] Full response object from /upload:', response); | |
| console.log('[CLIENT] Response status from /upload:', response.status); | |
| console.log('[CLIENT] Response status text from /upload:', response.statusText); | |
| const data = await response.json(); | |
| console.log('[CLIENT] Parsed JSON data from /upload:', data); | |
| if (response.ok) { | |
| console.log('[CLIENT] Response from /upload is OK (status 200-299)'); | |
| const videoElement = document.getElementById('outputVideo'); | |
| const resultDiv = document.getElementById('result'); | |
| console.log('[CLIENT] Setting video src to:', data.output_path); | |
| videoElement.src = data.output_path; | |
| resultDiv.classList.remove('hidden'); | |
| videoElement.load(); | |
| console.log('[CLIENT] Called video.load()'); | |
| videoElement.onloadeddata = () => { | |
| console.log('[CLIENT] Video data loaded successfully.'); | |
| }; | |
| videoElement.onerror = (e) => { | |
| console.error('[CLIENT] Video player error:', e); | |
| console.error('[CLIENT] Video src that failed:', videoElement.src); | |
| alert('Error loading video. Check browser console for details.'); | |
| }; | |
| } else { | |
| console.error('[CLIENT] Response from /upload not OK. Status:', response.status); | |
| alert(data.error || `Server error: ${response.status} ${response.statusText}. Check browser console.`); | |
| } | |
| } catch (error) { | |
| console.error('[CLIENT] Fetch Error during /upload:', error); | |
| alert('A critical error occurred while uploading/processing the video. Check browser console.'); | |
| } finally { | |
| document.getElementById('loading').classList.add('hidden'); | |
| } | |
| }); | |
| </script> | |
| </body> | |
| </html> |