cuemsutils 0.0.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,157 @@
1
+ from timecode import Timecode
2
+
3
+ #TODO: !IMPORTANT! fix milisecond parseing with more than 3 digits and leading 0's; Fix division returnig to 23:59...
4
+ class CTimecode(Timecode):
5
+ def __init__(self, init_dict = None, start_timecode=None, start_seconds=None, frames=None, framerate='ms'):
6
+ if init_dict is not None:
7
+ super().__init__(framerate, init_dict, start_seconds, frames)
8
+ else:
9
+ if start_seconds == 0:
10
+ start_seconds = None
11
+ frames = None
12
+ super().__init__(framerate, start_timecode, start_seconds, frames)
13
+
14
+ @classmethod
15
+ def from_dict(cls, init_dict):
16
+ return cls(init_dict = init_dict)
17
+
18
+ @property
19
+ def milliseconds(self):
20
+ """returns time as milliseconds
21
+ """
22
+ #TODO: float math for other framerates
23
+ millis_per_frame = 1000 / float(self._framerate)
24
+ return int(millis_per_frame * self.frame_number)
25
+
26
+ def return_in_other_framerate(self, framerate):
27
+ """returns a copy of the object with a different framerate.
28
+ """
29
+ new = CTimecode(framerate=framerate, start_seconds=float(self.milliseconds / 1000))
30
+ return new
31
+
32
+ def __hash__(self):
33
+ return hash((self.milliseconds, self.milliseconds))
34
+
35
+ def __eq__(self, other):
36
+ """Compares seconds of tc""" #TODO: decide if we cheek framerate and frame equality or time equiality
37
+ if isinstance(other, CTimecode):
38
+ return self.milliseconds == other.milliseconds
39
+ return NotImplemented
40
+
41
+ def __ne__(self, other):
42
+ """Compares seconds of tc""" #TODO: decide if we cheek framerate and frame equality or time equiality
43
+ if isinstance(other, CTimecode):
44
+ return self.milliseconds != other.milliseconds
45
+ return NotImplemented
46
+
47
+ def __lt__(self, other):
48
+ """Compares seconds of tc""" #TODO: decide if we cheek framerate and frame equality or time equiality
49
+ if isinstance(other, CTimecode):
50
+ return self.milliseconds < other.milliseconds
51
+ elif isinstance(other, int):
52
+ return self.milliseconds < other
53
+ elif isinstance(other, type(None)):
54
+ return other
55
+
56
+ return NotImplemented
57
+
58
+ def __le__(self, other):
59
+ """Compares seconds of tc""" #TODO: decide if we cheek framerate and frame equality or time equiality
60
+ if isinstance(other, CTimecode):
61
+ return self.milliseconds <= other.milliseconds
62
+ elif isinstance(other, type(None)):
63
+ return other
64
+ return NotImplemented
65
+
66
+ def __gt__(self, other):
67
+ """Compares seconds of tc""" #TODO: decide if we cheek framerate and frame equality or time equiality
68
+ if isinstance(other, CTimecode):
69
+ return self.milliseconds > other.milliseconds
70
+ elif isinstance(other, int):
71
+ return self.milliseconds > other
72
+ elif isinstance(other, type(None)):
73
+ return self
74
+ return NotImplemented
75
+
76
+ def __ge__(self, other):
77
+ """Compares seconds of tc""" #TODO: decide if we cheek framerate and frame equality or time equiality
78
+ if isinstance(other, CTimecode):
79
+ return self.milliseconds >= other.milliseconds
80
+ elif isinstance(other, type(None)):
81
+ return self
82
+ return NotImplemented
83
+
84
+ def __add__(self, other):
85
+ """returns new CTimecode instance with the given timecode or frames
86
+ added to this one
87
+ """
88
+ # duplicate current one
89
+ tc = CTimecode(framerate=self._framerate, frames=self.frames)
90
+
91
+ if isinstance(other, CTimecode):
92
+ tc.add_frames(other.frames)
93
+ elif isinstance(other, int):
94
+ tc.add_frames(other)
95
+ else:
96
+ raise CTimecodeError(
97
+ 'Type %s not supported for arithmetic.' %
98
+ other.__class__.__name__
99
+ )
100
+
101
+ return tc
102
+
103
+ def __sub__(self, other):
104
+ """returns new CTimecode instance with subtracted value"""
105
+ if isinstance(other, CTimecode):
106
+ subtracted_frames = self.frames - other.frames
107
+ elif isinstance(other, int):
108
+ subtracted_frames = self.frames - other
109
+ else:
110
+ raise CTimecodeError(
111
+ 'Type %s not supported for arithmetic.' %
112
+ other.__class__.__name__
113
+ )
114
+
115
+ return CTimecode(framerate=self._framerate, frames=subtracted_frames)
116
+
117
+ def __mul__(self, other):
118
+ """returns new CTimecode instance with multiplied value"""
119
+ if isinstance(other, CTimecode):
120
+ multiplied_frames = self.frames * other.frames
121
+ elif isinstance(other, int):
122
+ multiplied_frames = self.frames * other
123
+ else:
124
+ raise CTimecodeError(
125
+ 'Type %s not supported for arithmetic.' %
126
+ other.__class__.__name__
127
+ )
128
+
129
+ return CTimecode(framerate=self._framerate, frames=multiplied_frames)
130
+
131
+ def __truediv__(self, other):
132
+ """returns new CTimecode instance with divided value"""
133
+ if isinstance(other, CTimecode):
134
+ div_frames = self.frames / other.frames
135
+ elif isinstance(other, int):
136
+ div_frames = self.frames / other
137
+ else:
138
+ raise CTimecodeError(
139
+ 'Type %s not supported for arithmetic.' %
140
+ other.__class__.__name__
141
+ )
142
+
143
+ return CTimecode(framerate=self._framerate, frames=div_frames)
144
+
145
+ def __str__(self):
146
+ return self.tc_to_string(*self.frames_to_tc(self.frames))
147
+
148
+ def __iter__(self):
149
+ yield ('timecode', self.__str__())
150
+ yield ('framerate', self.framerate)
151
+
152
+
153
+
154
+ class CTimecodeError(Exception):
155
+ """Raised when an error occurred in timecode calculation
156
+ """
157
+ pass
cuemsutils/__init__.py ADDED
@@ -0,0 +1,4 @@
1
+ # SPDX-FileCopyrightText: 2025-present Adrià (NaturNum) <adria@naturnum.com>
2
+ #
3
+ # SPDX-License-Identifier: MIT
4
+ __version__ = "0.0.1"
@@ -0,0 +1,124 @@
1
+
2
+ from time import sleep
3
+ from threading import Thread
4
+
5
+ from Cue import Cue
6
+ from cuemsutils.log import logged
7
+
8
+ class ActionCue(Cue):
9
+ def __init__(self, init_dict = None):
10
+ if init_dict:
11
+ super().__init__(init_dict)
12
+
13
+ self._action_target_object = None
14
+
15
+ @property
16
+ def action_type(self):
17
+ return super().__getitem__('action_type')
18
+
19
+ @action_type.setter
20
+ def action_type(self, action_type):
21
+ super().__setitem__('action_type', action_type)
22
+
23
+ @property
24
+ def action_target(self):
25
+ return super().__getitem__('action_target')
26
+
27
+ @action_target.setter
28
+ def action_target(self, action_target):
29
+ super().__setitem__('action_target', action_target)
30
+
31
+ def arm(self, conf, ossia, armed_list, init = False):
32
+ self._conf = conf
33
+ self._armed_list = armed_list
34
+
35
+ if not self.enabled:
36
+ if self.loaded and self in self._armed_list:
37
+ self.disarm(ossia)
38
+ return False
39
+ elif self.loaded and not init:
40
+ if not self in self._armed_list:
41
+ self._armed_list.append(self)
42
+ return True
43
+
44
+ self.loaded = True
45
+ if not self in self._armed_list:
46
+ self._armed_list.append(self)
47
+
48
+ return True
49
+
50
+ @logged
51
+ def go(self, ossia, mtc):
52
+ if not self.loaded:
53
+ raise Exception(f'{self.__class__.__name__} {self.uuid} not loaded to go')
54
+ else:
55
+ # THREADED GO
56
+ thread = Thread(
57
+ name = f'GO:{self.__class__.__name__}:{self.uuid}',
58
+ target = self.go_thread,
59
+ args = [ossia, mtc]
60
+ )
61
+ thread.start()
62
+
63
+ def go_thread(self, ossia, mtc):
64
+ # ARM NEXT TARGET
65
+ if self._target_object is not None:
66
+ self._target_object.arm(self._conf, ossia, self._armed_list)
67
+
68
+ # PREWAIT
69
+ if self.prewait > 0:
70
+ sleep(self.prewait.milliseconds / 1000)
71
+
72
+ # PLAY : specific audio cue stuff
73
+ if self.action_type == 'load':
74
+ self._action_target_object.arm(self._conf, ossia, self._armed_list)
75
+ elif self.action_type == 'unload':
76
+ self._action_target_object.disarm(ossia)
77
+ elif self.action_type == 'play':
78
+ self._action_target_object.go(ossia, mtc)
79
+ elif self.action_type == 'pause':
80
+ pass
81
+ elif self.action_type == 'stop':
82
+ pass
83
+ elif self.action_type == 'enable':
84
+ self._action_target_object.enabled = True
85
+ elif self.action_type == 'disable':
86
+ self._action_target_object.enabled = False
87
+ elif self.action_type == 'fade_in':
88
+ self._action_target_object.enabled = False
89
+ elif self.action_type == 'fade_out':
90
+ self._action_target_object.enabled = False
91
+ elif self.action_type == 'wait':
92
+ self._action_target_object.enabled = False
93
+ elif self.action_type == 'go_to':
94
+ self._action_target_object.enabled = False
95
+ elif self.action_type == 'pause_project':
96
+ self._action_target_object.enabled = False
97
+ elif self.action_type == 'resume_project':
98
+ self._action_target_object.enabled = False
99
+
100
+ # POSTWAIT
101
+ if self.postwait > 0:
102
+ sleep(self.postwait.milliseconds / 1000)
103
+
104
+ # POST-GO GO
105
+ if self.post_go == 'go':
106
+ self._target_object.go(ossia, mtc)
107
+
108
+ # DISARM
109
+ if self in self._armed_list:
110
+ self.disarm(ossia)
111
+
112
+ def disarm(self):
113
+ if self.loaded is True:
114
+ try:
115
+ if self in self._armed_list:
116
+ self._armed_list.remove(self)
117
+ except:
118
+ pass
119
+
120
+ self.loaded = False
121
+
122
+ return True
123
+ else:
124
+ return False
@@ -0,0 +1,282 @@
1
+ # from pyossia import ossia_python as ossia
2
+ from time import sleep
3
+ from threading import Thread
4
+
5
+ from Cue import Cue
6
+ # from ..CTimecode import CTimecode
7
+ # from ..players.AudioPlayer import AudioPlayer
8
+ # from ..OssiaServer import OssiaServer, OSCConfData, PlayerOSCConfData
9
+ from cuemsutils.log import logged, Logger
10
+
11
+ class AudioCue(Cue):
12
+ # And dinamically attach it to the ossia for remote control it
13
+ # OSC_AUDIOPLAYER_CONF = {'/quit' : [ossia.ValueType.Impulse, None],
14
+ # '/load' : [ossia.ValueType.String, None],
15
+ # '/vol0' : [ossia.ValueType.Float, None],
16
+ # '/vol1' : [ossia.ValueType.Float, None],
17
+ # '/volmaster' : [ossia.ValueType.Float, None],
18
+ # '/play' : [ossia.ValueType.Impulse, None],
19
+ # '/stop' : [ossia.ValueType.Impulse, None],
20
+ # '/stoponlost' : [ossia.ValueType.Int, None],
21
+ # '/mtcfollow' : [ossia.ValueType.Int, None],
22
+ # '/offset' : [ossia.ValueType.Float, None],
23
+ # '/check' : [ossia.ValueType.Impulse, None]
24
+ # }
25
+
26
+ def __init__(self, init_dict = None):
27
+ super().__init__(init_dict)
28
+
29
+ self._player = None
30
+ self._osc_route = None
31
+
32
+ # self.OSC_AUDIOPLAYER_CONF['/offset'] = [ossia.ValueType.Float, None]
33
+
34
+ @property
35
+ def master_vol(self):
36
+ return super().__getitem__('master_vol')
37
+
38
+ @master_vol.setter
39
+ def master_vol(self, master_vol):
40
+ super().__setitem__('master_vol', master_vol)
41
+
42
+ @property
43
+ def outputs(self):
44
+ return super().__getitem__('Outputs')
45
+
46
+ @outputs.setter
47
+ def outputs(self, outputs):
48
+ super().__setitem__('Outputs', outputs)
49
+
50
+ def player(self, player):
51
+ self._player = player
52
+
53
+ def osc_route(self, osc_route):
54
+ self._osc_route = osc_route
55
+
56
+ def arm(self, conf, ossia, armed_list, init = False):
57
+ self._conf = conf
58
+ self._armed_list = armed_list
59
+
60
+ if not self.enabled:
61
+ if self.loaded and self in self._armed_list:
62
+ self.disarm(ossia)
63
+ return False
64
+ elif self.loaded and not init:
65
+ if not self in self._armed_list:
66
+ self._armed_list.append(self)
67
+ return True
68
+
69
+ # Assign its own audioplayer object
70
+ if self._local:
71
+ # try:
72
+ # self._player = AudioPlayer(
73
+ # self._conf.osc_port_index,
74
+ # self._conf.node_conf['audioplayer']['path'],
75
+ # self._conf.node_conf['audioplayer']['args'],
76
+ # str(
77
+ # path.join(
78
+ # self._conf.library_path,
79
+ # 'media',
80
+ # self.media['file_name']
81
+ # )
82
+ # ),
83
+ # self.uuid
84
+ # )
85
+ # except Exception as e:
86
+ # raise e
87
+
88
+ self._player.start()
89
+
90
+ # And dinamically attach it to the ossia for remote control it
91
+ self._osc_route = f'/players/audioplayer-{self.uuid}'
92
+
93
+ # ossia.add_player_nodes(
94
+ # PlayerOSCConfData(
95
+ # device_name=self._osc_route,
96
+ # host=self._conf.node_conf['osc_dest_host'],
97
+ # in_port=self._player.port,
98
+ # out_port=self._player.port + 1,
99
+ # dictionary=self.OSC_AUDIOPLAYER_CONF
100
+ # )
101
+ # )
102
+
103
+ self.loaded = True
104
+ if not self in self._armed_list:
105
+ self._armed_list.append(self)
106
+
107
+ # POST_GO CHAINED ARM
108
+ if self.post_go == 'go' and self._target_object:
109
+ self._target_object.arm(self._conf, ossia, self._armed_list, init)
110
+
111
+ return True
112
+
113
+ @logged
114
+ def go(self, ossia, mtc):
115
+ if not self.loaded:
116
+ raise Exception(f'{self.__class__.__name__} {self.uuid} not loaded to go')
117
+ else:
118
+ # THREADED GO
119
+ self._go_thread = Thread(
120
+ name = f'GO:{self.__class__.__name__}:{self.uuid}',
121
+ target = self.go_thread_func,
122
+ args = [ossia, mtc]
123
+ )
124
+ self._go_thread.start()
125
+
126
+ def go_thread_func(self, ossia, mtc):
127
+ # ARM NEXT TARGET
128
+ if self.post_go != 'go' and self._target_object:
129
+ self._target_object.arm(self._conf, ossia, self._armed_list)
130
+
131
+ # PREWAIT
132
+ if self.prewait > 0:
133
+ sleep(self.prewait.milliseconds / 1000)
134
+
135
+ # PLAY : specific audio cue stuff
136
+ # Set offset
137
+
138
+ ### harcoded for TODO: proto_fruta, need fixx
139
+ #try to make all cues start at sync at 20 second timecode!
140
+ harcoded_go_offset = 20000
141
+
142
+ if self._local:
143
+ try:
144
+ key = f'{self._osc_route}/offset'
145
+ #self._start_mtc = CTimecode(frames=mtc.main_tc.milliseconds + harcoded_go_offset)
146
+
147
+ # self._start_mtc = CTimecode(frames=harcoded_go_offset)
148
+
149
+ self._end_mtc = self._start_mtc + (self.media.regions[0].out_time - self.media.regions[0].in_time)
150
+ offset_to_go = float(-(self._start_mtc.milliseconds) + self.media.regions[0].in_time.milliseconds)
151
+ ossia.send_message(key, offset_to_go)
152
+ Logger.log_info(
153
+ f"Sending offset {offset_to_go} to {key + " " + str(ossia._oscquery_registered_nodes[key][0].value)}",
154
+ extra = {"caller": self.__class__.__name__}
155
+ )
156
+ except KeyError:
157
+ Logger.log_debug(
158
+ f'Key error 1 in go_callback {key}',
159
+ extra = {"caller": self.__class__.__name__}
160
+ )
161
+
162
+ # Connect to mtc signal
163
+ try:
164
+ key = f'{self._osc_route}/mtcfollow'
165
+ ossia.send_message(key, 1)
166
+ except KeyError:
167
+ Logger.log_debug(
168
+ f'Key error 2 in go_callback {key}',
169
+ extra = {"caller": self.__class__.__name__}
170
+ )
171
+
172
+ # POSTWAIT
173
+ if self.postwait > 0:
174
+ sleep(self.postwait.milliseconds / 1000)
175
+
176
+ # POST-GO GO
177
+ if self.post_go == 'go' and self._target_object:
178
+ self._target_object.go(ossia, mtc)
179
+
180
+ try:
181
+ loop_counter = 0
182
+ duration = self.media.regions[0].out_time - self.media.regions[0].in_time
183
+
184
+ while not self.media.regions[0].loop or loop_counter < self.media.regions[0].loop:
185
+ while self._player.is_alive() and (mtc.main_tc.milliseconds < self._end_mtc.milliseconds):
186
+ sleep(0.005)
187
+
188
+ if self._local:
189
+ # Recalculate offset and apply
190
+ # self._start_mtc = CTimecode(frames=mtc.main_tc.milliseconds)
191
+ self._end_mtc = self._start_mtc + (duration)
192
+ offset_to_go = float(-(self._start_mtc.milliseconds) + self.media.regions[0].in_time.milliseconds)
193
+ try:
194
+ key = f'{self._osc_route}/offset'
195
+ ossia.send_message(key, offset_to_go)
196
+ except KeyError:
197
+ Logger.log_debug(
198
+ f'Key error 3 in go_callback {key}',
199
+ extra = {"caller": self.__class__.__name__}
200
+ )
201
+
202
+ loop_counter += 1
203
+
204
+ if self._local:
205
+ try:
206
+ key = f'{self._osc_route}/mtcfollow'
207
+ ossia.send_message(key, 0)
208
+ except KeyError:
209
+ Logger.log_debug(
210
+ f'Key error 4 in go_callback {key}',
211
+ extra = {"caller": self.__class__.__name__}
212
+ )
213
+
214
+ except AttributeError:
215
+ pass
216
+
217
+ # POST-GO GO AT END
218
+ if self.post_go == 'go_at_end' and self._target_object:
219
+ self._target_object.go(ossia, mtc)
220
+
221
+ if self in self._armed_list:
222
+ self.disarm(ossia)
223
+
224
+ def disarm(self, ossia):
225
+ if self.loaded is True:
226
+ try:
227
+ self._conf.osc_port_index['used'].remove(self._player.port)
228
+ self._player.kill()
229
+ self._player = None
230
+
231
+ # ossia.remove_nodes( OSCConfData(device_name=self._osc_route, dictionary=self.OSC_AUDIOPLAYER_CONF) )
232
+
233
+ except Exception as e:
234
+ Logger.log_warning(
235
+ f'Could not properly unload {self.uuid} : {e}',
236
+ extra = {"caller": self.__class__.__name__}
237
+ )
238
+
239
+ try:
240
+ if self in self._armed_list:
241
+ self._armed_list.remove(self)
242
+ except:
243
+ pass
244
+
245
+ self.loaded = False
246
+
247
+ return True
248
+ else:
249
+ return False
250
+
251
+ def stop(self):
252
+ self._stop_requested = True
253
+ if self._player and self._player.is_alive():
254
+ self._player.kill()
255
+
256
+ def check_mappings(self, settings):
257
+ if not settings.project_node_mappings:
258
+ return True
259
+
260
+ found = True
261
+
262
+ map_list = ['default']
263
+
264
+ if settings.project_node_mappings['audio']['outputs']:
265
+ m = settings.project_node_mappings['audio']['outputs']
266
+ for elem in settings.project_node_mappings['audio']['outputs']:
267
+ for map in elem['mappings']:
268
+ map_list.append(map['mapped_to'])
269
+
270
+ for output in self.outputs:
271
+ #if output['node_uuid'] == settings.node_conf['uuid']:
272
+
273
+ if output['output_name'][:36] == settings.node_conf['uuid']:
274
+ self._local = True
275
+ if output['output_name'][37:] not in map_list:
276
+ found = False
277
+ break
278
+ else:
279
+ self._local = False
280
+ found = True
281
+
282
+ return found