diff --git a/Cargo.lock b/Cargo.lock index 8ddf7a2d8c..fef96c888c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7243,6 +7243,7 @@ dependencies = [ "serde", "serde_derive", "serde_json", + "test_data", "testing", "tokio", "url", diff --git a/crates/api_clients/muapi_client/Cargo.toml b/crates/api_clients/muapi_client/Cargo.toml index 75ad0c5928..8d85e04f42 100644 --- a/crates/api_clients/muapi_client/Cargo.toml +++ b/crates/api_clients/muapi_client/Cargo.toml @@ -28,5 +28,6 @@ url.workspace = true [dev-dependencies] env.workspace = true env_logger.workspace = true +test_data = { path = "../../lib/test_data" } testing = { path = "../../lib/testing" } tokio = { version = "1", features = ["full"] } diff --git a/crates/api_clients/muapi_client/src/error/muapi_error.rs b/crates/api_clients/muapi_client/src/error/muapi_error.rs index 261fa7c2f0..d9e72e54e9 100644 --- a/crates/api_clients/muapi_client/src/error/muapi_error.rs +++ b/crates/api_clients/muapi_client/src/error/muapi_error.rs @@ -17,7 +17,7 @@ impl MuapiError { Self::ApiGeneric(MuapiGenericApiError::CloudflareError(CloudflareError::BadGateway502)) => true, Self::ApiGeneric(MuapiGenericApiError::CloudflareError(CloudflareError::GatewayTimeout504)) => true, Self::ApiGeneric(MuapiGenericApiError::CloudflareError(CloudflareError::TimeoutOccurred524)) => true, - Self::ApiGeneric(MuapiGenericApiError::UncategorizedBadResponseWithStatusAndBody { status_code, body }) => { + Self::ApiGeneric(MuapiGenericApiError::UncategorizedBadResponseWithStatusAndBody { status_code, body: _ }) => { match status_code.as_u16() { 502 => true, 504 => true, diff --git a/crates/api_clients/muapi_client/src/requests/poll_prediction_result/poll_prediction_result.rs b/crates/api_clients/muapi_client/src/requests/poll_prediction_result/poll_prediction_result.rs index 9ecece21c6..b37659f825 100644 --- a/crates/api_clients/muapi_client/src/requests/poll_prediction_result/poll_prediction_result.rs +++ b/crates/api_clients/muapi_client/src/requests/poll_prediction_result/poll_prediction_result.rs @@ -120,7 +120,8 @@ mod tests { async fn test_poll_prediction_result() -> AnyhowResult<()> { setup_test_logging(LevelFilter::Trace); let session = get_test_api_key()?; - let request_id = "REPLACE_WITH_REAL_REQUEST_ID"; + let request_id = "5fd7f575-4b9e-4c0a-b7f7-0eb036027b81"; + let request_id = "82d622b1-8911-4cac-8362-e18efaa743ed"; let result = poll_prediction_result(PollPredictionResultArgs { session: &session, request_id, diff --git a/crates/api_clients/muapi_client/src/requests/seedance_2p0_image_to_video/seedance_2p0_image_to_video.rs b/crates/api_clients/muapi_client/src/requests/seedance_2p0_image_to_video/seedance_2p0_image_to_video.rs index 58a8bf7544..bfdab1fb6f 100644 --- a/crates/api_clients/muapi_client/src/requests/seedance_2p0_image_to_video/seedance_2p0_image_to_video.rs +++ b/crates/api_clients/muapi_client/src/requests/seedance_2p0_image_to_video/seedance_2p0_image_to_video.rs @@ -17,8 +17,10 @@ pub enum AspectRatio { Landscape16x9, /// 9:16 portrait Portrait9x16, - /// 1:1 square - Square1x1, + /// 4:3 standard + Standard4x3, + /// 3:4 portrait + Portrait3x4, } impl AspectRatio { @@ -26,7 +28,29 @@ impl AspectRatio { match self { Self::Landscape16x9 => "16:9", Self::Portrait9x16 => "9:16", - Self::Square1x1 => "1:1", + Self::Standard4x3 => "4:3", + Self::Portrait3x4 => "3:4", + } + } +} + +/// Video duration. +#[derive(Debug, Clone, Copy)] +pub enum Duration { + /// 5 seconds + FiveSeconds, + /// 10 seconds + TenSeconds, + /// 15 seconds + FifteenSeconds, +} + +impl Duration { + fn as_u8(&self) -> u8 { + match self { + Self::FiveSeconds => 5, + Self::TenSeconds => 10, + Self::FifteenSeconds => 15, } } } @@ -56,13 +80,13 @@ pub struct Seedance2p0ImageToVideoArgs<'a> { pub prompt: String, /// One or more image URLs to use as input frames. - pub images_list: Vec, + pub image_urls: Vec, /// The aspect ratio for the output video. pub aspect_ratio: AspectRatio, - /// Duration in seconds. - pub duration: u8, + /// Duration of the generated video. + pub duration: Duration, /// Quality tier. pub quality: Quality, @@ -72,7 +96,7 @@ impl std::fmt::Debug for Seedance2p0ImageToVideoArgs<'_> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("Seedance2p0ImageToVideoArgs") .field("prompt", &self.prompt) - .field("images_list", &self.images_list) + .field("image_urls", &self.image_urls) .field("aspect_ratio", &self.aspect_ratio) .field("duration", &self.duration) .field("quality", &self.quality) @@ -80,6 +104,7 @@ impl std::fmt::Debug for Seedance2p0ImageToVideoArgs<'_> { } } +#[derive(Debug)] pub struct Seedance2p0ImageToVideoResponse { /// The request ID used to poll for results. pub request_id: String, @@ -94,9 +119,9 @@ pub async fn seedance_2p0_image_to_video( let request_body = Seedance2p0I2vRequest { prompt: args.prompt, - images_list: args.images_list, + images_list: args.image_urls, aspect_ratio: args.aspect_ratio.as_str(), - duration: args.duration, + duration: args.duration.as_u8(), quality: args.quality.as_str(), }; @@ -145,6 +170,7 @@ mod tests { use crate::test_utils::setup_test_logging::setup_test_logging; use errors::AnyhowResult; use log::LevelFilter; + use test_data::web::image_urls::*; #[tokio::test] #[ignore] // manually test — requires real API key @@ -153,15 +179,16 @@ mod tests { let session = get_test_api_key()?; let args = Seedance2p0ImageToVideoArgs { session: &session, - prompt: "The lightbulb suddenly rockets across the room like a missile, smashing through curtains while water spins violently inside.".to_string(), - images_list: vec![ - "https://d3adwkbyhxyrtq.cloudfront.net/webassets/videomodels/seedance-v2.0-i2v.jpg".to_string(), + prompt: "The dog barks and runs across the lake's pier, tail wagging.".to_string(), + image_urls: vec![ + JUNO_AT_LAKE_IMAGE_URL.to_string(), ], aspect_ratio: AspectRatio::Landscape16x9, - duration: 5, - quality: Quality::Basic, + duration: Duration::FiveSeconds, + quality: Quality::High, }; let result = seedance_2p0_image_to_video(args).await?; + println!("Result: {:?}", result); println!("Request ID: {}", result.request_id); assert!(!result.request_id.is_empty()); assert_eq!(1, 2); // NB: Intentional failure to inspect output. @@ -175,15 +202,16 @@ mod tests { let session = get_test_api_key()?; let args = Seedance2p0ImageToVideoArgs { session: &session, - prompt: "A cat slowly blinks and yawns, stretching its paws forward.".to_string(), - images_list: vec![ - "https://d3adwkbyhxyrtq.cloudfront.net/webassets/videomodels/seedance-v2.0-i2v.jpg".to_string(), + prompt: "The corgi and shiba swim through the treasure-filled ocean, bubbles rising around them.".to_string(), + image_urls: vec![ + TALL_CORGI_SHIBA_TREASURE_OCEAN_URL.to_string(), ], aspect_ratio: AspectRatio::Portrait9x16, - duration: 5, + duration: Duration::FiveSeconds, quality: Quality::High, }; let result = seedance_2p0_image_to_video(args).await?; + println!("Result: {:?}", result); println!("Request ID: {}", result.request_id); assert!(!result.request_id.is_empty()); assert_eq!(1, 2); // NB: Intentional failure to inspect output. diff --git a/crates/api_clients/seedance2pro/src/error/seedance2pro_error.rs b/crates/api_clients/seedance2pro/src/error/seedance2pro_error.rs index b0f8c5a9b6..51652ade72 100644 --- a/crates/api_clients/seedance2pro/src/error/seedance2pro_error.rs +++ b/crates/api_clients/seedance2pro/src/error/seedance2pro_error.rs @@ -17,7 +17,7 @@ impl Seedance2ProError { Self::ApiGeneric(Seedance2ProGenericApiError::CloudflareError(CloudflareError::BadGateway502)) => true, Self::ApiGeneric(Seedance2ProGenericApiError::CloudflareError(CloudflareError::GatewayTimeout504)) => true, Self::ApiGeneric(Seedance2ProGenericApiError::CloudflareError(CloudflareError::TimeoutOccurred524)) => true, - Self::ApiGeneric(Seedance2ProGenericApiError::UncategorizedBadResponseWithStatusAndBody { status_code, body }) => { + Self::ApiGeneric(Seedance2ProGenericApiError::UncategorizedBadResponseWithStatusAndBody { status_code, body: _ }) => { match status_code.as_u16() { 502 => true, 504 => true,