mirror of
https://github.com/storytold/artcraft.git
synced 2026-10-21 09:22:41 -04:00
Compare commits
37
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
de0776009b | ||
|
|
11f8885bf6 | ||
|
|
0e2926b179 | ||
|
|
671e02edcb | ||
|
|
10f8649089 | ||
|
|
3aeea6d2b2 | ||
|
|
abccceb5bd | ||
|
|
9add7ee1cd | ||
|
|
d551e48a23 | ||
|
|
5cb608fad6 | ||
|
|
d5ae28ca4c | ||
|
|
8313558795 | ||
|
|
95173e032f | ||
|
|
b0f6551623 | ||
|
|
8f2a03e21e | ||
|
|
5c3760f9ec | ||
|
|
d6c1eaae5d | ||
|
|
d6abe9fb88 | ||
|
|
1e758b1b6c | ||
|
|
cd40edb5f9 | ||
|
|
3838cc3f21 | ||
|
|
0fec772d9a | ||
|
|
1afe2ac26f | ||
|
|
5eef6c0161 | ||
|
|
6c6f7f054d | ||
|
|
ba3d46607b | ||
|
|
12bf504d6f | ||
|
|
026c2bf827 | ||
|
|
d20854f75d | ||
|
|
7c9396aa65 | ||
|
|
90810231da | ||
|
|
e9613c6b4a | ||
|
|
cb2eb39183 | ||
|
|
7a100bd461 | ||
|
|
10c35381fa | ||
|
|
24c8353482 | ||
|
|
eb0db3d67d |
+12
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"db_name": "SQLite",
|
||||
"query": "\n UPDATE tasks\n SET is_dismissed_by_user = 1\n WHERE id = ?\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Right": 1
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "13d435d8f681b73e4080748dcef0579e246e41049af67e1bd79ffc06aa6ba974"
|
||||
}
|
||||
+116
@@ -0,0 +1,116 @@
|
||||
{
|
||||
"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 on_complete_primary_media_file_token,\n on_complete_primary_media_file_class,\n on_complete_batch_token,\n on_complete_primary_media_file_cdn_url,\n on_complete_primary_media_file_thumbnail_url_template,\n created_at as \"created_at: DateTime<Utc>\",\n updated_at as \"updated_at: DateTime<Utc>\",\n completed_at as \"completed_at: DateTime<Utc>\"\n FROM tasks\n WHERE is_dismissed_by_user == 0\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"
|
||||
},
|
||||
{
|
||||
"name": "on_complete_primary_media_file_token",
|
||||
"ordinal": 9,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "on_complete_primary_media_file_class",
|
||||
"ordinal": 10,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "on_complete_batch_token",
|
||||
"ordinal": 11,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "on_complete_primary_media_file_cdn_url",
|
||||
"ordinal": 12,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "on_complete_primary_media_file_thumbnail_url_template",
|
||||
"ordinal": 13,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "created_at: DateTime<Utc>",
|
||||
"ordinal": 14,
|
||||
"type_info": "Int64"
|
||||
},
|
||||
{
|
||||
"name": "updated_at: DateTime<Utc>",
|
||||
"ordinal": 15,
|
||||
"type_info": "Int64"
|
||||
},
|
||||
{
|
||||
"name": "completed_at: DateTime<Utc>",
|
||||
"ordinal": 16,
|
||||
"type_info": "Int64"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Right": 0
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
true
|
||||
]
|
||||
},
|
||||
"hash": "47f94c127abc20683b25c257aed3c2190b9bd29eb829d61392f08e10fd986767"
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"db_name": "SQLite",
|
||||
"query": "\n UPDATE tasks\n SET\n task_status = ?,\n on_complete_batch_token = ?,\n on_complete_primary_media_file_token = ?,\n on_complete_primary_media_file_class = ?,\n on_complete_primary_media_file_cdn_url = ?,\n on_complete_primary_media_file_thumbnail_url_template = ?\n WHERE id = ?\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Right": 7
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "50a2449b87abed7df3302a74ecd0e8ab87940077a247636680cb09b2fe488321"
|
||||
}
|
||||
Generated
+10
-15
@@ -660,7 +660,6 @@ dependencies = [
|
||||
"discord-rich-presence",
|
||||
"enums",
|
||||
"errors",
|
||||
"fal_client",
|
||||
"filesys",
|
||||
"futures 0.3.31",
|
||||
"idempotency",
|
||||
@@ -2439,7 +2438,7 @@ checksum = "2eac901828f88a5241ee0600950ab981148a18f2f756900ffba1b125ca6a3ef9"
|
||||
dependencies = [
|
||||
"cookie 0.18.1",
|
||||
"document-features",
|
||||
"idna 1.1.0",
|
||||
"idna 1.0.3",
|
||||
"log",
|
||||
"publicsuffix",
|
||||
"serde",
|
||||
@@ -4109,9 +4108,9 @@ checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b"
|
||||
|
||||
[[package]]
|
||||
name = "form_urlencoded"
|
||||
version = "1.2.2"
|
||||
version = "1.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf"
|
||||
checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456"
|
||||
dependencies = [
|
||||
"percent-encoding",
|
||||
]
|
||||
@@ -5404,9 +5403,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "idna"
|
||||
version = "1.1.0"
|
||||
version = "1.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de"
|
||||
checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e"
|
||||
dependencies = [
|
||||
"idna_adapter",
|
||||
"smallvec 1.15.1",
|
||||
@@ -5573,7 +5572,6 @@ dependencies = [
|
||||
"mimetypes",
|
||||
"mysql_queries",
|
||||
"once_cell",
|
||||
"openai_sora_client",
|
||||
"opentelemetry",
|
||||
"opentelemetry-otlp",
|
||||
"opentelemetry_sdk",
|
||||
@@ -5586,7 +5584,6 @@ dependencies = [
|
||||
"serde",
|
||||
"serde_derive",
|
||||
"serde_json",
|
||||
"shared_service_components",
|
||||
"sqlx",
|
||||
"subprocess",
|
||||
"subprocess_common",
|
||||
@@ -8301,7 +8298,7 @@ version = "2.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6f42ea446cab60335f76979ec15e12619a2165b5ae2c12166bef27d283a9fadf"
|
||||
dependencies = [
|
||||
"idna 1.1.0",
|
||||
"idna 1.0.3",
|
||||
"psl-types",
|
||||
]
|
||||
|
||||
@@ -10599,7 +10596,6 @@ dependencies = [
|
||||
"mimetypes",
|
||||
"mysql_queries",
|
||||
"once_cell",
|
||||
"openai_sora_client",
|
||||
"password",
|
||||
"primitives",
|
||||
"prompt_engineering",
|
||||
@@ -10616,7 +10612,6 @@ dependencies = [
|
||||
"serde_derive",
|
||||
"serde_json",
|
||||
"server_environment",
|
||||
"shared_service_components",
|
||||
"speculoos",
|
||||
"sqlx",
|
||||
"strum",
|
||||
@@ -12435,12 +12430,12 @@ checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
|
||||
|
||||
[[package]]
|
||||
name = "url"
|
||||
version = "2.5.7"
|
||||
version = "2.5.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "08bc136a29a3d1758e07a9cca267be308aeebf5cfd5a10f3f67ab2097683ef5b"
|
||||
checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60"
|
||||
dependencies = [
|
||||
"form_urlencoded",
|
||||
"idna 1.1.0",
|
||||
"idna 1.0.3",
|
||||
"percent-encoding",
|
||||
"serde",
|
||||
]
|
||||
@@ -13731,7 +13726,7 @@ dependencies = [
|
||||
"hyper-rustls",
|
||||
"hyper-tls 0.6.0",
|
||||
"hyper-util",
|
||||
"idna 1.1.0",
|
||||
"idna 1.0.3",
|
||||
"indexmap 1.9.3",
|
||||
"indexmap 2.9.0",
|
||||
"lazy_static",
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
-- noinspection SqlDialectInspectionForFile
|
||||
-- noinspection SqlNoDataSourceInspectionForFile
|
||||
-- noinspection SqlResolveForFile
|
||||
|
||||
select
|
||||
u.display_name,
|
||||
count(*) as sessions,
|
||||
a.session_duration_seconds / 60 / 60 as hours,
|
||||
a.measurement_count,
|
||||
u.email_address,
|
||||
a.os_platform,
|
||||
a.os_version,
|
||||
u.created_at,
|
||||
u.maybe_source,
|
||||
a.last_active_at
|
||||
from analytics_app_sessions as a
|
||||
left outer join users as u
|
||||
on a.user_token = u.token
|
||||
group by a.user_token
|
||||
order by hours desc;
|
||||
@@ -9,6 +9,14 @@
|
||||
-- tasks_v1.sqlite - initial version
|
||||
-- tasks_v2.sqlite - added model_type (nullable)
|
||||
-- tasks_v3.sqlite - added frontend_caller, comments
|
||||
-- tasks_v4.sqlite - added is_dismissed_by_user
|
||||
-- tasks_v5.sqlite - added five fields:
|
||||
-- on_complete_batch_token,
|
||||
-- on_complete_primary_media_file_token,
|
||||
-- on_complete_primary_media_file_class,
|
||||
-- on_complete_primary_media_file_cdn_url,
|
||||
-- on_complete_primary_media_file_thumbnail_url_template,
|
||||
--
|
||||
|
||||
CREATE TABLE tasks (
|
||||
-- Task auto-incrementing primary key.
|
||||
@@ -50,6 +58,32 @@ CREATE TABLE tasks (
|
||||
-- will be re-emitted back to the frontend.
|
||||
frontend_subscriber_payload TEXT,
|
||||
|
||||
-- Whether the user has dismissed the task from view.
|
||||
is_dismissed_by_user INTEGER NOT NULL DEFAULT 0,
|
||||
|
||||
-- OPTIONAL.
|
||||
-- When the generation completes, the batch token (if any).
|
||||
-- This will point to a collection of multiple files.
|
||||
on_complete_batch_token TEXT,
|
||||
|
||||
-- OPTIONAL.
|
||||
-- When the generation completes, the media token of the "primary" media file.
|
||||
-- We'll probably always generate homogenous types, but just in case this is the first or primary item in a batch.
|
||||
on_complete_primary_media_file_token TEXT,
|
||||
|
||||
-- OPTIONAL.
|
||||
-- When the generation completes, the file type of the primary media file.
|
||||
-- We'll probably always generate homogenous types, but just in case this is the first or primary item in a batch.
|
||||
on_complete_primary_media_file_class TEXT,
|
||||
|
||||
-- OPTIONAL.
|
||||
-- The URL to the media file.
|
||||
on_complete_primary_media_file_cdn_url TEXT,
|
||||
|
||||
-- OPTIONAL.
|
||||
-- The URL template for the thumbnail of the primary media file.
|
||||
on_complete_primary_media_file_thumbnail_url_template TEXT,
|
||||
|
||||
created_at INTEGER NOT NULL DEFAULT (unixepoch('now')),
|
||||
updated_at INTEGER NOT NULL DEFAULT (unixepoch('now')),
|
||||
completed_at INTEGER DEFAULT NULL
|
||||
|
||||
+35
-7
@@ -27,6 +27,32 @@ use tokens::tokens::model_weights::ModelWeightToken;
|
||||
use tokens::tokens::prompts::PromptToken;
|
||||
use uuid::uuid;
|
||||
|
||||
pub struct UploadVideoFromFileArgs<'a, P: AsRef<Path>> {
|
||||
pub api_host: &'a ApiHost,
|
||||
pub maybe_creds: Option<&'a StorytellerCredentialSet>,
|
||||
|
||||
// NB: Path needs to be owned for the request.
|
||||
pub path: P,
|
||||
|
||||
// /// If true, we should hide the image from the user's gallery.
|
||||
// pub is_intermediate_system_file: bool,
|
||||
|
||||
/// If provided, this is the prompt that this image is associated with.
|
||||
/// NOTE: Cannot set `is_intermediate_system_file = true` if this is set.
|
||||
pub maybe_prompt_token: Option<&'a PromptToken>,
|
||||
|
||||
// /// If provided, this is the service provider that created the image.
|
||||
// /// NOTE: Cannot set `is_intermediate_system_file = true` if this is set.
|
||||
// pub maybe_generation_provider: Option<GenerationProvider>,
|
||||
|
||||
// /// If provided, this groups the file into a batch
|
||||
// /// TODO: This shouldn't be set clientside without the backend generating the token
|
||||
// /// and cryptographically securing it. But we need to go fast here.
|
||||
// pub maybe_batch_token: Option<&'a BatchGenerationToken>,
|
||||
}
|
||||
|
||||
|
||||
|
||||
// TODO(bt,2025-04-22): Share API definitions between client and server in common crate.
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
@@ -37,12 +63,10 @@ pub struct UploadVideoMediaFileSuccessResponse {
|
||||
|
||||
/// Upload a video media file from a file.
|
||||
pub async fn upload_video_media_file_from_file<P: AsRef<Path>>(
|
||||
api_host: &ApiHost,
|
||||
maybe_creds: Option<&StorytellerCredentialSet>,
|
||||
path: P,
|
||||
args: UploadVideoFromFileArgs<'_, P>,
|
||||
) -> Result<UploadVideoMediaFileSuccessResponse, ApiError> {
|
||||
|
||||
let url = get_route(api_host);
|
||||
let url = get_route(args.api_host);
|
||||
|
||||
debug!("Requesting {:?}", &url);
|
||||
|
||||
@@ -50,16 +74,20 @@ pub async fn upload_video_media_file_from_file<P: AsRef<Path>>(
|
||||
.gzip(true)
|
||||
.build()?;
|
||||
|
||||
let form = Form::new()
|
||||
let mut form = Form::new()
|
||||
.text("uuid_idempotency_token", generate_random_uuid())
|
||||
.file("file", path)
|
||||
.file("file", args.path)
|
||||
.await?;
|
||||
|
||||
if let Some(prompt_token) = &args.maybe_prompt_token {
|
||||
form = form.text("maybe_prompt_token", prompt_token.to_string());
|
||||
}
|
||||
|
||||
let mut request_builder = client.post(url)
|
||||
.header("User-Agent", USER_AGENT)
|
||||
.header("Accept", APPLICATION_JSON);
|
||||
|
||||
if let Some(creds) = maybe_creds {
|
||||
if let Some(creds) = args.maybe_creds {
|
||||
if let Some(header) = &creds.maybe_as_cookie_header() {
|
||||
request_builder = request_builder.header("Cookie", header);
|
||||
}
|
||||
|
||||
+7
-2
@@ -1,7 +1,7 @@
|
||||
use crate::credentials::storyteller_credential_set::StorytellerCredentialSet;
|
||||
use crate::endpoints::media_files::upload_image_media_file_from_file::{upload_image_media_file_from_file, UploadImageFromFileArgs};
|
||||
use crate::endpoints::media_files::upload_new_engine_asset_from_file::upload_new_engine_asset_from_file;
|
||||
use crate::endpoints::media_files::upload_video_media_file_from_file::upload_video_media_file_from_file;
|
||||
use crate::endpoints::media_files::upload_video_media_file_from_file::{upload_video_media_file_from_file, UploadVideoFromFileArgs};
|
||||
use crate::error::client_error::ClientError;
|
||||
use crate::error::storyteller_error::StorytellerError;
|
||||
use crate::utils::api_host::ApiHost;
|
||||
@@ -42,7 +42,12 @@ pub async fn upload_media_file_from_file<P: AsRef<Path>>(
|
||||
}
|
||||
}
|
||||
Some(FileExtension::Mp4) => {
|
||||
match upload_video_media_file_from_file(api_host, maybe_creds, path).await {
|
||||
match upload_video_media_file_from_file(UploadVideoFromFileArgs {
|
||||
api_host,
|
||||
maybe_creds,
|
||||
path,
|
||||
maybe_prompt_token
|
||||
}).await {
|
||||
Ok(result) => Ok(UploadMediaFileSuccessResponse {
|
||||
success: result.success,
|
||||
media_file_token: result.media_file_token,
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
#[cfg(test)]
|
||||
pub (crate) mod test_utils;
|
||||
|
||||
// Internal lib
|
||||
pub (crate) mod constants;
|
||||
pub (crate) mod utils_internal;
|
||||
|
||||
// User API
|
||||
pub mod creds;
|
||||
pub mod error;
|
||||
pub mod recipes;
|
||||
|
||||
+55
@@ -0,0 +1,55 @@
|
||||
use crate::creds::sora_credential_set::SoraCredentialSet;
|
||||
use crate::error::sora_error::SoraError;
|
||||
use crate::recipes::utils::maybe_renew_session_jwt::maybe_renew_session_jwt;
|
||||
use crate::requests::generate_sora2_video::generate_sora2_video::{generate_sora2_video, GenerateSora2VideoArgs, GenerateSora2VideoResponse};
|
||||
|
||||
/// Generate Sora 2 video with session auto-renewal.
|
||||
/// If a new sora credential is returned, replace the old one with the new one.
|
||||
pub async fn generate_sora2_video_with_session_auto_renew(
|
||||
args: GenerateSora2VideoArgs<'_>,
|
||||
) -> Result<(GenerateSora2VideoResponse, Option<SoraCredentialSet>), SoraError> {
|
||||
let mut maybe_new_creds = maybe_renew_session_jwt(&args.credentials).await?;
|
||||
|
||||
let use_creds = maybe_new_creds.as_ref()
|
||||
.unwrap_or_else(|| &args.credentials);
|
||||
|
||||
let mut request = args.clone();
|
||||
request.credentials = use_creds;
|
||||
|
||||
let result = generate_sora2_video(request).await;
|
||||
|
||||
match result {
|
||||
Ok(response) => Ok((response, maybe_new_creds)),
|
||||
Err(err) => Err(err), // TODO: Should we retry?
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::recipes::generate_sora2_video::generate_sora2_video_with_session_auto_renew::generate_sora2_video_with_session_auto_renew;
|
||||
use crate::requests::generate_sora2_video::generate_sora2_video::{GenerateSora2VideoArgs, Orientation};
|
||||
use crate::test_utils::get_test_credentials::get_test_credentials;
|
||||
use errors::AnyhowResult;
|
||||
|
||||
#[ignore] // You can manually run "ignore" tests in the IDE, but they won't run in CI.
|
||||
#[tokio::test]
|
||||
pub async fn manual_test() -> AnyhowResult<()> {
|
||||
let creds = get_test_credentials()?;
|
||||
|
||||
let args = GenerateSora2VideoArgs {
|
||||
prompt: "a dog playing tennis",
|
||||
credentials: &creds,
|
||||
request_timeout: None,
|
||||
orientation: Orientation::Landscape,
|
||||
image_reference_media_ids: None,
|
||||
};
|
||||
|
||||
let (result , creds) =
|
||||
generate_sora2_video_with_session_auto_renew(args).await?;
|
||||
|
||||
println!("result: {:#?}", result);
|
||||
assert_eq!(1, 2);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
pub mod generate_sora2_video_with_session_auto_renew;
|
||||
+2
-1
@@ -8,7 +8,8 @@ use crate::requests::image_gen::common::{ImageSize, NumImages, SoraImageGenRespo
|
||||
use crate::requests::image_gen::image_gen_http_request;
|
||||
use crate::requests::image_gen::sora_image_gen_remix::{sora_image_gen_remix, SoraImageGenRemixRequest};
|
||||
use crate::requests::upload::upload_media_from_bytes::sora_media_upload_from_bytes;
|
||||
use crate::requests::upload::upload_media_http_request::{upload_media_http_request, SoraMediaUploadResponse};
|
||||
use crate::requests::upload::upload_media_http_request::upload_media_http_request;
|
||||
use crate::requests::upload::upload_media_http_response::SoraMediaUploadResponse;
|
||||
use anyhow::anyhow;
|
||||
use errors::AnyhowResult;
|
||||
use log::{error, info, warn};
|
||||
|
||||
+2
-1
@@ -3,7 +3,8 @@ use crate::error::sora_error::SoraError;
|
||||
use crate::error::sora_specific_api_error::SoraSpecificApiError;
|
||||
use crate::recipes::maybe_refresh_credentials_on_sora_error::maybe_refresh_credentials_on_sora_error;
|
||||
use crate::requests::upload::upload_media_from_file::sora_media_upload_from_file;
|
||||
use crate::requests::upload::upload_media_http_request::{upload_media_http_request, SoraMediaUploadResponse};
|
||||
use crate::requests::upload::upload_media_http_request::upload_media_http_request;
|
||||
use crate::requests::upload::upload_media_http_response::SoraMediaUploadResponse;
|
||||
use log::{info, warn};
|
||||
use std::path::Path;
|
||||
use std::time::Duration;
|
||||
|
||||
+60
@@ -0,0 +1,60 @@
|
||||
use crate::creds::sora_credential_set::SoraCredentialSet;
|
||||
use crate::error::sora_error::SoraError;
|
||||
use crate::recipes::image_upload_from_file_with_session_auto_renew::ImageUploadFromFileAutoRenewRequest;
|
||||
use crate::recipes::maybe_refresh_credentials_on_sora_error::maybe_refresh_credentials_on_sora_error;
|
||||
use crate::recipes::maybe_upgrade_or_renew_session::maybe_upgrade_or_renew_session;
|
||||
use crate::requests::common::task_id::TaskId;
|
||||
use crate::requests::deprecated::job_status::sora_job_status::{get_image_gen_status, StatusRequest, TaskResponse, VideoGenStatusResponse};
|
||||
use crate::requests::list_classic_tasks::list_classic_tasks::{list_classic_tasks, ListTasksResponse};
|
||||
use crate::requests::upload::upload_media_from_file::sora_media_upload_from_file;
|
||||
use anyhow::Error;
|
||||
use log::{info, warn};
|
||||
use std::path::Path;
|
||||
|
||||
/// Check Sora task statuses with session auto-renewal.
|
||||
/// If a new sora credential is returned, replace the old one with the new one.
|
||||
pub async fn list_classic_sora_tasks_with_session_auto_renew(
|
||||
credentials: &SoraCredentialSet,
|
||||
) -> Result<(ListTasksResponse, Option<SoraCredentialSet>), SoraError> {
|
||||
|
||||
let mut maybe_new_creds = maybe_renew_session(&credentials).await?;
|
||||
|
||||
let upgraded_creds = maybe_new_creds.as_ref()
|
||||
.unwrap_or_else(|| &credentials);
|
||||
|
||||
let result = list_classic_tasks(&upgraded_creds).await;
|
||||
|
||||
let err = match result {
|
||||
Ok(response) => return Ok((response, maybe_new_creds)),
|
||||
Err(err) => err,
|
||||
};
|
||||
|
||||
warn!("Task status polling failed: {:?}", err);
|
||||
|
||||
// TODO(bt,2025-05-28): This should only be done if the credentials were known to be bad
|
||||
let new_creds = maybe_refresh_credentials_on_sora_error(&upgraded_creds, err).await?;
|
||||
|
||||
// TODO(bt,2025-05-28): This should only be done if the credentials were actually refreshed.
|
||||
info!("Retrying task polling with new credentials...");
|
||||
|
||||
let result = list_classic_tasks(&new_creds).await?;
|
||||
|
||||
Ok((result, Some(new_creds)))
|
||||
}
|
||||
|
||||
async fn maybe_renew_session(creds: &SoraCredentialSet) -> Result<Option<SoraCredentialSet>, SoraError> {
|
||||
if !creds.jwt_bearer_token.is_none() {
|
||||
// TODO: Handle JWT expired case as well.
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
info!("JWT not set. Upgrading credentials...");
|
||||
let mut updated_creds = creds.clone();
|
||||
|
||||
if let Err(err) = maybe_upgrade_or_renew_session(&mut updated_creds).await {
|
||||
warn!("Failed to upgrade or renew session: {:?}", err);
|
||||
return Err(err);
|
||||
}
|
||||
|
||||
Ok(Some(updated_creds))
|
||||
}
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
use crate::creds::sora_credential_set::SoraCredentialSet;
|
||||
use crate::error::sora_error::SoraError;
|
||||
use crate::recipes::utils::maybe_renew_session_jwt::maybe_renew_session_jwt;
|
||||
use crate::requests::list_sora2_drafts::list_sora2_drafts::{list_sora2_drafts, ListSora2DraftsResponse};
|
||||
|
||||
/// Check Sora 2 drafts with session auto-renewal.
|
||||
/// If a new sora credential is returned, replace the old one with the new one.
|
||||
pub async fn list_sora2_drafts_with_session_auto_renew(
|
||||
credentials: &SoraCredentialSet,
|
||||
) -> Result<(ListSora2DraftsResponse, Option<SoraCredentialSet>), SoraError> {
|
||||
|
||||
let mut maybe_new_creds = maybe_renew_session_jwt(&credentials).await?;
|
||||
|
||||
let use_creds = maybe_new_creds.as_ref()
|
||||
.unwrap_or_else(|| &credentials);
|
||||
|
||||
let result = list_sora2_drafts(&use_creds).await;
|
||||
|
||||
match result {
|
||||
Ok(response) => Ok((response, maybe_new_creds)),
|
||||
Err(err) => Err(err), // TODO: Should we retry?
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::recipes::list_sora2_drafts::list_sora2_drafts_with_session_auto_renew::list_sora2_drafts_with_session_auto_renew;
|
||||
use crate::test_utils::get_test_credentials::get_test_credentials;
|
||||
use errors::AnyhowResult;
|
||||
|
||||
#[ignore] // You can manually run "ignore" tests in the IDE, but they won't run in CI.
|
||||
#[tokio::test]
|
||||
pub async fn manual_test() -> AnyhowResult<()> {
|
||||
let creds = get_test_credentials()?;
|
||||
|
||||
let (result , creds) =
|
||||
list_sora2_drafts_with_session_auto_renew(&creds).await?;
|
||||
|
||||
println!("result: {:#?}", result);
|
||||
assert_eq!(1, 2);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
pub mod list_sora2_drafts_with_session_auto_renew;
|
||||
-63
@@ -1,63 +0,0 @@
|
||||
use crate::creds::sora_credential_set::SoraCredentialSet;
|
||||
use crate::error::sora_error::SoraError;
|
||||
use crate::recipes::image_upload_from_file_with_session_auto_renew::ImageUploadFromFileAutoRenewRequest;
|
||||
use crate::recipes::maybe_refresh_credentials_on_sora_error::maybe_refresh_credentials_on_sora_error;
|
||||
use crate::recipes::maybe_upgrade_or_renew_session::maybe_upgrade_or_renew_session;
|
||||
use crate::requests::common::task_id::TaskId;
|
||||
use crate::requests::job_status::sora_job_status::{get_image_gen_status, StatusRequest, TaskResponse, VideoGenStatusResponse};
|
||||
use crate::requests::upload::upload_media_from_file::sora_media_upload_from_file;
|
||||
use crate::requests::upload::upload_media_http_request::SoraMediaUploadResponse;
|
||||
use anyhow::Error;
|
||||
use log::{info, warn};
|
||||
use std::path::Path;
|
||||
|
||||
pub struct StatusRequestArgs {
|
||||
pub limit: Option<u32>,
|
||||
pub before: Option<TaskId>,
|
||||
pub credentials: SoraCredentialSet,
|
||||
}
|
||||
|
||||
/// Check Sora task statuses with session auto-renewal.
|
||||
/// If a new sora credential is returned, replace the old one with the new one.
|
||||
pub async fn list_sora_task_status_with_session_auto_renew(
|
||||
args: StatusRequestArgs,
|
||||
) -> Result<(VideoGenStatusResponse, Option<SoraCredentialSet>), SoraError> {
|
||||
|
||||
let request = StatusRequest {
|
||||
limit: args.limit,
|
||||
before: args.before.map(|t| t.0),
|
||||
};
|
||||
|
||||
let mut new_creds = None;
|
||||
|
||||
if args.credentials.jwt_bearer_token.is_none() {
|
||||
info!("JWT not set. Upgrading credentials...");
|
||||
let mut updated_creds = args.credentials.clone();
|
||||
if let Err(err) = maybe_upgrade_or_renew_session(&mut updated_creds).await {
|
||||
warn!("Failed to upgrade or renew session: {:?}", err);
|
||||
}
|
||||
new_creds = Some(updated_creds);
|
||||
}
|
||||
|
||||
let upgraded_creds = new_creds.as_ref()
|
||||
.unwrap_or_else(|| &args.credentials);
|
||||
|
||||
let result = get_image_gen_status(&request, &upgraded_creds).await;
|
||||
|
||||
let err = match result {
|
||||
Ok(response) => return Ok((response, new_creds)),
|
||||
Err(err) => err,
|
||||
};
|
||||
|
||||
warn!("Task status polling failed: {:?}", err);
|
||||
|
||||
// TODO(bt,2025-05-28): This should only be done if the credentials were known to be bad
|
||||
let new_creds = maybe_refresh_credentials_on_sora_error(&args.credentials, err).await?;
|
||||
|
||||
// TODO(bt,2025-05-28): This should only be done if the credentials were actually refreshed.
|
||||
info!("Retrying task polling with new credentials...");
|
||||
|
||||
let result = get_image_gen_status(&request, &upgraded_creds).await?;
|
||||
|
||||
Ok((result, Some(new_creds)))
|
||||
}
|
||||
@@ -14,6 +14,7 @@ const EXPIRATION_DEADLINE : TimeDelta = TimeDelta::hours(12);
|
||||
/// Call this at session startup to generate the tokens for the first time.
|
||||
/// Call this periodically to refresh the JWT as it nears its expiration date.
|
||||
/// This will not refresh the sentinel token once it's been generated - we can use other flows to accomplish that.
|
||||
#[deprecated(note="The sentinel appears to be going away. Use `maybe_renew_session_jwt` instead for newer '2.0' code.")]
|
||||
pub async fn maybe_upgrade_or_renew_session(sora_credentials: &mut SoraCredentialSet) -> Result<bool, SoraError> {
|
||||
let mut refresh_jwt = !sora_credentials.jwt_bearer_token.is_some();
|
||||
let refresh_sentinel = !sora_credentials.sora_sentinel.is_some();
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
pub mod generate_sora2_video;
|
||||
pub mod image_remix_with_session_auto_renew;
|
||||
pub mod image_upload_from_bytes_with_session_auto_renew;
|
||||
pub mod image_upload_from_file_with_session_auto_renew;
|
||||
pub mod list_sora_task_status_with_session_auto_renew;
|
||||
pub mod list_classic_sora_tasks_with_session_auto_renew;
|
||||
pub mod list_sora2_drafts;
|
||||
pub mod maybe_refresh_credentials_on_sora_error;
|
||||
pub mod maybe_upgrade_or_renew_session;
|
||||
pub mod simple_image_gen_with_session_auto_renew;
|
||||
pub mod utils;
|
||||
pub mod wait_for_image_gen_status;
|
||||
@@ -0,0 +1,48 @@
|
||||
use crate::creds::sora_credential_set::SoraCredentialSet;
|
||||
use crate::creds::sora_jwt_bearer_token::SoraJwtBearerToken;
|
||||
use crate::error::sora_error::SoraError;
|
||||
use crate::recipes::maybe_upgrade_or_renew_session::maybe_upgrade_or_renew_session;
|
||||
use crate::requests::auth_bearer::generate_bearer_jwt_with_cookie::generate_bearer_jwt_with_cookie;
|
||||
use chrono::{TimeDelta, Utc};
|
||||
use log::{info, warn};
|
||||
use std::ops::Sub;
|
||||
|
||||
const EXPIRATION_DEADLINE : TimeDelta = TimeDelta::hours(12);
|
||||
|
||||
/// Returns a new set of credentials if they were updated, or None if no update was needed.
|
||||
pub async fn maybe_renew_session_jwt(creds: &SoraCredentialSet) -> Result<Option<SoraCredentialSet>, SoraError> {
|
||||
let mut refresh_jwt = false;
|
||||
|
||||
match creds.jwt_bearer_token.as_ref() {
|
||||
None => {
|
||||
info!("JWT not set.");
|
||||
refresh_jwt = true;
|
||||
}
|
||||
Some(jwt) => {
|
||||
let now = Utc::now();
|
||||
let refresh_deadline = now.sub(EXPIRATION_DEADLINE);
|
||||
if jwt.jwt_claims().expiration.lt(&now) {
|
||||
info!("JWT expiration time has elapsed.");
|
||||
refresh_jwt = true;
|
||||
} else if jwt.jwt_claims().expiration.lt(&refresh_deadline) {
|
||||
info!("JWT expiration time is under the expiration deadline.");
|
||||
refresh_jwt = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !refresh_jwt {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
info!("Refreshing JWT...");
|
||||
let token = generate_bearer_jwt_with_cookie(creds.cookies.as_str()).await?;
|
||||
|
||||
info!("Parsing JWT bearer token...");
|
||||
let token = SoraJwtBearerToken::new(token)?;
|
||||
|
||||
let mut updated_creds = creds.clone();
|
||||
updated_creds.jwt_bearer_token = Some(token);
|
||||
|
||||
Ok(Some(updated_creds))
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
pub mod maybe_renew_session_jwt;
|
||||
@@ -1,5 +1,5 @@
|
||||
use crate::creds::sora_credential_set::SoraCredentialSet;
|
||||
use crate::requests::job_status::sora_job_status::{get_image_gen_status, StatusRequest, TaskResponse, TaskStatus, VideoGenStatusResponse};
|
||||
use crate::requests::deprecated::job_status::sora_job_status::{get_image_gen_status, StatusRequest, TaskResponse, TaskStatus, VideoGenStatusResponse};
|
||||
use errors::AnyhowResult;
|
||||
|
||||
pub async fn wait_for_image_gen_status(task_id: &String, creds: &SoraCredentialSet, retry_limit: Option<u32>) -> AnyhowResult<TaskResponse> {
|
||||
|
||||
-1
@@ -2,7 +2,6 @@ use super::request::GenerateSentinelRefreshRequest;
|
||||
use crate::constants::user_agent::USER_AGENT;
|
||||
use crate::error::sora_error::SoraError;
|
||||
use crate::error::sora_generic_api_error::SoraGenericApiError;
|
||||
use crate::requests::list_media::list_media::ListMediaResponse;
|
||||
use crate::utils_internal::classify_general_http_error::classify_general_http_error;
|
||||
use errors::AnyhowResult;
|
||||
use idempotency::uuid::generate_random_uuid;
|
||||
|
||||
@@ -5,6 +5,28 @@ use serde_derive::{Deserialize, Serialize};
|
||||
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Serialize, Deserialize)]
|
||||
pub struct TaskId(pub String);
|
||||
|
||||
impl TaskId {
|
||||
pub fn from_string(id: String) -> Self {
|
||||
Self(id)
|
||||
}
|
||||
|
||||
pub fn from_str(id: &str) -> Self {
|
||||
Self(id.to_string())
|
||||
}
|
||||
|
||||
pub fn new(id: String) -> Self {
|
||||
Self(id)
|
||||
}
|
||||
|
||||
pub fn as_str(&self) -> &str {
|
||||
&self.0
|
||||
}
|
||||
|
||||
pub fn to_string(&self) -> String {
|
||||
self.0.to_string()
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for TaskId {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
std::fmt::Display::fmt(&self.0, f)
|
||||
|
||||
+3
-1
@@ -15,6 +15,8 @@ use std::{fs::File, io::Write, path::Path};
|
||||
use url::Url;
|
||||
use wreq::Client;
|
||||
|
||||
// NB(2025-10-02): The new URL is:
|
||||
// https://sora.chatgpt.com/backend/v2/list_tasks?limit=20
|
||||
const SORA_STATUS_URL: &str = "https://sora.com/backend/video_gen";
|
||||
|
||||
|
||||
@@ -298,7 +300,7 @@ pub async fn save_generations_to_dir(generations: &[Generation], dir: &str) -> A
|
||||
mod tests {
|
||||
use crate::creds::sora_credential_builder::SoraCredentialBuilder;
|
||||
use crate::recipes::wait_for_image_gen_status::wait_for_image_gen_status;
|
||||
use crate::requests::job_status::sora_job_status::{get_image_gen_status, save_generations_to_dir, StatusRequest, VideoGenStatusResponse};
|
||||
use crate::requests::deprecated::job_status::sora_job_status::{get_image_gen_status, save_generations_to_dir, StatusRequest, VideoGenStatusResponse};
|
||||
use errors::AnyhowResult;
|
||||
use std::fs::read_to_string;
|
||||
use testing::test_file_path::test_file_path;
|
||||
+1
-1
@@ -47,7 +47,7 @@ pub async fn unimplemented_sora_job_status(
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::creds::sora_credential_builder::SoraCredentialBuilder;
|
||||
use crate::requests::job_status::unimplemented_sora_job_status::unimplemented_sora_job_status;
|
||||
use crate::requests::deprecated::job_status::unimplemented_sora_job_status::unimplemented_sora_job_status;
|
||||
use errors::AnyhowResult;
|
||||
use std::fs::read_to_string;
|
||||
use testing::test_file_path::test_file_path;
|
||||
+2
-2
@@ -2,14 +2,14 @@ use crate::creds::sora_credential_set::SoraCredentialSet;
|
||||
use crate::error::sora_client_error::SoraClientError;
|
||||
use crate::error::sora_error::SoraError;
|
||||
use crate::error::sora_generic_api_error::SoraGenericApiError;
|
||||
use crate::requests::upload::upload_media_http_request::SoraMediaUploadResponse;
|
||||
use crate::utils_internal::classify_general_http_error::classify_general_http_error;
|
||||
use log::{error, info};
|
||||
use once_cell::sync::Lazy;
|
||||
use serde_derive::Deserialize;
|
||||
use wreq::Client;
|
||||
|
||||
const SORA_MEDIA_LIST_URL: &str = "https://sora.chatgpt.com/backend/video_gen?limit=50";
|
||||
//const SORA_MEDIA_LIST_URL: &str = "https://sora.chatgpt.com/backend/video_gen?limit=50";
|
||||
const SORA_MEDIA_LIST_URL : &str = "https://sora.chatgpt.com/backend/v2/list_tasks?limit=20";
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct ListMediaResponse {
|
||||
@@ -0,0 +1,2 @@
|
||||
pub mod job_status;
|
||||
pub mod list_media;
|
||||
+175
@@ -0,0 +1,175 @@
|
||||
use crate::constants::user_agent::USER_AGENT;
|
||||
use crate::creds::sora_credential_set::SoraCredentialSet;
|
||||
use crate::error::sora_client_error::SoraClientError;
|
||||
use crate::error::sora_error::SoraError;
|
||||
use crate::error::sora_generic_api_error::SoraGenericApiError;
|
||||
use crate::requests::common::task_id::TaskId;
|
||||
use crate::requests::generate_sora2_video::http_request::{HttpCreateRequest, InpaintItem};
|
||||
use crate::requests::generate_sora2_video::http_response::HttpCreateResponse;
|
||||
use crate::requests::image_gen::image_gen_http_request::{RawSoraImageGenRequest, RawSoraResponse};
|
||||
use log::error;
|
||||
use std::time::Duration;
|
||||
use wreq::header::{ACCEPT, ACCEPT_LANGUAGE, AUTHORIZATION, CONTENT_TYPE, COOKIE, ORIGIN, REFERER};
|
||||
use wreq::Client;
|
||||
|
||||
const GENERATE_SORA_2_VIDEO_URL: &str = "https://sora.chatgpt.com/backend/nf/create";
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct GenerateSora2VideoArgs<'a> {
|
||||
pub prompt: &'a str,
|
||||
pub credentials: &'a SoraCredentialSet,
|
||||
pub request_timeout: Option<Duration>,
|
||||
pub orientation: Orientation,
|
||||
pub image_reference_media_ids: Option<&'a Vec<String>>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub enum Orientation {
|
||||
Portrait,
|
||||
Landscape,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct GenerateSora2VideoResponse {
|
||||
pub task_id: TaskId,
|
||||
}
|
||||
|
||||
pub (crate) async fn generate_sora2_video(
|
||||
args: GenerateSora2VideoArgs<'_>,
|
||||
) -> Result<GenerateSora2VideoResponse, SoraError> {
|
||||
|
||||
let client = Client::new();
|
||||
|
||||
let authorization_header = args.credentials.jwt_bearer_token.as_ref()
|
||||
.ok_or(SoraClientError::NoBearerTokenForRequest)?
|
||||
.to_authorization_header_value();
|
||||
|
||||
//let sentinel = args.credentials.sora_sentinel.as_ref()
|
||||
// .map(|sentinel| sentinel.get_sentinel().to_string())
|
||||
// .ok_or(SoraClientError::NoSentinelTokenForRequest)?;
|
||||
|
||||
let cookie = args.credentials.cookies.to_string();
|
||||
|
||||
// TODO: Make the sec-* headers match the user agent and platform
|
||||
// TODO: No sentinel?
|
||||
// TODO: device id
|
||||
//-H 'oai-device-id: 7c216860-5b73-4e63-983f-142dbcae1809' \
|
||||
let mut http_request = client.post(GENERATE_SORA_2_VIDEO_URL)
|
||||
//.header("OpenAI-Sentinel-Token", &sentinel);
|
||||
.header(ACCEPT, "*/*")
|
||||
.header("priority", "u=1, i")
|
||||
.header(REFERER, "https://sora.chatgpt.com/explore")
|
||||
.header(ORIGIN, "https://sora.chatgpt.com")
|
||||
.header(ACCEPT_LANGUAGE, "en-US,en;q=0.9")
|
||||
.header(wreq::header::USER_AGENT, USER_AGENT)
|
||||
.header(COOKIE, &cookie)
|
||||
.header(AUTHORIZATION, &authorization_header)
|
||||
.header(CONTENT_TYPE, "application/json")
|
||||
.header("sec-ch-ua", "\"Chromium\";v=\"140\", \"Not=A?Brand\";v=\"24\", \"Google Chrome\";v=\"140\"")
|
||||
.header("sec-ch-ua-mobile", "?0")
|
||||
.header("sec-ch-ua-platform", "macOS")
|
||||
.header("sec-fetch-dest", "empty")
|
||||
.header("sec-fetch-mode", "cors")
|
||||
.header("sec-fetch-site", "same-origin");
|
||||
|
||||
if let Some(timeout) = args.request_timeout {
|
||||
http_request = http_request.timeout(timeout);
|
||||
}
|
||||
|
||||
let orientation = match args.orientation {
|
||||
Orientation::Portrait => "portrait",
|
||||
Orientation::Landscape => "landscape",
|
||||
};
|
||||
|
||||
let inpaint_items;
|
||||
|
||||
if let Some(media_ids) = args.image_reference_media_ids {
|
||||
inpaint_items = media_ids.iter()
|
||||
.map(|item_id| InpaintItem {
|
||||
kind: "upload".to_string(),
|
||||
upload_id: item_id.to_string(),
|
||||
})
|
||||
.collect();
|
||||
} else {
|
||||
inpaint_items = Vec::new();
|
||||
}
|
||||
|
||||
let request_body = HttpCreateRequest {
|
||||
kind: "video".to_string(),
|
||||
prompt: args.prompt.to_string(),
|
||||
title: None,
|
||||
orientation: orientation.to_string(),
|
||||
size: "small".to_string(),
|
||||
n_frames: 300,
|
||||
inpaint_items,
|
||||
cameo_ids: None,
|
||||
cameo_replacements: None,
|
||||
model: "sy_8".to_string(),
|
||||
style_id: None,
|
||||
audio_caption: None,
|
||||
audio_transcript: None,
|
||||
video_caption: None,
|
||||
storyboard_id: None,
|
||||
};
|
||||
|
||||
let http_request = http_request.json(&request_body).build()
|
||||
.map_err(|err| {
|
||||
error!("Error building Sora image generation HTTP request: {:?}", err);
|
||||
SoraClientError::WreqClientError(err)
|
||||
})?;
|
||||
|
||||
let response = client.execute(http_request)
|
||||
.await
|
||||
.map_err(|err| {
|
||||
error!("Error during Sora image generation request: {:?}", err);
|
||||
SoraClientError::WreqClientError(err)
|
||||
})?;
|
||||
|
||||
let status = response.status();
|
||||
|
||||
let response_body = &response.text().await
|
||||
.map_err(|err| {
|
||||
error!("Error reading Sora image generation response body: {:?}", err);
|
||||
SoraClientError::WreqClientError(err)
|
||||
})?;
|
||||
|
||||
if !status.is_success() {
|
||||
error!("Sora image generation request returned an error (code {}) : {:?}", status.as_u16(), response_body);
|
||||
|
||||
return Err(SoraGenericApiError::UncategorizedBadResponseWithStatusAndBody {
|
||||
status_code: status,
|
||||
body: response_body.to_string(),
|
||||
}.into());
|
||||
}
|
||||
|
||||
let response : HttpCreateResponse = serde_json::from_str(response_body)
|
||||
.map_err(|err| SoraGenericApiError::SerdeResponseParseErrorWithBody(err, response_body.to_string()))?;
|
||||
|
||||
Ok(GenerateSora2VideoResponse {
|
||||
task_id: response.id,
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::requests::generate_sora2_video::generate_sora2_video::{generate_sora2_video, GenerateSora2VideoArgs, Orientation};
|
||||
use crate::test_utils::get_test_credentials::get_test_credentials;
|
||||
use errors::AnyhowResult;
|
||||
|
||||
#[ignore] // You can manually run "ignore" tests in the IDE, but they won't run in CI.
|
||||
#[tokio::test]
|
||||
pub async fn manual_test() -> AnyhowResult<()> {
|
||||
let creds = get_test_credentials()?;
|
||||
let request = GenerateSora2VideoArgs {
|
||||
prompt: "A cute corgi wearing glasses, sitting on a picnic blanket and reading a book, digital art",
|
||||
credentials: &creds,
|
||||
request_timeout: None,
|
||||
orientation: Orientation::Landscape,
|
||||
image_reference_media_ids: None,
|
||||
};
|
||||
let result = generate_sora2_video(request).await?;
|
||||
println!("result: {:#?}", result);
|
||||
assert_eq!(1, 2);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
+121
File diff suppressed because one or more lines are too long
+60
@@ -0,0 +1,60 @@
|
||||
use serde_derive::Serialize;
|
||||
|
||||
#[derive(Serialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub (super) struct HttpCreateRequest {
|
||||
// eg. "video"
|
||||
pub kind: String,
|
||||
|
||||
// User prompt
|
||||
pub prompt: String,
|
||||
|
||||
// NB: sent as null
|
||||
pub title: Option<String>,
|
||||
|
||||
// eg. "portrait"
|
||||
pub orientation: String,
|
||||
|
||||
// eg. "small"
|
||||
pub size: String,
|
||||
|
||||
// eg. 300
|
||||
pub n_frames: u16,
|
||||
|
||||
// NB: sent as empty array
|
||||
pub inpaint_items: Vec<InpaintItem>,
|
||||
|
||||
// NB: Sent as null
|
||||
pub cameo_ids: Option<String>,
|
||||
|
||||
// NB: Sent as null
|
||||
pub cameo_replacements: Option<String>,
|
||||
|
||||
// eg. "sy_8"
|
||||
pub model: String,
|
||||
|
||||
// NB: Sent as null
|
||||
pub style_id: Option<String>,
|
||||
|
||||
// NB: Sent as null
|
||||
pub audio_caption: Option<String>,
|
||||
|
||||
// NB: Sent as null
|
||||
pub audio_transcript: Option<String>,
|
||||
|
||||
// NB: Sent as null
|
||||
pub video_caption: Option<String>,
|
||||
|
||||
// NB: Sent as null
|
||||
pub storyboard_id: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub (super) struct InpaintItem {
|
||||
// eg. "upload"
|
||||
pub kind: String,
|
||||
|
||||
// eg. "media_01abc..."
|
||||
pub upload_id: String,
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
use crate::requests::common::task_id::TaskId;
|
||||
use serde_derive::Deserialize;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub (super) struct HttpCreateResponse {
|
||||
pub id: TaskId,
|
||||
// NB: Ignoring other fields, like "priority".
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
pub mod generate_sora2_video;
|
||||
mod http_request;
|
||||
mod http_response;
|
||||
+163
@@ -0,0 +1,163 @@
|
||||
use crate::constants::user_agent::USER_AGENT;
|
||||
use crate::creds::sora_credential_set::SoraCredentialSet;
|
||||
use crate::error::sora_client_error::SoraClientError;
|
||||
use crate::error::sora_error::SoraError;
|
||||
use crate::error::sora_generic_api_error::SoraGenericApiError;
|
||||
use crate::requests::common::task_id::TaskId;
|
||||
use crate::requests::deprecated::job_status::sora_job_status::{Generation, StatusRequest, VideoGenStatusResponse};
|
||||
use crate::utils_internal::classify_general_http_status_code_and_body::classify_general_http_status_code_and_body;
|
||||
use log::{debug, error, warn};
|
||||
use serde_derive::Deserialize;
|
||||
use url::Url;
|
||||
use wreq::Client;
|
||||
|
||||
const SORA_LIST_CLASSIC_TASKS_URL: &str = "https://sora.chatgpt.com/backend/v2/list_tasks?limit=20";
|
||||
|
||||
|
||||
/// NB: We omit fields we're not using to prevent breakage.
|
||||
#[derive(Deserialize, Clone, Debug)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub struct ListTasksResponse {
|
||||
pub task_responses: Vec<PartialTaskResponse>,
|
||||
pub last_id: Option<String>,
|
||||
pub has_more: bool,
|
||||
}
|
||||
|
||||
/// NB: We omit fields we're not using to prevent breakage.
|
||||
#[derive(Deserialize, Clone, Debug)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub struct PartialTaskResponse {
|
||||
pub id: TaskId,
|
||||
pub status: TaskStatus,
|
||||
|
||||
/// Text prompt used to generate the media.
|
||||
pub prompt: Option<String>,
|
||||
|
||||
pub generations: Vec<PartialGeneration>,
|
||||
|
||||
//pub user: String,
|
||||
//pub created_at: String,
|
||||
//pub status: String, // eg. "succeeded"
|
||||
//pub progress_pct: Option<f64>,
|
||||
//pub progress_pos_in_queue: Option<i32>,
|
||||
//pub encodings: Vec<Encoding> // NB: These are webp images.
|
||||
// ... lots of other fields ...
|
||||
}
|
||||
|
||||
|
||||
#[derive(Deserialize, Clone, Debug, Eq, PartialEq)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum TaskStatus {
|
||||
Queued,
|
||||
Running,
|
||||
Succeeded,
|
||||
Failed,
|
||||
#[serde(untagged)]
|
||||
Unknown(String),
|
||||
}
|
||||
|
||||
/// NB: We omit fields we're not using to prevent breakage.
|
||||
#[derive(Deserialize, Clone, Debug)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub struct PartialGeneration {
|
||||
// A unique ID for the generation, eg. "gen_01abc..."
|
||||
pub id: String,
|
||||
pub url: String,
|
||||
|
||||
//pub id: String,
|
||||
//pub task_id: TaskId,
|
||||
//pub created_at: String,
|
||||
//pub deleted_at: Option<String>,
|
||||
//pub url: String,
|
||||
//pub seed: Option<i64>,
|
||||
//pub can_download: Option<bool>,
|
||||
// ... lots of other fields ...
|
||||
}
|
||||
|
||||
pub async fn list_classic_tasks(credentials: &SoraCredentialSet) -> Result<ListTasksResponse, SoraError> {
|
||||
|
||||
let bearer_header = match credentials.jwt_bearer_token.as_ref() {
|
||||
Some(bearer) => bearer.to_authorization_header_value(),
|
||||
None => {
|
||||
warn!("No JWT bearer token in client - cannot fetch image gen status!");
|
||||
return Err(SoraClientError::NoBearerTokenForRequest.into());
|
||||
},
|
||||
};
|
||||
|
||||
let client = Client::new();
|
||||
|
||||
let mut url = Url::parse(SORA_LIST_CLASSIC_TASKS_URL)
|
||||
.map_err(|err| {
|
||||
SoraClientError::UrlParseError(err)
|
||||
})?;
|
||||
|
||||
|
||||
// TODO: Future pagination.
|
||||
// Add query parameters
|
||||
//if let Some(limit) = status_request.limit {
|
||||
// url.query_pairs_mut().append_pair("limit", &limit.to_string());
|
||||
//}
|
||||
//if let Some(before) = &status_request.before {
|
||||
// url.query_pairs_mut().append_pair("before", &before);
|
||||
//}
|
||||
|
||||
let http_request = client.get(url.as_str())
|
||||
.header("User-Agent", USER_AGENT)
|
||||
.header("Cookie", credentials.cookies.as_str())
|
||||
.header("Authorization", bearer_header)
|
||||
.header("Content-Type", "application/json");
|
||||
|
||||
let http_request = http_request.build()
|
||||
.map_err(|err| {
|
||||
SoraClientError::WreqClientError(err)
|
||||
})?;
|
||||
|
||||
let response = client.execute(http_request)
|
||||
.await
|
||||
.map_err(|err| {
|
||||
error!("Client failed to fetch sora task list: {:?}", err);
|
||||
SoraGenericApiError::WreqError(err)
|
||||
})?;
|
||||
|
||||
let status = response.status();
|
||||
|
||||
let response_body = &response.text()
|
||||
.await
|
||||
.map_err(|err| {
|
||||
error!("Client failed to read sora task list: {:?}", err);
|
||||
SoraGenericApiError::WreqError(err)
|
||||
})?;
|
||||
|
||||
debug!("response_body: {}", response_body);
|
||||
|
||||
if !status.is_success() {
|
||||
error!("The sora task list request failed; status = {:?} ; response body = {}", status, response_body);
|
||||
let error = classify_general_http_status_code_and_body(status, &response_body).await;
|
||||
return Err(error);
|
||||
}
|
||||
|
||||
let response = serde_json::from_str::<ListTasksResponse>(response_body)
|
||||
.map_err(|err| {
|
||||
error!("Failed to parse status response: {:?}", err);
|
||||
SoraGenericApiError::SerdeResponseParseErrorWithBody(err, response_body.to_string())
|
||||
})?;
|
||||
|
||||
Ok(response)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::requests::list_classic_tasks::list_classic_tasks::list_classic_tasks;
|
||||
use crate::test_utils::get_test_credentials::get_test_credentials;
|
||||
use errors::AnyhowResult;
|
||||
|
||||
#[ignore] // You can manually run "ignore" tests in the IDE, but they won't run in CI.
|
||||
#[tokio::test]
|
||||
pub async fn manual_test() -> AnyhowResult<()> {
|
||||
let creds = get_test_credentials()?;
|
||||
let result = list_classic_tasks(&creds).await?;
|
||||
println!("result: {:#?}", result);
|
||||
assert_eq!(1, 2);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
+578
@@ -0,0 +1,578 @@
|
||||
{
|
||||
"task_responses": [
|
||||
{
|
||||
"id": "task_01k6kj8hjtezvvknx8fbtjf52q",
|
||||
"user": "user-hQ5fc1AV7vNcQLk2yfEFEUbo",
|
||||
"created_at": "2025-10-02T22:56:36.498766Z",
|
||||
"status": "succeeded",
|
||||
"progress_pct": 0.6719999999999999,
|
||||
"progress_pos_in_queue": null,
|
||||
"estimated_queue_wait_time": null,
|
||||
"queue_status_message": null,
|
||||
"priority": 1,
|
||||
"type": "image_gen",
|
||||
"prompt": "A red apple, photorealistic",
|
||||
"n_variants": 1,
|
||||
"n_frames": 1,
|
||||
"height": 480,
|
||||
"width": 854,
|
||||
"model": null,
|
||||
"operation": "remix",
|
||||
"inpaint_items": [
|
||||
{
|
||||
"crop_bounds": null,
|
||||
"type": "image",
|
||||
"preset_id": null,
|
||||
"generation_id": null,
|
||||
"upload_media_id": "media_01k6kj8fmnfvgvezw42kpps72c",
|
||||
"uploaded_file_id": null,
|
||||
"cameo_file_id": null,
|
||||
"frame_index": 0,
|
||||
"source_start_frame": 0,
|
||||
"source_end_frame": 0
|
||||
}
|
||||
],
|
||||
"preset_id": null,
|
||||
"caption": null,
|
||||
"actions": null,
|
||||
"interpolation": null,
|
||||
"sdedit": null,
|
||||
"remix_config": null,
|
||||
"quality": "high",
|
||||
"size": null,
|
||||
"generations": [
|
||||
{
|
||||
"id": "gen_01k6kj8k2hecrbtak79jy42a84",
|
||||
"task_id": "task_01k6kj8hjtezvvknx8fbtjf52q",
|
||||
"created_at": "2025-10-02T22:58:16.317475Z",
|
||||
"deleted_at": null,
|
||||
"url": "https://videos.openai.com/vg-assets/assets%2Ftask_01k6kj8hjtezvvknx8fbtjf52q%2F1759445854_src_0.png?st=2025-10-03T00%3A31%3A34Z&se=2025-10-09T01%3A31%3A34Z&sks=b&skt=2025-10-03T00%3A31%3A34Z&ske=2025-10-09T01%3A31%3A34Z&sktid=a48cca56-e6da-484e-a814-9c849652bcb3&skoid=aa5ddad1-c91a-4f0a-9aca-e20682cc8969&skv=2019-02-02&sv=2018-11-09&sr=b&sp=r&spr=https%2Chttp&sig=FOW7H2ah4Zxf60v%2BBezoJRNP2dlpo%2Bvg0NN4w1Su3Ak%3D&az=oaivgprodscus",
|
||||
"seed": 943768322,
|
||||
"can_download": true,
|
||||
"download_status": "ready",
|
||||
"is_favorite": false,
|
||||
"is_liked": false,
|
||||
"is_public": false,
|
||||
"is_archived": false,
|
||||
"is_featured": null,
|
||||
"featured_countries": [],
|
||||
"has_feedback": false,
|
||||
"like_count": 0,
|
||||
"num_direct_children": 0,
|
||||
"cloudflare_metadata": null,
|
||||
"cf_thumbnail_url": null,
|
||||
"encodings": {
|
||||
"source": {
|
||||
"path": "https://videos.openai.com/vg-assets/assets%2Ftask_01k6kj8hjtezvvknx8fbtjf52q%2F1759445854_img_0.webp?st=2025-10-03T00%3A31%3A34Z&se=2025-10-09T01%3A31%3A34Z&sks=b&skt=2025-10-03T00%3A31%3A34Z&ske=2025-10-09T01%3A31%3A34Z&sktid=a48cca56-e6da-484e-a814-9c849652bcb3&skoid=aa5ddad1-c91a-4f0a-9aca-e20682cc8969&skv=2019-02-02&sv=2018-11-09&sr=b&sp=r&spr=https%2Chttp&sig=mFtRgicMPOBagoDM%2BQ3mjfjoR2z08EfCWZNGYk7iPtk%3D&az=oaivgprodscus",
|
||||
"size": null,
|
||||
"width": null,
|
||||
"height": null,
|
||||
"duration_secs": null,
|
||||
"ssim": null,
|
||||
"az_path": null
|
||||
},
|
||||
"source_c2pa": null,
|
||||
"md": null,
|
||||
"ld": null,
|
||||
"thumbnail": {
|
||||
"path": "https://videos.openai.com/vg-assets/assets%2Ftask_01k6kj8hjtezvvknx8fbtjf52q%2F1759445854_img_0_thumb.webp?st=2025-10-03T00%3A31%3A34Z&se=2025-10-09T01%3A31%3A34Z&sks=b&skt=2025-10-03T00%3A31%3A34Z&ske=2025-10-09T01%3A31%3A34Z&sktid=a48cca56-e6da-484e-a814-9c849652bcb3&skoid=aa5ddad1-c91a-4f0a-9aca-e20682cc8969&skv=2019-02-02&sv=2018-11-09&sr=b&sp=r&spr=https%2Chttp&sig=tEQWUbyX9nZYa%2Fm6Xk98e2gQSdv%2F3wNEstvdwOnAHFA%3D&az=oaivgprodscus",
|
||||
"size": null,
|
||||
"az_path": null
|
||||
},
|
||||
"spritesheet": null,
|
||||
"gif": null,
|
||||
"mp3": null,
|
||||
"source_wm": null,
|
||||
"md_wm": null,
|
||||
"ld_wm": null,
|
||||
"thumbnail_wm": null,
|
||||
"spritesheet_wm": null,
|
||||
"gif_wm": null
|
||||
},
|
||||
"width": 1536,
|
||||
"height": 1024,
|
||||
"n_frames": 1,
|
||||
"prompt": "A red apple, photorealistic",
|
||||
"title": "Red Apple Drawing",
|
||||
"actions": null,
|
||||
"inpaint_items": [
|
||||
{
|
||||
"crop_bounds": null,
|
||||
"type": "image",
|
||||
"preset_id": null,
|
||||
"generation_id": null,
|
||||
"upload_media_id": "media_01k6kj8fmnfvgvezw42kpps72c",
|
||||
"uploaded_file_id": null,
|
||||
"cameo_file_id": null,
|
||||
"frame_index": 0,
|
||||
"source_start_frame": 0,
|
||||
"source_end_frame": 0
|
||||
}
|
||||
],
|
||||
"interpolation": null,
|
||||
"sdedit": null,
|
||||
"operation": "remix",
|
||||
"model": null,
|
||||
"preset_id": null,
|
||||
"user": {
|
||||
"id": "user-hQ5fc1AV7vNcQLk2yfEFEUbo",
|
||||
"username": "videodesign"
|
||||
},
|
||||
"moderation_result": {
|
||||
"type": "passed",
|
||||
"results_by_frame_index": {},
|
||||
"code": null,
|
||||
"is_output_rejection": false,
|
||||
"task_id": "task_01k6kj8hjtezvvknx8fbtjf52q"
|
||||
},
|
||||
"paragen_status": null,
|
||||
"task_type": "image_gen",
|
||||
"remix_config": null,
|
||||
"quality": "high"
|
||||
}
|
||||
],
|
||||
"num_unsafe_generations": 0,
|
||||
"title": "Red Apple Drawing",
|
||||
"moderation_result": {
|
||||
"type": "passed",
|
||||
"results_by_frame_index": {},
|
||||
"code": null,
|
||||
"is_output_rejection": false,
|
||||
"task_id": "task_01k6kj8hjtezvvknx8fbtjf52q"
|
||||
},
|
||||
"failure_reason": null,
|
||||
"needs_user_review": false
|
||||
},
|
||||
{
|
||||
"id": "task_01k6kj4jcbeneszh3zpddtfv4x",
|
||||
"user": "user-hQ5fc1AV7vNcQLk2yfEFEUbo",
|
||||
"created_at": "2025-10-02T22:54:26.240186Z",
|
||||
"status": "succeeded",
|
||||
"progress_pct": 0.99,
|
||||
"progress_pos_in_queue": null,
|
||||
"estimated_queue_wait_time": null,
|
||||
"queue_status_message": null,
|
||||
"priority": 1,
|
||||
"type": "image_gen",
|
||||
"prompt": "A red apple, photorealistic",
|
||||
"n_variants": 1,
|
||||
"n_frames": 1,
|
||||
"height": 480,
|
||||
"width": 854,
|
||||
"model": null,
|
||||
"operation": "remix",
|
||||
"inpaint_items": [
|
||||
{
|
||||
"crop_bounds": null,
|
||||
"type": "image",
|
||||
"preset_id": null,
|
||||
"generation_id": null,
|
||||
"upload_media_id": "media_01k6kj4g05edbbdz3xf8qtcpcb",
|
||||
"uploaded_file_id": null,
|
||||
"cameo_file_id": null,
|
||||
"frame_index": 0,
|
||||
"source_start_frame": 0,
|
||||
"source_end_frame": 0
|
||||
}
|
||||
],
|
||||
"preset_id": null,
|
||||
"caption": null,
|
||||
"actions": null,
|
||||
"interpolation": null,
|
||||
"sdedit": null,
|
||||
"remix_config": null,
|
||||
"quality": "high",
|
||||
"size": null,
|
||||
"generations": [
|
||||
{
|
||||
"id": "gen_01k6kj4kzhed4sfw1z37p4gde1",
|
||||
"task_id": "task_01k6kj4jcbeneszh3zpddtfv4x",
|
||||
"created_at": "2025-10-02T22:56:24.680138Z",
|
||||
"deleted_at": null,
|
||||
"url": "https://videos.openai.com/vg-assets/assets%2Ftask_01k6kj4jcbeneszh3zpddtfv4x%2F1759445784_src_0.png?st=2025-10-03T00%3A31%3A34Z&se=2025-10-09T01%3A31%3A34Z&sks=b&skt=2025-10-03T00%3A31%3A34Z&ske=2025-10-09T01%3A31%3A34Z&sktid=a48cca56-e6da-484e-a814-9c849652bcb3&skoid=aa5ddad1-c91a-4f0a-9aca-e20682cc8969&skv=2019-02-02&sv=2018-11-09&sr=b&sp=r&spr=https%2Chttp&sig=vckhBG1HfUriqlnNy40r4VSRV%2FHUY0Wym7pJbh%2Fp6Dw%3D&az=oaivgprodscus",
|
||||
"seed": 1780261786,
|
||||
"can_download": true,
|
||||
"download_status": "ready",
|
||||
"is_favorite": false,
|
||||
"is_liked": false,
|
||||
"is_public": false,
|
||||
"is_archived": false,
|
||||
"is_featured": null,
|
||||
"featured_countries": [],
|
||||
"has_feedback": false,
|
||||
"like_count": 0,
|
||||
"num_direct_children": 0,
|
||||
"cloudflare_metadata": null,
|
||||
"cf_thumbnail_url": null,
|
||||
"encodings": {
|
||||
"source": {
|
||||
"path": "https://videos.openai.com/vg-assets/assets%2Ftask_01k6kj4jcbeneszh3zpddtfv4x%2F1759445784_img_0.webp?st=2025-10-03T00%3A31%3A34Z&se=2025-10-09T01%3A31%3A34Z&sks=b&skt=2025-10-03T00%3A31%3A34Z&ske=2025-10-09T01%3A31%3A34Z&sktid=a48cca56-e6da-484e-a814-9c849652bcb3&skoid=aa5ddad1-c91a-4f0a-9aca-e20682cc8969&skv=2019-02-02&sv=2018-11-09&sr=b&sp=r&spr=https%2Chttp&sig=LsT9efFRd9EUtYpTpRWkodCnqLRmEDyx1olQpdrKubI%3D&az=oaivgprodscus",
|
||||
"size": null,
|
||||
"width": null,
|
||||
"height": null,
|
||||
"duration_secs": null,
|
||||
"ssim": null,
|
||||
"az_path": null
|
||||
},
|
||||
"source_c2pa": null,
|
||||
"md": null,
|
||||
"ld": null,
|
||||
"thumbnail": {
|
||||
"path": "https://videos.openai.com/vg-assets/assets%2Ftask_01k6kj4jcbeneszh3zpddtfv4x%2F1759445784_img_0_thumb.webp?st=2025-10-03T00%3A31%3A34Z&se=2025-10-09T01%3A31%3A34Z&sks=b&skt=2025-10-03T00%3A31%3A34Z&ske=2025-10-09T01%3A31%3A34Z&sktid=a48cca56-e6da-484e-a814-9c849652bcb3&skoid=aa5ddad1-c91a-4f0a-9aca-e20682cc8969&skv=2019-02-02&sv=2018-11-09&sr=b&sp=r&spr=https%2Chttp&sig=eqqFQnFLYlpYC9FtZCa5uPwXuwPByTPasocenvd0iOE%3D&az=oaivgprodscus",
|
||||
"size": null,
|
||||
"az_path": null
|
||||
},
|
||||
"spritesheet": null,
|
||||
"gif": null,
|
||||
"mp3": null,
|
||||
"source_wm": null,
|
||||
"md_wm": null,
|
||||
"ld_wm": null,
|
||||
"thumbnail_wm": null,
|
||||
"spritesheet_wm": null,
|
||||
"gif_wm": null
|
||||
},
|
||||
"width": 1536,
|
||||
"height": 1024,
|
||||
"n_frames": 1,
|
||||
"prompt": "A red apple, photorealistic",
|
||||
"title": "Red Apple Sketch",
|
||||
"actions": null,
|
||||
"inpaint_items": [
|
||||
{
|
||||
"crop_bounds": null,
|
||||
"type": "image",
|
||||
"preset_id": null,
|
||||
"generation_id": null,
|
||||
"upload_media_id": "media_01k6kj4g05edbbdz3xf8qtcpcb",
|
||||
"uploaded_file_id": null,
|
||||
"cameo_file_id": null,
|
||||
"frame_index": 0,
|
||||
"source_start_frame": 0,
|
||||
"source_end_frame": 0
|
||||
}
|
||||
],
|
||||
"interpolation": null,
|
||||
"sdedit": null,
|
||||
"operation": "remix",
|
||||
"model": null,
|
||||
"preset_id": null,
|
||||
"user": {
|
||||
"id": "user-hQ5fc1AV7vNcQLk2yfEFEUbo",
|
||||
"username": "videodesign"
|
||||
},
|
||||
"moderation_result": {
|
||||
"type": "passed",
|
||||
"results_by_frame_index": {},
|
||||
"code": null,
|
||||
"is_output_rejection": false,
|
||||
"task_id": "task_01k6kj4jcbeneszh3zpddtfv4x"
|
||||
},
|
||||
"paragen_status": null,
|
||||
"task_type": "image_gen",
|
||||
"remix_config": null,
|
||||
"quality": "high"
|
||||
}
|
||||
],
|
||||
"num_unsafe_generations": 0,
|
||||
"title": "Red Apple Sketch",
|
||||
"moderation_result": {
|
||||
"type": "passed",
|
||||
"results_by_frame_index": {},
|
||||
"code": null,
|
||||
"is_output_rejection": false,
|
||||
"task_id": "task_01k6kj4jcbeneszh3zpddtfv4x"
|
||||
},
|
||||
"failure_reason": null,
|
||||
"needs_user_review": false
|
||||
},
|
||||
{
|
||||
"id": "task_01k6hq8txzeykrhs0p1a5xp4n4",
|
||||
"user": "user-hQ5fc1AV7vNcQLk2yfEFEUbo",
|
||||
"created_at": "2025-10-02T05:45:40.034438Z",
|
||||
"status": "succeeded",
|
||||
"progress_pct": 0.99,
|
||||
"progress_pos_in_queue": null,
|
||||
"estimated_queue_wait_time": null,
|
||||
"queue_status_message": null,
|
||||
"priority": 1,
|
||||
"type": "image_gen",
|
||||
"prompt": "a mountain range with a river in front, make it super photorealistic, daytime",
|
||||
"n_variants": 1,
|
||||
"n_frames": 1,
|
||||
"height": 480,
|
||||
"width": 854,
|
||||
"model": null,
|
||||
"operation": "remix",
|
||||
"inpaint_items": [
|
||||
{
|
||||
"crop_bounds": null,
|
||||
"type": "image",
|
||||
"preset_id": null,
|
||||
"generation_id": null,
|
||||
"upload_media_id": "media_01k6hq8stqfv8t28v8mjzh2b6t",
|
||||
"uploaded_file_id": null,
|
||||
"cameo_file_id": null,
|
||||
"frame_index": 0,
|
||||
"source_start_frame": 0,
|
||||
"source_end_frame": 0
|
||||
}
|
||||
],
|
||||
"preset_id": null,
|
||||
"caption": null,
|
||||
"actions": null,
|
||||
"interpolation": null,
|
||||
"sdedit": null,
|
||||
"remix_config": null,
|
||||
"quality": "high",
|
||||
"size": null,
|
||||
"generations": [
|
||||
{
|
||||
"id": "gen_01k6hq8w1ke6ws0tz0wwq6rm3t",
|
||||
"task_id": "task_01k6hq8txzeykrhs0p1a5xp4n4",
|
||||
"created_at": "2025-10-02T05:47:48.834759Z",
|
||||
"deleted_at": null,
|
||||
"url": "https://videos.openai.com/vg-assets/assets%2Ftask_01k6hq8txzeykrhs0p1a5xp4n4%2F1759384068_src_0.png?st=2025-10-03T00%3A31%3A34Z&se=2025-10-09T01%3A31%3A34Z&sks=b&skt=2025-10-03T00%3A31%3A34Z&ske=2025-10-09T01%3A31%3A34Z&sktid=a48cca56-e6da-484e-a814-9c849652bcb3&skoid=aa5ddad1-c91a-4f0a-9aca-e20682cc8969&skv=2019-02-02&sv=2018-11-09&sr=b&sp=r&spr=https%2Chttp&sig=Lj%2FKVs5VTd97lxtMDcLbrhdJjtCadkz0xL7ls3s%2BoPw%3D&az=oaivgprodscus",
|
||||
"seed": 1780309707,
|
||||
"can_download": true,
|
||||
"download_status": "ready",
|
||||
"is_favorite": false,
|
||||
"is_liked": false,
|
||||
"is_public": false,
|
||||
"is_archived": false,
|
||||
"is_featured": null,
|
||||
"featured_countries": [],
|
||||
"has_feedback": false,
|
||||
"like_count": 0,
|
||||
"num_direct_children": 0,
|
||||
"cloudflare_metadata": null,
|
||||
"cf_thumbnail_url": null,
|
||||
"encodings": {
|
||||
"source": {
|
||||
"path": "https://videos.openai.com/vg-assets/assets%2Ftask_01k6hq8txzeykrhs0p1a5xp4n4%2F1759384068_img_0.webp?st=2025-10-03T00%3A31%3A34Z&se=2025-10-09T01%3A31%3A34Z&sks=b&skt=2025-10-03T00%3A31%3A34Z&ske=2025-10-09T01%3A31%3A34Z&sktid=a48cca56-e6da-484e-a814-9c849652bcb3&skoid=aa5ddad1-c91a-4f0a-9aca-e20682cc8969&skv=2019-02-02&sv=2018-11-09&sr=b&sp=r&spr=https%2Chttp&sig=K64t43YO0em%2FDyLD%2B68bRYJnUlM%2FwjYewUA75Q0eIlE%3D&az=oaivgprodscus",
|
||||
"size": null,
|
||||
"width": null,
|
||||
"height": null,
|
||||
"duration_secs": null,
|
||||
"ssim": null,
|
||||
"az_path": null
|
||||
},
|
||||
"source_c2pa": null,
|
||||
"md": null,
|
||||
"ld": null,
|
||||
"thumbnail": {
|
||||
"path": "https://videos.openai.com/vg-assets/assets%2Ftask_01k6hq8txzeykrhs0p1a5xp4n4%2F1759384068_img_0_thumb.webp?st=2025-10-03T00%3A31%3A34Z&se=2025-10-09T01%3A31%3A34Z&sks=b&skt=2025-10-03T00%3A31%3A34Z&ske=2025-10-09T01%3A31%3A34Z&sktid=a48cca56-e6da-484e-a814-9c849652bcb3&skoid=aa5ddad1-c91a-4f0a-9aca-e20682cc8969&skv=2019-02-02&sv=2018-11-09&sr=b&sp=r&spr=https%2Chttp&sig=aO%2Fp0356BoyZgkZ8CoQu%2ByHONO8VjXS%2Bvj%2F%2FCxxvo10%3D&az=oaivgprodscus",
|
||||
"size": null,
|
||||
"az_path": null
|
||||
},
|
||||
"spritesheet": null,
|
||||
"gif": null,
|
||||
"mp3": null,
|
||||
"source_wm": null,
|
||||
"md_wm": null,
|
||||
"ld_wm": null,
|
||||
"thumbnail_wm": null,
|
||||
"spritesheet_wm": null,
|
||||
"gif_wm": null
|
||||
},
|
||||
"width": 1536,
|
||||
"height": 1024,
|
||||
"n_frames": 1,
|
||||
"prompt": "a mountain range with a river in front, make it super photorealistic, daytime",
|
||||
"title": "Photorealistic Mountains and River",
|
||||
"actions": null,
|
||||
"inpaint_items": [
|
||||
{
|
||||
"crop_bounds": null,
|
||||
"type": "image",
|
||||
"preset_id": null,
|
||||
"generation_id": null,
|
||||
"upload_media_id": "media_01k6hq8stqfv8t28v8mjzh2b6t",
|
||||
"uploaded_file_id": null,
|
||||
"cameo_file_id": null,
|
||||
"frame_index": 0,
|
||||
"source_start_frame": 0,
|
||||
"source_end_frame": 0
|
||||
}
|
||||
],
|
||||
"interpolation": null,
|
||||
"sdedit": null,
|
||||
"operation": "remix",
|
||||
"model": null,
|
||||
"preset_id": null,
|
||||
"user": {
|
||||
"id": "user-hQ5fc1AV7vNcQLk2yfEFEUbo",
|
||||
"username": "videodesign"
|
||||
},
|
||||
"moderation_result": {
|
||||
"type": "passed",
|
||||
"results_by_frame_index": {},
|
||||
"code": null,
|
||||
"is_output_rejection": false,
|
||||
"task_id": "task_01k6hq8txzeykrhs0p1a5xp4n4"
|
||||
},
|
||||
"paragen_status": null,
|
||||
"task_type": "image_gen",
|
||||
"remix_config": null,
|
||||
"quality": "high"
|
||||
}
|
||||
],
|
||||
"num_unsafe_generations": 0,
|
||||
"title": "Photorealistic Mountains and River",
|
||||
"moderation_result": {
|
||||
"type": "passed",
|
||||
"results_by_frame_index": {},
|
||||
"code": null,
|
||||
"is_output_rejection": false,
|
||||
"task_id": "task_01k6hq8txzeykrhs0p1a5xp4n4"
|
||||
},
|
||||
"failure_reason": null,
|
||||
"needs_user_review": false
|
||||
},
|
||||
{
|
||||
"id": "task_01k6hp7tz7fkabg0vyshdnxzvx",
|
||||
"user": "user-hQ5fc1AV7vNcQLk2yfEFEUbo",
|
||||
"created_at": "2025-10-02T05:27:38.730048Z",
|
||||
"status": "succeeded",
|
||||
"progress_pct": 0.6649999999999999,
|
||||
"progress_pos_in_queue": null,
|
||||
"estimated_queue_wait_time": null,
|
||||
"queue_status_message": null,
|
||||
"priority": 1,
|
||||
"type": "image_gen",
|
||||
"prompt": "A green apple",
|
||||
"n_variants": 1,
|
||||
"n_frames": 1,
|
||||
"height": 480,
|
||||
"width": 854,
|
||||
"model": null,
|
||||
"operation": "remix",
|
||||
"inpaint_items": [
|
||||
{
|
||||
"crop_bounds": null,
|
||||
"type": "image",
|
||||
"preset_id": null,
|
||||
"generation_id": null,
|
||||
"upload_media_id": "media_01k6hp7t8bewa904zx1t9j743e",
|
||||
"uploaded_file_id": null,
|
||||
"cameo_file_id": null,
|
||||
"frame_index": 0,
|
||||
"source_start_frame": 0,
|
||||
"source_end_frame": 0
|
||||
}
|
||||
],
|
||||
"preset_id": null,
|
||||
"caption": null,
|
||||
"actions": null,
|
||||
"interpolation": null,
|
||||
"sdedit": null,
|
||||
"remix_config": null,
|
||||
"quality": "high",
|
||||
"size": null,
|
||||
"generations": [
|
||||
{
|
||||
"id": "gen_01k6hp7w03fxzab19j0kzdx01w",
|
||||
"task_id": "task_01k6hp7tz7fkabg0vyshdnxzvx",
|
||||
"created_at": "2025-10-02T05:28:39.448076Z",
|
||||
"deleted_at": null,
|
||||
"url": "https://videos.openai.com/vg-assets/assets%2Ftask_01k6hp7tz7fkabg0vyshdnxzvx%2F1759382919_src_0.png?st=2025-10-03T00%3A31%3A34Z&se=2025-10-09T01%3A31%3A34Z&sks=b&skt=2025-10-03T00%3A31%3A34Z&ske=2025-10-09T01%3A31%3A34Z&sktid=a48cca56-e6da-484e-a814-9c849652bcb3&skoid=aa5ddad1-c91a-4f0a-9aca-e20682cc8969&skv=2019-02-02&sv=2018-11-09&sr=b&sp=r&spr=https%2Chttp&sig=Z6UufywnbsRnyKwFaIfhY8SXsrySCcrV3DUQj7tXc08%3D&az=oaivgprodscus",
|
||||
"seed": 1021399906,
|
||||
"can_download": true,
|
||||
"download_status": "ready",
|
||||
"is_favorite": false,
|
||||
"is_liked": false,
|
||||
"is_public": false,
|
||||
"is_archived": false,
|
||||
"is_featured": null,
|
||||
"featured_countries": [],
|
||||
"has_feedback": false,
|
||||
"like_count": 0,
|
||||
"num_direct_children": 0,
|
||||
"cloudflare_metadata": null,
|
||||
"cf_thumbnail_url": null,
|
||||
"encodings": {
|
||||
"source": {
|
||||
"path": "https://videos.openai.com/vg-assets/assets%2Ftask_01k6hp7tz7fkabg0vyshdnxzvx%2F1759382919_img_0.webp?st=2025-10-03T00%3A31%3A34Z&se=2025-10-09T01%3A31%3A34Z&sks=b&skt=2025-10-03T00%3A31%3A34Z&ske=2025-10-09T01%3A31%3A34Z&sktid=a48cca56-e6da-484e-a814-9c849652bcb3&skoid=aa5ddad1-c91a-4f0a-9aca-e20682cc8969&skv=2019-02-02&sv=2018-11-09&sr=b&sp=r&spr=https%2Chttp&sig=HWeQrcRFn7iG3Cj%2BxkXW0nmQVdb%2BUjxNxz7%2Fnd1Q4Hw%3D&az=oaivgprodscus",
|
||||
"size": null,
|
||||
"width": null,
|
||||
"height": null,
|
||||
"duration_secs": null,
|
||||
"ssim": null,
|
||||
"az_path": null
|
||||
},
|
||||
"source_c2pa": null,
|
||||
"md": null,
|
||||
"ld": null,
|
||||
"thumbnail": {
|
||||
"path": "https://videos.openai.com/vg-assets/assets%2Ftask_01k6hp7tz7fkabg0vyshdnxzvx%2F1759382919_img_0_thumb.webp?st=2025-10-03T00%3A31%3A34Z&se=2025-10-09T01%3A31%3A34Z&sks=b&skt=2025-10-03T00%3A31%3A34Z&ske=2025-10-09T01%3A31%3A34Z&sktid=a48cca56-e6da-484e-a814-9c849652bcb3&skoid=aa5ddad1-c91a-4f0a-9aca-e20682cc8969&skv=2019-02-02&sv=2018-11-09&sr=b&sp=r&spr=https%2Chttp&sig=lMPxMYQ%2FXkUXr7qCGUF4iquf8%2Bj5FUeuQhvuKbfXckw%3D&az=oaivgprodscus",
|
||||
"size": null,
|
||||
"az_path": null
|
||||
},
|
||||
"spritesheet": null,
|
||||
"gif": null,
|
||||
"mp3": null,
|
||||
"source_wm": null,
|
||||
"md_wm": null,
|
||||
"ld_wm": null,
|
||||
"thumbnail_wm": null,
|
||||
"spritesheet_wm": null,
|
||||
"gif_wm": null
|
||||
},
|
||||
"width": 1536,
|
||||
"height": 1024,
|
||||
"n_frames": 1,
|
||||
"prompt": "A green apple",
|
||||
"title": "Green Apple Drawing",
|
||||
"actions": null,
|
||||
"inpaint_items": [
|
||||
{
|
||||
"crop_bounds": null,
|
||||
"type": "image",
|
||||
"preset_id": null,
|
||||
"generation_id": null,
|
||||
"upload_media_id": "media_01k6hp7t8bewa904zx1t9j743e",
|
||||
"uploaded_file_id": null,
|
||||
"cameo_file_id": null,
|
||||
"frame_index": 0,
|
||||
"source_start_frame": 0,
|
||||
"source_end_frame": 0
|
||||
}
|
||||
],
|
||||
"interpolation": null,
|
||||
"sdedit": null,
|
||||
"operation": "remix",
|
||||
"model": null,
|
||||
"preset_id": null,
|
||||
"user": {
|
||||
"id": "user-hQ5fc1AV7vNcQLk2yfEFEUbo",
|
||||
"username": "videodesign"
|
||||
},
|
||||
"moderation_result": {
|
||||
"type": "passed",
|
||||
"results_by_frame_index": {},
|
||||
"code": null,
|
||||
"is_output_rejection": false,
|
||||
"task_id": "task_01k6hp7tz7fkabg0vyshdnxzvx"
|
||||
},
|
||||
"paragen_status": null,
|
||||
"task_type": "image_gen",
|
||||
"remix_config": null,
|
||||
"quality": "high"
|
||||
}
|
||||
],
|
||||
"num_unsafe_generations": 0,
|
||||
"title": "Green Apple Drawing",
|
||||
"moderation_result": {
|
||||
"type": "passed",
|
||||
"results_by_frame_index": {},
|
||||
"code": null,
|
||||
"is_output_rejection": false,
|
||||
"task_id": "task_01k6hp7tz7fkabg0vyshdnxzvx"
|
||||
},
|
||||
"failure_reason": null,
|
||||
"needs_user_review": false
|
||||
}
|
||||
],
|
||||
"last_id": null,
|
||||
"has_more": false
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
pub mod list_classic_tasks;
|
||||
@@ -0,0 +1,57 @@
|
||||
use serde_derive::Deserialize;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub (super) struct HttpDraftsResponse {
|
||||
pub items: Vec<PartialItem>,
|
||||
|
||||
// NB: This is nullable.
|
||||
// It's null in my tests, so I don't know if this is a string or integer yet. Probably an opaque string?
|
||||
//pub cursor: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub (super) struct PartialItem {
|
||||
/// The generation id, eg. "gen_01abc..."
|
||||
pub id: String,
|
||||
|
||||
/// The parent task id, eg "task_01abc..."
|
||||
pub task_id: String,
|
||||
|
||||
//pub generation_id: String, // it looks like this matches "id" for now
|
||||
|
||||
/// The kind of draft
|
||||
pub kind: DraftKind,
|
||||
|
||||
// The text prompt
|
||||
pub prompt: String,
|
||||
|
||||
/// URL to the generation
|
||||
/// This will not be present if the request failed, eg. for content reasons.
|
||||
pub url: Option<String>,
|
||||
|
||||
/// "downloadable" URL to the generation
|
||||
/// it looks like this matches "url" for now
|
||||
/// This will not be present if the request failed, eg. for content reasons.
|
||||
pub downloadable_url: Option<String>,
|
||||
|
||||
/// If there was a content violation, this will describe why.
|
||||
pub reason_str: Option<String>,
|
||||
|
||||
// There are other things that might be interesting: "encodings", "width", "height", etc.
|
||||
}
|
||||
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum DraftKind {
|
||||
/// Generated video
|
||||
SoraDraft,
|
||||
|
||||
/// Video was blocked due to content
|
||||
/// These failures will have different "reason_str" reasons.
|
||||
SoraContentViolation,
|
||||
|
||||
/// Unknown type.
|
||||
#[serde(untagged)]
|
||||
Unknown(String),
|
||||
}
|
||||
+180
@@ -0,0 +1,180 @@
|
||||
use crate::constants::user_agent::USER_AGENT;
|
||||
use crate::creds::sora_credential_set::SoraCredentialSet;
|
||||
use crate::error::sora_client_error::SoraClientError;
|
||||
use crate::error::sora_error::SoraError;
|
||||
use crate::error::sora_generic_api_error::SoraGenericApiError;
|
||||
use crate::requests::common::task_id::TaskId;
|
||||
use crate::requests::deprecated::job_status::sora_job_status::{Generation, StatusRequest, VideoGenStatusResponse};
|
||||
use crate::requests::list_sora2_drafts::http_response::{DraftKind, HttpDraftsResponse};
|
||||
use crate::utils_internal::classify_general_http_status_code_and_body::classify_general_http_status_code_and_body;
|
||||
use log::{debug, error, info, warn};
|
||||
use serde_derive::Deserialize;
|
||||
use url::Url;
|
||||
use wreq::header::{ACCEPT, ACCEPT_LANGUAGE, AUTHORIZATION, CONTENT_TYPE, COOKIE, ORIGIN, REFERER};
|
||||
use wreq::Client;
|
||||
|
||||
const SORA_LIST_DRAFTS_URL: &str = "https://sora.chatgpt.com/backend/project_y/profile/drafts?limit=15";
|
||||
|
||||
|
||||
/// NB: We omit fields we're not using to prevent breakage.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct ListSora2DraftsResponse {
|
||||
pub drafts: Vec<Draft>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum Draft {
|
||||
Success(DraftSuccess),
|
||||
Failure(DraftFailure),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct DraftSuccess {
|
||||
pub id: String,
|
||||
pub task_id: TaskId,
|
||||
pub url: String,
|
||||
pub prompt: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct DraftFailure {
|
||||
pub task_id: TaskId,
|
||||
pub reason_message: String,
|
||||
}
|
||||
|
||||
pub async fn list_sora2_drafts(credentials: &SoraCredentialSet) -> Result<ListSora2DraftsResponse, SoraError> {
|
||||
|
||||
let client = Client::new();
|
||||
|
||||
let authorization_header = credentials.jwt_bearer_token.as_ref()
|
||||
.ok_or(SoraClientError::NoBearerTokenForRequest)?
|
||||
.to_authorization_header_value();
|
||||
|
||||
let cookie = credentials.cookies.to_string();
|
||||
|
||||
// TODO: Make the sec-* headers match the user agent and platform
|
||||
// TODO: device id
|
||||
//-H 'oai-device-id: 7c216860-5b73-4e63-983f-142dbcae1809' \
|
||||
let mut http_request = client.get(SORA_LIST_DRAFTS_URL)
|
||||
//.header("OpenAI-Sentinel-Token", &sentinel);
|
||||
.header(ACCEPT, "*/*")
|
||||
.header(REFERER, "https://sora.chatgpt.com/drafts")
|
||||
.header(ACCEPT_LANGUAGE, "en-US,en;q=0.9")
|
||||
.header(wreq::header::USER_AGENT, USER_AGENT)
|
||||
.header(COOKIE, &cookie)
|
||||
.header(AUTHORIZATION, &authorization_header)
|
||||
.header("priority", "u=1, i")
|
||||
.header("sec-ch-ua", "\"Chromium\";v=\"140\", \"Not=A?Brand\";v=\"24\", \"Google Chrome\";v=\"140\"")
|
||||
.header("sec-ch-ua-mobile", "?0")
|
||||
.header("sec-ch-ua-platform", "macOS")
|
||||
.header("sec-fetch-dest", "empty")
|
||||
.header("sec-fetch-mode", "cors")
|
||||
.header("sec-fetch-site", "same-origin");
|
||||
|
||||
//if let Some(timeout) = args.request_timeout {
|
||||
// http_request = http_request.timeout(timeout);
|
||||
//}
|
||||
|
||||
let http_request = http_request.build()
|
||||
.map_err(|err| {
|
||||
error!("Error building Sora 2 list drafts HTTP request: {:?}", err);
|
||||
SoraClientError::WreqClientError(err)
|
||||
})?;
|
||||
|
||||
let response = client.execute(http_request)
|
||||
.await
|
||||
.map_err(|err| {
|
||||
error!("Error during Sora 2 list drafts request: {:?}", err);
|
||||
SoraClientError::WreqClientError(err)
|
||||
})?;
|
||||
|
||||
let status = response.status();
|
||||
|
||||
let response_body = &response.text().await
|
||||
.map_err(|err| {
|
||||
error!("Error reading Sora 2 list drafts response body: {:?}", err);
|
||||
SoraClientError::WreqClientError(err)
|
||||
})?;
|
||||
|
||||
if !status.is_success() {
|
||||
error!("Sora list drafts request returned an error (code {}) : {:?}", status.as_u16(), response_body);
|
||||
|
||||
// TODO: Categorize failures.
|
||||
// error!("The sora task list request failed; status = {:?} ; response body = {}", status, response_body);
|
||||
// let error = classify_general_http_status_code_and_body(status, &response_body).await;
|
||||
// return Err(error);
|
||||
|
||||
return Err(SoraGenericApiError::UncategorizedBadResponseWithStatusAndBody {
|
||||
status_code: status,
|
||||
body: response_body.to_string(),
|
||||
}.into());
|
||||
}
|
||||
|
||||
let response : HttpDraftsResponse = serde_json::from_str(response_body)
|
||||
.map_err(|err| SoraGenericApiError::SerdeResponseParseErrorWithBody(err, response_body.to_string()))?;
|
||||
|
||||
Ok(ListSora2DraftsResponse {
|
||||
drafts: response.items
|
||||
.into_iter()
|
||||
.map(|draft| {
|
||||
let task_id = TaskId::from_string(draft.task_id);
|
||||
match draft.kind {
|
||||
DraftKind::SoraDraft => {
|
||||
match draft.url {
|
||||
Some(url) => {
|
||||
// Success case
|
||||
Draft::Success(DraftSuccess {
|
||||
id: draft.id,
|
||||
task_id,
|
||||
url,
|
||||
prompt: draft.prompt,
|
||||
})
|
||||
}
|
||||
None => {
|
||||
return Draft::Failure(DraftFailure {
|
||||
task_id,
|
||||
reason_message: "Generation failed. URL missing from the draft.".to_string(),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
DraftKind::SoraContentViolation => {
|
||||
Draft::Failure(DraftFailure {
|
||||
task_id,
|
||||
reason_message: format!(
|
||||
"Generation failed due to content violation: {}",
|
||||
draft.reason_str
|
||||
.unwrap_or_else(|| "no additional reason".to_string())),
|
||||
})
|
||||
}
|
||||
DraftKind::Unknown(value) => {
|
||||
Draft::Failure(DraftFailure {
|
||||
task_id,
|
||||
reason_message: format!(
|
||||
"Generation failed with unknown failure code: {}. Possible message: {}",
|
||||
value,
|
||||
draft.reason_str
|
||||
.unwrap_or_else(|| "no additional message".to_string())),
|
||||
})
|
||||
}
|
||||
}
|
||||
}).collect(),
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::requests::list_sora2_drafts::list_sora2_drafts::list_sora2_drafts;
|
||||
use crate::test_utils::get_test_credentials::get_test_credentials;
|
||||
use errors::AnyhowResult;
|
||||
|
||||
#[ignore] // You can manually run "ignore" tests in the IDE, but they won't run in CI.
|
||||
#[tokio::test]
|
||||
pub async fn manual_test() -> AnyhowResult<()> {
|
||||
let creds = get_test_credentials()?;
|
||||
let result = list_sora2_drafts(&creds).await?;
|
||||
println!("result: {:#?}", result);
|
||||
assert_eq!(1, 2);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
+16
File diff suppressed because one or more lines are too long
+233
@@ -0,0 +1,233 @@
|
||||
{
|
||||
"items": [
|
||||
{
|
||||
"id": "gen_01k6nkg3z3ebxrqd1313hj9a87",
|
||||
"kind": "sora_draft",
|
||||
"url": "https://videos.openai.com/vg-assets/assets%2Ftask_01k6nkbsdvervv5dsfnnpr27jw%2Ftask_01k6nkbsdvervv5dsfnnpr27jw_genid_517dfb8c-cdaa-42e4-9fd4-98743399b160_25_10_03_17_56_604392%2Fvideos%2F00000_wm%2Fsrc.mp4?st=2025-10-03T16%3A53%3A19Z&se=2025-10-09T17%3A53%3A19Z&sks=b&skt=2025-10-03T16%3A53%3A19Z&ske=2025-10-09T17%3A53%3A19Z&sktid=a48cca56-e6da-484e-a814-9c849652bcb3&skoid=aa5ddad1-c91a-4f0a-9aca-e20682cc8969&skv=2019-02-02&sv=2018-11-09&sr=b&sp=r&spr=https%2Chttp&sig=f8BZNAqfO%2B8C6RzIpm66I5rQMH7cSgUfpkVP5ku0Q0U%3D&az=oaivgprodscus",
|
||||
"downloadable_url": "https://videos.openai.com/vg-assets/assets%2Ftask_01k6nkbsdvervv5dsfnnpr27jw%2Ftask_01k6nkbsdvervv5dsfnnpr27jw_genid_517dfb8c-cdaa-42e4-9fd4-98743399b160_25_10_03_17_56_604392%2Fvideos%2F00000_wm%2Fsrc.mp4?st=2025-10-03T16%3A53%3A19Z&se=2025-10-09T17%3A53%3A19Z&sks=b&skt=2025-10-03T16%3A53%3A19Z&ske=2025-10-09T17%3A53%3A19Z&sktid=a48cca56-e6da-484e-a814-9c849652bcb3&skoid=aa5ddad1-c91a-4f0a-9aca-e20682cc8969&skv=2019-02-02&sv=2018-11-09&sr=b&sp=r&spr=https%2Chttp&sig=f8BZNAqfO%2B8C6RzIpm66I5rQMH7cSgUfpkVP5ku0Q0U%3D&az=oaivgprodscus",
|
||||
"width": 352,
|
||||
"height": 640,
|
||||
"created_at": 1759514220.244053,
|
||||
"prompt": "SpongeBob buys Ozemspic for Patrick. Patrick is offended. ",
|
||||
"title": null,
|
||||
"encodings": {
|
||||
"source": {
|
||||
"path": "https://videos.openai.com/vg-assets/assets%2Ftask_01k6nkbsdvervv5dsfnnpr27jw%2Ftask_01k6nkbsdvervv5dsfnnpr27jw_genid_517dfb8c-cdaa-42e4-9fd4-98743399b160_25_10_03_17_56_604392%2Fvideos%2F00000_wm%2Fsrc.mp4?st=2025-10-03T16%3A53%3A19Z&se=2025-10-09T17%3A53%3A19Z&sks=b&skt=2025-10-03T16%3A53%3A19Z&ske=2025-10-09T17%3A53%3A19Z&sktid=a48cca56-e6da-484e-a814-9c849652bcb3&skoid=aa5ddad1-c91a-4f0a-9aca-e20682cc8969&skv=2019-02-02&sv=2018-11-09&sr=b&sp=r&spr=https%2Chttp&sig=f8BZNAqfO%2B8C6RzIpm66I5rQMH7cSgUfpkVP5ku0Q0U%3D&az=oaivgprodscus"
|
||||
},
|
||||
"source_wm": {
|
||||
"path": "https://videos.openai.com/vg-assets/assets%2Ftask_01k6nkbsdvervv5dsfnnpr27jw%2Ftask_01k6nkbsdvervv5dsfnnpr27jw_genid_517dfb8c-cdaa-42e4-9fd4-98743399b160_25_10_03_17_56_604392%2Fvideos%2F00000_wm%2Fsrc.mp4?st=2025-10-03T16%3A53%3A19Z&se=2025-10-09T17%3A53%3A19Z&sks=b&skt=2025-10-03T16%3A53%3A19Z&ske=2025-10-09T17%3A53%3A19Z&sktid=a48cca56-e6da-484e-a814-9c849652bcb3&skoid=aa5ddad1-c91a-4f0a-9aca-e20682cc8969&skv=2019-02-02&sv=2018-11-09&sr=b&sp=r&spr=https%2Chttp&sig=f8BZNAqfO%2B8C6RzIpm66I5rQMH7cSgUfpkVP5ku0Q0U%3D&az=oaivgprodscus"
|
||||
},
|
||||
"thumbnail": {
|
||||
"path": "https://videos.openai.com/vg-assets/assets%2Ftask_01k6nkbsdvervv5dsfnnpr27jw%2Ftask_01k6nkbsdvervv5dsfnnpr27jw_genid_517dfb8c-cdaa-42e4-9fd4-98743399b160_25_10_03_17_56_604392%2Fvideos%2F00000_wm%2Fthumbnail.webp?st=2025-10-03T16%3A53%3A19Z&se=2025-10-09T17%3A53%3A19Z&sks=b&skt=2025-10-03T16%3A53%3A19Z&ske=2025-10-09T17%3A53%3A19Z&sktid=a48cca56-e6da-484e-a814-9c849652bcb3&skoid=aa5ddad1-c91a-4f0a-9aca-e20682cc8969&skv=2019-02-02&sv=2018-11-09&sr=b&sp=r&spr=https%2Chttp&sig=FmCDGzqvDYBOOEHrVP6P8S6Te%2FNXhySDf%2Bbp8aBf5Tw%3D&az=oaivgprodscus"
|
||||
},
|
||||
"unfurl": null,
|
||||
"md": {
|
||||
"path": "https://videos.openai.com/vg-assets/assets%2Ftask_01k6nkbsdvervv5dsfnnpr27jw%2Ftask_01k6nkbsdvervv5dsfnnpr27jw_genid_517dfb8c-cdaa-42e4-9fd4-98743399b160_25_10_03_17_56_604392%2Fvideos%2F00000_wm%2Fmd.mp4?st=2025-10-03T16%3A53%3A19Z&se=2025-10-09T17%3A53%3A19Z&sks=b&skt=2025-10-03T16%3A53%3A19Z&ske=2025-10-09T17%3A53%3A19Z&sktid=a48cca56-e6da-484e-a814-9c849652bcb3&skoid=aa5ddad1-c91a-4f0a-9aca-e20682cc8969&skv=2019-02-02&sv=2018-11-09&sr=b&sp=r&spr=https%2Chttp&sig=05P%2B9ui5Zf9jazG3FeiBBn%2Bi25ZzdrFzha8%2BXmRo3B4%3D&az=oaivgprodscus"
|
||||
},
|
||||
"gif": {
|
||||
"path": "https://videos.openai.com/vg-assets/assets%2Ftask_01k6nkbsdvervv5dsfnnpr27jw%2Ftask_01k6nkbsdvervv5dsfnnpr27jw_genid_517dfb8c-cdaa-42e4-9fd4-98743399b160_25_10_03_17_56_604392%2Fvideos%2F00000_wm%2Fpreview.gif?st=2025-10-03T16%3A53%3A19Z&se=2025-10-09T17%3A53%3A19Z&sks=b&skt=2025-10-03T16%3A53%3A19Z&ske=2025-10-09T17%3A53%3A19Z&sktid=a48cca56-e6da-484e-a814-9c849652bcb3&skoid=aa5ddad1-c91a-4f0a-9aca-e20682cc8969&skv=2019-02-02&sv=2018-11-09&sr=b&sp=r&spr=https%2Chttp&sig=GlTYf3fahHlfAuscTYPPI9i%2BsLfSQensqAox8tebN3E%3D&az=oaivgprodscus"
|
||||
}
|
||||
},
|
||||
"draft_reviewed": false,
|
||||
"task_id": "task_01k6nkbsdvervv5dsfnnpr27jw",
|
||||
"creation_config": {
|
||||
"remix_target_post": null,
|
||||
"style_id": null,
|
||||
"inpaint_image": null,
|
||||
"prompt": "SpongeBob buys Ozemspic for Patrick. Patrick is offended. ",
|
||||
"task_id": null,
|
||||
"cameo_profiles": null,
|
||||
"orientation": null,
|
||||
"n_frames": null
|
||||
},
|
||||
"tags": [],
|
||||
"generation_id": "gen_01k6nkg3z3ebxrqd1313hj9a87",
|
||||
"generation_type": ""
|
||||
},
|
||||
{
|
||||
"id": "gen_01k6njzfhxfafv8kgc4n5bmv38",
|
||||
"kind": "sora_draft",
|
||||
"url": "https://videos.openai.com/vg-assets/assets%2Ftask_01k6njvepzfk68mv77xxz9meca%2Ftask_01k6njvepzfk68mv77xxz9meca_genid_2765ab1f-e4fa-40cb-a33f-6d33f2ba2c04_25_10_03_17_47_588936%2Fvideos%2F00000_wm%2Fsrc.mp4?st=2025-10-03T16%3A53%3A19Z&se=2025-10-09T17%3A53%3A19Z&sks=b&skt=2025-10-03T16%3A53%3A19Z&ske=2025-10-09T17%3A53%3A19Z&sktid=a48cca56-e6da-484e-a814-9c849652bcb3&skoid=aa5ddad1-c91a-4f0a-9aca-e20682cc8969&skv=2019-02-02&sv=2018-11-09&sr=b&sp=r&spr=https%2Chttp&sig=aL0EUJ3H7dP6DIxzYarXIQEFyD0Xtnn0w4TNt0hssPM%3D&az=oaivgprodscus",
|
||||
"downloadable_url": "https://videos.openai.com/vg-assets/assets%2Ftask_01k6njvepzfk68mv77xxz9meca%2Ftask_01k6njvepzfk68mv77xxz9meca_genid_2765ab1f-e4fa-40cb-a33f-6d33f2ba2c04_25_10_03_17_47_588936%2Fvideos%2F00000_wm%2Fsrc.mp4?st=2025-10-03T16%3A53%3A19Z&se=2025-10-09T17%3A53%3A19Z&sks=b&skt=2025-10-03T16%3A53%3A19Z&ske=2025-10-09T17%3A53%3A19Z&sktid=a48cca56-e6da-484e-a814-9c849652bcb3&skoid=aa5ddad1-c91a-4f0a-9aca-e20682cc8969&skv=2019-02-02&sv=2018-11-09&sr=b&sp=r&spr=https%2Chttp&sig=aL0EUJ3H7dP6DIxzYarXIQEFyD0Xtnn0w4TNt0hssPM%3D&az=oaivgprodscus",
|
||||
"width": 352,
|
||||
"height": 640,
|
||||
"created_at": 1759513681.966017,
|
||||
"prompt": "A t-rex walking through manhattan in the morning fog, the pavement is slightly damp, sunlight gleams around the corners of buildings",
|
||||
"title": null,
|
||||
"encodings": {
|
||||
"source": {
|
||||
"path": "https://videos.openai.com/vg-assets/assets%2Ftask_01k6njvepzfk68mv77xxz9meca%2Ftask_01k6njvepzfk68mv77xxz9meca_genid_2765ab1f-e4fa-40cb-a33f-6d33f2ba2c04_25_10_03_17_47_588936%2Fvideos%2F00000_wm%2Fsrc.mp4?st=2025-10-03T16%3A53%3A19Z&se=2025-10-09T17%3A53%3A19Z&sks=b&skt=2025-10-03T16%3A53%3A19Z&ske=2025-10-09T17%3A53%3A19Z&sktid=a48cca56-e6da-484e-a814-9c849652bcb3&skoid=aa5ddad1-c91a-4f0a-9aca-e20682cc8969&skv=2019-02-02&sv=2018-11-09&sr=b&sp=r&spr=https%2Chttp&sig=aL0EUJ3H7dP6DIxzYarXIQEFyD0Xtnn0w4TNt0hssPM%3D&az=oaivgprodscus"
|
||||
},
|
||||
"source_wm": {
|
||||
"path": "https://videos.openai.com/vg-assets/assets%2Ftask_01k6njvepzfk68mv77xxz9meca%2Ftask_01k6njvepzfk68mv77xxz9meca_genid_2765ab1f-e4fa-40cb-a33f-6d33f2ba2c04_25_10_03_17_47_588936%2Fvideos%2F00000_wm%2Fsrc.mp4?st=2025-10-03T16%3A53%3A19Z&se=2025-10-09T17%3A53%3A19Z&sks=b&skt=2025-10-03T16%3A53%3A19Z&ske=2025-10-09T17%3A53%3A19Z&sktid=a48cca56-e6da-484e-a814-9c849652bcb3&skoid=aa5ddad1-c91a-4f0a-9aca-e20682cc8969&skv=2019-02-02&sv=2018-11-09&sr=b&sp=r&spr=https%2Chttp&sig=aL0EUJ3H7dP6DIxzYarXIQEFyD0Xtnn0w4TNt0hssPM%3D&az=oaivgprodscus"
|
||||
},
|
||||
"thumbnail": {
|
||||
"path": "https://videos.openai.com/vg-assets/assets%2Ftask_01k6njvepzfk68mv77xxz9meca%2Ftask_01k6njvepzfk68mv77xxz9meca_genid_2765ab1f-e4fa-40cb-a33f-6d33f2ba2c04_25_10_03_17_47_588936%2Fvideos%2F00000_wm%2Fthumbnail.webp?st=2025-10-03T16%3A53%3A19Z&se=2025-10-09T17%3A53%3A19Z&sks=b&skt=2025-10-03T16%3A53%3A19Z&ske=2025-10-09T17%3A53%3A19Z&sktid=a48cca56-e6da-484e-a814-9c849652bcb3&skoid=aa5ddad1-c91a-4f0a-9aca-e20682cc8969&skv=2019-02-02&sv=2018-11-09&sr=b&sp=r&spr=https%2Chttp&sig=Cj6XzDLw2sgRG9UtJlNWncmcemu%2B7QEhEjKRZUf1MF8%3D&az=oaivgprodscus"
|
||||
},
|
||||
"unfurl": null,
|
||||
"md": {
|
||||
"path": "https://videos.openai.com/vg-assets/assets%2Ftask_01k6njvepzfk68mv77xxz9meca%2Ftask_01k6njvepzfk68mv77xxz9meca_genid_2765ab1f-e4fa-40cb-a33f-6d33f2ba2c04_25_10_03_17_47_588936%2Fvideos%2F00000_wm%2Fmd.mp4?st=2025-10-03T16%3A53%3A19Z&se=2025-10-09T17%3A53%3A19Z&sks=b&skt=2025-10-03T16%3A53%3A19Z&ske=2025-10-09T17%3A53%3A19Z&sktid=a48cca56-e6da-484e-a814-9c849652bcb3&skoid=aa5ddad1-c91a-4f0a-9aca-e20682cc8969&skv=2019-02-02&sv=2018-11-09&sr=b&sp=r&spr=https%2Chttp&sig=Zg%2FbIN48W5p0uqiIC%2FS5GLJQeM5YIpFHpkW7cgi24m8%3D&az=oaivgprodscus"
|
||||
},
|
||||
"gif": {
|
||||
"path": "https://videos.openai.com/vg-assets/assets%2Ftask_01k6njvepzfk68mv77xxz9meca%2Ftask_01k6njvepzfk68mv77xxz9meca_genid_2765ab1f-e4fa-40cb-a33f-6d33f2ba2c04_25_10_03_17_47_588936%2Fvideos%2F00000_wm%2Fpreview.gif?st=2025-10-03T16%3A53%3A19Z&se=2025-10-09T17%3A53%3A19Z&sks=b&skt=2025-10-03T16%3A53%3A19Z&ske=2025-10-09T17%3A53%3A19Z&sktid=a48cca56-e6da-484e-a814-9c849652bcb3&skoid=aa5ddad1-c91a-4f0a-9aca-e20682cc8969&skv=2019-02-02&sv=2018-11-09&sr=b&sp=r&spr=https%2Chttp&sig=zp1iZmWDCPeqy4TNBZfiehyOWw0tncussbtjiMODfpU%3D&az=oaivgprodscus"
|
||||
}
|
||||
},
|
||||
"draft_reviewed": true,
|
||||
"task_id": "task_01k6njvepzfk68mv77xxz9meca",
|
||||
"creation_config": {
|
||||
"remix_target_post": null,
|
||||
"style_id": null,
|
||||
"inpaint_image": null,
|
||||
"prompt": "A t-rex walking through manhattan in the morning fog, the pavement is slightly damp, sunlight gleams around the corners of buildings",
|
||||
"task_id": null,
|
||||
"cameo_profiles": null,
|
||||
"orientation": null,
|
||||
"n_frames": null
|
||||
},
|
||||
"tags": [],
|
||||
"generation_id": "gen_01k6njzfhxfafv8kgc4n5bmv38",
|
||||
"generation_type": ""
|
||||
},
|
||||
{
|
||||
"id": "gen_01k6kaqq83egat15t75wc1zg3t",
|
||||
"kind": "sora_draft",
|
||||
"url": "https://videos.openai.com/vg-assets/assets%2Ftask_01k6kakyc4fk19y7bgfskxjt73%2Ftask_01k6kakyc4fk19y7bgfskxjt73_genid_e30103d1-f44c-4bf3-a2b7-82d0f8105909_25_10_02_20_44_495868%2Fvideos%2F00000_wm%2Fsrc.mp4?st=2025-10-03T16%3A53%3A19Z&se=2025-10-09T17%3A53%3A19Z&sks=b&skt=2025-10-03T16%3A53%3A19Z&ske=2025-10-09T17%3A53%3A19Z&sktid=a48cca56-e6da-484e-a814-9c849652bcb3&skoid=aa5ddad1-c91a-4f0a-9aca-e20682cc8969&skv=2019-02-02&sv=2018-11-09&sr=b&sp=r&spr=https%2Chttp&sig=aVqB7xZo%2FGN9ZIsFaDMfoBoacqwJc355Q%2FVbcWWBi%2B0%3D&az=oaivgprodscus",
|
||||
"downloadable_url": "https://videos.openai.com/vg-assets/assets%2Ftask_01k6kakyc4fk19y7bgfskxjt73%2Ftask_01k6kakyc4fk19y7bgfskxjt73_genid_e30103d1-f44c-4bf3-a2b7-82d0f8105909_25_10_02_20_44_495868%2Fvideos%2F00000_wm%2Fsrc.mp4?st=2025-10-03T16%3A53%3A19Z&se=2025-10-09T17%3A53%3A19Z&sks=b&skt=2025-10-03T16%3A53%3A19Z&ske=2025-10-09T17%3A53%3A19Z&sktid=a48cca56-e6da-484e-a814-9c849652bcb3&skoid=aa5ddad1-c91a-4f0a-9aca-e20682cc8969&skv=2019-02-02&sv=2018-11-09&sr=b&sp=r&spr=https%2Chttp&sig=aVqB7xZo%2FGN9ZIsFaDMfoBoacqwJc355Q%2FVbcWWBi%2B0%3D&az=oaivgprodscus",
|
||||
"width": 352,
|
||||
"height": 640,
|
||||
"created_at": 1759437920.420881,
|
||||
"prompt": "A hipster girl stands in front of a t-rex in a room made of glass. She makes a peace sign with her hands as the t-rex roars. ",
|
||||
"title": null,
|
||||
"encodings": {
|
||||
"source": {
|
||||
"path": "https://videos.openai.com/vg-assets/assets%2Ftask_01k6kakyc4fk19y7bgfskxjt73%2Ftask_01k6kakyc4fk19y7bgfskxjt73_genid_e30103d1-f44c-4bf3-a2b7-82d0f8105909_25_10_02_20_44_495868%2Fvideos%2F00000_wm%2Fsrc.mp4?st=2025-10-03T16%3A53%3A19Z&se=2025-10-09T17%3A53%3A19Z&sks=b&skt=2025-10-03T16%3A53%3A19Z&ske=2025-10-09T17%3A53%3A19Z&sktid=a48cca56-e6da-484e-a814-9c849652bcb3&skoid=aa5ddad1-c91a-4f0a-9aca-e20682cc8969&skv=2019-02-02&sv=2018-11-09&sr=b&sp=r&spr=https%2Chttp&sig=aVqB7xZo%2FGN9ZIsFaDMfoBoacqwJc355Q%2FVbcWWBi%2B0%3D&az=oaivgprodscus"
|
||||
},
|
||||
"source_wm": {
|
||||
"path": "https://videos.openai.com/vg-assets/assets%2Ftask_01k6kakyc4fk19y7bgfskxjt73%2Ftask_01k6kakyc4fk19y7bgfskxjt73_genid_e30103d1-f44c-4bf3-a2b7-82d0f8105909_25_10_02_20_44_495868%2Fvideos%2F00000_wm%2Fsrc.mp4?st=2025-10-03T16%3A53%3A19Z&se=2025-10-09T17%3A53%3A19Z&sks=b&skt=2025-10-03T16%3A53%3A19Z&ske=2025-10-09T17%3A53%3A19Z&sktid=a48cca56-e6da-484e-a814-9c849652bcb3&skoid=aa5ddad1-c91a-4f0a-9aca-e20682cc8969&skv=2019-02-02&sv=2018-11-09&sr=b&sp=r&spr=https%2Chttp&sig=aVqB7xZo%2FGN9ZIsFaDMfoBoacqwJc355Q%2FVbcWWBi%2B0%3D&az=oaivgprodscus"
|
||||
},
|
||||
"thumbnail": {
|
||||
"path": "https://videos.openai.com/vg-assets/assets%2Ftask_01k6kakyc4fk19y7bgfskxjt73%2Ftask_01k6kakyc4fk19y7bgfskxjt73_genid_e30103d1-f44c-4bf3-a2b7-82d0f8105909_25_10_02_20_44_495868%2Fvideos%2F00000_wm%2Fthumbnail.webp?st=2025-10-03T16%3A53%3A19Z&se=2025-10-09T17%3A53%3A19Z&sks=b&skt=2025-10-03T16%3A53%3A19Z&ske=2025-10-09T17%3A53%3A19Z&sktid=a48cca56-e6da-484e-a814-9c849652bcb3&skoid=aa5ddad1-c91a-4f0a-9aca-e20682cc8969&skv=2019-02-02&sv=2018-11-09&sr=b&sp=r&spr=https%2Chttp&sig=cJtvfHJmUUovVomC1MRlc2B4TP1FBN1h%2FcWfVxJGT2A%3D&az=oaivgprodscus"
|
||||
},
|
||||
"unfurl": null,
|
||||
"md": {
|
||||
"path": "https://videos.openai.com/vg-assets/assets%2Ftask_01k6kakyc4fk19y7bgfskxjt73%2Ftask_01k6kakyc4fk19y7bgfskxjt73_genid_e30103d1-f44c-4bf3-a2b7-82d0f8105909_25_10_02_20_44_495868%2Fvideos%2F00000_wm%2Fmd.mp4?st=2025-10-03T16%3A53%3A19Z&se=2025-10-09T17%3A53%3A19Z&sks=b&skt=2025-10-03T16%3A53%3A19Z&ske=2025-10-09T17%3A53%3A19Z&sktid=a48cca56-e6da-484e-a814-9c849652bcb3&skoid=aa5ddad1-c91a-4f0a-9aca-e20682cc8969&skv=2019-02-02&sv=2018-11-09&sr=b&sp=r&spr=https%2Chttp&sig=1ysL0RhapKRjenKQdaH7xmVHSIWFXTPFWCw5CRh20ew%3D&az=oaivgprodscus"
|
||||
},
|
||||
"gif": {
|
||||
"path": "https://videos.openai.com/vg-assets/assets%2Ftask_01k6kakyc4fk19y7bgfskxjt73%2Ftask_01k6kakyc4fk19y7bgfskxjt73_genid_e30103d1-f44c-4bf3-a2b7-82d0f8105909_25_10_02_20_44_495868%2Fvideos%2F00000_wm%2Fpreview.gif?st=2025-10-03T16%3A53%3A19Z&se=2025-10-09T17%3A53%3A19Z&sks=b&skt=2025-10-03T16%3A53%3A19Z&ske=2025-10-09T17%3A53%3A19Z&sktid=a48cca56-e6da-484e-a814-9c849652bcb3&skoid=aa5ddad1-c91a-4f0a-9aca-e20682cc8969&skv=2019-02-02&sv=2018-11-09&sr=b&sp=r&spr=https%2Chttp&sig=IdYqiCUJFkjlD2KOB5SyrCmlCU5KMPcm%2B2Ew6Adbtoo%3D&az=oaivgprodscus"
|
||||
}
|
||||
},
|
||||
"draft_reviewed": true,
|
||||
"task_id": "task_01k6kakyc4fk19y7bgfskxjt73",
|
||||
"creation_config": {
|
||||
"remix_target_post": null,
|
||||
"style_id": null,
|
||||
"inpaint_image": null,
|
||||
"prompt": "A hipster girl stands in front of a t-rex in a room made of glass. She makes a peace sign with her hands as the t-rex roars. ",
|
||||
"task_id": null,
|
||||
"cameo_profiles": null,
|
||||
"orientation": null,
|
||||
"n_frames": null
|
||||
},
|
||||
"tags": [],
|
||||
"generation_id": "gen_01k6kaqq83egat15t75wc1zg3t",
|
||||
"generation_type": ""
|
||||
},
|
||||
{
|
||||
"id": "68dee38aaf3c81918ed72fd2845abebe",
|
||||
"kind": "sora_content_violation",
|
||||
"width": 352,
|
||||
"height": 640,
|
||||
"created_at": null,
|
||||
"prompt": "A man with glasses stretches in front of a t-rex. Butterflies fly by as sun gleams over the horizon. The camera pans, and there are solar punk structures dotting the landscape. A field of pink flowers and an infinite blue sky with clouds. The T-rex roars as the man continues to stretch",
|
||||
"title": null,
|
||||
"reason": null,
|
||||
"reason_str": "This content may violate our guardrails concerning third-party likeness.",
|
||||
"task_id": "task_01k6kahg89fwzb1e40j47kfrfv",
|
||||
"draft_reviewed": false,
|
||||
"creation_config": {
|
||||
"remix_target_post": null,
|
||||
"style_id": null,
|
||||
"inpaint_image": null,
|
||||
"prompt": "A man with glasses stretches in front of a t-rex. Butterflies fly by as sun gleams over the horizon. The camera pans, and there are solar punk structures dotting the landscape. A field of pink flowers and an infinite blue sky with clouds. The T-rex roars as the man continues to stretch",
|
||||
"task_id": null,
|
||||
"cameo_profiles": null,
|
||||
"orientation": null,
|
||||
"n_frames": null
|
||||
},
|
||||
"tags": [],
|
||||
"generation_id": "68dee38aaf3c81918ed72fd2845abebe",
|
||||
"generation_type": ""
|
||||
},
|
||||
{
|
||||
"id": "68dee35e6c0c8191b6c2a65cdccf5812",
|
||||
"kind": "sora_content_violation",
|
||||
"width": 352,
|
||||
"height": 640,
|
||||
"created_at": null,
|
||||
"prompt": "A man with glasses stretches in front of a t-rex. Butterflies fly by as sun gleams over the horizon. The camera pans, and there are solar punk structures dotting the landscape. A field of pink flowers and an infinite blue sky with clouds. The T-rex roars as the man continues to stretch",
|
||||
"title": null,
|
||||
"reason": null,
|
||||
"reason_str": "We currently do not support uploads of images containing photorealistic people.",
|
||||
"task_id": "task_01k6kag470fwrrkf2k14tkv9pt",
|
||||
"draft_reviewed": false,
|
||||
"creation_config": {
|
||||
"remix_target_post": null,
|
||||
"style_id": null,
|
||||
"inpaint_image": null,
|
||||
"prompt": "A man with glasses stretches in front of a t-rex. Butterflies fly by as sun gleams over the horizon. The camera pans, and there are solar punk structures dotting the landscape. A field of pink flowers and an infinite blue sky with clouds. The T-rex roars as the man continues to stretch",
|
||||
"task_id": null,
|
||||
"cameo_profiles": null,
|
||||
"orientation": null,
|
||||
"n_frames": null
|
||||
},
|
||||
"tags": [],
|
||||
"generation_id": "68dee35e6c0c8191b6c2a65cdccf5812",
|
||||
"generation_type": ""
|
||||
},
|
||||
{
|
||||
"id": "gen_01k6ka1aqdfccbwrbre9jzyf68",
|
||||
"kind": "sora_draft",
|
||||
"url": "https://videos.openai.com/vg-assets/assets%2Ftask_01k6k9xez9eyz8aks3hk4fe01m%2Ftask_01k6k9xez9eyz8aks3hk4fe01m_genid_30b0d7ba-79b5-4877-bc4b-97b78eaa3747_25_10_02_20_32_430033%2Fvideos%2F00000_wm%2Fsrc.mp4?st=2025-10-03T16%3A53%3A19Z&se=2025-10-09T17%3A53%3A19Z&sks=b&skt=2025-10-03T16%3A53%3A19Z&ske=2025-10-09T17%3A53%3A19Z&sktid=a48cca56-e6da-484e-a814-9c849652bcb3&skoid=aa5ddad1-c91a-4f0a-9aca-e20682cc8969&skv=2019-02-02&sv=2018-11-09&sr=b&sp=r&spr=https%2Chttp&sig=oIsyZwXazMFaRRP%2FdjPTJOYQVYPzZnB4%2BVp9RAZmjCE%3D&az=oaivgprodscus",
|
||||
"downloadable_url": "https://videos.openai.com/vg-assets/assets%2Ftask_01k6k9xez9eyz8aks3hk4fe01m%2Ftask_01k6k9xez9eyz8aks3hk4fe01m_genid_30b0d7ba-79b5-4877-bc4b-97b78eaa3747_25_10_02_20_32_430033%2Fvideos%2F00000_wm%2Fsrc.mp4?st=2025-10-03T16%3A53%3A19Z&se=2025-10-09T17%3A53%3A19Z&sks=b&skt=2025-10-03T16%3A53%3A19Z&ske=2025-10-09T17%3A53%3A19Z&sktid=a48cca56-e6da-484e-a814-9c849652bcb3&skoid=aa5ddad1-c91a-4f0a-9aca-e20682cc8969&skv=2019-02-02&sv=2018-11-09&sr=b&sp=r&spr=https%2Chttp&sig=oIsyZwXazMFaRRP%2FdjPTJOYQVYPzZnB4%2BVp9RAZmjCE%3D&az=oaivgprodscus",
|
||||
"width": 352,
|
||||
"height": 640,
|
||||
"created_at": 1759437191.395544,
|
||||
"prompt": "The Green Power Ranger fighting the planter peanuts guy, Mr. Peanut. They're fighting in Neo Tokyo at night, and the scene is lit with bright neon lights and holograms in the background. Blue and purple hues. ",
|
||||
"title": null,
|
||||
"encodings": {
|
||||
"source": {
|
||||
"path": "https://videos.openai.com/vg-assets/assets%2Ftask_01k6k9xez9eyz8aks3hk4fe01m%2Ftask_01k6k9xez9eyz8aks3hk4fe01m_genid_30b0d7ba-79b5-4877-bc4b-97b78eaa3747_25_10_02_20_32_430033%2Fvideos%2F00000_wm%2Fsrc.mp4?st=2025-10-03T16%3A53%3A19Z&se=2025-10-09T17%3A53%3A19Z&sks=b&skt=2025-10-03T16%3A53%3A19Z&ske=2025-10-09T17%3A53%3A19Z&sktid=a48cca56-e6da-484e-a814-9c849652bcb3&skoid=aa5ddad1-c91a-4f0a-9aca-e20682cc8969&skv=2019-02-02&sv=2018-11-09&sr=b&sp=r&spr=https%2Chttp&sig=oIsyZwXazMFaRRP%2FdjPTJOYQVYPzZnB4%2BVp9RAZmjCE%3D&az=oaivgprodscus"
|
||||
},
|
||||
"source_wm": {
|
||||
"path": "https://videos.openai.com/vg-assets/assets%2Ftask_01k6k9xez9eyz8aks3hk4fe01m%2Ftask_01k6k9xez9eyz8aks3hk4fe01m_genid_30b0d7ba-79b5-4877-bc4b-97b78eaa3747_25_10_02_20_32_430033%2Fvideos%2F00000_wm%2Fsrc.mp4?st=2025-10-03T16%3A53%3A19Z&se=2025-10-09T17%3A53%3A19Z&sks=b&skt=2025-10-03T16%3A53%3A19Z&ske=2025-10-09T17%3A53%3A19Z&sktid=a48cca56-e6da-484e-a814-9c849652bcb3&skoid=aa5ddad1-c91a-4f0a-9aca-e20682cc8969&skv=2019-02-02&sv=2018-11-09&sr=b&sp=r&spr=https%2Chttp&sig=oIsyZwXazMFaRRP%2FdjPTJOYQVYPzZnB4%2BVp9RAZmjCE%3D&az=oaivgprodscus"
|
||||
},
|
||||
"thumbnail": {
|
||||
"path": "https://videos.openai.com/vg-assets/assets%2Ftask_01k6k9xez9eyz8aks3hk4fe01m%2Ftask_01k6k9xez9eyz8aks3hk4fe01m_genid_30b0d7ba-79b5-4877-bc4b-97b78eaa3747_25_10_02_20_32_430033%2Fvideos%2F00000_wm%2Fthumbnail.webp?st=2025-10-03T16%3A53%3A19Z&se=2025-10-09T17%3A53%3A19Z&sks=b&skt=2025-10-03T16%3A53%3A19Z&ske=2025-10-09T17%3A53%3A19Z&sktid=a48cca56-e6da-484e-a814-9c849652bcb3&skoid=aa5ddad1-c91a-4f0a-9aca-e20682cc8969&skv=2019-02-02&sv=2018-11-09&sr=b&sp=r&spr=https%2Chttp&sig=S1wEgK7fg5bPXZwSv7Wjbn2msJOBcUpw354WckMhfzc%3D&az=oaivgprodscus"
|
||||
},
|
||||
"unfurl": null,
|
||||
"md": {
|
||||
"path": "https://videos.openai.com/vg-assets/assets%2Ftask_01k6k9xez9eyz8aks3hk4fe01m%2Ftask_01k6k9xez9eyz8aks3hk4fe01m_genid_30b0d7ba-79b5-4877-bc4b-97b78eaa3747_25_10_02_20_32_430033%2Fvideos%2F00000_wm%2Fmd.mp4?st=2025-10-03T16%3A53%3A19Z&se=2025-10-09T17%3A53%3A19Z&sks=b&skt=2025-10-03T16%3A53%3A19Z&ske=2025-10-09T17%3A53%3A19Z&sktid=a48cca56-e6da-484e-a814-9c849652bcb3&skoid=aa5ddad1-c91a-4f0a-9aca-e20682cc8969&skv=2019-02-02&sv=2018-11-09&sr=b&sp=r&spr=https%2Chttp&sig=V35PKIIZnbiDd4HOkqssfjGQM%2FzF3Ot%2FP0CqovZSsws%3D&az=oaivgprodscus"
|
||||
},
|
||||
"gif": {
|
||||
"path": "https://videos.openai.com/vg-assets/assets%2Ftask_01k6k9xez9eyz8aks3hk4fe01m%2Ftask_01k6k9xez9eyz8aks3hk4fe01m_genid_30b0d7ba-79b5-4877-bc4b-97b78eaa3747_25_10_02_20_32_430033%2Fvideos%2F00000_wm%2Fpreview.gif?st=2025-10-03T16%3A53%3A19Z&se=2025-10-09T17%3A53%3A19Z&sks=b&skt=2025-10-03T16%3A53%3A19Z&ske=2025-10-09T17%3A53%3A19Z&sktid=a48cca56-e6da-484e-a814-9c849652bcb3&skoid=aa5ddad1-c91a-4f0a-9aca-e20682cc8969&skv=2019-02-02&sv=2018-11-09&sr=b&sp=r&spr=https%2Chttp&sig=O0mQlm%2Bt9baV283VQZfnD%2BRrjxkRuE1Kw5Nmh1Awe3Q%3D&az=oaivgprodscus"
|
||||
}
|
||||
},
|
||||
"draft_reviewed": true,
|
||||
"task_id": "task_01k6k9xez9eyz8aks3hk4fe01m",
|
||||
"creation_config": {
|
||||
"remix_target_post": null,
|
||||
"style_id": null,
|
||||
"inpaint_image": null,
|
||||
"prompt": "The Green Power Ranger fighting the planter peanuts guy, Mr. Peanut. They're fighting in Neo Tokyo at night, and the scene is lit with bright neon lights and holograms in the background. Blue and purple hues. ",
|
||||
"task_id": null,
|
||||
"cameo_profiles": null,
|
||||
"orientation": null,
|
||||
"n_frames": null
|
||||
},
|
||||
"tags": [],
|
||||
"generation_id": "gen_01k6ka1aqdfccbwrbre9jzyf68",
|
||||
"generation_type": ""
|
||||
}
|
||||
],
|
||||
"cursor": null
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
pub mod list_sora2_drafts;
|
||||
pub (super) mod http_response;
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
[
|
||||
{
|
||||
"id": "task_01k6nkbsdvervv5dsfnnpr27jw",
|
||||
"user": "user-hQ5fc1AV7vNcQLk2yfEFEUbo",
|
||||
"created_at": "2025-10-03T17:54:20.224561Z",
|
||||
"status": "running",
|
||||
"progress_pct": 0.6465000000000001,
|
||||
"progress_pos_in_queue": null,
|
||||
"estimated_queue_wait_time": null,
|
||||
"queue_status_message": null,
|
||||
"priority": 2,
|
||||
"type": "video_gen",
|
||||
"prompt": "SpongeBob buys Ozemspic for Patrick. Patrick is offended. ",
|
||||
"actions": null,
|
||||
"n_variants": 1,
|
||||
"n_frames": 300,
|
||||
"height": 640,
|
||||
"width": 352,
|
||||
"seed": null,
|
||||
"guidance": null,
|
||||
"inpaint_items": [],
|
||||
"interpolation": null,
|
||||
"sdedit": null,
|
||||
"model": "sy_8",
|
||||
"operation": "simple_compose",
|
||||
"is_storyboard": null,
|
||||
"preset_id": null,
|
||||
"remix_config": null,
|
||||
"organization_id": null,
|
||||
"project_id": null,
|
||||
"request_id": null,
|
||||
"tracking_id": null,
|
||||
"generations": [],
|
||||
"num_unsafe_generations": 0,
|
||||
"title": "New Video",
|
||||
"moderation_result": {
|
||||
"type": "passed",
|
||||
"results_by_frame_index": {},
|
||||
"code": null,
|
||||
"is_output_rejection": false,
|
||||
"task_id": "task_01k6nkbsdvervv5dsfnnpr27jw"
|
||||
},
|
||||
"failure_reason": null,
|
||||
"needs_user_review": false
|
||||
}
|
||||
]
|
||||
+19
File diff suppressed because one or more lines are too long
@@ -1,7 +1,10 @@
|
||||
pub mod auth_bearer;
|
||||
pub mod auth_sentinel;
|
||||
pub mod common;
|
||||
pub mod deprecated;
|
||||
pub mod generate_sora2_video;
|
||||
pub mod image_gen;
|
||||
pub mod job_status;
|
||||
pub mod list_media;
|
||||
pub mod upload;
|
||||
pub mod list_classic_tasks;
|
||||
pub mod list_sora2_drafts;
|
||||
pub mod upload;
|
||||
pub mod upload_sora2;
|
||||
@@ -1,3 +1,5 @@
|
||||
pub (crate) mod upload_media_http_request;
|
||||
pub mod upload_media_http_response;
|
||||
|
||||
pub mod upload_media_from_bytes;
|
||||
pub mod upload_media_from_file;
|
||||
+2
-1
@@ -1,6 +1,7 @@
|
||||
use crate::creds::sora_credential_set::SoraCredentialSet;
|
||||
use crate::error::sora_error::SoraError;
|
||||
use crate::requests::upload::upload_media_http_request::{upload_media_http_request, SoraMediaUploadResponse};
|
||||
use crate::requests::upload::upload_media_http_request::upload_media_http_request;
|
||||
use crate::requests::upload::upload_media_http_response::SoraMediaUploadResponse;
|
||||
use std::path::PathBuf;
|
||||
use std::time::Duration;
|
||||
|
||||
|
||||
+4
-13
@@ -1,7 +1,8 @@
|
||||
use crate::creds::sora_credential_set::SoraCredentialSet;
|
||||
use crate::error::sora_client_error::SoraClientError;
|
||||
use crate::error::sora_error::SoraError;
|
||||
use crate::requests::upload::upload_media_http_request::{upload_media_http_request, SoraMediaUploadResponse};
|
||||
use crate::requests::upload::upload_media_http_request::upload_media_http_request;
|
||||
use crate::requests::upload::upload_media_http_response::SoraMediaUploadResponse;
|
||||
use log::error;
|
||||
use std::path::Path;
|
||||
use std::time::Duration;
|
||||
@@ -55,28 +56,18 @@ pub async fn sora_media_upload_from_file<P: AsRef<Path>>(
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::creds::sora_credential_builder::SoraCredentialBuilder;
|
||||
use crate::requests::upload::upload_media_from_file::sora_media_upload_from_file;
|
||||
use crate::test_utils::get_test_credentials::get_test_credentials;
|
||||
use errors::AnyhowResult;
|
||||
use std::fs::read_to_string;
|
||||
use testing::test_file_path::test_file_path;
|
||||
|
||||
#[ignore] // You can manually run "ignore" tests in the IDE, but they won't run in CI.
|
||||
#[tokio::test]
|
||||
pub async fn manual_test() -> AnyhowResult<()> {
|
||||
let cookie = read_to_string(test_file_path("test_data/temp/cookie.txt")?)?;
|
||||
let cookie = cookie.trim().to_string();
|
||||
|
||||
let bearer = read_to_string(test_file_path("test_data/temp/bearer.txt")?)?;
|
||||
let bearer = bearer.trim().to_string();
|
||||
let creds = get_test_credentials()?;
|
||||
|
||||
let image_path = test_file_path("test_data/image/juno.jpg")?; // media_01jqyqgqpwf40tkcapq5bmaz5d
|
||||
|
||||
let creds = SoraCredentialBuilder::new()
|
||||
.with_cookies(&cookie)
|
||||
.with_jwt_bearer_token(&bearer)
|
||||
.build()?;
|
||||
|
||||
let response = sora_media_upload_from_file(
|
||||
image_path,
|
||||
&creds,
|
||||
|
||||
+17
-54
@@ -3,66 +3,18 @@ use crate::creds::sora_credential_set::SoraCredentialSet;
|
||||
use crate::error::sora_client_error::SoraClientError;
|
||||
use crate::error::sora_error::SoraError;
|
||||
use crate::error::sora_generic_api_error::SoraGenericApiError;
|
||||
use crate::requests::upload::upload_media_http_response::SoraMediaUploadResponse;
|
||||
use crate::utils_internal::classify_general_http_error::classify_general_http_error;
|
||||
use log::{error, info};
|
||||
use serde::Deserialize;
|
||||
use std::io::empty;
|
||||
use std::time::Duration;
|
||||
use wreq::header::{ACCEPT, ACCEPT_LANGUAGE, AUTHORIZATION, COOKIE, ORIGIN, REFERER};
|
||||
use wreq::multipart::{Form, Part};
|
||||
use wreq::Client;
|
||||
|
||||
const SORA_UPLOAD_MEDIA_URL: &str = "https://sora.com/backend/uploads";
|
||||
|
||||
// Content-Disposition: form-data; name="file"; filename="imagename.webp"
|
||||
|
||||
/*
|
||||
Response:
|
||||
{
|
||||
"id": "media_01jqt9vt20erx9zvryf3v1pecx",
|
||||
"type": "image",
|
||||
"created_at": "2025-04-02T03:49:04.185867Z",
|
||||
"filename": "Rith_essa.webp",
|
||||
"extension": "jpeg",
|
||||
"mime_type": "image/jpeg",
|
||||
"url": "https://videos.openai.com/vg-assets/assets%2Fclient_upload%2Fmedia%2Fuser-9wf6JFdgRSJLjvSJ53LNAbV8%2Fmedia_01jqt9vt20erx9zvryf3v1pecx.jpeg?st=2025-04-02T02%3A39%3A01Z&se=2025-04-08T03%3A39%3A01Z&sks=b&skt=2025-04-02T02%3A39%3A01Z&ske=2025-04-08T03%3A39%3A01Z&sktid=a48cca56-e6da-484e-a814-9c849652bcb3&skoid=3d249c53-07fa-4ba4-9b65-0bf8eb4ea46a&skv=2019-02-02&sv=2018-11-09&sr=b&sp=r&spr=https%2Chttp&sig=%2BdnTQyw9OFF2kaVoEReT2cVfTqmKjGt18m7P4wKCWCI%3D&az=oaivgprodscus",
|
||||
"width": 1200,
|
||||
"height": 675,
|
||||
"duration_sec": null,
|
||||
"n_frames": 1,
|
||||
"size_bytes": 118501,
|
||||
"thumbnail_url": "https://videos.openai.com/vg-assets/assets%2Fclient_upload%2Fmedia%2Fuser-9wf6JFdgRSJLjvSJ53LNAbV8%2Fmedia_01jqt9vt20erx9zvryf3v1pecx.jpg?st=2025-04-02T02%3A39%3A01Z&se=2025-04-08T03%3A39%3A01Z&sks=b&skt=2025-04-02T02%3A39%3A01Z&ske=2025-04-08T03%3A39%3A01Z&sktid=a48cca56-e6da-484e-a814-9c849652bcb3&skoid=3d249c53-07fa-4ba4-9b65-0bf8eb4ea46a&skv=2019-02-02&sv=2018-11-09&sr=b&sp=r&spr=https%2Chttp&sig=9aU9xKI0BkhRtrTb0RD05%2BT6prBnmXk1I7nV2nTsFbY%3D&az=oaivgprodscus"
|
||||
}
|
||||
|
||||
Error response:
|
||||
|
||||
Failed to upload scene media to Sora: Upload failed with status 401 Unauthorized: {
|
||||
"error": {
|
||||
"message": "Your authentication token has expired. Please try signing in again.",
|
||||
"type": "invalid_request_error",
|
||||
"param": null,
|
||||
"code": "token_expired"
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct SoraMediaUploadResponse {
|
||||
/// This is the media token id.
|
||||
pub id: String,
|
||||
pub r#type: String,
|
||||
pub created_at: String,
|
||||
pub filename: String,
|
||||
pub extension: String,
|
||||
pub mime_type: String,
|
||||
pub url: String,
|
||||
pub width: Option<u32>,
|
||||
pub height: Option<u32>,
|
||||
pub duration_sec: Option<f64>,
|
||||
pub n_frames: Option<u32>,
|
||||
pub size_bytes: u64,
|
||||
pub thumbnail_url: Option<String>,
|
||||
}
|
||||
|
||||
pub (crate) struct SoraMediaUploadRequest<'a> {
|
||||
pub file_path: String,
|
||||
pub credentials: &'a SoraCredentialSet,
|
||||
@@ -94,10 +46,21 @@ pub (crate) async fn upload_media_http_request(
|
||||
let client = Client::new();
|
||||
let mut request_builder = client.post(SORA_UPLOAD_MEDIA_URL)
|
||||
.multipart(form)
|
||||
.header("User-Agent", USER_AGENT)
|
||||
.header("Cookie", &cookie)
|
||||
.header("Authorization", &auth_header);
|
||||
|
||||
.header(ACCEPT, "*/*")
|
||||
.header(ACCEPT_LANGUAGE, "en-US,en;q=0.9")
|
||||
.header(AUTHORIZATION, &auth_header)
|
||||
.header(COOKIE, &cookie)
|
||||
.header(ORIGIN, "https://sora.chatgpt.com")
|
||||
.header("user-agent", USER_AGENT)
|
||||
.header(REFERER, "https://sora.chatgpt.com/explore")
|
||||
.header("priority", "u=1, i")
|
||||
.header("sec-ch-ua", "\"Chromium\";v=\"140\", \"Not=A?Brand\";v=\"24\", \"Google Chrome\";v=\"140\"")
|
||||
.header("sec-ch-ua-mobile", "?0")
|
||||
.header("sec-ch-ua-platform", "macOS")
|
||||
.header("sec-fetch-dest", "empty")
|
||||
.header("sec-fetch-mode", "cors")
|
||||
.header("sec-fetch-site", "same-origin");
|
||||
|
||||
if let Some(timeout) = maybe_timeout {
|
||||
request_builder = request_builder.timeout(timeout);
|
||||
}
|
||||
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
use serde_derive::Deserialize;
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct SoraMediaUploadResponse {
|
||||
/// This is the media token id.
|
||||
pub id: String,
|
||||
pub r#type: String,
|
||||
pub created_at: String,
|
||||
pub filename: String,
|
||||
pub extension: String,
|
||||
pub mime_type: String,
|
||||
pub url: String,
|
||||
pub width: Option<u32>,
|
||||
pub height: Option<u32>,
|
||||
pub duration_sec: Option<f64>,
|
||||
pub n_frames: Option<u32>,
|
||||
pub size_bytes: u64,
|
||||
pub thumbnail_url: Option<String>,
|
||||
}
|
||||
|
||||
// Content-Disposition: form-data; name="file"; filename="imagename.webp"
|
||||
|
||||
/*
|
||||
Response:
|
||||
{
|
||||
"id": "media_01jqt9vt20erx9zvryf3v1pecx",
|
||||
"type": "image",
|
||||
"created_at": "2025-04-02T03:49:04.185867Z",
|
||||
"filename": "Rith_essa.webp",
|
||||
"extension": "jpeg",
|
||||
"mime_type": "image/jpeg",
|
||||
"url": "https://videos.openai.com/vg-assets/assets%2Fclient_upload%2Fmedia%2Fuser-9wf6JFdgRSJLjvSJ53LNAbV8%2Fmedia_01jqt9vt20erx9zvryf3v1pecx.jpeg?st=2025-04-02T02%3A39%3A01Z&se=2025-04-08T03%3A39%3A01Z&sks=b&skt=2025-04-02T02%3A39%3A01Z&ske=2025-04-08T03%3A39%3A01Z&sktid=a48cca56-e6da-484e-a814-9c849652bcb3&skoid=3d249c53-07fa-4ba4-9b65-0bf8eb4ea46a&skv=2019-02-02&sv=2018-11-09&sr=b&sp=r&spr=https%2Chttp&sig=%2BdnTQyw9OFF2kaVoEReT2cVfTqmKjGt18m7P4wKCWCI%3D&az=oaivgprodscus",
|
||||
"width": 1200,
|
||||
"height": 675,
|
||||
"duration_sec": null,
|
||||
"n_frames": 1,
|
||||
"size_bytes": 118501,
|
||||
"thumbnail_url": "https://videos.openai.com/vg-assets/assets%2Fclient_upload%2Fmedia%2Fuser-9wf6JFdgRSJLjvSJ53LNAbV8%2Fmedia_01jqt9vt20erx9zvryf3v1pecx.jpg?st=2025-04-02T02%3A39%3A01Z&se=2025-04-08T03%3A39%3A01Z&sks=b&skt=2025-04-02T02%3A39%3A01Z&ske=2025-04-08T03%3A39%3A01Z&sktid=a48cca56-e6da-484e-a814-9c849652bcb3&skoid=3d249c53-07fa-4ba4-9b65-0bf8eb4ea46a&skv=2019-02-02&sv=2018-11-09&sr=b&sp=r&spr=https%2Chttp&sig=9aU9xKI0BkhRtrTb0RD05%2BT6prBnmXk1I7nV2nTsFbY%3D&az=oaivgprodscus"
|
||||
}
|
||||
|
||||
Error response:
|
||||
|
||||
Failed to upload scene media to Sora: Upload failed with status 401 Unauthorized: {
|
||||
"error": {
|
||||
"message": "Your authentication token has expired. Please try signing in again.",
|
||||
"type": "invalid_request_error",
|
||||
"param": null,
|
||||
"code": "token_expired"
|
||||
}
|
||||
}
|
||||
*/
|
||||
@@ -0,0 +1,21 @@
|
||||
use serde_derive::Deserialize;
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct SoraMediaUploadResponse {
|
||||
/// This is the media token id.
|
||||
pub id: String,
|
||||
|
||||
// NB: Don't need these, so commented out:
|
||||
//pub r#type: String,
|
||||
//pub created_at: String,
|
||||
//pub filename: String,
|
||||
//pub extension: String,
|
||||
//pub mime_type: String,
|
||||
//pub url: String,
|
||||
//pub width: Option<u32>,
|
||||
//pub height: Option<u32>,
|
||||
//pub duration_sec: Option<f64>,
|
||||
//pub n_frames: Option<u32>,
|
||||
//pub size_bytes: u64,
|
||||
//pub thumbnail_url: Option<String>,
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
//pub mod upload_media_sora2_from_bytes;
|
||||
//pub mod upload_media_sora2_from_file;
|
||||
//
|
||||
//pub mod upload_media_sora2_http_request;
|
||||
//mod http_response;
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
use crate::creds::sora_credential_set::SoraCredentialSet;
|
||||
use crate::error::sora_error::SoraError;
|
||||
use crate::requests::upload::upload_media_http_request::{upload_media_http_request, SoraMediaUploadResponse};
|
||||
use std::path::PathBuf;
|
||||
use std::time::Duration;
|
||||
|
||||
/// Upload bytes.
|
||||
/// The underlying reqwest lib needs to own the bytes, so we can't pass as a reference.
|
||||
pub async fn sora_media_upload_from_bytes(
|
||||
bytes: Vec<u8>,
|
||||
file_name: String,
|
||||
creds: &SoraCredentialSet,
|
||||
maybe_timeout: Option<Duration>,
|
||||
) -> Result<SoraMediaUploadResponse, SoraError> {
|
||||
let file_path = PathBuf::from(&file_name);
|
||||
|
||||
// TODO: Read file magic bytes first, then fall back to this.
|
||||
let mime_type = match file_path.extension().and_then(|e| e.to_str()) {
|
||||
Some("jpg") | Some("jpeg") => "image/jpeg",
|
||||
Some("png") => "image/png",
|
||||
// Some("webp") => "image/webp",
|
||||
// Some("gif") => "image/gif",
|
||||
// Some("mp4") => "video/mp4",
|
||||
// Some("mov") => "video/quicktime",
|
||||
// Some("webm") => "video/webm",
|
||||
_ => "application/octet-stream",
|
||||
};
|
||||
|
||||
Ok(upload_media_http_request(bytes, file_name, mime_type, creds, maybe_timeout).await?)
|
||||
}
|
||||
+95
@@ -0,0 +1,95 @@
|
||||
use crate::creds::sora_credential_set::SoraCredentialSet;
|
||||
use crate::error::sora_client_error::SoraClientError;
|
||||
use crate::error::sora_error::SoraError;
|
||||
use crate::requests::upload::upload_media_http_request::{upload_media_http_request, SoraMediaUploadResponse};
|
||||
use log::error;
|
||||
use std::path::Path;
|
||||
use std::time::Duration;
|
||||
use tokio::fs::File;
|
||||
use tokio::io::AsyncReadExt;
|
||||
|
||||
/// Try to prevent buffer reallocations.
|
||||
/// There's a better way to implement this.
|
||||
const INITIAL_BUFFER_SIZE : usize = 1024*1024;
|
||||
|
||||
pub async fn sora_media_upload_from_file<P: AsRef<Path>>(
|
||||
file_path: P,
|
||||
creds: &SoraCredentialSet,
|
||||
maybe_timeout: Option<Duration>,
|
||||
) -> Result<SoraMediaUploadResponse, SoraError> {
|
||||
let mut file = File::open(&file_path).await
|
||||
.map_err(|err| {
|
||||
error!("Failed to open file for upload: {}", err);
|
||||
SoraClientError::FileForUploadReadError(err)
|
||||
})?;
|
||||
|
||||
let mut buffer = Vec::with_capacity(INITIAL_BUFFER_SIZE);
|
||||
|
||||
file.read_to_end(&mut buffer).await
|
||||
.map_err(|err| {
|
||||
error!("Failed to read file for upload: {}", err);
|
||||
SoraClientError::FileForUploadReadError(err)
|
||||
})?;
|
||||
|
||||
let filename = file_path.as_ref().file_name()
|
||||
.ok_or_else(|| SoraClientError::FileForUploadHasInvalidPath)?
|
||||
.to_string_lossy()
|
||||
.to_string();
|
||||
|
||||
let maybe_ext = file_path.as_ref().extension().and_then(|e| e.to_str());
|
||||
|
||||
// TODO: Read file magic bytes first, then fall back to this.
|
||||
let mime_type = match maybe_ext {
|
||||
Some("jpg") | Some("jpeg") => "image/jpeg",
|
||||
Some("png") => "image/png",
|
||||
// Some("webp") => "image/webp",
|
||||
// Some("gif") => "image/gif",
|
||||
// Some("mp4") => "video/mp4",
|
||||
// Some("mov") => "video/quicktime",
|
||||
// Some("webm") => "video/webm",
|
||||
_ => "application/octet-stream",
|
||||
};
|
||||
|
||||
Ok(upload_media_http_request(buffer, filename, mime_type, creds, maybe_timeout).await?)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::creds::sora_credential_builder::SoraCredentialBuilder;
|
||||
use crate::requests::upload::upload_media_from_file::sora_media_upload_from_file;
|
||||
use errors::AnyhowResult;
|
||||
use std::fs::read_to_string;
|
||||
use testing::test_file_path::test_file_path;
|
||||
|
||||
#[ignore] // You can manually run "ignore" tests in the IDE, but they won't run in CI.
|
||||
#[tokio::test]
|
||||
pub async fn manual_test() -> AnyhowResult<()> {
|
||||
let cookie = read_to_string(test_file_path("test_data/temp/cookie.txt")?)?;
|
||||
let cookie = cookie.trim().to_string();
|
||||
|
||||
let bearer = read_to_string(test_file_path("test_data/temp/bearer.txt")?)?;
|
||||
let bearer = bearer.trim().to_string();
|
||||
|
||||
let image_path = test_file_path("test_data/image/juno.jpg")?; // media_01jqyqgqpwf40tkcapq5bmaz5d
|
||||
|
||||
let creds = SoraCredentialBuilder::new()
|
||||
.with_cookies(&cookie)
|
||||
.with_jwt_bearer_token(&bearer)
|
||||
.build()?;
|
||||
|
||||
let response = sora_media_upload_from_file(
|
||||
image_path,
|
||||
&creds,
|
||||
None,
|
||||
).await?;
|
||||
|
||||
println!("media: {:?}", response);
|
||||
|
||||
println!("media_id: {}", response.id);
|
||||
println!("media_url: {}", response.url);
|
||||
|
||||
assert!(response.id.starts_with("media_"));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
+88
@@ -0,0 +1,88 @@
|
||||
use crate::constants::user_agent::USER_AGENT;
|
||||
use crate::creds::sora_credential_set::SoraCredentialSet;
|
||||
use crate::error::sora_client_error::SoraClientError;
|
||||
use crate::error::sora_error::SoraError;
|
||||
use crate::error::sora_generic_api_error::SoraGenericApiError;
|
||||
use crate::utils_internal::classify_general_http_error::classify_general_http_error;
|
||||
use log::{error, info};
|
||||
use serde::Deserialize;
|
||||
use std::io::empty;
|
||||
use std::time::Duration;
|
||||
use wreq::multipart::{Form, Part};
|
||||
use wreq::Client;
|
||||
use crate::requests::list_sora2_drafts::list_sora2_drafts::Draft;
|
||||
|
||||
const SORA_UPLOAD_MEDIA_URL: &str = "https://sora.chatgpt.com/backend/uploads";
|
||||
|
||||
|
||||
|
||||
pub (crate) struct SoraMediaUploadArgs<'a> {
|
||||
pub credentials: &'a SoraCredentialSet,
|
||||
pub file_bytes: Vec<u8>, // NB: Reqwest needs to own the bytes, so we can't pass as a reference.
|
||||
pub filename: String, // NB: Reqwest needs to own the bytes, so we can't pass as a reference.
|
||||
pub mime_type: &'a str,
|
||||
pub maybe_timeout: Option<Duration>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct SoraMediaUploadResult {
|
||||
pub drafts: Vec<Draft>,
|
||||
}
|
||||
|
||||
pub (crate) async fn upload_media_sora2_http_request(
|
||||
args: SoraMediaUploadArgs<'_>
|
||||
) -> Result<SoraMediaUploadResult, SoraError> {
|
||||
|
||||
// Create multipart form
|
||||
let part = Part::bytes(args.file_bytes) // NB: Reqwest needs to own the bytes.
|
||||
.file_name(args.filename) // NB: Reqwest needs to own the bytes
|
||||
.mime_str(args.mime_type)
|
||||
.map_err(|e| SoraClientError::MultipartFormError(e))?;
|
||||
|
||||
let form = Form::new().part("file", part);
|
||||
|
||||
let cookie = args.credentials.cookies.to_string();
|
||||
let auth_header = args.credentials.jwt_bearer_token
|
||||
.as_ref()
|
||||
.ok_or(SoraClientError::NoBearerTokenForRequest)?
|
||||
.to_authorization_header_value();
|
||||
|
||||
// Make API request
|
||||
let client = Client::new();
|
||||
let mut request_builder = client.post(SORA_UPLOAD_MEDIA_URL)
|
||||
.multipart(form)
|
||||
.header("User-Agent", USER_AGENT)
|
||||
.header("Cookie", &cookie)
|
||||
.header("Authorization", &auth_header);
|
||||
|
||||
if let Some(timeout) = args.maybe_timeout {
|
||||
request_builder = request_builder.timeout(timeout);
|
||||
}
|
||||
|
||||
let response = request_builder.send()
|
||||
.await
|
||||
.map_err(|err| {
|
||||
SoraGenericApiError::WreqError(err)
|
||||
})?;
|
||||
|
||||
// Check response status
|
||||
if !response.status().is_success() {
|
||||
info!("Error uploading image: {:?}", response.status());
|
||||
let error = classify_general_http_error(response).await;
|
||||
return Err(error);
|
||||
}
|
||||
|
||||
let response_body = &response.text().await
|
||||
.map_err(|err| {
|
||||
error!("Error reading response body while attempting file upload: {:?}", err);
|
||||
SoraGenericApiError::WreqError(err)
|
||||
})?;
|
||||
|
||||
let upload_response : SoraMediaUploadResponse = serde_json::from_str(&response_body)
|
||||
.map_err(|err| {
|
||||
error!("Error parsing response body while attempting file upload: {:?}", err);
|
||||
SoraGenericApiError::SerdeResponseParseErrorWithBody(err, response_body.to_string())
|
||||
})?;
|
||||
|
||||
Ok(upload_response)
|
||||
}
|
||||
+21
File diff suppressed because one or more lines are too long
+15
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"id": "media_01k6nscysdfxktrvshw0qmkmv3",
|
||||
"type": "image",
|
||||
"created_at": "2025-10-03T19:39:49.938416Z",
|
||||
"filename": "artcraft_kscc8n1yj6tyqcxm5btfp32v9b412kz9.png",
|
||||
"extension": "png",
|
||||
"mime_type": "image/png",
|
||||
"url": "https://videos.openai.com/vg-assets/assets%2Fclient_upload%2Fmedia%2F984cd7658c4eeb0be057d5d91c4faf3d2e5fd579%2Fmedia_01k6nscysdfxktrvshw0qmkmv3.png?st=2025-10-03T18%3A03%3A28Z&se=2025-10-09T19%3A03%3A28Z&sks=b&skt=2025-10-03T18%3A03%3A28Z&ske=2025-10-09T19%3A03%3A28Z&sktid=a48cca56-e6da-484e-a814-9c849652bcb3&skoid=aa5ddad1-c91a-4f0a-9aca-e20682cc8969&skv=2019-02-02&sv=2018-11-09&sr=b&sp=r&spr=https%2Chttp&sig=bUkwbc6pZTrGs4YiV9Q6I%2FwP7JPgtexoyvC5b2Aa6CE%3D&az=oaivgprodscus",
|
||||
"width": 1248,
|
||||
"height": 832,
|
||||
"duration_sec": null,
|
||||
"n_frames": 1,
|
||||
"size_bytes": 2278064,
|
||||
"thumbnail_url": "https://videos.openai.com/vg-assets/assets%2Fclient_upload%2Fmedia%2F984cd7658c4eeb0be057d5d91c4faf3d2e5fd579%2Fmedia_01k6nscysdfxktrvshw0qmkmv3.jpg?st=2025-10-03T18%3A03%3A28Z&se=2025-10-09T19%3A03%3A28Z&sks=b&skt=2025-10-03T18%3A03%3A28Z&ske=2025-10-09T19%3A03%3A28Z&sktid=a48cca56-e6da-484e-a814-9c849652bcb3&skoid=aa5ddad1-c91a-4f0a-9aca-e20682cc8969&skv=2019-02-02&sv=2018-11-09&sr=b&sp=r&spr=https%2Chttp&sig=XhdbsZRUv0OiVGHf%2Fc9YWCDhWBqXy5YzqO32ahqKc1E%3D&az=oaivgprodscus"
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
use crate::creds::sora_credential_builder::SoraCredentialBuilder;
|
||||
use crate::creds::sora_credential_set::SoraCredentialSet;
|
||||
use errors::AnyhowResult;
|
||||
use std::fs::read_to_string;
|
||||
|
||||
#[cfg(test)]
|
||||
pub fn get_test_credentials() -> AnyhowResult<SoraCredentialSet> {
|
||||
// TODO: This should work on other folks' machines when they exist
|
||||
// This is so brittle...
|
||||
let sentinel = read_to_string("/Users/bt/Artcraft/credentials/sora_sentinel.txt")?;
|
||||
let sentinel = sentinel.trim().to_string();
|
||||
|
||||
let cookie = read_to_string("/Users/bt/Artcraft/credentials/sora_cookies.txt")?;
|
||||
let cookie = cookie.trim().to_string();
|
||||
|
||||
let bearer = read_to_string("/Users/bt/Artcraft/credentials/sora_bearer_token.txt")?;
|
||||
let bearer = bearer.trim().to_string();
|
||||
|
||||
let creds = SoraCredentialBuilder::new()
|
||||
.with_cookies(&cookie)
|
||||
.with_jwt_bearer_token(&bearer)
|
||||
.with_sora_sentinel(&sentinel)
|
||||
.build()?;
|
||||
|
||||
Ok(creds)
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
#[cfg(test)]
|
||||
pub (crate) mod get_test_credentials;
|
||||
@@ -31,7 +31,6 @@ build_metadata.workspace = true
|
||||
cookie_store.workspace = true
|
||||
enums.workspace = true
|
||||
errors.workspace = true
|
||||
fal_client.workspace = true
|
||||
filesys.workspace = true
|
||||
idempotency.workspace = true
|
||||
images.workspace = true
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
use crate::core::artcraft_error::ArtcraftError;
|
||||
use base64::DecodeError;
|
||||
use errors::AnyhowError;
|
||||
use fal_client::error::fal_error_plus::FalErrorPlus;
|
||||
use midjourney_client::error::midjourney_error::MidjourneyError;
|
||||
use openai_sora_client::error::sora_error::SoraError;
|
||||
use storyteller_client::error::storyteller_error::StorytellerError;
|
||||
//use fal_client::error::fal_error_plus::FalErrorPlus;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum GenerateError {
|
||||
@@ -14,6 +14,9 @@ pub enum GenerateError {
|
||||
|
||||
/// We couldn't find a provider to dispatch the request to.
|
||||
NoProviderAvailable,
|
||||
|
||||
/// We pulled out Fal (for now) - it's impacting build speeds. We'll add it in the future.
|
||||
FalNoLongerSupported,
|
||||
|
||||
/// There was a billing, credits, or payments issue.
|
||||
BillingIssue(BillingIssueReason),
|
||||
@@ -73,7 +76,7 @@ pub enum BillingProvider {
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum ProviderFailureReason {
|
||||
Fal(FalErrorPlus),
|
||||
//Fal(FalErrorPlus),
|
||||
MidjourneyError(MidjourneyError),
|
||||
/// NB: The midjourney client doesn't categorize all errors, so we have to do so on our end.
|
||||
MidjourneyJobEnqueueFailed,
|
||||
@@ -141,11 +144,11 @@ impl From<ArtcraftError> for GenerateError {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<FalErrorPlus> for GenerateError {
|
||||
fn from(value: FalErrorPlus) -> Self {
|
||||
Self::ProviderFailure(ProviderFailureReason::Fal(value))
|
||||
}
|
||||
}
|
||||
//impl From<FalErrorPlus> for GenerateError {
|
||||
// fn from(value: FalErrorPlus) -> Self {
|
||||
// Self::ProviderFailure(ProviderFailureReason::Fal(value))
|
||||
// }
|
||||
//}
|
||||
|
||||
impl From<MidjourneyError> for GenerateError {
|
||||
fn from(value: MidjourneyError) -> Self {
|
||||
|
||||
+2
-12
@@ -7,6 +7,7 @@ use crate::core::commands::response::failure_response_wrapper::{CommandErrorResp
|
||||
use crate::core::commands::response::shorthand::{Response, ResponseOrErrorType};
|
||||
use crate::core::commands::response::success_response_wrapper::SerializeMarker;
|
||||
use crate::core::events::basic_sendable_event_trait::BasicSendableEvent;
|
||||
use crate::core::events::functional_events::credits_balance_changed_event::CreditsBalanceChangedEvent;
|
||||
use crate::core::events::generation_events::common::{GenerationAction, GenerationServiceProvider};
|
||||
use crate::core::events::generation_events::generation_enqueue_failure_event::GenerationEnqueueFailureEvent;
|
||||
use crate::core::events::generation_events::generation_enqueue_success_event::GenerationEnqueueSuccessEvent;
|
||||
@@ -18,8 +19,6 @@ use crate::core::state::provider_priority::{Provider, ProviderPriorityStore};
|
||||
use crate::core::state::task_database::TaskDatabase;
|
||||
use crate::core::utils::get_url_file_extension::get_url_file_extension;
|
||||
use crate::core::utils::simple_http_download::simple_http_download;
|
||||
use crate::services::fal::state::fal_credential_manager::FalCredentialManager;
|
||||
use crate::services::fal::state::fal_task_queue::FalTaskQueue;
|
||||
use crate::services::sora::state::sora_credential_manager::SoraCredentialManager;
|
||||
use crate::services::sora::state::sora_task_queue::SoraTaskQueue;
|
||||
use crate::services::storyteller::state::storyteller_credential_manager::StorytellerCredentialManager;
|
||||
@@ -35,12 +34,11 @@ use openai_sora_client::requests::image_gen::common::{ImageSize, NumImages};
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use sqlite_tasks::queries::create_task::{create_task, CreateTaskArgs};
|
||||
use std::time::Duration;
|
||||
use storyteller_client::error::storyteller_error::StorytellerError;
|
||||
use storyteller_client::endpoints::media_files::get_media_file::get_media_file;
|
||||
use storyteller_client::error::storyteller_error::StorytellerError;
|
||||
use storyteller_client::utils::api_host::ApiHost;
|
||||
use tauri::{AppHandle, Manager, State};
|
||||
use tokens::tokens::media_files::MediaFileToken;
|
||||
use crate::core::events::functional_events::credits_balance_changed_event::CreditsBalanceChangedEvent;
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
pub struct EnqueueImageBgRemovalCommand {
|
||||
@@ -103,8 +101,6 @@ pub async fn enqueue_image_bg_removal_command(
|
||||
provider_priority_store: State<'_, ProviderPriorityStore>,
|
||||
task_database: State<'_, TaskDatabase>,
|
||||
storyteller_creds_manager: State<'_, StorytellerCredentialManager>,
|
||||
fal_creds_manager: State<'_, FalCredentialManager>,
|
||||
fal_task_queue: State<'_, FalTaskQueue>,
|
||||
) -> ResponseOrErrorType<EnqueueImageBgRemovalSuccessResponse, EnqueueImageBgRemovalErrorType> {
|
||||
|
||||
info!("enqueue_image_bg_removal_command called");
|
||||
@@ -117,8 +113,6 @@ pub async fn enqueue_image_bg_removal_command(
|
||||
&provider_priority_store,
|
||||
&task_database,
|
||||
&storyteller_creds_manager,
|
||||
&fal_creds_manager,
|
||||
&fal_task_queue,
|
||||
).await;
|
||||
|
||||
match result {
|
||||
@@ -167,8 +161,6 @@ pub async fn handle_request(
|
||||
provider_priority_store: &ProviderPriorityStore,
|
||||
task_database: &TaskDatabase,
|
||||
storyteller_creds_manager: &StorytellerCredentialManager,
|
||||
fal_creds_manager: &FalCredentialManager,
|
||||
fal_task_queue: &FalTaskQueue,
|
||||
) -> Result<TaskEnqueueSuccess, GenerateError> {
|
||||
|
||||
// TODO(bt,2025-07-07): Other model/provider routing...
|
||||
@@ -180,8 +172,6 @@ pub async fn handle_request(
|
||||
app_env_configs,
|
||||
provider_priority_store,
|
||||
storyteller_creds_manager,
|
||||
fal_creds_manager,
|
||||
fal_task_queue,
|
||||
).await?;
|
||||
|
||||
let result = success_event
|
||||
|
||||
-6
@@ -9,8 +9,6 @@ use crate::core::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess;
|
||||
use crate::core::state::app_env_configs::app_env_configs::AppEnvConfigs;
|
||||
use crate::core::state::data_dir::app_data_root::AppDataRoot;
|
||||
use crate::core::state::provider_priority::{Provider, ProviderPriorityStore};
|
||||
use crate::services::fal::state::fal_credential_manager::FalCredentialManager;
|
||||
use crate::services::fal::state::fal_task_queue::FalTaskQueue;
|
||||
use crate::services::sora::state::sora_credential_manager::SoraCredentialManager;
|
||||
use crate::services::sora::state::sora_task_queue::SoraTaskQueue;
|
||||
use crate::services::storyteller::state::storyteller_credential_manager::StorytellerCredentialManager;
|
||||
@@ -24,8 +22,6 @@ pub async fn handle_generic_bg_removal(
|
||||
app_env_configs: &AppEnvConfigs,
|
||||
provider_priority_store: &ProviderPriorityStore,
|
||||
storyteller_creds_manager: &StorytellerCredentialManager,
|
||||
fal_creds_manager: &FalCredentialManager,
|
||||
fal_task_queue: &FalTaskQueue,
|
||||
) -> Result<TaskEnqueueSuccess, GenerateError> {
|
||||
|
||||
let priority = provider_priority_store.get_priority()?;
|
||||
@@ -57,8 +53,6 @@ pub async fn handle_generic_bg_removal(
|
||||
app,
|
||||
app_data_root,
|
||||
app_env_configs,
|
||||
fal_creds_manager,
|
||||
fal_task_queue,
|
||||
).await;
|
||||
}
|
||||
}
|
||||
|
||||
-3
@@ -9,8 +9,6 @@ use crate::core::state::app_env_configs::app_env_configs::AppEnvConfigs;
|
||||
use crate::core::state::data_dir::app_data_root::AppDataRoot;
|
||||
use crate::core::state::provider_priority::ProviderPriorityStore;
|
||||
use crate::core::utils::save_base64_image_to_temp_dir::save_base64_image_to_temp_dir;
|
||||
use crate::services::fal::state::fal_credential_manager::FalCredentialManager;
|
||||
use crate::services::fal::state::fal_task_queue::FalTaskQueue;
|
||||
use crate::services::sora::state::sora_credential_manager::SoraCredentialManager;
|
||||
use crate::services::sora::state::sora_task_queue::SoraTaskQueue;
|
||||
use crate::services::storyteller::state::storyteller_credential_manager::StorytellerCredentialManager;
|
||||
@@ -23,7 +21,6 @@ use artcraft_api_defs::generate::image::generate_flux_pro_11_ultra_text_to_image
|
||||
use artcraft_api_defs::generate::image::remove_image_background::RemoveImageBackgroundRequest;
|
||||
use enums::common::generation_provider::GenerationProvider;
|
||||
use enums::tauri::tasks::task_type::TaskType;
|
||||
use fal_client::requests::queue::image_gen::enqueue_flux_pro_11_ultra_text_to_image::{enqueue_flux_pro_11_ultra_text_to_image, FluxPro11UltraTextToImageArgs};
|
||||
use idempotency::uuid::generate_random_uuid;
|
||||
use log::{error, info};
|
||||
use storyteller_client::credentials::storyteller_credential_set::StorytellerCredentialSet;
|
||||
|
||||
-4
@@ -3,8 +3,6 @@ use crate::core::commands::enqueue::image_bg_removal::enqueue_image_bg_removal_c
|
||||
use crate::core::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess;
|
||||
use crate::core::state::app_env_configs::app_env_configs::AppEnvConfigs;
|
||||
use crate::core::state::data_dir::app_data_root::AppDataRoot;
|
||||
use crate::services::fal::state::fal_credential_manager::FalCredentialManager;
|
||||
use crate::services::fal::state::fal_task_queue::FalTaskQueue;
|
||||
use tauri::AppHandle;
|
||||
|
||||
pub async fn handle_generic_bg_removal_fal(
|
||||
@@ -12,8 +10,6 @@ pub async fn handle_generic_bg_removal_fal(
|
||||
app: &AppHandle,
|
||||
app_data_root: &AppDataRoot,
|
||||
app_env_configs: &AppEnvConfigs,
|
||||
fal_creds_manager: &FalCredentialManager,
|
||||
fal_task_queue: &FalTaskQueue,
|
||||
) -> Result<TaskEnqueueSuccess, GenerateError> {
|
||||
Err(GenerateError::NotYetImplemented("TODO: Implement fal background removal handling".to_string()))
|
||||
}
|
||||
|
||||
-14
@@ -20,8 +20,6 @@ use crate::core::state::provider_priority::ProviderPriorityStore;
|
||||
use crate::core::state::task_database::TaskDatabase;
|
||||
use crate::core::utils::get_url_file_extension::get_url_file_extension;
|
||||
use crate::core::utils::simple_http_download::simple_http_download;
|
||||
use crate::services::fal::state::fal_credential_manager::FalCredentialManager;
|
||||
use crate::services::fal::state::fal_task_queue::FalTaskQueue;
|
||||
use crate::services::sora::state::sora_credential_manager::SoraCredentialManager;
|
||||
use crate::services::sora::state::sora_task_queue::SoraTaskQueue;
|
||||
use crate::services::storyteller::state::storyteller_credential_manager::StorytellerCredentialManager;
|
||||
@@ -169,8 +167,6 @@ pub async fn enqueue_contextual_edit_image_command(
|
||||
provider_priority_store: State<'_, ProviderPriorityStore>,
|
||||
task_database: State<'_, TaskDatabase>,
|
||||
storyteller_creds_manager: State<'_, StorytellerCredentialManager>,
|
||||
fal_creds_manager: State<'_, FalCredentialManager>,
|
||||
fal_task_queue: State<'_, FalTaskQueue>,
|
||||
sora_creds_manager: State<'_, SoraCredentialManager>,
|
||||
sora_task_queue: State<'_, SoraTaskQueue>,
|
||||
) -> ResponseOrErrorType<EnqueueContextualEditImageSuccessResponse, EnqueueContextualEditImageErrorType> {
|
||||
@@ -186,8 +182,6 @@ pub async fn enqueue_contextual_edit_image_command(
|
||||
&provider_priority_store,
|
||||
&task_database,
|
||||
&storyteller_creds_manager,
|
||||
&fal_creds_manager,
|
||||
&fal_task_queue,
|
||||
&sora_creds_manager,
|
||||
&sora_task_queue,
|
||||
).await;
|
||||
@@ -238,8 +232,6 @@ pub async fn handle_request(
|
||||
provider_priority_store: &ProviderPriorityStore,
|
||||
task_database: &TaskDatabase,
|
||||
storyteller_creds_manager: &StorytellerCredentialManager,
|
||||
fal_creds_manager: &FalCredentialManager,
|
||||
fal_task_queue: &FalTaskQueue,
|
||||
sora_creds_manager: &SoraCredentialManager,
|
||||
sora_task_queue: &SoraTaskQueue,
|
||||
) -> Result<TaskEnqueueSuccess, GenerateError> {
|
||||
@@ -255,8 +247,6 @@ pub async fn handle_request(
|
||||
app_env_configs,
|
||||
provider_priority_store,
|
||||
storyteller_creds_manager,
|
||||
fal_creds_manager,
|
||||
fal_task_queue,
|
||||
sora_creds_manager,
|
||||
sora_task_queue,
|
||||
).await?
|
||||
@@ -269,8 +259,6 @@ pub async fn handle_request(
|
||||
app_env_configs,
|
||||
provider_priority_store,
|
||||
storyteller_creds_manager,
|
||||
fal_creds_manager,
|
||||
fal_task_queue,
|
||||
sora_creds_manager,
|
||||
sora_task_queue,
|
||||
).await?
|
||||
@@ -283,8 +271,6 @@ pub async fn handle_request(
|
||||
app_env_configs,
|
||||
provider_priority_store,
|
||||
storyteller_creds_manager,
|
||||
fal_creds_manager,
|
||||
fal_task_queue,
|
||||
sora_creds_manager,
|
||||
sora_task_queue,
|
||||
).await?
|
||||
|
||||
-4
@@ -7,8 +7,6 @@ use crate::core::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess;
|
||||
use crate::core::state::app_env_configs::app_env_configs::AppEnvConfigs;
|
||||
use crate::core::state::data_dir::app_data_root::AppDataRoot;
|
||||
use crate::core::state::provider_priority::{Provider, ProviderPriorityStore};
|
||||
use crate::services::fal::state::fal_credential_manager::FalCredentialManager;
|
||||
use crate::services::fal::state::fal_task_queue::FalTaskQueue;
|
||||
use crate::services::sora::state::sora_credential_manager::SoraCredentialManager;
|
||||
use crate::services::sora::state::sora_task_queue::SoraTaskQueue;
|
||||
use crate::services::storyteller::state::storyteller_credential_manager::StorytellerCredentialManager;
|
||||
@@ -22,8 +20,6 @@ pub async fn handle_flux_kontext_edit(
|
||||
app_env_configs: &AppEnvConfigs,
|
||||
provider_priority_store: &ProviderPriorityStore,
|
||||
storyteller_creds_manager: &StorytellerCredentialManager,
|
||||
fal_creds_manager: &FalCredentialManager,
|
||||
fal_task_queue: &FalTaskQueue,
|
||||
sora_creds_manager: &SoraCredentialManager,
|
||||
sora_task_queue: &SoraTaskQueue,
|
||||
) -> Result<TaskEnqueueSuccess, GenerateError> {
|
||||
|
||||
-4
@@ -7,8 +7,6 @@ use crate::core::events::generation_events::generation_enqueue_failure_event::Ge
|
||||
use crate::core::state::app_env_configs::app_env_configs::AppEnvConfigs;
|
||||
use crate::core::state::data_dir::app_data_root::AppDataRoot;
|
||||
use crate::core::state::provider_priority::ProviderPriorityStore;
|
||||
use crate::services::fal::state::fal_credential_manager::FalCredentialManager;
|
||||
use crate::services::fal::state::fal_task_queue::FalTaskQueue;
|
||||
use crate::services::sora::state::sora_credential_manager::SoraCredentialManager;
|
||||
use crate::services::sora::state::sora_task_queue::SoraTaskQueue;
|
||||
use crate::services::storyteller::state::storyteller_credential_manager::StorytellerCredentialManager;
|
||||
@@ -21,8 +19,6 @@ use artcraft_api_defs::generate::image::generate_flux_pro_11_text_to_image::Gene
|
||||
use artcraft_api_defs::generate::image::generate_flux_pro_11_ultra_text_to_image::GenerateFluxPro11UltraTextToImageRequest;
|
||||
use enums::common::generation_provider::GenerationProvider;
|
||||
use enums::tauri::tasks::task_type::TaskType;
|
||||
use fal_client::requests::queue::image_gen::enqueue_flux_pro_11_ultra_text_to_image::{enqueue_flux_pro_11_ultra_text_to_image, FluxPro11UltraTextToImageArgs};
|
||||
use fal_client::requests::webhook::image::edit::enqueue_flux_pro_kontext_max_edit_webhook::FluxProKontextMaxNumImages;
|
||||
use idempotency::uuid::generate_random_uuid;
|
||||
use log::{error, info};
|
||||
use storyteller_client::endpoints::generate::image::edit::flux_pro_kontext_max_edit_image::flux_pro_kontext_max_edit_image;
|
||||
|
||||
-4
@@ -7,8 +7,6 @@ use crate::core::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess;
|
||||
use crate::core::state::app_env_configs::app_env_configs::AppEnvConfigs;
|
||||
use crate::core::state::data_dir::app_data_root::AppDataRoot;
|
||||
use crate::core::state::provider_priority::{Provider, ProviderPriorityStore};
|
||||
use crate::services::fal::state::fal_credential_manager::FalCredentialManager;
|
||||
use crate::services::fal::state::fal_task_queue::FalTaskQueue;
|
||||
use crate::services::sora::state::sora_credential_manager::SoraCredentialManager;
|
||||
use crate::services::sora::state::sora_task_queue::SoraTaskQueue;
|
||||
use crate::services::storyteller::state::storyteller_credential_manager::StorytellerCredentialManager;
|
||||
@@ -22,8 +20,6 @@ pub async fn handle_gemini_25_flash_edit(
|
||||
app_env_configs: &AppEnvConfigs,
|
||||
provider_priority_store: &ProviderPriorityStore,
|
||||
storyteller_creds_manager: &StorytellerCredentialManager,
|
||||
fal_creds_manager: &FalCredentialManager,
|
||||
fal_task_queue: &FalTaskQueue,
|
||||
sora_creds_manager: &SoraCredentialManager,
|
||||
sora_task_queue: &SoraTaskQueue,
|
||||
) -> Result<TaskEnqueueSuccess, GenerateError> {
|
||||
|
||||
-2
@@ -7,8 +7,6 @@ use crate::core::events::generation_events::generation_enqueue_failure_event::Ge
|
||||
use crate::core::state::app_env_configs::app_env_configs::AppEnvConfigs;
|
||||
use crate::core::state::data_dir::app_data_root::AppDataRoot;
|
||||
use crate::core::state::provider_priority::ProviderPriorityStore;
|
||||
use crate::services::fal::state::fal_credential_manager::FalCredentialManager;
|
||||
use crate::services::fal::state::fal_task_queue::FalTaskQueue;
|
||||
use crate::services::sora::state::sora_credential_manager::SoraCredentialManager;
|
||||
use crate::services::sora::state::sora_task_queue::SoraTaskQueue;
|
||||
use crate::services::storyteller::state::storyteller_credential_manager::StorytellerCredentialManager;
|
||||
|
||||
-4
@@ -6,8 +6,6 @@ use crate::core::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess;
|
||||
use crate::core::state::app_env_configs::app_env_configs::AppEnvConfigs;
|
||||
use crate::core::state::data_dir::app_data_root::AppDataRoot;
|
||||
use crate::core::state::provider_priority::{Provider, ProviderPriorityStore};
|
||||
use crate::services::fal::state::fal_credential_manager::FalCredentialManager;
|
||||
use crate::services::fal::state::fal_task_queue::FalTaskQueue;
|
||||
use crate::services::sora::state::sora_credential_manager::SoraCredentialManager;
|
||||
use crate::services::sora::state::sora_task_queue::SoraTaskQueue;
|
||||
use crate::services::storyteller::state::storyteller_credential_manager::StorytellerCredentialManager;
|
||||
@@ -23,8 +21,6 @@ pub async fn handle_gpt_image_1_edit(
|
||||
app_env_configs: &AppEnvConfigs,
|
||||
provider_priority_store: &ProviderPriorityStore,
|
||||
storyteller_creds_manager: &StorytellerCredentialManager,
|
||||
fal_creds_manager: &FalCredentialManager,
|
||||
fal_task_queue: &FalTaskQueue,
|
||||
sora_creds_manager: &SoraCredentialManager,
|
||||
sora_task_queue: &SoraTaskQueue,
|
||||
) -> Result<TaskEnqueueSuccess, GenerateError> {
|
||||
|
||||
-4
@@ -8,8 +8,6 @@ use crate::core::events::generation_events::generation_enqueue_failure_event::Ge
|
||||
use crate::core::state::app_env_configs::app_env_configs::AppEnvConfigs;
|
||||
use crate::core::state::data_dir::app_data_root::AppDataRoot;
|
||||
use crate::core::state::provider_priority::ProviderPriorityStore;
|
||||
use crate::services::fal::state::fal_credential_manager::FalCredentialManager;
|
||||
use crate::services::fal::state::fal_task_queue::FalTaskQueue;
|
||||
use crate::services::sora::state::sora_credential_manager::SoraCredentialManager;
|
||||
use crate::services::sora::state::sora_task_queue::SoraTaskQueue;
|
||||
use crate::services::storyteller::state::storyteller_credential_manager::StorytellerCredentialManager;
|
||||
@@ -21,8 +19,6 @@ use artcraft_api_defs::generate::image::generate_flux_pro_11_text_to_image::Gene
|
||||
use artcraft_api_defs::generate::image::generate_flux_pro_11_ultra_text_to_image::GenerateFluxPro11UltraTextToImageRequest;
|
||||
use enums::common::generation_provider::GenerationProvider;
|
||||
use enums::tauri::tasks::task_type::TaskType;
|
||||
use fal_client::requests::queue::image_gen::enqueue_flux_pro_11_ultra_text_to_image::{enqueue_flux_pro_11_ultra_text_to_image, FluxPro11UltraTextToImageArgs};
|
||||
use fal_client::requests::webhook::image::edit::enqueue_gpt_image_1_edit_image_webhook::GptEditImageQuality;
|
||||
use idempotency::uuid::generate_random_uuid;
|
||||
use log::{error, info};
|
||||
use storyteller_client::endpoints::generate::image::edit::gpt_image_1_edit_image::gpt_image_1_edit_image;
|
||||
|
||||
-3
@@ -14,8 +14,6 @@ use crate::core::state::data_dir::trait_data_subdir::DataSubdir;
|
||||
use crate::core::state::provider_priority::ProviderPriorityStore;
|
||||
use crate::core::utils::get_url_file_extension::get_url_file_extension;
|
||||
use crate::core::utils::simple_http_download::simple_http_download;
|
||||
use crate::services::fal::state::fal_credential_manager::FalCredentialManager;
|
||||
use crate::services::fal::state::fal_task_queue::FalTaskQueue;
|
||||
use crate::services::sora::state::sora_credential_manager::SoraCredentialManager;
|
||||
use crate::services::sora::state::sora_task_queue::SoraTaskQueue;
|
||||
use crate::services::storyteller::state::storyteller_credential_manager::StorytellerCredentialManager;
|
||||
@@ -27,7 +25,6 @@ use artcraft_api_defs::generate::image::generate_flux_pro_11_ultra_text_to_image
|
||||
use enums::common::generation_provider::GenerationProvider;
|
||||
use enums::tauri::tasks::task_type::TaskType;
|
||||
use errors::AnyhowResult;
|
||||
use fal_client::requests::queue::image_gen::enqueue_flux_pro_11_ultra_text_to_image::{enqueue_flux_pro_11_ultra_text_to_image, FluxPro11UltraTextToImageArgs};
|
||||
use idempotency::uuid::generate_random_uuid;
|
||||
use log::{error, info, warn};
|
||||
use openai_sora_client::recipes::image_remix_with_session_auto_renew::{image_remix_with_session_auto_renew, ImageRemixAutoRenewRequest};
|
||||
|
||||
+1
-17
@@ -22,8 +22,6 @@ use crate::core::state::provider_priority::ProviderPriorityStore;
|
||||
use crate::core::state::task_database::TaskDatabase;
|
||||
use crate::core::utils::get_url_file_extension::get_url_file_extension;
|
||||
use crate::core::utils::simple_http_download::simple_http_download;
|
||||
use crate::services::fal::state::fal_credential_manager::FalCredentialManager;
|
||||
use crate::services::fal::state::fal_task_queue::FalTaskQueue;
|
||||
use crate::services::sora::state::sora_credential_manager::SoraCredentialManager;
|
||||
use crate::services::sora::state::sora_task_queue::SoraTaskQueue;
|
||||
use crate::services::storyteller::state::storyteller_credential_manager::StorytellerCredentialManager;
|
||||
@@ -40,8 +38,8 @@ use openai_sora_client::requests::image_gen::common::{ImageSize, NumImages};
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use sqlite_tasks::queries::create_task::{create_task, CreateTaskArgs};
|
||||
use std::time::Duration;
|
||||
use storyteller_client::error::storyteller_error::StorytellerError;
|
||||
use storyteller_client::endpoints::media_files::get_media_file::get_media_file;
|
||||
use storyteller_client::error::storyteller_error::StorytellerError;
|
||||
use storyteller_client::utils::api_host::ApiHost;
|
||||
use tauri::{AppHandle, Manager, State};
|
||||
use tokens::tokens::media_files::MediaFileToken;
|
||||
@@ -158,8 +156,6 @@ pub async fn enqueue_image_inpaint_command(
|
||||
provider_priority_store: State<'_, ProviderPriorityStore>,
|
||||
task_database: State<'_, TaskDatabase>,
|
||||
storyteller_creds_manager: State<'_, StorytellerCredentialManager>,
|
||||
fal_creds_manager: State<'_, FalCredentialManager>,
|
||||
fal_task_queue: State<'_, FalTaskQueue>,
|
||||
sora_creds_manager: State<'_, SoraCredentialManager>,
|
||||
sora_task_queue: State<'_, SoraTaskQueue>,
|
||||
) -> ResponseOrErrorType<EnqueueImageInpaintSuccessResponse, EnqueueInpaintImageErrorType> {
|
||||
@@ -175,8 +171,6 @@ pub async fn enqueue_image_inpaint_command(
|
||||
&provider_priority_store,
|
||||
&task_database,
|
||||
&storyteller_creds_manager,
|
||||
&fal_creds_manager,
|
||||
&fal_task_queue,
|
||||
&sora_creds_manager,
|
||||
&sora_task_queue,
|
||||
).await;
|
||||
@@ -227,8 +221,6 @@ pub async fn handle_request(
|
||||
provider_priority_store: &ProviderPriorityStore,
|
||||
task_database: &TaskDatabase,
|
||||
storyteller_creds_manager: &StorytellerCredentialManager,
|
||||
fal_creds_manager: &FalCredentialManager,
|
||||
fal_task_queue: &FalTaskQueue,
|
||||
sora_creds_manager: &SoraCredentialManager,
|
||||
sora_task_queue: &SoraTaskQueue,
|
||||
) -> Result<TaskEnqueueSuccess, GenerateError> {
|
||||
@@ -244,8 +236,6 @@ pub async fn handle_request(
|
||||
app_env_configs,
|
||||
provider_priority_store,
|
||||
storyteller_creds_manager,
|
||||
fal_creds_manager,
|
||||
fal_task_queue,
|
||||
sora_creds_manager,
|
||||
sora_task_queue,
|
||||
).await?
|
||||
@@ -258,8 +248,6 @@ pub async fn handle_request(
|
||||
app_env_configs,
|
||||
provider_priority_store,
|
||||
storyteller_creds_manager,
|
||||
fal_creds_manager,
|
||||
fal_task_queue,
|
||||
sora_creds_manager,
|
||||
sora_task_queue,
|
||||
).await?
|
||||
@@ -273,8 +261,6 @@ pub async fn handle_request(
|
||||
app_env_configs,
|
||||
provider_priority_store,
|
||||
storyteller_creds_manager,
|
||||
fal_creds_manager,
|
||||
fal_task_queue,
|
||||
sora_creds_manager,
|
||||
sora_task_queue,
|
||||
).await?
|
||||
@@ -288,8 +274,6 @@ pub async fn handle_request(
|
||||
app_env_configs,
|
||||
provider_priority_store,
|
||||
storyteller_creds_manager,
|
||||
fal_creds_manager,
|
||||
fal_task_queue,
|
||||
sora_creds_manager,
|
||||
sora_task_queue,
|
||||
).await?
|
||||
|
||||
-4
@@ -8,8 +8,6 @@ use crate::core::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess;
|
||||
use crate::core::state::app_env_configs::app_env_configs::AppEnvConfigs;
|
||||
use crate::core::state::data_dir::app_data_root::AppDataRoot;
|
||||
use crate::core::state::provider_priority::{Provider, ProviderPriorityStore};
|
||||
use crate::services::fal::state::fal_credential_manager::FalCredentialManager;
|
||||
use crate::services::fal::state::fal_task_queue::FalTaskQueue;
|
||||
use crate::services::sora::state::sora_credential_manager::SoraCredentialManager;
|
||||
use crate::services::sora::state::sora_task_queue::SoraTaskQueue;
|
||||
use crate::services::storyteller::state::storyteller_credential_manager::StorytellerCredentialManager;
|
||||
@@ -23,8 +21,6 @@ pub async fn handle_flux_dev_juggernaut_inpaint(
|
||||
app_env_configs: &AppEnvConfigs,
|
||||
provider_priority_store: &ProviderPriorityStore,
|
||||
storyteller_creds_manager: &StorytellerCredentialManager,
|
||||
fal_creds_manager: &FalCredentialManager,
|
||||
fal_task_queue: &FalTaskQueue,
|
||||
sora_creds_manager: &SoraCredentialManager,
|
||||
sora_task_queue: &SoraTaskQueue,
|
||||
) -> Result<TaskEnqueueSuccess, GenerateError> {
|
||||
|
||||
-3
@@ -10,8 +10,6 @@ use crate::core::state::app_env_configs::app_env_configs::AppEnvConfigs;
|
||||
use crate::core::state::data_dir::app_data_root::AppDataRoot;
|
||||
use crate::core::state::provider_priority::ProviderPriorityStore;
|
||||
use crate::core::utils::save_base64_image_to_temp_dir::save_base64_image_to_temp_dir;
|
||||
use crate::services::fal::state::fal_credential_manager::FalCredentialManager;
|
||||
use crate::services::fal::state::fal_task_queue::FalTaskQueue;
|
||||
use crate::services::sora::state::sora_credential_manager::SoraCredentialManager;
|
||||
use crate::services::sora::state::sora_task_queue::SoraTaskQueue;
|
||||
use crate::services::storyteller::state::storyteller_credential_manager::StorytellerCredentialManager;
|
||||
@@ -24,7 +22,6 @@ use artcraft_api_defs::generate::image::generate_flux_pro_11_ultra_text_to_image
|
||||
use artcraft_api_defs::generate::image::inpaint::flux_dev_juggernaut_inpaint_image::{FluxDevJuggernautInpaintImageNumImages, FluxDevJuggernautInpaintImageRequest};
|
||||
use enums::common::generation_provider::GenerationProvider;
|
||||
use enums::tauri::tasks::task_type::TaskType;
|
||||
use fal_client::requests::queue::image_gen::enqueue_flux_pro_11_ultra_text_to_image::{enqueue_flux_pro_11_ultra_text_to_image, FluxPro11UltraTextToImageArgs};
|
||||
use idempotency::uuid::generate_random_uuid;
|
||||
use images::encoding::image_bytes_to_png_bytes::image_bytes_to_png_bytes;
|
||||
use images::encoding::image_bytes_to_png_bytes_with_dimensions::image_bytes_to_png_bytes_with_dimensions;
|
||||
|
||||
-4
@@ -8,8 +8,6 @@ use crate::core::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess;
|
||||
use crate::core::state::app_env_configs::app_env_configs::AppEnvConfigs;
|
||||
use crate::core::state::data_dir::app_data_root::AppDataRoot;
|
||||
use crate::core::state::provider_priority::{Provider, ProviderPriorityStore};
|
||||
use crate::services::fal::state::fal_credential_manager::FalCredentialManager;
|
||||
use crate::services::fal::state::fal_task_queue::FalTaskQueue;
|
||||
use crate::services::sora::state::sora_credential_manager::SoraCredentialManager;
|
||||
use crate::services::sora::state::sora_task_queue::SoraTaskQueue;
|
||||
use crate::services::storyteller::state::storyteller_credential_manager::StorytellerCredentialManager;
|
||||
@@ -23,8 +21,6 @@ pub async fn handle_flux_pro_1_inpaint(
|
||||
app_env_configs: &AppEnvConfigs,
|
||||
provider_priority_store: &ProviderPriorityStore,
|
||||
storyteller_creds_manager: &StorytellerCredentialManager,
|
||||
fal_creds_manager: &FalCredentialManager,
|
||||
fal_task_queue: &FalTaskQueue,
|
||||
sora_creds_manager: &SoraCredentialManager,
|
||||
sora_task_queue: &SoraTaskQueue,
|
||||
) -> Result<TaskEnqueueSuccess, GenerateError> {
|
||||
|
||||
-3
@@ -10,8 +10,6 @@ use crate::core::state::app_env_configs::app_env_configs::AppEnvConfigs;
|
||||
use crate::core::state::data_dir::app_data_root::AppDataRoot;
|
||||
use crate::core::state::provider_priority::ProviderPriorityStore;
|
||||
use crate::core::utils::save_base64_image_to_temp_dir::save_base64_image_to_temp_dir;
|
||||
use crate::services::fal::state::fal_credential_manager::FalCredentialManager;
|
||||
use crate::services::fal::state::fal_task_queue::FalTaskQueue;
|
||||
use crate::services::sora::state::sora_credential_manager::SoraCredentialManager;
|
||||
use crate::services::sora::state::sora_task_queue::SoraTaskQueue;
|
||||
use crate::services::storyteller::state::storyteller_credential_manager::StorytellerCredentialManager;
|
||||
@@ -24,7 +22,6 @@ use artcraft_api_defs::generate::image::generate_flux_pro_11_ultra_text_to_image
|
||||
use artcraft_api_defs::generate::image::inpaint::flux_pro_1_inpaint_image::{FluxPro1InpaintImageNumImages, FluxPro1InpaintImageRequest};
|
||||
use enums::common::generation_provider::GenerationProvider;
|
||||
use enums::tauri::tasks::task_type::TaskType;
|
||||
use fal_client::requests::queue::image_gen::enqueue_flux_pro_11_ultra_text_to_image::{enqueue_flux_pro_11_ultra_text_to_image, FluxPro11UltraTextToImageArgs};
|
||||
use idempotency::uuid::generate_random_uuid;
|
||||
use images::encoding::image_bytes_to_png_bytes::image_bytes_to_png_bytes;
|
||||
use images::encoding::image_bytes_to_png_bytes_with_dimensions::image_bytes_to_png_bytes_with_dimensions;
|
||||
|
||||
-4
@@ -6,8 +6,6 @@ use crate::core::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess;
|
||||
use crate::core::state::app_env_configs::app_env_configs::AppEnvConfigs;
|
||||
use crate::core::state::data_dir::app_data_root::AppDataRoot;
|
||||
use crate::core::state::provider_priority::{Provider, ProviderPriorityStore};
|
||||
use crate::services::fal::state::fal_credential_manager::FalCredentialManager;
|
||||
use crate::services::fal::state::fal_task_queue::FalTaskQueue;
|
||||
use crate::services::sora::state::sora_credential_manager::SoraCredentialManager;
|
||||
use crate::services::sora::state::sora_task_queue::SoraTaskQueue;
|
||||
use crate::services::storyteller::state::storyteller_credential_manager::StorytellerCredentialManager;
|
||||
@@ -21,8 +19,6 @@ pub async fn handle_flux_pro_kontext_inpaint(
|
||||
app_env_configs: &AppEnvConfigs,
|
||||
provider_priority_store: &ProviderPriorityStore,
|
||||
storyteller_creds_manager: &StorytellerCredentialManager,
|
||||
fal_creds_manager: &FalCredentialManager,
|
||||
fal_task_queue: &FalTaskQueue,
|
||||
sora_creds_manager: &SoraCredentialManager,
|
||||
sora_task_queue: &SoraTaskQueue,
|
||||
) -> Result<TaskEnqueueSuccess, GenerateError> {
|
||||
|
||||
-3
@@ -10,8 +10,6 @@ use crate::core::state::app_env_configs::app_env_configs::AppEnvConfigs;
|
||||
use crate::core::state::data_dir::app_data_root::AppDataRoot;
|
||||
use crate::core::state::provider_priority::ProviderPriorityStore;
|
||||
use crate::core::utils::save_base64_image_to_temp_dir::save_base64_image_to_temp_dir;
|
||||
use crate::services::fal::state::fal_credential_manager::FalCredentialManager;
|
||||
use crate::services::fal::state::fal_task_queue::FalTaskQueue;
|
||||
use crate::services::sora::state::sora_credential_manager::SoraCredentialManager;
|
||||
use crate::services::sora::state::sora_task_queue::SoraTaskQueue;
|
||||
use crate::services::storyteller::state::storyteller_credential_manager::StorytellerCredentialManager;
|
||||
@@ -25,7 +23,6 @@ use artcraft_api_defs::generate::image::generate_flux_pro_11_ultra_text_to_image
|
||||
use artcraft_api_defs::generate::image::inpaint::flux_pro_1_inpaint_image::{FluxPro1InpaintImageNumImages, FluxPro1InpaintImageRequest};
|
||||
use enums::common::generation_provider::GenerationProvider;
|
||||
use enums::tauri::tasks::task_type::TaskType;
|
||||
use fal_client::requests::queue::image_gen::enqueue_flux_pro_11_ultra_text_to_image::{enqueue_flux_pro_11_ultra_text_to_image, FluxPro11UltraTextToImageArgs};
|
||||
use idempotency::uuid::generate_random_uuid;
|
||||
use images::encoding::image_bytes_to_png_bytes::image_bytes_to_png_bytes;
|
||||
use images::encoding::image_bytes_to_png_bytes_with_dimensions::image_bytes_to_png_bytes_with_dimensions;
|
||||
|
||||
-4
@@ -7,8 +7,6 @@ use crate::core::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess;
|
||||
use crate::core::state::app_env_configs::app_env_configs::AppEnvConfigs;
|
||||
use crate::core::state::data_dir::app_data_root::AppDataRoot;
|
||||
use crate::core::state::provider_priority::{Provider, ProviderPriorityStore};
|
||||
use crate::services::fal::state::fal_credential_manager::FalCredentialManager;
|
||||
use crate::services::fal::state::fal_task_queue::FalTaskQueue;
|
||||
use crate::services::sora::state::sora_credential_manager::SoraCredentialManager;
|
||||
use crate::services::sora::state::sora_task_queue::SoraTaskQueue;
|
||||
use crate::services::storyteller::state::storyteller_credential_manager::StorytellerCredentialManager;
|
||||
@@ -22,8 +20,6 @@ pub async fn handle_gemini_25_flash_inpaint(
|
||||
app_env_configs: &AppEnvConfigs,
|
||||
provider_priority_store: &ProviderPriorityStore,
|
||||
storyteller_creds_manager: &StorytellerCredentialManager,
|
||||
fal_creds_manager: &FalCredentialManager,
|
||||
fal_task_queue: &FalTaskQueue,
|
||||
sora_creds_manager: &SoraCredentialManager,
|
||||
sora_task_queue: &SoraTaskQueue,
|
||||
) -> Result<TaskEnqueueSuccess, GenerateError> {
|
||||
|
||||
-3
@@ -10,8 +10,6 @@ use crate::core::state::app_env_configs::app_env_configs::AppEnvConfigs;
|
||||
use crate::core::state::data_dir::app_data_root::AppDataRoot;
|
||||
use crate::core::state::provider_priority::ProviderPriorityStore;
|
||||
use crate::core::utils::save_base64_image_to_temp_dir::save_base64_image_to_temp_dir;
|
||||
use crate::services::fal::state::fal_credential_manager::FalCredentialManager;
|
||||
use crate::services::fal::state::fal_task_queue::FalTaskQueue;
|
||||
use crate::services::sora::state::sora_credential_manager::SoraCredentialManager;
|
||||
use crate::services::sora::state::sora_task_queue::SoraTaskQueue;
|
||||
use crate::services::storyteller::state::storyteller_credential_manager::StorytellerCredentialManager;
|
||||
@@ -26,7 +24,6 @@ use artcraft_api_defs::generate::image::generate_flux_pro_11_ultra_text_to_image
|
||||
use artcraft_api_defs::generate::image::inpaint::flux_pro_1_inpaint_image::{FluxPro1InpaintImageNumImages, FluxPro1InpaintImageRequest};
|
||||
use enums::common::generation_provider::GenerationProvider;
|
||||
use enums::tauri::tasks::task_type::TaskType;
|
||||
use fal_client::requests::queue::image_gen::enqueue_flux_pro_11_ultra_text_to_image::{enqueue_flux_pro_11_ultra_text_to_image, FluxPro11UltraTextToImageArgs};
|
||||
use idempotency::uuid::generate_random_uuid;
|
||||
use images::encoding::image_bytes_to_png_bytes::image_bytes_to_png_bytes;
|
||||
use images::encoding::image_bytes_to_png_bytes_with_dimensions::image_bytes_to_png_bytes_with_dimensions;
|
||||
|
||||
-11
@@ -12,11 +12,8 @@ use crate::core::state::app_env_configs::app_env_configs::AppEnvConfigs;
|
||||
use crate::core::state::data_dir::app_data_root::AppDataRoot;
|
||||
use crate::core::state::provider_priority::{Provider, ProviderPriorityStore};
|
||||
use crate::core::state::task_database::TaskDatabase;
|
||||
use crate::services::fal::state::fal_credential_manager::FalCredentialManager;
|
||||
use crate::services::fal::state::fal_task_queue::FalTaskQueue;
|
||||
use crate::services::sora::state::sora_task_queue::SoraTaskQueue;
|
||||
use crate::services::storyteller::state::storyteller_credential_manager::StorytellerCredentialManager;
|
||||
use fal_client::requests::queue::image_gen::enqueue_flux_pro_11_ultra_text_to_image::{enqueue_flux_pro_11_ultra_text_to_image, FluxPro11UltraTextToImageArgs};
|
||||
use log::{error, info, warn};
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use tauri::{AppHandle, State};
|
||||
@@ -75,8 +72,6 @@ pub async fn enqueue_image_to_3d_object_command(
|
||||
app_data_root: State<'_, AppDataRoot>,
|
||||
provider_priority_store: State<'_, ProviderPriorityStore>,
|
||||
task_database: State<'_, TaskDatabase>,
|
||||
fal_creds_manager: State<'_, FalCredentialManager>,
|
||||
fal_task_queue: State<'_, FalTaskQueue>,
|
||||
storyteller_creds_manager: State<'_, StorytellerCredentialManager>,
|
||||
sora_task_queue: State<'_, SoraTaskQueue>,
|
||||
) -> Response<EnqueueImageTo3dObjectSuccessResponse, EnqueueImageTo3dObjectErrorType, ()> {
|
||||
@@ -90,9 +85,7 @@ pub async fn enqueue_image_to_3d_object_command(
|
||||
&app_data_root,
|
||||
&provider_priority_store,
|
||||
&task_database,
|
||||
&fal_creds_manager,
|
||||
&storyteller_creds_manager,
|
||||
&fal_task_queue,
|
||||
).await;
|
||||
|
||||
match result {
|
||||
@@ -162,9 +155,7 @@ pub async fn handle_request(
|
||||
app_data_root: &AppDataRoot,
|
||||
provider_priority_store: &ProviderPriorityStore,
|
||||
task_database: &TaskDatabase,
|
||||
fal_creds_manager: &FalCredentialManager,
|
||||
storyteller_creds_manager: &StorytellerCredentialManager,
|
||||
fal_task_queue: &FalTaskQueue,
|
||||
) -> Result<TaskEnqueueSuccess, GenerateError> {
|
||||
|
||||
let result = handle_object(
|
||||
@@ -173,9 +164,7 @@ pub async fn handle_request(
|
||||
&app_env_configs,
|
||||
&app_data_root,
|
||||
&provider_priority_store,
|
||||
&fal_creds_manager,
|
||||
&storyteller_creds_manager,
|
||||
&fal_task_queue,
|
||||
).await;
|
||||
|
||||
let success_event = match result {
|
||||
|
||||
+2
-17
@@ -1,3 +1,4 @@
|
||||
use crate::core::commands::enqueue::generate_error::GenerateError;
|
||||
use crate::core::commands::enqueue::image_to_object::enqueue_image_to_3d_object_command::EnqueueImageTo3dObjectRequest;
|
||||
use crate::core::commands::enqueue::image_to_object::generic::handle_object_artcraft::handle_object_artcraft;
|
||||
use crate::core::commands::enqueue::image_to_object::generic::handle_object_fal::handle_object_fal;
|
||||
@@ -5,11 +6,8 @@ use crate::core::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess;
|
||||
use crate::core::state::app_env_configs::app_env_configs::AppEnvConfigs;
|
||||
use crate::core::state::data_dir::app_data_root::AppDataRoot;
|
||||
use crate::core::state::provider_priority::{Provider, ProviderPriorityStore};
|
||||
use crate::services::fal::state::fal_credential_manager::FalCredentialManager;
|
||||
use crate::services::fal::state::fal_task_queue::FalTaskQueue;
|
||||
use crate::services::storyteller::state::storyteller_credential_manager::StorytellerCredentialManager;
|
||||
use tauri::AppHandle;
|
||||
use crate::core::commands::enqueue::generate_error::GenerateError;
|
||||
|
||||
pub async fn handle_object(
|
||||
request: EnqueueImageTo3dObjectRequest,
|
||||
@@ -17,15 +15,14 @@ pub async fn handle_object(
|
||||
app_env_configs: &AppEnvConfigs,
|
||||
app_data_root: &AppDataRoot,
|
||||
provider_priority_store: &ProviderPriorityStore,
|
||||
fal_creds_manager: &FalCredentialManager,
|
||||
storyteller_creds_manager: &StorytellerCredentialManager,
|
||||
fal_task_queue: &FalTaskQueue,
|
||||
) -> Result<TaskEnqueueSuccess, GenerateError> {
|
||||
|
||||
let priority = provider_priority_store.get_priority()?;
|
||||
|
||||
for provider in priority.iter() {
|
||||
match provider {
|
||||
Provider::Fal => {} // Fallthrough
|
||||
Provider::Sora => {} // Fallthrough
|
||||
Provider::Artcraft => {
|
||||
return Ok(handle_object_artcraft(
|
||||
@@ -36,18 +33,6 @@ pub async fn handle_object(
|
||||
storyteller_creds_manager
|
||||
).await?);
|
||||
}
|
||||
Provider::Fal => {
|
||||
if fal_creds_manager.has_apparent_api_token()? {
|
||||
return Ok(handle_object_fal(
|
||||
&app,
|
||||
app_env_configs,
|
||||
app_data_root,
|
||||
request,
|
||||
fal_creds_manager,
|
||||
fal_task_queue
|
||||
).await?);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
-1
@@ -12,7 +12,6 @@ use artcraft_api_defs::generate::object::generate_hunyuan_2_0_image_to_3d::Gener
|
||||
use artcraft_api_defs::generate::object::generate_hunyuan_2_1_image_to_3d::GenerateHunyuan21ImageTo3dRequest;
|
||||
use enums::common::generation_provider::GenerationProvider;
|
||||
use enums::tauri::tasks::task_type::TaskType;
|
||||
use fal_client::requests::queue::image_gen::enqueue_flux_pro_11_ultra_text_to_image::{enqueue_flux_pro_11_ultra_text_to_image, FluxPro11UltraTextToImageArgs};
|
||||
use idempotency::uuid::generate_random_uuid;
|
||||
use log::{error, info};
|
||||
use storyteller_client::endpoints::generate::object::generate_hunyuan_3d_2_0_image_to_3d::generate_hunyuan3d_2_0_image_to_3d;
|
||||
|
||||
+2
-123
@@ -1,21 +1,8 @@
|
||||
use crate::core::commands::enqueue::generate_error::GenerateError;
|
||||
use crate::core::commands::enqueue::image_to_object::enqueue_image_to_3d_object_command::{EnqueueImageTo3dObjectModel, EnqueueImageTo3dObjectRequest};
|
||||
use crate::core::commands::enqueue::image_to_object::enqueue_image_to_3d_object_command::EnqueueImageTo3dObjectRequest;
|
||||
use crate::core::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess;
|
||||
use crate::core::events::basic_sendable_event_trait::BasicSendableEvent;
|
||||
use crate::core::events::generation_events::common::{GenerationAction, GenerationModel, GenerationServiceProvider};
|
||||
use crate::core::events::generation_events::generation_enqueue_failure_event::GenerationEnqueueFailureEvent;
|
||||
use crate::core::events::generation_events::generation_enqueue_success_event::GenerationEnqueueSuccessEvent;
|
||||
use crate::core::state::app_env_configs::app_env_configs::AppEnvConfigs;
|
||||
use crate::core::state::data_dir::app_data_root::AppDataRoot;
|
||||
use crate::core::utils::download_media_file_to_temp_dir::download_media_file_to_temp_dir;
|
||||
use crate::services::fal::state::fal_credential_manager::FalCredentialManager;
|
||||
use crate::services::fal::state::fal_task_queue::FalTaskQueue;
|
||||
use anyhow::anyhow;
|
||||
use enums::common::generation_provider::GenerationProvider;
|
||||
use enums::tauri::tasks::task_type::TaskType;
|
||||
use fal_client::requests::queue::enqueue_hunyuan2_image_to_3d::{enqueue_hunyuan2_image_to_3d, Hunyuan2Args};
|
||||
use fal_client::requests::queue::image_gen::enqueue_flux_pro_11_ultra_text_to_image::{enqueue_flux_pro_11_ultra_text_to_image, FluxPro11UltraTextToImageArgs};
|
||||
use log::{error, info};
|
||||
use tauri::AppHandle;
|
||||
|
||||
pub async fn handle_object_fal(
|
||||
@@ -23,114 +10,6 @@ pub async fn handle_object_fal(
|
||||
app_env_configs: &AppEnvConfigs,
|
||||
app_data_root: &AppDataRoot,
|
||||
request: EnqueueImageTo3dObjectRequest,
|
||||
fal_creds_manager: &FalCredentialManager,
|
||||
fal_task_queue: &FalTaskQueue,
|
||||
) -> Result<TaskEnqueueSuccess, GenerateError> {
|
||||
|
||||
let api_key = match fal_creds_manager.get_key()? {
|
||||
Some(key) => key,
|
||||
None => {
|
||||
error!("No FAL API key is set. Can't perform action.");
|
||||
let event =
|
||||
GenerationEnqueueFailureEvent::no_fal_api_key(GenerationAction::ImageTo3d);
|
||||
|
||||
if let Err(err) = event.send(&app) {
|
||||
error!("Failed to emit event: {:?}", err); // Fail open.
|
||||
}
|
||||
|
||||
return Err(GenerateError::needs_fal_api_key());
|
||||
},
|
||||
};
|
||||
|
||||
//let api_key = fal_creds_manager.get_key_required()
|
||||
// .map_err(|err| {
|
||||
// error!("EnqueueTextToImage FAL api key required: {:?}", err);
|
||||
// InternalObjectError::NeedsFalApiKey
|
||||
// })?;
|
||||
|
||||
let mut temp_download;
|
||||
|
||||
if let Some(media_token) = request.image_media_token {
|
||||
temp_download = download_media_file_to_temp_dir(
|
||||
app_env_configs,
|
||||
app_data_root,
|
||||
&media_token
|
||||
).await?;
|
||||
} else {
|
||||
return Err(GenerateError::required_source_image_not_provided());
|
||||
}
|
||||
|
||||
info!("Calling FAL image to 3d ...");
|
||||
|
||||
let filename = temp_download.path().to_path_buf();
|
||||
|
||||
let mut used_model = None;
|
||||
|
||||
let result = match request.model {
|
||||
None => {
|
||||
return Err(GenerateError::no_model_specified());
|
||||
}
|
||||
Some(
|
||||
EnqueueImageTo3dObjectModel::Hunyuan3d2_0 |
|
||||
EnqueueImageTo3dObjectModel::Hunyuan3d2
|
||||
) => {
|
||||
used_model = Some(GenerationModel::Hunyuan3d2_0);
|
||||
info!("enqueue Hunyuan 3D 2.0");
|
||||
enqueue_hunyuan2_image_to_3d(Hunyuan2Args {
|
||||
image_path: filename,
|
||||
api_key: &api_key,
|
||||
}).await
|
||||
}
|
||||
_ => {
|
||||
return Err(GenerateError::AnyhowError(anyhow!("Wrong model specified: {:?}", request.model)));
|
||||
}
|
||||
};
|
||||
|
||||
let mut job_id = None;
|
||||
|
||||
match result {
|
||||
Ok(enqueued) => {
|
||||
info!("Successfully enqueued text to image");
|
||||
|
||||
job_id = Some(enqueued.request_id.to_string());
|
||||
|
||||
let event = GenerationEnqueueSuccessEvent {
|
||||
action: GenerationAction::ImageTo3d,
|
||||
service: GenerationServiceProvider::Fal,
|
||||
model: None,
|
||||
};
|
||||
|
||||
if let Err(err) = event.send(app) {
|
||||
error!("Failed to emit event: {:?}", err); // Fail open.
|
||||
}
|
||||
|
||||
if let Err(err) = fal_task_queue.insert(&enqueued) {
|
||||
error!("Failed to enqueue task: {:?}", err);
|
||||
return Err(GenerateError::AnyhowError(anyhow!("Failed to enqueue task: {:?}", err)));
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
error!("Failed to enqueue image to 3d: {:?}", err);
|
||||
|
||||
let event = GenerationEnqueueFailureEvent {
|
||||
action: GenerationAction::ImageTo3d,
|
||||
service: GenerationServiceProvider::Fal,
|
||||
model: None,
|
||||
reason: None,
|
||||
};
|
||||
|
||||
if let Err(err) = event.send(app) {
|
||||
error!("Failed to emit event: {:?}", err); // Fail open.
|
||||
}
|
||||
|
||||
return Err(GenerateError::from(err));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(TaskEnqueueSuccess {
|
||||
task_type: TaskType::ObjectGeneration,
|
||||
model: used_model,
|
||||
provider: GenerationProvider::Fal,
|
||||
provider_job_id: job_id,
|
||||
})
|
||||
Err(GenerateError::FalNoLongerSupported)
|
||||
}
|
||||
|
||||
+46
-23
@@ -1,6 +1,7 @@
|
||||
use crate::core::commands::enqueue::common::notify_frontend_of_errors::notify_frontend_of_errors;
|
||||
use crate::core::commands::enqueue::generate_error::{BadInputReason, GenerateError, MissingCredentialsReason, ProviderFailureReason};
|
||||
use crate::core::commands::enqueue::image_to_video::generic::handle_video::handle_video;
|
||||
use crate::core::commands::enqueue::image_to_video::sora2::handle_sora2_video::handle_sora2_video;
|
||||
use crate::core::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess;
|
||||
use crate::core::commands::response::failure_response_wrapper::{CommandErrorResponseWrapper, CommandErrorStatus};
|
||||
use crate::core::commands::response::shorthand::Response;
|
||||
@@ -12,11 +13,9 @@ use crate::core::state::app_env_configs::app_env_configs::AppEnvConfigs;
|
||||
use crate::core::state::data_dir::app_data_root::AppDataRoot;
|
||||
use crate::core::state::provider_priority::{Provider, ProviderPriorityStore};
|
||||
use crate::core::state::task_database::TaskDatabase;
|
||||
use crate::services::fal::state::fal_credential_manager::FalCredentialManager;
|
||||
use crate::services::fal::state::fal_task_queue::FalTaskQueue;
|
||||
use crate::services::sora::state::sora_credential_manager::SoraCredentialManager;
|
||||
use crate::services::sora::state::sora_task_queue::SoraTaskQueue;
|
||||
use crate::services::storyteller::state::storyteller_credential_manager::StorytellerCredentialManager;
|
||||
use fal_client::requests::queue::image_gen::enqueue_flux_pro_11_ultra_text_to_image::{enqueue_flux_pro_11_ultra_text_to_image, FluxPro11UltraTextToImageArgs};
|
||||
use log::{error, info, warn};
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use tauri::{AppHandle, State};
|
||||
@@ -39,6 +38,9 @@ pub enum VideoModel {
|
||||
#[serde(rename = "seedance_1.0_lite")]
|
||||
Seedance10Lite,
|
||||
|
||||
#[serde(rename = "sora_2")]
|
||||
Sora2,
|
||||
|
||||
#[serde(rename = "veo_2")]
|
||||
Veo2,
|
||||
|
||||
@@ -49,13 +51,13 @@ pub enum VideoModel {
|
||||
Veo3Fast,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[derive(Deserialize, Debug)]
|
||||
pub struct EnqueueImageToVideoRequest {
|
||||
/// REQUIRED.
|
||||
/// The model to use.
|
||||
pub model: Option<VideoModel>,
|
||||
|
||||
/// Currently REQUIRED.
|
||||
/// Currently REQUIRED by some downstream models.
|
||||
/// Image media file; the starting frame of the video.
|
||||
/// TODO: In the future we may support base64 images, URLs, or file paths here.
|
||||
pub image_media_token: Option<MediaFileToken>,
|
||||
@@ -68,6 +70,18 @@ pub struct EnqueueImageToVideoRequest {
|
||||
/// Optional.
|
||||
/// Text prompt used to direct the video.
|
||||
pub prompt: Option<String>,
|
||||
|
||||
/// OPTIONAL.
|
||||
/// Only for Sora2 model currently.
|
||||
pub sora_orientation: Option<SoraOrientation>,
|
||||
}
|
||||
|
||||
// TODO: Not sure how to handle so many different types of video (model) x (services).
|
||||
#[derive(Deserialize, Debug)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum SoraOrientation {
|
||||
Portrait,
|
||||
Landscape,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
@@ -95,19 +109,18 @@ pub enum EnqueueImageToVideoErrorType {
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn enqueue_image_to_video_command(
|
||||
app: AppHandle,
|
||||
request: EnqueueImageToVideoRequest,
|
||||
app: AppHandle,
|
||||
app_env_configs: State<'_, AppEnvConfigs>,
|
||||
app_data_root: State<'_, AppDataRoot>,
|
||||
provider_priority_store: State<'_, ProviderPriorityStore>,
|
||||
task_database: State<'_, TaskDatabase>,
|
||||
fal_creds_manager: State<'_, FalCredentialManager>,
|
||||
fal_task_queue: State<'_, FalTaskQueue>,
|
||||
storyteller_creds_manager: State<'_, StorytellerCredentialManager>,
|
||||
sora_task_queue: State<'_, SoraTaskQueue>,
|
||||
sora_creds_manager: State<'_, SoraCredentialManager>,
|
||||
) -> Response<EnqueueImageToVideoSuccessResponse, EnqueueImageToVideoErrorType, ()> {
|
||||
|
||||
info!("enqueue_image_to_video_command called");
|
||||
info!("enqueue_image_to_video_command called, request: {:?}", request);
|
||||
|
||||
let result = handle_request(
|
||||
request,
|
||||
@@ -116,9 +129,8 @@ pub async fn enqueue_image_to_video_command(
|
||||
&app_data_root,
|
||||
&provider_priority_store,
|
||||
&task_database,
|
||||
&fal_creds_manager,
|
||||
&sora_creds_manager,
|
||||
&storyteller_creds_manager,
|
||||
&fal_task_queue,
|
||||
).await;
|
||||
|
||||
match result {
|
||||
@@ -188,21 +200,32 @@ pub async fn handle_request(
|
||||
app_data_root: &AppDataRoot,
|
||||
provider_priority_store: &ProviderPriorityStore,
|
||||
task_database: &TaskDatabase,
|
||||
fal_creds_manager: &FalCredentialManager,
|
||||
sora_creds_manager: &SoraCredentialManager,
|
||||
storyteller_creds_manager: &StorytellerCredentialManager,
|
||||
fal_task_queue: &FalTaskQueue,
|
||||
) -> Result<TaskEnqueueSuccess, GenerateError> {
|
||||
|
||||
let result = handle_video(
|
||||
request,
|
||||
&app,
|
||||
&app_env_configs,
|
||||
&app_data_root,
|
||||
&provider_priority_store,
|
||||
&fal_creds_manager,
|
||||
&storyteller_creds_manager,
|
||||
&fal_task_queue,
|
||||
).await;
|
||||
let result = match request.model {
|
||||
Some(VideoModel::Sora2) => {
|
||||
handle_sora2_video(
|
||||
&request,
|
||||
app,
|
||||
app_data_root,
|
||||
app_env_configs,
|
||||
provider_priority_store,
|
||||
sora_creds_manager,
|
||||
).await
|
||||
}
|
||||
_ => {
|
||||
handle_video(
|
||||
request,
|
||||
app,
|
||||
app_env_configs,
|
||||
app_data_root,
|
||||
provider_priority_store,
|
||||
storyteller_creds_manager,
|
||||
).await
|
||||
}
|
||||
};
|
||||
|
||||
let success_event = match result {
|
||||
Err(err) => return Err(err),
|
||||
|
||||
+1
-17
@@ -1,13 +1,10 @@
|
||||
use crate::core::commands::enqueue::generate_error::GenerateError;
|
||||
use crate::core::commands::enqueue::image_to_video::enqueue_image_to_video_command::EnqueueImageToVideoRequest;
|
||||
use crate::core::commands::enqueue::image_to_video::generic::handle_video_artcraft::handle_video_artcraft;
|
||||
use crate::core::commands::enqueue::image_to_video::generic::handle_video_fal::handle_video_fal;
|
||||
use crate::core::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess;
|
||||
use crate::core::state::app_env_configs::app_env_configs::AppEnvConfigs;
|
||||
use crate::core::state::data_dir::app_data_root::AppDataRoot;
|
||||
use crate::core::state::provider_priority::{Provider, ProviderPriorityStore};
|
||||
use crate::services::fal::state::fal_credential_manager::FalCredentialManager;
|
||||
use crate::services::fal::state::fal_task_queue::FalTaskQueue;
|
||||
use crate::services::storyteller::state::storyteller_credential_manager::StorytellerCredentialManager;
|
||||
use tauri::AppHandle;
|
||||
|
||||
@@ -17,15 +14,14 @@ pub async fn handle_video(
|
||||
app_env_configs: &AppEnvConfigs,
|
||||
app_data_root: &AppDataRoot,
|
||||
provider_priority_store: &ProviderPriorityStore,
|
||||
fal_creds_manager: &FalCredentialManager,
|
||||
storyteller_creds_manager: &StorytellerCredentialManager,
|
||||
fal_task_queue: &FalTaskQueue,
|
||||
) -> Result<TaskEnqueueSuccess, GenerateError> {
|
||||
|
||||
let priority = provider_priority_store.get_priority()?;
|
||||
|
||||
for provider in priority.iter() {
|
||||
match provider {
|
||||
Provider::Fal => {} // Fallthrough
|
||||
Provider::Sora => {} // Fallthrough
|
||||
Provider::Artcraft => {
|
||||
return Ok(handle_video_artcraft(
|
||||
@@ -36,18 +32,6 @@ pub async fn handle_video(
|
||||
storyteller_creds_manager
|
||||
).await?);
|
||||
}
|
||||
Provider::Fal => {
|
||||
if fal_creds_manager.has_apparent_api_token()? {
|
||||
return Ok(handle_video_fal(
|
||||
&app,
|
||||
app_env_configs,
|
||||
app_data_root,
|
||||
request,
|
||||
fal_creds_manager,
|
||||
fal_task_queue
|
||||
).await?);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+6
-3
@@ -8,6 +8,7 @@ use crate::core::events::generation_events::generation_enqueue_success_event::Ge
|
||||
use crate::core::state::app_env_configs::app_env_configs::AppEnvConfigs;
|
||||
use crate::core::state::data_dir::app_data_root::AppDataRoot;
|
||||
use crate::services::storyteller::state::storyteller_credential_manager::StorytellerCredentialManager;
|
||||
use anyhow::anyhow;
|
||||
use artcraft_api_defs::generate::video::generate_kling_1_6_pro_image_to_video::{GenerateKling16ProAspectRatio, GenerateKling16ProImageToVideoRequest};
|
||||
use artcraft_api_defs::generate::video::generate_kling_2_1_master_image_to_video::{GenerateKling21MasterAspectRatio, GenerateKling21MasterImageToVideoRequest};
|
||||
use artcraft_api_defs::generate::video::generate_kling_2_1_pro_image_to_video::{GenerateKling21ProAspectRatio, GenerateKling21ProImageToVideoRequest};
|
||||
@@ -17,9 +18,6 @@ use artcraft_api_defs::generate::video::generate_veo_3_fast_image_to_video::Gene
|
||||
use artcraft_api_defs::generate::video::generate_veo_3_image_to_video::{GenerateVeo3AspectRatio, GenerateVeo3ImageToVideoRequest};
|
||||
use enums::common::generation_provider::GenerationProvider;
|
||||
use enums::tauri::tasks::task_type::TaskType;
|
||||
use fal_client::requests::queue::image_gen::enqueue_flux_pro_11_ultra_text_to_image::{enqueue_flux_pro_11_ultra_text_to_image, FluxPro11UltraTextToImageArgs};
|
||||
use fal_client::requests::queue::video_gen::enqueue_kling_16_pro_image_to_video::Kling16ProAspectRatio;
|
||||
use fal_client::requests::webhook::video::enqueue_veo_2_image_to_video_webhook::Veo2AspectRatio;
|
||||
use idempotency::uuid::generate_random_uuid;
|
||||
use log::{error, info};
|
||||
use storyteller_client::endpoints::generate::video::generate_kling_16_pro_image_to_video::generate_kling_16_pro_image_to_video;
|
||||
@@ -73,6 +71,11 @@ pub async fn handle_video_artcraft(
|
||||
None => {
|
||||
return Err(GenerateError::no_model_specified());
|
||||
}
|
||||
Some(VideoModel::Sora2) => {
|
||||
return Err(GenerateError::AnyhowError(
|
||||
anyhow!("wrong logic: another branch should handle this: {:?}",
|
||||
request.model)));
|
||||
}
|
||||
Some(VideoModel::Kling16Pro) => {
|
||||
info!("enqueue Kling 1.6 Pro with Artcraft API");
|
||||
selected_model = Some(GenerationModel::Kling1_6);
|
||||
|
||||
+3
-112
@@ -1,21 +1,8 @@
|
||||
use crate::core::commands::enqueue::generate_error::{BadInputReason, GenerateError};
|
||||
use crate::core::commands::enqueue::image_to_video::enqueue_image_to_video_command::{EnqueueImageToVideoRequest, VideoModel};
|
||||
use crate::core::commands::enqueue::generate_error::GenerateError;
|
||||
use crate::core::commands::enqueue::image_to_video::enqueue_image_to_video_command::EnqueueImageToVideoRequest;
|
||||
use crate::core::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess;
|
||||
use crate::core::events::basic_sendable_event_trait::BasicSendableEvent;
|
||||
use crate::core::events::generation_events::common::{GenerationAction, GenerationModel, GenerationServiceProvider};
|
||||
use crate::core::events::generation_events::generation_enqueue_failure_event::GenerationEnqueueFailureEvent;
|
||||
use crate::core::events::generation_events::generation_enqueue_success_event::GenerationEnqueueSuccessEvent;
|
||||
use crate::core::state::app_env_configs::app_env_configs::AppEnvConfigs;
|
||||
use crate::core::state::data_dir::app_data_root::AppDataRoot;
|
||||
use crate::core::utils::download_media_file_to_temp_dir::download_media_file_to_temp_dir;
|
||||
use crate::services::fal::state::fal_credential_manager::FalCredentialManager;
|
||||
use crate::services::fal::state::fal_task_queue::FalTaskQueue;
|
||||
use anyhow::anyhow;
|
||||
use enums::common::generation_provider::GenerationProvider;
|
||||
use enums::tauri::tasks::task_type::TaskType;
|
||||
use fal_client::requests::queue::image_gen::enqueue_flux_pro_11_ultra_text_to_image::{enqueue_flux_pro_11_ultra_text_to_image, FluxPro11UltraTextToImageArgs};
|
||||
use fal_client::requests::queue::video_gen::enqueue_kling_16_pro_image_to_video::{enqueue_kling_16_pro_image_to_video, Kling16ProArgs, Kling16ProAspectRatio, Kling16ProDuration};
|
||||
use log::{error, info};
|
||||
use tauri::AppHandle;
|
||||
|
||||
pub async fn handle_video_fal(
|
||||
@@ -23,102 +10,6 @@ pub async fn handle_video_fal(
|
||||
app_env_configs: &AppEnvConfigs,
|
||||
app_data_root: &AppDataRoot,
|
||||
request: EnqueueImageToVideoRequest,
|
||||
fal_creds_manager: &FalCredentialManager,
|
||||
fal_task_queue: &FalTaskQueue,
|
||||
) -> Result<TaskEnqueueSuccess, GenerateError> {
|
||||
|
||||
let api_key = match fal_creds_manager.get_key()? {
|
||||
Some(key) => key,
|
||||
None => {
|
||||
error!("No FAL API key is set. Can't perform action.");
|
||||
let event =
|
||||
GenerationEnqueueFailureEvent::no_fal_api_key(GenerationAction::GenerateVideo);
|
||||
|
||||
if let Err(err) = event.send(&app) {
|
||||
error!("Failed to emit event: {:?}", err); // Fail open.
|
||||
}
|
||||
|
||||
return Err(GenerateError::needs_fal_api_key());
|
||||
},
|
||||
};
|
||||
|
||||
//let api_key = fal_creds_manager.get_key_required()
|
||||
// .map_err(|err| {
|
||||
// error!("EnqueueTextToImage FAL api key required: {:?}", err);
|
||||
// InternalVideoError::NeedsFalApiKey
|
||||
// })?;
|
||||
|
||||
let mut temp_download;
|
||||
|
||||
if let Some(media_token) = request.image_media_token {
|
||||
temp_download = download_media_file_to_temp_dir(
|
||||
app_env_configs,
|
||||
app_data_root,
|
||||
&media_token
|
||||
).await?;
|
||||
} else {
|
||||
return Err(GenerateError::BadInput(BadInputReason::RequiredSourceImageNotProvided));
|
||||
}
|
||||
|
||||
let filename = temp_download.path().to_path_buf();
|
||||
|
||||
let mut selected_model = None;
|
||||
|
||||
let result = match request.model {
|
||||
None => {
|
||||
return Err(GenerateError::no_model_specified());
|
||||
}
|
||||
Some(VideoModel::Kling16Pro) => {
|
||||
info!("enqueue Kling 1.6 image to video with Kling API");
|
||||
selected_model = Some(GenerationModel::Kling1_6);
|
||||
enqueue_kling_16_pro_image_to_video(Kling16ProArgs {
|
||||
image_path: filename,
|
||||
api_key: &api_key,
|
||||
duration: Kling16ProDuration::Default,
|
||||
aspect_ratio: Kling16ProAspectRatio::WideSixteenNine,
|
||||
prompt: request.prompt.as_deref().unwrap_or(""),
|
||||
}).await
|
||||
}
|
||||
_ => {
|
||||
return Err(GenerateError::AnyhowError(anyhow!("Unsupported model: {:?}", request.model)));
|
||||
}
|
||||
};
|
||||
|
||||
let mut job_id = None;
|
||||
|
||||
match result {
|
||||
Ok(enqueued) => {
|
||||
info!("Successfully enqueued Kling 1.6 image to video");
|
||||
|
||||
job_id = Some(enqueued.request_id.to_string());
|
||||
|
||||
if let Err(err) = fal_task_queue.insert(&enqueued) {
|
||||
error!("Failed to enqueue task: {:?}", err);
|
||||
return Err(GenerateError::AnyhowError(anyhow!("Failed to enqueue task: {:?}", err)));
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
error!("Failed to enqueue image to video: {:?}", err);
|
||||
|
||||
let event = GenerationEnqueueFailureEvent {
|
||||
action: GenerationAction::GenerateVideo,
|
||||
service: GenerationServiceProvider::Fal,
|
||||
model: selected_model,
|
||||
reason: None,
|
||||
};
|
||||
|
||||
if let Err(err) = event.send(app) {
|
||||
error!("Failed to emit event: {:?}", err); // Fail open.
|
||||
}
|
||||
|
||||
return Err(GenerateError::from(err));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(TaskEnqueueSuccess {
|
||||
task_type: TaskType::VideoGeneration,
|
||||
model: selected_model,
|
||||
provider: GenerationProvider::Fal,
|
||||
provider_job_id: job_id,
|
||||
})
|
||||
Err(GenerateError::FalNoLongerSupported)
|
||||
}
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
pub mod enqueue_image_to_video_command;
|
||||
mod generic;
|
||||
mod generic;
|
||||
pub mod sora2;
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
use crate::core::commands::enqueue::generate_error::GenerateError;
|
||||
use crate::core::commands::enqueue::image_edit::enqueue_contextual_edit_image_command::EnqueueContextualEditImageCommand;
|
||||
use crate::core::commands::enqueue::image_inpaint::enqueue_image_inpaint_command::EnqueueInpaintImageCommand;
|
||||
use crate::core::commands::enqueue::image_to_video::enqueue_image_to_video_command::EnqueueImageToVideoRequest;
|
||||
use crate::core::commands::enqueue::image_to_video::sora2::handle_sora2_video_sora::handle_sora2_video_sora;
|
||||
use crate::core::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess;
|
||||
use crate::core::state::app_env_configs::app_env_configs::AppEnvConfigs;
|
||||
use crate::core::state::data_dir::app_data_root::AppDataRoot;
|
||||
use crate::core::state::provider_priority::{Provider, ProviderPriorityStore};
|
||||
use crate::services::sora::state::sora_credential_manager::SoraCredentialManager;
|
||||
use crate::services::sora::state::sora_task_queue::SoraTaskQueue;
|
||||
use crate::services::storyteller::state::storyteller_credential_manager::StorytellerCredentialManager;
|
||||
use log::info;
|
||||
use tauri::AppHandle;
|
||||
|
||||
pub async fn handle_sora2_video(
|
||||
request: &EnqueueImageToVideoRequest,
|
||||
app: &AppHandle,
|
||||
app_data_root: &AppDataRoot,
|
||||
app_env_configs: &AppEnvConfigs,
|
||||
provider_priority_store: &ProviderPriorityStore,
|
||||
sora_creds_manager: &SoraCredentialManager,
|
||||
) -> Result<TaskEnqueueSuccess, GenerateError> {
|
||||
|
||||
let priority = provider_priority_store.get_priority()?;
|
||||
|
||||
// TODO: Check if providers are available before proceeding.
|
||||
|
||||
info!("Providers by priority: {:?}", priority);
|
||||
|
||||
for provider in priority.iter() {
|
||||
match provider {
|
||||
Provider::Fal => {
|
||||
// Fallthrough (for now)
|
||||
}
|
||||
Provider::Artcraft => {
|
||||
// Fallthrough
|
||||
}
|
||||
Provider::Sora => {
|
||||
info!("Dispatching Sora2 via OpenAI Sora...");
|
||||
return handle_sora2_video_sora(
|
||||
request,
|
||||
app,
|
||||
app_data_root,
|
||||
app_env_configs,
|
||||
sora_creds_manager,
|
||||
).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Err(GenerateError::NoProviderAvailable)
|
||||
}
|
||||
+102
@@ -0,0 +1,102 @@
|
||||
use crate::core::commands::enqueue::generate_error::GenerateError;
|
||||
use crate::core::commands::enqueue::image_to_video::enqueue_image_to_video_command::{EnqueueImageToVideoRequest, SoraOrientation};
|
||||
use crate::core::commands::enqueue::task_enqueue_success::TaskEnqueueSuccess;
|
||||
use crate::core::events::generation_events::common::GenerationModel;
|
||||
use crate::core::state::app_env_configs::app_env_configs::AppEnvConfigs;
|
||||
use crate::core::state::data_dir::app_data_root::AppDataRoot;
|
||||
use crate::services::sora::state::sora_credential_manager::SoraCredentialManager;
|
||||
use crate::services::sora::state::sora_task_queue::SoraTaskQueue;
|
||||
use crate::services::sora::utils::upload_images_to_sora::{upload_images_to_sora, UploadImagesToSoraArgs, UploadImagesToSoraResult};
|
||||
use crate::services::storyteller::state::storyteller_credential_manager::StorytellerCredentialManager;
|
||||
use enums::common::generation_provider::GenerationProvider;
|
||||
use enums::tauri::tasks::task_type::TaskType;
|
||||
use log::{error, info};
|
||||
use openai_sora_client::recipes::generate_sora2_video::generate_sora2_video_with_session_auto_renew::generate_sora2_video_with_session_auto_renew;
|
||||
use openai_sora_client::recipes::list_sora2_drafts::list_sora2_drafts_with_session_auto_renew::list_sora2_drafts_with_session_auto_renew;
|
||||
use openai_sora_client::requests::generate_sora2_video::generate_sora2_video::{GenerateSora2VideoArgs, Orientation};
|
||||
use tauri::AppHandle;
|
||||
use tokens::tokens::media_files::MediaFileToken;
|
||||
use crate::core::events::functional_events::show_provider_login_modal_event::ShowProviderLoginModalEvent;
|
||||
|
||||
pub (super) async fn handle_sora2_video_sora(
|
||||
request: &EnqueueImageToVideoRequest,
|
||||
app: &AppHandle,
|
||||
app_data_root: &AppDataRoot,
|
||||
app_env_configs: &AppEnvConfigs,
|
||||
sora_creds_manager: &SoraCredentialManager,
|
||||
) -> Result<TaskEnqueueSuccess, GenerateError> {
|
||||
|
||||
let mut sora_creds = match sora_creds_manager.get_credentials()? {
|
||||
Some(creds) => creds,
|
||||
None => {
|
||||
ShowProviderLoginModalEvent::send_for_provider(GenerationProvider::Sora, &app);
|
||||
return Err(GenerateError::needs_sora_credentials());
|
||||
},
|
||||
};
|
||||
|
||||
// TODO: Handle multiple input images.
|
||||
let image_media_tokens = match &request.image_media_token {
|
||||
Some(token) => vec![token.clone()],
|
||||
None => vec![],
|
||||
};
|
||||
|
||||
let mut image_reference_media_ids = None;
|
||||
|
||||
if !image_media_tokens.is_empty() {
|
||||
let result = upload_images_to_sora(UploadImagesToSoraArgs {
|
||||
storyteller_host: &app_env_configs.storyteller_host,
|
||||
image_media_tokens: &image_media_tokens,
|
||||
app_data_root,
|
||||
sora_creds_manager,
|
||||
}).await?;
|
||||
|
||||
if let Some(creds) = result.maybe_new_sora_credentials {
|
||||
sora_creds = creds;
|
||||
}
|
||||
|
||||
image_reference_media_ids = Some(result.sora_media_tokens);
|
||||
}
|
||||
|
||||
let orientation = match request.sora_orientation {
|
||||
Some(SoraOrientation::Landscape) => Orientation::Landscape,
|
||||
Some(SoraOrientation::Portrait) => Orientation::Portrait,
|
||||
None => Orientation::Landscape,
|
||||
};
|
||||
|
||||
info!("Calling Sora 2 generate...");
|
||||
|
||||
let result = generate_sora2_video_with_session_auto_renew(
|
||||
GenerateSora2VideoArgs {
|
||||
prompt: request.prompt.as_deref().unwrap_or(""),
|
||||
credentials: &sora_creds,
|
||||
request_timeout: None,
|
||||
orientation,
|
||||
image_reference_media_ids: image_reference_media_ids.as_ref(),
|
||||
}
|
||||
).await;
|
||||
|
||||
let job_id = match result {
|
||||
Ok((response, maybe_new_session)) => {
|
||||
info!("Successfully enqueued Sora2 Video: {}", response.task_id);
|
||||
|
||||
if let Some(new_creds) = maybe_new_session {
|
||||
if let Err(err) = sora_creds_manager.set_credentials(&new_creds) {
|
||||
error!("Failed to save renewed Sora credentials: {:?}", err);
|
||||
}
|
||||
}
|
||||
|
||||
response.task_id
|
||||
}
|
||||
Err(err) => {
|
||||
error!("Failed to use Sora2 video: {:?}", err);
|
||||
return Err(GenerateError::from(err));
|
||||
}
|
||||
};
|
||||
|
||||
Ok(TaskEnqueueSuccess {
|
||||
provider: GenerationProvider::Sora,
|
||||
model: Some(GenerationModel::Sora2),
|
||||
provider_job_id: Some(job_id.to_string()),
|
||||
task_type: TaskType::VideoGeneration,
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
pub (super) mod handle_sora2_video;
|
||||
pub (super) mod handle_sora2_video_sora;
|
||||
@@ -70,6 +70,7 @@ impl TaskEnqueueSuccess{
|
||||
Some(GenerationModel::Kling21Pro) => Some(TaskModelType::Kling21Pro),
|
||||
Some(GenerationModel::Kling21Master) => Some(TaskModelType::Kling21Master),
|
||||
Some(GenerationModel::Seedance10Lite) => Some(TaskModelType::Seedance10Lite),
|
||||
Some(GenerationModel::Sora2) => Some(TaskModelType::Sora2),
|
||||
Some(GenerationModel::Veo2) => Some(TaskModelType::Veo2),
|
||||
Some(GenerationModel::Veo3) => Some(TaskModelType::Veo3),
|
||||
Some(GenerationModel::Veo3Fast) => Some(TaskModelType::Veo3Fast),
|
||||
|
||||
+1
-18
@@ -19,8 +19,6 @@ use crate::core::state::app_env_configs::app_env_configs::AppEnvConfigs;
|
||||
use crate::core::state::data_dir::app_data_root::AppDataRoot;
|
||||
use crate::core::state::provider_priority::{Provider, ProviderPriorityStore};
|
||||
use crate::core::state::task_database::TaskDatabase;
|
||||
use crate::services::fal::state::fal_credential_manager::FalCredentialManager;
|
||||
use crate::services::fal::state::fal_task_queue::FalTaskQueue;
|
||||
use crate::services::midjourney::state::midjourney_credential_manager::MidjourneyCredentialManager;
|
||||
use crate::services::sora::state::sora_credential_manager::SoraCredentialManager;
|
||||
use crate::services::sora::state::sora_task_queue::SoraTaskQueue;
|
||||
@@ -29,7 +27,6 @@ use enums::common::generation_provider::GenerationProvider;
|
||||
use enums::tauri::tasks::task_status::TaskStatus;
|
||||
use enums::tauri::tasks::task_type::TaskType;
|
||||
use enums::tauri::ux::tauri_command_caller::TauriCommandCaller;
|
||||
use fal_client::requests::queue::image_gen::enqueue_flux_pro_11_ultra_text_to_image::{enqueue_flux_pro_11_ultra_text_to_image, FluxPro11UltraTextToImageArgs};
|
||||
use log::{error, info, warn};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sqlite_tasks::queries::create_task::{create_task, CreateTaskArgs};
|
||||
@@ -146,10 +143,8 @@ pub async fn enqueue_text_to_image_command(
|
||||
app_env_configs: State<'_, AppEnvConfigs>,
|
||||
provider_priority_store: State<'_, ProviderPriorityStore>,
|
||||
task_database: State<'_, TaskDatabase>,
|
||||
fal_creds_manager: State<'_, FalCredentialManager>,
|
||||
mj_creds_manager: State<'_, MidjourneyCredentialManager>,
|
||||
storyteller_creds_manager: State<'_, StorytellerCredentialManager>,
|
||||
fal_task_queue: State<'_, FalTaskQueue>,
|
||||
sora_creds_manager: State<'_, SoraCredentialManager>,
|
||||
sora_task_queue: State<'_, SoraTaskQueue>,
|
||||
) -> Response<EnqueueTextToImageSuccessResponse, EnqueueTextToImageErrorType, ()> {
|
||||
@@ -162,11 +157,9 @@ pub async fn enqueue_text_to_image_command(
|
||||
&app_data_root,
|
||||
&provider_priority_store,
|
||||
&task_database,
|
||||
&fal_creds_manager,
|
||||
&mj_creds_manager,
|
||||
&storyteller_creds_manager,
|
||||
&app_env_configs,
|
||||
&fal_task_queue,
|
||||
&sora_creds_manager,
|
||||
&sora_task_queue,
|
||||
).await;
|
||||
@@ -232,11 +225,9 @@ pub async fn handle_request(
|
||||
app_data_root: &AppDataRoot,
|
||||
provider_priority_store: &ProviderPriorityStore,
|
||||
task_database: &TaskDatabase,
|
||||
fal_creds_manager: &FalCredentialManager,
|
||||
mj_creds_manager: &MidjourneyCredentialManager,
|
||||
storyteller_creds_manager: &StorytellerCredentialManager,
|
||||
app_env_configs: &AppEnvConfigs,
|
||||
fal_task_queue: &FalTaskQueue,
|
||||
sora_creds_manager: &SoraCredentialManager,
|
||||
sora_task_queue: &SoraTaskQueue,
|
||||
) -> Result<TaskEnqueueSuccess, GenerateError> {
|
||||
@@ -246,10 +237,8 @@ pub async fn handle_request(
|
||||
&app,
|
||||
&app_data_root,
|
||||
&provider_priority_store,
|
||||
&fal_creds_manager,
|
||||
&storyteller_creds_manager,
|
||||
&app_env_configs,
|
||||
&fal_task_queue,
|
||||
&mj_creds_manager,
|
||||
&sora_creds_manager,
|
||||
&sora_task_queue,
|
||||
@@ -282,10 +271,8 @@ pub async fn dispatch_request(
|
||||
app: &AppHandle,
|
||||
app_data_root: &AppDataRoot,
|
||||
provider_priority_store: &ProviderPriorityStore,
|
||||
fal_creds_manager: &FalCredentialManager,
|
||||
storyteller_creds_manager: &StorytellerCredentialManager,
|
||||
app_env_configs: &AppEnvConfigs,
|
||||
fal_task_queue: &FalTaskQueue,
|
||||
mj_creds_manager: &MidjourneyCredentialManager,
|
||||
sora_creds_manager: &SoraCredentialManager,
|
||||
sora_task_queue: &SoraTaskQueue,
|
||||
@@ -336,16 +323,12 @@ pub async fn dispatch_request(
|
||||
|
||||
for provider in priority.iter() {
|
||||
match provider {
|
||||
Provider::Fal => {} // Fallthrough
|
||||
Provider::Sora => {} // Fallthrough
|
||||
Provider::Artcraft => {
|
||||
return handle_image_artcraft(
|
||||
request, &app, app_env_configs, app_data_root, storyteller_creds_manager).await;
|
||||
}
|
||||
Provider::Fal => {
|
||||
if fal_creds_manager.has_apparent_api_token()? {
|
||||
return handle_image_fal(&app, request, fal_creds_manager, fal_task_queue).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
-1
@@ -17,7 +17,6 @@ use crate::services::sora::state::sora_task_queue::SoraTaskQueue;
|
||||
use crate::services::storyteller::state::storyteller_credential_manager::StorytellerCredentialManager;
|
||||
use enums::common::generation_provider::GenerationProvider;
|
||||
use enums::tauri::tasks::task_type::TaskType;
|
||||
use fal_client::requests::queue::image_gen::enqueue_flux_pro_11_ultra_text_to_image::{enqueue_flux_pro_11_ultra_text_to_image, FluxPro11UltraTextToImageArgs};
|
||||
use log::{error, info};
|
||||
use openai_sora_client::recipes::maybe_upgrade_or_renew_session::maybe_upgrade_or_renew_session;
|
||||
use openai_sora_client::recipes::simple_image_gen_with_session_auto_renew::{simple_image_gen_with_session_auto_renew, SimpleImageGenAutoRenewRequest};
|
||||
|
||||
-1
@@ -15,7 +15,6 @@ use artcraft_api_defs::generate::image::edit::gemini_25_flash_edit_image::{Gemin
|
||||
use artcraft_api_defs::generate::image::generate_gpt_image_1_text_to_image::{GenerateGptImage1TextToImageImageQuality, GenerateGptImage1TextToImageImageSize, GenerateGptImage1TextToImageNumImages, GenerateGptImage1TextToImageRequest};
|
||||
use enums::common::generation_provider::GenerationProvider;
|
||||
use enums::tauri::tasks::task_type::TaskType;
|
||||
use fal_client::requests::queue::image_gen::enqueue_flux_pro_11_ultra_text_to_image::{enqueue_flux_pro_11_ultra_text_to_image, FluxPro11UltraTextToImageArgs};
|
||||
use idempotency::uuid::generate_random_uuid;
|
||||
use log::{error, info};
|
||||
use openai_sora_client::recipes::maybe_upgrade_or_renew_session::maybe_upgrade_or_renew_session;
|
||||
|
||||
-1
@@ -14,7 +14,6 @@ use artcraft_api_defs::generate::image::generate_flux_pro_11_text_to_image::{Gen
|
||||
use artcraft_api_defs::generate::image::generate_flux_pro_11_ultra_text_to_image::{GenerateFluxPro11UltraTextToImageAspectRatio, GenerateFluxPro11UltraTextToImageNumImages, GenerateFluxPro11UltraTextToImageRequest};
|
||||
use enums::common::generation_provider::GenerationProvider;
|
||||
use enums::tauri::tasks::task_type::TaskType;
|
||||
use fal_client::requests::queue::image_gen::enqueue_flux_pro_11_ultra_text_to_image::{enqueue_flux_pro_11_ultra_text_to_image, FluxPro11UltraTextToImageArgs};
|
||||
use idempotency::uuid::generate_random_uuid;
|
||||
use log::{error, info};
|
||||
use storyteller_client::endpoints::generate::image::generate_flux_1_dev_text_to_image::generate_flux_1_dev_text_to_image;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user