{"id":19125,"date":"2026-09-16T23:30:54","date_gmt":"2026-09-16T21:30:54","guid":{"rendered":"https:\/\/www.beseit.net\/?p=19125"},"modified":"2026-09-17T07:38:17","modified_gmt":"2026-09-17T05:38:17","slug":"servidor-local-python","status":"publish","type":"post","link":"https:\/\/www.beseit.net\/?p=19125","title":{"rendered":"servidor local Python,"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\">I tant. La manera m\u00e9s senzilla \u00e9s fer que el navegador envi\u00ef el text a un <strong>servidor local Python<\/strong>, i aquest executi Docker\/Piper. Aix\u00ed no cal donar al navegador acc\u00e9s directe al terminal.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">1. Servidor Python<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Crea <code>servidor.py<\/code>: servidor.py<\/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=\"\">from http.server import BaseHTTPRequestHandler, HTTPServer\nimport subprocess\nimport json\n\nHOST = \"127.0.0.1\"\nPORT = 8765\n\nclass Handler(BaseHTTPRequestHandler):\n\n    def do_POST(self):\n        if self.path != \"\/parlar\":\n            self.send_error(404)\n            return\n\n        try:\n            length = int(self.headers.get(\"Content-Length\", 0))\n            data = json.loads(self.rfile.read(length))\n            text = data.get(\"text\", \"\").strip()\n\n            if not text:\n                self.send_error(400, \"Text buit\")\n                return\n\n            # Executem Piper dins del contenidor.\n            # El text entra per stdin, evitant problemes amb cometes.\n            cmd = [\n                \"docker\", \"exec\", \"-i\", \"piper\",\n                \"piper\",\n                \"--model\", \"\/config\/ca_ES-upc_ona-medium.onnx\",\n                \"--output_file\", \"-\"\n            ]\n\n            piper = subprocess.Popen(\n                cmd,\n                stdin=subprocess.PIPE,\n                stdout=subprocess.PIPE,\n                stderr=subprocess.PIPE\n            )\n\n            audio, error = piper.communicate(\n                text.encode(\"utf-8\"),\n                timeout=60\n            )\n\n            if piper.returncode != 0:\n                raise RuntimeError(error.decode(\"utf-8\", errors=\"replace\"))\n\n            # Reprodu\u00efm el PCM generat per Piper.\n            subprocess.run(\n                [\"aplay\", \"-f\", \"S16_LE\", \"-r\", \"22050\", \"-c\", \"1\"],\n                input=audio,\n                check=True\n            )\n\n            response = {\"ok\": True}\n\n            self.send_response(200)\n            self.send_header(\"Content-Type\", \"application\/json\")\n            self.send_header(\"Access-Control-Allow-Origin\", \"*\")\n            self.end_headers()\n            self.wfile.write(json.dumps(response).encode())\n\n        except Exception as e:\n            print(\"ERROR:\", e)\n\n            self.send_response(500)\n            self.send_header(\"Content-Type\", \"application\/json\")\n            self.send_header(\"Access-Control-Allow-Origin\", \"*\")\n            self.end_headers()\n            self.wfile.write(\n                json.dumps({\"ok\": False, \"error\": str(e)}).encode()\n            )\n\n    def do_OPTIONS(self):\n        self.send_response(204)\n        self.send_header(\"Access-Control-Allow-Origin\", \"*\")\n        self.send_header(\"Access-Control-Allow-Methods\", \"POST, OPTIONS\")\n        self.send_header(\"Access-Control-Allow-Headers\", \"Content-Type\")\n        self.end_headers()\n\n\nprint(f\"Ona preparada a http:\/\/{HOST}:{PORT}\")\nHTTPServer((HOST, PORT), Handler).serve_forever()\n<\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">2. La p\u00e0gina web<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Crea <code>ona.html<\/code>: ona.html<\/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=\"\">&lt;!DOCTYPE html>\n&lt;html lang=\"ca\">\n&lt;head>\n    &lt;meta charset=\"UTF-8\">\n    &lt;meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n\n    &lt;title>Ona \u00b7 UPC&lt;\/title>\n\n    &lt;style>\n        body {\n            margin: 0;\n            background: #111827;\n            color: white;\n            font-family: system-ui, sans-serif;\n            display: flex;\n            justify-content: center;\n            align-items: center;\n            min-height: 100vh;\n        }\n\n        .caixa {\n            width: min(600px, 90%);\n            background: #1f2937;\n            padding: 30px;\n            border-radius: 20px;\n            box-shadow: 0 15px 40px #0008;\n        }\n\n        h1 {\n            margin-top: 0;\n        }\n\n        textarea {\n            width: 100%;\n            height: 140px;\n            box-sizing: border-box;\n            resize: vertical;\n            padding: 15px;\n            border-radius: 12px;\n            border: 1px solid #4b5563;\n            background: #111827;\n            color: white;\n            font-size: 18px;\n        }\n\n        button {\n            margin-top: 15px;\n            width: 100%;\n            padding: 16px;\n            border: 0;\n            border-radius: 12px;\n            background: #2563eb;\n            color: white;\n            font-size: 18px;\n            cursor: pointer;\n        }\n\n        button:hover {\n            background: #1d4ed8;\n        }\n\n        button:disabled {\n            background: #4b5563;\n            cursor: wait;\n        }\n\n        #estat {\n            margin-top: 15px;\n            color: #9ca3af;\n        }\n    &lt;\/style>\n&lt;\/head>\n\n&lt;body>\n\n&lt;div class=\"caixa\">\n\n    &lt;h1>\ud83c\udf99\ufe0f Ona&lt;\/h1>\n\n    &lt;p>Escriu el que vols que digui:&lt;\/p>\n\n    &lt;textarea id=\"text\">Hola, s\u00f3c l'Ona, la meva veu s'ha desenvolupat a la UPC de Barcelona.&lt;\/textarea>\n\n    &lt;button id=\"parlar\">\n        \ud83d\udd0a Parla\n    &lt;\/button>\n\n    &lt;div id=\"estat\">Preparada&lt;\/div>\n\n&lt;\/div>\n\n&lt;script>\nconst text = document.getElementById(\"text\");\nconst boto = document.getElementById(\"parlar\");\nconst estat = document.getElementById(\"estat\");\n\nasync function parlar() {\n\n    const frase = text.value.trim();\n\n    if (!frase) {\n        estat.textContent = \"Escriu alguna cosa.\";\n        return;\n    }\n\n    boto.disabled = true;\n    estat.textContent = \"\ud83d\udde3\ufe0f L'Ona est\u00e0 parlant...\";\n\n    try {\n\n        const resposta = await fetch(\"http:\/\/127.0.0.1:8765\/parlar\", {\n            method: \"POST\",\n            headers: {\n                \"Content-Type\": \"application\/json\"\n            },\n            body: JSON.stringify({\n                text: frase\n            })\n        });\n\n        const resultat = await resposta.json();\n\n        if (!resultat.ok) {\n            throw new Error(resultat.error || \"Error desconegut\");\n        }\n\n        estat.textContent = \"\u2705 Fet\";\n\n    } catch (error) {\n\n        console.error(error);\n\n        estat.textContent =\n            \"\u274c No s'ha pogut contactar amb el servidor: \" +\n            error.message;\n\n    } finally {\n\n        boto.disabled = false;\n    }\n}\n\nboto.addEventListener(\"click\", parlar);\n\ntext.addEventListener(\"keydown\", function(e) {\n    if (e.ctrlKey &amp;&amp; e.key === \"Enter\") {\n        parlar();\n    }\n});\n&lt;\/script>\n\n&lt;\/body>\n&lt;\/html>\n:::\n\n## 3. Posar-ho en marxa\n\nAl Deepin, obre un terminal a la carpeta dels dos fitxers:\n\n```bash\npython3 servidor.py\n<\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Hauries de veure:<\/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=\"\">Ona preparada a http:\/\/127.0.0.1:8765\n<\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Despr\u00e9s obre <code>ona.html<\/code> amb Firefox o Chromium.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Escrius:<\/p>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p class=\"wp-block-paragraph\">Bon dia! S\u00f3c l&#8217;Ona i avui us acompanyar\u00e9.<\/p>\n<\/blockquote>\n\n\n\n<p class=\"wp-block-paragraph\">i prems <strong>\ud83d\udd0a Parla<\/strong>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">El recorregut ser\u00e0:<\/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=\"\">ona.html\n   \u2193\nhttp:\/\/127.0.0.1:8765\/parlar\n   \u2193\ndocker exec -i piper piper ...\n   \u2193\nca_ES-upc_ona-medium.onnx\n   \u2193\nPCM 22050 Hz\n   \u2193\naplay\n   \u2193\n\ud83d\udd0a altaveus\n<\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">Una difer\u00e8ncia important respecte al teu comandament<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Tu tenies:<\/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=\"\">docker exec piper bash -c \"echo \\\"Hola...\\\" | piper ...\"\n<\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Jo he fet servir:<\/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=\"\">docker exec -i piper piper ...\n<\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">i envio el text per <strong>stdin<\/strong>. \u00c9s millor perqu\u00e8 frases amb ap\u00f2strofs, cometes, accents, signes d&#8217;interrogaci\u00f3, etc. no trenquen el comandament.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Si vols, el seg\u00fcent pas pot ser fer-te una <strong>versi\u00f3 molt m\u00e9s bonica, tipus consola d&#8217;assistent<\/strong>, amb <strong>bot\u00f3 \ud83c\udf99\ufe0f, historial de converses, indicador que l&#8217;Ona est\u00e0 parlant i resposta autom\u00e0tica<\/strong>, mantenint Piper dins del teu Docker.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>I tant. La manera m\u00e9s senzilla \u00e9s fer que el navegador envi\u00ef el text a un servidor local Python, i aquest executi Docker\/Piper. Aix\u00ed no cal donar al navegador acc\u00e9s directe al terminal. 1. Servidor Python Crea servidor.py: servidor.py 2. &hellip; <a href=\"https:\/\/www.beseit.net\/?p=19125\">Continua llegint <span class=\"meta-nav\">&rarr;<\/span><\/a><\/p>\n","protected":false},"author":2,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[207,1],"tags":[],"class_list":["post-19125","post","type-post","status-publish","format-standard","hentry","category-deepin-linux","category-bloc-de-notes"],"_links":{"self":[{"href":"https:\/\/www.beseit.net\/index.php?rest_route=\/wp\/v2\/posts\/19125","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=19125"}],"version-history":[{"count":1,"href":"https:\/\/www.beseit.net\/index.php?rest_route=\/wp\/v2\/posts\/19125\/revisions"}],"predecessor-version":[{"id":19126,"href":"https:\/\/www.beseit.net\/index.php?rest_route=\/wp\/v2\/posts\/19125\/revisions\/19126"}],"wp:attachment":[{"href":"https:\/\/www.beseit.net\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=19125"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.beseit.net\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=19125"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.beseit.net\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=19125"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}