mirror of
https://github.com/storytold/artcraft.git
synced 2026-10-21 01:12:40 -04:00
Compare commits
50
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
98b0503d97 | ||
|
|
5d3c168b5b | ||
|
|
e2a4e426dd | ||
|
|
fbad200dee | ||
|
|
9e0a5558a8 | ||
|
|
6c0b32aa44 | ||
|
|
6f9b637da0 | ||
|
|
40dda6e5cd | ||
|
|
06fca6b54f | ||
|
|
7a04b131f7 | ||
|
|
427269feec | ||
|
|
2e2c433ffe | ||
|
|
c8228c5073 | ||
|
|
f89ce374cd | ||
|
|
affcbac10f | ||
|
|
cbf91709dc | ||
|
|
eea31d3f94 | ||
|
|
f8f3434d70 | ||
|
|
c6f3fd13b8 | ||
|
|
fd8f9d037f | ||
|
|
9fa4080851 | ||
|
|
73b16274f8 | ||
|
|
835c9ef6b0 | ||
|
|
11bf5949fe | ||
|
|
5415109a35 | ||
|
|
cb8c3b426f | ||
|
|
25ac4213af | ||
|
|
2a316f6cc7 | ||
|
|
2a1ae24917 | ||
|
|
68ff2b484b | ||
|
|
19258c257c | ||
|
|
4cd3bd8095 | ||
|
|
e7dc0abb36 | ||
|
|
a36fe72832 | ||
|
|
4eeb915bef | ||
|
|
19079ab7ec | ||
|
|
9ffcfe83c2 | ||
|
|
3404935720 | ||
|
|
b490b92833 | ||
|
|
f151837c2b | ||
|
|
86a6d16441 | ||
|
|
b9397fb809 | ||
|
|
5688d8cef7 | ||
|
|
6815040cb1 | ||
|
|
754a4da019 | ||
|
|
3a600fcb9f | ||
|
|
510589b758 | ||
|
|
7ee5a6aa9a | ||
|
|
f5cd65be9c | ||
|
|
fba1a65e95 |
@@ -200,10 +200,10 @@ jobs:
|
||||
|
||||
# Use npx to ensure we use the local nx version
|
||||
echo "Building with npx nx..."
|
||||
npx nx run editor3d:build
|
||||
npx nx run artcraft:build
|
||||
|
||||
echo "Checking build output..."
|
||||
ls -la apps/editor3d/dist/ | head -10
|
||||
ls -la apps/artcraft/dist/ | head -10
|
||||
|
||||
# Update Tauri config to skip the build (since we already built)
|
||||
cd ${{ github.workspace }}/crates/desktop/artcraft
|
||||
|
||||
@@ -75,10 +75,10 @@ jobs:
|
||||
|
||||
# Use npx to ensure we use the local nx version
|
||||
echo "Building with npx nx..."
|
||||
npx nx run editor3d:build
|
||||
npx nx run artcraft:build
|
||||
|
||||
echo "Checking build output..."
|
||||
dir apps/editor3d/dist/ | Select-Object -First 10
|
||||
dir apps/artcraft/dist/ | Select-Object -First 10
|
||||
|
||||
# Update Tauri config to skip the build (since we already built)
|
||||
cd ${{ github.workspace }}/crates/desktop/artcraft
|
||||
@@ -104,4 +104,4 @@ jobs:
|
||||
releaseBody: "Windows build - See the assets to download this version and install."
|
||||
releaseDraft: true
|
||||
prerelease: false
|
||||
args: ${{ matrix.args }}
|
||||
args: ${{ matrix.args }}
|
||||
|
||||
+1
-1
@@ -46,7 +46,7 @@ crates/lib/sqlite_queries/target/
|
||||
node_modules/
|
||||
|
||||
# Frontend build outputs
|
||||
frontend/apps/editor3d/bundle-report.html
|
||||
frontend/apps/artcraft/bundle-report.html
|
||||
|
||||
# Vim noise
|
||||
*.swo
|
||||
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"db_name": "SQLite",
|
||||
"query": "\n UPDATE tasks\n SET task_status = \"cancelled_by_user\"\n WHERE task_status NOT IN (\n \"complete_success\",\n \"complete_failure\",\n \"dead\",\n \"cancelled_by_user\",\n \"cancelled_by_provider\",\n \"cancelled_by_us\"\n )\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Right": 0
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "30272128d83543d9fbfca930b7cdd4af30adfb4d90f2de044463a111c6340495"
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"db_name": "SQLite",
|
||||
"query": "\n UPDATE tasks\n SET is_dismissed_by_user = 1\n WHERE is_dismissed_by_user != 1\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Right": 0
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "f83018ec5493982659a56576296914f937bb4e75db3a522eb4bda486d5caa402"
|
||||
}
|
||||
Generated
+47
@@ -698,6 +698,7 @@ dependencies = [
|
||||
"url_utils",
|
||||
"web_base64",
|
||||
"workspace-build-acceleration",
|
||||
"world_labs_client",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -5151,6 +5152,14 @@ dependencies = [
|
||||
"tokio-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "http_headers"
|
||||
version = "0.0.1"
|
||||
dependencies = [
|
||||
"errors",
|
||||
"workspace-build-acceleration",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "http_server_common"
|
||||
version = "0.0.1"
|
||||
@@ -14165,6 +14174,44 @@ dependencies = [
|
||||
"zstd-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "world_labs_client"
|
||||
version = "0.0.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"base64 0.22.1",
|
||||
"bstr",
|
||||
"byteorder",
|
||||
"chrono",
|
||||
"cloudflare_errors",
|
||||
"cloudflare_mitigation",
|
||||
"cookie_store 0.0.1",
|
||||
"env",
|
||||
"env_logger",
|
||||
"errors",
|
||||
"filesys",
|
||||
"futures 0.3.31",
|
||||
"http_headers",
|
||||
"indexmap 2.9.0",
|
||||
"jwt_light",
|
||||
"log",
|
||||
"log4rs",
|
||||
"once_cell",
|
||||
"regex",
|
||||
"scraper 0.24.0",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sha2 0.11.0-rc.2",
|
||||
"test_data",
|
||||
"testing",
|
||||
"tokio",
|
||||
"url",
|
||||
"uuid 1.18.1",
|
||||
"workspace-build-acceleration",
|
||||
"wreq",
|
||||
"wreq-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wreq"
|
||||
version = "6.0.0-rc.20"
|
||||
|
||||
@@ -18,6 +18,7 @@ members = [
|
||||
"crates/api_clients/openart_client",
|
||||
"crates/api_clients/runwayml_client",
|
||||
"crates/api_clients/thumbnail_generator",
|
||||
"crates/api_clients/world_labs_client",
|
||||
|
||||
# CLI tools for operations, etc.
|
||||
"crates/cli/devenv/dev-database-seed",
|
||||
@@ -67,6 +68,7 @@ members = [
|
||||
"crates/lib/files/media",
|
||||
"crates/lib/files/mimetypes",
|
||||
"crates/lib/files/videos",
|
||||
"crates/lib/http_headers",
|
||||
"crates/lib/idempotency",
|
||||
"crates/lib/image_algorithms",
|
||||
"crates/lib/jobs_common",
|
||||
@@ -199,6 +201,7 @@ fal_client = { path = "crates/api_clients/fal_client" }
|
||||
file_to_base64_url = { path = "crates/lib/files/file_to_base64_url" }
|
||||
filesys = { path = "crates/lib/files/filesys" }
|
||||
grok_client = { path = "crates/api_clients/grok_client" }
|
||||
http_headers = { path = "crates/lib/http_headers" }
|
||||
idempotency = { path = "crates/lib/idempotency" }
|
||||
images = { path = "crates/lib/files/images" }
|
||||
jwt_light = { path = "crates/lib/jwt_light" }
|
||||
@@ -217,6 +220,7 @@ testing = { path = "crates/lib/testing" }
|
||||
tokens = { path = "crates/schema/public/tokens" }
|
||||
url_utils = { path = "crates/lib/url_utils" }
|
||||
web_base64 = { path = "crates/lib/data/web_base64" }
|
||||
world_labs_client = { path = "crates/api_clients/world_labs_client" }
|
||||
|
||||
# External libs
|
||||
# Centrally pinned versions so that we can uniformly upgrade/downgrade versions across the workspace
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
pub mod remove_image_background;
|
||||
@@ -1,6 +1,6 @@
|
||||
pub mod flux_pro_kontext_max_edit_image;
|
||||
pub mod gpt_image_1_edit_image;
|
||||
pub mod gemini_25_flash_edit_image;
|
||||
pub mod gpt_image_1_edit_image;
|
||||
pub mod qwen_edit_image;
|
||||
pub mod seededit_3_edit_image;
|
||||
|
||||
|
||||
@@ -1,9 +1,5 @@
|
||||
pub mod bg_removal;
|
||||
pub mod edit;
|
||||
pub mod generate_flux_1_dev_text_to_image;
|
||||
pub mod generate_flux_1_schnell_text_to_image;
|
||||
pub mod generate_flux_pro_11_text_to_image;
|
||||
pub mod generate_flux_pro_11_ultra_text_to_image;
|
||||
pub mod generate_gpt_image_1_text_to_image;
|
||||
pub mod inpaint;
|
||||
pub mod multi_function;
|
||||
pub mod remove_image_background;
|
||||
pub mod text;
|
||||
+69
@@ -0,0 +1,69 @@
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use tokens::tokens::generic_inference_jobs::InferenceJobToken;
|
||||
use tokens::tokens::media_files::MediaFileToken;
|
||||
use utoipa::ToSchema;
|
||||
|
||||
pub const BYTEDANCE_SEEDREAM_V4_MULTI_FUNCTION_IMAGE_GEN_PATH: &str = "/v1/generate/image/multi_function/bytedance_seedream_4";
|
||||
|
||||
#[derive(Serialize, Deserialize, ToSchema)]
|
||||
pub struct BytedanceSeedreamV4MultiFunctionImageGenRequest {
|
||||
/// Idempotency token to prevent duplicate requests.
|
||||
pub uuid_idempotency_token: String,
|
||||
|
||||
/// Text prompt to generate the image from.
|
||||
pub prompt: Option<String>,
|
||||
|
||||
/// Image media tokens to include in the editing context.
|
||||
/// If present, we're doing image editing (image-to-image / image-editing)
|
||||
/// If absent, we're doing image generation (text-to-image)
|
||||
pub image_media_tokens: Option<Vec<MediaFileToken>>,
|
||||
|
||||
/// Number of images to generate. Default is one.
|
||||
pub num_images: Option<BytedanceSeedreamV4MultiFunctionImageGenNumImages>,
|
||||
|
||||
pub max_images: Option<BytedanceSeedreamV4MultiFunctionImageGenMaxImages>,
|
||||
|
||||
pub image_size: Option<BytedanceSeedreamV4MultiFunctionImageGenImageSize>,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize, ToSchema)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum BytedanceSeedreamV4MultiFunctionImageGenNumImages {
|
||||
One, // Default
|
||||
Two,
|
||||
Three,
|
||||
Four,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize, ToSchema)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum BytedanceSeedreamV4MultiFunctionImageGenMaxImages {
|
||||
One, // Default
|
||||
Two,
|
||||
Three,
|
||||
Four,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize, ToSchema)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum BytedanceSeedreamV4MultiFunctionImageGenImageSize {
|
||||
// Square
|
||||
Square,
|
||||
SquareHd,
|
||||
// Tall
|
||||
PortraitFourThree,
|
||||
PortraitSixteenNine,
|
||||
// Wide
|
||||
LandscapeFourThree,
|
||||
LandscapeSixteenNine,
|
||||
// Auto
|
||||
Auto,
|
||||
Auto2k,
|
||||
Auto4k,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, ToSchema)]
|
||||
pub struct BytedanceSeedreamV4MultiFunctionImageGenResponse {
|
||||
pub success: bool,
|
||||
pub inference_job_token: InferenceJobToken,
|
||||
}
|
||||
+68
@@ -0,0 +1,68 @@
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use tokens::tokens::generic_inference_jobs::InferenceJobToken;
|
||||
use tokens::tokens::media_files::MediaFileToken;
|
||||
use utoipa::ToSchema;
|
||||
|
||||
pub const BYTEDANCE_SEEDREAM_V4P5_MULTI_FUNCTION_IMAGE_GEN_PATH: &str = "/v1/generate/image/multi_function/bytedance_seedream_4p5";
|
||||
|
||||
#[derive(Serialize, Deserialize, ToSchema)]
|
||||
pub struct BytedanceSeedreamV4p5MultiFunctionImageGenRequest {
|
||||
/// Idempotency token to prevent duplicate requests.
|
||||
pub uuid_idempotency_token: String,
|
||||
|
||||
/// Text prompt to generate the image from.
|
||||
pub prompt: Option<String>,
|
||||
|
||||
/// Image media tokens to include in the editing context.
|
||||
/// If present, we're doing image editing (image-to-image / image-editing)
|
||||
/// If absent, we're doing image generation (text-to-image)
|
||||
pub image_media_tokens: Option<Vec<MediaFileToken>>,
|
||||
|
||||
/// Number of images to generate. Default is one.
|
||||
pub num_images: Option<BytedanceSeedreamV4p5MultiFunctionImageGenNumImages>,
|
||||
|
||||
pub max_images: Option<BytedanceSeedreamV4p5MultiFunctionImageGenMaxImages>,
|
||||
|
||||
pub image_size: Option<BytedanceSeedreamV4p5MultiFunctionImageGenImageSize>,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize, ToSchema)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum BytedanceSeedreamV4p5MultiFunctionImageGenNumImages {
|
||||
One, // Default
|
||||
Two,
|
||||
Three,
|
||||
Four,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize, ToSchema)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum BytedanceSeedreamV4p5MultiFunctionImageGenMaxImages {
|
||||
One, // Default
|
||||
Two,
|
||||
Three,
|
||||
Four,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize, ToSchema)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum BytedanceSeedreamV4p5MultiFunctionImageGenImageSize {
|
||||
// Square
|
||||
Square,
|
||||
SquareHd,
|
||||
// Tall
|
||||
PortraitFourThree,
|
||||
PortraitSixteenNine,
|
||||
// Wide
|
||||
LandscapeFourThree,
|
||||
LandscapeSixteenNine,
|
||||
// Auto
|
||||
Auto2k,
|
||||
Auto4k,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, ToSchema)]
|
||||
pub struct BytedanceSeedreamV4p5MultiFunctionImageGenResponse {
|
||||
pub success: bool,
|
||||
pub inference_job_token: InferenceJobToken,
|
||||
}
|
||||
+95
@@ -0,0 +1,95 @@
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use tokens::tokens::generic_inference_jobs::InferenceJobToken;
|
||||
use tokens::tokens::media_files::MediaFileToken;
|
||||
use utoipa::ToSchema;
|
||||
|
||||
pub const GPT_IMAGE_1P5_MULTI_FUNCTION_IMAGE_GEN_PATH: &str = "/v1/generate/image/multi_function/gpt_image_1p5";
|
||||
|
||||
#[derive(Serialize, Deserialize, ToSchema)]
|
||||
pub struct GptImage1p5MultiFunctionImageGenRequest {
|
||||
/// Idempotency token to prevent duplicate requests.
|
||||
pub uuid_idempotency_token: String,
|
||||
|
||||
/// Text prompt to generate the image from.
|
||||
pub prompt: Option<String>,
|
||||
|
||||
/// Image media tokens to include in the editing context (**OR INPAINTING CONTEXT!**).
|
||||
/// If present, we're doing image editing (image-to-image / image-editing / image-inpainting)
|
||||
/// If absent, we're doing image generation (text-to-image)
|
||||
pub image_media_tokens: Option<Vec<MediaFileToken>>,
|
||||
|
||||
/// Only for image editing, which turns this into an inpainting task.
|
||||
/// Text to image should not set this.
|
||||
pub mask_image_token: Option<MediaFileToken>,
|
||||
|
||||
/// Number of images to generate. Default is one.
|
||||
pub num_images: Option<GptImage1p5MultiFunctionImageGenNumImages>,
|
||||
|
||||
pub image_size: Option<GptImage1p5MultiFunctionImageGenSize>,
|
||||
|
||||
pub background: Option<GptImage1p5MultiFunctionImageGenBackground>,
|
||||
|
||||
pub quality: Option<GptImage1p5MultiFunctionImageGenQuality>,
|
||||
|
||||
/// Only for image editing.
|
||||
pub input_fidelity: Option<GptImage1p5MultiFunctionImageGenInputFidelity>,
|
||||
|
||||
pub output_format: Option<GptImage1p5MultiFunctionImageGenOutputFormat>,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize, ToSchema)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum GptImage1p5MultiFunctionImageGenNumImages {
|
||||
One, // Default
|
||||
Two,
|
||||
Three,
|
||||
Four,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize, ToSchema)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum GptImage1p5MultiFunctionImageGenSize {
|
||||
/// 1024x1024
|
||||
Square,
|
||||
/// 1536x1024
|
||||
Wide,
|
||||
/// 1024x1536
|
||||
Tall,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize, ToSchema)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum GptImage1p5MultiFunctionImageGenBackground {
|
||||
Auto,
|
||||
Transparent,
|
||||
Opaque,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize, ToSchema)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum GptImage1p5MultiFunctionImageGenQuality {
|
||||
Low,
|
||||
Medium,
|
||||
High,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize, ToSchema)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum GptImage1p5MultiFunctionImageGenInputFidelity {
|
||||
Low,
|
||||
High,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize, ToSchema)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum GptImage1p5MultiFunctionImageGenOutputFormat {
|
||||
Jpeg,
|
||||
Png,
|
||||
Webp,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, ToSchema)]
|
||||
pub struct GptImage1p5MultiFunctionImageGenResponse {
|
||||
pub success: bool,
|
||||
pub inference_job_token: InferenceJobToken,
|
||||
}
|
||||
@@ -1 +1,5 @@
|
||||
pub mod bytedance_seedream_v4_multi_function_image_gen;
|
||||
pub mod bytedance_seedream_v4p5_multi_function_image_gen;
|
||||
pub mod gpt_image_1p5_multi_function_image_gen;
|
||||
pub mod nano_banana_multi_function_image_gen;
|
||||
pub mod nano_banana_pro_multi_function_image_gen;
|
||||
+62
@@ -0,0 +1,62 @@
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use tokens::tokens::generic_inference_jobs::InferenceJobToken;
|
||||
use tokens::tokens::media_files::MediaFileToken;
|
||||
use utoipa::ToSchema;
|
||||
|
||||
pub const NANO_BANANA_MULTI_FUNCTION_IMAGE_GEN_PATH: &str = "/v1/generate/image/multi_function/nano_banana";
|
||||
|
||||
#[derive(Serialize, Deserialize, ToSchema, Debug)]
|
||||
pub struct NanoBananaMultiFunctionImageGenRequest {
|
||||
/// Idempotency token to prevent duplicate requests.
|
||||
pub uuid_idempotency_token: String,
|
||||
|
||||
/// Text prompt to generate the image from.
|
||||
pub prompt: Option<String>,
|
||||
|
||||
/// Image media tokens to include in the editing context.
|
||||
/// If present, we're doing image editing (image-to-image / image-editing)
|
||||
/// If absent, we're doing image generation (text-to-image)
|
||||
pub image_media_tokens: Option<Vec<MediaFileToken>>,
|
||||
|
||||
/// Number of images to generate. Default is one.
|
||||
pub num_images: Option<NanoBananaMultiFunctionImageGenNumImages>,
|
||||
|
||||
/// Aspect ratio of the images to generate. Default is "1:1"
|
||||
pub aspect_ratio: Option<NanoBananaMultiFunctionImageGenAspectRatio>,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize, ToSchema)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum NanoBananaMultiFunctionImageGenNumImages {
|
||||
One, // Default
|
||||
Two,
|
||||
Three,
|
||||
Four,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize, ToSchema)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum NanoBananaMultiFunctionImageGenAspectRatio {
|
||||
// Auto (only for image editing)
|
||||
Auto, // Default for image editing.
|
||||
// Square
|
||||
OneByOne, // Default for text-to-image
|
||||
// Wide
|
||||
FiveByFour,
|
||||
FourByThree,
|
||||
ThreeByTwo,
|
||||
SixteenByNine,
|
||||
TwentyOneByNine,
|
||||
// Tall
|
||||
FourByFive,
|
||||
ThreeByFour,
|
||||
TwoByThree,
|
||||
NineBySixteen, // NB: No NineByTwentyOne ?
|
||||
}
|
||||
|
||||
|
||||
#[derive(Serialize, Deserialize, ToSchema)]
|
||||
pub struct NanoBananaMultiFunctionImageGenResponse {
|
||||
pub success: bool,
|
||||
pub inference_job_token: InferenceJobToken,
|
||||
}
|
||||
+4
-2
@@ -5,7 +5,7 @@ use utoipa::ToSchema;
|
||||
|
||||
pub const NANO_BANANA_PRO_MULTI_FUNCTION_IMAGE_GEN_PATH: &str = "/v1/generate/image/multi_function/nano_banana_pro";
|
||||
|
||||
#[derive(Serialize, Deserialize, ToSchema)]
|
||||
#[derive(Serialize, Deserialize, ToSchema, Debug)]
|
||||
pub struct NanoBananaProMultiFunctionImageGenRequest {
|
||||
/// Idempotency token to prevent duplicate requests.
|
||||
pub uuid_idempotency_token: String,
|
||||
@@ -48,8 +48,10 @@ pub enum NanoBananaProMultiFunctionImageGenImageResolution {
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize, ToSchema)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum NanoBananaProMultiFunctionImageGenAspectRatio {
|
||||
// Auto (only for image editing)
|
||||
Auto, // Default for image editing.
|
||||
// Square
|
||||
OneByOne, // Default
|
||||
OneByOne, // Default for text-to-image
|
||||
// Wide
|
||||
FiveByFour,
|
||||
FourByThree,
|
||||
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use tokens::tokens::generic_inference_jobs::InferenceJobToken;
|
||||
use tokens::tokens::media_files::MediaFileToken;
|
||||
use utoipa::ToSchema;
|
||||
|
||||
pub const GEMINI_25_FLASH_EDIT_IMAGE_PATH: &str = "/v1/generate/image/gemini_25_flash";
|
||||
|
||||
#[derive(Serialize, Deserialize, ToSchema)]
|
||||
pub struct Gemini25FlashTextToImageImageRequest {
|
||||
/// Idempotency token to prevent duplicate requests.
|
||||
pub uuid_idempotency_token: String,
|
||||
|
||||
/// Text prompt to generate the image from.
|
||||
pub prompt: Option<String>,
|
||||
|
||||
// TODO: OneOf for image_urls or image_media_tokens
|
||||
/// Image media tokens to include in the editing context.
|
||||
pub image_media_tokens: Option<Vec<MediaFileToken>>,
|
||||
|
||||
/// Number of images to generate. Default is one.
|
||||
pub num_images: Option<Gemini25FlashTextToImageImageNumImages>,
|
||||
|
||||
/// Quality of the image to generate. Default is High.
|
||||
pub image_quality: Option<Gemini25FlashTextToImageImageImageQuality>,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize, ToSchema)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum Gemini25FlashTextToImageImageNumImages {
|
||||
One, // Default
|
||||
Two,
|
||||
Three,
|
||||
Four,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize, ToSchema)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum Gemini25FlashTextToImageImageImageQuality {
|
||||
Auto,
|
||||
Low,
|
||||
Medium,
|
||||
High, // Default
|
||||
}
|
||||
|
||||
|
||||
#[derive(Serialize, Deserialize, ToSchema)]
|
||||
pub struct Gemini25FlashTextToImageImageResponse {
|
||||
pub success: bool,
|
||||
pub inference_job_token: InferenceJobToken,
|
||||
}
|
||||
+1
@@ -19,6 +19,7 @@ pub struct GenerateFluxPro11TextToImageRequest {
|
||||
pub num_images: Option<GenerateFluxPro11TextToImageNumImages>,
|
||||
}
|
||||
|
||||
// TODO(btw,2026-01-01): Fal.ai and Fal.rs client libraries seem to disagree about the options.
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize, ToSchema)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum GenerateFluxPro11TextToImageAspectRatio {
|
||||
+4
@@ -23,10 +23,14 @@ pub struct GenerateFluxPro11UltraTextToImageRequest {
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum GenerateFluxPro11UltraTextToImageAspectRatio {
|
||||
Square, // 1:1
|
||||
LandscapeThreeByTwo, // 3:2
|
||||
LandscapeFourByThree, // 4:3
|
||||
LandscapeSixteenByNine, // 16:9
|
||||
LandscapeTwentyOneByNine, // 21:9
|
||||
PortraitTwoByThree, // 2:3
|
||||
PortraitThreeByFour, // 3:4
|
||||
PortraitNineBySixteen, // 9:16
|
||||
PortraitNineByTwentyOne, // 9:21
|
||||
//Custom { width: u32, height: u32 }, // TODO
|
||||
}
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
pub mod gemini_25_flash_text_to_image;
|
||||
pub mod generate_flux_1_dev_text_to_image;
|
||||
pub mod generate_flux_1_schnell_text_to_image;
|
||||
pub mod generate_flux_pro_11_text_to_image;
|
||||
pub mod generate_flux_pro_11_ultra_text_to_image;
|
||||
pub mod generate_gpt_image_1_text_to_image;
|
||||
+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,
|
||||
|
||||
@@ -5,4 +5,5 @@ pub mod generate_seedance_1_0_lite_image_to_video;
|
||||
pub mod generate_seedance_1_0_pro_image_to_video;
|
||||
pub mod generate_veo_2_image_to_video;
|
||||
pub mod generate_veo_3_fast_image_to_video;
|
||||
pub mod generate_veo_3_image_to_video;
|
||||
pub mod generate_veo_3_image_to_video;
|
||||
pub mod multi_function;
|
||||
+64
@@ -0,0 +1,64 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tokens::tokens::generic_inference_jobs::InferenceJobToken;
|
||||
use tokens::tokens::media_files::MediaFileToken;
|
||||
use utoipa::ToSchema;
|
||||
|
||||
pub const KLING_2P5_TURBO_PRO_MULTI_FUNCTION_VIDEO_VIDEO_PATH: &str = "/v1/generate/video/multi_function/kling_2p5_turbo_pro";
|
||||
|
||||
/// Both text-to-video and image-to-video in one request.
|
||||
#[derive(Serialize, Deserialize, ToSchema)]
|
||||
pub struct Kling2p5TurboProMultiFunctionVideoGenRequest {
|
||||
/// Idempotency token to prevent duplicate requests.
|
||||
pub uuid_idempotency_token: String,
|
||||
|
||||
/// "Required".
|
||||
/// Required downstream, but we'll coerce null to empty string.
|
||||
/// Text prompt.
|
||||
pub prompt: Option<String>,
|
||||
|
||||
/// Optional.
|
||||
/// Only for text-to-video.
|
||||
pub negative_prompt: Option<String>,
|
||||
|
||||
/// Optional.
|
||||
/// Only for image-to-video
|
||||
/// Source of the starting frame
|
||||
/// If present, we're doing image-to-video
|
||||
/// If absent, we're doing text-to-video
|
||||
pub start_frame_image_media_token: Option<MediaFileToken>,
|
||||
|
||||
/// Optional.
|
||||
/// Only for image-to-video
|
||||
/// Source of the ending frame (if desired)
|
||||
pub end_frame_image_media_token: Option<MediaFileToken>,
|
||||
|
||||
/// Optional
|
||||
/// Duration of the video
|
||||
pub duration: Option<Kling2p5TurboProMultiFunctionVideoGenDuration>,
|
||||
|
||||
/// Optional.
|
||||
/// Only for text-to-video
|
||||
pub aspect_ratio: Option<Kling2p5TurboProMultiFunctionVideoGenAspectRatio>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, ToSchema)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum Kling2p5TurboProMultiFunctionVideoGenDuration {
|
||||
FiveSeconds,
|
||||
TenSeconds,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, ToSchema)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum Kling2p5TurboProMultiFunctionVideoGenAspectRatio {
|
||||
Square,
|
||||
SixteenByNine,
|
||||
NineBySixteen,
|
||||
}
|
||||
|
||||
|
||||
#[derive(Serialize, Deserialize, ToSchema)]
|
||||
pub struct Kling2p5TurboProMultiFunctionVideoGenResponse {
|
||||
pub success: bool,
|
||||
pub inference_job_token: InferenceJobToken,
|
||||
}
|
||||
+68
@@ -0,0 +1,68 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tokens::tokens::generic_inference_jobs::InferenceJobToken;
|
||||
use tokens::tokens::media_files::MediaFileToken;
|
||||
use utoipa::ToSchema;
|
||||
|
||||
pub const KLING_2P6_PRO_MULTI_FUNCTION_VIDEO_VIDEO_PATH: &str = "/v1/generate/video/multi_function/kling_2p6_pro";
|
||||
|
||||
/// Both text-to-video and image-to-video in one request.
|
||||
#[derive(Serialize, Deserialize, ToSchema)]
|
||||
pub struct Kling2p6ProMultiFunctionVideoGenRequest {
|
||||
/// Idempotency token to prevent duplicate requests.
|
||||
pub uuid_idempotency_token: String,
|
||||
|
||||
/// "Required".
|
||||
/// Required downstream, but we'll coerce null to empty string.
|
||||
/// Text prompt.
|
||||
pub prompt: Option<String>,
|
||||
|
||||
/// Optional.
|
||||
/// Only for text-to-video.
|
||||
pub negative_prompt: Option<String>,
|
||||
|
||||
/// Optional.
|
||||
/// Only for image-to-video
|
||||
/// Source of the starting frame
|
||||
/// If present, we're doing image-to-video
|
||||
/// If absent, we're doing text-to-video
|
||||
pub start_frame_image_media_token: Option<MediaFileToken>,
|
||||
|
||||
// /// Optional.
|
||||
// /// Only for image-to-video
|
||||
// /// Source of the ending frame (if desired)
|
||||
// pub end_frame_image_media_token: Option<MediaFileToken>,
|
||||
|
||||
/// Optional
|
||||
/// Duration of the video
|
||||
pub duration: Option<Kling2p6ProMultiFunctionVideoGenDuration>,
|
||||
|
||||
/// Optional
|
||||
/// Whether to generate audio.
|
||||
pub generate_audio: Option<bool>,
|
||||
|
||||
/// Optional.
|
||||
/// Only for text-to-video
|
||||
pub aspect_ratio: Option<Kling2p6ProMultiFunctionVideoGenAspectRatio>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, ToSchema)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum Kling2p6ProMultiFunctionVideoGenDuration {
|
||||
FiveSeconds,
|
||||
TenSeconds,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, ToSchema)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum Kling2p6ProMultiFunctionVideoGenAspectRatio {
|
||||
Square,
|
||||
SixteenByNine,
|
||||
NineBySixteen,
|
||||
}
|
||||
|
||||
|
||||
#[derive(Serialize, Deserialize, ToSchema)]
|
||||
pub struct Kling2p6ProMultiFunctionVideoGenResponse {
|
||||
pub success: bool,
|
||||
pub inference_job_token: InferenceJobToken,
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
pub mod kling_2_5_turbo_multi_function_video_gen;
|
||||
pub mod kling_2_6_multi_function_video_gen;
|
||||
pub mod sora_2_multi_function_video_gen;
|
||||
pub mod sora_2_pro_multi_function_video_gen;
|
||||
pub mod veo_3p1_fast_multi_function_video_gen;
|
||||
pub mod veo_3p1_multi_function_video_gen;
|
||||
+66
@@ -0,0 +1,66 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tokens::tokens::generic_inference_jobs::InferenceJobToken;
|
||||
use tokens::tokens::media_files::MediaFileToken;
|
||||
use utoipa::ToSchema;
|
||||
|
||||
pub const SORA_2_MULTI_FUNCTION_VIDEO_VIDEO_PATH: &str = "/v1/generate/video/multi_function/sora_2";
|
||||
|
||||
/// Both text-to-video and image-to-video in one request.
|
||||
#[derive(Serialize, Deserialize, ToSchema)]
|
||||
pub struct Sora2MultiFunctionVideoGenRequest {
|
||||
/// Idempotency token to prevent duplicate requests.
|
||||
pub uuid_idempotency_token: String,
|
||||
|
||||
/// "Required".
|
||||
/// Required downstream, but we'll coerce null to empty string.
|
||||
/// Text prompt.
|
||||
pub prompt: Option<String>,
|
||||
|
||||
/// Optional.
|
||||
/// Only for image-to-video
|
||||
/// Source of the starting frame
|
||||
/// If present, we're doing image-to-video
|
||||
/// If absent, we're doing text-to-video
|
||||
pub image_media_token: Option<MediaFileToken>,
|
||||
|
||||
/// Optional.
|
||||
pub resolution: Option<Sora2MultiFunctionVideoGenResolution>,
|
||||
|
||||
/// Optional
|
||||
/// Duration of the video
|
||||
/// (this is uniform against all modes)
|
||||
pub duration: Option<Sora2MultiFunctionVideoGenDuration>,
|
||||
|
||||
/// Optional.
|
||||
pub aspect_ratio: Option<Sora2MultiFunctionVideoGenAspectRatio>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, ToSchema)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum Sora2MultiFunctionVideoGenResolution {
|
||||
Auto,
|
||||
SevenTwentyP,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, ToSchema)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum Sora2MultiFunctionVideoGenDuration {
|
||||
FourSeconds,
|
||||
EightSeconds,
|
||||
TwelveSeconds,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, ToSchema)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum Sora2MultiFunctionVideoGenAspectRatio {
|
||||
Auto,
|
||||
NineBySixteen,
|
||||
SixteenByNine,
|
||||
}
|
||||
|
||||
|
||||
#[derive(Serialize, Deserialize, ToSchema)]
|
||||
pub struct Sora2MultiFunctionVideoGenResponse {
|
||||
pub success: bool,
|
||||
pub inference_job_token: InferenceJobToken,
|
||||
}
|
||||
+67
@@ -0,0 +1,67 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tokens::tokens::generic_inference_jobs::InferenceJobToken;
|
||||
use tokens::tokens::media_files::MediaFileToken;
|
||||
use utoipa::ToSchema;
|
||||
|
||||
pub const SORA_2_PRO_MULTI_FUNCTION_VIDEO_VIDEO_PATH: &str = "/v1/generate/video/multi_function/sora_2_pro";
|
||||
|
||||
/// Both text-to-video and image-to-video in one request.
|
||||
#[derive(Serialize, Deserialize, ToSchema)]
|
||||
pub struct Sora2ProMultiFunctionVideoGenRequest {
|
||||
/// Idempotency token to prevent duplicate requests.
|
||||
pub uuid_idempotency_token: String,
|
||||
|
||||
/// "Required".
|
||||
/// Required downstream, but we'll coerce null to empty string.
|
||||
/// Text prompt.
|
||||
pub prompt: Option<String>,
|
||||
|
||||
/// Optional.
|
||||
/// Only for image-to-video
|
||||
/// Source of the starting frame
|
||||
/// If present, we're doing image-to-video
|
||||
/// If absent, we're doing text-to-video
|
||||
pub image_media_token: Option<MediaFileToken>,
|
||||
|
||||
/// Optional.
|
||||
pub resolution: Option<Sora2ProMultiFunctionVideoGenResolution>,
|
||||
|
||||
/// Optional
|
||||
/// Duration of the video
|
||||
/// (this is uniform against all modes)
|
||||
pub duration: Option<Sora2ProMultiFunctionVideoGenDuration>,
|
||||
|
||||
/// Optional.
|
||||
pub aspect_ratio: Option<Sora2ProMultiFunctionVideoGenAspectRatio>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, ToSchema)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum Sora2ProMultiFunctionVideoGenResolution {
|
||||
Auto,
|
||||
SevenTwentyP,
|
||||
TenEightyP,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, ToSchema)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum Sora2ProMultiFunctionVideoGenDuration {
|
||||
FourSeconds,
|
||||
EightSeconds,
|
||||
TwelveSeconds,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, ToSchema)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum Sora2ProMultiFunctionVideoGenAspectRatio {
|
||||
Auto,
|
||||
NineBySixteen,
|
||||
SixteenByNine,
|
||||
}
|
||||
|
||||
|
||||
#[derive(Serialize, Deserialize, ToSchema)]
|
||||
pub struct Sora2ProMultiFunctionVideoGenResponse {
|
||||
pub success: bool,
|
||||
pub inference_job_token: InferenceJobToken,
|
||||
}
|
||||
+90
@@ -0,0 +1,90 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tokens::tokens::generic_inference_jobs::InferenceJobToken;
|
||||
use tokens::tokens::media_files::MediaFileToken;
|
||||
use utoipa::ToSchema;
|
||||
|
||||
pub const VEO_3P1_FAST_MULTI_FUNCTION_VIDEO_VIDEO_PATH: &str = "/v1/generate/video/multi_function/veo_3p1_fast";
|
||||
|
||||
/// Both text-to-video and image-to-video in one request.
|
||||
#[derive(Serialize, Deserialize, ToSchema)]
|
||||
pub struct Veo3p1FastMultiFunctionVideoGenRequest {
|
||||
/// Idempotency token to prevent duplicate requests.
|
||||
pub uuid_idempotency_token: String,
|
||||
|
||||
/// "Required".
|
||||
/// Required downstream, but we'll coerce null to empty string.
|
||||
/// Text prompt.
|
||||
pub prompt: Option<String>,
|
||||
|
||||
/// Optional.
|
||||
/// Only for text-to-video.
|
||||
pub negative_prompt: Option<String>,
|
||||
|
||||
/// Optional.
|
||||
/// Only for image-to-video
|
||||
/// Source of the starting frame
|
||||
/// If present, we're doing image-to-video
|
||||
/// If absent, we're doing text-to-video
|
||||
pub start_frame_image_media_token: Option<MediaFileToken>,
|
||||
|
||||
/// Optional.
|
||||
/// Only for image-to-video
|
||||
/// Source of the ending frame (if desired)
|
||||
pub end_frame_image_media_token: Option<MediaFileToken>,
|
||||
|
||||
/// Optional
|
||||
/// Duration of the video
|
||||
/// (this is uniform against all modes)
|
||||
pub duration: Option<Veo3p1FastMultiFunctionVideoGenDuration>,
|
||||
|
||||
/// Optional.
|
||||
pub aspect_ratio: Option<Veo3p1FastMultiFunctionVideoGenAspectRatio>,
|
||||
|
||||
/// Optional.
|
||||
pub resolution: Option<Veo3p1FastMultiFunctionVideoGenResolution>,
|
||||
|
||||
/// Optional
|
||||
/// Whether to generate audio.
|
||||
pub generate_audio: Option<bool>,
|
||||
|
||||
/// Optional
|
||||
/// Only for text-to-video
|
||||
pub enhance_prompt: Option<bool>,
|
||||
|
||||
/// Optional
|
||||
/// Only for text-to-video
|
||||
pub seed: Option<i64>,
|
||||
|
||||
/// Optional
|
||||
/// Only for text-to-video
|
||||
pub auto_fix: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, ToSchema)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum Veo3p1FastMultiFunctionVideoGenDuration {
|
||||
FourSeconds,
|
||||
SixSeconds,
|
||||
EightSeconds,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, ToSchema)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum Veo3p1FastMultiFunctionVideoGenAspectRatio {
|
||||
Auto,
|
||||
SixteenByNine,
|
||||
NineBySixteen,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, ToSchema)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum Veo3p1FastMultiFunctionVideoGenResolution {
|
||||
SevenTwentyP,
|
||||
TenEightyP,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, ToSchema)]
|
||||
pub struct Veo3p1FastMultiFunctionVideoGenResponse {
|
||||
pub success: bool,
|
||||
pub inference_job_token: InferenceJobToken,
|
||||
}
|
||||
+90
@@ -0,0 +1,90 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tokens::tokens::generic_inference_jobs::InferenceJobToken;
|
||||
use tokens::tokens::media_files::MediaFileToken;
|
||||
use utoipa::ToSchema;
|
||||
|
||||
pub const VEO_3P1_MULTI_FUNCTION_VIDEO_VIDEO_PATH: &str = "/v1/generate/video/multi_function/veo_3p1";
|
||||
|
||||
/// Both text-to-video and image-to-video in one request.
|
||||
#[derive(Serialize, Deserialize, ToSchema)]
|
||||
pub struct Veo3p1MultiFunctionVideoGenRequest {
|
||||
/// Idempotency token to prevent duplicate requests.
|
||||
pub uuid_idempotency_token: String,
|
||||
|
||||
/// "Required".
|
||||
/// Required downstream, but we'll coerce null to empty string.
|
||||
/// Text prompt.
|
||||
pub prompt: Option<String>,
|
||||
|
||||
/// Optional.
|
||||
/// Only for text-to-video.
|
||||
pub negative_prompt: Option<String>,
|
||||
|
||||
/// Optional.
|
||||
/// Only for image-to-video
|
||||
/// Source of the starting frame
|
||||
/// If present, we're doing image-to-video
|
||||
/// If absent, we're doing text-to-video
|
||||
pub start_frame_image_media_token: Option<MediaFileToken>,
|
||||
|
||||
/// Optional.
|
||||
/// Only for image-to-video
|
||||
/// Source of the ending frame (if desired)
|
||||
pub end_frame_image_media_token: Option<MediaFileToken>,
|
||||
|
||||
/// Optional
|
||||
/// Duration of the video
|
||||
/// (this is uniform against all modes)
|
||||
pub duration: Option<Veo3p1MultiFunctionVideoGenDuration>,
|
||||
|
||||
/// Optional.
|
||||
pub aspect_ratio: Option<Veo3p1MultiFunctionVideoGenAspectRatio>,
|
||||
|
||||
/// Optional.
|
||||
pub resolution: Option<Veo3p1MultiFunctionVideoGenResolution>,
|
||||
|
||||
/// Optional
|
||||
/// Whether to generate audio.
|
||||
pub generate_audio: Option<bool>,
|
||||
|
||||
/// Optional
|
||||
/// Only for text-to-video
|
||||
pub enhance_prompt: Option<bool>,
|
||||
|
||||
/// Optional
|
||||
/// Only for text-to-video
|
||||
pub seed: Option<i64>,
|
||||
|
||||
/// Optional
|
||||
/// Only for text-to-video
|
||||
pub auto_fix: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, ToSchema)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum Veo3p1MultiFunctionVideoGenDuration {
|
||||
FourSeconds,
|
||||
SixSeconds,
|
||||
EightSeconds,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, ToSchema)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum Veo3p1MultiFunctionVideoGenAspectRatio {
|
||||
Auto,
|
||||
SixteenByNine,
|
||||
NineBySixteen,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, ToSchema)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum Veo3p1MultiFunctionVideoGenResolution {
|
||||
SevenTwentyP,
|
||||
TenEightyP,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, ToSchema)]
|
||||
pub struct Veo3p1MultiFunctionVideoGenResponse {
|
||||
pub success: bool,
|
||||
pub inference_job_token: InferenceJobToken,
|
||||
}
|
||||
+1
@@ -0,0 +1 @@
|
||||
pub mod remove_image_background;
|
||||
+1
-1
@@ -2,7 +2,7 @@ use crate::credentials::storyteller_credential_set::StorytellerCredentialSet;
|
||||
use crate::error::storyteller_error::StorytellerError;
|
||||
use crate::utils::api_host::ApiHost;
|
||||
use crate::utils::basic_json_post_request::basic_json_post_request;
|
||||
use artcraft_api_defs::generate::image::remove_image_background::{RemoveImageBackgroundRequest, RemoveImageBackgroundResponse, REMOVE_IMAGE_BACKGROUND_PATH};
|
||||
use artcraft_api_defs::generate::image::bg_removal::remove_image_background::{RemoveImageBackgroundRequest, RemoveImageBackgroundResponse, REMOVE_IMAGE_BACKGROUND_PATH};
|
||||
|
||||
pub async fn remove_image_background(
|
||||
api_host: &ApiHost,
|
||||
@@ -1,9 +1,5 @@
|
||||
pub mod bg_removal;
|
||||
pub mod edit;
|
||||
pub mod generate_flux_1_dev_text_to_image;
|
||||
pub mod generate_flux_1_schnell_text_to_image;
|
||||
pub mod generate_flux_pro_11_text_to_image;
|
||||
pub mod generate_flux_pro_11_ultra_text_to_image;
|
||||
pub mod generate_gpt_image_1_text_to_image;
|
||||
pub mod inpaint;
|
||||
pub mod multi_function;
|
||||
pub mod remove_image_background;
|
||||
pub mod text;
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
use crate::credentials::storyteller_credential_set::StorytellerCredentialSet;
|
||||
use crate::error::storyteller_error::StorytellerError;
|
||||
use crate::utils::api_host::ApiHost;
|
||||
use crate::utils::basic_json_post_request::basic_json_post_request;
|
||||
use artcraft_api_defs::generate::image::multi_function::bytedance_seedream_v4_multi_function_image_gen::{BytedanceSeedreamV4MultiFunctionImageGenRequest, BytedanceSeedreamV4MultiFunctionImageGenResponse, BYTEDANCE_SEEDREAM_V4_MULTI_FUNCTION_IMAGE_GEN_PATH};
|
||||
|
||||
pub async fn bytedance_seedream_v4_multi_function_image_gen(
|
||||
api_host: &ApiHost,
|
||||
maybe_creds: Option<&StorytellerCredentialSet>,
|
||||
request: BytedanceSeedreamV4MultiFunctionImageGenRequest,
|
||||
) -> Result<BytedanceSeedreamV4MultiFunctionImageGenResponse, StorytellerError> {
|
||||
Ok(basic_json_post_request(
|
||||
api_host,
|
||||
BYTEDANCE_SEEDREAM_V4_MULTI_FUNCTION_IMAGE_GEN_PATH,
|
||||
maybe_creds,
|
||||
request,
|
||||
).await?)
|
||||
}
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
use crate::credentials::storyteller_credential_set::StorytellerCredentialSet;
|
||||
use crate::error::storyteller_error::StorytellerError;
|
||||
use crate::utils::api_host::ApiHost;
|
||||
use crate::utils::basic_json_post_request::basic_json_post_request;
|
||||
use artcraft_api_defs::generate::image::multi_function::bytedance_seedream_v4p5_multi_function_image_gen::{BytedanceSeedreamV4p5MultiFunctionImageGenRequest, BytedanceSeedreamV4p5MultiFunctionImageGenResponse, BYTEDANCE_SEEDREAM_V4P5_MULTI_FUNCTION_IMAGE_GEN_PATH};
|
||||
|
||||
pub async fn bytedance_seedream_v4p5_multi_function_image_gen(
|
||||
api_host: &ApiHost,
|
||||
maybe_creds: Option<&StorytellerCredentialSet>,
|
||||
request: BytedanceSeedreamV4p5MultiFunctionImageGenRequest,
|
||||
) -> Result<BytedanceSeedreamV4p5MultiFunctionImageGenResponse, StorytellerError> {
|
||||
Ok(basic_json_post_request(
|
||||
api_host,
|
||||
BYTEDANCE_SEEDREAM_V4P5_MULTI_FUNCTION_IMAGE_GEN_PATH,
|
||||
maybe_creds,
|
||||
request,
|
||||
).await?)
|
||||
}
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
use crate::credentials::storyteller_credential_set::StorytellerCredentialSet;
|
||||
use crate::error::storyteller_error::StorytellerError;
|
||||
use crate::utils::api_host::ApiHost;
|
||||
use crate::utils::basic_json_post_request::basic_json_post_request;
|
||||
use artcraft_api_defs::generate::image::multi_function::gpt_image_1p5_multi_function_image_gen::{GptImage1p5MultiFunctionImageGenRequest, GptImage1p5MultiFunctionImageGenResponse, GPT_IMAGE_1P5_MULTI_FUNCTION_IMAGE_GEN_PATH};
|
||||
|
||||
pub async fn gpt_image_1p5_multi_function_image_gen(
|
||||
api_host: &ApiHost,
|
||||
maybe_creds: Option<&StorytellerCredentialSet>,
|
||||
request: GptImage1p5MultiFunctionImageGenRequest,
|
||||
) -> Result<GptImage1p5MultiFunctionImageGenResponse, StorytellerError> {
|
||||
Ok(basic_json_post_request(
|
||||
api_host,
|
||||
GPT_IMAGE_1P5_MULTI_FUNCTION_IMAGE_GEN_PATH,
|
||||
maybe_creds,
|
||||
request,
|
||||
).await?)
|
||||
}
|
||||
+4
@@ -1 +1,5 @@
|
||||
pub mod bytedance_seedream_v4_multi_function_image_gen_image;
|
||||
pub mod bytedance_seedream_v4p5_multi_function_image_gen_image;
|
||||
pub mod gpt_image_1p5_multi_function_image_gen_image;
|
||||
pub mod nano_banana_multi_function_image_gen_image;
|
||||
pub mod nano_banana_pro_multi_function_image_gen_image;
|
||||
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
use crate::credentials::storyteller_credential_set::StorytellerCredentialSet;
|
||||
use crate::error::storyteller_error::StorytellerError;
|
||||
use crate::utils::api_host::ApiHost;
|
||||
use crate::utils::basic_json_post_request::basic_json_post_request;
|
||||
use artcraft_api_defs::generate::image::multi_function::nano_banana_multi_function_image_gen::{NanoBananaMultiFunctionImageGenRequest, NanoBananaMultiFunctionImageGenResponse, NANO_BANANA_MULTI_FUNCTION_IMAGE_GEN_PATH};
|
||||
|
||||
pub async fn nano_banana_multi_function_image_gen(
|
||||
api_host: &ApiHost,
|
||||
maybe_creds: Option<&StorytellerCredentialSet>,
|
||||
request: NanoBananaMultiFunctionImageGenRequest,
|
||||
) -> Result<NanoBananaMultiFunctionImageGenResponse, StorytellerError> {
|
||||
Ok(basic_json_post_request(
|
||||
api_host,
|
||||
NANO_BANANA_MULTI_FUNCTION_IMAGE_GEN_PATH,
|
||||
maybe_creds,
|
||||
request,
|
||||
).await?)
|
||||
}
|
||||
+1
-1
@@ -2,7 +2,7 @@ use crate::credentials::storyteller_credential_set::StorytellerCredentialSet;
|
||||
use crate::error::storyteller_error::StorytellerError;
|
||||
use crate::utils::api_host::ApiHost;
|
||||
use crate::utils::basic_json_post_request::basic_json_post_request;
|
||||
use artcraft_api_defs::generate::image::generate_flux_1_dev_text_to_image::{GenerateFlux1DevTextToImageRequest, GenerateFlux1DevTextToImageResponse, GENERATE_FLUX_1_DEV_TEXT_TO_IMAGE_PATH};
|
||||
use artcraft_api_defs::generate::image::text::generate_flux_1_dev_text_to_image::{GenerateFlux1DevTextToImageRequest, GenerateFlux1DevTextToImageResponse, GENERATE_FLUX_1_DEV_TEXT_TO_IMAGE_PATH};
|
||||
|
||||
|
||||
pub async fn generate_flux_1_dev_text_to_image(
|
||||
+1
-1
@@ -2,7 +2,7 @@ use crate::credentials::storyteller_credential_set::StorytellerCredentialSet;
|
||||
use crate::error::storyteller_error::StorytellerError;
|
||||
use crate::utils::api_host::ApiHost;
|
||||
use crate::utils::basic_json_post_request::basic_json_post_request;
|
||||
use artcraft_api_defs::generate::image::generate_flux_1_schnell_text_to_image::{GenerateFlux1SchnellTextToImageRequest, GenerateFlux1SchnellTextToImageResponse, GENERATE_FLUX_1_SCHNELL_TEXT_TO_IMAGE_PATH};
|
||||
use artcraft_api_defs::generate::image::text::generate_flux_1_schnell_text_to_image::{GenerateFlux1SchnellTextToImageRequest, GenerateFlux1SchnellTextToImageResponse, GENERATE_FLUX_1_SCHNELL_TEXT_TO_IMAGE_PATH};
|
||||
|
||||
|
||||
pub async fn generate_flux_1_schnell_text_to_image(
|
||||
+1
-1
@@ -2,7 +2,7 @@ use crate::credentials::storyteller_credential_set::StorytellerCredentialSet;
|
||||
use crate::error::storyteller_error::StorytellerError;
|
||||
use crate::utils::api_host::ApiHost;
|
||||
use crate::utils::basic_json_post_request::basic_json_post_request;
|
||||
use artcraft_api_defs::generate::image::generate_flux_pro_11_text_to_image::{GenerateFluxPro11TextToImageRequest, GenerateFluxPro11TextToImageResponse, GENERATE_FLUX_PRO_11_TEXT_TO_IMAGE_PATH};
|
||||
use artcraft_api_defs::generate::image::text::generate_flux_pro_11_text_to_image::{GenerateFluxPro11TextToImageRequest, GenerateFluxPro11TextToImageResponse, GENERATE_FLUX_PRO_11_TEXT_TO_IMAGE_PATH};
|
||||
|
||||
|
||||
pub async fn generate_flux_pro_11_text_to_image(
|
||||
+1
-1
@@ -2,7 +2,7 @@ use crate::credentials::storyteller_credential_set::StorytellerCredentialSet;
|
||||
use crate::error::storyteller_error::StorytellerError;
|
||||
use crate::utils::api_host::ApiHost;
|
||||
use crate::utils::basic_json_post_request::basic_json_post_request;
|
||||
use artcraft_api_defs::generate::image::generate_flux_pro_11_ultra_text_to_image::{GenerateFluxPro11UltraTextToImageRequest, GenerateFluxPro11UltraTextToImageResponse, GENERATE_FLUX_PRO_11_ULTRA_TEXT_TO_IMAGE_PATH};
|
||||
use artcraft_api_defs::generate::image::text::generate_flux_pro_11_ultra_text_to_image::{GenerateFluxPro11UltraTextToImageRequest, GenerateFluxPro11UltraTextToImageResponse, GENERATE_FLUX_PRO_11_ULTRA_TEXT_TO_IMAGE_PATH};
|
||||
|
||||
pub async fn generate_flux_pro_11_ultra_text_to_image(
|
||||
api_host: &ApiHost,
|
||||
+1
-1
@@ -2,7 +2,7 @@ use crate::credentials::storyteller_credential_set::StorytellerCredentialSet;
|
||||
use crate::error::storyteller_error::StorytellerError;
|
||||
use crate::utils::api_host::ApiHost;
|
||||
use crate::utils::basic_json_post_request::basic_json_post_request;
|
||||
use artcraft_api_defs::generate::image::generate_gpt_image_1_text_to_image::{GenerateGptImage1TextToImageRequest, GenerateGptImage1TextToImageResponse, GPT_IMAGE_1_TEXT_TO_IMAGE_PATH};
|
||||
use artcraft_api_defs::generate::image::text::generate_gpt_image_1_text_to_image::{GenerateGptImage1TextToImageRequest, GenerateGptImage1TextToImageResponse, GPT_IMAGE_1_TEXT_TO_IMAGE_PATH};
|
||||
|
||||
|
||||
pub async fn generate_gpt_image_1_text_to_image(
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
pub mod generate_flux_1_dev_text_to_image;
|
||||
pub mod generate_flux_1_schnell_text_to_image;
|
||||
pub mod generate_flux_pro_11_text_to_image;
|
||||
pub mod generate_flux_pro_11_ultra_text_to_image;
|
||||
pub mod generate_gpt_image_1_text_to_image;
|
||||
@@ -5,3 +5,4 @@ pub mod generate_seedance_1_0_lite_image_to_video;
|
||||
pub mod generate_veo_2_image_to_video;
|
||||
pub mod generate_veo_3_fast_image_to_video;
|
||||
pub mod generate_veo_3_image_to_video;
|
||||
pub mod multi_function;
|
||||
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
use crate::credentials::storyteller_credential_set::StorytellerCredentialSet;
|
||||
use crate::error::storyteller_error::StorytellerError;
|
||||
use crate::utils::api_host::ApiHost;
|
||||
use crate::utils::basic_json_post_request::basic_json_post_request;
|
||||
use artcraft_api_defs::generate::video::multi_function::kling_2_5_turbo_multi_function_video_gen::{Kling2p5TurboProMultiFunctionVideoGenRequest, Kling2p5TurboProMultiFunctionVideoGenResponse, KLING_2P5_TURBO_PRO_MULTI_FUNCTION_VIDEO_VIDEO_PATH};
|
||||
|
||||
pub async fn kling_2p5_turbo_pro_multi_function_video_gen(
|
||||
api_host: &ApiHost,
|
||||
maybe_creds: Option<&StorytellerCredentialSet>,
|
||||
request: Kling2p5TurboProMultiFunctionVideoGenRequest,
|
||||
) -> Result<Kling2p5TurboProMultiFunctionVideoGenResponse, StorytellerError> {
|
||||
Ok(basic_json_post_request(
|
||||
api_host,
|
||||
KLING_2P5_TURBO_PRO_MULTI_FUNCTION_VIDEO_VIDEO_PATH,
|
||||
maybe_creds,
|
||||
request,
|
||||
).await?)
|
||||
}
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
use crate::credentials::storyteller_credential_set::StorytellerCredentialSet;
|
||||
use crate::error::storyteller_error::StorytellerError;
|
||||
use crate::utils::api_host::ApiHost;
|
||||
use crate::utils::basic_json_post_request::basic_json_post_request;
|
||||
use artcraft_api_defs::generate::video::multi_function::kling_2_6_multi_function_video_gen::{Kling2p6ProMultiFunctionVideoGenRequest, Kling2p6ProMultiFunctionVideoGenResponse, KLING_2P6_PRO_MULTI_FUNCTION_VIDEO_VIDEO_PATH};
|
||||
|
||||
pub async fn kling_2p6_pro_multi_function_video_gen(
|
||||
api_host: &ApiHost,
|
||||
maybe_creds: Option<&StorytellerCredentialSet>,
|
||||
request: Kling2p6ProMultiFunctionVideoGenRequest,
|
||||
) -> Result<Kling2p6ProMultiFunctionVideoGenResponse, StorytellerError> {
|
||||
Ok(basic_json_post_request(
|
||||
api_host,
|
||||
KLING_2P6_PRO_MULTI_FUNCTION_VIDEO_VIDEO_PATH,
|
||||
maybe_creds,
|
||||
request,
|
||||
).await?)
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
pub mod kling_2p5_turbo_pro_multi_function_video_gen;
|
||||
pub mod kling_2p6_pro_multi_function_video_gen;
|
||||
pub mod sora_2_multi_function_video_gen;
|
||||
pub mod sora_2_pro_multi_function_video_gen;
|
||||
pub mod veo_3p1_fast_multi_function_video_gen;
|
||||
pub mod veo_3p1_multi_function_video_gen;
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
use crate::credentials::storyteller_credential_set::StorytellerCredentialSet;
|
||||
use crate::error::storyteller_error::StorytellerError;
|
||||
use crate::utils::api_host::ApiHost;
|
||||
use crate::utils::basic_json_post_request::basic_json_post_request;
|
||||
use artcraft_api_defs::generate::video::multi_function::sora_2_multi_function_video_gen::{Sora2MultiFunctionVideoGenRequest, Sora2MultiFunctionVideoGenResponse, SORA_2_MULTI_FUNCTION_VIDEO_VIDEO_PATH};
|
||||
|
||||
pub async fn sora_2_multi_function_video_gen(
|
||||
api_host: &ApiHost,
|
||||
maybe_creds: Option<&StorytellerCredentialSet>,
|
||||
request: Sora2MultiFunctionVideoGenRequest,
|
||||
) -> Result<Sora2MultiFunctionVideoGenResponse, StorytellerError> {
|
||||
Ok(basic_json_post_request(
|
||||
api_host,
|
||||
SORA_2_MULTI_FUNCTION_VIDEO_VIDEO_PATH,
|
||||
maybe_creds,
|
||||
request,
|
||||
).await?)
|
||||
}
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
use crate::credentials::storyteller_credential_set::StorytellerCredentialSet;
|
||||
use crate::error::storyteller_error::StorytellerError;
|
||||
use crate::utils::api_host::ApiHost;
|
||||
use crate::utils::basic_json_post_request::basic_json_post_request;
|
||||
use artcraft_api_defs::generate::video::multi_function::sora_2_pro_multi_function_video_gen::{Sora2ProMultiFunctionVideoGenRequest, Sora2ProMultiFunctionVideoGenResponse, SORA_2_PRO_MULTI_FUNCTION_VIDEO_VIDEO_PATH};
|
||||
|
||||
pub async fn sora_2_pro_multi_function_video_gen(
|
||||
api_host: &ApiHost,
|
||||
maybe_creds: Option<&StorytellerCredentialSet>,
|
||||
request: Sora2ProMultiFunctionVideoGenRequest,
|
||||
) -> Result<Sora2ProMultiFunctionVideoGenResponse, StorytellerError> {
|
||||
Ok(basic_json_post_request(
|
||||
api_host,
|
||||
SORA_2_PRO_MULTI_FUNCTION_VIDEO_VIDEO_PATH,
|
||||
maybe_creds,
|
||||
request,
|
||||
).await?)
|
||||
}
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
use crate::credentials::storyteller_credential_set::StorytellerCredentialSet;
|
||||
use crate::error::storyteller_error::StorytellerError;
|
||||
use crate::utils::api_host::ApiHost;
|
||||
use crate::utils::basic_json_post_request::basic_json_post_request;
|
||||
use artcraft_api_defs::generate::video::multi_function::veo_3p1_fast_multi_function_video_gen::{Veo3p1FastMultiFunctionVideoGenRequest, Veo3p1FastMultiFunctionVideoGenResponse, VEO_3P1_FAST_MULTI_FUNCTION_VIDEO_VIDEO_PATH};
|
||||
|
||||
pub async fn veo_3p1_fast_multi_function_video_gen(
|
||||
api_host: &ApiHost,
|
||||
maybe_creds: Option<&StorytellerCredentialSet>,
|
||||
request: Veo3p1FastMultiFunctionVideoGenRequest,
|
||||
) -> Result<Veo3p1FastMultiFunctionVideoGenResponse, StorytellerError> {
|
||||
Ok(basic_json_post_request(
|
||||
api_host,
|
||||
VEO_3P1_FAST_MULTI_FUNCTION_VIDEO_VIDEO_PATH,
|
||||
maybe_creds,
|
||||
request,
|
||||
).await?)
|
||||
}
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
use crate::credentials::storyteller_credential_set::StorytellerCredentialSet;
|
||||
use crate::error::storyteller_error::StorytellerError;
|
||||
use crate::utils::api_host::ApiHost;
|
||||
use crate::utils::basic_json_post_request::basic_json_post_request;
|
||||
use artcraft_api_defs::generate::video::multi_function::veo_3p1_multi_function_video_gen::{Veo3p1MultiFunctionVideoGenRequest, Veo3p1MultiFunctionVideoGenResponse, VEO_3P1_MULTI_FUNCTION_VIDEO_VIDEO_PATH};
|
||||
|
||||
pub async fn veo_3p1_multi_function_video_gen(
|
||||
api_host: &ApiHost,
|
||||
maybe_creds: Option<&StorytellerCredentialSet>,
|
||||
request: Veo3p1MultiFunctionVideoGenRequest,
|
||||
) -> Result<Veo3p1MultiFunctionVideoGenResponse, StorytellerError> {
|
||||
Ok(basic_json_post_request(
|
||||
api_host,
|
||||
VEO_3P1_MULTI_FUNCTION_VIDEO_VIDEO_PATH,
|
||||
maybe_creds,
|
||||
request,
|
||||
).await?)
|
||||
}
|
||||
+143
@@ -0,0 +1,143 @@
|
||||
use crate::credentials::storyteller_credential_set::StorytellerCredentialSet;
|
||||
use crate::error::api_error::ApiError;
|
||||
use crate::error::client_error::ClientError;
|
||||
use crate::error::storyteller_error::StorytellerError;
|
||||
use crate::utils::api_host::ApiHost;
|
||||
use crate::utils::constants::{APPLICATION_JSON, USER_AGENT};
|
||||
use crate::utils::filter_bad_response::filter_bad_response;
|
||||
use crate::utils::http_get_anonymous::http_get_anonymous;
|
||||
use chrono::{DateTime, Utc};
|
||||
use enums::by_table::media_files::media_file_animation_type::MediaFileAnimationType;
|
||||
use enums::by_table::media_files::media_file_class::MediaFileClass;
|
||||
use enums::by_table::media_files::media_file_engine_category::MediaFileEngineCategory;
|
||||
use enums::by_table::media_files::media_file_type::MediaFileType;
|
||||
use enums::by_table::model_weights::weights_category::WeightsCategory;
|
||||
use enums::common::generation_provider::GenerationProvider;
|
||||
use enums::common::visibility::Visibility;
|
||||
use enums::no_table::style_transfer::style_transfer_name::StyleTransferName;
|
||||
use enums_public::by_table::model_weights::public_weights_types::PublicWeightsType;
|
||||
use idempotency::uuid::generate_random_uuid;
|
||||
use log::debug;
|
||||
use reqwest::multipart::Form;
|
||||
use reqwest::Client;
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use std::fs::File;
|
||||
use std::io::Read;
|
||||
use std::path::Path;
|
||||
use tokens::tokens::batch_generations::BatchGenerationToken;
|
||||
use tokens::tokens::media_files::MediaFileToken;
|
||||
use tokens::tokens::model_weights::ModelWeightToken;
|
||||
use tokens::tokens::prompts::PromptToken;
|
||||
use uuid::uuid;
|
||||
|
||||
pub struct LegacyUploadMediaFileFromFileArgs<'a, P: AsRef<Path>> {
|
||||
pub api_host: &'a ApiHost,
|
||||
pub maybe_creds: Option<&'a StorytellerCredentialSet>,
|
||||
|
||||
// NB: Path needs to be owned for the request.
|
||||
pub path: P,
|
||||
|
||||
// /// If true, we should hide the image from the user's gallery.
|
||||
// pub is_intermediate_system_file: bool,
|
||||
|
||||
// /// If provided, this is the prompt that this image is associated with.
|
||||
// /// NOTE: Cannot set `is_intermediate_system_file = true` if this is set.
|
||||
// pub maybe_prompt_token: Option<&'a PromptToken>,
|
||||
|
||||
// /// If provided, this is the service provider that created the image.
|
||||
// /// NOTE: Cannot set `is_intermediate_system_file = true` if this is set.
|
||||
// pub maybe_generation_provider: Option<GenerationProvider>,
|
||||
|
||||
// /// If provided, this groups the file into a batch
|
||||
// /// TODO: This shouldn't be set clientside without the backend generating the token
|
||||
// /// and cryptographically securing it. But we need to go fast here.
|
||||
// pub maybe_batch_token: Option<&'a BatchGenerationToken>,
|
||||
}
|
||||
|
||||
|
||||
/// Upload using the legacy endpoint (spz, etc.)
|
||||
pub async fn legacy_upload_media_file_from_file<P: AsRef<Path>>(
|
||||
args: LegacyUploadMediaFileFromFileArgs<'_, P>,
|
||||
) -> Result<UploadImageMediaFileSuccessResponse, StorytellerError> {
|
||||
|
||||
validate_args(&args)?;
|
||||
|
||||
let url = get_route(args.api_host);
|
||||
|
||||
debug!("Requesting {:?}", &url);
|
||||
|
||||
//let mut file = File::open(path)?;
|
||||
|
||||
let client = Client::builder()
|
||||
.gzip(true)
|
||||
.build()
|
||||
.map_err(|err| StorytellerError::Client(ClientError::from(err)))?;
|
||||
|
||||
let mut form = Form::new()
|
||||
.text("uuid_idempotency_token", generate_random_uuid())
|
||||
.file("file", args.path)
|
||||
.await
|
||||
.map_err(|err| StorytellerError::Client(ClientError::from(err)))?;
|
||||
|
||||
//if args.is_intermediate_system_file {
|
||||
// form = form.text("is_intermediate_system_file", "true");
|
||||
//}
|
||||
|
||||
//if let Some(prompt_token) = &args.maybe_prompt_token {
|
||||
// form = form.text("maybe_prompt_token", prompt_token.to_string());
|
||||
//}
|
||||
|
||||
//if let Some(batch_token) = &args.maybe_batch_token {
|
||||
// form = form.text("maybe_batch_token", batch_token.to_string());
|
||||
//}
|
||||
|
||||
let mut request_builder = client.post(url)
|
||||
.header("User-Agent", USER_AGENT)
|
||||
.header("Accept", APPLICATION_JSON);
|
||||
|
||||
if let Some(creds) = args.maybe_creds {
|
||||
if let Some(header) = &creds.maybe_as_cookie_header() {
|
||||
request_builder = request_builder.header("Cookie", header);
|
||||
}
|
||||
}
|
||||
|
||||
let response = request_builder
|
||||
.multipart(form)
|
||||
.send()
|
||||
.await
|
||||
.map_err(|err| StorytellerError::Api(ApiError::from(err)))?;
|
||||
|
||||
let response = filter_bad_response(response).await?;
|
||||
let response_body = &response.text().await
|
||||
.map_err(|err| StorytellerError::Api(ApiError::from(err)))?;
|
||||
|
||||
let media_file = serde_json::from_str(&response_body)
|
||||
.map_err(|err| StorytellerError::Api(ApiError::from(err)))?;
|
||||
|
||||
Ok(media_file)
|
||||
}
|
||||
|
||||
fn get_route(api_host: &ApiHost) -> String {
|
||||
let api_hostname_and_scheme = api_host.to_api_hostname_and_scheme();
|
||||
format!("{}/v1/media_files/upload", api_hostname_and_scheme)
|
||||
}
|
||||
|
||||
// TODO(bt,2025-04-22): Share API definitions between client and server in common crate.
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
pub struct UploadImageMediaFileSuccessResponse {
|
||||
pub success: bool,
|
||||
pub media_file_token: MediaFileToken,
|
||||
}
|
||||
|
||||
fn validate_args<P: AsRef<Path>>(
|
||||
args: &LegacyUploadMediaFileFromFileArgs<'_, P>,
|
||||
) -> Result<(), StorytellerError> {
|
||||
// if args.is_intermediate_system_file && args.maybe_prompt_token.is_some() {
|
||||
// return Err(StorytellerError::Client(ClientError::InvalidPreflightRequest(
|
||||
// "Cannot set `is_intermediate_system_file` to true if `maybe_prompt_token` is provided."
|
||||
// .to_string())));
|
||||
// }
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -5,3 +5,4 @@ pub mod upload_image_media_file_from_bytes;
|
||||
pub mod upload_image_media_file_from_file;
|
||||
pub mod upload_new_engine_asset_from_file;
|
||||
pub mod upload_video_media_file_from_file;
|
||||
pub mod legacy_upload_media_file_from_file;
|
||||
|
||||
@@ -5,7 +5,6 @@ use crate::error::storyteller_error::StorytellerError;
|
||||
use crate::utils::api_host::ApiHost;
|
||||
use crate::utils::constants::{APPLICATION_JSON, USER_AGENT};
|
||||
use crate::utils::filter_bad_response::filter_bad_response;
|
||||
use artcraft_api_defs::generate::image::generate_flux_1_schnell_text_to_image::{GenerateFlux1SchnellTextToImageRequest, GenerateFlux1SchnellTextToImageResponse, GENERATE_FLUX_1_SCHNELL_TEXT_TO_IMAGE_PATH};
|
||||
use log::{debug, info};
|
||||
use reqwest::Client;
|
||||
use serde::de::DeserializeOwned;
|
||||
|
||||
@@ -5,7 +5,6 @@ use crate::error::storyteller_error::StorytellerError;
|
||||
use crate::utils::api_host::ApiHost;
|
||||
use crate::utils::constants::{APPLICATION_JSON, USER_AGENT};
|
||||
use crate::utils::filter_bad_response::filter_bad_response;
|
||||
use artcraft_api_defs::generate::image::generate_flux_1_schnell_text_to_image::{GenerateFlux1SchnellTextToImageRequest, GenerateFlux1SchnellTextToImageResponse, GENERATE_FLUX_1_SCHNELL_TEXT_TO_IMAGE_PATH};
|
||||
use log::debug;
|
||||
use reqwest::Client;
|
||||
use serde::de::DeserializeOwned;
|
||||
|
||||
@@ -5,7 +5,6 @@ use crate::error::storyteller_error::StorytellerError;
|
||||
use crate::utils::api_host::ApiHost;
|
||||
use crate::utils::constants::{APPLICATION_JSON, USER_AGENT};
|
||||
use crate::utils::filter_bad_response::filter_bad_response;
|
||||
use artcraft_api_defs::generate::image::generate_flux_1_schnell_text_to_image::{GenerateFlux1SchnellTextToImageRequest, GenerateFlux1SchnellTextToImageResponse, GENERATE_FLUX_1_SCHNELL_TEXT_TO_IMAGE_PATH};
|
||||
use log::{debug, info};
|
||||
use reqwest::Client;
|
||||
use serde::de::DeserializeOwned;
|
||||
|
||||
-1
@@ -5,7 +5,6 @@ use crate::error::storyteller_error::StorytellerError;
|
||||
use crate::utils::api_host::ApiHost;
|
||||
use crate::utils::constants::{APPLICATION_JSON, USER_AGENT};
|
||||
use crate::utils::filter_bad_response::filter_bad_response;
|
||||
use artcraft_api_defs::generate::image::generate_flux_1_schnell_text_to_image::{GenerateFlux1SchnellTextToImageRequest, GenerateFlux1SchnellTextToImageResponse, GENERATE_FLUX_1_SCHNELL_TEXT_TO_IMAGE_PATH};
|
||||
use errors::AnyhowResult;
|
||||
use log::{debug, info};
|
||||
use reqwest::Client;
|
||||
|
||||
@@ -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;
|
||||
+45
@@ -11,6 +11,9 @@ pub struct Gemini25FlashEditArgs<'a, R: IntoUrl> {
|
||||
pub prompt: &'a str,
|
||||
pub image_urls: Vec<String>,
|
||||
pub num_images: Gemini25FlashEditNumImages,
|
||||
|
||||
// Optional
|
||||
pub aspect_ratio: Option<Gemini25FlashEditAspectRatio>,
|
||||
|
||||
// Fulfillment
|
||||
pub webhook_url: R,
|
||||
@@ -25,6 +28,26 @@ pub enum Gemini25FlashEditNumImages {
|
||||
Four,
|
||||
}
|
||||
|
||||
/// auto, 21:9, 16:9, 3:2, 4:3, 5:4, 1:1, 4:5, 3:4, 2:3, 9:16
|
||||
/// Default is "auto"
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub enum Gemini25FlashEditAspectRatio {
|
||||
// Automatic (default)
|
||||
Auto,
|
||||
// Square
|
||||
OneByOne,
|
||||
// Wide
|
||||
FiveByFour,
|
||||
FourByThree,
|
||||
ThreeByTwo,
|
||||
SixteenByNine,
|
||||
TwentyOneByNine,
|
||||
// Tall
|
||||
FourByFive,
|
||||
ThreeByFour,
|
||||
TwoByThree,
|
||||
NineBySixteen, // NB: No NineByTwentyOne ?
|
||||
}
|
||||
|
||||
pub async fn enqueue_gemini_25_flash_edit_webhook<R: IntoUrl>(
|
||||
args: Gemini25FlashEditArgs<'_, R>
|
||||
@@ -37,9 +60,30 @@ pub async fn enqueue_gemini_25_flash_edit_webhook<R: IntoUrl>(
|
||||
Gemini25FlashEditNumImages::Four => 4,
|
||||
};
|
||||
|
||||
let aspect_ratio = args.aspect_ratio
|
||||
.map(|aspect_ratio| match aspect_ratio {
|
||||
// Auto
|
||||
Gemini25FlashEditAspectRatio::Auto => "auto",
|
||||
// Square
|
||||
Gemini25FlashEditAspectRatio::OneByOne => "1:1",
|
||||
// Wide
|
||||
Gemini25FlashEditAspectRatio::FiveByFour => "5:4",
|
||||
Gemini25FlashEditAspectRatio::FourByThree => "4:3",
|
||||
Gemini25FlashEditAspectRatio::ThreeByTwo => "3:2",
|
||||
Gemini25FlashEditAspectRatio::SixteenByNine => "16:9",
|
||||
Gemini25FlashEditAspectRatio::TwentyOneByNine => "21:9",
|
||||
// Tall
|
||||
Gemini25FlashEditAspectRatio::FourByFive => "4:5",
|
||||
Gemini25FlashEditAspectRatio::ThreeByFour => "3:4",
|
||||
Gemini25FlashEditAspectRatio::TwoByThree => "2:3",
|
||||
Gemini25FlashEditAspectRatio::NineBySixteen => "9:16",
|
||||
})
|
||||
.map(|aspect_ratio| aspect_ratio.to_string());
|
||||
|
||||
let request = Gemini25FlashImageEditInput {
|
||||
prompt: args.prompt.to_string(),
|
||||
image_urls: args.image_urls,
|
||||
aspect_ratio,
|
||||
// Constants
|
||||
num_images: Some(num_images),
|
||||
output_format: Some("png".to_string()),
|
||||
@@ -75,6 +119,7 @@ mod tests {
|
||||
TREX_SKELETON_IMAGE_URL.to_string(),
|
||||
],
|
||||
num_images: Gemini25FlashEditNumImages::Two,
|
||||
aspect_ratio: None,
|
||||
prompt: "add the ghost to the image of the t-rex skeleton, make it look spooky but friendly",
|
||||
api_key: &api_key,
|
||||
webhook_url: "https://example.com/webhook",
|
||||
|
||||
+182
@@ -0,0 +1,182 @@
|
||||
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::gpt_image::gpt_image_1p5_image_edit::{gpt_image_1p5_image_edit, GptImage1p5ImageEditInput};
|
||||
use fal::webhook::WebhookResponse;
|
||||
use reqwest::IntoUrl;
|
||||
|
||||
pub struct EnqueueGptImage1p5EditImageArgs<'a, R: IntoUrl> {
|
||||
// Request required
|
||||
pub prompt: &'a str,
|
||||
pub image_urls: Vec<String>,
|
||||
pub num_images: EnqueueGptImage1p5EditImageNumImages,
|
||||
|
||||
// Optional args
|
||||
pub mask_image_url: Option<String>,
|
||||
pub image_size: Option<EnqueueGptImage1p5EditImageSize>,
|
||||
pub background: Option<EnqueueGptImage1p5EditImageBackground>,
|
||||
pub quality: Option<EnqueueGptImage1p5EditImageQuality>,
|
||||
pub input_fidelity: Option<EnqueueGptImage1p5EditImageInputFidelity>,
|
||||
pub output_format: Option<EnqueueGptImage1p5EditImageOutputFormat>,
|
||||
|
||||
// Fulfillment
|
||||
pub webhook_url: R,
|
||||
pub api_key: &'a FalApiKey,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub enum EnqueueGptImage1p5EditImageNumImages {
|
||||
One,
|
||||
Two,
|
||||
Three,
|
||||
Four,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub enum EnqueueGptImage1p5EditImageSize {
|
||||
/// 1024x1024
|
||||
Square,
|
||||
/// 1536x1024
|
||||
Wide,
|
||||
/// 1024x1536
|
||||
Tall,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub enum EnqueueGptImage1p5EditImageBackground {
|
||||
Auto,
|
||||
Transparent,
|
||||
Opaque,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub enum EnqueueGptImage1p5EditImageQuality {
|
||||
Low,
|
||||
Medium,
|
||||
High,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub enum EnqueueGptImage1p5EditImageInputFidelity {
|
||||
Low,
|
||||
High,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub enum EnqueueGptImage1p5EditImageOutputFormat {
|
||||
Jpeg,
|
||||
Png,
|
||||
Webp,
|
||||
}
|
||||
|
||||
pub async fn enqueue_gpt_image_1p5_image_edit_webhook<R: IntoUrl>(
|
||||
args: EnqueueGptImage1p5EditImageArgs<'_, R>
|
||||
) -> Result<WebhookResponse, FalErrorPlus> {
|
||||
|
||||
let num_images = match args.num_images {
|
||||
EnqueueGptImage1p5EditImageNumImages::One => 1,
|
||||
EnqueueGptImage1p5EditImageNumImages::Two => 2,
|
||||
EnqueueGptImage1p5EditImageNumImages::Three => 3,
|
||||
EnqueueGptImage1p5EditImageNumImages::Four => 4,
|
||||
};
|
||||
|
||||
let image_size = args.image_size
|
||||
.map(|s| match s {
|
||||
EnqueueGptImage1p5EditImageSize::Square => "1024x1024",
|
||||
EnqueueGptImage1p5EditImageSize::Wide => "1536x1024",
|
||||
EnqueueGptImage1p5EditImageSize::Tall => "1024x1536",
|
||||
})
|
||||
.map(|resolution| resolution.to_string());
|
||||
|
||||
let background = args.background
|
||||
.map(|s| match s {
|
||||
EnqueueGptImage1p5EditImageBackground::Auto => "auto",
|
||||
EnqueueGptImage1p5EditImageBackground::Transparent => "transparent",
|
||||
EnqueueGptImage1p5EditImageBackground::Opaque => "opaque",
|
||||
})
|
||||
.map(|aspect_ratio| aspect_ratio.to_string());
|
||||
|
||||
let quality = args.quality
|
||||
.map(|s| match s {
|
||||
EnqueueGptImage1p5EditImageQuality::Low => "low",
|
||||
EnqueueGptImage1p5EditImageQuality::Medium => "medium",
|
||||
EnqueueGptImage1p5EditImageQuality::High => "high",
|
||||
})
|
||||
.map(|aspect_ratio| aspect_ratio.to_string());
|
||||
|
||||
let input_fidelity = args.input_fidelity
|
||||
.map(|s| match s {
|
||||
EnqueueGptImage1p5EditImageInputFidelity::Low => "low",
|
||||
EnqueueGptImage1p5EditImageInputFidelity::High => "high",
|
||||
})
|
||||
.map(|aspect_ratio| aspect_ratio.to_string());
|
||||
|
||||
let output_format = args.output_format
|
||||
.map(|s| match s {
|
||||
EnqueueGptImage1p5EditImageOutputFormat::Jpeg => "jpeg",
|
||||
EnqueueGptImage1p5EditImageOutputFormat::Png => "png",
|
||||
EnqueueGptImage1p5EditImageOutputFormat::Webp => "webp",
|
||||
})
|
||||
.map(|aspect_ratio| aspect_ratio.to_string())
|
||||
.unwrap_or_else(|| "png".to_string());
|
||||
|
||||
let request = GptImage1p5ImageEditInput {
|
||||
prompt: args.prompt.to_string(),
|
||||
image_urls: args.image_urls,
|
||||
num_images: Some(num_images),
|
||||
output_format: Some(output_format),
|
||||
// Optionals
|
||||
mask_image_url: args.mask_image_url,
|
||||
image_size,
|
||||
background,
|
||||
quality,
|
||||
input_fidelity,
|
||||
};
|
||||
|
||||
let result = gpt_image_1p5_image_edit(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::image::edit::enqueue_gpt_image_1p5_edit_image_webhook::{enqueue_gpt_image_1p5_image_edit_webhook, EnqueueGptImage1p5EditImageArgs, EnqueueGptImage1p5EditImageNumImages};
|
||||
use errors::AnyhowResult;
|
||||
use std::fs::read_to_string;
|
||||
use test_data::web::image_urls::{ERNEST_SCARED_STUPID_IMAGE_URL, GHOST_IMAGE_URL, TREX_SKELETON_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 = EnqueueGptImage1p5EditImageArgs {
|
||||
image_urls: vec![
|
||||
GHOST_IMAGE_URL.to_string(),
|
||||
TREX_SKELETON_IMAGE_URL.to_string(),
|
||||
ERNEST_SCARED_STUPID_IMAGE_URL.to_string(),
|
||||
],
|
||||
prompt: "add the ghost and scared man to the image of the t-rex skeleton, make it look spooky but friendly",
|
||||
num_images: EnqueueGptImage1p5EditImageNumImages::Two,
|
||||
mask_image_url: None,
|
||||
image_size: None,
|
||||
background: None,
|
||||
quality: None,
|
||||
input_fidelity: None,
|
||||
api_key: &api_key,
|
||||
webhook_url: "https://example.com/webhook",
|
||||
output_format: None,
|
||||
};
|
||||
|
||||
let result = enqueue_gpt_image_1p5_image_edit_webhook(args).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
+7
-3
@@ -30,15 +30,17 @@ pub enum EnqueueNanoBananaProEditImageNumImages {
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub enum EnqueueNanoBananaProEditImageResolution {
|
||||
OneK,
|
||||
OneK, // Default value "1K"
|
||||
TwoK,
|
||||
FourK,
|
||||
}
|
||||
|
||||
/// 21:9, 16:9, 3:2, 4:3, 5:4, 1:1, 4:5, 3:4, 2:3, 9:16
|
||||
/// Default is "1:1"
|
||||
/// auto, 21:9, 16:9, 3:2, 4:3, 5:4, 1:1, 4:5, 3:4, 2:3, 9:16
|
||||
/// Default is "auto"
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub enum EnqueueNanoBananaProEditImageAspectRatio {
|
||||
// Automatic (default)
|
||||
Auto,
|
||||
// Square
|
||||
OneByOne,
|
||||
// Wide
|
||||
@@ -75,6 +77,8 @@ pub async fn enqueue_nano_banana_pro_image_edit_webhook<R: IntoUrl>(
|
||||
|
||||
let aspect_ratio = args.aspect_ratio
|
||||
.map(|aspect_ratio| match aspect_ratio {
|
||||
// Auto
|
||||
EnqueueNanoBananaProEditImageAspectRatio::Auto => "auto",
|
||||
// Square
|
||||
EnqueueNanoBananaProEditImageAspectRatio::OneByOne => "1:1",
|
||||
// Wide
|
||||
|
||||
@@ -3,6 +3,7 @@ pub mod enqueue_bytedance_seedream_v4p5_edit_image_webhook;
|
||||
pub mod enqueue_flux_pro_kontext_max_edit_webhook;
|
||||
pub mod enqueue_gemini_25_flash_edit_webhook;
|
||||
pub mod enqueue_gpt_image_1_edit_image_webhook;
|
||||
pub mod enqueue_gpt_image_1p5_edit_image_webhook;
|
||||
pub mod enqueue_nano_banana_pro_edit_image_webhook;
|
||||
pub mod enqueue_qwen_image_edit_webhook;
|
||||
pub mod enqueue_seededit_v3_edit_webhook;
|
||||
+2
-1
@@ -13,10 +13,11 @@ pub struct FluxPro11Args<'a, U: IntoUrl> {
|
||||
pub num_images: FluxPro11NumImages,
|
||||
}
|
||||
|
||||
// TODO(bt,2026-01-01): This seems to disagree between Fal.ai and Fal.rs client libraries.
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub enum FluxPro11AspectRatio {
|
||||
Square, // 1:1
|
||||
SquareHd, // 1:1
|
||||
SquareHd, // 1:1 (TODO: Is this in the API? I checked recently and don't see it.)
|
||||
LandscapeFourByThree, // 4:3
|
||||
LandscapeSixteenByNine, // 16:9
|
||||
PortraitThreeByFour, // 3:4
|
||||
|
||||
+8
@@ -16,10 +16,14 @@ pub struct FluxPro11UltraArgs<'a, U: IntoUrl> {
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub enum FluxPro11UltraAspectRatio {
|
||||
Square, // 1:1
|
||||
LandscapeThreeByTwo, // 3:2
|
||||
LandscapeFourByThree, // 4:3
|
||||
LandscapeSixteenByNine, // 16:9
|
||||
LandscapeTwentyOneByNine, // 21:9
|
||||
PortraitTwoByThree, // 2:3
|
||||
PortraitThreeByFour, // 3:4
|
||||
PortraitNineBySixteen, // 9:16
|
||||
PortraitNineByTwentyOne, // 9:21
|
||||
//Custom { width: u32, height: u32 }, // TODO
|
||||
}
|
||||
|
||||
@@ -44,10 +48,14 @@ pub async fn enqueue_flux_pro_11_ultra_text_to_image_webhook<U: IntoUrl>(
|
||||
|
||||
let aspect_ratio = match args.aspect_ratio {
|
||||
FluxPro11UltraAspectRatio::Square => AspectRatioProperty::Property_1_1,
|
||||
FluxPro11UltraAspectRatio::LandscapeThreeByTwo => AspectRatioProperty::Property_3_2,
|
||||
FluxPro11UltraAspectRatio::LandscapeFourByThree => AspectRatioProperty::Property_4_3,
|
||||
FluxPro11UltraAspectRatio::LandscapeSixteenByNine => AspectRatioProperty::Property_16_9,
|
||||
FluxPro11UltraAspectRatio::LandscapeTwentyOneByNine => AspectRatioProperty::Property_21_9,
|
||||
FluxPro11UltraAspectRatio::PortraitTwoByThree => AspectRatioProperty::Property_2_3,
|
||||
FluxPro11UltraAspectRatio::PortraitThreeByFour => AspectRatioProperty::Property_3_4,
|
||||
FluxPro11UltraAspectRatio::PortraitNineBySixteen => AspectRatioProperty::Property_9_16,
|
||||
FluxPro11UltraAspectRatio::PortraitNineByTwentyOne => AspectRatioProperty::Property_9_21,
|
||||
};
|
||||
|
||||
let request = FluxProUltraTextToImageInput {
|
||||
|
||||
+121
@@ -0,0 +1,121 @@
|
||||
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::prelude::fal_ai::gemini_25_flash_image::text_to_image::gemini_25_flash_text_to_image;
|
||||
use fal::prelude::fal_ai::gemini_25_flash_image::text_to_image::Gemini25FlashTextToImageInput;
|
||||
use fal::webhook::WebhookResponse;
|
||||
use reqwest::IntoUrl;
|
||||
|
||||
pub struct Gemini25FlashTextToImageArgs<'a, R: IntoUrl> {
|
||||
// Request required
|
||||
pub prompt: &'a str,
|
||||
pub num_images: Gemini25FlashTextToImageNumImages,
|
||||
|
||||
// Optional
|
||||
pub aspect_ratio: Option<Gemini25FlashTextToImageAspectRatio>,
|
||||
|
||||
// Fulfillment
|
||||
pub webhook_url: R,
|
||||
pub api_key: &'a FalApiKey,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub enum Gemini25FlashTextToImageNumImages {
|
||||
One,
|
||||
Two,
|
||||
Three,
|
||||
Four,
|
||||
}
|
||||
|
||||
/// 21:9, 16:9, 3:2, 4:3, 5:4, 1:1, 4:5, 3:4, 2:3, 9:16
|
||||
/// Default is "1:1"
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub enum Gemini25FlashTextToImageAspectRatio {
|
||||
// Square
|
||||
OneByOne,
|
||||
// Wide
|
||||
FiveByFour,
|
||||
FourByThree,
|
||||
ThreeByTwo,
|
||||
SixteenByNine,
|
||||
TwentyOneByNine,
|
||||
// Tall
|
||||
FourByFive,
|
||||
ThreeByFour,
|
||||
TwoByThree,
|
||||
NineBySixteen, // NB: No NineByTwentyOne ?
|
||||
}
|
||||
|
||||
pub async fn enqueue_gemini_25_flash_text_to_image_webhook<R: IntoUrl>(
|
||||
args: Gemini25FlashTextToImageArgs<'_, R>
|
||||
) -> Result<WebhookResponse, FalErrorPlus> {
|
||||
|
||||
let num_images = match args.num_images {
|
||||
Gemini25FlashTextToImageNumImages::One => 1,
|
||||
Gemini25FlashTextToImageNumImages::Two => 2,
|
||||
Gemini25FlashTextToImageNumImages::Three => 3,
|
||||
Gemini25FlashTextToImageNumImages::Four => 4,
|
||||
};
|
||||
|
||||
let aspect_ratio = args.aspect_ratio
|
||||
.map(|aspect_ratio| match aspect_ratio {
|
||||
// Square
|
||||
Gemini25FlashTextToImageAspectRatio::OneByOne => "1:1",
|
||||
// Wide
|
||||
Gemini25FlashTextToImageAspectRatio::FiveByFour => "5:4",
|
||||
Gemini25FlashTextToImageAspectRatio::FourByThree => "4:3",
|
||||
Gemini25FlashTextToImageAspectRatio::ThreeByTwo => "3:2",
|
||||
Gemini25FlashTextToImageAspectRatio::SixteenByNine => "16:9",
|
||||
Gemini25FlashTextToImageAspectRatio::TwentyOneByNine => "21:9",
|
||||
// Tall
|
||||
Gemini25FlashTextToImageAspectRatio::FourByFive => "4:5",
|
||||
Gemini25FlashTextToImageAspectRatio::ThreeByFour => "3:4",
|
||||
Gemini25FlashTextToImageAspectRatio::TwoByThree => "2:3",
|
||||
Gemini25FlashTextToImageAspectRatio::NineBySixteen => "9:16",
|
||||
})
|
||||
.map(|aspect_ratio| aspect_ratio.to_string());
|
||||
|
||||
let request = Gemini25FlashTextToImageInput {
|
||||
prompt: args.prompt.to_string(),
|
||||
aspect_ratio,
|
||||
// Constants
|
||||
num_images: Some(num_images),
|
||||
output_format: Some("png".to_string()),
|
||||
};
|
||||
|
||||
let result = gemini_25_flash_text_to_image(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::image::text::enqueue_gemini_25_flash_text_to_image_webhook::{enqueue_gemini_25_flash_text_to_image_webhook, Gemini25FlashTextToImageArgs, Gemini25FlashTextToImageNumImages};
|
||||
use errors::AnyhowResult;
|
||||
use std::fs::read_to_string;
|
||||
|
||||
#[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 = Gemini25FlashTextToImageArgs {
|
||||
prompt: "a warrior on the field of battle, final fantasy style, lots of hungry raptors rushing the heroes",
|
||||
num_images: Gemini25FlashTextToImageNumImages::Two,
|
||||
aspect_ratio: None,
|
||||
api_key: &api_key,
|
||||
webhook_url: "https://example.com/webhook",
|
||||
};
|
||||
|
||||
let result = enqueue_gemini_25_flash_text_to_image_webhook(args).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
+164
@@ -0,0 +1,164 @@
|
||||
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::gpt_image::gpt_image_1p5_text_to_image::{gpt_image_1p5_text_to_image, GptImage1p5TextToImageInput};
|
||||
use fal::webhook::WebhookResponse;
|
||||
use reqwest::IntoUrl;
|
||||
|
||||
pub struct EnqueueGptImage1p5TextToImageArgs<'a, R: IntoUrl> {
|
||||
// Request required
|
||||
pub prompt: &'a str,
|
||||
pub num_images: EnqueueGptImage1p5TextToImageNumImages,
|
||||
|
||||
// Optional args
|
||||
pub image_size: Option<EnqueueGptImage1p5TextToImageSize>,
|
||||
pub background: Option<EnqueueGptImage1p5TextToImageBackground>,
|
||||
pub quality: Option<EnqueueGptImage1p5TextToImageQuality>,
|
||||
pub output_format: Option<EnqueueGptImage1p5TextToImageOutputFormat>,
|
||||
|
||||
// Fulfillment
|
||||
pub webhook_url: R,
|
||||
pub api_key: &'a FalApiKey,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub enum EnqueueGptImage1p5TextToImageNumImages {
|
||||
One,
|
||||
Two,
|
||||
Three,
|
||||
Four,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub enum EnqueueGptImage1p5TextToImageSize {
|
||||
/// 1024x1024
|
||||
Square,
|
||||
/// 1536x1024
|
||||
Wide,
|
||||
/// 1024x1536
|
||||
Tall,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub enum EnqueueGptImage1p5TextToImageBackground {
|
||||
Auto,
|
||||
Transparent,
|
||||
Opaque,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub enum EnqueueGptImage1p5TextToImageQuality {
|
||||
Low,
|
||||
Medium,
|
||||
High,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub enum EnqueueGptImage1p5TextToImageOutputFormat {
|
||||
Jpeg,
|
||||
Png,
|
||||
Webp,
|
||||
}
|
||||
|
||||
|
||||
pub async fn enqueue_gpt_image_1p5_text_to_image_webhook<R: IntoUrl>(
|
||||
args: EnqueueGptImage1p5TextToImageArgs<'_, R>
|
||||
) -> Result<WebhookResponse, FalErrorPlus> {
|
||||
|
||||
|
||||
let num_images = match args.num_images {
|
||||
EnqueueGptImage1p5TextToImageNumImages::One => 1,
|
||||
EnqueueGptImage1p5TextToImageNumImages::Two => 2,
|
||||
EnqueueGptImage1p5TextToImageNumImages::Three => 3,
|
||||
EnqueueGptImage1p5TextToImageNumImages::Four => 4,
|
||||
};
|
||||
|
||||
let image_size = args.image_size
|
||||
.map(|s| match s {
|
||||
EnqueueGptImage1p5TextToImageSize::Square => "1024x1024",
|
||||
EnqueueGptImage1p5TextToImageSize::Wide => "1536x1024",
|
||||
EnqueueGptImage1p5TextToImageSize::Tall => "1024x1536",
|
||||
})
|
||||
.map(|resolution| resolution.to_string());
|
||||
|
||||
let background = args.background
|
||||
.map(|s| match s {
|
||||
EnqueueGptImage1p5TextToImageBackground::Auto => "auto",
|
||||
EnqueueGptImage1p5TextToImageBackground::Transparent => "transparent",
|
||||
EnqueueGptImage1p5TextToImageBackground::Opaque => "opaque",
|
||||
})
|
||||
.map(|aspect_ratio| aspect_ratio.to_string());
|
||||
|
||||
let quality = args.quality
|
||||
.map(|s| match s {
|
||||
EnqueueGptImage1p5TextToImageQuality::Low => "low",
|
||||
EnqueueGptImage1p5TextToImageQuality::Medium => "medium",
|
||||
EnqueueGptImage1p5TextToImageQuality::High => "high",
|
||||
})
|
||||
.map(|aspect_ratio| aspect_ratio.to_string());
|
||||
|
||||
let output_format = args.output_format
|
||||
.map(|s| match s {
|
||||
EnqueueGptImage1p5TextToImageOutputFormat::Jpeg => "jpeg",
|
||||
EnqueueGptImage1p5TextToImageOutputFormat::Png => "png",
|
||||
EnqueueGptImage1p5TextToImageOutputFormat::Webp => "webp",
|
||||
})
|
||||
.map(|aspect_ratio| aspect_ratio.to_string())
|
||||
.unwrap_or_else(|| "png".to_string());
|
||||
|
||||
let num_images = match args.num_images {
|
||||
EnqueueGptImage1p5TextToImageNumImages::One => 1,
|
||||
EnqueueGptImage1p5TextToImageNumImages::Two => 2,
|
||||
EnqueueGptImage1p5TextToImageNumImages::Three => 3,
|
||||
EnqueueGptImage1p5TextToImageNumImages::Four => 4,
|
||||
};
|
||||
|
||||
let request = GptImage1p5TextToImageInput {
|
||||
prompt: args.prompt.to_string(),
|
||||
num_images: Some(num_images),
|
||||
output_format: Some(output_format),
|
||||
// Optionals
|
||||
image_size,
|
||||
background,
|
||||
quality,
|
||||
};
|
||||
|
||||
let result = gpt_image_1p5_text_to_image(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::image::text::enqueue_gpt_image_1p5_text_to_image_webhook::{enqueue_gpt_image_1p5_text_to_image_webhook, EnqueueGptImage1p5TextToImageArgs, EnqueueGptImage1p5TextToImageNumImages};
|
||||
use errors::AnyhowResult;
|
||||
use std::fs::read_to_string;
|
||||
|
||||
#[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 = EnqueueGptImage1p5TextToImageArgs {
|
||||
prompt: "an anime girl riding on the back of a t-rex",
|
||||
num_images: EnqueueGptImage1p5TextToImageNumImages::Two,
|
||||
image_size: None,
|
||||
background: None,
|
||||
quality: None,
|
||||
api_key: &api_key,
|
||||
webhook_url: "https://example.com/webhook",
|
||||
output_format: None,
|
||||
};
|
||||
|
||||
let result = enqueue_gpt_image_1p5_text_to_image_webhook(args).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,5 @@
|
||||
pub mod enqueue_bytedance_seedream_v4_text_to_image_webhook;
|
||||
pub mod enqueue_bytedance_seedream_v4p5_text_to_image_webhook;
|
||||
pub mod enqueue_gemini_25_flash_text_to_image_webhook;
|
||||
pub mod enqueue_gpt_image_1p5_text_to_image_webhook;
|
||||
pub mod enqueue_nano_banana_pro_text_to_image_webhook;
|
||||
-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
-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::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
|
||||
@@ -36,7 +43,6 @@ pub enum EnqueueVeo3p1FastFirstLastFrameImageToVideoDurationSeconds {
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub enum EnqueueVeo3p1FastFirstLastFrameImageToVideoAspectRatio {
|
||||
Auto,
|
||||
Square,
|
||||
SixteenByNine,
|
||||
NineBySixteen,
|
||||
}
|
||||
@@ -47,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> {
|
||||
@@ -62,7 +93,6 @@ pub async fn enqueue_veo_3p1_fast_first_last_frame_image_to_video_webhook<R: Int
|
||||
let aspect_ratio = args.aspect_ratio
|
||||
.map(|aspect_ratio| match aspect_ratio {
|
||||
EnqueueVeo3p1FastFirstLastFrameImageToVideoAspectRatio::Auto => "auto",
|
||||
EnqueueVeo3p1FastFirstLastFrameImageToVideoAspectRatio::Square => "1:1",
|
||||
EnqueueVeo3p1FastFirstLastFrameImageToVideoAspectRatio::SixteenByNine => "16:9",
|
||||
EnqueueVeo3p1FastFirstLastFrameImageToVideoAspectRatio::NineBySixteen => "9:16",
|
||||
})
|
||||
|
||||
+27
-2
@@ -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;
|
||||
@@ -33,7 +34,6 @@ pub enum EnqueueVeo3p1FastImageToVideoDurationSeconds {
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub enum EnqueueVeo3p1FastImageToVideoAspectRatio {
|
||||
Auto,
|
||||
Square,
|
||||
SixteenByNine,
|
||||
NineBySixteen,
|
||||
}
|
||||
@@ -44,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> {
|
||||
@@ -59,7 +85,6 @@ pub async fn enqueue_veo_3p1_fast_image_to_video_webhook<R: IntoUrl>(
|
||||
let aspect_ratio = args.aspect_ratio
|
||||
.map(|aspect_ratio| match aspect_ratio {
|
||||
EnqueueVeo3p1FastImageToVideoAspectRatio::Auto => "auto",
|
||||
EnqueueVeo3p1FastImageToVideoAspectRatio::Square => "1:1",
|
||||
EnqueueVeo3p1FastImageToVideoAspectRatio::SixteenByNine => "16:9",
|
||||
EnqueueVeo3p1FastImageToVideoAspectRatio::NineBySixteen => "9:16",
|
||||
})
|
||||
|
||||
+26
-2
@@ -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;
|
||||
@@ -36,7 +37,6 @@ pub enum EnqueueVeo3p1FirstLastFrameImageToVideoDurationSeconds {
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub enum EnqueueVeo3p1FirstLastFrameImageToVideoAspectRatio {
|
||||
Auto,
|
||||
Square,
|
||||
SixteenByNine,
|
||||
NineBySixteen,
|
||||
}
|
||||
@@ -47,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> {
|
||||
@@ -62,7 +87,6 @@ pub async fn enqueue_veo_3p1_first_last_frame_image_to_video_webhook<R: IntoUrl>
|
||||
let aspect_ratio = args.aspect_ratio
|
||||
.map(|aspect_ratio| match aspect_ratio {
|
||||
EnqueueVeo3p1FirstLastFrameImageToVideoAspectRatio::Auto => "auto",
|
||||
EnqueueVeo3p1FirstLastFrameImageToVideoAspectRatio::Square => "1:1",
|
||||
EnqueueVeo3p1FirstLastFrameImageToVideoAspectRatio::SixteenByNine => "16:9",
|
||||
EnqueueVeo3p1FirstLastFrameImageToVideoAspectRatio::NineBySixteen => "9:16",
|
||||
})
|
||||
|
||||
+26
-2
@@ -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;
|
||||
@@ -33,7 +34,6 @@ pub enum EnqueueVeo3p1ImageToVideoDurationSeconds {
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub enum EnqueueVeo3p1ImageToVideoAspectRatio {
|
||||
Auto,
|
||||
Square,
|
||||
SixteenByNine,
|
||||
NineBySixteen,
|
||||
}
|
||||
@@ -44,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> {
|
||||
@@ -59,7 +84,6 @@ pub async fn enqueue_veo_3p1_image_to_video_webhook<R: IntoUrl>(
|
||||
let aspect_ratio = args.aspect_ratio
|
||||
.map(|aspect_ratio| match aspect_ratio {
|
||||
EnqueueVeo3p1ImageToVideoAspectRatio::Auto => "auto",
|
||||
EnqueueVeo3p1ImageToVideoAspectRatio::Square => "1:1",
|
||||
EnqueueVeo3p1ImageToVideoAspectRatio::SixteenByNine => "16:9",
|
||||
EnqueueVeo3p1ImageToVideoAspectRatio::NineBySixteen => "9:16",
|
||||
})
|
||||
|
||||
@@ -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]
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user