Compare commits

..
Author SHA1 Message Date
Brandon Thomas 1957bd32c7 tools for image gen 2025-10-30 17:09:02 -04:00
Brandon Thomas 9d161aae97 include request id 2025-10-30 16:48:38 -04:00
Brandon Thomas 977ea223cd tool to poll images 2025-10-30 16:38:52 -04:00
Brandon Thomas 25ed1a0cdf video download client 2025-10-30 15:48:07 -04:00
Brandon Thomas b396d95f9a parse video token and url 2025-10-30 15:26:53 -04:00
Brandon Thomas 33d4264db5 cleanup logging 2025-10-30 15:10:31 -04:00
Brandon Thomas 397409c60b debug logging instead of print 2025-10-30 15:00:53 -04:00
Brandon Thomas 9386fbbc86 cleanup 2025-10-30 14:45:10 -04:00
Brandon Thomas 6f16f9f832 cleanup client of dead weight 2025-10-30 14:33:14 -04:00
Brandon Thomas 14bc3f80c8 refactor 2025-10-30 14:27:52 -04:00
Brandon Thomas b7e635bc0b refactor 2025-10-30 14:11:58 -04:00
Brandon Thomas e45f74617c refactor 2025-10-30 13:23:57 -04:00
Brandon Thomas cc9089388f parse email address from index 2025-10-29 04:32:28 -04:00
Brandon Thomas a906bcf1be fetch user id 2025-10-29 04:26:40 -04:00
Brandon Thomas e92bf8c0cb add auto-liking 2025-10-29 03:38:27 -04:00
Brandon Thomas 245f193b4a fix request 2025-10-29 03:01:07 -04:00
Brandon Thomas e15880a078 updated request, not generating video 2025-10-29 02:45:30 -04:00
Brandon Thomas 8cc42de9e1 wip 2025-10-29 02:33:55 -04:00
Brandon Thomas 41cebf6f54 wip 2025-10-29 02:14:20 -04:00
Brandon Thomas 32d7cfb620 refactor 2025-10-29 02:04:45 -04:00
Brandon Thomas 829944068d wip, refactor 2025-10-29 01:30:49 -04:00
Brandon Thomas 03f34755c6 refactor 2025-10-29 01:14:26 -04:00
Brandon Thomas 3d46e8d4b6 refactor 2025-10-29 01:05:42 -04:00
Brandon Thomas 44dce4eb7f reorganize 2025-10-29 00:57:51 -04:00
Brandon Thomas 4d89ce130b wip 2025-10-29 00:48:20 -04:00
Brandon Thomas 6d19931943 refactor, stronger types 2025-10-29 00:37:13 -04:00
Brandon Thomas 1c99dfa48f stronger types 2025-10-29 00:12:03 -04:00
Brandon Thomas ad7f7928f5 cleanup 2025-10-28 22:14:48 -04:00
Brandon Thomas 27b400724c cleanup 2025-10-28 21:44:27 -04:00
Brandon Thomas 28ab25b4d3 extract sentry trace, new recipe for getting all ingredients 2025-10-28 21:40:13 -04:00
Brandon Thomas ab5f6e184b video wip 2025-10-28 20:54:58 -04:00
Brandon Thomas 81a6264154 timestamp is testable 2025-10-28 20:32:00 -04:00
Brandon Thomas d3269d185f working 2025-10-28 20:26:34 -04:00
Brandon Thomas 0eb2f9f79c wip 2025-10-28 20:12:48 -04:00
Brandon Thomas b7739295a8 xs works 2025-10-28 19:13:15 -04:00
Brandon Thomas 9ea6109bdf test cases from python 2025-10-28 19:07:41 -04:00
Brandon Thomas 0096ba5136 sig_to_hex needs tests 2025-10-28 19:03:02 -04:00
Brandon Thomas cd569002c9 wip on sig_to_hex 2025-10-28 18:55:25 -04:00
Brandon Thomas 1d1ab51baa matches 2025-10-28 18:18:57 -04:00
Brandon Thomas 15d5596cdf signature style working with test case 2025-10-28 18:05:58 -04:00
Brandon Thomas 8f044a79e1 helpers 2025-10-28 17:50:00 -04:00
Brandon Thomas 7ddfea4c74 trig 2025-10-28 17:44:19 -04:00
Brandon Thomas 87f3d0c5f2 color 2025-10-28 17:36:22 -04:00
Brandon Thomas ad984c9adf another test case 2025-10-28 17:24:53 -04:00
Brandon Thomas 8541de78bf bezier working, needs more tests 2025-10-28 17:22:31 -04:00
Brandon Thomas 91eac7e940 bezier wip 2025-10-28 17:18:33 -04:00
Brandon Thomas d9770440b9 stylecp matches python 2025-10-28 17:01:33 -04:00
Brandon Thomas c60819e995 test case 2025-10-28 16:54:56 -04:00
Brandon Thomas 2f39ebd192 test 2 2025-10-28 16:53:16 -04:00
Brandon Thomas 7197e4eb23 test cases 2025-10-28 16:51:37 -04:00
Brandon Thomas f63aab5d3f one test case works 2025-10-28 15:39:36 -04:00
Brandon Thomas d71eea3ad3 simulate style wip 2025-10-28 15:12:26 -04:00
Brandon Thomas b85dd82c92 wip 2025-10-28 15:02:33 -04:00
Brandon Thomas 4e001bc89d cleanup 2025-10-28 14:59:01 -04:00
Brandon Thomas e4a748f664 unit test xa 2025-10-28 14:56:25 -04:00
Brandon Thomas 213c1e9283 xa clean 2025-10-28 14:36:35 -04:00
Brandon Thomas 4acdc0f9f6 xs c 2025-10-28 14:18:10 -04:00
Brandon Thomas fb8422ce5f xs idx 2025-10-28 14:09:40 -04:00
Brandon Thomas f294f71fdb algo match so far 2025-10-28 06:23:42 -04:00
Brandon Thomas 722c031a09 refactor / cleanup for legibility 2025-10-28 04:23:52 -04:00
Brandon Thomas 6bac6aaab8 baggage 2025-10-28 04:16:10 -04:00
Brandon Thomas 10428cd182 path and data 2025-10-28 03:37:30 -04:00
Brandon Thomas 4453b1a774 svg paths 2025-10-28 03:17:18 -04:00
Brandon Thomas 59ce976c31 numbers parsing 2025-10-28 02:55:55 -04:00
Brandon Thomas dadf17ebcc rename 2025-10-28 01:18:22 -04:00
Brandon Thomas ad0c2d0cfb actions and xsid 2025-10-28 01:12:55 -04:00
Brandon Thomas bbc3e78378 actions capture works 2025-10-27 20:54:55 -04:00
Brandon Thomas c57cad23df get index page and scripts 2025-10-27 20:09:14 -04:00
Brandon Thomas 68e2c8e67c fix fetching 2025-10-27 19:56:27 -04:00
Brandon Thomas 3e903972b2 script fetching wip 2025-10-27 19:38:32 -04:00
Brandon Thomas 3af8ddd602 parse scripts 2025-10-27 18:40:11 -04:00
Brandon Thomas 29b2acdaf8 anim 2025-10-27 18:20:10 -04:00
Brandon Thomas d821a2d6de use of parsing 2025-10-27 17:46:55 -04:00
Brandon Thomas 91fdbe2a83 working captures 2025-10-27 17:20:04 -04:00
Brandon Thomas df2c2b781f requests, a few x crypto libraries from github 2025-10-24 10:48:01 -04:00
Brandon Thomas c0450f1aaa video gen shut down due to anti bot rules 2025-10-22 13:11:58 -04:00
Brandon Thomas 45c91b03a9 working media post 2025-10-22 13:00:28 -04:00
Brandon Thomas ac6377ec21 video urls 2025-10-22 12:29:11 -04:00
Brandon Thomas b0c68b7f5d url generation 2025-10-22 12:26:08 -04:00
Brandon Thomas 9f600a4a85 file upload works 2025-10-22 00:41:50 -04:00
Brandon Thomas a85a5693f9 upload file wip 2025-10-22 00:23:34 -04:00
Brandon Thomas 515f2b9c28 more requests 2025-10-21 20:21:06 -04:00
Brandon Thomas 0b4a5e938c more requests 2025-10-21 20:02:34 -04:00
Brandon Thomas 2d44d667af requests 2025-10-21 19:54:43 -04:00
Brandon Thomas 6854243fd4 request metadata 2025-10-21 19:38:33 -04:00
Brandon Thomas b4dafa5926 messages were floats 2025-10-21 19:14:14 -04:00
Brandon Thomas c8152ce364 test 2025-10-21 18:58:57 -04:00
Brandon Thomas e7575e2c57 why does pretty formatting matter 2025-10-21 18:55:53 -04:00
Brandon Thomas f7246daf9e wire message is unknown - why?? 2025-10-21 18:51:11 -04:00
Brandon Thomas 658328a0b1 not pulling correct values over the wire 2025-10-21 18:46:11 -04:00
Brandon Thomas 75df3cc64c test cases 2025-10-21 17:46:19 -04:00
Brandon Thomas f38f3f9000 wip 2025-10-21 03:04:58 -04:00
Brandon Thomas c949cd18d5 more fields 2025-10-21 02:11:07 -04:00
Brandon Thomas d89dd1431c image data 2025-10-21 02:07:53 -04:00
Brandon Thomas 5a0726e472 json tagging 2025-10-21 02:02:41 -04:00
Brandon Thomas 05c6108e88 refactor 2025-10-21 01:40:41 -04:00
Brandon Thomas fa68dfced8 this does it! 2025-10-21 00:50:53 -04:00
Brandon Thomas 47041e6bd2 wip trying to poll with timeout 2025-10-21 00:38:44 -04:00
Brandon Thomas a9ce5ff2f6 messages 2025-10-20 23:46:03 -04:00
Brandon Thomas d5f102deac prompting wip 2025-10-20 23:40:47 -04:00
Brandon Thomas de24b2115a prompt images wip 2025-10-20 23:30:05 -04:00
Brandon Thomas 6fa4c9f2ec also handle 401 2025-10-20 17:33:00 -04:00
Brandon Thomas 93d2f203a8 cleanup 2025-10-20 17:27:15 -04:00
Brandon Thomas 290edce1a3 cleanup, new lib 2025-10-20 17:02:06 -04:00
Brandon Thomas b578dc81df IT WORKS! 2025-10-20 01:08:21 -04:00
Brandon Thomas 93f5181252 wip 2025-10-20 00:58:31 -04:00
Brandon Thomas 6721a0ed2b cloudflare recognizes lower case 1.1 headers 2025-10-19 02:10:27 -04:00
Brandon Thomas ac8d61fbee it knows this is fake - why? 2025-10-19 02:09:00 -04:00
Brandon Thomas 7283adf320 wip 2025-10-19 02:05:08 -04:00
Brandon Thomas 87c5a463b1 need to adjust cookies and header casing 2025-10-19 01:44:37 -04:00
Brandon Thomas bce628bdac wip 2025-10-19 01:34:02 -04:00
Brandon Thomas 8c0358009a still not working wtf 2025-10-18 23:21:55 -04:00
Brandon Thomas d3c2a0da47 wip 2025-10-18 23:15:41 -04:00
Brandon Thomas 6f800fd92b wip 2025-10-18 23:04:17 -04:00
Brandon Thomas 2f06aff623 wip 2025-10-18 22:23:19 -04:00
Brandon Thomas d258ea4081 wip 2025-10-18 22:18:34 -04:00
Brandon Thomas 958015d33f mitm proxy wip 2025-10-18 22:15:12 -04:00
Brandon Thomas 0c74568080 details on wreq 2025-10-18 21:58:38 -04:00
Brandon Thomas be1e1d78b0 mitm proxy details 2025-10-18 21:48:22 -04:00
Brandon Thomas feda3314c3 notes 2025-10-18 00:31:44 -04:00
Brandon Thomas 820ab35527 wreq ff 143 not much different than 139 2025-10-18 00:08:26 -04:00
Brandon Thomas c13a7bc52a testing 2025-10-17 23:57:35 -04:00
Brandon Thomas 253e5e4287 wip 2025-10-17 23:50:03 -04:00
Brandon Thomas 79f5bfdf26 browser fingerprints 2025-10-17 23:13:18 -04:00
Brandon Thomas c4b1e85f54 cleanup 2025-10-17 15:19:36 -04:00
Brandon Thomas 2f62e1fd3b wip 2025-10-17 15:18:54 -04:00
Brandon Thomas a5962d0ceb wip 2025-10-17 15:14:29 -04:00
Brandon Thomas 937d0848d1 note 2025-10-17 14:57:24 -04:00
Brandon Thomas 8f7ef3d942 curl-impersonate now works (in current form) 2025-10-17 14:55:31 -04:00
Brandon Thomas 4d22be15b1 current test script 2025-10-17 14:45:48 -04:00
Brandon Thomas 8395b946f8 notes 2025-10-17 14:17:33 -04:00
Brandon Thomas 8678017156 still wip 2025-10-17 12:57:40 -04:00
Brandon Thomas b8409607fd still not working 2025-10-17 12:11:05 -04:00
Brandon Thomas 27680b6760 logging util 2025-10-17 11:41:08 -04:00
Brandon Thomas a06b35ae74 logger things not helping 2025-10-17 11:37:16 -04:00
Brandon Thomas 77c01d6170 headers 2025-10-17 10:45:59 -04:00
Brandon Thomas a3a3425bbc notes 2025-10-17 10:41:48 -04:00
Brandon Thomas 87d8304cc8 still broken 2025-10-17 10:13:01 -04:00
Brandon Thomas 82952e240e wip 2025-10-17 10:09:59 -04:00
Brandon Thomas d4473b852c something is wrong with websocket 2025-10-17 03:41:45 -04:00
Brandon Thomas d0fc1b47d6 account block 2025-10-17 03:15:54 -04:00
Brandon Thomas cbe2a2a6d5 ugh, user-agent header was scuffed 2025-10-17 03:15:37 -04:00
Brandon Thomas 89286ba459 tauri window wip 2025-10-17 02:26:01 -04:00
Brandon Thomas 512059c0f5 wip 2025-10-17 01:49:34 -04:00
Brandon Thomas 6af238c48e wip 2025-10-17 01:06:33 -04:00
Brandon Thomas 201dcb0192 wreq websocket wip 2025-10-17 00:50:29 -04:00
Brandon Thomas b867df290a grok wip 2025-10-17 00:12:32 -04:00
492 changed files with 6933 additions and 27587 deletions
-1
View File
@@ -1,7 +1,6 @@
SERVER_ENVIRONMENT=development
DATABASE_URL=mysql://storyteller:password@localhost/storyteller
#DATABASE_URL=sqlite:/tmp/tasks.sqlite
#DATABASE_URL=mysql://root:root@localhost/storyteller
#DATABASE_URL=mysql://root@localhost/storyteller
@@ -1,12 +0,0 @@
{
"db_name": "SQLite",
"query": "\n UPDATE tasks\n SET\n task_status = ?,\n on_complete_batch_token = ?,\n on_complete_primary_media_file_token = ?,\n on_complete_primary_media_file_class = ?,\n on_complete_primary_media_file_cdn_url = ?,\n on_complete_primary_media_file_thumbnail_url_template = ?\n WHERE\n provider = ?\n AND provider_job_id = ?\n ",
"describe": {
"columns": [],
"parameters": {
"Right": 8
},
"nullable": []
},
"hash": "1e3758851fc8451670f86a9432009b73bdb81de4a08dcaa96a86afb5f9be7a68"
}
@@ -1,68 +0,0 @@
{
"db_name": "SQLite",
"query": "\n SELECT\n id,\n task_status,\n task_type,\n model_type,\n provider,\n provider_job_id,\n frontend_caller,\n frontend_subscriber_id,\n frontend_subscriber_payload\n FROM tasks\n WHERE\n provider = ?\n AND provider_job_id = ?\n ",
"describe": {
"columns": [
{
"name": "id",
"ordinal": 0,
"type_info": "Text"
},
{
"name": "task_status",
"ordinal": 1,
"type_info": "Text"
},
{
"name": "task_type",
"ordinal": 2,
"type_info": "Text"
},
{
"name": "model_type",
"ordinal": 3,
"type_info": "Text"
},
{
"name": "provider",
"ordinal": 4,
"type_info": "Text"
},
{
"name": "provider_job_id",
"ordinal": 5,
"type_info": "Text"
},
{
"name": "frontend_caller",
"ordinal": 6,
"type_info": "Text"
},
{
"name": "frontend_subscriber_id",
"ordinal": 7,
"type_info": "Text"
},
{
"name": "frontend_subscriber_payload",
"ordinal": 8,
"type_info": "Text"
}
],
"parameters": {
"Right": 2
},
"nullable": [
false,
false,
false,
true,
true,
true,
true,
true,
true
]
},
"hash": "38ff2df237b16f077865711dd81261de1b5fa05a9787679ce9ccd4ac576b1069"
}
Generated
+69 -91
View File
@@ -662,7 +662,6 @@ dependencies = [
"errors",
"filesys",
"futures 0.3.31",
"grok_client",
"idempotency",
"image",
"images",
@@ -916,7 +915,31 @@ dependencies = [
"hmac 0.12.1",
"http-types",
"hyper 0.14.28",
"hyper-rustls 0.24.2",
"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",
"serde",
"serde_json",
"serde_path_to_error",
@@ -939,7 +962,7 @@ dependencies = [
"bytes 1.10.1",
"http-body-util",
"hyper 1.6.0",
"hyper-rustls 0.27.3",
"hyper-tls 0.6.0",
"hyper-util",
"miniserde",
"thiserror 2.0.12",
@@ -1124,11 +1147,10 @@ checksum = "9a13149d0cf3f7f9b9261fad4ec63b2efbf9a80665f52def86282d26255e6331"
dependencies = [
"http 1.3.1",
"log",
"rustls 0.22.4",
"native-tls",
"serde",
"serde_json",
"url",
"webpki-roots 0.26.2",
]
[[package]]
@@ -1671,17 +1693,6 @@ dependencies = [
"alloc-stdlib",
]
[[package]]
name = "bstr"
version = "1.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "63044e1ae8e69f3b5a92c736ca6269b8d12fa7efe39bf34ddb06d102cf0e2cab"
dependencies = [
"memchr",
"regex-automata",
"serde",
]
[[package]]
name = "bucket_paths"
version = "0.0.1"
@@ -4704,7 +4715,6 @@ version = "0.0.1"
dependencies = [
"anyhow",
"base64 0.22.1",
"bstr",
"byteorder",
"chrono",
"cloudflare_errors",
@@ -5239,22 +5249,6 @@ 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"
@@ -5265,12 +5259,11 @@ dependencies = [
"http 1.3.1",
"hyper 1.6.0",
"hyper-util",
"log",
"rustls 0.23.13",
"rustls-native-certs 0.8.0",
"rustls-native-certs",
"rustls-pki-types",
"tokio",
"tokio-rustls 0.26.0",
"tokio-rustls",
"tower-service",
"webpki-roots 0.26.2",
]
@@ -5287,6 +5280,19 @@ 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"
@@ -6114,15 +6120,13 @@ 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]]
@@ -9086,15 +9090,15 @@ dependencies = [
"http 0.2.11",
"http-body 0.4.6",
"hyper 0.14.28",
"hyper-rustls 0.24.2",
"hyper-tls 0.5.0",
"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",
@@ -9102,14 +9106,13 @@ dependencies = [
"sync_wrapper 0.1.2",
"system-configuration 0.5.1",
"tokio",
"tokio-rustls 0.24.1",
"tokio-native-tls",
"tokio-util",
"tower-service",
"url",
"wasm-bindgen",
"wasm-bindgen-futures",
"web-sys",
"webpki-roots 0.25.3",
"winreg 0.50.0",
]
@@ -9133,8 +9136,8 @@ dependencies = [
"http-body 1.0.1",
"http-body-util",
"hyper 1.6.0",
"hyper-rustls 0.27.3",
"hyper-tls",
"hyper-rustls",
"hyper-tls 0.6.0",
"hyper-util",
"js-sys",
"log",
@@ -9145,7 +9148,7 @@ dependencies = [
"pin-project-lite",
"quinn",
"rustls 0.23.13",
"rustls-native-certs 0.8.0",
"rustls-native-certs",
"rustls-pki-types",
"serde",
"serde_json",
@@ -9153,7 +9156,7 @@ dependencies = [
"sync_wrapper 1.0.2",
"tokio",
"tokio-native-tls",
"tokio-rustls 0.26.0",
"tokio-rustls",
"tokio-util",
"tower 0.5.2",
"tower-http",
@@ -9174,7 +9177,6 @@ dependencies = [
"serde",
"serde_derive",
"sqlx",
"sqlx-core",
"toml 0.8.22",
"workspace-build-acceleration",
]
@@ -9482,33 +9484,17 @@ 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",
@@ -9517,18 +9503,6 @@ 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"
@@ -10621,6 +10595,7 @@ dependencies = [
"indexmap 2.9.0",
"log",
"memchr",
"native-tls",
"once_cell",
"paste",
"percent-encoding",
@@ -10837,6 +10812,7 @@ dependencies = [
"anyhow",
"artcraft_api_defs",
"async-stripe 0.26.0",
"async-stripe 0.41.0",
"async-trait",
"base64 0.22.1",
"billing_artcraft_component",
@@ -12103,16 +12079,6 @@ 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"
@@ -12191,9 +12157,9 @@ dependencies = [
[[package]]
name = "tokio-util"
version = "0.7.16"
version = "0.7.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "14307c986784f72ef81c89db7d9e28d6ac26d16213b109ea501696195e6e3ce5"
checksum = "61e7c3654c13bcd040d4a03abee2c75b1d14a37b423cf5a813ceae1cc903ec6a"
dependencies = [
"bytes 1.10.1",
"futures-core",
@@ -14095,6 +14061,11 @@ 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",
@@ -14104,12 +14075,15 @@ 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",
@@ -14122,6 +14096,7 @@ dependencies = [
"regex",
"regex-automata",
"regex-syntax",
"reqwest 0.12.23",
"rsa",
"rustls-pki-types",
"schemars",
@@ -14135,6 +14110,10 @@ 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",
@@ -14200,7 +14179,6 @@ dependencies = [
"tokio",
"tokio-boring2",
"tokio-tungstenite",
"tokio-util",
"tower 0.5.2",
"tower-http",
"want",
@@ -14210,9 +14188,9 @@ dependencies = [
[[package]]
name = "wreq-util"
version = "3.0.0-rc.6"
version = "3.0.0-rc.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "05af16fb18a8b955ce166747c588ff64a39d49266e486211116dc5c38a73c13d"
checksum = "9a38c2ade0a913ec9e1f7f394de8ebdb78d7123e45f015f9d02fed5bdc9db67b"
dependencies = [
"typed-builder",
"wreq",
+3 -5
View File
@@ -198,7 +198,6 @@ errors = { path = "crates/lib/errors" }
fal_client = { path = "crates/api_clients/fal_client" }
file_to_base64_url = { path = "crates/lib/files/file_to_base64_url" }
filesys = { path = "crates/lib/files/filesys" }
grok_client = { path = "crates/api_clients/grok_client" }
idempotency = { path = "crates/lib/idempotency" }
images = { path = "crates/lib/files/images" }
jwt_light = { path = "crates/lib/jwt_light" }
@@ -233,7 +232,6 @@ actix-web-actors = "4.3.1+deprecated"
actix-web-lab = "0.20.2"
anyhow = "1.0.98"
base64 = "0.22.1"
bstr = "1.12.1"
bytes = "1.10.1"
chrono = { version = "0.4.41", features = ["serde"] }
cookie = "0.18.1"
@@ -246,7 +244,7 @@ log = "0.4.27"
once_cell = "1.21.3"
rand = "0.9.1"
regex = "1.12.2"
reqwest = { version = "0.12.23", default-features = false, features = [ "charset", "http2", "system-proxy", "json", "cookies", "rustls-tls" ] }
reqwest = { version = "0.12.23", features = ["json", "cookies"] }
reqwest-websocket = { version = "0.5.0" }
serde = { version = "1.0.219", features = ["derive"] }
serde_derive = "1.0.219"
@@ -277,5 +275,5 @@ uuid = "1.18.1"
# choco install pkgconfiglite -y
# choco install llvm -y
# choco install nasm -y
wreq = { version = "6.0.0-rc.20", features = ["json", "multipart", "cookies", "ws", "stream"] }
wreq-util = "3.0.0-rc.6"
wreq = { version = "6.0.0-rc.20", features = ["json", "multipart", "cookies", "ws"] }
wreq-util = "3.0.0-rc.5"
-1
View File
@@ -152,7 +152,6 @@ CREATE TABLE media_files (
-- * `vmd`
-- * `pmx`
-- * `csv`
-- * `spz` Gaussian splats
media_type VARCHAR(16) NOT NULL,
-- Broad class of media:
@@ -5,5 +5,4 @@ pub mod generate_flux_pro_11_text_to_image;
pub mod generate_flux_pro_11_ultra_text_to_image;
pub mod generate_gpt_image_1_text_to_image;
pub mod inpaint;
pub mod multi_function;
pub mod remove_image_background;
@@ -1 +0,0 @@
pub mod nano_banana_pro_multi_function_image_gen;
@@ -1,71 +0,0 @@
use serde_derive::{Deserialize, Serialize};
use tokens::tokens::generic_inference_jobs::InferenceJobToken;
use tokens::tokens::media_files::MediaFileToken;
use utoipa::ToSchema;
pub const NANO_BANANA_PRO_MULTI_FUNCTION_IMAGE_GEN_PATH: &str = "/v1/generate/image/multi_function/nano_banana_pro";
#[derive(Serialize, Deserialize, ToSchema)]
pub struct NanoBananaProMultiFunctionImageGenRequest {
/// Idempotency token to prevent duplicate requests.
pub uuid_idempotency_token: String,
/// Text prompt to generate the image from.
pub prompt: Option<String>,
/// Image media tokens to include in the editing context.
/// If present, we're doing image editing (image-to-image / image-editing)
/// If absent, we're doing image generation (text-to-image)
pub image_media_tokens: Option<Vec<MediaFileToken>>,
/// Number of images to generate. Default is one.
pub num_images: Option<NanoBananaProMultiFunctionImageGenNumImages>,
/// Resolution of the image to generate. Default is OneK (1K).
pub resolution: Option<NanoBananaProMultiFunctionImageGenImageResolution>,
/// Aspect ratio of the images to generate. Default is "1:1"
pub aspect_ratio: Option<NanoBananaProMultiFunctionImageGenAspectRatio>,
}
#[derive(Copy, Clone, Debug, Serialize, Deserialize, ToSchema)]
#[serde(rename_all = "snake_case")]
pub enum NanoBananaProMultiFunctionImageGenNumImages {
One, // Default
Two,
Three,
Four,
}
#[derive(Copy, Clone, Debug, Serialize, Deserialize, ToSchema)]
#[serde(rename_all = "snake_case")]
pub enum NanoBananaProMultiFunctionImageGenImageResolution {
OneK,
TwoK,
FourK,
}
#[derive(Copy, Clone, Debug, Serialize, Deserialize, ToSchema)]
#[serde(rename_all = "snake_case")]
pub enum NanoBananaProMultiFunctionImageGenAspectRatio {
// Square
OneByOne, // Default
// Wide
FiveByFour,
FourByThree,
ThreeByTwo,
SixteenByNine,
TwentyOneByNine,
// Tall
FourByFive,
ThreeByFour,
TwoByThree,
NineBySixteen, // NB: No NineByTwentyOne ?
}
#[derive(Serialize, Deserialize, ToSchema)]
pub struct NanoBananaProMultiFunctionImageGenResponse {
pub success: bool,
pub inference_job_token: InferenceJobToken,
}
@@ -5,5 +5,4 @@ pub mod generate_flux_pro_11_text_to_image;
pub mod generate_flux_pro_11_ultra_text_to_image;
pub mod generate_gpt_image_1_text_to_image;
pub mod inpaint;
pub mod multi_function;
pub mod remove_image_background;
@@ -1 +0,0 @@
pub mod nano_banana_pro_multi_function_image_gen_image;
@@ -1,18 +0,0 @@
use crate::credentials::storyteller_credential_set::StorytellerCredentialSet;
use crate::error::storyteller_error::StorytellerError;
use crate::utils::api_host::ApiHost;
use crate::utils::basic_json_post_request::basic_json_post_request;
use artcraft_api_defs::generate::image::multi_function::nano_banana_pro_multi_function_image_gen::{NanoBananaProMultiFunctionImageGenRequest, NanoBananaProMultiFunctionImageGenResponse, NANO_BANANA_PRO_MULTI_FUNCTION_IMAGE_GEN_PATH};
pub async fn nano_banana_pro_multi_function_image_gen(
api_host: &ApiHost,
maybe_creds: Option<&StorytellerCredentialSet>,
request: NanoBananaProMultiFunctionImageGenRequest,
) -> Result<NanoBananaProMultiFunctionImageGenResponse, StorytellerError> {
Ok(basic_json_post_request(
api_host,
NANO_BANANA_PRO_MULTI_FUNCTION_IMAGE_GEN_PATH,
maybe_creds,
request,
).await?)
}
+1 -1
View File
@@ -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", default-features = false, features = ["fail-on-err", "tags", "tokio-rustls-tls"] }
rust-s3 = { version = "0.36.0-beta.2" }
# Runtime
tokio.workspace = true
@@ -1,143 +0,0 @@
use crate::creds::fal_api_key::FalApiKey;
use crate::error::classify_fal_error::classify_fal_error;
use crate::error::fal_error_plus::FalErrorPlus;
use fal::endpoints::fal_ai::bytedance::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(())
}
}
@@ -1,142 +0,0 @@
use crate::creds::fal_api_key::FalApiKey;
use crate::error::classify_fal_error::classify_fal_error;
use crate::error::fal_error_plus::FalErrorPlus;
use fal::endpoints::fal_ai::bytedance::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(())
}
}
@@ -1,147 +0,0 @@
use crate::creds::fal_api_key::FalApiKey;
use crate::error::classify_fal_error::classify_fal_error;
use crate::error::fal_error_plus::FalErrorPlus;
use fal::endpoints::fal_ai::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,8 +1,5 @@
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,4 +6,3 @@ 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;
@@ -1,137 +0,0 @@
use crate::creds::fal_api_key::FalApiKey;
use crate::error::classify_fal_error::classify_fal_error;
use crate::error::fal_error_plus::FalErrorPlus;
use fal::endpoints::fal_ai::bytedance::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(())
}
}
@@ -1,134 +0,0 @@
use crate::creds::fal_api_key::FalApiKey;
use crate::error::classify_fal_error::classify_fal_error;
use crate::error::fal_error_plus::FalErrorPlus;
use fal::endpoints::fal_ai::bytedance::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(())
}
}
@@ -1,139 +0,0 @@
use crate::creds::fal_api_key::FalApiKey;
use crate::error::classify_fal_error::classify_fal_error;
use crate::error::fal_error_plus::FalErrorPlus;
use fal::endpoints::fal_ai::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(())
}
}
@@ -1,3 +0,0 @@
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;
@@ -1,70 +0,0 @@
use crate::creds::fal_api_key::FalApiKey;
use crate::error::classify_fal_error::classify_fal_error;
use crate::error::fal_error_plus::FalErrorPlus;
use fal::endpoints::fal_ai::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(())
}
}
@@ -1,70 +0,0 @@
use crate::creds::fal_api_key::FalApiKey;
use crate::error::classify_fal_error::classify_fal_error;
use crate::error::fal_error_plus::FalErrorPlus;
use fal::endpoints::fal_ai::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(())
}
}
@@ -1,90 +0,0 @@
use crate::creds::fal_api_key::FalApiKey;
use crate::error::classify_fal_error::classify_fal_error;
use crate::error::fal_error_plus::FalErrorPlus;
use fal::endpoints::fal_ai::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(())
}
}
@@ -1,86 +0,0 @@
use crate::creds::fal_api_key::FalApiKey;
use crate::error::classify_fal_error::classify_fal_error;
use crate::error::fal_error_plus::FalErrorPlus;
use fal::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(())
}
}
@@ -1,87 +0,0 @@
use crate::creds::fal_api_key::FalApiKey;
use crate::error::classify_fal_error::classify_fal_error;
use crate::error::fal_error_plus::FalErrorPlus;
use fal::endpoints::fal_ai::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(())
}
}
@@ -1,152 +0,0 @@
use crate::creds::fal_api_key::FalApiKey;
use crate::error::classify_fal_error::classify_fal_error;
use crate::error::fal_error_plus::FalErrorPlus;
use fal::endpoints::fal_ai::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(())
}
}
@@ -1,118 +0,0 @@
use crate::creds::fal_api_key::FalApiKey;
use crate::error::classify_fal_error::classify_fal_error;
use crate::error::fal_error_plus::FalErrorPlus;
use fal::endpoints::fal_ai::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(())
}
}
@@ -1,120 +0,0 @@
use crate::creds::fal_api_key::FalApiKey;
use crate::error::classify_fal_error::classify_fal_error;
use crate::error::fal_error_plus::FalErrorPlus;
use fal::endpoints::fal_ai::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(())
}
}
@@ -1,129 +0,0 @@
use crate::creds::fal_api_key::FalApiKey;
use crate::error::classify_fal_error::classify_fal_error;
use crate::error::fal_error_plus::FalErrorPlus;
use fal::endpoints::fal_ai::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(())
}
}
@@ -1,124 +0,0 @@
use crate::creds::fal_api_key::FalApiKey;
use crate::error::classify_fal_error::classify_fal_error;
use crate::error::fal_error_plus::FalErrorPlus;
use fal::endpoints::fal_ai::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(())
}
}
@@ -1,129 +0,0 @@
use crate::creds::fal_api_key::FalApiKey;
use crate::error::classify_fal_error::classify_fal_error;
use crate::error::fal_error_plus::FalErrorPlus;
use fal::endpoints::fal_ai::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(())
}
}
@@ -1,124 +0,0 @@
use crate::creds::fal_api_key::FalApiKey;
use crate::error::classify_fal_error::classify_fal_error;
use crate::error::fal_error_plus::FalErrorPlus;
use fal::endpoints::fal_ai::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(())
}
}
@@ -1,12 +0,0 @@
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,6 +5,4 @@ pub mod enqueue_seedance_1_lite_image_to_video_webhook;
pub mod enqueue_seedance_1_pro_image_to_video_webhook;
pub mod enqueue_veo_2_image_to_video_webhook;
pub mod enqueue_veo_3_fast_image_to_video_webhook;
pub mod enqueue_veo_3_image_to_video_webhook;
pub mod image;
pub mod text;
pub mod enqueue_veo_3_image_to_video_webhook;
@@ -1,66 +0,0 @@
use crate::creds::fal_api_key::FalApiKey;
use crate::error::classify_fal_error::classify_fal_error;
use crate::error::fal_error_plus::FalErrorPlus;
use fal::endpoints::fal_ai::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(())
}
}
@@ -1,100 +0,0 @@
use crate::creds::fal_api_key::FalApiKey;
use crate::error::classify_fal_error::classify_fal_error;
use crate::error::fal_error_plus::FalErrorPlus;
use fal::endpoints::fal_ai::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(())
}
}
@@ -1,103 +0,0 @@
use crate::creds::fal_api_key::FalApiKey;
use crate::error::classify_fal_error::classify_fal_error;
use crate::error::fal_error_plus::FalErrorPlus;
use fal::endpoints::fal_ai::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(())
}
}
@@ -1,148 +0,0 @@
use crate::creds::fal_api_key::FalApiKey;
use crate::error::classify_fal_error::classify_fal_error;
use crate::error::fal_error_plus::FalErrorPlus;
use fal::endpoints::fal_ai::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(())
}
}
@@ -1,116 +0,0 @@
use crate::creds::fal_api_key::FalApiKey;
use crate::error::classify_fal_error::classify_fal_error;
use crate::error::fal_error_plus::FalErrorPlus;
use fal::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(())
}
}
@@ -1,114 +0,0 @@
use crate::creds::fal_api_key::FalApiKey;
use crate::error::classify_fal_error::classify_fal_error;
use crate::error::fal_error_plus::FalErrorPlus;
use fal::endpoints::fal_ai::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(())
}
}
@@ -1,130 +0,0 @@
use crate::creds::fal_api_key::FalApiKey;
use crate::error::classify_fal_error::classify_fal_error;
use crate::error::fal_error_plus::FalErrorPlus;
use fal::endpoints::fal_ai::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(())
}
}
@@ -1,130 +0,0 @@
use crate::creds::fal_api_key::FalApiKey;
use crate::error::classify_fal_error::classify_fal_error;
use crate::error::fal_error_plus::FalErrorPlus;
use fal::endpoints::fal_ai::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(())
}
}
@@ -1,8 +0,0 @@
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;
@@ -24,7 +24,6 @@ errors.workspace = true
# External
anyhow.workspace = true
base64.workspace = true
bstr.workspace = true
byteorder = "1.5.0"
chrono.workspace = true
futures.workspace = true
@@ -1,6 +1,5 @@
use crate::credentials::grok_client_secrets::GrokClientSecrets;
use crate::credentials::grok_cookies::GrokCookies;
use crate::datatypes::api::user_id::UserId;
/// Includes the cookies and all the magic other bits needed to call the API.
#[derive(Clone)]
@@ -23,8 +22,4 @@ impl GrokFullCredentials {
client_secrets,
}
}
pub fn get_user_id_ref(&self) -> &UserId {
&self.client_secrets.user_id
}
}
@@ -1,22 +0,0 @@
use crate::datatypes::api::user_email::UserEmail;
use crate::datatypes::api::user_id::UserId;
#[derive(Clone)]
pub struct GrokUserData {
/// From index.html
/// Not strictly needed to sign requests, but typically needed to generate URLs.
pub user_id: UserId,
/// From index.html
/// Not needed, but returned alongside other details.
pub user_email: Option<UserEmail>,
}
impl GrokUserData {
pub fn from_id_and_email(id: String, email: Option<String>) -> Self {
Self {
user_id: UserId(id),
user_email: email.map(|email| UserEmail(email)),
}
}
}
@@ -1,4 +1,3 @@
pub mod grok_client_secrets;
pub mod grok_cookies;
pub mod grok_full_credentials;
pub mod grok_user_data;
pub mod grok_client_secrets;
@@ -1,13 +0,0 @@
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,8 +1,5 @@
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;
pub mod svg_path_data;
@@ -1,12 +0,0 @@
/// Type for Post IDs.
/// Post IDs are UUIds.
/// These are often shared with media/image uploads' `FileId`s, but seldom video generation `FileId`s
#[derive(Clone, Debug)]
pub struct PostId(pub String);
impl PostId {
pub fn to_string(&self) -> String {
self.0.clone()
}
}
@@ -5,9 +5,3 @@
/// Request IDs are UUIds.
#[derive(Clone, Debug)]
pub struct RequestId(pub String);
impl RequestId {
pub fn to_string(&self) -> String {
self.0.clone()
}
}
@@ -1,9 +1,3 @@
/// Type for User Emails
#[derive(Clone, Debug)]
pub struct UserEmail(pub String);
impl UserEmail {
pub fn to_string(&self) -> String {
self.0.clone()
}
}
@@ -3,10 +3,3 @@
/// User IDs are UUIDs.
#[derive(Clone, Debug)]
pub struct UserId(pub String);
impl UserId {
pub fn to_string(&self) -> String {
self.0.clone()
}
}
@@ -1,27 +0,0 @@
/// 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",
}
}
}
@@ -1 +0,0 @@
{"error":{"code":7,"message":"Request rejected by anti-bot rules.","details":[]}}
@@ -1,37 +0,0 @@
use crate::error::grok_error::GrokError;
use crate::error::grok_generic_api_error::GrokGenericApiError;
use crate::error::grok_specific_api_error::GrokSpecificApiError;
use cloudflare_errors::filter_cloudflare_errors::filter_cloudflare_errors;
use wreq::StatusCode;
/// We know the response is an error at this point.
/// We're just turning it into the right error.
pub fn categorize_grok_http_error(status_code: StatusCode, maybe_body: Option<&str>) -> GrokError {
if let Some(body) = maybe_body {
if let Err(err) = filter_cloudflare_errors(status_code.as_u16(), body) {
return GrokGenericApiError::CloudflareError(err).into();
}
let body = body.to_lowercase();
let anti_bot = body.contains("anti-bot") || body.contains("rejected");
if anti_bot {
return GrokSpecificApiError::AutomationBlocked.into();
}
match status_code {
StatusCode::TOO_MANY_REQUESTS => {
return GrokSpecificApiError::TooManyVideos.into();
}
_ => {},
}
return GrokGenericApiError::UncategorizedBadResponseWithStatusAndBody {
status_code,
body: body.to_string(),
}.into();
}
GrokGenericApiError::UncategorizedBadResponseWithStatus(status_code).into()
}
@@ -26,12 +26,6 @@ pub enum GrokClientError {
/// Can't read a local file for uploading.
CannotReadLocalFileForUpload(std::io::Error),
/// Couldn't write to the file
CannotOpenLocalFileForWriting(std::io::Error),
/// Couldn't write to the file
CannotWriteLocalFile(std::io::Error),
/// File for upload has an invalid path.
FileForUploadHasInvalidPath,
@@ -47,30 +41,12 @@ 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,
/// Something is broken with timeout math
TimeoutMathBroken,
/// 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),
//
@@ -120,21 +96,13 @@ impl Display for GrokClientError {
Self::WebsocketSendError(err) => write!(f, "Websocket send error: {}", err),
Self::CannotOpenLocalFileForUpload(err) => write!(f, "Cannot open local file for upload: {}", err),
Self::CannotReadLocalFileForUpload(err) => write!(f, "Cannot read local file for upload: {}", err),
Self::CannotOpenLocalFileForWriting(err) => write!(f, "Cannot open local file for writing: {}", err),
Self::CannotWriteLocalFile(err) => write!(f, "Cannot write local file: {}", err),
Self::FileForUploadHasInvalidPath => write!(f, "File for upload has invalid path"),
Self::HtmlParsingError => write!(f, "Html parsing error"),
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."),
@@ -40,9 +40,6 @@ pub enum GrokGenericApiError {
/// Specifically, this is for non-200 (error) responses.
SerdeParseErrorWithBodyOnNon200(serde_json::Error, String),
/// An uncategorized bad HTTP response from Grok.
UncategorizedBadResponseWithStatus(StatusCode),
/// An uncategorized bad HTTP response from Grok.
UncategorizedBadResponseWithStatusAndBody {
status_code: StatusCode,
@@ -72,7 +69,6 @@ impl Display for GrokGenericApiError {
Self::UnexpectedWebsocketUpgradeStatusCode(status) => write!(f, "Unexpected websocket upgrade status code: {}", status),
Self::SerdeResponseParseErrorWithBody(err, body) => write!(f, "Failed to parse response body: {:?}. Body: {}", err, body),
Self::SerdeParseErrorWithBodyOnNon200(err, body) => write!(f, "Failed to parse non-200 response body: {:?}. Body: {}", err, body),
Self::UncategorizedBadResponseWithStatus(status) => write!(f, "Uncategorized with status code: {}", status),
Self::UncategorizedBadResponseWithStatusAndBody { status_code, body } => write!(f, "Uncategorized bad response: status code {}, body: {}", status_code, body),
Self::UploadFailed => write!(f, "Upload failed"),
Self::WreqWebsocketUpgradeError(err) => write!(f, "Websocket upgrade error: {}", err),
@@ -3,12 +3,7 @@ use std::fmt::{Display, Formatter};
#[derive(Debug)]
pub enum GrokSpecificApiError {
/// Grok is detecting and blocking our bot, likely due to new secrets use.
AutomationBlocked,
/// Too many videos are being generated.
TooManyVideos,
TodoError,
// /// Failed to generate a bearer token despite the `200 OK` response.
// /// This has been generated by sending old/expired session cookies or empty cookies.
@@ -60,8 +55,7 @@ impl Error for GrokSpecificApiError {}
impl Display for GrokSpecificApiError {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
Self::AutomationBlocked => write!(f, "Automation blocked"),
Self::TooManyVideos => write!(f, "Too many videos are being generated"),
Self::TodoError => write!(f, "TodoError"),
//Self::FailedToGenerateBearerToken => write!(f, "Failed to generate bearer token. Probably invalid or expired session cookies."),
//Self::InvalidJwt => write!(f, "Invalid JWT token. Please refresh the token."),
@@ -1,5 +1,4 @@
pub mod grok_client_error;
pub mod grok_error;
pub mod grok_generic_api_error;
pub mod grok_specific_api_error;
pub (crate) mod categorize_grok_http_error;
pub mod grok_specific_api_error;
@@ -1,4 +1,2 @@
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;
mod upload_image_and_generate_video;
@@ -1,201 +0,0 @@
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,13 +1,10 @@
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;
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::create_media_post::grok_create_media_post::{GrokCreateMediaPost, MediaPostType};
use crate::requests::like_media::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;
@@ -22,50 +19,17 @@ pub struct UploadImageAndGenerateVideo<'a, P: AsRef<Path>> {
// NB: Must be owned.
pub file: FileUploadSpec<P>,
/// 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.
/// If we set this to false, we'll wait for the video ID and exit
/// the request early, before the video is finished asynchronously
/// generating.
pub wait_for_generation: bool,
pub individual_request_timeout: Option<Duration>,
}
#[derive(Debug, Clone)]
pub struct ImageUploadAndGenerateVideoResult {
/// The ID of the "post"
/// This often is equivalent to the upload id / `image_file_id`,
/// and this is in the user-facing URL for website navigation to the video.
pub post_id: PostId,
/// The ID of the file we uploaded
pub image_file_id: FileId,
/// The ID of the video we're generating, which may or may not exist yet.
pub struct ImageUploadResult {
pub video_file_id: Option<FileId>,
/// The URL for the video, which may or may not exist yet.
pub video_url: Option<String>,
/// Whether the video generation has already completed.
/// Based on request `wait_for_generation`.
pub generation_is_complete: bool,
}
pub async fn upload_image_and_generate_video<P: AsRef<Path>>(args: UploadImageAndGenerateVideo<'_, P>)
-> Result<ImageUploadAndGenerateVideoResult, GrokError>
{
pub async fn upload_image_and_generate_video<P: AsRef<Path>>(args: UploadImageAndGenerateVideo<'_, P>) -> Result<ImageUploadResult, GrokError> {
info!("Uploading file to Grok...");
@@ -80,7 +44,7 @@ pub async fn upload_image_and_generate_video<P: AsRef<Path>>(args: UploadImageAn
info!("File URI: {:?}", upload_result.file_uri);
info!("File Metadata ID: {:?}", upload_result.file_id);
let upload_file_id = match upload_result.file_id {
let upload_file_id = match &upload_result.file_id {
Some(id) => id,
None => {
error!("Previous file upload failed. Cannot continue.");
@@ -99,19 +63,16 @@ pub async fn upload_image_and_generate_video<P: AsRef<Path>>(args: UploadImageAn
let request = GrokCreateMediaPost {
user_id: &args.full_credentials.client_secrets.user_id,
file_id: &upload_file_id,
file_id: upload_file_id,
media_type: MediaPostType::UserUploadedImage,
cookie: args.full_credentials.cookies.as_str(),
request_timeout: args.individual_request_timeout,
};
let post_result = request.send().await?;
let _post_result = request.send().await?;
info!("Posted Image.");
info!("Video prompt: {:?}", args.prompt);
info!("Video aspect ratio: {:?}", args.aspect_ratio);
info!("Generate video...");
let request = GrokVideoGenChatConversationBuilder {
@@ -120,8 +81,6 @@ 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,
@@ -130,20 +89,10 @@ pub async fn upload_image_and_generate_video<P: AsRef<Path>>(args: UploadImageAn
numbers: &args.full_credentials.client_secrets.numbers,
};
let maybe_video_file_id;
let generation_is_complete;
// TODO: Get URL
let video_gen_result = request.send().await?;
if args.wait_for_generation {
info!("Waiting for video generation...");
let video_gen_result = request.wait_for_video().await?;
maybe_video_file_id = video_gen_result.video_file_id;
generation_is_complete = true;
} else {
info!("Streaming response partially...");
let video_gen_result = request.stream_only_video_id().await?;
maybe_video_file_id = video_gen_result.video_file_id;
generation_is_complete = false;
}
let maybe_video_file_id = video_gen_result.video_file_id;
let maybe_video_url = maybe_video_file_id
.as_ref()
@@ -170,12 +119,9 @@ pub async fn upload_image_and_generate_video<P: AsRef<Path>>(args: UploadImageAn
info!("Media Liked");
Ok(ImageUploadAndGenerateVideoResult {
post_id: post_result.post_id,
image_file_id: upload_file_id,
Ok(ImageUploadResult {
video_file_id: maybe_video_file_id,
video_url: maybe_video_url,
generation_is_complete,
})
}
@@ -183,7 +129,6 @@ 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};
@@ -191,6 +136,7 @@ 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") }
@@ -200,40 +146,32 @@ mod tests {
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 runs away from a giant t-rex");
let cookies = get_typed_test_cookies()?;
let secrets = request_client_secrets(RequestClientSecretsArgs {
cookies: &cookies,
}).await?;
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 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";
println!("Verification Token: {:?}", secrets.verification_token);
println!("Sentry Trace: {:?}", secrets.sentry_trace);
println!("Numbers: {:?}", secrets.numbers);
println!("Svg Path: {:?}", secrets.svg_path_data);
println!("Baggage: {:?}", secrets.baggage);
let credentials = GrokFullCredentials::from_cookies_and_client_secrets(cookies, secrets);
let video_result = upload_image_and_generate_video(UploadImageAndGenerateVideo {
full_credentials: &credentials,
file: FileUploadSpec::Path(image_path),
prompt: maybe_prompt,
aspect_ratio: Some(AspectRatio::Square),
mode: None,
prompt: Some(prompt),
individual_request_timeout: None,
wait_for_generation: false,
}).await?;
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);
println!("Video File ID: {:?}", video_result.video_file_id);
println!("Video URL: {:?}", video_result.video_url);
assert_eq!(1, 2);
@@ -1,348 +0,0 @@
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;
pub struct UploadImageAndGenerateVideoWithRetry<'a, P: AsRef<Path>> {
pub credentials: &'a GrokFullCredentials,
// NB: Must be owned.
pub file: FileUploadSpec<P>,
/// 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.
/// If we set this to false, we'll wait for the video ID and exit
/// the request early, before the video is finished asynchronously
/// generating.
pub wait_for_generation: bool,
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(
&current_full_credentials_ref.client_secrets.user_id,
&upload_file_id
);
info!("Uploaded URI: {:?}", url);
info!("Creating media post...");
let request = GrokCreateMediaPost {
user_id: &current_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: &current_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: &current_full_credentials_ref.client_secrets.baggage,
sentry_trace: &current_full_credentials_ref.client_secrets.sentry_trace,
verification_token: &current_full_credentials_ref.client_secrets.verification_token,
svg_data: &current_full_credentials_ref.client_secrets.svg_path_data,
numbers: &current_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: &current_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: &current_full_credentials_ref.client_secrets.baggage,
sentry_trace: &current_full_credentials_ref.client_secrets.sentry_trace,
verification_token: &current_full_credentials_ref.client_secrets.verification_token,
svg_data: &current_full_credentials_ref.client_secrets.svg_path_data,
numbers: &current_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: &current_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(&current_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(())
}
}
@@ -1,12 +1,10 @@
use crate::client::browser_user_agents::FIREFOX_143_MAC_USER_AGENT;
use crate::datatypes::api::file_id::FileId;
use crate::datatypes::api::post_id::PostId;
use crate::datatypes::api::user_id::UserId;
use crate::error::grok_client_error::GrokClientError;
use crate::error::grok_error::GrokError;
use crate::error::grok_generic_api_error::GrokGenericApiError;
use crate::requests::media_posts::create_media_post::request::CreateMediaPostWireRequest;
use crate::requests::media_posts::create_media_post::response::CreateMediaPostRawResponse;
use crate::requests::create_media_post::request::CreateMediaPostWireRequest;
use crate::requests::upload_file::grok_upload_file::{GrokUploadFile, GrokUploadFileResponse};
use crate::utils::user_and_file_id_to_image_url::user_and_file_id_to_image_url;
use log::{error, info};
@@ -34,9 +32,8 @@ pub enum MediaPostType {
}
/// Response type
#[derive(Debug, Clone)]
#[derive(Debug)]
pub struct GrokCreateMediaPostResponse {
pub post_id: PostId,
}
impl <'a> GrokCreateMediaPost<'a> {
@@ -50,17 +47,9 @@ impl <'a> GrokCreateMediaPost<'a> {
// TODO: Headers were from Chromium, not Firefox. Partial implementation.
let mut request_builder = client.post(CREATE_MEDIA_POST_URL)
// TODO: Missing header "baggage"
// TODO: Missing header "sentry-trace"
// TODO: Missing header "traceparent"
// TODO: Missing header "traceparent"
// TODO: Missing header "x-statsig-id"
// TODO: Missing header "x-xai-request-id"
//.header("sec-ch-ua", "") // NB: Not present in firefox
//.header("sec-ch-ua-mobile", "") // NB: Not present in firefox
//.header("sec-ch-ua-platform", "") // NB: Not present in firefox
.header(ACCEPT, "*/*")
.header(ACCEPT_LANGUAGE, "en-US,en;q=0.5")
// TODO: Missing header "baggage"
.header(CACHE_CONTROL, "no-cache")
.header(CONTENT_TYPE, "application/json")
.header(COOKIE, self.cookie.to_string())
@@ -68,9 +57,17 @@ impl <'a> GrokCreateMediaPost<'a> {
.header(PRAGMA, "no-cache")
.header("priority", "u=1, i")
.header(REFERER, "https://grok.com/imagine/favorites")
//.header("sec-ch-ua", "") // TODO
//.header("sec-ch-ua-mobile", "") // TODO
//.header("sec-ch-ua-platform", "") // TODO
.header("sec-fetch-dest", "empty")
.header("sec-fetch-mode", "cors")
.header("sec-fetch-site", "same-origin")
// TODO: Missing header "sentry-trace"
// TODO: Missing header "traceparent"
// TODO: Missing header "traceparent"
// TODO: Missing header "x-statsig-id"
// TODO: Missing header "x-xai-request-id"
.header(USER_AGENT, FIREFOX_143_MAC_USER_AGENT);
//info!("Sending...");
@@ -109,24 +106,23 @@ impl <'a> GrokCreateMediaPost<'a> {
let status = response.status();
let response_body = response.text()
let response_body = &response.text()
.await
.map_err(|err| {
error!("Error reading Grok create media response body: {:?}", err);
GrokGenericApiError::WreqError(err)
})?;
// TODO: Handle errors (Cloudflare, Grok, etc.)
// TODO:
if !status.is_success() {
error!("Upload file request returned an error (code {}) : {:?}", status.as_u16(), response_body);
//return Err(classify_general_http_status_code_and_body(status, response_body));
}
let response : CreateMediaPostRawResponse = serde_json::from_str(&response_body)
.map_err(|err| GrokGenericApiError::SerdeResponseParseErrorWithBody(err, response_body.to_string()))?;
//let response : GrokApiUploadFileResponse = serde_json::from_str(response_body)
// .map_err(|err| GrokGenericApiError::SerdeResponseParseErrorWithBody(err, response_body.to_string()))?;
Ok(GrokCreateMediaPostResponse {
post_id: PostId(response.post.id),
Ok(GrokCreateMediaPostResponse{
})
}
}
@@ -157,8 +153,6 @@ mod tests {
let result = request.send().await?;
println!("Result: {:?}", result);
println!("Post ID: {:?}", result.post_id);
assert_eq!(1, 2);
Ok(())
}
@@ -0,0 +1,2 @@
pub mod grok_create_media_post;
pub (super) mod request;
@@ -83,11 +83,9 @@ 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};
use crate::requests::download_file::download_video_file::{download_video_file, DownloadVideoFileArgs};
use crate::test_utils::get_test_cookies::get_typed_test_cookies;
use errors::AnyhowResult;
@@ -119,9 +117,6 @@ 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(())
}
@@ -0,0 +1 @@
mod download_video_file;
@@ -1,171 +0,0 @@
use crate::client::browser_user_agents::FIREFOX_143_MAC_USER_AGENT;
use crate::datatypes::api::file_id::FileId;
use crate::datatypes::api::user_id::UserId;
use crate::error::grok_client_error::GrokClientError;
use crate::error::grok_error::GrokError;
use crate::error::grok_generic_api_error::GrokGenericApiError;
use crate::utils::user_and_file_id_to_video_url::user_and_file_id_to_video_url;
use futures::{StreamExt, TryStreamExt};
use log::{error, info, warn};
use std::io::Write;
use std::path::Path;
use std::time::Duration;
use tokio::fs::File;
use tokio::io::AsyncWriteExt;
use wreq::header::{ACCEPT, ACCEPT_ENCODING, ACCEPT_LANGUAGE, CACHE_CONTROL, CONNECTION, CONTENT_TYPE, COOKIE, ORIGIN, REFERER, TE, UPGRADE_INSECURE_REQUESTS, USER_AGENT};
use wreq::{Client, Request, Response};
use wreq_util::Emulation;
pub struct GrokDownloadVideo<'a> {
pub cookies: &'a str,
pub user_id: &'a UserId,
pub file_id: &'a FileId,
pub request_timeout: Option<Duration>,
}
pub struct DownloadedVideoBytes {
pub bytes: Vec<u8>,
}
impl <'a> GrokDownloadVideo<'a> {
pub async fn download_to_path<P: AsRef<Path>>(&self, path: P) -> Result<(), GrokError> {
let response = self.send_request().await?;
let mut stream = response.bytes_stream();
let mut file = File::create(&path)
.await
.map_err(|err| GrokClientError::CannotOpenLocalFileForWriting(err))?;
//let _r = stream.for_each_concurrent(None, |chunk_result| async {
// match chunk_result {
// Ok(chunk) => {
// file;
// //if let Err(e) = file.write_all(&chunk).await {
// // //error!("Error writing to file: {}", e);
// //}
// }
// Err(e) => {
// //error!("Error in stream: {}", e);
// }
// }
//}).await;
while let Some(chunk) = stream.try_next()
.await
.map_err(|err| GrokGenericApiError::WreqError(err))?
{
file.write_all(&chunk)
.await
.map_err(|err| GrokClientError::CannotOpenLocalFileForWriting(err))?
}
file.flush().await.unwrap();
Ok(())
}
pub async fn download_bytes(&self) -> Result<DownloadedVideoBytes, GrokError> {
let response = self.send_request().await?;
let download_bytes = response.bytes()
.await
.map_err(|err| {
error!("Error reading Grok create media response body: {:?}", err);
GrokGenericApiError::WreqError(err)
})?;
Ok(DownloadedVideoBytes {
bytes: download_bytes.to_vec(),
})
}
async fn send_request(&self) -> Result<Response, GrokError> {
let client = Client::builder()
.emulation(Emulation::Firefox143)
.build()
.map_err(|err| GrokClientError::WreqClientError(err))?;
let video_url = user_and_file_id_to_video_url(&self.user_id, &self.file_id, false);
info!("Video file URL: {}", video_url);
let mut request_builder = client.get(&video_url)
.header(USER_AGENT, FIREFOX_143_MAC_USER_AGENT)
.header(ACCEPT, "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8")
.header(ACCEPT_LANGUAGE, "en-US,en;q=0.5")
.header(ACCEPT_ENCODING, "gzip, deflate, br, zstd")
.header(REFERER, "https://grok.com/")
.header("Sec-GPC", "1")
.header(CONNECTION, "keep-alive")
.header(COOKIE, self.cookies.to_string())
.header(UPGRADE_INSECURE_REQUESTS, "1")
.header("sec-fetch-dest", "document")
.header("sec-fetch-mode", "navigate")
.header("sec-fetch-site", "same-site")
.header("sec-fetch-user", "?1")
.header("priority", "u=0, i");
if let Some(timeout) = self.request_timeout {
request_builder = request_builder.timeout(timeout);
}
let response = request_builder.send()
.await
.map_err(|err| GrokGenericApiError::WreqError(err))?;
let status = response.status();
// TODO: Classify errors
if !status.is_success() {
warn!("Not successful liking media (status: {:?})", status);
// error!("Upload file request returned an error (code {}) : {:?}", status.as_u16(), response_body);
// return Err(classify_general_http_status_code_and_body(status, response_body));
}
Ok(response)
}
}
#[cfg(test)]
mod tests {
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};
use crate::test_utils::get_test_cookies::get_typed_test_cookies;
use errors::AnyhowResult;
#[tokio::test]
#[ignore] // Client side tests only
async fn test_download() -> AnyhowResult<()> {
//setup_test_logging(LevelFilter::Trace);
let cookies = get_typed_test_cookies()?;
let file_id = FileId("d9b300c6-9562-4e24-a87a-3ede2a53f0bc".to_string()); // Ernest
let secrets = request_client_secrets(RequestClientSecretsArgs {
cookies: &cookies,
}).await?;
println!("Verification Token: {:?}", secrets.verification_token);
println!("Sentry Trace: {:?}", secrets.sentry_trace);
println!("Numbers: {:?}", secrets.numbers);
println!("Svg Path: {:?}", secrets.svg_path_data);
println!("Baggage: {:?}", secrets.baggage);
// TDOO: This implementation is untested.
//let download = download_video_file(DownloadVideoFileArgs {
// cookies: cookies.as_str(),
// user_id: &secrets.user_id,
// file_id: &file_id,
// request_timeout: None,
//}).await?;
//println!("Video byte length: {:?}", download.bytes.len());
assert_eq!(1, 2);
Ok(())
}
}
@@ -1,2 +0,0 @@
pub mod download_video_file;
pub mod grok_download_video;
@@ -11,11 +11,11 @@ use wreq::ws::message::Message;
use wreq::ws::WebSocket;
#[derive(Clone)]
pub struct GrokWrappedWebsocket {
pub struct ClonableWebsocket {
pub(crate) websocket: Arc<RwLock<WebSocket>>,
}
impl GrokWrappedWebsocket {
impl ClonableWebsocket {
pub fn new(websocket: WebSocket) -> Self {
Self {
websocket: Arc::new(RwLock::new(websocket)),
@@ -77,6 +77,7 @@ impl GrokWrappedWebsocket {
}
}
pub async fn try_next_timeout(&self, duration: Duration) -> Result<Option<Message>, GrokClientError> {
match self.websocket.write() {
Err(_) => Err(GrokClientError::WebsocketLockError),
@@ -1,108 +0,0 @@
use crate::error::grok_client_error::GrokClientError;
use crate::error::grok_error::GrokError;
use crate::requests::image_websocket::messages::websocket_server_message::WebsocketServerMessage;
use futures::stream::FusedStream;
use futures::{Stream, TryStreamExt};
use log::{info, warn};
use serde::Serialize;
use std::sync::{Arc, RwLock};
use std::time::Duration;
use wreq::ws::message::Message;
use wreq::ws::WebSocket;
pub struct GrokWebsocket {
pub(crate) websocket: WebSocket,
}
impl GrokWebsocket {
pub fn new(websocket: WebSocket) -> Self {
Self {
websocket,
}
}
pub async fn send(&mut self, message: String) -> Result<(), GrokError> {
let message = Message::text(message);
self.websocket.send(message)
.await
.map_err(|err| {
GrokClientError::WreqClientError(err)
})?;
Ok(())
}
pub async fn send_serializable<T: Serialize>(&mut self, message: T) -> Result<(), GrokError> {
let message_json = serde_json::to_string(&message)
.map_err(|err| {
warn!("Failed to serialize prompt websocket message: {}", err);
GrokClientError::WebsocketRequestSerializationError(err)
})?;
self.send(message_json).await
}
pub async fn get_response_with_timeout(&mut self, duration: Duration) -> Result<Option<WebsocketServerMessage>, GrokError> {
let result = tokio::time::timeout(duration, self.websocket.try_next()).await;
match result {
Err(elapsed) => {
info!("Websocket try_next() elapsed without receiving a message: {:?}", elapsed);
Ok(None) // Timeout elapsed.
}
Ok(inner) => {
let maybe_message = inner.map_err(GrokClientError::WebsocketReadError)?;
match maybe_message {
None => Ok(None),
Some(Message::Text(text)) => {
let maybe_message = WebsocketServerMessage::from_json_str(&text)?;
Ok(Some(maybe_message))
},
Some(_) => {
warn!("Received non-text websocket message.");
Ok(None)
},
}
}
}
}
pub async fn try_next_timeout(&mut self, duration: Duration) -> Result<Option<Message>, GrokClientError> {
let result = tokio::time::timeout(duration, self.websocket.try_next()).await;
match result {
Err(elapsed) => {
info!("Websocket try_next() elapsed without receiving a message: {:?}", elapsed);
Ok(None) // Timeout elapsed.
}
Ok(inner) => {
let maybe_message = inner.map_err(GrokClientError::WebsocketReadError)?;
Ok(maybe_message)
}
}
}
// pub async fn is_terminated(&self) -> Result<bool, GrokClientError> {
// match self.websocket.read() {
// Err(_) => Err(GrokClientError::WebsocketLockError.into()),
// Ok(websocket) => Ok(websocket.is_terminated()),
// }
// }
// /// NB: This will block forever if there is no item.
// pub async fn try_next(&self) -> Result<Option<Message>, GrokClientError> {
// match self.websocket.write() {
// Err(_) => Err(GrokClientError::WebsocketLockError),
// Ok(mut websocket) => websocket
// .try_next()
// .await
// .map_err(|err| GrokClientError::WebsocketReadError(err)),
// }
// }
// pub fn size_hint(&self) -> Result<(usize, Option<usize>), GrokClientError> {
// match self.websocket.read() {
// Err(_) => Err(GrokClientError::WebsocketLockError),
// Ok(websocket) => Ok(websocket.size_hint())
// }
// }
}
@@ -1,8 +1,7 @@
use crate::datatypes::api::request_id::RequestId;
use crate::error::grok_client_error::GrokClientError;
use crate::error::grok_error::GrokError;
use crate::requests::image_websocket::grok_websocket::GrokWebsocket;
use crate::requests::image_websocket::grok_wrapped_websocket::GrokWrappedWebsocket;
use crate::requests::image_websocket::clonable_websocket::ClonableWebsocket;
use crate::requests::image_websocket::messages::message_image_data::MessageImageData;
use crate::utils::scrub_blobs_for_debug_logging::scrub_blobs_for_debug_logging;
use log::info;
@@ -13,7 +12,7 @@ use wreq::ws::message::Message;
const DEFAULT_IMAGE_COUNT: usize = 6;
pub struct ListenForWebsocketImagesArgs<'a> {
pub websocket: &'a mut GrokWebsocket,
pub websocket: &'a ClonableWebsocket,
pub timeout: Duration,
}
@@ -114,10 +113,9 @@ pub async fn listen_for_websocket_images(args: ListenForWebsocketImagesArgs<'_>)
#[cfg(test)]
mod tests {
use crate::requests::image_websocket::clonable_websocket::ClonableWebsocket;
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;
@@ -139,16 +137,14 @@ mod tests {
cookies: &cookies,
}).await?;
//let websocket = GrokWrappedWebsocket::new(websocket);
let mut websocket = GrokWebsocket::new(websocket);
let websocket = ClonableWebsocket::new(websocket);
println!("Sending...");
std::io::stdout().flush()?;
let _result = prompt_websocket_image(PromptWebsocketImageArgs {
websocket: &mut websocket,
websocket_wrapped: websocket.clone(),
prompt,
aspect_ratio: ClientMessageAspectRatio::WideThreeByTwo,
}).await?;
println!("Reading...");
@@ -157,7 +153,7 @@ mod tests {
println!("Polling.");
let images = listen_for_websocket_images(ListenForWebsocketImagesArgs {
websocket: &mut websocket,
websocket: &websocket,
timeout: Duration::from_millis(10_000),
}).await?;
@@ -1,13 +1,13 @@
use crate::datatypes::api::request_id::RequestId;
use crate::error::grok_client_error::GrokClientError;
use crate::error::grok_error::GrokError;
use crate::requests::image_websocket::grok_websocket::GrokWebsocket;
use crate::requests::image_websocket::clonable_websocket::ClonableWebsocket;
use crate::requests::image_websocket::messages::message_request_id::MessageRequestId;
use std::time::{Duration, Instant};
use wreq::ws::message::Message;
pub struct ListenForWebsocketRequestIdArgs<'a> {
pub websocket: &'a mut GrokWebsocket,
pub websocket: &'a ClonableWebsocket,
pub timeout: Duration,
}
@@ -70,10 +70,9 @@ pub async fn listen_for_websocket_request_id(args: ListenForWebsocketRequestIdAr
#[cfg(test)]
mod tests {
use crate::requests::image_websocket::clonable_websocket::ClonableWebsocket;
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;
@@ -91,20 +90,18 @@ mod tests {
let cookies = get_test_cookies()?;
let mut websocket = create_listen_websocket(CreateListenWebsocketArgs {
let websocket = create_listen_websocket(CreateListenWebsocketArgs {
cookies: &cookies,
}).await?;
//let websocket = GrokWrappedWebsocket::new(websocket);
let mut websocket = GrokWebsocket::new(websocket);
let websocket = ClonableWebsocket::new(websocket);
println!("Sending...");
std::io::stdout().flush()?;
let _result = prompt_websocket_image(PromptWebsocketImageArgs {
websocket: &mut websocket,
websocket_wrapped: websocket.clone(),
prompt,
aspect_ratio: ClientMessageAspectRatio::Square,
}).await?;
println!("Reading...");
@@ -113,7 +110,7 @@ mod tests {
println!("Polling.");
let request_data = listen_for_websocket_request_id(ListenForWebsocketRequestIdArgs {
websocket: &mut websocket,
websocket: &websocket,
timeout: Duration::from_millis(10_000),
}).await?;
@@ -46,24 +46,11 @@ 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, aspect_ratio: ClientMessageAspectRatio) -> Self {
pub fn new_image_prompt(prompt: &str) -> Self {
Self {
r#type: "conversation.item.create".to_string(),
timestamp: Utc::now().timestamp_millis() as u64,
@@ -80,7 +67,6 @@ impl WebsocketClientMessage {
enable_nsfw: true,
skip_upsampler: false,
is_initial: false,
aspect_ratio,
},
}
],
@@ -1,7 +1,6 @@
pub mod grok_wrapped_websocket;
pub mod clonable_websocket;
pub mod create_listen_websocket;
pub mod listen_for_websocket_images;
pub mod listen_for_websocket_request_id;
pub mod messages;
pub mod prompt_websocket_image;
pub mod grok_websocket;
pub mod prompt_websocket_image;
@@ -1,28 +1,25 @@
use crate::error::grok_error::GrokError;
use crate::requests::image_websocket::grok_websocket::GrokWebsocket;
use crate::requests::image_websocket::messages::websocket_client_message::{ClientMessageAspectRatio, WebsocketClientMessage};
use crate::requests::image_websocket::clonable_websocket::ClonableWebsocket;
use crate::requests::image_websocket::messages::websocket_client_message::WebsocketClientMessage;
pub struct PromptWebsocketImageArgs<'a> {
pub websocket: &'a mut GrokWebsocket,
pub websocket_wrapped: ClonableWebsocket,
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, args.aspect_ratio);
let message = WebsocketClientMessage::new_image_prompt(args.prompt);
args.websocket.send_serializable(&message).await?;
args.websocket_wrapped.send_serializable(&message).await?;
Ok(())
}
#[cfg(test)]
mod tests {
use crate::requests::image_websocket::clonable_websocket::ClonableWebsocket;
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;
@@ -43,16 +40,14 @@ mod tests {
cookies: &cookies,
}).await?;
//let websocket = GrokWrappedWebsocket::new(websocket);
let mut websocket = GrokWebsocket::new(websocket);
let websocket = ClonableWebsocket::new(websocket);
println!("Sending...");
std::io::stdout().flush()?;
let result = prompt_websocket_image(PromptWebsocketImageArgs {
websocket: &mut websocket,
websocket_wrapped: websocket.clone(),
prompt: "a dog riding a motorcycle",
aspect_ratio: ClientMessageAspectRatio::WideThreeByTwo,
}).await?;
println!("Reading...");
@@ -2,17 +2,14 @@ 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)"[^}]*?\(880932\)"#)
Regex::new(r#""(static/chunks/[^"]+\.js)"[^}]*?a\(880932\)"#)
.expect("Regex should parse")
});
@@ -39,32 +36,33 @@ pub fn parse_script_actions_and_xsid_script_path(scripts: &HashMap<String, Strin
}
}
// 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 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::Script1LogicOutOfDate),
None => return Err(GrokClientError::ScriptLogicOutOfDate),
};
let script_content_2 = match script_content_2 {
Some(script_content_2) => script_content_2,
None => return Err(GrokClientError::Script2LogicOutOfDate),
None => return Err(GrokClientError::ScriptLogicOutOfDate),
};
//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 = parse_xsid_script_path_from_turbopack_script(&script_content_2)?;
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),
};
Ok(ActionsAndXsid {
actions,
@@ -72,23 +70,10 @@ 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, parse_xsid_script_path_from_turbopack_script};
use crate::requests::index_page::parsers::script::parse_script_actions_and_xsid_script_path::parse_script_actions_and_xsid_script_path;
use crate::test_utils::get_test_cookies::get_test_cookies;
use errors::AnyhowResult;
@@ -108,17 +93,4 @@ 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());
}
}
@@ -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::ScriptSvgLogicOutOfDate
GrokClientError::ScriptLogicOutOfDate
})
}
@@ -1,15 +1,9 @@
use crate::client::browser_user_agents::FIREFOX_143_MAC_USER_AGENT;
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::verification_token::VerificationToken;
use crate::datatypes::api::xsid_numbers::XsidNumbers;
use crate::error::grok_client_error::GrokClientError;
use crate::error::grok_error::GrokError;
use crate::error::grok_generic_api_error::GrokGenericApiError;
use crate::requests::index_page::signature::generate_xsid::{generate_xsid, GenerateXsidArgs};
use crate::requests::media_posts::like_media_post::request::LikeMediaWireRequest;
use crate::requests::like_media::request::LikeMediaWireRequest;
use crate::requests::upload_file::grok_upload_file::{GrokUploadFile, GrokUploadFileResponse};
use crate::utils::user_and_file_id_to_image_url::user_and_file_id_to_image_url;
use log::{error, info, warn};
@@ -18,6 +12,12 @@ use uuid::Uuid;
use wreq::header::{ACCEPT, ACCEPT_ENCODING, ACCEPT_LANGUAGE, CACHE_CONTROL, CONNECTION, CONTENT_TYPE, COOKIE, ORIGIN, PRAGMA, REFERER, TE, USER_AGENT};
use wreq::Client;
use wreq_util::Emulation;
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::verification_token::VerificationToken;
use crate::datatypes::api::xsid_numbers::XsidNumbers;
const LIKE_MEDIA_POST_URL: &str = "https://grok.com/rest/media/post/like";
@@ -1,13 +0,0 @@
{
"post": {
"id": "3c896963-8e6b-4e71-83e1-4253b2bf2ac8",
"userId":"85980643-ffab-4984-a3de-59a608c47d7f",
"createTime":"2025-10-30T23:22:19.528702688Z",
"prompt":"",
"mediaType":"MEDIA_POST_TYPE_IMAGE",
"mediaUrl":"https://assets.grok.com/users/85980643-ffab-4984-a3de-59a608c47d7f/3c896963-8e6b-4e71-83e1-4253b2bf2ac8/content",
"mimeType":"image/jpeg",
"audioUrls":[],
"childPosts":[]
}
}
@@ -1,3 +0,0 @@
pub mod grok_create_media_post;
pub (super) mod request;
mod response;
@@ -1,28 +0,0 @@
use serde::Deserialize;
#[derive(Deserialize)]
pub struct CreateMediaPostRawResponse {
pub post: PostData,
}
#[derive(Deserialize)]
pub struct PostData {
/// The identifier for the post
pub id: String,
}
/*
{
"post": {
"id": "3c896963-8e6b-4e71-83e1-4253b2bf2ac8",
"userId":"85980643-ffab-4984-a3de-59a608c47d7f",
"createTime":"2025-10-30T23:22:19.528702688Z",
"prompt":"",
"mediaType":"MEDIA_POST_TYPE_IMAGE",
"mediaUrl":"https://assets.grok.com/users/85980643-ffab-4984-a3de-59a608c47d7f/3c896963-8e6b-4e71-83e1-4253b2bf2ac8/content",
"mimeType":"image/jpeg",
"audioUrls":[],
"childPosts":[]
}
}
*/

Some files were not shown because too many files have changed in this diff Show More