{"id":19238,"date":"2026-09-27T10:17:44","date_gmt":"2026-09-27T08:17:44","guid":{"rendered":"https:\/\/www.beseit.net\/?p=19238"},"modified":"2026-09-27T17:28:42","modified_gmt":"2026-09-27T15:28:42","slug":"funcio-php-amb-entrada-txt-i-sortida-mp3-o-wav","status":"publish","type":"post","link":"https:\/\/www.beseit.net\/?p=19238","title":{"rendered":"Funci\u00f3  PHP amb entrada.txt i sortida .mp3 o .wav"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\"><\/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=\"\">$text = \"Bon dia, com esteu?\";\n$lang = \"ca\";\n\nfitxer = generarTTS($lang, $text);<\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">i que produeixi autom\u00e0ticament:<\/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=\"\">sortida_ca.mp3\nsortida_es.mp3\nsortida_en.mp3<\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">o b\u00e9 <code>.wav<\/code>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Com que tu ja tens <strong>gTTS al Synology<\/strong>, una opci\u00f3 molt pr\u00e0ctica \u00e9s que <strong>PHP cridi un petit script Python<\/strong> que faci la conversi\u00f3. Aix\u00ed PHP nom\u00e9s s&#8217;encarrega de passar <code>$lang<\/code> i <code>$text<\/code>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Per exemple, l&#8217;arquitectura seria:<\/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=\"\">PHP\n \u2502\n \u2502 $lang + $text\n \u25bc\nPython \/ gTTS\n \u2502\n \u251c\u2500\u2500 ca \u2192 sortida_ca.mp3\n \u251c\u2500\u2500 es \u2192 sortida_es.mp3\n \u2514\u2500\u2500 en \u2192 sortida_en.mp3<\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Tamb\u00e9 podem fer que la mateixa funci\u00f3 decideixi autom\u00e0ticament el nom:<\/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=\"\">generarTTS(\"ca\", \"Bon dia\");\n\u2192 sortida_ca.mp3\n\ngenerarTTS(\"es\", \"Buenos d\u00edas\");\n\u2192 sortida_es.mp3\n\ngenerarTTS(\"en\", \"Good morning\");\n\u2192 sortida_en.mp3<\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>I encara millor:<\/strong> podem fer que la funci\u00f3 PHP <strong>retorni el nom\/ruta del fitxer generat<\/strong>, cosa que encaixa molt b\u00e9 amb el teu Flask\/Python actual i amb la reproducci\u00f3 autom\u00e0tica al navegador.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Si vols, et puc preparar ara mateix <strong>la funci\u00f3 PHP completa <code>generarTTS($lang, $text)<\/code><\/strong>, preparada per al teu Synology, incloent-hi <code>ca<\/code>, <code>es<\/code> i <code>en<\/code> i controlant errors.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<p class=\"wp-block-paragraph\">S\u00ed. Aqu\u00ed veig <strong>un punt molt probable de problema de codificaci\u00f3<\/strong>: estem passant el text catal\u00e0 directament com a argument de la shell:<\/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=\"\">escapeshellarg($text)<\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Amb textos com:<\/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=\"\">Bon dia, s\u00f3c l'Ona. Qu\u00e8 tal?<\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">els car\u00e0cters <code>\u00f3<\/code>, <code>\u00e8<\/code>, <code>\u00e7<\/code>, <code>\u00e0<\/code>, etc. poden donar problemes segons la configuraci\u00f3 de <code>locale<\/code> de PHP\/Apache.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">La manera m\u00e9s segura \u00e9s <strong>no passar mai el text UTF-8 directament per la shell<\/strong>. El codifiquem en Base64 a PHP i Python el recupera. Aix\u00ed <code>\u00f3<\/code>, <code>\u00e8<\/code>, <code>\u00e7<\/code>, emojis, ap\u00f2strofs, cometes, etc. arriben intactes.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">PHP<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Canviaria la part que crida Python per aquesta:<\/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;?php\n\nheader('Content-Type: text\/html; charset=UTF-8');\n\nif ($_SERVER['REQUEST_METHOD'] === 'POST') {\n\n    $idioma = trim($_POST['lang'] ?? 'ca');\n    $text   = trim($_POST['text'] ?? '');\n\n    if ($text !== '') {\n\n        \/\/ ------------------------------------------------\n        \/\/ CODIFICACI\u00d3 SEGURA DEL TEXT\n        \/\/ ------------------------------------------------\n        \/\/ El text original \u00e9s UTF-8.\n        \/\/ El passem a Base64 per evitar problemes amb la shell.\n        $text_base64 = base64_encode($text);\n\n        \/\/ Fitxer de sortida\n        $fitxer = __DIR__ . '\/sortides\/audio_' . $idioma . '.mp3';\n\n        \/\/ Script Python\n        $python = '\/usr\/bin\/python3';\n        $script = __DIR__ . '\/python_server\/tts.py';\n\n        \/\/ ------------------------------------------------\n        \/\/ ORDRE\n        \/\/ ------------------------------------------------\n        $ordre = $python . ' '\n               . escapeshellarg($script) . ' '\n               . escapeshellarg($idioma) . ' '\n               . escapeshellarg($text_base64) . ' '\n               . escapeshellarg($fitxer);\n\n        \/\/ Executar Python\n        exec($ordre . ' 2>&amp;1', $sortida, $retorn);\n\n        \/\/ ------------------------------------------------\n        \/\/ RESULTAT\n        \/\/ ------------------------------------------------\n        if ($retorn === 0 &amp;&amp; file_exists($fitxer)) {\n\n            echo \"&lt;p style='font-family:Segoe UI; font-size:18px;'>\";\n            echo \"Text: &lt;strong>\" . htmlspecialchars($text, ENT_QUOTES, 'UTF-8') . \"&lt;\/strong>&lt;br>\";\n            echo \"Idioma: &lt;strong>\" . htmlspecialchars($idioma, ENT_QUOTES, 'UTF-8') . \"&lt;\/strong>&lt;br>\";\n            echo \"Fitxer generat: &lt;strong>\"\n                . htmlspecialchars(basename($fitxer), ENT_QUOTES, 'UTF-8')\n                . \"&lt;\/strong>\";\n            echo \"&lt;\/p>\";\n\n            echo \"&lt;audio controls autoplay>\";\n            echo \"&lt;source src='sortides\/\"\n                . htmlspecialchars(basename($fitxer), ENT_QUOTES, 'UTF-8')\n                . \"' type='audio\/mpeg'>\";\n            echo \"El navegador no suporta \u00e0udio.\";\n            echo \"&lt;\/audio>\";\n\n        } else {\n\n            echo \"&lt;p style='color:red;'>Error executant TTS:&lt;\/p>\";\n\n            echo \"&lt;pre>\";\n            echo htmlspecialchars(\n                implode(\"\\n\", $sortida),\n                ENT_QUOTES,\n                'UTF-8'\n            );\n            echo \"&lt;\/pre>\";\n        }\n    }\n}\n?><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">Python<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">I el <code>tts.py<\/code> recuperaria el text aix\u00ed:<\/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=\"\">#!\/usr\/bin\/env python3\n\nimport sys\nimport base64\nfrom gtts import gTTS\n\n\nif len(sys.argv) != 4:\n    print(\"ERROR: arguments incorrectes\")\n    sys.exit(1)\n\n\nidioma = sys.argv[1]\ntext_base64 = sys.argv[2]\nfitxer = sys.argv[3]\n\n\ntry:\n\n    # ------------------------------------------------\n    # RECUPERAR TEXT UTF-8\n    # ------------------------------------------------\n    text = base64.b64decode(text_base64).decode(\"utf-8\")\n\n    print(\"IDIOMA:\", idioma)\n    print(\"TEXT:\", text)\n\n    # ------------------------------------------------\n    # gTTS\n    # ------------------------------------------------\n    tts = gTTS(\n        text=text,\n        lang=idioma,\n        slow=False\n    )\n\n    tts.save(fitxer)\n\n    print(\"OK:\", fitxer)\n\nexcept Exception as e:\n\n    print(\"ERROR:\", repr(e))\n    sys.exit(1)<\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">Per qu\u00e8 aquesta versi\u00f3 \u00e9s m\u00e9s segura?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">El recorregut queda:<\/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=\"\">FORMULARI\n   \u2502\n   \u2502  \"Bon dia, s\u00f3c l'Ona. Qu\u00e8 tal?\"\n   \u25bc\nPHP\n   \u2502\n   \u2502 UTF-8\n   \u25bc\nbase64_encode()\n   \u2502\n   \u2502 \"Qm9uIGRpYSwgc...\"\n   \u25bc\nShell\n   \u2502\n   \u25bc\nPython\n   \u2502\n   \u2502 base64.b64decode()\n   \u25bc\nUTF-8\n   \u2502\n   \u2502 \"Bon dia, s\u00f3c l'Ona. Qu\u00e8 tal?\"\n   \u25bc\ngTTS\n   \u2502\n   \u25bc\naudio_ca.mp3<\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Aix\u00ed <strong>el text no passa mai directament per la shell<\/strong>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">I aix\u00f2 \u00e9s especialment interessant en el teu cas perqu\u00e8 ja hem vist anteriorment que els accents s\u00f3n precisament un punt delicat en la cadena TTS.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Una prova molt bona<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Abans de provar tot el formulari, podem fer una prova espec\u00edfica amb:<\/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=\"\">Bon dia, s\u00f3c l'Ona. Qu\u00e8 tal? \u00c0ngel, despr\u00e9s prendr\u00e9 caf\u00e8.<\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Si al <code>print(\"TEXT:\", text)<\/code> de Python apareix <strong>exactament<\/strong> aix\u00ed:<\/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=\"\">TEXT: Bon dia, s\u00f3c l'Ona. Qu\u00e8 tal? \u00c0ngel, despr\u00e9s prendr\u00e9 caf\u00e8.<\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">ja sabrem que <strong>PHP \u2192 Python est\u00e0 passant UTF-8 correctament<\/strong>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Despr\u00e9s nom\u00e9s quedar\u00e0 comprovar <strong>Python \u2192 gTTS \u2192 MP3<\/strong>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Si vols, podem fer-ho encara m\u00e9s segur: <strong>mirem primer quin <code>python3<\/code> est\u00e0 utilitzant el teu Apache\/PHP i quin <code>locale<\/code> t\u00e9<\/strong>, perqu\u00e8 al teu Synology aix\u00f2 pot ser important.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>i que produeixi autom\u00e0ticament: o b\u00e9 .wav. Com que tu ja tens gTTS al Synology, una opci\u00f3 molt pr\u00e0ctica \u00e9s que PHP cridi un petit script Python que faci la conversi\u00f3. Aix\u00ed PHP nom\u00e9s s&#8217;encarrega de passar $lang i $text. &hellip; <a href=\"https:\/\/www.beseit.net\/?p=19238\">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":[1],"tags":[],"class_list":["post-19238","post","type-post","status-publish","format-standard","hentry","category-bloc-de-notes"],"_links":{"self":[{"href":"https:\/\/www.beseit.net\/index.php?rest_route=\/wp\/v2\/posts\/19238","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=19238"}],"version-history":[{"count":3,"href":"https:\/\/www.beseit.net\/index.php?rest_route=\/wp\/v2\/posts\/19238\/revisions"}],"predecessor-version":[{"id":19241,"href":"https:\/\/www.beseit.net\/index.php?rest_route=\/wp\/v2\/posts\/19238\/revisions\/19241"}],"wp:attachment":[{"href":"https:\/\/www.beseit.net\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=19238"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.beseit.net\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=19238"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.beseit.net\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=19238"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}