vull adaptar aquest comanda del servidor local TTS de Deepin ( que funciona perfectament )al format que mostra la imatge en html: una entrada de text i un botó en lloc d’una gravadora. És possible?
Sí, és totalment possible! Et proporcionaré una solució completa amb HTML, CSS i un backend senzill.
Solució completa:
1. Backend amb Node.js (recomanat):
Crea un fitxer server.js:
const express = require('express');
const { exec } = require('child_process');
const fs = require('fs');
const path = require('path');
const app = express();
const PORT = 3000;
app.use(express.json());
app.use(express.static('public'));
// Endpoint per generar TTS
app.post('/tts', (req, res) => {
const text = req.body.text;
const outputFile = `public/audio/tts_${Date.now()}.wav`;
// Comanda TTS (adapta-la al teu sistema Deepin)
// Opció 1: amb espeak
const command = `espeak -w "${outputFile}" "${text}"`;
// Opció 2: amb festival (descomenta si ho prefereixes)
// const command = `echo "${text}" | festival --tts --output="${outputFile}"`;
exec(command, (error) => {
if (error) {
return res.status(500).json({ error: error.message });
}
res.json({ audioUrl: outputFile });
});
});
app.listen(PORT, () => {
console.log(`Servidor TTS executant-se a http://localhost:${PORT}`);
});
2. Frontend HTML (fitxer public/index.html):
<!DOCTYPE html>
<html lang="ca">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>TTS Deepin</title>
<style>
body {
font-family: Arial, sans-serif;
max-width: 800px;
margin: 50px auto;
padding: 20px;
background-color: #f5f5f5;
}
.container {
background: white;
padding: 30px;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
}
h1 {
color: #333;
margin-bottom: 20px;
}
textarea {
width: 100%;
height: 150px;
padding: 12px;
border: 2px solid #ddd;
border-radius: 4px;
font-size: 16px;
resize: vertical;
box-sizing: border-box;
}
textarea:focus {
outline: none;
border-color: #4CAF50;
}
button {
margin-top: 15px;
padding: 12px 30px;
background-color: #4CAF50;
color: white;
border: none;
border-radius: 4px;
font-size: 16px;
cursor: pointer;
transition: background 0.3s;
}
button:hover {
background-color: #45a049;
}
button:disabled {
background-color: #cccccc;
cursor: not-allowed;
}
.player-container {
margin-top: 30px;
padding: 20px;
background: #f9f9f9;
border-radius: 4px;
display: none;
}
.player-container.show {
display: block;
}
audio {
width: 100%;
margin-bottom: 15px;
}
.download-link {
color: #4CAF50;
text-decoration: none;
font-weight: bold;
}
.download-link:hover {
text-decoration: underline;
}
.loading {
display: none;
margin-top: 15px;
color: #666;
}
.loading.show {
display: block;
}
</style>
</head>
<body>
<div class="container">
<h1>Text a Veu (TTS)</h1>
<textarea id="textInput" placeholder="Escriu el text aquí..."></textarea>
<br>
<button id="generateBtn" onclick="generateTTS()">Generar Àudio</button>
<div class="loading" id="loading">
Generant àudio...
</div>
<div class="player-container" id="playerContainer">
<audio id="audioPlayer" controls></audio>
<a id="downloadLink" class="download-link" download="tts.wav">
Descarregar àudio
</a>
</div>
</div>
<script>
async function generateTTS() {
const text = document.getElementById('textInput').value;
const btn = document.getElementById('generateBtn');
const loading = document.getElementById('loading');
const playerContainer = document.getElementById('playerContainer');
const audioPlayer = document.getElementById('audioPlayer');
const downloadLink = document.getElementById('downloadLink');
if (!text.trim()) {
alert('Si us plau, escriu un text');
return;
}
// Mostrar estat de càrrega
btn.disabled = true;
loading.classList.add('show');
playerContainer.classList.remove('show');
try {
const response = await fetch('/tts', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ text: text })
});
if (!response.ok) {
throw new Error('Error en generar l\'àudio');
}
const data = await response.json();
// Configurar el reproductor
audioPlayer.src = data.audioUrl;
downloadLink.href = data.audioUrl;
// Mostrar el reproductor
playerContainer.classList.add('show');
audioPlayer.play();
} catch (error) {
alert('Error: ' + error.message);
} finally {
btn.disabled = false;
loading.classList.remove('show');
}
}
</script>
</body>
</html>
3. Instal·lació i execució:
# Instal·lar dependències npm init -y npm install express # Crear carpeta per als àudios mkdir -p public/audio # Executar el servidor node server.js
4. Comandes TTS alternatives (adapta al teu sistema):
Amb espeak (el més comú a Linux):
espeak -w output.wav "text"
Amb festival:
echo "text" | festival --tts --output=output.wav
Amb speech-dispatcher (natiu de Deepin):
spd-say -o output.wav "text"
Característiques:
Perfecte! Si ja tens Piper TTS funcionant a 127.0.0.1:10200, la solució és molt més senzilla: només cal fer de pont entre el navegador i el teu servidor Piper.
Solució adaptada a Piper TTS
1. server.js (servidor intermediari Node.js)
const express = require('express');
const path = require('path');
const app = express();
const PORT = 3000;
const PIPER_URL = 'http://127.0.0.1:10200/api/tts';
app.use(express.json());
app.use(express.static('public'));
// Endpoint que consulta Piper TTS
app.post('/tts', async (req, res) => {
const text = req.body.text;
if (!text || !text.trim()) {
return res.status(400).json({ error: 'Text buit' });
}
try {
const response = await fetch(PIPER_URL, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ text: text })
});
if (!response.ok) {
throw new Error(`Piper TTS ha retornat ${response.status}`);
}
// Piper retorna l'àudio WAV directament
const audioBuffer = await response.arrayBuffer();
res.set({
'Content-Type': 'audio/wav',
'Content-Length': audioBuffer.byteLength
});
res.send(Buffer.from(audioBuffer));
} catch (error) {
console.error('Error amb Piper TTS:', error.message);
res.status(500).json({ error: error.message });
}
});
app.listen(PORT, () => {
console.log(`✅ Interfície TTS disponible a http://localhost:${PORT}`);
console.log(`🔊 Piper TTS a ${PIPER_URL}`);
});
2. public/index.html (la interfície que has demanat)
<!DOCTYPE html>
<html lang="ca">
<head>
<meta charset="UTF-8">
<title>TTS - Veu Ona (Piper)</title>
<style>
* { box-sizing: border-box; }
body {
font-family: 'Segoe UI', Arial, sans-serif;
max-width: 800px;
margin: 40px auto;
padding: 20px;
background: #f0f2f5;
}
.card {
background: white;
padding: 30px;
border-radius: 10px;
box-shadow: 0 2px 12px rgba(0,0,0,0.08);
}
h1 { color: #1a1a1a; margin-top: 0; }
.subtitle { color: #666; font-size: 14px; margin-bottom: 20px; }
textarea {
width: 100%;
height: 140px;
padding: 12px;
border: 2px solid #e0e0e0;
border-radius: 6px;
font-size: 16px;
resize: vertical;
font-family: inherit;
}
textarea:focus {
outline: none;
border-color: #4a90e2;
}
button {
margin-top: 15px;
padding: 12px 28px;
background: #4a90e2;
color: white;
border: none;
border-radius: 6px;
font-size: 15px;
font-weight: 600;
cursor: pointer;
transition: background 0.2s;
}
button:hover:not(:disabled) { background: #357abd; }
button:disabled {
background: #b0c4de;
cursor: not-allowed;
}
.status {
margin-top: 15px;
color: #666;
font-size: 14px;
min-height: 20px;
}
.player-box {
margin-top: 25px;
padding: 20px;
background: #f8f9fa;
border-radius: 8px;
display: none;
}
.player-box.visible { display: block; }
audio { width: 100%; margin-bottom: 12px; }
.download-link {
color: #4a90e2;
text-decoration: none;
font-weight: 600;
font-size: 14px;
}
.download-link:hover { text-decoration: underline; }
</style>
</head>
<body>
<div class="card">
<h1>🔊 Text a Veu</h1>
<div class="subtitle">Veu: Ona (català) · Motor: Piper TTS</div>
<textarea id="textInput" placeholder="Escriu aquí el text que vols escoltar...">L'arc de Sant Martí o arc del cel és un fenomen meteorològic òptic produït per la reflexió, refracció i dispersió de la llum.</textarea>
<br>
<button id="btn" onclick="speak()">▶ Generar i reproduir</button>
<div class="status" id="status"></div>
<div class="player-box" id="playerBox">
<audio id="audio" controls></audio>
<a id="download" class="download-link" download="tts.wav"> Descarregar àudio</a>
</div>
</div>
<script>
async function speak() {
const text = document.getElementById('textInput').value.trim();
const btn = document.getElementById('btn');
const status = document.getElementById('status');
const playerBox = document.getElementById('playerBox');
const audio = document.getElementById('audio');
const download = document.getElementById('download');
if (!text) {
status.textContent = '⚠ Escriu un text primer.';
return;
}
btn.disabled = true;
status.textContent = '⏳ Generant àudio amb Piper TTS...';
playerBox.classList.remove('visible');
try {
const res = await fetch('/tts', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ text })
});
if (!res.ok) throw new Error(`Error ${res.status}`);
const blob = await res.blob();
const url = URL.createObjectURL(blob);
audio.src = url;
download.href = url;
playerBox.classList.add('visible');
status.textContent = '✅ Àudio generat correctament.';
audio.play();
} catch (err) {
status.textContent = '❌ Error: ' + err.message;
} finally {
btn.disabled = false;
}
}
</script>
</body>
</html>
3. Execució
mkdir -p public # (posa index.html dins de public/) npm init -y npm install express node server.js
Obre http://localhost:3000 i ja ho tindràs.
Com funciona el flux
[Navegador] ─POST /tts─▶ [Node.js :3000] ─POST /api/tts─▶ [Piper TTS :10200]
▲ │ │
──── audio/wav ─────────────────── audio/wav ───────────────┘
Notes importants
- Si el teu Piper TTS usa un endpoint diferent (per exemple
GET /api/tts?text=...en lloc de POST), només has de canviar la línia delfetchalserver.js. Pots comprovar-ho amb:curl -X POST http://127.0.0.1:10200/api/tts -H "Content-Type: application/json" -d '{"text":"Hola"}' --output test.wavSi això funciona, l’endpoint és correcte. - CORS: com que el Node.js fa la petició al Piper des del servidor (no des del navegador), no hi ha problemes de CORS.
- Sense fitxers temporals: l’àudio es manté en memòria (Blob URL), així que no omple el disc.
Vols que afegeixi alguna cosa més, com selector de veu (si en tens més d’una), control de velocitat, o historial de frases?
✅ Input de text amb textarea
✅ Botó per generar
✅ Reproductor d’àudio amb controls (play, pausa, volum)
✅ Barra de progrés temporal
✅ Botó de descàrrega
✅ Disseny responsive
Accedeix a http://localhost:3000 i prova-ho! 🎵