factoriax.analysis.video#

Build video frames from env states and encode them to MP4.

A frame is the map render with the inventory panel of the selected player beside it. The PPO eval pipeline and the scripted-agent scenarios both use these frames.

Two encoders are available. write_video() holds every frame in memory at once and is the simpler call. write_video_streaming() holds one frame at a time and is the one to use for a long episode.

The module reads EnvState and nothing else from the training side. Keep it that way, so a scenario runner can import it without pulling in a training loop.

factoriax.analysis.video.compose_frame_with_inventory(state: EnvState, *, block_pixel_size: int = 16, inv_panel_width: int = 192) → ndarray#

Return map_render | inventory_panel concatenated horizontally.

The panel is the same one the agent debugger shows in its top right. It is drawn at the height of the map render, so the two join with no gap.

The panel shows the inventory of state.selected_player only. In a game with several players, the other inventories do not appear.

Parameters:
  • state – The state to draw.

  • block_pixel_size – The width of one map tile in pixels. This sets the size of the map render, and therefore of the whole frame.

  • inv_panel_width – The width of the inventory panel in pixels. A panel under 22 pixels tall holds no rows, but the height comes from the map render and is far above that in practice.

Returns:

RGB uint8 of shape (map_h, map_w + inv_panel_width, 3).

Return type:

numpy.ndarray

factoriax.analysis.video.write_video(path: Any, frames: list[ndarray], fps: int) → None#

Encode frames to an MP4 at path using imageio / FFMPEG.

The function stacks every frame into one array before it encodes. Peak memory therefore holds the whole episode twice over. Use write_video_streaming() when that total passes a few hundred megabytes.

Parameters:
  • path – Where to write the .mp4. Missing parent directories are created.

  • frames – RGB uint8 frames. Every frame must have the same shape, and both side lengths must be even for the yuv420p format.

  • fps – Frames per second in the output.

Raises:

ImportError – When imageio[ffmpeg] is absent. The import is inside the function, so a caller that never writes a video does not need the dependency.

factoriax.analysis.video.write_video_streaming(path: Any, frames: Iterable[ndarray], fps: int) → int#

Stream-encode frames to an MP4 one frame at a time.

Memory holds one frame at a time. A long scripted episode can run to 6000 frames of about 700 by 512 pixels. Buffered, that is near 6 GB of raw RGB, so streaming is the only workable choice.

Parameters:
  • path – Where to write the .mp4. Missing parent directories are created.

  • frames – RGB uint8 frames, read one at a time. A generator that renders each frame on demand is the intended argument, because it keeps the whole episode out of memory.

  • fps – Frames per second in the output.

Returns:

How many frames were written.

Return type:

int

Raises:

ImportError – When imageio[ffmpeg] is absent.

Notes

The writer is closed even when a frame raises, so a partial file is left behind and not a locked one.