better typehints

faster timeout for webcamera
promptserver should not be wrapped in exception
This commit is contained in:
Alexander G. Morano
2025-02-18 18:38:31 -05:00
parent a6a9c69ff9
commit 75d58b822b
14 changed files with 174 additions and 190 deletions
+74 -79
View File
@@ -369,7 +369,7 @@ class Lexicon(metaclass=LexiconMeta):
ZOOM = '🔎', "ZOOM"
@classmethod
def _parse(cls, node: dict) -> dict:
def _parse(cls, node: dict) -> Dict[str, str]:
for cat, entry in node.items():
if cat not in ['optional', 'required']:
continue
@@ -415,7 +415,7 @@ class JOVBaseNode:
return True
@classmethod
def INPUT_TYPES(cls, prompt:bool=False, extra_png:bool=False, dynprompt:bool=False) -> dict:
def INPUT_TYPES(cls, prompt:bool=False, extra_png:bool=False, dynprompt:bool=False) -> Dict[str, str]:
data = {
"optional": {},
"required": {},
@@ -685,7 +685,7 @@ def get_node_info(node_data: dict) -> Dict[str, Any]:
data[".md"] = md
return data
def deep_merge(d1: dict, d2: dict) -> dict:
def deep_merge(d1: dict, d2: dict) -> Dict[str, str]:
"""
Deep merge multiple dictionaries recursively.
@@ -734,12 +734,12 @@ class ComfyAPIMessage:
@classmethod
def poll(cls, ident, period=0.01, timeout=3) -> Any:
_t = time.monotonic()
_t = time.perf_counter()
if isinstance(ident, (set, list, tuple, )):
ident = ident[0]
sid = str(ident)
logger.debug(f'sid {sid} -- {cls.MESSAGE}')
while not (sid in cls.MESSAGE) and time.monotonic() - _t < timeout:
while not (sid in cls.MESSAGE) and time.perf_counter() - _t < timeout:
time.sleep(period)
if not (sid in cls.MESSAGE):
@@ -748,95 +748,90 @@ class ComfyAPIMessage:
dat = cls.MESSAGE.pop(sid)
return dat
def comfy_message(ident:str, route:str, data:dict) -> None:
def comfy_send_message(ident:str, route:str, data:dict) -> None:
data['id'] = ident
PromptServer.instance.send_sync(route, data)
try:
@PromptServer.instance.routes.get("/jovimetrix")
async def jovimetrix_home(request) -> Any:
data = template_load('home.html')
return web.Response(text=data.template, content_type='text/html')
@PromptServer.instance.routes.get("/jovimetrix")
async def jovimetrix_home(request) -> Any:
data = template_load('home.html')
return web.Response(text=data.template, content_type='text/html')
@PromptServer.instance.routes.get("/jovimetrix/message")
async def jovimetrix_message(request) -> Any:
return web.json_response(ComfyAPIMessage.MESSAGE)
@PromptServer.instance.routes.get("/jovimetrix/message")
async def jovimetrix_message(request) -> Any:
return web.json_response(ComfyAPIMessage.MESSAGE)
@PromptServer.instance.routes.post("/jovimetrix/message")
async def jovimetrix_message_post(request) -> Any:
json_data = await request.json()
logger.info(json_data)
if (did := json_data.get("id")) is not None:
ComfyAPIMessage.MESSAGE[str(did)] = json_data
return web.json_response(json_data)
return web.json_response({})
@PromptServer.instance.routes.post("/jovimetrix/message")
async def jovimetrix_message_post(request) -> Any:
json_data = await request.json()
logger.info(json_data)
if (did := json_data.get("id")) is not None:
ComfyAPIMessage.MESSAGE[str(did)] = json_data
return web.json_response(json_data)
return web.json_response({})
@PromptServer.instance.routes.get("/jovimetrix/config")
async def jovimetrix_config(request) -> Any:
global JOV_CONFIG, JOV_CONFIG_FILE
if len(JOV_CONFIG) == 0:
JOV_CONFIG = configLoad(JOV_CONFIG_FILE)
return web.json_response(JOV_CONFIG)
@PromptServer.instance.routes.get("/jovimetrix/config")
async def jovimetrix_config(request) -> Any:
global JOV_CONFIG, JOV_CONFIG_FILE
if len(JOV_CONFIG) == 0:
JOV_CONFIG = configLoad(JOV_CONFIG_FILE)
return web.json_response(JOV_CONFIG)
async def object_info(node_class: str, scheme:str, host: str) -> Any:
global COMFYUI_OBJ_DATA
if (info := COMFYUI_OBJ_DATA.get(node_class, None)) is None:
# look up via the route...
url = f"{scheme}://{host}/object_info/{node_class}"
async def object_info(node_class: str, scheme:str, host: str) -> Any:
global COMFYUI_OBJ_DATA
if (info := COMFYUI_OBJ_DATA.get(node_class, None)) is None:
# look up via the route...
url = f"{scheme}://{host}/object_info/{node_class}"
# Make an asynchronous HTTP request using aiohttp.ClientSession
async with ClientSession() as session:
try:
async with session.get(url) as response:
if response.status == 200:
info = await response.json()
if (data := info.get(node_class, None)) is not None:
info = get_node_info(data)
else:
info = {'.html': f"No data for {node_class}"}
COMFYUI_OBJ_DATA[node_class] = info
# Make an asynchronous HTTP request using aiohttp.ClientSession
async with ClientSession() as session:
try:
async with session.get(url) as response:
if response.status == 200:
info = await response.json()
if (data := info.get(node_class, None)) is not None:
info = get_node_info(data)
else:
info = {'.html': f"Failed to get docs {node_class}, status: {response.status}"}
logger.error(info)
except Exception as e:
logger.error(f"Failed to get docs {node_class}")
logger.exception(e)
info = {'.html': f"Failed to get docs {node_class}\n{e}"}
info = {'.html': f"No data for {node_class}"}
COMFYUI_OBJ_DATA[node_class] = info
else:
info = {'.html': f"Failed to get docs {node_class}, status: {response.status}"}
logger.error(info)
except Exception as e:
logger.error(f"Failed to get docs {node_class}")
logger.exception(e)
info = {'.html': f"Failed to get docs {node_class}\n{e}"}
return info
return info
@PromptServer.instance.routes.get("/jovimetrix/doc")
async def jovimetrix_doc(request) -> Any:
@PromptServer.instance.routes.get("/jovimetrix/doc")
async def jovimetrix_doc(request) -> Any:
for node_class in NODE_CLASS_MAPPINGS.keys():
if COMFYUI_OBJ_DATA.get(node_class, None) is None:
COMFYUI_OBJ_DATA[node_class] = await object_info(node_class, request.scheme, request.host)
node = NODE_DISPLAY_NAME_MAPPINGS[node_class]
fname = node.split(" (JOV)")[0]
path = Path(JOV_INTERNAL_DOC.replace("{name}", fname))
path.mkdir(parents=True, exist_ok=True)
if JOV_INTERNAL:
if (md := COMFYUI_OBJ_DATA[node_class].get('.md', None)) is not None:
with open(str(path / f"{fname}.md"), "w", encoding='utf-8') as f:
f.write(md)
with open(str(path / f"{fname}.html"), "w", encoding='utf-8') as f:
f.write(COMFYUI_OBJ_DATA[node_class]['.html'])
return web.json_response(COMFYUI_OBJ_DATA)
@PromptServer.instance.routes.get("/jovimetrix/doc/{node}")
async def jovimetrix_doc_node_comfy(request) -> Any:
node_class = request.match_info.get('node')
for node_class in NODE_CLASS_MAPPINGS.keys():
if COMFYUI_OBJ_DATA.get(node_class, None) is None:
COMFYUI_OBJ_DATA[node_class] = await object_info(node_class, request.scheme, request.host)
return web.Response(text=COMFYUI_OBJ_DATA[node_class]['.html'], content_type='text/html')
except Exception as e:
logger.error(e)
node = NODE_DISPLAY_NAME_MAPPINGS[node_class]
fname = node.split(" (JOV)")[0]
path = Path(JOV_INTERNAL_DOC.replace("{name}", fname))
path.mkdir(parents=True, exist_ok=True)
if JOV_INTERNAL:
if (md := COMFYUI_OBJ_DATA[node_class].get('.md', None)) is not None:
with open(str(path / f"{fname}.md"), "w", encoding='utf-8') as f:
f.write(md)
with open(str(path / f"{fname}.html"), "w", encoding='utf-8') as f:
f.write(COMFYUI_OBJ_DATA[node_class]['.html'])
return web.json_response(COMFYUI_OBJ_DATA)
@PromptServer.instance.routes.get("/jovimetrix/doc/{node}")
async def jovimetrix_doc_node_comfy(request) -> Any:
node_class = request.match_info.get('node')
if COMFYUI_OBJ_DATA.get(node_class, None) is None:
COMFYUI_OBJ_DATA[node_class] = await object_info(node_class, request.scheme, request.host)
return web.Response(text=COMFYUI_OBJ_DATA[node_class]['.html'], content_type='text/html')
# ==============================================================================
# === SUPPORT ===
+20 -20
View File
@@ -7,7 +7,7 @@ import sys
import math
import random
from enum import Enum
from typing import Any, Tuple
from typing import Any, Dict, Tuple
from collections import Counter
import torch
@@ -19,7 +19,7 @@ from comfy.utils import ProgressBar
from .. import JOV_TYPE_ANY, JOV_TYPE_FULL, JOV_TYPE_NUMBER, JOV_TYPE_VECTOR, \
Lexicon, JOVBaseNode, \
comfy_message, deep_merge, parse_reset
comfy_send_message, deep_merge, parse_reset
from ..sup.util import EnumConvertType, EnumSwizzle, \
parse_dynamic, parse_param, parse_value, vector_swap, zip_longest_fill
@@ -206,7 +206,7 @@ Split an input into separate bits. `BOOL`, `INT` and `FLOAT` use their numbers,
image.
"""
@classmethod
def INPUT_TYPES(cls) -> dict:
def INPUT_TYPES(cls) -> Dict[str, str]:
d = super().INPUT_TYPES()
d = deep_merge(d, {
"optional": {
@@ -234,7 +234,7 @@ Perform single function operations like absolute value, mean, median, mode, magn
"""
@classmethod
def INPUT_TYPES(cls) -> dict:
def INPUT_TYPES(cls) -> Dict[str, str]:
d = super().INPUT_TYPES()
d = deep_merge(d, {
"optional": {
@@ -336,7 +336,7 @@ Execute binary operations like addition, subtraction, multiplication, division,
"""
@classmethod
def INPUT_TYPES(cls) -> dict:
def INPUT_TYPES(cls) -> Dict[str, str]:
names_convert = EnumConvertType._member_names_[:10]
d = super().INPUT_TYPES()
d = deep_merge(d, {
@@ -478,7 +478,7 @@ Evaluates two inputs (A and B) with a specified comparison operators and optiona
"""
@classmethod
def INPUT_TYPES(cls) -> dict:
def INPUT_TYPES(cls) -> Dict[str, str]:
d = super().INPUT_TYPES()
d = deep_merge(d, {
"optional": {
@@ -600,7 +600,7 @@ Additionally, you can specify the easing function (EASE) and the desired output
"""
@classmethod
def INPUT_TYPES(cls) -> dict:
def INPUT_TYPES(cls) -> Dict[str, str]:
d = super().INPUT_TYPES()
names_convert = EnumConvertType._member_names_[:10]
d = deep_merge(d, {
@@ -685,7 +685,7 @@ Manipulate strings through filtering
"""
@classmethod
def INPUT_TYPES(cls) -> dict:
def INPUT_TYPES(cls) -> Dict[str, str]:
d = super().INPUT_TYPES()
d = deep_merge(d, {
"optional": {
@@ -750,7 +750,7 @@ Swap components between two vectors based on specified swizzle patterns and valu
"""
@classmethod
def INPUT_TYPES(cls) -> dict:
def INPUT_TYPES(cls) -> Dict[str, str]:
d = super().INPUT_TYPES()
names_convert = EnumConvertType._member_names_[3:10]
d = deep_merge(d, {
@@ -805,7 +805,7 @@ A timer and frame counter, emitting pulses or signals based on time intervals. I
"""
@classmethod
def INPUT_TYPES(cls) -> dict:
def INPUT_TYPES(cls) -> Dict[str, str]:
d = super().INPUT_TYPES()
d = deep_merge(d, {
"optional": {
@@ -880,7 +880,7 @@ A timer and frame counter, emitting pulses or signals based on time intervals. I
pbar.update_absolute(idx)
if batch < 2:
comfy_message(ident, "jovi-tick", {"i": self.__frame})
comfy_send_message(ident, "jovi-tick", {"i": self.__frame})
return (results.frame, results.lin, results.fixed, results.trigger, results.batch,)
class ValueNode(JOVBaseNode):
@@ -896,7 +896,7 @@ Supplies raw or default values for various data types, supporting vector input w
UPDATE = False
@classmethod
def INPUT_TYPES(cls) -> dict:
def INPUT_TYPES(cls) -> Dict[str, str]:
d = super().INPUT_TYPES()
typ = EnumConvertType._member_names_
@@ -1012,7 +1012,7 @@ Produce waveforms like sine, square, or sawtooth with adjustable frequency, ampl
"""
@classmethod
def INPUT_TYPES(cls) -> dict:
def INPUT_TYPES(cls) -> Dict[str, str]:
d = super().INPUT_TYPES()
d = deep_merge(d, {
"optional": {
@@ -1066,7 +1066,7 @@ Outputs a VEC2 or VEC2INT.
"""
@classmethod
def INPUT_TYPES(cls) -> dict:
def INPUT_TYPES(cls) -> Dict[str, str]:
d = super().INPUT_TYPES()
d = deep_merge(d, {
"optional": {
@@ -1104,7 +1104,7 @@ Outputs a VEC2 or VEC2INT.
"""
@classmethod
def INPUT_TYPES(cls) -> dict:
def INPUT_TYPES(cls) -> Dict[str, str]:
d = super().INPUT_TYPES()
d = deep_merge(d, {
"optional": {
@@ -1142,7 +1142,7 @@ Outputs a VEC3 or VEC3INT.
"""
@classmethod
def INPUT_TYPES(cls) -> dict:
def INPUT_TYPES(cls) -> Dict[str, str]:
d = super().INPUT_TYPES()
d = deep_merge(d, {
"optional": {
@@ -1183,7 +1183,7 @@ Outputs a VEC3 or VEC3INT.
"""
@classmethod
def INPUT_TYPES(cls) -> dict:
def INPUT_TYPES(cls) -> Dict[str, str]:
d = super().INPUT_TYPES()
d = deep_merge(d, {
"optional": {
@@ -1224,7 +1224,7 @@ Outputs a VEC4 or VEC4INT.
"""
@classmethod
def INPUT_TYPES(cls) -> dict:
def INPUT_TYPES(cls) -> Dict[str, str]:
d = super().INPUT_TYPES()
d = deep_merge(d, {
"optional": {
@@ -1268,7 +1268,7 @@ Outputs a VEC4 or VEC4INT.
"""
@classmethod
def INPUT_TYPES(cls) -> dict:
def INPUT_TYPES(cls) -> Dict[str, str]:
d = super().INPUT_TYPES()
d = deep_merge(d, {
"optional": {
@@ -1309,7 +1309,7 @@ class ParameterNode(JOVBaseNode):
"""
@classmethod
def INPUT_TYPES(cls) -> dict:
def INPUT_TYPES(cls) -> Dict[str, str]:
d = super().INPUT_TYPES()
d = deep_merge(d, {
"optional": {
+18 -18
View File
@@ -4,7 +4,7 @@ Composition
"""
from enum import Enum
from typing import Any, List, Tuple
from typing import Any, Dict, List, Tuple
import cv2
import torch
@@ -78,7 +78,7 @@ Enhance and modify images with various effects such as blurring, sharpening, col
"""
@classmethod
def INPUT_TYPES(cls) -> dict:
def INPUT_TYPES(cls) -> Dict[str, str]:
d = super().INPUT_TYPES()
d = deep_merge(d, {
"optional": {
@@ -226,7 +226,7 @@ Combine two input images using various blending modes, such as normal, screen, m
"""
@classmethod
def INPUT_TYPES(cls) -> dict:
def INPUT_TYPES(cls) -> Dict[str, str]:
d = super().INPUT_TYPES()
d = deep_merge(d, {
"optional": {
@@ -315,7 +315,7 @@ Simulate color blindness effects on images. You can select various types of colo
"""
@classmethod
def INPUT_TYPES(cls) -> dict:
def INPUT_TYPES(cls) -> Dict[str, str]:
d = super().INPUT_TYPES()
d = deep_merge(d, {
"optional": {
@@ -352,7 +352,7 @@ Adjust the color scheme of one image to match another with the Color Match Node.
"""
@classmethod
def INPUT_TYPES(cls) -> dict:
def INPUT_TYPES(cls) -> Dict[str, str]:
d = super().INPUT_TYPES()
d = deep_merge(d, {
"optional": {
@@ -440,7 +440,7 @@ The top-k colors ordered from most->least used as a strip, tonal palette and 3D
"""
@classmethod
def INPUT_TYPES(cls) -> dict:
def INPUT_TYPES(cls) -> Dict[str, str]:
d = super().INPUT_TYPES()
d = deep_merge(d, {
"optional": {
@@ -498,7 +498,7 @@ Generate a color harmony based on the selected scheme. Supported schemes include
"""
@classmethod
def INPUT_TYPES(cls) -> dict:
def INPUT_TYPES(cls) -> Dict[str, str]:
d = super().INPUT_TYPES()
d = deep_merge(d, {
"optional": {
@@ -537,7 +537,7 @@ Extract a portion of an input image or resize it. It supports various cropping m
"""
@classmethod
def INPUT_TYPES(cls) -> dict:
def INPUT_TYPES(cls) -> Dict[str, str]:
d = super().INPUT_TYPES()
d = deep_merge(d, {
"optional": {
@@ -601,7 +601,7 @@ Create masks based on specific color ranges within an image. Specify the color r
"""
@classmethod
def INPUT_TYPES(cls) -> dict:
def INPUT_TYPES(cls) -> Dict[str, str]:
d = super().INPUT_TYPES()
d = deep_merge(d, {
"optional": {
@@ -646,7 +646,7 @@ Combine multiple input images into a single image by summing their pixel values.
"""
@classmethod
def INPUT_TYPES(cls) -> dict:
def INPUT_TYPES(cls) -> Dict[str, str]:
d = super().INPUT_TYPES()
d = deep_merge(d, {
"optional": {
@@ -689,7 +689,7 @@ Remaps an input image using a gradient lookup table (LUT). The gradient image wi
"""
@classmethod
def INPUT_TYPES(cls) -> dict:
def INPUT_TYPES(cls) -> Dict[str, str]:
d = super().INPUT_TYPES()
d = deep_merge(d, {
"optional": {
@@ -741,7 +741,7 @@ Combines individual color channels (red, green, blue) along with an optional mas
"""
@classmethod
def INPUT_TYPES(cls) -> dict:
def INPUT_TYPES(cls) -> Dict[str, str]:
d = super().INPUT_TYPES()
d = deep_merge(d, {
"optional": {
@@ -823,7 +823,7 @@ Takes an input image and splits it into its individual color channels (red, gree
"""
@classmethod
def INPUT_TYPES(cls) -> dict:
def INPUT_TYPES(cls) -> Dict[str, str]:
d = super().INPUT_TYPES()
d = deep_merge(d, {
"optional": {
@@ -851,7 +851,7 @@ Swap pixel values between two input images based on specified channel swizzle op
"""
@classmethod
def INPUT_TYPES(cls) -> dict:
def INPUT_TYPES(cls) -> Dict[str, str]:
d = super().INPUT_TYPES()
d = deep_merge(d, {
"optional": {
@@ -916,7 +916,7 @@ Merge multiple input images into a single composite image by stacking them along
"""
@classmethod
def INPUT_TYPES(cls) -> dict:
def INPUT_TYPES(cls) -> Dict[str, str]:
d = super().INPUT_TYPES()
d = deep_merge(d, {
"optional": {
@@ -960,7 +960,7 @@ Define a range and apply it to an image for segmentation and feature extraction.
"""
@classmethod
def INPUT_TYPES(cls) -> dict:
def INPUT_TYPES(cls) -> Dict[str, str]:
d = super().INPUT_TYPES()
d = deep_merge(d, {
"optional": {
@@ -1003,7 +1003,7 @@ Apply various geometric transformations to images, including translation, rotati
"""
@classmethod
def INPUT_TYPES(cls) -> dict:
def INPUT_TYPES(cls) -> Dict[str, str]:
d = super().INPUT_TYPES()
d = deep_merge(d, {
"optional": {
@@ -1101,7 +1101,7 @@ The Histogram Node generates a histogram representation of the input image, show
"""
@classmethod
def INPUT_TYPES(cls) -> dict:
def INPUT_TYPES(cls) -> Dict[str, str]:
d = super().INPUT_TYPES()
d = deep_merge(d, {
"optional": {
+6 -7
View File
@@ -3,13 +3,12 @@ Jovimetrix - http://www.github.com/amorano/jovimetrix
Creation
"""
from typing import Tuple
from typing import Dict, Tuple
import torch
import numpy as np
from PIL import ImageFont
from skimage.filters import gaussian
from loguru import logger
from comfy.utils import ProgressBar
@@ -50,7 +49,7 @@ Generate a constant image or mask of a specified size and color. It can be used
"""
@classmethod
def INPUT_TYPES(cls) -> dict:
def INPUT_TYPES(cls) -> Dict[str, str]:
d = super().INPUT_TYPES()
d = deep_merge(d, {
"optional": {
@@ -97,7 +96,7 @@ Create n-sided polygons. These shapes can be customized by adjusting parameters
"""
@classmethod
def INPUT_TYPES(cls) -> dict:
def INPUT_TYPES(cls) -> Dict[str, str]:
d = super().INPUT_TYPES()
d = deep_merge(d, {
"optional": {
@@ -168,7 +167,7 @@ Generates false perception 3D images from 2D input. Set tile divisions, noise, g
"""
@classmethod
def INPUT_TYPES(cls) -> dict:
def INPUT_TYPES(cls) -> Dict[str, str]:
d = super().INPUT_TYPES()
d = deep_merge(d, {
"optional": {
@@ -214,7 +213,7 @@ class StereoscopicNode(JOVBaseNode):
Simulates depth perception in images by generating stereoscopic views. It accepts an optional input image for color matte. Adjust baseline and focal length for customized depth effects.
"""
@classmethod
def INPUT_TYPES(cls) -> dict:
def INPUT_TYPES(cls) -> Dict[str, str]:
d = super().INPUT_TYPES()
d = deep_merge(d, {
"optional": {
@@ -252,7 +251,7 @@ Generates images containing text based on parameters such as font, size, alignme
"""
@classmethod
def INPUT_TYPES(cls) -> dict:
def INPUT_TYPES(cls) -> Dict[str, str]:
d = super().INPUT_TYPES()
d = deep_merge(d, {
"optional": {
+7 -7
View File
@@ -5,7 +5,7 @@ Creation
import sys
from pathlib import Path
from typing import Any, Tuple
from typing import Any, Dict, Tuple
import torch
from loguru import logger
@@ -19,7 +19,7 @@ from comfy.utils import ProgressBar
from .. import JOV_TYPE_IMAGE, \
Lexicon, JOVImageNode, \
comfy_message, deep_merge
comfy_send_message, deep_merge
from ..sup.util import EnumConvertType, \
parse_param, parse_value
@@ -86,7 +86,7 @@ class GLSLNodeBase(JOVImageNode):
CATEGORY = f"JOVIMETRIX 🔺🟩🔵/GLSL"
@classmethod
def INPUT_TYPES(cls) -> dict:
def INPUT_TYPES(cls) -> Dict[str, str]:
d = super().INPUT_TYPES()
d = deep_merge(d, {
"optional": {
@@ -122,7 +122,7 @@ class GLSLNodeBase(JOVImageNode):
self.__glsl.vertex = getattr(self, 'VERTEX', kw.pop(Lexicon.PROG_VERT, None))
self.__glsl.fragment = getattr(self, 'FRAGMENT', kw.pop(Lexicon.PROG_FRAG, None))
except CompileException as e:
comfy_message(ident, "jovi-glsl-error", {"id": ident, "e": str(e)})
comfy_send_message(ident, "jovi-glsl-error", {"id": ident, "e": str(e)})
logger.error(self.NAME)
logger.error(e)
return
@@ -169,7 +169,7 @@ class GLSLNodeBase(JOVImageNode):
img = image_scalefit(img, w, h, mode, sample)
images.append(cv2tensor_full(img, matte))
self.__delta += step
comfy_message(ident, "jovi-glsl-time", {"id": ident, "t": self.__delta})
comfy_send_message(ident, "jovi-glsl-time", {"id": ident, "t": self.__delta})
pbar.update_absolute(idx)
return [torch.stack(i) for i in zip(*images)]
@@ -181,7 +181,7 @@ Execute custom GLSL (OpenGL Shading Language) fragment shaders to generate image
"""
@classmethod
def INPUT_TYPES(cls) -> dict:
def INPUT_TYPES(cls) -> Dict[str, str]:
d = super().INPUT_TYPES()
opts = d.get('optional', {})
opts.update({
@@ -203,7 +203,7 @@ class GLSLNodeDynamic(GLSLNodeBase):
PARAM = None
@classmethod
def INPUT_TYPES(cls) -> dict:
def INPUT_TYPES(cls) -> Dict[str, str]:
original_params = super().INPUT_TYPES()
opts = original_params.get('optional', {})
opts.update({
+5 -7
View File
@@ -7,12 +7,10 @@ Device -- MIDI
type 2 (asynchronous): each track is independent of the others
"""
from typing import Tuple
from typing import Dict, Tuple
from math import isclose
from queue import Queue
from loguru import logger
from comfy.utils import ProgressBar
from .. import JOVBaseNode, Lexicon, deep_merge
@@ -50,7 +48,7 @@ Processes MIDI messages received from an external MIDI controller or device. It
"""
@classmethod
def INPUT_TYPES(cls) -> dict:
def INPUT_TYPES(cls) -> Dict[str, str]:
d = super().INPUT_TYPES()
d = deep_merge(d, {
"optional": {
@@ -83,7 +81,7 @@ Captures MIDI messages from an external MIDI device or controller. It monitors M
CHANGED = False
@classmethod
def INPUT_TYPES(cls) -> dict:
def INPUT_TYPES(cls) -> Dict[str, str]:
d = super().INPUT_TYPES()
d = deep_merge(d, {
"optional": {
@@ -154,7 +152,7 @@ Provides advanced filtering capabilities for MIDI messages based on various crit
"""
@classmethod
def INPUT_TYPES(cls) -> dict:
def INPUT_TYPES(cls) -> Dict[str, str]:
d = super().INPUT_TYPES()
d = deep_merge(d, {
"optional": {
@@ -259,7 +257,7 @@ Filter MIDI messages based on various criteria, including MIDI mode (such as not
"""
@classmethod
def INPUT_TYPES(cls) -> dict:
def INPUT_TYPES(cls) -> Dict[str, str]:
d = super().INPUT_TYPES()
d = deep_merge(d, {
"optional": {
+4 -4
View File
@@ -6,7 +6,7 @@ Device -- WEBCAM, REMOTE URLS, SPOUT
import sys
import time
import uuid
from typing import Tuple
from typing import Dict, Tuple
from enum import Enum
import cv2
@@ -76,7 +76,7 @@ Capture frames from various sources such as URLs, cameras, monitors, windows, or
"""
@classmethod
def INPUT_TYPES(cls) -> dict:
def INPUT_TYPES(cls) -> Dict[str, str]:
d = super().INPUT_TYPES()
if cls.CAMERAS is None:
@@ -297,7 +297,7 @@ Sends frames to a specified route, typically for live streaming or recording pur
"""
@classmethod
def INPUT_TYPES(cls) -> dict:
def INPUT_TYPES(cls) -> Dict[str, str]:
d = super().INPUT_TYPES()
d = deep_merge(d, {
"optional": {
@@ -368,7 +368,7 @@ Sends frames to a specified Spout receiver application for real-time video shari
"""
@classmethod
def INPUT_TYPES(cls) -> dict:
def INPUT_TYPES(cls) -> Dict[str, str]:
d = super().INPUT_TYPES()
d = deep_merge(d, {
"optional": {
+7 -7
View File
@@ -11,7 +11,7 @@ import random
from enum import Enum
from pathlib import Path
from itertools import zip_longest
from typing import Any, List, Literal, Tuple
from typing import Any, Dict, List, Literal, Tuple
import torch
import numpy as np
@@ -22,7 +22,7 @@ from comfy.utils import ProgressBar
from nodes import interrupt_processing
from ... import JOV_TYPE_ANY, ROOT, Lexicon, JOVBaseNode, deep_merge, \
comfy_message, parse_reset
comfy_send_message, parse_reset
from ...sup.util import EnumConvertType, parse_dynamic, parse_param
@@ -70,7 +70,7 @@ Processes a batch of data based on the selected mode, such as merging, picking,
"""
@classmethod
def INPUT_TYPES(cls) -> dict:
def INPUT_TYPES(cls) -> Dict[str, str]:
d = super().INPUT_TYPES()
d = deep_merge(d, {
"optional": {
@@ -239,7 +239,7 @@ class QueueBaseNode(JOVBaseNode):
return float('nan')
@classmethod
def INPUT_TYPES(cls) -> dict:
def INPUT_TYPES(cls) -> Dict[str, str]:
d = super().INPUT_TYPES()
d = deep_merge(d, {
"optional": {
@@ -373,7 +373,7 @@ class QueueBaseNode(JOVBaseNode):
# make sure we have more to process if are a single fire queue
stop = parse_param(kw, Lexicon.STOP, EnumConvertType.BOOLEAN, False)[0]
if stop and self.__index >= self.__len:
comfy_message(ident, "jovi-queue-done", self.status)
comfy_send_message(ident, "jovi-queue-done", self.status)
interrupt_processing()
return self.__previous, self.__q, self.__current, self.__index_last+1, self.__len
@@ -430,7 +430,7 @@ class QueueBaseNode(JOVBaseNode):
self.__index += 1
self.__previous = data
comfy_message(ident, "jovi-queue-ping", self.status)
comfy_send_message(ident, "jovi-queue-ping", self.status)
if stop and batched:
interrupt_processing()
return data, self.__q, self.__current, self.__index, self.__len, self.__index == self.__index_last or batched
@@ -479,7 +479,7 @@ Manage a queue of specific items: media files. Supports various image and video
"""
@classmethod
def INPUT_TYPES(cls) -> dict:
def INPUT_TYPES(cls) -> Dict[str, str]:
d = super().INPUT_TYPES()
d = deep_merge(d, {
"optional": {
+3 -3
View File
@@ -5,7 +5,7 @@ Utility
import io
import json
from typing import Any, Tuple
from typing import Any, Dict, Tuple
import torch
import numpy as np
@@ -137,7 +137,7 @@ Visualize a series of data points over time. It accepts a dynamic number of valu
"""
@classmethod
def INPUT_TYPES(cls) -> dict:
def INPUT_TYPES(cls) -> Dict[str, str]:
d = super().INPUT_TYPES()
d = deep_merge(d, {
"optional": {
@@ -218,7 +218,7 @@ Exports and Displays immediate information about images.
"""
@classmethod
def INPUT_TYPES(cls) -> dict:
def INPUT_TYPES(cls) -> Dict[str, str]:
d = super().INPUT_TYPES()
d = deep_merge(d, {
"optional": {
+7 -7
View File
@@ -7,7 +7,7 @@ import os
import json
from uuid import uuid4
from pathlib import Path
from typing import Any, Tuple
from typing import Any, Dict, Tuple
import torch
import numpy as np
@@ -22,7 +22,7 @@ from nodes import interrupt_processing
from ... import JOV_TYPE_ANY, JOV_TYPE_IMAGE, \
Lexicon, JOVBaseNode, ComfyAPIMessage, TimedOutException, \
comfy_message, deep_merge
comfy_send_message, deep_merge
from ...sup.util import EnumConvertType, path_next, parse_param, \
zip_longest_fill
@@ -71,7 +71,7 @@ Introduce pauses in the workflow that accept an optional input to pass through a
"""
@classmethod
def INPUT_TYPES(cls) -> dict:
def INPUT_TYPES(cls) -> Dict[str, str]:
d = super().INPUT_TYPES()
d = deep_merge(d, {
"optional": {
@@ -94,7 +94,7 @@ Introduce pauses in the workflow that accept an optional input to pass through a
if delay < 0:
delay = JOV_DELAY_MAX
if delay > JOV_DELAY_MIN:
comfy_message(ident, "jovi-delay-user", {"id": ident, "timeout": delay})
comfy_send_message(ident, "jovi-delay-user", {"id": ident, "timeout": delay})
# enable = parse_param(kw, Lexicon.ENABLE, EnumConvertType.BOOLEAN, True)
step = 1
@@ -125,7 +125,7 @@ Responsible for saving images or animations to disk. It supports various output
"""
@classmethod
def INPUT_TYPES(cls) -> dict:
def INPUT_TYPES(cls) -> Dict[str, str]:
d = super().INPUT_TYPES()
d = deep_merge(d, {
"optional": {
@@ -235,7 +235,7 @@ Routes the input data from the optional input ports to the output port, preservi
"""
@classmethod
def INPUT_TYPES(cls) -> dict:
def INPUT_TYPES(cls) -> Dict[str, str]:
d = super().INPUT_TYPES()
e = {
"optional": {
@@ -263,7 +263,7 @@ Save the output image along with its metadata to the specified path. Supports sa
"""
@classmethod
def INPUT_TYPES(cls) -> dict:
def INPUT_TYPES(cls) -> Dict[str, str]:
d = super().INPUT_TYPES(True, True)
d = deep_merge(d, {
"optional": {
+3 -18
View File
@@ -6,12 +6,11 @@
Copyright 2023 Alexander Morano (Joviex)
"""
import io
from io import BytesIO
import math
import base64
import requests
from enum import Enum
from io import BytesIO
from typing import List, Tuple, Union
import cv2
@@ -19,8 +18,6 @@ import torch
import numpy as np
from PIL import Image, ImageOps
from loguru import logger
# ==============================================================================
# === GLOBAL ===
# ==============================================================================
@@ -54,11 +51,6 @@ TYPE_VECTOR = Union[TYPE_IMAGE, TYPE_PIXEL]
# === ENUMERATION ===
# ==============================================================================
class EnumGrayscaleCrunch(Enum):
LOW = 0
HIGH = 1
MEAN = 2
class EnumImageType(Enum):
GRAYSCALE = 0
RGB = 10
@@ -66,10 +58,6 @@ class EnumImageType(Enum):
BGR = 30
BGRA = 40
class EnumIntFloat(Enum):
FLOAT = 0
INT = 1
# ==============================================================================
# === CONVERSION ===
# ==============================================================================
@@ -92,13 +80,13 @@ def b64_2_tensor(base64str: str) -> torch.Tensor:
def b64_2_pil(base64_string):
prefix, base64_data = base64_string.split(",", 1)
image_data = base64.b64decode(base64_data)
image_stream = io.BytesIO(image_data)
image_stream = BytesIO(image_data)
return Image.open(image_stream)
def b64_2_cv(base64_string) -> TYPE_IMAGE:
_, data = base64_string.split(",", 1)
data = base64.b64decode(data)
data = io.BytesIO(data)
data = BytesIO(data)
data = Image.open(data)
data = np.array(data)
return cv2.cvtColor(data, cv2.COLOR_RGB2BGR)
@@ -386,7 +374,6 @@ def image_load(url: str) -> Tuple[TYPE_IMAGE, TYPE_IMAGE]:
raise ValueError(f"{url} could not be loaded.")
img = image_normalize(img)
# logger.debug(f"load image {url}: {img.ndim} {img.shape}")
if img.ndim == 3:
if img.shape[2] == 4:
img = cv2.cvtColor(img, cv2.COLOR_RGBA2BGRA)
@@ -396,7 +383,6 @@ def image_load(url: str) -> Tuple[TYPE_IMAGE, TYPE_IMAGE]:
img = np.expand_dims(img, -1)
except Exception:
logger.debug(f"load image fallback to PIL {url}")
try:
img = Image.open(url)
img = ImageOps.exif_transpose(img)
@@ -404,7 +390,6 @@ def image_load(url: str) -> Tuple[TYPE_IMAGE, TYPE_IMAGE]:
if img.dtype != np.uint8:
img = np.clip(np.array(img * 255), 0, 255).astype(dtype=np.uint8)
except Exception as e:
# logger.error(str(e))
raise Exception(f"Error loading image: {e}")
if img is None:
+5 -5
View File
@@ -10,16 +10,16 @@ import cv2
import torch
import numpy as np
from loguru import logger
from . import TYPE_IMAGE, TYPE_PIXEL, TYPE_fCOORD2D, \
EnumImageType, \
from . import \
TYPE_IMAGE, TYPE_PIXEL, \
TYPE_fCOORD2D, EnumImageType, \
image_convert, image_mask_add, image_matte, image_minmax, bgr2image, \
cv2tensor, image2bgr, tensor2cv
from .compose import image_blend, image_crop_center
from .channel import EnumPixelSwizzle, \
from .channel import \
EnumPixelSwizzle, \
channel_solid
# ==============================================================================
+10 -3
View File
@@ -15,10 +15,8 @@ from sklearn.cluster import KMeans
from daltonlens import simulate
from blendmodes.blend import BlendType
from loguru import logger
from . import TYPE_IMAGE, TYPE_PIXEL, \
EnumGrayscaleCrunch, EnumImageType, EnumIntFloat, \
EnumImageType, \
bgr2hsv, hsv2bgr, image_convert, image_mask, image_mask_add
from .compose import image_blend
@@ -33,6 +31,15 @@ TYPE_LUT = Tuple[int, int, int, int]
# === ENUMERATION ===
# ==============================================================================
class EnumIntFloat(Enum):
FLOAT = 0
INT = 1
class EnumGrayscaleCrunch(Enum):
LOW = 0
HIGH = 1
MEAN = 2
class EnumColorMap(Enum):
AUTUMN = cv2.COLORMAP_AUTUMN
BONE = cv2.COLORMAP_BONE
+5 -5
View File
@@ -10,7 +10,7 @@ import json
import time
import array
import threading
from typing import Any, List, Tuple
from typing import Any, Dict, List, Tuple
from itertools import repeat
from configparser import ConfigParser
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
@@ -97,7 +97,7 @@ def monitor_capture(monitor:int=0, tlwh:Tuple[int, int, int, int]=None, width:in
img = cv2.resize(img, (width, height))
return img
def monitor_list() -> dict:
def monitor_list() -> Dict[str, str]:
if JOV_DOCKERENV:
return {}
ret = {}
@@ -105,7 +105,7 @@ def monitor_list() -> dict:
ret = {i:v for i, v in enumerate(sct.monitors)}
return ret
def window_list() -> dict:
def window_list() -> Dict[str, str]:
return {}
if sys.platform.startswith('win'):
@@ -114,7 +114,7 @@ if sys.platform.startswith('win'):
import win32ui
from ctypes import windll
def window_list() -> dict:
def window_list() -> Dict[str, str]:
_windows = {}
def window_enum_handler(hwnd, ctx) -> None:
if win32gui.IsWindowVisible(hwnd):
@@ -191,7 +191,7 @@ elif sys.platform.startswith('darwin'):
return None
def window_list() -> dict:
def window_list() -> Dict[str, str]:
_windows = {}
window_list = Quartz.CGWindowListCopyWindowInfo(
Quartz.kCGWindowListOptionOnScreenOnly | Quartz.kCGWindowListExcludeDesktopElements,