VideoStream#

class nominal.experimental.video.VideoStream(rid: str, src: Src, options: StreamOptions | None)#

Bases: object

A live video stream from any source to a video channel on a Nominal dataset via WHIP.

Use VideoStream.create() to construct — it resolves the WHIP endpoint from the Nominal video and prepares the pipeline configuration. The pipeline itself is not started until open() is called (or the context manager is entered).

Requires pip install 'nominal[video]' and GStreamer 1.20+ on your system.

Example

from nominal.experimental.video import VideoStream, Src, StreamOptions

dataset = client.create_dataset("my stream")

# Context manager — open/close handled automatically:
with VideoStream.create(dataset, Src.camera(), channel="camera/front") as stream:
    stream.run()

# Timed stream — run for a fixed timeout then exit:
with VideoStream.create(video, Src.udp_rtp(5000)) as stream:
    stream.run(timedelta(seconds=30))

# Manual lifecycle — useful when you need the stream object outside a with block,
# or to restart after a NominalVideoStreamError (e.g. source disconnected):
stream = VideoStream.create(video, Src.rtsp("rtsp://192.168.1.10/live"))
stream.open()
try:
    stream.run()
except NominalVideoStreamError:
    stream.restart()  # re-opens the pipeline with the same WHIP endpoint
    stream.run()
finally:
    stream.close()

# Push frames manually from your own source.
# frame_bytes must be raw RGB bytes: width * height * 3 bytes per frame.
# Use Src.app(width, height, format=ImageFormat.Bgr) if your source is BGR (e.g. OpenCV).
with VideoStream.create(video, Src.app(1280, 720)) as stream:
    while capturing:
        frame_bytes: bytes = capture_rgb_frame()  # 1280 * 720 * 3 bytes
        stream.send_frame(frame_bytes, timestamp_ns=time.time_ns())
rid: str#
src: Src#
options: StreamOptions | None#
close() → None#

Stop the pipeline and release all resources. Idempotent — safe to call multiple times.

After close(), open() can be called again to restart with the same WHIP endpoint.

classmethod create(target: Dataset | Video, src: Src, options: StreamOptions | None = None, *, channel: str | None = None, tags: Mapping[str, str] | None = None) → VideoStream#

Create a VideoStream for a video channel on a dataset.

Resolves the WHIP endpoint from Nominal and configures the pipeline. The pipeline is not started until open() is called.

Parameters:
  • target – The dataset owning the video channel to stream to. Passing a legacy standalone Video is deprecated.

  • src – Video source. Common options:

    • Src.camera() — local webcam

    • Src.rtsp("rtsp://...") — RTSP stream

    • Src.udp_rtp(port) — incoming RTP over UDP

    • Src.udp_mpegts(port) — incoming MPEG-TS over UDP

    • Src.file("path/to/video.mp4") — video file

    • Src.app(width, height) — push frames manually via send_frame()

  • options – Encoding options — codec, bitrate, resolution, overlay, fps, etc. Defaults to H264 at 4 Mbps with no overlay.

  • channel – Name of the video channel on the dataset to stream to. Required when streaming to a dataset; unused when streaming to a legacy Video.

  • tags – Tags identifying the channel’s series. Defaults to none.

Returns:

A configured VideoStream, ready to open.

Raises:
  • ValueError – channel was omitted for a dataset target, or supplied for a Video target.

  • NominalVideoStreamError – Nominal rejected the request for a WHIP endpoint.

open() → None#

Build and start the GStreamer pipeline. Idempotent — safe to call multiple times.

Raises:

NominalVideoStreamError – if the pipeline fails to start (e.g. device not found, bad source URL).

restart() → None#

Stop and restart the pipeline.

Useful for recovering from errors or reconnecting after a source interruption. Reuses the same WHIP endpoint resolved at create() time.

run(timeout: timedelta | float | None = None) → None#

Block until the stream ends, errors, or Ctrl+C is pressed.

Calls close() internally when done, so no explicit cleanup is needed after run().

Parameters:

timeout – How long to stream before stopping — either a timedelta or a number of seconds. If None, runs until the source ends naturally (e.g. end of file) or until interrupted with Ctrl+C.

Raises:
send_frame(data: bytes, timestamp_ns: int | None = None) → bool#

Push a raw video frame into the pipeline. Only valid when using Src.app().

Parameters:
  • data – Raw frame bytes. Format must match the format passed to Src.app() (default is RGB — width * height * 3 bytes).

  • timestamp_ns – Absolute timestamp in nanoseconds (Unix epoch). If None, the pipeline assigns a timestamp automatically.

Returns:

True if the frame was accepted, False if the internal buffer is full.

Raises:

NominalVideoStreamNotOpenError – if the stream is not open.