API
personality.py
Go to the documentation of this file.
1 from dataclasses import dataclass
2 from enum import Enum
3 import os.path
4 import purepyindi2
5 from typing import Optional
6 import xml.etree.ElementTree as ET
7 
8 DEFAULT_DEBOUNCE_SEC = 3
9 
11  pass
12 
14  def __init__(self, markup):
15  self.markupmarkup = markup
16  def __repr__(self):
17  return f"{self.__class__.__name__}({repr(self.markup)})"
18  def __eq__(self, other):
19  return getattr(other, 'markup', None) == self.markupmarkup
20 
22  def __init__(self, path):
23  self.pathpath = path
24  def __repr__(self):
25  return f"{self.__class__.__name__}({repr(self.path)})"
26  def __eq__(self, other):
27  return getattr(other, 'path', None) == self.pathpath
28 
29 
30 def xml_to_speechrequest(elem, file_path):
31  if elem.tag == 'file':
32  return Recording(os.path.join(os.path.dirname(file_path), elem.attrib['name']))
33  else:
34  return SSML(ET.tostring(elem, 'utf-8').decode('utf8').strip())
35 
36 class Operation(Enum):
37  EQ = 'eq'
38  LT = 'lt'
39  LE = 'le'
40  GT = 'gt'
41  GE = 'ge'
42  NE = 'ne'
43  BETWEEN = 'between'
44  def __str__(self):
45  return self.value
46 
47 @dataclass(eq=True, frozen=True)
48 class Transition:
49  value : Optional[purepyindi2.AnyIndiValue]
50  value_2 : Optional[purepyindi2.AnyIndiValue]
51  debounce_sec : float = DEFAULT_DEBOUNCE_SEC
52  op : Optional[Operation] = None
53 
54  def compare(self, new_value):
55  if self.op is None:
56  return True
57  if self.op is Operation.EQ:
58  return new_value == self.value
59  elif self.op is Operation.NE:
60  return new_value != self.value
61  else:
62  try:
63  new_value = float(new_value)
64  except (ValueError, TypeError):
65  return False
66  if self.op is Operation.LT:
67  return new_value < self.value
68  elif self.op is Operation.LE:
69  return new_value <= self.value
70  elif self.op is Operation.GT:
71  return new_value > self.value
72  elif self.op is Operation.GE:
73  return new_value >= self.value
74  elif self.op is Operation.BETWEEN:
75  lo = min(self.value, self.value_2)
76  hi = max(self.value, self.value_2)
77  return lo <= new_value < hi
78  return False
79 
80 @dataclass
81 class Reaction:
82  indi_id : str
83  transitions : dict[Transition, list[SpeechRequest]]
84 
85 @dataclass
87  reactions : list[Reaction]
88  default_voice : str
89  random_utterances : list[SpeechRequest]
90  soundboard : dict[str, SpeechRequest]
91 
92  @classmethod
93  def from_path(cls, file_path):
94  tree = ET.parse(file_path)
95  root = tree.getroot()
96  reactions = []
97  random_utterances = []
98  soundboard = {}
99  default_voice = None
100 
101  for el in root:
102  transitions = {}
103  if el.tag == 'default-voice':
104  default_voice = el.attrib['name']
105  continue
106  elif el.tag == 'random-utterances':
107  for utterance in el:
108  random_utterances.append(xml_to_speechrequest(utterance, file_path))
109  continue
110  elif el.tag == 'soundboard':
111  for btn in el:
112  assert len(btn) == 1
113  soundboard[btn.attrib['name']] = xml_to_speechrequest(btn[0], file_path)
114  continue
115  assert el.tag == 'react-to'
116  indi_id = el.attrib['indi-id']
117  for transition in el:
118  assert transition.tag == 'transition'
119  if 'low' in transition.attrib:
120  value = purepyindi2.parse_string_into_any_indi_value(transition.attrib['low'])
121  value_2 = purepyindi2.parse_string_into_any_indi_value(transition.attrib['high'])
122  operation = Operation.BETWEEN
123  elif 'value' in transition.attrib:
124  value = purepyindi2.parse_string_into_any_indi_value(transition.attrib['value'])
125  value_2 = None
126  operation = purepyindi2.parse_string_into_enum(transition.attrib.get('op', 'eq'), Operation)
127  else:
128  value = None
129  value_2 = None
130  operation = None
131  if 'debounce_sec' in transition.attrib:
132  debounce_sec = float(transition.attrib['debounce_sec'])
133  else:
134  debounce_sec = DEFAULT_DEBOUNCE_SEC
135  trans = Transition(op=operation, value=value, value_2=value_2, debounce_sec=debounce_sec)
136  if trans in transitions:
137  raise RuntimeError(f"Multiply defined for {indi_id} {operation=} {value=}")
138  transitions[trans] = []
139  for utterance in transition:
140  assert utterance.tag in ('speak', 'file')
141  transitions[trans].append(xml_to_speechrequest(utterance, file_path))
142  reactions.append(Reaction(indi_id=indi_id, transitions=transitions))
143  return cls(
144  reactions=reactions,
145  default_voice=default_voice,
146  random_utterances=random_utterances,
147  soundboard=soundboard,
148  )
149 
150 if __name__ == "__main__":
151  import pprint
152  pprint.pprint(Personality.from_path('./default.xml'), width=255)
def __init__(self, markup)
Definition: personality.py:14
def xml_to_speechrequest(elem, file_path)
Definition: personality.py:30