Add Video Nodes
This commit is contained in:
@@ -40,6 +40,7 @@
|
||||
- Create Grid Image: Create a image grid from images at a destination with customizable glob pattern. Optional border size and color.
|
||||
- Create Morph Image: Create a GIF/APNG animation from two images, fading between them.
|
||||
- Create Morph Image by Path: Create a GIF/APNG animation from a path to a directory containing images, with optional pattern.
|
||||
- Create Video from Path: Create video from images from a specified path.
|
||||
- Dictionary to Console: Print a dictionary input to the console
|
||||
- Image Analyze
|
||||
- Black White Levels
|
||||
@@ -164,7 +165,7 @@
|
||||
- True Random.org Number Generator: Generate a truly random number online from atmospheric noise with [Random.org](https://random.org/)
|
||||
- [Get your API key from your account page](https://accounts.random.org/)
|
||||
- Write to Morph GIF: Write a new frame to an existing GIF (or create new one) with interpolation between frames.
|
||||
|
||||
- Write to Video: Write a frame as you generate to a video (Best used with FFV1 for lossless images)
|
||||
</details>
|
||||
|
||||
<br>
|
||||
@@ -246,7 +247,12 @@ You can set `webui_styles_persistent_update` to `true` to update the WAS Node Su
|
||||
If you're running on Linux, or non-admin account on windows you'll want to ensure `/ComfyUI/custom_nodes`, `was-node-suite-comfyui`, and `WAS_Node_Suite.py` has write permissions.
|
||||
|
||||
- Navigate to your `/ComfyUI/custom_nodes/` folder
|
||||
- `git clone https://github.com/WASasquatch/was-node-suite-comfyui/`
|
||||
- Run `git clone https://github.com/WASasquatch/was-node-suite-comfyui/`
|
||||
- Navigate to your `was-node-suite-comfyui` folder
|
||||
- Portable/venv:
|
||||
- Run `path/to/ComfUI/python_embeded/python.exe -m pip install -r requirements.txt`
|
||||
- With system python
|
||||
- Run `pip install -r requirements.txt`
|
||||
- Start ComfyUI
|
||||
- WAS Suite should uninstall legacy nodes automatically for you.
|
||||
- Tools will be located in the WAS Suite menu.
|
||||
@@ -256,6 +262,7 @@ If you're running on Linux, or non-admin account on windows you'll want to ensur
|
||||
|
||||
- Download `WAS_Node_Suite.py`
|
||||
- Move the file to your `/ComfyUI/custom_nodes/` folder
|
||||
- WAS Node Suite will attempt install dependencies on it's own, but you may need to manually do so. The dependencies required are in the `requirements.txt` on this repo.
|
||||
- Start, or Restart ComfyUI
|
||||
- WAS Suite should uninstall legacy nodes automatically for you.
|
||||
- Tools will be located in the WAS Suite menu.
|
||||
@@ -268,3 +275,10 @@ Create a new cell and add the following code, then run the cell. You may need to
|
||||
- `!git clone https://github.com/WASasquatch/was-node-suite-comfyui /content/ComfyUI/custom_nodes/was-node-suite-comfyui`
|
||||
- Restart Colab Runtime (don't disconnect)
|
||||
- Tools will be located in the WAS Suite menu.
|
||||
|
||||
## Video Nodes
|
||||
- For now I am only supporting **Windows** installations for video nodes.
|
||||
- I do not have access to Mac or a stand-alone linux distro. If you get them working and want to PR a patch/directions, feel free.
|
||||
- Video nodes require [FFMPEG](https://ffmpeg.org/download.html). You should download the proper FFMPEG binaries for you system and set the FFMPEG path in the config file.
|
||||
- Additionally, if you want to use H264 codec need to [download OpenH264 1.8.0](https://github.com/cisco/openh264/releases/tag/v1.8.0) and place it in the root of ComfyUI (Example: `C:\ComfyUI_windows_portable`).
|
||||
- FFV1 will complain about invalid container. You can ignore this. The resulting MKV file is readable. I have not figured out what this issue is about. Documentaion tells me to use MKV, but it's telling me it's unsupported.
|
||||
+471
-106
@@ -119,6 +119,7 @@ was_conf_template = {
|
||||
"sam_model_vitb_url": "https://dl.fbaipublicfiles.com/segment_anything/sam_vit_b_01ec64.pth",
|
||||
"history_display_limit": 32,
|
||||
"use_legacy_ascii_text": True, # ASCII Legacy is True For Now
|
||||
"ffmpeg_bin_path": "/path/to/ffmpeg",
|
||||
}
|
||||
|
||||
# Create, Load, or Update Config
|
||||
@@ -134,6 +135,7 @@ def getSuiteConfig():
|
||||
print(e)
|
||||
return False
|
||||
return was_config
|
||||
return was_config
|
||||
|
||||
def updateSuiteConfig(conf):
|
||||
try:
|
||||
@@ -479,7 +481,7 @@ def update_history_text_files(new_paths):
|
||||
HDB.insert("History", "TextFiles", new_paths)
|
||||
# WAS Filter Class
|
||||
|
||||
class WAS_Filter_Class():
|
||||
class WAS_Tools_Class():
|
||||
|
||||
# TOOLS
|
||||
|
||||
@@ -656,13 +658,16 @@ class WAS_Filter_Class():
|
||||
return output_file
|
||||
|
||||
class GifMorphWriter:
|
||||
def __init__(self, transition_frames=10, duration_ms=100, still_image_delay_ms=2500, loop=0):
|
||||
def __init__(self, transition_frames=30, duration_ms=100, still_image_delay_ms=2500, loop=0):
|
||||
self.transition_frames = transition_frames
|
||||
self.duration_ms = duration_ms
|
||||
self.still_image_delay_ms = still_image_delay_ms
|
||||
self.loop = loop
|
||||
|
||||
def write(self, image, gif_path):
|
||||
|
||||
import cv2
|
||||
|
||||
if not os.path.isfile(gif_path):
|
||||
# Create the GIF file if it doesn't exist
|
||||
with Image.new("RGBA", image.size) as new_gif:
|
||||
@@ -670,7 +675,7 @@ class WAS_Filter_Class():
|
||||
new_gif.paste(image.convert("RGBA"))
|
||||
new_gif.info["duration"] = self.still_image_delay_ms
|
||||
new_gif.save(gif_path, format="GIF", save_all=True, append_images=[], duration=self.still_image_delay_ms, loop=0)
|
||||
print(f"Created new Morph GIF at: {gif_path}")
|
||||
print(f"\033[34mWAS NS:\033[0m Created new GIF animation at: {gif_path}")
|
||||
else:
|
||||
with Image.open(gif_path) as gif:
|
||||
# Extract the last still frame of the GIF, if it exists
|
||||
@@ -728,7 +733,7 @@ class WAS_Filter_Class():
|
||||
loop=self.loop,
|
||||
)
|
||||
|
||||
print(f"Edited existing Morph GIF at: {gif_path}")
|
||||
print(f"\033[34mWAS NS:\033[0m Edited existing GIF animation at: {gif_path}")
|
||||
|
||||
|
||||
def pad_to_size(self, image, size):
|
||||
@@ -755,6 +760,207 @@ class WAS_Filter_Class():
|
||||
frame = Image.blend(start_frame, end_image, weight)
|
||||
frames.append(frame)
|
||||
return frames
|
||||
|
||||
class VideoWriter:
|
||||
def __init__(self, transition_frames=30, fps=25, still_image_delay_sec=2, max_size=512, codec="mp4v"):
|
||||
self.transition_frames = transition_frames
|
||||
self.fps = fps
|
||||
self.still_image_delay_frames = round(still_image_delay_sec * fps)
|
||||
self.max_size = int(max_size)
|
||||
self.valid_codecs = ["avc1","h264","ffv1","hfyu","mp4v"]
|
||||
self.extensions = {"avc1":".avi","h264":".mkv","ffv1":".mkv","mp4v":".mp4"}
|
||||
self.codec = codec.lower() if codec.lower() in self.valid_codecs else "mp4v"
|
||||
|
||||
def write(self, image, video_path):
|
||||
|
||||
import cv2
|
||||
|
||||
# Setup video path extension
|
||||
video_path += self.extensions[self.codec]
|
||||
|
||||
# Convert the input image to a cv2 image
|
||||
end_image = self.rescale(self.pil2cv(image), self.max_size)
|
||||
|
||||
if os.path.isfile(video_path):
|
||||
# If the video file already exists, load it
|
||||
cap = cv2.VideoCapture(video_path)
|
||||
|
||||
# Get the video dimensions
|
||||
width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
|
||||
height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
|
||||
fps = int(cap.get(cv2.CAP_PROP_FPS))
|
||||
total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
|
||||
|
||||
# Create a temporary file to hold the new frames
|
||||
temp_file_path = video_path.replace(self.extensions[self.codec], '_temp'+self.extensions[self.codec])
|
||||
fourcc = cv2.VideoWriter_fourcc(*self.codec)
|
||||
out = cv2.VideoWriter(temp_file_path, fourcc, fps, (width, height), isColor=True)
|
||||
|
||||
# Write the original frames to the temporary file
|
||||
for i in range(total_frames):
|
||||
ret, frame = cap.read()
|
||||
out.write(frame)
|
||||
|
||||
# Create transition
|
||||
if self.transition_frames > 0:
|
||||
cap.set(cv2.CAP_PROP_POS_FRAMES, total_frames - 1)
|
||||
ret, last_frame = cap.read()
|
||||
transition_frames = self.generate_transition_frames(last_frame, self.pad_to_size(end_image, (width, height)), self.transition_frames)
|
||||
for i, transition_frame in enumerate(transition_frames):
|
||||
out.write(transition_frame)
|
||||
|
||||
# Add the new image frames to the temporary file
|
||||
for i in range(self.still_image_delay_frames):
|
||||
out.write(end_image)
|
||||
|
||||
# Release resources
|
||||
cap.release()
|
||||
out.release()
|
||||
|
||||
# Replace the original video file with the temporary file
|
||||
os.remove(video_path)
|
||||
os.rename(temp_file_path, video_path)
|
||||
|
||||
print(f"\033[34mWAS NS:\033[0m Edited video at: {video_path}")
|
||||
|
||||
return video_path
|
||||
|
||||
else:
|
||||
# If the video file doesn't exist, create it
|
||||
fourcc = cv2.VideoWriter_fourcc(*self.codec)
|
||||
height, width, _ = end_image.shape
|
||||
out = cv2.VideoWriter(video_path, fourcc, self.fps, (width, height), isColor=True)
|
||||
|
||||
# Write the still image for the specified duration
|
||||
for i in range(self.still_image_delay_frames):
|
||||
out.write(end_image)
|
||||
|
||||
# Release resources
|
||||
out.release()
|
||||
|
||||
print(f"\033[34mWAS NS:\033[0m Created new video at: {video_path}")
|
||||
|
||||
return video_path
|
||||
|
||||
return ""
|
||||
|
||||
def create_video(self, image_folder, video_path):
|
||||
import cv2
|
||||
|
||||
# Get a list of the image files in the folder, sorted alphabetically
|
||||
image_paths = sorted([os.path.join(image_folder, f) for f in os.listdir(image_folder)
|
||||
if os.path.isfile(os.path.join(image_folder, f))
|
||||
and os.path.join(image_folder, f).lower().endswith(ALLOWED_EXT)])
|
||||
|
||||
print(image_paths)
|
||||
|
||||
# Check that there are image files in the folder
|
||||
if len(image_paths) == 0:
|
||||
print(f"\033[31mERR:\033[0m No valid image files found in `{image_folder}` directory. Valid image formats are", *sort(ALLOWED_EXT), end=" ")
|
||||
return
|
||||
|
||||
# Output file including extension
|
||||
output_file = video_path + self.extensions[self.codec]
|
||||
|
||||
# Load the first image to get the dimensions
|
||||
image = self.rescale(cv2.imread(image_paths[0]), self.max_size)
|
||||
height, width = image.shape[:2]
|
||||
|
||||
# Create a VideoWriter object
|
||||
fourcc = cv2.VideoWriter_fourcc(*self.codec)
|
||||
out = cv2.VideoWriter(output_file, fourcc, self.fps, (width, height), isColor=True)
|
||||
|
||||
# Write still frames for the first image
|
||||
out.write(image)
|
||||
for _ in range(self.still_image_delay_frames - 1):
|
||||
out.write(image)
|
||||
|
||||
for i in range(len(image_paths)):
|
||||
# Load frame(s)
|
||||
start_frame = cv2.imread(image_paths[i])
|
||||
end_frame = None
|
||||
if i+1 <= len(image_paths)-1:
|
||||
end_frame = self.rescale(cv2.imread(image_paths[i+1]), self.max_size)
|
||||
|
||||
# Create transition frames
|
||||
if isinstance(end_frame, np.ndarray):
|
||||
transition_frames = self.generate_transition_frames(start_frame, end_frame, self.transition_frames)
|
||||
# Resize transition frames to match video size
|
||||
transition_frames = [cv2.resize(frame, (width, height)) for frame in transition_frames]
|
||||
# Write transition frames to the video
|
||||
for _, frame in enumerate(transition_frames):
|
||||
out.write(frame)
|
||||
|
||||
# Write still frames for the current image after the transition frames
|
||||
for _ in range(self.still_image_delay_frames - self.transition_frames):
|
||||
out.write(end_frame)
|
||||
|
||||
else:
|
||||
# No transition frames for the last image in the folder
|
||||
out.write(start_frame)
|
||||
for _ in range(self.still_image_delay_frames - 1):
|
||||
out.write(start_frame)
|
||||
|
||||
# Release resources
|
||||
out.release()
|
||||
|
||||
if os.path.exists(output_file):
|
||||
print(f"\033[34mWAS NS:\033[0m Created video at: {output_file}")
|
||||
return output_file
|
||||
else:
|
||||
print(f"\033[34mWAS Node Suite\033[0m Error: Unable to create video at: {output_file}")
|
||||
return ""
|
||||
|
||||
def rescale(self, image, max_dimension):
|
||||
import cv2
|
||||
height, width, _ = image.shape
|
||||
if width > max_dimension or height > max_dimension:
|
||||
scaling_factor = max(width, height) / max_dimension
|
||||
new_width = int(width / scaling_factor)
|
||||
new_height = int(height / scaling_factor)
|
||||
image = cv2.resize(image, (new_width, new_height), interpolation=cv2.INTER_LINEAR)
|
||||
return image
|
||||
|
||||
def pad_to_size(self, image, size):
|
||||
import cv2
|
||||
# Pad the image with black pixels to match the desired size
|
||||
if image.shape[1] != size[0] or image.shape[0] != size[1]:
|
||||
image = np.zeros((size[1], size[0], 3), dtype=np.uint8)
|
||||
x_offset = (size[0] - image.shape[1]) // 2
|
||||
y_offset = (size[1] - image.shape[0]) // 2
|
||||
image[y_offset:y_offset+image.shape[0], x_offset:x_offset+image.shape[1], :] = cv2.resize(image, (size[0], size[1]))
|
||||
return image
|
||||
|
||||
def generate_transition_frames(self, img1, img2, num_frames):
|
||||
import cv2
|
||||
if img1 is None and img2 is None:
|
||||
return []
|
||||
|
||||
# Resize the images if necessary
|
||||
if img1 is not None and img2 is not None:
|
||||
if img1.shape != img2.shape:
|
||||
img2 = cv2.resize(img2, img1.shape[:2][::-1])
|
||||
elif img1 is not None:
|
||||
img2 = np.zeros_like(img1)
|
||||
else:
|
||||
img1 = np.zeros_like(img2)
|
||||
|
||||
height, width, _ = img2.shape
|
||||
|
||||
frame_sequence = []
|
||||
for i in range(num_frames):
|
||||
alpha = i / float(num_frames)
|
||||
blended = cv2.addWeighted(img1, 1 - alpha, img2, alpha,
|
||||
gamma=0.0, dtype=cv2.CV_8U)
|
||||
frame_sequence.append(blended)
|
||||
|
||||
return frame_sequence
|
||||
|
||||
def pil2cv(self, img):
|
||||
import cv2
|
||||
img = np.array(img)
|
||||
img = cv2.cvtColor(img, cv2.COLOR_RGB2BGR)
|
||||
return img
|
||||
|
||||
|
||||
# FILTERS
|
||||
@@ -1348,10 +1554,10 @@ class WAS_Shadow_And_Highlight_Adjustment:
|
||||
|
||||
def apply_shadow_and_highlight(self, image, shadow_threshold=30, highlight_threshold=220, shadow_factor=1.5, highlight_factor=0.5, shadow_smoothing=0, highlight_smoothing=0, simplify_isolation=0):
|
||||
|
||||
WFilter = WAS_Filter_Class()
|
||||
WTools = WAS_Tools_Class()
|
||||
|
||||
result, shadows, highlights = WFilter.shadows_and_highlights(tensor2pil(image), shadow_threshold, highlight_threshold, shadow_factor, highlight_factor, shadow_smoothing, highlight_smoothing, simplify_isolation)
|
||||
result, shadows, highlights = WFilter.shadows_and_highlights(tensor2pil(image), shadow_threshold, highlight_threshold, shadow_factor, highlight_factor, shadow_smoothing, highlight_smoothing, simplify_isolation)
|
||||
result, shadows, highlights = WTools.shadows_and_highlights(tensor2pil(image), shadow_threshold, highlight_threshold, shadow_factor, highlight_factor, shadow_smoothing, highlight_smoothing, simplify_isolation)
|
||||
result, shadows, highlights = WTools.shadows_and_highlights(tensor2pil(image), shadow_threshold, highlight_threshold, shadow_factor, highlight_factor, shadow_smoothing, highlight_smoothing, simplify_isolation)
|
||||
|
||||
return (pil2tensor(result), pil2tensor(shadows), pil2tensor(highlights) )
|
||||
|
||||
@@ -1506,7 +1712,7 @@ class WAS_Image_Style_Filter:
|
||||
image = tensor2pil(image)
|
||||
|
||||
# WAS Filters
|
||||
WFilter = WAS_Filter_Class()
|
||||
WTools = WAS_Tools_Class()
|
||||
|
||||
# Apply blending
|
||||
if style:
|
||||
@@ -1523,7 +1729,7 @@ class WAS_Image_Style_Filter:
|
||||
elif style == "earlybird":
|
||||
out_image = pilgram.earlybird(image)
|
||||
elif style == "fairy tale":
|
||||
out_image = WFilter.sparkle(image)
|
||||
out_image = WTools.sparkle(image)
|
||||
elif style == "gingham":
|
||||
out_image = pilgram.gingham(image)
|
||||
elif style == "hudson":
|
||||
@@ -1606,10 +1812,6 @@ class WAS_Image_Crop_Face:
|
||||
def image_crop_face(self, image, cascade_xml=None, crop_padding_factor=0.25, use_face_recognition_gpu="false"):
|
||||
|
||||
use_fr = False if use_face_recognition_gpu.strip().lower() == 'false' else True
|
||||
|
||||
if 'opencv-python' not in packages():
|
||||
print("\033[34mWAS NS:\033[0m Installing CV2...")
|
||||
subprocess.check_call([sys.executable, '-m', 'pip', '-q', 'install', 'opencv-python'])
|
||||
|
||||
if use_fr:
|
||||
if 'face_recognition' not in packages():
|
||||
@@ -2137,15 +2339,10 @@ class WAS_Image_Morph_GIF:
|
||||
RETURN_NAMES = ("image_a_pass","image_b_pass","filepath_text","filename_text")
|
||||
FUNCTION = "create_morph_gif"
|
||||
|
||||
CATEGORY = "WAS Suite/Image/Process"
|
||||
CATEGORY = "WAS Suite/Animation"
|
||||
|
||||
def create_morph_gif(self, image_a, image_b, transition_frames=10, still_image_delay_ms=10, duration_ms=0.1, loops=0, max_size=512,
|
||||
output_path="./ComfyUI/output", filename="morph", filetype="GIF"):
|
||||
|
||||
if 'opencv-python' not in packages():
|
||||
print("\033[34mWAS NS:\033[0m Installing CV2...")
|
||||
subprocess.check_call(
|
||||
[sys.executable, '-m', 'pip', '-q', 'install', 'opencv-python'])
|
||||
|
||||
if 'imageio' not in packages():
|
||||
print("\033[34mWAS NS:\033[0m Installing imageio...")
|
||||
@@ -2158,7 +2355,7 @@ class WAS_Image_Morph_GIF:
|
||||
output_path = "./ComfyUI/output"
|
||||
output_path = tokens.parseTokens(os.path.join(*output_path.split('/')))
|
||||
if not os.path.exists(output_path):
|
||||
os.mkdir(output_path)
|
||||
os.makedirs(output_path, exist_ok=True)
|
||||
|
||||
if image_a == None:
|
||||
image_a = pil2tensor(Image.new("RGB", (512,512), (0,0,0)))
|
||||
@@ -2176,9 +2373,9 @@ class WAS_Image_Morph_GIF:
|
||||
duration_ms = 60000.0
|
||||
|
||||
tokens = TextTokens()
|
||||
WFilter = WAS_Filter_Class()
|
||||
WTools = WAS_Tools_Class()
|
||||
|
||||
output_file = WFilter.morph_images([tensor2pil(image_a), tensor2pil(image_b)], steps=int(transition_frames), max_size=int(max_size), loop=int(loops),
|
||||
output_file = WTools.morph_images([tensor2pil(image_a), tensor2pil(image_b)], steps=int(transition_frames), max_size=int(max_size), loop=int(loops),
|
||||
still_duration=int(still_image_delay_ms), duration=int(duration_ms), output_path=output_path,
|
||||
filename=tokens.parseTokens(filename), filetype=filetype)
|
||||
|
||||
@@ -2214,15 +2411,10 @@ class WAS_Image_Morph_GIF_Writer:
|
||||
RETURN_NAMES = ("IMAGE_PASS","filepath_text","filename_text")
|
||||
FUNCTION = "write_to_morph_gif"
|
||||
|
||||
CATEGORY = "WAS Suite/Image/Process"
|
||||
CATEGORY = "WAS Suite/Animation/Writer"
|
||||
|
||||
def write_to_morph_gif(self, image, transition_frames=10, image_delay_ms=10, duration_ms=0.1, loops=0, max_size=512,
|
||||
output_path="./ComfyUI/output", filename="morph"):
|
||||
|
||||
if 'opencv-python' not in packages():
|
||||
print("\033[34mWAS NS:\033[0m Installing CV2...")
|
||||
subprocess.check_call(
|
||||
[sys.executable, '-m', 'pip', '-q', 'install', 'opencv-python'])
|
||||
|
||||
if 'imageio' not in packages():
|
||||
print("\033[34mWAS NS:\033[0m Installing imageio...")
|
||||
@@ -2250,14 +2442,13 @@ class WAS_Image_Morph_GIF_Writer:
|
||||
output_file = os.path.join(output_path, tokens.parseTokens(filename)+'.gif')
|
||||
|
||||
if not os.path.exists(output_path):
|
||||
os.mkdir(output_path)
|
||||
os.makedirs(output_path, exist_ok=True)
|
||||
|
||||
WFilter = WAS_Filter_Class()
|
||||
GifMorph = WFilter.GifMorphWriter(int(transition_frames), int(duration_ms), int(image_delay_ms))
|
||||
WTools = WAS_Tools_Class()
|
||||
GifMorph = WTools.GifMorphWriter(int(transition_frames), int(duration_ms), int(image_delay_ms))
|
||||
GifMorph.write(tensor2pil(image), output_file)
|
||||
|
||||
return (image, output_file, filename)
|
||||
|
||||
return (image, output_file, filename)
|
||||
|
||||
# IMAGE MORPH GIF BY PATH
|
||||
|
||||
@@ -2290,15 +2481,10 @@ class WAS_Image_Morph_GIF_By_Path:
|
||||
RETURN_NAMES = ("filepath_text","filename_text")
|
||||
FUNCTION = "create_morph_gif"
|
||||
|
||||
CATEGORY = "WAS Suite/Image/Process"
|
||||
CATEGORY = "WAS Suite/Animation"
|
||||
|
||||
def create_morph_gif(self, transition_frames=30, still_image_delay_ms=2500, duration_ms=0.1, loops=0, max_size=512,
|
||||
input_path="./ComfyUI/output", input_pattern="*", output_path="./ComfyUI/output", filename="morph", filetype="GIF"):
|
||||
|
||||
if 'opencv-python' not in packages():
|
||||
print("\033[34mWAS NS:\033[0m Installing CV2...")
|
||||
subprocess.check_call(
|
||||
[sys.executable, '-m', 'pip', '-q', 'install', 'opencv-python'])
|
||||
|
||||
if 'imageio' not in packages():
|
||||
print("\033[34mWAS NS:\033[0m Installing imageio...")
|
||||
@@ -2330,9 +2516,9 @@ class WAS_Image_Morph_GIF_By_Path:
|
||||
duration_ms = 60000.0
|
||||
|
||||
tokens = TextTokens()
|
||||
WFilter = WAS_Filter_Class()
|
||||
WTools = WAS_Tools_Class()
|
||||
|
||||
output_file = WFilter.morph_images(images, steps=int(transition_frames), max_size=int(max_size), loop=int(loops), still_duration=int(still_image_delay_ms),
|
||||
output_file = WTools.morph_images(images, steps=int(transition_frames), max_size=int(max_size), loop=int(loops), still_duration=int(still_image_delay_ms),
|
||||
duration=int(duration_ms), output_path=tokens.parseTokens(os.path.join(*output_path.split('/'))),
|
||||
filename=tokens.parseTokens(filename), filetype=filetype)
|
||||
|
||||
@@ -2510,16 +2696,16 @@ class WAS_Image_Monitor_Distortion_Filter:
|
||||
image = tensor2pil(image)
|
||||
|
||||
# WAS Filters
|
||||
WFilter = WAS_Filter_Class()
|
||||
WTools = WAS_Tools_Class()
|
||||
|
||||
# Apply image effect
|
||||
if mode:
|
||||
if mode == 'Digital Distortion':
|
||||
image = WFilter.digital_distortion(image, amplitude, offset)
|
||||
image = WTools.digital_distortion(image, amplitude, offset)
|
||||
elif mode == 'Signal Distortion':
|
||||
image = WFilter.signal_distortion(image, amplitude)
|
||||
image = WTools.signal_distortion(image, amplitude)
|
||||
elif mode == 'TV Distortion':
|
||||
image = WFilter.tv_vhs_distortion(image, amplitude)
|
||||
image = WTools.tv_vhs_distortion(image, amplitude)
|
||||
else:
|
||||
image = image
|
||||
|
||||
@@ -2556,9 +2742,9 @@ class WAS_Image_Perlin_Noise_Filter:
|
||||
if width > 1024 or height > 1024 and octaves > 6:
|
||||
octaves = 6
|
||||
|
||||
WFilter = WAS_Filter_Class()
|
||||
WTools = WAS_Tools_Class()
|
||||
|
||||
image = WFilter.perlin_noise(width, height, shape, density, octaves, seed)
|
||||
image = WTools.perlin_noise(width, height, shape, density, octaves, seed)
|
||||
|
||||
return (pil2tensor(image), )
|
||||
|
||||
@@ -2588,9 +2774,9 @@ class WAS_Image_Voronoi_Noise_Filter:
|
||||
|
||||
def voronoi_noise_filter(self, width, height, density, modulator, seed):
|
||||
|
||||
WFilter = WAS_Filter_Class()
|
||||
WTools = WAS_Tools_Class()
|
||||
|
||||
image = WFilter.worley_noise(height=width, width=height, density=density, option=modulator, use_broadcast_ops=True).image
|
||||
image = WTools.worley_noise(height=width, width=height, density=density, option=modulator, use_broadcast_ops=True).image
|
||||
|
||||
return (pil2tensor(image), )
|
||||
|
||||
@@ -2620,9 +2806,9 @@ class WAS_Image_Make_Seamless:
|
||||
|
||||
def make_seamless(self, image, blending, tiled, tiles):
|
||||
|
||||
WFilter = WAS_Filter_Class()
|
||||
WTools = WAS_Tools_Class()
|
||||
|
||||
image = WFilter.make_seamless(tensor2pil(image), blending, tiled, tiles)
|
||||
image = WTools.make_seamless(tensor2pil(image), blending, tiled, tiles)
|
||||
|
||||
return (pil2tensor(image), )
|
||||
|
||||
@@ -2654,7 +2840,7 @@ class WAS_Image_Color_Palette:
|
||||
image = tensor2pil(image)
|
||||
|
||||
# WAS Filters
|
||||
WFilter = WAS_Filter_Class()
|
||||
WTools = WAS_Tools_Class()
|
||||
|
||||
res_dir = os.path.join(WAS_SUITE_ROOT, 'res')
|
||||
font = os.path.join(res_dir, 'font.ttf')
|
||||
@@ -2665,7 +2851,7 @@ class WAS_Image_Color_Palette:
|
||||
print(f'\033[34mWAS NS:\033[0m Found font at `{font}`')
|
||||
|
||||
# Generate Color Palette
|
||||
image = WFilter.generate_palette(image, colors, 128, 10, font, 15)
|
||||
image = WTools.generate_palette(image, colors, 128, 10, font, 15)
|
||||
|
||||
return (pil2tensor(image), )
|
||||
|
||||
@@ -2697,14 +2883,14 @@ class WAS_Image_Analyze:
|
||||
image = tensor2pil(image)
|
||||
|
||||
# WAS Filters
|
||||
WFilter = WAS_Filter_Class()
|
||||
WTools = WAS_Tools_Class()
|
||||
|
||||
# Analye Image
|
||||
if mode:
|
||||
if mode == 'Black White Levels':
|
||||
image = WFilter.black_white_levels(image)
|
||||
image = WTools.black_white_levels(image)
|
||||
elif mode == 'RGB Levels':
|
||||
image = WFilter.channel_frequency(image)
|
||||
image = WTools.channel_frequency(image)
|
||||
else:
|
||||
image = image
|
||||
|
||||
@@ -2743,7 +2929,7 @@ class WAS_Image_Generate_Gradient:
|
||||
import io
|
||||
|
||||
# WAS Filters
|
||||
WFilter = WAS_Filter_Class()
|
||||
WTools = WAS_Tools_Class()
|
||||
|
||||
colors_dict = {}
|
||||
stops = io.StringIO(gradient_stops.strip().replace(' ',''))
|
||||
@@ -2752,7 +2938,7 @@ class WAS_Image_Generate_Gradient:
|
||||
colors = parts[1].replace('\n','').split(',')
|
||||
colors_dict[parts[0].replace('\n','')] = colors
|
||||
|
||||
image = WFilter.gradient((width, height), direction, colors_dict, tolerance)
|
||||
image = WTools.gradient((width, height), direction, colors_dict, tolerance)
|
||||
|
||||
return (pil2tensor(image), )
|
||||
|
||||
@@ -2784,9 +2970,9 @@ class WAS_Image_Gradient_Map:
|
||||
gradient_image = tensor2pil(gradient_image)
|
||||
|
||||
# WAS Filters
|
||||
WFilter = WAS_Filter_Class()
|
||||
WTools = WAS_Tools_Class()
|
||||
|
||||
image = WFilter.gradient_map(image, gradient_image, (True if flip_left_right == 'true' else False))
|
||||
image = WTools.gradient_map(image, gradient_image, (True if flip_left_right == 'true' else False))
|
||||
|
||||
return (pil2tensor(image), )
|
||||
|
||||
@@ -3071,9 +3257,9 @@ class WAS_Image_Stitch:
|
||||
if feathering > 2048:
|
||||
raise ValueError(f"\033[34mWAS NS\033[0m Error: The stitch feathering of `{feathering}` is too high. Please choose a value between `0` and `2048`")
|
||||
|
||||
WFilter = WAS_Filter_Class();
|
||||
WTools = WAS_Tools_Class();
|
||||
|
||||
stitched_image = WFilter.stitch_image(tensor2pil(image_a), tensor2pil(image_b), stitch, feathering)
|
||||
stitched_image = WTools.stitch_image(tensor2pil(image_a), tensor2pil(image_b), stitch, feathering)
|
||||
|
||||
return (pil2tensor(stitched_image), )
|
||||
|
||||
@@ -3870,8 +4056,6 @@ class WAS_Canny_Filter:
|
||||
|
||||
def canny_filter(self, image, threshold_low, threshold_high, enable_threshold):
|
||||
|
||||
self.install_opencv()
|
||||
|
||||
if enable_threshold == 'false':
|
||||
threshold_low = None
|
||||
threshold_high = None
|
||||
@@ -3984,12 +4168,6 @@ class WAS_Canny_Filter:
|
||||
# gradients of edges
|
||||
return mag
|
||||
|
||||
def install_opencv(self):
|
||||
if 'opencv-python' not in packages():
|
||||
print("\033[34mWAS NS:\033[0m Installing CV2...")
|
||||
subprocess.check_call([sys.executable, '-m', 'pip', '-q', 'install', 'opencv-python'])
|
||||
|
||||
|
||||
# IMAGE EDGE DETECTION
|
||||
|
||||
class WAS_Image_Edge:
|
||||
@@ -4053,11 +4231,6 @@ class WAS_Image_fDOF:
|
||||
|
||||
def fdof_composite(self, image, depth, radius, samples, mode):
|
||||
|
||||
if 'opencv-python' not in packages():
|
||||
print("\033[34mWAS NS:\033[0m Installing CV2...")
|
||||
subprocess.check_call(
|
||||
[sys.executable, '-m', 'pip', '-q', 'install', 'opencv-python'])
|
||||
|
||||
import cv2 as cv
|
||||
|
||||
# Convert tensor to a PIL Image
|
||||
@@ -4126,9 +4299,9 @@ class WAS_Dragon_Filter:
|
||||
|
||||
def apply_dragan_filter(self, image, saturation, contrast, sharpness, brightness, highpass_radius, highpass_samples, highpass_strength, colorize):
|
||||
|
||||
WFilter = WAS_Filter_Class()
|
||||
WTools = WAS_Tools_Class()
|
||||
|
||||
image = WFilter.dragan_filter(tensor2pil(image), saturation, contrast, sharpness, brightness, highpass_radius, highpass_samples, highpass_strength, colorize)
|
||||
image = WTools.dragan_filter(tensor2pil(image), saturation, contrast, sharpness, brightness, highpass_radius, highpass_samples, highpass_strength, colorize)
|
||||
|
||||
return (pil2tensor(image), )
|
||||
|
||||
@@ -4192,11 +4365,6 @@ class WAS_Image_Select_Color:
|
||||
|
||||
def select_color(self, image, red=255, green=255, blue=255, variance=10):
|
||||
|
||||
if 'opencv-python' not in packages():
|
||||
print("\033[34mWAS NS:\033[0m Installing CV2...")
|
||||
subprocess.check_call(
|
||||
[sys.executable, '-m', 'pip', '-q', 'install', 'opencv-python'])
|
||||
|
||||
image = self.color_pick(tensor2pil(image), red, green, blue, variance)
|
||||
|
||||
return (pil2tensor(image), )
|
||||
@@ -4343,13 +4511,13 @@ class WAS_Image_Save:
|
||||
}
|
||||
|
||||
RETURN_TYPES = ()
|
||||
FUNCTION = "save_images"
|
||||
FUNCTION = "was_save_images"
|
||||
|
||||
OUTPUT_NODE = True
|
||||
|
||||
CATEGORY = "WAS Suite/IO"
|
||||
|
||||
def save_images(self, images, output_path='', filename_prefix="ComfyUI", extension='png', quality=100, prompt=None, extra_pnginfo=None, overwrite_mode='false'):
|
||||
def was_save_images(self, images, output_path='', filename_prefix="ComfyUI", extension='png', quality=100, prompt=None, extra_pnginfo=None, overwrite_mode='false'):
|
||||
def map_filename(filename):
|
||||
prefix_len = len(filename_prefix)
|
||||
prefix = filename[:prefix_len + 1]
|
||||
@@ -4367,7 +4535,7 @@ class WAS_Image_Save:
|
||||
if output_path.strip() != '':
|
||||
if not os.path.exists(output_path.strip()):
|
||||
print(f'\033[34mWAS NS\033[0m Warning: The path `{output_path.strip()}` specified doesn\'t exist! Creating directory.')
|
||||
os.mkdir(output_path.strip())
|
||||
os.makedirs(output_path.strip(), exist_ok=True)
|
||||
self.output_dir = output_path.strip()
|
||||
|
||||
# Setup counter
|
||||
@@ -4377,11 +4545,11 @@ class WAS_Image_Save:
|
||||
except ValueError:
|
||||
counter = 1
|
||||
except FileNotFoundError:
|
||||
os.mkdir(self.output_dir)
|
||||
os.makedirs(self.output_dir, exist_ok=True)
|
||||
counter = 1
|
||||
|
||||
# Set Extension
|
||||
file_extension = ( extension if extension in ['png', 'jpeg', 'gif', 'tiff', 'gif'] else 'tiff' )
|
||||
file_extension = ( extension if extension in ['png', 'jpeg', 'gif', 'tiff', 'gif'] else 'png' )
|
||||
|
||||
paths = list()
|
||||
for image in images:
|
||||
@@ -4405,27 +4573,23 @@ class WAS_Image_Save:
|
||||
counter += 1
|
||||
file = f"{filename_prefix}_{counter:05}_.{file_extension}"
|
||||
try:
|
||||
output_file = os.path.abspath(os.path.join(self.output_dir, file))
|
||||
if extension == 'png':
|
||||
output_file = os.path.join(self.output_dir, file)
|
||||
img.save(output_file,
|
||||
pnginfo=metadata, optimize=True)
|
||||
print(f'\033[34mWAS NS:\033[0m Image file saved to:', output_file)
|
||||
elif extension == 'webp':
|
||||
output_file = os.path.join(self.output_dir, file)
|
||||
img.save(output_file, quality=quality)
|
||||
print(f'\033[34mWAS NS:\033[0m Image file saved to:', output_file)
|
||||
elif extension == 'jpeg':
|
||||
output_file = os.path.join(self.output_dir, file)
|
||||
img.save(output_file,
|
||||
quality=quality, optimize=True)
|
||||
print(f'\033[34mWAS NS:\033[0m Image file saved to:', output_file)
|
||||
elif extension == 'tiff':
|
||||
output_file = os.path.join(self.output_dir, file)
|
||||
img.save(output_file,
|
||||
quality=quality, optimize=True)
|
||||
print(f'\033[34mWAS NS:\033[0m Image file saved to:', output_file)
|
||||
else:
|
||||
output_file = os.path.join(self.output_dir, file)
|
||||
img.save(output_file)
|
||||
paths.append(file)
|
||||
except OSError as e:
|
||||
@@ -4734,10 +4898,6 @@ class MiDaS_Depth_Approx:
|
||||
print("\033[34mWAS NS:\033[0m Installing timm...")
|
||||
subprocess.check_call(
|
||||
[sys.executable, '-m', 'pip', '-q', 'install', 'timm'])
|
||||
if 'opencv-python' not in packages():
|
||||
print("\033[34mWAS NS:\033[0m Installing CV2...")
|
||||
subprocess.check_call(
|
||||
[sys.executable, '-m', 'pip', '-q', 'install', 'opencv-python'])
|
||||
MIDAS_INSTALLED = True
|
||||
|
||||
# MIDAS REMOVE BACKGROUND/FOREGROUND NODE
|
||||
@@ -4903,10 +5063,6 @@ class MiDaS_Background_Foreground_Removal:
|
||||
print("\033[34mWAS NS:\033[0m Installing timm...")
|
||||
subprocess.check_call(
|
||||
[sys.executable, '-m', 'pip', '-q', 'install', 'timm'])
|
||||
if 'opencv-python' not in packages():
|
||||
print("\033[34mWAS NS:\033[0m Installing CV2...")
|
||||
subprocess.check_call(
|
||||
[sys.executable, '-m', 'pip', '-q', 'install', 'opencv-python'])
|
||||
MIDAS_INSTALLED = True
|
||||
|
||||
|
||||
@@ -5462,7 +5618,7 @@ class WAS_Text_Save:
|
||||
print(
|
||||
f'\033[34mWAS NS\033[0m Warning: The path `{path}` doesn\'t exist! Creating it...')
|
||||
try:
|
||||
os.mkdir(path)
|
||||
os.makedirs(path, exist_ok=True)
|
||||
except OSError as e:
|
||||
print(
|
||||
f'\033[34mWAS NS\033[0m Warning: The path `{path}` could not be created! Is there write access?\n{e}')
|
||||
@@ -5973,7 +6129,7 @@ class WAS_BLIP_Analyze_Image:
|
||||
|
||||
blip_dir = os.path.join(MODELS_DIR, 'blip')
|
||||
if not os.path.exists(blip_dir):
|
||||
os.mkdir(blip_dir)
|
||||
os.makedirs(blip_dir, exist_ok=True)
|
||||
|
||||
torch.hub.set_dir(blip_dir)
|
||||
|
||||
@@ -5999,7 +6155,7 @@ class WAS_BLIP_Analyze_Image:
|
||||
|
||||
blip_dir = os.path.join(MODELS_DIR, 'blip')
|
||||
if not os.path.exists(blip_dir):
|
||||
os.mkdir(blip_dir)
|
||||
os.makedirs(blip_dir, exist_ok=True)
|
||||
|
||||
torch.hub.set_dir(blip_dir)
|
||||
|
||||
@@ -6071,7 +6227,7 @@ class WAS_SAM_Model_Loader:
|
||||
|
||||
sam_dir = os.path.join(( os.getcwd()+os.sep+'ComfyUI' if not os.getcwd().startswith('/content') else os.getcwd() ), 'models'+os.sep+'sam')
|
||||
if not os.path.exists(sam_dir):
|
||||
os.mkdir(sam_dir)
|
||||
os.makedirs(sam_dir, exist_ok=True)
|
||||
|
||||
sam_file = os.path.join(sam_dir, model_filename)
|
||||
if not os.path.exists(sam_file):
|
||||
@@ -7295,6 +7451,179 @@ class WAS_Lora_Loader:
|
||||
lora_path = comfy_paths.get_full_path("loras", lora_name)
|
||||
model_lora, clip_lora = comfy.sd.load_lora_for_models(model, clip, lora_path, strength_model, strength_clip)
|
||||
return (model_lora, clip_lora, os.path.splitext(os.path.basename(lora_name))[0])
|
||||
|
||||
class WAS_Upscale_Model_Loader:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {"required": { "model_name": (comfy_paths.get_filename_list("upscale_models"), ),
|
||||
}}
|
||||
RETURN_TYPES = ("UPSCALE_MODEL",TEXT_TYPE)
|
||||
RETURN_NAMES = ("UPSCALE_MODEL","MODEL_NAME_TEXT")
|
||||
FUNCTION = "load_model"
|
||||
|
||||
CATEGORY = "WAS Suite/Loaders"
|
||||
|
||||
def load_model(self, model_name):
|
||||
model_path = comfy_paths.get_full_path("upscale_models", model_name)
|
||||
sd = comfy.utils.load_torch_file(model_path)
|
||||
out = model_loading.load_state_dict(sd).eval()
|
||||
return (out,model_name)
|
||||
|
||||
# VIDEO WRITER
|
||||
|
||||
class WAS_Video_Writer:
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"image": ("IMAGE",),
|
||||
"transition_frames": ("INT", {"default":30, "min":0, "max":120, "step":1}),
|
||||
"image_delay_sec": ("FLOAT", {"default":2.5, "min":0.1, "max":60000.0, "step":0.1}),
|
||||
"fps": ("INT", {"default":30, "min":1, "max":60.0, "step":1}),
|
||||
"max_size": ("INT", {"default":512, "min":128, "max":1920, "step":1}),
|
||||
"output_path": ("STRING", {"default": "./ComfyUI/output", "multiline": False}),
|
||||
"filename": ("STRING", {"default": "comfy_writer", "multiline": False}),
|
||||
"codec": (["AVC1","FFV1","H264","MP4V"],),
|
||||
}
|
||||
}
|
||||
|
||||
#@classmethod
|
||||
#def IS_CHANGED(cls, **kwargs):
|
||||
# return float("NaN")
|
||||
|
||||
RETURN_TYPES = ("IMAGE",TEXT_TYPE,TEXT_TYPE)
|
||||
RETURN_NAMES = ("IMAGE_PASS","filepath_text","filename_text")
|
||||
FUNCTION = "write_video"
|
||||
|
||||
CATEGORY = "WAS Suite/Animation/Writer"
|
||||
|
||||
def write_video(self, image, transition_frames=10, image_delay_sec=10, fps=30, max_size=512,
|
||||
output_path="./ComfyUI/output", filename="morph", codec="H264"):
|
||||
|
||||
conf = getSuiteConfig()
|
||||
if not conf.__contains__('ffmpeg_bin_path'):
|
||||
print(f"\033[34mWAS Node Suite\033[0m Error: Unable to use MP4 Writer because the `ffmpeg_bin_path` is not set in `{WAS_CONFIG_FILE}`")
|
||||
return (image,"","")
|
||||
|
||||
if conf.__contains__('ffmpeg_bin_path'):
|
||||
if conf['ffmpeg_bin_path'] != "/path/to/ffmpeg":
|
||||
sys.path.append(conf['ffmpeg_bin_path'])
|
||||
os.environ["OPENCV_FFMPEG_CAPTURE_OPTIONS"] = "rtsp_transport;udp"
|
||||
os.environ['OPENCV_FFMPEG_BINARY'] = conf['ffmpeg_bin_path']
|
||||
|
||||
if output_path.strip() in [None, "", "."]:
|
||||
output_path = "./ComfyUI/output"
|
||||
|
||||
if image == None:
|
||||
image = pil2tensor(Image.new("RGB", (512,512), (0,0,0)))
|
||||
|
||||
if transition_frames < 0:
|
||||
transition_frames = 0
|
||||
elif transition_frames > 60:
|
||||
transition_frames = 60
|
||||
|
||||
if fps < 1:
|
||||
fps = 1
|
||||
elif fps > 60:
|
||||
fps = 60
|
||||
|
||||
image = self.rescale_image(tensor2pil(image), max_size)
|
||||
|
||||
tokens = TextTokens()
|
||||
output_path = os.path.abspath(os.path.join(*tokens.parseTokens(output_path).split('/')))
|
||||
output_file = os.path.join(output_path, tokens.parseTokens(filename))
|
||||
|
||||
if not os.path.exists(output_path):
|
||||
os.makedirs(output_path, exist_ok=True)
|
||||
|
||||
WTools = WAS_Tools_Class()
|
||||
MP4Writer = WTools.VideoWriter(int(transition_frames), int(fps), int(image_delay_sec), codec)
|
||||
path = MP4Writer.write(image, output_file)
|
||||
|
||||
return (pil2tensor(image), path, filename)
|
||||
|
||||
def rescale_image(self, image, max_dimension):
|
||||
width, height = image.size
|
||||
if width > max_dimension or height > max_dimension:
|
||||
scaling_factor = max(width, height) / max_dimension
|
||||
new_width = int(width / scaling_factor)
|
||||
new_height = int(height / scaling_factor)
|
||||
image = image.resize((new_width, new_height), Image.Resampling(1))
|
||||
return image
|
||||
|
||||
# VIDEO CREATOR
|
||||
|
||||
class WAS_Create_Video_From_Path:
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"transition_frames": ("INT", {"default":30, "min":0, "max":120, "step":1}),
|
||||
"image_delay_sec": ("FLOAT", {"default":2.5, "min":0.01, "max":60000.0, "step":0.01}),
|
||||
"fps": ("INT", {"default":30, "min":1, "max":60.0, "step":1}),
|
||||
"max_size": ("INT", {"default":512, "min":128, "max":1920, "step":1}),
|
||||
"input_path": ("STRING", {"default": "./ComfyUI/input", "multiline": False}),
|
||||
"output_path": ("STRING", {"default": "./ComfyUI/output", "multiline": False}),
|
||||
"filename": ("STRING", {"default": "comfy_video", "multiline": False}),
|
||||
"codec": (["AVC1","FFV1","H264","MP4V"],),
|
||||
}
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def IS_CHANGED(cls, **kwargs):
|
||||
return float("NaN")
|
||||
|
||||
RETURN_TYPES = (TEXT_TYPE,TEXT_TYPE)
|
||||
RETURN_NAMES = ("filepath_text","filename_text")
|
||||
FUNCTION = "create_video_from_path"
|
||||
|
||||
CATEGORY = "WAS Suite/Animation"
|
||||
|
||||
def create_video_from_path(self, transition_frames=10, image_delay_sec=10, fps=30, max_size=512,
|
||||
input_path="./ComfyUI/input", output_path="./ComfyUI/output", filename="morph", codec="H264"):
|
||||
|
||||
conf = getSuiteConfig()
|
||||
if not conf.__contains__('ffmpeg_bin_path'):
|
||||
print(f"\033[34mWAS Node Suite\033[0m Error: Unable to use MP4 Writer because the `ffmpeg_bin_path` is not set in `{WAS_CONFIG_FILE}`")
|
||||
return ("","")
|
||||
|
||||
if conf.__contains__('ffmpeg_bin_path'):
|
||||
if conf['ffmpeg_bin_path'] != "/path/to/ffmpeg":
|
||||
sys.path.append(conf['ffmpeg_bin_path'])
|
||||
os.environ["OPENCV_FFMPEG_CAPTURE_OPTIONS"] = "rtsp_transport;udp"
|
||||
os.environ['OPENCV_FFMPEG_BINARY'] = conf['ffmpeg_bin_path']
|
||||
|
||||
if output_path.strip() in [None, "", "."]:
|
||||
output_path = "./ComfyUI/output"
|
||||
|
||||
if transition_frames < 0:
|
||||
transition_frames = 0
|
||||
elif transition_frames > 60:
|
||||
transition_frames = 60
|
||||
|
||||
if fps < 1:
|
||||
fps = 1
|
||||
elif fps > 60:
|
||||
fps = 60
|
||||
|
||||
tokens = TextTokens()
|
||||
output_path = os.path.abspath(os.path.join(*tokens.parseTokens(output_path).split('/')))
|
||||
output_file = os.path.join(output_path, tokens.parseTokens(filename))
|
||||
|
||||
if not os.path.exists(output_path):
|
||||
os.makedirs(output_path, exist_ok=True)
|
||||
|
||||
WTools = WAS_Tools_Class()
|
||||
MP4Writer = WTools.VideoWriter(int(transition_frames), int(fps), int(image_delay_sec), max_size, codec)
|
||||
path = MP4Writer.create_video(input_path, output_file)
|
||||
|
||||
return (path, filename)
|
||||
|
||||
# NODE MAPPING
|
||||
NODE_CLASS_MAPPINGS = {
|
||||
@@ -7306,6 +7635,7 @@ NODE_CLASS_MAPPINGS = {
|
||||
"Create Grid Image": WAS_Image_Grid_Image,
|
||||
"Create Morph Image": WAS_Image_Morph_GIF,
|
||||
"Create Morph Image from Path": WAS_Image_Morph_GIF_By_Path,
|
||||
"Create Video from Path (Experimental)": WAS_Create_Video_From_Path,
|
||||
"Debug Number to Console": WAS_Debug_Number_to_Console,
|
||||
"Dictionary to Console": WAS_Dictionary_To_Console,
|
||||
"Diffusers Model Loader": WAS_Diffusers_Loader,
|
||||
@@ -7417,7 +7747,42 @@ NODE_CLASS_MAPPINGS = {
|
||||
"Text to String": WAS_Text_To_String,
|
||||
"True Random.org Number Generator": WAS_True_Random_Number,
|
||||
"unCLIP Checkpoint Loader": WAS_unCLIP_Checkpoint_Loader,
|
||||
"Write to Morph GIF": WAS_Image_Morph_GIF_Writer,
|
||||
}
|
||||
"Upscale Model Loader": WAS_Upscale_Model_Loader,
|
||||
"Write to GIF": WAS_Image_Morph_GIF_Writer,
|
||||
"Write to Video (Experimental)": WAS_Video_Writer,
|
||||
}
|
||||
|
||||
# opencv-python-headless handling
|
||||
if 'opencv-python' in packages() or 'opencv-python-headless' in packages():
|
||||
try:
|
||||
import cv2
|
||||
build_info = ' '.join(cv2.getBuildInformation().split())
|
||||
if "FFMPEG: YES" in build_info:
|
||||
print("\033[34mWAS Node Suite:\033[0m OpenCV Python FFMPEG support is enabled")
|
||||
if was_config.__contains__('ffmpeg_bin_path'):
|
||||
if was_config['ffmpeg_bin_path'] == "/path/to/ffmpeg":
|
||||
print(f"\033[34mWAS Node Suite\033[0m Warning: `ffmpeg_bin_path` is not set in `{WAS_CONFIG_FILE}` config file. Will attempt to use system ffmpeg binaries if available.")
|
||||
else:
|
||||
print("\033[34mWAS Node Suite:\033[0m `ffmpeg_bin_path` is set to:", was_config['ffmpeg_bin_path'])
|
||||
else:
|
||||
print("\033[34mWAS Node Suite: \033[93mOpenCV Python FFMPEG support is not enabled\033[0m. OpenCV Python FFMPEG support, and FFMPEG binaries is required for video writing.")
|
||||
except ImportError:
|
||||
print("\033[34mWAS Node Suite: \033[93mOpenCV Python module cannot be found. Attempting install...")
|
||||
subprocess.check_call([sys.executable, '-m', 'pip', 'uninstall', 'opencv-python', 'opencv-python-headless[ffmpeg]'])
|
||||
subprocess.check_call([sys.executable, '-m', 'pip', 'install', 'opencv-python-headless[ffmpeg]'])
|
||||
try:
|
||||
import cv2
|
||||
print("\033[34mWAS Node Suite:\033[0m OpenCV Python installed.")
|
||||
except ImportError:
|
||||
print("\033[34mWAS Node Suite: \033[93mOpenCV Python module still cannot be imported. There is a system conflict.")
|
||||
else:
|
||||
print("\033[34mWAS Node Suite:\033[0m Installing `opencv-python-headless` ...")
|
||||
subprocess.check_call([sys.executable, '-m', 'pip', 'install', 'opencv-python-headless[ffmpeg]'])
|
||||
try:
|
||||
import cv2
|
||||
print("\033[34mWAS Node Suite:\033[0m OpenCV Python installed.")
|
||||
except ImportError:
|
||||
print("\033[34mWAS Node Suite: \033[93mOpenCV Python module still cannot be imported. There is a system conflict.")
|
||||
|
||||
# Well we got here, we're as loaded as we're gonna get.
|
||||
print('\033[34mWAS Node Suite: \033[92mLoaded\033[0m')
|
||||
|
||||
+2
-3
@@ -3,11 +3,10 @@ pythonperlin
|
||||
git+https://github.com/WASasquatch/img2texture.git
|
||||
matplotlib
|
||||
scikit-learn
|
||||
opencv-python
|
||||
opencv-python-headless[ffmpeg]
|
||||
timm>=0.4.12
|
||||
transformers==4.26.1
|
||||
gitpython
|
||||
fairscale>=0.4.4
|
||||
face_recognition
|
||||
imageio
|
||||
tifffile
|
||||
imageio
|
||||
Reference in New Issue
Block a user