diff --git a/comfy/model_management.py b/comfy/model_management.py index 810cfbff8..0db22a257 100644 --- a/comfy/model_management.py +++ b/comfy/model_management.py @@ -142,10 +142,6 @@ def get_total_memory(dev=None, torch_total_too=False): total_vram = get_total_memory(get_torch_device()) / (1024 * 1024) total_ram = psutil.virtual_memory().total / (1024 * 1024) logging.info("Total VRAM {:0.0f} MB, total RAM {:0.0f} MB".format(total_vram, total_ram)) -if not args.normalvram and not args.cpu: - if lowvram_available and total_vram <= 4096: - logging.warning("Trying to enable lowvram mode because your GPU seems to have 4GB or less. If you don't want this use: --normalvram") - set_vram_to = VRAMState.LOW_VRAM try: OOM_EXCEPTION = torch.cuda.OutOfMemoryError @@ -484,9 +480,8 @@ def load_models_gpu(models, memory_required=0, force_patch_weights=False): model_size = loaded_model.model_memory_required(torch_dev) current_free_mem = get_free_memory(torch_dev) lowvram_model_memory = int(max(64 * (1024 * 1024), (current_free_mem - 1024 * (1024 * 1024)) / 1.3)) - if model_size > (current_free_mem - inference_memory): # only switch to lowvram if really necessary - vram_set_state = VRAMState.LOW_VRAM - else: + if model_size <= (current_free_mem - inference_memory): # only switch to lowvram if really necessary + lowvram_model_memory = 0 if vram_set_state == VRAMState.NO_VRAM: diff --git a/comfy/web/extensions/core/webcamCapture.js b/comfy/web/extensions/core/webcamCapture.js new file mode 100644 index 000000000..dd5725bd4 --- /dev/null +++ b/comfy/web/extensions/core/webcamCapture.js @@ -0,0 +1,126 @@ +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"; + }); + }, +}); diff --git a/comfy_extras/nodes/nodes_webcam.py b/comfy_extras/nodes/nodes_webcam.py new file mode 100644 index 000000000..ab87ded19 --- /dev/null +++ b/comfy_extras/nodes/nodes_webcam.py @@ -0,0 +1,33 @@ +from comfy.cmd import folder_paths +from comfy.nodes.base_nodes import LoadImage +from comfy.nodes.common import MAX_RESOLUTION + + +class WebcamCapture(LoadImage): + @classmethod + def INPUT_TYPES(s): + return { + "required": { + "image": ("WEBCAM", {}), + "width": ("INT", {"default": 0, "min": 0, "max": MAX_RESOLUTION, "step": 1}), + "height": ("INT", {"default": 0, "min": 0, "max": MAX_RESOLUTION, "step": 1}), + "capture_on_queue": ("BOOLEAN", {"default": True}), + } + } + + RETURN_TYPES = ("IMAGE",) + FUNCTION = "load_capture" + + CATEGORY = "image" + + def load_capture(s, image, **kwargs): + return super().load_image(folder_paths.get_annotated_filepath(image)) + + +NODE_CLASS_MAPPINGS = { + "WebcamCapture": WebcamCapture, +} + +NODE_DISPLAY_NAME_MAPPINGS = { + "WebcamCapture": "Webcam Capture", +}