diff --git a/crates/lib/artcraft_router/Cargo.toml b/crates/lib/artcraft_router/Cargo.toml new file mode 100644 index 0000000000..e282013544 --- /dev/null +++ b/crates/lib/artcraft_router/Cargo.toml @@ -0,0 +1,29 @@ +[package] +name = "artcraft_router" +edition = "2024" +version = "0.0.1" +publish = false + +[package.metadata.cargo-udeps.ignore] +normal = ["workspace-build-acceleration"] + +[lib] +name = "artcraft_router" +path = "src/lib.rs" + +[dependencies] +# Workspace hack (faster compile times) +workspace-build-acceleration.workspace = true + +# Internal +artcraft_api_defs.workspace = true +storyteller_client.workspace = true +tokens.workspace = true + +# External +anyhow.workspace = true +log.workspace = true +serde.workspace = true +serde_derive.workspace = true +tokio = { version = "1", features = ["full"] } +uuid = { version = "1", features = ["v4"] } diff --git a/crates/lib/artcraft_router/src/api/common_aspect_ratio.rs b/crates/lib/artcraft_router/src/api/common_aspect_ratio.rs new file mode 100644 index 0000000000..c16d63e159 --- /dev/null +++ b/crates/lib/artcraft_router/src/api/common_aspect_ratio.rs @@ -0,0 +1,42 @@ +use serde_derive::{Deserialize, Serialize}; + +/// This is a comprehensive list of common aspect ratios you can specify when enqueuing a generation. +/// Not every model will support every aspect ratio. +/// In the case a model doesn't support the aspect ratio, gracefully pick the nearest option. +#[derive(Copy, Clone, Debug, Serialize, Deserialize)] +#[serde(rename_all = "snake_case")] +pub enum CommonAspectRatio { + // Auto (eg. for image editing to use the source; used by Nano Banana Pro edit image, but not text-to-image) + Auto, + + // Square + Square, + + // Wide + WideThreeByTwo, + WideFourByThree, + WideFiveByFour, + WideSixteenByNine, + WideTwentyOneByNine, + + // Tall + TallTwoByThree, + TallThreeByFour, + TallFourByFive, + TallNineBySixteen, + TallNineByTwentyOne, + + // Imprecise semantic values that we probably remap to other meanings + // on a model-by-model basis. + Wide, + Tall, + + // Auto values that bake in resolution + // These are from the Seedream models + Auto2k, + Auto4k, + + // Defined aspect ratios that bake in resolution + // These are from the Seedream models + SquareHd, +} diff --git a/crates/lib/artcraft_router/src/api/common_resolution.rs b/crates/lib/artcraft_router/src/api/common_resolution.rs new file mode 100644 index 0000000000..69d89c7cc9 --- /dev/null +++ b/crates/lib/artcraft_router/src/api/common_resolution.rs @@ -0,0 +1,17 @@ +use serde_derive::{Deserialize, Serialize}; + +/// This is a comprehensive list of common resolutions you can specify when enqueuing a generation. +/// Not every model will support every resolution. +/// In the case a model doesn't support the resolution, gracefully pick the nearest option. +#[derive(Copy, Clone, Debug, Serialize, Deserialize)] +#[serde(rename_all = "snake_case")] +pub enum CommonVideoResolution { + /// Models: Nano Banana Pro + OneK, + /// Models: Nano Banana Pro + TwoK, + /// Models: (None) + ThreeK, + /// Models: Nano Banana Pro + FourK, +} diff --git a/crates/lib/artcraft_router/src/api/common_video_model.rs b/crates/lib/artcraft_router/src/api/common_video_model.rs new file mode 100644 index 0000000000..65d723eb1c --- /dev/null +++ b/crates/lib/artcraft_router/src/api/common_video_model.rs @@ -0,0 +1,52 @@ +use serde_derive::{Deserialize, Serialize}; + +/// Common video models supported by the router. +/// Not all models are available through all providers. +#[derive(Copy, Clone, Debug, Serialize, Deserialize)] +#[serde(rename_all = "snake_case")] +pub enum CommonVideoModel { + #[serde(rename = "grok_video")] + GrokVideo, + + #[serde(rename = "kling_1.6_pro")] + Kling16Pro, + + #[serde(rename = "kling_2.1_pro")] + Kling21Pro, + + #[serde(rename = "kling_2.1_master")] + Kling21Master, + + #[serde(rename = "kling_2p5_turbo_pro")] + Kling2p5TurboPro, + + #[serde(rename = "kling_2p6_pro")] + Kling2p6Pro, + + #[serde(rename = "seedance_1.0_lite")] + Seedance10Lite, + + #[serde(rename = "seedance_2p0")] + Seedance2p0, + + #[serde(rename = "sora_2")] + Sora2, + + #[serde(rename = "sora_2_pro")] + Sora2Pro, + + #[serde(rename = "veo_2")] + Veo2, + + #[serde(rename = "veo_3")] + Veo3, + + #[serde(rename = "veo_3_fast")] + Veo3Fast, + + #[serde(rename = "veo_3p1")] + Veo3p1, + + #[serde(rename = "veo_3p1_fast")] + Veo3p1Fast, +} diff --git a/crates/lib/artcraft_router/src/api/mod.rs b/crates/lib/artcraft_router/src/api/mod.rs new file mode 100644 index 0000000000..755a36706c --- /dev/null +++ b/crates/lib/artcraft_router/src/api/mod.rs @@ -0,0 +1,3 @@ +pub mod common_aspect_ratio; +pub mod common_resolution; +pub mod common_video_model; diff --git a/crates/lib/artcraft_router/src/errors/artcraft_router_error.rs b/crates/lib/artcraft_router/src/errors/artcraft_router_error.rs new file mode 100644 index 0000000000..520d634863 --- /dev/null +++ b/crates/lib/artcraft_router/src/errors/artcraft_router_error.rs @@ -0,0 +1,33 @@ +use crate::errors::provider_error::ProviderError; +use std::error::Error; +use std::fmt::{Display, Formatter}; + +#[derive(Debug)] +pub enum ArtcraftRouterError { + /// The requested model is not yet supported by the router. + UnsupportedModel(String), + + /// Invalid or missing input arguments. + InvalidInput(String), + + /// An error from an underlying provider. + Provider(ProviderError), +} + +impl Error for ArtcraftRouterError {} + +impl Display for ArtcraftRouterError { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + Self::UnsupportedModel(model) => write!(f, "Unsupported model: {}", model), + Self::InvalidInput(msg) => write!(f, "Invalid input: {}", msg), + Self::Provider(e) => write!(f, "Provider error: {}", e), + } + } +} + +impl From for ArtcraftRouterError { + fn from(error: ProviderError) -> Self { + Self::Provider(error) + } +} diff --git a/crates/lib/artcraft_router/src/errors/mod.rs b/crates/lib/artcraft_router/src/errors/mod.rs new file mode 100644 index 0000000000..541349864c --- /dev/null +++ b/crates/lib/artcraft_router/src/errors/mod.rs @@ -0,0 +1,2 @@ +pub mod artcraft_router_error; +pub mod provider_error; diff --git a/crates/lib/artcraft_router/src/errors/provider_error.rs b/crates/lib/artcraft_router/src/errors/provider_error.rs new file mode 100644 index 0000000000..0cc285ed16 --- /dev/null +++ b/crates/lib/artcraft_router/src/errors/provider_error.rs @@ -0,0 +1,24 @@ +use std::error::Error; +use std::fmt::{Display, Formatter}; +use storyteller_client::error::storyteller_error::StorytellerError; + +#[derive(Debug)] +pub enum ProviderError { + Storyteller(StorytellerError), +} + +impl Error for ProviderError {} + +impl Display for ProviderError { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + Self::Storyteller(e) => write!(f, "Storyteller provider error: {}", e), + } + } +} + +impl From for ProviderError { + fn from(error: StorytellerError) -> Self { + Self::Storyteller(error) + } +} diff --git a/crates/lib/artcraft_router/src/generate/generate_video/generate_video.rs b/crates/lib/artcraft_router/src/generate/generate_video/generate_video.rs new file mode 100644 index 0000000000..f3d1c0a4dd --- /dev/null +++ b/crates/lib/artcraft_router/src/generate/generate_video/generate_video.rs @@ -0,0 +1,37 @@ +use crate::api::common_aspect_ratio::CommonAspectRatio; +use crate::api::common_resolution::CommonVideoResolution; +use crate::api::common_video_model::CommonVideoModel; +use crate::errors::artcraft_router_error::ArtcraftRouterError; +use crate::generate::generate_video::providers::artcraft::generate_video_artcraft_seedance2p0::generate_video_artcraft_seedance2p0; +use storyteller_client::credentials::storyteller_credential_set::StorytellerCredentialSet; +use storyteller_client::utils::api_host::ApiHost; +use tokens::tokens::generic_inference_jobs::InferenceJobToken; + +pub struct GenerateVideoArgs { + pub model: CommonVideoModel, + pub resolution: Option, + pub aspect_ratio: Option, + pub prompt: Option, +} + +pub struct GenerateVideoResponse { + pub inference_job_token: InferenceJobToken, + pub all_inference_job_tokens: Vec, +} + +pub struct GenerateVideoContext<'a> { + pub api_host: &'a ApiHost, + pub storyteller_creds: Option<&'a StorytellerCredentialSet>, +} + +pub async fn generate_video( + args: &GenerateVideoArgs, + context: &GenerateVideoContext<'_>, +) -> Result { + match args.model { + CommonVideoModel::Seedance2p0 => { + generate_video_artcraft_seedance2p0(args, context.api_host, context.storyteller_creds).await + } + _ => Err(ArtcraftRouterError::UnsupportedModel(format!("{:?}", args.model))), + } +} diff --git a/crates/lib/artcraft_router/src/generate/generate_video/mod.rs b/crates/lib/artcraft_router/src/generate/generate_video/mod.rs new file mode 100644 index 0000000000..b14e2cbcc1 --- /dev/null +++ b/crates/lib/artcraft_router/src/generate/generate_video/mod.rs @@ -0,0 +1,2 @@ +pub mod generate_video; +pub mod providers; diff --git a/crates/lib/artcraft_router/src/generate/generate_video/providers/artcraft/generate_video_artcraft_seedance2p0.rs b/crates/lib/artcraft_router/src/generate/generate_video/providers/artcraft/generate_video_artcraft_seedance2p0.rs new file mode 100644 index 0000000000..3c1141e7fc --- /dev/null +++ b/crates/lib/artcraft_router/src/generate/generate_video/providers/artcraft/generate_video_artcraft_seedance2p0.rs @@ -0,0 +1,55 @@ +use crate::api::common_aspect_ratio::CommonAspectRatio; +use crate::errors::artcraft_router_error::ArtcraftRouterError; +use crate::errors::provider_error::ProviderError; +use crate::generate::generate_video::generate_video::{GenerateVideoArgs, GenerateVideoResponse}; +use artcraft_api_defs::generate::video::multi_function::seedance_2p0_multi_function_video_gen::{ + Seedance2p0AspectRatio, Seedance2p0BatchCount, Seedance2p0MultiFunctionVideoGenRequest, +}; +use storyteller_client::credentials::storyteller_credential_set::StorytellerCredentialSet; +use storyteller_client::endpoints::generate::video::multi_function::seedance_2p0_multi_function_video_gen::seedance_2p0_multi_function_video_gen; +use storyteller_client::utils::api_host::ApiHost; +use uuid::Uuid; + +fn map_aspect_ratio(aspect_ratio: Option) -> Option { + match aspect_ratio { + Some(CommonAspectRatio::WideSixteenByNine) | Some(CommonAspectRatio::Wide) => { + Some(Seedance2p0AspectRatio::Landscape16x9) + } + Some(CommonAspectRatio::TallNineBySixteen) | Some(CommonAspectRatio::Tall) => { + Some(Seedance2p0AspectRatio::Portrait9x16) + } + Some(CommonAspectRatio::Square) => Some(Seedance2p0AspectRatio::Square1x1), + Some(CommonAspectRatio::WideFourByThree) => Some(Seedance2p0AspectRatio::Standard4x3), + Some(CommonAspectRatio::TallThreeByFour) => Some(Seedance2p0AspectRatio::Portrait3x4), + _ => None, + } +} + +pub async fn generate_video_artcraft_seedance2p0( + args: &GenerateVideoArgs, + api_host: &ApiHost, + creds: Option<&StorytellerCredentialSet>, +) -> Result { + let uuid_idempotency_token = Uuid::new_v4().to_string(); + let aspect_ratio = map_aspect_ratio(args.aspect_ratio); + + let request = Seedance2p0MultiFunctionVideoGenRequest { + uuid_idempotency_token, + prompt: args.prompt.clone(), + start_frame_media_token: None, + end_frame_media_token: None, + reference_image_media_tokens: None, + aspect_ratio, + duration_seconds: None, + batch_count: Some(Seedance2p0BatchCount::One), + }; + + let response = seedance_2p0_multi_function_video_gen(api_host, creds, request) + .await + .map_err(|err| ArtcraftRouterError::Provider(ProviderError::Storyteller(err)))?; + + Ok(GenerateVideoResponse { + inference_job_token: response.inference_job_token, + all_inference_job_tokens: response.all_inference_job_tokens, + }) +} diff --git a/crates/lib/artcraft_router/src/generate/generate_video/providers/artcraft/mod.rs b/crates/lib/artcraft_router/src/generate/generate_video/providers/artcraft/mod.rs new file mode 100644 index 0000000000..07b2f3c07e --- /dev/null +++ b/crates/lib/artcraft_router/src/generate/generate_video/providers/artcraft/mod.rs @@ -0,0 +1 @@ +pub mod generate_video_artcraft_seedance2p0; diff --git a/crates/lib/artcraft_router/src/generate/generate_video/providers/mod.rs b/crates/lib/artcraft_router/src/generate/generate_video/providers/mod.rs new file mode 100644 index 0000000000..e3a8f2a048 --- /dev/null +++ b/crates/lib/artcraft_router/src/generate/generate_video/providers/mod.rs @@ -0,0 +1 @@ +pub mod artcraft; diff --git a/crates/lib/artcraft_router/src/generate/mod.rs b/crates/lib/artcraft_router/src/generate/mod.rs new file mode 100644 index 0000000000..2492e000e2 --- /dev/null +++ b/crates/lib/artcraft_router/src/generate/mod.rs @@ -0,0 +1 @@ +pub mod generate_video; diff --git a/crates/lib/artcraft_router/src/lib.rs b/crates/lib/artcraft_router/src/lib.rs new file mode 100644 index 0000000000..8e15b974d3 --- /dev/null +++ b/crates/lib/artcraft_router/src/lib.rs @@ -0,0 +1,3 @@ +pub mod api; +pub mod errors; +pub mod generate;