mirror of
https://github.com/storytold/artcraft.git
synced 2026-11-05 13:54:53 -05:00
Compare commits
102
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
75386b433b | ||
|
|
fdc14a4acf | ||
|
|
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 | ||
|
|
a1d08460db | ||
|
|
ba538142e4 | ||
|
|
99fd390bc5 | ||
|
|
cd283bf8de | ||
|
|
0c6021e95c | ||
|
|
859d295014 | ||
|
|
e116df736f | ||
|
|
9bb4a78111 | ||
|
|
edb0330cec | ||
|
|
146f152732 | ||
|
|
32b939a67e | ||
|
|
866a409ff1 | ||
|
|
92731d87ef | ||
|
|
f574d66897 | ||
|
|
21a30bde2d | ||
|
|
6a1eeaffb4 | ||
|
|
00065f120e | ||
|
|
e30dc03466 | ||
|
|
029789ac78 | ||
|
|
7b5550c546 | ||
|
|
0f0bd4c907 | ||
|
|
63f77a4b11 | ||
|
|
5ba6a3e226 | ||
|
|
7683fe34b7 | ||
|
|
1d41fa8174 | ||
|
|
8fe15839b0 | ||
|
|
1eada337f9 | ||
|
|
016f5107e0 | ||
|
|
224ce3f80b | ||
|
|
73a6d0494b | ||
|
|
0a5f220a7d | ||
|
|
d852d24226 | ||
|
|
e7da946e2d | ||
|
|
ea80dab67b | ||
|
|
957bb7ff2f | ||
|
|
2f41f345a2 | ||
|
|
53225932fb | ||
|
|
0c353f2a8c | ||
|
|
6036be1e01 | ||
|
|
197e91d4c6 | ||
|
|
5ddd23a8ff | ||
|
|
31eb00cfa7 | ||
|
|
7d6f262ffe | ||
|
|
33162e4337 | ||
|
|
5afb1db285 | ||
|
|
a511e0e0a4 | ||
|
|
0598aadaa1 | ||
|
|
06c6ff4b36 | ||
|
|
5bc759e511 | ||
|
|
02eafa96cc | ||
|
|
706bdb8d83 | ||
|
|
57534c7a77 | ||
|
|
2228a01466 | ||
|
|
262d931c3d | ||
|
|
a7826f8be3 | ||
|
|
dc0e59b8ce | ||
|
|
e1f3458c54 | ||
|
|
e385b3c6c3 | ||
|
|
dbedb25930 | ||
|
|
952d1e936c | ||
|
|
88799a482d |
@@ -1,6 +1,7 @@
|
||||
SERVER_ENVIRONMENT=development
|
||||
|
||||
DATABASE_URL=mysql://storyteller:password@localhost/storyteller
|
||||
#DATABASE_URL=sqlite:/tmp/tasks.sqlite
|
||||
#DATABASE_URL=mysql://root:root@localhost/storyteller
|
||||
#DATABASE_URL=mysql://root@localhost/storyteller
|
||||
|
||||
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"db_name": "SQLite",
|
||||
"query": "\n UPDATE tasks\n SET\n task_status = ?,\n on_complete_batch_token = ?,\n on_complete_primary_media_file_token = ?,\n on_complete_primary_media_file_class = ?,\n on_complete_primary_media_file_cdn_url = ?,\n on_complete_primary_media_file_thumbnail_url_template = ?\n WHERE\n provider = ?\n AND provider_job_id = ?\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Right": 8
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "1e3758851fc8451670f86a9432009b73bdb81de4a08dcaa96a86afb5f9be7a68"
|
||||
}
|
||||
+68
@@ -0,0 +1,68 @@
|
||||
{
|
||||
"db_name": "SQLite",
|
||||
"query": "\n SELECT\n id,\n task_status,\n task_type,\n model_type,\n provider,\n provider_job_id,\n frontend_caller,\n frontend_subscriber_id,\n frontend_subscriber_payload\n FROM tasks\n WHERE\n provider = ?\n AND provider_job_id = ?\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"name": "id",
|
||||
"ordinal": 0,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "task_status",
|
||||
"ordinal": 1,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "task_type",
|
||||
"ordinal": 2,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "model_type",
|
||||
"ordinal": 3,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "provider",
|
||||
"ordinal": 4,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "provider_job_id",
|
||||
"ordinal": 5,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "frontend_caller",
|
||||
"ordinal": 6,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "frontend_subscriber_id",
|
||||
"ordinal": 7,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "frontend_subscriber_payload",
|
||||
"ordinal": 8,
|
||||
"type_info": "Text"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Right": 2
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true
|
||||
]
|
||||
},
|
||||
"hash": "38ff2df237b16f077865711dd81261de1b5fa05a9787679ce9ccd4ac576b1069"
|
||||
}
|
||||
Generated
+585
-184
File diff suppressed because it is too large
Load Diff
+17
-4
@@ -12,11 +12,13 @@ members = [
|
||||
"crates/api_clients/fal_client",
|
||||
"crates/api_clients/google_drive_common",
|
||||
"crates/api_clients/google_sign_in",
|
||||
"crates/api_clients/grok_client",
|
||||
"crates/api_clients/midjourney_client",
|
||||
"crates/api_clients/openai_sora_client",
|
||||
"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",
|
||||
@@ -43,6 +45,7 @@ members = [
|
||||
"crates/lib/caching/memory_store",
|
||||
"crates/lib/caching/redis_caching",
|
||||
"crates/lib/cloudflare_errors",
|
||||
"crates/lib/cloudflare_mitigation",
|
||||
"crates/lib/concurrency",
|
||||
"crates/lib/cookie_store",
|
||||
"crates/lib/cookies",
|
||||
@@ -65,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",
|
||||
@@ -186,6 +190,7 @@ artcraft_api_defs = { path = "crates/api_clients/artcraft/artcraft_api_defs" }
|
||||
build_metadata = { path = "crates/lib/build_metadata" }
|
||||
cloud_storage = { path = "crates/api_clients/cloud_storage" }
|
||||
cloudflare_errors = { path = "crates/lib/cloudflare_errors" }
|
||||
cloudflare_mitigation = { path = "crates/lib/cloudflare_mitigation" }
|
||||
component_traits = { path = "crates/service/plugins/component_traits" }
|
||||
cookie_store = { path = "crates/lib/cookie_store" }
|
||||
crockford = { path = "crates/lib/data/crockford" }
|
||||
@@ -195,6 +200,8 @@ errors = { path = "crates/lib/errors" }
|
||||
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" }
|
||||
@@ -213,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
|
||||
@@ -229,22 +237,26 @@ actix-web-actors = "4.3.1+deprecated"
|
||||
actix-web-lab = "0.20.2"
|
||||
anyhow = "1.0.98"
|
||||
base64 = "0.22.1"
|
||||
bstr = "1.12.1"
|
||||
bytes = "1.10.1"
|
||||
chrono = { version = "0.4.41", features = ["serde"] }
|
||||
cookie = "0.18.1"
|
||||
env = "1.0.1"
|
||||
env_logger = "0.11.8"
|
||||
futures = { version = "0.3.31" }
|
||||
hex-literal = "1.0.0"
|
||||
indexmap = "2.9.0"
|
||||
log = "0.4.27"
|
||||
once_cell = "1.21.3"
|
||||
rand = "0.9.1"
|
||||
regex = "1.11.1"
|
||||
reqwest = { version = "0.12.23", features = ["json", "cookies"] }
|
||||
regex = "1.12.2"
|
||||
reqwest = { version = "0.12.23", default-features = false, features = [ "charset", "http2", "system-proxy", "json", "cookies", "rustls-tls" ] }
|
||||
reqwest-websocket = { version = "0.5.0" }
|
||||
serde = { version = "1.0.219", features = ["derive"] }
|
||||
serde_derive = "1.0.219"
|
||||
serde_json = "1.0.140"
|
||||
serde_yaml = "0.9.34+deprecated"
|
||||
sha2 = "0.11.0-rc.2"
|
||||
sha3 = "0.10.8"
|
||||
tauri = { version = "2.8.3", features = ["unstable", "devtools"] } # NB: "devtools" prevents App Store distribution; "ubstable" is needed for window access
|
||||
tauri-plugin-dialog = "2.3.3"
|
||||
@@ -257,6 +269,7 @@ thiserror = "2.0.12"
|
||||
tokio = { version = "1.45.1", features = ["macros"] }
|
||||
toml = "0.8.22"
|
||||
url = { version = "2.5.4", features = ["serde"] }
|
||||
uuid = "1.18.1"
|
||||
|
||||
# NB: wreq gets past fingerprinting, but we have to deal with platform-specific build issues.
|
||||
#
|
||||
@@ -268,5 +281,5 @@ url = { version = "2.5.4", features = ["serde"] }
|
||||
# choco install pkgconfiglite -y
|
||||
# choco install llvm -y
|
||||
# choco install nasm -y
|
||||
wreq = { version = "6.0.0-rc.20", features = ["json", "multipart", "cookies"] }
|
||||
wreq-util = "3.0.0-rc.5"
|
||||
wreq = { version = "6.0.0-rc.20", features = ["json", "multipart", "cookies", "ws", "stream"] }
|
||||
wreq-util = "3.0.0-rc.6"
|
||||
|
||||
@@ -152,6 +152,7 @@ CREATE TABLE media_files (
|
||||
-- * `vmd`
|
||||
-- * `pmx`
|
||||
-- * `csv`
|
||||
-- * `spz` Gaussian splats
|
||||
media_type VARCHAR(16) NOT NULL,
|
||||
|
||||
-- Broad class of media:
|
||||
|
||||
@@ -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,8 +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 remove_image_background;
|
||||
pub mod multi_function;
|
||||
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,
|
||||
}
|
||||
@@ -0,0 +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,
|
||||
}
|
||||
+73
@@ -0,0 +1,73 @@
|
||||
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_PRO_MULTI_FUNCTION_IMAGE_GEN_PATH: &str = "/v1/generate/image/multi_function/nano_banana_pro";
|
||||
|
||||
#[derive(Serialize, Deserialize, ToSchema, Debug)]
|
||||
pub struct NanoBananaProMultiFunctionImageGenRequest {
|
||||
/// 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<NanoBananaProMultiFunctionImageGenNumImages>,
|
||||
|
||||
/// Resolution of the image to generate. Default is OneK (1K).
|
||||
pub resolution: Option<NanoBananaProMultiFunctionImageGenImageResolution>,
|
||||
|
||||
/// Aspect ratio of the images to generate. Default is "1:1"
|
||||
pub aspect_ratio: Option<NanoBananaProMultiFunctionImageGenAspectRatio>,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize, ToSchema)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum NanoBananaProMultiFunctionImageGenNumImages {
|
||||
One, // Default
|
||||
Two,
|
||||
Three,
|
||||
Four,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize, ToSchema)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum NanoBananaProMultiFunctionImageGenImageResolution {
|
||||
OneK,
|
||||
TwoK,
|
||||
FourK,
|
||||
}
|
||||
|
||||
#[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 for text-to-image
|
||||
// Wide
|
||||
FiveByFour,
|
||||
FourByThree,
|
||||
ThreeByTwo,
|
||||
SixteenByNine,
|
||||
TwentyOneByNine,
|
||||
// Tall
|
||||
FourByFive,
|
||||
ThreeByFour,
|
||||
TwoByThree,
|
||||
NineBySixteen, // NB: No NineByTwentyOne ?
|
||||
}
|
||||
|
||||
|
||||
#[derive(Serialize, Deserialize, ToSchema)]
|
||||
pub struct NanoBananaProMultiFunctionImageGenResponse {
|
||||
pub success: bool,
|
||||
pub inference_job_token: InferenceJobToken,
|
||||
}
|
||||
+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,8 +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 remove_image_background;
|
||||
pub mod multi_function;
|
||||
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?)
|
||||
}
|
||||
+5
@@ -0,0 +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?)
|
||||
}
|
||||
+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_pro_multi_function_image_gen::{NanoBananaProMultiFunctionImageGenRequest, NanoBananaProMultiFunctionImageGenResponse, NANO_BANANA_PRO_MULTI_FUNCTION_IMAGE_GEN_PATH};
|
||||
|
||||
pub async fn nano_banana_pro_multi_function_image_gen(
|
||||
api_host: &ApiHost,
|
||||
maybe_creds: Option<&StorytellerCredentialSet>,
|
||||
request: NanoBananaProMultiFunctionImageGenRequest,
|
||||
) -> Result<NanoBananaProMultiFunctionImageGenResponse, StorytellerError> {
|
||||
Ok(basic_json_post_request(
|
||||
api_host,
|
||||
NANO_BANANA_PRO_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;
|
||||
|
||||
@@ -36,7 +36,7 @@ env_logger = "0.11.3"
|
||||
async-trait = "0.1.75"
|
||||
# The "never-encode-slash" feature seems to have been removed:
|
||||
# https://github.com/durch/rust-s3/pull/254
|
||||
rust-s3 = { version = "0.36.0-beta.2" }
|
||||
rust-s3 = { version = "0.36.0-beta.2", default-features = false, features = ["fail-on-err", "tags", "tokio-rustls-tls"] }
|
||||
|
||||
# Runtime
|
||||
tokio.workspace = true
|
||||
|
||||
@@ -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;
|
||||
+143
@@ -0,0 +1,143 @@
|
||||
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::seedream::bytedance_seedream_v4_edit_image::{bytedance_seedream_v4_edit_image, BytedanceSeedreamV4EditImageInput};
|
||||
use fal::webhook::WebhookResponse;
|
||||
use reqwest::IntoUrl;
|
||||
|
||||
pub struct EnqueueBytedanceSeedreamV4EditImageArgs<'a, R: IntoUrl> {
|
||||
// Request required
|
||||
pub prompt: &'a str,
|
||||
pub image_urls: Vec<String>,
|
||||
|
||||
// Optional args
|
||||
pub num_images: Option<EnqueueBytedanceSeedreamV4EditImageNumImages>,
|
||||
pub max_images: Option<EnqueueBytedanceSeedreamV4EditImageMaxImages>,
|
||||
pub image_size: Option<EnqueueBytedanceSeedreamV4EditImageSize>,
|
||||
|
||||
// Fulfillment
|
||||
pub webhook_url: R,
|
||||
pub api_key: &'a FalApiKey,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub enum EnqueueBytedanceSeedreamV4EditImageNumImages {
|
||||
One,
|
||||
Two,
|
||||
Three,
|
||||
Four,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub enum EnqueueBytedanceSeedreamV4EditImageMaxImages {
|
||||
One,
|
||||
Two,
|
||||
Three,
|
||||
Four,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub enum EnqueueBytedanceSeedreamV4EditImageSize {
|
||||
// Square
|
||||
Square,
|
||||
SquareHd,
|
||||
// Tall
|
||||
PortraitFourThree,
|
||||
PortraitSixteenNine,
|
||||
// Wide
|
||||
LandscapeFourThree,
|
||||
LandscapeSixteenNine,
|
||||
// Auto
|
||||
Auto,
|
||||
Auto2k,
|
||||
Auto4k,
|
||||
}
|
||||
|
||||
pub async fn enqueue_bytedance_seedream_v4_edit_image_webhook<R: IntoUrl>(
|
||||
args: EnqueueBytedanceSeedreamV4EditImageArgs<'_, R>
|
||||
) -> Result<WebhookResponse, FalErrorPlus> {
|
||||
|
||||
let num_images = args.num_images
|
||||
.map(|num_images| match num_images {
|
||||
EnqueueBytedanceSeedreamV4EditImageNumImages::One => 1,
|
||||
EnqueueBytedanceSeedreamV4EditImageNumImages::Two => 2,
|
||||
EnqueueBytedanceSeedreamV4EditImageNumImages::Three => 3,
|
||||
EnqueueBytedanceSeedreamV4EditImageNumImages::Four => 4,
|
||||
});
|
||||
|
||||
let max_images = args.max_images
|
||||
.map(|num_images| match num_images {
|
||||
EnqueueBytedanceSeedreamV4EditImageMaxImages::One => 1,
|
||||
EnqueueBytedanceSeedreamV4EditImageMaxImages::Two => 2,
|
||||
EnqueueBytedanceSeedreamV4EditImageMaxImages::Three => 3,
|
||||
EnqueueBytedanceSeedreamV4EditImageMaxImages::Four => 4,
|
||||
});
|
||||
|
||||
let image_size = args.image_size
|
||||
.map(|image_size| match image_size {
|
||||
EnqueueBytedanceSeedreamV4EditImageSize::Square => "square",
|
||||
EnqueueBytedanceSeedreamV4EditImageSize::SquareHd => "square_hd",
|
||||
EnqueueBytedanceSeedreamV4EditImageSize::PortraitFourThree => "portrait_4_3",
|
||||
EnqueueBytedanceSeedreamV4EditImageSize::PortraitSixteenNine => "portrait_16_9",
|
||||
EnqueueBytedanceSeedreamV4EditImageSize::LandscapeFourThree => "landscape_4_3",
|
||||
EnqueueBytedanceSeedreamV4EditImageSize::LandscapeSixteenNine => "landscape_16_9",
|
||||
EnqueueBytedanceSeedreamV4EditImageSize::Auto => "auto",
|
||||
EnqueueBytedanceSeedreamV4EditImageSize::Auto2k => "auto_2K",
|
||||
EnqueueBytedanceSeedreamV4EditImageSize::Auto4k => "auto_4K",
|
||||
})
|
||||
.map(|resolution| resolution.to_string());
|
||||
|
||||
let request = BytedanceSeedreamV4EditImageInput {
|
||||
prompt: args.prompt.to_string(),
|
||||
image_urls: args.image_urls,
|
||||
// Optionals
|
||||
num_images,
|
||||
max_images,
|
||||
image_size,
|
||||
// Constants
|
||||
enhance_prompt_mode: Some("standard".to_string()),
|
||||
enable_safety_checker: Some(false),
|
||||
};
|
||||
|
||||
let result = bytedance_seedream_v4_edit_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::edit::enqueue_bytedance_seedream_v4_edit_image_webhook::{enqueue_bytedance_seedream_v4_edit_image_webhook, EnqueueBytedanceSeedreamV4EditImageArgs, EnqueueBytedanceSeedreamV4EditImageMaxImages, EnqueueBytedanceSeedreamV4EditImageNumImages, EnqueueBytedanceSeedreamV4EditImageSize};
|
||||
use errors::AnyhowResult;
|
||||
use std::fs::read_to_string;
|
||||
use test_data::web::image_urls::{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 = EnqueueBytedanceSeedreamV4EditImageArgs {
|
||||
image_urls: vec![
|
||||
GHOST_IMAGE_URL.to_string(),
|
||||
TREX_SKELETON_IMAGE_URL.to_string(),
|
||||
],
|
||||
prompt: "add the ghost to the image of the t-rex skeleton, make it look spooky but friendly",
|
||||
num_images: Some(EnqueueBytedanceSeedreamV4EditImageNumImages::Two),
|
||||
max_images: Some(EnqueueBytedanceSeedreamV4EditImageMaxImages::Two),
|
||||
image_size: Some(EnqueueBytedanceSeedreamV4EditImageSize::Auto2k),
|
||||
api_key: &api_key,
|
||||
webhook_url: "https://example.com/webhook",
|
||||
};
|
||||
|
||||
let result = enqueue_bytedance_seedream_v4_edit_image_webhook(args).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
+142
@@ -0,0 +1,142 @@
|
||||
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::seedream::bytedance_seedream_v4p5_edit_image::{bytedance_seedream_v4p5_edit_image, BytedanceSeedreamV4p5EditImageInput};
|
||||
use fal::webhook::WebhookResponse;
|
||||
use reqwest::IntoUrl;
|
||||
|
||||
pub struct EnqueueBytedanceSeedreamV4p5EditImageArgs<'a, R: IntoUrl> {
|
||||
// Request required
|
||||
pub prompt: &'a str,
|
||||
pub image_urls: Vec<String>,
|
||||
|
||||
// Optional args
|
||||
pub num_images: Option<EnqueueBytedanceSeedreamV4p5EditImageNumImages>,
|
||||
pub max_images: Option<EnqueueBytedanceSeedreamV4p5EditImageMaxImages>,
|
||||
pub image_size: Option<EnqueueBytedanceSeedreamV4p5EditImageSize>,
|
||||
|
||||
// Fulfillment
|
||||
pub webhook_url: R,
|
||||
pub api_key: &'a FalApiKey,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub enum EnqueueBytedanceSeedreamV4p5EditImageNumImages {
|
||||
One,
|
||||
Two,
|
||||
Three,
|
||||
Four,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub enum EnqueueBytedanceSeedreamV4p5EditImageMaxImages {
|
||||
One,
|
||||
Two,
|
||||
Three,
|
||||
Four,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub enum EnqueueBytedanceSeedreamV4p5EditImageSize {
|
||||
// Square
|
||||
Square,
|
||||
SquareHd,
|
||||
// Tall
|
||||
PortraitFourThree,
|
||||
PortraitSixteenNine,
|
||||
// Wide
|
||||
LandscapeFourThree,
|
||||
LandscapeSixteenNine,
|
||||
// Auto
|
||||
Auto2k,
|
||||
Auto4k,
|
||||
}
|
||||
|
||||
pub async fn enqueue_bytedance_seedream_v4p5_edit_image_webhook<R: IntoUrl>(
|
||||
args: EnqueueBytedanceSeedreamV4p5EditImageArgs<'_, R>
|
||||
) -> Result<WebhookResponse, FalErrorPlus> {
|
||||
|
||||
let num_images = args.num_images
|
||||
.map(|num_images| match num_images {
|
||||
EnqueueBytedanceSeedreamV4p5EditImageNumImages::One => 1,
|
||||
EnqueueBytedanceSeedreamV4p5EditImageNumImages::Two => 2,
|
||||
EnqueueBytedanceSeedreamV4p5EditImageNumImages::Three => 3,
|
||||
EnqueueBytedanceSeedreamV4p5EditImageNumImages::Four => 4,
|
||||
});
|
||||
|
||||
let max_images = args.max_images
|
||||
.map(|num_images| match num_images {
|
||||
EnqueueBytedanceSeedreamV4p5EditImageMaxImages::One => 1,
|
||||
EnqueueBytedanceSeedreamV4p5EditImageMaxImages::Two => 2,
|
||||
EnqueueBytedanceSeedreamV4p5EditImageMaxImages::Three => 3,
|
||||
EnqueueBytedanceSeedreamV4p5EditImageMaxImages::Four => 4,
|
||||
});
|
||||
|
||||
let image_size = args.image_size
|
||||
.map(|image_size| match image_size {
|
||||
EnqueueBytedanceSeedreamV4p5EditImageSize::Square => "square",
|
||||
EnqueueBytedanceSeedreamV4p5EditImageSize::SquareHd => "square_hd",
|
||||
EnqueueBytedanceSeedreamV4p5EditImageSize::PortraitFourThree => "portrait_4_3",
|
||||
EnqueueBytedanceSeedreamV4p5EditImageSize::PortraitSixteenNine => "portrait_16_9",
|
||||
EnqueueBytedanceSeedreamV4p5EditImageSize::LandscapeFourThree => "landscape_4_3",
|
||||
EnqueueBytedanceSeedreamV4p5EditImageSize::LandscapeSixteenNine => "landscape_16_9",
|
||||
EnqueueBytedanceSeedreamV4p5EditImageSize::Auto2k => "auto_2K",
|
||||
EnqueueBytedanceSeedreamV4p5EditImageSize::Auto4k => "auto_4K",
|
||||
})
|
||||
.map(|resolution| resolution.to_string());
|
||||
|
||||
let request = BytedanceSeedreamV4p5EditImageInput {
|
||||
prompt: args.prompt.to_string(),
|
||||
image_urls: args.image_urls,
|
||||
// Optionals
|
||||
num_images,
|
||||
max_images,
|
||||
image_size,
|
||||
// Constants
|
||||
enable_safety_checker: Some(false),
|
||||
seed: None,
|
||||
};
|
||||
|
||||
let result = bytedance_seedream_v4p5_edit_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::edit::enqueue_bytedance_seedream_v4p5_edit_image_webhook::{enqueue_bytedance_seedream_v4p5_edit_image_webhook, EnqueueBytedanceSeedreamV4p5EditImageArgs, EnqueueBytedanceSeedreamV4p5EditImageMaxImages, EnqueueBytedanceSeedreamV4p5EditImageNumImages, EnqueueBytedanceSeedreamV4p5EditImageSize};
|
||||
use errors::AnyhowResult;
|
||||
use std::fs::read_to_string;
|
||||
use test_data::web::image_urls::{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 secret = read_to_string("/home/bt/Artcraft/credentials/fal_api_key.txt")?;
|
||||
|
||||
let api_key = FalApiKey::from_str(&secret);
|
||||
|
||||
let args = EnqueueBytedanceSeedreamV4p5EditImageArgs {
|
||||
image_urls: vec![
|
||||
GHOST_IMAGE_URL.to_string(),
|
||||
TREX_SKELETON_IMAGE_URL.to_string(),
|
||||
],
|
||||
prompt: "add the ghost to the image of the t-rex skeleton, make it look spooky but friendly",
|
||||
num_images: Some(EnqueueBytedanceSeedreamV4p5EditImageNumImages::Two),
|
||||
max_images: Some(EnqueueBytedanceSeedreamV4p5EditImageMaxImages::Two),
|
||||
image_size: Some(EnqueueBytedanceSeedreamV4p5EditImageSize::Auto2k),
|
||||
api_key: &api_key,
|
||||
webhook_url: "https://example.com/webhook",
|
||||
};
|
||||
|
||||
let result = enqueue_bytedance_seedream_v4p5_edit_image_webhook(args).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
+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(())
|
||||
}
|
||||
}
|
||||
+151
@@ -0,0 +1,151 @@
|
||||
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::nano_banana_pro::nano_banana_pro_image_edit::{nano_banana_pro_image_edit, NanoBananaProImageEditInput};
|
||||
use fal::webhook::WebhookResponse;
|
||||
use reqwest::IntoUrl;
|
||||
|
||||
pub struct EnqueueNanoBananaProEditImageArgs<'a, R: IntoUrl> {
|
||||
// Request required
|
||||
pub prompt: &'a str,
|
||||
pub image_urls: Vec<String>,
|
||||
pub num_images: EnqueueNanoBananaProEditImageNumImages,
|
||||
|
||||
// Optional args
|
||||
pub resolution: Option<EnqueueNanoBananaProEditImageResolution>,
|
||||
pub aspect_ratio: Option<EnqueueNanoBananaProEditImageAspectRatio>,
|
||||
|
||||
// Fulfillment
|
||||
pub webhook_url: R,
|
||||
pub api_key: &'a FalApiKey,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub enum EnqueueNanoBananaProEditImageNumImages {
|
||||
One,
|
||||
Two,
|
||||
Three,
|
||||
Four,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub enum EnqueueNanoBananaProEditImageResolution {
|
||||
OneK, // Default value "1K"
|
||||
TwoK,
|
||||
FourK,
|
||||
}
|
||||
|
||||
/// 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
|
||||
FiveByFour,
|
||||
FourByThree,
|
||||
ThreeByTwo,
|
||||
SixteenByNine,
|
||||
TwentyOneByNine,
|
||||
// Tall
|
||||
FourByFive,
|
||||
ThreeByFour,
|
||||
TwoByThree,
|
||||
NineBySixteen, // NB: No NineByTwentyOne ?
|
||||
}
|
||||
|
||||
pub async fn enqueue_nano_banana_pro_image_edit_webhook<R: IntoUrl>(
|
||||
args: EnqueueNanoBananaProEditImageArgs<'_, R>
|
||||
) -> Result<WebhookResponse, FalErrorPlus> {
|
||||
|
||||
let num_images = match args.num_images {
|
||||
EnqueueNanoBananaProEditImageNumImages::One => 1,
|
||||
EnqueueNanoBananaProEditImageNumImages::Two => 2,
|
||||
EnqueueNanoBananaProEditImageNumImages::Three => 3,
|
||||
EnqueueNanoBananaProEditImageNumImages::Four => 4,
|
||||
};
|
||||
|
||||
let resolution = args.resolution
|
||||
.map(|resolution| match resolution {
|
||||
EnqueueNanoBananaProEditImageResolution::OneK => "1K",
|
||||
EnqueueNanoBananaProEditImageResolution::TwoK => "2K",
|
||||
EnqueueNanoBananaProEditImageResolution::FourK => "4K",
|
||||
})
|
||||
.map(|resolution| resolution.to_string());
|
||||
|
||||
let aspect_ratio = args.aspect_ratio
|
||||
.map(|aspect_ratio| match aspect_ratio {
|
||||
// Auto
|
||||
EnqueueNanoBananaProEditImageAspectRatio::Auto => "auto",
|
||||
// Square
|
||||
EnqueueNanoBananaProEditImageAspectRatio::OneByOne => "1:1",
|
||||
// Wide
|
||||
EnqueueNanoBananaProEditImageAspectRatio::FiveByFour => "5:4",
|
||||
EnqueueNanoBananaProEditImageAspectRatio::FourByThree => "4:3",
|
||||
EnqueueNanoBananaProEditImageAspectRatio::ThreeByTwo => "3:2",
|
||||
EnqueueNanoBananaProEditImageAspectRatio::SixteenByNine => "16:9",
|
||||
EnqueueNanoBananaProEditImageAspectRatio::TwentyOneByNine => "21:9",
|
||||
// Tall
|
||||
EnqueueNanoBananaProEditImageAspectRatio::FourByFive => "4:5",
|
||||
EnqueueNanoBananaProEditImageAspectRatio::ThreeByFour => "3:4",
|
||||
EnqueueNanoBananaProEditImageAspectRatio::TwoByThree => "2:3",
|
||||
EnqueueNanoBananaProEditImageAspectRatio::NineBySixteen => "9:16",
|
||||
})
|
||||
.map(|aspect_ratio| aspect_ratio.to_string());
|
||||
|
||||
let request = NanoBananaProImageEditInput {
|
||||
prompt: args.prompt.to_string(),
|
||||
image_urls: args.image_urls,
|
||||
num_images: Some(num_images),
|
||||
// Optionals
|
||||
aspect_ratio,
|
||||
resolution,
|
||||
// Constants
|
||||
output_format: Some("png".to_string()),
|
||||
};
|
||||
|
||||
let result = nano_banana_pro_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_nano_banana_pro_edit_image_webhook::{enqueue_nano_banana_pro_image_edit_webhook, EnqueueNanoBananaProEditImageArgs, EnqueueNanoBananaProEditImageAspectRatio, EnqueueNanoBananaProEditImageNumImages, EnqueueNanoBananaProEditImageResolution};
|
||||
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 = EnqueueNanoBananaProEditImageArgs {
|
||||
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: EnqueueNanoBananaProEditImageNumImages::Two,
|
||||
aspect_ratio: Some(EnqueueNanoBananaProEditImageAspectRatio::SixteenByNine),
|
||||
resolution: Some(EnqueueNanoBananaProEditImageResolution::TwoK),
|
||||
api_key: &api_key,
|
||||
webhook_url: "https://example.com/webhook",
|
||||
};
|
||||
|
||||
let result = enqueue_nano_banana_pro_image_edit_webhook(args).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,9 @@
|
||||
pub mod enqueue_bytedance_seedream_v4_edit_image_webhook;
|
||||
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 {
|
||||
|
||||
@@ -6,3 +6,4 @@ pub mod enqueue_flux_pro_11_ultra_text_to_image_webhook;
|
||||
pub mod enqueue_gpt_image_1_text_to_image_webhook;
|
||||
pub mod infill;
|
||||
pub mod remove_background_rembg_webhook;
|
||||
pub mod text;
|
||||
|
||||
+137
@@ -0,0 +1,137 @@
|
||||
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::seedream::bytedance_seedream_v4_text_to_image::BytedanceSeedreamV4TextToImageInput;
|
||||
use fal::prelude::fal_ai::bytedance::seedream::bytedance_seedream_v4_text_to_image::bytedance_seedream_v4_text_to_image;
|
||||
use fal::webhook::WebhookResponse;
|
||||
use reqwest::IntoUrl;
|
||||
|
||||
pub struct EnqueueBytedanceSeedreamV4TextToImageArgs<'a, R: IntoUrl> {
|
||||
// Request required
|
||||
pub prompt: &'a str,
|
||||
|
||||
// Optional args
|
||||
pub num_images: Option<EnqueueBytedanceSeedreamV4TextToImageNumImages>,
|
||||
pub max_images: Option<EnqueueBytedanceSeedreamV4TextToImageMaxImages>,
|
||||
pub image_size: Option<EnqueueBytedanceSeedreamV4TextToImageSize>,
|
||||
|
||||
// Fulfillment
|
||||
pub webhook_url: R,
|
||||
pub api_key: &'a FalApiKey,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub enum EnqueueBytedanceSeedreamV4TextToImageNumImages {
|
||||
One,
|
||||
Two,
|
||||
Three,
|
||||
Four,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub enum EnqueueBytedanceSeedreamV4TextToImageMaxImages {
|
||||
One,
|
||||
Two,
|
||||
Three,
|
||||
Four,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub enum EnqueueBytedanceSeedreamV4TextToImageSize {
|
||||
// Square
|
||||
Square,
|
||||
SquareHd,
|
||||
// Tall
|
||||
PortraitFourThree,
|
||||
PortraitSixteenNine,
|
||||
// Wide
|
||||
LandscapeFourThree,
|
||||
LandscapeSixteenNine,
|
||||
// Auto
|
||||
Auto,
|
||||
Auto2k,
|
||||
Auto4k,
|
||||
}
|
||||
|
||||
pub async fn enqueue_bytedance_seedream_v4_text_to_image_webhook<R: IntoUrl>(
|
||||
args: EnqueueBytedanceSeedreamV4TextToImageArgs<'_, R>
|
||||
) -> Result<WebhookResponse, FalErrorPlus> {
|
||||
|
||||
let num_images = args.num_images
|
||||
.map(|num_images| match num_images {
|
||||
EnqueueBytedanceSeedreamV4TextToImageNumImages::One => 1,
|
||||
EnqueueBytedanceSeedreamV4TextToImageNumImages::Two => 2,
|
||||
EnqueueBytedanceSeedreamV4TextToImageNumImages::Three => 3,
|
||||
EnqueueBytedanceSeedreamV4TextToImageNumImages::Four => 4,
|
||||
});
|
||||
|
||||
let max_images = args.max_images
|
||||
.map(|num_images| match num_images {
|
||||
EnqueueBytedanceSeedreamV4TextToImageMaxImages::One => 1,
|
||||
EnqueueBytedanceSeedreamV4TextToImageMaxImages::Two => 2,
|
||||
EnqueueBytedanceSeedreamV4TextToImageMaxImages::Three => 3,
|
||||
EnqueueBytedanceSeedreamV4TextToImageMaxImages::Four => 4,
|
||||
});
|
||||
|
||||
let image_size = args.image_size
|
||||
.map(|image_size| match image_size {
|
||||
EnqueueBytedanceSeedreamV4TextToImageSize::Square => "square",
|
||||
EnqueueBytedanceSeedreamV4TextToImageSize::SquareHd => "square_hd",
|
||||
EnqueueBytedanceSeedreamV4TextToImageSize::PortraitFourThree => "portrait_4_3",
|
||||
EnqueueBytedanceSeedreamV4TextToImageSize::PortraitSixteenNine => "portrait_16_9",
|
||||
EnqueueBytedanceSeedreamV4TextToImageSize::LandscapeFourThree => "landscape_4_3",
|
||||
EnqueueBytedanceSeedreamV4TextToImageSize::LandscapeSixteenNine => "landscape_16_9",
|
||||
EnqueueBytedanceSeedreamV4TextToImageSize::Auto => "auto",
|
||||
EnqueueBytedanceSeedreamV4TextToImageSize::Auto2k => "auto_2K",
|
||||
EnqueueBytedanceSeedreamV4TextToImageSize::Auto4k => "auto_4K",
|
||||
})
|
||||
.map(|resolution| resolution.to_string());
|
||||
|
||||
let request = BytedanceSeedreamV4TextToImageInput {
|
||||
prompt: args.prompt.to_string(),
|
||||
// Optionals
|
||||
num_images,
|
||||
max_images,
|
||||
image_size,
|
||||
// Constants
|
||||
enhance_prompt_mode: Some("standard".to_string()),
|
||||
enable_safety_checker: Some(false),
|
||||
};
|
||||
|
||||
let result = bytedance_seedream_v4_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_bytedance_seedream_v4_text_to_image_webhook::{enqueue_bytedance_seedream_v4_text_to_image_webhook, EnqueueBytedanceSeedreamV4TextToImageArgs, EnqueueBytedanceSeedreamV4TextToImageMaxImages, EnqueueBytedanceSeedreamV4TextToImageNumImages, EnqueueBytedanceSeedreamV4TextToImageSize};
|
||||
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 = EnqueueBytedanceSeedreamV4TextToImageArgs {
|
||||
prompt: "an anime girl is riding a t-rex in the forest",
|
||||
num_images: Some(EnqueueBytedanceSeedreamV4TextToImageNumImages::Two),
|
||||
max_images: Some(EnqueueBytedanceSeedreamV4TextToImageMaxImages::Two),
|
||||
image_size: Some(EnqueueBytedanceSeedreamV4TextToImageSize::LandscapeSixteenNine),
|
||||
api_key: &api_key,
|
||||
webhook_url: "https://example.com/webhook",
|
||||
};
|
||||
|
||||
let result = enqueue_bytedance_seedream_v4_text_to_image_webhook(args).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
+134
@@ -0,0 +1,134 @@
|
||||
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::seedream::bytedance_seedream_v4p5_text_to_image::{bytedance_seedream_v4p5_text_to_image, BytedanceSeedreamV4p5TextToImageInput};
|
||||
use fal::webhook::WebhookResponse;
|
||||
use reqwest::IntoUrl;
|
||||
|
||||
pub struct EnqueueBytedanceSeedreamV4p5TextToImageArgs<'a, R: IntoUrl> {
|
||||
// Request required
|
||||
pub prompt: &'a str,
|
||||
|
||||
// Optional args
|
||||
pub num_images: Option<EnqueueBytedanceSeedreamV4p5TextToImageNumImages>,
|
||||
pub max_images: Option<EnqueueBytedanceSeedreamV4p5TextToImageMaxImages>,
|
||||
pub image_size: Option<EnqueueBytedanceSeedreamV4p5TextToImageSize>,
|
||||
|
||||
// Fulfillment
|
||||
pub webhook_url: R,
|
||||
pub api_key: &'a FalApiKey,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub enum EnqueueBytedanceSeedreamV4p5TextToImageNumImages {
|
||||
One,
|
||||
Two,
|
||||
Three,
|
||||
Four,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub enum EnqueueBytedanceSeedreamV4p5TextToImageMaxImages {
|
||||
One,
|
||||
Two,
|
||||
Three,
|
||||
Four,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub enum EnqueueBytedanceSeedreamV4p5TextToImageSize {
|
||||
// Square
|
||||
Square,
|
||||
SquareHd,
|
||||
// Tall
|
||||
PortraitFourThree,
|
||||
PortraitSixteenNine,
|
||||
// Wide
|
||||
LandscapeFourThree,
|
||||
LandscapeSixteenNine,
|
||||
// Auto
|
||||
Auto2k,
|
||||
Auto4k,
|
||||
}
|
||||
|
||||
pub async fn enqueue_bytedance_seedream_v4p5_text_to_image_webhook<R: IntoUrl>(
|
||||
args: EnqueueBytedanceSeedreamV4p5TextToImageArgs<'_, R>
|
||||
) -> Result<WebhookResponse, FalErrorPlus> {
|
||||
|
||||
let num_images = args.num_images
|
||||
.map(|num_images| match num_images {
|
||||
EnqueueBytedanceSeedreamV4p5TextToImageNumImages::One => 1,
|
||||
EnqueueBytedanceSeedreamV4p5TextToImageNumImages::Two => 2,
|
||||
EnqueueBytedanceSeedreamV4p5TextToImageNumImages::Three => 3,
|
||||
EnqueueBytedanceSeedreamV4p5TextToImageNumImages::Four => 4,
|
||||
});
|
||||
|
||||
let max_images = args.max_images
|
||||
.map(|num_images| match num_images {
|
||||
EnqueueBytedanceSeedreamV4p5TextToImageMaxImages::One => 1,
|
||||
EnqueueBytedanceSeedreamV4p5TextToImageMaxImages::Two => 2,
|
||||
EnqueueBytedanceSeedreamV4p5TextToImageMaxImages::Three => 3,
|
||||
EnqueueBytedanceSeedreamV4p5TextToImageMaxImages::Four => 4,
|
||||
});
|
||||
|
||||
let image_size = args.image_size
|
||||
.map(|image_size| match image_size {
|
||||
EnqueueBytedanceSeedreamV4p5TextToImageSize::Square => "square",
|
||||
EnqueueBytedanceSeedreamV4p5TextToImageSize::SquareHd => "square_hd",
|
||||
EnqueueBytedanceSeedreamV4p5TextToImageSize::PortraitFourThree => "portrait_4_3",
|
||||
EnqueueBytedanceSeedreamV4p5TextToImageSize::PortraitSixteenNine => "portrait_16_9",
|
||||
EnqueueBytedanceSeedreamV4p5TextToImageSize::LandscapeFourThree => "landscape_4_3",
|
||||
EnqueueBytedanceSeedreamV4p5TextToImageSize::LandscapeSixteenNine => "landscape_16_9",
|
||||
EnqueueBytedanceSeedreamV4p5TextToImageSize::Auto2k => "auto_2K",
|
||||
EnqueueBytedanceSeedreamV4p5TextToImageSize::Auto4k => "auto_4K",
|
||||
})
|
||||
.map(|resolution| resolution.to_string());
|
||||
|
||||
let request = BytedanceSeedreamV4p5TextToImageInput {
|
||||
prompt: args.prompt.to_string(),
|
||||
// Optionals
|
||||
num_images,
|
||||
max_images,
|
||||
image_size,
|
||||
// Constants
|
||||
enable_safety_checker: Some(false),
|
||||
seed: None,
|
||||
};
|
||||
|
||||
let result = bytedance_seedream_v4p5_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_bytedance_seedream_v4p5_text_to_image_webhook::{enqueue_bytedance_seedream_v4p5_text_to_image_webhook, EnqueueBytedanceSeedreamV4p5TextToImageArgs, EnqueueBytedanceSeedreamV4p5TextToImageMaxImages, EnqueueBytedanceSeedreamV4p5TextToImageNumImages, EnqueueBytedanceSeedreamV4p5TextToImageSize};
|
||||
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("/home/bt/Artcraft/credentials/fal_api_key.txt")?;
|
||||
|
||||
let api_key = FalApiKey::from_str(&secret);
|
||||
|
||||
let args = EnqueueBytedanceSeedreamV4p5TextToImageArgs {
|
||||
prompt: "an anime girl is riding a t-rex in the forest",
|
||||
num_images: Some(EnqueueBytedanceSeedreamV4p5TextToImageNumImages::Two),
|
||||
max_images: Some(EnqueueBytedanceSeedreamV4p5TextToImageMaxImages::Two),
|
||||
image_size: Some(EnqueueBytedanceSeedreamV4p5TextToImageSize::LandscapeSixteenNine),
|
||||
api_key: &api_key,
|
||||
webhook_url: "https://example.com/webhook",
|
||||
};
|
||||
|
||||
let result = enqueue_bytedance_seedream_v4p5_text_to_image_webhook(args).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
+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(())
|
||||
}
|
||||
}
|
||||
+139
@@ -0,0 +1,139 @@
|
||||
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::nano_banana_pro::nano_banana_pro_text_to_image::{nano_banana_pro_text_to_image, NanoBananaProTextToImageInput};
|
||||
use fal::webhook::WebhookResponse;
|
||||
use reqwest::IntoUrl;
|
||||
|
||||
pub struct EnqueueNanoBananaProTextToImageArgs<'a, R: IntoUrl> {
|
||||
// Request required
|
||||
pub prompt: &'a str,
|
||||
pub num_images: EnqueueNanoBananaProTextToImageNumImages,
|
||||
|
||||
// Optional args
|
||||
pub resolution: Option<EnqueueNanoBananaProTextToImageResolution>,
|
||||
pub aspect_ratio: Option<EnqueueNanoBananaProTextToImageAspectRatio>,
|
||||
|
||||
// Fulfillment
|
||||
pub webhook_url: R,
|
||||
pub api_key: &'a FalApiKey,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub enum EnqueueNanoBananaProTextToImageNumImages {
|
||||
One,
|
||||
Two,
|
||||
Three,
|
||||
Four,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub enum EnqueueNanoBananaProTextToImageResolution {
|
||||
OneK,
|
||||
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"
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub enum EnqueueNanoBananaProTextToImageAspectRatio {
|
||||
// Square
|
||||
OneByOne,
|
||||
// Wide
|
||||
FiveByFour,
|
||||
FourByThree,
|
||||
ThreeByTwo,
|
||||
SixteenByNine,
|
||||
TwentyOneByNine,
|
||||
// Tall
|
||||
FourByFive,
|
||||
ThreeByFour,
|
||||
TwoByThree,
|
||||
NineBySixteen, // NB: No NineByTwentyOne ?
|
||||
}
|
||||
|
||||
pub async fn enqueue_nano_banana_pro_text_to_image_webhook<R: IntoUrl>(
|
||||
args: EnqueueNanoBananaProTextToImageArgs<'_, R>
|
||||
) -> Result<WebhookResponse, FalErrorPlus> {
|
||||
|
||||
let num_images = match args.num_images {
|
||||
EnqueueNanoBananaProTextToImageNumImages::One => 1,
|
||||
EnqueueNanoBananaProTextToImageNumImages::Two => 2,
|
||||
EnqueueNanoBananaProTextToImageNumImages::Three => 3,
|
||||
EnqueueNanoBananaProTextToImageNumImages::Four => 4,
|
||||
};
|
||||
|
||||
let resolution = args.resolution
|
||||
.map(|resolution| match resolution {
|
||||
EnqueueNanoBananaProTextToImageResolution::OneK => "1K",
|
||||
EnqueueNanoBananaProTextToImageResolution::TwoK => "2K",
|
||||
EnqueueNanoBananaProTextToImageResolution::FourK => "4K",
|
||||
})
|
||||
.map(|resolution| resolution.to_string());
|
||||
|
||||
let aspect_ratio = args.aspect_ratio
|
||||
.map(|aspect_ratio| match aspect_ratio {
|
||||
// Square
|
||||
EnqueueNanoBananaProTextToImageAspectRatio::OneByOne => "1:1",
|
||||
// Wide
|
||||
EnqueueNanoBananaProTextToImageAspectRatio::FiveByFour => "5:4",
|
||||
EnqueueNanoBananaProTextToImageAspectRatio::FourByThree => "4:3",
|
||||
EnqueueNanoBananaProTextToImageAspectRatio::ThreeByTwo => "3:2",
|
||||
EnqueueNanoBananaProTextToImageAspectRatio::SixteenByNine => "16:9",
|
||||
EnqueueNanoBananaProTextToImageAspectRatio::TwentyOneByNine => "21:9",
|
||||
// Tall
|
||||
EnqueueNanoBananaProTextToImageAspectRatio::FourByFive => "4:5",
|
||||
EnqueueNanoBananaProTextToImageAspectRatio::ThreeByFour => "3:4",
|
||||
EnqueueNanoBananaProTextToImageAspectRatio::TwoByThree => "2:3",
|
||||
EnqueueNanoBananaProTextToImageAspectRatio::NineBySixteen => "9:16",
|
||||
})
|
||||
.map(|aspect_ratio| aspect_ratio.to_string());
|
||||
|
||||
let request = NanoBananaProTextToImageInput {
|
||||
prompt: args.prompt.to_string(),
|
||||
num_images: Some(num_images),
|
||||
// Optionals
|
||||
aspect_ratio,
|
||||
resolution,
|
||||
// Constants
|
||||
output_format: Some("png".to_string()),
|
||||
};
|
||||
|
||||
let result = nano_banana_pro_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_nano_banana_pro_text_to_image_webhook::{enqueue_nano_banana_pro_text_to_image_webhook, EnqueueNanoBananaProTextToImageArgs, EnqueueNanoBananaProTextToImageAspectRatio, EnqueueNanoBananaProTextToImageNumImages, EnqueueNanoBananaProTextToImageResolution};
|
||||
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 = EnqueueNanoBananaProTextToImageArgs {
|
||||
prompt: "an anime girl riding on the back of a t-rex",
|
||||
num_images: EnqueueNanoBananaProTextToImageNumImages::One,
|
||||
aspect_ratio: Some(EnqueueNanoBananaProTextToImageAspectRatio::SixteenByNine),
|
||||
resolution: Some(EnqueueNanoBananaProTextToImageResolution::TwoK),
|
||||
api_key: &api_key,
|
||||
webhook_url: "https://example.com/webhook",
|
||||
};
|
||||
|
||||
let result = enqueue_nano_banana_pro_text_to_image_webhook(args).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +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(())
|
||||
}
|
||||
}
|
||||
+81
@@ -0,0 +1,81 @@
|
||||
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;
|
||||
|
||||
pub struct EnqueueHailuoV2p3FastProImageToVideoArgs<'a, R: IntoUrl> {
|
||||
// Request required
|
||||
pub prompt: String,
|
||||
pub image_url: String,
|
||||
|
||||
// Optional args
|
||||
pub prompt_optimizer: Option<bool>,
|
||||
|
||||
// Fulfillment
|
||||
pub webhook_url: R,
|
||||
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> {
|
||||
|
||||
let prompt_optimizer = args.prompt_optimizer.unwrap_or(true);
|
||||
|
||||
let request = HailuoV2p3FastProImageToVideoInput {
|
||||
prompt: args.prompt,
|
||||
image_url: args.image_url,
|
||||
// Optionals
|
||||
prompt_optimizer: Some(prompt_optimizer),
|
||||
};
|
||||
|
||||
let result = hailuo_v2p3_fast_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::image::enqueue_hailuo_v2p3_fast_pro_image_to_video_webhook::{enqueue_hailuo_v2p3_fast_pro_image_to_video_webhook, EnqueueHailuoV2p3FastProImageToVideoArgs};
|
||||
use errors::AnyhowResult;
|
||||
use std::fs::read_to_string;
|
||||
use test_data::web::image_urls::TREX_SKELETON_IMAGE_URL;
|
||||
|
||||
#[tokio::test]
|
||||
#[ignore]
|
||||
async fn test() -> AnyhowResult<()> {
|
||||
// XXX: Don't commit secrets!
|
||||
let secret = read_to_string("/home/bt/Artcraft/credentials/fal_api_key.txt")?;
|
||||
|
||||
let api_key = FalApiKey::from_str(&secret);
|
||||
|
||||
let args = EnqueueHailuoV2p3FastProImageToVideoArgs {
|
||||
image_url: TREX_SKELETON_IMAGE_URL.to_string(),
|
||||
prompt: "the t-rex skeleton gets off the podium and begins walking to the camera. the camera orbits slightly. The t-rex gets close and then bites.".to_string(),
|
||||
prompt_optimizer: Some(true),
|
||||
api_key: &api_key,
|
||||
webhook_url: "https://example.com/webhook",
|
||||
};
|
||||
|
||||
let result = enqueue_hailuo_v2p3_fast_pro_image_to_video_webhook(args).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
+81
@@ -0,0 +1,81 @@
|
||||
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;
|
||||
|
||||
pub struct EnqueueHailuoV2p3ProImageToVideoArgs<'a, R: IntoUrl> {
|
||||
// Request required
|
||||
pub prompt: String,
|
||||
pub image_url: String,
|
||||
|
||||
// Optional args
|
||||
pub prompt_optimizer: Option<bool>,
|
||||
|
||||
// Fulfillment
|
||||
pub webhook_url: R,
|
||||
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> {
|
||||
|
||||
let prompt_optimizer = args.prompt_optimizer.unwrap_or(true);
|
||||
|
||||
let request = HailuoV2p3ProImageToVideoInput {
|
||||
prompt: args.prompt,
|
||||
image_url: args.image_url,
|
||||
// Optionals
|
||||
prompt_optimizer: Some(prompt_optimizer),
|
||||
};
|
||||
|
||||
let result = hailuo_v2p3_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::image::enqueue_hailuo_v2p3_pro_image_to_video_webhook::{enqueue_hailuo_v2p3_pro_image_to_video_webhook, EnqueueHailuoV2p3ProImageToVideoArgs};
|
||||
use errors::AnyhowResult;
|
||||
use std::fs::read_to_string;
|
||||
use test_data::web::image_urls::TREX_SKELETON_IMAGE_URL;
|
||||
|
||||
#[tokio::test]
|
||||
#[ignore]
|
||||
async fn test() -> AnyhowResult<()> {
|
||||
// XXX: Don't commit secrets!
|
||||
let secret = read_to_string("/home/bt/Artcraft/credentials/fal_api_key.txt")?;
|
||||
|
||||
let api_key = FalApiKey::from_str(&secret);
|
||||
|
||||
let args = EnqueueHailuoV2p3ProImageToVideoArgs {
|
||||
image_url: TREX_SKELETON_IMAGE_URL.to_string(),
|
||||
prompt: "the t-rex skeleton gets off the podium and begins walking to the camera. the camera orbits slightly. The t-rex gets close and then bites.".to_string(),
|
||||
prompt_optimizer: Some(true),
|
||||
api_key: &api_key,
|
||||
webhook_url: "https://example.com/webhook",
|
||||
};
|
||||
|
||||
let result = enqueue_hailuo_v2p3_pro_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::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(())
|
||||
}
|
||||
+107
@@ -0,0 +1,107 @@
|
||||
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;
|
||||
|
||||
pub struct EnqueueKlingV2p5TurboProImageToVideoArgs<'a, R: IntoUrl> {
|
||||
// Request required
|
||||
pub prompt: String,
|
||||
pub image_url: String,
|
||||
|
||||
// Optional args
|
||||
pub tail_image_url: Option<String>,
|
||||
pub negative_prompt: Option<String>,
|
||||
|
||||
pub duration: Option<EnqueueKlingV2p5TurboProImageToVideoDurationSeconds>,
|
||||
|
||||
// Fulfillment
|
||||
pub webhook_url: R,
|
||||
pub api_key: &'a FalApiKey,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub enum EnqueueKlingV2p5TurboProImageToVideoDurationSeconds {
|
||||
Five,
|
||||
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",
|
||||
EnqueueKlingV2p5TurboProImageToVideoDurationSeconds::Ten => "10",
|
||||
})
|
||||
.map(|resolution| resolution.to_string());
|
||||
|
||||
let request = KlingV2p5TurboProImageToVideoInput {
|
||||
prompt: args.prompt,
|
||||
image_url: args.image_url,
|
||||
// Optionals
|
||||
duration,
|
||||
tail_image_url: args.tail_image_url,
|
||||
negative_prompt: args.negative_prompt,
|
||||
// Constants
|
||||
cfg_scale: None,
|
||||
};
|
||||
|
||||
let result = kling_v2p5_turbo_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::image::enqueue_kling_v2p5_turbo_pro_image_to_video_webhook::{enqueue_kling_v2p5_turbo_pro_image_to_video_webhook, EnqueueKlingV2p5TurboProImageToVideoArgs, EnqueueKlingV2p5TurboProImageToVideoDurationSeconds};
|
||||
use errors::AnyhowResult;
|
||||
use std::fs::read_to_string;
|
||||
use test_data::web::image_urls::TREX_SKELETON_IMAGE_URL;
|
||||
|
||||
#[tokio::test]
|
||||
#[ignore]
|
||||
async fn test() -> AnyhowResult<()> {
|
||||
// XXX: Don't commit secrets!
|
||||
let secret = read_to_string("/home/bt/Artcraft/credentials/fal_api_key.txt")?;
|
||||
|
||||
let api_key = FalApiKey::from_str(&secret);
|
||||
|
||||
let args = EnqueueKlingV2p5TurboProImageToVideoArgs {
|
||||
image_url: TREX_SKELETON_IMAGE_URL.to_string(),
|
||||
prompt: "the t-rex skeleton gets off the podium and begins walking to the camera. the camera orbits slightly. The t-rex gets close and then bites.".to_string(),
|
||||
negative_prompt: None,
|
||||
tail_image_url: None,
|
||||
duration: Some(EnqueueKlingV2p5TurboProImageToVideoDurationSeconds::Five),
|
||||
api_key: &api_key,
|
||||
webhook_url: "https://example.com/webhook",
|
||||
};
|
||||
|
||||
let result = enqueue_kling_v2p5_turbo_pro_image_to_video_webhook(args).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
+103
@@ -0,0 +1,103 @@
|
||||
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;
|
||||
|
||||
pub struct EnqueueKlingV2p5TurboStandardImageToVideoArgs<'a, R: IntoUrl> {
|
||||
// Request required
|
||||
pub prompt: String,
|
||||
pub image_url: String,
|
||||
|
||||
// Optional args
|
||||
pub negative_prompt: Option<String>,
|
||||
pub duration: Option<EnqueueKlingV2p5TurboStandardImageToVideoDurationSeconds>,
|
||||
|
||||
// Fulfillment
|
||||
pub webhook_url: R,
|
||||
pub api_key: &'a FalApiKey,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub enum EnqueueKlingV2p5TurboStandardImageToVideoDurationSeconds {
|
||||
Five,
|
||||
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",
|
||||
EnqueueKlingV2p5TurboStandardImageToVideoDurationSeconds::Ten => "10",
|
||||
})
|
||||
.map(|resolution| resolution.to_string());
|
||||
|
||||
let request = KlingV2p5TurboStandardImageToVideoInput {
|
||||
prompt: args.prompt,
|
||||
image_url: args.image_url,
|
||||
// Optionals
|
||||
duration,
|
||||
negative_prompt: args.negative_prompt,
|
||||
// Constants
|
||||
cfg_scale: None,
|
||||
};
|
||||
|
||||
let result = kling_v2p5_turbo_standard_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_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;
|
||||
|
||||
#[tokio::test]
|
||||
#[ignore]
|
||||
async fn test() -> AnyhowResult<()> {
|
||||
// XXX: Don't commit secrets!
|
||||
let secret = read_to_string("/home/bt/Artcraft/credentials/fal_api_key.txt")?;
|
||||
|
||||
let api_key = FalApiKey::from_str(&secret);
|
||||
|
||||
let args = EnqueueKlingV2p5TurboStandardImageToVideoArgs {
|
||||
image_url: TREX_SKELETON_IMAGE_URL.to_string(),
|
||||
prompt: "the t-rex skeleton gets off the podium and begins walking to the camera. the camera orbits slightly. The t-rex gets close and then bites.".to_string(),
|
||||
negative_prompt: None,
|
||||
duration: Some(EnqueueKlingV2p5TurboStandardImageToVideoDurationSeconds::Five),
|
||||
api_key: &api_key,
|
||||
webhook_url: "https://example.com/webhook",
|
||||
};
|
||||
|
||||
let result = enqueue_kling_v2p5_turbo_standard_image_to_video_webhook(args).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
+113
@@ -0,0 +1,113 @@
|
||||
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;
|
||||
|
||||
pub struct EnqueueKlingV2p6ProImageToVideoArgs<'a, R: IntoUrl> {
|
||||
// Request required
|
||||
pub prompt: String,
|
||||
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
|
||||
pub webhook_url: R,
|
||||
pub api_key: &'a FalApiKey,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub enum EnqueueKlingV2p6ProImageToVideoDurationSeconds {
|
||||
Five,
|
||||
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> {
|
||||
|
||||
let duration = args.duration
|
||||
.map(|resolution| match resolution {
|
||||
EnqueueKlingV2p6ProImageToVideoDurationSeconds::Five => "5",
|
||||
EnqueueKlingV2p6ProImageToVideoDurationSeconds::Ten => "10",
|
||||
})
|
||||
.map(|resolution| resolution.to_string());
|
||||
|
||||
let request = KlingV2p6ProImageToVideoInput {
|
||||
prompt: args.prompt,
|
||||
image_url: args.image_url,
|
||||
// Optionals
|
||||
duration,
|
||||
negative_prompt: args.negative_prompt,
|
||||
generate_audio: args.generate_audio,
|
||||
};
|
||||
|
||||
let result = kling_v2p6_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::image::enqueue_kling_v2p6_pro_image_to_video_webhook::{enqueue_kling_v2p6_pro_image_to_video_webhook, EnqueueKlingV2p6ProImageToVideoArgs, EnqueueKlingV2p6ProImageToVideoDurationSeconds};
|
||||
use errors::AnyhowResult;
|
||||
use std::fs::read_to_string;
|
||||
use test_data::web::image_urls::ERNEST_SCARED_STUPID_IMAGE_URL;
|
||||
|
||||
#[tokio::test]
|
||||
#[ignore]
|
||||
async fn test() -> AnyhowResult<()> {
|
||||
// XXX: Don't commit secrets!
|
||||
let secret = read_to_string("/home/bt/Artcraft/credentials/fal_api_key.txt")?;
|
||||
|
||||
let api_key = FalApiKey::from_str(&secret);
|
||||
|
||||
let args = EnqueueKlingV2p6ProImageToVideoArgs {
|
||||
image_url: ERNEST_SCARED_STUPID_IMAGE_URL.to_string(),
|
||||
prompt: "the man says, 'these ghosts sure are scary!' and then runs. behind him, a group of ghosts appear from the trees and chase him. the camera tracks the action. finally, the man yells, 'go away ghosts'.".to_string(),
|
||||
negative_prompt: None,
|
||||
generate_audio: Some(true),
|
||||
duration: Some(EnqueueKlingV2p6ProImageToVideoDurationSeconds::Ten),
|
||||
api_key: &api_key,
|
||||
webhook_url: "https://example.com/webhook",
|
||||
};
|
||||
|
||||
let result = enqueue_kling_v2p6_pro_image_to_video_webhook(args).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
+183
@@ -0,0 +1,183 @@
|
||||
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;
|
||||
|
||||
pub struct EnqueuePixverseV5ImageToVideoArgs<'a, R: IntoUrl> {
|
||||
// Request required
|
||||
pub prompt: String,
|
||||
pub image_url: String,
|
||||
|
||||
// Optional args
|
||||
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>,
|
||||
|
||||
// Fulfillment
|
||||
pub webhook_url: R,
|
||||
pub api_key: &'a FalApiKey,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub enum EnqueuePixverseV5ImageToVideoDurationSeconds {
|
||||
Five,
|
||||
Eight,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub enum EnqueuePixverseV5ImageToVideoResolution {
|
||||
ThreeSixtyP,
|
||||
FiveFortyP,
|
||||
SevenTwentyP,
|
||||
TenEightyP,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub enum EnqueuePixverseV5ImageToVideoAspectRatio {
|
||||
Square,
|
||||
SixteenByNine,
|
||||
FourByThree,
|
||||
NineBySixteen,
|
||||
ThreeByFour,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub enum EnqueuePixverseV5ImageToVideoStyle {
|
||||
Anime,
|
||||
Animation3d,
|
||||
Clay,
|
||||
Comic,
|
||||
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> {
|
||||
|
||||
let duration = args.duration
|
||||
.map(|resolution| match resolution {
|
||||
EnqueuePixverseV5ImageToVideoDurationSeconds::Five => "5",
|
||||
EnqueuePixverseV5ImageToVideoDurationSeconds::Eight=> "8",
|
||||
})
|
||||
.map(|s| s.to_string());
|
||||
|
||||
let aspect_ratio = args.aspect_ratio
|
||||
.map(|aspect_ratio| match aspect_ratio {
|
||||
EnqueuePixverseV5ImageToVideoAspectRatio::Square => "1:1",
|
||||
EnqueuePixverseV5ImageToVideoAspectRatio::SixteenByNine => "16:9",
|
||||
EnqueuePixverseV5ImageToVideoAspectRatio::FourByThree => "4:3",
|
||||
EnqueuePixverseV5ImageToVideoAspectRatio::NineBySixteen => "9:16",
|
||||
EnqueuePixverseV5ImageToVideoAspectRatio::ThreeByFour => "3:4",
|
||||
})
|
||||
.map(|s| s.to_string());
|
||||
|
||||
let resolution = args.resolution
|
||||
.map(|resolution| match resolution {
|
||||
EnqueuePixverseV5ImageToVideoResolution::ThreeSixtyP => "360p",
|
||||
EnqueuePixverseV5ImageToVideoResolution::FiveFortyP => "540p",
|
||||
EnqueuePixverseV5ImageToVideoResolution::SevenTwentyP => "720p",
|
||||
EnqueuePixverseV5ImageToVideoResolution::TenEightyP => "1080p",
|
||||
})
|
||||
.map(|s| s.to_string());
|
||||
|
||||
let style = args.style
|
||||
.map(|style| match style {
|
||||
EnqueuePixverseV5ImageToVideoStyle::Anime => "anime",
|
||||
EnqueuePixverseV5ImageToVideoStyle::Animation3d => "3d_animation",
|
||||
EnqueuePixverseV5ImageToVideoStyle::Clay => "clay",
|
||||
EnqueuePixverseV5ImageToVideoStyle::Comic => "comic",
|
||||
EnqueuePixverseV5ImageToVideoStyle::Cyberpunk => "cyberpunk",
|
||||
})
|
||||
.map(|s| s.to_string());
|
||||
|
||||
let request = PixverseV5ImageToVideoInput {
|
||||
prompt: args.prompt,
|
||||
image_url: args.image_url,
|
||||
// Optionals
|
||||
duration,
|
||||
negative_prompt: args.negative_prompt,
|
||||
aspect_ratio,
|
||||
resolution,
|
||||
style,
|
||||
// Constant
|
||||
seed: None,
|
||||
};
|
||||
|
||||
let result = pixverse_v5_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_pixverse_v5_image_to_video_webhook::{enqueue_pixverse_v5_image_to_video_webhook, EnqueuePixverseV5ImageToVideoArgs, EnqueuePixverseV5ImageToVideoAspectRatio, EnqueuePixverseV5ImageToVideoDurationSeconds, EnqueuePixverseV5ImageToVideoResolution, EnqueuePixverseV5ImageToVideoStyle};
|
||||
use errors::AnyhowResult;
|
||||
use std::fs::read_to_string;
|
||||
use test_data::web::image_urls::TREX_SKELETON_IMAGE_URL;
|
||||
|
||||
#[tokio::test]
|
||||
#[ignore]
|
||||
async fn test() -> AnyhowResult<()> {
|
||||
// XXX: Don't commit secrets!
|
||||
let secret = read_to_string("/home/bt/Artcraft/credentials/fal_api_key.txt")?;
|
||||
|
||||
let api_key = FalApiKey::from_str(&secret);
|
||||
|
||||
let args = EnqueuePixverseV5ImageToVideoArgs {
|
||||
image_url: TREX_SKELETON_IMAGE_URL.to_string(),
|
||||
prompt: "the t-rex skeleton gets off the podium and begins walking to the camera. the camera orbits slightly. The t-rex gets close and then bites.".to_string(),
|
||||
negative_prompt: None,
|
||||
duration: Some(EnqueuePixverseV5ImageToVideoDurationSeconds::Five),
|
||||
style: Some(EnqueuePixverseV5ImageToVideoStyle::Cyberpunk),
|
||||
aspect_ratio: Some(EnqueuePixverseV5ImageToVideoAspectRatio::FourByThree),
|
||||
resolution: Some(EnqueuePixverseV5ImageToVideoResolution::TenEightyP),
|
||||
api_key: &api_key,
|
||||
webhook_url: "https://example.com/webhook",
|
||||
};
|
||||
|
||||
let result = enqueue_pixverse_v5_image_to_video_webhook(args).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
+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(())
|
||||
}
|
||||
}
|
||||
+136
@@ -0,0 +1,136 @@
|
||||
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;
|
||||
|
||||
pub struct EnqueueSora2ImageToVideoArgs<'a, R: IntoUrl> {
|
||||
// Request required
|
||||
pub prompt: String,
|
||||
pub image_url: String,
|
||||
|
||||
// Optional args
|
||||
|
||||
// NB: Duration defaults to four seconds
|
||||
pub duration: Option<EnqueueSora2ImageToVideoDurationSeconds>,
|
||||
|
||||
pub resolution: Option<EnqueueSora2ImageToVideoResolution>,
|
||||
pub aspect_ratio: Option<EnqueueSora2ImageToVideoAspectRatio>,
|
||||
|
||||
// Fulfillment
|
||||
pub webhook_url: R,
|
||||
pub api_key: &'a FalApiKey,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub enum EnqueueSora2ImageToVideoDurationSeconds {
|
||||
Four,
|
||||
Eight,
|
||||
Twelve,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub enum EnqueueSora2ImageToVideoResolution {
|
||||
Auto,
|
||||
SevenTwentyP,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub enum EnqueueSora2ImageToVideoAspectRatio {
|
||||
Auto,
|
||||
NineBySixteen,
|
||||
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> {
|
||||
|
||||
let duration = args.duration
|
||||
.map(|resolution| match resolution {
|
||||
EnqueueSora2ImageToVideoDurationSeconds::Four => 4,
|
||||
EnqueueSora2ImageToVideoDurationSeconds::Eight => 8,
|
||||
EnqueueSora2ImageToVideoDurationSeconds::Twelve => 12,
|
||||
});
|
||||
|
||||
let resolution = args.resolution
|
||||
.map(|resolution| match resolution {
|
||||
EnqueueSora2ImageToVideoResolution::Auto => "auto",
|
||||
EnqueueSora2ImageToVideoResolution::SevenTwentyP => "720p",
|
||||
})
|
||||
.map(|resolution| resolution.to_string());
|
||||
|
||||
let aspect_ratio = args.aspect_ratio
|
||||
.map(|aspect_ratio| match aspect_ratio {
|
||||
EnqueueSora2ImageToVideoAspectRatio::Auto => "auto",
|
||||
EnqueueSora2ImageToVideoAspectRatio::NineBySixteen => "9:16",
|
||||
EnqueueSora2ImageToVideoAspectRatio::SixteenByNine => "16:9",
|
||||
})
|
||||
.map(|resolution| resolution.to_string());
|
||||
|
||||
let request = Sora2ImageToVideoInput {
|
||||
prompt: args.prompt,
|
||||
image_url: args.image_url,
|
||||
// Optionals
|
||||
duration,
|
||||
resolution,
|
||||
aspect_ratio,
|
||||
// Constants
|
||||
delete_video: Some(false),
|
||||
};
|
||||
|
||||
let result = sora_2_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_sora_2_image_to_video_webhook::{enqueue_sora_2_image_to_video_webhook, EnqueueSora2ImageToVideoArgs, EnqueueSora2ImageToVideoAspectRatio, EnqueueSora2ImageToVideoDurationSeconds, EnqueueSora2ImageToVideoResolution};
|
||||
use errors::AnyhowResult;
|
||||
use std::fs::read_to_string;
|
||||
use test_data::web::image_urls::TREX_SKELETON_IMAGE_URL;
|
||||
|
||||
#[tokio::test]
|
||||
#[ignore]
|
||||
async fn test() -> AnyhowResult<()> {
|
||||
// XXX: Don't commit secrets!
|
||||
let secret = read_to_string("/home/bt/Artcraft/credentials/fal_api_key.txt")?;
|
||||
|
||||
let api_key = FalApiKey::from_str(&secret);
|
||||
|
||||
let args = EnqueueSora2ImageToVideoArgs {
|
||||
image_url: TREX_SKELETON_IMAGE_URL.to_string(),
|
||||
prompt: "the t-rex skeleton gets off the podium and begins walking to the camera. the camera orbits slightly. The t-rex gets close and then bites.".to_string(),
|
||||
duration: Some(EnqueueSora2ImageToVideoDurationSeconds::Twelve),
|
||||
aspect_ratio: Some(EnqueueSora2ImageToVideoAspectRatio::SixteenByNine),
|
||||
resolution: Some(EnqueueSora2ImageToVideoResolution::SevenTwentyP),
|
||||
api_key: &api_key,
|
||||
webhook_url: "https://example.com/webhook",
|
||||
};
|
||||
|
||||
let result = enqueue_sora_2_image_to_video_webhook(args).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
+148
@@ -0,0 +1,148 @@
|
||||
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;
|
||||
|
||||
pub struct EnqueueSora2ProImageToVideoArgs<'a, R: IntoUrl> {
|
||||
// Request required
|
||||
pub prompt: String,
|
||||
pub image_url: String,
|
||||
|
||||
// Optional args
|
||||
pub resolution: Option<EnqueueSora2ProImageToVideoResolution>,
|
||||
pub duration: Option<EnqueueSora2ProImageToVideoDurationSeconds>,
|
||||
pub aspect_ratio: Option<EnqueueSora2ProImageToVideoAspectRatio>,
|
||||
|
||||
// Fulfillment
|
||||
pub webhook_url: R,
|
||||
pub api_key: &'a FalApiKey,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub enum EnqueueSora2ProImageToVideoDurationSeconds {
|
||||
Four,
|
||||
Eight,
|
||||
Twelve,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub enum EnqueueSora2ProImageToVideoResolution {
|
||||
Auto,
|
||||
SevenTwentyP,
|
||||
TenEightyP,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub enum EnqueueSora2ProImageToVideoAspectRatio {
|
||||
Auto,
|
||||
NineBySixteen,
|
||||
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> {
|
||||
|
||||
let duration = args.duration
|
||||
.map(|resolution| match resolution {
|
||||
EnqueueSora2ProImageToVideoDurationSeconds::Four => 4,
|
||||
EnqueueSora2ProImageToVideoDurationSeconds::Eight => 8,
|
||||
EnqueueSora2ProImageToVideoDurationSeconds::Twelve => 12,
|
||||
});
|
||||
|
||||
let resolution = args.resolution
|
||||
.map(|resolution| match resolution {
|
||||
EnqueueSora2ProImageToVideoResolution::Auto => "auto",
|
||||
EnqueueSora2ProImageToVideoResolution::SevenTwentyP => "720p",
|
||||
EnqueueSora2ProImageToVideoResolution::TenEightyP => "1080p",
|
||||
})
|
||||
.map(|resolution| resolution.to_string());
|
||||
|
||||
let aspect_ratio = args.aspect_ratio
|
||||
.map(|aspect_ratio| match aspect_ratio {
|
||||
EnqueueSora2ProImageToVideoAspectRatio::Auto => "auto",
|
||||
EnqueueSora2ProImageToVideoAspectRatio::NineBySixteen => "9:16",
|
||||
EnqueueSora2ProImageToVideoAspectRatio::SixteenByNine => "16:9",
|
||||
})
|
||||
.map(|resolution| resolution.to_string());
|
||||
|
||||
let request = Sora2ProImageToVideoInput {
|
||||
prompt: args.prompt,
|
||||
image_url: args.image_url,
|
||||
// Optionals
|
||||
duration,
|
||||
resolution,
|
||||
aspect_ratio,
|
||||
// Constants
|
||||
delete_video: Some(false),
|
||||
};
|
||||
|
||||
let result = sora_2_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::image::enqueue_sora_2_pro_image_to_video_webhook::{enqueue_sora_2_pro_image_to_video_webhook, EnqueueSora2ProImageToVideoArgs, EnqueueSora2ProImageToVideoAspectRatio, EnqueueSora2ProImageToVideoDurationSeconds, EnqueueSora2ProImageToVideoResolution};
|
||||
use errors::AnyhowResult;
|
||||
use std::fs::read_to_string;
|
||||
use test_data::web::image_urls::TREX_SKELETON_IMAGE_URL;
|
||||
|
||||
#[tokio::test]
|
||||
#[ignore]
|
||||
async fn test() -> AnyhowResult<()> {
|
||||
// XXX: Don't commit secrets!
|
||||
let secret = read_to_string("/home/bt/Artcraft/credentials/fal_api_key.txt")?;
|
||||
|
||||
let api_key = FalApiKey::from_str(&secret);
|
||||
|
||||
let args = EnqueueSora2ProImageToVideoArgs {
|
||||
image_url: TREX_SKELETON_IMAGE_URL.to_string(),
|
||||
prompt: "the t-rex skeleton gets off the podium and begins walking to the camera. the camera orbits slightly. The t-rex gets close and then bites.".to_string(),
|
||||
duration: Some(EnqueueSora2ProImageToVideoDurationSeconds::Twelve),
|
||||
aspect_ratio: Some(EnqueueSora2ProImageToVideoAspectRatio::SixteenByNine),
|
||||
resolution: Some(EnqueueSora2ProImageToVideoResolution::TenEightyP),
|
||||
api_key: &api_key,
|
||||
webhook_url: "https://example.com/webhook",
|
||||
};
|
||||
|
||||
let result = enqueue_sora_2_pro_image_to_video_webhook(args).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
+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]
|
||||
+159
@@ -0,0 +1,159 @@
|
||||
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;
|
||||
|
||||
pub struct EnqueueVeo3p1FastFirstLastFrameImageToVideoArgs<'a, R: IntoUrl> {
|
||||
// Request required
|
||||
pub prompt: String,
|
||||
|
||||
// Starting frame
|
||||
pub first_frame_url: String,
|
||||
|
||||
// Ending frame
|
||||
pub last_frame_url: String,
|
||||
|
||||
// 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
|
||||
pub webhook_url: R,
|
||||
pub api_key: &'a FalApiKey,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub enum EnqueueVeo3p1FastFirstLastFrameImageToVideoDurationSeconds {
|
||||
Four,
|
||||
Six,
|
||||
Eight,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub enum EnqueueVeo3p1FastFirstLastFrameImageToVideoAspectRatio {
|
||||
Auto,
|
||||
SixteenByNine,
|
||||
NineBySixteen,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub enum EnqueueVeo3p1FastFirstLastFrameImageToVideoResolution {
|
||||
SevenTwentyP,
|
||||
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> {
|
||||
|
||||
let duration = args.duration
|
||||
.map(|resolution| match resolution {
|
||||
EnqueueVeo3p1FastFirstLastFrameImageToVideoDurationSeconds::Four => "4s",
|
||||
EnqueueVeo3p1FastFirstLastFrameImageToVideoDurationSeconds::Six => "6s",
|
||||
EnqueueVeo3p1FastFirstLastFrameImageToVideoDurationSeconds::Eight=> "8s",
|
||||
})
|
||||
.map(|s| s.to_string());
|
||||
|
||||
let aspect_ratio = args.aspect_ratio
|
||||
.map(|aspect_ratio| match aspect_ratio {
|
||||
EnqueueVeo3p1FastFirstLastFrameImageToVideoAspectRatio::Auto => "auto",
|
||||
EnqueueVeo3p1FastFirstLastFrameImageToVideoAspectRatio::SixteenByNine => "16:9",
|
||||
EnqueueVeo3p1FastFirstLastFrameImageToVideoAspectRatio::NineBySixteen => "9:16",
|
||||
})
|
||||
.map(|s| s.to_string());
|
||||
|
||||
let resolution = args.resolution
|
||||
.map(|resolution| match resolution {
|
||||
EnqueueVeo3p1FastFirstLastFrameImageToVideoResolution::SevenTwentyP => "720p",
|
||||
EnqueueVeo3p1FastFirstLastFrameImageToVideoResolution::TenEightyP => "1080p",
|
||||
})
|
||||
.map(|s| s.to_string());
|
||||
|
||||
let request = Veo3p1FastFirstLastFrameImageToVideoInput {
|
||||
prompt: args.prompt,
|
||||
first_frame_url: args.first_frame_url,
|
||||
last_frame_url: args.last_frame_url,
|
||||
generate_audio: args.generate_audio,
|
||||
// Optionals
|
||||
duration,
|
||||
aspect_ratio,
|
||||
resolution,
|
||||
};
|
||||
|
||||
let result = veo_3p1_fast_first_last_frame_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_veo_3p1_fast_first_last_frame_image_to_video_webhook::{enqueue_veo_3p1_fast_first_last_frame_image_to_video_webhook, EnqueueVeo3p1FastFirstLastFrameImageToVideoArgs, EnqueueVeo3p1FastFirstLastFrameImageToVideoAspectRatio, EnqueueVeo3p1FastFirstLastFrameImageToVideoDurationSeconds, EnqueueVeo3p1FastFirstLastFrameImageToVideoResolution};
|
||||
use errors::AnyhowResult;
|
||||
use std::fs::read_to_string;
|
||||
use test_data::web::image_urls::{TALL_CORGI_SHIBA_TREASURE_OCEAN_URL, TALL_CORGI_SHIBA_TREASURE_SKY_URL};
|
||||
|
||||
#[tokio::test]
|
||||
#[ignore]
|
||||
async fn test() -> AnyhowResult<()> {
|
||||
// XXX: Don't commit secrets!
|
||||
let secret = read_to_string("/home/bt/Artcraft/credentials/fal_api_key.txt")?;
|
||||
|
||||
let api_key = FalApiKey::from_str(&secret);
|
||||
|
||||
let args = EnqueueVeo3p1FastFirstLastFrameImageToVideoArgs {
|
||||
first_frame_url: TALL_CORGI_SHIBA_TREASURE_OCEAN_URL.to_string(),
|
||||
last_frame_url: TALL_CORGI_SHIBA_TREASURE_SKY_URL.to_string(),
|
||||
prompt: "There is a tiny ocean island with a corgi and shiba and treasure chest on it. The corgi and shiba are barking at the chest, when suddenly the island launches itself into the air. The camera tracks the island and follows it up high in the sky. The sun beams over the horizon. The dogs are happy and bark. The gold coins gleam in the sun.".to_string(),
|
||||
duration: Some(EnqueueVeo3p1FastFirstLastFrameImageToVideoDurationSeconds::Six),
|
||||
aspect_ratio: Some(EnqueueVeo3p1FastFirstLastFrameImageToVideoAspectRatio::NineBySixteen),
|
||||
resolution: Some(EnqueueVeo3p1FastFirstLastFrameImageToVideoResolution::SevenTwentyP),
|
||||
generate_audio: Some(true),
|
||||
api_key: &api_key,
|
||||
webhook_url: "https://example.com/webhook",
|
||||
};
|
||||
|
||||
let result = enqueue_veo_3p1_fast_first_last_frame_image_to_video_webhook(args).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
+149
@@ -0,0 +1,149 @@
|
||||
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;
|
||||
|
||||
pub struct EnqueueVeo3p1FastImageToVideoArgs<'a, R: IntoUrl> {
|
||||
// Request required
|
||||
pub prompt: String,
|
||||
|
||||
// Starting frame
|
||||
pub image_url: String,
|
||||
|
||||
// Optional args
|
||||
pub duration: Option<EnqueueVeo3p1FastImageToVideoDurationSeconds>,
|
||||
pub aspect_ratio: Option<EnqueueVeo3p1FastImageToVideoAspectRatio>,
|
||||
pub resolution: Option<EnqueueVeo3p1FastImageToVideoResolution>,
|
||||
pub generate_audio: Option<bool>,
|
||||
|
||||
// Fulfillment
|
||||
pub webhook_url: R,
|
||||
pub api_key: &'a FalApiKey,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub enum EnqueueVeo3p1FastImageToVideoDurationSeconds {
|
||||
Four,
|
||||
Six,
|
||||
Eight,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub enum EnqueueVeo3p1FastImageToVideoAspectRatio {
|
||||
Auto,
|
||||
SixteenByNine,
|
||||
NineBySixteen,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub enum EnqueueVeo3p1FastImageToVideoResolution {
|
||||
SevenTwentyP,
|
||||
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> {
|
||||
|
||||
let duration = args.duration
|
||||
.map(|resolution| match resolution {
|
||||
EnqueueVeo3p1FastImageToVideoDurationSeconds::Four => "4s",
|
||||
EnqueueVeo3p1FastImageToVideoDurationSeconds::Six => "6s",
|
||||
EnqueueVeo3p1FastImageToVideoDurationSeconds::Eight=> "8s",
|
||||
})
|
||||
.map(|s| s.to_string());
|
||||
|
||||
let aspect_ratio = args.aspect_ratio
|
||||
.map(|aspect_ratio| match aspect_ratio {
|
||||
EnqueueVeo3p1FastImageToVideoAspectRatio::Auto => "auto",
|
||||
EnqueueVeo3p1FastImageToVideoAspectRatio::SixteenByNine => "16:9",
|
||||
EnqueueVeo3p1FastImageToVideoAspectRatio::NineBySixteen => "9:16",
|
||||
})
|
||||
.map(|s| s.to_string());
|
||||
|
||||
let resolution = args.resolution
|
||||
.map(|resolution| match resolution {
|
||||
EnqueueVeo3p1FastImageToVideoResolution::SevenTwentyP => "720p",
|
||||
EnqueueVeo3p1FastImageToVideoResolution::TenEightyP => "1080p",
|
||||
})
|
||||
.map(|s| s.to_string());
|
||||
|
||||
let request = Veo3p1FastFirstFrameImageToVideoInput {
|
||||
prompt: args.prompt,
|
||||
image_url: args.image_url,
|
||||
generate_audio: args.generate_audio,
|
||||
// Optionals
|
||||
duration,
|
||||
aspect_ratio,
|
||||
resolution,
|
||||
};
|
||||
|
||||
let result = veo_3p1_fast_first_frame_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_veo_3p1_fast_image_to_video_webhook::{enqueue_veo_3p1_fast_image_to_video_webhook, EnqueueVeo3p1FastImageToVideoArgs, EnqueueVeo3p1FastImageToVideoAspectRatio, EnqueueVeo3p1FastImageToVideoDurationSeconds, EnqueueVeo3p1FastImageToVideoResolution};
|
||||
use errors::AnyhowResult;
|
||||
use std::fs::read_to_string;
|
||||
use test_data::web::image_urls::TALL_CORGI_SHIBA_TREASURE_OCEAN_URL;
|
||||
|
||||
#[tokio::test]
|
||||
#[ignore]
|
||||
async fn test() -> AnyhowResult<()> {
|
||||
// XXX: Don't commit secrets!
|
||||
let secret = read_to_string("/home/bt/Artcraft/credentials/fal_api_key.txt")?;
|
||||
|
||||
let api_key = FalApiKey::from_str(&secret);
|
||||
|
||||
let args = EnqueueVeo3p1FastImageToVideoArgs {
|
||||
image_url: TALL_CORGI_SHIBA_TREASURE_OCEAN_URL.to_string(),
|
||||
prompt: "There is a tiny ocean island with a corgi and shiba and treasure chest on it. The corgi and shiba are barking at the chest, when suddenly the island launches itself into the air. The camera tracks the island and follows it up high in the sky. The sun beams over the horizon. The dogs are happy and bark. The gold coins gleam in the sun.".to_string(),
|
||||
duration: Some(EnqueueVeo3p1FastImageToVideoDurationSeconds::Four),
|
||||
aspect_ratio: Some(EnqueueVeo3p1FastImageToVideoAspectRatio::SixteenByNine),
|
||||
resolution: Some(EnqueueVeo3p1FastImageToVideoResolution::TenEightyP),
|
||||
generate_audio: Some(true),
|
||||
api_key: &api_key,
|
||||
webhook_url: "https://example.com/webhook",
|
||||
};
|
||||
|
||||
let result = enqueue_veo_3p1_fast_image_to_video_webhook(args).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user