mirror of
https://github.com/storytold/artcraft.git
synced 2026-10-20 17:02:53 -04:00
Compare commits
4
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
22e59eb683 | ||
|
|
038e1c1895 | ||
|
|
0daccd4888 | ||
|
|
1fa26a92e8 |
Generated
+1
@@ -4185,6 +4185,7 @@ dependencies = [
|
||||
"log",
|
||||
"reqwest 0.12.23",
|
||||
"serde",
|
||||
"strum 0.28.0",
|
||||
"test_data",
|
||||
"testing",
|
||||
"tokio",
|
||||
|
||||
@@ -25,6 +25,7 @@ futures.workspace = true
|
||||
log.workspace = true
|
||||
reqwest.workspace = true
|
||||
serde.workspace = true
|
||||
strum.workspace = true
|
||||
tokio.workspace = true
|
||||
url.workspace = true
|
||||
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
pub mod image;
|
||||
pub mod video;
|
||||
|
||||
+12
-13
@@ -1,44 +1,43 @@
|
||||
use crate::prelude::{Deserialize, FalRequest, Serialize};
|
||||
use fal::request::FalRequest;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Default)]
|
||||
pub struct Sora2ImageToVideoInput {
|
||||
pub prompt: String,
|
||||
|
||||
/// Starting frame
|
||||
/// Starting frame image URL.
|
||||
pub image_url: String,
|
||||
|
||||
/// Possible enum values: auto, 720p
|
||||
/// Default value auto
|
||||
/// Default value: "auto"
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub resolution: Option<String>,
|
||||
|
||||
/// Possible enum values: auto, 9:16, 16:9
|
||||
/// Default value "auto"
|
||||
/// Default value: "auto"
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub aspect_ratio: Option<String>,
|
||||
|
||||
/// Duration in seconds
|
||||
/// Duration in seconds.
|
||||
/// Possible enum values: 4, 8, 12
|
||||
/// Default value 4
|
||||
/// Default value: 4
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub duration: Option<u8>,
|
||||
|
||||
/// Whether to delete the video after generation for privacy reasons.
|
||||
/// If True, the video cannot be used for remixing and will be permanently deleted.
|
||||
/// Default value: true
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub delete_video: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct Sora2ImageToVideoOutput {
|
||||
pub video: VideoFile,
|
||||
pub struct Sora2ImageToVideoVideoFile {
|
||||
pub url: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Default)]
|
||||
pub struct VideoFile {
|
||||
/// The URL where the file can be downloaded from.
|
||||
pub url: String,
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct Sora2ImageToVideoOutput {
|
||||
pub video: Sora2ImageToVideoVideoFile,
|
||||
}
|
||||
|
||||
pub fn sora_2_image_to_video(
|
||||
+12
-13
@@ -1,44 +1,43 @@
|
||||
use crate::prelude::{Deserialize, FalRequest, Serialize};
|
||||
use fal::request::FalRequest;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Default)]
|
||||
pub struct Sora2ProImageToVideoInput {
|
||||
pub prompt: String,
|
||||
|
||||
/// Starting frame
|
||||
/// Starting frame image URL.
|
||||
pub image_url: String,
|
||||
|
||||
/// Possible enum values: auto, 720p, 1080p
|
||||
/// Default value auto
|
||||
/// Default value: "auto"
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub resolution: Option<String>,
|
||||
|
||||
/// Possible enum values: auto, 9:16, 16:9
|
||||
/// Default value "auto"
|
||||
/// Default value: "auto"
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub aspect_ratio: Option<String>,
|
||||
|
||||
/// Duration in seconds
|
||||
/// Duration in seconds.
|
||||
/// Possible enum values: 4, 8, 12
|
||||
/// Default value 4
|
||||
/// Default value: 4
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub duration: Option<u8>,
|
||||
|
||||
/// Whether to delete the video after generation for privacy reasons.
|
||||
/// If True, the video cannot be used for remixing and will be permanently deleted.
|
||||
/// Default value: true
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub delete_video: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct Sora2ProImageToVideoOutput {
|
||||
pub video: VideoFile,
|
||||
pub struct Sora2ProImageToVideoVideoFile {
|
||||
pub url: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Default)]
|
||||
pub struct VideoFile {
|
||||
/// The URL where the file can be downloaded from.
|
||||
pub url: String,
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct Sora2ProImageToVideoOutput {
|
||||
pub video: Sora2ProImageToVideoVideoFile,
|
||||
}
|
||||
|
||||
pub fn sora_2_pro_image_to_video(
|
||||
+13
-14
@@ -1,41 +1,40 @@
|
||||
use crate::prelude::{Deserialize, FalRequest, Serialize};
|
||||
use fal::request::FalRequest;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Default)]
|
||||
pub struct Sora2ProTextToVideoInput {
|
||||
pub prompt: String,
|
||||
|
||||
/// Possible enum values: auto, 720p, 1080p
|
||||
/// Default value auto
|
||||
/// Possible enum values: 720p, 1080p
|
||||
/// Default value: "1080p"
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub resolution: Option<String>,
|
||||
|
||||
/// Possible enum values: auto, 9:16, 16:9
|
||||
/// Default value "auto"
|
||||
/// Possible enum values: 9:16, 16:9
|
||||
/// Default value: "16:9"
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub aspect_ratio: Option<String>,
|
||||
|
||||
/// Duration in seconds
|
||||
/// Duration in seconds.
|
||||
/// Possible enum values: 4, 8, 12
|
||||
/// Default value 4
|
||||
/// Default value: 4
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub duration: Option<u8>,
|
||||
|
||||
/// Whether to delete the video after generation for privacy reasons.
|
||||
/// If True, the video cannot be used for remixing and will be permanently deleted.
|
||||
/// Default value: true
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub delete_video: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct Sora2ProTextToVideoOutput {
|
||||
pub video: VideoFile,
|
||||
pub struct Sora2ProTextToVideoVideoFile {
|
||||
pub url: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Default)]
|
||||
pub struct VideoFile {
|
||||
/// The URL where the file can be downloaded from.
|
||||
pub url: String,
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct Sora2ProTextToVideoOutput {
|
||||
pub video: Sora2ProTextToVideoVideoFile,
|
||||
}
|
||||
|
||||
pub fn sora_2_pro_text_to_video(
|
||||
+12
-13
@@ -1,41 +1,40 @@
|
||||
use crate::prelude::{Deserialize, FalRequest, Serialize};
|
||||
use fal::request::FalRequest;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Default)]
|
||||
pub struct Sora2TextToVideoInput {
|
||||
pub prompt: String,
|
||||
|
||||
/// Possible enum values: 720p
|
||||
/// Default value 720p
|
||||
/// Default value: "720p"
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub resolution: Option<String>,
|
||||
|
||||
/// Possible enum values: auto, 9:16, 16:9
|
||||
/// Default value "auto"
|
||||
/// Possible enum values: 9:16, 16:9
|
||||
/// Default value: "16:9"
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub aspect_ratio: Option<String>,
|
||||
|
||||
/// Duration in seconds
|
||||
/// Duration in seconds.
|
||||
/// Possible enum values: 4, 8, 12
|
||||
/// Default value 4
|
||||
/// Default value: 4
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub duration: Option<u8>,
|
||||
|
||||
/// Whether to delete the video after generation for privacy reasons.
|
||||
/// If True, the video cannot be used for remixing and will be permanently deleted.
|
||||
/// Default value: true
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub delete_video: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct Sora2TextToVideoOutput {
|
||||
pub video: VideoFile,
|
||||
pub struct Sora2TextToVideoVideoFile {
|
||||
pub url: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Default)]
|
||||
pub struct VideoFile {
|
||||
/// The URL where the file can be downloaded from.
|
||||
pub url: String,
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct Sora2TextToVideoOutput {
|
||||
pub video: Sora2TextToVideoVideoFile,
|
||||
}
|
||||
|
||||
pub fn sora_2_text_to_video(
|
||||
@@ -0,0 +1,4 @@
|
||||
pub mod http_sora_2_text_to_video;
|
||||
pub mod http_sora_2_image_to_video;
|
||||
pub mod http_sora_2_pro_text_to_video;
|
||||
pub mod http_sora_2_pro_image_to_video;
|
||||
+88
-21
@@ -1,8 +1,8 @@
|
||||
use crate::creds::fal_api_key::FalApiKey;
|
||||
use crate::error::classify_fal_error::classify_fal_error;
|
||||
use crate::error::fal_error_plus::FalErrorPlus;
|
||||
use crate::requests::http::video::http_sora_2_image_to_video::{sora_2_image_to_video, Sora2ImageToVideoInput};
|
||||
use crate::requests::traits::fal_request_cost_calculator_trait::{FalRequestCostCalculator, UsdCents};
|
||||
use fal::endpoints::fal_ai::sora::sora2::sora_2_image_to_video::{sora_2_image_to_video, Sora2ImageToVideoInput};
|
||||
use fal::webhook::WebhookResponse;
|
||||
use reqwest::IntoUrl;
|
||||
|
||||
@@ -12,10 +12,7 @@ pub struct EnqueueSora2ImageToVideoArgs<'a, R: IntoUrl> {
|
||||
pub image_url: String,
|
||||
|
||||
// Optional args
|
||||
|
||||
// NB: Duration defaults to four seconds
|
||||
pub duration: Option<EnqueueSora2ImageToVideoDurationSeconds>,
|
||||
|
||||
pub resolution: Option<EnqueueSora2ImageToVideoResolution>,
|
||||
pub aspect_ratio: Option<EnqueueSora2ImageToVideoAspectRatio>,
|
||||
|
||||
@@ -24,20 +21,20 @@ pub struct EnqueueSora2ImageToVideoArgs<'a, R: IntoUrl> {
|
||||
pub api_key: &'a FalApiKey,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
#[derive(Copy, Clone, Debug, strum::EnumIter)]
|
||||
pub enum EnqueueSora2ImageToVideoDurationSeconds {
|
||||
Four,
|
||||
Eight,
|
||||
Twelve,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
#[derive(Copy, Clone, Debug, strum::EnumIter)]
|
||||
pub enum EnqueueSora2ImageToVideoResolution {
|
||||
Auto,
|
||||
SevenTwentyP,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
#[derive(Copy, Clone, Debug, strum::EnumIter)]
|
||||
pub enum EnqueueSora2ImageToVideoAspectRatio {
|
||||
Auto,
|
||||
NineBySixteen,
|
||||
@@ -49,8 +46,8 @@ impl <U: IntoUrl> FalRequestCostCalculator for EnqueueSora2ImageToVideoArgs<'_,
|
||||
// "The pricing is $0.1/s for Sora 2."
|
||||
let duration = self.duration.unwrap_or(EnqueueSora2ImageToVideoDurationSeconds::Four);
|
||||
match duration {
|
||||
EnqueueSora2ImageToVideoDurationSeconds::Four => 40, // $0.10 * 4
|
||||
EnqueueSora2ImageToVideoDurationSeconds::Eight => 80, // $0.10 * 8
|
||||
EnqueueSora2ImageToVideoDurationSeconds::Four => 40, // $0.10 * 4
|
||||
EnqueueSora2ImageToVideoDurationSeconds::Eight => 80, // $0.10 * 8
|
||||
EnqueueSora2ImageToVideoDurationSeconds::Twelve => 120 // $0.10 * 12
|
||||
}
|
||||
}
|
||||
@@ -63,35 +60,33 @@ pub async fn enqueue_sora_2_image_to_video_webhook<R: IntoUrl>(
|
||||
) -> Result<WebhookResponse, FalErrorPlus> {
|
||||
|
||||
let duration = args.duration
|
||||
.map(|resolution| match resolution {
|
||||
.map(|d| match d {
|
||||
EnqueueSora2ImageToVideoDurationSeconds::Four => 4,
|
||||
EnqueueSora2ImageToVideoDurationSeconds::Eight => 8,
|
||||
EnqueueSora2ImageToVideoDurationSeconds::Twelve => 12,
|
||||
});
|
||||
|
||||
let resolution = args.resolution
|
||||
.map(|resolution| match resolution {
|
||||
.map(|r| match r {
|
||||
EnqueueSora2ImageToVideoResolution::Auto => "auto",
|
||||
EnqueueSora2ImageToVideoResolution::SevenTwentyP => "720p",
|
||||
})
|
||||
.map(|resolution| resolution.to_string());
|
||||
.map(|r| r.to_string());
|
||||
|
||||
let aspect_ratio = args.aspect_ratio
|
||||
.map(|aspect_ratio| match aspect_ratio {
|
||||
.map(|ar| match ar {
|
||||
EnqueueSora2ImageToVideoAspectRatio::Auto => "auto",
|
||||
EnqueueSora2ImageToVideoAspectRatio::NineBySixteen => "9:16",
|
||||
EnqueueSora2ImageToVideoAspectRatio::SixteenByNine => "16:9",
|
||||
})
|
||||
.map(|resolution| resolution.to_string());
|
||||
.map(|ar| ar.to_string());
|
||||
|
||||
let request = Sora2ImageToVideoInput {
|
||||
prompt: args.prompt,
|
||||
image_url: args.image_url,
|
||||
// Optionals
|
||||
duration,
|
||||
resolution,
|
||||
aspect_ratio,
|
||||
// Constants
|
||||
delete_video: Some(false),
|
||||
};
|
||||
|
||||
@@ -105,18 +100,17 @@ pub async fn enqueue_sora_2_image_to_video_webhook<R: IntoUrl>(
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::creds::fal_api_key::FalApiKey;
|
||||
use crate::requests::webhook::video::image::enqueue_sora_2_image_to_video_webhook::{enqueue_sora_2_image_to_video_webhook, EnqueueSora2ImageToVideoArgs, EnqueueSora2ImageToVideoAspectRatio, EnqueueSora2ImageToVideoDurationSeconds, EnqueueSora2ImageToVideoResolution};
|
||||
use errors::AnyhowResult;
|
||||
use std::fs::read_to_string;
|
||||
use strum::IntoEnumIterator;
|
||||
use test_data::web::image_urls::TREX_SKELETON_IMAGE_URL;
|
||||
|
||||
#[tokio::test]
|
||||
#[ignore]
|
||||
#[ignore] // manually run — fires a real API request and incurs cost
|
||||
async fn test() -> AnyhowResult<()> {
|
||||
// XXX: Don't commit secrets!
|
||||
let secret = read_to_string("/home/bt/Artcraft/credentials/fal_api_key.txt")?;
|
||||
|
||||
let secret = read_to_string("/Users/bt/Artcraft/credentials/fal_api_key.txt")?;
|
||||
let api_key = FalApiKey::from_str(&secret);
|
||||
|
||||
let args = EnqueueSora2ImageToVideoArgs {
|
||||
@@ -130,6 +124,79 @@ mod tests {
|
||||
};
|
||||
|
||||
let result = enqueue_sora_2_image_to_video_webhook(args).await?;
|
||||
println!("result: {:?}", result);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[ignore] // manually run — fires a real API request per variant (expensive)
|
||||
async fn test_all_aspect_ratios() -> AnyhowResult<()> {
|
||||
let secret = read_to_string("/Users/bt/Artcraft/credentials/fal_api_key.txt")?;
|
||||
let api_key = FalApiKey::from_str(&secret);
|
||||
|
||||
for ar in EnqueueSora2ImageToVideoAspectRatio::iter() {
|
||||
println!("--- aspect ratio: {:?} ---", ar);
|
||||
let args = EnqueueSora2ImageToVideoArgs {
|
||||
image_url: TREX_SKELETON_IMAGE_URL.to_string(),
|
||||
prompt: "the skeleton comes alive and roars at the camera".to_string(),
|
||||
duration: Some(EnqueueSora2ImageToVideoDurationSeconds::Four),
|
||||
aspect_ratio: Some(ar),
|
||||
resolution: None,
|
||||
api_key: &api_key,
|
||||
webhook_url: "https://example.com/webhook",
|
||||
};
|
||||
let result = enqueue_sora_2_image_to_video_webhook(args).await?;
|
||||
println!("result: {:?}", result);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[ignore] // manually run — fires a real API request per variant (expensive)
|
||||
async fn test_all_durations() -> AnyhowResult<()> {
|
||||
let secret = read_to_string("/Users/bt/Artcraft/credentials/fal_api_key.txt")?;
|
||||
let api_key = FalApiKey::from_str(&secret);
|
||||
|
||||
for dur in EnqueueSora2ImageToVideoDurationSeconds::iter() {
|
||||
println!("--- duration: {:?} ---", dur);
|
||||
let args = EnqueueSora2ImageToVideoArgs {
|
||||
image_url: TREX_SKELETON_IMAGE_URL.to_string(),
|
||||
prompt: "the skeleton slowly turns its head".to_string(),
|
||||
duration: Some(dur),
|
||||
aspect_ratio: Some(EnqueueSora2ImageToVideoAspectRatio::SixteenByNine),
|
||||
resolution: None,
|
||||
api_key: &api_key,
|
||||
webhook_url: "https://example.com/webhook",
|
||||
};
|
||||
let result = enqueue_sora_2_image_to_video_webhook(args).await?;
|
||||
println!("result: {:?}", result);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[ignore] // manually run — fires a real API request per variant (expensive)
|
||||
async fn test_all_resolutions() -> AnyhowResult<()> {
|
||||
let secret = read_to_string("/Users/bt/Artcraft/credentials/fal_api_key.txt")?;
|
||||
let api_key = FalApiKey::from_str(&secret);
|
||||
|
||||
for res in EnqueueSora2ImageToVideoResolution::iter() {
|
||||
println!("--- resolution: {:?} ---", res);
|
||||
let args = EnqueueSora2ImageToVideoArgs {
|
||||
image_url: TREX_SKELETON_IMAGE_URL.to_string(),
|
||||
prompt: "the skeleton slowly comes alive".to_string(),
|
||||
duration: Some(EnqueueSora2ImageToVideoDurationSeconds::Four),
|
||||
aspect_ratio: Some(EnqueueSora2ImageToVideoAspectRatio::SixteenByNine),
|
||||
resolution: Some(res),
|
||||
api_key: &api_key,
|
||||
webhook_url: "https://example.com/webhook",
|
||||
};
|
||||
let result = enqueue_sora_2_image_to_video_webhook(args).await?;
|
||||
println!("result: {:?}", result);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
+91
-26
@@ -1,8 +1,8 @@
|
||||
use crate::creds::fal_api_key::FalApiKey;
|
||||
use crate::error::classify_fal_error::classify_fal_error;
|
||||
use crate::error::fal_error_plus::FalErrorPlus;
|
||||
use crate::requests::http::video::http_sora_2_pro_image_to_video::{sora_2_pro_image_to_video, Sora2ProImageToVideoInput};
|
||||
use crate::requests::traits::fal_request_cost_calculator_trait::{FalRequestCostCalculator, UsdCents};
|
||||
use fal::endpoints::fal_ai::sora::sora2::sora_2_pro_image_to_video::{sora_2_pro_image_to_video, Sora2ProImageToVideoInput};
|
||||
use fal::webhook::WebhookResponse;
|
||||
use reqwest::IntoUrl;
|
||||
|
||||
@@ -21,21 +21,21 @@ pub struct EnqueueSora2ProImageToVideoArgs<'a, R: IntoUrl> {
|
||||
pub api_key: &'a FalApiKey,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
#[derive(Copy, Clone, Debug, strum::EnumIter)]
|
||||
pub enum EnqueueSora2ProImageToVideoDurationSeconds {
|
||||
Four,
|
||||
Eight,
|
||||
Twelve,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
#[derive(Copy, Clone, Debug, strum::EnumIter)]
|
||||
pub enum EnqueueSora2ProImageToVideoResolution {
|
||||
Auto,
|
||||
SevenTwentyP,
|
||||
TenEightyP,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
#[derive(Copy, Clone, Debug, strum::EnumIter)]
|
||||
pub enum EnqueueSora2ProImageToVideoAspectRatio {
|
||||
Auto,
|
||||
NineBySixteen,
|
||||
@@ -44,29 +44,24 @@ pub enum EnqueueSora2ProImageToVideoAspectRatio {
|
||||
|
||||
impl <U: IntoUrl> FalRequestCostCalculator for EnqueueSora2ProImageToVideoArgs<'_, U> {
|
||||
fn calculate_cost_in_cents(&self) -> UsdCents {
|
||||
// "If an OpenAI API key is provided, it will be directly charged by OpenAI.
|
||||
// Otherwise, you will be charged fal credits.
|
||||
// The pricing is $0.30/s for 720p and $0.50/s for 1080p."
|
||||
// NB(bt): There's no way to provide an API key with this endpoint?
|
||||
|
||||
// Pricing: $0.30/s for 720p and $0.50/s for 1080p.
|
||||
let duration = self.duration.unwrap_or(EnqueueSora2ProImageToVideoDurationSeconds::Four);
|
||||
let resolution = self.resolution.unwrap_or(EnqueueSora2ProImageToVideoResolution::Auto); // WHAT IS THIS?!
|
||||
let resolution = self.resolution.unwrap_or(EnqueueSora2ProImageToVideoResolution::Auto);
|
||||
|
||||
match (duration, resolution) {
|
||||
(EnqueueSora2ProImageToVideoDurationSeconds::Four, EnqueueSora2ProImageToVideoResolution::Auto) => 120, // TODO: Auto = ???
|
||||
(EnqueueSora2ProImageToVideoDurationSeconds::Four, EnqueueSora2ProImageToVideoResolution::Auto) => 120,
|
||||
(EnqueueSora2ProImageToVideoDurationSeconds::Four, EnqueueSora2ProImageToVideoResolution::SevenTwentyP) => 120,
|
||||
(EnqueueSora2ProImageToVideoDurationSeconds::Four, EnqueueSora2ProImageToVideoResolution::TenEightyP) => 200,
|
||||
(EnqueueSora2ProImageToVideoDurationSeconds::Eight, EnqueueSora2ProImageToVideoResolution::Auto) => 240, // TODO: Auto = ???
|
||||
(EnqueueSora2ProImageToVideoDurationSeconds::Eight, EnqueueSora2ProImageToVideoResolution::Auto) => 240,
|
||||
(EnqueueSora2ProImageToVideoDurationSeconds::Eight, EnqueueSora2ProImageToVideoResolution::SevenTwentyP) => 240,
|
||||
(EnqueueSora2ProImageToVideoDurationSeconds::Eight, EnqueueSora2ProImageToVideoResolution::TenEightyP) => 400,
|
||||
(EnqueueSora2ProImageToVideoDurationSeconds::Twelve, EnqueueSora2ProImageToVideoResolution::Auto) => 360, // TODO: Auto = ???
|
||||
(EnqueueSora2ProImageToVideoDurationSeconds::Twelve, EnqueueSora2ProImageToVideoResolution::Auto) => 360,
|
||||
(EnqueueSora2ProImageToVideoDurationSeconds::Twelve, EnqueueSora2ProImageToVideoResolution::SevenTwentyP) => 360,
|
||||
(EnqueueSora2ProImageToVideoDurationSeconds::Twelve, EnqueueSora2ProImageToVideoResolution::TenEightyP) => 600,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// Sora 2 Pro Image-to-Video
|
||||
/// https://fal.ai/models/fal-ai/sora-2/image-to-video/pro
|
||||
pub async fn enqueue_sora_2_pro_image_to_video_webhook<R: IntoUrl>(
|
||||
@@ -74,36 +69,34 @@ pub async fn enqueue_sora_2_pro_image_to_video_webhook<R: IntoUrl>(
|
||||
) -> Result<WebhookResponse, FalErrorPlus> {
|
||||
|
||||
let duration = args.duration
|
||||
.map(|resolution| match resolution {
|
||||
.map(|d| match d {
|
||||
EnqueueSora2ProImageToVideoDurationSeconds::Four => 4,
|
||||
EnqueueSora2ProImageToVideoDurationSeconds::Eight => 8,
|
||||
EnqueueSora2ProImageToVideoDurationSeconds::Twelve => 12,
|
||||
});
|
||||
|
||||
let resolution = args.resolution
|
||||
.map(|resolution| match resolution {
|
||||
.map(|r| match r {
|
||||
EnqueueSora2ProImageToVideoResolution::Auto => "auto",
|
||||
EnqueueSora2ProImageToVideoResolution::SevenTwentyP => "720p",
|
||||
EnqueueSora2ProImageToVideoResolution::TenEightyP => "1080p",
|
||||
})
|
||||
.map(|resolution| resolution.to_string());
|
||||
.map(|r| r.to_string());
|
||||
|
||||
let aspect_ratio = args.aspect_ratio
|
||||
.map(|aspect_ratio| match aspect_ratio {
|
||||
.map(|ar| match ar {
|
||||
EnqueueSora2ProImageToVideoAspectRatio::Auto => "auto",
|
||||
EnqueueSora2ProImageToVideoAspectRatio::NineBySixteen => "9:16",
|
||||
EnqueueSora2ProImageToVideoAspectRatio::SixteenByNine => "16:9",
|
||||
})
|
||||
.map(|resolution| resolution.to_string());
|
||||
.map(|ar| ar.to_string());
|
||||
|
||||
let request = Sora2ProImageToVideoInput {
|
||||
prompt: args.prompt,
|
||||
image_url: args.image_url,
|
||||
// Optionals
|
||||
duration,
|
||||
resolution,
|
||||
aspect_ratio,
|
||||
// Constants
|
||||
delete_video: Some(false),
|
||||
};
|
||||
|
||||
@@ -117,18 +110,17 @@ pub async fn enqueue_sora_2_pro_image_to_video_webhook<R: IntoUrl>(
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::creds::fal_api_key::FalApiKey;
|
||||
use crate::requests::webhook::video::image::enqueue_sora_2_pro_image_to_video_webhook::{enqueue_sora_2_pro_image_to_video_webhook, EnqueueSora2ProImageToVideoArgs, EnqueueSora2ProImageToVideoAspectRatio, EnqueueSora2ProImageToVideoDurationSeconds, EnqueueSora2ProImageToVideoResolution};
|
||||
use errors::AnyhowResult;
|
||||
use std::fs::read_to_string;
|
||||
use strum::IntoEnumIterator;
|
||||
use test_data::web::image_urls::TREX_SKELETON_IMAGE_URL;
|
||||
|
||||
#[tokio::test]
|
||||
#[ignore]
|
||||
#[ignore] // manually run — fires a real API request and incurs cost
|
||||
async fn test() -> AnyhowResult<()> {
|
||||
// XXX: Don't commit secrets!
|
||||
let secret = read_to_string("/home/bt/Artcraft/credentials/fal_api_key.txt")?;
|
||||
|
||||
let secret = read_to_string("/Users/bt/Artcraft/credentials/fal_api_key.txt")?;
|
||||
let api_key = FalApiKey::from_str(&secret);
|
||||
|
||||
let args = EnqueueSora2ProImageToVideoArgs {
|
||||
@@ -142,6 +134,79 @@ mod tests {
|
||||
};
|
||||
|
||||
let result = enqueue_sora_2_pro_image_to_video_webhook(args).await?;
|
||||
println!("result: {:?}", result);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[ignore] // manually run — fires a real API request per variant (expensive)
|
||||
async fn test_all_aspect_ratios() -> AnyhowResult<()> {
|
||||
let secret = read_to_string("/Users/bt/Artcraft/credentials/fal_api_key.txt")?;
|
||||
let api_key = FalApiKey::from_str(&secret);
|
||||
|
||||
for ar in EnqueueSora2ProImageToVideoAspectRatio::iter() {
|
||||
println!("--- aspect ratio: {:?} ---", ar);
|
||||
let args = EnqueueSora2ProImageToVideoArgs {
|
||||
image_url: TREX_SKELETON_IMAGE_URL.to_string(),
|
||||
prompt: "the skeleton comes alive and roars at the camera".to_string(),
|
||||
duration: Some(EnqueueSora2ProImageToVideoDurationSeconds::Four),
|
||||
aspect_ratio: Some(ar),
|
||||
resolution: None,
|
||||
api_key: &api_key,
|
||||
webhook_url: "https://example.com/webhook",
|
||||
};
|
||||
let result = enqueue_sora_2_pro_image_to_video_webhook(args).await?;
|
||||
println!("result: {:?}", result);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[ignore] // manually run — fires a real API request per variant (expensive)
|
||||
async fn test_all_durations() -> AnyhowResult<()> {
|
||||
let secret = read_to_string("/Users/bt/Artcraft/credentials/fal_api_key.txt")?;
|
||||
let api_key = FalApiKey::from_str(&secret);
|
||||
|
||||
for dur in EnqueueSora2ProImageToVideoDurationSeconds::iter() {
|
||||
println!("--- duration: {:?} ---", dur);
|
||||
let args = EnqueueSora2ProImageToVideoArgs {
|
||||
image_url: TREX_SKELETON_IMAGE_URL.to_string(),
|
||||
prompt: "the skeleton slowly turns its head".to_string(),
|
||||
duration: Some(dur),
|
||||
aspect_ratio: Some(EnqueueSora2ProImageToVideoAspectRatio::SixteenByNine),
|
||||
resolution: None,
|
||||
api_key: &api_key,
|
||||
webhook_url: "https://example.com/webhook",
|
||||
};
|
||||
let result = enqueue_sora_2_pro_image_to_video_webhook(args).await?;
|
||||
println!("result: {:?}", result);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[ignore] // manually run — fires a real API request per variant (expensive)
|
||||
async fn test_all_resolutions() -> AnyhowResult<()> {
|
||||
let secret = read_to_string("/Users/bt/Artcraft/credentials/fal_api_key.txt")?;
|
||||
let api_key = FalApiKey::from_str(&secret);
|
||||
|
||||
for res in EnqueueSora2ProImageToVideoResolution::iter() {
|
||||
println!("--- resolution: {:?} ---", res);
|
||||
let args = EnqueueSora2ProImageToVideoArgs {
|
||||
image_url: TREX_SKELETON_IMAGE_URL.to_string(),
|
||||
prompt: "the skeleton slowly comes alive".to_string(),
|
||||
duration: Some(EnqueueSora2ProImageToVideoDurationSeconds::Four),
|
||||
aspect_ratio: Some(EnqueueSora2ProImageToVideoAspectRatio::SixteenByNine),
|
||||
resolution: Some(res),
|
||||
api_key: &api_key,
|
||||
webhook_url: "https://example.com/webhook",
|
||||
};
|
||||
let result = enqueue_sora_2_pro_image_to_video_webhook(args).await?;
|
||||
println!("result: {:?}", result);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
+85
-30
@@ -1,8 +1,8 @@
|
||||
use crate::creds::fal_api_key::FalApiKey;
|
||||
use crate::error::classify_fal_error::classify_fal_error;
|
||||
use crate::error::fal_error_plus::FalErrorPlus;
|
||||
use crate::requests::http::video::http_sora_2_pro_text_to_video::{sora_2_pro_text_to_video, Sora2ProTextToVideoInput};
|
||||
use crate::requests::traits::fal_request_cost_calculator_trait::{FalRequestCostCalculator, UsdCents};
|
||||
use fal::endpoints::fal_ai::sora::sora2::sora_2_pro_text_to_video::{sora_2_pro_text_to_video, Sora2ProTextToVideoInput};
|
||||
use fal::webhook::WebhookResponse;
|
||||
use reqwest::IntoUrl;
|
||||
|
||||
@@ -20,23 +20,21 @@ pub struct EnqueueSora2ProTextToVideoArgs<'a, R: IntoUrl> {
|
||||
pub api_key: &'a FalApiKey,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
#[derive(Copy, Clone, Debug, strum::EnumIter)]
|
||||
pub enum EnqueueSora2ProTextToVideoDurationSeconds {
|
||||
Four,
|
||||
Eight,
|
||||
Twelve,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
#[derive(Copy, Clone, Debug, strum::EnumIter)]
|
||||
pub enum EnqueueSora2ProTextToVideoResolution {
|
||||
Auto,
|
||||
SevenTwentyP,
|
||||
TenEightyP,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
#[derive(Copy, Clone, Debug, strum::EnumIter)]
|
||||
pub enum EnqueueSora2ProTextToVideoAspectRatio {
|
||||
Auto,
|
||||
NineBySixteen,
|
||||
SixteenByNine,
|
||||
}
|
||||
@@ -44,29 +42,21 @@ pub enum EnqueueSora2ProTextToVideoAspectRatio {
|
||||
|
||||
impl <U: IntoUrl> FalRequestCostCalculator for EnqueueSora2ProTextToVideoArgs<'_, U> {
|
||||
fn calculate_cost_in_cents(&self) -> UsdCents {
|
||||
// "If an OpenAI API key is provided, it will be directly charged by OpenAI.
|
||||
// Otherwise, you will be charged fal credits.
|
||||
// The pricing is $0.30/s for 720p and $0.50/s for 1080p."
|
||||
// NB(bt): There's no way to provide an API key with this endpoint?
|
||||
|
||||
// Pricing: $0.30/s for 720p and $0.50/s for 1080p.
|
||||
let duration = self.duration.unwrap_or(EnqueueSora2ProTextToVideoDurationSeconds::Four);
|
||||
let resolution = self.resolution.unwrap_or(EnqueueSora2ProTextToVideoResolution::Auto); // WHAT IS THIS?!
|
||||
let resolution = self.resolution.unwrap_or(EnqueueSora2ProTextToVideoResolution::TenEightyP);
|
||||
|
||||
match (duration, resolution) {
|
||||
(EnqueueSora2ProTextToVideoDurationSeconds::Four, EnqueueSora2ProTextToVideoResolution::Auto) => 120, // TODO: Auto = ???
|
||||
(EnqueueSora2ProTextToVideoDurationSeconds::Four, EnqueueSora2ProTextToVideoResolution::SevenTwentyP) => 120,
|
||||
(EnqueueSora2ProTextToVideoDurationSeconds::Four, EnqueueSora2ProTextToVideoResolution::TenEightyP) => 200,
|
||||
(EnqueueSora2ProTextToVideoDurationSeconds::Eight, EnqueueSora2ProTextToVideoResolution::Auto) => 240, // TODO: Auto = ???
|
||||
(EnqueueSora2ProTextToVideoDurationSeconds::Eight, EnqueueSora2ProTextToVideoResolution::SevenTwentyP) => 240,
|
||||
(EnqueueSora2ProTextToVideoDurationSeconds::Eight, EnqueueSora2ProTextToVideoResolution::TenEightyP) => 400,
|
||||
(EnqueueSora2ProTextToVideoDurationSeconds::Twelve, EnqueueSora2ProTextToVideoResolution::Auto) => 360, // TODO: Auto = ???
|
||||
(EnqueueSora2ProTextToVideoDurationSeconds::Twelve, EnqueueSora2ProTextToVideoResolution::SevenTwentyP) => 360,
|
||||
(EnqueueSora2ProTextToVideoDurationSeconds::Twelve, EnqueueSora2ProTextToVideoResolution::TenEightyP) => 600,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// Sora 2 Pro Text-to-Video
|
||||
/// https://fal.ai/models/fal-ai/sora-2/text-to-video/pro
|
||||
pub async fn enqueue_sora_2_pro_text_to_video_webhook<R: IntoUrl>(
|
||||
@@ -74,35 +64,31 @@ pub async fn enqueue_sora_2_pro_text_to_video_webhook<R: IntoUrl>(
|
||||
) -> Result<WebhookResponse, FalErrorPlus> {
|
||||
|
||||
let duration = args.duration
|
||||
.map(|resolution| match resolution {
|
||||
.map(|d| match d {
|
||||
EnqueueSora2ProTextToVideoDurationSeconds::Four => 4,
|
||||
EnqueueSora2ProTextToVideoDurationSeconds::Eight => 8,
|
||||
EnqueueSora2ProTextToVideoDurationSeconds::Twelve => 12,
|
||||
});
|
||||
|
||||
let resolution = args.resolution
|
||||
.map(|resolution| match resolution {
|
||||
EnqueueSora2ProTextToVideoResolution::Auto => "auto",
|
||||
.map(|r| match r {
|
||||
EnqueueSora2ProTextToVideoResolution::SevenTwentyP => "720p",
|
||||
EnqueueSora2ProTextToVideoResolution::TenEightyP => "1080p",
|
||||
})
|
||||
.map(|resolution| resolution.to_string());
|
||||
.map(|r| r.to_string());
|
||||
|
||||
let aspect_ratio = args.aspect_ratio
|
||||
.map(|aspect_ratio| match aspect_ratio {
|
||||
EnqueueSora2ProTextToVideoAspectRatio::Auto => "auto",
|
||||
.map(|ar| match ar {
|
||||
EnqueueSora2ProTextToVideoAspectRatio::NineBySixteen => "9:16",
|
||||
EnqueueSora2ProTextToVideoAspectRatio::SixteenByNine => "16:9",
|
||||
})
|
||||
.map(|resolution| resolution.to_string());
|
||||
.map(|ar| ar.to_string());
|
||||
|
||||
let request = Sora2ProTextToVideoInput {
|
||||
prompt: args.prompt,
|
||||
// Optionals
|
||||
duration,
|
||||
resolution,
|
||||
aspect_ratio,
|
||||
// Constants
|
||||
delete_video: Some(false),
|
||||
};
|
||||
|
||||
@@ -116,17 +102,16 @@ pub async fn enqueue_sora_2_pro_text_to_video_webhook<R: IntoUrl>(
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::creds::fal_api_key::FalApiKey;
|
||||
use crate::requests::webhook::video::text::enqueue_sora_2_pro_text_to_video_webhook::{enqueue_sora_2_pro_text_to_video_webhook, EnqueueSora2ProTextToVideoArgs, EnqueueSora2ProTextToVideoAspectRatio, EnqueueSora2ProTextToVideoDurationSeconds, EnqueueSora2ProTextToVideoResolution};
|
||||
use errors::AnyhowResult;
|
||||
use std::fs::read_to_string;
|
||||
use strum::IntoEnumIterator;
|
||||
|
||||
#[tokio::test]
|
||||
#[ignore]
|
||||
#[ignore] // manually run — fires a real API request and incurs cost
|
||||
async fn test() -> AnyhowResult<()> {
|
||||
// XXX: Don't commit secrets!
|
||||
let secret = read_to_string("/home/bt/Artcraft/credentials/fal_api_key.txt")?;
|
||||
|
||||
let secret = read_to_string("/Users/bt/Artcraft/credentials/fal_api_key.txt")?;
|
||||
let api_key = FalApiKey::from_str(&secret);
|
||||
|
||||
let args = EnqueueSora2ProTextToVideoArgs {
|
||||
@@ -139,6 +124,76 @@ mod tests {
|
||||
};
|
||||
|
||||
let result = enqueue_sora_2_pro_text_to_video_webhook(args).await?;
|
||||
println!("result: {:?}", result);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[ignore] // manually run — fires a real API request per variant (expensive)
|
||||
async fn test_all_aspect_ratios() -> AnyhowResult<()> {
|
||||
let secret = read_to_string("/Users/bt/Artcraft/credentials/fal_api_key.txt")?;
|
||||
let api_key = FalApiKey::from_str(&secret);
|
||||
|
||||
for ar in EnqueueSora2ProTextToVideoAspectRatio::iter() {
|
||||
println!("--- aspect ratio: {:?} ---", ar);
|
||||
let args = EnqueueSora2ProTextToVideoArgs {
|
||||
prompt: "a serene mountain lake at sunset with gentle ripples".to_string(),
|
||||
duration: Some(EnqueueSora2ProTextToVideoDurationSeconds::Four),
|
||||
aspect_ratio: Some(ar),
|
||||
resolution: None,
|
||||
api_key: &api_key,
|
||||
webhook_url: "https://example.com/webhook",
|
||||
};
|
||||
let result = enqueue_sora_2_pro_text_to_video_webhook(args).await?;
|
||||
println!("result: {:?}", result);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[ignore] // manually run — fires a real API request per variant (expensive)
|
||||
async fn test_all_durations() -> AnyhowResult<()> {
|
||||
let secret = read_to_string("/Users/bt/Artcraft/credentials/fal_api_key.txt")?;
|
||||
let api_key = FalApiKey::from_str(&secret);
|
||||
|
||||
for dur in EnqueueSora2ProTextToVideoDurationSeconds::iter() {
|
||||
println!("--- duration: {:?} ---", dur);
|
||||
let args = EnqueueSora2ProTextToVideoArgs {
|
||||
prompt: "Suspenseful Hollywood movie. Interior, night. Customers are eating at a city diner. Suddenly a t-rex bursts its head through the large windows. The customers run.".to_string(),
|
||||
duration: Some(dur),
|
||||
aspect_ratio: Some(EnqueueSora2ProTextToVideoAspectRatio::SixteenByNine),
|
||||
resolution: None,
|
||||
api_key: &api_key,
|
||||
webhook_url: "https://example.com/webhook",
|
||||
};
|
||||
let result = enqueue_sora_2_pro_text_to_video_webhook(args).await?;
|
||||
println!("result: {:?}", result);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[ignore] // manually run — fires a real API request per variant (expensive)
|
||||
async fn test_all_resolutions() -> AnyhowResult<()> {
|
||||
let secret = read_to_string("/Users/bt/Artcraft/credentials/fal_api_key.txt")?;
|
||||
let api_key = FalApiKey::from_str(&secret);
|
||||
|
||||
for res in EnqueueSora2ProTextToVideoResolution::iter() {
|
||||
println!("--- resolution: {:?} ---", res);
|
||||
let args = EnqueueSora2ProTextToVideoArgs {
|
||||
prompt: "a bird flying over an ocean at golden hour".to_string(),
|
||||
duration: Some(EnqueueSora2ProTextToVideoDurationSeconds::Four),
|
||||
aspect_ratio: Some(EnqueueSora2ProTextToVideoAspectRatio::SixteenByNine),
|
||||
resolution: Some(res),
|
||||
api_key: &api_key,
|
||||
webhook_url: "https://example.com/webhook",
|
||||
};
|
||||
let result = enqueue_sora_2_pro_text_to_video_webhook(args).await?;
|
||||
println!("result: {:?}", result);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
+62
-22
@@ -1,8 +1,8 @@
|
||||
use crate::creds::fal_api_key::FalApiKey;
|
||||
use crate::error::classify_fal_error::classify_fal_error;
|
||||
use crate::error::fal_error_plus::FalErrorPlus;
|
||||
use crate::requests::http::video::http_sora_2_text_to_video::{sora_2_text_to_video, Sora2TextToVideoInput};
|
||||
use crate::requests::traits::fal_request_cost_calculator_trait::{FalRequestCostCalculator, UsdCents};
|
||||
use fal::endpoints::fal_ai::sora::sora2::sora_2_text_to_video::{sora_2_text_to_video, Sora2TextToVideoInput};
|
||||
use fal::webhook::WebhookResponse;
|
||||
use reqwest::IntoUrl;
|
||||
|
||||
@@ -20,22 +20,20 @@ pub struct EnqueueSora2TextToVideoArgs<'a, R: IntoUrl> {
|
||||
pub api_key: &'a FalApiKey,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
#[derive(Copy, Clone, Debug, strum::EnumIter)]
|
||||
pub enum EnqueueSora2TextToVideoDurationSeconds {
|
||||
Four,
|
||||
Eight,
|
||||
Twelve,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
#[derive(Copy, Clone, Debug, strum::EnumIter)]
|
||||
pub enum EnqueueSora2TextToVideoResolution {
|
||||
Auto,
|
||||
SevenTwentyP,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
#[derive(Copy, Clone, Debug, strum::EnumIter)]
|
||||
pub enum EnqueueSora2TextToVideoAspectRatio {
|
||||
Auto,
|
||||
NineBySixteen,
|
||||
SixteenByNine,
|
||||
}
|
||||
@@ -46,8 +44,8 @@ impl <U: IntoUrl> FalRequestCostCalculator for EnqueueSora2TextToVideoArgs<'_, U
|
||||
// "The pricing is $0.1/s for Sora 2."
|
||||
let duration = self.duration.unwrap_or(EnqueueSora2TextToVideoDurationSeconds::Four);
|
||||
match duration {
|
||||
EnqueueSora2TextToVideoDurationSeconds::Four => 40, // $0.10 * 4
|
||||
EnqueueSora2TextToVideoDurationSeconds::Eight => 80, // $0.10 * 8
|
||||
EnqueueSora2TextToVideoDurationSeconds::Four => 40, // $0.10 * 4
|
||||
EnqueueSora2TextToVideoDurationSeconds::Eight => 80, // $0.10 * 8
|
||||
EnqueueSora2TextToVideoDurationSeconds::Twelve => 120 // $0.10 * 12
|
||||
}
|
||||
}
|
||||
@@ -60,34 +58,30 @@ pub async fn enqueue_sora_2_text_to_video_webhook<R: IntoUrl>(
|
||||
) -> Result<WebhookResponse, FalErrorPlus> {
|
||||
|
||||
let duration = args.duration
|
||||
.map(|resolution| match resolution {
|
||||
.map(|d| match d {
|
||||
EnqueueSora2TextToVideoDurationSeconds::Four => 4,
|
||||
EnqueueSora2TextToVideoDurationSeconds::Eight => 8,
|
||||
EnqueueSora2TextToVideoDurationSeconds::Twelve => 12,
|
||||
});
|
||||
|
||||
let resolution = args.resolution
|
||||
.map(|resolution| match resolution {
|
||||
EnqueueSora2TextToVideoResolution::Auto => "auto",
|
||||
.map(|r| match r {
|
||||
EnqueueSora2TextToVideoResolution::SevenTwentyP => "720p",
|
||||
})
|
||||
.map(|resolution| resolution.to_string());
|
||||
.map(|r| r.to_string());
|
||||
|
||||
let aspect_ratio = args.aspect_ratio
|
||||
.map(|aspect_ratio| match aspect_ratio {
|
||||
EnqueueSora2TextToVideoAspectRatio::Auto => "auto",
|
||||
.map(|ar| match ar {
|
||||
EnqueueSora2TextToVideoAspectRatio::NineBySixteen => "9:16",
|
||||
EnqueueSora2TextToVideoAspectRatio::SixteenByNine => "16:9",
|
||||
})
|
||||
.map(|resolution| resolution.to_string());
|
||||
.map(|ar| ar.to_string());
|
||||
|
||||
let request = Sora2TextToVideoInput {
|
||||
prompt: args.prompt,
|
||||
// Optionals
|
||||
duration,
|
||||
resolution,
|
||||
aspect_ratio,
|
||||
// Constants
|
||||
delete_video: Some(false),
|
||||
};
|
||||
|
||||
@@ -101,17 +95,16 @@ pub async fn enqueue_sora_2_text_to_video_webhook<R: IntoUrl>(
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::creds::fal_api_key::FalApiKey;
|
||||
use crate::requests::webhook::video::text::enqueue_sora_2_text_to_video_webhook::{enqueue_sora_2_text_to_video_webhook, EnqueueSora2TextToVideoArgs, EnqueueSora2TextToVideoAspectRatio, EnqueueSora2TextToVideoDurationSeconds, EnqueueSora2TextToVideoResolution};
|
||||
use errors::AnyhowResult;
|
||||
use std::fs::read_to_string;
|
||||
use strum::IntoEnumIterator;
|
||||
|
||||
#[tokio::test]
|
||||
#[ignore]
|
||||
#[ignore] // manually run — fires a real API request and incurs cost
|
||||
async fn test() -> AnyhowResult<()> {
|
||||
// XXX: Don't commit secrets!
|
||||
let secret = read_to_string("/home/bt/Artcraft/credentials/fal_api_key.txt")?;
|
||||
|
||||
let secret = read_to_string("/Users/bt/Artcraft/credentials/fal_api_key.txt")?;
|
||||
let api_key = FalApiKey::from_str(&secret);
|
||||
|
||||
let args = EnqueueSora2TextToVideoArgs {
|
||||
@@ -124,6 +117,53 @@ mod tests {
|
||||
};
|
||||
|
||||
let result = enqueue_sora_2_text_to_video_webhook(args).await?;
|
||||
println!("result: {:?}", result);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[ignore] // manually run — fires a real API request per variant (expensive)
|
||||
async fn test_all_aspect_ratios() -> AnyhowResult<()> {
|
||||
let secret = read_to_string("/Users/bt/Artcraft/credentials/fal_api_key.txt")?;
|
||||
let api_key = FalApiKey::from_str(&secret);
|
||||
|
||||
for ar in EnqueueSora2TextToVideoAspectRatio::iter() {
|
||||
println!("--- aspect ratio: {:?} ---", ar);
|
||||
let args = EnqueueSora2TextToVideoArgs {
|
||||
prompt: "a snowy mountain cabin at sunrise".to_string(),
|
||||
duration: Some(EnqueueSora2TextToVideoDurationSeconds::Four),
|
||||
aspect_ratio: Some(ar),
|
||||
resolution: None,
|
||||
api_key: &api_key,
|
||||
webhook_url: "https://example.com/webhook",
|
||||
};
|
||||
let result = enqueue_sora_2_text_to_video_webhook(args).await?;
|
||||
println!("result: {:?}", result);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[ignore] // manually run — fires a real API request per variant (expensive)
|
||||
async fn test_all_durations() -> AnyhowResult<()> {
|
||||
let secret = read_to_string("/Users/bt/Artcraft/credentials/fal_api_key.txt")?;
|
||||
let api_key = FalApiKey::from_str(&secret);
|
||||
|
||||
for dur in EnqueueSora2TextToVideoDurationSeconds::iter() {
|
||||
println!("--- duration: {:?} ---", dur);
|
||||
let args = EnqueueSora2TextToVideoArgs {
|
||||
prompt: "a corgi walks in front of a giant windmill in a beautiful village at sunrise".to_string(),
|
||||
duration: Some(dur),
|
||||
aspect_ratio: Some(EnqueueSora2TextToVideoAspectRatio::SixteenByNine),
|
||||
resolution: None,
|
||||
api_key: &api_key,
|
||||
webhook_url: "https://example.com/webhook",
|
||||
};
|
||||
let result = enqueue_sora_2_text_to_video_webhook(args).await?;
|
||||
println!("result: {:?}", result);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
+2
-2
@@ -210,14 +210,14 @@ pub async fn sora_2_multi_function_video_gen_handler(
|
||||
};
|
||||
|
||||
let aspect_ratio = match request.aspect_ratio {
|
||||
Some(Sora2MultiFunctionVideoGenAspectRatio::Auto) => EnqueueSora2TextToVideoAspectRatio::Auto,
|
||||
Some(Sora2MultiFunctionVideoGenAspectRatio::Auto) => EnqueueSora2TextToVideoAspectRatio::SixteenByNine,
|
||||
Some(Sora2MultiFunctionVideoGenAspectRatio::SixteenByNine) => EnqueueSora2TextToVideoAspectRatio::SixteenByNine,
|
||||
Some(Sora2MultiFunctionVideoGenAspectRatio::NineBySixteen) => EnqueueSora2TextToVideoAspectRatio::NineBySixteen,
|
||||
None => EnqueueSora2TextToVideoAspectRatio::SixteenByNine,
|
||||
};
|
||||
|
||||
let resolution = match request.resolution {
|
||||
Some(Sora2MultiFunctionVideoGenResolution::Auto) => EnqueueSora2TextToVideoResolution::Auto,
|
||||
Some(Sora2MultiFunctionVideoGenResolution::Auto) => EnqueueSora2TextToVideoResolution::SevenTwentyP,
|
||||
Some(Sora2MultiFunctionVideoGenResolution::SevenTwentyP) => EnqueueSora2TextToVideoResolution::SevenTwentyP,
|
||||
None => EnqueueSora2TextToVideoResolution::SevenTwentyP,
|
||||
};
|
||||
|
||||
+2
-2
@@ -211,14 +211,14 @@ pub async fn sora_2_pro_multi_function_video_gen_handler(
|
||||
};
|
||||
|
||||
let aspect_ratio = match request.aspect_ratio {
|
||||
Some(Sora2ProMultiFunctionVideoGenAspectRatio::Auto) => EnqueueSora2ProTextToVideoAspectRatio::Auto,
|
||||
Some(Sora2ProMultiFunctionVideoGenAspectRatio::Auto) => EnqueueSora2ProTextToVideoAspectRatio::SixteenByNine,
|
||||
Some(Sora2ProMultiFunctionVideoGenAspectRatio::SixteenByNine) => EnqueueSora2ProTextToVideoAspectRatio::SixteenByNine,
|
||||
Some(Sora2ProMultiFunctionVideoGenAspectRatio::NineBySixteen) => EnqueueSora2ProTextToVideoAspectRatio::NineBySixteen,
|
||||
None => EnqueueSora2ProTextToVideoAspectRatio::SixteenByNine,
|
||||
};
|
||||
|
||||
let resolution = match request.resolution {
|
||||
Some(Sora2ProMultiFunctionVideoGenResolution::Auto) => EnqueueSora2ProTextToVideoResolution::Auto,
|
||||
Some(Sora2ProMultiFunctionVideoGenResolution::Auto) => EnqueueSora2ProTextToVideoResolution::TenEightyP,
|
||||
Some(Sora2ProMultiFunctionVideoGenResolution::SevenTwentyP) => EnqueueSora2ProTextToVideoResolution::SevenTwentyP,
|
||||
Some(Sora2ProMultiFunctionVideoGenResolution::TenEightyP) => EnqueueSora2ProTextToVideoResolution::TenEightyP,
|
||||
None => EnqueueSora2ProTextToVideoResolution::TenEightyP,
|
||||
|
||||
@@ -1944,20 +1944,6 @@ pub mod sdxl_controlnet_union;
|
||||
)))
|
||||
)]
|
||||
pub mod skyreels_i2v;
|
||||
#[cfg(any(
|
||||
feature = "endpoints",
|
||||
feature = "endpoints_fal-ai",
|
||||
feature = "endpoints_fal-ai_sora"
|
||||
))]
|
||||
#[cfg_attr(
|
||||
docsrs,
|
||||
doc(cfg(any(
|
||||
feature = "endpoints",
|
||||
feature = "endpoints_fal-ai",
|
||||
feature = "endpoints_fal-ai_sora"
|
||||
)))
|
||||
)]
|
||||
pub mod sora;
|
||||
#[cfg(any(
|
||||
feature = "endpoints",
|
||||
feature = "endpoints_fal-ai",
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
pub mod sora2;
|
||||
@@ -1,4 +0,0 @@
|
||||
pub mod sora_2_image_to_video;
|
||||
pub mod sora_2_pro_image_to_video;
|
||||
pub mod sora_2_pro_text_to_video;
|
||||
pub mod sora_2_text_to_video;
|
||||
Reference in New Issue
Block a user