Spaces:
Build error
Build error
| import { app } from "../../scripts/app.js"; | |
| import { api } from "../../scripts/api.js"; | |
| const WEBCAM_READY = Symbol(); | |
| app.registerExtension({ | |
| name: "Comfy.WebcamCapture", | |
| getCustomWidgets(app) { | |
| return { | |
| WEBCAM(node, inputName) { | |
| let res; | |
| node[WEBCAM_READY] = new Promise((resolve) => (res = resolve)); | |
| const container = document.createElement("div"); | |
| container.style.background = "rgba(0,0,0,0.25)"; | |
| container.style.textAlign = "center"; | |
| const video = document.createElement("video"); | |
| video.style.height = video.style.width = "100%"; | |
| const loadVideo = async () => { | |
| try { | |
| const stream = await navigator.mediaDevices.getUserMedia({ video: true, audio: false }); | |
| container.replaceChildren(video); | |
| setTimeout(() => res(video), 500); // Fallback as loadedmetadata doesnt fire sometimes? | |
| video.addEventListener("loadedmetadata", () => res(video), false); | |
| video.srcObject = stream; | |
| video.play(); | |
| } catch (error) { | |
| const label = document.createElement("div"); | |
| label.style.color = "red"; | |
| label.style.overflow = "auto"; | |
| label.style.maxHeight = "100%"; | |
| label.style.whiteSpace = "pre-wrap"; | |
| if (window.isSecureContext) { | |
| label.textContent = "Unable to load webcam, please ensure access is granted:\n" + error.message; | |
| } else { | |
| label.textContent = "Unable to load webcam. A secure context is required, if you are not accessing ComfyUI on localhost (127.0.0.1) you will have to enable TLS (https)\n\n" + error.message; | |
| } | |
| container.replaceChildren(label); | |
| } | |
| }; | |
| loadVideo(); | |
| return { widget: node.addDOMWidget(inputName, "WEBCAM", container) }; | |
| }, | |
| }; | |
| }, | |
| nodeCreated(node) { | |
| if ((node.type, node.constructor.comfyClass !== "WebcamCapture")) return; | |
| let video; | |
| const camera = node.widgets.find((w) => w.name === "image"); | |
| const w = node.widgets.find((w) => w.name === "width"); | |
| const h = node.widgets.find((w) => w.name === "height"); | |
| const captureOnQueue = node.widgets.find((w) => w.name === "capture_on_queue"); | |
| const canvas = document.createElement("canvas"); | |
| const capture = () => { | |
| canvas.width = w.value; | |
| canvas.height = h.value; | |
| const ctx = canvas.getContext("2d"); | |
| ctx.drawImage(video, 0, 0, w.value, h.value); | |
| const data = canvas.toDataURL("image/png"); | |
| const img = new Image(); | |
| img.onload = () => { | |
| node.imgs = [img]; | |
| app.graph.setDirtyCanvas(true); | |
| requestAnimationFrame(() => { | |
| node.setSizeForImage?.(); | |
| }); | |
| }; | |
| img.src = data; | |
| }; | |
| const btn = node.addWidget("button", "waiting for camera...", "capture", capture); | |
| btn.disabled = true; | |
| btn.serializeValue = () => undefined; | |
| camera.serializeValue = async () => { | |
| if (captureOnQueue.value) { | |
| capture(); | |
| } else if (!node.imgs?.length) { | |
| const err = `No webcam image captured`; | |
| alert(err); | |
| throw new Error(err); | |
| } | |
| // Upload image to temp storage | |
| const blob = await new Promise((r) => canvas.toBlob(r)); | |
| const name = `${+new Date()}.png`; | |
| const file = new File([blob], name); | |
| const body = new FormData(); | |
| body.append("image", file); | |
| body.append("subfolder", "webcam"); | |
| body.append("type", "temp"); | |
| const resp = await api.fetchApi("/upload/image", { | |
| method: "POST", | |
| body, | |
| }); | |
| if (resp.status !== 200) { | |
| const err = `Error uploading camera image: ${resp.status} - ${resp.statusText}`; | |
| alert(err); | |
| throw new Error(err); | |
| } | |
| return `webcam/${name} [temp]`; | |
| }; | |
| node[WEBCAM_READY].then((v) => { | |
| video = v; | |
| // If width isnt specified then use video output resolution | |
| if (!w.value) { | |
| w.value = video.videoWidth || 640; | |
| h.value = video.videoHeight || 480; | |
| } | |
| btn.disabled = false; | |
| btn.label = "capture"; | |
| }); | |
| }, | |
| }); | |