Video Generation
Video Generation
The unified asynchronous video API supports Google Veo/Gemini Omni and xAI Grok Imagine. Start a job, poll or wait for completion, then download through the same high-level surface. OpenAI's Videos API is retired; see OpenAI Sora for compatibility and migration behavior.
Basic Usage
import { Connector, VideoGeneration, Vendor } from 'oneringai';
import * as fs from 'fs/promises';
// Setup connector
Connector.create({
name: 'google-video',
vendor: Vendor.Google,
auth: { type: 'api_key', apiKey: process.env.GOOGLE_API_KEY! },
});
// Create video generator
const videoGen = VideoGeneration.create({ connector: 'google-video' });
// Start video generation (returns immediately with job ID)
const job = await videoGen.generate({
prompt: 'A cinematic shot of a sunrise over mountains with clouds rolling',
model: 'veo-3.1-lite-generate-preview',
duration: 8, // 8 seconds
});
console.log('Job started:', job.jobId);
console.log('Status:', job.status); // 'pending'
// Wait for completion (polls every 10 seconds, default 10-minute timeout)
const result = await videoGen.waitForCompletion(job.jobId);
// Download the completed video
const videoBuffer = await videoGen.download(job.jobId);
await fs.writeFile('./output.mp4', videoBuffer);
Understanding the Async Model
Video generation takes significant time (often minutes). The API uses an async job model:
// 1. Start generation - returns immediately
const job = await videoGen.generate({ prompt: '...', duration: 8 });
// job.status = 'pending'
// 2. Poll for status (optional - if you want progress updates)
const status = await videoGen.getStatus(job.jobId);
// status.status = 'processing', status.progress = 45
// 3. Wait for completion (blocks until done or timeout)
const result = await videoGen.waitForCompletion(job.jobId);
// result.status = 'completed'
// 4. Download the video
const buffer = await videoGen.download(job.jobId);
Or use the convenience method:
// Generate and wait in one call
const result = await videoGen.generateAndWait({
prompt: 'A butterfly flying through a garden',
duration: 4,
});
const buffer = await videoGen.download(result.jobId);
Video Response Structure
The API returns a VideoResponse object:
interface VideoResponse {
jobId: string; // Unique job identifier
status: 'pending' | 'processing' | 'completed' | 'failed';
created: number; // Unix timestamp
progress?: number; // 0-100 percentage (when processing)
video?: {
url?: string; // Download URL (if available)
b64_json?: string; // Base64-encoded video data (if provider returns inline)
duration?: number; // Actual duration in seconds
resolution?: string; // Actual resolution
format?: string; // 'mp4' typically
};
audio?: { // Separate audio track (if provider returns one)
url?: string;
b64_json?: string;
};
error?: string; // Error message if failed
}
Viewing Your Generated Video
After downloading, the video is a standard MP4 file that can be:
// Save to file
await fs.writeFile('./output.mp4', videoBuffer);
// Open with default player (Node.js)
import { exec } from 'child_process';
exec('open ./output.mp4'); // macOS
exec('xdg-open ./output.mp4'); // Linux
exec('start ./output.mp4'); // Windows
// Serve via web server
import express from 'express';
const app = express();
app.get('/video', (req, res) => {
res.setHeader('Content-Type', 'video/mp4');
res.send(videoBuffer);
});
// Convert to base64 for embedding
const base64 = videoBuffer.toString('base64');
const dataUrl = `data:video/mp4;base64,${base64}`;
OpenAI Sora
OpenAI shut down the Videos API, sora-2, sora-2-pro, and their snapshots
on 2026-09-24, with no replacement listed. See the
official retirement notice.
The provider class, model constants, and high-level methods remain exported for
source compatibility. Generation (including an omitted model), polling,
download, extension, remix, edit, and character operations reject locally with
ProviderError (status 410), without SDK requests, uploads, or file reads.
listModels() returns [], the provider advertises no video capabilities, and
cancel() / cancelJob() return false because no remote cancellation occurs.
Constructing a video generator with an OpenAI connector still permits discovery.
Sora registry records report isActive: false and lifecycle: 'retired'.
They retain historical capabilities/pricing for saved
configuration and cost lookup; active, deprecated, feature, and audio selectors
exclude them. There is no OpenAI video default or automatic cross-vendor fallback.
Configure a named Google or Grok connector explicitly to generate new videos.
Existing OpenAI job IDs and credentials cannot be transferred to these providers.
Google Veo
// Setup Google connector
Connector.create({
name: 'google',
vendor: Vendor.Google,
auth: { type: 'api_key', apiKey: process.env.GOOGLE_API_KEY! },
});
const googleVideo = VideoGeneration.create({ connector: 'google' });
// Veo 3.1 Lite is the current high-level Google default.
const lite = await googleVideo.generate({
prompt: 'A colorful butterfly landing on a flower',
model: 'veo-3.1-lite-generate-preview',
duration: 6,
resolution: '1080p',
vendorOptions: {
negativePrompt: 'blurry, low quality', // What to avoid
},
});
// Veo 3.1 Fast (with audio support)
const veo3 = await googleVideo.generate({
prompt: 'A thunderstorm over a city with lightning',
model: 'veo-3.1-fast-generate-preview',
duration: 8,
vendorOptions: {
personGeneration: 'dont_allow', // Safety setting
},
});
// Veo 3.1 (full feature set and 4K support)
const veo31 = await googleVideo.generate({
prompt: 'A drone shot flying over mountains',
model: 'veo-3.1-generate-preview',
duration: 8,
resolution: '4k',
});
// Gemini Omni uses the Interactions API for conversational video editing.
const omni = await googleVideo.generate({
prompt: 'Animate this storyboard with a gentle dolly movement',
model: 'gemini-omni-1.1-flash',
image: './storyboard.png',
duration: 6,
});
For Omni image-to-video, local paths, buffers, data URIs, and HTTP(S) source
images are normalized to inline image blocks with their detected PNG or JPEG
MIME type. The public duration option is serialized as
response_format.duration (for example, "6s"); it is not placed in
generation_config.video_config, whose public control is the generation task.
veo-2.0-generate-001 is retired and remains only as a registry migration record.
xAI Grok Imagine Video
Connector.create({
name: 'xai',
vendor: Vendor.Grok,
auth: { type: 'api_key', apiKey: process.env.XAI_API_KEY! },
});
const grokVideo = VideoGeneration.create({ connector: 'xai' });
// Current text-to-video default.
const textJob = await grokVideo.generate({
prompt: 'A paper sculpture unfolding into a city skyline',
model: 'grok-imagine-video',
duration: 8,
resolution: '720p',
});
// Grok Imagine Video 1.5 is preferred for higher-fidelity image-to-video.
const imageJob = await grokVideo.generate({
prompt: 'Slow cinematic push-in with moving clouds',
model: 'grok-imagine-video-1.5',
image: './keyframe.png',
duration: 8,
resolution: '1080p',
});
Video Extension
Use extend() with a provider/model that supports extension, such as the Google
Veo provider. Its default extension model remains veo-3.1-generate-preview.
Supply the video reference expected by that provider. OpenAI Sora extension,
remix, editing, and reusable-character endpoints are retired and reject locally
as described above; the public method signatures remain available.
Available Models
Prices below are historical or checked-in registry estimates, not live quotes. The video registry retains retired Sora and Veo 2 records for historical lookup. Use the active-model selectors for new work.
OpenAI Sora Models
| Model | Features | Durations | Resolutions | Price/Second |
|---|---|---|---|---|
sora-2 |
Retired 2026-09-24; historical metadata only | 4-20s | 720p | $0.10 ($0.05 batch) |
sora-2-pro |
Retired 2026-09-24; historical metadata only | 4-20s | 720p-1080p | $0.30-$0.70 ($0.15-$0.35 batch) |
Google Veo Models
| Model | Features | Durations | Resolutions | Price/Second |
|---|---|---|---|---|
veo-3.1-lite-generate-preview |
Preferred low-cost preview, native audio | 4-8s | 720p-1080p | $0.05-$0.08 |
veo-3.1-fast-generate-preview |
Fast inference, audio | 4-8s | 720p-4K | $0.10-$0.30 |
veo-3.1-generate-preview |
Full features, 4K | 4-8s | 720p-4K | $0.40 |
gemini-omni-1.1-flash |
Active, preferred conversational generation/editing | 3-10s | 360p-4K | ~$0.10 at 720p |
gemini-omni-flash-preview |
Historical migration entry; inspect lifecycle before use | 3-10s | 720p | $0.10 |
xAI Grok Imagine Models
| Model | Features | Durations | Resolutions | Price/Second |
|---|---|---|---|---|
grok-imagine-video |
Text/image-to-video with audio | 1-15s | 480p-720p | $0.05-$0.07 |
grok-imagine-video-1.5 |
Preferred text/image/reference-to-video | 1-15s | 480p-1080p | $0.08-$0.25 plus $0.01 input image |
Model Introspection
// List available models
const models = await videoGen.listModels();
console.log('Available models:', models);
// Historical metadata is still available for retired models.
const info = videoGen.getModelInfo('sora-2');
console.log('Durations:', info.capabilities.durations); // [4, 8, 12, 16, 20]
console.log('Resolutions:', info.capabilities.resolutions); // ['720x1280', ...]
console.log('Has audio:', info.capabilities.audio); // true
console.log('Image-to-video:', info.capabilities.imageToVideo); // true
console.log('Style control:', info.capabilities.features.styleControl); // false
console.log('Lifecycle:', info.lifecycle); // retired
console.log('Callable:', info.isActive); // false
Cost Estimation
import { calculateVideoCost } from 'oneringai';
// Historical Sora 2 pricing: $0.10/second (generation is retired)
const soraCost = calculateVideoCost('sora-2', 8); // 8 seconds
console.log(`Sora 2 (8s): $${soraCost}`); // $0.80
// Historical Sora 2 Pro pricing: $0.30/second (generation is retired)
const proCost = calculateVideoCost('sora-2-pro', 12); // 12 seconds
console.log(`Sora 2 Pro (12s): $${proCost}`); // $3.60
// Veo 3.1 Lite at 1080p: $0.08/second
const veoCost = calculateVideoCost('veo-3.1-lite-generate-preview', 8, {
resolution: '1080p',
});
console.log(`Veo 3.1 Lite (8s, 1080p): $${veoCost}`); // $0.64
// Grok 1.5 at 1080p plus one image input.
const grokCost = calculateVideoCost('grok-imagine-video-1.5', 8, {
resolution: '1080p',
inputImages: 1,
});
console.log(`Grok Imagine 1.5 (8s, 1080p): $${grokCost}`); // $2.01
Error Handling
try {
const job = await videoGen.generate({
prompt: 'A video',
duration: 8,
});
const result = await videoGen.waitForCompletion(job.jobId, 300000); // 5 min timeout
if (result.status === 'completed') {
const buffer = await videoGen.download(result.jobId);
await fs.writeFile('./output.mp4', buffer);
}
} catch (error) {
if (error.message.includes('timed out')) {
console.error('Video generation took too long');
} else if (error.message.includes('failed')) {
console.error('Video generation failed:', error.message);
} else if (error.message.includes('policy')) {
console.error('Content policy violation');
} else {
console.error('Error:', error.message);
}
}
Job Management
// Cancel a pending job
const job = await videoGen.generate({ prompt: '...', duration: 8 });
// Changed your mind? Cancel it
const cancelled = await videoGen.cancel(job.jobId);
console.log('Cancelled:', cancelled); // true