# VideoStream

## class nominal.experimental.video.VideoStream(rid, src, options)

Bases: [`object`](https://docs.python.org/3/builtins/functions.html#object "(in Python v3.15)")

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

```python
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()

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, src, options=None, *, channel=None, tags=None)

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** ([*Dataset*](https://dev.nominal.io/python/ref/generated/nominal.core.Dataset.md#nominal.core.Dataset "nominal.core.Dataset") *|* [*Video*](https://dev.nominal.io/python/ref/generated/nominal.core.Video.md#nominal.core.Video "nominal.core.Video"))

      The dataset owning the video channel to stream to. Passing a legacy standalone
      `Video` is deprecated.
    - **src** ([*Src*](https://dev.nominal.io/python/ref/generated/nominal.experimental.video.Src.md#nominal.experimental.video.Src "nominal.experimental.video.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** ([*StreamOptions*](https://dev.nominal.io/python/ref/generated/nominal.experimental.video.StreamOptions.md#nominal.experimental.video.StreamOptions "nominal.experimental.video.StreamOptions") *|* *None*)

      Encoding options — codec, bitrate, resolution, overlay, fps, etc.
      Defaults to H264 at 4 Mbps with no overlay.
    - **channel** ([*str*](https://docs.python.org/3/builtins/stdtypes.html#str "(in Python v3.15)") *|* *None*)

      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** (*Mapping**[*[*str*](https://docs.python.org/3/builtins/stdtypes.html#str "(in Python v3.15)")*,* [*str*](https://docs.python.org/3/builtins/stdtypes.html#str "(in Python v3.15)")*]* *|* *None*)

      Tags identifying the channel’s series. Defaults to none.

Returns:
:   A configured VideoStream, ready to open.

Raises:
:   - [**ValueError**](https://docs.python.org/3/builtins/exceptions.html#ValueError "(in Python v3.15)") – `channel` was omitted for a dataset target, or supplied for a `Video` target.
    - [**NominalVideoStreamError**](https://dev.nominal.io/python/ref/generated/nominal.exceptions.NominalVideoStreamError.md#nominal.exceptions.NominalVideoStreamError "nominal.exceptions.NominalVideoStreamError") – Nominal rejected the request for a WHIP endpoint.

Return type:
:   [*VideoStream*](https://dev.nominal.io/python/ref/generated/nominal.experimental.video.VideoStream.md#nominal.experimental.video.VideoStream "nominal.experimental.video.VideoStream")

### open()

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

Raises:
:   [**NominalVideoStreamError**](https://dev.nominal.io/python/ref/generated/nominal.exceptions.NominalVideoStreamError.md#nominal.exceptions.NominalVideoStreamError "nominal.exceptions.NominalVideoStreamError") – if the pipeline fails to start (e.g. device not found, bad source URL).

### restart()

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=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** ([*timedelta*](https://docs.python.org/3/library/datetime.html#datetime.timedelta "(in Python v3.15)") *|* [*float*](https://docs.python.org/3/builtins/functions.html#float "(in Python v3.15)") *|* *None*)

    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:
:   - [**NominalVideoStreamNotOpenError**](https://dev.nominal.io/python/ref/generated/nominal.exceptions.NominalVideoStreamNotOpenError.md#nominal.exceptions.NominalVideoStreamNotOpenError "nominal.exceptions.NominalVideoStreamNotOpenError") – if the stream is not open — call open() first or use as a context manager.
    - [**NominalVideoStreamError**](https://dev.nominal.io/python/ref/generated/nominal.exceptions.NominalVideoStreamError.md#nominal.exceptions.NominalVideoStreamError "nominal.exceptions.NominalVideoStreamError") – if the pipeline encounters an unrecoverable error.

### send_frame(data, timestamp_ns=None)

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

Parameters:
:   - **data** ([*bytes*](https://docs.python.org/3/builtins/stdtypes.html#bytes "(in Python v3.15)"))

      Raw frame bytes. Format must match the format passed to `Src.app()`
      (default is RGB — width * height * 3 bytes).
    - **timestamp_ns** ([*int*](https://docs.python.org/3/builtins/functions.html#int "(in Python v3.15)") *|* *None*)

      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**](https://dev.nominal.io/python/ref/generated/nominal.exceptions.NominalVideoStreamNotOpenError.md#nominal.exceptions.NominalVideoStreamNotOpenError "nominal.exceptions.NominalVideoStreamNotOpenError") – if the stream is not open.

Return type:
:   [*bool*](https://docs.python.org/3/builtins/functions.html#bool "(in Python v3.15)")
