Edge TTS Worker: развертывание Microsoft Speech Synthesis API с помощью Cloudflare, совместимого формата OpenAI и упакованного веб-интерфейса
Общее введение
Edge TTS Worker (зависимости) край-тц Edge TTS Worker - это прокси-сервис, развернутый на Cloudflare Worker, который инкапсулирует сервис Microsoft Edge TTS в API-интерфейс, совместимый с форматом OpenAI. С помощью этого проекта пользователи могут легко использовать высококачественный сервис синтеза речи Microsoft без сертификации Microsoft.Edge TTS Worker обеспечивает многоязыковую поддержку, включая китайский, английский, японский, корейский и т. д., и является полностью бесплатным, основываясь на Cloudflare Worker Free Plan. Сервис также поддерживает пользовательские API-ключи для обеспечения безопасности и контроля и может быть развернут быстро, в течение нескольких минут.
Test API: https://tts.aishare.us.kg/ KEY: aisharenet
Проект по созданию простого интерфейса для API

Упаковка API и интерфейсов в комплекте с развертыванием Cloudflare:
Полный код генерируется с помощью CHATGPT, и он прикреплен в конце статьи, с возможностью добавить еще Программирование ИИ Инструменты или использование Trickle (Генерация доступной речи в один клик с помощью онлайн-сервисов):

Опыт работы: https://edgetts.aishare.us.kg/
Список функций
- Обеспечивает совместимые с OpenAI форматы интерфейса
- Обход ограничений доступа к материку и исключение этапа аутентификации служб Microsoft
- Поддержка нескольких языков, включая китайский, английский, японский, корейский и др.
- Полностью бесплатно, на основе бесплатного плана Cloudflare Worker Free Plan
- Поддержка пользовательских ключей API для обеспечения безопасности и контроля
- Быстрое развертывание, готовность за считанные минуты
- Предоставьте тестовые сценарии для тестирования различных голосовых эффектов
Использование помощи
Процесс установки
- Создание работника
- Войдите в панель Cloudflare Dashboard
- Перейдите в раздел Рабочие и страницы и нажмите кнопку Создать рабочего.
- Дайте работнику имя (например, edge-tts).
- Код развертывания
- Удалите код по умолчанию из редактора
- сделать копию
worker.js
и вставьте код в - Нажмите Сохранить и развернуть
- Установка ключа API (необязательно)
- Найдите Настройки -> Переменные на странице настроек рабочего.
- Нажмите кнопку Добавить переменную, введите API_KEY в качестве имени и значения нужного вам ключа.
- Нажмите Сохранить и развернуть
- Настройте пользовательское доменное имя (необязательно)
- Необходимые условия: ваш домен уже размещен на Cloudflare, а DNS-записи для домена проксированы через Cloudflare (статус прокси - оранжевое облако).
- Шаги конфигурации:
- Перейдите на вкладку Настройки на странице сведений о работнике.
- Найдите раздел Домен и маршрутизация и нажмите кнопку Добавить.
- Выберите Custom Domain и введите доменное имя, которое вы хотите использовать (например, tts.example.com).
- Нажмите кнопку Добавить домен и дождитесь завершения развертывания сертификата (обычно это занимает несколько минут).
Использование
- TTY (интерфейс преобразования текста в речь)
- Пример китайской речи:
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
- Использование тестового сценария
- Скачать сценарий тестирования
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
(строка): имя модели (фиксированное значение), напримерtts-1
input
(строка): текст для преобразования, например"你好,世界!"
voice
(строка): название голоса, напримерzh-CN-XiaoxiaoNeural
response_format
(строка, необязательно): формат вывода, значение по умолчаниюmp3
speed
(число, необязательно): скорость речи (0,5-2,0), по умолчанию1.0
pitch
(число, необязательно): тон (0,5-2,0), по умолчанию1.0
style
(строка, необязательно): эмоция, по умолчаниюgeneral
Список поддерживаемых голосов
Пожалуйста, убедитесь, что вы используете текст на языке, соответствующем голосу, например, китайский голос должен использоваться с китайским текстом. Ниже приведены примеры часто используемых голосов:
zh-CN-XiaoxiaoNeural
: Xiaoxiao - Теплый и живойzh-CN-XiaoyiNeural
: Сяои - Тепло и добротаzh-CN-YunxiNeural
:: Юмши - мужской голос, устойчивыйzh-CN-YunyangNeural
:: Юнь Ян - мужской голос, профессионалzh-CN-XiaohanNeural
: Xiaohan - Natural Flowzh-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 `
Инструмент преобразования текста в речь
Пожалуйста, подождите, пока голос будет сгенерирован...
`; }
© заявление об авторских правах
Авторское право на статью Круг обмена ИИ Пожалуйста, не воспроизводите без разрешения.
Похожие статьи
Нет комментариев...