lumen_server/service/
executor.rs1use 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}