⭐ 📚 Calibre (app taulell) amb la llibreria al Synology, des de Deepin

Guia completa: com obrir el Calibre des del terminal de Deepin (instal·lat des del taulell d’aplicacions)i com instal·lar el Calibre al Synology per tenir la biblioteca centralitzada al NAS.

Part 1 — Obrir el Calibre des del terminal de Deepin

El Calibre de Deepin s’instal·la com a aplicació Linglong (el format d’empaquetatge propi de Deepin). Per tant, no es llança amb una comanda normal, sinó amb ll-cli.

1. Comprovar que el Calibre està instal·lat

ll-cli list | grep -i calibre

Hauries de veure una línia com aquesta:

com.calibre-ebook.calibre   6.29.0.2   main   binary   calibre is a powerful and easy to use e-book manager.

2. Obrir el Calibre des del terminal

ll-cli run com.calibre-ebook.calibre

Si tens més d’una versió instal·lada i vols executar-ne una de concreta:

ll-cli run com.calibre-ebook.calibre/6.29.0.2

3. Muntar la llibreria del Synology

Si la teva biblioteca viu al NAS (com en el meu cas, a la carpeta llibres_deepin), cal muntar-la com a carpeta local perquè el Calibre hi pugui treballar.

Instal·lar les eines de muntatge SMB:

sudo apt install cifs-utils

Crear el fitxer de credencials (/etc/samba/calibre.creds):

username=EL_TEU_USUARI
password=LA_TEVA_CONTRASENYA

Protegir-lo:

sudo chmod 600 /etc/samba/calibre.creds

Afegir el muntatge permanent al /etc/fstab:

//192.168.1.41/llibres_deepin  /mnt/calibre-library  cifs  credentials=/etc/samba/calibre.creds,uid=1000,gid=1000,file_mode=0777,dir_mode=0777,nobrl,cache=none,iocharset=utf8,vers=3.0,nofail  0  0

💡 Les opcions nobrl i cache=none són clau per evitar bloquejos o corrupció de la base de dades SQLite del Calibre quan treballa sobre xarxa.

Muntar i comprovar:

sudo mkdir -p /mnt/calibre-library
sudo mount /mnt/calibre-library
ls /mnt/calibre-library

4. Apuntar el Calibre a la llibreria del NAS

Al Calibre: Canvia/Crea una llibreria → Fes servir una altra llibreria existent i navega fins a /mnt/calibre-library.

A partir d’aquí, el Calibre obre directament la llibreria del Synology, i el muntatge es fa sol a cada reinici gràcies al fstab.


Part 2 — Instal·lar el Calibre al Synology

Per tenir el Calibre funcionant al mateix NAS (com a servidor de contingut, accessible des de qualsevol dispositiu), la manera més neta és amb Docker.

Opció A — Docker (recomanada)

  1. Obre el Centre de paquets del DSM i instal·la Docker (o Container Manager a DSM 7.2+).
  2. Obre Docker → Registre → busca linuxserver/calibre → descarrega la imatge.
  3. A Contenidor → Crea → configura:
ParàmetreValor
Volum/config → carpeta de configuració al NAS
Volum/books → la teva llibreria (llibres_deepin)
Port8080:8080 (HTTP) i 8081:8081 (HTTPS)
VariablesPUID=1024, PGID=100, TZ=Europe/Madrid
  1. Inicia el contenidor i obre http://IP_DEL_SYNOLOGY:8080 des del navegador.

Per línia de comandes (SSH al NAS):

sudo docker run -d \
  --name=calibre \
  -e PUID=1024 -e PGID=100 -e TZ=Europe/Madrid \
  -p 8080:8080 -p 8081:8081 \
  -v /volume1/docker/calibre/config:/config \
  -v /volume1/llibres_deepin:/books \
  --restart unless-stopped \
  linuxserver/calibre

Opció B — Paquet SynoCommunity

  1. A DSM, afegeix el repositori de SynoCommunity (Centre de paquets → Configuració → Fonts de confiança).
  2. Cerca Calibre i instal·la’l.
  3. El servei quedarà actiu i accessible per xarxa.

Accedir-hi des del Deepin

Amb el servidor de contingut del Calibre actiu al Synology, des del Calibre local (Deepin) pots connectar-t’hi:

  • Connectar i compartir → Connecta’t al servidor de contingut → http://IP_DEL_SYNOLOGY:8080
  • O des del navegador de qualsevol dispositiu: http://IP_DEL_SYNOLOGY:8080 (interfície OPDS per a lectors com el Kindle o l’Android).

Resum de comandes útils:

ll-cli list | grep -i calibre        # veure la versió Linglong
ll-cli run com.calibre-ebook.calibre # obrir el Calibre
sudo mount /mnt/calibre-library       # muntar la llibreria del NAS

Si vols, et puc desar aquest post en un fitxer Markdown al teu ordinador (per exemple calibre-synology-deepin.md) perquè el tinguis llest per publicar o adaptar. T’ho faig? 😊

Aquí tens la nota preparada, llesta per enganxar al peu de l’entrada! 📝


📌 Nota d’observacions

a) La comanda calibre no arrenca res — Després d’instal·lar el Calibre natiu a /opt/calibre, la comanda calibre no funciona perquè l’instal·lador no va poder crear els enllaços a /usr/bin. Solució: crear l’enllaç manualment o usar la ruta completa amb la llibreria del Synology:

sudo ln -s /opt/calibre/calibre /usr/local/bin/calibre
/opt/calibre/calibre --with-library /mnt/calibre-library

b) El Calibre local també «recupera» la llibreria del Synology — Tant la versió Linglong com la nativa llegeixen la mateixa configuració (~/.config/calibre) i la llibreria viu al NAS (muntada a /mnt/calibre-library). Per això, amb qualsevol de les dues versions es veuen els mateixos llibres, sense duplicar res.

c) A Deepin (Unix) hi ha moltes més llengües que a Windows — El Calibre a Linux fa servir speech-dispatcher amb el motor espeak-ng, que suporta unes 100 llengües (català, castellà, èuscar, gallec…). A Windows, en canvi, només té les veus SAPI que tinguis instal·lades al sistema, que acostumen a ser poques.

d) El Calibre fa servir speech-dispatcher, no Python — Tot i que el missatge d’error pot fer dubtar, la síntesi de veu del Calibre passa per Qt (QTextToSpeech) → plugin speechd → speech-dispatcher → espeak-ng. Python és el llenguatge en què està escrit el programa, però no intervé en la veu. La confusió amb Firefox/Edge ve de Windows: allà tots dos navegadors fan servir les veus del sistema (SAPI), per això sonen igual; a Linux, Firefox no fa servir speech-dispatcher.


Si la vols amb una mica més d’estil (com una caixa destacada al final de l’article), només cal dir-m’ho i te la maqueto diferent. Gracies a tu! 😊

Veus($lang, $text, ‘🔊’)



Alternativa amb speech-dispatcher

Sí, i és ben senzill! 👌 Tens dues vies per fer la mateixa funció veus($lang, $tex) sense Python:

1. Amb speech-dispatcher → espeak-ng (via spd-say)

veus() {
  spd-say -l "$1" "$2"
}

Ús: veus ca "Hola, això és una prova" o veus en "Hello world"

Si la vols dins de Python igual que ara, només canvia la crida:

import subprocess

def veus(lang, text):
    subprocess.run(["spd-say", "-l", lang, text])

2. Amb espeak-ng directament (sense daemon)

veus() {
  espeak-ng -v "$1" "$2"
}

I en Python:

import subprocess

def veus(lang, text):
    subprocess.run(["espeak-ng", "-v", lang, text])

Diferència: la via 1 passa pel servei speech-dispatcher (més lent però unificat amb tot el sistema); la via 2 crida el motor directament (més ràpid i sense dependre de cap servei). Per a una funció pròpia, la via 2 sol ser més pràctica.

2. És gratuït? Qui n’és el propietari?

  • Gratuït? Sí, totalment. És programari lliure sota llicència GPL-3.0 (codi obert, sense cap cost).
  • Propietari? Cap empresa.espeak-ng és un projecte comunitari de codi obert, sense propietari comercial:
    • Va començar com a eSpeak, creat per Jonathan Duddington (també sense empresa al darrere).
    • espeak-ng és el fork comunitari mantingut per voluntaris, amb Reece H. Dunn com a mantenidor principal.
    • S’allotja a GitHub (espeak-ng/espeak-ng).

Per tant, és lliure, gratuït i obert: el pots usar, modificar i redistribuir sense pagar res a ningú. 🎉

Perfecte! Doncs quan acabis les teves coses pendents, aquí hi serem. 😊

I sí, tens tota la raó: espeak-ng directe és més ràpid perquè salta el daemon (speech-dispatcher) i parla amb el motor cara a cara. La diferència es nota sobretot en frases curtes o si fas moltes crides seguides.

Queda anotat per quan vulguis:

  1. ✅ Calibre natiu a /opt/calibre amb accés al TTS
  2. ⏳ Funció veus($lang, $tex) amb espeak-ng directe (substituint Python)
  3. ⏳ Post del blog amb tota la documentació (incloent les observacions a, b, c, d)

Quan tornis, ho enllestim! 🚀

Prova de veus

Amb el (venv) actiu i situat a /volume2/web/polidic:

🇷🇺 Prova de rus

gtts-cli "Здравствуйте, это проверка русского языка." --lang ru --output prova_ru.mp3

find /volume2/web/polidic -name ‘prova_eu.mp3’ -o -name ‘prova_es.mp3’

🇸🇪 Prova de suec

gtts-cli "Hej, det här är ett test av det svenska språket." --lang sv --output prova_sv.mp3

Permisos de les carpetes TTSW

Resum curt

Perfecte — ja tens el TTS funcionant. A continuació tens la configuració de permisos recomanada i les comandes exactes per aplicar‑la de manera segura.

Permisos recomanats (resum en taula)

RutaPropietari:GrupPermisosPropòsit
/volume2/web/beseit/ttswurqtejmi:users775Projecte; permet treballar al propietari i al grup
/volume2/web/beseit/ttsw/venvhttp:http755Entorn virtual usat per php‑fpm (evita donar escriptura a altres)
/volume2/web/beseit/ttsw/venv/bin/http:httprwxr-xr-x (755)Scripts executables del venv
/volume2/web/beseit/ttsw/venv/* (fitxers)http:httprw-r–r– (644)Fitxers del venv (pyvenv.cfg, libs, etc.)
/volume2/web/beseit/ttsw/tts.pyurqtejmi:users644 (o 755 si s’executa directament)Codi del projecte; lectura per a http, execució només si cal

Comandes exactes per aplicar-ho

Copia i enganxa aquestes comandes al teu terminal:

bash

# 1. directori del projecte (propietari teu usuari)
sudo chown -R urqtejmi:users /volume2/web/beseit/ttsw
sudo chmod 775 /volume2/web/beseit/ttsw

# 2. entorn virtual: propietari http i permisos segurs
sudo chown -R http:http /volume2/web/beseit/ttsw/venv
sudo find /volume2/web/beseit/ttsw/venv -type d -exec chmod 755 {} \;
sudo find /volume2/web/beseit/ttsw/venv -type f -exec chmod 644 {} \;

# 3. assegurar que els binaris són executables
sudo chmod a+rx /volume2/web/beseit/ttsw/venv/bin/*

# 4. fitxer del script TTS (lectura per a http)
sudo chown urqtejmi:users /volume2/web/beseit/ttsw/tts.py
sudo chmod 644 /volume2/web/beseit/ttsw/tts.py
# si vols executar-lo directament com a script (shebang), fer:
# sudo chmod 755 /volume2/web/beseit/ttsw/tts.py

Notes de seguretat i bones pràctiques

  • Per què http:http al venv: evita haver d’obrir permisos a “others” i limita la capacitat d’escriure dins l’entorn només al servei web.
  • Per què 775 al projecte: et permet a tu i al grup editar fitxers sense exposar‑los a tots els usuaris del sistema. Si no necessites que altres del grup accedeixin, usa 750.
  • No deixis 777: evita permisos 777 en directoris de projecte en producció.
  • Reinicia php‑fpm després de canviar propietaris o grups perquè els processos agafin els nous permisos.

Proves finals recomanades

Després d’aplicar els canvis, comprova:

bash

# provar python i l'import com a http
sudo -u http /volume2/web/beseit/ttsw/venv/bin/python -V
sudo -u http /volume2/web/beseit/ttsw/venv/bin/python -c "from gtts import gTTS; print('gTTS OK')"

# provar el script complet
cd /volume2/web/beseit/ttsw
sudo -u http /volume2/web/beseit/ttsw/venv/bin/python tts.py prova.txt prova.mp3 2>&1 | tee /tmp/tts-run.log
ls -lh prova.mp3
tail -n 200 /tmp/tts-run.log

Si vols, et dono un únic script que aplica tots els canvis i fa les proves automàticament; també puc explicar com integrar la ruta del venv/bin/python al teu PHP de forma robusta. Quin prefereixes?

Proves de veu amb Python

Prova 1

Ens situem sobre la carpeta on s’ha creat l’espai virtual TTSW i executem el fitxer de prova.txt amb el text carregat prèviament i executem l’escript tts.py que dona com sortida prova.mp3.

/volume1/web/ttsw/python3 tts.py prova.txt prova.mp3

/volume1/web/ttsw/venv/bin/python3 tts.py prova.txt prova.mp3

/volume1/web/ttsw/venv/bin/python3 tts.py prova.txt prova.mp3

o

/volume2/web/ttsw/venv/bin/python3 tts.py prova.txt prova.mp3



(venv) urqtejmi@synology_vall:/volume2/web/polidic$ python3.12 –version Python 3.12.13 (venv) urqtejmi@synology_vall:/volume2/web/polidic$ // voldria fer una prova de veu amb ttsg-cli pel terminal

Sí. I una petita correcció: crec que et refereixes a gtts-cli, la línia de comandes de gTTS, no ttsg-cli. gtts-cli permet generar un MP3 directament des del terminal i indicar l’idioma.

Com que ara tens el venv actiu i Python 3.12.13, jo faria la prova pas a pas.

1. Comprovar que estem dins del Python 3.12

python3 --version
which python3

Hauria de donar:

Python 3.12.13
/volume2/web/polidic/venv/bin/python3

2. Instal·lar gTTS dins d’aquest venv

python3 -m pip install gTTS

Això instal·larà gTTS dins de /volume2/web/polidic/venv, no al Python del sistema. La documentació oficial indica pip install gTTS com a instal·lació.

3. Comprovar gtts-cli

which gtts-cli

Hauria de sortir:

/volume2/web/polidic/venv/bin/gtts-cli

I:

gtts-cli --version

4. Primera prova en català

gtts-cli "Hola, sóc el Synology i estic parlant amb Python 3.12." --lang ca --output prova_ca.mp3

Si tot va bé:

ls -lh prova_ca.mp3

hauràs de tenir el fitxer MP3.

5. I podem provar anglès

gtts-cli "Hello, this is a test of Python 3.12." --lang en --output prova_en.mp3

I castellà:

gtts-cli "Hola, esta es una prueba de voz." --lang es --output prova_es.mp3

gtts-cli admet --lang i --output precisament per això.

Jo faria ara només els passos 1 i 2:

python3 --version
which python3
python3 -m pip install gTTS

Passa’m la sortida i després comprovarem que gtts-cli està realment dins del venv abans de generar cap veu.

Sí. I una petita correcció: crec que et refereixes a gtts-cli, la línia de comandes de gTTS, no ttsg-cli. gtts-cli permet generar un MP3 directament des del terminal i indicar l’idioma. (gTTS)

Com que ara tens el venv actiu i Python 3.12.13, jo faria la prova pas a pas.

1. Comprovar que estem dins del Python 3.12

python3 --version
which python3

Hauria de donar:

Python 3.12.13
/volume2/web/polidic/venv/bin/python3

2. Instal·lar gTTS dins d’aquest venv

python3 -m pip install gTTS

Això instal·larà gTTS dins de /volume2/web/polidic/venv, no al Python del sistema. La documentació oficial indica pip install gTTS com a instal·lació. (gTTS)

3. Comprovar gtts-cli

which gtts-cli

Hauria de sortir:

/volume2/web/polidic/venv/bin/gtts-cli

I:

gtts-cli --version

4. Primera prova en català

gtts-cli "Hola, sóc el Synology i estic parlant amb Python 3.12." --lang ca --output prova_ca.mp3

Si tot va bé:

ls -lh prova_ca.mp3

hauràs de tenir el fitxer MP3.

5. I podem provar anglès

gtts-cli "Hello, this is a test of Python 3.12." --lang en --output prova_en.mp3

I castellà:

gtts-cli "Hola, esta es una prueba de voz." --lang es --output prova_es.mp3

gtts-cli admet --lang i --output precisament per això. (gTTS)

Jo faria ara només els passos 1 i 2:

python3 --version
which python3
python3 -m pip install gTTS

Passa’m la sortida i després comprovarem que gtts-cli està realment dins del venv abans de generar cap veu.

Funció PHP amb entrada.txt i sortida .mp3 o .wav

$text = "Bon dia, com esteu?";
$lang = "ca";

fitxer = generarTTS($lang, $text);

i que produeixi automàticament:

sortida_ca.mp3
sortida_es.mp3
sortida_en.mp3

o bé .wav.

Com que tu ja tens gTTS al Synology, una opció molt pràctica és que PHP cridi un petit script Python que faci la conversió. Així PHP només s’encarrega de passar $lang i $text.

Per exemple, l’arquitectura seria:

PHP
 │
 │ $lang + $text
 ▼
Python / gTTS
 │
 ├── ca → sortida_ca.mp3
 ├── es → sortida_es.mp3
 └── en → sortida_en.mp3

També podem fer que la mateixa funció decideixi automàticament el nom:

generarTTS("ca", "Bon dia");
→ sortida_ca.mp3

generarTTS("es", "Buenos días");
→ sortida_es.mp3

generarTTS("en", "Good morning");
→ sortida_en.mp3

I encara millor: podem fer que la funció PHP retorni el nom/ruta del fitxer generat, cosa que encaixa molt bé amb el teu Flask/Python actual i amb la reproducció automàtica al navegador.

Si vols, et puc preparar ara mateix la funció PHP completa generarTTS($lang, $text), preparada per al teu Synology, incloent-hi ca, es i en i controlant errors.



Sí. Aquí veig un punt molt probable de problema de codificació: estem passant el text català directament com a argument de la shell:

escapeshellarg($text)

Amb textos com:

Bon dia, sóc l'Ona. Què tal?

els caràcters ó, è, ç, à, etc. poden donar problemes segons la configuració de locale de PHP/Apache.

La manera més segura és no passar mai el text UTF-8 directament per la shell. El codifiquem en Base64 a PHP i Python el recupera. Així ó, è, ç, emojis, apòstrofs, cometes, etc. arriben intactes.

PHP

Canviaria la part que crida Python per aquesta:

<?php

header('Content-Type: text/html; charset=UTF-8');

if ($_SERVER['REQUEST_METHOD'] === 'POST') {

    $idioma = trim($_POST['lang'] ?? 'ca');
    $text   = trim($_POST['text'] ?? '');

    if ($text !== '') {

        // ------------------------------------------------
        // CODIFICACIÓ SEGURA DEL TEXT
        // ------------------------------------------------
        // El text original és UTF-8.
        // El passem a Base64 per evitar problemes amb la shell.
        $text_base64 = base64_encode($text);

        // Fitxer de sortida
        $fitxer = __DIR__ . '/sortides/audio_' . $idioma . '.mp3';

        // Script Python
        $python = '/usr/bin/python3';
        $script = __DIR__ . '/python_server/tts.py';

        // ------------------------------------------------
        // ORDRE
        // ------------------------------------------------
        $ordre = $python . ' '
               . escapeshellarg($script) . ' '
               . escapeshellarg($idioma) . ' '
               . escapeshellarg($text_base64) . ' '
               . escapeshellarg($fitxer);

        // Executar Python
        exec($ordre . ' 2>&1', $sortida, $retorn);

        // ------------------------------------------------
        // RESULTAT
        // ------------------------------------------------
        if ($retorn === 0 && file_exists($fitxer)) {

            echo "<p style='font-family:Segoe UI; font-size:18px;'>";
            echo "Text: <strong>" . htmlspecialchars($text, ENT_QUOTES, 'UTF-8') . "</strong><br>";
            echo "Idioma: <strong>" . htmlspecialchars($idioma, ENT_QUOTES, 'UTF-8') . "</strong><br>";
            echo "Fitxer generat: <strong>"
                . htmlspecialchars(basename($fitxer), ENT_QUOTES, 'UTF-8')
                . "</strong>";
            echo "</p>";

            echo "<audio controls autoplay>";
            echo "<source src='sortides/"
                . htmlspecialchars(basename($fitxer), ENT_QUOTES, 'UTF-8')
                . "' type='audio/mpeg'>";
            echo "El navegador no suporta àudio.";
            echo "</audio>";

        } else {

            echo "<p style='color:red;'>Error executant TTS:</p>";

            echo "<pre>";
            echo htmlspecialchars(
                implode("\n", $sortida),
                ENT_QUOTES,
                'UTF-8'
            );
            echo "</pre>";
        }
    }
}
?>

Python

I el tts.py recuperaria el text així:

#!/usr/bin/env python3

import sys
import base64
from gtts import gTTS


if len(sys.argv) != 4:
    print("ERROR: arguments incorrectes")
    sys.exit(1)


idioma = sys.argv[1]
text_base64 = sys.argv[2]
fitxer = sys.argv[3]


try:

    # ------------------------------------------------
    # RECUPERAR TEXT UTF-8
    # ------------------------------------------------
    text = base64.b64decode(text_base64).decode("utf-8")

    print("IDIOMA:", idioma)
    print("TEXT:", text)

    # ------------------------------------------------
    # gTTS
    # ------------------------------------------------
    tts = gTTS(
        text=text,
        lang=idioma,
        slow=False
    )

    tts.save(fitxer)

    print("OK:", fitxer)

except Exception as e:

    print("ERROR:", repr(e))
    sys.exit(1)

Per què aquesta versió és més segura?

El recorregut queda:

FORMULARI
   │
   │  "Bon dia, sóc l'Ona. Què tal?"
   ▼
PHP
   │
   │ UTF-8
   ▼
base64_encode()
   │
   │ "Qm9uIGRpYSwgc..."
   ▼
Shell
   │
   ▼
Python
   │
   │ base64.b64decode()
   ▼
UTF-8
   │
   │ "Bon dia, sóc l'Ona. Què tal?"
   ▼
gTTS
   │
   ▼
audio_ca.mp3

Així el text no passa mai directament per la shell.

I això és especialment interessant en el teu cas perquè ja hem vist anteriorment que els accents són precisament un punt delicat en la cadena TTS.

Una prova molt bona

Abans de provar tot el formulari, podem fer una prova específica amb:

Bon dia, sóc l'Ona. Què tal? Àngel, després prendré cafè.

Si al print("TEXT:", text) de Python apareix exactament així:

TEXT: Bon dia, sóc l'Ona. Què tal? Àngel, després prendré cafè.

ja sabrem que PHP → Python està passant UTF-8 correctament.

Després només quedarà comprovar Python → gTTS → MP3.

Si vols, podem fer-ho encara més segur: mirem primer quin python3 està utilitzant el teu Apache/PHP i quin locale té, perquè al teu Synology això pot ser important.

Entorn virtual source venv/bin/activate

Molt bona pregunta! T’ho explico pas a pas:

Què fa source venv/bin/activate?

Aquesta comanda té dues parts:

1. source (o el seu equivalent .)

És una ordre interna de la terminal (bash) que diu:

«Llegeix aquest fitxer i executa les ordres que hi ha dins, com si les hagués escrit jo mateix a la terminal.»

És a dir, en lloc d’obrir el fitxer com un programa independent, el sistema llegeix les instruccions i les aplica al teu entorn actual.

2. venv/bin/activate

És el fitxer que conté les instruccions per «activar» l’entorn virtual. Quan l’executes, fa diverses coses:

  • Modifica el PATH: Li diu al sistema «quan executis python o pip, fes servir els que hi ha dins de venv/bin/, no els del sistema global».
  • Canvia el prompt: Afegeix (venv) al principi de la línia d’ordres perquè sàpigues que ets dins l’entorn virtual.
  • Defineix variables d’entorn: Configura VIRTUAL_ENV perquè Python sàpiga que està treballant en un entorn aïllat.

Per què és necessari?

Imagina que tens dos projectes Python:

  • Projecte A necessita la versió 1.0 d’una llibreria
  • Projecte B necessita la versió 2.0 de la mateixa llibreria

Si instal·lessis tot al sistema global, hi hauria conflictes. L’entorn virtual crea un «espai aïllat» per a cada projecte, on pots tenir les versions que necessitis sense que es barregin.

Quan fas source venv/bin/activate, li estàs dient al sistema:

«A partir d’ara, tots els paquets de Python que instal·li o utilitzi, fes-los servir d’aquesta carpeta venv, no del sistema global.»


Com crear-lo?

Comencem de zero:

# 1. Esborra l'entorn virtual existent
rm -rf venv

# 2. Verifica que Python3 està disponible
python3 --version

# 3. Crea un nou entorn virtual
python3 -m venv venv

# 4. Activa l'entorn virtual
source venv/bin/activate

# 5. Verifica que està activat (hauries de veure (venv) al prompt)
which python
which pip

Recorda per a futures sessions SSH

Cada vegada que et connectis al Synology per SSH i vulguis treballar amb el projecte, hauràs d’activar l’entorn:

cd /volume2/web/polidic
source venv/bin/activate
cd /volume2/web/polidic
source venv/bin/activate

deactivate

Instal·lar els paquets que necessitis

pip freeze > requirements.txt

Així, si algun dia has de recrear l’entorn, només hauràs de fer:

pip install -r requirements.txt

Com ho desactivo?

Quan acabis de treballar, simplement escrius:

deactivate

I el teu prompt tornarà a la normalitat (sense el (venv) al davant).


Ara que ja tens l’entorn activat (veiem el (venv) al teu prompt), ja pots executar:

./provar_en.sh

I l’script farà servir les llibreries de Python que hi ha dins de venv, no les del sistema global.

hem de matar el port el procés del port 5040:

sudo netstat -tulnp | grep 5040

sudo kill 14313 (el procés proporcionat a dalt)

(venv) urqtejmi@Synology_sec:/volume1/web/python_server$ sudo kill 14313
(venv) urqtejmi@Synology_sec:/volume1/web/python_server$

Instal·la les llibreries necessàries:

pip install gtts flask

./provar_en.sh
ls -lh *.mp3
Idiomalang
Catalàca
Anglèsen
Espanyoles
Francèsfr
Italiàit
Alemanyde
Portuguèspt
Neerlandèsnl

Sí. Aquí hi ha un punt important: Python no s’instal·la dins del venv. El que fem és crear el venv utilitzant el Python 3.12 que acabes d’instal·lar amb SynoCommunity. Així, quan activis venv, el python3 que veuràs serà el 3.12.

Ara mateix tens:

(venv) urqtejmi@synology_vall:/volume2/web/polidic$
which python3
/volume2/web/polidic/venv/bin/python3

Primer, sense tocar res, comprovaria quin Python 3.12 ha instal·lat SynoCommunity:

which python3.12
python3.12 --version

Si surt Python 3.12.x

Jo faria una còpia del venv actual i en crearia un de nou amb Python 3.12:

cd /volume2/web/polidic

Desactiva el venv actual:

deactivate

Fes una còpia de seguretat:

mv venv venv_old

Ara crea el nou venv amb Python 3.12:

python3.12 -m venv venv

Activa’l:

source venv/bin/activate

I comprova:

which python3
python3 --version

Hauria de sortir aproximadament:

/volume2/web/polidic/venv/bin/python3
Python 3.12.x

🎯 Això és exactament el que busquem.

Després instal·larem les llibreries

Una vegada confirmem que funciona:

python3 -m pip --version

i:

python3 -m pip list

Llavors podrem instal·lar al nou entorn Python 3.12 les llibreries que necessites per al teu projecte polidic, per exemple Flask, gTTS, etc., sense barrejar-les amb el Python del sistema.

No eliminaria encara venv_old, perquè així conservem l’entorn anterior per si alguna llibreria del Polidic depèn de la versió antiga.

Fes ara només aquestes dues:

which python3.12
python3.12 --version

i passa’m el resultat. A partir d’aquí et dono les ordres exactes per al Synology.

app.py de TTSG ducumentada

Sí. Ara que ja funciona perfectament, val molt la pena documentar-lo perquè aquest app.py ja té diverses peces interessants: recepció del formulari, gTTS, generació de MP3, escapament HTML, reproducció automàtica, retorn automàtic i servei del fitxer.

Et deixo una versió documentada però sense modificar el funcionament. He procurat que els comentaris expliquin sobretot per què existeix cada part.

```python
# ============================================================
# SERVIDOR TTS AMB FLASK + gTTS
# ============================================================
#
# Funció:
#   1. Rep text des del formulari web.
#   2. Genera un fitxer MP3 amb Google Text-to-Speech (gTTS).
#   3. Mostra una pàgina amb el reproductor d'àudio.
#   4. Reprodueix l'àudio automàticament.
#   5. Quan l'àudio acaba, retorna automàticament al formulari.
#
# Adreça del servidor:
#   http://192.168.1.49:5040
#
# Formulari principal:
#   http://192.168.1.49/python_server/
#
# Endpoint que rep el text:
#   POST /processa
#
# Fitxer d'àudio:
#   /sortida.mp3
#
# ============================================================


# ------------------------------------------------------------
# IMPORTACIONS
# ------------------------------------------------------------

from flask import Flask, request, send_from_directory
from gtts import gTTS
import os
import html


# ------------------------------------------------------------
# CREACIÓ DE L'APLICACIÓ FLASK
# ------------------------------------------------------------

app = Flask(__name__)


# ============================================================
# RUTA /processa
# ============================================================
#
# Aquesta és la ruta principal del sistema TTS.
#
# El formulari web envia el text mitjançant:
#
#     POST /processa
#
# El text arriba dins del camp:
#
#     text
#
# ============================================================

@app.route('/processa', methods=['POST'])
def processa():

    # --------------------------------------------------------
    # RECUPERAR EL TEXT DEL FORMULARI
    # --------------------------------------------------------
    #
    # request.form.get('text', '')
    #
    # Recupera el camp "text" enviat pel formulari.
    #
    # .strip()
    #
    # Elimina espais i salts de línia sobrants al principi
    # i al final del text.
    # --------------------------------------------------------

    text = request.form.get('text', '').strip()


    # --------------------------------------------------------
    # COMPROVACIÓ DE TEXT BUIT
    # --------------------------------------------------------
    #
    # Si l'usuari envia el formulari sense text, no intentem
    # generar cap àudio.
    #
    # El codi HTTP 400 indica una petició incorrecta.
    # --------------------------------------------------------

    if not text:
        return "No s'ha introduit cap text.", 400


    # ========================================================
    # GENERACIÓ DE L'ÀUDIO
    # ========================================================

    try:

        # ----------------------------------------------------
        # gTTS
        # ----------------------------------------------------
        #
        # text=text
        #     Text que volem convertir en veu.
        #
        # lang='ca'
        #     Català.
        #
        # slow=False
        #     Velocitat normal.
        #
        # ----------------------------------------------------

        tts = gTTS(
            text=text,
            lang='ca',
            slow=False
        )


        # ----------------------------------------------------
        # DESAR L'ÀUDIO
        # ----------------------------------------------------
        #
        # El fitxer sempre es genera amb el mateix nom:
        #
        #     sortida.mp3
        #
        # Per tant, una nova petició substitueix l'àudio
        # anterior.
        # ----------------------------------------------------

        tts.save("sortida.mp3")


    # --------------------------------------------------------
    # CONTROL D'ERRORS DE gTTS
    # --------------------------------------------------------

    except Exception as e:

        # Si gTTS falla, mostrem l'error i oferim un enllaç
        # per tornar al formulari.
        #
        # El codi HTTP 500 indica un error del servidor.

        return f'''
        <h1>Error generant l'àudio</h1>
        <p>{e}</p>
        <br>
        <a href="http://192.168.1.49/python_server/"
           style="
           padding: 10px 20px;
           background: #dc3545;
           color: white;
           text-decoration: none;
           border-radius: 5px;">
           Tornar al formulari
        </a>
        ''', 500


    # ========================================================
    # PROTECCIÓ DEL TEXT ABANS D'INSERTAR-LO A L'HTML
    # ========================================================
    #
    # L'usuari ha escrit el text i nosaltres el tornarem a
    # mostrar dins de la pàgina HTML.
    #
    # html.escape() converteix caràcters especials:
    #
    #     <   -> &lt;
    #     >   -> &gt;
    #     &   -> &amp;
    #     "   -> &quot;
    #
    # Això evita que el text introduït per l'usuari sigui
    # interpretat com a codi HTML.
    # --------------------------------------------------------

    text_segur = html.escape(text)


    # ========================================================
    # CONSTRUCCIÓ DE LA PÀGINA HTML DE RETORN
    # ========================================================
    #
    # Utilitzem una f-string perquè podem inserir:
    #
    #     {text_segur}
    #
    # dins de la pàgina.
    #
    # Important:
    #
    # Les claus de CSS i JavaScript apareixen com:
    #
    #     {{ }}
    #
    # perquè estem dins d'una f-string de Python.
    #
    # Python les transforma en:
    #
    #     { }
    #
    # quan genera l'HTML final.
    # ========================================================

    html_retorn = f'''
<!DOCTYPE html>

<html lang="ca">

<head>

    <meta charset="UTF-8">

    <title>Audio TTS</title>


    <!-- =====================================================
         ESTILS DE LA PÀGINA
         ===================================================== -->

    <style>

        body {{
            font-family: Arial, sans-serif;
            text-align: center;
            padding: 40px;
            background-color: #f4f4f9;
        }}


        .container {{
            background: white;
            padding: 30px;
            border-radius: 10px;
            box-shadow: 0 2px 10px rgba(0,0,0,0.1);
            max-width: 600px;
            margin: 0 auto;
        }}


        .btn-tornada {{
            display: inline-block;
            margin-top: 20px;
            padding: 12px 30px;
            background-color: #007bff;
            color: white;
            text-decoration: none;
            border-radius: 5px;
            font-weight: bold;
        }}


        .btn-tornada:hover {{
            background-color: #0056b3;
        }}


        audio {{
            width: 100%;
            margin: 20px 0;
        }}

    </style>

</head>


<body>


    <!-- =====================================================
         CONTENIDOR PRINCIPAL
         ===================================================== -->

    <div class="container">


        <!-- TÍTOL -->

        <h1>Audio generat correctament</h1>


        <!-- TEXT QUE HA ESCRIT L'USUARI -->

        <p>
            <strong>Text:</strong> {text_segur}
        </p>


        <!-- =================================================
             REPRODUCTOR D'ÀUDIO
             =================================================
             
             id="audio"
                 Permet localitzar l'element des de JavaScript.
             
             controls
                 Mostra els controls del reproductor.
             
             autoplay
                 Intenta començar la reproducció automàticament.
             ================================================= -->

        <audio id="audio" controls autoplay>

            <source
                src="/sortida.mp3"
                type="audio/mpeg">

            El navegador no pot reproduir aquest àudio.

        </audio>


        <br>


        <!-- =================================================
             BOTÓ DE RETORN MANUAL
             ================================================= -->

        <a
            href="http://192.168.1.49/python_server/"
            class="btn-tornada">

            &larr; Tornar al formulari

        </a>


    </div>


    <!-- =====================================================
         JAVASCRIPT
         =====================================================
         
         La funció d'aquest script és esperar que l'àudio
         finalitzi.
         
         Quan es produeix l'esdeveniment:
         
             ended
         
         el navegador retorna automàticament al formulari.
         ===================================================== -->

    <script>

        // Buscar l'element <audio> pel seu ID.

        const audio = document.getElementById("audio");


        // Comprovar que l'element existeix.

        if (audio) {{

            // ------------------------------------------------
            // EVENT "ended"
            // ------------------------------------------------
            //
            // Aquest esdeveniment es produeix quan el MP3
            // ha arribat al final.
            // ------------------------------------------------

            audio.addEventListener("ended", function() {{


                // Retorn automàtic al formulari.

                window.location.href =
                    "http://192.168.1.49/python_server/";

            }});

        }}

    </script>


</body>

</html>
'''


    # --------------------------------------------------------
    # RETORNAR LA PÀGINA AL NAVEGADOR
    # --------------------------------------------------------

    return html_retorn


# ============================================================
# RUTA /sortida.mp3
# ============================================================
#
# Aquesta ruta permet que el navegador pugui obtenir el fitxer
# MP3 que acabem de generar.
#
# Quan l'HTML conté:
#
#     <source src="/sortida.mp3">
#
# el navegador fa una petició:
#
#     GET /sortida.mp3
#
# i aquesta funció li retorna el fitxer.
# ============================================================

@app.route('/sortida.mp3')
def serve_mp3():

    return send_from_directory(
        os.getcwd(),
        'sortida.mp3',
        mimetype='audio/mpeg'
    )


# ============================================================
# INICI DEL SERVIDOR
# ============================================================
#
# Aquest bloc només s'executa quan iniciem directament
# aquest fitxer Python.
#
# host='0.0.0.0'
#     Permet connexions des d'altres equips de la xarxa.
#
# port=5040
#     Port utilitzat pel servidor Flask.
#
# debug=True
#     Activa el mode de depuració de Flask.
#
# ============================================================

if __name__ == '__main__':

    print(
        "Servidor Flask engegat a "
        "http://0.0.0.0:5040"
    )

    app.run(
        host='0.0.0.0',
        port=5040,
        debug=True
    )
```
# ============================================================
# SERVIDOR TTS AMB FLASK + gTTS
# ============================================================
#
# Funció:
#   1. Rep text des del formulari web.
#   2. Genera un fitxer MP3 amb Google Text-to-Speech (gTTS).
#   3. Mostra una pàgina amb el reproductor d'àudio.
#   4. Reprodueix l'àudio automàticament.
#   5. Quan l'àudio acaba, retorna automàticament al formulari.
#
# Adreça del servidor:
#   http://192.168.1.49:5040
#
# Formulari principal:
#   http://192.168.1.49/python_server/
#
# Endpoint que rep el text:
#   POST /processa
#
# Fitxer d'àudio:
#   /sortida.mp3
#
# ============================================================


# ------------------------------------------------------------
# IMPORTACIONS
# ------------------------------------------------------------

from flask import Flask, request, send_from_directory
from gtts import gTTS
import os
import html


# ------------------------------------------------------------
# CREACIÓ DE L'APLICACIÓ FLASK
# ------------------------------------------------------------

app = Flask(__name__)


# ============================================================
# RUTA /processa
# ============================================================
#
# Aquesta és la ruta principal del sistema TTS.
#
# El formulari web envia el text mitjançant:
#
#     POST /processa
#
# El text arriba dins del camp:
#
#     text
#
# ============================================================

@app.route('/processa', methods=['POST'])
def processa():

    # --------------------------------------------------------
    # RECUPERAR EL TEXT DEL FORMULARI
    # --------------------------------------------------------
    #
    # request.form.get('text', '')
    #
    # Recupera el camp "text" enviat pel formulari.
    #
    # .strip()
    #
    # Elimina espais i salts de línia sobrants al principi
    # i al final del text.
    # --------------------------------------------------------

    text = request.form.get('text', '').strip()


    # --------------------------------------------------------
    # COMPROVACIÓ DE TEXT BUIT
    # --------------------------------------------------------
    #
    # Si l'usuari envia el formulari sense text, no intentem
    # generar cap àudio.
    #
    # El codi HTTP 400 indica una petició incorrecta.
    # --------------------------------------------------------

    if not text:
        return "No s'ha introduit cap text.", 400


    # ========================================================
    # GENERACIÓ DE L'ÀUDIO
    # ========================================================

    try:

        # ----------------------------------------------------
        # gTTS
        # ----------------------------------------------------
        #
        # text=text
        #     Text que volem convertir en veu.
        #
        # lang='ca'
        #     Català.
        #
        # slow=False
        #     Velocitat normal.
        #
        # ----------------------------------------------------

        tts = gTTS(
            text=text,
            lang='ca',
            slow=False
        )


        # ----------------------------------------------------
        # DESAR L'ÀUDIO
        # ----------------------------------------------------
        #
        # El fitxer sempre es genera amb el mateix nom:
        #
        #     sortida.mp3
        #
        # Per tant, una nova petició substitueix l'àudio
        # anterior.
        # ----------------------------------------------------

        tts.save("sortida.mp3")


    # --------------------------------------------------------
    # CONTROL D'ERRORS DE gTTS
    # --------------------------------------------------------

    except Exception as e:

        # Si gTTS falla, mostrem l'error i oferim un enllaç
        # per tornar al formulari.
        #
        # El codi HTTP 500 indica un error del servidor.

        return f'''
        <h1>Error generant l'àudio</h1>
        <p>{e}</p>
        <br>
        <a href="http://192.168.1.49/python_server/"
           style="
           padding: 10px 20px;
           background: #dc3545;
           color: white;
           text-decoration: none;
           border-radius: 5px;">
           Tornar al formulari
        </a>
        ''', 500


    # ========================================================
    # PROTECCIÓ DEL TEXT ABANS D'INSERTAR-LO A L'HTML
    # ========================================================
    #
    # L'usuari ha escrit el text i nosaltres el tornarem a
    # mostrar dins de la pàgina HTML.
    #
    # html.escape() converteix caràcters especials:
    #
    #     <   -> &lt;
    #     >   -> &gt;
    #     &   -> &amp;
    #     "   -> &quot;
    #
    # Això evita que el text introduït per l'usuari sigui
    # interpretat com a codi HTML.
    # --------------------------------------------------------

    text_segur = html.escape(text)


    # ========================================================
    # CONSTRUCCIÓ DE LA PÀGINA HTML DE RETORN
    # ========================================================
    #
    # Utilitzem una f-string perquè podem inserir:
    #
    #     {text_segur}
    #
    # dins de la pàgina.
    #
    # Important:
    #
    # Les claus de CSS i JavaScript apareixen com:
    #
    #     {{ }}
    #
    # perquè estem dins d'una f-string de Python.
    #
    # Python les transforma en:
    #
    #     { }
    #
    # quan genera l'HTML final.
    # ========================================================

    html_retorn = f'''
<!DOCTYPE html>

<html lang="ca">

<head>

    <meta charset="UTF-8">

    <title>Audio TTS</title>


    <!-- =====================================================
         ESTILS DE LA PÀGINA
         ===================================================== -->

    <style>

        body {{
            font-family: Arial, sans-serif;
            text-align: center;
            padding: 40px;
            background-color: #f4f4f9;
        }}


        .container {{
            background: white;
            padding: 30px;
            border-radius: 10px;
            box-shadow: 0 2px 10px rgba(0,0,0,0.1);
            max-width: 600px;
            margin: 0 auto;
        }}


        .btn-tornada {{
            display: inline-block;
            margin-top: 20px;
            padding: 12px 30px;
            background-color: #007bff;
            color: white;
            text-decoration: none;
            border-radius: 5px;
            font-weight: bold;
        }}


        .btn-tornada:hover {{
            background-color: #0056b3;
        }}


        audio {{
            width: 100%;
            margin: 20px 0;
        }}

    </style>

</head>


<body>


    <!-- =====================================================
         CONTENIDOR PRINCIPAL
         ===================================================== -->

    <div class="container">


        <!-- TÍTOL -->

        <h1>Audio generat correctament</h1>


        <!-- TEXT QUE HA ESCRIT L'USUARI -->

        <p>
            <strong>Text:</strong> {text_segur}
        </p>


        <!-- =================================================
             REPRODUCTOR D'ÀUDIO
             =================================================
             
             id="audio"
                 Permet localitzar l'element des de JavaScript.
             
             controls
                 Mostra els controls del reproductor.
             
             autoplay
                 Intenta començar la reproducció automàticament.
             ================================================= -->

        <audio id="audio" controls autoplay>

            <source
                src="/sortida.mp3"
                type="audio/mpeg">

            El navegador no pot reproduir aquest àudio.

        </audio>


        <br>


        <!-- =================================================
             BOTÓ DE RETORN MANUAL
             ================================================= -->

        <a
            href="http://192.168.1.49/python_server/"
            class="btn-tornada">

            &larr; Tornar al formulari

        </a>


    </div>


    <!-- =====================================================
         JAVASCRIPT
         =====================================================
         
         La funció d'aquest script és esperar que l'àudio
         finalitzi.
         
         Quan es produeix l'esdeveniment:
         
             ended
         
         el navegador retorna automàticament al formulari.
         ===================================================== -->

    <script>

        // Buscar l'element <audio> pel seu ID.

        const audio = document.getElementById("audio");


        // Comprovar que l'element existeix.

        if (audio) {{

            // ------------------------------------------------
            // EVENT "ended"
            // ------------------------------------------------
            //
            // Aquest esdeveniment es produeix quan el MP3
            // ha arribat al final.
            // ------------------------------------------------

            audio.addEventListener("ended", function() {{


                // Retorn automàtic al formulari.

                window.location.href =
                    "http://192.168.1.49/python_server/";

            }});

        }}

    </script>


</body>

</html>
'''


    # --------------------------------------------------------
    # RETORNAR LA PÀGINA AL NAVEGADOR
    # --------------------------------------------------------

    return html_retorn


# ============================================================
# RUTA /sortida.mp3
# ============================================================
#
# Aquesta ruta permet que el navegador pugui obtenir el fitxer
# MP3 que acabem de generar.
#
# Quan l'HTML conté:
#
#     <source src="/sortida.mp3">
#
# el navegador fa una petició:
#
#     GET /sortida.mp3
#
# i aquesta funció li retorna el fitxer.
# ============================================================

@app.route('/sortida.mp3')
def serve_mp3():

    return send_from_directory(
        os.getcwd(),
        'sortida.mp3',
        mimetype='audio/mpeg'
    )


# ============================================================
# INICI DEL SERVIDOR
# ============================================================
#
# Aquest bloc només s'executa quan iniciem directament
# aquest fitxer Python.
#
# host='0.0.0.0'
#     Permet connexions des d'altres equips de la xarxa.
#
# port=5040
#     Port utilitzat pel servidor Flask.
#
# debug=True
#     Activa el mode de depuració de Flask.
#
# ============================================================

if __name__ == '__main__':

    print(
        "Servidor Flask engegat a "
        "http://0.0.0.0:5040"
    )

    app.run(
        host='0.0.0.0',
        port=5040,
        debug=True
    )

El flux que queda documentat

El sistema ara es pot entendre molt fàcilment com aquest circuit:

┌──────────────────────────────┐
│ http://192.168.1.49/        │
│ python_server/              │
│                              │
│ Formulari HTML               │
└──────────────┬───────────────┘
               │
               │ POST /processa
               │ text
               ▼
┌──────────────────────────────┐
│ Flask :5040                  │
│                              │
│ processa()                   │
│                              │
│ 1. Rep text                  │
│ 2. Comprova text             │
│ 3. gTTS català               │
│ 4. Genera sortida.mp3        │
└──────────────┬───────────────┘
               │
               │ HTML
               ▼
┌──────────────────────────────┐
│ Firefox / navegador          │
│                              │
│ <audio id="audio"            │
│        autoplay>              │
│                              │
│ Reprodueix sortida.mp3       │
└──────────────┬───────────────┘
               │
               │ esdeveniment
               │ "ended"
               ▼
┌──────────────────────────────┐
│ JavaScript                   │
│                              │
│ window.location.href         │
│                              │
│ ↓                            │
│ torna a python_server/       │
└──────────────────────────────┘

Una dada especialment important per conservar en la documentació: el problema que teníem amb {{ }} i }} ara queda explicat. No són un error en aquest app.py: són necessaris perquè tota la pàgina està dins d’una f-string (f'''...'''). Python converteix {{ en { i }} en } abans d’enviar l’HTML al navegador.

I el html.escape(text) també és una peça important: permet mostrar el text introduït sense que caràcters com <, > o & siguin interpretats com HTML.

Flux

Sí. I en el teu cas no cal tocar Flask ni el TTS: el que està impedint que soni automàticament és molt probablement la política d’autoreproducció del Firefox.

A la captura es veu que la pàgina ja genera correctament l’àudio i mostra el reproductor. El que falta és que Firefox faci play() sense que tu premis el botó.

1. Primer: fes que la pàgina HTML intenti reproduir-lo automàticament

Si ara tens alguna cosa semblant a:

<audio controls src="{{ audio_file }}"></audio>

canvia-ho per:

<audio controls autoplay src="{{ audio_file }}"></audio>

O, millor encara:

<audio id="audio" controls autoplay>
    <source src="{{ audio_file }}" type="audio/wav">
</audio>

<script>
window.addEventListener("load", function () {
    const audio = document.getElementById("audio");

    audio.play().catch(function(error) {
        console.log("Autoplay bloquejat:", error);
    });
});
</script>

Això farà que la pàgina demani a Firefox que comenci a reproduir l’àudio tan aviat com es carregui.

2. Però aquí hi ha la clau: Firefox

Firefox bloqueja per defecte la reproducció automàtica amb so en molts casos. Mozilla confirma que es pot permetre l’autoreproducció per a un lloc concret. (Suport de Mozilla)

Per tant, jo faria primer aquesta prova:

Obre:

http://192.168.1.49:5040/processa

i mira a l’esquerra de la barra d’adreces el símbol relacionat amb l’autoreproducció/permisos.

També pots anar a:

Firefox → Ajustos → Privadesa i seguretat → Permisos → Reproducció automàtica → Configuració

i permetre:

Permetre àudio i vídeo

per al teu servidor.

Mozilla permet precisament configurar aquesta excepció per lloc web, de manera que no cal canviar el comportament de tot Internet. (Suport de Mozilla)


3. Per al teu projecte jo faria això

El flux quedaria:

index.html
     │
     │ POST
     ▼
/processa
     │
     ▼
crear_html_i_tts.py
     │
     │ genera WAV
     ▼
pàgina Flask
     │
     │ AUTOPLAY
     ▼
🔊 ÀUDIO

És a dir, no necessitem cap botó «Play».

I això encaixa perfectament amb el que estàvem construint: el servidor genera l’àudio i el navegador simplement el reprodueix.

Una cosa important

Si vols que sigui 100% automàtic fins i tot quan Firefox obre una pestanya nova, hem de configurar dues coses:

  1. el <audio autoplay> / JavaScript de Flask;
  2. donar permís d’autoreproducció a 192.168.1.49:5040 al Firefox.

La segona és la que probablement et falta ara mateix.

Si vols, podem fer-ho pas a pas sobre el teu index.html actual i el Flask, i et preparo exactament el codi que has de substituir perquè quan entri a /processa soni directament l’àudio sense tocar absolutament res.

python3 –version al synology-> /usr/local/bin/python3 –version

/usr/local/bin/python3 --version

/usr/local/bin/python3 no està incorporat al PATH (nano ~/.profile : export PATH=»/usr/local/bin:$PATH») i per tant python3 –version no funciona.

s’ha de posar:

/usr/local/bin/python3 --version
/usr/local/bin/python3 --version

llançar el servidor:

python3 -m http.server 5060

/usr/local/bin/python3 -m http.server 5060