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