mirror of
https://github.com/storytold/artcraft.git
synced 2026-10-26 02:59:37 +00:00
first iteration of pipeline
This commit is contained in:
@@ -7,6 +7,7 @@ pub enum Provider {
|
||||
Artcraft,
|
||||
Fal,
|
||||
GmiCloud,
|
||||
GrokApi,
|
||||
Muapi,
|
||||
Seedance2Pro,
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ use std::fmt::{Display, Formatter};
|
||||
use artcraft_client::error::storyteller_error::StorytellerError;
|
||||
use fal_client::error::fal_error_plus::FalErrorPlus;
|
||||
use gmicloud_client::error::gmicloud_error::GmiCloudError;
|
||||
use grok_api_client::error::grok_error::GrokError;
|
||||
use muapi_client::error::muapi_error::MuapiError;
|
||||
use seedance2pro_client::error::seedance2pro_error::Seedance2ProError;
|
||||
|
||||
@@ -11,6 +12,7 @@ pub enum ProviderError {
|
||||
Storyteller(StorytellerError),
|
||||
Fal(FalErrorPlus),
|
||||
GmiCloud(GmiCloudError),
|
||||
Grok(GrokError),
|
||||
Muapi(MuapiError),
|
||||
Seedance2Pro(Seedance2ProError),
|
||||
}
|
||||
@@ -23,6 +25,7 @@ impl Display for ProviderError {
|
||||
Self::Storyteller(e) => write!(f, "Storyteller provider error: {}", e),
|
||||
Self::Fal(e) => write!(f, "Fal provider error: {}", e),
|
||||
Self::GmiCloud(e) => write!(f, "GmiCloud provider error: {}", e),
|
||||
Self::Grok(e) => write!(f, "Grok provider error: {}", e),
|
||||
Self::Muapi(e) => write!(f, "Muapi provider error: {}", e),
|
||||
Self::Seedance2Pro(e) => write!(f, "Seedance2Pro provider error: {}", e),
|
||||
}
|
||||
@@ -47,6 +50,12 @@ impl From<GmiCloudError> for ProviderError {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<GrokError> for ProviderError {
|
||||
fn from(error: GrokError) -> Self {
|
||||
Self::Grok(error)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<MuapiError> for ProviderError {
|
||||
fn from(error: MuapiError) -> Self {
|
||||
Self::Muapi(error)
|
||||
|
||||
+6
@@ -60,6 +60,7 @@ use crate::generate::generate_video_v2::providers::kinovi::happy_horse_1p0::buil
|
||||
use crate::generate::generate_video_v2::providers::kinovi::seedance_2p0::build::build_kinovi_seedance_2p0;
|
||||
use crate::generate::generate_video_v2::providers::gmicloud::seedance_2p0_g::build::build_gmicloud_seedance_2p0_u;
|
||||
use crate::generate::generate_video_v2::providers::gmicloud::seedance_2p0_fast_g::build::build_gmicloud_seedance_2p0_u_fast;
|
||||
use crate::generate::generate_video_v2::providers::grok_api::grok_imagine_video::build::build_grok_api_grok_imagine_video;
|
||||
use crate::generate::generate_video_v2::providers::kinovi::seedance_2p0_fast::build::build_kinovi_seedance_2p0_fast;
|
||||
use crate::generate::generate_video_v2::video_generation_draft_or_request::VideoGenerationDraftOrRequest;
|
||||
|
||||
@@ -165,6 +166,8 @@ impl GenerateVideoRequestBuilder {
|
||||
// GmiCloud
|
||||
(Provider::GmiCloud, CommonVideoModel::Seedance2p0Ultra) => true,
|
||||
(Provider::GmiCloud, CommonVideoModel::Seedance2p0UltraFast) => true,
|
||||
// Grok
|
||||
(Provider::GrokApi, CommonVideoModel::GrokImagineVideo) => true,
|
||||
// Kinovi
|
||||
(Provider::Seedance2Pro, CommonVideoModel::HappyHorse1p0) => true,
|
||||
(Provider::Seedance2Pro, CommonVideoModel::Seedance2p0) => true,
|
||||
@@ -189,6 +192,8 @@ impl GenerateVideoRequestBuilder {
|
||||
// GmiCloud
|
||||
(Provider::GmiCloud, CommonVideoModel::Seedance2p0Ultra) => build_gmicloud_seedance_2p0_u(self),
|
||||
(Provider::GmiCloud, CommonVideoModel::Seedance2p0UltraFast) => build_gmicloud_seedance_2p0_u_fast(self),
|
||||
// Grok
|
||||
(Provider::GrokApi, CommonVideoModel::GrokImagineVideo) => build_grok_api_grok_imagine_video(self),
|
||||
// Kinovi
|
||||
(Provider::Seedance2Pro, CommonVideoModel::HappyHorse1p0) => build_kinovi_happy_horse_1p0(self),
|
||||
(Provider::Seedance2Pro, CommonVideoModel::Seedance2p0) => build_kinovi_seedance_2p0(self),
|
||||
@@ -203,6 +208,7 @@ impl GenerateVideoRequestBuilder {
|
||||
Provider::Artcraft => self.build_artcraft(),
|
||||
Provider::Fal => self.build_fal(),
|
||||
Provider::GmiCloud => self.unsupported_provider(), // GmiCloud uses build2() only
|
||||
Provider::GrokApi => self.unsupported_provider(), // Grok uses build2() only
|
||||
Provider::Muapi => self.build_muapi(),
|
||||
Provider::Seedance2Pro => self.build_seedance2pro(),
|
||||
}
|
||||
|
||||
+15
@@ -39,10 +39,18 @@ pub struct GmiCloudVideoResponsePayload {
|
||||
pub request_id: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct GrokVideoResponsePayload {
|
||||
/// xAI request_id; poll with `grok_api_client::api::requests::videos::video_status`
|
||||
/// to fetch the rendered video URL once it's ready.
|
||||
pub request_id: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum GenerateVideoResponse {
|
||||
Artcraft(ArtcraftVideoResponsePayload),
|
||||
GmiCloud(GmiCloudVideoResponsePayload),
|
||||
Grok(GrokVideoResponsePayload),
|
||||
Muapi(MuapiVideoResponsePayload),
|
||||
Seedance2Pro(Seedance2proVideoResponsePayload),
|
||||
Fal(FalVideoResponsePayload),
|
||||
@@ -77,6 +85,13 @@ impl GenerateVideoResponse {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_grok_payload(&self) -> Option<GrokVideoResponsePayload> {
|
||||
match self {
|
||||
Self::Grok(p) => Some(p.clone()),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_fal_payload(&self) -> Option<FalVideoResponsePayload> {
|
||||
match self {
|
||||
Self::Fal(p) => Some(p.clone()),
|
||||
|
||||
+481
@@ -0,0 +1,481 @@
|
||||
use grok_api_client::api::requests::videos::video_generation::video_generation::{
|
||||
VideoGenerationRequest as GrokVideoGenerationRequest,
|
||||
VideoImageSource as GrokVideoImageSource,
|
||||
};
|
||||
use grok_api_client::api::types::video_types::video_aspect_ratio::VideoAspectRatio as GrokAspectRatio;
|
||||
use grok_api_client::api::types::video_types::video_resolution::VideoResolution as GrokResolution;
|
||||
|
||||
use crate::api::audio_list_ref::AudioListRef;
|
||||
use crate::api::common_aspect_ratio::CommonAspectRatio;
|
||||
use crate::api::common_resolution::CommonResolution;
|
||||
use crate::api::image_list_ref::ImageListRef;
|
||||
use crate::api::image_ref::ImageRef;
|
||||
use crate::api::video_list_ref::VideoListRef;
|
||||
use crate::client::request_mismatch_mitigation_strategy::RequestMismatchMitigationStrategy;
|
||||
use crate::errors::artcraft_router_error::ArtcraftRouterError;
|
||||
use crate::errors::client_error::ClientError;
|
||||
use crate::generate::generate_video::generate_video_request_builder::GenerateVideoRequestBuilder;
|
||||
use crate::generate::generate_video_v2::providers::grok_api::grok_imagine_video::request::GrokApiGrokImagineVideoRequestState;
|
||||
use crate::generate::generate_video_v2::video_generation_draft_or_request::VideoGenerationDraftOrRequest;
|
||||
use crate::generate::generate_video_v2::video_generation_request::VideoGenerationRequest;
|
||||
|
||||
/// Builds a Grok Imagine Video request from the generic GenerateVideoRequestBuilder.
|
||||
///
|
||||
/// xAI's grok-imagine-video accepts:
|
||||
/// - `image` (single source image, image-to-video mode) OR `reference_images` (multi-image reference-to-video).
|
||||
/// - These two are mutually exclusive per xAI's API; supplying both returns a BadRequest.
|
||||
///
|
||||
/// This binding rejects `end_frame`, `reference_videos`, and `reference_audio` since
|
||||
/// xAI doesn't support them.
|
||||
pub fn build_grok_api_grok_imagine_video(
|
||||
mut builder: GenerateVideoRequestBuilder,
|
||||
) -> Result<VideoGenerationDraftOrRequest, ArtcraftRouterError> {
|
||||
let strategy = builder.request_mismatch_mitigation_strategy;
|
||||
|
||||
// Plan the simple/scalar fields first.
|
||||
let prompt = builder.prompt.take().unwrap_or_default();
|
||||
let aspect_ratio = plan_aspect_ratio(builder.aspect_ratio.take(), strategy)?;
|
||||
let resolution = plan_resolution(builder.resolution.take(), strategy)?;
|
||||
let duration = builder.duration_seconds.take().map(|d| (d as u32).clamp(1, 15));
|
||||
|
||||
// start_frame → image-to-video; end_frame isn't supported.
|
||||
let image = resolve_url_to_image_source(builder.start_frame.take())?;
|
||||
if let Some(end_frame) = builder.end_frame.take() {
|
||||
let _ = end_frame; // referenced for the error message below.
|
||||
return Err(ArtcraftRouterError::Client(ClientError::ModelDoesNotSupportOption {
|
||||
field: "end_frame",
|
||||
value: "Grok Imagine Video does not support end-frame keyframes".to_string(),
|
||||
}));
|
||||
}
|
||||
|
||||
// reference_images → reference-to-video. Mutually exclusive with `image`.
|
||||
let reference_images = resolve_url_list_to_image_sources(builder.reference_images.take())?;
|
||||
if image.is_some() && reference_images.as_ref().is_some_and(|v| !v.is_empty()) {
|
||||
return Err(ArtcraftRouterError::Client(ClientError::ModelDoesNotSupportOption {
|
||||
field: "start_frame + reference_images",
|
||||
value: "Grok Imagine Video accepts either `start_frame` (image-to-video) OR \
|
||||
`reference_images` (reference-to-video), not both".to_string(),
|
||||
}));
|
||||
}
|
||||
|
||||
// Grok doesn't ingest video or audio references.
|
||||
if let Some(videos) = builder.reference_videos.take() {
|
||||
let _ = videos;
|
||||
return Err(ArtcraftRouterError::Client(ClientError::ModelDoesNotSupportOption {
|
||||
field: "reference_videos",
|
||||
value: "Grok Imagine Video does not accept reference videos".to_string(),
|
||||
}));
|
||||
}
|
||||
if let Some(audio) = builder.reference_audio.take() {
|
||||
let _ = audio;
|
||||
return Err(ArtcraftRouterError::Client(ClientError::ModelDoesNotSupportOption {
|
||||
field: "reference_audio",
|
||||
value: "Grok Imagine Video does not accept reference audio".to_string(),
|
||||
}));
|
||||
}
|
||||
|
||||
let request = GrokVideoGenerationRequest {
|
||||
prompt,
|
||||
model: None, // defaults to grok-imagine-video in the client
|
||||
image,
|
||||
reference_images,
|
||||
aspect_ratio,
|
||||
duration,
|
||||
resolution,
|
||||
user: None,
|
||||
};
|
||||
|
||||
let state = GrokApiGrokImagineVideoRequestState { request };
|
||||
Ok(VideoGenerationDraftOrRequest::Request(
|
||||
VideoGenerationRequest::GrokApiGrokImagineVideo(state),
|
||||
))
|
||||
}
|
||||
|
||||
// ── Field planners ──
|
||||
|
||||
fn plan_aspect_ratio(
|
||||
aspect_ratio: Option<CommonAspectRatio>,
|
||||
_strategy: RequestMismatchMitigationStrategy,
|
||||
) -> Result<Option<GrokAspectRatio>, ArtcraftRouterError> {
|
||||
// xAI supports exactly: 1:1, 16:9, 9:16, 4:3, 3:4, 3:2, 2:3.
|
||||
// Auto / unsupported ratios fall back to the closest match (or None → xAI default 16:9).
|
||||
match aspect_ratio {
|
||||
None
|
||||
| Some(CommonAspectRatio::Auto)
|
||||
| Some(CommonAspectRatio::Auto2k)
|
||||
| Some(CommonAspectRatio::Auto3k)
|
||||
| Some(CommonAspectRatio::Auto4k) => Ok(None),
|
||||
|
||||
Some(CommonAspectRatio::Square) | Some(CommonAspectRatio::SquareHd) => {
|
||||
Ok(Some(GrokAspectRatio::Square))
|
||||
}
|
||||
|
||||
Some(CommonAspectRatio::WideSixteenByNine) | Some(CommonAspectRatio::Wide) => {
|
||||
Ok(Some(GrokAspectRatio::Landscape16x9))
|
||||
}
|
||||
Some(CommonAspectRatio::TallNineBySixteen) | Some(CommonAspectRatio::Tall) => {
|
||||
Ok(Some(GrokAspectRatio::Portrait9x16))
|
||||
}
|
||||
|
||||
Some(CommonAspectRatio::WideFourByThree) => Ok(Some(GrokAspectRatio::Landscape4x3)),
|
||||
Some(CommonAspectRatio::TallThreeByFour) => Ok(Some(GrokAspectRatio::Portrait3x4)),
|
||||
|
||||
Some(CommonAspectRatio::WideThreeByTwo) => Ok(Some(GrokAspectRatio::Landscape3x2)),
|
||||
Some(CommonAspectRatio::TallTwoByThree) => Ok(Some(GrokAspectRatio::Portrait2x3)),
|
||||
|
||||
// No exact xAI match — pick the closest cardinal direction. Mild
|
||||
// information loss, but better than failing the request.
|
||||
Some(CommonAspectRatio::WideFiveByFour)
|
||||
| Some(CommonAspectRatio::WideTwentyOneByNine) => Ok(Some(GrokAspectRatio::Landscape16x9)),
|
||||
Some(CommonAspectRatio::TallFourByFive)
|
||||
| Some(CommonAspectRatio::TallNineByTwentyOne) => Ok(Some(GrokAspectRatio::Portrait9x16)),
|
||||
}
|
||||
}
|
||||
|
||||
fn plan_resolution(
|
||||
resolution: Option<CommonResolution>,
|
||||
_strategy: RequestMismatchMitigationStrategy,
|
||||
) -> Result<Option<GrokResolution>, ArtcraftRouterError> {
|
||||
// Grok supports 480p and 720p only (1080p is downsized to 720p per xAI docs).
|
||||
match resolution {
|
||||
None => Ok(None),
|
||||
Some(CommonResolution::FourEightyP) => Ok(Some(GrokResolution::FourEightyP)),
|
||||
Some(CommonResolution::SevenTwentyP) => Ok(Some(GrokResolution::SevenTwentyP)),
|
||||
// Higher-than-720p requests get clamped to 720p (Grok's cap).
|
||||
Some(CommonResolution::TenEightyP)
|
||||
| Some(CommonResolution::TwoK)
|
||||
| Some(CommonResolution::ThreeK)
|
||||
| Some(CommonResolution::FourK) => Ok(Some(GrokResolution::SevenTwentyP)),
|
||||
// Lower-than-480p requests get bumped to 480p (Grok's floor).
|
||||
Some(CommonResolution::HalfK) | Some(CommonResolution::OneK) => {
|
||||
Ok(Some(GrokResolution::FourEightyP))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ── Image source resolvers ──
|
||||
|
||||
fn resolve_url_to_image_source(
|
||||
image_ref: Option<ImageRef>,
|
||||
) -> Result<Option<GrokVideoImageSource>, ArtcraftRouterError> {
|
||||
match image_ref {
|
||||
None => Ok(None),
|
||||
Some(ImageRef::Url(url)) => Ok(Some(GrokVideoImageSource::Url(url))),
|
||||
Some(ImageRef::MediaFileToken(_)) => {
|
||||
Err(ArtcraftRouterError::Client(ClientError::ModelDoesNotSupportOption {
|
||||
field: "start_frame",
|
||||
value: "Grok Imagine Video only accepts image URLs, not media file tokens".to_string(),
|
||||
}))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn resolve_url_list_to_image_sources(
|
||||
list_ref: Option<ImageListRef>,
|
||||
) -> Result<Option<Vec<GrokVideoImageSource>>, ArtcraftRouterError> {
|
||||
match list_ref {
|
||||
None => Ok(None),
|
||||
Some(ImageListRef::Urls(urls)) => {
|
||||
Ok(Some(urls.into_iter().map(GrokVideoImageSource::Url).collect()))
|
||||
}
|
||||
Some(ImageListRef::MediaFileTokens(_)) => {
|
||||
Err(ArtcraftRouterError::Client(ClientError::ModelDoesNotSupportOption {
|
||||
field: "reference_images",
|
||||
value: "Grok Imagine Video only accepts image URLs, not media file tokens".to_string(),
|
||||
}))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// `reference_videos` and `reference_audio` resolvers exist only to consume
|
||||
// the values for a clean error message; both reject any non-None input.
|
||||
#[allow(dead_code)]
|
||||
fn reject_video_refs(_refs: Option<VideoListRef>) -> Result<(), ArtcraftRouterError> {
|
||||
Ok(())
|
||||
}
|
||||
#[allow(dead_code)]
|
||||
fn reject_audio_refs(_refs: Option<AudioListRef>) -> Result<(), ArtcraftRouterError> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use tokens::tokens::media_files::MediaFileToken;
|
||||
|
||||
use crate::api::common_aspect_ratio::CommonAspectRatio;
|
||||
use crate::api::common_resolution::CommonResolution;
|
||||
use crate::api::common_video_model::CommonVideoModel;
|
||||
use crate::api::image_list_ref::ImageListRef;
|
||||
use crate::api::image_ref::ImageRef;
|
||||
use crate::api::provider::Provider;
|
||||
use crate::api::video_list_ref::VideoListRef;
|
||||
use crate::api::audio_list_ref::AudioListRef;
|
||||
use crate::generate::generate_video::generate_video_request_builder::GenerateVideoRequestBuilder;
|
||||
use crate::generate::generate_video_v2::video_generation_draft_or_request::VideoGenerationDraftOrRequest;
|
||||
use crate::generate::generate_video_v2::video_generation_request::VideoGenerationRequest;
|
||||
|
||||
// ── Field passthrough ──
|
||||
|
||||
mod field_conversions {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn prompt_passed_through() {
|
||||
let req = unwrap_request(make_builder(|b| { b.prompt = Some("test prompt".to_string()); }));
|
||||
assert_eq!(req.request.prompt, "test prompt");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn duration_passed_through() {
|
||||
let req = unwrap_request(make_builder(|b| { b.duration_seconds = Some(8); }));
|
||||
assert_eq!(req.request.duration, Some(8));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn duration_clamped_to_min() {
|
||||
let req = unwrap_request(make_builder(|b| { b.duration_seconds = Some(0); }));
|
||||
assert_eq!(req.request.duration, Some(1));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn duration_clamped_to_max() {
|
||||
let req = unwrap_request(make_builder(|b| { b.duration_seconds = Some(99); }));
|
||||
assert_eq!(req.request.duration, Some(15));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn duration_none_stays_none() {
|
||||
let req = unwrap_request(make_builder(|b| { b.duration_seconds = None; }));
|
||||
assert_eq!(req.request.duration, None);
|
||||
}
|
||||
}
|
||||
|
||||
// ── Aspect ratio ──
|
||||
|
||||
mod aspect_ratio_tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn square() {
|
||||
let req = unwrap_request(make_builder(|b| { b.aspect_ratio = Some(CommonAspectRatio::Square); }));
|
||||
assert_eq!(req.request.aspect_ratio, Some(GrokAspectRatio::Square));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn landscape_16x9() {
|
||||
let req = unwrap_request(make_builder(|b| { b.aspect_ratio = Some(CommonAspectRatio::WideSixteenByNine); }));
|
||||
assert_eq!(req.request.aspect_ratio, Some(GrokAspectRatio::Landscape16x9));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn portrait_9x16() {
|
||||
let req = unwrap_request(make_builder(|b| { b.aspect_ratio = Some(CommonAspectRatio::TallNineBySixteen); }));
|
||||
assert_eq!(req.request.aspect_ratio, Some(GrokAspectRatio::Portrait9x16));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn landscape_4x3() {
|
||||
let req = unwrap_request(make_builder(|b| { b.aspect_ratio = Some(CommonAspectRatio::WideFourByThree); }));
|
||||
assert_eq!(req.request.aspect_ratio, Some(GrokAspectRatio::Landscape4x3));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn portrait_3x4() {
|
||||
let req = unwrap_request(make_builder(|b| { b.aspect_ratio = Some(CommonAspectRatio::TallThreeByFour); }));
|
||||
assert_eq!(req.request.aspect_ratio, Some(GrokAspectRatio::Portrait3x4));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn landscape_3x2() {
|
||||
let req = unwrap_request(make_builder(|b| { b.aspect_ratio = Some(CommonAspectRatio::WideThreeByTwo); }));
|
||||
assert_eq!(req.request.aspect_ratio, Some(GrokAspectRatio::Landscape3x2));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn portrait_2x3() {
|
||||
let req = unwrap_request(make_builder(|b| { b.aspect_ratio = Some(CommonAspectRatio::TallTwoByThree); }));
|
||||
assert_eq!(req.request.aspect_ratio, Some(GrokAspectRatio::Portrait2x3));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn auto_maps_to_none() {
|
||||
let req = unwrap_request(make_builder(|b| { b.aspect_ratio = Some(CommonAspectRatio::Auto); }));
|
||||
assert_eq!(req.request.aspect_ratio, None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unsupported_wide_falls_back_to_16x9() {
|
||||
let req = unwrap_request(make_builder(|b| {
|
||||
b.aspect_ratio = Some(CommonAspectRatio::WideTwentyOneByNine);
|
||||
}));
|
||||
assert_eq!(req.request.aspect_ratio, Some(GrokAspectRatio::Landscape16x9));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unsupported_tall_falls_back_to_9x16() {
|
||||
let req = unwrap_request(make_builder(|b| {
|
||||
b.aspect_ratio = Some(CommonAspectRatio::TallNineByTwentyOne);
|
||||
}));
|
||||
assert_eq!(req.request.aspect_ratio, Some(GrokAspectRatio::Portrait9x16));
|
||||
}
|
||||
}
|
||||
|
||||
// ── Resolution ──
|
||||
|
||||
mod resolution_tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn res_480p() {
|
||||
let req = unwrap_request(make_builder(|b| { b.resolution = Some(CommonResolution::FourEightyP); }));
|
||||
assert_eq!(req.request.resolution, Some(GrokResolution::FourEightyP));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn res_720p() {
|
||||
let req = unwrap_request(make_builder(|b| { b.resolution = Some(CommonResolution::SevenTwentyP); }));
|
||||
assert_eq!(req.request.resolution, Some(GrokResolution::SevenTwentyP));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn res_1080p_clamps_to_720p() {
|
||||
let req = unwrap_request(make_builder(|b| { b.resolution = Some(CommonResolution::TenEightyP); }));
|
||||
assert_eq!(req.request.resolution, Some(GrokResolution::SevenTwentyP));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn res_4k_clamps_to_720p() {
|
||||
let req = unwrap_request(make_builder(|b| { b.resolution = Some(CommonResolution::FourK); }));
|
||||
assert_eq!(req.request.resolution, Some(GrokResolution::SevenTwentyP));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn res_1k_bumps_to_480p() {
|
||||
let req = unwrap_request(make_builder(|b| { b.resolution = Some(CommonResolution::OneK); }));
|
||||
assert_eq!(req.request.resolution, Some(GrokResolution::FourEightyP));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn none_stays_none() {
|
||||
let req = unwrap_request(make_builder(|_| {}));
|
||||
assert!(req.request.resolution.is_none());
|
||||
}
|
||||
}
|
||||
|
||||
// ── Image source plumbing ──
|
||||
|
||||
mod image_tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn start_frame_url_becomes_image() {
|
||||
let req = unwrap_request(make_builder(|b| {
|
||||
b.start_frame = Some(ImageRef::Url("https://example.com/start.png".to_string()));
|
||||
}));
|
||||
match req.request.image {
|
||||
Some(GrokVideoImageSource::Url(u)) => assert_eq!(u, "https://example.com/start.png"),
|
||||
other => panic!("expected Url variant, got {:?}", other),
|
||||
}
|
||||
assert!(req.request.reference_images.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn start_frame_media_file_token_rejected() {
|
||||
let result = build_grok_api_grok_imagine_video(GenerateVideoRequestBuilder {
|
||||
start_frame: Some(ImageRef::MediaFileToken(MediaFileToken::new("mf_test".to_string()))),
|
||||
..base_builder()
|
||||
});
|
||||
assert!(result.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn reference_image_urls_passed_through() {
|
||||
let urls = vec!["https://example.com/a.png".to_string(), "https://example.com/b.png".to_string()];
|
||||
let req = unwrap_request(make_builder(|b| {
|
||||
b.reference_images = Some(ImageListRef::Urls(urls.clone()));
|
||||
}));
|
||||
let refs = req.request.reference_images.expect("reference_images should be set");
|
||||
assert_eq!(refs.len(), 2);
|
||||
assert!(req.request.image.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn reference_image_tokens_rejected() {
|
||||
let result = build_grok_api_grok_imagine_video(GenerateVideoRequestBuilder {
|
||||
reference_images: Some(ImageListRef::MediaFileTokens(vec![MediaFileToken::new("mf_a".to_string())])),
|
||||
..base_builder()
|
||||
});
|
||||
assert!(result.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn start_frame_and_reference_images_together_rejected() {
|
||||
let result = build_grok_api_grok_imagine_video(GenerateVideoRequestBuilder {
|
||||
start_frame: Some(ImageRef::Url("u".to_string())),
|
||||
reference_images: Some(ImageListRef::Urls(vec!["v".to_string()])),
|
||||
..base_builder()
|
||||
});
|
||||
assert!(result.is_err(), "expected rejection of mutually-exclusive image + reference_images");
|
||||
}
|
||||
}
|
||||
|
||||
// ── Unsupported features ──
|
||||
|
||||
mod unsupported_features {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn end_frame_rejected() {
|
||||
let result = build_grok_api_grok_imagine_video(GenerateVideoRequestBuilder {
|
||||
end_frame: Some(ImageRef::Url("u".to_string())),
|
||||
..base_builder()
|
||||
});
|
||||
assert!(result.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn reference_videos_rejected() {
|
||||
let result = build_grok_api_grok_imagine_video(GenerateVideoRequestBuilder {
|
||||
reference_videos: Some(VideoListRef::Urls(vec!["v".to_string()])),
|
||||
..base_builder()
|
||||
});
|
||||
assert!(result.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn reference_audio_rejected() {
|
||||
let result = build_grok_api_grok_imagine_video(GenerateVideoRequestBuilder {
|
||||
reference_audio: Some(AudioListRef::Urls(vec!["a.wav".to_string()])),
|
||||
..base_builder()
|
||||
});
|
||||
assert!(result.is_err());
|
||||
}
|
||||
}
|
||||
|
||||
// ── Helpers ──
|
||||
|
||||
fn base_builder() -> GenerateVideoRequestBuilder {
|
||||
GenerateVideoRequestBuilder {
|
||||
model: CommonVideoModel::GrokImagineVideo,
|
||||
provider: Provider::GrokApi,
|
||||
duration_seconds: Some(5),
|
||||
video_batch_count: Some(1),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
fn make_builder(f: impl FnOnce(&mut GenerateVideoRequestBuilder)) -> GenerateVideoRequestBuilder {
|
||||
let mut b = base_builder();
|
||||
f(&mut b);
|
||||
b
|
||||
}
|
||||
|
||||
fn unwrap_request(builder: GenerateVideoRequestBuilder) -> GrokApiGrokImagineVideoRequestState {
|
||||
let result = build_grok_api_grok_imagine_video(builder).expect("build should succeed");
|
||||
match result {
|
||||
VideoGenerationDraftOrRequest::Request(VideoGenerationRequest::GrokApiGrokImagineVideo(s)) => s,
|
||||
_ => panic!("expected GrokApiGrokImagineVideo request"),
|
||||
}
|
||||
}
|
||||
}
|
||||
+188
@@ -0,0 +1,188 @@
|
||||
use grok_api_client::api::traits::grok_request_cost_calculator_trait::GrokRequestCostCalculator;
|
||||
|
||||
use crate::generate::generate_video::video_generation_cost_estimate::VideoGenerationCostEstimate;
|
||||
use crate::generate::generate_video_v2::providers::grok_api::grok_imagine_video::request::GrokApiGrokImagineVideoRequestState;
|
||||
|
||||
pub struct GrokApiGrokImagineVideoCostState {
|
||||
request: GrokApiGrokImagineVideoRequestState,
|
||||
}
|
||||
|
||||
impl GrokApiGrokImagineVideoCostState {
|
||||
pub fn from_request(request: &GrokApiGrokImagineVideoRequestState) -> Self {
|
||||
Self { request: request.clone() }
|
||||
}
|
||||
|
||||
pub fn estimate_cost(&self) -> VideoGenerationCostEstimate {
|
||||
// grok_api_client's GrokRequestCostCalculator does the math:
|
||||
// - Output: resolution × duration (50 mills/s @ 480p, 70 mills/s @ 720p)
|
||||
// - Input: 2 mills × source image count
|
||||
// No source-video duration to factor in for `video_generation` — it's
|
||||
// text/image → video, not edit/extension.
|
||||
let cost_in_usd_cents = self.request.request.calculate_cost_in_cents();
|
||||
|
||||
VideoGenerationCostEstimate {
|
||||
cost_in_credits: None,
|
||||
cost_in_usd_cents: Some(cost_in_usd_cents),
|
||||
is_free: false,
|
||||
is_unlimited: false,
|
||||
is_rate_limited: false,
|
||||
has_watermark: false,
|
||||
failures_are_refunded: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::api::common_aspect_ratio::CommonAspectRatio;
|
||||
use crate::api::common_resolution::CommonResolution;
|
||||
use crate::api::common_video_model::CommonVideoModel;
|
||||
use crate::api::image_list_ref::ImageListRef;
|
||||
use crate::api::image_ref::ImageRef;
|
||||
use crate::api::provider::Provider;
|
||||
use crate::generate::generate_video::generate_video_request_builder::GenerateVideoRequestBuilder;
|
||||
|
||||
// Pricing reference (from grok_api_client/cost.rs, mirrors xAI's table):
|
||||
// Output: 480p 50 mills/s ($0.05/s)
|
||||
// 720p 70 mills/s ($0.07/s)
|
||||
// Input: 2 mills per source image ($0.002/img)
|
||||
//
|
||||
// Cost in cents = ceil(mills / 10).
|
||||
|
||||
mod pricing_720p {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn five_s_no_image_ref() {
|
||||
// 70 × 5 = 350 mills → 35¢
|
||||
assert_eq!(cost_cents(Some(CommonResolution::SevenTwentyP), 5, None, None), 35);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ten_s_no_image_ref() {
|
||||
// 70 × 10 = 700 mills → 70¢
|
||||
assert_eq!(cost_cents(Some(CommonResolution::SevenTwentyP), 10, None, None), 70);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fifteen_s_no_image_ref() {
|
||||
// 70 × 15 = 1050 mills → 105¢
|
||||
assert_eq!(cost_cents(Some(CommonResolution::SevenTwentyP), 15, None, None), 105);
|
||||
}
|
||||
}
|
||||
|
||||
mod pricing_480p {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn five_s_no_image_ref() {
|
||||
// 50 × 5 = 250 mills → 25¢
|
||||
assert_eq!(cost_cents(Some(CommonResolution::FourEightyP), 5, None, None), 25);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ten_s_no_image_ref() {
|
||||
// 50 × 10 = 500 mills → 50¢
|
||||
assert_eq!(cost_cents(Some(CommonResolution::FourEightyP), 10, None, None), 50);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fifteen_s_no_image_ref() {
|
||||
// 50 × 15 = 750 mills → 75¢
|
||||
assert_eq!(cost_cents(Some(CommonResolution::FourEightyP), 15, None, None), 75);
|
||||
}
|
||||
}
|
||||
|
||||
mod image_inputs_add_to_cost {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn single_start_frame_image_adds_2_mills() {
|
||||
// 5s @ 720p output 350 mills + 1 input image 2 mills = 352 mills → 36¢ (ceil)
|
||||
let cents = cost_cents(
|
||||
Some(CommonResolution::SevenTwentyP),
|
||||
5,
|
||||
Some(ImageRef::Url("https://example.com/a.png".to_string())),
|
||||
None,
|
||||
);
|
||||
assert_eq!(cents, 36);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn three_reference_images_adds_6_mills() {
|
||||
// 5s @ 720p output 350 mills + 3 input images 6 mills = 356 mills → 36¢ (ceil)
|
||||
let cents = cost_cents(
|
||||
Some(CommonResolution::SevenTwentyP),
|
||||
5,
|
||||
None,
|
||||
Some(ImageListRef::Urls(vec![
|
||||
"https://example.com/a.png".to_string(),
|
||||
"https://example.com/b.png".to_string(),
|
||||
"https://example.com/c.png".to_string(),
|
||||
])),
|
||||
);
|
||||
assert_eq!(cents, 36);
|
||||
}
|
||||
}
|
||||
|
||||
mod relative_pricing {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn higher_resolution_costs_more() {
|
||||
let p480 = cost_cents(Some(CommonResolution::FourEightyP), 10, None, None);
|
||||
let p720 = cost_cents(Some(CommonResolution::SevenTwentyP), 10, None, None);
|
||||
assert!(p480 < p720, "480p ({p480}) should be < 720p ({p720})");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn longer_duration_costs_more() {
|
||||
let c5 = cost_cents(Some(CommonResolution::SevenTwentyP), 5, None, None);
|
||||
let c10 = cost_cents(Some(CommonResolution::SevenTwentyP), 10, None, None);
|
||||
let c15 = cost_cents(Some(CommonResolution::SevenTwentyP), 15, None, None);
|
||||
assert!(c5 < c10);
|
||||
assert!(c10 < c15);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn one_k_request_clamps_to_480p_pricing() {
|
||||
// OneK gets bumped to 480p in plan_resolution, so cost equals 480p cost.
|
||||
let one_k = cost_cents(Some(CommonResolution::OneK), 10, None, None);
|
||||
let p480 = cost_cents(Some(CommonResolution::FourEightyP), 10, None, None);
|
||||
assert_eq!(one_k, p480);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ten_eighty_p_request_clamps_to_720p_pricing() {
|
||||
// TenEightyP gets clamped to 720p in plan_resolution.
|
||||
let p1080 = cost_cents(Some(CommonResolution::TenEightyP), 10, None, None);
|
||||
let p720 = cost_cents(Some(CommonResolution::SevenTwentyP), 10, None, None);
|
||||
assert_eq!(p1080, p720);
|
||||
}
|
||||
}
|
||||
|
||||
fn cost_cents(
|
||||
resolution: Option<CommonResolution>,
|
||||
duration_seconds: u16,
|
||||
start_frame: Option<ImageRef>,
|
||||
reference_images: Option<ImageListRef>,
|
||||
) -> u64 {
|
||||
let builder = GenerateVideoRequestBuilder {
|
||||
model: CommonVideoModel::GrokImagineVideo,
|
||||
provider: Provider::GrokApi,
|
||||
aspect_ratio: Some(CommonAspectRatio::WideSixteenByNine),
|
||||
resolution,
|
||||
duration_seconds: Some(duration_seconds),
|
||||
video_batch_count: Some(1),
|
||||
start_frame,
|
||||
reference_images,
|
||||
..Default::default()
|
||||
};
|
||||
builder.build2()
|
||||
.expect("build2 should succeed")
|
||||
.estimate_cost()
|
||||
.expect("estimate_cost should succeed")
|
||||
.cost_in_usd_cents
|
||||
.unwrap()
|
||||
}
|
||||
}
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
pub mod build;
|
||||
pub mod cost;
|
||||
pub mod request;
|
||||
+106
@@ -0,0 +1,106 @@
|
||||
use grok_api_client::api::requests::videos::video_generation::video_generation::{
|
||||
video_generation, VideoGenerationArgs, VideoGenerationRequest as GrokVideoGenerationRequest,
|
||||
};
|
||||
|
||||
use crate::client::router_grok_api_client::RouterGrokApiClient;
|
||||
use crate::errors::artcraft_router_error::ArtcraftRouterError;
|
||||
use crate::errors::provider_error::ProviderError;
|
||||
use crate::generate::generate_video::generate_video_response::{
|
||||
GenerateVideoResponse, GrokVideoResponsePayload,
|
||||
};
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct GrokApiGrokImagineVideoRequestState {
|
||||
/// The fully-resolved Grok request body. Doesn't carry the API key — that
|
||||
/// gets borrowed at send time from the router client.
|
||||
pub request: GrokVideoGenerationRequest,
|
||||
}
|
||||
|
||||
impl GrokApiGrokImagineVideoRequestState {
|
||||
pub async fn send(&self, client: &RouterGrokApiClient) -> Result<GenerateVideoResponse, ArtcraftRouterError> {
|
||||
let response = video_generation(VideoGenerationArgs {
|
||||
api_key: &client.api_key,
|
||||
request: self.request.clone(),
|
||||
})
|
||||
.await
|
||||
.map_err(|err| ArtcraftRouterError::Provider(ProviderError::Grok(err)))?;
|
||||
|
||||
Ok(GenerateVideoResponse::Grok(GrokVideoResponsePayload {
|
||||
request_id: response.request_id,
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use grok_api_client::creds::grok_api_key::GrokApiKey;
|
||||
use test_data::web::image_urls::JUNO_AT_LAKE_IMAGE_URL;
|
||||
|
||||
use crate::api::common_aspect_ratio::CommonAspectRatio;
|
||||
use crate::api::common_resolution::CommonResolution;
|
||||
use crate::api::common_video_model::CommonVideoModel;
|
||||
use crate::api::image_ref::ImageRef;
|
||||
use crate::api::provider::Provider;
|
||||
use crate::client::router_client::RouterClient;
|
||||
use crate::client::router_grok_api_client::RouterGrokApiClient;
|
||||
use crate::generate::generate_video::generate_video_request_builder::GenerateVideoRequestBuilder;
|
||||
use crate::generate::generate_video::generate_video_response::GenerateVideoResponse;
|
||||
use crate::generate::generate_video_v2::video_generation_draft_or_request::VideoGenerationDraftOrRequest;
|
||||
|
||||
#[tokio::test]
|
||||
#[ignore] // requires real API key, incurs costs
|
||||
async fn test_text_to_video_720p() {
|
||||
let response = run_pipeline(GenerateVideoRequestBuilder {
|
||||
prompt: Some("A glowing crystal rocket launching from Mars.".to_string()),
|
||||
aspect_ratio: Some(CommonAspectRatio::WideSixteenByNine),
|
||||
resolution: Some(CommonResolution::SevenTwentyP),
|
||||
duration_seconds: Some(5),
|
||||
..grok_builder()
|
||||
}).await;
|
||||
assert!(matches!(response, GenerateVideoResponse::Grok(_)));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[ignore] // requires real API key, incurs costs
|
||||
async fn test_image_to_video_480p() {
|
||||
let response = run_pipeline(GenerateVideoRequestBuilder {
|
||||
prompt: Some("The dog leaps into the lake and splashes around.".to_string()),
|
||||
start_frame: Some(ImageRef::Url(JUNO_AT_LAKE_IMAGE_URL.to_string())),
|
||||
resolution: Some(CommonResolution::FourEightyP),
|
||||
duration_seconds: Some(5),
|
||||
..grok_builder()
|
||||
}).await;
|
||||
assert!(matches!(response, GenerateVideoResponse::Grok(_)));
|
||||
}
|
||||
|
||||
fn grok_builder() -> GenerateVideoRequestBuilder {
|
||||
GenerateVideoRequestBuilder {
|
||||
model: CommonVideoModel::GrokImagineVideo,
|
||||
provider: Provider::GrokApi,
|
||||
video_batch_count: Some(1),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
fn get_grok_client() -> RouterClient {
|
||||
let secret = std::fs::read_to_string("/Users/bt/Artcraft/credentials/grok_api_key.txt")
|
||||
.expect("Failed to read Grok API key");
|
||||
let api_key = GrokApiKey::new(secret.trim().to_string());
|
||||
RouterClient::GrokApi(RouterGrokApiClient::new(api_key))
|
||||
}
|
||||
|
||||
async fn run_pipeline(builder: GenerateVideoRequestBuilder) -> GenerateVideoResponse {
|
||||
let client = get_grok_client();
|
||||
let draft_or_request = builder.build2().expect("build2 should succeed");
|
||||
let request = match draft_or_request {
|
||||
VideoGenerationDraftOrRequest::Request(r) => r,
|
||||
_ => panic!("expected Request variant (Grok skips draft)"),
|
||||
};
|
||||
let response = request.send_request(&client).await.expect("send_request should succeed");
|
||||
match &response {
|
||||
GenerateVideoResponse::Grok(p) => println!("grok request_id={}", p.request_id),
|
||||
other => println!("unexpected response: {:?}", other),
|
||||
}
|
||||
response
|
||||
}
|
||||
}
|
||||
+1
@@ -0,0 +1 @@
|
||||
pub mod grok_imagine_video;
|
||||
+1
@@ -1,3 +1,4 @@
|
||||
pub mod artcraft;
|
||||
pub mod gmicloud;
|
||||
pub mod grok_api;
|
||||
pub mod kinovi;
|
||||
+9
@@ -25,6 +25,8 @@ use crate::generate::generate_video_v2::providers::gmicloud::seedance_2p0_g::cos
|
||||
use crate::generate::generate_video_v2::providers::gmicloud::seedance_2p0_g::request::GmiCloudSeedance2p0UltraRequestState;
|
||||
use crate::generate::generate_video_v2::providers::gmicloud::seedance_2p0_fast_g::cost::GmiCloudSeedance2p0UltraFastCostState;
|
||||
use crate::generate::generate_video_v2::providers::gmicloud::seedance_2p0_fast_g::request::GmiCloudSeedance2p0UltraFastRequestState;
|
||||
use crate::generate::generate_video_v2::providers::grok_api::grok_imagine_video::cost::GrokApiGrokImagineVideoCostState;
|
||||
use crate::generate::generate_video_v2::providers::grok_api::grok_imagine_video::request::GrokApiGrokImagineVideoRequestState;
|
||||
use crate::generate::generate_video_v2::providers::kinovi::happy_horse_1p0::cost::KinoviHappyHorse1p0CostState;
|
||||
use crate::generate::generate_video_v2::providers::kinovi::happy_horse_1p0::request::KinoviHappyHorse1p0RequestState;
|
||||
use crate::generate::generate_video_v2::providers::kinovi::seedance_2p0::cost::KinoviSeedance2p0CostState;
|
||||
@@ -45,6 +47,7 @@ pub enum VideoGenerationRequest {
|
||||
ArtcraftSeedance2p0BytePlusFast(ArtcraftSeedance2p0BytePlusFastRequestState),
|
||||
GmiCloudSeedance2p0Ultra(GmiCloudSeedance2p0UltraRequestState),
|
||||
GmiCloudSeedance2p0UltraFast(GmiCloudSeedance2p0UltraFastRequestState),
|
||||
GrokApiGrokImagineVideo(GrokApiGrokImagineVideoRequestState),
|
||||
KinoviHappyHorse1p0(KinoviHappyHorse1p0RequestState),
|
||||
KinoviSeedance2p0(KinoviSeedance2p0RequestState),
|
||||
KinoviSeedance2p0Fast(KinoviSeedance2p0FastRequestState),
|
||||
@@ -65,6 +68,7 @@ impl VideoGenerationRequest {
|
||||
Self::ArtcraftSeedance2p0BytePlusFast(_) => Provider::Artcraft,
|
||||
Self::GmiCloudSeedance2p0Ultra(_) => Provider::GmiCloud,
|
||||
Self::GmiCloudSeedance2p0UltraFast(_) => Provider::GmiCloud,
|
||||
Self::GrokApiGrokImagineVideo(_) => Provider::GrokApi,
|
||||
Self::KinoviHappyHorse1p0(_) => Provider::Seedance2Pro,
|
||||
Self::KinoviSeedance2p0(_) => Provider::Seedance2Pro,
|
||||
Self::KinoviSeedance2p0Fast(_) => Provider::Seedance2Pro,
|
||||
@@ -85,6 +89,7 @@ impl VideoGenerationRequest {
|
||||
VideoGenerationRequest::ArtcraftSeedance2p0BytePlusFast(request) => Ok(ArtcraftSeedance2p0BytePlusFastCostState::from_request(request).estimate_cost()),
|
||||
VideoGenerationRequest::GmiCloudSeedance2p0Ultra(request) => Ok(GmiCloudSeedance2p0UltraCostState::from_request(request).estimate_cost()),
|
||||
VideoGenerationRequest::GmiCloudSeedance2p0UltraFast(request) => Ok(GmiCloudSeedance2p0UltraFastCostState::from_request(request).estimate_cost()),
|
||||
VideoGenerationRequest::GrokApiGrokImagineVideo(request) => Ok(GrokApiGrokImagineVideoCostState::from_request(request).estimate_cost()),
|
||||
VideoGenerationRequest::KinoviHappyHorse1p0(request) => Ok(KinoviHappyHorse1p0CostState::from_request(request).estimate_cost()),
|
||||
VideoGenerationRequest::KinoviSeedance2p0(request) => Ok(KinoviSeedance2p0CostState::from_request(request).estimate_cost()),
|
||||
VideoGenerationRequest::KinoviSeedance2p0Fast(request) => Ok(KinoviSeedance2p0FastCostState::from_request(request).estimate_cost()),
|
||||
@@ -139,6 +144,10 @@ impl VideoGenerationRequest {
|
||||
let client_ref = client.get_gmicloud_client_ref()?;
|
||||
request.send(client_ref).await
|
||||
},
|
||||
VideoGenerationRequest::GrokApiGrokImagineVideo(request) => {
|
||||
let client_ref = client.get_grok_api_client_ref()?;
|
||||
request.send(client_ref).await
|
||||
},
|
||||
VideoGenerationRequest::KinoviHappyHorse1p0(request) => {
|
||||
let client_ref = client.get_seedance2pro_client_ref()?;
|
||||
request.send(client_ref).await
|
||||
|
||||
+2
-1
@@ -347,7 +347,8 @@ mod tests {
|
||||
let result = video_status(VideoStatusArgs {
|
||||
api_key: &api_key,
|
||||
request: VideoStatusRequest {
|
||||
request_id: "ff94f941-62a7-9966-92da-6b84d9eedb50".to_string(),
|
||||
//request_id: "ff94f941-62a7-9966-92da-6b84d9eedb50".to_string(),
|
||||
request_id: "4eea215a-9d21-9261-be1a-324409ec22c5".to_string(),
|
||||
},
|
||||
}).await;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user