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:
@@ -125,7 +125,7 @@ VIDEO_FORMATS = {
|
||||
"ffv1-mkv": {"ext": ".mkv", "codec": ["-c:v", "ffv1", "-level", "3",
|
||||
"-coder", "1", "-context", "1", "-g", "1",
|
||||
"-slices", "16", "-slicecrc", "1"],
|
||||
"quality": "lossless", "pix_fmt": "yuv444p"},
|
||||
"quality": "lossless"},
|
||||
"prores-mov": {"ext": ".mov", "codec": ["-c:v", "prores_ks"],
|
||||
"quality": "profile", "color_mgmt": True},
|
||||
"nvenc_h264-mp4":{"ext": ".mp4", "codec": ["-c:v", "h264_nvenc"],
|
||||
@@ -175,7 +175,7 @@ class FastAbsoluteSaver:
|
||||
# --- VIDEO SPECIFIC ---
|
||||
"video_fps": ("INT", {"default": 24, "min": 1, "max": 120, "step": 1, "label": "Video FPS"}),
|
||||
"video_crf": ("INT", {"default": 18, "min": 0, "max": 51, "step": 1, "label": "Video CRF (0=Lossless, 51=Worst)"}),
|
||||
"video_pixel_format": (["yuv420p", "yuv444p", "yuv420p10le"], {"label": "Pixel Format"}),
|
||||
"video_pixel_format": (["yuv420p", "yuv422p", "yuv444p", "yuv420p10le", "rgb24", "bgra"], {"label": "Pixel Format"}),
|
||||
"video_bitrate": ("INT", {"default": 10, "min": 1, "max": 999, "step": 1, "label": "Video Bitrate (Mbps, NVENC)"}),
|
||||
"prores_profile": (["lt", "standard", "hq", "4444", "4444xq"], {"label": "ProRes Profile"}),
|
||||
"gif_dither": (["sierra2_4a", "floyd_steinberg", "bayer", "sierra2", "sierra3", "burkes", "atkinson", "heckbert", "none"], {"label": "GIF Dither Algorithm"}),
|
||||
|
||||
109
web/fast_saver.js
Normal file
109
web/fast_saver.js
Normal 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));
|
||||
};
|
||||
},
|
||||
});
|
||||
Reference in New Issue
Block a user