🔊 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|$_" }