ffmpeg_stringify/
lib.rs

1pub(crate) mod nodes;
2use std::fmt::Debug;
3
4use crate::nodes::filter::{Filter, FilterNode, FilterOptions};
5use crate::nodes::fnode_type::FNodeType;
6use crate::nodes::stream::Stream;
7use crate::nodes::stream::StreamType;
8pub use nodes::*;
9
10pub fn stringify(nodes: Vec<FNodeType>) -> String {
11    let mut str = "".to_string();
12    let inputs: Vec<_> = nodes
13        .clone()
14        .into_iter()
15        .filter(|node| match node.clone() {
16            FNodeType::Stream(node) => {
17                if let StreamType::Input = node.stream_type {
18                    true
19                } else {
20                    false
21                }
22            }
23            _ => false,
24        })
25        .collect();
26    inputs.clone().into_iter().for_each(|node| {
27        if let FNodeType::Stream(node) = node {
28            str = str.clone() + "-i " + node.path.as_str() + " ";
29        }
30    });
31
32    // println!("{:#?}",graph.into_nodes_edges().1.get(1).unwrap().target());
33    str = str + "-filter_complex '";
34    let filters: String = nodes
35        .clone()
36        .into_iter()
37        .filter(|node| match &node.clone() {
38            FNodeType::FilterNode(node) => true,
39            _ => false,
40        })
41        .map(|node| format_operation(Into::<FilterNode>::into(node.clone())))
42        .collect::<Vec<String>>()
43        .join(";");
44
45    let filtered_output: Vec<_> = nodes
46        .clone()
47        .into_iter()
48        .filter(|node| match node.clone() {
49            FNodeType::Stream(node) => {
50                if let StreamType::Output = node.stream_type {
51                    true
52                } else {
53                    false
54                }
55            }
56            _ => false,
57        })
58        .collect();
59
60    let mut maps = String::from("");
61    filtered_output.clone().into_iter().for_each(|node| {
62        let n: Stream = node.into();
63        if let Some(inputs) = n.inputs {
64            inputs
65                .into_iter()
66                .for_each(|input| maps = format!("{} -map '[{}]'", maps, input));
67        }
68    });
69
70    let output = filtered_output
71        .clone()
72        .into_iter()
73        .map(|node| Into::<Stream>::into(node).path)
74        .collect::<Vec<String>>()
75        .join("");
76
77    format!("{}{}' {} {}", str, filters, maps, output)
78}
79
80pub fn format_filter(filters: Vec<Filter>) -> String {
81    let filters: Vec<String> = filters
82        .into_iter()
83        .map(|filter| {
84            let mut filters = "".to_string();
85            match filter.options {
86                FilterOptions::HashMap(filter) => {
87                    filters = filter
88                        .into_iter()
89                        .map(|option| return format!("{}={}", option.0, option.1))
90                        .collect::<Vec<_>>()
91                        .join(":")
92                }
93                FilterOptions::String(filter) => filters = filter,
94            }
95            format!("{}={}", filter.name, filters)
96        })
97        .collect();
98    filters.join(",")
99}
100
101pub fn format_operation(filterNode: FilterNode) -> String {
102    let ins = filterNode
103        .inputs
104        .into_iter()
105        .map(|input| format!("[{}]", input))
106        .collect::<Vec<String>>()
107        .join("");
108    let ops = format_filter(filterNode.filters);
109    let outs = filterNode
110        .outputs
111        .into_iter()
112        .map(|output| format!("[{}]", output))
113        .collect::<Vec<String>>()
114        .join("");
115    format!("{}{}{}", ins, ops, outs)
116}
117
118#[cfg(test)]
119mod tests {
120    use super::*;
121    use crate::nodes::filter::FilterOptions;
122    use crate::nodes::stream::StreamType;
123    use map_macro::hash_map;
124    use std::{path, vec};
125
126    #[test]
127    fn serialize() {
128        let output = Stream {
129            path: "/data/vid-modified.mp4".to_string(),
130            name: "input1".to_string(),
131            stream_type: StreamType::Output,
132            inputs: None,
133        };
134        println!("{:#?}", serde_json::to_string(&output).unwrap());
135        let overlay = FilterNode {
136            filters: vec![Filter {
137                name: "fade".to_string(),
138                options: FilterOptions::HashMap(hash_map! {
139                    "type".to_string()=>"in".to_string(),
140                    "st".to_string()=>"0".to_string(),
141                    "duration".to_string()=>"1".to_string()
142                }),
143            }],
144
145            name: "overlay".to_string(),
146            inputs: vec!["0:v".to_string()],
147            outputs: vec![],
148        };
149        // println!("{:#?}",serde_json::to_string_pretty(&overlay).unwrap());
150    }
151
152    #[test]
153    fn it_works() {
154        let output = Stream {
155            path: "./data/vid-modified.mp4".to_string(),
156            name: "input1".to_string(),
157            stream_type: StreamType::Output,
158            inputs: Some(vec!["audio".to_string(), "out0".to_string()]),
159        };
160        let input = Stream {
161            path: "./data/vid.mp4".to_string(),
162            name: "input2".to_string(),
163            stream_type: StreamType::Input,
164            inputs: None,
165        };
166        let sound_input: Stream = Stream {
167            path: "./data/sound.mp3".to_string(),
168            name: "sound_input".to_string(),
169            stream_type: StreamType::Input,
170            inputs: None,
171        };
172        let trim = FilterNode {
173            filters: vec![Filter {
174                name: "trim".to_string(),
175                options: FilterOptions::HashMap(hash_map! {
176                    "duration".to_string()=>"4".to_string(),
177                }),
178            }],
179
180            name: "trim".to_string(),
181            inputs: vec!["0:v".to_string()],
182            outputs: vec!["out0".to_string()],
183        };
184
185        let audio = FilterNode {
186            filters: vec![Filter {
187                name: "volume".to_string(),
188                options: FilterOptions::String("1".to_string()),
189            }],
190            name: "volume".to_string(),
191            outputs: vec!["audio".to_string()],
192            inputs: vec!["trimmed_audio".to_string()],
193        };
194
195        let audio_trim = FilterNode {
196            filters: vec![Filter {
197                name: "atrim".to_string(),
198                options: FilterOptions::HashMap(hash_map! {
199                    "duration".to_string()=>"4".to_string()
200                }),
201            }],
202            name: "atrim".to_string(),
203            inputs: vec!["1:a".to_string()],
204            outputs: vec!["trimmed_audio".to_string()],
205        };
206
207        let string = stringify(vec![
208            FNodeType::Stream(input),
209            FNodeType::Stream(sound_input),
210            FNodeType::FilterNode(trim),
211            FNodeType::FilterNode(audio_trim),
212            FNodeType::FilterNode(audio),
213            FNodeType::Stream(output),
214        ]);
215        println!("{:?}", string);
216        assert_eq!(string, "-i ./data/vid.mp4 -i ./data/sound.mp3 -filter_complex '[0:v]trim=duration=4[out0];[1:a]atrim=duration=4[trimmed_audio];[trimmed_audio]volume=1[audio]'  -map '[audio]' -map '[out0]' ./data/vid-modified.mp4".to_string());
217    }
218}