Kokoro: chunk generation, add pre-process func #3412
| @@ -1,5 +1,5 @@ | ||
| 1 | 1 | import { debounce_timeout } from '../../constants.js'; |
| 2 | 2 | import { debounceAsync, splitRecursive } from '../../utils.js'; |
| 3 | 3 | import { getPreviewString, saveTtsProviderSettings } from './index.js'; |
| 4 | 4 | |
| 5 | 5 | export class KokoroTtsProvider { |
| @@ -52,6 +52,17 @@ export class KokoroTtsProvider { | ||
| 52 | 52 | this.initTtsDebounced = debounceAsync(this.initializeWorker.bind(this), debounce_timeout.relaxed); |
| 53 | 53 | } |
| 54 | 54 | |
| 55 | + /** | |
| 56 | + * Perform any text processing before passing to TTS engine. | |
| 57 | + * @param {string} text Input text | |
| 58 | + * @returns {string} Processed text | |
| 59 | + */ | |
| 60 | + processText(text) { | |
| 61 | + // TILDE! | |
| 62 | + text = text.replace(/~/g, '.'); | |
| 63 | + return text; | |
| 64 | + } | |
| 65 | + | |
| 55 | 66 | async loadSettings(settings) { |
| 56 | 67 | if (settings.modelId !== undefined) this.settings.modelId = settings.modelId; |
| 57 | 68 | if (settings.dtype !== undefined) this.settings.dtype = settings.dtype; |
| @@ -258,13 +269,17 @@ export class KokoroTtsProvider { | ||
| 258 | 269 | |
| 259 | 270 | const voice = this.getVoice(voiceId); |
| 260 | 271 | const previewText = getPreviewString(voice.lang); |
| 261 | 272 | for await (const response = awaitof this.generateTts(previewText, voiceId);) { |
| 262 | 273 | const audio = await response.blob(); |
| 263 | 274 | const url = URL.createObjectURL(audio); |
| 275 | + await new Promise(resolve => { | |
| 264 | 276 | const audioElement = new Audio(); |
| 265 | 277 | audioElement.src = url; |
| 266 | 278 | audioElement.play(); |
| 267 | 279 | audioElement.onended = () => URL.revokeObjectURLresolve(url); |
| 280 | + }); | |
| 281 | + URL.revokeObjectURL(url); | |
| 282 | + } | |
| 268 | 283 | } |
| 269 | 284 | |
| 270 | 285 | getVoiceDisplayName(voiceId) { |
| @@ -282,7 +297,13 @@ export class KokoroTtsProvider { | ||
| 282 | 297 | }; |
| 283 | 298 | } |
| 284 | 299 | |
| 285 | - async generateTts(text, voiceId) { | |
| 300 | + /** | |
| 301 | + * Generate TTS audio for the given text using the specified voice. | |
| 302 | + * @param {string} text Text to generate | |
| 303 | + * @param {string} voiceId Voice ID | |
| 304 | + * @returns {AsyncGenerator<Response>} Audio response generator | |
| 305 | + */ | |
| 306 | + async* generateTts(text, voiceId) { | |
| 286 | 307 | if (!this.ready || !this.worker) { |
| 287 | 308 | console.log('TTS not ready, initializing...'); |
| 288 | 309 | await this.initializeWorker(); |
| @@ -299,7 +320,11 @@ export class KokoroTtsProvider { | ||
| 299 | 320 | const voice = this.getVoice(voiceId); |
| 300 | 321 | const requestId = this.nextRequestId++; |
| 301 | 322 | |
| 302 | - return new Promise((resolve, reject) => { | |
| 323 | + const chunkSize = 400; | |
| 324 | + const chunks = splitRecursive(text, chunkSize, ['\n\n', '\n', '.', '?', '!', ',', ' ', '']); | |
| 325 | + | |
| 326 | + for (const chunk of chunks) { | |
| 327 | + yield await new Promise((resolve, reject) => { | |
| 303 | 328 | // Store the promise callbacks |
| 304 | 329 | this.pendingRequests.set(requestId, { resolve, reject }); |
| 305 | 330 | |
| @@ -307,7 +332,7 @@ export class KokoroTtsProvider { | ||
| 307 | 332 | this.worker.postMessage({ |
| 308 | 333 | action: 'generateTts', |
| 309 | 334 | data: { |
| 310 | - text, | |
| 335 | + text: chunk, | |
| 311 | 336 | voice: voice.voice_id, |
| 312 | 337 | speakingRate: this.settings.speakingRate || 1.0, |
| 313 | 338 | requestId, |
| @@ -315,6 +340,7 @@ export class KokoroTtsProvider { | ||
| 315 | 340 | }); |
| 316 | 341 | }); |
| 317 | 342 | } |
| 343 | + } | |
| 318 | 344 | |
| 319 | 345 | dispose() { |
| 320 | 346 | // Clean up the worker when the provider is disposed |
| @@ -1015,6 +1015,10 @@ export function splitRecursive(input, length, delimiters = ['\n\n', '\n', ' ', ' | ||
| 1015 | 1015 | return result; |
| 1016 | 1016 | } |
| 1017 | 1017 | |
| 1018 | +export function splitSentences(input, length) { | |
| 1019 | + var pattRegex = new RegExp(`^[\\s\\S]{${Math.floor(length / 2)},${length}}[.!?,]{1}|^[\\s\\S]{1,${length}}$|^[\\s\\S]{1,${length}}`); | |
| 1020 | +} | |
| 1021 | + | |
| 1018 | 1022 | /** |
| 1019 | 1023 | * Checks if a string is a valid data URL. |
| 1020 | 1024 | * @param {string} str The string to check. |