68 Commits
Author SHA1 Message Date
Morgan Johansson f48bed5b8a Removed resampling from Pillow access. 2025-02-16 15:45:39 +01:00
Morgan Johansson b09b8a79eb Removed resampling from Pillow access. 2025-02-16 15:45:15 +01:00
Morgan Johansson 44afbb07c6 Node list fix. 2025-02-15 19:13:25 +01:00
Morgan Johansson a8071b2089 Fix for IS_CHANGED. 2025-02-15 18:49:35 +01:00
Morgan Johansson 12d7ef2a54 Fix for IS_CHANGED 2025-02-12 18:09:16 +01:00
Morgan Johansson a94e21dbdd Fix for IS_CHANGED 2025-02-12 18:03:06 +01:00
Morgan Johansson ae524cd593 Fix for node defaults. 2025-02-11 21:58:33 +01:00
Morgan Johansson d45fffff23 Update check fix. 2025-02-11 21:48:17 +01:00
Morgan Johansson 411948551a Compatibility fix. 2025-02-11 21:41:14 +01:00
Morgan Johansson d87e5d8a7c Compatibility fix. 2025-02-11 21:33:05 +01:00
Morgan Johansson 9e87f209aa Compatibility fix. 2025-02-11 21:27:59 +01:00
Morgan Johansson f09e0f3e36 Compatibility fix. 2025-02-11 21:24:33 +01:00
Morgan Johansson b2dfaea97c Compatibility fix. 2025-02-11 21:18:25 +01:00
Morgan Johansson 1e50facb9e Install fix. 2025-02-11 21:09:48 +01:00
Morgan Johansson b63340f4b7 Version fix. 2025-02-11 21:07:03 +01:00
Morgan Johansson f77719bf34 Version fix. 2025-02-11 21:04:30 +01:00
Morgan Johansson c8fe03350e Version fix. 2025-02-11 20:38:32 +01:00
Morgan Johansson 96258138b6 Re-added random prompt node. 2025-02-11 20:30:11 +01:00
Morgan Johansson 98776cccb6 numpy req versions 2025-02-11 19:47:31 +01:00
Morgan Johansson 942d7d8931 Fix for change check warnings. 2025-02-11 19:46:06 +01:00
Morgan Johansson 1d38975ce3 prompt builder input range fix 2025-01-17 22:00:38 +01:00
Morgan Johansson 403095b513 Random Prompt Words fix. 2025-01-15 21:29:25 +01:00
Morgan Johansson 170664c592 Lazy switches added. 2025-01-15 19:05:13 +01:00
Morgan Johansson 7b73e65cec DreamRandomPromptWords fix. 2025-01-14 17:38:09 +01:00
Morgan Johansson d5c107e541 DreamRandomPromptWords fix. 2025-01-14 17:32:06 +01:00
Morgan Johansson a22b11b43c DreamRandomPromptWords separator added 2025-01-14 17:09:09 +01:00
Morgan Johansson 4b059e5095 DreamRandomPromptWords added 2025-01-14 17:03:29 +01:00
Morgan Johansson b5c804a332 Version bump. 2024-12-07 21:07:28 +01:00
Morgan Johansson 801bcaf1d7 Fixed requirements.txt that could cause parse error on some pip versions 2024-12-07 21:02:26 +01:00
Morgan Johansson b9e6959c1f 14: Switches will now return a None value if there is no valid input. 2024-12-01 10:09:08 +01:00
Morgan Johansson ed2c58cf2f 14: Switches will now return a None value if there is no valid input. 2024-12-01 09:58:49 +01:00
Morgan Johansson f44a70900a Version bump to 1.0.2 2024-12-01 09:50:42 +01:00
Morgan Johansson bff5ca4fbe Version bump to 1.0.1 2024-12-01 09:12:03 +01:00
Morgan Johansson a81c591e9d 14: resolved issue with inf loop in switches when there are inputs but none is valid. 2024-12-01 09:10:09 +01:00
alt-key-project 426a6e85e9 Merge pull request #12 from ComfyNodePRs/licence-update
Update PyProject Toml - License
2024-11-17 07:16:01 +01:00
snomiao e1a7ec58ed chore(licence-update): Update PyProject Toml - License 2024-07-31 15:02:24 +00:00
Morgan Johansson b2ddca87a9 fixed github action branch 2024-07-01 07:34:10 +02:00
Morgan Johansson 6bc4c1616f Merge branch 'master' of https://github.com/alt-key-project/comfyui-dream-project 2024-07-01 07:30:35 +02:00
Morgan Johansson bff8f66047 Version bump 2024-07-01 07:30:30 +02:00
alt-key-project 8babb241dd Merge pull request #10 from haohaocreates/publish
Add Github Action for Publishing to Comfy Registry
2024-07-01 07:27:24 +02:00
Morgan Johansson f456af2ec5 publisher id added 2024-07-01 07:18:55 +02:00
alt-key-project c3c7a4ccfd Merge pull request #11 from haohaocreates/pyproject
Add pyproject.toml for Custom Node Registry
2024-07-01 07:15:25 +02:00
haohaocreates b56e007c57 chore(pyproject): Add pyproject.toml for Custom Node Registry 2024-05-22 16:51:45 -04:00
haohaocreates e6545ec3c9 chore(publish): Add Github Action for Publishing to Comfy Registry 2024-05-22 16:51:43 -04:00
Morgan Johansson 2d642150da 5.0.1 2023-12-21 20:36:49 +01:00
Morgan Johansson c34e9f5213 Fixed issues with big switches. 2023-12-21 20:35:09 +01:00
Morgan Johansson 68246e3adf Added readme notice. 2023-11-25 12:27:15 +01:00
Morgan Johansson ab73436fc2 Added readme notice. 2023-11-25 12:25:42 +01:00
Morgan Johansson d9efc1516a Added readme notice. 2023-11-25 12:25:04 +01:00
Morgan Johansson 6880bde78c Minor update for changed python version. 2023-11-25 12:18:36 +01:00
Morgan Johansson 25cbdff39c Removed mpegCoder encoding as the module does not support newer versions of python. 2023-10-22 18:17:28 +02:00
Morgan Johansson 07bfda783d rename of types.py to dreamtypes.py 2023-10-22 12:08:37 +02:00
alt-key-project d74fdb8de7 Merge pull request #6 from linnkoln/master
Allows to output name of frame file
2023-10-17 21:22:49 +02:00
linnkoln 5def36f87f Allows to output name of frame file 2023-10-12 17:11:54 +03:00
Morgan Johansson 08a0a06820 Removed debug logging. 2023-09-19 20:39:38 +02:00
Morgan Johansson 5ce2e63f01 WAV curves. 2023-09-19 20:19:49 +02:00
Morgan Johansson ebed0e60d9 Update to examples. 2023-09-18 19:29:24 +02:00
Morgan Johansson f65df84de6 New example. 2023-09-18 19:22:54 +02:00
Morgan Johansson c5579afb12 minor fix 2023-09-18 06:59:15 +02:00
Morgan Johansson 7d5b6d8f4a Added string tokenizer utility 2023-09-18 06:54:59 +02:00
Morgan Johansson af492b7669 Example update. 2023-09-17 16:44:40 +02:00
Morgan Johansson 15084a9264 Label on string log entry. 2023-09-17 16:12:59 +02:00
Morgan Johansson 01a9d17b42 4.0 release - laboratory and logging 2023-09-17 10:08:45 +02:00
Morgan Johansson f4f5a5c7c5 Changed default codec to H.264 2023-09-16 10:49:40 +02:00
Morgan Johansson bcac6e1a33 New example + increased default bitrate for mpeg 2023-09-16 09:31:40 +02:00
Morgan Johansson 96e5fa3ae8 Contrast analysis/adjustment 2023-09-16 06:24:56 +02:00
Morgan Johansson 4418ba9454 version 2023-09-13 18:54:10 +02:00
Morgan Johansson fefe43ebe3 removed junk text 2023-09-13 18:53:08 +02:00
30 changed files with 12009 additions and 792 deletions
+21
View File
@@ -0,0 +1,21 @@
name: Publish to Comfy registry
on:
workflow_dispatch:
push:
branches:
- master
paths:
- "pyproject.toml"
jobs:
publish-node:
name: Publish Custom Node to registry
runs-on: ubuntu-latest
steps:
- name: Check out code
uses: actions/checkout@v4
- name: Publish Custom Node
uses: Comfy-Org/publish-node-action@main
with:
## Add your own personal access token to your Github Repository secrets and reference it here.
personal_access_token: ${{ secrets.REGISTRY_ACCESS_TOKEN }}
+2 -1
View File
@@ -1,3 +1,4 @@
.idea
__pycache__
config.json
config.json
*.cmd
+11 -3
View File
@@ -1,5 +1,8 @@
# -*- coding: utf-8 -*-
from typing import Type
import sys,os
sys.path.append(str(os.path.dirname(os.path.abspath(__file__))))
from .base import *
from .colors import *
@@ -14,6 +17,8 @@ from .seq_processing import *
from .switches import *
from .utility import *
from .calculate import *
from .laboratory import *
#from .lazyswitches import *
_NODE_CLASSES: List[Type] = [DreamSineWave, DreamLinear, DreamCSVCurve, DreamBeatCurve, DreamFrameDimensions,
DreamImageMotion, DreamNoiseFromPalette, DreamAnalyzePalette, DreamColorShift,
@@ -21,17 +26,20 @@ _NODE_CLASSES: List[Type] = [DreamSineWave, DreamLinear, DreamCSVCurve, DreamBea
DreamSimpleFrameCounter, DreamImageSequenceInputWithDefaultFallback,
DreamImageSequenceOutput, DreamCSVGenerator, DreamImageAreaSampler,
DreamVideoEncoder, DreamSequenceTweening, DreamSequenceBlend, DreamColorAlign,
DreamImageSampler, DreamNoiseFromAreaPalettes, DreamVideoEncoderMpegCoder,
DreamImageSampler, DreamNoiseFromAreaPalettes,
DreamInputString, DreamInputFloat, DreamInputInt, DreamInputText, DreamBigLatentSwitch,
DreamFrameCountCalculator, DreamBigImageSwitch, DreamBigTextSwitch, DreamBigFloatSwitch,
DreamBigIntSwitch, DreamBigPaletteSwitch, DreamWeightedPromptBuilder, DreamPromptFinalizer,
DreamFrameCounterInfo, DreamBoolToFloat, DreamBoolToInt, DreamSawWave, DreamTriangleWave,
DreamTriangleEvent, DreamSmoothEvent, DreamCalculation]
DreamTriangleEvent, DreamSmoothEvent, DreamCalculation, DreamImageColorShift,
DreamComparePalette, DreamImageContrast, DreamImageBrightness, DreamLogFile,
DreamLaboratory, DreamStringToLog, DreamIntToLog, DreamFloatToLog, DreamJoinLog,
DreamStringTokenizer, DreamWavCurve, DreamFrameCounterTimeOffset, DreamRandomPromptWords]
_SIGNATURE_SUFFIX = " [Dream]"
MANIFEST = {
"name": "Dream Project Animation",
"version": (3, 1, 0),
"version": (5, 1, 2),
"author": "Dream Project",
"project": "https://github.com/alt-key-project/comfyui-dream-project",
"description": "Various utility nodes for creating animations with ComfyUI",
+39 -22
View File
@@ -3,7 +3,7 @@ import glob
from .categories import NodeCategories
from .shared import *
from .types import *
from .dreamtypes import *
class DreamFrameCounterInfo:
@@ -22,10 +22,6 @@ class DreamFrameCounterInfo:
"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,
@@ -56,8 +52,14 @@ class DreamDirectoryFileCount:
FUNCTION = "result"
@classmethod
def IS_CHANGED(cls, *v):
return ALWAYS_CHANGED_FLAG
def IS_CHANGED(cls, directory_path, patterns):
if not os.path.isdir(directory_path):
return ""
total = 0
for pattern in patterns.split("|"):
files = list(glob.glob(pattern, root_dir=directory_path))
total += len(files)
return total
def result(self, directory_path, patterns):
if not os.path.isdir(directory_path):
@@ -66,7 +68,6 @@ class DreamDirectoryFileCount:
for pattern in patterns.split("|"):
files = list(glob.glob(pattern, root_dir=directory_path))
total += len(files)
print("total " + str(total))
return (total,)
@@ -88,13 +89,31 @@ class DreamFrameCounterOffset:
RETURN_NAMES = ("frame_counter",)
FUNCTION = "result"
@classmethod
def IS_CHANGED(cls, frame_counter, offset):
return hashed_as_strings(frame_counter, offset)
def result(self, frame_counter: FrameCounter, offset):
return (frame_counter.incremented(offset),)
class DreamFrameCounterTimeOffset:
NODE_NAME = "Frame Counter Time Offset"
ICON = "±"
@classmethod
def INPUT_TYPES(cls):
return {
"required": SharedTypes.frame_counter | {
"offset_seconds": ("FLOAT", {"default": 0.0}),
},
}
CATEGORY = NodeCategories.ANIMATION
RETURN_TYPES = (FrameCounter.ID,)
RETURN_NAMES = ("frame_counter",)
FUNCTION = "result"
def result(self, frame_counter: FrameCounter, offset_seconds):
offset = offset_seconds * frame_counter.frames_per_second
return (frame_counter.incremented(offset),)
class DreamSimpleFrameCounter:
NODE_NAME = "Frame Counter (Simple)"
@@ -115,10 +134,6 @@ class DreamSimpleFrameCounter:
RETURN_NAMES = ("frame_counter",)
FUNCTION = "result"
@classmethod
def IS_CHANGED(cls, *values):
return ALWAYS_CHANGED_FLAG
def result(self, frame_index, total_frames, frames_per_second):
n = frame_index
return (FrameCounter(n, total_frames, frames_per_second),)
@@ -146,8 +161,14 @@ class DreamDirectoryBackedFrameCounter:
FUNCTION = "result"
@classmethod
def IS_CHANGED(cls, *values):
return ALWAYS_CHANGED_FLAG
def IS_CHANGED(cls, directory_path, patterns, indexing, total_frames, frames_per_second):
if not os.path.isdir(directory_path):
return ""
total = 0
for pattern in patterns.split("|"):
files = list(glob.glob(pattern, root_dir=directory_path))
total += len(files)
return (total, indexing, total_frames, frames_per_second)
def result(self, directory_path, pattern, indexing, total_frames, frames_per_second):
results = list_images_in_directory(directory_path, pattern, indexing == "alphabetic order")
@@ -178,10 +199,6 @@ class DreamFrameCountCalculator:
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),)
+2 -5
View File
@@ -10,6 +10,7 @@ from .shared import hashed_as_strings
class DreamCalculation:
NODE_NAME = "Calculation"
ICON = "🖩"
@classmethod
def INPUT_TYPES(cls):
@@ -27,15 +28,11 @@ class DreamCalculation:
}
}
CATEGORY = NodeCategories.ANIMATION_CURVES
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()
+137 -30
View File
@@ -1,7 +1,8 @@
# -*- coding: utf-8 -*-
from .categories import NodeCategories
from .shared import *
from .types import *
from .dreamtypes import *
class DreamImageAreaSampler:
@@ -25,10 +26,6 @@ class DreamImageAreaSampler:
RETURN_NAMES = ("palette",)
FUNCTION = "result"
@classmethod
def IS_CHANGED(cls, *values):
return ALWAYS_CHANGED_FLAG
def _get_pixel_area(self, img: DreamImage, area):
w = img.width
h = img.height
@@ -101,10 +98,6 @@ class DreamImageSampler:
RETURN_NAMES = ("palette",)
FUNCTION = "result"
@classmethod
def IS_CHANGED(cls, *values):
return ALWAYS_CHANGED_FLAG
def result(self, image, samples, seed):
result = list()
r = random.Random()
@@ -128,7 +121,7 @@ class DreamColorAlign:
def INPUT_TYPES(cls):
return {
"required": SharedTypes.palette | {
"target_align": (RGBPalette.ID, ),
"target_align": (RGBPalette.ID,),
"alignment_factor": ("FLOAT", {"default": 0.5, "min": 0.0, "max": 10.0, "step": 0.1}),
}
}
@@ -138,10 +131,6 @@ class DreamColorAlign:
RETURN_NAMES = ("palette",)
FUNCTION = "result"
@classmethod
def IS_CHANGED(cls, *values):
return ALWAYS_CHANGED_FLAG
def result(self, palette: Tuple[RGBPalette], target_align: Tuple[RGBPalette], alignment_factor: float):
results = list()
@@ -151,8 +140,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
@@ -186,10 +175,6 @@ class DreamColorShift:
RETURN_NAMES = ("palette",)
FUNCTION = "result"
@classmethod
def IS_CHANGED(cls, *values):
return ALWAYS_CHANGED_FLAG
def result(self, palette, red_multiplier, green_multiplier, blue_multiplier, fixed_brightness):
results = list()
@@ -214,9 +199,134 @@ 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"
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"
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"
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"
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"
NODE = "📊"
ICON = "📊"
@classmethod
def INPUT_TYPES(cls):
@@ -226,22 +336,19 @@ class DreamAnalyzePalette:
}
CATEGORY = NodeCategories.IMAGE_COLORS
RETURN_TYPES = ("FLOAT", "FLOAT", "FLOAT", "FLOAT")
RETURN_NAMES = ("brightness", "redness", "greenness", "blueness")
RETURN_TYPES = ("FLOAT", "FLOAT", "FLOAT", "FLOAT", "FLOAT")
RETURN_NAMES = ("brightness", "contrast", "redness", "greenness", "blueness")
FUNCTION = "result"
@classmethod
def IS_CHANGED(cls, *values):
return ALWAYS_CHANGED_FLAG
def result(self, palette):
f = 1.0 / len(palette)
(w, r, g, b) = (0, 0, 0, 0)
(w, c, r, g, b) = (0, 0, 0, 0, 0)
for p in palette:
(brightness, red, green, blue) = p.analyze()
(brightness, contrast, red, green, blue) = p.analyze()
w += brightness
c += contrast
r += red
g += green
b += blue
return w * f, r * f, g * f, b * f
return w * f, c * f, r * f, g * f, b * f
+68 -40
View File
@@ -1,10 +1,14 @@
# -*- coding: utf-8 -*-
import csv
import functools
import math
import os
from scipy.io.wavfile import read as wav_read
from .categories import NodeCategories
from .shared import hashed_as_strings
from .types import SharedTypes, FrameCounter
from .dreamtypes import SharedTypes, FrameCounter
def _linear_value_calc(x, x_start, x_end, y_start, y_end):
@@ -40,10 +44,6 @@ class DreamSineWave:
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
a = (max_value - min_value) * 0.5
@@ -73,10 +73,6 @@ class DreamSawWave:
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
@@ -103,10 +99,6 @@ class DreamTriangleWave:
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
@@ -119,6 +111,66 @@ class DreamTriangleWave:
return _curve_result(y)
class WavData:
def __init__(self, sampling_rate: float, single_channel_samples, fps: float):
self._length_in_seconds = len(single_channel_samples) / sampling_rate
self._num_buckets = round(self._length_in_seconds * fps * 3)
self._bucket_size = len(single_channel_samples) / float(self._num_buckets)
self._buckets = list()
self._rate = sampling_rate
self._max_bucket_value = 0
for i in range(self._num_buckets):
start_index = round(i * self._bucket_size)
end_index = round((i + 1) * self._bucket_size) - 1
samples = list(map(lambda n: abs(n), single_channel_samples[start_index:end_index]))
bucket_total = sum(samples)
self._buckets.append(bucket_total)
self._max_bucket_value=max(bucket_total, self._max_bucket_value)
for i in range(self._num_buckets):
self._buckets[i] = float(self._buckets[i]) / self._max_bucket_value
def value_at_time(self, second: float) -> float:
if second < 0.0 or second > self._length_in_seconds:
return 0.0
nsample = second * self._rate
nbucket = min(max(0, round(nsample / self._bucket_size)), self._num_buckets - 1)
return self._buckets[nbucket]
@functools.lru_cache(4)
def _wav_loader(filepath, fps):
sampling_rate, samples = wav_read(filepath)
single_channel = samples[:, 0]
return WavData(sampling_rate, single_channel, fps)
class DreamWavCurve:
NODE_NAME = "WAV Curve"
CATEGORY = NodeCategories.ANIMATION_CURVES
RETURN_TYPES = ("FLOAT", "INT")
RETURN_NAMES = ("FLOAT", "INT")
FUNCTION = "result"
ICON = "∿"
@classmethod
def INPUT_TYPES(cls):
return {
"required": SharedTypes.frame_counter | {
"wav_path": ("STRING", {"default": "audio.wav"}),
"scale": ("FLOAT", {"default": 1.0, "multiline": False})
},
}
def result(self, frame_counter: FrameCounter, wav_path, scale):
if not os.path.isfile(wav_path):
return (0.0, 0)
data = _wav_loader(wav_path, frame_counter.frames_per_second)
frame_counter.current_time_in_seconds
v = data.value_at_time(frame_counter.current_time_in_seconds)
return (v * scale, round(v * scale))
class DreamTriangleEvent:
NODE_NAME = "Triangle Event Curve"
@@ -138,10 +190,6 @@ class DreamTriangleEvent:
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
@@ -174,10 +222,6 @@ class DreamSmoothEvent:
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
@@ -213,9 +257,9 @@ class DreamBeatCurve:
"accent_1": ("INT", {"default": 1, "min": 1, "max": 24}),
},
"optional": {
"accent_2": ("INT", {"default": 3, "min": 1, "max": 24}),
"accent_3": ("INT", {"default": 0}),
"accent_4": ("INT", {"default": 0}),
"accent_2": ("INT", {"default": 0, "min": 0, "max": 24}),
"accent_3": ("INT", {"default": 0, "min": 0, "max": 24}),
"accent_4": ("INT", {"default": 0, "min": 0, "max": 24}),
}
}
@@ -224,10 +268,6 @@ class DreamBeatCurve:
RETURN_NAMES = ("FLOAT", "INT")
FUNCTION = "result"
@classmethod
def IS_CHANGED(cls, *values):
return hashed_as_strings(*values)
def _get_value_for_accent(self, accent, measure_length, bpm, frame_counter: FrameCounter, frame_offset):
current_frame = frame_counter.current_frame + frame_offset
frames_per_minute = frame_counter.frames_per_second * 60.0
@@ -272,10 +312,6 @@ class DreamLinear:
RETURN_NAMES = ("FLOAT", "INT")
FUNCTION = "result"
@classmethod
def IS_CHANGED(cls, *values):
return hashed_as_strings(*values)
def result(self, initial_value, final_value, frame_counter: FrameCounter):
d = final_value - initial_value
v = initial_value + frame_counter.progress * d
@@ -310,10 +346,6 @@ class DreamCSVGenerator:
FUNCTION = "write"
OUTPUT_NODE = True
@classmethod
def IS_CHANGED(cls, *values):
return hashed_as_strings(*values)
def write(self, csvfile, frame_counter: FrameCounter, value, csv_dialect):
if frame_counter.is_first_frame and csvfile:
with open(csvfile, 'w', newline='') as csvfile:
@@ -346,10 +378,6 @@ class DreamCSVCurve:
RETURN_NAMES = ("FLOAT", "INT")
FUNCTION = "result"
@classmethod
def IS_CHANGED(cls, *values):
return hashed_as_strings(*values)
def _row_yield(self, file, csv_dialect):
prev_row = None
for row in csv.reader(file, dialect=csv_dialect):
+55 -3
View File
@@ -2,7 +2,7 @@
import random
import time
from typing import List, Dict
from typing import List, Dict, Tuple
from .shared import DreamImage
@@ -26,6 +26,24 @@ 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
@@ -38,7 +56,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, r, g, b)
return ((r + g + b) / 3.0, self._calculate_combined_contrast(), r, g, b)
def __len__(self):
return len(self._colors)
@@ -73,7 +91,12 @@ class PartialPrompt:
def add(self, text: str, weight: float):
output = PartialPrompt()
output._data = dict(self._data)
output._data[text.strip()] = weight
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):
@@ -110,6 +133,35 @@ class PartialPrompt:
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"
+2 -2
View File
@@ -3,7 +3,7 @@ EMBEDDED_CONFIGURATION = {
"file_extension": "mp4",
"path": "ffmpeg",
"arguments": ["-r", "%FPS%", "-f", "concat", "-safe", "0", "-vsync",
"cfr", "-i", "%FRAMES%", "-c:v", "libx265", "-pix_fmt",
"cfr", "-i", "%FRAMES%", "-c:v", "libx264", "-pix_fmt",
"yuv420p", "%OUTPUT%"]
},
"mpeg_coder": {
@@ -11,7 +11,7 @@ EMBEDDED_CONFIGURATION = {
"bitrate_factor": 1.0,
"max_b_frame": 2,
"file_extension": "mp4",
"codec_name": "libx265"
"codec_name": "libx264"
},
"encoding": {
"jpeg_quality": 95
File diff suppressed because it is too large Load Diff
+64 -139
View File
@@ -1,6 +1,6 @@
{
"last_node_id": 422,
"last_link_id": 5466,
"last_node_id": 423,
"last_link_id": 5467,
"nodes": [
{
"id": 77,
@@ -24,13 +24,7 @@
2312
],
"widget": {
"name": "text_2",
"config": [
"STRING",
{
"multiline": true
}
]
"name": "text"
}
}
],
@@ -65,13 +59,7 @@
],
"slot_index": 0,
"widget": {
"name": "text",
"config": [
"STRING",
{
"multiline": true
}
]
"name": "text"
}
}
],
@@ -1101,15 +1089,7 @@
"type": "INT",
"link": 5351,
"widget": {
"name": "total_frames",
"config": [
"INT",
{
"default": 100,
"min": 2,
"max": 5184000
}
]
"name": "total_frames"
},
"slot_index": 0
},
@@ -1118,14 +1098,7 @@
"type": "STRING",
"link": 5347,
"widget": {
"name": "directory_path",
"config": [
"STRING",
{
"default": "",
"multiline": false
}
]
"name": "directory_path"
}
},
{
@@ -1133,14 +1106,7 @@
"type": "INT",
"link": 5350,
"widget": {
"name": "frames_per_second",
"config": [
"INT",
{
"min": 1,
"default": 25
}
]
"name": "frames_per_second"
}
}
],
@@ -1503,13 +1469,7 @@
"type": "STRING",
"link": 2312,
"widget": {
"name": "text",
"config": [
"STRING",
{
"multiline": true
}
]
"name": "text"
},
"slot_index": 1
}
@@ -1560,13 +1520,7 @@
"type": "STRING",
"link": 2309,
"widget": {
"name": "text",
"config": [
"STRING",
{
"multiline": true
}
]
"name": "text"
}
}
],
@@ -1995,16 +1949,7 @@
"type": "INT",
"link": 5074,
"widget": {
"name": "width",
"config": [
"INT",
{
"default": 512,
"min": 1,
"max": 8192,
"step": 1
}
]
"name": "width"
}
},
{
@@ -2012,16 +1957,7 @@
"type": "INT",
"link": 5075,
"widget": {
"name": "height",
"config": [
"INT",
{
"default": 512,
"min": 1,
"max": 8192,
"step": 1
}
]
"name": "height"
}
}
],
@@ -2462,16 +2398,7 @@
"type": "FLOAT",
"link": 5222,
"widget": {
"name": "x_translation",
"config": [
"FLOAT",
{
"default": 0,
"min": -10,
"max": 10,
"step": 0.01
}
]
"name": "x_translation"
}
},
{
@@ -2479,16 +2406,7 @@
"type": "FLOAT",
"link": 5223,
"widget": {
"name": "y_translation",
"config": [
"FLOAT",
{
"default": 0,
"min": -10,
"max": 10,
"step": 0.01
}
]
"name": "y_translation"
}
},
{
@@ -2496,16 +2414,7 @@
"type": "FLOAT",
"link": 5225,
"widget": {
"name": "zoom",
"config": [
"FLOAT",
{
"default": 0,
"min": -10,
"max": 10,
"step": 0.01
}
]
"name": "zoom"
}
}
],
@@ -2707,14 +2616,7 @@
"type": "STRING",
"link": 5442,
"widget": {
"name": "directory_path",
"config": [
"STRING",
{
"default": "I:\\AI\\ComfyUI\\ComfyUI\\output",
"multiline": false
}
]
"name": "directory_path"
},
"slot_index": 2
}
@@ -2728,6 +2630,12 @@
],
"shape": 3,
"slot_index": 0
},
{
"name": "log_entry",
"type": "LOG_ENTRY",
"links": null,
"shape": 3
}
],
"properties": {
@@ -3206,14 +3114,7 @@
"type": "STRING",
"link": 5348,
"widget": {
"name": "directory_path",
"config": [
"STRING",
{
"default": "",
"multiline": false
}
]
"name": "directory_path"
},
"slot_index": 2
}
@@ -3227,6 +3128,12 @@
],
"shape": 3,
"slot_index": 0
},
{
"name": "frame_name",
"type": "STRING",
"links": null,
"shape": 3
}
],
"properties": {
@@ -4006,7 +3913,7 @@
"name": "sequence",
"type": "ANIMATION_SEQUENCE",
"links": [
5466
5467
],
"shape": 3,
"slot_index": 0
@@ -4022,11 +3929,11 @@
"bgcolor": "#593930"
},
{
"id": 422,
"type": "Video Encoder (mpegCoder) [Dream]",
"id": 423,
"type": "FFMPEG Video Encoder [Dream]",
"pos": [
4785,
561
557
],
"size": {
"0": 315,
@@ -4039,11 +3946,19 @@
{
"name": "sequence",
"type": "ANIMATION_SEQUENCE",
"link": 5466
"link": 5467
}
],
"outputs": [
{
"name": "log_entry",
"type": "LOG_ENTRY",
"links": null,
"shape": 3
}
],
"properties": {
"Node name for S&R": "Video Encoder (mpegCoder) [Dream]"
"Node name for S&R": "FFMPEG Video Encoder [Dream]"
},
"widgets_values": [
"video",
@@ -4942,10 +4857,10 @@
"*"
],
[
5466,
5467,
281,
0,
422,
423,
0,
"ANIMATION_SEQUENCE"
]
@@ -4959,7 +4874,8 @@
474,
885
],
"color": "#a1309b"
"color": "#a1309b",
"font_size": 24
},
{
"title": "Settings",
@@ -4969,7 +4885,8 @@
482,
742
],
"color": "#b58b2a"
"color": "#b58b2a",
"font_size": 24
},
{
"title": "Inpainting/Outpainting",
@@ -4979,7 +4896,8 @@
1711,
528
],
"color": "#929054"
"color": "#929054",
"font_size": 24
},
{
"title": "Prev Frame Move",
@@ -4989,7 +4907,8 @@
1450,
456
],
"color": "#3f789e"
"color": "#3f789e",
"font_size": 24
},
{
"title": "Full frame sampler",
@@ -4999,7 +4918,8 @@
691,
700
],
"color": "#88A"
"color": "#88A",
"font_size": 24
},
{
"title": "Output",
@@ -5009,7 +4929,8 @@
1458,
381
],
"color": "#b06634"
"color": "#b06634",
"font_size": 24
},
{
"title": "Animation Driver",
@@ -5019,7 +4940,8 @@
336,
373
],
"color": "#3f789e"
"color": "#3f789e",
"font_size": 24
},
{
"title": "Motion Control",
@@ -5029,7 +4951,8 @@
1451,
392
],
"color": "#ef75ff"
"color": "#ef75ff",
"font_size": 24
},
{
"title": "Model selection",
@@ -5039,7 +4962,8 @@
476,
204
],
"color": "#3f789e"
"color": "#3f789e",
"font_size": 24
},
{
"title": "Previews",
@@ -5049,7 +4973,8 @@
6473,
329
],
"color": "#444"
"color": "#444",
"font_size": 24
}
],
"config": {},
+162 -281
View File
@@ -1,6 +1,6 @@
{
"last_node_id": 421,
"last_link_id": 5462,
"last_node_id": 422,
"last_link_id": 5463,
"nodes": [
{
"id": 77,
@@ -24,13 +24,7 @@
2312
],
"widget": {
"name": "text_2",
"config": [
"STRING",
{
"multiline": true
}
]
"name": "text"
}
}
],
@@ -65,13 +59,7 @@
],
"slot_index": 0,
"widget": {
"name": "text",
"config": [
"STRING",
{
"multiline": true
}
]
"name": "text"
}
}
],
@@ -327,7 +315,7 @@
26
],
"flags": {},
"order": 24,
"order": 23,
"mode": 0,
"inputs": [
{
@@ -365,7 +353,7 @@
"1": 130
},
"flags": {},
"order": 49,
"order": 54,
"mode": 0,
"inputs": [
{
@@ -390,7 +378,7 @@
},
"widgets_values": [
256,
183250672320803,
984424700736240,
"randomize",
"center-right"
],
@@ -409,7 +397,7 @@
"1": 130
},
"flags": {},
"order": 50,
"order": 55,
"mode": 0,
"inputs": [
{
@@ -434,7 +422,7 @@
},
"widgets_values": [
256,
57339603779837,
393866830673316,
"randomize",
"bottom-center"
],
@@ -709,7 +697,7 @@
},
"widgets_values": [
256,
902082438494247,
912873459964733,
"randomize",
"bottom-center"
],
@@ -753,7 +741,7 @@
},
"widgets_values": [
256,
638849047502927,
257321647530942,
"randomize",
"center-right"
],
@@ -790,16 +778,7 @@
"type": "FLOAT",
"link": 5334,
"widget": {
"name": "alignment_factor",
"config": [
"FLOAT",
{
"default": 0.5,
"min": 0,
"max": 10,
"step": 0.1
}
]
"name": "alignment_factor"
}
}
],
@@ -818,7 +797,7 @@
"Node name for S&R": "Palette Color Align [Dream]"
},
"widgets_values": [
1.2
1.2000000000000002
],
"color": "#323",
"bgcolor": "#535"
@@ -853,16 +832,7 @@
"type": "FLOAT",
"link": 5325,
"widget": {
"name": "alignment_factor",
"config": [
"FLOAT",
{
"default": 0.5,
"min": 0,
"max": 10,
"step": 0.1
}
]
"name": "alignment_factor"
},
"slot_index": 2
}
@@ -882,7 +852,7 @@
"Node name for S&R": "Palette Color Align [Dream]"
},
"widgets_values": [
1.2
1.2000000000000002
],
"color": "#322",
"bgcolor": "#533"
@@ -917,16 +887,7 @@
"type": "FLOAT",
"link": 5333,
"widget": {
"name": "alignment_factor",
"config": [
"FLOAT",
{
"default": 0.5,
"min": 0,
"max": 10,
"step": 0.1
}
]
"name": "alignment_factor"
}
}
],
@@ -945,7 +906,7 @@
"Node name for S&R": "Palette Color Align [Dream]"
},
"widgets_values": [
1.2
1.2000000000000002
],
"color": "#223",
"bgcolor": "#335"
@@ -980,16 +941,7 @@
"type": "FLOAT",
"link": 5328,
"widget": {
"name": "alignment_factor",
"config": [
"FLOAT",
{
"default": 0.5,
"min": 0,
"max": 10,
"step": 0.1
}
]
"name": "alignment_factor"
}
}
],
@@ -1008,7 +960,7 @@
"Node name for S&R": "Palette Color Align [Dream]"
},
"widgets_values": [
1.2
1.2000000000000002
],
"color": "#432",
"bgcolor": "#653"
@@ -1209,7 +1161,7 @@
"Node name for S&R": "KSampler"
},
"widgets_values": [
586618692559734,
838611060522510,
"randomize",
25,
9,
@@ -1530,15 +1482,7 @@
"type": "INT",
"link": 5351,
"widget": {
"name": "total_frames",
"config": [
"INT",
{
"default": 100,
"min": 2,
"max": 5184000
}
]
"name": "total_frames"
},
"slot_index": 0
},
@@ -1547,14 +1491,7 @@
"type": "STRING",
"link": 5347,
"widget": {
"name": "directory_path",
"config": [
"STRING",
{
"default": "",
"multiline": false
}
]
"name": "directory_path"
}
},
{
@@ -1562,14 +1499,7 @@
"type": "INT",
"link": 5350,
"widget": {
"name": "frames_per_second",
"config": [
"INT",
{
"min": 1,
"default": 25
}
]
"name": "frames_per_second"
}
}
],
@@ -1638,47 +1568,6 @@
"color": "#432",
"bgcolor": "#653"
},
{
"id": 236,
"type": "LoadImage",
"pos": [
-2106,
679
],
"size": {
"0": 430,
"1": 340
},
"flags": {},
"order": 12,
"mode": 0,
"outputs": [
{
"name": "IMAGE",
"type": "IMAGE",
"links": [
5313
],
"shape": 3,
"slot_index": 0
},
{
"name": "MASK",
"type": "MASK",
"links": null,
"shape": 3
}
],
"properties": {
"Node name for S&R": "LoadImage"
},
"widgets_values": [
"after (3).jpg",
"image"
],
"color": "#2a363b",
"bgcolor": "#3f5159"
},
{
"id": 373,
"type": "Reroute",
@@ -1691,7 +1580,7 @@
26
],
"flags": {},
"order": 25,
"order": 24,
"mode": 0,
"inputs": [
{
@@ -1878,7 +1767,7 @@
"1": 82
},
"flags": {},
"order": 13,
"order": 12,
"mode": 0,
"outputs": [
{
@@ -1892,16 +1781,7 @@
],
"slot_index": 0,
"widget": {
"name": "alignment_factor",
"config": [
"FLOAT",
{
"default": 0.5,
"min": 0,
"max": 10,
"step": 0.1
}
]
"name": "alignment_factor"
}
}
],
@@ -1965,7 +1845,7 @@
"1": 130
},
"flags": {},
"order": 48,
"order": 53,
"mode": 0,
"inputs": [
{
@@ -1990,7 +1870,7 @@
},
"widgets_values": [
256,
1019724837772866,
919440470904266,
"randomize",
"center-left"
],
@@ -2034,7 +1914,7 @@
},
"widgets_values": [
256,
1014748475294523,
528123820881935,
"randomize",
"center-left"
],
@@ -2055,7 +1935,7 @@
"flags": {
"collapsed": true
},
"order": 34,
"order": 32,
"mode": 0,
"inputs": [
{
@@ -2068,13 +1948,7 @@
"type": "STRING",
"link": 2312,
"widget": {
"name": "text",
"config": [
"STRING",
{
"multiline": true
}
]
"name": "text"
},
"slot_index": 1
}
@@ -2112,7 +1986,7 @@
"flags": {
"collapsed": true
},
"order": 33,
"order": 31,
"mode": 0,
"inputs": [
{
@@ -2125,13 +1999,7 @@
"type": "STRING",
"link": 2309,
"widget": {
"name": "text",
"config": [
"STRING",
{
"multiline": true
}
]
"name": "text"
}
}
],
@@ -2166,7 +2034,7 @@
26
],
"flags": {},
"order": 41,
"order": 40,
"mode": 0,
"inputs": [
{
@@ -2316,7 +2184,7 @@
26
],
"flags": {},
"order": 42,
"order": 41,
"mode": 0,
"inputs": [
{
@@ -2353,7 +2221,7 @@
26
],
"flags": {},
"order": 23,
"order": 22,
"mode": 0,
"inputs": [
{
@@ -2389,7 +2257,7 @@
26
],
"flags": {},
"order": 26,
"order": 25,
"mode": 0,
"inputs": [
{
@@ -2464,7 +2332,7 @@
"Node name for S&R": "KSampler"
},
"widgets_values": [
55143763493153,
700759510514483,
"randomize",
5,
5,
@@ -2523,7 +2391,7 @@
"1": 60
},
"flags": {},
"order": 14,
"order": 13,
"mode": 0,
"title": "Note on full frame sampler",
"properties": {
@@ -2547,7 +2415,7 @@
26
],
"flags": {},
"order": 39,
"order": 44,
"mode": 0,
"inputs": [
{
@@ -2667,7 +2535,7 @@
26
],
"flags": {},
"order": 30,
"order": 35,
"mode": 0,
"inputs": [
{
@@ -2707,7 +2575,7 @@
"1": 270
},
"flags": {},
"order": 15,
"order": 14,
"mode": 0,
"title": "Note on noise",
"properties": {
@@ -2780,16 +2648,7 @@
"type": "INT",
"link": 5074,
"widget": {
"name": "width",
"config": [
"INT",
{
"default": 512,
"min": 1,
"max": 8192,
"step": 1
}
]
"name": "width"
}
},
{
@@ -2797,16 +2656,7 @@
"type": "INT",
"link": 5075,
"widget": {
"name": "height",
"config": [
"INT",
{
"default": 512,
"min": 1,
"max": 8192,
"step": 1
}
]
"name": "height"
}
}
],
@@ -2844,7 +2694,7 @@
"1": 130
},
"flags": {},
"order": 47,
"order": 52,
"mode": 0,
"inputs": [
{
@@ -2869,7 +2719,7 @@
},
"widgets_values": [
256,
309821851176166,
502577689536295,
"randomize",
"top-center"
],
@@ -2913,7 +2763,7 @@
},
"widgets_values": [
256,
973129878717336,
1125637252645999,
"randomize",
"top-center"
],
@@ -2934,7 +2784,7 @@
"flags": {
"collapsed": false
},
"order": 16,
"order": 15,
"mode": 0,
"outputs": [
{
@@ -3007,7 +2857,7 @@
26
],
"flags": {},
"order": 55,
"order": 51,
"mode": 0,
"inputs": [
{
@@ -3083,7 +2933,7 @@
26
],
"flags": {},
"order": 44,
"order": 43,
"mode": 0,
"inputs": [
{
@@ -3119,7 +2969,7 @@
26
],
"flags": {},
"order": 35,
"order": 33,
"mode": 0,
"inputs": [
{
@@ -3156,7 +3006,7 @@
26
],
"flags": {},
"order": 53,
"order": 49,
"mode": 0,
"inputs": [
{
@@ -3196,7 +3046,7 @@
26
],
"flags": {},
"order": 52,
"order": 48,
"mode": 0,
"inputs": [
{
@@ -3335,16 +3185,7 @@
"type": "FLOAT",
"link": 5222,
"widget": {
"name": "x_translation",
"config": [
"FLOAT",
{
"default": 0,
"min": -10,
"max": 10,
"step": 0.01
}
]
"name": "x_translation"
}
},
{
@@ -3352,16 +3193,7 @@
"type": "FLOAT",
"link": 5223,
"widget": {
"name": "y_translation",
"config": [
"FLOAT",
{
"default": 0,
"min": -10,
"max": 10,
"step": 0.01
}
]
"name": "y_translation"
}
},
{
@@ -3369,16 +3201,7 @@
"type": "FLOAT",
"link": 5225,
"widget": {
"name": "zoom",
"config": [
"FLOAT",
{
"default": 0,
"min": -10,
"max": 10,
"step": 0.01
}
]
"name": "zoom"
}
}
],
@@ -3580,14 +3403,7 @@
"type": "STRING",
"link": 5442,
"widget": {
"name": "directory_path",
"config": [
"STRING",
{
"default": "I:\\AI\\ComfyUI\\ComfyUI\\output",
"multiline": false
}
]
"name": "directory_path"
},
"slot_index": 2
}
@@ -3601,6 +3417,12 @@
],
"shape": 3,
"slot_index": 0
},
{
"name": "log_entry",
"type": "LOG_ENTRY",
"links": null,
"shape": 3
}
],
"properties": {
@@ -3664,7 +3486,7 @@
26
],
"flags": {},
"order": 51,
"order": 47,
"mode": 0,
"inputs": [
{
@@ -3700,7 +3522,7 @@
26
],
"flags": {},
"order": 40,
"order": 39,
"mode": 0,
"inputs": [
{
@@ -3737,7 +3559,7 @@
26
],
"flags": {},
"order": 32,
"order": 30,
"mode": 0,
"inputs": [
{
@@ -3773,7 +3595,7 @@
26
],
"flags": {},
"order": 43,
"order": 42,
"mode": 0,
"inputs": [
{
@@ -3809,7 +3631,7 @@
26
],
"flags": {},
"order": 54,
"order": 50,
"mode": 0,
"inputs": [
{
@@ -4079,14 +3901,7 @@
"type": "STRING",
"link": 5348,
"widget": {
"name": "directory_path",
"config": [
"STRING",
{
"default": "",
"multiline": false
}
]
"name": "directory_path"
},
"slot_index": 2
}
@@ -4100,6 +3915,12 @@
],
"shape": 3,
"slot_index": 0
},
{
"name": "frame_name",
"type": "STRING",
"links": null,
"shape": 3
}
],
"properties": {
@@ -4123,7 +3944,7 @@
26
],
"flags": {},
"order": 31,
"order": 36,
"mode": 0,
"inputs": [
{
@@ -4306,7 +4127,7 @@
256,
0.11818237304687501,
0.5,
340337213218643,
855083604429355,
"randomize"
],
"color": "#2a363b",
@@ -4443,7 +4264,7 @@
"flags": {
"collapsed": false
},
"order": 17,
"order": 16,
"mode": 0,
"inputs": [],
"outputs": [
@@ -4493,7 +4314,7 @@
26
],
"flags": {},
"order": 27,
"order": 26,
"mode": 0,
"inputs": [
{
@@ -4529,7 +4350,7 @@
26
],
"flags": {},
"order": 22,
"order": 27,
"mode": 0,
"inputs": [
{
@@ -4607,7 +4428,7 @@
"1": 90
},
"flags": {},
"order": 18,
"order": 17,
"mode": 0,
"title": "Note on motion",
"properties": {
@@ -4705,7 +4526,7 @@
26
],
"flags": {},
"order": 36,
"order": 34,
"mode": 0,
"inputs": [
{
@@ -5031,7 +4852,7 @@
"name": "sequence",
"type": "ANIMATION_SEQUENCE",
"links": [
5462
5463
],
"shape": 3,
"slot_index": 0
@@ -5047,11 +4868,11 @@
"bgcolor": "#593930"
},
{
"id": 421,
"type": "Video Encoder (mpegCoder) [Dream]",
"id": 422,
"type": "FFMPEG Video Encoder [Dream]",
"pos": [
4778,
559
4781,
562
],
"size": {
"0": 315,
@@ -5064,17 +4885,66 @@
{
"name": "sequence",
"type": "ANIMATION_SEQUENCE",
"link": 5462
"link": 5463
}
],
"outputs": [
{
"name": "log_entry",
"type": "LOG_ENTRY",
"links": null,
"shape": 3
}
],
"properties": {
"Node name for S&R": "Video Encoder (mpegCoder) [Dream]"
"Node name for S&R": "FFMPEG Video Encoder [Dream]"
},
"widgets_values": [
"video",
1,
true
]
},
{
"id": 236,
"type": "LoadImage",
"pos": [
-2106,
679
],
"size": {
"0": 430,
"1": 340
},
"flags": {},
"order": 18,
"mode": 0,
"outputs": [
{
"name": "IMAGE",
"type": "IMAGE",
"links": [
5313
],
"shape": 3,
"slot_index": 0
},
{
"name": "MASK",
"type": "MASK",
"links": null,
"shape": 3
}
],
"properties": {
"Node name for S&R": "LoadImage"
},
"widgets_values": [
"7159726-HSC00002-7 (1).jpg",
"image"
],
"color": "#2a363b",
"bgcolor": "#3f5159"
}
],
"links": [
@@ -6223,10 +6093,10 @@
"IMAGE"
],
[
5462,
5463,
281,
0,
421,
422,
0,
"ANIMATION_SEQUENCE"
]
@@ -6240,7 +6110,8 @@
474,
885
],
"color": "#a1309b"
"color": "#a1309b",
"font_size": 24
},
{
"title": "Settings",
@@ -6250,7 +6121,8 @@
482,
742
],
"color": "#b58b2a"
"color": "#b58b2a",
"font_size": 24
},
{
"title": "Inpainting/Outpainting",
@@ -6260,7 +6132,8 @@
1711,
528
],
"color": "#929054"
"color": "#929054",
"font_size": 24
},
{
"title": "Prev Frame Move",
@@ -6270,7 +6143,8 @@
1450,
456
],
"color": "#3f789e"
"color": "#3f789e",
"font_size": 24
},
{
"title": "Full frame sampler",
@@ -6280,7 +6154,8 @@
691,
700
],
"color": "#88A"
"color": "#88A",
"font_size": 24
},
{
"title": "Output",
@@ -6290,7 +6165,8 @@
1458,
381
],
"color": "#b06634"
"color": "#b06634",
"font_size": 24
},
{
"title": "Animation Driver",
@@ -6300,7 +6176,8 @@
336,
373
],
"color": "#3f789e"
"color": "#3f789e",
"font_size": 24
},
{
"title": "Motion Control",
@@ -6310,7 +6187,8 @@
1451,
392
],
"color": "#ef75ff"
"color": "#ef75ff",
"font_size": 24
},
{
"title": "Model selection",
@@ -6320,7 +6198,8 @@
476,
204
],
"color": "#3f789e"
"color": "#3f789e",
"font_size": 24
},
{
"title": "Noise",
@@ -6330,7 +6209,8 @@
1840,
765
],
"color": "#3f789e"
"color": "#3f789e",
"font_size": 24
},
{
"title": "Previews",
@@ -6340,7 +6220,8 @@
6473,
329
],
"color": "#444"
"color": "#444",
"font_size": 24
}
],
"config": {},
File diff suppressed because it is too large Load Diff
+5 -11
View File
@@ -4,13 +4,11 @@ import math
import numpy
import torch
from PIL import Image, ImageDraw
from PIL.Image import Resampling
from .categories import *
from .shared import ALWAYS_CHANGED_FLAG, convertTensorImageToPIL, DreamImageProcessor, \
from .shared import convertTensorImageToPIL, DreamImageProcessor, \
DreamImage, DreamMask
from .types import SharedTypes, FrameCounter
from .dreamtypes import SharedTypes, FrameCounter
class DreamImageMotion:
NODE_NAME = "Image Motion"
@@ -42,10 +40,6 @@ class DreamImageMotion:
RETURN_NAMES = ("image", "mask1", "mask2", "mask3")
FUNCTION = "result"
@classmethod
def IS_CHANGED(cls, *values):
return ALWAYS_CHANGED_FLAG
def _mk_PIL_image(self, size, color=None, mode="RGB") -> Image:
im = Image.new(mode=mode, size=size)
if color:
@@ -77,7 +71,7 @@ class DreamImageMotion:
return min(max(i, 1), 32767)
if output_resize_height and output_resize_width:
return lambda img: img.resize((bound(output_resize_width), bound(output_resize_height)), Resampling.NEAREST)
return lambda img: img.resize((bound(output_resize_width), bound(output_resize_height)))
else:
return lambda img: img
@@ -98,12 +92,12 @@ class DreamImageMotion:
noise = other.get("noise", None)
multiplier = math.pow(2, zoom)
resized_image = pil_image.resize((round(pil_image.width * multiplier),
round(pil_image.height * multiplier)), Resampling.BILINEAR)
round(pil_image.height * multiplier)))
if noise is None:
base_image = self._mk_PIL_image(pil_image.size, "black")
else:
base_image = convertTensorImageToPIL(noise).resize(pil_image.size, Resampling.BILINEAR)
base_image = convertTensorImageToPIL(noise).resize(pil_image.size)
selection_offset = (round(x_translation * pil_image.width), round(y_translation * pil_image.height))
selection = ((pil_image.width - resized_image.width) // 2 + selection_offset[0],
-16
View File
@@ -18,10 +18,6 @@ class DreamInputText:
RETURN_NAMES = ("STRING",)
FUNCTION = "noop"
@classmethod
def IS_CHANGED(cls, *values):
return hashed_as_strings(*values)
def noop(self, value):
return (value,)
@@ -42,10 +38,6 @@ class DreamInputString:
RETURN_NAMES = ("STRING",)
FUNCTION = "noop"
@classmethod
def IS_CHANGED(cls, *values):
return hashed_as_strings(*values)
def noop(self, value):
return (value,)
@@ -67,10 +59,6 @@ class DreamInputFloat:
RETURN_NAMES = ("FLOAT",)
FUNCTION = "noop"
@classmethod
def IS_CHANGED(cls, *values):
return hashed_as_strings(*values)
def noop(self, value):
return (value,)
@@ -92,9 +80,5 @@ class DreamInputInt:
RETURN_NAMES = ("INT",)
FUNCTION = "noop"
@classmethod
def IS_CHANGED(cls, *values):
return hashed_as_strings(*values)
def noop(self, value):
return (value,)
+7 -3
View File
@@ -1,10 +1,14 @@
# -*- coding: utf-8 -*-
from .shared import DreamConfig
import sys, os
#sys.path.append(str(os.path.dirname(os.path.abspath(__file__))))
#from . import shared
def setup_default_config():
DreamConfig()
#shared.DreamConfig()
pass
def run_install():
setup_default_config()
+96
View File
@@ -0,0 +1,96 @@
# -*- coding: utf-8 -*-
# -*- coding: utf-8 -*-
import json
from .categories import *
from .shared import DreamStateFile
from .dreamtypes 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"
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)))
+151
View File
@@ -0,0 +1,151 @@
from .categories import NodeCategories
from .dreamtypes import RGBPalette
_NOT_A_VALUE_I = 9223372036854775807
_NOT_A_VALUE_F = float(_NOT_A_VALUE_I)
_NOT_A_VALUE_S = "⭆"
def _generate_switch_input(type_nm: str, default_value=None):
d = dict()
for i in range(10):
if default_value is None:
d["input_" + str(i)] = (type_nm, {"lazy": True})
else:
d["input_" + str(i)] = (type_nm, {"default": default_value, "forceInput": True, "lazy": True})
return {
"required": {
"select": ("INT", {"default": 0, "min": 0, "max": 9})
},
"optional": d
}
def _check_big_switch_lazy_status(*args, **kwargs):
n = int(kwargs['select'])
input_name = f"input_{n}"
print(f"SELECTED: {input_name}")
if input_name in kwargs:
return [input_name]
else:
return []
class DreamLazyImageSwitch:
_switch_type = "IMAGE"
NODE_NAME = "Lazy 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)
def check_lazy_status(self, *args, **kwargs):
return _check_big_switch_lazy_status(*args, **kwargs)
def pick(self, select, **args):
return (args.get("input_"+str(select), None),)
class DreamLazyLatentSwitch:
_switch_type = "LATENT"
NODE_NAME = "Lazy 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)
def check_lazy_status(self, *args, **kwargs):
return _check_big_switch_lazy_status(*args, **kwargs)
def pick(self, select, **args):
return (args.get("input_" + str(select), None),)
class DreamLazyTextSwitch:
_switch_type = "STRING"
NODE_NAME = "Lazy 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, _NOT_A_VALUE_S)
def check_lazy_status(self, *args, **kwargs):
return _check_big_switch_lazy_status(*args, **kwargs)
def pick(self, select, **args):
return (args.get("input_" + str(select), None),)
class DreamLazyPaletteSwitch:
_switch_type = RGBPalette.ID
NODE_NAME = "Lazy 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)
def check_lazy_status(self, *args, **kwargs):
return _check_big_switch_lazy_status(*args, **kwargs)
def pick(self, select, **args):
return (args.get("input_" + str(select), None),)
class DreamLazyFloatSwitch:
_switch_type = "FLOAT"
NODE_NAME = "Lazy 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, _NOT_A_VALUE_F)
def check_lazy_status(self, *args, **kwargs):
return _check_big_switch_lazy_status(*args, **kwargs)
def pick(self, select, **args):
return (args.get("input_" + str(select), None),)
class DreamLazyIntSwitch:
_switch_type = "INT"
NODE_NAME = "Lazy 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, _NOT_A_VALUE_I)
def check_lazy_status(self, *args, **kwargs):
return _check_big_switch_lazy_status(*args, **kwargs)
def pick(self, select, **args):
return (args.get("input_" + str(select), None),)
+9 -7
View File
@@ -1,7 +1,8 @@
# -*- coding: utf-8 -*-
from .categories import NodeCategories
from .shared import ALWAYS_CHANGED_FLAG, list_images_in_directory, DreamImage
from .types import SharedTypes, FrameCounter
from .shared import list_images_in_directory, DreamImage
from .dreamtypes import SharedTypes, FrameCounter
import os
class DreamImageSequenceInputWithDefaultFallback:
@@ -22,13 +23,13 @@ class DreamImageSequenceInputWithDefaultFallback:
}
CATEGORY = NodeCategories.IMAGE_ANIMATION
RETURN_TYPES = ("IMAGE",)
RETURN_NAMES = ("image",)
RETURN_TYPES = ("IMAGE","STRING")
RETURN_NAMES = ("image","frame_name")
FUNCTION = "result"
@classmethod
def IS_CHANGED(cls, *values):
return ALWAYS_CHANGED_FLAG
def IS_CHANGED(cls, *values, **kwargs):
return float("NaN")
def result(self, frame_counter: FrameCounter, directory_path, pattern, indexing, **other):
default_image = other.get("default_image", None)
@@ -37,5 +38,6 @@ class DreamImageSequenceInputWithDefaultFallback:
if not entry:
return (default_image, "")
else:
image_names = [os.path.basename(file_path) for file_path in entry]
images = map(lambda f: DreamImage(file_path=f), entry)
return (DreamImage.join_to_tensor_data(images),)
return (DreamImage.join_to_tensor_data(images), image_names[0])
+16 -3
View File
@@ -1,5 +1,5 @@
{
"Analyze Palette [Dream]": "Output brightness, red, green and blue averages of a palette",
"Analyze Palette [Dream]": "Output brightness, contrast, 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",
@@ -14,34 +14,47 @@
"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",
"Frame Counter Time Offset [Dream]": "Adds an offset to a frame counter in seconds",
"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 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",
"Random Prompt Words [Dream]": "Picks random words from input",
"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"
} and switches.
"WAV Curve [Dream]": "WAV audio file as a curve"
}
+1 -9
View File
@@ -3,7 +3,7 @@ import math
from .categories import NodeCategories
from .shared import *
from .types import *
from .dreamtypes import *
def _generate_noise(image: DreamImage, color_function, rng: random.Random, block_size, blur_amount,
@@ -44,10 +44,6 @@ class DreamNoiseFromPalette:
RETURN_NAMES = ("image",)
FUNCTION = "result"
@classmethod
def IS_CHANGED(cls, *values):
return ALWAYS_CHANGED_FLAG
def result(self, palette: Tuple[RGBPalette], width, height, seed, blur_amount, density):
outputs = list()
rng = random.Random()
@@ -95,10 +91,6 @@ class DreamNoiseFromAreaPalettes:
RETURN_NAMES = ("image",)
FUNCTION = "result"
@classmethod
def IS_CHANGED(cls, *values):
return ALWAYS_CHANGED_FLAG
def _area_coordinates(self, width, height):
dx = width / 6
dy = height / 6
+23 -16
View File
@@ -6,9 +6,9 @@ import folder_paths as comfy_paths
from PIL.PngImagePlugin import PngInfo
from .categories import NodeCategories
from .shared import hashed_as_strings, DreamImageProcessor, DreamImage, \
from .shared import DreamImageProcessor, DreamImage, \
list_images_in_directory, DreamConfig
from .types import SharedTypes, FrameCounter, AnimationSequence
from .dreamtypes import SharedTypes, FrameCounter, AnimationSequence, LogEntry
CONFIG = DreamConfig()
@@ -42,7 +42,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", "keep going"],),
"at_end": (["stop output", "raise error", "keep going"],),
"filetype": (['png with embedded workflow', "png", 'jpg'],),
},
"hidden": {
@@ -52,23 +52,24 @@ class DreamImageSequenceOutput:
}
CATEGORY = NodeCategories.IMAGE_ANIMATION
RETURN_TYPES = (AnimationSequence.ID,)
RETURN_TYPES = (AnimationSequence.ID, LogEntry.ID)
OUTPUT_NODE = True
RETURN_NAMES = ("sequence",)
RETURN_NAMES = ("sequence", "log_entry")
FUNCTION = "save"
@classmethod
def IS_CHANGED(cls, *values):
return hashed_as_strings(*values)
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):
def _save_single_image(self, dream_image: DreamImage, batch_counter, frame_counter: FrameCounter,
directory_path,
prefix, digits, filetype, prompt, extra_pnginfo, at_end, logger):
if at_end == "stop output" and frame_counter.is_after_last_frame:
print("Reached end of animation - not saving output!")
logger("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)
@@ -81,7 +82,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)))
print("Saved {} in {}".format(filename, os.path.abspath(save_dir)))
logger("Saved {} in {}".format(filename, os.path.abspath(save_dir)))
return ()
def _generate_animation_sequence(self, filetype, directory_path, frame_counter):
@@ -93,13 +94,19 @@ 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"]
frame_counter = 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),)
frame_counter), log_entry)
else:
return (AnimationSequence(frame_counter),)
return (AnimationSequence(frame_counter), log_entry)
+40 -9
View File
@@ -1,6 +1,44 @@
from .categories import NodeCategories
from .shared import hashed_as_strings
from .types import PartialPrompt
from .dreamtypes import PartialPrompt
import random
class DreamRandomPromptWords:
NODE_NAME = "Random Prompt Words"
ICON = "⚅"
@classmethod
def INPUT_TYPES(cls):
return {
"optional": {
"partial_prompt": (PartialPrompt.ID,)
},
"required": {
"words": ("STRING", {"default": "", "multiline": True}),
"separator": ("STRING", {"default": ",", "multiline": False}),
"samples": ("INT", {"default": 1, "min": 1, "max": 100}),
"min_weight": ("FLOAT", {"default": 1.0, "min": -10, "max": 10}),
"max_weight": ("FLOAT", {"default": 1.0, "min": -10, "max": 10}),
"seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff}),
},
}
CATEGORY = NodeCategories.CONDITIONING
RETURN_TYPES = (PartialPrompt.ID,)
RETURN_NAMES = ("partial_prompt",)
FUNCTION = "result"
def result(self, words: str, separator, samples, min_weight, max_weight, seed, **args):
p = args.get("partial_prompt", PartialPrompt())
rnd = random.Random()
rnd.seed(seed)
words = list(set(map(lambda s: s.strip(), filter(lambda s: s.strip() != "", words.split(separator)))))
samples = min(samples, len(words))
for i in range(samples):
picked_word = words[rnd.randint(0, len(words)-1)]
words = list(filter(lambda s: s!=picked_word, words))
weight = rnd.uniform(min_weight, max_weight)
p = p.add(picked_word, weight)
return (p,)
class DreamWeightedPromptBuilder:
@@ -24,10 +62,6 @@ class DreamWeightedPromptBuilder:
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)
@@ -54,9 +88,6 @@ class DreamPromptFinalizer:
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():
+15
View File
@@ -0,0 +1,15 @@
[project]
name = "comfyui-dream-project"
description = "This extension offers various nodes that are useful for Deforum-like animations in ComfyUI."
version = "5.1.2"
license = { text = "MIT License" }
dependencies = ["imageio", "pilgram", "scipy", "numpy<2.0,>=1.18", "torchvision", "evalidate"]
[project.urls]
Repository = "https://github.com/alt-key-project/comfyui-dream-project"
# Used by Comfy Registry https://comfyregistry.org
[tool.comfy]
PublisherId = "altkeyproject"
DisplayName = "comfyui-dream-project"
Icon = ""
+64 -4
View File
@@ -7,6 +7,17 @@ and useful to many ComfyUI users.
I have demonstrated the use of these custom nodes in this [youtube video](https://youtu.be/pZ6Li3qF-Kk).
# Notice!
This custom node pack is currently not being updated. Stable Diffusion video generation is moving towards a different
workflow with AnimateDiff and Stable Video Diffusion. I decided to not try to update this node pack, but I am instead
creating a separate custom node pack here:
[github](https://github.com/alt-key-project/comfyui-dream-video-batches)
This new node pack will be getting my attention from now on (at least as long as stable diffusion video generation is done mostly
in batches).
## Installation
### Simple option
@@ -147,6 +158,9 @@ Mathematical calculation node. Exposes most of the mathematical functions in the
[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,12 +174,12 @@ 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.
### 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.
@@ -180,7 +194,19 @@ counter.
Extracts information from the frame counter.
### Frame Counter Offset [Dream]
Adds an offset to a frame counter.
Adds an offset (in frames) to a frame counter.
### Frame Counter Time Offset [Dream]
Adds an offset in seconds 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.
@@ -197,6 +223,19 @@ 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.
### Lazy *** Switch [Dream]
Switch nodes for different type for up to ten inputs. The lazy version only evaluates the
selected input, but first/next of the big switch is unsupported.
### 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.
@@ -231,12 +270,21 @@ 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.
### Random Prompt Words [Dream]
Randomly picks words/tokens/phrases from an input text.
### Triangle Curve [Dream]
Triangle wave curve.
### Triangle Event Curve [Dream]
Single event/peak curve with triangular shape.
### WAV Curve [Dream]
Use an uncompressed WAV audio file as a curve.
### Other custom nodes
Many of the nodes found in 'WAS Node Suite' are useful the Dream Project Animation nodes - I suggest you install those
@@ -262,6 +310,18 @@ This flow demonstrates sampling image areas into palettes and generating noise f
[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)
### Laboratory
This flow demonstrates use of the Laboratory and Logging nodes.
[laboratory](examples/laboratory.json)
## Known issues
### FFMPEG
+1 -2
View File
@@ -1,7 +1,6 @@
imageio
pilgram
scipy
numpy<1.24>=1.18
numpy<2.0,>=1.18
torchvision
mpegCoder
evalidate
+76 -69
View File
@@ -5,19 +5,20 @@ import subprocess
import tempfile
from functools import lru_cache
from PIL import Image
from PIL import Image as PilImage
from .categories import NodeCategories
from .err import on_error
from .shared import DreamConfig, MpegEncoderUtility
from .types import *
from .shared import DreamConfig
#from .shared import MpegEncoderUtility
from .dreamtypes import *
CONFIG = DreamConfig()
@lru_cache(5)
def _load_image_cached(filename) -> Image:
return Image.open(filename)
def _load_image_cached(filename):
return PilImage.open(filename)
class TempFileSet:
@@ -131,65 +132,67 @@ 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 = ()
RETURN_NAMES = ()
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 ()
config = DreamConfig()
filename = _make_video_filename(name, config.get("mpeg_coder.file_extension", "mp4"))
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()
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 ()
#
# 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"
@@ -207,8 +210,8 @@ class DreamVideoEncoder:
}
CATEGORY = NodeCategories.ANIMATION_POSTPROCESSING
RETURN_TYPES = ()
RETURN_NAMES = ()
RETURN_TYPES = (LogEntry.ID,)
RETURN_NAMES = ("log_entry",)
OUTPUT_NODE = True
FUNCTION = "encode"
@@ -230,24 +233,28 @@ 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):
if not sequence.is_defined:
return ()
return (LogEntry([]),)
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))
self.generate_video(images, sequence.fps * framerate_factor, filename, config)
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:
for imagepath in images:
if os.path.isfile(imagepath):
os.unlink(imagepath)
except Exception as e:
on_error(self.__class__, str(e))
return ()
return (log_entry,)
class DreamSequenceTweening:
+52 -62
View File
@@ -4,24 +4,25 @@ import hashlib
import json
import os
import random
import time
import folder_paths as comfy_paths
import tempfile
import glob
from io import BytesIO
import numpy
import torch
from PIL import Image, ImageFilter
from PIL import Image, ImageFilter, ImageEnhance
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
from .embedded_config import EMBEDDED_CONFIGURATION
tmpDir = tempfile.TemporaryDirectory("Dream_Anim")
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")
ALWAYS_CHANGED_FLAG = float("NaN")
TEMP_PATH = os.path.join(os.path.abspath(tempfile.gettempdir()), "Dream_Anim")
def convertTensorImageToPIL(tensor_image) -> Image:
@@ -151,6 +152,14 @@ class DreamImage:
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)
@@ -195,6 +204,15 @@ 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:
@@ -257,7 +275,7 @@ def list_images_in_directory(directory_path: str, pattern: str, alphabetic_index
def _num_from_filename(fn):
(text, _) = os.path.splitext(fn)
token: str = text.split("_")[-1]
token = text.split("_")[-1]
if token.isdigit():
return int(token)
else:
@@ -315,13 +333,13 @@ class DreamStateStore:
class DreamStateFile:
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
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)
if not os.path.isdir(self._dirname):
os.makedirs(self._dirname)
if not os.path.isfile(self._filepath):
self._data: dict = {}
self._data = {}
else:
with open(self._filepath, encoding="utf-8") as f:
self._data = json.load(f)
@@ -341,61 +359,33 @@ 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
def hashed_as_strings(*items):
def hash_tensor_data(image, hasher = None):
m = hashlib.sha256() if hasher is None else hasher
if isinstance(image, torch.Tensor):
buff = BytesIO()
torch.save(image, buff)
print("HASHING TENSOR")
m.update(buff.getvalue())
elif isinstance(image, bytes) or isinstance(image, bytearray):
print("HASHING BYTES")
m.update(image)
elif isinstance(image, list) or isinstance(image, tuple):
print("HASHING ITERABLE")
for item in image:
hash_tensor_data(item, m)
else:
print("HASING_AS_TEXT - "+str(type(image)))
m.update(str(image).encode(encoding="utf-8"))
return m.digest().hex()
def hashed_as_strings(*items, **kwargs):
tokens = "|".join(list(map(str, items)))
m = hashlib.sha256()
m.update(tokens.encode(encoding="utf-8"))
for pair in kwargs.items():
m.update(str(pair).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.075
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)
+27 -49
View File
@@ -1,30 +1,40 @@
from .categories import NodeCategories
from .dreamtypes import RGBPalette
from .err import *
from .shared import ALWAYS_CHANGED_FLAG, hashed_as_strings
from .types import RGBPalette
from .shared import hashed_as_strings, hash_tensor_data
_NOT_A_VALUE_I = 9223372036854775807
_NOT_A_VALUE_F = float(_NOT_A_VALUE_I)
_NOT_A_VALUE_S = "⭆"
def _generate_switch_input(type: str):
def _generate_switch_input(type_nm: str, default_value=None):
d = dict()
for i in range(10):
d["input_" + str(i)] = (type,)
if default_value is None:
d["input_" + str(i)] = (type_nm,)
else:
d["input_" + str(i)] = (type_nm, {"default": default_value, "forceInput": True})
return {
"required": {
"select": ("INT", {"defualt": 0, "min": 0, "max": 9}),
"select": ("INT", {"default": 0, "min": 0, "max": 9}),
"on_missing": (["previous", "next"],)
},
"optional": d
}
def _do_pick(cls, select, on_missing, **args):
def _do_pick(cls, select, test_val, 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:
n = len(args)
while not test_val(args.get("input_" + str(select), None)):
if n<0:
return (None,)
select = (select + direction) % 10
n = n - 1
return args["input_" + str(select)],
@@ -41,12 +51,8 @@ class DreamBigImageSwitch:
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)
return _do_pick(self.__class__, select, lambda n: n is not None, on_missing, **args)
class DreamBigLatentSwitch:
@@ -62,12 +68,8 @@ class DreamBigLatentSwitch:
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)
return _do_pick(self.__class__, select, lambda n: n is not None, on_missing, **args)
class DreamBigTextSwitch:
@@ -81,14 +83,10 @@ class DreamBigTextSwitch:
@classmethod
def INPUT_TYPES(cls):
return _generate_switch_input(cls._switch_type)
@classmethod
def IS_CHANGED(cls, *values):
return hashed_as_strings(values)
return _generate_switch_input(cls._switch_type, _NOT_A_VALUE_S)
def pick(self, select, on_missing, **args):
return _do_pick(self.__class__, select, on_missing, **args)
return _do_pick(self.__class__, select, lambda n: (n is not None) and (n != _NOT_A_VALUE_S), on_missing, **args)
class DreamBigPaletteSwitch:
@@ -104,12 +102,8 @@ class DreamBigPaletteSwitch:
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)
return _do_pick(self.__class__, select, lambda n: (n is not None), on_missing, **args)
class DreamBigFloatSwitch:
@@ -123,14 +117,10 @@ class DreamBigFloatSwitch:
@classmethod
def INPUT_TYPES(cls):
return _generate_switch_input(cls._switch_type)
@classmethod
def IS_CHANGED(cls, *values):
return hashed_as_strings(values)
return _generate_switch_input(cls._switch_type, _NOT_A_VALUE_F)
def pick(self, select, on_missing, **args):
return _do_pick(self.__class__, select, on_missing, **args)
return _do_pick(self.__class__, select, lambda n: (n is not None) and (n != _NOT_A_VALUE_F), on_missing, **args)
class DreamBigIntSwitch:
@@ -144,14 +134,10 @@ class DreamBigIntSwitch:
@classmethod
def INPUT_TYPES(cls):
return _generate_switch_input(cls._switch_type)
@classmethod
def IS_CHANGED(cls, *values):
return hashed_as_strings(values)
return _generate_switch_input(cls._switch_type, _NOT_A_VALUE_I)
def pick(self, select, on_missing, **args):
return _do_pick(self.__class__, select, on_missing, **args)
return _do_pick(self.__class__, select, lambda n: (n is not None) and (n != _NOT_A_VALUE_I), on_missing, **args)
class DreamBoolToFloat:
@@ -172,10 +158,6 @@ class DreamBoolToFloat:
}
}
@classmethod
def IS_CHANGED(cls, *values):
return hashed_as_strings(values)
def pick(self, boolean, on_true, on_false):
if boolean:
return (on_true,)
@@ -201,10 +183,6 @@ class DreamBoolToInt:
}
}
@classmethod
def IS_CHANGED(cls, *values):
return hashed_as_strings(values)
def pick(self, boolean, on_true, on_false):
if boolean:
return (on_true,)
+213 -6
View File
@@ -1,8 +1,220 @@
# -*- 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
from .shared import hashed_as_strings, DreamStateFile
from .dreamtypes 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[abs(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 ()
def _align_num(n: int, alignment: int, type: str):
@@ -38,10 +250,6 @@ class DreamFrameDimensions:
RETURN_NAMES = ("width", "height", "final_width", "final_height")
FUNCTION = "result"
@classmethod
def IS_CHANGED(cls, *values):
return hashed_as_strings(*values)
def result(self, size, aspect_ratio, orientation, divisor, alignment, alignment_type):
ratio = tuple(map(int, aspect_ratio.split(":")))
final_width = int(size)
@@ -52,4 +260,3 @@ class DreamFrameDimensions:
return (width, height, final_width, final_height)
else:
return (height, width, final_height, final_width)