🌟🌟 Conversor de text a veu

Caixa Comodí – Text a veu

🔊 Text a veu

to run into somebody.
have a good trip.
to pass on greetings.
there’s a restroom in the café.
📌 Selecciona un idioma i prem “🔊” per veure quina veu s’utilitza.
——————————
<!DOCTYPE html>
<html lang="ca">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Caixa Comodí - Text a veu</title>
  <link rel="icon" href="calculadora.ico" type="image/x-icon">
  <style>
    body {
      font-family: Arial, sans-serif;
      margin: 0;
      padding: 0;
      display: flex;
      justify-content: center;
      align-items: flex-start;
      background-color: #f0f0f0;
      height: 100vh;
    }

    .container {
      background-color: rgb(213, 225, 225);
      padding: 20px;
      border-radius: 10px;
      box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
      max-width: 440px;
      width: 100%;
      height: 60%;
      margin-top: 30px;
      box-sizing: border-box;
      display: flex;
      flex-direction: column;
      overflow: hidden;
    }

    h2 {
      text-align: center;
      color: #333;
      margin: 0 0 15px 0;
      font-size: 1.4em;
    }

    #editor {
      font-size: 1.3em;
      padding: 8px;
      margin: 8px 0;
      width: 100%;
      height: 320px;
      overflow-y: auto;
      border: 1px solid #ccc;
      white-space: pre-wrap;
      background: white;
      border-radius: 5px;
      box-sizing: border-box;
    }

    select {
      font-size: 1em;
      padding: 6px;
      margin: 6px 4px 0 0;
      width: calc(50% - 8px);
      height: 36px;
      border: 1px solid #ccc;
      border-radius: 5px;
      box-sizing: border-box;
    }

    #debugInfo {
      margin: 10px 0;
      padding: 8px;
      background: #fff;
      border-radius: 5px;
      font-size: 0.9em;
      color: #333;
      border: 1px solid #ccc;
    }

    .controls {
      display: flex;
      justify-content: center;
      gap: 16px;
      margin-top: 12px;
    }

    #speakBtn, #toggleBtn {
      font-size: 24px;
      background: #007bff;
      color: white;
      border: none;
      border-radius: 8px;
      width: 50px;
      height: 50px;
      cursor: pointer;
      display: flex;
      align-items: center;
      justify-content: center;
    }

    #speakBtn:hover, #toggleBtn:hover {
      background: #0056b3;
    }

    .linia {
      display: block;
      padding: 0.2em;
      transition: background-color 0.3s ease;
    }

    .activa {
      background-color: hsl(180, 1%, 72%);
    }
  </style>
</head>
<body>
  <div class="container">
    <h2>🔊 Text a veu</h2>

    <div id="editor" contenteditable="true" placeholder="Escriu el text que vols escoltar">
to run into somebody.<br>
have a good trip.<br>
to pass on greetings.<br>
there’s a restroom in the café.
    </div>
    <div>
      <select id="voiceSelect">
        <option value="">Idioma</option>
        <option value="en-US">🇺🇸 Anglès (US)</option>
        <option value="en-GB">🇬🇧 Anglès (UK)</option>
        <option value="fr-FR">🇫🇷 Francès</option>
        <option value="de-DE">🇩🇪 Alemany</option>
        <option value="it-IT">🇮🇹 Italià</option>
        <option value="pt-PT">🇵🇹 Portuguès</option>
        <option value="es-ES">🇪🇸 Espanyol</option>
        <option value="ca-ES">🎗️ Català</option>
      </select>

      <select id="pauseSelect">
        <option value="1">⏱️ Pausa x1</option>
        <option value="2">⏱️ Pausa x2</option>
        <option value="3">⏱️ Pausa x3</option>
        <option value="4">⏱️ Pausa x4</option>
        <option value="10">⏱️ Pausa x10</option>
      </select>
    </div>

    <!-- 🔍 Indicador de diagnòstic -->
    <div id="debugInfo">
      📌 Selecciona un idioma i prem "🔊" per veure quina veu s'utilitza.
    </div>

    <div class="controls">
      <button id="speakBtn" title="Escolta el text">🔊</button>
      <button id="toggleBtn" title="Atura o reprèn">⏹️</button>
    </div>
  </div>

  <script>
    document.addEventListener('DOMContentLoaded', () => {
      const editor = document.getElementById('editor');
      const speakBtn = document.getElementById('speakBtn');
      const toggleBtn = document.getElementById('toggleBtn');
      const voiceSelect = document.getElementById('voiceSelect');
      const pauseSelect = document.getElementById('pauseSelect');
      const debugInfo = document.getElementById('debugInfo');

      let voices = [];
      let isSpeaking = false;
      let liniesGuardades = [];
      let currentIndex = 0;

      function loadVoices() {
        voices = speechSynthesis.getVoices();
        // Opcional: mostra totes les veus disponibles al carregar (per depuració)
        // console.log('Veus disponibles:', voices.map(v => `${v.name} (${v.lang})`));
      }

      speechSynthesis.onvoiceschanged = loadVoices;
      loadVoices();

      const idiomaGuardat = localStorage.getItem('idiomaSeleccionat');
      if (idiomaGuardat) {
        voiceSelect.value = idiomaGuardat;
      }

      voiceSelect.addEventListener('change', () => {
        localStorage.setItem('idiomaSeleccionat', voiceSelect.value);
        // Actualitzem el missatge quan es canvia l'idioma
        debugInfo.textContent = `📌 Idioma seleccionat: ${voiceSelect.options[voiceSelect.selectedIndex].text}`;
      });

      function getLinies() {
        const text = editor.innerText || editor.textContent;
        return text.split('\n').map(l => l.trim()).filter(l => l);
      }

      function marcarLinia(index) {
        const linies = editor.querySelectorAll('.linia');
        linies.forEach((el, i) => {
          el.classList.toggle('activa', i === index);
        });
      }

      function prepararEditor() {
        const linies = getLinies();
        editor.innerHTML = '';
        linies.forEach((text, i) => {
          const span = document.createElement('span');
          span.className = 'linia';
          span.textContent = text;
          editor.appendChild(span);
          editor.appendChild(document.createElement('br'));
        });
      }

      function getPausaEntreLinies() {
        const factor = parseInt(pauseSelect.value || '1');
        return 500 * factor;
      }

      // 🔍 Funció per trobar la millor veu i mostrar informació
      function getBestVoiceAndReport(langCode) {
        if (!langCode) {
          debugInfo.innerHTML = `ℹ️ Cap idioma seleccionat. S'utilitzarà la veu per defecte.`;
          return null;
        }

        // Normalitzem per a comparacions flexibles
        const normalizedTarget = langCode.replace('-', '_').toLowerCase();

        // 1. Cerca exacta (amb normalització)
        let best = voices.find(v => 
          v.lang.replace('-', '_').toLowerCase() === normalizedTarget
        );

        if (best) {
          debugInfo.innerHTML = `✅ Idioma demanat: <strong>${langCode}</strong><br>🗣️ Veus trobada: <strong>${best.name}</strong> (${best.lang})`;
          return best;
        }

        // 2. Cerca per prefix (p. ex., "ca" per a "ca-ES")
        const prefix = langCode.split('-')[0].toLowerCase();
        best = voices.find(v => v.lang.toLowerCase().startsWith(prefix));

        if (best) {
          debugInfo.innerHTML = `⚠️ Idioma demanat: <strong>${langCode}</strong><br>🗣️ Veus aproximada: <strong>${best.name}</strong> (${best.lang})`;
          return best;
        }

        // 3. No s'ha trobat cap veu
        debugInfo.innerHTML = `❌ Idioma demanat: <strong>${langCode}</strong><br>🚫 Cap veu trobada. Comprova que tens l'idioma instal·lat a Android.`;
        return null;
      }

      function speakLinia() {
        if (currentIndex >= liniesGuardades.length) {
          isSpeaking = false;
          setTimeout(() => {
            speakText();
          }, 2000);
          return;
        }

        const linia = liniesGuardades[currentIndex];
        const utterance = new SpeechSynthesisUtterance(linia);

        const selectedLang = voiceSelect.value;
        const voice = getBestVoiceAndReport(selectedLang);
        if (voice) {
          utterance.voice = voice;
        }

        utterance.rate = 1;
        utterance.pitch = 1;

        utterance.onstart = () => {
          isSpeaking = true;
          marcarLinia(currentIndex);
        };

        utterance.onend = () => {
          currentIndex++;
          setTimeout(() => {
            speakLinia();
          }, getPausaEntreLinies());
        };

        speechSynthesis.speak(utterance);
      }

      function speakText() {
        if (isSpeaking) return;
        prepararEditor();
        liniesGuardades = getLinies();
        currentIndex = 0;
        speechSynthesis.cancel();
        speakLinia();
      }

      speakBtn.addEventListener('click', speakText);

      toggleBtn.addEventListener('click', () => {
        if (isSpeaking) {
          speechSynthesis.cancel();
          isSpeaking = false;
        } else {
          speakLinia();
        }
      });
    });
  </script>
</body>
</html>

Simple text to speak
Per afegir aquest codi de sota cal usar el connector Enlighter, afegir un bloc a la entrada amb el mateix nom i descarregat el codi del porta-retalls
<!DOCTYPE html>
<!-- Declara que aquest document és HTML5 -->
<html lang="ca">
<!-- L'idioma principal del contingut és el català -->
<head>
  <meta charset="UTF-8">
  <!-- Assegura la correcta visualització de caràcters especials i emojis -->
  <title>Conversor de text a veu</title>
  <!-- Títol que apareix a la pestanya del navegador -->

  <style>
    /* Estils generals de la pàgina */
    body {
      font-family: sans-serif;        /* Tipus de lletra llegible */
      padding: 2em;                   /* Espai al voltant del contingut */
      background-color: hsl(194, 91%, 91%); /* Fons blau clar suau */
    }

    /* Estil comú per al camp de text i el selector d'idioma */
    input, select {
      font-size: 1.2em;               /* Mida de lletra una mica més gran */
      padding: 0.5em;                 /* Espai interior dins dels camps */
      margin-top: 1em;                /* Separació superior */
      width: 100%;                    /* Ocupen tot l'ample disponible */
      max-width: 500px;               /* Però no més de 500px */
    }

    /* Estil del botó d'altaveu (🔊) */
    #speakBtn {
      font-size: 2em;                 /* Molt gran per facilitar la interacció */
      background: none;               /* Sense fons */
      border: none;                   /* Sense vores */
      cursor: pointer;                /* Cursor en forma de mà en passar-hi */
      margin-top: 1em;                /* Separació respecte als elements anteriors */
    }

    /* Efecte visual quan l'usuari passa el ratolí pel botó */
    #speakBtn:hover {
      color: #c8e3f8;                 /* Canvia a un blau més clar */
    }
  </style>
</head>

<body>
  <!-- Títol visible de la pàgina amb un emoji d'altaveu -->
  <h2>🔊 Conversor de text a veu</h2>

  <!-- Camp on l'usuari escriu el text a sintetitzar -->
  <input type="text" id="cercar" placeholder="Escriu el text que vols escoltar">

  <!-- Selector per triar la veu/idioma desitjat -->
  <select id="voiceSelect">
    <option value="">Selecciona idioma</option>
    <option value="en-US">🇺🇸 Anglès (US)</option>
    <option value="en-GB">🇬🇧 Anglès (UK)</option>
    <option value="fr-FR">🇫🇷 Francès</option>
    <option value="de-DE">🇩🇪 Alemany</option>
    <option value="it-IT">🇮🇹 Italià</option>
    <option value="pt-PT">🇵🇹 Portuguès</option>
    <option value="es-ES">🇪🇸 Espanyol</option>
    <option value="ca-ES">🎗️ Català</option>
    <!-- Els valors (value) són codis d'idioma estàndard BCP 47 -->
  </select>

  <!-- Botó per iniciar la síntesi de veu -->
  <button id="speakBtn" title="Escolta el text">🔊</button>

  <script>
    // S'executa quan tot el contingut HTML ha carregat
    document.addEventListener('DOMContentLoaded', () => {
      // Referències als elements HTML que necessitarem
      const inputElement = document.getElementById('cercar');      // Camp de text
      const speakBtn = document.getElementById('speakBtn');        // Botó d'altaveu
      const voiceSelect = document.getElementById('voiceSelect');  // Selector d'idioma
      let voices = [];  // Array per emmagatzemar les veus disponibles

      // Funció per carregar la llista de veus del sistema
      function loadVoices() {
        voices = speechSynthesis.getVoices();
        // Aquesta API pot trigar a carregar les veus en alguns navegadors
      }

      // Assegurem-nos que les veus es carreguin, fins i tot si arriben més tard
      speechSynthesis.onvoiceschanged = loadVoices;
      loadVoices(); // Intentem carregar-les ja des del principi

      // Funció principal: converteix el text a veu
      function speakText() {
        const text = inputElement.value.trim(); // Obtenim i netegem el text
        if (!text) return; // Si està buit, no fem res

        const selectedLang = voiceSelect.value; // Idioma seleccionat per l'usuari
        const utterance = new SpeechSynthesisUtterance(text); // Creem l'objecte de parla

        // Busquem una veu que coincideixi exactament amb l'idioma triat
        const voice = voices.find(v => v.lang === selectedLang);

        if (voice) {
          // Si la trobem, l'assignem
          utterance.voice = voice;
          utterance.lang = voice.lang;
        } else {
          // Si no, mostrem un avís a la consola (per depuració)
          console.log('No s\'ha trobat cap veu per a ' + selectedLang);
          // Nota: l'API pot usar una veu per defecte si no s'assigna cap
        }

        // Paràmetres opcionals de parla
        utterance.rate = 1;   // Velocitat normal (1 = 100%)
        utterance.pitch = 1;  // To normal

        // Cancel·lem qualsevol parla anterior per evitar solapaments
        speechSynthesis.cancel();
        // Iniciem la nova síntesi de veu
        speechSynthesis.speak(utterance);
      }

      // Quan l'usuari fa clic al botó, es llegeix el text
      speakBtn.addEventListener('click', speakText);

      // ⚠️ Aquesta línia fa que el text es llegeixi AUTOMÀTICAMENT cada 10 segons
      // Pot ser útil per a demostracions, però pot resultar molest en ús normal
      setInterval(speakText, 10000);
    });
  </script>
</body>
</html>