Skip to content
lumalabs.ai
Videos

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.

ScenarioUseWhy
Restyle an existing video while preserving its motionvideo_edit with source.generation_idRe-renders the source under a new prompt
Edit a local/private video filevideo_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 imagevideo + video.start_frameThis is not an edit — it’s a generation seeded by an image. See Video generation

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,
},
},
)

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.

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).

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.

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 valueBandBehaviour
adhere_1AdhereClosely preserves source motion, composition, and structure
adhere_2Adhere
adhere_3AdhereStrongest adherence in the band
flex_1FlexBalanced — preserves motion, lets the prompt reshape style and content
flex_2FlexCommon default for most edits
flex_3Flex
reimagine_1ReimagineThe prompt drives most of the output; source provides loose grounding
reimagine_2Reimagine
reimagine_3ReimagineStrongest reimagining
{ "video": { "edit": { "strength": "flex_2" } } }

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.

ControlFieldsNotes
poseenabled (boolean), strength (precise / coarse)Pose / skeleton conditioning
depthenabled (boolean), blur (float 0–1)Depth / scene-geometry conditioning. Higher blur allows more geometric freedom
normalsenabled (boolean), augmentation (float 0–1)Surface-normals conditioning. Higher augmentation allows more reinterpretation of surface geometry
trajectoryenabled (boolean), sparsity (float 0–1)Motion-trajectory conditioning. Higher sparsity uses fewer motion anchors
faceenabled (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},
},
},
},
)

You can anchor the edit to specific frames in two mutually exclusive ways: a single guide frame, or a multi-keyframe set.

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 same ImageRef shape as source / image_ref[] (a url, inline data, or a generation_id).
  • keyframe_indexes — a parallel list of non-negative, unique frame positions in the source video’s frame grid where each keyframes[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],
},
},
)

video.hdr and video.exr_export apply to edits the same way they do to generation.

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.

RuleConstraint
modelMust be ray-3.2
typeMust be "video_edit"
promptRequired, 1–6,000 characters
sourceRequired. Provide exactly one of source.generation_id, source.url, or source.data
source.generation_idUUID of a prior completed video generation owned by the same client. No upload size limit — references stored media
source.urlPublicly reachable video URL. Requires source.media_type set to a video/* MIME (e.g. "video/mp4"). Max 200 MB, ≤18s
source.dataInline base64 video. Requires source.media_type set to a video/* MIME. Max 200 MB, ≤18s
image_refRejected on video_edit
video.resolutionOne of 360p, 540p, 720p, 1080p, or omitted (defaults 720p). 360p is the draft tier; 360p/540p rejected with hdr: true
video.loopRejected on video_edit — create-only
video.end_frameRejected on video_edit — generation-only
video.start_frameOptional single guide frame (ImageRef). Mutually exclusive with video.edit.keyframes
video.edit.strengthOne of the 9 VideoEditStrength values, or omitted
video.edit.auto_controlsBoolean. Cannot combine with video.edit.controls
video.edit.controlsPer-signal objects (pose, depth, normals, trajectory, face). Cannot combine with auto_controls: true
video.edit.keyframesUp to 64 guide-frame ImageRefs. Provide with keyframe_indexes (same length). Mutually exclusive with video.start_frame
video.edit.keyframe_indexesParallel list of non-negative, unique source-frame positions; same length as keyframes
video.hdr / exr_exporthdr requires 720p/1080p; exr_export requires hdr: true
aspect_ratioSilently ignored — derived from source

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

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.

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.