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.
Name Culture —- ——- Microsoft Helena Desktop es-ES Microsoft Hedda de-DE Microsoft Katja de-DE Microsoft Stefan de-DE Microsoft George en-GB Microsoft Hazel en-GB Microsoft Susan en-GB Microsoft David en-US Microsoft Mark en-US Microsoft Zira en-US Microsoft Helena es-ES Microsoft Laura es-ES Microsoft Pablo es-ES Microsoft Hedda Desktop de-DE Microsoft Hazel Desktop en-GB Microsoft David Desktop en-US Microsoft Zira Desktop en-US Microsoft Herena ca-ES
La diferència entre les veus SAPI i les OneCore (modernes) a Windows és força significativa, tant pel que fa a la tecnologia com a l’ús que se’n pot fer. Aquí tens un resum clar:
🗣️ Veus SAPI (Speech API)
Característica
Detalls
🔧 Tecnologia
Basades en la interfície clàssica System.Speech.Synthesis
🖥️ Compatibilitat
Utilitzades per aplicacions de Windows antigues o clàssiques
🎙️ Qualitat de veu
Més robòtica o sintètica, menys natural
📦 Instal·lació
Ve amb Windows o s’instal·la amb paquets específics
🧩 Accés via PowerShell
System.Speech.Synthesis.SpeechSynthesizer
📁 Ruta al registre
HKLM:\SOFTWARE\Microsoft\SPEECH\Voices\Tokens
🧠 Veus OneCore (Modernes)
Característica
Detalls
🔧 Tecnologia
Utilitzen la nova API Windows.Media.SpeechSynthesis (UWP)
🖥️ Compatibilitat
Pensades per aplicacions modernes (UWP, apps de Windows 10/11)
🎙️ Qualitat de veu
Més natural, algunes amb tecnologia neural TTS
📦 Instal·lació
Es descarreguen des de Configuració > Idioma > Opcions > Veu
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)” } }
Les veus que veus són les veus TTS (Text-to-Speech) instal·lades al teu Windows 11. I sí, les mantindras de forma estable al sistema sempre que no les desinstal·lis o restableixis el sistema a valors d’origen.
✅ Què vol dir això?
Les veus que tens actualment:
Microsoft Hazel Desktop
en-GB
Femení
Anglès del Regne Unit
Microsoft David Desktop
en-US
Masculí
Anglès dels EUA
Microsoft Zira Desktop
en-US
Femení
Anglès dels EUA
Microsoft Hedda Desktop
de-DE
Femení
Alemany
Vocalizer Expressive Jordi Harpo
ca-ES
Masculí
Català (amb motor de tercers)
🧠 Aquestes veus són compatibles amb l’API clàssica (System.Speech.Synthesis) i poden ser utilitzades per programes com Narrador, Cortana, PowerShell, etc.
UPDATE wp_posts
SET post_content = REPLACE(post_content, '[audio mp3="http://192.168.1.140', '[audio mp3="https://192.168.1.140')
WHERE post_content LIKE '%[audio mp3="http://192.168.1.140%';