1use std::collections::HashMap;
2
3use serde::{Deserialize, Serialize};
4
5use crate::client::Client;
6use crate::error::Result;
7
8#[derive(Debug, Clone, Serialize, Default)]
10pub struct VideoRequest {
11 pub model: String,
13
14 pub prompt: String,
16
17 #[serde(skip_serializing_if = "Option::is_none")]
19 pub duration_seconds: Option<i32>,
20
21 #[serde(skip_serializing_if = "Option::is_none")]
23 pub aspect_ratio: Option<String>,
24}
25
26#[derive(Debug, Clone, Deserialize)]
28pub struct VideoResponse {
29 pub videos: Vec<GeneratedVideo>,
31
32 pub model: String,
34
35 #[serde(default)]
37 pub cost_ticks: i64,
38
39 #[serde(default)]
41 pub request_id: String,
42}
43
44#[derive(Debug, Clone, Deserialize)]
46pub struct GeneratedVideo {
47 pub base64: String,
49
50 pub format: String,
52
53 pub size_bytes: i64,
55
56 pub index: i32,
58}
59
60#[derive(Debug, Clone, Deserialize)]
66pub struct JobResponse {
67 pub job_id: String,
69
70 #[serde(default)]
72 pub status: String,
73
74 #[serde(default)]
76 pub cost_ticks: i64,
77
78 #[serde(flatten)]
80 pub extra: HashMap<String, serde_json::Value>,
81}
82
83#[derive(Debug, Clone, Serialize, Deserialize, Default)]
89pub struct StudioClip {
90 #[serde(skip_serializing_if = "Option::is_none")]
92 pub avatar_id: Option<String>,
93
94 #[serde(skip_serializing_if = "Option::is_none")]
96 pub voice_id: Option<String>,
97
98 #[serde(skip_serializing_if = "Option::is_none")]
100 pub script: Option<String>,
101
102 #[serde(skip_serializing_if = "Option::is_none")]
104 pub background: Option<serde_json::Value>,
105}
106
107#[derive(Debug, Clone, Serialize, Default)]
109pub struct StudioVideoRequest {
110 #[serde(skip_serializing_if = "Option::is_none")]
112 pub title: Option<String>,
113
114 pub clips: Vec<StudioClip>,
116
117 #[serde(skip_serializing_if = "Option::is_none")]
119 pub dimension: Option<String>,
120
121 #[serde(skip_serializing_if = "Option::is_none")]
123 pub aspect_ratio: Option<String>,
124}
125
126#[derive(Debug, Clone, Serialize, Default)]
132pub struct TranslateRequest {
133 #[serde(skip_serializing_if = "Option::is_none")]
135 pub video_url: Option<String>,
136
137 #[serde(skip_serializing_if = "Option::is_none")]
139 pub video_base64: Option<String>,
140
141 pub target_language: String,
143
144 #[serde(skip_serializing_if = "Option::is_none")]
146 pub source_language: Option<String>,
147}
148
149#[derive(Debug, Clone, Serialize, Default)]
155pub struct PhotoAvatarRequest {
156 pub photo_base64: String,
158
159 pub script: String,
161
162 #[serde(skip_serializing_if = "Option::is_none")]
164 pub voice_id: Option<String>,
165
166 #[serde(skip_serializing_if = "Option::is_none")]
168 pub aspect_ratio: Option<String>,
169}
170
171#[derive(Debug, Clone, Serialize, Default)]
177pub struct DigitalTwinRequest {
178 pub avatar_id: String,
180
181 pub script: String,
183
184 #[serde(skip_serializing_if = "Option::is_none")]
186 pub voice_id: Option<String>,
187
188 #[serde(skip_serializing_if = "Option::is_none")]
190 pub aspect_ratio: Option<String>,
191}
192
193#[derive(Debug, Clone, Deserialize)]
199pub struct Avatar {
200 pub avatar_id: String,
202
203 #[serde(default)]
205 pub name: Option<String>,
206
207 #[serde(default)]
209 pub gender: Option<String>,
210
211 #[serde(default)]
213 pub preview_url: Option<String>,
214
215 #[serde(flatten)]
217 pub extra: HashMap<String, serde_json::Value>,
218}
219
220#[derive(Debug, Clone, Deserialize)]
222pub struct AvatarsResponse {
223 pub avatars: Vec<Avatar>,
224}
225
226#[derive(Debug, Clone, Deserialize)]
232pub struct VideoTemplate {
233 pub template_id: String,
235
236 #[serde(default)]
238 pub name: Option<String>,
239
240 #[serde(default)]
242 pub preview_url: Option<String>,
243
244 #[serde(flatten)]
246 pub extra: HashMap<String, serde_json::Value>,
247}
248
249#[derive(Debug, Clone, Deserialize)]
251pub struct VideoTemplatesResponse {
252 pub templates: Vec<VideoTemplate>,
253}
254
255#[derive(Debug, Clone, Deserialize)]
261pub struct HeyGenVoice {
262 pub voice_id: String,
264
265 #[serde(default)]
267 pub name: Option<String>,
268
269 #[serde(default)]
271 pub language: Option<String>,
272
273 #[serde(default)]
275 pub gender: Option<String>,
276
277 #[serde(flatten)]
279 pub extra: HashMap<String, serde_json::Value>,
280}
281
282#[derive(Debug, Clone, Deserialize)]
284pub struct HeyGenVoicesResponse {
285 pub voices: Vec<HeyGenVoice>,
286}
287
288impl Client {
293 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 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 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 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 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 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 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 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}