{"id":16474,"date":"2025-07-02T12:51:36","date_gmt":"2025-07-02T10:51:36","guid":{"rendered":"https:\/\/www.beseit.net\/?p=16474"},"modified":"2025-07-04T12:17:37","modified_gmt":"2025-07-04T10:17:37","slug":"tts","status":"publish","type":"post","link":"http:\/\/www.beseit.net\/?p=16474","title":{"rendered":"? Test de veus SAPI i OneCore (tts)"},"content":{"rendered":"\n<pre class=\"wp-block-code\"><code>C:\\\n\u251c\u2500\u2500 tts\\\n|   \u251c\u2500\u2500tts-to-wav.py       \u2190 Script Python per generar l\u2019\u00e0udio\n\u2502   \u251c\u2500\u2500 tts-to-wav.ps1     \u2190 Script PowerShell per generar l\u2019\u00e0udio\n\u2502   \u2514\u2500\u2500 list-voices.ps1    \u2190 Script PowerShell per llistar veus\n\u2514\u2500\u2500 AppServ\\\n    \u2514\u2500\u2500 www\\\n        \u2514\u2500\u2500 tts\\\n            \u251c\u2500\u2500 index.php    \u2190 Interf\u00edcie web\n            \u2514\u2500\u2500 tts.wav      \u2190 Fitxer d\u2019\u00e0udio generat\n\nIndex.php\n--------\n\n&lt;?php\nini_set('display_errors', 1);\nini_set('display_startup_errors', 1);\nerror_reporting(E_ALL);\n\n\/\/ Executa PowerShell per obtenir la llista de veus disponibles\n$voiceListCmd = \"powershell -ExecutionPolicy Bypass -File C:\\\\tts\\\\list-voices.ps1\";\nexec($voiceListCmd, $voices, $voiceStatus);\n\n\/\/ Si falla, utilitza una llista per defecte\nif ($voiceStatus !== 0 || empty($voices)) {\n    $voices = &#91;\n        \"SAPI5|Microsoft Helena Desktop\",\n        \"SAPI5|Microsoft Hazel Desktop\",\n        \"SAPI5|Microsoft Zira Desktop\"\n    ];\n}else \n\/\/-----------provisional fins la sol\u00b7lucio definitiva----\n{\n    \/\/ Afegim manualment Herena a OneCore (nom\u00e9s si no est\u00e0 ja)\n    $herena = \"ONECORE|Microsoft Herena - Catalan (Catalan)\";\n    if (!in_array($herena, $voices)) {\n        $voices&#91;] = $herena;\n    }\n}\n\/\/------fi del provisional \u274c Error generant l\u2019\u00e0udio.---\n$text = $_GET&#91;'text'] ?? '';\n$voice = $_GET&#91;'voice'] ?? $voices&#91;0]; \/\/ ? AIX\u00d2 \u00c9S ON TOCA\n$audioFile = '\/tts\/tts.wav';\n\n$audioTag = '';\n\nif ($text) {\n    $escapedText = escapeshellarg($text);\n    $escapedVoice = escapeshellarg($voice);\n    $escapedOutput = escapeshellarg(__DIR__ . DIRECTORY_SEPARATOR . 'tts.wav');\n\n\n\/\/$cmd = \"powershell -ExecutionPolicy Bypass -File C:\\\\tts\\\\tts-to-wav.ps1 -text $escapedText -voiceName $escapedVoice -outputPath $escapedOutput\";\n    $cmd = \"python C:\\\\tts\\\\tts-to-wav.py $escapedText $escapedVoice $escapedOutput\";\n\n\n\n    exec($cmd, $outputLines, $status);\n\n    if ($status === 0 &amp;&amp; file_exists(__DIR__ . '\/tts.wav')) {\n        $audioTag = \"&lt;audio controls autoplay style='margin-top:20px;'>\n            &lt;source src=\\\"$audioFile\\\" type=\\\"audio\/wav\\\">\n            El teu navegador no suporta \u00e0udio.\n        &lt;\/audio>\";\n    } else {\n        $audioTag = \"&lt;p style='color:red;'>\u274c Error generant l\u2019\u00e0udio.&lt;\/p>\";\n    }\n}\n?>\n\n&lt;!-- Interf\u00edcie HTML -->\n&lt;!DOCTYPE html>\n&lt;html lang=\"ca\">\n&lt;head>\n    &lt;meta charset=\"UTF-8\">\n    &lt;title>Conversi\u00f3 TTS&lt;\/title>\n&lt;\/head>\n&lt;body>\n    &lt;form method=\"get\">\n        &lt;label for=\"text\">Text:&lt;\/label>&lt;br>\n        &lt;textarea name=\"text\" rows=\"4\" cols=\"50\">&lt;?= htmlspecialchars($text) ?>&lt;\/textarea>&lt;br>&lt;br>\n        \n        &lt;label for=\"voice\">Veu:&lt;\/label>\n        &lt;select name=\"voice\">\n            &lt;?php foreach ($voices as $v): ?>\n                &lt;option value=\"&lt;?= htmlspecialchars($v) ?>\" &lt;?= $v === $voice ? 'selected' : '' ?>>&lt;?= htmlspecialchars($v) ?>&lt;\/option>\n            &lt;?php endforeach; ?>\n        &lt;\/select>&lt;br>&lt;br>\n\n        &lt;button type=\"submit\">Generar \u00e0udio&lt;\/button>\n    &lt;\/form>\n\n    &lt;?= $audioTag ?>\n&lt;\/body>\n&lt;\/html>\n\n=========\n\n\n tts-to-wav.ps1 (usarem tts-to-wav.py)\n\nparam (\n    &#91;string]$text = \"Hola m\u00f3n\",\n    &#91;string]$voiceName,\n    &#91;string]$outputPath = \"C:\\tts\\tts.wav\"\n)\n\ntry {\n    if ($voiceName -like \"ONECORE|*\") {\n        $realVoice = $voiceName.Substring(8)\n\n        Add-Type -AssemblyName Windows.Media.SpeechSynthesis\n        $synth = New-Object Windows.Media.SpeechSynthesis.SpeechSynthesizer\n\n        $voice = $synth.AllVoices | Where-Object { $_.DisplayName -eq $realVoice }\n\n        if (-not $voice) {\n            Write-Error \"Veu '$realVoice' no trobada a OneCore.\"\n            exit 1\n        }\n\n        $synth.Voice = $voice\n\n        $streamOp = $synth.SynthesizeTextToStreamAsync($text)\n        $streamOp.Wait()\n        $stream = $streamOp.GetResults()\n\n        # Llegir el stream\n        $reader = New-Object Windows.Storage.Streams.DataReader($stream)\n        $reader.LoadAsync($stream.Size).AsTask().Wait()\n        $data = New-Object byte&#91;] $stream.Size\n        $reader.ReadBytes($data)\n\n        &#91;System.IO.File]::WriteAllBytes($outputPath, $data)\n    }\n    elseif ($voiceName -like \"SAPI5|*\") {\n        $realVoice = $voiceName.Substring(6)\n\n        Add-Type -AssemblyName System.Speech\n        $synth = New-Object System.Speech.Synthesis.SpeechSynthesizer\n        $synth.SelectVoice($realVoice)\n        $synth.SetOutputToWaveFile($outputPath)\n        $synth.Speak($text)\n    }\n    else {\n        Write-Error \"Format de veu desconegut: $voiceName\"\n        exit 1\n    }\n}\ncatch {\n    Write-Error \"Error inesperat: $_\"\n    exit 1\n}\n========================\ntts-to-wav.py\n\nimport sys\nimport os\nimport pyttsx3\n\ntext = sys.argv&#91;1]\nvoice_name = sys.argv&#91;2]\noutput_path = sys.argv&#91;3]\n\nengine = pyttsx3.init()\n\n# Buscar la veu exacta\nfound = False\nfor voice in engine.getProperty('voices'):\n    if voice.name.strip() == voice_name.strip():\n        engine.setProperty('voice', voice.id)\n        found = True\n        break\n\nif not found:\n    print(f\"\u274c Veu '{voice_name}' no trobada.\")\n    sys.exit(1)\n\nengine.save_to_file(text, output_path)\nengine.runAndWait()\nprint(f\"\u2705 \u00c0udio generat a {output_path}\")\n\n======================\n\nlist-voices.ps1\n\n# Llista de veus SAPI5\nAdd-Type -AssemblyName System.Speech\n$sapi = New-Object System.Speech.Synthesis.SpeechSynthesizer\n$sapiVoices = $sapi.GetInstalledVoices() | ForEach-Object { $_.VoiceInfo.Name }\n\n# Llista de veus OneCore\nAdd-Type -AssemblyName Windows.Media.SpeechSynthesis\n$core = New-Object Windows.Media.SpeechSynthesis.SpeechSynthesizer\n$coreVoices = $core.AllVoices | ForEach-Object { $_.DisplayName }\n\n# Sortida combinada amb etiquetes\n$sapiVoices | ForEach-Object { \"SAPI5|$_\" }\n$coreVoices | ForEach-Object { \"ONECORE|$_\" }\n<\/code><\/pre>\n","protected":false},"excerpt":{"rendered":"","protected":false},"author":2,"featured_media":3168,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[185],"tags":[],"class_list":["post-16474","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-veus"],"_links":{"self":[{"href":"http:\/\/www.beseit.net\/index.php?rest_route=\/wp\/v2\/posts\/16474","targetHints":{"allow":["GET"]}}],"collection":[{"href":"http:\/\/www.beseit.net\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"http:\/\/www.beseit.net\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"http:\/\/www.beseit.net\/index.php?rest_route=\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"http:\/\/www.beseit.net\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=16474"}],"version-history":[{"count":14,"href":"http:\/\/www.beseit.net\/index.php?rest_route=\/wp\/v2\/posts\/16474\/revisions"}],"predecessor-version":[{"id":16524,"href":"http:\/\/www.beseit.net\/index.php?rest_route=\/wp\/v2\/posts\/16474\/revisions\/16524"}],"wp:featuredmedia":[{"embeddable":true,"href":"http:\/\/www.beseit.net\/index.php?rest_route=\/wp\/v2\/media\/3168"}],"wp:attachment":[{"href":"http:\/\/www.beseit.net\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=16474"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"http:\/\/www.beseit.net\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=16474"},{"taxonomy":"post_tag","embeddable":true,"href":"http:\/\/www.beseit.net\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=16474"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}