Commit 8b8c37b8 authored by salma's avatar salma

TTS web interface connected to the new RVC pipeline

parent e2d1bde7
FROM python:3.11-slim
WORKDIR /app
# Install dependencies
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# Copy application
COPY main.py .
# Run the application
CMD ["python", "main.py"]
\ No newline at end of file
{
"schemaVersion": 2,
"dockerfilePath": "./Dockerfile"
}
from fastapi import FastAPI, HTTPException
from fastapi.responses import HTMLResponse, StreamingResponse
from pydantic import BaseModel
import httpx
import io
import os
app = FastAPI()
# ==========================================
# 1. HTML FRONTEND
# ==========================================
HTML_CONTENT = """
<!DOCTYPE html>
<html lang="ar" dir="rtl">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>تحويل النص إلى صوت</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
display: flex; justify-content: center; align-items: center; padding: 20px;
}
.container {
background: white; border-radius: 20px;
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
padding: 40px; max-width: 600px; width: 100%;
}
h1 { color: #667eea; margin-bottom: 30px; text-align: center; font-size: 2em; }
textarea {
width: 100%; min-height: 200px; padding: 15px;
border: 2px solid #e0e0e0; border-radius: 10px;
font-size: 16px; font-family: inherit; resize: vertical; margin-bottom: 20px;
}
textarea:focus { outline: none; border-color: #667eea; }
/* NEW: Speed Control Styles */
.control-group { margin-bottom: 20px; display: flex; align-items: center; gap: 10px; }
.control-group label { font-weight: bold; color: #444; }
.control-group input {
padding: 10px; border: 2px solid #e0e0e0; border-radius: 10px; width: 100px; font-size: 16px;
}
.button-group { display: flex; gap: 15px; margin-top: 10px; }
button {
flex: 1; padding: 15px 30px; border: none; border-radius: 10px;
font-size: 16px; font-weight: bold; cursor: pointer; transition: all 0.3s;
}
#generateBtn { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; }
#generateBtn:disabled { background: #ccc; cursor: not-allowed; }
#downloadBtn { background: #10b981; color: white; display: none; }
audio { width: 100%; margin-top: 20px; display: none; }
.status { margin-top: 20px; padding: 15px; border-radius: 10px; text-align: center; font-weight: bold; display: none; }
.status.loading { background: #fef3c7; color: #92400e; display: block; }
.status.success { background: #d1fae5; color: #065f46; display: block; }
.status.error { background: #fee2e2; color: #991b1b; display: block; }
.spinner {
display: inline-block; width: 16px; height: 16px;
border: 3px solid #92400e; border-top-color: transparent;
border-radius: 50%; animation: spin 1s linear infinite; margin-left: 10px;
}
@keyframes spin { to { transform: rotate(360deg); } }
</style>
</head>
<body>
<div class="container">
<h1>🎙️ تحويل النص إلى صوت</h1>
<textarea id="textInput" placeholder="اكتب النص العربي هنا...">مرحباً بك في خدمة تحويل النص إلى صوت.</textarea>
<!-- NEW: Speed Input -->
<div class="control-group">
<label for="speedInput">سرعة الصوت (1.0 = عادي):</label>
<input type="number" id="speedInput" value="1.0" min="0.25" max="4.0" step="0.05">
</div>
<div class="button-group">
<button id="generateBtn" onclick="generateAudio()">توليد الصوت</button>
<button id="downloadBtn" onclick="downloadAudio()">تحميل الملف</button>
</div>
<audio id="audioPlayer" controls></audio>
<div id="status" class="status"></div>
</div>
<script>
let audioBlob = null;
async function generateAudio() {
const text = document.getElementById('textInput').value.trim();
// NEW: Get speed value
const speed = parseFloat(document.getElementById('speedInput').value);
const generateBtn = document.getElementById('generateBtn');
const downloadBtn = document.getElementById('downloadBtn');
const audioPlayer = document.getElementById('audioPlayer');
if (!text) {
showStatus('error', 'الرجاء إدخال نص');
return;
}
generateBtn.disabled = true;
downloadBtn.style.display = 'none';
audioPlayer.style.display = 'none';
audioPlayer.src = '';
showStatus('loading', 'جاري الاتصال بالسيرفر...');
try {
// NEW: Send text AND speed
const response = await fetch('/synthesize', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
text: text,
speed: speed
})
});
if (!response.ok) {
const err = await response.json();
throw new Error(err.detail || 'فشل في توليد الصوت');
}
audioBlob = await response.blob();
const audioUrl = URL.createObjectURL(audioBlob);
audioPlayer.src = audioUrl;
audioPlayer.style.display = 'block';
audioPlayer.play();
showStatus('success', '✓ تم التوليد بنجاح!');
downloadBtn.style.display = 'block';
} catch (error) {
showStatus('error', '❌ حدث خطأ: ' + error.message);
} finally {
generateBtn.disabled = false;
}
}
function downloadAudio() {
if (!audioBlob) return;
const url = URL.createObjectURL(audioBlob);
const a = document.createElement('a');
a.href = url;
a.download = 'arabic_speech.wav';
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
}
function showStatus(type, message) {
const status = document.getElementById('status');
status.className = 'status ' + type;
if (type === 'loading') status.innerHTML = message + '<span class="spinner"></span>';
else status.textContent = message;
}
</script>
</body>
</html>
"""
# ==========================================
# 2. FASTAPI APP
# ==========================================
class TextInput(BaseModel):
text: str
speed: float = 1.0 # <--- NEW: Added speed parameter
@app.get("/", response_class=HTMLResponse)
async def read_root():
return HTML_CONTENT
@app.get("/health")
async def health_check():
return {"status": "healthy"}
@app.post("/synthesize")
async def synthesize(input_data: TextInput):
"""
Directly forwards text to the external API without modification.
"""
try:
# Configuration
external_url = "http://ec2-18-193-226-85.eu-central-1.compute.amazonaws.com:5000/generate_audio"
# Raw payload
payload = {
"text": input_data.text,
"speed": input_data.speed # <--- NEW: Forwarding speed to External API
}
# Call the external API (Timeout set to 60s for stability)
async with httpx.AsyncClient(timeout=60.0) as client:
response = await client.post(external_url, json=payload)
if response.status_code != 200:
print(f"External API Error: {response.text}")
raise HTTPException(status_code=response.status_code, detail="External TTS API failed")
# Stream result
return StreamingResponse(
io.BytesIO(response.content),
media_type="audio/wav",
headers={
"Content-Disposition": "attachment; filename=arabic_speech.wav"
}
)
except httpx.TimeoutException:
raise HTTPException(status_code=504, detail="TTS API took too long (Timeout)")
except Exception as e:
print(f"Server Error: {str(e)}")
raise HTTPException(status_code=500, detail=str(e))
if __name__ == "__main__":
import uvicorn
print("Starting server on http://localhost:80")
uvicorn.run(app, host="0.0.0.0", port=80)
\ No newline at end of file
fastapi
uvicorn[standard]
httpx
pydantic
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment