More pep8 fixes. A tiny bit of refactoring.

This commit is contained in:
Storm Dragon
2025-07-07 00:42:23 -04:00
parent d28c18faed
commit 3390c25dfe
343 changed files with 11092 additions and 7582 deletions

View File

@ -2,7 +2,7 @@
# -*- coding: utf-8 -*-
# Fenrir TTY screen reader
# By Chrys, Storm Dragon, and contributers.
# By Chrys, Storm Dragon, and contributors.
# generic driver
from fenrirscreenreader.core import debug
@ -10,67 +10,126 @@ from fenrirscreenreader.core.speechDriver import speech_driver
class driver(speech_driver):
"""Debug speech driver for Fenrir development and testing.
This driver provides speech output to console/debug output instead of
actual audio, making it useful for development, testing, and debugging
scenarios where audio output is not needed or available.
All speech operations are logged to console with descriptive messages,
allowing developers to trace speech behavior without audio hardware.
"""
def __init__(self):
speech_driver.__init__(self)
def initialize(self, environment):
"""Initialize the debug speech driver.
Args:
environment: Fenrir environment dictionary
"""
self._is_initialized = True
self.env = environment
print('Speech Debug Driver: Iitialized')
print("Speech Debug Driver: Initialized")
def shutdown(self):
"""Shutdown the debug speech driver."""
if self._is_initialized:
self.cancel()
self._is_initialized = False
print('Speech Debug Driver: Shutdown')
print("Speech Debug Driver: Shutdown")
def speak(self, text, queueable=True, ignore_punctuation=False):
"""Output speech text to console for debugging.
Args:
text (str): Text to speak
queueable (bool): Whether speech can be queued
ignore_punctuation (bool): Whether to ignore punctuation
"""
if not self._is_initialized:
return
if not queueable:
self.cancel()
print('Speech Debug Driver: Speak:' + text)
print('Speech Debug Driver: -----------------------------------')
print("Speech Debug Driver: Speak:" + text)
print("Speech Debug Driver: -----------------------------------")
def cancel(self):
"""Log speech cancellation to console."""
if not self._is_initialized:
return
print('Speech Debug Driver: Cancel')
print("Speech Debug Driver: Cancel")
def set_callback(self, callback):
print('Speech Debug Driver: set_callback')
"""Log callback setting to console.
Args:
callback: Callback function (logged but not used)
"""
print("Speech Debug Driver: set_callback")
def clear_buffer(self):
"""Log buffer clearing to console."""
if not self._is_initialized:
return
print('Speech Debug Driver: clear_buffer')
print("Speech Debug Driver: clear_buffer")
def set_voice(self, voice):
"""Log voice setting to console.
Args:
voice: Voice setting (logged but not used)
"""
if not self._is_initialized:
return
print('Speech Debug Driver: set_voice:' + str(voice))
print("Speech Debug Driver: set_voice:" + str(voice))
def set_pitch(self, pitch):
"""Log pitch setting to console.
Args:
pitch: Pitch setting (logged but not used)
"""
if not self._is_initialized:
return
print('Speech Debug Driver: set_pitch:' + str(pitch))
print("Speech Debug Driver: set_pitch:" + str(pitch))
def set_rate(self, rate):
"""Log rate setting to console.
Args:
rate: Rate setting (logged but not used)
"""
if not self._is_initialized:
return
print('Speech Debug Driver: set_rate:' + str(rate))
print("Speech Debug Driver: set_rate:" + str(rate))
def set_module(self, module):
"""Log module setting to console.
Args:
module: Module setting (logged but not used)
"""
if not self._is_initialized:
return
print('Speech Debug Driver: set_module:' + str(module))
print("Speech Debug Driver: set_module:" + str(module))
def set_language(self, language):
"""Log language setting to console.
Args:
language: Language setting (logged but not used)
"""
if not self._is_initialized:
return
print('Speech Debug Driver: set_language:' + str(language))
print("Speech Debug Driver: set_language:" + str(language))
def set_volume(self, volume):
"""Log volume setting to console.
Args:
volume: Volume setting (logged but not used)
"""
if not self._is_initialized:
return
print('Speech Debug Driver: set_volume:' + str(volume))
print("Speech Debug Driver: set_volume:" + str(volume))

View File

@ -2,7 +2,7 @@
# -*- coding: utf-8 -*-
# Fenrir TTY screen reader
# By Chrys, Storm Dragon, and contributers.
# By Chrys, Storm Dragon, and contributors.
# generic driver
from fenrirscreenreader.core import debug

View File

@ -2,15 +2,18 @@
# -*- coding: utf-8 -*-
# Fenrir TTY screen reader
# By Chrys, Storm Dragon, and contributers.
# By Chrys, Storm Dragon, and contributors.
# generic driver
from fenrirscreenreader.core import debug
from threading import Thread, Lock
from queue import Queue, Empty
import shlex
from subprocess import Popen
import subprocess
from queue import Empty
from queue import Queue
from subprocess import Popen
from threading import Lock
from threading import Thread
from fenrirscreenreader.core import debug
from fenrirscreenreader.core.speechDriver import speech_driver
@ -33,22 +36,29 @@ class driver(speech_driver):
def initialize(self, environment):
self.env = environment
self.minVolume = self.env['runtime']['SettingsManager'].get_setting_as_int(
'speech', 'fenrirMinVolume')
self.maxVolume = self.env['runtime']['SettingsManager'].get_setting_as_int(
'speech', 'fenrirMaxVolume')
self.minPitch = self.env['runtime']['SettingsManager'].get_setting_as_int(
'speech', 'fenrirMinPitch')
self.maxPitch = self.env['runtime']['SettingsManager'].get_setting_as_int(
'speech', 'fenrirMaxPitch')
self.minRate = self.env['runtime']['SettingsManager'].get_setting_as_int(
'speech', 'fenrirMinRate')
self.maxRate = self.env['runtime']['SettingsManager'].get_setting_as_int(
'speech', 'fenrirMaxRate')
self.minVolume = self.env["runtime"][
"SettingsManager"
].get_setting_as_int("speech", "fenrirMinVolume")
self.maxVolume = self.env["runtime"][
"SettingsManager"
].get_setting_as_int("speech", "fenrirMaxVolume")
self.minPitch = self.env["runtime"][
"SettingsManager"
].get_setting_as_int("speech", "fenrirMinPitch")
self.maxPitch = self.env["runtime"][
"SettingsManager"
].get_setting_as_int("speech", "fenrirMaxPitch")
self.minRate = self.env["runtime"][
"SettingsManager"
].get_setting_as_int("speech", "fenrirMinRate")
self.maxRate = self.env["runtime"][
"SettingsManager"
].get_setting_as_int("speech", "fenrirMaxRate")
self.speechCommand = self.env['runtime']['SettingsManager'].get_setting(
'speech', 'genericSpeechCommand')
if self.speechCommand == '':
self.speechCommand = self.env["runtime"][
"SettingsManager"
].get_setting("speech", "genericSpeechCommand")
if self.speechCommand == "":
self.speechCommand = 'espeak -a fenrirVolume -s fenrirRate -p fenrirPitch -v fenrirVoice -- "fenrirText"'
if False: # for debugging overwrite here
# self.speechCommand = 'spd-say --wait -r 100 -i 100 "fenrirText"'
@ -70,13 +80,13 @@ class driver(speech_driver):
if not queueable:
self.cancel()
utterance = {
'text': text,
'volume': self.volume,
'rate': self.rate,
'pitch': self.pitch,
'module': self.module,
'language': self.language,
'voice': self.voice,
"text": text,
"volume": self.volume,
"rate": self.rate,
"pitch": self.pitch,
"module": self.module,
"language": self.language,
"voice": self.voice,
}
self.textQueue.put(utterance.copy())
@ -97,22 +107,26 @@ class driver(speech_driver):
self.proc.kill()
self.proc.wait(timeout=1.0)
except Exception as e:
self.env['runtime']['DebugManager'].write_debug_out(
'speech_driver:Cancel:self.proc.terminate():' + str(e), debug.DebugLevel.WARNING)
self.env["runtime"]["DebugManager"].write_debug_out(
"speech_driver:Cancel:self.proc.terminate():" + str(e),
debug.DebugLevel.WARNING,
)
try:
self.proc.kill()
# Wait after kill to prevent zombies
self.proc.wait(timeout=1.0)
except Exception as e:
self.env['runtime']['DebugManager'].write_debug_out(
'speech_driver:Cancel:self.proc.kill():' + str(e), debug.DebugLevel.WARNING)
self.env["runtime"]["DebugManager"].write_debug_out(
"speech_driver:Cancel:self.proc.kill():" + str(e),
debug.DebugLevel.WARNING,
)
self.proc = None
finally:
# Ensure lock is always released, even if process termination fails
self.lock.release()
def set_callback(self, callback):
print('SpeechDummyDriver: set_callback')
print("SpeechDummyDriver: set_callback")
def clear_buffer(self):
if not self._is_initialized:
@ -127,8 +141,9 @@ class driver(speech_driver):
def set_pitch(self, pitch):
if not self._is_initialized:
return
self.pitch = str(self.minPitch + pitch *
(self.maxPitch - self.minPitch))
self.pitch = str(
self.minPitch + pitch * (self.maxPitch - self.minPitch)
)
def set_rate(self, rate):
if not self._is_initialized:
@ -148,8 +163,9 @@ class driver(speech_driver):
def set_volume(self, volume):
if not self._is_initialized:
return
self.volume = str(self.minVolume + volume *
(self.maxVolume - self.minVolume))
self.volume = str(
self.minVolume + volume * (self.maxVolume - self.minVolume)
)
def worker(self):
while True:
@ -163,68 +179,73 @@ class driver(speech_driver):
elif not isinstance(utterance, dict):
continue
# no text means nothing to speak
if 'text' not in utterance:
if "text" not in utterance:
continue
if not isinstance(utterance['text'], str):
if not isinstance(utterance["text"], str):
continue
if utterance['text'] == '':
if utterance["text"] == "":
continue
# check for valid data fields
if 'volume' not in utterance:
utterance['volume'] = ''
if not isinstance(utterance['volume'], str):
utterance['volume'] = ''
if 'module' not in utterance:
utterance['module'] = ''
if not isinstance(utterance['module'], str):
utterance['module'] = ''
if 'language' not in utterance:
utterance['language'] = ''
if not isinstance(utterance['language'], str):
utterance['language'] = ''
if 'voice' not in utterance:
utterance['voice'] = ''
if not isinstance(utterance['voice'], str):
utterance['voice'] = ''
if 'pitch' not in utterance:
utterance['pitch'] = ''
if not isinstance(utterance['pitch'], str):
utterance['pitch'] = ''
if 'rate' not in utterance:
utterance['rate'] = ''
if not isinstance(utterance['rate'], str):
utterance['rate'] = ''
if "volume" not in utterance:
utterance["volume"] = ""
if not isinstance(utterance["volume"], str):
utterance["volume"] = ""
if "module" not in utterance:
utterance["module"] = ""
if not isinstance(utterance["module"], str):
utterance["module"] = ""
if "language" not in utterance:
utterance["language"] = ""
if not isinstance(utterance["language"], str):
utterance["language"] = ""
if "voice" not in utterance:
utterance["voice"] = ""
if not isinstance(utterance["voice"], str):
utterance["voice"] = ""
if "pitch" not in utterance:
utterance["pitch"] = ""
if not isinstance(utterance["pitch"], str):
utterance["pitch"] = ""
if "rate" not in utterance:
utterance["rate"] = ""
if not isinstance(utterance["rate"], str):
utterance["rate"] = ""
popen_speech_command = shlex.split(self.speechCommand)
for idx, word in enumerate(popen_speech_command):
word = word.replace('fenrirVolume', str(utterance['volume']))
word = word.replace('fenrirModule', str(utterance['module']))
word = word.replace("fenrirVolume", str(utterance["volume"]))
word = word.replace("fenrirModule", str(utterance["module"]))
word = word.replace(
'fenrirLanguage', str(
utterance['language']))
word = word.replace('fenrirVoice', str(utterance['voice']))
word = word.replace('fenrirPitch', str(utterance['pitch']))
word = word.replace('fenrirRate', str(utterance['rate']))
"fenrirLanguage", str(utterance["language"])
)
word = word.replace("fenrirVoice", str(utterance["voice"]))
word = word.replace("fenrirPitch", str(utterance["pitch"]))
word = word.replace("fenrirRate", str(utterance["rate"]))
# Properly quote text to prevent command injection
word = word.replace('fenrirText',
shlex.quote(str(utterance['text'])))
word = word.replace(
"fenrirText", shlex.quote(str(utterance["text"]))
)
popen_speech_command[idx] = word
try:
self.env['runtime']['DebugManager'].write_debug_out(
'speech_driver:worker:' + ' '.join(popen_speech_command), debug.DebugLevel.INFO)
self.env["runtime"]["DebugManager"].write_debug_out(
"speech_driver:worker:" + " ".join(popen_speech_command),
debug.DebugLevel.INFO,
)
self.lock.acquire(True)
self.proc = Popen(
popen_speech_command,
stdin=None,
stdout=None,
stderr=None,
shell=False)
shell=False,
)
self.lock.release()
self.proc.wait()
except Exception as e:
self.env['runtime']['DebugManager'].write_debug_out(
'speech_driver:worker:' + str(e), debug.DebugLevel.ERROR)
self.env["runtime"]["DebugManager"].write_debug_out(
"speech_driver:worker:" + str(e), debug.DebugLevel.ERROR
)
self.lock.acquire(True)
self.proc = None

View File

@ -2,7 +2,7 @@
# -*- coding: utf-8 -*-
# Fenrir TTY screen reader
# By Chrys, Storm Dragon, and contributers.
# By Chrys, Storm Dragon, and contributors.
# speech-dispatcher driver
from fenrirscreenreader.core import debug
@ -10,33 +10,71 @@ from fenrirscreenreader.core.speechDriver import speech_driver
class driver(speech_driver):
"""Speech-dispatcher driver for Fenrir screen reader.
This driver provides text-to-speech functionality through speech-dispatcher,
which acts as a common interface to various TTS engines. It supports voice
selection, speech parameters (rate, pitch, volume), and multiple TTS modules.
Features:
- Dynamic voice switching and parameter adjustment
- Support for multiple speech-dispatcher modules (espeak, festival, etc.)
- Real-time speech cancellation and queueing
- Language and voice selection
- Speech callbacks for synchronization
Attributes:
_sd: Speech-dispatcher client connection
language (str): Current speech language
voice (str): Current voice name
module (str): Current TTS module name
"""
def __init__(self):
speech_driver.__init__(self)
def initialize(self, environment):
"""Initialize the speech-dispatcher connection.
Establishes connection to speech-dispatcher daemon and configures
initial speech parameters. Sets up callbacks and prepares the
speech subsystem for use.
Args:
environment: Fenrir environment dictionary with settings and managers
Note:
Gracefully handles cases where speech-dispatcher is not available.
"""
self._sd = None
self.env = environment
self._is_initialized = False
# Only set these if they haven't been set yet (preserve existing
# values)
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 = ''
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:
import speechd
self._sd = speechd.SSIPClient('fenrir-dev')
self._sd = speechd.SSIPClient("fenrir-dev")
self._punct = speechd.PunctuationMode()
self._is_initialized = True
except Exception as e:
self.env['runtime']['DebugManager'].write_debug_out(
'SpeechDriver initialize:' + str(e), debug.DebugLevel.ERROR)
self.env["runtime"]["DebugManager"].write_debug_out(
"SpeechDriver initialize:" + str(e), debug.DebugLevel.ERROR
)
def shutdown(self):
"""Shutdown the speech-dispatcher connection.
Cleanly closes the connection to speech-dispatcher and releases
any allocated resources.
"""
if not self._is_initialized:
return
self.cancel()
@ -47,6 +85,16 @@ class driver(speech_driver):
self._is_initialized = False
def speak(self, text, queueable=True, ignore_punctuation=False):
"""Speak the given text through speech-dispatcher.
Args:
text (str): Text to speak
queueable (bool): Whether speech can be queued with other speech
ignore_punctuation (bool): Whether to ignore punctuation settings
Note:
Handles text preprocessing and manages speech queue based on parameters.
"""
if not queueable:
self.cancel()
if not self._is_initialized:
@ -57,25 +105,28 @@ class driver(speech_driver):
return
try:
if self.module != '':
if self.module != "":
self._sd.set_output_module(self.module)
except Exception as e:
self.env['runtime']['DebugManager'].write_debug_out(
'SpeechDriver set_module:' + str(e), debug.DebugLevel.ERROR)
self.env["runtime"]["DebugManager"].write_debug_out(
"SpeechDriver set_module:" + str(e), debug.DebugLevel.ERROR
)
try:
if self.language != '':
if self.language != "":
self._sd.set_language(self.language)
except Exception as e:
self.env['runtime']['DebugManager'].write_debug_out(
'SpeechDriver set_language:' + str(e), debug.DebugLevel.ERROR)
self.env["runtime"]["DebugManager"].write_debug_out(
"SpeechDriver set_language:" + str(e), debug.DebugLevel.ERROR
)
try:
if self.voice != '':
if self.voice != "":
self._sd.set_synthesis_voice(self.voice)
except Exception as e:
self.env['runtime']['DebugManager'].write_debug_out(
'SpeechDriver set_voice:' + str(e), debug.DebugLevel.ERROR)
self.env["runtime"]["DebugManager"].write_debug_out(
"SpeechDriver set_voice:" + str(e), debug.DebugLevel.ERROR
)
try:
if ignore_punctuation:
@ -83,17 +134,24 @@ class driver(speech_driver):
else:
self._sd.set_punctuation(self._punct.NONE)
except Exception as e:
self.env['runtime']['DebugManager'].write_debug_out(
'SpeechDriver set_punctuation:' + str(e), debug.DebugLevel.ERROR)
self.env["runtime"]["DebugManager"].write_debug_out(
"SpeechDriver set_punctuation:" + str(e),
debug.DebugLevel.ERROR,
)
try:
self._sd.speak(text)
except Exception as e:
self.env['runtime']['DebugManager'].write_debug_out(
'SpeechDriver speak:' + str(e), debug.DebugLevel.ERROR)
self.env["runtime"]["DebugManager"].write_debug_out(
"SpeechDriver speak:" + str(e), debug.DebugLevel.ERROR
)
self._is_initialized = False
def cancel(self):
"""Cancel all pending and current speech.
Immediately stops speech output and clears the speech queue.
"""
if not self._is_initialized:
self.initialize(self.env)
if not self._is_initialized:
@ -101,33 +159,52 @@ class driver(speech_driver):
try:
self._sd.cancel()
except Exception as e:
self.env['runtime']['DebugManager'].write_debug_out(
'SpeechDriver cancel:' + str(e), debug.DebugLevel.ERROR)
self.env["runtime"]["DebugManager"].write_debug_out(
"SpeechDriver cancel:" + str(e), debug.DebugLevel.ERROR
)
self._is_initialized = False
def set_pitch(self, pitch):
"""Set the speech pitch.
Args:
pitch (float): Speech pitch (0.0 to 1.0, where 0.5 is normal)
"""
if not self._is_initialized:
return
try:
self._sd.set_pitch(int(-100 + pitch * 200))
except Exception as e:
self.env['runtime']['DebugManager'].write_debug_out(
'SpeechDriver set_pitch:' + str(e), debug.DebugLevel.ERROR)
self.env["runtime"]["DebugManager"].write_debug_out(
"SpeechDriver set_pitch:" + str(e), debug.DebugLevel.ERROR
)
def set_rate(self, rate):
"""Set the speech rate.
Args:
rate (float): Speech rate (0.0 to 1.0, where 0.5 is normal)
"""
if not self._is_initialized:
return
try:
self._sd.set_rate(int(-100 + rate * 200))
except Exception as e:
self.env['runtime']['DebugManager'].write_debug_out(
'SpeechDriver set_rate:' + str(e), debug.DebugLevel.ERROR)
self.env["runtime"]["DebugManager"].write_debug_out(
"SpeechDriver set_rate:" + str(e), debug.DebugLevel.ERROR
)
def set_volume(self, volume):
"""Set the speech volume.
Args:
volume (float): Volume level (0.0 to 1.0)
"""
if not self._is_initialized:
return
try:
self._sd.set_volume(int(-100 + volume * 200))
except Exception as e:
self.env['runtime']['DebugManager'].write_debug_out(
'SpeechDriver set_volume:' + str(e), debug.DebugLevel.ERROR)
self.env["runtime"]["DebugManager"].write_debug_out(
"SpeechDriver set_volume:" + str(e), debug.DebugLevel.ERROR
)