renderbox_sdk/graph/
output.rs1use std::collections::BTreeMap;
2use std::rc::Rc;
3
4use renderbox_dsl::{OpNode, ParamValue};
5
6use crate::sorts::{Audio, Video};
7
8use super::plan::PlanBuilder;
9use super::stream::Stream;
10
11pub fn output(path: impl AsRef<str>, video: Stream<Video>, audio: Stream<Audio>) -> PlanBuilder {
12 output_with_opts(path, video, audio, BTreeMap::new())
13}
14
15pub fn output_encoded(
16 path: impl AsRef<str>,
17 video: Stream<Video>,
18 audio: Stream<Audio>,
19 opts: impl crate::codec::ContainerOpts,
20) -> PlanBuilder {
21 output_with_opts(path, video, audio, opts.to_params())
22}
23
24fn output_with_opts(
25 path: impl AsRef<str>,
26 video: Stream<Video>,
27 audio: Stream<Audio>,
28 opts: BTreeMap<String, ParamValue>,
29) -> PlanBuilder {
30 assert!(
31 Rc::ptr_eq(&video.graph, &audio.graph),
32 "video and audio streams must belong to the same graph"
33 );
34 let graph = Rc::clone(&video.graph);
35 let node_id = graph.borrow_mut().add_node(OpNode::Output {
36 video: video.node_id,
37 audio: audio.node_id,
38 path: Some(path.as_ref().to_string()),
39 opts,
40 });
41 graph.borrow_mut().outputs.push(node_id);
42 PlanBuilder::new(graph)
43}
44
45pub fn output_video(path: impl AsRef<str>, video: Stream<Video>) -> PlanBuilder {
46 let graph = Rc::clone(&video.graph);
47 let node_id = graph.borrow_mut().add_node(OpNode::VideoOutput {
48 video: video.node_id,
49 path: Some(path.as_ref().to_string()),
50 opts: BTreeMap::new(),
51 });
52 graph.borrow_mut().outputs.push(node_id);
53 PlanBuilder::new(graph)
54}
55
56pub fn output_audio(path: impl AsRef<str>, audio: Stream<Audio>) -> PlanBuilder {
57 let graph = Rc::clone(&audio.graph);
58 let node_id = graph.borrow_mut().add_node(OpNode::AudioOutput {
59 audio: audio.node_id,
60 path: Some(path.as_ref().to_string()),
61 opts: BTreeMap::new(),
62 });
63 graph.borrow_mut().outputs.push(node_id);
64 PlanBuilder::new(graph)
65}
66
67#[cfg(test)]
68mod tests {
69 use super::*;
70 use crate::graph::input::input;
71 use crate::sorts::Video;
72
73 #[test]
74 fn output_adds_output_node() {
75 let (v, a) = input("test.mp4");
76 let builder = output("out.mp4", v, a);
77 assert_eq!(builder.graph.borrow().arena.len(), 2);
78 assert_eq!(builder.graph.borrow().outputs.len(), 1);
79 let bytes = builder.build().unwrap();
80 assert!(!bytes.is_empty());
81 }
82
83 #[test]
84 #[should_panic(expected = "video and audio streams must belong to the same graph")]
85 fn output_panics_on_different_graphs() {
86 let (v, _a) = input("a.mp4");
87 let (_v2, a2) = input("b.mp4");
88 output("out.mp4", v, a2);
89 }
90
91 #[test]
92 fn end_to_end_filter_pipeline() {
93 let (v, a) = input("test.mp4");
94 let v: Stream<Video> = v.pipe(|s| {
95 let node_id = s.graph.borrow_mut().add_node(OpNode::Filter {
96 name: "scale".into(),
97 params: BTreeMap::from([
98 ("w".into(), renderbox_dsl::ParamValue::Number(1920.0)),
99 ("h".into(), renderbox_dsl::ParamValue::Number(1080.0)),
100 ]),
101 input: s.node_id,
102 });
103 Stream::new(node_id, s.graph)
104 });
105 let bytes = output("out.mp4", v, a).build().unwrap();
106 assert!(!bytes.is_empty());
107 }
108}