diff --git a/index.html b/index.html index af25b26..6e953ff 100644 --- a/index.html +++ b/index.html @@ -204,6 +204,7 @@ client = openai.OpenAI( with client.audio.speech.with_streaming_response.create( model="tts-1", voice="alloy", + speed=0.8, input="I think so therefore, Today is a wonderful day to build something people love!" ) as response: response.stream_to_file("speech.mp3") @@ -222,6 +223,7 @@ async function getSpeech() { const response = await client.audio.speech.with_streaming_response.create({ model: "tts-1", voice: "alloy", + speed: 0.8, input: "I think so therefore, Today is a wonderful day to build something people love!" }); diff --git a/uncloseai.js b/uncloseai.js index 5a4860b..89d6a4f 100644 --- a/uncloseai.js +++ b/uncloseai.js @@ -77,106 +77,8 @@ function extractWebpageContent() { return content.trim(); } -// Function to compute SHA-256 hash -async function sha256(message) { - const encoder = new TextEncoder(); - const data = encoder.encode(message); - - const hashBuffer = await crypto.subtle.digest('SHA-256', data); - const hashArray = Array.from(new Uint8Array(hashBuffer)); - const hashHex = hashArray.map(b => b.toString(16).padStart(2, '0')).join(''); - return hashHex; -} - -// Function to open IndexedDB -function openDatabase() { - return new Promise((resolve, reject) => { - const request = indexedDB.open('TTSCache', 1); - - request.onupgradeneeded = (event) => { - const db = event.target.result; - db.createObjectStore('audio', { keyPath: 'hash' }); - }; - - request.onsuccess = (event) => { - resolve(event.target.result); - }; - - request.onerror = (event) => { - reject(event.target.error); - }; - }); -} - -// Function to get audio from IndexedDB -async function getAudioFromCache(hash) { - const db = await openDatabase(); - return new Promise((resolve, reject) => { - const transaction = db.transaction('audio', 'readonly'); - const store = transaction.objectStore('audio'); - const request = store.get(hash); - - request.onsuccess = (event) => { - resolve(event.target.result ? event.target.result.audioData : null); - }; - - request.onerror = (event) => { - reject(event.target.error); - }; - }); -} - -// Function to save audio to IndexedDB -async function saveAudioToCache(hash, audioData) { - const db = await openDatabase(); - return new Promise((resolve, reject) => { - const transaction = db.transaction('audio', 'readwrite'); - const store = transaction.objectStore('audio'); - const request = store.put({ hash, audioData }); - - request.onsuccess = () => { - resolve(); - }; - - request.onerror = (event) => { - reject(event.target.error); - }; - }); -} - -// Function to convert Blob to Base64 -function blobToBase64(blob) { - return new Promise((resolve, reject) => { - const reader = new FileReader(); - reader.onloadend = () => resolve(reader.result); - reader.onerror = reject; - reader.readAsDataURL(blob); - }); -} - -// Function to convert Base64 to Blob -function base64ToBlob(base64) { - const byteString = atob(base64.split(',')[1]); - const mimeString = base64.split(',')[0].split(':')[1].split(';')[0]; - const ab = new ArrayBuffer(byteString.length); - const ia = new Uint8Array(ab); - for (let i = 0; i < byteString.length; i++) { - ia[i] = byteString.charCodeAt(i); - } - return new Blob([ab], { type: mimeString }); -} - // Function to read text using TTS -async function speakText(text, voice = 'alloy') { - const textHash = await sha256(text + voice); // Use text and voice for hashing - const cachedAudioData = await getAudioFromCache(textHash); - - if (cachedAudioData) { - const audioBlob = base64ToBlob(cachedAudioData); - const audioUrl = URL.createObjectURL(audioBlob); - return { audio: new Audio(audioUrl), blob: audioBlob }; - } - +async function speakText(text, voice = 'alloy', rate = 0.8) { try { const response = await fetch(TTS_API_URL, { method: 'POST', @@ -196,10 +98,10 @@ async function speakText(text, voice = 'alloy') { } const audioBlob = await response.blob(); - const audioData = await blobToBase64(audioBlob); - await saveAudioToCache(textHash, audioData); // Cache the audio data const audioUrl = URL.createObjectURL(audioBlob); - return { audio: new Audio(audioUrl), blob: audioBlob }; + const audio = new Audio(audioUrl); + audio.playbackRate = rate; + return { audio, blob: audioBlob }; } catch (error) { console.error('Error in TTS:', error); throw error; @@ -562,16 +464,41 @@ async function openTTSModal() { }); article.appendChild(voiceSelection); + // Speed selection + const speedLabel = document.createElement('label'); + const speedValue = document.createElement('span'); + speedValue.textContent = '0.8'; // initial value + + speedLabel.textContent = `Speed: ${speedValue.textContent}`; + const speedSlider = document.createElement('input'); + speedSlider.type = 'range'; + speedSlider.min = '0.25'; + speedSlider.max = '4.0'; + speedSlider.step = '0.05'; + speedSlider.value = '0.8'; + + speedSlider.oninput = () => { + speedValue.textContent = speedSlider.value; + speedLabel.textContent = `Speed: ${speedSlider.value}`; + }; + + article.appendChild(speedLabel); + article.appendChild(speedSlider); + const playButton = document.createElement('button'); playButton.textContent = 'Play Text'; playButton.onclick = async () => { const currentText = textArea.value.trim(); const selectedVoice = document.querySelector('input[name="voice"]:checked').value; + const selectedSpeed = parseFloat(speedSlider.value); if (currentText !== lastTTSInput || selectedVoice !== lastTTSResult?.voice) { + if (lastTTSResult?.audio) { + lastTTSResult.audio.pause(); // Stop previous audio + } playButton.textContent = 'Processing...'; playButton.disabled = true; // Disable button while processing lastTTSInput = currentText; - lastTTSResult = await speakText(currentText, selectedVoice); + lastTTSResult = await speakText(currentText, selectedVoice, selectedSpeed); lastTTSResult.voice = selectedVoice; playButton.textContent = 'Pause Text'; playButton.disabled = false; // Re-enable button after processing