您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
调用responsivevoice.org的语音合成服务朗读选中文本
当前为
- // ==UserScript==
- // @name pageSpeak
- // @name:zh-CN 中英文语音合成选读
- // @namespace tts@reverland.org
- // @description text to speech Service from responsivevoice.org to read aloud the page
- // @description:zh-CN 调用responsivevoice.org的语音合成服务朗读选中文本
- // @include *
- // @version 1.1
- // @grant GM_xmlhttpRequest
- // @grant GM_registerMenuCommand
- // ==/UserScript==
- window.document.body.addEventListener("keyup", toggleTTS, true);
- //window.document.body.addEventListener("mouseup", tts, false);
- GM_registerMenuCommand("start/stop tts", toggleTTS, "t");
- var toggle = false;
- var progressBar = document.createElement('progress');
- progressBar.style.position = "fixed";
- progressBar.style.left = "0";
- progressBar.style.bottom = "0";
- progressBar.style.display = "none";
- progressBar.style.zIndex = "99999";
- document.body.appendChild(progressBar);
- // 播放状态
- var playing = false;
- function toggleTTS(e) {
- if (e && e.which == 69 && e.ctrlKey || !e) {//ctrl-e
- if (toggle) {
- window.document.body.removeEventListener("mouseup", tts, false);
- console.log("pageSpeak Stop...")
- toggle = false;
- } else {
- window.document.body.addEventListener("mouseup", tts, false);
- console.log("pageSpeak Start...")
- toggle = true;
- }
- }
- }
- function tts(e) {
- var text = getText(e);
- if (!text) {
- return;
- }
- progressBar.style.display = "block";
- //console.log("text to speech: ", text);
- play(text);
- }
- function getText(e) {
- var selectObj = document.getSelection();
- // if #text node
- if (selectObj.anchorNode.nodeType == 3) {
- //GM_log(selectObj.anchorNode.nodeType.toString());
- var word = selectObj.toString();
- if (word == "") {
- return;
- }
- // merge multiple spaces
- word = word.replace(/\s+/g, ' ');
- // linebreak wordwrap, optimize for pdf.js
- word = word.replace('-\n','');
- // multiline selection, optimize for pdf.js
- word = word.replace('\n', ' ');
- //console.log("word:", word);
- }
- return word
- }
- // split by 100 length
- function *split(text, maxLength) {
- var index = 0;
- var regZh = /[\u4E00-\u9FD5]+/g
- var step = maxLength;
- if (!regZh.test(text)) {
- // en-US断句在标点边界
- var counter = 0;
- while (index < text.length && counter < 20) {
- step = maxLength;
- counter++;
- // search step;
- // dirty hack 10 is ok!
- for (let i =0; i < (((text.length - index) < 10)?(text.length - index):10); i++) {
- if (/^[\s.,?!]+$/m.test(text.substr(index + step, 1))) {
- step += 1
- break;
- } else {
- step -= 1;
- }
- if (step == 0) {
- // 包括最后一个字符
- step = maxLength + 1;
- break;
- }
- }
- yield text.slice(index, index + step);
- index += step;
- }
- } else {
- // chinese!
- var reg = /[a-zA-Z\s]+/g
- var counter = 0;
- while (index < text.length && counter < 20) {
- counter++;
- let result = reg.exec(text);
- if (result) {
- yield text.slice(index, result.index);
- yield result[0];
- step = result.index - index + result[0].length;
- } else {
- yield text.slice(index, index + maxLength);
- step = maxLength;
- }
- index += step;
- }
- }
- }
- function play(text) {
- //console.log("[DEBUG] PLAYOUND")
- var context = new AudioContext();
- var voices = [];
- //var reg = /[\u4E00-\u9FD5]+/g
- var reg = /[\u4E00-\u9FCC]+/g
- for (let s of split(text, 100)) {
- if (!s) {
- return;
- }
- if (!reg.test(s)) {
- LANG = 'en-US';
- } else {
- LANG = 'zh-CN';
- }
- //var soundUrl = `https://code.responsivevoice.org/getvoice.php?t=${s}&tl=${LANG}&sv=&vn=&pitch=0.5&rate=0.5&vol=1`
- var soundUrl = `https://code.responsivevoice.org/develop/getvoice.php?t=${s}&tl=${LANG}&sv=&vn=&pitch=0.5&rate=0.5&vol=1`
- var p = new Promise(function(resolve, reject) {
- // console.log("text parts: ", s);
- var ret = GM_xmlhttpRequest({
- method: "GET",
- url: soundUrl,
- responseType: 'arraybuffer',
- onload: function(res) {
- try {
- // console.log("get data", res.statusText);
- resolve(res.response);
- progressBar.setAttribute('value', progressBar.getAttribute('value') + 1);
- } catch(e) {
- reject(e);
- }
- }
- });
- });
- voices.push(p);
- }
- progressBar.setAttribute('max', voices.length);
- progressBar.setAttribute('value', 0);
- Promise.all(voices).then(playSound, e=>console.log(e));
- function playSound(bufferList) {
- // finish
- progressBar.style.display = "none";
- var reader = new FileReader();
- var blob = new Blob(bufferList, {type: 'application/octet-binary'});
- reader.addEventListener("loadend", function() {
- var buffer = reader.result;
- //console.log("final ArrayBuffer:", buffer);
- context.decodeAudioData(buffer, function(buffer) {
- if (playing) {
- console.log('playing: ', playing);
- try {
- source.stop();
- playing = false;
- } catch (e) {
- console.log(e);
- }
- }
- source = context.createBufferSource();
- source.buffer = buffer;
- source.connect(context.destination);
- source.start(0);
- playing = true;
- source.onended = () => {playing = false;}
- })
- });
- reader.readAsArrayBuffer(blob);
- }
- }