Text-to-Speech Reader

Read selected text using OpenAI TTS API

当前为 2024-05-31 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name Text-to-Speech Reader
  3. // @namespace http://tampermonkey.net/
  4. // @version 1.0
  5. // @description Read selected text using OpenAI TTS API
  6. // @author https://linux.do/u/snaily
  7. // @match *://*/*
  8. // @grant GM_xmlhttpRequest
  9. // @grant GM_registerMenuCommand
  10. // @grant GM_addStyle
  11. // @grant GM_setValue
  12. // @grant GM_getValue
  13. // @license MIT
  14. // ==/UserScript==
  15.  
  16. (function() {
  17. 'use strict';
  18.  
  19. // Add a button to the page for reading selected text
  20. const button = document.createElement('button');
  21. button.innerText = 'Read Aloud';
  22. button.style.position = 'absolute';
  23. button.style.zIndex = '1000';
  24. button.style.display = 'none'; // Initially hidden
  25. button.style.backgroundColor = '#007BFF'; // Blue background
  26. button.style.color = '#FFFFFF'; // White text
  27. button.style.border = 'none';
  28. button.style.borderRadius = '5px';
  29. button.style.padding = '10px 20px';
  30. button.style.boxShadow = '0 2px 5px rgba(0, 0, 0, 0.2)';
  31. button.style.cursor = 'pointer';
  32. button.style.fontSize = '14px';
  33. button.style.fontFamily = 'Arial, sans-serif';
  34. document.body.appendChild(button);
  35.  
  36. // Function to get selected text
  37. function getSelectedText() {
  38. let text = '';
  39. if (window.getSelection) {
  40. text = window.getSelection().toString();
  41. } else if (document.selection && document.selection.type != 'Control') {
  42. text = document.selection.createRange().text;
  43. }
  44. console.log('Selected Text:', text); // Debugging line
  45. return text;
  46. }
  47.  
  48. // Function to call OpenAI TTS API
  49. function callOpenAITTS(text, baseUrl, apiKey, voice) {
  50. const cachedAudioUrl = getCachedAudio(text);
  51. if (cachedAudioUrl) {
  52. console.log('Using cached audio');
  53. playAudio(cachedAudioUrl);
  54. resetButton();
  55. return;
  56. }
  57.  
  58. const url = `${baseUrl}/v1/audio/speech`;
  59. console.log('Calling OpenAI TTS API with text:', text);
  60. GM_xmlhttpRequest({
  61. method: 'POST',
  62. url: url,
  63. headers: {
  64. 'Content-Type': 'application/json',
  65. 'Authorization': `Bearer ${apiKey}`
  66. },
  67. data: JSON.stringify({
  68. model: 'tts-1',
  69. input: text,
  70. voice: voice
  71. }),
  72. responseType: 'arraybuffer',
  73. onload: function(response) {
  74. if (response.status === 200) {
  75. console.log('API call successful'); // Debugging line
  76. const audioBlob = new Blob([response.response], { type: 'audio/mpeg' });
  77. const audioUrl = URL.createObjectURL(audioBlob);
  78. playAudio(audioUrl);
  79. cacheAudio(text, audioUrl);
  80. } else {
  81. console.error('Error:', response.statusText);
  82. }
  83. // Reset button after request is complete
  84. resetButton();
  85. },
  86. onerror: function(error) {
  87. console.error('Request failed', error);
  88. // Reset button after request is complete
  89. resetButton();
  90. }
  91. });
  92. }
  93.  
  94. // Function to play audio
  95. function playAudio(url) {
  96. const audio = new Audio(url);
  97. audio.play();
  98. }
  99.  
  100. // Function to use browser's built-in TTS
  101. function speakText(text) {
  102. const utterance = new SpeechSynthesisUtterance(text);
  103. speechSynthesis.speak(utterance);
  104. }
  105.  
  106. // Function to set button to loading state
  107. function setLoadingState() {
  108. button.disabled = true;
  109. button.innerText = 'Loading...';
  110. button.style.backgroundColor = '#6c757d'; // Grey background
  111. button.style.cursor = 'not-allowed';
  112. }
  113.  
  114. // Function to reset button to original state
  115. function resetButton() {
  116. button.disabled = false;
  117. button.innerText = 'Read Aloud';
  118. button.style.backgroundColor = '#007BFF'; // Blue background
  119. button.style.cursor = 'pointer';
  120. }
  121.  
  122. // Helper function to get cached audio URL
  123. function getCachedAudio(text) {
  124. const cache = GM_getValue('cache', {});
  125. const item = cache[text];
  126. if (item) {
  127. const now = new Date().getTime();
  128. const weekInMillis = 7 * 24 * 60 * 60 * 1000; // One day in milliseconds
  129. if (now - item.timestamp < weekInMillis) {
  130. return item.audioUrl;
  131. } else {
  132. delete cache[text]; // Remove expired cache item
  133. GM_setValue('cache', cache);
  134. }
  135. }
  136. return null;
  137. }
  138.  
  139. // Helper function to cache audio URL
  140. function cacheAudio(text, audioUrl) {
  141. const cache = GM_getValue('cache', {});
  142. cache[text] = {
  143. audioUrl: audioUrl,
  144. timestamp: new Date().getTime()
  145. };
  146. GM_setValue('cache', cache);
  147. }
  148.  
  149. // Function to clear cache
  150. function clearCache() {
  151. GM_setValue('cache', {});
  152. alert('Cache cleared successfully.');
  153. }
  154.  
  155.  
  156. // Event listener for button click
  157. button.addEventListener('click', () => {
  158. const selectedText = getSelectedText();
  159. if (selectedText) {
  160. let apiKey = GM_getValue('apiKey', null);
  161. let baseUrl = GM_getValue('baseUrl', null);
  162. let voice = GM_getValue('voice', 'onyx'); // Default to 'onyx'
  163. if (!baseUrl) {
  164. alert('Please set the base URL for the TTS API in the Tampermonkey menu.');
  165. return;
  166. }
  167. if (!apiKey) {
  168. alert('Please set the API key for the TTS API in the Tampermonkey menu.');
  169. return;
  170. }
  171. setLoadingState(); // Set button to loading state
  172. if (window.location.hostname === 'github.com') {
  173. speakText(selectedText);
  174. resetButton(); // Reset button immediately for built-in TTS
  175. }else {
  176. callOpenAITTS(selectedText, baseUrl, apiKey, voice);
  177. }
  178. } else {
  179. alert('Please select some text to read aloud.');
  180. }
  181. });
  182.  
  183. // Show the button near the selected text
  184. document.addEventListener('mouseup', (event) => {
  185. // Check if the mouseup event is triggered by the button itself
  186. if (event.target === button) {
  187. return;
  188. }
  189.  
  190. const selectedText = getSelectedText();
  191. if (selectedText) {
  192. const mouseX = event.pageX;
  193. const mouseY = event.pageY;
  194. button.style.left = `${mouseX + 10}px`;
  195. button.style.top = `${mouseY + 10}px`;
  196. button.style.display = 'block';
  197. } else {
  198. button.style.display = 'none';
  199. }
  200. });
  201.  
  202. // Register menu command to set base URL
  203. GM_registerMenuCommand('Set Base URL for TTS API', () => {
  204. const currentBaseUrl = GM_getValue('baseUrl', 'https://api.openai.com'); // Default to 'https://api.openai.com'
  205. const newBaseUrl = prompt('Enter the base URL for the TTS API:', currentBaseUrl);
  206. if (newBaseUrl) {
  207. GM_setValue('baseUrl', newBaseUrl);
  208. alert('Base URL set to: ' + newBaseUrl);
  209. }
  210. });
  211.  
  212. // Register menu command to set API key
  213. GM_registerMenuCommand('Set API Key for TTS API', () => {
  214. const currentApiKey = GM_getValue('apiKey', '');
  215. const newApiKey = prompt('Enter the API key for the TTS API:', currentApiKey);
  216. if (newApiKey) {
  217. GM_setValue('apiKey', newApiKey);
  218. alert('API key set to: ' + newApiKey);
  219. }
  220. });
  221.  
  222. // Register menu command to set voice
  223. GM_registerMenuCommand('Set Voice for TTS', () => {
  224. const currentVoice = GM_getValue('voice', 'onyx'); // Default to 'onyx'
  225. const newVoice = prompt('Enter the voice for the TTS API (alloy, echo, fable, onyx, nova, shimmer):', currentVoice);
  226. const validVoices = ['alloy', 'echo', 'fable', 'onyx', 'nova', 'shimmer'];
  227. if (newVoice && validVoices.includes(newVoice.toLowerCase())) {
  228. GM_setValue('voice', newVoice.toLowerCase());
  229. alert('Voice set to: ' + newVoice);
  230. } else {
  231. alert('Invalid voice. Please enter one of the following: alloy, echo, fable, onyx, nova, shimmer.');
  232. }
  233. });
  234.  
  235. // Register menu command to clear cache
  236. GM_registerMenuCommand('Clear TTS Cache', clearCache);
  237. })();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址