Azure Speech Download

为微软的文本转语音服务的 demo 页面添加下载按钮

目前為 2022-09-07 提交的版本,檢視 最新版本

  1. // ==UserScript==
  2. // @name Azure Speech Download
  3. // @namespace
  4. // @version 0.6
  5. // @description 为微软的文本转语音服务的 demo 页面添加下载按钮
  6. // @author Puteulanus
  7. // @homepage https://greasyfork.org/zh-CN/scripts/444347-azure-speech-download
  8. // @match https://azure.microsoft.com/*/services/cognitive-services/text-to-speech/*
  9. // @icon https://www.microsoft.com/favicon.ico
  10. // @require https://cdn.bootcdn.net/ajax/libs/FileSaver.js/2.0.5/FileSaver.min.js
  11. // @grant none
  12. // @run-at document-end
  13. // @namespace https://greasyfork.org/users/909438
  14. // ==/UserScript==
  15.  
  16. /* globals saveAs */
  17. /* jshint esversion: 6 */
  18. (function() {
  19. 'use strict';
  20.  
  21. // Your code here...
  22. if(!window.saveAs) {
  23. window.saveAs = (blob, name) => {
  24. const a = document.createElement("a");
  25. document.body.appendChild(a);
  26. a.style = "display: none";
  27.  
  28. const url = window.URL.createObjectURL(blob);
  29. a.href = url;
  30. a.download = name;
  31. a.click();
  32. window.URL.revokeObjectURL(url);
  33. }
  34. }
  35.  
  36. const SpeechSDK = window.SpeechSDK
  37. let fileSize = 0
  38. let streamSize = 0
  39. let wavFragments = []
  40. let enableDownload = false
  41. let enableCollect = false
  42. let autoProcessing = false
  43. let tasks = []
  44.  
  45. function createButton(id, color, content) {
  46. const button = document.getElementById('playli').cloneNode(true)
  47. button.id = id
  48. button.querySelector('span:last-of-type').textContent = content
  49. button.querySelector('button').style.backgroundColor = color
  50. button.querySelector('button').style.borderColor = color
  51. return button
  52. }
  53.  
  54. function setButton(button, color, content) {
  55. button.querySelector('span:last-of-type').textContent = content
  56. button.querySelector('button').style.backgroundColor = color
  57. button.querySelector('button').style.borderColor = color
  58. }
  59.  
  60. function downloadAndClean() {
  61. const sentAudio = new window.Uint8Array(fileSize)
  62. fileSize = 0
  63. streamSize = 0
  64. wavFragments.reduce((size, fragment) => {
  65. sentAudio.set(new window.Uint8Array(fragment), size)
  66. return size + fragment.byteLength
  67. }, 0)
  68. wavFragments.length = 0
  69. saveAs(new Blob([sentAudio]), (new Date()).toISOString().replace('T', ' ').replace(':', '_').split('.')[0] + '.mp3')
  70. }
  71.  
  72. function switchOptionDisplay() {
  73. if (enableCollect) {
  74. autoSplitButton.style.display = 'block'
  75. optionArea.style.display = 'block'
  76. } else {
  77. autoSplitButton.style.display = 'none'
  78. optionArea.style.display = 'none'
  79. }
  80. }
  81.  
  82. function dispatchTextChange() {
  83. const evt = document.createEvent('HTMLEvents')
  84. evt.initEvent('input', true, true)
  85. ttstext.dispatchEvent(evt)
  86. }
  87.  
  88. const downloadStatus = document.createElement('div')
  89. const downloadSize = document.createElement('div')
  90. const buttonArea = document.getElementById('playli').parentElement
  91. const ttstext = document.getElementById('ttstext')
  92.  
  93. ttstext.ondrop = async (e) => {
  94. const files = e.dataTransfer.files
  95. if (files.length === 1 && files[0].type === 'text/plain') {
  96. e.preventDefault()
  97. const file = files[0]
  98. ttstext.value = await file.text()
  99. dispatchTextChange()
  100. }
  101. }
  102.  
  103. // reuqired by Firefox
  104. ttstext.ondragover = function(e){
  105. e.preventDefault();
  106. }
  107.  
  108. // set document
  109. setTimeout(() => {
  110. setTimeout(() => {
  111. const languageselect = document.getElementById('languageselect')
  112. const onchange = languageselect.onchange
  113. languageselect.onchange = (...args) => {
  114. onchange(...args)
  115. ttstext.value += "\n\n\n收集模式:\n\n打开之后,点击\“下载\”按钮转换的音频会被收集,在收集模式关闭时合成一个音频下载"
  116. ttstext.value += "\n\n自动拆分:\n\n将长文本拆分为多个接近“段落长度”的片段,并只在“分隔符”处截断,避免句子被截断,影响阅读效果"
  117. ttstext.value += "\n\n\n拖拽 txt 文件至此框可加载文本文件"
  118. languageselect.onchange = onchange
  119. }
  120. }, 0)
  121. }, 0)
  122.  
  123. // set download button
  124. const downloadButton = createButton('donwloadli', 'green', '下载')
  125. downloadButton.addEventListener('click', () => {
  126. downloadStatus.textContent = '下载中'
  127. enableDownload = true
  128. streamSize = 0
  129. document.getElementById('playbtn').click()
  130. enableDownload = false
  131. })
  132. downloadStatus.style.marginRight = '10px'
  133. buttonArea.appendChild(downloadButton)
  134. // set collect button
  135. const collectButton = createButton('collectli', 'red', '收集模式关')
  136. collectButton.addEventListener('click', () => {
  137. if(!enableCollect) {
  138. enableCollect = true
  139. switchOptionDisplay()
  140. setButton(collectButton, 'green', '收集模式开')
  141. } else {
  142. enableCollect = false
  143. switchOptionDisplay()
  144. setButton(collectButton, 'red', '收集模式关')
  145. if (!fileSize) return
  146. downloadAndClean()
  147. }
  148. })
  149. collectButton.style.marginRight = '10px'
  150. buttonArea.appendChild(collectButton)
  151. // set options
  152. const optionArea = document.createElement('div')
  153. const maxSizeInput = document.createElement('input')
  154. const delimiterInput = document.createElement('input')
  155. const maxSizeLabel = document.createElement('span')
  156. const delimiterLabel = document.createElement('span')
  157. optionArea.id = 'optiondiv'
  158. optionArea.style.display = 'none'
  159. maxSizeLabel.textContent = '段落长度'
  160. maxSizeInput.style.width = '50px'
  161. maxSizeInput.style.margin = '10px'
  162. maxSizeInput.value = '300'
  163. delimiterLabel.textContent = '分隔符'
  164. delimiterInput.style.width = '100px'
  165. delimiterInput.style.margin = '10px'
  166. delimiterInput.value = ',。?,.?'
  167. optionArea.appendChild(maxSizeLabel)
  168. optionArea.appendChild(maxSizeInput)
  169. optionArea.appendChild(delimiterLabel)
  170. optionArea.appendChild(delimiterInput)
  171. buttonArea.parentElement.appendChild(optionArea)
  172. // set download status
  173. buttonArea.parentElement.appendChild(downloadStatus)
  174. buttonArea.parentElement.appendChild(downloadSize)
  175. // set auto split button
  176. const autoSplitButton = createButton('autosplit', 'red', '自动拆分')
  177. autoSplitButton.addEventListener('click', () => {
  178. setButton(autoSplitButton, 'green', '拆分中')
  179. autoProcessing = true
  180. const maxSize = +maxSizeInput.value
  181. const delimiters = delimiterInput.value.split('')
  182. const text = ttstext.value
  183. const textHandler = text.split('').reduce(
  184. (obj, char, index, arr) => {
  185. obj.buffer.push(char)
  186. if (delimiters.indexOf(char) >= 0) obj.end = index
  187. if (obj.buffer.length === maxSize) {
  188. obj.res.push(obj.buffer.splice(0, obj.end + 1 - obj.offset).join(''))
  189. obj.offset += obj.res[obj.res.length - 1].length
  190. }
  191. return obj
  192. }, {
  193. buffer: [],
  194. end: 0,
  195. offset:0,
  196. res: []
  197. })
  198. textHandler.res.push(textHandler.buffer.join(''))
  199. ttstext.value = textHandler.res.shift()
  200. tasks = textHandler.res
  201. dispatchTextChange()
  202. downloadButton.click()
  203. })
  204. autoSplitButton.style.display = 'none'
  205. buttonArea.appendChild(autoSplitButton)
  206.  
  207. const streamHandler = {
  208. write: function (dataBuffer) {
  209. streamSize += dataBuffer.byteLength
  210. if (streamSize <= 1900800) {
  211. fileSize += dataBuffer.byteLength
  212. downloadSize.textContent = `已接收 ${fileSize / 1000} kb`
  213. wavFragments.push(dataBuffer)
  214. }
  215. if (streamSize === 1900800) {
  216. downloadStatus.textContent = '下载长度超过免费限额,请分割文本后使用收集模式'
  217. if (!enableCollect) {
  218. fileSize = 0
  219. wavFragments.length = 0
  220. } else {
  221. fileSize -= 1900800
  222. wavFragments.length -= 1320
  223. }
  224. }
  225. },
  226. close: function () {
  227. downloadStatus.textContent = '下载完成'
  228. if (!enableCollect) {
  229. downloadAndClean()
  230. return
  231. }
  232. if (!autoProcessing) return
  233. if (tasks.length) {
  234. ttstext.value = tasks.shift()
  235. dispatchTextChange()
  236. downloadButton.click()
  237. } else {
  238. autoProcessing = false
  239. setButton(autoSplitButton, 'red', '自动拆分')
  240. collectButton.click()
  241. }
  242. }
  243. }
  244.  
  245. const outputStream = SpeechSDK.PushAudioOutputStream.create(streamHandler)
  246.  
  247. SpeechSDK.AudioConfig.fromSpeakerOutput = (() => {
  248. const fromSpeakerOutput = SpeechSDK.AudioConfig.fromSpeakerOutput
  249. return function (audioDestination) {
  250. return enableDownload ? audioDestination.onAudioEnd() || SpeechSDK.AudioConfig.fromStreamOutput(outputStream) : fromSpeakerOutput(audioDestination)
  251. }
  252. })()
  253. })();