Skip to main content
Pre-release

This release is a pre-release and may not be stable for production use.

ultimattewire

ultimattewire is a small, dependency-free Python library that archives and restores the configuration of a Blackmagic Ultimatte 12 or Ultimatte 12 4K keyer over its native TCP protocol. It produces and consumes the same zip archives that Blackmagic's Ultimatte Smart Remote 4 (also shipped as "Ultimatte Software Control") writes with Archive All and reads with Restore, so an archive taken with this library restores from the vendor app and vice versa. Blackmagic does not document the protocol; everything here was reverse-engineered from packet captures of the vendor app talking to real hardware, which is why the wire details in the code are marked as not to be changed without re-validating against a unit.

Features

  • archive_unit_to_bytes(host): pull every saved preset slot plus the GPISettings and SavedSettings resources into an in-memory zip in Smart Remote's exact layout, with a human-readable annotated state dump riding along.
  • restore_unit_from_bytes(host, zip_bytes): push such a zip back, in the order the vendor app uses, aborting at the first rejected write.
  • Reads the unit's text prelude on TCP 9998 (label, firmware release, live control values, the preset FILE LIST) and does binary slot/resource reads and writes on TCP 9996.
  • Failed reads are omitted from the archive and reported as warnings instead of being written as placeholder bytes that a later restore would push into live hardware.
  • Short reads raise; truncated blobs are never archived.
  • One-retry connects to survive cold-ARP and first-packet hiccups.
  • Per-parameter display ranges for about 150 control values, used to annotate the state dump (raw 0..10000 to percent, frames, pixels).
  • Pure standard library. No logging; results and warnings are returned to the caller.

Install

pip install "git+https://github.com/lucas-romanenko/ultimattewire.git@v0.1.0.dev0"

Python 3.10 or newer. To run the tests from a checkout:

pip install ".[test]"
python -m pytest

Usage

from ultimattewire import archive_unit_to_bytes, restore_unit_from_bytes

# Archive one unit. The label comes from the unit's own Label field.
zip_bytes, info = archive_unit_to_bytes("192.0.2.21")
with open(f"{info['label']}.zip", "wb") as f:
    f.write(zip_bytes)
print(info["slots"], info["warnings"])

# Restore it (or a Smart Remote "Archive All" zip) onto another unit.
with open("Keyer_A.zip", "rb") as f:
    ok, report = restore_unit_from_bytes("192.0.2.22", f.read())
if not ok:
    print("stopped at", report["stopped_at"])

A zip produced by this library can be opened with Smart Remote 4's Restore unchanged, and a Smart Remote "Archive All" zip can be passed straight to restore_unit_from_bytes.

Protocol notes

This is the reverse-engineered part and the most useful thing to read before changing anything.

How it was determined

The wire format was recovered from packet captures of Blackmagic's Ultimatte Smart Remote 4 / Ultimatte Software Control performing Archive All and Restore against a real Ultimatte 12 4K. The library was then validated by round-tripping archives through the vendor app: a zip produced here restores from Smart Remote, and a Smart Remote zip restores through this code. The display ranges come from the Ultimatte 12 Operations Manual (February 2026 revision) and from Smart Remote 4 panel screenshots, not from the wire.

Two TCP channels

Port Role Framing
9998 Text control channel The unit streams a text prelude on connect
9996 Binary settings channel One request per TCP connection, big-endian length-prefixed frames

The 9998 prelude

On connect the unit immediately sends a text prelude and, if the client does not continue with the interactive greeting the vendor app uses, may close the connection partway through. The library reads until the literal END PRELUDE: marker, then drains for a further 0.3 s, and returns whatever arrived (decoded as UTF-8 with replacement). The prelude is a sequence of sections introduced by an upper-case header ending in a colon, each holding key: value lines. The parts the library uses:

  • Label: <name>: the unit's user-assigned name. Sanitised to [A-Za-z0-9._-] for use as the archive filename root.
  • Software Release: <x.y>: the firmware version. Not used by the library itself, but this is the field to read if you want it.
  • CONTROL: and CONTROL DEFAULT:: the live and default control values as Name: raw lines, raw integers mostly in 0..10000. Only used for the annotated text dump.
  • FILE LIST:: one saved preset slot name per line. This is the list of slots the archive will read.

Section extraction is a regular expression anchored on the header and terminated by the next all-caps header or END PRELUDE:; two pitfalls already hit and fixed are recorded in _preamble.py (a $ anchor that truncated multi-line FILE LISTs, and a \s* that let an empty section swallow the next one).

The 9996 binary channel

Every request opens its own TCP connection; the unit closes it after replying.

Read (binary_request):

send  [opcode: u16 BE][len: u32 BE][payload: len bytes]
recv  [len: u32 BE][body: len bytes]

Write (upload_one):

send  [opcode: u16 BE][name_len: u32 BE][name][data_len: u32 BE][data][0x00]
recv  6 bytes of 0x00  (success ACK)

Known opcodes:

Opcode Direction Payload / name Meaning
0x0000 read preset slot name from FILE LIST read a saved preset blob
0x0003 read GPISettings or SavedSettings read a named resource
0x0100 write .../presets/<slot> write a preset slot (mirror of 0x0000)
0x0103 write .../GPISettings or .../SavedSettings write a resource (mirror of 0x0003)

Details that matter and were established by experiment:

  • The trailing 0x00 on a write frame is mandatory. Without it the unit silently declines to ACK.
  • The vendor app puts the full filesystem path of the extracted zip member into the name field (a macOS temporary directory under Ultimatte Software Control-PYREST/...). The unit appears to inspect only the last path segment, and for slot writes the presence of a presets/ segment. The library sends the same path shape for parity.
  • The ACK is six zero bytes, but the unit closes the socket so quickly after sending it that a client can see EOF after fewer than six. The library accepts any all-zero prefix as success; any non-zero byte is treated as a rejection and raised as IOError.
  • A short read of the 4-byte length header, including an immediate clean EOF, and a short body read both raise IOError. An empty resource whose header says length 0 returns b"" cleanly.
  • Blob contents are opaque. The library never parses preset or resource bytes; it moves them verbatim.

Archive layout

The zip must match the vendor app byte for byte in structure, or its Restore rejects it. Members in this order, with explicit directory entries carrying 0o40755 external attributes:

images/                       (empty directory entry)
GPISettings                   (raw bytes, no length prefix)
SavedSettings                 (raw bytes, no length prefix)
quickfiles/                   (empty directory entry)
presets/                      (explicit directory entry; required)
presets/<slot>                (one per FILE LIST entry)
<label>_config_readable.txt   (this library's addition; ignored by the vendor app)

Restore order

  1. Every presets/<slot> member, sorted by name, with opcode 0x0100.
  2. GPISettings with opcode 0x0103, if present.
  3. SavedSettings with opcode 0x0103, if present. It overwrites the live state, so it goes last.

The first failed write aborts the run; restore_unit_from_bytes returns (False, report) with report["stopped_at"] naming the member. Members that are not presets or the two resources are ignored, which is how the annotated text dump rides along harmlessly.

Timeouts and retries

Connect timeout 5 s, read timeout 30 s on writes, 2 s quiet timeout while reading the prelude. Every connect is retried once after 250 ms because a cold unit regularly refuses or times out the very first packet and answers the second.

Verified against

  • Ultimatte 12 4K, the unit the captures were taken from and the round-trip tests were run on. The docstrings also name the Ultimatte 12 (HD) as a target of the same protocol.
  • The firmware version of the bench unit was not recorded at capture time. Read it from the prelude's Software Release line on your own unit and treat any difference from the behaviour above as something to re-verify.

Unverified or guessed

  • Whether the unit reads anything in the write name field beyond the last segment and the presets/ anchor. The full vendor path is sent to be safe.
  • Whether non-zero ACK bytes carry an error code. They are treated as a generic rejection.
  • images/ and quickfiles/ are always written as empty directories. A unit that actually holds images or quick files would not have them archived by this library, and no opcode for them is known.
  • Other opcodes almost certainly exist (the gaps between 0x0000 and 0x0003, and between 0x0100 and 0x0103, are suggestive). None have been probed.
  • The interactive greeting the vendor app sends on 9998 after the prelude is not implemented; the library only ever reads the prelude.
  • read_preamble is bounded per recv but has no overall deadline or size cap; a unit that never stops sending would keep it reading.
  • Ultimatte 12 HD Mini and other models were not tested.
  • The display-range table is an interpretation of the manual and the panel UI, not wire data. It affects only the readable text dump.

License

MIT. See LICENSE.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

ultimattewire-0.1.0.dev0.tar.gz (18.9 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

ultimattewire-0.1.0.dev0-py3-none-any.whl (17.7 kB view details)

Uploaded Python 3

File details

Details for the file ultimattewire-0.1.0.dev0.tar.gz.

File metadata

  • Download URL: ultimattewire-0.1.0.dev0.tar.gz
  • Upload date:
  • Size: 18.9 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for ultimattewire-0.1.0.dev0.tar.gz
Algorithm Hash digest
SHA256 b841dcceeed1a697c4a3e735ddecfb73e7a1813bd0b8d4d7fd41f0bf170c1efc
MD5 5e580ac39ceddb1c6035fd9f05f78f0c
BLAKE2b-256 3c108d040f951fa42d13417a91e05f63895da7c07c7ac40f4602c47356d7ac3c

See more details on using hashes here.

Provenance

The following attestation bundles were made for ultimattewire-0.1.0.dev0.tar.gz:

Publisher: publish.yml on lucas-romanenko/ultimattewire

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file ultimattewire-0.1.0.dev0-py3-none-any.whl.

File metadata

File hashes

Hashes for ultimattewire-0.1.0.dev0-py3-none-any.whl
Algorithm Hash digest
SHA256 3e8657a264f663136c62cd5bee426cce9c203f294d9fd6dea61b55fbc0260877
MD5 8ad0c295bd25f7b50461e410d7ddfea0
BLAKE2b-256 aa650c15d93b1495c6f3cfe70e446bbb4289a72b6a06c68faf9a577277e79f38

See more details on using hashes here.

Provenance

The following attestation bundles were made for ultimattewire-0.1.0.dev0-py3-none-any.whl:

Publisher: publish.yml on lucas-romanenko/ultimattewire

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Release history Release notifications | RSS feed

This release

0.1.0.dev0 This release

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page