Skip to main content

openrouter/types/
video.rs

1//! Types for the asynchronous video-generation endpoints
2//! (`POST /videos`, `GET /videos/{job_id}`, `GET /videos/{job_id}/content`,
3//! `GET /videos/models`).
4//!
5//! Shapes mirror the Go SDK (`videos_models.go`).
6
7use std::collections::BTreeMap;
8
9use bytes::Bytes;
10use serde::{Deserialize, Serialize};
11use serde_json::Value;
12
13/// Supported aspect ratios for video generation.
14#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
15pub enum VideoAspectRatio {
16    /// 16:9 widescreen.
17    #[serde(rename = "16:9")]
18    R16x9,
19    /// 9:16 vertical.
20    #[serde(rename = "9:16")]
21    R9x16,
22    /// 1:1 square.
23    #[serde(rename = "1:1")]
24    R1x1,
25    /// 4:3 traditional.
26    #[serde(rename = "4:3")]
27    R4x3,
28    /// 3:4 portrait.
29    #[serde(rename = "3:4")]
30    R3x4,
31    /// 21:9 ultra-wide.
32    #[serde(rename = "21:9")]
33    R21x9,
34    /// 9:21 ultra-tall.
35    #[serde(rename = "9:21")]
36    R9x21,
37}
38
39/// Supported output resolutions.
40#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
41pub enum VideoResolution {
42    /// 480p.
43    #[serde(rename = "480p")]
44    P480,
45    /// 720p.
46    #[serde(rename = "720p")]
47    P720,
48    /// 1080p.
49    #[serde(rename = "1080p")]
50    P1080,
51    /// 1K.
52    #[serde(rename = "1K")]
53    K1,
54    /// 2K.
55    #[serde(rename = "2K")]
56    K2,
57    /// 4K.
58    #[serde(rename = "4K")]
59    K4,
60}
61
62/// Frame role for a supplied reference image.
63#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
64#[serde(rename_all = "snake_case")]
65pub enum VideoFrameType {
66    /// Use the image as the first frame.
67    FirstFrame,
68    /// Use the image as the last frame.
69    LastFrame,
70}
71
72/// Lifecycle status of a video generation job. Terminal values:
73/// [`VideoStatus::Completed`], [`VideoStatus::Failed`],
74/// [`VideoStatus::Cancelled`], [`VideoStatus::Expired`].
75#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
76#[serde(rename_all = "snake_case")]
77pub enum VideoStatus {
78    /// Queued, not yet started.
79    #[default]
80    Pending,
81    /// Currently generating.
82    InProgress,
83    /// Successfully completed.
84    Completed,
85    /// Generation failed.
86    Failed,
87    /// Cancelled by the caller.
88    Cancelled,
89    /// Output expired before retrieval.
90    Expired,
91}
92
93impl VideoStatus {
94    /// `true` once the job will never advance further.
95    pub fn is_terminal(self) -> bool {
96        matches!(
97            self,
98            VideoStatus::Completed
99                | VideoStatus::Failed
100                | VideoStatus::Cancelled
101                | VideoStatus::Expired
102        )
103    }
104}
105
106/// A URL wrapper used in image inputs.
107#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
108pub struct VideoImageUrl {
109    /// HTTPS URL or data URL pointing at the image bytes.
110    pub url: String,
111}
112
113/// Reference image used to guide generation.
114#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
115pub struct VideoContentPartImage {
116    /// Image reference.
117    pub image_url: VideoImageUrl,
118    /// Always `"image_url"`.
119    #[serde(rename = "type")]
120    pub kind: String,
121}
122
123impl VideoContentPartImage {
124    /// Construct a reference image from a URL.
125    pub fn new(url: impl Into<String>) -> Self {
126        Self {
127            image_url: VideoImageUrl { url: url.into() },
128            kind: "image_url".into(),
129        }
130    }
131}
132
133/// First- or last-frame reference image.
134#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
135pub struct VideoFrameImage {
136    /// Image reference.
137    pub image_url: VideoImageUrl,
138    /// Always `"image_url"`.
139    #[serde(rename = "type")]
140    pub kind: String,
141    /// Which frame this image represents.
142    pub frame_type: VideoFrameType,
143}
144
145impl VideoFrameImage {
146    /// Construct a frame-image reference.
147    pub fn new(url: impl Into<String>, frame_type: VideoFrameType) -> Self {
148        Self {
149            image_url: VideoImageUrl { url: url.into() },
150            kind: "image_url".into(),
151            frame_type,
152        }
153    }
154}
155
156/// Provider-specific passthrough options for video generation.
157#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
158pub struct VideoProvider {
159    /// Provider-keyed options. The map under the chosen provider's slug
160    /// is spread into the upstream request body.
161    #[serde(skip_serializing_if = "Option::is_none", default)]
162    pub options: Option<BTreeMap<String, BTreeMap<String, Value>>>,
163}
164
165/// Request body for [`crate::Client::create_video`].
166#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
167pub struct VideoGenerationRequest {
168    /// Video model id.
169    pub model: String,
170    /// Prompt describing the desired video.
171    pub prompt: String,
172    /// Output aspect ratio.
173    #[serde(skip_serializing_if = "Option::is_none", default)]
174    pub aspect_ratio: Option<VideoAspectRatio>,
175    /// Optional webhook URL invoked when the job completes.
176    #[serde(skip_serializing_if = "Option::is_none", default)]
177    pub callback_url: Option<String>,
178    /// Output duration in seconds.
179    #[serde(skip_serializing_if = "Option::is_none", default)]
180    pub duration: Option<u32>,
181    /// First/last-frame reference images.
182    #[serde(skip_serializing_if = "Vec::is_empty", default)]
183    pub frame_images: Vec<VideoFrameImage>,
184    /// Whether to generate an audio track.
185    #[serde(skip_serializing_if = "Option::is_none", default)]
186    pub generate_audio: Option<bool>,
187    /// Free-form reference images.
188    #[serde(skip_serializing_if = "Vec::is_empty", default)]
189    pub input_references: Vec<VideoContentPartImage>,
190    /// Provider-specific passthrough options.
191    #[serde(skip_serializing_if = "Option::is_none", default)]
192    pub provider: Option<VideoProvider>,
193    /// Output resolution.
194    #[serde(skip_serializing_if = "Option::is_none", default)]
195    pub resolution: Option<VideoResolution>,
196    /// Sampling seed for reproducibility.
197    #[serde(skip_serializing_if = "Option::is_none", default)]
198    pub seed: Option<i64>,
199    /// Exact pixel dimensions as `"WIDTHxHEIGHT"`.
200    #[serde(skip_serializing_if = "Option::is_none", default)]
201    pub size: Option<String>,
202}
203
204/// Cost / BYOK information reported on completion.
205#[derive(Clone, Copy, Debug, Default, PartialEq, Serialize, Deserialize)]
206pub struct VideoGenerationUsage {
207    /// USD cost of the request.
208    #[serde(default)]
209    pub cost: Option<f64>,
210    /// True when served via a BYOK provider key.
211    #[serde(default)]
212    pub is_byok: bool,
213}
214
215/// Response from `POST /videos` and `GET /videos/{job_id}`.
216#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
217pub struct VideoGenerationResponse {
218    /// Job identifier.
219    #[serde(default)]
220    pub id: String,
221    /// URL to poll for status updates.
222    #[serde(default)]
223    pub polling_url: String,
224    /// Lifecycle status.
225    pub status: VideoStatus,
226    /// Error message, when [`Self::status`] is [`VideoStatus::Failed`].
227    #[serde(default)]
228    pub error: String,
229    /// Underlying generation id (provider-side).
230    #[serde(default)]
231    pub generation_id: String,
232    /// Direct (unsigned) URLs for downloading the result.
233    #[serde(default)]
234    pub unsigned_urls: Vec<String>,
235    /// Cost / BYOK information, when reported.
236    #[serde(default)]
237    pub usage: Option<VideoGenerationUsage>,
238}
239
240/// Response from `GET /videos/{job_id}/content`: raw video bytes + the
241/// upstream `Content-Type` (typically `application/octet-stream`).
242#[derive(Clone, Debug)]
243pub struct VideoContentResponse {
244    /// Raw video bytes.
245    pub content: Bytes,
246    /// Upstream `Content-Type` header, when present.
247    pub content_type: Option<String>,
248}
249
250/// A single video-generation model from `GET /videos/models`.
251#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
252pub struct VideoModel {
253    /// Model id.
254    #[serde(default)]
255    pub id: String,
256    /// Display name.
257    #[serde(default)]
258    pub name: String,
259    /// Stable canonical slug.
260    #[serde(default)]
261    pub canonical_slug: String,
262    /// Unix-seconds creation timestamp.
263    #[serde(default)]
264    pub created: i64,
265    /// Long description.
266    #[serde(default)]
267    pub description: String,
268    /// Hugging Face model id, when published.
269    #[serde(default)]
270    pub hugging_face_id: Option<String>,
271    /// Allowed provider passthrough parameter names.
272    #[serde(default)]
273    pub allowed_passthrough_parameters: Vec<String>,
274    /// Whether audio generation is supported.
275    #[serde(default)]
276    pub generate_audio: Option<bool>,
277    /// Whether sampling seeds are supported.
278    #[serde(default)]
279    pub seed: Option<bool>,
280    /// Pricing SKU table keyed by SKU name.
281    #[serde(default)]
282    pub pricing_skus: BTreeMap<String, String>,
283    /// Supported aspect ratios.
284    #[serde(default)]
285    pub supported_aspect_ratios: Vec<VideoAspectRatio>,
286    /// Supported durations (seconds).
287    #[serde(default)]
288    pub supported_durations: Vec<u32>,
289    /// Supported frame-image roles.
290    #[serde(default)]
291    pub supported_frame_images: Vec<VideoFrameType>,
292    /// Supported output resolutions.
293    #[serde(default)]
294    pub supported_resolutions: Vec<VideoResolution>,
295    /// Supported exact pixel sizes.
296    #[serde(default)]
297    pub supported_sizes: Vec<String>,
298}
299
300/// Response from `GET /videos/models`.
301#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
302pub struct VideoModelsResponse {
303    /// Model rows.
304    #[serde(default)]
305    pub data: Vec<VideoModel>,
306}