bizyengine 1.2.48__py3-none-any.whl → 1.2.50__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -9,6 +9,7 @@ from .nodes_custom_sampler import *
9
9
  from .nodes_dataset import *
10
10
  from .nodes_differential_diffusion import *
11
11
  from .nodes_flux import *
12
+ from .nodes_gemini import *
12
13
  from .nodes_hunyuan3d import *
13
14
  from .nodes_image_utils import *
14
15
  from .nodes_ip2p import *
@@ -0,0 +1,311 @@
1
+ import base64
2
+ import io
3
+ import json
4
+ import logging
5
+ import re
6
+
7
+ import numpy as np
8
+ import torch
9
+ from comfy_api_nodes.apinode_utils import (
10
+ bytesio_to_image_tensor,
11
+ tensor_to_base64_string,
12
+ )
13
+ from PIL import Image, ImageOps
14
+
15
+ from bizyengine.core import BizyAirBaseNode, pop_api_key_and_prompt_id
16
+ from bizyengine.core.common import client
17
+ from bizyengine.core.common.env_var import BIZYAIR_SERVER_ADDRESS
18
+
19
+
20
+ # Tensor to PIL
21
+ def tensor_to_pil(image):
22
+ return Image.fromarray(
23
+ np.clip(255.0 * image.cpu().numpy().squeeze(), 0, 255).astype(np.uint8)
24
+ )
25
+
26
+
27
+ def image_to_base64(pil_image, pnginfo=None):
28
+ # 创建一个BytesIO对象,用于临时存储图像数据
29
+ image_data = io.BytesIO()
30
+
31
+ # 将图像保存到BytesIO对象中,格式为PNG
32
+ pil_image.save(image_data, format="PNG", pnginfo=pnginfo)
33
+
34
+ # 将BytesIO对象的内容转换为字节串
35
+ image_data_bytes = image_data.getvalue()
36
+
37
+ # 将图像数据编码为Base64字符串
38
+ encoded_image = "data:image/png;base64," + base64.b64encode(
39
+ image_data_bytes
40
+ ).decode("utf-8")
41
+
42
+ return encoded_image
43
+
44
+
45
+ def base64_to_image(base64_string):
46
+ # 去除前缀
47
+ base64_list = base64_string.split(",", 1)
48
+ if len(base64_list) == 2:
49
+ prefix, base64_data = base64_list
50
+ else:
51
+ base64_data = base64_list[0]
52
+
53
+ # 从base64字符串中解码图像数据
54
+ image_data = base64.b64decode(base64_data)
55
+
56
+ # 创建一个内存流对象
57
+ image_stream = io.BytesIO(image_data)
58
+
59
+ # 使用PIL的Image模块打开图像数据
60
+ image = Image.open(image_stream)
61
+
62
+ return image
63
+
64
+
65
+ def get_parts_from_response(
66
+ response: dict,
67
+ ):
68
+ return response["candidates"][0]["content"]["parts"]
69
+
70
+
71
+ def get_parts_by_type(response: dict, part_type: str):
72
+ parts = []
73
+ for part in get_parts_from_response(response):
74
+ if part_type == "text" and part.get("text", None):
75
+ parts.append(part)
76
+ elif (
77
+ part.get("inlineData", None) and part["inlineData"]["mimeType"] == part_type
78
+ ):
79
+ parts.append(part)
80
+ # Skip parts that don't match the requested type
81
+ return parts
82
+
83
+
84
+ def get_text_from_response(response: dict) -> str:
85
+ parts = get_parts_by_type(response, "text")
86
+ logging.debug(f"Text parts: {parts}")
87
+ return "\n".join([part["text"] for part in parts])
88
+
89
+
90
+ def get_image_from_response(response: dict) -> torch.Tensor:
91
+ image_tensors: list[torch.Tensor] = []
92
+ parts = get_parts_by_type(response, "image/png")
93
+ for part in parts:
94
+ b64_data = part["inlineData"]["data"]
95
+ if b64_data:
96
+ image_data = base64.b64decode(b64_data)
97
+ returned_image = bytesio_to_image_tensor(io.BytesIO(image_data))
98
+ image_tensors.append(returned_image)
99
+ if len(image_tensors) == 0:
100
+ return torch.zeros((1, 1024, 1024, 4))
101
+ return torch.cat(image_tensors, dim=0)
102
+
103
+
104
+ # FROM: https://github.com/ShmuelRonen/ComfyUI-NanoBanano/blob/9eeb8f2411fd0ff08791bdf5e24eec347456c8b8/nano_banano.py#L191
105
+ def build_prompt_for_operation(
106
+ prompt,
107
+ operation,
108
+ has_references=False,
109
+ aspect_ratio="1:1",
110
+ character_consistency=True,
111
+ ):
112
+ """Build optimized prompt based on operation type"""
113
+
114
+ aspect_instructions = {
115
+ "1:1": "square format",
116
+ "16:9": "widescreen landscape format",
117
+ "9:16": "portrait format",
118
+ "4:3": "standard landscape format",
119
+ "3:4": "standard portrait format",
120
+ }
121
+
122
+ base_quality = "Generate a high-quality, photorealistic image"
123
+ format_instruction = f"in {aspect_instructions.get(aspect_ratio, 'square format')}"
124
+
125
+ if operation == "generate":
126
+ if has_references:
127
+ final_prompt = f"{base_quality} inspired by the style and elements of the reference images. {prompt}. {format_instruction}."
128
+ else:
129
+ final_prompt = f"{base_quality} of: {prompt}. {format_instruction}."
130
+
131
+ elif operation == "edit":
132
+ if not has_references:
133
+ return "Error: Edit operation requires reference images"
134
+ # No aspect ratio for edit - preserve original image dimensions
135
+ final_prompt = f"Edit the provided reference image(s). {prompt}. Maintain the original composition and quality while making the requested changes."
136
+
137
+ elif operation == "style_transfer":
138
+ if not has_references:
139
+ return "Error: Style transfer requires reference images"
140
+ final_prompt = f"Apply the style from the reference images to create: {prompt}. Blend the stylistic elements naturally. {format_instruction}."
141
+
142
+ elif operation == "object_insertion":
143
+ if not has_references:
144
+ return "Error: Object insertion requires reference images"
145
+ final_prompt = f"Insert or blend the following into the reference image(s): {prompt}. Ensure natural lighting, shadows, and perspective. {format_instruction}."
146
+
147
+ if character_consistency and has_references:
148
+ final_prompt += " Maintain character consistency and visual identity from the reference images."
149
+
150
+ return final_prompt
151
+
152
+
153
+ class NanoBanana(BizyAirBaseNode):
154
+ def __init__(self):
155
+ pass
156
+
157
+ @classmethod
158
+ def INPUT_TYPES(s):
159
+ return {
160
+ "required": {
161
+ "prompt": (
162
+ "STRING",
163
+ {
164
+ "multiline": True,
165
+ "default": "",
166
+ },
167
+ ),
168
+ "operation": (
169
+ ["generate", "edit", "style_transfer", "object_insertion"],
170
+ {
171
+ "default": "generate",
172
+ "tooltip": "Choose the type of image operation",
173
+ },
174
+ ),
175
+ "temperature": (
176
+ "FLOAT",
177
+ {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.05},
178
+ ),
179
+ "top_p": (
180
+ "FLOAT",
181
+ {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.05},
182
+ ),
183
+ "seed": ("INT", {"default": 0, "min": 0, "max": 2147483647}),
184
+ "max_tokens": ("INT", {"default": 8192, "min": 1, "max": 8192}),
185
+ },
186
+ "optional": {
187
+ "image": ("IMAGE",),
188
+ "image2": ("IMAGE",),
189
+ "image3": ("IMAGE",),
190
+ "image4": ("IMAGE",),
191
+ "image5": ("IMAGE",),
192
+ "quality": (
193
+ ["standard", "high"],
194
+ {"default": "high", "tooltip": "Image generation quality"},
195
+ ),
196
+ "aspect_ratio": (
197
+ ["1:1", "16:9", "9:16", "4:3", "3:4"],
198
+ {"default": "1:1", "tooltip": "Output image aspect ratio"},
199
+ ),
200
+ "character_consistency": (
201
+ "BOOLEAN",
202
+ {
203
+ "default": True,
204
+ "tooltip": "Maintain character consistency across edits",
205
+ },
206
+ ),
207
+ },
208
+ }
209
+
210
+ RETURN_TYPES = ("IMAGE", "STRING")
211
+ FUNCTION = "execute"
212
+ OUTPUT_NODE = False
213
+ CATEGORY = "☁️BizyAir/External APIs/Gemini"
214
+
215
+ def execute(
216
+ self,
217
+ prompt,
218
+ operation,
219
+ temperature,
220
+ top_p,
221
+ seed,
222
+ max_tokens,
223
+ quality=None,
224
+ aspect_ratio=None,
225
+ character_consistency=None,
226
+ **kwargs,
227
+ ):
228
+ try:
229
+ url = f"{BIZYAIR_SERVER_ADDRESS}/proxy_inference/VertexAI/gemini-2.5-flash-image-preview"
230
+ extra_data = pop_api_key_and_prompt_id(kwargs)
231
+
232
+ parts = []
233
+ for _, img in enumerate(
234
+ [
235
+ kwargs.get("image", None),
236
+ kwargs.get("image2", None),
237
+ kwargs.get("image3", None),
238
+ kwargs.get("image4", None),
239
+ kwargs.get("image5", None),
240
+ ],
241
+ 1,
242
+ ):
243
+ if img is not None:
244
+ parts.append(
245
+ {
246
+ "inline_data": {
247
+ "mime_type": "image/png",
248
+ "data": tensor_to_base64_string(img),
249
+ }
250
+ }
251
+ )
252
+
253
+ prompt = build_prompt_for_operation(
254
+ prompt,
255
+ operation,
256
+ has_references=len(parts) > 0,
257
+ aspect_ratio=aspect_ratio,
258
+ character_consistency=character_consistency,
259
+ )
260
+ if quality == "high":
261
+ prompt += " Use the highest quality settings available."
262
+ parts.append({"text": prompt})
263
+
264
+ data = {
265
+ "contents": {
266
+ "parts": parts,
267
+ "role": "user",
268
+ },
269
+ "generationConfig": {
270
+ "seed": seed,
271
+ "responseModalities": ["TEXT", "IMAGE"],
272
+ "temperature": temperature,
273
+ "topP": top_p,
274
+ "maxOutputTokens": max_tokens,
275
+ },
276
+ }
277
+ json_payload = json.dumps(data).encode("utf-8")
278
+ headers = client.headers(api_key=extra_data["api_key"])
279
+ headers["X-BIZYAIR-PROMPT-ID"] = extra_data[
280
+ "prompt_id"
281
+ ] # 额外参数vertexai会拒绝,所以用请求头传
282
+ resp = client.send_request(
283
+ url=url,
284
+ data=json_payload,
285
+ headers=headers,
286
+ )
287
+ # 解析潜在错误
288
+ prompt_feedback = resp.get("promptFeedback", None)
289
+ if prompt_feedback:
290
+ logging.error(f"Response: {resp}")
291
+ raise ValueError(f"Prompt blocked: {prompt_feedback}")
292
+ if len(resp["candidates"]) == 0:
293
+ logging.error(f"Response: {resp}")
294
+ raise ValueError("No candidates found in response")
295
+ if resp["candidates"][0]["finishReason"] != "STOP":
296
+ logging.error(f"Response: {resp}")
297
+ raise ValueError(
298
+ f"Erroneous finish reason: {resp['candidates'][0]['finishReason']}"
299
+ )
300
+
301
+ # 解析文本
302
+ text = get_text_from_response(resp)
303
+
304
+ # 解析base64图片
305
+ image = get_image_from_response(resp)
306
+
307
+ return (image, text)
308
+
309
+ except Exception as e:
310
+ logging.error(f"Gemini API error: {e}")
311
+ raise e
bizyengine/version.txt CHANGED
@@ -1 +1 @@
1
- 1.2.48
1
+ 1.2.50
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: bizyengine
3
- Version: 1.2.48
3
+ Version: 1.2.50
4
4
  Summary: [a/BizyAir](https://github.com/siliconflow/BizyAir) Comfy Nodes that can run in any environment.
5
5
  Author-email: SiliconFlow <yaochi@siliconflow.cn>
6
6
  Project-URL: Repository, https://github.com/siliconflow/BizyAir
@@ -1,5 +1,5 @@
1
1
  bizyengine/__init__.py,sha256=GP9V-JM07fz7uv_qTB43QEA2rKdrVJxi5I7LRnn_3ZQ,914
2
- bizyengine/version.txt,sha256=bnvdsAiKg-CLDL8gDQ12c9u0SoaVKcwhsnSzGc7CnJA,7
2
+ bizyengine/version.txt,sha256=zGrmrSrzHGOEzO23IqIfRL3venxRA-IUVh6N6ZhB4X8,7
3
3
  bizyengine/bizy_server/__init__.py,sha256=SP9oSblnPo4KQyh7yOGD26YCskFAcQHAZy04nQBNRIw,200
4
4
  bizyengine/bizy_server/api_client.py,sha256=Z7G5IjaEqSJkF6nLLw2R3bpgBAOi5ClQiUbel6NMXmE,43932
5
5
  bizyengine/bizy_server/errno.py,sha256=1UiFmE2U7r7hCHgsw4-p_YL0VCmTJc9NyYDEbhkanaY,16336
@@ -10,7 +10,7 @@ bizyengine/bizy_server/resp.py,sha256=iOFT5Ud7VJBP2uqkojJIgc3y2ifMjjEXoj0ewneL9l
10
10
  bizyengine/bizy_server/server.py,sha256=7OWEA8GMrqpg4jxCvakX5zR2JukEOhKbqPRefYo6t8c,59707
11
11
  bizyengine/bizy_server/stream_response.py,sha256=H2XHqlVRtQMhgdztAuG7l8-iV_Pm42u2x6WJ0gNVIW0,9654
12
12
  bizyengine/bizy_server/utils.py,sha256=Kkn-AATZcdaDhg8Rg_EJW6aKqkyiSE2EYmuyOhUwXso,3863
13
- bizyengine/bizyair_extras/__init__.py,sha256=zzqOeOnd6uyyopeNFPa7asfG-sI4r3SSrKJkHGF4iGI,1064
13
+ bizyengine/bizyair_extras/__init__.py,sha256=lsGuqMnFX-WSOUszSQ5NB6FnKcHlhhO1WG9iiywWQug,1092
14
14
  bizyengine/bizyair_extras/nodes_advanced_refluxcontrol.py,sha256=cecfjrtnjJAty9aNkhz8BlmHUC1NImkFlUDiA0COEa4,2242
15
15
  bizyengine/bizyair_extras/nodes_cogview4.py,sha256=Ni0TDOycczyDhYPvSR68TxGV_wE2uhaxd8MIj-J4-3o,2031
16
16
  bizyengine/bizyair_extras/nodes_comfyui_detail_daemon.py,sha256=i71it24tiGvZ3h-XFWISr4CpZszUtPuz3UrZARYluLk,6169
@@ -22,6 +22,7 @@ bizyengine/bizyair_extras/nodes_custom_sampler.py,sha256=NK-7sdcp8oxJisjTEFfBskk
22
22
  bizyengine/bizyair_extras/nodes_dataset.py,sha256=htF0YZb_FHncLhLDEbJfNCVqJ6rvlo1ZLk7iY42Rylc,3440
23
23
  bizyengine/bizyair_extras/nodes_differential_diffusion.py,sha256=nSrbD-w0XtrwktwzME5M0Vmi1sI7Z08AqwgymTdThqo,370
24
24
  bizyengine/bizyair_extras/nodes_flux.py,sha256=ls94kGBuBNgW5c6uhG36iZLk1TTM2TIoTTcpERgEE5E,2683
25
+ bizyengine/bizyair_extras/nodes_gemini.py,sha256=AdHMxVCYuv7HTBEnxfqSIGLsmICWM5WhDvsLdJWLIqI,10594
25
26
  bizyengine/bizyair_extras/nodes_hunyuan3d.py,sha256=dWHLeqX68N7zKnfDMzm9nutmCNtFT6-wwt7P5cPDu7Q,2058
26
27
  bizyengine/bizyair_extras/nodes_image_utils.py,sha256=BldF_CKD2M01K8-SnG-QV86u3HZqFz_GP5GrCQ5CFDQ,2875
27
28
  bizyengine/bizyair_extras/nodes_ip2p.py,sha256=GSEFJvrs4f2tv0xwYkWqc8uhsXrzAJVPvvwcw0gTjR0,619
@@ -79,7 +80,7 @@ bizyengine/misc/route_sam.py,sha256=-bMIR2QalfnszipGxSxvDAHGJa5gPSrjkYPb5baaRg4,
79
80
  bizyengine/misc/segment_anything.py,sha256=wNKYwlYPMszfwj23524geFZJjZaG4eye65SGaUnh77I,8941
80
81
  bizyengine/misc/supernode.py,sha256=STN9gaxfTSErH8OiHeZa47d8z-G9S0I7fXuJvHQOBFM,4532
81
82
  bizyengine/misc/utils.py,sha256=CKduySGSMNGlJMImHyZmN-giABY5VUaB88f6Kq-HAV0,6831
82
- bizyengine-1.2.48.dist-info/METADATA,sha256=8AMoIRVUq3iBEVhTx-rwW4L92iNWLjtGTpXMBy7ah-8,708
83
- bizyengine-1.2.48.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
84
- bizyengine-1.2.48.dist-info/top_level.txt,sha256=2zapzqxX-we5cRyJkGf9bd5JinRtXp3-_uDI-xCAnc0,11
85
- bizyengine-1.2.48.dist-info/RECORD,,
83
+ bizyengine-1.2.50.dist-info/METADATA,sha256=oDy6bxpEMjEUlrLgBDEdsI-Rz1pW7SM_lDl3b5HbNJk,708
84
+ bizyengine-1.2.50.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
85
+ bizyengine-1.2.50.dist-info/top_level.txt,sha256=2zapzqxX-we5cRyJkGf9bd5JinRtXp3-_uDI-xCAnc0,11
86
+ bizyengine-1.2.50.dist-info/RECORD,,