Azure Speech Download

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

目前为 2022-09-07 提交的版本。查看 最新版本

  1. // ==UserScript==
  2. // @name Azure Speech Download
  3. // @namespace
  4. // @version 0.5
  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. document.getElementById('autosplit').style.display = 'block'
  75. document.getElementById('optiondiv').style.display = 'block'
  76. } else {
  77. document.getElementById('autosplit').style.display = 'none'
  78. document.getElementById('optiondiv').style.display = 'none'
  79. }
  80. }
  81.  
  82. const downloadStatus = document.createElement('div')
  83. const downloadSize = document.createElement('div')
  84. const buttonArea = document.getElementById('playli').parentElement
  85.  
  86. // set download button
  87. const downloadButton = createButton('donwloadli', 'green', '下载')
  88. downloadButton.addEventListener('click', () => {
  89. downloadStatus.textContent = '下载中'
  90. enableDownload = true
  91. streamSize = 0
  92. document.getElementById('playbtn').click()
  93. enableDownload = false
  94. })
  95. downloadStatus.style.marginRight = '10px'
  96. buttonArea.appendChild(downloadButton)
  97. // set collect button
  98. const collectButton = createButton('collectli', 'red', '收集模式关')
  99. collectButton.addEventListener('click', () => {
  100. if(!enableCollect) {
  101. enableCollect = true
  102. switchOptionDisplay()
  103. setButton(collectButton, 'green', '收集模式开')
  104. } else {
  105. enableCollect = false
  106. switchOptionDisplay()
  107. setButton(collectButton, 'red', '收集模式关')
  108. if (!fileSize) return
  109. downloadAndClean()
  110. }
  111. })
  112. collectButton.style.marginRight = '10px'
  113. buttonArea.appendChild(collectButton)
  114. // set options
  115. const optionArea = document.createElement('div')
  116. const maxSizeInput = document.createElement('input')
  117. const delimiterInput = document.createElement('input')
  118. const maxSizeLabel = document.createElement('span')
  119. const delimiterLabel = document.createElement('span')
  120. optionArea.id = 'optiondiv'
  121. optionArea.style.display = 'none'
  122. maxSizeLabel.textContent = '段落长度'
  123. maxSizeInput.style.width = '50px'
  124. maxSizeInput.style.margin = '10px'
  125. maxSizeInput.value = '300'
  126. delimiterLabel.textContent = '分隔符'
  127. delimiterInput.style.width = '100px'
  128. delimiterInput.style.margin = '10px'
  129. delimiterInput.value = ',。?,.?'
  130. optionArea.appendChild(maxSizeLabel)
  131. optionArea.appendChild(maxSizeInput)
  132. optionArea.appendChild(delimiterLabel)
  133. optionArea.appendChild(delimiterInput)
  134. buttonArea.parentElement.appendChild(optionArea)
  135. // set download status
  136. buttonArea.parentElement.appendChild(downloadStatus)
  137. buttonArea.parentElement.appendChild(downloadSize)
  138. // set auto split button
  139. const autoSplitButton = createButton('autosplit', 'red', '自动拆分')
  140. autoSplitButton.addEventListener('click', () => {
  141. setButton(autoSplitButton, 'green', '拆分中')
  142. autoProcessing = true
  143. const maxSize = +maxSizeInput.value
  144. const delimiters = delimiterInput.value.split('')
  145. const text = document.getElementById('ttstext').value
  146. const textHandler = text.split('').reduce(
  147. (obj, char, index, arr) => {
  148. obj.buffer.push(char)
  149. if (delimiters.indexOf(char) >= 0) obj.end = index
  150. if (obj.buffer.length === maxSize) {
  151. obj.res.push(obj.buffer.splice(0, obj.end + 1 - obj.offset).join(''))
  152. obj.offset += obj.res[obj.res.length - 1].length
  153. }
  154. return obj
  155. }, {
  156. buffer: [],
  157. end: 0,
  158. offset:0,
  159. res: []
  160. })
  161. textHandler.res.push(textHandler.buffer.join(''))
  162. document.getElementById('ttstext').value = textHandler.res.shift()
  163. tasks = textHandler.res
  164. const evt = document.createEvent('HTMLEvents')
  165. evt.initEvent('input', true, true)
  166. document.getElementById('ttstext').dispatchEvent(evt)
  167. downloadButton.click()
  168. })
  169. autoSplitButton.style.display = 'none'
  170. buttonArea.appendChild(autoSplitButton)
  171.  
  172. const streamHandler = {
  173. write: function (dataBuffer) {
  174. streamSize += dataBuffer.byteLength
  175. if (streamSize <= 1900800) {
  176. fileSize += dataBuffer.byteLength
  177. downloadSize.textContent = `已接收 ${fileSize / 1000} kb`
  178. wavFragments.push(dataBuffer)
  179. }
  180. if (streamSize === 1900800) {
  181. downloadStatus.textContent = '下载长度超过免费限额,请分割文本后使用收集模式'
  182. if (!enableCollect) {
  183. fileSize = 0
  184. wavFragments.length = 0
  185. } else {
  186. fileSize -= 1900800
  187. wavFragments.length -= 1320
  188. }
  189. }
  190. },
  191. close: function () {
  192. downloadStatus.textContent = '下载完成'
  193. if (!enableCollect) {
  194. downloadAndClean()
  195. return
  196. }
  197. if (!autoProcessing) return
  198. if (tasks.length) {
  199. document.getElementById('ttstext').value = tasks.shift()
  200. const evt = document.createEvent('HTMLEvents')
  201. evt.initEvent('input', true, true)
  202. document.getElementById('ttstext').dispatchEvent(evt)
  203. downloadButton.click()
  204. } else {
  205. autoProcessing = false
  206. setButton(autoSplitButton, 'red', '自动拆分')
  207. collectButton.click()
  208. }
  209. }
  210. }
  211.  
  212. const outputStream = SpeechSDK.PushAudioOutputStream.create(streamHandler)
  213.  
  214. SpeechSDK.AudioConfig.fromSpeakerOutput = (() => {
  215. const fromSpeakerOutput = SpeechSDK.AudioConfig.fromSpeakerOutput
  216. return function (audioDestination) {
  217. return enableDownload ? audioDestination.onAudioEnd() || SpeechSDK.AudioConfig.fromStreamOutput(outputStream) : fromSpeakerOutput(audioDestination)
  218. }
  219. })()
  220. })();