VOICECLONE / voice-clone.html
luis5463's picture
This web page has three parts. First, an upload area where the user uploads an audio sample to create a voice clone. After upload, the backend extracts and stores the speaker’s voice and returns an identifier for that voice. Second, a text input area where the user types the text they want the cloned voice to speak. Third, a results area where the page shows the generated audio and provides a download link.
0ddd5c8 verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>VoiceCloneAI - Clone Your Voice</title>
<script src="https://cdn.tailwindcss.com"></script>
<script src="https://cdn.jsdelivr.net/npm/feather-icons/dist/feather.min.js"></script>
<script src="https://unpkg.com/feather-icons"></script>
<style>
body {
font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
background: linear-gradient(135deg, #0f0f23 0%, #1a1a2e 50%, #16213e 100%);
color: #ffffff;
min-height: 100vh;
}
.wave-bg {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: -1;
opacity: 0.3;
}
.glass-card {
background: rgba(255, 255, 255, 0.05);
backdrop-filter: blur(20px);
-webkit-backdrop-filter: blur(20px);
border-radius: 24px;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
border: 1px solid rgba(255, 255, 255, 0.1);
}
.btn-gradient {
background: linear-gradient(135deg, #6366F1 0%, #8B5CF6 50%, #EC4899 100%);
color: white;
transition: all 0.4s ease;
border: none;
position: relative;
overflow: hidden;
}
.btn-gradient:hover {
transform: translateY(-3px);
box-shadow: 0 15px 30px rgba(99, 102, 241, 0.3);
}
.btn-gradient::before {
content: '';
position: absolute;
top: 0;
left: -100%;
width: 100%;
height: 100%;
background: linear-gradient(90deg, transparent, rgba(255,255,255,0.2), transparent);
transition: left 0.5s;
}
.btn-gradient:hover::before {
left: 100%;
}
.gradient-text {
background: linear-gradient(135deg, #6366F1 0%, #8B5CF6 30%, #EC4899 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
.neon-glow {
text-shadow: 0 0 10px rgba(99, 102, 241, 0.5),
0 0 20px rgba(99, 102, 241, 0.3),
0 0 40px rgba(99, 102, 241, 0.1);
}
.audio-wave {
background: linear-gradient(90deg,
#6366F1 0%,
#8B5CF6 25%,
#EC4899 50%,
#F59E0B 75%,
#10B981 100%);
height: 4px;
border-radius: 2px;
animation: wave 2s ease-in-out infinite;
}
@keyframes wave {
0%, 100% { transform: scaleX(0.8); opacity: 0.7; }
50% { transform: scaleX(1); opacity: 1; }
}
.floating {
animation: floating 3s ease-in-out infinite;
}
@keyframes floating {
0%, 100% { transform: translateY(0px); }
50% { transform: translateY(-10px); }
}
.upload-area {
transition: all 0.3s ease;
border: 2px dashed rgba(255, 255, 255, 0.2);
}
.upload-area:hover {
border-color: #6366F1;
background: rgba(99, 102, 241, 0.05);
}
.upload-area.dragover {
border-color: #8B5CF6;
background: rgba(139, 92, 246, 0.1);
}
</style>
</head>
<body class="bg-gray-50">
<div id="wave-bg" class="wave-bg"></div>
<div class="min-h-screen flex flex-col items-center justify-start px-4 pt-12">
<header class="w-full max-w-6xl mx-auto py-8">
<div class="flex flex-col items-center">
<div class="w-24 h-24 bg-gradient-to-br from-indigo-500 via-purple-500 to-pink-500 rounded-full flex items-center justify-center shadow-2xl mb-8 floating">
<i data-feather="copy" class="w-12 h-12 text-white"></i>
</div>
<h1 class="text-6xl font-black mb-4 gradient-text neon-glow">VoiceCloneAI</h1>
<p class="text-2xl text-gray-300 max-w-2xl text-center leading-relaxed">Clone Any Voice with Advanced AI Technology</p>
<div class="mt-6 audio-wave w-32"></div>
</div>
</header>
<main class="w-full max-w-6xl glass-card p-12 mb-12">
<div class="grid lg:grid-cols-3 gap-8">
<!-- Section 1: Upload Audio for Cloning -->
<div class="lg:col-span-1">
<div class="space-y-6">
<div>
<h2 class="text-2xl font-bold gradient-text mb-3">1. Upload Voice Sample</h2>
<p class="text-gray-300">Upload a clear audio sample to create your voice clone</p>
</div>
<div id="uploadArea" class="upload-area rounded-2xl p-8 text-center cursor-pointer">
<div class="flex flex-col items-center">
<i data-feather="upload-cloud" class="w-16 h-16 text-indigo-400 mb-4"></i>
<h3 class="text-lg font-semibold text-white mb-2">Drop your audio file here</h3>
<p class="text-gray-400 text-sm">Supports WAV, MP3, OGG (max 50MB)</p>
<p class="text-gray-500 text-xs mt-2">Or click to select file</p>
</div>
<input type="file" id="audioFile" accept="audio/*" class="hidden" />
</div>
<div id="fileInfo" class="hidden bg-gray-800 rounded-xl p-4">
<div class="flex items-center justify-between">
<div class="flex items-center space-x-3">
<i data-feather="file" class="w-5 h-5 text-green-400"></i>
<span id="fileName" class="text-white font-medium"></span>
</div>
<span id="fileSize" class="text-gray-400 text-sm"></span>
</div>
<div class="mt-3">
<div class="w-full bg-gray-700 rounded-full h-2">
<div id="uploadProgress" class="bg-gradient-to-r from-green-500 to-blue-500 h-2 rounded-full w-0 transition-all duration-300"></div>
</div>
</div>
</div>
<button id="cloneBtn" class="w-full px-6 py-4 btn-gradient rounded-xl flex items-center justify-center space-x-3 text-lg font-medium disabled:opacity-50 disabled:cursor-not-allowed" disabled>
<i data-feather="cpu" class="w-5 h-5"></i>
<span>Create Voice Clone</span>
</button>
<div id="voiceIdDisplay" class="hidden bg-gradient-to-r from-indigo-500 to-purple-500 rounded-xl p-4">
<div class="flex items-center justify-between">
<div class="flex items-center space-x-3">
<i data-feather="check-circle" class="w-5 h-5 text-white"></i>
<span class="text-white font-medium">Voice Clone Created!</span>
</div>
</div>
<div class="mt-2">
<span class="text-indigo-100 text-sm">Voice ID:</span>
<code id="voiceId" class="bg-black bg-opacity-30 px-2 py-1 rounded text-xs font-mono text-white ml-2"></code>
</div>
</div>
</div>
</div>
<!-- Section 2: Text Input -->
<div class="lg:col-span-1">
<div class="space-y-6">
<div>
<h2 class="text-2xl font-bold gradient-text mb-3">2. Enter Text to Speak</h2>
<p class="text-gray-300">Type what you want the cloned voice to say</p>
</div>
<div class="bg-gray-800 rounded-xl p-6">
<textarea
id="textInput"
rows="8"
class="w-full bg-gray-700 border border-gray-600 rounded-xl p-4 resize-none focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 text-white placeholder-gray-400"
placeholder="Enter the text you want the cloned voice to speak..."
disabled
></textarea>
<div class="flex items-center justify-between mt-4">
<span id="charCount" class="text-gray-400 text-sm">0 characters</span>
<span class="text-gray-400 text-sm">Max 1000 characters</span>
</div>
</div>
<button id="synthesizeBtn" class="w-full px-6 py-4 btn-gradient rounded-xl flex items-center justify-center space-x-3 text-lg font-medium disabled:opacity-50 disabled:cursor-not-allowed" disabled style="background: linear-gradient(135deg, #10B981 0%, #3B82F6 50%, #6366F1 100%);">
<i data-feather="play" class="w-5 h-5"></i>
<span>Synthesize & Play</span>
</button>
</div>
</div>
<!-- Section 3: Results -->
<div class="lg:col-span-1">
<div class="space-y-6">
<div>
<h2 class="text-2xl font-bold gradient-text mb-3">3. Generated Audio</h2>
<p class="text-gray-300">Listen to and download your synthesized voice</p>
</div>
<div id="noResultDisplay" class="bg-gray-800 rounded-xl p-8 text-center">
<div class="flex flex-col items-center justify-center">
<i data-feather="music" class="w-16 h-16 text-gray-500 mb-4"></i>
<h3 class="text-lg font-semibold text-gray-400 mb-2">No Audio Generated</h3>
<p class="text-gray-500 text-sm">Complete steps 1 and 2 to generate your voice clone audio</p>
</div>
</div>
<div id="resultDisplay" class="hidden bg-gradient-to-br from-purple-900 to-pink-900 rounded-xl p-6">
<div class="space-y-4">
<div class="flex items-center justify-between">
<h3 class="text-lg font-semibold text-white">Your Cloned Voice</h3>
<i data-feather="check-circle" class="w-5 h-5 text-green-400"></i>
</div>
<audio id="audioPlayer" controls class="w-full rounded-lg"></audio>
<div class="flex items-center justify-between pt-4 border-t border-purple-700">
<span class="text-purple-200 text-sm">Ready to download</span>
<button id="downloadBtn" class="px-4 py-2 bg-white bg-opacity-20 text-white rounded-lg hover:bg-opacity-30 transition-all duration-300 flex items-center space-x-2">
<i data-feather="download" class="w-4 h-4"></i>
<span>Download MP3</span>
</button>
</div>
</div>
</div>
<div id="errorDisplay" class="hidden bg-gradient-to-r from-red-900 to-pink-900 rounded-xl p-4">
<div class="flex items-center space-x-3">
<i data-feather="alert-triangle" class="w-5 h-5 text-red-300"></i>
<span id="errorMessage" class="text-red-200"></span>
</div>
</div>
</div>
</div>
</div>
</main>
<footer class="w-full max-w-6xl py-8 text-center">
<div class="flex items-center justify-center space-x-6 mb-4">
<a href="index.html" class="w-10 h-10 bg-gray-800 rounded-full flex items-center justify-center hover:bg-indigo-500 transition-all duration-300">
<i data-feather="home" class="w-5 h-5 text-gray-300"></i>
</a>
<a href="voice-clone.html" class="w-10 h-10 bg-indigo-500 rounded-full flex items-center justify-center">
<i data-feather="copy" class="w-5 h-5 text-white"></i>
</a>
</div>
<p class="text-gray-400 text-sm">© 2024 VoiceCloneAI - Advanced Voice Cloning Technology</p>
<div class="mt-4 audio-wave w-24 mx-auto"></div>
</footer>
</div>
<script>
feather.replace();
// Initialize Vanta.js waves background
VANTA.WAVES({
el: "#wave-bg",
color: 0x6366f1,
waveHeight: 20,
shininess: 80,
waveSpeed: 0.5,
zoom: 0.8,
colorDark: 0x0f0f23,
colorLight: 0x6366f1
});
// Add font
const link = document.createElement('link');
link.href = 'https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800;900&display=swap';
link.rel = 'stylesheet';
document.head.appendChild(link);
// Voice Cloning Application
class VoiceCloneApp {
constructor() {
this.currentVoiceId = null;
this.isLoading = false;
this.initializeEventListeners();
}
initializeEventListeners() {
// File upload handling
const uploadArea = document.getElementById('uploadArea');
const audioFileInput = document.getElementById('audioFile');
const cloneBtn = document.getElementById('cloneBtn');
const textInput = document.getElementById('textInput');
const synthesizeBtn = document.getElementById('synthesizeBtn');
// Upload area click and drag events
uploadArea.addEventListener('click', () => audioFileInput.click());
uploadArea.addEventListener('dragover', (e) => {
e.preventDefault();
uploadArea.classList.add('dragover');
});
uploadArea.addEventListener('dragleave', () => {
uploadArea.classList.remove('dragover');
});
uploadArea.addEventListener('drop', (e) => {
e.preventDefault();
uploadArea.classList.remove('dragover');
if (e.dataTransfer.files.length) {
audioFileInput.files = e.dataTransfer.files;
this.handleFileSelection();
}
});
// File input change
audioFileInput.addEventListener('change', () => this.handleFileSelection());
// Clone button
cloneBtn.addEventListener('click', () => this.cloneVoice());
// Text input character count
textInput.addEventListener('input', () => this.updateCharCount());
// Synthesize button
synthesizeBtn.addEventListener('click', () => this.synthesizeVoice());
}
handleFileSelection() {
const file = document.getElementById('audioFile').files[0];
const fileInfo = document.getElementById('fileInfo');
const uploadArea = document.getElementById('uploadArea');
const cloneBtn = document.getElementById('cloneBtn');
if (file) {
// Validate file type and size
if (!file.type.match('audio.*')) {
this.showError('Please select an audio file (WAV, MP3, OGG)');
return;
}
if (file.size > 50 * 1024 * 1024) {
this.showError('File size exceeds 50MB limit');
return;
}
// Show file info
fileInfo.classList.remove('hidden');
document.getElementById('fileName').textContent = file.name;
document.getElementById('fileSize').textContent = this.formatFileSize(file.size);
// Update upload area
uploadArea.innerHTML = `
<div class="flex flex-col items-center">
<i data-feather="check-circle" class="w-16 h-16 text-green-400 mb-4"></i>
<h3 class="text-lg font-semibold text-white mb-2">File Selected</h3>
<p class="text-gray-400 text-sm">${file.name}</p>
</div>
`;
feather.replace();
// Enable clone button
cloneBtn.disabled = false;
} else {
fileInfo.classList.add('hidden');
uploadArea.innerHTML = `
<div class="flex flex-col items-center">
<i data-feather="upload-cloud" class="w-16 h-16 text-indigo-400 mb-4"></i>
<h3 class="text-lg font-semibold text-white mb-2">Drop your audio file here</h3>
<p class="text-gray-400 text-sm">Supports WAV, MP3, OGG (max 50MB)</p>
<p class="text-gray-500 text-xs mt-2">Or click to select file</p>
</div>
`;
feather.replace();
cloneBtn.disabled = true;
}
// Reset voice ID and disable synthesize
this.currentVoiceId = null;
document.getElementById('voiceIdDisplay').classList.add('hidden');
document.getElementById('textInput').disabled = true;
document.getElementById('synthesizeBtn').disabled = true;
}
async cloneVoice() {
const file = document.getElementById('audioFile').files[0];
if (!file) {
this.showError('Please select an audio file first');
return;
}
this.setLoading(true);
this.hideError();
try {
// Simulate upload progress
this.simulateUploadProgress();
// Create FormData and send to backend
const formData = new FormData();
formData.append("file", file);
const response = await fetch("/api/clone-voice", {
method: "POST",
body: formData,
});
if (!response.ok) throw new Error(`Voice cloning failed: ${response.status}`);
const data = await response.json();
this.currentVoiceId = data.voiceId;
// Update UI
document.getElementById('voiceId').textContent = this.currentVoiceId;
document.getElementById('voiceIdDisplay').classList.remove('hidden');
document.getElementById('textInput').disabled = false;
document.getElementById('synthesizeBtn').disabled = false;
this.showSuccess('Voice clone created successfully!');
} catch (error) {
this.showError(error.message);
} finally {
this.setLoading(false);
}
}
async synthesizeVoice() {
if (!this.currentVoiceId) {
this.showError('Please create a voice clone first');
return;
}
const text = document.getElementById('textInput').value.trim();
if (!text) {
this.showError('Please enter text to synthesize');
return;
}
if (text.length > 1000) {
this.showError('Text exceeds 1000 character limit');
return;
}
this.setLoading(true);
this.hideError();
try {
const response = await fetch("/api/synthesize", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
voiceId: this.currentVoiceId,
text: text
}),
});
if (!response.ok) throw new Error(`Synthesis failed: ${response.status}`);
const data = await response.json();
// Update audio player and show results
const audioPlayer = document.getElementById('audioPlayer');
audioPlayer.src = data.audioUrl;
document.getElementById('noResultDisplay').classList.add('hidden');
document.getElementById('resultDisplay').classList.remove('hidden');
// Set up download button
document.getElementById('downloadBtn').onclick = () => {
const a = document.createElement('a');
a.href = data.audioUrl;
a.download = `voice-clone-${Date.now()}.mp3`;
a.click();
};
this.showSuccess('Audio generated successfully!');
} catch (error) {
this.showError(error.message);
} finally {
this.setLoading(false);
}
}
updateCharCount() {
const text = document.getElementById('textInput').value;
document.getElementById('charCount').textContent = `${text.length} characters`;
}
simulateUploadProgress() {
const progressBar = document.getElementById('uploadProgress');
let progress = 0;
const interval = setInterval(() => {
progress += Math.random() * 10;
if (progress > 100) {
progress = 100;
clearInterval(interval);
}
progressBar.style.width = `${progress}%`;
}, 200);
}
setLoading(loading) {
this.isLoading = loading;
const cloneBtn = document.getElementById('cloneBtn');
const synthesizeBtn = document.getElementById('synthesizeBtn');
cloneBtn.disabled = loading || !document.getElementById('audioFile').files[0];
cloneBtn.innerHTML = loading ?
'<i data-feather="loader" class="w-5 h-5 animate-spin"></i><span>Processing...</span>' :
'<i data-feather="cpu"></i><span>Create Voice Clone</span>';
synthesizeBtn.disabled = loading || !this.currentVoiceId;
synthesizeBtn.innerHTML = loading ?
'<i data-feather="loader" class="w-5 h-5 animate-spin"></i><span>Generating...</span>' :
'<i data-feather="play"></i><span>Synthesize & Play</span>';
feather.replace();
}
showError(message) {
const errorDisplay = document.getElementById('errorDisplay');
document.getElementById('errorMessage').textContent = message;
errorDisplay.classList.remove('hidden');
}
showSuccess(message) {
// Create temporary success notification
const notification = document.createElement('div');
notification.className = 'fixed top-4 right-4 bg-gradient-to-r from-green-500 to-blue-500 text-white px-6 py-3 rounded-xl shadow-2xl z-50';
notification.innerHTML = `✅ ${message}`;
document.body.appendChild(notification);
setTimeout(() => {
notification.remove();
}, 3000);
}
hideError() {
document.getElementById('errorDisplay').classList.add('hidden');
}
formatFileSize(bytes) {
if (bytes === 0) return '0 Bytes';
const k = 1024;
const sizes = ['Bytes', 'KB', 'MB', 'GB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
}
}
// Initialize the application
document.addEventListener('DOMContentLoaded', () => {
new VoiceCloneApp();
});
</script>
<script src="https://cdn.jsdelivr.net/npm/vanta@latest/dist/vanta.waves.min.js"></script>
</body>
</html>