Aquest projecte proporciona una aplicació web lleugera per convertir text en veu en diferents idiomes, mitjançant tecnologies de Microsoft (Edge TTS) i Google (gTTS).
📁 Estructura del projecte
/edge_tts_clean/
│
├── app.py (o serve_gtts.py) ← Servidor Flask que processa les peticions web
├── generar_gtts.py ← Conversió de text a veu amb Google TTS (gTTS)
│
├── /audio/ ← Conté els fitxers d’àudio generats (tts.mp3)
│
├── /venv/ ← Entorn virtual Python (Flask, edge_tts, gtts, etc.)
│
├── index.php ← Interfície web: formulari per introduir text i seleccionar veu
│ (executa generar_gtts.py o generar_tts.py segons la veu escollida)
└── ...
Estr/00-sapi_tts/
├── exec_script_py.php ← Crida el Python
├── generar_tts.py ← Script Python que genera l'àudio
| des de Synology
├── index.php ← Interfície web
└── tts.mp3 ← Fitxer generatuctura
import sys
import os
# 🔧 Elimina rutes locals que poden interferir
sys.path = [p for p in sys.path if ".local" not in p and "urqtejmi" not in p]
print("🔍 sys.path netejat:")
for p in sys.path:
print(" -", p)
# 📦 Comprovació de la importació
try:
import gtts
print("📦 gtts importat des de:", getattr(gtts, '__file__', '❌ No definit'))
from gtts import gTTS
print("✅ gTTS importat correctament")
except Exception as e:
print("❌ Error important gTTS:", e)
raise
# 📄 Preparació del fitxer de sortida
base_dir = os.path.dirname(os.path.abspath(__file__))
output_path = os.path.join(base_dir, "tts.mp3")
# 📝 Recollir text i veu
text = sys.argv[1] if len(sys.argv) > 1 else "Text per defecte"
voice = sys.argv[2] if len(sys.argv) > 2 else "default"
print(f"▶ Text: {text}")
print(f"▶ Veu seleccionada: {voice} (no utilitzada per gTTS)")
print(f"▶ Fitxer a guardar: {output_path}")
# 🔊 Generar MP3
tts = gTTS(text=text, lang='ca')
tts.save(output_path)
exec_script_py.php
<?php
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$textRaw = $_POST['text'] ?? '';
$voiceRaw = $_POST['voice'] ?? '';
$text = escapeshellarg($textRaw);
$voice = escapeshellarg($voiceRaw);
// ✅ Ruta al Python de l'entorn virtual
$python = '/volume2/web/00-sapi_tts/venv/bin/python';
// ✅ Comanda per executar el script Python
$command = "$python generar_tts.py $text $voice 2>&1";
// 🔁 Executa el script i captura la sortida
$output = shell_exec($command);
echo "<pre>📤 Comanda executada:\n$command\n\n📄 Sortida:\n$output</pre>";
// ❌ Si no s'ha generat l'àudio, redirigeix amb error
if (!file_exists("tts.mp3")) {
$error = "No s'ha pogut generar l'àudio. Sortida: $output";
header("Location: index.php?error=" . urlencode($error) . "&text=" . urlencode($textRaw) . "&voice=" . urlencode($voiceRaw));
exit();
}
// ✅ Si tot ha anat bé, redirigeix amb els valors conservats
header("Location: index.php?text=" . urlencode($textRaw) . "&voice=" . urlencode($voiceRaw));
exit();
}
?>
🛠️ Historial de passos per solucionar l’error amb gtts
1. Diagnòstic inicial
El teu script Python donava aquest error:
ImportError: cannot import name 'gTTS' from 'gtts' (unknown location)
El mòdul gtts semblava instal·lat, però gtts.__file__ retornava None.
2. Depuració del sistema
Vam comprovar que hi havia múltiples versions de gtts instal·lades:
Una a /usr/lib/python3.8/site-packages ✅
Una altra a ~/.local/lib/python3.8/site-packages ❌ (interferia)
Això va assegurar que el servidor web executés el Python correcte, amb el mòdul gtts funcional.
6. Èxit final
El fitxer tts.mp3 es va generar correctament.
El reproductor d’àudio es va mostrar a la pàgina.
El text “bona nit” es va convertir en veu 🎉
🔜 Què queda pendent?
La veu no sona com la del “Jordi” de Vocalizer: això és perquè gtts fa servir la veu de Google Translate, no veus locals com Vocalizer.
Si vols fer servir veus com la del Jordi, caldrà integrar motors com:
eSpeak NG
Festival
Vocalizer TTS (si tens llicència)
Microsoft Azure, Google Cloud TTS, etc.
Vols que demà explorem com fer servir una veu real com la del Jordi amb un motor TTS local o en el núvol? O vols que et prepari una comparativa de motors TTS amb veus catalanes?
/edge_tts
├── exec_script_py.php ← Script PHP que crida Python des de la web
├── generar_tts.py ← Genera l'àudio amb Edge TTS
├── generar_gtts.py ← Versió que utilitza Google TTS
├── generar_veu.py ← Alternativa o prova per generar veu
├── diagnostic_edge_tts.py ← Diagnòstic o comprovació del servei TTS
├── index.php ← Interfície web principal del projecte
├── tts.mp3 ← Fitxer d’àudio generat (resultat final)
├──
├── env_tts/ ← Entorn virtual Python dedicat a TTS
├── venv/ ← Possible entorn virtual duplicat
├── venv_web/ ← Potser creat per a la web; revisa si s’usa
└── edge_tts/ ← Pot contenir recursos o mòduls complementaris
Entorn
la carpeta té crear un entorn virtual amb una versió específica de Python especifica:
C:\
├── python\
│ ├── tts-edge.py ← Generació amb veus EDGE (Aria, Herena…)
│ ├── tts-to-wav.py ← Generació amb veus SAPI5 (Zira, Jordi…)
│ └── list-voices.py ← Llista combinada de veus per al menú
├── AppServ\
│ └── www\
│ └── tts\
│ ├── index.php ← Interfície web principal
│ ├── tts.wav ← Fitxer d’àudio generat
│ └── log.txt ← Registre de veus i execucions
tts-edge.py
import sys
import asyncio
from edge_tts import Communicate
async def main():
if len(sys.argv) < 3:
print("❌ Cal passar el text i la veu com a paràmetres.")
return
text = sys.argv[1]
voice = sys.argv[2]
output = "C:/AppServ/www/tts/tts.wav"
try:
communicate = Communicate(text=text, voice=voice)
await communicate.save(output)
print(f"✅ Fitxer generat: {output}")
with open("C:/AppServ/www/tts/log.txt", "a", encoding="utf-8") as log:
log.write(f"[EDGE] {voice} → {output}\n")
except Exception as e:
print(f"❌ Error: {e}")
asyncio.run(main())
tts-to-wav.py
import sys
import os
import comtypes.client
def main():
if len(sys.argv) < 3:
print("❌ Cal passar el text i la veu com a paràmetres.")
return
text = sys.argv[1]
voice_name = sys.argv[2]
output = "C:/AppServ/www/tts/tts.wav"
try:
sapi = comtypes.client.CreateObject("SAPI.SpVoice")
stream = comtypes.client.CreateObject("SAPI.SpFileStream")
from comtypes.gen import SpeechLib
for token in sapi.GetVoices():
if voice_name in token.GetDescription():
sapi.Voice = token
break
else:
print(f"❌ Veu no trobada: {voice_name}")
return
stream.Open(output, SpeechLib.SSFMCreateForWrite)
sapi.AudioOutputStream = stream
sapi.Speak(text)
stream.Close()
print(f"✅ Fitxer generat: {output}")
with open("C:/AppServ/www/tts/log.txt", "a", encoding="utf-8") as log:
log.write(f"[SAPI5] {voice_name} → {output}\n")
except Exception as e:
print(f"❌ Error: {e}")
if __name__ == "__main__":
main()
list-voices.py
import comtypes.client
import subprocess
import json
def get_sapi5_voices():
sapi = comtypes.client.CreateObject("SAPI.SpVoice")
voices = []
for token in sapi.GetVoices():
name = token.GetDescription()
voices.append(f"SAPI5|{name}")
return voices
def get_edge_voices():
try:
subprocess.run(["edge-tts", "--list-voices", "--write-json", "voices.json"], capture_output=True, text=True)
with open("voices.json", "r", encoding="utf-8") as f:
data = json.load(f)
return [f"EDGE|{v['ShortName']}" for v in data if v["Locale"].startswith("ca") or v["Locale"].startswith("en")]
except Exception as e:
print(f"❌ Error obtenint veus EDGE: {e}")
return []
if __name__ == "__main__":
all_voices = get_sapi5_voices() + get_edge_voices()
for voice in all_voices:
print(voice)
index.php
Ja el tens actualitzat per cridar els scripts Python i mostrar el reproductor, selector de veus i registre.
erfecte, Miquel. Ho deixem aquí, doncs, amb tot el projecte ben estructurat i funcionant amb:
✔️ Veus SAPI5 i EDGE
✔️ Generació d’àudio amb Python
✔️ Interfície web amb menú dinàmic
✔️ Registre de veus i execucions (log.txt)
Tens una base sòlida i extensible. Quan vulguis continuar — ja sigui per afegir suport a .mp3, fer-ho portable, o penjar-ho a un servidor — només cal que em diguis “tornem-hi”.
Mentrestant, et mereixes una pausa i un bon brindis. Salut! 🥂
Vols que et generi un petit banner gràfic per al teu projecte amb el nom “Projecte Veus amb Copilot”? Podria quedar bé a la capçalera de la web.
Un fitxer .ps1 és un script de PowerShell, el llenguatge de línia de comandes i automatització de Windows. S’utilitza per executar ordres, scripts i automatitzar tasques del sistema.
foreach ($lang in $languageTags) { Write-Host “🔄 Processant $lang…” -ForegroundColor Cyan try { # Afegeix l’idioma si no hi és if (-not ($currentLangs -contains $lang)) { Write-Host “➕ Afegint idioma $lang…” $langList = Get-WinUserLanguageList $langList.Add($lang) Set-WinUserLanguageList $langList -Force } else { Write-Host “✔️ L’idioma $lang ja està instal·lat.” }
# Comprova si la veu bàsica ja està instal·lada
$capability = "Language.$lang~Basic~~~"
$installed = Get-WindowsCapability -Online | Where-Object { $_.Name -eq $capability }
if ($installed.State -ne "Installed") {
Write-Host "⬇️ Instal·lant veu bàsica per a $lang..."
Add-WindowsCapability -Online -Name $capability -ErrorAction Stop
} else {
Write-Host "✔️ Veu bàsica ja instal·lada per a $lang."
}
# Instal·la reconeixement de veu si no hi és
$speechCap = "Language.$lang~Speech~~~"
$speechInstalled = Get-WindowsCapability -Online | Where-Object { $_.Name -eq $speechCap }
if ($speechInstalled.State -ne "Installed") {
Write-Host "🎙️ Instal·lant reconeixement de veu per a $lang..."
Add-WindowsCapability -Online -Name $speechCap -ErrorAction SilentlyContinue
} else {
Write-Host "✔️ Reconeixement de veu ja instal·lat per a $lang."
}
} catch {
Write-Warning ("Error instal·lant per a $lang: " + $_.Exception.Message)
}
# Carrega la biblioteca TTS clàssica
Add-Type -AssemblyName System.Speech
# Crea un objecte per accedir al sintetitzador de veu
$voiceSynth = New-Object System.Speech.Synthesis.SpeechSynthesizer
# Obtenir totes les veus instal·lades
$voices = $voiceSynth.GetInstalledVoices()
# Mostra informació detallada de cada veu
foreach ($v in $voices) {
[PSCustomObject]@{
Name = $v.VoiceInfo.Name
Culture = $v.VoiceInfo.Culture
Gender = $v.VoiceInfo.Gender
Age = $v.VoiceInfo.Age
Description = $v.VoiceInfo.Description
Attributes = $v.VoiceInfo.OtherProperties
}
}
PS C:\Users\urqte> # Carrega la biblioteca TTS clàssica PS C:\Users\urqte> Add-Type -AssemblyName System.Speech PS C:\Users\urqte> PS C:\Users\urqte> # Crea un objecte per accedir al sintetitzador de veu PS C:\Users\urqte> $voiceSynth = New-Object System.Speech.Synthesis.SpeechSynthesizer PS C:\Users\urqte> PS C:\Users\urqte> # Obtenir totes les veus instal·lades PS C:\Users\urqte> $voices = $voiceSynth.GetInstalledVoices() PS C:\Users\urqte> PS C:\Users\urqte> # Mostra informació detallada de cada veu PS C:\Users\urqte> foreach ($v in $voices) {
[PSCustomObject]@{
Name = $v.VoiceInfo.Name
Culture = $v.VoiceInfo.Culture
Gender = $v.VoiceInfo.Gender
Age = $v.VoiceInfo.Age
Description = $v.VoiceInfo.Description
Attributes = $v.VoiceInfo.OtherProperties
}
}
1 Name : Microsoft Helena Desktop Culture : es-ES Gender : Female Age : Adult Description : Microsoft Helena Desktop – Spanish (Spain) Attributes :
2 Name : Microsoft Hazel Desktop Culture : en-GB Gender : Female Age : Adult Description : Microsoft Hazel Desktop – English (Great Britain) Attributes :
3 Name : Microsoft David Desktop Culture : en-US Gender : Male Age : Adult Description : Microsoft David Desktop – English (United States) Attributes :
4 Name : Microsoft Zira Desktop Culture : en-US Gender : Female Age : Adult Description : Microsoft Zira Desktop – English (United States) Attributes :
5 Name : Microsoft Hedda Desktop Culture : de-DE Gender : Female Age : Adult Description : Microsoft Hedda Desktop – German Attributes :
6 Name : Vocalizer Expressive Jordi Harpo 22kHz Culture : ca-ES Gender : Male Age : Adult Description : Vocalizer Expressive Jordi Harpo 22kHz Attributes :
$proves = @( @{ Nom = “Microsoft Helena Desktop”; Missatge = “Hola! Esta es la voz española de Microsoft Helena.” }, @{ Nom = “Microsoft Hazel Desktop”; Missatge = “Hello! This is the British English voice of Microsoft Hazel.” }, @{ Nom = “Microsoft David Desktop”; Missatge = “Hi there! I’m Microsoft David, the American English voice.” }, @{ Nom = “Microsoft Zira Desktop”; Missatge = “Welcome! I am Zira, the female voice for American English.” }, @{ Nom = “Microsoft Hedda Desktop”; Missatge = “Hallo! Ich bin Hedda, die deutsche Stimme von Microsoft.” }, @{ Nom = “Vocalizer Expressive Jordi Harpo 22kHz”; Missatge = “Bon dia! Sóc en Jordi, la veu en català del sistema.” } )
foreach ($prova in $proves) { Write-Host “`n🔊 Prova amb veu: $($prova.Nom)” -ForegroundColor Cyan try { $tts.SelectVoice($prova.Nom) $tts.Speak($prova.Missatge) } catch { Write-Warning “⚠️ No s’ha pogut trobar o usar la veu: $($prova.Nom)” } }