Skip to main content

narrate_this/traits/
video_renderer.rs

1use async_trait::async_trait;
2
3use crate::error::Result;
4use crate::types::{AudioTrack, ContentOutput};
5
6/// Configuration for video rendering.
7#[derive(Clone)]
8pub struct RenderConfig {
9    /// Output width in pixels. Default: 1920.
10    pub width: u32,
11    /// Output height in pixels. Default: 1080.
12    pub height: u32,
13    /// Frames per second. Default: 30.
14    pub fps: u32,
15    /// Output file path. Default: `"output.mp4"`.
16    pub output_path: String,
17    /// Background audio tracks to mix with the narration.
18    pub audio_tracks: Vec<AudioTrack>,
19    /// Video codec (`-c:v`). Default: `"libx264"`.
20    pub video_codec: Option<String>,
21    /// Audio codec (`-c:a`). Default: `"aac"`.
22    pub audio_codec: Option<String>,
23    /// Encoder preset (`-preset`). Default: `"fast"`.
24    pub preset: Option<String>,
25    /// Constant Rate Factor (`-crf`) for quality control. Lower = better quality.
26    /// When `None`, ffmpeg uses its own default (typically 23 for libx264).
27    pub crf: Option<u32>,
28    /// Pixel format (`-pix_fmt`), e.g. `"yuv420p"` for broad player compatibility.
29    pub pix_fmt: Option<String>,
30    /// ASS/SSA subtitle force_style string. Default: `"FontSize=24,PrimaryColour=&HFFFFFF&"`.
31    pub subtitle_style: Option<String>,
32    /// Extra ffmpeg arguments inserted before the output path.
33    /// Use this as a catch-all for flags not covered above.
34    pub extra_output_args: Vec<String>,
35}
36
37impl Default for RenderConfig {
38    fn default() -> Self {
39        Self {
40            width: 1920,
41            height: 1080,
42            fps: 30,
43            output_path: "output.mp4".into(),
44            audio_tracks: Vec::new(),
45            video_codec: None,
46            audio_codec: None,
47            preset: None,
48            crf: None,
49            pix_fmt: None,
50            subtitle_style: None,
51            extra_output_args: Vec::new(),
52        }
53    }
54}
55
56impl RenderConfig {
57    /// Set the video codec (`-c:v`), e.g. `"libx265"`, `"h264_nvenc"`.
58    pub fn video_codec(mut self, codec: impl Into<String>) -> Self {
59        self.video_codec = Some(codec.into());
60        self
61    }
62
63    /// Set the audio codec (`-c:a`), e.g. `"libopus"`, `"libmp3lame"`.
64    pub fn audio_codec(mut self, codec: impl Into<String>) -> Self {
65        self.audio_codec = Some(codec.into());
66        self
67    }
68
69    /// Set the encoder preset (`-preset`), e.g. `"ultrafast"`, `"slow"`.
70    pub fn preset(mut self, preset: impl Into<String>) -> Self {
71        self.preset = Some(preset.into());
72        self
73    }
74
75    /// Set the Constant Rate Factor (`-crf`) for quality control.
76    pub fn crf(mut self, crf: u32) -> Self {
77        self.crf = Some(crf);
78        self
79    }
80
81    /// Set the pixel format (`-pix_fmt`), e.g. `"yuv420p"`.
82    pub fn pix_fmt(mut self, fmt: impl Into<String>) -> Self {
83        self.pix_fmt = Some(fmt.into());
84        self
85    }
86
87    /// Set a custom subtitle style (ASS force_style string).
88    pub fn subtitle_style(mut self, style: impl Into<String>) -> Self {
89        self.subtitle_style = Some(style.into());
90        self
91    }
92
93    /// Add extra ffmpeg arguments before the output path.
94    pub fn extra_output_args(mut self, args: impl IntoIterator<Item = impl Into<String>>) -> Self {
95        self.extra_output_args = args.into_iter().map(|a| a.into()).collect();
96        self
97    }
98}
99
100/// Renders pipeline output into a video file.
101///
102/// The built-in implementation is [`FfmpegRenderer`](crate::FfmpegRenderer),
103/// which composites media segments with audio and subtitles using FFmpeg.
104#[async_trait]
105pub trait VideoRenderer: Send + Sync {
106    /// Render a [`ContentOutput`] into a video file. Returns the output file path.
107    async fn render(&self, output: &ContentOutput, config: &RenderConfig) -> Result<String>;
108}