update checkReady. add voiceMap ui select
This commit is contained in:
parent
56fcf1cbb8
commit
5b43fe25e8
|
@ -42,8 +42,9 @@ const languageLabels = {
|
|||
|
||||
function throwIfModuleMissing() {
|
||||
if (!modules.includes('coqui-tts')) {
|
||||
toastr.error(`Add coqui-tts to enable-modules and restart the Extras API.`, "Coqui TTS module not loaded.", { timeOut: 10000, extendedTimeOut: 20000, preventDuplicates: true });
|
||||
throw new Error(DEBUG_PREFIX, `Coqui TTS module not loaded.`);
|
||||
const message = `Coqui TTS module not loaded. Add coqui-tts to enable-modules and restart the Extras API.`
|
||||
// toastr.error(message, { timeOut: 10000, extendedTimeOut: 20000, preventDuplicates: true });
|
||||
throw new Error(DEBUG_PREFIX, message);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -84,7 +85,6 @@ class CoquiTtsProvider {
|
|||
//#############################//
|
||||
|
||||
settings
|
||||
ready
|
||||
|
||||
defaultSettings = {
|
||||
voiceMap: "",
|
||||
|
@ -148,7 +148,6 @@ class CoquiTtsProvider {
|
|||
loadSettings(settings) {
|
||||
// Only accept keys defined in defaultSettings
|
||||
this.settings = this.defaultSettings
|
||||
this.ready = false
|
||||
|
||||
for (const key in settings) {
|
||||
if (key in this.settings) {
|
||||
|
@ -231,17 +230,8 @@ class CoquiTtsProvider {
|
|||
|
||||
// Perform a simple readiness check by trying to fetch voiceIds
|
||||
async checkReady(){
|
||||
try {
|
||||
if (!modules.includes('coqui-tts')){
|
||||
this.ready = false
|
||||
return
|
||||
}
|
||||
await this.fetchTtsVoiceIds()
|
||||
this.ready = true
|
||||
|
||||
} catch {
|
||||
this.ready = false
|
||||
}
|
||||
throwIfModuleMissing()
|
||||
await this.fetchTtsVoiceIds()
|
||||
}
|
||||
|
||||
updateVoiceMap() {
|
||||
|
|
|
@ -11,7 +11,6 @@ class EdgeTtsProvider {
|
|||
//########//
|
||||
|
||||
settings
|
||||
ready = false
|
||||
voices = []
|
||||
separator = ' . '
|
||||
audioElement = document.createElement('audio')
|
||||
|
@ -61,17 +60,8 @@ class EdgeTtsProvider {
|
|||
|
||||
// Perform a simple readiness check by trying to fetch voiceIds
|
||||
async checkReady(){
|
||||
try {
|
||||
if (!modules.includes('edge-tts')){
|
||||
this.ready = false
|
||||
return
|
||||
}
|
||||
await this.fetchTtsVoiceIds()
|
||||
this.ready = true
|
||||
|
||||
} catch {
|
||||
this.ready = false
|
||||
}
|
||||
throwIfModuleMissing()
|
||||
await this.fetchTtsVoiceIds()
|
||||
}
|
||||
|
||||
async onApplyClick() {
|
||||
|
@ -162,8 +152,9 @@ class EdgeTtsProvider {
|
|||
}
|
||||
function throwIfModuleMissing() {
|
||||
if (!modules.includes('edge-tts')) {
|
||||
toastr.error(`Edge TTS module not loaded. Add edge-tts to enable-modules and restart the Extras API.`)
|
||||
throw new Error(`Edge TTS module not loaded.`)
|
||||
const message = `Edge TTS module not loaded. Add edge-tts to enable-modules and restart the Extras API.`
|
||||
// toastr.error(message)
|
||||
throw new Error(message)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -6,7 +6,6 @@ class ElevenLabsTtsProvider {
|
|||
//########//
|
||||
|
||||
settings
|
||||
ready = false
|
||||
voices = []
|
||||
separator = ' ... ... ... '
|
||||
|
||||
|
@ -74,13 +73,7 @@ class ElevenLabsTtsProvider {
|
|||
|
||||
// Perform a simple readiness check by trying to fetch voiceIds
|
||||
async checkReady(){
|
||||
try {
|
||||
await this.fetchTtsVoiceIds()
|
||||
this.ready = true
|
||||
|
||||
} catch {
|
||||
this.ready = false
|
||||
}
|
||||
await this.fetchTtsVoiceIds()
|
||||
}
|
||||
|
||||
async onApplyClick() {
|
||||
|
|
|
@ -13,6 +13,7 @@ export { talkingAnimation };
|
|||
|
||||
const UPDATE_INTERVAL = 1000
|
||||
|
||||
let voiceMapEntries = []
|
||||
let voiceMap = {} // {charName:voiceid, charName2:voiceid2}
|
||||
let audioControl
|
||||
let storedvalue = false;
|
||||
|
@ -224,8 +225,8 @@ function debugTtsPlayback() {
|
|||
console.log(JSON.stringify(
|
||||
{
|
||||
"ttsProviderName": ttsProviderName,
|
||||
"voiceMap": voiceMap,
|
||||
"currentMessageNumber": currentMessageNumber,
|
||||
"isWorkerBusy": isWorkerBusy,
|
||||
"audioPaused": audioPaused,
|
||||
"audioJobQueue": audioJobQueue,
|
||||
"currentAudioJob": currentAudioJob,
|
||||
|
@ -486,6 +487,7 @@ function loadSettings() {
|
|||
if (Object.keys(extension_settings.tts).length === 0) {
|
||||
Object.assign(extension_settings.tts, defaultSettings)
|
||||
}
|
||||
$('#tts_provider').val(extension_settings.tts.currentProvider)
|
||||
$('#tts_enabled').prop(
|
||||
'checked',
|
||||
extension_settings.tts.enabled
|
||||
|
@ -513,59 +515,17 @@ function setTtsStatus(status, success) {
|
|||
}
|
||||
}
|
||||
|
||||
function parseVoiceMap(voiceMapString) {
|
||||
let parsedVoiceMap = {}
|
||||
for (const [charName, voiceId] of voiceMapString
|
||||
.split(',')
|
||||
.map(s => s.split(':'))) {
|
||||
if (charName && voiceId) {
|
||||
parsedVoiceMap[charName.trim()] = voiceId.trim()
|
||||
}
|
||||
}
|
||||
return parsedVoiceMap
|
||||
}
|
||||
|
||||
async function voicemapIsValid(parsedVoiceMap) {
|
||||
let valid = true
|
||||
for (const characterName in parsedVoiceMap) {
|
||||
const parsedVoiceName = parsedVoiceMap[characterName]
|
||||
try {
|
||||
await ttsProvider.getVoice(parsedVoiceName)
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
valid = false
|
||||
}
|
||||
}
|
||||
return valid
|
||||
}
|
||||
|
||||
async function updateVoiceMap() {
|
||||
let isValidResult = false
|
||||
|
||||
const value = $('#tts_voice_map').val()
|
||||
const parsedVoiceMap = parseVoiceMap(value)
|
||||
|
||||
isValidResult = await voicemapIsValid(parsedVoiceMap)
|
||||
if (isValidResult) {
|
||||
ttsProvider.settings.voiceMap = String(value)
|
||||
// console.debug(`ttsProvider.voiceMap: ${ttsProvider.settings.voiceMap}`)
|
||||
voiceMap = parsedVoiceMap
|
||||
console.debug(`Saved new voiceMap: ${value}`)
|
||||
saveSettingsDebounced()
|
||||
} else {
|
||||
throw 'Voice map is invalid, check console for errors'
|
||||
}
|
||||
}
|
||||
|
||||
function onApplyClick() {
|
||||
Promise.all([
|
||||
ttsProvider.onApplyClick(),
|
||||
updateVoiceMap()
|
||||
// updateVoiceMap()
|
||||
]).then(() => {
|
||||
extension_settings.tts[ttsProviderName] = ttsProvider.settings
|
||||
saveSettingsDebounced()
|
||||
setTtsStatus('Successfully applied settings', true)
|
||||
console.info(`Saved settings ${ttsProviderName} ${JSON.stringify(ttsProvider.settings)}`)
|
||||
initVoiceMap()
|
||||
updateVoiceMap()
|
||||
}).catch(error => {
|
||||
console.error(error)
|
||||
setTtsStatus(error, false)
|
||||
|
@ -608,13 +568,14 @@ function onNarrateTranslatedOnlyClick() {
|
|||
// TTS Provider //
|
||||
//##############//
|
||||
|
||||
function loadTtsProvider(provider) {
|
||||
async function loadTtsProvider(provider) {
|
||||
//Clear the current config and add new config
|
||||
$("#tts_provider_settings").html("")
|
||||
|
||||
if (!provider) {
|
||||
provider
|
||||
return
|
||||
}
|
||||
|
||||
// Init provider references
|
||||
extension_settings.tts.currentProvider = provider
|
||||
ttsProviderName = provider
|
||||
|
@ -626,27 +587,17 @@ function loadTtsProvider(provider) {
|
|||
console.warn(`Provider ${ttsProviderName} not in Extension Settings, initiatilizing provider in settings`)
|
||||
extension_settings.tts[ttsProviderName] = {}
|
||||
}
|
||||
|
||||
// Load voicemap settings
|
||||
let voiceMapFromSettings
|
||||
if ("voiceMap" in extension_settings.tts[ttsProviderName]) {
|
||||
voiceMapFromSettings = extension_settings.tts[ttsProviderName].voiceMap
|
||||
voiceMap = parseVoiceMap(voiceMapFromSettings)
|
||||
} else {
|
||||
voiceMapFromSettings = ""
|
||||
voiceMap = {}
|
||||
}
|
||||
$('#tts_voice_map').val(voiceMapFromSettings)
|
||||
$('#tts_provider').val(ttsProviderName)
|
||||
|
||||
ttsProvider.loadSettings(extension_settings.tts[ttsProviderName])
|
||||
initVoiceMap()
|
||||
}
|
||||
|
||||
function onTtsProviderChange() {
|
||||
const ttsProviderSelection = $('#tts_provider').val()
|
||||
extension_settings.tts.currentProvider = ttsProviderSelection
|
||||
loadTtsProvider(ttsProviderSelection)
|
||||
}
|
||||
|
||||
// Ensure that TTS provider settings are saved to extension settings.
|
||||
function onTtsProviderSettingsInput() {
|
||||
ttsProvider.onSettingsChange()
|
||||
|
||||
|
@ -658,6 +609,191 @@ function onTtsProviderSettingsInput() {
|
|||
}
|
||||
|
||||
|
||||
//###################//
|
||||
// voiceMap Handling //
|
||||
//###################//
|
||||
|
||||
async function onChatChanged() {
|
||||
await resetTtsPlayback()
|
||||
await initVoiceMap()
|
||||
}
|
||||
|
||||
function getCharacters(){
|
||||
const context = getContext()
|
||||
let characters = []
|
||||
if (context.groupId === null){
|
||||
// Single char chat
|
||||
characters.push(context.name1)
|
||||
characters.push(context.name2)
|
||||
} else {
|
||||
// Group chat
|
||||
characters.push(context.name1)
|
||||
const group = context.groups.find(group => context.groupId == group.id)
|
||||
for (let member of group.members) {
|
||||
// Remove suffix
|
||||
if (member.endsWith('.png')){
|
||||
member = member.slice(0, -4)
|
||||
}
|
||||
characters.push(member)
|
||||
}
|
||||
}
|
||||
return characters
|
||||
}
|
||||
|
||||
function sanitizeId(input) {
|
||||
// Remove any non-alphanumeric characters except underscore (_) and hyphen (-)
|
||||
let sanitized = input.replace(/[^a-zA-Z0-9-_]/g, '');
|
||||
|
||||
// Ensure first character is always a letter
|
||||
if (!/^[a-zA-Z]/.test(sanitized)) {
|
||||
sanitized = 'element_' + sanitized;
|
||||
}
|
||||
|
||||
return sanitized;
|
||||
}
|
||||
|
||||
function parseVoiceMap(voiceMapString) {
|
||||
let parsedVoiceMap = {}
|
||||
for (const [charName, voiceId] of voiceMapString
|
||||
.split(',')
|
||||
.map(s => s.split(':'))) {
|
||||
if (charName && voiceId) {
|
||||
parsedVoiceMap[charName.trim()] = voiceId.trim()
|
||||
}
|
||||
}
|
||||
return parsedVoiceMap
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Apply voiceMap based on current voiceMapEntries
|
||||
*/
|
||||
function updateVoiceMap() {
|
||||
const tempVoiceMap = {}
|
||||
for (const voice of voiceMapEntries){
|
||||
if (voice.voiceId === null){
|
||||
continue
|
||||
}
|
||||
tempVoiceMap[voice.name] = voice.voiceId
|
||||
}
|
||||
if (Object.keys(tempVoiceMap).length !== 0){
|
||||
voiceMap = tempVoiceMap
|
||||
console.log(`Voicemap updated to ${JSON.stringify(voiceMap)}`)
|
||||
}
|
||||
extension_settings.tts[ttsProviderName].voiceMap = voiceMap
|
||||
saveSettingsDebounced()
|
||||
}
|
||||
|
||||
class VoiceMapEntry {
|
||||
name
|
||||
voiceId
|
||||
selectElement
|
||||
constructor (name, voiceId='disabled') {
|
||||
this.name = name
|
||||
this.voiceId = voiceId
|
||||
this.selectElement = null
|
||||
}
|
||||
|
||||
addUI(voiceIds){
|
||||
let sanitizedName = sanitizeId(this.name)
|
||||
let template = `
|
||||
<div class='tts_voicemap_block_char flex-container flexGap5'>
|
||||
<span id='tts_voicemap_char_${sanitizedName}'>${this.name}</span>
|
||||
<select id='tts_voicemap_char_${sanitizedName}_voice'>
|
||||
<option>disabled</option>
|
||||
</select>
|
||||
</div>
|
||||
`
|
||||
$('#tts_voicemap_block').append(template)
|
||||
|
||||
// Populate voice ID select list
|
||||
for (const voiceId of voiceIds){
|
||||
const option = document.createElement('option');
|
||||
option.innerText = voiceId.name;
|
||||
option.value = voiceId.name;
|
||||
$(`#tts_voicemap_char_${sanitizedName}_voice`).append(option)
|
||||
}
|
||||
|
||||
this.selectElement = $(`#tts_voicemap_char_${sanitizedName}_voice`)
|
||||
this.selectElement.on('change', args => this.onSelectChange(args))
|
||||
this.selectElement.val(this.voiceId)
|
||||
}
|
||||
|
||||
onSelectChange(args) {
|
||||
this.voiceId = this.selectElement.find(':selected').val()
|
||||
updateVoiceMap()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Init voiceMapEntries for character select list. Should only be called when character/chat is changed.
|
||||
*
|
||||
*/
|
||||
async function initVoiceMap(){
|
||||
// Clear existing voiceMap state
|
||||
$('#tts_voicemap_block').empty()
|
||||
voiceMapEntries = []
|
||||
|
||||
// Gate initialization if not enabled or TTS Provider not ready. Prevents error popups.
|
||||
const enabled = $('#tts_enabled').is(':checked')
|
||||
if (!enabled){
|
||||
return
|
||||
}
|
||||
|
||||
// Keep errors inside extension UI rather than toastr. Toastr errors for TTS are annoying.
|
||||
try {
|
||||
await ttsProvider.checkReady()
|
||||
} catch (error) {
|
||||
const message = `TTS Provider not ready. ${error}`
|
||||
setTtsStatus(message, false)
|
||||
return
|
||||
}
|
||||
|
||||
setTtsStatus("TTS Provider Loaded", true)
|
||||
|
||||
// Get characters in current chat
|
||||
const characters = getCharacters()
|
||||
|
||||
// Get saved voicemap from provider settings, handling new and old representations
|
||||
let voiceMapFromSettings = {}
|
||||
if ("voiceMap" in extension_settings.tts[ttsProviderName]) {
|
||||
// Handle previous representation
|
||||
if (typeof extension_settings.tts[ttsProviderName].voiceMap === "string"){
|
||||
voiceMapFromSettings = parseVoiceMap(extension_settings.tts[ttsProviderName].voiceMap)
|
||||
// Handle new representation
|
||||
} else if (typeof extension_settings.tts[ttsProviderName].voiceMap === "object"){
|
||||
voiceMapFromSettings = extension_settings.tts[ttsProviderName].voiceMap
|
||||
}
|
||||
}
|
||||
|
||||
// Get voiceIds from provider
|
||||
let voiceIdsFromProvider
|
||||
try {
|
||||
voiceIdsFromProvider = await ttsProvider.fetchTtsVoiceIds()
|
||||
}
|
||||
catch {
|
||||
toastr.error("TTS Provider failed to return voice ids.")
|
||||
}
|
||||
|
||||
// Build UI using VoiceMapEntry objects
|
||||
for (const character of characters){
|
||||
if (character === "SillyTavern System"){
|
||||
continue
|
||||
}
|
||||
// Check provider settings for voiceIds
|
||||
let voiceId
|
||||
if (character in voiceMapFromSettings){
|
||||
voiceId = voiceMapFromSettings[character]
|
||||
} else {
|
||||
voiceId = 'disabled'
|
||||
}
|
||||
const voiceMapEntry = new VoiceMapEntry(character, voiceId)
|
||||
voiceMapEntry.addUI(voiceIdsFromProvider)
|
||||
voiceMapEntries.push(voiceMapEntry)
|
||||
}
|
||||
updateVoiceMap()
|
||||
}
|
||||
|
||||
$(document).ready(function () {
|
||||
function addExtensionControls() {
|
||||
|
@ -669,6 +805,8 @@ $(document).ready(function () {
|
|||
<div class="inline-drawer-icon fa-solid fa-circle-chevron-down down"></div>
|
||||
</div>
|
||||
<div class="inline-drawer-content">
|
||||
<div id="tts_status">
|
||||
</div>
|
||||
<div>
|
||||
<span>Select TTS Provider</span> </br>
|
||||
<select id="tts_provider">
|
||||
|
@ -696,16 +834,13 @@ $(document).ready(function () {
|
|||
<small>Narrate only the translated text</small>
|
||||
</label>
|
||||
</div>
|
||||
<label>Voice Map</label>
|
||||
<textarea id="tts_voice_map" type="text" class="text_pole textarea_compact" rows="4"
|
||||
placeholder="Enter comma separated map of charName:ttsName. Example: \nAqua:Bella,\nYou:Josh,"></textarea>
|
||||
|
||||
<div id="tts_status">
|
||||
<div id="tts_voicemap_block">
|
||||
</div>
|
||||
<hr>
|
||||
<form id="tts_provider_settings" class="inline-drawer-content">
|
||||
</form>
|
||||
<div class="tts_buttons">
|
||||
<input id="tts_apply" class="menu_button" type="submit" value="Apply" />
|
||||
<input id="tts_apply" class="menu_button" type="submit" value="Reload / Apply" />
|
||||
<input id="tts_voices" class="menu_button" type="submit" value="Available voices" />
|
||||
</div>
|
||||
</div>
|
||||
|
@ -735,4 +870,6 @@ $(document).ready(function () {
|
|||
const wrapper = new ModuleWorkerWrapper(moduleWorker);
|
||||
setInterval(wrapper.update.bind(wrapper), UPDATE_INTERVAL) // Init depends on all the things
|
||||
eventSource.on(event_types.MESSAGE_SWIPED, resetTtsPlayback);
|
||||
eventSource.on(event_types.CHAT_CHANGED, onChatChanged)
|
||||
eventSource.on(event_types.GROUP_UPDATED, onChatChanged)
|
||||
})
|
||||
|
|
|
@ -9,7 +9,6 @@ class NovelTtsProvider {
|
|||
//########//
|
||||
|
||||
settings
|
||||
ready = false
|
||||
voices = []
|
||||
separator = ' . '
|
||||
audioElement = document.createElement('audio')
|
||||
|
@ -95,13 +94,7 @@ class NovelTtsProvider {
|
|||
// Perform a simple readiness check by trying to fetch voiceIds
|
||||
// Doesnt really do much for Novel, not seeing a good way to test this at the moment.
|
||||
async checkReady(){
|
||||
try {
|
||||
await this.fetchTtsVoiceIds()
|
||||
this.ready = true
|
||||
|
||||
} catch {
|
||||
this.ready = false
|
||||
}
|
||||
await this.fetchTtsVoiceIds()
|
||||
}
|
||||
|
||||
async onApplyClick() {
|
||||
|
|
|
@ -69,17 +69,7 @@ class SileroTtsProvider {
|
|||
|
||||
// Perform a simple readiness check by trying to fetch voiceIds
|
||||
async checkReady(){
|
||||
try {
|
||||
if (!modules.includes('silero-tts')){
|
||||
this.ready = false
|
||||
return
|
||||
}
|
||||
await this.fetchTtsVoiceIds()
|
||||
this.ready = true
|
||||
|
||||
} catch {
|
||||
this.ready = false
|
||||
}
|
||||
await this.fetchTtsVoiceIds()
|
||||
}
|
||||
|
||||
async onApplyClick() {
|
||||
|
|
|
@ -50,4 +50,20 @@
|
|||
|
||||
.voice_preview .fa-play {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.tts-button {
|
||||
margin: 0;
|
||||
outline: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
transition: 0.3s;
|
||||
opacity: 0.7;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
}
|
||||
|
||||
.tts-button:hover {
|
||||
opacity: 1;
|
||||
}
|
|
@ -146,19 +146,12 @@ class SystemTtsProvider {
|
|||
$('#system_tts_pitch').val(this.settings.pitch || this.defaultSettings.pitch);
|
||||
$('#system_tts_pitch_output').text(this.settings.pitch);
|
||||
$('#system_tts_rate_output').text(this.settings.rate);
|
||||
this.checkReady()
|
||||
console.info("Settings loaded");
|
||||
}
|
||||
|
||||
// Perform a simple readiness check by trying to fetch voiceIds
|
||||
async checkReady(){
|
||||
try {
|
||||
await this.fetchTtsVoiceIds()
|
||||
this.ready = true
|
||||
|
||||
} catch {
|
||||
this.ready = false
|
||||
}
|
||||
await this.fetchTtsVoiceIds()
|
||||
}
|
||||
|
||||
async onApplyClick() {
|
||||
|
|
Loading…
Reference in New Issue