Skip to content
lumalabs.ai

Video generation

Generate videos with Ray 3.2 — text-to-video, image-to-video, multi-keyframe i2v, resolution, duration, looping, and HDR.

Generate videos from text or from anchor images with ray-3.2. This guide covers every parameter for type: "video" requests. For editing existing videos, see Video editing.

A text-to-video request needs model, type, a prompt, and (typically) a resolution and duration under video:

from luma_agents import Luma
client = Luma()
generation = client.generations.create(
model="ray-3.2",
type="video",
prompt="A slow dolly shot through a misty greenhouse at sunrise",
aspect_ratio="16:9",
video={
"resolution": "720p",
"duration": "5s",
},
)

Output is delivered as an MP4 via a presigned URL once the generation completes — same submit-poll-download pattern as image generation. Keep the completed generation’s top-level id if you want to edit, extend, or reframe it later with source.generation_id or a generation_id keyframe.

Pass an image as video.start_frame, video.end_frame, or both to seed the generation. Each accepts the same ImageRef shape as image_ref on image requests — a publicly accessible url, or inline base64 data with media_type.

generation = client.generations.create(
model="ray-3.2",
type="video",
prompt="The character turns to face the camera and smiles",
aspect_ratio="16:9",
video={
"resolution": "720p",
"duration": "5s",
"start_frame": {"url": "https://example.com/opening-frame.jpg"},
"end_frame": {"url": "https://example.com/closing-frame.jpg"},
},
)

video.start_frame / video.end_frame only pin the first and last frame. To guide the motion through the clip — pinning images at arbitrary positions — use two parallel arrays under video:

  • keyframes — 1–64 guide-frame images, each taking the same ImageRef shape as the anchor frames above (a url, inline base64 data, or a prior generation’s generation_id).
  • keyframe_indexes — a parallel list of non-negative, unique output-frame positions where each keyframes[i] is anchored, in the duration × 24fps grid: 5s → 0–120, 10s → 0–240.

The two arrays must be the same length, and you provide both or neither — one without the other is a 400. keyframes is the multi-anchor generalization of start_frame: a single anchor at index 0 (keyframes: [X], keyframe_indexes: [0]) is equivalent to start_frame: X, so the two surfaces are mutually exclusive — setting both on one request is a 400. Use one or the other.

Unlike the legacy start_frame / end_frame pair, multi-keyframe i2v routes through the full Ray 3.2 path, so it lifts those anchors’ restrictions: arbitrary frame positions, duration: "10s", and hdr: true are all supported.

generation = client.generations.create(
model="ray-3.2",
type="video",
prompt="A hot-air balloon drifts across the valley as the sun rises",
aspect_ratio="16:9",
video={
"resolution": "720p",
"duration": "5s",
"keyframes": [
{"url": "https://example.com/launch.jpg"},
{"url": "https://example.com/midflight.jpg"},
{"url": "https://example.com/sunrise.jpg"},
],
"keyframe_indexes": [0, 60, 120],
},
)

To continue or prepend a previous video, pass that completed generation’s top-level id as exactly one generation_id keyframe on a type: "video" request:

ShapeResult
video.start_frame: { "generation_id": "<id>" }Forward extend — the prior clip becomes the start, and the new generation continues after it
video.end_frame: { "generation_id": "<id>" }Backward extend — the prior clip becomes the end, and the new generation is prepended before it
{
"model": "ray-3.2",
"type": "video",
"prompt": "continue the camera move into the glowing forest",
"video": {
"resolution": "720p",
"start_frame": { "generation_id": "d290f1ee-6c54-4b01-90e6-d701748f0851" }
}
}

A text description of the video, 1–6,000 characters. Be specific about subject, motion, camera movement, lighting, and pacing.

Use ray-3.2 for video generation.

"video" for generation. For editing an existing video, use "video_edit" — see Video editing. For aspect-ratio reframing, use "video_reframe" — see Video reframing.

Ray 3.2 video accepts these six aspect ratios — a subset of the API-wide AspectRatio enum:

ValueOrientation
9:16Standard portrait
3:4Portrait
1:1Square
4:3Classic landscape
16:9Standard widescreen
21:9Cinematic ultrawide

When omitted, the model selects a ratio based on the prompt and anchor frames.

Output resolution. Defaults to 720p.

ValueNotes
360pDraft tier — fast, low-cost previews. SDR only (rejected with hdr: true)
540pStandard definition. Not available with hdr: true
720pHD — default
1080pFull HD

Video duration. Defaults to 5s.

ValueNotes
5sDefault
10sNot supported with hdr: true, start_frame, or end_frame

Boolean. When true, generates a seamlessly looping video.

Boolean. When true, generates an HDR-encoded MP4.

Boolean. When true, exports an EXR file alongside the MP4 for professional colour-grading workflows. Requires hdr: true.

Optional anchor ImageRefs. start_frame is the first frame (or, on video_edit, the single guide frame). end_frame is the last frame and is valid for type: "video" only. Each can also carry a prior generation id as { "generation_id": "..." } for the single-keyframe extend flows above. Neither is supported with duration: "10s".

{
"video": {
"start_frame": { "url": "https://example.com/first.jpg" },
"end_frame": { "data": "iVBORw0KGgo...", "media_type": "image/png" }
}
}

Optional multi-anchor image-to-video controls — see Multi-keyframe image-to-video. keyframes is a list of 1–64 guide-frame ImageRefs; keyframe_indexes is a parallel, same-length list of unique output-frame positions (duration × 24fps: 5s → 0–120, 10s → 0–240). Mutually exclusive with start_frame, end_frame, and loop.

RuleConstraint
modelMust be ray-3.2 for video
typeMust be "video" for this flow
promptRequired, 1–6,000 characters
aspect_ratioOne of 9:16, 3:4, 1:1, 4:3, 16:9, 21:9, or omitted
video.resolutionOne of 360p, 540p, 720p, 1080p, or omitted (defaults 720p). 360p (draft) and 540p are rejected with hdr: true
video.durationOne of 5s, 10s, or omitted (defaults 5s). 10s is rejected with hdr, start_frame, or end_frame
video.loopBoolean. type: "video" only; rejected with 10s, hdr, end_frame, or keyframes
video.hdrBoolean. Requires 720p/1080p; rejected with 360p/540p, 10s, or loop
video.exr_exportBoolean. Requires hdr: true
video.start_frame, video.end_frameOptional ImageRef (url or base64 data, max 50 MB and 8000 px per side; or a generation_id). Rejected with duration: "10s". end_frame is type: "video" only. Mutually exclusive with keyframes
video.keyframesOptional list of 1–64 guide-frame ImageRefs. Provide with keyframe_indexes (same length). Mutually exclusive with start_frame, end_frame, and loop
video.keyframe_indexesParallel list of non-negative, unique output-frame positions (5s → 0–120, 10s → 0–240); same length as keyframes
video.editRejected for type: "video" — edit-only
sourceRejected for type: "video" — use source only for image_edit, video_edit, or video_reframe
source.generation_idRejected for type: "video" — use video.start_frame.generation_id or video.end_frame.generation_id for extend

See Error handling for the exact detail strings rejected values produce.

A successful submit returns HTTP 201:

{
"id": "d290f1ee-6c54-4b01-90e6-d701748f0851",
"type": "video",
"state": "queued",
"model": "ray-3.2",
"created_at": "2026-05-26T12:00:00Z",
"output": [],
"failure_reason": null,
"failure_code": null
}

Poll GET /v1/generations/{id} until state is completed or failed. On completed, each output entry carries a presigned url to the MP4:

{
"output": [
{
"type": "video",
"url": "https://storage.example.com/generations/d290f1ee/output.mp4?X-Amz-Expires=3600&..."
}
]
}

Video generations take longer than image generations. Tune your initial wait and deadline accordingly — a 5s/720p generation typically completes in well under two minutes, but 10s/1080p with HDR can run several times longer.

import time
deadline = time.time() + 600 # 10-minute hard timeout
time.sleep(30) # don't bother polling right away
while generation.state not in ("completed", "failed"):
if time.time() > deadline:
raise TimeoutError(f"Generation {generation.id} did not complete in time")
generation = client.generations.get(generation.id)
time.sleep(5)

Ray 3.2 video generation is priced by duration, resolution, and dynamic range. Standard dynamic range has separate 5-second and 10-second totals; HDR and HDR + EXR are 5-second only. See Pricing — ray-3.2 per-video pricing for the full grid.