upload, finalize upload

This commit is contained in:
Brandon Thomas committed 2025-12-16 22:11:49 -05:00
1 parent 6c996c6bc1
commit 04a2bd1fc8
8 files changed
+255 -3

No files matched your search

@@ -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 +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}
@@ -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;
@@ -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";