Skip to content
This repository has been archived by the owner on Aug 11, 2024. It is now read-only.

Commit

Permalink
Merge pull request #47 from xingren23/dev
Browse files Browse the repository at this point in the history
support more output node, 'PreviewImage', 'SaveImage', 'SaveAnimatedWEBP', 'SaveAnimatedPNG', 'VHS_VideoCombine'
  • Loading branch information
xingren23 authored Dec 18, 2023
2 parents ac6ae24 + 948afd3 commit c171360
Show file tree
Hide file tree
Showing 6 changed files with 95 additions and 50 deletions.
2 changes: 1 addition & 1 deletion .streamlit/config.toml
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ enableXsrfProtection = true
# Max size, in megabytes, for files uploaded with the file_uploader.

# Default: 200
maxUploadSize = 10
maxUploadSize = 20

# Max size, in megabytes, of messages that can be sent via the WebSocket
# connection.
Expand Down
2 changes: 1 addition & 1 deletion modules/authenticate.py
Original file line number Diff line number Diff line change
Expand Up @@ -262,7 +262,7 @@ def register_user_info(self, form_name: str, location: str = 'main', data: dict=

register_user_form.subheader(form_name)
new_email = register_user_form.text_input('Email', value=data['email'], help='Please enter a valid email address')
new_username = register_user_form.text_input('Username', value=data['username'], help='Please enter a username')
new_username = register_user_form.text_input('Username', value=data['username'], help='Please enter a username, 0-9, a-z, A-Z, -, _, max length 20')
new_name = register_user_form.text_input('Name', value=data['username'],help='Please enter your name')

new_password = register_user_form.text_input('Password', type='password')
Expand Down
7 changes: 6 additions & 1 deletion modules/comfyclient.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,11 @@ def get_image(self, filename, subfolder, folder_type):
if resp.status_code != 200:
raise Exception(f"Failed to get image from server, {resp.status_code}")
return resp.content

def get_image_url(self, filename, subfolder, folder_type):
url = f"{self.server_addr}/view?filename={filename}&subfolder={subfolder}&type={folder_type}"
logger.info(f"Getting image url, {url}")
return url

def upload_image(self, imagefile, subfolder, type, overwrite):
data = {"subfolder": subfolder, "type": type, "overwrite": overwrite}
Expand Down Expand Up @@ -123,7 +128,7 @@ def dispatch_event(queue, event):
# Dispatch executing event with msg["data"]["node"]
dispatch_event(queue, {"type": "executing", "data": msg["data"]["node"]})
if msg["data"]["node"] is None:
logger.info("worflow finished, exiting websocket loop")
logger.info("workflow finished, exiting websocket loop")
break
elif msg_type == "executed":
# Dispatch executed event with msg["data"]
Expand Down
37 changes: 26 additions & 11 deletions modules/comfyflow.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,22 +85,34 @@ def generate(self):
prompt_id = self.comfy_client.gen_images(prompt, queue)
st.session_state['preview_prompt_id'] = prompt_id

def get_output_images(self):
def get_outputs(self):
# get output images by prompt_id
prompt_id = st.session_state['preview_prompt_id']
if prompt_id is None:
return None
history = self.comfy_client.get_history(prompt_id)[prompt_id]
for node_id in self.app_json['outputs']:
node_output = history['outputs'][node_id]
logger.info(f"Got output from server, {node_id}, {node_output}")
if 'images' in node_output:
images_output = []
for image in node_output['images']:
image_data = self.comfy_client.get_image(image['filename'], image['subfolder'], image['type'])
images_output.append(image_data)
image_url = self.comfy_client.get_image_url(image['filename'], image['subfolder'], image['type'])
images_output.append(image_url)

logger.info(f"Got images from server, {node_id}, {len(images_output)}")
return images_output
return 'images', images_output
elif 'gifs' in node_output:
gifs_output = []
format = 'gifs'
for gif in node_output['gifs']:
if gif['format'] == 'image/gif' or gif['format'] == 'image/webp':
format = 'images'
gif_url = self.comfy_client.get_image_url(gif['filename'], gif['subfolder'], gif['type'])
gifs_output.append(gif_url)

logger.info(f"Got gifs from server, {node_id}, {len(gifs_output)}")
return format, gifs_output


def create_ui_input(self, node_id, node_inputs):
Expand Down Expand Up @@ -227,12 +239,15 @@ def create_ui(self, show_header=True):
elif event_type == 'executing':
node = event['data']
if node is None:
output_image = self.get_output_images()
if output_image is not None:
img_placeholder.image(output_image, use_column_width=True)
type, outputs = self.get_outputs()
if type == 'images' and outputs is not None:
img_placeholder.image(outputs, use_column_width=True)
elif type == 'gifs' and outputs is not None:
for output in outputs:
img_placeholder.markdown(f'<iframe src="{output}" width="100%" height="360px"></iframe>', unsafe_allow_html=True)

output_progress.progress(1.0, text="Generate image finished")
logger.info("Generating image finished")
output_progress.progress(1.0, text="Generate finished")
logger.info("Generating finished")
st.session_state[f'{app_name}_previewed'] = True
break
else:
Expand All @@ -243,9 +258,9 @@ def create_ui(self, show_header=True):
img_placeholder.image(preview_image, use_column_width=True, caption="Preview")
except Exception as e:
logger.warning(f"get progress exception, {e}")
st.warning(f"get progress exception {e}")
# st.warning(f"get progress exception {e}")
else:
output_image = Image.open('./public/images/output-none.png')
logger.info("default output_image")
logger.info("default output")
img_placeholder.image(output_image, use_column_width=True, caption='None Image, Generate it!')

95 changes: 60 additions & 35 deletions modules/new_app.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
from modules import get_comfyui_object_info, get_workspace_model

NODE_SEP = '||'
FAQ_URL = "https://github.com/xingren23/ComfyFlowApp/wiki/FAQ"
SUPPORTED_COMFYUI_CLASSTYPE_OUTPUT = ['PreviewImage', 'SaveImage', 'SaveAnimatedWEBP', 'SaveAnimatedPNG', 'VHS_VideoCombine']

def format_input_node_info(param):
# format {id}.{class_type}.{alias}.{param_name}
Expand All @@ -32,13 +34,14 @@ def process_workflow_meta(image_upload):
logger.info(f"process_workflow_meta, {image_upload}")
img = Image.open(image_upload)
tran_img = ImageOps.exif_transpose(img)
logger.debug(f"process_workflow_meta, {tran_img.info.get('workflow')} {tran_img.info.get('prompt')}")
return tran_img.info
except Exception as e:
logger.error(f"process_workflow_meta error, {e}")
return None


def parse_prompt(prompt_info):
def parse_prompt(prompt_info, object_info_meta):
# parse prompt to inputs and outputs
try:
prompt = json.loads(prompt_info)
Expand All @@ -64,10 +67,10 @@ def parse_prompt(prompt_info):
params_inputs.update({option_key: option_value})
node_inputs.append(param_value)

is_output = get_comfyui_object_info()[class_type]['output_node']
is_output = object_info_meta[class_type]['output_node']
if is_output:
# TODO: support multi output
if class_type == 'SaveImage':
if class_type in SUPPORTED_COMFYUI_CLASSTYPE_OUTPUT:
option_key = f"{node_id}{NODE_SEP}{class_type}"
if len(node_inputs) == 0:
option_value = f"{node_id}{NODE_SEP}{class_type}{NODE_SEP}None"
Expand All @@ -77,62 +80,71 @@ def parse_prompt(prompt_info):
else:
logger.warning(f"Only support SaveImage as output node, {class_type}")

return params_inputs, params_outputs
return (params_inputs, params_outputs)
except Exception as e:
logger.error(f"parse_prompt error, {e}")
return None, None
st.error(f"parse_prompt error, {e} refer to {FAQ_URL}")
return (None, None)


def process_image_change():
comfyui_object_info = st.session_state.get('comfyui_object_info')
upload_image = st.session_state['create_upload_image']
if upload_image:
metas = process_workflow_meta(upload_image)
if metas and 'prompt' in metas.keys() and 'workflow' in metas.keys():
st.session_state['create_prompt'] = metas.get('prompt')
st.session_state['create_workflow'] = metas.get('workflow')
inputs, outputs = parse_prompt(metas.get('prompt'))
if inputs and outputs:
inputs, outputs = parse_prompt(metas.get('prompt'), comfyui_object_info)
if inputs:
logger.info(f"create_prompt_inputs, {inputs}")
st.success(f"parse inputs from workflow image, input nodes {len(inputs)}")
st.session_state['create_prompt_inputs'] = inputs
else:
st.error(f"parse workflow from image error, inputs is None, refer to {FAQ_URL}")

if outputs:
logger.info(f"create_prompt_outputs, {outputs}")
st.session_state['create_prompt_outputs'] = outputs

st.success("parse workflow from image successfully")
st.success(f"parse outputs from workflow image, output nodes {len(outputs)}")
st.session_state['create_prompt_outputs'] = outputs
else:
st.error("parse workflow from image error, inputs or outputs is None")
st.error(f"parse workflow from image error, outputs is None, refer to {FAQ_URL}")

else:
st.error("the image don't contain workflow info")
st.error(f"the image don't contain workflow info, refer to {FAQ_URL}")
else:
st.session_state['create_prompt'] = None
st.session_state['create_workflow'] = None
st.session_state['create_prompt_inputs'] = {}
st.session_state['create_prompt_outputs'] = {}

def process_image_edit(api_prompt):
comfyui_object_info = st.session_state.get('comfyui_object_info')
if api_prompt:
st.session_state['create_prompt'] =api_prompt
inputs, outputs = parse_prompt(api_prompt)
if inputs and outputs:
inputs, outputs = parse_prompt(api_prompt, comfyui_object_info)
if inputs:
logger.info(f"create_prompt_inputs, {inputs}")
st.success(f"parse inputs from workflow image, input nodes {len(inputs)}")
st.session_state['create_prompt_inputs'] = inputs
else:
st.error(f"parse workflow from image error, inputs is None, refer to {FAQ_URL}")

if outputs:
logger.info(f"create_prompt_outputs, {outputs}")
st.session_state['create_prompt_outputs'] = outputs

st.success("parse workflow from image successfully")
st.success(f"parse outputs from workflow image, output nodes {len(outputs)}")
st.session_state['create_prompt_outputs'] = outputs
else:
st.error("parse workflow from image error, inputs or outputs is None")
st.error(f"parse workflow from image error, outputs is None, refer to {FAQ_URL}")
else:
st.error("the image don't contain workflow info")
st.error(f"the image don't contain workflow info, refer to {FAQ_URL}")


def get_node_input_config(input_param, app_input_name, app_input_description):
params_inputs = st.session_state.get('create_prompt_inputs', {})
option_params_value = params_inputs[input_param]
logger.debug(f"get_node_input_config, {input_param} {option_params_value}")
node_id, class_type, param, param_value = option_params_value.split(NODE_SEP)
comfyui_object_info = get_comfyui_object_info()
comfyui_object_info = st.session_state.get('comfyui_object_info')
class_meta = comfyui_object_info[class_type]
class_input = class_meta['input']['required']
if 'optional' in class_meta['input'].keys():
Expand Down Expand Up @@ -327,6 +339,12 @@ def edit_app_ui(app):
header_row.title("🌱 Edit app from comfyui workflow")
header_row.button("Back Workspace", help="Back to your workspace", key="edit_back_workspace", on_click=on_edit_workspace)

try:
comfyui_object_info = get_comfyui_object_info()
st.session_state['comfyui_object_info'] = comfyui_object_info
except Exception as e:
st.error(f"connect to comfyui node error, {e}")
st.stop()

# upload workflow image and config params
with st.expander("### :one: Upload image of comfyui workflow", expanded=True):
Expand Down Expand Up @@ -386,19 +404,19 @@ def edit_app_ui(app):
input_param = input_params[0]
add_input_config_param(params_inputs_options, 1, input_param)
else:
add_input_config_param(params_inputs_options, 1)
add_input_config_param(params_inputs_options, 1, None)

if len(input_params) > 1:
input_param_2 = input_params[1]
add_input_config_param(params_inputs_options, 2, input_param_2)
else:
add_input_config_param(params_inputs_options, 2)
add_input_config_param(params_inputs_options, 2, None)

if len(input_params) > 2:
input_param_3 = input_params[2]
add_input_config_param(params_inputs_options, 3, input_param_3)
else:
add_input_config_param(params_inputs_options, 3)
add_input_config_param(params_inputs_options, 3, None)

with st.container():

Expand All @@ -424,7 +442,7 @@ def edit_app_ui(app):
output_param_1 = output_params[0]
add_output_config_param(params_outputs_options, 1, output_param_1)
else:
add_output_config_param(params_outputs_options, 1)
add_output_config_param(params_outputs_options, 1, None)

with st.container():
operation_row = row([0.15, 0.7, 0.15])
Expand All @@ -437,7 +455,7 @@ def edit_app_ui(app):
st.success("Save app successfully, back your workspace")
st.stop()
else:
st.error("Save app error, please check up app params")
st.error(f"Save app error, please check up app params, refer to {FAQ_URL}")

operation_row.empty()
next_placeholder = operation_row.empty()
Expand All @@ -446,7 +464,7 @@ def on_new_workspace():
st.session_state.pop('new_app', None)
logger.info("back to workspace")

def add_input_config_param(params_inputs_options, index, input_param=None):
def add_input_config_param(params_inputs_options, index, input_param):
if not input_param:
input_param = {
'name': None,
Expand All @@ -464,7 +482,7 @@ def add_input_config_param(params_inputs_options, index, input_param=None):
param_input_row.text_input("App Input Description", value=input_param['help'], placeholder="Param Description",
key=f"input_param{index}_desc", help="Input param description")

def add_output_config_param(params_outputs_options, index, output_param=None):
def add_output_config_param(params_outputs_options, index, output_param):
if not output_param:
output_param = {
'name': None,
Expand Down Expand Up @@ -494,13 +512,20 @@ def new_app_ui():
st.warning("Please go to homepage for your login :point_left:")
st.stop()

try:
comfyui_object_info = get_comfyui_object_info()
st.session_state['comfyui_object_info'] = comfyui_object_info
except Exception as e:
st.error(f"connect to comfyui node error, {e}")
st.stop()

# upload workflow image and config params
with st.expander("### :one: Upload image of comfyui workflow", expanded=True):
image_col1, image_col2 = st.columns([0.5, 0.5])
with image_col1:
st.file_uploader("Upload image *", type=["png", "jpg", "jpeg"],
st.file_uploader("Upload image from comfyui outputs *", type=["png", "jpg", "jpeg", "webp"],
key="create_upload_image",
help="upload image of comfyui workflow")
help="upload image from comfyui output folder", accept_multiple_files=False)
process_image_change()

with image_col2:
Expand Down Expand Up @@ -530,15 +555,15 @@ def new_app_ui():
params_inputs = st.session_state.get('create_prompt_inputs', {})
params_inputs_options = list(params_inputs.keys())

add_input_config_param(params_inputs_options, 1)
add_input_config_param(params_inputs_options, 2)
add_input_config_param(params_inputs_options, 3)
add_input_config_param(params_inputs_options, 1, None)
add_input_config_param(params_inputs_options, 2, None)
add_input_config_param(params_inputs_options, 3, None)
with st.container():
st.markdown("Output Params:")
params_outputs = st.session_state.get('create_prompt_outputs', {})
params_outputs_options = list(params_outputs.keys())

add_output_config_param(params_outputs_options, 1)
add_output_config_param(params_outputs_options, 1, None)


with st.container():
Expand All @@ -554,7 +579,7 @@ def new_app_ui():
elif submit_info == 'exist':
st.error("Submit app error, app name has existed")
else:
st.error("Submit app error, please check up app params")
st.error(f"Submit app error, please check up app params, refer to {FAQ_URL}")

operation_row.empty()

Expand Down
2 changes: 1 addition & 1 deletion modules/preview_app.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import streamlit as st
import modules.page as page
from streamlit_extras.row import row
from modules import get_comfy_client, get_inner_comfy_client, get_workspace_model
from modules import get_comfy_client, get_workspace_model
from modules.workspace_model import AppStatus

def on_preview_workspace():
Expand Down

0 comments on commit c171360

Please sign in to comment.