clear-saver-outputs
ClearSaverOutputs: Pre-Render Cleanup for Fusion Studio
Fusion Studio is incredibly powerful for complex motion graphics work, but its output management is nowhere near as polished as DaVinci Resolve's Deliver page. When I re-render a comp, old frames and movie files stick around, leading to mixed old/new frames in sequences, file lock issues on network drives, and constant confusion about which render is the latest. This script automates the pre-render cleanup that would otherwise require manually deleting files before every render.
The Problem
DaVinci Resolve's Deliver page makes rendering clean and straightforward:
- Set your codec and format
- Check "Delete before render" if you want a fresh output
- Click Render
- Done
Fusion Studio (the standalone compositor) doesn't have this workflow. Instead:
- You set up Saver nodes pointing to output paths
- You render the comp
- If you re-render, old outputs are still there
This causes real problems:
1. Mixed old/new frames in image sequences
If I render frames 1-100, then later re-render frames 50-75 (maybe I tweaked the animation), my output folder now has:
- Frames 1-49: old render
- Frames 50-75: new render
- Frames 76-100: old render
When I import this sequence into DaVinci Resolve or After Effects, I get a Frankenstein timeline—half old render, half new render. I won't notice until I play through and see the inconsistency, then I have to go back, manually delete the old frames, and re-render.
2. File lock issues with movie outputs
Rendering to .mov or .mp4 files on network drives (which I do for client work) can cause file lock errors. If the previous render is still "in use" by the OS or a network share, Fusion can't overwrite it. The render fails, and I have to manually delete the old file, wait for the network lock to release, then re-render.
3. "Which render is this?" confusion
When I have multiple iterations of a comp (client revisions, color tweaks, etc.), I often forget whether the output file is from the latest render or a previous version. Without automatic cleanup, I have to check file timestamps, which is tedious and error-prone.
The manual solution: Before every render, manually delete old outputs.
For image sequences, this means:
- Navigate to the output folder in Finder
- Select all frames in the render range
- Delete them
- Return to Fusion, trigger the render
For movie files:
- Navigate to the output folder
- Delete the old
.movfile - Return to Fusion, trigger the render
Estimated time: 15-30 seconds per render, depending on how many Saver nodes I have and how nested the output folders are. And I'm coming from DaVinci Resolve, where this is automatic. The friction is annoying.
The Solution
ClearSaverOutputs.lua is a Fusion pre-render script that automates cleanup before rendering starts. I bind it to a keyboard shortcut (Cmd+Shift+R in my case), and it handles everything:
- Image sequences: Deletes existing frames in the render range
- Movie files: Redirects Savers to render to a temporary filename (
.rendering.TIMESTAMP.ext), preventing overwrites and file locks - Opt-out mechanism: Put
[KEEP]in a Saver's Comments field to skip cleanup for that output
The workflow is now:
- Make changes to the comp
- Hit Cmd+Shift+R (runs ClearSaverOutputs script)
- Hit Cmd+R (standard Fusion render shortcut)
- Render completes with clean outputs
Total time: 2 seconds. No manual file deletion, no folder navigation, no checking timestamps.
Technical Implementation
This script runs as a PreRender hook in Fusion Studio, meaning it executes before the render starts. It iterates through all Saver nodes in the comp and applies different cleanup strategies depending on output type.
1. Detecting Output Type
The script categorizes outputs by file extension:
local MOVIE_EXT = { mov=true, mp4=true, mxf=true, avi=true, m4v=true, mpg=true, mpeg=true, mkv=true }
local IMAGE_EXT = { exr=true, dpx=true, tif=true, tiff=true, png=true, jpg=true, jpeg=true, tga=true, bmp=true, webp=true }
This lookup table approach is faster than pattern matching and easier to extend. Adding support for a new format (e.g., .webm for movie outputs) is a simple table insert.
2. Movie File Strategy: Temp Name Redirection
For movie outputs, the script redirects the Saver to render to a temporary filename:
if is_movie_ext(ext) then
local tmp = file:gsub("%."..ext.."$", "") .. ".rendering." .. now_ms() .. "." .. ext
local final_abs = join(dir, file)
local tmp_abs = join(dir, tmp)
s.Clip = tmp_abs
remember("MAP|"..(sa.TOOLB_Name or "Saver").."|"..final_abs.."|"..tmp_abs)
print("[PreRender] Movie redirect: "..final_abs.." -> "..tmp_abs)
end
How this works:
- Original Saver path:
/Volumes/Work/client_project/output.mov - Script redirects to:
/Volumes/Work/client_project/output.rendering.1735025847234.mov - The render writes to the temp file, not the final file
- After rendering completes, a PostRender script (not shown here, but part of my workflow) swaps the temp file to the final name
Why use temp names instead of direct overwrite?
- Prevents partial overwrites: If the render fails halfway through, the old
output.movis still intact. Without temp naming, a failed render would leave a corrupted half-written file. - Avoids file lock issues: Network drives (SMB, NFS) sometimes hold locks on open files. Rendering to a new temp file avoids conflicts.
- Atomic swap: The PostRender script uses
os.rename()to swap the temp file to the final name. This is an atomic operation on most filesystems, meaning there's no moment where the file is "half-moved."
The now_ms() function generates a unique timestamp to ensure temp names never collide:
local function now_ms()
return tostring(os.time())..tostring(math.floor((os.clock()%1)*1000))
end
This combines seconds-since-epoch with milliseconds, so even if I render twice in the same second, the temp names are unique.
3. Image Sequence Strategy: Delete In-Range Frames
For image sequences, the script reads the output directory and deletes frames matching the Saver's naming pattern:
if is_image_ext(ext) and SEQ_POLICY ~= "none" then
local base = file:gsub("%.%d+%."..ext.."$", ""):gsub("_%d+%."..ext.."$", ""):gsub("%."..ext.."$", "")
local files = bmd.readdir(dir)
if files then
for _, f in ipairs(files) do
local num = f:match("^"..base.."%.(%d+)%.%w+$") or f:match("^"..base.."_([0-9]+)%.%w+$")
local fext = (f:match("%.(%w+)$") or ""):lower()
if num and fext == lower(ext) then
local fn = tonumber(num)
local in_range = (SEQ_POLICY=="delete_all") or (fn and fn>=R0 and fn<=R1)
if in_range then
local p = join(dir, f)
if os.remove(p) then print("[PreRender] Deleted: "..p) end
end
end
end
end
end
Step-by-step breakdown:
- Extract base name: If the Saver outputs
shot_001.0001.exr, the base name isshot_001. The regex strips the frame number and extension. - Read directory:
bmd.readdir(dir)lists all files in the output folder. - Match frames: For each file, check if it matches the base name pattern (
shot_001.0001.exr,shot_001.0002.exr, etc.). - Check range: Extract the frame number. If
SEQ_POLICY == "delete_range", only delete frames within the render range (R0toR1). IfSEQ_POLICY == "delete_all", delete all matching frames. - Delete:
os.remove(p)deletes the file.
Why range-based deletion?
The SEQ_POLICY variable controls cleanup behavior:
delete_range(default): Only delete frames within the current render range. If I'm rendering frames 50-75, only frames 50-75 are deleted. Frames 1-49 and 76-100 are preserved.delete_all: Delete all frames matching the Saver's naming pattern, regardless of render range.none: Skip sequence cleanup entirely.
I use delete_range because I sometimes render partial frame ranges for client revisions. If I re-render frames 50-75 (because the client requested a timing change), I want to delete only those frames, not the entire sequence.
4. Opt-Out Mechanism: [KEEP] Comment Tag
Some Saver nodes should never be cleaned—for example, a reference render I want to preserve for comparison. The script checks each Saver's Comments field:
local comments = lower(s.Comments and s.Comments[comp.CurrentTime] or "")
if comments:find("%[keep%]") then goto continue end
If a Saver's Comments field contains [KEEP] (case-insensitive), the script skips cleanup for that node. This is a simple, non-intrusive opt-out—I don't have to disable the script or modify code, just add a comment to the node.
5. Passing State to PostRender
For movie outputs, the script redirects the Saver path to a temp file. But how does the PostRender script know which temp files to finalize?
The script writes a state file to /tmp/fusion_clear_<compname>.lst:
local function state_path()
local tmp = os.getenv("TMPDIR") or os.getenv("TEMP") or os.getenv("TMP") or "/tmp"
local compname = (comp:GetAttrs().COMPN_FileName or "untitled"):gsub("[^%w]+","_")
return join(tmp, "fusion_clear_"..compname..".lst")
end
local function remember(line) table.insert(STATE, line) end
local function save_state()
if #STATE == 0 then return end
local f = io.open(state_path(), "w")
if not f then return end
for _, L in ipairs(STATE) do f:write(L.."\n") end
f:close()
end
Each movie Saver generates a line like:
MAP|Saver1|/Volumes/Work/output.mov|/Volumes/Work/output.rendering.1735025847234.mov
The PostRender script reads this file, swaps the temp files to their final names, and cleans up the state file. This decouples PreRender and PostRender logic—the PostRender script doesn't need to re-scan all Savers, it just reads the state file.
Use Cases
Primary: Pre-Render Cleanup for Complex Fusion Comps
I use this when building large motion graphics in Fusion Studio (1-2 times per month). These comps typically have:
- Multiple Saver nodes (main output, alpha channel, depth pass, etc.)
- Image sequence outputs (EXR for compositing, PNG for client preview)
- Movie outputs (ProRes for editorial review)
Before this script, I'd manually delete old frames and movie files before each render. Now I hit Cmd+Shift+R, and cleanup is automatic.
Secondary: Iterative Rendering for Client Revisions
When a client requests changes to a delivered comp ("Can you adjust the timing on frames 50-75?"), I:
- Make the changes in Fusion
- Set the render range to frames 50-75
- Run ClearSaverOutputs (deletes only frames 50-75)
- Render
This ensures the output folder has the latest frames 50-75 mixed with the original frames 1-49 and 76-100, without manual file deletion.
Why Not Just Use Fusion's Built-In "Delete Before Render"?
Fusion Studio doesn't have a "Delete Before Render" option in the UI. You'd have to write a Lua script to do it… which is exactly what this is.
Time Savings
Manual process: Navigate to output folders, select and delete old frames/movies for each Saver node. Estimated time: 15-30 seconds depending on folder structure and number of Savers.
Automated process: Hit Cmd+Shift+R (script runs in ~0.5 seconds), then render. Total time: 0.5 seconds.
Savings per use: 14.5-29.5 seconds.
I use this 1-2 times per month when working in Fusion Studio. At 2 times per month, this saves 29-59 seconds (0.5-1 minute) monthly.
But the real value is eliminating mental overhead. I don't have to remember to delete old files before rendering. I don't have to worry about mixed old/new frames or file lock errors. The script handles it, and I can focus on the creative work.
Implementation Notes
This script requires:
- Fusion Studio (standalone compositor, not the Fusion page in DaVinci Resolve)
- Lua scripting enabled (default in Fusion Studio)
Setting Up as a PreRender Script
To run this script before every render:
Save
ClearSaverOutputs.luato your Fusion scripts folder:- macOS:
~/Library/Application Support/Blackmagic Design/Fusion/Scripts/Comp/ - Windows:
C:\Users\[USERNAME]\AppData\Roaming\Blackmagic Design\Fusion\Scripts\Comp\
- macOS:
Bind it to a keyboard shortcut:
- Open Fusion Studio
- Go to Fusion → Preferences → Global and Default Settings → Scripting
- Under Comp Scripts, assign a shortcut to
ClearSaverOutputs
Before rendering, hit your shortcut (e.g., Cmd+Shift+R), then render as normal (Cmd+R).
Customizing Sequence Cleanup Policy
The SEQ_POLICY variable controls how image sequences are cleaned:
local SEQ_POLICY = "delete_range"
Options:
delete_range: Delete only frames within the render range (default, safest)delete_all: Delete all frames matching the Saver's naming patternnone: Skip sequence cleanup entirely
I recommend delete_range for most workflows—it's the safest option for iterative rendering.
Adding New File Formats
To add support for a new movie or image format, just update the lookup tables:
local MOVIE_EXT = { mov=true, mp4=true, webm=true } -- Added webm
local IMAGE_EXT = { exr=true, dpx=true, tga=true }
Using the [KEEP] Opt-Out
To prevent cleanup for a specific Saver node:
- Select the Saver in the Flow view
- Open the Comments field (right panel, under "Comments")
- Add
[KEEP]anywhere in the comments (case-insensitive)
The script will skip cleanup for that Saver, preserving its existing output.
Why This Matters
Fusion Studio is an incredible compositor, but its output management feels dated compared to DaVinci Resolve's streamlined Deliver page. This script bridges that gap by automating the pre-render cleanup that would otherwise be manual, tedious, and error-prone.
For anyone working in Fusion Studio for complex motion graphics or VFX work—especially if you're coming from Resolve and expect a polished render workflow—this script eliminates friction and prevents the "mixed old/new frames" frustration that plagues iterative rendering.
The temp name redirection for movie outputs is a defensive scripting pattern worth adopting: instead of overwriting files directly, render to a temp name and swap after success. This prevents partial overwrites, file lock issues, and data loss from failed renders. It's the same strategy browsers use for downloads ("Downloading… → Move to Downloads folder"), and it's just as valuable for rendering.
The SEQ_POLICY flexibility is key for iterative workflows. Range-based deletion means I can re-render a subset of frames without destroying the entire sequence, which is critical for client revision work.
And the [KEEP] opt-out mechanism shows thoughtful API design: instead of forcing users to modify code or disable the script globally, provide an inline, per-node override. It's a pattern I use in all my automation scripts—sensible defaults with easy escape hatches.