bmdwire 1.1.0__tar.gz → 1.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.
- {bmdwire-1.1.0/bmdwire.egg-info → bmdwire-1.3.0}/PKG-INFO +4 -4
- {bmdwire-1.1.0 → bmdwire-1.3.0}/README.md +3 -3
- {bmdwire-1.1.0 → bmdwire-1.3.0}/atemwire/atem.py +4 -1
- {bmdwire-1.1.0 → bmdwire-1.3.0}/atemwire/connection.py +36 -1
- bmdwire-1.3.0/atemwire/docs/SURFACE.md +995 -0
- {bmdwire-1.1.0 → bmdwire-1.3.0}/atemwire/macrotransfer/_helpers.py +13 -0
- {bmdwire-1.1.0 → bmdwire-1.3.0}/atemwire/macrotransfer/switching.py +4 -17
- {bmdwire-1.1.0 → bmdwire-1.3.0}/atemwire/messages/input_video.py +19 -0
- {bmdwire-1.1.0 → bmdwire-1.3.0}/atemwire/messages/upstream_keyer.py +11 -1
- {bmdwire-1.1.0 → bmdwire-1.3.0}/atemwire/probe.py +9 -2
- {bmdwire-1.1.0 → bmdwire-1.3.0}/atemwire/profile/apply.py +51 -4
- {bmdwire-1.1.0 → bmdwire-1.3.0}/atemwire/profile/save.py +11 -4
- {bmdwire-1.1.0 → bmdwire-1.3.0/bmdwire.egg-info}/PKG-INFO +4 -4
- {bmdwire-1.1.0 → bmdwire-1.3.0}/bmdwire.egg-info/SOURCES.txt +2 -0
- bmdwire-1.3.0/hyperdeckwire/__init__.py +74 -0
- bmdwire-1.3.0/hyperdeckwire/setup.py +660 -0
- {bmdwire-1.1.0 → bmdwire-1.3.0}/pyproject.toml +1 -1
- bmdwire-1.1.0/hyperdeckwire/__init__.py +0 -46
- {bmdwire-1.1.0 → bmdwire-1.3.0}/LICENSE +0 -0
- {bmdwire-1.1.0 → bmdwire-1.3.0}/atemwire/LICENSE +0 -0
- {bmdwire-1.1.0 → bmdwire-1.3.0}/atemwire/LICENSE-gpl3.txt +0 -0
- {bmdwire-1.1.0 → bmdwire-1.3.0}/atemwire/NOTICE +0 -0
- {bmdwire-1.1.0 → bmdwire-1.3.0}/atemwire/__init__.py +0 -0
- {bmdwire-1.1.0 → bmdwire-1.3.0}/atemwire/_socketqueue.py +0 -0
- {bmdwire-1.1.0 → bmdwire-1.3.0}/atemwire/_transfer.py +0 -0
- {bmdwire-1.1.0 → bmdwire-1.3.0}/atemwire/docs/MACRO_FORMAT.md +0 -0
- {bmdwire-1.1.0 → bmdwire-1.3.0}/atemwire/docs/MEDIA_LOCK.md +0 -0
- {bmdwire-1.1.0 → bmdwire-1.3.0}/atemwire/docs/PROFILE_FORMAT.md +0 -0
- {bmdwire-1.1.0 → bmdwire-1.3.0}/atemwire/docs/PROFILE_SAVE_RESTORE.md +0 -0
- {bmdwire-1.1.0 → bmdwire-1.3.0}/atemwire/helpers.py +0 -0
- {bmdwire-1.1.0 → bmdwire-1.3.0}/atemwire/imaging.py +0 -0
- {bmdwire-1.1.0 → bmdwire-1.3.0}/atemwire/macrotransfer/__init__.py +0 -0
- {bmdwire-1.1.0 → bmdwire-1.3.0}/atemwire/macrotransfer/_io.py +0 -0
- {bmdwire-1.1.0 → bmdwire-1.3.0}/atemwire/macrotransfer/downstream_keyer.py +0 -0
- {bmdwire-1.1.0 → bmdwire-1.3.0}/atemwire/macrotransfer/fairlight.py +0 -0
- {bmdwire-1.1.0 → bmdwire-1.3.0}/atemwire/macrotransfer/flow.py +0 -0
- {bmdwire-1.1.0 → bmdwire-1.3.0}/atemwire/macrotransfer/hyperdeck.py +0 -0
- {bmdwire-1.1.0 → bmdwire-1.3.0}/atemwire/macrotransfer/media.py +0 -0
- {bmdwire-1.1.0 → bmdwire-1.3.0}/atemwire/macrotransfer/upstream_keyer.py +0 -0
- {bmdwire-1.1.0 → bmdwire-1.3.0}/atemwire/mediaconvertmodule.c +0 -0
- {bmdwire-1.1.0 → bmdwire-1.3.0}/atemwire/messages/__init__.py +0 -0
- {bmdwire-1.1.0 → bmdwire-1.3.0}/atemwire/messages/_dsl.py +0 -0
- {bmdwire-1.1.0 → bmdwire-1.3.0}/atemwire/messages/audio_legacy.py +0 -0
- {bmdwire-1.1.0 → bmdwire-1.3.0}/atemwire/messages/camera_control.py +0 -0
- {bmdwire-1.1.0 → bmdwire-1.3.0}/atemwire/messages/color_generator.py +0 -0
- {bmdwire-1.1.0 → bmdwire-1.3.0}/atemwire/messages/downstream_keyer.py +0 -0
- {bmdwire-1.1.0 → bmdwire-1.3.0}/atemwire/messages/fade_to_black.py +0 -0
- {bmdwire-1.1.0 → bmdwire-1.3.0}/atemwire/messages/fairlight.py +0 -0
- {bmdwire-1.1.0 → bmdwire-1.3.0}/atemwire/messages/file_transfer.py +0 -0
- {bmdwire-1.1.0 → bmdwire-1.3.0}/atemwire/messages/hyperdeck.py +0 -0
- {bmdwire-1.1.0 → bmdwire-1.3.0}/atemwire/messages/lock.py +0 -0
- {bmdwire-1.1.0 → bmdwire-1.3.0}/atemwire/messages/macros.py +0 -0
- {bmdwire-1.1.0 → bmdwire-1.3.0}/atemwire/messages/manual.py +0 -0
- {bmdwire-1.1.0 → bmdwire-1.3.0}/atemwire/messages/media.py +0 -0
- {bmdwire-1.1.0 → bmdwire-1.3.0}/atemwire/messages/meters.py +0 -0
- {bmdwire-1.1.0 → bmdwire-1.3.0}/atemwire/messages/multiviewer.py +0 -0
- {bmdwire-1.1.0 → bmdwire-1.3.0}/atemwire/messages/recording.py +0 -0
- {bmdwire-1.1.0 → bmdwire-1.3.0}/atemwire/messages/streaming.py +0 -0
- {bmdwire-1.1.0 → bmdwire-1.3.0}/atemwire/messages/supersource.py +0 -0
- {bmdwire-1.1.0 → bmdwire-1.3.0}/atemwire/messages/switching.py +0 -0
- {bmdwire-1.1.0 → bmdwire-1.3.0}/atemwire/messages/system_info.py +0 -0
- {bmdwire-1.1.0 → bmdwire-1.3.0}/atemwire/messages/tally.py +0 -0
- {bmdwire-1.1.0 → bmdwire-1.3.0}/atemwire/messages/transition.py +0 -0
- {bmdwire-1.1.0 → bmdwire-1.3.0}/atemwire/pool.py +0 -0
- {bmdwire-1.1.0 → bmdwire-1.3.0}/atemwire/profile/__init__.py +0 -0
- {bmdwire-1.1.0 → bmdwire-1.3.0}/atemwire/profile/_common.py +0 -0
- {bmdwire-1.1.0 → bmdwire-1.3.0}/atemwire/profile/_enums.py +0 -0
- {bmdwire-1.1.0 → bmdwire-1.3.0}/atemwire/profile/_xml.py +0 -0
- {bmdwire-1.1.0 → bmdwire-1.3.0}/atemwire/profile/options.py +0 -0
- {bmdwire-1.1.0 → bmdwire-1.3.0}/atemwire/protocol.py +0 -0
- {bmdwire-1.1.0 → bmdwire-1.3.0}/atemwire/py.typed +0 -0
- {bmdwire-1.1.0 → bmdwire-1.3.0}/atemwire/ready.py +0 -0
- {bmdwire-1.1.0 → bmdwire-1.3.0}/atemwire/state.py +0 -0
- {bmdwire-1.1.0 → bmdwire-1.3.0}/atemwire/testing.py +0 -0
- {bmdwire-1.1.0 → bmdwire-1.3.0}/atemwire/transport.py +0 -0
- {bmdwire-1.1.0 → bmdwire-1.3.0}/bmdwire.egg-info/dependency_links.txt +0 -0
- {bmdwire-1.1.0 → bmdwire-1.3.0}/bmdwire.egg-info/requires.txt +0 -0
- {bmdwire-1.1.0 → bmdwire-1.3.0}/bmdwire.egg-info/top_level.txt +0 -0
- {bmdwire-1.1.0 → bmdwire-1.3.0}/hyperdeckwire/LICENSE +0 -0
- {bmdwire-1.1.0 → bmdwire-1.3.0}/hyperdeckwire/client.py +0 -0
- {bmdwire-1.1.0 → bmdwire-1.3.0}/hyperdeckwire/upload.py +0 -0
- {bmdwire-1.1.0 → bmdwire-1.3.0}/setup.cfg +0 -0
- {bmdwire-1.1.0 → bmdwire-1.3.0}/setup.py +0 -0
- {bmdwire-1.1.0 → bmdwire-1.3.0}/ultimattewire/LICENSE +0 -0
- {bmdwire-1.1.0 → bmdwire-1.3.0}/ultimattewire/__init__.py +0 -0
- {bmdwire-1.1.0 → bmdwire-1.3.0}/ultimattewire/_preamble.py +0 -0
- {bmdwire-1.1.0 → bmdwire-1.3.0}/ultimattewire/_protocol.py +0 -0
- {bmdwire-1.1.0 → bmdwire-1.3.0}/ultimattewire/network.py +0 -0
- {bmdwire-1.1.0 → bmdwire-1.3.0}/ultimattewire/profile/__init__.py +0 -0
- {bmdwire-1.1.0 → bmdwire-1.3.0}/ultimattewire/profile/_ranges.py +0 -0
- {bmdwire-1.1.0 → bmdwire-1.3.0}/ultimattewire/profile/archive.py +0 -0
- {bmdwire-1.1.0 → bmdwire-1.3.0}/ultimattewire/profile/restore.py +0 -0
- {bmdwire-1.1.0 → bmdwire-1.3.0}/videohubwire/LICENSE +0 -0
- {bmdwire-1.1.0 → bmdwire-1.3.0}/videohubwire/__init__.py +0 -0
- {bmdwire-1.1.0 → bmdwire-1.3.0}/videohubwire/client.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: bmdwire
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.3.0
|
|
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
|
|
@@ -45,11 +45,11 @@ pip install bmdwire
|
|
|
45
45
|
| Import | Purpose | License | Docs |
|
|
46
46
|
|---|---|---|---|
|
|
47
47
|
| **`atemwire`** | ATEM switchers: the native UDP protocol, a connection pool, macro bytecode, "Save Switcher State" profiles | LGPL-3.0-only | [atemwire/README.md](atemwire/README.md) |
|
|
48
|
-
| **`hyperdeckwire`** | HyperDeck recorders: transport control and clip listing over TCP 9993, clip upload over FTP | MIT | [hyperdeckwire/README.md](hyperdeckwire/README.md) |
|
|
48
|
+
| **`hyperdeckwire`** | HyperDeck recorders: transport control and clip listing over TCP 9993, clip upload over FTP, and the deck's configuration (network, FTP / Web Media Manager / Ethernet Protocol switches, certificate, users, time) over the API HyperDeck Setup drives | MIT | [hyperdeckwire/README.md](hyperdeckwire/README.md) |
|
|
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.
|
|
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.3.0`) so a later release cannot change your install under you.
|
|
53
53
|
|
|
54
54
|
## atemwire stands on pyatem
|
|
55
55
|
|
|
@@ -74,7 +74,7 @@ If you want the original rather than this fork, it is at
|
|
|
74
74
|
|
|
75
75
|
The four libraries share an author, a shape and a purpose: they are the device layer of one broadcast control application, extracted and published so others can use them. Until 1.0.x each was its own PyPI project with its own version number. Nothing downstream ever wanted them apart, and four numbers to track for one release train was confusion with no benefit, so since 1.1.0 they are one distribution: one tag, one version, one line to pin. The import names did not change.
|
|
76
76
|
|
|
77
|
-
The four old
|
|
77
|
+
The four old PyPI projects (`atemwire`, `hyperdeckwire`, `ultimattewire`, `videohubwire`) were deleted on 2026-09-16; bmdwire is the only package. They were not replaced by shims, on purpose: a release of `atemwire` that depended on bmdwire would have broken `pip install -U atemwire`, because pip installs the dependency first and then removes the old package's files, which are the same paths bmdwire just wrote. **If an environment still has any of the four old packages from before, uninstall them before installing bmdwire** (`pip uninstall atemwire hyperdeckwire ultimattewire videohubwire`). Two distributions owning one module directory work until one of them is uninstalled. A fresh virtualenv, a pipx install or a container build never has this problem.
|
|
78
78
|
|
|
79
79
|
## Development
|
|
80
80
|
|
|
@@ -11,11 +11,11 @@ pip install bmdwire
|
|
|
11
11
|
| Import | Purpose | License | Docs |
|
|
12
12
|
|---|---|---|---|
|
|
13
13
|
| **`atemwire`** | ATEM switchers: the native UDP protocol, a connection pool, macro bytecode, "Save Switcher State" profiles | LGPL-3.0-only | [atemwire/README.md](atemwire/README.md) |
|
|
14
|
-
| **`hyperdeckwire`** | HyperDeck recorders: transport control and clip listing over TCP 9993, clip upload over FTP | MIT | [hyperdeckwire/README.md](hyperdeckwire/README.md) |
|
|
14
|
+
| **`hyperdeckwire`** | HyperDeck recorders: transport control and clip listing over TCP 9993, clip upload over FTP, and the deck's configuration (network, FTP / Web Media Manager / Ethernet Protocol switches, certificate, users, time) over the API HyperDeck Setup drives | MIT | [hyperdeckwire/README.md](hyperdeckwire/README.md) |
|
|
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.
|
|
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.3.0`) so a later release cannot change your install under you.
|
|
19
19
|
|
|
20
20
|
## atemwire stands on pyatem
|
|
21
21
|
|
|
@@ -40,7 +40,7 @@ If you want the original rather than this fork, it is at
|
|
|
40
40
|
|
|
41
41
|
The four libraries share an author, a shape and a purpose: they are the device layer of one broadcast control application, extracted and published so others can use them. Until 1.0.x each was its own PyPI project with its own version number. Nothing downstream ever wanted them apart, and four numbers to track for one release train was confusion with no benefit, so since 1.1.0 they are one distribution: one tag, one version, one line to pin. The import names did not change.
|
|
42
42
|
|
|
43
|
-
The four old
|
|
43
|
+
The four old PyPI projects (`atemwire`, `hyperdeckwire`, `ultimattewire`, `videohubwire`) were deleted on 2026-09-16; bmdwire is the only package. They were not replaced by shims, on purpose: a release of `atemwire` that depended on bmdwire would have broken `pip install -U atemwire`, because pip installs the dependency first and then removes the old package's files, which are the same paths bmdwire just wrote. **If an environment still has any of the four old packages from before, uninstall them before installing bmdwire** (`pip uninstall atemwire hyperdeckwire ultimattewire videohubwire`). Two distributions owning one module directory work until one of them is uninstalled. A fresh virtualenv, a pipx install or a container build never has this problem.
|
|
44
44
|
|
|
45
45
|
## Development
|
|
46
46
|
|
|
@@ -221,8 +221,11 @@ class ATEM:
|
|
|
221
221
|
|
|
222
222
|
@property
|
|
223
223
|
def connected(self) -> bool:
|
|
224
|
+
"""True while the session is up AND its state is current — False
|
|
225
|
+
during a transport re-handshake, when the state is empty (see
|
|
226
|
+
``ATEMConnection.is_ready``)."""
|
|
224
227
|
conn = self._conn
|
|
225
|
-
return bool(conn and conn.
|
|
228
|
+
return bool(conn and conn.is_ready)
|
|
226
229
|
|
|
227
230
|
@property
|
|
228
231
|
def video_mode(self) -> str:
|
|
@@ -131,6 +131,12 @@ class ATEMConnection:
|
|
|
131
131
|
# immediately reflects the post-disconnect state.
|
|
132
132
|
self._handshake_done: bool = False
|
|
133
133
|
|
|
134
|
+
# ``_session_ready`` follows the protocol's 'connected' /
|
|
135
|
+
# 'disconnected' events: False from the moment a transport
|
|
136
|
+
# re-handshake empties mixerstate until the new state dump is
|
|
137
|
+
# complete. Read through ``is_ready``.
|
|
138
|
+
self._session_ready: bool = False
|
|
139
|
+
|
|
134
140
|
self._lifecycle_lock = threading.Lock() # serializes connect/disconnect calls
|
|
135
141
|
self._transfer_serial_lock = threading.Lock() # one native transfer at a time
|
|
136
142
|
|
|
@@ -168,6 +174,19 @@ class ATEMConnection:
|
|
|
168
174
|
worker = self._worker
|
|
169
175
|
return worker is not None and worker.is_alive()
|
|
170
176
|
|
|
177
|
+
@property
|
|
178
|
+
def is_ready(self) -> bool:
|
|
179
|
+
"""True iff ``is_connected`` AND the switcher's state is current.
|
|
180
|
+
|
|
181
|
+
After ~5 s without packets the transport re-handshakes on its own:
|
|
182
|
+
the protocol raises 'disconnected' and empties ``mixerstate``, the
|
|
183
|
+
same worker carries on, and 'connected' fires again once the new
|
|
184
|
+
state dump is complete. ``is_connected`` stays True through that
|
|
185
|
+
gap (the worker is alive and recovering, so the pool must not evict
|
|
186
|
+
it); ``is_ready`` is False for its whole length.
|
|
187
|
+
"""
|
|
188
|
+
return self._session_ready and self.is_connected
|
|
189
|
+
|
|
171
190
|
@property
|
|
172
191
|
def mixerstate(self) -> dict:
|
|
173
192
|
"""Live state dict from atemwire. Safe to read from any thread — CPython
|
|
@@ -405,6 +424,7 @@ class ATEMConnection:
|
|
|
405
424
|
self._ready_event.clear()
|
|
406
425
|
self._connect_error = None
|
|
407
426
|
self._handshake_done = False
|
|
427
|
+
self._session_ready = False
|
|
408
428
|
self._died_signalled = False
|
|
409
429
|
self.ip_address = ip_address
|
|
410
430
|
|
|
@@ -478,12 +498,27 @@ class ATEMConnection:
|
|
|
478
498
|
# bare (non-indexed) field so the event name is 'change:macro-play-status'.
|
|
479
499
|
def _on_macro_play(contents):
|
|
480
500
|
running = bool(getattr(contents, 'running', False))
|
|
481
|
-
|
|
501
|
+
# Slot 0 is a real macro; only 0xFFFF (or no index) is idle.
|
|
502
|
+
idx = getattr(contents, 'index', None)
|
|
503
|
+
idx = 0xFFFF if idx is None else int(idx)
|
|
482
504
|
if running and idx != 0xFFFF and idx >= 0:
|
|
483
505
|
self.last_run_macro_index = idx
|
|
484
506
|
|
|
485
507
|
self._protocol.on('change:macro-play-status', _on_macro_play)
|
|
486
508
|
|
|
509
|
+
# Session readiness (``is_ready``): 'disconnected' is raised when
|
|
510
|
+
# the transport gives up on the session and mixerstate is
|
|
511
|
+
# emptied; 'connected' when a state dump is complete, at the
|
|
512
|
+
# first connect and again after every re-handshake.
|
|
513
|
+
def _on_connected():
|
|
514
|
+
self._session_ready = True
|
|
515
|
+
|
|
516
|
+
def _on_disconnected():
|
|
517
|
+
self._session_ready = False
|
|
518
|
+
|
|
519
|
+
self._protocol.on('connected', _on_connected)
|
|
520
|
+
self._protocol.on('disconnected', _on_disconnected)
|
|
521
|
+
|
|
487
522
|
try:
|
|
488
523
|
self._protocol.connect()
|
|
489
524
|
except Exception as e:
|