Skip to main content

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}