Skip to content

Commit

Permalink
relative imports
Browse files Browse the repository at this point in the history
  • Loading branch information
Amorano committed Jan 8, 2025
1 parent 98f8961 commit 46cfe2a
Show file tree
Hide file tree
Showing 6 changed files with 97 additions and 77 deletions.
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@

## UPDATES

**2024/01/07** @1.0.3:
* relative imports

**2024/01/04** @1.0.2:
* updated project description for registry

Expand Down
103 changes: 32 additions & 71 deletions __init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,20 +16,21 @@
@description: SPOUT support for ComfyUI.
@node list:
SpoutReaderNode, SpoutWriterNode
@version: 1.0.2
@version: 1.0.3
"""

__all__ = ["NODE_CLASS_MAPPINGS", "NODE_DISPLAY_NAME_MAPPINGS", "WEB_DIRECTORY"]
__author__ = """Alexander G. Morano"""
__email__ = "[email protected]"
__version__ = "1.0.2"
__version__ = "1.0.3"

import os
import sys
import json
import inspect
import importlib
from pathlib import Path
from types import ModuleType
from typing import Any

from loguru import logger
Expand All @@ -48,29 +49,15 @@

JOV_INTERNAL = os.getenv("JOV_INTERNAL", 'false').strip().lower() in ('true', '1', 't')

JOV_PACKAGE = "Jovi_Spout"

# ==============================================================================
# === THERE CAN BE ONLY ONE ===
# ==============================================================================

class Singleton(type):
_instances = {}

def __call__(cls, *arg, **kw) -> Any:
# If the instance does not exist, create and store it
if cls not in cls._instances:
instance = super().__call__(*arg, **kw)
cls._instances[cls] = instance
return cls._instances[cls]
JOV_PACKAGE = "JOV_SPOUT"

# ==============================================================================
# === CORE NODES ===
# ==============================================================================

class JOVBaseNode:
NOT_IDEMPOTENT = True
CATEGORY = f"{JOV_PACKAGE.upper()} 📺"
CATEGORY = f"{JOV_PACKAGE} 📺"
RETURN_TYPES = ()
FUNCTION = "run"

Expand Down Expand Up @@ -99,21 +86,9 @@ def INPUT_TYPES(cls, prompt:bool=False, extra_png:bool=False, dynprompt:bool=Fal
data["hidden"]["dynprompt"] = "DYNPROMPT"
return data

class JOVImageNode(JOVBaseNode):
RETURN_TYPES = ("IMAGE", "IMAGE", "MASK")
RETURN_NAMES = ("RGBA", "RGB", "MASK")

@classmethod
def INPUT_TYPES(cls) -> dict:
d = super().INPUT_TYPES()
d = deep_merge(d, {
"outputs": {
0: ("IMAGE", {"tooltip":"Full channel [RGBA] image. If there is an alpha, the image will be masked out with it when using this output."}),
1: ("IMAGE", {"tooltip":"Three channel [RGB] image. There will be no alpha."}),
2: ("MASK", {"tooltip":"Single channel mask output."}),
}
})
return d
# ==============================================================================
# === TYPE ===
# ==============================================================================

class AnyType(str):
"""AnyType input wildcard trick taken from pythongossss's:
Expand All @@ -124,32 +99,32 @@ def __ne__(self, __value: object) -> bool:
return False

JOV_TYPE_ANY = AnyType("*")
JOV_TYPE_IMAGE = JOV_TYPE_ANY

def deep_merge(d1: dict, d2: dict) -> dict:
"""
Deep merge multiple dictionaries recursively.
Args:
*dicts: Variable number of dictionaries to be merged.
Returns:
dict: Merged dictionary.
"""
for key in d2:
if key in d1:
if isinstance(d1[key], dict) and isinstance(d2[key], dict):
deep_merge(d1[key], d2[key])
else:
d1[key] = d2[key]
else:
d1[key] = d2[key]
return d1

# ==============================================================================
# === NODE LOADER ===
# ==============================================================================

def load_module(name: str) -> None|ModuleType:
module = inspect.getmodule(inspect.stack()[0][0]).__name__
try:
route = str(name).replace("\\", "/")
route = route.split(f"{module}/core/")[1]
route = route.split('.')[0].replace('/', '.')
except Exception as e:
logger.warning(f"module failed {name}")
logger.warning(str(e))
return

try:
module = f"{module}.core.{route}"
module = importlib.import_module(module)
except Exception as e:
logger.warning(f"module failed {module}")
logger.warning(str(e))
return

return module

def loader():
global NODE_DISPLAY_NAME_MAPPINGS, NODE_CLASS_MAPPINGS
NODE_LIST_MAP = {}
Expand All @@ -158,27 +133,13 @@ def loader():
if fname.stem.startswith('_'):
continue

try:
route = str(fname).replace("\\", "/").split(f"{JOV_PACKAGE}/core/")[1]
route = route.split('.')[0].replace('/', '.')
module = f"{JOV_PACKAGE}.core.{route}"
module = importlib.import_module(module)
except Exception as e:
logger.warning(f"module failed {fname}")
logger.warning(str(e))
if (module := load_module(fname)) is None:
continue

# check if there is a dynamic register function....
try:
for class_name, class_def in module.import_dynamic():
setattr(module, class_name, class_def)
except Exception as e:
pass

classes = inspect.getmembers(module, inspect.isclass)
for class_name, class_object in classes:
if not class_name.endswith('BaseNode') and hasattr(class_object, 'NAME') and hasattr(class_object, 'CATEGORY'):
name = class_object.NAME
name = f"{class_object.NAME} ({JOV_PACKAGE})"
NODE_DISPLAY_NAME_MAPPINGS[name] = name
NODE_CLASS_MAPPINGS[name] = class_object
desc = class_object.DESCRIPTION if hasattr(class_object, 'DESCRIPTION') else name
Expand All @@ -189,7 +150,7 @@ def loader():

keys = NODE_CLASS_MAPPINGS.keys()
for name in keys:
logger.debug(f"✅ {name} :: {NODE_DISPLAY_NAME_MAPPINGS[name]}")
logger.debug(f"✅ {name}")
logger.info(f"{len(keys)} nodes loaded")

# only do the list on local runs...
Expand Down
50 changes: 50 additions & 0 deletions core/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
"""
Jovi_Spout - http://www.github.com/Amorano/Jovi_Spout
Core
"""

from .. import JOVBaseNode

# ==============================================================================
# === CORE NODES ===
# ==============================================================================

class JOVImageNode(JOVBaseNode):
RETURN_TYPES = ("IMAGE", "IMAGE", "MASK")
RETURN_NAMES = ("RGBA", "RGB", "MASK")

@classmethod
def INPUT_TYPES(cls) -> dict:
d = super().INPUT_TYPES()
d = deep_merge(d, {
"outputs": {
0: ("IMAGE", {"tooltip":"Full channel [RGBA] image. If there is an alpha, the image will be masked out with it when using this output."}),
1: ("IMAGE", {"tooltip":"Three channel [RGB] image. There will be no alpha."}),
2: ("MASK", {"tooltip":"Single channel mask output."}),
}
})
return d

# ==============================================================================
# === SUPPORT ===
# ==============================================================================

def deep_merge(d1: dict, d2: dict) -> dict:
"""
Deep merge multiple dictionaries recursively.
Args:
*dicts: Variable number of dictionaries to be merged.
Returns:
dict: Merged dictionary.
"""
for key in d2:
if key in d1:
if isinstance(d1[key], dict) and isinstance(d2[key], dict):
deep_merge(d1[key], d2[key])
else:
d1[key] = d2[key]
else:
d1[key] = d2[key]
return d1
12 changes: 9 additions & 3 deletions core/spout.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,11 @@

from comfy.utils import ProgressBar

from Jovi_Spout import JOV_TYPE_IMAGE, JOVBaseNode, JOVImageNode, deep_merge
from .. import JOV_TYPE_ANY, \
JOVBaseNode

from ..core import JOVImageNode, \
deep_merge

# ==============================================================================
# === GLOBAL ===
Expand All @@ -33,6 +37,8 @@
TYPE_PIXEL = Union[int, float, TYPE_iRGB, TYPE_iRGBA, TYPE_fRGB, TYPE_fRGBA]
TYPE_IMAGE = Union[np.ndarray, torch.Tensor]

JOV_TYPE_IMAGE = JOV_TYPE_ANY

# ==============================================================================
# === ENUMERATION ===
# ==============================================================================
Expand Down Expand Up @@ -202,7 +208,7 @@ def cv2tensor_full(image: TYPE_IMAGE, matte:TYPE_PIXEL=(0,0,0,255)) -> Tuple[tor
# ==============================================================================

class SpoutReaderNode(JOVImageNode):
NAME = "SPOUT READER (JOV_SP) 📺"
NAME = "SPOUT READER"
SORT = 50
DESCRIPTION = """
Capture frames from Spout streams. It supports batch processing, allowing multiple frames to be captured simultaneously. The node provides options for configuring the source and number of frames to gather. The captured frames are returned as tensors, enabling further processing downstream.
Expand Down Expand Up @@ -267,7 +273,7 @@ def run(self, **kw) -> Tuple[torch.Tensor, ...]:
return [torch.stack(i) for i in zip(*frames)]

class SpoutWriterNode(JOVBaseNode):
NAME = "SPOUT WRITER (JOV_SP) 🎥"
NAME = "SPOUT WRITER"
RETURN_TYPES = ()
OUTPUT_NODE = True
SORT = 90
Expand Down
4 changes: 2 additions & 2 deletions node_list.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
{
"SPOUT READER (JOV_SP) \ud83d\udcfa": "Capture frames from Spout streams",
"SPOUT WRITER (JOV_SP) \ud83c\udfa5": "Sends frame(s) to a specified Spout receiver application for real-time video sharing"
"SPOUT READER (JOV_SPOUT)": "Capture frames from Spout streams",
"SPOUT WRITER (JOV_SPOUT)": "Sends frame(s) to a specified Spout receiver application for real-time video sharing"
}
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[project]
name = "jovi_spout"
description = "ComfyUI Nodes for using Spout streams "
version = "1.0.2"
version = "1.0.3"
license = { file = "LICENSE" }
dependencies = [
"aenum",
Expand Down

0 comments on commit 46cfe2a

Please sign in to comment.