Microsoft TTS Studio
🔧 CRITICAL BUG FIX: Speech Synthesis Issues Resolved
b8e63ae
import os
from fastapi import FastAPI
from fastapi.responses import HTMLResponse
import edge_tts
import asyncio
import tempfile
from pathlib import Path
app = FastAPI()
@app.get("/", response_class=HTMLResponse)
def read_root():
return """
<!DOCTYPE html>
<html>
<head>
<title>Microsoft Neural TTS Studio</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: 'Inter', sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
}
.container {
background: rgba(255, 255, 255, 0.95);
backdrop-filter: blur(10px);
border-radius: 20px;
padding: 40px;
box-shadow: 0 20px 40px rgba(0,0,0,0.1);
max-width: 800px;
width: 90%;
}
h1 {
color: #333;
margin-bottom: 30px;
text-align: center;
font-size: 2.5rem;
font-weight: 700;
}
.subtitle {
text-align: center;
color: #666;
margin-bottom: 30px;
font-size: 1.1rem;
}
.features {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 20px;
margin: 30px 0;
}
.feature {
background: linear-gradient(135deg, #667eea, #764ba2);
color: white;
padding: 20px;
border-radius: 15px;
text-align: center;
}
.feature h3 { margin-bottom: 10px; }
.demo {
background: #f8f9fa;
border-radius: 10px;
padding: 20px;
margin: 20px 0;
}
textarea {
width: 100%;
height: 100px;
border: 2px solid #e9ecef;
border-radius: 8px;
padding: 15px;
font-size: 1rem;
margin-bottom: 15px;
}
.voice-select {
width: 100%;
padding: 12px;
border: 2px solid #e9ecef;
border-radius: 8px;
font-size: 1rem;
margin-bottom: 15px;
background: white;
color: #333;
max-height: 200px;
}
.voice-select optgroup {
font-weight: 600;
color: #667eea;
font-size: 1.1rem;
padding: 8px 0;
border-bottom: 1px solid #e9ecef;
}
.voice-select option {
padding: 8px 12px;
font-size: 0.95rem;
}
.controls {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 15px;
margin-bottom: 15px;
}
.slider-group {
background: #f8f9fa;
padding: 15px;
border-radius: 8px;
}
.slider-group label {
display: block;
margin-bottom: 8px;
font-weight: 500;
color: #333;
}
.slider {
width: 100%;
-webkit-appearance: none;
height: 6px;
border-radius: 3px;
background: #e9ecef;
outline: none;
}
.slider::-webkit-slider-thumb {
-webkit-appearance: none;
appearance: none;
width: 20px;
height: 20px;
border-radius: 50%;
background: #667eea;
cursor: pointer;
}
.slider::-moz-range-thumb {
width: 20px;
height: 20px;
border-radius: 50%;
background: #667eea;
cursor: pointer;
}
.speak-btn {
background: linear-gradient(135deg, #28a745, #20c997);
color: white;
border: none;
padding: 15px 30px;
border-radius: 8px;
font-weight: 600;
cursor: pointer;
display: block;
margin: 0 auto;
font-size: 1.1rem;
}
.speak-btn:hover { transform: translateY(-2px); }
.speak-btn:disabled {
opacity: 0.6;
cursor: not-allowed;
transform: none;
}
.status {
text-align: center;
margin-top: 15px;
font-weight: 500;
color: #666;
min-height: 24px;
padding: 8px;
border-radius: 6px;
background: #f8f9fa;
}
.status.error {
background: #f8d7da;
color: #721c24;
border: 1px solid #f5c6cb;
}
.status.success {
background: #d4edda;
color: #155724;
border: 1px solid #c3e6cb;
}
.status.loading {
background: #d1ecf1;
color: #0c5460;
border: 1px solid #bee5eb;
}
.audio-player {
margin-top: 20px;
text-align: center;
}
audio {
width: 100%;
border-radius: 8px;
}
</style>
</head>
<body>
<div class="container">
<h1>🎙️ Microsoft Neural TTS Studio</h1>
<p class="subtitle">Professional Text-to-Speech with 200+ Neural Voices in 30+ Languages</p>
<div class="features">
<div class="feature">
<h3>🌍 30+ Languages</h3>
<p>Dutch, English, French, German, Spanish, Italian, Portuguese, Asian, European and more</p>
</div>
<div class="feature">
<h3>🎤 200+ Voices</h3>
<p>Male, Female, Regional variants with Microsoft neural technology</p>
</div>
<div class="feature">
<h3>⚡ Fast & Free</h3>
<p>No API keys required, instant synthesis, high-quality output</p>
</div>
</div>
<div class="demo">
<h3>Try It Now</h3>
<textarea id="text-input" placeholder="Type your text here...">Hello, this is a demonstration of Microsoft Neural TTS Studio! This is a professional text-to-speech application with 200+ high-quality neural voices in 30+ languages. Try different voices and languages to experience the power of Microsoft neural technology!</textarea>
<select id="voice-select" class="voice-select">
<!-- Dutch Voices -->
<optgroup label="🇳🇱 Dutch">
<option value="nl-NL-FennaNeural">Fenna (Female)</option>
<option value="nl-NL-ColetteNeural">Colette (Female)</option>
<option value="nl-NL-MaartenNeural">Maarten (Male)</option>
<option value="nl-BE-DenaNeural">Dena (Female, Flemish)</option>
<option value="nl-BE-ArnaudNeural">Arnaud (Male, Flemish)</option>
</optgroup>
<!-- English Voices -->
<optgroup label="🇺🇸 English">
<option value="en-US-JennyNeural">Jenny (Female, US)</option>
<option value="en-US-GuyNeural">Guy (Male, US)</option>
<option value="en-US-AriaNeural">Aria (Female, US)</option>
<option value="en-US-DavisNeural">Davis (Male, US)</option>
<option value="en-US-SaraNeural">Sara (Female, US)</option>
<option value="en-US-TonyNeural">Tony (Male, US)</option>
<option value="en-US-NancyNeural">Nancy (Female, US)</option>
<option value="en-US-AmberNeural">Amber (Female, US)</option>
<option value="en-US-AnaNeural">Ana (Female, US)</option>
<option value="en-US-BrandonNeural">Brandon (Male, US)</option>
<option value="en-US-ChristopherNeural">Christopher (Male, US)</option>
<option value="en-US-CoraNeural">Cora (Female, US)</option>
<option value="en-US-ElizabethNeural">Elizabeth (Female, US)</option>
<option value="en-US-EricNeural">Eric (Male, US)</option>
<option value="en-US-MonicaNeural">Monica (Female, US)</option>
<option value="en-GB-SoniaNeural">Sonia (Female, UK)</option>
<option value="en-GB-RyanNeural">Ryan (Male, UK)</option>
<option value="en-GB-LibbyNeural">Libby (Female, UK)</option>
<option value="en-GB-ThomasNeural">Thomas (Male, UK)</option>
<option value="en-GB-MiaNeural">Mia (Female, UK)</option>
<option value="en-GB-EmmaNeural">Emma (Female, UK)</option>
<option value="en-GB-OliverNeural">Oliver (Male, UK)</option>
<option value="en-GB-AvaNeural">Ava (Female, UK)</option>
<option value="en-GB-RebeccaNeural">Rebecca (Female, UK)</option>
<option value="en-AU-NatashaNeural">Natasha (Female, Australian)</option>
<option value="en-AU-WilliamNeural">William (Male, Australian)</option>
<option value="en-AU-DaisyNeural">Daisy (Female, Australian)</option>
<option value="en-CA-ClaraNeural">Clara (Female, Canadian)</option>
<option value="en-CA-LiamNeural">Liam (Male, Canadian)</option>
<option value="en-CA-HeatherNeural">Heather (Female, Canadian)</option>
<option value="en-IN-NeerjaNeural">Neerja (Female, Indian)</option>
<option value="en-IN-PrabhatNeural">Prabhat (Male, Indian)</option>
<option value="en-IN-SwaraNeural">Swara (Female, Indian)</option>
<option value="en-ZA-LeahNeural">Leah (Female, South African)</option>
<option value="en-ZA-LukeNeural">Luke (Male, South African)</option>
</optgroup>
<!-- French Voices -->
<optgroup label="🇫🇷 French">
<option value="fr-FR-DeniseNeural">Denise (Female)</option>
<option value="fr-FR-HenriNeural">Henri (Male)</option>
<option value="fr-FR-AlainNeural">Alain (Male)</option>
<option value="fr-FR-ArielleNeural">Arielle (Female)</option>
<option value="fr-FR-VivienneNeural">Vivienne (Female)</option>
<option value="fr-FR-CelesteNeural">Celeste (Female)</option>
<option value="fr-FR-ClaudineNeural">Claudine (Female)</option>
<option value="fr-FR-JacquelineNeural">Jacqueline (Female)</option>
<option value="fr-FR-JosephineNeural">Josephine (Female)</option>
<option value="fr-FR-YvesNeural">Yves (Male)</option>
<option value="fr-FR-PaulNeural">Paul (Male)</option>
<option value="fr-FR-RemyNeural">Remy (Male)</option>
<option value="fr-FR-MarcelNeural">Marcel (Male)</option>
<option value="fr-FR-LouiseNeural">Louise (Female)</option>
<option value="fr-FR-MargotNeural">Margot (Female)</option>
<option value="fr-BE-CharlineNeural">Charline (Female, Belgian)</option>
<option value="fr-CA-SylvieNeural">Sylvie (Female, Canadian)</option>
<option value="fr-CA-AntoineNeural">Antoine (Male, Canadian)</option>
<option value="fr-CA-JeanNeural">Jean (Male, Canadian)</option>
<option value="fr-CH-AliciaNeural">Alicia (Female, Swiss)</option>
<option value="fr-CH-FabienNeural">Fabien (Male, Swiss)</option>
<option value="fr-CH-RaphaelNeural">Raphael (Male, Swiss)</option>
</optgroup>
<!-- German Voices -->
<optgroup label="🇩🇪 German">
<option value="de-DE-KatjaNeural">Katja (Female)</option>
<option value="de-DE-ConradNeural">Conrad (Male)</option>
<option value="de-DE-AmalaNeural">Amala (Female)</option>
<option value="de-DE-BerndNeural">Bernd (Male)</option>
<option value="de-DE-ChristophNeural">Christoph (Male)</option>
<option value="de-DE-ElkeNeural">Elke (Female)</option>
<option value="de-DE-GiselaNeural">Gisela (Female)</option>
<option value="de-DE-KillianNeural">Killian (Male)</option>
<option value="de-DE-SeraphinaNeural">Seraphina (Female)</option>
<option value="de-DE-LouisaNeural">Louisa (Female)</option>
<option value="de-DE-MajaNeural">Maja (Female)</option>
<option value="de-DE-FlorianNeural">Florian (Male)</option>
<option value="de-DE-SabineNeural">Sabine (Female)</option>
<option value="de-DE-GerhardNeural">Gerhard (Male)</option>
<option value="de-DE-KlaraNeural">Klara (Female)</option>
<option value="de-DE-TanjaNeural">Tanja (Female)</option>
<option value="de-AT-IngridNeural">Ingrid (Female, Austrian)</option>
<option value="de-AT-JonasNeural">Jonas (Male, Austrian)</option>
<option value="de-CH-LeniNeural">Leni (Female, Swiss)</option>
<option value="de-CH-JanNeural">Jan (Male, Swiss)</option>
</optgroup>
<!-- Spanish Voices -->
<optgroup label="🇪🇸 Spanish">
<option value="es-ES-ElviraNeural">Elvira (Female)</option>
<option value="es-ES-AlvaroNeural">Alvaro (Male)</option>
<option value="es-ES-AbrilNeural">Abril (Female)</option>
<option value="es-ES-ArnauNeural">Arnau (Male)</option>
<option value="es-ES-DarioNeural">Dario (Male)</option>
<option value="es-ES-EliasNeural">Elias (Male)</option>
<option value="es-ES-EstrellaNeural">Estrella (Female)</option>
<option value="es-ES-XimenaNeural">Ximena (Female)</option>
<option value="es-ES-LauraNeural">Laura (Female)</option>
<option value="es-ES-IreneNeural">Irene (Female)</option>
<option value="es-ES-SofiaNeural">Sofia (Female)</option>
<option value="es-ES-PabloNeural">Pablo (Male)</option>
<option value="es-ES-ManuelNeural">Manuel (Male)</option>
<option value="es-ES-PedroNeural">Pedro (Male)</option>
<option value="es-MX-DaliaNeural">Dalia (Female, Mexican)</option>
<option value="es-MX-JorgeNeural">Jorge (Male, Mexican)</option>
<option value="es-MX-LuciaNeural">Lucia (Female, Mexican)</option>
<option value="es-AR-AlejandraNeural">Alejandra (Female, Argentine)</option>
<option value="es-AR-CastiNeural">Casti (Male, Argentine)</option>
<option value="es-CO-SalomeNeural">Salome (Female, Colombian)</option>
<option value="es-CO-GonzaloNeural">Gonzalo (Male, Colombian)</option>
</optgroup>
<!-- Italian Voices -->
<optgroup label="🇮🇹 Italian">
<option value="it-IT-ElsaNeural">Elsa (Female)</option>
<option value="it-IT-DiegoNeural">Diego (Male)</option>
<option value="it-IT-FabiolaNeural">Fabiola (Female)</option>
<option value="it-IT-GiuseppeNeural">Giuseppe (Male)</option>
<option value="it-IT-IsabellaNeural">Isabella (Female)</option>
<option value="it-IT-AlessandroNeural">Alessandro (Male)</option>
<option value="it-IT-BenedettaNeural">Benedetta (Female)</option>
<option value="it-IT-FrancescaNeural">Francesca (Female)</option>
<option value="it-IT-MarcoNeural">Marco (Male)</option>
<option value="it-IT-PaolaNeural">Paola (Female)</option>
</optgroup>
<!-- Portuguese Voices -->
<optgroup label="🇧🇷 Portuguese">
<option value="pt-BR-FranciscaNeural">Francisca (Female, Brazilian)</option>
<option value="pt-BR-AntonioNeural">Antonio (Male, Brazilian)</option>
<option value="pt-BR-BrendaNeural">Brenda (Female, Brazilian)</option>
<option value="pt-BR-ValerioNeural">Valerio (Male, Brazilian)</option>
<option value="pt-BR-ThalitaNeural">Thalita (Female, Brazilian)</option>
<option value="pt-BR-YaraNeural">Yara (Female, Brazilian)</option>
<option value="pt-BR-CamilaNeural">Camila (Female, Brazilian)</option>
<option value="pt-BR-JulianoNeural">Juliano (Male, Brazilian)</option>
<option value="pt-BR-LeandroNeural">Leandro (Male, Brazilian)</option>
<option value="pt-BR-NicolauNeural">Nicolau (Male, Brazilian)</option>
<option value="pt-PT-RaquelNeural">Raquel (Female, Portuguese)</option>
<option value="pt-PT-DuarteNeural">Duarte (Male, Portuguese)</option>
<option value="pt-PT-MariaNeural">Maria (Female, Portuguese)</option>
</optgroup>
<!-- Asian Languages -->
<optgroup label="🌏 Asian Languages">
<option value="zh-CN-XiaoxiaoNeural">Xiaoxiao (Female, Chinese)</option>
<option value="zh-CN-YunyangNeural">Yunyang (Male, Chinese)</option>
<option value="zh-CN-XiaoyiNeural">Xiaoyi (Female, Chinese)</option>
<option value="zh-CN-YunjianNeural">Yunjian (Male, Chinese)</option>
<option value="zh-CN-XiaochenNeural">Xiaochen (Female, Chinese)</option>
<option value="zh-CN-XiaoxuanNeural">Xiaoxuan (Female, Chinese)</option>
<option value="zh-CN-XiaomengNeural">Xiaomeng (Female, Chinese)</option>
<option value="zh-CN-XiaoyanNeural">Xiaoyan (Female, Chinese)</option>
<option value="zh-CN-XiaoyouNeural">Xiaoyou (Female, Chinese)</option>
<option value="zh-CN-XiaoyuNeural">Xiaoyu (Female, Chinese)</option>
<option value="zh-CN-HsiaochenNeural">Hsiaochen (Female, Chinese)</option>
<option value="zh-CN-HsiaochengNeural">Hsiaocheng (Male, Chinese)</option>
<option value="zh-CN-HsiaoyuNeural">Hsiaoyu (Female, Chinese)</option>
<option value="ja-JP-NanamiNeural">Nanami (Female, Japanese)</option>
<option value="ja-JP-KeitaNeural">Keita (Male, Japanese)</option>
<option value="ja-JP-AoiNeural">Aoi (Female, Japanese)</option>
<option value="ja-JP-NaomiNeural">Naomi (Female, Japanese)</option>
<option value="ja-JP-DaichiNeural">Daichi (Male, Japanese)</option>
<option value="ja-JP-MasaruNeural">Masaru (Male, Japanese)</option>
<option value="ko-KR-SunHiNeural">SunHi (Female, Korean)</option>
<option value="ko-KR-InJoonNeural">InJoon (Male, Korean)</option>
<option value="ko-KR-BongJinNeural">BongJin (Male, Korean)</option>
<option value="ko-KR-GookMinNeural">GookMin (Male, Korean)</option>
<option value="ko-KR-JiMinNeural">JiMin (Female, Korean)</option>
<option value="ko-KR-SeoHyeonNeural">SeoHyeon (Female, Korean)</option>
<option value="ko-KR-HyunsuNeural">Hyunsu (Male, Korean)</option>
<option value="ko-KR-SoonBokNeural">SoonBok (Male, Korean)</option>
<option value="hi-IN-SwaraNeural">Swara (Female, Hindi)</option>
<option value="hi-IN-MadhurNeural">Madhur (Male, Hindi)</option>
<option value="hi-IN-AnjaliNeural">Anjali (Female, Hindi)</option>
<option value="hi-IN-RajNeural">Raj (Male, Hindi)</option>
<option value="he-IL-AvriNeural">Avri (Male, Hebrew)</option>
<option value="he-IL-HilaNeural">Hila (Female, Hebrew)</option>
<option value="ar-SA-ZariyahNeural">Zariyah (Female, Arabic)</option>
<option value="ar-SA-HamedNeural">Hamed (Male, Arabic)</option>
<option value="ar-SA-FatimaNeural">Fatima (Female, Arabic)</option>
<option value="ar-SA-AliNeural">Ali (Male, Arabic)</option>
<option value="ar-SA-OmarNeural">Omar (Male, Arabic)</option>
</optgroup>
<!-- European Languages -->
<optgroup label="🌍 European Languages">
<option value="pl-PL-ZofiaNeural">Zofia (Female, Polish)</option>
<option value="pl-PL-JacekNeural">Jacek (Male, Polish)</option>
<option value="pl-PL-EwaNeural">Ewa (Female, Polish)</option>
<option value="pl-PL-MarekNeural">Marek (Male, Polish)</option>
<option value="pl-PL-JuliaNeural">Julia (Female, Polish)</option>
<option value="pl-PL-JanNeural">Jan (Male, Polish)</option>
<option value="pl-PL-MajaNeural">Maja (Female, Polish)</option>
<option value="ro-RO-AlinaNeural">Alina (Female, Romanian)</option>
<option value="ro-RO-EmilNeural">Emil (Male, Romanian)</option>
<option value="hu-HU-NoemiNeural">Noemi (Female, Hungarian)</option>
<option value="hu-HU-TamasNeural">Tamas (Male, Hungarian)</option>
<option value="el-GR-AthinaNeural">Athina (Female, Greek)</option>
<option value="el-GR-NestorasNeural">Nestoras (Male, Greek)</option>
<option value="fi-FI-SelmaNeural">Selma (Female, Finnish)</option>
<option value="fi-FI-HarriNeural">Harri (Male, Finnish)</option>
<option value="fi-FI-SeljaNeural">Selja (Female, Finnish)</option>
<option value="sv-SE-SofieNeural">Sofie (Female, Swedish)</option>
<option value="sv-SE-MattiasNeural">Mattias (Male, Swedish)</option>
<option value="sv-SE-AstridNeural">Astrid (Female, Swedish)</option>
<option value="da-DK-ChristelNeural">Christel (Female, Danish)</option>
<option value="da-DK-JeppeNeural">Jeppe (Male, Danish)</option>
<option value="da-DK-MetteNeural">Mette (Female, Danish)</option>
<option value="nb-NO-PernilleNeural">Pernille (Female, Norwegian)</option>
<option value="nb-NO-FinnNeural">Finn (Male, Norwegian)</option>
<option value="nb-NO-IselinNeural">Iselin (Female, Norwegian)</option>
<option value="ru-RU-SvetlanaNeural">Svetlana (Female, Russian)</option>
<option value="ru-RU-DmitryNeural">Dmitry (Male, Russian)</option>
<option value="ru-RU-DariyaNeural">Dariya (Female, Russian)</option>
<option value="ru-RU-PavelNeural">Pavel (Male, Russian)</option>
<option value="tr-TR-EmelNeural">Emel (Female, Turkish)</option>
<option value="tr-TR-AhmetNeural">Ahmet (Male, Turkish)</option>
<option value="tr-TR-SultanNeural">Sultan (Male, Turkish)</option>
</optgroup>
</select>
<div class="controls">
<div class="slider-group">
<label>Speed: <span id="speed-value">+0%</span></label>
<input type="range" id="speed-slider" class="slider" min="-50" max="50" value="0">
</div>
<div class="slider-group">
<label>Pitch: <span id="pitch-value">+0Hz</span></label>
<input type="range" id="pitch-slider" class="slider" min="-50" max="50" value="0">
</div>
</div>
<button class="speak-btn" id="speak-btn" onclick="speak()">🔊 Speak Text</button>
<div class="status" id="status">Ready to speak</div>
<div class="audio-player" id="audio-player" style="display: none;">
<audio id="audio-element" controls></audio>
</div>
</div>
</div>
<script>
// Update slider values
document.getElementById('speed-slider').addEventListener('input', function() {
const value = this.value;
document.getElementById('speed-value').textContent = value >= 0 ? `+${value}%` : `${value}%`;
});
document.getElementById('pitch-slider').addEventListener('input', function() {
const value = this.value;
document.getElementById('pitch-value').textContent = value >= 0 ? `+${value}Hz` : `${value}Hz`;
});
async function speak() {
const text = document.getElementById('text-input').value;
const voice = document.getElementById('voice-select').value;
const speed = document.getElementById('speed-slider').value;
const pitch = document.getElementById('pitch-slider').value;
if (!text.trim()) {
updateStatus('Please enter some text', 'error');
return;
}
if (text.length > 5000) {
updateStatus('Text too long! Maximum 5000 characters allowed.', 'error');
return;
}
const button = document.getElementById('speak-btn');
button.textContent = '⏳ Generating...';
button.disabled = true;
updateStatus('Generating speech...', 'loading');
try {
const response = await fetch('/synthesize', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Accept': 'audio/mpeg'
},
body: JSON.stringify({
text,
voice,
rate: speed >= 0 ? `+${speed}%` : `${speed}%`,
pitch: pitch >= 0 ? `+${pitch}Hz` : `${pitch}Hz`
})
});
if (response.ok) {
const contentType = response.headers.get('content-type');
if (contentType && contentType.includes('audio/mpeg')) {
const audioBlob = await response.blob();
const audioUrl = URL.createObjectURL(audioBlob);
const audioElement = document.getElementById('audio-element');
audioElement.src = audioUrl;
document.getElementById('audio-player').style.display = 'block';
audioElement.play();
updateStatus('Playing audio...', 'success');
} else {
const errorData = await response.json();
updateStatus(`Error: ${errorData.error || 'Unknown error'}`, 'error');
}
} else {
const errorData = await response.json();
updateStatus(`Speech synthesis failed: ${errorData.error || 'Unknown error'}`, 'error');
}
} catch (error) {
updateStatus(`Network error: ${error.message}`, 'error');
console.error('Synthesis error:', error);
} finally {
button.textContent = '🔊 Speak Text';
button.disabled = false;
}
}
function updateStatus(message, type) {
const statusElement = document.getElementById('status');
statusElement.textContent = message;
statusElement.className = 'status';
if (type) {
statusElement.classList.add(type);
}
}
// Auto-play when audio ends
document.getElementById('audio-element').addEventListener('ended', function() {
updateStatus('Ready to speak', 'normal');
});
</script>
</body>
</html>
"""
@app.post("/synthesize")
async def synthesize(request):
try:
data = await request.json()
text = data.get("text", "")
voice = data.get("voice", "en-US-JennyNeural")
rate = data.get("rate", "+0%")
pitch = data.get("pitch", "+0Hz")
if not text:
return {"error": "Text is required"}
# Validate text length (Edge TTS has limits)
if len(text) > 5000:
return {"error": "Text too long. Maximum 5000 characters allowed."}
# Validate voice format
if not voice or not isinstance(voice, str):
return {"error": "Invalid voice format"}
# Create communicate object with error handling
try:
communicate = edge_tts.Communicate(text, voice, rate=rate, pitch=pitch)
except Exception as e:
return {"error": f"Voice initialization failed: {str(e)}"}
# Generate audio with timeout and error handling
try:
audio_data = await communicate.get_audio_data()
if not audio_data:
return {"error": "No audio data generated"}
# Return audio as response
from fastapi.responses import Response
return Response(
content=audio_data,
media_type="audio/mpeg",
headers={
"Content-Disposition": "inline; filename=speech.mp3",
"Cache-Control": "no-cache"
}
)
except Exception as e:
return {"error": f"Audio generation failed: {str(e)}"}
except Exception as e:
return {"error": f"Request processing failed: {str(e)}"}
@app.get("/voices")
async def get_voices():
"""Get available voices for debugging"""
try:
voices = await edge_tts.list_voices()
return {"voices": voices[:50]} # Return first 50 voices for testing
except Exception as e:
return {"error": f"Failed to get voices: {str(e)}"}
@app.get("/test")
async def test_endpoint():
"""Test endpoint to verify server is working"""
return {"status": "ok", "message": "Microsoft Neural TTS Studio is working!"}