Skip to main content

lumen_server/service/
executor.rs

1use std::time::Instant;
2
3use crate::render::{RenderOptions, convert_project_payload, render_project_mp4};
4
5use super::{
6    BoxFuture, ProgressEvent, ProgressSink, RenderExecutor, RenderJob, RenderMetrics, RenderOutput,
7    ServiceError, ServiceResult,
8};
9
10#[derive(Debug, Default)]
11pub struct LocalRenderExecutor;
12
13impl RenderExecutor for LocalRenderExecutor {
14    fn execute<'a>(
15        &'a self,
16        job: RenderJob,
17        progress: &'a dyn ProgressSink,
18    ) -> BoxFuture<'a, ServiceResult<RenderOutput>> {
19        Box::pin(async move {
20            let bundle = convert_project_payload(&job.project).map_err(ServiceError::from)?;
21            let total_frames = bundle.project.duration_frames;
22            let options = RenderOptions {
23                media_root: job.media_root,
24                verbose_debug: false,
25                video_encoder: job.video_encoder,
26            };
27            let job_id = job.id.clone();
28            let started = Instant::now();
29            progress
30                .publish(ProgressEvent {
31                    job_id: job_id.clone(),
32                    stage: "accepted".to_string(),
33                    ratio: 0.0,
34                    frame: None,
35                    total_frames: Some(total_frames),
36                })
37                .await?;
38            let rendered = tokio::task::spawn_blocking(move || {
39                let mut ignore_progress = |_event| {};
40                render_project_mp4(&bundle, &options, &mut ignore_progress)
41            })
42            .await
43            .map_err(|err| ServiceError {
44                code: "render_worker_failed",
45                message: format!("render worker join failed: {err}"),
46                retryable: true,
47            })?
48            .map_err(ServiceError::from)?;
49            progress
50                .publish(ProgressEvent {
51                    job_id,
52                    stage: "completed".to_string(),
53                    ratio: 1.0,
54                    frame: Some(total_frames),
55                    total_frames: Some(total_frames),
56                })
57                .await?;
58
59            Ok(RenderOutput {
60                bytes: rendered,
61                content_type: "video/mp4",
62                metrics: RenderMetrics {
63                    render_ms: started.elapsed().as_millis(),
64                    total_frames: total_frames as u64,
65                },
66            })
67        })
68    }
69}