Spaces:
Running
Running
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>FreeVoice | Text to Speech</title> | |
| <script src="https://cdn.tailwindcss.com"></script> | |
| <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> | |
| <style> | |
| @import url('https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;500;600;700&display=swap'); | |
| body { | |
| font-family: 'Poppins', sans-serif; | |
| background-color: #f8fafc; | |
| } | |
| .gradient-bg { | |
| background: linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%); | |
| } | |
| .waveform { | |
| background: linear-gradient(90deg, #e0e7ff 0%, #c7d2fe 100%); | |
| height: 60px; | |
| border-radius: 8px; | |
| position: relative; | |
| overflow: hidden; | |
| } | |
| .waveform::before { | |
| content: ""; | |
| position: absolute; | |
| top: 0; | |
| left: 0; | |
| right: 0; | |
| bottom: 0; | |
| background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent); | |
| animation: wave 2s linear infinite; | |
| } | |
| @keyframes wave { | |
| 0% { transform: translateX(-100%); } | |
| 100% { transform: translateX(100%); } | |
| } | |
| .recording { | |
| animation: pulse 1.5s infinite; | |
| } | |
| @keyframes pulse { | |
| 0% { box-shadow: 0 0 0 0 rgba(236, 72, 153, 0.7); } | |
| 70% { box-shadow: 0 0 0 10px rgba(236, 72, 153, 0); } | |
| 100% { box-shadow: 0 0 0 0 rgba(236, 72, 153, 0); } | |
| } | |
| /* Custom audio player styles */ | |
| .audio-player { | |
| width: 100%; | |
| margin-top: 10px; | |
| } | |
| .audio-player::-webkit-media-controls-panel { | |
| background-color: #e0e7ff; | |
| border-radius: 8px; | |
| } | |
| .audio-player::-webkit-media-controls-play-button, | |
| .audio-player::-webkit-media-controls-mute-button { | |
| background-color: #6366f1; | |
| border-radius: 50%; | |
| } | |
| /* Custom range slider */ | |
| input[type="range"] { | |
| -webkit-appearance: none; | |
| height: 6px; | |
| border-radius: 3px; | |
| background: #e0e7ff; | |
| outline: none; | |
| } | |
| input[type="range"]::-webkit-slider-thumb { | |
| -webkit-appearance: none; | |
| appearance: none; | |
| width: 16px; | |
| height: 16px; | |
| border-radius: 50%; | |
| background: #6366f1; | |
| cursor: pointer; | |
| } | |
| /* Voice sample animation */ | |
| .voice-sample { | |
| transition: all 0.3s ease; | |
| } | |
| .voice-sample:hover { | |
| transform: translateY(-2px); | |
| box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); | |
| } | |
| .voice-sample.active { | |
| border-color: #6366f1; | |
| background-color: #e0e7ff; | |
| } | |
| </style> | |
| </head> | |
| <body class="bg-gray-50"> | |
| <div class="max-w-4xl mx-auto px-4 py-8"> | |
| <!-- Text to Speech Section --> | |
| <div class="bg-white shadow rounded-lg p-6 mb-8"> | |
| <h2 class="text-2xl font-bold text-gray-900 mb-6">Text to Speech</h2> | |
| <div class="grid grid-cols-1 md:grid-cols-3 gap-6"> | |
| <!-- Text Input --> | |
| <div class="md:col-span-2"> | |
| <label for="text-input" class="block text-sm font-medium text-gray-700 mb-1">Enter your text</label> | |
| <textarea id="text-input" rows="8" class="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500" placeholder="Type or paste your text here...">Welcome to FreeVoice, your free alternative to 11Labs with all premium features unlocked. This text will be converted to speech using our advanced AI technology.</textarea> | |
| <div class="mt-4 flex items-center justify-between"> | |
| <div class="flex items-center space-x-4"> | |
| <div> | |
| <label for="voice-select" class="block text-sm font-medium text-gray-700 mb-1">Voice</label> | |
| <select id="voice-select" class="mt-1 block w-full pl-3 pr-10 py-2 text-base border-gray-300 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm rounded-md"> | |
| <option>Rachel (Female, American)</option> | |
| <option>James (Male, British)</option> | |
| <option>Sophie (Female, Australian)</option> | |
| <option>Liam (Male, American)</option> | |
| <option>Emma (Female, Canadian)</option> | |
| <option selected>Oliver (Male, British)</option> | |
| <option>Charlotte (Female, American)</option> | |
| </select> | |
| </div> | |
| <div> | |
| <label for="model-select" class="block text-sm font-medium text-gray-700 mb-1">Model</label> | |
| <select id="model-select" class="mt-1 block w-full pl-3 pr-10 py-2 text-base border-gray-300 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm rounded-md"> | |
| <option>Standard</option> | |
| <option selected>Premium (High Quality)</option> | |
| <option>Premium Ultra (Highest Quality)</option> | |
| </select> | |
| </div> | |
| </div> | |
| <button id="generate-btn" class="bg-indigo-600 hover:bg-indigo-700 text-white px-6 py-2 rounded-md text-sm font-medium"> | |
| <i class="fas fa-play mr-2"></i>Generate | |
| </button> | |
| </div> | |
| <!-- Voice Samples --> | |
| <div class="mt-6"> | |
| <label class="block text-sm font-medium text-gray-700 mb-2">Voice Samples</label> | |
| <div class="grid grid-cols-2 sm:grid-cols-3 gap-3"> | |
| <div class="voice-sample border rounded-lg p-3 cursor-pointer" data-voice="Rachel"> | |
| <div class="flex items-center"> | |
| <div class="w-10 h-10 rounded-full bg-purple-100 flex items-center justify-center mr-3"> | |
| <i class="fas fa-female text-purple-600"></i> | |
| </div> | |
| <div> | |
| <div class="font-medium">Rachel</div> | |
| <div class="text-xs text-gray-500">Female, American</div> | |
| </div> | |
| </div> | |
| <audio src="https://actions.google.com/sounds/v1/alarms/beep_short.ogg" preload="auto"></audio> | |
| </div> | |
| <div class="voice-sample border rounded-lg p-3 cursor-pointer" data-voice="James"> | |
| <div class="flex items-center"> | |
| <div class="w-10 h-10 rounded-full bg-blue-100 flex items-center justify-center mr-3"> | |
| <i class="fas fa-male text-blue-600"></i> | |
| </div> | |
| <div> | |
| <div class="font-medium">James</div> | |
| <div class="text-xs text-gray-500">Male, British</div> | |
| </div> | |
| </div> | |
| <audio src="https://actions.google.com/sounds/v1/alarms/beep_short.ogg" preload="auto"></audio> | |
| </div> | |
| <div class="voice-sample border rounded-lg p-3 cursor-pointer" data-voice="Sophie"> | |
| <div class="flex items-center"> | |
| <div class="w-10 h-10 rounded-full bg-pink-100 flex items-center justify-center mr-3"> | |
| <i class="fas fa-female text-pink-600"></i> | |
| </div> | |
| <div> | |
| <div class="font-medium">Sophie</div> | |
| <div class="text-xs text-gray-500">Female, Australian</div> | |
| </div> | |
| </div> | |
| <audio src="https://actions.google.com/sounds/v1/alarms/beep_short.ogg" preload="auto"></audio> | |
| </div> | |
| <div class="voice-sample border rounded-lg p-3 cursor-pointer" data-voice="Liam"> | |
| <div class="flex items-center"> | |
| <div class="w-10 h-10 rounded-full bg-green-100 flex items-center justify-center mr-3"> | |
| <i class="fas fa-male text-green-600"></i> | |
| </div> | |
| <div> | |
| <div class="font-medium">Liam</div> | |
| <div class="text-xs text-gray-500">Male, American</div> | |
| </div> | |
| </div> | |
| <audio src="https://actions.google.com/sounds/v1/alarms/beep_short.ogg" preload="auto"></audio> | |
| </div> | |
| <div class="voice-sample border rounded-lg p-3 cursor-pointer" data-voice="Emma"> | |
| <div class="flex items-center"> | |
| <div class="w-10 h-10 rounded-full bg-yellow-100 flex items-center justify-center mr-3"> | |
| <i class="fas fa-female text-yellow-600"></i> | |
| </div> | |
| <div> | |
| <div class="font-medium">Emma</div> | |
| <div class="text-xs text-gray-500">Female, Canadian</div> | |
| </div> | |
| </div> | |
| <audio src="https://actions.google.com/sounds/v1/alarms/beep_short.ogg" preload="auto"></audio> | |
| </div> | |
| <div class="voice-sample border rounded-lg p-3 cursor-pointer active" data-voice="Oliver"> | |
| <div class="flex items-center"> | |
| <div class="w-10 h-10 rounded-full bg-indigo-100 flex items-center justify-center mr-3"> | |
| <i class="fas fa-male text-indigo-600"></i> | |
| </div> | |
| <div> | |
| <div class="font-medium">Oliver</div> | |
| <div class="text-xs text-gray-500">Male, British</div> | |
| </div> | |
| </div> | |
| <audio src="https://actions.google.com/sounds/v1/alarms/beep_short.ogg" preload="auto"></audio> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Voice Settings --> | |
| <div> | |
| <label class="block text-sm font-medium text-gray-700 mb-2">Voice Settings</label> | |
| <div class="bg-gray-50 p-4 rounded-lg"> | |
| <div class="mb-4"> | |
| <label for="stability" class="block text-sm font-medium text-gray-700 mb-1">Stability</label> | |
| <input type="range" id="stability" min="0" max="100" value="75" class="w-full h-2 bg-gray-200 rounded-lg appearance-none cursor-pointer"> | |
| <div class="flex justify-between text-xs text-gray-500 mt-1"> | |
| <span>More variable</span> | |
| <span>More stable</span> | |
| </div> | |
| </div> | |
| <div class="mb-4"> | |
| <label for="clarity" class="block text-sm font-medium text-gray-700 mb-1">Clarity + Similarity</label> | |
| <input type="range" id="clarity" min="0" max="100" value="80" class="w-full h-2 bg-gray-200 rounded-lg appearance-none cursor-pointer"> | |
| <div class="flex justify-between text-xs text-gray-500 mt-1"> | |
| <span>More clear</span> | |
| <span>More similar</span> | |
| </div> | |
| </div> | |
| <div> | |
| <label for="style" class="block text-sm font-medium text-gray-700 mb-1">Style Exaggeration</label> | |
| <input type="range" id="style" min="0" max="100" value="50" class="w-full h-2 bg-gray-200 rounded-lg appearance-none cursor-pointer"> | |
| <div class="flex justify-between text-xs text-gray-500 mt-1"> | |
| <span>Less style</span> | |
| <span>More style</span> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Advanced Settings --> | |
| <div class="mt-6"> | |
| <label class="block text-sm font-medium text-gray-700 mb-2">Advanced Settings</label> | |
| <div class="bg-gray-50 p-4 rounded-lg"> | |
| <div class="mb-4"> | |
| <label for="speed" class="block text-sm font-medium text-gray-700 mb-1">Speed</label> | |
| <input type="range" id="speed" min="80" max="120" value="100" class="w-full h-2 bg-gray-200 rounded-lg appearance-none cursor-pointer"> | |
| <div class="flex justify-between text-xs text-gray-500 mt-1"> | |
| <span>Slower</span> | |
| <span>Faster</span> | |
| </div> | |
| </div> | |
| <div> | |
| <label for="pitch" class="block text-sm font-medium text-gray-700 mb-1">Pitch</label> | |
| <input type="range" id="pitch" min="80" max="120" value="100" class="w-full h-2 bg-gray-200 rounded-lg appearance-none cursor-pointer"> | |
| <div class="flex justify-between text-xs text-gray-500 mt-1"> | |
| <span>Lower</span> | |
| <span>Higher</span> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Generated Audio --> | |
| <div id="audio-result" class="mt-8 hidden"> | |
| <div class="flex items-center justify-between mb-4"> | |
| <h3 class="text-lg font-medium text-gray-900">Generated Audio</h3> | |
| <div class="flex space-x-2"> | |
| <button id="download-btn" class="text-indigo-600 hover:text-indigo-800"> | |
| <i class="fas fa-download"></i> | |
| </button> | |
| <button id="share-btn" class="text-indigo-600 hover:text-indigo-800"> | |
| <i class="fas fa-share-alt"></i> | |
| </button> | |
| </div> | |
| </div> | |
| <div class="waveform p-4 mb-4"> | |
| <!-- Waveform visualization would go here --> | |
| </div> | |
| <audio id="generated-audio" controls class="audio-player w-full"> | |
| Your browser does not support the audio element. | |
| </audio> | |
| <div class="flex items-center space-x-4 mt-4"> | |
| <button id="play-btn" class="bg-indigo-600 hover:bg-indigo-700 text-white px-4 py-2 rounded-md text-sm font-medium"> | |
| <i class="fas fa-play mr-2"></i>Play | |
| </button> | |
| <button id="regenerate-btn" class="bg-white hover:bg-gray-50 text-gray-700 px-4 py-2 rounded-md text-sm font-medium border border-gray-300"> | |
| <i class="fas fa-redo mr-2"></i>Regenerate | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <script> | |
| // Voice samples functionality | |
| document.querySelectorAll('.voice-sample').forEach(sample => { | |
| sample.addEventListener('click', function() { | |
| // Remove active class from all samples | |
| document.querySelectorAll('.voice-sample').forEach(s => { | |
| s.classList.remove('active'); | |
| }); | |
| // Add active class to clicked sample | |
| this.classList.add('active'); | |
| // Update voice select dropdown | |
| const voiceName = this.getAttribute('data-voice'); | |
| const voiceSelect = document.getElementById('voice-select'); | |
| for (let i = 0; i < voiceSelect.options.length; i++) { | |
| if (voiceSelect.options[i].text.includes(voiceName)) { | |
| voiceSelect.selectedIndex = i; | |
| break; | |
| } | |
| } | |
| // Play sample audio | |
| const audio = this.querySelector('audio'); | |
| audio.currentTime = 0; | |
| audio.play(); | |
| }); | |
| }); | |
| // Text to Speech Functionality using Web Speech API | |
| document.getElementById('generate-btn').addEventListener('click', function() { | |
| const text = document.getElementById('text-input').value; | |
| const voice = document.getElementById('voice-select').value; | |
| const model = document.getElementById('model-select').value; | |
| const stability = document.getElementById('stability').value; | |
| const clarity = document.getElementById('clarity').value; | |
| const style = document.getElementById('style').value; | |
| const speed = document.getElementById('speed').value; | |
| const pitch = document.getElementById('pitch').value; | |
| if (!text.trim()) { | |
| alert('Please enter some text to convert to speech'); | |
| return; | |
| } | |
| // Show loading state | |
| const btn = this; | |
| const originalText = btn.innerHTML; | |
| btn.innerHTML = '<i class="fas fa-spinner fa-spin mr-2"></i>Generating...'; | |
| btn.disabled = true; | |
| // Simulate API call delay | |
| setTimeout(function() { | |
| // Show result | |
| document.getElementById('audio-result').classList.remove('hidden'); | |
| // Create speech synthesis | |
| const utterance = new SpeechSynthesisUtterance(text); | |
| // Set voice properties based on selection | |
| if (voice.includes("Rachel")) { | |
| // Female American voice - clear, professional | |
| utterance.rate = 1.1 * (speed / 100); | |
| utterance.pitch = 1.1 * (pitch / 100); | |
| utterance.lang = 'en-US'; | |
| } else if (voice.includes("James")) { | |
| // Male British voice - deep, formal | |
| utterance.rate = 0.95 * (speed / 100); | |
| utterance.pitch = 0.9 * (pitch / 100); | |
| utterance.lang = 'en-GB'; | |
| } else if (voice.includes("Sophie")) { | |
| // Female Australian voice - bright, friendly | |
| utterance.rate = 1.05 * (speed / 100); | |
| utterance.pitch = 1.15 * (pitch / 100); | |
| utterance.lang = 'en-AU'; | |
| } else if (voice.includes("Liam")) { | |
| // Male American voice - casual, youthful | |
| utterance.rate = 1.0 * (speed / 100); | |
| utterance.pitch = 1.0 * (pitch / 100); | |
| utterance.lang = 'en-US'; | |
| } else if (voice.includes("Emma")) { | |
| // Female Canadian voice - warm, soothing | |
| utterance.rate = 0.98 * (speed / 100); | |
| utterance.pitch = 1.05 * (pitch / 100); | |
| utterance.lang = 'en-CA'; | |
| } else if (voice.includes("Oliver")) { | |
| // Premium Male British voice - refined, articulate | |
| utterance.rate = 0.9 * (speed / 100); | |
| utterance.pitch = 0.85 * (pitch / 100); | |
| utterance.lang = 'en-GB'; | |
| } else if (voice.includes("Charlotte")) { | |
| // Premium Female American voice - elegant, expressive | |
| utterance.rate = 1.0 * (speed / 100); | |
| utterance.pitch = 1.2 * (pitch / 100); | |
| utterance.lang = 'en-US'; | |
| } | |
| // Adjust based on advanced settings | |
| utterance.rate *= (1 + (50 - clarity) / 100); | |
| utterance.pitch *= (1 + (style - 50) / 100); | |
| // Create audio blob from speech synthesis | |
| const audioCtx = new (window.AudioContext || window.webkitAudioContext)(); | |
| const dest = audioCtx.createMediaStreamDestination(); | |
| const mediaRecorder = new MediaRecorder(dest.stream); | |
| let chunks = []; | |
| mediaRecorder.ondataavailable = function(evt) { | |
| chunks.push(evt.data); | |
| }; | |
| mediaRecorder.onstop = function() { | |
| const blob = new Blob(chunks, { type: 'audio/wav' }); | |
| const audioUrl = URL.createObjectURL(blob); | |
| const audio = document.getElementById('generated-audio'); | |
| audio.src = audioUrl; | |
| // Reset button | |
| btn.innerHTML = originalText; | |
| btn.disabled = false; | |
| }; | |
| mediaRecorder.start(); | |
| // Speak the text | |
| speechSynthesis.speak(utterance); | |
| // Stop recording after speech ends | |
| utterance.onend = function() { | |
| mediaRecorder.stop(); | |
| }; | |
| // Log generation details | |
| console.log('Generated speech with:', { | |
| text: text, | |
| voice: voice, | |
| model: model, | |
| stability: stability, | |
| clarity: clarity, | |
| style: style, | |
| speed: speed, | |
| pitch: pitch | |
| }); | |
| }, 1500); | |
| }); | |
| // Play/Stop audio | |
| document.getElementById('play-btn').addEventListener('click', function() { | |
| const audio = document.getElementById('generated-audio'); | |
| const icon = this.querySelector('i'); | |
| if (audio.paused) { | |
| audio.play(); | |
| icon.classList.remove('fa-play'); | |
| icon.classList.add('fa-stop'); | |
| this.innerHTML = '<i class="fas fa-stop mr-2"></i>Stop'; | |
| } else { | |
| audio.pause(); | |
| audio.currentTime = 0; | |
| icon.classList.remove('fa-stop'); | |
| icon.classList.add('fa-play'); | |
| this.innerHTML = '<i class="fas fa-play mr-2"></i>Play'; | |
| } | |
| }); | |
| // Regenerate audio | |
| document.getElementById('regenerate-btn').addEventListener('click', function() { | |
| document.getElementById('generate-btn').click(); | |
| }); | |
| // Download audio | |
| document.getElementById('download-btn').addEventListener('click', function() { | |
| const audio = document.getElementById('generated-audio'); | |
| const a = document.createElement('a'); | |
| a.href = audio.src; | |
| // Get the selected voice name for the filename | |
| const voiceSelect = document.getElementById('voice-select'); | |
| const voiceName = voiceSelect.options[voiceSelect.selectedIndex].text | |
| .replace(/[^a-zA-Z0-9]/g, '_') | |
| .toLowerCase(); | |
| a.download = `freevoice_${voiceName}.wav`; | |
| document.body.appendChild(a); | |
| a.click(); | |
| document.body.removeChild(a); | |
| }); | |
| // Share audio | |
| document.getElementById('share-btn').addEventListener('click', function() { | |
| if (navigator.share) { | |
| navigator.share({ | |
| title: 'FreeVoice Text-to-Speech', | |
| text: 'Check out this AI-generated voice from FreeVoice!', | |
| url: window.location.href | |
| }).catch(err => { | |
| console.log('Error sharing:', err); | |
| alert('Sharing failed: ' + err.message); | |
| }); | |
| } else { | |
| alert('Web Share API not supported in your browser. Copy this link to share: ' + window.location.href); | |
| } | |
| }); | |
| // Update audio player when audio is playing/paused | |
| document.getElementById('generated-audio').addEventListener('play', function() { | |
| document.getElementById('play-btn').innerHTML = '<i class="fas fa-stop mr-2"></i>Stop'; | |
| }); | |
| document.getElementById('generated-audio').addEventListener('pause', function() { | |
| if (this.currentTime === 0 || this.ended) { | |
| document.getElementById('play-btn').innerHTML = '<i class="fas fa-play mr-2"></i>Play'; | |
| } | |
| }); | |
| document.getElementById('generated-audio').addEventListener('ended', function() { | |
| document.getElementById('play-btn').innerHTML = '<i class="fas fa-play mr-2"></i>Play'; | |
| }); | |
| // Initialize range sliders to show values | |
| const sliders = ['stability', 'clarity', 'style', 'speed', 'pitch']; | |
| sliders.forEach(sliderId => { | |
| const slider = document.getElementById(sliderId); | |
| slider.addEventListener('input', function() { | |
| // You could add visual feedback for the slider values if needed | |
| console.log(`${sliderId} value:`, this.value); | |
| }); | |
| }); | |
| </script> | |
| <p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=theaimoron/voice-over" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body> | |
| </html> |