merge tianlang0704/multiuser

This commit is contained in:
zombieyang
2024-10-03 11:05:09 +08:00
16 changed files with 2777 additions and 7 deletions
+166
View File
@@ -1,3 +1,4 @@
<<<<<<< HEAD:sdppp_python/comfy_nodes_legacy.py
import numpy as np
from nodes import LoadImage
from PIL import Image
@@ -198,3 +199,168 @@ def define_comfyui_nodes_legacy(sdppp):
'ImageTimesOpacity': ImageTimesOpacity,
'MaskTimesOpacity': MaskTimesOpacity,
}
=======
import numpy as np
import time
from nodes import LoadImage
from .apis import cache_images
from .photoshop_manager import PhotoshopManager
from server import PromptServer
prompt_server = PromptServer.instance
def define_comfyui_nodes(sdppp):
def call_async_func_in_server_thread(coro, dontwait = False):
handle = {
'done': False,
'result': None
}
loop = sdppp.loop
async def do_call():
handle['result'] = await coro
handle['done'] = True
loop.call_soon(lambda: loop.create_task(do_call()))
if not dontwait:
while not handle['done']:
pass
return handle['result']
else:
return None
class GetImageFromPhotoshopLayerNode:
# !!!!do not validate because there's no client_id when validating, so we validate manually before execution!!!!
# @classmethod
# def VALIDATE_INPUTS():
# pass
@classmethod
def IS_CHANGED(self, layer, use_layer_bounds):
photoshopInstance = PhotoshopManager.instance().instance_from_client_id(prompt_server.client_id)
if not photoshopInstance:
return np.random.rand()
else:
id = photoshopInstance.layer_name_to_id(layer)
bounds_id = photoshopInstance.layer_name_to_id(use_layer_bounds, id)
start = time.time()
is_changed, history_state_id = call_async_func_in_server_thread(photoshopInstance.check_layer_bounds_combo_changed(id, bounds_id))
if is_changed and history_state_id is None:
return photoshopInstance.update_comfyui_last_value(id, bounds_id, np.random.rand())
return photoshopInstance.update_comfyui_last_value(id, bounds_id, history_state_id)
@classmethod
def INPUT_TYPES(cls):
layer_strs = []
bounds_strs = []
return {
"required": {},
"optional": {
"layer": (layer_strs, {"default": layer_strs[0] if len(layer_strs) > 0 else None}),
"use_layer_bounds": (bounds_strs, {"default": bounds_strs[0] if len(bounds_strs) > 0 else None}),
}
}
RETURN_TYPES = ("IMAGE", "MASK", "FLOAT")
RETURN_NAMES = ("image_out", "mask_out", "layer_opacity")
FUNCTION = "get_image"
CATEGORY = "Photoshop"
def get_image(self, layer, use_layer_bounds):
photoshopInstance = PhotoshopManager.instance().instance_from_client_id(prompt_server.client_id)
if not photoshopInstance:
raise ValueError('Photoshop is not connected')
layer_strs = photoshopInstance.get_base_layers()
if layer not in layer_strs:
raise ValueError(f"Layer {layer} not found in Photoshop")
bounds_strs = photoshopInstance.get_bounds_layers()
if use_layer_bounds not in bounds_strs:
raise ValueError(f"Layer {use_layer_bounds} not found in Photoshop")
id = photoshopInstance.layer_name_to_id(layer)
bounds_id = photoshopInstance.layer_name_to_id(use_layer_bounds, id)
start = time.time()
image_id, layer_opacity = call_async_func_in_server_thread(photoshopInstance.get_image(layer_id=id, bounds_id=bounds_id))
loadImage = LoadImage()
(output_image, output_mask) = loadImage.load_image(image_id)
return (output_image, output_mask, layer_opacity / 100)
class SendImageToPhotoshopLayerNode:
# !!!!do not validate because there's no client_id when validating, so we validate manually before execution!!!!
# @classmethod
# def VALIDATE_INPUTS():
# pass
@classmethod
def INPUT_TYPES(cls):
layer_strs = []
return {
"required": {},
"optional": {
"images": ("IMAGE", ),
"layer": (layer_strs, {"default": layer_strs[0] if len(layer_strs) > 0 else None}),
}
}
RETURN_TYPES = ()
FUNCTION = "send_image"
CATEGORY = "Photoshop"
OUTPUT_NODE = True
def send_image(self, images, layer):
photoshopInstance = PhotoshopManager.instance().instance_from_client_id(prompt_server.client_id)
if not photoshopInstance:
raise ValueError('Photoshop is not connected')
ret = cache_images(images)
layer_id = photoshopInstance.layer_name_to_id(layer)
call_async_func_in_server_thread(photoshopInstance.send_images(image_ids=ret, layer_id=layer_id), True)
return (None,)
class ImageTimesOpacity:
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"images": ("IMAGE", ),
"opacity": ("FLOAT", {"default": 1.0, "min": 0.01, "max": 1.0, "step": 0.01}),
}
}
RETURN_TYPES = ("IMAGE",)
FUNCTION = "image_times_opacity"
CATEGORY = "Photoshop"
def image_times_opacity(self, images, opacity):
image_out = images * opacity
return (image_out,)
class MaskTimesOpacity:
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"masks": ("MASK", ),
"opacity": ("FLOAT", {"default": 1.0, "min": 0.01, "max": 1.0, "step": 0.01}),
}
}
RETURN_TYPES = ("MASK",)
FUNCTION = "mask_times_opacity"
CATEGORY = "Photoshop"
def mask_times_opacity(self, masks, opacity):
mask_out = masks * opacity
return (mask_out,)
return {
'GetImageFromPhotoshopLayerNode': GetImageFromPhotoshopLayerNode,
'SendImageToPhotoshopLayerNode': SendImageToPhotoshopLayerNode,
'ImageTimesOpacity': ImageTimesOpacity,
'MaskTimesOpacity': MaskTimesOpacity,
}
>>>>>>> tl/develop_sio_multi_user:comfy/nodes.py
+116
View File
@@ -0,0 +1,116 @@
import asyncio
from .photoshop_instance import PhotoshopInstance
DEFAULT_ID = 0
class PhotoshopManager:
_instance = None
@classmethod
def instance(cls) -> 'PhotoshopManager':
if cls._instance is None:
cls._instance = PhotoshopManager()
return cls._instance
def __init__(self):
# client id info
self.ip_to_client_id_list = {}
self.client_id_to_userid = {}
# ps instance info
self.ip_to_ps_instance_list = {}
# final match
self.client_id_to_ps_instance = {}
# get instance from client information, match new instance if not exist
def instance_from_client_info(self, ip, client_id, user_id) -> PhotoshopInstance:
if not ip: return None
if not client_id: user_id = 0
if not user_id: user_id = 0
instance = self.instance_from_client_id(client_id)
if instance is not None:
return instance
self._add_new_client(ip, client_id, user_id)
self._client_match_ps(ip, client_id, user_id)
return self.instance_from_client_id(client_id)
# get instance from client id
def instance_from_client_id(self, client_id) -> PhotoshopInstance:
return self.client_id_to_ps_instance.get(client_id, None)
# create new instance and record
async def new_ps_instance(self, sdppp, sid, ip, user_id) -> PhotoshopInstance:
if not ip: return None
if not user_id: user_id = 0
# create new instance
instance = PhotoshopInstance(sdppp, sid, user_id)
instance.on_destroy = self._on_instance_destroy
# record new instance
await self._add_new_ps_instance(ip, instance)
return instance
#------------------------------------------------------------------------------------------------------------------------------------------------
# callback when instance is destroyed and remove from record
def _on_instance_destroy(self, instance):
self._remove_instance(instance)
# record new ps instance for matching
async def _add_new_ps_instance(self, ip, instance):
# find old one and destroy
same_ip_list = self.ip_to_ps_instance_list.get(ip, [])
for check_instance in same_ip_list:
if check_instance.uid == instance.uid:
await check_instance.destroy()
# record ip to instance
ps_instance_list = self.ip_to_ps_instance_list.get(ip, [])
if instance not in ps_instance_list:
ps_instance_list.append(instance)
self.ip_to_ps_instance_list[ip] = ps_instance_list
# record new client id
def _add_new_client(self, ip, client_id, user_id):
# record client id, if exist, do nothing
client_id_list = self.ip_to_client_id_list.get(ip, [])
if client_id in client_id_list:
return
client_id_list.append(client_id)
self.ip_to_client_id_list[ip] = client_id_list
# record user_id
self.client_id_to_userid[client_id] = user_id
# match client id to ps instance for prompt use
# there are several ways to match:
# 1. one to one match(local network most likely): same ip client and ps instance, assign to it
# 2. multiple ps instance under the same ip(remote service most likely), check user id
def _client_match_ps(self, ip, client_id, user_id):
# check existing pair
instance = self.client_id_to_ps_instance.get(client_id, None)
if instance is not None:
return
# check same ip ps instance
ps_instance_list = self.ip_to_ps_instance_list.get(ip, [])
count = len(ps_instance_list)
if count <= 0:
return
# if only one same ip ps instance, assign client id
if count == 1:
self.client_id_to_ps_instance[client_id] = ps_instance_list[0]
return
# if more than one same ip ps instance, check same user_id, same ip + same user id = same ps instance
ps_user_id_list = [instance.uid for instance in ps_instance_list]
try:
check_index = ps_user_id_list.index(user_id)
except:
check_index = None
if check_index is None:
return
self.client_id_to_ps_instance[client_id] = ps_instance_list[check_index]
def _remove_instance(self, instance):
# remove id from ip to instance list
for ip, ps_instance_list in self.ip_to_ps_instance_list.items():
if instance in ps_instance_list:
ps_instance_list.remove(instance)
# remove instance from client id to instance
for client_id in list(self.client_id_to_ps_instance.keys()):
check_instance = self.client_id_to_ps_instance.get(client_id, None)
if check_instance == instance:
self.client_id_to_ps_instance.pop(client_id, None)