| | 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);
|
| | 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);
|
| | }
|
| |
|
| |
|
| | 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 (!w.value) {
|
| | w.value = video.videoWidth || 640;
|
| | h.value = video.videoHeight || 480;
|
| | }
|
| | btn.disabled = false;
|
| | btn.label = "capture";
|
| | });
|
| | },
|
| | });
|
| |
|