Add clipspace feature.

* feat: copy content to clipspace
* feat: paste content from clipspace

Extend validation to allow for validating annotated_path in addition to other parameters.

Add support for annotated_filepath in folder_paths function.

Generalize the '/upload/image' API to allow for uploading images to the 'input', 'temp', or 'output' directories.
This commit is contained in:
Lt.Dr.Data 2023-04-20 17:59:35 +09:00 committed by Dr.Lt.Data
parent 96b57a9ad6
commit 9e8c60e9f3
5 changed files with 106 additions and 9 deletions

View File

@ -11,6 +11,7 @@ import torch
import nodes
import comfy.model_management
import folder_paths
def get_input_data(inputs, class_def, unique_id, outputs={}, prompt={}, extra_data={}):
valid_inputs = class_def.INPUT_TYPES()
@ -250,7 +251,12 @@ def validate_inputs(prompt, item):
return (False, "Value bigger than max. {}, {}".format(class_type, x))
if isinstance(type_input, list):
if val not in type_input:
is_annotated_path = val.endswith("[temp]") or val.endswith("[input]") or val.endswith("[output]")
if is_annotated_path:
if not folder_paths.exists_annotated_filepath(val):
return (False, "Invalid file path. {}, {}: {}".format(class_type, x, val))
elif val not in type_input:
return (False, "Value not in list. {}, {}: {} not in {}".format(class_type, x, val, type_input))
return (True, "")

View File

@ -68,6 +68,41 @@ def get_directory_by_type(type_name):
return None
# determine base_dir rely on annotation if name is 'filename.ext [annotation]' format
# otherwise use default_path as base_dir
def get_annotated_filepath(name, default_dir=None):
if name.endswith("[output]"):
base_dir = get_output_directory()
name = name[:-9]
elif name.endswith("[input]"):
base_dir = get_input_directory()
name = name[:-8]
elif name.endswith("[temp]"):
base_dir = get_temp_directory()
name = name[:-7]
elif default_dir is not None:
base_dir = default_dir
else:
base_dir = get_input_directory() # fallback path
return os.path.join(base_dir, name)
def exists_annotated_filepath(name):
if name.endswith("[output]"):
base_dir = get_output_directory()
name = name[:-9]
elif name.endswith("[temp]"):
base_dir = get_temp_directory()
name = name[:-7]
else:
base_dir = get_input_directory()
name = name[:-8]
filepath = os.path.join(base_dir, name)
return os.path.exists(filepath)
def add_model_folder_path(folder_name, full_folder_path):
global folder_names_and_paths
if folder_name in folder_names_and_paths:

View File

@ -975,7 +975,7 @@ class LoadImage:
FUNCTION = "load_image"
def load_image(self, image):
input_dir = folder_paths.get_input_directory()
image_path = os.path.join(input_dir, image)
image_path = folder_paths.get_annotated_filepath(image, input_dir)
i = Image.open(image_path)
image = i.convert("RGB")
image = np.array(image).astype(np.float32) / 255.0
@ -990,7 +990,7 @@ class LoadImage:
@classmethod
def IS_CHANGED(s, image):
input_dir = folder_paths.get_input_directory()
image_path = os.path.join(input_dir, image)
image_path = folder_paths.get_annotated_filepath(image, input_dir)
m = hashlib.sha256()
with open(image_path, 'rb') as f:
m.update(f.read())
@ -1011,7 +1011,7 @@ class LoadImageMask:
FUNCTION = "load_image"
def load_image(self, image, channel):
input_dir = folder_paths.get_input_directory()
image_path = os.path.join(input_dir, image)
image_path = folder_paths.get_annotated_filepath(image, input_dir)
i = Image.open(image_path)
if i.getbands() != ("R", "G", "B", "A"):
i = i.convert("RGBA")
@ -1029,7 +1029,7 @@ class LoadImageMask:
@classmethod
def IS_CHANGED(s, image, channel):
input_dir = folder_paths.get_input_directory()
image_path = os.path.join(input_dir, image)
image_path = folder_paths.get_annotated_filepath(image, input_dir)
m = hashlib.sha256()
with open(image_path, 'rb') as f:
m.update(f.read())

View File

@ -112,13 +112,20 @@ class PromptServer():
@routes.post("/upload/image")
async def upload_image(request):
upload_dir = folder_paths.get_input_directory()
post = await request.post()
image = post.get("image")
if post.get("type") is None:
upload_dir = folder_paths.get_input_directory()
elif post.get("type") == "input":
upload_dir = folder_paths.get_input_directory()
elif post.get("type") == "temp":
upload_dir = folder_paths.get_temp_directory()
elif post.get("type") == "output":
upload_dir = folder_paths.get_output_directory()
if not os.path.exists(upload_dir):
os.makedirs(upload_dir)
post = await request.post()
image = post.get("image")
if image and image.file:
filename = image.filename

View File

@ -20,6 +20,12 @@ export class ComfyApp {
*/
#processingQueue = false;
/**
* Content Clipboard
* @type {serialized node object}
*/
static contentClipboard = null;
constructor() {
this.ui = new ComfyUI(this);
@ -130,6 +136,49 @@ export class ComfyApp {
);
}
}
options.push(
{
content: "Copy (Clipspace)",
callback: (obj) => {
console.log(this);
var widgets = null;
if(this.widgets) {
widgets = this.widgets.map(({ type, name, value }) => ({ type, name, value }));
}
ComfyApp.contentClipboard = { 'widgets': widgets, 'imgs': this.imgs, 'images': this.images };
}
},
{
content: "Paste (Clipspace)",
callback: () => {
if(ComfyApp.contentClipboard != null) {
console.log(ComfyApp.contentClipboard.widgets);
if(ComfyApp.contentClipboard.widgets != null) {
ComfyApp.contentClipboard.widgets.forEach(({ type, name, value }) => {
const prop = Object.values(this.widgets).find(obj => obj.type === type && obj.name === name);
if (prop) {
prop.value = value;
}
});
}
if(ComfyApp.contentClipboard.imgs != undefined && this.imgs != undefined) {
this.imgs = ComfyApp.contentClipboard.imgs;
this.images = ComfyApp.contentClipboard.images;
const index = this.widgets.findIndex(obj => obj.name === 'image');
if(index >= 0) {
let filename = `${this.images[0].filename} [${this.images[0].type}]`;
this.widgets[index].value = filename;
if(this.widgets_values != undefined) {
this.widgets_values[index] = filename;
}
}
}
}
}
}
);
};
}