Add dynamic widget visibility and per-format pixel formats for FastAbsoluteSaver

Hide/show format-specific widgets (CRF, bitrate, ProRes profile, GIF dither,
pixel format, webp settings) based on selected save_format. Pixel format combo
updates dynamically per codec. Remove hardcoded ffv1 pix_fmt to use widget value.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-05 15:20:20 +01:00
parent e70127d1a3
commit b64636c189
2 changed files with 111 additions and 2 deletions

109
web/fast_saver.js Normal file
View File

@@ -0,0 +1,109 @@
import { app } from "../../scripts/app.js";
const FORMAT_WIDGETS = {
"png": [],
"webp": ["webp_lossless", "webp_quality", "webp_method"],
"mp4": ["video_fps", "video_crf", "video_pixel_format"],
"h265-mp4": ["video_fps", "video_crf", "video_pixel_format"],
"av1-mp4": ["video_fps", "video_crf", "video_pixel_format"],
"webm": ["video_fps", "video_crf", "video_pixel_format"],
"gif": ["video_fps", "gif_dither"],
"ffv1-mkv": ["video_fps", "video_pixel_format"],
"prores-mov": ["video_fps", "prores_profile"],
"nvenc_h264-mp4": ["video_fps", "video_pixel_format", "video_bitrate"],
"nvenc_hevc-mp4": ["video_fps", "video_pixel_format", "video_bitrate"],
"nvenc_av1-mp4": ["video_fps", "video_pixel_format", "video_bitrate"],
};
const FORMAT_PIX_FMTS = {
"mp4": ["yuv420p", "yuv444p"],
"h265-mp4": ["yuv420p", "yuv444p", "yuv420p10le"],
"av1-mp4": ["yuv420p", "yuv420p10le"],
"webm": ["yuv420p", "yuv444p", "yuv420p10le"],
"ffv1-mkv": ["yuv420p", "yuv422p", "yuv444p", "rgb24", "bgra"],
"nvenc_h264-mp4": ["yuv420p", "yuv444p"],
"nvenc_hevc-mp4": ["yuv420p", "yuv444p", "yuv420p10le"],
"nvenc_av1-mp4": ["yuv420p", "yuv420p10le"],
};
const ALL_MANAGED = [
"webp_lossless", "webp_quality", "webp_method",
"video_fps", "video_crf", "video_pixel_format",
"video_bitrate", "prores_profile", "gif_dither",
];
function hideWidget(node, widget) {
if (widget.origType === undefined) widget.origType = widget.type;
widget.type = "hidden";
widget.computeSize = () => [0, -4];
}
function showWidget(node, widget) {
if (widget.origType !== undefined) {
widget.type = widget.origType;
delete widget.origType;
delete widget.computeSize;
}
}
function updateVisibility(node) {
const formatWidget = node.widgets?.find(w => w.name === "save_format");
if (!formatWidget) return;
const format = formatWidget.value;
const visible = new Set(FORMAT_WIDGETS[format] || []);
for (const name of ALL_MANAGED) {
const w = node.widgets?.find(w => w.name === name);
if (!w) continue;
if (visible.has(name)) {
showWidget(node, w);
} else {
hideWidget(node, w);
}
}
// Update pixel format combo options
const pixWidget = node.widgets?.find(w => w.name === "video_pixel_format");
if (pixWidget && FORMAT_PIX_FMTS[format]) {
const opts = FORMAT_PIX_FMTS[format];
pixWidget.options.values = opts;
if (!opts.includes(pixWidget.value)) {
pixWidget.value = opts[0];
}
}
node.setSize(node.computeSize());
app.graph?.setDirtyCanvas(true, true);
}
app.registerExtension({
name: "fast.absolute.saver.visibility",
async beforeRegisterNodeDef(nodeType, nodeData, app) {
if (nodeData.name !== "FastAbsoluteSaver") return;
const origOnNodeCreated = nodeType.prototype.onNodeCreated;
nodeType.prototype.onNodeCreated = function () {
origOnNodeCreated?.apply(this, arguments);
const formatWidget = this.widgets?.find(w => w.name === "save_format");
if (formatWidget) {
const origCallback = formatWidget.callback;
formatWidget.callback = (...args) => {
origCallback?.apply(formatWidget, args);
updateVisibility(this);
};
}
// Defer initial update so all widgets exist
queueMicrotask(() => updateVisibility(this));
};
const origOnConfigure = nodeType.prototype.onConfigure;
nodeType.prototype.onConfigure = function (info) {
origOnConfigure?.apply(this, arguments);
queueMicrotask(() => updateVisibility(this));
};
},
});