From 533386aff54efe708b6fbc48b4bf5d7fc311101b Mon Sep 17 00:00:00 2001 From: Morgan Johansson Date: Wed, 13 Sep 2023 18:52:07 +0200 Subject: [PATCH] 3.1 - curves, calculations, prompt building and switches. --- __init__.py | 13 ++- base.py | 65 +++++++++++++- calculate.py | 98 +++++++++++++++++++++ categories.py | 4 +- curves.py | 160 +++++++++++++++++++++++++++++++++- embedded_config.py | 41 +++++++++ node_list.json | 19 +++- prompting.py | 69 +++++++++++++++ readme.md | 32 +++++++ requirements.txt | 1 + shared.py | 41 +-------- switches.py | 212 +++++++++++++++++++++++++++++++++++++++++++++ types.py | 54 ++++++++++++ 13 files changed, 761 insertions(+), 48 deletions(-) create mode 100644 calculate.py create mode 100644 embedded_config.py create mode 100644 prompting.py create mode 100644 switches.py diff --git a/__init__.py b/__init__.py index b448264..4f36129 100644 --- a/__init__.py +++ b/__init__.py @@ -5,12 +5,15 @@ 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 .inputfields import * +from .calculate import * _NODE_CLASSES: List[Type] = [DreamSineWave, DreamLinear, DreamCSVCurve, DreamBeatCurve, DreamFrameDimensions, DreamImageMotion, DreamNoiseFromPalette, DreamAnalyzePalette, DreamColorShift, @@ -19,12 +22,16 @@ _NODE_CLASSES: List[Type] = [DreamSineWave, DreamLinear, DreamCSVCurve, DreamBea DreamImageSequenceOutput, DreamCSVGenerator, DreamImageAreaSampler, DreamVideoEncoder, DreamSequenceTweening, DreamSequenceBlend, DreamColorAlign, DreamImageSampler, DreamNoiseFromAreaPalettes, DreamVideoEncoderMpegCoder, - DreamInputString, DreamInputFloat, DreamInputInt, DreamInputText] + DreamInputString, DreamInputFloat, DreamInputInt, DreamInputText, DreamBigLatentSwitch, + DreamFrameCountCalculator, DreamBigImageSwitch, DreamBigTextSwitch, DreamBigFloatSwitch, + DreamBigIntSwitch, DreamBigPaletteSwitch, DreamWeightedPromptBuilder, DreamPromptFinalizer, + DreamFrameCounterInfo, DreamBoolToFloat, DreamBoolToInt, DreamSawWave, DreamTriangleWave, + DreamTriangleEvent, DreamSmoothEvent, DreamCalculation] _SIGNATURE_SUFFIX = " [Dream]" MANIFEST = { "name": "Dream Project Animation", - "version": (3, 0, 2), + "version": (3, 1, 0), "author": "Dream Project", "project": "https://github.com/alt-key-project/comfyui-dream-project", "description": "Various utility nodes for creating animations with ComfyUI", diff --git a/base.py b/base.py index fcf314e..4faca12 100644 --- a/base.py +++ b/base.py @@ -6,6 +6,37 @@ 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 = "📂" @@ -35,7 +66,7 @@ class DreamDirectoryFileCount: for pattern in patterns.split("|"): files = list(glob.glob(pattern, root_dir=directory_path)) total += len(files) - print("total "+str(total)) + print("total " + str(total)) return (total,) @@ -105,7 +136,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": 25}), + "frames_per_second": ("INT", {"min": 1, "default": 30}), }, } @@ -124,3 +155,33 @@ 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),) diff --git a/calculate.py b/calculate.py new file mode 100644 index 0000000..fa0dafd --- /dev/null +++ b/calculate.py @@ -0,0 +1,98 @@ +# -*- 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" + + @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.ANIMATION_CURVES + 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)) diff --git a/categories.py b/categories.py index 4bd9d9b..4299647 100644 --- a/categories.py +++ b/categories.py @@ -5,9 +5,11 @@ class NodeCategories: 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" \ No newline at end of file + UTILS = "utils" + UTILS_SWITCHES = "utils/switches" \ No newline at end of file diff --git a/curves.py b/curves.py index d4cb8a4..ed6bea1 100644 --- a/curves.py +++ b/curves.py @@ -7,6 +7,20 @@ 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" @@ -37,7 +51,149 @@ class DreamSineWave: b = 2 * math.pi / periodicity_seconds d = (max_value + min_value) / 2 y = a * math.sin(b * (x + c)) + d - return (y, int(round(y))) + 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) class DreamBeatCurve: @@ -96,7 +252,7 @@ class DreamBeatCurve: v = 1.0 - v r = low_value + v * (high_value - low_value) - return (r, int(round(r))) + return _curve_result(r) class DreamLinear: diff --git a/embedded_config.py b/embedded_config.py new file mode 100644 index 0000000..f685e2f --- /dev/null +++ b/embedded_config.py @@ -0,0 +1,41 @@ +EMBEDDED_CONFIGURATION = { + "ffmpeg": { + "file_extension": "mp4", + "path": "ffmpeg", + "arguments": ["-r", "%FPS%", "-f", "concat", "-safe", "0", "-vsync", + "cfr", "-i", "%FRAMES%", "-c:v", "libx265", "-pix_fmt", + "yuv420p", "%OUTPUT%"] + }, + "mpeg_coder": { + "encoding_threads": 4, + "bitrate_factor": 1.0, + "max_b_frame": 2, + "file_extension": "mp4", + "codec_name": "libx265" + }, + "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": "✨" + } + }, + +} diff --git a/node_list.json b/node_list.json index b8912e8..de24b25 100644 --- a/node_list.json +++ b/node_list.json @@ -1,14 +1,27 @@ { "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", "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)", + "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 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", @@ -23,8 +36,12 @@ "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)", "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" -} \ No newline at end of file +} and switches. \ No newline at end of file diff --git a/prompting.py b/prompting.py new file mode 100644 index 0000000..18861db --- /dev/null +++ b/prompting.py @@ -0,0 +1,69 @@ +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) diff --git a/readme.md b/readme.md index 30371f9..0e35a82 100644 --- a/readme.md +++ b/readme.md @@ -133,6 +133,20 @@ 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. + ### CSV Curve [Dream] CSV input curve where first column is frame or second and second column is value. @@ -152,6 +166,9 @@ Post processing for animation sequences using the python module mpegCoder with f ### File Count [Dream] Finds the number of files in a directory matching specified patterns. +### 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. @@ -159,6 +176,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. @@ -202,9 +222,21 @@ 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. + +### 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 diff --git a/requirements.txt b/requirements.txt index 9cd9d3d..4a17845 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,3 +4,4 @@ scipy numpy<1.24>=1.18 torchvision mpegCoder +evalidate diff --git a/shared.py b/shared.py index 0aaead0..14da6b6 100644 --- a/shared.py +++ b/shared.py @@ -13,6 +13,7 @@ import torch from PIL import Image, ImageFilter from PIL.ImageDraw import ImageDraw from PIL.PngImagePlugin import PngInfo +from .embedded_config import EMBEDDED_CONFIGURATION from typing import Dict, Tuple, List from .dreamlogger import DreamLog @@ -43,45 +44,7 @@ _config_data = None class DreamConfig: FILEPATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), "config.json") - DEFAULT_CONFIG = { - "ffmpeg": { - "file_extension": "mp4", - "path": "ffmpeg", - "arguments": ["-r", "%FPS%", "-f", "concat", "-safe", "0", "-vsync", - "cfr", "-i", "%FRAMES%", "-c:v", "libx265", "-pix_fmt", - "yuv420p", "%OUTPUT%"] - }, - "mpeg_coder": { - "encoding_threads": 4, - "bitrate_factor": 1.0, - "max_b_frame": 2, - "file_extension": "mp4", - "codec_name": "libx265" - }, - "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": "🌄", - "Dream": "✨" - } - }, - - } + DEFAULT_CONFIG = EMBEDDED_CONFIGURATION def __init__(self): global _config_data diff --git a/switches.py b/switches.py new file mode 100644 index 0000000..107c856 --- /dev/null +++ b/switches.py @@ -0,0 +1,212 @@ +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,) diff --git a/types.py b/types.py index 02db276..4e3445b 100644 --- a/types.py +++ b/types.py @@ -64,6 +64,52 @@ 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) + output._data[text.strip()] = 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 FrameCounter: ID = "FRAME_COUNTER" @@ -91,6 +137,14 @@ 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)