remove wrapup video rendering (ffmpeg)
All checks were successful
CI / Check / Test (push) Successful in 15m34s

SPA handles wrapup visuals client-side; server-side
renderer was dead code pulling in ffmpeg + image crates.
This commit is contained in:
2026-06-09 00:36:44 +02:00
parent f4fd915e35
commit 30a6200b5b
31 changed files with 27 additions and 1585 deletions

View File

@@ -11,8 +11,6 @@ pub struct WrapUpConfig {
pub font_path: Option<String>,
pub logo_path: Option<String>,
pub bg_dir: Option<String>,
pub ffmpeg_path: String,
pub max_concurrent_renders: usize,
}
impl AppConfig {
@@ -41,11 +39,6 @@ impl WrapUpConfig {
font_path: std::env::var("WRAPUP_FONT_PATH").ok(),
logo_path: std::env::var("WRAPUP_LOGO_PATH").ok(),
bg_dir: std::env::var("WRAPUP_BG_DIR").ok(),
ffmpeg_path: std::env::var("FFMPEG_PATH").unwrap_or_else(|_| "ffmpeg".to_string()),
max_concurrent_renders: std::env::var("WRAPUP_MAX_CONCURRENT")
.ok()
.and_then(|v| v.parse().ok())
.unwrap_or(2),
}
}
}

View File

@@ -7,7 +7,7 @@ use domain::ports::{
PosterFetcherClient, RemoteGoalRepository, RemoteWatchlistRepository, ReviewRepository,
SearchCommand, SearchPort, SocialQueryPort, StatsRepository, UserProfileFieldsRepository,
UserRepository, UserSettingsRepository, WatchEventRepository, WatchlistRepository,
WebhookTokenRepository, WrapUpRepository, WrapUpStatsQuery, WrapUpVideoRenderer,
WebhookTokenRepository, WrapUpRepository, WrapUpStatsQuery,
};
use crate::config::AppConfig;
@@ -49,7 +49,6 @@ 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

@@ -102,8 +102,6 @@ impl TestContextBuilder {
font_path: None,
logo_path: None,
bg_dir: None,
ffmpeg_path: "ffmpeg".into(),
max_concurrent_renders: 2,
},
},
}
@@ -185,7 +183,6 @@ 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,6 @@ use domain::errors::DomainError;
use domain::value_objects::WrapUpId;
use crate::context::AppContext;
use crate::wrapup::storage::WrapUpStorage;
pub async fn execute(ctx: &AppContext, id: WrapUpId) -> Result<(), DomainError> {
ctx.repos
@@ -11,8 +10,5 @@ pub async fn execute(ctx: &AppContext, id: WrapUpId) -> Result<(), DomainError>
.await?
.ok_or_else(|| DomainError::NotFound("wrap-up not found".into()))?;
let storage = WrapUpStorage::new(ctx.services.object_storage.clone());
let _ = storage.delete_video(&id).await;
ctx.repos.wrapup_repo.delete(&id).await
}

View File

@@ -15,10 +15,9 @@ pub struct WrapUpEventHandler {
impl WrapUpEventHandler {
pub fn new(ctx: AppContext) -> Self {
let max = ctx.config.wrapup.max_concurrent_renders;
Self {
ctx,
semaphore: Arc::new(Semaphore::new(max)),
semaphore: Arc::new(Semaphore::new(2)),
}
}
}

View File

@@ -1,9 +1,8 @@
use crate::context::AppContext;
use crate::wrapup::{compute, queries::ComputeWrapUpQuery, storage::WrapUpStorage};
use crate::wrapup::{compute, queries::ComputeWrapUpQuery};
use domain::errors::DomainError;
use domain::events::DomainEvent;
use domain::models::wrapup::{DateRange, WrapUpScope, WrapUpStatus};
use domain::ports::VideoRenderAssets;
use domain::value_objects::WrapUpId;
pub async fn execute(
@@ -45,30 +44,6 @@ pub async fn execute(
.set_complete(&wrapup_id, &report)
.await?;
if let Some(ref renderer) = ctx.services.video_renderer {
let asset_storage = WrapUpStorage::new(ctx.services.object_storage.clone());
let poster_images = asset_storage
.resolve_poster_images(&report.poster_paths)
.await;
let cast_images = asset_storage
.resolve_cast_images(&report.top_cast_profile_paths)
.await;
let assets = VideoRenderAssets {
poster_images,
cast_images,
};
match renderer.render(&report, assets).await {
Ok(video_bytes) => {
if let Err(e) = asset_storage.store_video(&wrapup_id, &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 })

View File

@@ -7,4 +7,3 @@ pub mod get_wrapup;
pub mod handle_requested;
pub mod list_wrapups;
pub mod queries;
pub mod storage;

View File

@@ -1,58 +0,0 @@
use domain::errors::DomainError;
use domain::ports::ObjectStorage;
use domain::value_objects::WrapUpId;
use std::sync::Arc;
pub struct WrapUpStorage {
inner: Arc<dyn ObjectStorage>,
}
impl WrapUpStorage {
pub fn new(storage: Arc<dyn ObjectStorage>) -> Self {
Self { inner: storage }
}
pub async fn store_video(&self, id: &WrapUpId, bytes: &[u8]) -> Result<(), DomainError> {
let key = format!("wrapups/{}/video.mp4", id.value());
self.inner.store(&key, bytes).await?;
Ok(())
}
pub async fn delete_video(&self, id: &WrapUpId) -> Result<(), DomainError> {
let key = format!("wrapups/{}/video.mp4", id.value());
self.inner.delete(&key).await
}
pub fn cast_image_key(profile_path: &str) -> String {
format!("cast{profile_path}")
}
pub async fn resolve_cast_images(&self, profile_paths: &[String]) -> Vec<(String, Vec<u8>)> {
let mut images = Vec::new();
for path in profile_paths.iter().take(20) {
let key = Self::cast_image_key(path);
match self.inner.get(&key).await {
Ok(bytes) => images.push((key, bytes)),
Err(e) => tracing::debug!("cast fetch skipped for {key}: {e}"),
}
}
tracing::info!(
"resolved {}/{} cast images",
images.len(),
profile_paths.len()
);
images
}
pub async fn resolve_poster_images(&self, paths: &[String]) -> Vec<(String, Vec<u8>)> {
let mut images = Vec::new();
for path in paths.iter().take(20) {
match self.inner.get(path).await {
Ok(bytes) => images.push((path.clone(), bytes)),
Err(e) => tracing::debug!("poster fetch skipped for {path}: {e}"),
}
}
tracing::info!("resolved {}/{} poster images", images.len(), paths.len());
images
}
}