Skip to content

OpenFX: Fit the zoom to the part of the clip used on the Resolve timeline - #82

Open
zgmrclick wants to merge 3 commits into
gyroflow:mainfrom
zgmrclick:resolve-trim-aware-zoom
Open

zgmrclick wants to merge 3 commits into
gyroflow:mainfrom
zgmrclick:resolve-trim-aware-zoom

Conversation

@zgmrclick

@zgmrclick zgmrclick commented Sep 28, 2026 •

Copy link
Copy Markdown

Problem

In DaVinci Resolve the dynamic zoom is computed for the whole source clip. If the end (or start) of a clip is shaky but trimmed away on the timeline, it still increases the crop of the visible part.

Why fuscript

Resolve doesn't expose the trim range through OpenFX. I logged everything available during render on the Edit page. For a clip that uses frames 230–525 of 708:

  • kOfxImageEffectPropFrameRange on the source clip is 0..707, the whole media.
  • kOfxImageEffectInstancePropEffectDuration is 708.
  • The unmapped frame range is 0..0.
  • kOfxImageEffectPropFrameRange in BeginSequenceRender holds just the current frame.

So the range is queried with fuscript, the same way the plugin already queries the current file path. TimelineItem:GetSourceStartFrame() and GetSourceEndFrame() give the used source range. If those methods aren't available, it falls back to GetLeftOffset() and GetDuration().

How it works

  • One shared query. A single query lists the used ranges of all video items on the current timeline and is shared by all instances. It takes about 0.2 s.
  • Timing. The query only starts on the first render, and then after a pause in rendering of more than 0.5 s (eg. after trimming a clip), at most every 2 s. It never starts during continuous playback or render. It runs in a background thread: the render waits for it at most 300 ms, then uses the previous ranges (or the whole clip on the very first render), and fuscript is killed if it doesn't finish in 10 s.
  • Why not synchronous. Resolve 21 answers scripting calls only after the host gets the frame being rendered. An earlier version of this PR waited for fuscript inside render(), which deadlocked playback in Resolve 21.1: the clip froze and fuscript hung for minutes.
  • Same file used more than once. The plugin uses the item whose range contains the rendered frame, or the closest one if none does.
  • Blade-cut clips. The range is part of the StabilizationManager cache key, because pieces of a blade-cut clip share the instance id but use different parts of the clip.
  • Only the zoom changes. Smoothing still uses the whole clip (trim_range_only = 0 for the smoothing algorithm). The camera motion doesn't change, and limiting the range can only reduce the zoom: frames outside the range get the widest FOV before the rolling min.

This differs from the After Effects path (b3a0dc3), which also limits smoothing to the trim. I tried that too. With the default algorithm, dropping a shaky part lowers the max velocity, so smoothing gets stronger and the crop often grows instead of shrinking.

Results

Maximum zoom within the used part, with default settings, on Sony A7 IV clips:

clip (used / total frames) now trim for smoothing + zoom trim for zoom only (this PR)
150–266 / 348 ×1.061 ×1.042 ×1.016
451–583 / 648 ×1.191 ×1.229 ×1.077
101–197 / 348 ×1.053 ×1.063 ×1.038
43–140 / 312 ×1.194 ×1.298 ×1.079
173–308 / 480 ×1.196 ×1.206 ×1.070
230–526 / 708 ×1.064 ×1.064 ×1.064

UI

There is a new "Zoom only for used part" checkbox, enabled by default:

  • It's hidden in the Adobe plugin.
  • On the Fusion page it has no effect.
  • It needs Resolve Studio with external scripting set to Local. Without that, nothing changes.

Because it's on by default, the zoom of existing projects changes (only ever decreases) after updating. If you'd prefer it off by default, I'm happy to change that.

Testing

Tested on macOS (M3 Pro) with Resolve Studio 20.3 and 21.1:

  • Edit and Color page playback and Deliver renders all work.
  • The log shows the right range per timeline item.
  • Rendered frames show the smaller crop with no black edges.
  • In 21.1, after the fix: scrubbing to a trimmed clip on the Edit page with pauses (so the query re-runs) renders every frame, the range is picked up, and no fuscript process is left hanging.
  • The Adobe crate still builds (cargo check).

…line

The dynamic zoom was computed for the whole source clip, so shaky footage
that was trimmed away on the timeline still increased the crop of the part
that is actually visible.

DaVinci Resolve doesn't provide the trim range through OpenFX (the clip frame
range is always the whole media), so it's queried with fuscript, the same way
as the current file path. One query returns all timeline items and is shared
by all instances; it runs synchronously the first time (so the first rendered
frames already use the right zoom) and then refreshes in the background at
most every 2 seconds to pick up re-trims. When the same file is used multiple
times, the item containing the rendered frame is used. The range is part of
the manager cache key, because pieces of a blade-cut clip share the instance
id.

Only the zoom is fit to the range: smoothing still uses the whole clip
(`trim_range_only` = 0), so the camera motion doesn't change and the option can
only reduce the zoom.

The new "Zoom only for used part" checkbox is enabled by default and requires
Resolve Studio with external scripting set to Local. Without it, nothing
changes.
@CLAassistant

CLAassistant commented Sep 28, 2026 •

Copy link
Copy Markdown

CLA assistant check
All committers have signed the CLA.

Resolve runs the script on its main thread, so refresh the used ranges only
after a pause in rendering (eg. after trimming a clip), and do it synchronously
so the rendered frames always use the current range.
Resolve 21 answers scripting calls only after it gets the frame being rendered, so waiting for fuscript
in render() deadlocks the playback. Query in a background thread, wait at most 300 ms, kill fuscript after 10 s.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants