{"id":16666,"date":"2024-12-29T20:18:18","date_gmt":"2024-12-29T12:18:18","guid":{"rendered":"https:\/\/www.aisharenet.com\/?p=16666"},"modified":"2025-07-20T05:46:40","modified_gmt":"2025-07-19T21:46:40","slug":"edge-tts-worker","status":"publish","type":"post","link":"https:\/\/www.kdjingpai.com\/pt\/edge-tts-worker\/","title":{"rendered":"Edge TTS Worker\uff1a\u4f7f\u7528Cloudflare\u90e8\u7f72\u5fae\u8f6f\u8bed\u97f3\u5408\u6210API\uff0c\u517c\u5bb9OpenAI \u683c\u5f0f\u5e76\u5c01\u88c5Web\u754c\u9762"},"content":{"rendered":"<p>Edge TTS Worker\uff08\u4f9d\u8d56\u00a0<a href=\"https:\/\/www.kdjingpai.com\/edge-tts\/\">edge-tts<\/a> \uff09 \u662f\u4e00\u4e2a\u90e8\u7f72\u5728 Cloudflare Worker \u4e0a\u7684\u4ee3\u7406\u670d\u52a1\uff0c\u5b83\u5c06\u5fae\u8f6f Edge TTS \u670d\u52a1\u5c01\u88c5\u6210\u517c\u5bb9 OpenAI \u683c\u5f0f\u7684 API \u63a5\u53e3\u3002\u901a\u8fc7\u672c\u9879\u76ee\uff0c\u7528\u6237\u53ef\u4ee5\u5728\u6ca1\u6709\u5fae\u8f6f\u8ba4\u8bc1\u7684\u60c5\u51b5\u4e0b\uff0c\u8f7b\u677e\u4f7f\u7528\u5fae\u8f6f\u9ad8\u8d28\u91cf\u7684\u8bed\u97f3\u5408\u6210\u670d\u52a1\u3002Edge TTS Worker \u63d0\u4f9b\u591a\u8bed\u79cd\u652f\u6301\uff0c\u5305\u62ec\u4e2d\u6587\u3001\u82f1\u6587\u3001\u65e5\u6587\u3001\u97e9\u6587\u7b49\uff0c\u4e14\u5b8c\u5168\u514d\u8d39\uff0c\u57fa\u4e8e Cloudflare Worker \u514d\u8d39\u8ba1\u5212\u3002\u8be5\u670d\u52a1\u8fd8\u652f\u6301\u81ea\u5b9a\u4e49 API \u5bc6\u94a5\uff0c\u786e\u4fdd\u5b89\u5168\u53ef\u63a7\uff0c\u5e76\u4e14\u53ef\u4ee5\u5feb\u901f\u90e8\u7f72\uff0c\u51e0\u5206\u949f\u5185\u5373\u53ef\u5b8c\u6210\u3002<\/p>\n<p>\u6d4b\u8bd5API\uff1ahttps:\/\/tts.aishare.us.kg\/ KEY\uff1aaisharenet<\/p>\n<p><a href=\"https:\/\/github.com\/jianchang512\/tts-pyvideotrans\">\u4e3aAPI\u5c01\u88c5\u7b80\u6613\u754c\u9762\u7684\u9879\u76ee<\/a><\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter wp-image-16667\" title=\"Edge TTS Worker\uff1a\u4f7f\u7528Cloudflare\u90e8\u7f72\u5fae\u8f6f\u8bed\u97f3\u5408\u6210API\uff0c\u517c\u5bb9OpenAI\u683c\u5f0f\u5e76\u5c01\u88c5Web\u754c\u9762-1\" src=\"https:\/\/www.kdjingpai.com\/wp-content\/uploads\/2024\/12\/531f56b650433f5.png\" alt=\"Edge TTS Worker\uff1a\u4f7f\u7528Cloudflare\u90e8\u7f72\u5fae\u8f6f\u8bed\u97f3\u5408\u6210API\uff0c\u517c\u5bb9OpenAI\u683c\u5f0f\u5e76\u5c01\u88c5Web\u754c\u9762-1\" width=\"875\" height=\"471\" srcset=\"https:\/\/www.kdjingpai.com\/wp-content\/uploads\/2024\/12\/531f56b650433f5.png 1132w, https:\/\/www.kdjingpai.com\/wp-content\/uploads\/2024\/12\/531f56b650433f5-300x161.png 300w, https:\/\/www.kdjingpai.com\/wp-content\/uploads\/2024\/12\/531f56b650433f5-1024x551.png 1024w, https:\/\/www.kdjingpai.com\/wp-content\/uploads\/2024\/12\/531f56b650433f5-768x413.png 768w, https:\/\/www.kdjingpai.com\/wp-content\/uploads\/2024\/12\/531f56b650433f5-18x10.png 18w\" sizes=\"auto, (max-width: 875px) 100vw, 875px\" \/><\/p>\n<p>&nbsp;<\/p>\n<p>\u5c01\u88c5API\u548c\u754c\u9762\u5b8c\u6574\u4f7f\u7528Cloudflare\u90e8\u7f72\uff1a<\/p>\n<p>\u5b8c\u6574\u4ee3\u7801\u7531CHATGPT\u751f\u6210\uff0c\u4ee3\u7801\u9644\u6587\u7ae0\u672b\u5c3e\uff0c\u53ef\u4ee5\u9009\u62e9\u66f4\u6240 <a href=\"https:\/\/www.kdjingpai.com\/tag\/aibiancheng\/\">AI\u7f16\u7a0b<\/a> \u5de5\u5177 \u6216\u4f7f\u7528 <a title=\"Trickle\uff1a\u4ec5\u901a\u8fc7\u6587\u672c\u63d0\u793a\u5feb\u901f\u6784\u5efa\u7f51\u9875\u5e94\u7528\u7a0b\u5e8f\uff0c\u53ef\u81ea\u52a8\u6784\u5efa\u540e\u7aef\u6570\u636e\u5e93-AI\u751f\u4ea7\u529b\u5de5\u5177\" href=\"https:\/\/www.kdjingpai.com\/trickle\/\" target=\"_blank\" rel=\"noopener\">Trickle<\/a> \u4e00\u952e\u751f\u6210\u5728\u7ebf\u53ef\u8bbf\u95ee\u7684\u8bed\u97f3\u751f\u6210\u670d\u52a1\uff09\uff1a<\/p>\n<div id=\"attachment_16669\" style=\"width: 953px\" class=\"wp-caption aligncenter\"><img loading=\"lazy\" decoding=\"async\" aria-describedby=\"caption-attachment-16669\" class=\"wp-image-16669 size-full\" title=\"Edge TTS Worker\uff1a\u4f7f\u7528Cloudflare\u90e8\u7f72\u5fae\u8f6f\u8bed\u97f3\u5408\u6210API\uff0c\u517c\u5bb9OpenAI \u683c\u5f0f\u5e76\u5c01\u88c5Web\u754c\u9762-1\" src=\"https:\/\/www.kdjingpai.com\/wp-content\/uploads\/2024\/12\/a22d19a43ef02a7.png\" alt=\"Edge TTS Worker\uff1a\u4f7f\u7528Cloudflare\u90e8\u7f72\u5fae\u8f6f\u8bed\u97f3\u5408\u6210API\uff0c\u517c\u5bb9OpenAI \u683c\u5f0f\u5e76\u5c01\u88c5Web\u754c\u9762-1\" width=\"943\" height=\"645\" srcset=\"https:\/\/www.kdjingpai.com\/wp-content\/uploads\/2024\/12\/a22d19a43ef02a7.png 943w, https:\/\/www.kdjingpai.com\/wp-content\/uploads\/2024\/12\/a22d19a43ef02a7-300x205.png 300w, https:\/\/www.kdjingpai.com\/wp-content\/uploads\/2024\/12\/a22d19a43ef02a7-220x150.png 220w, https:\/\/www.kdjingpai.com\/wp-content\/uploads\/2024\/12\/a22d19a43ef02a7-768x525.png 768w, https:\/\/www.kdjingpai.com\/wp-content\/uploads\/2024\/12\/a22d19a43ef02a7-18x12.png 18w\" sizes=\"auto, (max-width: 943px) 100vw, 943px\" \/><p id=\"caption-attachment-16669\" class=\"wp-caption-text\">\u4f53\u9a8c\u5730\u5740\uff1ahttps:\/\/edgetts.aishare.us.kg\/<\/p><\/div>\n<p>&nbsp;<\/p>\n<h2>\u529f\u80fd\u5217\u8868<\/h2>\n<ul>\n<li>\u63d0\u4f9b OpenAI \u517c\u5bb9\u7684\u63a5\u53e3\u683c\u5f0f<\/li>\n<li>\u7ed5\u8fc7\u5927\u9646\u5730\u533a\u8bbf\u95ee\u9650\u5236\uff0c\u514d\u53bb\u5fae\u8f6f\u670d\u52a1\u8ba4\u8bc1\u6b65\u9aa4<\/li>\n<li>\u591a\u8bed\u79cd\u652f\u6301\uff0c\u5305\u62ec\u4e2d\u6587\u3001\u82f1\u6587\u3001\u65e5\u6587\u3001\u97e9\u6587\u7b49<\/li>\n<li>\u5b8c\u5168\u514d\u8d39\uff0c\u57fa\u4e8e Cloudflare Worker \u514d\u8d39\u8ba1\u5212<\/li>\n<li>\u652f\u6301\u81ea\u5b9a\u4e49 API \u5bc6\u94a5\uff0c\u786e\u4fdd\u5b89\u5168\u53ef\u63a7<\/li>\n<li>\u5feb\u901f\u90e8\u7f72\uff0c\u51e0\u5206\u949f\u5185\u5373\u53ef\u5b8c\u6210<\/li>\n<li>\u63d0\u4f9b\u6d4b\u8bd5\u811a\u672c\uff0c\u65b9\u4fbf\u6d4b\u8bd5\u4e0d\u540c\u8bed\u97f3\u6548\u679c<\/li>\n<\/ul>\n<p>&nbsp;<\/p>\n<h2>\u4f7f\u7528\u5e2e\u52a9<\/h2>\n<h3>\u5b89\u88c5\u6d41\u7a0b<\/h3>\n<ol>\n<li><strong>\u521b\u5efa Worker<\/strong>\n<ul>\n<li>\u767b\u5f55 Cloudflare Dashboard<\/li>\n<li>\u8fdb\u5165 Workers &amp; Pages\uff0c\u70b9\u51fb Create Worker<\/li>\n<li>\u4e3a Worker \u53d6\u4e2a\u540d\u5b57\uff08\u6bd4\u5982 edge-tts\uff09<\/li>\n<\/ul>\n<\/li>\n<li><strong>\u90e8\u7f72\u4ee3\u7801<\/strong>\n<ul>\n<li>\u5220\u9664\u7f16\u8f91\u5668\u4e2d\u7684\u9ed8\u8ba4\u4ee3\u7801<\/li>\n<li>\u590d\u5236 <code>worker.js<\/code> \u4e2d\u7684\u4ee3\u7801\u5e76\u7c98\u8d34<\/li>\n<li>\u70b9\u51fb Save and deploy<\/li>\n<\/ul>\n<\/li>\n<li><strong>\u8bbe\u7f6e API Key\uff08\u53ef\u9009\uff09<\/strong>\n<ul>\n<li>\u5728 Worker \u7684\u8bbe\u7f6e\u9875\u9762\u4e2d\u627e\u5230 Settings -&gt; Variables<\/li>\n<li>\u70b9\u51fb Add variable\uff0c\u540d\u79f0\u586b\u5199 API_KEY\uff0c\u503c\u586b\u5199\u4f60\u60f3\u8981\u7684\u5bc6\u94a5<\/li>\n<li>\u70b9\u51fb Save and deploy<\/li>\n<\/ul>\n<\/li>\n<li><strong>\u914d\u7f6e\u81ea\u5b9a\u4e49\u57df\u540d\uff08\u53ef\u9009\uff09<\/strong>\n<ul>\n<li>\u524d\u63d0\u6761\u4ef6\uff1a\u4f60\u7684\u57df\u540d\u5df2\u7ecf\u6258\u7ba1\u5728 Cloudflare\uff0c\u57df\u540d\u7684 DNS \u8bb0\u5f55\u5df2\u7ecf\u901a\u8fc7 Cloudflare \u4ee3\u7406\uff08\u4ee3\u7406\u72b6\u6001\u4e3a\u6a59\u8272\u4e91\u6735\uff09<\/li>\n<li>\u914d\u7f6e\u6b65\u9aa4\uff1a\n<ul>\n<li>\u5728 Worker \u7684\u8be6\u60c5\u9875\u9762\u4e2d\u70b9\u51fb \u8bbe\u7f6e \u6807\u7b7e<\/li>\n<li>\u627e\u5230 \u57df\u548c\u8def\u7531 \u90e8\u5206\uff0c\u70b9\u51fb \u6dfb\u52a0 \u6309\u94ae<\/li>\n<li>\u9009\u62e9 \u81ea\u5b9a\u4e49\u57df\uff0c\u8f93\u5165\u4f60\u60f3\u8981\u4f7f\u7528\u7684\u57df\u540d\uff08\u6bd4\u5982 tts.example.com\uff09<\/li>\n<li>\u70b9\u51fb \u6dfb\u52a0\u57df\uff0c\u7b49\u5f85\u8bc1\u4e66\u90e8\u7f72\u5b8c\u6210\uff08\u901a\u5e38\u51e0\u5206\u949f\u5185\uff09<\/li>\n<\/ul>\n<\/li>\n<\/ul>\n<\/li>\n<\/ol>\n<h3>\u4f7f\u7528\u65b9\u6cd5<\/h3>\n<ol>\n<li><strong>\u6587\u672c\u8f6c\u8bed\u97f3\u63a5\u53e3<\/strong>\n<ul>\n<li>\u4e2d\u6587\u8bed\u97f3\u793a\u4f8b\uff1a<\/li>\n<\/ul>\n<pre><code> curl -X POST https:\/\/\u4f60\u7684worker\u5730\u5740\/v1\/audio\/speech \\\r\n-H \"Content-Type: application\/json\" \\\r\n-H \"Authorization: Bearer your-api-key\" \\\r\n-d '{\r\n\"model\": \"tts-1\",\r\n\"input\": \"\u4f60\u597d\uff0c\u4e16\u754c\uff01\",\r\n\"voice\": \"zh-CN-XiaoxiaoNeural\",\r\n\"response_format\": \"mp3\",\r\n\"speed\": 1.0,\r\n\"<a href=\"https:\/\/www.kdjingpai.com\/pt\/pitch\/\">pitch<\/a>\": 1.0,\r\n\"style\":\"general\"\r\n}' --output chinese.mp3\r\n<\/code><\/pre>\n<ul>\n<li>\u82f1\u6587\u8bed\u97f3\u793a\u4f8b\uff1a<\/li>\n<\/ul>\n<pre><code> curl -X POST https:\/\/\u4f60\u7684worker\u5730\u5740\/v1\/audio\/speech \\\r\n-H \"Content-Type: application\/json\" \\\r\n-H \"Authorization: Bearer your-api-key\" \\\r\n-d '{\r\n\"model\": \"tts-1\",\r\n\"input\": \"Hello, World!\",\r\n\"voice\": \"en-US-JennyNeural\",\r\n\"response_format\": \"mp3\",\r\n\"speed\": 1.0,\r\n\"pitch\": 1.0,\r\n\"style\":\"general\"\r\n}' --output english.mp3\r\n<\/code><\/pre>\n<\/li>\n<li><strong>\u6d4b\u8bd5\u811a\u672c\u4f7f\u7528<\/strong>\n<ul>\n<li>\u4e0b\u8f7d\u6d4b\u8bd5\u811a\u672c <code>test_voices.sh<\/code><\/li>\n<li>\u7ed9\u811a\u672c\u6dfb\u52a0\u6267\u884c\u6743\u9650\uff1a <code>bash<br \/>\nchmod +x test_voices.sh<br \/>\n<\/code><\/li>\n<li>\u8fd0\u884c\u811a\u672c\uff1a <code>bash<br \/>\n.\/test_voices.sh &lt;Worker\u5730\u5740&gt; [API\u5bc6\u94a5]<br \/>\n<\/code><\/li>\n<li>\u793a\u4f8b\uff1a <code>bash<br \/>\n# \u4f7f\u7528 API \u5bc6\u94a5<br \/>\n.\/test_voices.sh https:\/\/your-worker.workers.dev your-api-key<br \/>\n# \u4e0d\u4f7f\u7528 API \u5bc6\u94a5<br \/>\n.\/test_voices.sh https:\/\/your-worker.workers.dev<br \/>\n<\/code><\/li>\n<li>\u811a\u672c\u4f1a\u4e3a\u6bcf\u4e2a\u652f\u6301\u7684\u8bed\u97f3\u751f\u6210\u6d4b\u8bd5\u97f3\u9891\u6587\u4ef6\uff0c\u4f60\u53ef\u4ee5\u901a\u8fc7\u64ad\u653e\u8fd9\u4e9b\u6587\u4ef6\u6765\u9009\u62e9\u6700\u9002\u5408\u7684\u8bed\u97f3\u3002<\/li>\n<\/ul>\n<\/li>\n<\/ol>\n<h3>API \u53c2\u6570\u8bf4\u660e<\/h3>\n<ul>\n<li><code>model<\/code> (string): \u6a21\u578b\u540d\u79f0\uff08\u56fa\u5b9a\u503c\uff09\uff0c\u4f8b\u5982 <code>tts-1<\/code><\/li>\n<li><code>input<\/code> (string): \u8981\u8f6c\u6362\u7684\u6587\u672c\uff0c\u4f8b\u5982 <code>\"\u4f60\u597d\uff0c\u4e16\u754c\uff01\"<\/code><\/li>\n<li><code>voice<\/code> (string): \u8bed\u97f3\u540d\u79f0\uff0c\u4f8b\u5982 <code>zh-CN-XiaoxiaoNeural<\/code><\/li>\n<li><code>response_format<\/code> (string, \u53ef\u9009): \u8f93\u51fa\u683c\u5f0f\uff0c\u9ed8\u8ba4\u503c\u4e3a <code>mp3<\/code><\/li>\n<li><code>speed<\/code> (number, \u53ef\u9009): \u8bed\u901f (0.5-2.0)\uff0c\u9ed8\u8ba4\u503c\u4e3a <code>1.0<\/code><\/li>\n<li><code>pitch<\/code> (number, \u53ef\u9009): \u8bed\u8c03 (0.5-2.0)\uff0c\u9ed8\u8ba4\u503c\u4e3a <code>1.0<\/code><\/li>\n<li><code>style<\/code> (string, \u53ef\u9009): \u60c5\u7eea\uff0c\u9ed8\u8ba4\u503c\u4e3a <code>general<\/code><\/li>\n<\/ul>\n<h3>\u652f\u6301\u7684\u8bed\u97f3\u5217\u8868<\/h3>\n<p>\u8bf7\u786e\u4fdd\u4f7f\u7528\u4e0e\u8bed\u97f3\u5bf9\u5e94\u7684\u8bed\u8a00\u6587\u672c\uff0c\u4f8b\u5982\u4e2d\u6587\u8bed\u97f3\u9700\u914d\u5408\u4e2d\u6587\u6587\u672c\u4f7f\u7528\u3002\u4ee5\u4e0b\u662f\u5e38\u7528\u8bed\u97f3\u793a\u4f8b\uff1a<\/p>\n<ul>\n<li><code>zh-CN-XiaoxiaoNeural<\/code>: \u6653\u6653 &#8211; \u6e29\u6696\u6d3b\u6cfc<\/li>\n<li><code>zh-CN-XiaoyiNeural<\/code>: \u6653\u4f0a &#8211; \u6e29\u6696\u4eb2\u5207<\/li>\n<li><code>zh-CN-YunxiNeural<\/code>: \u4e91\u5e0c &#8211; \u7537\u58f0\uff0c\u7a33\u91cd<\/li>\n<li><code>zh-CN-YunyangNeural<\/code>: \u4e91\u626c &#8211; \u7537\u58f0\uff0c\u4e13\u4e1a<\/li>\n<li><code>zh-CN-XiaohanNeural<\/code>: \u6653\u6db5 &#8211; \u81ea\u7136\u6d41\u7545<\/li>\n<li><code>zh-CN-XiaomengNeural<\/code>: \u6653\u68a6 &#8211; \u751c\u7f8e\u6d3b\u529b<\/li>\n<li><code>zh-CN-XiaochenNeural<\/code>: \u6653\u8fb0 &#8211; \u6e29\u548c\u4ece\u5bb9<\/li>\n<li>\u7b49\u7b49&#8230;<\/li>\n<\/ul>\n<p>&nbsp;<\/p>\n<p><strong>workers.js\u4ee3\u7801<\/strong><\/p>\n<pre>const API_KEY = 'aisharenet'; \/\/ \u66ff\u6362\u4e3a\u4f60\u7684\u5b9e\u9645 API key\r\nconst TOKEN_REFRESH_BEFORE_EXPIRY = 3 * 60; \/\/ <a href=\"https:\/\/www.kdjingpai.com\/pt\/tokenization\/\">Token<\/a> \u5237\u65b0\u65f6\u95f4\uff08\u79d2\uff09\r\nconst DEFAULT_VOICE = 'zh-CN-XiaoxiaoNeural'; \/\/ \u9ed8\u8ba4\u8bed\u97f3\r\nconst DEFAULT_SPEED = 1.0; \/\/ \u9ed8\u8ba4\u8bed\u901f\r\nconst DEFAULT_PITCH = 1.0; \/\/ \u9ed8\u8ba4\u97f3\u8c03\r\n\r\nlet tokenInfo = {\r\n    endpoint: null,\r\n    token: null,\r\n    expiredAt: null\r\n};\r\n\r\n\/\/ \u5904\u7406\u8bf7\u6c42\r\naddEventListener('fetch', event =&gt; {\r\n    event.respondWith(handleRequest(event.request));\r\n});\r\n\r\nasync function handleRequest(request) {\r\n    const url = new URL(request.url);\r\n\r\n    \/\/ \u5982\u679c\u8bf7\u6c42\u6839\u8def\u5f84\uff0c\u8fd4\u56de Web UI\r\n    if (url.pathname === '\/') {\r\n        return new Response(getWebUI(), {\r\n            headers: { 'Content-Type': 'text\/html' },\r\n        });\r\n    }\r\n\r\n    \/\/ \u5904\u7406 TTS \u8bf7\u6c42\r\n    if (url.pathname === '\/v1\/audio\/speech') {\r\n        return handleTTSRequest(request);\r\n    }\r\n\r\n    \/\/ \u9ed8\u8ba4\u8fd4\u56de 404\r\n    return new Response('Not Found', { status: 404 });\r\n}\r\n\r\n\/\/ \u5904\u7406 TTS \u8bf7\u6c42\r\nasync function handleTTSRequest(request) {\r\n    if (request.method === 'OPTIONS') {\r\n        return handleOptions(request);\r\n    }\r\n\r\n    \/\/ \u9a8c\u8bc1 API Key\r\n    const authHeader = request.headers.get('authorization') || request.headers.get('x-api-key');\r\n    const apiKey = authHeader?.startsWith('Bearer ') ? authHeader.slice(7) : null;\r\n\r\n    if (API_KEY &amp;&amp; apiKey !== API_KEY) {\r\n        return createErrorResponse('Invalid API key. Use \\'Authorization: Bearer your-api-key\\' header', 'invalid_api_key', 401);\r\n    }\r\n\r\n    try {\r\n        const requestBody = await request.json();\r\n        const { \r\n            model = \"tts-1\",\r\n            input,\r\n            voice = DEFAULT_VOICE,\r\n            speed = DEFAULT_SPEED,\r\n            pitch = DEFAULT_PITCH\r\n        } = requestBody;\r\n\r\n        \/\/ \u9a8c\u8bc1\u53c2\u6570\u8303\u56f4\r\n        validateParameterRange('speed', speed, 0.5, 2.0);\r\n        validateParameterRange('pitch', pitch, 0.5, 2.0);\r\n\r\n        const rate = calculateRate(speed);\r\n        const numPitch = calculatePitch(pitch);\r\n        const audioResponse = await getVoice(\r\n            input,\r\n            voice,\r\n            rate &gt;= 0 ? `+${rate}%` : `${rate}%`,\r\n            numPitch &gt;= 0 ? `+${numPitch}%` : `${numPitch}%`,\r\n            '+0%',\r\n            'general', \/\/ \u56fa\u5b9a\u98ce\u683c\u4e3a\u901a\u7528\r\n            'audio-24khz-48kbitrate-mono-mp3' \/\/ \u56fa\u5b9a\u97f3\u9891\u683c\u5f0f\u4e3a MP3\r\n        );\r\n\r\n        return audioResponse;\r\n    } catch (error) {\r\n        return createErrorResponse(error.message, 'edge_tts_error', 500);\r\n    }\r\n}\r\n\r\n\/\/ \u5904\u7406 OPTIONS \u8bf7\u6c42\r\nfunction handleOptions(request) {\r\n    return new Response(null, {\r\n        headers: {\r\n            ...makeCORSHeaders(),\r\n            'Access-Control-Allow-Methods': 'GET,HEAD,POST,OPTIONS',\r\n            'Access-Control-Allow-Headers': request.headers.get('Access-Control-Request-Headers') || 'Authorization',\r\n        },\r\n    });\r\n}\r\n\r\n\/\/ \u521b\u5efa\u9519\u8bef\u54cd\u5e94\r\nfunction createErrorResponse(message, code, status) {\r\n    return new Response(\r\n        JSON.stringify({\r\n            error: { message, code }\r\n        }),\r\n        {\r\n            status,\r\n            headers: { 'Content-Type': 'application\/json', ...makeCORSHeaders() },\r\n        }\r\n    );\r\n}\r\n\r\n\/\/ \u9a8c\u8bc1\u53c2\u6570\u8303\u56f4\r\nfunction validateParameterRange(name, value, min, max) {\r\n    if (value &lt; min || value &gt; max) {\r\n        throw new Error(`${name} must be between ${min} and ${max}`);\r\n    }\r\n}\r\n\r\n\/\/ \u8ba1\u7b97\u8bed\u901f\r\nfunction calculateRate(speed) {\r\n    return parseInt(String((parseFloat(speed) - 1.0) * 100));\r\n}\r\n\r\n\/\/ \u8ba1\u7b97\u97f3\u8c03\r\nfunction calculatePitch(pitch) {\r\n    return parseInt(String((parseFloat(pitch) - 1.0) * 100));\r\n}\r\n\r\n\/\/ \u751f\u6210 CORS \u5934\r\nfunction makeCORSHeaders() {\r\n    return {\r\n        'Access-Control-Allow-Origin': '*',\r\n        'Access-Control-Allow-Methods': 'GET,HEAD,POST,OPTIONS',\r\n        'Access-Control-Allow-Headers': 'Content-Type, x-api-key',\r\n        'Access-Control-Max-Age': '86400',\r\n    };\r\n}\r\n\r\n\/\/ \u83b7\u53d6\u8bed\u97f3\r\nasync function getVoice(text, voiceName, rate, pitch, volume, style, outputFormat) {\r\n    try {\r\n        const chunks = text.trim().split(\"\\n\");\r\n        const audioChunks = await Promise.all(chunks.map(chunk =&gt; getAudioChunk(chunk, voiceName, rate, pitch, volume, style, outputFormat)));\r\n\r\n        \/\/ \u5c06\u97f3\u9891\u7247\u6bb5\u62fc\u63a5\u8d77\u6765\r\n        const concatenatedAudio = new Blob(audioChunks, { type: `audio\/${outputFormat.split('-').pop()}` });\r\n        return new Response(concatenatedAudio, {\r\n            headers: {\r\n                'Content-Type': `audio\/${outputFormat.split('-').pop()}`,\r\n                ...makeCORSHeaders(),\r\n            },\r\n        });\r\n    } catch (error) {\r\n        console.error(\"\u8bed\u97f3\u5408\u6210\u5931\u8d25:\", error);\r\n        return createErrorResponse(error.message, 'edge_tts_error', 500);\r\n    }\r\n}\r\n\r\n\/\/ \u83b7\u53d6\u5355\u4e2a\u97f3\u9891\u7247\u6bb5\r\nasync function getAudioChunk(text, voiceName, rate, pitch, volume, style, outputFormat) {\r\n    const endpoint = await getEndpoint();\r\n    const url = `https:\/\/${endpoint.r}.tts.speech.microsoft.com\/cognitiveservices\/v1`;\r\n    const slien = extractSilenceDuration(text);\r\n\r\n    const response = await fetch(url, {\r\n        method: \"POST\",\r\n        headers: {\r\n            \"Authorization\": endpoint.t,\r\n            \"Content-Type\": \"application\/ssml+xml\",\r\n            \"User-Agent\": \"Mozilla\/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit\/537.36 (KHTML, like Gecko) Chrome\/127.0.0.0 Safari\/537.36 Edg\/127.0.0.0\",\r\n            \"X-Microsoft-OutputFormat\": outputFormat,\r\n        },\r\n        body: getSsml(text, voiceName, rate, pitch, volume, style, slien),\r\n    });\r\n\r\n    if (!response.ok) {\r\n        const errorText = await response.text();\r\n        throw new Error(`Edge TTS API error: ${response.status} ${errorText}`);\r\n    }\r\n\r\n    return response.blob();\r\n}\r\n\r\n\/\/ \u63d0\u53d6\u9759\u97f3\u65f6\u957f\r\nfunction extractSilenceDuration(text) {\r\n    const match = text.match(\/\\[(\\d+)\\]\\s*?$\/);\r\n    return match &amp;&amp; match.length === 2 ? parseInt(match[1]) : 0;\r\n}\r\n\r\n\/\/ \u751f\u6210 SSML\r\nfunction getSsml(text, voiceName, rate, pitch, volume, style, slien) {\r\n    const slienStr = slien &gt; 0 ? `` : '';\r\n    return ` \r\n                 \r\n                     \r\n                        ${text} \r\n                     \r\n                    ${slienStr}\r\n                 \r\n            `;\r\n}\r\n\r\n\/\/ \u83b7\u53d6 Endpoint\r\nasync function getEndpoint() {\r\n    const now = Date.now() \/ 1000;\r\n    if (tokenInfo.token &amp;&amp; tokenInfo.expiredAt &amp;&amp; now &lt; tokenInfo.expiredAt - TOKEN_REFRESH_BEFORE_EXPIRY) {\r\n        return tokenInfo.endpoint;\r\n    }\r\n\r\n    \/\/ \u83b7\u53d6\u65b0 Token\r\n    const endpointUrl = \"https:\/\/dev.microsofttranslator.com\/apps\/endpoint?api-version=1.0\";\r\n    const clientId = crypto.randomUUID().replace(\/-\/g, \"\");\r\n\r\n    try {\r\n        const response = await fetch(endpointUrl, {\r\n            method: \"POST\",\r\n            headers: {\r\n                \"Accept-Language\": \"zh-Hans\",\r\n                \"X-ClientVersion\": \"4.0.530a 5fe1dc6c\",\r\n                \"X-UserId\": \"0f04d16a175c411e\",\r\n                \"X-HomeGeographicRegion\": \"zh-Hans-CN\",\r\n                \"X-ClientTraceId\": clientId,\r\n                \"X-MT-Signature\": await sign(endpointUrl),\r\n                \"User-Agent\": \"Mozilla\/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit\/537.36 (KHTML, like Gecko) Chrome\/127.0.0.0 Safari\/537.36 Edg\/127.0.0.0\",\r\n                \"Content-Type\": \"application\/json; charset=utf-8\",\r\n                \"Content-Length\": \"0\",\r\n                \"Accept-Encoding\": \"gzip\",\r\n            },\r\n        });\r\n\r\n        if (!response.ok) {\r\n            throw new Error(`\u83b7\u53d6 Endpoint \u5931\u8d25: ${response.status}`);\r\n        }\r\n\r\n        const data = await response.json();\r\n        const jwt = data.t.split(\".\")[1];\r\n        const decodedJwt = JSON.parse(atob(jwt));\r\n\r\n        tokenInfo = {\r\n            endpoint: data,\r\n            token: data.t,\r\n            expiredAt: decodedJwt.exp,\r\n        };\r\n\r\n        return data;\r\n    } catch (error) {\r\n        console.error(\"\u83b7\u53d6 Endpoint \u5931\u8d25:\", error);\r\n        if (tokenInfo.token) {\r\n            console.log(\"\u4f7f\u7528\u8fc7\u671f\u7684\u7f13\u5b58 Token\");\r\n            return tokenInfo.endpoint;\r\n        }\r\n        throw error;\r\n    }\r\n}\r\n\r\n\/\/ \u7b7e\u540d\r\nasync function sign(urlStr) {\r\n    const url = urlStr.split(\":\/\/\")[1];\r\n    const encodedUrl = encodeURIComponent(url);\r\n    const uuidStr = uuid();\r\n    const formattedDate = dateFormat();\r\n    const bytesToSign = `MSTranslatorAndroidApp${encodedUrl}${formattedDate}${uuidStr}`.toLowerCase();\r\n    const decode = await base64ToBytes(\"oik6PdDdMnOXemTbwvMn9de\/h9lFnfBaCWbGMMZqqoSaQaqUOqjVGm5NqsmjcBI1x+sS9ugjB55HEJWRiFXYFw==\");\r\n    const signData = await hmacSha256(decode, bytesToSign);\r\n    const signBase64 = await bytesToBase64(signData);\r\n    return `MSTranslatorAndroidApp::${signBase64}::${formattedDate}::${uuidStr}`;\r\n}\r\n\r\n\/\/ \u683c\u5f0f\u5316\u65e5\u671f\r\nfunction dateFormat() {\r\n    return (new Date()).toUTCString().replace(\/GMT\/, \"\").trim() + \" GMT\";\r\n}\r\n\r\n\/\/ HMAC SHA-256 \u7b7e\u540d\r\nasync function hmacSha256(key, data) {\r\n    const cryptoKey = await crypto.subtle.importKey(\r\n        \"raw\",\r\n        key,\r\n        { name: \"HMAC\", hash: { name: \"SHA-256\" } },\r\n        false,\r\n        [\"sign\"]\r\n    );\r\n    const signature = await crypto.subtle.sign(\"HMAC\", cryptoKey, new TextEncoder().encode(data));\r\n    return new Uint8Array(signature);\r\n}\r\n\r\n\/\/ Base64 \u8f6c\u5b57\u8282\u6570\u7ec4\r\nasync function base64ToBytes(base64) {\r\n    const binaryString = atob(base64);\r\n    const bytes = new Uint8Array(binaryString.length);\r\n    for (let i = 0; i &lt; binaryString.length; i++) {\r\n        bytes[i] = binaryString.charCodeAt(i);\r\n    }\r\n    return bytes;\r\n}\r\n\r\n\/\/ \u5b57\u8282\u6570\u7ec4\u8f6c Base64\r\nasync function bytesToBase64(bytes) {\r\n    return btoa(String.fromCharCode.apply(null, bytes));\r\n}\r\n\r\n\/\/ \u751f\u6210 UUID\r\nfunction uuid() {\r\n    return crypto.randomUUID().replace(\/-\/g, \"\");\r\n}\r\n\r\n\/\/ \u83b7\u53d6 Web UI\r\nfunction getWebUI() {\r\n    return `\r\n        \r\n        \r\n        \r\n        \r\n        \r\n\r\n\r\n<style>\r\n            \/* Neo-Brutalism \u98ce\u683c *\/\r\n            body {\r\n                font-family: 'Arial', sans-serif;\r\n                margin: 0;\r\n                padding: 20px;\r\n                background-color: #f0f0f0;\r\n                color: #333;\r\n            }\r\n            h1 {\r\n                text-align: center;\r\n                color: #333;\r\n                font-size: 2rem;\r\n                margin-bottom: 20px;\r\n                text-transform: uppercase;\r\n                letter-spacing: 2px;\r\n            }\r\n            .form-container {\r\n                max-width: 600px;\r\n                margin: 0 auto;\r\n                padding: 20px;\r\n                background-color: #fff;\r\n                border: 3px solid #333;\r\n                border-radius: 10px;\r\n                box-shadow: 8px 8px 0 #333;\r\n            }\r\n            .form-group {\r\n                margin-bottom: 20px;\r\n            }\r\n            label {\r\n                display: block;\r\n                margin-bottom: 8px;\r\n                font-weight: bold;\r\n                font-size: 1rem;\r\n                color: #333;\r\n            }\r\n            input, select, textarea, button {\r\n                width: 100%;\r\n                padding: 12px;\r\n                margin-top: 5px;\r\n                border: 2px solid #333;\r\n                border-radius: 5px;\r\n                font-size: 1rem;\r\n                background-color: #fff;\r\n                color: #333;\r\n                outline: none;\r\n                box-sizing: border-box; \/* \u786e\u4fdd\u5bbd\u5ea6\u5305\u542b padding \u548c border *\/\r\n            }\r\n            textarea {\r\n                resize: vertical;\r\n                height: 150px;\r\n            }\r\n            button {\r\n                background-color: #333;\r\n                color: #fff;\r\n                font-weight: bold;\r\n                cursor: pointer;\r\n                transition: background-color 0.3s ease;\r\n            }\r\n            button:hover {\r\n                background-color: #555;\r\n            }\r\n            audio {\r\n                width: 100%;\r\n                margin-top: 20px;\r\n                border: 2px solid #333;\r\n                border-radius: 5px;\r\n            }\r\n            .loading {\r\n                display: none;\r\n                text-align: center;\r\n                margin-top: 20px;\r\n                font-size: 1.2rem;\r\n                color: #333;\r\n            }\r\n            .error {\r\n                color: #ff4d4d;\r\n                margin-top: 10px;\r\n                text-align: center;\r\n                font-size: 1rem;\r\n            }\r\n    \r\n            \/* \u54cd\u5e94\u5f0f\u8bbe\u8ba1 *\/\r\n            @media (max-width: 768px) {\r\n                h1 {\r\n                    font-size: 1.5rem;\r\n                }\r\n                .form-container {\r\n                    padding: 15px;\r\n                }\r\n                input, select, textarea, button {\r\n                    padding: 10px;\r\n                    font-size: 0.9rem;\r\n                }\r\n            }\r\n    \r\n            @media (max-width: 480px) {\r\n                h1 {\r\n                    font-size: 1.2rem;\r\n                }\r\n                .form-container {\r\n                    padding: 10px;\r\n                }\r\n                input, select, textarea, button {\r\n                    padding: 8px;\r\n                    font-size: 0.8rem;\r\n                }\r\n            }\r\n        <\/style><\/pre>\n<p>&nbsp;<\/p>\n<p>&nbsp;<\/p>\n<h1>\u6587\u672c\u8f6c\u8bed\u97f3\u5de5\u5177<\/h1>\n<p>&nbsp;<\/p>\n<div class=\"form-container\">\n<p>&nbsp;<\/p>\n<form id=\"tts-form\">&nbsp;<\/p>\n<div class=\"form-group\"><label for=\"text\">\u8f93\u5165\u6587\u672c\uff1a<\/label><br \/>\n<textarea id=\"text\" required=\"\" placeholder=\"\u8bf7\u8f93\u5165\u8981\u8f6c\u6362\u4e3a\u8bed\u97f3\u7684\u6587\u672c\">\u4f60\u597d\u554a\uff0c\u4eb2\u7231\u7684\u670b\u53cb\u4eec\uff01<\/textarea><\/div>\n<p>&nbsp;<\/p>\n<div class=\"form-group\"><label for=\"voice\">\u9009\u62e9\u8bed\u97f3\uff1a<\/label><select id=\"voice\"><!-- \u4e2d\u6587\u8bed\u97f3 --><option value=\"zh-CN-XiaoxiaoNeural\">\u6653\u6653 &#8211; \u6e29\u6696\u6d3b\u6cfc<\/option><option value=\"zh-CN-XiaoyiNeural\">\u6653\u4f0a &#8211; \u6e29\u6696\u4eb2\u5207<\/option><option value=\"zh-CN-YunxiNeural\">\u4e91\u5e0c &#8211; \u7537\u58f0\uff0c\u7a33\u91cd<\/option><option value=\"zh-CN-YunyangNeural\">\u4e91\u626c &#8211; \u7537\u58f0\uff0c\u4e13\u4e1a<\/option><option value=\"zh-CN-XiaohanNeural\">\u6653\u6db5 &#8211; \u81ea\u7136\u6d41\u7545<\/option><option value=\"zh-CN-XiaomengNeural\">\u6653\u68a6 &#8211; \u751c\u7f8e\u6d3b\u529b<\/option><option value=\"zh-CN-XiaochenNeural\">\u6653\u8fb0 &#8211; \u6e29\u548c\u4ece\u5bb9<\/option><option value=\"zh-CN-XiaoruiNeural\">\u6653\u777f &#8211; \u7537\u58f0\uff0c\u5112\u96c5<\/option><option value=\"zh-CN-XiaoshuangNeural\">\u6653\u53cc &#8211; \u5973\u58f0\uff0c\u6e29\u67d4<\/option><option value=\"zh-CN-YunfengNeural\">\u4e91\u67ab &#8211; \u7537\u58f0\uff0c\u6210\u719f<\/option><option value=\"zh-CN-YunjianNeural\">\u4e91\u5065 &#8211; \u7537\u58f0\uff0c\u9633\u5149<\/option><option value=\"zh-CN-XiaoxuanNeural\">\u6653\u8431 &#8211; \u5973\u58f0\uff0c\u77e5\u6027<\/option><option value=\"zh-CN-YunxiaNeural\">\u4e91\u590f &#8211; \u7537\u58f0\uff0c\u9752\u6625<\/option><option value=\"zh-CN-XiaomoNeural\">\u6653\u58a8 &#8211; \u5973\u58f0\uff0c\u4f18\u96c5<\/option><option value=\"zh-CN-XiaozhenNeural\">\u6653\u7504 &#8211; \u5973\u58f0\uff0c\u81ea\u4fe1<\/option><!-- \u82f1\u6587\u8bed\u97f3 --><option value=\"en-US-JennyNeural\">Jenny &#8211; \u82f1\u6587<\/option><option value=\"en-US-GuyNeural\">Guy &#8211; \u82f1\u6587<\/option><!-- \u65e5\u6587\u8bed\u97f3 --><option value=\"ja-JP-NanamiNeural\">Nanami &#8211; \u65e5\u6587<\/option><option value=\"ja-JP-KeitaNeural\">Keita &#8211; \u65e5\u6587<\/option><!-- \u97e9\u6587\u8bed\u97f3 --><option value=\"ko-KR-SunHiNeural\">Sun-Hi &#8211; \u97e9\u6587<\/option><option value=\"ko-KR-InJoonNeural\">InJoon &#8211; \u97e9\u6587<\/option><\/select><\/div>\n<p>&nbsp;<\/p>\n<div class=\"form-group\"><label for=\"speed\">\u8bed\u901f (0.5-2.0)\uff1a<\/label> <input id=\"speed\" max=\"2.0\" min=\"0.5\" step=\"0.1\" type=\"number\" value=\"1.0\" \/><\/div>\n<p>&nbsp;<\/p>\n<div class=\"form-group\"><label for=\"pitch\">\u97f3\u8c03 (0.5-2.0)\uff1a<\/label> <input id=\"pitch\" max=\"2.0\" min=\"0.5\" step=\"0.1\" type=\"number\" value=\"1.0\" \/><\/div>\n<p><button type=\"submit\">\u751f\u6210\u8bed\u97f3<\/button><\/p>\n<\/form>\n<p>&nbsp;<\/p>\n<div id=\"loading\" class=\"loading\">\u6b63\u5728\u751f\u6210\u8bed\u97f3\uff0c\u8bf7\u7a0d\u5019&#8230;<\/div>\n<p>&nbsp;<\/p>\n<div id=\"error\" class=\"error\"><\/div>\n<p><audio id=\"audio-player\" controls=\"controls\"><\/audio><\/p>\n<\/div>\n<p><script>\n            document.getElementById('tts-form').addEventListener('submit', async function(event) {\n                event.preventDefault();\n                document.getElementById('loading').style.display = 'block';\n                document.getElementById('error').textContent = '';\n                const text = document.getElementById('text').value;\n                const voice = document.getElementById('voice').value;\n                const speed = document.getElementById('speed').value;\n                const pitch = document.getElementById('pitch').value;\n                const requestBody = { \n                    model: \"tts-1\", \n                    input: text, \n                    voice: voice, \n                    speed: parseFloat(speed), \n                    pitch: parseFloat(pitch)\n                };\n                try {\n                    const response = await fetch('\/v1\/audio\/speech', {\n                        method: 'POST',\n                        headers: { 'Content-Type': 'application\/json', 'Authorization': 'Bearer aisharenet' },\n                        body: JSON.stringify(requestBody)\n                    });\n                    if (response.ok) {\n                        const blob = await response.blob();\n                        const audioUrl = URL.createObjectURL(blob);\n                        document.getElementById('audio-player').src = audioUrl;\n                    } else {\n                        const error = await response.json();\n                        document.getElementById('error').textContent = \\`\u9519\u8bef\uff1a\\${error.error.message}\\`;\n                    }\n                } catch (error) {\n                    document.getElementById('error').textContent = \\`\u9519\u8bef\uff1a\\${error.message}\\`;\n                } finally {\n                    document.getElementById('loading').style.display = 'none';\n                }\n            });\n        <\/script><\/p>\n<pre>`; }<\/pre>\n","protected":false},"excerpt":{"rendered":"<p>Edge TTS Worker\uff08\u4f9d\u8d56\u00a0edge-tts \uff09 \u662f\u4e00\u4e2a\u90e8\u7f72\u5728 Cloudflare Worker \u4e0a\u7684\u4ee3\u7406\u670d\u52a1\uff0c\u5b83\u5c06\u5fae\u8f6f Edge TTS \u670d\u52a1\u5c01\u88c5\u6210\u517c\u5bb9 OpenAI \u683c\u5f0f\u7684 API \u63a5\u53e3\u3002\u901a\u8fc7\u672c\u9879\u76ee\uff0c\u7528\u6237\u53ef\u4ee5\u5728\u6ca1\u6709\u5fae\u8f6f\u8ba4\u8bc1\u7684&#8230;<\/p>\n","protected":false},"author":1,"featured_media":32782,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[20],"tags":[254,230,215],"class_list":["post-16666","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-tool","tag-aifuyexiangmu","tag-aikaiyuanxiangmu","tag-aiwenbenzhuanyuyin"],"_links":{"self":[{"href":"https:\/\/www.kdjingpai.com\/pt\/wp-json\/wp\/v2\/posts\/16666","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.kdjingpai.com\/pt\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.kdjingpai.com\/pt\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.kdjingpai.com\/pt\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.kdjingpai.com\/pt\/wp-json\/wp\/v2\/comments?post=16666"}],"version-history":[{"count":0,"href":"https:\/\/www.kdjingpai.com\/pt\/wp-json\/wp\/v2\/posts\/16666\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.kdjingpai.com\/pt\/wp-json\/wp\/v2\/media\/32782"}],"wp:attachment":[{"href":"https:\/\/www.kdjingpai.com\/pt\/wp-json\/wp\/v2\/media?parent=16666"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.kdjingpai.com\/pt\/wp-json\/wp\/v2\/categories?post=16666"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.kdjingpai.com\/pt\/wp-json\/wp\/v2\/tags?post=16666"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}