import { app } from '../../../scripts/app.js' import { api } from '../../../scripts/api.js' function offsetDOMWidget( widget, ctx, node, widgetWidth, widgetY, height ) { const margin = 10 const elRect = ctx.canvas.getBoundingClientRect() const transform = new DOMMatrix() .scaleSelf( elRect.width / ctx.canvas.width, elRect.height / ctx.canvas.height ) .multiplySelf(ctx.getTransform()) .translateSelf(0, widgetY + margin) const scale = new DOMMatrix().scaleSelf(transform.a, transform.d) Object.assign(widget.inputEl.style, { transformOrigin: '0 0', transform: scale, left: `${transform.e}px`, top: `${transform.d + transform.f}px`, width: `${widgetWidth}px`, height: `${(height || widget.parent?.inputHeight || 32) - margin}px`, position: 'absolute', background: !node.color ? '' : node.color, color: !node.color ? '' : 'white', zIndex: 5, //app.graph._nodes.indexOf(node), }) } export const hasWidgets = (node) => { if (!node.widgets || !node.widgets?.[Symbol.iterator]) { return false } return true } export const cleanupNode = (node) => { if (!hasWidgets(node)) { return } for (const w of node.widgets) { if (w.canvas) { w.canvas.remove() } if (w.inputEl) { w.inputEl.remove() } // calls the widget remove callback w.onRemoved?.() } } const CreatePreviewElement = (name, val, format) => { const [type] = format.split('/') const w = { name, type, value: val, draw: function (ctx, node, widgetWidth, widgetY, height) { const [cw, ch] = this.computeSize(widgetWidth) offsetDOMWidget(this, ctx, node, widgetWidth, widgetY, ch) }, computeSize: function (_) { const ratio = this.inputRatio || 1 const width = Math.max(220, this.parent.size[0]) return [width, (width / ratio + 10)] }, onRemoved: function () { if (this.inputEl) { this.inputEl.remove() } }, } w.inputEl = document.createElement(type === 'video' ? 'video' : 'img') w.inputEl.src = w.value if (type === 'video') { w.inputEl.setAttribute('type', 'video/webm'); w.inputEl.autoplay = true w.inputEl.loop = true w.inputEl.controls = false; } w.inputEl.onload = function () { w.inputRatio = w.inputEl.naturalWidth / w.inputEl.naturalHeight } document.body.appendChild(w.inputEl) return w } const gif_preview = { name: 'AnimateDiff.gif_preview', async beforeRegisterNodeDef(nodeType, nodeData, app) { switch (nodeData.name) { case 'ADE_AnimateDiffCombine':{ const onExecuted = nodeType.prototype.onExecuted nodeType.prototype.onExecuted = function (message) { const prefix = 'ad_gif_preview_' const r = onExecuted ? onExecuted.apply(this, message) : undefined if (this.widgets) { const pos = this.widgets.findIndex((w) => w.name === `${prefix}_0`) if (pos !== -1) { for (let i = pos; i < this.widgets.length; i++) { this.widgets[i].onRemoved?.() } this.widgets.length = pos } if (message?.gifs) { message.gifs.forEach((params, i) => { const previewUrl = api.apiURL( '/view?' + new URLSearchParams(params).toString() ) const w = this.addCustomWidget( CreatePreviewElement(`${prefix}_${i}`, previewUrl, params.format || 'image/gif') ) w.parent = this }) } const onRemoved = this.onRemoved this.onRemoved = () => { cleanupNode(this) return onRemoved?.() } } this.setSize([this.size[0], this.computeSize([this.size[0], this.size[1]])[1]]) return r } break } } } } app.registerExtension(gif_preview)