1 Commits
Author SHA1 Message Date
Morgan Johansson 240176df63 Color sampling/noise for areas. 2023-09-08 07:03:26 +02:00
15 changed files with 19 additions and 5926 deletions
+3 -37
View File
@@ -21,7 +21,7 @@ _SIGNATURE_SUFFIX = " [Dream]"
MANIFEST = {
"name": "Dream Project Animation",
"version": (2, 1, 0),
"version": (2, 0, 0),
"author": "Dream Project",
"project": "https://github.com/alt-key-project/comfyui-dream-project",
"description": "Various utility nodes for creating animations with ComfyUI",
@@ -31,47 +31,13 @@ NODE_CLASS_MAPPINGS = {}
NODE_DISPLAY_NAME_MAPPINGS = {}
config = DreamConfig()
def update_category(cls):
top = config.get("ui.top_category", "").strip().strip("/")
leaf_icon = ""
if top and "CATEGORY" in cls.__dict__:
cls.CATEGORY = top + "/" + cls.CATEGORY.lstrip("/")
if "CATEGORY" in cls.__dict__:
joined = []
for partial in cls.CATEGORY.split("/"):
icon = config.get("ui.category_icons." + partial, "")
if icon:
leaf_icon = icon
if config.get("ui.prepend_icon_to_category", False):
partial = icon.lstrip() + " " + partial
if config.get("ui.append_icon_to_category", False):
partial = partial + " " + icon.rstrip()
joined.append(partial)
cls.CATEGORY = "/".join(joined)
return leaf_icon
def update_display_name(cls, category_icon, display_name):
icon = cls.__dict__.get("ICON", category_icon)
if config.get("ui.prepend_icon_to_node", False):
display_name = icon.lstrip() + " " + display_name
if config.get("ui.append_icon_to_node", False):
display_name = display_name + " " + icon.rstrip()
return display_name
for cls in _NODE_CLASSES:
category_icon = update_category(cls)
clsname = cls.__name__
if "NODE_NAME" in cls.__dict__:
node_name = cls.__dict__["NODE_NAME"] + _SIGNATURE_SUFFIX
NODE_CLASS_MAPPINGS[node_name] = cls
NODE_DISPLAY_NAME_MAPPINGS[node_name] = update_display_name(cls, category_icon,
cls.__dict__.get("DISPLAY_NAME",
cls.__dict__["NODE_NAME"]))
display_name = cls.__dict__.get("DISPLAY_NAME", cls.__dict__["NODE_NAME"]) + _SIGNATURE_SUFFIX
NODE_DISPLAY_NAME_MAPPINGS[node_name] = display_name
else:
raise Exception("Class {} is missing NODE_NAME!".format(str(cls)))
-6
View File
@@ -1,4 +1,3 @@
# -*- coding: utf-8 -*-
import glob
from .categories import NodeCategories
@@ -8,7 +7,6 @@ from .types import *
class DreamDirectoryFileCount:
NODE_NAME = "File Count"
ICON = "📂"
@classmethod
def INPUT_TYPES(cls):
@@ -40,8 +38,6 @@ class DreamDirectoryFileCount:
class DreamFrameCounterOffset:
NODE_NAME = "Frame Counter Offset"
ICON = "±"
@classmethod
def INPUT_TYPES(cls):
return {
@@ -65,7 +61,6 @@ class DreamFrameCounterOffset:
class DreamSimpleFrameCounter:
NODE_NAME = "Frame Counter (Simple)"
ICON = "⚋"
@classmethod
def INPUT_TYPES(cls):
@@ -93,7 +88,6 @@ class DreamSimpleFrameCounter:
class DreamDirectoryBackedFrameCounter:
NODE_NAME = "Frame Counter (Directory)"
ICON = "⚋"
@classmethod
def INPUT_TYPES(cls):
+1 -3
View File
@@ -1,5 +1,3 @@
# -*- coding: utf-8 -*-
class NodeCategories:
ANIMATION = "animation"
ANIMATION_POSTPROCESSING = ANIMATION + "/postprocessing"
@@ -10,4 +8,4 @@ class NodeCategories:
IMAGE_COLORS = "image/color"
IMAGE_GENERATE = "image/generate"
IMAGE = "image"
UTILS = "utils"
UTILS = "utils"
-1
View File
@@ -215,7 +215,6 @@ class DreamColorShift:
class DreamAnalyzePalette:
NODE_NAME = "Analyze Palette"
NODE = "📊"
@classmethod
def INPUT_TYPES(cls):
-1
View File
@@ -135,7 +135,6 @@ def _is_as_float(s: str):
class DreamCSVGenerator:
NODE_NAME = "CSV Generator"
ICON = "⌗"
@classmethod
def INPUT_TYPES(cls):
-8
View File
@@ -1,8 +0,0 @@
def _get_node_name(cls):
return cls.__dict__.get("NODE_NAME", str(cls))
def on_error(node_cls: type, message: str):
msg = "Failure in [" + _get_node_name(node_cls) + "]:" + message
print(msg)
raise Exception(msg)
File diff suppressed because it is too large Load Diff
-21
View File
@@ -1,21 +0,0 @@
MIT License
Copyright (c) 2023 Morgan Johansson/Dream Project
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
-1
View File
@@ -6,7 +6,6 @@ from .types import SharedTypes, FrameCounter
class DreamImageSequenceInputWithDefaultFallback:
NODE_NAME = "Image Sequence Loader"
ICON = "💾"
@classmethod
def INPUT_TYPES(cls):
+1 -1
View File
@@ -15,7 +15,7 @@
"Image Sequence Saver [Dream]": "Saves a frame to a directory",
"Image Sequence Tweening [Dream]": "Post processing for animation sequences generating blended in-between frames",
"Linear Curve [Dream]": "Linear interpolation between two value over the full animation",
"Noise from Area Palettes [Dream]": "Generates noise based on the colors of up to nine different palettes",
"Noise from Area Palettes [Dream]": "",
"Noise from Palette [Dream]": "Generates noise based on the colors in a palette",
"Palette Color Align [Dream]": "Shifts the colors of one palette towards another target palette",
"Palette Color Shift [Dream]": "Multiplies the color values in a palette",
-2
View File
@@ -24,7 +24,6 @@ def _generate_noise(image: DreamImage, color_function, rng: random.Random, block
class DreamNoiseFromPalette:
NODE_NAME = "Noise from Palette"
ICON = "🌫"
@classmethod
def INPUT_TYPES(cls):
@@ -89,7 +88,6 @@ class DreamNoiseFromAreaPalettes:
}
CATEGORY = NodeCategories.IMAGE_GENERATE
ICON = "🌫"
RETURN_TYPES = ("IMAGE",)
RETURN_NAMES = ("image",)
FUNCTION = "result"
-1
View File
@@ -31,7 +31,6 @@ def _save_jpg(pil_image, filepath, quality):
class DreamImageSequenceOutput:
NODE_NAME = "Image Sequence Saver"
ICON = "💾"
@classmethod
def INPUT_TYPES(cls):
+2 -49
View File
@@ -9,12 +9,6 @@ I have demonstrated the use of these custom nodes in this [youtube video](https:
## Installation
### Simple option
You can install Dream Project Animation Nodes using the ComfyUI Manager.
### Manual option
Run within (ComfyUI)/custom_nodes/ folder:
* git clone https://github.com/alt-key-project/comfyui-dream-project.git
@@ -32,41 +26,6 @@ Finally:
After startup, a configuration file 'config.json' should have been created in the 'comfyui-dream-project' directory.
Specifically check that the path of ffmpeg works in your system (add full path to the command if needed).
## Configuration
### ffmpeg.path
Path to the ffmpeg executable or just the command if ffmpeg is in PATH.
### ffmpeg.arguments
The arguments sent to FFMPEG. A few of the values are provided by the node:
* %FPS% the target framerate
* %FRAMES% a frame ionput file
* %OUTPUT% output video file path
### encoding.jpeg__quality
Sets the encoding quality of jpeg images.
### ui.top_category
Sets the name of the top level category on the menu. Set to empty string "" to remove the top level. If the top level
is removed you may also want to disable the category icons to get nodes into existing category folders.
### prepend_icon_to_category / append_icon_to_category
Flags to add a icon before and/or after the category name at each level.
### prepend_icon_icon_to_node / append_icon_icon_to_node
Flags to add an icon before and/or after the node name.
### ui.category_icons
Each key defines a unicode symbol as an icon used for the specified category.
## Concepts used
These are some concepts used in nodes:
@@ -180,18 +139,12 @@ This example should be a starting point for anyone wanting to build with the Dre
[motion-workflow-example](examples/motion-workflow-example.json)
### Image Motion with Color Coherence.
Same as above but with added color coherence through palettes.
[motion-workflow-with-color-coherence](examples/motion-workflow-with-color-coherence.json)
## Known issues
### FFMPEG
The call to FFMPEG currently in the default configuration (in config.json) does not seem to work for everyone. The good
news is that you can change the arguments to whatever works for you - the node-supplied parameters (that probably all need to be in the call)
news is that you can change the arguments to whatever works for you - the fixed parameters (that need to be in the call)
are:
* -i %FRAMES% (the input file listing frames)
@@ -199,4 +152,4 @@ are:
* %OUTPUT% (the path to the video file)
If possible, I will change the default configuration to one that more versions/builds of ffmpeg will accept. Do let me
know what arguments are causing issues for you!
know what arguments are causing issues for you!
+5 -5
View File
@@ -1,4 +1,5 @@
import os
import random
import shutil
import subprocess
import tempfile
@@ -7,8 +8,7 @@ from functools import lru_cache
from PIL import Image
from .categories import NodeCategories
from .err import on_error
from .shared import DreamConfig
from .shared import DreamConfig, DreamImage
from .types import *
CONFIG = DreamConfig()
@@ -121,14 +121,13 @@ def _ffmpeg(config, filenames, fps, output):
for (key, value) in replacements.items():
cmd = list(map(lambda s: s.replace(key, value), cmd))
subprocess.check_output(cmd, shell=True)
subprocess.run(cmd, shell=True)
finally:
os.unlink(tempfilepath)
class DreamVideoEncoder:
NODE_NAME = "FFMPEG Video Encoder"
ICON = "🎬"
@classmethod
def INPUT_TYPES(cls):
@@ -179,7 +178,8 @@ class DreamVideoEncoder:
if os.path.isfile(imagepath):
os.unlink(imagepath)
except Exception as e:
on_error(self.__class__, str(e))
print("Failed to encode files in dir {}!".format(os.path.dirname(images[0])))
print(str(e))
return ()
+7 -52
View File
@@ -1,13 +1,11 @@
# -*- coding: utf-8 -*-
import glob
import hashlib
import json
import os
import random
import folder_paths as comfy_paths
import glob
import numpy
import random
import torch
from PIL import Image, ImageFilter
from PIL.ImageDraw import ImageDraw
@@ -35,9 +33,6 @@ def _replace_pil_image(data):
return data
_config_data = None
class DreamConfig:
FILEPATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), "config.json")
DEFAULT_CONFIG = {
@@ -48,55 +43,15 @@ class DreamConfig:
},
"encoding": {
"jpeg_quality": 95
},
"ui": {
"top_category": "Dream",
"prepend_icon_to_category": True,
"append_icon_to_category": False,
"prepend_icon_to_node": True,
"append_icon_to_node": False,
"category_icons": {
"animation": "🎥",
"postprocessing": "⚙",
"transforms": "🔀",
"curves": "📈",
"color": "🎨",
"generate": "⚡",
"utils": "🛠",
"image": "🌄",
"Dream": "✨"
}
},
}
}
def __init__(self):
global _config_data
if not os.path.isfile(DreamConfig.FILEPATH):
self._data = DreamConfig.DEFAULT_CONFIG
self._save()
if _config_data is None:
with open(DreamConfig.FILEPATH, encoding="utf-8") as f:
self._data = json.load(f)
if self._merge_with_defaults(self._data, DreamConfig.DEFAULT_CONFIG):
self._save()
_config_data = self._data
else:
self._data = _config_data
def _save(self):
with open(DreamConfig.FILEPATH, "w", encoding="utf-8") as f:
json.dump(self._data, f, indent=2)
def _merge_with_defaults(self, config: dict, default_config: dict) -> bool:
changed = False
for key in default_config.keys():
if key not in config:
changed = True
config[key] = default_config[key]
elif isinstance(default_config[key], dict):
changed = changed or self._merge_with_defaults(config[key], default_config[key])
return changed
with open(DreamConfig.FILEPATH, "w") as f:
json.dump(DreamConfig.DEFAULT_CONFIG, f, indent=2)
with open(DreamConfig.FILEPATH) as f:
self._data = json.load(f)
def get(self, key: str, default=None):
key = key.split(".")