您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
为微软的文本转语音服务的 demo 页面添加下载按钮
当前为
// ==UserScript== // @name Azure Speech Download // @namespace // @version 0.8 // @description 为微软的文本转语音服务的 demo 页面添加下载按钮 // @author Puteulanus // @homepage https://greasyfork.org/zh-CN/scripts/444347-azure-speech-download // @match https://azure.microsoft.com/*/services/cognitive-services/text-to-speech/* // @icon https://www.microsoft.com/favicon.ico // @require https://cdn.bootcdn.net/ajax/libs/FileSaver.js/2.0.5/FileSaver.min.js // @grant none // @run-at document-end // @namespace https://greasyfork.org/users/909438 // ==/UserScript== /* globals saveAs */ /* jshint esversion: 6 */ (function() { 'use strict'; // Your code here... if(!window.saveAs) { window.saveAs = (blob, name) => { const a = document.createElement("a"); document.body.appendChild(a); a.style = "display: none"; const url = window.URL.createObjectURL(blob); a.href = url; a.download = name; a.click(); window.URL.revokeObjectURL(url); } } const SpeechSDK = window.SpeechSDK let fileSize = 0 let streamSize = 0 let wavFragments = [] let enableDownload = false let enableCollect = false let autoProcessing = false let tasks = [] let fileExt = '.mp3' function createButton(id, color, content) { const button = document.getElementById('playli').cloneNode(true) button.id = id button.querySelector('span:last-of-type').textContent = content button.querySelector('button').style.backgroundColor = color button.querySelector('button').style.borderColor = color return button } function setButton(button, color, content) { button.querySelector('span:last-of-type').textContent = content button.querySelector('button').style.backgroundColor = color button.querySelector('button').style.borderColor = color } function downloadAndClean() { const sentAudio = new window.Uint8Array(fileSize) fileSize = 0 streamSize = 0 wavFragments.reduce((size, fragment) => { sentAudio.set(new window.Uint8Array(fragment), size) return size + fragment.byteLength }, 0) wavFragments.length = 0 saveAs(new Blob([sentAudio]), (new Date()).toISOString().replace('T', ' ').replace(':', '_').split('.')[0] + fileExt) } function switchOptionDisplay() { if (enableCollect) { autoSplitButton.style.display = 'block' optionArea.style.display = 'block' previewPlayer.style.display = 'inline-block' } else { autoSplitButton.style.display = 'none' optionArea.style.display = 'none' previewPlayer.style.display = 'none' } } function syncAudioToPlayer() { const sentAudio = new window.Uint8Array(fileSize) wavFragments.reduce((size, fragment) => { sentAudio.set(new window.Uint8Array(fragment), size) return size + fragment.byteLength }, 0) const audioBlob = new Blob([sentAudio], {type : 'audio/ogg'}) previewPlayer.src = URL.createObjectURL(audioBlob) } function dispatchTextChange() { const evt = document.createEvent('HTMLEvents') evt.initEvent('input', true, true) ttstext.dispatchEvent(evt) } const downloadStatus = document.createElement('div') const downloadSize = document.createElement('div') const buttonArea = document.getElementById('playli').parentElement const ttstext = document.getElementById('ttstext') const styleSelecter = document.getElementById('voicestyleselect').parentElement ttstext.ondrop = async (e) => { const files = e.dataTransfer.files if (files.length === 1 && files[0].type === 'text/plain') { e.preventDefault() const file = files[0] ttstext.value = await file.text() dispatchTextChange() } } // reuqired by Firefox ttstext.ondragover = function(e){ e.preventDefault(); } // set document setTimeout(() => { setTimeout(() => { const languageselect = document.getElementById('languageselect') const onchange = languageselect.onchange languageselect.onchange = (...args) => { onchange(...args) ttstext.value += "\n\n\n收集模式:\n\n打开之后,点击\“下载\”按钮转换的音频会被收集,在收集模式关闭时合成一个音频下载" ttstext.value += "\n\n自动拆分:\n\n将长文本拆分为多个接近“段落长度”的片段,并只在“分隔符”处截断,避免句子被截断,影响阅读效果" ttstext.value += "\n\n\n\n拖拽 txt 文件至此框可加载文本文件" languageselect.onchange = onchange } }, 0) }, 0) // set download button const downloadButton = createButton('donwloadli', 'green', '下载') downloadButton.addEventListener('click', () => { downloadStatus.textContent = '下载中' enableDownload = true streamSize = 0 document.getElementById('playbtn').click() enableDownload = false }) downloadStatus.style.marginRight = '10px' buttonArea.appendChild(downloadButton) // set collect button const collectButton = createButton('collectli', 'red', '收集模式关') collectButton.addEventListener('click', () => { if(!enableCollect) { enableCollect = true switchOptionDisplay() setButton(collectButton, 'green', '收集模式开') } else { enableCollect = false switchOptionDisplay() setButton(collectButton, 'red', '收集模式关') if (!fileSize) return downloadAndClean() } }) collectButton.style.marginRight = '10px' buttonArea.appendChild(collectButton) // set options const optionArea = document.createElement('div') const maxSizeInput = document.createElement('input') const delimiterInput = document.createElement('input') const maxSizeLabel = document.createElement('span') const delimiterLabel = document.createElement('span') optionArea.id = 'optiondiv' optionArea.style.display = 'none' maxSizeLabel.textContent = '段落长度' maxSizeInput.style.width = '50px' maxSizeInput.style.margin = '10px' maxSizeInput.value = '300' delimiterLabel.textContent = '分隔符' delimiterInput.style.width = '100px' delimiterInput.style.margin = '10px' delimiterInput.value = ',。?,.?' optionArea.appendChild(maxSizeLabel) optionArea.appendChild(maxSizeInput) optionArea.appendChild(delimiterLabel) optionArea.appendChild(delimiterInput) buttonArea.parentElement.appendChild(optionArea) // set download status buttonArea.parentElement.appendChild(downloadStatus) buttonArea.parentElement.appendChild(downloadSize) // set auto split button const autoSplitButton = createButton('autosplit', 'red', '自动拆分') autoSplitButton.addEventListener('click', () => { setButton(autoSplitButton, 'green', '拆分中') autoProcessing = true const maxSize = +maxSizeInput.value const delimiters = delimiterInput.value.split('') const text = ttstext.value const textHandler = text.split('').reduce( (obj, char, index, arr) => { obj.buffer.push(char) if (delimiters.indexOf(char) >= 0) obj.end = index if (obj.buffer.length === maxSize) { obj.res.push(obj.buffer.splice(0, obj.end + 1 - obj.offset).join('')) obj.offset += obj.res[obj.res.length - 1].length } return obj }, { buffer: [], end: 0, offset:0, res: [] }) textHandler.res.push(textHandler.buffer.join('')) ttstext.value = textHandler.res.shift() tasks = textHandler.res dispatchTextChange() downloadButton.click() }) autoSplitButton.style.display = 'none' buttonArea.appendChild(autoSplitButton) // set preview player const previewPlayer = document.createElement('audio') previewPlayer.controls = true previewPlayer.style.display = 'none' previewPlayer.style.width = '100%' previewPlayer.style.marginTop = '10px' ttstext.after(previewPlayer) // set formatting options try { const optionSelector = styleSelecter.cloneNode(true) const label = optionSelector.querySelector('label') label.textContent = '音频编码' label.htmlFor = 'voiceformatselect' const options = optionSelector.querySelector('select') options.id = 'voiceformatselect' options.innerHTML = '' Object.entries(SpeechSDK.SpeechSynthesisOutputFormat).filter(item => !isNaN(item[0])) .filter(item => /(^Audio.+Mp3$)|(^Ogg)|(^Webm)/.test(item[1])) .forEach(item => { const format = item[1] const option = document.createElement("option") option.value = format option.text = format if (format === 'Audio24Khz96KBitRateMonoMp3') option.selected = true options.appendChild(option) }) styleSelecter.after(optionSelector) options.addEventListener('change', () => { SpeechSDK.SpeechSynthesisOutputFormat.Audio24Khz96KBitRateMonoMp3 = SpeechSDK.SpeechSynthesisOutputFormat[options.value] if (options.value.startsWith('Ogg')) { fileExt = '.ogg' } else if (options.value.startsWith('Webm')) { fileExt = '.webm' } else { fileExt = '.mp3' } }) } catch (e) { console.log(e) } const streamHandler = { write: function (dataBuffer) { streamSize += dataBuffer.byteLength if (streamSize <= 1900800) { fileSize += dataBuffer.byteLength downloadSize.textContent = `已接收 ${fileSize / 1000} kb` if (autoProcessing) downloadSize.textContent = `剩余分段 ${tasks.length} ` + downloadSize.textContent wavFragments.push(dataBuffer) } if (streamSize === 1900800) { downloadStatus.textContent = '下载长度超过免费限额,请分割文本后使用收集模式' if (!enableCollect) { fileSize = 0 wavFragments.length = 0 } else { fileSize -= 1900800 wavFragments.length -= 1320 } } }, close: function () { downloadStatus.textContent = '下载完成' if (!enableCollect) { downloadAndClean() return } if (!autoProcessing) { syncAudioToPlayer() return } if (tasks.length) { ttstext.value = tasks.shift() dispatchTextChange() downloadButton.click() } else { autoProcessing = false setButton(autoSplitButton, 'red', '自动拆分') ttstext.value = "自动拆分完成\n\n使用下方播放器播放,或关闭收集模式下载音频文件" syncAudioToPlayer() } } } const outputStream = SpeechSDK.PushAudioOutputStream.create(streamHandler) SpeechSDK.AudioConfig.fromSpeakerOutput = (() => { const fromSpeakerOutput = SpeechSDK.AudioConfig.fromSpeakerOutput return function (audioDestination) { return enableDownload ? audioDestination.onAudioEnd() || SpeechSDK.AudioConfig.fromStreamOutput(outputStream) : fromSpeakerOutput(audioDestination) } })() })();