script Python intel·ligent

Detecta si el text és en català o anglès

Tria automàticament la veu adequada per a edge-tts

Genera un fitxer .mp3 amb veu natural

tts_auto.py
import sys
import subprocess
from langdetect import detect

# ? Configura les veus segons l'idioma
VOICES = {
    'ca': 'ca-ES-JoanaNeural',
    'en': 'en-GB-LibbyNeural'
}

def detect_language(text):
    try:
        lang = detect(text)
        return lang if lang in VOICES else 'en'
    except:
        return 'en'

def synthesize(text, output_file):
    lang = detect_language(text)
    voice = VOICES[lang]
    print(f"Idioma detectat: {lang} → Veu: {voice}")
    
    command = [
        '/var/services/homes/urqtejmi/.local/bin/edge-tts',
        '--text', text,
        '--voice', voice,
        '--write-media', output_file
    ]
    
    subprocess.run(command)

if __name__ == "__main__":
    if len(sys.argv) < 2:
        print("Ús: python tts_auto.py \"Text a llegir\"")
        sys.exit(1)

    text = sys.argv[1]
    output = "sortida.mp3"
    synthesize(text, output)
    print(f"Àudio generat: {output}")

python 9

ssh urqtejmi@192.168.0.49
ls -l /volume1/@appstore/

drwxr-xr-x 1 root       root              44 May 28 00:15 Apache2.4
drwxr-xr-x 1 root       ContainerManager 114 Jul  8 18:09 ContainerManager
drwxr-xr-x 1 root       HyperBackup       58 Jun  4 12:03 HyperBackup
drwxr-xr-x 1 root       root              74 Jun 17 18:24 HyperBackupVault
drwxr-xr-x 1 root       root             152 May 28 00:15 MariaDB10
drwxr-xr-x 1 root       root              12 Jul  8 18:11 Node.js_v14
drwxr-xr-x 1 root       root              12 Jul  8 18:10 Node.js_v16
drwxr-xr-x 1 root       root              12 Jul  8 18:10 Node.js_v18
drwxr-xr-x 1 root       root              12 Jul  8 18:10 Node.js_v20
drwxr-xr-x 1 root       root              20 May 28 09:34 PHP7.4
drwxr-xr-x 1 root       root              20 May 28 00:15 PHP8.0
drwxr-xr-x 1 phpMyAdmin phpMyAdmin        46 May 28 08:46 phpMyAdmin
drwxr-xr-x 1 root       root              38 Jul  8 17:53 Python3.9
drwxr-xr-x 1 root       root             186 May 28 00:14 WebStation
drwxr-xr-x 1 WordPress  WordPress         70 May 28 00:16 WordPress

reservar ip (192.168.0.49 vs. 192.168.1.49

Què vol dir “reservar una IP”?

Reservar una IP (també anomenat reserva DHCP o IP estàtica assignada per MAC) vol dir que el router sempre assignarà la mateixa IP al dispositiu (en aquest cas, el Synology) quan es connecti, basant-se en la seva adreça MAC.

?️ Com reservar la IP al router

? Pas previ: Troba l’adreça MAC del Synology

  1. Entra al DSM (http://192.168.0.49:5000).
  2. Ves a Panel de control → Red → Interfaz de red → LAN 1 → Detalles.
  3. Copia l’adreça MAC (ex: 00:11:32:AB:CD:EF).

? A cada router (un per cada domicili):

? Router 1 (xarxa 192.168.0.x)

  1. Entra a la configuració del router (normalment http://192.168.0.1).
  2. Ves a la secció DHCP o LAN Settings.
  3. Busca una opció com “Reservar IP”, “Static DHCP”, “Address Reservation” o similar.
  4. Afegeix:
    • MAC: la del Synology
    • IP: 192.168.0.49
  5. Desa i reinicia el router si cal.

? Router 2 (xarxa 192.168.1.x)

  1. Quan estiguis a l’altre domicili, repeteix el procés:
  2. Entra al router (http://192.168.1.1).
  3. Ves a la secció DHCP i reserva:
    • MAC: la mateixa
    • IP: 192.168.1.49

Instal·la eSpeak-ng i i ffmpeg (Calibre) al teu Synology

s’ha de substituir gTTS per eSpeak-ng, et mostro com adaptar-la pas a pas per fer servir aquest motor localment i afegir un selector de veus reals.


?️ 1. Instal·la espeak-ng i ffmpeg al teu Synology

Si tens accés a apt o ipkg:

sudo apt install espeak-ng ffmpeg

? 2. Modifica generar_tts.py per fer servir espeak-ng

import sys
import os
import subprocess

text = sys.argv[1] if len(sys.argv) > 1 else "Text per defecte"
voice = sys.argv[2] if len(sys.argv) > 2 else "ca"  # idioma per defecte: català

base_dir = os.path.dirname(os.path.abspath(__file__))
output_wav = os.path.join(base_dir, "tts.wav")
output_mp3 = os.path.join(base_dir, "tts.mp3")

# ?️ Generar WAV amb eSpeak-ng
espeak_cmd = ["espeak-ng", "-v", voice, text, "--stdout"]
with open(output_wav, "wb") as wav_file:
    subprocess.run(espeak_cmd, stdout=wav_file)

# ? Convertir a MP3 amb ffmpeg
subprocess.run(["ffmpeg", "-y", "-i", output_wav, output_mp3])

? 3. Actualitza index.php amb idiomes/veus disponibles

Substitueix la llista de veus per idiomes compatibles amb eSpeak-ng:

$voices = [
    "ca", // Català
    "es", // Espanyol
    "en", // Anglès
    "fr", // Francès
    "de", // Alemany
    "it", // Italià
    "pt", // Portuguès
    "ru", // Rus
    "ja", // Japonès
];

Pots afegir variants com ca+f3 o en+m1 per canviar el timbre (masculí/femení) si vols més control.


? 4. Prova-ho

Ara, quan seleccionis una veu (en realitat, un idioma o variant) des del formulari, es passarà com a paràmetre a espeak-ng, que generarà l’àudio amb la veu corresponent.


? Vols més personalització?

  • Puc ajudar-te a llistar totes les veus disponibles amb espeak-ng --voices
  • O afegir un selector de velocitat i to
  • O fins i tot fer que el sistema recordi l’última veu usada

Vols que et prepari un script per mostrar totes les veus disponibles al teu sistema?

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

Text to

quan parlem davan d’un microfono hi ha un sortida electrica que es capaz de de fer parlar un altaveu. Com és diu el dispositu que converteix text en caracters ascii en senyal electrica capaç de emetre so per un altaveu?

El dispositiu o sistema que converteix text en caràcters ASCII en un senyal elèctric capaç de fer parlar un altaveu s’anomena sintetitzador de veu o sistema de Text-to-Speech (TTS).

? Com funciona un sistema TTS?

  • ? Entrada: Rep text (en format ASCII o Unicode).
  • ? Processament: Analitza el text, el divideix en paraules i fonemes, i aplica regles de pronunciació i prosòdia.
  • ?️ Síntesi: Converteix els fonemes en senyals d’àudio mitjançant tècniques com la concatenació d’àudio pregravat o la síntesi neuronal.
  • ? Sortida: Genera un senyal elèctric que pot ser enviat a un altaveu per produir so audible.

? Components clau:

ComponentFunció
Motor TTS (Text-to-Speech)Converteix text en àudio
DAC (Convertidor Digital-Analògic)Transforma el senyal digital en senyal elèctric analògic per l’altaveu
AltaveuEmiteix el so generat

En el cas de la pàgina que has obert, s’utilitza una veu com Microsoft Hedda a través de SAPI5, que és una interfície de Microsoft per a la síntesi de veu.

Vols que et mostri un exemple de com fer-ho amb codi o que t’expliqui com funciona dins d’un ordinador o dispositiu concret?

Quant isiomes diferents por arriba a parlar un sintetitzador?

Els sintetitzadors de veu moderns poden parlar fins a 100 idiomes o més, depenent de la plataforma i la tecnologia utilitzada. Per exemple:

? Plataformes com Narakeet ofereixen:

  • ? 800 veus sintetitzadores en
  • ?️ 100 idiomes diferents, incloent-hi variants regionals com l’anglès dels EUA, Regne Unit, Austràlia, Índia, etc.

? Sistemes com NVDA (lector de pantalla):

  • Inclouen sintetitzadors com eSpeak, que suporta més de 80 idiomes

?️ Exemples d’idiomes disponibles:

  • Anglès (diversos accents)
  • Espanyol (de molts països)
  • Català, Basc, Gallec
  • Francès, Alemany, Italià
  • Xinès (mandarí, cantonès, etc.)
  • Àrab (amb múltiples variants)
  • Hindi, Japonès, Coreà, Rus, etc.

Això vol dir que un bon sintetitzador pot llegir textos en gairebé qualsevol idioma parlat al món, i fins i tot adaptar-se a accents i dialectes locals.

Vols que et mostri com provar-ne un en català o en un altre idioma que t’interessi?

com es fa una veu sintetizadora?

Crear una veu sintetitzadora és un procés fascinant que combina lingüística, enginyeria de so i intel·ligència artificial. Aquí tens una explicació clara i detallada:

? Com es fa una veu sintetitzadora?

Una veu sintetitzadora es construeix mitjançant un sistema de síntesi de veu o Text-to-Speech (TTS). Aquest sistema converteix text escrit en so audible que imita la parla humana.

? Parts d’un sistema TTS

Segons la Viquipèdia, un sistema TTS es divideix en dues grans parts:

PartFunció
Front-endConverteix el text en una representació fonètica i prosòdica.
Back-endConverteix aquesta representació en una ona de so sintetitzada.

?️ Etapes per crear una veu sintetitzadora

1. Normalització del text

  • Converteix abreviatures, números i símbols en paraules completes.
  • Ex: “Dr.” → “Doctor”, “12/07” → “dotze de juliol”.

2. Conversió a fonemes

  • El text es transforma en sons bàsics del llenguatge (fonemes).
  • Ex: “hola” → /ˈo.la/

3. Afegir prosòdia

  • Es defineix l’entonació, ritme i pauses per sonar natural.

4. Síntesi de la veu

Hi ha diversos mètodes per generar la veu:

MètodeDescripció
Síntesi de formantsGenera veu mitjançant models acústics. So robòtic però flexible.
Síntesi concatenadaUtilitza fragments de veu humana gravada. So més natural.
Síntesi neuronal (deep learning)Utilitza xarxes neuronals per generar veu molt realista. Ex: Tacotron, WaveNet.

? Exemple pràctic: Microsoft Hedda

A la pàgina que tens oberta, s’utilitza la veu Microsoft Hedda mitjançant SAPI5, una interfície de Microsoft per a la síntesi de veu. Aquesta veu és generada per un sistema TTS que segueix els passos anteriors i produeix un fitxer d’àudio que pots escoltar.

? Vols crear la teva pròpia veu?

És possible entrenar una veu personalitzada amb eines com:

  • ? Mozilla TTS (codi obert)
  • ? Google Cloud Text-to-Speech
  • ?️ ElevenLabs (veu hiperrealista)
  • ? Festival o eSpeak (lliures i personalitzables)

Vols que et mostri com fer-ho pas a pas amb una d’aquestes eines?

en definitiva s’ha de crear. Es pot dir que un creador de veus sintetizades en podria crear infinites, encara que algunes no les esntedria ningú?

Exactament, Miquel! ?️

En definitiva, , un creador de veus sintetitzades pot generar infinites veus diferents, tot i que:

  • ? Algunes poden sonar naturals i comprensibles
  • ? Altres poden ser tan artificials o abstractes que ningú les entendria

? Per què es poden crear infinites veus?

1. Paràmetres modificables

Un sintetitzador pot variar:

  • El timbre (veu greu o aguda)
  • La velocitat de parla
  • L’entonació i la prosòdia
  • L’accent o la pronunciació
  • L’idioma o fins i tot un idioma inventat

2. Veus experimentals

Amb eines com sintetitzadors neuronals (com Tacotron o VITS), es poden crear veus:

  • Amb sons no humans
  • Amb estructures fonètiques inventades
  • Amb emocions exagerades o distorsionades

3. Límit: la comprensibilitat

Una veu pot ser tècnicament “parlable”, però si no segueix les regles fonètiques d’un idioma conegut, no serà comprensible per cap persona.

? Analogia artística

És com pintar: pots crear infinites combinacions de colors i formes, però no totes transmetran un missatge clar. Algunes seran art abstracte, d’altres retrats realistes.

Vols que et mostri com crear una veu “estranya” o experimental amb alguna eina lliure? O potser una veu en català amb un toc personalitzat?

no en referia solament al timbre, velocitat.. Es poden crear inclus veus que ara ja no existeixen, oi? inclus s’en poden inventar veus que encara no es parlen, oi?

Sí, Miquel, ho has clavat amb la teva intuïció: sí que es poden crear veus que no existeixen actualment, i fins i tot veus d’idiomes que encara no es parlen. És una de les capacitats més sorprenents de la síntesi de veu moderna i de la intel·ligència artificial generativa.

? Crear veus que mai han existit

Sí, Miquel, tens tota la raó: els navegadors poden accedir a diferents veus segons el sistema operatiu, el motor de síntesi que utilitzen i les API disponibles. Però la bona notícia és que sí que es pot separar el sintetitzador del navegador i controlar les veus des del sistema operatiu o des d’un programa extern.


? Per què cada navegador té veus diferents?

Els navegadors utilitzen l’API Web Speech Synthesis, que accedeix a:

  • ? Les veus instal·lades al sistema (com les de Windows, macOS o Linux)
  • ? O veus pròpies del navegador (com les de Google Chrome o Edge)

Això fa que:

  • Chrome pugui usar veus com “Google UK English Male”
  • Edge pugui accedir a veus com “Microsoft Zira” o “Microsoft Jordi” (via SAPI5)
  • Firefox depengui més del sistema operatiu

?️ Com separar el sintetitzador del navegador?

✅ Opció 1: Fer servir SAPI5 (Windows)

  • És l’API de síntesi de veu de Windows.
  • Pots controlar les veus disponibles, afegir-ne de noves i fer-les servir des de qualsevol aplicació (no només el navegador).
  • Pots escriure scripts en PowerShell, Python o C# per parlar amb qualsevol veu instal·lada.

✅ Opció 2: Fer servir un motor TTS extern

  • Balabolka (Windows): programa gratuït que et permet triar qualsevol veu TTS del sistema.
  • Festival o eSpeak (Linux): motors TTS independents.
  • RHVoice, MaryTTS, Mozilla TTS: opcions lliures i multiplataforma.

✅ Opció 3: Crear un servidor TTS local

  • Pots muntar un petit servidor web al teu ordinador que rebi text i retorni àudio.
  • Així, qualsevol navegador o aplicació pot enviar text i rebre la veu que tu decideixis.
  • Ex: amb Python + Flask + pyttsx3 o gTTS.

? Beneficis de separar el sintetitzador

  • Control total sobre les veus disponibles
  • Possibilitat d’usar veus personalitzades o idiomes minoritaris
  • Independència del navegador i més coherència entre aplicacions

Si vols, et puc ajudar a:

  • Configurar un script que parli amb la veu que tu triïs
  • Crear un petit servidor TTS local
  • O explorar com afegir noves veus al teu sistema

Quina opció t’agradaria provar?






Envieu un missatge al Copilot

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