lumen-server 0.2.2

Embeddable render service primitives and local HTTP server for Lumen.
Documentation
use std::time::Instant;

use crate::render::{RenderOptions, convert_project_payload, render_project_mp4};

use super::{
    BoxFuture, ProgressEvent, ProgressSink, RenderExecutor, RenderJob, RenderMetrics, RenderOutput,
    ServiceError, ServiceResult,
};

#[derive(Debug, Default)]
pub struct LocalRenderExecutor;

impl RenderExecutor for LocalRenderExecutor {
    fn execute<'a>(
        &'a self,
        job: RenderJob,
        progress: &'a dyn ProgressSink,
    ) -> BoxFuture<'a, ServiceResult<RenderOutput>> {
        Box::pin(async move {
            let bundle = convert_project_payload(&job.project).map_err(ServiceError::from)?;
            let total_frames = bundle.project.duration_frames;
            let options = RenderOptions {
                media_root: job.media_root,
                verbose_debug: false,
                video_encoder: job.video_encoder,
            };
            let job_id = job.id.clone();
            let started = Instant::now();
            progress
                .publish(ProgressEvent {
                    job_id: job_id.clone(),
                    stage: "accepted".to_string(),
                    ratio: 0.0,
                    frame: None,
                    total_frames: Some(total_frames),
                })
                .await?;
            let rendered = tokio::task::spawn_blocking(move || {
                let mut ignore_progress = |_event| {};
                render_project_mp4(&bundle, &options, &mut ignore_progress)
            })
            .await
            .map_err(|err| ServiceError {
                code: "render_worker_failed",
                message: format!("render worker join failed: {err}"),
                retryable: true,
            })?
            .map_err(ServiceError::from)?;
            progress
                .publish(ProgressEvent {
                    job_id,
                    stage: "completed".to_string(),
                    ratio: 1.0,
                    frame: Some(total_frames),
                    total_frames: Some(total_frames),
                })
                .await?;

            Ok(RenderOutput {
                bytes: rendered,
                content_type: "video/mp4",
                metrics: RenderMetrics {
                    render_ms: started.elapsed().as_millis(),
                    total_frames: total_frames as u64,
                },
            })
        })
    }
}