Skip to content

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_folder convention used by experiments and evaluation notebooks;
  • the trigger cadence for expensive environments;
  • the name_prefix fields that encode algorithm, seed, and checkpoint names;
  • the agent_frame_fn used 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.