Clean up opentts_abc

This commit is contained in:
Michael Hansen 2022-03-25 11:52:23 -04:00
commit bfdc26621e
4 changed files with 318 additions and 179 deletions

View file

@ -0,0 +1,4 @@
# Open Text to Speech (TTS) Abstract Base Classes (ABC)
Base classes for open text to speech systems.

View file

@ -1,68 +1,107 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
"""Base classes for Open Text to Speech systems""" """Base classes for Open Text to Speech systems"""
import dataclasses
import io import io
import typing import typing
import wave import wave
from abc import ABCMeta, abstractmethod from abc import ABCMeta, abstractmethod
from contextlib import AbstractContextManager from contextlib import AbstractContextManager
from copy import deepcopy
from dataclasses import dataclass from dataclasses import dataclass
@dataclass @dataclass
class Settings: class Settings:
"""Current settings for TTS system"""
voice: typing.Optional[str] = None voice: typing.Optional[str] = None
"""Current voice key"""
language: typing.Optional[str] = None language: typing.Optional[str] = None
"""Current language (e.g., en_US)"""
volume: typing.Optional[float] = None volume: typing.Optional[float] = None
"""Current speaking volume"""
rate: typing.Optional[float] = None rate: typing.Optional[float] = None
"""Current speaking rate"""
pitch: typing.Optional[float] = None pitch: typing.Optional[float] = None
active_lexicons: typing.Optional[typing.Sequence[str]] = None """Current speaking pitch"""
other_settings: typing.Optional[typing.Mapping[str, typing.Any]] = None other_settings: typing.Optional[typing.Mapping[str, typing.Any]] = None
"""Custom settings"""
@dataclass @dataclass
class BaseToken(metaclass=ABCMeta): class BaseToken(metaclass=ABCMeta):
"""Base class for spoken tokens"""
text: str text: str
"""Text of the token"""
@dataclass @dataclass
class Word(BaseToken): class Word(BaseToken):
"""Token representing a single word"""
role: typing.Optional[str] = None role: typing.Optional[str] = None
"""Role of the word (typically part of speech)"""
@dataclass @dataclass
class Phonemes(BaseToken): class Phonemes(BaseToken):
"""Token representing a phonemized word"""
alphabet: typing.Optional[str] = None alphabet: typing.Optional[str] = None
"""Phoneme alphabet (e.g., ipa)"""
@dataclass @dataclass
class SayAs(BaseToken): class SayAs(BaseToken):
"""Token representing a word or phrase that must be spoken a particular way"""
interpret_as: str interpret_as: str
"""Implementation-dependent token interpretation (e.g., characters or digits)"""
format: typing.Optional[str] = None format: typing.Optional[str] = None
"""Implementation-dependent token format (depends on interpret_as)"""
@dataclass @dataclass
class _BaseResultDefaults: class _BaseResultDefaults:
"""Base class of results from TTS end_utterance"""
tag: typing.Optional[typing.Any] = None tag: typing.Optional[typing.Any] = None
"""Optional tag to associate with results"""
@dataclass @dataclass
class BaseResult(metaclass=ABCMeta): class BaseResult(metaclass=ABCMeta):
pass """Base class of results from TTS end_utterance"""
@dataclass @dataclass
class _AudioResultBase: class _AudioResultBase:
"""Synthesized audio result"""
sample_rate_hz: int sample_rate_hz: int
"""Sample rate in Hertz (e.g., 22050)"""
sample_width_bytes: int sample_width_bytes: int
"""Sample width in bytes (e.g., 2)"""
num_channels: int num_channels: int
"""Number of audio channels (e.g., 1)"""
audio_bytes: bytes audio_bytes: bytes
"""Raw audio bytes (no header)"""
@dataclass @dataclass
class AudioResult(BaseResult, _BaseResultDefaults, _AudioResultBase): class AudioResult(BaseResult, _BaseResultDefaults, _AudioResultBase):
"""Synthesized audio result"""
def to_wav_bytes(self) -> bytes: def to_wav_bytes(self) -> bytes:
"""Convert audio bytes to WAV"""
with io.BytesIO() as wav_io: with io.BytesIO() as wav_io:
wav_file: wave.Wave_write = wave.open(wav_io, "wb") wav_file: wave.Wave_write = wave.open(wav_io, "wb")
with wav_file: with wav_file:
@ -76,92 +115,126 @@ class AudioResult(BaseResult, _BaseResultDefaults, _AudioResultBase):
@dataclass @dataclass
class _MarkResultBase: class _MarkResultBase:
"""Result indicating a <mark> has been reached in SSML"""
name: str name: str
"""Name of the <mark>"""
@dataclass @dataclass
class MarkResult(BaseResult, _BaseResultDefaults, _MarkResultBase): class MarkResult(BaseResult, _BaseResultDefaults, _MarkResultBase):
pass """Result indicating a <mark> has been reached in SSML"""
@dataclass @dataclass
class Voice: class Voice:
"""Details of a voice in a text to speech system"""
key: str key: str
"""Unique key that can be used to reference the voice"""
name: str name: str
"""Human-readable name of the voice"""
language: str language: str
"""Language of the voice (e.g., en_US)"""
description: str description: str
"""Human-readable description of the voice"""
speakers: typing.Optional[typing.Sequence[str]] = None speakers: typing.Optional[typing.Sequence[str]] = None
"""List of speakers within the voice model if multi-speaker"""
properties: typing.Optional[typing.Mapping[str, typing.Any]] = None properties: typing.Optional[typing.Mapping[str, typing.Any]] = None
"""Additional properties associated with the voice"""
@property @property
def is_multispeaker(self) -> bool: def is_multispeaker(self) -> bool:
"""True if voice has multiple speakers"""
return (self.speakers is not None) and (len(self.speakers) > 1) return (self.speakers is not None) and (len(self.speakers) > 1)
# @dataclass
# class LexiconEntry:
# word: str
# pronunciation: str
# role: typing.Optional[str] = None
# @dataclass
# class Lexicon:
# name: str
# entries: typing.Mapping[str, typing.Sequence[LexiconEntry]]
class TextToSpeechSystem(AbstractContextManager, metaclass=ABCMeta): class TextToSpeechSystem(AbstractContextManager, metaclass=ABCMeta):
"""Abstract base class for open text to speech systems""" """Abstract base class for open text to speech systems.
Expected usage:
begin_utterance()
speak_text(...)
add_break(...)
set_mark(...)
speak_tokens(...)
speak_text(...)
results = end_utterance()
In between begin_utterance() and end_utterance(), the voice/language may
also be changed.
"""
@property @property
@abstractmethod @abstractmethod
def voice(self) -> str: def voice(self) -> str:
pass """Get the current voice key"""
@voice.setter @voice.setter
def voice(self, new_voice: str): def voice(self, new_voice: str):
pass """Set the current voice key"""
@property @property
@abstractmethod @abstractmethod
def language(self) -> str: def language(self) -> str:
pass """Get the current voice language"""
@language.setter @language.setter
def language(self, new_language: str): def language(self, new_language: str):
pass """Set the current voice language"""
def shutdown(self): def shutdown(self):
pass """Called by the host program when the text to speech system should be stopped"""
def __exit__(self, exc_type, exc_value, traceback): def __exit__(self, exc_type, exc_value, traceback):
"""Automatically call shutdown when context manager has exited"""
self.shutdown() self.shutdown()
@abstractmethod @abstractmethod
def get_voices(self) -> typing.Iterable[Voice]: def get_voices(self) -> typing.Iterable[Voice]:
pass """Returns an iterable of available voices"""
@abstractmethod @abstractmethod
def begin_utterance(self): def begin_utterance(self):
pass """Begins a new utterance"""
@abstractmethod @abstractmethod
def speak_text(self, text: str): def speak_text(self, text: str):
pass """Speaks text using the underlying system's tokenization mechanism.
Becomes an AudioResult in end_utterance()
"""
@abstractmethod @abstractmethod
def speak_tokens(self, tokens: typing.Iterable[BaseToken]): def speak_tokens(self, tokens: typing.Iterable[BaseToken]):
pass """Speak user-defined tokens.
Becomes an AudioResult in end_utterance()
"""
@abstractmethod @abstractmethod
def add_break(self, time_ms: int): def add_break(self, time_ms: int):
pass """Add milliseconds of silence to the current utterance.
Becomes an AudioResult in end_utterance()
"""
@abstractmethod @abstractmethod
def set_mark(self, name: str): def set_mark(self, name: str):
pass """Set a named mark at this point in the utterance.
Becomes a MarkResult in end_utterance()
"""
@abstractmethod @abstractmethod
def end_utterance(self) -> typing.Iterable[BaseResult]: def end_utterance(self) -> typing.Iterable[BaseResult]:
pass """Complete an utterance after begin_utterance().
Returns an iterable of results (audio, marks, etc.)
"""

View file

@ -1,4 +1,5 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
"""Support for Speech Synthesis Markup Language (SSML)"""
import enum import enum
import logging import logging
import re import re
@ -6,14 +7,7 @@ import typing
import xml.etree.ElementTree as etree import xml.etree.ElementTree as etree
from dataclasses import dataclass from dataclasses import dataclass
from opentts_abc import ( from opentts_abc import BaseResult, Phonemes, SayAs, TextToSpeechSystem, Word
BaseResult,
Phonemes,
SayAs,
Settings,
TextToSpeechSystem,
Word,
)
LOG = logging.getLogger("opentts_abc.ssml") LOG = logging.getLogger("opentts_abc.ssml")
NO_NAMESPACE_PATTERN = re.compile(r"^{[^}]+}") NO_NAMESPACE_PATTERN = re.compile(r"^{[^}]+}")
@ -43,15 +37,6 @@ class ParsingState(int, enum.Enum):
IN_PHONEME = enum.auto() IN_PHONEME = enum.auto()
"""Inside <phoneme>""" """Inside <phoneme>"""
IN_LEXICON = enum.auto()
"""Inside <lexicon>"""
IN_LEXICON_GRAPHEME = enum.auto()
"""Inside <lexicon><grapheme>..."""
IN_LEXICON_PHONEME = enum.auto()
"""Inside <lexicon><phoneme>..."""
IN_METADATA = enum.auto() IN_METADATA = enum.auto()
"""Inside <metadata>""" """Inside <metadata>"""
@ -63,18 +48,24 @@ class ParsingState(int, enum.Enum):
class SSMLSpeaker: class SSMLSpeaker:
"""Wrapper for TextToSpeechSystem that parses/implements SSML.
See: https://www.w3.org/TR/speech-synthesis11/
"""
def __init__(self, tts: TextToSpeechSystem): def __init__(self, tts: TextToSpeechSystem):
self.state_stack: typing.List[ParsingState] = [ParsingState.DEFAULT] self._state_stack: typing.List[ParsingState] = [ParsingState.DEFAULT]
self.element_stack: typing.List[etree.Element] = [] self._element_stack: typing.List[etree.Element] = []
self.voice_stack: typing.List[str] = [] self._voice_stack: typing.List[str] = []
self.lang_stack: typing.List[str] = [] self._lang_stack: typing.List[str] = []
self.interpret_as: typing.Optional[str] = None self._interpret_as: typing.Optional[str] = None
self.say_as_format: typing.Optional[str] = None self._say_as_format: typing.Optional[str] = None
self.tts = tts self.tts = tts
def speak( def speak(
self, ssml: typing.Union[str, etree.Element] self, ssml: typing.Union[str, etree.Element]
) -> typing.Iterable[BaseResult]: ) -> typing.Iterable[BaseResult]:
"""Parses and realizes a set of SSML utterances using the underlying TextToSpeechSystem"""
if isinstance(ssml, etree.Element): if isinstance(ssml, etree.Element):
root_element = ssml root_element = ssml
@ -84,39 +75,39 @@ class SSMLSpeaker:
# Process sub-elements and text chunks # Process sub-elements and text chunks
for elem_or_text in text_and_elements(root_element): for elem_or_text in text_and_elements(root_element):
if isinstance(elem_or_text, str): if isinstance(elem_or_text, str):
if self.state in {ParsingState.IN_METADATA}: if self._state in {ParsingState.IN_METADATA}:
# Skip metadata text # Skip metadata text
continue continue
# Text chunk # Text chunk
text = typing.cast(str, elem_or_text) text = typing.cast(str, elem_or_text)
self.handle_text(text) self._handle_text(text)
elif isinstance(elem_or_text, EndElement): elif isinstance(elem_or_text, EndElement):
# End of an element (e.g., </w>) # End of an element (e.g., </w>)
end_elem = typing.cast(EndElement, elem_or_text) end_elem = typing.cast(EndElement, elem_or_text)
end_tag = tag_no_namespace(end_elem.element.tag) end_tag = tag_no_namespace(end_elem.element.tag)
if end_tag == "s": if end_tag == "s":
yield from self.handle_end_sentence() yield from self._handle_end_sentence()
elif end_tag in {"w", "token"}: elif end_tag in {"w", "token"}:
self.handle_end_word() self._handle_end_word()
elif end_tag in {"phoneme"}: elif end_tag in {"phoneme"}:
self.handle_end_phoneme() self._handle_end_phoneme()
elif end_tag == "voice": elif end_tag == "voice":
self.handle_end_voice() self._handle_end_voice()
elif end_tag == "say-as": elif end_tag == "say-as":
self.handle_end_say_as() self._handle_end_say_as()
elif end_tag in {"sub"}: elif end_tag in {"sub"}:
# Handled in handle_text # Handled in handle_text
pass pass
elif end_tag in {"metadata", "meta"}: elif end_tag in {"metadata", "meta"}:
self.handle_end_metadata() self._handle_end_metadata()
elif end_tag == "speak": elif end_tag == "speak":
yield from self.handle_end_speak() yield from self._handle_end_speak()
else: else:
LOG.debug("Ignoring end tag: %s", end_tag) LOG.debug("Ignoring end tag: %s", end_tag)
else: else:
if self.state in {ParsingState.IN_METADATA}: if self._state in {ParsingState.IN_METADATA}:
# Skip metadata text # Skip metadata text
continue continue
@ -132,86 +123,90 @@ class SSMLSpeaker:
elem_tag = tag_no_namespace(elem.tag) elem_tag = tag_no_namespace(elem.tag)
if elem_tag == "s": if elem_tag == "s":
self.handle_begin_sentence() self._handle_begin_sentence()
elif elem_tag in {"w", "token"}: elif elem_tag in {"w", "token"}:
self.handle_begin_word(elem) self._handle_begin_word(elem)
elif elem_tag == "sub": elif elem_tag == "sub":
self.handle_begin_sub(elem) self._handle_begin_sub(elem)
elif elem_tag == "phoneme": elif elem_tag == "phoneme":
self.handle_begin_phoneme(elem) self._handle_begin_phoneme(elem)
elif elem_tag == "break": elif elem_tag == "break":
self.handle_break(elem) self._handle_break(elem)
elif elem_tag == "mark": elif elem_tag == "mark":
self.handle_mark(elem) self._handle_mark(elem)
elif elem_tag == "voice": elif elem_tag == "voice":
self.handle_begin_voice(elem) self._handle_begin_voice(elem)
elif elem_tag == "say-as": elif elem_tag == "say-as":
self.handle_begin_say_as(elem) self._handle_begin_say_as(elem)
elif elem_tag in {"metadata", "meta"}: elif elem_tag in {"metadata", "meta"}:
self.handle_begin_metadata() self._handle_begin_metadata()
else: else:
LOG.debug("Ignoring start tag: %s", elem_tag) LOG.debug("Ignoring start tag: %s", elem_tag)
assert self.state in { assert self._state in {
ParsingState.IN_SENTENCE, ParsingState.IN_SENTENCE,
ParsingState.DEFAULT, ParsingState.DEFAULT,
}, self.state }, self._state
if self.state in {ParsingState.IN_SENTENCE}:
yield from self.handle_end_sentence() if self._state in {ParsingState.IN_SENTENCE}:
yield from self._handle_end_sentence()
# ------------------------------------------------------------------------- # -------------------------------------------------------------------------
def handle_text(self, text: str): def _handle_text(self, text: str):
assert self.state in { """Handle sentence/word text"""
assert self._state in {
ParsingState.DEFAULT, ParsingState.DEFAULT,
ParsingState.IN_SENTENCE, ParsingState.IN_SENTENCE,
ParsingState.IN_WORD, ParsingState.IN_WORD,
ParsingState.IN_SUB, ParsingState.IN_SUB,
ParsingState.IN_PHONEME, ParsingState.IN_PHONEME,
ParsingState.IN_SAY_AS, ParsingState.IN_SAY_AS,
}, self.state }, self._state
if self.state == ParsingState.IN_PHONEME: if self._state == ParsingState.IN_PHONEME:
# Phonemes were emitted in handle_begin_phoneme # Phonemes were emitted in handle_begin_phoneme
return return
if self.state == ParsingState.IN_SUB: if self._state == ParsingState.IN_SUB:
# Substitute text # Substitute text
assert self.element is not None assert self._element is not None
text = attrib_no_namespace(self.element, "alias", "") text = attrib_no_namespace(self._element, "alias", "")
LOG.debug("alias text: %s", text) LOG.debug("alias text: %s", text)
# Terminate <sub> early # Terminate <sub> early
self.handle_end_sub() self._handle_end_sub()
if self.state == ParsingState.DEFAULT: if self._state == ParsingState.DEFAULT:
self.handle_begin_sentence() self._handle_begin_sentence()
LOG.debug("text: %s", text) LOG.debug("text: %s", text)
if self.state == ParsingState.IN_WORD: if self._state == ParsingState.IN_WORD:
self.handle_word(text, self.element) self._handle_word(text, self._element)
elif self.state == ParsingState.IN_SAY_AS: elif self._state == ParsingState.IN_SAY_AS:
assert self.interpret_as is not None assert self._interpret_as is not None
self.tts.speak_tokens( self.tts.speak_tokens(
[ [
SayAs( SayAs(
text=text, text=text,
interpret_as=self.interpret_as, interpret_as=self._interpret_as,
format=self.say_as_format, format=self._say_as_format,
) )
] ]
) )
else: else:
self.tts.speak_text(text) self.tts.speak_text(text)
def handle_begin_word(self, elem: etree.Element): def _handle_begin_word(self, elem: etree.Element):
"""Handle <w> or <t>"""
LOG.debug("begin word") LOG.debug("begin word")
self.push_element(elem) self._push_element(elem)
self.push_state(ParsingState.IN_WORD) self._push_state(ParsingState.IN_WORD)
def handle_word(self, text: str, elem: typing.Optional[etree.Element] = None): def _handle_word(self, text: str, elem: typing.Optional[etree.Element] = None):
assert self.state in {ParsingState.IN_WORD}, self.state """Handle text from word"""
assert self._state in {ParsingState.IN_WORD}, self._state
role: typing.Optional[str] = None role: typing.Optional[str] = None
if elem is not None: if elem is not None:
@ -219,28 +214,32 @@ class SSMLSpeaker:
self.tts.speak_tokens([Word(text, role=role)]) self.tts.speak_tokens([Word(text, role=role)])
def handle_end_word(self): def _handle_end_word(self):
"""Handle </w> or </t>"""
LOG.debug("end word") LOG.debug("end word")
assert self.state in {ParsingState.IN_WORD}, self.state assert self._state in {ParsingState.IN_WORD}, self._state
self.pop_state() self._pop_state()
self.pop_element() self._pop_element()
def handle_begin_sub(self, elem: etree.Element): def _handle_begin_sub(self, elem: etree.Element):
"""Handle <sub>"""
LOG.debug("begin sub") LOG.debug("begin sub")
self.push_element(elem) self._push_element(elem)
self.push_state(ParsingState.IN_SUB) self._push_state(ParsingState.IN_SUB)
def handle_end_sub(self): def _handle_end_sub(self):
"""Handle </sub>"""
LOG.debug("end sub") LOG.debug("end sub")
assert self.state in {ParsingState.IN_SUB}, self.state assert self._state in {ParsingState.IN_SUB}, self._state
self.pop_state() self._pop_state()
self.pop_element() self._pop_element()
def handle_begin_phoneme(self, elem: etree.Element): def _handle_begin_phoneme(self, elem: etree.Element):
"""Handle <phoneme>"""
LOG.debug("begin phoneme") LOG.debug("begin phoneme")
if self.state == ParsingState.DEFAULT: if self._state == ParsingState.DEFAULT:
self.handle_begin_sentence() self._handle_begin_sentence()
phonemes = attrib_no_namespace(elem, "ph", "") phonemes = attrib_no_namespace(elem, "ph", "")
alphabet = attrib_no_namespace(elem, "alphabet", "") alphabet = attrib_no_namespace(elem, "alphabet", "")
@ -249,61 +248,70 @@ class SSMLSpeaker:
self.tts.speak_tokens([Phonemes(text=phonemes, alphabet=alphabet)]) self.tts.speak_tokens([Phonemes(text=phonemes, alphabet=alphabet)])
self.push_element(elem) self._push_element(elem)
self.push_state(ParsingState.IN_PHONEME) self._push_state(ParsingState.IN_PHONEME)
def handle_end_phoneme(self): def _handle_end_phoneme(self):
"""Handle </phoneme>"""
LOG.debug("end phoneme") LOG.debug("end phoneme")
assert self.state in {ParsingState.IN_PHONEME}, self.state assert self._state in {ParsingState.IN_PHONEME}, self._state
self.pop_state() self._pop_state()
self.pop_element() self._pop_element()
def handle_begin_metadata(self): def _handle_begin_metadata(self):
"""Handle <metadata>"""
LOG.debug("begin metadata") LOG.debug("begin metadata")
self.push_state(ParsingState.IN_METADATA) self._push_state(ParsingState.IN_METADATA)
def handle_end_metadata(self): def _handle_end_metadata(self):
"""Handle </metadata>"""
LOG.debug("end metadata") LOG.debug("end metadata")
assert self.state in {ParsingState.IN_METADATA}, self.state assert self._state in {ParsingState.IN_METADATA}, self._state
self.pop_state() self._pop_state()
def handle_begin_sentence(self): def _handle_begin_sentence(self):
"""Handle <s>"""
LOG.debug("begin sentence") LOG.debug("begin sentence")
assert self.state in {ParsingState.DEFAULT}, self.state assert self._state in {ParsingState.DEFAULT}, self._state
self.push_state(ParsingState.IN_SENTENCE) self._push_state(ParsingState.IN_SENTENCE)
self.tts.begin_utterance() self.tts.begin_utterance()
def handle_end_sentence(self) -> typing.Iterable[BaseResult]: def _handle_end_sentence(self) -> typing.Iterable[BaseResult]:
"""Handle </s>"""
LOG.debug("end sentence") LOG.debug("end sentence")
assert self.state in {ParsingState.IN_SENTENCE}, self.state assert self._state in {ParsingState.IN_SENTENCE}, self._state
self.pop_state() self._pop_state()
yield from self.tts.end_utterance() yield from self.tts.end_utterance()
def handle_end_speak(self) -> typing.Iterable[BaseResult]: def _handle_end_speak(self) -> typing.Iterable[BaseResult]:
"""Handle </speak>"""
LOG.debug("end speak") LOG.debug("end speak")
assert self.state in {ParsingState.DEFAULT}, self.state assert self._state in {ParsingState.DEFAULT}, self._state
yield from self.tts.end_utterance() yield from self.tts.end_utterance()
def handle_begin_voice(self, elem: etree.Element): def _handle_begin_voice(self, elem: etree.Element):
"""Handle <voice>"""
LOG.debug("begin voice") LOG.debug("begin voice")
voice_name = attrib_no_namespace(elem, "name") voice_name = attrib_no_namespace(elem, "name")
LOG.debug("voice: %s", voice_name) LOG.debug("voice: %s", voice_name)
self.push_voice(voice_name) self._push_voice(voice_name)
# Set new voice # Set new voice
self.tts.voice = voice_name self.tts.voice = voice_name
def handle_end_voice(self): def _handle_end_voice(self):
"""Handle </voice>"""
LOG.debug("end voice") LOG.debug("end voice")
voice_name = self.pop_voice() voice_name = self._pop_voice()
# Restore voice # Restore voice
self.tts.voice = voice_name self.tts.voice = voice_name
def handle_break(self, elem: etree.Element): def _handle_break(self, elem: etree.Element):
"""Handle <break>"""
time_str = attrib_no_namespace(elem, "time", "").strip() time_str = attrib_no_namespace(elem, "time", "").strip()
time_ms: int = 0 time_ms: int = 0
@ -316,90 +324,105 @@ class SSMLSpeaker:
LOG.debug("Break: %s ms", time_ms) LOG.debug("Break: %s ms", time_ms)
self.tts.add_break(time_ms) self.tts.add_break(time_ms)
def handle_mark(self, elem: etree.Element): def _handle_mark(self, elem: etree.Element):
"""Handle <mark>"""
name = attrib_no_namespace(elem, "name", "") name = attrib_no_namespace(elem, "name", "")
LOG.debug("Mark: %s", name) LOG.debug("Mark: %s", name)
self.tts.set_mark(name) self.tts.set_mark(name)
def handle_begin_say_as(self, elem: etree.Element): def _handle_begin_say_as(self, elem: etree.Element):
"""Handle <say-as>"""
LOG.debug("begin say-as") LOG.debug("begin say-as")
self.interpret_as = attrib_no_namespace(elem, "interpret-as", "") self._interpret_as = attrib_no_namespace(elem, "interpret-as", "")
self.say_as_format = attrib_no_namespace(elem, "format", "") self._say_as_format = attrib_no_namespace(elem, "format", "")
LOG.debug("Say as %s, format=%s", self.interpret_as, self.say_as_format) LOG.debug("Say as %s, format=%s", self._interpret_as, self._say_as_format)
self.push_state(ParsingState.IN_SAY_AS) self._push_state(ParsingState.IN_SAY_AS)
def handle_end_say_as(self): def _handle_end_say_as(self):
"""Handle </say-as>"""
LOG.debug("end say-as") LOG.debug("end say-as")
assert self.state in {ParsingState.IN_SAY_AS} assert self._state in {ParsingState.IN_SAY_AS}
self.interpret_as = None self._interpret_as = None
self.say_as_format = None self._say_as_format = None
self.pop_state() self._pop_state()
# ------------------------------------------------------------------------- # -------------------------------------------------------------------------
@property @property
def state(self) -> ParsingState: def _state(self) -> ParsingState:
if self.state_stack: """Get state at the top of the stack"""
return self.state_stack[-1] if self._state_stack:
return self._state_stack[-1]
return ParsingState.DEFAULT return ParsingState.DEFAULT
def push_state(self, new_state: ParsingState): def _push_state(self, new_state: ParsingState):
self.state_stack.append(new_state) """Push new state on to the stack"""
self._state_stack.append(new_state)
def pop_state(self) -> ParsingState: def _pop_state(self) -> ParsingState:
if self.state_stack: """Pop state off the stack"""
return self.state_stack.pop() if self._state_stack:
return self._state_stack.pop()
return ParsingState.DEFAULT return ParsingState.DEFAULT
@property @property
def element(self) -> typing.Optional[etree.Element]: def _element(self) -> typing.Optional[etree.Element]:
if self.element_stack: """Get XML element at the top of the stack"""
return self.element_stack[-1] if self._element_stack:
return self._element_stack[-1]
return None return None
def push_element(self, new_element: etree.Element): def _push_element(self, new_element: etree.Element):
self.element_stack.append(new_element) """Push new XML element on to the stack"""
self._element_stack.append(new_element)
def pop_element(self) -> typing.Optional[etree.Element]: def _pop_element(self) -> typing.Optional[etree.Element]:
if self.element_stack: """Pop XML element off the stack"""
return self.element_stack.pop() if self._element_stack:
return self._element_stack.pop()
return None return None
@property @property
def lang(self) -> typing.Optional[str]: def _lang(self) -> typing.Optional[str]:
if self.lang_stack: """Get language at the top of the stack"""
return self.lang_stack[-1] if self._lang_stack:
return self._lang_stack[-1]
return self.tts.language return self.tts.language
def push_lang(self, new_lang: str): def _push_lang(self, new_lang: str):
self.lang_stack.append(new_lang) """Push new language on to the stack"""
self._lang_stack.append(new_lang)
def pop_lang(self) -> typing.Optional[str]: def _pop_lang(self) -> typing.Optional[str]:
if self.lang_stack: """Pop language off the stop of the stack"""
return self.lang_stack.pop() if self._lang_stack:
return self._lang_stack.pop()
return self.tts.language return self.tts.language
@property @property
def voice(self) -> typing.Optional[str]: def _voice(self) -> typing.Optional[str]:
if self.voice_stack: """Get voice at the top of the stack"""
return self.voice_stack[-1] if self._voice_stack:
return self._voice_stack[-1]
return self.tts.voice return self.tts.voice
def push_voice(self, new_voice: str): def _push_voice(self, new_voice: str):
self.voice_stack.append(new_voice) """Push new voice on to the stack"""
self._voice_stack.append(new_voice)
def pop_voice(self) -> typing.Optional[str]: def _pop_voice(self) -> typing.Optional[str]:
if self.voice_stack: """Pop voice off the top of the stack"""
return self.voice_stack.pop() if self._voice_stack:
return self._voice_stack.pop()
return self.tts.voice return self.tts.voice

39
opentts-abc/pylintrc Normal file
View file

@ -0,0 +1,39 @@
[MESSAGES CONTROL]
disable=
format,
abstract-class-little-used,
abstract-method,
cyclic-import,
duplicate-code,
global-statement,
import-outside-toplevel,
inconsistent-return-statements,
locally-disabled,
not-context-manager,
redefined-variable-type,
too-few-public-methods,
too-many-arguments,
too-many-branches,
too-many-instance-attributes,
too-many-lines,
too-many-locals,
too-many-public-methods,
too-many-return-statements,
too-many-statements,
too-many-boolean-expressions,
unnecessary-pass,
unused-argument,
broad-except,
too-many-nested-blocks,
invalid-name,
unused-import,
no-self-use,
fixme,
useless-super-delegation,
missing-module-docstring,
missing-class-docstring,
missing-function-docstring,
import-error
[FORMAT]
expected-line-ending-format=LF