tts: fix edge tts, closes #1821 (#1828)

This commit is contained in:
Huang Xin
2025-08-18 20:44:38 +08:00
committed by GitHub
parent 952d2a3553
commit 2e98ed44ee
+34 -1
View File
@@ -1,4 +1,5 @@
import { md5 } from 'js-md5';
import { createHash } from 'crypto';
import { randomMd5 } from '@/utils/misc';
import { LRUCache } from '@/utils/lru';
import { genSSML } from '@/utils/ssml';
@@ -6,6 +7,7 @@ import { genSSML } from '@/utils/ssml';
const EDGE_SPEECH_URL =
'wss://speech.platform.bing.com/consumer/speech/synthesize/readaloud/edge/v1';
const EDGE_API_TOKEN = '6A5AA1D4EAFF4E9FB37E23D68491D6F4';
const CHROMIUM_FULL_VERSION = '130.0.2849.68';
const EDGE_TTS_VOICES = {
'af-ZA': ['af-ZA-AdriNeural', 'af-ZA-WillemNeural'],
'am-ET': ['am-ET-AmehaNeural', 'am-ET-MekdesNeural'],
@@ -120,6 +122,31 @@ const EDGE_TTS_VOICES = {
'zh-TW': ['zh-TW-HsiaoChenNeural', 'zh-TW-HsiaoYuNeural', 'zh-TW-YunJheNeural'],
};
/**
* Generates the Sec-MS-GEC token value.
* This function generates a token value based on the current time in Windows file time format
* adjusted for clock skew, and rounded down to the nearest 5 minutes. The token is then hashed
* using SHA256 and returned as an uppercased hex digest.
*
* @returns The generated Sec-MS-GEC token value.
* @see https://github.com/rany2/edge-tts/issues/290#issuecomment-2464956570
*/
const WIN_EPOCH_OFFSET = 11644473600; // Windows epoch offset in seconds (1601 to 1970)
const S_TO_NS = 1000000000; // Seconds to nanoseconds conversion
const generateSecMsGec = () => {
let ticks = Math.floor(Date.now() / 1000);
// Switch to Windows file time epoch (1601-01-01 00:00:00 UTC)
ticks += WIN_EPOCH_OFFSET;
// Round down to the nearest 5 minutes (300 seconds)
ticks -= ticks % 300;
// Convert the ticks to 100-nanosecond intervals (Windows file time format)
ticks *= S_TO_NS / 100;
// Create the string to hash by concatenating the ticks and the trusted client token
const strToHash = `${ticks.toFixed(0)}${EDGE_API_TOKEN}`;
// Compute the SHA256 hash and return the uppercased hex digest
return createHash('sha256').update(strToHash, 'ascii').digest('hex').toUpperCase();
};
const genVoiceList = (voices: Record<string, string[]>) => {
return Object.entries(voices).flatMap(([lang, voices]) => {
return voices.map((id) => {
@@ -150,7 +177,13 @@ export class EdgeSpeechTTS {
async #fetchEdgeSpeechWs({ lang, text, voice, rate }: EdgeTTSPayload): Promise<Response> {
const connectId = randomMd5();
const url = `${EDGE_SPEECH_URL}?ConnectionId=${connectId}&TrustedClientToken=${EDGE_API_TOKEN}`;
const params = new URLSearchParams({
ConnectionId: connectId,
TrustedClientToken: EDGE_API_TOKEN,
'Sec-MS-GEC': generateSecMsGec(),
'Sec-MS-GEC-Version': `1-${CHROMIUM_FULL_VERSION}`,
});
const url = `${EDGE_SPEECH_URL}?${params.toString()}`;
const date = new Date().toString();
const configHeaders = {
'Content-Type': 'application/json; charset=utf-8',