AI Video Reference
Use the AI video hooks to submit long-running video jobs, poll for completion, and play back hosted MP4 results. Video generation is job-based so apps can survive reloads, slow model runs, and flaky networks.
Job-Based Video Generation
Video generation uses two hooks:
useSubmitVideoJobsubmits a request and returns ajobId.useVideoJobStatuspolls that job until it completes or fails.
useSubmitVideoJob
Hook Options
onError((error: Error) => void): Optional error callback.
Returns
submitVideo(function): Submit a video generation job.isSubmitting(boolean): Whether submission is in progress.error(Error | null): Current submission error.clearError(() => void): Clear the current error.
submitVideo Input
prompt(string, required): Description of the motion or scene.image(File,{ file: File }, or{ url: string }): Source image to animate, or first frame when usinglastFrame.video(File,{ file: File }, or{ url: string }): Source video to edit.lastFrame(File,{ file: File }, or{ url: string }): Final frame for interpolation; requiresimage.referenceImages(FileList,File[], or an array of file or URL objects): Style or subject references.extendVideo(File,{ file: File }, or{ url: string }): Video to continue.aspectRatio("16:9"or"9:16"): Common landscape or portrait output ratio.durationSeconds(number): Duration for generation workflows. Do not use it withvideoorextendVideo.quality("low","medium", or"high"): Optional quality tier. Omit it unless the user requests it.generateAudio(boolean, defaultfalse): Whether to request generated audio.model(string): Model ID for the request.
useVideoJobStatus
Arguments
jobId(string | null): Job to poll, ornullto disable polling.options(object): Optional polling configuration.
Status Options
pollInterval(number, default10000): Polling interval in milliseconds.enabled(boolean, defaulttrue): Whether polling is enabled.onComplete((result: { url: string }) => void): Called once when the final URL is available.onError((error: string) => void): Called once when the job fails.
Returns
status("pending","processing","completed","failed", ornull): Current job status.resultUrl(string | null): Completed hosted video URL.error(string | null): Current job error.createdAt(string | null): Job creation timestamp.completedAt(string | null): Job completion timestamp.refetch(() => void): Trigger an immediate status fetch.
Generation Workflows
Choose one workflow per submitVideo call:
- Text to video:
prompt. - Animate an image:
promptandimage. - Interpolate between frames:
prompt,image, andlastFrame. - Reference-guided generation:
promptandreferenceImages. - Extend a video:
promptandextendVideo. - Continue through frame extraction: extract the last frame with
useVideoFrame, then pass it asimage. - Edit a video:
promptandvideo.
Use lastFrame only with image. Treat image, referenceImages, and extendVideo as alternative starting points. durationSeconds applies only to generation workflows and must not be used with video or extendVideo.
Persist and Poll Example
Import both hooks from the AI hook module:
import { useSubmitVideoJob, useVideoJobStatus } from '@/hooks/use-ai'
import { usePersistentItem } from '@/hooks/use-persistent-item'
export default function App() {
const [prompt, setPrompt] = React.useState('')
const [jobId, setJobId] = usePersistentItem<string | null>('videoJobId', null)
const [videoUrl, setVideoUrl] = usePersistentItem<string | null>(
'videoUrl',
null,
)
const [videoError, setVideoError] = React.useState<string | null>(null)
const { submitVideo, isSubmitting, error: submitError } = useSubmitVideoJob()
const job = useVideoJobStatus(jobId, {
onComplete: ({ url }) => {
setVideoUrl(url)
setJobId(null)
},
onError: (message) => {
setVideoError(message)
setJobId(null)
},
})
const isGenerating = job.status === 'pending' || job.status === 'processing'
const handleGenerate = async () => {
if (!prompt.trim()) return
setVideoError(null)
const result = await submitVideo({
prompt: prompt.trim(),
aspectRatio: '16:9',
durationSeconds: 4,
})
if (result) setJobId(result.jobId)
}
const errorMessage = submitError?.message || videoError || job.error
return (
<div>
<input
value={prompt}
onChange={(event) => setPrompt(event.target.value)}
placeholder="Describe the video you want..."
/>
<button onClick={handleGenerate} disabled={isSubmitting || isGenerating}>
{isSubmitting
? 'Submitting...'
: isGenerating
? 'Generating...'
: 'Generate video'}
</button>
{errorMessage ? <p className="text-destructive">{errorMessage}</p> : null}
{videoUrl ? (
<video src={videoUrl} controls className="w-full rounded-lg" />
) : null}
</div>
)
}
Video Utilities
The platform also includes client-side video helpers for workflows around generated videos. These hooks process media locally in the browser and return local File objects that work with useDownload, useFileUpload, or video generation inputs.
All three utilities accept hosted URLs and local media. URL sources must be same-origin or CORS-enabled; platform-hosted media works automatically. Only one stitch or audio-overlay operation can run at a time.
useVideoFrame
Extract a still frame from a video URL, Blob, File, or { url } object.
Hook Options
onError((error: Error) => void): Optional custom error handler.onFinish((frame: File) => void): Called after successful extraction.
Returns
extractFrame(function): Extract a frame and resolve to aFileorundefined.isLoading(boolean): Whether extraction is in progress.error(Error | null): Current extraction error.clearError(() => void): Clear the current error.
extractFrame Input
source(string,Blob,File, or{ url: string }, required): Video source.time(number,"first", or"last"): Frame position.format("image/png"or"image/jpeg"): Output image format.quality(number): Image quality.
import { useVideoFrame } from '@/hooks/use-video-frame'
const { extractFrame, isLoading, error, clearError } = useVideoFrame()
const frameFile = await extractFrame({
source: previousVideoUrl,
time: 'last',
format: 'image/png',
})
time can be "first", "last", or a timestamp in seconds. Use the returned File directly as the image for the next submitVideo call.
useVideoStitch
Concatenate two or more clips into one MP4 file.
Hook Options
onError((error: Error) => void): Optional custom error handler.onFinish((video: File) => void): Called after successful stitching.onProgress((progress: number) => void): Called with progress from 0 through 1.
Returns
stitch(function): Concatenate clips and resolve to an MP4Fileorundefined.isProcessing(boolean): Whether stitching is in progress.progress(number): Progress from 0 through 1.error(Error | null): Current stitching error.cancel(() => void): Cancel the current operation.clearError(() => void): Clear the current error.
stitch Input
sources(array ofstring,Blob,File, or{ url: string }, required): Two or more clips in output order.width(number): Output width; defaults to the first video's width.height(number): Output height; defaults to the first video's height.muted(boolean, defaultfalse): Whether to discard source audio.
import { useDownload } from '@/hooks/use-download'
import { useVideoStitch } from '@/hooks/use-video-stitch'
const { stitch, isProcessing, progress, cancel } = useVideoStitch()
const { download } = useDownload()
const result = await stitch({
sources: [firstClipUrl, secondClipUrl],
muted: false,
})
if (result) download(result)
All clips should use the same aspect ratio for best results. The returned File already includes the .mp4 extension, so pass it directly to download(result).
useVideoAudioOverlay
Replace or mix audio on top of a video.
Hook Options
onError((error: Error) => void): Optional custom error handler.onFinish((video: File) => void): Called after a successful overlay.onProgress((progress: number) => void): Called with progress from 0 through 1.
Returns
overlay(function): Overlay audio and resolve to an MP4Fileorundefined.isProcessing(boolean): Whether processing is in progress.progress(number): Progress from 0 through 1.error(Error | null): Current overlay error.cancel(() => void): Cancel the current operation.clearError(() => void): Clear the current error.
overlay Input
video(string,Blob,File, or{ url: string }, required): Video source.audio(string,Blob,File, or{ url: string }, required): Audio source.audioVolume(number, default1): Overlay volume from 0 through 1.videoVolume(number, default0): Original video volume from 0 through 1.audioOffset(number, default0): Seconds to skip at the start of the audio.width(number): Output width; defaults to the video's width.height(number): Output height; defaults to the video's height.
import { useVideoAudioOverlay } from '@/hooks/use-video-audio-overlay'
const { overlay, isProcessing, progress, cancel } = useVideoAudioOverlay()
const result = await overlay({
video: videoUrl,
audio: speechUrl,
audioVolume: 1,
videoVolume: 0,
})
videoVolume defaults to 0, so the overlay replaces the original audio. Set it above 0 to mix both tracks. Audio longer than the video is truncated; when it is shorter, the remainder of the video plays without overlay audio. The returned File already includes the .mp4 extension.
Model Selection
When no model is specified, the platform chooses an efficient default model for the user's plan. To expose model choice, pass a model ID and build selectors from listVideoModels():
import { listVideoModels, useSubmitVideoJob } from '@/hooks/use-ai'
const models = listVideoModels()
const [modelId, setModelId] = React.useState<string | undefined>(undefined)
const { submitVideo } = useSubmitVideoJob()
await submitVideo({
prompt: 'A drone shot over mountains',
model: modelId,
})
listVideoModels() returns model objects with id, displayName, provider, tier, description, and capabilities. Video model capabilities include:
image_inputreference_imagesreference_videosreference_audiovideo_extensionvideo_editinglast_frame
Prompt-only text-to-video is implicit for video models and is not listed as a capability. Let the server validate exact combinations such as duration, aspect ratio, and incompatible inputs.
Best Practices
- Persist the
jobIduntil completion so polling survives reloads. - Use
resultUrlfor playback and persistence. - Treat
job.statusas the source of truth for loading UI. - Use
onCompleteandonErrorto copy terminal state into your own durable state. - Choose one video task per submission instead of mixing incompatible inputs.
- Prefer frame extraction for cross-model continuation, then generate the next clip from the extracted
File. - Use
useVideoStitchto assemble multi-shot workflows into a downloadable file. - Use
useVideoAudioOverlayfor narration, background music, or replacing original audio. - Include subject, action, style, camera motion, composition, focus, and ambiance in video prompts.