ez_ffmpeg/core/context/output/metadata.rs
1use super::Output;
2use std::collections::HashMap;
3
4impl Output {
5 // ========== Metadata API Methods ==========
6 // The following helpers mirror FFmpeg's command-line metadata options as implemented in
7 // fftools/ffmpeg_opt.c (opt_metadata / opt_map_metadata) and the automatic propagation rules
8 // in fftools/ffmpeg_mux_init.c:2913-2983. Each method references the corresponding FFmpeg
9 // behavior so callers can cross-check the C implementation when needed.
10
11 /// Add or update global metadata for the output file.
12 ///
13 /// If value is empty string, the key will be removed (FFmpeg behavior).
14 /// Replicates FFmpeg's `-metadata key=value` option.
15 ///
16 /// FFmpeg reference: fftools/ffmpeg_opt.c (`opt_metadata()` handles `-metadata key=value`).
17 ///
18 /// # Examples
19 /// ```rust,ignore
20 /// let output = Output::from("output.mp4")
21 /// .add_metadata("title", "My Video")
22 /// .add_metadata("author", "John Doe");
23 /// ```
24 pub fn add_metadata(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
25 let key = key.into();
26 let value = value.into();
27
28 if value.is_empty() {
29 // Empty value means remove the key (FFmpeg behavior)
30 if let Some(ref mut metadata) = self.global_metadata {
31 metadata.remove(&key);
32 }
33 } else {
34 self.global_metadata
35 .get_or_insert_with(HashMap::new)
36 .insert(key, value);
37 }
38 self
39 }
40
41 /// Add multiple global metadata entries at once.
42 ///
43 /// FFmpeg reference: fftools/ffmpeg_opt.c (consecutive `-metadata` invocations append to the
44 /// same dictionary; this helper simply batches the calls on the Rust side).
45 ///
46 /// # Examples
47 /// ```rust,ignore
48 /// let mut metadata = HashMap::new();
49 /// metadata.insert("title".to_string(), "My Video".to_string());
50 /// metadata.insert("author".to_string(), "John Doe".to_string());
51 ///
52 /// let output = Output::from("output.mp4")
53 /// .add_metadata_map(metadata);
54 /// ```
55 pub fn add_metadata_map(mut self, metadata: HashMap<String, String>) -> Self {
56 for (key, value) in metadata {
57 self = self.add_metadata(key, value);
58 }
59 self
60 }
61
62 /// Remove a global metadata key.
63 ///
64 /// FFmpeg reference: fftools/ffmpeg_opt.c (`-metadata key=` deletes the key when value is
65 /// empty; we follow the same rule by interpreting an empty string as removal).
66 ///
67 /// # Examples
68 /// ```rust,ignore
69 /// let output = Output::from("output.mp4")
70 /// .add_metadata("title", "My Video")
71 /// .remove_metadata("title"); // Remove the title
72 /// ```
73 pub fn remove_metadata(mut self, key: &str) -> Self {
74 if let Some(ref mut metadata) = self.global_metadata {
75 metadata.remove(key);
76 }
77 self
78 }
79
80 /// Clear all metadata (global, stream, chapter, program) and mappings.
81 ///
82 /// Useful when you want to start fresh without any metadata.
83 ///
84 /// FFmpeg reference: fftools/ffmpeg_opt.c (users typically issue `-map_metadata -1` and then
85 /// reapply `-metadata` options; this helper emulates that workflow programmatically).
86 ///
87 /// # Examples
88 /// ```rust,ignore
89 /// let output = Output::from("output.mp4")
90 /// .add_metadata("title", "My Video")
91 /// .clear_all_metadata(); // Remove all metadata
92 /// ```
93 pub fn clear_all_metadata(mut self) -> Self {
94 self.global_metadata = None;
95 self.stream_metadata.clear();
96 self.chapter_metadata.clear();
97 self.program_metadata.clear();
98 self.metadata_map.clear();
99 self
100 }
101
102 /// Disable automatic metadata copying from input files.
103 ///
104 /// By default, FFmpeg automatically copies global and stream metadata
105 /// from input files to output. This method disables that behavior,
106 /// similar to FFmpeg's `-map_metadata -1` option.
107 /// FFmpeg reference: ffmpeg_mux_init.c (`copy_meta()` sets metadata_global_manual when
108 /// `-map_metadata -1` is used; `auto_copy_metadata` mirrors the same flag).
109 ///
110 /// # Examples
111 /// ```rust,ignore
112 /// let output = Output::from("output.mp4")
113 /// .disable_auto_copy_metadata() // Don't copy any metadata from input
114 /// .add_metadata("title", "New Title"); // Only use explicitly set metadata
115 /// ```
116 pub fn disable_auto_copy_metadata(mut self) -> Self {
117 self.auto_copy_metadata = false;
118 self
119 }
120
121 /// Add or update stream-specific metadata.
122 ///
123 /// Uses FFmpeg's stream specifier syntax to identify target streams.
124 /// If value is empty string, the key will be removed (FFmpeg behavior).
125 /// Replicates FFmpeg's `-metadata:s:spec key=value` option.
126 ///
127 /// FFmpeg reference: fftools/ffmpeg_opt.c (`opt_metadata()` with stream specifiers, lines
128 /// 2465-2520 in FFmpeg 7.x).
129 ///
130 /// # Stream Specifier Syntax
131 /// - `"v:0"` - First video stream
132 /// - `"a:1"` - Second audio stream
133 /// - `"s"` - All subtitle streams
134 /// - `"v"` - All video streams
135 /// - `"p:0:v"` - Video streams in program 0
136 /// - `"#0x100"` or `"i:256"` - Stream with specific ID
137 /// - `"m:language:eng"` - Streams with metadata language=eng
138 /// - `"u"` - Usable streams only
139 /// - `"disp:default"` - Streams with default disposition
140 ///
141 /// # Examples
142 /// ```rust,ignore
143 /// let output = Output::from("output.mp4")
144 /// .add_stream_metadata("v:0", "language", "eng")
145 /// .add_stream_metadata("a:0", "title", "Main Audio");
146 /// ```
147 ///
148 /// # Errors
149 /// Returns error if the stream specifier syntax is invalid.
150 pub fn add_stream_metadata(
151 mut self,
152 stream_spec: impl Into<String>,
153 key: impl Into<String>,
154 value: impl Into<String>,
155 ) -> Result<Self, String> {
156 use crate::core::metadata::StreamSpecifier;
157
158 let stream_spec_str = stream_spec.into();
159 let key = key.into();
160 let value = value.into();
161
162 // Parse and validate stream specifier
163 let _specifier = StreamSpecifier::parse(&stream_spec_str)?;
164
165 // Store as (spec, key, value) tuple
166 // During output initialization, this will be matched against actual streams
167 // using StreamSpecifier::matches and applied to all matching streams
168 // Replicates FFmpeg's of_add_metadata behavior
169 self.stream_metadata.push((stream_spec_str, key, value));
170
171 Ok(self)
172 }
173
174 /// Add or update chapter-specific metadata.
175 ///
176 /// Chapters are used for DVD-like navigation points in media files.
177 /// If value is empty string, the key will be removed (FFmpeg behavior).
178 /// Replicates FFmpeg's `-metadata:c:N key=value` option.
179 /// FFmpeg reference: fftools/ffmpeg_opt.c (`opt_metadata()` handles the `c:` target selector).
180 ///
181 /// # Examples
182 /// ```rust,ignore
183 /// let output = Output::from("output.mp4")
184 /// .add_chapter_metadata(0, "title", "Introduction")
185 /// .add_chapter_metadata(1, "title", "Main Content");
186 /// ```
187 pub fn add_chapter_metadata(
188 mut self,
189 chapter_index: usize,
190 key: impl Into<String>,
191 value: impl Into<String>,
192 ) -> Self {
193 let key = key.into();
194 let value = value.into();
195
196 if value.is_empty() {
197 // Empty value means remove the key (FFmpeg behavior)
198 if let Some(metadata) = self.chapter_metadata.get_mut(&chapter_index) {
199 metadata.remove(&key);
200 }
201 } else {
202 self.chapter_metadata
203 .entry(chapter_index)
204 .or_default()
205 .insert(key, value);
206 }
207 self
208 }
209
210 /// Add or update program-specific metadata.
211 ///
212 /// Programs are used in multi-program transport streams (e.g., MPEG-TS).
213 /// If value is empty string, the key will be removed (FFmpeg behavior).
214 /// Replicates FFmpeg's `-metadata:p:N key=value` option.
215 /// FFmpeg reference: fftools/ffmpeg_opt.c (`opt_metadata()` with `p:` selector).
216 ///
217 /// # Examples
218 /// ```rust,ignore
219 /// let output = Output::from("output.ts")
220 /// .add_program_metadata(0, "service_name", "Channel 1")
221 /// .add_program_metadata(1, "service_name", "Channel 2");
222 /// ```
223 pub fn add_program_metadata(
224 mut self,
225 program_index: usize,
226 key: impl Into<String>,
227 value: impl Into<String>,
228 ) -> Self {
229 let key = key.into();
230 let value = value.into();
231
232 if value.is_empty() {
233 // Empty value means remove the key (FFmpeg behavior)
234 if let Some(metadata) = self.program_metadata.get_mut(&program_index) {
235 metadata.remove(&key);
236 }
237 } else {
238 self.program_metadata
239 .entry(program_index)
240 .or_default()
241 .insert(key, value);
242 }
243 self
244 }
245
246 /// Map metadata from an input file to this output.
247 ///
248 /// Replicates FFmpeg's `-map_metadata [src_file_idx]:src_type:dst_type` option.
249 /// This allows copying metadata from specific locations in input files to
250 /// specific locations in the output file.
251 ///
252 /// # Type Specifiers
253 /// - `"g"` or `""` - Global metadata
254 /// - `"s"` or `"s:spec"` - Stream metadata (with optional stream specifier)
255 /// - `"c:N"` - Chapter N metadata
256 /// - `"p:N"` - Program N metadata
257 ///
258 /// # Examples
259 /// ```rust,ignore
260 /// use ez_ffmpeg::core::metadata::{MetadataType, MetadataMapping};
261 ///
262 /// let output = Output::from("output.mp4")
263 /// // Copy global metadata from input 0 to output global
264 /// .map_metadata_from_input(0, "g", "g")?
265 /// // Copy first video stream metadata from input 1 to output first video stream
266 /// .map_metadata_from_input(1, "s:v:0", "s:v:0")?;
267 /// ```
268 ///
269 /// # Errors
270 /// Returns error if the type specifier syntax is invalid.
271 /// FFmpeg reference: fftools/ffmpeg_opt.c (`opt_map_metadata()` parses the same
272 /// `[file][:type]` triplet and feeds it into `MetadataMapping`).
273 pub fn map_metadata_from_input(
274 mut self,
275 input_index: usize,
276 src_type_spec: impl Into<String>,
277 dst_type_spec: impl Into<String>,
278 ) -> Result<Self, String> {
279 use crate::core::metadata::{MetadataMapping, MetadataType};
280
281 let src_type = MetadataType::parse(&src_type_spec.into())?;
282 let dst_type = MetadataType::parse(&dst_type_spec.into())?;
283
284 self.metadata_map.push(MetadataMapping {
285 src_type,
286 dst_type,
287 input_index,
288 });
289
290 Ok(self)
291 }
292}