Edge TTS Worker : Déploiement des API de synthèse vocale de Microsoft à l'aide de Cloudflare, d'un format compatible avec OpenAI et d'une interface Web packagée
Introduction générale
Edge TTS Worker (dépendances) edge-tts Edge TTS Worker est un service proxy déployé sur Cloudflare Worker, qui encapsule le service Microsoft Edge TTS dans une interface API compatible avec le format OpenAI. Avec ce projet, les utilisateurs peuvent facilement utiliser le service de synthèse vocale de haute qualité de Microsoft sans la certification de Microsoft.Edge TTS Worker fournit un support multilingue, y compris le chinois, l'anglais, le japonais, le coréen, etc. et est entièrement gratuit, basé sur le plan gratuit de Cloudflare Worker. Le service prend également en charge les clés API personnalisées pour garantir la sécurité et le contrôle, et peut être déployé rapidement, en quelques minutes.
Test API : https://tts.aishare.us.kg/ KEY : aisharenet
Un projet visant à créer une interface simple pour l'API

Emballage des API et des interfaces avec déploiement de Cloudflare :
Le code complet est généré par CHATGPT, et le code est joint à la fin de l'article, avec la possibilité d'en ajouter d'autres. Programmation de l'IA Outils ou utilisation Trickle (Génération en un clic de services de génération de discours accessibles en ligne) :

Expérience : https://edgetts.aishare.us.kg/
Liste des fonctions
- Fournit des formats d'interface compatibles avec l'OpenAI
- Contourner les restrictions d'accès au continent et éliminer l'étape d'authentification des services Microsoft
- Prise en charge de plusieurs langues, dont le chinois, l'anglais, le japonais, le coréen, etc.
- Totalement gratuit, basé sur le plan Cloudflare Worker Free
- Prise en charge des clés d'API personnalisées pour garantir la sécurité et le contrôle
- Déploiement rapide, prêt en quelques minutes
- Fournir des scripts de test pour tester différents effets vocaux
Utiliser l'aide
Processus d'installation
- Création d'un travailleur
- Se connecter au tableau de bord Cloudflare
- Allez dans Travailleurs et pages et cliquez sur Créer un travailleur.
- Donner un nom au travailleur (par exemple, edge-tts)
- Code de déploiement
- Supprimer le code par défaut de l'éditeur
- faire une copie de
worker.js
et collez le code dans le champ - Cliquez sur Enregistrer et déployer
- Définition de la clé API (facultatif)
- Trouvez Paramètres -> Variables dans la page des paramètres du travailleur.
- Cliquez sur Add variable, remplissez API_KEY pour le nom et la valeur de la clé que vous voulez.
- Cliquez sur Enregistrer et déployer
- Configurer un nom de domaine personnalisé (optionnel)
- Prérequis : Votre domaine est déjà hébergé sur Cloudflare et les enregistrements DNS pour le domaine ont été mandatés par Cloudflare (le statut du mandat est nuage orange).
- Étapes de configuration :
- Cliquez sur l'onglet Paramètres de la page Détails du travailleur.
- Localisez la section Domaine et routage et cliquez sur le bouton Ajouter.
- Sélectionnez Domaine personnalisé et saisissez le nom de domaine que vous souhaitez utiliser (par exemple, tts.example.com).
- Cliquez sur Ajouter un domaine et attendez que le déploiement du certificat soit terminé (généralement en quelques minutes).
Utilisation
- TTY (interface texte-parole)
- Exemple de discours en chinois :
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
- Exemple de discours en anglais :
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
- Utilisation du script de test
- Télécharger le script de test
test_voices.sh
- Ajouter des autorisations d'exécution au script :
bash
chmod +x test_voices.sh - Exécutez le script :
bash
./test_voices.sh <Worker地址> [API密钥] - Exemple :
bash
# 使用 API 密钥
./test_voices.sh https://your-worker.workers.dev your-api-key
# 不使用 API 密钥
./test_voices.sh https://your-worker.workers.dev - Le script génère des fichiers audio de test pour chaque voix prise en charge, que vous pouvez écouter pour sélectionner la voix la plus appropriée.
- Télécharger le script de test
Paramètre API Description
model
(chaîne) : nom du modèle (valeur fixe), par exempletts-1
input
(chaîne) : le texte à convertir, par exemple"你好,世界!"
voice
(chaîne) : nom de la voix, par exemplezh-CN-XiaoxiaoNeural
response_format
(chaîne, facultatif) : format de sortie, la valeur par défaut estmp3
speed
(nombre, optionnel) : débit de parole (0.5-2.0), valeur par défaut de1.0
pitch
(nombre, facultatif) : tonalité (0,5-2,0), la valeur par défaut est1.0
style
(chaîne, facultatif) : émotion, par défautgeneral
Liste des voix prises en charge
Veillez à utiliser le texte de la langue correspondant à la voix, par exemple la voix chinoise doit être utilisée avec le texte chinois. Voici des exemples de voix couramment utilisées :
zh-CN-XiaoxiaoNeural
Xiaoxiao - Chaleureuse et vivantezh-CN-XiaoyiNeural
Xiaoyi - Chaleur et gentillessezh-CN-YunxiNeural
: : Yumshi - voix d'homme, régulièrezh-CN-YunyangNeural
: : Yun Yang - voix masculine, professionnellezh-CN-XiaohanNeural
Xiaohan - Natural Flow (flux naturel)zh-CN-XiaomengNeural
Xiaomeng - Douceur et vitalitézh-CN-XiaochenNeural
Xiaochen - Douceur et facilité- Attendez...
code 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 `
Outil de synthèse vocale
Génération de la voix, veuillez patienter...
`; }
© déclaration de droits d'auteur
L'article est protégé par le droit d'auteur et ne doit pas être reproduit sans autorisation.
Articles connexes
Pas de commentaires...