webatem 2.2.1__tar.gz → 2.3.0__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 (135) hide show
  1. {webatem-2.2.1 → webatem-2.3.0}/PKG-INFO +1 -1
  2. {webatem-2.2.1 → webatem-2.3.0}/atem_control/control/consumer.py +27 -2
  3. {webatem-2.2.1 → webatem-2.3.0}/atem_control/static/js/atem_control.js +23 -1
  4. {webatem-2.2.1 → webatem-2.3.0}/atem_control/static/vendor/webatem.css +1 -1
  5. {webatem-2.2.1 → webatem-2.3.0}/atem_control/templates/control.html +27 -3
  6. {webatem-2.2.1 → webatem-2.3.0}/pyproject.toml +1 -1
  7. {webatem-2.2.1 → webatem-2.3.0}/webatem.egg-info/PKG-INFO +1 -1
  8. {webatem-2.2.1 → webatem-2.3.0}/LICENSE +0 -0
  9. {webatem-2.2.1 → webatem-2.3.0}/README.md +0 -0
  10. {webatem-2.2.1 → webatem-2.3.0}/atem_control/__init__.py +0 -0
  11. {webatem-2.2.1 → webatem-2.3.0}/atem_control/activity.py +0 -0
  12. {webatem-2.2.1 → webatem-2.3.0}/atem_control/apps.py +0 -0
  13. {webatem-2.2.1 → webatem-2.3.0}/atem_control/control/__init__.py +0 -0
  14. {webatem-2.2.1 → webatem-2.3.0}/atem_control/control/activity.py +0 -0
  15. {webatem-2.2.1 → webatem-2.3.0}/atem_control/control/commands.py +0 -0
  16. {webatem-2.2.1 → webatem-2.3.0}/atem_control/control/device_api.py +0 -0
  17. {webatem-2.2.1 → webatem-2.3.0}/atem_control/control/logging.py +0 -0
  18. {webatem-2.2.1 → webatem-2.3.0}/atem_control/control/views.py +0 -0
  19. {webatem-2.2.1 → webatem-2.3.0}/atem_control/discovery.py +0 -0
  20. {webatem-2.2.1 → webatem-2.3.0}/atem_control/hooks.py +0 -0
  21. {webatem-2.2.1 → webatem-2.3.0}/atem_control/hyperdeck/__init__.py +0 -0
  22. {webatem-2.2.1 → webatem-2.3.0}/atem_control/hyperdeck/connection.py +0 -0
  23. {webatem-2.2.1 → webatem-2.3.0}/atem_control/hyperdeck/views.py +0 -0
  24. {webatem-2.2.1 → webatem-2.3.0}/atem_control/ip_upload_lock.py +0 -0
  25. {webatem-2.2.1 → webatem-2.3.0}/atem_control/media_pool/__init__.py +0 -0
  26. {webatem-2.2.1 → webatem-2.3.0}/atem_control/media_pool/broadcast.py +0 -0
  27. {webatem-2.2.1 → webatem-2.3.0}/atem_control/media_pool/still_cache.py +0 -0
  28. {webatem-2.2.1 → webatem-2.3.0}/atem_control/media_pool/views.py +0 -0
  29. {webatem-2.2.1 → webatem-2.3.0}/atem_control/media_pool/watcher.py +0 -0
  30. {webatem-2.2.1 → webatem-2.3.0}/atem_control/migrations/0001_initial.py +0 -0
  31. {webatem-2.2.1 → webatem-2.3.0}/atem_control/migrations/0002_legacy_log.py +0 -0
  32. {webatem-2.2.1 → webatem-2.3.0}/atem_control/migrations/__init__.py +0 -0
  33. {webatem-2.2.1 → webatem-2.3.0}/atem_control/models.py +0 -0
  34. {webatem-2.2.1 → webatem-2.3.0}/atem_control/netutil.py +0 -0
  35. {webatem-2.2.1 → webatem-2.3.0}/atem_control/profile/__init__.py +0 -0
  36. {webatem-2.2.1 → webatem-2.3.0}/atem_control/profile/dialog.py +0 -0
  37. {webatem-2.2.1 → webatem-2.3.0}/atem_control/profile/export.py +0 -0
  38. {webatem-2.2.1 → webatem-2.3.0}/atem_control/profile/views.py +0 -0
  39. {webatem-2.2.1 → webatem-2.3.0}/atem_control/routing.py +0 -0
  40. {webatem-2.2.1 → webatem-2.3.0}/atem_control/sightings.py +0 -0
  41. {webatem-2.2.1 → webatem-2.3.0}/atem_control/static/brand/apple-touch-icon.png +0 -0
  42. {webatem-2.2.1 → webatem-2.3.0}/atem_control/static/brand/brand.css +0 -0
  43. {webatem-2.2.1 → webatem-2.3.0}/atem_control/static/brand/favicon-32.png +0 -0
  44. {webatem-2.2.1 → webatem-2.3.0}/atem_control/static/brand/favicon.ico +0 -0
  45. {webatem-2.2.1 → webatem-2.3.0}/atem_control/static/brand/fonts/Archivo-400-latin-ext.woff2 +0 -0
  46. {webatem-2.2.1 → webatem-2.3.0}/atem_control/static/brand/fonts/Archivo-400-latin.woff2 +0 -0
  47. {webatem-2.2.1 → webatem-2.3.0}/atem_control/static/brand/fonts/Archivo-500-latin-ext.woff2 +0 -0
  48. {webatem-2.2.1 → webatem-2.3.0}/atem_control/static/brand/fonts/Archivo-500-latin.woff2 +0 -0
  49. {webatem-2.2.1 → webatem-2.3.0}/atem_control/static/brand/fonts/Archivo-600-latin-ext.woff2 +0 -0
  50. {webatem-2.2.1 → webatem-2.3.0}/atem_control/static/brand/fonts/Archivo-600-latin.woff2 +0 -0
  51. {webatem-2.2.1 → webatem-2.3.0}/atem_control/static/brand/fonts/Archivo-800-latin-ext.woff2 +0 -0
  52. {webatem-2.2.1 → webatem-2.3.0}/atem_control/static/brand/fonts/Archivo-800-latin.woff2 +0 -0
  53. {webatem-2.2.1 → webatem-2.3.0}/atem_control/static/brand/fonts/IBMPlexMono-400-latin-ext.woff2 +0 -0
  54. {webatem-2.2.1 → webatem-2.3.0}/atem_control/static/brand/fonts/IBMPlexMono-400-latin.woff2 +0 -0
  55. {webatem-2.2.1 → webatem-2.3.0}/atem_control/static/brand/fonts/IBMPlexMono-500-latin-ext.woff2 +0 -0
  56. {webatem-2.2.1 → webatem-2.3.0}/atem_control/static/brand/fonts/IBMPlexMono-500-latin.woff2 +0 -0
  57. {webatem-2.2.1 → webatem-2.3.0}/atem_control/static/brand/fonts/IBMPlexMono-600-latin-ext.woff2 +0 -0
  58. {webatem-2.2.1 → webatem-2.3.0}/atem_control/static/brand/fonts/IBMPlexMono-600-latin.woff2 +0 -0
  59. {webatem-2.2.1 → webatem-2.3.0}/atem_control/static/brand/fonts/OFL-Archivo.txt +0 -0
  60. {webatem-2.2.1 → webatem-2.3.0}/atem_control/static/brand/fonts/OFL-IBMPlexMono.txt +0 -0
  61. {webatem-2.2.1 → webatem-2.3.0}/atem_control/static/brand/icon-192.png +0 -0
  62. {webatem-2.2.1 → webatem-2.3.0}/atem_control/static/brand/icon-512.png +0 -0
  63. {webatem-2.2.1 → webatem-2.3.0}/atem_control/static/brand/icon-maskable-512.png +0 -0
  64. {webatem-2.2.1 → webatem-2.3.0}/atem_control/static/brand/manifest.webmanifest +0 -0
  65. {webatem-2.2.1 → webatem-2.3.0}/atem_control/static/brand/mark.svg +0 -0
  66. {webatem-2.2.1 → webatem-2.3.0}/atem_control/static/brand/tray/linux-24.png +0 -0
  67. {webatem-2.2.1 → webatem-2.3.0}/atem_control/static/brand/tray/mac-22.png +0 -0
  68. {webatem-2.2.1 → webatem-2.3.0}/atem_control/static/brand/tray/mac-44.png +0 -0
  69. {webatem-2.2.1 → webatem-2.3.0}/atem_control/static/brand/tray/win-16.png +0 -0
  70. {webatem-2.2.1 → webatem-2.3.0}/atem_control/static/brand/tray/win-32.png +0 -0
  71. {webatem-2.2.1 → webatem-2.3.0}/atem_control/static/css/atem_connect.css +0 -0
  72. {webatem-2.2.1 → webatem-2.3.0}/atem_control/static/css/atem_control.css +0 -0
  73. {webatem-2.2.1 → webatem-2.3.0}/atem_control/static/icon.png +0 -0
  74. {webatem-2.2.1 → webatem-2.3.0}/atem_control/static/js/atem_connect.js +0 -0
  75. {webatem-2.2.1 → webatem-2.3.0}/atem_control/static/js/atem_control_page.js +0 -0
  76. {webatem-2.2.1 → webatem-2.3.0}/atem_control/static/js/atem_discovery.js +0 -0
  77. {webatem-2.2.1 → webatem-2.3.0}/atem_control/static/js/atem_hyperdeck.js +0 -0
  78. {webatem-2.2.1 → webatem-2.3.0}/atem_control/static/js/atem_profile.js +0 -0
  79. {webatem-2.2.1 → webatem-2.3.0}/atem_control/static/js/atem_realtime_slider.js +0 -0
  80. {webatem-2.2.1 → webatem-2.3.0}/atem_control/static/js/custom-scrollbar.js +0 -0
  81. {webatem-2.2.1 → webatem-2.3.0}/atem_control/static/js/recent_label.js +0 -0
  82. {webatem-2.2.1 → webatem-2.3.0}/atem_control/static/js/wa-select.js +0 -0
  83. {webatem-2.2.1 → webatem-2.3.0}/atem_control/static/vendor/README.md +0 -0
  84. {webatem-2.2.1 → webatem-2.3.0}/atem_control/static/vendor/alpine.min.js +0 -0
  85. {webatem-2.2.1 → webatem-2.3.0}/atem_control/static/vendor/alpinejs-collapse-3.14.3.min.js +0 -0
  86. {webatem-2.2.1 → webatem-2.3.0}/atem_control/static/vendor/bootstrap-icons/bootstrap-icons.min.css +0 -0
  87. {webatem-2.2.1 → webatem-2.3.0}/atem_control/static/vendor/bootstrap-icons/fonts/bootstrap-icons.woff +0 -0
  88. {webatem-2.2.1 → webatem-2.3.0}/atem_control/static/vendor/bootstrap-icons/fonts/bootstrap-icons.woff2 +0 -0
  89. {webatem-2.2.1 → webatem-2.3.0}/atem_control/storage.py +0 -0
  90. {webatem-2.2.1 → webatem-2.3.0}/atem_control/tally.py +0 -0
  91. {webatem-2.2.1 → webatem-2.3.0}/atem_control/templates/connect.html +0 -0
  92. {webatem-2.2.1 → webatem-2.3.0}/atem_control/templates/control/_atem_info.html +0 -0
  93. {webatem-2.2.1 → webatem-2.3.0}/atem_control/templates/control/_fade_to_black.html +0 -0
  94. {webatem-2.2.1 → webatem-2.3.0}/atem_control/templates/control/_fairlight.html +0 -0
  95. {webatem-2.2.1 → webatem-2.3.0}/atem_control/templates/control/_header.html +0 -0
  96. {webatem-2.2.1 → webatem-2.3.0}/atem_control/templates/control/_hyperdeck_panel.html +0 -0
  97. {webatem-2.2.1 → webatem-2.3.0}/atem_control/templates/control/_macros.html +0 -0
  98. {webatem-2.2.1 → webatem-2.3.0}/atem_control/templates/control/_media_pool.html +0 -0
  99. {webatem-2.2.1 → webatem-2.3.0}/atem_control/templates/control/_profile_modal.html +0 -0
  100. {webatem-2.2.1 → webatem-2.3.0}/atem_control/templates/control/_settings_dsk.html +0 -0
  101. {webatem-2.2.1 → webatem-2.3.0}/atem_control/templates/control/_settings_transition.html +0 -0
  102. {webatem-2.2.1 → webatem-2.3.0}/atem_control/templates/control/_settings_usk.html +0 -0
  103. {webatem-2.2.1 → webatem-2.3.0}/atem_control/templates/control/_slider.html +0 -0
  104. {webatem-2.2.1 → webatem-2.3.0}/atem_control/templates/control/_transition_controls.html +0 -0
  105. {webatem-2.2.1 → webatem-2.3.0}/atem_control/templates/control/_transition_style.html +0 -0
  106. {webatem-2.2.1 → webatem-2.3.0}/atem_control/templates/control/_usk_fly.html +0 -0
  107. {webatem-2.2.1 → webatem-2.3.0}/atem_control/templates/control/_usk_keyframe.html +0 -0
  108. {webatem-2.2.1 → webatem-2.3.0}/atem_control/templates/control/_usk_mask.html +0 -0
  109. {webatem-2.2.1 → webatem-2.3.0}/atem_control/templates/control/_usk_slider.html +0 -0
  110. {webatem-2.2.1 → webatem-2.3.0}/atem_control/templatetags/__init__.py +0 -0
  111. {webatem-2.2.1 → webatem-2.3.0}/atem_control/templatetags/static_v.py +0 -0
  112. {webatem-2.2.1 → webatem-2.3.0}/atem_control/uploader.py +0 -0
  113. {webatem-2.2.1 → webatem-2.3.0}/atem_control/urls.py +0 -0
  114. {webatem-2.2.1 → webatem-2.3.0}/setup.cfg +0 -0
  115. {webatem-2.2.1 → webatem-2.3.0}/webatem/__init__.py +0 -0
  116. {webatem-2.2.1 → webatem-2.3.0}/webatem/__main__.py +0 -0
  117. {webatem-2.2.1 → webatem-2.3.0}/webatem/applog.py +0 -0
  118. {webatem-2.2.1 → webatem-2.3.0}/webatem/asgi.py +0 -0
  119. {webatem-2.2.1 → webatem-2.3.0}/webatem/context.py +0 -0
  120. {webatem-2.2.1 → webatem-2.3.0}/webatem/launcher.py +0 -0
  121. {webatem-2.2.1 → webatem-2.3.0}/webatem/server.py +0 -0
  122. {webatem-2.2.1 → webatem-2.3.0}/webatem/settings.py +0 -0
  123. {webatem-2.2.1 → webatem-2.3.0}/webatem/templates/base.html +0 -0
  124. {webatem-2.2.1 → webatem-2.3.0}/webatem/templates/brand/_mark.svg +0 -0
  125. {webatem-2.2.1 → webatem-2.3.0}/webatem/templates/launcher.html +0 -0
  126. {webatem-2.2.1 → webatem-2.3.0}/webatem/uninstall.py +0 -0
  127. {webatem-2.2.1 → webatem-2.3.0}/webatem/updates.py +0 -0
  128. {webatem-2.2.1 → webatem-2.3.0}/webatem/urls.py +0 -0
  129. {webatem-2.2.1 → webatem-2.3.0}/webatem/views.py +0 -0
  130. {webatem-2.2.1 → webatem-2.3.0}/webatem/websocket.py +0 -0
  131. {webatem-2.2.1 → webatem-2.3.0}/webatem.egg-info/SOURCES.txt +0 -0
  132. {webatem-2.2.1 → webatem-2.3.0}/webatem.egg-info/dependency_links.txt +0 -0
  133. {webatem-2.2.1 → webatem-2.3.0}/webatem.egg-info/entry_points.txt +0 -0
  134. {webatem-2.2.1 → webatem-2.3.0}/webatem.egg-info/requires.txt +0 -0
  135. {webatem-2.2.1 → webatem-2.3.0}/webatem.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: webatem
3
- Version: 2.2.1
3
+ Version: 2.3.0
4
4
  Summary: Browser-based control for Blackmagic ATEM switchers: run it, open the browser, your switchers are there
5
5
  Author: Lucas Romanenko
6
6
  License: MIT
@@ -199,6 +199,18 @@ class ATEMConsumer(ATEMConnectionLoggingMixin, ATEMStateMixin, AsyncWebsocketCon
199
199
  def is_connected(self) -> bool:
200
200
  return bool(self.connection and self.connection.is_connected)
201
201
 
202
+ @property
203
+ def is_ready(self) -> bool:
204
+ """The switcher's state is current. Through a transport re-handshake
205
+ ``is_connected`` stays True while this is False: what is sent then
206
+ goes out under the session being replaced, and is lost or lands
207
+ late (#38, #39). bmdwire before 1.3 has no ``is_ready``; there it
208
+ is ``is_connected``. The poll loop stays on ``is_connected``: it
209
+ must keep polling through the gap to see the switcher go and
210
+ come back."""
211
+ conn = self.connection
212
+ return bool(conn and getattr(conn, 'is_ready', conn.is_connected))
213
+
202
214
  # =========================================================================
203
215
  # WebSocket Lifecycle
204
216
  # =========================================================================
@@ -336,8 +348,12 @@ class ATEMConsumer(ATEMConnectionLoggingMixin, ATEMStateMixin, AsyncWebsocketCon
336
348
  drags (20 Hz × 30 KB = ~600 KB/s purely from per-command pushes,
337
349
  on top of the polling loop). That saturates the WebSocket write
338
350
  buffer and queues audio meter / other small messages behind it.
351
+
352
+ Refused while the switcher is away (``is_ready``), not only once
353
+ the worker is gone: a command sent into a re-handshake is lost,
354
+ or lands after the operator has moved on (#38, #39).
339
355
  """
340
- if not self.is_connected:
356
+ if not self.is_ready:
341
357
  await self.send_json({'type': 'error', 'message': 'ATEM not connected'})
342
358
  return
343
359
 
@@ -722,9 +738,16 @@ class ATEMConsumer(ATEMConnectionLoggingMixin, ATEMStateMixin, AsyncWebsocketCon
722
738
  if was_atem_reachable and not atem_reachable:
723
739
  logger.info("ATEM dropped mid-session; notifying frontend")
724
740
  try:
741
+ # ``reconnecting``: bmdwire re-handshakes in
742
+ # place until the switcher answers, and the
743
+ # branch below says so. The page stays and
744
+ # waits (#38); the other connected=False
745
+ # notices (a failed connect, a disconnect)
746
+ # send it to the connect page.
725
747
  await self.send_json({
726
748
  'type': 'connection_status',
727
749
  'connected': False,
750
+ 'reconnecting': True,
728
751
  'message': 'ATEM connection lost',
729
752
  })
730
753
  except Exception as e: # noqa: BLE001
@@ -1341,7 +1364,9 @@ class ATEMConsumer(ATEMConnectionLoggingMixin, ATEMStateMixin, AsyncWebsocketCon
1341
1364
  if not isinstance(slot, int) or slot < 0 or slot >= 32:
1342
1365
  await self.send_json({'type': 'error', 'message': f'Invalid slot: {slot}'})
1343
1366
  return
1344
- if not self.is_connected:
1367
+ # is_ready, as for every command (#39): bmdwire's clear_still
1368
+ # refuses a re-handshake too (bmdwire#8), but as a failed delete.
1369
+ if not self.is_ready:
1345
1370
  await self.send_json({'type': 'error', 'message': 'ATEM not connected'})
1346
1371
  return
1347
1372
  try:
@@ -885,6 +885,14 @@
885
885
  window.chromaSampleBox = chromaSampleBox;
886
886
 
887
887
  // ../../atem_control/js/runtime.js
888
+ var SESSION_VERBS = /* @__PURE__ */ new Set([
889
+ "connect",
890
+ "disconnect",
891
+ "refresh",
892
+ "subscribe_audio_meters",
893
+ "unsubscribe_audio_meters",
894
+ "media_pool_refresh"
895
+ ]);
888
896
  window.ATEMControl = {
889
897
  ws: null,
890
898
  // ---- Declared-state-keys guard ------------------------------------
@@ -1088,7 +1096,9 @@
1088
1096
  // global verbs ignore it. Explicit params.me wins over the stamp.
1089
1097
  cmd(command, params = {}) {
1090
1098
  if (!this.ws || this.ws.readyState !== WebSocket.OPEN) return;
1091
- const me = window.Alpine?.store?.("atem")?.activeMe ?? 0;
1099
+ const store = window.Alpine?.store?.("atem");
1100
+ if (store?.switcherAway && !SESSION_VERBS.has(command)) return;
1101
+ const me = store?.activeMe ?? 0;
1092
1102
  this.ws.send(JSON.stringify({ command, me, ...params }));
1093
1103
  },
1094
1104
  // Audio per-strip command helper. Bundles ``source`` and ``channel``
@@ -1431,9 +1441,15 @@
1431
1441
  const alpineInstance = Alpine.store("atem");
1432
1442
  switch (data.type) {
1433
1443
  case "connection_status":
1444
+ if (!data.connected && data.reconnecting && alpineInstance.stateReady) {
1445
+ alpineInstance.switcherAway = true;
1446
+ alpineInstance.statusText = data.message;
1447
+ break;
1448
+ }
1434
1449
  alpineInstance.connected = data.connected;
1435
1450
  alpineInstance.statusText = data.message;
1436
1451
  if (data.connected) {
1452
+ alpineInstance.switcherAway = false;
1437
1453
  alpineInstance.armAudioMixerWait();
1438
1454
  this.isReconnecting = false;
1439
1455
  this.connectingIP = null;
@@ -2440,6 +2456,12 @@
2440
2456
  Alpine.store("atem", {
2441
2457
  // Connection state
2442
2458
  connected: false,
2459
+ // True from a mid-session "connection lost" (``reconnecting``) to
2460
+ // "restored": the switcher went quiet and bmdwire is
2461
+ // re-handshaking. ``connected`` stays true, so the surface keeps
2462
+ // the last state on screen; it goes inert under a banner, and
2463
+ // cmd() sends the switcher nothing (#38).
2464
+ switcherAway: false,
2443
2465
  // ``stateReady`` flips true on the FIRST atem_state arrival
2444
2466
  // (see updateState). Templates that don't want to render against
2445
2467
  // the initial default values gate their x-show on