From 424a49c6a2a3b02fda06f40eaca5b2ff98b68bd4 Mon Sep 17 00:00:00 2001 From: Brandon Thomas Date: Thu, 28 Aug 2025 09:56:06 -0400 Subject: [PATCH] add veo3 handler --- .../enqueue_veo_3_image_to_video_webhook.rs | 34 ++- .../video/generate_veo_3_image_to_video.rs | 65 +++++ .../src/generate/video/mod.rs | 3 +- .../public/enums/src/common/model_type.rs | 11 +- .../generate_veo_3_image_to_video_handler.rs | 254 ++++++++++++++++++ .../endpoints/generate/video/mod.rs | 3 +- .../http_server/routes/add_generate_routes.rs | 11 +- .../endpoints/fal_ai/veo3/image_to_video.rs | 15 +- 8 files changed, 375 insertions(+), 21 deletions(-) create mode 100644 crates/schema/public/api/artcraft_api_defs/src/generate/video/generate_veo_3_image_to_video.rs create mode 100644 crates/service/web/storyteller_web/src/http_server/endpoints/generate/video/generate_veo_3_image_to_video_handler.rs diff --git a/crates/api_clients/fal_client/src/requests/webhook/video/enqueue_veo_3_image_to_video_webhook.rs b/crates/api_clients/fal_client/src/requests/webhook/video/enqueue_veo_3_image_to_video_webhook.rs index c4d843b358..5a5ff02a26 100644 --- a/crates/api_clients/fal_client/src/requests/webhook/video/enqueue_veo_3_image_to_video_webhook.rs +++ b/crates/api_clients/fal_client/src/requests/webhook/video/enqueue_veo_3_image_to_video_webhook.rs @@ -6,12 +6,13 @@ use fal::webhook::WebhookResponse; use reqwest::IntoUrl; pub struct Veo3Args<'a, U: IntoUrl, V: IntoUrl> { - pub prompt: &'a str, pub image_url: U, + pub prompt: &'a str, pub duration: Veo3Duration, - pub api_key: &'a FalApiKey, + pub aspect_ratio: Veo3AspectRatio, pub resolution: Veo3Resolution, pub generate_audio: bool, + pub api_key: &'a FalApiKey, pub webhook_url: V, } @@ -21,6 +22,14 @@ pub enum Veo3Duration { EightSeconds, } +#[derive(Copy, Clone, Debug)] +pub enum Veo3AspectRatio { + Default, + WideSixteenNine, + TallNineSixteen, + Square, +} + #[derive(Copy, Clone, Debug)] pub enum Veo3Resolution { Default, @@ -35,6 +44,13 @@ pub async fn enqueue_veo_3_image_to_video_webhook( Veo3Duration::Default => None, Veo3Duration::EightSeconds => Some("8s".to_string()), }; + + let aspect_ratio = match args.aspect_ratio { + Veo3AspectRatio::Default => None, + Veo3AspectRatio::WideSixteenNine => Some("16:9".to_string()), + Veo3AspectRatio::TallNineSixteen => Some("9:16".to_string()), + Veo3AspectRatio::Square => Some("1:1".to_string()), + }; let resolution= match args.resolution { Veo3Resolution::Default => None, @@ -47,6 +63,7 @@ pub async fn enqueue_veo_3_image_to_video_webhook( let request = ImageToVideoInput { image_url, prompt: args.prompt.to_string(), + aspect_ratio, resolution, duration, generate_audio: Some(args.generate_audio), @@ -64,28 +81,27 @@ pub async fn enqueue_veo_3_image_to_video_webhook( #[cfg(test)] mod tests { use crate::creds::fal_api_key::FalApiKey; - use crate::requests::webhook::video::enqueue_veo_3_image_to_video_webhook::{enqueue_veo_3_image_to_video_webhook, Veo3Args, Veo3Duration, Veo3Resolution}; + use crate::requests::webhook::video::enqueue_veo_3_image_to_video_webhook::{enqueue_veo_3_image_to_video_webhook, Veo3Args, Veo3AspectRatio, Veo3Duration, Veo3Resolution}; use errors::AnyhowResult; use std::fs::read_to_string; - use test_data::web::image_urls::JUNO_AT_LAKE_IMAGE_URL; + use test_data::web::image_urls::{JUNO_AT_LAKE_IMAGE_URL, TREX_SKELETON_IMAGE_URL}; #[tokio::test] #[ignore] async fn test() -> AnyhowResult<()> { - let image_url = JUNO_AT_LAKE_IMAGE_URL; - // XXX: Don't commit secrets! let secret = read_to_string("/Users/bt/Artcraft/credentials/fal_api_key.txt")?; let api_key = FalApiKey::from_str(&secret); let args = Veo3Args { - image_url: image_url, - prompt: "corgi at the lake. the corgi barks. camera pulls back to show a wider shot of the lake as the corgi jumps into the water. there are sounds of barking and splashing. cinematic, 8k, ultra wide shot", + image_url: TREX_SKELETON_IMAGE_URL, + prompt: "the t-rex skeleton starts walking towards the camera and roars", api_key: &api_key, duration: Veo3Duration::EightSeconds, - generate_audio: true, + aspect_ratio: Veo3AspectRatio::WideSixteenNine, resolution: Veo3Resolution::TenEightyP, + generate_audio: true, webhook_url: "https://example.com/webhook", }; diff --git a/crates/schema/public/api/artcraft_api_defs/src/generate/video/generate_veo_3_image_to_video.rs b/crates/schema/public/api/artcraft_api_defs/src/generate/video/generate_veo_3_image_to_video.rs new file mode 100644 index 0000000000..9325cf1350 --- /dev/null +++ b/crates/schema/public/api/artcraft_api_defs/src/generate/video/generate_veo_3_image_to_video.rs @@ -0,0 +1,65 @@ +use serde::{Deserialize, Serialize}; +use tokens::tokens::generic_inference_jobs::InferenceJobToken; +use tokens::tokens::media_files::MediaFileToken; +use utoipa::ToSchema; + +pub const GENERATE_VEO_3_IMAGE_TO_VIDEO_URL_PATH: &str = "/v1/generate/video/veo_3_image_to_video"; + +#[derive(Serialize, Deserialize, ToSchema)] +pub struct GenerateVeo3ImageToVideoRequest { + /// Idempotency token to prevent duplicate requests. + pub uuid_idempotency_token: String, + + /// Source of the image file to convert to video. + /// It must be an image. + pub media_file_token: Option, + + /// Optional text prompt. + pub prompt: Option, + + /// Optional: aspect ratio of the generated video. + pub aspect_ratio: Option, + + /// Optional: resolution of the generated video. + pub resolution: Option, + + /// Optional: aspect ratio of the generated video. + pub duration: Option, + + /// Optional: Generate an audio track along with the video + pub generate_audio: Option, + + // /// Optional. + // pub enhance_prompt: Option, + // /// Optional. Whether to automatically attempt to fix prompts that fail + // /// content policy or other validation checks by rewriting them + // /// Default value: true + // pub auto_fix: Option, +} + +#[derive(Serialize, Deserialize, ToSchema)] +#[serde(rename_all = "snake_case")] +pub enum GenerateVeo3AspectRatio { + WideSixteenNine, // 16:9 + TallNineSixteen, // 9:16 + Square, // 1:1 +} + +#[derive(Serialize, Deserialize, ToSchema)] +#[serde(rename_all = "snake_case")] +pub enum GenerateVeo3Resolution { + SevenTwentyP, // 720p + TenEightyP, // 1080p +} + +#[derive(Serialize, Deserialize, ToSchema)] +#[serde(rename_all = "snake_case")] +pub enum GenerateVeo3Duration { + EightSeconds, +} + +#[derive(Serialize, Deserialize, ToSchema)] +pub struct GenerateVeo3ImageToVideoResponse { + pub success: bool, + pub inference_job_token: InferenceJobToken, +} diff --git a/crates/schema/public/api/artcraft_api_defs/src/generate/video/mod.rs b/crates/schema/public/api/artcraft_api_defs/src/generate/video/mod.rs index 9dae98d730..f0bf537f8f 100644 --- a/crates/schema/public/api/artcraft_api_defs/src/generate/video/mod.rs +++ b/crates/schema/public/api/artcraft_api_defs/src/generate/video/mod.rs @@ -2,4 +2,5 @@ pub mod generate_kling_1_6_pro_image_to_video; pub mod generate_kling_2_1_master_image_to_video; pub mod generate_kling_2_1_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 +pub mod generate_veo_2_image_to_video; +pub mod generate_veo_3_image_to_video; \ No newline at end of file diff --git a/crates/schema/public/enums/src/common/model_type.rs b/crates/schema/public/enums/src/common/model_type.rs index 34c1a2ba16..9d3fdf2126 100644 --- a/crates/schema/public/enums/src/common/model_type.rs +++ b/crates/schema/public/enums/src/common/model_type.rs @@ -67,6 +67,8 @@ pub enum ModelType { Seedance10Lite, #[serde(rename = "veo_2")] Veo2, + #[serde(rename = "veo_3")] + Veo3, // 3D Object generation models #[serde(rename = "hunyuan_3d_2p0")] @@ -110,6 +112,7 @@ impl ModelType { Self::Kling21Master => "kling_2p1_master", Self::Seedance10Lite => "seedance_1p0_lite", Self::Veo2 => "veo_2", + Self::Veo3 => "veo_3", // 3D Object generation models Self::Hunyuan3d2_0 => "hunyuan_3d_2p0", @@ -145,6 +148,7 @@ impl ModelType { "kling_2p1_master" => Ok(Self::Kling21Master), "seedance_1p0_lite" => Ok(Self::Seedance10Lite), "veo_2" => Ok(Self::Veo2), + "veo_3" => Ok(Self::Veo3), // 3D Object generation models "hunyuan_3d_2p0" => Ok(Self::Hunyuan3d2_0), @@ -184,6 +188,7 @@ impl ModelType { Self::Kling21Master, Self::Seedance10Lite, Self::Veo2, + Self::Veo3, // 3D Object generation models Self::Hunyuan3d2_0, @@ -227,6 +232,7 @@ mod tests { assert_serialization(ModelType::Kling21Master, "kling_2p1_master"); assert_serialization(ModelType::Seedance10Lite, "seedance_1p0_lite"); assert_serialization(ModelType::Veo2, "veo_2"); + assert_serialization(ModelType::Veo3, "veo_3"); // 3D Object generation models assert_serialization(ModelType::Hunyuan3d2_0, "hunyuan_3d_2p0"); assert_serialization(ModelType::Hunyuan3d2_1, "hunyuan_3d_2p1"); @@ -260,6 +266,7 @@ mod tests { assert_eq!(ModelType::Kling21Master.to_str(), "kling_2p1_master"); assert_eq!(ModelType::Seedance10Lite.to_str(), "seedance_1p0_lite"); assert_eq!(ModelType::Veo2.to_str(), "veo_2"); + assert_eq!(ModelType::Veo3.to_str(), "veo_3"); // 3D Object generation models assert_eq!(ModelType::Hunyuan3d2_0.to_str(), "hunyuan_3d_2p0"); @@ -293,6 +300,7 @@ mod tests { assert_eq!(ModelType::from_str("kling_2p1_master").unwrap(), ModelType::Kling21Master); assert_eq!(ModelType::from_str("seedance_1p0_lite").unwrap(), ModelType::Seedance10Lite); assert_eq!(ModelType::from_str("veo_2").unwrap(), ModelType::Veo2); + assert_eq!(ModelType::from_str("veo_3").unwrap(), ModelType::Veo3); // 3D Object generation models assert_eq!(ModelType::from_str("hunyuan_3d_2p0").unwrap(), ModelType::Hunyuan3d2_0); assert_eq!(ModelType::from_str("hunyuan_3d_2p1").unwrap(), ModelType::Hunyuan3d2_1); @@ -301,7 +309,7 @@ mod tests { #[test] fn all_variants() { let mut variants = ModelType::all_variants(); - assert_eq!(variants.len(), 25); + assert_eq!(variants.len(), 26); // Image models assert_eq!(variants.pop_first(), Some(ModelType::Flux1Dev)); assert_eq!(variants.pop_first(), Some(ModelType::Flux1Schnell)); @@ -327,6 +335,7 @@ mod tests { assert_eq!(variants.pop_first(), Some(ModelType::Kling21Master)); assert_eq!(variants.pop_first(), Some(ModelType::Seedance10Lite)); assert_eq!(variants.pop_first(), Some(ModelType::Veo2)); + assert_eq!(variants.pop_first(), Some(ModelType::Veo3)); // 3D Object generation models assert_eq!(variants.pop_first(), Some(ModelType::Hunyuan3d2_0)); assert_eq!(variants.pop_first(), Some(ModelType::Hunyuan3d2_1)); diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/generate/video/generate_veo_3_image_to_video_handler.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/generate/video/generate_veo_3_image_to_video_handler.rs new file mode 100644 index 0000000000..5b5bffc24c --- /dev/null +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/generate/video/generate_veo_3_image_to_video_handler.rs @@ -0,0 +1,254 @@ +use std::sync::Arc; + +use crate::http_server::common_responses::common_web_error::CommonWebError; +use crate::http_server::common_responses::media::media_links_builder::MediaLinksBuilder; +use crate::http_server::endpoints::analytics::log_browser_session_handler::LogBrowserSessionError; +use crate::http_server::endpoints::media_files::helpers::get_media_domain::get_media_domain; +use crate::http_server::validations::validate_idempotency_token_format::validate_idempotency_token_format; +use crate::state::server_state::ServerState; +use actix_web::web::Json; +use actix_web::{web, HttpRequest}; +use anyhow::anyhow; +use artcraft_api_defs::generate::video::generate_veo_3_image_to_video::{GenerateVeo3AspectRatio, GenerateVeo3Duration, GenerateVeo3ImageToVideoRequest, GenerateVeo3ImageToVideoResponse, GenerateVeo3Resolution}; +use bucket_paths::legacy::typified_paths::public::media_files::bucket_file_path::MediaFileBucketPath; +use enums::by_table::prompts::prompt_type::PromptType; +use enums::common::generation_provider::GenerationProvider; +use enums::common::model_type::ModelType; +use enums::common::visibility::Visibility; +use fal_client::requests::webhook::video::enqueue_veo_3_image_to_video_webhook::{enqueue_veo_3_image_to_video_webhook, Veo3Args, Veo3AspectRatio, Veo3Duration, Veo3Resolution}; +use http_server_common::request::get_request_ip::get_request_ip; +use log::{error, info, warn}; +use mysql_queries::queries::generic_inference::fal::insert_generic_inference_job_for_fal_queue::insert_generic_inference_job_for_fal_queue; +use mysql_queries::queries::generic_inference::fal::insert_generic_inference_job_for_fal_queue::FalCategory; +use mysql_queries::queries::generic_inference::fal::insert_generic_inference_job_for_fal_queue::InsertGenericInferenceForFalArgs; +use mysql_queries::queries::idepotency_tokens::insert_idempotency_token::insert_idempotency_token; +use mysql_queries::queries::media_files::get::get_media_file::get_media_file_with_connection; +use mysql_queries::queries::prompts::insert_prompt::{insert_prompt, InsertPromptArgs}; +use sqlx::Acquire; +use tokens::tokens::prompts::PromptToken; +use utoipa::ToSchema; + +/// Veo 3 Image to Video +#[utoipa::path( + post, + tag = "Generate Videos", + path = "/v1/generate/video/veo_3_image_to_video", + responses( + (status = 200, description = "Success", body = GenerateVeo3ImageToVideoResponse), + ), + params( + ("request" = GenerateVeo3ImageToVideoRequest, description = "Payload for Request"), + ) +)] +pub async fn generate_veo_3_image_to_video_handler( + http_request: HttpRequest, + request: Json, + server_state: web::Data> +) -> Result, CommonWebError> { + let mut mysql_connection = server_state.mysql_pool + .acquire() + .await?; + + let maybe_user_session = server_state + .session_checker + .maybe_get_user_session_from_connection(&http_request, &mut mysql_connection) + .await + .map_err(|e| { + warn!("Session checker error: {:?}", e); + CommonWebError::ServerError + })?; + + let maybe_avt_token = server_state + .avt_cookie_manager + .get_avt_token_from_request(&http_request); + + // TODO: Limit usage for new accounts. Billing, free credits metering, etc. + + //let user_session = match maybe_user_session { + // Some(session) => session, + // None => { + // warn!("not logged in"); + // return Err(RemoveImageBackgroundError::NotAuthorized); + // } + //}; + + let media_file_token = match &request.media_file_token { + Some(token) => token, + None => { + warn!("No media file token provided"); + return Err(CommonWebError::BadInputWithSimpleMessage("No media file token provided".to_string())); + } + }; + + if let Err(reason) = validate_idempotency_token_format(&request.uuid_idempotency_token) { + return Err(CommonWebError::BadInputWithSimpleMessage(reason)); + } + + insert_idempotency_token(&request.uuid_idempotency_token, &mut *mysql_connection) + .await + .map_err(|err| { + error!("Error inserting idempotency token: {:?}", err); + CommonWebError::BadInputWithSimpleMessage("invalid idempotency token".to_string()) + })?; + const IS_MOD : bool = false; + + let media_file_lookup_result = get_media_file_with_connection( + media_file_token, + IS_MOD, + &mut mysql_connection, + ).await; + + let media_file = match media_file_lookup_result { + Ok(Some(media_file)) => media_file, + Ok(None) => { + warn!("MediaFile not found: {:?}", media_file_token); + return Err(CommonWebError::NotFound); + }, + Err(err) => { + warn!("Error looking up media_file: {:?}", err); + return Err(CommonWebError::ServerError); + } + }; + + if !media_file.media_type.is_jpg_or_png_or_legacy_image() { + return Err(CommonWebError::BadInputWithSimpleMessage("Media file must be a JPG or PNG image".to_string())); + } + + let media_domain = get_media_domain(&http_request); + + let bucket_path = MediaFileBucketPath::from_object_hash( + &media_file.public_bucket_directory_hash, + media_file.maybe_public_bucket_prefix.as_deref(), + media_file.maybe_public_bucket_extension.as_deref()); + + let media_links = MediaLinksBuilder::from_media_path_and_env( + media_domain, + server_state.server_environment, + &bucket_path); + + info!("Fal webhook URL: {}", server_state.fal.webhook_url); + + let prompt = request.prompt + .as_deref() + .map(|prompt| prompt.trim()) + .unwrap_or_else(|| ""); + + let aspect_ratio = match &request.aspect_ratio { + Some(GenerateVeo3AspectRatio::WideSixteenNine) => Veo3AspectRatio::WideSixteenNine, + Some(GenerateVeo3AspectRatio::TallNineSixteen) => Veo3AspectRatio::TallNineSixteen, + Some(GenerateVeo3AspectRatio::Square) => Veo3AspectRatio::Square, + None => Veo3AspectRatio::WideSixteenNine, + }; + + let resolution = match &request.resolution { + Some(GenerateVeo3Resolution::SevenTwentyP) => Veo3Resolution::SevenTwentyP, + Some(GenerateVeo3Resolution::TenEightyP) => Veo3Resolution::TenEightyP, + None => Veo3Resolution::Default, + }; + + let duration = match &request.duration { + Some(GenerateVeo3Duration::EightSeconds) => Veo3Duration::EightSeconds, + None => Veo3Duration::EightSeconds, + }; + + let generate_audio = request.generate_audio + .unwrap_or(false); + + let args = Veo3Args { + image_url: media_links.cdn_url, + prompt, + duration, + resolution, + aspect_ratio, + generate_audio, + webhook_url: &server_state.fal.webhook_url, + api_key: &server_state.fal.api_key, + }; + + let fal_result = enqueue_veo_3_image_to_video_webhook(args) + .await + .map_err(|err| { + warn!("Error calling enqueue_veo_3_image_to_video_webhook: {:?}", err); + CommonWebError::ServerError + })?; + + let external_job_id = fal_result.request_id + .ok_or_else(|| { + warn!("Fal request_id is None"); + CommonWebError::ServerError + })?; + + info!("Fal request_id: {}", external_job_id); + + let ip_address = get_request_ip(&http_request); + + let mut transaction = mysql_connection + .begin() + .await + .map_err(|err| { + error!("Error starting MySQL transaction: {:?}", err); + CommonWebError::ServerError + })?; + + // NB: Don't fail the job if the query fails. + let prompt_result = insert_prompt(InsertPromptArgs { + maybe_apriori_prompt_token: None, + prompt_type: PromptType::ArtcraftApp, + maybe_creator_user_token: maybe_user_session + .as_ref() + .map(|s| &s.user_token), + maybe_model_type: Some(ModelType::Veo3), + maybe_generation_provider: Some(GenerationProvider::Artcraft), + maybe_positive_prompt: Some(prompt), + maybe_negative_prompt: None, + maybe_other_args: None, + creator_ip_address: &ip_address, + mysql_executor: &mut *transaction, + phantom: Default::default(), + }).await; + + let prompt_token = match prompt_result { + Ok(token) => Some(token), + Err(err) => { + warn!("Error inserting prompt: {:?}", err); + None // Don't fail the job if the prompt insertion fails. + } + }; + + let db_result = insert_generic_inference_job_for_fal_queue(InsertGenericInferenceForFalArgs { + uuid_idempotency_token: &request.uuid_idempotency_token, + maybe_external_third_party_id: &external_job_id, + fal_category: FalCategory::VideoGeneration, + maybe_inference_args: None, + maybe_prompt_token: prompt_token.as_ref(), + maybe_creator_user_token: maybe_user_session + .as_ref() + .map(|s| &s.user_token), + maybe_avt_token: maybe_avt_token.as_ref(), + creator_ip_address: &ip_address, + creator_set_visibility: Visibility::Public, + mysql_executor: &mut *transaction, + phantom: Default::default(), + }).await; + + let job_token = match db_result { + Ok(token) => token, + Err(err) => { + warn!("Error inserting generic inference job for FAL queue: {:?}", err); + return Err(CommonWebError::ServerError); + } + }; + + let _r = transaction + .commit() + .await + .map_err(|err| { + error!("Error committing MySQL transaction: {:?}", err); + CommonWebError::ServerError + })?; + + Ok(Json(GenerateVeo3ImageToVideoResponse { + success: true, + inference_job_token: job_token, + })) +} diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/generate/video/mod.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/generate/video/mod.rs index 0dacc10a6c..b421e8094a 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/generate/video/mod.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/generate/video/mod.rs @@ -2,4 +2,5 @@ pub mod generate_kling_1_6_pro_video_handler; pub mod generate_kling_2_1_master_video_handler; pub mod generate_kling_2_1_pro_video_handler; pub mod generate_seedance_1_0_lite_image_to_video_handler; -pub mod generate_veo_2_image_to_video_handler; \ No newline at end of file +pub mod generate_veo_2_image_to_video_handler; +pub mod generate_veo_3_image_to_video_handler; \ No newline at end of file diff --git a/crates/service/web/storyteller_web/src/http_server/routes/add_generate_routes.rs b/crates/service/web/storyteller_web/src/http_server/routes/add_generate_routes.rs index 997842b467..6a76ee3a1e 100644 --- a/crates/service/web/storyteller_web/src/http_server/routes/add_generate_routes.rs +++ b/crates/service/web/storyteller_web/src/http_server/routes/add_generate_routes.rs @@ -1,10 +1,12 @@ use crate::http_server::endpoints::generate::image::edit::flux_pro_kontext_max_edit_image_handler::flux_pro_kontext_max_edit_image_handler; +use crate::http_server::endpoints::generate::image::edit::gemini_25_flash_edit_image_handler::gemini_25_flash_edit_image_handler; use crate::http_server::endpoints::generate::image::edit::gpt_image_1_edit_image_handler::gpt_image_1_edit_image_handler; use crate::http_server::endpoints::generate::image::generate_flux_1_dev_text_to_image_handler::generate_flux_1_dev_text_to_image_handler; use crate::http_server::endpoints::generate::image::generate_flux_1_schnell_text_to_image_handler::generate_flux_1_schnell_text_to_image_handler; use crate::http_server::endpoints::generate::image::generate_flux_pro_11_text_to_image_handler::generate_flux_pro_11_text_to_image_handler; use crate::http_server::endpoints::generate::image::generate_flux_pro_11_ultra_text_to_image_handler::generate_flux_pro_11_ultra_text_to_image_handler; use crate::http_server::endpoints::generate::image::generate_gpt_image_1_text_to_image_handler::generate_gpt_image_1_text_to_image_handler; +use crate::http_server::endpoints::generate::image::inpaint::flux_dev_juggernaut_inpaint_handler::flux_dev_juggernaut_inpaint_image_handler; use crate::http_server::endpoints::generate::image::inpaint::flux_pro_1_inpaint_handler::flux_pro_1_inpaint_image_handler; use crate::http_server::endpoints::generate::image::remove_image_background_handler::remove_image_background_handler; use crate::http_server::endpoints::generate::object::generate_hunyuan_2_0_image_to_3d_handler::generate_hunyuan_2_0_image_to_3d_handler; @@ -14,12 +16,11 @@ use crate::http_server::endpoints::generate::video::generate_kling_2_1_master_vi use crate::http_server::endpoints::generate::video::generate_kling_2_1_pro_video_handler::generate_kling_2_1_pro_video_handler; use crate::http_server::endpoints::generate::video::generate_seedance_1_0_lite_image_to_video_handler::generate_seedance_1_0_lite_image_to_video_handler; use crate::http_server::endpoints::generate::video::generate_veo_2_image_to_video_handler::generate_veo_2_image_to_video_handler; +use crate::http_server::endpoints::generate::video::generate_veo_3_image_to_video_handler::generate_veo_3_image_to_video_handler; use actix_http::body::MessageBody; use actix_service::ServiceFactory; use actix_web::dev::{ServiceRequest, ServiceResponse}; use actix_web::{web, App, Error, HttpResponse}; -use crate::http_server::endpoints::generate::image::edit::gemini_25_flash_edit_image_handler::gemini_25_flash_edit_image_handler; -use crate::http_server::endpoints::generate::image::inpaint::flux_dev_juggernaut_inpaint_handler::flux_dev_juggernaut_inpaint_image_handler; pub fn add_generate_routes (app: App) -> App where @@ -47,7 +48,7 @@ where .route(web::post().to(gpt_image_1_edit_image_handler)) .route(web::head().to(|| HttpResponse::Ok())) ) - + ) .service(web::scope("/inpaint") .service(web::resource("/flux_dev_juggernaut") @@ -105,6 +106,10 @@ where .route(web::post().to(generate_veo_2_image_to_video_handler)) .route(web::head().to(|| HttpResponse::Ok())) ) + .service(web::resource("/veo_3_image_to_video") + .route(web::post().to(generate_veo_3_image_to_video_handler)) + .route(web::head().to(|| HttpResponse::Ok())) + ) ) .service(web::scope("/object") .service(web::resource("/hunyuan_2.0_image_to_3d") diff --git a/crates/vendor/fal-rs/src/endpoints/fal_ai/veo3/image_to_video.rs b/crates/vendor/fal-rs/src/endpoints/fal_ai/veo3/image_to_video.rs index 8f150f6f67..e8677e44ea 100644 --- a/crates/vendor/fal-rs/src/endpoints/fal_ai/veo3/image_to_video.rs +++ b/crates/vendor/fal-rs/src/endpoints/fal_ai/veo3/image_to_video.rs @@ -28,26 +28,29 @@ pub struct File { #[derive(Debug, Serialize, Deserialize, Default)] pub struct ImageToVideoInput { - /// The text prompt describing how the image should be animated - /// "A lego chef cooking eggs" - pub prompt: String, - /// URL of the input image to animate. Should be 720p or higher resolution. /// "https://fal.media/files/elephant/6fq8JDSjb1osE_c3J_F2H.png" pub image_url: String, + /// The text prompt describing how the image should be animated + /// "A lego chef cooking eggs" + pub prompt: String, + /// The duration of the generated video in seconds /// eg "8s". #[serde(skip_serializing_if = "Option::is_none")] pub duration: Option, - /// Generate audio #[serde(skip_serializing_if = "Option::is_none")] - pub generate_audio: Option, + pub aspect_ratio: Option, /// Resolution, eg. "720p" #[serde(skip_serializing_if = "Option::is_none")] pub resolution: Option, + + /// Generate audio + #[serde(skip_serializing_if = "Option::is_none")] + pub generate_audio: Option, } #[derive(Debug, Serialize, Deserialize)]