|
|
ÓïÒôºÏ³ÉºËÐÄÄ£¿éAIWROK µ¥Îļþ°æ
- /**
- * ============================================================================
- * AIWROK TTS CORE - ÓïÒôºÏ³ÉºËÐÄÄ£¿é£¨AIWROK µ¥Îļþ°æ£©
- * ============================================================================
- */
- // ==================== µ¼Èë Java ÀࣨAIWROK ÄÚÖã© ====================
- importClass(Packages.java.lang.Thread);
- importClass(Packages.java.io.File);
- importClass(Packages.java.io.FileOutputStream);
- importClass(Packages.java.io.BufferedOutputStream);
- importClass(Packages.java.security.MessageDigest);
- // ×¢Ò⣺Array ºÍ Byte ²»×ö importClass£¬»áºÍ JS È«¾Ö¶ÔÏó³åÍ»
- // ´úÂëÀïÖ±½ÓÓÃÍêÕûÀàÃû£ºjava.lang.reflect.Array / java.lang.Byte
- importClass(Packages.okhttp3.OkHttpClient);
- importClass(Packages.okhttp3.Request);
- importClass(Packages.okhttp3.RequestBody);
- importClass(Packages.okhttp3.WebSocketListener);
- importClass(Packages.okio.ByteString);
- importClass(Packages.okio.Buffer);
- importClass(Packages.java.util.concurrent.TimeUnit);
- // ==================== ÒôÉ«Áбí ====================
- var EDGE_VOICES = [
- { id: "zh-CN-XiaoxiaoNeural", name: "ÏþÏþ (ÌðÃÀÅ®Éù)" },
- { id: "zh-CN-XiaoyiNeural", name: "ÏþÒÁ (ÈíÃÈÉÙÅ®)" },
- { id: "zh-CN-XiaohanNeural", name: "Ïþº (ÖªÐÔÓù½ã)" },
- { id: "zh-CN-XiaomengNeural", name: "ÏþÃÎ (ÎÂÈáÖÎÓú)" },
- { id: "zh-CN-XiaomoNeural", name: "ÏþÄ« (³ÉÊìÎÈÖØ)" },
- { id: "zh-CN-XiaoxuanNeural", name: "ÏþÝæ (»îÆÃ¿É°®)" },
- { id: "zh-CN-XiaoyanNeural", name: "ÏþÑÕ (ÐÂÎÅÅ®Ö÷²¥)" },
- { id: "zh-CN-XiaochenNeural", name: "Ïþ³½ (ÎÂůŮÉù)" },
- { id: "zh-CN-XiaoshuangNeural", name: "ÏþË« (¶ùͯŮÉù)" },
- { id: "zh-CN-XiaoruiNeural", name: "Ïþî£ (ÀÏÄêÅ®Éù)" },
- { id: "zh-CN-YunxiNeural", name: "ÔÆÏ£ (ÄêÇáÄÐÉù)" },
- { id: "zh-CN-YunjianNeural", name: "ÔÆ½¡ (³ÉÊìÄÐÉù)" },
- { id: "zh-CN-YunyangNeural", name: "ÔÆÑï (ÐÂÎŲ¥Òô)" },
- { id: "zh-CN-YunxiaNeural", name: "ÔÆÏÄ (ÉÙÄêÄÐÉù)" },
- { id: "zh-CN-YunyeNeural", name: "ÔÆÒ° (¹Êʽ²Êö)" },
- { id: "zh-CN-YunfengNeural", name: "ÔÆ·ã (ÀÏÄêÄÐÉù)" },
- { id: "zh-TW-HsiaoChenNeural", name: "•ÔÕé (̨ÍåÅ®Éù)" },
- { id: "zh-TW-YunJheNeural", name: "ë…ÕÜ (̨ÍåÄÐÉù)" },
- { id: "zh-HK-HiuMaanNeural", name: "•ÔÂü (ÔÁÓïÅ®Éù)" },
- { id: "zh-HK-WanLungNeural", name: "ë…ýˆ (ÔÁÓïÄÐÉù)" }
- ];
- var DOUBAO_VOICES = [
- { id: "zh_female_taozi_conversation_v4_wvae_bigtts", name: "ÌÒ×Ó (ÎÂÈáÅ®Éù)" },
- { id: "zh_male_wennuanahu_radar_v4_wvae_bigtts", name: "ÎÂů (ÄÐÉù)" },
- { id: "zh_female_qingxinahu_radar_v4_wvae_bigtts", name: "ÇåР(Å®Éù)" },
- { id: "zh_male_zhiyu_radar_v5_wvae_bigtts", name: "ÖÎÓú (ÄÐÉù)" },
- { id: "zh_female_wanwanxiaohe_moon_bigtts", name: "ÍäÍäСºØ (Ůͯ)" },
- { id: "zh_female_linjiaxiaoyu_moon_bigtts", name: "ÁÚ¼ÒСÓï (ÉÙÅ®)" }
- ];
- var DEFAULT_EDGE_VOICE = "zh-CN-XiaoxiaoNeural";
- var DEFAULT_DOUBAO_VOICE = "zh_female_taozi_conversation_v4_wvae_bigtts";
- // ==================== ͨÓù¤¾ßº¯Êý ====================
- /**
- * Éú³É UUID v4£¨Rhino ES5 ¼æÈÝ£©
- */
- function uuid() {
- var chars = '0123456789abcdef';
- var s = [];
- for (var i = 0; i < 36; i++) s[i] = chars[Math.floor(Math.random() * 16)];
- s[14] = '4';
- s[19] = chars[(parseInt(s[19], 16) & 0x3) | 0x8];
- s[8] = s[13] = s[18] = s[23] = '-';
- return s.join('');
- }
- /** XML תÒå */
- function escapeXml(str) {
- return String(str)
- .replace(/&/g, '&')
- .replace(/</g, '<')
- .replace(/>/g, '>')
- .replace(/"/g, '"')
- .replace(/'/g, ''');
- }
- /**
- * °Ñ JS µÄ ArrayBuffer / Uint8Array / number[] ת³É Java byte[]
- * Rhino Àï ByteString.of() ºÍ Java IO ¶¼ÐèÒªÔÉú byte[]
- */
- function toJavaBytes(data) {
- var view;
- if (data instanceof ArrayBuffer) {
- view = new Uint8Array(data);
- } else if (data instanceof Uint8Array) {
- view = data;
- } else if (data && typeof data.length === 'number') {
- view = new Uint8Array(data.length);
- for (var i2 = 0; i2 < data.length; i2++) view[i2] = data[i2];
- } else {
- return java.lang.reflect.Array.newInstance(java.lang.Byte.TYPE, 0);
- }
- var jBytes = java.lang.reflect.Array.newInstance(java.lang.Byte.TYPE, view.length);
- for (var k = 0; k < view.length; k++) {
- var v = view[k] & 0xff;
- jBytes[k] = (v > 127) ? (v - 256) : v;
- }
- return jBytes;
- }
- /**
- * °Ñ Uint8Array / number[] / ArrayBuffer дÈëÎļþ
- * @param {string} path
- * @param {Uint8Array|number[]|ArrayBuffer} data
- * @return {boolean}
- */
- function writeBinaryFile(path, data) {
- try {
- var jBytes = toJavaBytes(data);
- if (!jBytes || jBytes.length === 0) return false;
- // È·±£¸¸Ä¿Â¼´æÔÚ
- var f = new java.io.File(path);
- var parent = f.getParentFile();
- if (parent && !parent.exists()) parent.mkdirs();
- var fos = new java.io.FileOutputStream(f);
- var out = new java.io.BufferedOutputStream(fos);
- out.write(jBytes);
- out.flush();
- out.close();
- fos.close();
- return true;
- } catch (e) {
- print.log("[writeBinaryFile] ʧ°Ü: " + e);
- return false;
- }
- }
- /**
- * ¼òÒ× sleep£¨²»Å×Òì³££©
- */
- function sleep(ms) {
- try { java.lang.Thread.sleep(ms); } catch (e) {}
- }
- /**
- * ×Ö·û´®×ó²à²¹Æë£¨Rhino ²»Ö§³Ö padStart£©
- */
- function padStart(s, len, ch) {
- s = String(s);
- while (s.length < len) s = ch + s;
- return s;
- }
- // ==================== Protobuf ±à½âÂ루¶¹°üÓã© ====================
- function encodeVarint(v) {
- var out = [];
- v = v >>> 0;
- while (v > 0x7f) {
- out.push((v & 0x7f) | 0x80);
- v = v >>> 7;
- }
- out.push(v & 0x7f);
- return out;
- }
- function encodeBytesField(fieldNum, data) {
- var bytes;
- if (typeof data === 'string') {
- // UTF-8 ±àÂ루²»ÄÜÓà unescape£¬ÖÐÎÄ»áÂÒÂ룩
- var u8 = unescape(encodeURIComponent(data));
- bytes = [];
- for (var i = 0; i < u8.length; i++) bytes.push(u8.charCodeAt(i) & 0xff);
- } else if (data && data.length !== undefined) {
- bytes = [];
- for (var j = 0; j < data.length; j++) bytes.push(data[j] & 0xff);
- } else {
- bytes = [];
- }
- var tag = (fieldNum << 3) | 2;
- return encodeVarint(tag).concat(encodeVarint(bytes.length), bytes);
- }
- /**
- * ¹¹ÔìÍêÕûµÄ VoiceGenie ÇëÇóÖ¡£¬·µ»Ø ArrayBuffer
- */
- function buildRequestFrame(event, payload, sessionId) {
- var frame = [];
- frame = frame.concat(encodeBytesField(2, 'GOqQpfo1fO7slHv8'));
- frame = frame.concat(encodeBytesField(3, 'VoiceGenie'));
- frame = frame.concat(encodeBytesField(5, event));
- frame = frame.concat(encodeBytesField(6, payload));
- if (sessionId) {
- frame = frame.concat(encodeBytesField(8, sessionId));
- }
- // number[] ¡ú ArrayBuffer
- var ab = new ArrayBuffer(frame.length);
- var v = new Uint8Array(ab);
- for (var i = 0; i < frame.length; i++) v[i] = frame[i];
- return ab;
- }
- /**
- * ½âÎö·þÎñÆ÷·µ»ØµÄ protobuf Ö¡
- */
- function parseResponseFrame(buf) {
- var a = (buf instanceof Uint8Array) ? buf : new Uint8Array(buf);
- var fields = [];
- var i = 0;
- function readVarint() {
- var v = 0, s = 0;
- while (i < a.length) {
- var b = a[i++];
- v |= (b & 0x7f) << s;
- if (!(b & 0x80)) return v >>> 0;
- s += 7;
- if (s > 35) break;
- }
- return v >>> 0;
- }
- while (i < a.length) {
- var tag;
- try { tag = readVarint(); } catch (e) { break; }
- if (tag === 0) break;
- var fieldNum = tag >> 3;
- var wireType = tag & 7;
- var field = { fieldNum: fieldNum, wireType: wireType };
- if (wireType === 0) {
- field.value = readVarint();
- } else if (wireType === 2) {
- var len = readVarint();
- // ¼æÈÝ Rhino Uint8Array.subarray/slice
- var sliceArr = [];
- for (var kk = 0; kk < len; kk++) sliceArr.push(a[i + kk]);
- field.data = sliceArr; // number[]
- i += len;
- if (len <= 8000) {
- try {
- // ת Java byte[] ÔÙ UTF-8 ½âÂ루ÖÐÎݲȫ£©
- var jb = java.lang.reflect.Array.newInstance(java.lang.Byte.TYPE, len);
- for (var m = 0; m < len; m++) {
- var bv = a[i - len + m] & 0xff;
- jb[m] = (bv > 127) ? (bv - 256) : bv;
- }
- field.asString = String(new java.lang.String(jb, "UTF-8"));
- } catch (e) {}
- }
- } else if (wireType === 5) {
- i += 4;
- } else if (wireType === 1) {
- i += 8;
- } else {
- break;
- }
- fields.push(field);
- }
- var result = { eventType: null, payload: null, audioData: null, seq: -1, sessionId: null };
- for (var f = 0; f < fields.length; f++) {
- var fld = fields[f];
- if (fld.fieldNum === 1) result.sessionId = fld.asString || null;
- if (fld.fieldNum === 4) result.eventType = fld.asString || null;
- if (fld.fieldNum === 7) result.payload = fld.data || null;
- if (fld.fieldNum === 8) result.audioData = fld.data; // number[]
- if (fld.fieldNum === 9) result.seq = fld.value;
- }
- return result;
- }
- // ==================== ¶¹°ü Cookie / UID ¹¤¾ß ====================
- function cookieObjToString(cookies) {
- if (typeof cookies === 'string') return cookies;
- if (cookies && typeof cookies === 'object') {
- // ES5 ûÓÐ Object.entries
- var parts = [];
- for (var k in cookies) {
- if (cookies.hasOwnProperty(k)) {
- parts.push(k + '=' + cookies[k]);
- }
- }
- return parts.join('; ');
- }
- return '';
- }
- function getCookieFromStr(cookieStr, name) {
- if (!cookieStr) return null;
- var m = cookieStr.match(new RegExp('(?:^|;\\s*)' + name + '=([^;]+)'));
- return m ? m[1] : null;
- }
- function getUid(cookieStr) {
- var uidCookie = getCookieFromStr(cookieStr, 'uid_tt') || getCookieFromStr(cookieStr, 'uid');
- if (uidCookie) {
- try {
- // Óà Java BASE64 ½âÂ루Rhino ûÓÐ atob£©
- var decoded = String(java.util.Base64.getDecoder().decode(uidCookie));
- if (/^\d+$/.test(decoded)) return decoded;
- } catch (e) {}
- if (/^\d+$/.test(uidCookie)) return uidCookie;
- }
- return String(Date.now() % 1000000000000);
- }
- function buildDoubaoWsUrl(cookieStr) {
- var deviceId = getCookieFromStr(cookieStr, 'device_id') ||
- getCookieFromStr(cookieStr, 's_v_web_id') ||
- (String(Date.now()) + padStart(String(Math.floor(Math.random() * 1e6)), 6, '0'));
- var webId = getCookieFromStr(cookieStr, 'web_id') ||
- getCookieFromStr(cookieStr, 's_v_web_id') ||
- deviceId;
- var params = [
- 'api_app_key=GOqQpfo1fO7slHv8',
- 'namespace=VoiceGenie',
- 'version_code=20800',
- 'language=zh',
- 'device_platform=web',
- 'pkg_type=release_version',
- 'pc_version=3.30.5',
- 'region=CN',
- 'sys_region=CN',
- 'samantha_web=1',
- 'use-olympus-account=1',
- 'doubao_device_platform=web',
- 'aid=497858',
- 'real_aid=497858',
- 'device_id=' + deviceId,
- 'doubao_pc_version=3.30.5',
- 'web_id=' + webId,
- 'tea_uuid=' + webId,
- 'web_platform=browser',
- 'web_tab_id=' + uuid()
- ];
- return 'wss://frontier-audio-web-ws.doubao.com/api/v2/sami/voicegenie?' + params.join('&');
- }
- function buildDoubaoSessionPayload(opts) {
- var replyUniqueKey = uuid();
- var queryLocalMessageId = uuid();
- var uid = getUid(opts.cookieStr);
- var payload = {
- business: 1,
- conversation_id: opts.conversationId,
- chat: {
- bot_id: '7338286299411103781',
- conversation_id: opts.conversationId,
- question_id: opts.questionId,
- message_id: opts.messageId,
- uid: uid,
- new_conversation: false,
- chat_next: '1',
- enable_toast_reading: 'true',
- extra: { chat_next: '1', reply_unique_key: replyUniqueKey, query_local_message_id: queryLocalMessageId },
- reply_unique_key: replyUniqueKey,
- section_id: opts.sectionId,
- local_message_id: uuid(),
- query_local_message_id: queryLocalMessageId
- },
- request_type: 4,
- enable_text_reading: true,
- interrupt_type: 0,
- query_mode: 2,
- tts: {
- speaker: opts.speaker,
- audio_config: opts.audioConfig,
- extra: {
- chat_next: '1',
- query_local_message_id: queryLocalMessageId,
- post_process: { pitch: opts.pitch || 0, speech_rate: opts.speechRate || 1.0 }
- }
- },
- extra: {
- enable_toast_reading: 'true',
- chat_next: '1',
- uid: uid,
- section_id: opts.sectionId,
- reply_unique_key: replyUniqueKey,
- query_local_message_id: queryLocalMessageId,
- enable_text_simple_report: true,
- enable_section_report: true,
- enable_card_simple_report: true,
- enable_image_asr_ctx: true,
- disable_markdown_filter: true,
- enable_asr_audio_context: false,
- is_server_error_retry: false
- }
- };
- return JSON.stringify(payload);
- }
- // ==================== Edge TTS ºËÐÄ£¨1:1 ¶ÔÓ¦ Python rany2/edge-tts ¹Ù·½Ëã·¨£© ====================
- var EDGE_TTS_WS_URL = 'wss://speech.platform.bing.com/consumer/speech/synthesize/readaloud/edge/v1';
- var EDGE_TTS_TOKEN = '6A5AA1D4EAFF4E9FB37E23D68491D6F4';
- // ÍêÈ«¶ÔÆë¹Ù·½ constants.py CHROMIUM_FULL_VERSION
- var EDGE_CHROMIUM_FULL_VERSION = '143.0.3650.75';
- var EDGE_CHROMIUM_MAJOR_VERSION = '143';
- var EDGE_TTS_GEC_VERSION = '1-' + EDGE_CHROMIUM_FULL_VERSION;
- var EDGE_WIN_EPOCH_SECONDS = 11644473600;
- /**
- * È«¾Ö DRM ʱÖÓÆ«²î£¨µ¥Î»Ã룬ÕýÊý=·þÎñÆ÷±È±¾µØ¿ì£©
- * ¹Ù·½£ºDRM.clock_skew_seconds£¬403ʧ°Üʱ´Ó·þÎñÆ÷ Date ͷУ׼ºó×Ô¶¯ÖØÊÔ
- */
- var EDGE_CLOCK_SKEW_SECONDS = 0.0;
- /**
- * ½âÎö RFC 2616 GMT ÈÕÆÚ£¨RFC_1123_DATE_TIME ¸ñʽ£©
- * ÓÅÏÈʹÓà Java DateTimeFormatter£¬±ÜÃâ×Ô¼ºÕýÔò½âÎö³ö´í¡£
- *
- * @param {string|null|undefined} dateStr ÀýÈç "Sat, 08 Aug 2026 07:20:58 GMT"
- * @returns {number|null} UTC ÃëÊý£¨float£©£¬Ê§°Ü·µ»Ø null
- */
- function parseRfc2616Date(dateStr) {
- if (!dateStr) return null;
- var s = String(dateStr).trim();
- try {
- // === ·½°¸1£ºJava 8+ java.time£¨°²×¿ 7.0+ Ö§³Ö£¬AIWROK ÔËÐл·¾³Ò»°ã¶¼ÓУ© ===
- var formatter = java.time.format.DateTimeFormatter.RFC_1123_DATE_TIME;
- var temporal = formatter.parse(s);
- var zdt = java.time.ZonedDateTime.from(temporal);
- var epochSec = zdt.toEpochSecond(); // long
- // ×¢Ò⣺RFC 2616 ÈÕÆÚ²»ÌṩºÁÃ룬ֻµ½Ãë
- return parseFloat(String(epochSec));
- } catch (e1) {
- try {
- // === ·½°¸2£ºÀϰ²×¿ SimpleDateFormat ===
- var sdf = new java.text.SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss zzz", java.util.Locale.US);
- sdf.setTimeZone(java.util.TimeZone.getTimeZone("GMT"));
- var d = sdf.parse(s);
- return parseFloat(String(d.getTime())) / 1000.0;
- } catch (e2) {
- // === ·½°¸3£º×îºó¶µµ× Rhino JS ÔÉú Date.parse ===
- try {
- var fb = Date.parse(s);
- if (!isNaN(fb)) return fb / 1000.0;
- } catch (e3) {}
- return null;
- }
- }
- }
- /**
- * Éú³É MUID£¨32Î»Ëæ»ú´óдHEX£©
- * ¹Ù·½ DRM.generate_muid ¡ú headers_with_muid Èûµ½ Cookie Àï
- */
- function generateEdgeMuid() {
- var hex = '0123456789ABCDEF';
- var sb = '';
- for (var i = 0; i < 32; i++) {
- sb += hex.charAt(Math.floor(Math.random() * 16));
- }
- return sb;
- }
- /**
- * Éú³É Sec-MS-GEC Ç©Ãû£¨Ñϸñ¶ÔÆë¹Ù·½ drm.py DRM.generate_sec_ms_gec + clock_skew£©
- *
- * ¹Ù·½ Python Á÷³Ì£º
- * ticks = dt.now(tz.utc).timestamp() + DRM.clock_skew_seconds
- * ticks += WIN_EPOCH
- * ticks -= ticks % 300
- * ticks *= 1e7
- * str = f"{ticks:.0f}{TOKEN}"
- * return sha256(str.encode("ascii")).hexdigest().upper()
- */
- var _EDGE_GEC_LOG_ONCE = false; // Ö»´òÓ¡Ò»´Î±ÜÃâË¢ÆÁ
- function generateSecMsGec() {
- try {
- var unixSecFloat = (java.lang.System.currentTimeMillis() / 1000.0) + EDGE_CLOCK_SKEW_SECONDS;
- var ticks = unixSecFloat + EDGE_WIN_EPOCH_SECONDS;
- // ¶Ô¸ºÊýȡģ¶ÔÆë Python£ºPython % ÓÀÔ¶·Ç¸º£¬Rhino/JS ¿ÉÄÜ·µ»Ø¸ºÊý ¡ú ÊÖ¶¯¹éÒ»»¯
- var mod = ticks % 300;
- if (mod < 0) mod += 300;
- ticks = ticks - mod;
- ticks = ticks * 10000000.0;
- var ticksLong = java.lang.Math.round(ticks);
- var ticksStr = String(ticksLong);
- var src = ticksStr + EDGE_TTS_TOKEN;
- var md = MessageDigest.getInstance('SHA-256');
- var srcBytes = new java.lang.String(src).getBytes('US-ASCII');
- var hash = md.digest(srcBytes);
- var sb = new java.lang.StringBuilder(hash.length * 2);
- for (var i = 0; i < hash.length; i++) {
- var h = hash[i] & 0xff;
- // ============================================================
- // ÐÞ¸´ Rhino ƽ̨´ó¿Ó£º
- // java.lang.Integer.toHexString() ·µ»ØµÄÊÇ java.lang.String£¨²»ÊÇ JS String£¡£©
- // ¡ú java.lang.String µÄ .length ÊÇ METHOD/FUNCTION£¬²»ÊÇ JS ÊýÖµÊôÐÔ length
- // ¡ú ¾É´úÂ룺if (s.length < 2) ¡ú NaN < 2 ¡ú ÓÀÔ¶ FALSE£¡
- // ¡ú ½á¹û£ºÖ»Òª byte < 0x10£¨¸ßλΪ 0£©£¬²¹Áã¾Í±»Ìø¹ý£¡
- // ¡ú ÿÉÙÒ»¸öÁã¾Í´íλ 4 bit£¬µ¼ÖÂÕû¸ö GEC ºÍ¹Ù·½²îºÃ¼¸¸ö×Ö·û ¡ú ΢Èí 403
- // ============================================================
- var hexJava = java.lang.Integer.toHexString(h);
- // ¢Ù ǿתΪ JS ÔÉú String£¨´Ëʱ .length ¾ÍÊÇÊýÖµÊôÐÔ£©
- var hex = String(hexJava);
- // ¢Ú Ò²¿ÉÒÔÓà Java ·½·¨ .length() Ë«ÖØ±£ÏÕ£¨ÈÎÒ»·½Ê½¶¼¿ÉÒÔ£©
- if (hex.length < 2) sb.append('0');
- sb.append(hex);
- }
- var gec = String(sb.toString()).toUpperCase();
- // ===== DEBUG£ºÃ¿´ÎÉú³É GEC ʱ£¬Ö»ÔÚµÚÒ»´Î / 403 ǰºó´òÓ¡¹Ø¼ü²ÎÊý =====
- // Óû§¿É¸´ÖÆÏÂÃæÕâÐе½ Python gec_verify.py ×ö 1:1 ¶Ôײ
- if (!_EDGE_GEC_LOG_ONCE || EDGE_CLOCK_SKEW_SECONDS !== 0.0) {
- var dbgLine = '[Edge TTS][GEC] ticks_input=' + java.lang.String.format("%.6f", unixSecFloat)
- + ' skew=' + EDGE_CLOCK_SKEW_SECONDS.toFixed(4)
- + ' mod300=' + mod.toFixed(6)
- + ' ticksLong=' + ticksLong
- + ' ticksStr_len=' + ticksStr.length
- + ' src_preview=' + ticksStr.substring(0, Math.min(12, ticksStr.length)) + '...'
- + ' gec=' + gec;
- print.log(dbgLine);
- _EDGE_GEC_LOG_ONCE = true;
- }
- return gec;
- } catch (e) {
- print.log('[Edge TTS] Éú³É Sec-MS-GEC ʧ°Ü: ' + e);
- return null;
- }
- }
- function buildSSML(text, voice, rate, volume, pitch) {
- return "<speak version='1.0' xmlns='http://www.w3.org/2001/10/synthesis' " +
- "xmlns:mstts='https://www.w3.org/2001/mstts' xml:lang='zh-CN'>" +
- "<voice name='" + voice + "'>" +
- "<prosody rate='" + rate + "' volume='" + volume + "' pitch='" + pitch + "'>" +
- escapeXml(text) +
- "</prosody></voice></speak>";
- }
- /**
- * Edge TTS µ¥´ÎÎÕÊÖ³¢ÊÔ£¨ÄÚ²¿º¯Êý£¬¹© generateEdge ×ö×Ô¶¯ÖØÊÔ£©
- * @returns {Object} { ok, code, message, serverDate, output }
- */
- function tryEdgeOnce(sessionOpts) {
- var text = sessionOpts.text;
- var voice = sessionOpts.voice;
- var rate = sessionOpts.rate;
- var volume = sessionOpts.volume;
- var pitch = sessionOpts.pitch;
- var output = sessionOpts.output;
- var requestId = uuid().replace(/-/g, '');
- var gec = generateSecMsGec();
- var wsUrl = EDGE_TTS_WS_URL
- + '?TrustedClientToken=' + EDGE_TTS_TOKEN
- + '&ConnectionId=' + requestId;
- if (gec) {
- wsUrl += '&Sec-MS-GEC=' + gec
- + '&Sec-MS-GEC-Version=' + EDGE_TTS_GEC_VERSION;
- }
- var state = {
- connected: false,
- closed: false,
- err: null,
- httpCode: 0,
- serverDate: null,
- audioChunks: [],
- totalLen: 0,
- _loggedFirstFrame: false
- };
- var listener = new WebSocketListener({
- onOpen: function (ws, response) {
- state.connected = true;
- var code = 0;
- try { code = response ? response.code() : 0; } catch(e){}
- print.log('[Edge TTS][WS] ✅ onOpen ÒÑ´¥·¢£¬HTTP code=' + code + ' (ÆÚÍû 101 Switching Protocols)');
- try {
- var xts = (function isoZ() {
- var d = new Date();
- var pad = function (n, w) { n = String(n); while (n.length < w) n = '0' + n; return n; };
- return d.getUTCFullYear() + '-' + pad(d.getUTCMonth() + 1, 2) + '-' + pad(d.getUTCDate(), 2)
- + 'T' + pad(d.getUTCHours(), 2) + ':' + pad(d.getUTCMinutes(), 2) + ':' + pad(d.getUTCSeconds(), 2) + '.000Z';
- })();
- var configMsg = JSON.stringify({
- context: {
- synthesis: {
- audio: {
- metadataoptions: { sentenceBoundaryEnabled: false, wordBoundaryEnabled: false },
- outputFormat: 'audio-24khz-48kbitrate-mono-mp3'
- }
- }
- }
- });
- ws.send('X-Timestamp:' + xts + '\r\nContent-Type:application/json; charset=utf-8\r\nPath:speech.config\r\n\r\n' + configMsg);
- var ssml = buildSSML(text, voice, rate, volume, pitch);
- ws.send('X-RequestId:' + requestId + '\r\nContent-Type:application/ssml+xml\r\nX-Timestamp:' + xts + '\r\nPath:ssml\r\n\r\n' + ssml);
- } catch (e) {
- state.err = '·¢ËÍʧ°Ü: ' + e;
- print.log('[Edge TTS][WS] onOpen ÄÚ²¿·¢ËÍʧ°Ü: ' + e);
- }
- },
- onMessage: function (ws, message) {
- try {
- // ========== 1. ¼æÈÝ Rhino/OkHttp ¶àÖÖÏûÏ¢ÀàÐÍ ==========
- if (message === null || message === undefined) return;
- // ----------- Îı¾ÏûÏ¢£º¾²Ä¬ºöÂÔ£¨TEXT Ö¡ÊÇ turn.start / response / audio.metadata / turn.end µÈ¿ØÖÆÏûÏ¢£© -----------
- var isJavaString = false;
- try {
- if (typeof message === 'string') {
- isJavaString = true;
- } else if (message && message.getClass) {
- var cls = message.getClass();
- if (cls === java.lang.String.class) isJavaString = true;
- // Ôٴζµµ×£ºÈç¹ûÊÇ okhttp ´«¹ýÀ´µÄ String£¨±ÈÈçijЩ°æ±¾»áÓÃ
- // java.lang.CharSequence impl£©£¬µ÷Óà toString() Ö®ºóÔÙ¼ì²é
- // ÊÇ·ñÈ«ÊǿɴòÓ¡×Ö·ûÒ²¿ÉÒÔ ¡ú Óà instanceof ¸üÎÈ
- else if (java.lang.String.class.isInstance(message)) isJavaString = true;
- }
- } catch (e_ignore) {}
- if (isJavaString) return;
- // ¶þ½øÖÆÏûÏ¢£º¿ÉÄÜÊÇ okhttp3 ByteString (ÓÐ toByteArray())£¬¿ÉÄÜÊÇÔÉú byte[]£¬¿ÉÄÜÊÇ Buffer/ByteString ÆäËü
- var jBytes = null;
- try {
- if (typeof message.toByteArray === 'function') {
- jBytes = message.toByteArray();
- } else if (typeof message.length === 'number') {
- // ¿ÉÄÜÊÇ JS ArrayBuffer / Uint8Array£¿ AIWROK Rhino Àï byte[] Ò²ÓÐ length ÊôÐÔ
- var maybeLen = message.length;
- if (maybeLen === 0 || (maybeLen > 0 && typeof message[0] === 'number')) {
- // JS Uint8Array ·ç¸ñ£¬×ª Java byte[]
- jBytes = java.lang.reflect.Array.newInstance(java.lang.Byte.TYPE, maybeLen);
- for (var _k = 0; _k < maybeLen; _k++) {
- var _bv = message[_k] & 0xff;
- jBytes[_k] = (_bv > 127) ? (_bv - 256) : _bv;
- }
- } else if (maybeLen > 0 && message.getClass && message.getClass().isArray()) {
- // ÔÉú Java byte[]
- jBytes = message;
- }
- }
- } catch (convErr) {
- // ת»»Ê§°Üͨ³£ÊÇÎı¾¿ØÖÆÖ¡£¬¾²Ä¬ºöÂÔ¼´¿É
- return;
- }
- if (!jBytes) {
- // ·Ç¶þ½øÖÆÏûÏ¢£¨Îı¾/ÆäËû¿ØÖÆÖ¡£©£¬¾²Ä¬ºöÂÔ
- return;
- }
- var len = jBytes.length;
- if (len < 2) {
- print.log('[Edge TTS][bin] frame too small, len=' + len);
- return;
- }
- // ========== 2. °´¹Ù·½¹«Ê½£ºheader_length = ǰ 2 bytes big-endian ==========
- var headerLength = ((jBytes[0] & 0xff) << 8) | (jBytes[1] & 0xff);
- // ¹Ù·½ get_headers_and_data: data[header_length+2:] £¨ÕâÀïµÄ data ¾ÍÊÇÕû¸ö jBytes£©
- // ËùÒÔ header_length ±ØÐë <= len - 2£¨ºóÃæÖÁÉÙÁô 2 ×Ö½Ú¸ø start index£©
- if (headerLength < 0 || (headerLength + 2) > len) {
- print.log('[Edge TTS][bin] invalid: headerLength=' + headerLength + ' totalLen=' + len);
- return;
- }
- var audioStart = headerLength + 2; // ¹Ù·½ 1:1
- var audioLen = len - audioStart; // ¹Ù·½ 1:1
- // ========== 3. ½âÎö header£¨¹Ù·½£ºline.split(b":", 1)£¬²» strip value£© ==========
- var pathVal = null;
- var contentTypeVal = null;
- try {
- // jBytes[2 : 2 + headerLength] = header bytes
- var headerArr = java.lang.reflect.Array.newInstance(java.lang.Byte.TYPE, headerLength);
- for (var _hi = 0; _hi < headerLength; _hi++) headerArr[_hi] = jBytes[2 + _hi];
- var headerStr = String(new java.lang.String(headerArr, 'US-ASCII'));
- var lines = headerStr.split('\r\n');
- for (var _li = 0; _li < lines.length; _li++) {
- var ln = lines[_li];
- if (!ln) continue;
- var colon = ln.indexOf(':');
- if (colon === -1) continue;
- var rawK = ln.substring(0, colon);
- var rawV = ln.substring(colon + 1);
- // Rhino String: Õý³£ key£¬Ò²¿ÉÄÜ´ø \x00\x80 ǰ׺£¨Èç×¥°üÖеÚÒ»¸ö key = \x00\x80X-RequestId£©
- // ËùÒÔ trim() key Êǰ²È«µÄ£¨È¥µô\x00ÕâÀà¿ØÖÆ·û»á¸üºÃ£©£¬È»ºó toLowerCase
- var k = rawK.replace(/[\x00-\x1f\x7f]/g, '').trim().toLowerCase();
- var v = rawV.replace(/[\x00-\x1f\x7f]/g, '').trim().toLowerCase();
- if (k === 'path') pathVal = v;
- if (k === 'content-type') contentTypeVal = v;
- }
- } catch (parseErr) {
- print.log('[Edge TTS][bin] header parse error: ' + parseErr);
- pathVal = null; contentTypeVal = null;
- }
- // ÈÕÖ¾£¨²»´òÿ֡£¬Ö»ÔÚÊ×´Î audio ³É¹¦ºÍ×ܼƴò£¬±ÜÃâÈÕÖ¾±¬Õ¨£©
- if (!state._loggedFirstFrame) {
- print.log('[Edge TTS][bin] first frame: total=' + len + ' headerLen=' + headerLength + ' audioLen=' + audioLen + ' Path=' + (pathVal||'<NULL>') + ' Content-Type=' + (contentTypeVal||'<NULL>'));
- }
- // ========== 4. °´¹Ù·½ÅжϣºPath == 'audio' ÇÒ (Content-Type == 'audio/mpeg' »ò NULL) ==========
- if (pathVal !== 'audio') {
- return; // ·ÇÒôƵ֡£¨ÀýÈç WebRTC ÐÄÌøµÈ¶þ½øÖÆÖ¡£©
- }
- if (contentTypeVal !== 'audio/mpeg') {
- // Content-Type Ϊ NULL ÇÒ data Ϊ¿Õ = ÖÕÖ¹Ö¡£¬Ö±½ÓÌø¹ý
- if (contentTypeVal === null && audioLen === 0) return;
- // ÓÐ data µ«Ã»ÓÐ Content-Type = UnexpectedResponse£¨¹Ù·½Å×Òì³££©£¬ÕâÀïÖ±½Ó¼ÇÈÕÖ¾Ìø¹ý
- if (contentTypeVal === null) {
- print.log('[Edge TTS][bin] WARN: Path=audio, no Content-Type but data exists, audioLen=' + audioLen);
- return;
- }
- return;
- }
- if (audioLen <= 0) return;
- // ========== 5. ÊÕ¼¯ÒôƵ ==========
- var chunk = new Array(audioLen);
- for (var kk2 = 0; kk2 < audioLen; kk2++) chunk[kk2] = jBytes[audioStart + kk2] & 0xff;
- state.audioChunks.push(chunk);
- state.totalLen += audioLen;
- if (!state._loggedFirstFrame) {
- state._loggedFirstFrame = true;
- var first2 = chunk[0].toString(16) + (chunk[1] < 16 ? '0' : '') + chunk[1].toString(16);
- print.log('[Edge TTS][bin] first audio bytes: 0x' + first2.toUpperCase() + ' (expect MP3: FFF3/FFF2)');
- }
- } catch (e) {
- try { print.log('[Edge TTS] onMessage uncaught error: ' + e); } catch(ee){}
- }
- },
- onClosed: function (ws, code, reason) {
- state.closed = true;
- state.connected = false;
- try { print.log('[Edge TTS][WS] onClosed ´¥·¢ code=' + code + ' reason=' + reason); } catch(e){}
- },
- onFailure: function (ws, t, response) {
- state.closed = true;
- state.connected = false;
- state.err = t ? String(t.getMessage()) : 'ws_error';
- try {
- if (response) {
- try {
- state.httpCode = response.code() || 0;
- } catch (ee) {}
- try {
- var hd = response.header('Date');
- if (hd) state.serverDate = String(hd);
- } catch (ee) {}
- }
- } catch (e) {}
- try {
- print.log('[Edge TTS][WS] ❌ onFailure: err_msg=' + (state.err || '')
- + ' HTTP_code=' + state.httpCode
- + ' Date=' + (state.serverDate || '<none>')
- + ' Throwable_type=' + (t ? String(t.getClass().getName()) : 'null'));
- } catch(e2) {}
- }
- });
- var client = new OkHttpClient.Builder()
- .pingInterval(10, TimeUnit.SECONDS)
- .connectTimeout(25, TimeUnit.SECONDS)
- .readTimeout(40, TimeUnit.SECONDS)
- .build();
- var muid = generateEdgeMuid();
- var edgeUA = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
- + ' (KHTML, like Gecko) Chrome/' + EDGE_CHROMIUM_MAJOR_VERSION + '.0.0.0 Safari/537.36'
- + ' Edg/' + EDGE_CHROMIUM_MAJOR_VERSION + '.0.0.0';
- var request = new Request.Builder()
- .url(wsUrl)
- .addHeader('User-Agent', edgeUA)
- .addHeader('Accept-Encoding', 'gzip, deflate, br, zstd')
- .addHeader('Accept-Language', 'en-US,en;q=0.9')
- .addHeader('Pragma', 'no-cache')
- .addHeader('Cache-Control', 'no-cache')
- .addHeader('Origin', 'chrome-extension://jdiccldimpdaibmpdkjnbmckianbfold')
- .addHeader('Sec-WebSocket-Version', '13')
- .addHeader('Cookie', 'muid=' + muid + ';')
- .build();
- var ws = client.newWebSocket(request, listener);
- print.log('[Edge TTS][WS] newWebSocket ÒÑ·¢Æð£¬µÈ´ý onOpen / onFailure / 25s timeout...');
- var w = 0;
- while (!state.connected && !state.err && w < 25000) {
- sleep(100);
- w += 100;
- }
- if (state.err) {
- try { ws.close(1000, 'err'); } catch (e) {}
- try { client.dispatcher().executorService().shutdown(); } catch (e) {}
- var code = 'error';
- if (state.httpCode === 403) code = '403';
- return { ok: false, code: code, message: state.err, serverDate: state.serverDate };
- }
- if (!state.connected) {
- try { ws.close(1000, 'timeout'); } catch (e) {}
- try { client.dispatcher().executorService().shutdown(); } catch (e) {}
- print.log('[Edge TTS][WS] ⏱️ onOpen µÈ´ý³¬Ê±£¨25Ã룩δ´¥·¢£¬Òѵȴý' + (w/1000).toFixed(1) + 'Ã룬err=' + (state.err || '<none>'));
- return { ok: false, code: 'timeout', message: 'WebSocket Á¬½Ó³¬Ê±£¨25Ã룩', serverDate: state.serverDate };
- }
- w = 0;
- var lastLen = 0;
- var noGrowthMs = 0;
- while (!state.closed && !state.err && w < 45000) {
- sleep(200);
- w += 200;
- if (state.totalLen !== lastLen) {
- lastLen = state.totalLen;
- noGrowthMs = 0;
- } else {
- noGrowthMs += 200;
- if (state.totalLen > 100 && noGrowthMs > 2000) break;
- }
- }
- print.log('[Edge TTS][WS] ½ÓÊÕÑ»·½áÊø£ºµÈ´ý=' + (w/1000).toFixed(1) + 'Ãë audioBytes_total=' + state.totalLen + ' closed=' + state.closed + ' err=' + (state.err||'<none>'));
- try { ws.close(1000, 'done'); } catch (e) {}
- try { client.dispatcher().executorService().shutdown(); } catch (e) {}
- if (state.err) {
- var code2 = 'error';
- if (state.httpCode === 403) code2 = '403';
- return { ok: false, code: code2, message: state.err, serverDate: state.serverDate };
- }
- if (state.totalLen < 100) {
- return { ok: false, code: 'noaudio', message: 'δ»ñÈ¡µ½ÒôƵÊý¾Ý£¬Çë¼ì²éÍøÂç»ò²ÎÊý', serverDate: state.serverDate };
- }
- var merged = new Uint8Array(state.totalLen);
- var offset = 0;
- for (var ci = 0; ci < state.audioChunks.length; ci++) {
- var chunk = state.audioChunks[ci];
- for (var cj = 0; cj < chunk.length; cj++) merged[offset + cj] = chunk[cj];
- offset += chunk.length;
- }
- if (output) {
- if (!writeBinaryFile(output, merged)) {
- return { ok: false, code: 'write', message: 'дÈëÎļþʧ°Ü: ' + output, serverDate: state.serverDate };
- }
- print.log('[Edge TTS] Òѱ£´æ: ' + output + ' (' + state.totalLen + ' ×Ö½Ú)');
- return { ok: true, output: output, serverDate: state.serverDate };
- }
- return { ok: true, output: merged, serverDate: state.serverDate };
- }
- /**
- * Edge TTS ºËÐÄÉú³É£¨Í¬²½×èÈû£¬¹Ø¼ü×Ö½Ó¿Ú£©¡ª¡ª ´ø¹Ù·½ DRM ʱÖÓÆ«ÒÆ×Ô¶¯Ð£×¼+ÖØÊÔ
- *
- * @param {Object} options
- * @param {string} options.text - ÒªºÏ³ÉµÄÎÄ×Ö£¨±ØÌ
- * @param {string} [options.voice] - ÒôÉ«ID£¬Ä¬ÈÏ zh-CN-XiaoxiaoNeural
- * @param {string} [options.rate] - ÓïËÙ "+0%" / "-20%" / "+50%"
- * @param {string} [options.volume] - ÒôÁ¿ "+0%" / "+50%"
- * @param {string} [options.pitch] - Òôµ÷ "+0Hz" / "-10Hz"
- * @param {string} [options.output] - Êä³ö·¾¶(.mp3)£¬²»Ìî·µ»Ø Uint8Array
- * @returns {string|Uint8Array|null} ³É¹¦·µ»ØÂ·¾¶»ò Uint8Array£¬Ê§°Ü·µ»Ø null
- */
- function generateEdge(options) {
- var opts = options || {};
- if (!opts.text || !String(opts.text).trim()) {
- print.log('[Edge TTS] ȱÉÙ±ØÌî²ÎÊý: text');
- return null;
- }
- var sessionOpts = {
- text: opts.text,
- voice: opts.voice || DEFAULT_EDGE_VOICE,
- rate: opts.rate || '+0%',
- volume: opts.volume || '+0%',
- pitch: opts.pitch || '+0Hz',
- output: opts.output || null
- };
- // µÚ 1 ´Î³¢ÊÔ
- var r = tryEdgeOnce(sessionOpts);
- if (r.ok) return r.output;
- // ¹Ù·½ DRM.handle_client_response_error£º
- // Èôʧ°ÜÇÒ´ø Date Í· ¡ú ¼ÆËã skew = serverDate - localNow ¡ú ÐÞÕý skew ¡ú ×Ô¶¯ÖØÊÔ
- if (r.code === '403' && r.serverDate) {
- print.log('[Edge TTS][403] ÊÕµ½·þÎñÆ÷ Date Í·: ' + String(r.serverDate));
- var serverTs = parseRfc2616Date(r.serverDate);
- var localTsNow = java.lang.System.currentTimeMillis() / 1000.0;
- if (serverTs !== null) {
- var skew = serverTs - localTsNow;
- print.log('[Edge TTS][403] serverTs_parsed=' + serverTs.toFixed(3)
- + ' localNow=' + localTsNow.toFixed(3)
- + ' skew=' + skew.toFixed(3));
- var oldSkew = EDGE_CLOCK_SKEW_SECONDS;
- EDGE_CLOCK_SKEW_SECONDS += skew;
- print.log('[Edge TTS] У׼ʱÖÓÆ«²î: old_skew=' + oldSkew.toFixed(3)
- + ' +delta=' + skew.toFixed(3)
- + ' ¡ú new_skew=' + EDGE_CLOCK_SKEW_SECONDS.toFixed(3) + ' Ã루403 ºó×Ô¶¯ÖØÊÔ£©');
- var r2 = tryEdgeOnce(sessionOpts);
- if (r2.ok) return r2.output;
- print.log('[Edge TTS] ÖØÊÔºóÈÔʧ°Ü: code=' + r2.code + ' msg=' + r2.message
- + (r2.serverDate ? ' serverDate=' + r2.serverDate : ''));
- return null;
- } else {
- print.log('[Edge TTS][403] ÈÕÆÚ½âÎöʧ°Ü£¡ÎÞ·¨¼ÆËãʱÖÓÆ«²î£¬·ÅÆúÖØÊÔ');
- }
- }
- // ÎÞ Date Í·»ò·Ç 403 ´íÎó£¬Ö±½Ó´òÓ¡
- print.log('[Edge TTS] ʧ°Ü(' + r.code + '): ' + r.message);
- return null;
- }
- // ==================== ¶¹°ü VoiceGenie TTS ºËÐÄ ====================
- var DOUBAO_AUDIO_CONFIG = { bit_rate: 32000, format: 'ogg_opus', sample_rate: 24000 };
- /**
- * ¶¹°ü VoiceGenie TTS ºËÐÄÉú³É£¨Í¬²½×èÈû£¬¹Ø¼ü×Ö½Ó¿Ú£©
- */
- function generateDoubao(options) {
- var opts = options || {};
- var cookies = opts.cookies;
- var conversationId = opts.conversationId;
- var messageId = opts.messageId;
- var questionId = opts.questionId || null;
- var sectionId = opts.sectionId || '0';
- var speaker = opts.speaker || DEFAULT_DOUBAO_VOICE;
- var speechRate = opts.speechRate || 1.0;
- var pitch = opts.pitch || 0;
- var audioConfig = opts.audioConfig || DOUBAO_AUDIO_CONFIG;
- var output = opts.output || null;
- if (!cookies) { print.log('[¶¹°ü TTS] ȱÉÙ±ØÌî²ÎÊý: cookies'); return null; }
- if (!conversationId) { print.log('[¶¹°ü TTS] ȱÉÙ±ØÌî²ÎÊý: conversationId'); return null; }
- if (!messageId) { print.log('[¶¹°ü TTS] ȱÉÙ±ØÌî²ÎÊý: messageId'); return null; }
- var cookieStr = cookieObjToString(cookies);
- var qId = questionId || messageId;
- var sessionOpts = {
- conversationId: conversationId, questionId: qId, messageId: messageId,
- sectionId: sectionId, speaker: speaker, speechRate: speechRate,
- pitch: pitch, audioConfig: audioConfig, cookieStr: cookieStr
- };
- var state = {
- connected: false,
- closed: false,
- sessionId: null,
- gotTTSEnded: false,
- audioChunks: [],
- totalLen: 0,
- err: null
- };
- var wsUrl = buildDoubaoWsUrl(cookieStr);
- var listener = new WebSocketListener({
- onOpen: function (ws, response) {
- state.connected = true;
- try {
- ws.send(ByteString.of(toJavaBytes(buildRequestFrame('StartTask', '{}', null))));
- ws.send(ByteString.of(toJavaBytes(buildRequestFrame('Ping', '{}', null))));
- } catch (e) {
- state.err = '·¢ËÍ StartTask ʧ°Ü: ' + e;
- }
- },
- onMessage: function (ws, message) {
- try {
- if (typeof message === 'string') return;
- if (!message || !message.toByteArray) return;
- var jBytes = message.toByteArray();
- var u8 = new Uint8Array(jBytes.length);
- for (var i = 0; i < jBytes.length; i++) u8[i] = jBytes[i] & 0xff;
- var parsed = parseResponseFrame(u8);
- var ev = parsed.eventType;
- if (ev === 'TaskStarted') {
- var payload1 = buildDoubaoSessionPayload(sessionOpts);
- ws.send(ByteString.of(toJavaBytes(buildRequestFrame('StartSession', payload1, null))));
- } else if (ev === 'SessionStarted') {
- state.sessionId = uuid();
- ws.send(ByteString.of(toJavaBytes(buildRequestFrame('Ping', '{}', state.sessionId))));
- var payload2 = buildDoubaoSessionPayload(sessionOpts);
- ws.send(ByteString.of(toJavaBytes(buildRequestFrame('StartTTS', payload2, state.sessionId))));
- } else if (ev === 'TTSResponse') {
- if (parsed.audioData && parsed.audioData.length > 0) {
- state.audioChunks.push({ seq: parsed.seq, data: parsed.audioData });
- state.totalLen += parsed.audioData.length;
- }
- } else if (ev === 'TTSEnded') {
- state.gotTTSEnded = true;
- try { ws.close(1000, 'done'); } catch (e) {}
- }
- } catch (e) {}
- },
- onClosed: function (ws, code, reason) {
- state.closed = true;
- state.connected = false;
- },
- onFailure: function (ws, t, response) {
- state.closed = true;
- state.connected = false;
- state.err = t ? String(t.getMessage()) : 'ws_error';
- }
- });
- var client = new OkHttpClient.Builder()
- .pingInterval(10, TimeUnit.SECONDS)
- .connectTimeout(15, TimeUnit.SECONDS)
- .readTimeout(60, TimeUnit.SECONDS)
- .build();
- var request = new Request.Builder()
- .url(wsUrl)
- .addHeader('Cookie', cookieStr)
- .addHeader('User-Agent', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36')
- .addHeader('Origin', 'https://www.doubao.com')
- .build();
- var ws = client.newWebSocket(request, listener);
- var w = 0;
- while (!state.connected && !state.err && w < 15000) {
- sleep(100);
- w += 100;
- }
- if (state.err) {
- print.log('[¶¹°ü TTS] Á¬½Óʧ°Ü: ' + state.err);
- try { ws.close(1000, 'err'); } catch (e) {}
- try { client.dispatcher().executorService().shutdown(); } catch (e) {}
- return null;
- }
- if (!state.connected) {
- print.log('[¶¹°ü TTS] WebSocket Á¬½Ó³¬Ê± (15Ãë)');
- try { ws.close(1000, 'timeout'); } catch (e) {}
- try { client.dispatcher().executorService().shutdown(); } catch (e) {}
- return null;
- }
- w = 0;
- var lastLen = 0;
- var noGrowthMs = 0;
- while (!state.closed && !state.gotTTSEnded && !state.err && w < 60000) {
- sleep(200);
- w += 200;
- if (state.totalLen !== lastLen) {
- lastLen = state.totalLen;
- noGrowthMs = 0;
- } else {
- noGrowthMs += 200;
- if (state.totalLen > 0 && noGrowthMs > 3000) break;
- }
- }
- try { ws.close(1000, 'done'); } catch (e) {}
- try { client.dispatcher().executorService().shutdown(); } catch (e) {}
- if (state.err) {
- print.log('[¶¹°ü TTS] ' + state.err);
- return null;
- }
- if (state.audioChunks.length === 0) {
- print.log('[¶¹°ü TTS] δÊÕµ½ÈκÎÒôƵÊý¾Ý£¬Çë¼ì²é cookies/¶Ô»°²ÎÊý');
- return null;
- }
- state.audioChunks.sort(function (a, b) { return a.seq - b.seq; });
- var merged = new Uint8Array(state.totalLen);
- var offset = 0;
- for (var ci = 0; ci < state.audioChunks.length; ci++) {
- var data = state.audioChunks[ci].data;
- for (var cj = 0; cj < data.length; cj++) {
- merged[offset + cj] = data[cj];
- }
- offset += data.length;
- }
- if (output) {
- if (writeBinaryFile(output, merged)) {
- print.log('[¶¹°ü TTS] Òѱ£´æ: ' + output + ' (' + state.totalLen + ' ×Ö½Ú)');
- return output;
- }
- print.log('[¶¹°ü TTS] дÈëÎļþʧ°Ü: ' + output);
- return null;
- }
- return merged;
- }
- // ==================== Ö±½ÓÔËÐÐʾÀý ====================
- (function runDemo() {
- print.log('=== AIWROK TTS CORE µ¥Îļþ°æ ===');
- print.log('ʱ¼ä: ' + new Date().toString());
- print.log('');
- // --- Edge TTS ʾÀý ---
- print.log('[1] Edge TTS Éú³ÉÖÐ...');
- var demoText = '¸ý×ÓÄêºó¾©³ÇµÄÂòÂô²»ºÃ×ö£¬Ç°ÃÅ´ó½Öµ¹ÁËÈý¼Ò²è¹Ý£¬Î¨¶ÀµÂ˳µÄÍõÕÆ¹ñ£¬ÈÕ×Ó·´µ¹Ô½¹ýÔ½ÊæÕ¹¡£'
- + 'Êì¿ÍÃǶ¼´òȤ£¬ËµÀÏÍõÊÇ׿·ØÃ°ÁËÇàÑÌ£¬Æ½°×Î޹ʵĺÃÔËÍù»³Àïײ¡£'
- + 'ÍõÕÆ¹ñÓиöÅÔÈËÇÆ²»ÉϵÄСϰ¹ß£ºÃ¿ÌìÌì¸ÕÃÉÃÉÁÁ£¬»ï¼Æ»¹Ã»ÉÏÃÅ£¬ËûÏȰáÌõµÊ×ø¹ñ̨ºóÍ·£¬'
- + '¾Í×ÅÓ͵ƵĹ⣬ÔÚÂéÖ½±¾×ÓÉÏд°ëÒ³×Ö¡£Ð´µÄ²»ÊÇʲôʥÏÍÎÄÕ£¬È«ÊǼ¦Áã¹·ËéµÄÏÐÊ£º'
- + 'ÕŶþÒ¯°®ºÈì˵Ãõ¦µÄÏãÆ¬£¬ÀîÈýÉÞÁËÁ©Í×Ó¶ùµÄ²èÇ®£¬Â¿´ò¹öÊ£Á˰˿éÂô²»¶¯£¬½Ö¶Ô¹ýÔÓ»õÆÌµÄ¶«¼Ò°®ºÈÜÔÀò˫Ѭ¡£'
- + '³£À´²ä²èºÈµÄ»ì»ì¶ùÁõËÄЦËû£º¡°ÍõÕÆ¹ñ£¬ÄãÕâÊÇÒª¿¼Ðã²Å°¡£¿¿ª²è¹ÝÂôË®µÃÁË£¬·ÑÄÇÑ󾢸ÉÂ¡±'
- + 'ÍõÕÆ¹ñÒ²²»ÄÕ£¬ºÙºÙһЦ£¬°Ñ±¾×ÓÊÕ½ø³éÌ룬µÚ¶þÌìÕÕ¾Éд¡£'
- + 'ÄÇÄê´ºÉϲèÒ¶¼ÛÕǵÃаºõ£¬±ð¼Ò²è¹ÝҪôÕǼ۰ѿÍÈ˼·×ߣ¬ÒªÃ´»»Ëé²èÄ©×ÓÔÒÕÐÅÆ¡£'
- + 'Ψ¶ÀµÂ˳£¬¼Ûû´óÕÇ£¬²èζ»¹¸ú´ÓǰһÑùµØµÀ¡£Âú½ÖµÄÈ˶¼ËµÀÏÍõÔËÆøºÃ£¬Ìáǰ¶ÚןòèÁË¡£'
- + 'ÄÄÊÇÔËÆø£¿ËûÄǰëÒ³Ö½ÉÏ£¬Ôç¼Ç×Å¡°Ã¿Ä꿪´º²è¼Û±ØÕÇÈý³É£¬Í·ÄêÇïÀﶨ»õ×îÎÈ¡±¡£'
- + 'Õâ²»ÊÇËûÌìÉú»áË㣬ÊÇÌìÌìд¡¢ÄêÄê¼Ç£¬Ãþ³öÀ´µÄÀÏ¹æ¾Ø¡£ÅÔÈËÄêÄêÁٿʾò¾®£¬ËûÔç°Ñ¾®ÍÚÔÚÁËǰͷ¡£'
- + '»¹ÓÐÒ»»Ø£¬ÓиöÄÏϵijñ¶ÐÉÌÀ´³ÇÀïÊÕ´û£¬½ø²è¹ÝЪ½Å¡£ÍõÕÆ¹ñÒ»ÑÛ¾ÍÏëÆð£¬'
- + 'ǰÕó×Ó±¾×ÓÉϼǹýÕâλ¿Í¹Ù°®ºÈ±ÌÂÝ´º£¬»¹ÍÐÈË´òÌý¹ý¾©Àï¿¿Æ×µÄ³ñ¶Ðׯ¡£'
- + 'ËûÒ»±ßÉϲèÒ»±ß˳×ìÌáÁËÁ½¼Ò×ֺţ¬Á¬¶«¼ÒµÄÆ¢Æø±üÐÔ¶¼Ë³´øËµÁË¡£'
- + 'ÄÇÉÌÈ˸м¤²»¾¡£¬Íùºó´øÁ˺ü¸²¦Í¬ÐÐÀ´£¬³ÉÁ˹̶¨µÄ´óÖ÷¹Ë¡£'
- + 'ÅÔÈËÓÖ˵£¬ÀÏÍõ»áÀ´Ê£¬Æ½°×ײÉÏÁ˹óÈË¡£ÆäʵÄÄÊÇײµÄ£¿ÊÇËûÌìÌì¼Ç×Å¿ÍÈ˵ÄϲºÃ£¬»ú»áµÝµ½¸úǰ£¬ÉìÊ־ͽÓסÁË¡£'
- + 'ÈÕ×Ó¾ÃÁË£¬µÂ˳µÄÉúÒâÔ½×öÔ½ÎÈ£¬±ð¼Òµ¹±ÕµÄ»ï¼Æ¶¼À´Í¶±¼¡£'
- + 'ÓÐÈËרÃÅÀ´Ìֽ̡°×ªÔË¡±µÄ·¨×Ó£¬ÍõÕÆ¹ñ¾Í°ÑÄDZ¾ÂéÖ½±¾×Ó·³öÀ´¸øÈË¿´£º'
- + '¡°ÄÄÓÐʲôºÃÔË£¿ÎÒ¾ÍÊÇÿÌìÔçÆðд°ëÒ³Ö½£¬Ð´×Åд×Å£¬ÕËÒ²ÇåÁË£¬ÈËÒ²ÊìÁË£¬¸Ã±ÜµÄ¿Ó±ÜÁË£¬¸Ã×¥µÄ»ú»áҲûÅÜ¡£¡±'
- + '˽ÛӵijÂÏÈÉúßÈÁ˿ڲ裬ÂýÓÆÓÆ²¹Á˾䣺¡°Õâ¾Í½Ð¹°ÐÄʯµÄϰ¹ß¡£¿´×ÅÊÇÿÌì°ëÒ³Ö½µÄСÊ£¬'
- + 'ʵÔò°Ñ¼ÇÐÔ¡¢ÑÛÁ¦¡¢ÐÄ˼¶¼´®ÆðÀ´ÁË¡£Ï°¹ß³ÉÁ˱¾ÄÜ£¬¾Í²»ÓÃÌìÌì·ÑÄÔ×Ó×ÁÄ¥ÔËÆø¡£'
- + 'ÌáʾÊÇ¿ªÃÅµãµÆ£¬¶¯×÷ÊǼÇÕËʶÈË£¬ÌðÍ·ÊÇÂòÂô˳µ±£¬ÈÕ×ÓÒ»³¤£¬ºÃÔ˾ͳÉÁË×ÔÈ»¶øÈ»µÄʶù¡£¡±'
- + 'Ò²ÓÐÈ˲»·þÆø£¬ÂòÁ˱¾×Ó»ØÈ¥Ñ§£¬Ã»°ë¸öÔ¾ÍÏÓÂé·³£¬ÈÓÔÚÁËǽ½Ç¡£'
- + 'ÕÕ¾ÉÊÇÏ¡ÀïºýÍ¿¹ýÈÕ×Ó£¬ÅâÁË׬Á˶¼²»ÖªµÀ¸ùÓÉ£¬Óö×Ų»Ë³ÐľÍÔ¹Ãü²»ºÃ¡£'
- + 'ÍõÕÆ¹ñºóÀ´³£¸ú»ï¼ÆËµ£¬Ììµ×ϵĺÃÔË£¬´ÓÀ´²»ÊÇÌìÉϵôÏÂÀ´µÄ¡£'
- + '¾ÍÏñÕâ²è¹ÝµÄÃÅÖᣬÌìÌì²Á×ÅÈó×Å£¬¿ªÃžÍ˳µ±£»ÐâסÁË£¬ÔÙʹ¾¢Ò²ÍƲ»¿ª¡£'
- + 'ÿÌì°ëÒ³Ö½£¬¾ÍÊÇÄǵãÈóÖáµÄÓÍ£¬¿´×Ų»ÆðÑÛ£¬ÈÕ×Ó¾ÃÁË£¬¾ÍÄܳůðÕû·ÝÈÕ×ӵݲÎÈ¡£';
- var edgeOut = generateEdge({
- text: demoText,
- voice: DEFAULT_EDGE_VOICE,
- rate: '+0%',
- volume: '+0%',
- pitch: '+0Hz',
- output: '/sdcard/edge_tts_demo.mp3'
- });
- if (edgeOut) {
- print.log(' Éú³É³É¹¦: ' + edgeOut);
- } else {
- print.log(' Éú³Éʧ°Ü');
- }
- print.log('');
- // --- ¶¹°ü TTS ʾÀý£¨ÐèÕæÊµ²ÎÊý£¬·ñÔò»áʧ°Ü£© ---
- print.log('[2] ¶¹°ü TTS ʾÀý£¨ÐèÌîÈëÕæÊµ²ÎÊý²ÅÄÜÔËÐУ©...');
- var doubaoOut = generateDoubao({
- cookies: 'sessionid=YOUR_SESSIONID; session_ss=YOUR_SESSION_SS',
- conversationId: 'YOUR_CONVERSATION_ID',
- messageId: 'YOUR_MESSAGE_ID',
- speaker: DEFAULT_DOUBAO_VOICE,
- speechRate: 1.0,
- output: '/sdcard/doubao_tts_demo.ogg'
- });
- if (doubaoOut) {
- print.log(' Éú³É³É¹¦: ' + doubaoOut);
- } else {
- print.log(' Ô¤ÆÚʧ°Ü£¨²ÎÊýδÌ');
- }
- // --- ´òÓ¡ÒôÉ«Áбí ---
- print.log('');
- print.log('=== ¿ÉÓÃÒôÉ« ===');
- print.log('Edge TTS (' + EDGE_VOICES.length + ' ¸ö):');
- for (var i = 0; i < EDGE_VOICES.length; i++) {
- print.log(' ' + EDGE_VOICES[i].id + ' [' + EDGE_VOICES[i].name + ']');
- }
- print.log('');
- print.log('¶¹°ü TTS (' + DOUBAO_VOICES.length + ' ¸ö):');
- for (var j = 0; j < DOUBAO_VOICES.length; j++) {
- print.log(' ' + DOUBAO_VOICES[j].id + ' [' + DOUBAO_VOICES[j].name + ']');
- }
- })();
- exit();
¸´ÖÆ´úÂë
|
|