API
 
Loading...
Searching...
No Matches
personality.py
Go to the documentation of this file.
1from dataclasses import dataclass
2from enum import Enum
3import os.path
4import purepyindi2
5from typing import Optional
6import xml.etree.ElementTree as ET
7
8DEFAULT_DEBOUNCE_SEC = 3
9
11 pass
12
14 def __init__(self, markup):
15 self.markup = 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.markup
20
22 def __init__(self, path):
23 self.path = 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.path
28
29
30def 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
36class 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)
49 # id duplicated here from Reaction because Transition is used as a dict key and we need uniqueness:
50 indi_id : str
51 value : Optional[purepyindi2.AnyIndiValue]
52 value_2 : Optional[purepyindi2.AnyIndiValue]
53 debounce_sec : float = DEFAULT_DEBOUNCE_SEC
54 op : Optional[Operation] = None
55
56 def compare(self, new_value):
57 if self.op is None:
58 return True
59 if self.op is Operation.EQ:
60 return new_value == self.value
61 elif self.op is Operation.NE:
62 return new_value != self.value
63 else:
64 try:
65 new_value = float(new_value)
66 except (ValueError, TypeError):
67 return False
68 if self.op is Operation.LT:
69 return new_value < self.value
70 elif self.op is Operation.LE:
71 return new_value <= self.value
72 elif self.op is Operation.GT:
73 return new_value > self.value
74 elif self.op is Operation.GE:
75 return new_value >= self.value
76 elif self.op is Operation.BETWEEN:
77 lo = min(self.value, self.value_2)
78 hi = max(self.value, self.value_2)
79 return lo <= new_value < hi
80 return False
81
82@dataclass
84 indi_id : str
85 transitions : dict[Transition, list[SpeechRequest]]
86
87@dataclass
89 reactions : list[Reaction]
90 default_voice : str
91 random_utterances : list[SpeechRequest]
92 soundboard : dict[str, SpeechRequest]
93 walkups : dict[str, list[SpeechRequest]]
94
95 @classmethod
96 def from_path(cls, file_path):
97 tree = ET.parse(file_path)
98 root = tree.getroot()
99 reactions = []
100 random_utterances = []
101 soundboard = {}
102 default_voice = None
103 walkups = {}
104
105 for el in root:
106 transitions = {}
107 if el.tag == 'default-voice':
108 default_voice = el.attrib['name']
109 continue
110 elif el.tag == 'random-utterances':
111 for utterance in el:
112 random_utterances.append(xml_to_speechrequest(utterance, file_path))
113 continue
114 elif el.tag == 'soundboard':
115 for btn in el:
116 assert len(btn) == 1
117 soundboard[btn.attrib['name']] = xml_to_speechrequest(btn[0], file_path)
118 continue
119 elif el.tag == 'walk-ups':
120 for wup in el:
121 assert wup.tag == 'walk-up'
122 email = wup.attrib['email'].replace('.', '-dot-').replace('@', '-at-')
123 walkups[email] = []
124 for utterance in wup:
125 assert utterance.tag in ('speak', 'file')
126 walkups[email].append(xml_to_speechrequest(utterance, file_path))
127 continue
128 assert el.tag == 'react-to'
129 indi_id = el.attrib['indi-id']
130 for transition in el:
131 assert transition.tag == 'transition'
132 if 'low' in transition.attrib:
133 value = purepyindi2.parse_string_into_any_indi_value(transition.attrib['low'])
134 value_2 = purepyindi2.parse_string_into_any_indi_value(transition.attrib['high'])
135 operation = Operation.BETWEEN
136 elif 'value' in transition.attrib:
137 value = purepyindi2.parse_string_into_any_indi_value(transition.attrib['value'])
138 value_2 = None
139 operation = purepyindi2.parse_string_into_enum(transition.attrib.get('op', 'eq'), Operation)
140 else:
141 value = None
142 value_2 = None
143 operation = None
144 if 'debounce_sec' in transition.attrib:
145 debounce_sec = float(transition.attrib['debounce_sec'])
146 else:
147 debounce_sec = DEFAULT_DEBOUNCE_SEC
148 trans = Transition(indi_id=indi_id, op=operation, value=value, value_2=value_2, debounce_sec=debounce_sec)
149 if trans in transitions:
150 raise RuntimeError(f"Multiply defined for {indi_id} {operation=} {value=}")
151 transitions[trans] = []
152 for utterance in transition:
153 assert utterance.tag in ('speak', 'file')
154 transitions[trans].append(xml_to_speechrequest(utterance, file_path))
155 reactions.append(Reaction(indi_id=indi_id, transitions=transitions))
156 return cls(
157 reactions=reactions,
158 default_voice=default_voice,
159 random_utterances=random_utterances,
160 soundboard=soundboard,
161 walkups=walkups,
162 )
163
164if __name__ == "__main__":
165 import pprint
166 pprint.pprint(Personality.from_path('personalities/default.xml'), width=255)
xml_to_speechrequest(elem, file_path)