feat: download top-5 cast photos during TMDb enrichment
Some checks failed
CI / Check / Test (push) Failing after 41s

This commit is contained in:
2026-06-03 00:42:25 +02:00
parent a5cf62e281
commit c842ad6a55
3 changed files with 64 additions and 12 deletions

View File

@@ -8,8 +8,8 @@ use domain::{
events::DomainEvent, events::DomainEvent,
models::{CastMember, CrewMember, Genre, Keyword, MovieProfile}, models::{CastMember, CrewMember, Genre, Keyword, MovieProfile},
ports::{ ports::{
EventHandler, MovieEnrichmentClient, MovieProfileRepository, MovieRepository, EventHandler, ImageStorage, MovieEnrichmentClient, MovieProfileRepository,
PersonCommand, SearchCommand, MovieRepository, PersonCommand, SearchCommand,
}, },
value_objects::MovieId, value_objects::MovieId,
}; };
@@ -229,6 +229,52 @@ pub struct EnrichmentHandler {
pub profile_repo: Arc<dyn MovieProfileRepository>, pub profile_repo: Arc<dyn MovieProfileRepository>,
pub person_command: Arc<dyn PersonCommand>, pub person_command: Arc<dyn PersonCommand>,
pub search_command: Arc<dyn SearchCommand>, pub search_command: Arc<dyn SearchCommand>,
pub image_storage: Arc<dyn ImageStorage>,
http: reqwest::Client,
}
impl EnrichmentHandler {
pub fn new(
enrichment_client: Arc<dyn MovieEnrichmentClient>,
movie_repository: Arc<dyn MovieRepository>,
profile_repo: Arc<dyn MovieProfileRepository>,
person_command: Arc<dyn PersonCommand>,
search_command: Arc<dyn SearchCommand>,
image_storage: Arc<dyn ImageStorage>,
) -> Self {
Self {
enrichment_client,
movie_repository,
profile_repo,
person_command,
search_command,
image_storage,
http: reqwest::Client::new(),
}
}
async fn download_cast_photos(&self, profile: &MovieProfile) {
for member in profile.cast.iter().take(5) {
let Some(ref path) = member.profile_path else {
continue;
};
let key = format!("cast{path}");
if self.image_storage.get(&key).await.is_ok() {
continue;
}
let url = format!("https://image.tmdb.org/t/p/w185{path}");
match self.http.get(&url).send().await {
Ok(resp) if resp.status().is_success() => {
if let Ok(bytes) = resp.bytes().await {
if let Err(e) = self.image_storage.store(&key, &bytes).await {
tracing::debug!("cast photo store failed for {path}: {e}");
}
}
}
_ => tracing::debug!("cast photo download failed for {path}"),
}
}
}
} }
#[async_trait] #[async_trait]
@@ -263,6 +309,7 @@ impl EventHandler for EnrichmentHandler {
.await .await
{ {
Ok(profile) => { Ok(profile) => {
self.download_cast_photos(&profile).await;
enrich_movie::execute( enrich_movie::execute(
&self.movie_repository, &self.movie_repository,
&self.profile_repo, &self.profile_repo,

View File

@@ -2,7 +2,7 @@ use crate::context::AppContext;
use crate::wrapup::{compute, queries::ComputeWrapUpQuery}; use crate::wrapup::{compute, queries::ComputeWrapUpQuery};
use domain::errors::DomainError; use domain::errors::DomainError;
use domain::events::DomainEvent; use domain::events::DomainEvent;
use domain::models::wrapup::{DateRange, WrapUpReport, WrapUpScope, WrapUpStatus}; use domain::models::wrapup::{DateRange, WrapUpScope, WrapUpStatus};
use domain::ports::{VideoRenderAssets, VideoRenderConfig}; use domain::ports::{VideoRenderAssets, VideoRenderConfig};
use domain::value_objects::WrapUpId; use domain::value_objects::WrapUpId;
@@ -41,8 +41,12 @@ pub async fn execute(
if let Some(ref renderer) = ctx.services.video_renderer { if let Some(ref renderer) = ctx.services.video_renderer {
let poster_images = resolve_images(ctx, &report.poster_paths, "poster").await; let poster_images = resolve_images(ctx, &report.poster_paths, "poster").await;
let cast_images = let cast_keys: Vec<String> = report
resolve_images(ctx, &report.top_cast_profile_paths, "cast").await; .top_cast_profile_paths
.iter()
.map(|p| format!("cast{p}"))
.collect();
let cast_images = resolve_images(ctx, &cast_keys, "cast").await;
let wc = &ctx.config.wrapup; let wc = &ctx.config.wrapup;
let config = VideoRenderConfig { let config = VideoRenderConfig {
slide_duration_secs: 4, slide_duration_secs: 4,

View File

@@ -127,13 +127,14 @@ async fn main() -> anyhow::Result<()> {
match tmdb_enrichment::TmdbEnrichmentClient::from_env() { match tmdb_enrichment::TmdbEnrichmentClient::from_env() {
Ok(client) => { Ok(client) => {
tracing::info!("TMDb enrichment enabled"); tracing::info!("TMDb enrichment enabled");
let handler = Arc::new(tmdb_enrichment::EnrichmentHandler { let handler = Arc::new(tmdb_enrichment::EnrichmentHandler::new(
enrichment_client: Arc::new(client), Arc::new(client),
movie_repository: Arc::clone(&ctx.repos.movie), Arc::clone(&ctx.repos.movie),
profile_repo: Arc::clone(&ctx.repos.movie_profile), Arc::clone(&ctx.repos.movie_profile),
person_command: Arc::clone(&ctx.repos.person_command), Arc::clone(&ctx.repos.person_command),
search_command: Arc::clone(&ctx.repos.search_command), Arc::clone(&ctx.repos.search_command),
}) as Arc<dyn EventHandler>; Arc::clone(&ctx.services.image_storage),
)) as Arc<dyn EventHandler>;
let job = Arc::new(application::jobs::EnrichmentStalenessJob::new(ctx.clone())) let job = Arc::new(application::jobs::EnrichmentStalenessJob::new(ctx.clone()))
as Arc<dyn PeriodicJob>; as Arc<dyn PeriodicJob>;
(Some(handler), Some(job)) (Some(handler), Some(job))