Skip to main content

ff_encode/video/builder/
meta.rs

1//! Metadata, container, and miscellaneous settings for [`VideoEncoderBuilder`].
2
3use super::VideoEncoderBuilder;
4use crate::OutputContainer;
5
6impl VideoEncoderBuilder {
7    /// Set container format explicitly (usually auto-detected from file extension).
8    #[must_use]
9    pub fn container(mut self, container: OutputContainer) -> Self {
10        self.container = Some(container);
11        self
12    }
13
14    /// Set a closure as the progress callback.
15    #[must_use]
16    pub fn on_progress<F>(mut self, callback: F) -> Self
17    where
18        F: FnMut(&crate::EncodeProgress) + Send + 'static,
19    {
20        self.progress_callback = Some(Box::new(callback));
21        self
22    }
23
24    /// Set a [`crate::EncodeProgressCallback`] trait object (supports cancellation).
25    #[must_use]
26    pub fn progress_callback<C: crate::EncodeProgressCallback + 'static>(
27        mut self,
28        callback: C,
29    ) -> Self {
30        self.progress_callback = Some(Box::new(callback));
31        self
32    }
33
34    /// Enable two-pass encoding for more accurate bitrate distribution.
35    ///
36    /// Two-pass encoding is video-only and is incompatible with audio streams.
37    #[must_use]
38    pub fn two_pass(mut self) -> Self {
39        self.two_pass = true;
40        self
41    }
42
43    /// Embed a metadata tag in the output container.
44    ///
45    /// Calls `av_dict_set` on `AVFormatContext->metadata` before the header
46    /// is written. Multiple calls accumulate entries; duplicate keys use the
47    /// last value.
48    #[must_use]
49    pub fn metadata(mut self, key: &str, value: &str) -> Self {
50        self.metadata.push((key.to_string(), value.to_string()));
51        self
52    }
53
54    /// Add a chapter to the output container.
55    ///
56    /// Allocates an `AVChapter` entry on `AVFormatContext` before the header
57    /// is written. Multiple calls accumulate chapters in the order added.
58    #[must_use]
59    pub fn chapter(mut self, chapter: ff_format::chapter::ChapterInfo) -> Self {
60        self.chapters.push(chapter);
61        self
62    }
63
64    /// Copy a subtitle stream from an existing file into the output container.
65    ///
66    /// Opens `source_path`, locates the stream at `stream_index`, and registers it
67    /// as a passthrough stream in the output.  Packets are copied verbatim using
68    /// `av_interleaved_write_frame` without re-encoding.
69    ///
70    /// `stream_index` is the zero-based index of the subtitle stream inside
71    /// `source_path`.  For files with a single subtitle track this is typically `0`
72    /// (or whichever index `ffprobe` reports).
73    ///
74    /// If the source cannot be opened or the stream index is invalid, a warning is
75    /// logged and encoding continues without subtitles.
76    #[must_use]
77    pub fn subtitle_passthrough(mut self, source_path: &str, stream_index: usize) -> Self {
78        self.subtitle_passthrough = Some((source_path.to_string(), stream_index));
79        self
80    }
81
82    /// Set per-codec encoding options.
83    ///
84    /// Applied via `av_opt_set` before `avcodec_open2` during [`build()`](Self::build).
85    /// This is additive — omitting it leaves codec defaults unchanged.
86    /// Any option that the chosen encoder does not support is logged as a
87    /// warning and skipped; it never causes `build()` to return an error.
88    ///
89    /// The [`crate::VideoCodecOptions`] variant should match the codec selected via
90    /// [`video_codec()`](Self::video_codec).  A mismatch is silently ignored.
91    #[must_use]
92    pub fn codec_options(mut self, opts: crate::VideoCodecOptions) -> Self {
93        self.codec_options = Some(opts);
94        self
95    }
96
97    /// Embed a binary attachment in the output container.
98    ///
99    /// Attachments are supported in MKV/WebM containers and are used for
100    /// fonts (required by ASS/SSA subtitle rendering), cover art, or other
101    /// binary files that consumers of the file may need.
102    ///
103    /// - `data` — raw bytes of the attachment
104    /// - `mime_type` — MIME type string (e.g. `"application/x-truetype-font"`,
105    ///   `"image/jpeg"`)
106    /// - `filename` — the name reported inside the container (e.g. `"Arial.ttf"`)
107    ///
108    /// Multiple calls accumulate entries; each attachment becomes its own stream
109    /// with `AVMEDIA_TYPE_ATTACHMENT` codec parameters.
110    #[must_use]
111    pub fn add_attachment(mut self, data: Vec<u8>, mime_type: &str, filename: &str) -> Self {
112        self.attachments
113            .push((data, mime_type.to_string(), filename.to_string()));
114        self
115    }
116}
117
118#[cfg(test)]
119mod tests {
120    use super::*;
121    use std::path::PathBuf;
122
123    #[test]
124    fn builder_container_should_be_stored() {
125        let builder = VideoEncoderBuilder::new(PathBuf::from("output.mp4"))
126            .video(1920, 1080, 30.0)
127            .container(OutputContainer::Mp4);
128        assert_eq!(builder.container, Some(OutputContainer::Mp4));
129    }
130
131    #[test]
132    fn two_pass_flag_should_be_stored_in_builder() {
133        let builder = VideoEncoderBuilder::new(PathBuf::from("output.mp4"))
134            .video(640, 480, 30.0)
135            .two_pass();
136        assert!(builder.two_pass);
137    }
138
139    #[test]
140    fn add_attachment_should_accumulate_entries() {
141        let builder = VideoEncoderBuilder::new(PathBuf::from("output.mkv"))
142            .video(320, 240, 30.0)
143            .add_attachment(vec![1, 2, 3], "application/x-truetype-font", "font.ttf")
144            .add_attachment(vec![4, 5, 6], "image/jpeg", "cover.jpg");
145        assert_eq!(builder.attachments.len(), 2);
146        assert_eq!(builder.attachments[0].0, vec![1u8, 2, 3]);
147        assert_eq!(builder.attachments[0].1, "application/x-truetype-font");
148        assert_eq!(builder.attachments[0].2, "font.ttf");
149        assert_eq!(builder.attachments[1].1, "image/jpeg");
150        assert_eq!(builder.attachments[1].2, "cover.jpg");
151    }
152
153    #[test]
154    fn add_attachment_with_no_attachments_should_start_empty() {
155        let builder = VideoEncoderBuilder::new(PathBuf::from("output.mkv")).video(320, 240, 30.0);
156        assert!(builder.attachments.is_empty());
157    }
158}