mirror of
https://github.com/storytold/artcraft.git
synced 2026-11-10 20:11:49 +00:00
update to handle generic file upload
This commit is contained in:
1 parent
2e2c308c5f
commit
10f9a9839a
9 files changed
+82
-53
No files matched your search
@@ -1,4 +1,4 @@
|
||||
pub mod generate_video;
|
||||
pub mod poll_orders;
|
||||
pub mod prepare_image_upload;
|
||||
pub mod upload_image;
|
||||
pub mod prepare_file_upload;
|
||||
pub mod upload_file;
|
||||
@@ -0,0 +1,2 @@
|
||||
pub mod prepare_file_upload;
|
||||
mod request_types;
|
||||
+39
-14
@@ -2,7 +2,7 @@ use crate::creds::seedance2pro_session::Seedance2ProSession;
|
||||
use crate::error::seedance2pro_client_error::Seedance2ProClientError;
|
||||
use crate::error::seedance2pro_error::Seedance2ProError;
|
||||
use crate::error::seedance2pro_generic_api_error::Seedance2ProGenericApiError;
|
||||
use crate::requests::prepare_image_upload::request_types::*;
|
||||
use crate::requests::prepare_file_upload::request_types::*;
|
||||
use crate::utils::common_headers::FIREFOX_USER_AGENT;
|
||||
use chrono::Utc;
|
||||
use log::info;
|
||||
@@ -13,32 +13,35 @@ use wreq_util::Emulation;
|
||||
const SIGNED_UPLOAD_URL: &str = "https://seedance2-pro.com/api/trpc/uploads.signedUploadUrl?batch=1";
|
||||
|
||||
/// Generates a material path based on the current time.
|
||||
/// Format: `materials/YYYYMMDD/<unix_millis>-<random_hex>.png`
|
||||
fn generate_material_path() -> String {
|
||||
/// Format: `materials/YYYYMMDD/<unix_millis>-<random_hex>.<extension>`
|
||||
fn generate_material_path(extension: &str) -> String {
|
||||
let now = Utc::now();
|
||||
let date_part = now.format("%Y%m%d").to_string();
|
||||
let timestamp_millis = now.timestamp_millis();
|
||||
let random_hex: u32 = rand::rng().random();
|
||||
let hex_part = format!("{:08x}", random_hex);
|
||||
format!("materials/{}/{}-{}.png", date_part, timestamp_millis, hex_part)
|
||||
format!("materials/{}/{}-{}.{}", date_part, timestamp_millis, hex_part, extension)
|
||||
}
|
||||
|
||||
pub struct PrepareImageUploadArgs<'a> {
|
||||
pub struct PrepareFileUploadArgs<'a> {
|
||||
pub session: &'a Seedance2ProSession,
|
||||
|
||||
/// File extension without the dot, e.g. "png", "mp4", "mp3".
|
||||
pub extension: String,
|
||||
}
|
||||
|
||||
pub struct PrepareImageUploadResponse {
|
||||
/// The signed URL to upload the image to (Cloudflare R2 / S3-compatible).
|
||||
pub struct PrepareFileUploadResponse {
|
||||
/// The signed URL to upload the file to (Cloudflare R2 / S3-compatible).
|
||||
pub upload_url: String,
|
||||
|
||||
/// The material path that was generated for this upload.
|
||||
pub material_path: String,
|
||||
}
|
||||
|
||||
pub async fn prepare_image_upload(args: PrepareImageUploadArgs<'_>) -> Result<PrepareImageUploadResponse, Seedance2ProError> {
|
||||
let material_path = generate_material_path();
|
||||
pub async fn prepare_file_upload(args: PrepareFileUploadArgs<'_>) -> Result<PrepareFileUploadResponse, Seedance2ProError> {
|
||||
let material_path = generate_material_path(&args.extension);
|
||||
|
||||
info!("Preparing image upload with path: {}", material_path);
|
||||
info!("Preparing file upload with path: {}", material_path);
|
||||
|
||||
let client = Client::builder()
|
||||
.emulation(Emulation::Firefox143)
|
||||
@@ -103,7 +106,7 @@ pub async fn prepare_image_upload(args: PrepareImageUploadArgs<'_>) -> Result<Pr
|
||||
.data
|
||||
.json;
|
||||
|
||||
Ok(PrepareImageUploadResponse {
|
||||
Ok(PrepareFileUploadResponse {
|
||||
upload_url,
|
||||
material_path,
|
||||
})
|
||||
@@ -120,19 +123,41 @@ mod tests {
|
||||
|
||||
#[tokio::test]
|
||||
#[ignore] // manually test — requires real cookies
|
||||
async fn test_prepare_image_upload() -> AnyhowResult<()> {
|
||||
async fn test_prepare_file_upload_png() -> AnyhowResult<()> {
|
||||
setup_test_logging(LevelFilter::Trace);
|
||||
let cookies = get_test_cookies()?;
|
||||
let session = Seedance2ProSession::from_cookies_string(cookies);
|
||||
let args = PrepareImageUploadArgs {
|
||||
let args = PrepareFileUploadArgs {
|
||||
session: &session,
|
||||
extension: "png".to_string(),
|
||||
};
|
||||
let result = prepare_image_upload(args).await?;
|
||||
let result = prepare_file_upload(args).await?;
|
||||
println!("Upload URL: {}", result.upload_url);
|
||||
println!("Material path: {}", result.material_path);
|
||||
assert!(!result.upload_url.is_empty());
|
||||
assert!(result.upload_url.contains("cloudflarestorage.com"));
|
||||
assert!(result.material_path.starts_with("materials/"));
|
||||
assert!(result.material_path.ends_with(".png"));
|
||||
|
||||
assert_eq!(1, 2); // NB: Intentional failure to check the response.
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[ignore] // manually test — requires real cookies
|
||||
async fn test_prepare_file_upload_mp4() -> AnyhowResult<()> {
|
||||
setup_test_logging(LevelFilter::Trace);
|
||||
let cookies = get_test_cookies()?;
|
||||
let session = Seedance2ProSession::from_cookies_string(cookies);
|
||||
let args = PrepareFileUploadArgs {
|
||||
session: &session,
|
||||
extension: "mp4".to_string(),
|
||||
};
|
||||
let result = prepare_file_upload(args).await?;
|
||||
println!("Upload URL: {}", result.upload_url);
|
||||
println!("Material path: {}", result.material_path);
|
||||
assert!(!result.upload_url.is_empty());
|
||||
assert!(result.material_path.ends_with(".mp4"));
|
||||
|
||||
assert_eq!(1, 2); // NB: Intentional failure to check the response.
|
||||
Ok(())
|
||||
File renamed without changes.
@@ -1,2 +0,0 @@
|
||||
pub mod prepare_image_upload;
|
||||
mod request_types;
|
||||
@@ -0,0 +1 @@
|
||||
pub mod upload_file;
|
||||
+21
-20
@@ -9,16 +9,16 @@ use wreq_util::Emulation;
|
||||
|
||||
const STATIC_BASE_URL: &str = "https://static.seedance2-pro.com";
|
||||
|
||||
pub struct UploadImageArgs {
|
||||
/// The signed upload URL returned by `prepare_image_upload`.
|
||||
pub struct UploadFileArgs {
|
||||
/// The signed upload URL returned by `prepare_file_upload`.
|
||||
pub upload_url: String,
|
||||
|
||||
/// The raw image bytes to upload.
|
||||
pub image_bytes: Vec<u8>,
|
||||
/// The raw file bytes to upload.
|
||||
pub file_bytes: Vec<u8>,
|
||||
}
|
||||
|
||||
pub struct UploadImageResponse {
|
||||
/// The public-facing URL for the uploaded image.
|
||||
pub struct UploadFileResponse {
|
||||
/// The public-facing URL for the uploaded file.
|
||||
/// e.g. `https://static.seedance2-pro.com/materials/20260219/1771463564512-b14bfe90.png`
|
||||
pub public_url: String,
|
||||
}
|
||||
@@ -33,8 +33,8 @@ fn build_public_url(upload_url: &str) -> Result<String, Seedance2ProError> {
|
||||
Ok(format!("{}{}", STATIC_BASE_URL, path))
|
||||
}
|
||||
|
||||
pub async fn upload_image(args: UploadImageArgs) -> Result<UploadImageResponse, Seedance2ProError> {
|
||||
info!("Uploading image to: {}", args.upload_url);
|
||||
pub async fn upload_file(args: UploadFileArgs) -> Result<UploadFileResponse, Seedance2ProError> {
|
||||
info!("Uploading file to: {}", args.upload_url);
|
||||
|
||||
let client = Client::builder()
|
||||
.emulation(Emulation::Firefox143)
|
||||
@@ -53,7 +53,7 @@ pub async fn upload_image(args: UploadImageArgs) -> Result<UploadImageResponse,
|
||||
.header("Sec-Fetch-Mode", "cors")
|
||||
.header("Sec-Fetch-Site", "cross-site")
|
||||
.header("Priority", "u=4")
|
||||
.body(args.image_bytes)
|
||||
.body(args.file_bytes)
|
||||
.send()
|
||||
.await
|
||||
.map_err(|err| Seedance2ProGenericApiError::WreqError(err))?;
|
||||
@@ -77,7 +77,7 @@ pub async fn upload_image(args: UploadImageArgs) -> Result<UploadImageResponse,
|
||||
|
||||
info!("Public URL: {}", public_url);
|
||||
|
||||
Ok(UploadImageResponse { public_url })
|
||||
Ok(UploadFileResponse { public_url })
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -86,8 +86,8 @@ mod tests {
|
||||
use crate::creds::seedance2pro_session::Seedance2ProSession;
|
||||
use crate::test_utils::get_test_cookies::get_test_cookies;
|
||||
use crate::test_utils::setup_test_logging::setup_test_logging;
|
||||
use crate::requests::prepare_image_upload::prepare_image_upload::{
|
||||
prepare_image_upload, PrepareImageUploadArgs,
|
||||
use crate::requests::prepare_file_upload::prepare_file_upload::{
|
||||
prepare_file_upload, PrepareFileUploadArgs,
|
||||
};
|
||||
use errors::AnyhowResult;
|
||||
use log::LevelFilter;
|
||||
@@ -95,28 +95,29 @@ mod tests {
|
||||
|
||||
#[tokio::test]
|
||||
#[ignore] // manually test — requires real cookies and a test image
|
||||
async fn test_upload_image() -> AnyhowResult<()> {
|
||||
async fn test_upload_image_file() -> AnyhowResult<()> {
|
||||
setup_test_logging(LevelFilter::Trace);
|
||||
|
||||
// Step 1: Get a signed upload URL
|
||||
let cookies = get_test_cookies()?;
|
||||
let session = Seedance2ProSession::from_cookies_string(cookies);
|
||||
let prepare_args = PrepareImageUploadArgs {
|
||||
let prepare_args = PrepareFileUploadArgs {
|
||||
session: &session,
|
||||
extension: "jpg".to_string(),
|
||||
};
|
||||
let prepare_result = prepare_image_upload(prepare_args).await?;
|
||||
let prepare_result = prepare_file_upload(prepare_args).await?;
|
||||
println!("Upload URL: {}", prepare_result.upload_url);
|
||||
|
||||
// Step 2: Read a test image
|
||||
let image_bytes = fs::read("/Users/bt/dev/storyteller/artcraft/test_data/image/juno.jpg")?;
|
||||
println!("Image size: {} bytes", image_bytes.len());
|
||||
let file_bytes = fs::read("/Users/bt/dev/storyteller/artcraft/test_data/image/juno.jpg")?;
|
||||
println!("File size: {} bytes", file_bytes.len());
|
||||
|
||||
// Step 3: Upload
|
||||
let upload_args = UploadImageArgs {
|
||||
let upload_args = UploadFileArgs {
|
||||
upload_url: prepare_result.upload_url,
|
||||
image_bytes,
|
||||
file_bytes,
|
||||
};
|
||||
let result = upload_image(upload_args).await?;
|
||||
let result = upload_file(upload_args).await?;
|
||||
println!("Public URL: {}", result.public_url);
|
||||
|
||||
assert!(result.public_url.starts_with("https://static.seedance2-pro.com/materials/"));
|
||||
@@ -1 +0,0 @@
|
||||
pub mod upload_image;
|
||||
+17
-14
@@ -34,10 +34,10 @@ use seedance2pro::creds::seedance2pro_session::Seedance2ProSession;
|
||||
use seedance2pro::requests::generate_video::generate_video::{
|
||||
generate_video, BatchCount, GenerateVideoArgs, Resolution,
|
||||
};
|
||||
use seedance2pro::requests::prepare_image_upload::prepare_image_upload::{
|
||||
prepare_image_upload, PrepareImageUploadArgs,
|
||||
use seedance2pro::requests::prepare_file_upload::prepare_file_upload::{
|
||||
prepare_file_upload, PrepareFileUploadArgs,
|
||||
};
|
||||
use seedance2pro::requests::upload_image::upload_image::{upload_image, UploadImageArgs};
|
||||
use seedance2pro::requests::upload_file::upload_file::{upload_file, UploadFileArgs};
|
||||
use sqlx::Acquire;
|
||||
use tokens::tokens::generic_inference_jobs::InferenceJobToken;
|
||||
|
||||
@@ -389,20 +389,23 @@ async fn upload_to_seedance2pro(
|
||||
})?
|
||||
.to_vec();
|
||||
|
||||
let prepare_result = prepare_image_upload(PrepareImageUploadArgs { session })
|
||||
.await
|
||||
.map_err(|err| {
|
||||
warn!("Error preparing seedance2pro image upload: {:?}", err);
|
||||
CommonWebError::ServerError
|
||||
})?;
|
||||
|
||||
let upload_result = upload_image(UploadImageArgs {
|
||||
upload_url: prepare_result.upload_url,
|
||||
image_bytes,
|
||||
let prepare_result = prepare_file_upload(PrepareFileUploadArgs {
|
||||
session,
|
||||
extension: "png".to_string(),
|
||||
})
|
||||
.await
|
||||
.map_err(|err| {
|
||||
warn!("Error uploading image to seedance2pro: {:?}", err);
|
||||
warn!("Error preparing seedance2pro file upload: {:?}", err);
|
||||
CommonWebError::ServerError
|
||||
})?;
|
||||
|
||||
let upload_result = upload_file(UploadFileArgs {
|
||||
upload_url: prepare_result.upload_url,
|
||||
file_bytes: image_bytes,
|
||||
})
|
||||
.await
|
||||
.map_err(|err| {
|
||||
warn!("Error uploading file to seedance2pro: {:?}", err);
|
||||
CommonWebError::ServerError
|
||||
})?;
|
||||
|
||||
|
||||
Reference in new issue
Block a user