├── README.md ├── azure-text-to-speech-saver.aardio └── tts.aardio /README.md: -------------------------------------------------------------------------------- 1 | # azure-text-to-speech-saver 2 | 微软Azure文本转语音下载器 3 | 4 | **脚本基于[aardio](https://www.aardio.com/)编程语言** 5 | 6 | tts.aardio,白嫖免费接口 7 | 8 | ~~Azure上的文本转语音云服务,效果更接近真人发音,并有语气和换气音。Azure的demo网页仅能在线试听,想下载二次使用目前网络上有2种方案: 9 | 10 | 1. 录音法:缺点比较明显,为全局录音容易受干扰,且必须播放完才能完整获取数据 11 | 2. js注入法:现存的注入法,是利用油猴脚本重写SpeechSDK,获取前端合成后的数据,不需要听完音频,数据传输完毕就可以下载 12 | 13 | 本例采取的方案与第二种类似,也是注入法,但由于油猴脚本的限制,第二种方法的代码稍显复杂,而使用aardio的webview库,重写websocket,使代码更加简洁,可维护性强。 14 | 15 | **解决方案概述:** 16 | 17 | 云服务是通过WebSocket将音频分段后传输到前端。我们可以重写WebSocket库,更改监听“message”事件,将数据传递给aardio里处理。~~ 18 | 19 | -------------------------------------------------------------------------------- /azure-text-to-speech-saver.aardio: -------------------------------------------------------------------------------- 1 | import win.ui; 2 | /*DSG{{*/ 3 | var winform = win.form(text="WebView2";right=966;bottom=622) 4 | winform.add() 5 | /*}}*/ 6 | 7 | import web.view; 8 | import crypt.bin; 9 | var wb = web.view(winform); 10 | 11 | var buf = '' 12 | wb.external = { 13 | getData = function(data){ 14 | if(string.indexOf(data,"start")){ 15 | buf = '' 16 | }elseif(string.indexOf(data,"end")){ 17 | string.save("D:\1.mp3",buf ) 18 | winform.msgbox("下载完成") 19 | buf = '' 20 | }else { 21 | var src = crypt.bin.decodeHex(data); 22 | //去掉分段的token信息,只保留音频数据 23 | src = string.slice( src,131) 24 | buf = string.concat( buf,src ) 25 | } 26 | }; 27 | } 28 | 29 | wb.preloadScript(` 30 | (function() { 31 | //arraybuffer to hex 32 | function buf2hex(buffer) { // buffer is an ArrayBuffer 33 | return Array.prototype.map.call(new Uint8Array(buffer), x => ('00' + x.toString(16)).slice(-2)).join(''); 34 | } 35 | 36 | function hookWebSocket() { 37 | window.RealWebSocket = window.WebSocket; 38 | 39 | window.WebSocket = function() { 40 | var socket = new window.RealWebSocket(...arguments); 41 | 42 | socket.addEventListener("message", (e) => { 43 | if (typeof(e.data) == 'string') { 44 | aardio.getData(e.data) 45 | } else { 46 | aardio.getData(buf2hex(e.data)) 47 | } 48 | }); 49 | return socket; 50 | } 51 | } 52 | 53 | hookWebSocket() 54 | })() 55 | `) 56 | 57 | wb.go("https://azure.microsoft.com/zh-cn/services/cognitive-services/text-to-speech/") 58 | 59 | winform.show(); 60 | win.loopMessage(); -------------------------------------------------------------------------------- /tts.aardio: -------------------------------------------------------------------------------- 1 | import console; 2 | import string.template 3 | import web.rest.client 4 | var ssmlTemp = string.template() 5 | 6 | ssmlTemp.template = /*** 7 | 8 | 9 | 10 | 11 | ${text} 12 | 13 | 14 | 15 | 16 | ***/ 17 | 18 | var ssml = ssmlTemp.format( 19 | text = "aardio 在诞生之初就设计了良好的架构与语法。正因如此,aardio 历经17年发展,日新月异,每一年都会带来大量的更新扩展,但仍然能保持最初简洁高效的结构与语法,即使是最早的 aardio 源代码仍然能不经修改在最新版本开发环境中完美运行。"; 20 | ) 21 | 22 | client = web.rest.client() 23 | client.addHeaders = { 24 | authority: "southeastasia.api.speech.microsoft.com", 25 | accept: "*/*", 26 | "accept-language": "zh-CN,zh;q=0.9", 27 | customvoiceconnectionid: "d8a3a480-dd87-11ed-8758-97b5a7fbfaf6", 28 | origin: "https://speech.microsoft.com", 29 | "sec-ch-ua": 30 | '"Google Chrome";v="111", "Not(A:Brand";v="8", "Chromium";v="111"', 31 | "sec-ch-ua-mobile": "?0", 32 | "sec-ch-ua-platform": '"Windows"', 33 | "sec-fetch-dest": "empty", 34 | "sec-fetch-mode": "cors", 35 | "sec-fetch-site": "same-site", 36 | "user-agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/111.0.0.0 Safari/537.36", 37 | "content-type": "application/json", 38 | } 39 | 40 | api = client.api("https://southeastasia.api.speech.microsoft.com/accfreetrial/texttospeech/acc/v3.0-beta1") 41 | console.log("语音合成中") 42 | var res,e,c = api.vcg.speak.post(web.json.stringify( 43 | { 44 | ssml = ssml; 45 | ttsAudioFormat = "audio-24khz-160kbitrate-mono-mp3"; 46 | offsetInPlainText = 0; 47 | properties = { 48 | SpeakTriggerSource = "AccTuningPagePlayButton"; 49 | }; 50 | })) 51 | 52 | if(res){ 53 | import fsys.media 54 | string.save("D:\test.mp3",res ) 55 | fsys.media("D:\test.mp3").play() 56 | } 57 | console.pause() --------------------------------------------------------------------------------