Video editing
Edit existing videos with Ray 3.2 — source.generation_id, source URLs and inline video, strength, auto-controls, per-signal conditioning controls, and single- or multi-keyframe guide frames.
Modify existing videos with natural-language prompts using ray-3.2. This guide covers every parameter for type: "video_edit" requests. For generating new videos from text or anchor images, see Video generation.
When to use video editing
Section titled “When to use video editing”| Scenario | Use | Why |
|---|---|---|
| Restyle an existing video while preserving its motion | video_edit with source.generation_id | Re-renders the source under a new prompt |
| Edit a local/private video file | video_edit with source.url or source.data (+ source.media_type) | References a hosted video URL or uploads inline base64 video when you do not have a prior generation |
| Generate a new video that opens from an anchor image | video + video.start_frame | This is not an edit — it’s a generation seeded by an image. See Video generation |
Basic request
Section titled “Basic request”auto_controls: true lets the model derive the conditioning schedule from the source video — the recommended default. Reference a previous video by passing that completed generation’s top-level id as source.generation_id.
from luma_agents import Luma
client = Luma()
# The source is a previous completed video generation.previous = client.generations.get("generation-id")
generation = client.generations.create( model="ray-3.2", type="video_edit", prompt="Transform the scene into moonlit 35mm film footage", source={"generation_id": previous.id}, video={ "resolution": "720p", "edit": { "auto_controls": True, }, },)import Luma from "luma-agents";
const client = new Luma();
// The source is a previous completed video generation.const previous = await client.generations.get("generation-id");
const generation = await client.generations.create({ model: "ray-3.2", type: "video_edit", prompt: "Transform the scene into moonlit 35mm film footage", source: { generation_id: previous.id }, video: { resolution: "720p", edit: { auto_controls: true, }, },});// The source is a previous completed video generation.previous, err := client.Generations.Get(ctx, "generation-id")if err != nil { return err}
generation, err := client.Generations.New(ctx, lumaagents.GenerationNewParams{ Model: lumaagents.F(lumaagents.ModelRay3_2), Type: lumaagents.F(lumaagents.GenerationNewParamsTypeVideoEdit), Prompt: lumaagents.F("Transform the scene into moonlit 35mm film footage"), Source: lumaagents.F(lumaagents.ImageRefParam{ GenerationID: lumaagents.F(previous.ID), }), Video: lumaagents.F(lumaagents.VideoOptionsParam{ Resolution: lumaagents.F(lumaagents.VideoResolution720p), Edit: lumaagents.F(lumaagents.VideoEditOptionsParam{ AutoControls: lumaagents.F(true), }), }),})curl -X POST https://agents.lumalabs.ai/v1/generations \ -H "Authorization: Bearer $LUMA_AGENTS_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "ray-3.2", "type": "video_edit", "prompt": "Transform the scene into moonlit 35mm film footage", "source": { "generation_id": "d290f1ee-6c54-4b01-90e6-d701748f0851" }, "video": { "resolution": "720p", "edit": { "auto_controls": true } } }'Referencing the source video
Section titled “Referencing the source video”A video_edit request needs exactly one source video. Provide it three ways:
1. A prior Luma generation — pass the completed generation’s top-level id as source.generation_id. It must be completed, belong to the same client, and have a video output:
{ "source": { "generation_id": "d290f1ee-6c54-4b01-90e6-d701748f0851" }}2. A hosted video URL — pass a publicly reachable URL as source.url, with a video/* source.media_type such as "video/mp4":
{ "source": { "url": "https://example.com/source.mp4", "media_type": "video/mp4" }}3. Inline base64 video — pass the encoded bytes as source.data, with a video/* source.media_type:
{ "source": { "data": "AAAAIGZ0eXBtcDQy...", "media_type": "video/mp4" }}image_ref is rejected on type: "video_edit"; guide the edit through the prompt, optional video.start_frame, and video.edit.
Edit controls — video.edit
Section titled “Edit controls — video.edit”The video.edit object carries the video-to-video conditioning. It has three conditioning fields — strength, auto_controls, and controls — plus two multi-keyframe anchoring fields, keyframes and keyframe_indexes (see Guide frames below).
auto_controls
Section titled “auto_controls”Boolean. When true, the model derives the full conditioning schedule from the source video — the recommended default. When omitted, supplying strength or controls implies manual mode.
strength
Section titled “strength”A preservation-vs-reimagination preset (VideoEditStrength). Nine values across three bands; lower numbers within each band are lighter, higher numbers stronger. Supplying strength implies manual mode unless auto_controls is explicitly set.
| Wire value | Band | Behaviour |
|---|---|---|
adhere_1 | Adhere | Closely preserves source motion, composition, and structure |
adhere_2 | Adhere | |
adhere_3 | Adhere | Strongest adherence in the band |
flex_1 | Flex | Balanced — preserves motion, lets the prompt reshape style and content |
flex_2 | Flex | Common default for most edits |
flex_3 | Flex | |
reimagine_1 | Reimagine | The prompt drives most of the output; source provides loose grounding |
reimagine_2 | Reimagine | |
reimagine_3 | Reimagine | Strongest reimagining |
{ "video": { "edit": { "strength": "flex_2" } } }controls — per-signal conditioning
Section titled “controls — per-signal conditioning”For workflows that need finer control than strength, set video.edit.controls with per-signal objects. Each control has an enabled flag plus its own tuning parameter; omit a field to use the model default. controls cannot be combined with auto_controls: true.
| Control | Fields | Notes |
|---|---|---|
pose | enabled (boolean), strength (precise / coarse) | Pose / skeleton conditioning |
depth | enabled (boolean), blur (float 0–1) | Depth / scene-geometry conditioning. Higher blur allows more geometric freedom |
normals | enabled (boolean), augmentation (float 0–1) | Surface-normals conditioning. Higher augmentation allows more reinterpretation of surface geometry |
trajectory | enabled (boolean), sparsity (float 0–1) | Motion-trajectory conditioning. Higher sparsity uses fewer motion anchors |
face | enabled (boolean) | Face-identity conditioning |
generation = client.generations.create( model="ray-3.2", type="video_edit", prompt="Transform the scene into moonlit 35mm film footage", source={"generation_id": "d290f1ee-6c54-4b01-90e6-d701748f0851"}, video={ "resolution": "720p", "edit": { "controls": { "pose": {"enabled": True, "strength": "precise"}, "depth": {"enabled": True, "blur": 0.2}, "face": {"enabled": True}, }, }, },)const generation = await client.generations.create({ model: "ray-3.2", type: "video_edit", prompt: "Transform the scene into moonlit 35mm film footage", source: { generation_id: "d290f1ee-6c54-4b01-90e6-d701748f0851" }, video: { resolution: "720p", edit: { controls: { pose: { enabled: true, strength: "precise" }, depth: { enabled: true, blur: 0.2 }, face: { enabled: true }, }, }, },});generation, err := client.Generations.New(ctx, lumaagents.GenerationNewParams{ Model: lumaagents.F(lumaagents.ModelRay3_2), Type: lumaagents.F(lumaagents.GenerationNewParamsTypeVideoEdit), Prompt: lumaagents.F("Transform the scene into moonlit 35mm film footage"), Source: lumaagents.F(lumaagents.ImageRefParam{ GenerationID: lumaagents.F("d290f1ee-6c54-4b01-90e6-d701748f0851"), }), Video: lumaagents.F(lumaagents.VideoOptionsParam{ Resolution: lumaagents.F(lumaagents.VideoResolution720p), Edit: lumaagents.F(lumaagents.VideoEditOptionsParam{ Controls: lumaagents.F(lumaagents.AdvancedControlsParam{ Pose: lumaagents.F(lumaagents.PoseControlParam{Enabled: lumaagents.F(true), Strength: lumaagents.F(lumaagents.PoseControlStrengthPrecise)}), Depth: lumaagents.F(lumaagents.DepthControlParam{Enabled: lumaagents.F(true), Blur: lumaagents.F(0.2)}), Face: lumaagents.F(lumaagents.FaceControlParam{Enabled: lumaagents.F(true)}), }), }), }),})curl -X POST https://agents.lumalabs.ai/v1/generations \ -H "Authorization: Bearer $LUMA_AGENTS_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "ray-3.2", "type": "video_edit", "prompt": "Transform the scene into moonlit 35mm film footage", "source": { "generation_id": "d290f1ee-6c54-4b01-90e6-d701748f0851" }, "video": { "resolution": "720p", "edit": { "controls": { "pose": {"enabled": true, "strength": "precise"}, "depth": {"enabled": true, "blur": 0.2}, "face": {"enabled": true} } } } }'Guide frames
Section titled “Guide frames”You can anchor the edit to specific frames in two mutually exclusive ways: a single guide frame, or a multi-keyframe set.
Single guide frame — video.start_frame
Section titled “Single guide frame — video.start_frame”On type: "video_edit", video.start_frame is accepted as a single guide frame for the edit (an ImageRef). end_frame is generation-only and rejected on edits.
Multiple keyframes — video.edit.keyframes
Section titled “Multiple keyframes — video.edit.keyframes”For finer anchoring, pin guide-frame images at arbitrary positions in the source video’s frame grid using two parallel arrays under video.edit:
keyframes— a list of guide-frame images (up to 64), each taking the sameImageRefshape assource/image_ref[](aurl, inlinedata, or ageneration_id).keyframe_indexes— a parallel list of non-negative, unique frame positions in the source video’s frame grid where eachkeyframes[i]is anchored.
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 video.start_frame: setting video.start_frame: X is equivalent to keyframes: [X], keyframe_indexes: [0], so the two are mutually exclusive — setting both on the same request is a 400. Use one or the other.
generation = client.generations.create( model="ray-3.2", type="video_edit", prompt="Transform the scene into moonlit 35mm film footage", source={"generation_id": "d290f1ee-6c54-4b01-90e6-d701748f0851"}, video={ "resolution": "720p", "edit": { "keyframes": [ {"url": "https://example.com/frame-0.jpg"}, {"url": "https://example.com/frame-90.jpg"}, ], "keyframe_indexes": [0, 90], }, },)const generation = await client.generations.create({ model: "ray-3.2", type: "video_edit", prompt: "Transform the scene into moonlit 35mm film footage", source: { generation_id: "d290f1ee-6c54-4b01-90e6-d701748f0851" }, video: { resolution: "720p", edit: { keyframes: [ { url: "https://example.com/frame-0.jpg" }, { url: "https://example.com/frame-90.jpg" }, ], keyframe_indexes: [0, 90], }, },});curl -X POST https://agents.lumalabs.ai/v1/generations \ -H "Authorization: Bearer $LUMA_AGENTS_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "ray-3.2", "type": "video_edit", "prompt": "Transform the scene into moonlit 35mm film footage", "source": { "generation_id": "d290f1ee-6c54-4b01-90e6-d701748f0851" }, "video": { "resolution": "720p", "edit": { "keyframes": [ { "url": "https://example.com/frame-0.jpg" }, { "url": "https://example.com/frame-90.jpg" } ], "keyframe_indexes": [0, 90] } } }'HDR on edits
Section titled “HDR on edits”video.hdr and video.exr_export apply to edits the same way they do to generation.
Aspect ratio on edits
Section titled “Aspect ratio on edits”The output preserves the source video’s aspect ratio. Setting aspect_ratio on a video_edit request is silently ignored — same behaviour as on image edits.
Validation rules
Section titled “Validation rules”| Rule | Constraint |
|---|---|
model | Must be ray-3.2 |
type | Must be "video_edit" |
prompt | Required, 1–6,000 characters |
source | Required. Provide exactly one of source.generation_id, source.url, or source.data |
source.generation_id | UUID of a prior completed video generation owned by the same client. No upload size limit — references stored media |
source.url | Publicly reachable video URL. Requires source.media_type set to a video/* MIME (e.g. "video/mp4"). Max 200 MB, ≤18s |
source.data | Inline base64 video. Requires source.media_type set to a video/* MIME. Max 200 MB, ≤18s |
image_ref | Rejected on video_edit |
video.resolution | One of 360p, 540p, 720p, 1080p, or omitted (defaults 720p). 360p is the draft tier; 360p/540p rejected with hdr: true |
video.loop | Rejected on video_edit — create-only |
video.end_frame | Rejected on video_edit — generation-only |
video.start_frame | Optional single guide frame (ImageRef). Mutually exclusive with video.edit.keyframes |
video.edit.strength | One of the 9 VideoEditStrength values, or omitted |
video.edit.auto_controls | Boolean. Cannot combine with video.edit.controls |
video.edit.controls | Per-signal objects (pose, depth, normals, trajectory, face). Cannot combine with auto_controls: true |
video.edit.keyframes | Up to 64 guide-frame ImageRefs. Provide with keyframe_indexes (same length). Mutually exclusive with video.start_frame |
video.edit.keyframe_indexes | Parallel list of non-negative, unique source-frame positions; same length as keyframes |
video.hdr / exr_export | hdr requires 720p/1080p; exr_export requires hdr: true |
aspect_ratio | Silently ignored — derived from source |
See Error handling for the exact detail strings rejected requests produce.
Response
Section titled “Response”A successful submit returns HTTP 201 with the same envelope shape used for video generations. Poll GET /v1/generations/{id} until state is completed or failed. On completed, output[].url carries the presigned MP4 URL with a 1-hour expiry. Keep the generation’s top-level id for any further edits or reframes.
Pricing
Section titled “Pricing”Ray 3.2 video edits are priced by duration (separate 5s and 10s rates) at the video-edit tier, which differs from the generation tier. See Pricing — ray-3.2 per-video pricing.
Next steps
Section titled “Next steps”- Video generation — Generate new videos from text or anchor images
- Video reframing — Reframe an existing video to a new aspect ratio
- Models — Capability matrix for every model
- Pricing — Per-video pricing for Ray 3.2 video
- Error handling — Every error code with troubleshooting steps
- API Reference — Complete endpoint specifications