Text-to-Speech Reader

Read selected text using OpenAI TTS API

2024-05-31 या दिनांकाला. सर्वात नवीन आवृत्ती पाहा.

ही स्क्रिप्ट इंस्टॉल करण्यासाठी तुम्हाला Tampermonkey, Greasemonkey किंवा Violentmonkey यासारखे एक्स्टेंशन इंस्टॉल करावे लागेल.

ही स्क्रिप्ट इंस्टॉल करण्यासाठी तुम्हाला Tampermonkey किंवा Violentmonkey यासारखे एक्स्टेंशन इंस्टॉल करावे लागेल..

ही स्क्रिप्ट इंस्टॉल करण्यासाठी तुम्हाला Tampermonkey किंवा Violentmonkey यासारखे एक्स्टेंशन इंस्टॉल करावे लागेल..

You will need to install an extension such as Tampermonkey or Userscripts to install this script.

ही स्क्रिप्ट इंस्टॉल करण्यासाठी तुम्हाला Tampermonkey यासारखे एक्स्टेंशन इंस्टॉल करावे लागेल..

ही स्क्रिप्ट इंस्टॉल करण्यासाठी तुम्हाला एक युझर स्क्रिप्ट व्यवस्थापक एक्स्टेंशन इंस्टॉल करावे लागेल.

(माझ्याकडे आधीच युझर स्क्रिप्ट व्यवस्थापक आहे, मला इंस्टॉल करू द्या!)

ही स्टाईल इंस्टॉल करण्यासाठी तुम्हाला Stylus सारखे एक्स्टेंशन इंस्टॉल करावे लागेल.

ही स्टाईल इंस्टॉल करण्यासाठी तुम्हाला Stylus सारखे एक्स्टेंशन इंस्टॉल करावे लागेल.

ही स्टाईल इंस्टॉल करण्यासाठी तुम्हाला Stylus सारखे एक्स्टेंशन इंस्टॉल करावे लागेल.

ही स्टाईल इंस्टॉल करण्यासाठी तुम्हाला एक युझर स्टाईल व्यवस्थापक इंस्टॉल करावे लागेल.

ही स्टाईल इंस्टॉल करण्यासाठी तुम्हाला एक युझर स्टाईल व्यवस्थापक इंस्टॉल करावे लागेल.

ही स्टाईल इंस्टॉल करण्यासाठी तुम्हाला एक युझर स्टाईल व्यवस्थापक इंस्टॉल करावे लागेल.

(माझ्याकडे आधीच युझर स्टाईल व्यवस्थापक आहे, मला इंस्टॉल करू द्या!)

// ==UserScript==
// @name         Text-to-Speech Reader
// @namespace    http://tampermonkey.net/
// @version      1.0
// @description  Read selected text using OpenAI TTS API
// @author       https://linux.do/u/snaily
// @match        *://*/*
// @grant        GM_xmlhttpRequest
// @grant        GM_registerMenuCommand
// @grant        GM_addStyle
// @grant        GM_setValue
// @grant        GM_getValue
// @license      MIT
// ==/UserScript==

(function() {
    'use strict';

    // Add a button to the page for reading selected text
    const button = document.createElement('button');
    button.innerText = 'Read Aloud';
    button.style.position = 'absolute';
    button.style.zIndex = '1000';
    button.style.display = 'none'; // Initially hidden
    button.style.backgroundColor = '#007BFF'; // Blue background
    button.style.color = '#FFFFFF'; // White text
    button.style.border = 'none';
    button.style.borderRadius = '5px';
    button.style.padding = '10px 20px';
    button.style.boxShadow = '0 2px 5px rgba(0, 0, 0, 0.2)';
    button.style.cursor = 'pointer';
    button.style.fontSize = '14px';
    button.style.fontFamily = 'Arial, sans-serif';
    document.body.appendChild(button);

    // Function to get selected text
    function getSelectedText() {
        let text = '';
        if (window.getSelection) {
            text = window.getSelection().toString();
        } else if (document.selection && document.selection.type != 'Control') {
            text = document.selection.createRange().text;
        }
        console.log('Selected Text:', text); // Debugging line
        return text;
    }

    // Function to call OpenAI TTS API
    function callOpenAITTS(text, baseUrl, apiKey, voice) {
        const cachedAudioUrl = getCachedAudio(text);
        if (cachedAudioUrl) {
            console.log('Using cached audio');
            playAudio(cachedAudioUrl);
            resetButton();
            return;
        }

        const url = `${baseUrl}/v1/audio/speech`;
        console.log('Calling OpenAI TTS API with text:', text);
        GM_xmlhttpRequest({
            method: 'POST',
            url: url,
            headers: {
                'Content-Type': 'application/json',
                'Authorization': `Bearer ${apiKey}`
            },
            data: JSON.stringify({
                model: 'tts-1',
                input: text,
                voice: voice
            }),
            responseType: 'arraybuffer',
            onload: function(response) {
                if (response.status === 200) {
                    console.log('API call successful'); // Debugging line
                    const audioBlob = new Blob([response.response], { type: 'audio/mpeg' });
                    const audioUrl = URL.createObjectURL(audioBlob);
                    playAudio(audioUrl);
                    cacheAudio(text, audioUrl);
                } else {
                    console.error('Error:', response.statusText);
                }
                // Reset button after request is complete
                resetButton();
            },
            onerror: function(error) {
                console.error('Request failed', error);
                // Reset button after request is complete
                resetButton();
            }
        });
    }

    // Function to play audio
    function playAudio(url) {
        const audio = new Audio(url);
        audio.play();
    }

    // Function to use browser's built-in TTS
    function speakText(text) {
        const utterance = new SpeechSynthesisUtterance(text);
        speechSynthesis.speak(utterance);
    }

    // Function to set button to loading state
    function setLoadingState() {
        button.disabled = true;
        button.innerText = 'Loading...';
        button.style.backgroundColor = '#6c757d'; // Grey background
        button.style.cursor = 'not-allowed';
    }

    // Function to reset button to original state
    function resetButton() {
        button.disabled = false;
        button.innerText = 'Read Aloud';
        button.style.backgroundColor = '#007BFF'; // Blue background
        button.style.cursor = 'pointer';
    }

    // Helper function to get cached audio URL
    function getCachedAudio(text) {
        const cache = GM_getValue('cache', {});
        const item = cache[text];
        if (item) {
            const now = new Date().getTime();
            const weekInMillis = 7 * 24 * 60 * 60 * 1000; // One day in milliseconds
            if (now - item.timestamp < weekInMillis) {
                return item.audioUrl;
            } else {
                delete cache[text]; // Remove expired cache item
                GM_setValue('cache', cache);
            }
        }
        return null;
    }

    // Helper function to cache audio URL
    function cacheAudio(text, audioUrl) {
        const cache = GM_getValue('cache', {});
        cache[text] = {
            audioUrl: audioUrl,
            timestamp: new Date().getTime()
        };
        GM_setValue('cache', cache);
    }

    // Function to clear cache
    function clearCache() {
        GM_setValue('cache', {});
        alert('Cache cleared successfully.');
    }


    // Event listener for button click
    button.addEventListener('click', () => {
        const selectedText = getSelectedText();
        if (selectedText) {
            let apiKey = GM_getValue('apiKey', null);
            let baseUrl = GM_getValue('baseUrl', null);
            let voice = GM_getValue('voice', 'onyx'); // Default to 'onyx'
            if (!baseUrl) {
                alert('Please set the base URL for the TTS API in the Tampermonkey menu.');
                return;
            }
            if (!apiKey) {
                alert('Please set the API key for the TTS API in the Tampermonkey menu.');
                return;
            }
            setLoadingState(); // Set button to loading state
            if (window.location.hostname === 'github.com') {
                speakText(selectedText);
                resetButton(); // Reset button immediately for built-in TTS
            }else {
                callOpenAITTS(selectedText, baseUrl, apiKey, voice);
            }
        } else {
            alert('Please select some text to read aloud.');
        }
    });

     // Show the button near the selected text
    document.addEventListener('mouseup', (event) => {
        // Check if the mouseup event is triggered by the button itself
        if (event.target === button) {
            return;
        }

        const selectedText = getSelectedText();
        if (selectedText) {
            const mouseX = event.pageX;
            const mouseY = event.pageY;
            button.style.left = `${mouseX + 10}px`;
            button.style.top = `${mouseY + 10}px`;
            button.style.display = 'block';
        } else {
            button.style.display = 'none';
        }
    });

    // Register menu command to set base URL
    GM_registerMenuCommand('Set Base URL for TTS API', () => {
        const currentBaseUrl = GM_getValue('baseUrl', 'https://api.openai.com'); // Default to 'https://api.openai.com'
        const newBaseUrl = prompt('Enter the base URL for the TTS API:', currentBaseUrl);
        if (newBaseUrl) {
            GM_setValue('baseUrl', newBaseUrl);
            alert('Base URL set to: ' + newBaseUrl);
        }
    });

    // Register menu command to set API key
    GM_registerMenuCommand('Set API Key for TTS API', () => {
        const currentApiKey = GM_getValue('apiKey', '');
        const newApiKey = prompt('Enter the API key for the TTS API:', currentApiKey);
        if (newApiKey) {
            GM_setValue('apiKey', newApiKey);
            alert('API key set to: ' + newApiKey);
        }
    });

    // Register menu command to set voice
    GM_registerMenuCommand('Set Voice for TTS', () => {
        const currentVoice = GM_getValue('voice', 'onyx'); // Default to 'onyx'
        const newVoice = prompt('Enter the voice for the TTS API (alloy, echo, fable, onyx, nova, shimmer):', currentVoice);
        const validVoices = ['alloy', 'echo', 'fable', 'onyx', 'nova', 'shimmer'];
        if (newVoice && validVoices.includes(newVoice.toLowerCase())) {
            GM_setValue('voice', newVoice.toLowerCase());
            alert('Voice set to: ' + newVoice);
        } else {
            alert('Invalid voice. Please enter one of the following: alloy, echo, fable, onyx, nova, shimmer.');
        }
    });

    // Register menu command to clear cache
    GM_registerMenuCommand('Clear TTS Cache', clearCache);
})();