AI个人学习
和实操指南

Edge TTS Worker:使用Cloudflare部署微软语音合成API,兼容OpenAI 格式并封装Web界面

综合介绍

Edge TTS Worker(依赖 edge-tts ) 是一个部署在 Cloudflare Worker 上的代理服务,它将微软 Edge TTS 服务封装成兼容 OpenAI 格式的 API 接口。通过本项目,用户可以在没有微软认证的情况下,轻松使用微软高质量的语音合成服务。Edge TTS Worker 提供多语种支持,包括中文、英文、日文、韩文等,且完全免费,基于 Cloudflare Worker 免费计划。该服务还支持自定义 API 密钥,确保安全可控,并且可以快速部署,几分钟内即可完成。

测试API:https://tts.aishare.us.kg/ KEY:aisharenet


为API封装简易界面的项目

Edge TTS Worker:使用Cloudflare部署微软语音合成API,兼容OpenAI格式并封装Web界面-1

 

封装API和界面完整使用Cloudflare部署:

完整代码由CHATGPT生成,代码附文章末尾,可以选择更所 AI编程 工具 或使用 Trickle 一键生成在线可访问的语音生成服务):

Edge TTS Worker:使用Cloudflare部署微软语音合成API,兼容OpenAI 格式并封装Web界面-1

体验地址:https://edgetts.aishare.us.kg/

 

功能列表

  • 提供 OpenAI 兼容的接口格式
  • 绕过大陆地区访问限制,免去微软服务认证步骤
  • 多语种支持,包括中文、英文、日文、韩文等
  • 完全免费,基于 Cloudflare Worker 免费计划
  • 支持自定义 API 密钥,确保安全可控
  • 快速部署,几分钟内即可完成
  • 提供测试脚本,方便测试不同语音效果

 

使用帮助

安装流程

  1. 创建 Worker
    • 登录 Cloudflare Dashboard
    • 进入 Workers & Pages,点击 Create Worker
    • 为 Worker 取个名字(比如 edge-tts)
  2. 部署代码
    • 删除编辑器中的默认代码
    • 复制 worker.js 中的代码并粘贴
    • 点击 Save and deploy
  3. 设置 API Key(可选)
    • 在 Worker 的设置页面中找到 Settings -> Variables
    • 点击 Add variable,名称填写 API_KEY,值填写你想要的密钥
    • 点击 Save and deploy
  4. 配置自定义域名(可选)
    • 前提条件:你的域名已经托管在 Cloudflare,域名的 DNS 记录已经通过 Cloudflare 代理(代理状态为橙色云朵)
    • 配置步骤:
      • 在 Worker 的详情页面中点击 设置 标签
      • 找到 域和路由 部分,点击 添加 按钮
      • 选择 自定义域,输入你想要使用的域名(比如 tts.example.com)
      • 点击 添加域,等待证书部署完成(通常几分钟内)

使用方法

  1. 文本转语音接口
    • 中文语音示例:
     curl -X POST https://你的worker地址/v1/audio/speech \
    -H "Content-Type: application/json" \
    -H "Authorization: Bearer your-api-key" \
    -d '{
    "model": "tts-1",
    "input": "你好,世界!",
    "voice": "zh-CN-XiaoxiaoNeural",
    "response_format": "mp3",
    "speed": 1.0,
    "pitch": 1.0,
    "style":"general"
    }' --output chinese.mp3
    
    • 英文语音示例:
     curl -X POST https://你的worker地址/v1/audio/speech \
    -H "Content-Type: application/json" \
    -H "Authorization: Bearer your-api-key" \
    -d '{
    "model": "tts-1",
    "input": "Hello, World!",
    "voice": "en-US-JennyNeural",
    "response_format": "mp3",
    "speed": 1.0,
    "pitch": 1.0,
    "style":"general"
    }' --output english.mp3
    
  2. 测试脚本使用
    • 下载测试脚本 test_voices.sh
    • 给脚本添加执行权限: bash
      chmod +x test_voices.sh
    • 运行脚本: bash
      ./test_voices.sh <Worker地址> [API密钥]
    • 示例: bash
      # 使用 API 密钥
      ./test_voices.sh https://your-worker.workers.dev your-api-key
      # 不使用 API 密钥
      ./test_voices.sh https://your-worker.workers.dev
    • 脚本会为每个支持的语音生成测试音频文件,你可以通过播放这些文件来选择最适合的语音。

API 参数说明

  • model (string): 模型名称(固定值),例如 tts-1
  • input (string): 要转换的文本,例如 "你好,世界!"
  • voice (string): 语音名称,例如 zh-CN-XiaoxiaoNeural
  • response_format (string, 可选): 输出格式,默认值为 mp3
  • speed (number, 可选): 语速 (0.5-2.0),默认值为 1.0
  • pitch (number, 可选): 语调 (0.5-2.0),默认值为 1.0
  • style (string, 可选): 情绪,默认值为 general

支持的语音列表

请确保使用与语音对应的语言文本,例如中文语音需配合中文文本使用。以下是常用语音示例:

  • zh-CN-XiaoxiaoNeural: 晓晓 - 温暖活泼
  • zh-CN-XiaoyiNeural: 晓伊 - 温暖亲切
  • zh-CN-YunxiNeural: 云希 - 男声,稳重
  • zh-CN-YunyangNeural: 云扬 - 男声,专业
  • zh-CN-XiaohanNeural: 晓涵 - 自然流畅
  • zh-CN-XiaomengNeural: 晓梦 - 甜美活力
  • zh-CN-XiaochenNeural: 晓辰 - 温和从容
  • 等等...

 

workers.js代码

const API_KEY = 'aisharenet'; // 替换为你的实际 API key
const TOKEN_REFRESH_BEFORE_EXPIRY = 3 * 60; // Token 刷新时间(秒)
const DEFAULT_VOICE = 'zh-CN-XiaoxiaoNeural'; // 默认语音
const DEFAULT_SPEED = 1.0; // 默认语速
const DEFAULT_PITCH = 1.0; // 默认音调
let tokenInfo = {
endpoint: null,
token: null,
expiredAt: null
};
// 处理请求
addEventListener('fetch', event => {
event.respondWith(handleRequest(event.request));
});
async function handleRequest(request) {
const url = new URL(request.url);
// 如果请求根路径,返回 Web UI
if (url.pathname === '/') {
return new Response(getWebUI(), {
headers: { 'Content-Type': 'text/html' },
});
}
// 处理 TTS 请求
if (url.pathname === '/v1/audio/speech') {
return handleTTSRequest(request);
}
// 默认返回 404
return new Response('Not Found', { status: 404 });
}
// 处理 TTS 请求
async function handleTTSRequest(request) {
if (request.method === 'OPTIONS') {
return handleOptions(request);
}
// 验证 API Key
const authHeader = request.headers.get('authorization') || request.headers.get('x-api-key');
const apiKey = authHeader?.startsWith('Bearer ') ? authHeader.slice(7) : null;
if (API_KEY && apiKey !== API_KEY) {
return createErrorResponse('Invalid API key. Use \'Authorization: Bearer your-api-key\' header', 'invalid_api_key', 401);
}
try {
const requestBody = await request.json();
const { 
model = "tts-1",
input,
voice = DEFAULT_VOICE,
speed = DEFAULT_SPEED,
pitch = DEFAULT_PITCH
} = requestBody;
// 验证参数范围
validateParameterRange('speed', speed, 0.5, 2.0);
validateParameterRange('pitch', pitch, 0.5, 2.0);
const rate = calculateRate(speed);
const numPitch = calculatePitch(pitch);
const audioResponse = await getVoice(
input,
voice,
rate >= 0 ? `+${rate}%` : `${rate}%`,
numPitch >= 0 ? `+${numPitch}%` : `${numPitch}%`,
'+0%',
'general', // 固定风格为通用
'audio-24khz-48kbitrate-mono-mp3' // 固定音频格式为 MP3
);
return audioResponse;
} catch (error) {
return createErrorResponse(error.message, 'edge_tts_error', 500);
}
}
// 处理 OPTIONS 请求
function handleOptions(request) {
return new Response(null, {
headers: {
...makeCORSHeaders(),
'Access-Control-Allow-Methods': 'GET,HEAD,POST,OPTIONS',
'Access-Control-Allow-Headers': request.headers.get('Access-Control-Request-Headers') || 'Authorization',
},
});
}
// 创建错误响应
function createErrorResponse(message, code, status) {
return new Response(
JSON.stringify({
error: { message, code }
}),
{
status,
headers: { 'Content-Type': 'application/json', ...makeCORSHeaders() },
}
);
}
// 验证参数范围
function validateParameterRange(name, value, min, max) {
if (value < min || value > max) {
throw new Error(`${name} must be between ${min} and ${max}`);
}
}
// 计算语速
function calculateRate(speed) {
return parseInt(String((parseFloat(speed) - 1.0) * 100));
}
// 计算音调
function calculatePitch(pitch) {
return parseInt(String((parseFloat(pitch) - 1.0) * 100));
}
// 生成 CORS 头
function makeCORSHeaders() {
return {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'GET,HEAD,POST,OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type, x-api-key',
'Access-Control-Max-Age': '86400',
};
}
// 获取语音
async function getVoice(text, voiceName, rate, pitch, volume, style, outputFormat) {
try {
const chunks = text.trim().split("\n");
const audioChunks = await Promise.all(chunks.map(chunk => getAudioChunk(chunk, voiceName, rate, pitch, volume, style, outputFormat)));
// 将音频片段拼接起来
const concatenatedAudio = new Blob(audioChunks, { type: `audio/${outputFormat.split('-').pop()}` });
return new Response(concatenatedAudio, {
headers: {
'Content-Type': `audio/${outputFormat.split('-').pop()}`,
...makeCORSHeaders(),
},
});
} catch (error) {
console.error("语音合成失败:", error);
return createErrorResponse(error.message, 'edge_tts_error', 500);
}
}
// 获取单个音频片段
async function getAudioChunk(text, voiceName, rate, pitch, volume, style, outputFormat) {
const endpoint = await getEndpoint();
const url = `https://${endpoint.r}.tts.speech.microsoft.com/cognitiveservices/v1`;
const slien = extractSilenceDuration(text);
const response = await fetch(url, {
method: "POST",
headers: {
"Authorization": endpoint.t,
"Content-Type": "application/ssml+xml",
"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",
"X-Microsoft-OutputFormat": outputFormat,
},
body: getSsml(text, voiceName, rate, pitch, volume, style, slien),
});
if (!response.ok) {
const errorText = await response.text();
throw new Error(`Edge TTS API error: ${response.status} ${errorText}`);
}
return response.blob();
}
// 提取静音时长
function extractSilenceDuration(text) {
const match = text.match(/\[(\d+)\]\s*?$/);
return match && match.length === 2 ? parseInt(match[1]) : 0;
}
// 生成 SSML
function getSsml(text, voiceName, rate, pitch, volume, style, slien) {
const slienStr = slien > 0 ? `` : '';
return ` 
${text} 
${slienStr}
`;
}
// 获取 Endpoint
async function getEndpoint() {
const now = Date.now() / 1000;
if (tokenInfo.token && tokenInfo.expiredAt && now < tokenInfo.expiredAt - TOKEN_REFRESH_BEFORE_EXPIRY) {
return tokenInfo.endpoint;
}
// 获取新 Token
const endpointUrl = "https://dev.microsofttranslator.com/apps/endpoint?api-version=1.0";
const clientId = crypto.randomUUID().replace(/-/g, "");
try {
const response = await fetch(endpointUrl, {
method: "POST",
headers: {
"Accept-Language": "zh-Hans",
"X-ClientVersion": "4.0.530a 5fe1dc6c",
"X-UserId": "0f04d16a175c411e",
"X-HomeGeographicRegion": "zh-Hans-CN",
"X-ClientTraceId": clientId,
"X-MT-Signature": await sign(endpointUrl),
"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",
"Content-Type": "application/json; charset=utf-8",
"Content-Length": "0",
"Accept-Encoding": "gzip",
},
});
if (!response.ok) {
throw new Error(`获取 Endpoint 失败: ${response.status}`);
}
const data = await response.json();
const jwt = data.t.split(".")[1];
const decodedJwt = JSON.parse(atob(jwt));
tokenInfo = {
endpoint: data,
token: data.t,
expiredAt: decodedJwt.exp,
};
return data;
} catch (error) {
console.error("获取 Endpoint 失败:", error);
if (tokenInfo.token) {
console.log("使用过期的缓存 Token");
return tokenInfo.endpoint;
}
throw error;
}
}
// 签名
async function sign(urlStr) {
const url = urlStr.split("://")[1];
const encodedUrl = encodeURIComponent(url);
const uuidStr = uuid();
const formattedDate = dateFormat();
const bytesToSign = `MSTranslatorAndroidApp${encodedUrl}${formattedDate}${uuidStr}`.toLowerCase();
const decode = await base64ToBytes("oik6PdDdMnOXemTbwvMn9de/h9lFnfBaCWbGMMZqqoSaQaqUOqjVGm5NqsmjcBI1x+sS9ugjB55HEJWRiFXYFw==");
const signData = await hmacSha256(decode, bytesToSign);
const signBase64 = await bytesToBase64(signData);
return `MSTranslatorAndroidApp::${signBase64}::${formattedDate}::${uuidStr}`;
}
// 格式化日期
function dateFormat() {
return (new Date()).toUTCString().replace(/GMT/, "").trim() + " GMT";
}
// HMAC SHA-256 签名
async function hmacSha256(key, data) {
const cryptoKey = await crypto.subtle.importKey(
"raw",
key,
{ name: "HMAC", hash: { name: "SHA-256" } },
false,
["sign"]
);
const signature = await crypto.subtle.sign("HMAC", cryptoKey, new TextEncoder().encode(data));
return new Uint8Array(signature);
}
// Base64 转字节数组
async function base64ToBytes(base64) {
const binaryString = atob(base64);
const bytes = new Uint8Array(binaryString.length);
for (let i = 0; i < binaryString.length; i++) {
bytes[i] = binaryString.charCodeAt(i);
}
return bytes;
}
// 字节数组转 Base64
async function bytesToBase64(bytes) {
return btoa(String.fromCharCode.apply(null, bytes));
}
// 生成 UUID
function uuid() {
return crypto.randomUUID().replace(/-/g, "");
}
// 获取 Web UI
function getWebUI() {
return `

 

文本转语音工具

 

 

 


 

 

 

 

正在生成语音,请稍候...

 

`; }
未经允许不得转载:首席AI分享圈 » Edge TTS Worker:使用Cloudflare部署微软语音合成API,兼容OpenAI 格式并封装Web界面

首席AI分享圈

首席AI分享圈专注于人工智能学习,提供全面的AI学习内容、AI工具和实操指导。我们的目标是通过高质量的内容和实践经验分享,帮助用户掌握AI技术,一起挖掘AI的无限潜能。无论您是AI初学者还是资深专家,这里都是您获取知识、提升技能、实现创新的理想之地。

联系我们
zh_CN简体中文