mirror of
https://github.com/storytold/artcraft.git
synced 2026-11-13 21:32:18 +00:00
Merge branch 'main' into feature/3d-page-splats
This commit is contained in:
commit
7da65b231e
209 files changed
+8689
-1158
No files matched your search
Generated
+73
-65
@@ -916,31 +916,7 @@ dependencies = [
|
||||
"hmac 0.12.1",
|
||||
"http-types",
|
||||
"hyper 0.14.28",
|
||||
"hyper-tls 0.5.0",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_path_to_error",
|
||||
"serde_qs 0.10.1",
|
||||
"sha2 0.10.8",
|
||||
"smart-default 0.6.0",
|
||||
"smol_str 0.1.24",
|
||||
"thiserror 1.0.63",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "async-stripe"
|
||||
version = "0.41.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cecbddf002ad7a13d2041eadf1b234cb3f57653ffdd901a01bc3f1c65aa77440"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"futures-util",
|
||||
"hex",
|
||||
"hmac 0.12.1",
|
||||
"http-types",
|
||||
"hyper 0.14.28",
|
||||
"hyper-tls 0.5.0",
|
||||
"hyper-rustls 0.24.2",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_path_to_error",
|
||||
@@ -963,7 +939,7 @@ dependencies = [
|
||||
"bytes 1.10.1",
|
||||
"http-body-util",
|
||||
"hyper 1.6.0",
|
||||
"hyper-tls 0.6.0",
|
||||
"hyper-rustls 0.27.3",
|
||||
"hyper-util",
|
||||
"miniserde",
|
||||
"thiserror 2.0.12",
|
||||
@@ -1148,10 +1124,11 @@ checksum = "9a13149d0cf3f7f9b9261fad4ec63b2efbf9a80665f52def86282d26255e6331"
|
||||
dependencies = [
|
||||
"http 1.3.1",
|
||||
"log",
|
||||
"native-tls",
|
||||
"rustls 0.22.4",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"url",
|
||||
"webpki-roots 0.26.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -5262,6 +5239,22 @@ dependencies = [
|
||||
"want",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hyper-rustls"
|
||||
version = "0.24.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ec3efd23720e2049821a693cbc7e65ea87c72f1c58ff2f9522ff332b1491e590"
|
||||
dependencies = [
|
||||
"futures-util",
|
||||
"http 0.2.11",
|
||||
"hyper 0.14.28",
|
||||
"log",
|
||||
"rustls 0.21.12",
|
||||
"rustls-native-certs 0.6.3",
|
||||
"tokio",
|
||||
"tokio-rustls 0.24.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hyper-rustls"
|
||||
version = "0.27.3"
|
||||
@@ -5272,11 +5265,12 @@ dependencies = [
|
||||
"http 1.3.1",
|
||||
"hyper 1.6.0",
|
||||
"hyper-util",
|
||||
"log",
|
||||
"rustls 0.23.13",
|
||||
"rustls-native-certs",
|
||||
"rustls-native-certs 0.8.0",
|
||||
"rustls-pki-types",
|
||||
"tokio",
|
||||
"tokio-rustls",
|
||||
"tokio-rustls 0.26.0",
|
||||
"tower-service",
|
||||
"webpki-roots 0.26.2",
|
||||
]
|
||||
@@ -5293,19 +5287,6 @@ dependencies = [
|
||||
"tokio-io-timeout",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hyper-tls"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905"
|
||||
dependencies = [
|
||||
"bytes 1.10.1",
|
||||
"hyper 0.14.28",
|
||||
"native-tls",
|
||||
"tokio",
|
||||
"tokio-native-tls",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hyper-tls"
|
||||
version = "0.6.0"
|
||||
@@ -6133,13 +6114,15 @@ dependencies = [
|
||||
"httpdate",
|
||||
"idna 0.5.0",
|
||||
"mime",
|
||||
"native-tls",
|
||||
"nom",
|
||||
"once_cell",
|
||||
"quoted_printable",
|
||||
"rustls 0.21.12",
|
||||
"rustls-pemfile 1.0.4",
|
||||
"socket2 0.5.10",
|
||||
"tokio",
|
||||
"url",
|
||||
"webpki-roots 0.25.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -9103,15 +9086,15 @@ dependencies = [
|
||||
"http 0.2.11",
|
||||
"http-body 0.4.6",
|
||||
"hyper 0.14.28",
|
||||
"hyper-tls 0.5.0",
|
||||
"hyper-rustls 0.24.2",
|
||||
"ipnet",
|
||||
"js-sys",
|
||||
"log",
|
||||
"mime",
|
||||
"native-tls",
|
||||
"once_cell",
|
||||
"percent-encoding",
|
||||
"pin-project-lite",
|
||||
"rustls 0.21.12",
|
||||
"rustls-pemfile 1.0.4",
|
||||
"serde",
|
||||
"serde_json",
|
||||
@@ -9119,13 +9102,14 @@ dependencies = [
|
||||
"sync_wrapper 0.1.2",
|
||||
"system-configuration 0.5.1",
|
||||
"tokio",
|
||||
"tokio-native-tls",
|
||||
"tokio-rustls 0.24.1",
|
||||
"tokio-util",
|
||||
"tower-service",
|
||||
"url",
|
||||
"wasm-bindgen",
|
||||
"wasm-bindgen-futures",
|
||||
"web-sys",
|
||||
"webpki-roots 0.25.3",
|
||||
"winreg 0.50.0",
|
||||
]
|
||||
|
||||
@@ -9149,8 +9133,8 @@ dependencies = [
|
||||
"http-body 1.0.1",
|
||||
"http-body-util",
|
||||
"hyper 1.6.0",
|
||||
"hyper-rustls",
|
||||
"hyper-tls 0.6.0",
|
||||
"hyper-rustls 0.27.3",
|
||||
"hyper-tls",
|
||||
"hyper-util",
|
||||
"js-sys",
|
||||
"log",
|
||||
@@ -9161,7 +9145,7 @@ dependencies = [
|
||||
"pin-project-lite",
|
||||
"quinn",
|
||||
"rustls 0.23.13",
|
||||
"rustls-native-certs",
|
||||
"rustls-native-certs 0.8.0",
|
||||
"rustls-pki-types",
|
||||
"serde",
|
||||
"serde_json",
|
||||
@@ -9169,7 +9153,7 @@ dependencies = [
|
||||
"sync_wrapper 1.0.2",
|
||||
"tokio",
|
||||
"tokio-native-tls",
|
||||
"tokio-rustls",
|
||||
"tokio-rustls 0.26.0",
|
||||
"tokio-util",
|
||||
"tower 0.5.2",
|
||||
"tower-http",
|
||||
@@ -9190,6 +9174,7 @@ dependencies = [
|
||||
"serde",
|
||||
"serde_derive",
|
||||
"sqlx",
|
||||
"sqlx-core",
|
||||
"toml 0.8.22",
|
||||
"workspace-build-acceleration",
|
||||
]
|
||||
@@ -9497,17 +9482,33 @@ version = "0.21.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3f56a14d1f48b391359b22f731fd4bd7e43c97f3c50eee276f3aa09c94784d3e"
|
||||
dependencies = [
|
||||
"log",
|
||||
"ring",
|
||||
"rustls-webpki 0.101.7",
|
||||
"sct",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustls"
|
||||
version = "0.22.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bf4ef73721ac7bcd79b2b315da7779d8fc09718c6b3d2d1b2d94850eb8c18432"
|
||||
dependencies = [
|
||||
"log",
|
||||
"ring",
|
||||
"rustls-pki-types",
|
||||
"rustls-webpki 0.102.8",
|
||||
"subtle",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustls"
|
||||
version = "0.23.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f2dabaac7466917e566adb06783a81ca48944c6898a1b08b9374106dd671f4c8"
|
||||
dependencies = [
|
||||
"log",
|
||||
"once_cell",
|
||||
"ring",
|
||||
"rustls-pki-types",
|
||||
@@ -9516,6 +9517,18 @@ dependencies = [
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustls-native-certs"
|
||||
version = "0.6.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a9aace74cb666635c918e9c12bc0d348266037aa8eb599b5cba565709a8dff00"
|
||||
dependencies = [
|
||||
"openssl-probe",
|
||||
"rustls-pemfile 1.0.4",
|
||||
"schannel",
|
||||
"security-framework",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustls-native-certs"
|
||||
version = "0.8.0"
|
||||
@@ -10608,7 +10621,6 @@ dependencies = [
|
||||
"indexmap 2.9.0",
|
||||
"log",
|
||||
"memchr",
|
||||
"native-tls",
|
||||
"once_cell",
|
||||
"paste",
|
||||
"percent-encoding",
|
||||
@@ -10825,7 +10837,6 @@ dependencies = [
|
||||
"anyhow",
|
||||
"artcraft_api_defs",
|
||||
"async-stripe 0.26.0",
|
||||
"async-stripe 0.41.0",
|
||||
"async-trait",
|
||||
"base64 0.22.1",
|
||||
"billing_artcraft_component",
|
||||
@@ -12092,6 +12103,16 @@ dependencies = [
|
||||
"tokio-sync",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio-rustls"
|
||||
version = "0.24.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081"
|
||||
dependencies = [
|
||||
"rustls 0.21.12",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio-rustls"
|
||||
version = "0.26.0"
|
||||
@@ -14074,11 +14095,6 @@ dependencies = [
|
||||
"hashbrown 0.14.5",
|
||||
"heck 0.4.1",
|
||||
"hmac 0.12.1",
|
||||
"hyper 0.14.28",
|
||||
"hyper 1.6.0",
|
||||
"hyper-rustls",
|
||||
"hyper-tls 0.6.0",
|
||||
"hyper-util",
|
||||
"idna 1.0.3",
|
||||
"indexmap 1.9.3",
|
||||
"indexmap 2.9.0",
|
||||
@@ -14088,15 +14104,12 @@ dependencies = [
|
||||
"md-5 0.10.6",
|
||||
"memchr",
|
||||
"mime_guess",
|
||||
"native-tls",
|
||||
"num-bigint",
|
||||
"num-integer",
|
||||
"num-iter",
|
||||
"num-rational",
|
||||
"num-traits",
|
||||
"once_cell",
|
||||
"openssl",
|
||||
"openssl-sys",
|
||||
"parking_lot 0.12.2",
|
||||
"percent-encoding",
|
||||
"proc-macro2",
|
||||
@@ -14109,7 +14122,6 @@ dependencies = [
|
||||
"regex",
|
||||
"regex-automata",
|
||||
"regex-syntax",
|
||||
"reqwest 0.12.23",
|
||||
"rsa",
|
||||
"rustls-pki-types",
|
||||
"schemars",
|
||||
@@ -14123,10 +14135,6 @@ dependencies = [
|
||||
"smallvec 1.15.1",
|
||||
"socket2 0.5.10",
|
||||
"spin 0.9.8",
|
||||
"sqlx",
|
||||
"sqlx-core",
|
||||
"sqlx-macros",
|
||||
"sqlx-macros-core",
|
||||
"sqlx-mysql",
|
||||
"sqlx-sqlite",
|
||||
"stable_deref_trait",
|
||||
|
||||
+1
-1
@@ -246,7 +246,7 @@ log = "0.4.27"
|
||||
once_cell = "1.21.3"
|
||||
rand = "0.9.1"
|
||||
regex = "1.12.2"
|
||||
reqwest = { version = "0.12.23", features = ["json", "cookies"] }
|
||||
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"
|
||||
|
||||
@@ -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
|
||||
+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(())
|
||||
}
|
||||
}
|
||||
+147
@@ -0,0 +1,147 @@
|
||||
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,
|
||||
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 EnqueueNanoBananaProEditImageAspectRatio {
|
||||
// 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 {
|
||||
// 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,8 @@
|
||||
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_nano_banana_pro_edit_image_webhook;
|
||||
pub mod enqueue_qwen_image_edit_webhook;
|
||||
pub mod enqueue_seededit_v3_edit_webhook;
|
||||
@@ -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(())
|
||||
}
|
||||
}
|
||||
+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,3 @@
|
||||
pub mod enqueue_bytedance_seedream_v4_text_to_image_webhook;
|
||||
pub mod enqueue_bytedance_seedream_v4p5_text_to_image_webhook;
|
||||
pub mod enqueue_nano_banana_pro_text_to_image_webhook;
|
||||
+70
@@ -0,0 +1,70 @@
|
||||
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::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,
|
||||
}
|
||||
|
||||
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(())
|
||||
}
|
||||
}
|
||||
+70
@@ -0,0 +1,70 @@
|
||||
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::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,
|
||||
}
|
||||
|
||||
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(())
|
||||
}
|
||||
}
|
||||
+90
@@ -0,0 +1,90 @@
|
||||
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::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,
|
||||
}
|
||||
|
||||
pub async fn enqueue_kling_v2p5_turbo_pro_image_to_video_webhook<R: IntoUrl>(
|
||||
args: EnqueueKlingV2p5TurboProImageToVideoArgs<'_, R>
|
||||
) -> Result<WebhookResponse, FalErrorPlus> {
|
||||
|
||||
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(())
|
||||
}
|
||||
}
|
||||
+86
@@ -0,0 +1,86 @@
|
||||
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::webhook::WebhookResponse;
|
||||
use reqwest::IntoUrl;
|
||||
use fal::endpoints::fal_ai::kling_video::v2_5::kling_v2p5_turbo_standard_image_to_video::{kling_v2p5_turbo_standard_image_to_video, KlingV2p5TurboStandardImageToVideoInput};
|
||||
|
||||
pub struct EnqueueKlingV2p5TurboStandardImageToVideoArgs<'a, R: IntoUrl> {
|
||||
// Request required
|
||||
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,
|
||||
}
|
||||
|
||||
pub async fn enqueue_kling_v2p5_turbo_standard_image_to_video_webhook<R: IntoUrl>(
|
||||
args: EnqueueKlingV2p5TurboStandardImageToVideoArgs<'_, R>
|
||||
) -> Result<WebhookResponse, FalErrorPlus> {
|
||||
|
||||
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 errors::AnyhowResult;
|
||||
use std::fs::read_to_string;
|
||||
use test_data::web::image_urls::TREX_SKELETON_IMAGE_URL;
|
||||
use crate::requests::webhook::video::image::enqueue_kling_v2p5_turbo_standard_image_to_video_webhook::{enqueue_kling_v2p5_turbo_standard_image_to_video_webhook, EnqueueKlingV2p5TurboStandardImageToVideoArgs, EnqueueKlingV2p5TurboStandardImageToVideoDurationSeconds};
|
||||
|
||||
#[tokio::test]
|
||||
#[ignore]
|
||||
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(())
|
||||
}
|
||||
}
|
||||
+87
@@ -0,0 +1,87 @@
|
||||
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::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
|
||||
pub generate_audio: Option<bool>,
|
||||
pub negative_prompt: Option<String>,
|
||||
pub duration: Option<EnqueueKlingV2p6ProImageToVideoDurationSeconds>,
|
||||
|
||||
// Fulfillment
|
||||
pub webhook_url: R,
|
||||
pub api_key: &'a FalApiKey,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub enum EnqueueKlingV2p6ProImageToVideoDurationSeconds {
|
||||
Five,
|
||||
Ten,
|
||||
}
|
||||
|
||||
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(())
|
||||
}
|
||||
}
|
||||
+152
@@ -0,0 +1,152 @@
|
||||
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::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
|
||||
pub duration: Option<EnqueuePixverseV5ImageToVideoDurationSeconds>,
|
||||
pub aspect_ratio: Option<EnqueuePixverseV5ImageToVideoAspectRatio>,
|
||||
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,
|
||||
}
|
||||
|
||||
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(())
|
||||
}
|
||||
}
|
||||
+118
@@ -0,0 +1,118 @@
|
||||
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::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
|
||||
pub resolution: Option<EnqueueSora2ImageToVideoResolution>,
|
||||
pub duration: Option<EnqueueSora2ImageToVideoDurationSeconds>,
|
||||
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,
|
||||
}
|
||||
|
||||
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(())
|
||||
}
|
||||
}
|
||||
+120
@@ -0,0 +1,120 @@
|
||||
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::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,
|
||||
}
|
||||
|
||||
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(())
|
||||
}
|
||||
}
|
||||
+129
@@ -0,0 +1,129 @@
|
||||
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::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
|
||||
pub duration: Option<EnqueueVeo3p1FastFirstLastFrameImageToVideoDurationSeconds>,
|
||||
pub aspect_ratio: Option<EnqueueVeo3p1FastFirstLastFrameImageToVideoAspectRatio>,
|
||||
pub resolution: Option<EnqueueVeo3p1FastFirstLastFrameImageToVideoResolution>,
|
||||
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,
|
||||
Square,
|
||||
SixteenByNine,
|
||||
NineBySixteen,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub enum EnqueueVeo3p1FastFirstLastFrameImageToVideoResolution {
|
||||
SevenTwentyP,
|
||||
TenEightyP,
|
||||
}
|
||||
|
||||
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::Square => "1:1",
|
||||
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(())
|
||||
}
|
||||
}
|
||||
+124
@@ -0,0 +1,124 @@
|
||||
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::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,
|
||||
Square,
|
||||
SixteenByNine,
|
||||
NineBySixteen,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub enum EnqueueVeo3p1FastImageToVideoResolution {
|
||||
SevenTwentyP,
|
||||
TenEightyP,
|
||||
}
|
||||
|
||||
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::Square => "1:1",
|
||||
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(())
|
||||
}
|
||||
}
|
||||
+129
@@ -0,0 +1,129 @@
|
||||
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::veo::veo3_1::veo_3p1_first_last_frame_image_to_video::{veo_3p1_first_last_frame_image_to_video, Veo3p1FirstLastFrameImageToVideoInput};
|
||||
use fal::webhook::WebhookResponse;
|
||||
use reqwest::IntoUrl;
|
||||
|
||||
pub struct EnqueueVeo3p1FirstLastFrameImageToVideoArgs<'a, R: IntoUrl> {
|
||||
// Request required
|
||||
pub prompt: String,
|
||||
|
||||
// Starting frame
|
||||
pub first_frame_url: String,
|
||||
|
||||
// Ending frame
|
||||
pub last_frame_url: String,
|
||||
|
||||
// Optional args
|
||||
pub duration: Option<EnqueueVeo3p1FirstLastFrameImageToVideoDurationSeconds>,
|
||||
pub aspect_ratio: Option<EnqueueVeo3p1FirstLastFrameImageToVideoAspectRatio>,
|
||||
pub resolution: Option<EnqueueVeo3p1FirstLastFrameImageToVideoResolution>,
|
||||
pub generate_audio: Option<bool>,
|
||||
|
||||
// Fulfillment
|
||||
pub webhook_url: R,
|
||||
pub api_key: &'a FalApiKey,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub enum EnqueueVeo3p1FirstLastFrameImageToVideoDurationSeconds {
|
||||
Four,
|
||||
Six,
|
||||
Eight,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub enum EnqueueVeo3p1FirstLastFrameImageToVideoAspectRatio {
|
||||
Auto,
|
||||
Square,
|
||||
SixteenByNine,
|
||||
NineBySixteen,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub enum EnqueueVeo3p1FirstLastFrameImageToVideoResolution {
|
||||
SevenTwentyP,
|
||||
TenEightyP,
|
||||
}
|
||||
|
||||
pub async fn enqueue_veo_3p1_first_last_frame_image_to_video_webhook<R: IntoUrl>(
|
||||
args: EnqueueVeo3p1FirstLastFrameImageToVideoArgs<'_, R>
|
||||
) -> Result<WebhookResponse, FalErrorPlus> {
|
||||
|
||||
let duration = args.duration
|
||||
.map(|resolution| match resolution {
|
||||
EnqueueVeo3p1FirstLastFrameImageToVideoDurationSeconds::Four => "4s",
|
||||
EnqueueVeo3p1FirstLastFrameImageToVideoDurationSeconds::Six => "6s",
|
||||
EnqueueVeo3p1FirstLastFrameImageToVideoDurationSeconds::Eight=> "8s",
|
||||
})
|
||||
.map(|s| s.to_string());
|
||||
|
||||
let aspect_ratio = args.aspect_ratio
|
||||
.map(|aspect_ratio| match aspect_ratio {
|
||||
EnqueueVeo3p1FirstLastFrameImageToVideoAspectRatio::Auto => "auto",
|
||||
EnqueueVeo3p1FirstLastFrameImageToVideoAspectRatio::Square => "1:1",
|
||||
EnqueueVeo3p1FirstLastFrameImageToVideoAspectRatio::SixteenByNine => "16:9",
|
||||
EnqueueVeo3p1FirstLastFrameImageToVideoAspectRatio::NineBySixteen => "9:16",
|
||||
})
|
||||
.map(|s| s.to_string());
|
||||
|
||||
let resolution = args.resolution
|
||||
.map(|resolution| match resolution {
|
||||
EnqueueVeo3p1FirstLastFrameImageToVideoResolution::SevenTwentyP => "720p",
|
||||
EnqueueVeo3p1FirstLastFrameImageToVideoResolution::TenEightyP => "1080p",
|
||||
})
|
||||
.map(|s| s.to_string());
|
||||
|
||||
let request = Veo3p1FirstLastFrameImageToVideoInput {
|
||||
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_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_first_last_frame_image_to_video_webhook::{enqueue_veo_3p1_first_last_frame_image_to_video_webhook, EnqueueVeo3p1FirstLastFrameImageToVideoArgs, EnqueueVeo3p1FirstLastFrameImageToVideoAspectRatio, EnqueueVeo3p1FirstLastFrameImageToVideoDurationSeconds, EnqueueVeo3p1FirstLastFrameImageToVideoResolution};
|
||||
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 = EnqueueVeo3p1FirstLastFrameImageToVideoArgs {
|
||||
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(EnqueueVeo3p1FirstLastFrameImageToVideoDurationSeconds::Eight),
|
||||
aspect_ratio: Some(EnqueueVeo3p1FirstLastFrameImageToVideoAspectRatio::NineBySixteen),
|
||||
resolution: Some(EnqueueVeo3p1FirstLastFrameImageToVideoResolution::TenEightyP),
|
||||
generate_audio: Some(true),
|
||||
api_key: &api_key,
|
||||
webhook_url: "https://example.com/webhook",
|
||||
};
|
||||
|
||||
let result = enqueue_veo_3p1_first_last_frame_image_to_video_webhook(args).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
+124
@@ -0,0 +1,124 @@
|
||||
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::veo::veo3_1::veo_3p1_first_frame_image_to_video::{veo_3p1_first_frame_image_to_video, Veo3p1FirstFrameImageToVideoInput};
|
||||
use fal::webhook::WebhookResponse;
|
||||
use reqwest::IntoUrl;
|
||||
|
||||
pub struct EnqueueVeo3p1ImageToVideoArgs<'a, R: IntoUrl> {
|
||||
// Request required
|
||||
pub prompt: String,
|
||||
|
||||
// Starting frame
|
||||
pub image_url: String,
|
||||
|
||||
// Optional args
|
||||
pub duration: Option<EnqueueVeo3p1ImageToVideoDurationSeconds>,
|
||||
pub aspect_ratio: Option<EnqueueVeo3p1ImageToVideoAspectRatio>,
|
||||
pub resolution: Option<EnqueueVeo3p1ImageToVideoResolution>,
|
||||
pub generate_audio: Option<bool>,
|
||||
|
||||
// Fulfillment
|
||||
pub webhook_url: R,
|
||||
pub api_key: &'a FalApiKey,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub enum EnqueueVeo3p1ImageToVideoDurationSeconds {
|
||||
Four,
|
||||
Six,
|
||||
Eight,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub enum EnqueueVeo3p1ImageToVideoAspectRatio {
|
||||
Auto,
|
||||
Square,
|
||||
SixteenByNine,
|
||||
NineBySixteen,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub enum EnqueueVeo3p1ImageToVideoResolution {
|
||||
SevenTwentyP,
|
||||
TenEightyP,
|
||||
}
|
||||
|
||||
pub async fn enqueue_veo_3p1_image_to_video_webhook<R: IntoUrl>(
|
||||
args: EnqueueVeo3p1ImageToVideoArgs<'_, R>
|
||||
) -> Result<WebhookResponse, FalErrorPlus> {
|
||||
|
||||
let duration = args.duration
|
||||
.map(|resolution| match resolution {
|
||||
EnqueueVeo3p1ImageToVideoDurationSeconds::Four => "4s",
|
||||
EnqueueVeo3p1ImageToVideoDurationSeconds::Six => "6s",
|
||||
EnqueueVeo3p1ImageToVideoDurationSeconds::Eight=> "8s",
|
||||
})
|
||||
.map(|s| s.to_string());
|
||||
|
||||
let aspect_ratio = args.aspect_ratio
|
||||
.map(|aspect_ratio| match aspect_ratio {
|
||||
EnqueueVeo3p1ImageToVideoAspectRatio::Auto => "auto",
|
||||
EnqueueVeo3p1ImageToVideoAspectRatio::Square => "1:1",
|
||||
EnqueueVeo3p1ImageToVideoAspectRatio::SixteenByNine => "16:9",
|
||||
EnqueueVeo3p1ImageToVideoAspectRatio::NineBySixteen => "9:16",
|
||||
})
|
||||
.map(|s| s.to_string());
|
||||
|
||||
let resolution = args.resolution
|
||||
.map(|resolution| match resolution {
|
||||
EnqueueVeo3p1ImageToVideoResolution::SevenTwentyP => "720p",
|
||||
EnqueueVeo3p1ImageToVideoResolution::TenEightyP => "1080p",
|
||||
})
|
||||
.map(|s| s.to_string());
|
||||
|
||||
let request = Veo3p1FirstFrameImageToVideoInput {
|
||||
prompt: args.prompt,
|
||||
image_url: args.image_url,
|
||||
generate_audio: args.generate_audio,
|
||||
// Optionals
|
||||
duration,
|
||||
aspect_ratio,
|
||||
resolution,
|
||||
};
|
||||
|
||||
let result = veo_3p1_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_image_to_video_webhook::{enqueue_veo_3p1_image_to_video_webhook, EnqueueVeo3p1ImageToVideoArgs, EnqueueVeo3p1ImageToVideoAspectRatio, EnqueueVeo3p1ImageToVideoDurationSeconds, EnqueueVeo3p1ImageToVideoResolution};
|
||||
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 = EnqueueVeo3p1ImageToVideoArgs {
|
||||
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(EnqueueVeo3p1ImageToVideoDurationSeconds::Eight),
|
||||
aspect_ratio: Some(EnqueueVeo3p1ImageToVideoAspectRatio::NineBySixteen),
|
||||
resolution: Some(EnqueueVeo3p1ImageToVideoResolution::TenEightyP),
|
||||
generate_audio: Some(true),
|
||||
api_key: &api_key,
|
||||
webhook_url: "https://example.com/webhook",
|
||||
};
|
||||
|
||||
let result = enqueue_veo_3p1_image_to_video_webhook(args).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
pub mod enqueue_hailuo_v2p3_fast_pro_image_to_video_webhook;
|
||||
pub mod enqueue_hailuo_v2p3_pro_image_to_video_webhook;
|
||||
pub mod enqueue_kling_v2p5_turbo_pro_image_to_video_webhook;
|
||||
pub mod enqueue_kling_v2p5_turbo_standard_image_to_video_webhook;
|
||||
pub mod enqueue_kling_v2p6_pro_image_to_video_webhook;
|
||||
pub mod enqueue_pixverse_v5_image_to_video_webhook;
|
||||
pub mod enqueue_sora_2_image_to_video_webhook;
|
||||
pub mod enqueue_sora_2_pro_image_to_video_webhook;
|
||||
pub mod enqueue_veo_3p1_fast_first_last_frame_image_to_video_webhook;
|
||||
pub mod enqueue_veo_3p1_fast_image_to_video_webhook;
|
||||
pub mod enqueue_veo_3p1_first_last_frame_image_to_video_webhook;
|
||||
pub mod enqueue_veo_3p1_image_to_video_webhook;
|
||||
@@ -5,4 +5,6 @@ pub mod enqueue_seedance_1_lite_image_to_video_webhook;
|
||||
pub mod enqueue_seedance_1_pro_image_to_video_webhook;
|
||||
pub mod enqueue_veo_2_image_to_video_webhook;
|
||||
pub mod enqueue_veo_3_fast_image_to_video_webhook;
|
||||
pub mod enqueue_veo_3_image_to_video_webhook;
|
||||
pub mod enqueue_veo_3_image_to_video_webhook;
|
||||
pub mod image;
|
||||
pub mod text;
|
||||
+66
@@ -0,0 +1,66 @@
|
||||
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::minimax::hailuo::hailuo_v2p3_pro_text_to_video::{hailuo_v2p3_pro_text_to_video, HailuoV2p3ProTextToVideoInput};
|
||||
use fal::webhook::WebhookResponse;
|
||||
use reqwest::IntoUrl;
|
||||
|
||||
pub struct EnqueueHailuoV2p3ProTextToVideoArgs<'a, R: IntoUrl> {
|
||||
// Request required
|
||||
pub prompt: String,
|
||||
|
||||
// Optional args
|
||||
pub prompt_optimizer: Option<bool>,
|
||||
|
||||
// Fulfillment
|
||||
pub webhook_url: R,
|
||||
pub api_key: &'a FalApiKey,
|
||||
}
|
||||
|
||||
pub async fn enqueue_hailuo_v2p3_pro_text_to_video_webhook<R: IntoUrl>(
|
||||
args: EnqueueHailuoV2p3ProTextToVideoArgs<'_, R>
|
||||
) -> Result<WebhookResponse, FalErrorPlus> {
|
||||
|
||||
let prompt_optimizer = args.prompt_optimizer.unwrap_or(true);
|
||||
|
||||
let request = HailuoV2p3ProTextToVideoInput {
|
||||
prompt: args.prompt,
|
||||
// Optionals
|
||||
prompt_optimizer: Some(prompt_optimizer),
|
||||
};
|
||||
|
||||
let result = hailuo_v2p3_pro_text_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::text::enqueue_hailuo_v2p3_pro_text_to_video_webhook::{enqueue_hailuo_v2p3_pro_text_to_video_webhook, EnqueueHailuoV2p3ProTextToVideoArgs};
|
||||
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 = EnqueueHailuoV2p3ProTextToVideoArgs {
|
||||
prompt: "a gray alien with big eyes dressed in an american flag tank top gives the peace symbol, it then barbecues some hot dogs on the grill".to_string(),
|
||||
prompt_optimizer: Some(true),
|
||||
api_key: &api_key,
|
||||
webhook_url: "https://example.com/webhook",
|
||||
};
|
||||
|
||||
let result = enqueue_hailuo_v2p3_pro_text_to_video_webhook(args).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
+100
@@ -0,0 +1,100 @@
|
||||
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::kling_video::v2_5::kling_v2p5_turbo_pro_text_to_video::{kling_v2p5_turbo_pro_text_to_video, KlingV2p5TurboProTextToVideoInput};
|
||||
use fal::webhook::WebhookResponse;
|
||||
use reqwest::IntoUrl;
|
||||
|
||||
pub struct EnqueueKlingV2p5TurboProTextToVideoArgs<'a, R: IntoUrl> {
|
||||
// Request required
|
||||
pub prompt: String,
|
||||
|
||||
// Optional args
|
||||
pub negative_prompt: Option<String>,
|
||||
pub duration: Option<EnqueueKlingV2p5TurboProTextToVideoDurationSeconds>,
|
||||
pub aspect_ratio: Option<EnqueueKlingV2p5TurboProTextToVideoAspectRatio>,
|
||||
|
||||
// Fulfillment
|
||||
pub webhook_url: R,
|
||||
pub api_key: &'a FalApiKey,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub enum EnqueueKlingV2p5TurboProTextToVideoDurationSeconds {
|
||||
Five,
|
||||
Ten,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub enum EnqueueKlingV2p5TurboProTextToVideoAspectRatio {
|
||||
Square,
|
||||
SixteenByNine,
|
||||
NineBySixteen,
|
||||
}
|
||||
|
||||
pub async fn enqueue_kling_v2p5_turbo_pro_text_to_video_webhook<R: IntoUrl>(
|
||||
args: EnqueueKlingV2p5TurboProTextToVideoArgs<'_, R>
|
||||
) -> Result<WebhookResponse, FalErrorPlus> {
|
||||
|
||||
let duration = args.duration
|
||||
.map(|resolution| match resolution {
|
||||
EnqueueKlingV2p5TurboProTextToVideoDurationSeconds::Five => "5",
|
||||
EnqueueKlingV2p5TurboProTextToVideoDurationSeconds::Ten => "10",
|
||||
})
|
||||
.map(|s| s.to_string());
|
||||
|
||||
let aspect_ratio = args.aspect_ratio
|
||||
.map(|aspect| match aspect {
|
||||
EnqueueKlingV2p5TurboProTextToVideoAspectRatio::Square => "1:1",
|
||||
EnqueueKlingV2p5TurboProTextToVideoAspectRatio::SixteenByNine => "16:9",
|
||||
EnqueueKlingV2p5TurboProTextToVideoAspectRatio::NineBySixteen => "9:16",
|
||||
})
|
||||
.map(|s| s.to_string());
|
||||
|
||||
let request = KlingV2p5TurboProTextToVideoInput {
|
||||
prompt: args.prompt,
|
||||
// Optionals
|
||||
duration,
|
||||
aspect_ratio,
|
||||
negative_prompt: args.negative_prompt,
|
||||
// Constants
|
||||
cfg_scale: None,
|
||||
};
|
||||
|
||||
let result = kling_v2p5_turbo_pro_text_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::text::enqueue_kling_v2p5_turbo_pro_text_to_video_webhook::{enqueue_kling_v2p5_turbo_pro_text_to_video_webhook, EnqueueKlingV2p5TurboProTextToVideoArgs, EnqueueKlingV2p5TurboProTextToVideoAspectRatio, EnqueueKlingV2p5TurboProTextToVideoDurationSeconds};
|
||||
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 = EnqueueKlingV2p5TurboProTextToVideoArgs {
|
||||
prompt: "an owl flies by an abandoned castle at dusk. the camera tracks the flying owl. the castle stones are damp and gritty. there are lots of trees. fireflies dance over the fields".to_string(),
|
||||
negative_prompt: None,
|
||||
aspect_ratio: Some(EnqueueKlingV2p5TurboProTextToVideoAspectRatio::SixteenByNine),
|
||||
duration: Some(EnqueueKlingV2p5TurboProTextToVideoDurationSeconds::Five),
|
||||
api_key: &api_key,
|
||||
webhook_url: "https://example.com/webhook",
|
||||
};
|
||||
|
||||
let result = enqueue_kling_v2p5_turbo_pro_text_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 fal::endpoints::fal_ai::kling_video::v2_6::kling_v2p6_pro_text_to_video::{kling_v2p6_pro_text_to_video, KlingV2p6ProTextToVideoInput};
|
||||
use fal::webhook::WebhookResponse;
|
||||
use reqwest::IntoUrl;
|
||||
|
||||
pub struct EnqueueKlingV2p6ProTextToVideoArgs<'a, R: IntoUrl> {
|
||||
// Request required
|
||||
pub prompt: String,
|
||||
|
||||
// Optional args
|
||||
pub generate_audio: Option<bool>,
|
||||
pub negative_prompt: Option<String>,
|
||||
pub duration: Option<EnqueueKlingV2p6ProTextToVideoDurationSeconds>,
|
||||
pub aspect_ratio: Option<EnqueueKlingV2p6ProTextToVideoAspectRatio>,
|
||||
|
||||
// Fulfillment
|
||||
pub webhook_url: R,
|
||||
pub api_key: &'a FalApiKey,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub enum EnqueueKlingV2p6ProTextToVideoDurationSeconds {
|
||||
Five,
|
||||
Ten,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub enum EnqueueKlingV2p6ProTextToVideoAspectRatio {
|
||||
Square,
|
||||
SixteenByNine,
|
||||
NineBySixteen,
|
||||
}
|
||||
|
||||
pub async fn enqueue_kling_v2p6_pro_text_to_video_webhook<R: IntoUrl>(
|
||||
args: EnqueueKlingV2p6ProTextToVideoArgs<'_, R>
|
||||
) -> Result<WebhookResponse, FalErrorPlus> {
|
||||
|
||||
let duration = args.duration
|
||||
.map(|resolution| match resolution {
|
||||
EnqueueKlingV2p6ProTextToVideoDurationSeconds::Five => "5",
|
||||
EnqueueKlingV2p6ProTextToVideoDurationSeconds::Ten => "10",
|
||||
})
|
||||
.map(|s| s.to_string());
|
||||
|
||||
let aspect_ratio = args.aspect_ratio
|
||||
.map(|aspect| match aspect {
|
||||
EnqueueKlingV2p6ProTextToVideoAspectRatio::Square => "1:1",
|
||||
EnqueueKlingV2p6ProTextToVideoAspectRatio::SixteenByNine => "16:9",
|
||||
EnqueueKlingV2p6ProTextToVideoAspectRatio::NineBySixteen => "9:16",
|
||||
})
|
||||
.map(|s| s.to_string());
|
||||
|
||||
let request = KlingV2p6ProTextToVideoInput {
|
||||
prompt: args.prompt,
|
||||
// Optionals
|
||||
generate_audio: args.generate_audio,
|
||||
duration,
|
||||
aspect_ratio,
|
||||
negative_prompt: args.negative_prompt,
|
||||
// Constants
|
||||
cfg_scale: None,
|
||||
};
|
||||
|
||||
let result = kling_v2p6_pro_text_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 errors::AnyhowResult;
|
||||
use std::fs::read_to_string;
|
||||
use crate::requests::webhook::video::text::enqueue_kling_v2p6_pro_text_to_video_webhook::{enqueue_kling_v2p6_pro_text_to_video_webhook, EnqueueKlingV2p6ProTextToVideoArgs, EnqueueKlingV2p6ProTextToVideoAspectRatio, EnqueueKlingV2p6ProTextToVideoDurationSeconds};
|
||||
|
||||
#[tokio::test]
|
||||
#[ignore]
|
||||
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 = EnqueueKlingV2p6ProTextToVideoArgs {
|
||||
prompt: "a humanoid lizard creature walks around on the surface of an alien planet. the sky is purple and there are yellow plants. The lizard says, 'You should consider day trading'. Suddenly a giant t-rex comes in from off screen and eats the lizard creature.".to_string(),
|
||||
negative_prompt: None,
|
||||
aspect_ratio: Some(EnqueueKlingV2p6ProTextToVideoAspectRatio::SixteenByNine),
|
||||
duration: Some(EnqueueKlingV2p6ProTextToVideoDurationSeconds::Ten),
|
||||
generate_audio: Some(true),
|
||||
api_key: &api_key,
|
||||
webhook_url: "https://example.com/webhook",
|
||||
};
|
||||
|
||||
let result = enqueue_kling_v2p6_pro_text_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 fal::endpoints::fal_ai::pixverse::v5::pixverse_v5_text_to_video::{pixverse_v5_text_to_video, PixverseV5TextToVideoInput};
|
||||
use fal::webhook::WebhookResponse;
|
||||
use reqwest::IntoUrl;
|
||||
|
||||
pub struct EnqueuePixverseV5TextToVideoArgs<'a, R: IntoUrl> {
|
||||
// Request required
|
||||
pub prompt: String,
|
||||
|
||||
// Optional args
|
||||
pub negative_prompt: Option<String>,
|
||||
|
||||
// NB: eight-second videos do not work with 1080P
|
||||
pub duration: Option<EnqueuePixverseV5TextToVideoDurationSeconds>,
|
||||
pub aspect_ratio: Option<EnqueuePixverseV5TextToVideoAspectRatio>,
|
||||
pub resolution: Option<EnqueuePixverseV5TextToVideoResolution>,
|
||||
pub style: Option<EnqueuePixverseV5TextToVideoStyle>,
|
||||
|
||||
// Fulfillment
|
||||
pub webhook_url: R,
|
||||
pub api_key: &'a FalApiKey,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub enum EnqueuePixverseV5TextToVideoDurationSeconds {
|
||||
Five,
|
||||
Eight,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub enum EnqueuePixverseV5TextToVideoResolution {
|
||||
ThreeSixtyP,
|
||||
FiveFortyP,
|
||||
SevenTwentyP,
|
||||
TenEightyP,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub enum EnqueuePixverseV5TextToVideoAspectRatio {
|
||||
Square,
|
||||
SixteenByNine,
|
||||
FourByThree,
|
||||
NineBySixteen,
|
||||
ThreeByFour,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub enum EnqueuePixverseV5TextToVideoStyle {
|
||||
Anime,
|
||||
Animation3d,
|
||||
Clay,
|
||||
Comic,
|
||||
Cyberpunk,
|
||||
}
|
||||
|
||||
pub async fn enqueue_pixverse_v5_text_to_video_webhook<R: IntoUrl>(
|
||||
args: EnqueuePixverseV5TextToVideoArgs<'_, R>
|
||||
) -> Result<WebhookResponse, FalErrorPlus> {
|
||||
|
||||
let duration = args.duration
|
||||
.map(|resolution| match resolution {
|
||||
EnqueuePixverseV5TextToVideoDurationSeconds::Five => "5",
|
||||
EnqueuePixverseV5TextToVideoDurationSeconds::Eight=> "8",
|
||||
})
|
||||
.map(|s| s.to_string());
|
||||
|
||||
let aspect_ratio = args.aspect_ratio
|
||||
.map(|aspect_ratio| match aspect_ratio {
|
||||
EnqueuePixverseV5TextToVideoAspectRatio::Square => "1:1",
|
||||
EnqueuePixverseV5TextToVideoAspectRatio::SixteenByNine => "16:9",
|
||||
EnqueuePixverseV5TextToVideoAspectRatio::FourByThree => "4:3",
|
||||
EnqueuePixverseV5TextToVideoAspectRatio::NineBySixteen => "9:16",
|
||||
EnqueuePixverseV5TextToVideoAspectRatio::ThreeByFour => "3:4",
|
||||
})
|
||||
.map(|s| s.to_string());
|
||||
|
||||
let resolution = args.resolution
|
||||
.map(|resolution| match resolution {
|
||||
EnqueuePixverseV5TextToVideoResolution::ThreeSixtyP => "360p",
|
||||
EnqueuePixverseV5TextToVideoResolution::FiveFortyP => "540p",
|
||||
EnqueuePixverseV5TextToVideoResolution::SevenTwentyP => "720p",
|
||||
EnqueuePixverseV5TextToVideoResolution::TenEightyP => "1080p",
|
||||
})
|
||||
.map(|s| s.to_string());
|
||||
|
||||
let style = args.style
|
||||
.map(|style| match style {
|
||||
EnqueuePixverseV5TextToVideoStyle::Anime => "anime",
|
||||
EnqueuePixverseV5TextToVideoStyle::Animation3d => "3d_animation",
|
||||
EnqueuePixverseV5TextToVideoStyle::Clay => "clay",
|
||||
EnqueuePixverseV5TextToVideoStyle::Comic => "comic",
|
||||
EnqueuePixverseV5TextToVideoStyle::Cyberpunk => "cyberpunk",
|
||||
})
|
||||
.map(|s| s.to_string());
|
||||
|
||||
let request = PixverseV5TextToVideoInput {
|
||||
prompt: args.prompt,
|
||||
// Optionals
|
||||
duration,
|
||||
negative_prompt: args.negative_prompt,
|
||||
aspect_ratio,
|
||||
resolution,
|
||||
style,
|
||||
// Constant
|
||||
seed: None,
|
||||
};
|
||||
|
||||
let result = pixverse_v5_text_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::text::enqueue_pixverse_v5_text_to_video_webhook::{enqueue_pixverse_v5_text_to_video_webhook, EnqueuePixverseV5TextToVideoArgs, EnqueuePixverseV5TextToVideoAspectRatio, EnqueuePixverseV5TextToVideoDurationSeconds, EnqueuePixverseV5TextToVideoResolution, EnqueuePixverseV5TextToVideoStyle};
|
||||
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 = EnqueuePixverseV5TextToVideoArgs {
|
||||
prompt: "an angry racoon shakes its fist at the garbage truck as it drives away, the camera orbits the racoon. the racoon sighs".to_string(),
|
||||
negative_prompt: None,
|
||||
duration: Some(EnqueuePixverseV5TextToVideoDurationSeconds::Eight),
|
||||
style: Some(EnqueuePixverseV5TextToVideoStyle::Anime),
|
||||
aspect_ratio: Some(EnqueuePixverseV5TextToVideoAspectRatio::FourByThree),
|
||||
resolution: Some(EnqueuePixverseV5TextToVideoResolution::SevenTwentyP),
|
||||
api_key: &api_key,
|
||||
webhook_url: "https://example.com/webhook",
|
||||
};
|
||||
|
||||
let result = enqueue_pixverse_v5_text_to_video_webhook(args).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
+116
@@ -0,0 +1,116 @@
|
||||
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::webhook::WebhookResponse;
|
||||
use reqwest::IntoUrl;
|
||||
use fal::endpoints::fal_ai::sora::sora2::sora_2_pro_text_to_video::{sora_2_pro_text_to_video, Sora2ProTextToVideoInput};
|
||||
|
||||
pub struct EnqueueSora2ProTextToVideoArgs<'a, R: IntoUrl> {
|
||||
// Request required
|
||||
pub prompt: String,
|
||||
|
||||
// Optional args
|
||||
pub resolution: Option<EnqueueSora2ProTextToVideoResolution>,
|
||||
pub duration: Option<EnqueueSora2ProTextToVideoDurationSeconds>,
|
||||
pub aspect_ratio: Option<EnqueueSora2ProTextToVideoAspectRatio>,
|
||||
|
||||
// Fulfillment
|
||||
pub webhook_url: R,
|
||||
pub api_key: &'a FalApiKey,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub enum EnqueueSora2ProTextToVideoDurationSeconds {
|
||||
Four,
|
||||
Eight,
|
||||
Twelve,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub enum EnqueueSora2ProTextToVideoResolution {
|
||||
Auto,
|
||||
SevenTwentyP,
|
||||
TenEightyP,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub enum EnqueueSora2ProTextToVideoAspectRatio {
|
||||
Auto,
|
||||
NineBySixteen,
|
||||
SixteenByNine,
|
||||
}
|
||||
|
||||
pub async fn enqueue_sora_2_pro_text_to_video_webhook<R: IntoUrl>(
|
||||
args: EnqueueSora2ProTextToVideoArgs<'_, R>
|
||||
) -> Result<WebhookResponse, FalErrorPlus> {
|
||||
|
||||
let duration = args.duration
|
||||
.map(|resolution| match resolution {
|
||||
EnqueueSora2ProTextToVideoDurationSeconds::Four => 4,
|
||||
EnqueueSora2ProTextToVideoDurationSeconds::Eight => 8,
|
||||
EnqueueSora2ProTextToVideoDurationSeconds::Twelve => 12,
|
||||
});
|
||||
|
||||
let resolution = args.resolution
|
||||
.map(|resolution| match resolution {
|
||||
EnqueueSora2ProTextToVideoResolution::Auto => "auto",
|
||||
EnqueueSora2ProTextToVideoResolution::SevenTwentyP => "720p",
|
||||
EnqueueSora2ProTextToVideoResolution::TenEightyP => "1080p",
|
||||
})
|
||||
.map(|resolution| resolution.to_string());
|
||||
|
||||
let aspect_ratio = args.aspect_ratio
|
||||
.map(|aspect_ratio| match aspect_ratio {
|
||||
EnqueueSora2ProTextToVideoAspectRatio::Auto => "auto",
|
||||
EnqueueSora2ProTextToVideoAspectRatio::NineBySixteen => "9:16",
|
||||
EnqueueSora2ProTextToVideoAspectRatio::SixteenByNine => "16:9",
|
||||
})
|
||||
.map(|resolution| resolution.to_string());
|
||||
|
||||
let request = Sora2ProTextToVideoInput {
|
||||
prompt: args.prompt,
|
||||
// Optionals
|
||||
duration,
|
||||
resolution,
|
||||
aspect_ratio,
|
||||
// Constants
|
||||
delete_video: Some(false),
|
||||
};
|
||||
|
||||
let result = sora_2_pro_text_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 errors::AnyhowResult;
|
||||
use std::fs::read_to_string;
|
||||
use crate::requests::webhook::video::text::enqueue_sora_2_pro_text_to_video_webhook::{enqueue_sora_2_pro_text_to_video_webhook, EnqueueSora2ProTextToVideoArgs, EnqueueSora2ProTextToVideoAspectRatio, EnqueueSora2ProTextToVideoDurationSeconds, EnqueueSora2ProTextToVideoResolution};
|
||||
|
||||
#[tokio::test]
|
||||
#[ignore]
|
||||
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 = EnqueueSora2ProTextToVideoArgs {
|
||||
prompt: "a dinosaur turns to the camera and asks, 'do you have adequate car insurance?' it then stomps off and attacks a brontosaurus".to_string(),
|
||||
duration: Some(EnqueueSora2ProTextToVideoDurationSeconds::Eight),
|
||||
aspect_ratio: Some(EnqueueSora2ProTextToVideoAspectRatio::NineBySixteen),
|
||||
resolution: Some(EnqueueSora2ProTextToVideoResolution::SevenTwentyP),
|
||||
api_key: &api_key,
|
||||
webhook_url: "https://example.com/webhook",
|
||||
};
|
||||
|
||||
let result = enqueue_sora_2_pro_text_to_video_webhook(args).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
+114
@@ -0,0 +1,114 @@
|
||||
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::sora::sora2::sora_2_text_to_video::{sora_2_text_to_video, Sora2TextToVideoInput};
|
||||
use fal::webhook::WebhookResponse;
|
||||
use reqwest::IntoUrl;
|
||||
|
||||
pub struct EnqueueSora2TextToVideoArgs<'a, R: IntoUrl> {
|
||||
// Request required
|
||||
pub prompt: String,
|
||||
|
||||
// Optional args
|
||||
pub resolution: Option<EnqueueSora2TextToVideoResolution>,
|
||||
pub duration: Option<EnqueueSora2TextToVideoDurationSeconds>,
|
||||
pub aspect_ratio: Option<EnqueueSora2TextToVideoAspectRatio>,
|
||||
|
||||
// Fulfillment
|
||||
pub webhook_url: R,
|
||||
pub api_key: &'a FalApiKey,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub enum EnqueueSora2TextToVideoDurationSeconds {
|
||||
Four,
|
||||
Eight,
|
||||
Twelve,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub enum EnqueueSora2TextToVideoResolution {
|
||||
Auto,
|
||||
SevenTwentyP,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub enum EnqueueSora2TextToVideoAspectRatio {
|
||||
Auto,
|
||||
NineBySixteen,
|
||||
SixteenByNine,
|
||||
}
|
||||
|
||||
pub async fn enqueue_sora_2_text_to_video_webhook<R: IntoUrl>(
|
||||
args: EnqueueSora2TextToVideoArgs<'_, R>
|
||||
) -> Result<WebhookResponse, FalErrorPlus> {
|
||||
|
||||
let duration = args.duration
|
||||
.map(|resolution| match resolution {
|
||||
EnqueueSora2TextToVideoDurationSeconds::Four => 4,
|
||||
EnqueueSora2TextToVideoDurationSeconds::Eight => 8,
|
||||
EnqueueSora2TextToVideoDurationSeconds::Twelve => 12,
|
||||
});
|
||||
|
||||
let resolution = args.resolution
|
||||
.map(|resolution| match resolution {
|
||||
EnqueueSora2TextToVideoResolution::Auto => "auto",
|
||||
EnqueueSora2TextToVideoResolution::SevenTwentyP => "720p",
|
||||
})
|
||||
.map(|resolution| resolution.to_string());
|
||||
|
||||
let aspect_ratio = args.aspect_ratio
|
||||
.map(|aspect_ratio| match aspect_ratio {
|
||||
EnqueueSora2TextToVideoAspectRatio::Auto => "auto",
|
||||
EnqueueSora2TextToVideoAspectRatio::NineBySixteen => "9:16",
|
||||
EnqueueSora2TextToVideoAspectRatio::SixteenByNine => "16:9",
|
||||
})
|
||||
.map(|resolution| resolution.to_string());
|
||||
|
||||
let request = Sora2TextToVideoInput {
|
||||
prompt: args.prompt,
|
||||
// Optionals
|
||||
duration,
|
||||
resolution,
|
||||
aspect_ratio,
|
||||
// Constants
|
||||
delete_video: Some(false),
|
||||
};
|
||||
|
||||
let result = sora_2_text_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::text::enqueue_sora_2_text_to_video_webhook::{enqueue_sora_2_text_to_video_webhook, EnqueueSora2TextToVideoArgs, EnqueueSora2TextToVideoAspectRatio, EnqueueSora2TextToVideoDurationSeconds, EnqueueSora2TextToVideoResolution};
|
||||
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 = EnqueueSora2TextToVideoArgs {
|
||||
prompt: "a dinosaur turns to the camera and asks, 'do you have adequate car insurance?' it then stomps off and attacks a brontosaurus".to_string(),
|
||||
duration: Some(EnqueueSora2TextToVideoDurationSeconds::Eight),
|
||||
aspect_ratio: Some(EnqueueSora2TextToVideoAspectRatio::NineBySixteen),
|
||||
resolution: Some(EnqueueSora2TextToVideoResolution::SevenTwentyP),
|
||||
api_key: &api_key,
|
||||
webhook_url: "https://example.com/webhook",
|
||||
};
|
||||
|
||||
let result = enqueue_sora_2_text_to_video_webhook(args).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
+130
@@ -0,0 +1,130 @@
|
||||
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::veo::veo3_1::veo_3p1_fast_text_to_video::{veo_3p1_fast_text_to_video, Veo3p1FastTextToVideoInput};
|
||||
use fal::webhook::WebhookResponse;
|
||||
use reqwest::IntoUrl;
|
||||
|
||||
pub struct EnqueueVeo3p1FastTextToVideoArgs<'a, R: IntoUrl> {
|
||||
// Request required
|
||||
pub prompt: String,
|
||||
|
||||
// Optional args
|
||||
pub duration: Option<EnqueueVeo3p1FastTextToVideoDurationSeconds>,
|
||||
pub aspect_ratio: Option<EnqueueVeo3p1FastTextToVideoAspectRatio>,
|
||||
pub resolution: Option<EnqueueVeo3p1FastTextToVideoResolution>,
|
||||
pub generate_audio: Option<bool>,
|
||||
pub enhance_prompt: Option<bool>,
|
||||
pub negative_prompt: Option<String>,
|
||||
pub seed: Option<i64>,
|
||||
pub auto_fix: Option<bool>,
|
||||
|
||||
// Fulfillment
|
||||
pub webhook_url: R,
|
||||
pub api_key: &'a FalApiKey,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub enum EnqueueVeo3p1FastTextToVideoDurationSeconds {
|
||||
Four,
|
||||
Six,
|
||||
Eight,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub enum EnqueueVeo3p1FastTextToVideoAspectRatio {
|
||||
Auto,
|
||||
Square,
|
||||
SixteenByNine,
|
||||
NineBySixteen,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub enum EnqueueVeo3p1FastTextToVideoResolution {
|
||||
SevenTwentyP,
|
||||
TenEightyP,
|
||||
}
|
||||
|
||||
pub async fn enqueue_veo_3p1_fast_text_to_video_webhook<R: IntoUrl>(
|
||||
args: EnqueueVeo3p1FastTextToVideoArgs<'_, R>
|
||||
) -> Result<WebhookResponse, FalErrorPlus> {
|
||||
|
||||
let duration = args.duration
|
||||
.map(|resolution| match resolution {
|
||||
EnqueueVeo3p1FastTextToVideoDurationSeconds::Four => "4s",
|
||||
EnqueueVeo3p1FastTextToVideoDurationSeconds::Six => "6s",
|
||||
EnqueueVeo3p1FastTextToVideoDurationSeconds::Eight=> "8s",
|
||||
})
|
||||
.map(|s| s.to_string());
|
||||
|
||||
let aspect_ratio = args.aspect_ratio
|
||||
.map(|aspect_ratio| match aspect_ratio {
|
||||
EnqueueVeo3p1FastTextToVideoAspectRatio::Auto => "auto",
|
||||
EnqueueVeo3p1FastTextToVideoAspectRatio::Square => "1:1",
|
||||
EnqueueVeo3p1FastTextToVideoAspectRatio::SixteenByNine => "16:9",
|
||||
EnqueueVeo3p1FastTextToVideoAspectRatio::NineBySixteen => "9:16",
|
||||
})
|
||||
.map(|s| s.to_string());
|
||||
|
||||
let resolution = args.resolution
|
||||
.map(|resolution| match resolution {
|
||||
EnqueueVeo3p1FastTextToVideoResolution::SevenTwentyP => "720p",
|
||||
EnqueueVeo3p1FastTextToVideoResolution::TenEightyP => "1080p",
|
||||
})
|
||||
.map(|s| s.to_string());
|
||||
|
||||
let request = Veo3p1FastTextToVideoInput {
|
||||
prompt: args.prompt,
|
||||
// Optionals
|
||||
duration,
|
||||
aspect_ratio,
|
||||
resolution,
|
||||
negative_prompt: args.negative_prompt,
|
||||
generate_audio: args.generate_audio,
|
||||
enhance_prompt: args.enhance_prompt,
|
||||
seed: args.seed,
|
||||
auto_fix: args.auto_fix,
|
||||
};
|
||||
|
||||
let result = veo_3p1_fast_text_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::text::enqueue_veo_3p1_fast_text_to_video_webhook::{enqueue_veo_3p1_fast_text_to_video_webhook, EnqueueVeo3p1FastTextToVideoArgs, EnqueueVeo3p1FastTextToVideoAspectRatio, EnqueueVeo3p1FastTextToVideoDurationSeconds, EnqueueVeo3p1FastTextToVideoResolution};
|
||||
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 = EnqueueVeo3p1FastTextToVideoArgs {
|
||||
prompt: "An alien space ship hovers over new york city. it looks ominous, ready to attack. suddenly, it drops a bunch of ping pong balls on the city".to_string(),
|
||||
duration: Some(EnqueueVeo3p1FastTextToVideoDurationSeconds::Eight),
|
||||
aspect_ratio: Some(EnqueueVeo3p1FastTextToVideoAspectRatio::SixteenByNine),
|
||||
resolution: Some(EnqueueVeo3p1FastTextToVideoResolution::TenEightyP),
|
||||
generate_audio: Some(true),
|
||||
negative_prompt: None,
|
||||
enhance_prompt: Some(true),
|
||||
auto_fix: Some(true),
|
||||
seed: None,
|
||||
api_key: &api_key,
|
||||
webhook_url: "https://example.com/webhook",
|
||||
};
|
||||
|
||||
let result = enqueue_veo_3p1_fast_text_to_video_webhook(args).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
+130
@@ -0,0 +1,130 @@
|
||||
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::veo::veo3_1::veo_3p1_text_to_video::{veo_3p1_text_to_video, Veo3p1TextToVideoInput};
|
||||
use fal::webhook::WebhookResponse;
|
||||
use reqwest::IntoUrl;
|
||||
|
||||
pub struct EnqueueVeo3p1TextToVideoArgs<'a, R: IntoUrl> {
|
||||
// Request required
|
||||
pub prompt: String,
|
||||
|
||||
// Optional args
|
||||
pub duration: Option<EnqueueVeo3p1TextToVideoDurationSeconds>,
|
||||
pub aspect_ratio: Option<EnqueueVeo3p1TextToVideoAspectRatio>,
|
||||
pub resolution: Option<EnqueueVeo3p1TextToVideoResolution>,
|
||||
pub generate_audio: Option<bool>,
|
||||
pub enhance_prompt: Option<bool>,
|
||||
pub negative_prompt: Option<String>,
|
||||
pub seed: Option<i64>,
|
||||
pub auto_fix: Option<bool>,
|
||||
|
||||
// Fulfillment
|
||||
pub webhook_url: R,
|
||||
pub api_key: &'a FalApiKey,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub enum EnqueueVeo3p1TextToVideoDurationSeconds {
|
||||
Four,
|
||||
Six,
|
||||
Eight,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub enum EnqueueVeo3p1TextToVideoAspectRatio {
|
||||
Auto,
|
||||
Square,
|
||||
SixteenByNine,
|
||||
NineBySixteen,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub enum EnqueueVeo3p1TextToVideoResolution {
|
||||
SevenTwentyP,
|
||||
TenEightyP,
|
||||
}
|
||||
|
||||
pub async fn enqueue_veo_3p1_text_to_video_webhook<R: IntoUrl>(
|
||||
args: EnqueueVeo3p1TextToVideoArgs<'_, R>
|
||||
) -> Result<WebhookResponse, FalErrorPlus> {
|
||||
|
||||
let duration = args.duration
|
||||
.map(|resolution| match resolution {
|
||||
EnqueueVeo3p1TextToVideoDurationSeconds::Four => "4s",
|
||||
EnqueueVeo3p1TextToVideoDurationSeconds::Six => "6s",
|
||||
EnqueueVeo3p1TextToVideoDurationSeconds::Eight=> "8s",
|
||||
})
|
||||
.map(|s| s.to_string());
|
||||
|
||||
let aspect_ratio = args.aspect_ratio
|
||||
.map(|aspect_ratio| match aspect_ratio {
|
||||
EnqueueVeo3p1TextToVideoAspectRatio::Auto => "auto",
|
||||
EnqueueVeo3p1TextToVideoAspectRatio::Square => "1:1",
|
||||
EnqueueVeo3p1TextToVideoAspectRatio::SixteenByNine => "16:9",
|
||||
EnqueueVeo3p1TextToVideoAspectRatio::NineBySixteen => "9:16",
|
||||
})
|
||||
.map(|s| s.to_string());
|
||||
|
||||
let resolution = args.resolution
|
||||
.map(|resolution| match resolution {
|
||||
EnqueueVeo3p1TextToVideoResolution::SevenTwentyP => "720p",
|
||||
EnqueueVeo3p1TextToVideoResolution::TenEightyP => "1080p",
|
||||
})
|
||||
.map(|s| s.to_string());
|
||||
|
||||
let request = Veo3p1TextToVideoInput {
|
||||
prompt: args.prompt,
|
||||
// Optionals
|
||||
duration,
|
||||
aspect_ratio,
|
||||
resolution,
|
||||
negative_prompt: args.negative_prompt,
|
||||
generate_audio: args.generate_audio,
|
||||
enhance_prompt: args.enhance_prompt,
|
||||
seed: args.seed,
|
||||
auto_fix: args.auto_fix,
|
||||
};
|
||||
|
||||
let result = veo_3p1_text_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::text::enqueue_veo_3p1_text_to_video_webhook::{enqueue_veo_3p1_text_to_video_webhook, EnqueueVeo3p1TextToVideoArgs, EnqueueVeo3p1TextToVideoAspectRatio, EnqueueVeo3p1TextToVideoDurationSeconds, EnqueueVeo3p1TextToVideoResolution};
|
||||
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 = EnqueueVeo3p1TextToVideoArgs {
|
||||
prompt: "An alien space ship hovers over new york city. it looks ominous, ready to attack. suddenly, it drops a bunch of ping pong balls on the city".to_string(),
|
||||
duration: Some(EnqueueVeo3p1TextToVideoDurationSeconds::Eight),
|
||||
aspect_ratio: Some(EnqueueVeo3p1TextToVideoAspectRatio::SixteenByNine),
|
||||
resolution: Some(EnqueueVeo3p1TextToVideoResolution::TenEightyP),
|
||||
generate_audio: Some(true),
|
||||
negative_prompt: None,
|
||||
enhance_prompt: Some(true),
|
||||
auto_fix: Some(true),
|
||||
seed: None,
|
||||
api_key: &api_key,
|
||||
webhook_url: "https://example.com/webhook",
|
||||
};
|
||||
|
||||
let result = enqueue_veo_3p1_text_to_video_webhook(args).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
pub mod enqueue_hailuo_v2p3_pro_text_to_video_webhook;
|
||||
pub mod enqueue_kling_v2p5_turbo_pro_text_to_video_webhook;
|
||||
pub mod enqueue_kling_v2p6_pro_text_to_video_webhook;
|
||||
pub mod enqueue_pixverse_v5_text_to_video_webhook;
|
||||
pub mod enqueue_sora_2_pro_text_to_video_webhook;
|
||||
pub mod enqueue_sora_2_text_to_video_webhook;
|
||||
pub mod enqueue_veo_3p1_fast_text_to_video_webhook;
|
||||
pub mod enqueue_veo_3p1_text_to_video_webhook;
|
||||
@@ -0,0 +1,13 @@
|
||||
use serde::Serialize;
|
||||
|
||||
#[derive(Serialize, Clone, Copy, Debug)]
|
||||
pub enum AspectRatio {
|
||||
#[serde(rename = "2:3")]
|
||||
TallTwoByThree,
|
||||
|
||||
#[serde(rename = "3:2")]
|
||||
WideThreeByTwo,
|
||||
|
||||
#[serde(rename = "1:1")]
|
||||
Square,
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
pub mod aspect_ratio;
|
||||
pub mod baggage;
|
||||
pub mod file_id;
|
||||
pub mod video_generation_mode;
|
||||
pub mod post_id;
|
||||
pub mod request_id;
|
||||
pub mod sentry_trace;
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
|
||||
/// The video generation mode selector.
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub enum VideoGenerationMode {
|
||||
/// Supposedly the "NSFW" mode.
|
||||
Spicy,
|
||||
|
||||
Fun,
|
||||
|
||||
Normal,
|
||||
|
||||
/// This might be the only mode that allows for a custom text prompt
|
||||
Custom,
|
||||
}
|
||||
|
||||
impl VideoGenerationMode {
|
||||
|
||||
/// This is the value for `--mode` in video generation chats.
|
||||
pub fn as_api_mode_arg(&self) -> &'static str {
|
||||
match self {
|
||||
Self::Spicy => "extremely-spicy-or-crazy",
|
||||
Self::Fun => "extremely-crazy",
|
||||
Self::Normal => "normal",
|
||||
Self::Custom => "custom",
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -47,6 +47,18 @@ pub enum GrokClientError {
|
||||
/// Our script logic is out of date
|
||||
ScriptLogicOutOfDate,
|
||||
|
||||
/// Our script logic is out of date (SVG)
|
||||
ScriptSvgLogicOutOfDate,
|
||||
|
||||
/// Our script logic is out of date (script 1)
|
||||
Script1LogicOutOfDate,
|
||||
|
||||
/// Our script logic is out of date (script 2)
|
||||
Script2LogicOutOfDate,
|
||||
|
||||
/// Our script logic is out of date (xsid script)
|
||||
ScriptXsidLogicOutOfDate,
|
||||
|
||||
/// Signature algorithm isn't working with inputs
|
||||
BadSignatureInputs,
|
||||
|
||||
@@ -56,6 +68,9 @@ pub enum GrokClientError {
|
||||
/// Can't make request because cookies aren't present
|
||||
NoCookiesPresent,
|
||||
|
||||
/// Unknown error generating video
|
||||
ErrorGeneratingVideo,
|
||||
|
||||
// /// An error reading the file for upload.
|
||||
// FileForUploadReadError(std::io::Error),
|
||||
//
|
||||
@@ -112,9 +127,14 @@ impl Display for GrokClientError {
|
||||
Self::FailedToDecodeVerificationToken(err) => write!(f, "Failed to decode verification token: {}", err),
|
||||
Self::InvalidVerificationTokenBytes => write!(f, "Invalid verification token bytes"),
|
||||
Self::ScriptLogicOutOfDate => write!(f, "Script logic out of date"),
|
||||
Self::ScriptSvgLogicOutOfDate => write!(f, "Script logic out of date (SVG parsing)"),
|
||||
Self::Script1LogicOutOfDate => write!(f, "Script logic out of date (script 1)"),
|
||||
Self::Script2LogicOutOfDate => write!(f, "Script logic out of date (script 2)"),
|
||||
Self::ScriptXsidLogicOutOfDate => write!(f, "Script logic out of date (xsid script)"),
|
||||
Self::BadSignatureInputs => write!(f, "Bad signature inputs"),
|
||||
Self::TimeoutMathBroken => write!(f, "Timeout math is broken"),
|
||||
Self::NoCookiesPresent => write!(f, "No cookies present"),
|
||||
Self::ErrorGeneratingVideo => write!(f, "Error generating video"),
|
||||
|
||||
//Self::FileForUploadReadError(err) => write!(f, "Error reading file for upload: {}", err),
|
||||
//Self::FileForUploadHasInvalidPath => write!(f, "The file path provided for upload is invalid."),
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
pub mod prompt_websocket_image_with_retry;
|
||||
pub mod request_client_secrets;
|
||||
pub mod upload_image_and_generate_video;
|
||||
pub mod upload_image_and_generate_video_with_retry;
|
||||
@@ -0,0 +1,201 @@
|
||||
use crate::credentials::grok_cookies::GrokCookies;
|
||||
use crate::error::grok_client_error::GrokClientError;
|
||||
use crate::error::grok_error::GrokError;
|
||||
use crate::requests::image_websocket::create_listen_websocket::{create_listen_websocket, CreateListenWebsocketArgs};
|
||||
use crate::requests::image_websocket::grok_websocket::GrokWebsocket;
|
||||
use crate::requests::image_websocket::messages::websocket_client_message::{ClientMessageAspectRatio, WebsocketClientMessage};
|
||||
use log::{error, info};
|
||||
|
||||
pub struct PromptWebsocketImageWithRetryArgs<'a> {
|
||||
pub websocket: &'a mut GrokWebsocket,
|
||||
pub prompt: &'a str,
|
||||
pub aspect_ratio: ClientMessageAspectRatio,
|
||||
pub cookies: &'a GrokCookies,
|
||||
}
|
||||
|
||||
pub struct PromptWebsocketImageWithRetryResult {
|
||||
pub maybe_new_websocket: Option<GrokWebsocket>,
|
||||
}
|
||||
|
||||
|
||||
pub async fn prompt_websocket_image_with_retry(args: PromptWebsocketImageWithRetryArgs<'_>) -> Result<PromptWebsocketImageWithRetryResult, GrokError>
|
||||
{
|
||||
let mut maybe_new_websocket : Option<GrokWebsocket> = None;
|
||||
|
||||
for i in 0..3 {
|
||||
info!("Starting Grok Websocket image generation (attempt {}) ...", (i+1));
|
||||
|
||||
let message = WebsocketClientMessage::new_image_prompt(args.prompt, args.aspect_ratio);
|
||||
|
||||
let result =
|
||||
if let Some(websocket) = maybe_new_websocket.as_mut() {
|
||||
websocket.send_serializable(&message).await
|
||||
} else {
|
||||
args.websocket.send_serializable(&message).await
|
||||
};
|
||||
|
||||
match result {
|
||||
Ok(()) => {
|
||||
break;
|
||||
}
|
||||
Err(GrokError::Client(GrokClientError::WreqClientError(err))) => {
|
||||
error!("Error sending message to websocket (WreqClientError): {}", err);
|
||||
}
|
||||
Err(GrokError::Client(err)) => {
|
||||
error!("Error sending message to websocket (GrokClientError): {}", err);
|
||||
}
|
||||
Err(GrokError::ApiSpecific(err)) => {
|
||||
error!("Error sending message to websocket (GrokSpecificApiError): {}", err);
|
||||
}
|
||||
Err(GrokError::ApiGeneric(err)) => {
|
||||
error!("Error sending message to websocket (GrokGenericApiError): {}", err);
|
||||
}
|
||||
}
|
||||
|
||||
let result = create_listen_websocket(CreateListenWebsocketArgs {
|
||||
cookies: args.cookies.as_str(),
|
||||
}).await?;
|
||||
|
||||
maybe_new_websocket = Some(GrokWebsocket::new(result));
|
||||
}
|
||||
|
||||
Ok(PromptWebsocketImageWithRetryResult {
|
||||
maybe_new_websocket,
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::recipes::prompt_websocket_image_with_retry::{prompt_websocket_image_with_retry, PromptWebsocketImageWithRetryArgs};
|
||||
use crate::requests::image_websocket::create_listen_websocket::{create_listen_websocket, CreateListenWebsocketArgs};
|
||||
use crate::requests::image_websocket::grok_websocket::GrokWebsocket;
|
||||
use crate::requests::image_websocket::grok_wrapped_websocket::GrokWrappedWebsocket;
|
||||
use crate::requests::image_websocket::messages::websocket_client_message::ClientMessageAspectRatio;
|
||||
use crate::requests::image_websocket::messages::websocket_server_message::WebsocketServerMessage;
|
||||
use crate::requests::image_websocket::prompt_websocket_image::{prompt_websocket_image, PromptWebsocketImageArgs};
|
||||
use crate::test_utils::get_test_cookies::{get_test_cookies, get_typed_test_cookies};
|
||||
use errors::AnyhowResult;
|
||||
use futures::SinkExt;
|
||||
use log::warn;
|
||||
use std::io::Write;
|
||||
use std::time::Duration;
|
||||
use wreq::ws::message::{CloseCode, Message, Utf8Bytes};
|
||||
|
||||
#[tokio::test]
|
||||
#[ignore] // manually test
|
||||
async fn prompt() -> AnyhowResult<()> {
|
||||
//setup_test_logging(LevelFilter::Trace);
|
||||
|
||||
let cookies = get_typed_test_cookies()?;
|
||||
|
||||
let websocket = create_listen_websocket(CreateListenWebsocketArgs {
|
||||
cookies: cookies.as_str(),
|
||||
}).await?;
|
||||
|
||||
let prompt = "A dinosaur on stilts walking on the beach";
|
||||
|
||||
//let websocket = GrokWrappedWebsocket::new(websocket);
|
||||
let mut websocket = GrokWebsocket::new(websocket);
|
||||
|
||||
//println!("Sending...");
|
||||
//std::io::stdout().flush()?;
|
||||
|
||||
//println!("For our test, we'll close the websocket and retry...");
|
||||
//websocket.websocket
|
||||
// .close(CloseCode::NORMAL, Utf8Bytes::try_from(Vec::new())?).await?;
|
||||
|
||||
//let mut websocket = GrokWebsocket::new(websocket.websocket);
|
||||
|
||||
let result = prompt_websocket_image_with_retry(PromptWebsocketImageWithRetryArgs {
|
||||
websocket: &mut websocket,
|
||||
prompt,
|
||||
aspect_ratio: ClientMessageAspectRatio::WideThreeByTwo,
|
||||
cookies: &cookies,
|
||||
}).await?;
|
||||
|
||||
println!("Reading...");
|
||||
std::io::stdout().flush()?;
|
||||
|
||||
let mut count = 0;
|
||||
|
||||
println!("Polling.");
|
||||
|
||||
loop {
|
||||
//let maybe_message =
|
||||
// websocket.get_response_with_timeout(Duration::from_millis(1000)).await?;
|
||||
|
||||
let maybe_message =
|
||||
websocket.try_next_timeout(Duration::from_millis(1000)).await?;
|
||||
|
||||
let mut maybe_raw_text : Option<String> = None;
|
||||
|
||||
let maybe_message = match maybe_message {
|
||||
None => None,
|
||||
Some(Message::Text(text)) => {
|
||||
let maybe_message = WebsocketServerMessage::from_json_str(text.as_str())?;
|
||||
let mut text = text.as_str().to_string();
|
||||
text.truncate(100);
|
||||
//println!("Received message: {}", text);
|
||||
maybe_raw_text = Some(text);
|
||||
Some(maybe_message)
|
||||
},
|
||||
Some(_) => {
|
||||
warn!("Received non-text websocket message.");
|
||||
None
|
||||
},
|
||||
};
|
||||
|
||||
match maybe_message {
|
||||
None => {
|
||||
println!("No message received within timeout.");
|
||||
count = count + 1;
|
||||
}
|
||||
Some(message) => {
|
||||
match message {
|
||||
WebsocketServerMessage::Image(image) => {
|
||||
println!("IMAGE: {:?}", image.percentage_complete);
|
||||
if let Some(percent) = image.percentage_complete {
|
||||
if percent > 90.0 {
|
||||
println!("Image: {:?}", image);
|
||||
}
|
||||
}
|
||||
}
|
||||
WebsocketServerMessage::Json(json) => {
|
||||
println!("JSON : {:?}", json.percentage_complete);
|
||||
if let Some(percent) = json.percentage_complete {
|
||||
if percent > 90.0 {
|
||||
println!("Image: {:?}", json);
|
||||
}
|
||||
}
|
||||
}
|
||||
WebsocketServerMessage::Unknown(unknown) => {
|
||||
//let typ = unknown.get("type");
|
||||
let unknown_string = unknown.as_str();
|
||||
println!("[UNKNOWN] websocket message: {:?}", maybe_raw_text);
|
||||
}
|
||||
}
|
||||
count = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Make sure we capture all the events for images
|
||||
// TODO: Explore the other APIs
|
||||
// TODO understand the IDs
|
||||
// TODO: Video websocket APIs
|
||||
// TODO: Upload APIs
|
||||
|
||||
if count > 5 {
|
||||
println!("No messages after 5 seconds");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
println!("Done polling.");
|
||||
|
||||
log::logger().flush();
|
||||
|
||||
assert_eq!(1,2);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,8 @@
|
||||
use crate::credentials::grok_full_credentials::GrokFullCredentials;
|
||||
use crate::datatypes::api::aspect_ratio::AspectRatio;
|
||||
use crate::datatypes::api::file_id::FileId;
|
||||
use crate::datatypes::api::post_id::PostId;
|
||||
use crate::datatypes::api::video_generation_mode::VideoGenerationMode;
|
||||
use crate::datatypes::file_upload_spec::FileUploadSpec;
|
||||
use crate::error::grok_error::GrokError;
|
||||
use crate::error::grok_generic_api_error::GrokGenericApiError;
|
||||
@@ -23,6 +25,12 @@ pub struct UploadImageAndGenerateVideo<'a, P: AsRef<Path>> {
|
||||
/// Video generation prompt
|
||||
pub prompt: Option<&'a str>,
|
||||
|
||||
/// Aspect ratio for the video.
|
||||
pub aspect_ratio: Option<AspectRatio>,
|
||||
|
||||
/// Mode to generate the video: normal, fun, spicy, etc.
|
||||
pub mode: Option<VideoGenerationMode>,
|
||||
|
||||
/// Wait for the full video to be generated before returning
|
||||
/// By default, the endpoint stays open for 30-ish seconds while
|
||||
/// the video generates and streams back JSON progress updates.
|
||||
@@ -101,6 +109,9 @@ pub async fn upload_image_and_generate_video<P: AsRef<Path>>(args: UploadImageAn
|
||||
|
||||
info!("Posted Image.");
|
||||
|
||||
info!("Video prompt: {:?}", args.prompt);
|
||||
info!("Video aspect ratio: {:?}", args.aspect_ratio);
|
||||
|
||||
info!("Generate video...");
|
||||
|
||||
let request = GrokVideoGenChatConversationBuilder {
|
||||
@@ -109,6 +120,8 @@ pub async fn upload_image_and_generate_video<P: AsRef<Path>>(args: UploadImageAn
|
||||
media_type: VideoMediaPostType::UserUploadedImage,
|
||||
cookie: args.full_credentials.cookies.as_str(),
|
||||
prompt: args.prompt,
|
||||
mode: args.mode,
|
||||
aspect_ratio: args.aspect_ratio,
|
||||
request_timeout: args.individual_request_timeout,
|
||||
baggage: &args.full_credentials.client_secrets.baggage,
|
||||
sentry_trace: &args.full_credentials.client_secrets.sentry_trace,
|
||||
@@ -170,6 +183,7 @@ pub async fn upload_image_and_generate_video<P: AsRef<Path>>(args: UploadImageAn
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::credentials::grok_full_credentials::GrokFullCredentials;
|
||||
use crate::datatypes::api::aspect_ratio::AspectRatio;
|
||||
use crate::datatypes::file_upload_spec::FileUploadSpec;
|
||||
use crate::recipes::request_client_secrets::{request_client_secrets, RequestClientSecretsArgs};
|
||||
use crate::recipes::upload_image_and_generate_video::{upload_image_and_generate_video, UploadImageAndGenerateVideo};
|
||||
@@ -177,7 +191,6 @@ mod tests {
|
||||
use crate::test_utils::setup_test_logging::setup_test_logging;
|
||||
use errors::AnyhowResult;
|
||||
use log::LevelFilter;
|
||||
|
||||
// Result: GrokUploadFileResponse { file_metadata_id:
|
||||
// Some("acdee48f-9d6f-4bc6-9d06-fcc97dd4418a"), file_uri:
|
||||
// Some("users/85980643-ffab-4984-a3de-59a608c47d7f/acdee48f-9d6f-4bc6-9d06-fcc97dd4418a/content") }
|
||||
@@ -211,6 +224,8 @@ mod tests {
|
||||
full_credentials: &credentials,
|
||||
file: FileUploadSpec::Path(image_path),
|
||||
prompt: maybe_prompt,
|
||||
aspect_ratio: Some(AspectRatio::Square),
|
||||
mode: None,
|
||||
individual_request_timeout: None,
|
||||
wait_for_generation: false,
|
||||
}).await?;
|
||||
|
||||
+326
-2
@@ -1,7 +1,23 @@
|
||||
use crate::credentials::grok_client_secrets::GrokClientSecrets;
|
||||
use crate::credentials::grok_full_credentials::GrokFullCredentials;
|
||||
use crate::datatypes::api::aspect_ratio::AspectRatio;
|
||||
use crate::datatypes::api::video_generation_mode::VideoGenerationMode;
|
||||
use crate::datatypes::file_upload_spec::FileUploadSpec;
|
||||
use crate::error::grok_client_error::GrokClientError;
|
||||
use crate::error::grok_error::GrokError;
|
||||
use crate::error::grok_generic_api_error::GrokGenericApiError;
|
||||
use crate::error::grok_specific_api_error::GrokSpecificApiError;
|
||||
use crate::recipes::request_client_secrets::{request_client_secrets, RequestClientSecretsArgs};
|
||||
use crate::recipes::upload_image_and_generate_video::{ImageUploadAndGenerateVideoResult, UploadImageAndGenerateVideo};
|
||||
use crate::requests::media_posts::create_media_post::grok_create_media_post::{GrokCreateMediaPost, MediaPostType};
|
||||
use crate::requests::media_posts::like_media_post::grok_like_media::GrokLikeMediaPost;
|
||||
use crate::requests::upload_file::grok_upload_file::GrokUploadFile;
|
||||
use crate::requests::video_chat::grok_video_gen_chat_conversation::{GrokVideoGenChatConversationBuilder, VideoMediaPostType};
|
||||
use crate::utils::user_and_file_id_to_image_url::user_and_file_id_to_image_url;
|
||||
use crate::utils::user_and_file_id_to_video_url::user_and_file_id_to_video_url;
|
||||
use log::{error, info};
|
||||
use std::path::Path;
|
||||
use std::time::Duration;
|
||||
use crate::credentials::grok_full_credentials::GrokFullCredentials;
|
||||
use crate::datatypes::file_upload_spec::FileUploadSpec;
|
||||
|
||||
pub struct UploadImageAndGenerateVideoWithRetry<'a, P: AsRef<Path>> {
|
||||
pub credentials: &'a GrokFullCredentials,
|
||||
@@ -12,6 +28,12 @@ pub struct UploadImageAndGenerateVideoWithRetry<'a, P: AsRef<Path>> {
|
||||
/// Video generation prompt
|
||||
pub prompt: Option<&'a str>,
|
||||
|
||||
/// Aspect ratio for the video.
|
||||
pub aspect_ratio: Option<AspectRatio>,
|
||||
|
||||
/// Mode to generate the video: normal, fun, spicy, etc.
|
||||
pub mode: Option<VideoGenerationMode>,
|
||||
|
||||
/// Wait for the full video to be generated before returning
|
||||
/// By default, the endpoint stays open for 30-ish seconds while
|
||||
/// the video generates and streams back JSON progress updates.
|
||||
@@ -22,3 +44,305 @@ pub struct UploadImageAndGenerateVideoWithRetry<'a, P: AsRef<Path>> {
|
||||
|
||||
pub individual_request_timeout: Option<Duration>,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct ImageUploadAndGenerateVideoWithRetryResult {
|
||||
/// If new secrets were generated, they're stored here.
|
||||
pub maybe_new_client_secrets: Option<GrokClientSecrets>,
|
||||
|
||||
/// The video generation result.
|
||||
pub upload_result: ImageUploadAndGenerateVideoResult,
|
||||
}
|
||||
|
||||
pub async fn upload_image_and_generate_video_with_retry<P: AsRef<Path>>(
|
||||
args: UploadImageAndGenerateVideoWithRetry<'_, P>
|
||||
) -> Result<ImageUploadAndGenerateVideoWithRetryResult, GrokError> {
|
||||
|
||||
let mut current_full_credentials_ref = args.credentials;
|
||||
let mut maybe_new_credentials = None;
|
||||
|
||||
info!("Uploading file to Grok...");
|
||||
|
||||
let request = GrokUploadFile {
|
||||
file: args.file,
|
||||
cookie: current_full_credentials_ref.cookies.to_string(),
|
||||
request_timeout: args.individual_request_timeout,
|
||||
};
|
||||
|
||||
let upload_result = request.upload().await?;
|
||||
|
||||
info!("File URI: {:?}", upload_result.file_uri);
|
||||
info!("File Metadata ID: {:?}", upload_result.file_id);
|
||||
|
||||
let upload_file_id = match upload_result.file_id {
|
||||
Some(id) => id,
|
||||
None => {
|
||||
error!("Previous file upload failed. Cannot continue.");
|
||||
return Err(GrokGenericApiError::UploadFailed.into());
|
||||
}
|
||||
};
|
||||
|
||||
let url = user_and_file_id_to_image_url(
|
||||
¤t_full_credentials_ref.client_secrets.user_id,
|
||||
&upload_file_id
|
||||
);
|
||||
|
||||
info!("Uploaded URI: {:?}", url);
|
||||
|
||||
info!("Creating media post...");
|
||||
|
||||
let request = GrokCreateMediaPost {
|
||||
user_id: ¤t_full_credentials_ref.client_secrets.user_id,
|
||||
file_id: &upload_file_id,
|
||||
media_type: MediaPostType::UserUploadedImage,
|
||||
cookie: current_full_credentials_ref.cookies.as_str(),
|
||||
request_timeout: args.individual_request_timeout,
|
||||
};
|
||||
|
||||
let post_result = request.send().await?;
|
||||
|
||||
info!("Posted Image.");
|
||||
|
||||
info!("Video prompt: {:?}", args.prompt);
|
||||
info!("Video aspect ratio: {:?}", args.aspect_ratio);
|
||||
|
||||
let mut video_enqueued_successfully = false;
|
||||
let mut generation_is_complete= false;
|
||||
let mut maybe_video_file_id = None;
|
||||
let mut last_error = None;
|
||||
|
||||
for i in 0..3 {
|
||||
info!("Generate video... attempt {}", (i+1));
|
||||
|
||||
let request = GrokVideoGenChatConversationBuilder {
|
||||
user_id: ¤t_full_credentials_ref.client_secrets.user_id,
|
||||
file_id: &upload_file_id,
|
||||
media_type: VideoMediaPostType::UserUploadedImage,
|
||||
cookie: current_full_credentials_ref.cookies.as_str(),
|
||||
prompt: args.prompt,
|
||||
mode: args.mode,
|
||||
aspect_ratio: args.aspect_ratio,
|
||||
request_timeout: args.individual_request_timeout,
|
||||
baggage: ¤t_full_credentials_ref.client_secrets.baggage,
|
||||
sentry_trace: ¤t_full_credentials_ref.client_secrets.sentry_trace,
|
||||
verification_token: ¤t_full_credentials_ref.client_secrets.verification_token,
|
||||
svg_data: ¤t_full_credentials_ref.client_secrets.svg_path_data,
|
||||
numbers: ¤t_full_credentials_ref.client_secrets.numbers,
|
||||
};
|
||||
|
||||
let video_gen_result =
|
||||
if args.wait_for_generation {
|
||||
request.wait_for_video()
|
||||
.await
|
||||
.map(|res| res.video_file_id)
|
||||
} else {
|
||||
request.stream_only_video_id()
|
||||
.await
|
||||
.map(|res| res.video_file_id)
|
||||
};
|
||||
|
||||
match video_gen_result {
|
||||
Ok(res) => {
|
||||
info!("Video Generation Enqueued!");
|
||||
video_enqueued_successfully = true;
|
||||
generation_is_complete = args.wait_for_generation; // If we synchronously waited, it'll be complete.
|
||||
maybe_video_file_id = res;
|
||||
break;
|
||||
}
|
||||
Err(GrokError::ApiSpecific(GrokSpecificApiError::AutomationBlocked)) => {
|
||||
info!("Grok automation blocked; renewing credentials.");
|
||||
last_error = Some(GrokError::ApiSpecific(GrokSpecificApiError::AutomationBlocked));
|
||||
}
|
||||
Err(err) => {
|
||||
info!("Grok error; renewing credentials. Error = {:?}", err);
|
||||
last_error = Some(err);
|
||||
}
|
||||
}
|
||||
|
||||
info!("Refreshing Grok client secrets...");
|
||||
|
||||
let secrets = request_client_secrets(RequestClientSecretsArgs {
|
||||
cookies: ¤t_full_credentials_ref.cookies,
|
||||
}).await?;
|
||||
|
||||
// NB: This is just to appease the borrow checker. It doesn't like that we're borrowing `maybe_new_credentials`
|
||||
// in a loop and replacing it while borrowed, hence this hack.
|
||||
current_full_credentials_ref = args.credentials;
|
||||
|
||||
maybe_new_credentials = Some(GrokFullCredentials::from_cookies_and_client_secrets(
|
||||
current_full_credentials_ref.cookies.clone(),
|
||||
secrets,
|
||||
));
|
||||
|
||||
if let Some(creds) = maybe_new_credentials.as_ref() {
|
||||
current_full_credentials_ref = creds;
|
||||
}
|
||||
}
|
||||
|
||||
if !video_enqueued_successfully {
|
||||
if let Some(err) = last_error {
|
||||
return Err(err);
|
||||
} else {
|
||||
// NB: This branch should be impossible as we should always set last_error.
|
||||
return Err(GrokError::Client(GrokClientError::ErrorGeneratingVideo))
|
||||
}
|
||||
}
|
||||
|
||||
let mut video_liked_successfully = false;
|
||||
let mut last_error = None;
|
||||
|
||||
for i in 0..3 {
|
||||
info!("Liking media ... attempt {}", (i+1));
|
||||
|
||||
let request = GrokLikeMediaPost {
|
||||
file_id: &upload_file_id,
|
||||
cookie: current_full_credentials_ref.cookies.as_str(),
|
||||
request_timeout: args.individual_request_timeout,
|
||||
baggage: ¤t_full_credentials_ref.client_secrets.baggage,
|
||||
sentry_trace: ¤t_full_credentials_ref.client_secrets.sentry_trace,
|
||||
verification_token: ¤t_full_credentials_ref.client_secrets.verification_token,
|
||||
svg_data: ¤t_full_credentials_ref.client_secrets.svg_path_data,
|
||||
numbers: ¤t_full_credentials_ref.client_secrets.numbers,
|
||||
};
|
||||
|
||||
let like_result = request.send().await;
|
||||
|
||||
match like_result {
|
||||
Ok(_) => {
|
||||
info!("Media Liked");
|
||||
video_liked_successfully = true;
|
||||
break;
|
||||
}
|
||||
Err(GrokError::ApiSpecific(GrokSpecificApiError::AutomationBlocked)) => {
|
||||
info!("Grok automation blocked liking media; renewing credentials.");
|
||||
last_error = Some(GrokError::ApiSpecific(GrokSpecificApiError::AutomationBlocked));
|
||||
}
|
||||
Err(err) => {
|
||||
info!("Grok error liking media; renewing credentials. Error = {:?}", err);
|
||||
last_error = Some(err);
|
||||
}
|
||||
}
|
||||
|
||||
info!("Refreshing Grok client secrets...");
|
||||
|
||||
let secrets = request_client_secrets(RequestClientSecretsArgs {
|
||||
cookies: ¤t_full_credentials_ref.cookies,
|
||||
}).await?;
|
||||
|
||||
// NB: This is just to appease the borrow checker. It doesn't like that we're borrowing `maybe_new_credentials`
|
||||
// in a loop and replacing it while borrowed, hence this hack.
|
||||
current_full_credentials_ref = args.credentials;
|
||||
|
||||
maybe_new_credentials = Some(GrokFullCredentials::from_cookies_and_client_secrets(
|
||||
current_full_credentials_ref.cookies.clone(),
|
||||
secrets,
|
||||
));
|
||||
|
||||
if let Some(creds) = maybe_new_credentials.as_ref() {
|
||||
current_full_credentials_ref = creds;
|
||||
}
|
||||
}
|
||||
|
||||
if !video_liked_successfully {
|
||||
if let Some(err) = last_error {
|
||||
return Err(err);
|
||||
} else {
|
||||
// NB: This branch should be impossible as we should always set last_error.
|
||||
return Err(GrokError::Client(GrokClientError::ErrorGeneratingVideo))
|
||||
}
|
||||
}
|
||||
|
||||
let maybe_video_url = maybe_video_file_id
|
||||
.as_ref()
|
||||
.map(|file_id| {
|
||||
user_and_file_id_to_video_url(¤t_full_credentials_ref.client_secrets.user_id, file_id, false)
|
||||
});
|
||||
|
||||
Ok(ImageUploadAndGenerateVideoWithRetryResult {
|
||||
maybe_new_client_secrets: maybe_new_credentials
|
||||
.map(|secrets| secrets.client_secrets),
|
||||
upload_result: ImageUploadAndGenerateVideoResult {
|
||||
post_id: post_result.post_id,
|
||||
image_file_id: upload_file_id,
|
||||
video_file_id: maybe_video_file_id,
|
||||
video_url: maybe_video_url,
|
||||
generation_is_complete,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::credentials::grok_full_credentials::GrokFullCredentials;
|
||||
use crate::datatypes::api::aspect_ratio::AspectRatio;
|
||||
use crate::datatypes::api::video_generation_mode::VideoGenerationMode;
|
||||
use crate::datatypes::file_upload_spec::FileUploadSpec;
|
||||
use crate::recipes::request_client_secrets::{request_client_secrets, RequestClientSecretsArgs};
|
||||
use crate::recipes::upload_image_and_generate_video_with_retry::{upload_image_and_generate_video_with_retry, UploadImageAndGenerateVideoWithRetry};
|
||||
use crate::test_utils::get_test_cookies::get_typed_test_cookies;
|
||||
use crate::test_utils::setup_test_logging::setup_test_logging;
|
||||
use errors::AnyhowResult;
|
||||
use log::LevelFilter;
|
||||
|
||||
// Result: GrokUploadFileResponse { file_metadata_id:
|
||||
// Some("acdee48f-9d6f-4bc6-9d06-fcc97dd4418a"), file_uri:
|
||||
// Some("users/85980643-ffab-4984-a3de-59a608c47d7f/acdee48f-9d6f-4bc6-9d06-fcc97dd4418a/content") }
|
||||
|
||||
#[tokio::test]
|
||||
#[ignore] // Client side tests only
|
||||
async fn test_upload_image_and_generate_video() -> AnyhowResult<()> {
|
||||
setup_test_logging(LevelFilter::Info);
|
||||
|
||||
//let image_path = "/Users/bt/Pictures/Zelda 64 Art/FCgYX6tWEAEhpsy.jpg";
|
||||
//let prompt = "our hero link plunges the sword into the pedestal, the temple is glowing with a blue aura";
|
||||
|
||||
let image_path = "/Users/bt/Pictures/Midjourney/hipster_girl.png";
|
||||
let maybe_prompt = Some("an anime girl turns and high fives a giant t-rex");
|
||||
|
||||
let cookies = get_typed_test_cookies()?;
|
||||
|
||||
let mut bad_secrets = request_client_secrets(RequestClientSecretsArgs {
|
||||
cookies: &cookies,
|
||||
}).await?;
|
||||
|
||||
// NB: We're purposely messing these up to trigger "retry".
|
||||
bad_secrets.numbers.numbers = Vec::new();
|
||||
bad_secrets.sentry_trace.0 = "".to_string();
|
||||
|
||||
let credentials = GrokFullCredentials::from_cookies_and_client_secrets(cookies, bad_secrets);
|
||||
|
||||
let result = upload_image_and_generate_video_with_retry(UploadImageAndGenerateVideoWithRetry {
|
||||
credentials: &credentials,
|
||||
file: FileUploadSpec::Path(image_path),
|
||||
prompt: maybe_prompt,
|
||||
mode: Some(VideoGenerationMode::Custom),
|
||||
aspect_ratio: Some(AspectRatio::TallTwoByThree),
|
||||
individual_request_timeout: None,
|
||||
wait_for_generation: false,
|
||||
}).await?;
|
||||
|
||||
let new_secrets = result.maybe_new_client_secrets;
|
||||
|
||||
println!("[test] New secrets ? : {:?}", new_secrets.is_some());
|
||||
|
||||
if let Some(secrets) = new_secrets {
|
||||
println!("[test] Verification Token: {:?}", secrets.verification_token);
|
||||
println!("[test] Sentry Trace: {:?}", secrets.sentry_trace);
|
||||
println!("[test] Numbers: {:?}", secrets.numbers);
|
||||
println!("[test] Svg Path: {:?}", secrets.svg_path_data);
|
||||
println!("[test] Baggage: {:?}", secrets.baggage);
|
||||
}
|
||||
|
||||
let video_result = result.upload_result;
|
||||
|
||||
println!("[test] Post ID: {:?}", video_result.post_id);
|
||||
println!("[test] Image File ID: {:?}", video_result.image_file_id);
|
||||
println!("[test] Video File ID: {:?}", video_result.video_file_id);
|
||||
println!("[test] Video URL: {:?}", video_result.video_url);
|
||||
|
||||
assert_eq!(1, 2);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -83,6 +83,8 @@ pub async fn download_video_file(args: DownloadVideoFileArgs<'_>) -> Result<Vide
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::fs::File;
|
||||
use std::io::Write;
|
||||
use crate::datatypes::api::file_id::FileId;
|
||||
use crate::recipes::request_client_secrets::{request_client_secrets, RequestClientSecretsArgs};
|
||||
use crate::requests::download_video_file::download_video_file::{download_video_file, DownloadVideoFileArgs};
|
||||
@@ -117,6 +119,9 @@ mod tests {
|
||||
|
||||
println!("Video byte length: {:?}", download.bytes.len());
|
||||
|
||||
let mut file = File::create("download_video_file.test.mp4")?;
|
||||
file.write_all(&download.bytes)?;
|
||||
|
||||
assert_eq!(1, 2);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
+2
@@ -117,6 +117,7 @@ mod tests {
|
||||
use crate::requests::image_websocket::create_listen_websocket::{create_listen_websocket, CreateListenWebsocketArgs};
|
||||
use crate::requests::image_websocket::grok_websocket::GrokWebsocket;
|
||||
use crate::requests::image_websocket::listen_for_websocket_images::{listen_for_websocket_images, ListenForWebsocketImagesArgs};
|
||||
use crate::requests::image_websocket::messages::websocket_client_message::ClientMessageAspectRatio;
|
||||
use crate::requests::image_websocket::prompt_websocket_image::{prompt_websocket_image, PromptWebsocketImageArgs};
|
||||
use crate::test_utils::get_test_cookies::get_test_cookies;
|
||||
use crate::test_utils::setup_test_logging::setup_test_logging;
|
||||
@@ -147,6 +148,7 @@ mod tests {
|
||||
let _result = prompt_websocket_image(PromptWebsocketImageArgs {
|
||||
websocket: &mut websocket,
|
||||
prompt,
|
||||
aspect_ratio: ClientMessageAspectRatio::WideThreeByTwo,
|
||||
}).await?;
|
||||
|
||||
println!("Reading...");
|
||||
|
||||
+2
@@ -73,6 +73,7 @@ mod tests {
|
||||
use crate::requests::image_websocket::create_listen_websocket::{create_listen_websocket, CreateListenWebsocketArgs};
|
||||
use crate::requests::image_websocket::grok_websocket::GrokWebsocket;
|
||||
use crate::requests::image_websocket::listen_for_websocket_request_id::{listen_for_websocket_request_id, ListenForWebsocketRequestIdArgs};
|
||||
use crate::requests::image_websocket::messages::websocket_client_message::ClientMessageAspectRatio;
|
||||
use crate::requests::image_websocket::prompt_websocket_image::{prompt_websocket_image, PromptWebsocketImageArgs};
|
||||
use crate::test_utils::get_test_cookies::get_test_cookies;
|
||||
use crate::test_utils::setup_test_logging::setup_test_logging;
|
||||
@@ -103,6 +104,7 @@ mod tests {
|
||||
let _result = prompt_websocket_image(PromptWebsocketImageArgs {
|
||||
websocket: &mut websocket,
|
||||
prompt,
|
||||
aspect_ratio: ClientMessageAspectRatio::Square,
|
||||
}).await?;
|
||||
|
||||
println!("Reading...");
|
||||
|
||||
+15
-1
@@ -46,11 +46,24 @@ pub struct ClientMessageItemContentProperties {
|
||||
pub enable_nsfw: bool,
|
||||
pub skip_upsampler: bool,
|
||||
pub is_initial: bool,
|
||||
pub aspect_ratio: ClientMessageAspectRatio,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Clone, Copy, Debug)]
|
||||
pub enum ClientMessageAspectRatio {
|
||||
#[serde(rename = "2:3")]
|
||||
TallTwoByThree,
|
||||
|
||||
#[serde(rename = "3:2")]
|
||||
WideThreeByTwo,
|
||||
|
||||
#[serde(rename = "1:1")]
|
||||
Square,
|
||||
}
|
||||
|
||||
impl WebsocketClientMessage {
|
||||
/// Create a new image prompt websocket client message
|
||||
pub fn new_image_prompt(prompt: &str) -> Self {
|
||||
pub fn new_image_prompt(prompt: &str, aspect_ratio: ClientMessageAspectRatio) -> Self {
|
||||
Self {
|
||||
r#type: "conversation.item.create".to_string(),
|
||||
timestamp: Utc::now().timestamp_millis() as u64,
|
||||
@@ -67,6 +80,7 @@ impl WebsocketClientMessage {
|
||||
enable_nsfw: true,
|
||||
skip_upsampler: false,
|
||||
is_initial: false,
|
||||
aspect_ratio,
|
||||
},
|
||||
}
|
||||
],
|
||||
|
||||
+5
-2
@@ -1,15 +1,16 @@
|
||||
use crate::error::grok_error::GrokError;
|
||||
use crate::requests::image_websocket::grok_websocket::GrokWebsocket;
|
||||
use crate::requests::image_websocket::messages::websocket_client_message::WebsocketClientMessage;
|
||||
use crate::requests::image_websocket::messages::websocket_client_message::{ClientMessageAspectRatio, WebsocketClientMessage};
|
||||
|
||||
pub struct PromptWebsocketImageArgs<'a> {
|
||||
pub websocket: &'a mut GrokWebsocket,
|
||||
pub prompt: &'a str,
|
||||
pub aspect_ratio: ClientMessageAspectRatio,
|
||||
}
|
||||
|
||||
|
||||
pub async fn prompt_websocket_image(args: PromptWebsocketImageArgs<'_>) -> Result<(), GrokError> {
|
||||
let message = WebsocketClientMessage::new_image_prompt(args.prompt);
|
||||
let message = WebsocketClientMessage::new_image_prompt(args.prompt, args.aspect_ratio);
|
||||
|
||||
args.websocket.send_serializable(&message).await?;
|
||||
|
||||
@@ -21,6 +22,7 @@ mod tests {
|
||||
use crate::requests::image_websocket::create_listen_websocket::{create_listen_websocket, CreateListenWebsocketArgs};
|
||||
use crate::requests::image_websocket::grok_websocket::GrokWebsocket;
|
||||
use crate::requests::image_websocket::grok_wrapped_websocket::GrokWrappedWebsocket;
|
||||
use crate::requests::image_websocket::messages::websocket_client_message::ClientMessageAspectRatio;
|
||||
use crate::requests::image_websocket::messages::websocket_server_message::WebsocketServerMessage;
|
||||
use crate::requests::image_websocket::prompt_websocket_image::{prompt_websocket_image, PromptWebsocketImageArgs};
|
||||
use crate::test_utils::get_test_cookies::get_test_cookies;
|
||||
@@ -50,6 +52,7 @@ mod tests {
|
||||
let result = prompt_websocket_image(PromptWebsocketImageArgs {
|
||||
websocket: &mut websocket,
|
||||
prompt: "a dog riding a motorcycle",
|
||||
aspect_ratio: ClientMessageAspectRatio::WideThreeByTwo,
|
||||
}).await?;
|
||||
|
||||
println!("Reading...");
|
||||
|
||||
+44
-16
@@ -2,14 +2,17 @@ use crate::error::grok_client_error::GrokClientError;
|
||||
use once_cell::sync::Lazy;
|
||||
use regex::Regex;
|
||||
use std::collections::HashMap;
|
||||
use log::info;
|
||||
|
||||
static ACTIONS_REGEX : Lazy<Regex> = Lazy::new(|| {
|
||||
Regex::new(r#"createServerReference\)\("([a-f0-9]+)""#)
|
||||
.expect("Regex should parse")
|
||||
});
|
||||
|
||||
/// 2025-10-01: "(static/chunks/[^"]+\.js)"[^}]*?a\(880932\)"
|
||||
/// 2025-12-01: "(static/chunks/[^"]+\.js)"[^}]*?\(880932\)"
|
||||
static XSID_SCRIPT_REGEX : Lazy<Regex> = Lazy::new(|| {
|
||||
Regex::new(r#""(static/chunks/[^"]+\.js)"[^}]*?a\(880932\)"#)
|
||||
Regex::new(r#""(static/chunks/[^"]+\.js)"[^}]*?\(880932\)"#)
|
||||
.expect("Regex should parse")
|
||||
});
|
||||
|
||||
@@ -36,33 +39,32 @@ pub fn parse_script_actions_and_xsid_script_path(scripts: &HashMap<String, Strin
|
||||
}
|
||||
}
|
||||
|
||||
let action_script_path = match action_script_path {
|
||||
Some(action_script_path) => action_script_path,
|
||||
None => return Err(GrokClientError::ScriptLogicOutOfDate),
|
||||
};
|
||||
// NB: Commented out because we don't use it.
|
||||
// let action_script_path = match action_script_path {
|
||||
// Some(action_script_path) => action_script_path,
|
||||
// None => return Err(GrokClientError::ScriptLogicOutOfDate),
|
||||
// };
|
||||
|
||||
let script_content_1 = match script_content_1 {
|
||||
Some(script_content_1) => script_content_1,
|
||||
None => return Err(GrokClientError::ScriptLogicOutOfDate),
|
||||
None => return Err(GrokClientError::Script1LogicOutOfDate),
|
||||
};
|
||||
|
||||
let script_content_2 = match script_content_2 {
|
||||
Some(script_content_2) => script_content_2,
|
||||
None => return Err(GrokClientError::ScriptLogicOutOfDate),
|
||||
None => return Err(GrokClientError::Script2LogicOutOfDate),
|
||||
};
|
||||
|
||||
//println!("Script content 1:");
|
||||
//println!("{}", script_content_1);
|
||||
//println!("Script content 2:");
|
||||
//println!("{}", script_content_2);
|
||||
|
||||
let actions = ACTIONS_REGEX.captures_iter(&script_content_1)
|
||||
.flat_map(|captures| captures.get(1).map(|m| m.as_str().to_string()))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let xsid_script_path = XSID_SCRIPT_REGEX.captures(&script_content_2)
|
||||
.map(|captures| captures.get(1).map(|m| m.as_str().to_string()))
|
||||
.flatten();
|
||||
|
||||
let xsid_script_path = match xsid_script_path {
|
||||
Some(xsid_script) => xsid_script,
|
||||
None => return Err(GrokClientError::ScriptLogicOutOfDate),
|
||||
};
|
||||
let xsid_script_path = parse_xsid_script_path_from_turbopack_script(&script_content_2)?;
|
||||
|
||||
Ok(ActionsAndXsid {
|
||||
actions,
|
||||
@@ -70,10 +72,23 @@ pub fn parse_script_actions_and_xsid_script_path(scripts: &HashMap<String, Strin
|
||||
})
|
||||
}
|
||||
|
||||
fn parse_xsid_script_path_from_turbopack_script(script_source: &str) -> Result<String, GrokClientError> {
|
||||
let xsid_script_path = XSID_SCRIPT_REGEX.captures(&script_source)
|
||||
.map(|captures| captures.get(1)
|
||||
.map(|m| m.as_str()
|
||||
.to_string()))
|
||||
.flatten();
|
||||
|
||||
match xsid_script_path {
|
||||
Some(xsid_script) => Ok(xsid_script),
|
||||
None => Err(GrokClientError::ScriptXsidLogicOutOfDate),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::requests::index_page::get_index_page_and_scripts::{get_index_page_and_scripts, GetIndexPageAndScriptsArgs};
|
||||
use crate::requests::index_page::parsers::script::parse_script_actions_and_xsid_script_path::parse_script_actions_and_xsid_script_path;
|
||||
use crate::requests::index_page::parsers::script::parse_script_actions_and_xsid_script_path::{parse_script_actions_and_xsid_script_path, parse_xsid_script_path_from_turbopack_script};
|
||||
use crate::test_utils::get_test_cookies::get_test_cookies;
|
||||
use errors::AnyhowResult;
|
||||
|
||||
@@ -93,4 +108,17 @@ mod tests {
|
||||
assert_eq!(1, 2);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_turbopack_script_2_for_xsid() {
|
||||
// NB: The XSID parsing script broke on December 1st.
|
||||
// The valid script to parse out is "static/chunks/6450f465e55b5c24.js" (as it contains the magic numbers). This comes before the string (880932)
|
||||
// They probably just changed the TurboPack configs.
|
||||
let example_script_2 = r#"
|
||||
(globalThis.TURBOPACK||(globalThis.TURBOPACK=[])).push(["object"==typeof document?document.currentScript:void 0,483347,a=>{a.v(s=>Promise.all(["static/chunks/6450f465e55b5c24.js"].map(s=>a.l(s))).then(()=>s(880932)))},330040,a=>{a.v(s=>Promise.all(["static/chunks/5cc0fc78839e38d4.js"].map(s=>a.l(s))).then(()=>s(921398)))},127660,a=>{a.v(s=>Promise.all(["static/chunks/fc24fa4ac57f0fa9.js"].map(s=>a.l(s))).then(()=>s(969999)))},307492,a=>{a.v(s=>Promise.all(["static/chunks/eb46b9f393cc69d9.js"].map(s=>a.l(s))).then(()=>s(529107)))},460973,a=>{a.v(s=>Promise.all(["static/chunks/7ff9ad06da182f94.js"].map(s=>a.l(s))).then(()=>s(296273)))},900864,
|
||||
"#;
|
||||
|
||||
let parsed = parse_xsid_script_path_from_turbopack_script(example_script_2).unwrap();
|
||||
assert_eq!(parsed, "static/chunks/6450f465e55b5c24.js".to_string());
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -11,7 +11,7 @@ pub fn select_svg_path_by_loading_anim(
|
||||
.map(|path| path.clone())
|
||||
.ok_or_else(|| {
|
||||
error!("There was not an SVG path data at index {} (array len {})", loading_anim.0, svg_paths.len());
|
||||
GrokClientError::ScriptLogicOutOfDate
|
||||
GrokClientError::ScriptSvgLogicOutOfDate
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
curl 'https://grok.com/rest/app-chat/conversations/new' \
|
||||
-H 'accept: */*' \
|
||||
-H 'accept-language: en-US,en;q=0.9' \
|
||||
-H 'baggage: sentry-environment=production,sentry-public_key=b311e0f2690c81f25e2c4cf6d4f7ce1c,sentry-trace_id=7798e88fd61243ba8985ca9f589f5113,sentry-org_id=4508179396558848,sentry-transaction=%2F%3Aslug*%3F,sentry-sampled=false,sentry-sample_rand=0.01740155041905489,sentry-sample_rate=0' \
|
||||
-H 'cache-control: no-cache' \
|
||||
-H 'content-type: application/json' \
|
||||
-b '_ga=GA1.1.1232202746.1760710013; i18nextLng=en; sso=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzZXNzaW9uX2lkIjoiOGU3MDFiNzctOTdkNC00ZjM0LWExOTctOWFmMDU1MzY3NDAwIn0.-a6x0InxbGzfTVfUlrdzxskxCnvMDI8lC90z4wHeGIk; sso-rw=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzZXNzaW9uX2lkIjoiOGU3MDFiNzctOTdkNC00ZjM0LWExOTctOWFmMDU1MzY3NDAwIn0.-a6x0InxbGzfTVfUlrdzxskxCnvMDI8lC90z4wHeGIk; stblid=b3331fc1-45d7-466b-83df-67427c0b2367; x-userid=85980643-ffab-4984-a3de-59a608c47d7f; mp_ea93da913ddb66b6372b89d97b1029ac_mixpanel=%7B%22distinct_id%22%3A%2285980643-ffab-4984-a3de-59a608c47d7f%22%2C%22%24device_id%22%3A%2279ce237a-a0f3-4913-bf4b-519ac8a98263%22%2C%22%24initial_referrer%22%3A%22%24direct%22%2C%22%24initial_referring_domain%22%3A%22%24direct%22%2C%22__mps%22%3A%7B%7D%2C%22__mpso%22%3A%7B%7D%2C%22__mpus%22%3A%7B%7D%2C%22__mpa%22%3A%7B%7D%2C%22__mpu%22%3A%7B%7D%2C%22__mpr%22%3A%5B%5D%2C%22__mpap%22%3A%5B%5D%2C%22%24user_id%22%3A%2285980643-ffab-4984-a3de-59a608c47d7f%22%7D; cf_clearance=5QPOzLWH8eEniaDlJR23k9VB4XjRPHdR.kfVR15BRkI-1764645588-1.2.1.1-WBy8wpvS9.Q.ofWBIMTLHDrKRIPt0Xrn5kAQ6I7xf4u0JSbB.E4CRd.fK6kEZo8OTXMAdt.F0YkoLJzB0TufxM79dMu63TKLmorKB.gSgUWJcea1eQcLJ1WG80uhv4VCMrSJ_HHAaq6fqiNNvS7pArgMT4tvoaQXShfDuV8ivYsRp31LYMgNnRFdtiCiJ5mxhf7uUMpSS_UbvCSaYe7CysUr472alMjEYUd.QhKpb8E; _ga_8FEWB057YH=GS2.1.s1764645588$o22$g1$t1764646543$j57$l0$h0' \
|
||||
-H 'origin: https://grok.com' \
|
||||
-H 'pragma: no-cache' \
|
||||
-H 'priority: u=1, i' \
|
||||
-H 'referer: https://grok.com/imagine/post/463eb5f6-7485-495a-a344-70ab4fe454a9' \
|
||||
-H 'sec-ch-ua: "Chromium";v="142", "Google Chrome";v="142", "Not_A Brand";v="99"' \
|
||||
-H 'sec-ch-ua-mobile: ?0' \
|
||||
-H 'sec-ch-ua-platform: "macOS"' \
|
||||
-H 'sec-fetch-dest: empty' \
|
||||
-H 'sec-fetch-mode: cors' \
|
||||
-H 'sec-fetch-site: same-origin' \
|
||||
-H 'sentry-trace: 7798e88fd61243ba8985ca9f589f5113-889cac2356ac270b-0' \
|
||||
-H 'traceparent: 00-9698441276035e5f4414a5c7b7cde4e7-043b158b0070e175-00' \
|
||||
-H 'user-agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/142.0.0.0 Safari/537.36' \
|
||||
-H 'x-statsig-id: eVTlFkyAcCo5RWxUQOUfzewYmzWMb/GbAFj4n2ymSal+UQwbGGxtFMSDrd+DdsVB0EaFp33Dd+aDMEmMn9Ay0MCCa8Bqeg' \
|
||||
-H 'x-xai-request-id: ac5e2fc3-daed-4967-a9ff-1d12b60ee0e1' \
|
||||
--data-raw '{"temporary":true,"modelName":"grok-3","message":"https://imagine-public.x.ai/imagine-public/images/463eb5f6-7485-495a-a344-70ab4fe454a9.png --mode=extremely-crazy","toolOverrides":{"videoGen":true},"responseMetadata":{"modelConfigOverride":{"modelMap":{"videoGenModelConfig":{"parentPostId":"463eb5f6-7485-495a-a344-70ab4fe454a9","aspectRatio":"2:3","videoLength":6}}}}}'
|
||||
|
||||
|
||||
--data-raw
|
||||
{
|
||||
"temporary":true,
|
||||
"modelName":"grok-3",
|
||||
"message":"https://imagine-public.x.ai/imagine-public/images/463eb5f6-7485-495a-a344-70ab4fe454a9.png --mode=extremely-crazy",
|
||||
"toolOverrides": {
|
||||
"videoGen":true
|
||||
},
|
||||
"responseMetadata": {
|
||||
"modelConfigOverride": {
|
||||
"modelMap": {
|
||||
"videoGenModelConfig": {
|
||||
"parentPostId":"463eb5f6-7485-495a-a344-70ab4fe454a9",
|
||||
"aspectRatio": "2:3",
|
||||
"videoLength":6
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
curl 'https://grok.com/rest/app-chat/conversations/new' \
|
||||
-H 'accept: */*' \
|
||||
-H 'accept-language: en-US,en;q=0.9' \
|
||||
-H 'baggage: sentry-environment=production,sentry-public_key=b311e0f2690c81f25e2c4cf6d4f7ce1c,sentry-trace_id=7798e88fd61243ba8985ca9f589f5113,sentry-org_id=4508179396558848,sentry-transaction=%2F%3Aslug*%3F,sentry-sampled=false,sentry-sample_rand=0.01740155041905489,sentry-sample_rate=0' \
|
||||
-H 'cache-control: no-cache' \
|
||||
-H 'content-type: application/json' \
|
||||
-b '_ga=GA1.1.1232202746.1760710013; i18nextLng=en; sso=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzZXNzaW9uX2lkIjoiOGU3MDFiNzctOTdkNC00ZjM0LWExOTctOWFmMDU1MzY3NDAwIn0.-a6x0InxbGzfTVfUlrdzxskxCnvMDI8lC90z4wHeGIk; sso-rw=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzZXNzaW9uX2lkIjoiOGU3MDFiNzctOTdkNC00ZjM0LWExOTctOWFmMDU1MzY3NDAwIn0.-a6x0InxbGzfTVfUlrdzxskxCnvMDI8lC90z4wHeGIk; stblid=b3331fc1-45d7-466b-83df-67427c0b2367; x-userid=85980643-ffab-4984-a3de-59a608c47d7f; mp_ea93da913ddb66b6372b89d97b1029ac_mixpanel=%7B%22distinct_id%22%3A%2285980643-ffab-4984-a3de-59a608c47d7f%22%2C%22%24device_id%22%3A%2279ce237a-a0f3-4913-bf4b-519ac8a98263%22%2C%22%24initial_referrer%22%3A%22%24direct%22%2C%22%24initial_referring_domain%22%3A%22%24direct%22%2C%22__mps%22%3A%7B%7D%2C%22__mpso%22%3A%7B%7D%2C%22__mpus%22%3A%7B%7D%2C%22__mpa%22%3A%7B%7D%2C%22__mpu%22%3A%7B%7D%2C%22__mpr%22%3A%5B%5D%2C%22__mpap%22%3A%5B%5D%2C%22%24user_id%22%3A%2285980643-ffab-4984-a3de-59a608c47d7f%22%7D; cf_clearance=5QPOzLWH8eEniaDlJR23k9VB4XjRPHdR.kfVR15BRkI-1764645588-1.2.1.1-WBy8wpvS9.Q.ofWBIMTLHDrKRIPt0Xrn5kAQ6I7xf4u0JSbB.E4CRd.fK6kEZo8OTXMAdt.F0YkoLJzB0TufxM79dMu63TKLmorKB.gSgUWJcea1eQcLJ1WG80uhv4VCMrSJ_HHAaq6fqiNNvS7pArgMT4tvoaQXShfDuV8ivYsRp31LYMgNnRFdtiCiJ5mxhf7uUMpSS_UbvCSaYe7CysUr472alMjEYUd.QhKpb8E; _ga_8FEWB057YH=GS2.1.s1764645588$o22$g1$t1764646543$j57$l0$h0' \
|
||||
-H 'origin: https://grok.com' \
|
||||
-H 'pragma: no-cache' \
|
||||
-H 'priority: u=1, i' \
|
||||
-H 'referer: https://grok.com/imagine/post/463eb5f6-7485-495a-a344-70ab4fe454a9' \
|
||||
-H 'sec-ch-ua: "Chromium";v="142", "Google Chrome";v="142", "Not_A Brand";v="99"' \
|
||||
-H 'sec-ch-ua-mobile: ?0' \
|
||||
-H 'sec-ch-ua-platform: "macOS"' \
|
||||
-H 'sec-fetch-dest: empty' \
|
||||
-H 'sec-fetch-mode: cors' \
|
||||
-H 'sec-fetch-site: same-origin' \
|
||||
-H 'sentry-trace: 7798e88fd61243ba8985ca9f589f5113-ac16ba34e7a9e3c2-0' \
|
||||
-H 'traceparent: 00-017b734e99d0585d4e05bf0376ef9ffb-c5854dfb0968df83-00' \
|
||||
-H 'user-agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/142.0.0.0 Safari/537.36' \
|
||||
-H 'x-statsig-id: 8dxtnsQI+KKxzeTcyG2XRWSQE70E53kTiNBwF+QuwSH22YSTkOTlnEwLJVcL/k3JWIkNL/XNm7U/U87VShlyvfCIJ01k8g' \
|
||||
-H 'x-xai-request-id: c2eab60f-859f-4963-8ef8-b5eefaaf5966' \
|
||||
--data-raw '{"temporary":true,"modelName":"grok-3","message":"https://imagine-public.x.ai/imagine-public/images/463eb5f6-7485-495a-a344-70ab4fe454a9.png --mode=normal","toolOverrides":{"videoGen":true},"responseMetadata":{"modelConfigOverride":{"modelMap":{"videoGenModelConfig":{"parentPostId":"463eb5f6-7485-495a-a344-70ab4fe454a9","aspectRatio":"2:3","videoLength":6}}}}}'
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
curl 'https://grok.com/rest/app-chat/conversations/new' \
|
||||
-H 'accept: */*' \
|
||||
-H 'accept-language: en-US,en;q=0.9' \
|
||||
-H 'baggage: sentry-environment=production,sentry-public_key=b311e0f2690c81f25e2c4cf6d4f7ce1c,sentry-trace_id=277d6351c5063fd659b51b77b50bb453,sentry-org_id=4508179396558848,sentry-sampled=false,sentry-sample_rand=0.9765826569629004,sentry-sample_rate=0' \
|
||||
-H 'cache-control: no-cache' \
|
||||
-H 'content-type: application/json' \
|
||||
-b '_ga=GA1.1.1232202746.1760710013; i18nextLng=en; sso=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzZXNzaW9uX2lkIjoiOGU3MDFiNzctOTdkNC00ZjM0LWExOTctOWFmMDU1MzY3NDAwIn0.-a6x0InxbGzfTVfUlrdzxskxCnvMDI8lC90z4wHeGIk; sso-rw=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzZXNzaW9uX2lkIjoiOGU3MDFiNzctOTdkNC00ZjM0LWExOTctOWFmMDU1MzY3NDAwIn0.-a6x0InxbGzfTVfUlrdzxskxCnvMDI8lC90z4wHeGIk; stblid=b3331fc1-45d7-466b-83df-67427c0b2367; x-userid=85980643-ffab-4984-a3de-59a608c47d7f; mp_ea93da913ddb66b6372b89d97b1029ac_mixpanel=%7B%22distinct_id%22%3A%2285980643-ffab-4984-a3de-59a608c47d7f%22%2C%22%24device_id%22%3A%2279ce237a-a0f3-4913-bf4b-519ac8a98263%22%2C%22%24initial_referrer%22%3A%22%24direct%22%2C%22%24initial_referring_domain%22%3A%22%24direct%22%2C%22__mps%22%3A%7B%7D%2C%22__mpso%22%3A%7B%7D%2C%22__mpus%22%3A%7B%7D%2C%22__mpa%22%3A%7B%7D%2C%22__mpu%22%3A%7B%7D%2C%22__mpr%22%3A%5B%5D%2C%22__mpap%22%3A%5B%5D%2C%22%24user_id%22%3A%2285980643-ffab-4984-a3de-59a608c47d7f%22%7D; cf_clearance=5QPOzLWH8eEniaDlJR23k9VB4XjRPHdR.kfVR15BRkI-1764645588-1.2.1.1-WBy8wpvS9.Q.ofWBIMTLHDrKRIPt0Xrn5kAQ6I7xf4u0JSbB.E4CRd.fK6kEZo8OTXMAdt.F0YkoLJzB0TufxM79dMu63TKLmorKB.gSgUWJcea1eQcLJ1WG80uhv4VCMrSJ_HHAaq6fqiNNvS7pArgMT4tvoaQXShfDuV8ivYsRp31LYMgNnRFdtiCiJ5mxhf7uUMpSS_UbvCSaYe7CysUr472alMjEYUd.QhKpb8E; _ga_8FEWB057YH=GS2.1.s1764645588$o22$g1$t1764645617$j31$l0$h0' \
|
||||
-H 'origin: https://grok.com' \
|
||||
-H 'pragma: no-cache' \
|
||||
-H 'priority: u=1, i' \
|
||||
-H 'referer: https://grok.com/imagine/post/6136f31a-4aaf-48e0-ab86-fe9871feca91' \
|
||||
-H 'sec-ch-ua: "Chromium";v="142", "Google Chrome";v="142", "Not_A Brand";v="99"' \
|
||||
-H 'sec-ch-ua-mobile: ?0' \
|
||||
-H 'sec-ch-ua-platform: "macOS"' \
|
||||
-H 'sec-fetch-dest: empty' \
|
||||
-H 'sec-fetch-mode: cors' \
|
||||
-H 'sec-fetch-site: same-origin' \
|
||||
-H 'sentry-trace: 277d6351c5063fd659b51b77b50bb453-928bfb8d1221fde1-0' \
|
||||
-H 'traceparent: 00-b0bd9846c2990e253ecca887fdfba8a2-c415282f4b74bf60-00' \
|
||||
-H 'user-agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/142.0.0.0 Safari/537.36' \
|
||||
-H 'x-statsig-id: HjOCcSvnF01eIgszJ4J4qot//FLrCJb8Zz+f+AvBLs4ZNmt8fwsKc6PkyrjkEaImt9npwBpAUCrYQeeYTG/5jRWOKfLdHQ' \
|
||||
-H 'x-xai-request-id: d5ac2ce6-ed77-4a5d-8905-613a498035d3' \
|
||||
--data-raw '{"temporary":true,"modelName":"grok-3","message":"https://imagine-public.x.ai/imagine-public/images/6136f31a-4aaf-48e0-ab86-fe9871feca91.png --mode=extremely-spicy-or-crazy","toolOverrides":{"videoGen":true},"responseMetadata":{"modelConfigOverride":{"modelMap":{"videoGenModelConfig":{"parentPostId":"6136f31a-4aaf-48e0-ab86-fe9871feca91","aspectRatio":"2:3","videoLength":6}}}}}'
|
||||
|
||||
|
||||
--data-raw
|
||||
{
|
||||
"temporary":true,
|
||||
"modelName":"grok-3",
|
||||
"message":"https://imagine-public.x.ai/imagine-public/images/6136f31a-4aaf-48e0-ab86-fe9871feca91.png --mode=extremely-spicy-or-crazy",
|
||||
"toolOverrides": {
|
||||
"videoGen":true
|
||||
},
|
||||
"responseMetadata": {
|
||||
"modelConfigOverride": {
|
||||
"modelMap": {
|
||||
"videoGenModelConfig": {
|
||||
"parentPostId": "6136f31a-4aaf-48e0-ab86-fe9871feca91",
|
||||
"aspectRatio": "2:3",
|
||||
"videoLength":6
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
curl 'https://grok.com/rest/app-chat/conversations/new' \
|
||||
-H 'accept: */*' \
|
||||
-H 'accept-language: en-US,en;q=0.9' \
|
||||
-H 'baggage: sentry-environment=production,sentry-public_key=b311e0f2690c81f25e2c4cf6d4f7ce1c,sentry-trace_id=a0dfe5fb521c4aff81dc8e4c29ddf512,sentry-org_id=4508179396558848,sentry-transaction=%2F%3Aslug*%3F,sentry-sampled=false,sentry-sample_rand=0.2766847188981013,sentry-sample_rate=0' \
|
||||
-H 'cache-control: no-cache' \
|
||||
-H 'content-type: application/json' \
|
||||
-b '_ga=GA1.1.1232202746.1760710013; i18nextLng=en; sso=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzZXNzaW9uX2lkIjoiOGU3MDFiNzctOTdkNC00ZjM0LWExOTctOWFmMDU1MzY3NDAwIn0.-a6x0InxbGzfTVfUlrdzxskxCnvMDI8lC90z4wHeGIk; sso-rw=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzZXNzaW9uX2lkIjoiOGU3MDFiNzctOTdkNC00ZjM0LWExOTctOWFmMDU1MzY3NDAwIn0.-a6x0InxbGzfTVfUlrdzxskxCnvMDI8lC90z4wHeGIk; stblid=b3331fc1-45d7-466b-83df-67427c0b2367; x-userid=85980643-ffab-4984-a3de-59a608c47d7f; cf_clearance=_kkd42wRt_Q2aVTB3qvDnUe8BZj7wJA5tYQ.SruABFE-1764222553-1.2.1.1-2U5UwW9vy4CXMEktGjlRBBtDSPkect5mmH5T98k7blNsLt_Mkxum6WZrSkwvTrujXz54E3k46_0elKO.37jq_sU2C.CoCmQ8FxU85JIPG05ybIx26PQkS54vvnnKDtvc7Jcat56FTRzKO8x._p4umwZZWsSSuthdvP9ogWyODnacVg7jyEnJ_KtZInJeLJOwUyL7qxLDTze0yIs9lfXuDzvCQrak4vl2P3FF9q0P1dA; mp_ea93da913ddb66b6372b89d97b1029ac_mixpanel=%7B%22distinct_id%22%3A%2285980643-ffab-4984-a3de-59a608c47d7f%22%2C%22%24device_id%22%3A%2279ce237a-a0f3-4913-bf4b-519ac8a98263%22%2C%22%24initial_referrer%22%3A%22%24direct%22%2C%22%24initial_referring_domain%22%3A%22%24direct%22%2C%22__mps%22%3A%7B%7D%2C%22__mpso%22%3A%7B%7D%2C%22__mpus%22%3A%7B%7D%2C%22__mpa%22%3A%7B%7D%2C%22__mpu%22%3A%7B%7D%2C%22__mpr%22%3A%5B%5D%2C%22__mpap%22%3A%5B%5D%2C%22%24user_id%22%3A%2285980643-ffab-4984-a3de-59a608c47d7f%22%7D; _ga_8FEWB057YH=GS2.1.s1764228571$o20$g1$t1764228595$j36$l0$h0' \
|
||||
-H 'origin: https://grok.com' \
|
||||
-H 'pragma: no-cache' \
|
||||
-H 'priority: u=1, i' \
|
||||
-H 'referer: https://grok.com/imagine/post/51bc87b7-d985-486b-87f9-2345f9329675' \
|
||||
-H 'sec-ch-ua: "Chromium";v="142", "Google Chrome";v="142", "Not_A Brand";v="99"' \
|
||||
-H 'sec-ch-ua-mobile: ?0' \
|
||||
-H 'sec-ch-ua-platform: "macOS"' \
|
||||
-H 'sec-fetch-dest: empty' \
|
||||
-H 'sec-fetch-mode: cors' \
|
||||
-H 'sec-fetch-site: same-origin' \
|
||||
-H 'sentry-trace: a0dfe5fb521c4aff81dc8e4c29ddf512-9498b36577293fef-0' \
|
||||
-H 'traceparent: 00-88e6010c9ecea790a91d0b27c38fd0e1-fac74f63c226ad4f-00' \
|
||||
-H 'user-agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/142.0.0.0 Safari/537.36' \
|
||||
-H 'x-statsig-id: F6uioputjm5qWZ97AsfHQy+xxfuYSos+nBN7lvk1K0RxkSYiOP3iuyRLttV62pbJK5ONzxM3lf1JMZXxkVl8vNszvMiIFA' \
|
||||
-H 'x-xai-request-id: 1e445b21-9e73-4cb3-af6d-7918900fab62' \
|
||||
--data-raw '{"temporary":true,"modelName":"grok-3","message":"https://assets.grok.com/users/85980643-ffab-4984-a3de-59a608c47d7f/51bc87b7-d985-486b-87f9-2345f9329675/content --mode=normal","fileAttachments":["51bc87b7-d985-486b-87f9-2345f9329675"],"toolOverrides":{"videoGen":true},"responseMetadata":{"modelConfigOverride":{"modelMap":{"videoGenModelConfig":{"parentPostId":"51bc87b7-d985-486b-87f9-2345f9329675","aspectRatio":"1:1","videoLength":6}}}}}'
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
curl 'https://grok.com/rest/app-chat/conversations/new' \
|
||||
-H 'accept: */*' \
|
||||
-H 'accept-language: en-US,en;q=0.9' \
|
||||
-H 'baggage: sentry-environment=production,sentry-public_key=b311e0f2690c81f25e2c4cf6d4f7ce1c,sentry-trace_id=a0dfe5fb521c4aff81dc8e4c29ddf512,sentry-org_id=4508179396558848,sentry-transaction=%2F%3Aslug*%3F,sentry-sampled=false,sentry-sample_rand=0.2766847188981013,sentry-sample_rate=0' \
|
||||
-H 'cache-control: no-cache' \
|
||||
-H 'content-type: application/json' \
|
||||
-b '_ga=GA1.1.1232202746.1760710013; i18nextLng=en; sso=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzZXNzaW9uX2lkIjoiOGU3MDFiNzctOTdkNC00ZjM0LWExOTctOWFmMDU1MzY3NDAwIn0.-a6x0InxbGzfTVfUlrdzxskxCnvMDI8lC90z4wHeGIk; sso-rw=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzZXNzaW9uX2lkIjoiOGU3MDFiNzctOTdkNC00ZjM0LWExOTctOWFmMDU1MzY3NDAwIn0.-a6x0InxbGzfTVfUlrdzxskxCnvMDI8lC90z4wHeGIk; stblid=b3331fc1-45d7-466b-83df-67427c0b2367; x-userid=85980643-ffab-4984-a3de-59a608c47d7f; cf_clearance=_kkd42wRt_Q2aVTB3qvDnUe8BZj7wJA5tYQ.SruABFE-1764222553-1.2.1.1-2U5UwW9vy4CXMEktGjlRBBtDSPkect5mmH5T98k7blNsLt_Mkxum6WZrSkwvTrujXz54E3k46_0elKO.37jq_sU2C.CoCmQ8FxU85JIPG05ybIx26PQkS54vvnnKDtvc7Jcat56FTRzKO8x._p4umwZZWsSSuthdvP9ogWyODnacVg7jyEnJ_KtZInJeLJOwUyL7qxLDTze0yIs9lfXuDzvCQrak4vl2P3FF9q0P1dA; mp_ea93da913ddb66b6372b89d97b1029ac_mixpanel=%7B%22distinct_id%22%3A%2285980643-ffab-4984-a3de-59a608c47d7f%22%2C%22%24device_id%22%3A%2279ce237a-a0f3-4913-bf4b-519ac8a98263%22%2C%22%24initial_referrer%22%3A%22%24direct%22%2C%22%24initial_referring_domain%22%3A%22%24direct%22%2C%22__mps%22%3A%7B%7D%2C%22__mpso%22%3A%7B%7D%2C%22__mpus%22%3A%7B%7D%2C%22__mpa%22%3A%7B%7D%2C%22__mpu%22%3A%7B%7D%2C%22__mpr%22%3A%5B%5D%2C%22__mpap%22%3A%5B%5D%2C%22%24user_id%22%3A%2285980643-ffab-4984-a3de-59a608c47d7f%22%7D; _ga_8FEWB057YH=GS2.1.s1764228571$o20$g1$t1764228668$j60$l0$h0' \
|
||||
-H 'origin: https://grok.com' \
|
||||
-H 'pragma: no-cache' \
|
||||
-H 'priority: u=1, i' \
|
||||
-H 'referer: https://grok.com/imagine/post/b0b88b45-9724-49bd-a82c-c83ddcd2937f' \
|
||||
-H 'sec-ch-ua: "Chromium";v="142", "Google Chrome";v="142", "Not_A Brand";v="99"' \
|
||||
-H 'sec-ch-ua-mobile: ?0' \
|
||||
-H 'sec-ch-ua-platform: "macOS"' \
|
||||
-H 'sec-fetch-dest: empty' \
|
||||
-H 'sec-fetch-mode: cors' \
|
||||
-H 'sec-fetch-site: same-origin' \
|
||||
-H 'sentry-trace: a0dfe5fb521c4aff81dc8e4c29ddf512-8d3208b7533c8446-0' \
|
||||
-H 'traceparent: 00-46471c2129f7b3cac9db10d504ba132a-7851e787db05c657-00' \
|
||||
-H 'user-agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/142.0.0.0 Safari/537.36' \
|
||||
-H 'x-statsig-id: T/P6+sP11jYyAccjWp+fG3fpnaPAEtNmxEsjzqFtcxwpyX56YKW643wT7o0igs6Rc6TVl0smbLBKnHfKsZhcEQxzyvdlTA' \
|
||||
-H 'x-xai-request-id: 9c853e9c-ce87-403b-a2c9-d073523b34f6' \
|
||||
--data-raw '{"temporary":true,"modelName":"grok-3","message":"A waterspout wrecks an oil tanker, misty, huge waves --mode=custom","toolOverrides":{"videoGen":true},"responseMetadata":{"modelConfigOverride":{"modelMap":{"videoGenModelConfig":{"parentPostId":"b0b88b45-9724-49bd-a82c-c83ddcd2937f","aspectRatio":"2:3","videoLength":6}}}}}'
|
||||
+31
-3
@@ -1,10 +1,12 @@
|
||||
use crate::client::browser_user_agents::FIREFOX_143_MAC_USER_AGENT;
|
||||
use crate::datatypes::api::aspect_ratio::AspectRatio;
|
||||
use crate::datatypes::api::baggage::Baggage;
|
||||
use crate::datatypes::api::file_id::FileId;
|
||||
use crate::datatypes::api::sentry_trace::SentryTrace;
|
||||
use crate::datatypes::api::svg_path_data::SvgPathData;
|
||||
use crate::datatypes::api::user_id::UserId;
|
||||
use crate::datatypes::api::verification_token::VerificationToken;
|
||||
use crate::datatypes::api::video_generation_mode::VideoGenerationMode;
|
||||
use crate::datatypes::api::xsid_numbers::XsidNumbers;
|
||||
use crate::error::categorize_grok_http_error::categorize_grok_http_error;
|
||||
use crate::error::grok_client_error::GrokClientError;
|
||||
@@ -14,7 +16,7 @@ use crate::requests::index_page::signature::generate_xsid::{generate_xsid, Gener
|
||||
use crate::requests::upload_file::grok_upload_file::{GrokUploadFile, GrokUploadFileResponse};
|
||||
use crate::requests::video_chat::parse_video_id::parse_video_id;
|
||||
use crate::requests::video_chat::parse_video_id_from_partial_byte_stream_buffer::parse_video_id_from_partial_byte_stream_buffer;
|
||||
use crate::requests::video_chat::request::{CreateChatConversationWireRequest, ModelConfigOverride, ModelMap, ResponseMetadata, ToolOverrides, VideoGenModelConfig};
|
||||
use crate::requests::video_chat::request::{CreateChatConversationWireRequest, ModelConfigOverride, ModelMap, ResponseMetadata, ToolOverrides, VideoGenAspectRatio, VideoGenModelConfig};
|
||||
use crate::utils::user_and_file_id_to_image_url::user_and_file_id_to_image_url;
|
||||
use futures::StreamExt;
|
||||
use log::{debug, error, info, warn};
|
||||
@@ -32,6 +34,8 @@ pub struct GrokVideoGenChatConversationBuilder<'a> {
|
||||
pub file_id: &'a FileId,
|
||||
pub media_type: VideoMediaPostType,
|
||||
pub prompt: Option<&'a str>,
|
||||
pub mode: Option<VideoGenerationMode>,
|
||||
pub aspect_ratio: Option<AspectRatio>,
|
||||
|
||||
pub cookie: &'a str,
|
||||
pub user_id: &'a UserId,
|
||||
@@ -230,12 +234,30 @@ impl <'a> GrokVideoGenChatConversationBuilder<'a> {
|
||||
VideoMediaPostType::Video => unimplemented!("implement for videos"),
|
||||
};
|
||||
|
||||
let mut prompt = format!("{media_url} --mode=normal");
|
||||
let mode_string = self.mode
|
||||
.unwrap_or(VideoGenerationMode::Normal)
|
||||
.as_api_mode_arg();
|
||||
|
||||
let mut prompt = format!("{media_url} --mode={mode_string}");
|
||||
|
||||
if let Some(user_prompt) = self.prompt {
|
||||
prompt = format!("{media_url} {user_prompt} --mode=custom");
|
||||
// If a text prompt is set, mode seems to typically be "custom".
|
||||
let mode_string = self.mode
|
||||
.unwrap_or(VideoGenerationMode::Custom)
|
||||
.as_api_mode_arg();
|
||||
|
||||
prompt = format!("{media_url} {user_prompt} --mode={mode_string}");
|
||||
}
|
||||
|
||||
info!("Grok video prompt: {prompt}");
|
||||
|
||||
let aspect_ratio = match self.aspect_ratio {
|
||||
Some(AspectRatio::Square) => VideoGenAspectRatio::Square,
|
||||
Some(AspectRatio::WideThreeByTwo) => VideoGenAspectRatio::WideThreeByTwo,
|
||||
Some(AspectRatio::TallTwoByThree) => VideoGenAspectRatio::TallTwoByThree,
|
||||
None => VideoGenAspectRatio::WideThreeByTwo,
|
||||
};
|
||||
|
||||
let request_body = CreateChatConversationWireRequest {
|
||||
temporary: true,
|
||||
model_name: "grok-3".to_string(),
|
||||
@@ -251,6 +273,8 @@ impl <'a> GrokVideoGenChatConversationBuilder<'a> {
|
||||
model_map: ModelMap {
|
||||
video_gen_model_config: VideoGenModelConfig {
|
||||
parent_post_id: self.file_id.0.to_string(),
|
||||
video_length: 6, // NB: API defaults to "6"
|
||||
aspect_ratio,
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -340,6 +364,8 @@ mod tests {
|
||||
file_id: &file_id,
|
||||
media_type: VideoMediaPostType::UserUploadedImage,
|
||||
prompt: maybe_prompt,
|
||||
mode: Some(VideoGenerationMode::Custom),
|
||||
aspect_ratio: Some(AspectRatio::TallTwoByThree),
|
||||
|
||||
cookie: cookies.as_str(),
|
||||
user_id: &secrets.user_id,
|
||||
@@ -402,6 +428,8 @@ mod tests {
|
||||
file_id: &file_id,
|
||||
media_type: VideoMediaPostType::UserUploadedImage,
|
||||
prompt: maybe_prompt,
|
||||
mode: None,
|
||||
aspect_ratio: Some(AspectRatio::Square),
|
||||
|
||||
cookie: cookies.as_str(),
|
||||
user_id: &secrets.user_id,
|
||||
|
||||
@@ -51,10 +51,55 @@ pub (super) struct ModelMap {
|
||||
pub (super) struct VideoGenModelConfig {
|
||||
#[serde(rename = "parentPostId")]
|
||||
pub (super) parent_post_id: String,
|
||||
|
||||
/// Newer field as of November
|
||||
/// values: "1:1", "2:3" (tall),
|
||||
#[serde(rename = "aspectRatio")]
|
||||
pub (super) aspect_ratio: VideoGenAspectRatio,
|
||||
|
||||
/// Newer field as of November
|
||||
/// default seems to be "6"
|
||||
#[serde(rename = "videoLength")]
|
||||
pub (super) video_length: u32,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Clone, Copy, Debug)]
|
||||
pub (super) enum VideoGenAspectRatio {
|
||||
#[serde(rename = "2:3")]
|
||||
TallTwoByThree,
|
||||
|
||||
#[serde(rename = "3:2")]
|
||||
WideThreeByTwo,
|
||||
|
||||
#[serde(rename = "1:1")]
|
||||
Square,
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
November 27 no image (text only) square aspect ratio -
|
||||
|
||||
--data-raw
|
||||
{
|
||||
"temporary":true,
|
||||
"modelName":"grok-3",
|
||||
"message":"A waterspout wrecks an oil tanker, misty, huge waves --mode=custom",
|
||||
"toolOverrides": {
|
||||
"videoGen":true
|
||||
},
|
||||
"responseMetadata": {
|
||||
"modelConfigOverride": {
|
||||
"modelMap": {
|
||||
"videoGenModelConfig": {
|
||||
"parentPostId":"b0b88b45-9724-49bd-a82c-c83ddcd2937f",
|
||||
"aspectRatio":"2:3",
|
||||
"videoLength":6
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
October 29 request struct -
|
||||
|
||||
--data-raw
|
||||
@@ -113,4 +158,6 @@ October 22 request struct -
|
||||
"videoGen":true
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
*/
|
||||
|
||||
@@ -15,5 +15,5 @@ workspace-build-acceleration.workspace = true
|
||||
# External
|
||||
easyenv = { path = "../../lib/easyenv" }
|
||||
log.workspace = true
|
||||
reqwest = "0.12.4"
|
||||
reqwest = { version = "0.12.4", default-features = false, features = [ "charset", "http2", "system-proxy", "rustls-tls" ] }
|
||||
serde_json.workspace = true
|
||||
@@ -43,9 +43,9 @@ rand.workspace = true
|
||||
# NB: We switched to Tokio from `async-std` for broader ecosystem support.
|
||||
tokio = { version = "1.38.0", features = ["macros"] }
|
||||
|
||||
sqlx = { version = "0.7.4", features = [ "mysql", "runtime-tokio", "tls-rustls", "chrono" ] }
|
||||
sqlx = { version = "0.7.4", features = [ "mysql", "runtime-tokio-rustls", "chrono" ] }
|
||||
|
||||
elasticsearch = "8.5.0-alpha.1"
|
||||
elasticsearch = { version = "8.5.0-alpha.1", default-features = false, features = ["rustls-tls"] }
|
||||
|
||||
[dev-dependencies]
|
||||
# None yet
|
||||
@@ -39,7 +39,7 @@ log.workspace = true
|
||||
# NB: We switched to Tokio from `async-std` for broader ecosystem support.
|
||||
tokio.workspace = true
|
||||
|
||||
sqlx = { version = "0.7.4", features = [ "mysql", "runtime-tokio", "tls-rustls", "chrono" ] }
|
||||
sqlx = { version = "0.7.4", features = [ "mysql", "runtime-tokio-rustls", "chrono" ] }
|
||||
|
||||
[dev-dependencies]
|
||||
# None yet
|
||||
@@ -36,7 +36,7 @@ log = "0.4.14"
|
||||
|
||||
# Bigger pieces
|
||||
futures = { version = "0.3.17", features = ["thread-pool"] }
|
||||
sqlx = { version = "0.7.4", features = [ "mysql", "runtime-tokio", "tls-rustls", "chrono" ] }
|
||||
sqlx = { version = "0.7.4", features = [ "mysql", "runtime-tokio-rustls", "chrono" ] }
|
||||
tokio = { version = "1.38.0", features = ["macros"] }
|
||||
|
||||
# Serialization
|
||||
|
||||
@@ -36,7 +36,7 @@ log = "0.4.14"
|
||||
|
||||
# Bigger pieces
|
||||
tokio = { version = "1.38.0", features = ["macros"] }
|
||||
sqlx = { version = "0.7.4", features = [ "mysql", "runtime-tokio", "tls-rustls", "chrono" ] }
|
||||
sqlx = { version = "0.7.4", features = [ "mysql", "runtime-tokio-rustls", "chrono" ] }
|
||||
|
||||
# Serialization
|
||||
serde = "1.0.125"
|
||||
|
||||
@@ -38,8 +38,8 @@ strum = { version = "0.26", features = ["derive"] }
|
||||
|
||||
# Bigger pieces
|
||||
tokio = { version = "1.38.0", features = ["macros"] }
|
||||
sqlx = { version = "0.7.4", features = [ "mysql", "runtime-tokio", "tls-rustls", "chrono" ] }
|
||||
elasticsearch = "8.5.0-alpha.1"
|
||||
sqlx = { version = "0.7.4", features = [ "mysql", "runtime-tokio-rustls", "chrono" ] }
|
||||
elasticsearch = { version = "8.5.0-alpha.1", default-features = false, features = ["rustls-tls"] }
|
||||
|
||||
# Serialization
|
||||
serde = "1.0.125"
|
||||
|
||||
@@ -40,7 +40,7 @@ tempdir.workspace = true
|
||||
|
||||
# Async + clients
|
||||
tokio.workspace = true
|
||||
sqlx = { version = "0.7.4", features = [ "mysql", "runtime-tokio", "tls-rustls", "chrono" ] }
|
||||
sqlx = { version = "0.7.4", features = [ "mysql", "runtime-tokio-rustls", "chrono" ] }
|
||||
|
||||
# Serialization
|
||||
serde.workspace = true
|
||||
|
||||
+23
-2
@@ -17,6 +17,7 @@ use crate::services::storyteller::state::storyteller_credential_manager::Storyte
|
||||
use log::{error, info, warn};
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use tauri::{AppHandle, State};
|
||||
use enums::tauri::ux::tauri_command_caller::TauriCommandCaller;
|
||||
use tokens::tokens::media_files::MediaFileToken;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
@@ -27,6 +28,21 @@ pub struct EnqueueImageTo3dObjectRequest {
|
||||
|
||||
/// The model to use.
|
||||
pub model: Option<EnqueueImageTo3dObjectModel>,
|
||||
|
||||
/// OPTIONAL.
|
||||
/// Name of the frontend caller.
|
||||
/// We'll use this to selectively trigger events.
|
||||
pub frontend_caller: Option<TauriCommandCaller>,
|
||||
|
||||
/// OPTIONAL.
|
||||
/// A frontend-defined identifier that we'll send back to the frontend
|
||||
/// as a Tauri event on task completion.
|
||||
pub frontend_subscriber_id: Option<String>,
|
||||
|
||||
/// OPTIONAL.
|
||||
/// A frontend-defined payload that we'll send back to the frontend
|
||||
/// as a Tauri event on task completion.
|
||||
pub frontend_subscriber_payload: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug, Copy, Clone)]
|
||||
@@ -159,7 +175,7 @@ pub async fn handle_request(
|
||||
) -> Result<TaskEnqueueSuccess, GenerateError> {
|
||||
|
||||
let result = handle_object(
|
||||
request,
|
||||
&request,
|
||||
&app,
|
||||
&app_env_configs,
|
||||
&app_data_root,
|
||||
@@ -173,7 +189,12 @@ pub async fn handle_request(
|
||||
};
|
||||
|
||||
let result = success_event
|
||||
.insert_into_task_database(task_database)
|
||||
.insert_into_task_database_with_frontend_payload(
|
||||
task_database,
|
||||
request.frontend_caller.clone(),
|
||||
request.frontend_subscriber_id.as_deref(),
|
||||
request.frontend_subscriber_payload.as_deref(),
|
||||
)
|
||||
.await;
|
||||
|
||||
if let Err(err) = result {
|
||||
|
||||
+1
-1
@@ -10,7 +10,7 @@ use crate::services::storyteller::state::storyteller_credential_manager::Storyte
|
||||
use tauri::AppHandle;
|
||||
|
||||
pub async fn handle_object(
|
||||
request: EnqueueImageTo3dObjectRequest,
|
||||
request: &EnqueueImageTo3dObjectRequest,
|
||||
app: &AppHandle,
|
||||
app_env_configs: &AppEnvConfigs,
|
||||
app_data_root: &AppDataRoot,
|
||||
|
||||
+3
-3
@@ -19,7 +19,7 @@ use storyteller_client::endpoints::generate::object::generate_hunyuan_3d_2_1_ima
|
||||
use tauri::AppHandle;
|
||||
|
||||
pub async fn handle_object_artcraft(
|
||||
request: EnqueueImageTo3dObjectRequest,
|
||||
request: &EnqueueImageTo3dObjectRequest,
|
||||
app: &AppHandle,
|
||||
app_env_configs: &AppEnvConfigs,
|
||||
app_data_root: &AppDataRoot,
|
||||
@@ -59,7 +59,7 @@ pub async fn handle_object_artcraft(
|
||||
used_model = Some(GenerationModel::Hunyuan3d2_0);
|
||||
let request = GenerateHunyuan20ImageTo3dRequest {
|
||||
uuid_idempotency_token,
|
||||
media_file_token: request.image_media_token,
|
||||
media_file_token: request.image_media_token.clone(),
|
||||
};
|
||||
let result = generate_hunyuan3d_2_0_image_to_3d(
|
||||
&app_env_configs.storyteller_host,
|
||||
@@ -82,7 +82,7 @@ pub async fn handle_object_artcraft(
|
||||
used_model = Some(GenerationModel::Hunyuan3d2_1);
|
||||
let request = GenerateHunyuan21ImageTo3dRequest {
|
||||
uuid_idempotency_token,
|
||||
media_file_token: request.image_media_token,
|
||||
media_file_token: request.image_media_token.clone(),
|
||||
};
|
||||
let result = generate_hunyuan3d_2_1_image_to_3d(
|
||||
&app_env_configs.storyteller_host,
|
||||
|
||||
+41
-2
@@ -20,6 +20,7 @@ use crate::services::storyteller::state::storyteller_credential_manager::Storyte
|
||||
use log::{error, info, warn};
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use tauri::{AppHandle, State};
|
||||
use enums::tauri::ux::tauri_command_caller::TauriCommandCaller;
|
||||
use tokens::tokens::media_files::MediaFileToken;
|
||||
use crate::core::events::generation_events::common::GenerationAction;
|
||||
use crate::core::events::generation_events::generation_enqueue_failure_event::GenerationEnqueueFailureEvent;
|
||||
@@ -78,9 +79,33 @@ pub struct EnqueueImageToVideoRequest {
|
||||
/// Text prompt used to direct the video.
|
||||
pub prompt: Option<String>,
|
||||
|
||||
/// Optional.
|
||||
/// Generate the video with audio, if there is an option.
|
||||
/// Typically, this costs money when presented as an option, so we default to off.
|
||||
pub generate_audio: Option<bool>,
|
||||
|
||||
/// OPTIONAL.
|
||||
/// Only for Sora2 model currently.
|
||||
pub sora_orientation: Option<SoraOrientation>,
|
||||
|
||||
/// OPTIONAL.
|
||||
/// Only for Grok model currently
|
||||
pub grok_aspect_ratio: Option<GrokAspectRatio>,
|
||||
|
||||
/// OPTIONAL.
|
||||
/// Name of the frontend caller.
|
||||
/// We'll use this to selectively trigger events.
|
||||
pub frontend_caller: Option<TauriCommandCaller>,
|
||||
|
||||
/// OPTIONAL.
|
||||
/// A frontend-defined identifier that we'll send back to the frontend
|
||||
/// as a Tauri event on task completion.
|
||||
pub frontend_subscriber_id: Option<String>,
|
||||
|
||||
/// OPTIONAL.
|
||||
/// A frontend-defined payload that we'll send back to the frontend
|
||||
/// as a Tauri event on task completion.
|
||||
pub frontend_subscriber_payload: Option<String>,
|
||||
}
|
||||
|
||||
// TODO: Not sure how to handle so many different types of video (model) x (services).
|
||||
@@ -91,6 +116,15 @@ pub enum SoraOrientation {
|
||||
Landscape,
|
||||
}
|
||||
|
||||
// TODO: Not sure how to handle so many different types of video (model) x (services).
|
||||
#[derive(Deserialize, Debug)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum GrokAspectRatio {
|
||||
Portrait,
|
||||
Landscape,
|
||||
Square
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct EnqueueImageToVideoSuccessResponse {
|
||||
}
|
||||
@@ -236,7 +270,7 @@ pub async fn handle_request(
|
||||
}
|
||||
_ => {
|
||||
handle_video(
|
||||
request,
|
||||
&request,
|
||||
app,
|
||||
app_env_configs,
|
||||
app_data_root,
|
||||
@@ -252,7 +286,12 @@ pub async fn handle_request(
|
||||
};
|
||||
|
||||
let result = success_event
|
||||
.insert_into_task_database(task_database)
|
||||
.insert_into_task_database_with_frontend_payload(
|
||||
task_database,
|
||||
request.frontend_caller,
|
||||
request.frontend_subscriber_id.as_deref(),
|
||||
request.frontend_subscriber_payload.as_deref()
|
||||
)
|
||||
.await;
|
||||
|
||||
if let Err(err) = result {
|
||||
|
||||
+2
-2
@@ -9,7 +9,7 @@ use crate::services::storyteller::state::storyteller_credential_manager::Storyte
|
||||
use tauri::AppHandle;
|
||||
|
||||
pub async fn handle_video(
|
||||
request: EnqueueImageToVideoRequest,
|
||||
request: &EnqueueImageToVideoRequest,
|
||||
app: &AppHandle,
|
||||
app_env_configs: &AppEnvConfigs,
|
||||
app_data_root: &AppDataRoot,
|
||||
@@ -25,7 +25,7 @@ pub async fn handle_video(
|
||||
Provider::Sora => {} // Fallthrough
|
||||
Provider::Artcraft => {
|
||||
return Ok(handle_video_artcraft(
|
||||
request,
|
||||
&request,
|
||||
&app,
|
||||
app_env_configs,
|
||||
app_data_root,
|
||||
|
||||
+20
-20
@@ -31,7 +31,7 @@ use storyteller_client::utils::api_host::ApiHost;
|
||||
use tauri::AppHandle;
|
||||
|
||||
pub async fn handle_video_artcraft(
|
||||
request: EnqueueImageToVideoRequest,
|
||||
request: &EnqueueImageToVideoRequest,
|
||||
app: &AppHandle,
|
||||
app_env_configs: &AppEnvConfigs,
|
||||
app_data_root: &AppDataRoot,
|
||||
@@ -84,10 +84,10 @@ pub async fn handle_video_artcraft(
|
||||
selected_model = Some(GenerationModel::Kling1_6);
|
||||
let request = GenerateKling16ProImageToVideoRequest {
|
||||
uuid_idempotency_token,
|
||||
media_file_token: request.image_media_token,
|
||||
end_frame_image_media_token: request.end_frame_image_media_token,
|
||||
media_file_token: request.image_media_token.clone(),
|
||||
end_frame_image_media_token: request.end_frame_image_media_token.clone(),
|
||||
aspect_ratio: Some(GenerateKling16ProAspectRatio::WideSixteenNine),
|
||||
prompt: request.prompt,
|
||||
prompt: request.prompt.clone(),
|
||||
duration: None,
|
||||
};
|
||||
let result = generate_kling_16_pro_image_to_video(
|
||||
@@ -111,9 +111,9 @@ pub async fn handle_video_artcraft(
|
||||
selected_model = Some(GenerationModel::Kling21Master);
|
||||
let request = GenerateKling21MasterImageToVideoRequest {
|
||||
uuid_idempotency_token,
|
||||
media_file_token: request.image_media_token,
|
||||
media_file_token: request.image_media_token.clone(),
|
||||
aspect_ratio: Some(GenerateKling21MasterAspectRatio::WideSixteenNine),
|
||||
prompt: request.prompt,
|
||||
prompt: request.prompt.clone(),
|
||||
duration: None,
|
||||
};
|
||||
let result = generate_kling_21_master_image_to_video(
|
||||
@@ -137,10 +137,10 @@ pub async fn handle_video_artcraft(
|
||||
selected_model = Some(GenerationModel::Kling21Pro);
|
||||
let request = GenerateKling21ProImageToVideoRequest {
|
||||
uuid_idempotency_token,
|
||||
media_file_token: request.image_media_token,
|
||||
end_frame_image_media_token: request.end_frame_image_media_token,
|
||||
media_file_token: request.image_media_token.clone(),
|
||||
end_frame_image_media_token: request.end_frame_image_media_token.clone(),
|
||||
aspect_ratio: Some(GenerateKling21ProAspectRatio::WideSixteenNine),
|
||||
prompt: request.prompt,
|
||||
prompt: request.prompt.clone(),
|
||||
duration: None,
|
||||
};
|
||||
let result = generate_kling_21_pro_image_to_video(
|
||||
@@ -164,9 +164,9 @@ pub async fn handle_video_artcraft(
|
||||
selected_model = Some(GenerationModel::Seedance10Lite);
|
||||
let request = GenerateSeedance10LiteImageToVideoRequest {
|
||||
uuid_idempotency_token,
|
||||
media_file_token: request.image_media_token,
|
||||
end_frame_image_media_token: request.end_frame_image_media_token,
|
||||
prompt: request.prompt,
|
||||
media_file_token: request.image_media_token.clone(),
|
||||
end_frame_image_media_token: request.end_frame_image_media_token.clone(),
|
||||
prompt: request.prompt.clone(),
|
||||
resolution: None,
|
||||
duration: None,
|
||||
};
|
||||
@@ -191,9 +191,9 @@ pub async fn handle_video_artcraft(
|
||||
selected_model = Some(GenerationModel::Veo2);
|
||||
let request = GenerateVeo2ImageToVideoRequest {
|
||||
uuid_idempotency_token,
|
||||
media_file_token: request.image_media_token,
|
||||
media_file_token: request.image_media_token.clone(),
|
||||
aspect_ratio: Some(GenerateVeo2AspectRatio::WideSixteenNine),
|
||||
prompt: request.prompt,
|
||||
prompt: request.prompt.clone(),
|
||||
duration: None,
|
||||
};
|
||||
let result = generate_veo_2_image_to_video(
|
||||
@@ -217,12 +217,12 @@ pub async fn handle_video_artcraft(
|
||||
selected_model = Some(GenerationModel::Veo3);
|
||||
let request = GenerateVeo3ImageToVideoRequest {
|
||||
uuid_idempotency_token,
|
||||
media_file_token: request.image_media_token,
|
||||
media_file_token: request.image_media_token.clone(),
|
||||
aspect_ratio: Some(GenerateVeo3AspectRatio::WideSixteenNine),
|
||||
prompt: request.prompt,
|
||||
prompt: request.prompt.clone(),
|
||||
duration: None,
|
||||
resolution: None,
|
||||
generate_audio: None,
|
||||
generate_audio: request.generate_audio,
|
||||
};
|
||||
let result = generate_veo_3_image_to_video(
|
||||
&app_env_configs.storyteller_host,
|
||||
@@ -245,11 +245,11 @@ pub async fn handle_video_artcraft(
|
||||
selected_model = Some(GenerationModel::Veo3Fast);
|
||||
let request = GenerateVeo3FastImageToVideoRequest {
|
||||
uuid_idempotency_token,
|
||||
media_file_token: request.image_media_token,
|
||||
prompt: request.prompt,
|
||||
media_file_token: request.image_media_token.clone(),
|
||||
prompt: request.prompt.clone(),
|
||||
duration: None,
|
||||
resolution: None,
|
||||
generate_audio: None,
|
||||
generate_audio: request.generate_audio,
|
||||
};
|
||||
let result = generate_veo_3_fast_image_to_video(
|
||||
&app_env_configs.storyteller_host,
|
||||
|
||||
+20
-6
@@ -1,7 +1,7 @@
|
||||
use crate::core::commands::enqueue::generate_error::{BadInputReason, GenerateError};
|
||||
use crate::core::commands::enqueue::image_edit::enqueue_contextual_edit_image_command::EnqueueContextualEditImageCommand;
|
||||
use crate::core::commands::enqueue::image_inpaint::enqueue_image_inpaint_command::EnqueueInpaintImageCommand;
|
||||
use crate::core::commands::enqueue::image_to_video::enqueue_image_to_video_command::{EnqueueImageToVideoRequest, SoraOrientation};
|
||||
use crate::core::commands::enqueue::image_to_video::enqueue_image_to_video_command::{EnqueueImageToVideoRequest, GrokAspectRatio, SoraOrientation};
|
||||
use crate::core::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess;
|
||||
use crate::core::events::functional_events::show_provider_login_modal_event::ShowProviderLoginModalEvent;
|
||||
use crate::core::events::generation_events::common::GenerationModel;
|
||||
@@ -19,9 +19,11 @@ use enums::common::generation_provider::GenerationProvider;
|
||||
use enums::tauri::tasks::task_type::TaskType;
|
||||
use grok_client::credentials::grok_cookies::GrokCookies;
|
||||
use grok_client::credentials::grok_full_credentials::GrokFullCredentials;
|
||||
use grok_client::datatypes::api::aspect_ratio::AspectRatio;
|
||||
use grok_client::datatypes::file_upload_spec::FileUploadSpec;
|
||||
use grok_client::recipes::request_client_secrets::{request_client_secrets, RequestClientSecretsArgs};
|
||||
use grok_client::recipes::upload_image_and_generate_video::{upload_image_and_generate_video, UploadImageAndGenerateVideo};
|
||||
use grok_client::recipes::upload_image_and_generate_video_with_retry::{upload_image_and_generate_video_with_retry, UploadImageAndGenerateVideoWithRetry};
|
||||
use log::{error, info, warn};
|
||||
use openai_sora_client::recipes::generate_sora2_video::generate_sora2_video_with_session_auto_renew::generate_sora2_video_with_session_auto_renew;
|
||||
use openai_sora_client::requests::generate_sora2_video::generate_sora2_video::{GenerateSora2VideoArgs, Orientation};
|
||||
@@ -55,24 +57,36 @@ pub async fn handle_grok_video(
|
||||
image_token,
|
||||
).await?;
|
||||
|
||||
let aspect_ratio = match request.grok_aspect_ratio {
|
||||
None => AspectRatio::WideThreeByTwo,
|
||||
Some(GrokAspectRatio::Landscape) => AspectRatio::WideThreeByTwo,
|
||||
Some(GrokAspectRatio::Portrait) => AspectRatio::TallTwoByThree,
|
||||
Some(GrokAspectRatio::Square) => AspectRatio::Square,
|
||||
};
|
||||
|
||||
info!("Calling Grok Video generate...");
|
||||
|
||||
let upload = upload_image_and_generate_video(UploadImageAndGenerateVideo {
|
||||
full_credentials: &creds,
|
||||
let upload_result = upload_image_and_generate_video_with_retry(UploadImageAndGenerateVideoWithRetry {
|
||||
credentials: &creds,
|
||||
file: FileUploadSpec::Path(local_image_file.path()),
|
||||
prompt: request.prompt.as_deref(),
|
||||
aspect_ratio: Some(aspect_ratio),
|
||||
wait_for_generation: false,
|
||||
individual_request_timeout: Some(GROK_IMAGE_UPLOAD_TIMEOUT)
|
||||
}).await;
|
||||
|
||||
let post_id = match upload {
|
||||
let post_id = match upload_result {
|
||||
Err(err) => {
|
||||
error!("Failed to use Grok video: {:?}", err);
|
||||
return Err(GenerateError::from(err));
|
||||
}
|
||||
Ok(result) => {
|
||||
info!("Successfully enqueued Grok Video: post_id = {:?}", result.post_id);
|
||||
result.post_id
|
||||
if let Some(secrets) = result.maybe_new_client_secrets {
|
||||
info!("New Grok secrets generated! Storing them in the credential manager...");
|
||||
grok_credential_manager.replace_client_secrets_only(secrets)?;
|
||||
}
|
||||
info!("Successfully enqueued Grok Video: post_id = {:?}", result.upload_result.post_id);
|
||||
result.upload_result.post_id
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
+11
-1
@@ -1,12 +1,13 @@
|
||||
use crate::core::commands::enqueue::generate_error::GenerateError;
|
||||
use crate::core::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess;
|
||||
use crate::core::commands::enqueue::text_to_image::enqueue_text_to_image_command::EnqueueTextToImageRequest;
|
||||
use crate::core::commands::enqueue::text_to_image::enqueue_text_to_image_command::{EnqueueTextToImageRequest, TextToImageSize};
|
||||
use crate::core::events::generation_events::common::GenerationModel;
|
||||
use crate::core::state::app_env_configs::app_env_configs::AppEnvConfigs;
|
||||
use crate::services::grok::state::grok_credential_manager::GrokCredentialManager;
|
||||
use crate::services::grok::state::grok_image_prompt_queue::{GrokImagePromptQueue, PromptItem};
|
||||
use enums::common::generation_provider::GenerationProvider;
|
||||
use enums::tauri::tasks::task_type::TaskType;
|
||||
use grok_client::requests::image_websocket::messages::websocket_client_message::ClientMessageAspectRatio;
|
||||
use idempotency::uuid::generate_random_uuid;
|
||||
use tauri::AppHandle;
|
||||
|
||||
@@ -53,9 +54,18 @@ pub async fn handle_grok(
|
||||
.map(|prompt| prompt.trim().to_string())
|
||||
.unwrap_or_else(|| "".to_string());
|
||||
|
||||
let aspect_ratio = match request.aspect_ratio {
|
||||
None => ClientMessageAspectRatio::Square,
|
||||
Some(TextToImageSize::Auto) => ClientMessageAspectRatio::Square,
|
||||
Some(TextToImageSize::Square) => ClientMessageAspectRatio::Square,
|
||||
Some(TextToImageSize::Wide) => ClientMessageAspectRatio::WideThreeByTwo,
|
||||
Some(TextToImageSize::Tall) => ClientMessageAspectRatio::TallTwoByThree,
|
||||
};
|
||||
|
||||
grok_image_prompt_queue.enqueue(PromptItem {
|
||||
task_id: job_id.clone(),
|
||||
prompt,
|
||||
aspect_ratio,
|
||||
})?;
|
||||
|
||||
Ok(TaskEnqueueSuccess {
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
pub mod canvas_background_removal_complete_event;
|
||||
pub mod credits_balance_changed_event;
|
||||
pub mod image_edit_complete_event;
|
||||
pub mod object_generation_complete_event;
|
||||
pub mod refresh_account_state_event;
|
||||
pub mod show_provider_billing_modal_event;
|
||||
pub mod show_provider_login_modal_event;
|
||||
pub mod subscription_plan_changed_event;
|
||||
pub mod text_to_image_generation_complete_event;
|
||||
pub mod text_to_image_generation_complete_event;
|
||||
pub mod video_generation_complete_event;
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
use crate::core::events::basic_sendable_event_trait::{BasicEventStatus, BasicSendableEvent};
|
||||
use enums::tauri::ux::tauri_event_name::TauriEventName;
|
||||
use serde_derive::Serialize;
|
||||
use tokens::tokens::media_files::MediaFileToken;
|
||||
use url::Url;
|
||||
|
||||
#[derive(Clone, Debug, Serialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub struct ObjectGenerationCompleteEvent {
|
||||
pub generated_object: Option<GeneratedObject>,
|
||||
pub maybe_frontend_subscriber_id: Option<String>,
|
||||
pub maybe_frontend_subscriber_payload: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub struct GeneratedObject {
|
||||
pub media_token: MediaFileToken,
|
||||
pub cdn_url: Url,
|
||||
pub maybe_thumbnail_template: Option<String>,
|
||||
}
|
||||
|
||||
impl BasicSendableEvent for ObjectGenerationCompleteEvent {
|
||||
const FRONTEND_EVENT_NAME: TauriEventName = TauriEventName::ObjectGenerationCompleteEvent;
|
||||
const EVENT_STATUS: BasicEventStatus = BasicEventStatus::Success;
|
||||
}
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
use crate::core::events::basic_sendable_event_trait::{BasicEventStatus, BasicSendableEvent};
|
||||
use enums::tauri::ux::tauri_event_name::TauriEventName;
|
||||
use serde_derive::Serialize;
|
||||
use tokens::tokens::media_files::MediaFileToken;
|
||||
use url::Url;
|
||||
|
||||
#[derive(Clone, Debug, Serialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub struct VideoGenerationCompleteEvent {
|
||||
pub generated_video: Option<GeneratedVideo>,
|
||||
pub maybe_frontend_subscriber_id: Option<String>,
|
||||
pub maybe_frontend_subscriber_payload: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub struct GeneratedVideo {
|
||||
pub media_token: MediaFileToken,
|
||||
pub cdn_url: Url,
|
||||
pub maybe_thumbnail_template: Option<String>,
|
||||
}
|
||||
|
||||
impl BasicSendableEvent for VideoGenerationCompleteEvent {
|
||||
const FRONTEND_EVENT_NAME: TauriEventName = TauriEventName::VideoGenerationCompleteEvent;
|
||||
const EVENT_STATUS: BasicEventStatus = BasicEventStatus::Success;
|
||||
}
|
||||
@@ -25,9 +25,14 @@ pub async fn setup_main_window(
|
||||
.focusable(true)
|
||||
.focused(true);
|
||||
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
// NB: Setting decorations(false) "breaks" Mac, though I never documented how/why.
|
||||
// NB: On Linux, setting decorations(false) will cause the window to be unable to be
|
||||
// resized, despite the arrow cursor indicating resize is possible. This might be
|
||||
// fixable another way, but for now it seems fine to disable this. There also seem to
|
||||
// be some performance loss with decorations(false), but I might be imagining it.
|
||||
#[cfg(target_os = "windows")]
|
||||
let win_builder = win_builder
|
||||
.decorations(false); // NB: This breaks Mac!
|
||||
.decorations(false); // NB: This breaks Mac! (And breaks resize on Linux)
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
let win_builder = win_builder
|
||||
|
||||
@@ -102,7 +102,7 @@ impl GrokCredentialManager {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn replace_full_credentials(&self, creds: GrokFullCredentials) -> anyhow::Result<()> {
|
||||
pub fn replace_full_credentials(&self, creds: GrokFullCredentials) -> anyhow::Result<()> {
|
||||
match self.credential_data.write() {
|
||||
Err(err) => Err(anyhow::anyhow!("Failed to acquire write lock: {:?}", err)),
|
||||
Ok(mut holder) => {
|
||||
@@ -112,6 +112,22 @@ impl GrokCredentialManager {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn replace_client_secrets_only(&self, secrets: GrokClientSecrets) -> anyhow::Result<()> {
|
||||
match self.credential_data.write() {
|
||||
Err(err) => Err(anyhow::anyhow!("Failed to acquire write lock: {:?}", err)),
|
||||
Ok(mut holder) => {
|
||||
let mut maybe_full_creds = holder.grok_full_credentials.clone();
|
||||
if let Some(creds) = maybe_full_creds.as_mut() {
|
||||
creds.client_secrets = secrets;
|
||||
} else {
|
||||
warn!("No existing credentials to replace secrets into.");
|
||||
}
|
||||
holder.grok_full_credentials = maybe_full_creds;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// NB: This is just a heuristic. We'll add better checks later.
|
||||
pub fn do_task_polling(&self) -> anyhow::Result<bool> {
|
||||
self.session_appears_active()
|
||||
|
||||
@@ -2,6 +2,7 @@ use crate::core::artcraft_error::ArtcraftError;
|
||||
use log::error;
|
||||
use std::collections::VecDeque;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use grok_client::requests::image_websocket::messages::websocket_client_message::ClientMessageAspectRatio;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct GrokImagePromptQueue {
|
||||
@@ -16,6 +17,9 @@ pub struct PromptItem {
|
||||
|
||||
/// Text prompt
|
||||
pub prompt: String,
|
||||
|
||||
/// The aspect ratio of the image
|
||||
pub aspect_ratio: ClientMessageAspectRatio,
|
||||
}
|
||||
|
||||
impl GrokImagePromptQueue {
|
||||
|
||||
+26
-44
@@ -18,10 +18,12 @@ use enums::common::model_type::ModelType;
|
||||
use enums::tauri::tasks::task_media_file_class::TaskMediaFileClass;
|
||||
use enums::tauri::tasks::task_model_type::TaskModelType;
|
||||
use errors::AnyhowResult;
|
||||
use grok_client::recipes::prompt_websocket_image_with_retry::{prompt_websocket_image_with_retry, PromptWebsocketImageWithRetryArgs};
|
||||
use grok_client::requests::image_websocket::create_listen_websocket::{create_listen_websocket, CreateListenWebsocketArgs};
|
||||
use grok_client::requests::image_websocket::grok_websocket::GrokWebsocket;
|
||||
use grok_client::requests::image_websocket::grok_wrapped_websocket::GrokWrappedWebsocket;
|
||||
use grok_client::requests::image_websocket::listen_for_websocket_images::{listen_for_websocket_images, ImageResults, ListenForWebsocketImagesArgs};
|
||||
use grok_client::requests::image_websocket::messages::websocket_client_message::ClientMessageAspectRatio;
|
||||
use grok_client::requests::image_websocket::prompt_websocket_image::{prompt_websocket_image, PromptWebsocketImageArgs};
|
||||
use idempotency::uuid::generate_random_uuid;
|
||||
use log::{error, info, warn};
|
||||
@@ -85,47 +87,33 @@ async fn inner_loop(
|
||||
continue;
|
||||
}
|
||||
|
||||
let maybe_cookie_header = grok_creds.maybe_copy_cookie_header_string()?;
|
||||
let maybe_cookies = grok_creds.maybe_copy_typed_cookies()?;
|
||||
|
||||
let cookie_header = match maybe_cookie_header {
|
||||
Some(cookie_header) => cookie_header,
|
||||
//let maybe_cookie_header = grok_creds.maybe_copy_cookie_header_string()?;
|
||||
|
||||
let cookies = match maybe_cookies {
|
||||
Some(cookies) => cookies,
|
||||
None => {
|
||||
tokio::time::sleep(std::time::Duration::from_millis(10_000)).await;
|
||||
continue;
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
//let cookie_header = match maybe_cookie_header {
|
||||
// Some(cookie_header) => cookie_header,
|
||||
// None => {
|
||||
// tokio::time::sleep(std::time::Duration::from_millis(10_000)).await;
|
||||
// continue;
|
||||
// }
|
||||
//};
|
||||
|
||||
let websocket = create_listen_websocket(CreateListenWebsocketArgs {
|
||||
cookies: &cookie_header,
|
||||
cookies: cookies.as_str(),
|
||||
}).await?;
|
||||
|
||||
let mut websocket = GrokWebsocket::new(websocket);
|
||||
|
||||
//poll_task_loop(
|
||||
// &app_handle,
|
||||
// &app_env_configs,
|
||||
// &app_data_root,
|
||||
// &task_database,
|
||||
// &grok_creds,
|
||||
// websocket,
|
||||
// prompt_queue,
|
||||
// &storyteller_creds_manager,
|
||||
//).await?;
|
||||
|
||||
loop {
|
||||
// let local_tasks = list_tasks_by_provider_and_status(ListTasksByProviderAndStatusArgs {
|
||||
// db: task_database.get_connection(),
|
||||
// provider: GenerationProvider::Grok,
|
||||
// task_statuses: &TASK_DATABASE_PENDING_STATUSES,
|
||||
// }).await?;
|
||||
// // Only Grok images
|
||||
// let local_tasks = local_tasks.tasks.into_iter()
|
||||
// .filter(|task| match task.model_type {
|
||||
// Some(TaskModelType::GrokImage) => true,
|
||||
// _ => false,
|
||||
// })
|
||||
// .collect::<Vec<_>>();
|
||||
|
||||
let maybe_prompt = prompt_queue.dequeue()?;
|
||||
|
||||
let prompt_item = match maybe_prompt {
|
||||
@@ -136,13 +124,20 @@ async fn inner_loop(
|
||||
}
|
||||
};
|
||||
|
||||
info!("Prompting Grok websocket: {}", prompt_item.prompt);
|
||||
info!("Prompt received over in-memory queue. Prompting Grok websocket: {}", prompt_item.prompt);
|
||||
|
||||
let _result = prompt_websocket_image(PromptWebsocketImageArgs {
|
||||
let result = prompt_websocket_image_with_retry(PromptWebsocketImageWithRetryArgs {
|
||||
websocket: &mut websocket,
|
||||
prompt: &prompt_item.prompt,
|
||||
aspect_ratio: prompt_item.aspect_ratio,
|
||||
cookies: &cookies,
|
||||
}).await?;
|
||||
|
||||
if let Some(new_websocket) = result.maybe_new_websocket {
|
||||
info!("Replacing websocket with new one...");
|
||||
websocket = new_websocket;
|
||||
}
|
||||
|
||||
let images = listen_for_websocket_images(ListenForWebsocketImagesArgs {
|
||||
websocket: &mut websocket,
|
||||
timeout: Duration::from_millis(30_000),
|
||||
@@ -162,19 +157,6 @@ async fn inner_loop(
|
||||
}
|
||||
}
|
||||
|
||||
//async fn poll_task_loop(
|
||||
// app_handle: &AppHandle,
|
||||
// app_env_configs: &AppEnvConfigs,
|
||||
// app_data_root: &AppDataRoot,
|
||||
// task_database: &TaskDatabase,
|
||||
// grok_creds: &GrokCredentialManager,
|
||||
// grok_websocket: GrokWebsocket,
|
||||
// prompt_queue: &GrokImagePromptQueue,
|
||||
// storyteller_creds_manager: &StorytellerCredentialManager,
|
||||
//) -> AnyhowResult<()> {
|
||||
//
|
||||
//}
|
||||
|
||||
async fn upload_images_to_storyteller(
|
||||
app_handle: &AppHandle,
|
||||
app_env_configs: &AppEnvConfigs,
|
||||
|
||||
+244
@@ -0,0 +1,244 @@
|
||||
use crate::core::events::basic_sendable_event_trait::BasicSendableEvent;
|
||||
use crate::core::events::functional_events::image_edit_complete_event::{EditedImage, ImageEditCompleteEvent};
|
||||
use crate::core::events::functional_events::object_generation_complete_event::{GeneratedObject, ObjectGenerationCompleteEvent};
|
||||
use crate::core::events::functional_events::text_to_image_generation_complete_event::{GeneratedImage, TextToImageGenerationCompleteEvent};
|
||||
use crate::core::events::functional_events::video_generation_complete_event::{GeneratedVideo, VideoGenerationCompleteEvent};
|
||||
use crate::core::state::app_env_configs::app_env_configs::AppEnvConfigs;
|
||||
use anyhow::anyhow;
|
||||
use artcraft_api_defs::jobs::list_session_jobs::{ListSessionJobsItem, ListSessionResultDetailsResponse};
|
||||
use artcraft_api_defs::utils::media_links_to_thumbnail_template::media_links_to_thumbnail_template;
|
||||
use enums::tauri::tasks::task_type::TaskType;
|
||||
use enums::tauri::ux::tauri_command_caller::TauriCommandCaller;
|
||||
use errors::AnyhowResult;
|
||||
use log::{error, warn};
|
||||
use sqlite_tasks::queries::list_tasks_by_provider_and_tokens::{list_tasks_by_provider_and_tokens, ListTasksArgs};
|
||||
use sqlite_tasks::queries::task::Task;
|
||||
use storyteller_client::credentials::storyteller_credential_set::StorytellerCredentialSet;
|
||||
use storyteller_client::endpoints::media_files::list_batch_generated_redux_media_files::list_batch_generated_redux_media_files;
|
||||
use tauri::AppHandle;
|
||||
use tokens::tokens::batch_generations::BatchGenerationToken;
|
||||
use tokens::tokens::media_files::MediaFileToken;
|
||||
use crate::core::events::functional_events::canvas_background_removal_complete_event::CanvasBackgroundRemovalCompleteEvent;
|
||||
|
||||
pub async fn maybe_handle_frontend_caller_notification(
|
||||
app: &AppHandle,
|
||||
app_env_configs: &AppEnvConfigs,
|
||||
maybe_creds: Option<&StorytellerCredentialSet>,
|
||||
task: &Task,
|
||||
job: &ListSessionJobsItem,
|
||||
) -> AnyhowResult<()> {
|
||||
|
||||
let job_result = match job.maybe_result {
|
||||
Some(ref res) => res,
|
||||
None => {
|
||||
warn!("Job result is None for task: {:?}", task);
|
||||
return Ok(()); // No result, nothing to do
|
||||
},
|
||||
};
|
||||
|
||||
match task.task_type {
|
||||
TaskType::ImageGeneration => {
|
||||
let _r = handle_image_generation(
|
||||
app,
|
||||
task,
|
||||
job_result,
|
||||
app_env_configs,
|
||||
maybe_creds,
|
||||
).await?;
|
||||
}
|
||||
TaskType::ImageInpaintEdit => {
|
||||
let _r = handle_inpaint_image_generation(
|
||||
app,
|
||||
task,
|
||||
job_result,
|
||||
app_env_configs,
|
||||
maybe_creds,
|
||||
).await?;
|
||||
}
|
||||
TaskType::VideoGeneration => {
|
||||
let _r = handle_video_generation(
|
||||
app,
|
||||
task,
|
||||
job_result,
|
||||
).await?;
|
||||
}
|
||||
TaskType::ObjectGeneration => {
|
||||
let _r = handle_object_generation(
|
||||
app,
|
||||
task,
|
||||
job_result,
|
||||
).await?;
|
||||
}
|
||||
TaskType::BackgroundRemoval => {
|
||||
let _r = handle_background_removal_generation(
|
||||
app,
|
||||
task,
|
||||
job_result,
|
||||
).await?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn handle_image_generation(
|
||||
app: &AppHandle,
|
||||
task: &Task,
|
||||
job_result: &ListSessionResultDetailsResponse,
|
||||
app_env_configs: &AppEnvConfigs,
|
||||
maybe_creds: Option<&StorytellerCredentialSet>,
|
||||
) -> AnyhowResult<()> {
|
||||
|
||||
let generated_images = match job_result.maybe_batch_token.as_ref() {
|
||||
None => {
|
||||
vec![GeneratedImage {
|
||||
media_token: MediaFileToken::new_from_str(&job_result.entity_token),
|
||||
cdn_url: job_result.media_links.cdn_url.clone(),
|
||||
maybe_thumbnail_template: job_result.media_links.maybe_thumbnail_template.clone(),
|
||||
}]
|
||||
}
|
||||
Some(batch_token) => {
|
||||
let result = list_batch_generated_redux_media_files(
|
||||
&app_env_configs.storyteller_host,
|
||||
maybe_creds,
|
||||
batch_token,
|
||||
).await?;
|
||||
|
||||
if result.media_files.is_empty() {
|
||||
return Err(anyhow!("No media files found for batch token: {}", batch_token));
|
||||
}
|
||||
|
||||
result.media_files
|
||||
.into_iter()
|
||||
.map(|file| GeneratedImage {
|
||||
media_token: file.token,
|
||||
cdn_url: file.media_links.cdn_url,
|
||||
maybe_thumbnail_template: file.media_links.maybe_thumbnail_template,
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
};
|
||||
|
||||
let event = TextToImageGenerationCompleteEvent {
|
||||
generated_images,
|
||||
maybe_frontend_subscriber_id: task.frontend_subscriber_id.clone(),
|
||||
maybe_frontend_subscriber_payload: task.frontend_subscriber_payload.clone(),
|
||||
};
|
||||
|
||||
event.send_infallible(&app);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn handle_inpaint_image_generation(
|
||||
app: &AppHandle,
|
||||
task: &Task,
|
||||
job_result: &ListSessionResultDetailsResponse,
|
||||
app_env_configs: &AppEnvConfigs,
|
||||
maybe_creds: Option<&StorytellerCredentialSet>,
|
||||
) -> AnyhowResult<()> {
|
||||
|
||||
let edited_images = match job_result.maybe_batch_token.as_ref() {
|
||||
None => {
|
||||
vec![EditedImage {
|
||||
media_token: MediaFileToken::new_from_str(&job_result.entity_token),
|
||||
cdn_url: job_result.media_links.cdn_url.clone(),
|
||||
maybe_thumbnail_template: job_result.media_links.maybe_thumbnail_template.clone(),
|
||||
}]
|
||||
}
|
||||
Some(batch_token) => {
|
||||
let result = list_batch_generated_redux_media_files(
|
||||
&app_env_configs.storyteller_host,
|
||||
maybe_creds,
|
||||
batch_token,
|
||||
).await?;
|
||||
|
||||
if result.media_files.is_empty() {
|
||||
return Err(anyhow!("No media files found for batch token: {}", batch_token));
|
||||
}
|
||||
|
||||
result.media_files
|
||||
.into_iter()
|
||||
.map(|file| EditedImage {
|
||||
media_token: file.token,
|
||||
cdn_url: file.media_links.cdn_url,
|
||||
maybe_thumbnail_template: file.media_links.maybe_thumbnail_template,
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
};
|
||||
|
||||
let event = ImageEditCompleteEvent {
|
||||
edited_images,
|
||||
maybe_frontend_subscriber_id: task.frontend_subscriber_id.clone(),
|
||||
maybe_frontend_subscriber_payload: task.frontend_subscriber_payload.clone(),
|
||||
};
|
||||
|
||||
event.send_infallible(&app);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn handle_video_generation(
|
||||
app: &AppHandle,
|
||||
task: &Task,
|
||||
job_result: &ListSessionResultDetailsResponse,
|
||||
) -> AnyhowResult<()> {
|
||||
|
||||
// NB: For now, we only generate one video at a time.
|
||||
let event = VideoGenerationCompleteEvent {
|
||||
generated_video: Some(GeneratedVideo {
|
||||
media_token: MediaFileToken::new_from_str(&job_result.entity_token),
|
||||
cdn_url: job_result.media_links.cdn_url.clone(),
|
||||
maybe_thumbnail_template: media_links_to_thumbnail_template(&job_result.media_links)
|
||||
.map(|s| s.to_owned()),
|
||||
}),
|
||||
maybe_frontend_subscriber_id: task.frontend_subscriber_id.clone(),
|
||||
maybe_frontend_subscriber_payload: task.frontend_subscriber_payload.clone(),
|
||||
};
|
||||
|
||||
event.send_infallible(&app);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn handle_object_generation(
|
||||
app: &AppHandle,
|
||||
task: &Task,
|
||||
job_result: &ListSessionResultDetailsResponse,
|
||||
) -> AnyhowResult<()> {
|
||||
|
||||
// NB: For now, we only generate one object (3d mesh) at a time.
|
||||
let event = ObjectGenerationCompleteEvent {
|
||||
generated_object: Some(GeneratedObject {
|
||||
media_token: MediaFileToken::new_from_str(&job_result.entity_token),
|
||||
cdn_url: job_result.media_links.cdn_url.clone(),
|
||||
maybe_thumbnail_template: media_links_to_thumbnail_template(&job_result.media_links)
|
||||
.map(|s| s.to_owned()),
|
||||
}),
|
||||
maybe_frontend_subscriber_id: task.frontend_subscriber_id.clone(),
|
||||
maybe_frontend_subscriber_payload: task.frontend_subscriber_payload.clone(),
|
||||
};
|
||||
|
||||
event.send_infallible(&app);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn handle_background_removal_generation(
|
||||
app: &AppHandle,
|
||||
task: &Task,
|
||||
job_result: &ListSessionResultDetailsResponse,
|
||||
) -> AnyhowResult<()> {
|
||||
|
||||
let event = CanvasBackgroundRemovalCompleteEvent {
|
||||
media_token: MediaFileToken::new_from_str(&job_result.entity_token),
|
||||
image_cdn_url: job_result.media_links.cdn_url.clone(),
|
||||
maybe_frontend_subscriber_id: task.frontend_subscriber_id.clone(),
|
||||
maybe_frontend_subscriber_payload: task.frontend_subscriber_payload.clone(),
|
||||
};
|
||||
|
||||
event.send_infallible(&app);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
pub (super) mod maybe_handle_frontend_caller_notification;
|
||||
pub (super) mod maybe_handle_inpainting_complete_event;
|
||||
pub (super) mod maybe_handle_text_to_image_complete_event;
|
||||
pub (super) mod maybe_send_background_removal_complete_event;
|
||||
+33
-20
@@ -12,6 +12,7 @@ use crate::core::utils::enum_conversion::task_type::to_generation_action;
|
||||
use crate::services::sora::state::sora_credential_manager::SoraCredentialManager;
|
||||
use crate::services::sora::state::sora_task_queue::SoraTaskQueue;
|
||||
use crate::services::storyteller::state::storyteller_credential_manager::StorytellerCredentialManager;
|
||||
use crate::services::storyteller::threads::events::maybe_handle_frontend_caller_notification::maybe_handle_frontend_caller_notification;
|
||||
use crate::services::storyteller::threads::events::maybe_handle_inpainting_complete_event::maybe_handle_inpainting_complete_event;
|
||||
use crate::services::storyteller::threads::events::maybe_handle_text_to_image_complete_event::maybe_handle_text_to_image_complete_event;
|
||||
use crate::services::storyteller::threads::events::maybe_send_background_removal_complete_event::maybe_send_background_removal_complete_event;
|
||||
@@ -197,7 +198,7 @@ async fn send_additional_events(
|
||||
) {
|
||||
info!("Attempting to dispatch events for completed Storyteller : {:?}", task);
|
||||
|
||||
let result = maybe_handle_text_to_image_complete_event(
|
||||
let result = maybe_handle_frontend_caller_notification(
|
||||
app_handle,
|
||||
app_env_configs,
|
||||
creds,
|
||||
@@ -206,30 +207,42 @@ async fn send_additional_events(
|
||||
).await;
|
||||
|
||||
if let Err(err) = result {
|
||||
error!("Failed to send text-to-image complete event: {:?}", err);
|
||||
error!("Failed to send generation complete event: {:?}", err);
|
||||
}
|
||||
|
||||
let result = maybe_handle_inpainting_complete_event(
|
||||
app_handle,
|
||||
app_env_configs,
|
||||
creds,
|
||||
task,
|
||||
job,
|
||||
).await;
|
||||
//let result = maybe_handle_text_to_image_complete_event(
|
||||
// app_handle,
|
||||
// app_env_configs,
|
||||
// creds,
|
||||
// task,
|
||||
// job,
|
||||
//).await;
|
||||
|
||||
if let Err(err) = result {
|
||||
error!("Failed to send image inpainting complete event: {:?}", err);
|
||||
}
|
||||
//if let Err(err) = result {
|
||||
// error!("Failed to send text-to-image complete event: {:?}", err);
|
||||
//}
|
||||
|
||||
let result = maybe_send_background_removal_complete_event(
|
||||
app_handle,
|
||||
task,
|
||||
job,
|
||||
).await;
|
||||
//let result = maybe_handle_inpainting_complete_event(
|
||||
// app_handle,
|
||||
// app_env_configs,
|
||||
// creds,
|
||||
// task,
|
||||
// job,
|
||||
//).await;
|
||||
|
||||
if let Err(err) = result {
|
||||
error!("Failed to send background removal complete event: {:?}", err);
|
||||
}
|
||||
//if let Err(err) = result {
|
||||
// error!("Failed to send image inpainting complete event: {:?}", err);
|
||||
//}
|
||||
|
||||
//let result = maybe_send_background_removal_complete_event(
|
||||
// app_handle,
|
||||
// task,
|
||||
// job,
|
||||
//).await;
|
||||
|
||||
//if let Err(err) = result {
|
||||
// error!("Failed to send background removal complete event: {:?}", err);
|
||||
//}
|
||||
}
|
||||
|
||||
fn get_thumbnail_template<'a>(job: &'a ListSessionJobsItem) -> Option<&'a str> {
|
||||
|
||||
@@ -20,5 +20,5 @@ errors = { path = "../errors" }
|
||||
workspace-build-acceleration = { version = "0.1", path = "../../workspace-build-acceleration" }
|
||||
|
||||
# External
|
||||
lettre = "0.11.2"
|
||||
lettre = { version = "0.11.2", default-features = false, features = ["smtp-transport", "pool", "hostname", "builder", "rustls-tls"] }
|
||||
|
||||
@@ -42,7 +42,7 @@ toml.workspace = true
|
||||
|
||||
# Runtime + Clients
|
||||
tokio = { version = "1.38.0", features = ["full"] }
|
||||
sqlx = { version = "0.7.4", features = [ "mysql", "runtime-tokio", "tls-rustls", "chrono" ] }
|
||||
sqlx = { version = "0.7.4", features = [ "mysql", "runtime-tokio-rustls", "chrono" ] }
|
||||
|
||||
[dev-dependencies]
|
||||
# None yet
|
||||
@@ -23,7 +23,7 @@ errors = { path = "../errors" }
|
||||
workspace-build-acceleration = { version = "0.1", path = "../../workspace-build-acceleration" }
|
||||
|
||||
# External
|
||||
sqlx = { version = "0.7.4", features = [ "mysql", "runtime-tokio", "tls-rustls", "chrono" ] }
|
||||
sqlx = { version = "0.7.4", features = [ "mysql", "runtime-tokio-rustls", "chrono" ] }
|
||||
|
||||
[dev-dependencies]
|
||||
# None yet
|
||||
@@ -35,3 +35,12 @@ pub const TALL_MOCHI_WITH_GLASSES_GLASSES_MASK_IMAGE_URL : &str = "https://cdn-2
|
||||
|
||||
// media token: m_77cpdyaby60z0qv9g6tv67gw2a1337
|
||||
pub const TREX_SKELETON_IMAGE_URL : &str = "https://cdn-2.fakeyou.com/media/f/4/j/5/3/f4j53296sc35wqs3csgw4j4vvjf5yyxe/image_f4j53296sc35wqs3csgw4j4vvjf5yyxe.png";
|
||||
|
||||
// media token: m_zyh17gn6prfp4xs6z76mqkz9k0sr0j
|
||||
pub const TALL_CORGI_SHIBA_TREASURE_OCEAN_URL: &str = "https://cdn-2.fakeyou.com/media/j/z/7/2/p/jz72pgwb6fegem74pr36yy88hsshwx4m/artcraft_jz72pgwb6fegem74pr36yy88hsshwx4m.png";
|
||||
|
||||
// media token: m_a9c4g43nw87y6mebjammrqz849c5xe
|
||||
pub const WIDE_CORGI_SHIBA_TREASURE_OCEAN_URL: &str = "https://cdn-2.fakeyou.com/media/v/a/g/g/a/vaggan4137tqsz173432cjjg33xkrxpc/artcraft_vaggan4137tqsz173432cjjg33xkrxpc.png";
|
||||
|
||||
// media token: m_ytkp3tk2ptvr03p75fwqvend0m3qsg
|
||||
pub const TALL_CORGI_SHIBA_TREASURE_SKY_URL: &str = "https://cdn-2.fakeyou.com/media/a/4/s/x/x/a4sxxecwenrbta5kx5jk34dp2z9apxqv/artcraft_a4sxxecwenrbta5kx5jk34dp2z9apxqv.png";
|
||||
@@ -36,7 +36,7 @@ serde_derive = "1.0.125"
|
||||
serde_json = "1.0.108"
|
||||
|
||||
# HTTP
|
||||
reqwest = { version = "0.12", features = ["json"] } # HTTP Client
|
||||
reqwest = { version = "0.12", default-features = false, features = ["charset", "http2", "system-proxy", "rustls-tls", "json"] } # HTTP Client
|
||||
|
||||
# Scraping
|
||||
rss = "2.0.2"
|
||||
|
||||
@@ -25,7 +25,7 @@ errors = { workspace = true }
|
||||
tokens = { workspace = true }
|
||||
|
||||
# External
|
||||
elasticsearch = "8.5.0-alpha.1"
|
||||
elasticsearch = { version = "8.5.0-alpha.1", default-features = false, features = ["rustls-tls"] }
|
||||
jsonpath_lib = "0.3.0"
|
||||
log = "0.4.14"
|
||||
once_cell = "1.19.0"
|
||||
|
||||
@@ -47,7 +47,7 @@ num-traits = "0.2.19"
|
||||
|
||||
tokio = { version = "1.38.0", features = ["full"] }
|
||||
rand.workspace = true
|
||||
sqlx = { version = "0.7.4", features = [ "mysql", "runtime-tokio", "tls-rustls", "chrono", "bigdecimal" ] }
|
||||
sqlx = { version = "0.7.4", features = [ "mysql", "runtime-tokio-rustls", "chrono", "bigdecimal" ] }
|
||||
|
||||
[dev-dependencies]
|
||||
serial_test = "3.0.0"
|
||||
@@ -1,3 +1,5 @@
|
||||
# NOTE: THIS IS NOT THE ACTIVE `sqlite` package!
|
||||
|
||||
[package]
|
||||
name = "sqlite_queries"
|
||||
edition = "2018"
|
||||
@@ -28,7 +30,7 @@ log = "0.4.14"
|
||||
once_cell = "1.9.0"
|
||||
regex = "1.5.4"
|
||||
|
||||
sqlx = { version = "0.7.4", features = [ "sqlite", "runtime-tokio-native-tls", "chrono" ] }
|
||||
sqlx = { version = "0.7.4", features = [ "sqlite", "runtime-tokio-rustls", "chrono" ] }
|
||||
|
||||
[dev-dependencies]
|
||||
# None yet
|
||||
@@ -27,7 +27,7 @@ log.workspace = true
|
||||
|
||||
# TODO(bt,2025-07-09): Update sqlx ASAP. We're stuck on 0.7 because of the complex use of internal types and macros in the database crate.
|
||||
# We're going to embed in-memory SQLite
|
||||
sqlx = { package = "sqlx", version = "0.7.4", features = ["runtime-tokio", "sqlite", "chrono"] }
|
||||
sqlx = { package = "sqlx", version = "0.7.4", features = ["runtime-tokio-rustls", "sqlite", "chrono"] }
|
||||
|
||||
# Needed to safety check some queries we can't use escaping for
|
||||
once_cell.workspace = true
|
||||
@@ -43,5 +43,5 @@ serde_json.workspace = true
|
||||
|
||||
[build-dependencies]
|
||||
tempfile.workspace = true
|
||||
sqlx = { package = "sqlx", version = "0.7.4", features = ["runtime-tokio", "sqlite", "chrono"] }
|
||||
sqlx = { package = "sqlx", version = "0.7.4", features = ["runtime-tokio-rustls", "sqlite", "chrono"] }
|
||||
#rusqlite = "0.25.4" # NB: For the build.rs file
|
||||
@@ -28,7 +28,7 @@ tokens = { workspace = true }
|
||||
serde = "1.0.125"
|
||||
serde_derive = "1.0.125"
|
||||
|
||||
sqlx = { version = "0.7.4", features = [ "mysql", "sqlite", "runtime-tokio-native-tls", "chrono" ] }
|
||||
sqlx = { version = "0.7.4", features = [ "mysql", "sqlite", "runtime-tokio-rustls", "chrono" ] }
|
||||
sqlx-core = { version = "0.7.4" }
|
||||
sqlx-mysql= { version = "0.7.4" }
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@ serde.workspace = true
|
||||
serde_derive.workspace = true
|
||||
|
||||
# Database conversion of enum types is optional
|
||||
sqlx = { version = "0.7.4", features = [ "mysql", "runtime-tokio-native-tls", "chrono" ], optional = true }
|
||||
sqlx = { version = "0.7.4", features = [ "mysql", "runtime-tokio-rustls", "chrono" ], optional = true }
|
||||
sqlx-core = { version = "0.7.4" , optional = true }
|
||||
sqlx-mysql = { version = "0.7.4" , optional = true }
|
||||
|
||||
|
||||
@@ -21,6 +21,8 @@ pub enum TauriCommandCaller {
|
||||
TextToImage,
|
||||
/// The image-to-video page
|
||||
ImageToVideo,
|
||||
/// A mini-app (doesn't specify which one)
|
||||
MiniApp,
|
||||
}
|
||||
|
||||
impl_enum_display_and_debug_using_to_str!(TauriCommandCaller);
|
||||
@@ -36,6 +38,7 @@ impl TauriCommandCaller {
|
||||
Self::ImageEditor => "image_editor",
|
||||
Self::TextToImage => "text_to_image",
|
||||
Self::ImageToVideo => "image_to_video",
|
||||
Self::MiniApp => "mini_app",
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,6 +48,7 @@ impl TauriCommandCaller {
|
||||
"image_editor" => Ok(Self::ImageEditor),
|
||||
"text_to_image" => Ok(Self::TextToImage),
|
||||
"image_to_video" => Ok(Self::ImageToVideo),
|
||||
"mini_app" => Ok(Self::MiniApp),
|
||||
_ => Err(EnumError::CouldNotConvertFromString(value.to_string())),
|
||||
}
|
||||
}
|
||||
@@ -57,6 +61,7 @@ impl TauriCommandCaller {
|
||||
Self::ImageEditor,
|
||||
Self::TextToImage,
|
||||
Self::ImageToVideo,
|
||||
Self::MiniApp,
|
||||
])
|
||||
}
|
||||
}
|
||||
@@ -76,6 +81,7 @@ mod tests {
|
||||
assert_serialization(TauriCommandCaller::ImageEditor, "image_editor");
|
||||
assert_serialization(TauriCommandCaller::TextToImage, "text_to_image");
|
||||
assert_serialization(TauriCommandCaller::ImageToVideo, "image_to_video");
|
||||
assert_serialization(TauriCommandCaller::MiniApp, "mini_app");
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -84,6 +90,7 @@ mod tests {
|
||||
assert_eq!(TauriCommandCaller::ImageEditor.to_str(), "image_editor");
|
||||
assert_eq!(TauriCommandCaller::TextToImage.to_str(), "text_to_image");
|
||||
assert_eq!(TauriCommandCaller::ImageToVideo.to_str(), "image_to_video");
|
||||
assert_eq!(TauriCommandCaller::MiniApp.to_str(), "mini_app");
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -92,6 +99,7 @@ mod tests {
|
||||
assert_eq!(TauriCommandCaller::from_str("image_editor").unwrap(), TauriCommandCaller::ImageEditor);
|
||||
assert_eq!(TauriCommandCaller::from_str("text_to_image").unwrap(), TauriCommandCaller::TextToImage);
|
||||
assert_eq!(TauriCommandCaller::from_str("image_to_video").unwrap(), TauriCommandCaller::ImageToVideo);
|
||||
assert_eq!(TauriCommandCaller::from_str("mini_app").unwrap(), TauriCommandCaller::MiniApp);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -108,11 +116,12 @@ mod tests {
|
||||
#[test]
|
||||
fn all_variants() {
|
||||
let mut variants = TauriCommandCaller::all_variants();
|
||||
assert_eq!(variants.len(), 4);
|
||||
assert_eq!(variants.len(), 5);
|
||||
assert_eq!(variants.pop_first(), Some(TauriCommandCaller::Canvas));
|
||||
assert_eq!(variants.pop_first(), Some(TauriCommandCaller::ImageEditor));
|
||||
assert_eq!(variants.pop_first(), Some(TauriCommandCaller::TextToImage));
|
||||
assert_eq!(variants.pop_first(), Some(TauriCommandCaller::ImageToVideo));
|
||||
assert_eq!(variants.pop_first(), Some(TauriCommandCaller::MiniApp));
|
||||
assert_eq!(variants.pop_first(), None);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,7 +54,7 @@ pub enum TauriEventName {
|
||||
CanvasBgRemovedEvent,
|
||||
|
||||
/// Special event:
|
||||
/// Image edit is complete
|
||||
/// Image generation is complete
|
||||
#[serde(rename = "text_to_image_generation_complete_event")]
|
||||
TextToImageGenerationCompleteEvent,
|
||||
|
||||
@@ -62,6 +62,16 @@ pub enum TauriEventName {
|
||||
/// Image edit is complete
|
||||
#[serde(rename = "image_edit_complete_event")]
|
||||
ImageEditCompleteEvent,
|
||||
|
||||
/// Special event:
|
||||
/// Object (3D mesh) generation is complete
|
||||
#[serde(rename = "object_generation_complete_event")]
|
||||
ObjectGenerationCompleteEvent,
|
||||
|
||||
/// Special event:
|
||||
/// Video generation is complete
|
||||
#[serde(rename = "video_generation_complete_event")]
|
||||
VideoGenerationCompleteEvent,
|
||||
|
||||
/// Special event:
|
||||
/// Refresh account states
|
||||
@@ -102,6 +112,8 @@ impl TauriEventName {
|
||||
Self::CanvasBgRemovedEvent => "canvas_bg_removed_event",
|
||||
Self::TextToImageGenerationCompleteEvent => "text_to_image_generation_complete_event",
|
||||
Self::ImageEditCompleteEvent => "image_edit_complete_event",
|
||||
Self::ObjectGenerationCompleteEvent => "object_generation_complete_event",
|
||||
Self::VideoGenerationCompleteEvent => "video_generation_complete_event",
|
||||
Self::RefreshAccountStateEvent => "refresh_account_state_event",
|
||||
Self::ShowProviderBillingModalEvent => "show_provider_billing_modal_event",
|
||||
Self::ShowProviderLoginModalEvent => "show_provider_login_modal_event",
|
||||
@@ -120,6 +132,8 @@ impl TauriEventName {
|
||||
"canvas_bg_removed_event" => Ok(Self::CanvasBgRemovedEvent),
|
||||
"text_to_image_generation_complete_event" => Ok(Self::TextToImageGenerationCompleteEvent),
|
||||
"image_edit_complete_event" => Ok(Self::ImageEditCompleteEvent),
|
||||
"object_generation_complete_event" => Ok(Self::ObjectGenerationCompleteEvent),
|
||||
"video_generation_complete_event" => Ok(Self::VideoGenerationCompleteEvent),
|
||||
"refresh_account_state_event" => Ok(Self::RefreshAccountStateEvent),
|
||||
"show_provider_billing_modal_event" => Ok(Self::ShowProviderBillingModalEvent),
|
||||
"show_provider_login_modal_event" => Ok(Self::ShowProviderLoginModalEvent),
|
||||
@@ -141,6 +155,8 @@ impl TauriEventName {
|
||||
Self::CanvasBgRemovedEvent,
|
||||
Self::TextToImageGenerationCompleteEvent,
|
||||
Self::ImageEditCompleteEvent,
|
||||
Self::ObjectGenerationCompleteEvent,
|
||||
Self::VideoGenerationCompleteEvent,
|
||||
Self::RefreshAccountStateEvent,
|
||||
Self::ShowProviderBillingModalEvent,
|
||||
Self::ShowProviderLoginModalEvent,
|
||||
@@ -168,6 +184,8 @@ mod tests {
|
||||
assert_serialization(TauriEventName::CanvasBgRemovedEvent, "canvas_bg_removed_event");
|
||||
assert_serialization(TauriEventName::TextToImageGenerationCompleteEvent, "text_to_image_generation_complete_event");
|
||||
assert_serialization(TauriEventName::ImageEditCompleteEvent, "image_edit_complete_event");
|
||||
assert_serialization(TauriEventName::ObjectGenerationCompleteEvent, "object_generation_complete_event");
|
||||
assert_serialization(TauriEventName::VideoGenerationCompleteEvent, "video_generation_complete_event");
|
||||
assert_serialization(TauriEventName::RefreshAccountStateEvent, "refresh_account_state_event");
|
||||
assert_serialization(TauriEventName::ShowProviderBillingModalEvent, "show_provider_billing_modal_event");
|
||||
assert_serialization(TauriEventName::ShowProviderLoginModalEvent, "show_provider_login_modal_event");
|
||||
@@ -185,6 +203,8 @@ mod tests {
|
||||
assert_eq!(TauriEventName::CanvasBgRemovedEvent.to_str(), "canvas_bg_removed_event");
|
||||
assert_eq!(TauriEventName::TextToImageGenerationCompleteEvent.to_str(), "text_to_image_generation_complete_event");
|
||||
assert_eq!(TauriEventName::ImageEditCompleteEvent.to_str(), "image_edit_complete_event");
|
||||
assert_eq!(TauriEventName::ObjectGenerationCompleteEvent.to_str(), "object_generation_complete_event");
|
||||
assert_eq!(TauriEventName::VideoGenerationCompleteEvent.to_str(), "video_generation_complete_event");
|
||||
assert_eq!(TauriEventName::RefreshAccountStateEvent.to_str(), "refresh_account_state_event");
|
||||
assert_eq!(TauriEventName::ShowProviderBillingModalEvent.to_str(), "show_provider_billing_modal_event");
|
||||
assert_eq!(TauriEventName::ShowProviderLoginModalEvent.to_str(), "show_provider_login_modal_event");
|
||||
@@ -202,6 +222,8 @@ mod tests {
|
||||
assert_eq!(TauriEventName::from_str("canvas_bg_removed_event").unwrap(), TauriEventName::CanvasBgRemovedEvent);
|
||||
assert_eq!(TauriEventName::from_str("text_to_image_generation_complete_event").unwrap(), TauriEventName::TextToImageGenerationCompleteEvent);
|
||||
assert_eq!(TauriEventName::from_str("image_edit_complete_event").unwrap(), TauriEventName::ImageEditCompleteEvent);
|
||||
assert_eq!(TauriEventName::from_str("object_generation_complete_event").unwrap(), TauriEventName::ObjectGenerationCompleteEvent);
|
||||
assert_eq!(TauriEventName::from_str("video_generation_complete_event").unwrap(), TauriEventName::VideoGenerationCompleteEvent);
|
||||
assert_eq!(TauriEventName::from_str("refresh_account_state_event").unwrap(), TauriEventName::RefreshAccountStateEvent);
|
||||
assert_eq!(TauriEventName::from_str("show_provider_billing_modal_event").unwrap(), TauriEventName::ShowProviderBillingModalEvent);
|
||||
assert_eq!(TauriEventName::from_str("show_provider_login_modal_event").unwrap(), TauriEventName::ShowProviderLoginModalEvent);
|
||||
@@ -211,7 +233,7 @@ mod tests {
|
||||
#[test]
|
||||
fn all_variants() {
|
||||
let mut variants = TauriEventName::all_variants();
|
||||
assert_eq!(variants.len(), 13);
|
||||
assert_eq!(variants.len(), 15);
|
||||
assert_eq!(variants.pop_first(), Some(TauriEventName::GenerationEnqueueSuccessEvent));
|
||||
assert_eq!(variants.pop_first(), Some(TauriEventName::GenerationEnqueueFailureEvent));
|
||||
assert_eq!(variants.pop_first(), Some(TauriEventName::GenerationCompleteEvent));
|
||||
@@ -221,6 +243,8 @@ mod tests {
|
||||
assert_eq!(variants.pop_first(), Some(TauriEventName::CanvasBgRemovedEvent));
|
||||
assert_eq!(variants.pop_first(), Some(TauriEventName::TextToImageGenerationCompleteEvent));
|
||||
assert_eq!(variants.pop_first(), Some(TauriEventName::ImageEditCompleteEvent));
|
||||
assert_eq!(variants.pop_first(), Some(TauriEventName::ObjectGenerationCompleteEvent));
|
||||
assert_eq!(variants.pop_first(), Some(TauriEventName::VideoGenerationCompleteEvent));
|
||||
assert_eq!(variants.pop_first(), Some(TauriEventName::RefreshAccountStateEvent));
|
||||
assert_eq!(variants.pop_first(), Some(TauriEventName::ShowProviderBillingModalEvent));
|
||||
assert_eq!(variants.pop_first(), Some(TauriEventName::ShowProviderLoginModalEvent));
|
||||
|
||||
@@ -30,7 +30,8 @@ serde = { workspace = true }
|
||||
serde_derive = { workspace = true }
|
||||
|
||||
# MySql Database
|
||||
sqlx = { version = "0.7.4", features = [ "mysql", "runtime-tokio", "tls-rustls", "chrono" ] }
|
||||
sqlx = { version = "0.7.4", default-features = false, features = [ "mysql", "runtime-tokio-rustls", "chrono" ] }
|
||||
sqlx-core = { version = "0.7.4", features = ["rustls"] }
|
||||
|
||||
[dev-dependencies]
|
||||
# Test serialization
|
||||
|
||||
@@ -36,7 +36,7 @@ rand = { version = "0.8.5", features = ["std_rng"] }
|
||||
once_cell.workspace = true
|
||||
|
||||
# Database conversion of enum types is optional
|
||||
sqlx = { version = "0.7.4", features = [ "mysql", "runtime-tokio", "tls-rustls", "chrono" ], optional = true }
|
||||
sqlx = { version = "0.7.4", features = [ "mysql", "runtime-tokio-rustls", "chrono" ], optional = true }
|
||||
sqlx-core = { version = "0.7.4", optional = true }
|
||||
sqlx-mysql= { version = "0.7.4", optional = true }
|
||||
|
||||
|
||||
@@ -38,7 +38,7 @@ hostname = "0.4.0"
|
||||
log = "0.4.14"
|
||||
rand = "0.8.5"
|
||||
|
||||
sqlx = { version = "0.7.4", features = [ "mysql", "runtime-tokio", "tls-rustls", "chrono" ] }
|
||||
sqlx = { version = "0.7.4", features = [ "mysql", "runtime-tokio-rustls", "chrono" ] }
|
||||
|
||||
# Runtime
|
||||
# NB: We switched to Tokio from `async-std` for broader ecosystem support.
|
||||
|
||||
@@ -50,7 +50,7 @@ subprocess = "0.2.9"
|
||||
tempdir.workspace = true
|
||||
zip = "0.6.6"
|
||||
|
||||
sqlx = { version = "0.7.4", features = [ "mysql", "runtime-tokio", "tls-rustls", "chrono" ] }
|
||||
sqlx = { version = "0.7.4", features = [ "mysql", "runtime-tokio-rustls", "chrono" ] }
|
||||
|
||||
# Redis
|
||||
r2d2_redis = "0.14.0"
|
||||
|
||||
@@ -37,7 +37,7 @@ server_environment = { path = "../../../lib/server_environment" }
|
||||
anyhow = { workspace = true }
|
||||
log = { workspace = true }
|
||||
|
||||
sqlx = { version = "0.7.4", features = [ "mysql", "runtime-tokio", "tls-rustls", "chrono" ] }
|
||||
sqlx = { version = "0.7.4", features = [ "mysql", "runtime-tokio-rustls", "chrono" ] }
|
||||
|
||||
# Redis
|
||||
r2d2_redis = "0.14.0"
|
||||
|
||||
Loaded 100 of 209 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user