Recording Video#
Isaac Lab can record video from a Kit, Newton GL, or Newton RTX visualizer, or directly from a
scene camera sensor, by adding VideoRecorderCfg
entries to the environment config. Each recorder captures from a configurable source and writes
mp4 clips to disk independently. Streaming visualizers (Rerun and Viser) do not support local
frame capture; see Visualizer compatibility below.
from isaaclab.envs.utils.video_recorder_cfg import VideoRecorderCfg
env_cfg.video_recorders = [
VideoRecorderCfg(source="visualizer:kit", output_dir="videos/")
]
This guide is accompanied by the run_video_recording.py tutorial script in
IsaacLab/scripts/tutorials/07_visualizers. Pass --example 1, --example 2,
or --example 3 to select which recording configuration to run.
Code for run_video_recording.py
1# Copyright (c) 2022-2026, The Isaac Lab Project Developers (https://github.com/isaac-sim/IsaacLab/blob/main/CONTRIBUTORS.md).
2# All rights reserved.
3#
4# SPDX-License-Identifier: BSD-3-Clause
5
6"""Tutorial: recording video from visualizers and scene sensors.
7
8This script demonstrates three progressively richer recording configurations,
9all using the Shadow Hand cube-reorientation task
10(``Isaac-Reorient-Cube-Shadow-Camera-Direct``).
11
12Example 1 — Kit viewport (simplest)
13 One clip from the Kit interactive viewport, showing 4 parallel environments.
14
15 .. code-block:: bash
16
17 uv run python scripts/tutorials/07_visualizers/run_video_recording.py \
18 --example 1 --num_envs 4
19
20Example 2 — scene sensor only, headless
21 One clip captured directly from the scene's tiled-camera sensor.
22 No visualizer window opens; the sensor renders offline.
23
24 .. code-block:: bash
25
26 uv run python scripts/tutorials/07_visualizers/run_video_recording.py \
27 --example 2 --num_envs 16
28
29Example 3 — Kit viewport + Kit tiled grid + Newton viewport + scene sensor
30 Four independent clip streams recorded simultaneously.
31
32 .. code-block:: bash
33
34 uv run python scripts/tutorials/07_visualizers/run_video_recording.py \
35 --example 3 --num_envs 4
36
37Clips are written to ``videos/recording_tutorial/example_<N>/`` in the working directory.
38Examples 1 and 2 each demonstrate one recording source; Example 3 combines all of them.
39"""
40
41from __future__ import annotations
42
43import argparse
44import contextlib
45import os
46import sys
47
48import gymnasium as gym
49import torch
50
51import isaaclab_tasks # noqa: F401
52
53with contextlib.suppress(ImportError):
54 import isaaclab_tasks_experimental # noqa: F401
55
56from isaaclab.app import add_launcher_args, launch_simulation
57from isaaclab.envs.utils.video_recorder_cfg import VideoRecorderCfg
58
59from isaaclab_tasks.utils import resolve_task_config, setup_preset_cli
60
61# ---------------------------------------------------------------------------
62# Constants
63# ---------------------------------------------------------------------------
64
65_VIDEO_LENGTH = 100 # env steps per clip
66_NUM_STEPS = 115 # slightly more than _VIDEO_LENGTH so the clip flushes cleanly
67
68_TASK_SHADOW = "Isaac-Reorient-Cube-Shadow-Camera-Direct"
69
70# Kit viewport camera: positioned to show a 2×2 grid of Shadow Hand environments.
71# env_spacing=1.0 with 4 envs → envs centered at ±0.5 in x and y.
72# Cube spawns at ~(0, -0.39, 0.6) per env; wrist cylinder is the landmark at the top.
73_SHADOW_EYE = (0.0, -2.2, 1.8)
74_SHADOW_LOOKAT = (0.0, -0.1, 0.4)
75_SHADOW_ENV_SPACING = 1.0
76
77# Tiled camera eye offset from each robot root for generated per-env cameras.
78_SHADOW_TILED_EYE = (0.0, 0.35, 0.8)
79
80# Skip the first few steps so the RTX renderer has warmed up before recording starts.
81_KIT_STEP_OFFSET = 5
82
83
84def _output_dir(example: int) -> str:
85 return os.path.join("videos", "recording_tutorial", f"example_{example}")
86
87
88def _shadow_env_cfg(num_envs: int, env_spacing: float = _SHADOW_ENV_SPACING):
89 """Build a base Shadow Hand camera env cfg shared by all examples."""
90 env_cfg, _ = resolve_task_config(_TASK_SHADOW, "", overrides=(*sys.argv[1:], "env.tiled_camera=rgb"))
91 env_cfg.tiled_camera.height = 256
92 env_cfg.tiled_camera.width = 256
93 env_cfg.scene.num_envs = num_envs
94 env_cfg.scene.env_spacing = env_spacing
95 return env_cfg
96
97
98# ---------------------------------------------------------------------------
99# Per-example environment config builders
100# ---------------------------------------------------------------------------
101
102
103def _build_env_cfg_example_1(num_envs: int):
104 """Shadow Hand + Kit viewport: one clip from the interactive viewport."""
105 from isaaclab_visualizers.kit import KitVisualizerCfg
106
107 env_cfg = _shadow_env_cfg(num_envs)
108 env_cfg.sim.visualizer_cfgs = [KitVisualizerCfg(eye=_SHADOW_EYE, lookat=_SHADOW_LOOKAT)]
109
110 out = _output_dir(1)
111 env_cfg.video_recorders = [
112 VideoRecorderCfg(
113 source="visualizer:kit",
114 output_dir=out,
115 output_filename_prefix="kit_viewport",
116 video_length=_VIDEO_LENGTH,
117 fps=30,
118 step_offset=_KIT_STEP_OFFSET,
119 ),
120 ]
121 return env_cfg, _TASK_SHADOW
122
123
124def _build_env_cfg_example_2(num_envs: int):
125 """Shadow Hand + headless: scene tiled-camera sensor clip only."""
126 env_cfg = _shadow_env_cfg(num_envs, env_spacing=2.0)
127 env_cfg.sim.visualizer_cfgs = [] # no interactive visualizer
128
129 out = _output_dir(2)
130 env_cfg.video_recorders = [
131 VideoRecorderCfg(
132 source="sensor:tiled_camera",
133 output_dir=out,
134 output_filename_prefix="sensor",
135 video_length=_VIDEO_LENGTH,
136 fps=30,
137 ),
138 ]
139 return env_cfg, _TASK_SHADOW
140
141
142def _build_env_cfg_example_3(num_envs: int):
143 """Shadow Hand + Kit viewport + Kit tiled grid + Newton viewport + sensor: four simultaneous streams.
144
145 Note: ``source='visualizer:newton'`` captures the full Newton GL window. When
146 ``streaming_view=True`` is set on :class:`~isaaclab_visualizers.newton.NewtonGLVisualizerCfg`,
147 the GL window displays the per-environment camera panel, so this effectively records
148 a Newton streaming view without a separate ``render_tiled_rgb_array()`` call.
149 """
150 from isaaclab_visualizers.kit import KitVisualizerCfg
151 from isaaclab_visualizers.newton import NewtonGLVisualizerCfg
152
153 env_cfg = _shadow_env_cfg(num_envs)
154 kit_cfg = KitVisualizerCfg(
155 eye=_SHADOW_EYE,
156 lookat=_SHADOW_LOOKAT,
157 streaming_view=True,
158 streaming_envs=min(num_envs, 16),
159 # Reuse the existing scene camera sensor so the streaming panel shows
160 # the same RTX-rendered views as source="sensor:tiled_camera".
161 streaming_sensor_prim_path="/World/envs/env_.*/Camera",
162 )
163 newton_cfg = NewtonGLVisualizerCfg(
164 eye=_SHADOW_EYE,
165 lookat=_SHADOW_LOOKAT,
166 window_width=1280,
167 window_height=720,
168 focal_length=25.0,
169 )
170 env_cfg.sim.visualizer_cfgs = [kit_cfg, newton_cfg]
171
172 out = _output_dir(3)
173 env_cfg.video_recorders = [
174 VideoRecorderCfg(
175 source="visualizer:kit",
176 output_dir=out,
177 output_filename_prefix="kit_viewport",
178 video_length=_VIDEO_LENGTH,
179 fps=30,
180 step_offset=_KIT_STEP_OFFSET,
181 ),
182 VideoRecorderCfg(
183 source="visualizer:kit:streaming_view",
184 output_dir=out,
185 output_filename_prefix="tiled_kit_viewport",
186 video_length=_VIDEO_LENGTH,
187 fps=30,
188 step_offset=_KIT_STEP_OFFSET,
189 ),
190 VideoRecorderCfg(
191 source="visualizer:newton",
192 output_dir=out,
193 output_filename_prefix="newton_viewport",
194 video_length=_VIDEO_LENGTH,
195 fps=30,
196 ),
197 VideoRecorderCfg(
198 source="sensor:tiled_camera",
199 output_dir=out,
200 output_filename_prefix="sensor",
201 video_length=_VIDEO_LENGTH,
202 fps=30,
203 ),
204 ]
205 return env_cfg, _TASK_SHADOW
206
207
208_BUILDERS = {
209 1: _build_env_cfg_example_1,
210 2: _build_env_cfg_example_2,
211 3: _build_env_cfg_example_3,
212}
213
214# ---------------------------------------------------------------------------
215# Argument parsing
216# ---------------------------------------------------------------------------
217parser = argparse.ArgumentParser(description="Video recording tutorial for Isaac Lab environments.")
218parser.add_argument(
219 "--example", type=int, default=1, choices=[1, 2, 3], help="Which recording example to run (1, 2, or 3)."
220)
221parser.add_argument("--num_envs", type=int, default=None, help="Number of environments to simulate.")
222add_launcher_args(parser)
223args_cli, hydra_args = setup_preset_cli(parser)
224sys.argv = [sys.argv[0]] + hydra_args
225
226
227def main():
228 """Run the selected video recording example."""
229 defaults = {1: 4, 2: 16, 3: 4}
230 num_envs = args_cli.num_envs if args_cli.num_envs is not None else defaults[args_cli.example]
231 env_cfg, task = _BUILDERS[args_cli.example](num_envs)
232 env_cfg.sim.device = args_cli.device if args_cli.device is not None else env_cfg.sim.device
233
234 # Examples 1 and 3 record from the Kit viewport via omni.replicator, which requires
235 # camera rendering support. Force it here for visualizer-only recording.
236 if args_cli.example in (1, 3):
237 args_cli.enable_cameras = True
238
239 with launch_simulation(env_cfg, args_cli):
240 env = gym.make(task, cfg=env_cfg)
241
242 out = _output_dir(args_cli.example)
243 print(f"[INFO]: Running Example {args_cli.example} — clips → {out}/")
244 print(f"[INFO]: Gym observation space: {env.observation_space}")
245 print(f"[INFO]: Gym action space: {env.action_space}")
246
247 print("[INFO]: Setup complete.")
248 env.reset()
249 for _ in range(_NUM_STEPS):
250 with torch.inference_mode():
251 actions = 2 * torch.rand(env.action_space.shape, device=env.unwrapped.device) - 1
252 env.step(actions)
253
254 env.close()
255 print(f"[INFO]: Done. Clips written to {out}/")
256
257
258if __name__ == "__main__":
259 main()
Tutorial examples#
All three examples use the Shadow Hand cube-reorientation task
(Isaac-Reorient-Cube-Shadow-Camera-Direct), which ships with a built-in tiled camera
sensor. Example 1 and Example 2 each demonstrate one recording source; Example 3
combines all of them simultaneously.
Example 1: Kit viewport#
uv run python scripts/tutorials/07_visualizers/run_video_recording.py \
--example 1 --num_envs 4
Records the Kit interactive viewport (RTX renderer) showing 4 parallel environments.
One clip is written to videos/recording_tutorial/example_1/kit_viewport_0000.mp4.
Example 2: Scene sensor, headless#
uv run python scripts/tutorials/07_visualizers/run_video_recording.py \
--example 2 --num_envs 16
No visualizer window opens. Frames are read directly from the tiled_camera sensor,
writing one clip to videos/recording_tutorial/example_2/sensor_0000.mp4.
source="sensor:tiled_camera" refers to the key under which the camera is registered
in env.scene.sensors. The sensor must have "rgb" in its data_types; only the
rgb channel is currently supported for sensor sources.
Example 3: All sources simultaneously#
uv run python scripts/tutorials/07_visualizers/run_video_recording.py \
--example 3 --num_envs 4
Four independent clips are written to videos/recording_tutorial/example_3/:
kit_viewport_0000.mp4— Kit interactive viewport (RTX renderer).tiled_kit_viewport_0000.mp4— Kit tiled-camera grid (per-environment views).newton_viewport_0000.mp4— Newton GL viewer framebuffer.sensor_0000.mp4— scene tiled-camera sensor (offline render).
Each VideoRecorderCfg entry is fully independent — different sources write different
files at their own cadence. There is no limit on the number of simultaneous recorders.
Source types#
The source string selects what to capture:
Source string |
Captures from |
|---|---|
|
First active recording-capable visualizer (auto) |
|
Kit visualizer viewport |
|
Kit streaming camera panel (requires |
|
Newton GL visualizer viewport |
|
Newton OVRTX path-traced viewport |
|
Newton GL streaming camera panel (requires |
|
|
|
RGB channel |
|
Depth, turbo colormap (range: |
|
Segmentation, colorized |
|
Surface normals, colorized |
The camera angle, resolution, and other visualizer settings are configured on the corresponding visualizer config, not on the recorder.
Note
The Newton RTX viewer framebuffer can be recorded with "visualizer:newton_rtx", but
recording its streaming view is not supported.
Clip control#
Field |
Default |
Meaning |
|---|---|---|
|
|
Env steps per clip |
|
|
|
|
|
Output frame rate; |
|
|
Directory for output files (created on demand) |
|
|
File stem; output is |
|
|
Delete older clips; |
One clip at the start of a run:
VideoRecorderCfg(source="visualizer:kit", video_length=500, video_interval=0)
Recurring clips every 1 000 env steps:
VideoRecorderCfg(source="visualizer:kit", video_length=200, video_interval=1000)
Keep only the most recent clip on disk:
VideoRecorderCfg(source="visualizer:kit", video_length=200, video_interval=1000,
keep_last_n_clips=1)
Recording from an independent camera angle#
Configure the recording angle on the visualizer rather than on the recorder. To open a headless Newton visualizer at a different angle alongside an interactive Kit viewer:
from isaaclab_visualizers.kit import KitVisualizerCfg
from isaaclab_visualizers.newton import NewtonGLVisualizerCfg
env_cfg.sim.visualizer_cfgs = [
KitVisualizerCfg(eye=(4.0, 4.0, 2.0)),
NewtonGLVisualizerCfg(eye=(12.0, 0.0, 6.0), headless=True),
]
env_cfg.video_recorders = [
VideoRecorderCfg(source="visualizer:newton", output_dir="videos/"),
]
Alternatively, use a CameraCfg sensor in the scene and record
with source="sensor:<name>", which gives full control over the recording viewpoint
without requiring a second interactive visualizer.
Requirements#
Install the
videoextra to provide moviepy 1.x and itsffmpegruntime. In a uv checkout, add--extra videoto the command.For
source="visualizer:kit"or"visualizer:kit:streaming_view": the Kit app is launched automatically byAppLauncher. In headless mode (--headless), you must also pass--enable_cameras(or setENABLE_CAMERAS=1) to activate the Replicator offscreen render pipeline; without it, captured frames are black. The--videoflag sets--enable_camerasautomatically when no explicit recorder source is configured.For
source="visualizer:newton"or"visualizer:newton_gl"/"visualizer:newton:streaming_view": an activeNewtonGLVisualizerCfgmust be inenv_cfg.sim.visualizer_cfgs. Newton GL uses pyglet’s EGL backend and works headlessly without--enable_cameras.For
source="visualizer:newton_rtx": the OVRTX runtime and an activeNewtonRTXVisualizerCfgare required. Capturing the path-traced LDR framebuffer performs a GPU-to-CPU readback.For
source="sensor:<name>": the named field must exist on the scene config and have"rgb"in itsdata_types.
Visualizer compatibility#
kit, newton_gl, and newton_rtx support frame capture and can run headless.
Visualizer |
|
Notes |
|---|---|---|
|
✓ |
Kit/Omniverse viewport; supports headless mode |
|
✓ |
Newton OpenGL viewport; supports headless mode |
|
✓ |
Newton OVRTX path-traced viewport; native-resolution LDR readback |
|
✗ |
Remote streaming tool; no local frame-capture API |
|
✗ |
Browser streaming tool; no local frame-capture API |
Passing --video alongside --viz rerun or --viz viser raises an error when no other
recording-capable visualizer is configured.
To run a streaming visualizer and record video simultaneously, add a headless capture backend
alongside it in sim.visualizer_cfgs:
from isaaclab_visualizers.kit import KitVisualizerCfg
from isaaclab_visualizers.rerun import RerunVisualizerCfg
env_cfg.sim.visualizer_cfgs = [
RerunVisualizerCfg(...), # streaming — for monitoring
KitVisualizerCfg(headless=True), # headless — provides frames for --video
]
Alternatively, record directly from a scene camera sensor without any visualizer:
VideoRecorderCfg(source="sensor:<name>") # add to env_cfg.video_recorders
Limitations#
source="visualizer:kit"andsource="visualizer:kit:streaming_view"require cubric to propagate Newton Fabric scene transforms to the RTX renderer. Without cubric, a warning is logged and a black-frame warning is emitted at clip write time. Usesource="visualizer:newton"for guaranteed capture with Newton physics.source="visualizer:newton:streaming_view"andsource="visualizer:kit:streaming_view"requirestreaming_view=Trueon the corresponding visualizer cfg. ARuntimeErroris raised at the first capture attempt if it is not set.
See also#
Visualization — configuring interactive visualizers
Using the Visualizer Streaming Camera View — tiled camera panel setup
Capturing sensor frames during training — saving per-frame sensor outputs as images



