zai_rs/model/gen_video_async/
video_request.rs

1use super::super::traits::*;
2use serde::Serialize;
3use validator::*;
4
5#[derive(Debug, Clone, Validate, Serialize)]
6#[validate(schema(function = "validate_prompt_or_image"))]
7pub struct VideoBody<N>
8where
9    N: ModelName + Serialize,
10{
11    /// Model identifier for video generation API
12    pub model: N,
13    /// Image URL(s) for video generation base
14    /// Supports single URL string or array of URLs (1-2 URLs)
15    /// Supported formats: .png, .jpeg, .jpg, max 5MB
16    /// Either prompt or image_url must be provided (or both)
17    #[serde(skip_serializing_if = "Option::is_none")]
18    pub image_url: Option<ImageUrl>,
19    /// Text description for video generation, max 1500 characters
20    /// Either prompt or image_url must be provided (or both)
21    #[serde(skip_serializing_if = "Option::is_none")]
22    #[validate(length(max = 1500))]
23    pub prompt: Option<String>,
24    /// Output quality mode, defaults to "speed"
25    /// "quality": prioritize higher generation quality
26    /// "speed": prioritize faster generation with slightly lower quality
27    #[serde(skip_serializing_if = "Option::is_none")]
28    pub quality: Option<VideoQuality>,
29    /// Whether to generate AI audio effects, defaults to false
30    #[serde(skip_serializing_if = "Option::is_none")]
31    pub with_audio: Option<bool>,
32    /// Control watermark for AI-generated content
33    /// true: enable watermarks to meet policy requirements
34    /// false: disable watermarks, only for authorized customers
35    #[serde(skip_serializing_if = "Option::is_none")]
36    pub watermark_enabled: Option<bool>,
37    /// Video resolution size
38    /// If not specified, short side defaults to 1080, long side determined by aspect ratio
39    /// Supports up to 4K resolution
40    #[serde(skip_serializing_if = "Option::is_none")]
41    pub size: Option<VideoSize>,
42    /// Video frame rate (FPS), supported values: 30 or 60, defaults to 30
43    #[serde(skip_serializing_if = "Option::is_none")]
44    pub fps: Option<Fps>,
45    /// Video duration in seconds, defaults to 5, supported: 5 or 10
46    #[serde(skip_serializing_if = "Option::is_none")]
47    pub duration: Option<VideoDuration>,
48    /// Unique request identifier provided by client
49    /// If not provided, platform will generate one automatically
50    #[serde(skip_serializing_if = "Option::is_none")]
51    pub request_id: Option<String>,
52    /// End user's unique ID for policy enforcement
53    /// Length requirements: minimum 6 characters, maximum 128 characters
54    #[serde(skip_serializing_if = "Option::is_none")]
55    #[validate(length(min = 6, max = 128))]
56    pub user_id: Option<String>,
57}
58
59impl<N> VideoBody<N>
60where
61    N: ModelName + Serialize,
62{
63    /// Create a new VideoBody with the specified model
64    pub fn new(model: N) -> Self {
65        Self {
66            model,
67            prompt: None,
68            quality: None,
69            with_audio: None,
70            watermark_enabled: None,
71            image_url: None,
72            size: None,
73            fps: None,
74            duration: None,
75            request_id: None,
76            user_id: None,
77        }
78    }
79
80    /// Set the text prompt for video generation
81    pub fn with_prompt(mut self, prompt: impl Into<String>) -> Self {
82        self.prompt = Some(prompt.into());
83        self
84    }
85
86    /// Set the quality mode (speed or quality)
87    pub fn with_quality(mut self, quality: VideoQuality) -> Self {
88        self.quality = Some(quality);
89        self
90    }
91
92    /// Enable/disable audio generation
93    pub fn with_audio(mut self, with_audio: bool) -> Self {
94        self.with_audio = Some(with_audio);
95        self
96    }
97
98    /// Enable/disable watermark
99    pub fn with_watermark_enabled(mut self, watermark_enabled: bool) -> Self {
100        self.watermark_enabled = Some(watermark_enabled);
101        self
102    }
103
104    /// Set image URL(s) for video generation
105    pub fn with_image_url(mut self, image_url: ImageUrl) -> Self {
106        self.image_url = Some(image_url);
107        self
108    }
109
110    /// Set video resolution size
111    pub fn with_size(mut self, size: VideoSize) -> Self {
112        self.size = Some(size);
113        self
114    }
115
116    /// Set video frame rate (30 or 60 FPS)
117    pub fn with_fps(mut self, fps: Fps) -> Self {
118        self.fps = Some(fps);
119        self
120    }
121
122    /// Set video duration (5 or 10 seconds)
123    pub fn with_duration(mut self, duration: VideoDuration) -> Self {
124        self.duration = Some(duration);
125        self
126    }
127
128    /// Set custom request ID
129    pub fn with_request_id(mut self, request_id: impl Into<String>) -> Self {
130        self.request_id = Some(request_id.into());
131        self
132    }
133
134    /// Set user ID for policy enforcement
135    pub fn with_user_id(mut self, user_id: impl Into<String>) -> Self {
136        self.user_id = Some(user_id.into());
137        self
138    }
139
140    /// Create a video request with prompt only (Format 1)
141    pub fn prompt_only(model: N, prompt: impl Into<String>) -> Self {
142        Self::new(model).with_prompt(prompt)
143    }
144
145    /// Create a video request with single image URL and prompt (Format 2)
146    pub fn with_single_image(
147        model: N,
148        image_url: impl Into<String>,
149        prompt: impl Into<String>,
150    ) -> Self {
151        Self::new(model)
152            .with_image_url(ImageUrl::from_url(image_url))
153            .with_prompt(prompt)
154    }
155
156    /// Create a video request with multiple image URLs and prompt (Format 3)
157    pub fn with_multiple_images(
158        model: N,
159        mut image_urls: Vec<impl Into<String>>,
160        prompt: impl Into<String>,
161    ) -> Self {
162        let image_url = if image_urls.len() == 1 {
163            ImageUrl::from_url(image_urls.remove(0))
164        } else if image_urls.len() == 2 {
165            ImageUrl::from_two_urls(image_urls.remove(0), image_urls.remove(0))
166        } else {
167            panic!("with_multiple_images requires 1 or 2 URLs");
168        };
169
170        Self::new(model)
171            .with_image_url(image_url)
172            .with_prompt(prompt)
173    }
174}
175
176// Struct-level validation: require at least one of prompt or image_url.
177#[allow(dead_code)]
178fn validate_prompt_or_image<N>(body: &VideoBody<N>) -> Result<(), validator::ValidationError>
179where
180    N: ModelName + Serialize,
181{
182    let has_prompt = body.prompt.as_ref().map(|s| !s.is_empty()).unwrap_or(false);
183    let has_image = body.image_url.is_some();
184    if has_prompt || has_image {
185        Ok(())
186    } else {
187        Err(validator::ValidationError::new("prompt_or_image_required"))
188    }
189}
190
191#[derive(Debug, Clone, Serialize)]
192#[serde(rename_all = "lowercase")]
193pub enum VideoQuality {
194    /// Prioritize faster generation with slightly lower quality
195    Speed,
196    /// Prioritize higher generation quality
197    Quality,
198}
199
200#[derive(Debug, Clone, Serialize)]
201#[serde(untagged)]
202pub enum ImageUrl {
203    /// Base64 encoded image data
204    Base64(String),
205    /// Single URL or array of URLs (1-2 URLs)
206    VecUrl(Vec<String>),
207}
208
209impl ImageUrl {
210    /// Create ImageUrl from base64-encoded data
211    pub fn base64(data: impl Into<String>) -> Self {
212        ImageUrl::Base64(data.into())
213    }
214
215    /// Create ImageUrl from a single URL
216    pub fn from_url(url: impl Into<String>) -> Self {
217        ImageUrl::VecUrl(vec![url.into()])
218    }
219
220    /// Create ImageUrl from exactly two URLs
221    pub fn from_two_urls(u1: impl Into<String>, u2: impl Into<String>) -> Self {
222        ImageUrl::VecUrl(vec![u1.into(), u2.into()])
223    }
224}
225
226#[derive(Debug, Clone, Serialize)]
227pub enum VideoSize {
228    /// 1280x720 resolution (HD)
229    #[serde(rename = "1280x720")]
230    Size1280x720,
231    /// 720x1280 resolution (vertical HD)
232    #[serde(rename = "720x1280")]
233    Size720x1280,
234    /// 1024x1024 resolution (square)
235    #[serde(rename = "1024x1024")]
236    Size1024x1024,
237    /// 1920x1080 resolution (Full HD)
238    #[serde(rename = "1920x1080")]
239    Size1920x1080,
240    /// 1080x1920 resolution (vertical Full HD)
241    #[serde(rename = "1080x1920")]
242    Size1080x1920,
243    /// 2048x1080 resolution (2K)
244    #[serde(rename = "2048x1080")]
245    Size2048x1080,
246    /// 3840x2160 resolution (4K)
247    #[serde(rename = "3840x2160")]
248    Size3840x2160,
249}
250
251#[derive(Debug, Clone, Serialize)]
252#[serde(rename_all = "lowercase")]
253pub enum Fps {
254    /// 30 frames per second
255    Fps30,
256    /// 60 frames per second
257    Fps60,
258}
259
260#[derive(Debug, Clone, Serialize)]
261#[serde(rename_all = "lowercase")]
262pub enum VideoDuration {
263    /// 5 seconds duration
264    Duration5,
265    /// 10 seconds duration
266    Duration10,
267}