lumen_server/render/
mod.rs1mod 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}