openreader/src/lib/shared/kokoro.ts

63 lines
1.9 KiB
TypeScript

/**
* Kokoro Utilities
*
* Utilities for handling Kokoro multi-voice syntax.
*/
/**
* Parses a Kokoro voice string into individual voice names
* Strips weights like "af_heart(0.5)" -> "af_heart"
*
* @param voiceString - Voice string to parse (e.g., "af_heart(0.5)+bf_emma(0.5)")
* @returns Array of voice names without weights
*/
export const parseKokoroVoiceNames = (voiceString: string): string[] =>
voiceString
.split('+')
.map(s => s.trim())
.filter(Boolean)
.map(s => s.replace(/\([^)]*\)/g, '').trim());
/**
* Builds a Kokoro voice string from an array of voice names
* Automatically calculates equal weights for multiple voices
*
* @param names - Array of voice names
* @returns Formatted voice string with weights or single voice name
*/
export const buildKokoroVoiceString = (names: string[]): string => {
const n = names.length;
if (n === 0) return '';
if (n === 1) return names[0];
const weight = 1 / n;
const weightString = weight.toFixed(2).replace(/0+$/, '').replace(/\.$/, '');
return names.map(name => `${name}(${weightString})`).join('+');
};
/**
* Checks if a model name is a Kokoro model
*
* @param modelName - TTS model name
* @returns True if the model is a Kokoro model
*/
export const isKokoroModel = (modelName: string | undefined): boolean => {
return (modelName || '').toLowerCase().includes('kokoro');
};
/**
* Determines the maximum number of voices allowed for a provider/model combination
*
* @param provider - TTS provider name
* @param model - TTS model name
* @returns Maximum number of voices (Infinity for unlimited)
*/
export const getMaxVoicesForProvider = (provider: string, model: string): number => {
if (!isKokoroModel(model)) return 1;
// Deepinfra Kokoro does not support multiple voices
if (provider === 'deepinfra') return 1;
// Other providers with Kokoro support unlimited voices
return Infinity;
};