Spaces:
Running
Running
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
| <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> |