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