File size: 3,957 Bytes
e26a977
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
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";
		});
	},
});