With format set to gif or webp the file was saved, but the node's stage stayed empty and said "Execution finished without video metadata". The node has shown a gif or webp in an <img> since the two formats were added, because a <video> cannot play either. The run's result was still searched with a list of video file endings only, so for these two nothing was found and the <img> was never filled. The list of picture endings now sits beside that lookup in js/shared/video_preview.mjs, and both the lookup and the choice of tag read it. A png is still not taken. In the classic renderer a saved webp was also drawn a second time under the stage. ComfyUI shows an animated webp in a widget of its own, neither its video preview nor its picture preview, and only those two were stood down. suppressCoreVideoPreview hides that widget as well. The node's smallest height with a saved webp goes from 469 px back to 415 px. Tests: the lookup finds a saved gif and webp and sends them to the <img>; every row of the backend's VIDEO_FORMATS is read from its source and has to be found and sent to the right tag; the animated widget is hidden on arrival and when it is already on the node.
95 lines
3.9 KiB
JavaScript
95 lines
3.9 KiB
JavaScript
// Stand ComfyUI's own in-node previews down on nodes that draw their own.
|
|
//
|
|
// A node that returns a ui payload — or streams progress previews — gets a
|
|
// preview from the frontend automatically: an image one as the canvas widget
|
|
// named $$canvas-image-preview, a video one as the DOM widget named
|
|
// video-preview. Every AusBoss node here already presents that same result
|
|
// inside its own panel, so left alone the node shows it twice, once in the
|
|
// panel and once again underneath.
|
|
//
|
|
// The widgets are hidden rather than removed. The frontend re-adds a preview
|
|
// whenever it finds none and the node has images, so removing one just brings
|
|
// it back on the next frame; a hidden widget is still found by name and left
|
|
// alone. Its value is untouched either way, and neither serializes.
|
|
|
|
export const CORE_VIDEO_PREVIEW_WIDGET = "video-preview";
|
|
export const CORE_IMAGE_PREVIEW_WIDGET = "$$canvas-image-preview";
|
|
// An animated webp is a picture to the frontend, not a video, and it gets a
|
|
// third widget: a DOM one like the video preview, under a name of its own.
|
|
export const CORE_ANIMATED_PREVIEW_WIDGET = "$$comfy_animation_preview";
|
|
|
|
export function hideCanvasWidget(widget, element = null) {
|
|
if (!widget || widget.__ausbossHidden) return;
|
|
widget.__ausbossHidden = true;
|
|
widget.hidden = true;
|
|
widget._hidden = true;
|
|
widget.computeSize = () => [0, -4];
|
|
widget.computeLayoutSize = () => ({ minWidth: 0, minHeight: 0 });
|
|
widget.draw = () => {};
|
|
widget.mouse = () => false;
|
|
if (element) element.style.display = "none";
|
|
}
|
|
|
|
// Wrap one of the node's own add*Widget methods so the named widget is hidden
|
|
// the moment the frontend creates it, and sweep any copy already present.
|
|
function suppressWidget(node, flag, method, targetName, elementOf) {
|
|
if (!node || node[flag]) return;
|
|
node[flag] = true;
|
|
const prior = node[method];
|
|
if (typeof prior === "function") {
|
|
node[method] = function (...args) {
|
|
const widget = prior.apply(this, args);
|
|
if (widget?.name === targetName || args[0] === targetName) {
|
|
hideCanvasWidget(widget, elementOf(widget, args));
|
|
}
|
|
return widget;
|
|
};
|
|
}
|
|
for (const widget of node.widgets || []) {
|
|
if (widget?.name === targetName) hideCanvasWidget(widget, elementOf(widget, []));
|
|
}
|
|
}
|
|
|
|
export function suppressCoreVideoPreview(node) {
|
|
// Nodes 2.0 has no video-preview widget to hide: it draws the node's
|
|
// output under the widgets itself unless the node sets this flag (the
|
|
// classic renderer never reads it). Without it Save Video showed its clip
|
|
// twice there, once in the panel and once underneath.
|
|
if (node) node.hideOutputImages = true;
|
|
suppressWidget(
|
|
node,
|
|
"__ausbossCorePreviewSuppressed",
|
|
"addDOMWidget",
|
|
CORE_VIDEO_PREVIEW_WIDGET,
|
|
(widget, args) => args[2] || widget?.element || node.videoContainer,
|
|
);
|
|
// A clip saved as an animated webp arrives in the animated widget instead.
|
|
// Left alone, Save Video drew it a second time under its stage in the
|
|
// classic renderer, and the node's smallest height grew to hold it.
|
|
suppressWidget(
|
|
node,
|
|
"__ausbossCoreAnimatedPreviewSuppressed",
|
|
"addDOMWidget",
|
|
CORE_ANIMATED_PREVIEW_WIDGET,
|
|
(widget, args) => args[2] || widget?.element,
|
|
);
|
|
}
|
|
|
|
export function suppressCoreImagePreview(node) {
|
|
// Nodes 2.0 draws its own copy of the node's output images under the
|
|
// widgets unless the node sets this flag (classic never reads it). That
|
|
// copy is also the only thing that fills node.imgs there, so a node that
|
|
// stands it down reads its result from app.nodeOutputs instead
|
|
// (js/input_preview).
|
|
if (node) node.hideOutputImages = true;
|
|
// The image preview arrives through addCustomWidget, not addDOMWidget: it
|
|
// is painted straight onto the graph canvas and owns no element.
|
|
suppressWidget(
|
|
node,
|
|
"__ausbossCoreImagePreviewSuppressed",
|
|
"addCustomWidget",
|
|
CORE_IMAGE_PREVIEW_WIDGET,
|
|
() => null,
|
|
);
|
|
}
|