Repository: pytorch/rl

Issue:
[Feature Request] Allow custom video settings to be passed into `CSVExperiment.add_video`
## Motivation

Please outline the motivation for the proposal.
Is your feature request related to a problem? e.g., "I'm always frustrated when [...]".
If this is related to another issue, please link here too.


### NOTE: This relates to a personal issue encountered with a custom TorchRL environment which has a **known good** render function.

Currently, `VideoRecorder` (and its downstream logger `CSVLogger`) does not allow for custom video settings to be passed when dumping environment rollouts (via `pyAV`/`torchvision`)

These video settings are actually `FFmpeg` settings which may have undesirable defaults from `pyAV`/`torchvision`, resulting in blurry/low-quality videos.

Among the various other reasons there may be to play with the video dumping settings, it may conversely be desirable to lower the visual fidelity of the video for better performance.

## Solution

A clear and concise description of what you want to happen.

Expose `kwargs` in `CSVExperiment.add_video` to `torchvision.io.write_video`, allowing end-user to pass FFmpeg `kwargs` when recording rollouts.

## Checklist

- [x] I have checked that there is no similar issue in the repo (**required**)



Discussion:

Resolving patch:
diff --git a/torchrl/record/loggers/csv.py b/torchrl/record/loggers/csv.py
--- a/torchrl/record/loggers/csv.py
+++ b/torchrl/record/loggers/csv.py
@@ -51,6 +51,18 @@ def add_scalar(self, name: str, value: float, global_step: Optional[int] = None)
         fd.flush()
 
     def add_video(self, tag, vid_tensor, global_step: Optional[int] = None, **kwargs):
+        """Writes a video on a file on disk.
+
+        The video format can be one of
+          - `"pt"`: uses :func:`~torch.save` to save the video tensor);
+          - `"memmap"`: saved the file as memory-mapped array (reading this file will require
+            the dtype and shape to be known at read time);
+          - `"mp4"`: saves the file as an `.mp4` file using torchvision :func:`~torchvision.io.write_video`
+            API. Any ``kwargs`` passed to ``add_video`` will be transmitted to ``write_video``.
+            These include ``preset``, ``crf`` and others.
+            See ffmpeg's doc (https://trac.ffmpeg.org/wiki/Encode/H.264) for some more information of the video format options.
+
+        """
         if global_step is None:
             global_step = self.videos_counter[tag]
             self.videos_counter[tag] += 1
@@ -86,7 +98,8 @@ def add_video(self, tag, vid_tensor, global_step: Optional[int] = None, **kwargs
                 vid_tensor = vid_tensor.flatten(0, vid_tensor.ndim - 4)
             vid_tensor = vid_tensor.permute((0, 2, 3, 1))
             vid_tensor = vid_tensor.expand(*vid_tensor.shape[:-1], 3)
-            torchvision.io.write_video(filepath, vid_tensor, fps=self.video_fps)
+            kwargs.setdefault("fps", self.video_fps)
+            torchvision.io.write_video(filepath, vid_tensor, **kwargs)
         else:
             raise ValueError(
                 f"Unknown video format {self.video_format}. Must be one of 'pt', 'memmap' or 'mp4'."
@@ -122,7 +135,7 @@ class CSVLogger(Logger):
         exp_name (str): The name of the experiment.
         log_dir (str or Path, optional): where the experiment should be saved.
             Defaults to ``<cur_dir>/csv_logs``.
-        video_format (str, optional): how videos should be saved. Must be one of
+        video_format (str, optional): how videos should be saved when calling :meth:`~torchrl.record.loggers.csv.CSVExperiment.add_video`. Must be one of
             ``"pt"`` (video saved as a `video_<tag>_<step>.pt` file with torch.save),
             ``"memmap"`` (video saved as a `video_<tag>_<step>.memmap` file with :class:`~tensordict.MemoryMappedTensor`),
             ``"mp4"`` (video saved as a `video_<tag>_<step>.mp4` file, requires torchvision to be installed).
@@ -163,12 +176,18 @@ def log_scalar(self, name: str, value: float, step: int = None) -> None:
         self.experiment.add_scalar(name, value, global_step=step)
 
     def log_video(self, name: str, video: Tensor, step: int = None, **kwargs) -> None:
-        """Log videos inputs to a .pt file.
+        """Log videos inputs to a .pt (or other format) file.
 
         Args:
             name (str): The name of the video.
             video (Tensor): The video to be logged.
             step (int, optional): The step at which the video is logged. Defaults to None.
+            **kwargs: other kwargs passed to the underlying video logger.
+
+        .. note:: If the video format is `mp4`, many more arguments can be passed to the :meth:`~torchvision.io.write_video`
+            function.
+            For more information on video logging with :class:`~torchrl.record.loggers.csv.CSVLogger`,
+            see the :meth:`~torchrl.record.loggers.csv.CSVExperiment.add_video` documentation.
         """
         # check for correct format of the video tensor ((N), T, C, H, W)
         # check that the color channel (C) is either 1 or 3
diff --git a/tutorials/sphinx-tutorials/getting-started-4.py b/tutorials/sphinx-tutorials/getting-started-4.py
--- a/tutorials/sphinx-tutorials/getting-started-4.py
+++ b/tutorials/sphinx-tutorials/getting-started-4.py
@@ -40,7 +40,7 @@
 #
 # Usually, building a logger requires
 # at least an experiment name and possibly a logging directory and other
-# hyperapameters.
+# hyperparameters.
 #
 
 from torchrl.record import CSVLogger
@@ -66,9 +66,12 @@
 #
 # Let's first see how we can create a Gym environment that outputs images
 # alongside its observations. :class:`~torchrl.envs.GymEnv` accept two keywords
-# for this purpose: ``from_pixels=True`` will make the env ``step`` function
+# for this purpose:
+# - ``from_pixels=True`` will make the env ``step`` function
 # write a ``"pixels"`` entry containing the images corresponding to your
-# observations, and the ``pixels_only=False`` will indicate that you want the
+# observations, and
+#
+# - ``pixels_only=False`` will indicate that you want the
 # observations to be returned as well.
 #
 
@@ -94,8 +97,8 @@
 
 #####################################
 # When running this environment, all the ``"pixels"`` entries will be saved in
-# a local buffer and dumped in a video on demand (it is important that you
-# call this method when appropriate):
+# a local buffer (i.e. RAM) and dumped in a video on demand (to prevent excessive
+# RAM usage, you are advised to call this method whenever appropriate!):
 
 rollout = record_env.rollout(max_steps=3)
 # Uncomment this line to save the video on disk:
@@ -105,6 +108,8 @@
 # In this specific case, the video format can be chosen when instantiating
 # the CSVLogger.
 #
+# (If you want to customise how your video is recorded, have a look at :ref:`our knowledge base <ref_knowledge_base>`.)
+#
 # This is all we wanted to cover in the getting started tutorial.
 # You should now be ready to code your
 # :ref:`first training loop with TorchRL <gs_first_training>`!


Test patch: