Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
240176df63 |
+4
-52
@@ -1,20 +1,14 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from typing import Type
|
||||
|
||||
from .base import *
|
||||
from .colors import *
|
||||
from .curves import *
|
||||
from .image_processing import *
|
||||
from .inputfields import *
|
||||
from .loaders import *
|
||||
from .noise import *
|
||||
from .output import *
|
||||
from .prompting import *
|
||||
from .seq_processing import *
|
||||
from .switches import *
|
||||
from .utility import *
|
||||
from .calculate import *
|
||||
from .laboratory import *
|
||||
|
||||
_NODE_CLASSES: List[Type] = [DreamSineWave, DreamLinear, DreamCSVCurve, DreamBeatCurve, DreamFrameDimensions,
|
||||
DreamImageMotion, DreamNoiseFromPalette, DreamAnalyzePalette, DreamColorShift,
|
||||
@@ -22,20 +16,12 @@ _NODE_CLASSES: List[Type] = [DreamSineWave, DreamLinear, DreamCSVCurve, DreamBea
|
||||
DreamSimpleFrameCounter, DreamImageSequenceInputWithDefaultFallback,
|
||||
DreamImageSequenceOutput, DreamCSVGenerator, DreamImageAreaSampler,
|
||||
DreamVideoEncoder, DreamSequenceTweening, DreamSequenceBlend, DreamColorAlign,
|
||||
DreamImageSampler, DreamNoiseFromAreaPalettes, DreamVideoEncoderMpegCoder,
|
||||
DreamInputString, DreamInputFloat, DreamInputInt, DreamInputText, DreamBigLatentSwitch,
|
||||
DreamFrameCountCalculator, DreamBigImageSwitch, DreamBigTextSwitch, DreamBigFloatSwitch,
|
||||
DreamBigIntSwitch, DreamBigPaletteSwitch, DreamWeightedPromptBuilder, DreamPromptFinalizer,
|
||||
DreamFrameCounterInfo, DreamBoolToFloat, DreamBoolToInt, DreamSawWave, DreamTriangleWave,
|
||||
DreamTriangleEvent, DreamSmoothEvent, DreamCalculation, DreamImageColorShift,
|
||||
DreamComparePalette, DreamImageContrast, DreamImageBrightness, DreamLogFile,
|
||||
DreamLaboratory, DreamStringToLog, DreamIntToLog, DreamFloatToLog, DreamJoinLog,
|
||||
DreamStringTokenizer]
|
||||
DreamImageSampler, DreamNoiseFromAreaPalettes]
|
||||
_SIGNATURE_SUFFIX = " [Dream]"
|
||||
|
||||
MANIFEST = {
|
||||
"name": "Dream Project Animation",
|
||||
"version": (4, 2, 0),
|
||||
"version": (2, 0, 0),
|
||||
"author": "Dream Project",
|
||||
"project": "https://github.com/alt-key-project/comfyui-dream-project",
|
||||
"description": "Various utility nodes for creating animations with ComfyUI",
|
||||
@@ -45,47 +31,13 @@ NODE_CLASS_MAPPINGS = {}
|
||||
|
||||
NODE_DISPLAY_NAME_MAPPINGS = {}
|
||||
|
||||
config = DreamConfig()
|
||||
|
||||
|
||||
def update_category(cls):
|
||||
top = config.get("ui.top_category", "").strip().strip("/")
|
||||
leaf_icon = ""
|
||||
if top and "CATEGORY" in cls.__dict__:
|
||||
cls.CATEGORY = top + "/" + cls.CATEGORY.lstrip("/")
|
||||
if "CATEGORY" in cls.__dict__:
|
||||
joined = []
|
||||
for partial in cls.CATEGORY.split("/"):
|
||||
icon = config.get("ui.category_icons." + partial, "")
|
||||
if icon:
|
||||
leaf_icon = icon
|
||||
if config.get("ui.prepend_icon_to_category", False):
|
||||
partial = icon.lstrip() + " " + partial
|
||||
if config.get("ui.append_icon_to_category", False):
|
||||
partial = partial + " " + icon.rstrip()
|
||||
joined.append(partial)
|
||||
cls.CATEGORY = "/".join(joined)
|
||||
return leaf_icon
|
||||
|
||||
|
||||
def update_display_name(cls, category_icon, display_name):
|
||||
icon = cls.__dict__.get("ICON", category_icon)
|
||||
if config.get("ui.prepend_icon_to_node", False):
|
||||
display_name = icon.lstrip() + " " + display_name
|
||||
if config.get("ui.append_icon_to_node", False):
|
||||
display_name = display_name + " " + icon.rstrip()
|
||||
return display_name
|
||||
|
||||
|
||||
for cls in _NODE_CLASSES:
|
||||
category_icon = update_category(cls)
|
||||
clsname = cls.__name__
|
||||
if "NODE_NAME" in cls.__dict__:
|
||||
node_name = cls.__dict__["NODE_NAME"] + _SIGNATURE_SUFFIX
|
||||
NODE_CLASS_MAPPINGS[node_name] = cls
|
||||
NODE_DISPLAY_NAME_MAPPINGS[node_name] = update_display_name(cls, category_icon,
|
||||
cls.__dict__.get("DISPLAY_NAME",
|
||||
cls.__dict__["NODE_NAME"]))
|
||||
display_name = cls.__dict__.get("DISPLAY_NAME", cls.__dict__["NODE_NAME"]) + _SIGNATURE_SUFFIX
|
||||
NODE_DISPLAY_NAME_MAPPINGS[node_name] = display_name
|
||||
else:
|
||||
raise Exception("Class {} is missing NODE_NAME!".format(str(cls)))
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import glob
|
||||
|
||||
from .categories import NodeCategories
|
||||
@@ -6,40 +5,8 @@ from .shared import *
|
||||
from .types import *
|
||||
|
||||
|
||||
class DreamFrameCounterInfo:
|
||||
NODE_NAME = "Frame Counter Info"
|
||||
ICON = "⚋"
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": SharedTypes.frame_counter
|
||||
}
|
||||
|
||||
CATEGORY = NodeCategories.ANIMATION
|
||||
RETURN_TYPES = ("INT", "INT", "BOOLEAN", "BOOLEAN", "FLOAT", "FLOAT", "FLOAT", "FLOAT")
|
||||
RETURN_NAMES = ("frames_completed", "total_frames", "first_frame", "last_frame",
|
||||
"elapsed_seconds", "remaining_seconds", "total_seconds", "completion")
|
||||
FUNCTION = "result"
|
||||
|
||||
@classmethod
|
||||
def IS_CHANGED(cls, *v):
|
||||
return ALWAYS_CHANGED_FLAG
|
||||
|
||||
def result(self, frame_counter: FrameCounter):
|
||||
return (frame_counter.current_frame,
|
||||
frame_counter.total_frames,
|
||||
frame_counter.is_first_frame,
|
||||
frame_counter.is_final_frame,
|
||||
frame_counter.current_time_in_seconds,
|
||||
frame_counter.remaining_time_in_seconds,
|
||||
frame_counter.total_time_in_seconds,
|
||||
frame_counter.current_time_in_seconds / max(0.01, frame_counter.total_time_in_seconds))
|
||||
|
||||
|
||||
class DreamDirectoryFileCount:
|
||||
NODE_NAME = "File Count"
|
||||
ICON = "📂"
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
@@ -59,22 +26,18 @@ class DreamDirectoryFileCount:
|
||||
def IS_CHANGED(cls, *v):
|
||||
return ALWAYS_CHANGED_FLAG
|
||||
|
||||
def result(self, directory_path, patterns):
|
||||
def result(self, directory_path, patterns, indexing):
|
||||
if not os.path.isdir(directory_path):
|
||||
return (0,)
|
||||
total = 0
|
||||
for pattern in patterns.split("|"):
|
||||
files = list(glob.glob(pattern, root_dir=directory_path))
|
||||
total += len(files)
|
||||
print("total " + str(total))
|
||||
total += len(glob.glob(pattern))
|
||||
return (total,)
|
||||
|
||||
|
||||
class DreamFrameCounterOffset:
|
||||
NODE_NAME = "Frame Counter Offset"
|
||||
|
||||
ICON = "±"
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
@@ -98,7 +61,6 @@ class DreamFrameCounterOffset:
|
||||
|
||||
class DreamSimpleFrameCounter:
|
||||
NODE_NAME = "Frame Counter (Simple)"
|
||||
ICON = "⚋"
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
@@ -126,7 +88,6 @@ class DreamSimpleFrameCounter:
|
||||
|
||||
class DreamDirectoryBackedFrameCounter:
|
||||
NODE_NAME = "Frame Counter (Directory)"
|
||||
ICON = "⚋"
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
@@ -136,7 +97,7 @@ class DreamDirectoryBackedFrameCounter:
|
||||
"pattern": ("STRING", {"default": '*', "multiline": False}),
|
||||
"indexing": (["numeric", "alphabetic order"],),
|
||||
"total_frames": ("INT", {"default": 100, "min": 2, "max": 24 * 3600 * 60}),
|
||||
"frames_per_second": ("INT", {"min": 1, "default": 30}),
|
||||
"frames_per_second": ("INT", {"min": 1, "default": 25}),
|
||||
},
|
||||
}
|
||||
|
||||
@@ -155,33 +116,3 @@ class DreamDirectoryBackedFrameCounter:
|
||||
return (FrameCounter(0, total_frames, frames_per_second),)
|
||||
n = max(results.keys()) + 1
|
||||
return (FrameCounter(n, total_frames, frames_per_second),)
|
||||
|
||||
|
||||
class DreamFrameCountCalculator:
|
||||
NODE_NAME = "Frame Count Calculator"
|
||||
ICON = "⌛"
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"hours": ("INT", {"min": 0, "default": 0, "max": 23}),
|
||||
"minutes": ("INT", {"min": 0, "default": 0, "max": 59}),
|
||||
"seconds": ("INT", {"min": 0, "default": 10, "max": 59}),
|
||||
"milliseconds": ("INT", {"min": 0, "default": 0, "max": 59}),
|
||||
"frames_per_second": ("INT", {"min": 1, "default": 30})
|
||||
},
|
||||
}
|
||||
|
||||
CATEGORY = NodeCategories.ANIMATION
|
||||
RETURN_TYPES = ("INT",)
|
||||
RETURN_NAMES = ("TOTAL",)
|
||||
FUNCTION = "result"
|
||||
|
||||
@classmethod
|
||||
def IS_CHANGED(cls, *v):
|
||||
return ALWAYS_CHANGED_FLAG
|
||||
|
||||
def result(self, hours, minutes, seconds, milliseconds, frames_per_second):
|
||||
total_s = seconds + 0.001 * milliseconds + minutes * 60 + hours * 3600
|
||||
return (round(total_s * frames_per_second),)
|
||||
|
||||
@@ -1,99 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import math
|
||||
|
||||
from evalidate import Expr, EvalException, base_eval_model
|
||||
|
||||
from .categories import *
|
||||
from .err import on_error
|
||||
from .shared import hashed_as_strings
|
||||
|
||||
|
||||
class DreamCalculation:
|
||||
NODE_NAME = "Calculation"
|
||||
ICON = "🖩"
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"expression": ("STRING", {"default": "a + b + c - (r * s * t)", "multiline": True})
|
||||
},
|
||||
"optional": {
|
||||
"a_int": ("INT", {"default": 0, "multiline": False}),
|
||||
"b_int": ("INT", {"default": 0, "multiline": False}),
|
||||
"c_int": ("INT", {"default": 0, "multiline": False}),
|
||||
"r_float": ("FLOAT", {"default": 0.0, "multiline": False}),
|
||||
"s_float": ("FLOAT", {"default": 0.0, "multiline": False}),
|
||||
"t_float": ("FLOAT", {"default": 0.0, "multiline": False})
|
||||
}
|
||||
}
|
||||
|
||||
CATEGORY = NodeCategories.UTILS
|
||||
RETURN_TYPES = ("FLOAT", "INT")
|
||||
RETURN_NAMES = ("FLOAT", "INT")
|
||||
FUNCTION = "result"
|
||||
|
||||
@classmethod
|
||||
def IS_CHANGED(cls, *values):
|
||||
return hashed_as_strings(*values)
|
||||
|
||||
def _make_model(self):
|
||||
funcs = self._make_functions()
|
||||
m = base_eval_model.clone()
|
||||
m.nodes.append('Mult')
|
||||
m.nodes.append('Call')
|
||||
for funname in funcs.keys():
|
||||
m.allowed_functions.append(funname)
|
||||
return (m, funcs)
|
||||
|
||||
def _make_functions(self):
|
||||
return {
|
||||
"round": round,
|
||||
"float": float,
|
||||
"int": int,
|
||||
"abs": abs,
|
||||
"min": min,
|
||||
"max": max,
|
||||
"tan": math.tan,
|
||||
"tanh": math.tanh,
|
||||
"sin": math.sin,
|
||||
"sinh": math.sinh,
|
||||
"cos": math.cos,
|
||||
"cosh": math.cosh,
|
||||
"pow": math.pow,
|
||||
"sqrt": math.sqrt,
|
||||
"ceil": math.ceil,
|
||||
"floor": math.floor,
|
||||
"pi": math.pi,
|
||||
"log": math.log,
|
||||
"log2": math.log2,
|
||||
"acos": math.acos,
|
||||
"asin": math.asin,
|
||||
"acosh": math.acosh,
|
||||
"asinh": math.asinh,
|
||||
"atan": math.atan,
|
||||
"atanh": math.atanh,
|
||||
"exp": math.exp,
|
||||
"fmod": math.fmod,
|
||||
"factorial": math.factorial,
|
||||
"dist": math.dist,
|
||||
"atan2": math.atan2,
|
||||
"log10": math.log10
|
||||
}
|
||||
|
||||
def result(self, expression, **values):
|
||||
model, funcs = self._make_model()
|
||||
vars = funcs
|
||||
for key in ("a_int", "b_int", "c_int", "r_float", "s_float", "t_float"):
|
||||
nm = key.split("_")[0]
|
||||
v = values.get(key, None)
|
||||
if v is not None:
|
||||
vars[nm] = v
|
||||
try:
|
||||
data = Expr(expression, model=model).eval(vars)
|
||||
if isinstance(data, (int, float)):
|
||||
return float(data), int(round(data))
|
||||
else:
|
||||
return 0.0, 0
|
||||
except EvalException as e:
|
||||
on_error(DreamCalculation, str(e))
|
||||
@@ -1,15 +1,11 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
class NodeCategories:
|
||||
ANIMATION = "animation"
|
||||
ANIMATION_POSTPROCESSING = ANIMATION + "/postprocessing"
|
||||
ANIMATION_TRANSFORMS = ANIMATION + "/transforms"
|
||||
ANIMATION_CURVES = "animation/curves"
|
||||
CONDITIONING = "conditioning"
|
||||
IMAGE_POSTPROCESSING = "image/postprocessing"
|
||||
IMAGE_ANIMATION = "image/animation"
|
||||
IMAGE_COLORS = "image/color"
|
||||
IMAGE_GENERATE = "image/generate"
|
||||
IMAGE = "image"
|
||||
UTILS = "utils"
|
||||
UTILS_SWITCHES = "utils/switches"
|
||||
@@ -1,5 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from .categories import NodeCategories
|
||||
from .shared import *
|
||||
from .types import *
|
||||
@@ -129,7 +127,7 @@ class DreamColorAlign:
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": SharedTypes.palette | {
|
||||
"target_align": (RGBPalette.ID,),
|
||||
"target_align": (RGBPalette.ID, {"forceInput": True}),
|
||||
"alignment_factor": ("FLOAT", {"default": 0.5, "min": 0.0, "max": 10.0, "step": 0.1}),
|
||||
}
|
||||
}
|
||||
@@ -152,8 +150,8 @@ class DreamColorAlign:
|
||||
for i in range(len(palette)):
|
||||
p = palette[i]
|
||||
t = target_align[i]
|
||||
(_, _, r1, g1, b1) = p.analyze()
|
||||
(_, _, r2, g2, b2) = t.analyze()
|
||||
(_, r1, g1, b1) = p.analyze()
|
||||
(_, r2, g2, b2) = t.analyze()
|
||||
|
||||
dr = (r2 - r1) * alignment_factor
|
||||
dg = (g2 - g1) * alignment_factor
|
||||
@@ -215,150 +213,8 @@ class DreamColorShift:
|
||||
return (tuple(results),)
|
||||
|
||||
|
||||
class DreamImageColorShift:
|
||||
NODE_NAME = "Image Color Shift"
|
||||
ICON = "🖼"
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {"image": ("IMAGE",),
|
||||
"red_multiplier": ("FLOAT", {"default": 1.0, "min": 0.0}),
|
||||
"green_multiplier": ("FLOAT", {"default": 1.0, "min": 0.0}),
|
||||
"blue_multiplier": ("FLOAT", {"default": 1.0, "min": 0.0}),
|
||||
},
|
||||
|
||||
}
|
||||
|
||||
CATEGORY = NodeCategories.IMAGE_COLORS
|
||||
RETURN_TYPES = ("IMAGE",)
|
||||
RETURN_NAMES = ("image",)
|
||||
FUNCTION = "result"
|
||||
|
||||
@classmethod
|
||||
def IS_CHANGED(cls, *values):
|
||||
return ALWAYS_CHANGED_FLAG
|
||||
|
||||
def result(self, image, red_multiplier, green_multiplier, blue_multiplier):
|
||||
proc = DreamImageProcessor(inputs=image)
|
||||
|
||||
def recolor(im: DreamImage, *a, **args):
|
||||
return (im.adjust_colors(red_multiplier, green_multiplier, blue_multiplier),)
|
||||
|
||||
return proc.process(recolor)
|
||||
|
||||
|
||||
class DreamImageBrightness:
|
||||
NODE_NAME = "Image Brightness Adjustment"
|
||||
ICON = "☼"
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {"image": ("IMAGE",),
|
||||
"factor": ("FLOAT", {"default": 1.0, "min": 0.0}),
|
||||
},
|
||||
|
||||
}
|
||||
|
||||
CATEGORY = NodeCategories.IMAGE_COLORS
|
||||
RETURN_TYPES = ("IMAGE",)
|
||||
RETURN_NAMES = ("image",)
|
||||
FUNCTION = "result"
|
||||
|
||||
@classmethod
|
||||
def IS_CHANGED(cls, *values):
|
||||
return ALWAYS_CHANGED_FLAG
|
||||
|
||||
def result(self, image, factor):
|
||||
proc = DreamImageProcessor(inputs=image)
|
||||
|
||||
def change(im: DreamImage, *a, **args):
|
||||
return (im.change_brightness(factor),)
|
||||
|
||||
return proc.process(change)
|
||||
|
||||
|
||||
class DreamImageContrast:
|
||||
NODE_NAME = "Image Contrast Adjustment"
|
||||
ICON = "◐"
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {"image": ("IMAGE",),
|
||||
"factor": ("FLOAT", {"default": 1.0, "min": 0.0}),
|
||||
},
|
||||
|
||||
}
|
||||
|
||||
CATEGORY = NodeCategories.IMAGE_COLORS
|
||||
RETURN_TYPES = ("IMAGE",)
|
||||
RETURN_NAMES = ("image",)
|
||||
FUNCTION = "result"
|
||||
|
||||
@classmethod
|
||||
def IS_CHANGED(cls, *values):
|
||||
return ALWAYS_CHANGED_FLAG
|
||||
|
||||
def result(self, image, factor):
|
||||
proc = DreamImageProcessor(inputs=image)
|
||||
|
||||
def change(im: DreamImage, *a, **args):
|
||||
return (im.change_contrast(factor),)
|
||||
|
||||
return proc.process(change)
|
||||
|
||||
|
||||
class DreamComparePalette:
|
||||
NODE_NAME = "Compare Palettes"
|
||||
ICON = "📊"
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"a": (RGBPalette.ID,),
|
||||
"b": (RGBPalette.ID,),
|
||||
},
|
||||
}
|
||||
|
||||
CATEGORY = NodeCategories.IMAGE_COLORS
|
||||
RETURN_TYPES = ("FLOAT", "FLOAT", "FLOAT", "FLOAT")
|
||||
RETURN_NAMES = (
|
||||
"brightness_multiplier", "contrast_multiplier", "red_multiplier", "green_multiplier", "blue_multiplier")
|
||||
FUNCTION = "result"
|
||||
|
||||
@classmethod
|
||||
def IS_CHANGED(cls, *values):
|
||||
return ALWAYS_CHANGED_FLAG
|
||||
|
||||
def result(self, a, b):
|
||||
MIN_VALUE = 1 / 255.0
|
||||
|
||||
brightness = list()
|
||||
contrasts = list()
|
||||
reds = list()
|
||||
greens = list()
|
||||
blues = list()
|
||||
|
||||
for i in range(min(len(a), len(b))):
|
||||
(bright, ctr, red, green, blue) = a[i].analyze()
|
||||
(bright2, ctr2, red2, green2, blue2) = b[i].analyze()
|
||||
brightness.append(bright2 / max(MIN_VALUE, bright))
|
||||
contrasts.append(ctr2 / max(MIN_VALUE, ctr))
|
||||
reds.append(red2 / max(MIN_VALUE, red))
|
||||
greens.append(green2 / max(MIN_VALUE, green))
|
||||
blues.append(blue2 / max(MIN_VALUE, blue))
|
||||
|
||||
n = len(brightness)
|
||||
|
||||
return (sum(brightness) / n, sum(contrasts) / n, sum(reds) / n,
|
||||
sum(greens) / n, sum(blues) / n)
|
||||
|
||||
|
||||
class DreamAnalyzePalette:
|
||||
NODE_NAME = "Analyze Palette"
|
||||
ICON = "📊"
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
@@ -368,8 +224,8 @@ class DreamAnalyzePalette:
|
||||
}
|
||||
|
||||
CATEGORY = NodeCategories.IMAGE_COLORS
|
||||
RETURN_TYPES = ("FLOAT", "FLOAT", "FLOAT", "FLOAT", "FLOAT")
|
||||
RETURN_NAMES = ("brightness", "contrast", "redness", "greenness", "blueness")
|
||||
RETURN_TYPES = ("FLOAT", "FLOAT", "FLOAT", "FLOAT")
|
||||
RETURN_NAMES = ("brightness", "redness", "greenness", "blueness")
|
||||
FUNCTION = "result"
|
||||
|
||||
@classmethod
|
||||
@@ -378,13 +234,12 @@ class DreamAnalyzePalette:
|
||||
|
||||
def result(self, palette):
|
||||
f = 1.0 / len(palette)
|
||||
(w, c, r, g, b) = (0, 0, 0, 0, 0)
|
||||
(w, r, g, b) = (0, 0, 0, 0)
|
||||
for p in palette:
|
||||
(brightness, contrast, red, green, blue) = p.analyze()
|
||||
(brightness, red, green, blue) = p.analyze()
|
||||
w += brightness
|
||||
c += contrast
|
||||
r += red
|
||||
g += green
|
||||
b += blue
|
||||
|
||||
return w * f, c * f, r * f, g * f, b * f
|
||||
return w * f, r * f, g * f, b * f
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import csv
|
||||
import math
|
||||
|
||||
@@ -7,20 +6,6 @@ from .shared import hashed_as_strings
|
||||
from .types import SharedTypes, FrameCounter
|
||||
|
||||
|
||||
def _linear_value_calc(x, x_start, x_end, y_start, y_end):
|
||||
if x <= x_start:
|
||||
return y_start
|
||||
if x >= x_end:
|
||||
return y_end
|
||||
dx = max(x_end - x_start, 0.0001)
|
||||
n = (x - x_start) / dx
|
||||
return (y_end - y_start) * n + y_start
|
||||
|
||||
|
||||
def _curve_result(f: float):
|
||||
return (f, int(round(f)))
|
||||
|
||||
|
||||
class DreamSineWave:
|
||||
NODE_NAME = "Sine Curve"
|
||||
|
||||
@@ -51,149 +36,7 @@ class DreamSineWave:
|
||||
b = 2 * math.pi / periodicity_seconds
|
||||
d = (max_value + min_value) / 2
|
||||
y = a * math.sin(b * (x + c)) + d
|
||||
return _curve_result(y)
|
||||
|
||||
|
||||
class DreamSawWave:
|
||||
NODE_NAME = "Saw Curve"
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": SharedTypes.frame_counter | {
|
||||
"max_value": ("FLOAT", {"default": 1.0, "multiline": False}),
|
||||
"min_value": ("FLOAT", {"default": 0.0, "multiline": False}),
|
||||
"periodicity_seconds": ("FLOAT", {"default": 10.0, "multiline": False, "min": 0.01}),
|
||||
"phase": ("FLOAT", {"default": 0.0, "multiline": False, "min": -1, "max": 1}),
|
||||
},
|
||||
}
|
||||
|
||||
CATEGORY = NodeCategories.ANIMATION_CURVES
|
||||
RETURN_TYPES = ("FLOAT", "INT")
|
||||
RETURN_NAMES = ("FLOAT", "INT")
|
||||
FUNCTION = "result"
|
||||
|
||||
@classmethod
|
||||
def IS_CHANGED(cls, *values):
|
||||
return hashed_as_strings(*values)
|
||||
|
||||
def result(self, frame_counter: FrameCounter, max_value, min_value, periodicity_seconds, phase):
|
||||
x = frame_counter.current_time_in_seconds
|
||||
x = ((x + periodicity_seconds * phase) % periodicity_seconds) / periodicity_seconds
|
||||
y = x * (max_value - min_value) + min_value
|
||||
return _curve_result(y)
|
||||
|
||||
|
||||
class DreamTriangleWave:
|
||||
NODE_NAME = "Triangle Curve"
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": SharedTypes.frame_counter | {
|
||||
"max_value": ("FLOAT", {"default": 1.0, "multiline": False}),
|
||||
"min_value": ("FLOAT", {"default": 0.0, "multiline": False}),
|
||||
"periodicity_seconds": ("FLOAT", {"default": 10.0, "multiline": False, "min": 0.01}),
|
||||
"phase": ("FLOAT", {"default": 0.0, "multiline": False, "min": -1, "max": 1}),
|
||||
},
|
||||
}
|
||||
|
||||
CATEGORY = NodeCategories.ANIMATION_CURVES
|
||||
RETURN_TYPES = ("FLOAT", "INT")
|
||||
RETURN_NAMES = ("FLOAT", "INT")
|
||||
FUNCTION = "result"
|
||||
|
||||
@classmethod
|
||||
def IS_CHANGED(cls, *values):
|
||||
return hashed_as_strings(*values)
|
||||
|
||||
def result(self, frame_counter: FrameCounter, max_value, min_value, periodicity_seconds, phase):
|
||||
x = frame_counter.current_time_in_seconds
|
||||
x = ((x + periodicity_seconds * phase) % periodicity_seconds) / periodicity_seconds
|
||||
if x <= 0.5:
|
||||
x *= 2
|
||||
y = x * (max_value - min_value) + min_value
|
||||
else:
|
||||
x = (x - 0.5) * 2
|
||||
y = max_value - x * (max_value - min_value)
|
||||
return _curve_result(y)
|
||||
|
||||
|
||||
class DreamTriangleEvent:
|
||||
NODE_NAME = "Triangle Event Curve"
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": SharedTypes.frame_counter | {
|
||||
"max_value": ("FLOAT", {"default": 1.0, "multiline": False}),
|
||||
"min_value": ("FLOAT", {"default": 0.0, "multiline": False}),
|
||||
"width_seconds": ("FLOAT", {"default": 1.0, "multiline": False, "min": 0.1}),
|
||||
"center_seconds": ("FLOAT", {"default": 10.0, "multiline": False, "min": 0.0}),
|
||||
},
|
||||
}
|
||||
|
||||
CATEGORY = NodeCategories.ANIMATION_CURVES
|
||||
RETURN_TYPES = ("FLOAT", "INT")
|
||||
RETURN_NAMES = ("FLOAT", "INT")
|
||||
FUNCTION = "result"
|
||||
|
||||
@classmethod
|
||||
def IS_CHANGED(cls, *values):
|
||||
return hashed_as_strings(*values)
|
||||
|
||||
def result(self, frame_counter: FrameCounter, max_value, min_value, width_seconds, center_seconds):
|
||||
x = frame_counter.current_time_in_seconds
|
||||
start = center_seconds - width_seconds * 0.5
|
||||
end = center_seconds + width_seconds * 0.5
|
||||
if start <= x <= center_seconds:
|
||||
y = _linear_value_calc(x, start, center_seconds, min_value, max_value)
|
||||
elif center_seconds < x <= end:
|
||||
y = _linear_value_calc(x, center_seconds, end, max_value, min_value)
|
||||
else:
|
||||
y = min_value
|
||||
return _curve_result(y)
|
||||
|
||||
|
||||
class DreamSmoothEvent:
|
||||
NODE_NAME = "Smooth Event Curve"
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": SharedTypes.frame_counter | {
|
||||
"max_value": ("FLOAT", {"default": 1.0, "multiline": False}),
|
||||
"min_value": ("FLOAT", {"default": 0.0, "multiline": False}),
|
||||
"width_seconds": ("FLOAT", {"default": 1.0, "multiline": False, "min": 0.1}),
|
||||
"center_seconds": ("FLOAT", {"default": 10.0, "multiline": False, "min": 0.0}),
|
||||
},
|
||||
}
|
||||
|
||||
CATEGORY = NodeCategories.ANIMATION_CURVES
|
||||
RETURN_TYPES = ("FLOAT", "INT")
|
||||
RETURN_NAMES = ("FLOAT", "INT")
|
||||
FUNCTION = "result"
|
||||
|
||||
@classmethod
|
||||
def IS_CHANGED(cls, *values):
|
||||
return hashed_as_strings(*values)
|
||||
|
||||
def result(self, frame_counter: FrameCounter, max_value, min_value, width_seconds, center_seconds):
|
||||
x = frame_counter.current_time_in_seconds
|
||||
start = center_seconds - width_seconds * 0.5
|
||||
end = center_seconds + width_seconds * 0.5
|
||||
if start <= x <= center_seconds:
|
||||
y = _linear_value_calc(x, start, center_seconds, 0.0, 1.0)
|
||||
elif center_seconds < x <= end:
|
||||
y = _linear_value_calc(x, center_seconds, end, 1.0, 0.0)
|
||||
else:
|
||||
y = 0.0
|
||||
if y < 0.5:
|
||||
y = ((y + y) * (y + y)) * 0.5
|
||||
else:
|
||||
a = (y - 0.5) * 2
|
||||
y = math.pow(a, 0.25) * 0.5 + 0.5
|
||||
return _curve_result(y * (max_value - min_value) + min_value)
|
||||
return (y, int(round(y)))
|
||||
|
||||
|
||||
class DreamBeatCurve:
|
||||
@@ -252,7 +95,7 @@ class DreamBeatCurve:
|
||||
v = 1.0 - v
|
||||
|
||||
r = low_value + v * (high_value - low_value)
|
||||
return _curve_result(r)
|
||||
return (r, int(round(r)))
|
||||
|
||||
|
||||
class DreamLinear:
|
||||
@@ -292,7 +135,6 @@ def _is_as_float(s: str):
|
||||
|
||||
class DreamCSVGenerator:
|
||||
NODE_NAME = "CSV Generator"
|
||||
ICON = "⌗"
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
def run_disable():
|
||||
pass
|
||||
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
class DreamLog:
|
||||
def __init__(self, debug_active=False):
|
||||
self._debug = debug_active
|
||||
|
||||
def _print(self, text: str, *args, **kwargs):
|
||||
if args or kwargs:
|
||||
text = text.format(*args, **kwargs)
|
||||
print("[DREAM] " + text)
|
||||
|
||||
def error(self, text: str, *args, **kwargs):
|
||||
self._print(text, *args, **kwargs)
|
||||
|
||||
def info(self, text: str, *args, **kwargs):
|
||||
self._print(text, *args, **kwargs)
|
||||
|
||||
def debug(self, text: str, *args, **kwargs):
|
||||
if self._debug:
|
||||
self._print(text, *args, **kwargs)
|
||||
@@ -1,41 +0,0 @@
|
||||
EMBEDDED_CONFIGURATION = {
|
||||
"ffmpeg": {
|
||||
"file_extension": "mp4",
|
||||
"path": "ffmpeg",
|
||||
"arguments": ["-r", "%FPS%", "-f", "concat", "-safe", "0", "-vsync",
|
||||
"cfr", "-i", "%FRAMES%", "-c:v", "libx264", "-pix_fmt",
|
||||
"yuv420p", "%OUTPUT%"]
|
||||
},
|
||||
"mpeg_coder": {
|
||||
"encoding_threads": 4,
|
||||
"bitrate_factor": 1.0,
|
||||
"max_b_frame": 2,
|
||||
"file_extension": "mp4",
|
||||
"codec_name": "libx264"
|
||||
},
|
||||
"encoding": {
|
||||
"jpeg_quality": 95
|
||||
},
|
||||
"debug": False,
|
||||
"ui": {
|
||||
"top_category": "Dream",
|
||||
"prepend_icon_to_category": True,
|
||||
"append_icon_to_category": False,
|
||||
"prepend_icon_to_node": True,
|
||||
"append_icon_to_node": False,
|
||||
"category_icons": {
|
||||
"animation": "🎥",
|
||||
"postprocessing": "⚙",
|
||||
"transforms": "🔀",
|
||||
"curves": "📈",
|
||||
"color": "🎨",
|
||||
"generate": "⚡",
|
||||
"utils": "🛠",
|
||||
"image": "🌄",
|
||||
"switches": "⭆",
|
||||
"conditioning": "☯",
|
||||
"Dream": "✨"
|
||||
}
|
||||
},
|
||||
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
def _get_node_name(cls):
|
||||
return cls.__dict__.get("NODE_NAME", str(cls))
|
||||
|
||||
|
||||
def on_error(node_cls: type, message: str):
|
||||
msg = "Failure in [" + _get_node_name(node_cls) + "]:" + message
|
||||
print(msg)
|
||||
raise Exception(msg)
|
||||
File diff suppressed because it is too large
Load Diff
+2977
-3547
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Binary file not shown.
|
Before Width: | Height: | Size: 2.8 KiB |
@@ -1,4 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import math
|
||||
|
||||
import numpy
|
||||
|
||||
-100
@@ -1,100 +0,0 @@
|
||||
from .categories import *
|
||||
from .shared import *
|
||||
|
||||
class DreamInputText:
|
||||
NODE_NAME = "Text Input"
|
||||
ICON = "✍"
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"value": ("STRING", {"default": "", "multiline": True}),
|
||||
},
|
||||
}
|
||||
|
||||
CATEGORY = NodeCategories.UTILS
|
||||
RETURN_TYPES = ("STRING",)
|
||||
RETURN_NAMES = ("STRING",)
|
||||
FUNCTION = "noop"
|
||||
|
||||
@classmethod
|
||||
def IS_CHANGED(cls, *values):
|
||||
return hashed_as_strings(*values)
|
||||
|
||||
def noop(self, value):
|
||||
return (value,)
|
||||
|
||||
class DreamInputString:
|
||||
NODE_NAME = "String Input"
|
||||
ICON = "✍"
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"value": ("STRING", {"default": "", "multiline": False}),
|
||||
},
|
||||
}
|
||||
|
||||
CATEGORY = NodeCategories.UTILS
|
||||
RETURN_TYPES = ("STRING",)
|
||||
RETURN_NAMES = ("STRING",)
|
||||
FUNCTION = "noop"
|
||||
|
||||
@classmethod
|
||||
def IS_CHANGED(cls, *values):
|
||||
return hashed_as_strings(*values)
|
||||
|
||||
def noop(self, value):
|
||||
return (value,)
|
||||
|
||||
|
||||
class DreamInputFloat:
|
||||
NODE_NAME = "Float Input"
|
||||
ICON = "✍"
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"value": ("FLOAT", {"default": 0.0}),
|
||||
},
|
||||
}
|
||||
|
||||
CATEGORY = NodeCategories.UTILS
|
||||
RETURN_TYPES = ("FLOAT",)
|
||||
RETURN_NAMES = ("FLOAT",)
|
||||
FUNCTION = "noop"
|
||||
|
||||
@classmethod
|
||||
def IS_CHANGED(cls, *values):
|
||||
return hashed_as_strings(*values)
|
||||
|
||||
def noop(self, value):
|
||||
return (value,)
|
||||
|
||||
|
||||
class DreamInputInt:
|
||||
NODE_NAME = "Int Input"
|
||||
ICON = "✍"
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"value": ("INT", {"default": 0}),
|
||||
},
|
||||
}
|
||||
|
||||
CATEGORY = NodeCategories.UTILS
|
||||
RETURN_TYPES = ("INT",)
|
||||
RETURN_NAMES = ("INT",)
|
||||
FUNCTION = "noop"
|
||||
|
||||
@classmethod
|
||||
def IS_CHANGED(cls, *values):
|
||||
return hashed_as_strings(*values)
|
||||
|
||||
def noop(self, value):
|
||||
return (value,)
|
||||
@@ -1,4 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from .shared import DreamConfig
|
||||
|
||||
|
||||
|
||||
-100
@@ -1,100 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import json
|
||||
|
||||
from .categories import *
|
||||
from .shared import ALWAYS_CHANGED_FLAG, DreamStateFile
|
||||
from .types import *
|
||||
|
||||
_laboratory_state = DreamStateFile("laboratory")
|
||||
|
||||
|
||||
class DreamLaboratory:
|
||||
NODE_NAME = "Laboratory"
|
||||
ICON = "🧪"
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": SharedTypes.frame_counter | {
|
||||
"key": ("STRING", {"default": "Random value " + str(random.randint(0, 1000000))}),
|
||||
"seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff}),
|
||||
"renew_policy": (["every frame", "first frame"],),
|
||||
"min_value": ("FLOAT", {"default": 0.0}),
|
||||
"max_value": ("FLOAT", {"default": 1.0}),
|
||||
"mode": (["random uniform", "random bell", "ladder", "random walk"],),
|
||||
},
|
||||
"optional": {
|
||||
"step_size": ("FLOAT", {"default": 0.1}),
|
||||
},
|
||||
}
|
||||
|
||||
CATEGORY = NodeCategories.UTILS
|
||||
RETURN_TYPES = ("FLOAT", "INT", LogEntry.ID)
|
||||
RETURN_NAMES = ("FLOAT", "INT", "log_entry")
|
||||
FUNCTION = "result"
|
||||
|
||||
@classmethod
|
||||
def IS_CHANGED(cls, *values):
|
||||
return ALWAYS_CHANGED_FLAG
|
||||
|
||||
def _generate(self, seed, last_value, min_value, max_value, mode, step_size):
|
||||
rnd = random.Random()
|
||||
rnd.seed(seed)
|
||||
|
||||
def jsonify(v: float):
|
||||
return json.loads(json.dumps(v))
|
||||
|
||||
if mode == "random uniform":
|
||||
return jsonify(self._mode_uniform(rnd, last_value, min_value, max_value, step_size))
|
||||
elif mode == "random bell":
|
||||
return jsonify(self._mode_bell(rnd, last_value, min_value, max_value, step_size))
|
||||
elif mode == "ladder":
|
||||
return jsonify(self._mode_ladder(rnd, last_value, min_value, max_value, step_size))
|
||||
else:
|
||||
return jsonify(self._mode_walk(rnd, last_value, min_value, max_value, step_size))
|
||||
|
||||
def _mode_uniform(self, rnd: random.Random, last_value: float, min_value: float, max_value: float, step_size):
|
||||
return rnd.random() * (max_value - min_value) + min_value
|
||||
|
||||
def _mode_bell(self, rnd: random.Random, last_value: float, min_value: float, max_value: float, step_size):
|
||||
s = 0.0
|
||||
for i in range(3):
|
||||
s += rnd.random() * (max_value - min_value) + min_value
|
||||
return s / 3.0
|
||||
|
||||
def _mode_ladder(self, rnd: random.Random, last_value: float, min_value: float, max_value: float, step_size):
|
||||
if last_value is None:
|
||||
last_value = min_value - step_size
|
||||
next_value = last_value + step_size
|
||||
if next_value > max_value:
|
||||
d = abs(max_value - min_value)
|
||||
next_value = (next_value - min_value) % d + min_value
|
||||
return next_value
|
||||
|
||||
def _mode_walk(self, rnd: random.Random, last_value: float, min_value: float, max_value: float, step_size):
|
||||
if last_value is None:
|
||||
last_value = (max_value - min_value) * 0.5
|
||||
if rnd.random() >= 0.5:
|
||||
return min(max_value, last_value + step_size)
|
||||
else:
|
||||
return max(min_value, last_value - step_size)
|
||||
|
||||
def result(self, key, frame_counter: FrameCounter, seed, renew_policy, min_value, max_value, mode, **values):
|
||||
if min_value > max_value:
|
||||
t = max_value
|
||||
max_value = min_value
|
||||
min_value = t
|
||||
step_size = values.get("step_size", abs(max_value - min_value) * 0.1)
|
||||
last_value = _laboratory_state.get_section("values").get(key, None)
|
||||
|
||||
if (last_value is None) or (renew_policy == "every frame") or frame_counter.is_first_frame:
|
||||
v = _laboratory_state.get_section("values") \
|
||||
.update(key, 0, lambda old: self._generate(seed, last_value, min_value, max_value, mode, step_size))
|
||||
return v, round(v), LogEntry.new(
|
||||
"Laboratory generated new value for '{}': {} ({})".format(key, v, round(v)))
|
||||
else:
|
||||
return last_value, round(last_value), LogEntry.new("Laboratory reused value for '{}': {} ({})"
|
||||
.format(key, last_value, round(last_value)))
|
||||
-21
@@ -1,21 +0,0 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2023 Morgan Johansson/Dream Project
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
+1
-2
@@ -1,4 +1,4 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from .types import SharedTypes, FrameCounter
|
||||
from .categories import NodeCategories
|
||||
from .shared import ALWAYS_CHANGED_FLAG, list_images_in_directory, DreamImage
|
||||
from .types import SharedTypes, FrameCounter
|
||||
@@ -6,7 +6,6 @@ from .types import SharedTypes, FrameCounter
|
||||
|
||||
class DreamImageSequenceInputWithDefaultFallback:
|
||||
NODE_NAME = "Image Sequence Loader"
|
||||
ICON = "💾"
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
|
||||
+3
-36
@@ -1,58 +1,25 @@
|
||||
{
|
||||
"Analyze Palette [Dream]": "Output brightness, contrast, red, green and blue averages of a palette",
|
||||
"Analyze Palette [Dream]": "Output brightness, red, green and blue averages of a palette",
|
||||
"Beat Curve [Dream]": "Beat pattern curve with impulses at specified beats of a measure",
|
||||
"Big Float Switch [Dream]": "Switch for up to 10 inputs",
|
||||
"Big Image Switch [Dream]": "Switch for up to 10 inputs",
|
||||
"Big Int Switch [Dream]": "Switch for up to 10 inputs",
|
||||
"Big Latent Switch [Dream]": "Switch for up to 10 inputs",
|
||||
"Big Palette Switch [Dream]": "Switch for up to 10 inputs",
|
||||
"Big Text Switch [Dream]": "Switch for up to 10 inputs",
|
||||
"Boolean To Float [Dream]": "Converts a boolean value to two different float values",
|
||||
"Boolean To Int [Dream]": "Converts a boolean value to two different int values",
|
||||
"Build Prompt [Dream]": "Weighted text prompt builder utility",
|
||||
"CSV Curve [Dream]": "CSV input curve where first column is frame or second and second column is value",
|
||||
"CSV Generator [Dream]": "CSV output, mainly for debugging purposes",
|
||||
"Calculation [Dream]": "Mathematical calculation node",
|
||||
"Common Frame Dimensions [Dream]": "Utility for calculating good width/height based on common video dimensions",
|
||||
"Compare Palettes [Dream]": "Analyses two palettes producing the factor for each color channel",
|
||||
"FFMPEG Video Encoder [Dream]": "Post processing for animation sequences calling FFMPEG to generate video file",
|
||||
"File Count [Dream]": "Finds the number of files in a directory matching specified patterns",
|
||||
"Finalize Prompt [Dream]": "Used in conjunction with 'Build Prompt'",
|
||||
"Float Input [Dream]": "Float input (until primitive routing issues are solved)",
|
||||
"Float to Log Entry [Dream]": "Logging for float values",
|
||||
"Frame Count Calculator [Dream]": "Simple utility to calculate number of frames based on duration and framerate",
|
||||
"Frame Counter (Directory) [Dream]": "Directory backed frame counter, for output directories",
|
||||
"Frame Counter (Simple) [Dream]": "Integer value used as frame counter",
|
||||
"Frame Counter Info [Dream]": "Extracts information from the frame counter",
|
||||
"Frame Counter Offset [Dream]": "Adds an offset to a frame counter",
|
||||
"Image Brightness Adjustment [Dream]": "Adjusts the brightness of an image by a factor",
|
||||
"Image Color Shift [Dream]": "Adjust the colors (or brightness) of an image",
|
||||
"Image Contrast Adjustment [Dream]": "Adjusts the contrast of an image by a factor",
|
||||
"Image Motion [Dream]": "Node supporting zooming in/out and translating an image",
|
||||
"Image Sequence Blend [Dream]": "Post processing for animation sequences blending frame for a smoother blurred effect",
|
||||
"Image Sequence Loader [Dream]": "Loads a frame from a directory of images",
|
||||
"Image Sequence Saver [Dream]": "Saves a frame to a directory",
|
||||
"Image Sequence Tweening [Dream]": "Post processing for animation sequences generating blended in-between frames",
|
||||
"Int Input [Dream]": "Integer input (until primitive routing issues are solved)",
|
||||
"Int to Log Entry [Dream]": "Logging for int values",
|
||||
"Laboratory [Dream]": "Super-charged number generator for experimenting with ComfyUI",
|
||||
"Linear Curve [Dream]": "Linear interpolation between two value over the full animation",
|
||||
"Log Entry Joiner [Dream]": "Merges multiple log entries (reduces noodling)",
|
||||
"Log File [Dream]": "Logging node for output to file",
|
||||
"Noise from Area Palettes [Dream]": "Generates noise based on the colors of up to nine different palettes",
|
||||
"Noise from Area Palettes [Dream]": "",
|
||||
"Noise from Palette [Dream]": "Generates noise based on the colors in a palette",
|
||||
"Palette Color Align [Dream]": "Shifts the colors of one palette towards another target palette",
|
||||
"Palette Color Shift [Dream]": "Multiplies the color values in a palette",
|
||||
"Sample Image Area as Palette [Dream]": "Samples a palette from an image based on pre-defined areas",
|
||||
"Sample Image as Palette [Dream]": "Randomly samples pixel values to build a palette from an image",
|
||||
"Saw Curve [Dream]": "Saw wave curve",
|
||||
"Sine Curve [Dream]": "Simple sine wave curve",
|
||||
"Smooth Event Curve [Dream]": "Single event/peak curve with a slight bell-shape",
|
||||
"String Input [Dream]": "String input (until primitive routing issues are solved)",
|
||||
"String Tokenizer [Dream]": "Extract individual words or phrases from a text as tokens",
|
||||
"String to Log Entry [Dream]": "Use any string as a log entry",
|
||||
"Text Input [Dream]": "Multiline string input (until primitive routing issues are solved)",
|
||||
"Triangle Curve [Dream]": "Triangle wave curve",
|
||||
"Triangle Event Curve [Dream]": "Single event/peak curve with triangular shape",
|
||||
"Video Encoder (mpegCoder) [Dream]": "Post processing for animation sequences using mpegCoder module to generate video file"
|
||||
"Sine Curve [Dream]": "Simple sine wave curve"
|
||||
}
|
||||
@@ -1,4 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import math
|
||||
|
||||
from .categories import NodeCategories
|
||||
@@ -25,7 +24,6 @@ def _generate_noise(image: DreamImage, color_function, rng: random.Random, block
|
||||
|
||||
class DreamNoiseFromPalette:
|
||||
NODE_NAME = "Noise from Palette"
|
||||
ICON = "🌫"
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
@@ -69,15 +67,15 @@ class DreamNoiseFromAreaPalettes:
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"optional": {
|
||||
"top_left_palette": (RGBPalette.ID,),
|
||||
"top_center_palette": (RGBPalette.ID,),
|
||||
"top_right_palette": (RGBPalette.ID,),
|
||||
"center_left_palette": (RGBPalette.ID,),
|
||||
"center_palette": (RGBPalette.ID,),
|
||||
"center_right_palette": (RGBPalette.ID,),
|
||||
"bottom_left_palette": (RGBPalette.ID,),
|
||||
"bottom_center_palette": (RGBPalette.ID,),
|
||||
"bottom_right_palette": (RGBPalette.ID,),
|
||||
"top_left_palette": (RGBPalette.ID, {"forceInput": True}),
|
||||
"top_center_palette": (RGBPalette.ID, {"forceInput": True}),
|
||||
"top_right_palette": (RGBPalette.ID, {"forceInput": True}),
|
||||
"center_left_palette": (RGBPalette.ID, {"forceInput": True}),
|
||||
"center_palette": (RGBPalette.ID, {"forceInput": True}),
|
||||
"center_right_palette": (RGBPalette.ID, {"forceInput": True}),
|
||||
"bottom_left_palette": (RGBPalette.ID, {"forceInput": True}),
|
||||
"bottom_center_palette": (RGBPalette.ID, {"forceInput": True}),
|
||||
"bottom_right_palette": (RGBPalette.ID, {"forceInput": True}),
|
||||
},
|
||||
"required": {
|
||||
"area_sharpness": ("FLOAT", {"default": 0.5, "min": 0.0, "max": 1.0, "step": 0.05}),
|
||||
@@ -85,12 +83,11 @@ class DreamNoiseFromAreaPalettes:
|
||||
"height": ("INT", {"default": 512, "min": 1, "max": 8192}),
|
||||
"blur_amount": ("FLOAT", {"default": 0.3, "min": 0, "max": 1.0, "step": 0.05}),
|
||||
"density": ("FLOAT", {"default": 0.5, "min": 0.1, "max": 1.0, "step": 0.025}),
|
||||
"seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff}),
|
||||
"seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff})
|
||||
},
|
||||
}
|
||||
|
||||
CATEGORY = NodeCategories.IMAGE_GENERATE
|
||||
ICON = "🌫"
|
||||
RETURN_TYPES = ("IMAGE",)
|
||||
RETURN_NAMES = ("image",)
|
||||
FUNCTION = "result"
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import json
|
||||
import os
|
||||
|
||||
@@ -8,7 +7,7 @@ from PIL.PngImagePlugin import PngInfo
|
||||
from .categories import NodeCategories
|
||||
from .shared import hashed_as_strings, DreamImageProcessor, DreamImage, \
|
||||
list_images_in_directory, DreamConfig
|
||||
from .types import SharedTypes, FrameCounter, AnimationSequence, LogEntry
|
||||
from .types import SharedTypes, FrameCounter, AnimationSequence
|
||||
|
||||
CONFIG = DreamConfig()
|
||||
|
||||
@@ -32,7 +31,6 @@ def _save_jpg(pil_image, filepath, quality):
|
||||
|
||||
class DreamImageSequenceOutput:
|
||||
NODE_NAME = "Image Sequence Saver"
|
||||
ICON = "💾"
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
@@ -42,7 +40,7 @@ class DreamImageSequenceOutput:
|
||||
"directory_path": ("STRING", {"default": comfy_paths.output_directory, "multiline": False}),
|
||||
"prefix": ("STRING", {"default": 'frame', "multiline": False}),
|
||||
"digits": ("INT", {"default": 5}),
|
||||
"at_end": (["stop output", "raise error", "keep going"],),
|
||||
"at_end": (["stop output", "keep going"],),
|
||||
"filetype": (['png with embedded workflow', "png", 'jpg'],),
|
||||
},
|
||||
"hidden": {
|
||||
@@ -52,9 +50,9 @@ class DreamImageSequenceOutput:
|
||||
}
|
||||
|
||||
CATEGORY = NodeCategories.IMAGE_ANIMATION
|
||||
RETURN_TYPES = (AnimationSequence.ID, LogEntry.ID)
|
||||
RETURN_TYPES = (AnimationSequence.ID,)
|
||||
OUTPUT_NODE = True
|
||||
RETURN_NAMES = ("sequence", "log_entry")
|
||||
RETURN_NAMES = ("sequence",)
|
||||
FUNCTION = "save"
|
||||
|
||||
@classmethod
|
||||
@@ -64,16 +62,11 @@ class DreamImageSequenceOutput:
|
||||
def _get_new_filename(self, current_frame, prefix, digits, filetype):
|
||||
return prefix + "_" + str(current_frame).zfill(digits) + "." + filetype.split(" ")[0]
|
||||
|
||||
def _save_single_image(self, dream_image: DreamImage, batch_counter, frame_counter: FrameCounter,
|
||||
directory_path,
|
||||
prefix, digits, filetype, prompt, extra_pnginfo, at_end, logger):
|
||||
|
||||
def _save_single_image(self, dream_image: DreamImage, batch_counter, frame_counter: FrameCounter, directory_path,
|
||||
prefix, digits, filetype, prompt, extra_pnginfo, at_end):
|
||||
if at_end == "stop output" and frame_counter.is_after_last_frame:
|
||||
logger("Reached end of animation - not saving output!")
|
||||
print("Reached end of animation - not saving output!")
|
||||
return ()
|
||||
if at_end == "raise error" and frame_counter.is_after_last_frame:
|
||||
logger("Reached end of animation - raising error to stop processing!")
|
||||
raise Exception("Reached end of animation!")
|
||||
filename = self._get_new_filename(frame_counter.current_frame, prefix, digits, filetype)
|
||||
if batch_counter >= 0:
|
||||
filepath = os.path.join(directory_path, "batch_" + (str(batch_counter).zfill(4)), filename)
|
||||
@@ -86,7 +79,7 @@ class DreamImageSequenceOutput:
|
||||
dream_image.save_png(filepath, filetype == 'png with embedded workflow', prompt, extra_pnginfo)
|
||||
elif filetype == "jpg":
|
||||
dream_image.save_jpg(filepath, int(CONFIG.get("encoding.jpeg_quality", 95)))
|
||||
logger("Saved {} in {}".format(filename, os.path.abspath(save_dir)))
|
||||
print("Saved {} in {}".format(filename, os.path.abspath(save_dir)))
|
||||
return ()
|
||||
|
||||
def _generate_animation_sequence(self, filetype, directory_path, frame_counter):
|
||||
@@ -98,19 +91,13 @@ class DreamImageSequenceOutput:
|
||||
return AnimationSequence(frame_counter, frames)
|
||||
|
||||
def save(self, image, **args):
|
||||
log_texts = list()
|
||||
logger = lambda s: log_texts.append(s)
|
||||
if not args.get("directory_path", ""):
|
||||
args["directory_path"] = comfy_paths.output_directory
|
||||
args["logger"] = logger
|
||||
proc = DreamImageProcessor(image, **args)
|
||||
proc.process(self._save_single_image)
|
||||
frame_counter: FrameCounter = args["frame_counter"]
|
||||
log_entry = LogEntry([])
|
||||
for text in log_texts:
|
||||
log_entry = log_entry.add(text)
|
||||
if frame_counter.is_final_frame:
|
||||
return (self._generate_animation_sequence(args["filetype"], args["directory_path"],
|
||||
frame_counter), log_entry)
|
||||
frame_counter),)
|
||||
else:
|
||||
return (AnimationSequence(frame_counter), log_entry)
|
||||
return (AnimationSequence(frame_counter),)
|
||||
|
||||
@@ -1,69 +0,0 @@
|
||||
from .categories import NodeCategories
|
||||
from .shared import hashed_as_strings
|
||||
from .types import PartialPrompt
|
||||
|
||||
|
||||
class DreamWeightedPromptBuilder:
|
||||
NODE_NAME = "Build Prompt"
|
||||
ICON = "⚖"
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"optional": {
|
||||
"partial_prompt": (PartialPrompt.ID,)
|
||||
},
|
||||
"required": {
|
||||
"added_prompt": ("STRING", {"default": "", "multiline": True}),
|
||||
"weight": ("FLOAT", {"default": 1.0}),
|
||||
},
|
||||
}
|
||||
|
||||
CATEGORY = NodeCategories.CONDITIONING
|
||||
RETURN_TYPES = (PartialPrompt.ID,)
|
||||
RETURN_NAMES = ("partial_prompt",)
|
||||
FUNCTION = "result"
|
||||
|
||||
@classmethod
|
||||
def IS_CHANGED(cls, *values):
|
||||
return hashed_as_strings(*values)
|
||||
|
||||
def result(self, added_prompt, weight, **args):
|
||||
input = args.get("partial_prompt", PartialPrompt())
|
||||
p = input.add(added_prompt, weight)
|
||||
return (p,)
|
||||
|
||||
|
||||
class DreamPromptFinalizer:
|
||||
NODE_NAME = "Finalize Prompt"
|
||||
ICON = "🗫"
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"partial_prompt": (PartialPrompt.ID,),
|
||||
"adjustment": (["raw", "by_abs_max", "by_abs_sum"],),
|
||||
"clamp": ("FLOAT", {"default": 2.0, "min": 0.1, "step": 0.1}),
|
||||
"adjustment_reference": ("FLOAT", {"default": 1.0, "min": 0.1}),
|
||||
},
|
||||
}
|
||||
|
||||
CATEGORY = NodeCategories.CONDITIONING
|
||||
RETURN_TYPES = ("STRING", "STRING")
|
||||
RETURN_NAMES = ("positive", "negative")
|
||||
FUNCTION = "result"
|
||||
|
||||
@classmethod
|
||||
def IS_CHANGED(cls, *values):
|
||||
return hashed_as_strings(*values)
|
||||
|
||||
def result(self, partial_prompt: PartialPrompt, adjustment, adjustment_reference, clamp):
|
||||
if adjustment == "raw" or partial_prompt.is_empty():
|
||||
return partial_prompt.finalize(clamp)
|
||||
elif adjustment == "by_abs_sum":
|
||||
f = adjustment_reference / partial_prompt.abs_sum()
|
||||
return partial_prompt.scaled_by(f).finalize(clamp)
|
||||
else:
|
||||
f = adjustment_reference / partial_prompt.abs_max()
|
||||
return partial_prompt.scaled_by(f).finalize(clamp)
|
||||
@@ -9,12 +9,6 @@ I have demonstrated the use of these custom nodes in this [youtube video](https:
|
||||
|
||||
## Installation
|
||||
|
||||
### Simple option
|
||||
|
||||
You can install Dream Project Animation Nodes using the ComfyUI Manager.
|
||||
|
||||
### Manual option
|
||||
|
||||
Run within (ComfyUI)/custom_nodes/ folder:
|
||||
|
||||
* git clone https://github.com/alt-key-project/comfyui-dream-project.git
|
||||
@@ -32,76 +26,6 @@ Finally:
|
||||
After startup, a configuration file 'config.json' should have been created in the 'comfyui-dream-project' directory.
|
||||
Specifically check that the path of ffmpeg works in your system (add full path to the command if needed).
|
||||
|
||||
## Upgrade
|
||||
|
||||
When upgrading, it is good to re-run the pip install command as specified in the install section. This will install any
|
||||
new dependencies.
|
||||
|
||||
## Configuration
|
||||
|
||||
### debug
|
||||
|
||||
Setting this to true will enable some trace-level logging.
|
||||
|
||||
### ffmpeg.file_extension
|
||||
|
||||
Sets the output file extension and with that the envelope used.
|
||||
|
||||
### ffmpeg.path
|
||||
|
||||
Path to the ffmpeg executable or just the command if ffmpeg is in PATH.
|
||||
|
||||
### ffmpeg.arguments
|
||||
|
||||
The arguments sent to FFMPEG. A few of the values are provided by the node:
|
||||
|
||||
* %FPS% the target framerate
|
||||
* %FRAMES% a frame ionput file
|
||||
* %OUTPUT% output video file path
|
||||
|
||||
### encoding.jpeg__quality
|
||||
|
||||
Sets the encoding quality of jpeg images.
|
||||
|
||||
### ui.top_category
|
||||
|
||||
Sets the name of the top level category on the menu. Set to empty string "" to remove the top level. If the top level
|
||||
is removed you may also want to disable the category icons to get nodes into existing category folders.
|
||||
|
||||
### prepend_icon_to_category / append_icon_to_category
|
||||
|
||||
Flags to add a icon before and/or after the category name at each level.
|
||||
|
||||
### prepend_icon_icon_to_node / append_icon_icon_to_node
|
||||
|
||||
Flags to add an icon before and/or after the node name.
|
||||
|
||||
### ui.category_icons
|
||||
|
||||
Each key defines a unicode symbol as an icon used for the specified category.
|
||||
|
||||
### mpeg_coder.bitrate_factor
|
||||
|
||||
This factor allows changing the bitrate to better fit the required quality and codec. A value of 1 is typically
|
||||
suitable for H.265.
|
||||
|
||||
### mpeg_coder.codec_name
|
||||
|
||||
Codec names as specified by ffmpeg. Some common options include "libx264", "libx264" and "mpeg2video".
|
||||
|
||||
### mpeg_coder.encoding_threads
|
||||
|
||||
Increasing the number of encoding threads in mpegCoder will generally reduce the overall encoding time, but it will also
|
||||
increase the load on the computer.
|
||||
|
||||
### mpeg_coder.file_extension
|
||||
|
||||
Sets the output file extension and with that the envelope used.
|
||||
|
||||
### mpeg_coder.max_b_frame
|
||||
|
||||
Sets the max-b-frames parameter for as specified in ffmpeg.
|
||||
|
||||
## Concepts used
|
||||
|
||||
These are some concepts used in nodes:
|
||||
@@ -133,23 +57,6 @@ Output brightness, red, green and blue averages of a palette. Useful to control
|
||||
### Beat Curve [Dream]
|
||||
Beat pattern curve with impulses at specified beats of a measure.
|
||||
|
||||
### Big *** Switch [Dream]
|
||||
Switch nodes for different type for up to ten inputs.
|
||||
|
||||
### Boolean To Float/Int [Dream]
|
||||
Converts a boolean value to two different numeric values.
|
||||
|
||||
### Build Prompt [Dream] (and Finalize Prompt [Dream])
|
||||
Weighted text prompt builder utility. Chain any number of these nodes and terminate with 'Finalize Prompt'.
|
||||
|
||||
### Calculation [Dream]
|
||||
Mathematical calculation node. Exposes most of the mathematical functions in the python
|
||||
[math module](https://docs.python.org/3/library/math.html), mathematical operators as well as round, abs, int,
|
||||
float, max and min.
|
||||
|
||||
### Compare Palettes [Dream]
|
||||
Analyses two palettes and produces the quotient for each individual channel (b/a) and brightness.
|
||||
|
||||
### CSV Curve [Dream]
|
||||
CSV input curve where first column is frame or second and second column is value.
|
||||
|
||||
@@ -160,21 +67,12 @@ Recreates file at frame 0 (removing and existing content in the file).
|
||||
### Common Frame Dimensions [Dream]
|
||||
Utility for calculating good width/height based on common video dimensions.
|
||||
|
||||
### Video Encoder (FFMPEG) [Dream]
|
||||
Post processing for animation sequences calling FFMPEG to generate video files.
|
||||
|
||||
### Video Encoder (mpegCoder) [Dream]
|
||||
Post processing for animation sequences using the python module mpegCoder with ffmpeg library to generate video files.
|
||||
### FFMPEG Video Encoder [Dream]
|
||||
Post processing for animation sequences calling FFMPEG to generate video file.
|
||||
|
||||
### File Count [Dream]
|
||||
Finds the number of files in a directory matching specified patterns.
|
||||
|
||||
### Float/Int/string to Log Entry [Dream]
|
||||
Logging for float/int/string values.
|
||||
|
||||
### Frame Count Calculator [Dream]
|
||||
Simple utility to calculate number of frames based on time and framerate.
|
||||
|
||||
### Frame Counter (Directory) [Dream]
|
||||
Directory backed frame counter, for output directories.
|
||||
|
||||
@@ -182,21 +80,9 @@ Directory backed frame counter, for output directories.
|
||||
Integer value used as frame counter. Useful for testing or if an auto-incrementing primitive is used as a frame
|
||||
counter.
|
||||
|
||||
### Frame Counter Info [Dream]
|
||||
Extracts information from the frame counter.
|
||||
|
||||
### Frame Counter Offset [Dream]
|
||||
Adds an offset to a frame counter.
|
||||
|
||||
### Image Brightness Adjustment [Dream]
|
||||
Adjusts the brightness of an image by a factor.
|
||||
|
||||
### Image Color Shift [Dream]
|
||||
Allows changing the colors of an image with a multiplier for each channel (RGB).
|
||||
|
||||
### Image Contrast Adjustment [Dream]
|
||||
Adjusts the contrast of an image by a factor.
|
||||
|
||||
### Image Motion [Dream]
|
||||
Node supporting zooming in/out and translating an image.
|
||||
|
||||
@@ -212,15 +98,6 @@ Saves a frame to a directory.
|
||||
### Image Sequence Tweening [Dream]
|
||||
Post processing for animation sequences generating blended in-between frames.
|
||||
|
||||
### Laboratory [Dream]
|
||||
Super-charged number generator for experimenting with ComfyUI.
|
||||
|
||||
### Log Entry Joiner [Dream]
|
||||
Merges multiple log entries (reduces noodling).
|
||||
|
||||
### Log File [Dream]
|
||||
The text logging facility for the Dream Project Animation nodes.
|
||||
|
||||
### Linear Curve [Dream]
|
||||
Linear interpolation between two values over the full animation.
|
||||
|
||||
@@ -246,24 +123,9 @@ of equal size and each node may sample one of these.
|
||||
### Sample Image as Palette [Dream]
|
||||
Randomly samples pixels from a source image to build a palette from it.
|
||||
|
||||
### Saw Curve [Dream]
|
||||
Saw wave curve.
|
||||
|
||||
### Sine Curve [Dream]
|
||||
Simple sine wave curve.
|
||||
|
||||
### Smooth Event Curve [Dream]
|
||||
Single event/peak curve with a slight bell-shape.
|
||||
|
||||
### String Tokenizer [Dream]
|
||||
Splits a text into tokens by a separator and returns one of the tokens based on a given index.
|
||||
|
||||
### Triangle Curve [Dream]
|
||||
Triangle wave curve.
|
||||
|
||||
### Triangle Event Curve [Dream]
|
||||
Single event/peak curve with triangular shape.
|
||||
|
||||
### Other custom nodes
|
||||
|
||||
Many of the nodes found in 'WAS Node Suite' are useful the Dream Project Animation nodes - I suggest you install those
|
||||
@@ -277,30 +139,12 @@ This example should be a starting point for anyone wanting to build with the Dre
|
||||
|
||||
[motion-workflow-example](examples/motion-workflow-example.json)
|
||||
|
||||
### Image Motion with Color Coherence
|
||||
|
||||
Same as above but with added color coherence through palettes.
|
||||
|
||||
[motion-workflow-with-color-coherence](examples/motion-workflow-with-color-coherence.json)
|
||||
|
||||
### Area Sampled Noise
|
||||
|
||||
This flow demonstrates sampling image areas into palettes and generating noise for these areas.
|
||||
|
||||
[area-sampled-noise](examples/area-sampled-noise.json)
|
||||
|
||||
### Prompt Morphing
|
||||
|
||||
This flow demonstrates prompt building with weights based on curves and brightness and contrast control.
|
||||
|
||||
[prompt-morphing](examples/prompt-morphing.json)
|
||||
|
||||
## Known issues
|
||||
|
||||
### FFMPEG
|
||||
|
||||
The call to FFMPEG currently in the default configuration (in config.json) does not seem to work for everyone. The good
|
||||
news is that you can change the arguments to whatever works for you - the node-supplied parameters (that probably all need to be in the call)
|
||||
news is that you can change the arguments to whatever works for you - the fixed parameters (that need to be in the call)
|
||||
are:
|
||||
|
||||
* -i %FRAMES% (the input file listing frames)
|
||||
@@ -308,9 +152,4 @@ are:
|
||||
* %OUTPUT% (the path to the video file)
|
||||
|
||||
If possible, I will change the default configuration to one that more versions/builds of ffmpeg will accept. Do let me
|
||||
know what arguments are causing issues for you!
|
||||
|
||||
### Framerate is not always right with mpegCoder encoding node
|
||||
|
||||
The mpegCoder library will always use variable frame rate encoding if it is available in the output format. With most
|
||||
outputs this means that your actual framerate will differ slightly from the requested one.
|
||||
know what arguments are causing issues for you!
|
||||
+1
-3
@@ -2,6 +2,4 @@ imageio
|
||||
pilgram
|
||||
scipy
|
||||
numpy<1.24>=1.18
|
||||
torchvision
|
||||
mpegCoder
|
||||
evalidate
|
||||
torchvision
|
||||
+17
-90
@@ -1,23 +1,22 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import os
|
||||
import random
|
||||
import shutil
|
||||
import subprocess
|
||||
import tempfile
|
||||
from functools import lru_cache
|
||||
|
||||
from PIL import Image as PilImage
|
||||
from PIL import Image
|
||||
|
||||
from .categories import NodeCategories
|
||||
from .err import on_error
|
||||
from .shared import DreamConfig, MpegEncoderUtility
|
||||
from .shared import DreamConfig, DreamImage
|
||||
from .types import *
|
||||
|
||||
CONFIG = DreamConfig()
|
||||
|
||||
|
||||
@lru_cache(5)
|
||||
def _load_image_cached(filename):
|
||||
return PilImage.open(filename)
|
||||
def _load_image_cached(filename) -> Image:
|
||||
return Image.open(filename)
|
||||
|
||||
|
||||
class TempFileSet:
|
||||
@@ -122,95 +121,27 @@ def _ffmpeg(config, filenames, fps, output):
|
||||
for (key, value) in replacements.items():
|
||||
cmd = list(map(lambda s: s.replace(key, value), cmd))
|
||||
|
||||
subprocess.check_output(cmd, shell=True)
|
||||
subprocess.run(cmd, shell=True)
|
||||
finally:
|
||||
os.unlink(tempfilepath)
|
||||
|
||||
|
||||
def _make_video_filename(name, file_ext):
|
||||
(b, _) = os.path.splitext(name)
|
||||
return b + "." + file_ext.strip(".")
|
||||
|
||||
|
||||
class DreamVideoEncoderMpegCoder:
|
||||
NODE_NAME = "Video Encoder (mpegCoder)"
|
||||
ICON = "🎬"
|
||||
CATEGORY = NodeCategories.ANIMATION_POSTPROCESSING
|
||||
RETURN_TYPES = (LogEntry.ID,)
|
||||
RETURN_NAMES = ("log_entry",)
|
||||
OUTPUT_NODE = True
|
||||
FUNCTION = "encode"
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": SharedTypes.sequence | {
|
||||
"name": ("STRING", {"default": 'video', "multiline": False}),
|
||||
"framerate_factor": ("FLOAT", {"default": 1.0, "min": 0.01, "max": 100.0}),
|
||||
"remove_images": ("BOOLEAN", {"default": True})
|
||||
},
|
||||
}
|
||||
|
||||
def _find_free_filename(self, filename, defaultdir):
|
||||
if os.path.basename(filename) == filename:
|
||||
filename = os.path.join(defaultdir, filename)
|
||||
n = 1
|
||||
tested = filename
|
||||
while os.path.exists(tested):
|
||||
n += 1
|
||||
(b, ext) = os.path.splitext(filename)
|
||||
tested = b + "_" + str(n) + ext
|
||||
return tested
|
||||
|
||||
def encode(self, sequence, name, framerate_factor, remove_images):
|
||||
if not sequence.is_defined:
|
||||
return (LogEntry([]),)
|
||||
config = DreamConfig()
|
||||
filename = _make_video_filename(name, config.get("mpeg_coder.file_extension", "mp4"))
|
||||
log_entry = LogEntry([])
|
||||
for batch_num in sequence.batches:
|
||||
try:
|
||||
images = list(sequence.get_image_files_of_batch(batch_num))
|
||||
filename = self._find_free_filename(filename, os.path.dirname(images[0]))
|
||||
first_image = DreamImage.from_file(images[0])
|
||||
enc = MpegEncoderUtility(video_path=filename,
|
||||
bit_rate_factor=float(config.get("mpeg_coder.bitrate_factor", 1.0)),
|
||||
encoding_threads=int(config.get("mpeg_coder.encoding_threads", 4)),
|
||||
max_b_frame=int(config.get("mpeg_coder.max_b_frame", 2)),
|
||||
width=first_image.width,
|
||||
height=first_image.height,
|
||||
files=images,
|
||||
fps=sequence.fps * framerate_factor,
|
||||
codec_name=config.get("mpeg_coder.codec_name", "libx265"))
|
||||
enc.encode()
|
||||
log_entry = log_entry.add("Generated video '{}'".format(filename))
|
||||
if remove_images:
|
||||
for imagepath in images:
|
||||
if os.path.isfile(imagepath):
|
||||
os.unlink(imagepath)
|
||||
except Exception as e:
|
||||
on_error(self.__class__, str(e))
|
||||
return (log_entry,)
|
||||
|
||||
|
||||
class DreamVideoEncoder:
|
||||
NODE_NAME = "FFMPEG Video Encoder"
|
||||
DISPLAY_NAME = "Video Encoder (FFMPEG)"
|
||||
ICON = "🎬"
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": SharedTypes.sequence | {
|
||||
"name": ("STRING", {"default": 'video', "multiline": False}),
|
||||
"filename": ("STRING", {"default": 'video.mp4', "multiline": False}),
|
||||
"framerate_factor": ("FLOAT", {"default": 1.0, "min": 0.01, "max": 100.0}),
|
||||
"remove_images": ("BOOLEAN", {"default": True})
|
||||
"remove_images": (["yes", "no"],)
|
||||
},
|
||||
}
|
||||
|
||||
CATEGORY = NodeCategories.ANIMATION_POSTPROCESSING
|
||||
RETURN_TYPES = (LogEntry.ID,)
|
||||
RETURN_NAMES = ("log_entry",)
|
||||
RETURN_TYPES = ()
|
||||
RETURN_NAMES = ()
|
||||
OUTPUT_NODE = True
|
||||
FUNCTION = "encode"
|
||||
|
||||
@@ -232,28 +163,24 @@ class DreamVideoEncoder:
|
||||
def generate_video(self, files, fps, filename, config):
|
||||
filename = self._find_free_filename(filename, os.path.dirname(files[0]))
|
||||
_ffmpeg(config, files, fps, filename)
|
||||
return filename
|
||||
|
||||
def encode(self, sequence: AnimationSequence, name: str, remove_images, framerate_factor):
|
||||
def encode(self, sequence: AnimationSequence, filename: str, remove_images, framerate_factor):
|
||||
if not sequence.is_defined:
|
||||
return (LogEntry([]),)
|
||||
return ()
|
||||
|
||||
config = DreamConfig()
|
||||
filename = _make_video_filename(name, config.get("ffmpeg.file_extension", "mp4"))
|
||||
log_entry = LogEntry([])
|
||||
for batch_num in sequence.batches:
|
||||
try:
|
||||
images = list(sequence.get_image_files_of_batch(batch_num))
|
||||
actual_filename = self.generate_video(images, sequence.fps * framerate_factor, filename, config)
|
||||
|
||||
log_entry = log_entry.add("Generated video '{}'".format(actual_filename))
|
||||
if remove_images:
|
||||
self.generate_video(images, sequence.fps * framerate_factor, filename, config)
|
||||
if remove_images == "yes":
|
||||
for imagepath in images:
|
||||
if os.path.isfile(imagepath):
|
||||
os.unlink(imagepath)
|
||||
except Exception as e:
|
||||
on_error(self.__class__, str(e))
|
||||
return (log_entry,)
|
||||
print("Failed to encode files in dir {}!".format(os.path.dirname(images[0])))
|
||||
print(str(e))
|
||||
return ()
|
||||
|
||||
|
||||
class DreamSequenceTweening:
|
||||
|
||||
@@ -1,23 +1,17 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
import glob
|
||||
import hashlib
|
||||
import json
|
||||
import os
|
||||
import random
|
||||
import time
|
||||
|
||||
import folder_paths as comfy_paths
|
||||
import glob
|
||||
import numpy
|
||||
import random
|
||||
import torch
|
||||
from PIL import Image, ImageFilter, ImageEnhance
|
||||
from PIL import Image, ImageFilter
|
||||
from PIL.ImageDraw import ImageDraw
|
||||
from PIL.PngImagePlugin import PngInfo
|
||||
from typing import Dict, Tuple, List
|
||||
|
||||
from .dreamlogger import DreamLog
|
||||
from .embedded_config import EMBEDDED_CONFIGURATION
|
||||
|
||||
NODE_FILE = os.path.abspath(__file__)
|
||||
DREAM_NODES_SOURCE_ROOT = os.path.dirname(NODE_FILE)
|
||||
TEMP_PATH = os.path.join(os.path.abspath(comfy_paths.temp_directory), "Dream_Anim")
|
||||
@@ -39,40 +33,25 @@ def _replace_pil_image(data):
|
||||
return data
|
||||
|
||||
|
||||
_config_data = None
|
||||
|
||||
|
||||
class DreamConfig:
|
||||
FILEPATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), "config.json")
|
||||
DEFAULT_CONFIG = EMBEDDED_CONFIGURATION
|
||||
DEFAULT_CONFIG = {
|
||||
"ffmpeg": {
|
||||
"path": "ffmpeg",
|
||||
"arguments": ["-r", "%FPS%", "-f", "concat", "-safe", "0", "-i", "%FRAMES%", "-c:v", "libx265", "-pix_fmt",
|
||||
"yuv420p", "%OUTPUT%"]
|
||||
},
|
||||
"encoding": {
|
||||
"jpeg_quality": 95
|
||||
}
|
||||
}
|
||||
|
||||
def __init__(self):
|
||||
global _config_data
|
||||
if not os.path.isfile(DreamConfig.FILEPATH):
|
||||
self._data = DreamConfig.DEFAULT_CONFIG
|
||||
self._save()
|
||||
if _config_data is None:
|
||||
with open(DreamConfig.FILEPATH, encoding="utf-8") as f:
|
||||
self._data = json.load(f)
|
||||
if self._merge_with_defaults(self._data, DreamConfig.DEFAULT_CONFIG):
|
||||
self._save()
|
||||
_config_data = self._data
|
||||
else:
|
||||
self._data = _config_data
|
||||
|
||||
def _save(self):
|
||||
with open(DreamConfig.FILEPATH, "w", encoding="utf-8") as f:
|
||||
json.dump(self._data, f, indent=2)
|
||||
|
||||
def _merge_with_defaults(self, config: dict, default_config: dict) -> bool:
|
||||
changed = False
|
||||
for key in default_config.keys():
|
||||
if key not in config:
|
||||
changed = True
|
||||
config[key] = default_config[key]
|
||||
elif isinstance(default_config[key], dict):
|
||||
changed = changed or self._merge_with_defaults(config[key], default_config[key])
|
||||
return changed
|
||||
with open(DreamConfig.FILEPATH, "w") as f:
|
||||
json.dump(DreamConfig.DEFAULT_CONFIG, f, indent=2)
|
||||
with open(DreamConfig.FILEPATH) as f:
|
||||
self._data = json.load(f)
|
||||
|
||||
def get(self, key: str, default=None):
|
||||
key = key.split(".")
|
||||
@@ -85,11 +64,6 @@ class DreamConfig:
|
||||
return d
|
||||
|
||||
|
||||
def get_logger():
|
||||
config = DreamConfig()
|
||||
return DreamLog(config.get("debug", False))
|
||||
|
||||
|
||||
class DreamImageProcessor:
|
||||
def __init__(self, inputs: torch.Tensor, **extra_args):
|
||||
self._images_in_batch = [convertTensorImageToPIL(tensor) for tensor in inputs]
|
||||
@@ -134,34 +108,20 @@ class DreamImage:
|
||||
l = list(map(lambda i: i.create_tensor_image(), images))
|
||||
return torch.cat(l, dim=0)
|
||||
|
||||
def __init__(self, tensor_image=None, pil_image=None, file_path=None, with_alpha=False):
|
||||
def __init__(self, tensor_image=None, pil_image=None, file_path=None):
|
||||
if pil_image is not None:
|
||||
self.pil_image = pil_image
|
||||
elif tensor_image is not None:
|
||||
self.pil_image = convertTensorImageToPIL(tensor_image)
|
||||
else:
|
||||
self.pil_image = Image.open(file_path)
|
||||
if with_alpha and self.pil_image.mode != "RGBA":
|
||||
if self.pil_image.mode not in ("RGB", "RGBA"):
|
||||
self.pil_image = self.pil_image.convert("RGBA")
|
||||
else:
|
||||
if self.pil_image.mode not in ("RGB", "RGBA"):
|
||||
self.pil_image = self.pil_image.convert("RGB")
|
||||
self.width = self.pil_image.width
|
||||
self.height = self.pil_image.height
|
||||
self.size = self.pil_image.size
|
||||
self._draw = ImageDraw(self.pil_image)
|
||||
|
||||
def change_brightness(self, factor):
|
||||
enhancer = ImageEnhance.Brightness(self.pil_image)
|
||||
return DreamImage(pil_image=enhancer.enhance(factor))
|
||||
|
||||
def change_contrast(self, factor):
|
||||
enhancer = ImageEnhance.Contrast(self.pil_image)
|
||||
return DreamImage(pil_image=enhancer.enhance(factor))
|
||||
|
||||
def numpy_array(self):
|
||||
return numpy.array(self.pil_image)
|
||||
|
||||
def _renew(self, pil_image):
|
||||
self.pil_image = pil_image
|
||||
self._draw = ImageDraw(self.pil_image)
|
||||
@@ -185,11 +145,6 @@ class DreamImage:
|
||||
|
||||
return _Pixels(self)
|
||||
|
||||
def convert(self, mode="RGB"):
|
||||
if self.pil_image.mode == mode:
|
||||
return self
|
||||
return DreamImage(pil_image=self.pil_image.convert(mode))
|
||||
|
||||
def create_tensor_image(self):
|
||||
return convertFromPILToTensorImage(self.pil_image)
|
||||
|
||||
@@ -203,15 +158,6 @@ class DreamImage:
|
||||
def blur(self, amount):
|
||||
return DreamImage(pil_image=self.pil_image.filter(ImageFilter.GaussianBlur(amount)))
|
||||
|
||||
def adjust_colors(self, red_factor=1.0, green_factor=1.0, blue_factor=1.0):
|
||||
# newRed = 1.1*oldRed + 0*oldGreen + 0*oldBlue + constant
|
||||
# newGreen = 0*oldRed + 0.9*OldGreen + 0*OldBlue + constant
|
||||
# newBlue = 0*oldRed + 0*OldGreen + 1*OldBlue + constant
|
||||
matrix = (red_factor, 0, 0, 0,
|
||||
0, green_factor, 0, 0,
|
||||
0, 0, blue_factor, 0)
|
||||
return DreamImage(pil_image=self.pil_image.convert("RGB", matrix))
|
||||
|
||||
def get_pixel(self, x, y):
|
||||
p = self.pil_image.getpixel((x, y))
|
||||
if len(p) == 4:
|
||||
@@ -241,10 +187,6 @@ class DreamImage:
|
||||
def save_jpg(self, filepath, quality=98):
|
||||
self.pil_image.save(filepath, quality=quality, optimize=True)
|
||||
|
||||
@classmethod
|
||||
def from_file(cls, file_path):
|
||||
return DreamImage(pil_image=Image.open(file_path))
|
||||
|
||||
|
||||
class DreamMask:
|
||||
def __init__(self, tensor_image=None, pil_image=None):
|
||||
@@ -332,9 +274,9 @@ class DreamStateStore:
|
||||
|
||||
|
||||
class DreamStateFile:
|
||||
def __init__(self, state_collection_name="state"):
|
||||
self._filepath = os.path.join(TEMP_PATH, state_collection_name+".json")
|
||||
self._dirname = os.path.dirname(self._filepath)
|
||||
def __init__(self, state_file_path=os.path.join(TEMP_PATH, "state.json")):
|
||||
self._dirname = os.path.dirname(state_file_path)
|
||||
self._filepath = state_file_path
|
||||
if not os.path.isdir(self._dirname):
|
||||
os.makedirs(self._dirname)
|
||||
if not os.path.isfile(self._filepath):
|
||||
@@ -358,6 +300,7 @@ class DreamStateFile:
|
||||
self._data[key] = value
|
||||
with open(self._filepath, "w", encoding="utf-8") as f:
|
||||
json.dump(self._data, f)
|
||||
print("* {} -> {}".format(key, value))
|
||||
return previous
|
||||
|
||||
|
||||
@@ -366,53 +309,3 @@ def hashed_as_strings(*items):
|
||||
m = hashlib.sha256()
|
||||
m.update(tokens.encode(encoding="utf-8"))
|
||||
return m.digest().hex()
|
||||
|
||||
|
||||
class MpegEncoderUtility:
|
||||
def __init__(self, video_path: str, bit_rate_factor: float, width: int, height: int, files: List[str],
|
||||
fps: float, encoding_threads: int, codec_name, max_b_frame):
|
||||
import mpegCoder
|
||||
self._files = files
|
||||
self._logger = get_logger()
|
||||
self._enc = mpegCoder.MpegEncoder()
|
||||
bit_rate = self._calculate_bit_rate(width, height, fps, bit_rate_factor)
|
||||
self._logger.info("Bitrate " + str(bit_rate))
|
||||
self._enc.setParameter(
|
||||
videoPath=video_path, codecName=codec_name,
|
||||
nthread=encoding_threads, bitRate=bit_rate, width=width, height=height, widthSrc=width,
|
||||
heightSrc=height,
|
||||
GOPSize=len(files), maxBframe=max_b_frame, frameRate=self._fps_to_tuple(fps))
|
||||
|
||||
def _calculate_bit_rate(self, width: int, height: int, fps: float, bit_rate_factor: float):
|
||||
bits_per_pixel_base = 0.5
|
||||
return round(max(10, float(width * height * fps * bits_per_pixel_base * bit_rate_factor * 0.001)))
|
||||
|
||||
def encode(self):
|
||||
if not self._enc.FFmpegSetup():
|
||||
raise Exception("Failed to setup MPEG Encoder - check parameters!")
|
||||
try:
|
||||
t = time.time()
|
||||
|
||||
for filepath in self._files:
|
||||
self._logger.debug("Encoding frame {}", filepath)
|
||||
image = DreamImage.from_file(filepath).convert("RGB")
|
||||
self._enc.EncodeFrame(image.numpy_array())
|
||||
self._enc.FFmpegClose()
|
||||
self._logger.info("Completed video encoding of {n} frames in {t} seconds", n=len(self._files),
|
||||
t=round(time.time() - t))
|
||||
finally:
|
||||
self._enc.clear()
|
||||
|
||||
def _fps_to_tuple(self, fps: float):
|
||||
def _is_almost_int(f: float):
|
||||
return abs(f - int(f)) < 0.001
|
||||
|
||||
a = fps
|
||||
b = 1
|
||||
while not _is_almost_int(a) and b < 100:
|
||||
a /= 10
|
||||
b *= 10
|
||||
a = round(a)
|
||||
b = round(b)
|
||||
self._logger.info("Video specified as {fps} fps - encoder framerate {a}/{b}", fps=fps, a=a, b=b)
|
||||
return (a, b)
|
||||
|
||||
-212
@@ -1,212 +0,0 @@
|
||||
from .categories import NodeCategories
|
||||
from .err import *
|
||||
from .shared import ALWAYS_CHANGED_FLAG, hashed_as_strings
|
||||
from .types import RGBPalette
|
||||
|
||||
|
||||
def _generate_switch_input(type: str):
|
||||
d = dict()
|
||||
for i in range(10):
|
||||
d["input_" + str(i)] = (type,)
|
||||
return {
|
||||
"required": {
|
||||
"select": ("INT", {"defualt": 0, "min": 0, "max": 9}),
|
||||
"on_missing": (["previous", "next"],)
|
||||
},
|
||||
"optional": d
|
||||
}
|
||||
|
||||
|
||||
def _do_pick(cls, select, on_missing, **args):
|
||||
direction = 1
|
||||
if on_missing == "previous":
|
||||
direction = -1
|
||||
if len(args) == 0:
|
||||
on_error(cls, "No inputs provided!")
|
||||
while args.get("input_" + str(select), None) is None:
|
||||
select = (select + direction) % 10
|
||||
return args["input_" + str(select)],
|
||||
|
||||
|
||||
class DreamBigImageSwitch:
|
||||
_switch_type = "IMAGE"
|
||||
NODE_NAME = "Big Image Switch"
|
||||
ICON = "⭆"
|
||||
CATEGORY = NodeCategories.UTILS_SWITCHES
|
||||
RETURN_TYPES = (_switch_type,)
|
||||
RETURN_NAMES = ("selected",)
|
||||
FUNCTION = "pick"
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return _generate_switch_input(cls._switch_type)
|
||||
|
||||
@classmethod
|
||||
def IS_CHANGED(cls, *values):
|
||||
return ALWAYS_CHANGED_FLAG
|
||||
|
||||
def pick(self, select, on_missing, **args):
|
||||
return _do_pick(self.__class__, select, on_missing, **args)
|
||||
|
||||
|
||||
class DreamBigLatentSwitch:
|
||||
_switch_type = "LATENT"
|
||||
NODE_NAME = "Big Latent Switch"
|
||||
ICON = "⭆"
|
||||
CATEGORY = NodeCategories.UTILS_SWITCHES
|
||||
RETURN_TYPES = (_switch_type,)
|
||||
RETURN_NAMES = ("selected",)
|
||||
FUNCTION = "pick"
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return _generate_switch_input(cls._switch_type)
|
||||
|
||||
@classmethod
|
||||
def IS_CHANGED(cls, *values):
|
||||
return ALWAYS_CHANGED_FLAG
|
||||
|
||||
def pick(self, select, on_missing, **args):
|
||||
return _do_pick(self.__class__, select, on_missing, **args)
|
||||
|
||||
|
||||
class DreamBigTextSwitch:
|
||||
_switch_type = "STRING"
|
||||
NODE_NAME = "Big Text Switch"
|
||||
ICON = "⭆"
|
||||
CATEGORY = NodeCategories.UTILS_SWITCHES
|
||||
RETURN_TYPES = (_switch_type,)
|
||||
RETURN_NAMES = ("selected",)
|
||||
FUNCTION = "pick"
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return _generate_switch_input(cls._switch_type)
|
||||
|
||||
@classmethod
|
||||
def IS_CHANGED(cls, *values):
|
||||
return hashed_as_strings(values)
|
||||
|
||||
def pick(self, select, on_missing, **args):
|
||||
return _do_pick(self.__class__, select, on_missing, **args)
|
||||
|
||||
|
||||
class DreamBigPaletteSwitch:
|
||||
_switch_type = RGBPalette.ID
|
||||
NODE_NAME = "Big Palette Switch"
|
||||
ICON = "⭆"
|
||||
CATEGORY = NodeCategories.UTILS_SWITCHES
|
||||
RETURN_TYPES = (_switch_type,)
|
||||
RETURN_NAMES = ("selected",)
|
||||
FUNCTION = "pick"
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return _generate_switch_input(cls._switch_type)
|
||||
|
||||
@classmethod
|
||||
def IS_CHANGED(cls, *values):
|
||||
return ALWAYS_CHANGED_FLAG
|
||||
|
||||
def pick(self, select, on_missing, **args):
|
||||
return _do_pick(self.__class__, select, on_missing, **args)
|
||||
|
||||
|
||||
class DreamBigFloatSwitch:
|
||||
_switch_type = "FLOAT"
|
||||
NODE_NAME = "Big Float Switch"
|
||||
ICON = "⭆"
|
||||
CATEGORY = NodeCategories.UTILS_SWITCHES
|
||||
RETURN_TYPES = (_switch_type,)
|
||||
RETURN_NAMES = ("selected",)
|
||||
FUNCTION = "pick"
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return _generate_switch_input(cls._switch_type)
|
||||
|
||||
@classmethod
|
||||
def IS_CHANGED(cls, *values):
|
||||
return hashed_as_strings(values)
|
||||
|
||||
def pick(self, select, on_missing, **args):
|
||||
return _do_pick(self.__class__, select, on_missing, **args)
|
||||
|
||||
|
||||
class DreamBigIntSwitch:
|
||||
_switch_type = "INT"
|
||||
NODE_NAME = "Big Int Switch"
|
||||
ICON = "⭆"
|
||||
CATEGORY = NodeCategories.UTILS_SWITCHES
|
||||
RETURN_TYPES = (_switch_type,)
|
||||
RETURN_NAMES = ("selected",)
|
||||
FUNCTION = "pick"
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return _generate_switch_input(cls._switch_type)
|
||||
|
||||
@classmethod
|
||||
def IS_CHANGED(cls, *values):
|
||||
return hashed_as_strings(values)
|
||||
|
||||
def pick(self, select, on_missing, **args):
|
||||
return _do_pick(self.__class__, select, on_missing, **args)
|
||||
|
||||
|
||||
class DreamBoolToFloat:
|
||||
NODE_NAME = "Boolean To Float"
|
||||
ICON = "⬖"
|
||||
CATEGORY = NodeCategories.UTILS_SWITCHES
|
||||
RETURN_TYPES = ("FLOAT",)
|
||||
RETURN_NAMES = ("result",)
|
||||
FUNCTION = "pick"
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"boolean": ("BOOLEAN", {"default": False}),
|
||||
"on_true": ("FLOAT", {"default": 1.0}),
|
||||
"on_false": ("FLOAT", {"default": 0.0})
|
||||
}
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def IS_CHANGED(cls, *values):
|
||||
return hashed_as_strings(values)
|
||||
|
||||
def pick(self, boolean, on_true, on_false):
|
||||
if boolean:
|
||||
return (on_true,)
|
||||
else:
|
||||
return (on_false,)
|
||||
|
||||
|
||||
class DreamBoolToInt:
|
||||
NODE_NAME = "Boolean To Int"
|
||||
ICON = "⬖"
|
||||
CATEGORY = NodeCategories.UTILS_SWITCHES
|
||||
RETURN_TYPES = ("INT",)
|
||||
RETURN_NAMES = ("result",)
|
||||
FUNCTION = "pick"
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"boolean": ("BOOLEAN", {"default": False}),
|
||||
"on_true": ("INT", {"default": 1}),
|
||||
"on_false": ("INT", {"default": 0})
|
||||
}
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def IS_CHANGED(cls, *values):
|
||||
return hashed_as_strings(values)
|
||||
|
||||
def pick(self, boolean, on_true, on_false):
|
||||
if boolean:
|
||||
return (on_true,)
|
||||
else:
|
||||
return (on_false,)
|
||||
@@ -1,8 +1,7 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import random
|
||||
import time
|
||||
|
||||
from typing import List, Dict, Tuple
|
||||
from typing import List, Dict
|
||||
|
||||
from .shared import DreamImage
|
||||
|
||||
@@ -26,24 +25,6 @@ class RGBPalette:
|
||||
for c in colors:
|
||||
self._colors.append(_fix_tuple(c))
|
||||
|
||||
def _calculate_channel_contrast(self, c):
|
||||
hist = list(map(lambda _: 0, range(16)))
|
||||
for pixel in self._colors:
|
||||
hist[pixel[c] // 16] += 1
|
||||
s = 0
|
||||
max_possible = (15 - 0) * (len(self) // 2) * (len(self) // 2)
|
||||
for i in range(16):
|
||||
for j in range(i):
|
||||
if i != j:
|
||||
s += abs(i - j) * hist[i] * hist[j]
|
||||
return s / max_possible
|
||||
|
||||
def _calculate_combined_contrast(self):
|
||||
s = 0
|
||||
for c in range(3):
|
||||
s += self._calculate_channel_contrast(c)
|
||||
return s / 3
|
||||
|
||||
def analyze(self):
|
||||
total_red = 0
|
||||
total_blue = 0
|
||||
@@ -56,7 +37,7 @@ class RGBPalette:
|
||||
r = float(total_red) / (255 * n)
|
||||
g = float(total_green) / (255 * n)
|
||||
b = float(total_blue) / (255 * n)
|
||||
return ((r + g + b) / 3.0, self._calculate_combined_contrast(), r, g, b)
|
||||
return ((r + g + b) / 3.0, r, g, b)
|
||||
|
||||
def __len__(self):
|
||||
return len(self._colors)
|
||||
@@ -82,86 +63,6 @@ class RGBPalette:
|
||||
return _ColorIterator()
|
||||
|
||||
|
||||
class PartialPrompt:
|
||||
ID = "PARTIAL_PROMPT"
|
||||
|
||||
def __init__(self):
|
||||
self._data = {}
|
||||
|
||||
def add(self, text: str, weight: float):
|
||||
output = PartialPrompt()
|
||||
output._data = dict(self._data)
|
||||
for parts in text.split(","):
|
||||
parts = parts.strip()
|
||||
if " " in parts:
|
||||
output._data["(" + parts + ")"] = weight
|
||||
else:
|
||||
output._data[parts] = weight
|
||||
return output
|
||||
|
||||
def is_empty(self):
|
||||
return not self._data
|
||||
|
||||
def abs_sum(self):
|
||||
if not self._data:
|
||||
return 0.0
|
||||
return sum(map(abs, self._data.values()))
|
||||
|
||||
def abs_max(self):
|
||||
if not self._data:
|
||||
return 0.0
|
||||
return max(map(abs, self._data.values()))
|
||||
|
||||
def scaled_by(self, f: float):
|
||||
new_data = PartialPrompt()
|
||||
new_data._data = dict(self._data)
|
||||
for text, weight in new_data._data.items():
|
||||
new_data._data[text] = weight * f
|
||||
return new_data
|
||||
|
||||
def finalize(self, clamp: float):
|
||||
items = self._data.items()
|
||||
items = sorted(items, key=lambda pair: (pair[1], pair[0]))
|
||||
pos = list()
|
||||
neg = list()
|
||||
for text, w in sorted(items, key=lambda pair: (-pair[1], pair[0])):
|
||||
if w >= 0.0001:
|
||||
pos.append("({}:{:.3f})".format(text, min(clamp, w)))
|
||||
for text, w in sorted(items, key=lambda pair: (pair[1], pair[0])):
|
||||
if w <= -0.0001:
|
||||
neg.append("({}:{:.3f})".format(text, min(clamp, -w)))
|
||||
return ", ".join(pos), ", ".join(neg)
|
||||
|
||||
|
||||
class LogEntry:
|
||||
ID = "LOG_ENTRY"
|
||||
|
||||
@classmethod
|
||||
def new(cls, text):
|
||||
return LogEntry([(time.time(), text)])
|
||||
|
||||
def __init__(self, data: List[Tuple[float, str]] = None):
|
||||
if data is None:
|
||||
self._data = list()
|
||||
else:
|
||||
self._data = list(data)
|
||||
|
||||
def add(self, text: str):
|
||||
new_data = list(self._data)
|
||||
new_data.append((time.time(), text))
|
||||
return LogEntry(new_data)
|
||||
|
||||
def merge(self, log_entry):
|
||||
new_data = list(self._data)
|
||||
new_data.extend(log_entry._data)
|
||||
return LogEntry(new_data)
|
||||
|
||||
def get_filtered_entries(self, t: float):
|
||||
for d in sorted(self._data):
|
||||
if d[0] > t:
|
||||
yield d
|
||||
|
||||
|
||||
class FrameCounter:
|
||||
ID = "FRAME_COUNTER"
|
||||
|
||||
@@ -189,14 +90,6 @@ class FrameCounter:
|
||||
def current_time_in_seconds(self):
|
||||
return float(self.current_frame) / self.frames_per_second
|
||||
|
||||
@property
|
||||
def total_time_in_seconds(self):
|
||||
return float(self.total_frames) / self.frames_per_second
|
||||
|
||||
@property
|
||||
def remaining_time_in_seconds(self):
|
||||
return self.total_time_in_seconds - self.current_time_in_seconds
|
||||
|
||||
@property
|
||||
def progress(self):
|
||||
return float(self.current_frame) / (max(2, self.total_frames) - 1)
|
||||
@@ -233,6 +126,6 @@ class AnimationSequence:
|
||||
|
||||
|
||||
class SharedTypes:
|
||||
frame_counter = {"frame_counter": (FrameCounter.ID,)}
|
||||
sequence = {"sequence": (AnimationSequence.ID,)}
|
||||
palette = {"palette": (RGBPalette.ID,)}
|
||||
frame_counter = {"frame_counter": (FrameCounter.ID, {"forceInput": True})}
|
||||
sequence = {"sequence": (AnimationSequence.ID, {"forceInput": True})}
|
||||
palette = {"palette": (RGBPalette.ID, {"forceInput": True})}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
def run_uninstall():
|
||||
pass
|
||||
|
||||
|
||||
+1
-215
@@ -1,220 +1,7 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import datetime
|
||||
import math
|
||||
import os
|
||||
|
||||
import folder_paths as comfy_paths
|
||||
|
||||
from .categories import NodeCategories
|
||||
from .shared import hashed_as_strings, DreamStateFile
|
||||
from .types import LogEntry, SharedTypes, FrameCounter
|
||||
|
||||
_logfile_state = DreamStateFile("logging")
|
||||
|
||||
|
||||
class DreamJoinLog:
|
||||
NODE_NAME = "Log Entry Joiner"
|
||||
ICON = "🗎"
|
||||
CATEGORY = NodeCategories.UTILS
|
||||
RETURN_TYPES = (LogEntry.ID,)
|
||||
RETURN_NAMES = ("log_entry",)
|
||||
FUNCTION = "convert"
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"optional": {
|
||||
"entry_0": (LogEntry.ID,),
|
||||
"entry_1": (LogEntry.ID,),
|
||||
"entry_2": (LogEntry.ID,),
|
||||
"entry_3": (LogEntry.ID,),
|
||||
}
|
||||
}
|
||||
|
||||
def convert(self, **values):
|
||||
entry = LogEntry([])
|
||||
for i in range(4):
|
||||
txt = values.get("entry_" + str(i), None)
|
||||
if txt:
|
||||
entry = entry.merge(txt)
|
||||
return (entry,)
|
||||
|
||||
|
||||
class DreamFloatToLog:
|
||||
NODE_NAME = "Float to Log Entry"
|
||||
ICON = "🗎"
|
||||
CATEGORY = NodeCategories.UTILS
|
||||
RETURN_TYPES = (LogEntry.ID,)
|
||||
RETURN_NAMES = ("log_entry",)
|
||||
FUNCTION = "convert"
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"value": ("FLOAT", {"default": 0}),
|
||||
"label": ("STRING", {"default": ""}),
|
||||
},
|
||||
}
|
||||
|
||||
def convert(self, label, value):
|
||||
return (LogEntry.new(label + ": " + str(value)),)
|
||||
|
||||
|
||||
class DreamIntToLog:
|
||||
NODE_NAME = "Int to Log Entry"
|
||||
ICON = "🗎"
|
||||
CATEGORY = NodeCategories.UTILS
|
||||
RETURN_TYPES = (LogEntry.ID,)
|
||||
RETURN_NAMES = ("log_entry",)
|
||||
FUNCTION = "convert"
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"value": ("INT", {"default": 0}),
|
||||
"label": ("STRING", {"default": ""}),
|
||||
},
|
||||
}
|
||||
|
||||
def convert(self, label, value):
|
||||
return (LogEntry.new(label + ": " + str(value)),)
|
||||
|
||||
|
||||
class DreamStringToLog:
|
||||
NODE_NAME = "String to Log Entry"
|
||||
ICON = "🗎"
|
||||
OUTPUT_NODE = True
|
||||
CATEGORY = NodeCategories.UTILS
|
||||
RETURN_TYPES = (LogEntry.ID,)
|
||||
RETURN_NAMES = ("log_entry",)
|
||||
FUNCTION = "convert"
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"text": ("STRING", {"default": ""}),
|
||||
},
|
||||
"optional": {
|
||||
"label": ("STRING", {"default": ""}),
|
||||
}
|
||||
}
|
||||
|
||||
def convert(self, text, **values):
|
||||
label = values.get("label", "")
|
||||
if label:
|
||||
return (LogEntry.new(label + ": " + text),)
|
||||
else:
|
||||
return (LogEntry.new(text),)
|
||||
|
||||
|
||||
class DreamStringTokenizer:
|
||||
NODE_NAME = "String Tokenizer"
|
||||
ICON = "🪙"
|
||||
OUTPUT_NODE = True
|
||||
CATEGORY = NodeCategories.UTILS
|
||||
RETURN_TYPES = ("STRING",)
|
||||
RETURN_NAMES = ("token",)
|
||||
FUNCTION = "exec"
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"text": ("STRING", {"default": "", "multiline": True}),
|
||||
"separator": ("STRING", {"default": ","}),
|
||||
"selected": ("INT", {"default": 0, "min": 0})
|
||||
},
|
||||
}
|
||||
|
||||
def exec(self, text: str, separator: str, selected: int):
|
||||
if separator is None or separator == "":
|
||||
separator = " "
|
||||
parts = text.split(sep=separator)
|
||||
return (parts[max(0, selected) % len(parts)].strip(),)
|
||||
|
||||
|
||||
class DreamLogFile:
|
||||
NODE_NAME = "Log File"
|
||||
ICON = "🗎"
|
||||
OUTPUT_NODE = True
|
||||
CATEGORY = NodeCategories.UTILS
|
||||
RETURN_TYPES = ()
|
||||
RETURN_NAMES = ()
|
||||
FUNCTION = "write"
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": SharedTypes.frame_counter | {
|
||||
"log_directory": ("STRING", {"default": comfy_paths.output_directory}),
|
||||
"log_filename": ("STRING", {"default": "dreamlog.txt"}),
|
||||
"stdout": ("BOOLEAN", {"default": True}),
|
||||
"active": ("BOOLEAN", {"default": True}),
|
||||
"clock_has_24_hours": ("BOOLEAN", {"default": True}),
|
||||
},
|
||||
"optional": {
|
||||
"entry_0": (LogEntry.ID,),
|
||||
"entry_1": (LogEntry.ID,),
|
||||
"entry_2": (LogEntry.ID,),
|
||||
"entry_3": (LogEntry.ID,),
|
||||
"entry_4": (LogEntry.ID,),
|
||||
"entry_5": (LogEntry.ID,),
|
||||
"entry_6": (LogEntry.ID,),
|
||||
"entry_7": (LogEntry.ID,),
|
||||
},
|
||||
}
|
||||
|
||||
def _path_to_log_file(self, log_directory, logfile):
|
||||
if os.path.isabs(logfile):
|
||||
return os.path.normpath(os.path.abspath(logfile))
|
||||
elif os.path.isabs(log_directory):
|
||||
return os.path.normpath(os.path.abspath(os.path.join(log_directory, logfile)))
|
||||
elif log_directory:
|
||||
return os.path.normpath(os.path.abspath(os.path.join(comfy_paths.output_directory, log_directory, logfile)))
|
||||
else:
|
||||
return os.path.normpath(os.path.abspath(os.path.join(comfy_paths.output_directory, logfile)))
|
||||
|
||||
def _get_tm_format(self, clock_has_24_hours):
|
||||
if clock_has_24_hours:
|
||||
return "%a %H:%M:%S"
|
||||
else:
|
||||
return "%a %I:%M:%S %p"
|
||||
|
||||
def write(self, frame_counter: FrameCounter, log_directory, log_filename, stdout, active, clock_has_24_hours,
|
||||
**entries):
|
||||
if not active:
|
||||
return ()
|
||||
log_entry = None
|
||||
for i in range(8):
|
||||
e = entries.get("entry_" + str(i), None)
|
||||
if e is not None:
|
||||
if log_entry is None:
|
||||
log_entry = e
|
||||
else:
|
||||
log_entry = log_entry.merge(e)
|
||||
log_file_path = self._path_to_log_file(log_directory, log_filename)
|
||||
ts = _logfile_state.get_section("timestamps").get(log_file_path, 0)
|
||||
output_text = list()
|
||||
last_t = 0
|
||||
for (t, text) in log_entry.get_filtered_entries(ts):
|
||||
dt = datetime.datetime.fromtimestamp(t)
|
||||
output_text.append("[frame {}/{} (~{}%), timestamp {}]\n{}".format(frame_counter.current_frame + 1,
|
||||
frame_counter.total_frames,
|
||||
round(frame_counter.progress * 100),
|
||||
dt.strftime(self._get_tm_format(
|
||||
clock_has_24_hours)), text.rstrip()))
|
||||
output_text.append("---")
|
||||
last_t = max(t, last_t)
|
||||
output_text = "\n".join(output_text) + "\n"
|
||||
if stdout:
|
||||
print(output_text)
|
||||
with open(log_file_path, "a", encoding="utf-8") as f:
|
||||
f.write(output_text)
|
||||
_logfile_state.get_section("timestamps").update(log_file_path, 0, lambda _: last_t)
|
||||
return ()
|
||||
from .shared import hashed_as_strings
|
||||
|
||||
|
||||
def _align_num(n: int, alignment: int, type: str):
|
||||
@@ -230,7 +17,6 @@ def _align_num(n: int, alignment: int, type: str):
|
||||
|
||||
class DreamFrameDimensions:
|
||||
NODE_NAME = "Common Frame Dimensions"
|
||||
ICON = "⌗"
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
|
||||
Reference in New Issue
Block a user