diff --git a/crates/adapters/wrapup-renderer/src/charts.rs b/crates/adapters/wrapup-renderer/src/charts.rs index 9170908..c9483d1 100644 --- a/crates/adapters/wrapup-renderer/src/charts.rs +++ b/crates/adapters/wrapup-renderer/src/charts.rs @@ -10,17 +10,11 @@ pub fn render_genre_chart( let mut buf = vec![0u8; (width * height * 3) as usize]; { - let root = - BitMapBackend::with_buffer(&mut buf, (width, height)).into_drawing_area(); + let root = BitMapBackend::with_buffer(&mut buf, (width, height)).into_drawing_area(); root.fill(&RGBColor(26, 26, 36)) .map_err(|e| DomainError::InfrastructureError(e.to_string()))?; - let max_count = report - .top_genres - .iter() - .map(|g| g.count) - .max() - .unwrap_or(1); + let max_count = report.top_genres.iter().map(|g| g.count).max().unwrap_or(1); let mut chart = ChartBuilder::on(&root) .margin(40) @@ -36,7 +30,7 @@ pub fn render_genre_chart( .configure_mesh() .disable_mesh() .label_style(("sans-serif", 14, &WHITE)) - .axis_style(&RGBColor(100, 100, 100)) + .axis_style(RGBColor(100, 100, 100)) .draw() .map_err(|e| DomainError::InfrastructureError(e.to_string()))?; diff --git a/crates/adapters/wrapup-renderer/src/ffmpeg.rs b/crates/adapters/wrapup-renderer/src/ffmpeg.rs index 1871f9f..9e7dfc8 100644 --- a/crates/adapters/wrapup-renderer/src/ffmpeg.rs +++ b/crates/adapters/wrapup-renderer/src/ffmpeg.rs @@ -6,14 +6,12 @@ pub async fn stitch_slides( slides: &[Vec], config: &VideoRenderConfig, ) -> Result, DomainError> { - let dir = - tempfile::tempdir().map_err(|e| DomainError::InfrastructureError(e.to_string()))?; + let dir = tempfile::tempdir().map_err(|e| DomainError::InfrastructureError(e.to_string()))?; // Write slide PNGs for (i, png) in slides.iter().enumerate() { let path = dir.path().join(format!("slide_{:04}.png", i)); - std::fs::write(&path, png) - .map_err(|e| DomainError::InfrastructureError(e.to_string()))?; + std::fs::write(&path, png).map_err(|e| DomainError::InfrastructureError(e.to_string()))?; } let output_path = dir.path().join("output.mp4"); diff --git a/crates/adapters/wrapup-renderer/src/lib.rs b/crates/adapters/wrapup-renderer/src/lib.rs index 93b53f2..daadc61 100644 --- a/crates/adapters/wrapup-renderer/src/lib.rs +++ b/crates/adapters/wrapup-renderer/src/lib.rs @@ -7,6 +7,7 @@ use domain::errors::DomainError; use domain::models::wrapup::WrapUpReport; use domain::ports::{VideoRenderConfig, WrapUpVideoRenderer}; +#[derive(Default)] pub struct FfmpegWrapUpRenderer; impl FfmpegWrapUpRenderer { @@ -25,10 +26,8 @@ impl WrapUpVideoRenderer for FfmpegWrapUpRenderer { ) -> Result, DomainError> { let (width, height) = config.resolution; - let renderer = slides::SlideRenderer::new( - config.font_path.as_deref(), - config.logo_path.as_deref(), - )?; + let renderer = + slides::SlideRenderer::new(config.font_path.as_deref(), config.logo_path.as_deref())?; // 1. Generate slide images let mut slide_pngs = Vec::new(); diff --git a/crates/adapters/wrapup-renderer/src/slides.rs b/crates/adapters/wrapup-renderer/src/slides.rs index a4b48f2..e577ee9 100644 --- a/crates/adapters/wrapup-renderer/src/slides.rs +++ b/crates/adapters/wrapup-renderer/src/slides.rs @@ -144,7 +144,13 @@ impl SlideRenderer { } // rating distribution bars - let max_count = report.rating_distribution.iter().copied().max().unwrap_or(1).max(1); + let max_count = report + .rating_distribution + .iter() + .copied() + .max() + .unwrap_or(1) + .max(1); let bar_area_top = (h / 2) as i32; let bar_h = 36u32; let bar_gap = 16u32; @@ -165,11 +171,7 @@ impl SlideRenderer { // filled bar let fill_w = ((count as f32 / max_count as f32) * max_bar_w as f32) as u32; if fill_w > 0 { - draw_filled_rect_mut( - &mut img, - Rect::at(margin_x, y).of_size(fill_w, bar_h), - GOLD, - ); + draw_filled_rect_mut(&mut img, Rect::at(margin_x, y).of_size(fill_w, bar_h), GOLD); } // count label let count_s = count.to_string(); @@ -336,11 +338,8 @@ fn fill(w: u32, h: u32) -> RgbaImage { fn to_png(img: &RgbaImage) -> Result, DomainError> { let mut buf = Vec::new(); - img.write_to( - &mut std::io::Cursor::new(&mut buf), - image::ImageFormat::Png, - ) - .map_err(|e| DomainError::InfrastructureError(e.to_string()))?; + img.write_to(&mut std::io::Cursor::new(&mut buf), image::ImageFormat::Png) + .map_err(|e| DomainError::InfrastructureError(e.to_string()))?; Ok(buf) } @@ -355,11 +354,11 @@ fn load_system_font() -> Result { "/System/Library/Fonts/Helvetica.ttc", ]; for path in &candidates { - if let Ok(bytes) = std::fs::read(path) { - if let Ok(font) = FontArc::try_from_vec(bytes) { - tracing::info!("loaded system font: {path}"); - return Ok(font); - } + if let Ok(bytes) = std::fs::read(path) + && let Ok(font) = FontArc::try_from_vec(bytes) + { + tracing::info!("loaded system font: {path}"); + return Ok(font); } } Err(DomainError::InfrastructureError( diff --git a/crates/application/src/wrapup/compute.rs b/crates/application/src/wrapup/compute.rs index 14a3f9c..de2ffda 100644 --- a/crates/application/src/wrapup/compute.rs +++ b/crates/application/src/wrapup/compute.rs @@ -22,7 +22,11 @@ pub async fn execute( Ok(build_report(query.scope, query.date_range, &rows)) } -fn build_report(scope: WrapUpScope, date_range: DateRange, rows: &[WrapUpMovieRow]) -> WrapUpReport { +fn build_report( + scope: WrapUpScope, + date_range: DateRange, + rows: &[WrapUpMovieRow], +) -> WrapUpReport { let total_movies = rows.len() as u32; let total_watch_time_minutes: u32 = rows.iter().filter_map(|r| r.runtime_minutes).sum(); @@ -179,7 +183,10 @@ fn compute_busiest_day(rows: &[WrapUpMovieRow]) -> Option { } fn compute_runtime_extremes(rows: &[WrapUpMovieRow]) -> (Option, Option) { - let with_runtime: Vec<_> = rows.iter().filter(|r| r.runtime_minutes.is_some()).collect(); + let with_runtime: Vec<_> = rows + .iter() + .filter(|r| r.runtime_minutes.is_some()) + .collect(); let longest = with_runtime .iter() .max_by_key(|r| r.runtime_minutes.unwrap_or(0)) @@ -192,22 +199,20 @@ fn compute_runtime_extremes(rows: &[WrapUpMovieRow]) -> (Option, Optio } fn compute_rating_extremes(rows: &[WrapUpMovieRow]) -> (Option, Option) { - let highest = rows.iter().max_by_key(|r| r.rating).map(|r| movie_ref(r)); - let lowest = rows.iter().min_by_key(|r| r.rating).map(|r| movie_ref(r)); + let highest = rows.iter().max_by_key(|r| r.rating).map(movie_ref); + let lowest = rows.iter().min_by_key(|r| r.rating).map(movie_ref); (highest, lowest) } -fn compute_chronological_extremes( - rows: &[WrapUpMovieRow], -) -> (Option, Option) { +fn compute_chronological_extremes(rows: &[WrapUpMovieRow]) -> (Option, Option) { let first = rows .iter() .min_by_key(|r| r.watched_at) - .map(|r| movie_ref(r)); + .map(movie_ref); let last = rows .iter() .max_by_key(|r| r.watched_at) - .map(|r| movie_ref(r)); + .map(movie_ref); (first, last) } @@ -215,11 +220,11 @@ fn compute_year_extremes(rows: &[WrapUpMovieRow]) -> (Option, Option (Vec, u32) { } }) .collect(); - stats.sort_by(|a, b| b.count.cmp(&a.count).then(b.avg_rating.total_cmp(&a.avg_rating))); + stats.sort_by(|a, b| { + b.count + .cmp(&a.count) + .then(b.avg_rating.total_cmp(&a.avg_rating)) + }); stats.truncate(5); (stats, diversity) } @@ -257,10 +266,7 @@ fn compute_actor_stats(rows: &[WrapUpMovieRow]) -> (Vec, u32, Vec (Vec, u32, Vec = stats .iter() @@ -316,7 +326,7 @@ fn compute_genre_stats( } }) .collect(); - stats.sort_by(|a, b| b.count.cmp(&a.count)); + stats.sort_by_key(|s| std::cmp::Reverse(s.count)); let highest = stats .iter() .max_by(|a, b| a.avg_rating.total_cmp(&b.avg_rating)) @@ -341,7 +351,7 @@ fn compute_keyword_stats(rows: &[WrapUpMovieRow]) -> Vec { .into_iter() .map(|(keyword, count)| KeywordStat { keyword, count }) .collect(); - stats.sort_by(|a, b| b.count.cmp(&a.count)); + stats.sort_by_key(|s| std::cmp::Reverse(s.count)); stats.truncate(20); stats } @@ -371,7 +381,7 @@ fn compute_language_stats(rows: &[WrapUpMovieRow]) -> Vec { .into_iter() .map(|(language, count)| LangStat { language, count }) .collect(); - stats.sort_by(|a, b| b.count.cmp(&a.count)); + stats.sort_by_key(|s| std::cmp::Reverse(s.count)); stats } diff --git a/crates/application/src/wrapup/handle_requested.rs b/crates/application/src/wrapup/handle_requested.rs index 94403fd..8106bee 100644 --- a/crates/application/src/wrapup/handle_requested.rs +++ b/crates/application/src/wrapup/handle_requested.rs @@ -34,7 +34,10 @@ pub async fn execute( Ok(report) => { 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?; + ctx.repos + .wrapup_repo + .set_complete(&wrapup_id, &json) + .await?; // Optionally render video (non-fatal) if let Some(ref renderer) = ctx.services.video_renderer { @@ -51,7 +54,12 @@ pub async fn execute( 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 { + if let Err(e) = ctx + .services + .image_storage + .store(&video_key, &video_bytes) + .await + { tracing::warn!("failed to store wrapup video: {e}"); } } @@ -80,9 +88,8 @@ pub async fn execute( async fn resolve_poster_images(ctx: &AppContext, report: &WrapUpReport) -> Vec<(String, Vec)> { 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(_) => {} + if let Ok(bytes) = ctx.services.image_storage.get(path).await { + images.push((path.clone(), bytes)); } } images