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> |