import logging
import signal
from prompt_toolkit import PromptSession
from prompt_toolkit.output import create_output
from .api.message import ConfigError, ModuleError, CtlMessageType, LlmMessageType
from .api.model import AudioSink, OutputFilter, SentenceSegmenter
from .config import Config
from .factory import Factory, FactoryError
from .log import setup_logging, redirect_logging
from .queue import Message, MessageQueueR, MessageQueueW
from .utils.process import Rusage
class MainTts:
def __init__(self, logger: logging.Logger, factory: Factory) -> None:
self._logger: logging.Logger = logger
self._factory: Factory = factory
def _run(self, llm: MessageQueueR[LlmMessageType], ctl: MessageQueueW[CtlMessageType]) -> None:
with self._factory.create_synthesizer() as synthesizer:
sink: AudioSink = self._factory.create_sink_for(synthesizer)
feedback: OutputFilter = self._factory.create_output_filter_for(synthesizer)
segmenter: SentenceSegmenter = self._factory.create_sentence_segmenter_for(synthesizer)
with sink:
playing: bool = False
for event in llm:
self._logger.debug(str(event))
for message in feedback.accept(event):
if message.msg == LlmMessageType.Token:
if playing and message.data is not None:
for sentence in segmenter.push(message.data):
self._logger.info(f"Synth: {sentence}")
sink.play_all(synthesizer.generate(sentence))
elif message.msg == LlmMessageType.Utterance:
if playing:
for sentence in segmenter.drain(message.data):
self._logger.info(f"Synth: {sentence}")
sink.play_all(synthesizer.generate(sentence))
else:
for sentence in segmenter.drain(None):
self._logger.info(f"Synth: {sentence}")
sink.play_all(synthesizer.generate(sentence))
for buffer in feedback.generate(message):
sink.play(buffer)
if message.msg == LlmMessageType.Stop:
sink.drain()
return
elif message.msg == LlmMessageType.Start:
self._logger.info("Starting playback")
playing = True
elif message.msg == LlmMessageType.End:
self._logger.info("Stopping playback")
playing = False
sink.drain()
ctl.put(Message(CtlMessageType.Listen, None))
@classmethod
def run(cls, config: Config, llm: MessageQueueR[LlmMessageType], ctl: MessageQueueW[CtlMessageType]) -> int:
signal.signal(signal.SIGINT, signal.SIG_IGN) # handled and propagated by llm main
logger: logging.Logger = setup_logging(config.logging, cls.__name__, config.log_level)
with redirect_logging(logger), Rusage(logger, logging.INFO):
try:
factory: Factory = Factory(config)
cls(logger, factory)._run(llm, ctl)
except FactoryError as e:
logger.error(str(e), exc_info=e.__cause__)
return 1
except (ConfigError, ModuleError) as e:
logger.error(str(e))
return 1
except BaseException as e:
logger.error(str(e), exc_info=e)
return 1
else:
return 0
finally:
ctl.put(Message(CtlMessageType.Stop))
ctl.close()
@classmethod
def run_cli(cls, config: Config) -> int:
logger: logging.Logger = setup_logging(config.logging, cls.__name__, config.log_level)
try:
with redirect_logging(logger) as stdout, Rusage(logger, logging.INFO):
multiline: bool = False
prompt_session: PromptSession = PromptSession(multiline=multiline, output=create_output(stdout))
factory: Factory = Factory(config)
with factory.create_synthesizer() as synthesizer:
sink: AudioSink = factory.create_sink_for(synthesizer)
segmenter: SentenceSegmenter = factory.create_sentence_segmenter_for(synthesizer)
with sink:
try:
while True:
prompt: str = prompt_session.prompt(message=[("ansigreen bold", "> ")],
placeholder="<Meta+Enter>" if multiline else None)
if not prompt: # or ^C or ^D
break
for sentence in segmenter.push(prompt):
for buffer in synthesizer.generate(sentence):
sink.play(buffer)
for sentence in segmenter.drain(None):
for buffer in synthesizer.generate(sentence):
sink.play(buffer)
sink.drain()
except (KeyboardInterrupt, EOFError):
return 0
except FactoryError as e:
logger.error(str(e), exc_info=e.__cause__)
return 1
except (ConfigError, ModuleError) as e:
logger.error(str(e))
return 1
except BaseException as e:
logger.error(str(e), exc_info=e)
return 1
else:
return 0