Record Video
rle.reusable.record_video provides small MP4 recording wrappers for
Gymnasium, PettingZoo Parallel, and PettingZoo AEC environments. It is meant for
research repos that need "record every Nth episode" without adopting the larger
Gymnasium RecordVideo surface.
The wrapper-specific Gymnasium and PettingZoo imports are lazy enough that
unrelated rle.reusable utilities can still run in lightweight notebooks that
do not install those environment packages.
API
The package root re-exports the public API:
from rle.reusable import RecordVideo
Use RecordVideo(...) as a factory, or import a concrete wrapper when the env
type is already known:
from rle.reusable.record_video import (
AECRecordVideo,
GymRecordVideo,
ParallelRecordVideo,
RecordVideo,
)
The constructor accepts:
| option | purpose |
|---|---|
video_folder |
directory for .mp4 files |
trigger |
Callable[[int], bool] evaluated on episode or step counters |
trigger_type |
"episode" or "step" |
name_prefix |
filename prefix, default "rl-video" |
fps |
frames per second, defaulting to env.metadata["render_fps"] or 30 |
record_agents |
PettingZoo only: "auto", "always", or "never" |
agent_frame_fn |
PettingZoo only: converts (agent, observation, env) to a frame |
disable_logger |
suppress MoviePy progress logs by default |
Counters are zero-based. This records episodes 0, 5, 10, ...:
env = RecordVideo(
env,
video_folder="exports/videos",
trigger=lambda episode: episode % 5 == 0,
trigger_type="episode",
)
Skip the first episode explicitly when desired:
env = RecordVideo(
env,
video_folder="exports/videos",
trigger=lambda episode: episode > 0 and episode % 5 == 0,
)
Episode Triggers
Episode triggers are evaluated at reset(...). A triggered clip includes the
initial reset frame and every rendered frame through the terminal frame.
Shared videos are named:
<name_prefix>-episode-<episode_id>.mp4
Step Triggers
Step triggers are evaluated after wrapper step(...) calls. A step trigger does
not start recording the current partial episode. Instead, it marks the next
reset so the next complete episode is recorded from its first frame.
If several step triggers fire before the next reset, only the first triggering step is used for the next video name:
<name_prefix>-step-<step_id>-episode-<episode_id>.mp4
For AEC environments, step_id counts AEC API step(...) calls.
PettingZoo Agents
PettingZoo wrappers record one shared video through env.render() unless
per-agent clips are needed.
With record_agents="auto", observations are compared at episode start:
- equal/shared observations produce one shared video;
- differing image-like observations produce one video per agent;
- differing non-image observations require
agent_frame_fn.
Per-agent videos append the safe agent id:
<name_prefix>-episode-<episode_id>-<agent_id>.mp4
Set record_agents="never" to force shared render videos. Set
record_agents="always" when a partially observable environment should always
write one clip per agent.
Frames
Gymnasium and shared PettingZoo recordings call env.render(), so environments
must have an image-returning render mode such as rgb_array.
The recorder accepts RGB, RGBA, grayscale, and float [0, 1] arrays, then writes
normalized uint8 RGB frames with MoviePy.
Adapting To Your Project
For a new repo, copy src/rle/reusable/record_video and re-export
RecordVideo from the project package root if notebooks should use the short
import path. Add moviepy to the project dependencies, plus whichever
environment API the project records from, such as gymnasium or
pettingzoo.
The main project-level choice is where videos should be written. Keep video paths aligned with the experiment export layout so clips can be inspected next to the run records that produced them:
env = RecordVideo(
env,
video_folder=f"exports/{group}/{algorithm_slug}/videos",
trigger=lambda episode: episode % 10 == 0,
name_prefix=f"{algorithm_slug}-seed-{seed}",
)
Projects with custom image observations should decide whether env.render()
is the canonical shared view or whether per-agent videos are more useful. For
PettingZoo environments that expose non-image observations, provide an
agent_frame_fn in the project layer rather than teaching the reusable wrapper
about environment-specific observation formats.
The blocks most likely to change are:
- the
video_folderconvention used by experiments and evaluation notebooks; - the
triggercadence for expensive environments; - the
name_prefixfields that encode algorithm, seed, and checkpoint names; - the
agent_frame_fnused to convert project observations into RGB frames.
Keep rendering details in the environment or in a small project adapter. The recording wrapper should stay focused on trigger handling, frame normalization, and MP4 writing so it remains easy to vendor into the next repo.