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/[email protected]/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/[email protected]/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> |