diff --git a/comfy/cli_args.py b/comfy/cli_args.py index c99c9e65e..a864205be 100644 --- a/comfy/cli_args.py +++ b/comfy/cli_args.py @@ -1,7 +1,6 @@ import argparse import enum import os -from typing import Optional import comfy.options @@ -166,13 +165,14 @@ parser.add_argument( """, ) -def is_valid_directory(path: Optional[str]) -> Optional[str]: - """Validate if the given path is a directory.""" - if path is None: - return None - +def is_valid_directory(path: str) -> str: + """Validate if the given path is a directory, and check permissions.""" + if not os.path.exists(path): + raise argparse.ArgumentTypeError(f"The path '{path}' does not exist.") if not os.path.isdir(path): - raise argparse.ArgumentTypeError(f"{path} is not a valid directory.") + raise argparse.ArgumentTypeError(f"'{path}' is not a directory.") + if not os.access(path, os.R_OK): + raise argparse.ArgumentTypeError(f"You do not have read permissions for '{path}'.") return path parser.add_argument( diff --git a/comfy/comfy_types/__init__.py b/comfy/comfy_types/__init__.py index 19ec33f98..7640fbe3f 100644 --- a/comfy/comfy_types/__init__.py +++ b/comfy/comfy_types/__init__.py @@ -1,6 +1,6 @@ import torch from typing import Callable, Protocol, TypedDict, Optional, List -from .node_typing import IO, InputTypeDict, ComfyNodeABC, CheckLazyMixin +from .node_typing import IO, InputTypeDict, ComfyNodeABC, CheckLazyMixin, FileLocator class UnetApplyFunction(Protocol): @@ -42,4 +42,5 @@ __all__ = [ InputTypeDict.__name__, ComfyNodeABC.__name__, CheckLazyMixin.__name__, + FileLocator.__name__, ] diff --git a/comfy/comfy_types/node_typing.py b/comfy/comfy_types/node_typing.py index 0696dbe5e..fe130567d 100644 --- a/comfy/comfy_types/node_typing.py +++ b/comfy/comfy_types/node_typing.py @@ -134,6 +134,8 @@ class InputTypeOptions(TypedDict): """ remote: RemoteInputOptions """Specifies the configuration for a remote input.""" + control_after_generate: bool + """Specifies whether a control widget should be added to the input, adding options to automatically change the value after each prompt is queued. Currently only used for INT and COMBO types.""" class HiddenInputTypeDict(TypedDict): @@ -293,3 +295,14 @@ class CheckLazyMixin: need = [name for name in kwargs if kwargs[name] is None] return need + + +class FileLocator(TypedDict): + """Provides type hinting for the file location""" + + filename: str + """The filename of the file.""" + subfolder: str + """The subfolder of the file.""" + type: Literal["input", "output", "temp"] + """The root folder of the file.""" diff --git a/comfy_extras/nodes_audio.py b/comfy_extras/nodes_audio.py index 3cb918e09..136ad6159 100644 --- a/comfy_extras/nodes_audio.py +++ b/comfy_extras/nodes_audio.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import torchaudio import torch import comfy.model_management @@ -10,6 +12,7 @@ import random import hashlib import node_helpers from comfy.cli_args import args +from comfy.comfy_types import FileLocator class EmptyLatentAudio: def __init__(self): @@ -164,7 +167,7 @@ class SaveAudio: def save_audio(self, audio, filename_prefix="ComfyUI", prompt=None, extra_pnginfo=None): filename_prefix += self.prefix_append full_output_folder, filename, counter, subfolder, filename_prefix = folder_paths.get_save_image_path(filename_prefix, self.output_dir) - results = list() + results: list[FileLocator] = [] metadata = {} if not args.disable_metadata: diff --git a/comfy_extras/nodes_custom_sampler.py b/comfy_extras/nodes_custom_sampler.py index 576fc3b2c..c9689b745 100644 --- a/comfy_extras/nodes_custom_sampler.py +++ b/comfy_extras/nodes_custom_sampler.py @@ -454,7 +454,7 @@ class SamplerCustom: return {"required": {"model": ("MODEL",), "add_noise": ("BOOLEAN", {"default": True}), - "noise_seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff}), + "noise_seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff, "control_after_generate": True}), "cfg": ("FLOAT", {"default": 8.0, "min": 0.0, "max": 100.0, "step":0.1, "round": 0.01}), "positive": ("CONDITIONING", ), "negative": ("CONDITIONING", ), @@ -605,10 +605,16 @@ class DisableNoise: class RandomNoise(DisableNoise): @classmethod def INPUT_TYPES(s): - return {"required":{ - "noise_seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff}), - } - } + return { + "required": { + "noise_seed": ("INT", { + "default": 0, + "min": 0, + "max": 0xffffffffffffffff, + "control_after_generate": True, + }), + } + } def get_noise(self, noise_seed): return (Noise_RandomNoise(noise_seed),) diff --git a/comfy_extras/nodes_images.py b/comfy_extras/nodes_images.py index af37666b2..e11a4583a 100644 --- a/comfy_extras/nodes_images.py +++ b/comfy_extras/nodes_images.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import nodes import folder_paths from comfy.cli_args import args @@ -9,6 +11,8 @@ import numpy as np import json import os +from comfy.comfy_types import FileLocator + MAX_RESOLUTION = nodes.MAX_RESOLUTION class ImageCrop: @@ -99,7 +103,7 @@ class SaveAnimatedWEBP: method = self.methods.get(method) filename_prefix += self.prefix_append full_output_folder, filename, counter, subfolder, filename_prefix = folder_paths.get_save_image_path(filename_prefix, self.output_dir, images[0].shape[1], images[0].shape[0]) - results = list() + results: list[FileLocator] = [] pil_images = [] for image in images: i = 255. * image.cpu().numpy() diff --git a/comfy_extras/nodes_video.py b/comfy_extras/nodes_video.py index 53920ba18..97ca513d8 100644 --- a/comfy_extras/nodes_video.py +++ b/comfy_extras/nodes_video.py @@ -1,9 +1,12 @@ +from __future__ import annotations + import os import av import torch import folder_paths import json from fractions import Fraction +from comfy.comfy_types import FileLocator class SaveWEBM: @@ -62,7 +65,7 @@ class SaveWEBM: container.mux(stream.encode()) container.close() - results = [{ + results: list[FileLocator] = [{ "filename": file, "subfolder": subfolder, "type": self.type diff --git a/nodes.py b/nodes.py index f7f6cb156..bbf49915c 100644 --- a/nodes.py +++ b/nodes.py @@ -25,7 +25,7 @@ import comfy.sample import comfy.sd import comfy.utils import comfy.controlnet -from comfy.comfy_types import IO, ComfyNodeABC, InputTypeDict +from comfy.comfy_types import IO, ComfyNodeABC, InputTypeDict, FileLocator import comfy.clip_vision @@ -479,7 +479,7 @@ class SaveLatent: file = f"{filename}_{counter:05}_.latent" - results = list() + results: list[FileLocator] = [] results.append({ "filename": file, "subfolder": subfolder, @@ -1519,7 +1519,7 @@ class KSampler: return { "required": { "model": ("MODEL", {"tooltip": "The model used for denoising the input latent."}), - "seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff, "tooltip": "The random seed used for creating the noise."}), + "seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff, "control_after_generate": True, "tooltip": "The random seed used for creating the noise."}), "steps": ("INT", {"default": 20, "min": 1, "max": 10000, "tooltip": "The number of steps used in the denoising process."}), "cfg": ("FLOAT", {"default": 8.0, "min": 0.0, "max": 100.0, "step":0.1, "round": 0.01, "tooltip": "The Classifier-Free Guidance scale balances creativity and adherence to the prompt. Higher values result in images more closely matching the prompt however too high values will negatively impact quality."}), "sampler_name": (comfy.samplers.KSampler.SAMPLERS, {"tooltip": "The algorithm used when sampling, this can affect the quality, speed, and style of the generated output."}), @@ -1547,7 +1547,7 @@ class KSamplerAdvanced: return {"required": {"model": ("MODEL",), "add_noise": (["enable", "disable"], ), - "noise_seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff}), + "noise_seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff, "control_after_generate": True}), "steps": ("INT", {"default": 20, "min": 1, "max": 10000}), "cfg": ("FLOAT", {"default": 8.0, "min": 0.0, "max": 100.0, "step":0.1, "round": 0.01}), "sampler_name": (comfy.samplers.KSampler.SAMPLERS, ),