feat: wire video renderer pipeline + download endpoint
Some checks failed
CI / Check / Test (push) Failing after 41s

This commit is contained in:
2026-06-02 22:34:55 +02:00
parent d45d8aa913
commit 490bd97a40
10 changed files with 79 additions and 2 deletions

View File

@@ -6,7 +6,8 @@ use domain::ports::{
MovieRepository, PasswordHasher, PersonCommand, PersonQuery, PosterFetcherClient,
RemoteWatchlistRepository, ReviewRepository, SearchCommand, SearchPort, SocialQueryPort,
StatsRepository, UserProfileFieldsRepository, UserRepository, WatchEventRepository,
WatchlistRepository, WrapUpRepository, WrapUpStatsQuery, WebhookTokenRepository,
WatchlistRepository, WrapUpRepository, WrapUpStatsQuery, WrapUpVideoRenderer,
WebhookTokenRepository,
};
use crate::config::AppConfig;
@@ -45,6 +46,7 @@ pub struct Services {
pub event_publisher: Arc<dyn EventPublisher>,
pub diary_exporter: Arc<dyn DiaryExporter>,
pub document_parser: Arc<dyn DocumentParser>,
pub video_renderer: Option<Arc<dyn WrapUpVideoRenderer>>,
}
#[derive(Clone)]

View File

@@ -166,6 +166,7 @@ impl TestContextBuilder {
event_publisher: self.event_publisher,
diary_exporter: self.diary_exporter,
document_parser: self.document_parser,
video_renderer: None,
},
config: self.config,
}

View File

@@ -2,7 +2,8 @@ use crate::context::AppContext;
use crate::wrapup::{compute, queries::ComputeWrapUpQuery};
use domain::errors::DomainError;
use domain::events::DomainEvent;
use domain::models::wrapup::{DateRange, WrapUpScope, WrapUpStatus};
use domain::models::wrapup::{DateRange, WrapUpReport, WrapUpScope, WrapUpStatus};
use domain::ports::VideoRenderConfig;
use domain::value_objects::WrapUpId;
pub async fn execute(
@@ -34,6 +35,29 @@ pub async fn execute(
let json = serde_json::to_string(&report)
.map_err(|e| DomainError::InfrastructureError(e.to_string()))?;
ctx.repos.wrapup_repo.set_complete(&wrapup_id, &json).await?;
// Optionally render video (non-fatal)
if let Some(ref renderer) = ctx.services.video_renderer {
let poster_images = resolve_poster_images(ctx, &report).await;
let config = VideoRenderConfig {
slide_duration_secs: 4,
transition_duration_secs: 0.8,
resolution: (1080, 1920),
ffmpeg_path: "ffmpeg".to_string(),
};
match renderer.render(&report, poster_images, &config).await {
Ok(video_bytes) => {
let video_key = format!("wrapups/{}/video.mp4", wrapup_id.value());
if let Err(e) = ctx.services.image_storage.store(&video_key, &video_bytes).await {
tracing::warn!("failed to store wrapup video: {e}");
}
}
Err(e) => {
tracing::warn!("video render failed (non-fatal): {e}");
}
}
}
ctx.services
.event_publisher
.publish(&DomainEvent::WrapUpCompleted { wrapup_id })
@@ -49,3 +73,14 @@ pub async fn execute(
}
}
}
async fn resolve_poster_images(ctx: &AppContext, report: &WrapUpReport) -> Vec<(String, Vec<u8>)> {
let mut images = Vec::new();
for path in report.poster_paths.iter().take(20) {
match ctx.services.image_storage.get(path).await {
Ok(bytes) => images.push((path.clone(), bytes)),
Err(_) => {}
}
}
images
}