bmdwire 1.5.2__tar.gz → 1.5.3__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 (94) hide show
  1. {bmdwire-1.5.2/bmdwire.egg-info → bmdwire-1.5.3}/PKG-INFO +2 -2
  2. {bmdwire-1.5.2 → bmdwire-1.5.3}/README.md +1 -1
  3. {bmdwire-1.5.2 → bmdwire-1.5.3}/atemwire/docs/SURFACE.md +12 -0
  4. {bmdwire-1.5.2 → bmdwire-1.5.3}/atemwire/protocol.py +13 -3
  5. {bmdwire-1.5.2 → bmdwire-1.5.3}/atemwire/transport.py +33 -21
  6. {bmdwire-1.5.2 → bmdwire-1.5.3/bmdwire.egg-info}/PKG-INFO +2 -2
  7. {bmdwire-1.5.2 → bmdwire-1.5.3}/pyproject.toml +1 -1
  8. {bmdwire-1.5.2 → bmdwire-1.5.3}/LICENSE +0 -0
  9. {bmdwire-1.5.2 → bmdwire-1.5.3}/atemwire/LICENSE +0 -0
  10. {bmdwire-1.5.2 → bmdwire-1.5.3}/atemwire/LICENSE-gpl3.txt +0 -0
  11. {bmdwire-1.5.2 → bmdwire-1.5.3}/atemwire/NOTICE +0 -0
  12. {bmdwire-1.5.2 → bmdwire-1.5.3}/atemwire/__init__.py +0 -0
  13. {bmdwire-1.5.2 → bmdwire-1.5.3}/atemwire/_socketqueue.py +0 -0
  14. {bmdwire-1.5.2 → bmdwire-1.5.3}/atemwire/_transfer.py +0 -0
  15. {bmdwire-1.5.2 → bmdwire-1.5.3}/atemwire/atem.py +0 -0
  16. {bmdwire-1.5.2 → bmdwire-1.5.3}/atemwire/connection.py +0 -0
  17. {bmdwire-1.5.2 → bmdwire-1.5.3}/atemwire/docs/MACRO_FORMAT.md +0 -0
  18. {bmdwire-1.5.2 → bmdwire-1.5.3}/atemwire/docs/MEDIA_LOCK.md +0 -0
  19. {bmdwire-1.5.2 → bmdwire-1.5.3}/atemwire/docs/PROFILE_FORMAT.md +0 -0
  20. {bmdwire-1.5.2 → bmdwire-1.5.3}/atemwire/docs/PROFILE_SAVE_RESTORE.md +0 -0
  21. {bmdwire-1.5.2 → bmdwire-1.5.3}/atemwire/helpers.py +0 -0
  22. {bmdwire-1.5.2 → bmdwire-1.5.3}/atemwire/imaging.py +0 -0
  23. {bmdwire-1.5.2 → bmdwire-1.5.3}/atemwire/macrotransfer/__init__.py +0 -0
  24. {bmdwire-1.5.2 → bmdwire-1.5.3}/atemwire/macrotransfer/_helpers.py +0 -0
  25. {bmdwire-1.5.2 → bmdwire-1.5.3}/atemwire/macrotransfer/_io.py +0 -0
  26. {bmdwire-1.5.2 → bmdwire-1.5.3}/atemwire/macrotransfer/downstream_keyer.py +0 -0
  27. {bmdwire-1.5.2 → bmdwire-1.5.3}/atemwire/macrotransfer/fairlight.py +0 -0
  28. {bmdwire-1.5.2 → bmdwire-1.5.3}/atemwire/macrotransfer/flow.py +0 -0
  29. {bmdwire-1.5.2 → bmdwire-1.5.3}/atemwire/macrotransfer/hyperdeck.py +0 -0
  30. {bmdwire-1.5.2 → bmdwire-1.5.3}/atemwire/macrotransfer/media.py +0 -0
  31. {bmdwire-1.5.2 → bmdwire-1.5.3}/atemwire/macrotransfer/switching.py +0 -0
  32. {bmdwire-1.5.2 → bmdwire-1.5.3}/atemwire/macrotransfer/upstream_keyer.py +0 -0
  33. {bmdwire-1.5.2 → bmdwire-1.5.3}/atemwire/mediaconvertmodule.c +0 -0
  34. {bmdwire-1.5.2 → bmdwire-1.5.3}/atemwire/messages/__init__.py +0 -0
  35. {bmdwire-1.5.2 → bmdwire-1.5.3}/atemwire/messages/_dsl.py +0 -0
  36. {bmdwire-1.5.2 → bmdwire-1.5.3}/atemwire/messages/audio_legacy.py +0 -0
  37. {bmdwire-1.5.2 → bmdwire-1.5.3}/atemwire/messages/camera_control.py +0 -0
  38. {bmdwire-1.5.2 → bmdwire-1.5.3}/atemwire/messages/color_generator.py +0 -0
  39. {bmdwire-1.5.2 → bmdwire-1.5.3}/atemwire/messages/downstream_keyer.py +0 -0
  40. {bmdwire-1.5.2 → bmdwire-1.5.3}/atemwire/messages/fade_to_black.py +0 -0
  41. {bmdwire-1.5.2 → bmdwire-1.5.3}/atemwire/messages/fairlight.py +0 -0
  42. {bmdwire-1.5.2 → bmdwire-1.5.3}/atemwire/messages/file_transfer.py +0 -0
  43. {bmdwire-1.5.2 → bmdwire-1.5.3}/atemwire/messages/hyperdeck.py +0 -0
  44. {bmdwire-1.5.2 → bmdwire-1.5.3}/atemwire/messages/input_video.py +0 -0
  45. {bmdwire-1.5.2 → bmdwire-1.5.3}/atemwire/messages/lock.py +0 -0
  46. {bmdwire-1.5.2 → bmdwire-1.5.3}/atemwire/messages/macros.py +0 -0
  47. {bmdwire-1.5.2 → bmdwire-1.5.3}/atemwire/messages/manual.py +0 -0
  48. {bmdwire-1.5.2 → bmdwire-1.5.3}/atemwire/messages/media.py +0 -0
  49. {bmdwire-1.5.2 → bmdwire-1.5.3}/atemwire/messages/meters.py +0 -0
  50. {bmdwire-1.5.2 → bmdwire-1.5.3}/atemwire/messages/multiviewer.py +0 -0
  51. {bmdwire-1.5.2 → bmdwire-1.5.3}/atemwire/messages/recording.py +0 -0
  52. {bmdwire-1.5.2 → bmdwire-1.5.3}/atemwire/messages/streaming.py +0 -0
  53. {bmdwire-1.5.2 → bmdwire-1.5.3}/atemwire/messages/supersource.py +0 -0
  54. {bmdwire-1.5.2 → bmdwire-1.5.3}/atemwire/messages/switching.py +0 -0
  55. {bmdwire-1.5.2 → bmdwire-1.5.3}/atemwire/messages/system_info.py +0 -0
  56. {bmdwire-1.5.2 → bmdwire-1.5.3}/atemwire/messages/tally.py +0 -0
  57. {bmdwire-1.5.2 → bmdwire-1.5.3}/atemwire/messages/transition.py +0 -0
  58. {bmdwire-1.5.2 → bmdwire-1.5.3}/atemwire/messages/upstream_keyer.py +0 -0
  59. {bmdwire-1.5.2 → bmdwire-1.5.3}/atemwire/pool.py +0 -0
  60. {bmdwire-1.5.2 → bmdwire-1.5.3}/atemwire/probe.py +0 -0
  61. {bmdwire-1.5.2 → bmdwire-1.5.3}/atemwire/profile/__init__.py +0 -0
  62. {bmdwire-1.5.2 → bmdwire-1.5.3}/atemwire/profile/_common.py +0 -0
  63. {bmdwire-1.5.2 → bmdwire-1.5.3}/atemwire/profile/_enums.py +0 -0
  64. {bmdwire-1.5.2 → bmdwire-1.5.3}/atemwire/profile/_xml.py +0 -0
  65. {bmdwire-1.5.2 → bmdwire-1.5.3}/atemwire/profile/apply.py +0 -0
  66. {bmdwire-1.5.2 → bmdwire-1.5.3}/atemwire/profile/options.py +0 -0
  67. {bmdwire-1.5.2 → bmdwire-1.5.3}/atemwire/profile/save.py +0 -0
  68. {bmdwire-1.5.2 → bmdwire-1.5.3}/atemwire/py.typed +0 -0
  69. {bmdwire-1.5.2 → bmdwire-1.5.3}/atemwire/ready.py +0 -0
  70. {bmdwire-1.5.2 → bmdwire-1.5.3}/atemwire/state.py +0 -0
  71. {bmdwire-1.5.2 → bmdwire-1.5.3}/atemwire/testing.py +0 -0
  72. {bmdwire-1.5.2 → bmdwire-1.5.3}/bmdwire.egg-info/SOURCES.txt +0 -0
  73. {bmdwire-1.5.2 → bmdwire-1.5.3}/bmdwire.egg-info/dependency_links.txt +0 -0
  74. {bmdwire-1.5.2 → bmdwire-1.5.3}/bmdwire.egg-info/requires.txt +0 -0
  75. {bmdwire-1.5.2 → bmdwire-1.5.3}/bmdwire.egg-info/top_level.txt +0 -0
  76. {bmdwire-1.5.2 → bmdwire-1.5.3}/hyperdeckwire/LICENSE +0 -0
  77. {bmdwire-1.5.2 → bmdwire-1.5.3}/hyperdeckwire/__init__.py +0 -0
  78. {bmdwire-1.5.2 → bmdwire-1.5.3}/hyperdeckwire/client.py +0 -0
  79. {bmdwire-1.5.2 → bmdwire-1.5.3}/hyperdeckwire/setup.py +0 -0
  80. {bmdwire-1.5.2 → bmdwire-1.5.3}/hyperdeckwire/upload.py +0 -0
  81. {bmdwire-1.5.2 → bmdwire-1.5.3}/setup.cfg +0 -0
  82. {bmdwire-1.5.2 → bmdwire-1.5.3}/setup.py +0 -0
  83. {bmdwire-1.5.2 → bmdwire-1.5.3}/ultimattewire/LICENSE +0 -0
  84. {bmdwire-1.5.2 → bmdwire-1.5.3}/ultimattewire/__init__.py +0 -0
  85. {bmdwire-1.5.2 → bmdwire-1.5.3}/ultimattewire/_preamble.py +0 -0
  86. {bmdwire-1.5.2 → bmdwire-1.5.3}/ultimattewire/_protocol.py +0 -0
  87. {bmdwire-1.5.2 → bmdwire-1.5.3}/ultimattewire/network.py +0 -0
  88. {bmdwire-1.5.2 → bmdwire-1.5.3}/ultimattewire/profile/__init__.py +0 -0
  89. {bmdwire-1.5.2 → bmdwire-1.5.3}/ultimattewire/profile/_ranges.py +0 -0
  90. {bmdwire-1.5.2 → bmdwire-1.5.3}/ultimattewire/profile/archive.py +0 -0
  91. {bmdwire-1.5.2 → bmdwire-1.5.3}/ultimattewire/profile/restore.py +0 -0
  92. {bmdwire-1.5.2 → bmdwire-1.5.3}/videohubwire/LICENSE +0 -0
  93. {bmdwire-1.5.2 → bmdwire-1.5.3}/videohubwire/__init__.py +0 -0
  94. {bmdwire-1.5.2 → bmdwire-1.5.3}/videohubwire/client.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: bmdwire
3
- Version: 1.5.2
3
+ Version: 1.5.3
4
4
  Summary: Python libraries for Blackmagic Design broadcast devices: ATEM switchers, HyperDeck recorders, Ultimatte keyers and Videohub routers
5
5
  Author: Lucas Romanenko, Martijn Braam and the OpenAtem contributors
6
6
  License-Expression: LGPL-3.0-only AND MIT
@@ -49,7 +49,7 @@ pip install bmdwire
49
49
  | **`ultimattewire`** | Ultimatte 12 keyers: archive and restore over the native TCP protocol, Smart Remote 4 compatible zips | MIT | [ultimattewire/README.md](ultimattewire/README.md) |
50
50
  | **`videohubwire`** | Videohub routers: state snapshot, crosspoint routing, labels over TCP 9990 | MIT | [videohubwire/README.md](videohubwire/README.md) |
51
51
 
52
- All four are pure standard library except atemwire, whose small C extension ships prebuilt for Linux, macOS and Windows on CPython 3.10 to 3.14, so installing needs no compiler. Pin the version in a requirements file (`bmdwire==1.5.2`) so a later release cannot change your install under you.
52
+ All four are pure standard library except atemwire, whose small C extension ships prebuilt for Linux, macOS and Windows on CPython 3.10 to 3.14, so installing needs no compiler. Pin the version in a requirements file (`bmdwire==1.5.3`) so a later release cannot change your install under you.
53
53
 
54
54
  ## atemwire stands on pyatem
55
55
 
@@ -15,7 +15,7 @@ pip install bmdwire
15
15
  | **`ultimattewire`** | Ultimatte 12 keyers: archive and restore over the native TCP protocol, Smart Remote 4 compatible zips | MIT | [ultimattewire/README.md](ultimattewire/README.md) |
16
16
  | **`videohubwire`** | Videohub routers: state snapshot, crosspoint routing, labels over TCP 9990 | MIT | [videohubwire/README.md](videohubwire/README.md) |
17
17
 
18
- All four are pure standard library except atemwire, whose small C extension ships prebuilt for Linux, macOS and Windows on CPython 3.10 to 3.14, so installing needs no compiler. Pin the version in a requirements file (`bmdwire==1.5.2`) so a later release cannot change your install under you.
18
+ All four are pure standard library except atemwire, whose small C extension ships prebuilt for Linux, macOS and Windows on CPython 3.10 to 3.14, so installing needs no compiler. Pin the version in a requirements file (`bmdwire==1.5.3`) so a later release cannot change your install under you.
19
19
 
20
20
  ## atemwire stands on pyatem
21
21
 
@@ -1095,6 +1095,18 @@ it; item 27 was fixed after 1.5.1.
1095
1095
  queued at the release, and its next request goes out at once, beside another client's,
1096
1096
  aborting it as before. *Fixed in `1b30449`:* every per-frame release starts the wait, after an
1097
1097
  FTDE 6 too, and so does any client's announced release.
1098
+ 28. **The UDP thread can wait on itself for good** (1.5.0). `thread_queue` is a `SocketQueue`:
1099
+ each put writes a byte to a socketpair (278 fit on Linux) that only the UDP thread reads.
1100
+ `drain_queue` puts a whole upload budget, up to 768 chunks, at once, and the UDP thread queued
1101
+ its own packets too, the ACK for every reliable packet received among them. A packet arriving
1102
+ mid-budget (another session's lock or transfer broadcast, say) left both threads waiting in
1103
+ `SocketQueue.put` for good: the upload stops, and a caller's timeout never runs if its pump is
1104
+ the protocol thread. *Fixed in `81955c1`:* what the UDP thread sends goes to the socket at once.
1105
+ 29. **A caller's Wakeup runs the trigger in every yield** (1.5.2). `_service_yields` ran on
1106
+ every `Wakeup`, a caller's included, and triggered each store with work queued before its
1107
+ turn was due: two INFO lines per `Wakeup`, 100 a second under WebATEM's pump. *Fixed in
1108
+ `81955c1`:* the trigger runs once the turn is due; before it, the protocol only wakes for the
1109
+ due time again.
1098
1110
 
1099
1111
  ## Counts
1100
1112
 
@@ -992,12 +992,22 @@ class AtemProtocol:
992
992
 
993
993
  def _service_yields(self):
994
994
  """Ask for each store we left free whose yield is over; forget a
995
- yield that is over with nothing waiting on it."""
995
+ yield that is over with nothing waiting on it. A Wakeup comes from
996
+ callers too (WebATEM's pump sends one every 20 ms): a yield not yet
997
+ over is only woken for again, its time perhaps moved by someone at
998
+ work, so the trigger runs once per turn, not once per tick."""
999
+ now = time.monotonic()
996
1000
  for store in list(self._yielding):
1001
+ due = self._yield_due(store)
997
1002
  if not self.transfer_queue.get(store):
998
- if time.monotonic() >= self._yield_due(store):
1003
+ if now >= due:
999
1004
  del self._yielding[store]
1000
- elif not self.transfer_requested and not self.locks.get(store):
1005
+ elif (self.transfer_requested or self.locks.get(store)
1006
+ or store in self._foreign_held):
1007
+ continue # their release asks for us
1008
+ elif now < due:
1009
+ self._wake_at(due)
1010
+ else:
1001
1011
  self._transfer_trigger(store)
1002
1012
 
1003
1013
  def _transfer_trigger(self, store, retry=False):
@@ -496,27 +496,12 @@ class UdpProtocol(BaseProtocol):
496
496
  elif queue is self.thread_queue:
497
497
  item = queue.get()
498
498
  if item is None:
499
- # Defensive: tolerate a stray wake sentinel on the
500
- # send queue (no current producer).
499
+ # wake_at's sentinel: select() returned, so the
500
+ # next one takes the new timeout.
501
501
  continue
502
502
  try:
503
- self._send_packet_low(item)
503
+ self._send_on_udp_thread(item)
504
504
  except OSError as e:
505
- if e.errno in TRANSIENT_SEND_ERRNOS:
506
- # A momentary network hiccup (VPN blip, ICMP
507
- # unreachable burst, full socket buffer) must
508
- # not kill the transport thread: a dead thread
509
- # leaves a zombie connection whose worker
510
- # blocks forever while is_connected stays True
511
- # and send() silently drops (2026-07-06).
512
- # Only an ACK or a handshake packet gets here:
513
- # a reliable one keeps its sequence number in
514
- # _send_packet_low and is resent. ACKs go out
515
- # again with the next packet.
516
- self.log.error(
517
- f"Transient send error (packet dropped, "
518
- f"thread stays up): {e}")
519
- continue
520
505
  self.log.error(e)
521
506
  # Queue a None to signal the socket died
522
507
  self.thread_recv_queue.put(None)
@@ -525,8 +510,34 @@ class UdpProtocol(BaseProtocol):
525
510
  self.thread_recv_queue.put(None)
526
511
  raise RuntimeError("Unexpected result from select()")
527
512
 
513
+ def _send_on_udp_thread(self, packet):
514
+ try:
515
+ self._send_packet_low(packet)
516
+ except OSError as e:
517
+ if e.errno not in TRANSIENT_SEND_ERRNOS:
518
+ raise
519
+ # A momentary network hiccup (VPN blip, ICMP unreachable
520
+ # burst, full socket buffer) must not kill the transport
521
+ # thread: a dead thread leaves a zombie connection whose
522
+ # worker blocks forever while is_connected stays True and
523
+ # send() silently drops (2026-07-06). Only an ACK or a
524
+ # handshake packet gets here: a reliable one keeps its
525
+ # sequence number in _send_packet_low and is resent. ACKs go
526
+ # out again with the next packet.
527
+ self.log.error(
528
+ f"Transient send error (packet dropped, thread stays up): {e}")
529
+
528
530
  def _send_packet(self, packet):
529
- self.thread_queue.put(packet)
531
+ if threading.current_thread() is self.thread:
532
+ # The UDP thread is thread_queue's only reader, and a put waits
533
+ # while the socketpair under it is full: a few hundred entries,
534
+ # fewer than an upload budget drain_queue puts at once. Its own
535
+ # packets (the ACK for each reliable packet received, a SYN
536
+ # after a timeout) queued behind a budget had it wait on itself
537
+ # for good, and the upload with it (2026-10-15). They go now.
538
+ self._send_on_udp_thread(packet)
539
+ else:
540
+ self.thread_queue.put(packet)
530
541
  self.packet_success += 1
531
542
 
532
543
  def close_session(self):
@@ -853,8 +864,9 @@ class UdpProtocol(BaseProtocol):
853
864
  protocol's timed work, which its packets alone can't drive (the
854
865
  switcher's pings never reach it). The earliest of several wins."""
855
866
  with self._wake_lock:
856
- if self._wake_due is None or when < self._wake_due:
857
- self._wake_due = when
867
+ if self._wake_due is not None and self._wake_due <= when:
868
+ return # select() wakes sooner anyway
869
+ self._wake_due = when
858
870
  self.thread_queue.put(None) # select() takes the new timeout
859
871
 
860
872
  def _fire_wake(self):
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: bmdwire
3
- Version: 1.5.2
3
+ Version: 1.5.3
4
4
  Summary: Python libraries for Blackmagic Design broadcast devices: ATEM switchers, HyperDeck recorders, Ultimatte keyers and Videohub routers
5
5
  Author: Lucas Romanenko, Martijn Braam and the OpenAtem contributors
6
6
  License-Expression: LGPL-3.0-only AND MIT
@@ -49,7 +49,7 @@ pip install bmdwire
49
49
  | **`ultimattewire`** | Ultimatte 12 keyers: archive and restore over the native TCP protocol, Smart Remote 4 compatible zips | MIT | [ultimattewire/README.md](ultimattewire/README.md) |
50
50
  | **`videohubwire`** | Videohub routers: state snapshot, crosspoint routing, labels over TCP 9990 | MIT | [videohubwire/README.md](videohubwire/README.md) |
51
51
 
52
- All four are pure standard library except atemwire, whose small C extension ships prebuilt for Linux, macOS and Windows on CPython 3.10 to 3.14, so installing needs no compiler. Pin the version in a requirements file (`bmdwire==1.5.2`) so a later release cannot change your install under you.
52
+ All four are pure standard library except atemwire, whose small C extension ships prebuilt for Linux, macOS and Windows on CPython 3.10 to 3.14, so installing needs no compiler. Pin the version in a requirements file (`bmdwire==1.5.3`) so a later release cannot change your install under you.
53
53
 
54
54
  ## atemwire stands on pyatem
55
55
 
@@ -13,7 +13,7 @@ build-backend = "setuptools.build_meta"
13
13
 
14
14
  [project]
15
15
  name = "bmdwire"
16
- version = "1.5.2"
16
+ version = "1.5.3"
17
17
  description = "Python libraries for Blackmagic Design broadcast devices: ATEM switchers, HyperDeck recorders, Ultimatte keyers and Videohub routers"
18
18
  readme = "README.md"
19
19
  # atemwire is a fork of pyatem and stays LGPL-3.0-only; the other three were
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes