Edge & Google TTS Web App

Edge & Google TTS Web App

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)
└── ...


(venv_web) root@Synology_sec:/volume1/web/edge_tts_clean# dir
total 48
drwxrwxr-x+ 1 urqtejmi http    176 Jul 12 16:33 .
dr-xr-xr-x+ 1 root     root   1780 Jul 12 12:32 ..
dr-xr-xr-x+ 1 urqtejmi users     0 Jul 12 12:37 app
dr-xr-xr-x+ 1 urqtejmi users   420 Jul 12 13:56 audio
-r-xr-xr-x+ 1 urqtejmi users   603 Jul 12 12:36 estructura.php
-r-xr-xr-x+ 1 urqtejmi users   531 Jul 12 16:34 generar_gtts.py
-r-xr-xr-x+ 1 root     root  15984 Jul 12 13:37 hola.mp3
-r-xr-xr-x+ 1 urqtejmi users  3344 Jul 12 17:07 index.php
-r-xr-xr-x+ 1 urqtejmi users  3723 Jul 12 15:48 serve_tts.py
-r-xr-xr-x  1 urqtejmi users   420 Jul 12 13:35 setup.sh
-rwxrwxr-x+ 1 http     http  10224 Jul 12 17:10 tts.mp3
dr-xr-xr-x+ 1 root     root     56 Jul 12 13:31 venv
dr-xr-xr-x+ 1 urqtejmi users     0 Jul 12 12:37 web
(venv_web) root@Synology_sec:/volume1/web/edge_tts_clean#

✨ Característiques

  • Conversió de text a veu en català, castellà, anglès i francès
  • Suport per a veus de Microsoft Edge i Google
  • Interfície web simple (Flask) amb selector de veu
  • Opcionalment integrable amb PHP (index.php)

⚙️ Requisits

  • Python 3.9
  • Entorn virtual creat amb python -m venv venv

Paquets necessaris:

pip install flask edge-tts gtts

🚀 Execució

  1. Activa l’entorn virtual:
source venv/bin/activate
  1. Inicia el servidor:
python serve_gtts.py
  1. Accedeix a l’aplicació web:
http://<IP-del-servidor>:8000

🌍 Veus disponibles

Català

  • gtts-ca → Google TTS (Català)
  • ca-ES-JoanaNeural → Microsoft Edge TTS

Castellà

  • es-ES-ElviraNeural
  • es-ES-AlvaroNeural

Anglès

  • en-US-JennyNeural
  • en-GB-RyanNeural

Francès

  • fr-FR-DeniseNeural
  • fr-FR-HenriNeural

📄 Fitxers clau

  • serve_gtts.py: Aplicació Flask que gestiona la interfície i genera l’àudio.
  • generar_tts.py: Utilitza edge-tts per generar veu.
  • generar_gtts.py: Utilitza gtts per generar veu.
  • index.php: Versió alternativa amb PHP per entorns web que ho requereixin.

🚫 Notes de seguretat

  • Valida les entrades si s’exposa públicament.
  • Revisa permisos d’escriptura a la carpeta audio/.

📍 Desenvolupat a

  • Synology NAS (amb Python i entorns virtuals)
  • Accés LAN via IP local (p. ex. http://192.168.0.49:8000)

🎓 Llicència

Projecte privat per a finalitats educatives o de prova. No distribuïble com a servei comercial sense llicències de veus.


✏️ Contacte

Per a preguntes o ampliacions: [Afegeix les dades de contacte si cal]

Publicat dins de Veus

Text to Speech (amb Python)

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

index.php

<?php
// Llista de veus
$voices = [
    "Microsoft Helena Desktop",
    "Microsoft Hazel Desktop",
    "Microsoft Zira Desktop",
    "Microsoft Hortense Desktop",
    "Vocalizer Expressive Jordi Harpo 22kHz"
];

// Carrega valors antics per mantenir-los al formulari
$lastText = isset($_GET['text']) ? htmlspecialchars($_GET['text']) : '';
$lastVoice = isset($_GET['voice']) ? $_GET['voice'] : '';
?>
<!DOCTYPE html>
<html lang="ca">
<head>
    <meta charset="UTF-8">
    <title>Generador TTS</title>
</head>
<body>
    <h1>Text-to-Speech (amb Python)</h1>
    <form action="exec_script_py.php" method="POST">

        <label for="text">Introdueix el text:</label><br>
        <textarea name="text" id="text" rows="4" cols="50"><?= $lastText ?></textarea><br><br>

        <label for="voice">Selecciona una veu:</label><br>
        <select name="voice" id="voice">
            <?php foreach ($voices as $voice): ?>
                <option value="<?= htmlspecialchars($voice) ?>" <?= ($voice == $lastVoice ? 'selected' : '') ?>>
                    <?= htmlspecialchars($voice) ?>
                </option>
            <?php endforeach; ?>
        </select><br><br>

        <input type="submit" value="Generar àudio">
    </form>

    <?php if (isset($_GET['error'])): ?>
        <p style="color:red;">❌ Error: <?= htmlspecialchars($_GET['error']) ?></p>
    <?php endif; ?>

    <?php if (file_exists("tts.mp3")): ?>
        <h2>Resultat:</h2>
        <audio controls>
            <source src="tts.mp3?<?= time() ?>" type="audio/mpeg">
            El teu navegador no suporta àudio.
        </audio>
    <?php endif; ?>
</body>
</html>

generar_tts.py

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)
  • Vam eliminar la versió local conflictiva:
  rm -rf ~/.local/lib/python3.8/site-packages/gtts
  rm -rf ~/.local/lib/python3.8/site-packages/gTTS-*.dist-info

3. Reinstal·lació neta

  • Vam reinstal·lar gtts globalment:
  sudo /bin/python3 -m pip install --no-cache-dir gtts
  • Tot i això, l’error persistia perquè el servidor web seguia usant el Python del sistema, amb rutes contaminades.

4. Creació d’un entorn virtual

  • Vam crear un entorn virtual aïllat:
  python3 -m virtualenv venv
  source venv/bin/activate
  pip install gtts
  • Això va instal·lar gtts netament a:
  /volume2/web/00-sapi_tts/venv/lib/python3.8/site-packages

5. Modificació del script PHP

  • Vam modificar exec_script_py.php per cridar el Python del virtualenv:
  $python = '/volume2/web/00-sapi_tts/venv/bin/python';
  $command = "$python generar_tts.py $text $voice 2>&1";
  • 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?

Publicat dins de Veus

🔊 Generador de veu amb Edge TTS i Google TTS🔊 tts a 192.168.0.49/00-sapi_tts/ & tts a 192.168.1.49/00-sapi_tts/

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

index.php

<?php
// Llista de veus
$voices = [
    "Microsoft Helena Desktop",
    "Microsoft Hazel Desktop",
    "Microsoft Zira Desktop",
    "Microsoft Hortense Desktop",
    "Vocalizer Expressive Jordi Harpo 22kHz"
];

// Carrega valors antics per mantenir-los al formulari
$lastText = isset($_GET['text']) ? htmlspecialchars($_GET['text']) : '';
$lastVoice = isset($_GET['voice']) ? $_GET['voice'] : '';
?>
<!DOCTYPE html>
<html lang="ca">
<head>
    <meta charset="UTF-8">
    <title>Generador TTS</title>
</head>
<body>
    <h1>Text-to-Speech (amb Python)</h1>
    <form action="exec_script_py.php" method="post">
        <label for="text">Introdueix el text:</label><br>
        <textarea name="text" id="text" rows="4" cols="50"><?= $lastText ?></textarea><br><br>

        <label for="voice">Selecciona una veu:</label><br>
        <select name="voice" id="voice">
            <?php foreach ($voices as $voice): ?>
                <option value="<?= htmlspecialchars($voice) ?>" <?= ($voice == $lastVoice ? 'selected' : '') ?>>
                    <?= htmlspecialchars($voice) ?>
                </option>
            <?php endforeach; ?>
        </select><br><br>

        <input type="submit" value="Generar àudio">
    </form>

    <?php if (isset($_GET['error'])): ?>
        <p style="color:red;">❌ Error: <?= htmlspecialchars($_GET['error']) ?></p>
    <?php endif; ?>

    <?php if (file_exists("tts.mp3")): ?>
        <h2>Resultat:</h2>
        <audio controls>
            <source src="tts.mp3?<?= time() ?>" type="audio/mpeg">
            El teu navegador no suporta àudio.
        </audio>
    <?php endif; ?>
</body>
</html>

generar_tts.py


import sys
import os
from gtts import gTTS

# Agafa el directori actual del fitxer Python
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'] ?? '';

    // Guarda per reenviar després
    $text = escapeshellarg($textRaw);
    $voice = escapeshellarg($voiceRaw);

    // DEBUG: mostra quin python s'està usant
    $pythonPath = shell_exec("where python3");
    echo "<pre>Python detectat:\n$pythonPath</pre>";



    // Crida python del synology? o pytho windows 11 amb la comanda real
    $command = "/bin/python3 generar_tts.py $text $voice 2>&1";
    $output = shell_exec($command);

    echo "<pre>Sortida execució:\n$command\n\n$output</pre>"; // Ajuda per depuració

    // Comprova si s'ha creat l'àudio
    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();
    }

    // Redirigeix a index amb el text i veu conservats
    header("Location: index.php?text=" . urlencode($textRaw) . "&voice=" . urlencode($voiceRaw));
    exit();
}
?>

Publicat dins de Veus

projecte python

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.

Publicat dins de Veus

Projecte Veus amb Copilot

📁 Estructura del projecte

C:\
├── tts\
│   ├── tts-to-wav.ps1       ← Script PowerShell per generar l’àudio
│   └── list-voices.ps1      ← Script PowerShell per llistar veus
└── AppServ\
    └── www\
        └── tts\
            ├── index.php    ← Interfície web
            └── tts.wav      ← Fitxer d’àudio generat

📄 Què és un fitxer .ps1?

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.

📘 Documentació dels components

1. list-voices.ps1 — Llista de veus SAPI5


Add-Type -AssemblyName System.Speech
$synth = New-Object System.Speech.Synthesis.SpeechSynthesizer

$synth.GetInstalledVoices() | ForEach-Object {
    $info = $_.VoiceInfo
    $label = "SAPI5|$($info.Name)"
    Write-Output $label
}

🔹 Funció: Detecta totes les veus SAPI5 instal·lades i les imprimeix amb el prefix SAPI5|.


2. tts-to-wav.ps1 — Generació d’àudio

param (
    [string]$text = "Hola món",
    [string]$voiceName,
    [string]$outputPath = "C:\tts\tts.wav"
)

try {
    if ($voiceName -like "SAPI5|*") {
        $realVoice = $voiceName.Substring(6)
        Add-Type -AssemblyName System.Speech
        $synth = New-Object System.Speech.Synthesis.SpeechSynthesizer
        $synth.SelectVoice($realVoice)
        $synth.SetOutputToWaveFile($outputPath)
        $synth.Speak($text)
    } else {
        Write-Error "Format de veu desconegut: $voiceName"
        exit 1
    }
}
catch {
    Write-Error "Error inesperat: $_"
    exit 1
}

🔹 Funció: Rep un text i una veu, i genera un fitxer .wav amb la veu seleccionada.

3. index.php — Interfície web

<?php
// Obtenim la llista de veus
$voiceListCmd = "C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe -ExecutionPolicy Bypass -File C:\\tts\\list-voices.ps1";
exec($voiceListCmd, $voices, $voiceStatus);

// Depuració: mostra les veus detectades
echo "<pre><strong>Veus detectades:</strong>\n";
print_r($voices);
echo "</pre>";

// Si falla, usem una llista per defecte
if ($voiceStatus !== 0 || empty($voices)) {
    $voices = [
        "SAPI5|Microsoft Helena Desktop",
        "SAPI5|Microsoft Hazel Desktop",
        "SAPI5|Microsoft Zira Desktop"
    ];
}
?>

<!DOCTYPE html>
<html lang="ca">
<head>
    <meta charset="UTF-8">
    <title>Conversió TTS</title>
</head>
<body>
    <h2>🔊 Conversió de text a veu (SAPI5)</h2>
    
  
    <form method="get">
        <label for="text">Text a llegir:</label><br>
        <textarea name="text" rows="4" cols="60"><?= htmlspecialchars($_GET['text'] ?? '') ?></textarea><br><br>

        <label for="voice">Selecciona una veu:</label><br>
        <select name="voice">
            <?php foreach ($voices as $v): ?>
                <option value="<?= htmlspecialchars($v) ?>" <?= ($v === ($_GET['voice'] ?? '')) ? 'selected' : '' ?>>
                    <?= htmlspecialchars($v) ?>
                </option>
            <?php endforeach; ?>
        </select><br><br>

        <button type="submit">▶️ Generar àudio</button>
    </form>

    <?php
    $text = $_GET['text'] ?? '';
    $voice = $_GET['voice'] ?? '';
    $audioFile = '/tts/tts.wav';

    if ($text && $voice) {
        $escapedText = escapeshellarg($text);
        $escapedVoice = escapeshellarg($voice);
        $escapedOutput = escapeshellarg(__DIR__ . DIRECTORY_SEPARATOR . 'tts.wav');

        $cmd = "C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe -ExecutionPolicy Bypass -File C:\\tts\\tts-to-wav.ps1 -text $escapedText -voiceName $escapedVoice -outputPath $escapedOutput";

        // 🔍 DEBUG: Mostra la comanda que s'executarà
        echo "<pre><strong>Comanda PowerShell:</strong>\n$cmd\n</pre>";

        exec($cmd, $outputLines, $status);

        if ($status === 0 && file_exists(__DIR__ . '/tts.wav')) {
            echo "<h3>✅ Àudio generat:</h3>";
            echo "<audio controls autoplay style='margin-top:10px;'>
                    <source src=\"$audioFile\" type=\"audio/wav\">
                    El teu navegador no suporta àudio.
                  </audio>";
        } else {
            echo "<p style='color:red;'>❌ Error generant l’àudio.</p>";
            echo "<pre><strong>Sortida PowerShell:</strong>\n";
            print_r($outputLines);
            echo "\nCodi de sortida: $status</pre>";
        }
    }
    ?>
</body>
</html>
Publicat dins de Veus

python i veus

Requisits:

  • Tener instal·lat Python.
  • Instal·lar la llibreria pyttsx3:
pip install pyttsx3

pip install pyttsx3

PS C:\WINDOWS\system32> python -V
Python 3.13.5
PS C:\WINDOWS\system32>
import pyttsx3

def llistar_veus():
    engine = pyttsx3.init()
    voices = engine.getProperty('voices')
    
    for idx, voice in enumerate(voices):
        print(f"Veu {idx + 1}:")
        print(f"  ID: {voice.id}")
        print(f"  Nom: {voice.name}")
        print(f"  Llengua: {voice.languages}")
        print(f"  Gènere: {voice.gender}")
        print()

if __name__ == "__main__":
    llistar_veus()

Aquest script es pot llançar des de powerShell

python c:\python\llista_veus.py

python s:\python\llista_veus.py

si volem afegir les OneCore


python c:\python\llista_veus_onecore.py

python s:\python\llista_veus_onecore.py


python c:\python\llista_veus_onecore.py

python s:\python\llista_veus_onecore.py

import pyttsx3
import winreg

def llistar_veus_sapi():
    print("🎙️ Veus SAPI instal·lades:\n")
    engine = pyttsx3.init()
    voices = engine.getProperty('voices')
    for idx, voice in enumerate(voices):
        print(f"Veu {idx + 1}:")
        print(f"  ID: {voice.id}")
        print(f"  Nom: {voice.name}")
        print(f"  Llengua: {voice.languages}")
        print(f"  Gènere: {voice.gender}")
        print()

def obtenir_nom_veu_onecore(subkey):
    for value_name in ["", "Name", "DisplayName", "Attributes"]:
        try:
            val = winreg.QueryValueEx(subkey, value_name)[0]
            if isinstance(val, str) and val.strip():
                return val.strip()
        except FileNotFoundError:
            continue
    return "Desconegut"

def llistar_veus_onecore():
    base_key = r"SOFTWARE\Microsoft\Speech_OneCore\Voices\Tokens"
    try:
        with winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, base_key) as key:
            print("🎙️ Veus OneCore disponibles:\n")
            for i in range(winreg.QueryInfoKey(key)[0]):
                subkey_name = winreg.EnumKey(key, i)
                with winreg.OpenKey(key, subkey_name) as subkey:
                    nom = obtenir_nom_veu_onecore(subkey)
                    print(f"ID: {subkey_name}")
                    print(f"Nom: {nom}\n")
    except FileNotFoundError:
        print("No s'ha trobat la clau de registre per a les veus OneCore. Assegura't que estàs a Windows 11.")

if __name__ == "__main__":
    llistar_veus_sapi()
    llistar_veus_onecore()


python c:\python\veus_sapi_onecore.py

python s:\python\veus_sapi_onecore.py

import pyttsx3
import winreg

def llistar_veus_sapi():
    print("🎙️ Veus SAPI instal·lades:\n")
    engine = pyttsx3.init()
    voices = engine.getProperty('voices')
    for idx, voice in enumerate(voices):
        print(f"Veu {idx + 1}:")
        print(f"  ID: {voice.id}")
        print(f"  Nom: {voice.name}")
        print(f"  Llengua: {voice.languages}")
        print(f"  Gènere: {voice.gender}")
        print()

def obtenir_nom_veu_onecore(subkey):
    for value_name in ["", "Name", "DisplayName", "Attributes"]:
        try:
            val = winreg.QueryValueEx(subkey, value_name)[0]
            if isinstance(val, str) and val.strip():
                return val.strip()
        except FileNotFoundError:
            continue
    return "Desconegut"

def llistar_veus_onecore():
    base_key = r"SOFTWARE\Microsoft\Speech_OneCore\Voices\Tokens"
    try:
        with winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, base_key) as key:
            print("🎙️ Veus OneCore disponibles:\n")
            for i in range(winreg.QueryInfoKey(key)[0]):
                subkey_name = winreg.EnumKey(key, i)
                with winreg.OpenKey(key, subkey_name) as subkey:
                    nom = obtenir_nom_veu_onecore(subkey)
                    print(f"ID: {subkey_name}")
                    print(f"Nom: {nom}\n")
    except FileNotFoundError:
        print("No s'ha trobat la clau de registre per a les veus OneCore. Assegura't que estàs a Windows 11.")

if __name__ == "__main__":
    llistar_veus_sapi()
    llistar_veus_onecore()

Publicat dins de Veus

instal·la-veus-onecore.ps1

# Llengües que vols instal·lar
$Languages = @("ca-ES", "es-ES", "en-US", "en-GB", "fr-FR")

# Components de veu i reconeixement per a cada llengua
$CapabilitiesBase = @(
  "Language.Basic~~~{0}~0.0.1.0",
  "Language.Handwriting~~~{0}~0.0.1.0",
  "Language.Speech~~~{0}~0.0.1.0",
  "Language.TextToSpeech~~~{0}~0.0.1.0"
)

foreach ($lang in $Languages) {
    Write-Host "🌐 Processant idioma: $lang" -ForegroundColor Cyan
    foreach ($capTemplate in $CapabilitiesBase) {
        $cap = [string]::Format($capTemplate, $lang)
        $capState = Get-WindowsCapability -Online -Name $cap -ErrorAction SilentlyContinue

        if ($capState -and $capState.State -eq "Installed") {
            Write-Host "✅ Ja instal·lat: $cap" -ForegroundColor Green
        }
        else {
            Write-Host "📦 Instal·lant: $cap" -ForegroundColor Yellow
            try {
                Add-WindowsCapability -Online -Name $cap -ErrorAction Stop
                Write-Host "✅ Instal·lat correctament: $cap" -ForegroundColor Green
            } catch {
                Write-Host "❌ Error instal·lant $cap: $_" -ForegroundColor Red
            }
        }
    }
    Write-Host "----------------------------`n"
}

Write-Host "🔁 Quan acabi TOT, reinicia Windows manualment per activar les veus." -ForegroundColor Magenta

es-ES (Espanyol)

en-US (Anglès EUA)

en-GB (Anglès UK)

fr-FR (Francès)

ca-ES (Català)


📌 Com usar-lo

  1. Desa el contingut anterior com a fitxer:
    • Exemple: C:\tts\instal·la-veus-onecore.ps1
  2. Obre PowerShell com a administrador
  3. Executa:
Set-ExecutionPolicy -Scope Process -ExecutionPolicy Bypass
C:\tts\instal·la-veus-onecore.ps1

🔁 Després de córrer-lo

  1. Reinicia l’ordinador
  2. Executa el teu script Python de detecció (Speech_OneCore) per veure les veus noves
  3. Ja podràs usar-les via Python o integració a la web

Publicat dins de Veus

🔊 Test de veus SAPI i OneCore (tts)

C:\
├── tts\
|   ├──tts-to-wav.py       ← Script Python per generar l’àudio
│   ├── tts-to-wav.ps1     ← Script PowerShell per generar l’àudio
│   └── list-voices.ps1    ← Script PowerShell per llistar veus
└── AppServ\
    └── www\
        └── tts\
            ├── index.php    ← Interfície web
            └── tts.wav      ← Fitxer d’àudio generat

Index.php
--------

<?php
ini_set('display_errors', 1);
ini_set('display_startup_errors', 1);
error_reporting(E_ALL);

// Executa PowerShell per obtenir la llista de veus disponibles
$voiceListCmd = "powershell -ExecutionPolicy Bypass -File C:\\tts\\list-voices.ps1";
exec($voiceListCmd, $voices, $voiceStatus);

// Si falla, utilitza una llista per defecte
if ($voiceStatus !== 0 || empty($voices)) {
    $voices = [
        "SAPI5|Microsoft Helena Desktop",
        "SAPI5|Microsoft Hazel Desktop",
        "SAPI5|Microsoft Zira Desktop"
    ];
}else 
//-----------provisional fins la sol·lucio definitiva----
{
    // Afegim manualment Herena a OneCore (només si no està ja)
    $herena = "ONECORE|Microsoft Herena - Catalan (Catalan)";
    if (!in_array($herena, $voices)) {
        $voices[] = $herena;
    }
}
//------fi del provisional ❌ Error generant l’àudio.---
$text = $_GET['text'] ?? '';
$voice = $_GET['voice'] ?? $voices[0]; // 👈 AIXÒ ÉS ON TOCA
$audioFile = '/tts/tts.wav';

$audioTag = '';

if ($text) {
    $escapedText = escapeshellarg($text);
    $escapedVoice = escapeshellarg($voice);
    $escapedOutput = escapeshellarg(__DIR__ . DIRECTORY_SEPARATOR . 'tts.wav');


//$cmd = "powershell -ExecutionPolicy Bypass -File C:\\tts\\tts-to-wav.ps1 -text $escapedText -voiceName $escapedVoice -outputPath $escapedOutput";
    $cmd = "python C:\\tts\\tts-to-wav.py $escapedText $escapedVoice $escapedOutput";



    exec($cmd, $outputLines, $status);

    if ($status === 0 && file_exists(__DIR__ . '/tts.wav')) {
        $audioTag = "<audio controls autoplay style='margin-top:20px;'>
            <source src=\"$audioFile\" type=\"audio/wav\">
            El teu navegador no suporta àudio.
        </audio>";
    } else {
        $audioTag = "<p style='color:red;'>❌ Error generant l’àudio.</p>";
    }
}
?>

<!-- Interfície HTML -->
<!DOCTYPE html>
<html lang="ca">
<head>
    <meta charset="UTF-8">
    <title>Conversió TTS</title>
</head>
<body>
    <form method="get">
        <label for="text">Text:</label><br>
        <textarea name="text" rows="4" cols="50"><?= htmlspecialchars($text) ?></textarea><br><br>
        
        <label for="voice">Veu:</label>
        <select name="voice">
            <?php foreach ($voices as $v): ?>
                <option value="<?= htmlspecialchars($v) ?>" <?= $v === $voice ? 'selected' : '' ?>><?= htmlspecialchars($v) ?></option>
            <?php endforeach; ?>
        </select><br><br>

        <button type="submit">Generar àudio</button>
    </form>

    <?= $audioTag ?>
</body>
</html>

=========


 tts-to-wav.ps1 (usarem tts-to-wav.py)

param (
    [string]$text = "Hola món",
    [string]$voiceName,
    [string]$outputPath = "C:\tts\tts.wav"
)

try {
    if ($voiceName -like "ONECORE|*") {
        $realVoice = $voiceName.Substring(8)

        Add-Type -AssemblyName Windows.Media.SpeechSynthesis
        $synth = New-Object Windows.Media.SpeechSynthesis.SpeechSynthesizer

        $voice = $synth.AllVoices | Where-Object { $_.DisplayName -eq $realVoice }

        if (-not $voice) {
            Write-Error "Veu '$realVoice' no trobada a OneCore."
            exit 1
        }

        $synth.Voice = $voice

        $streamOp = $synth.SynthesizeTextToStreamAsync($text)
        $streamOp.Wait()
        $stream = $streamOp.GetResults()

        # Llegir el stream
        $reader = New-Object Windows.Storage.Streams.DataReader($stream)
        $reader.LoadAsync($stream.Size).AsTask().Wait()
        $data = New-Object byte[] $stream.Size
        $reader.ReadBytes($data)

        [System.IO.File]::WriteAllBytes($outputPath, $data)
    }
    elseif ($voiceName -like "SAPI5|*") {
        $realVoice = $voiceName.Substring(6)

        Add-Type -AssemblyName System.Speech
        $synth = New-Object System.Speech.Synthesis.SpeechSynthesizer
        $synth.SelectVoice($realVoice)
        $synth.SetOutputToWaveFile($outputPath)
        $synth.Speak($text)
    }
    else {
        Write-Error "Format de veu desconegut: $voiceName"
        exit 1
    }
}
catch {
    Write-Error "Error inesperat: $_"
    exit 1
}
========================
tts-to-wav.py

import sys
import os
import pyttsx3

text = sys.argv[1]
voice_name = sys.argv[2]
output_path = sys.argv[3]

engine = pyttsx3.init()

# Buscar la veu exacta
found = False
for voice in engine.getProperty('voices'):
    if voice.name.strip() == voice_name.strip():
        engine.setProperty('voice', voice.id)
        found = True
        break

if not found:
    print(f"❌ Veu '{voice_name}' no trobada.")
    sys.exit(1)

engine.save_to_file(text, output_path)
engine.runAndWait()
print(f"✅ Àudio generat a {output_path}")

======================

list-voices.ps1

# Llista de veus SAPI5
Add-Type -AssemblyName System.Speech
$sapi = New-Object System.Speech.Synthesis.SpeechSynthesizer
$sapiVoices = $sapi.GetInstalledVoices() | ForEach-Object { $_.VoiceInfo.Name }

# Llista de veus OneCore
Add-Type -AssemblyName Windows.Media.SpeechSynthesis
$core = New-Object Windows.Media.SpeechSynthesis.SpeechSynthesizer
$coreVoices = $core.AllVoices | ForEach-Object { $_.DisplayName }

# Sortida combinada amb etiquetes
$sapiVoices | ForEach-Object { "SAPI5|$_" }
$coreVoices | ForEach-Object { "ONECORE|$_" }
Publicat dins de Veus

🛠️ Script PowerShell complet i robust per instal·lar veus OneCore

Idiomes i veus que vols instal·lar

$languageTags = @(
“en-US”, # English (US)
“en-GB”, # English (UK)
“es-ES”, # Spanish (Spain)
“ca-ES”, # Catalan (Spain)
“fr-FR”, # French (France)
“de-DE” # German
)

Obté la llista actual d’idiomes

$currentLangs = Get-WinUserLanguageList | ForEach-Object { $_.LanguageTag }

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)

}

}

Mostra les veus disponibles al final

Write-Host “`n📋 Veus SAPI instal·lades:” -ForegroundColor Yellow
Add-Type -AssemblyName System.Speech
(New-Object System.Speech.Synthesis.SpeechSynthesizer).GetInstalledVoices() |
ForEach-Object { “• ” + $_.VoiceInfo.Name }

Publicat dins de Veus

Script PowerShell per instal·lar veus OneCore per idioma

Aquest script utilitza Add-WindowsCapability per instal·lar els paquets de veu oficials de Microsoft via DISM (eina integrada).

varificar la versió de powershell

$PSVersionTable.PSVersion
powershellCopiaModifica
# Idiomes i veus que vols instal·lar
$languageTags = @(
    "en-US",   # English (US)
    "en-GB",   # English (UK)
    "es-ES",   # Spanish (Spain)
    "ca-ES",   # Catalan (Spain)
    "fr-FR",   # French (France)
    "de-DE"    # German
)

foreach ($lang in $languageTags) {
    Write-Host "Instal·lant veu per a $lang..." -ForegroundColor Cyan
    try {
        # Instal·la la llengua si no existeix
        $langList = Get-WinUserLanguageList
        if (-not ($langList.LanguageTag -contains $lang)) {
            $langList.Add($lang)
            Set-WinUserLanguageList $langList -Force
        }

        # Instal·la la veu OneCore associada
        $capability = "Language.$lang~Basic~~~"
        Add-WindowsCapability -Online -Name $capability -ErrorAction Stop

        # Opcional: activa reconeixement de parla
        $speechCap = "Language.$lang~Speech~~~"
        Add-WindowsCapability -Online -Name $speechCap -ErrorAction SilentlyContinue
    }
    catch {
        Write-Warning "Error instal·lant per a $lang: $_"
    }
}

🧩 Què fa aquest script?

✔️ Funciona amb PowerShell 5.x (Windows PowerShell): parcialment en 7

  • Afegeix l’idioma si cal.
  • Instal·la la veu (OneCore).
  • Intenta afegir també el reconeixement de veu (opcional).
  • Funciona en Windows 10 i 11, però cal accés d’administrador.

2. 🔊 Comprova que les veus apareixen a OneCore:

Get-ChildItem "HKLM:\SOFTWARE\Microsoft\Speech_OneCore\Voices\Tokens" | ForEach-Object {
    $keyPath = $_.PSPath
    $subkeys = Get-ChildItem -Path $keyPath -ErrorAction SilentlyContinue
    $displayName = $null

    foreach ($subkey in $subkeys) {
        $subProps = Get-ItemProperty -Path $subkey.PSPath -ErrorAction SilentlyContinue
        if ($subProps.DisplayName) {
            $displayName = $subProps.DisplayName
            break
        }
    }

    [PSCustomObject]@{
        VoiceId = $_.PSChildName
        Name    = $displayName
        Path    = $keyPath
    }
}

i a powerhshell

——- —- —-
MSTTS_V110_caES_Herena Microsoft.PowerShell.Core\Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Speech_OneCor…
MSTTS_V110_deDE_HeddaM Microsoft.PowerShell.Core\Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Speech_OneCor…
MSTTS_V110_deDE_KatjaM Microsoft.PowerShell.Core\Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Speech_OneCor…
MSTTS_V110_deDE_StefanM Microsoft.PowerShell.Core\Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Speech_OneCor…
MSTTS_V110_enGB_GeorgeM Microsoft.PowerShell.Core\Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Speech_OneCor…
MSTTS_V110_enGB_HazelM Microsoft.PowerShell.Core\Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Speech_OneCor…
MSTTS_V110_enGB_SusanM Microsoft.PowerShell.Core\Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Speech_OneCor…
MSTTS_V110_enUS_DavidM Microsoft.PowerShell.Core\Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Speech_OneCor…
MSTTS_V110_enUS_MarkM Microsoft.PowerShell.Core\Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Speech_OneCor…
MSTTS_V110_enUS_ZiraM Microsoft.PowerShell.Core\Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Speech_OneCor…
MSTTS_V110_esES_HelenaM Microsoft.PowerShell.Core\Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Speech_OneCor…
MSTTS_V110_esES_LauraM Microsoft.PowerShell.Core\Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Speech_OneCor…
MSTTS_V110_esES_PabloM Microsoft.PowerShell.Core\Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Speech_OneCor…


veus clàssiques (SAPI5)

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


Script PowerShell — Prova de veus SAPI (Desktop)

Add-Type -AssemblyName System.Speech
$tts = New-Object System.Speech.Synthesis.SpeechSynthesizer

$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)”
}
}

Publicat dins de Veus