~martijnbraam/pyatem

ref: caf4092631dda3c8865e8174215e89c47a1fb590 pyatem/pyatem/macrocommand.py -rw-r--r-- 4.9 KiB
caf40926Martijn Braam openswitcher: fix loading of the first volume control 8 months ago
                                                                                
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
import shlex
import struct


class BaseMacroCommand:
    NAME = "Unknown action"
    TAG = "unknown-action"

    def __init__(self):
        self.actions = {}
        self.fields = {}
        self.lengths = {}
        self.widgets = {}

    def define_field(self, action, name, offset, packing):
        if action not in self.fields:
            self.fields[action] = []

        self.fields[action].append((offset, name, packing))

    def field_length(self, action, length):
        # Store action length without header
        self.lengths[action] = length - 4

    def add_action(self, action_id, raw):
        self.actions[action_id] = raw[4:]

    def add_widget(self, action_id, name, label, datatype, **kwargs):
        if action_id not in self.widgets:
            self.widgets[action_id] = []
        self.widgets[action_id].append((name, datatype, label, kwargs))

    def definition(self):
        pass

    def make_format(self, action):
        f = '<'
        current_offset = 0
        for offset, name, packing in sorted(self.fields[action]):
            if offset > current_offset:
                f += f'{offset - current_offset}x '
                current_offset = offset
            f += f'{packing} '
            current_offset += struct.calcsize(packing)
        if action in self.lengths:
            if current_offset < self.lengths[action]:
                f += '{}x'.format(self.lengths[action] - current_offset)
        return f

    def decode(self):
        self.definition()
        for action in self.fields:
            f = self.make_format(action)
            result = struct.unpack_from(f, self.actions[action], 0)
            for idx, field in enumerate(sorted(self.fields[action])):
                setattr(self, field[1], result[idx])

    def encode(self):
        result = b''
        for action in self.fields:
            f = self.make_format(action)
            fields = []
            for idx, field in enumerate(sorted(self.fields[action])):
                fields.append(getattr(self, field[1]))
            raw = struct.pack(f, *fields)
            header = struct.pack('<HH', self.lengths[action] + 4, action)
            result += header + raw
        return result

    def encode_script(self):
        result = self.__class__.TAG
        data = {}
        for action in self.fields:
            for offset, name, packing in self.fields[action]:
                data[name] = getattr(self, name)
        if len(data):
            parts = []
            for key in data:
                part = key + '='
                if isinstance(data[key], int) or isinstance(data[key], float) or isinstance(data[key], bool):
                    part += str(data[key])
                elif data[key] is None:
                    continue
                else:
                    part += '"' + str(data[key]).replace("\n", "\\n") + '"'
                parts.append(part)
            result += ' ' + ' '.join(parts)
        return result + "\n"

    def decode_script(self, raw_data):
        parts = shlex.split(raw_data, posix=False)
        for part in parts:
            key, value = part.split('=', maxsplit=1)
            if value.startswith('"'):
                value = value[1:-1]
            elif value == "False" or value == "True":
                value = value == "True"
            elif '.' in value:
                value = float(value)
            elif value.isnumeric():
                value = int(value)
            setattr(self, key, value)

    def __repr__(self):
        for a in self.actions:
            return '<action-unknown id={}>'.format(a)


class SleepMacroCommand(BaseMacroCommand):
    NAME = "Sleep"
    TAG = "sleep"

    def __init__(self):
        super().__init__()
        self.frames = None

    def definition(self):
        self.field_length(0x0007, 8)
        self.define_field(0x0007, 'frames', 0, 'H')
        self.add_widget(0x0007, 'frames', 'Duration', 'framecount')

    def __repr__(self):
        return '<sleep frames={}>'.format(self.frames)


class PreviewInputMacroCommand(BaseMacroCommand):
    NAME = "Preview input"
    TAG = "preview-input"

    def __init__(self):
        super().__init__()
        self.index = None
        self.source = None

    def definition(self):
        self.field_length(0x0003, 8)
        self.define_field(0x0003, 'index', 0, 'B')
        self.define_field(0x0003, 'source', 2, 'H')
        self.add_widget(0x0003, 'index', 'M/E unit', 'number', offset=1, min=1, max=4)
        self.add_widget(0x0003, 'source', 'Input', 'source', dataset='available_me')

    def __repr__(self):
        return '<preview-input me={} source={}>'.format(self.index, self.source)


class ProgramInputMacroCommand(BaseMacroCommand):
    NAME = "Program input"
    TAG = "program-input"

    def __repr__(self):
        return '<program-input>'


class TransitionWipeMacroCommand(BaseMacroCommand):
    NAME = "Transition wipe settings"
    TAG = "transition-wipe-settings"

    def __repr__(self):
        return '<transition-wipe>'