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}