video generation just works!

This commit is contained in:
Brandon Thomas committed 2025-10-03 15:18:33 -04:00
1 parent 64e9244169
commit cf89c2bd9e
6 files changed
+213 -4

No files matched your search

@@ -0,0 +1,146 @@
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;
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";
pub struct GenerateSora2VideoArgs<'a> {
pub prompt: &'a str,
pub credentials: &'a SoraCredentialSet,
pub request_timeout: Option<Duration>,
}
#[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 request_body = HttpCreateRequest {
kind: "video".to_string(),
prompt: args.prompt.to_string(),
title: None,
orientation: "portrait".to_string(),
size: "small".to_string(),
n_frames: 300,
inpaint_items: vec![],
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};
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 baby sea otter wearing a beret and glasses, sitting on a rock and reading a book, digital art",
credentials: &creds,
request_timeout: None,
};
let result = generate_sora2_video(request).await?;
println!("result: {:#?}", result);
assert_eq!(1, 2);
Ok(())
}
}
@@ -1,9 +1,11 @@
curl 'https://sora.chatgpt.com/backend/nf/create' \
-H 'accept: */*' \
-H 'accept-language: en-US,en;q=0.9' \
-H 'authorization: Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6IjE5MzQ0ZTY1LWJiYzktNDRkMS1hOWQwLWY5NTdiMDc5YmQwZSIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsiaHR0cHM6Ly9hcGkub3BlbmFpLmNvbS92MSJdLCJjbGllbnRfaWQiOiJhcHBfWDh6WTZ2VzJwUTl0UjNkRTduSzFqTDVnSCIsImV4cCI6MTc2MDMwMDg4NSwiaHR0cHM6Ly9hcGkub3BlbmFpLmNvbS9hdXRoIjp7InVzZXJfaWQiOiJ1c2VyLWhRNWZjMUFWN3ZOY1FMazJ5ZkVGRVVibyJ9LCJodHRwczovL2FwaS5vcGVuYWkuY29tL3Byb2ZpbGUiOnsiZW1haWwiOiJ2aWN0b3J2b2ljZTg5QGdtYWlsLmNvbSIsImVtYWlsX3ZlcmlmaWVkIjp0cnVlfSwiaWF0IjoxNzU5NDM2ODg1LCJpc3MiOiJodHRwczovL2F1dGgub3BlbmFpLmNvbSIsImp0aSI6IjE1ZTU5NjYyLTIxOTYtNDE3MS05ODQwLWVlZmRkMTZkZWQzYSIsIm5iZiI6MTc1OTQzNjg4NSwicHdkX2F1dGhfdGltZSI6MTc1OTQzNjg4NDY3NCwic2NwIjpbIm9wZW5pZCIsImVtYWlsIiwicHJvZmlsZSIsIm9mZmxpbmVfYWNjZXNzIiwibW9kZWwucmVxdWVzdCIsIm1vZGVsLnJlYWQiLCJvcmdhbml6YXRpb24ucmVhZCIsIm9yZ2FuaXphdGlvbi53cml0ZSJdLCJzZXNzaW9uX2lkIjoiYXV0aHNlc3NfSU1TS1pmS0ZWMEh4R1I3eGhjNk5DVkRvIiwic3ViIjoiZ29vZ2xlLW9hdXRoMnwxMTY0NTkzMDAxOTg2MTk4ODg4MzYifQ.l2dQ26eSPCpKpGONFQACXSn-Rndp5ClfwWEAE1uB2eGECSbbn9-m_9zlA0yWnjqwjXYZ5pdWKbuTgWlDgDEwjsprp2OVFH6GIPVOAVKwSZzUWf8ywdpsQBcYIYTpNWGWTK7HytK3P4Xu5I--nZ4TMZI5_iFrXOb8eS-DOSzqm6DjWfiYRcm9qr4JRJokPsXXl1JnHVm0m9iI6xAgS95B0iPoWvfIHgihxWq5k3LdwM-rzIU2srKAZxjuyOnslHkakKZ5algVcxsDfKR4N8oIyNSoOeHPUKDiGnr-O-PqODbdAwqzo1JJP7Bmskc_5bx01pbi6bUQd5MPnT7qdEbIZexkYjUuV6qRCObmCWNV5WTJwVvdTZ9nBhQ2cp6wmAee3y4buQawtXlnhwNaYgkJtpOBgSYIKnTKPTKjMd1PigO57LgSrUPUHVyDVtZ_qBhIlw_PqAu_bFEfx9xyOiWp72KgyS89-32zeZzJcd6m85NsP_1clIe483GMl6wGYMhGxmJazM_twq28wrXwrI3vWnShhPA0qXLKYgFBT10z937OyNgmxXfNWRMB4OgkgXF5ylFHW0aN_ztu7H2NGx2jCIPn0QkAoc8bSjyW98Ybwn_adMiV4FkEeNLvc-KQf6DcULOYiKDYAZWVthpfc2yTAcJUtqmg9Gkw053xWObrRZ4' \
-H 'content-type: application/json' \
-b '__Host-next-auth.csrf-token=53557711393b5bb42525188b40374f5a2d88c6e62f40ffa7303a6855b7ed3abf%7C666b09f1d850c146bf75cb2ef458d273b49cdcc3536619a4748c8b6f213c7ae0; __Secure-next-auth.callback-url=https%3A%2F%2Fsora.com; _cfuvid=41MbomhrDEOwWq1PxwZypVaYzoaB.18Jvwv7pcEZwsE-1759436874191-0.0.1.1-604800000; oai-did=7c216860-5b73-4e63-983f-142dbcae1809; _cfuvid=3J0VKjFQbgG6Hrj6WpYA0qUMQ1FEb.Q6LBxmKcpfxyY-1759436879377-0.0.1.1-604800000; __cf_bm=A6amfZkzP5mF6VMM6IWMFCGx76Zc7infb4IIsJl.y_U-1759513275-1.0.1.1-YHpxVFHc7HuRvLnJZUYqV5_cFsTl4IQJHtIx1F82rxnk4DyZiJtZeZpmihYxYc38GmSxyqNcgLG7asQpY_SQCKQbAIzPySauv3AcNxFIuZQ; __cflb=0H28vBjUqcdJN5F5i8D8gyGbfU6KPdHGPmAV43mv94y; __cf_bm=YnxDZlIQqRlaAL_mwiFsbihIehwgCQ5m5hAbB5ywJz8-1759513279-1.0.1.1-xuUkakGqQN4IluRXGntBH6yxiSymBpjBRhUmOe9sIrB5hcn8jcIeRH2UfpGNHr2hbSd27ULgJ7zLcdkVgyMESRN2lqAlvFENbxtyP.Ei1lg; cf_clearance=.kz.DV5lLl_xAI2gP8EgTkpVwdjIftRJZYngwpDXo7M-1759513279-1.2.1.1-lL5ZfXJc137uNnBkoBgZTU8PHVxsdGFjlQz_2.D7W1e5c3oWOUJpDgzItw1tJHuLjUpb_pUV3t.gkwhDC8FJ1YVAOsP5gLJMtd3u88ENeVywIqv5UFevMEqWx9ftykyYOo5YQDeyzpCmHIMc27M3a.dbdpObo7foz3hBQZq59ApKLokhNl9.tC_8E77wVtH1qeHsuAMhH8BjDD2KsP9zmbDi.dHitv5MfE1BPPijC.0; cf_clearance=HRNRwdDQ77cyE4AnpAJrMTTMHyraDA8Z9H5O382kfXg-1759513279-1.2.1.1-8IR07RaXitgzhFxOLUtyQCKZLbD8aM18_7FgQlYE9mknbpQ6K77abiTNg7iub3rlrVavoTrJufW8co3mPgRrljOrzEoZ.qjmGfVYJUGSKKKa7CWGAC15DdMMihMZVilj2jp_eC1sOAuXgbxEaR7QoTMUfcKr_2j8DOnZ6ISdQkD1NdR6xBBhGrSVN3FTAXiJ3GSthnzwFQEIqrMvLTI10wFZQrAGQPfBFfQt9Ir.VkQ; oai-sc=0gAAAAABo4ArA8k5QOFRdnIRvcTY0StzTu1xvrrwcMw-zx_Z8udzogOUxSBc6Bb4NcmMfNmgJ8-IVdajuMyCqYyQw4SYe13w8ev8ydftBOuPp4tlfLJAOM9CtkSlWilKeJxM1eL3qm4QSX0k7CT6BiIDOW0HANclGTmpM471Fk8y43BkuypFNG7c3ScTcSLUMEBqFfayttl4zJleGafscgfvQWo5zqnlJfA; __Secure-next-auth.session-token=eyJhbGciOiJkaXIiLCJlbmMiOiJBMjU2R0NNIn0..Hnw3hRsSeLnw2jF8.Go37dT_kOzeMROBsINCgknMy7dGYt6EXpmgK8jxi6fnlG_YtZzjFuKoY_xLxa51Q6GcWatqCnBnxj23K9MdMnwjKkaXWMm-zzWxGbdKI6cYfITNi0oDW59ieCB23dOAF8oqIhK1Hgrq2pEj4MLsK5h4GlS61SxDmgKEiOfM5VCz9uozD0wH7Pdecfk41szaSSs-vu2glT3wiKHn7f2ST8kl9I6CxcOwe2qYeWCMEKqTH2uERT7cRQ0PXC1qCL-N3N0oAiqiiVme4QKzbrdd9GRrcuyadXDPORmNQ5QSzwlfs4LVbHzHFYwyQdPJlYBJKmH1tmwGPWGt483OBbXuBCKCyvSX99bW7EiKETy2SC1KtOhPYh-BT-LVrgd4Nhiyy8OHJlI3UboW4OveRRdyTuhKmX5_li30uiZXKiPaAuYWXz2xvi8a2XusIF4IZvNzIIpsDidFOCExjqDt70xeEPLbW4ojPyfIxqGyrTqsuiJ9yTBgKemOcnXhm24udGbPpmFCPnXLe8iiZUJLDBVNUq-k7Fj2RbiQ5aRM31a4RcK_fxddyRTQwAInmm4_NXOA_agOtUUhGfTtkiknIYcvhsG2pJVNFmavVJfqP9jvnMI6syIltw_-7MOSChCXOsQsPmMniZOdCF58FWuEI1D7qd0pQeRhiadgV5olErauS25zhV90w4ozOGo5TiYFJo0s6_7RTI-1mIaRPE5m0o0J1t3gIONDasZv1zUu1YnS6iNRa5C01Pcqxj14vekoYz01YUNuHYIVTg0wfAzs1rvosRNW5XCN6ij0M8ugfv3hWZH3Qy5iWaI51cCmnCWgGZolcu0t-qif8xo0MhWlnbASfXNKYEQcDb7bAitTQhY62tCyjvJ4GcoB0yPnu_9jEGlVtQD_Xmmg2wmzZLrNN4nEGF6rIax6sIqA6MI1AYX4TKULd2LD4cC6XceadPOeQKPF6cSiRiV2ANqLlM7VsHCv07hd2-OBGxHSxNAdWeSmnF99Gm3tkyCYwE-Fho1LajXRpaH9MFErQqs_LIawESL7ZqvefLdu4ZfxYKqMBvftPzbIeOF9p4orI0n-jCny28qv1_u_xACR8o9M47Qi3FSrEEkxDlBFcjOa9Xh72y2bzLEtqI4DhJGhlBd3oN9gjv9Wu_--veIA0bDKwKT6E5Vt2KAXCTsfHeOEg9CG8HJVo4O6ZiZPBPCjQB9Z8yuC5ZRTgIyTZlOEkX3PFOofk7I4dAIVSL26PwGqcQIgGmxop6437L_aQnqgHjV01dQcduR0MUqjriQXHisHQxuQHTAYYEoJPKB7aLCO112WicaLRhiacAhVV00g2IePXlpO6DsKhCLS1VddbqrYF7KFvssAYmQCas3P8unFvjoDXQlAHgHKsI1YPzeCtR9ZWhXfKlSD8jhqaaeExltCNaM4aRRjTEs8p-dtFp6_qdniS8sV5m4kDF4KXAgoldjk98dhyosFG5rL45MU-y2tmoIpGcuQ3yLc_TYNLBnI6mnuveosVM4fG-BcrnLPNeNHGWzvJWb6oRPglXvWw400YUXRuYZ4lJS3xfSgrCPkXLmdNLnispXtH4702gQMoBjXJTdZI64HT8pPOJpz1TQLPFWXvs0oVSdcoXiewX3pCnzTHOybPJUMgOh4S4gqfJ3XDq_KK6pGQw4L63BWfWkxmSXstAIjCipqY-e-Epu1CWiqraJX7YvMzRIHalITTKl8JstTGqDjypIByQsju7eS44T5shxX3kkC4KHZhgtQYrOybygdpzhi2fs2rM90wk_ayXq_RTn75tjisJv3MfkwvjDAfixnYpsvFObxm4Qtt1a6GXs6ZCKaeTPkhJwQduBIxSm3aaoyMy3eMgHOpabG6ifdGNACGXgD47MOr5rzpRiSgRR6AyLl0W154tp26A0UojlQeh4qBn9Q9-8I3_z0-JwqCedWr9KbM2fKA-j7_Js12PDSDpojaSDuuq6qcAuOZEPpPCEsAugS8CGsoFJ_GGhIUA5BfMa-aaQj17RmHDKK9DYqC1mM-YLiPn0gJhdZnpQZd102W6Wtt7opJ9Y3R4UiUsEtYT75dhYEVyXXCj-f3Mgc6w5RZI6tzTntFgpmfGXqAxeiQhLzY26xwE9-L0TZ5PxJDpCkMwOAFzwvRelUQwuxcPaZWeWlWwT7oquhAF7SMo87tLV-FJwb1xwLy-gfiDq88-OEtqtkZ6d4PnDH7z4wGwWuXsjGUhaB3sBAmBVM55n_li42ikao3u1ZWyNhEiKht2y1hJzH9Tnt-zLMnuz8BWVu_D_qfwRkvI1MtiiX5zD1_0FQdOHaWsVfIjigTsxNumzAo6qh-tth3DG0kzV0tk2tSxRXiw0dK1PkyZoZeBUgER5CmFpYj1tr6QjvEb5kt8g_Y1MV2qRfMjSkfIWc9aqeyoVI-a8HJycCsspV4wDyhGlGjtWn6_sApSa82FsrDOVZ13u9FNHnEWflB9dw_aajPzgkPF_UeIS3s-p72orQoYVohmjaIZ_AAfFv-zPY8jR06se1vo-z52aDS7-2a3taMwd_XVLY9jGijCZLGrZW-zrznbi8ZFmypMBp8_goZkxJEcam6vIHoFNgYeWh27MLskxQ204Q27w-HjxCga1f1i69EoJpSivl7oLiX5ZVyUtI6DahP1tEC3d_pdYYAH3t8iLTVvan0HqQwzJyAMPbVEikXxr_xFOYut1kdY6jlqUwL7yHuHMGeQNnJ8Dw94CuMS_D8uF1FsOvABXX4hddhi16vq_AFVmyK3r4M0sTYfsnIJ2jAKhPwrOfA37fxVoX5fhhRNhC5P2fGT0YQGGyLK9qh5lLxP85zNHGNNHefeOapvjqdfmRnT-SM31Ot6V6M5TO-rbdJE5YS3sKxmszmhz-0aPzujWXmgAYXpcyj2W37cBJR6cJkCzgZTK5agjVZM3tbyiK6MXaym8-Iyqi7trqHYSouGmB6PqFe3dGkLXJz5B6bhv6H12UsWoZhLJpp7ZYdIP8S45mfyzSx9ei1YCy2JeP1hZR9c2Rv6DR0tIzWlh5rktuCAIIQgSLUyfttknoJtjjDU-h1N1452hpYon_fNN_K_pZgF_qaizFqR5B2_aS_Ic4INJcajP1gDsClTrKVMQBRvVjyyrhic2olineuXYRfL041bS8PfUFI-Ybdq94eeS-Ccfpfi3OmWCMD1eI8QHa6K7_rKfTLine truncated
-H 'oai-device-id: 7c216860-5b73-4e63-983f-142dbcae1809' \
-H 'origin: https://sora.chatgpt.com' \
-H 'priority: u=1, i' \
@@ -0,0 +1,50 @@
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<String>,
// 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>,
}
@@ -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".
}
@@ -1 +1,3 @@
mod generate_sora2_video;
mod generate_sora2_video;
mod http_request;
mod http_response;
@@ -2,8 +2,8 @@ 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 list_sora2_tasks;
pub mod list_tasks;
pub mod upload;
mod generate_sora2_video;
mod list_sora2_tasks;
pub mod upload;