66import random
77import sys
88import re
9- from typing import Dict , List , Callable , Tuple
9+ from typing import Dict , List , Any , Callable , Tuple , TextIO
1010from argparse import ArgumentParser
1111
1212import black
2020 get_value_at_index ,
2121)
2222
23- sys . path . append ( "../" )
23+ add_comfyui_directory_to_sys_path ( )
2424from nodes import NODE_CLASS_MAPPINGS
2525
2626
@@ -36,7 +36,7 @@ class FileHandler:
3636 """
3737
3838 @staticmethod
39- def read_json_file (file_path : str ) -> dict :
39+ def read_json_file (file_path : str | TextIO , encoding : str = "utf-8" ) -> dict :
4040 """
4141 Reads a JSON file and returns its contents as a dictionary.
4242
@@ -51,35 +51,14 @@ def read_json_file(file_path: str) -> dict:
5151 ValueError: If the file is not a valid JSON.
5252 """
5353
54- try :
55- with open (file_path , "r" ) as file :
56- data = json .load (file )
57- return data
58-
59- except FileNotFoundError :
60- # Get the directory from the file_path
61- directory = os .path .dirname (file_path )
62-
63- # If the directory is an empty string (which means file is in the current directory),
64- # get the current working directory
65- if not directory :
66- directory = os .getcwd ()
67-
68- # Find all JSON files in the directory
69- json_files = glob .glob (f"{ directory } /*.json" )
70-
71- # Format the list of JSON files as a string
72- json_files_str = "\n " .join (json_files )
73-
74- raise FileNotFoundError (
75- f"\n \n File not found: { file_path } . JSON files in the directory:\n { json_files_str } "
76- )
77-
78- except json .JSONDecodeError :
79- raise ValueError (f"Invalid JSON format in file: { file_path } " )
54+ if hasattr (file_path , "read" ):
55+ return json .load (file_path )
56+ with open (file_path , "r" , encoding = "utf-8" ) as file :
57+ data = json .load (file )
58+ return data
8059
8160 @staticmethod
82- def write_code_to_file (file_path : str , code : str ) -> None :
61+ def write_code_to_file (file_path : str | TextIO , code : str ) -> None :
8362 """Write the specified code to a Python file.
8463
8564 Args:
@@ -89,16 +68,19 @@ def write_code_to_file(file_path: str, code: str) -> None:
8968 Returns:
9069 None
9170 """
92- # Extract directory from the filename
93- directory = os .path .dirname (file_path )
71+ if isinstance (file_path , str ):
72+ # Extract directory from the filename
73+ directory = os .path .dirname (file_path )
9474
95- # If the directory does not exist, create it
96- if directory and not os .path .exists (directory ):
97- os .makedirs (directory )
75+ # If the directory does not exist, create it
76+ if directory and not os .path .exists (directory ):
77+ os .makedirs (directory )
9878
99- # Save the code to a .py file
100- with open (file_path , "w" ) as file :
101- file .write (code )
79+ # Save the code to a .py file
80+ with open (file_path , "w" , encoding = "utf-8" ) as file :
81+ file .write (code )
82+ else :
83+ file_path .write (code )
10284
10385
10486class LoadOrderDeterminer :
@@ -203,15 +185,12 @@ def __init__(self, node_class_mappings: Dict, base_node_class_mappings: Dict):
203185 def generate_workflow (
204186 self ,
205187 load_order : List ,
206- filename : str = "generated_code_workflow.py" ,
207188 queue_size : int = 10 ,
208189 ) -> str :
209190 """Generate the execution code based on the load order.
210191
211192 Args:
212193 load_order (List): A list of tuples representing the load order.
213- filename (str): The name of the Python file to which the code should be saved.
214- Defaults to 'generated_code_workflow.py'.
215194 queue_size (int): The number of photos that will be created by the script.
216195
217196 Returns:
@@ -515,23 +494,37 @@ class ComfyUItoPython:
515494
516495 def __init__ (
517496 self ,
518- input_file : str ,
519- output_file : str ,
520- queue_size : int = 10 ,
497+ workflow : str = "" ,
498+ input_file : str = "" ,
499+ output_file : str | TextIO = "" ,
500+ queue_size : int = 1 ,
521501 node_class_mappings : Dict = NODE_CLASS_MAPPINGS ,
502+ needs_init_custom_nodes : bool = False ,
522503 ):
523- """Initialize the ComfyUItoPython class with the given parameters.
524-
504+ """Initialize the ComfyUItoPython class with the given parameters. Exactly one of workflow or input_file must be specified.
525505 Args:
506+ workflow (str): The workflow's JSON.
526507 input_file (str): Path to the input JSON file.
527- output_file (str): Path to the output Python file.
528- queue_size (int): The number of times a workflow will be executed by the script. Defaults to 10 .
508+ output_file (str | TextIO ): Path to the output file or a file-like object .
509+ queue_size (int): The number of times a workflow will be executed by the script. Defaults to 1 .
529510 node_class_mappings (Dict): Mappings of node classes. Defaults to NODE_CLASS_MAPPINGS.
511+ needs_init_custom_nodes (bool): Whether to initialize custom nodes. Defaults to False.
530512 """
513+ if input_file and workflow :
514+ raise ValueError ("Can't provide both input_file and workflow" )
515+ elif not input_file and not workflow :
516+ raise ValueError ("Needs input_file or workflow" )
517+
518+ if not output_file :
519+ raise ValueError ("Needs output_file" )
520+
521+ self .workflow = workflow
531522 self .input_file = input_file
532523 self .output_file = output_file
533524 self .queue_size = queue_size
534525 self .node_class_mappings = node_class_mappings
526+ self .needs_init_custom_nodes = needs_init_custom_nodes
527+
535528 self .base_node_class_mappings = copy .deepcopy (self .node_class_mappings )
536529 self .execute ()
537530
@@ -541,11 +534,18 @@ def execute(self):
541534 Returns:
542535 None
543536 """
544- # Step 1: Import all custom nodes
545- import_custom_nodes ()
537+ # Step 1: Import all custom nodes if we need to
538+ if self .needs_init_custom_nodes :
539+ import_custom_nodes ()
540+ else :
541+ # If they're already imported, we don't know which nodes are custom nodes, so we need to import all of them
542+ self .base_node_class_mappings = {}
546543
547544 # Step 2: Read JSON data from the input file
548- data = FileHandler .read_json_file (self .input_file )
545+ if self .input_file :
546+ data = FileHandler .read_json_file (self .input_file )
547+ else :
548+ data = json .loads (self .workflow )
549549
550550 # Step 3: Determine the load order
551551 load_order_determiner = LoadOrderDeterminer (data , self .node_class_mappings )
@@ -556,7 +556,7 @@ def execute(self):
556556 self .node_class_mappings , self .base_node_class_mappings
557557 )
558558 generated_code = code_generator .generate_workflow (
559- load_order , filename = self . output_file , queue_size = self .queue_size
559+ load_order , queue_size = self .queue_size
560560 )
561561
562562 # Step 5: Write the generated code to a file
@@ -582,7 +582,12 @@ def run(
582582 Returns:
583583 None
584584 """
585- ComfyUItoPython (input_file , output_file , queue_size )
585+ ComfyUItoPython (
586+ input_file = input_file ,
587+ output_file = output_file ,
588+ queue_size = queue_size ,
589+ needs_init_custom_nodes = True ,
590+ )
586591
587592
588593def main () -> None :
@@ -612,7 +617,7 @@ def main() -> None:
612617 default = DEFAULT_QUEUE_SIZE ,
613618 )
614619 pargs = parser .parse_args ()
615- ComfyUItoPython (** vars (pargs ))
620+ run (** vars (pargs ))
616621 print ("Done." )
617622
618623
0 commit comments