1use crate::client::XaiClient;
4use crate::models::videos::{
5 Video, VideoEditRequest, VideoGenerationRequest, VideoGenerationResponse, VideoResponseFormat,
6};
7use crate::{Error, Result};
8
9#[derive(Debug, Clone)]
11pub struct VideosApi {
12 client: XaiClient,
13}
14
15impl VideosApi {
16 pub(crate) fn new(client: XaiClient) -> Self {
17 Self { client }
18 }
19
20 pub fn generate(
35 &self,
36 model: impl Into<String>,
37 prompt: impl Into<String>,
38 ) -> VideoGenerationBuilder {
39 VideoGenerationBuilder::new(self.client.clone(), model.into(), prompt.into())
40 }
41
42 pub fn edit(
44 &self,
45 model: impl Into<String>,
46 video: impl Into<String>,
47 prompt: impl Into<String>,
48 ) -> VideoEditBuilder {
49 VideoEditBuilder::new(
50 self.client.clone(),
51 model.into(),
52 video.into(),
53 prompt.into(),
54 )
55 }
56
57 pub async fn get(&self, video_id: &str) -> Result<Video> {
59 let id = XaiClient::encode_path(video_id);
60 let url = format!("{}/videos/{}", self.client.base_url(), id);
61
62 let response = self.client.send(self.client.http().get(&url)).await?;
63 if !response.status().is_success() {
64 return Err(Error::from_response(response).await);
65 }
66 Ok(response.json().await?)
67 }
68}
69
70#[derive(Debug)]
72pub struct VideoGenerationBuilder {
73 client: XaiClient,
74 request: VideoGenerationRequest,
75}
76
77impl VideoGenerationBuilder {
78 fn new(client: XaiClient, model: String, prompt: String) -> Self {
79 Self {
80 client,
81 request: VideoGenerationRequest::new(model, prompt),
82 }
83 }
84
85 pub fn duration_seconds(mut self, duration_seconds: u32) -> Self {
87 self.request = self.request.duration_seconds(duration_seconds);
88 self
89 }
90
91 pub fn n(mut self, n: u32) -> Self {
93 self.request.n = Some(n.clamp(1, 4));
94 self
95 }
96
97 pub fn response_format(mut self, format: VideoResponseFormat) -> Self {
99 self.request = self.request.response_format(format);
100 self
101 }
102
103 pub fn url_format(self) -> Self {
105 self.response_format(VideoResponseFormat::Url)
106 }
107
108 pub fn base64_format(self) -> Self {
110 self.response_format(VideoResponseFormat::B64Json)
111 }
112
113 pub async fn send(self) -> Result<VideoGenerationResponse> {
115 let url = format!("{}/videos/generations", self.client.base_url());
116 let response = self
117 .client
118 .send(self.client.http().post(&url).json(&self.request))
119 .await?;
120
121 if !response.status().is_success() {
122 return Err(Error::from_response(response).await);
123 }
124 Ok(response.json().await?)
125 }
126}
127
128#[derive(Debug)]
130pub struct VideoEditBuilder {
131 client: XaiClient,
132 request: VideoEditRequest,
133}
134
135impl VideoEditBuilder {
136 fn new(client: XaiClient, model: String, video: String, prompt: String) -> Self {
137 Self {
138 client,
139 request: VideoEditRequest::new(model, video, prompt),
140 }
141 }
142
143 pub fn duration_seconds(mut self, duration_seconds: u32) -> Self {
145 self.request = self.request.duration_seconds(duration_seconds);
146 self
147 }
148
149 pub fn response_format(mut self, format: VideoResponseFormat) -> Self {
151 self.request = self.request.response_format(format);
152 self
153 }
154
155 pub fn url_format(self) -> Self {
157 self.response_format(VideoResponseFormat::Url)
158 }
159
160 pub fn base64_format(self) -> Self {
162 self.response_format(VideoResponseFormat::B64Json)
163 }
164
165 pub async fn send(self) -> Result<VideoGenerationResponse> {
167 let url = format!("{}/videos/edits", self.client.base_url());
168 let response = self
169 .client
170 .send(self.client.http().post(&url).json(&self.request))
171 .await?;
172
173 if !response.status().is_success() {
174 return Err(Error::from_response(response).await);
175 }
176 Ok(response.json().await?)
177 }
178}
179
180#[cfg(test)]
181mod tests {
182 use super::*;
183 use serde_json::json;
184 use wiremock::matchers::{method, path};
185 use wiremock::{Mock, MockServer, ResponseTemplate};
186
187 #[tokio::test]
188 async fn generate_forwards_request_body() {
189 let server = MockServer::start().await;
190
191 Mock::given(method("POST"))
192 .and(path("/videos/generations"))
193 .respond_with(move |req: &wiremock::Request| {
194 let body = serde_json::from_slice::<serde_json::Value>(&req.body).unwrap();
195 assert_eq!(body["model"], "grok-video");
196 assert_eq!(body["prompt"], "cat running");
197 assert_eq!(body["n"], 2);
198 ResponseTemplate::new(200).set_body_json(json!({
199 "created": 1700000000,
200 "data": [{"url": "https://example.com/video.mp4"}]
201 }))
202 })
203 .mount(&server)
204 .await;
205
206 let client = XaiClient::builder()
207 .api_key("test-key")
208 .base_url(server.uri())
209 .build()
210 .unwrap();
211
212 let response = client
213 .videos()
214 .generate("grok-video", "cat running")
215 .n(2)
216 .send()
217 .await
218 .unwrap();
219 assert_eq!(response.first_url(), Some("https://example.com/video.mp4"));
220 }
221
222 #[tokio::test]
223 async fn edit_forwards_request_body() {
224 let server = MockServer::start().await;
225
226 Mock::given(method("POST"))
227 .and(path("/videos/edits"))
228 .respond_with(move |req: &wiremock::Request| {
229 let body = serde_json::from_slice::<serde_json::Value>(&req.body).unwrap();
230 assert_eq!(body["model"], "grok-video");
231 assert_eq!(body["video"], "video-1");
232 assert_eq!(body["prompt"], "slow motion");
233 ResponseTemplate::new(200).set_body_json(json!({
234 "created": 1700000000,
235 "data": [{"url": "https://example.com/video-edit.mp4"}]
236 }))
237 })
238 .mount(&server)
239 .await;
240
241 let client = XaiClient::builder()
242 .api_key("test-key")
243 .base_url(server.uri())
244 .build()
245 .unwrap();
246
247 let response = client
248 .videos()
249 .edit("grok-video", "video-1", "slow motion")
250 .send()
251 .await
252 .unwrap();
253 assert_eq!(
254 response.first_url(),
255 Some("https://example.com/video-edit.mp4")
256 );
257 }
258
259 #[tokio::test]
260 async fn get_video_encodes_id() {
261 let server = MockServer::start().await;
262
263 Mock::given(method("GET"))
264 .and(path("/videos/video%2Fencoded"))
265 .respond_with(ResponseTemplate::new(200).set_body_json(json!({
266 "id": "video/encoded",
267 "status": "ready",
268 "url": "https://example.com/video.mp4"
269 })))
270 .mount(&server)
271 .await;
272
273 let client = XaiClient::builder()
274 .api_key("test-key")
275 .base_url(server.uri())
276 .build()
277 .unwrap();
278
279 let response = client.videos().get("video/encoded").await.unwrap();
280 assert_eq!(response.id, "video/encoded");
281 assert_eq!(
282 response.url.as_deref(),
283 Some("https://example.com/video.mp4")
284 );
285 }
286}