{"id":19220,"date":"2026-09-26T12:45:05","date_gmt":"2026-09-26T10:45:05","guid":{"rendered":"https:\/\/www.beseit.net\/?p=19220"},"modified":"2026-09-26T12:47:10","modified_gmt":"2026-09-26T10:47:10","slug":"app-py-de-ttsg-ducumentada","status":"publish","type":"post","link":"https:\/\/www.beseit.net\/?p=19220","title":{"rendered":"app.py de TTSG ducumentada"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\">S\u00ed. Ara que <strong>ja funciona perfectament<\/strong>, val molt la pena documentar-lo perqu\u00e8 aquest <code>app.py<\/code> ja t\u00e9 diverses peces interessants: recepci\u00f3 del formulari, gTTS, generaci\u00f3 de MP3, escapament HTML, reproducci\u00f3 autom\u00e0tica, retorn autom\u00e0tic i servei del fitxer.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Et deixo una versi\u00f3 <strong>documentada per\u00f2 sense modificar el funcionament<\/strong>. He procurat que els comentaris expliquin sobretot <em>per qu\u00e8<\/em> existeix cada part.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>```python\n# ============================================================\n# SERVIDOR TTS AMB FLASK + gTTS\n# ============================================================\n#\n# Funci\u00f3:\n#   1. Rep text des del formulari web.\n#   2. Genera un fitxer MP3 amb Google Text-to-Speech (gTTS).\n#   3. Mostra una p\u00e0gina amb el reproductor d'\u00e0udio.\n#   4. Reprodueix l'\u00e0udio autom\u00e0ticament.\n#   5. Quan l'\u00e0udio acaba, retorna autom\u00e0ticament al formulari.\n#\n# Adre\u00e7a del servidor:\n#   http:\/\/192.168.1.49:5040\n#\n# Formulari principal:\n#   http:\/\/192.168.1.49\/python_server\/\n#\n# Endpoint que rep el text:\n#   POST \/processa\n#\n# Fitxer d'\u00e0udio:\n#   \/sortida.mp3\n#\n# ============================================================\n\n\n# ------------------------------------------------------------\n# IMPORTACIONS\n# ------------------------------------------------------------\n\nfrom flask import Flask, request, send_from_directory\nfrom gtts import gTTS\nimport os\nimport html\n\n\n# ------------------------------------------------------------\n# CREACI\u00d3 DE L'APLICACI\u00d3 FLASK\n# ------------------------------------------------------------\n\napp = Flask(__name__)\n\n\n# ============================================================\n# RUTA \/processa\n# ============================================================\n#\n# Aquesta \u00e9s la ruta principal del sistema TTS.\n#\n# El formulari web envia el text mitjan\u00e7ant:\n#\n#     POST \/processa\n#\n# El text arriba dins del camp:\n#\n#     text\n#\n# ============================================================\n\n@app.route('\/processa', methods=&#91;'POST'])\ndef processa():\n\n    # --------------------------------------------------------\n    # RECUPERAR EL TEXT DEL FORMULARI\n    # --------------------------------------------------------\n    #\n    # request.form.get('text', '')\n    #\n    # Recupera el camp \"text\" enviat pel formulari.\n    #\n    # .strip()\n    #\n    # Elimina espais i salts de l\u00ednia sobrants al principi\n    # i al final del text.\n    # --------------------------------------------------------\n\n    text = request.form.get('text', '').strip()\n\n\n    # --------------------------------------------------------\n    # COMPROVACI\u00d3 DE TEXT BUIT\n    # --------------------------------------------------------\n    #\n    # Si l'usuari envia el formulari sense text, no intentem\n    # generar cap \u00e0udio.\n    #\n    # El codi HTTP 400 indica una petici\u00f3 incorrecta.\n    # --------------------------------------------------------\n\n    if not text:\n        return \"No s'ha introduit cap text.\", 400\n\n\n    # ========================================================\n    # GENERACI\u00d3 DE L'\u00c0UDIO\n    # ========================================================\n\n    try:\n\n        # ----------------------------------------------------\n        # gTTS\n        # ----------------------------------------------------\n        #\n        # text=text\n        #     Text que volem convertir en veu.\n        #\n        # lang='ca'\n        #     Catal\u00e0.\n        #\n        # slow=False\n        #     Velocitat normal.\n        #\n        # ----------------------------------------------------\n\n        tts = gTTS(\n            text=text,\n            lang='ca',\n            slow=False\n        )\n\n\n        # ----------------------------------------------------\n        # DESAR L'\u00c0UDIO\n        # ----------------------------------------------------\n        #\n        # El fitxer sempre es genera amb el mateix nom:\n        #\n        #     sortida.mp3\n        #\n        # Per tant, una nova petici\u00f3 substitueix l'\u00e0udio\n        # anterior.\n        # ----------------------------------------------------\n\n        tts.save(\"sortida.mp3\")\n\n\n    # --------------------------------------------------------\n    # CONTROL D'ERRORS DE gTTS\n    # --------------------------------------------------------\n\n    except Exception as e:\n\n        # Si gTTS falla, mostrem l'error i oferim un enlla\u00e7\n        # per tornar al formulari.\n        #\n        # El codi HTTP 500 indica un error del servidor.\n\n        return f'''\n        &lt;h1>Error generant l'\u00e0udio&lt;\/h1>\n        &lt;p>{e}&lt;\/p>\n        &lt;br>\n        &lt;a href=\"http:\/\/192.168.1.49\/python_server\/\"\n           style=\"\n           padding: 10px 20px;\n           background: #dc3545;\n           color: white;\n           text-decoration: none;\n           border-radius: 5px;\">\n           Tornar al formulari\n        &lt;\/a>\n        ''', 500\n\n\n    # ========================================================\n    # PROTECCI\u00d3 DEL TEXT ABANS D'INSERTAR-LO A L'HTML\n    # ========================================================\n    #\n    # L'usuari ha escrit el text i nosaltres el tornarem a\n    # mostrar dins de la p\u00e0gina HTML.\n    #\n    # html.escape() converteix car\u00e0cters especials:\n    #\n    #     &lt;   -> &amp;lt;\n    #     >   -> &amp;gt;\n    #     &amp;   -> &amp;amp;\n    #     \"   -> &amp;quot;\n    #\n    # Aix\u00f2 evita que el text introdu\u00eft per l'usuari sigui\n    # interpretat com a codi HTML.\n    # --------------------------------------------------------\n\n    text_segur = html.escape(text)\n\n\n    # ========================================================\n    # CONSTRUCCI\u00d3 DE LA P\u00c0GINA HTML DE RETORN\n    # ========================================================\n    #\n    # Utilitzem una f-string perqu\u00e8 podem inserir:\n    #\n    #     {text_segur}\n    #\n    # dins de la p\u00e0gina.\n    #\n    # Important:\n    #\n    # Les claus de CSS i JavaScript apareixen com:\n    #\n    #     {{ }}\n    #\n    # perqu\u00e8 estem dins d'una f-string de Python.\n    #\n    # Python les transforma en:\n    #\n    #     { }\n    #\n    # quan genera l'HTML final.\n    # ========================================================\n\n    html_retorn = f'''\n&lt;!DOCTYPE html>\n\n&lt;html lang=\"ca\">\n\n&lt;head>\n\n    &lt;meta charset=\"UTF-8\">\n\n    &lt;title>Audio TTS&lt;\/title>\n\n\n    &lt;!-- =====================================================\n         ESTILS DE LA P\u00c0GINA\n         ===================================================== -->\n\n    &lt;style>\n\n        body {{\n            font-family: Arial, sans-serif;\n            text-align: center;\n            padding: 40px;\n            background-color: #f4f4f9;\n        }}\n\n\n        .container {{\n            background: white;\n            padding: 30px;\n            border-radius: 10px;\n            box-shadow: 0 2px 10px rgba(0,0,0,0.1);\n            max-width: 600px;\n            margin: 0 auto;\n        }}\n\n\n        .btn-tornada {{\n            display: inline-block;\n            margin-top: 20px;\n            padding: 12px 30px;\n            background-color: #007bff;\n            color: white;\n            text-decoration: none;\n            border-radius: 5px;\n            font-weight: bold;\n        }}\n\n\n        .btn-tornada:hover {{\n            background-color: #0056b3;\n        }}\n\n\n        audio {{\n            width: 100%;\n            margin: 20px 0;\n        }}\n\n    &lt;\/style>\n\n&lt;\/head>\n\n\n&lt;body>\n\n\n    &lt;!-- =====================================================\n         CONTENIDOR PRINCIPAL\n         ===================================================== -->\n\n    &lt;div class=\"container\">\n\n\n        &lt;!-- T\u00cdTOL -->\n\n        &lt;h1>Audio generat correctament&lt;\/h1>\n\n\n        &lt;!-- TEXT QUE HA ESCRIT L'USUARI -->\n\n        &lt;p>\n            &lt;strong>Text:&lt;\/strong> {text_segur}\n        &lt;\/p>\n\n\n        &lt;!-- =================================================\n             REPRODUCTOR D'\u00c0UDIO\n             =================================================\n             \n             id=\"audio\"\n                 Permet localitzar l'element des de JavaScript.\n             \n             controls\n                 Mostra els controls del reproductor.\n             \n             autoplay\n                 Intenta comen\u00e7ar la reproducci\u00f3 autom\u00e0ticament.\n             ================================================= -->\n\n        &lt;audio id=\"audio\" controls autoplay>\n\n            &lt;source\n                src=\"\/sortida.mp3\"\n                type=\"audio\/mpeg\">\n\n            El navegador no pot reproduir aquest \u00e0udio.\n\n        &lt;\/audio>\n\n\n        &lt;br>\n\n\n        &lt;!-- =================================================\n             BOT\u00d3 DE RETORN MANUAL\n             ================================================= -->\n\n        &lt;a\n            href=\"http:\/\/192.168.1.49\/python_server\/\"\n            class=\"btn-tornada\">\n\n            &amp;larr; Tornar al formulari\n\n        &lt;\/a>\n\n\n    &lt;\/div>\n\n\n    &lt;!-- =====================================================\n         JAVASCRIPT\n         =====================================================\n         \n         La funci\u00f3 d'aquest script \u00e9s esperar que l'\u00e0udio\n         finalitzi.\n         \n         Quan es produeix l'esdeveniment:\n         \n             ended\n         \n         el navegador retorna autom\u00e0ticament al formulari.\n         ===================================================== -->\n\n    &lt;script>\n\n        \/\/ Buscar l'element &lt;audio> pel seu ID.\n\n        const audio = document.getElementById(\"audio\");\n\n\n        \/\/ Comprovar que l'element existeix.\n\n        if (audio) {{\n\n            \/\/ ------------------------------------------------\n            \/\/ EVENT \"ended\"\n            \/\/ ------------------------------------------------\n            \/\/\n            \/\/ Aquest esdeveniment es produeix quan el MP3\n            \/\/ ha arribat al final.\n            \/\/ ------------------------------------------------\n\n            audio.addEventListener(\"ended\", function() {{\n\n\n                \/\/ Retorn autom\u00e0tic al formulari.\n\n                window.location.href =\n                    \"http:\/\/192.168.1.49\/python_server\/\";\n\n            }});\n\n        }}\n\n    &lt;\/script>\n\n\n&lt;\/body>\n\n&lt;\/html>\n'''\n\n\n    # --------------------------------------------------------\n    # RETORNAR LA P\u00c0GINA AL NAVEGADOR\n    # --------------------------------------------------------\n\n    return html_retorn\n\n\n# ============================================================\n# RUTA \/sortida.mp3\n# ============================================================\n#\n# Aquesta ruta permet que el navegador pugui obtenir el fitxer\n# MP3 que acabem de generar.\n#\n# Quan l'HTML cont\u00e9:\n#\n#     &lt;source src=\"\/sortida.mp3\">\n#\n# el navegador fa una petici\u00f3:\n#\n#     GET \/sortida.mp3\n#\n# i aquesta funci\u00f3 li retorna el fitxer.\n# ============================================================\n\n@app.route('\/sortida.mp3')\ndef serve_mp3():\n\n    return send_from_directory(\n        os.getcwd(),\n        'sortida.mp3',\n        mimetype='audio\/mpeg'\n    )\n\n\n# ============================================================\n# INICI DEL SERVIDOR\n# ============================================================\n#\n# Aquest bloc nom\u00e9s s'executa quan iniciem directament\n# aquest fitxer Python.\n#\n# host='0.0.0.0'\n#     Permet connexions des d'altres equips de la xarxa.\n#\n# port=5040\n#     Port utilitzat pel servidor Flask.\n#\n# debug=True\n#     Activa el mode de depuraci\u00f3 de Flask.\n#\n# ============================================================\n\nif __name__ == '__main__':\n\n    print(\n        \"Servidor Flask engegat a \"\n        \"http:\/\/0.0.0.0:5040\"\n    )\n\n    app.run(\n        host='0.0.0.0',\n        port=5040,\n        debug=True\n    )\n```\n<\/code><\/pre>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\"># ============================================================\n# SERVIDOR TTS AMB FLASK + gTTS\n# ============================================================\n#\n# Funci\u00f3:\n#   1. Rep text des del formulari web.\n#   2. Genera un fitxer MP3 amb Google Text-to-Speech (gTTS).\n#   3. Mostra una p\u00e0gina amb el reproductor d'\u00e0udio.\n#   4. Reprodueix l'\u00e0udio autom\u00e0ticament.\n#   5. Quan l'\u00e0udio acaba, retorna autom\u00e0ticament al formulari.\n#\n# Adre\u00e7a del servidor:\n#   http:\/\/192.168.1.49:5040\n#\n# Formulari principal:\n#   http:\/\/192.168.1.49\/python_server\/\n#\n# Endpoint que rep el text:\n#   POST \/processa\n#\n# Fitxer d'\u00e0udio:\n#   \/sortida.mp3\n#\n# ============================================================\n\n\n# ------------------------------------------------------------\n# IMPORTACIONS\n# ------------------------------------------------------------\n\nfrom flask import Flask, request, send_from_directory\nfrom gtts import gTTS\nimport os\nimport html\n\n\n# ------------------------------------------------------------\n# CREACI\u00d3 DE L'APLICACI\u00d3 FLASK\n# ------------------------------------------------------------\n\napp = Flask(__name__)\n\n\n# ============================================================\n# RUTA \/processa\n# ============================================================\n#\n# Aquesta \u00e9s la ruta principal del sistema TTS.\n#\n# El formulari web envia el text mitjan\u00e7ant:\n#\n#     POST \/processa\n#\n# El text arriba dins del camp:\n#\n#     text\n#\n# ============================================================\n\n@app.route('\/processa', methods=['POST'])\ndef processa():\n\n    # --------------------------------------------------------\n    # RECUPERAR EL TEXT DEL FORMULARI\n    # --------------------------------------------------------\n    #\n    # request.form.get('text', '')\n    #\n    # Recupera el camp \"text\" enviat pel formulari.\n    #\n    # .strip()\n    #\n    # Elimina espais i salts de l\u00ednia sobrants al principi\n    # i al final del text.\n    # --------------------------------------------------------\n\n    text = request.form.get('text', '').strip()\n\n\n    # --------------------------------------------------------\n    # COMPROVACI\u00d3 DE TEXT BUIT\n    # --------------------------------------------------------\n    #\n    # Si l'usuari envia el formulari sense text, no intentem\n    # generar cap \u00e0udio.\n    #\n    # El codi HTTP 400 indica una petici\u00f3 incorrecta.\n    # --------------------------------------------------------\n\n    if not text:\n        return \"No s'ha introduit cap text.\", 400\n\n\n    # ========================================================\n    # GENERACI\u00d3 DE L'\u00c0UDIO\n    # ========================================================\n\n    try:\n\n        # ----------------------------------------------------\n        # gTTS\n        # ----------------------------------------------------\n        #\n        # text=text\n        #     Text que volem convertir en veu.\n        #\n        # lang='ca'\n        #     Catal\u00e0.\n        #\n        # slow=False\n        #     Velocitat normal.\n        #\n        # ----------------------------------------------------\n\n        tts = gTTS(\n            text=text,\n            lang='ca',\n            slow=False\n        )\n\n\n        # ----------------------------------------------------\n        # DESAR L'\u00c0UDIO\n        # ----------------------------------------------------\n        #\n        # El fitxer sempre es genera amb el mateix nom:\n        #\n        #     sortida.mp3\n        #\n        # Per tant, una nova petici\u00f3 substitueix l'\u00e0udio\n        # anterior.\n        # ----------------------------------------------------\n\n        tts.save(\"sortida.mp3\")\n\n\n    # --------------------------------------------------------\n    # CONTROL D'ERRORS DE gTTS\n    # --------------------------------------------------------\n\n    except Exception as e:\n\n        # Si gTTS falla, mostrem l'error i oferim un enlla\u00e7\n        # per tornar al formulari.\n        #\n        # El codi HTTP 500 indica un error del servidor.\n\n        return f'''\n        &lt;h1>Error generant l'\u00e0udio&lt;\/h1>\n        &lt;p>{e}&lt;\/p>\n        &lt;br>\n        &lt;a href=\"http:\/\/192.168.1.49\/python_server\/\"\n           style=\"\n           padding: 10px 20px;\n           background: #dc3545;\n           color: white;\n           text-decoration: none;\n           border-radius: 5px;\">\n           Tornar al formulari\n        &lt;\/a>\n        ''', 500\n\n\n    # ========================================================\n    # PROTECCI\u00d3 DEL TEXT ABANS D'INSERTAR-LO A L'HTML\n    # ========================================================\n    #\n    # L'usuari ha escrit el text i nosaltres el tornarem a\n    # mostrar dins de la p\u00e0gina HTML.\n    #\n    # html.escape() converteix car\u00e0cters especials:\n    #\n    #     &lt;   -> &amp;lt;\n    #     >   -> &amp;gt;\n    #     &amp;   -> &amp;amp;\n    #     \"   -> &amp;quot;\n    #\n    # Aix\u00f2 evita que el text introdu\u00eft per l'usuari sigui\n    # interpretat com a codi HTML.\n    # --------------------------------------------------------\n\n    text_segur = html.escape(text)\n\n\n    # ========================================================\n    # CONSTRUCCI\u00d3 DE LA P\u00c0GINA HTML DE RETORN\n    # ========================================================\n    #\n    # Utilitzem una f-string perqu\u00e8 podem inserir:\n    #\n    #     {text_segur}\n    #\n    # dins de la p\u00e0gina.\n    #\n    # Important:\n    #\n    # Les claus de CSS i JavaScript apareixen com:\n    #\n    #     {{ }}\n    #\n    # perqu\u00e8 estem dins d'una f-string de Python.\n    #\n    # Python les transforma en:\n    #\n    #     { }\n    #\n    # quan genera l'HTML final.\n    # ========================================================\n\n    html_retorn = f'''\n&lt;!DOCTYPE html>\n\n&lt;html lang=\"ca\">\n\n&lt;head>\n\n    &lt;meta charset=\"UTF-8\">\n\n    &lt;title>Audio TTS&lt;\/title>\n\n\n    &lt;!-- =====================================================\n         ESTILS DE LA P\u00c0GINA\n         ===================================================== -->\n\n    &lt;style>\n\n        body {{\n            font-family: Arial, sans-serif;\n            text-align: center;\n            padding: 40px;\n            background-color: #f4f4f9;\n        }}\n\n\n        .container {{\n            background: white;\n            padding: 30px;\n            border-radius: 10px;\n            box-shadow: 0 2px 10px rgba(0,0,0,0.1);\n            max-width: 600px;\n            margin: 0 auto;\n        }}\n\n\n        .btn-tornada {{\n            display: inline-block;\n            margin-top: 20px;\n            padding: 12px 30px;\n            background-color: #007bff;\n            color: white;\n            text-decoration: none;\n            border-radius: 5px;\n            font-weight: bold;\n        }}\n\n\n        .btn-tornada:hover {{\n            background-color: #0056b3;\n        }}\n\n\n        audio {{\n            width: 100%;\n            margin: 20px 0;\n        }}\n\n    &lt;\/style>\n\n&lt;\/head>\n\n\n&lt;body>\n\n\n    &lt;!-- =====================================================\n         CONTENIDOR PRINCIPAL\n         ===================================================== -->\n\n    &lt;div class=\"container\">\n\n\n        &lt;!-- T\u00cdTOL -->\n\n        &lt;h1>Audio generat correctament&lt;\/h1>\n\n\n        &lt;!-- TEXT QUE HA ESCRIT L'USUARI -->\n\n        &lt;p>\n            &lt;strong>Text:&lt;\/strong> {text_segur}\n        &lt;\/p>\n\n\n        &lt;!-- =================================================\n             REPRODUCTOR D'\u00c0UDIO\n             =================================================\n             \n             id=\"audio\"\n                 Permet localitzar l'element des de JavaScript.\n             \n             controls\n                 Mostra els controls del reproductor.\n             \n             autoplay\n                 Intenta comen\u00e7ar la reproducci\u00f3 autom\u00e0ticament.\n             ================================================= -->\n\n        &lt;audio id=\"audio\" controls autoplay>\n\n            &lt;source\n                src=\"\/sortida.mp3\"\n                type=\"audio\/mpeg\">\n\n            El navegador no pot reproduir aquest \u00e0udio.\n\n        &lt;\/audio>\n\n\n        &lt;br>\n\n\n        &lt;!-- =================================================\n             BOT\u00d3 DE RETORN MANUAL\n             ================================================= -->\n\n        &lt;a\n            href=\"http:\/\/192.168.1.49\/python_server\/\"\n            class=\"btn-tornada\">\n\n            &amp;larr; Tornar al formulari\n\n        &lt;\/a>\n\n\n    &lt;\/div>\n\n\n    &lt;!-- =====================================================\n         JAVASCRIPT\n         =====================================================\n         \n         La funci\u00f3 d'aquest script \u00e9s esperar que l'\u00e0udio\n         finalitzi.\n         \n         Quan es produeix l'esdeveniment:\n         \n             ended\n         \n         el navegador retorna autom\u00e0ticament al formulari.\n         ===================================================== -->\n\n    &lt;script>\n\n        \/\/ Buscar l'element &lt;audio> pel seu ID.\n\n        const audio = document.getElementById(\"audio\");\n\n\n        \/\/ Comprovar que l'element existeix.\n\n        if (audio) {{\n\n            \/\/ ------------------------------------------------\n            \/\/ EVENT \"ended\"\n            \/\/ ------------------------------------------------\n            \/\/\n            \/\/ Aquest esdeveniment es produeix quan el MP3\n            \/\/ ha arribat al final.\n            \/\/ ------------------------------------------------\n\n            audio.addEventListener(\"ended\", function() {{\n\n\n                \/\/ Retorn autom\u00e0tic al formulari.\n\n                window.location.href =\n                    \"http:\/\/192.168.1.49\/python_server\/\";\n\n            }});\n\n        }}\n\n    &lt;\/script>\n\n\n&lt;\/body>\n\n&lt;\/html>\n'''\n\n\n    # --------------------------------------------------------\n    # RETORNAR LA P\u00c0GINA AL NAVEGADOR\n    # --------------------------------------------------------\n\n    return html_retorn\n\n\n# ============================================================\n# RUTA \/sortida.mp3\n# ============================================================\n#\n# Aquesta ruta permet que el navegador pugui obtenir el fitxer\n# MP3 que acabem de generar.\n#\n# Quan l'HTML cont\u00e9:\n#\n#     &lt;source src=\"\/sortida.mp3\">\n#\n# el navegador fa una petici\u00f3:\n#\n#     GET \/sortida.mp3\n#\n# i aquesta funci\u00f3 li retorna el fitxer.\n# ============================================================\n\n@app.route('\/sortida.mp3')\ndef serve_mp3():\n\n    return send_from_directory(\n        os.getcwd(),\n        'sortida.mp3',\n        mimetype='audio\/mpeg'\n    )\n\n\n# ============================================================\n# INICI DEL SERVIDOR\n# ============================================================\n#\n# Aquest bloc nom\u00e9s s'executa quan iniciem directament\n# aquest fitxer Python.\n#\n# host='0.0.0.0'\n#     Permet connexions des d'altres equips de la xarxa.\n#\n# port=5040\n#     Port utilitzat pel servidor Flask.\n#\n# debug=True\n#     Activa el mode de depuraci\u00f3 de Flask.\n#\n# ============================================================\n\nif __name__ == '__main__':\n\n    print(\n        \"Servidor Flask engegat a \"\n        \"http:\/\/0.0.0.0:5040\"\n    )\n\n    app.run(\n        host='0.0.0.0',\n        port=5040,\n        debug=True\n    )<\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">El flux que queda documentat<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">El sistema ara es pot entendre molt f\u00e0cilment com aquest circuit:<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">\u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n\u2502 http:\/\/192.168.1.49\/        \u2502\n\u2502 python_server\/              \u2502\n\u2502                              \u2502\n\u2502 Formulari HTML               \u2502\n\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n               \u2502\n               \u2502 POST \/processa\n               \u2502 text\n               \u25bc\n\u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n\u2502 Flask :5040                  \u2502\n\u2502                              \u2502\n\u2502 processa()                   \u2502\n\u2502                              \u2502\n\u2502 1. Rep text                  \u2502\n\u2502 2. Comprova text             \u2502\n\u2502 3. gTTS catal\u00e0               \u2502\n\u2502 4. Genera sortida.mp3        \u2502\n\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n               \u2502\n               \u2502 HTML\n               \u25bc\n\u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n\u2502 Firefox \/ navegador          \u2502\n\u2502                              \u2502\n\u2502 &lt;audio id=\"audio\"            \u2502\n\u2502        autoplay>              \u2502\n\u2502                              \u2502\n\u2502 Reprodueix sortida.mp3       \u2502\n\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n               \u2502\n               \u2502 esdeveniment\n               \u2502 \"ended\"\n               \u25bc\n\u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n\u2502 JavaScript                   \u2502\n\u2502                              \u2502\n\u2502 window.location.href         \u2502\n\u2502                              \u2502\n\u2502 \u2193                            \u2502\n\u2502 torna a python_server\/       \u2502\n\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518<\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Una dada especialment important per conservar en la documentaci\u00f3:<\/strong> el problema que ten\u00edem amb <code>{{ }}<\/code> i <code>}}<\/code> ara queda explicat. <strong>No s\u00f3n un error<\/strong> en aquest <code>app.py<\/code>: s\u00f3n necessaris perqu\u00e8 tota la p\u00e0gina est\u00e0 dins d&#8217;una <code>f-string<\/code> (<code>f'''...'''<\/code>). Python converteix <code>{{<\/code> en <code>{<\/code> i <code>}}<\/code> en <code>}<\/code> abans d&#8217;enviar l&#8217;HTML al navegador.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">I el <code>html.escape(text)<\/code> tamb\u00e9 \u00e9s una pe\u00e7a important: permet mostrar el text introdu\u00eft sense que car\u00e0cters com <code>&lt;<\/code>, <code>&gt;<\/code> o <code>&amp;<\/code> siguin interpretats com HTML.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><\/p>\n","protected":false},"excerpt":{"rendered":"<p>S\u00ed. Ara que ja funciona perfectament, val molt la pena documentar-lo perqu\u00e8 aquest app.py ja t\u00e9 diverses peces interessants: recepci\u00f3 del formulari, gTTS, generaci\u00f3 de MP3, escapament HTML, reproducci\u00f3 autom\u00e0tica, retorn autom\u00e0tic i servei del fitxer. Et deixo una versi\u00f3 &hellip; <a href=\"https:\/\/www.beseit.net\/?p=19220\">Continua llegint <span class=\"meta-nav\">&rarr;<\/span><\/a><\/p>\n","protected":false},"author":2,"featured_media":3167,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[207,1],"tags":[],"class_list":["post-19220","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-deepin-linux","category-bloc-de-notes"],"_links":{"self":[{"href":"https:\/\/www.beseit.net\/index.php?rest_route=\/wp\/v2\/posts\/19220","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.beseit.net\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.beseit.net\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.beseit.net\/index.php?rest_route=\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/www.beseit.net\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=19220"}],"version-history":[{"count":1,"href":"https:\/\/www.beseit.net\/index.php?rest_route=\/wp\/v2\/posts\/19220\/revisions"}],"predecessor-version":[{"id":19221,"href":"https:\/\/www.beseit.net\/index.php?rest_route=\/wp\/v2\/posts\/19220\/revisions\/19221"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.beseit.net\/index.php?rest_route=\/wp\/v2\/media\/3167"}],"wp:attachment":[{"href":"https:\/\/www.beseit.net\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=19220"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.beseit.net\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=19220"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.beseit.net\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=19220"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}