diff --git a/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video_v2/providers/kinovi/seedance_2p0/draft.rs b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video_v2/providers/kinovi/seedance_2p0/draft.rs index dd48697ba0..1e32c643f7 100644 --- a/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video_v2/providers/kinovi/seedance_2p0/draft.rs +++ b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video_v2/providers/kinovi/seedance_2p0/draft.rs @@ -12,7 +12,6 @@ use crate::api::video_list_ref::VideoListRef; use crate::client::router_seedance2pro_client::RouterSeedance2ProClient; use crate::errors::artcraft_router_error::ArtcraftRouterError; use crate::errors::client_error::ClientError; -use crate::generate::generate_video::generate_video_request_builder::GenerateVideoRequestBuilder; use crate::generate::generate_video_v2::providers::kinovi::seedance_2p0::request::KinoviSeedance2p0RequestState; use crate::generate::generate_video_v2::providers::kinovi::upload::upload_to_seedance2pro; @@ -55,24 +54,21 @@ impl KinoviSeedance2p0DraftState { let mut reference_audio_urls = None; if let Some(remaining) = self.unhandled_request_state.take() { - start_frame_url = resolve_and_upload_image_ref( - session, remaining.start_frame, maybe_media_file_to_url_map, + let map = maybe_media_file_to_url_map; + + start_frame_url = resolve_and_upload_single(session, remaining.start_frame, map).await?; + end_frame_url = resolve_and_upload_single(session, remaining.end_frame, map).await?; + + reference_image_urls = resolve_and_upload_list( + session, remaining.reference_images.map(ImageListRef::into_urls_or_tokens), map, ).await?; - end_frame_url = resolve_and_upload_image_ref( - session, remaining.end_frame, maybe_media_file_to_url_map, + reference_video_urls = resolve_and_upload_list( + session, remaining.reference_videos.map(VideoListRef::into_urls_or_tokens), map, ).await?; - reference_image_urls = resolve_and_upload_image_list_ref( - session, remaining.reference_images, maybe_media_file_to_url_map, - ).await?; - - reference_video_urls = resolve_and_upload_video_list_ref( - session, remaining.reference_videos, maybe_media_file_to_url_map, - ).await?; - - reference_audio_urls = resolve_and_upload_audio_list_ref( - session, remaining.reference_audio, maybe_media_file_to_url_map, + reference_audio_urls = resolve_and_upload_list( + session, remaining.reference_audio.map(AudioListRef::into_urls_or_tokens), map, ).await?; // TODO: Handle remaining.reference_character_tokens @@ -98,49 +94,71 @@ impl KinoviSeedance2p0DraftState { } } +/// Either resolved URLs or unresolved media file tokens. +enum UrlsOrTokens { + Urls(Vec), + Tokens(Vec), +} + +// Allow each list ref type to decompose into this common representation. +impl ImageListRef { + fn into_urls_or_tokens(self) -> UrlsOrTokens { + match self { + Self::Urls(urls) => UrlsOrTokens::Urls(urls), + Self::MediaFileTokens(tokens) => UrlsOrTokens::Tokens(tokens), + } + } +} + +impl VideoListRef { + fn into_urls_or_tokens(self) -> UrlsOrTokens { + match self { + Self::Urls(urls) => UrlsOrTokens::Urls(urls), + Self::MediaFileTokens(tokens) => UrlsOrTokens::Tokens(tokens), + } + } +} + +impl AudioListRef { + fn into_urls_or_tokens(self) -> UrlsOrTokens { + match self { + Self::Urls(urls) => UrlsOrTokens::Urls(urls), + Self::MediaFileTokens(tokens) => UrlsOrTokens::Tokens(tokens), + } + } +} + // --- Resolve + upload helpers --- -/// Resolve a single ImageRef to a URL string, then upload to Seedance2Pro CDN. -async fn resolve_and_upload_image_ref( +/// Resolve a single ImageRef and upload to Seedance2Pro CDN. +async fn resolve_and_upload_single( session: &Seedance2ProSession, image_ref: Option, maybe_map: Option<&HashMap>, ) -> Result, ArtcraftRouterError> { - let image_ref = match image_ref { - None => return Ok(None), - Some(r) => r, - }; - let source_url = match image_ref { - ImageRef::Url(url) => url, - ImageRef::MediaFileToken(token) => resolve_token_to_url(&token, maybe_map)?, + None => return Ok(None), + Some(ImageRef::Url(url)) => url, + Some(ImageRef::MediaFileToken(token)) => resolve_token(maybe_map, &token)?, }; - - let uploaded = upload_to_seedance2pro(session, &source_url).await?; - Ok(Some(uploaded)) + Ok(Some(upload_to_seedance2pro(session, &source_url).await?)) } -/// Resolve an ImageListRef to URL strings, then upload each to Seedance2Pro CDN. -/// Order of the input list is preserved in the output. -async fn resolve_and_upload_image_list_ref( +/// Resolve a list of refs to URLs and upload each to Seedance2Pro CDN. +/// Order is preserved. +async fn resolve_and_upload_list( session: &Seedance2ProSession, - image_list_ref: Option, + urls_or_tokens: Option, maybe_map: Option<&HashMap>, ) -> Result>, ArtcraftRouterError> { - let list = match image_list_ref { + let source_urls = match urls_or_tokens { None => return Ok(None), - Some(r) => r, + Some(UrlsOrTokens::Urls(urls)) if urls.is_empty() => return Ok(None), + Some(UrlsOrTokens::Urls(urls)) => urls, + Some(UrlsOrTokens::Tokens(tokens)) if tokens.is_empty() => return Ok(None), + Some(UrlsOrTokens::Tokens(tokens)) => resolve_tokens(maybe_map, &tokens)?, }; - let source_urls = match list { - ImageListRef::Urls(urls) => urls, - ImageListRef::MediaFileTokens(tokens) => resolve_tokens_to_urls(&tokens, maybe_map)?, - }; - - if source_urls.is_empty() { - return Ok(None); - } - let mut uploaded = Vec::with_capacity(source_urls.len()); for url in &source_urls { uploaded.push(upload_to_seedance2pro(session, url).await?); @@ -148,100 +166,23 @@ async fn resolve_and_upload_image_list_ref( Ok(Some(uploaded)) } -/// Resolve a VideoListRef to URL strings, then upload each to Seedance2Pro CDN. -/// Order of the input list is preserved in the output. -async fn resolve_and_upload_video_list_ref( - session: &Seedance2ProSession, - video_list_ref: Option, +// --- Token resolution --- + +fn resolve_token( maybe_map: Option<&HashMap>, -) -> Result>, ArtcraftRouterError> { - let list = match video_list_ref { - None => return Ok(None), - Some(r) => r, - }; - - let source_urls = match list { - VideoListRef::Urls(urls) => urls, - VideoListRef::MediaFileTokens(tokens) => resolve_tokens_to_urls(&tokens, maybe_map)?, - }; - - if source_urls.is_empty() { - return Ok(None); - } - - let mut uploaded = Vec::with_capacity(source_urls.len()); - for url in &source_urls { - uploaded.push(upload_to_seedance2pro(session, url).await?); - } - Ok(Some(uploaded)) -} - -/// Resolve an AudioListRef to URL strings, then upload each to Seedance2Pro CDN. -/// Order of the input list is preserved in the output. -async fn resolve_and_upload_audio_list_ref( - session: &Seedance2ProSession, - audio_list_ref: Option, - maybe_map: Option<&HashMap>, -) -> Result>, ArtcraftRouterError> { - let list = match audio_list_ref { - None => return Ok(None), - Some(r) => r, - }; - - let source_urls = match list { - AudioListRef::Urls(urls) => urls, - AudioListRef::MediaFileTokens(tokens) => resolve_tokens_to_urls(&tokens, maybe_map)?, - }; - - if source_urls.is_empty() { - return Ok(None); - } - - let mut uploaded = Vec::with_capacity(source_urls.len()); - for url in &source_urls { - uploaded.push(upload_to_seedance2pro(session, url).await?); - } - Ok(Some(uploaded)) -} - -// --- Token resolution helpers --- - -/// Look up a single media file token in the map to get its CDN URL. -fn resolve_token_to_url( token: &MediaFileToken, - maybe_map: Option<&HashMap>, ) -> Result { - let map = maybe_map.ok_or_else(|| { - ArtcraftRouterError::Client(ClientError::MediaFileToUrlMapNotProvided) - })?; - - map.get(token) - .cloned() - .ok_or_else(|| { - ArtcraftRouterError::Client(ClientError::MediaFileTokenNotFoundInMap { - token: token.as_str().to_string(), - }) + let map = maybe_map.ok_or(ArtcraftRouterError::Client(ClientError::MediaFileToUrlMapNotProvided))?; + map.get(token).cloned().ok_or_else(|| { + ArtcraftRouterError::Client(ClientError::MediaFileTokenNotFoundInMap { + token: token.as_str().to_string(), }) + }) } -/// Look up multiple media file tokens in the map. Order is preserved. -fn resolve_tokens_to_urls( - tokens: &[MediaFileToken], +fn resolve_tokens( maybe_map: Option<&HashMap>, + tokens: &[MediaFileToken], ) -> Result, ArtcraftRouterError> { - let map = maybe_map.ok_or_else(|| { - ArtcraftRouterError::Client(ClientError::MediaFileToUrlMapNotProvided) - })?; - - tokens.iter() - .map(|token| { - map.get(token) - .cloned() - .ok_or_else(|| { - ArtcraftRouterError::Client(ClientError::MediaFileTokenNotFoundInMap { - token: token.as_str().to_string(), - }) - }) - }) - .collect() + tokens.iter().map(|t| resolve_token(maybe_map, t)).collect() } diff --git a/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video_v2/providers/kinovi/upload.rs b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video_v2/providers/kinovi/upload.rs index f106ecde44..a0689368be 100644 --- a/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video_v2/providers/kinovi/upload.rs +++ b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video_v2/providers/kinovi/upload.rs @@ -1,41 +1,14 @@ use crate::errors::artcraft_router_error::ArtcraftRouterError; +use crate::errors::provider_error::ProviderError; +use crate::utils::download_file::download_file; use seedance2pro_client::creds::seedance2pro_session::Seedance2ProSession; use seedance2pro_client::requests::prepare_file_upload::prepare_file_upload::{prepare_file_upload, PrepareFileUploadArgs}; use seedance2pro_client::requests::upload_file::upload_file::{upload_file, UploadFileArgs}; use url_utils::extension::extract_extension_from_url::{extract_extension_from_url_str, ExtractExtensions}; -use crate::errors::provider_error::ProviderError; -use crate::utils::download_file::download_file; - -pub(super) async fn upload_optional_url( - session: &Seedance2ProSession, - url: Option<&str>, -) -> Result, ArtcraftRouterError> { - match url { - None => Ok(None), - Some(u) => Ok(Some(upload_to_seedance2pro(session, u).await?)), - } -} - -pub(super) async fn upload_optional_url_list( - session: &Seedance2ProSession, - urls: Option<&[String]>, -) -> Result>, ArtcraftRouterError> { - match urls { - None => Ok(None), - Some(url_list) if url_list.is_empty() => Ok(None), - Some(url_list) => { - let mut uploaded = Vec::with_capacity(url_list.len()); - for url in url_list { - uploaded.push(upload_to_seedance2pro(session, url).await?); - } - Ok(Some(uploaded)) - } - } -} /// Downloads a file from a source URL and re-uploads it to seedance2pro CDN. pub(crate) async fn upload_to_seedance2pro( - session: &seedance2pro_client::creds::seedance2pro_session::Seedance2ProSession, + session: &Seedance2ProSession, source_url: &str, ) -> Result { let extension = extract_extension_from_url_str(source_url, &ExtractExtensions::All)