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
5import logging
6from typing import Optional
7import xml.etree.ElementTree as ET
8
9log = logging.getLogger(__name__)
10
11DEFAULT_DEBOUNCE_SEC = 3
12
14 pass
15
17 def __init__(self, markup):
18 self.markup = markup
19 def __repr__(self):
20 return f"{self.__class__.__name__}({repr(self.markup)})"
21 def __eq__(self, other):
22 return getattr(other, 'markup', None) == self.markup
23
25 def __init__(self, path):
26 self.path = path
27 def __repr__(self):
28 return f"{self.__class__.__name__}({repr(self.path)})"
29 def __eq__(self, other):
30 return getattr(other, 'path', None) == self.path
31
32
33def xml_to_speechrequest(elem, file_path):
34 if elem.tag == 'file':
35 return Recording(os.path.join(os.path.dirname(file_path), elem.attrib['name']))
36 else:
37 return SSML(ET.tostring(elem, 'utf-8').decode('utf8').strip())
38
39class Operation(Enum):
40 EQ = 'eq'
41 LT = 'lt'
42 LE = 'le'
43 GT = 'gt'
44 GE = 'ge'
45 NE = 'ne'
46 BETWEEN = 'between'
47 def __str__(self):
48 return self.value
49
50@dataclass(eq=True, frozen=True)
52 # id duplicated here from Reaction because Transition is used as a dict key and we need uniqueness:
53 indi_id : str
54 value : Optional[purepyindi2.AnyIndiValue]
55 value_2 : Optional[purepyindi2.AnyIndiValue]
56 debounce_sec : float = DEFAULT_DEBOUNCE_SEC
57 op : Optional[Operation] = None
58
59 def compare(self, new_value):
60 if self.op is None:
61 return True
62 if self.op is Operation.EQ:
63 return new_value == self.value
64 elif self.op is Operation.NE:
65 return new_value != self.value
66 else:
67 try:
68 new_value = float(new_value)
69 except (ValueError, TypeError):
70 return False
71 if self.op is Operation.LT:
72 return new_value < self.value
73 elif self.op is Operation.LE:
74 return new_value <= self.value
75 elif self.op is Operation.GT:
76 return new_value > self.value
77 elif self.op is Operation.GE:
78 return new_value >= self.value
79 elif self.op is Operation.BETWEEN:
80 lo = min(self.value, self.value_2)
81 hi = max(self.value, self.value_2)
82 return lo <= new_value < hi
83 return False
84
85@dataclass
87 indi_id : str
88 transitions : dict[Transition, list[SpeechRequest]]
89
90@dataclass
92 reactions : list[Reaction]
93 default_voice : str
94 random_utterances : list[SpeechRequest]
95 soundboard : dict[str, SpeechRequest]
96 walkups : dict[str, list[SpeechRequest]]
97
98 @classmethod
99 def from_path(cls, file_path):
100 tree = ET.parse(file_path)
101 root = tree.getroot()
102 reactions = []
103 random_utterances = []
104 soundboard = {}
105 default_voice = None
106 walkups = {}
107
108 for el in root:
109 transitions = {}
110 if el.tag == 'default-voice':
111 default_voice = el.attrib['name']
112 continue
113 elif el.tag == 'random-utterances':
114 for utterance in el:
116 continue
117 elif el.tag == 'soundboard':
118 for btn in el:
119 assert len(btn) == 1
120 soundboard[btn.attrib['name']] = xml_to_speechrequest(btn[0], file_path)
121 continue
122 elif el.tag == 'walk-ups':
123 for wup in el:
124 assert wup.tag == 'walk-up'
125 email = wup.attrib['email'].replace('.', '-dot-').replace('@', '-at-')
126 walkups[email] = []
127 for utterance in wup:
128 assert utterance.tag in ('speak', 'file')
129 walkups[email].append(xml_to_speechrequest(utterance, file_path))
130 continue
131 assert el.tag == 'react-to'
132 indi_id = el.attrib['indi-id']
133 for transition in el:
134 assert transition.tag == 'transition'
135 if 'low' in transition.attrib:
138 operation = Operation.BETWEEN
139 elif 'value' in transition.attrib:
141 value_2 = None
142 operation = purepyindi2.parse_string_into_enum(transition.attrib.get('op', 'eq'), Operation)
143 else:
144 value = None
145 value_2 = None
146 operation = None
147 if 'debounce_sec' in transition.attrib:
148 debounce_sec = float(transition.attrib['debounce_sec'])
149 else:
150 debounce_sec = DEFAULT_DEBOUNCE_SEC
151 trans = Transition(indi_id=indi_id, op=operation, value=value, value_2=value_2, debounce_sec=debounce_sec)
152 if trans in transitions:
153 raise RuntimeError(f"Multiply defined for {indi_id} {operation=} {value=}")
154 transitions[trans] = []
155 for utterance in transition:
156 if utterance.tag not in ('speak', 'file'):
157 log.warning(f"{transition}: {utterance} not 'speak' or 'file'?")
158 continue
159 transitions[trans].append(xml_to_speechrequest(utterance, file_path))
160 reactions.append(Reaction(indi_id=indi_id, transitions=transitions))
161 return cls(
162 reactions=reactions,
163 default_voice=default_voice,
164 random_utterances=random_utterances,
165 soundboard=soundboard,
166 walkups=walkups,
167 )
168
169if __name__ == "__main__":
170 import pprint
171 pprint.pprint(Personality.from_path('personalities/default.xml'), width=255)
xml_to_speechrequest(elem, file_path)