Compare commits

...
Author SHA1 Message Date
Brandon Thomas 05ec3d5b69 video endpoints charge, removing from image endpoints 2025-12-29 03:37:32 -05:00
Brandon Thomas c30135b228 billing on all multi-function (untested) 2025-12-29 03:25:44 -05:00
Brandon Thomas 7ad7117934 billing wip, untested 2025-12-29 03:12:36 -05:00
Brandon Thomas faf2c97825 final text-to-video models with costs 2025-12-29 02:14:15 -05:00
Brandon Thomas a3fb9d23fd sora 2 text to video costs 2025-12-29 02:09:34 -05:00
Brandon Thomas b6e899b9fa veo 3.1 text to video costs 2025-12-29 02:05:30 -05:00
Brandon Thomas 1b26ae0ce4 costs for veo 3.1 2025-12-29 02:00:12 -05:00
Brandon Thomas 12d46666c3 veo 2 and veo 3 costs 2025-12-29 01:46:15 -05:00
Brandon Thomas ca142a252a sora pricing 2025-12-29 01:29:39 -05:00
Brandon Thomas efe56050c4 seedance pro 1.0 cost 2025-12-29 01:17:57 -05:00
Brandon Thomas 02cb7558e4 seedance pricing calculation 2025-12-29 01:09:28 -05:00
Brandon Thomas 4631971865 hailuo 2.3 pro cost 2025-12-29 00:30:21 -05:00
Brandon Thomas 2af0227330 cost hailuo 2.3 fast pro 2025-12-29 00:28:44 -05:00
Brandon Thomas 6b612f4ba6 pixverse cost 2025-12-29 00:25:33 -05:00
Brandon Thomas 994f848c5c cost kling 2.6 pro 2025-12-29 00:02:50 -05:00
Brandon Thomas ed4ad1cfdd cost kling 2.5 turbo standard 2025-12-28 23:53:34 -05:00
Brandon Thomas 628fc05dba cost kling 2.5 turbo pro 2025-12-28 23:49:15 -05:00
Brandon Thomas bb56d64c05 kling 2.1 pro cost 2025-12-28 23:42:33 -05:00
Brandon Thomas 36e048e5b4 cost kling 1.6 pro 2025-12-28 23:37:07 -05:00
Brandon Thomas 95ef7d6d1f kling 2.1 master cost 2025-12-28 23:32:07 -05:00
Brandon Thomas fb34a80ac7 viz 2025-12-28 23:23:57 -05:00
Brandon Thomas 85dc8a7492 trait 2025-12-28 23:23:39 -05:00
Brandon Thomas f832237f13 module refactor done 2025-12-28 23:13:29 -05:00
Brandon Thomas 3ac322ca97 begin reorganizing library modules 2025-12-28 23:00:54 -05:00
61 changed files with 1936 additions and 668 deletions
@@ -21,11 +21,14 @@ pub struct GenerateSeedance10LiteImageToVideoRequest {
/// Optional text prompt.
pub prompt: Option<String>,
/// Optional: aspect ratio of the generated video.
/// Optional: resolution of the generated video.
pub resolution: Option<GenerateSeedance10LiteResolution>,
/// Optional: aspect ratio of the generated video.
/// Optional: duration of the generated video.
pub duration: Option<GenerateSeedance10LiteDuration>,
/// Optional: aspect ratio of the generated video.
pub aspect_ratio: Option<GenerateSeedance10LiteAspectRatio>,
}
#[derive(Serialize, Deserialize, ToSchema)]
@@ -42,6 +45,19 @@ pub enum GenerateSeedance10LiteDuration {
TenSeconds,
}
#[derive(Serialize, Deserialize, ToSchema)]
#[serde(rename_all = "snake_case")]
pub enum GenerateSeedance10LiteAspectRatio {
Auto,
TwentyOneByNine,
SixteenByNine,
FourByThree,
Square,
ThreeByFour,
NineBySixteen,
}
#[derive(Serialize, Deserialize, ToSchema)]
pub struct GenerateSeedance10LiteImageToVideoResponse {
pub success: bool,
@@ -1,2 +1,3 @@
pub mod queue;
pub mod webhook;
pub mod webhook;
pub mod traits;
@@ -0,0 +1,9 @@
/// Cost in pennies.
pub type UsdCents = u64;
pub trait FalRequestCostCalculator {
/// Calculate the cost of the request.
fn calculate_cost_in_cents(&self) -> UsdCents;
}
@@ -0,0 +1 @@
pub mod fal_request_cost_calculator_trait;
@@ -1,105 +0,0 @@
use crate::creds::fal_api_key::FalApiKey;
use crate::error::classify_fal_error::classify_fal_error;
use crate::error::fal_error_plus::FalErrorPlus;
use fal::endpoints::fal_ai::bytedance::seedance::v1::lite::image_to_video::image_to_video;
use fal::endpoints::fal_ai::bytedance::seedance::v1::lite::image_to_video::ImageToVideoRequest;
use fal::webhook::WebhookResponse;
use reqwest::IntoUrl;
pub struct Seedance1LiteArgs<'a, U: IntoUrl, T: IntoUrl, V: IntoUrl> {
pub image_url: U,
pub end_frame_image_url: Option<T>,
pub webhook_url: V,
pub prompt: &'a str,
pub api_key: &'a FalApiKey,
pub camera_fixed: bool,
pub duration: Seedance1LiteDuration,
pub resolution: Seedance1LiteResolution,
pub seed: Option<u32>,
}
#[derive(Copy, Clone, Debug)]
pub enum Seedance1LiteDuration {
FiveSeconds,
TenSeconds,
}
#[derive(Copy, Clone, Debug)]
pub enum Seedance1LiteResolution {
FourEightyP, // 480p
SevenTwentyP, // 720p
}
pub async fn enqueue_seedance_1_lite_image_to_video_webhook<U: IntoUrl, T: IntoUrl, V: IntoUrl>(
args: Seedance1LiteArgs<'_, U, T, V>
) -> Result<WebhookResponse, FalErrorPlus> {
let duration = match args.duration {
Seedance1LiteDuration::FiveSeconds => Some("5".to_string()),
Seedance1LiteDuration::TenSeconds => Some("10".to_string()),
};
let resolution = match args.resolution {
Seedance1LiteResolution::FourEightyP => Some("480p".to_string()),
Seedance1LiteResolution::SevenTwentyP => Some("720p".to_string()),
};
let image_url = args.image_url.as_str().to_string();
let end_image_url = args.end_frame_image_url
.map(|url| url.as_str().to_string());
let request = ImageToVideoRequest {
image_url,
end_image_url,
prompt: args.prompt.to_string(),
duration,
resolution,
// TODO: Add these later
camera_fixed: None,
seed: -1,
// Static
enable_safety_checker: Some(false),
};
let result = image_to_video(request)
.with_api_key(&args.api_key.0)
.queue_webhook(args.webhook_url)
.await;
result.map_err(|err| classify_fal_error(err))
}
#[cfg(test)]
mod tests {
use crate::creds::fal_api_key::FalApiKey;
use crate::requests::webhook::video::enqueue_seedance_1_lite_image_to_video_webhook::{enqueue_seedance_1_lite_image_to_video_webhook, Seedance1LiteArgs, Seedance1LiteDuration, Seedance1LiteResolution};
use errors::AnyhowResult;
use std::fs::read_to_string;
use test_data::web::image_urls::{JUNO_AT_LAKE_IMAGE_URL, TALL_MOCHI_WITH_GLASSES_IMAGE_URL};
#[tokio::test]
#[ignore]
async fn test() -> AnyhowResult<()> {
// 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 = Seedance1LiteArgs {
image_url: TALL_MOCHI_WITH_GLASSES_IMAGE_URL,
end_frame_image_url: Some(JUNO_AT_LAKE_IMAGE_URL.to_string()),
prompt: "shiba in glasses runs to the lake and stands by the shore",
api_key: &api_key,
camera_fixed: false,
duration: Seedance1LiteDuration::FiveSeconds,
resolution: Seedance1LiteResolution::SevenTwentyP,
seed: None,
webhook_url: "https://example.com/webhook",
};
let result = enqueue_seedance_1_lite_image_to_video_webhook(args).await?;
Ok(())
}
}
@@ -1,115 +0,0 @@
use crate::creds::fal_api_key::FalApiKey;
use crate::error::classify_fal_error::classify_fal_error;
use crate::error::fal_error_plus::FalErrorPlus;
use fal::endpoints::fal_ai::bytedance::seedance::v1::pro::image_to_video::{seedance_v1_pro_image_to_video, SeedanceImageToVideoProRequest};
use fal::webhook::WebhookResponse;
use reqwest::IntoUrl;
pub struct Seedance1ProArgs<'a, U: IntoUrl, V: IntoUrl> {
pub image_url: U,
pub webhook_url: V,
pub prompt: &'a str,
pub api_key: &'a FalApiKey,
pub camera_fixed: bool,
pub duration: Seedance1ProDuration,
pub resolution: Seedance1ProResolution,
pub seed: Option<u32>,
}
#[derive(Copy, Clone, Debug)]
pub enum Seedance1ProDuration {
ThreeSeconds,
FourSeconds,
FiveSeconds,
SixSeconds,
SevenSeconds,
EightSeconds,
NineSeconds,
TenSeconds,
ElevenSeconds,
TwelveSeconds,
}
#[derive(Copy, Clone, Debug)]
pub enum Seedance1ProResolution {
FourEightyP, // 480p
SevenTwentyP, // 720p
TenEightyP, // 1080p
}
pub async fn enqueue_seedance_1_pro_image_to_video_webhook<U: IntoUrl, V: IntoUrl>(
args: Seedance1ProArgs<'_, U, V>
) -> Result<WebhookResponse, FalErrorPlus> {
let duration = match args.duration {
Seedance1ProDuration::ThreeSeconds => Some("3".to_string()),
Seedance1ProDuration::FourSeconds => Some("4".to_string()),
Seedance1ProDuration::FiveSeconds => Some("5".to_string()),
Seedance1ProDuration::SixSeconds => Some("6".to_string()),
Seedance1ProDuration::SevenSeconds => Some("7".to_string()),
Seedance1ProDuration::EightSeconds => Some("8".to_string()),
Seedance1ProDuration::NineSeconds => Some("9".to_string()),
Seedance1ProDuration::TenSeconds => Some("10".to_string()),
Seedance1ProDuration::ElevenSeconds => Some("11".to_string()),
Seedance1ProDuration::TwelveSeconds => Some("12".to_string()),
};
let resolution = match args.resolution {
Seedance1ProResolution::FourEightyP => Some("480p".to_string()),
Seedance1ProResolution::SevenTwentyP => Some("720p".to_string()),
Seedance1ProResolution::TenEightyP => Some("1080p".to_string()),
};
let image_url = args.image_url.as_str().to_string();
let request = SeedanceImageToVideoProRequest {
image_url,
prompt: args.prompt.to_string(),
duration,
resolution,
// TODO: Add these later
camera_fixed: None,
// Static
enable_safety_checker: Some(false),
};
let result = seedance_v1_pro_image_to_video(request)
.with_api_key(&args.api_key.0)
.queue_webhook(args.webhook_url)
.await;
result.map_err(|err| classify_fal_error(err))
}
#[cfg(test)]
mod tests {
use crate::creds::fal_api_key::FalApiKey;
use crate::requests::webhook::video::enqueue_seedance_1_pro_image_to_video_webhook::{enqueue_seedance_1_pro_image_to_video_webhook, Seedance1ProArgs, Seedance1ProDuration, Seedance1ProResolution};
use errors::AnyhowResult;
use std::fs::read_to_string;
use test_data::web::image_urls::TALL_MOCHI_WITH_GLASSES_IMAGE_URL;
#[tokio::test]
#[ignore]
async fn test() -> AnyhowResult<()> {
// 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 = Seedance1ProArgs {
image_url: TALL_MOCHI_WITH_GLASSES_IMAGE_URL,
prompt: "shiba in glasses runs to the lake and stands by the shore",
api_key: &api_key,
camera_fixed: false,
duration: Seedance1ProDuration::FiveSeconds,
resolution: Seedance1ProResolution::SevenTwentyP,
seed: None,
webhook_url: "https://example.com/webhook",
};
let result = enqueue_seedance_1_pro_image_to_video_webhook(args).await?;
Ok(())
}
}
@@ -1,6 +1,7 @@
use crate::creds::fal_api_key::FalApiKey;
use crate::error::classify_fal_error::classify_fal_error;
use crate::error::fal_error_plus::FalErrorPlus;
use crate::requests::traits::fal_request_cost_calculator_trait::{FalRequestCostCalculator, UsdCents};
use fal::endpoints::fal_ai::minimax::hailuo::hailuo_v2p3_fast_pro_image_to_video::{hailuo_v2p3_fast_pro_image_to_video, HailuoV2p3FastProImageToVideoInput};
use fal::webhook::WebhookResponse;
use reqwest::IntoUrl;
@@ -18,6 +19,16 @@ pub struct EnqueueHailuoV2p3FastProImageToVideoArgs<'a, R: IntoUrl> {
pub api_key: &'a FalApiKey,
}
impl <U: IntoUrl> FalRequestCostCalculator for EnqueueHailuoV2p3FastProImageToVideoArgs<'_, U> {
fn calculate_cost_in_cents(&self) -> UsdCents {
/// "Your request will cost $0.33 per video."
33
}
}
/// Hailuo 2.3 Fast Pro Image-to-Video
/// https://fal.ai/models/fal-ai/minimax/hailuo-2.3-fast/pro/image-to-video
pub async fn enqueue_hailuo_v2p3_fast_pro_image_to_video_webhook<R: IntoUrl>(
args: EnqueueHailuoV2p3FastProImageToVideoArgs<'_, R>
) -> Result<WebhookResponse, FalErrorPlus> {
@@ -1,6 +1,7 @@
use crate::creds::fal_api_key::FalApiKey;
use crate::error::classify_fal_error::classify_fal_error;
use crate::error::fal_error_plus::FalErrorPlus;
use crate::requests::traits::fal_request_cost_calculator_trait::{FalRequestCostCalculator, UsdCents};
use fal::endpoints::fal_ai::minimax::hailuo::hailuo_v2p3_pro_image_to_video::{hailuo_v2p3_pro_image_to_video, HailuoV2p3ProImageToVideoInput};
use fal::webhook::WebhookResponse;
use reqwest::IntoUrl;
@@ -18,6 +19,16 @@ pub struct EnqueueHailuoV2p3ProImageToVideoArgs<'a, R: IntoUrl> {
pub api_key: &'a FalApiKey,
}
impl <U: IntoUrl> FalRequestCostCalculator for EnqueueHailuoV2p3ProImageToVideoArgs<'_, U> {
fn calculate_cost_in_cents(&self) -> UsdCents {
/// "Your request will cost $0.49 per video generation."
49
}
}
/// Hailuo 2.3 Pro Image-to-Video
/// https://fal.ai/models/fal-ai/minimax/hailuo-2.3/pro/image-to-video
pub async fn enqueue_hailuo_v2p3_pro_image_to_video_webhook<R: IntoUrl>(
args: EnqueueHailuoV2p3ProImageToVideoArgs<'_, R>
) -> Result<WebhookResponse, FalErrorPlus> {
@@ -1,47 +1,62 @@
use crate::creds::fal_api_key::FalApiKey;
use crate::error::classify_fal_error::classify_fal_error;
use crate::error::fal_error_plus::FalErrorPlus;
use crate::requests::traits::fal_request_cost_calculator_trait::{FalRequestCostCalculator, UsdCents};
use fal::endpoints::fal_ai::kling_video::v1_6::pro::image_to_video::{image_to_video, ProImageToVideoRequest};
use fal::webhook::WebhookResponse;
use reqwest::IntoUrl;
pub struct Kling16ProArgs<'a, U: IntoUrl, T: IntoUrl, V: IntoUrl> {
pub struct Kling1p6ProArgs<'a, U: IntoUrl, T: IntoUrl, V: IntoUrl> {
pub image_url: U,
pub end_frame_image_url: Option<T>,
pub webhook_url: V,
pub prompt: &'a str,
pub api_key: &'a FalApiKey,
pub duration: Kling16Duration,
pub aspect_ratio: Kling16ProAspectRatio,
pub duration: Kling1p6ProDuration,
pub aspect_ratio: Kling1p6ProAspectRatio,
}
#[derive(Copy, Clone, Debug)]
pub enum Kling16Duration {
pub enum Kling1p6ProDuration {
Default,
FiveSeconds,
TenSeconds,
}
#[derive(Copy, Clone, Debug)]
pub enum Kling16ProAspectRatio {
pub enum Kling1p6ProAspectRatio {
Square, // 1:1
WideSixteenNine, // 16:9
TallNineSixteen, // 9:16
}
pub async fn enqueue_kling_16_pro_image_to_video_webhook<U: IntoUrl, T: IntoUrl, V: IntoUrl>(
args: Kling16ProArgs<'_, U, T, V>
impl <U: IntoUrl, T: IntoUrl, V: IntoUrl> FalRequestCostCalculator for Kling1p6ProArgs<'_, U, T, V> {
fn calculate_cost_in_cents(&self) -> UsdCents {
// "Your request will cost $0.095 per second."
match self.duration {
Kling1p6ProDuration::Default => 48, // $0.095 * 5 = $0.475 (round up)
Kling1p6ProDuration::FiveSeconds => 48, // $0.095 * 5 = $0.475 (round up)
Kling1p6ProDuration::TenSeconds => 95, // $0.095 * 10 = $0.95
}
}
}
/// Kling 1.6 Pro Image-to-Video
/// https://fal.ai/models/fal-ai/kling-video/v1.6/pro/image-to-video
pub async fn enqueue_kling_v1p6_pro_image_to_video_webhook<U: IntoUrl, T: IntoUrl, V: IntoUrl>(
args: Kling1p6ProArgs<'_, U, T, V>
) -> Result<WebhookResponse, FalErrorPlus> {
let duration = match args.duration {
Kling16Duration::Default => None,
Kling16Duration::FiveSeconds => Some("5".to_string()), // Gross...
Kling16Duration::TenSeconds => Some("10".to_string()),
Kling1p6ProDuration::Default => None, // defaults to "5"
Kling1p6ProDuration::FiveSeconds => Some("5".to_string()), // Gross...
Kling1p6ProDuration::TenSeconds => Some("10".to_string()),
};
let aspect_ratio = match args.aspect_ratio {
Kling16ProAspectRatio::Square => Some("1:1".to_string()),
Kling16ProAspectRatio::WideSixteenNine => Some("16:9".to_string()),
Kling16ProAspectRatio::TallNineSixteen => Some("9:16".to_string()),
Kling1p6ProAspectRatio::Square => Some("1:1".to_string()),
Kling1p6ProAspectRatio::WideSixteenNine => Some("16:9".to_string()),
Kling1p6ProAspectRatio::TallNineSixteen => Some("9:16".to_string()),
};
let image_url = args.image_url.as_str().to_string();
@@ -71,7 +86,7 @@ pub async fn enqueue_kling_16_pro_image_to_video_webhook<U: IntoUrl, T: IntoUrl,
#[cfg(test)]
mod tests {
use crate::creds::fal_api_key::FalApiKey;
use crate::requests::webhook::video::enqueue_kling_16_pro_image_to_video_webhook::{enqueue_kling_16_pro_image_to_video_webhook, Kling16Duration, Kling16ProArgs, Kling16ProAspectRatio};
use crate::requests::webhook::video::image::enqueue_kling_v1p6_pro_image_to_video_webhook::{enqueue_kling_v1p6_pro_image_to_video_webhook, Kling1p6ProArgs, Kling1p6ProAspectRatio, Kling1p6ProDuration};
use errors::AnyhowResult;
use std::fs::read_to_string;
use test_data::web::image_urls::{JUNO_AT_LAKE_IMAGE_URL, TALL_MOCHI_WITH_GLASSES_IMAGE_URL};
@@ -84,17 +99,17 @@ mod tests {
let api_key = FalApiKey::from_str(&secret);
let args = Kling16ProArgs {
let args = Kling1p6ProArgs {
image_url: TALL_MOCHI_WITH_GLASSES_IMAGE_URL,
end_frame_image_url: Some(JUNO_AT_LAKE_IMAGE_URL.to_string()),
prompt: "shiba in glasses runs to the lake and stands by the shore",
api_key: &api_key,
duration: Kling16Duration::Default,
aspect_ratio: Kling16ProAspectRatio::WideSixteenNine,
duration: Kling1p6ProDuration::Default,
aspect_ratio: Kling1p6ProAspectRatio::WideSixteenNine,
webhook_url: "https://example.com/webhook",
};
let result = enqueue_kling_16_pro_image_to_video_webhook(args).await?;
let result = enqueue_kling_v1p6_pro_image_to_video_webhook(args).await?;
Ok(())
}
@@ -4,43 +4,59 @@ use crate::error::fal_error_plus::FalErrorPlus;
use fal::endpoints::fal_ai::kling_video::v2_1::master::image_to_video::{image_to_video, MasterImageToVideoRequest};
use fal::webhook::WebhookResponse;
use reqwest::IntoUrl;
use crate::requests::traits::fal_request_cost_calculator_trait::{FalRequestCostCalculator, UsdCents};
pub struct Kling21MasterArgs<'a, U: IntoUrl, V: IntoUrl> {
pub struct Kling2p1MasterArgs<'a, U: IntoUrl, V: IntoUrl> {
pub image_url: U,
pub webhook_url: V,
pub prompt: &'a str,
pub api_key: &'a FalApiKey,
pub duration: Kling21MasterDuration,
pub aspect_ratio: Kling21MasterAspectRatio,
pub duration: Kling2p1MasterDuration,
pub aspect_ratio: Kling2p1MasterAspectRatio,
}
#[derive(Copy, Clone, Debug)]
pub enum Kling21MasterDuration {
pub enum Kling2p1MasterDuration {
Default,
FiveSeconds,
TenSeconds,
}
#[derive(Copy, Clone, Debug)]
pub enum Kling21MasterAspectRatio {
pub enum Kling2p1MasterAspectRatio {
Square, // 1:1
WideSixteenNine, // 16:9
TallNineSixteen, // 9:16
}
pub async fn enqueue_kling_21_master_image_to_video_webhook<U: IntoUrl, V: IntoUrl>(
args: Kling21MasterArgs<'_, U, V>
impl <U: IntoUrl, V: IntoUrl> FalRequestCostCalculator for Kling2p1MasterArgs<'_, U, V> {
fn calculate_cost_in_cents(&self) -> UsdCents {
// "For 5s video your request will cost $1.40.
// For every additional second you will be charged $0.28."
match self.duration {
Kling2p1MasterDuration::Default => 140, // $1.40 for 5 seconds
Kling2p1MasterDuration::FiveSeconds => 140, // $1.40 for 5 seconds
Kling2p1MasterDuration::TenSeconds => 280, // $1.40 + (0.28 * 5) = $2.80
}
}
}
/// Kling 2.1 Master Image-to-Video
/// https://fal.ai/models/fal-ai/kling-video/v2.1/master/image-to-video
pub async fn enqueue_kling_v2p1_master_image_to_video_webhook<U: IntoUrl, V: IntoUrl>(
args: Kling2p1MasterArgs<'_, U, V>
) -> Result<WebhookResponse, FalErrorPlus> {
let duration = match args.duration {
Kling21MasterDuration::Default => None,
Kling21MasterDuration::FiveSeconds => Some("5".to_string()), // Gross...
Kling21MasterDuration::TenSeconds => Some("10".to_string()),
Kling2p1MasterDuration::Default => None, // defaults to "5"
Kling2p1MasterDuration::FiveSeconds => Some("5".to_string()), // Gross...
Kling2p1MasterDuration::TenSeconds => Some("10".to_string()),
};
let aspect_ratio = match args.aspect_ratio {
Kling21MasterAspectRatio::Square => Some("1:1".to_string()),
Kling21MasterAspectRatio::WideSixteenNine => Some("16:9".to_string()),
Kling21MasterAspectRatio::TallNineSixteen => Some("9:16".to_string()),
Kling2p1MasterAspectRatio::Square => Some("1:1".to_string()),
Kling2p1MasterAspectRatio::WideSixteenNine => Some("16:9".to_string()),
Kling2p1MasterAspectRatio::TallNineSixteen => Some("9:16".to_string()),
};
let image_url = args.image_url.as_str().to_string();
@@ -68,7 +84,7 @@ pub async fn enqueue_kling_21_master_image_to_video_webhook<U: IntoUrl, V: IntoU
#[cfg(test)]
mod tests {
use crate::creds::fal_api_key::FalApiKey;
use crate::requests::webhook::video::enqueue_kling_21_master_image_to_video_webhook::{enqueue_kling_21_master_image_to_video_webhook, Kling21MasterArgs, Kling21MasterAspectRatio, Kling21MasterDuration};
use crate::requests::webhook::video::image::enqueue_kling_v2p1_master_image_to_video_webhook::{enqueue_kling_v2p1_master_image_to_video_webhook, Kling2p1MasterArgs, Kling2p1MasterAspectRatio, Kling2p1MasterDuration};
use errors::AnyhowResult;
use std::fs::read_to_string;
@@ -82,16 +98,16 @@ mod tests {
let api_key = FalApiKey::from_str(&secret);
let args = Kling21MasterArgs {
let args = Kling2p1MasterArgs {
image_url: image_url,
prompt: "a shot of the mountains, the camera rotates around to show the mountain range, the sun begins to set, and the moon becomes visible",
api_key: &api_key,
duration: Kling21MasterDuration::Default,
aspect_ratio: Kling21MasterAspectRatio::WideSixteenNine,
duration: Kling2p1MasterDuration::Default,
aspect_ratio: Kling2p1MasterAspectRatio::WideSixteenNine,
webhook_url: "https://example.com/webhook",
};
let result = enqueue_kling_21_master_image_to_video_webhook(args).await?;
let result = enqueue_kling_v2p1_master_image_to_video_webhook(args).await?;
Ok(())
}
@@ -1,47 +1,62 @@
use crate::creds::fal_api_key::FalApiKey;
use crate::error::classify_fal_error::classify_fal_error;
use crate::error::fal_error_plus::FalErrorPlus;
use crate::requests::traits::fal_request_cost_calculator_trait::{FalRequestCostCalculator, UsdCents};
use fal::endpoints::fal_ai::kling_video::v2_1::pro::image_to_video::{image_to_video, ProImageToVideoRequest};
use fal::webhook::WebhookResponse;
use reqwest::IntoUrl;
pub struct Kling21ProArgs<'a, U: IntoUrl, T: IntoUrl, V: IntoUrl> {
pub struct Kling2p1ProArgs<'a, U: IntoUrl, T: IntoUrl, V: IntoUrl> {
pub image_url: U,
pub end_frame_image_url: Option<T>,
pub webhook_url: V,
pub prompt: &'a str,
pub api_key: &'a FalApiKey,
pub duration: Kling21ProDuration,
pub aspect_ratio: Kling21ProAspectRatio,
pub duration: Kling2p1ProDuration,
pub aspect_ratio: Kling2p1ProAspectRatio,
}
#[derive(Copy, Clone, Debug)]
pub enum Kling21ProDuration {
pub enum Kling2p1ProDuration {
Default,
FiveSeconds,
TenSeconds,
}
#[derive(Copy, Clone, Debug)]
pub enum Kling21ProAspectRatio {
pub enum Kling2p1ProAspectRatio {
Square, // 1:1
WideSixteenNine, // 16:9
TallNineSixteen, // 9:16
}
pub async fn enqueue_kling_21_pro_image_to_video_webhook<U: IntoUrl, T: IntoUrl, V: IntoUrl>(
args: Kling21ProArgs<'_, U, T, V>
impl <U: IntoUrl, T: IntoUrl, V: IntoUrl> FalRequestCostCalculator for Kling2p1ProArgs<'_, U, T, V> {
fn calculate_cost_in_cents(&self) -> UsdCents {
// "For 5s video your request will cost $0.45.
// For every additional second you will be charged $0.09."
match self.duration {
Kling2p1ProDuration::Default => 45, // $0.45
Kling2p1ProDuration::FiveSeconds => 45, // $0.45
Kling2p1ProDuration::TenSeconds => 90, // $0.45 + (5 * 0.09) = $0.90
}
}
}
/// Kling 2.1 Pro Image-to-Video
/// https://fal.ai/models/fal-ai/kling-video/v2.1/pro/image-to-video
pub async fn enqueue_kling_v2p1_pro_image_to_video_webhook<U: IntoUrl, T: IntoUrl, V: IntoUrl>(
args: Kling2p1ProArgs<'_, U, T, V>
) -> Result<WebhookResponse, FalErrorPlus> {
let duration = match args.duration {
Kling21ProDuration::Default => None,
Kling21ProDuration::FiveSeconds => Some("5".to_string()), // Gross...
Kling21ProDuration::TenSeconds => Some("10".to_string()),
Kling2p1ProDuration::Default => None, // Defaults to 5 seconds
Kling2p1ProDuration::FiveSeconds => Some("5".to_string()), // Gross...
Kling2p1ProDuration::TenSeconds => Some("10".to_string()),
};
let aspect_ratio = match args.aspect_ratio {
Kling21ProAspectRatio::Square => Some("1:1".to_string()),
Kling21ProAspectRatio::WideSixteenNine => Some("16:9".to_string()),
Kling21ProAspectRatio::TallNineSixteen => Some("9:16".to_string()),
Kling2p1ProAspectRatio::Square => Some("1:1".to_string()),
Kling2p1ProAspectRatio::WideSixteenNine => Some("16:9".to_string()),
Kling2p1ProAspectRatio::TallNineSixteen => Some("9:16".to_string()),
};
let image_url = args.image_url.as_str().to_string();
@@ -71,7 +86,7 @@ pub async fn enqueue_kling_21_pro_image_to_video_webhook<U: IntoUrl, T: IntoUrl,
#[cfg(test)]
mod tests {
use crate::creds::fal_api_key::FalApiKey;
use crate::requests::webhook::video::enqueue_kling_21_pro_image_to_video_webhook::{enqueue_kling_21_pro_image_to_video_webhook, Kling21ProArgs, Kling21ProAspectRatio, Kling21ProDuration};
use crate::requests::webhook::video::image::enqueue_kling_v2p1_pro_image_to_video_webhook::{enqueue_kling_v2p1_pro_image_to_video_webhook, Kling2p1ProArgs, Kling2p1ProAspectRatio, Kling2p1ProDuration};
use errors::AnyhowResult;
use std::fs::read_to_string;
use test_data::web::image_urls::{JUNO_AT_LAKE_IMAGE_URL, SUPER_WIDE_FALL_MOUNTAINS_IMAGE_URL};
@@ -84,17 +99,17 @@ mod tests {
let api_key = FalApiKey::from_str(&secret);
let args = Kling21ProArgs {
let args = Kling2p1ProArgs {
image_url: SUPER_WIDE_FALL_MOUNTAINS_IMAGE_URL,
end_frame_image_url: Some(JUNO_AT_LAKE_IMAGE_URL.to_string()),
prompt: "a shot of the mountains, the camera pulls back to show a corgi waiting to jump into the lake",
api_key: &api_key,
duration: Kling21ProDuration::Default,
aspect_ratio: Kling21ProAspectRatio::WideSixteenNine,
duration: Kling2p1ProDuration::Default,
aspect_ratio: Kling2p1ProAspectRatio::WideSixteenNine,
webhook_url: "https://example.com/webhook",
};
let result = enqueue_kling_21_pro_image_to_video_webhook(args).await?;
let result = enqueue_kling_v2p1_pro_image_to_video_webhook(args).await?;
Ok(())
}
@@ -1,6 +1,7 @@
use crate::creds::fal_api_key::FalApiKey;
use crate::error::classify_fal_error::classify_fal_error;
use crate::error::fal_error_plus::FalErrorPlus;
use crate::requests::traits::fal_request_cost_calculator_trait::{FalRequestCostCalculator, UsdCents};
use fal::endpoints::fal_ai::kling_video::v2_5::kling_v2p5_turbo_pro_image_to_video::{kling_v2p5_turbo_pro_image_to_video, KlingV2p5TurboProImageToVideoInput};
use fal::webhook::WebhookResponse;
use reqwest::IntoUrl;
@@ -27,10 +28,26 @@ pub enum EnqueueKlingV2p5TurboProImageToVideoDurationSeconds {
Ten,
}
impl <U: IntoUrl> FalRequestCostCalculator for EnqueueKlingV2p5TurboProImageToVideoArgs<'_, U> {
fn calculate_cost_in_cents(&self) -> UsdCents {
// "For 5s video your request will cost $0.35.
// For every additional second you will be charged $0.07."
match self.duration {
None => 35, // $0.35
Some(EnqueueKlingV2p5TurboProImageToVideoDurationSeconds::Five) => 35, // $0.35
Some(EnqueueKlingV2p5TurboProImageToVideoDurationSeconds::Ten) => 70, // $0.35 + (5 * $0.07) = $0.70
}
}
}
/// Kling 2.5 Turbo Pro Image-to-Video
/// https://fal.ai/models/fal-ai/kling-video/v2.5-turbo/pro/image-to-video
pub async fn enqueue_kling_v2p5_turbo_pro_image_to_video_webhook<R: IntoUrl>(
args: EnqueueKlingV2p5TurboProImageToVideoArgs<'_, R>
) -> Result<WebhookResponse, FalErrorPlus> {
// NB: Defaults to 5 seconds
let duration = args.duration
.map(|resolution| match resolution {
EnqueueKlingV2p5TurboProImageToVideoDurationSeconds::Five => "5",
@@ -1,9 +1,10 @@
use crate::creds::fal_api_key::FalApiKey;
use crate::error::classify_fal_error::classify_fal_error;
use crate::error::fal_error_plus::FalErrorPlus;
use crate::requests::traits::fal_request_cost_calculator_trait::{FalRequestCostCalculator, UsdCents};
use fal::endpoints::fal_ai::kling_video::v2_5::kling_v2p5_turbo_standard_image_to_video::{kling_v2p5_turbo_standard_image_to_video, KlingV2p5TurboStandardImageToVideoInput};
use fal::webhook::WebhookResponse;
use reqwest::IntoUrl;
use fal::endpoints::fal_ai::kling_video::v2_5::kling_v2p5_turbo_standard_image_to_video::{kling_v2p5_turbo_standard_image_to_video, KlingV2p5TurboStandardImageToVideoInput};
pub struct EnqueueKlingV2p5TurboStandardImageToVideoArgs<'a, R: IntoUrl> {
// Request required
@@ -25,10 +26,26 @@ pub enum EnqueueKlingV2p5TurboStandardImageToVideoDurationSeconds {
Ten,
}
impl <U: IntoUrl> FalRequestCostCalculator for EnqueueKlingV2p5TurboStandardImageToVideoArgs<'_, U> {
fn calculate_cost_in_cents(&self) -> UsdCents {
// "For 5s video your request will cost $0.21.
// For every additional second you will be charged $0.042."
match self.duration {
None => 21, // $0.21
Some(EnqueueKlingV2p5TurboStandardImageToVideoDurationSeconds::Five) => 21, // $0.21
Some(EnqueueKlingV2p5TurboStandardImageToVideoDurationSeconds::Ten) => 42, // $0.21 + (5 * $0.042) = $0.42
}
}
}
/// Kling 2.5 Turbo Standard Image-to-Video
/// https://fal.ai/models/fal-ai/kling-video/v2.5-turbo/standard/image-to-video
pub async fn enqueue_kling_v2p5_turbo_standard_image_to_video_webhook<R: IntoUrl>(
args: EnqueueKlingV2p5TurboStandardImageToVideoArgs<'_, R>
) -> Result<WebhookResponse, FalErrorPlus> {
// NB: Defaults to 5 seconds.
let duration = args.duration
.map(|resolution| match resolution {
EnqueueKlingV2p5TurboStandardImageToVideoDurationSeconds::Five => "5",
@@ -57,10 +74,10 @@ pub async fn enqueue_kling_v2p5_turbo_standard_image_to_video_webhook<R: IntoUrl
#[cfg(test)]
mod tests {
use crate::creds::fal_api_key::FalApiKey;
use crate::requests::webhook::video::image::enqueue_kling_v2p5_turbo_standard_image_to_video_webhook::{enqueue_kling_v2p5_turbo_standard_image_to_video_webhook, EnqueueKlingV2p5TurboStandardImageToVideoArgs, EnqueueKlingV2p5TurboStandardImageToVideoDurationSeconds};
use errors::AnyhowResult;
use std::fs::read_to_string;
use test_data::web::image_urls::TREX_SKELETON_IMAGE_URL;
use crate::requests::webhook::video::image::enqueue_kling_v2p5_turbo_standard_image_to_video_webhook::{enqueue_kling_v2p5_turbo_standard_image_to_video_webhook, EnqueueKlingV2p5TurboStandardImageToVideoArgs, EnqueueKlingV2p5TurboStandardImageToVideoDurationSeconds};
#[tokio::test]
#[ignore]
@@ -1,6 +1,7 @@
use crate::creds::fal_api_key::FalApiKey;
use crate::error::classify_fal_error::classify_fal_error;
use crate::error::fal_error_plus::FalErrorPlus;
use crate::requests::traits::fal_request_cost_calculator_trait::{FalRequestCostCalculator, UsdCents};
use fal::endpoints::fal_ai::kling_video::v2_6::kling_v2p6_pro_image_to_video::{kling_v2p6_pro_image_to_video, KlingV2p6ProImageToVideoInput};
use fal::webhook::WebhookResponse;
use reqwest::IntoUrl;
@@ -11,8 +12,13 @@ pub struct EnqueueKlingV2p6ProImageToVideoArgs<'a, R: IntoUrl> {
pub image_url: String,
// Optional args
/// NB: Generate audio defaults to true, and it costs twice as much.
pub generate_audio: Option<bool>,
pub negative_prompt: Option<String>,
/// NB: Duration defaults to 5 seconds
pub duration: Option<EnqueueKlingV2p6ProImageToVideoDurationSeconds>,
// Fulfillment
@@ -26,6 +32,26 @@ pub enum EnqueueKlingV2p6ProImageToVideoDurationSeconds {
Ten,
}
impl <U: IntoUrl> FalRequestCostCalculator for EnqueueKlingV2p6ProImageToVideoArgs<'_, U> {
fn calculate_cost_in_cents(&self) -> UsdCents {
// "For every second of video you generated, you will be
// charged $0.07 (audio off) or $0.14 (audio on).
// For example, a 5s video with audio on will cost $0.70"
let generate_audio = self.generate_audio.unwrap_or(true);
let duration = self.duration.unwrap_or(EnqueueKlingV2p6ProImageToVideoDurationSeconds::Five);
match (generate_audio, duration) {
(false, EnqueueKlingV2p6ProImageToVideoDurationSeconds::Five) => 35, // audio off: $0.07 * 5 = $0.35
(false, EnqueueKlingV2p6ProImageToVideoDurationSeconds::Ten) => 70, // audio off: $0.07 * 10 = $0.70
(true, EnqueueKlingV2p6ProImageToVideoDurationSeconds::Five) => 70, // audio on: $0.14 * 5 = $0.70
(true, EnqueueKlingV2p6ProImageToVideoDurationSeconds::Ten) => 140, // audio on: $0.14 * 10 = $1.40
}
}
}
/// Kling 2.6 Pro Image-to-Video
/// https://fal.ai/models/fal-ai/kling-video/v2.6/pro/image-to-video
pub async fn enqueue_kling_v2p6_pro_image_to_video_webhook<R: IntoUrl>(
args: EnqueueKlingV2p6ProImageToVideoArgs<'_, R>
) -> Result<WebhookResponse, FalErrorPlus> {
@@ -1,6 +1,7 @@
use crate::creds::fal_api_key::FalApiKey;
use crate::error::classify_fal_error::classify_fal_error;
use crate::error::fal_error_plus::FalErrorPlus;
use crate::requests::traits::fal_request_cost_calculator_trait::{FalRequestCostCalculator, UsdCents};
use fal::endpoints::fal_ai::pixverse::v5::pixverse_v5_image_to_video::{pixverse_v5_image_to_video, PixverseV5ImageToVideoInput};
use fal::webhook::WebhookResponse;
use reqwest::IntoUrl;
@@ -14,8 +15,11 @@ pub struct EnqueuePixverseV5ImageToVideoArgs<'a, R: IntoUrl> {
pub negative_prompt: Option<String>,
// NB: eight-second videos do not work with 1080P
// NB: Defaults to 5 seconds
pub duration: Option<EnqueuePixverseV5ImageToVideoDurationSeconds>,
pub aspect_ratio: Option<EnqueuePixverseV5ImageToVideoAspectRatio>,
// NB: Defaults to 720p
pub resolution: Option<EnqueuePixverseV5ImageToVideoResolution>,
pub style: Option<EnqueuePixverseV5ImageToVideoStyle>,
@@ -56,6 +60,33 @@ pub enum EnqueuePixverseV5ImageToVideoStyle {
Cyberpunk,
}
impl <U: IntoUrl> FalRequestCostCalculator for EnqueuePixverseV5ImageToVideoArgs<'_, U> {
fn calculate_cost_in_cents(&self) -> UsdCents {
// "For 5s video your request will cost
// $0.15 for 360p and 540p,
// $0.2 for 720p and
// $0.4 for 1080p.
// For $1 you can run this model with approximately 2 times."
let duration = self.duration.unwrap_or(EnqueuePixverseV5ImageToVideoDurationSeconds::Five);
let resolution = self.resolution.unwrap_or(EnqueuePixverseV5ImageToVideoResolution::SevenTwentyP);
match (duration, resolution) {
// Five seconds
(EnqueuePixverseV5ImageToVideoDurationSeconds::Five, EnqueuePixverseV5ImageToVideoResolution::ThreeSixtyP) => 15, // $0.15
(EnqueuePixverseV5ImageToVideoDurationSeconds::Five, EnqueuePixverseV5ImageToVideoResolution::FiveFortyP) => 15, // $0.15
(EnqueuePixverseV5ImageToVideoDurationSeconds::Five, EnqueuePixverseV5ImageToVideoResolution::SevenTwentyP) => 20, // $0.20
(EnqueuePixverseV5ImageToVideoDurationSeconds::Five, EnqueuePixverseV5ImageToVideoResolution::TenEightyP) => 40, // $0.40
// Ten seconds
(EnqueuePixverseV5ImageToVideoDurationSeconds::Eight, EnqueuePixverseV5ImageToVideoResolution::ThreeSixtyP) => 30, // NB: Doubling since unclear
(EnqueuePixverseV5ImageToVideoDurationSeconds::Eight, EnqueuePixverseV5ImageToVideoResolution::FiveFortyP) => 30, // NB: Doubling since unclear
(EnqueuePixverseV5ImageToVideoDurationSeconds::Eight, EnqueuePixverseV5ImageToVideoResolution::SevenTwentyP) => 40, // NB: Doubling since unclear
(EnqueuePixverseV5ImageToVideoDurationSeconds::Eight, EnqueuePixverseV5ImageToVideoResolution::TenEightyP) => 80, // NB: Doubling since unclear
}
}
}
/// Pixverse 5 Image-to-Video
/// https://fal.ai/models/fal-ai/pixverse/v5/image-to-video
pub async fn enqueue_pixverse_v5_image_to_video_webhook<R: IntoUrl>(
args: EnqueuePixverseV5ImageToVideoArgs<'_, R>
) -> Result<WebhookResponse, FalErrorPlus> {
@@ -0,0 +1,220 @@
use crate::creds::fal_api_key::FalApiKey;
use crate::error::classify_fal_error::classify_fal_error;
use crate::error::fal_error_plus::FalErrorPlus;
use crate::requests::traits::fal_request_cost_calculator_trait::{FalRequestCostCalculator, UsdCents};
use fal::endpoints::fal_ai::bytedance::seedance::v1::lite::image_to_video::image_to_video;
use fal::endpoints::fal_ai::bytedance::seedance::v1::lite::image_to_video::ImageToVideoRequest;
use fal::webhook::WebhookResponse;
use reqwest::IntoUrl;
pub struct Seedance1LiteArgs<'a, U: IntoUrl, T: IntoUrl, V: IntoUrl> {
pub image_url: U,
pub end_frame_image_url: Option<T>,
pub prompt: &'a str,
pub duration: Seedance1LiteDuration,
pub resolution: Seedance1LiteResolution,
pub aspect_ratio: Option<Seedance1LiteAspectRatio>,
pub camera_fixed: bool,
pub seed: Option<u32>,
pub api_key: &'a FalApiKey,
pub webhook_url: V,
}
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum Seedance1LiteDuration {
FiveSeconds,
TenSeconds,
}
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum Seedance1LiteResolution {
FourEightyP, // 480p
SevenTwentyP, // 720p
TenEightyP, // 1080p
}
/// Possible enum values: 21:9, 16:9, 4:3, 1:1, 3:4, 9:16, auto
#[derive(Copy, Clone, Debug)]
pub enum Seedance1LiteAspectRatio {
Auto,
TwentyOneByNine,
SixteenByNine,
FourByThree,
Square,
ThreeByFour,
NineBySixteen,
}
impl <U: IntoUrl, T: IntoUrl, V: IntoUrl> FalRequestCostCalculator for Seedance1LiteArgs<'_, U, T, V> {
fn calculate_cost_in_cents(&self) -> UsdCents {
// "Each 720p 5 second video costs $0.18.
// For other resolutions, 1 million video tokens costs $1.8.
// tokens(video) = (height x width x FPS x duration) / 1024."
if self.resolution == Seedance1LiteResolution::SevenTwentyP
&& self.duration == Seedance1LiteDuration::FiveSeconds
{
return 18;
}
// TODO: Only correct for some aspect ratios for now.
let (width, height) = match self.resolution {
Seedance1LiteResolution::FourEightyP => (640u32, 480u32), // NB: Only for 4:3 !
Seedance1LiteResolution::SevenTwentyP => (1280, 720), // NB: Only for 16:9 !
Seedance1LiteResolution::TenEightyP => (1920, 1080),
};
let duration = match self.duration {
Seedance1LiteDuration::FiveSeconds => 5.0,
Seedance1LiteDuration::TenSeconds => 10.0,
};
// TODO: Not sure if FPS is right.
// Inferred from https://help.scenario.com/en/articles/seedance-models-the-essentials/
const FPS : f64 = 30.0;
let tokens = (height as f64) * (width as f64) * FPS * duration;
let tokens = tokens / 1024.0;
let cost = tokens * 1.8 / 1_000_000.0;
let cost = cost * 100.0; // Dollars to cents.
let cost = cost.ceil(); // NB: This is probably what Fal does.
cost as UsdCents
}
}
/// Seedance 1.0 Lite Image-to-Video
/// https://fal.ai/models/fal-ai/bytedance/seedance/v1/lite/image-to-video
pub async fn enqueue_seedance_1_lite_image_to_video_webhook<U: IntoUrl, T: IntoUrl, V: IntoUrl>(
args: Seedance1LiteArgs<'_, U, T, V>
) -> Result<WebhookResponse, FalErrorPlus> {
let duration = match args.duration {
Seedance1LiteDuration::FiveSeconds => Some("5".to_string()),
Seedance1LiteDuration::TenSeconds => Some("10".to_string()),
};
let resolution = match args.resolution {
Seedance1LiteResolution::FourEightyP => Some("480p".to_string()),
Seedance1LiteResolution::SevenTwentyP => Some("720p".to_string()),
Seedance1LiteResolution::TenEightyP => Some("1080p".to_string()),
};
/// Possible enum values: 21:9, 16:9, 4:3, 1:1, 3:4, 9:16, auto
let aspect_ratio = args.aspect_ratio
.map(|r| match r {
Seedance1LiteAspectRatio::Auto => "auto",
Seedance1LiteAspectRatio::TwentyOneByNine => "21:9",
Seedance1LiteAspectRatio::SixteenByNine => "16:9",
Seedance1LiteAspectRatio::FourByThree => "4:3",
Seedance1LiteAspectRatio::Square => "1:1",
Seedance1LiteAspectRatio::ThreeByFour => "3:4",
Seedance1LiteAspectRatio::NineBySixteen => "9:16",
})
.map(|s| s.to_string());
let image_url = args.image_url.as_str().to_string();
let end_image_url = args.end_frame_image_url
.map(|url| url.as_str().to_string());
let request = ImageToVideoRequest {
image_url,
end_image_url,
prompt: args.prompt.to_string(),
duration,
resolution,
aspect_ratio,
// TODO: Add these later
camera_fixed: None,
seed: -1,
// Static
enable_safety_checker: Some(false),
};
let result = image_to_video(request)
.with_api_key(&args.api_key.0)
.queue_webhook(args.webhook_url)
.await;
result.map_err(|err| classify_fal_error(err))
}
#[cfg(test)]
mod tests {
use crate::creds::fal_api_key::FalApiKey;
use crate::requests::webhook::video::image::enqueue_seedance_1_lite_image_to_video_webhook::{enqueue_seedance_1_lite_image_to_video_webhook, Seedance1LiteArgs, Seedance1LiteDuration, Seedance1LiteResolution};
use errors::AnyhowResult;
use std::fs::read_to_string;
use test_data::web::image_urls::{JUNO_AT_LAKE_IMAGE_URL, TALL_MOCHI_WITH_GLASSES_IMAGE_URL};
use crate::requests::traits::fal_request_cost_calculator_trait::FalRequestCostCalculator;
#[test]
fn test_cost() {
let api_key = FalApiKey::from_str("");
let mut args = Seedance1LiteArgs {
image_url: "",
end_frame_image_url: Some(""),
prompt: "",
api_key: &api_key,
camera_fixed: false,
duration: Seedance1LiteDuration::FiveSeconds,
resolution: Seedance1LiteResolution::SevenTwentyP,
aspect_ratio: None,
seed: None,
webhook_url: "https://example.com/webhook",
};
// NB: Constant value specified by FAL
args.duration = Seedance1LiteDuration::FiveSeconds;
args.resolution = Seedance1LiteResolution::SevenTwentyP;
let cost = args.calculate_cost_in_cents();
assert_eq!(cost, 18);
// NB: Calculations follow...
args.duration = Seedance1LiteDuration::FiveSeconds;
args.resolution = Seedance1LiteResolution::TenEightyP;
let cost = args.calculate_cost_in_cents();
assert_eq!(cost, 55);
args.duration = Seedance1LiteDuration::FiveSeconds;
args.resolution = Seedance1LiteResolution::FourEightyP;
let cost = args.calculate_cost_in_cents();
assert_eq!(cost, 9);
args.duration = Seedance1LiteDuration::TenSeconds;
args.resolution = Seedance1LiteResolution::TenEightyP;
let cost = args.calculate_cost_in_cents();
assert_eq!(cost, 110);
}
#[tokio::test]
#[ignore]
async fn test() -> AnyhowResult<()> {
// 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 = Seedance1LiteArgs {
image_url: TALL_MOCHI_WITH_GLASSES_IMAGE_URL,
end_frame_image_url: Some(JUNO_AT_LAKE_IMAGE_URL.to_string()),
prompt: "shiba in glasses runs to the lake and stands by the shore",
api_key: &api_key,
camera_fixed: false,
duration: Seedance1LiteDuration::FiveSeconds,
resolution: Seedance1LiteResolution::SevenTwentyP,
aspect_ratio: None,
seed: None,
webhook_url: "https://example.com/webhook",
};
let result = enqueue_seedance_1_lite_image_to_video_webhook(args).await?;
Ok(())
}
}
@@ -0,0 +1,205 @@
use crate::creds::fal_api_key::FalApiKey;
use crate::error::classify_fal_error::classify_fal_error;
use crate::error::fal_error_plus::FalErrorPlus;
use crate::requests::traits::fal_request_cost_calculator_trait::{FalRequestCostCalculator, UsdCents};
use fal::endpoints::fal_ai::bytedance::seedance::v1::pro::image_to_video::{seedance_v1_pro_image_to_video, SeedanceImageToVideoProRequest};
use fal::webhook::WebhookResponse;
use reqwest::IntoUrl;
pub struct Seedance1ProArgs<'a, U: IntoUrl, V: IntoUrl> {
pub image_url: U,
pub webhook_url: V,
pub prompt: &'a str,
pub api_key: &'a FalApiKey,
pub camera_fixed: bool,
pub duration: Seedance1ProDuration,
pub resolution: Seedance1ProResolution,
pub seed: Option<u32>,
}
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum Seedance1ProDuration {
ThreeSeconds,
FourSeconds,
FiveSeconds,
SixSeconds,
SevenSeconds,
EightSeconds,
NineSeconds,
TenSeconds,
ElevenSeconds,
TwelveSeconds,
}
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum Seedance1ProResolution {
FourEightyP, // 480p
SevenTwentyP, // 720p
TenEightyP, // 1080p
}
impl <U: IntoUrl, V: IntoUrl> FalRequestCostCalculator for Seedance1ProArgs<'_, U, V> {
fn calculate_cost_in_cents(&self) -> UsdCents {
// "Each 1080p 5 second video costs roughly $0.62.
// For other resolutions, 1 million video tokens costs $2.5.
// tokens(video) = (height x width x FPS x duration) / 1024."
if self.resolution == Seedance1ProResolution::TenEightyP
&& self.duration == Seedance1ProDuration::FiveSeconds
{
return 62;
}
// TODO: Only correct for some aspect ratios for now.
let (width, height) = match self.resolution {
Seedance1ProResolution::FourEightyP => (640u32, 480u32), // NB: Only for 4:3 !
Seedance1ProResolution::SevenTwentyP => (1280, 720), // NB: Only for 16:9 !
Seedance1ProResolution::TenEightyP => (1920, 1080),
};
let duration = match self.duration {
Seedance1ProDuration::ThreeSeconds => 3.0,
Seedance1ProDuration::FourSeconds => 4.0,
Seedance1ProDuration::FiveSeconds => 5.0,
Seedance1ProDuration::SixSeconds => 6.0,
Seedance1ProDuration::SevenSeconds => 7.0,
Seedance1ProDuration::EightSeconds => 8.0,
Seedance1ProDuration::NineSeconds => 9.0,
Seedance1ProDuration::TenSeconds => 10.0,
Seedance1ProDuration::ElevenSeconds => 11.0,
Seedance1ProDuration::TwelveSeconds => 12.0,
};
// TODO: Not sure if FPS is right.
// Inferred from https://help.scenario.com/en/articles/seedance-models-the-essentials/
const FPS : f64 = 30.0;
let tokens = (height as f64) * (width as f64) * FPS * duration;
let tokens = tokens / 1024.0;
let cost = tokens * 2.5 / 1_000_000.0;
let cost = cost * 100.0; // Dollars to cents.
let cost = cost.ceil(); // NB: This is probably what Fal does.
cost as UsdCents
}
}
/// Seedance 1.0 Pro Image-to-Video
/// https://fal.ai/models/fal-ai/bytedance/seedance/v1/pro/image-to-video
pub async fn enqueue_seedance_1_pro_image_to_video_webhook<U: IntoUrl, V: IntoUrl>(
args: Seedance1ProArgs<'_, U, V>
) -> Result<WebhookResponse, FalErrorPlus> {
let duration = match args.duration {
Seedance1ProDuration::ThreeSeconds => Some("3".to_string()),
Seedance1ProDuration::FourSeconds => Some("4".to_string()),
Seedance1ProDuration::FiveSeconds => Some("5".to_string()),
Seedance1ProDuration::SixSeconds => Some("6".to_string()),
Seedance1ProDuration::SevenSeconds => Some("7".to_string()),
Seedance1ProDuration::EightSeconds => Some("8".to_string()),
Seedance1ProDuration::NineSeconds => Some("9".to_string()),
Seedance1ProDuration::TenSeconds => Some("10".to_string()),
Seedance1ProDuration::ElevenSeconds => Some("11".to_string()),
Seedance1ProDuration::TwelveSeconds => Some("12".to_string()),
};
let resolution = match args.resolution {
Seedance1ProResolution::FourEightyP => Some("480p".to_string()),
Seedance1ProResolution::SevenTwentyP => Some("720p".to_string()),
Seedance1ProResolution::TenEightyP => Some("1080p".to_string()),
};
let image_url = args.image_url.as_str().to_string();
let request = SeedanceImageToVideoProRequest {
image_url,
prompt: args.prompt.to_string(),
duration,
resolution,
// TODO: Add these later
camera_fixed: None,
// Static
enable_safety_checker: Some(false),
};
let result = seedance_v1_pro_image_to_video(request)
.with_api_key(&args.api_key.0)
.queue_webhook(args.webhook_url)
.await;
result.map_err(|err| classify_fal_error(err))
}
#[cfg(test)]
mod tests {
use crate::creds::fal_api_key::FalApiKey;
use crate::requests::traits::fal_request_cost_calculator_trait::FalRequestCostCalculator;
use crate::requests::webhook::video::image::enqueue_seedance_1_pro_image_to_video_webhook::{enqueue_seedance_1_pro_image_to_video_webhook, Seedance1ProArgs, Seedance1ProDuration, Seedance1ProResolution};
use errors::AnyhowResult;
use std::fs::read_to_string;
use test_data::web::image_urls::TALL_MOCHI_WITH_GLASSES_IMAGE_URL;
#[test]
fn test_cost() {
let api_key = FalApiKey::from_str("");
let mut args = Seedance1ProArgs {
image_url: "",
prompt: "",
api_key: &api_key,
camera_fixed: false,
duration: Seedance1ProDuration::FiveSeconds,
resolution: Seedance1ProResolution::TenEightyP,
seed: None,
webhook_url: "https://example.com/webhook",
};
// NB: Constant value specified by FAL
args.duration = Seedance1ProDuration::FiveSeconds;
args.resolution = Seedance1ProResolution::TenEightyP;
let cost = args.calculate_cost_in_cents();
assert_eq!(cost, 62);
// NB: Calculations follow...
args.duration = Seedance1ProDuration::FiveSeconds;
args.resolution = Seedance1ProResolution::SevenTwentyP;
let cost = args.calculate_cost_in_cents();
assert_eq!(cost, 34);
args.duration = Seedance1ProDuration::TenSeconds;
args.resolution = Seedance1ProResolution::SevenTwentyP;
let cost = args.calculate_cost_in_cents();
assert_eq!(cost, 68);
args.duration = Seedance1ProDuration::TenSeconds;
args.resolution = Seedance1ProResolution::TenEightyP;
let cost = args.calculate_cost_in_cents();
assert_eq!(cost, 152);
}
#[tokio::test]
#[ignore]
async fn test() -> AnyhowResult<()> {
// 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 = Seedance1ProArgs {
image_url: TALL_MOCHI_WITH_GLASSES_IMAGE_URL,
prompt: "shiba in glasses runs to the lake and stands by the shore",
api_key: &api_key,
camera_fixed: false,
duration: Seedance1ProDuration::FiveSeconds,
resolution: Seedance1ProResolution::SevenTwentyP,
seed: None,
webhook_url: "https://example.com/webhook",
};
let result = enqueue_seedance_1_pro_image_to_video_webhook(args).await?;
Ok(())
}
}
@@ -1,6 +1,7 @@
use crate::creds::fal_api_key::FalApiKey;
use crate::error::classify_fal_error::classify_fal_error;
use crate::error::fal_error_plus::FalErrorPlus;
use crate::requests::traits::fal_request_cost_calculator_trait::{FalRequestCostCalculator, UsdCents};
use fal::endpoints::fal_ai::sora::sora2::sora_2_image_to_video::{sora_2_image_to_video, Sora2ImageToVideoInput};
use fal::webhook::WebhookResponse;
use reqwest::IntoUrl;
@@ -11,8 +12,11 @@ pub struct EnqueueSora2ImageToVideoArgs<'a, R: IntoUrl> {
pub image_url: String,
// Optional args
pub resolution: Option<EnqueueSora2ImageToVideoResolution>,
// NB: Duration defaults to four seconds
pub duration: Option<EnqueueSora2ImageToVideoDurationSeconds>,
pub resolution: Option<EnqueueSora2ImageToVideoResolution>,
pub aspect_ratio: Option<EnqueueSora2ImageToVideoAspectRatio>,
// Fulfillment
@@ -40,6 +44,20 @@ pub enum EnqueueSora2ImageToVideoAspectRatio {
SixteenByNine,
}
impl <U: IntoUrl> FalRequestCostCalculator for EnqueueSora2ImageToVideoArgs<'_, U> {
fn calculate_cost_in_cents(&self) -> UsdCents {
// "The pricing is $0.1/s for Sora 2."
let duration = self.duration.unwrap_or(EnqueueSora2ImageToVideoDurationSeconds::Four);
match duration {
EnqueueSora2ImageToVideoDurationSeconds::Four => 40, // $0.10 * 4
EnqueueSora2ImageToVideoDurationSeconds::Eight => 80, // $0.10 * 8
EnqueueSora2ImageToVideoDurationSeconds::Twelve => 120 // $0.10 * 12
}
}
}
/// Sora 2 Image-to-Video
/// https://fal.ai/models/fal-ai/sora-2/image-to-video
pub async fn enqueue_sora_2_image_to_video_webhook<R: IntoUrl>(
args: EnqueueSora2ImageToVideoArgs<'_, R>
) -> Result<WebhookResponse, FalErrorPlus> {
@@ -1,6 +1,7 @@
use crate::creds::fal_api_key::FalApiKey;
use crate::error::classify_fal_error::classify_fal_error;
use crate::error::fal_error_plus::FalErrorPlus;
use crate::requests::traits::fal_request_cost_calculator_trait::{FalRequestCostCalculator, UsdCents};
use fal::endpoints::fal_ai::sora::sora2::sora_2_pro_image_to_video::{sora_2_pro_image_to_video, Sora2ProImageToVideoInput};
use fal::webhook::WebhookResponse;
use reqwest::IntoUrl;
@@ -41,6 +42,33 @@ pub enum EnqueueSora2ProImageToVideoAspectRatio {
SixteenByNine,
}
impl <U: IntoUrl> FalRequestCostCalculator for EnqueueSora2ProImageToVideoArgs<'_, U> {
fn calculate_cost_in_cents(&self) -> UsdCents {
// "If an OpenAI API key is provided, it will be directly charged by OpenAI.
// Otherwise, you will be charged fal credits.
// The pricing is $0.30/s for 720p and $0.50/s for 1080p."
// NB(bt): There's no way to provide an API key with this endpoint?
let duration = self.duration.unwrap_or(EnqueueSora2ProImageToVideoDurationSeconds::Four);
let resolution = self.resolution.unwrap_or(EnqueueSora2ProImageToVideoResolution::Auto); // WHAT IS THIS?!
match (duration, resolution) {
(EnqueueSora2ProImageToVideoDurationSeconds::Four, EnqueueSora2ProImageToVideoResolution::Auto) => 120, // TODO: Auto = ???
(EnqueueSora2ProImageToVideoDurationSeconds::Four, EnqueueSora2ProImageToVideoResolution::SevenTwentyP) => 120,
(EnqueueSora2ProImageToVideoDurationSeconds::Four, EnqueueSora2ProImageToVideoResolution::TenEightyP) => 200,
(EnqueueSora2ProImageToVideoDurationSeconds::Eight, EnqueueSora2ProImageToVideoResolution::Auto) => 240, // TODO: Auto = ???
(EnqueueSora2ProImageToVideoDurationSeconds::Eight, EnqueueSora2ProImageToVideoResolution::SevenTwentyP) => 240,
(EnqueueSora2ProImageToVideoDurationSeconds::Eight, EnqueueSora2ProImageToVideoResolution::TenEightyP) => 400,
(EnqueueSora2ProImageToVideoDurationSeconds::Twelve, EnqueueSora2ProImageToVideoResolution::Auto) => 360, // TODO: Auto = ???
(EnqueueSora2ProImageToVideoDurationSeconds::Twelve, EnqueueSora2ProImageToVideoResolution::SevenTwentyP) => 360,
(EnqueueSora2ProImageToVideoDurationSeconds::Twelve, EnqueueSora2ProImageToVideoResolution::TenEightyP) => 600,
}
}
}
/// Sora 2 Pro Image-to-Video
/// https://fal.ai/models/fal-ai/sora-2/image-to-video/pro
pub async fn enqueue_sora_2_pro_image_to_video_webhook<R: IntoUrl>(
args: EnqueueSora2ProImageToVideoArgs<'_, R>
) -> Result<WebhookResponse, FalErrorPlus> {
@@ -1,6 +1,7 @@
use crate::creds::fal_api_key::FalApiKey;
use crate::error::classify_fal_error::classify_fal_error;
use crate::error::fal_error_plus::FalErrorPlus;
use crate::requests::traits::fal_request_cost_calculator_trait::{FalRequestCostCalculator, UsdCents};
use fal::endpoints::fal_ai::veo2::image_to_video::image_to_video;
use fal::endpoints::fal_ai::veo2::image_to_video::ImageToVideoInput;
use fal::webhook::WebhookResponse;
@@ -17,7 +18,7 @@ pub struct Veo2Args<'a, U: IntoUrl, V: IntoUrl> {
#[derive(Copy, Clone, Debug)]
pub enum Veo2Duration {
Default,
Default, // NB: Default is 5 seconds
FiveSeconds,
SixSeconds,
SevenSeconds,
@@ -32,11 +33,28 @@ pub enum Veo2AspectRatio {
TallNineSixteen, // 9:16
}
impl <U: IntoUrl, V: IntoUrl> FalRequestCostCalculator for Veo2Args<'_, U, V> {
fn calculate_cost_in_cents(&self) -> UsdCents {
// "For 5s video your request will cost $2.50.
// For every aditional second you will be charged $0.50."
match self.duration {
Veo2Duration::Default => 250,
Veo2Duration::FiveSeconds => 250,
Veo2Duration::SixSeconds => 300,
Veo2Duration::SevenSeconds => 350,
Veo2Duration::EightSeconds => 400,
}
}
}
/// Veo 2 Image-to-Video
/// https://fal.ai/models/fal-ai/veo2/image-to-video
pub async fn enqueue_veo_2_image_to_video_webhook<U: IntoUrl, V: IntoUrl>(
args: Veo2Args<'_, U, V>
) -> Result<WebhookResponse, FalErrorPlus> {
let duration = match args.duration {
Veo2Duration::Default => None,
Veo2Duration::Default => None, // NB: Default is 5 seconds.
Veo2Duration::FiveSeconds => Some("5s".to_string()),
Veo2Duration::SixSeconds => Some("6s".to_string()),
Veo2Duration::SevenSeconds => Some("7s".to_string()),
@@ -72,7 +90,7 @@ pub async fn enqueue_veo_2_image_to_video_webhook<U: IntoUrl, V: IntoUrl>(
#[cfg(test)]
mod tests {
use crate::creds::fal_api_key::FalApiKey;
use crate::requests::webhook::video::enqueue_veo_2_image_to_video_webhook::{enqueue_veo_2_image_to_video_webhook, Veo2Args, Veo2AspectRatio, Veo2Duration};
use crate::requests::webhook::video::image::enqueue_veo_2_image_to_video_webhook::{enqueue_veo_2_image_to_video_webhook, Veo2Args, Veo2AspectRatio, Veo2Duration};
use errors::AnyhowResult;
use std::fs::read_to_string;
@@ -1,6 +1,7 @@
use crate::creds::fal_api_key::FalApiKey;
use crate::error::classify_fal_error::classify_fal_error;
use crate::error::fal_error_plus::FalErrorPlus;
use crate::requests::traits::fal_request_cost_calculator_trait::{FalRequestCostCalculator, UsdCents};
use fal::endpoints::fal_ai::veo3::image_to_video_fast::{image_to_video_fast, ImageToVideoFastInput};
use fal::webhook::WebhookResponse;
use reqwest::IntoUrl;
@@ -17,7 +18,9 @@ pub struct Veo3FastArgs<'a, U: IntoUrl, V: IntoUrl> {
#[derive(Copy, Clone, Debug)]
pub enum Veo3FastDuration {
Default,
Default, // NB: Defaults to 8 seconds.
FourSeconds,
SixSeconds,
EightSeconds,
}
@@ -28,11 +31,34 @@ pub enum Veo3FastResolution {
TenEightyP,
}
impl <U: IntoUrl, V: IntoUrl> FalRequestCostCalculator for Veo3FastArgs<'_, U, V> {
fn calculate_cost_in_cents(&self) -> UsdCents {
// "For every second of video you generated, you will be charged
// $0.10 (audio off) or $0.15 (audio on).
// For example, a 5s video with audio on will cost $0.75."
match (self.duration, self.generate_audio) {
(Veo3FastDuration::FourSeconds, false) => 40,
(Veo3FastDuration::SixSeconds, false) => 60,
(Veo3FastDuration::EightSeconds, false) => 80,
(Veo3FastDuration::Default, false) => 80, // NB: Default is 8 seconds
(Veo3FastDuration::FourSeconds, true) => 60,
(Veo3FastDuration::SixSeconds, true) => 90,
(Veo3FastDuration::EightSeconds, true) => 120,
(Veo3FastDuration::Default, true) => 120, // NB: Default is 8 seconds
}
}
}
/// Veo 3 Fast Image-to-Video
/// https://fal.ai/models/fal-ai/veo3/fast/image-to-video
pub async fn enqueue_veo_3_fast_image_to_video_webhook<U: IntoUrl, V: IntoUrl>(
args: Veo3FastArgs<'_, U, V>
) -> Result<WebhookResponse, FalErrorPlus> {
let duration = match args.duration {
Veo3FastDuration::Default => None,
Veo3FastDuration::Default => None, // NB: Defaults to 8.
Veo3FastDuration::FourSeconds => Some("4s".to_string()),
Veo3FastDuration::SixSeconds => Some("6s".to_string()),
Veo3FastDuration::EightSeconds => Some("8s".to_string()),
};
@@ -64,7 +90,7 @@ pub async fn enqueue_veo_3_fast_image_to_video_webhook<U: IntoUrl, V: IntoUrl>(
#[cfg(test)]
mod tests {
use crate::creds::fal_api_key::FalApiKey;
use crate::requests::webhook::video::enqueue_veo_3_fast_image_to_video_webhook::{enqueue_veo_3_fast_image_to_video_webhook, Veo3FastArgs, Veo3FastDuration, Veo3FastResolution};
use crate::requests::webhook::video::image::enqueue_veo_3_fast_image_to_video_webhook::{enqueue_veo_3_fast_image_to_video_webhook, Veo3FastArgs, Veo3FastDuration, Veo3FastResolution};
use errors::AnyhowResult;
use std::fs::read_to_string;
use test_data::web::image_urls::ERNEST_GHOST_TREX_IMAGE_URL;
@@ -1,6 +1,7 @@
use crate::creds::fal_api_key::FalApiKey;
use crate::error::classify_fal_error::classify_fal_error;
use crate::error::fal_error_plus::FalErrorPlus;
use crate::requests::traits::fal_request_cost_calculator_trait::{FalRequestCostCalculator, UsdCents};
use fal::endpoints::fal_ai::veo3::image_to_video::{image_to_video, ImageToVideoInput};
use fal::webhook::WebhookResponse;
use reqwest::IntoUrl;
@@ -18,7 +19,9 @@ pub struct Veo3Args<'a, U: IntoUrl, V: IntoUrl> {
#[derive(Copy, Clone, Debug)]
pub enum Veo3Duration {
Default,
Default, // Default is 8 seconds
FourSeconds,
SixSeconds,
EightSeconds,
}
@@ -37,11 +40,36 @@ pub enum Veo3Resolution {
TenEightyP,
}
impl <U: IntoUrl, V: IntoUrl> FalRequestCostCalculator for Veo3Args<'_, U, V> {
fn calculate_cost_in_cents(&self) -> UsdCents {
// "For every second of video you generated, you will be charged
// $0.20 (audio off) or
// $0.40 (audio on).
// For example, a 5s video with audio on will cost $2."
match (self.duration, self.generate_audio) {
(Veo3Duration::FourSeconds, false) => 80,
(Veo3Duration::SixSeconds, false) => 120,
(Veo3Duration::EightSeconds, false) => 160,
(Veo3Duration::Default, false) => 160, // NB: Default is 8 seconds
(Veo3Duration::FourSeconds, true) => 160,
(Veo3Duration::SixSeconds, true) => 240,
(Veo3Duration::EightSeconds, true) => 320,
(Veo3Duration::Default, true) => 320, // NB: Default is 8 seconds320
}
}
}
/// Veo 3 Image-to-Video
/// https://fal.ai/models/fal-ai/veo3/image-to-video
pub async fn enqueue_veo_3_image_to_video_webhook<U: IntoUrl, V: IntoUrl>(
args: Veo3Args<'_, U, V>
) -> Result<WebhookResponse, FalErrorPlus> {
let duration = match args.duration {
Veo3Duration::Default => None,
Veo3Duration::FourSeconds => Some("4s".to_string()),
Veo3Duration::SixSeconds => Some("6s".to_string()),
Veo3Duration::EightSeconds => Some("8s".to_string()),
};
@@ -81,10 +109,10 @@ pub async fn enqueue_veo_3_image_to_video_webhook<U: IntoUrl, V: IntoUrl>(
#[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, Veo3AspectRatio, Veo3Duration, Veo3Resolution};
use crate::requests::webhook::video::image::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, TREX_SKELETON_IMAGE_URL};
use test_data::web::image_urls::TREX_SKELETON_IMAGE_URL;
#[tokio::test]
#[ignore]
@@ -1,6 +1,7 @@
use crate::creds::fal_api_key::FalApiKey;
use crate::error::classify_fal_error::classify_fal_error;
use crate::error::fal_error_plus::FalErrorPlus;
use crate::requests::traits::fal_request_cost_calculator_trait::{FalRequestCostCalculator, UsdCents};
use fal::endpoints::fal_ai::veo::veo3_1::veo_3p1_fast_first_last_frame_image_to_video::{veo_3p1_fast_first_last_frame_image_to_video, Veo3p1FastFirstLastFrameImageToVideoInput};
use fal::webhook::WebhookResponse;
use reqwest::IntoUrl;
@@ -15,10 +16,16 @@ pub struct EnqueueVeo3p1FastFirstLastFrameImageToVideoArgs<'a, R: IntoUrl> {
// Ending frame
pub last_frame_url: String,
// Optional args
// Optional args follow
// NB: Defaults to 8 seconds
pub duration: Option<EnqueueVeo3p1FastFirstLastFrameImageToVideoDurationSeconds>,
pub aspect_ratio: Option<EnqueueVeo3p1FastFirstLastFrameImageToVideoAspectRatio>,
pub resolution: Option<EnqueueVeo3p1FastFirstLastFrameImageToVideoResolution>,
// NB: Defaults to true. Costs more to generate audio.
pub generate_audio: Option<bool>,
// Fulfillment
@@ -46,6 +53,31 @@ pub enum EnqueueVeo3p1FastFirstLastFrameImageToVideoResolution {
TenEightyP,
}
impl <R: IntoUrl> FalRequestCostCalculator for EnqueueVeo3p1FastFirstLastFrameImageToVideoArgs<'_, R> {
fn calculate_cost_in_cents(&self) -> UsdCents {
// "For every second of video you generated, you will be charged
// $0.10 (audio off) or
// $0.15 (audio on).
// For example, a 5s video with audio on will cost $0.75."
let duration = self.duration
.unwrap_or(EnqueueVeo3p1FastFirstLastFrameImageToVideoDurationSeconds::Eight);
let generate_audio = self.generate_audio.unwrap_or(true);
match (duration, generate_audio) {
(EnqueueVeo3p1FastFirstLastFrameImageToVideoDurationSeconds::Four, false) => 40,
(EnqueueVeo3p1FastFirstLastFrameImageToVideoDurationSeconds::Six, false) => 60,
(EnqueueVeo3p1FastFirstLastFrameImageToVideoDurationSeconds::Eight, false) => 80,
(EnqueueVeo3p1FastFirstLastFrameImageToVideoDurationSeconds::Four, true) => 60,
(EnqueueVeo3p1FastFirstLastFrameImageToVideoDurationSeconds::Six, true) => 90,
(EnqueueVeo3p1FastFirstLastFrameImageToVideoDurationSeconds::Eight, true) => 120,
}
}
}
/// Veo 3.1 Fast First Frame / Last Frame Image-to-Video
/// https://fal.ai/models/fal-ai/veo3.1/fast/first-last-frame-to-video
pub async fn enqueue_veo_3p1_fast_first_last_frame_image_to_video_webhook<R: IntoUrl>(
args: EnqueueVeo3p1FastFirstLastFrameImageToVideoArgs<'_, R>
) -> Result<WebhookResponse, FalErrorPlus> {
@@ -1,6 +1,7 @@
use crate::creds::fal_api_key::FalApiKey;
use crate::error::classify_fal_error::classify_fal_error;
use crate::error::fal_error_plus::FalErrorPlus;
use crate::requests::traits::fal_request_cost_calculator_trait::{FalRequestCostCalculator, UsdCents};
use fal::endpoints::fal_ai::veo::veo3_1::veo_3p1_fast_first_frame_image_to_video::{veo_3p1_fast_first_frame_image_to_video, Veo3p1FastFirstFrameImageToVideoInput};
use fal::webhook::WebhookResponse;
use reqwest::IntoUrl;
@@ -43,6 +44,32 @@ pub enum EnqueueVeo3p1FastImageToVideoResolution {
TenEightyP,
}
impl <R: IntoUrl> FalRequestCostCalculator for EnqueueVeo3p1FastImageToVideoArgs<'_, R> {
fn calculate_cost_in_cents(&self) -> UsdCents {
// "For every second of video you generated, you will be charged
// $0.10 (audio off) or
// $0.15 (audio on).
// For example, a 5s video with audio on will cost $0.75."
let duration = self.duration
.unwrap_or(EnqueueVeo3p1FastImageToVideoDurationSeconds::Eight);
let generate_audio = self.generate_audio.unwrap_or(true);
match (duration, generate_audio) {
(EnqueueVeo3p1FastImageToVideoDurationSeconds::Four, false) => 40,
(EnqueueVeo3p1FastImageToVideoDurationSeconds::Six, false) => 60,
(EnqueueVeo3p1FastImageToVideoDurationSeconds::Eight, false) => 80,
(EnqueueVeo3p1FastImageToVideoDurationSeconds::Four, true) => 60,
(EnqueueVeo3p1FastImageToVideoDurationSeconds::Six, true) => 90,
(EnqueueVeo3p1FastImageToVideoDurationSeconds::Eight, true) => 120,
}
}
}
/// Veo 3.1 Fast Image-to-Video
/// https://fal.ai/models/fal-ai/veo3.1/fast/image-to-video
pub async fn enqueue_veo_3p1_fast_image_to_video_webhook<R: IntoUrl>(
args: EnqueueVeo3p1FastImageToVideoArgs<'_, R>
) -> Result<WebhookResponse, FalErrorPlus> {
@@ -1,6 +1,7 @@
use crate::creds::fal_api_key::FalApiKey;
use crate::error::classify_fal_error::classify_fal_error;
use crate::error::fal_error_plus::FalErrorPlus;
use crate::requests::traits::fal_request_cost_calculator_trait::{FalRequestCostCalculator, UsdCents};
use fal::endpoints::fal_ai::veo::veo3_1::veo_3p1_first_last_frame_image_to_video::{veo_3p1_first_last_frame_image_to_video, Veo3p1FirstLastFrameImageToVideoInput};
use fal::webhook::WebhookResponse;
use reqwest::IntoUrl;
@@ -46,6 +47,31 @@ pub enum EnqueueVeo3p1FirstLastFrameImageToVideoResolution {
TenEightyP,
}
impl <R: IntoUrl> FalRequestCostCalculator for EnqueueVeo3p1FirstLastFrameImageToVideoArgs<'_, R> {
fn calculate_cost_in_cents(&self) -> UsdCents {
// "For every second of video you generated, you will be charged
// $0.20 (audio off) or
// $0.40 (audio on).
// For example, a 5s video with audio on will cost $2."
let duration = self.duration
.unwrap_or(EnqueueVeo3p1FirstLastFrameImageToVideoDurationSeconds::Eight);
let generate_audio = self.generate_audio.unwrap_or(true);
match (duration, generate_audio) {
(EnqueueVeo3p1FirstLastFrameImageToVideoDurationSeconds::Four, false) => 80,
(EnqueueVeo3p1FirstLastFrameImageToVideoDurationSeconds::Six, false) => 120,
(EnqueueVeo3p1FirstLastFrameImageToVideoDurationSeconds::Eight, false) => 160,
(EnqueueVeo3p1FirstLastFrameImageToVideoDurationSeconds::Four, true) => 160,
(EnqueueVeo3p1FirstLastFrameImageToVideoDurationSeconds::Six, true) => 240,
(EnqueueVeo3p1FirstLastFrameImageToVideoDurationSeconds::Eight, true) => 320,
}
}
}
/// Veo 3.1 First Frame / Last Frame Image-to-Video
/// https://fal.ai/models/fal-ai/veo3.1/first-last-frame-to-video
pub async fn enqueue_veo_3p1_first_last_frame_image_to_video_webhook<R: IntoUrl>(
args: EnqueueVeo3p1FirstLastFrameImageToVideoArgs<'_, R>
) -> Result<WebhookResponse, FalErrorPlus> {
@@ -1,6 +1,7 @@
use crate::creds::fal_api_key::FalApiKey;
use crate::error::classify_fal_error::classify_fal_error;
use crate::error::fal_error_plus::FalErrorPlus;
use crate::requests::traits::fal_request_cost_calculator_trait::{FalRequestCostCalculator, UsdCents};
use fal::endpoints::fal_ai::veo::veo3_1::veo_3p1_first_frame_image_to_video::{veo_3p1_first_frame_image_to_video, Veo3p1FirstFrameImageToVideoInput};
use fal::webhook::WebhookResponse;
use reqwest::IntoUrl;
@@ -43,6 +44,31 @@ pub enum EnqueueVeo3p1ImageToVideoResolution {
TenEightyP,
}
impl <R: IntoUrl> FalRequestCostCalculator for EnqueueVeo3p1ImageToVideoArgs<'_, R> {
fn calculate_cost_in_cents(&self) -> UsdCents {
// "For every second of video you generated, you will be charged
// $0.20 (audio off) or
// $0.40 (audio on).
// For example, a 5s video with audio on will cost $2."
let duration = self.duration
.unwrap_or(EnqueueVeo3p1ImageToVideoDurationSeconds::Eight);
let generate_audio = self.generate_audio.unwrap_or(true);
match (duration, generate_audio) {
(EnqueueVeo3p1ImageToVideoDurationSeconds::Four, false) => 80,
(EnqueueVeo3p1ImageToVideoDurationSeconds::Six, false) => 120,
(EnqueueVeo3p1ImageToVideoDurationSeconds::Eight, false) => 160,
(EnqueueVeo3p1ImageToVideoDurationSeconds::Four, true) => 160,
(EnqueueVeo3p1ImageToVideoDurationSeconds::Six, true) => 240,
(EnqueueVeo3p1ImageToVideoDurationSeconds::Eight, true) => 320,
}
}
}
/// Veo 3.1 Image-to-Video
/// https://fal.ai/models/fal-ai/veo3.1/image-to-video
pub async fn enqueue_veo_3p1_image_to_video_webhook<R: IntoUrl>(
args: EnqueueVeo3p1ImageToVideoArgs<'_, R>
) -> Result<WebhookResponse, FalErrorPlus> {
@@ -1,11 +1,19 @@
pub mod enqueue_hailuo_v2p3_fast_pro_image_to_video_webhook;
pub mod enqueue_hailuo_v2p3_pro_image_to_video_webhook;
pub mod enqueue_kling_v1p6_pro_image_to_video_webhook;
pub mod enqueue_kling_v2p1_master_image_to_video_webhook;
pub mod enqueue_kling_v2p1_pro_image_to_video_webhook;
pub mod enqueue_kling_v2p5_turbo_pro_image_to_video_webhook;
pub mod enqueue_kling_v2p5_turbo_standard_image_to_video_webhook;
pub mod enqueue_kling_v2p6_pro_image_to_video_webhook;
pub mod enqueue_pixverse_v5_image_to_video_webhook;
pub mod enqueue_seedance_1_lite_image_to_video_webhook;
pub mod enqueue_seedance_1_pro_image_to_video_webhook;
pub mod enqueue_sora_2_image_to_video_webhook;
pub mod enqueue_sora_2_pro_image_to_video_webhook;
pub mod enqueue_veo_2_image_to_video_webhook;
pub mod enqueue_veo_3_fast_image_to_video_webhook;
pub mod enqueue_veo_3_image_to_video_webhook;
pub mod enqueue_veo_3p1_fast_first_last_frame_image_to_video_webhook;
pub mod enqueue_veo_3p1_fast_image_to_video_webhook;
pub mod enqueue_veo_3p1_first_last_frame_image_to_video_webhook;
@@ -1,10 +1,2 @@
pub mod enqueue_kling_16_pro_image_to_video_webhook;
pub mod enqueue_kling_21_master_image_to_video_webhook;
pub mod enqueue_kling_21_pro_image_to_video_webhook;
pub mod enqueue_seedance_1_lite_image_to_video_webhook;
pub mod enqueue_seedance_1_pro_image_to_video_webhook;
pub mod enqueue_veo_2_image_to_video_webhook;
pub mod enqueue_veo_3_fast_image_to_video_webhook;
pub mod enqueue_veo_3_image_to_video_webhook;
pub mod image;
pub mod text;
@@ -1,6 +1,7 @@
use crate::creds::fal_api_key::FalApiKey;
use crate::error::classify_fal_error::classify_fal_error;
use crate::error::fal_error_plus::FalErrorPlus;
use crate::requests::traits::fal_request_cost_calculator_trait::{FalRequestCostCalculator, UsdCents};
use fal::endpoints::fal_ai::minimax::hailuo::hailuo_v2p3_pro_text_to_video::{hailuo_v2p3_pro_text_to_video, HailuoV2p3ProTextToVideoInput};
use fal::webhook::WebhookResponse;
use reqwest::IntoUrl;
@@ -17,6 +18,16 @@ pub struct EnqueueHailuoV2p3ProTextToVideoArgs<'a, R: IntoUrl> {
pub api_key: &'a FalApiKey,
}
impl <U: IntoUrl> FalRequestCostCalculator for EnqueueHailuoV2p3ProTextToVideoArgs<'_, U> {
fn calculate_cost_in_cents(&self) -> UsdCents {
/// "Your request will cost $0.49 per video generation."
49
}
}
/// Hailuo 2.3 Pro Text-to-Video
/// https://fal.ai/models/fal-ai/minimax/hailuo-2.3/pro/text-to-video
pub async fn enqueue_hailuo_v2p3_pro_text_to_video_webhook<R: IntoUrl>(
args: EnqueueHailuoV2p3ProTextToVideoArgs<'_, R>
) -> Result<WebhookResponse, FalErrorPlus> {
@@ -1,6 +1,7 @@
use crate::creds::fal_api_key::FalApiKey;
use crate::error::classify_fal_error::classify_fal_error;
use crate::error::fal_error_plus::FalErrorPlus;
use crate::requests::traits::fal_request_cost_calculator_trait::{FalRequestCostCalculator, UsdCents};
use fal::endpoints::fal_ai::kling_video::v2_5::kling_v2p5_turbo_pro_text_to_video::{kling_v2p5_turbo_pro_text_to_video, KlingV2p5TurboProTextToVideoInput};
use fal::webhook::WebhookResponse;
use reqwest::IntoUrl;
@@ -32,6 +33,22 @@ pub enum EnqueueKlingV2p5TurboProTextToVideoAspectRatio {
NineBySixteen,
}
impl <U: IntoUrl> FalRequestCostCalculator for EnqueueKlingV2p5TurboProTextToVideoArgs<'_, U> {
fn calculate_cost_in_cents(&self) -> UsdCents {
// "For 5s video your request will cost $0.35.
// For every additional second you will be charged $0.07."
match self.duration {
None => 35, // $0.35
Some(EnqueueKlingV2p5TurboProTextToVideoDurationSeconds::Five) => 35, // $0.35
Some(EnqueueKlingV2p5TurboProTextToVideoDurationSeconds::Ten) => 70, // $0.35 + (5 * $0.07) = $0.70
}
}
}
/// Kling 2.5 Turbo Pro Text-to-Video
/// https://fal.ai/models/fal-ai/kling-video/v2.5-turbo/pro/text-to-video
pub async fn enqueue_kling_v2p5_turbo_pro_text_to_video_webhook<R: IntoUrl>(
args: EnqueueKlingV2p5TurboProTextToVideoArgs<'_, R>
) -> Result<WebhookResponse, FalErrorPlus> {
@@ -1,6 +1,7 @@
use crate::creds::fal_api_key::FalApiKey;
use crate::error::classify_fal_error::classify_fal_error;
use crate::error::fal_error_plus::FalErrorPlus;
use crate::requests::traits::fal_request_cost_calculator_trait::{FalRequestCostCalculator, UsdCents};
use fal::endpoints::fal_ai::kling_video::v2_6::kling_v2p6_pro_text_to_video::{kling_v2p6_pro_text_to_video, KlingV2p6ProTextToVideoInput};
use fal::webhook::WebhookResponse;
use reqwest::IntoUrl;
@@ -33,6 +34,26 @@ pub enum EnqueueKlingV2p6ProTextToVideoAspectRatio {
NineBySixteen,
}
impl <U: IntoUrl> FalRequestCostCalculator for EnqueueKlingV2p6ProTextToVideoArgs<'_, U> {
fn calculate_cost_in_cents(&self) -> UsdCents {
// "For every second of video you generated, you will be
// charged $0.07 (audio off) or $0.14 (audio on).
// For example, a 5s video with audio on will cost $0.70"
let generate_audio = self.generate_audio.unwrap_or(true);
let duration = self.duration.unwrap_or(EnqueueKlingV2p6ProTextToVideoDurationSeconds::Five);
match (generate_audio, duration) {
(false, EnqueueKlingV2p6ProTextToVideoDurationSeconds::Five) => 35, // audio off: $0.07 * 5 = $0.35
(false, EnqueueKlingV2p6ProTextToVideoDurationSeconds::Ten) => 70, // audio off: $0.07 * 10 = $0.70
(true, EnqueueKlingV2p6ProTextToVideoDurationSeconds::Five) => 70, // audio on: $0.14 * 5 = $0.70
(true, EnqueueKlingV2p6ProTextToVideoDurationSeconds::Ten) => 140, // audio on: $0.14 * 10 = $1.40
}
}
}
/// Kling 2.6 Pro Text-to-Video
/// https://fal.ai/models/fal-ai/kling-video/v2.6/pro/text-to-video
pub async fn enqueue_kling_v2p6_pro_text_to_video_webhook<R: IntoUrl>(
args: EnqueueKlingV2p6ProTextToVideoArgs<'_, R>
) -> Result<WebhookResponse, FalErrorPlus> {
@@ -74,9 +95,9 @@ pub async fn enqueue_kling_v2p6_pro_text_to_video_webhook<R: IntoUrl>(
#[cfg(test)]
mod tests {
use crate::creds::fal_api_key::FalApiKey;
use crate::requests::webhook::video::text::enqueue_kling_v2p6_pro_text_to_video_webhook::{enqueue_kling_v2p6_pro_text_to_video_webhook, EnqueueKlingV2p6ProTextToVideoArgs, EnqueueKlingV2p6ProTextToVideoAspectRatio, EnqueueKlingV2p6ProTextToVideoDurationSeconds};
use errors::AnyhowResult;
use std::fs::read_to_string;
use crate::requests::webhook::video::text::enqueue_kling_v2p6_pro_text_to_video_webhook::{enqueue_kling_v2p6_pro_text_to_video_webhook, EnqueueKlingV2p6ProTextToVideoArgs, EnqueueKlingV2p6ProTextToVideoAspectRatio, EnqueueKlingV2p6ProTextToVideoDurationSeconds};
#[tokio::test]
#[ignore]
@@ -1,9 +1,10 @@
use crate::creds::fal_api_key::FalApiKey;
use crate::error::classify_fal_error::classify_fal_error;
use crate::error::fal_error_plus::FalErrorPlus;
use crate::requests::traits::fal_request_cost_calculator_trait::{FalRequestCostCalculator, UsdCents};
use fal::endpoints::fal_ai::sora::sora2::sora_2_pro_text_to_video::{sora_2_pro_text_to_video, Sora2ProTextToVideoInput};
use fal::webhook::WebhookResponse;
use reqwest::IntoUrl;
use fal::endpoints::fal_ai::sora::sora2::sora_2_pro_text_to_video::{sora_2_pro_text_to_video, Sora2ProTextToVideoInput};
pub struct EnqueueSora2ProTextToVideoArgs<'a, R: IntoUrl> {
// Request required
@@ -40,6 +41,34 @@ pub enum EnqueueSora2ProTextToVideoAspectRatio {
SixteenByNine,
}
impl <U: IntoUrl> FalRequestCostCalculator for EnqueueSora2ProTextToVideoArgs<'_, U> {
fn calculate_cost_in_cents(&self) -> UsdCents {
// "If an OpenAI API key is provided, it will be directly charged by OpenAI.
// Otherwise, you will be charged fal credits.
// The pricing is $0.30/s for 720p and $0.50/s for 1080p."
// NB(bt): There's no way to provide an API key with this endpoint?
let duration = self.duration.unwrap_or(EnqueueSora2ProTextToVideoDurationSeconds::Four);
let resolution = self.resolution.unwrap_or(EnqueueSora2ProTextToVideoResolution::Auto); // WHAT IS THIS?!
match (duration, resolution) {
(EnqueueSora2ProTextToVideoDurationSeconds::Four, EnqueueSora2ProTextToVideoResolution::Auto) => 120, // TODO: Auto = ???
(EnqueueSora2ProTextToVideoDurationSeconds::Four, EnqueueSora2ProTextToVideoResolution::SevenTwentyP) => 120,
(EnqueueSora2ProTextToVideoDurationSeconds::Four, EnqueueSora2ProTextToVideoResolution::TenEightyP) => 200,
(EnqueueSora2ProTextToVideoDurationSeconds::Eight, EnqueueSora2ProTextToVideoResolution::Auto) => 240, // TODO: Auto = ???
(EnqueueSora2ProTextToVideoDurationSeconds::Eight, EnqueueSora2ProTextToVideoResolution::SevenTwentyP) => 240,
(EnqueueSora2ProTextToVideoDurationSeconds::Eight, EnqueueSora2ProTextToVideoResolution::TenEightyP) => 400,
(EnqueueSora2ProTextToVideoDurationSeconds::Twelve, EnqueueSora2ProTextToVideoResolution::Auto) => 360, // TODO: Auto = ???
(EnqueueSora2ProTextToVideoDurationSeconds::Twelve, EnqueueSora2ProTextToVideoResolution::SevenTwentyP) => 360,
(EnqueueSora2ProTextToVideoDurationSeconds::Twelve, EnqueueSora2ProTextToVideoResolution::TenEightyP) => 600,
}
}
}
/// Sora 2 Pro Text-to-Video
/// https://fal.ai/models/fal-ai/sora-2/text-to-video/pro
pub async fn enqueue_sora_2_pro_text_to_video_webhook<R: IntoUrl>(
args: EnqueueSora2ProTextToVideoArgs<'_, R>
) -> Result<WebhookResponse, FalErrorPlus> {
@@ -88,9 +117,9 @@ pub async fn enqueue_sora_2_pro_text_to_video_webhook<R: IntoUrl>(
#[cfg(test)]
mod tests {
use crate::creds::fal_api_key::FalApiKey;
use crate::requests::webhook::video::text::enqueue_sora_2_pro_text_to_video_webhook::{enqueue_sora_2_pro_text_to_video_webhook, EnqueueSora2ProTextToVideoArgs, EnqueueSora2ProTextToVideoAspectRatio, EnqueueSora2ProTextToVideoDurationSeconds, EnqueueSora2ProTextToVideoResolution};
use errors::AnyhowResult;
use std::fs::read_to_string;
use crate::requests::webhook::video::text::enqueue_sora_2_pro_text_to_video_webhook::{enqueue_sora_2_pro_text_to_video_webhook, EnqueueSora2ProTextToVideoArgs, EnqueueSora2ProTextToVideoAspectRatio, EnqueueSora2ProTextToVideoDurationSeconds, EnqueueSora2ProTextToVideoResolution};
#[tokio::test]
#[ignore]
@@ -1,6 +1,7 @@
use crate::creds::fal_api_key::FalApiKey;
use crate::error::classify_fal_error::classify_fal_error;
use crate::error::fal_error_plus::FalErrorPlus;
use crate::requests::traits::fal_request_cost_calculator_trait::{FalRequestCostCalculator, UsdCents};
use fal::endpoints::fal_ai::sora::sora2::sora_2_text_to_video::{sora_2_text_to_video, Sora2TextToVideoInput};
use fal::webhook::WebhookResponse;
use reqwest::IntoUrl;
@@ -39,6 +40,21 @@ pub enum EnqueueSora2TextToVideoAspectRatio {
SixteenByNine,
}
impl <U: IntoUrl> FalRequestCostCalculator for EnqueueSora2TextToVideoArgs<'_, U> {
fn calculate_cost_in_cents(&self) -> UsdCents {
// "The pricing is $0.1/s for Sora 2."
let duration = self.duration.unwrap_or(EnqueueSora2TextToVideoDurationSeconds::Four);
match duration {
EnqueueSora2TextToVideoDurationSeconds::Four => 40, // $0.10 * 4
EnqueueSora2TextToVideoDurationSeconds::Eight => 80, // $0.10 * 8
EnqueueSora2TextToVideoDurationSeconds::Twelve => 120 // $0.10 * 12
}
}
}
/// Sora 2 Text-to-Video
/// https://fal.ai/models/fal-ai/sora-2/text-to-video
pub async fn enqueue_sora_2_text_to_video_webhook<R: IntoUrl>(
args: EnqueueSora2TextToVideoArgs<'_, R>
) -> Result<WebhookResponse, FalErrorPlus> {
@@ -1,6 +1,7 @@
use crate::creds::fal_api_key::FalApiKey;
use crate::error::classify_fal_error::classify_fal_error;
use crate::error::fal_error_plus::FalErrorPlus;
use crate::requests::traits::fal_request_cost_calculator_trait::{FalRequestCostCalculator, UsdCents};
use fal::endpoints::fal_ai::veo::veo3_1::veo_3p1_fast_text_to_video::{veo_3p1_fast_text_to_video, Veo3p1FastTextToVideoInput};
use fal::webhook::WebhookResponse;
use reqwest::IntoUrl;
@@ -44,6 +45,31 @@ pub enum EnqueueVeo3p1FastTextToVideoResolution {
TenEightyP,
}
impl <R: IntoUrl> FalRequestCostCalculator for EnqueueVeo3p1FastTextToVideoArgs<'_, R> {
fn calculate_cost_in_cents(&self) -> UsdCents {
// "For every second of video you generated, you will be charged
// $0.10 (audio off) or
// $0.15 (audio on).
// For example, a 5s video with audio on will cost $0.75."
let duration = self.duration
.unwrap_or(EnqueueVeo3p1FastTextToVideoDurationSeconds::Eight);
let generate_audio = self.generate_audio.unwrap_or(true);
match (duration, generate_audio) {
(EnqueueVeo3p1FastTextToVideoDurationSeconds::Four, false) => 40,
(EnqueueVeo3p1FastTextToVideoDurationSeconds::Six, false) => 60,
(EnqueueVeo3p1FastTextToVideoDurationSeconds::Eight, false) => 80,
(EnqueueVeo3p1FastTextToVideoDurationSeconds::Four, true) => 60,
(EnqueueVeo3p1FastTextToVideoDurationSeconds::Six, true) => 90,
(EnqueueVeo3p1FastTextToVideoDurationSeconds::Eight, true) => 120,
}
}
}
/// Veo 3.1 Fast Text-to-Video
/// https://fal.ai/models/fal-ai/veo3.1/fast
pub async fn enqueue_veo_3p1_fast_text_to_video_webhook<R: IntoUrl>(
args: EnqueueVeo3p1FastTextToVideoArgs<'_, R>
) -> Result<WebhookResponse, FalErrorPlus> {
@@ -1,6 +1,7 @@
use crate::creds::fal_api_key::FalApiKey;
use crate::error::classify_fal_error::classify_fal_error;
use crate::error::fal_error_plus::FalErrorPlus;
use crate::requests::traits::fal_request_cost_calculator_trait::{FalRequestCostCalculator, UsdCents};
use fal::endpoints::fal_ai::veo::veo3_1::veo_3p1_text_to_video::{veo_3p1_text_to_video, Veo3p1TextToVideoInput};
use fal::webhook::WebhookResponse;
use reqwest::IntoUrl;
@@ -44,6 +45,32 @@ pub enum EnqueueVeo3p1TextToVideoResolution {
TenEightyP,
}
impl <R: IntoUrl> FalRequestCostCalculator for EnqueueVeo3p1TextToVideoArgs<'_, R> {
fn calculate_cost_in_cents(&self) -> UsdCents {
// "For every second of video you generated, you will be charged
// $0.20 (audio off) or
// $0.40 (audio on).
// For example, a 5s video with audio on will cost $2."
let duration = self.duration
.unwrap_or(EnqueueVeo3p1TextToVideoDurationSeconds::Eight);
let generate_audio = self.generate_audio.unwrap_or(true);
match (duration, generate_audio) {
(EnqueueVeo3p1TextToVideoDurationSeconds::Four, false) => 80,
(EnqueueVeo3p1TextToVideoDurationSeconds::Six, false) => 120,
(EnqueueVeo3p1TextToVideoDurationSeconds::Eight, false) => 160,
(EnqueueVeo3p1TextToVideoDurationSeconds::Four, true) => 160,
(EnqueueVeo3p1TextToVideoDurationSeconds::Six, true) => 240,
(EnqueueVeo3p1TextToVideoDurationSeconds::Eight, true) => 320,
}
}
}
/// Veo 3.1 Text-to-Video
/// https://fal.ai/models/fal-ai/veo3.1
pub async fn enqueue_veo_3p1_text_to_video_webhook<R: IntoUrl>(
args: EnqueueVeo3p1TextToVideoArgs<'_, R>
) -> Result<WebhookResponse, FalErrorPlus> {
@@ -1,26 +1,15 @@
use anyhow::anyhow;
use log::info;
use sqlx::MySqlPool;
use sqlx::{Executor, MySql};
use std::marker::PhantomData;
use enums::by_table::generic_inference_jobs::inference_category::InferenceCategory;
use enums::by_table::generic_inference_jobs::inference_input_source_token_type::InferenceInputSourceTokenType;
use enums::by_table::generic_inference_jobs::inference_job_external_third_party::InferenceJobExternalThirdParty;
use enums::by_table::generic_inference_jobs::inference_job_product_category::InferenceJobProductCategory;
use enums::by_table::generic_inference_jobs::inference_job_type::InferenceJobType;
use enums::by_table::generic_inference_jobs::inference_model_type::InferenceModelType;
use enums::common::job_status_plus::JobStatusPlus;
use enums::common::visibility::Visibility;
use tokens::tokens::anonymous_visitor_tracking::AnonymousVisitorTrackingToken;
use tokens::tokens::generic_inference_jobs::InferenceJobToken;
use tokens::tokens::media_files::MediaFileToken;
use tokens::tokens::prompts::PromptToken;
use tokens::tokens::users::UserToken;
use crate::errors::database_query_error::DatabaseQueryError;
use crate::payloads::generic_inference_args::generic_inference_args::GenericInferenceArgs;
use crate::queries::tts::tts_inference_jobs::kill_tts_inference_jobs::JobStatus;
use crate::queries::generic_inference::fal::insert_generic_inference_job_for_fal_queue_with_apriori_job_token::{insert_generic_inference_job_for_fal_queue_with_apriori_job_token, InsertGenericInferenceForFalWithAprioriJobTokenArgs};
#[derive(Debug, Clone, Copy)]
pub enum FalCategory {
@@ -62,114 +51,20 @@ pub async fn insert_generic_inference_job_for_fal_queue<'e, 'c : 'e, E>(args: In
{
let job_token = InferenceJobToken::generate();
let serialized_args_payload = serde_json::ser::to_string(&args.maybe_inference_args)
.map_err(|_e| anyhow!("could not encode inference args"))?;
const JOB_TYPE : InferenceJobType = InferenceJobType::FalQueue;
let (inference_category, product_category) =
match args.fal_category {
FalCategory::ImageGeneration => (
InferenceCategory::ImageGeneration,
InferenceJobProductCategory::FalImage
),
FalCategory::VideoGeneration => (
InferenceCategory::VideoGeneration,
InferenceJobProductCategory::FalVideo
),
FalCategory::BackgroundRemoval => (
InferenceCategory::BackgroundRemoval,
InferenceJobProductCategory::FalBgRemoval
),
FalCategory::ObjectGeneration => (
InferenceCategory::ObjectGeneration,
InferenceJobProductCategory::FalObject,
),
};
let maybe_external_third_party = InferenceJobExternalThirdParty::Fal;
let maybe_external_third_party_id = "";
const STATUS : JobStatusPlus = JobStatusPlus::Pending;
let query = sqlx::query!(
r#"
INSERT INTO generic_inference_jobs
SET
token = ?,
uuid_idempotency_token = ?,
job_type = ?,
maybe_external_third_party = ?,
maybe_external_third_party_id = ?,
product_category = ?,
inference_category = ?,
maybe_model_type = NULL,
maybe_model_token = NULL,
maybe_input_source_token = NULL,
maybe_input_source_token_type = NULL,
maybe_download_url = NULL,
maybe_cover_image_media_file_token = NULL,
maybe_prompt_token = ?,
maybe_raw_inference_text = NULL,
maybe_inference_args = ?,
maybe_creator_user_token = ?,
maybe_creator_anonymous_visitor_token = ?,
creator_ip_address = ?,
creator_set_visibility = ?,
priority_level = 0,
is_keepalive_required = FALSE,
max_duration_seconds = 0,
is_debug_request = FALSE,
maybe_routing_tag = NULL,
status = ?
"#,
job_token.as_str(),
args.uuid_idempotency_token,
JOB_TYPE.to_str(),
maybe_external_third_party.to_str(),
args.maybe_external_third_party_id,
product_category.to_str(),
inference_category.to_str(),
args.maybe_prompt_token.map(|t| t.to_string()),
serialized_args_payload,
args.maybe_creator_user_token.map(|t| t.to_string()),
args.maybe_avt_token.map(|t| t.to_string()),
args.creator_ip_address,
args.creator_set_visibility.to_str(),
STATUS.to_str(),
);
let query_result = query.execute(args.mysql_executor)
.await;
let record_id = match query_result {
Err(err) => return Err(DatabaseQueryError::from(err)),
Ok(res) => res.last_insert_id(),
let args = InsertGenericInferenceForFalWithAprioriJobTokenArgs {
uuid_idempotency_token: args.uuid_idempotency_token,
apriori_job_token: &job_token,
maybe_external_third_party_id: args.maybe_external_third_party_id,
fal_category: args.fal_category,
maybe_inference_args: args.maybe_inference_args,
maybe_prompt_token: args.maybe_prompt_token,
maybe_creator_user_token: args.maybe_creator_user_token,
maybe_avt_token: args.maybe_avt_token,
creator_ip_address: args.creator_ip_address,
creator_set_visibility: args.creator_set_visibility,
mysql_executor: args.mysql_executor,
phantom: args.phantom,
};
info!("Insert generic inference job for FAL queue: {job_token} with record ID {record_id}");
Ok(job_token)
insert_generic_inference_job_for_fal_queue_with_apriori_job_token(args).await
}
@@ -0,0 +1,170 @@
use anyhow::anyhow;
use log::info;
use sqlx::MySqlPool;
use sqlx::{Executor, MySql};
use std::marker::PhantomData;
use enums::by_table::generic_inference_jobs::inference_category::InferenceCategory;
use enums::by_table::generic_inference_jobs::inference_input_source_token_type::InferenceInputSourceTokenType;
use enums::by_table::generic_inference_jobs::inference_job_external_third_party::InferenceJobExternalThirdParty;
use enums::by_table::generic_inference_jobs::inference_job_product_category::InferenceJobProductCategory;
use enums::by_table::generic_inference_jobs::inference_job_type::InferenceJobType;
use enums::by_table::generic_inference_jobs::inference_model_type::InferenceModelType;
use enums::common::job_status_plus::JobStatusPlus;
use enums::common::visibility::Visibility;
use tokens::tokens::anonymous_visitor_tracking::AnonymousVisitorTrackingToken;
use tokens::tokens::generic_inference_jobs::InferenceJobToken;
use tokens::tokens::media_files::MediaFileToken;
use tokens::tokens::prompts::PromptToken;
use tokens::tokens::users::UserToken;
use crate::errors::database_query_error::DatabaseQueryError;
use crate::payloads::generic_inference_args::generic_inference_args::GenericInferenceArgs;
use crate::queries::generic_inference::fal::insert_generic_inference_job_for_fal_queue::FalCategory;
use crate::queries::tts::tts_inference_jobs::kill_tts_inference_jobs::JobStatus;
pub struct InsertGenericInferenceForFalWithAprioriJobTokenArgs<'e, 'c, E>
where E: 'e + Executor<'c, Database = MySql>
{
pub uuid_idempotency_token: &'e str,
// NOTE: We'll generate this ahead of time so we can save it with billing info!
pub apriori_job_token: &'e InferenceJobToken,
/// The external primary key identifier for the job.
pub maybe_external_third_party_id: &'e str,
pub fal_category: FalCategory,
pub maybe_inference_args: Option<GenericInferenceArgs>,
pub maybe_prompt_token: Option<&'e PromptToken>,
pub maybe_creator_user_token: Option<&'e UserToken>,
pub maybe_avt_token: Option<&'e AnonymousVisitorTrackingToken>,
pub creator_ip_address: &'e str,
pub creator_set_visibility: Visibility,
pub mysql_executor: E,
// TODO: Not sure if this works to tell the compiler we need the lifetime annotation.
// See: https://doc.rust-lang.org/std/marker/struct.PhantomData.html#unused-lifetime-parameters
pub phantom: PhantomData<&'c E>,
}
pub async fn insert_generic_inference_job_for_fal_queue_with_apriori_job_token<'e, 'c : 'e, E>(args: InsertGenericInferenceForFalWithAprioriJobTokenArgs<'e, 'c, E>)
-> Result<InferenceJobToken, DatabaseQueryError>
where E: 'e + Executor<'c, Database = MySql>
{
let serialized_args_payload = serde_json::ser::to_string(&args.maybe_inference_args)
.map_err(|_e| anyhow!("could not encode inference args"))?;
const JOB_TYPE : InferenceJobType = InferenceJobType::FalQueue;
let (inference_category, product_category) =
match args.fal_category {
FalCategory::ImageGeneration => (
InferenceCategory::ImageGeneration,
InferenceJobProductCategory::FalImage
),
FalCategory::VideoGeneration => (
InferenceCategory::VideoGeneration,
InferenceJobProductCategory::FalVideo
),
FalCategory::BackgroundRemoval => (
InferenceCategory::BackgroundRemoval,
InferenceJobProductCategory::FalBgRemoval
),
FalCategory::ObjectGeneration => (
InferenceCategory::ObjectGeneration,
InferenceJobProductCategory::FalObject,
),
};
let maybe_external_third_party = InferenceJobExternalThirdParty::Fal;
let maybe_external_third_party_id = "";
const STATUS : JobStatusPlus = JobStatusPlus::Pending;
let query = sqlx::query!(
r#"
INSERT INTO generic_inference_jobs
SET
token = ?,
uuid_idempotency_token = ?,
job_type = ?,
maybe_external_third_party = ?,
maybe_external_third_party_id = ?,
product_category = ?,
inference_category = ?,
maybe_model_type = NULL,
maybe_model_token = NULL,
maybe_input_source_token = NULL,
maybe_input_source_token_type = NULL,
maybe_download_url = NULL,
maybe_cover_image_media_file_token = NULL,
maybe_prompt_token = ?,
maybe_raw_inference_text = NULL,
maybe_inference_args = ?,
maybe_creator_user_token = ?,
maybe_creator_anonymous_visitor_token = ?,
creator_ip_address = ?,
creator_set_visibility = ?,
priority_level = 0,
is_keepalive_required = FALSE,
max_duration_seconds = 0,
is_debug_request = FALSE,
maybe_routing_tag = NULL,
status = ?
"#,
args.apriori_job_token.as_str(),
args.uuid_idempotency_token,
JOB_TYPE.to_str(),
maybe_external_third_party.to_str(),
args.maybe_external_third_party_id,
product_category.to_str(),
inference_category.to_str(),
args.maybe_prompt_token.map(|t| t.to_string()),
serialized_args_payload,
args.maybe_creator_user_token.map(|t| t.to_string()),
args.maybe_avt_token.map(|t| t.to_string()),
args.creator_ip_address,
args.creator_set_visibility.to_str(),
STATUS.to_str(),
);
let query_result = query.execute(args.mysql_executor)
.await;
let record_id = match query_result {
Err(err) => return Err(DatabaseQueryError::from(err)),
Ok(res) => res.last_insert_id(),
};
info!("Insert generic inference job for FAL queue: {} with record ID {}", args.apriori_job_token, record_id);
Ok(args.apriori_job_token.clone())
}
@@ -1,3 +1,4 @@
pub mod get_inference_job_by_fal_id;
pub mod insert_generic_inference_job_for_fal_queue;
pub mod insert_generic_inference_job_for_fal_queue_with_apriori_job_token;
pub mod mark_fal_generic_inference_job_successfully_done;
@@ -1,13 +1,12 @@
use crate::queries::wallet_ledger_entries::internal_insert_wallet_ledger_entry::InsertWalletLedgerEntry;
use enums::by_table::wallet_ledger_entries::wallet_ledger_entry_type::WalletLedgerEntryType;
use errors::AnyhowResult;
use sqlx::MySql;
use tokens::tokens::wallets::WalletToken;
pub (crate) async fn internal_insert_wallet_created_ledger_entry(
wallet_token: &WalletToken,
transaction: &mut sqlx::Transaction<'_, MySql>,
) -> AnyhowResult<()> {
) -> Result<(), sqlx::Error> {
let record = InsertWalletLedgerEntry {
wallet_token,
@@ -1,6 +1,7 @@
use crate::queries::wallet_ledger_entries::internal_insert_wallet_created_ledger_entry::internal_insert_wallet_created_ledger_entry;
use enums::common::payments_namespace::PaymentsNamespace;
use errors::AnyhowResult;
use log::error;
use sqlx::MySql;
use tokens::tokens::users::UserToken;
use tokens::tokens::wallets::WalletToken;
@@ -10,7 +11,7 @@ const ARTCRAFT_NAMESPACE: &str = PaymentsNamespace::Artcraft.to_str();
pub async fn create_new_artcraft_wallet_for_owner_user(
user_token: &UserToken,
transaction: &mut sqlx::Transaction<'_, MySql>,
) -> AnyhowResult<WalletToken> {
) -> Result<WalletToken, sqlx::Error> {
let token = WalletToken::generate();
let result = sqlx::query!(
@@ -33,7 +34,8 @@ SET
.await;
if let Err(err) = result {
return Err(anyhow::anyhow!("Database query error when creating wallet: {}", err));
error!("Error while inserting wallet: {}", err);
return Err(err);
}
internal_insert_wallet_created_ledger_entry(&token, transaction).await?;
@@ -1,6 +1,7 @@
use crate::errors::select_exactly_one_error::SelectExactlyOneError;
use std::error::Error;
use std::fmt::{Display, Formatter};
use crate::errors::select_optional_record_error::SelectOptionalRecordError;
#[derive(Debug)]
pub enum WalletSpendError {
@@ -15,6 +16,9 @@ pub enum WalletSpendError {
/// Error selecting the wallet
SelectError(SelectExactlyOneError),
/// Error selecting the wallet
SelectOptionalError(SelectOptionalRecordError),
/// Error updating the wallet
SqlxError(sqlx::Error),
@@ -32,6 +36,7 @@ impl Display for WalletSpendError {
requested_to_spend_amount, available_amount)
},
WalletSpendError::SelectError(err) => write!(f, "Error selecting wallet: {}", err),
WalletSpendError::SelectOptionalError(err) => write!(f, "Error selecting wallet: {}", err),
WalletSpendError::SqlxError(err) => write!(f, "Database error: {}", err),
}
}
@@ -43,6 +48,12 @@ impl From<SelectExactlyOneError> for WalletSpendError {
}
}
impl From<SelectOptionalRecordError> for WalletSpendError {
fn from(err: SelectOptionalRecordError) -> Self {
WalletSpendError::SelectOptionalError(err)
}
}
impl From<sqlx::Error> for WalletSpendError {
fn from(err: sqlx::Error) -> Self {
WalletSpendError::SqlxError(err)
@@ -0,0 +1,134 @@
use crate::http_server::common_responses::common_web_error::CommonWebError;
use anyhow::anyhow;
use enums::common::payments_namespace::PaymentsNamespace;
use errors::AnyhowResult;
use log::{error, info, warn};
use mysql_queries::queries::wallets::create_new_artcraft_wallet_for_owner_user::create_new_artcraft_wallet_for_owner_user;
use mysql_queries::queries::wallets::find_primary_wallet_token_for_owner::{find_primary_wallet_token_for_owner_using_connection, find_primary_wallet_token_for_owner_using_transaction};
use mysql_queries::queries::wallets::spend::try_to_spend_wallet_balance::try_to_spend_wallet_balance;
use mysql_queries::queries::wallets::spend::wallet_spend_error::WalletSpendError;
use sqlx::pool::PoolConnection;
use sqlx::{Acquire, MySql};
use tokens::tokens::users::UserToken;
use tokens::tokens::wallets::WalletToken;
// TODO: This hasn't really been tested yet...
pub async fn attempt_wallet_deduction_else_common_web_error(
user_token: &UserToken,
maybe_reference_token: Option<&str>,
amount_to_deduct: u64,
connection: &mut PoolConnection<MySql>
) -> Result<(), CommonWebError> {
let result = try_wallet_deduction(
user_token,
maybe_reference_token,
amount_to_deduct,
connection
).await;
// Infallible for now.
if let Err(err) = result {
return match err {
WalletSpendError::InvalidAmountToSpend => {
log::error!("invalid spend amount charged");
Err(CommonWebError::PaymentRequired)
}
WalletSpendError::InsufficientBalance { requested_to_spend_amount, available_amount } => {
log::error!("payment is required - requested: {}, available: {}", requested_to_spend_amount, available_amount);
Err(CommonWebError::PaymentRequired)
}
WalletSpendError::SelectError(err) => {
log::error!("SQL error (select) in attempt_wallet_deduction: {:?}", err);
Err(CommonWebError::ServerError)
}
WalletSpendError::SelectOptionalError(err) => {
log::error!("SQL error (select optional) in attempt_wallet_deduction: {:?}", err);
Err(CommonWebError::ServerError)
}
WalletSpendError::SqlxError(err) => {
log::error!("SQL error (sqlx) in attempt_wallet_deduction: {:?}", err);
Err(CommonWebError::ServerError)
}
}
}
Ok(())
}
async fn try_wallet_deduction(
owner_user_token: &UserToken,
maybe_reference_token: Option<&str>,
amount_to_deduct: u64,
connection: &mut PoolConnection<MySql>
) -> Result<(), WalletSpendError>
{
let maybe_wallet_token = find_primary_wallet_token_for_owner_using_connection(
owner_user_token,
PaymentsNamespace::Artcraft,
connection
).await?;
let mut transaction = connection.begin().await?;
let result = try_wallet_deduction_with_transaction(
owner_user_token,
maybe_wallet_token,
maybe_reference_token,
amount_to_deduct,
&mut transaction
).await;
match result {
Ok(()) => {
transaction.commit().await?;
},
Err(err) => {
error!("Error handling temporary wallet deduction for user {:?} : {:?}",
owner_user_token, err);
transaction.rollback().await?;
return Err(err);
}
}
Ok(())
}
async fn try_wallet_deduction_with_transaction(
owner_user_token: &UserToken,
maybe_wallet_token: Option<WalletToken>,
maybe_reference_token: Option<&str>,
amount_to_deduct: u64,
transaction: &mut sqlx::Transaction<'_, MySql>,
) -> Result<(), WalletSpendError>
{
let wallet_token = match maybe_wallet_token {
Some(token) => token,
None => {
info!("No wallet found for user: {} ; creating a new one...", owner_user_token.as_str());
create_new_artcraft_wallet_for_owner_user(owner_user_token, transaction).await?
}
};
let result = try_to_spend_wallet_balance(
&wallet_token,
amount_to_deduct,
maybe_reference_token,
transaction
).await;
if let Err(err) = result {
error!("Failed to deduct {} credits from wallet {} for user {} : {:?}",
amount_to_deduct,
wallet_token.as_str(),
owner_user_token.as_str(),
err);
return Err(err);
}
Ok(())
}
@@ -1 +1,2 @@
pub mod attempt_wallet_deduction;
pub mod temporary_test_wallet_deduction;
@@ -1,4 +1,3 @@
use fal_client::export::prelude::rundiffusion_fal::juggernaut_flux::pro::pro;
use crate::http_server::common_responses::common_web_error::CommonWebError;
pub fn payments_error_test(prompt: &str) -> Result<(), CommonWebError> {
@@ -8,28 +7,5 @@ pub fn payments_error_test(prompt: &str) -> Result<(), CommonWebError> {
return Err(CommonWebError::PaymentRequired);
}
// TODO(bt,2025-08-30): Remove these later.
if prompt.contains("test") {
if prompt.contains("payment") {
return Err(CommonWebError::PaymentRequired)
}
if prompt.contains("stripe") {
return Err(CommonWebError::PaymentRequired)
}
if prompt.contains("error") {
return Err(CommonWebError::PaymentRequired)
}
}
// TODO(bt,2025-08-30): Remove these later.
if prompt.contains("payment") {
if prompt.contains("error") {
return Err(CommonWebError::PaymentRequired)
}
if prompt.contains("failure") {
return Err(CommonWebError::PaymentRequired)
}
}
Ok(())
}
@@ -141,27 +141,27 @@ pub async fn flux_pro_kontext_max_edit_image_handler(
CommonWebError::BadInputWithSimpleMessage("repeated idempotency token".to_string())
})?;
// TODO: This is test code
let credits = match request.num_images {
Some(FluxProKontextMaxEditImageNumImages::One) => 50,
Some(FluxProKontextMaxEditImageNumImages::Two) => 100,
Some(FluxProKontextMaxEditImageNumImages::Three) => 150,
Some(FluxProKontextMaxEditImageNumImages::Four) => 1234,
None => 50, // Default to One
};
//// TODO: This is test code
//let credits = match request.num_images {
// Some(FluxProKontextMaxEditImageNumImages::One) => 50,
// Some(FluxProKontextMaxEditImageNumImages::Two) => 100,
// Some(FluxProKontextMaxEditImageNumImages::Three) => 150,
// Some(FluxProKontextMaxEditImageNumImages::Four) => 1234,
// None => 50, // Default to One
//};
// TODO: This is test code
let result = temporary_test_wallet_deduction(
user_token,
Some("todo-reference-token"),
credits,
&mut mysql_connection,
).await;
//// TODO: This is test code
//let result = temporary_test_wallet_deduction(
// user_token,
// Some("todo-reference-token"),
// credits,
// &mut mysql_connection,
//).await;
// TODO: This is test code
if let Err(err) = result {
warn!("Temporary wallet deduction failed: {:?}", err); // Infallible for now.
}
//// TODO: This is test code
//if let Err(err) = result {
// warn!("Temporary wallet deduction failed: {:?}", err); // Infallible for now.
//}
info!("Fal webhook URL: {}", server_state.fal.webhook_url);
@@ -90,7 +90,7 @@ pub async fn generate_flux_1_schnell_text_to_image_handler(
CommonWebError::BadInputWithSimpleMessage("repeated idempotency token".to_string())
})?;
// TODO: This is test code
//// TODO: This is test code
let credits = match request.num_images {
Some(GenerateFlux1SchnellTextToImageNumImages::One) => 25,
Some(GenerateFlux1SchnellTextToImageNumImages::Two) => 50,
@@ -1,5 +1,6 @@
use std::sync::Arc;
use crate::billing::wallets::attempt_wallet_deduction::attempt_wallet_deduction_else_common_web_error;
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::generate::common::payments_error_test::payments_error_test;
@@ -17,20 +18,20 @@ 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_kling_16_pro_image_to_video_webhook::enqueue_kling_16_pro_image_to_video_webhook;
use fal_client::requests::webhook::video::enqueue_kling_16_pro_image_to_video_webhook::Kling16Duration;
use fal_client::requests::webhook::video::enqueue_kling_16_pro_image_to_video_webhook::Kling16ProArgs;
use fal_client::requests::webhook::video::enqueue_kling_16_pro_image_to_video_webhook::Kling16ProAspectRatio;
use fal_client::requests::traits::fal_request_cost_calculator_trait::FalRequestCostCalculator;
use fal_client::requests::webhook::video::image::enqueue_kling_v1p6_pro_image_to_video_webhook::{enqueue_kling_v1p6_pro_image_to_video_webhook, Kling1p6ProArgs, Kling1p6ProAspectRatio, Kling1p6ProDuration};
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::generic_inference::fal::insert_generic_inference_job_for_fal_queue_with_apriori_job_token::{insert_generic_inference_job_for_fal_queue_with_apriori_job_token, InsertGenericInferenceForFalWithAprioriJobTokenArgs};
use mysql_queries::queries::idepotency_tokens::insert_idempotency_token::insert_idempotency_token;
use mysql_queries::queries::media_files::get::batch_get_media_files_by_tokens::batch_get_media_files_by_tokens_with_connection;
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::generic_inference_jobs::InferenceJobToken;
use utoipa::ToSchema;
/// Kling 1.6 Pro Image to Video
@@ -70,15 +71,12 @@ pub async fn generate_kling_1_6_pro_video_handler(
.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 user_token = match maybe_user_session.as_ref() {
Some(session) => &session.user_token,
None => {
return Err(CommonWebError::NotAuthorized);
}
};
let start_frame_media_file_token = match &request.media_file_token {
Some(token) => token,
@@ -144,6 +142,8 @@ pub async fn generate_kling_1_6_pro_video_handler(
};
info!("Fal webhook URL: {}", server_state.fal.webhook_url);
let apriori_job_token = InferenceJobToken::generate();
let prompt = request.prompt
.as_deref()
@@ -151,19 +151,19 @@ pub async fn generate_kling_1_6_pro_video_handler(
.unwrap_or_else(|| "");
let aspect_ratio = match &request.aspect_ratio {
Some(GenerateKling16ProAspectRatio::Square) => Kling16ProAspectRatio::Square,
Some(GenerateKling16ProAspectRatio::WideSixteenNine) => Kling16ProAspectRatio::WideSixteenNine,
Some(GenerateKling16ProAspectRatio::TallNineSixteen) => Kling16ProAspectRatio::TallNineSixteen,
None => Kling16ProAspectRatio::WideSixteenNine, // Default to 16:9
Some(GenerateKling16ProAspectRatio::Square) => Kling1p6ProAspectRatio::Square,
Some(GenerateKling16ProAspectRatio::WideSixteenNine) => Kling1p6ProAspectRatio::WideSixteenNine,
Some(GenerateKling16ProAspectRatio::TallNineSixteen) => Kling1p6ProAspectRatio::TallNineSixteen,
None => Kling1p6ProAspectRatio::WideSixteenNine, // Default to 16:9
};
let duration = match &request.duration {
Some(GenerateKling16ProDuration::FiveSeconds) => Kling16Duration::FiveSeconds,
Some(GenerateKling16ProDuration::TenSeconds) => Kling16Duration::TenSeconds,
None => Kling16Duration::FiveSeconds,
Some(GenerateKling16ProDuration::FiveSeconds) => Kling1p6ProDuration::FiveSeconds,
Some(GenerateKling16ProDuration::TenSeconds) => Kling1p6ProDuration::TenSeconds,
None => Kling1p6ProDuration::FiveSeconds,
};
let args = Kling16ProArgs {
let args = Kling1p6ProArgs {
image_url: start_frame_url,
end_frame_image_url: maybe_end_frame_url,
webhook_url: &server_state.fal.webhook_url,
@@ -172,8 +172,19 @@ pub async fn generate_kling_1_6_pro_video_handler(
aspect_ratio,
api_key: &server_state.fal.api_key,
};
let cost = args.calculate_cost_in_cents();
let fal_result = enqueue_kling_16_pro_image_to_video_webhook(args)
info!("Charging wallet: {}", cost);
attempt_wallet_deduction_else_common_web_error(
user_token,
Some(apriori_job_token.as_str()),
cost,
&mut mysql_connection,
).await?;
let fal_result = enqueue_kling_v1p6_pro_image_to_video_webhook(args)
.await
.map_err(|err| {
warn!("Error calling enqueue_kling_16_pro_image_to_video_webhook: {:?}", err);
@@ -225,7 +236,8 @@ pub async fn generate_kling_1_6_pro_video_handler(
// TODO(bt,2025-08-26): Save reference images.
let db_result = insert_generic_inference_job_for_fal_queue(InsertGenericInferenceForFalArgs {
let db_result = insert_generic_inference_job_for_fal_queue_with_apriori_job_token(InsertGenericInferenceForFalWithAprioriJobTokenArgs {
apriori_job_token: &apriori_job_token,
uuid_idempotency_token: &request.uuid_idempotency_token,
maybe_external_third_party_id: &external_job_id,
fal_category: FalCategory::VideoGeneration,
@@ -1,5 +1,6 @@
use std::sync::Arc;
use crate::billing::wallets::attempt_wallet_deduction::attempt_wallet_deduction_else_common_web_error;
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::generate::common::payments_error_test::payments_error_test;
@@ -15,19 +16,19 @@ 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_kling_21_master_image_to_video_webhook::enqueue_kling_21_master_image_to_video_webhook;
use fal_client::requests::webhook::video::enqueue_kling_21_master_image_to_video_webhook::Kling21MasterArgs;
use fal_client::requests::webhook::video::enqueue_kling_21_master_image_to_video_webhook::Kling21MasterAspectRatio;
use fal_client::requests::webhook::video::enqueue_kling_21_master_image_to_video_webhook::Kling21MasterDuration;
use fal_client::requests::traits::fal_request_cost_calculator_trait::FalRequestCostCalculator;
use fal_client::requests::webhook::video::image::enqueue_kling_v2p1_master_image_to_video_webhook::{enqueue_kling_v2p1_master_image_to_video_webhook, Kling2p1MasterArgs, Kling2p1MasterAspectRatio, Kling2p1MasterDuration};
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::generic_inference::fal::insert_generic_inference_job_for_fal_queue_with_apriori_job_token::{insert_generic_inference_job_for_fal_queue_with_apriori_job_token, InsertGenericInferenceForFalWithAprioriJobTokenArgs};
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, get_media_file_with_connection};
use mysql_queries::queries::prompts::insert_prompt::{insert_prompt, InsertPromptArgs};
use sqlx::Acquire;
use tokens::tokens::generic_inference_jobs::InferenceJobToken;
use utoipa::ToSchema;
/// Kling 2.1 Master Image to Video
@@ -67,15 +68,12 @@ pub async fn generate_kling_2_1_master_video_handler(
.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 user_token = match maybe_user_session.as_ref() {
Some(session) => &session.user_token,
None => {
return Err(CommonWebError::NotAuthorized);
}
};
let media_file_token = match &request.media_file_token {
Some(token) => token,
@@ -133,25 +131,27 @@ pub async fn generate_kling_2_1_master_video_handler(
info!("Fal webhook URL: {}", server_state.fal.webhook_url);
let apriori_job_token = InferenceJobToken::generate();
let prompt = request.prompt
.as_deref()
.map(|prompt| prompt.trim())
.unwrap_or_else(|| "");
let aspect_ratio = match &request.aspect_ratio {
Some(GenerateKling21MasterAspectRatio::Square) => Kling21MasterAspectRatio::Square,
Some(GenerateKling21MasterAspectRatio::WideSixteenNine) => Kling21MasterAspectRatio::WideSixteenNine,
Some(GenerateKling21MasterAspectRatio::TallNineSixteen) => Kling21MasterAspectRatio::TallNineSixteen,
None => Kling21MasterAspectRatio::WideSixteenNine, // Default to 16:9
Some(GenerateKling21MasterAspectRatio::Square) => Kling2p1MasterAspectRatio::Square,
Some(GenerateKling21MasterAspectRatio::WideSixteenNine) => Kling2p1MasterAspectRatio::WideSixteenNine,
Some(GenerateKling21MasterAspectRatio::TallNineSixteen) => Kling2p1MasterAspectRatio::TallNineSixteen,
None => Kling2p1MasterAspectRatio::WideSixteenNine, // Default to 16:9
};
let duration = match &request.duration {
Some(GenerateKling21MasterDuration::FiveSeconds) => Kling21MasterDuration::FiveSeconds,
Some(GenerateKling21MasterDuration::TenSeconds) => Kling21MasterDuration::TenSeconds,
None => Kling21MasterDuration::FiveSeconds,
Some(GenerateKling21MasterDuration::FiveSeconds) => Kling2p1MasterDuration::FiveSeconds,
Some(GenerateKling21MasterDuration::TenSeconds) => Kling2p1MasterDuration::TenSeconds,
None => Kling2p1MasterDuration::FiveSeconds,
};
let args = Kling21MasterArgs {
let args = Kling2p1MasterArgs {
image_url: media_links.cdn_url,
webhook_url: &server_state.fal.webhook_url,
duration,
@@ -159,8 +159,19 @@ pub async fn generate_kling_2_1_master_video_handler(
aspect_ratio,
api_key: &server_state.fal.api_key,
};
let cost = args.calculate_cost_in_cents();
let fal_result = enqueue_kling_21_master_image_to_video_webhook(args)
info!("Charging wallet: {}", cost);
attempt_wallet_deduction_else_common_web_error(
user_token,
Some(apriori_job_token.as_str()),
cost,
&mut mysql_connection,
).await?;
let fal_result = enqueue_kling_v2p1_master_image_to_video_webhook(args)
.await
.map_err(|err| {
warn!("Error calling enqueue_kling_21_master_image_to_video_webhook: {:?}", err);
@@ -210,7 +221,8 @@ pub async fn generate_kling_2_1_master_video_handler(
}
};
let db_result = insert_generic_inference_job_for_fal_queue(InsertGenericInferenceForFalArgs {
let db_result = insert_generic_inference_job_for_fal_queue_with_apriori_job_token(InsertGenericInferenceForFalWithAprioriJobTokenArgs {
apriori_job_token: &apriori_job_token,
uuid_idempotency_token: &request.uuid_idempotency_token,
maybe_external_third_party_id: &external_job_id,
fal_category: FalCategory::VideoGeneration,
@@ -1,5 +1,6 @@
use std::sync::Arc;
use crate::billing::wallets::attempt_wallet_deduction::attempt_wallet_deduction_else_common_web_error;
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::generate::common::payments_error_test::payments_error_test;
@@ -17,19 +18,22 @@ 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_kling_21_pro_image_to_video_webhook::enqueue_kling_21_pro_image_to_video_webhook;
use fal_client::requests::webhook::video::enqueue_kling_21_pro_image_to_video_webhook::Kling21ProArgs;
use fal_client::requests::webhook::video::enqueue_kling_21_pro_image_to_video_webhook::Kling21ProAspectRatio;
use fal_client::requests::webhook::video::enqueue_kling_21_pro_image_to_video_webhook::Kling21ProDuration;
use fal_client::requests::traits::fal_request_cost_calculator_trait::FalRequestCostCalculator;
use fal_client::requests::webhook::video::image::enqueue_kling_v2p1_pro_image_to_video_webhook::enqueue_kling_v2p1_pro_image_to_video_webhook;
use fal_client::requests::webhook::video::image::enqueue_kling_v2p1_pro_image_to_video_webhook::Kling2p1ProArgs;
use fal_client::requests::webhook::video::image::enqueue_kling_v2p1_pro_image_to_video_webhook::Kling2p1ProAspectRatio;
use fal_client::requests::webhook::video::image::enqueue_kling_v2p1_pro_image_to_video_webhook::Kling2p1ProDuration;
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::generic_inference::fal::insert_generic_inference_job_for_fal_queue_with_apriori_job_token::{insert_generic_inference_job_for_fal_queue_with_apriori_job_token, InsertGenericInferenceForFalWithAprioriJobTokenArgs};
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, get_media_file_with_connection};
use mysql_queries::queries::prompts::insert_prompt::{insert_prompt, InsertPromptArgs};
use sqlx::Acquire;
use tokens::tokens::generic_inference_jobs::InferenceJobToken;
use utoipa::ToSchema;
/// Kling 2.1 Pro Image to Video
@@ -69,15 +73,12 @@ pub async fn generate_kling_2_1_pro_video_handler(
.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 user_token = match maybe_user_session.as_ref() {
Some(session) => &session.user_token,
None => {
return Err(CommonWebError::NotAuthorized);
}
};
let start_frame_media_file_token = match &request.media_file_token {
Some(token) => token,
@@ -144,25 +145,27 @@ pub async fn generate_kling_2_1_pro_video_handler(
info!("Fal webhook URL: {}", server_state.fal.webhook_url);
let apriori_job_token = InferenceJobToken::generate();
let prompt = request.prompt
.as_deref()
.map(|prompt| prompt.trim())
.unwrap_or_else(|| "");
let aspect_ratio = match &request.aspect_ratio {
Some(GenerateKling21ProAspectRatio::Square) => Kling21ProAspectRatio::Square,
Some(GenerateKling21ProAspectRatio::WideSixteenNine) => Kling21ProAspectRatio::WideSixteenNine,
Some(GenerateKling21ProAspectRatio::TallNineSixteen) => Kling21ProAspectRatio::TallNineSixteen,
None => Kling21ProAspectRatio::WideSixteenNine, // Default to 16:9
Some(GenerateKling21ProAspectRatio::Square) => Kling2p1ProAspectRatio::Square,
Some(GenerateKling21ProAspectRatio::WideSixteenNine) => Kling2p1ProAspectRatio::WideSixteenNine,
Some(GenerateKling21ProAspectRatio::TallNineSixteen) => Kling2p1ProAspectRatio::TallNineSixteen,
None => Kling2p1ProAspectRatio::WideSixteenNine, // Default to 16:9
};
let duration = match &request.duration {
Some(GenerateKling21ProDuration::FiveSeconds) => Kling21ProDuration::FiveSeconds,
Some(GenerateKling21ProDuration::TenSeconds) => Kling21ProDuration::TenSeconds,
None => Kling21ProDuration::FiveSeconds,
Some(GenerateKling21ProDuration::FiveSeconds) => Kling2p1ProDuration::FiveSeconds,
Some(GenerateKling21ProDuration::TenSeconds) => Kling2p1ProDuration::TenSeconds,
None => Kling2p1ProDuration::FiveSeconds,
};
let args = Kling21ProArgs {
let args = Kling2p1ProArgs {
image_url: start_frame_url,
end_frame_image_url: maybe_end_frame_url,
webhook_url: &server_state.fal.webhook_url,
@@ -171,8 +174,19 @@ pub async fn generate_kling_2_1_pro_video_handler(
aspect_ratio,
api_key: &server_state.fal.api_key,
};
let cost = args.calculate_cost_in_cents();
let fal_result = enqueue_kling_21_pro_image_to_video_webhook(args)
info!("Charging wallet: {}", cost);
attempt_wallet_deduction_else_common_web_error(
user_token,
Some(apriori_job_token.as_str()),
cost,
&mut mysql_connection,
).await?;
let fal_result = enqueue_kling_v2p1_pro_image_to_video_webhook(args)
.await
.map_err(|err| {
warn!("Error calling enqueue_kling_21_pro_image_to_video_webhook: {:?}", err);
@@ -222,7 +236,8 @@ pub async fn generate_kling_2_1_pro_video_handler(
}
};
let db_result = insert_generic_inference_job_for_fal_queue(InsertGenericInferenceForFalArgs {
let db_result = insert_generic_inference_job_for_fal_queue_with_apriori_job_token(InsertGenericInferenceForFalWithAprioriJobTokenArgs {
apriori_job_token: &apriori_job_token,
uuid_idempotency_token: &request.uuid_idempotency_token,
maybe_external_third_party_id: &external_job_id,
fal_category: FalCategory::VideoGeneration,
@@ -1,5 +1,6 @@
use std::sync::Arc;
use crate::billing::wallets::attempt_wallet_deduction::attempt_wallet_deduction_else_common_web_error;
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::generate::common::payments_error_test::payments_error_test;
@@ -10,27 +11,30 @@ use crate::util::lookup::fetch_all_required_media_files::fetch_all_required_medi
use crate::util::traits::into_media_links_trait::IntoMediaLinks;
use actix_web::web::Json;
use actix_web::{web, HttpRequest};
use artcraft_api_defs::generate::video::generate_seedance_1_0_lite_image_to_video::GenerateSeedance10LiteDuration;
use artcraft_api_defs::generate::video::generate_seedance_1_0_lite_image_to_video::GenerateSeedance10LiteImageToVideoRequest;
use artcraft_api_defs::generate::video::generate_seedance_1_0_lite_image_to_video::GenerateSeedance10LiteImageToVideoResponse;
use artcraft_api_defs::generate::video::generate_seedance_1_0_lite_image_to_video::GenerateSeedance10LiteResolution;
use artcraft_api_defs::generate::video::generate_seedance_1_0_lite_image_to_video::{GenerateSeedance10LiteAspectRatio, GenerateSeedance10LiteDuration};
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_seedance_1_lite_image_to_video_webhook::enqueue_seedance_1_lite_image_to_video_webhook;
use fal_client::requests::webhook::video::enqueue_seedance_1_lite_image_to_video_webhook::Seedance1LiteDuration;
use fal_client::requests::webhook::video::enqueue_seedance_1_lite_image_to_video_webhook::{Seedance1LiteArgs, Seedance1LiteResolution};
use fal_client::requests::traits::fal_request_cost_calculator_trait::FalRequestCostCalculator;
use fal_client::requests::webhook::video::image::enqueue_seedance_1_lite_image_to_video_webhook::Seedance1LiteDuration;
use fal_client::requests::webhook::video::image::enqueue_seedance_1_lite_image_to_video_webhook::{enqueue_seedance_1_lite_image_to_video_webhook, Seedance1LiteAspectRatio};
use fal_client::requests::webhook::video::image::enqueue_seedance_1_lite_image_to_video_webhook::{Seedance1LiteArgs, Seedance1LiteResolution};
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::generic_inference::fal::insert_generic_inference_job_for_fal_queue_with_apriori_job_token::{insert_generic_inference_job_for_fal_queue_with_apriori_job_token, InsertGenericInferenceForFalWithAprioriJobTokenArgs};
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, get_media_file_with_connection};
use mysql_queries::queries::prompts::insert_prompt::{insert_prompt, InsertPromptArgs};
use sqlx::Acquire;
use tokens::tokens::generic_inference_jobs::InferenceJobToken;
use utoipa::ToSchema;
/// Seedance 1.0 Lite Image to Video
@@ -70,15 +74,12 @@ pub async fn generate_seedance_1_0_lite_image_to_video_handler(
.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 user_token = match maybe_user_session.as_ref() {
Some(session) => &session.user_token,
None => {
return Err(CommonWebError::NotAuthorized);
}
};
let start_frame_media_file_token = match &request.media_file_token {
Some(token) => token,
@@ -145,6 +146,8 @@ pub async fn generate_seedance_1_0_lite_image_to_video_handler(
info!("Fal webhook URL: {}", server_state.fal.webhook_url);
let apriori_job_token = InferenceJobToken::generate();
let prompt = request.prompt
.as_deref()
.map(|prompt| prompt.trim())
@@ -161,7 +164,19 @@ pub async fn generate_seedance_1_0_lite_image_to_video_handler(
Some(GenerateSeedance10LiteDuration::TenSeconds) => Seedance1LiteDuration::TenSeconds,
None => Seedance1LiteDuration::FiveSeconds,
};
let aspect_ratio = request.aspect_ratio
.as_ref()
.map(|ar| match ar {
GenerateSeedance10LiteAspectRatio::Auto => Seedance1LiteAspectRatio::Auto,
GenerateSeedance10LiteAspectRatio::TwentyOneByNine => Seedance1LiteAspectRatio::TwentyOneByNine,
GenerateSeedance10LiteAspectRatio::SixteenByNine => Seedance1LiteAspectRatio::SixteenByNine,
GenerateSeedance10LiteAspectRatio::FourByThree => Seedance1LiteAspectRatio::FourByThree,
GenerateSeedance10LiteAspectRatio::Square => Seedance1LiteAspectRatio::Square,
GenerateSeedance10LiteAspectRatio::ThreeByFour => Seedance1LiteAspectRatio::ThreeByFour,
GenerateSeedance10LiteAspectRatio::NineBySixteen => Seedance1LiteAspectRatio::NineBySixteen,
});
let args = Seedance1LiteArgs {
image_url: start_frame_url,
end_frame_image_url: maybe_end_frame_url,
@@ -170,9 +185,21 @@ pub async fn generate_seedance_1_0_lite_image_to_video_handler(
duration,
resolution,
prompt,
aspect_ratio,
camera_fixed: false, // TODO: Parameterize
seed: None, // TODO: Parameterize
};
let cost = args.calculate_cost_in_cents();
info!("Charging wallet: {}", cost);
attempt_wallet_deduction_else_common_web_error(
user_token,
Some(apriori_job_token.as_str()),
cost,
&mut mysql_connection,
).await?;
let fal_result = enqueue_seedance_1_lite_image_to_video_webhook(args)
.await
@@ -224,7 +251,8 @@ pub async fn generate_seedance_1_0_lite_image_to_video_handler(
}
};
let db_result = insert_generic_inference_job_for_fal_queue(InsertGenericInferenceForFalArgs {
let db_result = insert_generic_inference_job_for_fal_queue_with_apriori_job_token(InsertGenericInferenceForFalWithAprioriJobTokenArgs {
apriori_job_token: &apriori_job_token,
uuid_idempotency_token: &request.uuid_idempotency_token,
maybe_external_third_party_id: &external_job_id,
fal_category: FalCategory::VideoGeneration,
@@ -1,5 +1,6 @@
use std::sync::Arc;
use crate::billing::wallets::attempt_wallet_deduction::attempt_wallet_deduction_else_common_web_error;
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::generate::common::payments_error_test::payments_error_test;
@@ -19,18 +20,21 @@ 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_seedance_1_pro_image_to_video_webhook::enqueue_seedance_1_pro_image_to_video_webhook;
use fal_client::requests::webhook::video::enqueue_seedance_1_pro_image_to_video_webhook::Seedance1ProDuration;
use fal_client::requests::webhook::video::enqueue_seedance_1_pro_image_to_video_webhook::{Seedance1ProArgs, Seedance1ProResolution};
use fal_client::requests::traits::fal_request_cost_calculator_trait::FalRequestCostCalculator;
use fal_client::requests::webhook::video::image::enqueue_seedance_1_pro_image_to_video_webhook::enqueue_seedance_1_pro_image_to_video_webhook;
use fal_client::requests::webhook::video::image::enqueue_seedance_1_pro_image_to_video_webhook::Seedance1ProDuration;
use fal_client::requests::webhook::video::image::enqueue_seedance_1_pro_image_to_video_webhook::{Seedance1ProArgs, Seedance1ProResolution};
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::generic_inference::fal::insert_generic_inference_job_for_fal_queue_with_apriori_job_token::{insert_generic_inference_job_for_fal_queue_with_apriori_job_token, InsertGenericInferenceForFalWithAprioriJobTokenArgs};
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, get_media_file_with_connection};
use mysql_queries::queries::prompts::insert_prompt::{insert_prompt, InsertPromptArgs};
use sqlx::Acquire;
use tokens::tokens::generic_inference_jobs::InferenceJobToken;
use utoipa::ToSchema;
/// Seedance 1.0 Pro Image to Video
@@ -70,15 +74,12 @@ pub async fn generate_seedance_1_0_pro_image_to_video_handler(
.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 user_token = match maybe_user_session.as_ref() {
Some(session) => &session.user_token,
None => {
return Err(CommonWebError::NotAuthorized);
}
};
let start_frame_media_file_token = match &request.media_file_token {
Some(token) => token,
@@ -126,7 +127,9 @@ pub async fn generate_seedance_1_0_pro_image_to_video_handler(
})?;
info!("Fal webhook URL: {}", server_state.fal.webhook_url);
let apriori_job_token = InferenceJobToken::generate();
let prompt = request.prompt
.as_deref()
.map(|prompt| prompt.trim())
@@ -164,6 +167,17 @@ pub async fn generate_seedance_1_0_pro_image_to_video_handler(
seed: None, // TODO: Parameterize
};
let cost = args.calculate_cost_in_cents();
info!("Charging wallet: {}", cost);
attempt_wallet_deduction_else_common_web_error(
user_token,
Some(apriori_job_token.as_str()),
cost,
&mut mysql_connection,
).await?;
let fal_result = enqueue_seedance_1_pro_image_to_video_webhook(args)
.await
.map_err(|err| {
@@ -214,7 +228,8 @@ pub async fn generate_seedance_1_0_pro_image_to_video_handler(
}
};
let db_result = insert_generic_inference_job_for_fal_queue(InsertGenericInferenceForFalArgs {
let db_result = insert_generic_inference_job_for_fal_queue_with_apriori_job_token(InsertGenericInferenceForFalWithAprioriJobTokenArgs {
apriori_job_token: &apriori_job_token,
uuid_idempotency_token: &request.uuid_idempotency_token,
maybe_external_third_party_id: &external_job_id,
fal_category: FalCategory::VideoGeneration,
@@ -1,5 +1,6 @@
use std::sync::Arc;
use crate::billing::wallets::attempt_wallet_deduction::attempt_wallet_deduction_else_common_web_error;
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;
@@ -19,19 +20,22 @@ 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_2_image_to_video_webhook::enqueue_veo_2_image_to_video_webhook;
use fal_client::requests::webhook::video::enqueue_veo_2_image_to_video_webhook::Veo2Args;
use fal_client::requests::webhook::video::enqueue_veo_2_image_to_video_webhook::Veo2AspectRatio;
use fal_client::requests::webhook::video::enqueue_veo_2_image_to_video_webhook::Veo2Duration;
use fal_client::requests::traits::fal_request_cost_calculator_trait::FalRequestCostCalculator;
use fal_client::requests::webhook::video::image::enqueue_veo_2_image_to_video_webhook::enqueue_veo_2_image_to_video_webhook;
use fal_client::requests::webhook::video::image::enqueue_veo_2_image_to_video_webhook::Veo2Args;
use fal_client::requests::webhook::video::image::enqueue_veo_2_image_to_video_webhook::Veo2AspectRatio;
use fal_client::requests::webhook::video::image::enqueue_veo_2_image_to_video_webhook::Veo2Duration;
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::generic_inference::fal::insert_generic_inference_job_for_fal_queue_with_apriori_job_token::{insert_generic_inference_job_for_fal_queue_with_apriori_job_token, InsertGenericInferenceForFalWithAprioriJobTokenArgs};
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::generic_inference_jobs::InferenceJobToken;
use tokens::tokens::prompts::PromptToken;
use utoipa::ToSchema;
@@ -72,15 +76,12 @@ pub async fn generate_veo_2_image_to_video_handler(
.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 user_token = match maybe_user_session.as_ref() {
Some(session) => &session.user_token,
None => {
return Err(CommonWebError::NotAuthorized);
}
};
let media_file_token = match &request.media_file_token {
Some(token) => token,
@@ -138,6 +139,8 @@ pub async fn generate_veo_2_image_to_video_handler(
info!("Fal webhook URL: {}", server_state.fal.webhook_url);
let apriori_job_token = InferenceJobToken::generate();
let prompt = request.prompt
.as_deref()
.map(|prompt| prompt.trim())
@@ -167,6 +170,17 @@ pub async fn generate_veo_2_image_to_video_handler(
aspect_ratio,
api_key: &server_state.fal.api_key,
};
let cost = args.calculate_cost_in_cents();
info!("Charging wallet: {}", cost);
attempt_wallet_deduction_else_common_web_error(
user_token,
Some(apriori_job_token.as_str()),
cost,
&mut mysql_connection,
).await?;
let fal_result = enqueue_veo_2_image_to_video_webhook(args)
.await
@@ -218,7 +232,8 @@ pub async fn generate_veo_2_image_to_video_handler(
}
};
let db_result = insert_generic_inference_job_for_fal_queue(InsertGenericInferenceForFalArgs {
let db_result = insert_generic_inference_job_for_fal_queue_with_apriori_job_token(InsertGenericInferenceForFalWithAprioriJobTokenArgs {
apriori_job_token: &apriori_job_token,
uuid_idempotency_token: &request.uuid_idempotency_token,
maybe_external_third_party_id: &external_job_id,
fal_category: FalCategory::VideoGeneration,
@@ -1,5 +1,6 @@
use std::sync::Arc;
use crate::billing::wallets::attempt_wallet_deduction::attempt_wallet_deduction_else_common_web_error;
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;
@@ -16,16 +17,19 @@ 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_fast_image_to_video_webhook::{enqueue_veo_3_fast_image_to_video_webhook, Veo3FastArgs, Veo3FastDuration, Veo3FastResolution};
use fal_client::requests::traits::fal_request_cost_calculator_trait::FalRequestCostCalculator;
use fal_client::requests::webhook::video::image::enqueue_veo_3_fast_image_to_video_webhook::{enqueue_veo_3_fast_image_to_video_webhook, Veo3FastArgs, Veo3FastDuration, Veo3FastResolution};
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::generic_inference::fal::insert_generic_inference_job_for_fal_queue_with_apriori_job_token::{insert_generic_inference_job_for_fal_queue_with_apriori_job_token, InsertGenericInferenceForFalWithAprioriJobTokenArgs};
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::generic_inference_jobs::InferenceJobToken;
use tokens::tokens::prompts::PromptToken;
use utoipa::ToSchema;
@@ -66,15 +70,12 @@ pub async fn generate_veo_3_fast_image_to_video_handler(
.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 user_token = match maybe_user_session.as_ref() {
Some(session) => &session.user_token,
None => {
return Err(CommonWebError::NotAuthorized);
}
};
let media_file_token = match &request.media_file_token {
Some(token) => token,
@@ -131,6 +132,8 @@ pub async fn generate_veo_3_fast_image_to_video_handler(
&bucket_path);
info!("Fal webhook URL: {}", server_state.fal.webhook_url);
let apriori_job_token = InferenceJobToken::generate();
let prompt = request.prompt
.as_deref()
@@ -160,6 +163,17 @@ pub async fn generate_veo_3_fast_image_to_video_handler(
webhook_url: &server_state.fal.webhook_url,
api_key: &server_state.fal.api_key,
};
let cost = args.calculate_cost_in_cents();
info!("Charging wallet: {}", cost);
attempt_wallet_deduction_else_common_web_error(
user_token,
Some(apriori_job_token.as_str()),
cost,
&mut mysql_connection,
).await?;
let fal_result = enqueue_veo_3_fast_image_to_video_webhook(args)
.await
@@ -211,7 +225,8 @@ pub async fn generate_veo_3_fast_image_to_video_handler(
}
};
let db_result = insert_generic_inference_job_for_fal_queue(InsertGenericInferenceForFalArgs {
let db_result = insert_generic_inference_job_for_fal_queue_with_apriori_job_token(InsertGenericInferenceForFalWithAprioriJobTokenArgs {
apriori_job_token: &apriori_job_token,
uuid_idempotency_token: &request.uuid_idempotency_token,
maybe_external_third_party_id: &external_job_id,
fal_category: FalCategory::VideoGeneration,
@@ -1,5 +1,6 @@
use std::sync::Arc;
use crate::billing::wallets::attempt_wallet_deduction::attempt_wallet_deduction_else_common_web_error;
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;
@@ -16,16 +17,19 @@ 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 fal_client::requests::traits::fal_request_cost_calculator_trait::FalRequestCostCalculator;
use fal_client::requests::webhook::video::image::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::generic_inference::fal::insert_generic_inference_job_for_fal_queue_with_apriori_job_token::{insert_generic_inference_job_for_fal_queue_with_apriori_job_token, InsertGenericInferenceForFalWithAprioriJobTokenArgs};
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::generic_inference_jobs::InferenceJobToken;
use tokens::tokens::prompts::PromptToken;
use utoipa::ToSchema;
@@ -66,15 +70,12 @@ pub async fn generate_veo_3_image_to_video_handler(
.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 user_token = match maybe_user_session.as_ref() {
Some(session) => &session.user_token,
None => {
return Err(CommonWebError::NotAuthorized);
}
};
let media_file_token = match &request.media_file_token {
Some(token) => token,
@@ -132,6 +133,8 @@ pub async fn generate_veo_3_image_to_video_handler(
info!("Fal webhook URL: {}", server_state.fal.webhook_url);
let apriori_job_token = InferenceJobToken::generate();
let prompt = request.prompt
.as_deref()
.map(|prompt| prompt.trim())
@@ -168,6 +171,17 @@ pub async fn generate_veo_3_image_to_video_handler(
webhook_url: &server_state.fal.webhook_url,
api_key: &server_state.fal.api_key,
};
let cost = args.calculate_cost_in_cents();
info!("Charging wallet: {}", cost);
attempt_wallet_deduction_else_common_web_error(
user_token,
Some(apriori_job_token.as_str()),
cost,
&mut mysql_connection,
).await?;
let fal_result = enqueue_veo_3_image_to_video_webhook(args)
.await
@@ -219,7 +233,8 @@ pub async fn generate_veo_3_image_to_video_handler(
}
};
let db_result = insert_generic_inference_job_for_fal_queue(InsertGenericInferenceForFalArgs {
let db_result = insert_generic_inference_job_for_fal_queue_with_apriori_job_token(InsertGenericInferenceForFalWithAprioriJobTokenArgs {
apriori_job_token: &apriori_job_token,
uuid_idempotency_token: &request.uuid_idempotency_token,
maybe_external_third_party_id: &external_job_id,
fal_category: FalCategory::VideoGeneration,
@@ -2,6 +2,7 @@ use std::collections::{HashMap, HashSet};
use std::iter::FromIterator;
use std::sync::Arc;
use crate::billing::wallets::attempt_wallet_deduction::attempt_wallet_deduction_else_common_web_error;
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::generate::common::payments_error_test::payments_error_test;
@@ -19,6 +20,7 @@ use enums::common::generation_provider::GenerationProvider;
use enums::common::model_type::ModelType;
use enums::common::visibility::Visibility;
use fal_client::creds::open_ai_api_key::OpenAiApiKey;
use fal_client::requests::traits::fal_request_cost_calculator_trait::FalRequestCostCalculator;
use fal_client::requests::webhook::image::edit::enqueue_gpt_image_1p5_edit_image_webhook::{enqueue_gpt_image_1p5_image_edit_webhook, EnqueueGptImage1p5EditImageArgs, EnqueueGptImage1p5EditImageBackground, EnqueueGptImage1p5EditImageInputFidelity, EnqueueGptImage1p5EditImageNumImages, EnqueueGptImage1p5EditImageQuality, EnqueueGptImage1p5EditImageSize};
use fal_client::requests::webhook::image::text::enqueue_gpt_image_1p5_text_to_image_webhook::{enqueue_gpt_image_1p5_text_to_image_webhook, EnqueueGptImage1p5TextToImageArgs, EnqueueGptImage1p5TextToImageBackground, EnqueueGptImage1p5TextToImageNumImages, EnqueueGptImage1p5TextToImageQuality, EnqueueGptImage1p5TextToImageSize};
use fal_client::requests::webhook::video::image::enqueue_kling_v2p5_turbo_pro_image_to_video_webhook::{enqueue_kling_v2p5_turbo_pro_image_to_video_webhook, EnqueueKlingV2p5TurboProImageToVideoArgs, EnqueueKlingV2p5TurboProImageToVideoDurationSeconds};
@@ -28,6 +30,7 @@ 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::generic_inference::fal::insert_generic_inference_job_for_fal_queue_with_apriori_job_token::{insert_generic_inference_job_for_fal_queue_with_apriori_job_token, InsertGenericInferenceForFalWithAprioriJobTokenArgs};
use mysql_queries::queries::idepotency_tokens::insert_idempotency_token::insert_idempotency_token;
use mysql_queries::queries::media_files::get::batch_get_media_files_by_tokens::{batch_get_media_files_by_tokens, batch_get_media_files_by_tokens_with_connection};
use mysql_queries::queries::prompt_context_items::insert_batch_prompt_context_items::{insert_batch_prompt_context_items, InsertBatchArgs, PromptContextItem};
@@ -35,6 +38,7 @@ use mysql_queries::queries::prompts::insert_prompt::{insert_prompt, InsertPrompt
use server_environment::ServerEnvironment;
use sqlx::pool::PoolConnection;
use sqlx::{Acquire, MySql};
use tokens::tokens::generic_inference_jobs::InferenceJobToken;
use tokens::tokens::media_files::MediaFileToken;
use utoipa::ToSchema;
@@ -75,15 +79,12 @@ pub async fn kling_2p5_turbo_pro_multi_function_video_gen_handler(
.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(CommonWebError::NotAuthorized);
// }
//};
let user_token = match maybe_user_session.as_ref() {
Some(session) => &session.user_token,
None => {
return Err(CommonWebError::NotAuthorized);
}
};
if let Err(reason) = validate_idempotency_token_format(&request.uuid_idempotency_token) {
return Err(CommonWebError::BadInputWithSimpleMessage(reason));
@@ -145,6 +146,8 @@ pub async fn kling_2p5_turbo_pro_multi_function_video_gen_handler(
info!("Fal webhook URL: {}", server_state.fal.webhook_url);
let apriori_job_token = InferenceJobToken::generate();
let fal_result;
if let Some(start_frame_url) = maybe_start_frame_image_url {
@@ -166,6 +169,17 @@ pub async fn kling_2p5_turbo_pro_multi_function_video_gen_handler(
api_key: &server_state.fal.api_key,
};
let cost = args.calculate_cost_in_cents();
info!("Charging wallet: {}", cost);
attempt_wallet_deduction_else_common_web_error(
user_token,
Some(apriori_job_token.as_str()),
cost,
&mut mysql_connection,
).await?;
fal_result = enqueue_kling_v2p5_turbo_pro_image_to_video_webhook(args)
.await
.map_err(|err| {
@@ -198,6 +212,17 @@ pub async fn kling_2p5_turbo_pro_multi_function_video_gen_handler(
api_key: &server_state.fal.api_key,
};
let cost = args.calculate_cost_in_cents();
info!("Charging wallet: {}", cost);
attempt_wallet_deduction_else_common_web_error(
user_token,
Some(apriori_job_token.as_str()),
cost,
&mut mysql_connection,
).await?;
fal_result = enqueue_kling_v2p5_turbo_pro_text_to_video_webhook(args)
.await
.map_err(|err| {
@@ -280,7 +305,8 @@ pub async fn kling_2p5_turbo_pro_multi_function_video_gen_handler(
}
}
let db_result = insert_generic_inference_job_for_fal_queue(InsertGenericInferenceForFalArgs {
let db_result = insert_generic_inference_job_for_fal_queue_with_apriori_job_token(InsertGenericInferenceForFalWithAprioriJobTokenArgs {
apriori_job_token: &apriori_job_token,
uuid_idempotency_token: &request.uuid_idempotency_token,
maybe_external_third_party_id: &external_job_id,
fal_category: FalCategory::VideoGeneration,
@@ -2,6 +2,8 @@ use std::collections::{HashMap, HashSet};
use std::iter::FromIterator;
use std::sync::Arc;
use crate::billing::wallets::attempt_wallet_deduction::attempt_wallet_deduction_else_common_web_error;
use crate::billing::wallets::temporary_test_wallet_deduction::temporary_test_wallet_deduction;
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::generate::common::payments_error_test::payments_error_test;
@@ -19,20 +21,23 @@ use enums::common::generation_provider::GenerationProvider;
use enums::common::model_type::ModelType;
use enums::common::visibility::Visibility;
use fal_client::creds::open_ai_api_key::OpenAiApiKey;
use fal_client::requests::traits::fal_request_cost_calculator_trait::FalRequestCostCalculator;
use fal_client::requests::webhook::video::image::enqueue_kling_v2p6_pro_image_to_video_webhook::{enqueue_kling_v2p6_pro_image_to_video_webhook, EnqueueKlingV2p6ProImageToVideoArgs, EnqueueKlingV2p6ProImageToVideoDurationSeconds};
use fal_client::requests::webhook::video::text::enqueue_kling_v2p6_pro_text_to_video_webhook::{enqueue_kling_v2p6_pro_text_to_video_webhook, EnqueueKlingV2p6ProTextToVideoArgs, EnqueueKlingV2p6ProTextToVideoAspectRatio, EnqueueKlingV2p6ProTextToVideoDurationSeconds};
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::generic_inference::fal::insert_generic_inference_job_for_fal_queue_with_apriori_job_token::{insert_generic_inference_job_for_fal_queue_with_apriori_job_token, InsertGenericInferenceForFalWithAprioriJobTokenArgs};
use mysql_queries::queries::idepotency_tokens::insert_idempotency_token::insert_idempotency_token;
use mysql_queries::queries::media_files::get::batch_get_media_files_by_tokens::{batch_get_media_files_by_tokens, batch_get_media_files_by_tokens_with_connection};
use mysql_queries::queries::prompt_context_items::insert_batch_prompt_context_items::{insert_batch_prompt_context_items, InsertBatchArgs, PromptContextItem};
use mysql_queries::queries::prompts::insert_prompt::{insert_prompt, InsertPromptArgs};
use mysql_queries::queries::wallets::spend::wallet_spend_error::WalletSpendError;
use server_environment::ServerEnvironment;
use sqlx::pool::PoolConnection;
use sqlx::{Acquire, MySql};
use tokens::tokens::generic_inference_jobs::InferenceJobToken;
use tokens::tokens::media_files::MediaFileToken;
use utoipa::ToSchema;
@@ -73,15 +78,12 @@ pub async fn kling_2p6_pro_multi_function_video_gen_handler(
.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(CommonWebError::NotAuthorized);
// }
//};
let user_token = match maybe_user_session.as_ref() {
Some(session) => &session.user_token,
None => {
return Err(CommonWebError::NotAuthorized);
}
};
if let Err(reason) = validate_idempotency_token_format(&request.uuid_idempotency_token) {
return Err(CommonWebError::BadInputWithSimpleMessage(reason));
@@ -143,6 +145,8 @@ pub async fn kling_2p6_pro_multi_function_video_gen_handler(
info!("Fal webhook URL: {}", server_state.fal.webhook_url);
let apriori_job_token = InferenceJobToken::generate();
let fal_result;
if let Some(start_frame_url) = maybe_start_frame_image_url {
@@ -163,6 +167,17 @@ pub async fn kling_2p6_pro_multi_function_video_gen_handler(
webhook_url: &server_state.fal.webhook_url,
api_key: &server_state.fal.api_key,
};
let cost = args.calculate_cost_in_cents();
info!("Charging wallet: {}", cost);
attempt_wallet_deduction_else_common_web_error(
user_token,
Some(apriori_job_token.as_str()),
cost,
&mut mysql_connection,
).await?;
fal_result = enqueue_kling_v2p6_pro_image_to_video_webhook(args)
.await
@@ -196,6 +211,17 @@ pub async fn kling_2p6_pro_multi_function_video_gen_handler(
webhook_url: &server_state.fal.webhook_url,
api_key: &server_state.fal.api_key,
};
let cost = args.calculate_cost_in_cents();
info!("Charging wallet...");
attempt_wallet_deduction_else_common_web_error(
user_token,
Some(apriori_job_token.as_str()),
cost,
&mut mysql_connection,
).await?;
fal_result = enqueue_kling_v2p6_pro_text_to_video_webhook(args)
.await
@@ -279,7 +305,8 @@ pub async fn kling_2p6_pro_multi_function_video_gen_handler(
}
}
let db_result = insert_generic_inference_job_for_fal_queue(InsertGenericInferenceForFalArgs {
let db_result = insert_generic_inference_job_for_fal_queue_with_apriori_job_token(InsertGenericInferenceForFalWithAprioriJobTokenArgs {
apriori_job_token: &apriori_job_token,
uuid_idempotency_token: &request.uuid_idempotency_token,
maybe_external_third_party_id: &external_job_id,
fal_category: FalCategory::VideoGeneration,
@@ -2,6 +2,7 @@ use std::collections::{HashMap, HashSet};
use std::iter::FromIterator;
use std::sync::Arc;
use crate::billing::wallets::attempt_wallet_deduction::attempt_wallet_deduction_else_common_web_error;
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::generate::common::payments_error_test::payments_error_test;
@@ -19,6 +20,7 @@ use enums::common::generation_provider::GenerationProvider;
use enums::common::model_type::ModelType;
use enums::common::visibility::Visibility;
use fal_client::creds::open_ai_api_key::OpenAiApiKey;
use fal_client::requests::traits::fal_request_cost_calculator_trait::FalRequestCostCalculator;
use fal_client::requests::webhook::video::image::enqueue_sora_2_image_to_video_webhook::{enqueue_sora_2_image_to_video_webhook, EnqueueSora2ImageToVideoArgs, EnqueueSora2ImageToVideoAspectRatio, EnqueueSora2ImageToVideoDurationSeconds, EnqueueSora2ImageToVideoResolution};
use fal_client::requests::webhook::video::text::enqueue_sora_2_text_to_video_webhook::{enqueue_sora_2_text_to_video_webhook, EnqueueSora2TextToVideoArgs, EnqueueSora2TextToVideoAspectRatio, EnqueueSora2TextToVideoDurationSeconds, EnqueueSora2TextToVideoResolution};
use http_server_common::request::get_request_ip::get_request_ip;
@@ -26,6 +28,7 @@ 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::generic_inference::fal::insert_generic_inference_job_for_fal_queue_with_apriori_job_token::{insert_generic_inference_job_for_fal_queue_with_apriori_job_token, InsertGenericInferenceForFalWithAprioriJobTokenArgs};
use mysql_queries::queries::idepotency_tokens::insert_idempotency_token::insert_idempotency_token;
use mysql_queries::queries::media_files::get::batch_get_media_files_by_tokens::{batch_get_media_files_by_tokens, batch_get_media_files_by_tokens_with_connection};
use mysql_queries::queries::prompt_context_items::insert_batch_prompt_context_items::{insert_batch_prompt_context_items, InsertBatchArgs, PromptContextItem};
@@ -33,6 +36,7 @@ use mysql_queries::queries::prompts::insert_prompt::{insert_prompt, InsertPrompt
use server_environment::ServerEnvironment;
use sqlx::pool::PoolConnection;
use sqlx::{Acquire, MySql};
use tokens::tokens::generic_inference_jobs::InferenceJobToken;
use tokens::tokens::media_files::MediaFileToken;
use utoipa::ToSchema;
@@ -73,15 +77,12 @@ pub async fn sora_2_multi_function_video_gen_handler(
.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(CommonWebError::NotAuthorized);
// }
//};
let user_token = match maybe_user_session.as_ref() {
Some(session) => &session.user_token,
None => {
return Err(CommonWebError::NotAuthorized);
}
};
if let Err(reason) = validate_idempotency_token_format(&request.uuid_idempotency_token) {
return Err(CommonWebError::BadInputWithSimpleMessage(reason));
@@ -143,6 +144,8 @@ pub async fn sora_2_multi_function_video_gen_handler(
info!("Fal webhook URL: {}", server_state.fal.webhook_url);
let apriori_job_token = InferenceJobToken::generate();
let fal_result;
if let Some(image_url) = maybe_image_url {
@@ -178,6 +181,17 @@ pub async fn sora_2_multi_function_video_gen_handler(
api_key: &server_state.fal.api_key,
};
let cost = args.calculate_cost_in_cents();
info!("Charging wallet: {}", cost);
attempt_wallet_deduction_else_common_web_error(
user_token,
Some(apriori_job_token.as_str()),
cost,
&mut mysql_connection,
).await?;
fal_result = enqueue_sora_2_image_to_video_webhook(args)
.await
.map_err(|err| {
@@ -217,6 +231,17 @@ pub async fn sora_2_multi_function_video_gen_handler(
api_key: &server_state.fal.api_key,
};
let cost = args.calculate_cost_in_cents();
info!("Charging wallet: {}", cost);
attempt_wallet_deduction_else_common_web_error(
user_token,
Some(apriori_job_token.as_str()),
cost,
&mut mysql_connection,
).await?;
fal_result = enqueue_sora_2_text_to_video_webhook(args)
.await
.map_err(|err| {
@@ -299,7 +324,8 @@ pub async fn sora_2_multi_function_video_gen_handler(
}
}
let db_result = insert_generic_inference_job_for_fal_queue(InsertGenericInferenceForFalArgs {
let db_result = insert_generic_inference_job_for_fal_queue_with_apriori_job_token(InsertGenericInferenceForFalWithAprioriJobTokenArgs {
apriori_job_token: &apriori_job_token,
uuid_idempotency_token: &request.uuid_idempotency_token,
maybe_external_third_party_id: &external_job_id,
fal_category: FalCategory::VideoGeneration,
@@ -2,6 +2,7 @@ use std::collections::{HashMap, HashSet};
use std::iter::FromIterator;
use std::sync::Arc;
use crate::billing::wallets::attempt_wallet_deduction::attempt_wallet_deduction_else_common_web_error;
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::generate::common::payments_error_test::payments_error_test;
@@ -11,6 +12,7 @@ use crate::state::server_state::ServerState;
use crate::util::lookup::lookup_image_urls_as_map::lookup_image_urls_as_map;
use actix_web::web::Json;
use actix_web::{web, HttpRequest};
use artcraft_api_defs::generate::video::multi_function::sora_2_pro_multi_function_video_gen::{Sora2ProMultiFunctionVideoGenAspectRatio, Sora2ProMultiFunctionVideoGenDuration, Sora2ProMultiFunctionVideoGenRequest, Sora2ProMultiFunctionVideoGenResolution, Sora2ProMultiFunctionVideoGenResponse};
use bucket_paths::legacy::typified_paths::public::media_files::bucket_file_path::MediaFileBucketPath;
use enums::by_table::prompt_context_items::prompt_context_semantic_type::PromptContextSemanticType;
use enums::by_table::prompts::prompt_type::PromptType;
@@ -18,11 +20,15 @@ use enums::common::generation_provider::GenerationProvider;
use enums::common::model_type::ModelType;
use enums::common::visibility::Visibility;
use fal_client::creds::open_ai_api_key::OpenAiApiKey;
use fal_client::requests::traits::fal_request_cost_calculator_trait::FalRequestCostCalculator;
use fal_client::requests::webhook::video::image::enqueue_sora_2_pro_image_to_video_webhook::{enqueue_sora_2_pro_image_to_video_webhook, EnqueueSora2ProImageToVideoArgs, EnqueueSora2ProImageToVideoAspectRatio, EnqueueSora2ProImageToVideoDurationSeconds, EnqueueSora2ProImageToVideoResolution};
use fal_client::requests::webhook::video::text::enqueue_sora_2_pro_text_to_video_webhook::{enqueue_sora_2_pro_text_to_video_webhook, EnqueueSora2ProTextToVideoArgs, EnqueueSora2ProTextToVideoAspectRatio, EnqueueSora2ProTextToVideoDurationSeconds, EnqueueSora2ProTextToVideoResolution};
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::generic_inference::fal::insert_generic_inference_job_for_fal_queue_with_apriori_job_token::{insert_generic_inference_job_for_fal_queue_with_apriori_job_token, InsertGenericInferenceForFalWithAprioriJobTokenArgs};
use mysql_queries::queries::idepotency_tokens::insert_idempotency_token::insert_idempotency_token;
use mysql_queries::queries::media_files::get::batch_get_media_files_by_tokens::{batch_get_media_files_by_tokens, batch_get_media_files_by_tokens_with_connection};
use mysql_queries::queries::prompt_context_items::insert_batch_prompt_context_items::{insert_batch_prompt_context_items, InsertBatchArgs, PromptContextItem};
@@ -30,11 +36,9 @@ use mysql_queries::queries::prompts::insert_prompt::{insert_prompt, InsertPrompt
use server_environment::ServerEnvironment;
use sqlx::pool::PoolConnection;
use sqlx::{Acquire, MySql};
use tokens::tokens::generic_inference_jobs::InferenceJobToken;
use tokens::tokens::media_files::MediaFileToken;
use utoipa::ToSchema;
use artcraft_api_defs::generate::video::multi_function::sora_2_pro_multi_function_video_gen::{Sora2ProMultiFunctionVideoGenAspectRatio, Sora2ProMultiFunctionVideoGenDuration, Sora2ProMultiFunctionVideoGenRequest, Sora2ProMultiFunctionVideoGenResolution, Sora2ProMultiFunctionVideoGenResponse};
use fal_client::requests::webhook::video::image::enqueue_sora_2_pro_image_to_video_webhook::{enqueue_sora_2_pro_image_to_video_webhook, EnqueueSora2ProImageToVideoArgs, EnqueueSora2ProImageToVideoAspectRatio, EnqueueSora2ProImageToVideoDurationSeconds, EnqueueSora2ProImageToVideoResolution};
use fal_client::requests::webhook::video::text::enqueue_sora_2_pro_text_to_video_webhook::{enqueue_sora_2_pro_text_to_video_webhook, EnqueueSora2ProTextToVideoArgs, EnqueueSora2ProTextToVideoAspectRatio, EnqueueSora2ProTextToVideoDurationSeconds, EnqueueSora2ProTextToVideoResolution};
/// Sora 2 Pro Multi-Function (text and image to video)
#[utoipa::path(
@@ -73,15 +77,12 @@ pub async fn sora_2_pro_multi_function_video_gen_handler(
.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(CommonWebError::NotAuthorized);
// }
//};
let user_token = match maybe_user_session.as_ref() {
Some(session) => &session.user_token,
None => {
return Err(CommonWebError::NotAuthorized);
}
};
if let Err(reason) = validate_idempotency_token_format(&request.uuid_idempotency_token) {
return Err(CommonWebError::BadInputWithSimpleMessage(reason));
@@ -143,6 +144,8 @@ pub async fn sora_2_pro_multi_function_video_gen_handler(
info!("Fal webhook URL: {}", server_state.fal.webhook_url);
let apriori_job_token = InferenceJobToken::generate();
let fal_result;
if let Some(image_url) = maybe_image_url {
@@ -179,6 +182,17 @@ pub async fn sora_2_pro_multi_function_video_gen_handler(
api_key: &server_state.fal.api_key,
};
let cost = args.calculate_cost_in_cents();
info!("Charging wallet: {}", cost);
attempt_wallet_deduction_else_common_web_error(
user_token,
Some(apriori_job_token.as_str()),
cost,
&mut mysql_connection,
).await?;
fal_result = enqueue_sora_2_pro_image_to_video_webhook(args)
.await
.map_err(|err| {
@@ -219,6 +233,17 @@ pub async fn sora_2_pro_multi_function_video_gen_handler(
api_key: &server_state.fal.api_key,
};
let cost = args.calculate_cost_in_cents();
info!("Charging wallet: {}", cost);
attempt_wallet_deduction_else_common_web_error(
user_token,
Some(apriori_job_token.as_str()),
cost,
&mut mysql_connection,
).await?;
fal_result = enqueue_sora_2_pro_text_to_video_webhook(args)
.await
.map_err(|err| {
@@ -301,7 +326,8 @@ pub async fn sora_2_pro_multi_function_video_gen_handler(
}
}
let db_result = insert_generic_inference_job_for_fal_queue(InsertGenericInferenceForFalArgs {
let db_result = insert_generic_inference_job_for_fal_queue_with_apriori_job_token(InsertGenericInferenceForFalWithAprioriJobTokenArgs {
apriori_job_token: &apriori_job_token,
uuid_idempotency_token: &request.uuid_idempotency_token,
maybe_external_third_party_id: &external_job_id,
fal_category: FalCategory::VideoGeneration,
@@ -2,6 +2,7 @@ use std::collections::{HashMap, HashSet};
use std::iter::FromIterator;
use std::sync::Arc;
use crate::billing::wallets::attempt_wallet_deduction::attempt_wallet_deduction_else_common_web_error;
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::generate::common::payments_error_test::payments_error_test;
@@ -19,6 +20,7 @@ use enums::common::generation_provider::GenerationProvider;
use enums::common::model_type::ModelType;
use enums::common::visibility::Visibility;
use fal_client::creds::open_ai_api_key::OpenAiApiKey;
use fal_client::requests::traits::fal_request_cost_calculator_trait::FalRequestCostCalculator;
use fal_client::requests::webhook::video::image::enqueue_veo_3p1_fast_first_last_frame_image_to_video_webhook::{enqueue_veo_3p1_fast_first_last_frame_image_to_video_webhook, EnqueueVeo3p1FastFirstLastFrameImageToVideoArgs, EnqueueVeo3p1FastFirstLastFrameImageToVideoAspectRatio, EnqueueVeo3p1FastFirstLastFrameImageToVideoDurationSeconds, EnqueueVeo3p1FastFirstLastFrameImageToVideoResolution};
use fal_client::requests::webhook::video::image::enqueue_veo_3p1_fast_image_to_video_webhook::{enqueue_veo_3p1_fast_image_to_video_webhook, EnqueueVeo3p1FastImageToVideoArgs, EnqueueVeo3p1FastImageToVideoAspectRatio, EnqueueVeo3p1FastImageToVideoDurationSeconds, EnqueueVeo3p1FastImageToVideoResolution};
use fal_client::requests::webhook::video::text::enqueue_veo_3p1_fast_text_to_video_webhook::{enqueue_veo_3p1_fast_text_to_video_webhook, EnqueueVeo3p1FastTextToVideoArgs, EnqueueVeo3p1FastTextToVideoAspectRatio, EnqueueVeo3p1FastTextToVideoDurationSeconds, EnqueueVeo3p1FastTextToVideoResolution};
@@ -27,6 +29,7 @@ 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::generic_inference::fal::insert_generic_inference_job_for_fal_queue_with_apriori_job_token::{insert_generic_inference_job_for_fal_queue_with_apriori_job_token, InsertGenericInferenceForFalWithAprioriJobTokenArgs};
use mysql_queries::queries::idepotency_tokens::insert_idempotency_token::insert_idempotency_token;
use mysql_queries::queries::media_files::get::batch_get_media_files_by_tokens::{batch_get_media_files_by_tokens, batch_get_media_files_by_tokens_with_connection};
use mysql_queries::queries::prompt_context_items::insert_batch_prompt_context_items::{insert_batch_prompt_context_items, InsertBatchArgs, PromptContextItem};
@@ -34,6 +37,7 @@ use mysql_queries::queries::prompts::insert_prompt::{insert_prompt, InsertPrompt
use server_environment::ServerEnvironment;
use sqlx::pool::PoolConnection;
use sqlx::{Acquire, MySql};
use tokens::tokens::generic_inference_jobs::InferenceJobToken;
use tokens::tokens::media_files::MediaFileToken;
use utoipa::ToSchema;
@@ -74,15 +78,12 @@ pub async fn veo_3p1_fast_multi_function_video_gen_handler(
.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(CommonWebError::NotAuthorized);
// }
//};
let user_token = match maybe_user_session.as_ref() {
Some(session) => &session.user_token,
None => {
return Err(CommonWebError::NotAuthorized);
}
};
if let Err(reason) = validate_idempotency_token_format(&request.uuid_idempotency_token) {
return Err(CommonWebError::BadInputWithSimpleMessage(reason));
@@ -144,6 +145,8 @@ pub async fn veo_3p1_fast_multi_function_video_gen_handler(
info!("Fal webhook URL: {}", server_state.fal.webhook_url);
let apriori_job_token = InferenceJobToken::generate();
let fal_result;
let generate_audio = request.generate_audio.unwrap_or(true);
@@ -185,6 +188,17 @@ pub async fn veo_3p1_fast_multi_function_video_gen_handler(
api_key: &server_state.fal.api_key,
};
let cost = args.calculate_cost_in_cents();
info!("Charging wallet: {}", cost);
attempt_wallet_deduction_else_common_web_error(
user_token,
Some(apriori_job_token.as_str()),
cost,
&mut mysql_connection,
).await?;
fal_result = enqueue_veo_3p1_fast_first_last_frame_image_to_video_webhook(args)
.await
.map_err(|err| {
@@ -226,6 +240,17 @@ pub async fn veo_3p1_fast_multi_function_video_gen_handler(
api_key: &server_state.fal.api_key,
};
let cost = args.calculate_cost_in_cents();
info!("Charging wallet: {}", cost);
attempt_wallet_deduction_else_common_web_error(
user_token,
Some(apriori_job_token.as_str()),
cost,
&mut mysql_connection,
).await?;
fal_result = enqueue_veo_3p1_fast_image_to_video_webhook(args)
.await
.map_err(|err| {
@@ -270,6 +295,17 @@ pub async fn veo_3p1_fast_multi_function_video_gen_handler(
webhook_url: &server_state.fal.webhook_url,
api_key: &server_state.fal.api_key,
};
let cost = args.calculate_cost_in_cents();
info!("Charging wallet: {}", cost);
attempt_wallet_deduction_else_common_web_error(
user_token,
Some(apriori_job_token.as_str()),
cost,
&mut mysql_connection,
).await?;
fal_result = enqueue_veo_3p1_fast_text_to_video_webhook(args)
.await
@@ -353,7 +389,8 @@ pub async fn veo_3p1_fast_multi_function_video_gen_handler(
}
}
let db_result = insert_generic_inference_job_for_fal_queue(InsertGenericInferenceForFalArgs {
let db_result = insert_generic_inference_job_for_fal_queue_with_apriori_job_token(InsertGenericInferenceForFalWithAprioriJobTokenArgs {
apriori_job_token: &apriori_job_token,
uuid_idempotency_token: &request.uuid_idempotency_token,
maybe_external_third_party_id: &external_job_id,
fal_category: FalCategory::VideoGeneration,
@@ -2,6 +2,7 @@ use std::collections::{HashMap, HashSet};
use std::iter::FromIterator;
use std::sync::Arc;
use crate::billing::wallets::attempt_wallet_deduction::attempt_wallet_deduction_else_common_web_error;
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::generate::common::payments_error_test::payments_error_test;
@@ -19,6 +20,7 @@ use enums::common::generation_provider::GenerationProvider;
use enums::common::model_type::ModelType;
use enums::common::visibility::Visibility;
use fal_client::creds::open_ai_api_key::OpenAiApiKey;
use fal_client::requests::traits::fal_request_cost_calculator_trait::FalRequestCostCalculator;
use fal_client::requests::webhook::video::image::enqueue_veo_3p1_first_last_frame_image_to_video_webhook::{enqueue_veo_3p1_first_last_frame_image_to_video_webhook, EnqueueVeo3p1FirstLastFrameImageToVideoArgs, EnqueueVeo3p1FirstLastFrameImageToVideoAspectRatio, EnqueueVeo3p1FirstLastFrameImageToVideoDurationSeconds, EnqueueVeo3p1FirstLastFrameImageToVideoResolution};
use fal_client::requests::webhook::video::image::enqueue_veo_3p1_image_to_video_webhook::{enqueue_veo_3p1_image_to_video_webhook, EnqueueVeo3p1ImageToVideoArgs, EnqueueVeo3p1ImageToVideoAspectRatio, EnqueueVeo3p1ImageToVideoDurationSeconds, EnqueueVeo3p1ImageToVideoResolution};
use fal_client::requests::webhook::video::text::enqueue_veo_3p1_text_to_video_webhook::{enqueue_veo_3p1_text_to_video_webhook, EnqueueVeo3p1TextToVideoArgs, EnqueueVeo3p1TextToVideoAspectRatio, EnqueueVeo3p1TextToVideoDurationSeconds, EnqueueVeo3p1TextToVideoResolution};
@@ -27,6 +29,7 @@ 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::generic_inference::fal::insert_generic_inference_job_for_fal_queue_with_apriori_job_token::{insert_generic_inference_job_for_fal_queue_with_apriori_job_token, InsertGenericInferenceForFalWithAprioriJobTokenArgs};
use mysql_queries::queries::idepotency_tokens::insert_idempotency_token::insert_idempotency_token;
use mysql_queries::queries::media_files::get::batch_get_media_files_by_tokens::{batch_get_media_files_by_tokens, batch_get_media_files_by_tokens_with_connection};
use mysql_queries::queries::prompt_context_items::insert_batch_prompt_context_items::{insert_batch_prompt_context_items, InsertBatchArgs, PromptContextItem};
@@ -34,6 +37,7 @@ use mysql_queries::queries::prompts::insert_prompt::{insert_prompt, InsertPrompt
use server_environment::ServerEnvironment;
use sqlx::pool::PoolConnection;
use sqlx::{Acquire, MySql};
use tokens::tokens::generic_inference_jobs::InferenceJobToken;
use tokens::tokens::media_files::MediaFileToken;
use utoipa::ToSchema;
@@ -74,15 +78,12 @@ pub async fn veo_3p1_multi_function_video_gen_handler(
.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(CommonWebError::NotAuthorized);
// }
//};
let user_token = match maybe_user_session.as_ref() {
Some(session) => &session.user_token,
None => {
return Err(CommonWebError::NotAuthorized);
}
};
if let Err(reason) = validate_idempotency_token_format(&request.uuid_idempotency_token) {
return Err(CommonWebError::BadInputWithSimpleMessage(reason));
@@ -144,6 +145,8 @@ pub async fn veo_3p1_multi_function_video_gen_handler(
info!("Fal webhook URL: {}", server_state.fal.webhook_url);
let apriori_job_token = InferenceJobToken::generate();
let fal_result;
let generate_audio = request.generate_audio.unwrap_or(true);
@@ -184,6 +187,17 @@ pub async fn veo_3p1_multi_function_video_gen_handler(
webhook_url: &server_state.fal.webhook_url,
api_key: &server_state.fal.api_key,
};
let cost = args.calculate_cost_in_cents();
info!("Charging wallet: {}", cost);
attempt_wallet_deduction_else_common_web_error(
user_token,
Some(apriori_job_token.as_str()),
cost,
&mut mysql_connection,
).await?;
fal_result = enqueue_veo_3p1_first_last_frame_image_to_video_webhook(args)
.await
@@ -225,6 +239,17 @@ pub async fn veo_3p1_multi_function_video_gen_handler(
webhook_url: &server_state.fal.webhook_url,
api_key: &server_state.fal.api_key,
};
let cost = args.calculate_cost_in_cents();
info!("Charging wallet: {}", cost);
attempt_wallet_deduction_else_common_web_error(
user_token,
Some(apriori_job_token.as_str()),
cost,
&mut mysql_connection,
).await?;
fal_result = enqueue_veo_3p1_image_to_video_webhook(args)
.await
@@ -270,6 +295,17 @@ pub async fn veo_3p1_multi_function_video_gen_handler(
webhook_url: &server_state.fal.webhook_url,
api_key: &server_state.fal.api_key,
};
let cost = args.calculate_cost_in_cents();
info!("Charging wallet: {}", cost);
attempt_wallet_deduction_else_common_web_error(
user_token,
Some(apriori_job_token.as_str()),
cost,
&mut mysql_connection,
).await?;
fal_result = enqueue_veo_3p1_text_to_video_webhook(args)
.await
@@ -353,7 +389,8 @@ pub async fn veo_3p1_multi_function_video_gen_handler(
}
}
let db_result = insert_generic_inference_job_for_fal_queue(InsertGenericInferenceForFalArgs {
let db_result = insert_generic_inference_job_for_fal_queue_with_apriori_job_token(InsertGenericInferenceForFalWithAprioriJobTokenArgs {
apriori_job_token: &apriori_job_token,
uuid_idempotency_token: &request.uuid_idempotency_token,
maybe_external_third_party_id: &external_job_id,
fal_category: FalCategory::VideoGeneration,
@@ -12,10 +12,16 @@ pub struct ImageToVideoRequest {
#[serde(skip_serializing_if = "Option::is_none")]
pub end_image_url: Option<String>,
/// The resolution of the generated video frame
/// The resolution of the generated video frame.
/// Defaults to "720p"
#[serde(skip_serializing_if = "Option::is_none")]
pub resolution: Option<String>,
/// Aspect ratio.
/// Defaults to "auto"
#[serde(skip_serializing_if = "Option::is_none")]
pub aspect_ratio: Option<String>,
/// The duration of the generated video in seconds
#[serde(skip_serializing_if = "Option::is_none")]
pub duration: Option<String>,