pyst2110
SMPTE ST 2110 protocol for Python: RTP and RFC 4175 headers read and written, frame boundaries, sequence-loss accounting, pgroup geometry, and SDP.
REQUIREMENTS.md — the problem. SPEC.md — the design and its rationale. ROADMAP.md — what is not built yet.
| Layer | Owns |
|---|---|
| A transport binding (a NIC binding, a socket, a capture file) | Moving bytes |
| pyst2110 | RTP + RFC 4175 headers, geometry, SDP, ST 2110-21 timing |
| The consuming runtime | Pixels — packing, unpacking, colour |
The layer between a transport and a raster: it says what packet headers mean and where their payloads belong, and never moves a pixel itself. numpy is the only runtime dependency — no vendor SDK, no transport binding, no NIC and no licence, so it runs anywhere CI does.
Install
A git dependency pinned by tag. Publication to PyPI waits for a consumer depending on a released version (§road:pypi):
uv add "pyst2110 @ git+https://github.com/Fuse-Technical-Group/pyst2110@v0.2.0"
Usage
from pyst2110 import parse_sdp, parse_video_format
offer = open("flow.sdp").read()
flow = parse_sdp(offer) # destination address, port, source filter
video = parse_video_format(offer) # width, height, rate, sampling, depth
Parsing is vectorized over whole chunks: every function takes a
two-dimensional uint8 array, one row per packet, and returns one array
per field — the shape a header-data-split receiver already hands out.
Header fields are reported as the wire declared them, this being a parse
and not a filter. A packet is free to name a row outside the image, so
fits_raster masks the descriptors that name a place inside the flow's
raster and a consumer places only those:
fits = fits_raster(video, payload.line, payload.offset_samples)
starts = raster_offset(video, payload.line[fits], payload.offset_samples[fits])
Sending is the same shape in reverse. A frame's headers are built once for a format and payload size, then stamped per frame with the only two fields that move — the sequence numbers and the media timestamp:
from pyst2110 import FrameHeaders, choose_payload_size, format_sdp, max_payload_size
payload_size = choose_payload_size(video, max_payload_size(video))
frame = FrameHeaders(video, payload_size, ssrc=0x1234ABCD)
for index in range(frames):
headers = frame.stamp(index) # (packets, 20) uint8, one row per packet
... # send each header with frame.frame_offset_octets
offer = format_sdp(flow, video, session_name="my sender") # what was sent
The offer carries the media type parameters ST 2110-20 section 7.2 and
ST 2110-21 section 8.1 require of a sender. TP describes the pacing,
which is the transport's and not this library's, so sender_type= is
the caller's to set — it defaults to 2110TPN. A multicast flow names
its sender or passes any_source=True; see §spec:sdp for why that is a
choice rather than a default.
An ST 2022-7 redundant pair is one offer and not two. format_dup_sdp
writes RFC 7104's grouping — a session-level a=group:DUP over two
m=video blocks, each with its own address and sender — and
parse_dup_sdp reads both legs back, in the order the group names them:
from pyst2110 import format_dup_sdp, parse_dup_sdp
offer = format_dup_sdp(red, blue, video, session_name="my sender")
first, second = parse_dup_sdp(offer)
A document whose DUP tags and media blocks disagree is refused rather
than read as a single-leg offer, and parse_sdp refuses a grouped offer
for the same reason: a sender that emitted one leg where two were meant
sends unprotected essence and reports success.
session_name defaults to the single space RFC 4566 section 5.3 prescribes
for a session with no meaningful name — and at least one transmit SDK refuses
it, so name the session where a sender will read the offer back. NVIDIA
Rivermax reports 'x=<token>' format not found and fails stream creation,
accepting the same document once it is named.
frame_offset_octets says which octets of the frame buffer each packet
carries. Moving them is the consumer's, as on the receive side.
stamp hands back the same array every time, restamped in place — that
is what keeps the loop above from allocating a frame of headers per
frame. So headers is only valid until the next stamp: a caller
queueing two frames at once copies the first.
API
Everything is re-exported from the top-level package, and the
docstrings there are the authority; help(pyst2110) is the index.
Both paths are built: SDP parsing and emit — the single-leg offer and the ST 2022-7 pair — the RFC 3550 header parse, format geometry, sequence and frame tracking, RFC 4175 payload descriptors, the transmit header block, the ST 2022-7 reconstruction of one flow from two legs, and the ST 2110-21 timing model — read schedules, sender limits, and the two leaky buckets that judge a capture's emission instants. What is not built is listed in ROADMAP.md.
Development
uv sync
bash tools/ci.sh
tools/ci.sh is the gate CI runs — ruff, mypy, pytest.
License
MIT — see LICENSE. The standards this implements are public and an implementation of them should be too.
Release files for pyst2110 0.9.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| pyst2110-0.9.1.tar.gz | 150.1 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| pyst2110-0.9.1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 214.4 kB
Release files / pyst2110-0.9.1.tar.gz
| Download URL | pyst2110-0.9.1.tar.gz |
|---|---|
| Size | 150.1 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
cfa035006c044a12efc94598eed78755251aacea305a48d3bffad50eaf6bd309
|
|
BLAKE2b-256 checksum How to use checksums |
827c790b808b41d6f94b2846fa50c3c833cb590880f080d3700a827f7ce36974
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 18, 2026.
Transparency logRelease files / pyst2110-0.9.1-py3-none-any.whl
| Download URL | pyst2110-0.9.1-py3-none-any.whl |
|---|---|
| Size | 64.3 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
f9eef2461208dcd388ff46d909809d015369122be53aa81cb271436f71b900e1
|
|
BLAKE2b-256 checksum How to use checksums |
f880996f61a95deacb9ce4c90bfdd00eb401e99b34358f546d8f41540eee75b9
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 18, 2026.
Transparency log