Skip to Content

Proxies

A proxy is a low-resolution copy of a clip that the viewport uses in place of the original, so that heavy footage scrubs at a usable rate. Output never sees it.

Why they exist

Seeking is what makes 4K slow, and it is the only cost that scales with resolution. Measured in Chromium on H.264 yuv420p, GOP 60, 30 samples per cell, median milliseconds:

4K1080p540p
Seek, random171.836.617.4
Seek, one frame148.040.916.3
Upload (WebGPU)4.34.43.8
Upload (WebGL2)0.10.10.1

Seek is 97.6% of the 4K cost. Upload is flat across a 16× payload range — 31.6 down to 2.0 MB per frame — so it is per-call overhead, not bytes, and re-uploading the full-resolution frame is not what makes 4K slow.

At 148 ms, stepping a 4K clip one frame at a time is a 6.8 fps scrub. Resolution alone predicts a 3.6× improvement; because the remaining cost is decode-from-keyframe, the proxy also gets a short GOP (-g 12) on top.

The safety property

A proxy is substituted for the interactive viewport only. Never for output.

That is enforced by polarity rather than by vigilance. The useProxies flag is absent or false by default, so export, the offline renderer, the export preview and component thumbnails all reach full resolution by doing nothing. Only the two interactive viewport hosts opt in. There is no opt-out to forget.

Substitution is pixels only, at a single chokepoint. Size, duration, frame rate, pixel aspect, alpha interpretation, loop count, trim, slip, stretch and time remap all keep reading the original asset. There is no second record to keep in step, so a proxy cannot drift out of alignment with its source.

Using one

The proxy row lives on the asset, next to Interpret Footage ▸ Alpha — the same shape of thing, because the record belongs to the file. One proxy therefore speeds up every layer using that file, in every composition.

The row always states which state the file is in, because a proxy silently in use looks like a quality bug:

StateMeaning
Full resolutionNo proxy. The row still says what one would be — 1920 × 1080 for a 4K source — so the action is a known quantity before you click it
GeneratingAn ffmpeg job is running. The asset renders at full resolution meanwhile
ReadyA decodable stand-in exists
In useReady, and the global switch is on. Styled loudly rather than left to be inferred from how soft the picture looks
FailedGeneration ran and failed, or the file went missing. The error says why, and renders fall back to full resolution

The global switch — proxies on or off for the whole project — is repeated on the row rather than buried in a menu.

Generating, attaching, detaching

Generate runs ffmpeg as a child process in the main process, for the same reason the export encode does: the transcode must not compete with the UI thread, because the whole promise is that the asset stays usable at full resolution while it runs. The call returns as soon as the job is queued — import is never blocked.

The target is a halved long edge, at or below 1920, rounded to even dimensions. Alpha sources become VP9 WebM; everything else becomes H.264 MP4.

Cancel is real, not cooperative: the child process is killed. Quitting kills any survivors, which is the other half of why a generating record is never persisted — there would be no process behind it after a reload.

Attach points the record at a file you supply yourself. It needs no ffmpeg, which makes it the whole proxy story in browser builds, where generation is absent. Detach revokes the object URL for a proxy we generated, and deliberately does not delete a file you attached.

Every failure path lands the asset back at full resolution rather than in an error state. A failed proxy is a missed optimisation; a broken asset is a lost shot. The enumerated cases: no ffmpeg, encode error, a throwing bridge, an unreadable original, empty output, cancellation, an asset deleted mid-encode, and an asset re-imported mid-encode — where the stale job re-reads the record after every await and stands down rather than overwriting the new one.

When it refuses, and why

Refusals are explained rather than attempted, and the check is pure — so the UI can disable the action and say why without starting anything.

ReasonMessage
No ffmpegProxies need ffmpeg, which this build cannot reach. You can still attach one
Not videoOnly video footage can have a proxy
Too smallThis footage is already small enough to scrub smoothly
Unknown sizeThis file’s dimensions are unknown, so no proxy size can be chosen
Already runningA proxy is already being generated for this file
Source unreadableThe original file could not be read

“Too small” means a long edge at or below 1280. Below that the source already seeks in roughly 17 ms — inside a 30 fps frame budget — so halving it would buy a few milliseconds and cost the disk and the wait.

Last updated on