bmdwire 1.5.2__tar.gz → 1.5.4__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.4}/PKG-INFO +2 -2
  2. {bmdwire-1.5.2 → bmdwire-1.5.4}/README.md +1 -1
  3. {bmdwire-1.5.2 → bmdwire-1.5.4}/atemwire/docs/SURFACE.md +18 -0
  4. {bmdwire-1.5.2 → bmdwire-1.5.4}/atemwire/protocol.py +13 -3
  5. {bmdwire-1.5.2 → bmdwire-1.5.4}/atemwire/transport.py +40 -22
  6. {bmdwire-1.5.2 → bmdwire-1.5.4/bmdwire.egg-info}/PKG-INFO +2 -2
  7. {bmdwire-1.5.2 → bmdwire-1.5.4}/pyproject.toml +1 -1
  8. {bmdwire-1.5.2 → bmdwire-1.5.4}/LICENSE +0 -0
  9. {bmdwire-1.5.2 → bmdwire-1.5.4}/atemwire/LICENSE +0 -0
  10. {bmdwire-1.5.2 → bmdwire-1.5.4}/atemwire/LICENSE-gpl3.txt +0 -0
  11. {bmdwire-1.5.2 → bmdwire-1.5.4}/atemwire/NOTICE +0 -0
  12. {bmdwire-1.5.2 → bmdwire-1.5.4}/atemwire/__init__.py +0 -0
  13. {bmdwire-1.5.2 → bmdwire-1.5.4}/atemwire/_socketqueue.py +0 -0
  14. {bmdwire-1.5.2 → bmdwire-1.5.4}/atemwire/_transfer.py +0 -0
  15. {bmdwire-1.5.2 → bmdwire-1.5.4}/atemwire/atem.py +0 -0
  16. {bmdwire-1.5.2 → bmdwire-1.5.4}/atemwire/connection.py +0 -0
  17. {bmdwire-1.5.2 → bmdwire-1.5.4}/atemwire/docs/MACRO_FORMAT.md +0 -0
  18. {bmdwire-1.5.2 → bmdwire-1.5.4}/atemwire/docs/MEDIA_LOCK.md +0 -0
  19. {bmdwire-1.5.2 → bmdwire-1.5.4}/atemwire/docs/PROFILE_FORMAT.md +0 -0
  20. {bmdwire-1.5.2 → bmdwire-1.5.4}/atemwire/docs/PROFILE_SAVE_RESTORE.md +0 -0
  21. {bmdwire-1.5.2 → bmdwire-1.5.4}/atemwire/helpers.py +0 -0
  22. {bmdwire-1.5.2 → bmdwire-1.5.4}/atemwire/imaging.py +0 -0
  23. {bmdwire-1.5.2 → bmdwire-1.5.4}/atemwire/macrotransfer/__init__.py +0 -0
  24. {bmdwire-1.5.2 → bmdwire-1.5.4}/atemwire/macrotransfer/_helpers.py +0 -0
  25. {bmdwire-1.5.2 → bmdwire-1.5.4}/atemwire/macrotransfer/_io.py +0 -0
  26. {bmdwire-1.5.2 → bmdwire-1.5.4}/atemwire/macrotransfer/downstream_keyer.py +0 -0
  27. {bmdwire-1.5.2 → bmdwire-1.5.4}/atemwire/macrotransfer/fairlight.py +0 -0
  28. {bmdwire-1.5.2 → bmdwire-1.5.4}/atemwire/macrotransfer/flow.py +0 -0
  29. {bmdwire-1.5.2 → bmdwire-1.5.4}/atemwire/macrotransfer/hyperdeck.py +0 -0
  30. {bmdwire-1.5.2 → bmdwire-1.5.4}/atemwire/macrotransfer/media.py +0 -0
  31. {bmdwire-1.5.2 → bmdwire-1.5.4}/atemwire/macrotransfer/switching.py +0 -0
  32. {bmdwire-1.5.2 → bmdwire-1.5.4}/atemwire/macrotransfer/upstream_keyer.py +0 -0
  33. {bmdwire-1.5.2 → bmdwire-1.5.4}/atemwire/mediaconvertmodule.c +0 -0
  34. {bmdwire-1.5.2 → bmdwire-1.5.4}/atemwire/messages/__init__.py +0 -0
  35. {bmdwire-1.5.2 → bmdwire-1.5.4}/atemwire/messages/_dsl.py +0 -0
  36. {bmdwire-1.5.2 → bmdwire-1.5.4}/atemwire/messages/audio_legacy.py +0 -0
  37. {bmdwire-1.5.2 → bmdwire-1.5.4}/atemwire/messages/camera_control.py +0 -0
  38. {bmdwire-1.5.2 → bmdwire-1.5.4}/atemwire/messages/color_generator.py +0 -0
  39. {bmdwire-1.5.2 → bmdwire-1.5.4}/atemwire/messages/downstream_keyer.py +0 -0
  40. {bmdwire-1.5.2 → bmdwire-1.5.4}/atemwire/messages/fade_to_black.py +0 -0
  41. {bmdwire-1.5.2 → bmdwire-1.5.4}/atemwire/messages/fairlight.py +0 -0
  42. {bmdwire-1.5.2 → bmdwire-1.5.4}/atemwire/messages/file_transfer.py +0 -0
  43. {bmdwire-1.5.2 → bmdwire-1.5.4}/atemwire/messages/hyperdeck.py +0 -0
  44. {bmdwire-1.5.2 → bmdwire-1.5.4}/atemwire/messages/input_video.py +0 -0
  45. {bmdwire-1.5.2 → bmdwire-1.5.4}/atemwire/messages/lock.py +0 -0
  46. {bmdwire-1.5.2 → bmdwire-1.5.4}/atemwire/messages/macros.py +0 -0
  47. {bmdwire-1.5.2 → bmdwire-1.5.4}/atemwire/messages/manual.py +0 -0
  48. {bmdwire-1.5.2 → bmdwire-1.5.4}/atemwire/messages/media.py +0 -0
  49. {bmdwire-1.5.2 → bmdwire-1.5.4}/atemwire/messages/meters.py +0 -0
  50. {bmdwire-1.5.2 → bmdwire-1.5.4}/atemwire/messages/multiviewer.py +0 -0
  51. {bmdwire-1.5.2 → bmdwire-1.5.4}/atemwire/messages/recording.py +0 -0
  52. {bmdwire-1.5.2 → bmdwire-1.5.4}/atemwire/messages/streaming.py +0 -0
  53. {bmdwire-1.5.2 → bmdwire-1.5.4}/atemwire/messages/supersource.py +0 -0
  54. {bmdwire-1.5.2 → bmdwire-1.5.4}/atemwire/messages/switching.py +0 -0
  55. {bmdwire-1.5.2 → bmdwire-1.5.4}/atemwire/messages/system_info.py +0 -0
  56. {bmdwire-1.5.2 → bmdwire-1.5.4}/atemwire/messages/tally.py +0 -0
  57. {bmdwire-1.5.2 → bmdwire-1.5.4}/atemwire/messages/transition.py +0 -0
  58. {bmdwire-1.5.2 → bmdwire-1.5.4}/atemwire/messages/upstream_keyer.py +0 -0
  59. {bmdwire-1.5.2 → bmdwire-1.5.4}/atemwire/pool.py +0 -0
  60. {bmdwire-1.5.2 → bmdwire-1.5.4}/atemwire/probe.py +0 -0
  61. {bmdwire-1.5.2 → bmdwire-1.5.4}/atemwire/profile/__init__.py +0 -0
  62. {bmdwire-1.5.2 → bmdwire-1.5.4}/atemwire/profile/_common.py +0 -0
  63. {bmdwire-1.5.2 → bmdwire-1.5.4}/atemwire/profile/_enums.py +0 -0
  64. {bmdwire-1.5.2 → bmdwire-1.5.4}/atemwire/profile/_xml.py +0 -0
  65. {bmdwire-1.5.2 → bmdwire-1.5.4}/atemwire/profile/apply.py +0 -0
  66. {bmdwire-1.5.2 → bmdwire-1.5.4}/atemwire/profile/options.py +0 -0
  67. {bmdwire-1.5.2 → bmdwire-1.5.4}/atemwire/profile/save.py +0 -0
  68. {bmdwire-1.5.2 → bmdwire-1.5.4}/atemwire/py.typed +0 -0
  69. {bmdwire-1.5.2 → bmdwire-1.5.4}/atemwire/ready.py +0 -0
  70. {bmdwire-1.5.2 → bmdwire-1.5.4}/atemwire/state.py +0 -0
  71. {bmdwire-1.5.2 → bmdwire-1.5.4}/atemwire/testing.py +0 -0
  72. {bmdwire-1.5.2 → bmdwire-1.5.4}/bmdwire.egg-info/SOURCES.txt +0 -0
  73. {bmdwire-1.5.2 → bmdwire-1.5.4}/bmdwire.egg-info/dependency_links.txt +0 -0
  74. {bmdwire-1.5.2 → bmdwire-1.5.4}/bmdwire.egg-info/requires.txt +0 -0
  75. {bmdwire-1.5.2 → bmdwire-1.5.4}/bmdwire.egg-info/top_level.txt +0 -0
  76. {bmdwire-1.5.2 → bmdwire-1.5.4}/hyperdeckwire/LICENSE +0 -0
  77. {bmdwire-1.5.2 → bmdwire-1.5.4}/hyperdeckwire/__init__.py +0 -0
  78. {bmdwire-1.5.2 → bmdwire-1.5.4}/hyperdeckwire/client.py +0 -0
  79. {bmdwire-1.5.2 → bmdwire-1.5.4}/hyperdeckwire/setup.py +0 -0
  80. {bmdwire-1.5.2 → bmdwire-1.5.4}/hyperdeckwire/upload.py +0 -0
  81. {bmdwire-1.5.2 → bmdwire-1.5.4}/setup.cfg +0 -0
  82. {bmdwire-1.5.2 → bmdwire-1.5.4}/setup.py +0 -0
  83. {bmdwire-1.5.2 → bmdwire-1.5.4}/ultimattewire/LICENSE +0 -0
  84. {bmdwire-1.5.2 → bmdwire-1.5.4}/ultimattewire/__init__.py +0 -0
  85. {bmdwire-1.5.2 → bmdwire-1.5.4}/ultimattewire/_preamble.py +0 -0
  86. {bmdwire-1.5.2 → bmdwire-1.5.4}/ultimattewire/_protocol.py +0 -0
  87. {bmdwire-1.5.2 → bmdwire-1.5.4}/ultimattewire/network.py +0 -0
  88. {bmdwire-1.5.2 → bmdwire-1.5.4}/ultimattewire/profile/__init__.py +0 -0
  89. {bmdwire-1.5.2 → bmdwire-1.5.4}/ultimattewire/profile/_ranges.py +0 -0
  90. {bmdwire-1.5.2 → bmdwire-1.5.4}/ultimattewire/profile/archive.py +0 -0
  91. {bmdwire-1.5.2 → bmdwire-1.5.4}/ultimattewire/profile/restore.py +0 -0
  92. {bmdwire-1.5.2 → bmdwire-1.5.4}/videohubwire/LICENSE +0 -0
  93. {bmdwire-1.5.2 → bmdwire-1.5.4}/videohubwire/__init__.py +0 -0
  94. {bmdwire-1.5.2 → bmdwire-1.5.4}/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.4
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.4`) 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.4`) so a later release cannot change your install under you.
19
19
 
20
20
  ## atemwire stands on pyatem
21
21
 
@@ -1095,6 +1095,24 @@ 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.
1110
+ 30. **A receive buffer size the system refuses fails the connect** (1.5.3). `UdpProtocol` asked
1111
+ for a 16 MB `SO_RCVBUF` with a bare `setsockopt`. Linux caps the request silently and Windows
1112
+ takes it, but macOS 12 refuses anything above `kern.ipc.maxsockbuf` with errno 55
1113
+ (`ENOBUFS`): the constructor raised, and no switcher could be connected from that system.
1114
+ *Fixed in `c08f830`:* the request steps down 16, 8, 4, 1 MB and keeps the first size taken,
1115
+ or the system's default if it takes none.
1098
1116
 
1099
1117
  ## Counts
1100
1118
 
@@ -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):
@@ -312,7 +312,13 @@ class UdpProtocol(BaseProtocol):
312
312
 
313
313
  self.sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
314
314
  self.sock.settimeout(self.timeout)
315
- self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_RCVBUF, 1024 * 1024 * 16)
315
+ # macOS refuses more than kern.ipc.maxsockbuf with errno 55 (ENOBUFS) instead of capping.
316
+ for megabytes in (16, 8, 4, 1):
317
+ try:
318
+ self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_RCVBUF, 1024 * 1024 * megabytes)
319
+ break
320
+ except OSError:
321
+ pass
316
322
  try:
317
323
  # Room for an upload budget sent back to back (768 chunks of
318
324
  # ~1.4 KB); the system may cap it lower, which is fine.
@@ -496,27 +502,12 @@ class UdpProtocol(BaseProtocol):
496
502
  elif queue is self.thread_queue:
497
503
  item = queue.get()
498
504
  if item is None:
499
- # Defensive: tolerate a stray wake sentinel on the
500
- # send queue (no current producer).
505
+ # wake_at's sentinel: select() returned, so the
506
+ # next one takes the new timeout.
501
507
  continue
502
508
  try:
503
- self._send_packet_low(item)
509
+ self._send_on_udp_thread(item)
504
510
  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
511
  self.log.error(e)
521
512
  # Queue a None to signal the socket died
522
513
  self.thread_recv_queue.put(None)
@@ -525,8 +516,34 @@ class UdpProtocol(BaseProtocol):
525
516
  self.thread_recv_queue.put(None)
526
517
  raise RuntimeError("Unexpected result from select()")
527
518
 
519
+ def _send_on_udp_thread(self, packet):
520
+ try:
521
+ self._send_packet_low(packet)
522
+ except OSError as e:
523
+ if e.errno not in TRANSIENT_SEND_ERRNOS:
524
+ raise
525
+ # A momentary network hiccup (VPN blip, ICMP unreachable
526
+ # burst, full socket buffer) must not kill the transport
527
+ # thread: a dead thread leaves a zombie connection whose
528
+ # worker blocks forever while is_connected stays True and
529
+ # send() silently drops (2026-07-06). Only an ACK or a
530
+ # handshake packet gets here: a reliable one keeps its
531
+ # sequence number in _send_packet_low and is resent. ACKs go
532
+ # out again with the next packet.
533
+ self.log.error(
534
+ f"Transient send error (packet dropped, thread stays up): {e}")
535
+
528
536
  def _send_packet(self, packet):
529
- self.thread_queue.put(packet)
537
+ if threading.current_thread() is self.thread:
538
+ # The UDP thread is thread_queue's only reader, and a put waits
539
+ # while the socketpair under it is full: a few hundred entries,
540
+ # fewer than an upload budget drain_queue puts at once. Its own
541
+ # packets (the ACK for each reliable packet received, a SYN
542
+ # after a timeout) queued behind a budget had it wait on itself
543
+ # for good, and the upload with it (2026-10-15). They go now.
544
+ self._send_on_udp_thread(packet)
545
+ else:
546
+ self.thread_queue.put(packet)
530
547
  self.packet_success += 1
531
548
 
532
549
  def close_session(self):
@@ -853,8 +870,9 @@ class UdpProtocol(BaseProtocol):
853
870
  protocol's timed work, which its packets alone can't drive (the
854
871
  switcher's pings never reach it). The earliest of several wins."""
855
872
  with self._wake_lock:
856
- if self._wake_due is None or when < self._wake_due:
857
- self._wake_due = when
873
+ if self._wake_due is not None and self._wake_due <= when:
874
+ return # select() wakes sooner anyway
875
+ self._wake_due = when
858
876
  self.thread_queue.put(None) # select() takes the new timeout
859
877
 
860
878
  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.4
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.4`) 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.4"
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