Skip to main content

quantum_sdk/
video.rs

1use std::collections::HashMap;
2
3use serde::{Deserialize, Serialize};
4
5use crate::client::Client;
6use crate::error::Result;
7
8/// Request body for video generation.
9#[derive(Debug, Clone, Serialize, Default)]
10pub struct VideoRequest {
11    /// Video generation model (e.g. "heygen", "grok-imagine-video", "sora-2", "veo-2").
12    pub model: String,
13
14    /// Describes the video to generate.
15    pub prompt: String,
16
17    /// Target video duration in seconds (default 8).
18    #[serde(skip_serializing_if = "Option::is_none")]
19    pub duration_seconds: Option<i32>,
20
21    /// Video aspect ratio (e.g. "16:9", "9:16").
22    #[serde(skip_serializing_if = "Option::is_none")]
23    pub aspect_ratio: Option<String>,
24}
25
26/// Response from video generation.
27#[derive(Debug, Clone, Deserialize)]
28pub struct VideoResponse {
29    /// Generated videos.
30    pub videos: Vec<GeneratedVideo>,
31
32    /// Model that generated the videos.
33    pub model: String,
34
35    /// Total cost in ticks.
36    #[serde(default)]
37    pub cost_ticks: i64,
38
39    /// Post-deduction credit balance in ticks (Receipt Pattern).
40    #[serde(default)]
41    pub balance_after: i64,
42
43    /// Unique request identifier.
44    #[serde(default)]
45    pub request_id: String,
46}
47
48/// A single generated video.
49#[derive(Debug, Clone, Deserialize)]
50pub struct GeneratedVideo {
51    /// Base64-encoded video data (or a URL).
52    pub base64: String,
53
54    /// Video format (e.g. "mp4").
55    pub format: String,
56
57    /// Video file size.
58    pub size_bytes: i64,
59
60    /// Video index within the batch.
61    pub index: i32,
62}
63
64// ---------------------------------------------------------------------------
65// Job response (shared by HeyGen endpoints)
66// ---------------------------------------------------------------------------
67
68/// Response from async video job submission.
69#[derive(Debug, Clone, Deserialize)]
70pub struct JobResponse {
71    /// Job identifier for polling status.
72    pub job_id: String,
73
74    /// Current status.
75    #[serde(default)]
76    pub status: String,
77
78    /// Total cost in ticks (may be 0 until job completes).
79    #[serde(default)]
80    pub cost_ticks: i64,
81
82    /// Additional response fields.
83    #[serde(flatten)]
84    pub extra: HashMap<String, serde_json::Value>,
85}
86
87// ---------------------------------------------------------------------------
88// HeyGen Studio
89// ---------------------------------------------------------------------------
90
91/// A clip in a studio video.
92#[derive(Debug, Clone, Serialize, Deserialize, Default)]
93pub struct StudioClip {
94    /// Avatar ID.
95    #[serde(skip_serializing_if = "Option::is_none")]
96    pub avatar_id: Option<String>,
97
98    /// Voice ID.
99    #[serde(skip_serializing_if = "Option::is_none")]
100    pub voice_id: Option<String>,
101
102    /// Script text for this clip.
103    #[serde(skip_serializing_if = "Option::is_none")]
104    pub script: Option<String>,
105
106    /// Background settings.
107    #[serde(skip_serializing_if = "Option::is_none")]
108    pub background: Option<serde_json::Value>,
109}
110
111/// Request body for HeyGen studio video creation.
112#[derive(Debug, Clone, Serialize, Default)]
113pub struct VideoStudioRequest {
114    /// Video title.
115    #[serde(skip_serializing_if = "Option::is_none")]
116    pub title: Option<String>,
117
118    /// Video clips.
119    pub clips: Vec<StudioClip>,
120
121    /// Video dimensions.
122    #[serde(skip_serializing_if = "Option::is_none")]
123    pub dimension: Option<String>,
124
125    /// Aspect ratio.
126    #[serde(skip_serializing_if = "Option::is_none")]
127    pub aspect_ratio: Option<String>,
128}
129
130// ---------------------------------------------------------------------------
131// HeyGen Translate
132// ---------------------------------------------------------------------------
133
134/// Backwards-compatible alias.
135pub type StudioVideoRequest = VideoStudioRequest;
136
137/// Request body for video translation.
138#[derive(Debug, Clone, Serialize, Default)]
139pub struct VideoTranslateRequest {
140    /// URL of the video to translate.
141    #[serde(skip_serializing_if = "Option::is_none")]
142    pub video_url: Option<String>,
143
144    /// Base64-encoded video (alternative to URL).
145    #[serde(skip_serializing_if = "Option::is_none")]
146    pub video_base64: Option<String>,
147
148    /// Target language code.
149    pub target_language: String,
150
151    /// Source language code (auto-detected if omitted).
152    #[serde(skip_serializing_if = "Option::is_none")]
153    pub source_language: Option<String>,
154}
155
156/// Backwards-compatible alias.
157pub type TranslateRequest = VideoTranslateRequest;
158
159// ---------------------------------------------------------------------------
160// HeyGen Photo Avatar
161// ---------------------------------------------------------------------------
162
163/// Request body for creating a photo avatar video.
164#[derive(Debug, Clone, Serialize, Default)]
165pub struct PhotoAvatarRequest {
166    /// Base64-encoded photo.
167    pub photo_base64: String,
168
169    /// Script text for the avatar to speak.
170    pub script: String,
171
172    /// Voice ID.
173    #[serde(skip_serializing_if = "Option::is_none")]
174    pub voice_id: Option<String>,
175
176    /// Aspect ratio.
177    #[serde(skip_serializing_if = "Option::is_none")]
178    pub aspect_ratio: Option<String>,
179}
180
181// ---------------------------------------------------------------------------
182// HeyGen Digital Twin
183// ---------------------------------------------------------------------------
184
185/// Request body for digital twin video generation.
186#[derive(Debug, Clone, Serialize, Default)]
187pub struct DigitalTwinRequest {
188    /// Digital twin / avatar ID.
189    pub avatar_id: String,
190
191    /// Script text.
192    pub script: String,
193
194    /// Voice ID (uses twin's default voice if omitted).
195    #[serde(skip_serializing_if = "Option::is_none")]
196    pub voice_id: Option<String>,
197
198    /// Aspect ratio.
199    #[serde(skip_serializing_if = "Option::is_none")]
200    pub aspect_ratio: Option<String>,
201}
202
203// ---------------------------------------------------------------------------
204// HeyGen Avatars
205// ---------------------------------------------------------------------------
206
207/// A HeyGen avatar.
208#[derive(Debug, Clone, Deserialize)]
209pub struct Avatar {
210    /// Avatar identifier.
211    pub avatar_id: String,
212
213    /// Avatar name.
214    #[serde(default)]
215    pub name: Option<String>,
216
217    /// Avatar gender.
218    #[serde(default)]
219    pub gender: Option<String>,
220
221    /// Preview image URL.
222    #[serde(default)]
223    pub preview_url: Option<String>,
224
225    /// Additional fields.
226    #[serde(flatten)]
227    pub extra: HashMap<String, serde_json::Value>,
228}
229
230/// Response from listing HeyGen avatars.
231#[derive(Debug, Clone, Deserialize)]
232pub struct AvatarsResponse {
233    pub avatars: Vec<Avatar>,
234}
235
236// ---------------------------------------------------------------------------
237// HeyGen Templates
238// ---------------------------------------------------------------------------
239
240/// A HeyGen video template.
241#[derive(Debug, Clone, Deserialize)]
242pub struct VideoTemplate {
243    /// Template identifier.
244    pub template_id: String,
245
246    /// Template name.
247    #[serde(default)]
248    pub name: Option<String>,
249
250    /// Preview image URL.
251    #[serde(default)]
252    pub preview_url: Option<String>,
253
254    /// Additional fields.
255    #[serde(flatten)]
256    pub extra: HashMap<String, serde_json::Value>,
257}
258
259/// Response from listing HeyGen video templates.
260#[derive(Debug, Clone, Deserialize)]
261pub struct VideoTemplatesResponse {
262    pub templates: Vec<VideoTemplate>,
263}
264
265// ---------------------------------------------------------------------------
266// HeyGen typed responses (with request_id)
267// ---------------------------------------------------------------------------
268
269/// Response from listing HeyGen avatars (includes request_id).
270#[derive(Debug, Clone, Deserialize)]
271pub struct HeyGenAvatarsResponse {
272    /// Available avatars (raw JSON items).
273    #[serde(default)]
274    pub avatars: Vec<serde_json::Value>,
275
276    /// Unique request identifier.
277    #[serde(default)]
278    pub request_id: String,
279}
280
281/// Response from listing HeyGen templates (includes request_id).
282#[derive(Debug, Clone, Deserialize)]
283pub struct HeyGenTemplatesResponse {
284    /// Available templates (raw JSON items).
285    #[serde(default)]
286    pub templates: Vec<serde_json::Value>,
287
288    /// Unique request identifier.
289    #[serde(default)]
290    pub request_id: String,
291}
292
293// ---------------------------------------------------------------------------
294// HeyGen Voices
295// ---------------------------------------------------------------------------
296
297/// A HeyGen voice.
298#[derive(Debug, Clone, Deserialize)]
299pub struct HeyGenVoice {
300    /// Voice identifier.
301    pub voice_id: String,
302
303    /// Voice name.
304    #[serde(default)]
305    pub name: Option<String>,
306
307    /// Language.
308    #[serde(default)]
309    pub language: Option<String>,
310
311    /// Gender.
312    #[serde(default)]
313    pub gender: Option<String>,
314
315    /// Additional fields.
316    #[serde(flatten)]
317    pub extra: HashMap<String, serde_json::Value>,
318}
319
320/// Response from listing HeyGen voices.
321#[derive(Debug, Clone, Deserialize)]
322pub struct HeyGenVoicesResponse {
323    pub voices: Vec<HeyGenVoice>,
324}
325
326// ---------------------------------------------------------------------------
327// Client impl
328// ---------------------------------------------------------------------------
329
330impl Client {
331    /// Generates a video from a text prompt.
332    ///
333    /// Video generation is slow (30s-5min). For production use, consider submitting
334    /// via the Jobs API instead.
335    pub async fn generate_video(&self, req: &VideoRequest) -> Result<VideoResponse> {
336        let (mut resp, meta) = self
337            .post_json::<VideoRequest, VideoResponse>("/qai/v1/video/generate", req)
338            .await?;
339        if resp.cost_ticks == 0 {
340            resp.cost_ticks = meta.cost_ticks;
341        }
342        if resp.request_id.is_empty() {
343            resp.request_id = meta.request_id;
344        }
345        Ok(resp)
346    }
347
348    /// Creates a HeyGen studio video from clips.
349    pub async fn video_studio(&self, req: &VideoStudioRequest) -> Result<JobResponse> {
350        let (resp, _meta) = self
351            .post_json::<VideoStudioRequest, JobResponse>("/qai/v1/video/studio", req)
352            .await?;
353        Ok(resp)
354    }
355
356    /// Translates a video into another language (HeyGen).
357    pub async fn video_translate(&self, req: &VideoTranslateRequest) -> Result<JobResponse> {
358        let (resp, _meta) = self
359            .post_json::<VideoTranslateRequest, JobResponse>("/qai/v1/video/translate", req)
360            .await?;
361        Ok(resp)
362    }
363
364    /// Creates a video from a photo avatar (HeyGen).
365    pub async fn video_photo_avatar(&self, req: &PhotoAvatarRequest) -> Result<JobResponse> {
366        let (resp, _meta) = self
367            .post_json::<PhotoAvatarRequest, JobResponse>("/qai/v1/video/photo-avatar", req)
368            .await?;
369        Ok(resp)
370    }
371
372    /// Creates a video from a digital twin avatar (HeyGen).
373    pub async fn video_digital_twin(&self, req: &DigitalTwinRequest) -> Result<JobResponse> {
374        let (resp, _meta) = self
375            .post_json::<DigitalTwinRequest, JobResponse>("/qai/v1/video/digital-twin", req)
376            .await?;
377        Ok(resp)
378    }
379
380    /// Lists available HeyGen avatars.
381    pub async fn video_avatars(&self) -> Result<AvatarsResponse> {
382        let (resp, _meta) = self
383            .get_json::<AvatarsResponse>("/qai/v1/video/avatars")
384            .await?;
385        Ok(resp)
386    }
387
388    /// Lists available HeyGen video templates.
389    pub async fn video_templates(&self) -> Result<VideoTemplatesResponse> {
390        let (resp, _meta) = self
391            .get_json::<VideoTemplatesResponse>("/qai/v1/video/templates")
392            .await?;
393        Ok(resp)
394    }
395
396    /// Lists available HeyGen voices.
397    pub async fn video_heygen_voices(&self) -> Result<HeyGenVoicesResponse> {
398        let (resp, _meta) = self
399            .get_json::<HeyGenVoicesResponse>("/qai/v1/video/heygen-voices")
400            .await?;
401        Ok(resp)
402    }
403}