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 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
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
150if __name__ == "__main__":
151 import pprint
152 pprint.pprint(Personality.from_path('./default.xml'), width=255)
xml_to_speechrequest(elem, file_path)