diff --git a/Cargo.lock b/Cargo.lock index 8374526d7d..ccf9ea0c02 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -773,6 +773,7 @@ dependencies = [ "artcraft_client", "enums", "fal_client", + "gmicloud_client", "log", "muapi_client", "reqwest 0.12.23", diff --git a/crates/api_clients/artcraft/artcraft_router/Cargo.toml b/crates/api_clients/artcraft/artcraft_router/Cargo.toml index 11a86e0d0f..d30f6ee0ff 100644 --- a/crates/api_clients/artcraft/artcraft_router/Cargo.toml +++ b/crates/api_clients/artcraft/artcraft_router/Cargo.toml @@ -20,6 +20,7 @@ artcraft_api_defs.workspace = true enums.workspace = true artcraft_client.workspace = true fal_client.workspace = true +gmicloud_client.workspace = true muapi_client.workspace = true seedance2pro_client.workspace = true tokens.workspace = true diff --git a/crates/api_clients/artcraft/artcraft_router/src/api/provider.rs b/crates/api_clients/artcraft/artcraft_router/src/api/provider.rs index e1389c56a4..15d0d970f3 100644 --- a/crates/api_clients/artcraft/artcraft_router/src/api/provider.rs +++ b/crates/api_clients/artcraft/artcraft_router/src/api/provider.rs @@ -6,6 +6,7 @@ use serde_derive::{Deserialize, Serialize}; pub enum Provider { Artcraft, Fal, + GmiCloud, Muapi, Seedance2Pro, } diff --git a/crates/api_clients/artcraft/artcraft_router/src/client/mod.rs b/crates/api_clients/artcraft/artcraft_router/src/client/mod.rs index 4fac64c689..1d0c3c6b85 100644 --- a/crates/api_clients/artcraft/artcraft_router/src/client/mod.rs +++ b/crates/api_clients/artcraft/artcraft_router/src/client/mod.rs @@ -6,5 +6,6 @@ pub mod router_artcraft_client; pub mod router_client; pub mod router_fal_client; pub mod router_fal_webhook_optional_client; +pub mod router_gmicloud_client; pub mod router_muapi_client; pub mod router_seedance2pro_client; diff --git a/crates/api_clients/artcraft/artcraft_router/src/client/multi_router_client.rs b/crates/api_clients/artcraft/artcraft_router/src/client/multi_router_client.rs index c1db20f3fc..ced2d9ff57 100644 --- a/crates/api_clients/artcraft/artcraft_router/src/client/multi_router_client.rs +++ b/crates/api_clients/artcraft/artcraft_router/src/client/multi_router_client.rs @@ -1,6 +1,7 @@ use crate::client::router_artcraft_client::RouterArtcraftClient; use crate::client::router_fal_client::RouterFalClient; use crate::client::router_fal_webhook_optional_client::RouterFalWebhookOptionalClient; +use crate::client::router_gmicloud_client::RouterGmiCloudClient; use crate::client::router_muapi_client::RouterMuapiClient; use crate::client::router_seedance2pro_client::RouterSeedance2ProClient; use crate::errors::client_error::{ClientError, ClientType}; @@ -9,6 +10,7 @@ pub struct MultiRouterClient { pub(crate) artcraft_client: Option, pub(crate) fal_client: Option, pub(crate) fal_webhook_optional_client: Option, + pub(crate) gmicloud_client: Option, pub(crate) muapi_client: Option, pub(crate) seedance2pro_client: Option, } @@ -23,11 +25,17 @@ impl MultiRouterClient { self.fal_client.as_ref() .ok_or(ClientError::ClientNotConfigured(ClientType::Fal)) } + pub fn get_fal_webhook_optional_client_ref(&self) -> Result<&RouterFalWebhookOptionalClient, ClientError> { self.fal_webhook_optional_client.as_ref() .ok_or(ClientError::ClientNotConfigured(ClientType::FalWebhookOptional)) } + pub fn get_gmicloud_client_ref(&self) -> Result<&RouterGmiCloudClient, ClientError> { + self.gmicloud_client.as_ref() + .ok_or(ClientError::ClientNotConfigured(ClientType::GmiCloud)) + } + pub fn get_muapi_client_ref(&self) -> Result<&RouterMuapiClient, ClientError> { self.muapi_client.as_ref() .ok_or(ClientError::ClientNotConfigured(ClientType::Muapi)) diff --git a/crates/api_clients/artcraft/artcraft_router/src/client/multi_router_client_builder.rs b/crates/api_clients/artcraft/artcraft_router/src/client/multi_router_client_builder.rs index 9d0c516429..e410b9530e 100644 --- a/crates/api_clients/artcraft/artcraft_router/src/client/multi_router_client_builder.rs +++ b/crates/api_clients/artcraft/artcraft_router/src/client/multi_router_client_builder.rs @@ -2,6 +2,7 @@ use crate::client::multi_router_client::MultiRouterClient; use crate::client::router_artcraft_client::RouterArtcraftClient; use crate::client::router_fal_client::RouterFalClient; use crate::client::router_fal_webhook_optional_client::RouterFalWebhookOptionalClient; +use crate::client::router_gmicloud_client::RouterGmiCloudClient; use crate::client::router_muapi_client::RouterMuapiClient; use crate::client::router_seedance2pro_client::RouterSeedance2ProClient; @@ -9,6 +10,7 @@ pub struct MultiRouterClientBuilder { artcraft_client: Option, fal_client: Option, fal_webhook_optional_client: Option, + gmicloud_client: Option, muapi_client: Option, seedance2pro_client: Option, } @@ -19,6 +21,7 @@ impl MultiRouterClientBuilder { artcraft_client: None, fal_client: None, fal_webhook_optional_client: None, + gmicloud_client: None, muapi_client: None, seedance2pro_client: None, } @@ -33,12 +36,17 @@ impl MultiRouterClientBuilder { self.fal_client = Some(client); self } - + pub fn set_fal_webhook_optional_client(mut self, client: RouterFalWebhookOptionalClient) -> Self { self.fal_webhook_optional_client = Some(client); self } + pub fn set_gmicloud_client(mut self, client: RouterGmiCloudClient) -> Self { + self.gmicloud_client = Some(client); + self + } + pub fn set_muapi_client(mut self, client: RouterMuapiClient) -> Self { self.muapi_client = Some(client); self @@ -54,6 +62,7 @@ impl MultiRouterClientBuilder { artcraft_client: self.artcraft_client, fal_client: self.fal_client, fal_webhook_optional_client: self.fal_webhook_optional_client, + gmicloud_client: self.gmicloud_client, muapi_client: self.muapi_client, seedance2pro_client: self.seedance2pro_client, } diff --git a/crates/api_clients/artcraft/artcraft_router/src/client/router_client.rs b/crates/api_clients/artcraft/artcraft_router/src/client/router_client.rs index fb3cc04751..53207960d2 100644 --- a/crates/api_clients/artcraft/artcraft_router/src/client/router_client.rs +++ b/crates/api_clients/artcraft/artcraft_router/src/client/router_client.rs @@ -2,6 +2,7 @@ use crate::client::multi_router_client::MultiRouterClient; use crate::client::router_artcraft_client::RouterArtcraftClient; use crate::client::router_fal_client::RouterFalClient; use crate::client::router_fal_webhook_optional_client::RouterFalWebhookOptionalClient; +use crate::client::router_gmicloud_client::RouterGmiCloudClient; use crate::client::router_muapi_client::RouterMuapiClient; use crate::client::router_seedance2pro_client::RouterSeedance2ProClient; use crate::errors::client_error::{ClientError, ClientType}; @@ -11,6 +12,7 @@ pub enum RouterClient { Artcraft(RouterArtcraftClient), Fal(RouterFalClient), FalWebhookOptional(RouterFalWebhookOptionalClient), + GmiCloud(RouterGmiCloudClient), Muapi(RouterMuapiClient), Seedance2Pro(RouterSeedance2ProClient), } @@ -40,6 +42,14 @@ impl RouterClient { } } + pub fn get_gmicloud_client_ref(&self) -> Result<&RouterGmiCloudClient, ClientError> { + match self { + RouterClient::GmiCloud(client) => Ok(client), + RouterClient::Multi(multi) => multi.get_gmicloud_client_ref(), + _ => Err(ClientError::ClientNotConfigured(ClientType::GmiCloud)), + } + } + pub fn get_muapi_client_ref(&self) -> Result<&RouterMuapiClient, ClientError> { match self { RouterClient::Muapi(client) => Ok(client), diff --git a/crates/api_clients/artcraft/artcraft_router/src/client/router_gmicloud_client.rs b/crates/api_clients/artcraft/artcraft_router/src/client/router_gmicloud_client.rs new file mode 100644 index 0000000000..8660ffd2c1 --- /dev/null +++ b/crates/api_clients/artcraft/artcraft_router/src/client/router_gmicloud_client.rs @@ -0,0 +1,11 @@ +use gmicloud_client::creds::gmicloud_api_key::GmiCloudApiKey; + +pub struct RouterGmiCloudClient { + pub(crate) api_key: GmiCloudApiKey, +} + +impl RouterGmiCloudClient { + pub fn new(api_key: GmiCloudApiKey) -> Self { + RouterGmiCloudClient { api_key } + } +} diff --git a/crates/api_clients/artcraft/artcraft_router/src/errors/client_error.rs b/crates/api_clients/artcraft/artcraft_router/src/errors/client_error.rs index e8323acefc..88da3cd101 100644 --- a/crates/api_clients/artcraft/artcraft_router/src/errors/client_error.rs +++ b/crates/api_clients/artcraft/artcraft_router/src/errors/client_error.rs @@ -8,6 +8,7 @@ pub enum ClientType { Artcraft, Fal, FalWebhookOptional, + GmiCloud, Muapi, Seedance2Pro, } @@ -18,6 +19,7 @@ impl Display for ClientType { Self::Artcraft => write!(f, "Artcraft"), Self::Fal => write!(f, "Fal"), Self::FalWebhookOptional => write!(f, "FalWebhookOptional"), + Self::GmiCloud => write!(f, "GmiCloud"), Self::Muapi => write!(f, "Muapi"), Self::Seedance2Pro => write!(f, "Seedance2Pro"), } diff --git a/crates/api_clients/artcraft/artcraft_router/src/errors/provider_error.rs b/crates/api_clients/artcraft/artcraft_router/src/errors/provider_error.rs index 774d893926..96e9d05b7f 100644 --- a/crates/api_clients/artcraft/artcraft_router/src/errors/provider_error.rs +++ b/crates/api_clients/artcraft/artcraft_router/src/errors/provider_error.rs @@ -2,6 +2,7 @@ use std::error::Error; use std::fmt::{Display, Formatter}; use artcraft_client::error::storyteller_error::StorytellerError; use fal_client::error::fal_error_plus::FalErrorPlus; +use gmicloud_client::error::gmicloud_error::GmiCloudError; use muapi_client::error::muapi_error::MuapiError; use seedance2pro_client::error::seedance2pro_error::Seedance2ProError; @@ -9,6 +10,7 @@ use seedance2pro_client::error::seedance2pro_error::Seedance2ProError; pub enum ProviderError { Storyteller(StorytellerError), Fal(FalErrorPlus), + GmiCloud(GmiCloudError), Muapi(MuapiError), Seedance2Pro(Seedance2ProError), } @@ -20,6 +22,7 @@ impl Display for ProviderError { match self { Self::Storyteller(e) => write!(f, "Storyteller provider error: {}", e), Self::Fal(e) => write!(f, "Fal provider error: {}", e), + Self::GmiCloud(e) => write!(f, "GmiCloud provider error: {}", e), Self::Muapi(e) => write!(f, "Muapi provider error: {}", e), Self::Seedance2Pro(e) => write!(f, "Seedance2Pro provider error: {}", e), } @@ -38,6 +41,12 @@ impl From for ProviderError { } } +impl From for ProviderError { + fn from(error: GmiCloudError) -> Self { + Self::GmiCloud(error) + } +} + impl From for ProviderError { fn from(error: MuapiError) -> Self { Self::Muapi(error) diff --git a/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/generate_video_request_builder.rs b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/generate_video_request_builder.rs index 0c033f8713..d445cbdd71 100644 --- a/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/generate_video_request_builder.rs +++ b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/generate_video_request_builder.rs @@ -52,6 +52,8 @@ use crate::generate::generate_video_v2::providers::artcraft::seedance_2p0::build use crate::generate::generate_video_v2::providers::artcraft::seedance_2p0_fast::build::build_artcraft_seedance_2p0_fast; use crate::generate::generate_video_v2::providers::kinovi::happy_horse_1p0::build::build_kinovi_happy_horse_1p0; use crate::generate::generate_video_v2::providers::kinovi::seedance_2p0::build::build_kinovi_seedance_2p0; +use crate::generate::generate_video_v2::providers::gmicloud::seedance_2p0_g::build::build_gmicloud_seedance_2p0_g; +use crate::generate::generate_video_v2::providers::gmicloud::seedance_2p0_fast_g::build::build_gmicloud_seedance_2p0_fast_g; use crate::generate::generate_video_v2::providers::kinovi::seedance_2p0_fast::build::build_kinovi_seedance_2p0_fast; use crate::generate::generate_video_v2::video_generation_draft_or_request::VideoGenerationDraftOrRequest; @@ -147,6 +149,8 @@ impl GenerateVideoRequestBuilder { (Provider::Artcraft, CommonVideoModel::HappyHorse1p0) => true, (Provider::Artcraft, CommonVideoModel::Seedance2p0) => true, (Provider::Artcraft, CommonVideoModel::Seedance2p0Fast) => true, + (Provider::GmiCloud, CommonVideoModel::Seedance2p0Global) => true, + (Provider::GmiCloud, CommonVideoModel::Seedance2p0FastGlobal) => true, (Provider::Seedance2Pro, CommonVideoModel::HappyHorse1p0) => true, (Provider::Seedance2Pro, CommonVideoModel::Seedance2p0) => true, (Provider::Seedance2Pro, CommonVideoModel::Seedance2p0Fast) => true, @@ -160,6 +164,8 @@ impl GenerateVideoRequestBuilder { (Provider::Artcraft, CommonVideoModel::HappyHorse1p0) => build_artcraft_happy_horse_1p0(self), (Provider::Artcraft, CommonVideoModel::Seedance2p0) => build_artcraft_seedance_2p0(self), (Provider::Artcraft, CommonVideoModel::Seedance2p0Fast) => build_artcraft_seedance_2p0_fast(self), + (Provider::GmiCloud, CommonVideoModel::Seedance2p0Global) => build_gmicloud_seedance_2p0_g(self), + (Provider::GmiCloud, CommonVideoModel::Seedance2p0FastGlobal) => build_gmicloud_seedance_2p0_fast_g(self), (Provider::Seedance2Pro, CommonVideoModel::HappyHorse1p0) => build_kinovi_happy_horse_1p0(self), (Provider::Seedance2Pro, CommonVideoModel::Seedance2p0) => build_kinovi_seedance_2p0(self), (Provider::Seedance2Pro, CommonVideoModel::Seedance2p0Fast) => build_kinovi_seedance_2p0_fast(self), @@ -172,9 +178,9 @@ impl GenerateVideoRequestBuilder { match self.provider { Provider::Artcraft => self.build_artcraft(), Provider::Fal => self.build_fal(), + Provider::GmiCloud => self.unsupported_provider(), // GmiCloud uses build2() only Provider::Muapi => self.build_muapi(), Provider::Seedance2Pro => self.build_seedance2pro(), - _ => self.unsupported_provider(), } } diff --git a/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/generate_video_response.rs b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/generate_video_response.rs index f8c9839841..80b82fbda9 100644 --- a/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/generate_video_response.rs +++ b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video/generate_video_response.rs @@ -34,9 +34,15 @@ pub struct FalVideoResponsePayload { pub maybe_outbound_request: Option>, } +#[derive(Clone, Debug)] +pub struct GmiCloudVideoResponsePayload { + pub request_id: String, +} + #[derive(Clone, Debug)] pub enum GenerateVideoResponse { Artcraft(ArtcraftVideoResponsePayload), + GmiCloud(GmiCloudVideoResponsePayload), Muapi(MuapiVideoResponsePayload), Seedance2Pro(Seedance2proVideoResponsePayload), Fal(FalVideoResponsePayload), @@ -64,6 +70,13 @@ impl GenerateVideoResponse { } } + pub fn get_gmicloud_payload(&self) -> Option { + match self { + Self::GmiCloud(p) => Some(p.clone()), + _ => None, + } + } + pub fn get_fal_payload(&self) -> Option { match self { Self::Fal(p) => Some(p.clone()), diff --git a/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video_v2/providers/gmicloud/mod.rs b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video_v2/providers/gmicloud/mod.rs new file mode 100644 index 0000000000..f76c64a320 --- /dev/null +++ b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video_v2/providers/gmicloud/mod.rs @@ -0,0 +1,2 @@ +pub mod seedance_2p0_g; +pub mod seedance_2p0_fast_g; diff --git a/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video_v2/providers/gmicloud/seedance_2p0_fast_g/build.rs b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video_v2/providers/gmicloud/seedance_2p0_fast_g/build.rs new file mode 100644 index 0000000000..06bb37d7f3 --- /dev/null +++ b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video_v2/providers/gmicloud/seedance_2p0_fast_g/build.rs @@ -0,0 +1,100 @@ +use gmicloud_client::requests::api::video::seedance_2_0_fast_260128::api::{ + Seedance20FastRatio, Seedance20FastRequest, Seedance20FastResolution, +}; + +use crate::api::common_aspect_ratio::CommonAspectRatio; +use crate::api::common_resolution::CommonResolution; +use crate::api::image_ref::ImageRef; +use crate::client::request_mismatch_mitigation_strategy::RequestMismatchMitigationStrategy; +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::gmicloud::seedance_2p0_fast_g::request::GmiCloudSeedance2p0FastGRequestState; +use crate::generate::generate_video_v2::video_generation_draft_or_request::VideoGenerationDraftOrRequest; +use crate::generate::generate_video_v2::video_generation_request::VideoGenerationRequest; + +pub fn build_gmicloud_seedance_2p0_fast_g( + mut builder: GenerateVideoRequestBuilder, +) -> Result { + let strategy = builder.request_mismatch_mitigation_strategy; + + let ratio = plan_ratio(builder.aspect_ratio.take(), strategy)?; + let resolution = plan_resolution(builder.resolution.take(), strategy)?; + let duration = builder.duration_seconds.take().map(|d| (d as u8).clamp(4, 15)); + let prompt = builder.prompt.take().unwrap_or_default(); + + let first_frame = resolve_url(builder.start_frame.take())?; + let last_frame = resolve_url(builder.end_frame.take())?; + + let request = Seedance20FastRequest { + prompt, + duration, + resolution, + ratio, + seed: None, + watermark: None, + generate_audio: None, + web_search: None, + first_frame, + last_frame, + reference_images: None, + reference_videos: None, + reference_audios: None, + reference_asset_ids: None, + }; + + let state = GmiCloudSeedance2p0FastGRequestState { request }; + Ok(VideoGenerationDraftOrRequest::Request(VideoGenerationRequest::GmiCloudSeedance2p0FastG(state))) +} + +fn plan_ratio( + aspect_ratio: Option, + _strategy: RequestMismatchMitigationStrategy, +) -> Result, ArtcraftRouterError> { + match aspect_ratio { + None | Some(CommonAspectRatio::Auto) | Some(CommonAspectRatio::Auto2k) + | Some(CommonAspectRatio::Auto3k) | Some(CommonAspectRatio::Auto4k) => Ok(None), + Some(CommonAspectRatio::WideSixteenByNine) | Some(CommonAspectRatio::Wide) => Ok(Some(Seedance20FastRatio::Landscape16x9)), + Some(CommonAspectRatio::TallNineBySixteen) | Some(CommonAspectRatio::Tall) => Ok(Some(Seedance20FastRatio::Portrait9x16)), + Some(CommonAspectRatio::Square) | Some(CommonAspectRatio::SquareHd) => Ok(Some(Seedance20FastRatio::Square)), + Some(CommonAspectRatio::WideFourByThree) => Ok(Some(Seedance20FastRatio::Standard4x3)), + Some(CommonAspectRatio::TallThreeByFour) => Ok(Some(Seedance20FastRatio::Portrait3x4)), + Some(CommonAspectRatio::WideTwentyOneByNine) | Some(CommonAspectRatio::TallNineByTwentyOne) => { + Ok(Some(Seedance20FastRatio::UltraWide21x9)) + } + Some(CommonAspectRatio::WideThreeByTwo) | Some(CommonAspectRatio::WideFiveByFour) + | Some(CommonAspectRatio::TallFourByFive) | Some(CommonAspectRatio::TallTwoByThree) => { + Ok(Some(Seedance20FastRatio::Adaptive)) + } + } +} + +fn plan_resolution( + resolution: Option, + _strategy: RequestMismatchMitigationStrategy, +) -> Result, ArtcraftRouterError> { + match resolution { + None => Ok(None), + Some(CommonResolution::FourEightyP) => Ok(Some(Seedance20FastResolution::FourEightyP)), + Some(CommonResolution::SevenTwentyP) => Ok(Some(Seedance20FastResolution::SevenTwentyP)), + // Fast model doesn't support 1080p — fall back to 720p + Some(CommonResolution::TenEightyP) => Ok(Some(Seedance20FastResolution::SevenTwentyP)), + Some(CommonResolution::HalfK) | Some(CommonResolution::OneK) => Ok(Some(Seedance20FastResolution::FourEightyP)), + Some(CommonResolution::TwoK) | Some(CommonResolution::ThreeK) | Some(CommonResolution::FourK) => { + Ok(Some(Seedance20FastResolution::SevenTwentyP)) + } + } +} + +fn resolve_url(image_ref: Option) -> Result, ArtcraftRouterError> { + match image_ref { + None => Ok(None), + Some(ImageRef::Url(url)) => Ok(Some(url)), + Some(ImageRef::MediaFileToken(_)) => { + Err(ArtcraftRouterError::Client(ClientError::ModelDoesNotSupportOption { + field: "start_frame/end_frame", + value: "GmiCloud only supports image URLs, not media file tokens".to_string(), + })) + } + } +} diff --git a/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video_v2/providers/gmicloud/seedance_2p0_fast_g/cost.rs b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video_v2/providers/gmicloud/seedance_2p0_fast_g/cost.rs new file mode 100644 index 0000000000..1460181942 --- /dev/null +++ b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video_v2/providers/gmicloud/seedance_2p0_fast_g/cost.rs @@ -0,0 +1,27 @@ +use gmicloud_client::traits::gmicloud_request_cost_calculator_trait::GmiCloudRequestCostCalculator; + +use crate::generate::generate_video::video_generation_cost_estimate::VideoGenerationCostEstimate; +use crate::generate::generate_video_v2::providers::gmicloud::seedance_2p0_fast_g::request::GmiCloudSeedance2p0FastGRequestState; + +pub struct GmiCloudSeedance2p0FastGCostState { + request: GmiCloudSeedance2p0FastGRequestState, +} + +impl GmiCloudSeedance2p0FastGCostState { + pub fn from_request(request: &GmiCloudSeedance2p0FastGRequestState) -> Self { + Self { request: request.clone() } + } + + pub fn estimate_cost(&self) -> VideoGenerationCostEstimate { + let cost_in_usd_cents = self.request.request.calculate_cost_in_cents(); + + VideoGenerationCostEstimate { + cost_in_credits: None, + cost_in_usd_cents: Some(cost_in_usd_cents), + is_free: false, + is_unlimited: false, + is_rate_limited: false, + has_watermark: false, + } + } +} diff --git a/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video_v2/providers/gmicloud/seedance_2p0_fast_g/mod.rs b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video_v2/providers/gmicloud/seedance_2p0_fast_g/mod.rs new file mode 100644 index 0000000000..98150bd014 --- /dev/null +++ b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video_v2/providers/gmicloud/seedance_2p0_fast_g/mod.rs @@ -0,0 +1,3 @@ +pub mod build; +pub mod cost; +pub mod request; diff --git a/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video_v2/providers/gmicloud/seedance_2p0_fast_g/request.rs b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video_v2/providers/gmicloud/seedance_2p0_fast_g/request.rs new file mode 100644 index 0000000000..52bc3d6743 --- /dev/null +++ b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video_v2/providers/gmicloud/seedance_2p0_fast_g/request.rs @@ -0,0 +1,25 @@ +use gmicloud_client::requests::api::video::seedance_2_0_fast_260128::api::Seedance20FastRequest; + +use crate::client::router_gmicloud_client::RouterGmiCloudClient; +use crate::errors::artcraft_router_error::ArtcraftRouterError; +use crate::errors::provider_error::ProviderError; +use crate::generate::generate_video::generate_video_response::{ + GenerateVideoResponse, GmiCloudVideoResponsePayload, +}; + +#[derive(Clone, Debug)] +pub struct GmiCloudSeedance2p0FastGRequestState { + pub request: Seedance20FastRequest, +} + +impl GmiCloudSeedance2p0FastGRequestState { + pub async fn send(&self, client: &RouterGmiCloudClient) -> Result { + let response = self.request.send_request(&client.api_key) + .await + .map_err(|err| ArtcraftRouterError::Provider(ProviderError::GmiCloud(err)))?; + + Ok(GenerateVideoResponse::GmiCloud(GmiCloudVideoResponsePayload { + request_id: response.request_id, + })) + } +} diff --git a/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video_v2/providers/gmicloud/seedance_2p0_g/build.rs b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video_v2/providers/gmicloud/seedance_2p0_g/build.rs new file mode 100644 index 0000000000..13727db3d9 --- /dev/null +++ b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video_v2/providers/gmicloud/seedance_2p0_g/build.rs @@ -0,0 +1,99 @@ +use gmicloud_client::requests::api::video::seedance_2_0_260128::api::{ + Seedance20Ratio, Seedance20Request, Seedance20Resolution, +}; + +use crate::api::common_aspect_ratio::CommonAspectRatio; +use crate::api::common_resolution::CommonResolution; +use crate::api::image_ref::ImageRef; +use crate::client::request_mismatch_mitigation_strategy::RequestMismatchMitigationStrategy; +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::gmicloud::seedance_2p0_g::request::GmiCloudSeedance2p0GRequestState; +use crate::generate::generate_video_v2::video_generation_draft_or_request::VideoGenerationDraftOrRequest; +use crate::generate::generate_video_v2::video_generation_request::VideoGenerationRequest; + +pub fn build_gmicloud_seedance_2p0_g( + mut builder: GenerateVideoRequestBuilder, +) -> Result { + let strategy = builder.request_mismatch_mitigation_strategy; + + let ratio = plan_ratio(builder.aspect_ratio.take(), strategy)?; + let resolution = plan_resolution(builder.resolution.take(), strategy)?; + let duration = builder.duration_seconds.take().map(|d| (d as u8).clamp(4, 15)); + let prompt = builder.prompt.take().unwrap_or_default(); + + let first_frame = resolve_url(builder.start_frame.take())?; + let last_frame = resolve_url(builder.end_frame.take())?; + + let request = Seedance20Request { + prompt, + duration, + resolution, + ratio, + seed: None, + watermark: None, + generate_audio: None, + web_search: None, + first_frame, + last_frame, + reference_images: None, + reference_videos: None, + reference_audios: None, + reference_asset_ids: None, + }; + + let state = GmiCloudSeedance2p0GRequestState { request }; + Ok(VideoGenerationDraftOrRequest::Request(VideoGenerationRequest::GmiCloudSeedance2p0G(state))) +} + +fn plan_ratio( + aspect_ratio: Option, + _strategy: RequestMismatchMitigationStrategy, +) -> Result, ArtcraftRouterError> { + match aspect_ratio { + None | Some(CommonAspectRatio::Auto) | Some(CommonAspectRatio::Auto2k) + | Some(CommonAspectRatio::Auto3k) | Some(CommonAspectRatio::Auto4k) => Ok(None), + Some(CommonAspectRatio::WideSixteenByNine) | Some(CommonAspectRatio::Wide) => Ok(Some(Seedance20Ratio::Landscape16x9)), + Some(CommonAspectRatio::TallNineBySixteen) | Some(CommonAspectRatio::Tall) => Ok(Some(Seedance20Ratio::Portrait9x16)), + Some(CommonAspectRatio::Square) | Some(CommonAspectRatio::SquareHd) => Ok(Some(Seedance20Ratio::Square)), + Some(CommonAspectRatio::WideFourByThree) => Ok(Some(Seedance20Ratio::Standard4x3)), + Some(CommonAspectRatio::TallThreeByFour) => Ok(Some(Seedance20Ratio::Portrait3x4)), + Some(CommonAspectRatio::WideTwentyOneByNine) | Some(CommonAspectRatio::TallNineByTwentyOne) => { + Ok(Some(Seedance20Ratio::UltraWide21x9)) + } + Some(CommonAspectRatio::WideThreeByTwo) | Some(CommonAspectRatio::WideFiveByFour) + | Some(CommonAspectRatio::TallFourByFive) | Some(CommonAspectRatio::TallTwoByThree) => { + Ok(Some(Seedance20Ratio::Adaptive)) + } + } +} + +fn plan_resolution( + resolution: Option, + _strategy: RequestMismatchMitigationStrategy, +) -> Result, ArtcraftRouterError> { + match resolution { + None => Ok(None), + Some(CommonResolution::FourEightyP) => Ok(Some(Seedance20Resolution::FourEightyP)), + Some(CommonResolution::SevenTwentyP) => Ok(Some(Seedance20Resolution::SevenTwentyP)), + Some(CommonResolution::TenEightyP) => Ok(Some(Seedance20Resolution::TenEightyP)), + Some(CommonResolution::HalfK) | Some(CommonResolution::OneK) => Ok(Some(Seedance20Resolution::FourEightyP)), + Some(CommonResolution::TwoK) | Some(CommonResolution::ThreeK) | Some(CommonResolution::FourK) => { + Ok(Some(Seedance20Resolution::TenEightyP)) + } + } +} + +fn resolve_url(image_ref: Option) -> Result, ArtcraftRouterError> { + match image_ref { + None => Ok(None), + Some(ImageRef::Url(url)) => Ok(Some(url)), + Some(ImageRef::MediaFileToken(_)) => { + Err(ArtcraftRouterError::Client(ClientError::ModelDoesNotSupportOption { + field: "start_frame/end_frame", + value: "GmiCloud only supports image URLs, not media file tokens".to_string(), + })) + } + } +} diff --git a/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video_v2/providers/gmicloud/seedance_2p0_g/cost.rs b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video_v2/providers/gmicloud/seedance_2p0_g/cost.rs new file mode 100644 index 0000000000..b89571095c --- /dev/null +++ b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video_v2/providers/gmicloud/seedance_2p0_g/cost.rs @@ -0,0 +1,27 @@ +use gmicloud_client::traits::gmicloud_request_cost_calculator_trait::GmiCloudRequestCostCalculator; + +use crate::generate::generate_video::video_generation_cost_estimate::VideoGenerationCostEstimate; +use crate::generate::generate_video_v2::providers::gmicloud::seedance_2p0_g::request::GmiCloudSeedance2p0GRequestState; + +pub struct GmiCloudSeedance2p0GCostState { + request: GmiCloudSeedance2p0GRequestState, +} + +impl GmiCloudSeedance2p0GCostState { + pub fn from_request(request: &GmiCloudSeedance2p0GRequestState) -> Self { + Self { request: request.clone() } + } + + pub fn estimate_cost(&self) -> VideoGenerationCostEstimate { + let cost_in_usd_cents = self.request.request.calculate_cost_in_cents(); + + VideoGenerationCostEstimate { + cost_in_credits: None, + cost_in_usd_cents: Some(cost_in_usd_cents), + is_free: false, + is_unlimited: false, + is_rate_limited: false, + has_watermark: false, + } + } +} diff --git a/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video_v2/providers/gmicloud/seedance_2p0_g/mod.rs b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video_v2/providers/gmicloud/seedance_2p0_g/mod.rs new file mode 100644 index 0000000000..98150bd014 --- /dev/null +++ b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video_v2/providers/gmicloud/seedance_2p0_g/mod.rs @@ -0,0 +1,3 @@ +pub mod build; +pub mod cost; +pub mod request; diff --git a/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video_v2/providers/gmicloud/seedance_2p0_g/request.rs b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video_v2/providers/gmicloud/seedance_2p0_g/request.rs new file mode 100644 index 0000000000..08e0a7fc2d --- /dev/null +++ b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video_v2/providers/gmicloud/seedance_2p0_g/request.rs @@ -0,0 +1,25 @@ +use gmicloud_client::requests::api::video::seedance_2_0_260128::api::Seedance20Request; + +use crate::client::router_gmicloud_client::RouterGmiCloudClient; +use crate::errors::artcraft_router_error::ArtcraftRouterError; +use crate::errors::provider_error::ProviderError; +use crate::generate::generate_video::generate_video_response::{ + GenerateVideoResponse, GmiCloudVideoResponsePayload, +}; + +#[derive(Clone, Debug)] +pub struct GmiCloudSeedance2p0GRequestState { + pub request: Seedance20Request, +} + +impl GmiCloudSeedance2p0GRequestState { + pub async fn send(&self, client: &RouterGmiCloudClient) -> Result { + let response = self.request.send_request(&client.api_key) + .await + .map_err(|err| ArtcraftRouterError::Provider(ProviderError::GmiCloud(err)))?; + + Ok(GenerateVideoResponse::GmiCloud(GmiCloudVideoResponsePayload { + request_id: response.request_id, + })) + } +} diff --git a/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video_v2/providers/mod.rs b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video_v2/providers/mod.rs index 6fe4c6b3b5..56e6b0e6dc 100644 --- a/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video_v2/providers/mod.rs +++ b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video_v2/providers/mod.rs @@ -1,2 +1,3 @@ pub mod artcraft; +pub mod gmicloud; pub mod kinovi; \ No newline at end of file diff --git a/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video_v2/video_generation_request.rs b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video_v2/video_generation_request.rs index 18a1151417..b1e67d0b05 100644 --- a/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video_v2/video_generation_request.rs +++ b/crates/api_clients/artcraft/artcraft_router/src/generate/generate_video_v2/video_generation_request.rs @@ -9,6 +9,10 @@ use crate::generate::generate_video_v2::providers::artcraft::seedance_2p0::cost: use crate::generate::generate_video_v2::providers::artcraft::seedance_2p0::request::ArtcraftSeedance2p0RequestState; use crate::generate::generate_video_v2::providers::artcraft::seedance_2p0_fast::cost::ArtcraftSeedance2p0FastCostState; use crate::generate::generate_video_v2::providers::artcraft::seedance_2p0_fast::request::ArtcraftSeedance2p0FastRequestState; +use crate::generate::generate_video_v2::providers::gmicloud::seedance_2p0_g::cost::GmiCloudSeedance2p0GCostState; +use crate::generate::generate_video_v2::providers::gmicloud::seedance_2p0_g::request::GmiCloudSeedance2p0GRequestState; +use crate::generate::generate_video_v2::providers::gmicloud::seedance_2p0_fast_g::cost::GmiCloudSeedance2p0FastGCostState; +use crate::generate::generate_video_v2::providers::gmicloud::seedance_2p0_fast_g::request::GmiCloudSeedance2p0FastGRequestState; use crate::generate::generate_video_v2::providers::kinovi::happy_horse_1p0::cost::KinoviHappyHorse1p0CostState; use crate::generate::generate_video_v2::providers::kinovi::happy_horse_1p0::request::KinoviHappyHorse1p0RequestState; use crate::generate::generate_video_v2::providers::kinovi::seedance_2p0::cost::KinoviSeedance2p0CostState; @@ -21,6 +25,8 @@ pub enum VideoGenerationRequest { ArtcraftHappyHorse1p0(ArtcraftHappyHorse1p0RequestState), ArtcraftSeedance2p0(ArtcraftSeedance2p0RequestState), ArtcraftSeedance2p0Fast(ArtcraftSeedance2p0FastRequestState), + GmiCloudSeedance2p0G(GmiCloudSeedance2p0GRequestState), + GmiCloudSeedance2p0FastG(GmiCloudSeedance2p0FastGRequestState), KinoviHappyHorse1p0(KinoviHappyHorse1p0RequestState), KinoviSeedance2p0(KinoviSeedance2p0RequestState), KinoviSeedance2p0Fast(KinoviSeedance2p0FastRequestState), @@ -33,6 +39,8 @@ impl VideoGenerationRequest { Self::ArtcraftHappyHorse1p0(_) => Provider::Artcraft, Self::ArtcraftSeedance2p0(_) => Provider::Artcraft, Self::ArtcraftSeedance2p0Fast(_) => Provider::Artcraft, + Self::GmiCloudSeedance2p0G(_) => Provider::GmiCloud, + Self::GmiCloudSeedance2p0FastG(_) => Provider::GmiCloud, Self::KinoviHappyHorse1p0(_) => Provider::Seedance2Pro, Self::KinoviSeedance2p0(_) => Provider::Seedance2Pro, Self::KinoviSeedance2p0Fast(_) => Provider::Seedance2Pro, @@ -45,6 +53,8 @@ impl VideoGenerationRequest { VideoGenerationRequest::ArtcraftHappyHorse1p0(request) => Ok(ArtcraftHappyHorse1p0CostState::from_request(request).estimate_cost()), VideoGenerationRequest::ArtcraftSeedance2p0(request) => Ok(ArtcraftSeedance2p0CostState::from_request(request).estimate_cost()), VideoGenerationRequest::ArtcraftSeedance2p0Fast(request) => Ok(ArtcraftSeedance2p0FastCostState::from_request(request).estimate_cost()), + VideoGenerationRequest::GmiCloudSeedance2p0G(request) => Ok(GmiCloudSeedance2p0GCostState::from_request(request).estimate_cost()), + VideoGenerationRequest::GmiCloudSeedance2p0FastG(request) => Ok(GmiCloudSeedance2p0FastGCostState::from_request(request).estimate_cost()), VideoGenerationRequest::KinoviHappyHorse1p0(request) => Ok(KinoviHappyHorse1p0CostState::from_request(request).estimate_cost()), VideoGenerationRequest::KinoviSeedance2p0(request) => Ok(KinoviSeedance2p0CostState::from_request(request).estimate_cost()), VideoGenerationRequest::KinoviSeedance2p0Fast(request) => Ok(KinoviSeedance2p0FastCostState::from_request(request).estimate_cost()), @@ -67,6 +77,14 @@ impl VideoGenerationRequest { let client_ref = client.get_artcraft_client_ref()?; request.send(client_ref).await }, + VideoGenerationRequest::GmiCloudSeedance2p0G(request) => { + let client_ref = client.get_gmicloud_client_ref()?; + request.send(client_ref).await + }, + VideoGenerationRequest::GmiCloudSeedance2p0FastG(request) => { + let client_ref = client.get_gmicloud_client_ref()?; + request.send(client_ref).await + }, VideoGenerationRequest::KinoviHappyHorse1p0(request) => { let client_ref = client.get_seedance2pro_client_ref()?; request.send(client_ref).await