Files
ausboss-ComfyUI-AusBoss/js/shared/core_preview.mjs
ausboss 0c404b37f3 Save Video: a saved gif or webp shows on the node
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.
2026-10-06 17:35:31 -05:00

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,
);
}