1use std::fmt;
7
8use crate::{
9 error::{LumenError, PropertyError},
10 expr::Expression,
11};
12
13pub mod compositing;
14pub mod media_output;
15pub mod processing;
16pub mod source;
17pub mod vector;
18
19#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
20pub struct NodeId(pub u64);
21
22impl NodeId {
23 pub const fn new(value: u64) -> Self {
24 Self(value)
25 }
26}
27
28impl fmt::Display for NodeId {
29 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
30 write!(f, "{}", self.0)
31 }
32}
33
34#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
35pub struct TrackId(pub u64);
36
37impl TrackId {
38 pub const fn new(value: u64) -> Self {
39 Self(value)
40 }
41}
42
43impl fmt::Display for TrackId {
44 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
45 write!(f, "{}", self.0)
46 }
47}
48
49#[derive(Debug, Clone, Copy, PartialEq, Eq)]
50pub struct InputPortDef {
51 pub name: &'static str,
52 pub kind: PortKind,
53 pub optional: bool,
54 pub variadic: bool,
55}
56
57#[derive(Debug, Clone, Copy, PartialEq, Eq)]
58pub struct OutputPortDef {
59 pub name: &'static str,
60 pub kind: PortKind,
61}
62
63#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
64#[repr(u8)]
65pub enum PropertyKind {
66 Float = 0,
67 Int = 1,
68 Bool = 2,
69 String = 3,
70 Color = 4,
71 Vec2 = 5,
72 Enum = 6,
73}
74
75#[cfg(any(feature = "json", feature = "metadata"))]
76#[derive(Debug, Clone, Copy, PartialEq, Eq)]
77pub struct EnumOptionDef {
78 pub name: &'static str,
79 pub label: &'static str,
80 pub value: i64,
81}
82
83#[cfg(any(feature = "json", feature = "metadata"))]
84#[derive(Debug, Clone, Copy, PartialEq, Eq)]
85pub struct EnumDef {
86 pub name: &'static str,
87 pub options: &'static [EnumOptionDef],
88}
89
90#[cfg(any(feature = "json", feature = "metadata"))]
91pub trait NodeEnum {
92 fn enum_def() -> &'static EnumDef;
93}
94
95#[derive(Debug, Clone, Copy, PartialEq)]
96pub struct PropertyDef {
97 pub id: &'static str,
98 pub expected: PropertyKind,
99 #[cfg(any(feature = "json", feature = "metadata"))]
100 pub enum_def: Option<&'static EnumDef>,
101 #[cfg(feature = "metadata")]
102 pub name: &'static str,
103 #[cfg(feature = "metadata")]
104 pub description: &'static str,
105 #[cfg(feature = "metadata")]
106 pub constraints: PropertyConstraints,
107}
108
109#[cfg(feature = "metadata")]
110#[derive(Debug, Clone, Copy, Default, PartialEq)]
111pub struct PropertyConstraints {
112 pub min: Option<f64>,
113 pub max: Option<f64>,
114 pub step: Option<f64>,
115 pub format: Option<&'static str>,
116 pub multiline: bool,
117 pub recommended_rows: Option<u32>,
118 pub role: Option<&'static str>,
119}
120
121#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
122#[repr(u8)]
123pub enum NodeCategory {
124 Compositing = 0,
125 Processing = 1,
126 Source = 2,
127 Output = 3,
128 Vector = 4,
129}
130
131#[derive(Debug, Clone)]
132pub struct NodeSchemaDef {
133 pub kind: &'static str,
134 pub name: &'static str,
135 pub description: &'static str,
136 pub category: NodeCategory,
137 pub inputs: &'static [InputPortDef],
138 pub properties: Vec<PropertyDef>,
139 pub default_properties: Vec<(&'static str, NodeProperty)>,
140}
141
142#[cfg(feature = "metadata")]
143pub trait NodeSchema: Default {
144 fn schema() -> NodeSchemaDef;
145}
146
147#[cfg(feature = "json")]
148pub trait JsonNode: Default {
149 fn from_json(
150 id: NodeId,
151 properties: Option<&serde_json::Map<String, serde_json::Value>>,
152 ) -> anyhow::Result<Self>
153 where
154 Self: Sized;
155
156 fn set_input_json(&mut self, port: &str, source: PortRef) -> anyhow::Result<()>;
157}
158
159#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
160#[repr(u8)]
161pub enum PortKind {
162 Raster = 0,
163 Vector = 1,
164}
165
166#[derive(Debug, Clone)]
167pub enum NodeProperty {
168 Float(f64),
169 Int(i64),
170 Bool(bool),
171 String(String),
172 Color([u8; 4]),
173 Vec2((f64, f64)),
174 FloatVec(Vec<f64>),
175 IntVec(Vec<i64>),
176 StringVec(Vec<String>),
177 Expr(Expression),
178}
179
180impl NodeProperty {
181 fn invalid_type(
182 node_id: NodeId,
183 property_path: &str,
184 expected: &'static str,
185 actual: &'static str,
186 ) -> LumenError {
187 LumenError::Property(PropertyError::InvalidType {
188 node_id,
189 property_path: property_path.to_string(),
190 expected,
191 actual,
192 })
193 }
194
195 pub fn resolve_float(
196 &self,
197 node_id: NodeId,
198 property_path: &str,
199 ctx: &crate::expr::ExpressionContext<'_>,
200 ) -> crate::Result<f64> {
201 match self {
202 Self::Float(value) => Ok(*value),
203 Self::Int(value) => Ok(*value as f64),
204 Self::String(value) => value
205 .parse::<f64>()
206 .map_err(|_| Self::invalid_type(node_id, property_path, "Float", "String")),
207 Self::Expr(expr) => expr
208 .evaluate(ctx)?
209 .as_f64()
210 .ok_or_else(|| Self::invalid_type(node_id, property_path, "Float", "expression")),
211 _ => Err(Self::invalid_type(
212 node_id,
213 property_path,
214 "Float",
215 "unsupported",
216 )),
217 }
218 }
219
220 pub fn resolve_int(
221 &self,
222 node_id: NodeId,
223 property_path: &str,
224 ctx: &crate::expr::ExpressionContext<'_>,
225 ) -> crate::Result<i64> {
226 match self {
227 Self::Int(value) => Ok(*value),
228 Self::Float(value) => Ok(*value as i64),
229 Self::Bool(value) => Ok(i64::from(*value)),
230 Self::String(value) => value
231 .parse::<i64>()
232 .map_err(|_| Self::invalid_type(node_id, property_path, "Int", "String")),
233 Self::Expr(expr) => {
234 let value = expr.evaluate(ctx)?.as_f64().ok_or_else(|| {
235 Self::invalid_type(node_id, property_path, "Int", "expression")
236 })?;
237 Ok(value as i64)
238 }
239 _ => Err(Self::invalid_type(
240 node_id,
241 property_path,
242 "Int",
243 "unsupported",
244 )),
245 }
246 }
247
248 pub fn resolve_bool(
249 &self,
250 node_id: NodeId,
251 property_path: &str,
252 ctx: &crate::expr::ExpressionContext<'_>,
253 ) -> crate::Result<bool> {
254 match self {
255 Self::Bool(value) => Ok(*value),
256 Self::Int(value) => Ok(*value != 0),
257 Self::Float(value) => Ok(*value != 0.0),
258 Self::String(value) => match value.to_ascii_lowercase().as_str() {
259 "true" | "1" => Ok(true),
260 "false" | "0" => Ok(false),
261 _ => Err(Self::invalid_type(node_id, property_path, "Bool", "String")),
262 },
263 Self::Expr(expr) => Ok(expr.evaluate(ctx)?.as_bool()),
264 _ => Err(Self::invalid_type(
265 node_id,
266 property_path,
267 "Bool",
268 "unsupported",
269 )),
270 }
271 }
272
273 pub fn resolve_string(
274 &self,
275 node_id: NodeId,
276 property_path: &str,
277 ctx: &crate::expr::ExpressionContext<'_>,
278 ) -> crate::Result<String> {
279 match self {
280 Self::String(value) => Ok(value.clone()),
281 Self::Int(value) => Ok(value.to_string()),
282 Self::Float(value) => Ok(value.to_string()),
283 Self::Bool(value) => Ok(value.to_string()),
284 Self::Expr(expr) => Ok(expr.evaluate(ctx)?.as_string()),
285 _ => Err(Self::invalid_type(
286 node_id,
287 property_path,
288 "String",
289 "unsupported",
290 )),
291 }
292 }
293
294 pub fn resolve_color(
295 &self,
296 node_id: NodeId,
297 property_path: &str,
298 _ctx: &crate::expr::ExpressionContext<'_>,
299 ) -> crate::Result<[u8; 4]> {
300 match self {
301 Self::Color(value) => Ok(*value),
302 _ => Err(Self::invalid_type(
303 node_id,
304 property_path,
305 "Color",
306 "unsupported",
307 )),
308 }
309 }
310
311 pub fn resolve_vec2(
312 &self,
313 node_id: NodeId,
314 property_path: &str,
315 _ctx: &crate::expr::ExpressionContext<'_>,
316 ) -> crate::Result<(f64, f64)> {
317 match self {
318 Self::Vec2(value) => Ok(*value),
319 _ => Err(Self::invalid_type(
320 node_id,
321 property_path,
322 "Vec2",
323 "unsupported",
324 )),
325 }
326 }
327}
328
329pub trait Node: Send + Sync {
330 fn id(&self) -> NodeId;
331 fn input_port_defs(&self) -> &'static [InputPortDef];
332 fn output_port_defs(&self) -> &'static [OutputPortDef] {
333 SINGLE_RASTER_OUTPUT
334 }
335}
336
337pub trait PropertyEval {
338 fn get_property(&self, id: &str) -> crate::Result<Option<NodeProperty>>;
339}
340
341pub const SINGLE_RASTER_OUTPUT: &[OutputPortDef] = &[OutputPortDef {
342 name: "output",
343 kind: PortKind::Raster,
344}];
345
346#[derive(Debug, Clone, PartialEq, Eq, Hash)]
347pub struct PortRef {
348 pub id: NodeId,
349 pub port: String,
350}
351
352impl PortRef {
353 pub fn new(id: NodeId, port: String) -> Self {
354 Self { id, port }
355 }
356
357 pub fn empty() -> Self {
358 Self {
359 id: NodeId::new(0),
360 port: String::new(),
361 }
362 }
363
364 pub fn is_empty(&self) -> bool {
365 self.id.0 == 0
366 }
367}
368
369#[derive(Debug)]
370pub enum NodeKind {
371 MediaIn(source::media_in::MediaIn),
372 SolidColor(source::solid_color::SolidColor),
373 Text(source::text::Text),
374 Path(vector::path::Path),
375 Shape(vector::shape::Shape),
376 Boolean(compositing::boolean::Boolean),
377 Merge(compositing::merge::Merge),
378 RasterMultiMerge(compositing::raster_multimerge::RasterMultiMerge),
379 AlphaPremultiply(processing::alpha_premultiply::AlphaPremultiply),
380 Blur(processing::blur::Blur),
381 ChannelShuffle(processing::channel_shuffle::ChannelShuffle),
382 ColorGrade(processing::color_grade::ColorGrade),
383 Curves(processing::curves::Curves),
384 Exposure(processing::exposure::Exposure),
385 HueSaturation(processing::hue_saturation::HueSaturation),
386 Levels(processing::levels::Levels),
387 Memo(processing::memo::Memo),
388 TimeRemap(processing::time_remap::TimeRemap),
389 Transform(processing::transform::Transform),
390 Crop(processing::crop::Crop),
391 Resize(processing::resize::Resize),
392 Shadow(processing::shadow::Shadow),
393 WgslShader(processing::wgsl_shader::WgslShader),
394 Switch(compositing::switch::Switch),
395 MediaOutput(media_output::MediaOutput),
396}
397
398impl NodeKind {
399 pub fn id(&self) -> NodeId {
400 match self {
401 Self::MediaIn(node) => node.id,
402 Self::SolidColor(node) => node.id,
403 Self::Text(node) => node.id,
404 Self::Path(node) => node.id,
405 Self::Shape(node) => node.id,
406 Self::Boolean(node) => node.id,
407 Self::Merge(node) => node.id,
408 Self::RasterMultiMerge(node) => node.id,
409 Self::AlphaPremultiply(node) => node.id,
410 Self::Blur(node) => node.id,
411 Self::ChannelShuffle(node) => node.id,
412 Self::ColorGrade(node) => node.id,
413 Self::Curves(node) => node.id,
414 Self::Exposure(node) => node.id,
415 Self::HueSaturation(node) => node.id,
416 Self::Levels(node) => node.id,
417 Self::Memo(node) => node.id,
418 Self::TimeRemap(node) => node.id,
419 Self::Transform(node) => node.id,
420 Self::Crop(node) => node.id,
421 Self::Resize(node) => node.id,
422 Self::Shadow(node) => node.id,
423 Self::WgslShader(node) => node.id,
424 Self::Switch(node) => node.id,
425 Self::MediaOutput(node) => node.id,
426 }
427 }
428
429 pub fn as_property_eval(&self) -> &dyn PropertyEval {
430 self
431 }
432
433 #[cfg(feature = "metadata")]
434 pub fn schemas() -> Vec<NodeSchemaDef> {
435 vec![
436 source::media_in::MediaIn::schema(),
437 source::solid_color::SolidColor::schema(),
438 source::text::Text::schema(),
439 vector::path::Path::schema(),
440 vector::shape::Shape::schema(),
441 compositing::boolean::Boolean::schema(),
442 compositing::merge::Merge::schema(),
443 compositing::raster_multimerge::RasterMultiMerge::schema(),
444 compositing::switch::Switch::schema(),
445 processing::memo::Memo::schema(),
446 processing::alpha_premultiply::AlphaPremultiply::schema(),
447 processing::blur::Blur::schema(),
448 processing::channel_shuffle::ChannelShuffle::schema(),
449 processing::color_grade::ColorGrade::schema(),
450 processing::curves::Curves::schema(),
451 processing::exposure::Exposure::schema(),
452 processing::hue_saturation::HueSaturation::schema(),
453 processing::levels::Levels::schema(),
454 processing::time_remap::TimeRemap::schema(),
455 processing::transform::Transform::schema(),
456 processing::crop::Crop::schema(),
457 processing::resize::Resize::schema(),
458 processing::shadow::Shadow::schema(),
459 processing::wgsl_shader::WgslShader::schema(),
460 media_output::MediaOutput::schema(),
461 ]
462 }
463}
464
465impl Node for NodeKind {
466 fn id(&self) -> NodeId {
467 self.id()
468 }
469
470 fn input_port_defs(&self) -> &'static [InputPortDef] {
471 match self {
472 Self::MediaIn(node) => node.input_port_defs(),
473 Self::SolidColor(node) => node.input_port_defs(),
474 Self::Text(node) => node.input_port_defs(),
475 Self::Path(node) => node.input_port_defs(),
476 Self::Shape(node) => node.input_port_defs(),
477 Self::Boolean(node) => node.input_port_defs(),
478 Self::Merge(node) => node.input_port_defs(),
479 Self::RasterMultiMerge(node) => node.input_port_defs(),
480 Self::AlphaPremultiply(node) => node.input_port_defs(),
481 Self::Blur(node) => node.input_port_defs(),
482 Self::ChannelShuffle(node) => node.input_port_defs(),
483 Self::ColorGrade(node) => node.input_port_defs(),
484 Self::Curves(node) => node.input_port_defs(),
485 Self::Exposure(node) => node.input_port_defs(),
486 Self::HueSaturation(node) => node.input_port_defs(),
487 Self::Levels(node) => node.input_port_defs(),
488 Self::Memo(node) => node.input_port_defs(),
489 Self::TimeRemap(node) => node.input_port_defs(),
490 Self::Transform(node) => node.input_port_defs(),
491 Self::Crop(node) => node.input_port_defs(),
492 Self::Resize(node) => node.input_port_defs(),
493 Self::Shadow(node) => node.input_port_defs(),
494 Self::WgslShader(node) => node.input_port_defs(),
495 Self::Switch(node) => node.input_port_defs(),
496 Self::MediaOutput(node) => node.input_port_defs(),
497 }
498 }
499
500 fn output_port_defs(&self) -> &'static [OutputPortDef] {
501 SINGLE_RASTER_OUTPUT
502 }
503}
504
505impl PropertyEval for NodeKind {
506 fn get_property(&self, id: &str) -> crate::Result<Option<NodeProperty>> {
507 match self {
508 Self::MediaIn(node) => node.get_property(id),
509 Self::SolidColor(node) => node.get_property(id),
510 Self::Text(node) => node.get_property(id),
511 Self::Path(node) => node.get_property(id),
512 Self::Shape(node) => node.get_property(id),
513 Self::Boolean(node) => node.get_property(id),
514 Self::Merge(node) => node.get_property(id),
515 Self::RasterMultiMerge(node) => node.get_property(id),
516 Self::AlphaPremultiply(node) => node.get_property(id),
517 Self::Blur(node) => node.get_property(id),
518 Self::ChannelShuffle(node) => node.get_property(id),
519 Self::ColorGrade(node) => node.get_property(id),
520 Self::Curves(node) => node.get_property(id),
521 Self::Exposure(node) => node.get_property(id),
522 Self::HueSaturation(node) => node.get_property(id),
523 Self::Levels(node) => node.get_property(id),
524 Self::Memo(node) => node.get_property(id),
525 Self::TimeRemap(node) => node.get_property(id),
526 Self::Transform(node) => node.get_property(id),
527 Self::Crop(node) => node.get_property(id),
528 Self::Resize(node) => node.get_property(id),
529 Self::Shadow(node) => node.get_property(id),
530 Self::WgslShader(node) => node.get_property(id),
531 Self::Switch(node) => node.get_property(id),
532 Self::MediaOutput(node) => node.get_property(id),
533 }
534 }
535}