code: def save_video(
      frames: list,
      video_folder: str,
      episode_trigger: Callable[[int], bool] = None,
      step_trigger: Callable[[int], bool] = None,
      video_length: Optional[int] = None,
      name_prefix: str = "rl-video",
      episode_index: int = 0,
      step_starting_index: int = 0,
      **kwargs,
  ):
      """Save videos from rendering frames.
  
      This function extract video from a list of render frame episodes.
  
      Args:
          frames (List[RenderFrame]): A list of frames to compose the video.
          video_folder (str): The folder where the recordings will be stored
          episode_trigger: Function that accepts an integer and returns ``True`` iff a recording should be started at this episode
          step_trigger: Function that accepts an integer and returns ``True`` iff a recording should be started at this step
          video_length (int): The length of recorded episodes. If it isn't specified, the entire episode is recorded.
              Otherwise, snippets of the specified length are captured.
          name_prefix (str): Will be prepended to the filename of the recordings.
          episode_index (int): The index of the current episode.
          step_starting_index (int): The step index of the first frame.
          **kwargs: The kwargs that will be passed to moviepy's ImageSequenceClip.
              You need to specify either fps or duration.
  
      Example:
          >>> import gym
          >>> from gym.utils.save_video import save_video
          >>> env = gym.make("FrozenLake-v1", render_mode="rgb_array_list")
          >>> env.reset()
          >>> step_starting_index = 0
          >>> episode_index = 0
          >>> for step_index in range(199):
          ...    action = env.action_space.sample()
          ...    _, _, done, _ = env.step(action)
          ...    if done:
          ...       save_video(
          ...          env.render(),
          ...          "videos",
          ...          fps=env.metadata["render_fps"],
          ...          step_starting_index=step_starting_index,
          ...          episode_index=episode_index
          ...       )
          ...       step_starting_index = step_index + 1
          ...       episode_index += 1
          ...       env.reset()
          >>> env.close()
      """
      if not isinstance(frames, list):
          logger.error(
              f"Expected a list of frames, got a {type(frames)} instead."
          )
      if episode_trigger is None and step_trigger is None:
          episode_trigger = capped_cubic_video_schedule
  
      video_folder = os.path.abspath(video_folder)
      os.makedirs(video_folder, exist_ok=True)
      path_prefix = f"{video_folder}/{name_prefix}"
  
      if episode_trigger is not None and episode_trigger(episode_index):
          clip = ImageSequenceClip(frames[:video_length], **kwargs)
          clip.write_videofile(
              f"{path_prefix}-episode-{episode_index}.mp4"
          )
  
      if step_trigger is not None:
          # skip the first frame since it comes from reset
          for step_index, frame_index in enumerate(
              range(1, len(frames)), start=step_starting_index
          ):
              if step_trigger(step_index):
                  end_index = (
                      frame_index + video_length
                      if video_length is not None
                      else None
                  )
                  clip = ImageSequenceClip(
                      frames[frame_index:end_index], **kwargs
                  )
                  clip.write_videofile(
                      f"{path_prefix}-step-{step_index}.mp4"
                  )
  
imports: import os
  from typing import Callable, Optional
  import gym
  from gym import logger
label_desc: Use image node_id 2 for calling a global function with example usage: save_video(frames, video_folder, episode_trigger, step_trigger, video_length, name_prefix, episode_index, step_starting_index) without return types