1717
1818logger = logging .getLogger (__name__ )
1919
20- class VolcanoOutpaintingNode :
21- @classmethod
22- def INPUT_TYPES (s ):
23- return {
24- "required" : {
25- "image" : ("IMAGE" ,),
26- "mask" : ("MASK" ,),
27- "ak" : ("STRING" , {"default" : "" }),
28- "sk" : ("STRING" , {"default" : "" }),
29- "seed" : ("INT" , {"default" : 42 , "min" : 0 , "max" : 999999 })
30- }
31- }
32-
33- RETURN_TYPES = ("IMAGE" ,)
34- RETURN_NAMES = ("result_image" ,)
35- FUNCTION = "process_outpainting"
36- CATEGORY = "image/volcano"
37-
20+ class VolcanoBaseNode :
21+ """火山引擎视觉服务基类"""
22+
23+ def __init__ (self ):
24+ self .visual_service = VisualService ()
25+
3826 def save_config (self , config ):
27+ """保存配置到文件"""
3928 config_path = os .path .join (config_dir , 'volcano_config.yml' )
4029 with open (config_path , 'w' , encoding = 'utf-8' ) as f :
4130 yaml .dump (config , f , indent = 4 )
4231
4332 def load_config (self ):
33+ """从文件加载配置"""
4434 config_path = os .path .join (config_dir , 'volcano_config.yml' )
45- with open (config_path , 'r' , encoding = 'utf-8' ) as f :
46- config = yaml .load (f , Loader = yaml .FullLoader )
47- return config
35+ try :
36+ with open (config_path , 'r' , encoding = 'utf-8' ) as f :
37+ config = yaml .load (f , Loader = yaml .FullLoader )
38+ return config
39+ except FileNotFoundError :
40+ return {}
4841
4942 def pil_to_base64 (self , pil_image ):
5043 """将PIL图像转换为base64字符串"""
5144 buffer = io .BytesIO ()
5245 pil_image .save (buffer , format = 'PNG' )
5346 img_data = buffer .getvalue ()
5447 return base64 .b64encode (img_data ).decode ('utf-8' )
55-
56- def request_valcanic_outpainting (self , req_key , ak , sk , image_base64s , seed = 42 , top = 0 , left = 0 , bottom = 0 , right = 0 ):
57- """调用火山引擎图像扩展API"""
58- visual_service = VisualService ()
59-
48+
49+ def setup_credentials (self , ak , sk ):
50+ """设置API凭证"""
6051 if ak and sk :
6152 self .save_config ({"ak" : ak , "sk" : sk })
6253 else :
@@ -65,34 +56,49 @@ def request_valcanic_outpainting(self, req_key, ak, sk, image_base64s, seed=42,
6556 sk = config .get ("sk" )
6657 if not ak or not sk :
6758 raise Exception ("volcano engine ak or sk not found" )
68-
69- visual_service .set_ak (ak )
70- visual_service .set_sk (sk )
71-
72- # 请求参数
73- form = {
74- "req_key" : req_key ,
75- "binary_data_base64" : image_base64s ,
76- "top" : top ,
77- "left" : left ,
78- "bottom" : bottom ,
79- "right" : right ,
80- "seed" : seed
81- }
82-
83- resp = visual_service .cv_process (form )
59+
60+ self .visual_service .set_ak (ak )
61+ self .visual_service .set_sk (sk )
62+ return ak , sk
63+
64+ def request_volcano_api (self , ak , sk , form_data ):
65+ """通用的火山引擎API请求方法"""
66+ # 设置凭证
67+ self .setup_credentials (ak , sk )
68+
69+ # 发送请求
70+ resp = self .visual_service .cv_process (form_data )
8471
8572 # 解码返回的图像
8673 img_base64 = resp ['data' ]['binary_data_base64' ][0 ]
8774 img_data = base64 .b64decode (img_base64 )
88- # 转换为PIL图像
8975 result_image = Image .open (io .BytesIO (img_data ))
76+
77+ # 删除图片base64,方便print
78+ resp ['data' ]['binary_data_base64' ][0 ] = ""
79+ logger .debug (f"volcano api response: { resp } " )
80+
81+ return result_image , resp
9082
91- # 删除图片base64, 方便print
92- resp ['data' ]['binary_data_base64' ][0 ] = ""
83+ class VolcanoOutpaintingNode (VolcanoBaseNode ):
84+ """火山引擎图像扩展节点"""
85+
86+ @classmethod
87+ def INPUT_TYPES (s ):
88+ return {
89+ "required" : {
90+ "image" : ("IMAGE" ,),
91+ "mask" : ("MASK" ,),
92+ "ak" : ("STRING" , {"default" : "" }),
93+ "sk" : ("STRING" , {"default" : "" }),
94+ "seed" : ("INT" , {"default" : 42 , "min" : 0 , "max" : 999999 })
95+ }
96+ }
9397
94- logger .debug (f"volcano outpainting response: { resp } " )
95- return result_image , resp
98+ RETURN_TYPES = ("IMAGE" ,)
99+ RETURN_NAMES = ("result_image" ,)
100+ FUNCTION = "process_outpainting"
101+ CATEGORY = "image/volcano"
96102
97103 def process_outpainting (self , image , mask , ak , sk , seed ):
98104 try :
@@ -104,18 +110,23 @@ def process_outpainting(self, image, mask, ak, sk, seed):
104110 if pil_mask .mode != 'L' :
105111 pil_mask = pil_mask .convert ('L' )
106112
107- # 直接转换为base64
113+ # 转换为base64
108114 image_base64 = self .pil_to_base64 (pil_image )
109115 mask_base64 = self .pil_to_base64 (pil_mask )
110116
111- # 调用API
112- result_image , _ = self .request_valcanic_outpainting (
113- req_key = "i2i_outpainting" ,
114- ak = ak ,
115- sk = sk ,
116- image_base64s = [image_base64 , mask_base64 ],
117- seed = seed
118- )
117+ # 构建请求参数
118+ form_data = {
119+ "req_key" : "i2i_outpainting" ,
120+ "binary_data_base64" : [image_base64 , mask_base64 ],
121+ "top" : 0 ,
122+ "left" : 0 ,
123+ "bottom" : 0 ,
124+ "right" : 0 ,
125+ "seed" : seed
126+ }
127+
128+ # 调用基类的通用API请求方法
129+ result_image , _ = self .request_volcano_api (ak , sk , form_data )
119130
120131 # 使用节点库的转换函数转换结果为tensor
121132 result_tensor = pil2tensor (result_image )
@@ -127,11 +138,66 @@ def process_outpainting(self, image, mask, ak, sk, seed):
127138 # 返回原图作为fallback
128139 return (image ,)
129140
141+ class VolcanoImageEditNode (VolcanoBaseNode ):
142+ """火山引擎图像编辑节点"""
143+
144+ @classmethod
145+ def INPUT_TYPES (s ):
146+ return {
147+ "required" : {
148+ "image" : ("IMAGE" ,),
149+ "prompt" : ("STRING" , {"multiline" : True , "default" : "" }),
150+ "ak" : ("STRING" , {"default" : "" }),
151+ "sk" : ("STRING" , {"default" : "" }),
152+ "seed" : ("INT" , {"default" : 42 , "min" : 0 , "max" : 0xffffffffffffffff }),
153+ "scale" : ("FLOAT" , {"default" : 0.5 , "min" : 0.1 , "max" : 1.0 , "step" : 0.01 })
154+ },
155+ "optional" : {
156+ }
157+ }
158+
159+ RETURN_TYPES = ("IMAGE" ,)
160+ RETURN_NAMES = ("edited_image" ,)
161+ FUNCTION = "process_image_edit"
162+ CATEGORY = "image/volcano"
163+
164+ def process_image_edit (self , image , prompt , ak , sk , seed , scale = 0.5 ):
165+ try :
166+ # 使用节点库的转换函数
167+ pil_image = tensor2pil (image )
168+
169+ # 转换为base64
170+ image_base64 = self .pil_to_base64 (pil_image )
171+ binary_data = [image_base64 ]
172+ # 构建请求参数
173+ form_data = {
174+ "req_key" : "byteedit_v2.0" ,
175+ "binary_data_base64" : binary_data ,
176+ "prompt" : prompt ,
177+ "seed" : seed ,
178+ "scale" : scale
179+ }
180+
181+ # 调用基类的通用API请求方法
182+ result_image , _ = self .request_volcano_api (ak , sk , form_data )
183+
184+ # 使用节点库的转换函数转换结果为tensor
185+ result_tensor = pil2tensor (result_image )
186+
187+ return (result_tensor ,)
188+
189+ except Exception as e :
190+ logger .exception (f"图像编辑处理失败: { e } " )
191+ # 返回原图作为fallback
192+ return (image ,)
193+
130194# 节点映射
131195NODE_CLASS_MAPPINGS = {
132- "VolcanoOutpaintingNode" : VolcanoOutpaintingNode
196+ "VolcanoOutpaintingNode" : VolcanoOutpaintingNode ,
197+ "VolcanoImageEditNode" : VolcanoImageEditNode
133198}
134199
135200NODE_DISPLAY_NAME_MAPPINGS = {
136- "VolcanoOutpaintingNode" : "volcano outpainting"
201+ "VolcanoOutpaintingNode" : "volcano outpainting" ,
202+ "VolcanoImageEditNode" : "volcano image edit"
137203}
0 commit comments