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:
#
# < -> <
# > -> >
# & -> &
# " -> "
#
# 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">
← 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:
#
# < -> <
# > -> >
# & -> &
# " -> "
#
# 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">
← 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.