65 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
30 changed files with 11807 additions and 790 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
+10 -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,18 +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, DreamImageColorShift,
DreamComparePalette, DreamImageContrast, DreamImageBrightness]
DreamComparePalette, DreamImageContrast, DreamImageBrightness, DreamLogFile,
DreamLaboratory, DreamStringToLog, DreamIntToLog, DreamFloatToLog, DreamJoinLog,
DreamStringTokenizer, DreamWavCurve, DreamFrameCounterTimeOffset, DreamRandomPromptWords]
_SIGNATURE_SUFFIX = " [Dream]"
MANIFEST = {
"name": "Dream Project Animation",
"version": (3, 2, 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),)
-4
View File
@@ -33,10 +33,6 @@ class DreamCalculation:
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()
+1 -37
View File
@@ -2,7 +2,7 @@
from .categories import NodeCategories
from .shared import *
from .types import *
from .dreamtypes import *
class DreamImageAreaSampler:
@@ -26,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
@@ -102,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()
@@ -139,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()
@@ -187,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()
@@ -234,10 +218,6 @@ class DreamImageColorShift:
RETURN_NAMES = ("image",)
FUNCTION = "result"
@classmethod
def IS_CHANGED(cls, *values):
return ALWAYS_CHANGED_FLAG
def result(self, image, red_multiplier, green_multiplier, blue_multiplier):
proc = DreamImageProcessor(inputs=image)
@@ -265,10 +245,6 @@ class DreamImageBrightness:
RETURN_NAMES = ("image",)
FUNCTION = "result"
@classmethod
def IS_CHANGED(cls, *values):
return ALWAYS_CHANGED_FLAG
def result(self, image, factor):
proc = DreamImageProcessor(inputs=image)
@@ -296,10 +272,6 @@ class DreamImageContrast:
RETURN_NAMES = ("image",)
FUNCTION = "result"
@classmethod
def IS_CHANGED(cls, *values):
return ALWAYS_CHANGED_FLAG
def result(self, image, factor):
proc = DreamImageProcessor(inputs=image)
@@ -328,10 +300,6 @@ class DreamComparePalette:
"brightness_multiplier", "contrast_multiplier", "red_multiplier", "green_multiplier", "blue_multiplier")
FUNCTION = "result"
@classmethod
def IS_CHANGED(cls, *values):
return ALWAYS_CHANGED_FLAG
def result(self, a, b):
MIN_VALUE = 1 / 255.0
@@ -372,10 +340,6 @@ class DreamAnalyzePalette:
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, c, r, g, b) = (0, 0, 0, 0, 0)
+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):
+30 -1
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
@@ -133,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])
+10 -1
View File
@@ -19,11 +19,13 @@
"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",
@@ -33,19 +35,26 @@
"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"
"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 = ""
+52 -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
@@ -163,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.
@@ -183,7 +194,10 @@ 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.
@@ -209,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.
@@ -243,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
@@ -274,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
+75 -66
View File
@@ -4,11 +4,14 @@ import shutil
import subprocess
import tempfile
from functools import lru_cache
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()
@@ -129,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"
@@ -205,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"
@@ -228,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:
+33 -61
View File
@@ -4,10 +4,10 @@ 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, ImageEnhance
@@ -18,10 +18,11 @@ 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:
@@ -274,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:
@@ -332,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)
@@ -358,62 +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.125
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)