felger e23f3a6314
feature: 'kokoro-js' supports TTS #3412 (#3656)
* feature: 'kokoro-js' supports TTS #3412

* Linting, add credits for kokoro library

* Fix voice preview

* Fix display languages on previews

* Fix settings restoration. Debounce model init on settings change

* Fix engine sorting

* Move TTS processing to a web worker. Remove unused gain setting

* Speaking rate fix

* Update status when recreating a worker

* Pass voices list from TTS engine

* Call dispose function on provider change

* Extend worker init timeout to 10 minutes

---------

Co-authored-by: ryan <1014670860@qq.com>
Co-authored-by: Cohee <18619528+Cohee1207@users.noreply.github.com>
2025-03-10 22:54:54 +02:00

114 lines
3.1 KiB
JavaScript

// kokoro-worker.js
/** @type {import('./lib/kokoro.web.js').KokoroTTS} */
let tts = null;
/** @type {boolean} */
let ready = false;
/** @type {string[]} */
let voices = [];
// Handle messages from the main thread
self.onmessage = async function(e) {
const { action, data } = e.data;
switch (action) {
case 'initialize':
try {
const result = await initializeTts(data);
self.postMessage({
action: 'initialized',
success: result,
voices,
});
} catch (error) {
self.postMessage({
action: 'initialized',
success: false,
error: error.message,
});
}
break;
case 'generateTts':
try {
const audioBlob = await generateTts(data.text, data.voice, data.speakingRate);
const blobUrl = URL.createObjectURL(audioBlob);
self.postMessage({
action: 'generatedTts',
success: true,
blobUrl,
requestId: data.requestId,
});
} catch (error) {
self.postMessage({
action: 'generatedTts',
success: false,
error: error.message,
requestId: data.requestId,
});
}
break;
case 'checkReady':
self.postMessage({ action: 'readyStatus', ready });
break;
}
};
// Initialize the TTS engine
async function initializeTts(settings) {
try {
const { KokoroTTS } = await import('./lib/kokoro.web.js');
console.log('Worker: Initializing Kokoro TTS with settings:', {
modelId: settings.modelId,
dtype: settings.dtype,
device: settings.device,
});
// Create TTS instance
tts = await KokoroTTS.from_pretrained(settings.modelId, {
dtype: settings.dtype,
device: settings.device,
});
// Get available voices
voices = Object.keys(tts.voices);
// Check if generate method exists
if (typeof tts.generate !== 'function') {
throw new Error('TTS instance does not have generate method');
}
console.log('Worker: TTS initialized successfully');
ready = true;
return true;
} catch (error) {
console.error('Worker: Kokoro TTS initialization failed:', error);
ready = false;
throw error;
}
}
// Generate TTS audio
async function generateTts(text, voiceId, speakingRate) {
if (!ready || !tts) {
throw new Error('TTS engine not initialized');
}
if (text.trim().length === 0) {
throw new Error('Empty text');
}
try {
const audio = await tts.generate(text, {
voice: voiceId,
speed: speakingRate || 1.0,
});
return audio.toBlob();
} catch (error) {
console.error('Worker: TTS generation failed:', error);
throw error;
}
}