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}