cuemsutils 0.1.0rc14__tar.gz → 0.1.0rc15__tar.gz

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.
Files changed (46) hide show
  1. {cuemsutils-0.1.0rc14 → cuemsutils-0.1.0rc15}/PKG-INFO +2 -2
  2. {cuemsutils-0.1.0rc14 → cuemsutils-0.1.0rc15}/cuemsutils/__init__.py +2 -1
  3. {cuemsutils-0.1.0rc14 → cuemsutils-0.1.0rc15}/cuemsutils/cues/FadeCue.py +17 -2
  4. {cuemsutils-0.1.0rc14 → cuemsutils-0.1.0rc15}/cuemsutils/cues/MediaCue.py +90 -0
  5. {cuemsutils-0.1.0rc14 → cuemsutils-0.1.0rc15}/cuemsutils/xml/Parsers.py +6 -0
  6. {cuemsutils-0.1.0rc14 → cuemsutils-0.1.0rc15}/cuemsutils/xml/XmlBuilder.py +28 -1
  7. {cuemsutils-0.1.0rc14 → cuemsutils-0.1.0rc15}/cuemsutils/xml/schemas/script.xsd +33 -0
  8. {cuemsutils-0.1.0rc14 → cuemsutils-0.1.0rc15}/.gitignore +0 -0
  9. {cuemsutils-0.1.0rc14 → cuemsutils-0.1.0rc15}/LICENSE +0 -0
  10. {cuemsutils-0.1.0rc14 → cuemsutils-0.1.0rc15}/README.md +0 -0
  11. {cuemsutils-0.1.0rc14 → cuemsutils-0.1.0rc15}/cuemsutils/create_script.py +0 -0
  12. {cuemsutils-0.1.0rc14 → cuemsutils-0.1.0rc15}/cuemsutils/cues/ActionCue.py +0 -0
  13. {cuemsutils-0.1.0rc14 → cuemsutils-0.1.0rc15}/cuemsutils/cues/AudioCue.py +0 -0
  14. {cuemsutils-0.1.0rc14 → cuemsutils-0.1.0rc15}/cuemsutils/cues/Cue.py +0 -0
  15. {cuemsutils-0.1.0rc14 → cuemsutils-0.1.0rc15}/cuemsutils/cues/CueList.py +0 -0
  16. {cuemsutils-0.1.0rc14 → cuemsutils-0.1.0rc15}/cuemsutils/cues/CueOutput.py +0 -0
  17. {cuemsutils-0.1.0rc14 → cuemsutils-0.1.0rc15}/cuemsutils/cues/CuemsScript.py +0 -0
  18. {cuemsutils-0.1.0rc14 → cuemsutils-0.1.0rc15}/cuemsutils/cues/DmxCue.py +0 -0
  19. {cuemsutils-0.1.0rc14 → cuemsutils-0.1.0rc15}/cuemsutils/cues/FadeProfile.py +0 -0
  20. {cuemsutils-0.1.0rc14 → cuemsutils-0.1.0rc15}/cuemsutils/cues/VideoCue.py +0 -0
  21. {cuemsutils-0.1.0rc14 → cuemsutils-0.1.0rc15}/cuemsutils/cues/__init__.py +0 -0
  22. {cuemsutils-0.1.0rc14 → cuemsutils-0.1.0rc15}/cuemsutils/helpers.py +0 -0
  23. {cuemsutils-0.1.0rc14 → cuemsutils-0.1.0rc15}/cuemsutils/log.py +0 -0
  24. {cuemsutils-0.1.0rc14 → cuemsutils-0.1.0rc15}/cuemsutils/timeoutloop.py +0 -0
  25. {cuemsutils-0.1.0rc14 → cuemsutils-0.1.0rc15}/cuemsutils/tools/CTimecode.py +0 -0
  26. {cuemsutils-0.1.0rc14 → cuemsutils-0.1.0rc15}/cuemsutils/tools/CTimecodeTimer.py +0 -0
  27. {cuemsutils-0.1.0rc14 → cuemsutils-0.1.0rc15}/cuemsutils/tools/CommunicatorServices.py +0 -0
  28. {cuemsutils-0.1.0rc14 → cuemsutils-0.1.0rc15}/cuemsutils/tools/ConfigBase.py +0 -0
  29. {cuemsutils-0.1.0rc14 → cuemsutils-0.1.0rc15}/cuemsutils/tools/ConfigManager.py +0 -0
  30. {cuemsutils-0.1.0rc14 → cuemsutils-0.1.0rc15}/cuemsutils/tools/CopyMoveVersioned.py +0 -0
  31. {cuemsutils-0.1.0rc14 → cuemsutils-0.1.0rc15}/cuemsutils/tools/FadeCalculator.py +0 -0
  32. {cuemsutils-0.1.0rc14 → cuemsutils-0.1.0rc15}/cuemsutils/tools/HubServices.py +0 -0
  33. {cuemsutils-0.1.0rc14 → cuemsutils-0.1.0rc15}/cuemsutils/tools/SignalEngine.py +0 -0
  34. {cuemsutils-0.1.0rc14 → cuemsutils-0.1.0rc15}/cuemsutils/tools/StringSanitizer.py +0 -0
  35. {cuemsutils-0.1.0rc14 → cuemsutils-0.1.0rc15}/cuemsutils/tools/Uuid.py +0 -0
  36. {cuemsutils-0.1.0rc14 → cuemsutils-0.1.0rc15}/cuemsutils/tools/__init__.py +0 -0
  37. {cuemsutils-0.1.0rc14 → cuemsutils-0.1.0rc15}/cuemsutils/xml/CMLCuemsConverter.py +0 -0
  38. {cuemsutils-0.1.0rc14 → cuemsutils-0.1.0rc15}/cuemsutils/xml/Settings.py +0 -0
  39. {cuemsutils-0.1.0rc14 → cuemsutils-0.1.0rc15}/cuemsutils/xml/XmlReaderWriter.py +0 -0
  40. {cuemsutils-0.1.0rc14 → cuemsutils-0.1.0rc15}/cuemsutils/xml/__init__.py +0 -0
  41. {cuemsutils-0.1.0rc14 → cuemsutils-0.1.0rc15}/cuemsutils/xml/schemas/network_map.xsd +0 -0
  42. {cuemsutils-0.1.0rc14 → cuemsutils-0.1.0rc15}/cuemsutils/xml/schemas/outputs.xsd +0 -0
  43. {cuemsutils-0.1.0rc14 → cuemsutils-0.1.0rc15}/cuemsutils/xml/schemas/project_mappings.xsd +0 -0
  44. {cuemsutils-0.1.0rc14 → cuemsutils-0.1.0rc15}/cuemsutils/xml/schemas/project_settings.xsd +0 -0
  45. {cuemsutils-0.1.0rc14 → cuemsutils-0.1.0rc15}/cuemsutils/xml/schemas/settings.xsd +0 -0
  46. {cuemsutils-0.1.0rc14 → cuemsutils-0.1.0rc15}/pyproject.toml +0 -0
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.4
1
+ Metadata-Version: 2.5
2
2
  Name: cuemsutils
3
- Version: 0.1.0rc14
3
+ Version: 0.1.0rc15
4
4
  Summary: Reusable classes and methods for CueMS system
5
5
  Project-URL: Documentation, https://stagesoft.github.io/cuems-utils/
6
6
  Project-URL: Issues, https://github.com/stagesoft/cuems-utils/issues
@@ -1,4 +1,5 @@
1
1
  # SPDX-FileCopyrightText: 2025-present Adrià (StageLab) <adria@stagelab.coop>
2
2
  #
3
3
  # SPDX-License-Identifier: GPL-3.0
4
- __version__ = "0.1.0rc14"
4
+ # SPDX-FileContributor: Ion Reguera <ion@stagelab.coop>
5
+ __version__ = "0.1.0rc15"
@@ -1,3 +1,7 @@
1
+ # SPDX-FileCopyrightText: 2026 Stagelab Coop SCCL
2
+ # SPDX-License-Identifier: GPL-3.0-or-later
3
+ # SPDX-FileContributor: Ion Reguera <ion@stagelab.coop>
4
+
1
5
  from enum import Enum
2
6
 
3
7
  from ..helpers import ensure_items, format_timecode
@@ -8,12 +12,22 @@ _ZERO_TC = CTimecode('00:00:00.000')
8
12
 
9
13
 
10
14
  class FadeCurveType(Enum):
11
- """Enumeration of supported fade curve shapes."""
15
+ """Enumeration of supported fade curve shapes.
16
+
17
+ ``linear``, ``sigmoid``, ``ease_in`` and ``ease_out`` are the shapes
18
+ gradient-motiond's CurveFactory implements; the editor UI submits these.
19
+ ``exponential`` and ``logarithmic`` are legacy names kept so projects
20
+ authored by older editors still load — CurveFactory does NOT implement
21
+ them, and the frontend maps them onto ``ease_in``/``ease_out`` (the same
22
+ accelerating/decelerating shapes) on load, healing them on next save.
23
+ """
12
24
 
13
25
  linear = 'linear'
14
26
  exponential = 'exponential'
15
27
  logarithmic = 'logarithmic'
16
28
  sigmoid = 'sigmoid'
29
+ ease_in = 'ease_in'
30
+ ease_out = 'ease_out'
17
31
 
18
32
  def __str__(self):
19
33
  return self.value
@@ -125,7 +139,8 @@ class FadeCue(ActionCue):
125
139
 
126
140
  Args:
127
141
  curve_type (FadeCurveType | str): A FadeCurveType member or its
128
- string value ('linear', 'exponential', 'logarithmic', 'sigmoid').
142
+ string value ('linear', 'sigmoid', 'ease_in', 'ease_out', or
143
+ the legacy 'exponential'/'logarithmic').
129
144
 
130
145
  Raises:
131
146
  ValueError: If curve_type is not a recognised value.
@@ -1,3 +1,6 @@
1
+ # SPDX-FileCopyrightText: 2026 Stagelab Coop SCCL
2
+ # SPDX-License-Identifier: GPL-3.0-or-later
3
+ # SPDX-FileContributor: Ion Reguera <ion@stagelab.coop>
1
4
  from typing import Tuple
2
5
 
3
6
  from .Cue import Cue
@@ -237,6 +240,93 @@ class Media(CuemsDict):
237
240
 
238
241
  regions: list[Region] = property(get_regions, set_regions)
239
242
 
243
+ # --- Stored size of the media file (869fat84r) --------------------------
244
+ #
245
+ # ``pixel_width`` / ``pixel_height`` are the original size of the file's
246
+ # first video stream, as ffprobe reports it, and ``file_size`` is the
247
+ # file's size in bytes when they were measured. ``file_md5`` (below)
248
+ # identifies the file itself. The editor stores them at
249
+ # upload so the engine does not have to probe the file when it arms a
250
+ # cue; ``file_size`` lets the engine notice a file replaced under the
251
+ # same name. All three are optional: absent means unknown.
252
+ #
253
+ # Parsing a project never calls these setters (the parsers assign keys
254
+ # raw), so the schema and MediaXmlBuilder, not these, guard what is read
255
+ # from and written to a file.
256
+
257
+ def _get_optional(self, key):
258
+ return self.get(key)
259
+
260
+ def _set_positive_int(self, key, value):
261
+ """Store *value* as a positive ``int``; ``None`` removes the key.
262
+
263
+ Raises:
264
+ ValueError: for anything that is not a positive integer or a
265
+ string of digits (``bool`` included: it is an ``int``).
266
+ """
267
+ if value is None:
268
+ self.pop(key, None)
269
+ return
270
+ if isinstance(value, bool):
271
+ raise ValueError(f"Media {key} must be a positive integer, not {value!r}")
272
+ if isinstance(value, str) and value.isdigit():
273
+ value = int(value)
274
+ if not isinstance(value, int) or value <= 0:
275
+ raise ValueError(f"Media {key} must be a positive integer, not {value!r}")
276
+ super().__setitem__(key, value)
277
+
278
+ def get_pixel_width(self):
279
+ """Width in pixels of the media's first video stream, or ``None``."""
280
+ return self._get_optional('pixel_width')
281
+
282
+ def set_pixel_width(self, pixel_width):
283
+ """Set the width in pixels; ``None`` removes it."""
284
+ self._set_positive_int('pixel_width', pixel_width)
285
+
286
+ pixel_width = property(get_pixel_width, set_pixel_width)
287
+
288
+ def get_pixel_height(self):
289
+ """Height in pixels of the media's first video stream, or ``None``."""
290
+ return self._get_optional('pixel_height')
291
+
292
+ def set_pixel_height(self, pixel_height):
293
+ """Set the height in pixels; ``None`` removes it."""
294
+ self._set_positive_int('pixel_height', pixel_height)
295
+
296
+ pixel_height = property(get_pixel_height, set_pixel_height)
297
+
298
+ def get_file_size(self):
299
+ """Size in bytes of the file the dimensions were measured on, or ``None``."""
300
+ return self._get_optional('file_size')
301
+
302
+ def set_file_size(self, file_size):
303
+ """Set the file size in bytes; ``None`` removes it."""
304
+ self._set_positive_int('file_size', file_size)
305
+
306
+ file_size = property(get_file_size, set_file_size)
307
+
308
+ def get_file_md5(self):
309
+ """The file's MD5 (32 lowercase hex digits), or ``None``. Every media
310
+ type; it comes from the upload, which already carries it (D18)."""
311
+ return self._get_optional('file_md5')
312
+
313
+ def set_file_md5(self, file_md5):
314
+ """Set the file's MD5 (any case, stored lowercase); ``None`` removes it.
315
+
316
+ Raises:
317
+ ValueError: for anything that is not 32 hex digits.
318
+ """
319
+ if file_md5 is None:
320
+ self.pop('file_md5', None)
321
+ return
322
+ if not isinstance(file_md5, str) or len(file_md5) != 32 or any(
323
+ c not in '0123456789abcdef' for c in file_md5.lower()
324
+ ):
325
+ raise ValueError(f"Media file_md5 must be 32 hex digits, not {file_md5!r}")
326
+ super().__setitem__('file_md5', file_md5.lower())
327
+
328
+ file_md5 = property(get_file_md5, set_file_md5)
329
+
240
330
  class MediaCue(Cue):
241
331
  """Base class for media-related cues (audio and video).
242
332
 
@@ -1,3 +1,6 @@
1
+ # SPDX-FileCopyrightText: 2026 Stagelab Coop SCCL
2
+ # SPDX-License-Identifier: GPL-3.0-or-later
3
+ # SPDX-FileContributor: Ion Reguera <ion@stagelab.coop>
1
4
  from ..cues import *
2
5
  from ..cues.FadeProfile import FadeFunctionParameter, FadeProfile
3
6
  from ..cues.MediaCue import Media, Region
@@ -33,6 +36,9 @@ STRING_TYPED_KEYS = frozenset({
33
36
  'name', 'description', 'file_name',
34
37
  # defensive -- see above
35
38
  'output_name', 'parameter_name', 'icon', 'color', 'unix_name',
39
+ # an MD5 is hex: about one in a million is all digits (-> int) or digits
40
+ # with one 'e' (-> float), and would no longer validate (869fat84r D18)
41
+ 'file_md5',
36
42
  })
37
43
 
38
44
  class GenericDict(dict):
@@ -1,3 +1,6 @@
1
+ # SPDX-FileCopyrightText: 2026 Stagelab Coop SCCL
2
+ # SPDX-License-Identifier: GPL-3.0-or-later
3
+ # SPDX-FileContributor: Ion Reguera <ion@stagelab.coop>
1
4
  from enum import Enum
2
5
  from xml.etree.ElementTree import Element, ElementTree, SubElement, register_namespace
3
6
 
@@ -170,10 +173,34 @@ class GenericComplexSubObjectXmlBuilder(CuemsScriptXmlBuilder):
170
173
  class CTimecodeXmlBuilder(GenericSimpleSubObjectXmlBuilder):
171
174
  pass
172
175
 
176
+ #: ``MediaType``'s element order in script.xsd. The writer follows it, not the
177
+ #: dict's order: the editor appends the stored dimensions after whatever the
178
+ #: frontend sent, and the parsers assign keys in arrival order (869fat84r).
179
+ MEDIA_ELEMENT_ORDER = (
180
+ 'file_name', 'id', 'duration', 'regions',
181
+ 'pixel_width', 'pixel_height', 'file_size', 'file_md5',
182
+ )
183
+
184
+ #: Optional MediaType elements: a ``None`` value writes no element at all
185
+ #: (an empty ``<pixel_width/>`` would fail ``xs:positiveInteger``).
186
+ MEDIA_OPTIONAL_ELEMENTS = frozenset({'pixel_width', 'pixel_height', 'file_size', 'file_md5'})
187
+
188
+
189
+ def _media_items_in_schema_order(media):
190
+ """``media``'s items, known keys in schema order, then any others in
191
+ arrival order (so the validator, not this writer, rejects them)."""
192
+ known = [(k, media[k]) for k in MEDIA_ELEMENT_ORDER if k in media]
193
+ other = [(k, v) for k, v in media.items() if k not in MEDIA_ELEMENT_ORDER]
194
+ return [
195
+ (k, v) for k, v in known + other
196
+ if not (v is None and k in MEDIA_OPTIONAL_ELEMENTS)
197
+ ]
198
+
199
+
173
200
  class MediaXmlBuilder(GenericComplexSubObjectXmlBuilder):
174
201
  def build(self):
175
202
  if isinstance(self._object, dict):
176
- for key, value in self._object.items():
203
+ for key, value in _media_items_in_schema_order(self._object):
177
204
  if isinstance(value, VALUE_TYPES):
178
205
  cue_subelement = SubElement(self.xml_tree, key)
179
206
  cue_subelement.text = str(value)
@@ -1,4 +1,9 @@
1
1
  <?xml version="1.1" encoding="utf-8"?>
2
+ <!--
3
+ SPDX-FileCopyrightText: 2026 Stagelab Coop SCCL
4
+ SPDX-License-Identifier: GPL-3.0-or-later
5
+ SPDX-FileContributor: Ion Reguera <ion@stagelab.coop>
6
+ -->
2
7
  <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:vc="http://www.w3.org/2007/XMLSchema-versioning" vc:minVersion="1.1" targetNamespace="https://stagelab.coop/cuems/" xmlns:cms="https://stagelab.coop/cuems/">
3
8
  <!-- The main capsule to make it a whole, named CueMs -->
4
9
  <!-- Our main object the script-->
@@ -181,9 +186,31 @@
181
186
  <xs:element name="id" type="cms:TargetType" />
182
187
  <xs:element name="duration" type="cms:TimecodeType" />
183
188
  <xs:element name="regions" type="cms:RegionsType" />
189
+ <!-- Stored size of the media file (869fat84r), measured by the editor
190
+ at upload so the engine does not probe the file when it arms a cue.
191
+ pixel_width / pixel_height: the ORIGINAL size of the file's first
192
+ video stream, as ffprobe reports it; not the layer's size on screen
193
+ (that would get its own names) and unrelated to CanvasRegionType's
194
+ unit-float width / height. file_size: the file's size in bytes when
195
+ they were measured; the engine compares it with its own copy to
196
+ notice a file replaced under the same name. Optional: absent means
197
+ unknown, and the engine then probes as before. The pixel size is
198
+ for VideoCues only; file_size and file_md5 for every media type.
199
+ file_md5: the file's MD5 (lowercase hex), from the upload, which
200
+ already carries it; identifies the file for integrity checks. -->
201
+ <xs:element name="pixel_width" type="xs:positiveInteger" minOccurs="0" />
202
+ <xs:element name="pixel_height" type="xs:positiveInteger" minOccurs="0" />
203
+ <xs:element name="file_size" type="xs:positiveInteger" minOccurs="0" />
204
+ <xs:element name="file_md5" type="cms:Md5Type" minOccurs="0" />
184
205
  </xs:sequence>
185
206
  </xs:complexType>
186
207
 
208
+ <xs:simpleType name="Md5Type">
209
+ <xs:restriction base="xs:string">
210
+ <xs:pattern value="[0-9a-f]{32}" />
211
+ </xs:restriction>
212
+ </xs:simpleType>
213
+
187
214
  <xs:complexType name="OutputsType">
188
215
  <xs:choice minOccurs="0" maxOccurs="unbounded">
189
216
  <xs:element name="AudioCueOutput" type="cms:AudioCueOutputsType" />
@@ -251,12 +278,18 @@
251
278
  </xs:restriction>
252
279
  </xs:simpleType>
253
280
 
281
+ <!-- linear/sigmoid/ease_in/ease_out are the curves gradient-motiond's
282
+ CurveFactory implements (the editor UI submits only these);
283
+ exponential/logarithmic are legacy names kept so old projects still
284
+ load — the frontend heals them to ease_in/ease_out on next save. -->
254
285
  <xs:simpleType name="FadeCurveType">
255
286
  <xs:restriction base="xs:string">
256
287
  <xs:enumeration value="linear" />
257
288
  <xs:enumeration value="exponential" />
258
289
  <xs:enumeration value="logarithmic" />
259
290
  <xs:enumeration value="sigmoid" />
291
+ <xs:enumeration value="ease_in" />
292
+ <xs:enumeration value="ease_out" />
260
293
  </xs:restriction>
261
294
  </xs:simpleType>
262
295
 
File without changes
File without changes