Skip to main content

lumen_server/render/
mod.rs

1mod encoder;
2#[cfg(all(target_os = "linux", feature = "cuda", feature = "vulkan"))]
3mod frame_timing;
4mod gpu;
5mod media;
6
7use std::path::PathBuf;
8
9use lumen_engine::composition::Composition;
10
11pub use gpu::render_project_mp4;
12
13#[derive(Debug, Clone)]
14pub struct RenderError {
15    pub code: &'static str,
16    pub message: String,
17    pub retryable: bool,
18}
19
20impl std::fmt::Display for RenderError {
21    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
22        write!(f, "{}: {}", self.code, self.message)
23    }
24}
25
26impl std::error::Error for RenderError {}
27
28#[derive(Debug, Clone)]
29pub struct RenderProgress {
30    pub stage: &'static str,
31    pub frame: u32,
32    pub total_frames: u32,
33    pub ratio: f32,
34}
35
36#[derive(Debug, Clone, Default)]
37pub struct RenderOptions {
38    pub media_root: Option<PathBuf>,
39    pub verbose_debug: bool,
40    pub video_encoder: Option<String>,
41}
42
43#[derive(Debug, Clone)]
44pub struct ProjectInfo {
45    pub width: u32,
46    pub height: u32,
47    pub duration_frames: u32,
48}
49
50pub struct ProjectBundle {
51    pub project: ProjectInfo,
52    pub composition: Composition,
53}
54
55pub fn convert_project_payload(payload: &serde_json::Value) -> Result<ProjectBundle, RenderError> {
56    let project = serde_json::to_string(payload).map_err(|err| RenderError {
57        code: "invalid_project_payload",
58        message: err.to_string(),
59        retryable: false,
60    })?;
61
62    let composition = lumen_engine::json::parse(&project).map_err(|err| RenderError {
63        code: "invalid_project_payload",
64        message: err.to_string(),
65        retryable: false,
66    })?;
67    Ok(ProjectBundle {
68        project: ProjectInfo {
69            width: composition.render_settings.width,
70            height: composition.render_settings.height,
71            duration_frames: composition.timeline.duration_frames,
72        },
73        composition,
74    })
75}
76
77#[cfg(test)]
78mod tests {
79    use serde_json::json;
80
81    use super::convert_project_payload;
82
83    #[test]
84    fn convert_current_composition_preserves_duration_frames() {
85        let payload = json!({
86            "timeline": {
87                "fps": 24,
88                "duration_frames": 120
89            },
90            "render_settings": {
91                "width": 64,
92                "height": 64,
93                "background_color": [0, 0, 0, 255]
94            },
95            "nodes": [
96                {
97                    "id": 1,
98                    "type": "background",
99                    "properties": {
100                        "paint": [255, 255, 255, 255],
101                        "width": 64,
102                        "height": 64
103                    }
104                },
105                {
106                    "id": 2,
107                    "type": "media_output",
108                    "properties": {}
109                }
110            ],
111            "connections": [
112                {
113                    "from_node": 1,
114                    "from_port": "output",
115                    "to_node": 2,
116                    "to_port": "source"
117                }
118            ]
119        });
120
121        let bundle = convert_project_payload(&payload).expect("project bundle");
122        assert_eq!(bundle.project.duration_frames, 120);
123    }
124}