speed slider
modified: index.html modified: uncloseai.js
This commit is contained in:
parent
b5a6f75627
commit
292c94cc67
2 changed files with 32 additions and 103 deletions
|
|
@ -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!"
|
||||
});
|
||||
|
||||
|
|
|
|||
133
uncloseai.js
133
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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue