Fixes to the voice driver. It should actually work completely now.
This commit is contained in:
parent
72bd334d65
commit
43871cea3c
@ -37,16 +37,18 @@ class command():
|
|||||||
oldVoice = settingsManager.getSetting('speech', 'voice')
|
oldVoice = settingsManager.getSetting('speech', 'voice')
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Apply new settings to runtime only
|
# Apply new settings to runtime only (use setSetting to update settingArgDict)
|
||||||
settingsManager.settings['speech']['driver'] = 'speechdDriver'
|
settingsManager.setSetting('speech', 'driver', 'speechdDriver')
|
||||||
settingsManager.settings['speech']['module'] = module
|
settingsManager.setSetting('speech', 'module', module)
|
||||||
settingsManager.settings['speech']['voice'] = voice
|
settingsManager.setSetting('speech', 'voice', voice)
|
||||||
|
|
||||||
# Try to reinitialize speech driver
|
# Apply to speech driver instance directly
|
||||||
if 'speechDriver' in self.env['runtime']:
|
if 'speechDriver' in self.env['runtime']:
|
||||||
speechDriver = self.env['runtime']['speechDriver']
|
speechDriver = self.env['runtime']['speechDriver']
|
||||||
speechDriver.shutdown()
|
|
||||||
speechDriver.initialize(self.env)
|
# Set the module and voice on the driver instance
|
||||||
|
speechDriver.setModule(module)
|
||||||
|
speechDriver.setVoice(voice)
|
||||||
|
|
||||||
self.env['runtime']['outputManager'].presentText("Voice applied successfully!", interrupt=True)
|
self.env['runtime']['outputManager'].presentText("Voice applied successfully!", interrupt=True)
|
||||||
self.env['runtime']['outputManager'].presentText("Use save settings to make permanent", interrupt=True)
|
self.env['runtime']['outputManager'].presentText("Use save settings to make permanent", interrupt=True)
|
||||||
@ -54,9 +56,9 @@ class command():
|
|||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
# Revert on failure
|
# Revert on failure
|
||||||
settingsManager.settings['speech']['driver'] = oldDriver
|
settingsManager.setSetting('speech', 'driver', oldDriver)
|
||||||
settingsManager.settings['speech']['module'] = oldModule
|
settingsManager.setSetting('speech', 'module', oldModule)
|
||||||
settingsManager.settings['speech']['voice'] = oldVoice
|
settingsManager.setSetting('speech', 'voice', oldVoice)
|
||||||
|
|
||||||
self.env['runtime']['outputManager'].presentText(f"Failed to apply voice, reverted: {str(e)}", interrupt=True)
|
self.env['runtime']['outputManager'].presentText(f"Failed to apply voice, reverted: {str(e)}", interrupt=True)
|
||||||
self.env['runtime']['outputManager'].playSound('Error')
|
self.env['runtime']['outputManager'].playSound('Error')
|
||||||
|
@ -1,302 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
|
|
||||||
import os
|
|
||||||
import configparser
|
|
||||||
import subprocess
|
|
||||||
import tempfile
|
|
||||||
from typing import Dict, List, Optional, Tuple, Any
|
|
||||||
|
|
||||||
class ConfigCommand:
|
|
||||||
"""Base class for Fenrir configuration vmenu commands"""
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
self.env = None
|
|
||||||
self.config = None
|
|
||||||
self.settingsFile = None
|
|
||||||
|
|
||||||
# Configuration presets from original configure_fenrir.py
|
|
||||||
self.presetOptions = {
|
|
||||||
'sound.driver': ['genericDriver', 'gstreamerDriver'],
|
|
||||||
'speech.driver': ['speechdDriver', 'genericDriver'],
|
|
||||||
'screen.driver': ['vcsaDriver', 'dummyDriver', 'ptyDriver', 'debugDriver'],
|
|
||||||
'keyboard.driver': ['evdevDriver', 'dummyDriver'],
|
|
||||||
'remote.driver': ['unixDriver', 'tcpDriver'],
|
|
||||||
'keyboard.charEchoMode': ['0', '1', '2'],
|
|
||||||
'general.punctuationLevel': ['none', 'some', 'most', 'all'],
|
|
||||||
'general.debugMode': ['File', 'Print'],
|
|
||||||
'keyboard.device': ['ALL', 'NOMICE'],
|
|
||||||
'screen.encoding': ['auto', 'utf-8', 'cp1252', 'iso-8859-1']
|
|
||||||
}
|
|
||||||
|
|
||||||
# Help text for various options
|
|
||||||
self.helpText = {
|
|
||||||
'sound.volume': 'Volume level from 0 (quietest) to 1.0 (loudest)',
|
|
||||||
'speech.rate': 'Speech rate from 0 (slowest) to 1.0 (fastest)',
|
|
||||||
'speech.pitch': 'Voice pitch from 0 (lowest) to 1.0 (highest)',
|
|
||||||
'speech.capitalPitch': 'Pitch for capital letters from 0 to 1.0',
|
|
||||||
'keyboard.charEchoMode': '0 = None, 1 = always, 2 = only while capslock',
|
|
||||||
'keyboard.doubleTapTimeout': 'Timeout for double tap in seconds',
|
|
||||||
'screen.screenUpdateDelay': 'Delay between screen updates in seconds',
|
|
||||||
'general.punctuationLevel': 'none = no punctuation, some = basic, most = detailed, all = everything',
|
|
||||||
'general.numberOfClipboards': 'Number of clipboard slots to maintain'
|
|
||||||
}
|
|
||||||
|
|
||||||
def initialize(self, environment):
|
|
||||||
"""Initialize with Fenrir environment"""
|
|
||||||
self.env = environment
|
|
||||||
self.settingsFile = self.env['runtime']['settingsManager'].settingsFile
|
|
||||||
self.config = configparser.ConfigParser(interpolation=None)
|
|
||||||
self.config.read(self.settingsFile)
|
|
||||||
|
|
||||||
def shutdown(self):
|
|
||||||
"""Cleanup resources"""
|
|
||||||
pass
|
|
||||||
|
|
||||||
def getDescription(self):
|
|
||||||
"""Return description for this configuration action"""
|
|
||||||
return "Base configuration command"
|
|
||||||
|
|
||||||
def run(self):
|
|
||||||
"""Execute the configuration action - to be overridden"""
|
|
||||||
self.presentText("Configuration base class - override run() method")
|
|
||||||
|
|
||||||
def presentText(self, text: str, interrupt: bool = True):
|
|
||||||
"""Present text using Fenrir's speech system"""
|
|
||||||
if self.env and 'runtime' in self.env and 'outputManager' in self.env['runtime']:
|
|
||||||
self.env['runtime']['outputManager'].presentText(text, interrupt=interrupt)
|
|
||||||
|
|
||||||
def presentConfirmation(self, text: str):
|
|
||||||
"""Present confirmation message that won't be interrupted by menu navigation"""
|
|
||||||
self.presentText(text, interrupt=False)
|
|
||||||
|
|
||||||
def playSound(self, soundName: str):
|
|
||||||
"""Play a sound using Fenrir's sound system (deprecated - sounds removed from vmenu)"""
|
|
||||||
# Sounds removed from vmenu commands to avoid configuration issues
|
|
||||||
pass
|
|
||||||
|
|
||||||
def getSetting(self, section: str, option: str, default: str = "") -> str:
|
|
||||||
"""Get a setting value from configuration"""
|
|
||||||
try:
|
|
||||||
if section in self.config and option in self.config[section]:
|
|
||||||
return self.config[section][option]
|
|
||||||
return default
|
|
||||||
except Exception:
|
|
||||||
return default
|
|
||||||
|
|
||||||
def setSetting(self, section: str, option: str, value: str) -> bool:
|
|
||||||
"""Set a setting value and save configuration"""
|
|
||||||
try:
|
|
||||||
if section not in self.config:
|
|
||||||
self.config[section] = {}
|
|
||||||
|
|
||||||
oldValue = self.getSetting(section, option)
|
|
||||||
self.config[section][option] = str(value)
|
|
||||||
|
|
||||||
# Write configuration file
|
|
||||||
with open(self.settingsFile, 'w') as configfile:
|
|
||||||
self.config.write(configfile)
|
|
||||||
|
|
||||||
# Apply setting immediately if possible
|
|
||||||
self.applySettingImmediate(section, option, value, oldValue)
|
|
||||||
|
|
||||||
return True
|
|
||||||
except Exception as e:
|
|
||||||
self.presentText(f"Error saving setting: {str(e)}")
|
|
||||||
return False
|
|
||||||
|
|
||||||
def applySettingImmediate(self, section: str, option: str, newValue: str, oldValue: str):
|
|
||||||
"""Apply setting immediately without restart where possible"""
|
|
||||||
try:
|
|
||||||
# Apply speech settings immediately
|
|
||||||
if section == 'speech':
|
|
||||||
if option in ['rate', 'pitch', 'volume']:
|
|
||||||
settingsManager = self.env['runtime']['settingsManager']
|
|
||||||
settingsManager.setSetting(section, option, newValue)
|
|
||||||
|
|
||||||
# Apply sound settings immediately
|
|
||||||
elif section == 'sound':
|
|
||||||
if option == 'volume':
|
|
||||||
settingsManager = self.env['runtime']['settingsManager']
|
|
||||||
settingsManager.setSetting(section, option, newValue)
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
# Silent fail - settings will apply on restart
|
|
||||||
pass
|
|
||||||
|
|
||||||
def validateInput(self, section: str, option: str, value: str) -> Tuple[bool, str]:
|
|
||||||
"""Validate user input based on option type and constraints"""
|
|
||||||
try:
|
|
||||||
# Validate float values (volume, rate, pitch)
|
|
||||||
if option in ['volume', 'rate', 'pitch', 'capitalPitch']:
|
|
||||||
floatVal = float(value)
|
|
||||||
if not 0 <= floatVal <= 1.0:
|
|
||||||
return False, "Value must be between 0 and 1.0"
|
|
||||||
return True, str(floatVal)
|
|
||||||
|
|
||||||
# Validate integer values
|
|
||||||
elif option in ['numberOfClipboards', 'ignoreScreen']:
|
|
||||||
intVal = int(value)
|
|
||||||
if intVal < 0:
|
|
||||||
return False, "Value must be 0 or greater"
|
|
||||||
return True, str(intVal)
|
|
||||||
|
|
||||||
# Validate float values with different ranges
|
|
||||||
elif option == 'doubleTapTimeout':
|
|
||||||
floatVal = float(value)
|
|
||||||
if not 0 <= floatVal <= 2.0:
|
|
||||||
return False, "Value must be between 0 and 2.0 seconds"
|
|
||||||
return True, str(floatVal)
|
|
||||||
|
|
||||||
elif option == 'screenUpdateDelay':
|
|
||||||
floatVal = float(value)
|
|
||||||
if not 0 <= floatVal <= 1.0:
|
|
||||||
return False, "Value must be between 0 and 1.0 seconds"
|
|
||||||
return True, str(floatVal)
|
|
||||||
|
|
||||||
# Validate boolean values
|
|
||||||
elif self.isBooleanOption(value):
|
|
||||||
if value.lower() in ['true', 'false']:
|
|
||||||
return True, value.capitalize()
|
|
||||||
return False, "Value must be True or False"
|
|
||||||
|
|
||||||
# Validate preset options
|
|
||||||
key = f"{section}.{option}"
|
|
||||||
if key in self.presetOptions:
|
|
||||||
if value in self.presetOptions[key]:
|
|
||||||
return True, value
|
|
||||||
return False, f"Value must be one of: {', '.join(self.presetOptions[key])}"
|
|
||||||
|
|
||||||
# Default validation - accept any string
|
|
||||||
return True, str(value).strip()
|
|
||||||
|
|
||||||
except ValueError:
|
|
||||||
return False, "Invalid number format"
|
|
||||||
except Exception as e:
|
|
||||||
return False, f"Validation error: {str(e)}"
|
|
||||||
|
|
||||||
def isBooleanOption(self, value: str) -> bool:
|
|
||||||
"""Check if the current value is likely a boolean option"""
|
|
||||||
return value.lower() in ['true', 'false']
|
|
||||||
|
|
||||||
def getBooleanSetting(self, section: str, option: str, default: bool = False) -> bool:
|
|
||||||
"""Get a boolean setting value"""
|
|
||||||
value = self.getSetting(section, option, str(default)).lower()
|
|
||||||
return value in ['true', '1', 'yes', 'on']
|
|
||||||
|
|
||||||
def setBooleanSetting(self, section: str, option: str, value: bool) -> bool:
|
|
||||||
"""Set a boolean setting value"""
|
|
||||||
return self.setSetting(section, option, 'True' if value else 'False')
|
|
||||||
|
|
||||||
def toggleBooleanSetting(self, section: str, option: str) -> bool:
|
|
||||||
"""Toggle a boolean setting and return new value"""
|
|
||||||
currentValue = self.getBooleanSetting(section, option)
|
|
||||||
newValue = not currentValue
|
|
||||||
success = self.setBooleanSetting(section, option, newValue)
|
|
||||||
return newValue if success else currentValue
|
|
||||||
|
|
||||||
def getFloatSetting(self, section: str, option: str, default: float = 0.0) -> float:
|
|
||||||
"""Get a float setting value"""
|
|
||||||
try:
|
|
||||||
return float(self.getSetting(section, option, str(default)))
|
|
||||||
except ValueError:
|
|
||||||
return default
|
|
||||||
|
|
||||||
def setFloatSetting(self, section: str, option: str, value: float) -> bool:
|
|
||||||
"""Set a float setting value"""
|
|
||||||
return self.setSetting(section, option, str(value))
|
|
||||||
|
|
||||||
def adjustFloatSetting(self, section: str, option: str, delta: float,
|
|
||||||
minVal: float = 0.0, maxVal: float = 1.0) -> float:
|
|
||||||
"""Adjust a float setting by delta amount"""
|
|
||||||
currentValue = self.getFloatSetting(section, option)
|
|
||||||
newValue = max(minVal, min(maxVal, currentValue + delta))
|
|
||||||
success = self.setFloatSetting(section, option, newValue)
|
|
||||||
return newValue if success else currentValue
|
|
||||||
|
|
||||||
def runCommand(self, cmd: List[str], timeout: int = 10) -> Optional[str]:
|
|
||||||
"""Run a command and return output"""
|
|
||||||
try:
|
|
||||||
result = subprocess.run(cmd, capture_output=True, text=True, timeout=timeout)
|
|
||||||
return result.stdout.strip() if result.returncode == 0 else None
|
|
||||||
except Exception:
|
|
||||||
return None
|
|
||||||
|
|
||||||
def getSpeechdModules(self) -> List[str]:
|
|
||||||
"""Get list of available speech-dispatcher modules"""
|
|
||||||
output = self.runCommand(['spd-say', '-O'])
|
|
||||||
if output:
|
|
||||||
lines = output.split('\n')
|
|
||||||
return [line.strip() for line in lines[1:] if line.strip()]
|
|
||||||
return []
|
|
||||||
|
|
||||||
def getModuleVoices(self, module: str) -> List[str]:
|
|
||||||
"""Get list of voices for a specific speech module"""
|
|
||||||
output = self.runCommand(['spd-say', '-o', module, '-L'])
|
|
||||||
if output:
|
|
||||||
lines = output.split('\n')
|
|
||||||
voices = []
|
|
||||||
for line in lines[1:]:
|
|
||||||
if not line.strip():
|
|
||||||
continue
|
|
||||||
if module.lower() == 'espeak-ng':
|
|
||||||
voice = self.processEspeakVoice(line)
|
|
||||||
if voice:
|
|
||||||
voices.append(voice)
|
|
||||||
else:
|
|
||||||
voices.append(line.strip())
|
|
||||||
return voices
|
|
||||||
return []
|
|
||||||
|
|
||||||
def processEspeakVoice(self, voiceLine: str) -> Optional[str]:
|
|
||||||
"""Process espeak-ng voice line format"""
|
|
||||||
parts = [p for p in voiceLine.split() if p]
|
|
||||||
if len(parts) < 2:
|
|
||||||
return None
|
|
||||||
langCode = parts[-2].lower()
|
|
||||||
variant = parts[-1].lower()
|
|
||||||
return f"{langCode}+{variant}" if variant and variant != 'none' else langCode
|
|
||||||
|
|
||||||
def testVoice(self, module: str, voice: str, testMessage: str = None) -> bool:
|
|
||||||
"""Test a voice configuration"""
|
|
||||||
if not testMessage:
|
|
||||||
testMessage = "This is a voice test. If you can hear this clearly, the voice is working properly."
|
|
||||||
|
|
||||||
try:
|
|
||||||
# Announce the test
|
|
||||||
self.presentText("Testing voice configuration. Listen for the test message.")
|
|
||||||
|
|
||||||
# Run voice test
|
|
||||||
cmd = ['spd-say', '-o', module, '-y', voice, testMessage]
|
|
||||||
result = subprocess.run(cmd, timeout=10)
|
|
||||||
|
|
||||||
return result.returncode == 0
|
|
||||||
except Exception:
|
|
||||||
return False
|
|
||||||
|
|
||||||
def backupConfig(self, announce: bool = True) -> tuple[bool, str]:
|
|
||||||
"""Create a backup of current configuration"""
|
|
||||||
try:
|
|
||||||
import shutil
|
|
||||||
from datetime import datetime
|
|
||||||
|
|
||||||
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
|
||||||
backupFile = f"{self.settingsFile}.backup_{timestamp}"
|
|
||||||
shutil.copy2(self.settingsFile, backupFile)
|
|
||||||
|
|
||||||
message = f"Configuration backed up to {backupFile}"
|
|
||||||
if announce:
|
|
||||||
self.presentText(message, interrupt=False)
|
|
||||||
return True, message
|
|
||||||
except Exception as e:
|
|
||||||
error_msg = f"Error creating backup: {str(e)}"
|
|
||||||
if announce:
|
|
||||||
self.presentText(error_msg, interrupt=False)
|
|
||||||
return False, error_msg
|
|
||||||
|
|
||||||
def reloadConfig(self):
|
|
||||||
"""Reload configuration from file"""
|
|
||||||
try:
|
|
||||||
self.config.read(self.settingsFile)
|
|
||||||
return True
|
|
||||||
except Exception:
|
|
||||||
return False
|
|
@ -1,76 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
|
|
||||||
class command():
|
|
||||||
def __init__(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def initialize(self, environment):
|
|
||||||
self.env = environment
|
|
||||||
|
|
||||||
def shutdown(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def getDescription(self):
|
|
||||||
return "Apply last previewed voice to current session (temporary)"
|
|
||||||
|
|
||||||
def run(self):
|
|
||||||
# Check if we have a tested voice stored (must be tested first)
|
|
||||||
if ('commandBuffer' not in self.env or
|
|
||||||
'lastTestedModule' not in self.env['commandBuffer'] or
|
|
||||||
'lastTestedVoice' not in self.env['commandBuffer']):
|
|
||||||
self.env['runtime']['outputManager'].presentText("No voice has been tested yet", interrupt=True)
|
|
||||||
self.env['runtime']['outputManager'].presentText("Use test voice command first to confirm the voice works", interrupt=True)
|
|
||||||
self.env['runtime']['outputManager'].playSound('Cancel')
|
|
||||||
return
|
|
||||||
|
|
||||||
module = self.env['commandBuffer']['lastTestedModule']
|
|
||||||
voice = self.env['commandBuffer']['lastTestedVoice']
|
|
||||||
|
|
||||||
self.env['runtime']['outputManager'].presentText(f"Applying tested voice: {voice} from module {module}", interrupt=True)
|
|
||||||
self.env['runtime']['outputManager'].presentText("This will change your current voice temporarily", interrupt=True)
|
|
||||||
|
|
||||||
try:
|
|
||||||
# Apply to runtime settings only (temporary until saved)
|
|
||||||
settingsManager = self.env['runtime']['settingsManager']
|
|
||||||
|
|
||||||
# Store old values in case we need to revert
|
|
||||||
oldDriver = settingsManager.getSetting('speech', 'driver')
|
|
||||||
oldModule = settingsManager.getSetting('speech', 'module')
|
|
||||||
oldVoice = settingsManager.getSetting('speech', 'voice')
|
|
||||||
|
|
||||||
# Apply new settings to runtime only
|
|
||||||
settingsManager.settings['speech']['driver'] = 'speechdDriver'
|
|
||||||
settingsManager.settings['speech']['module'] = module
|
|
||||||
settingsManager.settings['speech']['voice'] = voice
|
|
||||||
|
|
||||||
# Try to reinitialize speech driver with new settings
|
|
||||||
if 'speechDriver' in self.env['runtime']:
|
|
||||||
try:
|
|
||||||
speechDriver = self.env['runtime']['speechDriver']
|
|
||||||
speechDriver.shutdown()
|
|
||||||
speechDriver.initialize(self.env)
|
|
||||||
|
|
||||||
# Test the new voice
|
|
||||||
self.env['runtime']['outputManager'].presentText("Voice applied successfully. This is how it sounds.", interrupt=False)
|
|
||||||
self.env['runtime']['outputManager'].presentText("Use save command to make this change permanent", interrupt=False)
|
|
||||||
self.env['runtime']['outputManager'].playSound('Accept')
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
# Revert on failure
|
|
||||||
settingsManager.settings['speech']['driver'] = oldDriver
|
|
||||||
settingsManager.settings['speech']['module'] = oldModule
|
|
||||||
settingsManager.settings['speech']['voice'] = oldVoice
|
|
||||||
|
|
||||||
self.env['runtime']['outputManager'].presentText(f"Failed to apply voice: {str(e)}", interrupt=False)
|
|
||||||
self.env['runtime']['outputManager'].presentText("Reverted to previous settings", interrupt=False)
|
|
||||||
self.env['runtime']['outputManager'].playSound('Error')
|
|
||||||
else:
|
|
||||||
self.env['runtime']['outputManager'].presentText("Speech driver not available", interrupt=False)
|
|
||||||
self.env['runtime']['outputManager'].playSound('Error')
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
self.env['runtime']['outputManager'].presentText(f"Error applying voice: {str(e)}", interrupt=False)
|
|
||||||
self.env['runtime']['outputManager'].playSound('Error')
|
|
||||||
|
|
||||||
def setCallback(self, callback):
|
|
||||||
pass
|
|
@ -1,182 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
|
|
||||||
import subprocess
|
|
||||||
import time
|
|
||||||
|
|
||||||
class command():
|
|
||||||
def __init__(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def initialize(self, environment):
|
|
||||||
self.env = environment
|
|
||||||
self.testMessage = "This is a voice test. If you can hear this message clearly, press Enter to accept this voice. Otherwise, wait 30 seconds to cancel."
|
|
||||||
|
|
||||||
def shutdown(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def getDescription(self):
|
|
||||||
return "Configure speech module and voice with testing"
|
|
||||||
|
|
||||||
def run(self):
|
|
||||||
self.env['runtime']['outputManager'].presentText("Starting voice configuration wizard", interrupt=True)
|
|
||||||
|
|
||||||
# Step 1: Get available speech modules
|
|
||||||
modules = self.getSpeechdModules()
|
|
||||||
if not modules:
|
|
||||||
self.env['runtime']['outputManager'].presentText("No speech-dispatcher modules found. Please install speech-dispatcher.", interrupt=True)
|
|
||||||
self.env['runtime']['outputManager'].playSound('Error')
|
|
||||||
return
|
|
||||||
|
|
||||||
# For this implementation, cycle through modules or use the first available one
|
|
||||||
# Get current module
|
|
||||||
currentModule = self.env['runtime']['settingsManager'].getSetting('speech', 'module')
|
|
||||||
if not currentModule:
|
|
||||||
currentModule = modules[0]
|
|
||||||
|
|
||||||
# Find next module or cycle to first
|
|
||||||
try:
|
|
||||||
currentIndex = modules.index(currentModule)
|
|
||||||
nextIndex = (currentIndex + 1) % len(modules)
|
|
||||||
selectedModule = modules[nextIndex]
|
|
||||||
except ValueError:
|
|
||||||
selectedModule = modules[0]
|
|
||||||
|
|
||||||
self.env['runtime']['outputManager'].presentText(f"Selected speech module: {selectedModule}", interrupt=True)
|
|
||||||
|
|
||||||
# Step 2: Get available voices for the module
|
|
||||||
voices = self.getModuleVoices(selectedModule)
|
|
||||||
if not voices:
|
|
||||||
self.env['runtime']['outputManager'].presentText(f"No voices found for module {selectedModule}", interrupt=True)
|
|
||||||
self.env['runtime']['outputManager'].playSound('Error')
|
|
||||||
return
|
|
||||||
|
|
||||||
# Get current voice and cycle to next, or use best voice
|
|
||||||
currentVoice = self.env['runtime']['settingsManager'].getSetting('speech', 'voice')
|
|
||||||
if currentVoice and currentVoice in voices:
|
|
||||||
try:
|
|
||||||
currentIndex = voices.index(currentVoice)
|
|
||||||
nextIndex = (currentIndex + 1) % len(voices)
|
|
||||||
selectedVoice = voices[nextIndex]
|
|
||||||
except ValueError:
|
|
||||||
selectedVoice = self.selectBestVoice(voices)
|
|
||||||
else:
|
|
||||||
selectedVoice = self.selectBestVoice(voices)
|
|
||||||
|
|
||||||
self.env['runtime']['outputManager'].presentText(f"Testing voice: {selectedVoice}", interrupt=True)
|
|
||||||
|
|
||||||
# Step 3: Test the voice configuration
|
|
||||||
if self.testVoiceWithConfirmation(selectedModule, selectedVoice):
|
|
||||||
# User confirmed - save the configuration
|
|
||||||
self.env['runtime']['settingsManager'].setSetting('speech', 'driver', 'speechdDriver')
|
|
||||||
self.env['runtime']['settingsManager'].setSetting('speech', 'module', selectedModule)
|
|
||||||
self.env['runtime']['settingsManager'].setSetting('speech', 'voice', selectedVoice)
|
|
||||||
|
|
||||||
self.env['runtime']['outputManager'].presentText("Voice configuration saved successfully!", interrupt=True)
|
|
||||||
self.env['runtime']['outputManager'].presentText(f"Module: {selectedModule}, Voice: {selectedVoice}", interrupt=True)
|
|
||||||
self.env['runtime']['outputManager'].playSound('Accept')
|
|
||||||
else:
|
|
||||||
# User cancelled or test failed
|
|
||||||
self.env['runtime']['outputManager'].presentText("Voice configuration cancelled. Settings unchanged.", interrupt=True)
|
|
||||||
self.env['runtime']['outputManager'].playSound('Cancel')
|
|
||||||
|
|
||||||
def getSpeechdModules(self):
|
|
||||||
"""Get list of available speech-dispatcher modules"""
|
|
||||||
try:
|
|
||||||
result = subprocess.run(['spd-say', '-O'], capture_output=True, text=True, timeout=10)
|
|
||||||
if result.returncode == 0:
|
|
||||||
lines = result.stdout.strip().split('\n')
|
|
||||||
return [line.strip() for line in lines[1:] if line.strip()]
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
return []
|
|
||||||
|
|
||||||
def getModuleVoices(self, module):
|
|
||||||
"""Get list of voices for a specific speech module"""
|
|
||||||
try:
|
|
||||||
result = subprocess.run(['spd-say', '-o', module, '-L'], capture_output=True, text=True, timeout=10)
|
|
||||||
if result.returncode == 0:
|
|
||||||
lines = result.stdout.strip().split('\n')
|
|
||||||
voices = []
|
|
||||||
for line in lines[1:]:
|
|
||||||
if not line.strip():
|
|
||||||
continue
|
|
||||||
if module.lower() == 'espeak-ng':
|
|
||||||
voice = self.processEspeakVoice(line)
|
|
||||||
if voice:
|
|
||||||
voices.append(voice)
|
|
||||||
else:
|
|
||||||
voices.append(line.strip())
|
|
||||||
return voices
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
return []
|
|
||||||
|
|
||||||
def processEspeakVoice(self, voiceLine):
|
|
||||||
"""Process espeak-ng voice line format"""
|
|
||||||
parts = [p for p in voiceLine.split() if p]
|
|
||||||
if len(parts) < 2:
|
|
||||||
return None
|
|
||||||
langCode = parts[-2].lower()
|
|
||||||
variant = parts[-1].lower()
|
|
||||||
return f"{langCode}+{variant}" if variant and variant != 'none' else langCode
|
|
||||||
|
|
||||||
def selectBestVoice(self, voices):
|
|
||||||
"""Select the best voice from available voices, preferring English"""
|
|
||||||
# Look for English voices first
|
|
||||||
for voice in voices:
|
|
||||||
if any(lang in voice.lower() for lang in ['en', 'english', 'us', 'gb']):
|
|
||||||
return voice
|
|
||||||
|
|
||||||
# If no English voice found, return the first available
|
|
||||||
return voices[0] if voices else ""
|
|
||||||
|
|
||||||
def testVoiceWithConfirmation(self, module, voice):
|
|
||||||
"""Test voice and wait for user confirmation"""
|
|
||||||
try:
|
|
||||||
# Start the voice test
|
|
||||||
self.env['runtime']['outputManager'].presentText("Starting voice test. Listen carefully.", interrupt=True)
|
|
||||||
time.sleep(1) # Brief pause
|
|
||||||
|
|
||||||
# Use spd-say to test the voice
|
|
||||||
process = subprocess.Popen([
|
|
||||||
'spd-say', '-o', module, '-y', voice, self.testMessage
|
|
||||||
])
|
|
||||||
|
|
||||||
# Wait for the test message to finish (give it some time)
|
|
||||||
time.sleep(2)
|
|
||||||
|
|
||||||
# Now wait for user input
|
|
||||||
self.env['runtime']['outputManager'].presentText("Press Enter if you heard the test message and want to keep this voice, or wait 30 seconds to cancel.", interrupt=True)
|
|
||||||
|
|
||||||
# Set up a simple confirmation system
|
|
||||||
# Since vmenu doesn't support input waiting natively, we'll use a simpler approach
|
|
||||||
# The user will need to run this command again to cycle through voices
|
|
||||||
# and the settings will be applied immediately for testing
|
|
||||||
|
|
||||||
# Apply settings temporarily for immediate testing
|
|
||||||
oldModule = self.env['runtime']['settingsManager'].getSetting('speech', 'module')
|
|
||||||
oldVoice = self.env['runtime']['settingsManager'].getSetting('speech', 'voice')
|
|
||||||
|
|
||||||
self.env['runtime']['settingsManager'].setSetting('speech', 'module', module)
|
|
||||||
self.env['runtime']['settingsManager'].setSetting('speech', 'voice', voice)
|
|
||||||
|
|
||||||
# Test with Fenrir's own speech system
|
|
||||||
time.sleep(1)
|
|
||||||
self.env['runtime']['outputManager'].presentText("This is how the new voice sounds in Fenrir. Run this command again to try the next voice, or exit the menu to keep this voice.", interrupt=True)
|
|
||||||
|
|
||||||
# For now, we'll auto-accept since we can't wait for input in vmenu
|
|
||||||
# The user can cycle through by running the command multiple times
|
|
||||||
return True
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
self.env['runtime']['outputManager'].presentText(f"Error testing voice: {str(e)}", interrupt=True)
|
|
||||||
return False
|
|
||||||
finally:
|
|
||||||
# Clean up any running processes
|
|
||||||
try:
|
|
||||||
process.terminate()
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
|
|
||||||
def setCallback(self, callback):
|
|
||||||
pass
|
|
@ -1,136 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
|
|
||||||
import subprocess
|
|
||||||
|
|
||||||
class command():
|
|
||||||
def __init__(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def initialize(self, environment):
|
|
||||||
self.env = environment
|
|
||||||
self.testMessage = "This is a voice preview. The quick brown fox jumps over the lazy dog."
|
|
||||||
|
|
||||||
def shutdown(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def getDescription(self):
|
|
||||||
return "Cycle through available voices (run multiple times to browse)"
|
|
||||||
|
|
||||||
def run(self):
|
|
||||||
# Get available modules
|
|
||||||
modules = self.getSpeechdModules()
|
|
||||||
if not modules:
|
|
||||||
self.env['runtime']['outputManager'].presentText("No speech-dispatcher modules found", interrupt=True)
|
|
||||||
self.env['runtime']['outputManager'].playSound('Error')
|
|
||||||
return
|
|
||||||
|
|
||||||
# Get stored indexes or initialize
|
|
||||||
moduleIndex = self.env['commandBuffer'].get('voicePreviewModuleIndex', 0)
|
|
||||||
voiceIndex = self.env['commandBuffer'].get('voicePreviewVoiceIndex', 0)
|
|
||||||
|
|
||||||
# Ensure indexes are valid
|
|
||||||
if moduleIndex >= len(modules):
|
|
||||||
moduleIndex = 0
|
|
||||||
|
|
||||||
selectedModule = modules[moduleIndex]
|
|
||||||
|
|
||||||
# Get voices for current module
|
|
||||||
voices = self.getModuleVoices(selectedModule)
|
|
||||||
if not voices:
|
|
||||||
self.env['runtime']['outputManager'].presentText(f"No voices found for {selectedModule}, trying next module", interrupt=True)
|
|
||||||
moduleIndex = (moduleIndex + 1) % len(modules)
|
|
||||||
self.env['commandBuffer']['voicePreviewModuleIndex'] = moduleIndex
|
|
||||||
self.env['commandBuffer']['voicePreviewVoiceIndex'] = 0
|
|
||||||
return
|
|
||||||
|
|
||||||
# Ensure voice index is valid
|
|
||||||
if voiceIndex >= len(voices):
|
|
||||||
voiceIndex = 0
|
|
||||||
|
|
||||||
selectedVoice = voices[voiceIndex]
|
|
||||||
|
|
||||||
# Present current selection
|
|
||||||
self.env['runtime']['outputManager'].presentText(
|
|
||||||
f"Module: {selectedModule} ({moduleIndex + 1}/{len(modules)})", interrupt=True
|
|
||||||
)
|
|
||||||
self.env['runtime']['outputManager'].presentText(
|
|
||||||
f"Voice: {selectedVoice} ({voiceIndex + 1}/{len(voices)})", interrupt=True
|
|
||||||
)
|
|
||||||
|
|
||||||
# Test the voice
|
|
||||||
self.env['runtime']['outputManager'].presentText("Testing voice...", interrupt=True)
|
|
||||||
if self.previewVoice(selectedModule, selectedVoice):
|
|
||||||
self.env['runtime']['outputManager'].presentText("Voice test completed", interrupt=True)
|
|
||||||
self.env['runtime']['outputManager'].presentText("Run again for next voice, or use Apply Voice to make this active", interrupt=True)
|
|
||||||
|
|
||||||
# Store for potential application
|
|
||||||
self.env['commandBuffer']['lastTestedModule'] = selectedModule
|
|
||||||
self.env['commandBuffer']['lastTestedVoice'] = selectedVoice
|
|
||||||
|
|
||||||
self.env['runtime']['outputManager'].playSound('Accept')
|
|
||||||
else:
|
|
||||||
self.env['runtime']['outputManager'].presentText("Voice test failed", interrupt=True)
|
|
||||||
self.env['runtime']['outputManager'].playSound('Error')
|
|
||||||
|
|
||||||
# Advance to next voice for next run
|
|
||||||
voiceIndex += 1
|
|
||||||
if voiceIndex >= len(voices):
|
|
||||||
voiceIndex = 0
|
|
||||||
moduleIndex = (moduleIndex + 1) % len(modules)
|
|
||||||
|
|
||||||
# Store indexes for next run
|
|
||||||
self.env['commandBuffer']['voicePreviewModuleIndex'] = moduleIndex
|
|
||||||
self.env['commandBuffer']['voicePreviewVoiceIndex'] = voiceIndex
|
|
||||||
|
|
||||||
def previewVoice(self, module, voice):
|
|
||||||
"""Preview voice using spd-say without affecting Fenrir"""
|
|
||||||
try:
|
|
||||||
cmd = ['spd-say', '-o', module, '-y', voice, self.testMessage]
|
|
||||||
result = subprocess.run(cmd, timeout=15)
|
|
||||||
return result.returncode == 0
|
|
||||||
except Exception:
|
|
||||||
return False
|
|
||||||
|
|
||||||
def getSpeechdModules(self):
|
|
||||||
"""Get list of available speech-dispatcher modules"""
|
|
||||||
try:
|
|
||||||
result = subprocess.run(['spd-say', '-O'], capture_output=True, text=True, timeout=10)
|
|
||||||
if result.returncode == 0:
|
|
||||||
lines = result.stdout.strip().split('\n')
|
|
||||||
return [line.strip() for line in lines[1:] if line.strip()]
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
return []
|
|
||||||
|
|
||||||
def getModuleVoices(self, module):
|
|
||||||
"""Get list of voices for a specific speech module"""
|
|
||||||
try:
|
|
||||||
result = subprocess.run(['spd-say', '-o', module, '-L'], capture_output=True, text=True, timeout=10)
|
|
||||||
if result.returncode == 0:
|
|
||||||
lines = result.stdout.strip().split('\n')
|
|
||||||
voices = []
|
|
||||||
for line in lines[1:]:
|
|
||||||
if not line.strip():
|
|
||||||
continue
|
|
||||||
if module.lower() == 'espeak-ng':
|
|
||||||
voice = self.processEspeakVoice(line)
|
|
||||||
if voice:
|
|
||||||
voices.append(voice)
|
|
||||||
else:
|
|
||||||
voices.append(line.strip())
|
|
||||||
return voices
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
return []
|
|
||||||
|
|
||||||
def processEspeakVoice(self, voiceLine):
|
|
||||||
"""Process espeak-ng voice line format"""
|
|
||||||
parts = [p for p in voiceLine.split() if p]
|
|
||||||
if len(parts) < 2:
|
|
||||||
return None
|
|
||||||
langCode = parts[-2].lower()
|
|
||||||
variant = parts[-1].lower()
|
|
||||||
return f"{langCode}+{variant}" if variant and variant != 'none' else langCode
|
|
||||||
|
|
||||||
def setCallback(self, callback):
|
|
||||||
pass
|
|
@ -1,62 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
|
|
||||||
import subprocess
|
|
||||||
|
|
||||||
class command():
|
|
||||||
def __init__(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def initialize(self, environment):
|
|
||||||
self.env = environment
|
|
||||||
|
|
||||||
def shutdown(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def getDescription(self):
|
|
||||||
return "Test current voice configuration"
|
|
||||||
|
|
||||||
def run(self):
|
|
||||||
# Get current speech settings
|
|
||||||
driver = self.env['runtime']['settingsManager'].getSetting('speech', 'driver')
|
|
||||||
module = self.env['runtime']['settingsManager'].getSetting('speech', 'module')
|
|
||||||
voice = self.env['runtime']['settingsManager'].getSetting('speech', 'voice')
|
|
||||||
rate = self.env['runtime']['settingsManager'].getSetting('speech', 'rate')
|
|
||||||
pitch = self.env['runtime']['settingsManager'].getSetting('speech', 'pitch')
|
|
||||||
volume = self.env['runtime']['settingsManager'].getSetting('speech', 'volume')
|
|
||||||
|
|
||||||
# Present current configuration
|
|
||||||
self.env['runtime']['outputManager'].presentText("Testing current voice configuration", interrupt=True)
|
|
||||||
|
|
||||||
if driver == 'speechdDriver' and module:
|
|
||||||
self.env['runtime']['outputManager'].presentText(f"Driver: {driver}", interrupt=True)
|
|
||||||
self.env['runtime']['outputManager'].presentText(f"Module: {module}", interrupt=True)
|
|
||||||
if voice:
|
|
||||||
self.env['runtime']['outputManager'].presentText(f"Voice: {voice}", interrupt=True)
|
|
||||||
|
|
||||||
try:
|
|
||||||
ratePercent = int(float(rate) * 100)
|
|
||||||
self.env['runtime']['outputManager'].presentText(f"Rate: {ratePercent} percent", interrupt=True)
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
|
|
||||||
try:
|
|
||||||
pitchPercent = int(float(pitch) * 100)
|
|
||||||
self.env['runtime']['outputManager'].presentText(f"Pitch: {pitchPercent} percent", interrupt=True)
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
|
|
||||||
try:
|
|
||||||
volumePercent = int(float(volume) * 100)
|
|
||||||
self.env['runtime']['outputManager'].presentText(f"Volume: {volumePercent} percent", interrupt=True)
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
|
|
||||||
# Test message
|
|
||||||
testMessage = "This is a test of your current voice configuration. The quick brown fox jumps over the lazy dog. Numbers: one, two, three, four, five. If you can hear this message clearly, your voice settings are working properly."
|
|
||||||
|
|
||||||
self.env['runtime']['outputManager'].presentText("Voice test:", interrupt=True)
|
|
||||||
self.env['runtime']['outputManager'].presentText(testMessage, interrupt=True)
|
|
||||||
self.env['runtime']['outputManager'].playSound('Accept')
|
|
||||||
|
|
||||||
def setCallback(self, callback):
|
|
||||||
pass
|
|
@ -3,6 +3,7 @@
|
|||||||
import subprocess
|
import subprocess
|
||||||
import importlib.util
|
import importlib.util
|
||||||
import os
|
import os
|
||||||
|
import time
|
||||||
|
|
||||||
class DynamicVoiceCommand:
|
class DynamicVoiceCommand:
|
||||||
"""Dynamic command class for voice selection"""
|
"""Dynamic command class for voice selection"""
|
||||||
@ -23,34 +24,42 @@ class DynamicVoiceCommand:
|
|||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
try:
|
try:
|
||||||
self.env['runtime']['outputManager'].presentText(f"Testing {self.voice} from {self.module}", interrupt=True)
|
self.env['runtime']['outputManager'].presentText(f"Testing voice {self.voice} from {self.module}. Please wait.", interrupt=True)
|
||||||
|
|
||||||
# Test voice first
|
# Brief pause before testing to avoid speech overlap
|
||||||
if self.testVoice():
|
time.sleep(0.5)
|
||||||
self.env['runtime']['outputManager'].presentText("Voice test completed. Press Enter again to apply.", interrupt=True)
|
|
||||||
|
# Test voice
|
||||||
|
testResult, errorMsg = self.testVoice()
|
||||||
|
if testResult:
|
||||||
|
self.env['runtime']['outputManager'].presentText("Voice test completed successfully. Navigate to Apply Tested Voice to use this voice.", interrupt=False, flush=False)
|
||||||
|
|
||||||
# Store for confirmation
|
# Store for confirmation (use same variables as apply_tested_voice.py)
|
||||||
|
self.env['commandBuffer']['lastTestedModule'] = self.module
|
||||||
|
self.env['commandBuffer']['lastTestedVoice'] = self.voice
|
||||||
self.env['commandBuffer']['pendingVoiceModule'] = self.module
|
self.env['commandBuffer']['pendingVoiceModule'] = self.module
|
||||||
self.env['commandBuffer']['pendingVoiceVoice'] = self.voice
|
self.env['commandBuffer']['pendingVoiceVoice'] = self.voice
|
||||||
self.env['commandBuffer']['voiceTestCompleted'] = True
|
self.env['commandBuffer']['voiceTestCompleted'] = True
|
||||||
|
|
||||||
self.env['runtime']['outputManager'].playSound('Accept')
|
|
||||||
else:
|
else:
|
||||||
self.env['runtime']['outputManager'].presentText("Voice test failed", interrupt=True)
|
self.env['runtime']['outputManager'].presentText(f"Voice test failed: {errorMsg}", interrupt=False, flush=False)
|
||||||
self.env['runtime']['outputManager'].playSound('Error')
|
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.env['runtime']['outputManager'].presentText(f"Voice selection error: {str(e)}", interrupt=True)
|
self.env['runtime']['outputManager'].presentText(f"Voice selection error: {str(e)}", interrupt=False, flush=False)
|
||||||
self.env['runtime']['outputManager'].playSound('Error')
|
|
||||||
|
|
||||||
def testVoice(self):
|
def testVoice(self):
|
||||||
"""Test voice with spd-say"""
|
"""Test voice with spd-say"""
|
||||||
try:
|
try:
|
||||||
cmd = ['spd-say', '-o', self.module, '-y', self.voice, self.testMessage]
|
cmd = ['spd-say', '-C', '-w', '-o', self.module, '-y', self.voice, self.testMessage]
|
||||||
result = subprocess.run(cmd, timeout=8)
|
result = subprocess.run(cmd, timeout=8, capture_output=True, text=True)
|
||||||
return result.returncode == 0
|
if result.returncode == 0:
|
||||||
except Exception:
|
return True, "Voice test successful"
|
||||||
return False
|
else:
|
||||||
|
error_msg = result.stderr.strip() if result.stderr else f"Command failed with return code {result.returncode}"
|
||||||
|
return False, error_msg
|
||||||
|
except subprocess.TimeoutExpired:
|
||||||
|
return False, "Voice test timed out"
|
||||||
|
except Exception as e:
|
||||||
|
return False, f"Error running voice test: {str(e)}"
|
||||||
|
|
||||||
def setCallback(self, callback):
|
def setCallback(self, callback):
|
||||||
pass
|
pass
|
||||||
@ -80,30 +89,83 @@ class DynamicApplyVoiceCommand:
|
|||||||
|
|
||||||
self.env['runtime']['outputManager'].presentText(f"Applying {voice} from {module}", interrupt=True)
|
self.env['runtime']['outputManager'].presentText(f"Applying {voice} from {module}", interrupt=True)
|
||||||
|
|
||||||
# Apply to runtime settings
|
# Debug: Show current settings
|
||||||
settingsManager = self.env['runtime']['settingsManager']
|
settingsManager = self.env['runtime']['settingsManager']
|
||||||
settingsManager.settings['speech']['driver'] = 'speechdDriver'
|
currentModule = settingsManager.getSetting('speech', 'module')
|
||||||
settingsManager.settings['speech']['module'] = module
|
currentVoice = settingsManager.getSetting('speech', 'voice')
|
||||||
settingsManager.settings['speech']['voice'] = voice
|
self.env['runtime']['outputManager'].presentText(f"Current: {currentVoice} from {currentModule}", interrupt=False, flush=False)
|
||||||
|
|
||||||
# Reinitialize speech driver
|
# Apply to runtime settings with fallback
|
||||||
if 'speechDriver' in self.env['runtime']:
|
settingsManager = self.env['runtime']['settingsManager']
|
||||||
speechDriver = self.env['runtime']['speechDriver']
|
|
||||||
speechDriver.shutdown()
|
# Store old values for safety
|
||||||
speechDriver.initialize(self.env)
|
oldDriver = settingsManager.getSetting('speech', 'driver')
|
||||||
|
oldModule = settingsManager.getSetting('speech', 'module')
|
||||||
|
oldVoice = settingsManager.getSetting('speech', 'voice')
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Apply new settings to runtime only (use setSetting to update settingArgDict)
|
||||||
|
settingsManager.setSetting('speech', 'driver', 'speechdDriver')
|
||||||
|
settingsManager.setSetting('speech', 'module', module)
|
||||||
|
settingsManager.setSetting('speech', 'voice', voice)
|
||||||
|
|
||||||
self.env['runtime']['outputManager'].presentText("Voice applied successfully!", interrupt=True)
|
# Apply settings to speech driver directly
|
||||||
self.env['runtime']['outputManager'].playSound('Accept')
|
if 'speechDriver' in self.env['runtime']:
|
||||||
|
speechDriver = self.env['runtime']['speechDriver']
|
||||||
|
|
||||||
|
# Get current module to see if we're changing modules
|
||||||
|
currentModule = settingsManager.getSetting('speech', 'module')
|
||||||
|
moduleChanging = (currentModule != module)
|
||||||
|
|
||||||
|
# Set module and voice on driver instance first
|
||||||
|
speechDriver.setModule(module)
|
||||||
|
speechDriver.setVoice(voice)
|
||||||
|
|
||||||
|
if moduleChanging:
|
||||||
|
# Module change requires reinitializing the speech driver
|
||||||
|
self.env['runtime']['outputManager'].presentText(f"Switching from {currentModule} to {module} module", interrupt=True)
|
||||||
|
speechDriver.shutdown()
|
||||||
|
speechDriver.initialize(self.env)
|
||||||
|
# Re-set after initialization
|
||||||
|
speechDriver.setModule(module)
|
||||||
|
speechDriver.setVoice(voice)
|
||||||
|
self.env['runtime']['outputManager'].presentText("Speech driver reinitialized", interrupt=True)
|
||||||
|
|
||||||
|
# Debug: verify what was actually set
|
||||||
|
self.env['runtime']['outputManager'].presentText(f"Speech driver now has module: {speechDriver.module}, voice: {speechDriver.voice}", interrupt=True)
|
||||||
|
|
||||||
|
# Force application by speaking a test message
|
||||||
|
self.env['runtime']['outputManager'].presentText("Voice applied successfully! You should hear this in the new voice.", interrupt=True)
|
||||||
|
|
||||||
|
# Brief pause then more speech to test
|
||||||
|
time.sleep(1)
|
||||||
|
self.env['runtime']['outputManager'].presentText("Use save settings to make permanent", interrupt=True)
|
||||||
|
|
||||||
# Clear pending state
|
# Clear pending state
|
||||||
self.env['commandBuffer']['voiceTestCompleted'] = False
|
self.env['commandBuffer']['voiceTestCompleted'] = False
|
||||||
|
|
||||||
# Exit vmenu after successful application
|
# Exit vmenu after successful application
|
||||||
self.env['runtime']['vmenuManager'].setActive(False)
|
self.env['runtime']['vmenuManager'].setActive(False)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
# Revert on failure
|
||||||
|
settingsManager.settings['speech']['driver'] = oldDriver
|
||||||
|
settingsManager.settings['speech']['module'] = oldModule
|
||||||
|
settingsManager.settings['speech']['voice'] = oldVoice
|
||||||
|
|
||||||
|
# Try to reinitialize with old settings
|
||||||
|
if 'speechDriver' in self.env['runtime']:
|
||||||
|
try:
|
||||||
|
speechDriver = self.env['runtime']['speechDriver']
|
||||||
|
speechDriver.shutdown()
|
||||||
|
speechDriver.initialize(self.env)
|
||||||
|
except:
|
||||||
|
pass # If this fails, at least we tried
|
||||||
|
|
||||||
|
self.env['runtime']['outputManager'].presentText(f"Failed to apply voice, reverted: {str(e)}", interrupt=False, flush=False)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.env['runtime']['outputManager'].presentText(f"Apply failed: {str(e)}", interrupt=True)
|
self.env['runtime']['outputManager'].presentText(f"Apply voice error: {str(e)}", interrupt=False, flush=False)
|
||||||
self.env['runtime']['outputManager'].playSound('Error')
|
|
||||||
|
|
||||||
def setCallback(self, callback):
|
def setCallback(self, callback):
|
||||||
pass
|
pass
|
||||||
@ -129,13 +191,9 @@ def addDynamicVoiceMenus(vmenuManager):
|
|||||||
for module in modules[:8]: # Limit to 8 modules to keep menu manageable
|
for module in modules[:8]: # Limit to 8 modules to keep menu manageable
|
||||||
moduleMenu = {}
|
moduleMenu = {}
|
||||||
|
|
||||||
# Get voices for this module (limit to prevent huge menus)
|
# Get voices for this module
|
||||||
voices = getModuleVoices(module)
|
voices = getModuleVoices(module)
|
||||||
if voices:
|
if voices:
|
||||||
# Limit voices to keep menu usable
|
|
||||||
if len(voices) > 50:
|
|
||||||
voices = voices[:50]
|
|
||||||
moduleMenu['Note: Showing first 50 voices Action'] = createInfoCommand(f"Module {module} has {len(getModuleVoices(module))} voices, showing first 50", env)
|
|
||||||
|
|
||||||
# Add voice commands
|
# Add voice commands
|
||||||
for voice in voices:
|
for voice in voices:
|
||||||
|
@ -192,3 +192,12 @@ class outputManager():
|
|||||||
self.presentText(' review cursor ', interrupt=interrupt_p)
|
self.presentText(' review cursor ', interrupt=interrupt_p)
|
||||||
else:
|
else:
|
||||||
self.presentText(' text cursor ', interrupt=interrupt_p)
|
self.presentText(' text cursor ', interrupt=interrupt_p)
|
||||||
|
|
||||||
|
def resetSpeechDriver(self):
|
||||||
|
"""Reset speech driver to clean state - called by settingsManager"""
|
||||||
|
if 'speechDriver' in self.env['runtime'] and self.env['runtime']['speechDriver']:
|
||||||
|
try:
|
||||||
|
self.env['runtime']['speechDriver'].reset()
|
||||||
|
self.env['runtime']['debug'].writeDebugOut("Speech driver reset successfully", debug.debugLevel.INFO)
|
||||||
|
except Exception as e:
|
||||||
|
self.env['runtime']['debug'].writeDebugOut(f"resetSpeechDriver error: {e}", debug.debugLevel.ERROR)
|
||||||
|
@ -16,9 +16,15 @@ class driver(speechDriver):
|
|||||||
self._sd = None
|
self._sd = None
|
||||||
self.env = environment
|
self.env = environment
|
||||||
self._isInitialized = False
|
self._isInitialized = False
|
||||||
self.language = ''
|
|
||||||
self.voice = ''
|
# Only set these if they haven't been set yet (preserve existing values)
|
||||||
self.module = ''
|
if not hasattr(self, 'language') or self.language is None:
|
||||||
|
self.language = ''
|
||||||
|
if not hasattr(self, 'voice') or self.voice is None:
|
||||||
|
self.voice = ''
|
||||||
|
if not hasattr(self, 'module') or self.module is None:
|
||||||
|
self.module = ''
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import speechd
|
import speechd
|
||||||
self._sd = speechd.SSIPClient('fenrir')
|
self._sd = speechd.SSIPClient('fenrir')
|
||||||
|
Loading…
x
Reference in New Issue
Block a user