> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.nvidia.com/holoscan/sdk-user-guide/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.nvidia.com/holoscan/sdk-user-guide/_mcp/server.

So far we have been working with simple operators to demonstrate Holoscan SDK concepts.
In this example, we look at two built-in Holoscan operators that have many practical
applications.

In this example we'll cover:

* How to load a video file from disk using **VideoStreamReplayerOp** operator.
* How to display video using **HolovizOp** operator.
* How to configure your operator's parameters using a YAML configuration file.

The example source code and run instructions can be found in the [examples](https://github.com/nvidia-holoscan/holoscan-sdk/blob/main/examples#holoscan-sdk-examples) directory on GitHub, or under `/opt/nvidia/holoscan/examples` in the NGC container and the Debian package, alongside their executables.

## Operators and Workflow

Here is the diagram of the operators and workflow used in this example.

![Workflow to load and display video from a file](https://kroki.io/graphviz/svg/eNpVjUEKwjAQRfc5xZCVLuwBlLh2IQhV3JQg0Qw2GDNh2gbUencjStDV8Jn3_rfuzCa2kJxFOjBGb27I8BBswsU6VnJdSxHIIjRdayIqxhOx1QshCt14c0Sv5P5dsu0ZzbX-_jZxhJGGPg79JJ8pzGGHoSOWuSG5e3FX5CnnzDcu6DyCLiF3hYcR5N_obAm__mejqqqiSi2e4gWMdU-A)

We connect the "output" port of the replayer operator to the "receivers" port of the Holoviz
operator.

## Video Stream Replayer Operator

The built-in video stream replayer operator can be used to replay a video stream that has been encoded as gxf entities. You can use the `convert_video_to_gxf_entities.py` script (installed in `/opt/nvidia/holoscan/bin` or available [on GitHub](https://github.com/nvidia-holoscan/holoscan-sdk/tree/main/scripts#convert_video_to_gxf_entitiespy)) to encode a video file as gxf entities for use by this operator.

This operator processes the encoded file sequentially and supports realtime, faster than realtime, or slower than realtime playback of prerecorded data. The input data can optionally be repeated to loop forever or only for a specified count. For more details, see `holoscan::ops::VideoStreamReplayerOp`.

We will use the replayer to read GXF entities from disk and send the frames downstream to the Holoviz operator.

## Holoviz Operator

The built-in Holoviz operator provides the functionality to composite realtime streams of frames with multiple different other layers like segmentation mask layers, geometry layers and GUI layers.

We will use Holoviz to display frames that have been sent by the replayer operator to its "receivers" port which can receive any number of inputs.  In more intricate workflows, this port can receive multiple streams of input data where, for example, one stream is the original video data, while other streams detect objects in the video to create bounding boxes and/or text overlays.

## Application Configuration File (YAML)

The SDK supports reading an optional YAML configuration file and can be used to customize the application's workflow and operators.  For more complex workflows, it may be helpful to use the application configuration file to help separate operator parameter settings from your code.  See [Configuring an Application](/holoscan/sdk-user-guide/using-the-sdk/create-an-application#configuring-an-application) for additional details.

This example uses the following configuration file to configure the parameters for the replayer and Holoviz operators.  The full list of parameters can
be found at `holoscan::ops::VideoStreamReplayerOp` and `holoscan::ops::HolovizOp`.

```text
%YAML 1.2
replayer:
  directory: "../data/racerx"   # Path to gxf entity video data
  basename: "racerx"             # Look for <basename>.gxf_{entities|index}
  frame_rate: 0       # Frame rate to replay.  (default: 0 follow frame rate in timestamps)
  repeat: true        # Loop video?   (default: false)
  realtime: true      # Play in realtime, based on frame_rate/timestamps   (default: true)
  count: 0            # Number of frames to read  (default: 0 for no frame count restriction)
 
holoviz:
  width: 854          # width of window size
  height: 480         # height of window size
  tensors:
    - name: ""        # name of tensor containing input data to display
      type: color     # input type e.g., color, triangles, text, depth_map
      opacity: 1.0    # layer opacity
      priority: 0     # determines render order, higher priority layers are rendered on top
```

The code below shows our `video_replayer` example.  Operator parameters are configured from a configuration file
using `from_config()` (C++) and `self.**kwargs()` (Python).

```cpp
  #include &lt;holoscan/holoscan.hpp&gt;
  #include &lt;holoscan/operators/video_stream_replayer/video_stream_replayer.hpp&gt;
  #include &lt;holoscan/operators/holoviz/holoviz.hpp&gt;
   
  class VideoReplayerApp : public holoscan::Application {
   public:
    void on_window_closed() {
      // Stop the entire application when the Holoviz window is closed
      stop_execution();
    }
   
    void compose() override {
      using namespace holoscan;
   
      // Define the replayer and holoviz operators and configure using yaml configuration
      auto replayer = make_operator&lt;ops::VideoStreamReplayerOp&gt;("replayer", from_config("replayer"));
      auto visualizer = make_operator&lt;ops::HolovizOp&gt;(
          "holoviz", from_config("holoviz"), Arg("window_close_callback", [this]() {
            on_window_closed();
          }));
   
      // Define the workflow: replayer -> holoviz
      add_flow(replayer, visualizer, {{"output", "receivers"}});
    }
  };
   
  int main(int argc, char** argv) {
    // Get the yaml configuration file
    auto config_path = std::filesystem::canonical(argv[0]).parent_path();
    config_path /= std::filesystem::path("video_replayer.yaml");
    if ( argc >= 2 ) {
      config_path = argv[1];
    }
   
    auto app = holoscan::make_application<VideoReplayerApp>();
    app->config(config_path);
    app->run();
   
    return 0;
  }
```

* The built-in **VideoStreamReplayerOp** and **HolovizOp** operators are included from lines 2 and 3, respectively.
* We create an instance of **VideoStreamReplayerOp** named "replayer" with parameters initialized from the YAML configuration file using the call to `from_config()` (line 16).
* We create an instance of **HolovizOp** named "holoviz" with parameters initialized from the YAML configuration file using the call to `from_config()`, and pass a `window_close_callback` that calls `stop_execution()` so the application terminates cleanly when the Holoviz window is closed (lines 17-20).
* The "output" port of "replayer" operator is connected to the "receivers" port of the "holoviz" operator and defines the application workflow (line 23).
* The application's YAML configuration file contains the parameters for our operators, and is loaded before `app->run()` (line 36). If no argument is passed to the executable, the application looks for a file with the name "video\_replayer.yaml" in the same directory as the executable; otherwise it treats the argument as the path to the application's YAML configuration file (lines 30–34).

```python
  import os
   
  from holoscan.core import Application
  from holoscan.operators import HolovizOp, VideoStreamReplayerOp
   
  sample_data_path = os.environ.get("HOLOSCAN_INPUT_PATH", "../data")
   
   
  class VideoReplayerApp(Application):
      """Example of an application that uses the operators defined above.
   
      This application has the following operators:
   
      - VideoStreamReplayerOp
      - HolovizOp
   
      The VideoStreamReplayerOp reads a video file and sends the frames to the HolovizOp.
      The HolovizOp displays the frames.
      """
   
      def on_window_closed(self):
          # Stop the entire application when the Holoviz window is closed
          self.stop_execution()
   
      def compose(self):
          video_dir = os.path.join(sample_data_path, "racerx")
          if not os.path.exists(video_dir):
              raise ValueError(f"Could not find video data: {video_dir=}")
   
          # Define the replayer and holoviz operators
          replayer = VideoStreamReplayerOp(
              self, name="replayer", directory=video_dir, **self.kwargs("replayer")
          )
          visualizer = HolovizOp(
              self,
              name="holoviz",
              window_close_callback=self.on_window_closed,
              **self.kwargs("holoviz"),
          )
   
          # Define the workflow
          self.add_flow(replayer, visualizer, {("output", "receivers")})
   
   
  def main(config_file):
      app = VideoReplayerApp()
      # if the --config command line argument was provided, it will override this config_file
      app.config(config_file)
      app.run()
   
   
  if __name__ == "__main__":
      config_file = os.path.join(os.path.dirname(__file__), "video_replayer.yaml")
      main(config_file=config_file)
```

* The built-in **VideoStreamReplayerOp** and **HolovizOp** operators are imported on line 4.
* We define an `on_window_closed()` method that calls `self.stop_execution()` to cleanly terminate the application when the Holoviz window is closed (lines 21–23).
* We create an instance of **VideoStreamReplayerOp** named "replayer" with parameters initialized from the YAML configuration file using `**self.kwargs()` (lines 31–33).
* For the Python script, the path to the GXF entity video data is not set in the application configuration file, but determined by the code on lines 6 and 26 and is passed directly as the `directory` argument (line 32). This allows more flexibility for the user to run the script from any directory by setting the `HOLOSCAN_INPUT_PATH` environment variable (line 6).
* We create an instance of **HolovizOp** named "holoviz" with parameters initialized from the YAML configuration file using `**self.kwargs()`, and pass `window_close_callback=self.on_window_closed` so the application terminates cleanly when the Holoviz window is closed (lines 34–39).
* The "output" port of "replayer" operator is connected to the "receivers" port of the "holoviz" operator and defines the application workflow (line 42).
* The application's YAML configuration file contains the parameters for our operators, and is loaded on line 48. If no argument is passed to the Python script, the application looks for a file with the name "video\_replayer.yaml" in the same directory as the script (line 53). Otherwise it treats the argument as the path to the app's YAML configuration file.

## Running the Application

Running the application should bring up video playback of the video referenced in the YAML file.

![](https://files.buildwithfern.com/nvidia-holoscan.docs.buildwithfern.com/holoscan/sdk-user-guide/c6ad083203e0a510d84f9b625a9c198f5a9accfdc3a29c3d149ff08a90ba046c/docs/images/video_replayer.png)