{"id":16560,"date":"2025-07-05T23:40:01","date_gmt":"2025-07-05T21:40:01","guid":{"rendered":"https:\/\/www.beseit.net\/?p=16560"},"modified":"2025-07-09T09:52:48","modified_gmt":"2025-07-09T07:52:48","slug":"text-to-speech-amb-python","status":"publish","type":"post","link":"http:\/\/www.beseit.net\/?p=16560","title":{"rendered":"Text to Speech (amb Python)"},"content":{"rendered":"\n<pre class=\"wp-block-code\"><code>Estr\/00-sapi_tts\/\n\u251c\u2500\u2500 exec_script_py.php     \u2190 Crida el Python\n\u251c\u2500\u2500 generar_tts.py \u2190 Script Python que genera l'\u00e0udio \n|                    des de Synology\n\u251c\u2500\u2500 index.php              \u2190 Interf\u00edcie web\n\u2514\u2500\u2500 tts.mp3                \u2190 Fitxer generatuctura\n<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">index.php<\/h2>\n\n\n\n<pre class=\"wp-block-code\"><code>&lt;?php\n\/\/ Llista de veus\n$voices = &#91;\n    \"Microsoft Helena Desktop\",\n    \"Microsoft Hazel Desktop\",\n    \"Microsoft Zira Desktop\",\n    \"Microsoft Hortense Desktop\",\n    \"Vocalizer Expressive Jordi Harpo 22kHz\"\n];\n\n\/\/ Carrega valors antics per mantenir-los al formulari\n$lastText = isset($_GET&#91;'text']) ? htmlspecialchars($_GET&#91;'text']) : '';\n$lastVoice = isset($_GET&#91;'voice']) ? $_GET&#91;'voice'] : '';\n?>\n&lt;!DOCTYPE html>\n&lt;html lang=\"ca\">\n&lt;head>\n    &lt;meta charset=\"UTF-8\">\n    &lt;title>Generador TTS&lt;\/title>\n&lt;\/head>\n&lt;body>\n    &lt;h1>Text-to-Speech (amb Python)&lt;\/h1>\n    &lt;form action=\"exec_script_py.php\" method=\"POST\">\n\n        &lt;label for=\"text\">Introdueix el text:&lt;\/label>&lt;br>\n        &lt;textarea name=\"text\" id=\"text\" rows=\"4\" cols=\"50\">&lt;?= $lastText ?>&lt;\/textarea>&lt;br>&lt;br>\n\n        &lt;label for=\"voice\">Selecciona una veu:&lt;\/label>&lt;br>\n        &lt;select name=\"voice\" id=\"voice\">\n            &lt;?php foreach ($voices as $voice): ?>\n                &lt;option value=\"&lt;?= htmlspecialchars($voice) ?>\" &lt;?= ($voice == $lastVoice ? 'selected' : '') ?>>\n                    &lt;?= htmlspecialchars($voice) ?>\n                &lt;\/option>\n            &lt;?php endforeach; ?>\n        &lt;\/select>&lt;br>&lt;br>\n\n        &lt;input type=\"submit\" value=\"Generar \u00e0udio\">\n    &lt;\/form>\n\n    &lt;?php if (isset($_GET&#91;'error'])): ?>\n        &lt;p style=\"color:red;\">\u274c Error: &lt;?= htmlspecialchars($_GET&#91;'error']) ?>&lt;\/p>\n    &lt;?php endif; ?>\n\n    &lt;?php if (file_exists(\"tts.mp3\")): ?>\n        &lt;h2>Resultat:&lt;\/h2>\n        &lt;audio controls>\n            &lt;source src=\"tts.mp3?&lt;?= time() ?>\" type=\"audio\/mpeg\">\n            El teu navegador no suporta \u00e0udio.\n        &lt;\/audio>\n    &lt;?php endif; ?>\n&lt;\/body>\n&lt;\/html>\n<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">generar_tts.py<\/h2>\n\n\n\n<pre class=\"wp-block-code\"><code>import sys\nimport os\n\n# ? Elimina rutes locals que poden interferir\nsys.path = &#91;p for p in sys.path if \".local\" not in p and \"urqtejmi\" not in p]\n\nprint(\"? sys.path netejat:\")\nfor p in sys.path:\n    print(\"  -\", p)\n\n# ? Comprovaci\u00f3 de la importaci\u00f3\ntry:\n    import gtts\n    print(\"? gtts importat des de:\", getattr(gtts, '__file__', '\u274c No definit'))\n    from gtts import gTTS\n    print(\"\u2705 gTTS importat correctament\")\nexcept Exception as e:\n    print(\"\u274c Error important gTTS:\", e)\n    raise\n\n# ? Preparaci\u00f3 del fitxer de sortida\nbase_dir = os.path.dirname(os.path.abspath(__file__))\noutput_path = os.path.join(base_dir, \"tts.mp3\")\n\n# ? Recollir text i veu\ntext = sys.argv&#91;1] if len(sys.argv) > 1 else \"Text per defecte\"\nvoice = sys.argv&#91;2] if len(sys.argv) > 2 else \"default\"\n\nprint(f\"\u25b6 Text: {text}\")\nprint(f\"\u25b6 Veu seleccionada: {voice} (no utilitzada per gTTS)\")\nprint(f\"\u25b6 Fitxer a guardar: {output_path}\")\n\n# ? Generar MP3\ntts = gTTS(text=text, lang='ca')\ntts.save(output_path)\n<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">exec_script_py.php<\/h2>\n\n\n\n<pre class=\"wp-block-code\"><code>&lt;?php\nif ($_SERVER&#91;'REQUEST_METHOD'] === 'POST') {\n    $textRaw = $_POST&#91;'text'] ?? '';\n    $voiceRaw = $_POST&#91;'voice'] ?? '';\n\n    $text = escapeshellarg($textRaw);\n    $voice = escapeshellarg($voiceRaw);\n\n    \/\/ \u2705 Ruta al Python de l'entorn virtual\n    $python = '\/volume2\/web\/00-sapi_tts\/venv\/bin\/python';\n\n    \/\/ \u2705 Comanda per executar el script Python\n    $command = \"$python generar_tts.py $text $voice 2>&amp;1\";\n\n    \/\/ ? Executa el script i captura la sortida\n    $output = shell_exec($command);\n\n    echo \"&lt;pre>? Comanda executada:\\n$command\\n\\n? Sortida:\\n$output&lt;\/pre>\";\n\n    \/\/ \u274c Si no s'ha generat l'\u00e0udio, redirigeix amb error\n    if (!file_exists(\"tts.mp3\")) {\n        $error = \"No s'ha pogut generar l'\u00e0udio. Sortida: $output\";\n        header(\"Location: index.php?error=\" . urlencode($error) . \"&amp;text=\" . urlencode($textRaw) . \"&amp;voice=\" . urlencode($voiceRaw));\n        exit();\n    }\n\n    \/\/ \u2705 Si tot ha anat b\u00e9, redirigeix amb els valors conservats\n    header(\"Location: index.php?text=\" . urlencode($textRaw) . \"&amp;voice=\" . urlencode($voiceRaw));\n    exit();\n}\n?>\n<\/code><\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">?\ufe0f Historial de passos per solucionar l\u2019error amb <code>gtts<\/code><\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">1. <strong>Diagn\u00f2stic inicial<\/strong><\/h3>\n\n\n\n<ul class=\"wp-block-list\">\n<li>El teu script Python donava aquest error:<\/li>\n<\/ul>\n\n\n\n<pre class=\"wp-block-code\"><code>  ImportError: cannot import name 'gTTS' from 'gtts' (unknown location)<\/code><\/pre>\n\n\n\n<ul class=\"wp-block-list\">\n<li>El m\u00f2dul <code>gtts<\/code> semblava instal\u00b7lat, per\u00f2 <code>gtts.__file__<\/code> retornava <code>None<\/code>.<\/li>\n<\/ul>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\">2. <strong>Depuraci\u00f3 del sistema<\/strong><\/h3>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Vam comprovar que hi havia m\u00faltiples versions de <code>gtts<\/code> instal\u00b7lades:<\/li>\n\n\n\n<li>Una a <code>\/usr\/lib\/python3.8\/site-packages<\/code> \u2705<\/li>\n\n\n\n<li>Una altra a <code>~\/.local\/lib\/python3.8\/site-packages<\/code> \u274c (interferia)<\/li>\n\n\n\n<li>Vam eliminar la versi\u00f3 local conflictiva:<\/li>\n<\/ul>\n\n\n\n<pre class=\"wp-block-code\"><code>  rm -rf ~\/.local\/lib\/python3.8\/site-packages\/gtts\n  rm -rf ~\/.local\/lib\/python3.8\/site-packages\/gTTS-*.dist-info<\/code><\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\">3. <strong>Reinstal\u00b7laci\u00f3 neta<\/strong><\/h3>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Vam reinstal\u00b7lar <code>gtts<\/code> globalment:<\/li>\n<\/ul>\n\n\n\n<pre class=\"wp-block-code\"><code>  sudo \/bin\/python3 -m pip install --no-cache-dir gtts<\/code><\/pre>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Tot i aix\u00f2, l\u2019error persistia perqu\u00e8 el servidor web seguia usant el Python del sistema, amb rutes contaminades.<\/li>\n<\/ul>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading alignwide\" id=\"we-re-a-studio-in-berlin-with-an-international-practice-in-architecture-urban-planning-and-interior-design-we-believe-in-sharing-knowledge-and-promoting-dialogue-to-increase-the-creative-potential-of-collaboration\" style=\"font-size:48px;line-height:1.1\">4. <strong>Creaci\u00f3 d\u2019un entorn virtual<\/strong><\/h2>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Vam crear un entorn virtual a\u00efllat:<\/li>\n<\/ul>\n\n\n\n<pre class=\"wp-block-code\"><code>  python3 -m virtualenv venv\n  source venv\/bin\/activate\n  pip install gtts<\/code><\/pre>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Aix\u00f2 va instal\u00b7lar <code>gtts<\/code> netament a:<\/li>\n<\/ul>\n\n\n\n<pre class=\"wp-block-code\"><code>  \/volume2\/web\/00-sapi_tts\/venv\/lib\/python3.8\/site-packages<\/code><\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\">5. <strong>Modificaci\u00f3 del script PHP<\/strong><\/h3>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Vam modificar <code>exec_script_py.php<\/code> per cridar el Python del virtualenv:<\/li>\n<\/ul>\n\n\n\n<pre class=\"wp-block-code\"><code>  $python = '\/volume2\/web\/00-sapi_tts\/venv\/bin\/python';\n  $command = \"$python generar_tts.py $text $voice 2&gt;&amp;1\";<\/code><\/pre>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Aix\u00f2 va assegurar que el servidor web execut\u00e9s el Python correcte, amb el m\u00f2dul <code>gtts<\/code> funcional.<\/li>\n<\/ul>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\">6. <strong>\u00c8xit final<\/strong><\/h3>\n\n\n\n<ul class=\"wp-block-list\">\n<li>El fitxer <code>tts.mp3<\/code> es va generar correctament.<\/li>\n\n\n\n<li>El reproductor d\u2019\u00e0udio es va mostrar a la p\u00e0gina.<\/li>\n\n\n\n<li>El text \u201cbona nit\u201d es va convertir en veu ?<\/li>\n<\/ul>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">? Qu\u00e8 queda pendent?<\/h2>\n\n\n\n<ul class=\"wp-block-list\">\n<li>La veu no sona com la del \u201cJordi\u201d de Vocalizer: aix\u00f2 \u00e9s perqu\u00e8 <code>gtts<\/code> fa servir la veu de Google Translate, no veus locals com Vocalizer.<\/li>\n\n\n\n<li>Si vols fer servir veus com la del Jordi, caldr\u00e0 integrar motors com:<\/li>\n\n\n\n<li><strong>eSpeak NG<\/strong><\/li>\n\n\n\n<li><strong>Festival<\/strong><\/li>\n\n\n\n<li><strong>Vocalizer TTS<\/strong> (si tens llic\u00e8ncia)<\/li>\n\n\n\n<li><strong>Microsoft Azure<\/strong>, <strong>Google Cloud TTS<\/strong>, etc.<\/li>\n<\/ul>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<p>Vols que dem\u00e0 explorem com fer servir una veu real com la del Jordi amb un motor TTS local o en el n\u00favol? O vols que et prepari una comparativa de motors TTS amb veus catalanes?<\/p>\n","protected":false},"excerpt":{"rendered":"<p>index.php generar_tts.py exec_script_py.php ?\ufe0f Historial de passos per solucionar l\u2019error amb gtts 1. Diagn\u00f2stic inicial 2. Depuraci\u00f3 del sistema 3. Reinstal\u00b7laci\u00f3 neta 4. Creaci\u00f3 d\u2019un entorn virtual 5. Modificaci\u00f3 del script PHP 6. \u00c8xit final ? Qu\u00e8 queda pendent? Vols &hellip; <a href=\"http:\/\/www.beseit.net\/?p=16560\">Continua llegint <span class=\"meta-nav\">&rarr;<\/span><\/a><\/p>\n","protected":false},"author":2,"featured_media":3170,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[185],"tags":[],"class_list":["post-16560","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-veus"],"_links":{"self":[{"href":"http:\/\/www.beseit.net\/index.php?rest_route=\/wp\/v2\/posts\/16560","targetHints":{"allow":["GET"]}}],"collection":[{"href":"http:\/\/www.beseit.net\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"http:\/\/www.beseit.net\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"http:\/\/www.beseit.net\/index.php?rest_route=\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"http:\/\/www.beseit.net\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=16560"}],"version-history":[{"count":1,"href":"http:\/\/www.beseit.net\/index.php?rest_route=\/wp\/v2\/posts\/16560\/revisions"}],"predecessor-version":[{"id":16561,"href":"http:\/\/www.beseit.net\/index.php?rest_route=\/wp\/v2\/posts\/16560\/revisions\/16561"}],"wp:featuredmedia":[{"embeddable":true,"href":"http:\/\/www.beseit.net\/index.php?rest_route=\/wp\/v2\/media\/3170"}],"wp:attachment":[{"href":"http:\/\/www.beseit.net\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=16560"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"http:\/\/www.beseit.net\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=16560"},{"taxonomy":"post_tag","embeddable":true,"href":"http:\/\/www.beseit.net\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=16560"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}