Skip to main content

renderbox_sdk/graph/
output.rs

1use 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}