From b66aef525f0e48816e85ee505d44d561b667169b Mon Sep 17 00:00:00 2001 From: Brandon Thomas Date: Wed, 18 Jun 2025 19:20:00 -0400 Subject: [PATCH] Veo and Seedance --- .../video/enqueue_image_to_video_command.rs | 4 ++ .../enqueue/video/handle_video_artcraft.rs | 57 ++++++++++++++++ .../core/events/generation_events/common.rs | 4 ++ ...nerate_seedance_1_0_lite_image_to_video.rs | 68 +++++++++++++++++++ .../video/generate_veo_2_image_to_video.rs | 68 +++++++++++++++++++ .../src/generate/video/mod.rs | 4 +- 6 files changed, 204 insertions(+), 1 deletion(-) create mode 100644 crates/lib/api_clients/storyteller_client/src/generate/video/generate_seedance_1_0_lite_image_to_video.rs create mode 100644 crates/lib/api_clients/storyteller_client/src/generate/video/generate_veo_2_image_to_video.rs diff --git a/crates/desktop/artcraft/src/core/commands/enqueue/video/enqueue_image_to_video_command.rs b/crates/desktop/artcraft/src/core/commands/enqueue/video/enqueue_image_to_video_command.rs index bc076744c7..cb80436360 100644 --- a/crates/desktop/artcraft/src/core/commands/enqueue/video/enqueue_image_to_video_command.rs +++ b/crates/desktop/artcraft/src/core/commands/enqueue/video/enqueue_image_to_video_command.rs @@ -81,6 +81,10 @@ pub enum EnqueueImageToVideoModel { Kling21Pro, #[serde(rename = "kling_2_1_master")] Kling21Master, + #[serde(rename = "seedance_1_0_lite")] + Seedance10Lite, + #[serde(rename = "veo_2")] + Veo2, } #[derive(Serialize)] diff --git a/crates/desktop/artcraft/src/core/commands/enqueue/video/handle_video_artcraft.rs b/crates/desktop/artcraft/src/core/commands/enqueue/video/handle_video_artcraft.rs index b5827405c3..817b60ee61 100644 --- a/crates/desktop/artcraft/src/core/commands/enqueue/video/handle_video_artcraft.rs +++ b/crates/desktop/artcraft/src/core/commands/enqueue/video/handle_video_artcraft.rs @@ -30,8 +30,13 @@ use storyteller_client::utils::api_host::ApiHost; use tauri::{AppHandle, State}; use artcraft_api_defs::generate::video::generate_kling_2_1_master_image_to_video::{GenerateKling21MasterAspectRatio, GenerateKling21MasterImageToVideoRequest}; use artcraft_api_defs::generate::video::generate_kling_2_1_pro_image_to_video::{GenerateKling21ProAspectRatio, GenerateKling21ProImageToVideoRequest}; +use artcraft_api_defs::generate::video::generate_seedance_1_0_lite_image_to_video::GenerateSeedance10LiteImageToVideoRequest; +use artcraft_api_defs::generate::video::generate_veo_2_image_to_video::{GenerateVeo2AspectRatio, GenerateVeo2ImageToVideoRequest}; +use fal_client::requests::webhook::video::enqueue_veo_2_image_to_video_webhook::Veo2AspectRatio; use storyteller_client::generate::video::generate_kling_21_master_image_to_video::generate_kling_21_master_image_to_video; use storyteller_client::generate::video::generate_kling_21_pro_image_to_video::generate_kling_21_pro_image_to_video; +use storyteller_client::generate::video::generate_seedance_1_0_lite_image_to_video::generate_seedance_1_0_lite_image_to_video; +use storyteller_client::generate::video::generate_veo_2_image_to_video::generate_veo_2_image_to_video; pub async fn handle_video_artcraft( request: EnqueueImageToVideoRequest, @@ -154,6 +159,58 @@ pub async fn handle_video_artcraft( } } } + Some(EnqueueImageToVideoModel::Seedance10Lite) => { + info!("enqueue Seedance 1.0 Lite with Artcraft API"); + selected_model = Some(GenerationModel::Seedance10Lite); + let request = GenerateSeedance10LiteImageToVideoRequest { + uuid_idempotency_token, + media_file_token: request.image_media_token, + prompt: None, + resolution: None, + duration: None, + }; + let result = generate_seedance_1_0_lite_image_to_video( + &ApiHost::Storyteller, + Some(&creds), + request, + ).await; + match result { + Ok(enqueued) => { + info!("Successfully enqueued Artcraft Seedance 1.0 Lite video generation"); + enqueued.inference_job_token + } + Err(err) => { + error!("Failed to use Artcraft Seedance 1.0 Lite video generation: {:?}", err); + return Err(InternalVideoError::StorytellerError(err)); + } + } + } + Some(EnqueueImageToVideoModel::Veo2) => { + info!("enqueue Veo 2 with Artcraft API"); + selected_model = Some(GenerationModel::Veo2); + let request = GenerateVeo2ImageToVideoRequest { + uuid_idempotency_token, + media_file_token: request.image_media_token, + aspect_ratio: Some(GenerateVeo2AspectRatio::WideSixteenNine), + prompt: None, + duration: None, + }; + let result = generate_veo_2_image_to_video( + &ApiHost::Storyteller, + Some(&creds), + request, + ).await; + match result { + Ok(enqueued) => { + info!("Successfully enqueued Artcraft Veo 2 video generation"); + enqueued.inference_job_token + } + Err(err) => { + error!("Failed to use Artcraft Veo 2 video generation: {:?}", err); + return Err(InternalVideoError::StorytellerError(err)); + } + } + } }; info!("Successfully enqueued video generation"); diff --git a/crates/desktop/artcraft/src/core/events/generation_events/common.rs b/crates/desktop/artcraft/src/core/events/generation_events/common.rs index 959c8bc0cf..df7d8dfe04 100644 --- a/crates/desktop/artcraft/src/core/events/generation_events/common.rs +++ b/crates/desktop/artcraft/src/core/events/generation_events/common.rs @@ -28,6 +28,10 @@ pub enum GenerationModel { #[serde(rename = "kling_2.1_pro")] Kling21Pro, Sora, + #[serde(rename = "seedance_1.0_lite")] + Seedance10Lite, + #[serde(rename = "veo_2")] + Veo2, } #[derive(Debug, Copy, Clone, Serialize)] diff --git a/crates/lib/api_clients/storyteller_client/src/generate/video/generate_seedance_1_0_lite_image_to_video.rs b/crates/lib/api_clients/storyteller_client/src/generate/video/generate_seedance_1_0_lite_image_to_video.rs new file mode 100644 index 0000000000..4b2c436114 --- /dev/null +++ b/crates/lib/api_clients/storyteller_client/src/generate/video/generate_seedance_1_0_lite_image_to_video.rs @@ -0,0 +1,68 @@ +use crate::credentials::storyteller_credential_set::StorytellerCredentialSet; +use crate::error::api_error::ApiError; +use crate::error::client_error::ClientError; +use crate::error::storyteller_error::StorytellerError; +use crate::utils::api_host::ApiHost; +use crate::utils::constants::{APPLICATION_JSON, USER_AGENT}; +use crate::utils::filter_bad_response::filter_bad_response; +use artcraft_api_defs::generate::video::generate_seedance_1_0_lite_image_to_video::{GenerateSeedance10LiteImageToVideoRequest, GenerateSeedance10LiteImageToVideoResponse, GENERATE_SEEDANCE_1_0_LITE_IMAGE_TO_VIDEO_URL_PATH}; +use log::{debug, info}; +use reqwest::Client; + +pub async fn generate_seedance_1_0_lite_image_to_video( + api_host: &ApiHost, + maybe_creds: Option<&StorytellerCredentialSet>, + request: GenerateSeedance10LiteImageToVideoRequest, +) -> Result { + + let url = get_route(api_host); + + info!("Requesting {:?}", &url); + + let client = Client::builder() + .gzip(true) + .build() + .map_err(|err| StorytellerError::Client(ClientError::from(err)))?; + + let mut request_builder = client.post(url) + .header("User-Agent", USER_AGENT) + .header("Accept", APPLICATION_JSON) + .header("Content-Type", APPLICATION_JSON); + + if let Some(creds) = maybe_creds { + if let Some(header) = &creds.maybe_as_cookie_header() { + request_builder = request_builder.header("Cookie", header); + } + } + + let request_body = serde_json::to_string(&request) + .map_err(|err| StorytellerError::Client(ClientError::from(err)))?; + + debug!("Request body: {:?}", request_body); + + let response = request_builder + .body(request_body) + .send() + .await + .map_err(|err| StorytellerError::Api(ApiError::from(err)))?; + + let response = filter_bad_response(response) + .await + .map_err(|err| StorytellerError::Api(ApiError::from(err)))?; + + let response_body = &response.text() + .await + .map_err(|err| StorytellerError::Api(ApiError::from(err)))?; + + debug!("Response body: {:?}", response_body); + + let response = serde_json::from_str(&response_body) + .map_err(|err| StorytellerError::Api(ApiError::from(err)))?; + + Ok(response) +} + +fn get_route(api_host: &ApiHost) -> String { + let api_hostname = api_host.to_api_hostname(); + format!("https://{}{}", api_hostname, GENERATE_SEEDANCE_1_0_LITE_IMAGE_TO_VIDEO_URL_PATH) +} diff --git a/crates/lib/api_clients/storyteller_client/src/generate/video/generate_veo_2_image_to_video.rs b/crates/lib/api_clients/storyteller_client/src/generate/video/generate_veo_2_image_to_video.rs new file mode 100644 index 0000000000..03c06d6a99 --- /dev/null +++ b/crates/lib/api_clients/storyteller_client/src/generate/video/generate_veo_2_image_to_video.rs @@ -0,0 +1,68 @@ +use crate::credentials::storyteller_credential_set::StorytellerCredentialSet; +use crate::error::api_error::ApiError; +use crate::error::client_error::ClientError; +use crate::error::storyteller_error::StorytellerError; +use crate::utils::api_host::ApiHost; +use crate::utils::constants::{APPLICATION_JSON, USER_AGENT}; +use crate::utils::filter_bad_response::filter_bad_response; +use artcraft_api_defs::generate::video::generate_veo_2_image_to_video::{GenerateVeo2ImageToVideoRequest, GenerateVeo2ImageToVideoResponse, GENERATE_VEO_2_IMAGE_TO_VIDEO_URL_PATH}; +use log::{debug, info}; +use reqwest::Client; + +pub async fn generate_veo_2_image_to_video( + api_host: &ApiHost, + maybe_creds: Option<&StorytellerCredentialSet>, + request: GenerateVeo2ImageToVideoRequest, +) -> Result { + + let url = get_route(api_host); + + info!("Requesting {:?}", &url); + + let client = Client::builder() + .gzip(true) + .build() + .map_err(|err| StorytellerError::Client(ClientError::from(err)))?; + + let mut request_builder = client.post(url) + .header("User-Agent", USER_AGENT) + .header("Accept", APPLICATION_JSON) + .header("Content-Type", APPLICATION_JSON); + + if let Some(creds) = maybe_creds { + if let Some(header) = &creds.maybe_as_cookie_header() { + request_builder = request_builder.header("Cookie", header); + } + } + + let request_body = serde_json::to_string(&request) + .map_err(|err| StorytellerError::Client(ClientError::from(err)))?; + + debug!("Request body: {:?}", request_body); + + let response = request_builder + .body(request_body) + .send() + .await + .map_err(|err| StorytellerError::Api(ApiError::from(err)))?; + + let response = filter_bad_response(response) + .await + .map_err(|err| StorytellerError::Api(ApiError::from(err)))?; + + let response_body = &response.text() + .await + .map_err(|err| StorytellerError::Api(ApiError::from(err)))?; + + debug!("Response body: {:?}", response_body); + + let response = serde_json::from_str(&response_body) + .map_err(|err| StorytellerError::Api(ApiError::from(err)))?; + + Ok(response) +} + +fn get_route(api_host: &ApiHost) -> String { + let api_hostname = api_host.to_api_hostname(); + format!("https://{}{}", api_hostname, GENERATE_VEO_2_IMAGE_TO_VIDEO_URL_PATH) +} diff --git a/crates/lib/api_clients/storyteller_client/src/generate/video/mod.rs b/crates/lib/api_clients/storyteller_client/src/generate/video/mod.rs index 3550db92ce..6feaa265f0 100644 --- a/crates/lib/api_clients/storyteller_client/src/generate/video/mod.rs +++ b/crates/lib/api_clients/storyteller_client/src/generate/video/mod.rs @@ -1,3 +1,5 @@ pub mod generate_kling_16_pro_image_to_video; pub mod generate_kling_21_master_image_to_video; -pub mod generate_kling_21_pro_image_to_video; \ No newline at end of file +pub mod generate_kling_21_pro_image_to_video; +pub mod generate_seedance_1_0_lite_image_to_video; +pub mod generate_veo_2_image_to_video; \ No newline at end of file