Mini PC NIPOGI con AMD Ryzen 7 7730U, 16GB DDR4, 512GB M.2 SSD, Mini PC para Juegos con USB Tipo-C, Compatible con Pantalla Triple 4K & Mini PC Intel i9-8950HK/Intel i3-6100U Windows 11 Pro 16GB RAM 1/2TB SSD WiFi6 BT5.2 computadora de escritorio trabajo PC de bolsillo

Descripción

Denunciar artículo

Diseño Compacto

El diseño compacto de este Mini PC lo hace ideal para entornos con espacio limitado.

Conectividad Avanzada

Cuenta con USB Tipo-C, lo que proporciona una transferencia de datos rápida y opciones de conectividad versátiles.

Procesamiento Potente

Equipado con AMD Ryzen 7 7730U, este Mini PC ofrece un rendimiento robusto para una multitarea y juegos fluidos.

Amplio Almacenamiento

Con un SSD M.2 de 512 GB, el dispositivo garantiza un acceso rápido a los datos y un amplio almacenamiento para tus archivos y juegos.

Soporte para pantallas de alta resolución

Admite triple pantalla 4K, lo que lo hace perfecto para profesionales y jugadores que exigen claridad visual.


L’i9 va resultar un engany tenda poc confiable

Detalles


Descripción

Realment he rebut: Intel i3 venedor de poca confiança

l’altre:

Processor Main Frequency2.0useUse Comercial
Memory Capacity16 gigas
Hard Drive Capacity
512 GB
Model NumberE3BProcessor BrandAMD
Processor ModelR7 7730UTTypeMINI Escritorio
Graphics Card TypeTarjeta integrada de NameNIPOGI
OriginCN(Origen)TipeMINI ordenador personal

phpMyAdmin

SQL llest per a phpMyAdmin amb 33 entrades del teu porta-retalls (estructura exacta de la teva taula preposition). Es pot executar a la pestanya SQL de phpMyAdmin:

INSERT INTO `preposition` (
  `id`, `catala`, `pron`, `tipus`, `c_a_t`, `qualificador`, 
  `castella`, `ajuda`, `data_creacio`, `data_modif`, 
  `found`, `num_alea`, `encert`, `nota`, `estrelles`, `aux`
) VALUES
(NULL, 'at > We’ll meet ___ 7:30 PM tomorrow.', NULL, NULL, NULL, NULL, '', NULL, current_timestamp(), '0000-00-00 00:00:00.000000', '0', '0', '0', NULL, NULL, NULL),
(NULL, 'in, in > She was born ___ 1995 ___ Madrid.', NULL, NULL, NULL, NULL, '', NULL, current_timestamp(), '0000-00-00 00:00:00.000000', '0', '0', '0', NULL, NULL, NULL),
(NULL, 'on > The keys are ___ the kitchen counter.', NULL, NULL, NULL, NULL, '', NULL, current_timestamp(), '0000-00-00 00:00:00.000000', '0', '0', '0', NULL, NULL, NULL),
(NULL, 'on > I’ll see you ___ Saturday morning.', NULL, NULL, NULL, NULL, '', NULL, current_timestamp(), '0000-00-00 00:00:00.000000', '0', '0', '0', NULL, NULL, NULL),
(NULL, 'on > They got married ___ Valentine’s Day.', NULL, NULL, NULL, NULL, '', NULL, current_timestamp(), '0000-00-00 00:00:00.000000', '0', '0', '0', NULL, NULL, NULL),
(NULL, 'on > There’s a stain ___ my shirt.', NULL, NULL, NULL, NULL, '', NULL, current_timestamp(), '0000-00-00 00:00:00.000000', '0', '0', '0', NULL, NULL, NULL),
(NULL, 'at > We always eat dinner ___ sunset.', NULL, NULL, NULL, NULL, '', NULL, current_timestamp(), '0000-00-00 00:00:00.000000', '0', '0', '0', NULL, NULL, NULL),
(NULL, 'at > He lives ___ 45 Oxford Street.', NULL, NULL, NULL, NULL, '', NULL, current_timestamp(), '0000-00-00 00:00:00.000000', '0', '0', '0', NULL, NULL, NULL),
(NULL, 'at > Don’t go out ___ midnight!', NULL, NULL, NULL, NULL, '', NULL, current_timestamp(), '0000-00-00 00:00:00.000000', '0', '0', '0', NULL, NULL, NULL),
(NULL, 'on > The museum is closed ___ Mondays.', NULL, NULL, NULL, NULL, '', NULL, current_timestamp(), '0000-00-00 00:00:00.000000', '0', '0', '0', NULL, NULL, NULL),
(NULL, 'at, at > She’s good ___ math but bad ___ drawing.', NULL, NULL, NULL, NULL, '', NULL, current_timestamp(), '0000-00-00 00:00:00.000000', '0', '0', '0', NULL, NULL, NULL),
(NULL, 'in > We’re going skiing ___ December.', NULL, NULL, NULL, NULL, '', NULL, current_timestamp(), '0000-00-00 00:00:00.000000', '0', '0', '0', NULL, NULL, NULL),
(NULL, 'on > There’s a poster hanging ___ the wall.', NULL, NULL, NULL, NULL, '', NULL, current_timestamp(), '0000-00-00 00:00:00.000000', '0', '0', '0', NULL, NULL, NULL),
(NULL, 'on > I’ll finish this report ___ time for the meeting.', NULL, NULL, NULL, NULL, '', NULL, current_timestamp(), '0000-00-00 00:00:00.000000', '0', '0', '0', NULL, NULL, NULL),
(NULL, 'on > The concert is ___ Friday night.', NULL, NULL, NULL, NULL, '', NULL, current_timestamp(), '0000-00-00 00:00:00.000000', '0', '0', '0', NULL, NULL, NULL),
(NULL, 'in > My birthday is ___ July.', NULL, NULL, NULL, NULL, '', NULL, current_timestamp(), '0000-00-00 00:00:00.000000', '0', '0', '0', NULL, NULL, NULL),
(NULL, 'at > She’s waiting ___ the bus stop.', NULL, NULL, NULL, NULL, '', NULL, current_timestamp(), '0000-00-00 00:00:00.000000', '0', '0', '0', NULL, NULL, NULL),
(NULL, 'on > The cat is sleeping ___ the sofa.', NULL, NULL, NULL, NULL, '', NULL, current_timestamp(), '0000-00-00 00:00:00.000000', '0', '0', '0', NULL, NULL, NULL),
(NULL, 'in > He usually studies ___ the evening.', NULL, NULL, NULL, NULL, '', NULL, current_timestamp(), '0000-00-00 00:00:00.000000', '0', '0', '0', NULL, NULL, NULL),
(NULL, 'at > The train arrives ___ 6:15 sharp.', NULL, NULL, NULL, NULL, '', NULL, current_timestamp(), '0000-00-00 00:00:00.000000', '0', '0', '0', NULL, NULL, NULL),
(NULL, 'on > There’s a crack ___ the ceiling.', NULL, NULL, NULL, NULL, '', NULL, current_timestamp(), '0000-00-00 00:00:00.000000', '0', '0', '0', NULL, NULL, NULL),
(NULL, 'in > They moved to London ___ 2018.', NULL, NULL, NULL, NULL, '', NULL, current_timestamp(), '0000-00-00 00:00:00.000000', '0', '0', '0', NULL, NULL, NULL),
(NULL, 'at > I’ll see you ___ the airport.', NULL, NULL, NULL, NULL, '', NULL, current_timestamp(), '0000-00-00 00:00:00.000000', '0', '0', '0', NULL, NULL, NULL),
(NULL, 'on > The meeting is ___ the first floor.', NULL, NULL, NULL, NULL, '', NULL, current_timestamp(), '0000-00-00 00:00:00.000000', '0', '0', '0', NULL, NULL, NULL),
(NULL, 'at > Someone is knocking ___ the door.', NULL, NULL, NULL, NULL, '', NULL, current_timestamp(), '0000-00-00 00:00:00.000000', '0', '0', '0', NULL, NULL, NULL),
(NULL, 'on > She wrote her name ___ the envelope.', NULL, NULL, NULL, NULL, '', NULL, current_timestamp(), '0000-00-00 00:00:00.000000', '0', '0', '0', NULL, NULL, NULL),
(NULL, 'in > The children are playing ___ the garden.', NULL, NULL, NULL, NULL, '', NULL, current_timestamp(), '0000-00-00 00:00:00.000000', '0', '0', '0', NULL, NULL, NULL),
(NULL, 'at > He’s really good ___ chess.', NULL, NULL, NULL, NULL, '', NULL, current_timestamp(), '0000-00-00 00:00:00.000000', '0', '0', '0', NULL, NULL, NULL),
(NULL, 'on > The movie will be released ___ Christmas Day.', NULL, NULL, NULL, NULL, '', NULL, current_timestamp(), '0000-00-00 00:00:00.000000', '0', '0', '0', NULL, NULL, NULL),
(NULL, 'in > I left my glasses ___ my backpack.', NULL, NULL, NULL, NULL, '', NULL, current_timestamp(), '0000-00-00 00:00:00.000000', '0', '0', '0', NULL, NULL, NULL),
(NULL, 'at > They arrived ___ the same time.', NULL, NULL, NULL, NULL, '', NULL, current_timestamp(), '0000-00-00 00:00:00.000000', '0', '0', '0', NULL, NULL, NULL),
(NULL, 'on > The picture is hanging ___ the fridge.', NULL, NULL, NULL, NULL, '', NULL, current_timestamp(), '0000-00-00 00:00:00.000000', '0', '0', '0', NULL, NULL, NULL),
(NULL, 'in > She’ll be ready ___ a moment.', NULL, NULL, NULL, NULL, '', NULL, current_timestamp(), '0000-00-00 00:00:00.000000', '0', '0', '0', NULL, NULL, NULL);

Reactor nuclear de sal fosa i Tori

En un reactor nuclear de sal fosa (del tipus MSR, Molten Salt Reactor), si es fa servir tori (Th) com a combustible nuclear, no és el tori en si mateix el que sofreix fissió, sinó un isòtop derivat: urani-233 ((^{233}\text{U})), que es produeix a partir del tori-232 ((^{232}\text{Th})) mitjançant transmutació. A continuació et detallo les reaccions nuclears principals que es produeixen:


1. Captura neutrònica pel tori-232

El tori-232, que és fèrtil (no fissil), absorbeix un neutró tèrmic i es converteix en tori-233:

[
^{232}\text{Th} + n \rightarrow ^{233}\text{Th}
]


2. Desintegració beta del tori-233

El (^{233}\text{Th}) és inestable i es desintegra mitjançant emissió beta ((\beta^-)) a protactini-233 amb una semivida d’aproximadament 22 minuts:

[
^{233}\text{Th} \xrightarrow{\beta^-} ^{233}\text{Pa} \quad (t_{1/2} \approx 22\ \text{min})
]


3. Desintegració beta del protactini-233

El (^{233}\text{Pa}) també és radioactiu i es desintegra a urani-233 ((^{233}\text{U})) amb una semivida d’uns 27 dies:

[
^{233}\text{Pa} \xrightarrow{\beta^-} ^{233}\text{U} \quad (t_{1/2} \approx 27\ \text{dies})
]


4. Fissió de l’urani-233

L’(^{233}\text{U}) és fissil i, quan absorbeix un neutró (especialment tèrmic), pot fissionar-se, alliberant:

  • Energia tèrmica (aprox. 200 MeV per fissió),
  • Neutrons addicionals (2–3 de mitjana),
  • Productes de fissió (elements més lleugers com criptó, bari, estronci, etc.).

Exemple simplificat de fissió:

[
^{233}\text{U} + n \rightarrow \text{productes de fissió} + 2\text{–}3\,n + \text{energia}
]

Els neutrons alliberats poden:

  • Mantenir la reacció en cadena,
  • Ser capturats per més (^{232}\text{Th}), produint més (^{233}\text{U}) (breeding, o «cria» de combustible).

Resum del cicle del tori en un MSR:

[
^{232}\text{Th} \xrightarrow{(n,\gamma)} ^{233}\text{Th} \xrightarrow{\beta^-} ^{233}\text{Pa} \xrightarrow{\beta^-} ^{233}\text{U} \xrightarrow{\text{fissió}} \text{energia + neutrons}
]


Observacions importants en reactors de sal fosa:

  • La sal fosa (normalment fluorurs com LiF-BeF₂, anomenat «FLiBe») actua com refrigerant i portador del combustible.
  • El tori i l’urani es dissolen com a fluorurs (p. ex., ThF₄, UF₄).
  • No hi ha «reaccions químiques» en el sentit convencional que alliberin energia; l’energia prové exclusivament de reaccions nuclears.
  • El disseny del MSR permet extreure en línia productes de fissió i, opcionalment, el protactini-233, per evitar la seva captura de neutrons (ja que (^{233}\text{Pa}) pot absorbir neutrons i convertir-se en (^{234}\text{Pa}), que no dona lloc a (^{233}\text{U})).

Avantatges del cicle del tori en MSR:

  • Abundància del tori (més que l’urani).
  • Menor producció de actínids menors (residus de llarga vida).
  • (^{233}\text{U}) té bones propietats neutròniques en espectre tèrmic.
  • El tori no és directament utilitzable per armes nuclears (encara que (^{233}\text{U}) sí que és armable, normalment està contaminat amb (^{232}\text{U}), que emet radiació gamma intensa, dificultant el seu ús clandestí).

Si tens interès en aspectes químics de les sals (com la corrosió, estabilitat dels fluorurs, etc.) o en el tractament de residus, també puc ampliar la informació.

Els reactors nuclears de sals foses (MSR) tenen diversos avantatges respecte als reactors convencionals d’urani (com els d’aigua lleugera). Els principals són:

? Seguretat

  • Més segurs intrínsecament: funcionen a pressió atmosfèrica, reduint el risc d’explosions.
  • Refredament passiu: si hi ha un problema, el combustible es pot drenar automàticament a un dipòsit segur on la reacció s’atura.
  • No hi ha fusió del nucli: el combustible ja és líquid.

♻️ Gestió de residus

  • Menys residus radioactius a llarg termini.
  • Poden reutilitzar residus nuclears existents com a combustible.
  • Especialment eficients si utilitzen tori, que genera residus menys perillosos que l’urani.

⚡ Eficiència

  • Major eficiència tèrmica: operen a temperatures més altes.
  • Millor aprofitament del combustible (menys urani o tori per la mateixa energia).

? Impacte ambiental

  • Menor necessitat de mineria.
  • Menor risc de contaminació massiva en cas d’accident.
  • Emissions indirectes de CO₂ molt baixes.

? Operació i manteniment

  • Disseny més simple (sense sistemes de pressió elevats).
  • Possibilitat de funcionament continu, ja que el combustible es pot processar sense aturar el reactor.

? Proliferació nuclear

  • Més difícils d’usar per a armes nuclears, especialment els reactors de tori.

⚠️ Inconvenients (per context)

  • Tecnologia encara poc desenvolupada comercialment.
  • Problemes de corrosió dels materials per les sals calentes.
  • Cost inicial elevat de recerca i desenvolupament.

Si vols, puc fer una taula comparativa, explicar-ho de manera més divulgativa, o enfocar-ho des del punt de vista del tori vs urani.

Google TV

APPS

Google TV (basat en Android TV) té accés a una gran part de l’ecosistema d’aplicacions d’Android, però no totes les aplicacions de Google Play Store són compatibles amb Google TV. La compatibilitat depèn de diversos factors:

  • L’aplicació ha de ser compatible amb Android TV o declarar suport per a pantalles grans i controls remots.
  • Algunes apps es mostren a Google TV només si estan optimitzades per a aquesta interfície (interfície de 10 peus – 10-foot UI).
  • Les aplicacions que requereixen touchscreen, sensors específics o funcionalitats no disponibles en TV (com càmera, GPS, etc.) sovint no es mostren a la botiga de Google TV.

Categories d’aplicacions disponibles a Google TV

Tot i que no existeix un llistat oficial complet i dinàmic mantingut per Google (la botiga es filtra dinàmicament segons el dispositiu), sí que es poden identificar categories i exemples comuns:

1. Streaming i vídeo

  • YouTube
  • YouTube TV
  • Netflix
  • Amazon Prime Video
  • Disney+
  • HBO Max / Max
  • Hulu
  • Apple TV+
  • Rakuten TV
  • Rakuten Viki
  • Crunchyroll
  • Plex
  • VLC (limitat)
  • Tubi
  • Peacock
  • Pluto TV
  • Sling TV

2. Música i àudio

  • Spotify
  • YouTube Music
  • Pandora
  • Tidal
  • Deezer
  • TuneIn Radio
  • iHeartRadio

3. Jocs (limitat)

  • Google Play Games (només jocs compatibles amb comandament)
  • Atari Flashback
  • Tetris
  • Solitaire
  • Chess.com
  • Stadia (descontinuat, però alguns dispositius encara el tenen)
  • Jocs de taula o casuals compatibles amb D-pad

4. Esports i notícies

  • ESPN
  • DAZN
  • CBS Sports
  • Sky News
  • BBC News
  • France 24

5. Educació i nens

  • PBS Kids
  • Khan Academy Kids
  • YouTube Kids
  • Lingokids

6. Productivitat i utilitats (molt limitat)

  • Google Chrome (com a navegador web senzill)
  • Google Photos
  • Google Drive (visualització bàsica)
  • Web Video Cast (per emetre contingut del mòbil)

7. Cast i control remot

  • Google Home
  • Android TV Remote Control
  • BubbleUPnP (per DLNA/UPnP)
  • Kodi (si instal·lat manualment)

Com trobar totes les apps disponibles per a Google TV

  1. Des del propi dispositiu Google TV:
  • Obre Google Play Store a la TV.
  • Ves a Apps o Categorias → només es mostren apps compatibles.
  1. Des del navegador (des del PC o mòbil):
  • Ves a https://play.google.com/store/apps
  • Accedeix des d’un compte vinculat al teu dispositiu Google TV.
  • Filtra per “Apps para Android TV” (normalment no apareix explícitament, però si cerques una app concreta, et diu “Compatible amb el teu dispositiu” si ho és).
  1. Instal·lació manual (APK):
  • Algunes apps no disponibles a la botiga es poden instal·lar manualment via ADB o Downloader, però no sempre funcionen bé sense interfície adaptada.

Limitacions importants

  • Cap accés directe a apps mòbils no optimitzades: per exemple, Instagram, WhatsApp, TikTok, etc., no estan disponibles.
  • La interfície depèn del fabricant: Hisense, Sony, TCL, etc., poden afegir o amagar apps segons acords comercials.
  • Google TV ≠ Android TV: Google TV és una capa d’interfície sobre Android TV. Totes les apps d’Android TV funcionen a Google TV, però la inversa no sempre és recomanada (si l’app no ha estat provada en entorn TV).

Si tens un tipus concret d’aplicació en ment (per exemple, “vull saber si hi ha apps de diccionari o TTS per a Google TV”), puc fer una cerca més específica.

Funció TTS dels navegadors: SpeechSynthesis

<?php

// ? ? ? CANVI 1: Mapeig de codis curts ('cat', 'eng', etc.) a codis d'idioma vàlids
session_start();

// Mapeig de codis curts a codis d'idioma vàlids per a l'àudio
// Mapeig de codis curts a codis d'idioma vàlids per a l'àudio
$langMap = [
    'cat' => 'ca-ES', // Català (Espanya)
    'eus' => 'eu-ES', // Euskera / Basc (Espanya)
    'cas' => 'es-ES', // Castellà / Espanyol (Espanya)
    'eng' => 'en-US', // Anglès (Estats Units)
    'fra' => 'fr-FR', // Francès (França)
    'ger' => 'de-DE', // Alemany (Alemanya)
    'por' => 'pt-PT', // Portuguès (Portugal)
    'ita' => 'it-IT'  // Italià (Itàlia)
];

// Comprovem si $_SESSION['lang'] està definit i és vàlid
$shortLang = $_SESSION['lang'] ; // Assigna 'cat' si $_SESSION['lang'] no està definit

// Assignem el codi d'àudio corresponent
$_SESSION['lang_audio'] = $langMap[$shortLang] ?? 'es-ES'; // Assegura que sigui 'ca-ES' si el codi no es troba al mapeig

// Mostrem el resultat
//echo "<br>SESSION['lang_audio']: " . $_SESSION['lang_audio'];

// ? ? ? fi CANVI
?>
<?php 
//echo $_SESSION['resposta']; //prova per veure que entra bé
   // exit ('31');
?>
 <form id="cercaForm" method="POST" action="BD_conexions/conexio_diccionari_bd.php">
 
             <div class="search-container">
                <!-- Camp de cerca -->
                <input 
            type="text" 
            id="cercar" 
            name="cercar" 
            onkeyup="" 
            placeholder="cercar.." 
            autofocus 
            value="<?php echo isset($_SESSION['cercar']) ? htmlspecialchars($_SESSION['cercar'], 
                    ENT_QUOTES, 'UTF-8') : ''; ?>"
                    >
                <!-- Botó de cerca -->
                <button type="submit">Cercar/Confirmar</button>
                <button id="speakButton" title="Llegir en veu alta">
                <span>?</span> 
                </button>
            </div> 
    <div>
  
    <p>
    <input type="button" value="<?php echo '-num-'.  $_SESSION['num'].'--'; ?>" style="display:none;">
    <input type="button" value="<?php echo '-id-'.  $_SESSION['id'].'--'; ?>" style="display:none;">
    <input type="button" value="<?php echo '-retorn-'. $_SESSION['nom'].'--'; ?>" style="display:none;">
    <input type="button" value="<?php echo '--'.  $_SESSION['lang_r'].'--'; ?>" style="display:none;">
    <input type="button" value="<?php echo '-1-'. $_SESSION['diccionari'].'--'; ?>" style="display:none;">
    <input type="button" value="<?php echo '-2-'. $_SESSION['diccionari_r'].'--'; ?>" style="display:none;">
    <input type="button" value="<?php echo '-3-'. $_SESSION['dic'].'--'; ?>" style="display:none;">
    <input type="button" value="<?php echo '--'. $_SESSION['bd']; ?>" style="display:none;">
    <input type="button" value="<?php echo '--'. $_SESSION['bd_r']; ?>" style="display:none;">
    <input
        type="text" 
        id="dicionari_A"
        name="dicionari_AJ" 
        onkeyup="" 
        placeholder=<?php echo htmlspecialchars($_SESSION['diccionari']); ?>
        autofocus
        data-diccionari=<?php echo htmlspecialchars($_SESSION['diccionari']); ?>
        style="display:none;">
</p>
<script>
        // Capturar elements del DOM
        const entrada = document.getElementById('cercar'); // Primer camp de text
        const boto = document.getElementById('boto');       // Botó
        const sortida = document.getElementById('replicar'); // Segon camp de text

        // Afegir un event listener al botó
        boto.addEventListener('click', function() {
            // Obtenir el valor de la primera entrada
            const text = entrada.value;

            // Assignar el valor a la segona entrada
            sortida.value = text;
        });
        
    </script>



<script>
  
  document.addEventListener('DOMContentLoaded', () => {
    const inputElement = document.getElementById('cercar');
    const speakBtn = document.getElementById('speakButton');
    let voices = [];

    // ? Carreguem l'idioma des de PHP
    const userLang = '<?php echo addslashes($_SESSION["lang_audio"]); ?>';

    function loadVoices() {
      voices = speechSynthesis.getVoices();
    }

    speechSynthesis.onvoiceschanged = loadVoices;
    loadVoices();

    function speakText() {
      const text = inputElement.value.trim();
      if (!text) return;

      const utterance = new SpeechSynthesisUtterance(text);

      // ? Busquem una veu que suporti l'idioma de la sessió (ex: 'ca-ES')
      let selectedVoice = voices.find(v => v.lang === userLang);

      // Si no n'hi ha d'exacta, provem amb el codi base (ex: 'ca')
      if (!selectedVoice) {
        const baseLang = userLang.split('-')[0];
        selectedVoice = voices.find(v => v.lang.startsWith(baseLang));
      }

      // Assignem la veu trobada (o només l'idioma si no hi ha veu específica)
      if (selectedVoice) {
        utterance.voice = selectedVoice;
        utterance.lang = selectedVoice.lang;
      } else {
        utterance.lang = userLang; // el navegador triarà una veu
      }

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

      speechSynthesis.cancel();
      speechSynthesis.speak(utterance);
    }

    speakBtn.addEventListener('click', (e) => {
      e.preventDefault();
      speakText();
    });
  });

</script>


    </div>
             <!-- Contenidor de l'autocompletat -->
             <div id="resultats_autocompletar_container">
                <ul id="resultats_autocompletar">
                    <!-- Resultats dinàmics via JavaScript -->
                </ul>
            </div>
            


        </form>
    

⭐⭐Simple text a veu





Conversor de text a veu


Conversor de text a veu





<!DOCTYPE html>
<html lang="ca">
<head>
  <meta charset="UTF-8">
  <title>Conversor de text a veu</title>
  <style>
    body {
      font-family: sans-serif;
      padding: 2em;
      background-color: #f4f4f4;
    }
    input {
      font-size: 1.2em;
      padding: 0.5em;
      margin-top: 1em;
      width: 100%;
      max-width: 500px;
    }
    #speakBtn {
      font-size: 2em;
      background: none;
      border: none;
      cursor: pointer;
      margin-top: 1em;
    }
    #speakBtn:hover {
      color: #0078D7;
    }
  </style>
</head>
<body>
  <h2>Conversor de text a veu</h2>
  <input type="text" id="cercar" placeholder="Escriu el text que vols escoltar">
  <button id="speakBtn" title="Escolta el text"></button>

  <script>
    document.addEventListener('DOMContentLoaded', () => {
      const inputElement = document.getElementById('cercar');
      const speakBtn = document.getElementById('speakBtn');
      let voices = [];

      function loadVoices() {
        voices = speechSynthesis.getVoices();
      }

      speechSynthesis.onvoiceschanged = loadVoices;
      loadVoices();

      function speakText() {
        const text = inputElement.value.trim();
        if (!text) return;

        const utterance = new SpeechSynthesisUtterance(text);
        const zira = voices.find(v => v.name === 'Microsoft Zira - English (United States)');

        if (zira) {
          utterance.voice = zira;
          utterance.lang = zira.lang;
        }

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

        speechSynthesis.cancel();
        speechSynthesis.speak(utterance);
      }

      speakBtn.addEventListener('click', speakText);

      // Repetir cada 10 segons
      setInterval(speakText, 10000);
    });
  </script>
</body>
</html>

Aquest script, mosta les veus que admet el navegador

```html
<!DOCTYPE html>
<html lang="ca">
<head>
  <meta charset="UTF-8">
  <title>Conversor de text a veu</title>

  <style>
    body {
      font-family: sans-serif;
      padding: 2em;
      background-color: #f4f4f4;
    }

    input {
      font-size: 1.2em;
      padding: 0.5em;
      margin-top: 1em;
      width: 100%;
      max-width: 500px;
      box-sizing: border-box;
    }

    select {
      font-size: 1.1em;
      padding: 0.5em;
      margin-top: 1em;
      width: 100%;
      max-width: 500px;
    }

    #languageInfo {
      margin-top: 1em;
      padding: 0.7em;
      background: white;
      border: 1px solid #ccc;
      border-radius: 6px;
      max-width: 500px;
    }

    #speakBtn {
      font-size: 2em;
      background: none;
      border: none;
      cursor: pointer;
      margin-top: 1em;
    }

    #speakBtn:hover {
      color: #0078D7;
    }

    #status {
      margin-top: 0.8em;
      font-size: 0.95em;
      color: #555;
    }
  </style>
</head>

<body>

  <h2>Conversor de text a veu</h2>

  <input
    type="text"
    id="cercar"
    placeholder="Escriu el text que vols escoltar"
  >

  <br>

  <select id="voiceSelect">
    <option value="">Carregant veus...</option>
  </select>

  <div id="languageInfo">
    <strong>Llengua carregada:</strong>
    <span id="currentLanguage">--</span>
  </div>

  <div id="status"></div>

  <button id="speakBtn" title="Escolta el text">🔊</button>


  <script>

    document.addEventListener('DOMContentLoaded', () => {

      const inputElement = document.getElementById('cercar');
      const speakBtn = document.getElementById('speakBtn');
      const voiceSelect = document.getElementById('voiceSelect');
      const currentLanguage = document.getElementById('currentLanguage');
      const status = document.getElementById('status');

      let voices = [];


      /*
       * Carrega totes les veus que Firefox posa a disposició
       */
      function loadVoices() {

        voices = speechSynthesis.getVoices();

        console.log("Veus disponibles:", voices);

        voiceSelect.innerHTML = '';

        if (voices.length === 0) {

          const option = document.createElement('option');
          option.textContent = 'No s’han trobat veus';
          voiceSelect.appendChild(option);

          currentLanguage.textContent = 'Cap veu disponible';

          return;
        }


        /*
         * Ordenem per llengua i després per nom
         */
        voices.sort((a, b) => {

          if (a.lang !== b.lang) {
            return a.lang.localeCompare(b.lang);
          }

          return a.name.localeCompare(b.name);

        });


        /*
         * Afegim totes les veus al desplegable
         */
        voices.forEach((voice, index) => {

          const option = document.createElement('option');

          option.value = index;

          option.textContent =
            `${voice.lang} — ${voice.name}`;

          voiceSelect.appendChild(option);

        });


        /*
         * Busquem primer una veu catalana
         *
         * ca
         * ca-ES
         * ca-ES-...
         */
        let catalanIndex = voices.findIndex(voice =>
          voice.lang.toLowerCase() === 'ca' ||
          voice.lang.toLowerCase().startsWith('ca-')
        );


        /*
         * Si existeix català, el seleccionem
         */
        if (catalanIndex !== -1) {

          voiceSelect.value = catalanIndex;

          updateLanguage(catalanIndex);

          status.textContent =
            '✓ S’ha detectat una veu catalana.';

        } else {

          /*
           * Si no hi ha català, agafem la primera veu
           */
          voiceSelect.value = 0;

          updateLanguage(0);

          status.textContent =
            '⚠ Firefox no ha detectat cap veu catalana.';

        }

      }


      /*
       * Actualitza el botó/informació de llengua
       */
      function updateLanguage(index) {

        const voice = voices[index];

        if (!voice) {
          currentLanguage.textContent = '--';
          return;
        }

        currentLanguage.textContent =
          `${voice.lang} — ${voice.name}`;

      }


      /*
       * Quan l'usuari canvia la veu
       */
      voiceSelect.addEventListener('change', () => {

        updateLanguage(voiceSelect.value);

      });


      /*
       * Parla el text
       */
      function speakText() {

        const text = inputElement.value.trim();

        if (!text) return;

        const selectedIndex = parseInt(voiceSelect.value);

        const voice = voices[selectedIndex];

        if (!voice) {

          status.textContent =
            'No hi ha cap veu seleccionada.';

          return;

        }


        const utterance =
          new SpeechSynthesisUtterance(text);


        /*
         * Utilitzem EXACTAMENT la veu seleccionada
         */
        utterance.voice = voice;

        utterance.lang = voice.lang;

        utterance.rate = 1;

        utterance.pitch = 1;


        speechSynthesis.cancel();

        speechSynthesis.speak(utterance);


        /*
         * Mostrem què està parlant realment
         */
        currentLanguage.textContent =
          `${voice.lang} — ${voice.name}`;

      }


      /*
       * Botó de reproducció
       */
      speakBtn.addEventListener('click', speakText);


      /*
       * Les veus poden arribar de manera asíncrona
       * a Firefox.
       */
      speechSynthesis.onvoiceschanged = loadVoices;

      loadVoices();


      /*
       * Repetir cada 10 segons
       */
      setInterval(speakText, 10000);

    });

  </script>

</body>
</html>
```

Sí. El problema principal del teu codi és que forces Microsoft Zira en anglès, així que Firefox no té cap possibilitat d’escollir català.

Et proposo aquesta versió:

  • mostra en un botó quina veu/llengua està carregada;
  • afegeix un desplegable amb totes les veus que Firefox detecti;
  • permet seleccionar manualment la veu;
  • indica si Català (ca) està disponible;
  • si hi ha una veu catalana, la selecciona automàticament;
  • manté la reproducció cada 10 segons.
<!DOCTYPE html>
<html lang="ca">
<head>
  <meta charset="UTF-8">
  <title>Conversor de text a veu</title>

  <style>
    body {
      font-family: sans-serif;
      padding: 2em;
      background-color: #f4f4f4;
    }

    input {
      font-size: 1.2em;
      padding: 0.5em;
      margin-top: 1em;
      width: 100%;
      max-width: 500px;
      box-sizing: border-box;
    }

    select {
      font-size: 1.1em;
      padding: 0.5em;
      margin-top: 1em;
      width: 100%;
      max-width: 500px;
    }

    #languageInfo {
      margin-top: 1em;
      padding: 0.7em;
      background: white;
      border: 1px solid #ccc;
      border-radius: 6px;
      max-width: 500px;
    }

    #speakBtn {
      font-size: 2em;
      background: none;
      border: none;
      cursor: pointer;
      margin-top: 1em;
    }

    #speakBtn:hover {
      color: #0078D7;
    }

    #status {
      margin-top: 0.8em;
      font-size: 0.95em;
      color: #555;
    }
  </style>
</head>

<body>

  <h2>Conversor de text a veu</h2>

  <input
    type="text"
    id="cercar"
    placeholder="Escriu el text que vols escoltar"
  >

  <br>

  <select id="voiceSelect">
    <option value="">Carregant veus...</option>
  </select>

  <div id="languageInfo">
    <strong>Llengua carregada:</strong>
    <span id="currentLanguage">--</span>
  </div>

  <div id="status"></div>

  <button id="speakBtn" title="Escolta el text">🔊</button>


  <script>

    document.addEventListener('DOMContentLoaded', () => {

      const inputElement = document.getElementById('cercar');
      const speakBtn = document.getElementById('speakBtn');
      const voiceSelect = document.getElementById('voiceSelect');
      const currentLanguage = document.getElementById('currentLanguage');
      const status = document.getElementById('status');

      let voices = [];


      /*
       * Carrega totes les veus que Firefox posa a disposició
       */
      function loadVoices() {

        voices = speechSynthesis.getVoices();

        console.log("Veus disponibles:", voices);

        voiceSelect.innerHTML = '';

        if (voices.length === 0) {

          const option = document.createElement('option');
          option.textContent = 'No s’han trobat veus';
          voiceSelect.appendChild(option);

          currentLanguage.textContent = 'Cap veu disponible';

          return;
        }


        /*
         * Ordenem per llengua i després per nom
         */
        voices.sort((a, b) => {

          if (a.lang !== b.lang) {
            return a.lang.localeCompare(b.lang);
          }

          return a.name.localeCompare(b.name);

        });


        /*
         * Afegim totes les veus al desplegable
         */
        voices.forEach((voice, index) => {

          const option = document.createElement('option');

          option.value = index;

          option.textContent =
            `${voice.lang} — ${voice.name}`;

          voiceSelect.appendChild(option);

        });


        /*
         * Busquem primer una veu catalana
         *
         * ca
         * ca-ES
         * ca-ES-...
         */
        let catalanIndex = voices.findIndex(voice =>
          voice.lang.toLowerCase() === 'ca' ||
          voice.lang.toLowerCase().startsWith('ca-')
        );


        /*
         * Si existeix català, el seleccionem
         */
        if (catalanIndex !== -1) {

          voiceSelect.value = catalanIndex;

          updateLanguage(catalanIndex);

          status.textContent =
            '✓ S’ha detectat una veu catalana.';

        } else {

          /*
           * Si no hi ha català, agafem la primera veu
           */
          voiceSelect.value = 0;

          updateLanguage(0);

          status.textContent =
            '⚠ Firefox no ha detectat cap veu catalana.';

        }

      }


      /*
       * Actualitza el botó/informació de llengua
       */
      function updateLanguage(index) {

        const voice = voices[index];

        if (!voice) {
          currentLanguage.textContent = '--';
          return;
        }

        currentLanguage.textContent =
          `${voice.lang} — ${voice.name}`;

      }


      /*
       * Quan l'usuari canvia la veu
       */
      voiceSelect.addEventListener('change', () => {

        updateLanguage(voiceSelect.value);

      });


      /*
       * Parla el text
       */
      function speakText() {

        const text = inputElement.value.trim();

        if (!text) return;

        const selectedIndex = parseInt(voiceSelect.value);

        const voice = voices[selectedIndex];

        if (!voice) {

          status.textContent =
            'No hi ha cap veu seleccionada.';

          return;

        }


        const utterance =
          new SpeechSynthesisUtterance(text);


        /*
         * Utilitzem EXACTAMENT la veu seleccionada
         */
        utterance.voice = voice;

        utterance.lang = voice.lang;

        utterance.rate = 1;

        utterance.pitch = 1;


        speechSynthesis.cancel();

        speechSynthesis.speak(utterance);


        /*
         * Mostrem què està parlant realment
         */
        currentLanguage.textContent =
          `${voice.lang} — ${voice.name}`;

      }


      /*
       * Botó de reproducció
       */
      speakBtn.addEventListener('click', speakText);


      /*
       * Les veus poden arribar de manera asíncrona
       * a Firefox.
       */
      speechSynthesis.onvoiceschanged = loadVoices;

      loadVoices();


      /*
       * Repetir cada 10 segons
       */
      setInterval(speakText, 10000);

    });

  </script>

</body>
</html>

Què veurem ara

El desplegable et mostrarà coses semblants a:

ca-ES — Veu catalana
de-DE — German ...
en-US — Microsoft Zira ...
en-US — ...
es-ES — Spanish ...
fr-FR — French ...

I a sota:

Llengua carregada: ca-ES — NOM_DE_LA_VEU

Això és especialment útil per al que estem investigant amb Firefox + Speech Dispatcher + Piper/Ona, perquè podrem veure si Firefox realment rep una veu catalana o simplement no la té disponible.

Una cosa important: que Linux tingui una veu catalana instal·lada no vol dir necessàriament que Firefox la vegi a speechSynthesis.getVoices(). Aquest és precisament el test que faria amb aquesta pàgina.

Si vols, el següent pas pot ser fer que el desplegable mostri només les llengües (Català, Espanyol, Anglès, etc.) i, en seleccionar-ne una, triï automàticament la millor veu disponible.



Sí. Ona és un model de veu que es guarda com a fitxers, principalment:

ca_ES-upc_ona-medium.onnx
ca_ES-upc_ona-medium.onnx.json

El .onnx és el model neuronal de la veu i el .json conté la configuració/metadades necessàries. Piper funciona precisament amb aquesta parella de fitxers. (GitHub)

I sí, hi ha veus Piper per a molts idiomes. El repositori oficial de veus actual indica 35 idiomes, i inclou català, castellà, anglès, francès, alemany, italià, portuguès, neerlandès, rus, xinès, japonès? etc.; però no tots els idiomes del món tenen una veu Piper disponible. (Hugging Face)

Per exemple, actualment tenim:

LlenguaVeus Piper
🇨🇦 Catalàupc_ona, upc_pau
🇪🇸 Castellàcarlfm, davefx, mls, sharvard…
🇬🇧 Anglès UKalan, alba, cori…
🇺🇸 Anglès USamy, lessac, ryan, sam…
🇫🇷 Francèsgilles, siwis, tom, upmc…
🇩🇪 Alemanythorsten, karlsson, ramona…
🇮🇹 Italiàpaola, riccardo
🇵🇹 Portuguèstugão, cadu, edresson…
🇳🇱 Neerlandèsdiverses
🇷🇺 Rusdenis, dmitri, irina, ruslan
🇨🇳 Xinèshuayan

La llista oficial mostra que Català (ca_ES) està molt ben representat, amb upc_ona i upc_pau. (GitHub)

I això encaixa molt bé amb el que estàvem parlant del Synology

Podríem tenir, per exemple:

/volume2/web/00-sapi_tts/
       │
       ├── voices/
       │     ├── ca_ES-upc_ona-medium.onnx
       │     ├── ca_ES-upc_ona-medium.onnx.json
       │     ├── es_ES-davefx-medium.onnx
       │     ├── es_ES-davefx-medium.onnx.json
       │     ├── en_US-ryan-medium.onnx
       │     ├── en_US-ryan-medium.onnx.json
       │     └── ...
       │
       └── servidor TTS

I el nostre servidor podria tenir un menú:

┌─────────────────────────────┐
│ Veu:                        │
│                             │
│ ▼ Català — Ona              │
│   Català — Pau              │
│   Castellà — DaveFX         │
│   Castellà — Sharvard       │
│   English US — Ryan         │
│   English US — Amy          │
│   Français — Tom            │
│   Deutsch — Thorsten        │
└─────────────────────────────┘

Aquesta és probablement la millor arquitectura per al teu TTS de la intranet. No depenem de les veus d’Edge/Firefox ni d’Internet: els models .onnx queden al Synology i Piper el

Sí. La diferència que veus entre Windows 11 i Deepin/Linux és real, i en el teu cas explica perfectament el missatge:

⚠️ Firefox no ha detectat cap veu catalana.

Windows 11

A Windows, Firefox pot accedir a les veus TTS disponibles al sistema, com la que veus a la captura:

Microsoft Zira Desktop - English (United States)

Per això el teu JavaScript speechSynthesis.getVoices() rep una llista de veus i el selector les pot mostrar.

Deepin/Linux

Aquí Firefox funciona d’una manera diferent. Firefox per a Linux no porta un conjunt propi de veus. La seva implementació de Web Speech synthesis utilitza Speech Dispatcher com a intermediari del sistema. Mozilla ho documenta explícitament. (Bugzilla)

El camí és aproximadament:

Firefox
↓
Web Speech API
↓
Speech Dispatcher
↓
mòdul TTS
↓
veu

I aquí apareix el problema interessant que ja estàvem treballant:

Firefox
↓
Speech Dispatcher
↓
Piper
↓
Ona

Aquesta arquitectura és perfectament possible, però Firefox no necessàriament veu automàticament totes les veus que tu tens instal·lades a Piper.

De fet, Mozilla té fins i tot una incidència específica sobre això: Piper està instal·lat com a mòdul de Speech Dispatcher però Firefox no incorpora les veus Piper a speechSynthesis.getVoices(). (Bugzilla)

I això explica exactament el que hem estat fent

Tu ja tens:

spd-say -L

i hi apareixen centenars/milers de veus.

Això significa que:

Speech Dispatcher sí que té veus.

Però el teu navegador està dient:

Firefox → speechSynthesis.getVoices()
↓
[]

Per tant, el problema no és que Deepin no tingui veus.

És que hi ha una diferència entre:

veus disponibles per al sistema

i

veus que Firefox Linux exposa a la Web Speech API

Mozilla reconeix precisament aquesta fragilitat de la implementació Linux. (Bugzilla)

Això també explica una cosa molt important

La nostra infraestructura:

Firefox → Speech Dispatcher → Piper → Ona

sí que pot funcionar per fer parlar Firefox, però hem de superar la capa de detecció de veus de speechSynthesis.

Per això jo faria ara una prova molt concreta al teu Deepin:

speechSynthesis.getVoices().map(v => ({
name: v.name,
lang: v.lang,
localService: v.localService
}))

Si retorna [], ja tenim confirmat que el problema està entre Firefox i Speech Dispatcher, no entre Piper i Ona.

I això és important perquè no cal abandonar Piper/Ona ni tornar a eSpeak. Podem intentar fer que Firefox vegi explícitament la veu que ja tens funcionant.

Incidència de Mozilla sobre Piper i Web Speech API

s utilitza localment.

I una cosa important: no cal instal·lar totes les veus. Podem començar amb, per exemple, Ona + una veu castellana + anglès + francès + alemany, i anar afegint-ne quan les necessitis.



Sí. La diferència que veus entre Windows 11 i Deepin/Linux és real, i en el teu cas explica perfectament el missatge:

⚠️ Firefox no ha detectat cap veu catalana.

Windows 11

A Windows, Firefox pot accedir a les veus TTS disponibles al sistema, com la que veus a la captura:

Microsoft Zira Desktop - English (United States)

Per això el teu JavaScript speechSynthesis.getVoices() rep una llista de veus i el selector les pot mostrar.

Deepin/Linux

Aquí Firefox funciona d’una manera diferent. Firefox per a Linux no porta un conjunt propi de veus. La seva implementació de Web Speech synthesis utilitza Speech Dispatcher com a intermediari del sistema. Mozilla ho documenta explícitament.

El camí és aproximadament:

Firefox
   ↓
Web Speech API
   ↓
Speech Dispatcher
   ↓
mòdul TTS
   ↓
veu

I aquí apareix el problema interessant que ja estàvem treballant:

Firefox
   ↓
Speech Dispatcher
   ↓
Piper
   ↓
Ona

Aquesta arquitectura és perfectament possible, però Firefox no necessàriament veu automàticament totes les veus que tu tens instal·lades a Piper.

De fet, Mozilla té fins i tot una incidència específica sobre això: Piper està instal·lat com a mòdul de Speech Dispatcher però Firefox no incorpora les veus Piper a speechSynthesis.getVoices().


I això explica exactament el que hem estat fent

Tu ja tens:

spd-say -L

i hi apareixen centenars/milers de veus.

Això significa que:

Speech Dispatcher sí que té veus.

Però el teu navegador està dient:

Firefox → speechSynthesis.getVoices()
                         ↓
                       []

Per tant, el problema no és que Deepin no tingui veus.

És que hi ha una diferència entre:

veus disponibles per al sistema

i

veus que Firefox Linux exposa a la Web Speech API

Mozilla reconeix precisament aquesta fragilitat de la implementació Linux.

Això també explica una cosa molt important

La nostra infraestructura:

Firefox → Speech Dispatcher → Piper → Ona

sí que pot funcionar per fer parlar Firefox, però hem de superar la capa de detecció de veus de speechSynthesis.

Per això jo faria ara una prova molt concreta al teu Deepin:

speechSynthesis.getVoices().map(v => ({
    name: v.name,
    lang: v.lang,
    localService: v.localService
}))

Si retorna [], ja tenim confirmat que el problema està entre Firefox i Speech Dispatcher, no entre Piper i Ona.

I això és important perquè no cal abandonar Piper/Ona ni tornar a eSpeak. Podem intentar fer que Firefox vegi explícitament la veu que ja tens funcionant.

Incidència de Mozilla sobre Piper i Web Speech API