Spaces:
Sleeping
Sleeping
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>AI Talking Avatar Generator</title> | |
| <!-- Bootstrap CSS --> | |
| <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet"> | |
| <link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" rel="stylesheet"> | |
| <link href="https://fonts.googleapis.com/css2?family=Nunito:wght@200;300;400;600;700;800;900&display=swap" rel="stylesheet"> | |
| <!-- Custom CSS --> | |
| <style> | |
| /* Custom Bootstrap overrides */ | |
| :root { | |
| --primary-color: #4e73df; | |
| --secondary-color: #858796; | |
| --success-color: #1cc88a; | |
| } | |
| body { | |
| background-color: #f8f9fc; | |
| font-family: 'Nunito', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif; | |
| } | |
| .card { | |
| border: none; | |
| border-radius: 0.35rem; | |
| box-shadow: 0 0.15rem 1.75rem 0 rgba(58, 59, 69, 0.15); | |
| } | |
| .card-header { | |
| background-color: var(--primary-color); | |
| border-bottom: none; | |
| } | |
| .btn-primary { | |
| background-color: var(--primary-color); | |
| border-color: var(--primary-color); | |
| } | |
| .btn-primary:hover { | |
| background-color: #2e59d9; | |
| border-color: #2653d4; | |
| } | |
| /* Upload area styling */ | |
| .upload-area { | |
| border: 2px dashed #d1d3e2; | |
| border-radius: 0.35rem; | |
| padding: 2rem; | |
| text-align: center; | |
| margin-bottom: 1.5rem; | |
| cursor: pointer; | |
| transition: all 0.3s; | |
| } | |
| .upload-area:hover { | |
| border-color: var(--primary-color); | |
| background-color: rgba(78, 115, 223, 0.05); | |
| } | |
| .upload-area i { | |
| font-size: 3rem; | |
| color: var(--secondary-color); | |
| margin-bottom: 1rem; | |
| } | |
| /* Video result styling */ | |
| #result { | |
| transition: all 0.3s; | |
| } | |
| #outputVideo { | |
| border-radius: 0.35rem; | |
| background-color: #000; | |
| } | |
| /* Loading spinner */ | |
| .spinner-border { | |
| width: 1.2rem; | |
| height: 1.2rem; | |
| border-width: 0.15em; | |
| } | |
| /* Responsive adjustments */ | |
| @media (max-width: 768px) { | |
| .card-body { | |
| padding: 1.25rem; | |
| } | |
| .upload-area { | |
| padding: 1.5rem; | |
| } | |
| } | |
| /* Animation for processing state */ | |
| @keyframes pulse { | |
| 0% { opacity: 0.6; } | |
| 50% { opacity: 1; } | |
| 100% { opacity: 0.6; } | |
| } | |
| .processing { | |
| animation: pulse 1.5s infinite; | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <div class="container py-5"> | |
| <div class="row justify-content-center"> | |
| <div class="col-lg-8"> | |
| <div class="card"> | |
| <div class="card-header text-white"> | |
| <h4 class="mb-0"><i class="fas fa-video me-2"></i>AI Talking Avatar Generator</h4> | |
| </div> | |
| <div class="card-body"> | |
| <form id="avatarForm" enctype="multipart/form-data"> | |
| <!-- Image Upload --> | |
| <div class="mb-4"> | |
| <label class="form-label">Upload Photo</label> | |
| <div class="upload-area" onclick="document.getElementById('imageInput').click()"> | |
| <i class="fas fa-cloud-upload-alt"></i> | |
| <p class="mb-0">Click to upload an image</p> | |
| <small class="text-muted">Supports JPG, PNG formats</small> | |
| </div> | |
| <input type="file" id="imageInput" class="d-none" accept="image/*" required> | |
| </div> | |
| <!-- Text Input --> | |
| <div class="mb-4"> | |
| <label for="textInput" class="form-label">Enter Text</label> | |
| <textarea id="textInput" class="form-control" rows="4" | |
| placeholder="Enter the text you want the avatar to speak..." required></textarea> | |
| </div> | |
| <!-- Submit Button --> | |
| <button type="submit" class="btn btn-primary btn-lg w-100"> | |
| <i class="fas fa-magic me-2"></i>Generate Video | |
| </button> | |
| </form> | |
| </div> | |
| </div> | |
| <!-- Result Section --> | |
| <div id="result" class="card mt-4 d-none"> | |
| <div class="card-header"> | |
| <h5 class="mb-0"><i class="fas fa-play-circle me-2"></i>Generated Video</h5> | |
| </div> | |
| <div class="card-body text-center"> | |
| <video id="outputVideo" controls class="w-100 mb-3" style="max-height: 400px;"></video> | |
| <button id="downloadBtn" class="btn btn-success"> | |
| <i class="fas fa-download me-2"></i>Download Video | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Bootstrap JS --> | |
| <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"></script> | |
| <script> | |
| // Debug: Check if script is loaded | |
| console.log('Scripts.js loaded successfully'); | |
| document.addEventListener('DOMContentLoaded', function() { | |
| console.log('DOM loaded, initializing form handler'); | |
| const form = document.getElementById('avatarForm'); | |
| if (!form) { | |
| console.error('Avatar form not found!'); | |
| return; | |
| } | |
| form.addEventListener('submit', async (e) => { | |
| e.preventDefault(); | |
| console.log('Form submitted'); | |
| const imageInput = document.getElementById('imageInput'); | |
| const textInput = document.getElementById('textInput'); | |
| if (!imageInput.files[0]) { | |
| alert('Please select an image'); | |
| return; | |
| } | |
| if (!textInput.value.trim()) { | |
| alert('Please enter some text'); | |
| return; | |
| } | |
| const formData = new FormData(); | |
| formData.append('image', imageInput.files[0]); | |
| formData.append('text', textInput.value); | |
| // Show loading state | |
| const btn = document.querySelector('button[type="submit"]'); | |
| btn.disabled = true; | |
| btn.innerHTML = '<span class="spinner-border spinner-border-sm me-2" role="status"></span> Processing...'; | |
| try { | |
| console.log('Sending request to /generate'); | |
| const response = await fetch('/generate', { | |
| method: 'POST', | |
| body: formData | |
| }); | |
| if (!response.ok) { | |
| throw new Error(`HTTP error! status: ${response.status}`); | |
| } | |
| const data = await response.json(); | |
| console.log('Response received:', data); | |
| if (data.error) { | |
| throw new Error(data.error); | |
| } | |
| // Display result | |
| const video = document.getElementById('outputVideo'); | |
| video.src = data.video; | |
| document.getElementById('result').classList.remove('d-none'); | |
| // Set up download | |
| document.getElementById('downloadBtn').onclick = () => { | |
| const a = document.createElement('a'); | |
| a.href = data.video; | |
| a.download = 'talking_avatar.mp4'; | |
| a.click(); | |
| }; | |
| } catch (error) { | |
| console.error('Error:', error); | |
| alert('Error: ' + error.message); | |
| } finally { | |
| btn.disabled = false; | |
| btn.innerHTML = '<i class="fas fa-magic me-2"></i>Generate Video'; | |
| } | |
| }); | |
| }); | |
| </script> | |
| </body> | |
| </html> |