6
6
import random
7
7
import sys
8
8
import re
9
- from typing import Dict , List , Callable , Tuple
9
+ from typing import Dict , List , Any , Callable , Tuple , TextIO
10
10
from argparse import ArgumentParser
11
11
12
12
import black
20
20
get_value_at_index ,
21
21
)
22
22
23
- sys . path . append ( "../" )
23
+ add_comfyui_directory_to_sys_path ( )
24
24
from nodes import NODE_CLASS_MAPPINGS
25
25
26
26
@@ -36,7 +36,7 @@ class FileHandler:
36
36
"""
37
37
38
38
@staticmethod
39
- def read_json_file (file_path : str ) -> dict :
39
+ def read_json_file (file_path : str | TextIO , encoding : str = "utf-8" ) -> dict :
40
40
"""
41
41
Reads a JSON file and returns its contents as a dictionary.
42
42
@@ -51,35 +51,14 @@ def read_json_file(file_path: str) -> dict:
51
51
ValueError: If the file is not a valid JSON.
52
52
"""
53
53
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
80
59
81
60
@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 :
83
62
"""Write the specified code to a Python file.
84
63
85
64
Args:
@@ -89,16 +68,19 @@ def write_code_to_file(file_path: str, code: str) -> None:
89
68
Returns:
90
69
None
91
70
"""
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 )
94
74
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 )
98
78
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 )
102
84
103
85
104
86
class LoadOrderDeterminer :
@@ -203,15 +185,12 @@ def __init__(self, node_class_mappings: Dict, base_node_class_mappings: Dict):
203
185
def generate_workflow (
204
186
self ,
205
187
load_order : List ,
206
- filename : str = "generated_code_workflow.py" ,
207
188
queue_size : int = 10 ,
208
189
) -> str :
209
190
"""Generate the execution code based on the load order.
210
191
211
192
Args:
212
193
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'.
215
194
queue_size (int): The number of photos that will be created by the script.
216
195
217
196
Returns:
@@ -515,23 +494,37 @@ class ComfyUItoPython:
515
494
516
495
def __init__ (
517
496
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 ,
521
501
node_class_mappings : Dict = NODE_CLASS_MAPPINGS ,
502
+ needs_init_custom_nodes : bool = False ,
522
503
):
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.
525
505
Args:
506
+ workflow (str): The workflow's JSON.
526
507
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 .
529
510
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.
530
512
"""
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
531
522
self .input_file = input_file
532
523
self .output_file = output_file
533
524
self .queue_size = queue_size
534
525
self .node_class_mappings = node_class_mappings
526
+ self .needs_init_custom_nodes = needs_init_custom_nodes
527
+
535
528
self .base_node_class_mappings = copy .deepcopy (self .node_class_mappings )
536
529
self .execute ()
537
530
@@ -541,11 +534,18 @@ def execute(self):
541
534
Returns:
542
535
None
543
536
"""
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 = {}
546
543
547
544
# 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 )
549
549
550
550
# Step 3: Determine the load order
551
551
load_order_determiner = LoadOrderDeterminer (data , self .node_class_mappings )
@@ -556,7 +556,7 @@ def execute(self):
556
556
self .node_class_mappings , self .base_node_class_mappings
557
557
)
558
558
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
560
560
)
561
561
562
562
# Step 5: Write the generated code to a file
@@ -582,7 +582,12 @@ def run(
582
582
Returns:
583
583
None
584
584
"""
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
+ )
586
591
587
592
588
593
def main () -> None :
@@ -612,7 +617,7 @@ def main() -> None:
612
617
default = DEFAULT_QUEUE_SIZE ,
613
618
)
614
619
pargs = parser .parse_args ()
615
- ComfyUItoPython (** vars (pargs ))
620
+ run (** vars (pargs ))
616
621
print ("Done." )
617
622
618
623
0 commit comments