artcraft_router

This commit is contained in:
Brandon Thomas
2026-05-16 09:52:46 -04:00
parent 1f1a75af1d
commit c40e1f4e74
23 changed files with 404 additions and 2 deletions
Generated
+1
View File
@@ -773,6 +773,7 @@ dependencies = [
"artcraft_client",
"enums",
"fal_client",
"gmicloud_client",
"log",
"muapi_client",
"reqwest 0.12.23",
@@ -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
@@ -6,6 +6,7 @@ use serde_derive::{Deserialize, Serialize};
pub enum Provider {
Artcraft,
Fal,
GmiCloud,
Muapi,
Seedance2Pro,
}
@@ -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;
@@ -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<RouterArtcraftClient>,
pub(crate) fal_client: Option<RouterFalClient>,
pub(crate) fal_webhook_optional_client: Option<RouterFalWebhookOptionalClient>,
pub(crate) gmicloud_client: Option<RouterGmiCloudClient>,
pub(crate) muapi_client: Option<RouterMuapiClient>,
pub(crate) seedance2pro_client: Option<RouterSeedance2ProClient>,
}
@@ -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))
@@ -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<RouterArtcraftClient>,
fal_client: Option<RouterFalClient>,
fal_webhook_optional_client: Option<RouterFalWebhookOptionalClient>,
gmicloud_client: Option<RouterGmiCloudClient>,
muapi_client: Option<RouterMuapiClient>,
seedance2pro_client: Option<RouterSeedance2ProClient>,
}
@@ -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,
}
@@ -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),
@@ -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 }
}
}
@@ -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"),
}
@@ -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<FalErrorPlus> for ProviderError {
}
}
impl From<GmiCloudError> for ProviderError {
fn from(error: GmiCloudError) -> Self {
Self::GmiCloud(error)
}
}
impl From<MuapiError> for ProviderError {
fn from(error: MuapiError) -> Self {
Self::Muapi(error)
@@ -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(),
}
}
@@ -34,9 +34,15 @@ pub struct FalVideoResponsePayload {
pub maybe_outbound_request: Option<Arc<dyn Debug + Send + Sync>>,
}
#[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<GmiCloudVideoResponsePayload> {
match self {
Self::GmiCloud(p) => Some(p.clone()),
_ => None,
}
}
pub fn get_fal_payload(&self) -> Option<FalVideoResponsePayload> {
match self {
Self::Fal(p) => Some(p.clone()),
@@ -0,0 +1,2 @@
pub mod seedance_2p0_g;
pub mod seedance_2p0_fast_g;
@@ -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<VideoGenerationDraftOrRequest, ArtcraftRouterError> {
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<CommonAspectRatio>,
_strategy: RequestMismatchMitigationStrategy,
) -> Result<Option<Seedance20FastRatio>, 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<CommonResolution>,
_strategy: RequestMismatchMitigationStrategy,
) -> Result<Option<Seedance20FastResolution>, 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<ImageRef>) -> Result<Option<String>, 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(),
}))
}
}
}
@@ -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,
}
}
}
@@ -0,0 +1,3 @@
pub mod build;
pub mod cost;
pub mod request;
@@ -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<GenerateVideoResponse, ArtcraftRouterError> {
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,
}))
}
}
@@ -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<VideoGenerationDraftOrRequest, ArtcraftRouterError> {
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<CommonAspectRatio>,
_strategy: RequestMismatchMitigationStrategy,
) -> Result<Option<Seedance20Ratio>, 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<CommonResolution>,
_strategy: RequestMismatchMitigationStrategy,
) -> Result<Option<Seedance20Resolution>, 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<ImageRef>) -> Result<Option<String>, 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(),
}))
}
}
}
@@ -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,
}
}
}
@@ -0,0 +1,3 @@
pub mod build;
pub mod cost;
pub mod request;
@@ -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<GenerateVideoResponse, ArtcraftRouterError> {
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,
}))
}
}
@@ -1,2 +1,3 @@
pub mod artcraft;
pub mod gmicloud;
pub mod kinovi;
@@ -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