Allgemeine Einführung
Edge TTS Worker (Abhängigkeiten) edge-tts Edge TTS Worker ist ein auf Cloudflare Worker bereitgestellter Proxy-Dienst, der den Microsoft Edge TTS-Dienst in eine API-Schnittstelle kapselt, die mit dem OpenAI-Format kompatibel ist. Edge TTS Worker bietet Unterstützung für mehrere Sprachen, darunter Chinesisch, Englisch, Japanisch, Koreanisch usw., und ist völlig kostenlos, basierend auf dem Cloudflare Worker Free Plan. Der Dienst unterstützt auch benutzerdefinierte API-Schlüssel, um Sicherheit und Kontrolle zu gewährleisten, und kann innerhalb von Minuten bereitgestellt werden.
Test-API: https://tts.aishare.us.kg/ KEY: aisharenet
Ein Projekt zur Paketierung einer einfachen Schnittstelle für die API
Paketierung von APIs und Schnittstellen komplett mit Cloudflare-Bereitstellung:
Der vollständige Code wird von CHATGPT generiert und ist am Ende des Artikels angehängt, mit der Option, mehr hinzuzufügen KI-Programmierung Werkzeuge oder Verwendung Trickle (Generierung von online zugänglichen Sprachgenerierungsdiensten mit einem Klick):
Funktionsliste
- Bietet OpenAI-kompatible Schnittstellenformate
- Umgehung der Zugriffsbeschränkungen auf das Festland und Wegfall des Authentifizierungsschritts für Microsoft-Dienste
- Mehrsprachige Unterstützung, einschließlich Chinesisch, Englisch, Japanisch, Koreanisch usw.
- Völlig kostenlos, basierend auf Cloudflare Worker Free Plan
- Unterstützung für benutzerdefinierte API-Schlüssel zur Gewährleistung von Sicherheit und Kontrolle
- Schneller Einsatz, bereit in Minuten
- Bereitstellung von Testskripten zum Testen verschiedener Spracheffekte
Hilfe verwenden
Ablauf der Installation
- Erstellen eines Arbeiters
- Anmeldung bei Cloudflare Dashboard
- Gehen Sie zu Workers & Pages und klicken Sie auf Create Worker.
- Geben Sie dem Arbeiter einen Namen (z. B. edge-tts)
- Bereitstellungscode
- Entfernen Sie den Standardcode aus dem Editor
- eine Kopie machen von
worker.js
und fügen Sie den Code in das Feld - Klicken Sie auf Speichern und Bereitstellen
- Einstellen des API-Schlüssels (optional)
- Suchen Sie Einstellungen -> Variablen auf der Einstellungsseite des Workers.
- Klicken Sie auf Variable hinzufügen, geben Sie API_KEY als Namen und den Wert des gewünschten Schlüssels ein.
- Klicken Sie auf Speichern und Bereitstellen
- Konfigurieren Sie einen benutzerdefinierten Domänennamen (optional)
- Voraussetzungen: Ihre Domain wird bereits auf Cloudflare gehostet und die DNS-Einträge für die Domain wurden durch Cloudflare proxyiert (Proxy-Status ist orange Wolke)
- Schritte zur Konfiguration:
- Klicken Sie auf der Seite mit den Arbeitnehmerdetails auf die Registerkarte Einstellungen.
- Suchen Sie den Abschnitt Domäne und Routing und klicken Sie auf die Schaltfläche Hinzufügen.
- Wählen Sie Benutzerdefinierte Domäne und geben Sie den gewünschten Domänennamen ein (z. B. tts.example.com)
- Klicken Sie auf Domäne hinzufügen und warten Sie, bis die Zertifikatsbereitstellung abgeschlossen ist (in der Regel dauert es ein paar Minuten).
Verwendung
- TTY (Text-to-Speech-Schnittstelle)
- Beispiel für chinesische Sprache:
curl -X POST https://你的worker地址/v1/audio/speech \ -H "Content-Type: application/json" \\ -H "Authorization: Bearer your-api-key" \ -d '{ "model": "tts-1", "input": "Hallo". "input": "Hallo, Welt!" , "voice": "zh-CN-XiaoxiaoNeural", "speed": 1.0, "response_format". "Stellplatz": 1.0, "style": "general" }' --output chinese.mp3
- Beispiel für englische Sprache:
curl -X POST https://你的worker地址/v1/audio/speech \ -H "Content-Type: application/json" \\ -H "Authorization: Bearer your-api-key" \ -d '{ "model": "tts-1", "input": "Hallo Welt". "input": "Hallo, Welt!", "voice": "en-US". "Stimme": "en-US-JennyNeural", "Tonhöhe": 1.0, "Stil": "allgemein". "style": "general" }' --output english.mp3
- Verwendung des Testskripts
- Testskript herunterladen
test_Stimmen.sh
- Fügen Sie dem Skript Ausführungsberechtigungen hinzu:
bash
chmod +x test_voices.sh
- Führen Sie das Skript aus:
bash
. /test_voices.sh [API-Schlüssel].
- Beispiel:
bash
# API-Schlüssel verwenden
. /test_voices.sh https://your-worker.workers.dev ihr-api-schlüssel
# verwendet keinen API-Schlüssel
. /test_voices.sh https://your-worker.workers.dev
- Das Skript erzeugt Test-Audiodateien für jede unterstützte Stimme, die Sie abspielen können, um die am besten geeignete Stimme auszuwählen.
- Testskript herunterladen
API-Parameter Beschreibung
Modell
(string): Modellname (fester Wert), z. B.tts-1
Eingabe
(string): der zu konvertierende Text, z. B."Hallo, Welt!"
Stimme
(string): Name der Stimme, z. B.zh-CN-XiaoxiaoNeural
response_format
(string, optional): Ausgabeformat, Standardwert istmp3
Geschwindigkeit
(Zahl, optional): Sprechgeschwindigkeit (0,5-2,0), Standardwert ist1.0
Stellplatz
(Zahl, optional): Ton (0,5-2,0), Standardwert ist1.0
Stil
(string, optional): Emotion, Standardwert istallgemein
Liste der unterstützten Stimmen
Bitte stellen Sie sicher, dass Sie den Text in der Sprache verwenden, die der Stimme entspricht, z. B. sollte die chinesische Stimme mit dem chinesischen Text verwendet werden. Im Folgenden finden Sie Beispiele für häufig verwendete Stimmen:
zh-CN-XiaoxiaoNeural
Xiaoxiao - Warm und lebendigzh-CN-XiaoyiNeural
Xiaoyi - Herzlichkeit und Freundlichkeitzh-CN-YunxiNeural
:: Yumshi - männliche Stimme, konstantzh-CN-YunyangNeural
:: Yun Yang - männliche Stimme, professionellzh-CN-XiaohanNeural
Xiaohan - Natürlicher Flusszh-CN-XiaomengNeural
Xiaomeng - Süß und lebendigzh-CN-XiaochenNeural
Xiaochen - Sanft und leicht- Warten Sie...
workers.js-Code
const API_KEY = 'aisharenet'; // ersetzen Sie durch Ihren tatsächlichen API-Schlüssel 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 `
Text-zu-Sprache-Werkzeug
`; }