1use std::collections::BTreeMap;
2use std::rc::Rc;
3
4use renderbox_dsl::{OpNode, ParamValue};
5
6use crate::ops::params;
7use crate::sorts::{Audio, Sort, Video};
8use crate::graph::Stream;
9
10#[derive(Default)]
15pub struct OverlayOpts {
16 pub x: Option<String>,
17 pub y: Option<String>,
18 pub alpha: Option<f64>,
19}
20
21pub fn overlay(
22 fg: Stream<Video>,
23 opts: OverlayOpts,
24) -> impl FnOnce(Stream<Video>) -> Stream<Video> {
25 move |bg| {
26 assert!(
27 Rc::ptr_eq(&bg.graph, &fg.graph),
28 "streams must belong to the same graph"
29 );
30 let mut p = BTreeMap::new();
31 if let Some(x) = &opts.x {
32 p.insert("x".into(), ParamValue::String(x.clone()));
33 }
34 if let Some(y) = &opts.y {
35 p.insert("y".into(), ParamValue::String(y.clone()));
36 }
37 if let Some(a) = opts.alpha {
38 p.insert("alpha".into(), ParamValue::Number(a));
39 }
40 let node_id = bg.graph.borrow_mut().add_node(OpNode::Combine2 {
41 op: "overlay".into(),
42 a: bg.node_id,
43 b: fg.node_id,
44 params: p,
45 });
46 Stream::new(node_id, bg.graph)
47 }
48}
49
50fn combine_n<S: Sort>(
55 streams: &[Stream<S>],
56 op: &str,
57 params: BTreeMap<String, ParamValue>,
58) -> Stream<S> {
59 assert!(
60 streams.len() >= 2,
61 "{op} requires at least 2 streams"
62 );
63 let graph = streams[0].graph.clone();
64 for s in &streams[1..] {
65 assert!(
66 Rc::ptr_eq(&graph, &s.graph),
67 "all streams must belong to the same graph"
68 );
69 }
70 let ids: Vec<_> = streams.iter().map(|s| s.node_id).collect();
71 let node_id = graph.borrow_mut().add_node(OpNode::CombineN {
72 op: op.into(),
73 streams: ids,
74 params,
75 });
76 Stream::new(node_id, graph)
77}
78
79pub fn concat(streams: &[Stream<Video>]) -> Stream<Video> {
80 combine_n(streams, "concat", params! {})
81}
82
83pub fn concat_audio(streams: &[Stream<Audio>]) -> Stream<Audio> {
84 combine_n(streams, "concat", params! {})
85}
86
87pub fn amix(streams: &[Stream<Audio>]) -> Stream<Audio> {
88 combine_n(streams, "amix", params! {})
89}
90
91pub fn hstack(streams: &[Stream<Video>]) -> Stream<Video> {
92 combine_n(streams, "hstack", params! {})
93}
94
95pub fn vstack(streams: &[Stream<Video>]) -> Stream<Video> {
96 combine_n(streams, "vstack", params! {})
97}
98
99#[derive(Debug, Clone, Copy)]
100pub enum XfadeTransition {
101 Fade,
102 Wipeleft,
103 Wiperight,
104 Wipeup,
105 Wipedown,
106 Dissolve,
107 Pixelize,
108 Radial,
109 Hblur,
110 Hlslice,
111 Hrslice,
112 Vuslice,
113 Vdslice,
114 Horzopen,
115 Horzclose,
116 Vertopen,
117 Vertclose,
118 Diagtl,
119 Diagtr,
120 Diagbl,
121 Diagbr,
122 Slideright,
123 Slideleft,
124 Slideup,
125 Slidedown,
126 Circlecrop,
127 Rectcrop,
128 Circleopen,
129 Circleclose,
130 Smoothleft,
131 Smoothright,
132 Smoothup,
133 Smoothdown,
134}
135
136impl XfadeTransition {
137 fn as_str(&self) -> &'static str {
138 match self {
139 XfadeTransition::Fade => "fade",
140 XfadeTransition::Wipeleft => "wipeleft",
141 XfadeTransition::Wiperight => "wiperight",
142 XfadeTransition::Wipeup => "wipeup",
143 XfadeTransition::Wipedown => "wipedown",
144 XfadeTransition::Dissolve => "dissolve",
145 XfadeTransition::Pixelize => "pixelize",
146 XfadeTransition::Radial => "radial",
147 XfadeTransition::Hblur => "hblur",
148 XfadeTransition::Hlslice => "hlslice",
149 XfadeTransition::Hrslice => "hrslice",
150 XfadeTransition::Vuslice => "vuslice",
151 XfadeTransition::Vdslice => "vdslice",
152 XfadeTransition::Horzopen => "horzopen",
153 XfadeTransition::Horzclose => "horzclose",
154 XfadeTransition::Vertopen => "vertopen",
155 XfadeTransition::Vertclose => "vertclose",
156 XfadeTransition::Diagtl => "diagtl",
157 XfadeTransition::Diagtr => "diagtr",
158 XfadeTransition::Diagbl => "diagbl",
159 XfadeTransition::Diagbr => "diagbr",
160 XfadeTransition::Slideright => "slideright",
161 XfadeTransition::Slideleft => "slideleft",
162 XfadeTransition::Slideup => "slideup",
163 XfadeTransition::Slidedown => "slidedown",
164 XfadeTransition::Circlecrop => "circlecrop",
165 XfadeTransition::Rectcrop => "rectcrop",
166 XfadeTransition::Circleopen => "circleopen",
167 XfadeTransition::Circleclose => "circleclose",
168 XfadeTransition::Smoothleft => "smoothleft",
169 XfadeTransition::Smoothright => "smoothright",
170 XfadeTransition::Smoothup => "smoothup",
171 XfadeTransition::Smoothdown => "smoothdown",
172 }
173 }
174}
175
176pub fn xfade(
177 a: Stream<Video>,
178 b: Stream<Video>,
179 transition: XfadeTransition,
180 duration: f64,
181 offset: f64,
182) -> Stream<Video> {
183 assert!(
184 Rc::ptr_eq(&a.graph, &b.graph),
185 "streams must belong to the same graph"
186 );
187 let node_id = a.graph.borrow_mut().add_node(OpNode::Combine2 {
188 op: "xfade".into(),
189 a: a.node_id,
190 b: b.node_id,
191 params: params! {
192 "transition" => transition.as_str(),
193 "duration" => duration,
194 "offset" => offset
195 },
196 });
197 Stream::new(node_id, a.graph)
198}
199
200#[cfg(test)]
201mod tests {
202 use super::*;
203 use crate::graph::input;
204
205 #[test]
206 fn overlay_creates_combine2() {
207 let (v, _a) = input("bg.mp4");
208 let (fg, _) = input("fg.mp4");
209 }
213
214 #[test]
215 fn concat_creates_combinen() {
216 let (v, _a) = input("test.mp4");
217 let v1 = v.clone();
218 let v2 = v.clone();
219 let result = concat(&[v1, v2]);
220 let graph = result.graph.borrow();
221 match graph.arena.get(result.node_id) {
222 OpNode::CombineN { op, streams, .. } => {
223 assert_eq!(op, "concat");
224 assert_eq!(streams.len(), 2);
225 }
226 other => panic!("expected CombineN, got {:?}", other),
227 }
228 }
229
230 #[test]
231 fn hstack_creates_combinen() {
232 let (v, _a) = input("test.mp4");
233 let v1 = v.clone();
234 let v2 = v.clone();
235 let v3 = v.clone();
236 let result = hstack(&[v1, v2, v3]);
237 let graph = result.graph.borrow();
238 match graph.arena.get(result.node_id) {
239 OpNode::CombineN { op, streams, .. } => {
240 assert_eq!(op, "hstack");
241 assert_eq!(streams.len(), 3);
242 }
243 other => panic!("expected CombineN, got {:?}", other),
244 }
245 }
246
247 #[test]
248 #[should_panic(expected = "requires at least 2 streams")]
249 fn concat_requires_at_least_2() {
250 let (v, _a) = input("test.mp4");
251 concat(&[v]);
252 }
253
254 #[test]
255 fn xfade_creates_combine2() {
256 let (v, _a) = input("test.mp4");
257 let v1 = v.clone();
258 let v2 = v.clone();
259 let result = xfade(v1, v2, XfadeTransition::Dissolve, 1.0, 4.0);
260 let graph = result.graph.borrow();
261 match graph.arena.get(result.node_id) {
262 OpNode::Combine2 { op, params, .. } => {
263 assert_eq!(op, "xfade");
264 assert_eq!(
265 params["transition"],
266 ParamValue::String("dissolve".into())
267 );
268 }
269 other => panic!("expected Combine2, got {:?}", other),
270 }
271 }
272}