From a35071fcb27cc86fab98fd3ba3ea26a6aa430709 Mon Sep 17 00:00:00 2001 From: bymyself Date: Mon, 24 Feb 2025 08:48:55 -0700 Subject: [PATCH] Add previewMediaType flag for simpler node preview rendering (#2694) Co-authored-by: huchenlei --- src/composables/useNodeImage.ts | 16 ++++--- src/extensions/core/uploadAudio.ts | 2 + src/types/litegraph-augmentation.d.ts | 4 +- src/utils/litegraphUtil.ts | 62 ++++----------------------- 4 files changed, 23 insertions(+), 61 deletions(-) diff --git a/src/composables/useNodeImage.ts b/src/composables/useNodeImage.ts index bd938e213..84e187a25 100644 --- a/src/composables/useNodeImage.ts +++ b/src/composables/useNodeImage.ts @@ -10,6 +10,7 @@ const VIDEO_DEFAULT_OPTIONS = { } as const const MEDIA_LOAD_TIMEOUT = 8192 as const const MAX_RETRIES = 1 as const +const VIDEO_PIXEL_OFFSET = 64 as const type MediaElement = HTMLImageElement | HTMLVideoElement @@ -54,11 +55,6 @@ export const useNodePreview = ( const loadElements = async (urls: string[]) => Promise.all(urls.map((url) => loadElementWithTimeout(url))) - const render = () => { - node.setSizeForImage?.() - node.graph?.setDirtyCanvas(true) - } - /** * Displays media element(s) on the node. */ @@ -77,7 +73,7 @@ export const useNodePreview = ( ) if (validElements.length) { onLoaded?.(validElements) - render() + node.graph?.setDirtyCanvas(true) } }) .catch(() => { @@ -97,6 +93,8 @@ export const useNodePreview = ( * Attaches a preview image to a node. */ export const useNodeImage = (node: LGraphNode) => { + node.previewMediaType = 'image' + const loadElement = (url: string): Promise => new Promise((resolve) => { const img = new Image() @@ -108,6 +106,7 @@ export const useNodeImage = (node: LGraphNode) => { const onLoaded = (elements: HTMLImageElement[]) => { node.imageIndex = null node.imgs = elements + node.setSizeForImage?.() } return useNodePreview(node, { @@ -123,6 +122,8 @@ export const useNodeImage = (node: LGraphNode) => { * Attaches a preview video to a node. */ export const useNodeVideo = (node: LGraphNode) => { + node.previewMediaType = 'video' + const loadElement = (url: string): Promise => new Promise((resolve) => { const video = document.createElement('video') @@ -152,7 +153,8 @@ export const useNodeVideo = (node: LGraphNode) => { } node.videoContainer.replaceChildren(videoElement) - node.imageOffset = 64 + node.imageOffset = VIDEO_PIXEL_OFFSET + node.setSizeForImage?.(true) } return useNodePreview(node, { diff --git a/src/extensions/core/uploadAudio.ts b/src/extensions/core/uploadAudio.ts index fd903bcc2..14a81cb3e 100644 --- a/src/extensions/core/uploadAudio.ts +++ b/src/extensions/core/uploadAudio.ts @@ -199,6 +199,8 @@ app.registerExtension({ ) uploadWidget.label = 'choose file to upload' + node.previewMediaType = 'audio' + return { widget: uploadWidget } } } diff --git a/src/types/litegraph-augmentation.d.ts b/src/types/litegraph-augmentation.d.ts index 966b2d95d..33be37010 100644 --- a/src/types/litegraph-augmentation.d.ts +++ b/src/types/litegraph-augmentation.d.ts @@ -129,6 +129,8 @@ declare module '@comfyorg/litegraph' { videoContainer?: HTMLElement /** Whether the node's preview media is loading */ isLoading?: boolean + /** The content type of the node's preview media */ + previewMediaType?: 'image' | 'video' | 'audio' | 'model' preview: string[] /** Index of the currently selected image on a multi-image node such as Preview Image */ @@ -141,7 +143,7 @@ declare module '@comfyorg/litegraph' { /** @deprecated Unused */ inputHeight?: unknown - /** @deprecated Unused */ + /** The y offset of the image preview to the top of the node body. */ imageOffset?: number /** Callback for pasting an image file into the node */ pasteFile?(file: File): void diff --git a/src/utils/litegraphUtil.ts b/src/utils/litegraphUtil.ts index 4fedcf6cd..2c6d9e076 100644 --- a/src/utils/litegraphUtil.ts +++ b/src/utils/litegraphUtil.ts @@ -8,67 +8,23 @@ import { import type { IComboWidget } from '@comfyorg/litegraph/dist/types/widgets' import _ from 'lodash' -import { ComfyInputsSpec, ComfyNodeDef, InputSpec } from '@/types/apiTypes' - -const IMAGE_NODE_PROPERTY = 'image_upload' -const VIDEO_NODE_PROPERTY = 'video_upload' - -const getNodeData = (node: LGraphNode): ComfyNodeDef | undefined => - node.constructor?.nodeData as ComfyNodeDef | undefined - -const getInputSpecsFromData = ( - inputData: ComfyInputsSpec | undefined -): InputSpec[] => { - if (!inputData) return [] - - const { required, optional } = inputData - const inputSpecs: InputSpec[] = [] - if (required) { - for (const value of Object.values(required)) { - inputSpecs.push(value) - } - } - if (optional) { - for (const value of Object.values(optional)) { - inputSpecs.push(value) - } - } - return inputSpecs +type ImageNode = LGraphNode & { imgs: HTMLImageElement[] | undefined } +type VideoNode = LGraphNode & { + videoContainer: HTMLElement | undefined + imgs: HTMLVideoElement[] | undefined } -const hasImageElements = (imgs: unknown[]): boolean => - Array.isArray(imgs) && - imgs.some((img): img is HTMLImageElement => img instanceof HTMLImageElement) - -const hasInputProperty = ( - node: LGraphNode | undefined, - property: string -): boolean => { - if (!node) return false - const nodeData = getNodeData(node) - if (!nodeData?.input) return false - - const inputs = getInputSpecsFromData(nodeData.input) - return inputs.some((input) => input?.[1]?.[property]) -} - -type ImageNode = LGraphNode & { imgs: HTMLImageElement[] } -type VideoNode = LGraphNode & { videoContainer: HTMLElement } - export function isImageNode(node: LGraphNode | undefined): node is ImageNode { if (!node) return false - if (node.imgs?.length && hasImageElements(node.imgs)) return true - if (!node.widgets) return false - - return hasInputProperty(node, IMAGE_NODE_PROPERTY) + return ( + node.previewMediaType === 'image' || + (node.previewMediaType !== 'video' && !!node.imgs?.length) + ) } export function isVideoNode(node: LGraphNode | undefined): node is VideoNode { if (!node) return false - if (node.videoContainer) return true - if (!node.widgets) return false - - return hasInputProperty(node, VIDEO_NODE_PROPERTY) + return node.previewMediaType === 'video' || !!node.videoContainer } export function addToComboValues(widget: IComboWidget, value: string) {