<style data-wp-block-html="css">
Texti í Tal - Vinnustöð https://cdn.tailwindcss.com @import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap'); body { font-family: 'Inter', sans-serif; } /* Sérsniðið útlit fyrir hljóðspilarann */ audio::-webkit-media-controls-panel { background-color: #f3f4f6; }
Texti í Tal Vinnustöð
Búðu til hljóðskrár fyrir heimasíðuna þína með nýjustu gervigreind.
Google Gemini API Lykill
Vista
Lykillinn er vistaður örugglega í vafranum þínum (Local Storage).
Texti til lestrar 0 stafir
Rödd:
Fenrir (Djúp karlmannsrödd) Aoede (Fágaðri kvenrödd) Kore (Róleg kvenrödd) Puck (Hress karlmannsrödd) Charon (Dýpri karlmannsrödd)
Búa til hljóð
Villa kom upp.
Spilari
Vinn textann...
Sækja skrá (WAV)
Engin hljóðskrá tilbúin
Ábending
Best er að skrifa tölustafi með bókstöfum (t.d. "tuttugu" í stað "20") til að tryggja réttan framburð á íslensku.
// DOM Elements const apiKeyInput = document.getElementById('apiKeyInput'); const saveKeyBtn = document.getElementById('saveKeyBtn'); const textInput = document.getElementById('textInput'); const voiceSelect = document.getElementById('voiceSelect'); const generateBtn = document.getElementById('generateBtn'); const errorBox = document.getElementById('errorBox'); const errorText = document.getElementById('errorText'); const loadingIndicator = document.getElementById('loadingIndicator'); const playerContainer = document.getElementById('playerContainer'); const emptyState = document.getElementById('emptyState'); const audioPlayer = document.getElementById('audioPlayer'); const downloadLink = document.getElementById('downloadLink'); const charCount = document.getElementById('charCount'); // State let audioContext = null; // Load API Key on start const savedKey = localStorage.getItem('gemini_tts_api_key'); if (savedKey) { apiKeyInput.value = savedKey; } // Save Key Function saveKeyBtn.addEventListener('click', () => { const key = apiKeyInput.value.trim(); if (key) { localStorage.setItem('gemini_tts_api_key', key); alert('Lykill vistaður!'); } }); // Character Count textInput.addEventListener('input', () => { charCount.textContent = `${textInput.value.length} stafir`; }); // Main Generation Function generateBtn.addEventListener('click', async () => { const text = textInput.value.trim(); const apiKey = apiKeyInput.value.trim(); const voice = voiceSelect.value; // Validation if (!apiKey) { showError('Vinsamlegast sláðu inn API lykil.'); return; } if (!text) { showError('Vinsamlegast skrifaðu einhvern texta.'); return; } // UI Reset hideError(); playerContainer.classList.add('hidden'); emptyState.classList.add('hidden'); loadingIndicator.classList.remove('hidden'); generateBtn.disabled = true; generateBtn.classList.add('opacity-50', 'cursor-not-allowed'); try { // API Call const url = `https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash-preview-tts:generateContent?key=${apiKey}`; const payload = { contents: [{ parts: [{ text: text }] }], generationConfig: { responseModalities: ["AUDIO"], speechConfig: { voiceConfig: { prebuiltVoiceConfig: { voiceName: voice } } } } }; const response = await fetch(url, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload) }); if (!response.ok) { const errData = await response.json(); throw new Error(errData.error?.message || `Villa ${response.status}`); } const data = await response.json(); if (!data.candidates?.[0]?.content?.parts?.[0]?.inlineData) { throw new Error("Engin hljóðgögn bárust."); } // Process Audio const base64Audio = data.candidates[0].content.parts[0].inlineData.data; const audioBlob = base64ToBlob(base64Audio, 'audio/wav'); // Model output usually implies PCM/WAV container compatibility const audioUrl = URL.createObjectURL(audioBlob); // Setup Player audioPlayer.src = audioUrl; downloadLink.href = audioUrl; // Create filename with date const date = new Date().toISOString().slice(0,10); downloadLink.download = `lestur_${voice}_${date}.wav`; // Show UI loadingIndicator.classList.add('hidden'); playerContainer.classList.remove('hidden'); // Auto play audioPlayer.play().catch(e => console.log("Autoplay blocked usually, user must interact")); } catch (err) { console.error(err); loadingIndicator.classList.add('hidden'); showError(err.message); } finally { generateBtn.disabled = false; generateBtn.classList.remove('opacity-50', 'cursor-not-allowed'); } }); // Helpers function showError(msg) { errorBox.classList.remove('hidden'); errorText.textContent = msg; } function hideError() { errorBox.classList.add('hidden'); } function base64ToBlob(base64, mimeType) { const byteCharacters = atob(base64); const byteArrays = []; for (let offset = 0; offset < byteCharacters.length; offset += 512) { const slice = byteCharacters.slice(offset, offset + 512); const byteNumbers = new Array(slice.length); for (let i = 0; i < slice.length; i++) { byteNumbers[i] = slice.charCodeAt(i); } const byteArray = new Uint8Array(byteNumbers); byteArrays.push(byteArray); } return new Blob(byteArrays, { type: mimeType }); }
</style>
Texti í Tal - Vinnustöð
<a href="https://cdn.tailwindcss.com">https://cdn.tailwindcss.com</a>
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap');
body { font-family: 'Inter', sans-serif; }
/* Sérsniðið útlit fyrir hljóðspilarann */
audio::-webkit-media-controls-panel {
background-color: #f3f4f6;
}
<div class="max-w-4xl mx-auto">
<!-- Header -->
<div class="text-center mb-10">
<h1 class="text-3xl md:text-4xl font-bold text-slate-800 mb-2">Texti í Tal <span class="text-blue-600">Vinnustöð</span></h1>
<p class="text-slate-500">Búðu til hljóðskrár fyrir heimasíðuna þína með nýjustu gervigreind.</p>
</div>
<div class="grid grid-cols-1 lg:grid-cols-3 gap-6">
<!-- Vinstri dálkur: Stillingar og Inntak -->
<div class="lg:col-span-2 space-y-6">
<!-- API Lykill -->
<div class="bg-white p-6 rounded-xl shadow-sm border border-slate-200">
<i class="fas fa-key mr-1 text-yellow-500"></i> Google Gemini API Lykill
<div class="flex gap-2">
<button id="saveKeyBtn" class="bg-gray-100 hover:bg-gray-200 text-gray-600 px-4 py-2 rounded-lg text-sm font-medium transition" title="Vista í vafra">
Vista
</button>
</div>
<p class="text-xs text-gray-400 mt-2">Lykillinn er vistaður örugglega í vafranum þínum (Local Storage).</p>
</div>
<!-- Textasvæði -->
<div class="bg-white p-6 rounded-xl shadow-sm border border-slate-200">
<div class="flex justify-between items-center mb-3">
Texti til lestrar
<span class="text-xs text-gray-400" id="charCount">0 stafir</span>
</div>
<div class="mt-4 flex flex-wrap gap-4 items-center justify-between">
<!-- Raddval -->
<div class="flex items-center gap-3">
Rödd:
<div class="relative">
Fenrir (Djúp karlmannsrödd)
Aoede (Fágaðri kvenrödd)
Kore (Róleg kvenrödd)
Puck (Hress karlmannsrödd)
Charon (Dýpri karlmannsrödd)
<div class="pointer-events-none absolute inset-y-0 right-0 flex items-center px-2 text-slate-500">
<i class="fas fa-chevron-down text-xs"></i>
</div>
</div>
</div>
<button id="generateBtn" class="bg-blue-600 hover:bg-blue-700 text-white font-bold py-2.5 px-6 rounded-lg shadow-md hover:shadow-lg transform transition active:scale-95 flex items-center">
<i class="fas fa-wand-magic-sparkles mr-2"></i> Búa til hljóð
</button>
</div>
</div>
<!-- Villuskilaboð -->
<div id="errorBox" class="hidden bg-red-50 border border-red-200 text-red-700 px-4 py-3 rounded-lg text-sm flex items-start">
<i class="fas fa-exclamation-circle mt-0.5 mr-2"></i>
<span id="errorText">Villa kom upp.</span>
</div>
</div>
<!-- Hægri dálkur: Niðurstöður og Saga -->
<div class="lg:col-span-1 space-y-6">
<!-- Spilari -->
<div class="bg-white p-6 rounded-xl shadow-lg border border-blue-100 relative overflow-hidden">
<div class="absolute top-0 left-0 w-full h-1 bg-gradient-to-r from-blue-400 to-indigo-500"></div>
<h3 class="text-lg font-bold text-slate-800 mb-4">Spilari</h3>
<div id="loadingIndicator" class="hidden flex flex-col items-center justify-center py-8 text-blue-600">
<i class="fas fa-circle-notch fa-spin text-3xl mb-3"></i>
<span class="text-sm font-medium animate-pulse">Vinn textann...</span>
</div>
<div id="playerContainer" class="hidden text-center">
<div class="bg-slate-50 rounded-full p-4 mb-4 inline-block shadow-inner">
<i class="fas fa-music text-2xl text-slate-400"></i>
</div>
<audio id="audioPlayer" controls="" class="w-full mb-4"></audio>
<a id="downloadLink" href="#" class="block w-full bg-green-600 hover:bg-green-700 text-white font-bold py-2 px-4 rounded-lg shadow transition text-center">
<i class="fas fa-download mr-2"></i> Sækja skrá (WAV)
</a>
</div>
<div id="emptyState" class="text-center py-8 text-gray-400">
<i class="fas fa-headphones text-4xl mb-2 opacity-20"></i>
<p class="text-sm">Engin hljóðskrá tilbúin</p>
</div>
</div>
<!-- Leiðbeiningar -->
<div class="bg-blue-50 p-6 rounded-xl border border-blue-100">
<h4 class="font-bold text-blue-800 mb-2 text-sm"><i class="fas fa-info-circle mr-1"></i> Ábending</h4>
<p class="text-xs text-blue-700 leading-relaxed">
Best er að skrifa tölustafi með bókstöfum (t.d. "tuttugu" í stað "20") til að tryggja réttan framburð á íslensku.
</p>
</div>
</div>
</div>
</div>
// DOM Elements
const apiKeyInput = document.getElementById('apiKeyInput');
const saveKeyBtn = document.getElementById('saveKeyBtn');
const textInput = document.getElementById('textInput');
const voiceSelect = document.getElementById('voiceSelect');
const generateBtn = document.getElementById('generateBtn');
const errorBox = document.getElementById('errorBox');
const errorText = document.getElementById('errorText');
const loadingIndicator = document.getElementById('loadingIndicator');
const playerContainer = document.getElementById('playerContainer');
const emptyState = document.getElementById('emptyState');
const audioPlayer = document.getElementById('audioPlayer');
const downloadLink = document.getElementById('downloadLink');
const charCount = document.getElementById('charCount');
// State
let audioContext = null;
// Load API Key on start
const savedKey = localStorage.getItem('gemini_tts_api_key');
if (savedKey) {
apiKeyInput.value = savedKey;
}
// Save Key Function
saveKeyBtn.addEventListener('click', () => {
const key = apiKeyInput.value.trim();
if (key) {
localStorage.setItem('gemini_tts_api_key', key);
alert('Lykill vistaður!');
}
});
// Character Count
textInput.addEventListener('input', () => {
charCount.textContent = `${textInput.value.length} stafir`;
});
// Main Generation Function
generateBtn.addEventListener('click', async () => {
const text = textInput.value.trim();
const apiKey = apiKeyInput.value.trim();
const voice = voiceSelect.value;
// Validation
if (!apiKey) {
showError('Vinsamlegast sláðu inn API lykil.');
return;
}
if (!text) {
showError('Vinsamlegast skrifaðu einhvern texta.');
return;
}
// UI Reset
hideError();
playerContainer.classList.add('hidden');
emptyState.classList.add('hidden');
loadingIndicator.classList.remove('hidden');
generateBtn.disabled = true;
generateBtn.classList.add('opacity-50', 'cursor-not-allowed');
try {
// API Call
const url = `https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash-preview-tts:generateContent?key=${apiKey}`;
const payload = {
contents: [{ parts: [{ text: text }] }],
generationConfig: {
responseModalities: ["AUDIO"],
speechConfig: {
voiceConfig: {
prebuiltVoiceConfig: {
voiceName: voice
}
}
}
}
};
const response = await fetch(url, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload)
});
if (!response.ok) {
const errData = await response.json();
throw new Error(errData.error?.message || `Villa ${response.status}`);
}
const data = await response.json();
if (!data.candidates?.[0]?.content?.parts?.[0]?.inlineData) {
throw new Error("Engin hljóðgögn bárust.");
}
// Process Audio
const base64Audio = data.candidates[0].content.parts[0].inlineData.data;
const audioBlob = base64ToBlob(base64Audio, 'audio/wav'); // Model output usually implies PCM/WAV container compatibility
const audioUrl = URL.createObjectURL(audioBlob);
// Setup Player
audioPlayer.src = audioUrl;
downloadLink.href = audioUrl;
// Create filename with date
const date = new Date().toISOString().slice(0,10);
downloadLink.download = `lestur_${voice}_${date}.wav`;
// Show UI
loadingIndicator.classList.add('hidden');
playerContainer.classList.remove('hidden');
// Auto play
audioPlayer.play().catch(e => console.log("Autoplay blocked usually, user must interact"));
} catch (err) {
console.error(err);
loadingIndicator.classList.add('hidden');
showError(err.message);
} finally {
generateBtn.disabled = false;
generateBtn.classList.remove('opacity-50', 'cursor-not-allowed');
}
});
// Helpers
function showError(msg) {
errorBox.classList.remove('hidden');
errorText.textContent = msg;
}
function hideError() {
errorBox.classList.add('hidden');
}
function base64ToBlob(base64, mimeType) {
const byteCharacters = atob(base64);
const byteArrays = [];
for (let offset = 0; offset < byteCharacters.length; offset += 512) {
const slice = byteCharacters.slice(offset, offset + 512);
const byteNumbers = new Array(slice.length);
for (let i = 0; i < slice.length; i++) {
byteNumbers[i] = slice.charCodeAt(i);
}
const byteArray = new Uint8Array(byteNumbers);
byteArrays.push(byteArray);
}
return new Blob(byteArrays, { type: mimeType });
}