mirror of
https://github.com/storytold/artcraft.git
synced 2026-11-04 09:28:11 +00:00
upload, finalize upload
This commit is contained in:
1 parent
6c996c6bc1
commit
04a2bd1fc8
8 files changed
+255
-3
No files matched your search
+12
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"context": null,
|
||||
"id": "cf326bd0-b7eb-454c-b433-54f36eb4d5a6",
|
||||
"display_name": "uploaded_image_7e2fb65f-819a-486b-9609-f782ad4d244f.jpeg",
|
||||
"owner_id": "sVgAQDyH3BfYOBfF6epWTjLHygX2",
|
||||
"status": "READY",
|
||||
"created_at": 1765325556,
|
||||
"updated_at": 1765325558,
|
||||
"object_uri": "https://cdn.marble.worldlabs.ai/object/cf326bd0-b7eb-454c-b433-54f36eb4d5a6/asset.jpg",
|
||||
"mime_type": "image/jpeg",
|
||||
"metadata": null
|
||||
}
|
||||
-1
@@ -1 +0,0 @@
|
||||
{"context":null,"id":"cf326bd0-b7eb-454c-b433-54f36eb4d5a6","display_name":"uploaded_image_7e2fb65f-819a-486b-9609-f782ad4d244f.jpeg","owner_id":"sVgAQDyH3BfYOBfF6epWTjLHygX2","status":"READY","created_at":1765325556,"updated_at":1765325558,"object_uri":"https://cdn.marble.worldlabs.ai/object/cf326bd0-b7eb-454c-b433-54f36eb4d5a6/asset.jpg","mime_type":"image/jpeg","metadata":null}
|
||||
+102
@@ -0,0 +1,102 @@
|
||||
use crate::api::api_types::upload_mime_type::UploadMimeType;
|
||||
use crate::api::common::common_header_values::{ORIGIN_VALUE, REFERER_VALUE};
|
||||
use crate::credentials::world_labs_bearer_token::WorldLabsBearerToken;
|
||||
use crate::credentials::world_labs_cookies::WorldLabsCookies;
|
||||
use crate::error::world_labs_client_error::WorldLabsClientError;
|
||||
use crate::error::world_labs_error::WorldLabsError;
|
||||
use crate::error::world_labs_generic_api_error::WorldLabsGenericApiError;
|
||||
use http_headers::names::{PRIORITY, SEC_FETCH_DEST, SEC_FETCH_MODE, SEC_FETCH_SITE, SEC_GPC};
|
||||
use http_headers::values::accept::ACCEPT_ALL;
|
||||
use http_headers::values::cache_control::CACHE_CONTROL_NO_CACHE;
|
||||
use http_headers::values::connection::CONNECTION_KEEP_ALIVE;
|
||||
use http_headers::values::pragma::PRAGMA_NO_CACHE;
|
||||
use http_headers::values::priority::{PRIORITY_6};
|
||||
use http_headers::values::sec::{SEC_FETCH_DEST_EMPTY, SEC_FETCH_MODE_CORS, SEC_FETCH_SITE_CROSS_SITE};
|
||||
use http_headers::values::te::TE_TRAILERS;
|
||||
use log::error;
|
||||
use serde::{Serialize};
|
||||
use std::time::Duration;
|
||||
use wreq::header::{ACCEPT, ACCEPT_ENCODING, ACCEPT_LANGUAGE, AUTHORIZATION, CACHE_CONTROL, CONNECTION, CONTENT_LENGTH, CONTENT_TYPE, ORIGIN, PRAGMA, REFERER, TE};
|
||||
use wreq::Client;
|
||||
use wreq_util::Emulation;
|
||||
|
||||
pub struct GoogleUploadImageArgs<'a> {
|
||||
pub cookies: &'a WorldLabsCookies,
|
||||
pub bearer_token: &'a WorldLabsBearerToken,
|
||||
pub upload_url: &'a str,
|
||||
pub upload_mime_type: UploadMimeType,
|
||||
pub file_bytes: Vec<u8>,
|
||||
pub request_timeout: Option<Duration>,
|
||||
}
|
||||
|
||||
/// Marble Image-to-World
|
||||
/// Upload the binary to GCP using the signed URL
|
||||
/// Request #4 (of ~10)
|
||||
pub async fn google_upload_image(args: GoogleUploadImageArgs<'_>) -> Result<(), WorldLabsError> {
|
||||
let client = Client::builder()
|
||||
.emulation(Emulation::Firefox143)
|
||||
.build()
|
||||
.map_err(|err| WorldLabsClientError::WreqClientError(err))?;
|
||||
|
||||
let content_type = args.upload_mime_type.content_type();
|
||||
|
||||
let mut request_builder = client.post(args.upload_url)
|
||||
.header(ACCEPT, ACCEPT_ALL)
|
||||
.header(ACCEPT_LANGUAGE, "en-US,en;q=0.5")
|
||||
.header(ACCEPT_ENCODING, "gzip, deflate, br, zstd")
|
||||
.header(REFERER, REFERER_VALUE)
|
||||
.header(CONTENT_TYPE, content_type)
|
||||
.header("x-goog-content-length-range", "0,1048576000")
|
||||
.header(AUTHORIZATION, args.bearer_token.to_bearer_token_string())
|
||||
.header(ORIGIN, ORIGIN_VALUE)
|
||||
.header(SEC_GPC, "1")
|
||||
.header(CONNECTION, CONNECTION_KEEP_ALIVE)
|
||||
.header(SEC_FETCH_DEST, SEC_FETCH_DEST_EMPTY)
|
||||
.header(SEC_FETCH_MODE, SEC_FETCH_MODE_CORS)
|
||||
.header(SEC_FETCH_SITE, SEC_FETCH_SITE_CROSS_SITE)
|
||||
.header(PRIORITY, PRIORITY_6)
|
||||
.header(PRAGMA, PRAGMA_NO_CACHE)
|
||||
.header(CACHE_CONTROL, CACHE_CONTROL_NO_CACHE)
|
||||
.header(CONTENT_LENGTH, 0)
|
||||
.header(TE, TE_TRAILERS);
|
||||
|
||||
if let Some(timeout) = args.request_timeout {
|
||||
request_builder = request_builder.timeout(timeout);
|
||||
}
|
||||
|
||||
let http_request = request_builder
|
||||
.body(args.file_bytes)
|
||||
.build()
|
||||
.map_err(|err| {
|
||||
error!("Error building request: {:?}", err);
|
||||
WorldLabsClientError::WreqClientError(err)
|
||||
})?;
|
||||
|
||||
let response = client.execute(http_request)
|
||||
.await
|
||||
.map_err(|err| {
|
||||
error!("Error during request execution: {:?}", err);
|
||||
WorldLabsGenericApiError::WreqError(err)
|
||||
})?;
|
||||
|
||||
let status = response.status();
|
||||
|
||||
let response_body = response.text()
|
||||
.await
|
||||
.map_err(|err| {
|
||||
error!("Error reading response body: {:?}", err);
|
||||
WorldLabsGenericApiError::WreqError(err)
|
||||
})?;
|
||||
|
||||
// TODO: Handle errors (Cloudflare, Grok, etc.)
|
||||
if !status.is_success() {
|
||||
error!("Request returned an error (code {}) : {:?}", status.as_u16(), response_body);
|
||||
//return Err(classify_general_http_status_code_and_body(status, response_body));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
pub mod google_upload_image;
|
||||
@@ -1 +1,2 @@
|
||||
pub mod objects;
|
||||
pub mod objects;
|
||||
pub mod google_upload;
|
||||
+132
@@ -0,0 +1,132 @@
|
||||
use crate::api::api_types::object_id::ObjectId;
|
||||
use crate::api::api_types::upload_id::UploadId;
|
||||
use crate::api::api_types::upload_mime_type::UploadMimeType;
|
||||
use crate::api::common::common_header_values::{ORIGIN_VALUE, REFERER_VALUE};
|
||||
use crate::credentials::world_labs_bearer_token::WorldLabsBearerToken;
|
||||
use crate::credentials::world_labs_cookies::WorldLabsCookies;
|
||||
use crate::error::world_labs_client_error::WorldLabsClientError;
|
||||
use crate::error::world_labs_error::WorldLabsError;
|
||||
use crate::error::world_labs_generic_api_error::WorldLabsGenericApiError;
|
||||
use chrono::Utc;
|
||||
use http_headers::names::{PRIORITY, SEC_FETCH_DEST, SEC_FETCH_MODE, SEC_FETCH_SITE, SEC_GPC};
|
||||
use http_headers::values::accept::ACCEPT_ALL;
|
||||
use http_headers::values::cache_control::CACHE_CONTROL_NO_CACHE;
|
||||
use http_headers::values::connection::CONNECTION_KEEP_ALIVE;
|
||||
use http_headers::values::content_type::CONTENT_TYPE_APPLICATION_JSON;
|
||||
use http_headers::values::pragma::PRAGMA_NO_CACHE;
|
||||
use http_headers::values::priority::PRIORITY_4;
|
||||
use http_headers::values::sec::{SEC_FETCH_DEST_EMPTY, SEC_FETCH_MODE_CORS, SEC_FETCH_SITE_CROSS_SITE};
|
||||
use http_headers::values::te::TE_TRAILERS;
|
||||
use log::error;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::time::Duration;
|
||||
use uuid::Uuid;
|
||||
use wreq::header::{ACCEPT, ACCEPT_ENCODING, ACCEPT_LANGUAGE, AUTHORIZATION, CACHE_CONTROL, CONNECTION, CONTENT_LENGTH, CONTENT_TYPE, ORIGIN, PRAGMA, REFERER, TE};
|
||||
use wreq::Client;
|
||||
use wreq_util::Emulation;
|
||||
|
||||
const BASE_URL : &str = "https://marble2-kgw-prod-iac1.wlt-ai.art/api/v1/objects";
|
||||
|
||||
fn get_url(object_id: &ObjectId) -> String {
|
||||
format!("{}/{}:complete", BASE_URL, object_id.0)
|
||||
}
|
||||
|
||||
pub struct BeginObjectImageUploadArgs<'a> {
|
||||
pub cookies: &'a WorldLabsCookies,
|
||||
pub bearer_token: &'a WorldLabsBearerToken,
|
||||
pub object_id: &'a ObjectId,
|
||||
pub request_timeout: Option<Duration>,
|
||||
}
|
||||
|
||||
pub struct BeginObjectImageUploadResponse {
|
||||
pub object_url: String,
|
||||
}
|
||||
|
||||
/// Marble Image-to-World
|
||||
/// Mark the upload as complete.
|
||||
/// Request #5 (of ~10)
|
||||
pub async fn finalize_object_image_upload(args: BeginObjectImageUploadArgs<'_>) -> Result<BeginObjectImageUploadResponse, WorldLabsError> {
|
||||
let client = Client::builder()
|
||||
.emulation(Emulation::Firefox143)
|
||||
.build()
|
||||
.map_err(|err| WorldLabsClientError::WreqClientError(err))?;
|
||||
|
||||
let url = get_url(args.object_id);
|
||||
|
||||
let mut request_builder = client.post(url)
|
||||
.header(ACCEPT, ACCEPT_ALL)
|
||||
.header(ACCEPT_LANGUAGE, "en-US,en;q=0.5")
|
||||
.header(ACCEPT_ENCODING, "gzip, deflate, br, zstd")
|
||||
.header(REFERER, REFERER_VALUE)
|
||||
.header(AUTHORIZATION, args.bearer_token.to_bearer_token_string())
|
||||
.header(ORIGIN, ORIGIN_VALUE)
|
||||
.header(SEC_GPC, "1")
|
||||
.header(CONNECTION, CONNECTION_KEEP_ALIVE)
|
||||
.header(SEC_FETCH_DEST, SEC_FETCH_DEST_EMPTY)
|
||||
.header(SEC_FETCH_MODE, SEC_FETCH_MODE_CORS)
|
||||
.header(SEC_FETCH_SITE, SEC_FETCH_SITE_CROSS_SITE)
|
||||
.header(PRIORITY, PRIORITY_4)
|
||||
.header(PRAGMA, PRAGMA_NO_CACHE)
|
||||
.header(CACHE_CONTROL, CACHE_CONTROL_NO_CACHE)
|
||||
.header(CONTENT_LENGTH, 0)
|
||||
.header(TE, TE_TRAILERS);
|
||||
|
||||
if let Some(timeout) = args.request_timeout {
|
||||
request_builder = request_builder.timeout(timeout);
|
||||
}
|
||||
|
||||
let http_request = request_builder
|
||||
.build()
|
||||
.map_err(|err| {
|
||||
error!("Error building request: {:?}", err);
|
||||
WorldLabsClientError::WreqClientError(err)
|
||||
})?;
|
||||
|
||||
let response = client.execute(http_request)
|
||||
.await
|
||||
.map_err(|err| {
|
||||
error!("Error during request execution: {:?}", err);
|
||||
WorldLabsGenericApiError::WreqError(err)
|
||||
})?;
|
||||
|
||||
let status = response.status();
|
||||
|
||||
let response_body = response.text()
|
||||
.await
|
||||
.map_err(|err| {
|
||||
error!("Error reading response body: {:?}", err);
|
||||
WorldLabsGenericApiError::WreqError(err)
|
||||
})?;
|
||||
|
||||
// TODO: Handle errors (Cloudflare, Grok, etc.)
|
||||
if !status.is_success() {
|
||||
error!("Request returned an error (code {}) : {:?}", status.as_u16(), response_body);
|
||||
//return Err(classify_general_http_status_code_and_body(status, response_body));
|
||||
}
|
||||
|
||||
let response : RawResponse = serde_json::from_str(&response_body)
|
||||
.map_err(|err| WorldLabsGenericApiError::SerdeResponseParseErrorWithBody(err, response_body.to_string()))?;
|
||||
|
||||
Ok(BeginObjectImageUploadResponse {
|
||||
object_url: response.object_uri,
|
||||
})
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct RawResponse {
|
||||
/// eg. "https://cdn.marble.worldlabs.ai/object/foo-bar-baz-bin/asset.jpg"
|
||||
pub object_uri: String,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::api::api_types::object_id::ObjectId;
|
||||
use crate::api::requests::objects::finalize_object_image_upload::get_url;
|
||||
|
||||
#[test]
|
||||
fn test_get_url() {
|
||||
let object_id = ObjectId("foo-bar-baz-bin".to_string());
|
||||
let expected = "https://marble2-kgw-prod-iac1.wlt-ai.art/api/v1/objects/foo-bar-baz-bin:complete";
|
||||
assert_eq!(get_url(&object_id), expected);
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
pub mod create_object;
|
||||
pub mod begin_object_image_attachment;
|
||||
pub mod begin_object_image_upload;
|
||||
pub mod begin_object_image_upload;
|
||||
pub mod finalize_object_image_upload;
|
||||
@@ -4,3 +4,7 @@ pub const PRIORITY_HIGHEST : &str = "u=0";
|
||||
/// eg. -H 'Priority: u=4'
|
||||
/// "The default priority value for requests is 3."
|
||||
pub const PRIORITY_4: &str = "u=4";
|
||||
|
||||
/// eg. -H 'Priority: u=6'
|
||||
/// "The default priority value for requests is 3."
|
||||
pub const PRIORITY_6: &str = "u=6";
|
||||
Reference in new issue
Block a user