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?