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 StudioVideoRequest {
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/// Request body for video translation.
131#[derive(Debug, Clone, Serialize, Default)]
132pub struct TranslateRequest {
133    /// URL of the video to translate.
134    #[serde(skip_serializing_if = "Option::is_none")]
135    pub video_url: Option<String>,
136
137    /// Base64-encoded video (alternative to URL).
138    #[serde(skip_serializing_if = "Option::is_none")]
139    pub video_base64: Option<String>,
140
141    /// Target language code.
142    pub target_language: String,
143
144    /// Source language code (auto-detected if omitted).
145    #[serde(skip_serializing_if = "Option::is_none")]
146    pub source_language: Option<String>,
147}
148
149// ---------------------------------------------------------------------------
150// HeyGen Photo Avatar
151// ---------------------------------------------------------------------------
152
153/// Request body for creating a photo avatar video.
154#[derive(Debug, Clone, Serialize, Default)]
155pub struct PhotoAvatarRequest {
156    /// Base64-encoded photo.
157    pub photo_base64: String,
158
159    /// Script text for the avatar to speak.
160    pub script: String,
161
162    /// Voice ID.
163    #[serde(skip_serializing_if = "Option::is_none")]
164    pub voice_id: Option<String>,
165
166    /// Aspect ratio.
167    #[serde(skip_serializing_if = "Option::is_none")]
168    pub aspect_ratio: Option<String>,
169}
170
171// ---------------------------------------------------------------------------
172// HeyGen Digital Twin
173// ---------------------------------------------------------------------------
174
175/// Request body for digital twin video generation.
176#[derive(Debug, Clone, Serialize, Default)]
177pub struct DigitalTwinRequest {
178    /// Digital twin / avatar ID.
179    pub avatar_id: String,
180
181    /// Script text.
182    pub script: String,
183
184    /// Voice ID (uses twin's default voice if omitted).
185    #[serde(skip_serializing_if = "Option::is_none")]
186    pub voice_id: Option<String>,
187
188    /// Aspect ratio.
189    #[serde(skip_serializing_if = "Option::is_none")]
190    pub aspect_ratio: Option<String>,
191}
192
193// ---------------------------------------------------------------------------
194// HeyGen Avatars
195// ---------------------------------------------------------------------------
196
197/// A HeyGen avatar.
198#[derive(Debug, Clone, Deserialize)]
199pub struct Avatar {
200    /// Avatar identifier.
201    pub avatar_id: String,
202
203    /// Avatar name.
204    #[serde(default)]
205    pub name: Option<String>,
206
207    /// Avatar gender.
208    #[serde(default)]
209    pub gender: Option<String>,
210
211    /// Preview image URL.
212    #[serde(default)]
213    pub preview_url: Option<String>,
214
215    /// Additional fields.
216    #[serde(flatten)]
217    pub extra: HashMap<String, serde_json::Value>,
218}
219
220/// Response from listing HeyGen avatars.
221#[derive(Debug, Clone, Deserialize)]
222pub struct AvatarsResponse {
223    pub avatars: Vec<Avatar>,
224}
225
226// ---------------------------------------------------------------------------
227// HeyGen Templates
228// ---------------------------------------------------------------------------
229
230/// A HeyGen video template.
231#[derive(Debug, Clone, Deserialize)]
232pub struct VideoTemplate {
233    /// Template identifier.
234    pub template_id: String,
235
236    /// Template name.
237    #[serde(default)]
238    pub name: Option<String>,
239
240    /// Preview image URL.
241    #[serde(default)]
242    pub preview_url: Option<String>,
243
244    /// Additional fields.
245    #[serde(flatten)]
246    pub extra: HashMap<String, serde_json::Value>,
247}
248
249/// Response from listing HeyGen video templates.
250#[derive(Debug, Clone, Deserialize)]
251pub struct VideoTemplatesResponse {
252    pub templates: Vec<VideoTemplate>,
253}
254
255// ---------------------------------------------------------------------------
256// HeyGen Voices
257// ---------------------------------------------------------------------------
258
259/// A HeyGen voice.
260#[derive(Debug, Clone, Deserialize)]
261pub struct HeyGenVoice {
262    /// Voice identifier.
263    pub voice_id: String,
264
265    /// Voice name.
266    #[serde(default)]
267    pub name: Option<String>,
268
269    /// Language.
270    #[serde(default)]
271    pub language: Option<String>,
272
273    /// Gender.
274    #[serde(default)]
275    pub gender: Option<String>,
276
277    /// Additional fields.
278    #[serde(flatten)]
279    pub extra: HashMap<String, serde_json::Value>,
280}
281
282/// Response from listing HeyGen voices.
283#[derive(Debug, Clone, Deserialize)]
284pub struct HeyGenVoicesResponse {
285    pub voices: Vec<HeyGenVoice>,
286}
287
288// ---------------------------------------------------------------------------
289// Client impl
290// ---------------------------------------------------------------------------
291
292impl Client {
293    /// Generates a video from a text prompt.
294    ///
295    /// Video generation is slow (30s-5min). For production use, consider submitting
296    /// via the Jobs API instead.
297    pub async fn generate_video(&self, req: &VideoRequest) -> Result<VideoResponse> {
298        let (mut resp, meta) = self
299            .post_json::<VideoRequest, VideoResponse>("/qai/v1/video/generate", req)
300            .await?;
301        if resp.cost_ticks == 0 {
302            resp.cost_ticks = meta.cost_ticks;
303        }
304        if resp.request_id.is_empty() {
305            resp.request_id = meta.request_id;
306        }
307        Ok(resp)
308    }
309
310    /// Creates a HeyGen studio video from clips.
311    pub async fn video_studio(&self, req: &StudioVideoRequest) -> Result<JobResponse> {
312        let (resp, _meta) = self
313            .post_json::<StudioVideoRequest, JobResponse>("/qai/v1/video/studio", req)
314            .await?;
315        Ok(resp)
316    }
317
318    /// Translates a video into another language (HeyGen).
319    pub async fn video_translate(&self, req: &TranslateRequest) -> Result<JobResponse> {
320        let (resp, _meta) = self
321            .post_json::<TranslateRequest, JobResponse>("/qai/v1/video/translate", req)
322            .await?;
323        Ok(resp)
324    }
325
326    /// Creates a video from a photo avatar (HeyGen).
327    pub async fn video_photo_avatar(&self, req: &PhotoAvatarRequest) -> Result<JobResponse> {
328        let (resp, _meta) = self
329            .post_json::<PhotoAvatarRequest, JobResponse>("/qai/v1/video/photo-avatar", req)
330            .await?;
331        Ok(resp)
332    }
333
334    /// Creates a video from a digital twin avatar (HeyGen).
335    pub async fn video_digital_twin(&self, req: &DigitalTwinRequest) -> Result<JobResponse> {
336        let (resp, _meta) = self
337            .post_json::<DigitalTwinRequest, JobResponse>("/qai/v1/video/digital-twin", req)
338            .await?;
339        Ok(resp)
340    }
341
342    /// Lists available HeyGen avatars.
343    pub async fn video_avatars(&self) -> Result<AvatarsResponse> {
344        let (resp, _meta) = self
345            .get_json::<AvatarsResponse>("/qai/v1/video/avatars")
346            .await?;
347        Ok(resp)
348    }
349
350    /// Lists available HeyGen video templates.
351    pub async fn video_templates(&self) -> Result<VideoTemplatesResponse> {
352        let (resp, _meta) = self
353            .get_json::<VideoTemplatesResponse>("/qai/v1/video/templates")
354            .await?;
355        Ok(resp)
356    }
357
358    /// Lists available HeyGen voices.
359    pub async fn video_heygen_voices(&self) -> Result<HeyGenVoicesResponse> {
360        let (resp, _meta) = self
361            .get_json::<HeyGenVoicesResponse>("/qai/v1/video/heygen-voices")
362            .await?;
363        Ok(resp)
364    }
365}