295 lines
13 KiB
Python
295 lines
13 KiB
Python
"""
|
|
██ ██████ ██ ██ ██ ███ ███ ███████ ████████ ██████ ██ ██ ██
|
|
██ ██ ██ ██ ██ ██ ████ ████ ██ ██ ██ ██ ██ ██ ██
|
|
██ ██ ██ ██ ██ ██ ██ ████ ██ █████ ██ ██████ ██ ███
|
|
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
|
|
█████ ██████ ████ ██ ██ ██ ███████ ██ ██ ██ ██ ██ ██
|
|
|
|
Animation, Image Compositing & Procedural Creation
|
|
|
|
@title: Jovimetrix
|
|
@author: Alexander G. Morano
|
|
@category: Compositing
|
|
@reference: https://github.com/Amorano/Jovimetrix
|
|
@tags: adjust, animate, compose, compositing, composition, device, flow, video,
|
|
mask, shape, animation, logic
|
|
@description: GIPHY. Animation via tick.
|
|
Wave-based parameter modulation, Math operations with
|
|
Unary and Binary support, universal Value conversion for all major
|
|
types (int, string, list, dict, Image, Mask), shape masking, image channel ops,
|
|
batch processing, dynamic bus routing. Queue & Load from URLs.
|
|
@node list:
|
|
ConstantNode, ShapeNode, StereogramNode, StereoscopicNode, TextNode,
|
|
AdjustNode, BlendNode, ColorBlindNode, ColorMatchNode, ColorTheoryNode, CropNode,
|
|
FilterMaskNode, FlattenNode, GradientMapNode, PixelMergeNode, PixelSplitNode,
|
|
PixelSwapNode, StackNode, ThresholdNode,TransformNode,
|
|
ComparisonNode, DelayNode, LerpNode, CalcUnaryOPNode, CalcBinaryOPNode,
|
|
StringerNode, SwizzleNode, TickNode, ValueNode, WaveGeneratorNode,
|
|
AkashicNode, ArrayNode, ExportNode, ValueGraphNode, ImageInfoNode, QueueNode,
|
|
QueueTooNode, RouteNode, SaveOutputNode
|
|
"""
|
|
|
|
__author__ = "Alexander G. Morano"
|
|
__email__ = "amorano@gmail.com"
|
|
|
|
from pathlib import Path
|
|
from typing import Any, Dict
|
|
|
|
from aiohttp import web
|
|
from server import PromptServer
|
|
|
|
from cozy_comfyui import \
|
|
logger
|
|
|
|
from cozy_comfyui.node import \
|
|
loader
|
|
|
|
from cozy_comfyui.api import \
|
|
ComfyAPIMessage
|
|
|
|
JOV_DOCKERENV = False
|
|
try:
|
|
with open('/proc/1/cgroup', 'rt') as f:
|
|
content = f.read()
|
|
JOV_DOCKERENV = any(x in content for x in ['docker', 'kubepods', 'containerd'])
|
|
except FileNotFoundError:
|
|
pass
|
|
|
|
if JOV_DOCKERENV:
|
|
logger.info("RUNNING IN A DOCKER")
|
|
|
|
# ==============================================================================
|
|
# === LEXICON ===
|
|
# ==============================================================================
|
|
|
|
# EMOJI OCD Support
|
|
# 🔗 ⚓ 📀 🍿 🎪 🐘 🤯 😱 💀 ⛓️ 🔒 🔑 🪀 🪁 🧿 🧯 🦚 ♻️ ⚜️ 🚮 🤲🏽 👍 ✳️ ✌🏽 ☝🏽
|
|
|
|
class LexiconMeta(type):
|
|
def __new__(cls, name, bases, dct) -> object:
|
|
_tooltips = {}
|
|
for attr_name, attr_value in dct.items():
|
|
if isinstance(attr_value, tuple):
|
|
attr_name = attr_value[1]
|
|
attr_value = attr_value[0]
|
|
_tooltips[attr_value] = attr_name
|
|
dct['_tooltipsDB'] = _tooltips
|
|
return super().__new__(cls, name, bases, dct)
|
|
|
|
def __getattribute__(cls, name) -> Any | None:
|
|
parts = name.split('.')
|
|
value = super().__getattribute__(parts[0])
|
|
if type(value) == tuple:
|
|
try:
|
|
idx = int(parts[-1])
|
|
value = value[idx]
|
|
except:
|
|
value = value[0]
|
|
return value
|
|
|
|
class Lexicon(metaclass=LexiconMeta):
|
|
A = '⬜', "Alpha"
|
|
ABSOLUTE = 'ABSOLUTE', "Return the absolute value of the input"
|
|
ADAPT = '🧬', "X-Men"
|
|
ALIGN = 'ALIGN', "Top, Center or Bottom alignment"
|
|
AMP = '🔊', "Amplitude"
|
|
ANGLE = '📐', "Rotation Angle"
|
|
ANY = '🔮', "Any Type"
|
|
ANY_OUT = '🦄', "Any Type"
|
|
AUTOSIZE = 'AUTOSIZE', "Scale based on Width & Height"
|
|
AXIS = 'AXIS', "Axis"
|
|
B = '🟦', "Blue"
|
|
BATCH = 'BATCH', "Output as a BATCH (all images in a single Tensor) or as a LIST of images (each image processed separately)"
|
|
BATCH_CHUNK = 'CHUNK', "How many items to put per output. Default (0) is all items"
|
|
BATCH_MODE = 'MODE', "Make, merge, splice or split a batch or list"
|
|
BI = '💙', "Blue Channel"
|
|
BLBR = 'BL-BR', "Bottom Left - Bottom Right"
|
|
BLUR = 'BLUR', "Blur"
|
|
BOOLEAN = '🇴', "Boolean"
|
|
BOTTOM = '🔽', "Bottom"
|
|
BPM = 'BPM', "The number of Beats Per Minute"
|
|
C1 = '🔵', "Color Scheme Result 1"
|
|
C2 = '🟡', "Color Scheme Result 2"
|
|
C3 = '🟣', "Color Scheme Result 3"
|
|
C4 = '⚫️', "Color Scheme Result 4"
|
|
C5 = '⚪', "Color Scheme Result 5"
|
|
COLORMAP = '🇸🇨', "One of two dozen CV2 Built-in Colormap LUT (Look Up Table) Presets"
|
|
COLORMATCH_MAP = 'MAP', "Custom image that will be transformed into a LUT or a built-in cv2 LUT"
|
|
COLORMATCH_MODE = 'MODE', "Match colors from an image or built-in (LUT), Histogram lookups or Reinhard method"
|
|
COLUMNS = 'COLS', "0 = Auto-Fit, >0 = Fit into N columns"
|
|
COMP_A = '😍', "pass this data on a successful condition"
|
|
COMP_B = '🥵', "pass this data on a failure condition"
|
|
COMPARE = '🕵🏽♀️', "Comparison function. Will pass the data in 😍 on successful comparison"
|
|
CONTRAST = '🌓', "Contrast"
|
|
COUNT = 'COUNT', 'Number of things'
|
|
CURRENT = 'CURRENT', "Current"
|
|
DEFICIENCY = 'DEFICIENCY', "Type of color deficiency: Red (Protanopia), Green (Deuteranopia), Blue (Tritanopia)"
|
|
DEPTH = 'DEPTH', "Grayscale image representing a depth map"
|
|
EASE = 'EASE', "Easing function"
|
|
EDGE = 'EDGE', "Clip or Wrap the Canvas Edge"
|
|
ENABLE = 'ENABLE', "Enable or Disable"
|
|
END = 'END', "End of the range"
|
|
FLIP = '🙃', "Flip Input A and Input B with each other"
|
|
FLOAT = '🛟', "Float"
|
|
FOCAL = '📽️', "Focal Length"
|
|
FONT = 'FONT', "Available System Fonts"
|
|
FONT_SIZE = 'SIZE', "Text Size"
|
|
FORMAT = 'FORMAT', "Format"
|
|
FPS = '🏎️', "Frames per second"
|
|
FREQ = 'FREQ', "Frequency"
|
|
FUNC = '⚒️', "Function"
|
|
G = '🟩', "Green"
|
|
GAMMA = '🔆', "Gamma"
|
|
GI = '💚', "Green Channel"
|
|
GRADIENT = '🇲🇺', "Gradient"
|
|
H = '🇭', "Hue"
|
|
HI = 'HI', "High / Top of range"
|
|
HSV = 'HSV', "Hue, Saturation and Value"
|
|
IMAGE = '🖼️', "RGB-A color image with alpha channel"
|
|
IN_A = '🅰️', "Input A"
|
|
IN_B = '🅱️', "Input B"
|
|
INDEX = 'INDEX', "Current item index in the Queue list"
|
|
INT = '🔟', "Integer"
|
|
INVERT = '🔳', "Color Inversion"
|
|
JUSTIFY = 'JUSTIFY', "How to align the text to the side margins of the canvas: Left, Right, or Centered"
|
|
KEY = '🔑', "Key"
|
|
LEFT = '◀️', "Left"
|
|
LENGTH = 'LENGTH', "Length"
|
|
LENGTH2 = 'FULL SIZE', "All items"
|
|
LETTER = 'LETTER', "If each letter be generated and output in a batch"
|
|
LINEAR = '🛟', "Linear"
|
|
LIST = '🧾', "List"
|
|
LMH = 'LMH', "Low, Middle, High"
|
|
LO = 'LO', "Low"
|
|
LOHI = 'LoHi', "Low and High"
|
|
LOOP = '🔄', "Loop"
|
|
LUT = '😎', "Size of each output lut palette square"
|
|
MARGIN = 'MARGIN', "Whitespace padding around canvas"
|
|
MASK = '😷', "Mask or Image to use as Mask to control where adjustments are applied"
|
|
MATTE = 'MATTE', "Background color for padding"
|
|
MI = '🤍', "Alpha Channel"
|
|
MID = 'MID', "Middle"
|
|
MIRROR = '🪞', "Mirror"
|
|
MODE = 'MODE', "Decide whether the images should be resized to fit a specific dimension. Available modes include scaling to fit within given dimensions or keeping the original size"
|
|
NOISE = 'NOISE', "Noise"
|
|
NOTE = '🎶', "Note"
|
|
OFFSET = 'OFFSET', "Offset"
|
|
OPTIMIZE = 'OPT', "Optimize"
|
|
OVERWRITE = 'OVERWRITE', "Overwrite"
|
|
PALETTE = '🎨', "Palette"
|
|
PASS_IN = '📥', "Pass In"
|
|
PASS_OUT = '📤', "Pass Out"
|
|
PHASE = 'PHASE', "Phase"
|
|
PIVOT = 'PIVOT', "Pivot"
|
|
PIXEL = '👾', "Pixel Data (RGBA, RGB or Grayscale)"
|
|
PIXEL_A = '👾A', "Pixel Data (RGBA, RGB or Grayscale)"
|
|
PIXEL_B = '👾B', "Pixel Data (RGBA, RGB or Grayscale)"
|
|
PREFIX = 'PREFIX', "Prefix"
|
|
PROJECTION = 'PROJ', "Projection"
|
|
QUALITY = 'QUALITY', "Quality"
|
|
QUALITY_M = 'MOTION', "Motion Quality"
|
|
QUEUE = 'Q', "Current items to process during Queue iteration."
|
|
R = '🟥', "Red"
|
|
RADIUS = '🅡', "Radius"
|
|
RANGE = 'RANGE', "start index, ending index (0 means full length) and how many items to skip per step"
|
|
RECURSE = 'RECURSE', "Search within sub-directories"
|
|
REPLACE = 'REPLACE', "String to use as replacement"
|
|
RESET = 'RESET', "Reset"
|
|
RGB = '🌈', "RGB (no alpha) Color"
|
|
RGB_A = '🌈A', "RGB (no alpha) Color"
|
|
RGBA_A = '🌈A', "RGB with Alpha Color"
|
|
RI = '❤️', "Red Channel"
|
|
RIGHT = '▶️', "Right"
|
|
ROUTE = '🚌', "Route"
|
|
S = '🇸', "Saturation"
|
|
SAMPLE = '🎞️', "Method for resizing images."
|
|
SCHEME = 'SCHEME', "Scheme"
|
|
SEED = 'seed', "Random generator's initial value"
|
|
SHAPE = 'SHAPE', "Circle, Square or Polygonal forms"
|
|
SHIFT = 'SHIFT', "Shift"
|
|
SIDES = 'SIDES', "Number of sides polygon has (3-100)"
|
|
SIMULATOR = 'SIMULATOR', "Solver to use when translating to new color space"
|
|
SIZE = '📏', "Scalar by which to scale the input"
|
|
SPACING = 'SPACING', "Line Spacing between Text Lines"
|
|
START = 'START', "Start of the range"
|
|
STEP = '🦶🏽', "Steps/Stride between pulses -- useful to do odd or even batches. If set to 0 will stretch from (VAL -> LOOP) / Batch giving a linear range of values."
|
|
STOP = 'STOP', "Halt processing"
|
|
STRENGTH = '💪🏽', "Strength"
|
|
STRING = '📝', "String Entry"
|
|
SWAP_A = 'SWAP A', "Replace input Alpha channel with target channel or constant"
|
|
SWAP_B = 'SWAP B', "Replace input Blue channel with target channel or constant"
|
|
SWAP_G = 'SWAP G', "Replace input Green channel with target channel or constant"
|
|
SWAP_R = 'SWAP R', "Replace input Red channel with target channel or constant"
|
|
SWAP_W = 'SWAP W', "Replace input W channel with target channel or constant"
|
|
SWAP_X = 'SWAP X', "Replace input Red channel with target channel or constant"
|
|
SWAP_Y = 'SWAP Y', "Replace input Red channel with target channel or constant"
|
|
SWAP_Z = 'SWAP Z', "Replace input Red channel with target channel or constant"
|
|
THRESHOLD = '📉', "Threshold"
|
|
TILE = 'TILE', "How many times to repeat the data in the X and Y"
|
|
TIME = '🕛', "Time"
|
|
TIMER = '⏱', "Timer"
|
|
TLTR = 'TL-TR', "Top Left - Top Right"
|
|
TOP = '🔼', "Top"
|
|
TOTAL = 'TOTAL', "Total items in the current Queue List"
|
|
TRIGGER = '⚡', "Trigger"
|
|
TYPE = '❓', "Type"
|
|
UNKNOWN = '❔', "Unknown"
|
|
V = '🇻', "Value"
|
|
VALUE = 'VAL', "Value"
|
|
VEC = 'VECTOR', "Compound value of type float, vec2, vec3 or vec4"
|
|
W = '🇼', "Width"
|
|
WAIT = '✋🏽', "Wait"
|
|
WAVE = '♒', "Wave Function"
|
|
WH = '🇼🇭', "Width and Height as a Vector2 (x,y)"
|
|
WHC = '🇼🇭🇨', "Width, Height and Channel as a Vector3 (x,y,z)"
|
|
X = '🇽', "X"
|
|
XY = '🇽🇾', "X and Y"
|
|
Y = '🇾', "Y"
|
|
Z = '🇿', "Z"
|
|
|
|
@classmethod
|
|
def _parse(cls, node: dict) -> Dict[str, str]:
|
|
for cat, entry in node.items():
|
|
if cat not in ['optional', 'required']:
|
|
continue
|
|
for k, v in entry.items():
|
|
if (widget_data := v[1] if isinstance(v, (tuple, list,)) and len(v) > 1 else None) is None:
|
|
continue
|
|
if (tip := widget_data.get("tooltip", None)):
|
|
continue
|
|
if (tip := cls._tooltipsDB.get(k, None)) is None:
|
|
continue
|
|
widget_data["tooltip"] = tip
|
|
node[cat][k] = (v[0], widget_data)
|
|
return node
|
|
|
|
# ==============================================================================
|
|
# === GLOBAL ===
|
|
# ==============================================================================
|
|
|
|
PACKAGE = "JOVIMETRIX"
|
|
WEB_DIRECTORY = "./web"
|
|
ROOT = Path(__file__).resolve().parent
|
|
NODE_CLASS_MAPPINGS, NODE_DISPLAY_NAME_MAPPINGS = loader(ROOT,
|
|
PACKAGE,
|
|
"core",
|
|
f"{PACKAGE} 🔺🟩🔵",
|
|
False)
|
|
|
|
# ==============================================================================
|
|
# === API RESPONSE ===
|
|
# ==============================================================================
|
|
|
|
@PromptServer.instance.routes.get("/jovimetrix/message")
|
|
async def jovimetrix_message(req) -> Any:
|
|
return web.json_response(ComfyAPIMessage.MESSAGE)
|
|
|
|
@PromptServer.instance.routes.post("/jovimetrix/message")
|
|
async def jovimetrix_message_post(req) -> Any:
|
|
json_data = await req.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({})
|