Skip to content

Rendering

render_video

render_video(
    project: VideoProject,
    output_path: str | Path,
    *,
    storage_options: dict[str, Any] | None = None,
    overwrite: bool = False,
    **kwargs: Any
) -> Path

Renders a video based on a project.

Parameters:

Name Type Description Default

project

VideoProject

The project to render.

required

output_path

str | Path

The output path. If a directory is provided, the output file will be saved in the directory with the project title as the filename. Otherwise, be sure that the file extension matches the codec used. By default, the output file will be an MP4 file (H.264 codec). The available codecs are: - libx264: .mp4 - mpeg4: .mp4 - rawvideo: .avi - png: .avi - libvorbis: .ogv - libvpx: .webm

required

storage_options

dict[str, Any] | None

Optional storage options to pass to the clip.

None

overwrite

bool

Whether to overwrite the output file if it already exists.

False

kwargs

Any

Additional keyword arguments to pass to Moviepy clip video writer.

{}

Returns:

Type Description
Path

The path to the rendered video.

Source code in src/mosaico/video/rendering.py
def render_video(
    project: VideoProject,
    output_path: str | Path,
    *,
    storage_options: dict[str, Any] | None = None,
    overwrite: bool = False,
    **kwargs: Any,
) -> Path:
    """
    Renders a video based on a project.

    :param project: The project to render.
    :param output_path: The output path. If a directory is provided, the output file will be saved in the directory
        with the project title as the filename. Otherwise, be sure that the file extension matches the codec used.
        By default, the output file will be an MP4 file (H.264 codec). The available codecs are:

        - libx264: .mp4
        - mpeg4: .mp4
        - rawvideo: .avi
        - png: .avi
        - libvorbis: .ogv
        - libvpx: .webm

    :param storage_options: Optional storage options to pass to the clip.
    :param overwrite: Whether to overwrite the output file if it already exists.
    :param kwargs: Additional keyword arguments to pass to Moviepy clip video writer.
    :return: The path to the rendered video.
    """
    output_path = Path(output_path).resolve()
    output_codec = kwargs.get("codec") or _guess_codec_from_file_path(output_path) or "libx264"
    output_file_ext = _CODEC_FILE_EXTENSION_MAP[output_codec]

    if output_path.is_dir():
        output_path /= f"{project.config.title}.{output_file_ext}"

    if output_path.suffix != output_file_ext:
        raise ValueError(f"Output file must be an '{output_file_ext}' file.")

    if not output_path.parent.exists():
        raise FileNotFoundError(f"Output directory does not exist: {output_path.parent}")

    if output_path.exists() and not overwrite:
        msg = f"Output file already exists: {output_path}"
        raise FileExistsError(msg)

    video_clips = []
    audio_clips = []

    for event in project.timeline:
        event_asset_ref_pairs = _get_event_assets_and_refs(event, project)
        event_video_clips, event_audio_clips = _render_event_clips(
            event_asset_ref_pairs, project.config.resolution, storage_options
        )
        video_clips.extend(event_video_clips or [])
        audio_clips.extend(event_audio_clips or [])

    video: VideoClip = (
        CompositeVideoClip(video_clips, size=project.config.resolution)
        .with_fps(project.config.fps)
        .with_duration(project.duration)
    )

    if audio_clips:
        audio = CompositeAudioClip(audio_clips).with_duration(project.duration)
        video = video.with_audio(audio)

    kwargs["codec"] = output_codec
    kwargs["audio_codec"] = kwargs.get("audio_codec", "aac")
    kwargs["threads"] = kwargs.get("threads", multiprocessing.cpu_count())
    kwargs["temp_audiofile_path"] = kwargs.get("temp_audiofile_path", output_path.parent.as_posix())

    video.write_videofile(output_path.as_posix(), **kwargs)
    video.close()

    return output_path