mirror of
https://github.com/storytold/artcraft.git
synced 2026-11-03 13:02:37 -05:00
Compare commits
24
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
05ec3d5b69 | ||
|
|
c30135b228 | ||
|
|
7ad7117934 | ||
|
|
faf2c97825 | ||
|
|
a3fb9d23fd | ||
|
|
b6e899b9fa | ||
|
|
1b26ae0ce4 | ||
|
|
12d46666c3 | ||
|
|
ca142a252a | ||
|
|
efe56050c4 | ||
|
|
02cb7558e4 | ||
|
|
4631971865 | ||
|
|
2af0227330 | ||
|
|
6b612f4ba6 | ||
|
|
994f848c5c | ||
|
|
ed4ad1cfdd | ||
|
|
628fc05dba | ||
|
|
bb56d64c05 | ||
|
|
36e048e5b4 | ||
|
|
95ef7d6d1f | ||
|
|
fb34a80ac7 | ||
|
|
85dc8a7492 | ||
|
|
f832237f13 | ||
|
|
3ac322ca97 |
+18
-2
@@ -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;
|
||||
-105
@@ -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(())
|
||||
}
|
||||
}
|
||||
-115
@@ -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(())
|
||||
}
|
||||
}
|
||||
+11
@@ -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> {
|
||||
|
||||
+11
@@ -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> {
|
||||
|
||||
+33
-18
@@ -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(())
|
||||
}
|
||||
+34
-18
@@ -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(())
|
||||
}
|
||||
+33
-18
@@ -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(())
|
||||
}
|
||||
+17
@@ -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",
|
||||
|
||||
+19
-2
@@ -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]
|
||||
|
||||
+26
@@ -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> {
|
||||
|
||||
+31
@@ -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> {
|
||||
|
||||
+220
@@ -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(())
|
||||
}
|
||||
}
|
||||
+205
@@ -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(())
|
||||
}
|
||||
}
|
||||
+19
-1
@@ -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> {
|
||||
|
||||
+28
@@ -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> {
|
||||
|
||||
+21
-3
@@ -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;
|
||||
|
||||
+29
-3
@@ -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;
|
||||
+31
-3
@@ -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]
|
||||
+33
-1
@@ -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> {
|
||||
|
||||
+27
@@ -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> {
|
||||
|
||||
+26
@@ -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> {
|
||||
|
||||
+26
@@ -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;
|
||||
+11
@@ -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> {
|
||||
|
||||
+17
@@ -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> {
|
||||
|
||||
+22
-1
@@ -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]
|
||||
|
||||
+31
-2
@@ -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]
|
||||
|
||||
+16
@@ -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> {
|
||||
|
||||
+26
@@ -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> {
|
||||
|
||||
+27
@@ -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> {
|
||||
|
||||
+15
-120
@@ -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
|
||||
}
|
||||
|
||||
+170
@@ -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
-2
@@ -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,
|
||||
|
||||
+4
-2
@@ -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;
|
||||
-24
@@ -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(())
|
||||
}
|
||||
|
||||
+19
-19
@@ -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);
|
||||
|
||||
|
||||
+1
-1
@@ -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,
|
||||
|
||||
+35
-23
@@ -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,
|
||||
|
||||
+35
-23
@@ -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,
|
||||
|
||||
+38
-23
@@ -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,
|
||||
|
||||
+43
-15
@@ -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,
|
||||
|
||||
+29
-14
@@ -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,
|
||||
|
||||
+29
-14
@@ -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,
|
||||
|
||||
+26
-11
@@ -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,
|
||||
|
||||
+26
-11
@@ -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,
|
||||
|
||||
+36
-10
@@ -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,
|
||||
|
||||
+38
-11
@@ -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,
|
||||
|
||||
+36
-10
@@ -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,
|
||||
|
||||
+39
-13
@@ -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,
|
||||
|
||||
+47
-10
@@ -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,
|
||||
|
||||
+47
-10
@@ -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,
|
||||
|
||||
+7
-1
@@ -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>,
|
||||
|
||||
Reference in New Issue
Block a user