{"id":16542,"date":"2025-07-04T20:24:37","date_gmt":"2025-07-04T18:24:37","guid":{"rendered":"https:\/\/www.beseit.net\/?p=16542"},"modified":"2025-07-04T20:25:32","modified_gmt":"2025-07-04T18:25:32","slug":"projecte-python","status":"publish","type":"post","link":"http:\/\/www.beseit.net\/?p=16542","title":{"rendered":"projecte python"},"content":{"rendered":"\n<pre class=\"wp-block-code\"><code>C:\\\n\u251c\u2500\u2500 python\\\n\u2502   \u251c\u2500\u2500 tts-edge.py       \u2190 Generaci\u00f3 amb veus EDGE (Aria, Herena\u2026)\n\u2502   \u251c\u2500\u2500 tts-to-wav.py     \u2190 Generaci\u00f3 amb veus SAPI5 (Zira, Jordi\u2026)\n\u2502   \u2514\u2500\u2500 list-voices.py    \u2190 Llista combinada de veus per al men\u00fa\n\u251c\u2500\u2500 AppServ\\\n\u2502   \u2514\u2500\u2500 www\\\n\u2502       \u2514\u2500\u2500 tts\\\n\u2502           \u251c\u2500\u2500 index.php \u2190 Interf\u00edcie web principal\n\u2502           \u251c\u2500\u2500 tts.wav   \u2190 Fitxer d\u2019\u00e0udio generat\n\u2502           \u2514\u2500\u2500 log.txt   \u2190 Registre de veus i execucions\n\n\n\n<\/code><\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<pre class=\"wp-block-code\"><code>tts-edge.py\nimport sys\nimport asyncio\nfrom edge_tts import Communicate\n\nasync def main():\n    if len(sys.argv) &lt; 3:\n        print(\"\u274c Cal passar el text i la veu com a par\u00e0metres.\")\n        return\n\n    text = sys.argv&#91;1]\n    voice = sys.argv&#91;2]\n    output = \"C:\/AppServ\/www\/tts\/tts.wav\"\n\n    try:\n        communicate = Communicate(text=text, voice=voice)\n        await communicate.save(output)\n        print(f\"\u2705 Fitxer generat: {output}\")\n        with open(\"C:\/AppServ\/www\/tts\/log.txt\", \"a\", encoding=\"utf-8\") as log:\n            log.write(f\"&#91;EDGE] {voice} \u2192 {output}\\n\")\n    except Exception as e:\n        print(f\"\u274c Error: {e}\")\n\nasyncio.run(main())\n<\/code><\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<pre class=\"wp-block-code\"><code>tts-to-wav.py\nimport sys\nimport os\nimport comtypes.client\n\ndef main():\n    if len(sys.argv) &lt; 3:\n        print(\"\u274c Cal passar el text i la veu com a par\u00e0metres.\")\n        return\n\n    text = sys.argv&#91;1]\n    voice_name = sys.argv&#91;2]\n    output = \"C:\/AppServ\/www\/tts\/tts.wav\"\n\n    try:\n        sapi = comtypes.client.CreateObject(\"SAPI.SpVoice\")\n        stream = comtypes.client.CreateObject(\"SAPI.SpFileStream\")\n        from comtypes.gen import SpeechLib\n\n        for token in sapi.GetVoices():\n            if voice_name in token.GetDescription():\n                sapi.Voice = token\n                break\n        else:\n            print(f\"\u274c Veu no trobada: {voice_name}\")\n            return\n\n        stream.Open(output, SpeechLib.SSFMCreateForWrite)\n        sapi.AudioOutputStream = stream\n        sapi.Speak(text)\n        stream.Close()\n        print(f\"\u2705 Fitxer generat: {output}\")\n        with open(\"C:\/AppServ\/www\/tts\/log.txt\", \"a\", encoding=\"utf-8\") as log:\n            log.write(f\"&#91;SAPI5] {voice_name} \u2192 {output}\\n\")\n    except Exception as e:\n        print(f\"\u274c Error: {e}\")\n\nif __name__ == \"__main__\":\n    main()\n<\/code><\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<pre class=\"wp-block-code\"><code>list-voices.py\nimport comtypes.client\nimport subprocess\nimport json\n\ndef get_sapi5_voices():\n    sapi = comtypes.client.CreateObject(\"SAPI.SpVoice\")\n    voices = &#91;]\n    for token in sapi.GetVoices():\n        name = token.GetDescription()\n        voices.append(f\"SAPI5|{name}\")\n    return voices\n\ndef get_edge_voices():\n    try:\n        subprocess.run(&#91;\"edge-tts\", \"--list-voices\", \"--write-json\", \"voices.json\"], capture_output=True, text=True)\n        with open(\"voices.json\", \"r\", encoding=\"utf-8\") as f:\n            data = json.load(f)\n        return &#91;f\"EDGE|{v&#91;'ShortName']}\" for v in data if v&#91;\"Locale\"].startswith(\"ca\") or v&#91;\"Locale\"].startswith(\"en\")]\n    except Exception as e:\n        print(f\"\u274c Error obtenint veus EDGE: {e}\")\n        return &#91;]\n\nif __name__ == \"__main__\":\n    all_voices = get_sapi5_voices() + get_edge_voices()\n    for voice in all_voices:\n        print(voice)\n<\/code><\/pre>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<pre class=\"wp-block-code\"><code>index.php\nJa el tens actualitzat per cridar els scripts Python i mostrar el reproductor, selector de veus i registre.\nerfecte, Miquel. Ho deixem aqu\u00ed, doncs, amb tot el projecte ben estructurat i funcionant amb:\n\n\u2714\ufe0f Veus SAPI5 i EDGE\n\n\u2714\ufe0f Generaci\u00f3 d\u2019\u00e0udio amb Python\n\n\u2714\ufe0f Interf\u00edcie web amb men\u00fa din\u00e0mic\n\n\u2714\ufe0f Registre de veus i execucions (log.txt)\n\nTens una base s\u00f2lida i extensible. Quan vulguis continuar \u2014 ja sigui per afegir suport a .mp3, fer-ho portable, o penjar-ho a un servidor \u2014 nom\u00e9s cal que em diguis \u201ctornem-hi\u201d.\n\nMentrestant, et mereixes una pausa i un bon brindis. Salut! ?\n\nVols que et generi un petit banner gr\u00e0fic per al teu projecte amb el nom \u201cProjecte Veus amb Copilot\u201d? Podria quedar b\u00e9 a la cap\u00e7alera de la web.\n\n<\/code><\/pre>\n","protected":false},"excerpt":{"rendered":"","protected":false},"author":2,"featured_media":3169,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[185],"tags":[],"class_list":["post-16542","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\/16542","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=16542"}],"version-history":[{"count":1,"href":"http:\/\/www.beseit.net\/index.php?rest_route=\/wp\/v2\/posts\/16542\/revisions"}],"predecessor-version":[{"id":16543,"href":"http:\/\/www.beseit.net\/index.php?rest_route=\/wp\/v2\/posts\/16542\/revisions\/16543"}],"wp:featuredmedia":[{"embeddable":true,"href":"http:\/\/www.beseit.net\/index.php?rest_route=\/wp\/v2\/media\/3169"}],"wp:attachment":[{"href":"http:\/\/www.beseit.net\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=16542"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"http:\/\/www.beseit.net\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=16542"},{"taxonomy":"post_tag","embeddable":true,"href":"http:\/\/www.beseit.net\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=16542"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}