Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d49baa1540 | ||
|
|
5c686af842 | ||
|
|
22425b5bc6 | ||
|
|
b6d9b338d2 | ||
|
|
a191a13751 | ||
|
|
3eccdbcc9b | ||
|
|
50063903f9 | ||
|
|
95a1b70533 | ||
|
|
c3679ac90b | ||
|
|
29e48eb6a2 | ||
|
|
71d02e9651 | ||
|
|
f5193f3eec | ||
|
|
480c4d6919 | ||
|
|
1c5e030540 | ||
|
|
27e83a5908 | ||
|
|
d3cbf8fa8d | ||
|
|
74b1f8129b | ||
|
|
56ed513cfd | ||
|
|
5f412371c4 | ||
|
|
c6b0b67585 | ||
|
|
16d18e681a | ||
|
|
1fe99f33b2 | ||
|
|
a2ece25ac0 | ||
|
|
4865f4d148 | ||
|
|
41e88824cf | ||
|
|
0961ab138e | ||
|
|
fe8271a12f | ||
|
|
f3866ede89 | ||
|
|
d938adf3cc | ||
|
|
9908cff64b | ||
|
|
1b9b0bb4e6 | ||
|
|
03acd9bea5 | ||
|
|
4c42949023 | ||
|
|
2e4d9836e5 | ||
|
|
43c6b58354 | ||
|
|
df637e8196 | ||
|
|
8e78f9786c | ||
|
|
f4130f06ed | ||
|
|
b766e714a4 | ||
|
|
1100a90be3 | ||
|
|
33aaf80c82 | ||
|
|
b5861dbc24 | ||
|
|
93731416fc | ||
|
|
a305e736ca | ||
|
|
a046ebbafb | ||
|
|
af65e96723 | ||
|
|
c9eb0ab5f0 | ||
|
|
9802e841a8 | ||
|
|
b896df8d54 | ||
|
|
720b8c237b | ||
|
|
371f9f813f | ||
|
|
33e229c41c | ||
|
|
2c33c0d801 | ||
|
|
d64fee5954 | ||
|
|
0217678c8c | ||
|
|
2959a9c31f | ||
|
|
1928a18992 | ||
|
|
a168171009 | ||
|
|
6f767f9700 | ||
|
|
e5459f63fd | ||
|
|
d0ab85a8c4 | ||
|
|
b7b86fe8c4 | ||
|
|
c3bcf6907a | ||
|
|
330fed867b | ||
|
|
45bbc31dc1 | ||
|
|
9dc45239c4 | ||
|
|
b6cfb30908 | ||
|
|
8efd94cc76 | ||
|
|
5d864e7ea2 | ||
|
|
7a1c91a2d5 | ||
|
|
1339c8a1f4 | ||
|
|
27025579d6 | ||
|
|
473d70f818 | ||
|
|
907a5d8d7e | ||
|
|
28c349fd7e | ||
|
|
df9545015f | ||
|
|
07b0b94e46 | ||
|
|
92f85b6d9c | ||
|
|
6919eadb21 | ||
|
|
be76280fd2 | ||
|
|
6b673bdd44 | ||
|
|
c6591db45e | ||
|
|
5813ebaa1c | ||
|
|
743e752b0f | ||
|
|
707cd28cb7 | ||
|
|
ecdb687c25 | ||
|
|
ba68d2dd45 | ||
|
|
d8259de52f | ||
|
|
07cec3566b | ||
|
|
dd8c531889 | ||
|
|
686ebcfd8b | ||
|
|
51aaba39cf | ||
|
|
d7c6632499 | ||
|
|
1b0ea06876 | ||
|
|
8ebe88629b | ||
|
|
e226992703 | ||
|
|
11a8394d69 | ||
|
|
9f54a1b91a | ||
|
|
35d11061e9 | ||
|
|
65d8b490ca | ||
|
|
0fef12c3b1 | ||
|
|
8516bff224 | ||
|
|
1bcc501352 | ||
|
|
f492b17fbe | ||
|
|
48ae90f80e | ||
|
|
9321ccbc48 | ||
|
|
402cd01e1a | ||
|
|
7b2d0e29c6 | ||
|
|
52d38c401a | ||
|
|
a34dd61076 | ||
|
|
a53a3e772a | ||
|
|
8f24c294a7 | ||
|
|
acc3f76654 | ||
|
|
8c977fb442 | ||
|
|
aa20a2de67 | ||
|
|
5fcb154d89 | ||
|
|
0980129f4e | ||
|
|
1258746886 | ||
|
|
ed128b0ad6 | ||
|
|
f0db08acd6 | ||
|
|
7c655e3080 | ||
|
|
1b9871c3df | ||
|
|
7568aaf243 | ||
|
|
a76be8450d | ||
|
|
5564ee1246 | ||
|
|
a6e9251521 | ||
|
|
0bee093916 | ||
|
|
13a9878823 | ||
|
|
acd35d50f8 | ||
|
|
5c0d99e72d | ||
|
|
e37af93be3 | ||
|
|
244c1700e1 | ||
|
|
a3a15473ba | ||
|
|
d734b5077c | ||
|
|
5430072b19 | ||
|
|
465aebaed4 | ||
|
|
ee0b16c2ea | ||
|
|
21d5eacb41 | ||
|
|
c06688eb0b | ||
|
|
b74bbcd279 | ||
|
|
64d8d9b05d | ||
|
|
6ab60f281b | ||
|
|
e35be3b2fa | ||
|
|
0a0c27ac96 | ||
|
|
fb249e84eb | ||
|
|
76ad86fcae | ||
|
|
037614d227 | ||
|
|
4a50e445fd | ||
|
|
6f3c1c4393 | ||
|
|
c6a9b4b592 | ||
|
|
e915ac4eca | ||
|
|
a857793f63 | ||
|
|
31914f7510 | ||
|
|
0be859f0ee | ||
|
|
14b9c3697b | ||
|
|
96b66a57bb | ||
|
|
f13701c489 | ||
|
|
31515b810e | ||
|
|
29e84e08a4 | ||
|
|
0ac9ad9757 | ||
|
|
9a432e0608 | ||
|
|
c83ba5fe7f | ||
|
|
1b55c743ea | ||
|
|
a93579376c | ||
|
|
eba49f3c68 | ||
|
|
3a3da49c69 | ||
|
|
3d68e48219 | ||
|
|
4351fa6a0e | ||
|
|
77222d2808 | ||
|
|
36db7e5a9a | ||
|
|
3572368f16 | ||
|
|
0755dc1462 | ||
|
|
94f81b7102 | ||
|
|
08f8fe3d7e | ||
|
|
a6cd383d67 | ||
|
|
10face6ab0 | ||
|
|
a6259ff600 | ||
|
|
d7d9e6cbfe | ||
|
|
8263609470 | ||
|
|
fc2367de76 | ||
|
|
9a4f2ebc70 | ||
|
|
812879610a | ||
|
|
c5e521ccc1 | ||
|
|
bc1c8fa351 | ||
|
|
7271fcf9c1 | ||
|
|
ace3b7707b | ||
|
|
9eb65cc4ee | ||
|
|
c5c2bc779c | ||
|
|
ae1751d9c0 | ||
|
|
d17583ef7d | ||
|
|
a363713ae0 | ||
|
|
1566165bd4 | ||
|
|
fe065fa318 | ||
|
|
202d5cf071 | ||
|
|
b785a9dc5b | ||
|
|
73bc658b2f | ||
|
|
0b94216138 | ||
|
|
86eec2b4cc | ||
|
|
c99b531d28 | ||
|
|
74a4338cb5 | ||
|
|
d691c52e49 | ||
|
|
bd3e9e4b3c | ||
|
|
3c3ca5fb9c | ||
|
|
e4ff4fce1c | ||
|
|
2918d4b07d | ||
|
|
c40e49be46 | ||
|
|
4ccda20975 | ||
|
|
09957617d3 | ||
|
|
c703aa7058 | ||
|
|
64d366d323 | ||
|
|
333e0a2faa | ||
|
|
a677d95bc8 | ||
|
|
aafd87e84b | ||
|
|
efa3bae54b | ||
|
|
8e4362689d | ||
|
|
b86634284e | ||
|
|
b3293ddccd | ||
|
|
4b40831b83 | ||
|
|
e426b0521c | ||
|
|
1777bf6e06 | ||
|
|
dafc892f0f | ||
|
|
fea0cfd5dd | ||
|
|
a7e158db6d | ||
|
|
455ac4abd3 | ||
|
|
778dfa2cf5 | ||
|
|
329f2e6f81 | ||
|
|
63ad6d97d7 | ||
|
|
8db56db7cf | ||
|
|
bd542f1e0b | ||
|
|
a162e53dea | ||
|
|
a8a4c848ed | ||
|
|
bddd38996a | ||
|
|
9f084eae94 | ||
|
|
c54c635161 | ||
|
|
fa8b42e05e | ||
|
|
9c3c323884 | ||
|
|
c8a46439be | ||
|
|
b7ec701259 | ||
|
|
a2cd0e0a38 | ||
|
|
7bccc0e236 | ||
|
|
a3a649a79f | ||
|
|
2a14d30552 | ||
|
|
313bef0609 | ||
|
|
960a80aeca | ||
|
|
20e6d50a98 | ||
|
|
507c3417d6 | ||
|
|
d1adb8d4ed | ||
|
|
915ff12747 | ||
|
|
fbade79137 | ||
|
|
a240a677d0 | ||
|
|
ab64cf31f6 | ||
|
|
67f2e32dae | ||
|
|
0dc40fe052 | ||
|
|
b7225be552 | ||
|
|
629e00ec94 | ||
|
|
6d033c9314 | ||
|
|
4f8926ed00 | ||
|
|
1daa1a4603 | ||
|
|
062773d929 | ||
|
|
57decadaef | ||
|
|
ec804ab7c9 | ||
|
|
3ee7533098 | ||
|
|
0d383ccc1f | ||
|
|
e2f2257c34 | ||
|
|
d62b9fc4c6 | ||
|
|
be7ad0c7fb | ||
|
|
156864cc8b | ||
|
|
7240e496cc | ||
|
|
8acdf4018d | ||
|
|
1ea7c3e203 | ||
|
|
e54aeb6125 | ||
|
|
d59f51fbcf | ||
|
|
a667eb6982 | ||
|
|
8d72732247 | ||
|
|
f0f3b30a62 | ||
|
|
d99fe24542 | ||
|
|
ea4c7381bd | ||
|
|
e900d20641 | ||
|
|
8a46647d8c | ||
|
|
968178bf57 | ||
|
|
406a255db0 | ||
|
|
574557810e | ||
|
|
998a02c3a4 | ||
|
|
c6f964c921 | ||
|
|
efb0e147c5 | ||
|
|
9cf7356f98 | ||
|
|
af05c43174 | ||
|
|
380c68ff2b | ||
|
|
068b00b99f | ||
|
|
cd6a42ab64 | ||
|
|
f115abec92 | ||
|
|
f0e23cf878 | ||
|
|
a94f11d809 | ||
|
|
38972bea5f | ||
|
|
a761ff552a | ||
|
|
dbeb84ea9a | ||
|
|
0a4938f39a | ||
|
|
b2182c716d | ||
|
|
8253be73f6 | ||
|
|
20318e296e | ||
|
|
cea1b69286 | ||
|
|
ab8aa69389 | ||
|
|
681491f1d0 | ||
|
|
c2fb815074 | ||
|
|
fffa14dc44 | ||
|
|
df37166d42 | ||
|
|
25fa3a8f6a | ||
|
|
b11507c5e6 | ||
|
|
f06d02489f | ||
|
|
c203af2f71 | ||
|
|
c715155a70 | ||
|
|
8163133294 | ||
|
|
765be5dab4 | ||
|
|
7c1523389d | ||
|
|
7a2b1ba166 | ||
|
|
45b4dcfcd0 | ||
|
|
3b2e535566 | ||
|
|
db556d13a3 | ||
|
|
a987063c68 | ||
|
|
4ce30ef899 | ||
|
|
d988282d98 | ||
|
|
695fdf7ceb | ||
|
|
0befe164cc | ||
|
|
d506c68a80 | ||
|
|
c59c429b75 | ||
|
|
c726b6e4a2 | ||
|
|
96075ad4e1 | ||
|
|
7a8dc07a8a | ||
|
|
1fdac0bc09 | ||
|
|
804b942a36 | ||
|
|
6335d4378b | ||
|
|
cfc2189616 | ||
|
|
e06e032701 | ||
|
|
c9499c2c79 | ||
|
|
2bf43541bb | ||
|
|
0386c7266d | ||
|
|
7aa6ed9a5d | ||
|
|
b71325afa5 | ||
|
|
2cc29bdf77 | ||
|
|
b5d602abc4 | ||
|
|
33c45637ac | ||
|
|
662d4478d0 | ||
|
|
31d3809572 | ||
|
|
024ff4a309 | ||
|
|
9ae8d30b6b | ||
|
|
44349c10b0 | ||
|
|
506520a3c4 | ||
|
|
50f7020977 | ||
|
|
02a27a03cc | ||
|
|
4ad6bacf7b | ||
|
|
26ecc0fa44 | ||
|
|
2619befca6 | ||
|
|
ea24f52b13 | ||
|
|
4abfc47346 | ||
|
|
3b95010d06 | ||
|
|
b3c1b96088 | ||
|
|
a9f1326873 | ||
|
|
75a696fb64 | ||
|
|
24aaacba6d | ||
|
|
e3cd7d5f91 | ||
|
|
2e228e8db5 | ||
|
|
5c9dd80370 | ||
|
|
727f5f2e48 | ||
|
|
1c238f7697 | ||
|
|
119d7cce15 | ||
|
|
4afc8f6083 | ||
|
|
a9ec3af066 | ||
|
|
9edae81fee | ||
|
|
b941b12f12 | ||
|
|
3069de188a | ||
|
|
a968f08abd | ||
|
|
4153d3e5ff | ||
|
|
9d9c1a6c84 | ||
|
|
16ef10a4d9 | ||
|
|
b3766e440a | ||
|
|
6359c3f70f | ||
|
|
efe73fb965 | ||
|
|
c45a962fcc | ||
|
|
f98a03e2e9 | ||
|
|
5b6257814d | ||
|
|
69a445d4ed | ||
|
|
e82c786b8a | ||
|
|
eec2225c89 | ||
|
|
f7355e0b71 | ||
|
|
6c6a99cfe4 | ||
|
|
b4634e2e0d | ||
|
|
3f4cba0612 | ||
|
|
38db99cc75 | ||
|
|
4d5906394b | ||
|
|
2fc212b156 | ||
|
|
53fbb5b027 | ||
|
|
4f24721450 | ||
|
|
83043727b5 | ||
|
|
2d336afb85 | ||
|
|
4d309435c8 | ||
|
|
099ce9cdfd | ||
|
|
8914e60cb8 | ||
|
|
dbd30a40e9 | ||
|
|
c9a598fd59 | ||
|
|
e331e588cf | ||
|
|
2011557771 | ||
|
|
f0ba45d14e | ||
|
|
8352a521b7 | ||
|
|
aa3d4d79f8 | ||
|
|
4f650d760c | ||
|
|
ea4b792627 | ||
|
|
883605239a | ||
|
|
5b8cab920c | ||
|
|
8d3d327335 | ||
|
|
32574050c4 | ||
|
|
8d45a90d9b | ||
|
|
f66862a422 | ||
|
|
6a56be3a9b | ||
|
|
ebf6395de2 | ||
|
|
5df9fbf50d | ||
|
|
ff961155c9 | ||
|
|
14838d06a8 | ||
|
|
99def24dd8 | ||
|
|
f5b210d142 | ||
|
|
a137a23b48 | ||
|
|
6bbf06d9e9 | ||
|
|
0b614b40cf | ||
|
|
27673561bd | ||
|
|
6e2070410d | ||
|
|
7ccf21f74f | ||
|
|
3b2710f285 | ||
|
|
c4b277235b | ||
|
|
a55318add1 | ||
|
|
b57123a4fe | ||
|
|
04dcc00670 | ||
|
|
746a02b49f | ||
|
|
bdbe3db2a9 | ||
|
|
27ae99ad86 | ||
|
|
38add89547 | ||
|
|
a9612fbb2f | ||
|
|
429cc29b5b | ||
|
|
8eca94e405 | ||
|
|
ad71daafb6 | ||
|
|
c936d83688 | ||
|
|
c6684d680f | ||
|
|
fe358b0e13 | ||
|
|
897f259a2a | ||
|
|
f3302c1b3a | ||
|
|
573feeaaab | ||
|
|
019c98ecc1 | ||
|
|
ad6a51a4b5 | ||
|
|
f94278776e | ||
|
|
315885cb0b | ||
|
|
7e605f8228 | ||
|
|
aed70435f9 | ||
|
|
1fb1728ede | ||
|
|
497c4fe5a3 | ||
|
|
09ad7764b8 | ||
|
|
bc3d24fddf | ||
|
|
2d634d628a | ||
|
|
790c22d919 | ||
|
|
c275a56806 | ||
|
|
82c3c7addd | ||
|
|
b464d85c04 | ||
|
|
078618b4cf | ||
|
|
561805a417 | ||
|
|
7bb4324365 | ||
|
|
0608653d35 | ||
|
|
fa3472cdc5 | ||
|
|
7d553b6fcf | ||
|
|
677627630e | ||
|
|
9f23172b22 | ||
|
|
14ceaa472d | ||
|
|
1188b9d3bc | ||
|
|
424c9a9423 | ||
|
|
8f60c81ef3 | ||
|
|
dd648d1ae5 | ||
|
|
02e839e272 | ||
|
|
ec8c56707b | ||
|
|
1e8d317ee8 | ||
|
|
e81df111a7 | ||
|
|
90e55ffe14 | ||
|
|
4e73b1d3fc | ||
|
|
21dca1e34f | ||
|
|
c2292850bb | ||
|
|
04791c92e2 | ||
|
|
d9462b6d8a | ||
|
|
be9b83559e | ||
|
|
958889afee | ||
|
|
dce677035f | ||
|
|
d98a8855ad | ||
|
|
7cd0587a64 | ||
|
|
bb3967f11e | ||
|
|
36ee203bd7 | ||
|
|
914919ba75 | ||
|
|
a4514e8565 | ||
|
|
116e983c50 | ||
|
|
fb667b6c42 | ||
|
|
b0a090cf14 | ||
|
|
110b470d33 | ||
|
|
c517c4d015 | ||
|
|
35ed4f9101 | ||
|
|
18c723c2e3 | ||
|
|
631223602c | ||
|
|
a6cc907de2 | ||
|
|
f847dcccf4 | ||
|
|
ff3f8f52d0 | ||
|
|
d7d46682fc |
@@ -1,4 +1,8 @@
|
||||
__pycache__/
|
||||
https/
|
||||
nodes/config.json
|
||||
workflow/my_workflow.json
|
||||
workflow/my_workflow.json
|
||||
workflow/my_workflow_app.json
|
||||
workflow/prompt_result.json
|
||||
app/*
|
||||
workflow/prompt_result.json
|
||||
|
||||
@@ -1,11 +1,65 @@
|
||||
##
|
||||
> 适配了最新版comfyui的py3.11 ,torch 2.1.2+cu121
|
||||
|
||||
> [Mixlab nodes discord](https://discord.gg/cXs9vZSqeK)
|
||||
|
||||
####
|
||||
[comfyui-Image-reward](https://github.com/shadowcz007/comfyui-Image-reward)
|
||||
|
||||
[comfyui-ultralytics-yolo](https://github.com/shadowcz007/comfyui-ultralytics-yolo)
|
||||
|
||||
[comfyui-moondream](https://github.com/shadowcz007/comfyui-moondream)
|
||||
|
||||
[comfyui-CLIPSeg](https://github.com/shadowcz007/comfyui-CLIPSeg)
|
||||
|
||||
|
||||
## 🚀🚗🚚🏃 Workflow-to-APP
|
||||
- 新增AppInfo节点,可以通过简单的配置,把workflow转变为一个Web APP。
|
||||
- 支持多个web app 切换
|
||||
- 发布为app的workflow,可以在右键里再次编辑了
|
||||
- web app可以设置分类,在comfyui右键菜单可以编辑更新web app
|
||||
|
||||
|
||||
- Support multiple web app switching.
|
||||
- Add the AppInfo node, which allows you to transform the workflow into a web app by simple configuration.
|
||||
- The workflow, which is now released as an app, can also be edited again by right-clicking.
|
||||
- The web app can be configured with categories, and the web app can be edited and updated in the right-click menu of ComfyUI.
|
||||
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
Example:
|
||||
- workflow
|
||||

|
||||
[text-to-image](./workflow/Text-to-Image-app.json)
|
||||
|
||||
APP-JSON:
|
||||
- [text-to-image](./example/Text-to-Image_3.json)
|
||||
- [image-to-image](./example/Image-to-Image_2.json)
|
||||
- text-to-text
|
||||
|
||||
> 暂时支持 9 种节点作为界面上的输入节点:Load Image、VHS_LoadVideo、CLIPTextEncode、PromptSlide、TextInput_、Color、FloatSlider、IntNumber、CheckpointLoaderSimple、LoraLoader
|
||||
|
||||
> 输出节点:PreviewImage 、SaveImage、ShowTextForGPT、VHS_VideoCombine、PromptImage
|
||||
|
||||
> seed统一输入控件,支持:SamplerCustom、KSampler
|
||||
|
||||
> 配套[ps插件](https://github.com/shadowcz007/comfyui-ps-plugin)
|
||||
|
||||
> 如果遇到上传图片不成功,请检查下:局域网或者是云服务,请使用https,端口8189这个服务( 感谢 @Damien 反馈问题)
|
||||
|
||||
> If you encounter difficulties in uploading images, please check the following: for local network or cloud services, please use HTTPS and the service on port 8189. (Thanks to @Damien for reporting the issue.)
|
||||
|
||||
|
||||
|
||||
## 🏃🚗🚚🚀 Real-time Design
|
||||
> ScreenShareNode & FloatingVideoNode. Now comfyui supports capturing screen pixel streams from any software and can be used for LCM-Lora integration. Let's get started with implementation and design! 💻🌐
|
||||
|
||||

|
||||
|
||||
|
||||
### ScreenShareNode & FloatingVideoNode
|
||||
> Now comfyui supports capturing screen pixel streams from any software and can be used for LCM-Lora integration. Let's get started with implementation and design! 💻🌐
|
||||
|
||||
https://github.com/shadowcz007/comfyui-mixlab-nodes/assets/12645064/e7e77f90-e43e-410a-ab3a-1952b7b4e7da
|
||||
|
||||
|
||||
@@ -15,23 +69,147 @@ https://github.com/shadowcz007/comfyui-mixlab-nodes/assets/12645064/e7e77f90-e43
|
||||
!! Please use the address with HTTPS (https://127.0.0.1).
|
||||
|
||||
|
||||
### LoadImagesFromLocal
|
||||
> Monitor changes to images in a local folder, and trigger real-time execution of workflows, supporting common image formats, especially PSD format, in conjunction with Photoshop. Q: Translate into English
|
||||
|
||||

|
||||
|
||||
[workflow-4](./workflow/4-loadfromlocal-watcher-workflow.json)
|
||||
### SpeechRecognition & SpeechSynthesis
|
||||

|
||||
|
||||
[Voice + Real-time Face Swap Workflow](./workflow/语音+实时换脸workflow.json)
|
||||
|
||||
### GPT
|
||||
> ChatGPT、ChatGLM3 , Some code provided by rui.
|
||||
|
||||
> Support for calling multiple GPTs.ChatGPT、ChatGLM3 、ChatGLM4 , Some code provided by rui. If you are using OpenAI's service, fill in https://api.openai.com/v1 . If you are using a local LLM service, fill in http://127.0.0.1:xxxx/v1 . Azure OpenAI:https://xxxx.openai.azure.com
|
||||
|
||||

|
||||
|
||||
[workflow-5](./workflow/5-gpt-workflow.json)
|
||||
|
||||
|
||||
## Prompt
|
||||
> PromptSlide
|
||||

|
||||
|
||||
<!--  -->
|
||||
|
||||
> randomPrompt
|
||||
|
||||

|
||||
|
||||
> ClipInterrogator
|
||||
|
||||
[add clip-interrogator](https://github.com/pharmapsychotic/clip-interrogator)
|
||||
|
||||
> PromptImage & PromptSimplification,Assist in simplifying prompt words, comparing images and prompt word nodes.
|
||||
|
||||
> ChinesePrompt && PromptGenerate,中文prompt节点,直接用中文书写你的prompt
|
||||
|
||||

|
||||
|
||||
|
||||
### Layers
|
||||
> A new layer class node has been added, allowing you to separate the image into layers. After merging the images, you can input the controlnet for further processing.
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
|
||||
### 3D
|
||||

|
||||
[workflow](./workflow/3D-workflow.json)
|
||||
|
||||
|
||||
### Image
|
||||
|
||||
#### LoadImagesToBatch
|
||||
> Upload multiple images for batch input into the IP adapter.
|
||||
|
||||
#### LoadImagesFromLocal
|
||||
> Monitor changes to images in a local folder, and trigger real-time execution of workflows, supporting common image formats, especially PSD format, in conjunction with Photoshop.
|
||||
|
||||

|
||||
|
||||
[workflow-4](./workflow/4-loadfromlocal-watcher-workflow.json)
|
||||
|
||||
#### LoadImagesFromURL
|
||||
> Conveniently load images from a fixed address on the internet to ensure that default images in the workflow can be executed.
|
||||
|
||||
|
||||
### Style
|
||||
> Apply VisualStyle Prompting , Modified from [ComfyUI_VisualStylePrompting](https://github.com/ExponentialML/ComfyUI_VisualStylePrompting)
|
||||
|
||||

|
||||
|
||||
> StyleAligned , Modified from [style_aligned_comfy](https://github.com/brianfitzgerald/style_aligned_comfy)
|
||||
|
||||
|
||||
### Utils
|
||||
> The Color node provides a color picker for easy color selection, the Font node offers built-in font selection for use with TextImage to generate text images, and the DynamicDelayByText node allows delayed execution based on the length of the input text.
|
||||
|
||||
- [添加了DynamicDelayByText功能,可以根据输入文本的长度进行延迟执行。](./workflow/audio-chatgpt-workflow.json)
|
||||
|
||||
- [Added DynamicDelayByText, enabling delayed execution based on input text length.](./workflow/audio-chatgpt-workflow.json)
|
||||
|
||||
- [使用CkptNames 对比不同的模型效果](./workflow/ckpts-image-workflow.json)
|
||||
|
||||
- [CkptNames compare the effects of different models.](./workflow/ckpts-image-workflow.json)
|
||||
|
||||
|
||||
|
||||
### Other Nodes
|
||||
|
||||

|
||||

|
||||
|
||||
[workflow-1](./workflow/1-workflow.json)
|
||||
|
||||
|
||||
|
||||
> TransparentImage
|
||||
|
||||

|
||||
|
||||
|
||||
> FeatheredMask、SmoothMask
|
||||
|
||||
Add edges to an image.
|
||||
|
||||

|
||||
|
||||
|
||||
> LaMaInpainting
|
||||
|
||||
from [simple-lama-inpainting](https://github.com/enesmsahin/simple-lama-inpainting)
|
||||
|
||||
|
||||
> rembgNode
|
||||
|
||||
"briarmbg","u2net","u2netp","u2net_human_seg","u2net_cloth_seg","silueta","isnet-general-use","isnet-anime"
|
||||
|
||||
*** briarmbg *** model was developed by BRlA Al and can be used as an open-source model for non-commercial purposes
|
||||
|
||||
|
||||
### Improvement
|
||||
|
||||
- Add "help" option to the context menu for each node.
|
||||
- Add "Nodes Map" option to the global context menu.
|
||||
|
||||
An improvement has been made to directly redirect to GitHub to search for missing nodes when loading the graph.
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
|
||||
### Models
|
||||
|
||||
[Download rembg Models](https://github.com/danielgatis/rembg/tree/main#Models),move to:models/rembg
|
||||
|
||||
[Download lama](https://github.com/enesmsahin/simple-lama-inpainting/releases/download/v0.1.0/big-lama.pt), move to : models/lama
|
||||
|
||||
[Download Salesforce/blip-image-captioning-base](https://huggingface.co/Salesforce/blip-image-captioning-base), move to : models/clip_interrogator/Salesforce/blip-image-captioning-base
|
||||
|
||||
[Download succinctly/text2image-prompt-generator](https://huggingface.co/succinctly/text2image-prompt-generator/tree/main),move to:prompt_generator/text2image-prompt-generator
|
||||
|
||||
[Download Helsinki-NLP/opus-mt-zh-en](https://huggingface.co/Helsinki-NLP/opus-mt-zh-en/tree/main),move to:prompt_generator/opus-mt-zh-en
|
||||
|
||||
## Installation
|
||||
|
||||
manually install, simply clone the repo into the custom_nodes directory with this command:
|
||||
@@ -47,7 +225,7 @@ Install the requirements:
|
||||
|
||||
run directly:
|
||||
```
|
||||
cd ComfyUI_Mixlab
|
||||
cd ComfyUI/custom_nodes/comfyui-mixlab-nodes
|
||||
install.bat
|
||||
```
|
||||
|
||||
@@ -63,60 +241,40 @@ pip3 install -r requirements.txt
|
||||
|
||||
|
||||
|
||||
## Nodes
|
||||
|
||||

|
||||

|
||||
|
||||
[workflow-1](./workflow/1-workflow.json)
|
||||
|
||||
> randomPrompt
|
||||
|
||||

|
||||
|
||||
> TransparentImage
|
||||
|
||||

|
||||
#### Chinese community
|
||||
访问 [www.mixcomfy.com](https://www.mixcomfy.com),获得更多内测功能,关注微信公众号:Mixlab无界社区
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
> Consistency Decoder
|
||||
|
||||
[openai Consistency Decoder]( https://github.com/openai/consistencydecoder)
|
||||
|
||||

|
||||
After downloading the OpenAI VAE model, place it in the "model/vae" directory for use.
|
||||
https://openaipublic.azureedge.net/diff-vae/c9cebd3132dd9c42936d803e33424145a748843c8f716c0814838bdc8a2fe7cb/decoder.pt
|
||||
|
||||
|
||||
> FeatheredMask、SmoothMask
|
||||
|
||||
Add edges to an image.
|
||||
|
||||

|
||||
####
|
||||
File / LoadImagesFromPath SaveImageToLocal LoadImagesFromURL
|
||||
|
||||
|
||||
|
||||
### Improvement
|
||||
An improvement has been made to directly redirect to GitHub to search for missing nodes when loading the graph.
|
||||
|
||||

|
||||
|
||||
|
||||
### Models
|
||||
[Download CLIPSeg](https://huggingface.co/CIDAS/clipseg-rd64-refined/tree/main), move to : model/clipseg
|
||||
|
||||
<!-- ### Workflow
|
||||
[Workflow](./workflow.md) -->
|
||||
|
||||
#### Thanks:
|
||||
[ComfyUI-CLIPSeg](https://github.com/biegert/ComfyUI-CLIPSeg/tree/main)
|
||||
|
||||
#### discussions:
|
||||
[discussions](https://github.com/shadowcz007/comfyui-mixlab-nodes/discussions)
|
||||
|
||||
### TODO:
|
||||
- vector https://github.com/GeorgLegato/stable-diffusion-webui-vectorstudio
|
||||
|
||||
<picture>
|
||||
<source
|
||||
media="(prefers-color-scheme: dark)"
|
||||
srcset="
|
||||
https://api.star-history.com/svg?repos=shadowcz007/comfyui-mixlab-nodes&type=Date&theme=dark
|
||||
"
|
||||
/>
|
||||
<source
|
||||
media="(prefers-color-scheme: light)"
|
||||
srcset="
|
||||
https://api.star-history.com/svg?repos=shadowcz007/comfyui-mixlab-nodes&type=Date
|
||||
"
|
||||
/>
|
||||
<img
|
||||
alt="Star History Chart"
|
||||
src="https://api.star-history.com/svg?repos=shadowcz007/comfyui-mixlab-nodes&type=Date"
|
||||
/>
|
||||
</picture>
|
||||
|
||||
|
||||
@@ -4,9 +4,9 @@ import subprocess
|
||||
import importlib.util
|
||||
import sys,json
|
||||
import urllib
|
||||
|
||||
import hashlib
|
||||
import datetime
|
||||
|
||||
import folder_paths
|
||||
|
||||
python = sys.executable
|
||||
|
||||
@@ -64,9 +64,28 @@ except ImportError:
|
||||
sys.exit()
|
||||
|
||||
|
||||
|
||||
def install_openai():
|
||||
# Helper function to install the OpenAI module if not already installed
|
||||
try:
|
||||
importlib.import_module('openai')
|
||||
except ImportError:
|
||||
import pip
|
||||
pip.main(['install', 'openai'])
|
||||
|
||||
install_openai()
|
||||
|
||||
|
||||
current_path = os.path.abspath(os.path.dirname(__file__))
|
||||
|
||||
|
||||
|
||||
def calculate_md5(string):
|
||||
encoded_string = string.encode()
|
||||
md5_hash = hashlib.md5(encoded_string).hexdigest()
|
||||
return md5_hash
|
||||
|
||||
|
||||
def create_key(key_p,crt_p):
|
||||
import OpenSSL
|
||||
# 生成自签名证书
|
||||
@@ -114,8 +133,38 @@ def create_for_https():
|
||||
return (crt,key)
|
||||
|
||||
|
||||
|
||||
# workflow 目录下的所有json
|
||||
def read_workflow_json_files_all(folder_path):
|
||||
print('#read_workflow_json_files_all',folder_path)
|
||||
json_files = []
|
||||
for root, dirs, files in os.walk(folder_path):
|
||||
for file in files:
|
||||
if file.endswith('.json'):
|
||||
json_files.append(os.path.join(root, file))
|
||||
|
||||
data = []
|
||||
for file_path in json_files:
|
||||
try:
|
||||
with open(file_path) as json_file:
|
||||
json_data = json.load(json_file)
|
||||
creation_time = datetime.datetime.fromtimestamp(os.path.getctime(file_path))
|
||||
numeric_timestamp = creation_time.timestamp()
|
||||
file_info = {
|
||||
'filename': os.path.basename(file_path),
|
||||
'category': os.path.dirname(file_path),
|
||||
'data': json_data,
|
||||
'date': numeric_timestamp
|
||||
}
|
||||
data.append(file_info)
|
||||
except Exception as e:
|
||||
print(e)
|
||||
|
||||
sorted_data = sorted(data, key=lambda x: x['date'], reverse=True)
|
||||
return sorted_data
|
||||
|
||||
# workflow
|
||||
def read_workflow_json_files(folder_path):
|
||||
def read_workflow_json_files(folder_path ):
|
||||
json_files = []
|
||||
for filename in os.listdir(folder_path):
|
||||
if filename.endswith('.json'):
|
||||
@@ -143,43 +192,262 @@ def read_workflow_json_files(folder_path):
|
||||
def get_workflows():
|
||||
# print("#####path::", current_path)
|
||||
workflow_path=os.path.join(current_path, "workflow")
|
||||
print('workflow_path: ',workflow_path)
|
||||
# print('workflow_path: ',workflow_path)
|
||||
if not os.path.exists(workflow_path):
|
||||
# 使用mkdir()方法创建新目录
|
||||
os.mkdir(workflow_path)
|
||||
workflows=read_workflow_json_files(workflow_path)
|
||||
return workflows
|
||||
|
||||
def get_my_workflow_for_app(filename="my_workflow_app.json",category="",is_all=False):
|
||||
app_path=os.path.join(current_path, "app")
|
||||
if not os.path.exists(app_path):
|
||||
os.mkdir(app_path)
|
||||
|
||||
category_path=os.path.join(app_path,category)
|
||||
if not os.path.exists(category_path):
|
||||
os.mkdir(category_path)
|
||||
|
||||
apps=[]
|
||||
if filename==None:
|
||||
|
||||
#TODO 支持目录内遍历
|
||||
if is_all:
|
||||
data=read_workflow_json_files_all(category_path)
|
||||
else:
|
||||
data=read_workflow_json_files(category_path)
|
||||
|
||||
i=0
|
||||
for item in data:
|
||||
# print(item)
|
||||
try:
|
||||
x=item["data"]
|
||||
# 管理员模式,读取全部数据
|
||||
if i==0 or is_all:
|
||||
apps.append({
|
||||
"filename":item["filename"],
|
||||
# "category":item['category'],
|
||||
"data":x,
|
||||
"date":item["date"],
|
||||
})
|
||||
else:
|
||||
category=''
|
||||
input=None
|
||||
output=None
|
||||
if 'category' in x['app']:
|
||||
category=x['app']['category']
|
||||
if 'input' in x['app']:
|
||||
input=x['app']['input']
|
||||
if 'output' in x['app']:
|
||||
output=x['app']['output']
|
||||
apps.append({
|
||||
"filename":item["filename"],
|
||||
"category":category,
|
||||
"data":{
|
||||
"app":{
|
||||
"category":category,
|
||||
"description":x['app']['description'],
|
||||
"filename":(x['app']['filename'] if 'filename' in x['app'] else "") ,
|
||||
"icon":(x['app']['icon'] if 'icon' in x['app'] else None),
|
||||
"name":x['app']['name'],
|
||||
"version":x['app']['version'],
|
||||
"input":input,
|
||||
"output":output,
|
||||
"id":x['app']['id']
|
||||
}
|
||||
},
|
||||
"date":item["date"]
|
||||
})
|
||||
i+=1
|
||||
except Exception as e:
|
||||
print("发生异常:", str(e))
|
||||
else:
|
||||
app_workflow_path=os.path.join(category_path, filename)
|
||||
# print('app_workflow_path: ',app_workflow_path)
|
||||
try:
|
||||
with open(app_workflow_path) as json_file:
|
||||
apps = [{
|
||||
'filename':filename,
|
||||
'data':json.load(json_file)
|
||||
}]
|
||||
except Exception as e:
|
||||
print("发生异常:", str(e))
|
||||
|
||||
if len(apps)==1 and category!='' and category!=None:
|
||||
data=read_workflow_json_files(category_path)
|
||||
|
||||
for item in data:
|
||||
x=item["data"]
|
||||
# print(apps[0]['filename'] ,item["filename"])
|
||||
if apps[0]['filename']!=item["filename"]:
|
||||
category=''
|
||||
input=None
|
||||
output=None
|
||||
if 'category' in x['app']:
|
||||
category=x['app']['category']
|
||||
if 'input' in x['app']:
|
||||
input=x['app']['input']
|
||||
if 'output' in x['app']:
|
||||
output=x['app']['output']
|
||||
apps.append({
|
||||
"filename":item["filename"],
|
||||
# "category":category,
|
||||
"data":{
|
||||
"app":{
|
||||
"category":category,
|
||||
"description":x['app']['description'],
|
||||
"filename":(x['app']['filename'] if 'filename' in x['app'] else "") ,
|
||||
"icon":(x['app']['icon'] if 'icon' in x['app'] else None),
|
||||
"name":x['app']['name'],
|
||||
"version":x['app']['version'],
|
||||
"input":input,
|
||||
"output":output,
|
||||
"id":x['app']['id']
|
||||
}
|
||||
},
|
||||
"date":item["date"]
|
||||
})
|
||||
|
||||
return apps
|
||||
|
||||
# 历史记录
|
||||
def save_prompt_result(id,data):
|
||||
prompt_result_path=os.path.join(current_path, "workflow/prompt_result.json")
|
||||
prompt_result={}
|
||||
if os.path.exists(prompt_result_path):
|
||||
with open(prompt_result_path) as json_file:
|
||||
prompt_result = json.load(json_file)
|
||||
|
||||
prompt_result[id]=data
|
||||
|
||||
with open(prompt_result_path, 'w') as file:
|
||||
json.dump(prompt_result, file)
|
||||
return prompt_result_path
|
||||
|
||||
def get_prompt_result():
|
||||
prompt_result_path=os.path.join(current_path, "workflow/prompt_result.json")
|
||||
prompt_result={}
|
||||
if os.path.exists(prompt_result_path):
|
||||
with open(prompt_result_path) as json_file:
|
||||
prompt_result = json.load(json_file)
|
||||
res=list(prompt_result.values())
|
||||
# print(res)
|
||||
return res
|
||||
|
||||
|
||||
def save_workflow_json(data):
|
||||
workflow_path=os.path.join(current_path, "workflow/my_workflow.json")
|
||||
with open(workflow_path, 'w') as file:
|
||||
json.dump(data, file)
|
||||
return workflow_path
|
||||
|
||||
def save_workflow_for_app(data,filename="my_workflow_app.json",category=""):
|
||||
app_path=os.path.join(current_path, "app")
|
||||
if not os.path.exists(app_path):
|
||||
os.mkdir(app_path)
|
||||
|
||||
category_path=os.path.join(app_path,category)
|
||||
if not os.path.exists(category_path):
|
||||
os.mkdir(category_path)
|
||||
|
||||
app_workflow_path=os.path.join(category_path, filename)
|
||||
|
||||
try:
|
||||
output_str = json.dumps(data['output'])
|
||||
data['app']['id']=calculate_md5(output_str)
|
||||
# id=data['app']['id']
|
||||
except Exception as e:
|
||||
print("发生异常:", str(e))
|
||||
|
||||
with open(app_workflow_path, 'w') as file:
|
||||
json.dump(data, file)
|
||||
return filename
|
||||
|
||||
def get_nodes_map():
|
||||
# print("#####path::", current_path)
|
||||
data_path=os.path.join(current_path, "data")
|
||||
print('data_path: ',data_path)
|
||||
# if not os.path.exists(data_path):
|
||||
# # 使用mkdir()方法创建新目录
|
||||
# os.mkdir(data_path)
|
||||
json_data={}
|
||||
nodes_map=os.path.join(current_path, "data/extension-node-map.json")
|
||||
if os.path.exists(nodes_map):
|
||||
with open(nodes_map) as json_file:
|
||||
json_data = json.load(json_file)
|
||||
|
||||
return json_data
|
||||
|
||||
|
||||
# 保存原始的 get 方法
|
||||
_original_request = aiohttp.ClientSession._request
|
||||
|
||||
# 定义新的 get 方法
|
||||
async def new_request(self, method, url, *args, **kwargs):
|
||||
# 检查环境变量以确定是否使用代理
|
||||
proxy = os.environ.get('HTTP_PROXY') or os.environ.get('HTTPS_PROXY') or os.environ.get('http_proxy') or os.environ.get('https_proxy')
|
||||
# print('Proxy Config:',proxy)
|
||||
if proxy and 'proxy' not in kwargs:
|
||||
kwargs['proxy'] = proxy
|
||||
print('Use Proxy:',proxy)
|
||||
# 调用原始的 _request 方法
|
||||
return await _original_request(self, method, url, *args, **kwargs)
|
||||
|
||||
# 应用 Monkey Patch
|
||||
aiohttp.ClientSession._request = new_request
|
||||
import socket
|
||||
|
||||
async def check_port_available(address, port):
|
||||
#检查端口是否可用
|
||||
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
|
||||
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||
try:
|
||||
sock.bind((address, port))
|
||||
return True
|
||||
except socket.error:
|
||||
return False
|
||||
|
||||
# https
|
||||
async def new_start(self, address, port, verbose=True, call_on_start=None):
|
||||
try:
|
||||
runner = web.AppRunner(self.app, access_log=None)
|
||||
await runner.setup()
|
||||
|
||||
if not await check_port_available(address, port):
|
||||
raise RuntimeError(f"Port {port} is already in use.")
|
||||
|
||||
site = web.TCPSite(runner, address, port)
|
||||
await site.start()
|
||||
|
||||
import ssl
|
||||
crt,key=create_for_https()
|
||||
crt, key = create_for_https()
|
||||
ssl_context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)
|
||||
ssl_context.load_cert_chain(crt,key)
|
||||
site2 = web.TCPSite(runner, address, port+1,ssl_context=ssl_context)
|
||||
await site2.start()
|
||||
ssl_context.load_cert_chain(crt, key)
|
||||
|
||||
success = False
|
||||
for i in range(10): # 尝试最多10次
|
||||
if await check_port_available(address, port + 1 + i):
|
||||
https_port = port + 1 + i
|
||||
site2 = web.TCPSite(runner, address, https_port, ssl_context=ssl_context)
|
||||
await site2.start()
|
||||
success = True
|
||||
break
|
||||
|
||||
if not success:
|
||||
raise RuntimeError(f"Ports {port + 1} to {port + 10} are all in use.")
|
||||
|
||||
if address == '':
|
||||
address = '0.0.0.0'
|
||||
if verbose:
|
||||
# print('\033[91mMixlab Nodes: \033[93mLoaded\033[0m')
|
||||
print("\033[93mStarting server\n")
|
||||
print("\033[93mTo see the GUI go to: http://{}:{}".format(address, port))
|
||||
print("\033[93mTo see the GUI go to: https://{}:{}\033[0m".format(address, port+1))
|
||||
print("\033[93mTo see the GUI go to: https://{}:{}\033[0m".format(address, https_port))
|
||||
if call_on_start is not None:
|
||||
call_on_start(address, port)
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error starting the server: {e}")
|
||||
|
||||
# import webbrowser
|
||||
# if os.name == 'nt' and address == '0.0.0.0':
|
||||
# address = '127.0.0.1'
|
||||
@@ -190,7 +458,7 @@ async def new_start(self, address, port, verbose=True, call_on_start=None):
|
||||
PromptServer.start=new_start
|
||||
|
||||
# 创建路由表
|
||||
routes = web.RouteTableDef()
|
||||
routes = PromptServer.instance.routes
|
||||
|
||||
@routes.post('/mixlab')
|
||||
async def mixlab_hander(request):
|
||||
@@ -205,6 +473,18 @@ async def mixlab_hander(request):
|
||||
print(e)
|
||||
return web.json_response(data)
|
||||
|
||||
|
||||
@routes.get('/mixlab/app')
|
||||
async def mixlab_app_handler(request):
|
||||
html_file = os.path.join(current_path, "web/index.html")
|
||||
if os.path.exists(html_file):
|
||||
with open(html_file, 'r', encoding='utf-8', errors='ignore') as f:
|
||||
html_data = f.read()
|
||||
return web.Response(text=html_data, content_type='text/html')
|
||||
else:
|
||||
return web.Response(text="HTML file not found", status=404)
|
||||
|
||||
|
||||
@routes.post('/mixlab/workflow')
|
||||
async def mixlab_workflow_hander(request):
|
||||
data = await request.json()
|
||||
@@ -217,6 +497,29 @@ async def mixlab_workflow_hander(request):
|
||||
'status':'success',
|
||||
'file_path':file_path
|
||||
}
|
||||
elif data['task']=='save_app':
|
||||
category=""
|
||||
if "category" in data:
|
||||
category=data['category']
|
||||
file_path=save_workflow_for_app(data['data'],data['filename'],category)
|
||||
result={
|
||||
'status':'success',
|
||||
'file_path':file_path
|
||||
}
|
||||
elif data['task']=='my_app':
|
||||
filename=None
|
||||
category=""
|
||||
admin=False
|
||||
if 'filename' in data:
|
||||
filename=data['filename']
|
||||
if 'category' in data:
|
||||
category=data['category']
|
||||
if 'admin' in data:
|
||||
admin=data['admin']
|
||||
result={
|
||||
'data':get_my_workflow_for_app(filename,category,admin),
|
||||
'status':'success',
|
||||
}
|
||||
elif data['task']=='list':
|
||||
result={
|
||||
'data':get_workflows(),
|
||||
@@ -227,19 +530,52 @@ async def mixlab_workflow_hander(request):
|
||||
|
||||
return web.json_response(result)
|
||||
|
||||
def new_add_routes(self):
|
||||
import nodes
|
||||
self.app.add_routes(routes)
|
||||
self.app.add_routes(self.routes)
|
||||
for name, dir in nodes.EXTENSION_WEB_DIRS.items():
|
||||
self.app.add_routes([
|
||||
web.static('/extensions/' + urllib.parse.quote(name), dir, follow_symlinks=True),
|
||||
])
|
||||
self.app.add_routes([
|
||||
web.static('/', self.web_root, follow_symlinks=True),
|
||||
])
|
||||
@routes.post('/mixlab/nodes_map')
|
||||
async def nodes_map_hander(request):
|
||||
data = await request.json()
|
||||
result={}
|
||||
try:
|
||||
result={
|
||||
'data':get_nodes_map(),
|
||||
'status':'success',
|
||||
}
|
||||
except Exception as e:
|
||||
print(e)
|
||||
|
||||
return web.json_response(result)
|
||||
|
||||
|
||||
@routes.post("/mixlab/folder_paths")
|
||||
async def get_checkpoints(request):
|
||||
data = await request.json()
|
||||
t="checkpoints"
|
||||
try:
|
||||
t=data['type']
|
||||
except Exception as e:
|
||||
print('/mixlab/folder_paths',False,e)
|
||||
|
||||
names = folder_paths.get_filename_list(t)
|
||||
|
||||
return web.json_response({"names":names,"types":list(folder_paths.folder_names_and_paths.keys())})
|
||||
|
||||
|
||||
@routes.post("/mixlab/prompt_result")
|
||||
async def post_prompt_result(request):
|
||||
data = await request.json()
|
||||
res=None
|
||||
# print(data)
|
||||
try:
|
||||
action=data['action']
|
||||
if action=='save':
|
||||
result=data['data']
|
||||
res=save_prompt_result(result['prompt_id'],result)
|
||||
elif action=='all':
|
||||
res=get_prompt_result()
|
||||
except Exception as e:
|
||||
print('/mixlab/prompt_result',False,e)
|
||||
|
||||
return web.json_response({"result":res})
|
||||
|
||||
PromptServer.add_routes=new_add_routes
|
||||
|
||||
|
||||
|
||||
@@ -255,51 +591,199 @@ PromptServer.add_routes=new_add_routes
|
||||
|
||||
|
||||
# 导入节点
|
||||
from .nodes.PromptNode import RandomPrompt
|
||||
from .nodes.ImageNode import TransparentImage,LoadImagesFromPath,AreaToMask,SmoothMask,FeatheredMask,SplitLongMask,ImageCropByAlpha,EnhanceImage,FaceToMask
|
||||
from .nodes.Vae import VAELoader,VAEDecode
|
||||
from .nodes.PromptNode import GLIGENTextBoxApply_Advanced,EmbeddingPrompt,RandomPrompt,PromptSlide,PromptSimplification,PromptImage,JoinWithDelimiter
|
||||
from .nodes.ImageNode import LoadImages_,CompositeImages,GridDisplayAndSave,GridInput,ImagesPrompt,SaveImageAndMetadata,SaveImageToLocal,SplitImage,GridOutput,GetImageSize_,MirroredImage,ImageColorTransfer,NoiseImage,TransparentImage,GradientImage,LoadImagesFromPath,LoadImagesFromURL,ResizeImage,TextImage,SvgImage,Image3D,ShowLayer,NewLayer,MergeLayers,CenterImage,AreaToMask,SmoothMask,SplitLongMask,ImageCropByAlpha,EnhanceImage,FaceToMask
|
||||
# from .nodes.Vae import VAELoader,VAEDecode
|
||||
from .nodes.ScreenShareNode import ScreenShareNode,FloatingVideo
|
||||
from .nodes.Clipseg import CLIPSeg,CombineMasks
|
||||
from .nodes.ChatGPT import ChatGPTNode,SessionHistory
|
||||
|
||||
from .nodes.ChatGPT import ChatGPTNode,ShowTextForGPT,CharacterInText,TextSplitByDelimiter
|
||||
from .nodes.Audio import GamePal,SpeechRecognition,SpeechSynthesis
|
||||
from .nodes.Utils import IncrementingListNode,ListSplit,CreateLoraNames,CreateSampler_names,CreateCkptNames,CreateSeedNode,TESTNODE_,TESTNODE_TOKEN,AppInfo,IntNumber,FloatSlider,TextInput,ColorInput,FontInput,TextToNumber,DynamicDelayProcessor,LimitNumber,SwitchByIndex,MultiplicationNode
|
||||
from .nodes.Mask import MaskListReplace,MaskListMerge,OutlineMask,FeatheredMask
|
||||
|
||||
from .nodes.Style import ApplyVisualStylePrompting,StyleAlignedReferenceSampler,StyleAlignedBatchAlign,StyleAlignedSampleReferenceLatents
|
||||
|
||||
from .nodes.Video import VideoCombine_Adv,LoadVideoAndSegment,ImageListReplace,VAEEncodeForInpaint_Frames
|
||||
|
||||
|
||||
# 要导出的所有节点及其名称的字典
|
||||
# 注意:名称应全局唯一
|
||||
NODE_CLASS_MAPPINGS = {
|
||||
"AppInfo":AppInfo,
|
||||
"TESTNODE_":TESTNODE_,
|
||||
"TESTNODE_TOKEN":TESTNODE_TOKEN,
|
||||
"RandomPrompt":RandomPrompt,
|
||||
# "LoraPrompt":LoraPrompt,
|
||||
"EmbeddingPrompt":EmbeddingPrompt,
|
||||
"PromptSlide":PromptSlide,
|
||||
"GLIGENTextBoxApply_Advanced":GLIGENTextBoxApply_Advanced,
|
||||
"PromptSimplification":PromptSimplification,
|
||||
"PromptImage":PromptImage,
|
||||
"MirroredImage":MirroredImage,
|
||||
"NoiseImage":NoiseImage,
|
||||
"GradientImage":GradientImage,
|
||||
"TransparentImage":TransparentImage,
|
||||
"ResizeImageMixlab":ResizeImage,
|
||||
"LoadImagesFromPath":LoadImagesFromPath,
|
||||
"LoadImagesFromURL":LoadImagesFromURL,
|
||||
"LoadImagesToBatch":LoadImages_,
|
||||
"TextImage":TextImage,
|
||||
"EnhanceImage":EnhanceImage,
|
||||
"SvgImage":SvgImage,
|
||||
"3DImage":Image3D,
|
||||
"ImageColorTransfer":ImageColorTransfer,
|
||||
"ShowLayer":ShowLayer,
|
||||
"NewLayer":NewLayer,
|
||||
"CompositeImages_":CompositeImages,
|
||||
"SplitImage":SplitImage,
|
||||
"CenterImage":CenterImage,
|
||||
"GridOutput":GridOutput,
|
||||
"GridDisplayAndSave":GridDisplayAndSave,
|
||||
"GridInput":GridInput,
|
||||
"MergeLayers":MergeLayers,
|
||||
"SplitLongMask":SplitLongMask,
|
||||
"FeatheredMask":FeatheredMask,
|
||||
"SmoothMask":SmoothMask,
|
||||
"FaceToMask":FaceToMask,
|
||||
"AreaToMask":AreaToMask,
|
||||
"ImageCropByAlpha":ImageCropByAlpha,
|
||||
"VAELoaderConsistencyDecoder":VAELoader,
|
||||
"VAEDecodeConsistencyDecoder":VAEDecode,
|
||||
"ImagesPrompt_":ImagesPrompt,
|
||||
# "VAELoaderConsistencyDecoder":VAELoader,
|
||||
"SaveImageToLocal":SaveImageToLocal,
|
||||
"SaveImageAndMetadata_":SaveImageAndMetadata,
|
||||
# "VAEDecodeConsistencyDecoder":VAEDecode,
|
||||
"ScreenShare":ScreenShareNode,
|
||||
"FloatingVideo":FloatingVideo,
|
||||
"CLIPSeg_":CLIPSeg,
|
||||
"CombineMasks_":CombineMasks,
|
||||
"ChatGPT":ChatGPTNode,
|
||||
"SessionHistory":SessionHistory
|
||||
"ChatGPTOpenAI":ChatGPTNode,
|
||||
"ShowTextForGPT":ShowTextForGPT,
|
||||
"CharacterInText":CharacterInText,
|
||||
"TextSplitByDelimiter":TextSplitByDelimiter,
|
||||
"SpeechRecognition":SpeechRecognition,
|
||||
"SpeechSynthesis":SpeechSynthesis,
|
||||
"Color":ColorInput,
|
||||
"FloatSlider":FloatSlider,
|
||||
"IntNumber":IntNumber,
|
||||
"TextInput_":TextInput,
|
||||
"Font":FontInput,
|
||||
"TextToNumber":TextToNumber,
|
||||
"DynamicDelayProcessor":DynamicDelayProcessor,
|
||||
"MultiplicationNode":MultiplicationNode,
|
||||
"GetImageSize_":GetImageSize_,
|
||||
"SwitchByIndex":SwitchByIndex,
|
||||
"LimitNumber":LimitNumber,
|
||||
"OutlineMask":OutlineMask,
|
||||
"MaskListMerge_":MaskListMerge,
|
||||
"JoinWithDelimiter":JoinWithDelimiter,
|
||||
"Seed_":CreateSeedNode,
|
||||
"CkptNames_":CreateCkptNames,
|
||||
"SamplerNames_":CreateSampler_names,
|
||||
"LoraNames_":CreateLoraNames,
|
||||
"ApplyVisualStylePrompting_":ApplyVisualStylePrompting,
|
||||
"StyleAlignedReferenceSampler_": StyleAlignedReferenceSampler,
|
||||
"StyleAlignedSampleReferenceLatents_": StyleAlignedSampleReferenceLatents,
|
||||
"StyleAlignedBatchAlign_": StyleAlignedBatchAlign,
|
||||
"LoadVideoAndSegment_":LoadVideoAndSegment,
|
||||
"VideoCombine_Adv":VideoCombine_Adv,
|
||||
"ListSplit_":ListSplit,
|
||||
"MaskListReplace_":MaskListReplace,
|
||||
"ImageListReplace_":ImageListReplace,
|
||||
"VAEEncodeForInpaint_Frames":VAEEncodeForInpaint_Frames,
|
||||
"IncrementingListNode_":IncrementingListNode
|
||||
# "GamePal":GamePal
|
||||
}
|
||||
|
||||
# 一个包含节点友好/可读的标题的字典
|
||||
NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
"RandomPrompt": "Random Prompt #Mixlab",
|
||||
"AppInfo":"App Info ♾️MixlabApp",
|
||||
"Color":"Color Input ♾️MixlabApp",
|
||||
"TextInput_":"Text Input ♾️MixlabApp",
|
||||
"FloatSlider":"Float Slider Input ♾️MixlabApp",
|
||||
"IntNumber":"Int Input ♾️MixlabApp",
|
||||
"ImagesPrompt_":"Images Input ♾️MixlabApp",
|
||||
"SaveImageAndMetadata_":"Save Image Output ♾️MixlabApp",
|
||||
"ResizeImageMixlab":"Resize Image ♾️Mixlab",
|
||||
"RandomPrompt": "Random Prompt ♾️Mixlab",
|
||||
"PromptImage":"Output Prompt and Image",
|
||||
"SplitLongMask":"Splitting a long image into sections",
|
||||
"VAELoaderConsistencyDecoder":"Consistency Decoder Loader",
|
||||
"VAEDecodeConsistencyDecoder":"Consistency Decoder Decode",
|
||||
"ScreenShare":"ScreenShare #Mixlab",
|
||||
"FloatingVideo":"FloatingVideo #Mixlab",
|
||||
"ChatGPT":"ChatGPT #Mixlab",
|
||||
"SessionHistory":"SessionHistory #Mixlab"
|
||||
"ScreenShare":"Screen Share ♾️Mixlab",
|
||||
"FloatingVideo":"FloatingVideo ♾️Mixlab",
|
||||
"ChatGPTOpenAI":"ChatGPT ♾️Mixlab",
|
||||
"ShowTextForGPT":"Show Text ♾️MixlabApp",
|
||||
"MergeLayers":"Merge Layers ♾️Mixlab",
|
||||
"SpeechSynthesis":"SpeechSynthesis ♾️Mixlab",
|
||||
"SpeechRecognition":"SpeechRecognition ♾️Mixlab",
|
||||
"3DImage":"3DImage ♾️Mixlab",
|
||||
"CompositeImages_":"Composite Images",
|
||||
"DynamicDelayProcessor":"DynamicDelayByText ♾️Mixlab",
|
||||
"LaMaInpainting":"LaMaInpainting ♾️Mixlab",
|
||||
"PromptSlide":"Prompt Slide ♾️Mixlab",
|
||||
"PromptGenerate_Mix":"Prompt Generate ♾️Mixlab",
|
||||
"ChinesePrompt_Mix":"Chinese Prompt ♾️Mixlab",
|
||||
"GamePal":"GamePal ♾️Mixlab",
|
||||
"RembgNode_Mix":"Remove Background",
|
||||
"LoraNames_":"LoraName",
|
||||
"ApplyVisualStylePrompting_":"Apply VisualStyle Prompting",
|
||||
"StyleAlignedReferenceSampler_": "StyleAligned Reference Sampler",
|
||||
"StyleAlignedSampleReferenceLatents_": "StyleAligned Sample Reference Latents",
|
||||
"StyleAlignedBatchAlign_": "StyleAligned Batch Align",
|
||||
"LoadVideoAndSegment_":"Load Video And Segment",
|
||||
"VideoCombine_Adv":"Video Combine",
|
||||
"MaskListMerge_":"MaskList to Mask",
|
||||
"ListSplit_":"Split List",
|
||||
"MaskListReplace_":"MaskList Replace",
|
||||
"ImageListReplace_":"ImageList Replace",
|
||||
"SwitchByIndex":"List Switch By Index",
|
||||
"GLIGENTextBoxApply_Advanced":"GLIGEN TextBox Apply ♾️Mixlab",
|
||||
"GridDisplayAndSave":"Grid Display And Save",
|
||||
"GridInput":"Grid Input",
|
||||
"GridOutput":"Grid Output",
|
||||
"GetImageSize_":"Get Image Size",
|
||||
"VAEEncodeForInpaint_Frames":"VAE Encode For Inpaint Frames",
|
||||
"IncrementingListNode_":"Create Incrementing Number List",
|
||||
"LoadImagesToBatch":"Load Images To Batch"
|
||||
}
|
||||
|
||||
# web ui的节点功能
|
||||
WEB_DIRECTORY = "./web"
|
||||
|
||||
print('--------------')
|
||||
print('\033[91mMixlab Nodes: \033[93mLoaded\033[0m')
|
||||
print('--------------')
|
||||
print('\033[91m ### Mixlab Nodes: \033[93mLoaded')
|
||||
|
||||
try:
|
||||
from .nodes.Lama import LaMaInpainting
|
||||
print('LaMaInpainting.available',LaMaInpainting.available)
|
||||
if LaMaInpainting.available:
|
||||
NODE_CLASS_MAPPINGS['LaMaInpainting']=LaMaInpainting
|
||||
except Exception as e:
|
||||
print('LaMaInpainting.available',False,e)
|
||||
|
||||
try:
|
||||
from .nodes.ClipInterrogator import ClipInterrogator
|
||||
print('ClipInterrogator.available',ClipInterrogator.available)
|
||||
if ClipInterrogator.available:
|
||||
NODE_CLASS_MAPPINGS['ClipInterrogator']=ClipInterrogator
|
||||
except Exception as e:
|
||||
print('ClipInterrogator.available',False,e)
|
||||
|
||||
try:
|
||||
from .nodes.TextGenerateNode import PromptGenerate,ChinesePrompt
|
||||
print('PromptGenerate.available',PromptGenerate.available)
|
||||
if PromptGenerate.available:
|
||||
NODE_CLASS_MAPPINGS['PromptGenerate_Mix']=PromptGenerate
|
||||
print('ChinesePrompt.available',ChinesePrompt.available)
|
||||
if ChinesePrompt.available:
|
||||
NODE_CLASS_MAPPINGS['ChinesePrompt_Mix']=ChinesePrompt
|
||||
except Exception as e:
|
||||
print('TextGenerateNode.available',False,e)
|
||||
|
||||
try:
|
||||
from .nodes.RembgNode import RembgNode_
|
||||
print('RembgNode_.available',RembgNode_.available)
|
||||
if RembgNode_.available:
|
||||
NODE_CLASS_MAPPINGS['RembgNode_Mix']=RembgNode_
|
||||
except Exception as e:
|
||||
print('RembgNode_.available',False,e)
|
||||
|
||||
print('\033[93m -------------- \033[0m')
|
||||
|
After Width: | Height: | Size: 101 KiB |
|
After Width: | Height: | Size: 450 KiB |
|
After Width: | Height: | Size: 2.4 MiB |
|
After Width: | Height: | Size: 1.1 MiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 240 KiB |
|
After Width: | Height: | Size: 254 KiB |
|
After Width: | Height: | Size: 73 KiB |
|
Before Width: | Height: | Size: 784 KiB |
|
After Width: | Height: | Size: 255 KiB |
|
After Width: | Height: | Size: 7.1 MiB |
|
After Width: | Height: | Size: 51 KiB |
|
After Width: | Height: | Size: 9.9 MiB |
|
After Width: | Height: | Size: 477 KiB |
@@ -0,0 +1,30 @@
|
||||
Jony Ive
|
||||
Dieter Rams
|
||||
Philippe Starck
|
||||
Karim Rashid
|
||||
Yves Béhar
|
||||
Marc Newson
|
||||
Naoto Fukasawa
|
||||
Jonathan Adler
|
||||
Patricia Urquiola
|
||||
Ross Lovegrove
|
||||
Tom Dixon
|
||||
Jasper Morrison
|
||||
Charles Eames
|
||||
Ray Eames
|
||||
Achille Castiglioni
|
||||
Ron Arad
|
||||
Konstantin Grcic
|
||||
Marcel Wanders
|
||||
Maarten Baas
|
||||
Stefan Sagmeister
|
||||
Ingo Maurer
|
||||
Hella Jongerius
|
||||
Sam Hecht
|
||||
Kim Colin
|
||||
Jaime Hayon
|
||||
Michael Anastassiades
|
||||
Nendo
|
||||
Oki Sato
|
||||
Matali Crasset
|
||||
Tokujin Yoshioka
|
||||
@@ -0,0 +1,10 @@
|
||||
Chibi Anime Style
|
||||
Gakuen Anime Style
|
||||
Gekiga Anime Style
|
||||
Jidaimono Anime Style
|
||||
Kawaii Anime Style
|
||||
Mecha Anime Style
|
||||
Realistic Anime Style
|
||||
Semi-Realistic Anime Style
|
||||
Shoji Anime Style
|
||||
Kemonomimi Anime Style
|
||||
@@ -0,0 +1,23 @@
|
||||
GoPro
|
||||
Drone
|
||||
polaroid
|
||||
black and white film
|
||||
Kodachrome
|
||||
shot on 8mm
|
||||
shot on 16mm
|
||||
shot on 35mm
|
||||
Microscopic
|
||||
Fisheye Lens
|
||||
Wide Angle
|
||||
Ultra-Wide Angle
|
||||
Panorama
|
||||
Short Exposure
|
||||
Long Exposure
|
||||
Double Exposure
|
||||
f2.8
|
||||
Depth of Field
|
||||
Soft Focus
|
||||
Deep Focus
|
||||
Shallow Focus
|
||||
Vanishing Point
|
||||
Vantage Point
|
||||
@@ -0,0 +1,30 @@
|
||||
Elegant evening gown
|
||||
Casual jeans and t-shirt
|
||||
Formal black suit
|
||||
Stylish leather jacket
|
||||
Flowy bohemian dress
|
||||
Sporty tracksuit
|
||||
Chic little black dress
|
||||
Trendy ripped jeans
|
||||
Classic white button-down shirt
|
||||
Cozy oversized sweater
|
||||
Sophisticated tailored blazer
|
||||
Quirky patterned leggings
|
||||
Striped sailor top
|
||||
Polished knee-length skirt
|
||||
Vintage-inspired floral dress
|
||||
Edgy motorcycle jacket
|
||||
Preppy polo shirt
|
||||
Boho maxi skirt
|
||||
Professional pinstripe suit
|
||||
Relaxed denim shorts
|
||||
Glamorous sequined dress
|
||||
Athletic running shoes
|
||||
Formal bow tie
|
||||
Casual baseball cap
|
||||
Stylish fedora hat
|
||||
Warm woolen scarf
|
||||
Comfortable cotton socks
|
||||
Trendy ankle boots
|
||||
Cute summer sandals
|
||||
Cozy pajama set
|
||||
@@ -0,0 +1,30 @@
|
||||
Happy
|
||||
Sad
|
||||
Angry
|
||||
Surprised
|
||||
Excited
|
||||
Worried
|
||||
Confused
|
||||
Disgusted
|
||||
Amused
|
||||
Bored
|
||||
Curious
|
||||
Embarrassed
|
||||
Frustrated
|
||||
Nervous
|
||||
Pleased
|
||||
Relieved
|
||||
Shy
|
||||
Tired
|
||||
Serious
|
||||
Silly
|
||||
Proud
|
||||
Grumpy
|
||||
Smug
|
||||
Sarcastic
|
||||
Flirty
|
||||
Skeptical
|
||||
Shocked
|
||||
Blissful
|
||||
Envious
|
||||
Mischievous
|
||||
@@ -0,0 +1,10 @@
|
||||
[
|
||||
{
|
||||
"keyword":"Dog",
|
||||
"imgurl":"http://127.0.0.1:8188/view?filename=1709966910233.png&type=input&subfolder=&rand=0.2734446552394221"
|
||||
},
|
||||
{
|
||||
"keyword":"x",
|
||||
"imgurl":"http://127.0.0.1:8188/view?filename=image%20(33).png&type=input&subfolder=pasted&rand=0.6984318219852814"
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,16 @@
|
||||
Mood Lighting
|
||||
Moody Lighting
|
||||
Studio Lighting
|
||||
Cove Lighting
|
||||
Soft Lighting
|
||||
Hard Lighting
|
||||
Volumetric Lighting
|
||||
Low-Key Lighting
|
||||
High-Key Lighting
|
||||
Epic Light
|
||||
Rembrandt Lighting
|
||||
Contre-Jour
|
||||
Veiling Flare
|
||||
Crepuscular Rays
|
||||
Rays of Shimmering Light
|
||||
Godrays
|
||||
@@ -0,0 +1 @@
|
||||
{}
|
||||
@@ -0,0 +1,132 @@
|
||||
Aaron Siskind
|
||||
Alessio Albi
|
||||
Alfred Eisenstaedt
|
||||
Alfred Stieglitz
|
||||
Alyssa Monks
|
||||
André Kertész
|
||||
Andreas Gursky
|
||||
Andrew Wyeth
|
||||
Anne Geddes
|
||||
Annie Leibovitz
|
||||
Ansel Adams
|
||||
Arnold Newman
|
||||
August Sander
|
||||
Balthus
|
||||
Berenice Abbott
|
||||
Bill Brandt
|
||||
Bill Henson
|
||||
Brassaï (Gyula Halász)
|
||||
Brooke Shaden
|
||||
Bruce Davidson
|
||||
Bruce Weber
|
||||
Bunny Yeager
|
||||
Carleton Watkins
|
||||
Carrie Mae Weems
|
||||
Chuck Close
|
||||
Cindy Sherman
|
||||
Clarence H. White
|
||||
Claude Cahun
|
||||
Danny Lyon
|
||||
David LaChapelle
|
||||
Dawoud Bey
|
||||
Diane Arbus
|
||||
Don McCullin
|
||||
Dora Maar
|
||||
Dorothea Lange
|
||||
Duane Michals
|
||||
Eadweard Muybridge
|
||||
Edward Burtynsky
|
||||
Edward Curtis
|
||||
Edward Ruscha
|
||||
Edward Steichen
|
||||
Edward Weston
|
||||
Elliott Erwitt
|
||||
Ernst Haas
|
||||
Eugene Atget
|
||||
Fan Ho
|
||||
Francesca Woodman
|
||||
Frans Lanting
|
||||
Garry Winogrand
|
||||
Georges Melies
|
||||
Gerda Taro
|
||||
Gertrude Käsebier
|
||||
Gordon Parks
|
||||
Graciela Iturbide
|
||||
Gregory Crewdson
|
||||
Harold Edgerton
|
||||
Helen Levitt
|
||||
Helmut Newton
|
||||
Hendrik Kerstens
|
||||
Henri Cartier-Bresson
|
||||
Hugh Kretschmer
|
||||
Irving Penn
|
||||
Jacques Henri Lartigue
|
||||
James Nachtwey
|
||||
James Van Der Zee
|
||||
Jay Maisel
|
||||
Jerry Uelsmann
|
||||
Joel Peter Witkin
|
||||
Joel Sartore
|
||||
John Frederick William Herschel
|
||||
Josef Sudek
|
||||
Julia Margaret Cameron
|
||||
Karl Blossfeldt
|
||||
Larry Burrows
|
||||
László Moholy-Nagy (photography)
|
||||
Lee Jeffries
|
||||
Lewis Hine
|
||||
Lorna Simpson
|
||||
Lynsey Addario
|
||||
Margaret Bourke-White
|
||||
Mario Testino
|
||||
Martin Parr
|
||||
Martin Schoeller
|
||||
Mary Ellen Mark
|
||||
Mathew B. Brady
|
||||
Méret Oppenheim
|
||||
Meryl McMaster
|
||||
Mick Rock
|
||||
Miles Aldridge
|
||||
Minor Martin White
|
||||
Nan Goldin
|
||||
Nathan Wirth
|
||||
Olive Cotton
|
||||
Olivier Rousteing
|
||||
Patrick Demarchelier
|
||||
Paul Nicklen
|
||||
Paul Outerbridge
|
||||
Paul Strand
|
||||
Pete Souza
|
||||
Peter Dombrovskis
|
||||
Peter Henry Emerson
|
||||
Peter Lik
|
||||
Peter Lindbergh
|
||||
Philip-Lorca diCorcia
|
||||
Philippe Halsman
|
||||
Ralph Gibson
|
||||
Richard Avedon
|
||||
Robert Adams
|
||||
Robert Bechtle
|
||||
Robert Capa
|
||||
Robert Frank
|
||||
Robert Mapplethorpe
|
||||
Roger Fenton
|
||||
Ruth Bernhard
|
||||
Sally Mann
|
||||
Sebastião Salgado
|
||||
Shirin Neshat
|
||||
Stefan Gesell
|
||||
Steven Meisel
|
||||
Susan Meiselas
|
||||
Vivian Maier
|
||||
Vivian Maier
|
||||
Viviane Sassen
|
||||
Walker Evans
|
||||
Wes Anderson
|
||||
William Eggleston
|
||||
William Eugene Smith
|
||||
William Henry Fox Talbot
|
||||
Yinka Shonibare
|
||||
Yousuf Karsh
|
||||
Man Ray
|
||||
Robert Mapplethorpe
|
||||
@@ -0,0 +1,101 @@
|
||||
Doctor
|
||||
Teacher
|
||||
Engineer
|
||||
Lawyer
|
||||
Accountant
|
||||
Nurse
|
||||
Architect
|
||||
Chef
|
||||
Pilot
|
||||
Scientist
|
||||
Artist
|
||||
Writer
|
||||
Musician
|
||||
Actor
|
||||
Photographer
|
||||
Police officer
|
||||
Firefighter
|
||||
Dentist
|
||||
Pharmacist
|
||||
Veterinarian
|
||||
Electrician
|
||||
Plumber
|
||||
Carpenter
|
||||
Mechanic
|
||||
Farmer
|
||||
Astronaut
|
||||
Athlete
|
||||
Journalist
|
||||
Politician
|
||||
Economist
|
||||
Psychologist
|
||||
Social worker
|
||||
Librarian
|
||||
Translator
|
||||
Salesperson
|
||||
Entrepreneur
|
||||
Financial advisor
|
||||
Graphic designer
|
||||
Web developer
|
||||
Marketing manager
|
||||
Human resources manager
|
||||
Project manager
|
||||
Event planner
|
||||
Fashion designer
|
||||
Interior decorator
|
||||
Real estate agent
|
||||
Archaeologist
|
||||
Biologist
|
||||
Chemist
|
||||
Geologist
|
||||
Physicist
|
||||
Mathematician
|
||||
Historian
|
||||
Geographer
|
||||
Economist
|
||||
Sociologist
|
||||
Anthropologist
|
||||
Archaeologist
|
||||
Linguist
|
||||
Philosopher
|
||||
Economist
|
||||
Sociologist
|
||||
Anthropologist
|
||||
Archaeologist
|
||||
Linguist
|
||||
Philosopher
|
||||
Geographer
|
||||
Historian
|
||||
Economist
|
||||
Sociologist
|
||||
Anthropologist
|
||||
Archaeologist
|
||||
Linguist
|
||||
Philosopher
|
||||
Geographer
|
||||
Historian
|
||||
Economist
|
||||
Sociologist
|
||||
Anthropologist
|
||||
Archaeologist
|
||||
Linguist
|
||||
Philosopher
|
||||
Geographer
|
||||
Historian
|
||||
Economist
|
||||
Sociologist
|
||||
Anthropologist
|
||||
Archaeologist
|
||||
Linguist
|
||||
Philosopher
|
||||
Geographer
|
||||
Historian
|
||||
Economist
|
||||
Sociologist
|
||||
Anthropologist
|
||||
Archaeologist
|
||||
Linguist
|
||||
Philosopher
|
||||
Geographer
|
||||
Historian
|
||||
#MixCopilot
|
||||
@@ -0,0 +1,58 @@
|
||||
Residential space
|
||||
Apartment building
|
||||
Villa
|
||||
Bungalow
|
||||
Condominium
|
||||
Commercial space
|
||||
Shopping mall
|
||||
Supermarket
|
||||
Restaurant
|
||||
Store
|
||||
Market
|
||||
Office space
|
||||
Office building
|
||||
Office
|
||||
Meeting room
|
||||
Co-working space
|
||||
Educational space
|
||||
School
|
||||
University
|
||||
Training institution
|
||||
Library
|
||||
Laboratory
|
||||
Medical space
|
||||
Hospital
|
||||
Clinic
|
||||
Pharmacy
|
||||
Nursing home
|
||||
Rehabilitation center
|
||||
Cultural space
|
||||
Museum
|
||||
Library
|
||||
Theater
|
||||
Concert hall
|
||||
Gallery
|
||||
Sports space
|
||||
Sports stadium
|
||||
Gym
|
||||
Swimming pool
|
||||
Basketball court
|
||||
Football field
|
||||
Transportation space
|
||||
Airport
|
||||
Train station
|
||||
Subway station
|
||||
Bus stop
|
||||
Parking lot
|
||||
Public space
|
||||
Park
|
||||
Square
|
||||
Street
|
||||
Pedestrian street
|
||||
Community center
|
||||
Industrial space
|
||||
Factory
|
||||
Warehouse
|
||||
Production workshop
|
||||
Mine
|
||||
Power plant
|
||||
@@ -0,0 +1,135 @@
|
||||
Vintage
|
||||
Grain
|
||||
Sepia
|
||||
High Key
|
||||
Low Key
|
||||
High Dynamic Range
|
||||
Cross Process
|
||||
Radial Blur
|
||||
Infrared
|
||||
Lomo
|
||||
Photocopy
|
||||
Pencil Sketch
|
||||
Pop Art
|
||||
Orton
|
||||
Mosaic
|
||||
Selective Black and White
|
||||
Torn Paper
|
||||
Tilt-Shift
|
||||
Double Exposure
|
||||
Polaroid
|
||||
Liquid Ink
|
||||
Color Splash
|
||||
Sketch
|
||||
Water Drops
|
||||
Polarizer
|
||||
Chinese Painting
|
||||
Water Droplets
|
||||
Polarization
|
||||
Color Inversion
|
||||
Fish-eye
|
||||
Soft Focus
|
||||
Solarization
|
||||
Posterize
|
||||
Comic Book
|
||||
Duotone
|
||||
Gradient Map
|
||||
Edge Detection
|
||||
Oil Painting
|
||||
Reflection
|
||||
Mirror
|
||||
ASCII Art
|
||||
Glitch
|
||||
Time-Lapse
|
||||
Day to Night
|
||||
Surreal
|
||||
Black and White
|
||||
Sepia Tone
|
||||
Vintage Film
|
||||
Grainy Texture
|
||||
High Key Lighting
|
||||
Low Key Lighting
|
||||
Cross Processed Film
|
||||
Infrared Photography
|
||||
Photocopy
|
||||
Pencil Drawing
|
||||
Pop Art Filter
|
||||
Mosaic Filter
|
||||
Selective Desaturation
|
||||
Torn Paper
|
||||
Tilt-Shift Photography
|
||||
Double Exposure
|
||||
Polaroid Style Frame
|
||||
Water Drops Texture
|
||||
Polarizer
|
||||
Chinese Painting
|
||||
Water Droplets Texture
|
||||
Polarization
|
||||
Color Inversion
|
||||
Fish-eye Lens
|
||||
Soft Focus
|
||||
Solarize Filter
|
||||
Edge Detection
|
||||
Oil Painting
|
||||
Reflection
|
||||
Mirror Image
|
||||
Time-Lapse Photography
|
||||
Day to Night Transition
|
||||
Surreal Art Style
|
||||
Abstract Expressionism
|
||||
Acrylic Painting
|
||||
Anime
|
||||
Art Deco
|
||||
Biomorphic Abstraction
|
||||
Black and White Photograph
|
||||
Cartoon
|
||||
Charcoal Sketch
|
||||
Chibi Anime
|
||||
Chinese Painting
|
||||
Classicist Painting
|
||||
Collage
|
||||
Concept Art
|
||||
Cyberpunk
|
||||
Dada Art
|
||||
Digital Art
|
||||
Fantasy Art
|
||||
Fashion Art
|
||||
Fashion Sketch
|
||||
Fish-Eye lens Photograph
|
||||
Goth Art
|
||||
Graffiti
|
||||
Harlem Renaissance
|
||||
High Key Photograph
|
||||
Hyperrealist Pencil Sketch
|
||||
Impressionist Painting
|
||||
Josei Anime
|
||||
Long Exposure Photograph
|
||||
Low Key Photograph
|
||||
Macro Photograph
|
||||
Manga
|
||||
Metal Sculpture
|
||||
Mid Century Modern Illustration
|
||||
Mixed Media
|
||||
Modern Art
|
||||
Moe Anime
|
||||
Nihonga
|
||||
Origami
|
||||
Paper Mache
|
||||
Pen and Ink
|
||||
Pencil Sketch
|
||||
Photograph
|
||||
Photorealism
|
||||
Pinup Art
|
||||
Romanticist Painting
|
||||
Sci-Fi Art
|
||||
Semi Realistic Fantasy Art
|
||||
Semi Realistic Cyberpunk Art
|
||||
Shallow Depth of Field Photograph
|
||||
Steam Punk Art
|
||||
Stone Sculpture
|
||||
Superhero Comic
|
||||
Surrealist Art
|
||||
Tempura Painting
|
||||
Underground Comic
|
||||
Watercolor Painting
|
||||
Zulu Urban Art
|
||||
@@ -0,0 +1,100 @@
|
||||
|
||||
|
||||
|
||||
|
||||
class SpeechRecognition:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {"required": {
|
||||
"upload":("AUDIOINPUTMIX",), },
|
||||
"optional":{
|
||||
"start_by":("INT", {
|
||||
"default": 0,
|
||||
"min": 0, #Minimum value
|
||||
"max": 2048, #Maximum value
|
||||
"step": 1, #Slider's step
|
||||
"display": "number" # Cosmetic only: display as "number" or "slider"
|
||||
}),
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("STRING",)
|
||||
RETURN_NAMES = ("prompt",)
|
||||
|
||||
FUNCTION = "run"
|
||||
|
||||
CATEGORY = "♾️Mixlab/Audio"
|
||||
|
||||
INPUT_IS_LIST = False
|
||||
OUTPUT_IS_LIST = (False,)
|
||||
|
||||
def run(self,upload,start_by):
|
||||
return {"ui": {"start_by": [start_by]}, "result": (upload,)}
|
||||
|
||||
|
||||
class SpeechSynthesis:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {
|
||||
"required": {
|
||||
"text": ("STRING", {"forceInput": True}),
|
||||
}
|
||||
}
|
||||
|
||||
INPUT_IS_LIST = True
|
||||
RETURN_TYPES = ("STRING",)
|
||||
FUNCTION = "run"
|
||||
OUTPUT_NODE = True
|
||||
OUTPUT_IS_LIST = (True,)
|
||||
|
||||
CATEGORY = "♾️Mixlab/Audio"
|
||||
|
||||
def run(self, text):
|
||||
# print(session_history)
|
||||
return {"ui": {"text": text}, "result": (text,)}
|
||||
|
||||
|
||||
#
|
||||
class GamePal:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {
|
||||
"required": {
|
||||
"input_text": ("STRING",{"multiline": True,"default": ""}),
|
||||
},
|
||||
"optional": {
|
||||
|
||||
"input_num": ("INT",{
|
||||
"default":100,
|
||||
"min": -1, #Minimum value
|
||||
"max": 0xffffffffffffffff, #Maximum value
|
||||
"step": 1, #Slider's step
|
||||
"display": "slider" # Cosmetic only: display as "number" or "slider"
|
||||
}),
|
||||
"python_code": ("STRING",{"multiline": True,"default": "result= 1 if 'Mixlab' in input_text else 0"}),
|
||||
}
|
||||
}
|
||||
|
||||
INPUT_IS_LIST = False
|
||||
RETURN_TYPES = ("INT",)
|
||||
FUNCTION = "run"
|
||||
OUTPUT_NODE = True
|
||||
OUTPUT_IS_LIST = (False,)
|
||||
|
||||
CATEGORY = "♾️Mixlab/Audio"
|
||||
|
||||
def run(self, input_text,input_num,python_code):
|
||||
exec(python_code)
|
||||
res=None
|
||||
try:
|
||||
# 可能会引发异常的代码
|
||||
res=result
|
||||
except:
|
||||
# 处理异常的代码
|
||||
print('')
|
||||
|
||||
print(res)
|
||||
|
||||
# print(session_history)
|
||||
return {"ui": {"text": [input_text],"num":[input_num]}, "result": (res,)}
|
||||
@@ -1,7 +1,28 @@
|
||||
import openai
|
||||
import time
|
||||
import urllib.error
|
||||
import re,json
|
||||
import re,json,os,string,random
|
||||
import folder_paths
|
||||
import hashlib
|
||||
import codecs
|
||||
from zhipuai import ZhipuAI
|
||||
|
||||
def get_unique_hash(string):
|
||||
hash_object = hashlib.sha1(string.encode())
|
||||
unique_hash = hash_object.hexdigest()
|
||||
return unique_hash
|
||||
|
||||
def generate_random_string(length):
|
||||
letters = string.ascii_letters + string.digits
|
||||
return ''.join(random.choice(letters) for _ in range(length))
|
||||
|
||||
class AnyType(str):
|
||||
"""A special class that is always equal in not equal comparisons. Credit to pythongosssss"""
|
||||
|
||||
def __ne__(self, __value: object) -> bool:
|
||||
return False
|
||||
|
||||
any_type = AnyType("*")
|
||||
|
||||
# 判断是否是azure服务
|
||||
def is_azure_url(url):
|
||||
@@ -27,6 +48,11 @@ def openai_client(key,url):
|
||||
base_url=url
|
||||
)
|
||||
return client
|
||||
def ZhipuAI_client(key):
|
||||
client = ZhipuAI(
|
||||
api_key=key, # 填写您的 APIKey
|
||||
)
|
||||
return client
|
||||
|
||||
|
||||
|
||||
@@ -46,7 +72,7 @@ def chat(client, model_name,messages ):
|
||||
except (urllib.error.HTTPError, openai.OpenAIError) as ex:
|
||||
if try_count >= 3:
|
||||
raise ex
|
||||
time.sleep(5)
|
||||
time.sleep(3)
|
||||
continue
|
||||
|
||||
finish_reason = response.choices[0].finish_reason
|
||||
@@ -66,33 +92,46 @@ class ChatGPTNode:
|
||||
def __init__(self):
|
||||
# self.__client = OpenAI()
|
||||
self.session_history = [] # 用于存储会话历史的列表
|
||||
self.seed=0
|
||||
# self.seed=0
|
||||
self.system_content="You are ChatGPT, a large language model trained by OpenAI. Answer as concisely as possible."
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"api_key":("KEY", {"default": "", "multiline": True}),
|
||||
"api_url":("URL", {"default": "", "multiline": True}),
|
||||
"prompt": ("STRING", {"default": "", "multiline": True}),
|
||||
"api_key":("KEY", {"default": "", "multiline": True,"dynamicPrompts": False}),
|
||||
"api_url":("URL", {"default": "", "multiline": True,"dynamicPrompts": False}),
|
||||
"prompt": ("STRING", {"multiline": True,"dynamicPrompts": False}),
|
||||
"system_content": ("STRING",
|
||||
{
|
||||
"default": "You are ChatGPT, a large language model trained by OpenAI. Answer as concisely as possible.",
|
||||
"multiline": True
|
||||
"multiline": True,"dynamicPrompts": False
|
||||
}),
|
||||
"model": (["gpt-3.5-turbo", "gpt-3.5-turbo-16k-0613", "gpt-4-0613","gpt-4-1106-preview"],
|
||||
"model": ([
|
||||
"gpt-3.5-turbo",
|
||||
"gpt-3.5-turbo-0125",
|
||||
"gpt-35-turbo",
|
||||
"gpt-3.5-turbo-16k",
|
||||
"gpt-3.5-turbo-16k-0613",
|
||||
"gpt-4-0613",
|
||||
"gpt-4-1106-preview",
|
||||
"glm-4"],
|
||||
{"default": "gpt-3.5-turbo"}),
|
||||
"seed": ("INT", {"default": 0, "min": 0, "max": 10000, "step": 1}),
|
||||
"seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff, "step": 1}),
|
||||
"context_size":("INT", {"default": 1, "min": 0, "max":30, "step": 1}),
|
||||
},
|
||||
"hidden": {
|
||||
"unique_id": "UNIQUE_ID",
|
||||
"extra_pnginfo": "EXTRA_PNGINFO",
|
||||
},
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("STRING","STRING",)
|
||||
RETURN_NAMES = ("text","session_history",)
|
||||
RETURN_TYPES = ("STRING","STRING","STRING",)
|
||||
RETURN_NAMES = ("text","messages","session_history",)
|
||||
FUNCTION = "generate_contextual_text"
|
||||
CATEGORY = "Mixlab/GPT"
|
||||
CATEGORY = "♾️Mixlab/GPT"
|
||||
INPUT_IS_LIST = False
|
||||
OUTPUT_IS_LIST = (False,False,)
|
||||
OUTPUT_IS_LIST = (False,False,False,)
|
||||
|
||||
|
||||
def generate_contextual_text(self,
|
||||
@@ -101,18 +140,13 @@ class ChatGPTNode:
|
||||
prompt,
|
||||
system_content,
|
||||
model,
|
||||
seed):
|
||||
print(api_key,
|
||||
api_url,
|
||||
prompt,
|
||||
system_content,
|
||||
model,
|
||||
seed)
|
||||
seed,context_size,unique_id = None, extra_pnginfo=None):
|
||||
# print(api_key!='',api_url,prompt,system_content,model,seed)
|
||||
# 可以选择保留会话历史以维持上下文记忆
|
||||
# 或者在此处清除会话历史 self.session_history.clear()
|
||||
if seed!=self.seed:
|
||||
self.seed=seed
|
||||
self.session_history=[]
|
||||
# if seed!=self.seed:
|
||||
# self.seed=seed
|
||||
# self.session_history=[]
|
||||
|
||||
# 把系统信息和初始信息添加到会话历史中
|
||||
if system_content:
|
||||
@@ -124,27 +158,59 @@ class ChatGPTNode:
|
||||
if is_azure_url(api_url):
|
||||
client=azure_client(api_key,api_url)
|
||||
else:
|
||||
client=openai_client(api_key,api_url)
|
||||
print('openai url')
|
||||
# 根据用户选择的模型,设置相应的接口和模型名称
|
||||
if model == "glm-4" :
|
||||
client = ZhipuAI_client(api_key) # 使用 Zhipuai 的接口
|
||||
print('using Zhipuai interface')
|
||||
else :
|
||||
client = openai_client(api_key,api_url) # 使用 ChatGPT 的接口
|
||||
print('using ChatGPT interface')
|
||||
|
||||
# 把用户的提示添加到会话历史中
|
||||
# 调用API时传递整个会话历史
|
||||
messages=[{"role": "system", "content": self.system_content}]+self.session_history+[{"role": "user", "content": prompt}]
|
||||
|
||||
def crop_list_tail(lst, size):
|
||||
if size >= len(lst):
|
||||
return lst
|
||||
elif size==0:
|
||||
return []
|
||||
else:
|
||||
return lst[-size:]
|
||||
|
||||
session_history=crop_list_tail(self.session_history,context_size)
|
||||
|
||||
messages=[{"role": "system", "content": self.system_content}]+session_history+[{"role": "user", "content": prompt}]
|
||||
response_content = chat(client,model,messages)
|
||||
|
||||
self.session_history=self.session_history+[{"role": "user", "content": prompt}]+[{'role':'assistant',"content":response_content}]
|
||||
|
||||
|
||||
# if unique_id and extra_pnginfo and "workflow" in extra_pnginfo[0]:
|
||||
# workflow = extra_pnginfo[0]["workflow"]
|
||||
# node = next((x for x in workflow["nodes"] if str(x["id"]) == unique_id[0]), None)
|
||||
# if node:
|
||||
# node["widgets_values"] = ["",
|
||||
# api_url,
|
||||
# prompt,
|
||||
# system_content,
|
||||
# model,
|
||||
# seed,
|
||||
# context_size]
|
||||
|
||||
return (response_content,json.dumps(self.session_history, indent=4),)
|
||||
return (response_content,json.dumps(messages, indent=4),json.dumps(self.session_history, indent=4),)
|
||||
|
||||
|
||||
|
||||
class SessionHistory:
|
||||
class ShowTextForGPT:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {
|
||||
"required": {
|
||||
"session_history": ("STRING", {"forceInput": True}),
|
||||
}
|
||||
"text": ("STRING", {"forceInput": True,"dynamicPrompts": False}),
|
||||
},
|
||||
"optional":{
|
||||
"output_dir": ("STRING",{"forceInput": True,"default": "","multiline": True,"dynamicPrompts": False}),
|
||||
}
|
||||
}
|
||||
|
||||
INPUT_IS_LIST = True
|
||||
@@ -153,8 +219,145 @@ class SessionHistory:
|
||||
OUTPUT_NODE = True
|
||||
OUTPUT_IS_LIST = (True,)
|
||||
|
||||
CATEGORY = "Mixlab/GPT"
|
||||
CATEGORY = "♾️Mixlab/Text"
|
||||
|
||||
def run(self, session_history):
|
||||
# print(session_history)
|
||||
return {"ui": {"text": session_history}, "result": (session_history,)}
|
||||
def run(self, text,output_dir=[""]):
|
||||
|
||||
# 类型纠正
|
||||
texts=[]
|
||||
for t in text:
|
||||
if not isinstance(t, str):
|
||||
t = str(t)
|
||||
texts.append(t)
|
||||
|
||||
text=texts
|
||||
|
||||
if len(output_dir)==1 and (output_dir[0]=='' or os.path.dirname(output_dir[0])==''):
|
||||
t='\n'.join(text)
|
||||
output_dir=[
|
||||
os.path.join(folder_paths.get_temp_directory(),
|
||||
get_unique_hash(t)+'.txt'
|
||||
)
|
||||
]
|
||||
elif len(output_dir)==1:
|
||||
base=os.path.basename(output_dir[0])
|
||||
t='\n'.join(text)
|
||||
if base=='' or os.path.splitext(base)[1]=='':
|
||||
base=get_unique_hash(t)+'.txt'
|
||||
output_dir=[
|
||||
os.path.join(output_dir[0],
|
||||
base
|
||||
)
|
||||
]
|
||||
# elif len(output_dir)>1:
|
||||
|
||||
|
||||
|
||||
if len(output_dir)==1 and len(text)>1:
|
||||
output_dir=[output_dir[0] for _ in range(len(text))]
|
||||
|
||||
for i in range(len(text)):
|
||||
|
||||
o_fp=output_dir[i]
|
||||
dirp=os.path.dirname(o_fp)
|
||||
if dirp=='':
|
||||
dirp=folder_paths.get_temp_directory()
|
||||
o_fp=os.path.join(folder_paths.get_temp_directory(),o_fp
|
||||
)
|
||||
|
||||
if not os.path.exists(dirp):
|
||||
os.mkdir(dirp)
|
||||
|
||||
if not os.path.splitext(o_fp)[1].lower()=='.txt':
|
||||
o_fp=o_fp+'.txt'
|
||||
|
||||
t=text[i]
|
||||
with open(o_fp, 'w') as file:
|
||||
file.write(t)
|
||||
|
||||
# print(text)
|
||||
return {"ui": {"text": text}, "result": (text,)}
|
||||
|
||||
|
||||
|
||||
class CharacterInText:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {
|
||||
"required": {
|
||||
"text": ("STRING", {"multiline": True,"dynamicPrompts": False}),
|
||||
"character": ("STRING", {"multiline": True,"dynamicPrompts": False}),
|
||||
"start_index": ("INT", {
|
||||
"default": 1,
|
||||
"min": 0, #Minimum value
|
||||
"max": 1024, #Maximum value
|
||||
"step": 1, #Slider's step
|
||||
"display": "number" # Cosmetic only: display as "number" or "slider"
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
INPUT_IS_LIST = False
|
||||
RETURN_TYPES = ("INT",)
|
||||
FUNCTION = "run"
|
||||
# OUTPUT_NODE = True
|
||||
OUTPUT_IS_LIST = (False,)
|
||||
|
||||
CATEGORY = "♾️Mixlab/Text"
|
||||
|
||||
def run(self, text,character,start_index):
|
||||
# print(text,character,start_index)
|
||||
b=1 if character.lower() in text.lower() else 0
|
||||
|
||||
return (b+start_index,)
|
||||
|
||||
class TextSplitByDelimiter:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {
|
||||
"required": {
|
||||
"text": ("STRING", {"multiline": True,"dynamicPrompts": False}),
|
||||
"delimiter":("STRING", {"multiline": False,"default":",","dynamicPrompts": False}),
|
||||
"start_index": ("INT", {
|
||||
"default": 0,
|
||||
"min": 0, #Minimum value
|
||||
"max": 1000, #Maximum value
|
||||
"step": 1, #Slider's step
|
||||
"display": "number" # Cosmetic only: display as "number" or "slider"
|
||||
}),
|
||||
"skip_every": ("INT", {
|
||||
"default": 0,
|
||||
"min": 0, #Minimum value
|
||||
"max": 10, #Maximum value
|
||||
"step": 1, #Slider's step
|
||||
"display": "number" # Cosmetic only: display as "number" or "slider"
|
||||
}),
|
||||
"max_count": ("INT", {
|
||||
"default": 10,
|
||||
"min": 1, #Minimum value
|
||||
"max": 1000, #Maximum value
|
||||
"step": 1, #Slider's step
|
||||
"display": "number" # Cosmetic only: display as "number" or "slider"
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
INPUT_IS_LIST = False
|
||||
RETURN_TYPES = ("STRING",)
|
||||
FUNCTION = "run"
|
||||
# OUTPUT_NODE = True
|
||||
OUTPUT_IS_LIST = (True,)
|
||||
|
||||
CATEGORY = "♾️Mixlab/Text"
|
||||
|
||||
def run(self, text,delimiter,start_index,skip_every,max_count):
|
||||
|
||||
if delimiter=="":
|
||||
arr=[text.strip()]
|
||||
else:
|
||||
delimiter=codecs.decode(delimiter, 'unicode_escape')
|
||||
arr= [line for line in text.split(delimiter) if line.strip()]
|
||||
|
||||
arr= arr[start_index:start_index + max_count * (skip_every+1):(skip_every+1)]
|
||||
|
||||
return (arr,)
|
||||
|
||||
@@ -0,0 +1,277 @@
|
||||
import os,sys
|
||||
import folder_paths
|
||||
|
||||
from PIL import Image
|
||||
import importlib.util
|
||||
|
||||
import comfy.utils
|
||||
import numpy as np
|
||||
import json
|
||||
import torch
|
||||
import random
|
||||
|
||||
|
||||
# from clip_interrogator import Config, Interrogator
|
||||
|
||||
|
||||
global _available
|
||||
_available=False
|
||||
|
||||
def is_installed(package):
|
||||
try:
|
||||
spec = importlib.util.find_spec(package)
|
||||
except ModuleNotFoundError:
|
||||
return False
|
||||
return spec is not None
|
||||
|
||||
|
||||
try:
|
||||
if is_installed('clip_interrogator')==False:
|
||||
import subprocess
|
||||
|
||||
# 安装
|
||||
print('#pip install clip-interrogator==0.6.0')
|
||||
|
||||
result = subprocess.run([sys.executable, '-s', '-m', 'pip', 'install', 'clip-interrogator==0.6.0'], capture_output=True, text=True)
|
||||
|
||||
#检查命令执行结果
|
||||
if result.returncode == 0:
|
||||
print("#install success")
|
||||
from clip_interrogator import Config, Interrogator
|
||||
_available=True
|
||||
else:
|
||||
print("#install error")
|
||||
|
||||
else:
|
||||
from clip_interrogator import Config, Interrogator
|
||||
_available=True
|
||||
|
||||
except:
|
||||
_available=False
|
||||
|
||||
try:
|
||||
from transformers import AutoProcessor, BlipForConditionalGeneration
|
||||
except:
|
||||
_available=False
|
||||
print('pls check transformers.__version__>=4.36.0:: AutoProcessor, BlipForConditionalGeneration')
|
||||
|
||||
|
||||
|
||||
def load_caption_model(model_path,config,t='blip-base'):
|
||||
dtype=torch.float16 if config.device == 'cuda' else torch.float32
|
||||
caption_model = BlipForConditionalGeneration.from_pretrained(model_path, torch_dtype=dtype)
|
||||
|
||||
caption_processor = AutoProcessor.from_pretrained(model_path)
|
||||
|
||||
caption_model.eval()
|
||||
if not config.caption_offload:
|
||||
caption_model = caption_model.to(config.device)
|
||||
|
||||
return (caption_model,caption_processor)
|
||||
|
||||
|
||||
|
||||
caption_model_path=os.path.join(folder_paths.models_dir, "clip_interrogator/Salesforce/blip-image-captioning-base")
|
||||
if not os.path.exists(caption_model_path):
|
||||
print(f"## clip_interrogator_model not found: {caption_model_path}, pls download from https://huggingface.co/Salesforce/blip-image-captioning-base")
|
||||
caption_model_path='Salesforce/blip-image-captioning-base'
|
||||
|
||||
cache_path=os.path.join(folder_paths.models_dir, "clip_interrogator")
|
||||
|
||||
|
||||
# Tensor to PIL
|
||||
def tensor2pil(image):
|
||||
return Image.fromarray(np.clip(255. * image.cpu().numpy().squeeze(), 0, 255).astype(np.uint8))
|
||||
|
||||
# Convert PIL to Tensor
|
||||
def pil2tensor(image):
|
||||
return torch.from_numpy(np.array(image).astype(np.float32) / 255.0).unsqueeze(0)
|
||||
|
||||
|
||||
def image_analysis_fn(ci,image):
|
||||
image = image.convert('RGB')
|
||||
image_features = ci.image_to_features(image)
|
||||
|
||||
top_mediums = ci.mediums.rank(image_features, 5)
|
||||
top_artists = ci.artists.rank(image_features, 5)
|
||||
top_movements = ci.movements.rank(image_features, 5)
|
||||
top_trendings = ci.trendings.rank(image_features, 5)
|
||||
top_flavors = ci.flavors.rank(image_features, 5)
|
||||
|
||||
medium_ranks = {medium: sim for medium, sim in zip(top_mediums, ci.similarities(image_features, top_mediums))}
|
||||
artist_ranks = {artist: sim for artist, sim in zip(top_artists, ci.similarities(image_features, top_artists))}
|
||||
movement_ranks = {movement: sim for movement, sim in zip(top_movements, ci.similarities(image_features, top_movements))}
|
||||
trending_ranks = {trending: sim for trending, sim in zip(top_trendings, ci.similarities(image_features, top_trendings))}
|
||||
flavor_ranks = {flavor: sim for flavor, sim in zip(top_flavors, ci.similarities(image_features, top_flavors))}
|
||||
|
||||
return {
|
||||
"medium_ranks":medium_ranks,
|
||||
"artist_ranks":artist_ranks,
|
||||
"movement_ranks":movement_ranks,
|
||||
"trending_ranks":trending_ranks,
|
||||
"flavor_ranks":flavor_ranks
|
||||
}
|
||||
|
||||
|
||||
def generate_sentences(data):
|
||||
sentences = []
|
||||
|
||||
# Get the length of data
|
||||
data_length = len(data)
|
||||
|
||||
# Use a recursive function to handle variable-length data
|
||||
def generate_recursive(index, current_sentence, current_score):
|
||||
# Check if recursion is complete
|
||||
if index == data_length:
|
||||
sentences.append({"sentence": current_sentence, "score": current_score})
|
||||
return
|
||||
|
||||
# Get the current level data
|
||||
current_data = data[index]
|
||||
|
||||
# Iterate through the current level data
|
||||
for phrase in current_data:
|
||||
sentence = current_sentence + ("," if current_sentence.strip() else "") + phrase
|
||||
score = current_score + current_data[phrase]
|
||||
generate_recursive(index + 1, sentence, score)
|
||||
|
||||
# Start recursive generation of sentences
|
||||
generate_recursive(0, "", 0)
|
||||
|
||||
# Sort the generated sentences by score in descending order
|
||||
sentences.sort(key=lambda x: x["score"], reverse=True)
|
||||
|
||||
def get_random_elements(elements, num):
|
||||
return random.sample(elements, num)
|
||||
|
||||
ps = get_random_elements(sentences, 5)
|
||||
ps = [s["sentence"] for s in sorted(ps, key=lambda x: x["score"], reverse=True)]
|
||||
|
||||
return ps
|
||||
|
||||
|
||||
|
||||
|
||||
def image_to_prompt(ci,image, mode):
|
||||
ci.config.chunk_size = 2048 if ci.config.clip_model_name == "ViT-L-14/openai" else 1024
|
||||
ci.config.flavor_intermediate_count = 2048 if ci.config.clip_model_name == "ViT-L-14/openai" else 1024
|
||||
image = image.convert('RGB')
|
||||
if mode == 'best':
|
||||
return ci.interrogate(image)
|
||||
elif mode == 'classic':
|
||||
return ci.interrogate_classic(image)
|
||||
elif mode == 'fast':
|
||||
return ci.interrogate_fast(image)
|
||||
elif mode == 'negative':
|
||||
return ci.interrogate_negative(image)
|
||||
|
||||
# image = Image.open(image_path).convert('RGB')
|
||||
# ci = Interrogator(Config(clip_model_name="ViT-L-14/openai"))
|
||||
# print(ci.interrogate(image))
|
||||
|
||||
|
||||
class ClipInterrogator:
|
||||
|
||||
global _available
|
||||
available=_available
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {"required": {
|
||||
"image": ("IMAGE",),
|
||||
"prompt_mode": (['fast','classic','best','negative'],),
|
||||
"image_analysis": (["off","on"],),
|
||||
},
|
||||
|
||||
# "optional":{
|
||||
# "output":("CLIPINTERROGATOR", {"multiline": True,"default": "", "dynamicPrompts": False})
|
||||
# },
|
||||
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("STRING","STRING",)
|
||||
RETURN_NAMES = ("prompt","random_samples",)
|
||||
|
||||
FUNCTION = "run"
|
||||
|
||||
CATEGORY = "♾️Mixlab/Prompt"
|
||||
OUTPUT_NODE = True
|
||||
INPUT_IS_LIST = True
|
||||
OUTPUT_IS_LIST = (True,True,)
|
||||
global ci
|
||||
ci = None
|
||||
def run(self,image,prompt_mode,image_analysis):
|
||||
global ci
|
||||
|
||||
prompt_mode=prompt_mode[0]
|
||||
analysis=image_analysis[0]
|
||||
|
||||
prompt_result=[]
|
||||
analysis_result=[]
|
||||
|
||||
# 进度条
|
||||
pbar = comfy.utils.ProgressBar(len(image)*(2 if analysis=='on' else 1))
|
||||
|
||||
if ci==None:
|
||||
config=Config(
|
||||
clip_model_name="ViT-L-14/openai",
|
||||
device="cuda" if torch.cuda.is_available() else "cpu",
|
||||
download_cache=True,
|
||||
clip_model_path=cache_path,
|
||||
cache_path=cache_path
|
||||
)
|
||||
config.apply_low_vram_defaults()
|
||||
|
||||
caption_model,caption_processor=load_caption_model(caption_model_path,config)
|
||||
|
||||
config.caption_model= caption_model
|
||||
config.caption_processor= caption_processor
|
||||
|
||||
ci = Interrogator(config)
|
||||
# else:
|
||||
# simple_lama.model.to("cuda" if torch.cuda.is_available() else "cpu")
|
||||
|
||||
for i in range(len(image)):
|
||||
im=image[i]
|
||||
|
||||
im=tensor2pil(im)
|
||||
im=im.convert('RGB')
|
||||
|
||||
if analysis=='on':
|
||||
analysis_res=image_analysis_fn(ci,im)
|
||||
analysis_result.append( analysis_res )
|
||||
pbar.update(1)
|
||||
|
||||
prompt=image_to_prompt(ci,im,prompt_mode)
|
||||
pbar.update(1)
|
||||
prompt_result.append(prompt)
|
||||
|
||||
|
||||
# result.save("inpainted.png")
|
||||
if ci.config.clip_offload and not ci.clip_offloaded:
|
||||
ci.clip_model = ci.clip_model.to('cpu')
|
||||
ci.clip_offloaded = True
|
||||
|
||||
if ci.config.caption_offload and not ci.caption_offloaded:
|
||||
ci.caption_model = ci.caption_model.to('cpu')
|
||||
ci.caption_offloaded = True
|
||||
|
||||
# analysis_result=[]
|
||||
# items = app.graph.getNodeById(31).widgets[2].value["items"]
|
||||
|
||||
random_samples=[]
|
||||
|
||||
for r in analysis_result:
|
||||
random_sample = generate_sentences([r['medium_ranks'], r['artist_ranks'],r['movement_ranks'],r['trending_ranks'],r['flavor_ranks']])
|
||||
for s in random_sample:
|
||||
random_samples.append(s)
|
||||
# print(len(random_samples))
|
||||
# print('-----')
|
||||
# print( random_samples)
|
||||
return {
|
||||
"ui":{
|
||||
"prompt": prompt_result,
|
||||
"analysis":analysis_result,
|
||||
"random_samples":random_samples
|
||||
},
|
||||
"result": (prompt_result,random_samples,)}
|
||||
@@ -1,258 +0,0 @@
|
||||
from transformers import CLIPSegProcessor, CLIPSegForImageSegmentation
|
||||
|
||||
from PIL import Image
|
||||
import torch
|
||||
import torchvision.transforms as T
|
||||
import numpy as np
|
||||
|
||||
from torchvision.transforms.functional import to_pil_image
|
||||
import matplotlib.pyplot as plt
|
||||
import matplotlib.cm as cm
|
||||
|
||||
|
||||
import cv2
|
||||
|
||||
from scipy.ndimage import gaussian_filter
|
||||
|
||||
from typing import Optional, Tuple
|
||||
|
||||
import warnings,os
|
||||
warnings.filterwarnings("ignore", category=UserWarning, module="torch")
|
||||
warnings.filterwarnings("ignore", category=UserWarning, module="safetensors")
|
||||
|
||||
import folder_paths
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger('CLIPSeg nodes')
|
||||
|
||||
clipseg_model_dir = os.path.join(folder_paths.models_dir, "clipseg")
|
||||
|
||||
if not os.path.exists(clipseg_model_dir):
|
||||
clipseg_model_dir='CIDAS/clipseg-rd64-refined'
|
||||
|
||||
"""Helper methods for CLIPSeg nodes"""
|
||||
|
||||
def tensor_to_numpy(tensor: torch.Tensor) -> np.ndarray:
|
||||
"""Convert a tensor to a numpy array and scale its values to 0-255."""
|
||||
array = tensor.numpy().squeeze()
|
||||
return (array * 255).astype(np.uint8)
|
||||
|
||||
def numpy_to_tensor(array: np.ndarray) -> torch.Tensor:
|
||||
"""Convert a numpy array to a tensor and scale its values from 0-255 to 0-1."""
|
||||
array = array.astype(np.float32) / 255.0
|
||||
return torch.from_numpy(array)[None,]
|
||||
|
||||
def apply_colormap(mask: torch.Tensor, colormap) -> np.ndarray:
|
||||
"""Apply a colormap to a tensor and convert it to a numpy array."""
|
||||
colored_mask = colormap(mask.numpy())[:, :, :3]
|
||||
return (colored_mask * 255).astype(np.uint8)
|
||||
|
||||
def resize_image(image: np.ndarray, dimensions: Tuple[int, int]) -> np.ndarray:
|
||||
"""Resize an image to the given dimensions using linear interpolation."""
|
||||
return cv2.resize(image, dimensions, interpolation=cv2.INTER_LINEAR)
|
||||
|
||||
def overlay_image(background: np.ndarray, foreground: np.ndarray, alpha: float) -> np.ndarray:
|
||||
"""Overlay the foreground image onto the background with a given opacity (alpha)."""
|
||||
return cv2.addWeighted(background, 1 - alpha, foreground, alpha, 0)
|
||||
|
||||
def dilate_mask(mask: torch.Tensor, dilation_factor: float) -> torch.Tensor:
|
||||
"""Dilate a mask using a square kernel with a given dilation factor."""
|
||||
kernel_size = int(dilation_factor * 2) + 1
|
||||
kernel = np.ones((kernel_size, kernel_size), np.uint8)
|
||||
mask_dilated = cv2.dilate(mask.numpy(), kernel, iterations=1)
|
||||
return torch.from_numpy(mask_dilated)
|
||||
|
||||
|
||||
|
||||
class CLIPSeg:
|
||||
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
"""
|
||||
Return a dictionary which contains config for all input fields.
|
||||
Some types (string): "MODEL", "VAE", "CLIP", "CONDITIONING", "LATENT", "IMAGE", "INT", "STRING", "FLOAT".
|
||||
Input types "INT", "STRING" or "FLOAT" are special values for fields on the node.
|
||||
The type can be a list for selection.
|
||||
|
||||
Returns: `dict`:
|
||||
- Key input_fields_group (`string`): Can be either required, hidden or optional. A node class must have property `required`
|
||||
- Value input_fields (`dict`): Contains input fields config:
|
||||
* Key field_name (`string`): Name of a entry-point method's argument
|
||||
* Value field_config (`tuple`):
|
||||
+ First value is a string indicate the type of field or a list for selection.
|
||||
+ Secound value is a config for type "INT", "STRING" or "FLOAT".
|
||||
"""
|
||||
return {"required":
|
||||
{
|
||||
"image": ("IMAGE",),
|
||||
"text": ("STRING", {"multiline": False}),
|
||||
|
||||
},
|
||||
"optional":
|
||||
{
|
||||
"blur": ("FLOAT", {"min": 0, "max": 15, "step": 0.1, "default": 7}),
|
||||
"threshold": ("FLOAT", {"min": 0, "max": 1, "step": 0.05, "default": 0.4}),
|
||||
"dilation_factor": ("INT", {"min": 0, "max": 10, "step": 1, "default": 4}),
|
||||
}
|
||||
}
|
||||
|
||||
CATEGORY = "Mixlab/mask"
|
||||
RETURN_TYPES = ("MASK", "IMAGE", "IMAGE",)
|
||||
RETURN_NAMES = ("Mask","Heatmap Mask", "BW Mask")
|
||||
|
||||
# INPUT_IS_LIST = True
|
||||
# OUTPUT_IS_LIST = (True,)
|
||||
|
||||
FUNCTION = "segment_image"
|
||||
def segment_image(self, image: torch.Tensor, text: str, blur: float, threshold: float, dilation_factor: int) -> Tuple[torch.Tensor, torch.Tensor, torch.Tensor]:
|
||||
"""Create a segmentation mask from an image and a text prompt using CLIPSeg.
|
||||
|
||||
Args:
|
||||
image (torch.Tensor): The image to segment.
|
||||
text (str): The text prompt to use for segmentation.
|
||||
blur (float): How much to blur the segmentation mask.
|
||||
threshold (float): The threshold to use for binarizing the segmentation mask.
|
||||
dilation_factor (int): How much to dilate the segmentation mask.
|
||||
|
||||
Returns:
|
||||
Tuple[torch.Tensor, torch.Tensor, torch.Tensor]: The segmentation mask, the heatmap mask, and the binarized mask.
|
||||
"""
|
||||
|
||||
# Convert the Tensor to a PIL image
|
||||
image_np = image.numpy().squeeze() # Remove the first dimension (batch size of 1)
|
||||
# Convert the numpy array back to the original range (0-255) and data type (uint8)
|
||||
image_np = (image_np * 255).astype(np.uint8)
|
||||
# Create a PIL image from the numpy array
|
||||
i = Image.fromarray(image_np, mode="RGB")
|
||||
|
||||
processor = CLIPSegProcessor.from_pretrained(clipseg_model_dir)
|
||||
model = CLIPSegForImageSegmentation.from_pretrained(clipseg_model_dir)
|
||||
|
||||
prompt = text
|
||||
|
||||
input_prc = processor(text=prompt, images=i, padding="max_length", return_tensors="pt")
|
||||
|
||||
# Predict the segemntation mask
|
||||
with torch.no_grad():
|
||||
outputs = model(**input_prc)
|
||||
|
||||
tensor = torch.sigmoid(outputs[0]) # get the mask
|
||||
|
||||
# Apply a threshold to the original tensor to cut off low values
|
||||
thresh = threshold
|
||||
tensor_thresholded = torch.where(tensor > thresh, tensor, torch.tensor(0, dtype=torch.float))
|
||||
|
||||
# Apply Gaussian blur to the thresholded tensor
|
||||
sigma = blur
|
||||
tensor_smoothed = gaussian_filter(tensor_thresholded.numpy(), sigma=sigma)
|
||||
tensor_smoothed = torch.from_numpy(tensor_smoothed)
|
||||
|
||||
# Normalize the smoothed tensor to [0, 1]
|
||||
mask_normalized = (tensor_smoothed - tensor_smoothed.min()) / (tensor_smoothed.max() - tensor_smoothed.min())
|
||||
|
||||
# Dilate the normalized mask
|
||||
mask_dilated = dilate_mask(mask_normalized, dilation_factor)
|
||||
|
||||
# Convert the mask to a heatmap and a binary mask
|
||||
heatmap = apply_colormap(mask_dilated, cm.viridis)
|
||||
binary_mask = apply_colormap(mask_dilated, cm.Greys_r)
|
||||
|
||||
# Overlay the heatmap and binary mask on the original image
|
||||
dimensions = (image_np.shape[1], image_np.shape[0])
|
||||
heatmap_resized = resize_image(heatmap, dimensions)
|
||||
binary_mask_resized = resize_image(binary_mask, dimensions)
|
||||
|
||||
alpha_heatmap, alpha_binary = 0.5, 1
|
||||
overlay_heatmap = overlay_image(image_np, heatmap_resized, alpha_heatmap)
|
||||
overlay_binary = overlay_image(image_np, binary_mask_resized, alpha_binary)
|
||||
|
||||
# Convert the numpy arrays to tensors
|
||||
image_out_heatmap = numpy_to_tensor(overlay_heatmap)
|
||||
image_out_binary = numpy_to_tensor(overlay_binary)
|
||||
|
||||
# Save or display the resulting binary mask
|
||||
binary_mask_image = Image.fromarray(binary_mask_resized[..., 0])
|
||||
|
||||
# convert PIL image to numpy array
|
||||
tensor_bw = binary_mask_image.convert("RGB")
|
||||
tensor_bw = np.array(tensor_bw).astype(np.float32) / 255.0
|
||||
tensor_bw = torch.from_numpy(tensor_bw)[None,]
|
||||
tensor_bw = tensor_bw.squeeze(0)[..., 0]
|
||||
|
||||
return tensor_bw, image_out_heatmap, image_out_binary
|
||||
|
||||
#OUTPUT_NODE = False
|
||||
|
||||
class CombineMasks:
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {"required":
|
||||
{
|
||||
"input_image": ("IMAGE", ),
|
||||
"mask_1": ("MASK", ),
|
||||
"mask_2": ("MASK", ),
|
||||
},
|
||||
"optional":
|
||||
{
|
||||
"mask_3": ("MASK",),
|
||||
},
|
||||
}
|
||||
|
||||
CATEGORY = "Mixlab/mask"
|
||||
RETURN_TYPES = ("MASK", "IMAGE", "IMAGE",)
|
||||
RETURN_NAMES = ("Combined Mask","Heatmap Mask", "BW Mask")
|
||||
|
||||
FUNCTION = "combine_masks"
|
||||
|
||||
def combine_masks(self, input_image: torch.Tensor, mask_1: torch.Tensor, mask_2: torch.Tensor, mask_3: Optional[torch.Tensor] = None) -> Tuple[torch.Tensor, torch.Tensor, torch.Tensor]:
|
||||
"""A method that combines two or three masks into one mask. Takes in tensors and returns the mask as a tensor, as well as the heatmap and binary mask as tensors."""
|
||||
|
||||
# Combine masks
|
||||
if mask_1 is not None:
|
||||
mask_1 = mask_1.squeeze()
|
||||
if mask_2 is not None:
|
||||
mask_2 = mask_2.squeeze()
|
||||
if mask_3 is not None:
|
||||
mask_3 = mask_3.squeeze()
|
||||
|
||||
print(mask_1.shape,mask_2.shape , mask_3.shape)
|
||||
combined_mask = mask_1 + mask_2 + mask_3 if mask_3 is not None else mask_1 + mask_2
|
||||
# print(combined_mask)
|
||||
|
||||
# Convert image and masks to numpy arrays
|
||||
image_np = tensor_to_numpy(input_image)
|
||||
heatmap = apply_colormap(combined_mask, cm.viridis)
|
||||
binary_mask = apply_colormap(combined_mask, cm.Greys_r)
|
||||
|
||||
# Resize heatmap and binary mask to match the original image dimensions
|
||||
dimensions = (image_np.shape[1], image_np.shape[0])
|
||||
print('heatmap',heatmap)
|
||||
if dimensions is None or dimensions[0] == 0 or dimensions[1] == 0:
|
||||
raise ValueError("Invalid dimensions")
|
||||
|
||||
heatmap_resized = resize_image(heatmap, dimensions)
|
||||
binary_mask_resized = resize_image(binary_mask, dimensions)
|
||||
|
||||
# Overlay the heatmap and binary mask onto the original image
|
||||
alpha_heatmap, alpha_binary = 0.5, 1
|
||||
overlay_heatmap = overlay_image(image_np, heatmap_resized, alpha_heatmap)
|
||||
overlay_binary = overlay_image(image_np, binary_mask_resized, alpha_binary)
|
||||
|
||||
# Convert overlays to tensors
|
||||
image_out_heatmap = numpy_to_tensor(overlay_heatmap)
|
||||
image_out_binary = numpy_to_tensor(overlay_binary)
|
||||
|
||||
return combined_mask, image_out_heatmap, image_out_binary
|
||||
|
||||
# A dictionary that contains all nodes you want to export with their names
|
||||
# NOTE: names should be globally unique
|
||||
NODE_CLASS_MAPPINGS = {
|
||||
"CLIPSeg": CLIPSeg,
|
||||
"CombineSegMasks": CombineMasks,
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
import os,sys
|
||||
import folder_paths
|
||||
|
||||
from PIL import Image
|
||||
import importlib.util
|
||||
import numpy as np
|
||||
|
||||
import torch
|
||||
|
||||
global _available
|
||||
_available=False
|
||||
|
||||
def is_installed(package):
|
||||
try:
|
||||
spec = importlib.util.find_spec(package)
|
||||
except ModuleNotFoundError:
|
||||
return False
|
||||
return spec is not None
|
||||
|
||||
if is_installed('simple_lama_inpainting')==False:
|
||||
import subprocess
|
||||
from packaging import version
|
||||
|
||||
if version.parse(torch.__version__)>=version.parse('2.1'):
|
||||
# 安装
|
||||
print('#pip install simple_lama_inpainting')
|
||||
|
||||
result = subprocess.run([sys.executable, '-s', '-m', 'pip', 'install', 'simple_lama_inpainting'], capture_output=True, text=True)
|
||||
|
||||
#检查命令执行结果
|
||||
if result.returncode == 0:
|
||||
print("#install success")
|
||||
from simple_lama_inpainting import SimpleLama
|
||||
_available=True
|
||||
else:
|
||||
print("#install error")
|
||||
else:
|
||||
print('#pls check your torch version >= 2.1')
|
||||
|
||||
else:
|
||||
from simple_lama_inpainting import SimpleLama
|
||||
_available=True
|
||||
|
||||
|
||||
|
||||
llma_model_path=os.path.join(folder_paths.models_dir, "lama/big-lama.pt")
|
||||
if not os.path.exists(llma_model_path):
|
||||
os.environ['LAMA_MODEL']=''
|
||||
print(f"## lama torchscript model not found: {llma_model_path},pls download from https://github.com/enesmsahin/simple-lama-inpainting/releases/download/v0.1.0/big-lama.pt")
|
||||
else:
|
||||
os.environ['LAMA_MODEL'] = llma_model_path
|
||||
|
||||
# Tensor to PIL
|
||||
def tensor2pil(image):
|
||||
return Image.fromarray(np.clip(255. * image.cpu().numpy().squeeze(), 0, 255).astype(np.uint8))
|
||||
|
||||
# Convert PIL to Tensor
|
||||
def pil2tensor(image):
|
||||
return torch.from_numpy(np.array(image).astype(np.float32) / 255.0).unsqueeze(0)
|
||||
|
||||
|
||||
# simple_lama = SimpleLama()
|
||||
|
||||
# img_path = "image.png"
|
||||
# mask_path = "mask.png"
|
||||
|
||||
# image = Image.open(img_path)
|
||||
# mask = Image.open(mask_path).convert('L')
|
||||
|
||||
# result = simple_lama(image, mask)
|
||||
# result.save("inpainted.png")
|
||||
|
||||
|
||||
class LaMaInpainting:
|
||||
global _available
|
||||
available=_available
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {"required": {
|
||||
"image": ("IMAGE",),
|
||||
"mask": ("MASK",),
|
||||
},
|
||||
|
||||
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("IMAGE",)
|
||||
|
||||
FUNCTION = "run"
|
||||
|
||||
CATEGORY = "♾️Mixlab/Image"
|
||||
|
||||
INPUT_IS_LIST = True
|
||||
OUTPUT_IS_LIST = (True,)
|
||||
global simple_lama
|
||||
simple_lama = None
|
||||
def run(self,image,mask):
|
||||
global simple_lama
|
||||
|
||||
result=[]
|
||||
if simple_lama==None:
|
||||
simple_lama = SimpleLama()
|
||||
else:
|
||||
simple_lama.model.to("cuda" if torch.cuda.is_available() else "cpu")
|
||||
|
||||
for i in range(len(image)):
|
||||
im=image[i]
|
||||
ma=mask[i]
|
||||
im=tensor2pil(im)
|
||||
ma=tensor2pil(ma)
|
||||
ma =ma.convert('L')
|
||||
|
||||
res = simple_lama(im, ma)
|
||||
res=pil2tensor(res)
|
||||
result.append(res)
|
||||
# result.save("inpainted.png")
|
||||
if simple_lama.device=='cuda':
|
||||
simple_lama.model.to('cpu')
|
||||
|
||||
return (result,)
|
||||
@@ -0,0 +1,247 @@
|
||||
|
||||
import scipy.ndimage
|
||||
import torch
|
||||
|
||||
from nodes import MAX_RESOLUTION
|
||||
|
||||
import numpy as np
|
||||
# from PIL import Image, ImageDraw
|
||||
from PIL import Image, ImageOps
|
||||
|
||||
from comfy.cli_args import args
|
||||
import cv2
|
||||
|
||||
|
||||
|
||||
# Tensor to PIL
|
||||
def tensor2pil(image):
|
||||
return Image.fromarray(np.clip(255. * image.cpu().numpy().squeeze(), 0, 255).astype(np.uint8))
|
||||
|
||||
# Convert PIL to Tensor
|
||||
def pil2tensor(image):
|
||||
return torch.from_numpy(np.array(image).astype(np.float32) / 255.0).unsqueeze(0)
|
||||
|
||||
|
||||
def add_masks(mask1, mask2):
|
||||
mask1 = mask1.cpu()
|
||||
mask2 = mask2.cpu()
|
||||
cv2_mask1 = np.array(mask1) * 255
|
||||
cv2_mask2 = np.array(mask2) * 255
|
||||
|
||||
if cv2_mask1.shape == cv2_mask2.shape:
|
||||
cv2_mask = cv2.add(cv2_mask1, cv2_mask2)
|
||||
return torch.clamp(torch.from_numpy(cv2_mask) / 255.0, min=0, max=1)
|
||||
else:
|
||||
return mask1
|
||||
|
||||
|
||||
def grow(mask, expand, tapered_corners):
|
||||
c = 0 if tapered_corners else 1
|
||||
kernel = np.array([[c, 1, c],
|
||||
[1, 1, 1],
|
||||
[c, 1, c]])
|
||||
mask = mask.reshape((-1, mask.shape[-2], mask.shape[-1]))
|
||||
out = []
|
||||
for m in mask:
|
||||
output = m.numpy()
|
||||
for _ in range(abs(expand)):
|
||||
if expand < 0:
|
||||
output = scipy.ndimage.grey_erosion(output, footprint=kernel)
|
||||
else:
|
||||
output = scipy.ndimage.grey_dilation(output, footprint=kernel)
|
||||
output = torch.from_numpy(output)
|
||||
out.append(output)
|
||||
return torch.stack(out, dim=0)
|
||||
|
||||
def combine(destination, source, x, y):
|
||||
output = destination.reshape((-1, destination.shape[-2], destination.shape[-1])).clone()
|
||||
source = source.reshape((-1, source.shape[-2], source.shape[-1]))
|
||||
|
||||
left, top = (x, y,)
|
||||
right, bottom = (min(left + source.shape[-1], destination.shape[-1]), min(top + source.shape[-2], destination.shape[-2]))
|
||||
visible_width, visible_height = (right - left, bottom - top,)
|
||||
|
||||
source_portion = source[:, :visible_height, :visible_width]
|
||||
destination_portion = destination[:, top:bottom, left:right]
|
||||
|
||||
#operation == "subtract":
|
||||
output[:, top:bottom, left:right] = destination_portion - source_portion
|
||||
|
||||
output = torch.clamp(output, 0.0, 1.0)
|
||||
|
||||
return output
|
||||
|
||||
class OutlineMask:
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {
|
||||
"required": {
|
||||
"mask": ("MASK",),
|
||||
"outline_width":("INT", {"default": 10,"min": 1, "max": MAX_RESOLUTION, "step": 1}),
|
||||
"tapered_corners": ("BOOLEAN", {"default": True}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ('MASK',)
|
||||
|
||||
FUNCTION = "run"
|
||||
|
||||
CATEGORY = "♾️Mixlab/Mask"
|
||||
|
||||
# 运行的函数
|
||||
def run(self, mask, outline_width, tapered_corners):
|
||||
|
||||
m1=grow(mask,outline_width,tapered_corners)
|
||||
m2=grow(mask,-outline_width,tapered_corners)
|
||||
|
||||
m3=combine(m1,m2,0,0)
|
||||
|
||||
return (m3,)
|
||||
|
||||
|
||||
class MaskListReplace:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {"required": {
|
||||
"masks": ("MASK",),
|
||||
"mask_replace": ("MASK",),
|
||||
"start_index":("INT", {"default": 0, "min": 0, "step": 1}),
|
||||
"end_index":("INT", {"default": 0, "min": 0, "step": 1}),
|
||||
"invert": ("BOOLEAN", {"default": False}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("MASK",)
|
||||
FUNCTION = "run"
|
||||
CATEGORY = "♾️Mixlab/Video"
|
||||
|
||||
INPUT_IS_LIST = True
|
||||
OUTPUT_IS_LIST = (True,)
|
||||
|
||||
def run(self, masks,mask_replace,start_index,end_index,invert):
|
||||
mask_replace=mask_replace[0]
|
||||
start_index=start_index[0]
|
||||
end_index=end_index[0]
|
||||
invert=invert[0]
|
||||
|
||||
new_masks=[]
|
||||
for i in range(len(masks)):
|
||||
if i>=start_index and i<=end_index:
|
||||
if invert:
|
||||
new_masks.append(masks[i])
|
||||
else:
|
||||
new_masks.append(mask_replace)
|
||||
else:
|
||||
if invert:
|
||||
new_masks.append(mask_replace)
|
||||
else:
|
||||
new_masks.append(masks[i])
|
||||
|
||||
return (new_masks,)
|
||||
|
||||
|
||||
class MaskListMerge:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {"required": {
|
||||
"masks": ("MASK",),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("MASK",)
|
||||
FUNCTION = "run"
|
||||
CATEGORY = "♾️Mixlab/Mask"
|
||||
|
||||
INPUT_IS_LIST = True
|
||||
OUTPUT_IS_LIST = (False,)
|
||||
|
||||
def run(self, masks):
|
||||
mask=masks[0]
|
||||
if isinstance(masks, list):
|
||||
for m in masks:
|
||||
# print(m.shape)
|
||||
mask = add_masks(mask, m)
|
||||
return (mask,)
|
||||
|
||||
|
||||
class FeatheredMask:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {
|
||||
"required": {
|
||||
"mask": ("MASK",),
|
||||
"start_offset":("INT", {"default": 1,
|
||||
"min": -150,
|
||||
"max": 150,
|
||||
"step": 1,
|
||||
"display": "slider"}),
|
||||
"feathering_weight":("FLOAT", {"default": 0.1,
|
||||
"min": 0.0,
|
||||
"max": 1,
|
||||
"step": 0.1,
|
||||
"display": "slider"})
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ('MASK',)
|
||||
|
||||
FUNCTION = "run"
|
||||
|
||||
CATEGORY = "♾️Mixlab/Mask"
|
||||
|
||||
OUTPUT_IS_LIST = (True,)
|
||||
|
||||
# 运行的函数
|
||||
def run(self,mask,start_offset, feathering_weight):
|
||||
# print(mask.shape,mask.size())
|
||||
|
||||
num,_,_=mask.size()
|
||||
|
||||
masks=[]
|
||||
|
||||
for i in range(num):
|
||||
mm=mask[i]
|
||||
image=tensor2pil(mm)
|
||||
|
||||
# Open the image using PIL
|
||||
image = image.convert("L")
|
||||
if start_offset>0:
|
||||
image=ImageOps.invert(image)
|
||||
|
||||
# Convert the image to a numpy array
|
||||
image_np = np.array(image)
|
||||
|
||||
# Use Canny edge detection to get black contours
|
||||
edges = cv2.Canny(image_np, 30, 150)
|
||||
|
||||
for i in range(0,abs(start_offset)):
|
||||
# int(100*feathering_weight)
|
||||
a=int(abs(start_offset)*0.1*i)
|
||||
# Dilate the black contours to make them wider
|
||||
kernel = np.ones((a, a), np.uint8)
|
||||
|
||||
dilated_edges = cv2.dilate(edges, kernel, iterations=1)
|
||||
# dilated_edges = cv2.erode(edges, kernel, iterations=1)
|
||||
# Smooth the dilated edges using Gaussian blur
|
||||
smoothed_edges = cv2.GaussianBlur(dilated_edges, (5, 5), 0)
|
||||
|
||||
# Adjust the feathering weight
|
||||
feathering_weight = max(0, min(feathering_weight, 1))
|
||||
|
||||
# Blend the smoothed edges with the original image to achieve feathering effect
|
||||
image_np = cv2.addWeighted(image_np, 1, smoothed_edges, feathering_weight, feathering_weight)
|
||||
|
||||
# Convert the result back to PIL image
|
||||
result_image = Image.fromarray(np.uint8(image_np))
|
||||
result_image=result_image.convert("L")
|
||||
|
||||
if start_offset>0:
|
||||
result_image=ImageOps.invert(result_image)
|
||||
|
||||
result_image=result_image.convert("L")
|
||||
mt=pil2tensor(result_image)
|
||||
masks.append(mt)
|
||||
|
||||
# print( mt.size())
|
||||
return (masks,)
|
||||
@@ -1,15 +1,91 @@
|
||||
import random
|
||||
import comfy.utils
|
||||
import json
|
||||
import os
|
||||
import numpy as np
|
||||
from urllib import request, parse
|
||||
import folder_paths
|
||||
from PIL import Image, ImageOps,ImageFilter,ImageEnhance,ImageDraw,ImageSequence, ImageFont
|
||||
from PIL.PngImagePlugin import PngInfo
|
||||
|
||||
import hashlib
|
||||
import requests
|
||||
import json
|
||||
|
||||
|
||||
# def queue_prompt(prompt_workflow):
|
||||
# p = {"prompt": prompt_workflow}
|
||||
# data = json.dumps(p).encode('utf-8')
|
||||
# req = request.Request("http://127.0.0.1:8188/prompt", data=data)
|
||||
# request.urlopen(req)
|
||||
|
||||
embeddings_path=os.path.join(folder_paths.models_dir, "embeddings")
|
||||
|
||||
def get_files_with_extension(directory, extension):
|
||||
|
||||
file_list = []
|
||||
for root, dirs, files in os.walk(directory):
|
||||
for file in files:
|
||||
if file.endswith(extension):
|
||||
file_name = os.path.splitext(file)[0]
|
||||
file_list.append(file_name)
|
||||
return file_list
|
||||
|
||||
def join_with_(text_list,delimiter):
|
||||
joined_text = delimiter.join(text_list)
|
||||
return joined_text
|
||||
|
||||
|
||||
|
||||
def queue_prompt(prompt_workflow):
|
||||
p = {"prompt": prompt_workflow}
|
||||
data = json.dumps(p).encode('utf-8')
|
||||
req = request.Request("http://127.0.0.1:8188/prompt", data=data)
|
||||
request.urlopen(req)
|
||||
def load_json(file_path):
|
||||
try:
|
||||
with open(file_path, 'r') as json_file:
|
||||
data = json.load(json_file)
|
||||
return data
|
||||
except FileNotFoundError:
|
||||
print(f"File not found: {file_path}")
|
||||
return None
|
||||
except json.JSONDecodeError:
|
||||
print(f"Error decoding JSON in file: {file_path}")
|
||||
return None
|
||||
|
||||
def save_json(data_dict, file_path):
|
||||
try:
|
||||
with open(file_path, 'w') as json_file:
|
||||
json.dump(data_dict, json_file, indent=4)
|
||||
print(f"Data saved to {file_path}")
|
||||
except Exception as e:
|
||||
print(f"Error saving JSON to file: {e}")
|
||||
|
||||
# pysss的lora加载器
|
||||
# def get_model_version_info(hash_value):
|
||||
# # http://127.0.0.1:1082
|
||||
# proxies = {'http': 'http://127.0.0.1:1082', 'https': 'https://127.0.0.1:1082'}
|
||||
# api_url = f"https://civitai.com/api/v1/model-versions/by-hash/{hash_value}"
|
||||
# print(api_url)
|
||||
# response = requests.get(api_url,proxies=proxies, verify=False)
|
||||
|
||||
# if response.status_code == 200:
|
||||
# return response.json()
|
||||
# else:
|
||||
# return None
|
||||
|
||||
# def calculate_sha256(file_path):
|
||||
# sha256_hash = hashlib.sha256()
|
||||
# with open(file_path, "rb") as f:
|
||||
# for chunk in iter(lambda: f.read(4096), b""):
|
||||
# sha256_hash.update(chunk)
|
||||
# return sha256_hash.hexdigest()
|
||||
|
||||
|
||||
|
||||
|
||||
class AnyType(str):
|
||||
"""A special class that is always equal in not equal comparisons. Credit to pythongosssss"""
|
||||
|
||||
def __ne__(self, __value: object) -> bool:
|
||||
return False
|
||||
|
||||
any_type = AnyType("*")
|
||||
|
||||
|
||||
default_prompt1='''Swing
|
||||
@@ -45,6 +121,232 @@ default_prompt1='''Swing
|
||||
'''
|
||||
default_prompt1="\n".join([p.strip() for p in default_prompt1.split('\n') if p.strip()!=''])
|
||||
|
||||
|
||||
def tensor2pil(image):
|
||||
return Image.fromarray(np.clip(255. * image.cpu().numpy().squeeze(), 0, 255).astype(np.uint8))
|
||||
|
||||
def addWeight(text, weight=1):
|
||||
if weight == 1:
|
||||
return text
|
||||
else:
|
||||
return f"({text}:{round(weight,3)})"
|
||||
|
||||
def prompt_delete_words(sentence, new_words_length):
|
||||
# 使用逗号分割句子,并去除空格
|
||||
words = [word.strip() for word in sentence.split(",")]
|
||||
|
||||
# 计算需要删除的单词数量
|
||||
num_to_delete = len(words) - new_words_length
|
||||
|
||||
words_to=[w for w in words]
|
||||
|
||||
# 逐个删除单词并存储在新列表中
|
||||
new_words = []
|
||||
for i in range(len(words)):
|
||||
if num_to_delete > 0:
|
||||
num_to_delete -= 1
|
||||
else:
|
||||
words_to.pop()
|
||||
if len(words_to)>0:
|
||||
new_words.append(", ".join(words_to))
|
||||
|
||||
return new_words
|
||||
|
||||
# # 测试方法
|
||||
# sentence = "a computer, a glass tablet with a keyboard on a dark background, 3d illustration, reflection, cgi 8k, clear glass, archaic, cut-away, white outline"
|
||||
# new_words_length = 5
|
||||
# result = prompt_delete_words(sentence, new_words_length)
|
||||
# print(result)
|
||||
|
||||
class PromptImage:
|
||||
def __init__(self):
|
||||
self.output_dir = folder_paths.get_output_directory()
|
||||
self.type = "output"
|
||||
self.prefix_append = "PromptImage"
|
||||
self.compress_level = 4
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {
|
||||
"required": {
|
||||
"prompts": ("STRING",
|
||||
{
|
||||
"multiline": True,
|
||||
"default": '',
|
||||
"dynamicPrompts": False
|
||||
}),
|
||||
|
||||
"images": ("IMAGE",{"default": None}),
|
||||
"save_to_image": (["enable", "disable"],),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ()
|
||||
|
||||
OUTPUT_NODE = True
|
||||
|
||||
INPUT_IS_LIST = True
|
||||
|
||||
FUNCTION = "run"
|
||||
|
||||
CATEGORY = "♾️Mixlab/Output"
|
||||
|
||||
# 运行的函数
|
||||
def run(self,prompts,images,save_to_image):
|
||||
filename_prefix="mixlab_"
|
||||
filename_prefix += self.prefix_append
|
||||
full_output_folder, filename, counter, subfolder, filename_prefix = folder_paths.get_save_image_path(
|
||||
filename_prefix, self.output_dir, images[0].shape[1], images[0].shape[0])
|
||||
|
||||
results = list()
|
||||
|
||||
save_to_image=save_to_image[0]=='enable'
|
||||
|
||||
for index in range(len(images)):
|
||||
res=[]
|
||||
imgs=images[index]
|
||||
|
||||
for image in imgs:
|
||||
img=tensor2pil(image)
|
||||
|
||||
metadata = None
|
||||
if save_to_image:
|
||||
metadata = PngInfo()
|
||||
prompt_text=prompts[index]
|
||||
if prompt_text is not None:
|
||||
metadata.add_text("prompt_text", prompt_text)
|
||||
|
||||
file = f"{filename}_{index}_{counter:05}_.png"
|
||||
img.save(os.path.join(full_output_folder, file), pnginfo=metadata, compress_level=self.compress_level)
|
||||
res.append({
|
||||
"filename": file,
|
||||
"subfolder": subfolder,
|
||||
"type": self.type
|
||||
})
|
||||
counter += 1
|
||||
results.append(res)
|
||||
|
||||
return { "ui": { "_images": results,"prompts":prompts } }
|
||||
|
||||
|
||||
|
||||
|
||||
class PromptSimplification:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {
|
||||
"required": {
|
||||
"prompt": ("STRING",
|
||||
{
|
||||
"multiline": True,
|
||||
"default": '',
|
||||
"dynamicPrompts": False
|
||||
}),
|
||||
|
||||
"length":("INT", {"default": 5, "min": 1,"max":100, "step": 1, "display": "number"}),
|
||||
|
||||
# "min_value":("FLOAT", {
|
||||
# "default": -2,
|
||||
# "min": -10,
|
||||
# "max": 0xffffffffffffffff,
|
||||
# "step": 0.01,
|
||||
# "display": "number"
|
||||
# }),
|
||||
# "max_value":("FLOAT", {
|
||||
# "default": 2,
|
||||
# "min": -10,
|
||||
# "max": 0xffffffffffffffff,
|
||||
# "step": 0.01,
|
||||
# "display": "number"
|
||||
# }),
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("STRING",)
|
||||
RETURN_NAMES = ("prompts",)
|
||||
|
||||
FUNCTION = "run"
|
||||
|
||||
CATEGORY = "♾️Mixlab/Prompt"
|
||||
|
||||
INPUT_IS_LIST = True
|
||||
OUTPUT_IS_LIST = (True,)
|
||||
OUTPUT_NODE = True
|
||||
|
||||
# 运行的函数
|
||||
def run(self,prompt,length):
|
||||
length=length[0]
|
||||
result=[]
|
||||
for p in prompt:
|
||||
nps=prompt_delete_words(p,length)
|
||||
for n in nps:
|
||||
result.append(n)
|
||||
|
||||
result= [elem.strip() for elem in result if elem.strip()]
|
||||
|
||||
return {"ui": {"prompts": result}, "result": (result,)}
|
||||
|
||||
|
||||
|
||||
class PromptSlide:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {
|
||||
"required": {
|
||||
|
||||
"prompt_keyword": ("STRING",
|
||||
{
|
||||
"multiline": False,
|
||||
"default": '',
|
||||
"dynamicPrompts": False
|
||||
}),
|
||||
|
||||
"weight":("FLOAT", {"default": 1, "min": -3,"max": 3,
|
||||
"step": 0.01,
|
||||
"display": "slider"}),
|
||||
|
||||
# "min_value":("FLOAT", {
|
||||
# "default": -2,
|
||||
# "min": -10,
|
||||
# "max": 0xffffffffffffffff,
|
||||
# "step": 0.01,
|
||||
# "display": "number"
|
||||
# }),
|
||||
# "max_value":("FLOAT", {
|
||||
# "default": 2,
|
||||
# "min": -10,
|
||||
# "max": 0xffffffffffffffff,
|
||||
# "step": 0.01,
|
||||
# "display": "number"
|
||||
# }),
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("STRING",)
|
||||
RETURN_NAMES = ("prompt",)
|
||||
|
||||
FUNCTION = "run"
|
||||
|
||||
CATEGORY = "♾️Mixlab/Prompt"
|
||||
|
||||
INPUT_IS_LIST = False
|
||||
OUTPUT_IS_LIST = (False,)
|
||||
OUTPUT_NODE = False
|
||||
|
||||
# 运行的函数
|
||||
def run(self,prompt_keyword,weight):
|
||||
# if weight < min_value:
|
||||
# weight= min_value
|
||||
# elif weight > max_value:
|
||||
# weight= max_value
|
||||
p=addWeight(prompt_keyword,weight)
|
||||
return (p,)
|
||||
|
||||
|
||||
|
||||
|
||||
class RandomPrompt:
|
||||
|
||||
'''
|
||||
@@ -70,6 +372,10 @@ class RandomPrompt:
|
||||
"default": 'sticker, Cartoon, ``'
|
||||
}),
|
||||
"random_sample": (["enable", "disable"],),
|
||||
# "seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff, "step": 1}),
|
||||
},
|
||||
"optional":{
|
||||
"seed": (any_type, {"default": 0, "min": 0, "max": 0xffffffffffffffff}),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -79,15 +385,15 @@ class RandomPrompt:
|
||||
|
||||
FUNCTION = "run"
|
||||
|
||||
CATEGORY = "Mixlab/prompt"
|
||||
CATEGORY = "♾️Mixlab/Prompt"
|
||||
|
||||
OUTPUT_IS_LIST = (True,)
|
||||
OUTPUT_NODE = True
|
||||
|
||||
|
||||
# 运行的函数
|
||||
def run(self,max_count,mutable_prompt,immutable_prompt,random_sample):
|
||||
print('#运行的函数',mutable_prompt,immutable_prompt,max_count,random_sample)
|
||||
def run(self,max_count,mutable_prompt,immutable_prompt,random_sample,seed=0):
|
||||
# print('#运行的函数',mutable_prompt,immutable_prompt,max_count,random_sample)
|
||||
|
||||
# Split the text into an array of words
|
||||
words1 = mutable_prompt.split("\n")
|
||||
@@ -106,6 +412,11 @@ class RandomPrompt:
|
||||
w1=w1.strip()
|
||||
for w2 in words2:
|
||||
w2=w2.strip()
|
||||
if '``' not in w2:
|
||||
if w2=="":
|
||||
w2='``'
|
||||
else:
|
||||
w2=w2+',``'
|
||||
if w1!='' and w2!='':
|
||||
prompts.append(w2.replace('``', w1))
|
||||
pbar.update(1)
|
||||
@@ -119,69 +430,258 @@ class RandomPrompt:
|
||||
else:
|
||||
prompts = prompts[:min(max_count,len(prompts))]
|
||||
|
||||
prompts= [elem.strip() for elem in prompts if elem.strip()]
|
||||
|
||||
# return (new_prompt)
|
||||
return {"ui": {"prompts": prompts}, "result": (prompts,)}
|
||||
|
||||
|
||||
# class LoraPrompt:
|
||||
# @classmethod
|
||||
# def INPUT_TYPES(s):
|
||||
# return {
|
||||
# "required": {
|
||||
# "lora_name":(sorted(folder_paths.get_filename_list("loras"), key=str.lower),),
|
||||
# "weight": ("FLOAT", {"default": 1, "min": -2, "max": 2,"step":0.01 ,"display": "slider"}),
|
||||
# "force_update": ("BOOLEAN", {"default": False}),
|
||||
# },
|
||||
|
||||
# }
|
||||
|
||||
# RETURN_TYPES = ("STRING","STRING",any_type)
|
||||
# RETURN_NAMES = ("lora_name","prompt","tags",)
|
||||
|
||||
# FUNCTION = "run"
|
||||
|
||||
# CATEGORY = "♾️Mixlab/Prompt"
|
||||
|
||||
# OUTPUT_IS_LIST = (False,False,True,)
|
||||
# # OUTPUT_NODE = True
|
||||
|
||||
# # 运行的函数
|
||||
# def run(self,lora_name,weight,force_update=False):
|
||||
|
||||
# # print('##LoraPrompt',__file__)
|
||||
# # 从本地数据库读取
|
||||
# json_tags_path = os.path.join(os.path.dirname(os.path.dirname(__file__)),r'data/loras_tags.json')
|
||||
|
||||
# if not os.path.exists(json_tags_path):
|
||||
# save_json({},json_tags_path)
|
||||
|
||||
# lora_tags = load_json(json_tags_path)
|
||||
# output_tags = lora_tags.get(lora_name, None) if lora_tags is not None else None
|
||||
# if output_tags is not None:
|
||||
# output_tags = ",".join(output_tags)
|
||||
# print("trainedWords:",output_tags)
|
||||
# else:
|
||||
# output_tags = ""
|
||||
|
||||
|
||||
# lora_path = folder_paths.get_full_path("loras", lora_name)
|
||||
# if output_tags == "" or force_update:
|
||||
# print("calculating lora hash")
|
||||
# LORAsha256 = calculate_sha256(lora_path)
|
||||
# print("requesting infos")
|
||||
# model_info = get_model_version_info(LORAsha256)
|
||||
# if model_info is not None:
|
||||
# if "trainedWords" in model_info:
|
||||
# print("tags found!")
|
||||
# if lora_tags is None:
|
||||
# lora_tags = {}
|
||||
# lora_tags[lora_name] = model_info["trainedWords"]
|
||||
# save_json(lora_tags,json_tags_path)
|
||||
# output_tags = ",".join(model_info["trainedWords"])
|
||||
# print("trainedWords:",output_tags)
|
||||
# else:
|
||||
# print("No informations found.")
|
||||
# if lora_tags is None:
|
||||
# lora_tags = {}
|
||||
# lora_tags[lora_name] = []
|
||||
# save_json(lora_tags,json_tags_path)
|
||||
|
||||
|
||||
# weight = round(weight, 3)
|
||||
# prompt=[]
|
||||
# for p in output_tags.split(','):
|
||||
|
||||
# if weight!=1:
|
||||
# prompt.append('('+p+':'+str(weight)+')')
|
||||
# else:
|
||||
# prompt.append(p)
|
||||
|
||||
# prompt=",".join(prompt)
|
||||
|
||||
# return (lora_name,prompt,output_tags.split(','),)
|
||||
|
||||
|
||||
|
||||
class RunWorkflow:
|
||||
class EmbeddingPrompt:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
|
||||
return {
|
||||
"required": {
|
||||
"workflow": ("STRING", {
|
||||
"multiline": False,
|
||||
"default": ''
|
||||
}),
|
||||
"prompt": ("STRING", {
|
||||
"multiline": False,
|
||||
"default": ''
|
||||
}),
|
||||
"image": ("IMAGE",),
|
||||
"input_node": ("STRING", {
|
||||
"multiline": False,
|
||||
"default": ''
|
||||
}),
|
||||
"output_node": ("STRING", {
|
||||
"multiline": False,
|
||||
"default": ''
|
||||
}),
|
||||
"embedding":(folder_paths.get_filename_list("embeddings"),),
|
||||
"weight": ("FLOAT", {"default": 1, "min": -2, "max": 2,"step":0.01 ,"display": "slider"}),
|
||||
},
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
RETURN_TYPES = ("IMAGE","STRING",)
|
||||
RETURN_TYPES = ("STRING",)
|
||||
|
||||
FUNCTION = "run"
|
||||
|
||||
CATEGORY = "Mixlab/workflow"
|
||||
|
||||
OUTPUT_IS_LIST = (True,)
|
||||
OUTPUT_NODE = True
|
||||
CATEGORY = "♾️Mixlab/Prompt"
|
||||
|
||||
OUTPUT_IS_LIST = (False,)
|
||||
# OUTPUT_NODE = True
|
||||
|
||||
# 运行的函数
|
||||
def run(self,workflow,prompt,image,input_node,output_node):
|
||||
print('#运行的函数',prompt,image,input_node,output_node)
|
||||
workflow=json.loads(workflow)
|
||||
input_node=input_node.split(".")
|
||||
workflow[input_node[0]][input_node[1]][input_node[2]]=prompt
|
||||
|
||||
workflow_new={}
|
||||
# 遍历,seed设为随机
|
||||
for key, value in workflow.items():
|
||||
if 'inputs' in value:
|
||||
if 'seed' in value['inputs']:
|
||||
value['inputs']['seed']= random.randint(1, 18446744073709551614)
|
||||
workflow_new[key]=value
|
||||
|
||||
queue_prompt(workflow_new)
|
||||
print('#运行的函数',workflow_new[input_node[0]])
|
||||
|
||||
def run(self,embedding,weight):
|
||||
weight = round(weight, 3)
|
||||
prompt='embedding:'+embedding
|
||||
if weight!=1:
|
||||
prompt='('+prompt+':'+str(weight)+')'
|
||||
prompt=" "+prompt+' '
|
||||
# return (new_prompt)
|
||||
return {"ui":{"images": []},"result": ([image],['text'],)}
|
||||
return (prompt,)
|
||||
|
||||
# RETURN_TYPES = (any_type,)
|
||||
|
||||
# conditioning :提示,正向or负向
|
||||
# clip:clip模型
|
||||
# gligen_textbox_model:gligen模型
|
||||
# grids:矩形框的集合
|
||||
# labels:每个矩形框对应的标签的集合
|
||||
# index:选取第几个矩形框作为gligen的box
|
||||
|
||||
class GLIGENTextBoxApply_Advanced:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {"required": {"conditioning": ("CONDITIONING", ),
|
||||
"clip": ("CLIP", ),
|
||||
"gligen_textbox_model": ("GLIGEN", ),
|
||||
"grids": ("_GRID",),
|
||||
"labels": ("STRING",
|
||||
{
|
||||
"multiline": True,
|
||||
"default": "",
|
||||
"forceInput": True
|
||||
}),
|
||||
"index": ("INT", {"default": -1, "min": -1, "max": 300, "step": 1}),
|
||||
"max_size": ("INT", {"default": 8, "min": 1, "max": 300, "step": 1}),
|
||||
"random_shuffle":(["on","off"],),
|
||||
},
|
||||
"optional":{
|
||||
"seed": (any_type, {"default": 0, "min": 0, "max": 0xffffffffffffffff,"step": 1}),
|
||||
}
|
||||
}
|
||||
RETURN_TYPES = ("CONDITIONING","STRING",)
|
||||
RETURN_NAMES = ("CONDITIONING","label",)
|
||||
|
||||
FUNCTION = "run"
|
||||
# INPUT_IS_LIST = True
|
||||
CATEGORY = "♾️Mixlab/Prompt"
|
||||
|
||||
def run(self, conditioning, clip, gligen_textbox_model, grids, labels, index,max_size,random_shuffle,seed=0):
|
||||
# print('grids',grids)
|
||||
# conditioning=conditioning[0]
|
||||
# clip=clip[0]
|
||||
# gligen_textbox_model=gligen_textbox_model[0]
|
||||
# index=index[0]
|
||||
# max_size=max_size[0]
|
||||
# random_shuffle=random_shuffle[0]
|
||||
|
||||
texts=labels
|
||||
|
||||
if index>-1:
|
||||
texts=[labels[index]]
|
||||
grids=[grids[index]]
|
||||
|
||||
if random_shuffle=='on':
|
||||
sss=[[texts[i],grids[i]] for i in range(len(texts))]
|
||||
random.shuffle(sss)
|
||||
texts=[s[0] for s in sss]
|
||||
grids=[s[1] for s in sss]
|
||||
|
||||
if len(texts) > max_size:
|
||||
texts = texts[:max_size]
|
||||
|
||||
c = []
|
||||
|
||||
for t in conditioning:
|
||||
n = [t[0], t[1].copy()]
|
||||
|
||||
|
||||
# 多个
|
||||
position_params=[]
|
||||
for i in range(len(texts)):
|
||||
text=texts[i]
|
||||
grid=grids[i]
|
||||
x,y,width,height=grid
|
||||
# print(text)
|
||||
cond, cond_pooled = clip.encode_from_tokens(clip.tokenize(text), return_pooled=True)
|
||||
position_params =position_params+ [(cond_pooled, height // 8, width // 8, y // 8, x // 8)]
|
||||
|
||||
# 前一个
|
||||
prev = []
|
||||
if "gligen" in n[1]:
|
||||
prev = n[1]['gligen'][2]
|
||||
|
||||
n[1]['gligen'] = ("position", gligen_textbox_model, prev + position_params)
|
||||
# print('gligen',n)
|
||||
c.append(n)
|
||||
|
||||
# 下面这个写法有bug
|
||||
# for i in range(len(texts)):
|
||||
# text=texts[i]
|
||||
# grid=grids[i]
|
||||
# x,y,width,height=grid
|
||||
|
||||
# cond, cond_pooled = clip.encode_from_tokens(clip.tokenize(text), return_pooled=True)
|
||||
# for t in conditioning:
|
||||
# n = [t[0], t[1].copy()]
|
||||
# position_params = [(cond_pooled, height // 8, width // 8, y // 8, x // 8)]
|
||||
# prev = []
|
||||
# if "gligen" in n[1]:
|
||||
# prev = n[1]['gligen'][2]
|
||||
|
||||
# n[1]['gligen'] = ("position", gligen_textbox_model, prev + position_params)
|
||||
# c.append(n)
|
||||
|
||||
|
||||
return (c,texts, )
|
||||
|
||||
|
||||
class JoinWithDelimiter:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {"required": {
|
||||
"text_list": (any_type,),
|
||||
"delimiter":(["newline","comma","backslash","space"],),
|
||||
},
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("STRING",)
|
||||
|
||||
FUNCTION = "run"
|
||||
|
||||
CATEGORY = "♾️Mixlab/Text"
|
||||
|
||||
INPUT_IS_LIST = True # 当true的时候,输入时list,当false的时候,如果输入是list,则会自动包一层for循环调用
|
||||
OUTPUT_IS_LIST = (False,)
|
||||
|
||||
def run(self,text_list,delimiter):
|
||||
delimiter=delimiter[0]
|
||||
if delimiter =='newline':
|
||||
delimiter='\n'
|
||||
elif delimiter=='comma':
|
||||
delimiter=','
|
||||
elif delimiter=='backslash':
|
||||
delimiter='\\'
|
||||
elif delimiter=='space':
|
||||
delimiter=' '
|
||||
t=''
|
||||
if isinstance(text_list, list):
|
||||
t=join_with_(text_list,delimiter)
|
||||
return (t,)
|
||||
@@ -0,0 +1,694 @@
|
||||
import os,sys
|
||||
import folder_paths
|
||||
|
||||
from PIL import Image
|
||||
import importlib.util
|
||||
|
||||
import comfy.utils
|
||||
import numpy as np
|
||||
import torch
|
||||
|
||||
from huggingface_hub import hf_hub_download
|
||||
import torch.nn as nn
|
||||
import torch.nn.functional as F
|
||||
from torchvision.transforms.functional import normalize
|
||||
# BRIA-RMBG-1.4 / briarmbg.py
|
||||
class REBNCONV(nn.Module):
|
||||
def __init__(self,in_ch=3,out_ch=3,dirate=1,stride=1):
|
||||
super(REBNCONV,self).__init__()
|
||||
|
||||
self.conv_s1 = nn.Conv2d(in_ch,out_ch,3,padding=1*dirate,dilation=1*dirate,stride=stride)
|
||||
self.bn_s1 = nn.BatchNorm2d(out_ch)
|
||||
self.relu_s1 = nn.ReLU(inplace=True)
|
||||
|
||||
def forward(self,x):
|
||||
|
||||
hx = x
|
||||
xout = self.relu_s1(self.bn_s1(self.conv_s1(hx)))
|
||||
|
||||
return xout
|
||||
|
||||
## upsample tensor 'src' to have the same spatial size with tensor 'tar'
|
||||
def _upsample_like(src,tar):
|
||||
|
||||
src = F.interpolate(src,size=tar.shape[2:],mode='bilinear')
|
||||
|
||||
return src
|
||||
|
||||
|
||||
### RSU-7 ###
|
||||
class RSU7(nn.Module):
|
||||
|
||||
def __init__(self, in_ch=3, mid_ch=12, out_ch=3, img_size=512):
|
||||
super(RSU7,self).__init__()
|
||||
|
||||
self.in_ch = in_ch
|
||||
self.mid_ch = mid_ch
|
||||
self.out_ch = out_ch
|
||||
|
||||
self.rebnconvin = REBNCONV(in_ch,out_ch,dirate=1) ## 1 -> 1/2
|
||||
|
||||
self.rebnconv1 = REBNCONV(out_ch,mid_ch,dirate=1)
|
||||
self.pool1 = nn.MaxPool2d(2,stride=2,ceil_mode=True)
|
||||
|
||||
self.rebnconv2 = REBNCONV(mid_ch,mid_ch,dirate=1)
|
||||
self.pool2 = nn.MaxPool2d(2,stride=2,ceil_mode=True)
|
||||
|
||||
self.rebnconv3 = REBNCONV(mid_ch,mid_ch,dirate=1)
|
||||
self.pool3 = nn.MaxPool2d(2,stride=2,ceil_mode=True)
|
||||
|
||||
self.rebnconv4 = REBNCONV(mid_ch,mid_ch,dirate=1)
|
||||
self.pool4 = nn.MaxPool2d(2,stride=2,ceil_mode=True)
|
||||
|
||||
self.rebnconv5 = REBNCONV(mid_ch,mid_ch,dirate=1)
|
||||
self.pool5 = nn.MaxPool2d(2,stride=2,ceil_mode=True)
|
||||
|
||||
self.rebnconv6 = REBNCONV(mid_ch,mid_ch,dirate=1)
|
||||
|
||||
self.rebnconv7 = REBNCONV(mid_ch,mid_ch,dirate=2)
|
||||
|
||||
self.rebnconv6d = REBNCONV(mid_ch*2,mid_ch,dirate=1)
|
||||
self.rebnconv5d = REBNCONV(mid_ch*2,mid_ch,dirate=1)
|
||||
self.rebnconv4d = REBNCONV(mid_ch*2,mid_ch,dirate=1)
|
||||
self.rebnconv3d = REBNCONV(mid_ch*2,mid_ch,dirate=1)
|
||||
self.rebnconv2d = REBNCONV(mid_ch*2,mid_ch,dirate=1)
|
||||
self.rebnconv1d = REBNCONV(mid_ch*2,out_ch,dirate=1)
|
||||
|
||||
def forward(self,x):
|
||||
b, c, h, w = x.shape
|
||||
|
||||
hx = x
|
||||
hxin = self.rebnconvin(hx)
|
||||
|
||||
hx1 = self.rebnconv1(hxin)
|
||||
hx = self.pool1(hx1)
|
||||
|
||||
hx2 = self.rebnconv2(hx)
|
||||
hx = self.pool2(hx2)
|
||||
|
||||
hx3 = self.rebnconv3(hx)
|
||||
hx = self.pool3(hx3)
|
||||
|
||||
hx4 = self.rebnconv4(hx)
|
||||
hx = self.pool4(hx4)
|
||||
|
||||
hx5 = self.rebnconv5(hx)
|
||||
hx = self.pool5(hx5)
|
||||
|
||||
hx6 = self.rebnconv6(hx)
|
||||
|
||||
hx7 = self.rebnconv7(hx6)
|
||||
|
||||
hx6d = self.rebnconv6d(torch.cat((hx7,hx6),1))
|
||||
hx6dup = _upsample_like(hx6d,hx5)
|
||||
|
||||
hx5d = self.rebnconv5d(torch.cat((hx6dup,hx5),1))
|
||||
hx5dup = _upsample_like(hx5d,hx4)
|
||||
|
||||
hx4d = self.rebnconv4d(torch.cat((hx5dup,hx4),1))
|
||||
hx4dup = _upsample_like(hx4d,hx3)
|
||||
|
||||
hx3d = self.rebnconv3d(torch.cat((hx4dup,hx3),1))
|
||||
hx3dup = _upsample_like(hx3d,hx2)
|
||||
|
||||
hx2d = self.rebnconv2d(torch.cat((hx3dup,hx2),1))
|
||||
hx2dup = _upsample_like(hx2d,hx1)
|
||||
|
||||
hx1d = self.rebnconv1d(torch.cat((hx2dup,hx1),1))
|
||||
|
||||
return hx1d + hxin
|
||||
|
||||
|
||||
### RSU-6 ###
|
||||
class RSU6(nn.Module):
|
||||
|
||||
def __init__(self, in_ch=3, mid_ch=12, out_ch=3):
|
||||
super(RSU6,self).__init__()
|
||||
|
||||
self.rebnconvin = REBNCONV(in_ch,out_ch,dirate=1)
|
||||
|
||||
self.rebnconv1 = REBNCONV(out_ch,mid_ch,dirate=1)
|
||||
self.pool1 = nn.MaxPool2d(2,stride=2,ceil_mode=True)
|
||||
|
||||
self.rebnconv2 = REBNCONV(mid_ch,mid_ch,dirate=1)
|
||||
self.pool2 = nn.MaxPool2d(2,stride=2,ceil_mode=True)
|
||||
|
||||
self.rebnconv3 = REBNCONV(mid_ch,mid_ch,dirate=1)
|
||||
self.pool3 = nn.MaxPool2d(2,stride=2,ceil_mode=True)
|
||||
|
||||
self.rebnconv4 = REBNCONV(mid_ch,mid_ch,dirate=1)
|
||||
self.pool4 = nn.MaxPool2d(2,stride=2,ceil_mode=True)
|
||||
|
||||
self.rebnconv5 = REBNCONV(mid_ch,mid_ch,dirate=1)
|
||||
|
||||
self.rebnconv6 = REBNCONV(mid_ch,mid_ch,dirate=2)
|
||||
|
||||
self.rebnconv5d = REBNCONV(mid_ch*2,mid_ch,dirate=1)
|
||||
self.rebnconv4d = REBNCONV(mid_ch*2,mid_ch,dirate=1)
|
||||
self.rebnconv3d = REBNCONV(mid_ch*2,mid_ch,dirate=1)
|
||||
self.rebnconv2d = REBNCONV(mid_ch*2,mid_ch,dirate=1)
|
||||
self.rebnconv1d = REBNCONV(mid_ch*2,out_ch,dirate=1)
|
||||
|
||||
def forward(self,x):
|
||||
|
||||
hx = x
|
||||
|
||||
hxin = self.rebnconvin(hx)
|
||||
|
||||
hx1 = self.rebnconv1(hxin)
|
||||
hx = self.pool1(hx1)
|
||||
|
||||
hx2 = self.rebnconv2(hx)
|
||||
hx = self.pool2(hx2)
|
||||
|
||||
hx3 = self.rebnconv3(hx)
|
||||
hx = self.pool3(hx3)
|
||||
|
||||
hx4 = self.rebnconv4(hx)
|
||||
hx = self.pool4(hx4)
|
||||
|
||||
hx5 = self.rebnconv5(hx)
|
||||
|
||||
hx6 = self.rebnconv6(hx5)
|
||||
|
||||
|
||||
hx5d = self.rebnconv5d(torch.cat((hx6,hx5),1))
|
||||
hx5dup = _upsample_like(hx5d,hx4)
|
||||
|
||||
hx4d = self.rebnconv4d(torch.cat((hx5dup,hx4),1))
|
||||
hx4dup = _upsample_like(hx4d,hx3)
|
||||
|
||||
hx3d = self.rebnconv3d(torch.cat((hx4dup,hx3),1))
|
||||
hx3dup = _upsample_like(hx3d,hx2)
|
||||
|
||||
hx2d = self.rebnconv2d(torch.cat((hx3dup,hx2),1))
|
||||
hx2dup = _upsample_like(hx2d,hx1)
|
||||
|
||||
hx1d = self.rebnconv1d(torch.cat((hx2dup,hx1),1))
|
||||
|
||||
return hx1d + hxin
|
||||
|
||||
### RSU-5 ###
|
||||
class RSU5(nn.Module):
|
||||
|
||||
def __init__(self, in_ch=3, mid_ch=12, out_ch=3):
|
||||
super(RSU5,self).__init__()
|
||||
|
||||
self.rebnconvin = REBNCONV(in_ch,out_ch,dirate=1)
|
||||
|
||||
self.rebnconv1 = REBNCONV(out_ch,mid_ch,dirate=1)
|
||||
self.pool1 = nn.MaxPool2d(2,stride=2,ceil_mode=True)
|
||||
|
||||
self.rebnconv2 = REBNCONV(mid_ch,mid_ch,dirate=1)
|
||||
self.pool2 = nn.MaxPool2d(2,stride=2,ceil_mode=True)
|
||||
|
||||
self.rebnconv3 = REBNCONV(mid_ch,mid_ch,dirate=1)
|
||||
self.pool3 = nn.MaxPool2d(2,stride=2,ceil_mode=True)
|
||||
|
||||
self.rebnconv4 = REBNCONV(mid_ch,mid_ch,dirate=1)
|
||||
|
||||
self.rebnconv5 = REBNCONV(mid_ch,mid_ch,dirate=2)
|
||||
|
||||
self.rebnconv4d = REBNCONV(mid_ch*2,mid_ch,dirate=1)
|
||||
self.rebnconv3d = REBNCONV(mid_ch*2,mid_ch,dirate=1)
|
||||
self.rebnconv2d = REBNCONV(mid_ch*2,mid_ch,dirate=1)
|
||||
self.rebnconv1d = REBNCONV(mid_ch*2,out_ch,dirate=1)
|
||||
|
||||
def forward(self,x):
|
||||
|
||||
hx = x
|
||||
|
||||
hxin = self.rebnconvin(hx)
|
||||
|
||||
hx1 = self.rebnconv1(hxin)
|
||||
hx = self.pool1(hx1)
|
||||
|
||||
hx2 = self.rebnconv2(hx)
|
||||
hx = self.pool2(hx2)
|
||||
|
||||
hx3 = self.rebnconv3(hx)
|
||||
hx = self.pool3(hx3)
|
||||
|
||||
hx4 = self.rebnconv4(hx)
|
||||
|
||||
hx5 = self.rebnconv5(hx4)
|
||||
|
||||
hx4d = self.rebnconv4d(torch.cat((hx5,hx4),1))
|
||||
hx4dup = _upsample_like(hx4d,hx3)
|
||||
|
||||
hx3d = self.rebnconv3d(torch.cat((hx4dup,hx3),1))
|
||||
hx3dup = _upsample_like(hx3d,hx2)
|
||||
|
||||
hx2d = self.rebnconv2d(torch.cat((hx3dup,hx2),1))
|
||||
hx2dup = _upsample_like(hx2d,hx1)
|
||||
|
||||
hx1d = self.rebnconv1d(torch.cat((hx2dup,hx1),1))
|
||||
|
||||
return hx1d + hxin
|
||||
|
||||
### RSU-4 ###
|
||||
class RSU4(nn.Module):
|
||||
|
||||
def __init__(self, in_ch=3, mid_ch=12, out_ch=3):
|
||||
super(RSU4,self).__init__()
|
||||
|
||||
self.rebnconvin = REBNCONV(in_ch,out_ch,dirate=1)
|
||||
|
||||
self.rebnconv1 = REBNCONV(out_ch,mid_ch,dirate=1)
|
||||
self.pool1 = nn.MaxPool2d(2,stride=2,ceil_mode=True)
|
||||
|
||||
self.rebnconv2 = REBNCONV(mid_ch,mid_ch,dirate=1)
|
||||
self.pool2 = nn.MaxPool2d(2,stride=2,ceil_mode=True)
|
||||
|
||||
self.rebnconv3 = REBNCONV(mid_ch,mid_ch,dirate=1)
|
||||
|
||||
self.rebnconv4 = REBNCONV(mid_ch,mid_ch,dirate=2)
|
||||
|
||||
self.rebnconv3d = REBNCONV(mid_ch*2,mid_ch,dirate=1)
|
||||
self.rebnconv2d = REBNCONV(mid_ch*2,mid_ch,dirate=1)
|
||||
self.rebnconv1d = REBNCONV(mid_ch*2,out_ch,dirate=1)
|
||||
|
||||
def forward(self,x):
|
||||
|
||||
hx = x
|
||||
|
||||
hxin = self.rebnconvin(hx)
|
||||
|
||||
hx1 = self.rebnconv1(hxin)
|
||||
hx = self.pool1(hx1)
|
||||
|
||||
hx2 = self.rebnconv2(hx)
|
||||
hx = self.pool2(hx2)
|
||||
|
||||
hx3 = self.rebnconv3(hx)
|
||||
|
||||
hx4 = self.rebnconv4(hx3)
|
||||
|
||||
hx3d = self.rebnconv3d(torch.cat((hx4,hx3),1))
|
||||
hx3dup = _upsample_like(hx3d,hx2)
|
||||
|
||||
hx2d = self.rebnconv2d(torch.cat((hx3dup,hx2),1))
|
||||
hx2dup = _upsample_like(hx2d,hx1)
|
||||
|
||||
hx1d = self.rebnconv1d(torch.cat((hx2dup,hx1),1))
|
||||
|
||||
return hx1d + hxin
|
||||
|
||||
### RSU-4F ###
|
||||
class RSU4F(nn.Module):
|
||||
|
||||
def __init__(self, in_ch=3, mid_ch=12, out_ch=3):
|
||||
super(RSU4F,self).__init__()
|
||||
|
||||
self.rebnconvin = REBNCONV(in_ch,out_ch,dirate=1)
|
||||
|
||||
self.rebnconv1 = REBNCONV(out_ch,mid_ch,dirate=1)
|
||||
self.rebnconv2 = REBNCONV(mid_ch,mid_ch,dirate=2)
|
||||
self.rebnconv3 = REBNCONV(mid_ch,mid_ch,dirate=4)
|
||||
|
||||
self.rebnconv4 = REBNCONV(mid_ch,mid_ch,dirate=8)
|
||||
|
||||
self.rebnconv3d = REBNCONV(mid_ch*2,mid_ch,dirate=4)
|
||||
self.rebnconv2d = REBNCONV(mid_ch*2,mid_ch,dirate=2)
|
||||
self.rebnconv1d = REBNCONV(mid_ch*2,out_ch,dirate=1)
|
||||
|
||||
def forward(self,x):
|
||||
|
||||
hx = x
|
||||
|
||||
hxin = self.rebnconvin(hx)
|
||||
|
||||
hx1 = self.rebnconv1(hxin)
|
||||
hx2 = self.rebnconv2(hx1)
|
||||
hx3 = self.rebnconv3(hx2)
|
||||
|
||||
hx4 = self.rebnconv4(hx3)
|
||||
|
||||
hx3d = self.rebnconv3d(torch.cat((hx4,hx3),1))
|
||||
hx2d = self.rebnconv2d(torch.cat((hx3d,hx2),1))
|
||||
hx1d = self.rebnconv1d(torch.cat((hx2d,hx1),1))
|
||||
|
||||
return hx1d + hxin
|
||||
|
||||
|
||||
class myrebnconv(nn.Module):
|
||||
def __init__(self, in_ch=3,
|
||||
out_ch=1,
|
||||
kernel_size=3,
|
||||
stride=1,
|
||||
padding=1,
|
||||
dilation=1,
|
||||
groups=1):
|
||||
super(myrebnconv,self).__init__()
|
||||
|
||||
self.conv = nn.Conv2d(in_ch,
|
||||
out_ch,
|
||||
kernel_size=kernel_size,
|
||||
stride=stride,
|
||||
padding=padding,
|
||||
dilation=dilation,
|
||||
groups=groups)
|
||||
self.bn = nn.BatchNorm2d(out_ch)
|
||||
self.rl = nn.ReLU(inplace=True)
|
||||
|
||||
def forward(self,x):
|
||||
return self.rl(self.bn(self.conv(x)))
|
||||
|
||||
|
||||
class BriaRMBG(nn.Module):
|
||||
|
||||
def __init__(self,in_ch=3,out_ch=1):
|
||||
super(BriaRMBG,self).__init__()
|
||||
|
||||
self.conv_in = nn.Conv2d(in_ch,64,3,stride=2,padding=1)
|
||||
self.pool_in = nn.MaxPool2d(2,stride=2,ceil_mode=True)
|
||||
|
||||
self.stage1 = RSU7(64,32,64)
|
||||
self.pool12 = nn.MaxPool2d(2,stride=2,ceil_mode=True)
|
||||
|
||||
self.stage2 = RSU6(64,32,128)
|
||||
self.pool23 = nn.MaxPool2d(2,stride=2,ceil_mode=True)
|
||||
|
||||
self.stage3 = RSU5(128,64,256)
|
||||
self.pool34 = nn.MaxPool2d(2,stride=2,ceil_mode=True)
|
||||
|
||||
self.stage4 = RSU4(256,128,512)
|
||||
self.pool45 = nn.MaxPool2d(2,stride=2,ceil_mode=True)
|
||||
|
||||
self.stage5 = RSU4F(512,256,512)
|
||||
self.pool56 = nn.MaxPool2d(2,stride=2,ceil_mode=True)
|
||||
|
||||
self.stage6 = RSU4F(512,256,512)
|
||||
|
||||
# decoder
|
||||
self.stage5d = RSU4F(1024,256,512)
|
||||
self.stage4d = RSU4(1024,128,256)
|
||||
self.stage3d = RSU5(512,64,128)
|
||||
self.stage2d = RSU6(256,32,64)
|
||||
self.stage1d = RSU7(128,16,64)
|
||||
|
||||
self.side1 = nn.Conv2d(64,out_ch,3,padding=1)
|
||||
self.side2 = nn.Conv2d(64,out_ch,3,padding=1)
|
||||
self.side3 = nn.Conv2d(128,out_ch,3,padding=1)
|
||||
self.side4 = nn.Conv2d(256,out_ch,3,padding=1)
|
||||
self.side5 = nn.Conv2d(512,out_ch,3,padding=1)
|
||||
self.side6 = nn.Conv2d(512,out_ch,3,padding=1)
|
||||
|
||||
# self.outconv = nn.Conv2d(6*out_ch,out_ch,1)
|
||||
|
||||
def forward(self,x):
|
||||
|
||||
hx = x
|
||||
|
||||
hxin = self.conv_in(hx)
|
||||
#hx = self.pool_in(hxin)
|
||||
|
||||
#stage 1
|
||||
hx1 = self.stage1(hxin)
|
||||
hx = self.pool12(hx1)
|
||||
|
||||
#stage 2
|
||||
hx2 = self.stage2(hx)
|
||||
hx = self.pool23(hx2)
|
||||
|
||||
#stage 3
|
||||
hx3 = self.stage3(hx)
|
||||
hx = self.pool34(hx3)
|
||||
|
||||
#stage 4
|
||||
hx4 = self.stage4(hx)
|
||||
hx = self.pool45(hx4)
|
||||
|
||||
#stage 5
|
||||
hx5 = self.stage5(hx)
|
||||
hx = self.pool56(hx5)
|
||||
|
||||
#stage 6
|
||||
hx6 = self.stage6(hx)
|
||||
hx6up = _upsample_like(hx6,hx5)
|
||||
|
||||
#-------------------- decoder --------------------
|
||||
hx5d = self.stage5d(torch.cat((hx6up,hx5),1))
|
||||
hx5dup = _upsample_like(hx5d,hx4)
|
||||
|
||||
hx4d = self.stage4d(torch.cat((hx5dup,hx4),1))
|
||||
hx4dup = _upsample_like(hx4d,hx3)
|
||||
|
||||
hx3d = self.stage3d(torch.cat((hx4dup,hx3),1))
|
||||
hx3dup = _upsample_like(hx3d,hx2)
|
||||
|
||||
hx2d = self.stage2d(torch.cat((hx3dup,hx2),1))
|
||||
hx2dup = _upsample_like(hx2d,hx1)
|
||||
|
||||
hx1d = self.stage1d(torch.cat((hx2dup,hx1),1))
|
||||
|
||||
|
||||
#side output
|
||||
d1 = self.side1(hx1d)
|
||||
d1 = _upsample_like(d1,x)
|
||||
|
||||
d2 = self.side2(hx2d)
|
||||
d2 = _upsample_like(d2,x)
|
||||
|
||||
d3 = self.side3(hx3d)
|
||||
d3 = _upsample_like(d3,x)
|
||||
|
||||
d4 = self.side4(hx4d)
|
||||
d4 = _upsample_like(d4,x)
|
||||
|
||||
d5 = self.side5(hx5d)
|
||||
d5 = _upsample_like(d5,x)
|
||||
|
||||
d6 = self.side6(hx6)
|
||||
d6 = _upsample_like(d6,x)
|
||||
|
||||
return [F.sigmoid(d1), F.sigmoid(d2), F.sigmoid(d3), F.sigmoid(d4), F.sigmoid(d5), F.sigmoid(d6)],[hx1d,hx2d,hx3d,hx4d,hx5d,hx6]
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
U2NET_HOME=os.path.join(folder_paths.models_dir, "rembg")
|
||||
os.environ["U2NET_HOME"] = U2NET_HOME
|
||||
|
||||
global _available
|
||||
_available=False
|
||||
|
||||
def is_installed(package):
|
||||
try:
|
||||
spec = importlib.util.find_spec(package)
|
||||
except ModuleNotFoundError:
|
||||
return False
|
||||
return spec is not None
|
||||
|
||||
|
||||
try:
|
||||
if is_installed('rembg')==False:
|
||||
import subprocess
|
||||
|
||||
# 安装
|
||||
print('#pip install rembg[gpu]')
|
||||
|
||||
result = subprocess.run([sys.executable, '-s', '-m', 'pip', 'install', 'rembg[gpu]'], capture_output=True, text=True)
|
||||
|
||||
#检查命令执行结果
|
||||
if result.returncode == 0:
|
||||
print("#install success")
|
||||
from rembg import new_session, remove
|
||||
_available=True
|
||||
else:
|
||||
print("#install error")
|
||||
|
||||
else:
|
||||
from rembg import new_session, remove
|
||||
_available=True
|
||||
|
||||
except:
|
||||
_available=False
|
||||
|
||||
|
||||
def briarmbg_run(images=[]):
|
||||
mroot=os.path.join(folder_paths.models_dir, "rembg")
|
||||
m=os.path.join(mroot,'briarmbg.pth')
|
||||
if os.path.exists(m)==False:
|
||||
# 下载
|
||||
m1=hf_hub_download("briaai/RMBG-1.4",
|
||||
local_dir=mroot,
|
||||
filename='model.pth',
|
||||
local_dir_use_symlinks=False,
|
||||
endpoint='https://hf-mirror.com')
|
||||
os.rename(m1, m)
|
||||
|
||||
net=BriaRMBG()
|
||||
if torch.cuda.is_available():
|
||||
net.load_state_dict(torch.load(m))
|
||||
net=net.cuda()
|
||||
else:
|
||||
net.load_state_dict(torch.load(m,map_location="cpu"))
|
||||
net.eval()
|
||||
|
||||
masks=[]
|
||||
rgba_images=[]
|
||||
rgb_images=[]
|
||||
for orig_image in images:
|
||||
|
||||
w,h = orig_im_size = orig_image.size
|
||||
|
||||
image = orig_image.convert('RGB')
|
||||
model_input_size = (1024, 1024)
|
||||
image = image.resize(model_input_size, Image.BILINEAR)
|
||||
|
||||
im_np = np.array(image)
|
||||
im_tensor = torch.tensor(im_np, dtype=torch.float32).permute(2,0,1)
|
||||
im_tensor = torch.unsqueeze(im_tensor,0)
|
||||
im_tensor = torch.divide(im_tensor,255.0)
|
||||
im_tensor = normalize(im_tensor,[0.5,0.5,0.5],[1.0,1.0,1.0])
|
||||
if torch.cuda.is_available():
|
||||
im_tensor=im_tensor.cuda()
|
||||
|
||||
result=net(im_tensor)
|
||||
result = torch.squeeze(F.interpolate(result[0][0], size=(h,w), mode='bilinear') ,0)
|
||||
ma = torch.max(result)
|
||||
mi = torch.min(result)
|
||||
result = (result-mi)/(ma-mi)
|
||||
im_array = (result*255).cpu().data.numpy().astype(np.uint8)
|
||||
mask = Image.fromarray(np.squeeze(im_array))
|
||||
# mask.save('test.png')
|
||||
# mask=tensor2pil(result)
|
||||
mask=mask.convert('L')
|
||||
|
||||
masks.append(mask)
|
||||
|
||||
# rgba图
|
||||
image_rgba =orig_image.convert("RGBA")
|
||||
image_rgba.putalpha(mask)
|
||||
rgba_images.append(image_rgba)
|
||||
|
||||
#rgb
|
||||
rgb_image = Image.new("RGB", image_rgba.size, (0, 0, 0))
|
||||
rgb_image.paste(image_rgba, mask=image_rgba.split()[3])
|
||||
rgb_images.append(rgb_image)
|
||||
return (masks,rgba_images,rgb_images)
|
||||
|
||||
|
||||
def run_bg(model_name= "unet",images=[]):
|
||||
# model_name = "unet" # "isnet-general-use"
|
||||
rembg_session = new_session(model_name)
|
||||
masks=[]
|
||||
rgba_images=[]
|
||||
rgb_images=[]
|
||||
# 进度条
|
||||
pbar = comfy.utils.ProgressBar(len(images) )
|
||||
for img in images:
|
||||
# use the post_process_mask argument to post process the mask to get better results.
|
||||
mask = remove(img, session=rembg_session,only_mask=True,post_process_mask=True)
|
||||
# mask=mask.convert('L')
|
||||
# masks.append(mask)
|
||||
if model_name=="u2net_cloth_seg":
|
||||
width, original_height = mask.size
|
||||
num_slices = original_height // img.height
|
||||
for i in range(num_slices):
|
||||
top = i * img.height
|
||||
bottom = (i + 1) * img.height
|
||||
slice_image = mask.crop((0, top, width, bottom))
|
||||
slice_mask=slice_image.convert('L')
|
||||
masks.append(slice_mask)
|
||||
|
||||
# rgba图
|
||||
image_rgba = img.convert("RGBA")
|
||||
image_rgba.putalpha(slice_mask)
|
||||
rgba_images.append(image_rgba)
|
||||
|
||||
#rgb
|
||||
rgb_image = Image.new("RGB", image_rgba.size, (0, 0, 0))
|
||||
rgb_image.paste(image_rgba, mask=image_rgba.split()[3])
|
||||
rgb_images.append(rgb_image)
|
||||
|
||||
else:
|
||||
mask=mask.convert('L')
|
||||
# mask.save(output_path)
|
||||
masks.append(mask)
|
||||
|
||||
# rgba图
|
||||
image_rgba = img.convert("RGBA")
|
||||
image_rgba.putalpha(mask)
|
||||
rgba_images.append(image_rgba)
|
||||
|
||||
#rgb
|
||||
rgb_image = Image.new("RGB", image_rgba.size, (0, 0, 0))
|
||||
rgb_image.paste(image_rgba, mask=image_rgba.split()[3])
|
||||
rgb_images.append(rgb_image)
|
||||
|
||||
pbar.update(1)
|
||||
return (masks,rgba_images,rgb_images)
|
||||
|
||||
|
||||
# Tensor to PIL
|
||||
def tensor2pil(image):
|
||||
return Image.fromarray(np.clip(255. * image.cpu().numpy().squeeze(), 0, 255).astype(np.uint8))
|
||||
|
||||
# Convert PIL to Tensor
|
||||
def pil2tensor(image):
|
||||
return torch.from_numpy(np.array(image).astype(np.float32) / 255.0).unsqueeze(0)
|
||||
|
||||
|
||||
class RembgNode_:
|
||||
|
||||
global _available
|
||||
available=_available
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {"required": {
|
||||
"image": ("IMAGE",),
|
||||
"model_name": ([
|
||||
"briarmbg",
|
||||
"u2net",
|
||||
"u2netp",
|
||||
"u2net_human_seg",
|
||||
"u2net_cloth_seg",
|
||||
"silueta",
|
||||
"isnet-general-use",
|
||||
"isnet-anime",
|
||||
|
||||
],),
|
||||
|
||||
},
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("MASK","IMAGE","RGBA",)
|
||||
RETURN_NAMES = ("masks","images","RGBAs")
|
||||
|
||||
FUNCTION = "run"
|
||||
|
||||
CATEGORY = "♾️Mixlab/Mask"
|
||||
OUTPUT_NODE = True
|
||||
INPUT_IS_LIST = True
|
||||
OUTPUT_IS_LIST = (True,True,True,)
|
||||
|
||||
def run(self,image,model_name):
|
||||
# 兼容list输入和batch输入
|
||||
|
||||
model_name=model_name[0]
|
||||
|
||||
images=[]
|
||||
|
||||
for ims in image:
|
||||
for im in ims:
|
||||
im=tensor2pil(im)
|
||||
images.append(im)
|
||||
|
||||
if model_name=='briarmbg':
|
||||
masks,rgba_images,rgb_images=briarmbg_run(images)
|
||||
else:
|
||||
masks,rgba_images,rgb_images=run_bg(model_name,images)
|
||||
|
||||
masks=[pil2tensor(m) for m in masks]
|
||||
|
||||
rgba_images=[pil2tensor(m) for m in rgba_images]
|
||||
|
||||
rgb_images=[pil2tensor(m) for m in rgb_images]
|
||||
|
||||
return (masks,rgb_images,rgba_images,)
|
||||
@@ -79,27 +79,31 @@ class ScreenShareNode:
|
||||
def INPUT_TYPES(s):
|
||||
return { "required":{
|
||||
"image_base64": ("CHEESE",),
|
||||
"refresh_rate": ("INT", {"default": 500, "min": 0,"step": 50, "max": 0xffffffffffffffff}),
|
||||
},
|
||||
"optional":{
|
||||
"prompt": ("PROMPT",),
|
||||
"slide": ("SLIDE",),
|
||||
"seed": ("SEED",),
|
||||
|
||||
# "seed": ("INT", {"default": 1, "min": 0, "max": 0xffffffffffffffff}),
|
||||
} }
|
||||
|
||||
RETURN_TYPES = ('IMAGE','STRING')
|
||||
|
||||
RETURN_TYPES = ('IMAGE','STRING','FLOAT',"INT")
|
||||
RETURN_NAMES = ("IMAGE","PROMPT","FLOAT","INT")
|
||||
FUNCTION = "run"
|
||||
|
||||
CATEGORY = "Mixlab/image"
|
||||
CATEGORY = "♾️Mixlab/Screen"
|
||||
|
||||
# INPUT_IS_LIST = True
|
||||
OUTPUT_IS_LIST = (False,False,False)
|
||||
OUTPUT_IS_LIST = (False,False,False,False)
|
||||
|
||||
# 运行的函数
|
||||
def run(self,image_base64,prompt):
|
||||
def run(self,image_base64,refresh_rate ,prompt,slide,seed):
|
||||
im,mask=base64_save(image_base64)
|
||||
# print('##########prompt',prompt)
|
||||
return (im,prompt)
|
||||
|
||||
return {"ui":{"refresh_rate": [refresh_rate]},"result": (im,prompt,slide,seed,)}
|
||||
|
||||
|
||||
class FloatingVideo:
|
||||
@classmethod
|
||||
@@ -114,7 +118,7 @@ class FloatingVideo:
|
||||
OUTPUT_NODE = True
|
||||
FUNCTION = "run"
|
||||
|
||||
CATEGORY = "Mixlab/image"
|
||||
CATEGORY = "♾️Mixlab/Screen"
|
||||
|
||||
# INPUT_IS_LIST = True
|
||||
# OUTPUT_IS_LIST = (False,False,)
|
||||
@@ -137,3 +141,15 @@ class FloatingVideo:
|
||||
|
||||
return { "ui": { "images_": results } }
|
||||
|
||||
|
||||
|
||||
# class SildeNode:
|
||||
# CATEGORY = "quicknodes"
|
||||
# @classmethod
|
||||
# def INPUT_TYPES(s):
|
||||
# return { "required":{} }
|
||||
# RETURN_TYPES = ()
|
||||
# RETURN_NAMES = ()
|
||||
# FUNCTION = "func"
|
||||
# def func(self):
|
||||
# return ()
|
||||
@@ -0,0 +1,503 @@
|
||||
import comfy
|
||||
import torch
|
||||
|
||||
from dataclasses import dataclass
|
||||
import torch.nn as nn
|
||||
from comfy.model_patcher import ModelPatcher
|
||||
import comfy.ops
|
||||
from typing import Union
|
||||
import comfy.sample
|
||||
import latent_preview
|
||||
import comfy.utils
|
||||
|
||||
T = torch.Tensor
|
||||
|
||||
|
||||
from .VisualStylePrompting.attention_functions import VisualStyleProcessor
|
||||
|
||||
class ApplyVisualStylePrompting:
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {
|
||||
"required": {
|
||||
"reference_image": ("IMAGE",),
|
||||
"reference_image_text": ("STRING", {"multiline": True}),
|
||||
"model": ("MODEL",),
|
||||
"clip": ("CLIP", ),
|
||||
"vae": ("VAE", ),
|
||||
"positive": ("CONDITIONING",),
|
||||
"negative": ("CONDITIONING", ),
|
||||
"enabled": ("BOOLEAN", {"default": True}),
|
||||
"denoise": ("FLOAT", {"default": 1., "min": 0., "max": 1., "step": 1e-2}),
|
||||
"batch_size": ("INT", {"default": 1, "min": 1, "max": 4096,"step":2})
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("MODEL", "CONDITIONING","CONDITIONING", "LATENT")
|
||||
RETURN_NAMES = ("model", "positive", "negative", "latents")
|
||||
|
||||
CATEGORY = "♾️Mixlab/Style"
|
||||
|
||||
FUNCTION = "run"
|
||||
|
||||
def run(
|
||||
self,
|
||||
reference_image,
|
||||
reference_image_text,
|
||||
model: comfy.model_patcher.ModelPatcher,
|
||||
clip,
|
||||
vae,
|
||||
positive,
|
||||
negative,
|
||||
enabled,
|
||||
denoise,
|
||||
batch_size=1
|
||||
):
|
||||
|
||||
tokens = clip.tokenize(reference_image_text)
|
||||
cond, pooled = clip.encode_from_tokens(tokens, return_pooled=True)
|
||||
reference_image_prompt=[[cond, {"pooled_output": pooled}]]
|
||||
|
||||
reference_image = reference_image.repeat(((batch_size+1)//2, 1,1,1))
|
||||
|
||||
self.model = model
|
||||
reference_latent = vae.encode(reference_image[:,:,:,:3])
|
||||
|
||||
for n, m in model.model.diffusion_model.named_modules():
|
||||
if m.__class__.__name__ == "CrossAttention":
|
||||
processor = VisualStyleProcessor(m, enabled=enabled)
|
||||
setattr(m, 'forward', processor.visual_style_forward)
|
||||
|
||||
conditioning_prompt = reference_image_prompt + positive
|
||||
negative_prompt = negative * 2
|
||||
|
||||
latents = torch.zeros_like(reference_latent)
|
||||
latents = torch.cat([latents] * 2)
|
||||
|
||||
if denoise < 1.0:
|
||||
latents[::1] = reference_latent[:1]
|
||||
else:
|
||||
latents[::2] = reference_latent
|
||||
|
||||
denoise_mask = torch.ones_like(latents)[:, :1, ...] * denoise
|
||||
|
||||
denoise_mask[0] = 0.
|
||||
|
||||
return (model, conditioning_prompt, negative_prompt, {"samples": latents, "noise_mask": denoise_mask})
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
def exists(val):
|
||||
return val is not None
|
||||
|
||||
def default(val, d):
|
||||
if exists(val):
|
||||
return val
|
||||
return d
|
||||
|
||||
|
||||
class StyleAlignedArgs:
|
||||
def __init__(self, share_attn: str) -> None:
|
||||
self.adain_keys = "k" in share_attn
|
||||
self.adain_values = "v" in share_attn
|
||||
self.adain_queries = "q" in share_attn
|
||||
|
||||
share_attention: bool = True
|
||||
adain_queries: bool = True
|
||||
adain_keys: bool = True
|
||||
adain_values: bool = True
|
||||
|
||||
|
||||
def expand_first(
|
||||
feat: T,
|
||||
scale=1.0,
|
||||
) -> T:
|
||||
"""
|
||||
Expand the first element so it has the same shape as the rest of the batch.
|
||||
"""
|
||||
b = feat.shape[0]
|
||||
feat_style = torch.stack((feat[0], feat[b // 2])).unsqueeze(1)
|
||||
if scale == 1:
|
||||
feat_style = feat_style.expand(2, b // 2, *feat.shape[1:])
|
||||
else:
|
||||
feat_style = feat_style.repeat(1, b // 2, 1, 1, 1)
|
||||
feat_style = torch.cat([feat_style[:, :1], scale * feat_style[:, 1:]], dim=1)
|
||||
return feat_style.reshape(*feat.shape)
|
||||
|
||||
|
||||
def concat_first(feat: T, dim=2, scale=1.0) -> T:
|
||||
"""
|
||||
concat the the feature and the style feature expanded above
|
||||
"""
|
||||
feat_style = expand_first(feat, scale=scale)
|
||||
return torch.cat((feat, feat_style), dim=dim)
|
||||
|
||||
|
||||
def calc_mean_std(feat, eps: float = 1e-5) -> "tuple[T, T]":
|
||||
feat_std = (feat.var(dim=-2, keepdims=True) + eps).sqrt()
|
||||
feat_mean = feat.mean(dim=-2, keepdims=True)
|
||||
return feat_mean, feat_std
|
||||
|
||||
def adain(feat: T) -> T:
|
||||
feat_mean, feat_std = calc_mean_std(feat)
|
||||
feat_style_mean = expand_first(feat_mean)
|
||||
feat_style_std = expand_first(feat_std)
|
||||
feat = (feat - feat_mean) / feat_std
|
||||
feat = feat * feat_style_std + feat_style_mean
|
||||
return feat
|
||||
|
||||
class SharedAttentionProcessor:
|
||||
def __init__(self, args: StyleAlignedArgs, scale: float):
|
||||
self.args = args
|
||||
self.scale = scale
|
||||
|
||||
def __call__(self, q, k, v, extra_options):
|
||||
if self.args.adain_queries:
|
||||
q = adain(q)
|
||||
if self.args.adain_keys:
|
||||
k = adain(k)
|
||||
if self.args.adain_values:
|
||||
v = adain(v)
|
||||
if self.args.share_attention:
|
||||
k = concat_first(k, -2, scale=self.scale)
|
||||
v = concat_first(v, -2)
|
||||
|
||||
return q, k, v
|
||||
|
||||
|
||||
def get_norm_layers(
|
||||
layer: nn.Module,
|
||||
norm_layers_: "dict[str, list[Union[nn.GroupNorm, nn.LayerNorm]]]",
|
||||
share_layer_norm: bool,
|
||||
share_group_norm: bool,
|
||||
):
|
||||
if isinstance(layer, nn.LayerNorm) and share_layer_norm:
|
||||
norm_layers_["layer"].append(layer)
|
||||
if isinstance(layer, nn.GroupNorm) and share_group_norm:
|
||||
norm_layers_["group"].append(layer)
|
||||
else:
|
||||
for child_layer in layer.children():
|
||||
get_norm_layers(
|
||||
child_layer, norm_layers_, share_layer_norm, share_group_norm
|
||||
)
|
||||
|
||||
|
||||
def register_norm_forward(
|
||||
norm_layer: Union[nn.GroupNorm, nn.LayerNorm],
|
||||
) -> Union[nn.GroupNorm, nn.LayerNorm]:
|
||||
if not hasattr(norm_layer, "orig_forward"):
|
||||
setattr(norm_layer, "orig_forward", norm_layer.forward)
|
||||
orig_forward = norm_layer.orig_forward
|
||||
|
||||
def forward_(hidden_states: T) -> T:
|
||||
n = hidden_states.shape[-2]
|
||||
hidden_states = concat_first(hidden_states, dim=-2)
|
||||
hidden_states = orig_forward(hidden_states) # type: ignore
|
||||
return hidden_states[..., :n, :]
|
||||
|
||||
norm_layer.forward = forward_ # type: ignore
|
||||
return norm_layer
|
||||
|
||||
|
||||
def register_shared_norm(
|
||||
model: ModelPatcher,
|
||||
share_group_norm: bool = True,
|
||||
share_layer_norm: bool = True,
|
||||
):
|
||||
norm_layers = {"group": [], "layer": []}
|
||||
get_norm_layers(model.model, norm_layers, share_layer_norm, share_group_norm)
|
||||
print(
|
||||
f"Patching {len(norm_layers['group'])} group norms, {len(norm_layers['layer'])} layer norms."
|
||||
)
|
||||
return [register_norm_forward(layer) for layer in norm_layers["group"]] + [
|
||||
register_norm_forward(layer) for layer in norm_layers["layer"]
|
||||
]
|
||||
|
||||
|
||||
SHARE_NORM_OPTIONS = ["both", "group", "layer", "disabled"]
|
||||
SHARE_ATTN_OPTIONS = ["q+k", "q+k+v", "disabled"]
|
||||
|
||||
class StyleAlignedSampleReferenceLatents:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {"required":
|
||||
{
|
||||
"reference_image": ("IMAGE",),
|
||||
"positive": ("CONDITIONING",),
|
||||
"negative": ("CONDITIONING", ),
|
||||
"model": ("MODEL",),
|
||||
"vae": ("VAE", ),
|
||||
"seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff}),
|
||||
"steps": ("INT", {"default": 20, "min": 1, "max": 10000}),
|
||||
"cfg": ("FLOAT", {"default": 8.0, "min": 0.0, "max": 100.0, "step":0.1, "round": 0.01}),
|
||||
|
||||
"scheduler": (comfy.samplers.KSampler.SCHEDULERS.reverse(), ),
|
||||
"denoise": ("FLOAT", {"default": 1, "min": 0.0, "max": 1.0, "step": 0.01}),
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("STEP_LATENTS","LATENT")
|
||||
RETURN_NAMES = ("ref_latents", "noised_output")
|
||||
|
||||
FUNCTION = "run"
|
||||
|
||||
# CATEGORY = "style_aligned"
|
||||
CATEGORY = "♾️Mixlab/Style"
|
||||
|
||||
def run(self, reference_image, positive, negative, model, vae, seed, steps, cfg,scheduler,denoise):
|
||||
|
||||
# TODO noise_mask?
|
||||
def vae_encode_crop_pixels(pixels):
|
||||
x = (pixels.shape[1] // 8) * 8
|
||||
y = (pixels.shape[2] // 8) * 8
|
||||
if pixels.shape[1] != x or pixels.shape[2] != y:
|
||||
x_offset = (pixels.shape[1] % 8) // 2
|
||||
y_offset = (pixels.shape[2] % 8) // 2
|
||||
pixels = pixels[:, x_offset:x + x_offset, y_offset:y + y_offset, :]
|
||||
return pixels
|
||||
|
||||
pixels=vae_encode_crop_pixels(reference_image)
|
||||
t = vae.encode(pixels[:,:,:,:3])
|
||||
latent_image = {"samples":t}
|
||||
|
||||
noise_seed=seed
|
||||
|
||||
sampler_name="ddim"
|
||||
|
||||
sampler = comfy.samplers.sampler_object(sampler_name)
|
||||
|
||||
total_steps = steps
|
||||
if denoise < 1.0:
|
||||
total_steps = int(steps/denoise)
|
||||
|
||||
comfy.model_management.load_models_gpu([model])
|
||||
sigmas = comfy.samplers.calculate_sigmas_scheduler(model.model, scheduler, total_steps).cpu()
|
||||
sigmas = sigmas[-(steps + 1):]
|
||||
|
||||
sigmas = sigmas.flip(0)
|
||||
if sigmas[0] == 0:
|
||||
sigmas[0] = 0.0001
|
||||
|
||||
latent = latent_image
|
||||
latent_image = latent["samples"]
|
||||
noise = torch.zeros(latent_image.size(), dtype=latent_image.dtype, layout=latent_image.layout, device="cpu")
|
||||
|
||||
|
||||
noise_mask = None
|
||||
if "noise_mask" in latent:
|
||||
noise_mask = latent["noise_mask"]
|
||||
|
||||
ref_latents = []
|
||||
def callback(step: int, x0: T, x: T, steps: int):
|
||||
ref_latents.insert(0, x[0])
|
||||
|
||||
disable_pbar = not comfy.utils.PROGRESS_BAR_ENABLED
|
||||
samples = comfy.sample.sample_custom(model, noise, cfg, sampler, sigmas, positive, negative, latent_image, noise_mask=noise_mask, callback=callback, disable_pbar=disable_pbar, seed=noise_seed)
|
||||
|
||||
out = latent.copy()
|
||||
out["samples"] = samples
|
||||
out_noised = out
|
||||
|
||||
ref_latents = torch.stack(ref_latents)
|
||||
|
||||
return (ref_latents, out_noised)
|
||||
|
||||
class StyleAlignedReferenceSampler:
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
|
||||
"ref_latents": ("STEP_LATENTS",),
|
||||
"reference_image_text": ("STRING", {"multiline": True}),
|
||||
"model": ("MODEL",),
|
||||
"clip": ("CLIP", ),
|
||||
|
||||
"positive": ("CONDITIONING",),
|
||||
"negative": ("CONDITIONING",),
|
||||
|
||||
"share_norm": (SHARE_NORM_OPTIONS,),
|
||||
"share_attn": (SHARE_ATTN_OPTIONS,),
|
||||
"scale": ("FLOAT", {"default": 1, "min": 0, "max": 2.0, "step": 0.01}),
|
||||
"batch_size": ("INT", {"default": 2, "min": 1, "max": 8, "step": 1}),
|
||||
"seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff}),
|
||||
"steps": ("INT", {"default": 20, "min": 1, "max": 10000}),
|
||||
"cfg": ("FLOAT", {"default": 8.0, "min": 0.0, "max": 100.0, "step":0.1, "round": 0.01}),
|
||||
"scheduler": (comfy.samplers.KSampler.SCHEDULERS, ),
|
||||
"denoise": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.01}),
|
||||
},
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("LATENT", "LATENT")
|
||||
RETURN_NAMES = ("output", "denoised_output")
|
||||
FUNCTION = "patch"
|
||||
# CATEGORY = "style_aligned"
|
||||
CATEGORY = "♾️Mixlab/Style"
|
||||
def patch(
|
||||
self,
|
||||
ref_latents,
|
||||
reference_image_text,
|
||||
model,
|
||||
clip,
|
||||
positive,
|
||||
negative,
|
||||
share_norm,
|
||||
share_attn,
|
||||
scale,
|
||||
batch_size,
|
||||
seed,steps,cfg,scheduler,denoise
|
||||
|
||||
) -> "tuple[dict, dict]":
|
||||
|
||||
m = model.clone()
|
||||
|
||||
# ref_latents = vae.encode(reference_image[:,:,:,:3])
|
||||
|
||||
tokens = clip.tokenize(reference_image_text)
|
||||
cond, pooled = clip.encode_from_tokens(tokens, return_pooled=True)
|
||||
ref_positive=[[cond, {"pooled_output": pooled}]]
|
||||
|
||||
noise_seed=seed
|
||||
|
||||
|
||||
total_steps = steps
|
||||
if denoise < 1.0:
|
||||
total_steps = int(steps/denoise)
|
||||
|
||||
# comfy.model_management.load_models_gpu([model])
|
||||
sigmas = comfy.samplers.calculate_sigmas_scheduler(model.model, scheduler, total_steps).cpu()
|
||||
sigmas = sigmas[-(steps + 1):]
|
||||
|
||||
sampler_name="ddim"
|
||||
|
||||
sampler = comfy.samplers.sampler_object(sampler_name)
|
||||
|
||||
args = StyleAlignedArgs(share_attn)
|
||||
|
||||
# Concat batch with style latent
|
||||
style_latent_tensor = ref_latents[0].unsqueeze(0)
|
||||
height, width = style_latent_tensor.shape[-2:]
|
||||
latent_t = torch.zeros(
|
||||
[batch_size, 4, height, width], device=ref_latents.device
|
||||
)
|
||||
latent = {"samples": latent_t}
|
||||
noise = comfy.sample.prepare_noise(latent_t, noise_seed)
|
||||
|
||||
latent_t = torch.cat((style_latent_tensor, latent_t), dim=0)
|
||||
ref_noise = torch.zeros_like(noise[0]).unsqueeze(0)
|
||||
noise = torch.cat((ref_noise, noise), dim=0)
|
||||
|
||||
x0_output = {}
|
||||
preview_callback = latent_preview.prepare_callback(m, sigmas.shape[-1] - 1, x0_output)
|
||||
|
||||
# Replace first latent with the corresponding reference latent after each step
|
||||
def callback(step: int, x0: T, x: T, steps: int):
|
||||
preview_callback(step, x0, x, steps)
|
||||
if (step + 1 < steps):
|
||||
# 当ref_latents的step不够时
|
||||
if step+1>len(ref_latents)-1:
|
||||
step=len(ref_latents)-2
|
||||
|
||||
x[0] = ref_latents[step+1]
|
||||
x0[0] = ref_latents[step+1]
|
||||
|
||||
# Register shared norms
|
||||
share_group_norm = share_norm in ["group", "both"]
|
||||
share_layer_norm = share_norm in ["layer", "both"]
|
||||
register_shared_norm(m, share_group_norm, share_layer_norm)
|
||||
|
||||
# Patch cross attn
|
||||
m.set_model_attn1_patch(SharedAttentionProcessor(args, scale))
|
||||
|
||||
# Add reference conditioning to batch
|
||||
batched_condition = []
|
||||
for i,condition in enumerate(positive):
|
||||
additional = condition[1].copy()
|
||||
batch_with_reference = torch.cat([ref_positive[i][0], condition[0].repeat([batch_size] + [1] * len(condition[0].shape[1:]))], dim=0)
|
||||
if 'pooled_output' in additional and 'pooled_output' in ref_positive[i][1]:
|
||||
# combine pooled output
|
||||
pooled_output = torch.cat([ref_positive[i][1]['pooled_output'], additional['pooled_output'].repeat([batch_size]
|
||||
+ [1] * len(additional['pooled_output'].shape[1:]))], dim=0)
|
||||
additional['pooled_output'] = pooled_output
|
||||
if 'control' in additional:
|
||||
if 'control' in ref_positive[i][1]:
|
||||
# combine control conditioning
|
||||
control_hint = torch.cat([ref_positive[i][1]['control'].cond_hint_original, additional['control'].cond_hint_original.repeat([batch_size]
|
||||
+ [1] * len(additional['control'].cond_hint_original.shape[1:]))], dim=0)
|
||||
cloned_controlnet = additional['control'].copy()
|
||||
cloned_controlnet.set_cond_hint(control_hint, strength=additional['control'].strength, timestep_percent_range=additional['control'].timestep_percent_range)
|
||||
additional['control'] = cloned_controlnet
|
||||
else:
|
||||
# add zeros for first in batch
|
||||
control_hint = torch.cat([torch.zeros_like(additional['control'].cond_hint_original), additional['control'].cond_hint_original.repeat([batch_size]
|
||||
+ [1] * len(additional['control'].cond_hint_original.shape[1:]))], dim=0)
|
||||
cloned_controlnet = additional['control'].copy()
|
||||
cloned_controlnet.set_cond_hint(control_hint, strength=additional['control'].strength, timestep_percent_range=additional['control'].timestep_percent_range)
|
||||
additional['control'] = cloned_controlnet
|
||||
batched_condition.append([batch_with_reference, additional])
|
||||
|
||||
disable_pbar = not comfy.utils.PROGRESS_BAR_ENABLED
|
||||
samples = comfy.sample.sample_custom(
|
||||
m,
|
||||
noise,
|
||||
cfg,
|
||||
sampler,
|
||||
sigmas,
|
||||
batched_condition,
|
||||
negative,
|
||||
latent_t,
|
||||
callback=callback,
|
||||
disable_pbar=disable_pbar,
|
||||
seed=noise_seed,
|
||||
)
|
||||
|
||||
# remove reference image
|
||||
samples = samples[1:]
|
||||
|
||||
out = latent.copy()
|
||||
out["samples"] = samples
|
||||
if "x0" in x0_output:
|
||||
out_denoised = latent.copy()
|
||||
x0 = x0_output["x0"][1:]
|
||||
out_denoised["samples"] = m.model.process_latent_out(x0.cpu())
|
||||
else:
|
||||
out_denoised = out
|
||||
return (out, out_denoised)
|
||||
|
||||
|
||||
class StyleAlignedBatchAlign:
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"model": ("MODEL",),
|
||||
"share_norm": (SHARE_NORM_OPTIONS,),
|
||||
"share_attn": (SHARE_ATTN_OPTIONS,),
|
||||
"scale": ("FLOAT", {"default": 1, "min": 0, "max": 1.0, "step": 0.1}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("MODEL",)
|
||||
FUNCTION = "patch"
|
||||
# CATEGORY = "style_aligned"
|
||||
CATEGORY = "♾️Mixlab/Style"
|
||||
def patch(
|
||||
self,
|
||||
model: ModelPatcher,
|
||||
share_norm: str,
|
||||
share_attn: str,
|
||||
scale: float,
|
||||
):
|
||||
m = model.clone()
|
||||
share_group_norm = share_norm in ["group", "both"]
|
||||
share_layer_norm = share_norm in ["layer", "both"]
|
||||
register_shared_norm(model, share_group_norm, share_layer_norm)
|
||||
args = StyleAlignedArgs(share_attn)
|
||||
m.set_model_attn1_patch(SharedAttentionProcessor(args, scale))
|
||||
return (m,)
|
||||
|
||||
|
||||
@@ -0,0 +1,431 @@
|
||||
from transformers import pipeline, set_seed,AutoTokenizer, AutoModelForSeq2SeqLM
|
||||
import random
|
||||
import re
|
||||
|
||||
import os,sys
|
||||
import folder_paths
|
||||
|
||||
# from PIL import Image
|
||||
import importlib.util
|
||||
|
||||
import comfy.utils
|
||||
# import numpy as np
|
||||
import torch
|
||||
import random
|
||||
from lark import Lark, Transformer, v_args
|
||||
|
||||
|
||||
global _available
|
||||
_available=True
|
||||
|
||||
|
||||
text_generator_model_path=os.path.join(folder_paths.models_dir, "prompt_generator/text2image-prompt-generator")
|
||||
if not os.path.exists(text_generator_model_path):
|
||||
print(f"## text_generator_model not found: {text_generator_model_path}, pls download from https://huggingface.co/succinctly/text2image-prompt-generator/tree/main")
|
||||
text_generator_model_path='succinctly/text2image-prompt-generator'
|
||||
|
||||
zh_en_model_path=os.path.join(folder_paths.models_dir, "prompt_generator/opus-mt-zh-en")
|
||||
if not os.path.exists(zh_en_model_path):
|
||||
print(f"## zh_en_model not found: {zh_en_model_path}, pls download from https://huggingface.co/Helsinki-NLP/opus-mt-zh-en/tree/main")
|
||||
zh_en_model_path='Helsinki-NLP/opus-mt-zh-en'
|
||||
|
||||
|
||||
|
||||
def is_installed(package):
|
||||
try:
|
||||
spec = importlib.util.find_spec(package)
|
||||
except ModuleNotFoundError:
|
||||
return False
|
||||
return spec is not None
|
||||
|
||||
|
||||
try:
|
||||
if is_installed('sentencepiece')==False:
|
||||
import subprocess
|
||||
|
||||
# 安装
|
||||
print('#pip install sentencepiece')
|
||||
|
||||
result = subprocess.run([sys.executable, '-s', '-m', 'pip', 'install', 'sentencepiece'], capture_output=True, text=True)
|
||||
|
||||
#检查命令执行结果
|
||||
if result.returncode == 0 and is_installed('sentencepiece'):
|
||||
print("#install success")
|
||||
_available=True
|
||||
else:
|
||||
print("#install error")
|
||||
_available=False
|
||||
|
||||
else:
|
||||
_available=True
|
||||
|
||||
except:
|
||||
_available=False
|
||||
|
||||
|
||||
|
||||
def translate(text):
|
||||
global text_pipe,zh_en_model,zh_en_tokenizer
|
||||
|
||||
if zh_en_model==None:
|
||||
zh_en_model = AutoModelForSeq2SeqLM.from_pretrained(zh_en_model_path).eval()
|
||||
zh_en_tokenizer = AutoTokenizer.from_pretrained(zh_en_model_path,padding=True, truncation=True)
|
||||
|
||||
zh_en_model.to("cuda" if torch.cuda.is_available() else "cpu")
|
||||
with torch.no_grad():
|
||||
encoded = zh_en_tokenizer([text], return_tensors="pt")
|
||||
encoded.to(zh_en_model.device)
|
||||
sequences = zh_en_model.generate(**encoded)
|
||||
return zh_en_tokenizer.batch_decode(sequences, skip_special_tokens=True)[0]
|
||||
|
||||
# input = "青春不能回头,所以青春没有终点。 ——《火影忍者》"
|
||||
# print(input, translate(input))
|
||||
|
||||
|
||||
def text_generate(text_pipe,input,seed=None):
|
||||
|
||||
if seed==None:
|
||||
seed = random.randint(100, 1000000)
|
||||
|
||||
set_seed(seed)
|
||||
|
||||
for count in range(6):
|
||||
sequences = text_pipe(input, max_length=random.randint(60, 90), num_return_sequences=8)
|
||||
list = []
|
||||
for sequence in sequences:
|
||||
line = sequence['generated_text'].strip()
|
||||
if line != input and len(line) > (len(input) + 4) and line.endswith((":", "-", "—")) is False:
|
||||
list.append(line)
|
||||
|
||||
result = "\n".join(list)
|
||||
result = re.sub('[^ ]+\.[^ ]+','', result)
|
||||
result = result.replace("<", "").replace(">", "")
|
||||
if result != "":
|
||||
return result
|
||||
if count == 5:
|
||||
return result
|
||||
|
||||
# input = "Youth can't turn back, so there's no end to youth."
|
||||
# print(input, text_generate(input))
|
||||
|
||||
|
||||
import re
|
||||
|
||||
def correct_prompt_syntax(prompt=""):
|
||||
|
||||
# print("input prompt",prompt)
|
||||
corrected_elements = []
|
||||
# 处理成统一的英文标点
|
||||
prompt = prompt.replace('(', '(').replace(')', ')').replace(',', ',').replace(';', ',').replace('。', '.').replace(':',':')
|
||||
# 删除多余的空格
|
||||
prompt = re.sub(r'\s+', ' ', prompt).strip()
|
||||
prompt = prompt.replace("< ","<").replace(" >",">").replace("( ","(").replace(" )",")").replace("[ ","[").replace(' ]',']')
|
||||
|
||||
# 分词
|
||||
prompt_elements = prompt.split(',')
|
||||
|
||||
def balance_brackets(element, open_bracket, close_bracket):
|
||||
open_brackets_count = element.count(open_bracket)
|
||||
close_brackets_count = element.count(close_bracket)
|
||||
return element + close_bracket * (open_brackets_count - close_brackets_count)
|
||||
|
||||
for element in prompt_elements:
|
||||
element = element.strip()
|
||||
|
||||
# 处理空元素
|
||||
if not element:
|
||||
continue
|
||||
|
||||
# 检查并处理圆括号、方括号、尖括号
|
||||
if element[0] in '([':
|
||||
corrected_element = balance_brackets(element, '(', ')') if element[0] == '(' else balance_brackets(element, '[', ']')
|
||||
elif element[0] == '<':
|
||||
corrected_element = balance_brackets(element, '<', '>')
|
||||
else:
|
||||
# 删除开头的右括号或右方括号
|
||||
corrected_element = element.lstrip(')]')
|
||||
|
||||
corrected_elements.append(corrected_element)
|
||||
|
||||
# 重组修正后的prompt
|
||||
return ','.join(corrected_elements)
|
||||
|
||||
|
||||
# # 示例使用
|
||||
# test_prompt = "((middle-century castles)), [forsaken: 0.8], (mystery dragons: 1.3, mist forests, sunsets, quiet; (((dummy)), [fisting city: 0.5] background, radiant, soft and flavoured,] promising mountains, ((starry: 1.6), [[crowds], [middle-century castle: urban landscapes of the future: 0.5], [yellow: bright sun: 0.7], overlooking"
|
||||
# corrected_prompt = correct_prompt_syntax(test_prompt)
|
||||
# print(corrected_prompt)
|
||||
|
||||
def detect_language(input_str):
|
||||
# 统计中文和英文字符的数量
|
||||
count_cn = count_en = 0
|
||||
for char in input_str:
|
||||
if '\u4e00' <= char <= '\u9fff':
|
||||
count_cn += 1
|
||||
elif char.isalpha():
|
||||
count_en += 1
|
||||
|
||||
# 根据统计的字符数量判断主要语言
|
||||
if count_cn > count_en:
|
||||
return "cn"
|
||||
elif count_en > count_cn:
|
||||
return "en"
|
||||
else:
|
||||
return "unknow"
|
||||
|
||||
|
||||
|
||||
|
||||
#定义Prompt文法
|
||||
grammar = """
|
||||
start: sentence
|
||||
sentence: phrase ("," phrase)*
|
||||
phrase: emphasis | weight | word | lora | embedding | schedule
|
||||
emphasis: "(" sentence ")" -> emphasis
|
||||
| "[" sentence "]" -> weak_emphasis
|
||||
weight: "(" word ":" NUMBER ")"
|
||||
schedule: "[" word ":" word ":" NUMBER "]"
|
||||
lora: "<" WORD ":" WORD (":" NUMBER)? (":" NUMBER)? ">"
|
||||
embedding: "embedding" ":" WORD (":" NUMBER)? (":" NUMBER)?
|
||||
word: WORD
|
||||
|
||||
NUMBER: /\s*-?\d+(\.\d+)?\s*/
|
||||
WORD: /[^,:\(\)\[\]<>]+/
|
||||
"""
|
||||
|
||||
|
||||
|
||||
@v_args(inline=True) # Decorator to flatten the tree directly into the function arguments
|
||||
class ChinesePromptTranslate(Transformer):
|
||||
|
||||
def sentence(self, *args):
|
||||
return ", ".join(args)
|
||||
|
||||
def phrase(self, *args):
|
||||
return "".join(args)
|
||||
|
||||
def emphasis(self, *args):
|
||||
# Reconstruct the emphasis with translated content
|
||||
return "(" + "".join(args) + ")"
|
||||
|
||||
def weak_emphasis(self, *args):
|
||||
print('weak_emphasis:',args)
|
||||
return "[" + "".join(args) + "]"
|
||||
|
||||
def embedding(self,*args):
|
||||
print('prompt embedding',args[0])
|
||||
if len(args) == 1:
|
||||
# print('prompt embedding',str(args[0]))
|
||||
# 只传递了一个参数,意味着只有embedding名称没有数字
|
||||
embedding_name = str(args[0])
|
||||
return f"embedding:{embedding_name}"
|
||||
elif len(args) > 1:
|
||||
embedding_name,*numbers = args
|
||||
|
||||
if len(numbers)==2:
|
||||
return f"embedding:{embedding_name}:{numbers[0]}:{numbers[1]}"
|
||||
elif len(numbers)==1:
|
||||
return f"embedding:{embedding_name}:{numbers[0]}"
|
||||
else:
|
||||
return f"embedding:{embedding_name}"
|
||||
|
||||
def lora(self,*args):
|
||||
print('lora prompt',*args)
|
||||
if len(args) == 1:
|
||||
return f"<lora:{loar_name}>"
|
||||
elif len(args) > 1:
|
||||
# print('lora', args)
|
||||
_,loar_name,*numbers = args
|
||||
loar_name = str(loar_name).strip()
|
||||
if len(numbers)==2:
|
||||
return f"<lora:{loar_name}:{numbers[0]}:{numbers[1]}>"
|
||||
elif len(numbers)==1:
|
||||
return f"<lora:{loar_name}:{numbers[0]}>"
|
||||
else:
|
||||
return f"<lora:{loar_name}>"
|
||||
|
||||
def weight(self, word,number):
|
||||
translated_word = translate(str(word)).rstrip('.')
|
||||
return f"({translated_word}:{str(number).strip()})"
|
||||
|
||||
def schedule(self,*args):
|
||||
print('prompt schedule',args)
|
||||
data = [str(arg).strip() for arg in args]
|
||||
|
||||
return f"[{':'.join(data)}]"
|
||||
|
||||
def word(self, word):
|
||||
# Translate each word using the dictionary
|
||||
if detect_language(str(word)) == "cn":
|
||||
return translate(str(word)).rstrip('.')
|
||||
else:
|
||||
return str(word).rstrip('.')
|
||||
|
||||
class ChinesePrompt:
|
||||
|
||||
global _available
|
||||
available=_available
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {"required": {
|
||||
"text": ("STRING",{"multiline": True,"default": "", "dynamicPrompts": False}),
|
||||
"generation": (["on","off"],{"default": "off"}),
|
||||
},
|
||||
|
||||
"optional":{
|
||||
"seed":("INT", {"default": 100, "min": 100, "max": 1000000}),
|
||||
|
||||
},
|
||||
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("STRING",)
|
||||
RETURN_NAMES = ("prompt",)
|
||||
|
||||
FUNCTION = "run"
|
||||
|
||||
CATEGORY = "♾️Mixlab/Prompt"
|
||||
OUTPUT_NODE = True
|
||||
INPUT_IS_LIST = True
|
||||
OUTPUT_IS_LIST = (True,)
|
||||
|
||||
global text_pipe,zh_en_model,zh_en_tokenizer
|
||||
|
||||
text_pipe= None
|
||||
zh_en_model=None
|
||||
zh_en_tokenizer=None
|
||||
|
||||
def run(self,text,seed,generation):
|
||||
|
||||
|
||||
seed=seed[0]
|
||||
generation=generation[0]
|
||||
|
||||
# 进度条
|
||||
pbar = comfy.utils.ProgressBar(len(text)+1)
|
||||
texts = [correct_prompt_syntax(t) for t in text]
|
||||
|
||||
global text_pipe,zh_en_model,zh_en_tokenizer
|
||||
if zh_en_model==None:
|
||||
zh_en_model = AutoModelForSeq2SeqLM.from_pretrained(zh_en_model_path).eval()
|
||||
zh_en_tokenizer = AutoTokenizer.from_pretrained(zh_en_model_path,padding=True, truncation=True)
|
||||
|
||||
zh_en_model.to("cuda" if torch.cuda.is_available() else "cpu")
|
||||
# zh_en_tokenizer.to("cuda" if torch.cuda.is_available() else "cpu")
|
||||
|
||||
text_pipe=pipeline('text-generation', model=text_generator_model_path,device="cuda" if torch.cuda.is_available() else "cpu")
|
||||
|
||||
# text_pipe.model.to("cuda" if torch.cuda.is_available() else "cpu")
|
||||
|
||||
prompt_result=[]
|
||||
|
||||
# print('zh_en_model device',zh_en_model.device,text_pipe.model.device,torch.cuda.current_device() )
|
||||
en_texts=[]
|
||||
|
||||
for t in texts:
|
||||
if t:
|
||||
# translated_text = translated_word = translate(zh_en_tokenizer,zh_en_model,str(t))
|
||||
parser = Lark(grammar, start="start", parser="lalr", transformer=ChinesePromptTranslate())
|
||||
# print('t',t)
|
||||
result = parser.parse(t).children
|
||||
# print('en_result',result)
|
||||
# en_text=translate(zh_en_tokenizer,zh_en_model,text_without_syntax)
|
||||
en_texts.append(result[0])
|
||||
|
||||
zh_en_model.to('cpu')
|
||||
print("test en_text",en_texts)
|
||||
# en_text.to("cuda" if torch.cuda.is_available() else "cpu")
|
||||
|
||||
pbar.update(1)
|
||||
for t in en_texts:
|
||||
if generation=='on':
|
||||
prompt =text_generate(text_pipe,t,seed)
|
||||
# 多条,还是单条
|
||||
lines = prompt.split("\n")
|
||||
longest_line = max(lines, key=len)
|
||||
# print(longest_line)
|
||||
prompt_result.append(longest_line)
|
||||
else:
|
||||
prompt_result.append(t)
|
||||
pbar.update(1)
|
||||
|
||||
text_pipe.model.to('cpu')
|
||||
|
||||
print('prompt_result',prompt_result,)
|
||||
# prompt_result = [','.join(correct_prompt_syntax(p)) for p in prompt_result]
|
||||
if len(prompt_result)==0:
|
||||
prompt_result=[""]
|
||||
return {
|
||||
"ui":{
|
||||
"prompt": prompt_result
|
||||
},
|
||||
"result": (prompt_result,)}
|
||||
|
||||
|
||||
|
||||
|
||||
class PromptGenerate:
|
||||
|
||||
global _available
|
||||
available=_available
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {"required": {
|
||||
"text": ("STRING",{"multiline": True,"default": "", "dynamicPrompts": False}),
|
||||
},
|
||||
|
||||
"optional":{
|
||||
"multiple": (["off","on"],),
|
||||
"seed":("INT", {"default": 100, "min": 100, "max": 1000000}),
|
||||
},
|
||||
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("STRING",)
|
||||
RETURN_NAMES = ("prompt",)
|
||||
|
||||
FUNCTION = "run"
|
||||
|
||||
CATEGORY = "♾️Mixlab/Prompt"
|
||||
OUTPUT_NODE = True
|
||||
INPUT_IS_LIST = True
|
||||
OUTPUT_IS_LIST = (True,)
|
||||
|
||||
global text_pipe
|
||||
|
||||
text_pipe= None
|
||||
#
|
||||
|
||||
def run(self,text,multiple,seed):
|
||||
global text_pipe
|
||||
|
||||
seed=seed[0]
|
||||
|
||||
multiple=multiple[0]
|
||||
|
||||
# 进度条
|
||||
pbar = comfy.utils.ProgressBar(len(text))
|
||||
|
||||
text_pipe=pipeline('text-generation', model=text_generator_model_path,device="cuda" if torch.cuda.is_available() else "cpu")
|
||||
|
||||
prompt_result=[]
|
||||
|
||||
for t in text:
|
||||
prompt =text_generate(text_pipe,t,seed)
|
||||
prompt = prompt.split("\n")
|
||||
if multiple=='off':
|
||||
prompt = [max(prompt, key=len)]
|
||||
|
||||
for p in prompt:
|
||||
prompt_result.append(p)
|
||||
pbar.update(1)
|
||||
|
||||
text_pipe.model.to('cpu')
|
||||
|
||||
return {
|
||||
"ui":{
|
||||
"prompt": prompt_result
|
||||
},
|
||||
"result": (prompt_result,)}
|
||||
@@ -0,0 +1,989 @@
|
||||
import os,platform
|
||||
import re,random,json
|
||||
from PIL import Image
|
||||
import numpy as np
|
||||
# FONT_PATH= os.path.abspath(os.path.join(os.path.dirname(__file__),'../assets/王汉宗颜楷体繁.ttf'))
|
||||
import folder_paths
|
||||
import matplotlib.font_manager as fm
|
||||
import torch
|
||||
import importlib.util
|
||||
|
||||
def create_incrementing_list(min_value, max_value, step, count):
|
||||
l1 = [int(min_value + i * step) for i in range(count) if min_value + i * step <= max_value]
|
||||
l2 = [float(min_value + i * step) for i in range(count) if min_value + i * step <= max_value]
|
||||
return (l1,l2)
|
||||
|
||||
def split_list(lst, chunk_size, transition_size):
|
||||
result = []
|
||||
for i in range(0, len(lst), chunk_size):
|
||||
start = i - transition_size
|
||||
end = i + chunk_size + transition_size
|
||||
result.append(lst[max(start, 0):end])
|
||||
return result
|
||||
|
||||
def recursive_search(directory, excluded_dir_names=None):
|
||||
if not os.path.isdir(directory):
|
||||
return [], {}
|
||||
|
||||
if excluded_dir_names is None:
|
||||
excluded_dir_names = []
|
||||
|
||||
result = []
|
||||
dirs = {directory: os.path.getmtime(directory)}
|
||||
for dirpath, subdirs, filenames in os.walk(directory, followlinks=True, topdown=True):
|
||||
subdirs[:] = [d for d in subdirs if d not in excluded_dir_names]
|
||||
for file_name in filenames:
|
||||
relative_path = os.path.relpath(os.path.join(dirpath, file_name), directory)
|
||||
result.append(relative_path)
|
||||
for d in subdirs:
|
||||
path = os.path.join(dirpath, d)
|
||||
dirs[path] = os.path.getmtime(path)
|
||||
return result, dirs
|
||||
|
||||
def filter_files_extensions(files, extensions):
|
||||
return sorted(list(filter(lambda a: os.path.splitext(a)[-1].lower() in extensions or len(extensions) == 0, files)))
|
||||
|
||||
|
||||
def get_system_font_path():
|
||||
ps=[]
|
||||
system = platform.system()
|
||||
if system == "Windows":
|
||||
ps.append(os.path.join(os.environ["WINDIR"], "Fonts"))
|
||||
elif system == "Darwin":
|
||||
ps.append(os.path.join("/Library", "Fonts"))
|
||||
elif system == "Linux":
|
||||
ps.append(os.path.join("/usr", "share", "fonts"))
|
||||
ps.append(os.path.join("/usr", "local", "share", "fonts"))
|
||||
ps=[p for p in ps if os.path.exists(p)]
|
||||
file_paths=[]
|
||||
for f in ps:
|
||||
result, dirs=recursive_search(f)
|
||||
for r in result:
|
||||
file_paths.append(r)
|
||||
file_paths=filter_files_extensions(file_paths,[".otf", ".ttf"])
|
||||
|
||||
return file_paths
|
||||
|
||||
|
||||
|
||||
# import json
|
||||
# import hashlib
|
||||
|
||||
|
||||
# def get_json_hash(json_content):
|
||||
# json_string = json.dumps(json_content, sort_keys=True)
|
||||
# hash_object = hashlib.sha256(json_string.encode())
|
||||
# hash_value = hash_object.hexdigest()
|
||||
# return hash_value
|
||||
|
||||
|
||||
|
||||
def tensor2pil(image):
|
||||
return Image.fromarray(np.clip(255. * image.cpu().numpy().squeeze(), 0, 255).astype(np.uint8))
|
||||
|
||||
|
||||
def create_temp_file(image):
|
||||
output_dir = folder_paths.get_temp_directory()
|
||||
|
||||
(
|
||||
full_output_folder,
|
||||
filename,
|
||||
counter,
|
||||
subfolder,
|
||||
_,
|
||||
) = folder_paths.get_save_image_path('tmp', output_dir)
|
||||
|
||||
|
||||
im=tensor2pil(image)
|
||||
|
||||
image_file = f"{filename}_{counter:05}.png"
|
||||
|
||||
image_path=os.path.join(full_output_folder, image_file)
|
||||
|
||||
im.save(image_path,compress_level=4)
|
||||
|
||||
return [{
|
||||
"filename": image_file,
|
||||
"subfolder": subfolder,
|
||||
"type": "temp"
|
||||
}]
|
||||
|
||||
def get_font_files(directory):
|
||||
font_files = {}
|
||||
|
||||
# 从指定目录加载字体
|
||||
for file in os.listdir(directory):
|
||||
if file.endswith('.ttf') or file.endswith('.otf'):
|
||||
font_name = os.path.splitext(file)[0]
|
||||
font_path = os.path.join(directory, file)
|
||||
font_files[font_name] = os.path.abspath(font_path)
|
||||
|
||||
# 尝试获取系统字体
|
||||
try:
|
||||
font_paths = get_system_font_path()
|
||||
for file in font_paths:
|
||||
try:
|
||||
font_name = os.path.splitext(file)[0]
|
||||
font_path = file
|
||||
font_files[font_name] = os.path.abspath(font_path)
|
||||
except Exception as e:
|
||||
print(f"Error processing font {file}: {e}")
|
||||
except Exception as e:
|
||||
print(f"Error finding system fonts: {e}")
|
||||
|
||||
return font_files
|
||||
|
||||
r_directory = os.path.join(os.path.dirname(__file__), '../assets/')
|
||||
|
||||
font_files = get_font_files(r_directory)
|
||||
# print(font_files)
|
||||
|
||||
|
||||
def flatten_list(nested_list):
|
||||
flat_list = []
|
||||
for item in nested_list:
|
||||
if isinstance(item, list):
|
||||
flat_list.extend(flatten_list(item))
|
||||
else:
|
||||
if torch.is_tensor(item):
|
||||
print('item.shape',item.shape)
|
||||
for i in range(item.shape[0]):
|
||||
flat_list.append(item[i:i + 1, ...])
|
||||
else:
|
||||
flat_list.append(item)
|
||||
return flat_list
|
||||
|
||||
|
||||
class ColorInput:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {"required": {
|
||||
"color":("TCOLOR",),
|
||||
},
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("STRING","INT","INT","INT","FLOAT",)
|
||||
RETURN_NAMES = ("hex","r","g","b","a",)
|
||||
|
||||
FUNCTION = "run"
|
||||
|
||||
CATEGORY = "♾️Mixlab/Color"
|
||||
|
||||
INPUT_IS_LIST = False
|
||||
OUTPUT_IS_LIST = (False,False,False,False,False,)
|
||||
|
||||
def run(self,color):
|
||||
h=color['hex']
|
||||
r=color['r']
|
||||
g=color['g']
|
||||
b=color['b']
|
||||
a=color['a']
|
||||
return (h,r,g,b,a,)
|
||||
|
||||
|
||||
|
||||
class FontInput:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {"required": {
|
||||
|
||||
"font": (list(font_files.keys()),),
|
||||
},
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("STRING",)
|
||||
# RETURN_NAMES = ("WIDTH","HEIGHT","X","Y",)
|
||||
|
||||
FUNCTION = "run"
|
||||
|
||||
CATEGORY = "♾️Mixlab/Input"
|
||||
|
||||
INPUT_IS_LIST = False
|
||||
OUTPUT_IS_LIST = (False,)
|
||||
|
||||
def run(self,font):
|
||||
|
||||
return (font_files[font],)
|
||||
|
||||
class TextToNumber:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {"required": {
|
||||
"text": ("STRING",{"multiline": False,"default": "1"}),
|
||||
"random_number": (["enable", "disable"],),
|
||||
"max_num":("INT", {
|
||||
"default": 10,
|
||||
"min":2, #Minimum value
|
||||
"max": 10000000000, #Maximum value
|
||||
"step": 1, #Slider's step
|
||||
"display": "number" # Cosmetic only: display as "number" or "slider"
|
||||
}),
|
||||
},
|
||||
"optional":{
|
||||
"seed": (any_type, {"default": 0, "min": 0, "max": 0xffffffffffffffff}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("INT",)
|
||||
# RETURN_NAMES = ("WIDTH","HEIGHT","X","Y",)
|
||||
|
||||
FUNCTION = "run"
|
||||
|
||||
CATEGORY = "♾️Mixlab/Text"
|
||||
|
||||
INPUT_IS_LIST = False
|
||||
OUTPUT_IS_LIST = (False,)
|
||||
|
||||
def run(self,text,random_number,max_num,seed=0):
|
||||
|
||||
numbers = re.findall(r'\d+', text)
|
||||
result=0
|
||||
for n in numbers:
|
||||
result = int(n)
|
||||
# print(result)
|
||||
|
||||
if random_number=='enable' and result>0:
|
||||
result= random.randint(1, max_num)
|
||||
return {"ui": {"text": [text],"num":[result]}, "result": (result,)}
|
||||
|
||||
|
||||
|
||||
class FloatSlider:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {"required": {
|
||||
"number":("FLOAT", {
|
||||
"default": 0,
|
||||
"min": 0, #Minimum value
|
||||
"max": 0xffffffffffffffff, #Maximum value
|
||||
"step": 0.001, #Slider's step
|
||||
"display": "slider" # Cosmetic only: display as "number" or "slider"
|
||||
}),
|
||||
"min_value":("FLOAT", {
|
||||
"default": 0,
|
||||
"min": -0xffffffffffffffff,
|
||||
"max": 0xffffffffffffffff,
|
||||
"step": 0.001,
|
||||
"display": "number"
|
||||
}),
|
||||
"max_value":("FLOAT", {
|
||||
"default": 1,
|
||||
"min": -0xffffffffffffffff,
|
||||
"max": 0xffffffffffffffff,
|
||||
"step": 0.001,
|
||||
"display": "number"
|
||||
}),
|
||||
"step":("FLOAT", {
|
||||
"default": 0.001,
|
||||
"min": -0xffffffffffffffff,
|
||||
"max": 0xffffffffffffffff,
|
||||
"step": 0.001,
|
||||
"display": "number"
|
||||
}),
|
||||
},
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("FLOAT",)
|
||||
RETURN_NAMES = ('weight(0-1)',)
|
||||
FUNCTION = "run"
|
||||
|
||||
CATEGORY = "♾️Mixlab/Input"
|
||||
|
||||
INPUT_IS_LIST = False
|
||||
OUTPUT_IS_LIST = (False,)
|
||||
|
||||
def run(self, number, min_value, max_value, step):
|
||||
if number < min_value:
|
||||
number = min_value
|
||||
elif number > max_value:
|
||||
number = max_value
|
||||
scaled_number = (number - min_value) / (max_value - min_value)
|
||||
return (scaled_number,)
|
||||
|
||||
|
||||
class IntNumber:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {"required": {
|
||||
"number":("INT", {
|
||||
"default": 0,
|
||||
"min": -1, #Minimum value
|
||||
"max": 0xffffffffffffffff,
|
||||
"step": 1,
|
||||
"display": "number"
|
||||
}),
|
||||
"min_value":("INT", {
|
||||
"default": 0,
|
||||
"min": -0xffffffffffffffff,
|
||||
"max": 0xffffffffffffffff,
|
||||
"step": 1,
|
||||
"display": "number"
|
||||
}),
|
||||
"max_value":("INT", {
|
||||
"default": 1,
|
||||
"min": -0xffffffffffffffff,
|
||||
"max": 0xffffffffffffffff,
|
||||
"step": 1,
|
||||
"display": "number"
|
||||
}),
|
||||
"step":("INT", {
|
||||
"default": 1,
|
||||
"min": -0xffffffffffffffff,
|
||||
"max": 0xffffffffffffffff,
|
||||
"step":1,
|
||||
"display": "number"
|
||||
}),
|
||||
},
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("INT",)
|
||||
|
||||
FUNCTION = "run"
|
||||
|
||||
CATEGORY = "♾️Mixlab/Input"
|
||||
|
||||
INPUT_IS_LIST = False
|
||||
OUTPUT_IS_LIST = (False,)
|
||||
|
||||
def run(self,number,min_value,max_value,step):
|
||||
if number < min_value:
|
||||
number= min_value
|
||||
elif number > max_value:
|
||||
number= max_value
|
||||
return (number,)
|
||||
|
||||
class MultiplicationNode:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {"required": {
|
||||
"numberA":(any_type,),
|
||||
"multiply_by":("FLOAT", {
|
||||
"default": 1,
|
||||
"min": -2, #Minimum value
|
||||
"max": 0xffffffffffffffff,
|
||||
"step": 0.01, #Slider's step
|
||||
"display": "number" # Cosmetic only: display as "number" or "slider"
|
||||
}),
|
||||
"add_by":("FLOAT", {
|
||||
"default": 0,
|
||||
"min": -2000, #Minimum value
|
||||
"max": 0xffffffffffffffff,
|
||||
"step": 0.01, #Slider's step
|
||||
"display": "number" # Cosmetic only: display as "number" or "slider"
|
||||
})
|
||||
},
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("FLOAT","INT",)
|
||||
|
||||
FUNCTION = "run"
|
||||
|
||||
CATEGORY = "♾️Mixlab/Utils"
|
||||
|
||||
INPUT_IS_LIST = False
|
||||
OUTPUT_IS_LIST = (False,False,)
|
||||
|
||||
def run(self,numberA,multiply_by,add_by):
|
||||
b=int(numberA*multiply_by+add_by)
|
||||
a=float(numberA*multiply_by+add_by)
|
||||
return (a,b,)
|
||||
|
||||
class TextInput:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {"required": {
|
||||
"text": ("STRING",{"multiline": True,"default": ""})
|
||||
},
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("STRING",)
|
||||
|
||||
FUNCTION = "run"
|
||||
|
||||
CATEGORY = "♾️Mixlab/Input"
|
||||
|
||||
INPUT_IS_LIST = False
|
||||
OUTPUT_IS_LIST = (False,)
|
||||
|
||||
def run(self,text):
|
||||
|
||||
return (text,)
|
||||
|
||||
|
||||
class IncrementingListNode:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {"required": {
|
||||
"min_value": ("FLOAT", {
|
||||
"default": 0,
|
||||
"min": -2000, #Minimum value
|
||||
"max": 0xffffffffffffffff,
|
||||
"step": 0.01, #Slider's step
|
||||
"display": "number" # Cosmetic only: display as "number" or "slider"
|
||||
}),
|
||||
"max_value": ("FLOAT", {
|
||||
"default": 10,
|
||||
"min": -2000, #Minimum value
|
||||
"max": 0xffffffffffffffff,
|
||||
"step": 0.01, #Slider's step
|
||||
"display": "number" # Cosmetic only: display as "number" or "slider"
|
||||
}),
|
||||
"step": ("FLOAT", {
|
||||
"default": 0,
|
||||
"min": -2000, #Minimum value
|
||||
"max": 0xffffffffffffffff,
|
||||
"step": 0.01, #Slider's step
|
||||
"display": "number" # Cosmetic only: display as "number" or "slider"
|
||||
}),
|
||||
"count": ("INT", {
|
||||
"default": 1,
|
||||
"min": 1, #Minimum value
|
||||
"max": 0xffffffffffffffff,
|
||||
"step":1, #Slider's step
|
||||
"display": "number" # Cosmetic only: display as "number" or "slider"
|
||||
})
|
||||
},
|
||||
"optional":{
|
||||
"seed":("INT", {"default": -1, "min": -1, "max": 1000000}),
|
||||
|
||||
},
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("INT","FLOAT",)
|
||||
RETURN_NAMES = ('int_list','float_list',)
|
||||
|
||||
FUNCTION = "run"
|
||||
|
||||
CATEGORY = "♾️Mixlab/Video"
|
||||
|
||||
INPUT_IS_LIST = False
|
||||
OUTPUT_IS_LIST = (True,True,)
|
||||
|
||||
def run(self,min_value,max_value,step,count,seed):
|
||||
print('create_incrementing_list',seed)
|
||||
l1,l2=create_incrementing_list(min_value,max_value,step,count)
|
||||
return (l1,l2,)
|
||||
|
||||
# 接收一个值,然后根据字符串或数值长度计算延迟时间,用户可以自定义延迟"字/s",延迟之后将转化
|
||||
|
||||
import comfy.samplers
|
||||
import folder_paths
|
||||
|
||||
# import time
|
||||
|
||||
|
||||
class AnyType(str):
|
||||
"""A special class that is always equal in not equal comparisons. Credit to pythongosssss"""
|
||||
|
||||
def __ne__(self, __value: object) -> bool:
|
||||
return False
|
||||
|
||||
any_type = AnyType("*")
|
||||
import time
|
||||
|
||||
class DynamicDelayProcessor:
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
# print("print INPUT_TYPES",cls)
|
||||
return {
|
||||
"required":{
|
||||
"delay_seconds":("INT",{
|
||||
"default":1,
|
||||
"min": 0,
|
||||
"max": 1000000,
|
||||
}),
|
||||
},
|
||||
"optional":{
|
||||
"any_input":(any_type,),
|
||||
"delay_by_text":("STRING",{"multiline":True,"dynamicPrompts": False,}),
|
||||
"words_per_seconds":("FLOAT",{ "default":1.50,"min": 0.0,"max": 1000.00,"display":"Chars per second?"}),
|
||||
"replace_output": (["disable","enable"],),
|
||||
"replace_value":("INT",{ "default":-1,"min": 0,"max": 1000000,"display":"Replacement value"})
|
||||
}
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def calculate_words_length(cls,text):
|
||||
chinese_char_pattern = re.compile(r'[\u4e00-\u9fff]')
|
||||
english_word_pattern = re.compile(r'\b[a-zA-Z]+\b')
|
||||
number_pattern = re.compile(r'\b[0-9]+\b')
|
||||
|
||||
words_length = 0
|
||||
for segment in text.split():
|
||||
if chinese_char_pattern.search(segment):
|
||||
# 中文字符,每个字符计为 1
|
||||
words_length += len(segment)
|
||||
elif number_pattern.match(segment):
|
||||
# 数字,每个字符计为 1
|
||||
words_length += len(segment)
|
||||
elif english_word_pattern.match(segment):
|
||||
# 英文单词,整个单词计为 1
|
||||
words_length += 1
|
||||
|
||||
return words_length
|
||||
|
||||
|
||||
|
||||
FUNCTION = "run"
|
||||
RETURN_TYPES = (any_type,)
|
||||
RETURN_NAMES = ('output',)
|
||||
|
||||
CATEGORY = "♾️Mixlab/Utils"
|
||||
def run(self,any_input,delay_seconds,delay_by_text,words_per_seconds,replace_output,replace_value):
|
||||
# print(f"Delay text:",delay_by_text )
|
||||
# 获取开始时间戳
|
||||
start_time = time.time()
|
||||
|
||||
# 计算延迟时间
|
||||
delay_time = delay_seconds
|
||||
if delay_by_text and isinstance(delay_by_text, str) and words_per_seconds > 0:
|
||||
words_length = self.calculate_words_length(delay_by_text)
|
||||
print(f"Delay text: {delay_by_text}, Length: {words_length}")
|
||||
delay_time += words_length / words_per_seconds
|
||||
|
||||
# 延迟执行
|
||||
print(f"延迟执行: {delay_time}")
|
||||
time.sleep(delay_time)
|
||||
|
||||
# 获取结束时间戳并计算间隔
|
||||
end_time = time.time()
|
||||
elapsed_time = end_time - start_time
|
||||
print(f"实际延迟时间: {elapsed_time} 秒")
|
||||
|
||||
# 根据 replace_output 决定输出值
|
||||
return (max(0, replace_value),) if replace_output == "enable" else (any_input,)
|
||||
|
||||
|
||||
|
||||
|
||||
# app 配置节点
|
||||
class AppInfo:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {"required": {
|
||||
"name": ("STRING",{"multiline": False,"default": "Mixlab-App","dynamicPrompts": False}),
|
||||
"input_ids":("STRING",{"multiline": True,"default": "\n".join(["1","2","3"]),"dynamicPrompts": False}),
|
||||
"output_ids":("STRING",{"multiline": True,"default": "\n".join(["5","9"]),"dynamicPrompts": False}),
|
||||
},
|
||||
|
||||
"optional":{
|
||||
"IMAGE": ("IMAGE",),
|
||||
"description":("STRING",{"multiline": True,"default": "","dynamicPrompts": False}),
|
||||
"version":("INT", {
|
||||
"default": 1,
|
||||
"min": 1,
|
||||
"max": 10000,
|
||||
"step": 1,
|
||||
"display": "number"
|
||||
}),
|
||||
"share_prefix":("STRING",{"multiline": False,"default": "","dynamicPrompts": False}),
|
||||
"link":("STRING",{"multiline": False,"default": "https://","dynamicPrompts": False}),
|
||||
"category":("STRING",{"multiline": False,"default": "","dynamicPrompts": False}),
|
||||
"auto_save": (["enable","disable"],),
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
RETURN_TYPES = ()
|
||||
# RETURN_NAMES = ("IMAGE",)
|
||||
|
||||
FUNCTION = "run"
|
||||
|
||||
CATEGORY = "♾️Mixlab"
|
||||
|
||||
OUTPUT_NODE = True
|
||||
INPUT_IS_LIST = True
|
||||
# OUTPUT_IS_LIST = (True,)
|
||||
|
||||
def run(self,name,input_ids,output_ids,IMAGE,description,version,share_prefix,link,category,auto_save):
|
||||
name=name[0]
|
||||
|
||||
im=None
|
||||
if IMAGE:
|
||||
im=IMAGE[0][0]
|
||||
#TODO batch 的方式需要处理
|
||||
im=create_temp_file(im)
|
||||
# image [img,] img[batch,w,h,a] 列表里面是batch,
|
||||
|
||||
input_ids=input_ids[0]
|
||||
output_ids=output_ids[0]
|
||||
description=description[0]
|
||||
version=version[0]
|
||||
share_prefix=share_prefix[0]
|
||||
link=link[0]
|
||||
category=category[0]
|
||||
|
||||
# id=get_json_hash([name,im,input_ids,output_ids,description,version])
|
||||
|
||||
return {"ui": {"json": [name,im,input_ids,output_ids,description,version,share_prefix,link,category]}, "result": ()}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
class SwitchByIndex:
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"optional":{
|
||||
"A":(any_type,),
|
||||
"B":(any_type,),
|
||||
},
|
||||
"required": {
|
||||
"index":("INT", {
|
||||
"default": -1,
|
||||
"min": -1,
|
||||
"max": 1000,
|
||||
"step": 1,
|
||||
"display": "number"
|
||||
}),
|
||||
"flat": (['off',"on"],),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = (any_type,"INT",)
|
||||
RETURN_NAMES = ("list", "count",)
|
||||
|
||||
FUNCTION = "run"
|
||||
|
||||
CATEGORY = "♾️Mixlab/Utils"
|
||||
|
||||
INPUT_IS_LIST = True
|
||||
OUTPUT_IS_LIST = (True, False,)
|
||||
|
||||
def run(self, A=[],B=[],index=-1,flat='on'):
|
||||
|
||||
flat=flat[0]
|
||||
|
||||
C=[]
|
||||
index=index[0]
|
||||
|
||||
for a in A:
|
||||
C.append(a)
|
||||
for b in B:
|
||||
C.append(b)
|
||||
|
||||
if flat=='on':
|
||||
C=flatten_list(C)
|
||||
|
||||
if index>-1:
|
||||
try:
|
||||
C=[C[index]]
|
||||
except Exception as e:
|
||||
C=[C[-1]] #最后一个
|
||||
|
||||
return (C, len(C),)
|
||||
|
||||
class ListSplit:
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"optional":{
|
||||
"A":(any_type,),
|
||||
},
|
||||
"required": {
|
||||
"chunk_size": ("INT", {"default": 10, "min": 1, "step": 1}),
|
||||
"transition_size": ("INT", {"default": 0, "min": 0, "step": 1}),
|
||||
"index": ("INT", {"default": -1, "min": -1, "step": 1}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = (any_type,)
|
||||
RETURN_NAMES = ("B",)
|
||||
|
||||
FUNCTION = "run"
|
||||
|
||||
CATEGORY = "♾️Mixlab/Utils"
|
||||
|
||||
INPUT_IS_LIST = True
|
||||
OUTPUT_IS_LIST = (True,)
|
||||
|
||||
def run(self, A=[],chunk_size=[10],transition_size=[0],index=[-1]):
|
||||
# print(len(A))
|
||||
B=split_list(A,chunk_size[0],transition_size[0])
|
||||
|
||||
if index[0]>-1:
|
||||
B=B[index[0]]
|
||||
|
||||
return (B,)
|
||||
|
||||
|
||||
|
||||
class LimitNumber:
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"number":(any_type,),
|
||||
"min_value":("INT", {
|
||||
"default": 0,
|
||||
"min": 0,
|
||||
"max": 0xffffffffffffffff,
|
||||
"step": 1,
|
||||
"display": "number"
|
||||
}),
|
||||
"max_value":("INT", {
|
||||
"default": 1,
|
||||
"min": 1,
|
||||
"max": 0xffffffffffffffff,
|
||||
"step": 1,
|
||||
"display": "number"
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = (any_type,)
|
||||
RETURN_NAMES = ("number",)
|
||||
|
||||
FUNCTION = "run"
|
||||
|
||||
CATEGORY = "♾️Mixlab/Input"
|
||||
|
||||
INPUT_IS_LIST = False
|
||||
OUTPUT_IS_LIST = (False,)
|
||||
|
||||
def run(self, number, min_value, max_value):
|
||||
nn=number
|
||||
|
||||
if isinstance(number, int):
|
||||
min_value=int(min_value)
|
||||
max_value=int(max_value)
|
||||
if isinstance(number, float):
|
||||
min_value=float(min_value)
|
||||
max_value=float(max_value)
|
||||
|
||||
if number < min_value:
|
||||
nn= min_value
|
||||
elif number > max_value:
|
||||
nn= max_value
|
||||
|
||||
return (nn,)
|
||||
|
||||
|
||||
|
||||
class ListStatistics:
|
||||
@staticmethod
|
||||
def count_types(lst):
|
||||
type_count = {}
|
||||
|
||||
for item in lst:
|
||||
item_type = type(item).__name__
|
||||
if item_type not in type_count:
|
||||
type_count[item_type] = []
|
||||
|
||||
if item_type in ['dict', 'str', 'int', 'float']:
|
||||
type_count[item_type].append(item)
|
||||
|
||||
return type_count
|
||||
|
||||
# # 示例列表
|
||||
# my_list = [1, 'hello', {'name': 'John'}, 3.14, {'age': 25}, 'world', 10]
|
||||
|
||||
# # 创建ListStatistics对象
|
||||
# list_stats = ListStatistics()
|
||||
|
||||
# # 调用count_types方法进行统计
|
||||
# result = list_stats.count_types(my_list)
|
||||
|
||||
# # 输出结果
|
||||
# for item_type, values in result.items():
|
||||
# print(item_type + ':')
|
||||
# for value in values:
|
||||
# print(value)
|
||||
# print('---')
|
||||
|
||||
class TESTNODE_:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {"required": {
|
||||
"ANY":(any_type,),
|
||||
},
|
||||
}
|
||||
|
||||
RETURN_TYPES = (any_type,)
|
||||
|
||||
FUNCTION = "run"
|
||||
|
||||
CATEGORY = "♾️Mixlab/Test"
|
||||
|
||||
OUTPUT_NODE = True
|
||||
INPUT_IS_LIST = True
|
||||
OUTPUT_IS_LIST = (True,)
|
||||
|
||||
def run(self,ANY):
|
||||
print(type(ANY))
|
||||
try:
|
||||
print(ANY[0].shape)
|
||||
img= tensor2pil(ANY[0])
|
||||
print(img.size)
|
||||
except:
|
||||
print('')
|
||||
|
||||
# data=ANY
|
||||
list_stats = ListStatistics()
|
||||
|
||||
# 调用count_types方法进行统计
|
||||
result = list_stats.count_types(ANY)
|
||||
|
||||
|
||||
# 假设我们有一个模块文件名为 my_module.py,它位于 'importables' 目录下
|
||||
module_path = os.path.join(os.path.dirname(__file__),'test.py')
|
||||
|
||||
# 使用 spec_from_file_location 获取模块的元数据(名称、定义等)
|
||||
spec = importlib.util.spec_from_file_location('test', module_path)
|
||||
|
||||
module = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(module)
|
||||
|
||||
functions = getattr(module, 'run') # 获取函数
|
||||
|
||||
functions(ANY)
|
||||
|
||||
|
||||
return {"ui": {"data": result,"type":[str(type(ANY[0]))]}, "result": (ANY,)}
|
||||
|
||||
|
||||
class TESTNODE_TOKEN:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {"required": {
|
||||
"text":("STRING", {"forceInput": True,}),
|
||||
"clip": ("CLIP", )
|
||||
},
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("STRING",)
|
||||
|
||||
FUNCTION = "run"
|
||||
|
||||
CATEGORY = "♾️Mixlab/Test"
|
||||
|
||||
OUTPUT_NODE = True
|
||||
INPUT_IS_LIST = False
|
||||
OUTPUT_IS_LIST = (False,)
|
||||
|
||||
def run(self,text,clip=None):
|
||||
# print(text)
|
||||
|
||||
tokens = clip.tokenize(text)
|
||||
|
||||
tokens=[v for v in tokens.values()][0][0]
|
||||
|
||||
tokens=json.dumps(tokens)
|
||||
|
||||
return (tokens,)
|
||||
|
||||
|
||||
|
||||
class CreateSeedNode:
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("INT",)
|
||||
RETURN_NAMES = ("seed",)
|
||||
|
||||
OUTPUT_NODE = True
|
||||
FUNCTION = "run"
|
||||
|
||||
CATEGORY = "♾️Mixlab/Experiment"
|
||||
|
||||
def run(self, seed):
|
||||
return (seed,)
|
||||
|
||||
|
||||
class CreateCkptNames:
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"ckpt_names": ("STRING",{"multiline": True,"default": "\n".join(folder_paths.get_filename_list("checkpoints")),"dynamicPrompts": False}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = (any_type,)
|
||||
RETURN_NAMES = ("ckpt_names",)
|
||||
|
||||
INPUT_IS_LIST = False
|
||||
OUTPUT_IS_LIST = (True,)
|
||||
|
||||
# OUTPUT_NODE = True
|
||||
FUNCTION = "run"
|
||||
|
||||
CATEGORY = "♾️Mixlab/Experiment"
|
||||
|
||||
def run(self, ckpt_names):
|
||||
ckpt_names=ckpt_names.split('\n')
|
||||
ckpt_names = [name for name in ckpt_names if name.strip()]
|
||||
return (ckpt_names,)
|
||||
|
||||
|
||||
class CreateLoraNames:
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"lora_names": ("STRING",{"multiline": True,"default": "\n".join(folder_paths.get_filename_list("loras")),"dynamicPrompts": False}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = (any_type,"STRING",)
|
||||
RETURN_NAMES = ("lora_names","prompt",)
|
||||
|
||||
INPUT_IS_LIST = False
|
||||
OUTPUT_IS_LIST = (True,True,)
|
||||
|
||||
# OUTPUT_NODE = True
|
||||
FUNCTION = "run"
|
||||
|
||||
CATEGORY = "♾️Mixlab/Experiment"
|
||||
|
||||
def run(self, lora_names):
|
||||
lora_names=lora_names.split('\n')
|
||||
lora_names = [name for name in lora_names if name.strip()]
|
||||
prompts=[os.path.splitext(n)[0] for n in lora_names]
|
||||
return (lora_names,prompts,)
|
||||
|
||||
|
||||
|
||||
class CreateSampler_names:
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"sampler_names": ("STRING",{"multiline": True,"default": "\n".join(comfy.samplers.KSampler.SAMPLERS),"dynamicPrompts": False}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = (any_type,)
|
||||
RETURN_NAMES = ("sampler_names",)
|
||||
|
||||
INPUT_IS_LIST = False
|
||||
OUTPUT_IS_LIST = (True,)
|
||||
|
||||
# OUTPUT_NODE = True
|
||||
FUNCTION = "run"
|
||||
|
||||
CATEGORY = "♾️Mixlab/Experiment"
|
||||
|
||||
def run(self, sampler_names):
|
||||
sampler_names=sampler_names.split('\n')
|
||||
sampler_names = [name for name in sampler_names if name.strip()]
|
||||
return (sampler_names,)
|
||||
@@ -1,179 +0,0 @@
|
||||
# https://github.com/openai/consistencydecoder/blob/main/consistencydecoder/__init__.py
|
||||
|
||||
import folder_paths
|
||||
from comfy import model_management
|
||||
|
||||
import math
|
||||
import torch
|
||||
import numpy as np
|
||||
from PIL import Image
|
||||
|
||||
|
||||
class ConsistencyDecoderWrapper:
|
||||
def __init__(self, decoder):
|
||||
self.decoder = decoder
|
||||
def decode(self, x):
|
||||
return self.decoder(x)
|
||||
|
||||
def _extract_into_tensor(arr, timesteps, broadcast_shape):
|
||||
|
||||
# from: https://github.com/openai/guided-diffusion/blob/22e0df8183507e13a7813f8d38d51b072ca1e67c/guided_diffusion/gaussian_diffusion.py#L895 """
|
||||
res = arr[timesteps].float()
|
||||
dims_to_append = len(broadcast_shape) - len(res.shape)
|
||||
return res[(...,) + (None,) * dims_to_append]
|
||||
|
||||
|
||||
def betas_for_alpha_bar(num_diffusion_timesteps, alpha_bar, max_beta=0.999):
|
||||
# from: https://github.com/openai/guided-diffusion/blob/22e0df8183507e13a7813f8d38d51b072ca1e67c/guided_diffusion/gaussian_diffusion.py#L45
|
||||
betas = []
|
||||
for i in range(num_diffusion_timesteps):
|
||||
t1 = i / num_diffusion_timesteps
|
||||
t2 = (i + 1) / num_diffusion_timesteps
|
||||
betas.append(min(1 - alpha_bar(t2) / alpha_bar(t1), max_beta))
|
||||
return torch.tensor(betas)
|
||||
|
||||
class ConsistencyDecoder:
|
||||
def __init__(self, device="cuda:0", download_target=""):
|
||||
self.n_distilled_steps = 64
|
||||
# download_target = _download("https://openaipublic.azureedge.net/diff-vae/c9cebd3132dd9c42936d803e33424145a748843c8f716c0814838bdc8a2fe7cb/decoder.pt", download_root)
|
||||
self.ckpt = torch.jit.load(download_target).to(device)
|
||||
self.device = device
|
||||
sigma_data = 0.5
|
||||
betas = betas_for_alpha_bar(
|
||||
1024, lambda t: math.cos((t + 0.008) / 1.008 * math.pi / 2) ** 2
|
||||
).to(device)
|
||||
alphas = 1.0 - betas
|
||||
alphas_cumprod = torch.cumprod(alphas, dim=0)
|
||||
self.sqrt_alphas_cumprod = torch.sqrt(alphas_cumprod)
|
||||
self.sqrt_one_minus_alphas_cumprod = torch.sqrt(1.0 - alphas_cumprod)
|
||||
sqrt_recip_alphas_cumprod = torch.sqrt(1.0 / alphas_cumprod)
|
||||
sigmas = torch.sqrt(1.0 / alphas_cumprod - 1)
|
||||
self.c_skip = (
|
||||
sqrt_recip_alphas_cumprod
|
||||
* sigma_data**2
|
||||
/ (sigmas**2 + sigma_data**2)
|
||||
)
|
||||
self.c_out = sigmas * sigma_data / (sigmas**2 + sigma_data**2) ** 0.5
|
||||
self.c_in = sqrt_recip_alphas_cumprod / (sigmas**2 + sigma_data**2) ** 0.5
|
||||
|
||||
@staticmethod
|
||||
def round_timesteps(
|
||||
timesteps, total_timesteps, n_distilled_steps, truncate_start=True
|
||||
):
|
||||
with torch.no_grad():
|
||||
space = torch.div(total_timesteps, n_distilled_steps, rounding_mode="floor")
|
||||
rounded_timesteps = (
|
||||
torch.div(timesteps, space, rounding_mode="floor") + 1
|
||||
) * space
|
||||
if truncate_start:
|
||||
rounded_timesteps[rounded_timesteps == total_timesteps] -= space
|
||||
else:
|
||||
rounded_timesteps[rounded_timesteps == total_timesteps] -= space
|
||||
rounded_timesteps[rounded_timesteps == 0] += space
|
||||
return rounded_timesteps
|
||||
|
||||
@staticmethod
|
||||
def ldm_transform_latent(z, extra_scale_factor=1):
|
||||
channel_means = [0.38862467, 0.02253063, 0.07381133, -0.0171294]
|
||||
channel_stds = [0.9654121, 1.0440036, 0.76147926, 0.77022034]
|
||||
|
||||
if len(z.shape) != 4:
|
||||
raise ValueError()
|
||||
|
||||
z = z * 0.18215
|
||||
channels = [z[:, i] for i in range(z.shape[1])]
|
||||
|
||||
channels = [
|
||||
extra_scale_factor * (c - channel_means[i]) / channel_stds[i]
|
||||
for i, c in enumerate(channels)
|
||||
]
|
||||
return torch.stack(channels, dim=1)
|
||||
|
||||
@torch.no_grad()
|
||||
def __call__(
|
||||
self,
|
||||
features: torch.Tensor,
|
||||
schedule=[1.0, 0.5],
|
||||
):
|
||||
features = self.ldm_transform_latent(features)
|
||||
|
||||
ts = self.round_timesteps(
|
||||
torch.arange(0, 1024),
|
||||
1024,
|
||||
self.n_distilled_steps,
|
||||
truncate_start=False,
|
||||
)
|
||||
shape = (
|
||||
features.size(0),
|
||||
3,
|
||||
8 * features.size(2),
|
||||
8 * features.size(3),
|
||||
)
|
||||
|
||||
x_start = torch.zeros(shape, device=features.device, dtype=features.dtype)
|
||||
schedule_timesteps = [int((1024 - 1) * s) for s in schedule]
|
||||
for i in schedule_timesteps:
|
||||
t = ts[i].item()
|
||||
t_ = torch.tensor([t] * features.shape[0]).to(self.device)
|
||||
noise = torch.randn_like(x_start)
|
||||
|
||||
x_start = (
|
||||
_extract_into_tensor(self.sqrt_alphas_cumprod, t_, x_start.shape)
|
||||
* x_start
|
||||
+ _extract_into_tensor(
|
||||
self.sqrt_one_minus_alphas_cumprod, t_, x_start.shape
|
||||
)
|
||||
* noise
|
||||
)
|
||||
c_in = _extract_into_tensor(self.c_in, t_, x_start.shape)
|
||||
model_output = self.ckpt(c_in * x_start, t_, features=features)
|
||||
B, C = x_start.shape[:2]
|
||||
model_output, _ = torch.split(model_output, C, dim=1)
|
||||
pred_xstart = (
|
||||
_extract_into_tensor(self.c_out, t_, x_start.shape) * model_output
|
||||
+ _extract_into_tensor(self.c_skip, t_, x_start.shape) * x_start
|
||||
).clamp(-1, 1)
|
||||
x_start = pred_xstart
|
||||
return x_start
|
||||
|
||||
|
||||
|
||||
class VAELoader:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {"required": { "vae_name": (folder_paths.get_filename_list("vae"), )}}
|
||||
RETURN_TYPES = ("VAE",)
|
||||
FUNCTION = "load_vae"
|
||||
|
||||
CATEGORY = "Mixlab/ConsistencyDecoder"
|
||||
|
||||
#TODO: scale factor?
|
||||
def load_vae(self, vae_name):
|
||||
vae_path = folder_paths.get_full_path("vae", vae_name)
|
||||
device = 'cuda:0'
|
||||
# print('device',device)
|
||||
consistencyDecoder = ConsistencyDecoder(device=device,
|
||||
download_target=vae_path) # Model size: 2.49 GB
|
||||
vae = ConsistencyDecoderWrapper(consistencyDecoder)
|
||||
return (vae,)
|
||||
|
||||
|
||||
class VAEDecode:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {"required": { "samples": ("LATENT", ), "vae": ("VAE", )}}
|
||||
RETURN_TYPES = ("IMAGE",)
|
||||
FUNCTION = "decode"
|
||||
|
||||
CATEGORY = "Mixlab/ConsistencyDecoder"
|
||||
|
||||
def decode(self, vae, samples):
|
||||
image = vae.decode(samples["samples"].to("cuda:0"))
|
||||
image = image[0].cpu().numpy()
|
||||
image = (image + 1.0) * 127.5
|
||||
image = image.clip(0, 255).astype(np.uint8)
|
||||
image = Image.fromarray(image.transpose(1, 2, 0))
|
||||
image = image.convert("RGB")
|
||||
image = np.array(image).astype(np.float32) / 255.0
|
||||
image = torch.from_numpy(image)[None,]
|
||||
return (image, )
|
||||
@@ -0,0 +1,692 @@
|
||||
import os
|
||||
import hashlib
|
||||
import json
|
||||
import subprocess
|
||||
import shutil
|
||||
import re
|
||||
import time,math
|
||||
import numpy as np
|
||||
from typing import List
|
||||
import torch
|
||||
from PIL import Image, ImageOps
|
||||
from PIL.PngImagePlugin import PngInfo
|
||||
import cv2,random,string
|
||||
from pathlib import Path
|
||||
|
||||
import folder_paths
|
||||
from comfy.k_diffusion.utils import FolderOfImages
|
||||
from comfy.utils import common_upscale
|
||||
|
||||
|
||||
|
||||
|
||||
def generate_folder_name(directory,video_path):
|
||||
# Get the directory and filename from the video path
|
||||
_, filename = os.path.split(video_path)
|
||||
# Generate a random string of lowercase letters and digits
|
||||
random_string = ''.join(random.choices(string.ascii_lowercase + string.digits, k=8))
|
||||
# Create the folder name by combining the random string and the filename
|
||||
folder_name = random_string + '_' + filename
|
||||
# Create the full folder path by joining the directory and the folder name
|
||||
folder_path = os.path.join(directory, folder_name)
|
||||
return folder_path
|
||||
|
||||
def create_folder(directory,video_path):
|
||||
folder_path = generate_folder_name(directory,video_path)
|
||||
os.makedirs(folder_path)
|
||||
return folder_path
|
||||
|
||||
|
||||
def split_video(video_path, video_segment_frames, transition_frames, output_dir):
|
||||
# 读取视频文件
|
||||
video_capture = cv2.VideoCapture(video_path)
|
||||
|
||||
# 获取视频的总帧数和帧率
|
||||
total_frames = int(video_capture.get(cv2.CAP_PROP_FRAME_COUNT))
|
||||
fps = video_capture.get(cv2.CAP_PROP_FPS)
|
||||
|
||||
# 计算每个视频片段的总帧数,包括过渡帧
|
||||
segment_total_frames = video_segment_frames + transition_frames
|
||||
|
||||
# 计算可以分割的片段数量,向上取整
|
||||
num_segments = (total_frames + transition_frames - 1) // segment_total_frames
|
||||
|
||||
vs=[]
|
||||
# 计算每个片段的起始帧和结束帧
|
||||
start_frame = 0
|
||||
for i in range(num_segments):
|
||||
# 计算当前片段的结束帧,注意最后一个片段可能没有过渡帧
|
||||
end_frame = min(start_frame + segment_total_frames, total_frames)
|
||||
|
||||
# 打印当前片段的起始帧和结束帧
|
||||
print(f"Segment {i+1}: Start Frame {start_frame}, End Frame {end_frame}")
|
||||
|
||||
# 保存当前片段为一个视频文件
|
||||
segment_video_path = f"{output_dir}/segment_{i+1}.avi"
|
||||
|
||||
fourcc = cv2.VideoWriter_fourcc(*'XVID')
|
||||
segment_video = cv2.VideoWriter(segment_video_path, fourcc, fps, (int(video_capture.get(cv2.CAP_PROP_FRAME_WIDTH)),
|
||||
int(video_capture.get(cv2.CAP_PROP_FRAME_HEIGHT))))
|
||||
|
||||
for frame_num in range(start_frame, end_frame):
|
||||
ret, frame = video_capture.read()
|
||||
if ret:
|
||||
segment_video.write(frame)
|
||||
else:
|
||||
break # 如果读取失败,则退出循环
|
||||
|
||||
# 更新起始帧为下一个片段的起始位置
|
||||
start_frame = end_frame + transition_frames
|
||||
vs.append(segment_video_path)
|
||||
|
||||
# 释放视频捕获对象
|
||||
video_capture.release()
|
||||
# print(vs)
|
||||
return (vs,total_frames,fps)
|
||||
|
||||
|
||||
folder_paths.folder_names_and_paths["video_formats"] = (
|
||||
[
|
||||
os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", "video_formats"),
|
||||
],
|
||||
[".json"]
|
||||
)
|
||||
|
||||
ffmpeg_path = shutil.which("ffmpeg")
|
||||
if ffmpeg_path is None:
|
||||
print("ffmpeg could not be found. Using ffmpeg from imageio-ffmpeg.")
|
||||
from imageio_ffmpeg import get_ffmpeg_exe
|
||||
try:
|
||||
ffmpeg_path = get_ffmpeg_exe()
|
||||
except:
|
||||
print("ffmpeg could not be found. Outputs that require it have been disabled")
|
||||
|
||||
# Tensor to PIL
|
||||
def tensor2pil(image):
|
||||
return Image.fromarray(np.clip(255. * image.cpu().numpy().squeeze(), 0, 255).astype(np.uint8))
|
||||
|
||||
# Convert PIL to Tensor
|
||||
def pil2tensor(image):
|
||||
return torch.from_numpy(np.array(image).astype(np.float32) / 255.0).unsqueeze(0)
|
||||
|
||||
def count_files(directory):
|
||||
count = 0
|
||||
for root, dirs, files in os.walk(directory):
|
||||
count += len(files)
|
||||
return count
|
||||
|
||||
def create_temp_file(image):
|
||||
output_dir = folder_paths.get_temp_directory()
|
||||
|
||||
c=count_files(output_dir)
|
||||
|
||||
(
|
||||
full_output_folder,
|
||||
filename,
|
||||
counter,
|
||||
subfolder,
|
||||
_,
|
||||
) = folder_paths.get_save_image_path('temp_', output_dir)
|
||||
|
||||
|
||||
image=tensor2pil(image)
|
||||
|
||||
image_file = f"{filename}_{c}_{counter:05}.png"
|
||||
|
||||
image_path=os.path.join(full_output_folder, image_file)
|
||||
|
||||
image.save(image_path,compress_level=4)
|
||||
|
||||
return [{
|
||||
"filename": image_file,
|
||||
"subfolder": subfolder,
|
||||
"type": "temp"
|
||||
}]
|
||||
|
||||
|
||||
def split_list(lst, chunk_size, transition_size):
|
||||
result = []
|
||||
for i in range(0, len(lst), chunk_size):
|
||||
start = i - transition_size
|
||||
end = i + chunk_size + transition_size
|
||||
result.append(lst[max(start, 0):end])
|
||||
return result
|
||||
|
||||
# images = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
|
||||
# chunk_size = 3
|
||||
# transition_size = 1
|
||||
|
||||
# result = split_list(images, chunk_size, transition_size)
|
||||
# print(result)
|
||||
|
||||
class ImageListReplace:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {"required": {
|
||||
"images": ("IMAGE",),
|
||||
"start_index":("INT", {"default": 0, "min": 0, "step": 1}),
|
||||
"end_index":("INT", {"default": 0, "min": 0, "step": 1}),
|
||||
"invert": ("BOOLEAN", {"default": False}),
|
||||
},
|
||||
"optional":{
|
||||
"image_replace": ("IMAGE",),
|
||||
"images_replace": ("IMAGE",),
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("IMAGE","IMAGE",)
|
||||
RETURN_NAMES = ("images","select_images",)
|
||||
FUNCTION = "run"
|
||||
CATEGORY = "♾️Mixlab/Video"
|
||||
|
||||
OUTPUT_NODE = True
|
||||
INPUT_IS_LIST = True
|
||||
OUTPUT_IS_LIST = (True,True,)
|
||||
|
||||
def run(self, images,start_index=[0],end_index=[0],invert=[False],image_replace=None,images_replace=None):
|
||||
start_index=start_index[0]
|
||||
end_index=end_index[0]
|
||||
invert=invert[0]
|
||||
|
||||
image_rs=[]
|
||||
|
||||
if image_replace!=None:
|
||||
for i in range(end_index-start_index+1):
|
||||
image_rs.append(image_replace[0])
|
||||
|
||||
if images_replace!=None:
|
||||
image_rs=images_replace
|
||||
|
||||
# 如果image replace 为空
|
||||
if image_replace==None and images_replace==None:
|
||||
# print('如果image replace 为空',images[0])
|
||||
# [[tensor(
|
||||
# tensor([[[[0.
|
||||
first_image=tensor2pil(images[0][0])
|
||||
width, height = first_image.size
|
||||
image_replace=Image.new("RGB", (width, height), (0, 0, 0))
|
||||
image_replace=pil2tensor(image_replace)
|
||||
for i in range(end_index-start_index+1):
|
||||
image_rs.append(image_replace)
|
||||
|
||||
|
||||
new_images=[]
|
||||
select_images=[]
|
||||
k=0
|
||||
for i in range(len(images)):
|
||||
if i>=start_index and i<=end_index:
|
||||
if invert:
|
||||
new_images.append(images[i])
|
||||
else:
|
||||
new_images.append(image_rs[k])
|
||||
select_images.append(images[i])
|
||||
k+=1
|
||||
else:
|
||||
if invert:
|
||||
new_images.append(image_rs[k])
|
||||
select_images.append(images[i])
|
||||
k+=1
|
||||
else:
|
||||
new_images.append(images[i])
|
||||
|
||||
imss=[]
|
||||
# print(len(images))
|
||||
for i in range(len(images)):
|
||||
t=images[i][0]
|
||||
t=tensor2pil(t)
|
||||
t = t.convert("RGB")
|
||||
original_width, original_height = t.size
|
||||
scale = 300 / original_width
|
||||
new_height = int(original_height * scale)
|
||||
t = t.resize((300, new_height))
|
||||
|
||||
ims=create_temp_file(pil2tensor(t))
|
||||
imss.append(ims[0])
|
||||
|
||||
# image_replace=create_temp_file(image_replace)
|
||||
|
||||
return {"ui":{"_images": imss},"result": (new_images,select_images,)}
|
||||
|
||||
# The code is based on ComfyUI-VideoHelperSuite modification.
|
||||
class LoadVideoAndSegment:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
video_extensions = ['webm', 'mp4', 'mkv', 'gif']
|
||||
input_dir = folder_paths.get_input_directory()
|
||||
files = []
|
||||
for f in os.listdir(input_dir):
|
||||
if os.path.isfile(os.path.join(input_dir, f)):
|
||||
file_parts = f.split('.')
|
||||
if len(file_parts) > 1 and (file_parts[-1] in video_extensions):
|
||||
files.append(f)
|
||||
return {"required": {
|
||||
"video": (sorted(files), {"video_upload": True}),
|
||||
"video_segment_frames": ("INT", {"default": 10, "min": 1, "step": 1}),
|
||||
"transition_frames": ("INT", {"default": 0, "min": 0, "step": 1}),
|
||||
},}
|
||||
|
||||
CATEGORY = "♾️Mixlab/Video"
|
||||
|
||||
RETURN_TYPES = ("SCENE_VIDEO","INT", "INT","INT",)
|
||||
RETURN_NAMES = ("scenes_video","scenes_count","frame_count","fps",)
|
||||
FUNCTION = "load_video"
|
||||
OUTPUT_NODE = True
|
||||
OUTPUT_IS_LIST = (True,False,False,False,)
|
||||
|
||||
|
||||
def is_gif(self, filename):
|
||||
file_parts = filename.split('.')
|
||||
return len(file_parts) > 1 and file_parts[-1] == "gif"
|
||||
|
||||
def load_video_cv_fallback(self, video, frame_load_cap, skip_first_frames):
|
||||
try:
|
||||
video_cap = cv2.VideoCapture(folder_paths.get_annotated_filepath(video))
|
||||
if not video_cap.isOpened():
|
||||
raise ValueError(f"{video} could not be loaded with cv fallback.")
|
||||
# set video_cap to look at start_index frame
|
||||
images = []
|
||||
total_frame_count = 0
|
||||
frames_added = 0
|
||||
base_frame_time = 1/video_cap.get(cv2.CAP_PROP_FPS)
|
||||
|
||||
target_frame_time = base_frame_time
|
||||
|
||||
time_offset=0.0
|
||||
while video_cap.isOpened():
|
||||
if time_offset < target_frame_time:
|
||||
is_returned, frame = video_cap.read()
|
||||
# if didn't return frame, video has ended
|
||||
if not is_returned:
|
||||
break
|
||||
time_offset += base_frame_time
|
||||
if time_offset < target_frame_time:
|
||||
continue
|
||||
time_offset -= target_frame_time
|
||||
# if not at start_index, skip doing anything with frame
|
||||
total_frame_count += 1
|
||||
if total_frame_count <= skip_first_frames:
|
||||
continue
|
||||
# TODO: do whatever operations need to happen, like force_size, etc
|
||||
|
||||
# opencv loads images in BGR format (yuck), so need to convert to RGB for ComfyUI use
|
||||
# follow up: can videos ever have an alpha channel?
|
||||
frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
|
||||
# convert frame to comfyui's expected format (taken from comfy's load image code)
|
||||
image = Image.fromarray(frame)
|
||||
image = ImageOps.exif_transpose(image)
|
||||
image = np.array(image, dtype=np.float32) / 255.0
|
||||
image = torch.from_numpy(image)[None,]
|
||||
images.append(image)
|
||||
frames_added += 1
|
||||
# if cap exists and we've reached it, stop processing frames
|
||||
if frame_load_cap > 0 and frames_added >= frame_load_cap:
|
||||
break
|
||||
finally:
|
||||
video_cap.release()
|
||||
images = torch.cat(images, dim=0)
|
||||
|
||||
return (images, frames_added)
|
||||
|
||||
def load_video(self, video,video_segment_frames,transition_frames ):
|
||||
|
||||
video_path = folder_paths.get_annotated_filepath(video)
|
||||
|
||||
# check if video is a gif - will need to use cv fallback to read frames
|
||||
# use cv fallback if ffmpeg not installed or gif
|
||||
# if ffmpeg_path is None:
|
||||
# return self.load_video_cv_fallback(video, frame_load_cap, skip_first_frames)
|
||||
# otherwise, continue with ffmpeg
|
||||
|
||||
# args_dummy = [ffmpeg_path, "-i", video_path, "-f", "null", "-"]
|
||||
# try:
|
||||
# with subprocess.Popen(args_dummy, stdout=subprocess.DEVNULL, stderr=subprocess.PIPE) as proc:
|
||||
# for line in proc.stderr.readlines():
|
||||
# match = re.search(", ([1-9]|\\d{2,})x(\\d+)",line.decode('utf-8'))
|
||||
# if match is not None:
|
||||
# size = [int(match.group(1)), int(match.group(2))]
|
||||
# break
|
||||
# except Exception as e:
|
||||
# print(f"Retrying with opencv due to ffmpeg error: {e}")
|
||||
# return self.load_video_cv_fallback(video, frame_load_cap, skip_first_frames)
|
||||
# args_all_frames = [ffmpeg_path, "-i", video_path, "-v", "error",
|
||||
# "-pix_fmt", "rgb24"]
|
||||
|
||||
# vfilters = []
|
||||
|
||||
# if skip_first_frames > 0:
|
||||
# vfilters.append(f"select=gt(n\\,{skip_first_frames-1})")
|
||||
# if frame_load_cap > 0:
|
||||
# vfilters.append(f"select=gt({frame_load_cap}\\,n)")
|
||||
# #manually calculate aspect ratio to ensure reads remain aligned
|
||||
|
||||
# if len(vfilters) > 0:
|
||||
# args_all_frames += ["-vf", ",".join(vfilters)]
|
||||
|
||||
# args_all_frames += ["-f", "rawvideo", "-"]
|
||||
# images = []
|
||||
# try:
|
||||
# with subprocess.Popen(args_all_frames, stdout=subprocess.PIPE) as proc:
|
||||
# #Manually buffer enough bytes for an image
|
||||
# bpi = size[0]*size[1]*3
|
||||
# current_bytes = bytearray(bpi)
|
||||
# current_offset=0
|
||||
# while True:
|
||||
# bytes_read = proc.stdout.read(bpi - current_offset)
|
||||
# if bytes_read is None:#sleep to wait for more data
|
||||
# time.sleep(.2)
|
||||
# continue
|
||||
# if len(bytes_read) == 0:#EOF
|
||||
# break
|
||||
# current_bytes[current_offset:len(bytes_read)] = bytes_read
|
||||
# current_offset+=len(bytes_read)
|
||||
# if current_offset == bpi:
|
||||
# images.append(np.array(current_bytes, dtype=np.float32).reshape(size[1], size[0], 3) / 255.0)
|
||||
# current_offset = 0
|
||||
# except Exception as e:
|
||||
# print(f"Retrying with opencv due to ffmpeg error: {e}")
|
||||
# return self.load_video_cv_fallback(video, frame_load_cap, skip_first_frames)
|
||||
|
||||
# imgs=split_list(images,video_segment_frames,transition_frames)
|
||||
|
||||
# temp path
|
||||
tp=folder_paths.get_temp_directory()
|
||||
basename = os.path.basename(video_path) # 获取文件名
|
||||
name_without_extension = os.path.splitext(basename)[0] # 去掉文件后缀
|
||||
|
||||
folder_path = create_folder(tp,name_without_extension)
|
||||
|
||||
|
||||
# 导出的数据
|
||||
scenes_video,total_frames,fps=split_video(video_path,video_segment_frames,
|
||||
transition_frames,folder_path)
|
||||
|
||||
|
||||
# imgs=[torch.from_numpy(np.stack(im)) for im in imgs]
|
||||
|
||||
# images = torch.from_numpy(np.stack(images))
|
||||
|
||||
return (scenes_video,len(scenes_video), total_frames,fps,)
|
||||
|
||||
@classmethod
|
||||
def IS_CHANGED(s, video, **kwargs):
|
||||
image_path = folder_paths.get_annotated_filepath(video)
|
||||
m = hashlib.sha256()
|
||||
with open(image_path, 'rb') as f:
|
||||
m.update(f.read())
|
||||
return m.digest().hex()
|
||||
|
||||
@classmethod
|
||||
def VALIDATE_INPUTS(s, video, **kwargs):
|
||||
if not folder_paths.exists_annotated_filepath(video):
|
||||
return "Invalid image file: {}".format(video)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
# The code is based on ComfyUI-VideoHelperSuite modification.
|
||||
class VideoCombine_Adv:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
#Hide ffmpeg formats if ffmpeg isn't available
|
||||
if ffmpeg_path is not None:
|
||||
ffmpeg_formats = ["video/"+x[:-5] for x in folder_paths.get_filename_list("video_formats")]
|
||||
else:
|
||||
ffmpeg_formats = []
|
||||
return {
|
||||
"required": {
|
||||
"image_batch": ("IMAGE",),
|
||||
"frame_rate": (
|
||||
"INT",
|
||||
{"default": 8, "min": 1, "step": 1},
|
||||
),
|
||||
"loop_count": ("INT", {"default": 0, "min": 0, "max": 100, "step": 1}),
|
||||
"filename_prefix": ("STRING", {"default": "Comfyui"}),
|
||||
"format": (["image/gif", "image/webp"] + ffmpeg_formats,),
|
||||
"pingpong": ("BOOLEAN", {"default": False}),
|
||||
"save_image": ("BOOLEAN", {"default": True}),
|
||||
"metadata": ("BOOLEAN", {"default": False}),
|
||||
},
|
||||
"hidden": {
|
||||
"prompt": "PROMPT",
|
||||
"extra_pnginfo": "EXTRA_PNGINFO",
|
||||
},
|
||||
}
|
||||
|
||||
RETURN_TYPES = ()
|
||||
OUTPUT_NODE = True
|
||||
CATEGORY = "♾️Mixlab/Video"
|
||||
FUNCTION = "run"
|
||||
|
||||
def save_with_tempfile(self, args, metadata, file_path, frames, env):
|
||||
#Ensure temp directory exists
|
||||
os.makedirs(folder_paths.get_temp_directory(), exist_ok=True)
|
||||
|
||||
metadata_path = os.path.join(folder_paths.get_temp_directory(), "metadata.txt")
|
||||
#metadata from file should escape = ; # \ and newline
|
||||
#From my testing, though, only backslashes need escapes and = in particular causes problems
|
||||
#It is likely better to prioritize future compatibility with containers that don't support
|
||||
#or shouldn't use the comment tag for embedding metadata
|
||||
metadata = metadata.replace("\\","\\\\")
|
||||
metadata = metadata.replace(";","\\;")
|
||||
metadata = metadata.replace("#","\\#")
|
||||
#metadata = metadata.replace("=","\\=")
|
||||
metadata = metadata.replace("\n","\\\n")
|
||||
with open(metadata_path, "w") as f:
|
||||
f.write(";FFMETADATA1\n")
|
||||
f.write(metadata)
|
||||
args = args[:1] + ["-i", metadata_path] + args[1:] + [file_path]
|
||||
with subprocess.Popen(args, stdin=subprocess.PIPE, env=env) as proc:
|
||||
for frame in frames:
|
||||
proc.stdin.write(frame.tobytes())
|
||||
|
||||
def run(
|
||||
self,
|
||||
image_batch,
|
||||
frame_rate: int,
|
||||
loop_count: int,
|
||||
filename_prefix="AnimateDiff",
|
||||
format="image/gif",
|
||||
pingpong=False,
|
||||
save_image=True,
|
||||
metadata=False,
|
||||
prompt=None,
|
||||
extra_pnginfo=None,
|
||||
):
|
||||
images=image_batch
|
||||
|
||||
frames: List[Image.Image] = []
|
||||
for image in images:
|
||||
img = 255.0 * image.cpu().numpy()
|
||||
img = Image.fromarray(np.clip(img, 0, 255).astype(np.uint8))
|
||||
# resize 保证
|
||||
# 检查图像的高度是否是2的倍数,如果不是,则调整高度
|
||||
if img.height % 2 != 0:
|
||||
img = img.resize((img.width, img.height + 1))
|
||||
|
||||
# 检查图像的宽度是否是2的倍数,如果不是,则调整宽度
|
||||
if img.width % 2 != 0:
|
||||
img = img.resize((img.width + 1, img.height))
|
||||
|
||||
frames.append(img)
|
||||
|
||||
# get output information
|
||||
output_dir = (
|
||||
folder_paths.get_output_directory()
|
||||
if save_image
|
||||
else folder_paths.get_temp_directory()
|
||||
)
|
||||
(
|
||||
full_output_folder,
|
||||
filename,
|
||||
counter,
|
||||
subfolder,
|
||||
_,
|
||||
) = folder_paths.get_save_image_path(filename_prefix, output_dir)
|
||||
|
||||
metadata = PngInfo()
|
||||
video_metadata = {}
|
||||
if prompt is not None:
|
||||
metadata.add_text("prompt", json.dumps(prompt))
|
||||
video_metadata["prompt"] = prompt
|
||||
if extra_pnginfo is not None:
|
||||
for x in extra_pnginfo:
|
||||
metadata.add_text(x, json.dumps(extra_pnginfo[x]))
|
||||
video_metadata[x] = extra_pnginfo[x]
|
||||
|
||||
# 取消保存metadata
|
||||
if metadata==False:
|
||||
metadata = PngInfo()
|
||||
|
||||
# save first frame as png to keep metadata
|
||||
file = f"{filename}_{counter:05}_.png"
|
||||
file_path = os.path.join(full_output_folder, file)
|
||||
frames[0].save(
|
||||
file_path,
|
||||
pnginfo=metadata,
|
||||
compress_level=4,
|
||||
)
|
||||
if pingpong:
|
||||
frames = frames + frames[-2:0:-1]
|
||||
|
||||
format_type, format_ext = format.split("/")
|
||||
file = f"{filename}_{counter:05}_.{format_ext}"
|
||||
file_path = os.path.join(full_output_folder, file)
|
||||
if format_type == "image":
|
||||
# Use pillow directly to save an animated image
|
||||
frames[0].save(
|
||||
file_path,
|
||||
format=format_ext.upper(),
|
||||
save_all=True,
|
||||
append_images=frames[1:],
|
||||
duration=round(1000 / frame_rate),
|
||||
loop=loop_count,
|
||||
compress_level=4,
|
||||
)
|
||||
else:
|
||||
# Use ffmpeg to save a video
|
||||
if ffmpeg_path is None:
|
||||
#Should never be reachable
|
||||
raise ProcessLookupError("Could not find ffmpeg")
|
||||
|
||||
video_format_path = folder_paths.get_full_path("video_formats", format_ext + ".json")
|
||||
with open(video_format_path, 'r') as stream:
|
||||
video_format = json.load(stream)
|
||||
file = f"{filename}_{counter:05}_.{video_format['extension']}"
|
||||
file_path = os.path.join(full_output_folder, file)
|
||||
dimensions = f"{frames[0].width}x{frames[0].height}"
|
||||
metadata_args = ["-metadata", "comment=" + json.dumps(video_metadata)]
|
||||
args = [ffmpeg_path, "-v", "error", "-f", "rawvideo", "-pix_fmt", "rgb24",
|
||||
"-s", dimensions, "-r", str(frame_rate), "-i", "-"] \
|
||||
+ video_format['main_pass']
|
||||
# On linux, max arg length is Pagesize * 32 -> 131072
|
||||
# On windows, this around 32767 but seems to vary wildly by > 500
|
||||
# in a manor not solely related to other arguments
|
||||
if os.name == 'posix':
|
||||
max_arg_length = 4096*32
|
||||
else:
|
||||
max_arg_length = 32767 - len(" ".join(args + [metadata_args[0]] + [file_path])) - 1
|
||||
#test max limit
|
||||
#metadata_args[1] = metadata_args[1] + "a"*(max_arg_length - len(metadata_args[1])-1)
|
||||
|
||||
env=os.environ.copy()
|
||||
if "environment" in video_format:
|
||||
env.update(video_format["environment"])
|
||||
if len(metadata_args[1]) >= max_arg_length:
|
||||
print(f"Using fallback file for extremely long metadata: {len(metadata_args[1])}/{max_arg_length}")
|
||||
self.save_with_tempfile(args, metadata_args[1], file_path, frames, env)
|
||||
else:
|
||||
try:
|
||||
with subprocess.Popen(args + metadata_args + [file_path],
|
||||
stdin=subprocess.PIPE, env=env) as proc:
|
||||
for frame in frames:
|
||||
proc.stdin.write(frame.tobytes())
|
||||
except FileNotFoundError as e:
|
||||
if "winerror" in dir(e) and e.winerror == 206:
|
||||
print("Metadata was too long. Retrying with fallback file")
|
||||
self.save_with_tempfile(args, metadata_args[1], file_path, frames, env)
|
||||
else:
|
||||
raise
|
||||
except OSError as e:
|
||||
if "errno" in dir(e) and e.errno == 7:
|
||||
print("Metadata was too long. Retrying with fallback file")
|
||||
self.save_with_tempfile(args, metadata_args[1], file_path, frames, env)
|
||||
else:
|
||||
raise
|
||||
|
||||
previews = [
|
||||
{
|
||||
"filename": file,
|
||||
"subfolder": subfolder,
|
||||
"type": "output" if save_image else "temp",
|
||||
"format": format,
|
||||
}
|
||||
]
|
||||
return {"ui": {"gifs": previews}}
|
||||
|
||||
|
||||
class VAEEncodeForInpaint_Frames:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {"required": {
|
||||
"vae": ("VAE", ),
|
||||
"images": ("IMAGE", ),
|
||||
"masks": ("MASK", ),
|
||||
"grow_mask_by": ("INT", {"default": 6, "min": 0, "max": 64, "step": 1}),
|
||||
}}
|
||||
|
||||
FUNCTION = "encode"
|
||||
|
||||
|
||||
RETURN_TYPES = ("LATENT",)
|
||||
RETURN_NAMES = ("LATENT",)
|
||||
|
||||
CATEGORY = "♾️Mixlab/Video"
|
||||
|
||||
OUTPUT_NODE = True
|
||||
INPUT_IS_LIST = True
|
||||
OUTPUT_IS_LIST = (True,)
|
||||
|
||||
|
||||
def encode(self, vae, images, masks, grow_mask_by=[6]):
|
||||
vae=vae[0]
|
||||
grow_mask_by=grow_mask_by[0]
|
||||
|
||||
result=[]
|
||||
|
||||
for i in range(len(images)):
|
||||
pixels=images[i]
|
||||
mask=masks[i]
|
||||
|
||||
|
||||
x = (pixels.shape[1] // 8) * 8
|
||||
y = (pixels.shape[2] // 8) * 8
|
||||
mask = torch.nn.functional.interpolate(mask.reshape((-1, 1, mask.shape[-2], mask.shape[-1])), size=(pixels.shape[1], pixels.shape[2]), mode="bilinear")
|
||||
|
||||
pixels = pixels.clone()
|
||||
if pixels.shape[1] != x or pixels.shape[2] != y:
|
||||
x_offset = (pixels.shape[1] % 8) // 2
|
||||
y_offset = (pixels.shape[2] % 8) // 2
|
||||
pixels = pixels[:,x_offset:x + x_offset, y_offset:y + y_offset,:]
|
||||
mask = mask[:,:,x_offset:x + x_offset, y_offset:y + y_offset]
|
||||
|
||||
#grow mask by a few pixels to keep things seamless in latent space
|
||||
if grow_mask_by == 0:
|
||||
mask_erosion = mask
|
||||
else:
|
||||
kernel_tensor = torch.ones((1, 1, grow_mask_by, grow_mask_by))
|
||||
padding = math.ceil((grow_mask_by - 1) / 2)
|
||||
|
||||
mask_erosion = torch.clamp(torch.nn.functional.conv2d(mask.round(), kernel_tensor, padding=padding), 0, 1)
|
||||
|
||||
m = (1.0 - mask.round()).squeeze(1)
|
||||
for i in range(3):
|
||||
pixels[:,:,:,i] -= 0.5
|
||||
pixels[:,:,:,i] *= m
|
||||
pixels[:,:,:,i] += 0.5
|
||||
t = vae.encode(pixels)
|
||||
|
||||
result.append({"samples":t, "noise_mask": (mask_erosion[:,:,:x,:y].round())})
|
||||
|
||||
|
||||
return (result, )
|
||||
@@ -0,0 +1,45 @@
|
||||
from comfy.ldm.modules.attention import default, optimized_attention, optimized_attention_masked
|
||||
from .style_functions import adain, concat_first
|
||||
|
||||
class VisualStyleProcessor(object):
|
||||
def __init__(self,
|
||||
module_self,
|
||||
keys_scale: float = 1.0,
|
||||
enabled: bool = True,
|
||||
adain_queries: bool = True,
|
||||
adain_keys: bool = True,
|
||||
adain_values: bool = False
|
||||
):
|
||||
self.module_self = module_self
|
||||
self.keys_scale = keys_scale
|
||||
self.enabled = enabled
|
||||
self.adain_queries = adain_queries
|
||||
self.adain_keys = adain_keys
|
||||
self.adain_values = adain_values
|
||||
|
||||
def visual_style_forward(self, x, context, value, mask=None):
|
||||
q = self.module_self.to_q(x)
|
||||
context = default(context, x)
|
||||
k = self.module_self.to_k(context)
|
||||
if value is not None:
|
||||
v = self.module_self.to_v(value)
|
||||
del value
|
||||
else:
|
||||
v = self.module_self.to_v(context)
|
||||
|
||||
if self.enabled:
|
||||
if self.adain_queries:
|
||||
q = adain(q)
|
||||
if self.adain_keys:
|
||||
k = adain(k)
|
||||
if self.adain_values:
|
||||
v = adain(v)
|
||||
|
||||
k = concat_first(k, -2, self.keys_scale)
|
||||
v = concat_first(v, -2)
|
||||
|
||||
if mask is None:
|
||||
out = optimized_attention(q, k, v, self.module_self.heads)
|
||||
else:
|
||||
out = optimized_attention_masked(q, k, v, self.module_self.heads, mask)
|
||||
return self.module_self.to_out(out)
|
||||
@@ -0,0 +1,60 @@
|
||||
import torch
|
||||
|
||||
from einops import rearrange
|
||||
from dataclasses import dataclass
|
||||
|
||||
T = torch.Tensor
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class StyleAlignedArgs:
|
||||
share_group_norm: bool = True
|
||||
share_layer_norm: bool = True,
|
||||
share_attention: bool = True
|
||||
adain_queries: bool = True
|
||||
adain_keys: bool = True
|
||||
adain_values: bool = False
|
||||
full_attention_share: bool = False
|
||||
keys_scale: float = 1.
|
||||
only_self_level: float = 0.
|
||||
|
||||
def expand_first(feat: T, scale=1., ) -> T:
|
||||
b = feat.shape[0]
|
||||
feat_style = torch.stack((feat[0], feat[b // 2])).unsqueeze(1)
|
||||
if scale == 1:
|
||||
feat_style = feat_style.expand(2, b // 2, *feat.shape[1:])
|
||||
else:
|
||||
feat_style = feat_style.repeat(1, b // 2, 1, 1, 1)
|
||||
feat_style = torch.cat([feat_style[:, :1], scale * feat_style[:, 1:]], dim=1)
|
||||
return feat_style.reshape(*feat.shape)
|
||||
|
||||
|
||||
def concat_first(feat: T, dim=2, scale=1.) -> T:
|
||||
feat_style = expand_first(feat, scale=scale)
|
||||
return torch.cat((feat, feat_style), dim=dim)
|
||||
|
||||
|
||||
def calc_mean_std(feat, eps: float = 1e-5) -> tuple[T, T]:
|
||||
feat_std = (feat.var(dim=-2, keepdims=True) + eps).sqrt()
|
||||
feat_mean = feat.mean(dim=-2, keepdims=True)
|
||||
return feat_mean, feat_std
|
||||
|
||||
|
||||
def adain(feat: T) -> T:
|
||||
feat_mean, feat_std = calc_mean_std(feat)
|
||||
feat_style_mean = expand_first(feat_mean)
|
||||
feat_style_std = expand_first(feat_std)
|
||||
feat = (feat - feat_mean) / feat_std
|
||||
feat = feat * feat_style_std + feat_style_mean
|
||||
return feat
|
||||
|
||||
def swapping_attention(key, value, chunk_size=2):
|
||||
chunk_length = key.size()[0] // chunk_size # [text-condition, null-condition]
|
||||
reference_image_index = [0] * chunk_length # [0 0 0 0 0]
|
||||
key = rearrange(key, "(b f) d c -> b f d c", f=chunk_length)
|
||||
key = key[:, reference_image_index] # ref to all
|
||||
key = rearrange(key, "b f d c -> (b f) d c")
|
||||
value = rearrange(value, "(b f) d c -> b f d c", f=chunk_length)
|
||||
value = value[:, reference_image_index] # ref to all
|
||||
value = rearrange(value, "b f d c -> (b f) d c")
|
||||
|
||||
return key, value
|
||||
@@ -0,0 +1,8 @@
|
||||
import folder_paths
|
||||
|
||||
# 外挂一个文件,用来编写新的节点
|
||||
def run(v):
|
||||
|
||||
output_dir = folder_paths.get_temp_directory()
|
||||
|
||||
print('1323',v,output_dir)
|
||||
@@ -3,4 +3,10 @@ pyOpenSSL
|
||||
watchdog
|
||||
opencv-python-headless
|
||||
matplotlib
|
||||
openai
|
||||
openai
|
||||
simple-lama-inpainting
|
||||
clip-interrogator==0.6.0
|
||||
transformers>=4.36.0
|
||||
zhipuai
|
||||
lark-parser
|
||||
imageio-ffmpeg
|
||||
@@ -0,0 +1,630 @@
|
||||
import { app } from '../../../scripts/app.js'
|
||||
import { api } from '../../../scripts/api.js'
|
||||
import { $el } from '../../../scripts/ui.js'
|
||||
|
||||
const getLocalData = key => {
|
||||
let data = {}
|
||||
try {
|
||||
data = JSON.parse(localStorage.getItem(key)) || {}
|
||||
} catch (error) {
|
||||
return {}
|
||||
}
|
||||
return data
|
||||
}
|
||||
function getContentTypeFromBase64 (base64Data) {
|
||||
const regex = /^data:(.+);base64,/
|
||||
const matches = base64Data.match(regex)
|
||||
if (matches && matches.length >= 2) {
|
||||
return matches[1]
|
||||
}
|
||||
return null
|
||||
}
|
||||
function base64ToBlobFromURL (base64URL, contentType) {
|
||||
return fetch(base64URL).then(response => response.blob())
|
||||
}
|
||||
const setLocalDataOfWin = (key, value) => {
|
||||
localStorage.setItem(key, JSON.stringify(value))
|
||||
// window[key] = value
|
||||
}
|
||||
async function uploadImage (blob, fileType = '.svg', filename) {
|
||||
// const blob = await (await fetch(src)).blob();
|
||||
const body = new FormData()
|
||||
body.append(
|
||||
'image',
|
||||
new File([blob], (filename || new Date().getTime()) + fileType)
|
||||
)
|
||||
|
||||
const resp = await api.fetchApi('/upload/image', {
|
||||
method: 'POST',
|
||||
body
|
||||
})
|
||||
|
||||
// console.log(resp)
|
||||
let data = await resp.json()
|
||||
let { name, subfolder } = data
|
||||
let src = api.apiURL(
|
||||
`/view?filename=${encodeURIComponent(
|
||||
name
|
||||
)}&type=input&subfolder=${subfolder}${app.getPreviewFormatParam()}${app.getRandParam()}`
|
||||
)
|
||||
|
||||
return src
|
||||
}
|
||||
|
||||
function createImage (url) {
|
||||
let im = new Image()
|
||||
return new Promise((res, rej) => {
|
||||
im.onload = () => res(im)
|
||||
im.src = url
|
||||
})
|
||||
}
|
||||
|
||||
const parseImage = url => {
|
||||
return new Promise((res, rej) => {
|
||||
fetch(url)
|
||||
.then(response => response.blob())
|
||||
.then(blob => {
|
||||
const reader = new FileReader()
|
||||
reader.onloadend = () => {
|
||||
const base64data = reader.result
|
||||
res(base64data)
|
||||
// 在这里可以将base64数据用于进一步处理或显示图片
|
||||
}
|
||||
reader.readAsDataURL(blob)
|
||||
})
|
||||
.catch(error => {
|
||||
console.log('发生错误:', error)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
function get_position_style (ctx, widget_width, y, node_height) {
|
||||
const MARGIN = 4 // the margin around the html element
|
||||
|
||||
/* Create a transform that deals with all the scrolling and zooming */
|
||||
const elRect = ctx.canvas.getBoundingClientRect()
|
||||
const transform = new DOMMatrix()
|
||||
.scaleSelf(
|
||||
elRect.width / ctx.canvas.width,
|
||||
elRect.height / ctx.canvas.height
|
||||
)
|
||||
.multiplySelf(ctx.getTransform())
|
||||
.translateSelf(MARGIN, MARGIN + y)
|
||||
|
||||
return {
|
||||
transformOrigin: '0 0',
|
||||
transform: transform,
|
||||
left: `0`,
|
||||
top: `0`,
|
||||
cursor: 'pointer',
|
||||
position: 'absolute',
|
||||
maxWidth: `${widget_width - MARGIN * 2}px`,
|
||||
// maxHeight: `${node_height - MARGIN * 2}px`, // we're assuming we have the whole height of the node
|
||||
width: `${widget_width - MARGIN * 2}px`,
|
||||
// height: `${node_height * 0.3 - MARGIN * 2}px`,
|
||||
// background: '#EEEEEE',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
// alignItems: 'center',
|
||||
justifyContent: 'space-around'
|
||||
}
|
||||
}
|
||||
|
||||
async function extractMaterial (
|
||||
modelViewerVariants,
|
||||
selectMaterial,
|
||||
material_img
|
||||
) {
|
||||
// 材质
|
||||
const materialsNames = []
|
||||
for (
|
||||
let index = 0;
|
||||
index < modelViewerVariants.model.materials.length;
|
||||
index++
|
||||
) {
|
||||
let m = modelViewerVariants.model.materials[index]
|
||||
let thumbUrl
|
||||
try {
|
||||
thumbUrl =
|
||||
await m.pbrMetallicRoughness.baseColorTexture.texture.source.createThumbnail(
|
||||
1024,
|
||||
1024
|
||||
)
|
||||
} catch (error) {}
|
||||
if (thumbUrl)
|
||||
materialsNames.push({
|
||||
value: m.name,
|
||||
text: `#${index} ${m.name}`,
|
||||
index,
|
||||
thumbUrl
|
||||
})
|
||||
}
|
||||
|
||||
selectMaterial.innerHTML = ''
|
||||
material_img.innerHTML = ''
|
||||
|
||||
for (let index = 0; index < materialsNames.length; index++) {
|
||||
const name = materialsNames[index]
|
||||
const option = document.createElement('option')
|
||||
option.value = name.thumbUrl
|
||||
option.textContent = name.text
|
||||
option.setAttribute('data-index', index)
|
||||
selectMaterial.appendChild(option)
|
||||
let img = new Image()
|
||||
img.src = name.thumbUrl
|
||||
// img.setAttribute('data-index',name.index)
|
||||
img.style.width = '40px'
|
||||
material_img.appendChild(img)
|
||||
if (index == 0) {
|
||||
material_img.setAttribute('src', name.thumbUrl)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function changeMaterial (
|
||||
modelViewerVariants,
|
||||
targetMaterial,
|
||||
newImageUrl
|
||||
) {
|
||||
const targetTexture = await modelViewerVariants.createTexture(newImageUrl)
|
||||
// 用图片创建纹理
|
||||
targetMaterial.pbrMetallicRoughness.baseColorTexture.setTexture(targetTexture)
|
||||
}
|
||||
|
||||
app.registerExtension({
|
||||
name: 'Mixlab.3D.3DImage',
|
||||
async getCustomWidgets (app) {
|
||||
return {
|
||||
THREED (node, inputName, inputData, app) {
|
||||
// console.log('##node', node, inputName, inputData)
|
||||
const widget = {
|
||||
type: inputData[0], // the type, CHEESE
|
||||
name: inputName, // the name, slice
|
||||
size: [128, 88], // a default size
|
||||
draw (ctx, node, width, y) {},
|
||||
computeSize (...args) {
|
||||
return [128, 88] // a method to compute the current size of the widget
|
||||
},
|
||||
async serializeValue (nodeId, widgetIndex) {
|
||||
let d = getLocalData('_mixlab_3d_image')
|
||||
// console.log('serializeValue', node)
|
||||
if (d && d[node.id]) {
|
||||
let { url, bg, material } = d[node.id]
|
||||
let data = {}
|
||||
if (url) {
|
||||
data.image = await parseImage(url)
|
||||
}
|
||||
if (bg) {
|
||||
data.bg_image = await parseImage(bg)
|
||||
if (!data.bg_image.match('data:image/')) {
|
||||
delete data.bg_image
|
||||
}
|
||||
}
|
||||
|
||||
if (material) {
|
||||
data.material = await parseImage(material)
|
||||
}
|
||||
|
||||
return JSON.parse(JSON.stringify(data))
|
||||
} else {
|
||||
return {}
|
||||
}
|
||||
}
|
||||
}
|
||||
node.addCustomWidget(widget)
|
||||
return widget
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
async beforeRegisterNodeDef (nodeType, nodeData, app) {
|
||||
if (nodeType.comfyClass == '3DImage') {
|
||||
const orig_nodeCreated = nodeType.prototype.onNodeCreated
|
||||
nodeType.prototype.onNodeCreated = async function () {
|
||||
orig_nodeCreated?.apply(this, arguments)
|
||||
|
||||
const uploadWidget = this.widgets.filter(w => w.name == 'upload')[0]
|
||||
|
||||
const widget = {
|
||||
type: 'div',
|
||||
name: 'upload-preview',
|
||||
draw (ctx, node, widget_width, y, widget_height) {
|
||||
Object.assign(
|
||||
this.div.style,
|
||||
get_position_style(ctx, widget_width, 88, node.size[1])
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
widget.div = $el('div', {})
|
||||
widget.div.style.width = `120px`
|
||||
|
||||
document.body.appendChild(widget.div)
|
||||
|
||||
const inputDiv = (key, placeholder, preview) => {
|
||||
let div = document.createElement('div')
|
||||
const ip = document.createElement('input')
|
||||
ip.type = 'file'
|
||||
ip.className = `${'comfy-multiline-input'} ${placeholder}`
|
||||
div.style = `display: flex;
|
||||
align-items: center;
|
||||
margin: 6px 8px;
|
||||
margin-top: 0;`
|
||||
ip.placeholder = placeholder
|
||||
// ip.value = value
|
||||
|
||||
ip.style = `outline: none;
|
||||
border: none;
|
||||
padding: 4px;
|
||||
width: 60%;cursor: pointer;
|
||||
height: 32px;`
|
||||
const label = document.createElement('label')
|
||||
label.style = 'font-size: 10px;min-width:32px'
|
||||
label.innerText = placeholder
|
||||
div.appendChild(label)
|
||||
div.appendChild(ip)
|
||||
|
||||
let that = this,
|
||||
filename = new Date().getTime()
|
||||
|
||||
ip.addEventListener('change', async event => {
|
||||
const file = event.target.files[0]
|
||||
const reader = new FileReader()
|
||||
filename = new Date().getTime()
|
||||
// 读取文件内容
|
||||
reader.onload = async e => {
|
||||
const fileURL = URL.createObjectURL(file)
|
||||
// console.log('文件URL: ', fileURL)
|
||||
let html = `<model-viewer src="${fileURL}"
|
||||
min-field-of-view="0deg" max-field-of-view="180deg"
|
||||
shadow-intensity="1"
|
||||
camera-controls
|
||||
touch-action="pan-y">
|
||||
|
||||
<div class="controls">
|
||||
<div>Variant: <select class="variant"></select></div>
|
||||
<div>Material: <select class="material"></select></div>
|
||||
<div>Material: <div class="material_img"> </div></div>
|
||||
<div><button class="bg">BG</button></div>
|
||||
<div><button class="export">Export GLB</button></div>
|
||||
|
||||
</div></model-viewer>`
|
||||
|
||||
preview.innerHTML = html
|
||||
if (that.size[1] < 400) {
|
||||
that.setSize([that.size[0], that.size[1] + 300])
|
||||
app.canvas.draw(true, true)
|
||||
}
|
||||
|
||||
const modelViewerVariants = preview.querySelector('model-viewer')
|
||||
const select = preview.querySelector('.variant')
|
||||
const selectMaterial = preview.querySelector('.material')
|
||||
const material_img = preview.querySelector('.material_img')
|
||||
const bg = preview.querySelector('.bg')
|
||||
const exportGLB = preview.querySelector('.export')
|
||||
|
||||
if (modelViewerVariants) {
|
||||
modelViewerVariants.style.width = `${that.size[0] - 24}px`
|
||||
modelViewerVariants.style.height = `${that.size[1] - 48}px`
|
||||
}
|
||||
|
||||
modelViewerVariants.addEventListener('load', async () => {
|
||||
const names = modelViewerVariants.availableVariants
|
||||
|
||||
// 变量
|
||||
for (const name of names) {
|
||||
const option = document.createElement('option')
|
||||
option.value = name
|
||||
option.textContent = name
|
||||
select.appendChild(option)
|
||||
}
|
||||
// Adds a default option.
|
||||
if (names.length === 0) {
|
||||
const option = document.createElement('option')
|
||||
option.value = 'default'
|
||||
option.textContent = 'Default'
|
||||
select.appendChild(option)
|
||||
}
|
||||
|
||||
// 材质
|
||||
extractMaterial(
|
||||
modelViewerVariants,
|
||||
selectMaterial,
|
||||
material_img
|
||||
)
|
||||
})
|
||||
|
||||
let timer = null
|
||||
const delay = 500 // 延迟时间,单位为毫秒
|
||||
|
||||
async function checkCameraChange () {
|
||||
let dd = getLocalData(key)
|
||||
let base64Data = modelViewerVariants.toDataURL()
|
||||
|
||||
const contentType = getContentTypeFromBase64(base64Data)
|
||||
|
||||
const blob = await base64ToBlobFromURL(base64Data, contentType)
|
||||
|
||||
// const fileBlob = new Blob([e.target.result], { type: file.type });
|
||||
let url = await uploadImage(blob, '.png')
|
||||
// console.log(url)
|
||||
|
||||
let bg_blob = await base64ToBlobFromURL(
|
||||
'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mN88uXrPQAFwwK/6xJ6CQAAAABJRU5ErkJggg=='
|
||||
)
|
||||
let url_bg = await uploadImage(bg_blob, '.png')
|
||||
// console.log('url_bg',url_bg)
|
||||
|
||||
if (!dd[that.id]) {
|
||||
dd[that.id] = { url, bg: url_bg }
|
||||
} else {
|
||||
dd[that.id] = { ...dd[that.id], url }
|
||||
}
|
||||
|
||||
// 材质贴图
|
||||
let thumbUrl = material_img.getAttribute('src')
|
||||
if (thumbUrl) {
|
||||
let tb = await base64ToBlobFromURL(thumbUrl)
|
||||
let tUrl = await uploadImage(tb, '.png')
|
||||
// console.log('材质贴图', tUrl, thumbUrl)
|
||||
dd[that.id].material = tUrl
|
||||
}
|
||||
|
||||
setLocalDataOfWin(key, dd)
|
||||
}
|
||||
|
||||
function startTimer () {
|
||||
if (timer) clearTimeout(timer)
|
||||
timer = setTimeout(checkCameraChange, delay)
|
||||
}
|
||||
|
||||
modelViewerVariants.addEventListener('camera-change', startTimer)
|
||||
|
||||
select.addEventListener('input', async event => {
|
||||
modelViewerVariants.variantName =
|
||||
event.target.value === 'default' ? null : event.target.value
|
||||
// 材质
|
||||
await extractMaterial(
|
||||
modelViewerVariants,
|
||||
selectMaterial,
|
||||
material_img
|
||||
)
|
||||
checkCameraChange()
|
||||
})
|
||||
|
||||
selectMaterial.addEventListener('input', event => {
|
||||
// console.log(selectMaterial.value)
|
||||
material_img.setAttribute('src', selectMaterial.value)
|
||||
|
||||
if (selectMaterial.getAttribute('data-new-material')) {
|
||||
let index =
|
||||
~~selectMaterial.selectedOptions[0].getAttribute(
|
||||
'data-index'
|
||||
)
|
||||
changeMaterial(
|
||||
modelViewerVariants,
|
||||
modelViewerVariants.model.materials[index],
|
||||
selectMaterial.getAttribute('data-new-material')
|
||||
)
|
||||
}
|
||||
|
||||
checkCameraChange()
|
||||
})
|
||||
|
||||
bg.addEventListener('click', () => {
|
||||
// 创建一个input元素
|
||||
var input = document.createElement('input')
|
||||
input.type = 'file'
|
||||
|
||||
// 监听input的change事件
|
||||
input.addEventListener('change', function () {
|
||||
// 获取上传的文件
|
||||
var file = input.files[0]
|
||||
|
||||
// 创建一个FileReader对象来读取文件
|
||||
var reader = new FileReader()
|
||||
|
||||
// 监听FileReader的load事件
|
||||
reader.addEventListener('load', async () => {
|
||||
let base64 = reader.result
|
||||
// 将读取的文件内容设置为div的背景
|
||||
preview.style.backgroundImage = 'url(' + base64 + ')'
|
||||
|
||||
const contentType = getContentTypeFromBase64(base64)
|
||||
|
||||
const blob = await base64ToBlobFromURL(base64, contentType)
|
||||
|
||||
// const fileBlob = new Blob([e.target.result], { type: file.type });
|
||||
let bg_url = await uploadImage(blob, '.png')
|
||||
let bg_img = await createImage(base64)
|
||||
|
||||
let dd = getLocalData(key)
|
||||
// console.log(dd[that.id],bg_url)
|
||||
if (!dd[that.id]) dd[that.id] = { url: '', bg: bg_url }
|
||||
dd[that.id] = {
|
||||
...dd[that.id],
|
||||
bg: bg_url,
|
||||
bg_w: bg_img.naturalWidth,
|
||||
bg_h: bg_img.naturalHeight
|
||||
}
|
||||
|
||||
setLocalDataOfWin(key, dd)
|
||||
|
||||
// 更新尺寸
|
||||
let w = that.size[0] - 24,
|
||||
h = (w * bg_img.naturalHeight) / bg_img.naturalWidth
|
||||
|
||||
if (modelViewerVariants) {
|
||||
modelViewerVariants.style.width = `${w}px`
|
||||
modelViewerVariants.style.height = `${h}px`
|
||||
}
|
||||
preview.style.width = `${w}px`
|
||||
})
|
||||
|
||||
// 读取文件
|
||||
reader.readAsDataURL(file)
|
||||
})
|
||||
|
||||
// 触发input的点击事件
|
||||
input.click()
|
||||
})
|
||||
|
||||
exportGLB.addEventListener('click', async () => {
|
||||
const glTF = await modelViewerVariants.exportScene()
|
||||
const file = new File([glTF], 'export.glb')
|
||||
const link = document.createElement('a')
|
||||
link.download = file.name
|
||||
link.href = URL.createObjectURL(file)
|
||||
link.click()
|
||||
})
|
||||
|
||||
uploadWidget.value = await uploadWidget.serializeValue()
|
||||
|
||||
// 更新尺寸
|
||||
let dd = getLocalData(key)
|
||||
// console.log(dd[that.id],bg_url)
|
||||
if (dd[that.id]) {
|
||||
const { bg_w, bg_h } = dd[that.id]
|
||||
if (bg_h && bg_w) {
|
||||
let w = that.size[0] - 24,
|
||||
h = (w * bg_h) / bg_w
|
||||
|
||||
if (modelViewerVariants) {
|
||||
modelViewerVariants.style.width = `${w}px`
|
||||
modelViewerVariants.style.height = `${h}px`
|
||||
}
|
||||
preview.style.width = `${w}px`
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 以文本形式读取文件
|
||||
reader.readAsDataURL(file)
|
||||
})
|
||||
return div
|
||||
}
|
||||
|
||||
let preview = document.createElement('div')
|
||||
preview.className = 'preview'
|
||||
preview.style = `margin-top: 12px;display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;background-repeat: no-repeat;background-size: contain;`
|
||||
|
||||
let upload = inputDiv('_mixlab_3d_image', '3D Model', preview)
|
||||
|
||||
widget.div.appendChild(upload)
|
||||
widget.div.appendChild(preview)
|
||||
this.addCustomWidget(widget)
|
||||
|
||||
const onResize = this.onResize
|
||||
let that = this
|
||||
this.onResize = function () {
|
||||
let modelViewerVariants = preview.querySelector('model-viewer')
|
||||
|
||||
// 更新尺寸
|
||||
let dd = getLocalData('_mixlab_3d_image')
|
||||
// console.log(dd[that.id],bg_url)
|
||||
if (dd[that.id]) {
|
||||
const { bg_w, bg_h } = dd[that.id]
|
||||
if (bg_h && bg_w) {
|
||||
let w = that.size[0] - 24,
|
||||
h = (w * bg_h) / bg_w
|
||||
|
||||
if (modelViewerVariants) {
|
||||
modelViewerVariants.style.width = `${w}px`
|
||||
modelViewerVariants.style.height = `${h}px`
|
||||
}
|
||||
preview.style.width = `${w}px`
|
||||
}
|
||||
}
|
||||
|
||||
return onResize?.apply(this, arguments)
|
||||
}
|
||||
|
||||
const onRemoved = this.onRemoved
|
||||
this.onRemoved = () => {
|
||||
upload.remove()
|
||||
preview.remove()
|
||||
widget.div.remove()
|
||||
return onRemoved?.()
|
||||
}
|
||||
|
||||
if (this.onResize) {
|
||||
this.onResize(this.size)
|
||||
}
|
||||
// this.isVirtualNode = true
|
||||
this.serialize_widgets = false //需要保存参数
|
||||
}
|
||||
|
||||
const onExecuted = nodeType.prototype.onExecuted
|
||||
nodeType.prototype.onExecuted = function (message) {
|
||||
const r = onExecuted?.apply?.(this, arguments)
|
||||
|
||||
let div = this.widgets.filter(d => d.div)[0]?.div
|
||||
console.log('Test', this.widgets)
|
||||
|
||||
let material = message.material[0]
|
||||
if (material) {
|
||||
const { filename, subfolder, type } = material
|
||||
let src = api.apiURL(
|
||||
`/view?filename=${encodeURIComponent(
|
||||
filename
|
||||
)}&type=${type}&subfolder=${subfolder}${app.getPreviewFormatParam()}${app.getRandParam()}`
|
||||
)
|
||||
|
||||
const modelViewerVariants = div.querySelector('model-viewer')
|
||||
|
||||
const selectMaterial = div.querySelector('.material')
|
||||
|
||||
let index =
|
||||
~~selectMaterial.selectedOptions[0].getAttribute('data-index')
|
||||
|
||||
selectMaterial.setAttribute('data-new-material', src)
|
||||
|
||||
changeMaterial(
|
||||
modelViewerVariants,
|
||||
modelViewerVariants.model.materials[index],
|
||||
src
|
||||
)
|
||||
}
|
||||
|
||||
this.onResize?.(this.size)
|
||||
|
||||
return r
|
||||
}
|
||||
}
|
||||
},
|
||||
async loadedGraphNode (node, app) {
|
||||
// Fires every time a node is constructed
|
||||
// You can modify widgets/add handlers/etc here
|
||||
const sleep = (t = 1000) => {
|
||||
return new Promise((res, rej) => {
|
||||
setTimeout(() => res(1), t)
|
||||
})
|
||||
}
|
||||
if (node.type === '3DImage') {
|
||||
// await sleep(0)
|
||||
let widget = node.widgets.filter(w => w.name === 'upload-preview')[0]
|
||||
|
||||
let dd = getLocalData('_mixlab_3d_image')
|
||||
|
||||
let id = node.id
|
||||
// console.log('3dImage load', node.widgets[0], node.widgets)
|
||||
if (!dd[id]) return
|
||||
|
||||
let { url, bg } = dd[id]
|
||||
if (!url) return
|
||||
// let base64 = await parseImage(url)
|
||||
|
||||
let pre = widget.div.querySelector('.preview')
|
||||
pre.style.width = `${node.size[0]}px`
|
||||
pre.innerHTML = `
|
||||
${url ? `<img src="${url}" style="width:100%"/>` : ''}
|
||||
`
|
||||
pre.style.backgroundImage = 'url(' + bg + ')'
|
||||
|
||||
const uploadWidget = node.widgets.filter(w => w.name == 'upload')[0]
|
||||
uploadWidget.value = await uploadWidget.serializeValue()
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,683 @@
|
||||
import { app } from '../../../scripts/app.js'
|
||||
import { $el } from '../../../scripts/ui.js'
|
||||
import { api } from '../../../scripts/api.js'
|
||||
|
||||
const base64Df =
|
||||
'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAMCAYAAABWdVznAAAAAXNSR0IArs4c6QAAALZJREFUKFOFkLERwjAQBPdbgBkInECGaMLUQDsE0AkRVRAYWqAByxldPPOWHwnw4OBGye1p50UDSoA+W2ABLPN7i+C5dyC6R/uiAUXRQCs0bXoNIu4QPQzAxDKxHoALOrZcqtiyR/T6CXw7+3IGHhkYcy6BOR2izwT8LptG8rbMiCRAUb+CQ6WzQVb0SNOi5Z2/nX35DRyb/ENazhpWKoGwrpD6nICp5c2qogc4of+c7QcrhgF4Aa/aoAFHiL+RAAAAAElFTkSuQmCC'
|
||||
|
||||
const parseImageToBase64 = url => {
|
||||
return new Promise((res, rej) => {
|
||||
fetch(url)
|
||||
.then(response => response.blob())
|
||||
.then(blob => {
|
||||
const reader = new FileReader()
|
||||
reader.onloadend = () => {
|
||||
const base64data = reader.result
|
||||
res(base64data)
|
||||
// 在这里可以将base64数据用于进一步处理或显示图片
|
||||
}
|
||||
reader.readAsDataURL(blob)
|
||||
})
|
||||
.catch(error => {
|
||||
console.log('发生错误:', error)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
function get_position_style (ctx, widget_width, y, node_height) {
|
||||
const MARGIN = 12 // the margin around the html element
|
||||
|
||||
/* Create a transform that deals with all the scrolling and zooming */
|
||||
const elRect = ctx.canvas.getBoundingClientRect()
|
||||
const transform = new DOMMatrix()
|
||||
.scaleSelf(
|
||||
elRect.width / ctx.canvas.width,
|
||||
elRect.height / ctx.canvas.height
|
||||
)
|
||||
.multiplySelf(ctx.getTransform())
|
||||
.translateSelf(MARGIN, MARGIN + y)
|
||||
|
||||
return {
|
||||
transformOrigin: '0 0',
|
||||
transform: transform,
|
||||
left: `0`,
|
||||
top: `0`,
|
||||
cursor: 'pointer',
|
||||
position: 'absolute',
|
||||
maxWidth: `${widget_width - MARGIN * 2}px`,
|
||||
// maxHeight: `${node_height - MARGIN * 2}px`, // we're assuming we have the whole height of the node
|
||||
width: `${widget_width - MARGIN * 2}px`,
|
||||
// height: `${node_height * 0.3 - MARGIN * 2}px`,
|
||||
// background: '#EEEEEE',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
// alignItems: 'center',
|
||||
justifyContent: 'flex-start'
|
||||
}
|
||||
}
|
||||
|
||||
async function drawImageToCanvas (imageUrl, sFactor = 320) {
|
||||
var canvas = document.createElement('canvas')
|
||||
var ctx = canvas.getContext('2d')
|
||||
var img = new Image()
|
||||
|
||||
await new Promise((resolve, reject) => {
|
||||
img.onload = function () {
|
||||
var scaleFactor = sFactor / img.width
|
||||
var canvasWidth = img.width * scaleFactor
|
||||
var canvasHeight = img.height * scaleFactor
|
||||
|
||||
canvas.width = canvasWidth
|
||||
canvas.height = canvasHeight
|
||||
|
||||
ctx.drawImage(img, 0, 0, canvasWidth, canvasHeight)
|
||||
|
||||
resolve()
|
||||
}
|
||||
|
||||
img.onerror = function () {
|
||||
reject(new Error('Failed to load image'))
|
||||
}
|
||||
|
||||
img.src = imageUrl
|
||||
})
|
||||
|
||||
var base64 = canvas.toDataURL('image/jpeg')
|
||||
// console.log(base64); // 输出Base64数据
|
||||
return base64
|
||||
// 可以在这里执行其他操作,比如将Base64数据保存到服务器或显示在页面上
|
||||
}
|
||||
|
||||
async function extractInputAndOutputData (
|
||||
jsonData,
|
||||
inputIds = [],
|
||||
outputIds = []
|
||||
) {
|
||||
// workflow
|
||||
// const workflow=jsonData.workflow;
|
||||
// const nodes=workflow.nodes;
|
||||
|
||||
const data = jsonData.output
|
||||
let input = []
|
||||
let output = []
|
||||
const seed = {}
|
||||
const seedTitle = {}
|
||||
|
||||
for (const id in data) {
|
||||
if (data.hasOwnProperty(id)) {
|
||||
let node = app.graph.getNodeById(id)
|
||||
if (inputIds.includes(id)) {
|
||||
// let node = app.graph.getNodeById(id)
|
||||
let options = {}
|
||||
// 模型
|
||||
try {
|
||||
if (node.type === 'CheckpointLoaderSimple') {
|
||||
options = node.widgets.filter(w => w.name === 'ckpt_name')[0]
|
||||
.options.values
|
||||
} else if (node.type === 'LoraLoader') {
|
||||
options = node.widgets.filter(w => w.name === 'lora_name')[0]
|
||||
.options.values
|
||||
}
|
||||
} catch (error) {}
|
||||
|
||||
if (node.type == 'IntNumber' || node.type == 'FloatSlider') {
|
||||
// min max step
|
||||
let [v, min, max, step] = Array.from(node.widgets, w => w.value)
|
||||
options = { min, max, step }
|
||||
// node.widgets.filter(w => w.type === 'number')[0].options
|
||||
}
|
||||
|
||||
if (node.type == 'PromptSlide') {
|
||||
// min max step
|
||||
options = node.widgets.filter(w => w.type === 'slider')[0].options
|
||||
// 备选的keywords清单
|
||||
try {
|
||||
let keywords = node.widgets.filter(w => w.name === 'upload')[0]
|
||||
.value
|
||||
keywords = JSON.parse(keywords)
|
||||
options.keywords = keywords
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
}
|
||||
}
|
||||
|
||||
if (node.type == 'ImagesPrompt_') {
|
||||
//图库
|
||||
// console.log('ImagesPrompt_', data[id])
|
||||
let image_base64 = data[id].inputs.image_base64
|
||||
let img_index = 0
|
||||
let imgsData = JSON.parse(data[id].inputs.upload)
|
||||
for (let index = 0; index < imgsData.length; index++) {
|
||||
const imgd = imgsData[index].imgurl
|
||||
imgsData[index].index = index
|
||||
//TODO缩放大小
|
||||
imgsData[index].imgurl = await parseImageToBase64(imgd)
|
||||
if (image_base64 == imgsData[index].imgurl) {
|
||||
img_index = index
|
||||
}
|
||||
}
|
||||
options.images = imgsData
|
||||
delete data[id].inputs.upload
|
||||
delete data[id].inputs.image_base64
|
||||
|
||||
data[id].inputs.imageIndex = img_index
|
||||
}
|
||||
|
||||
if (node.type == 'Color') {
|
||||
}
|
||||
|
||||
if (node.type === 'LoadImage') {
|
||||
// loadImage的mask支持
|
||||
let output = node.outputs.filter(ot => ot.type == 'MASK')[0]
|
||||
if (output.links) {
|
||||
// 有输出
|
||||
options.hasMask = true
|
||||
}
|
||||
// loadImage的默认图,转为base64
|
||||
let imgurl = app.graph.getNodeById(id).imgs[0].src
|
||||
|
||||
options.defaultImage = await drawImageToCanvas(imgurl, 512)
|
||||
console.log('#loadImage的默认图', options)
|
||||
}
|
||||
|
||||
input[inputIds.indexOf(id)] = {
|
||||
...data[id],
|
||||
title: node.title,
|
||||
id,
|
||||
options
|
||||
}
|
||||
// input.push()
|
||||
}
|
||||
if (outputIds.includes(id)) {
|
||||
// let node = app.graph.getNodeById(id)
|
||||
// output.push()
|
||||
output[outputIds.indexOf(id)] = { ...data[id], title: node.title, id }
|
||||
}
|
||||
|
||||
if (
|
||||
node.type === 'KSampler' ||
|
||||
node.type == 'SamplerCustom' ||
|
||||
node.type === 'ChinesePrompt_Mix' ||
|
||||
node.type === 'Seed_'
|
||||
) {
|
||||
// seed 的类型收集
|
||||
try {
|
||||
seed[id] = node.widgets.filter(
|
||||
w => w.name === 'seed' || w.name == 'noise_seed'
|
||||
)[0].linkedWidgets[0].value
|
||||
seedTitle[id] = node.title
|
||||
} catch (error) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 修复bug,当节点不存在时
|
||||
input = input.filter(i => i)
|
||||
output = output.filter(i => i)
|
||||
|
||||
return { input, output, seed, seedTitle }
|
||||
}
|
||||
|
||||
function getUrl () {
|
||||
let api_host = `${window.location.hostname}:${window.location.port}`
|
||||
let api_base = ''
|
||||
let url = `${window.location.protocol}//${api_host}${api_base}`
|
||||
return url
|
||||
}
|
||||
|
||||
const getLocalData = key => {
|
||||
let data = {}
|
||||
try {
|
||||
data = JSON.parse(localStorage.getItem(key)) || {}
|
||||
} catch (error) {
|
||||
return {}
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
async function save_app (json) {
|
||||
let url = getUrl()
|
||||
|
||||
const res = await fetch(`${url}/mixlab/workflow`, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
data: json,
|
||||
task: 'save_app',
|
||||
filename: json.app.filename,
|
||||
category: json.app.category
|
||||
})
|
||||
})
|
||||
return await res.json()
|
||||
}
|
||||
|
||||
function downloadJsonFile (jsonData, fileName = 'mix_app.json') {
|
||||
const dataString = JSON.stringify(jsonData)
|
||||
const blob = new Blob([dataString], { type: 'application/json' })
|
||||
const url = URL.createObjectURL(blob)
|
||||
|
||||
const link = document.createElement('a')
|
||||
link.href = url
|
||||
link.download = fileName
|
||||
link.click()
|
||||
|
||||
// 释放URL对象
|
||||
setTimeout(() => {
|
||||
URL.revokeObjectURL(url)
|
||||
}, 0)
|
||||
}
|
||||
|
||||
async function save (json, download = false, showInfo = true) {
|
||||
console.log('####SAVE', json[0])
|
||||
const name = json[0],
|
||||
version = json[5],
|
||||
share_prefix = json[6], //用于分享的功能扩展
|
||||
link = json[7], //用于创建界面上的跳转链接
|
||||
category = json[8] || '', //用于分类
|
||||
description = json[4],
|
||||
inputIds = json[2].split('\n').filter(f => f),
|
||||
outputIds = json[3].split('\n').filter(f => f)
|
||||
|
||||
const iconData = json[1][0]
|
||||
let { filename, subfolder, type } = iconData
|
||||
let iconUrl = api.apiURL(
|
||||
`/view?filename=${encodeURIComponent(
|
||||
filename
|
||||
)}&type=${type}&subfolder=${subfolder}${app.getPreviewFormatParam()}${app.getRandParam()}`
|
||||
)
|
||||
|
||||
try {
|
||||
let data = await app.graphToPrompt()
|
||||
|
||||
let { input, output, seed, seedTitle } = await extractInputAndOutputData(
|
||||
data,
|
||||
inputIds,
|
||||
outputIds
|
||||
)
|
||||
|
||||
let authorAvatar =
|
||||
localStorage.getItem('_mixlab_author_avatar') || base64Df,
|
||||
authorName =
|
||||
localStorage.getItem('_mixlab_author_name') ||
|
||||
localStorage.getItem('Comfy.userName'),
|
||||
authorLink = localStorage.getItem('_mixlab_author_link') || ''
|
||||
|
||||
data.app = {
|
||||
name,
|
||||
description,
|
||||
version,
|
||||
input,
|
||||
output,
|
||||
seed, //控制是fixed 还是random
|
||||
seedTitle,
|
||||
share_prefix,
|
||||
link,
|
||||
category,
|
||||
filename: `${name}_${version}.json`,
|
||||
author: {
|
||||
avatar: authorAvatar,
|
||||
name: authorName,
|
||||
link: authorLink
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
data.app.icon = await drawImageToCanvas(iconUrl)
|
||||
} catch (error) {}
|
||||
// console.log(data.app)
|
||||
// let http_workflow = app.graph.serialize()
|
||||
await save_app(data)
|
||||
if (download) {
|
||||
await downloadJsonFile(data, data.app.filename)
|
||||
}
|
||||
|
||||
if (showInfo) {
|
||||
let open = window.confirm(
|
||||
`You can now access the standalone application on a new page!\n${getUrl()}/mixlab/app?filename=${encodeURIComponent(
|
||||
data.app.filename
|
||||
)}&category=${encodeURIComponent(data.app.category)}`
|
||||
)
|
||||
if (open)
|
||||
window.open(
|
||||
`${getUrl()}/mixlab/app?filename=${encodeURIComponent(
|
||||
data.app.filename
|
||||
)}&category=${encodeURIComponent(data.app.category)}`
|
||||
)
|
||||
}
|
||||
} catch (error) {
|
||||
console.log('###error', error)
|
||||
}
|
||||
}
|
||||
|
||||
function getInputsAndOutputs () {
|
||||
const inputs =
|
||||
`LoadImage ImagesPrompt_ VHS_LoadVideo CLIPTextEncode PromptSlide TextInput_ Color FloatSlider IntNumber CheckpointLoaderSimple LoraLoader`.split(
|
||||
' '
|
||||
),
|
||||
outputs =
|
||||
`PreviewImage,SaveImage,ShowTextForGPT,VHS_VideoCombine,Image Save,SaveImageAndMetadata_`.split(
|
||||
','
|
||||
)
|
||||
|
||||
let inputsId = [],
|
||||
outputsId = []
|
||||
|
||||
for (let node of app.graph._nodes) {
|
||||
if (inputs.includes(node.type)) {
|
||||
inputsId.push(node.id)
|
||||
}
|
||||
|
||||
if (outputs.includes(node.type)) {
|
||||
outputsId.push(node.id)
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
input: inputsId,
|
||||
output: outputsId
|
||||
}
|
||||
}
|
||||
|
||||
app.registerExtension({
|
||||
name: 'Mixlab.utils.AppInfo',
|
||||
async beforeRegisterNodeDef (nodeType, nodeData, app) {
|
||||
if (nodeType.comfyClass == 'AppInfo') {
|
||||
const orig_nodeCreated = nodeType.prototype.onNodeCreated
|
||||
nodeType.prototype.onNodeCreated = function () {
|
||||
orig_nodeCreated?.apply(this, arguments)
|
||||
// console.log('#orig_nodeCreated', this)
|
||||
|
||||
// 自动计算workflow里哪些节点支持
|
||||
let input_ids = this.widgets.filter(w => w.name == 'input_ids')[0],
|
||||
output_ids = this.widgets.filter(w => w.name == 'output_ids')[0]
|
||||
|
||||
const { input, output } = getInputsAndOutputs()
|
||||
input_ids.value = input.join('\n')
|
||||
output_ids.value = output.join('\n')
|
||||
|
||||
const widget = {
|
||||
type: 'div',
|
||||
name: 'AppInfoRun',
|
||||
draw (ctx, node, widget_width, y, widget_height) {
|
||||
Object.assign(
|
||||
this.div.style,
|
||||
get_position_style(
|
||||
ctx,
|
||||
widget_width,
|
||||
node.size[1] - widget_height,
|
||||
node.size[1]
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const style = `
|
||||
flex-direction: row;
|
||||
background-color: var(--comfy-input-bg);
|
||||
border-radius: 8px;
|
||||
border-color: var(--border-color);
|
||||
border-style: solid;
|
||||
color: var(--descrip-text);`
|
||||
|
||||
widget.div = $el('div', {})
|
||||
|
||||
const btn = document.createElement('button')
|
||||
btn.innerText = 'Save & Open'
|
||||
btn.style = style
|
||||
|
||||
btn.addEventListener('click', () => {
|
||||
// console.log('hahhah')
|
||||
if (window._mixlab_app_json) {
|
||||
save(window._mixlab_app_json)
|
||||
} else {
|
||||
alert('Please run the workflow before saving')
|
||||
// app.queuePrompt(0, 1)
|
||||
this.widgets.filter(w => w.name === 'version')[0].value += 1
|
||||
}
|
||||
})
|
||||
|
||||
const download = document.createElement('button')
|
||||
download.innerText = 'Download For App'
|
||||
download.style = style
|
||||
download.style.marginLeft = '12px'
|
||||
|
||||
download.addEventListener('click', () => {
|
||||
// console.log('hahhah')
|
||||
if (window._mixlab_app_json) {
|
||||
save(window._mixlab_app_json, true)
|
||||
} else {
|
||||
alert('Please run the workflow before saving')
|
||||
// app.queuePrompt(0, 1)
|
||||
this.widgets.filter(w => w.name === 'version')[0].value += 1
|
||||
}
|
||||
})
|
||||
|
||||
// author
|
||||
let author = document.createElement('div')
|
||||
// author.style=`display: flex`
|
||||
|
||||
let authorAvatar = document.createElement('img')
|
||||
authorAvatar.className = `${'comfy-multiline-input'}`
|
||||
authorAvatar.style = `outline: none;
|
||||
border: none;
|
||||
padding: 4px;
|
||||
width: 32px;
|
||||
cursor: pointer;
|
||||
height: 32px;`
|
||||
|
||||
if (localStorage.getItem('_mixlab_author_avatar')) {
|
||||
authorAvatar.src =
|
||||
localStorage.getItem('_mixlab_author_avatar') || base64Df
|
||||
}
|
||||
|
||||
let authorAvatarUpload = document.createElement('input')
|
||||
authorAvatarUpload.type = 'file'
|
||||
authorAvatarUpload.style = `display:none`
|
||||
|
||||
let authorAvatarInput = document.createElement('div')
|
||||
authorAvatarInput.style = `display: flex;justify-content: flex-start;
|
||||
align-items: center;`
|
||||
let authorAvatarInputLabel = document.createElement('p')
|
||||
authorAvatarInputLabel.innerText = 'Author Avatar'
|
||||
authorAvatarInputLabel.className = `${'comfy-multiline-input'}`
|
||||
authorAvatarInputLabel.style = `font-size:12px`
|
||||
|
||||
authorAvatar.addEventListener('click', e => {
|
||||
authorAvatarUpload.click()
|
||||
})
|
||||
|
||||
authorAvatarInputLabel.addEventListener('click', e => {
|
||||
authorAvatarUpload.click()
|
||||
})
|
||||
|
||||
authorAvatarUpload.addEventListener('change', event => {
|
||||
const file = event.target.files[0]
|
||||
const reader = new FileReader()
|
||||
|
||||
reader.onload = async e => {
|
||||
let im = new Image()
|
||||
im.src = e.target.result
|
||||
authorAvatar.src = e.target.result
|
||||
im.onload = () => {
|
||||
let c = document.createElement('canvas')
|
||||
let ctx = c.getContext('2d')
|
||||
c.width = 72
|
||||
c.height = 72
|
||||
ctx.drawImage(
|
||||
im,
|
||||
0,
|
||||
0,
|
||||
im.naturalWidth,
|
||||
im.naturalHeight,
|
||||
0,
|
||||
0,
|
||||
c.width,
|
||||
c.height
|
||||
)
|
||||
window._mixlab_author_avatar = c.toDataURL()
|
||||
localStorage.setItem(
|
||||
'_mixlab_author_avatar',
|
||||
window._mixlab_author_avatar
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// 以文本形式读取文件
|
||||
reader.readAsDataURL(file)
|
||||
})
|
||||
|
||||
author.appendChild(authorAvatarInput)
|
||||
authorAvatarInput.appendChild(authorAvatarInputLabel)
|
||||
authorAvatarInput.appendChild(authorAvatar)
|
||||
authorAvatarInput.appendChild(authorAvatarUpload)
|
||||
|
||||
let authorName = document.createElement('input')
|
||||
authorName.type = 'text'
|
||||
authorName.value =
|
||||
localStorage.getItem('_mixlab_author_name') ||
|
||||
localStorage.getItem('Comfy.userName')
|
||||
authorName.placeholder = 'author name'
|
||||
authorName.className = `${'comfy-multiline-input'}`
|
||||
authorName.style = `
|
||||
outline: none;
|
||||
border: none;
|
||||
padding: 4px;
|
||||
width: 100%;
|
||||
cursor: pointer;
|
||||
height: 32px;`
|
||||
|
||||
let authorNameInput = document.createElement('div')
|
||||
authorNameInput.style = `display: flex;justify-content: flex-start;
|
||||
align-items: center;`
|
||||
let authorNameInputLabel = document.createElement('p')
|
||||
authorNameInputLabel.innerText = 'Author Name'
|
||||
authorNameInputLabel.className = `${'comfy-multiline-input'}`
|
||||
authorNameInputLabel.style = `font-size:12px;width: 110px`
|
||||
|
||||
authorName.addEventListener('change', e => {
|
||||
window._mixlab_author_name = authorName.value.trim()
|
||||
localStorage.setItem(
|
||||
'_mixlab_author_name',
|
||||
window._mixlab_author_name
|
||||
)
|
||||
})
|
||||
|
||||
author.appendChild(authorNameInput)
|
||||
authorNameInput.appendChild(authorNameInputLabel)
|
||||
authorNameInput.appendChild(authorName)
|
||||
|
||||
// 社交链接
|
||||
let authorLink = document.createElement('input')
|
||||
authorLink.type = 'text'
|
||||
authorLink.value = localStorage.getItem('_mixlab_author_link') || ''
|
||||
authorLink.placeholder = 'author link'
|
||||
authorLink.className = `${'comfy-multiline-input'}`
|
||||
authorLink.style = `
|
||||
outline: none;
|
||||
border: none;
|
||||
padding: 4px;
|
||||
width: 100%;
|
||||
cursor: pointer;
|
||||
height: 32px;`
|
||||
|
||||
let authorLinkInput = document.createElement('div')
|
||||
authorLinkInput.style = `display: flex;justify-content: flex-start;
|
||||
align-items: center;`
|
||||
let authorLinkInputLabel = document.createElement('p')
|
||||
authorLinkInputLabel.innerText = 'Author Link'
|
||||
authorLinkInputLabel.className = `${'comfy-multiline-input'}`
|
||||
authorLinkInputLabel.style = `font-size:12px;width: 110px`
|
||||
|
||||
authorLink.addEventListener('change', e => {
|
||||
window._mixlab_author_link = authorLink.value.trim()
|
||||
localStorage.setItem(
|
||||
'_mixlab_author_link',
|
||||
window._mixlab_author_link
|
||||
)
|
||||
})
|
||||
|
||||
author.appendChild(authorLinkInput)
|
||||
authorLinkInput.appendChild(authorLinkInputLabel)
|
||||
authorLinkInput.appendChild(authorLink)
|
||||
|
||||
widget.div.appendChild(author)
|
||||
|
||||
let btns = document.createElement('div')
|
||||
|
||||
widget.div.appendChild(btns)
|
||||
|
||||
btns.appendChild(btn)
|
||||
btns.appendChild(download)
|
||||
|
||||
document.body.appendChild(widget.div)
|
||||
this.addCustomWidget(widget)
|
||||
|
||||
const onRemoved = this.onRemoved
|
||||
this.onRemoved = () => {
|
||||
widget.div.remove()
|
||||
return onRemoved?.()
|
||||
}
|
||||
|
||||
this.serialize_widgets = true //需要保存参数
|
||||
|
||||
window._mixlab_app_json = null
|
||||
}
|
||||
|
||||
const onExecuted = nodeType.prototype.onExecuted
|
||||
nodeType.prototype.onExecuted = function (message) {
|
||||
onExecuted?.apply(this, arguments)
|
||||
console.log(message.json)
|
||||
window._mixlab_app_json = message.json
|
||||
try {
|
||||
let a = this.widgets.filter(w => w.name === 'AppInfoRun')[0]
|
||||
if (a) {
|
||||
if (!a.value) a.value = 0
|
||||
a.value += 1
|
||||
}
|
||||
|
||||
const div = this.widgets.filter(w => w.div)[0].div
|
||||
Array.from(
|
||||
div.querySelectorAll('button'),
|
||||
b => (b.style.background = 'yellow')
|
||||
)
|
||||
} catch (error) {}
|
||||
}
|
||||
}
|
||||
},
|
||||
async loadedGraphNode (node, app) {
|
||||
// console.log('#loadedGraphNode1111')
|
||||
window._mixlab_app_json = null //切换workflow需要清空
|
||||
if (node.type === 'AppInfo') {
|
||||
let auto_save = node.widgets.filter(w => w.name == 'auto_save')[0]
|
||||
if (auto_save) {
|
||||
if (!['enable', 'disable'].includes(auto_save.value)) {
|
||||
auto_save.value = 'enable'
|
||||
}
|
||||
}
|
||||
|
||||
// app.canvas.centerOnNode(node)
|
||||
// app.canvas.setZoom(0.45)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
api.addEventListener('execution_start', async ({ detail }) => {
|
||||
console.log('#execution_start', detail)
|
||||
window._mixlab_app_json = null
|
||||
})
|
||||
|
||||
api.addEventListener('executed', async ({ detail }) => {
|
||||
console.log('#executed', detail)
|
||||
// window._mixlab_app_json=null;
|
||||
const { output } = getInputsAndOutputs()
|
||||
if (output.includes(parseInt(detail.node))) {
|
||||
let appinfo = app.graph.findNodesByType('AppInfo')[0]
|
||||
if (appinfo) {
|
||||
let auto_save = appinfo.widgets.filter(w => w.name == 'auto_save')[0]
|
||||
if (auto_save?.value === 'enable') {
|
||||
// 自动保存
|
||||
console.log('auto_save')
|
||||
if (window._mixlab_app_json) save(window._mixlab_app_json, false, false)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,398 @@
|
||||
import { app } from '../../../scripts/app.js'
|
||||
// import { api } from '../../../scripts/api.js'
|
||||
import { ComfyWidgets } from '../../../scripts/widgets.js'
|
||||
import { $el } from '../../../scripts/ui.js'
|
||||
|
||||
function get_position_style (ctx, widget_width, y, node_height) {
|
||||
const MARGIN = 4 // the margin around the html element
|
||||
|
||||
/* Create a transform that deals with all the scrolling and zooming */
|
||||
const elRect = ctx.canvas.getBoundingClientRect()
|
||||
const transform = new DOMMatrix()
|
||||
.scaleSelf(
|
||||
elRect.width / ctx.canvas.width,
|
||||
elRect.height / ctx.canvas.height
|
||||
)
|
||||
.multiplySelf(ctx.getTransform())
|
||||
.translateSelf(MARGIN, MARGIN + y)
|
||||
|
||||
return {
|
||||
transformOrigin: '0 0',
|
||||
transform: transform,
|
||||
left: `0`,
|
||||
top: `0`,
|
||||
cursor: 'pointer',
|
||||
position: 'absolute',
|
||||
maxWidth: `${widget_width - MARGIN * 2}px`,
|
||||
// maxHeight: `${node_height - MARGIN * 2}px`, // we're assuming we have the whole height of the node
|
||||
width: `${widget_width - MARGIN * 2}px`,
|
||||
// height: `${node_height * 0.3 - MARGIN * 2}px`,
|
||||
// background: '#EEEEEE',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
// alignItems: 'center',
|
||||
justifyContent: 'space-around'
|
||||
}
|
||||
}
|
||||
|
||||
const getLocalData = key => {
|
||||
let data = {}
|
||||
try {
|
||||
data = JSON.parse(localStorage.getItem(key)) || {}
|
||||
} catch (error) {
|
||||
return {}
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
function speakText (text) {
|
||||
const speechMsg = new SpeechSynthesisUtterance()
|
||||
speechMsg.text = text
|
||||
|
||||
// 语音合成结束时触发的事件
|
||||
speechMsg.onend = function (event) {
|
||||
console.log('语音播放结束')
|
||||
window._mixlab_speech_synthesis_onend = true
|
||||
}
|
||||
|
||||
// 语音合成错误时触发的事件
|
||||
speechMsg.onerror = function (event) {
|
||||
console.error('语音播放错误:', event.error)
|
||||
}
|
||||
|
||||
// 使用浏览器默认语音合成器进行语音播放
|
||||
speechSynthesis.speak(speechMsg)
|
||||
}
|
||||
|
||||
// 调用方法,将文字转换为语音播放
|
||||
// speakText('Hello, how are you?');
|
||||
// #MixCopilot
|
||||
|
||||
const start = (element, id, startBtn, node) => {
|
||||
startBtn.className = 'loading_mixlab'
|
||||
|
||||
window.recognition = new webkitSpeechRecognition()
|
||||
|
||||
window.recognition.continuous = true
|
||||
window.recognition.interimResults = true
|
||||
window.recognition.lang = navigator.language
|
||||
|
||||
let timeoutId, intervalId
|
||||
|
||||
window.recognition.onstart = () => {
|
||||
console.log('开始语音输入', window._mixlab_speech_synthesis_onend)
|
||||
window._mixlab_speech_synthesis_onend = false
|
||||
}
|
||||
|
||||
window.recognition.onresult = function (event) {
|
||||
const result = event.results[event.results.length - 1][0].transcript
|
||||
console.log('识别结果:', result)
|
||||
element.value = result
|
||||
|
||||
let data = getLocalData('_mixlab_speech_recognition')
|
||||
data[id] = result.trim()
|
||||
localStorage.setItem('_mixlab_speech_recognition', JSON.stringify(data))
|
||||
|
||||
if (timeoutId) clearTimeout(timeoutId)
|
||||
|
||||
if (!window.recognition) return
|
||||
|
||||
timeoutId = setTimeout(function () {
|
||||
console.log('结果传递::', result)
|
||||
|
||||
// 把数据发送到chatgpt的输入prompt里
|
||||
try {
|
||||
const sendToId = node.widgets.filter(
|
||||
w => w.name === 'Send to ChatGPT #'
|
||||
)[0].value
|
||||
app.graph
|
||||
.getNodeById(sendToId)
|
||||
.widgets.filter(w => w.name === 'prompt')[0].value = result
|
||||
} catch (error) {}
|
||||
|
||||
setTimeout(() => app.queuePrompt(0, 1), 100)
|
||||
window.recognition?.stop()
|
||||
window.recognition = null
|
||||
startBtn.className = ''
|
||||
startBtn.innerText = 'START'
|
||||
|
||||
timeoutId = null
|
||||
|
||||
intervalId = setInterval(() => {
|
||||
if (
|
||||
app.ui.lastQueueSize === 0 &&
|
||||
!window.recognition &&
|
||||
window._mixlab_speech_synthesis_onend
|
||||
) {
|
||||
start(element, id, startBtn, node)
|
||||
startBtn.innerText = 'STOP'
|
||||
if (intervalId) {
|
||||
clearInterval(intervalId)
|
||||
}
|
||||
}
|
||||
}, 2200)
|
||||
}, 2000)
|
||||
}
|
||||
|
||||
window.recognition.onend = function () {
|
||||
console.log('语音输入结束')
|
||||
}
|
||||
|
||||
window.recognition.onspeechend = function () {
|
||||
console.log('onspeechend')
|
||||
}
|
||||
|
||||
window.recognition.onerror = function (event) {
|
||||
console.log('Error occurred in recognition: ' + event.error)
|
||||
}
|
||||
|
||||
window.recognition.start()
|
||||
}
|
||||
|
||||
app.registerExtension({
|
||||
name: 'Mixlab.audio.SpeechRecognition',
|
||||
async getCustomWidgets (app) {
|
||||
return {
|
||||
AUDIOINPUTMIX (node, inputName, inputData, app) {
|
||||
// console.log('##node', node)
|
||||
const widget = {
|
||||
type: inputData[0], // the type, CHEESE
|
||||
name: inputName, // the name, slice
|
||||
size: [128, 32], // a default size
|
||||
draw (ctx, node, width, y) {},
|
||||
computeSize (...args) {
|
||||
return [128, 32] // a method to compute the current size of the widget
|
||||
},
|
||||
async serializeValue (nodeId, widgetIndex) {
|
||||
let data = getLocalData('_mixlab_speech_recognition')
|
||||
return data[node.id] || 'Hello Mixlab'
|
||||
}
|
||||
}
|
||||
// widget.something = something; // maybe adds stuff to it
|
||||
node.addCustomWidget(widget) // adds it to the node
|
||||
return widget // and returns it.
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
async beforeRegisterNodeDef (nodeType, nodeData, app) {
|
||||
if (nodeType.comfyClass == 'SpeechRecognition') {
|
||||
const orig_nodeCreated = nodeType.prototype.onNodeCreated
|
||||
nodeType.prototype.onNodeCreated = function () {
|
||||
orig_nodeCreated?.apply(this, arguments)
|
||||
|
||||
const sendTo = ComfyWidgets.INT(
|
||||
this,
|
||||
'Send to ChatGPT #',
|
||||
['INT', { default: 0 }],
|
||||
app
|
||||
)
|
||||
// console.log('sendTo',sendTo)
|
||||
|
||||
const widget = {
|
||||
type: 'div',
|
||||
name: 'chatgptdiv',
|
||||
draw (ctx, node, widget_width, y, widget_height) {
|
||||
Object.assign(
|
||||
this.div.style,
|
||||
get_position_style(ctx, widget_width, 78, node.size[1])
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
widget.div = $el('div', {})
|
||||
|
||||
document.body.appendChild(widget.div)
|
||||
|
||||
const inputDiv = (key, placeholder) => {
|
||||
let div = document.createElement('div')
|
||||
const startBtn = document.createElement('button')
|
||||
|
||||
const textArea = document.createElement('textarea')
|
||||
textArea.placeholder = 'speak text'
|
||||
// sendTo.type='range';
|
||||
// sendTo.min=0;
|
||||
// sendTo.max=2000;
|
||||
// sendTo.step=1;
|
||||
// sendTo.className='comfy-multiline-input'
|
||||
|
||||
textArea.className = `${'comfy-multiline-input'} ${placeholder}`
|
||||
|
||||
textArea.style = `margin-top: 14px;
|
||||
height: 44px;`
|
||||
|
||||
div.style = `flex-direction: column;
|
||||
display: flex;
|
||||
margin: 0px 8px 6px;`
|
||||
|
||||
startBtn.style = `
|
||||
margin-top:48px;
|
||||
background-color: var(--comfy-input-bg);
|
||||
border-radius: 8px;
|
||||
border-color: var(--border-color);
|
||||
border-style: solid;
|
||||
color: var(--descrip-text);
|
||||
`
|
||||
|
||||
startBtn.innerText = 'START'
|
||||
|
||||
div.appendChild(startBtn)
|
||||
// div.appendChild(sendTo);
|
||||
div.appendChild(textArea)
|
||||
|
||||
startBtn.addEventListener('click', () => {
|
||||
if (window.recognition) {
|
||||
window.recognition.stop()
|
||||
window.recognition = null
|
||||
startBtn.innerText = 'START'
|
||||
startBtn.className = ''
|
||||
} else {
|
||||
start(textArea, this.id, startBtn, this)
|
||||
startBtn.innerText = 'STOP'
|
||||
}
|
||||
})
|
||||
|
||||
// sendTo.addEventListener('change',()=>{
|
||||
// console.log(sendTo.value)
|
||||
// })
|
||||
|
||||
return div
|
||||
}
|
||||
|
||||
let inputAudio = inputDiv('_mixlab_speech_recognition', 'audio')
|
||||
widget.div.appendChild(inputAudio)
|
||||
|
||||
this.addCustomWidget(widget)
|
||||
|
||||
const onRemoved = this.onRemoved
|
||||
this.onRemoved = () => {
|
||||
inputAudio.remove()
|
||||
widget.div.remove()
|
||||
return onRemoved?.()
|
||||
}
|
||||
|
||||
this.serialize_widgets = true //需要保存参数
|
||||
}
|
||||
|
||||
// const onGraphConfigured=nodeType.prototype.onGraphConfigured;
|
||||
// nodeType.prototype.onGraphConfigured = function (message) {
|
||||
// onGraphConfigured?.apply(this, arguments)
|
||||
// console.log('###SpeechRecognition onGraphConfigured',this,message)
|
||||
// }
|
||||
|
||||
const onExecuted = nodeType.prototype.onExecuted
|
||||
nodeType.prototype.onExecuted = function (message) {
|
||||
onExecuted?.apply(this, arguments)
|
||||
// console.log('this.widgets', this.widgets)
|
||||
|
||||
try {
|
||||
// 是否根据start by 开启
|
||||
let open = message.start_by[0] > 0
|
||||
if (open) {
|
||||
const div = this.widgets.filter(w => w.name == 'chatgptdiv')[0].div
|
||||
const startBtn = div.querySelector('button')
|
||||
let textArea = div.querySelector('textarea')
|
||||
if (open && !window.recognition) {
|
||||
start(textArea, this.id, startBtn, this)
|
||||
startBtn.innerText = 'STOP'
|
||||
} else if (!open && window.recognition) {
|
||||
window.recognition.stop()
|
||||
window.recognition = null
|
||||
startBtn.innerText = 'START'
|
||||
startBtn.className = ''
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.log('###SpeechRecognition', error)
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
async loadedGraphNode (node, app) {
|
||||
if (node.type === 'SpeechRecognition') {
|
||||
let data = getLocalData('_mixlab_speech_recognition')
|
||||
// console.log('_mixlab_speech_recognition', node )
|
||||
let div = node.widgets.filter(f => f.type === 'div')[0]
|
||||
if (div && data[node.id]) {
|
||||
div.div.querySelector('textarea').value = data[node.id]
|
||||
}
|
||||
|
||||
try {
|
||||
let open = node.widgets_values[1] > 0
|
||||
if (open) {
|
||||
const div = node.widgets.filter(w => w.name == 'chatgptdiv')[0].div
|
||||
const startBtn = div.querySelector('button')
|
||||
let textArea = div.querySelector('textarea')
|
||||
if (open && !window.recognition) {
|
||||
start(textArea, node.id, startBtn, node)
|
||||
startBtn.innerText = 'STOP'
|
||||
} else if (!open && window.recognition) {
|
||||
window.recognition.stop()
|
||||
window.recognition = null
|
||||
startBtn.innerText = 'START'
|
||||
startBtn.className = ''
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.log('###SpeechRecognition', error)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
app.registerExtension({
|
||||
name: 'Mixlab.audio.SpeechSynthesis',
|
||||
async beforeRegisterNodeDef (nodeType, nodeData, app) {
|
||||
if (nodeData.name === 'SpeechSynthesis') {
|
||||
function populate (text) {
|
||||
// console.log('SpeechSynthesis',this.widgets)
|
||||
|
||||
if (this.widgets) {
|
||||
const pos = this.widgets.findIndex(w => w.name === 'text')
|
||||
if (pos !== -1) {
|
||||
for (let i = pos; i < this.widgets.length; i++) {
|
||||
this.widgets[i].onRemove?.()
|
||||
}
|
||||
this.widgets.length = pos
|
||||
}
|
||||
}
|
||||
|
||||
for (let list of text) {
|
||||
const w = ComfyWidgets['STRING'](
|
||||
this,
|
||||
'text',
|
||||
['STRING', { multiline: true }],
|
||||
app
|
||||
).widget
|
||||
w.inputEl.readOnly = true
|
||||
w.inputEl.style.opacity = 0.6
|
||||
w.value = list
|
||||
}
|
||||
|
||||
speakText(text.join('\n'))
|
||||
|
||||
// console.log('ShowTextForGPT',this.widgets.length)
|
||||
requestAnimationFrame(() => {
|
||||
const sz = this.computeSize()
|
||||
if (sz[0] < this.size[0]) {
|
||||
sz[0] = this.size[0]
|
||||
}
|
||||
if (sz[1] < this.size[1]) {
|
||||
sz[1] = this.size[1]
|
||||
}
|
||||
this.onResize?.(sz)
|
||||
app.graph.setDirtyCanvas(true, false)
|
||||
})
|
||||
}
|
||||
|
||||
// When the node is executed we will be sent the input text, display this in the widget
|
||||
const onExecuted = nodeType.prototype.onExecuted
|
||||
nodeType.prototype.onExecuted = function (message) {
|
||||
onExecuted?.apply(this, arguments)
|
||||
populate.call(this, message.text)
|
||||
}
|
||||
|
||||
this.serialize_widgets = true //需要保存参数
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -3,20 +3,24 @@ import { app } from '../../../scripts/app.js'
|
||||
const repoOwner = 'shadowcz007' // 替换为仓库的所有者
|
||||
const repoName = 'comfyui-mixlab-nodes' // 替换为仓库的名称
|
||||
|
||||
const version = 'v0.2.5.1'
|
||||
const version = 'v0.21.0'
|
||||
|
||||
fetch(`https://api.github.com/repos/${repoOwner}/${repoName}/releases/latest`)
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
const latestVersion = data.tag_name
|
||||
console.log('Latest release version:', latestVersion)
|
||||
// if (latestVersion === localStorage.getItem('_mixlab_nodes_vesion')) return
|
||||
if (latestVersion != version) {
|
||||
// localStorage.setItem('_mixlab_nodes_vesion', latestVersion)
|
||||
if (
|
||||
latestVersion &&
|
||||
latestVersion === localStorage.getItem('_mixlab_nodes_vesion')
|
||||
)
|
||||
return
|
||||
if (latestVersion && latestVersion != version) {
|
||||
localStorage.setItem('_mixlab_nodes_vesion', latestVersion)
|
||||
app.ui.dialog.show(`<h4 style="font-size: 18px;">${repoName} <br>
|
||||
Latest release version: ${latestVersion}</h4>
|
||||
<p>Please proceed to the official repository to download the latest version.</p>
|
||||
<a style=" color: #2196F3;
|
||||
<a style="color: #2196F3;
|
||||
font-size: 18px;
|
||||
font-weight: 800;
|
||||
letter-spacing: 2px;
|
||||
|
||||
@@ -0,0 +1,119 @@
|
||||
import { app } from '../../../scripts/app.js'
|
||||
// import { api } from '../../../scripts/api.js'
|
||||
import { ComfyWidgets } from '../../../scripts/widgets.js'
|
||||
import { $el } from '../../../scripts/ui.js'
|
||||
|
||||
function getRandomElements (arr, num) {
|
||||
var result = []
|
||||
var len = arr.length
|
||||
|
||||
for (var i = 0; i < num; i++) {
|
||||
var randomIndex = Math.floor(Math.random() * len)
|
||||
result.push(arr[randomIndex])
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
const createPrompt = (node, prompts, items, sample) => {
|
||||
const w = ComfyWidgets['STRING'](
|
||||
node,
|
||||
'text',
|
||||
['STRING', { multiline: true }],
|
||||
app
|
||||
).widget
|
||||
w.inputEl.readOnly = true
|
||||
w.inputEl.style.opacity = 0.6
|
||||
|
||||
w.value = typeof prompts === 'string' ? prompts : prompts.join('\n\n')
|
||||
|
||||
const w2 = ComfyWidgets['STRING'](
|
||||
node,
|
||||
'text',
|
||||
['STRING', { multiline: true }],
|
||||
app
|
||||
).widget
|
||||
w2.inputEl.readOnly = true
|
||||
w2.inputEl.style.opacity = 0.6
|
||||
|
||||
w2.value = typeof items === 'string' ? items : JSON.stringify(items, null, 2)
|
||||
|
||||
const w3 = ComfyWidgets['STRING'](
|
||||
node,
|
||||
'text',
|
||||
['STRING', { multiline: true }],
|
||||
app
|
||||
).widget
|
||||
w3.inputEl.readOnly = true
|
||||
w3.inputEl.style.opacity = 0.6
|
||||
w3.value = typeof sample === 'string' ? sample : sample.join('\n\n')
|
||||
}
|
||||
|
||||
app.registerExtension({
|
||||
name: 'Mixlab.prompt.ClipInterrogator',
|
||||
async beforeRegisterNodeDef (nodeType, nodeData, app) {
|
||||
if (nodeData.name === 'ClipInterrogator') {
|
||||
function populate (prompts, items, random_samples) {
|
||||
if (this.widgets) {
|
||||
for (let i = 0; i < this.widgets.length; i++) {
|
||||
if (this.widgets[i].type !== 'combo') this.widgets[i].onRemove?.()
|
||||
}
|
||||
this.widgets.length = 2
|
||||
}
|
||||
|
||||
createPrompt(this, prompts, items, random_samples)
|
||||
|
||||
// console.log('ClipInterrogator', w, w2)
|
||||
requestAnimationFrame(() => {
|
||||
const sz = this.computeSize()
|
||||
if (sz[0] < this.size[0]) {
|
||||
sz[0] = this.size[0]
|
||||
}
|
||||
if (sz[1] < this.size[1]) {
|
||||
sz[1] = this.size[1]
|
||||
}
|
||||
this.onResize?.(sz)
|
||||
app.graph.setDirtyCanvas(true, false)
|
||||
})
|
||||
}
|
||||
|
||||
// When the node is executed we will be sent the input text, display this in the widget
|
||||
const onExecuted = nodeType.prototype.onExecuted
|
||||
nodeType.prototype.onExecuted = function (message) {
|
||||
onExecuted?.apply(this, arguments)
|
||||
// console.log('##', message)
|
||||
populate.call(
|
||||
this,
|
||||
message.prompt,
|
||||
message.analysis,
|
||||
message.random_samples
|
||||
)
|
||||
}
|
||||
|
||||
this.serialize_widgets = true //需要保存参数
|
||||
}
|
||||
},
|
||||
async loadedGraphNode (node, app) {
|
||||
// Fires every time a node is constructed
|
||||
// You can modify widgets/add handlers/etc here
|
||||
|
||||
if (node.type === 'ClipInterrogator') {
|
||||
try {
|
||||
|
||||
let widgets_values = node.widgets_values
|
||||
console.log(widgets_values )
|
||||
try {
|
||||
if (widgets_values[2] && widgets_values[3] && widgets_values[4])
|
||||
createPrompt(
|
||||
node,
|
||||
widgets_values[2],
|
||||
widgets_values[3],
|
||||
widgets_values[4]
|
||||
)
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
}
|
||||
} catch (error) {}
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -46,22 +46,33 @@ function get_position_style (ctx, widget_width, y, node_height) {
|
||||
}
|
||||
}
|
||||
|
||||
const getLocalData = key => {
|
||||
let data = {}
|
||||
try {
|
||||
data = JSON.parse(localStorage.getItem(key)) || {}
|
||||
} catch (error) {
|
||||
return {}
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
app.registerExtension({
|
||||
name: 'Mixlab.GPT.ChatGPT',
|
||||
name: 'Mixlab.GPT.ChatGPTOpenAI',
|
||||
async getCustomWidgets (app) {
|
||||
return {
|
||||
KEY (node, inputName, inputData, app) {
|
||||
// console.log('node', inputName, inputData[0])
|
||||
// console.log('##inputData', inputData)
|
||||
const widget = {
|
||||
type: inputData[0], // the type, CHEESE
|
||||
name: inputName, // the name, slice
|
||||
size: [128, 24], // a default size
|
||||
size: [128, 32], // a default size
|
||||
draw (ctx, node, width, y) {},
|
||||
computeSize (...args) {
|
||||
return [128, 24] // a method to compute the current size of the widget
|
||||
return [128, 32] // a method to compute the current size of the widget
|
||||
},
|
||||
async serializeValue (nodeId, widgetIndex) {
|
||||
return localStorage.getItem('_mixlab_api_key') || ''
|
||||
let data = getLocalData('_mixlab_api_key')
|
||||
return data[node.id] || 'by Mixlab'
|
||||
}
|
||||
}
|
||||
// widget.something = something; // maybe adds stuff to it
|
||||
@@ -73,15 +84,16 @@ app.registerExtension({
|
||||
const widget = {
|
||||
type: inputData[0], // the type, CHEESE
|
||||
name: inputName, // the name, slice
|
||||
size: [128, 24], // a default size
|
||||
size: [128, 32], // a default size
|
||||
draw (ctx, node, width, y) {
|
||||
// a method to draw the widget (ctx is a CanvasRenderingContext2D)
|
||||
},
|
||||
computeSize (...args) {
|
||||
return [128, 24] // a method to compute the current size of the widget
|
||||
return [128, 32] // a method to compute the current size of the widget
|
||||
},
|
||||
async serializeValue (nodeId, widgetIndex) {
|
||||
return localStorage.getItem('_mixlab_api_url') || ''
|
||||
let data = getLocalData('_mixlab_api_url')
|
||||
return data[node.id] || 'https://api.openai.com/v1'
|
||||
}
|
||||
}
|
||||
// widget.something = something; // maybe adds stuff to it
|
||||
@@ -90,22 +102,21 @@ app.registerExtension({
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
async beforeRegisterNodeDef (nodeType, nodeData, app) {
|
||||
// console.log(nodeType.comfyClass)
|
||||
if (nodeType.comfyClass == 'ChatGPT') {
|
||||
if (nodeType.comfyClass == 'ChatGPTOpenAI') {
|
||||
const orig_nodeCreated = nodeType.prototype.onNodeCreated
|
||||
nodeType.prototype.onNodeCreated = function () {
|
||||
orig_nodeCreated?.apply(this, arguments)
|
||||
|
||||
console.log('ChatGPT widtget', this.widgets)
|
||||
|
||||
const api_key = this.widgets.filter(w => w.name == 'api_key')[0]
|
||||
const api_url = this.widgets.filter(w => w.name == 'api_url')[0]
|
||||
console.log('api_key', api_key, api_url)
|
||||
|
||||
console.log('ChatGPTOpenAI nodeData', this.widgets)
|
||||
|
||||
const widget = {
|
||||
type: 'div',
|
||||
name: 'chatgpt div',
|
||||
name: 'chatgptdiv',
|
||||
draw (ctx, node, widget_width, y, widget_height) {
|
||||
Object.assign(
|
||||
this.div.style,
|
||||
@@ -118,36 +129,43 @@ app.registerExtension({
|
||||
|
||||
document.body.appendChild(widget.div)
|
||||
|
||||
const inputKey = document.createElement('input'),
|
||||
inputUrl = document.createElement('input')
|
||||
inputKey.type = 'text'
|
||||
inputUrl.type = 'text'
|
||||
const inputDiv = (key, placeholder) => {
|
||||
let div = document.createElement('div')
|
||||
const ip = document.createElement('input')
|
||||
ip.type = placeholder === 'Key' ? 'password' : 'text'
|
||||
ip.className = `${'comfy-multiline-input'} ${placeholder}`
|
||||
div.style = `display: flex;
|
||||
align-items: center;
|
||||
margin: 6px 8px;
|
||||
margin-top: 0;`
|
||||
ip.placeholder = placeholder
|
||||
ip.value = placeholder
|
||||
|
||||
inputKey.placeholder = 'Key'
|
||||
inputUrl.placeholder = 'URL'
|
||||
ip.style = `margin-left: 24px;
|
||||
outline: none;
|
||||
border: none;
|
||||
padding: 4px;width: 100%;`
|
||||
const label = document.createElement('label')
|
||||
label.style = 'font-size: 10px;min-width:32px'
|
||||
label.innerText = placeholder
|
||||
div.appendChild(label)
|
||||
div.appendChild(ip)
|
||||
|
||||
inputKey.style = `margin:4px 48px;`
|
||||
inputUrl.style = `margin:4px 48px`
|
||||
ip.addEventListener('change', () => {
|
||||
let data = getLocalData(key)
|
||||
data[this.id] = ip.value.trim()
|
||||
localStorage.setItem(key, JSON.stringify(data))
|
||||
console.log(this.id, key)
|
||||
})
|
||||
return div
|
||||
}
|
||||
|
||||
inputUrl.value = localStorage.getItem('_mixlab_api_url') || ''
|
||||
inputKey.value = localStorage.getItem('_mixlab_api_key') || ''
|
||||
let inputKey = inputDiv('_mixlab_api_key', 'Key')
|
||||
let inputUrl = inputDiv('_mixlab_api_url', 'URL')
|
||||
|
||||
widget.div.appendChild(inputKey)
|
||||
widget.div.appendChild(inputUrl)
|
||||
|
||||
inputKey.addEventListener('change', () => {
|
||||
api_key.serializeValue = () => inputKey.value || ''
|
||||
localStorage.setItem('_mixlab_api_key', inputKey.value)
|
||||
})
|
||||
|
||||
inputUrl.addEventListener('change', () => {
|
||||
api_url.serializeValue = () => inputUrl.value || ''
|
||||
localStorage.setItem('_mixlab_api_url', inputUrl.value)
|
||||
})
|
||||
|
||||
/*
|
||||
Add the widget, make sure we clean up nicely, and we do not want to be serialized!
|
||||
*/
|
||||
this.addCustomWidget(widget)
|
||||
|
||||
const onRemoved = this.onRemoved
|
||||
@@ -158,65 +176,89 @@ app.registerExtension({
|
||||
return onRemoved?.()
|
||||
}
|
||||
|
||||
this.serialize_widgets = false
|
||||
this.serialize_widgets = true //需要保存参数
|
||||
}
|
||||
}
|
||||
},
|
||||
async loadedGraphNode (node, app) {
|
||||
// Fires every time a node is constructed
|
||||
// You can modify widgets/add handlers/etc here
|
||||
|
||||
if (node.type === 'ChatGPTOpenAI') {
|
||||
let widget = node.widgets.filter(w => w.div)[0]
|
||||
|
||||
let apiKey = getLocalData('_mixlab_api_key'),
|
||||
url = getLocalData('_mixlab_api_url')
|
||||
|
||||
let id = node.id
|
||||
|
||||
// console.log('ChatGPTOpenAI serialize_widgets', this)
|
||||
|
||||
widget.div.querySelector('.Key').value = apiKey[id] || 'by Mixlab'
|
||||
widget.div.querySelector('.URL').value =
|
||||
url[id] || 'https://api.openai.com/v1'
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
app.registerExtension({
|
||||
name: 'Mixlab.GPT.SessionHistory',
|
||||
name: 'Mixlab.GPT.ShowTextForGPT',
|
||||
async beforeRegisterNodeDef (nodeType, nodeData, app) {
|
||||
if (nodeData.name === 'SessionHistory') {
|
||||
if (nodeData.name === 'ShowTextForGPT') {
|
||||
function populate (text) {
|
||||
text = text.filter(t => t && t?.trim())
|
||||
|
||||
if (this.widgets) {
|
||||
// const pos = this.widgets.findIndex((w) => w.name === "text");
|
||||
// if (pos !== -1) {
|
||||
// for (let i = pos; i < this.widgets.length; i++) {
|
||||
// this.widgets[i].onRemove?.();
|
||||
// }
|
||||
// this.widgets.length = pos;
|
||||
// }
|
||||
|
||||
// const pos = this.widgets.findIndex(w => w.name === 'text')
|
||||
for (let i = 0; i < this.widgets.length; i++) {
|
||||
this.widgets[i].onRemove?.()
|
||||
if (this.widgets[i].name == 'show_text') this.widgets[i].onRemove?.()
|
||||
}
|
||||
this.widgets.length = 0
|
||||
this.widgets.length = 1
|
||||
}
|
||||
// console.log('ShowTextForGPT',text)
|
||||
for (let list of text) {
|
||||
if (list) {
|
||||
// console.log('#####', list)
|
||||
const w = ComfyWidgets['STRING'](
|
||||
this,
|
||||
'show_text',
|
||||
['STRING', { multiline: true }],
|
||||
app
|
||||
).widget
|
||||
w.inputEl.readOnly = true
|
||||
w.inputEl.style.opacity = 0.6
|
||||
|
||||
console.log('SessionHistory', this.widgets, text)
|
||||
try {
|
||||
if (typeof list != 'string') {
|
||||
let data = JSON.parse(list)
|
||||
data = Array.from(data, d => {
|
||||
return {
|
||||
...d,
|
||||
content: decodeURIComponent(d.content)
|
||||
}
|
||||
})
|
||||
list = JSON.stringify(data, null, 2)
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
}
|
||||
|
||||
for (const list of text) {
|
||||
const w = ComfyWidgets['STRING'](
|
||||
this,
|
||||
'text',
|
||||
['STRING', { multiline: true }],
|
||||
app
|
||||
).widget
|
||||
w.inputEl.readOnly = true
|
||||
w.inputEl.style.opacity = 0.6
|
||||
|
||||
let res = list
|
||||
|
||||
try {
|
||||
res = JSON.stringify(JSON.parse(list), null, 2)
|
||||
} catch (error) {
|
||||
// console.log(list)
|
||||
w.value = list
|
||||
}
|
||||
|
||||
w.value = res
|
||||
}
|
||||
|
||||
// console.log('ShowTextForGPT',this.widgets.length)
|
||||
requestAnimationFrame(() => {
|
||||
const sz = this.computeSize()
|
||||
if (sz[0] < this.size[0]) {
|
||||
sz[0] = this.size[0]
|
||||
if (this) {
|
||||
const sz = this.computeSize()
|
||||
if (sz[0] < this.size[0]) {
|
||||
sz[0] = this.size[0]
|
||||
}
|
||||
if (sz[1] < this.size[1]) {
|
||||
sz[1] = this.size[1]
|
||||
}
|
||||
this.onResize?.(sz)
|
||||
app.graph.setDirtyCanvas(true, false)
|
||||
}
|
||||
if (sz[1] < this.size[1]) {
|
||||
sz[1] = this.size[1]
|
||||
}
|
||||
this.onResize?.(sz)
|
||||
app.graph.setDirtyCanvas(true, false)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -224,7 +266,8 @@ app.registerExtension({
|
||||
const onExecuted = nodeType.prototype.onExecuted
|
||||
nodeType.prototype.onExecuted = function (message) {
|
||||
onExecuted?.apply(this, arguments)
|
||||
populate.call(this, message.text)
|
||||
// console.log('##onExecuted', this, message)
|
||||
if (message.text) populate.call(this, message.text)
|
||||
}
|
||||
|
||||
const onConfigure = nodeType.prototype.onConfigure
|
||||
@@ -234,6 +277,8 @@ app.registerExtension({
|
||||
populate.call(this, this.widgets_values)
|
||||
}
|
||||
}
|
||||
|
||||
this.serialize_widgets = true //需要保存参数
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@@ -0,0 +1,782 @@
|
||||
import { app } from '../../../scripts/app.js'
|
||||
import { api } from '../../../scripts/api.js'
|
||||
import { ComfyWidgets } from '../../../scripts/widgets.js'
|
||||
import { $el } from '../../../scripts/ui.js'
|
||||
|
||||
async function uploadImage (blob, fileType = '.svg', filename) {
|
||||
// const blob = await (await fetch(src)).blob();
|
||||
const body = new FormData()
|
||||
body.append(
|
||||
'image',
|
||||
new File([blob], (filename || new Date().getTime()) + fileType)
|
||||
)
|
||||
|
||||
const resp = await api.fetchApi('/upload/image', {
|
||||
method: 'POST',
|
||||
body
|
||||
})
|
||||
|
||||
// console.log(resp)
|
||||
let data = await resp.json()
|
||||
let { name, subfolder } = data
|
||||
let src = api.apiURL(
|
||||
`/view?filename=${encodeURIComponent(
|
||||
name
|
||||
)}&type=input&subfolder=${subfolder}${app.getPreviewFormatParam()}${app.getRandParam()}`
|
||||
)
|
||||
|
||||
return src
|
||||
}
|
||||
|
||||
const base64Df =
|
||||
'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAMCAYAAABWdVznAAAAAXNSR0IArs4c6QAAALZJREFUKFOFkLERwjAQBPdbgBkInECGaMLUQDsE0AkRVRAYWqAByxldPPOWHwnw4OBGye1p50UDSoA+W2ABLPN7i+C5dyC6R/uiAUXRQCs0bXoNIu4QPQzAxDKxHoALOrZcqtiyR/T6CXw7+3IGHhkYcy6BOR2izwT8LptG8rbMiCRAUb+CQ6WzQVb0SNOi5Z2/nX35DRyb/ENazhpWKoGwrpD6nICp5c2qogc4of+c7QcrhgF4Aa/aoAFHiL+RAAAAAElFTkSuQmCC'
|
||||
|
||||
function base64ToBlobFromURL (base64URL, contentType) {
|
||||
return fetch(base64URL).then(response => response.blob())
|
||||
}
|
||||
|
||||
function getContentTypeFromBase64 (base64Data) {
|
||||
const regex = /^data:(.+);base64,/
|
||||
const matches = base64Data.match(regex)
|
||||
if (matches && matches.length >= 2) {
|
||||
return matches[1]
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
// 示例用法
|
||||
// const base64Data = 'data:image/jpeg;base64,/9j/4AAQSkZJRgABAQEAAAAAAAD/...'; // 替换为实际的base64图片数据
|
||||
// const contentType = getContentTypeFromBase64(base64Data);
|
||||
// console.log(contentType);
|
||||
|
||||
// // 示例用法
|
||||
// const base64Data = '...'; // 替换为实际的base64图片数据
|
||||
// const contentType = 'image/jpeg'; // 替换为实际的图片类型
|
||||
|
||||
// const blob = base64ToBlob(base64Data, contentType);
|
||||
// console.log(blob);
|
||||
|
||||
function get_position_style (ctx, widget_width, y, node_height) {
|
||||
const MARGIN = 4 // the margin around the html element
|
||||
|
||||
/* Create a transform that deals with all the scrolling and zooming */
|
||||
const elRect = ctx.canvas.getBoundingClientRect()
|
||||
const transform = new DOMMatrix()
|
||||
.scaleSelf(
|
||||
elRect.width / ctx.canvas.width,
|
||||
elRect.height / ctx.canvas.height
|
||||
)
|
||||
.multiplySelf(ctx.getTransform())
|
||||
.translateSelf(MARGIN, MARGIN + y)
|
||||
|
||||
return {
|
||||
transformOrigin: '0 0',
|
||||
transform: transform,
|
||||
left: `0`,
|
||||
top: `0`,
|
||||
cursor: 'pointer',
|
||||
position: 'absolute',
|
||||
maxWidth: `${widget_width - MARGIN * 2}px`,
|
||||
// maxHeight: `${node_height - MARGIN * 2}px`, // we're assuming we have the whole height of the node
|
||||
width: `${widget_width - MARGIN * 2}px`,
|
||||
// height: `${node_height * 0.3 - MARGIN * 2}px`,
|
||||
// background: '#EEEEEE',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
// alignItems: 'center',
|
||||
justifyContent: 'space-around'
|
||||
}
|
||||
}
|
||||
|
||||
const getLocalData = key => {
|
||||
let data = {}
|
||||
try {
|
||||
data = JSON.parse(localStorage.getItem(key)) || {}
|
||||
} catch (error) {
|
||||
return {}
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
const setLocalDataOfWin = (key, value) => {
|
||||
localStorage.setItem(key, JSON.stringify(value))
|
||||
// window[key] = value
|
||||
}
|
||||
|
||||
function createImage (url) {
|
||||
let im = new Image()
|
||||
return new Promise((res, rej) => {
|
||||
im.onload = () => res(im)
|
||||
im.src = url
|
||||
})
|
||||
}
|
||||
|
||||
const parseImageToBase64 = url => {
|
||||
return new Promise((res, rej) => {
|
||||
fetch(url)
|
||||
.then(response => response.blob())
|
||||
.then(blob => {
|
||||
const reader = new FileReader()
|
||||
reader.onloadend = () => {
|
||||
const base64data = reader.result
|
||||
res(base64data)
|
||||
// 在这里可以将base64数据用于进一步处理或显示图片
|
||||
}
|
||||
reader.readAsDataURL(blob)
|
||||
})
|
||||
.catch(error => {
|
||||
console.log('发生错误:', error)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
const parseSvg = async svgContent => {
|
||||
let scale = 2
|
||||
// 创建一个临时的DOM元素来解析SVG
|
||||
const tempContainer = document.createElement('div')
|
||||
tempContainer.innerHTML = svgContent
|
||||
|
||||
// 提取SVG元素
|
||||
const svgElement = tempContainer.querySelector('svg')
|
||||
if (!svgElement) return
|
||||
// 获取SVG中 rect元素
|
||||
var rectElements = svgElement?.querySelectorAll('rect') || []
|
||||
// console.log(rectElements,svgElement)
|
||||
// 定义一个数组来存储处理后的数据
|
||||
var data = []
|
||||
|
||||
Array.from(rectElements, (rectElement, i) => {
|
||||
// 获取rect元素的属性值
|
||||
var x = ~~(rectElement.getAttribute('x') || 0)
|
||||
var y = ~~(rectElement.getAttribute('y') || 0)
|
||||
var width = ~~rectElement.getAttribute('width')
|
||||
var height = ~~rectElement.getAttribute('height')
|
||||
// console.log('rectElements',rectElement,x,y,width,height)
|
||||
if (x != undefined && y != undefined && width && height) {
|
||||
// 创建一个新的canvas元素
|
||||
var canvas = document.createElement('canvas')
|
||||
canvas.width = width
|
||||
canvas.height = height
|
||||
var context = canvas.getContext('2d')
|
||||
|
||||
// 填充颜色到canvas
|
||||
var fill = rectElement.getAttribute('fill')
|
||||
context.fillStyle = fill
|
||||
context.fillRect(0, 0, width, height)
|
||||
|
||||
// 将canvas转换为base64格式
|
||||
var base64 = canvas.toDataURL()
|
||||
|
||||
// 将数据转化为指定的JSON格式
|
||||
|
||||
var rectData = {
|
||||
x: parseInt(x),
|
||||
y: parseInt(y),
|
||||
width: parseInt(width),
|
||||
height: parseInt(height),
|
||||
z_index: i + 1,
|
||||
scale_option: 'width',
|
||||
image: base64,
|
||||
mask: base64,
|
||||
type: 'base64',
|
||||
_t: 'rect'
|
||||
}
|
||||
|
||||
// 将处理后的数据添加到数组中
|
||||
data.push(rectData)
|
||||
}
|
||||
})
|
||||
|
||||
var svgWidth = svgElement.getAttribute('width')
|
||||
var svgHeight = svgElement.getAttribute('height')
|
||||
|
||||
if (!(svgWidth && svgHeight)) {
|
||||
// viewBox
|
||||
let viewBox = svgElement.viewBox.baseVal
|
||||
|
||||
svgWidth = viewBox.width
|
||||
svgHeight = viewBox.height
|
||||
} else {
|
||||
try {
|
||||
svgWidth = ~~svgWidth.replace('px', '')
|
||||
svgHeight = ~~svgHeight.replace('px', '')
|
||||
} catch (error) {}
|
||||
}
|
||||
|
||||
// 创建一个新的canvas元素
|
||||
var canvas = document.createElement('canvas')
|
||||
canvas.width = svgWidth
|
||||
canvas.height = svgHeight
|
||||
var context = canvas.getContext('2d')
|
||||
// 绘制SVG到canvas
|
||||
var svgString = new XMLSerializer().serializeToString(svgElement)
|
||||
var DOMURL = window.URL || window.webkitURL || window
|
||||
|
||||
var svgBlob = new Blob([svgString], { type: 'image/svg+xml;charset=utf-8' })
|
||||
var url = DOMURL.createObjectURL(svgBlob)
|
||||
|
||||
let img = await createImage(url)
|
||||
context.drawImage(img, 0, 0)
|
||||
|
||||
let base64 = canvas.toDataURL()
|
||||
|
||||
var rectData = {
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: parseInt(svgWidth),
|
||||
height: parseInt(svgHeight),
|
||||
z_index: 0,
|
||||
scale_option: 'width',
|
||||
image: base64,
|
||||
mask: base64,
|
||||
type: 'base64',
|
||||
_t: 'canvas'
|
||||
}
|
||||
data.push(rectData)
|
||||
|
||||
// 打印处理后的数据
|
||||
console.log('layers', { data, image: base64, svgElement })
|
||||
return { data, image: base64, svgElement }
|
||||
}
|
||||
|
||||
function exportModelViewerImage (
|
||||
modelViewer,
|
||||
width,
|
||||
height,
|
||||
format = 'image/png',
|
||||
quality = 1.0
|
||||
) {
|
||||
const canvas = document.createElement('canvas')
|
||||
canvas.width = width
|
||||
canvas.height = height
|
||||
const context = canvas.getContext('2d')
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
context.drawImage(modelViewer, 0, 0, width, height)
|
||||
|
||||
resolve(canvas.toDataURL(format, quality))
|
||||
})
|
||||
}
|
||||
|
||||
app.registerExtension({
|
||||
name: 'Mixlab.image.SvgImage',
|
||||
async getCustomWidgets (app) {
|
||||
return {
|
||||
SVG (node, inputName, inputData, app) {
|
||||
// console.log('##node', node, inputName, inputData)
|
||||
const widget = {
|
||||
type: inputData[0], // the type, CHEESE
|
||||
name: inputName, // the name, slice
|
||||
size: [128, 88], // a default size
|
||||
draw (ctx, node, width, y) {},
|
||||
computeSize (...args) {
|
||||
return [128, 88] // a method to compute the current size of the widget
|
||||
},
|
||||
async serializeValue (nodeId, widgetIndex) {
|
||||
let d = getLocalData('_mixlab_svg_image')
|
||||
// console.log('serializeValue',d)
|
||||
if (d) {
|
||||
let url = d[node.id]
|
||||
let dt = await fetch(url)
|
||||
let svgStr = await dt.text()
|
||||
const { data, image } = (await parseSvg(svgStr)) || {}
|
||||
// console.log(data, image)
|
||||
return JSON.parse(JSON.stringify({ data, image }))
|
||||
} else {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// console.log('##node',node.serialize)
|
||||
// widget.something = something; // maybe adds stuff to it
|
||||
node.addCustomWidget(widget) // adds it to the node
|
||||
|
||||
return widget // and returns it.
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
async beforeRegisterNodeDef (nodeType, nodeData, app) {
|
||||
if (nodeType.comfyClass == 'SvgImage') {
|
||||
const orig_nodeCreated = nodeType.prototype.onNodeCreated
|
||||
nodeType.prototype.onNodeCreated = async function () {
|
||||
orig_nodeCreated?.apply(this, arguments)
|
||||
|
||||
const uploadWidget = this.widgets.filter(w => w.name == 'upload')[0]
|
||||
// console.log('SvgImage nodeData',await uploadWidget.serializeValue())
|
||||
|
||||
const widget = {
|
||||
type: 'div',
|
||||
name: 'upload-preview',
|
||||
draw (ctx, node, widget_width, y, widget_height) {
|
||||
Object.assign(
|
||||
this.div.style,
|
||||
get_position_style(ctx, widget_width, 44, node.size[1])
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
widget.div = $el('div', {})
|
||||
|
||||
document.body.appendChild(widget.div)
|
||||
|
||||
const inputDiv = (key, placeholder, svgContainer) => {
|
||||
let div = document.createElement('div')
|
||||
const ip = document.createElement('input')
|
||||
ip.type = 'file'
|
||||
ip.className = `${'comfy-multiline-input'} ${placeholder}`
|
||||
div.style = `display: flex;
|
||||
align-items: center;
|
||||
margin: 6px 8px;
|
||||
margin-top: 0;`
|
||||
ip.placeholder = placeholder
|
||||
// ip.value = value
|
||||
|
||||
ip.style = `outline: none;
|
||||
border: none;
|
||||
padding: 4px;
|
||||
width: 60%;cursor: pointer;
|
||||
height: 32px;`
|
||||
const label = document.createElement('label')
|
||||
label.style = 'font-size: 10px;min-width:32px'
|
||||
label.innerText = placeholder
|
||||
div.appendChild(label)
|
||||
div.appendChild(ip)
|
||||
|
||||
let that = this
|
||||
|
||||
ip.addEventListener('change', event => {
|
||||
const file = event.target.files[0]
|
||||
const reader = new FileReader()
|
||||
|
||||
// 读取文件内容
|
||||
reader.onload = async e => {
|
||||
const svgContent = e.target.result
|
||||
|
||||
var blob = new Blob([svgContent], { type: 'image/svg+xml' })
|
||||
let url = await uploadImage(blob)
|
||||
// console.log(url)
|
||||
const { svgElement, data, image } = await parseSvg(svgContent)
|
||||
// 将提取的SVG元素显示在页面上
|
||||
let dd = getLocalData(key)
|
||||
dd[that.id] = url
|
||||
setLocalDataOfWin(key, dd)
|
||||
// console.log(this.id, ip.value.trim())
|
||||
|
||||
svgElement.style = `width: 90%;padding: 5%;height: auto;`
|
||||
// 将提取的SVG元素显示在页面上
|
||||
|
||||
svgContainer.innerHTML = ''
|
||||
svgContainer.appendChild(svgElement)
|
||||
let h = ~~getComputedStyle(svgElement).height.replace('px', '')
|
||||
if (that.size && that.size[1] < h) {
|
||||
that.setSize([that.size[0], that.size[1] + h])
|
||||
app.canvas.draw(true, true)
|
||||
}
|
||||
// console.log(that.size,~~getComputedStyle(svgElement).height.replace('px',''))
|
||||
|
||||
uploadWidget.value = await uploadWidget.serializeValue()
|
||||
}
|
||||
|
||||
// 以文本形式读取文件
|
||||
reader.readAsText(file)
|
||||
})
|
||||
return div
|
||||
}
|
||||
|
||||
let svg = document.createElement('div')
|
||||
svg.className = 'preview'
|
||||
svg.style = `background:#eee;margin-top: 12px;`
|
||||
|
||||
let upload = inputDiv('_mixlab_svg_image', 'Svg', svg)
|
||||
|
||||
widget.div.appendChild(upload)
|
||||
widget.div.appendChild(svg)
|
||||
this.addCustomWidget(widget)
|
||||
|
||||
const onRemoved = this.onRemoved
|
||||
this.onRemoved = () => {
|
||||
upload.remove()
|
||||
svg.remove()
|
||||
widget.div.remove()
|
||||
return onRemoved?.()
|
||||
}
|
||||
|
||||
if (this.onResize) {
|
||||
this.onResize(this.size)
|
||||
}
|
||||
|
||||
this.serialize_widgets = true //需要保存参数
|
||||
}
|
||||
}
|
||||
},
|
||||
async loadedGraphNode (node, app) {
|
||||
// Fires every time a node is constructed
|
||||
// You can modify widgets/add handlers/etc here
|
||||
const sleep = (t = 1000) => {
|
||||
return new Promise((res, rej) => {
|
||||
setTimeout(() => res(1), t)
|
||||
})
|
||||
}
|
||||
if (node.type === 'SvgImage') {
|
||||
// await sleep(0)
|
||||
let widget = node.widgets.filter(w => w.name === 'upload-preview')[0]
|
||||
|
||||
let dd = getLocalData('_mixlab_svg_image')
|
||||
|
||||
let id = node.id
|
||||
console.log('SvgImage load', node.widgets[0], node.widgets)
|
||||
if (!dd[id]) return
|
||||
let dt = await fetch(dd[id])
|
||||
let svgStr = await dt.text()
|
||||
|
||||
const { svgElement, data, image } = await parseSvg(svgStr)
|
||||
svgElement.style = `width: 90%;padding: 5%;height:auto`
|
||||
// 将提取的SVG元素显示在页面上
|
||||
|
||||
widget.div.querySelector('.preview').innerHTML = ''
|
||||
widget.div.querySelector('.preview').appendChild(svgElement)
|
||||
|
||||
const uploadWidget = node.widgets.filter(w => w.name == 'upload')[0]
|
||||
uploadWidget.value = await uploadWidget.serializeValue()
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const createSelect = (imgDiv, select, opts, targetWidget, textWidget) => {
|
||||
select.style.display = 'block'
|
||||
let html = ''
|
||||
let isMatch = false
|
||||
for (const opt of opts) {
|
||||
html += `<option value='${opt.keyword}' ${opt.selected ? 'selected' : ''}>${
|
||||
opt.keyword
|
||||
}</option>`
|
||||
if (opt.selected) {
|
||||
isMatch = true
|
||||
imgDiv.src = opt.imgurl
|
||||
// targetWidget.value = opt.keyword
|
||||
}
|
||||
}
|
||||
select.innerHTML = html
|
||||
if (!isMatch) {
|
||||
// targetWidget.value = opts[0].keyword
|
||||
imgDiv.src = opts[0].imgurl
|
||||
}
|
||||
|
||||
// 添加change事件监听器
|
||||
select.addEventListener('change', async function () {
|
||||
// 获取选中的选项的值
|
||||
var selectedOption = select.options[select.selectedIndex].value
|
||||
let t = opts.filter(opt => opt.keyword === selectedOption)[0]
|
||||
|
||||
targetWidget.value = await parseImageToBase64(t.imgurl)
|
||||
imgDiv.src = targetWidget.value
|
||||
textWidget.value = t.keyword
|
||||
})
|
||||
// console.log(select)
|
||||
}
|
||||
|
||||
app.registerExtension({
|
||||
name: 'Mixlab.prompt.ImagesPrompt_',
|
||||
async beforeRegisterNodeDef (nodeType, nodeData, app) {
|
||||
if (nodeType.comfyClass == 'ImagesPrompt_') {
|
||||
const orig_nodeCreated = nodeType.prototype.onNodeCreated
|
||||
nodeType.prototype.onNodeCreated = async function () {
|
||||
orig_nodeCreated?.apply(this, arguments)
|
||||
|
||||
const image_prompt = this.widgets.filter(
|
||||
w => w.name == 'image_base64'
|
||||
)[0]
|
||||
const image_text = this.widgets.filter(w => w.name == 'text')[0]
|
||||
|
||||
const node = this
|
||||
|
||||
const widget = {
|
||||
type: 'div',
|
||||
name: 'upload',
|
||||
draw (ctx, node, widget_width, y, widget_height) {
|
||||
Object.assign(
|
||||
this.div.style,
|
||||
get_position_style(ctx, widget_width, y, node.size[1])
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
widget.div = $el('div', {})
|
||||
|
||||
// console.log('image_prompt',image_prompt)
|
||||
const img = new Image()
|
||||
img.src = image_prompt?.value || base64Df
|
||||
widget.div.appendChild(img)
|
||||
|
||||
const btn = document.createElement('button')
|
||||
btn.innerText = 'Upload Images JSON'
|
||||
|
||||
btn.style = `cursor: pointer;
|
||||
font-weight: 300;
|
||||
margin: 2px;
|
||||
color: var(--descrip-text);
|
||||
background-color: var(--comfy-input-bg);
|
||||
border-radius: 8px;
|
||||
border-color: var(--border-color);
|
||||
border-style: solid;height: 30px;min-width: 122px;
|
||||
`
|
||||
|
||||
const select = document.createElement('select')
|
||||
select.style = `display:none;cursor: pointer;
|
||||
font-weight: 300;
|
||||
margin: 2px;
|
||||
color: var(--descrip-text);
|
||||
background-color: var(--comfy-input-bg);
|
||||
border-radius: 8px;
|
||||
border-color: var(--border-color);
|
||||
border-style: solid;height: 30px;min-width: 100px;
|
||||
`
|
||||
widget.select = select
|
||||
|
||||
// const btn=document.createElement('button');
|
||||
// btn.innerText='Upload'
|
||||
btn.addEventListener('click', () => {
|
||||
let inp = document.createElement('input')
|
||||
inp.type = 'file'
|
||||
inp.accept = '.json'
|
||||
inp.click()
|
||||
inp.addEventListener('change', event => {
|
||||
// 获取选择的文件
|
||||
// [{title,imageUrl}]
|
||||
const file = event.target.files[0]
|
||||
this.title = file.name.split('.')[0]
|
||||
|
||||
// console.log(file.name.split('.')[0])
|
||||
// 创建文件读取器
|
||||
const reader = new FileReader()
|
||||
|
||||
// 定义读取完成事件的回调函数
|
||||
reader.onload = async event => {
|
||||
// 读取完成后的文本内容
|
||||
const json = JSON.parse(event.target.result)
|
||||
console.log(node, json)
|
||||
|
||||
widget.value = JSON.stringify(json)
|
||||
|
||||
let img = widget.div.querySelector('img')
|
||||
|
||||
createSelect(img, select, json, image_prompt, image_text)
|
||||
|
||||
image_prompt.value = await parseImageToBase64(json[0].imgurl)
|
||||
image_text.value = json[0].keyword
|
||||
|
||||
if (img) {
|
||||
img.src = image_prompt.value
|
||||
}
|
||||
|
||||
inp.remove()
|
||||
}
|
||||
|
||||
// 以文本方式读取文件
|
||||
reader.readAsText(file)
|
||||
})
|
||||
})
|
||||
|
||||
widget.div.appendChild(btn)
|
||||
widget.div.appendChild(select)
|
||||
document.body.appendChild(widget.div)
|
||||
this.addCustomWidget(widget)
|
||||
|
||||
const onRemoved = this.onRemoved
|
||||
this.onRemoved = () => {
|
||||
widget.div.remove()
|
||||
return onRemoved?.()
|
||||
}
|
||||
|
||||
if (this.onResize) {
|
||||
this.onResize(this.size)
|
||||
}
|
||||
|
||||
this.serialize_widgets = true //需要保存参数
|
||||
}
|
||||
}
|
||||
},
|
||||
async loadedGraphNode (node, app) {
|
||||
if (node.type === 'ImagesPrompt_') {
|
||||
try {
|
||||
let prompt = node.widgets.filter(w => w.name === 'image_base64')[0]
|
||||
let text = node.widgets.filter(w => w.name === 'text')[0]
|
||||
let uploadWidget = node.widgets.filter(w => w.name == 'upload')[0]
|
||||
// console.log('##prompt',prompt.value)
|
||||
let img = uploadWidget.div.querySelector('img')
|
||||
let json = JSON.parse(uploadWidget.value)
|
||||
|
||||
for (let index = 0; index < json.length; index++) {
|
||||
const j = json[index]
|
||||
let base64 = await parseImageToBase64(j.imgurl)
|
||||
if (base64 === prompt.value) {
|
||||
json[index].selected = true
|
||||
}
|
||||
}
|
||||
|
||||
if (json && json[0]) {
|
||||
uploadWidget.select.style.display = 'block'
|
||||
createSelect(img, uploadWidget.select, json, prompt, text)
|
||||
}
|
||||
} catch (error) {}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const createInputImageForBatch = (base64, widget) => {
|
||||
let im = new Image()
|
||||
im.src = base64
|
||||
im.style = `width: 88px;`
|
||||
|
||||
im.addEventListener('click', e => {
|
||||
let newValue = []
|
||||
let items = widget.value?.base64 || []
|
||||
for (const v of items) {
|
||||
if (v != base64) newValue.push(v)
|
||||
}
|
||||
widget.value.base64 = newValue
|
||||
im.remove()
|
||||
})
|
||||
|
||||
return im
|
||||
}
|
||||
|
||||
app.registerExtension({
|
||||
name: 'Mixlab.Comfy.LoadImagesToBatch',
|
||||
async getCustomWidgets (app) {
|
||||
return {
|
||||
IMAGEBASE64 (node, inputName, inputData, app) {
|
||||
// console.log('##node', node)
|
||||
const widget = {
|
||||
value: {
|
||||
base64: []
|
||||
}, // 不能[x,x,x]
|
||||
type: inputData[0], // the type
|
||||
name: inputName, // the name, slice
|
||||
size: [128, 32], // a default size
|
||||
draw (ctx, node, width, y) {},
|
||||
computeSize (...args) {
|
||||
return [128, 32] // a method to compute the current size of the widget
|
||||
}
|
||||
// serializeValue (nodeId, widgetIndex) {
|
||||
// return widget.value
|
||||
// },
|
||||
}
|
||||
// widget.something = something; // maybe adds stuff to it
|
||||
node.addCustomWidget(widget) // adds it to the node
|
||||
return widget // and returns it.
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
async beforeRegisterNodeDef (nodeType, nodeData, app) {
|
||||
if (nodeType.comfyClass == 'LoadImagesToBatch') {
|
||||
const orig_nodeCreated = nodeType.prototype.onNodeCreated
|
||||
nodeType.prototype.onNodeCreated = function () {
|
||||
orig_nodeCreated?.apply(this, arguments)
|
||||
|
||||
let imagesWidget = this.widgets.filter(w => w.name == 'images')[0]
|
||||
|
||||
const widget = {
|
||||
type: 'div',
|
||||
name: 'image_base64',
|
||||
draw (ctx, node, widget_width, y, widget_height) {
|
||||
Object.assign(
|
||||
this.div.style,
|
||||
get_position_style(ctx, widget_width, 44, node.size[1])
|
||||
)
|
||||
},
|
||||
serialize: false
|
||||
}
|
||||
|
||||
widget.div = $el('div', {})
|
||||
|
||||
document.body.appendChild(widget.div)
|
||||
|
||||
let imagePreview = document.createElement('div')
|
||||
let imagesDiv = document.createElement('div') //显示图片
|
||||
imagesDiv.className = 'images_preview'
|
||||
imagesDiv.style = `width: calc(100% - 14px);
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
padding: 7px; justify-content: space-between;
|
||||
align-items: center;`
|
||||
|
||||
let inputImage = document.createElement('input')
|
||||
inputImage.type = 'file'
|
||||
inputImage.style.display = 'none'
|
||||
inputImage.addEventListener('change', e => {
|
||||
e.preventDefault()
|
||||
const file = e.target.files[0]
|
||||
const reader = new FileReader()
|
||||
reader.onload = async event => {
|
||||
const base64 = event.target.result
|
||||
// console.log(base64)
|
||||
if (!imagesWidget.value) imagesWidget.value = { base64: [] }
|
||||
imagesWidget.value.base64.push(base64)
|
||||
let im = createInputImageForBatch(base64, imagesWidget)
|
||||
imagesDiv.appendChild(im)
|
||||
}
|
||||
reader.readAsDataURL(file)
|
||||
})
|
||||
|
||||
const btn = document.createElement('button')
|
||||
btn.innerText = 'Upload Image'
|
||||
|
||||
btn.style = `cursor: pointer;
|
||||
font-weight: 300;
|
||||
margin: 2px;
|
||||
color: var(--descrip-text);
|
||||
background-color: var(--comfy-input-bg);
|
||||
border-radius: 8px;
|
||||
border-color: var(--border-color);
|
||||
border-style: solid;height: 30px;min-width: 122px;
|
||||
`
|
||||
|
||||
btn.addEventListener('click', e => {
|
||||
e.preventDefault()
|
||||
inputImage.click()
|
||||
})
|
||||
|
||||
widget.div.appendChild(imagePreview)
|
||||
imagePreview.appendChild(imagesDiv)
|
||||
imagePreview.appendChild(btn)
|
||||
imagePreview.appendChild(inputImage)
|
||||
|
||||
this.addCustomWidget(widget)
|
||||
|
||||
// document.addEventListener('wheel', handleMouseWheel)
|
||||
|
||||
const onRemoved = this.onRemoved
|
||||
this.onRemoved = () => {
|
||||
inputImage.remove()
|
||||
widget.div.remove()
|
||||
try {
|
||||
// document.removeEventListener('wheel', handleMouseWheel)
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
}
|
||||
|
||||
return onRemoved?.()
|
||||
}
|
||||
|
||||
this.serialize_widgets = true //需要保存参数
|
||||
}
|
||||
}
|
||||
},
|
||||
async loadedGraphNode (node, app) {
|
||||
if (node.type === 'LoadImagesToBatch') {
|
||||
// await sleep(0)
|
||||
let imagesWidget = node.widgets.filter(w => w.name === 'images')[0]
|
||||
let imagePreview = node.widgets.filter(w => w.name == 'image_base64')[0]
|
||||
|
||||
let pre = imagePreview.div.querySelector('.images_preview')
|
||||
for (const d of imagesWidget.value?.base64 || []) {
|
||||
let im = createInputImageForBatch(d, imagesWidget)
|
||||
pre.appendChild(im)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,565 @@
|
||||
import { app } from '../../../scripts/app.js'
|
||||
import { api } from '../../../scripts/api.js'
|
||||
import { ComfyWidgets } from '../../../scripts/widgets.js'
|
||||
import { $el } from '../../../scripts/ui.js'
|
||||
|
||||
import PhotoSwipeLightbox from '/extensions/comfyui-mixlab-nodes/lib/photoswipe-lightbox.esm.min.js'
|
||||
function loadCSS (url) {
|
||||
var link = document.createElement('link')
|
||||
link.rel = 'stylesheet'
|
||||
link.type = 'text/css'
|
||||
link.href = url
|
||||
document.getElementsByTagName('head')[0].appendChild(link)
|
||||
|
||||
// Create a style element
|
||||
const style = document.createElement('style')
|
||||
// Define the CSS rule for scrollbar width
|
||||
const cssRule = `.pswp__custom-caption {
|
||||
background: rgb(20 27 70);
|
||||
font-size: 16px;
|
||||
color: #fff;
|
||||
width: calc(100% - 32px);
|
||||
max-width: 980px;
|
||||
padding: 2px 8px;
|
||||
border-radius: 4px;
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
bottom: 16px;
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
.pswp__custom-caption a {
|
||||
color: #fff;
|
||||
text-decoration: underline;
|
||||
}
|
||||
.hidden-caption-content {
|
||||
display: none;
|
||||
}`
|
||||
// Add the CSS rule to the style element
|
||||
style.appendChild(document.createTextNode(cssRule))
|
||||
|
||||
// Append the style element to the document head
|
||||
document.head.appendChild(style)
|
||||
}
|
||||
loadCSS('/extensions/comfyui-mixlab-nodes/lib/photoswipe.min.css')
|
||||
|
||||
function initLightBox () {
|
||||
const lightbox = new PhotoSwipeLightbox({
|
||||
gallery: '.prompt_image_output',
|
||||
children: 'a',
|
||||
pswpModule: () =>
|
||||
import('/extensions/comfyui-mixlab-nodes/lib/photoswipe.esm.min.js')
|
||||
})
|
||||
|
||||
lightbox.on('uiRegister', function () {
|
||||
lightbox.pswp.ui.registerElement({
|
||||
name: 'custom-caption',
|
||||
order: 9,
|
||||
isButton: false,
|
||||
appendTo: 'root',
|
||||
html: 'Caption text',
|
||||
onInit: (el, pswp) => {
|
||||
lightbox.pswp.on('change', () => {
|
||||
const currSlideElement = lightbox.pswp.currSlide.data.element
|
||||
let captionHTML = ''
|
||||
if (currSlideElement) {
|
||||
const hiddenCaption = currSlideElement.querySelector(
|
||||
'.hidden-caption-content'
|
||||
)
|
||||
if (hiddenCaption) {
|
||||
// get caption from element with class hidden-caption-content
|
||||
captionHTML = hiddenCaption.innerHTML
|
||||
} else {
|
||||
// get caption from alt attribute
|
||||
captionHTML = currSlideElement
|
||||
.querySelector('img')
|
||||
.getAttribute('alt')
|
||||
}
|
||||
}
|
||||
el.innerHTML = captionHTML || ''
|
||||
})
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
lightbox.init()
|
||||
}
|
||||
|
||||
function get_position_style (ctx, widget_width, y, node_height) {
|
||||
const MARGIN = 4 // the margin around the html element
|
||||
|
||||
/* Create a transform that deals with all the scrolling and zooming */
|
||||
const elRect = ctx.canvas.getBoundingClientRect()
|
||||
const transform = new DOMMatrix()
|
||||
.scaleSelf(
|
||||
elRect.width / ctx.canvas.width,
|
||||
elRect.height / ctx.canvas.height
|
||||
)
|
||||
.multiplySelf(ctx.getTransform())
|
||||
.translateSelf(MARGIN, MARGIN + y)
|
||||
|
||||
return {
|
||||
transformOrigin: '0 0',
|
||||
transform: transform,
|
||||
left: `0`,
|
||||
top: `0`,
|
||||
cursor: 'pointer',
|
||||
position: 'absolute',
|
||||
maxWidth: `${widget_width - MARGIN * 2}px`,
|
||||
// maxHeight: `${node_height - MARGIN * 2}px`, // we're assuming we have the whole height of the node
|
||||
width: `${widget_width - MARGIN * 2 - 24}px`,
|
||||
// height: `${node_height * 0.3 - MARGIN * 2}px`,
|
||||
// background: '#EEEEEE',
|
||||
paddingLeft: '12px',
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
// alignItems: 'center',
|
||||
justifyContent: 'space-between'
|
||||
}
|
||||
}
|
||||
function createImage (url) {
|
||||
let im = new Image()
|
||||
return new Promise((res, rej) => {
|
||||
im.onload = () => res(im)
|
||||
im.src = url
|
||||
})
|
||||
}
|
||||
|
||||
async function fetchImage (url) {
|
||||
try {
|
||||
const response = await fetch(url)
|
||||
const blob = await response.blob()
|
||||
|
||||
return blob
|
||||
} catch (error) {
|
||||
console.error('出现错误:', error)
|
||||
}
|
||||
}
|
||||
|
||||
const getLocalData = key => {
|
||||
let data = {}
|
||||
try {
|
||||
data = JSON.parse(localStorage.getItem(key)) || {}
|
||||
} catch (error) {
|
||||
return {}
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
const setLocalDataOfWin = (key, value) => {
|
||||
localStorage.setItem(key, JSON.stringify(value))
|
||||
// window[key] = value
|
||||
}
|
||||
|
||||
const createSelect = (select, opts, targetWidget) => {
|
||||
select.style.display = 'block'
|
||||
let html = ''
|
||||
let isMatch = false
|
||||
for (const opt of opts) {
|
||||
html += `<option value='${opt}' ${
|
||||
targetWidget.value === opt ? 'selected' : ''
|
||||
}>${opt}</option>`
|
||||
if (targetWidget.value === opt) isMatch = true
|
||||
}
|
||||
select.innerHTML = html
|
||||
if (!isMatch) targetWidget.value = opts[0]
|
||||
// 添加change事件监听器
|
||||
select.addEventListener('change', function () {
|
||||
// 获取选中的选项的值
|
||||
var selectedOption = select.options[select.selectedIndex].value
|
||||
targetWidget.value = selectedOption
|
||||
// console.log(widget,selectedOption)
|
||||
})
|
||||
}
|
||||
|
||||
app.registerExtension({
|
||||
name: 'Mixlab.prompt.RandomPrompt',
|
||||
async beforeRegisterNodeDef (nodeType, nodeData, app) {
|
||||
if (nodeType.comfyClass == 'RandomPrompt') {
|
||||
const orig_nodeCreated = nodeType.prototype.onNodeCreated
|
||||
nodeType.prototype.onNodeCreated = async function () {
|
||||
orig_nodeCreated?.apply(this, arguments)
|
||||
|
||||
const mutable_prompt = this.widgets.filter(
|
||||
w => w.name == 'mutable_prompt'
|
||||
)[0]
|
||||
// console.log('PromptSlide nodeData', prompt_keyword)
|
||||
|
||||
const widget = {
|
||||
type: 'div',
|
||||
name: 'upload',
|
||||
draw (ctx, node, widget_width, y, widget_height) {
|
||||
Object.assign(
|
||||
this.div.style,
|
||||
get_position_style(ctx, widget_width, y, node.size[1])
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
widget.div = $el('div', {})
|
||||
|
||||
const btn = document.createElement('button')
|
||||
btn.innerText = 'Upload Keywords'
|
||||
|
||||
btn.style = `cursor: pointer;
|
||||
font-weight: 300;
|
||||
margin: 2px;
|
||||
color: var(--descrip-text);
|
||||
background-color: var(--comfy-input-bg);
|
||||
border-radius: 8px;
|
||||
border-color: var(--border-color);
|
||||
border-style: solid; height: 30px;min-width: 122px;
|
||||
`
|
||||
|
||||
// const btn=document.createElement('button');
|
||||
// btn.innerText='Upload'
|
||||
btn.addEventListener('click', () => {
|
||||
let inp = document.createElement('input')
|
||||
inp.type = 'file'
|
||||
inp.accept = '.txt'
|
||||
inp.click()
|
||||
inp.addEventListener('change', event => {
|
||||
// 获取选择的文件
|
||||
const file = event.target.files[0]
|
||||
this.title = file.name.split('.')[0]
|
||||
|
||||
// console.log(file.name.split('.')[0])
|
||||
// 创建文件读取器
|
||||
const reader = new FileReader()
|
||||
|
||||
// 定义读取完成事件的回调函数
|
||||
reader.onload = event => {
|
||||
// 读取完成后的文本内容
|
||||
const fileContent = event.target.result.split('\n')
|
||||
const keywords = Array.from(fileContent, f => f.trim()).filter(
|
||||
f => f
|
||||
)
|
||||
// 打印文件内容
|
||||
// console.log(keywords)
|
||||
|
||||
mutable_prompt.value = keywords.join('\n')
|
||||
|
||||
inp.remove()
|
||||
}
|
||||
|
||||
// 以文本方式读取文件
|
||||
reader.readAsText(file)
|
||||
})
|
||||
})
|
||||
|
||||
widget.div.appendChild(btn)
|
||||
document.body.appendChild(widget.div)
|
||||
this.addCustomWidget(widget)
|
||||
|
||||
const onRemoved = this.onRemoved
|
||||
this.onRemoved = () => {
|
||||
widget.div.remove()
|
||||
return onRemoved?.()
|
||||
}
|
||||
|
||||
if (this.onResize) {
|
||||
this.onResize(this.size)
|
||||
}
|
||||
|
||||
this.serialize_widgets = true //需要保存参数
|
||||
}
|
||||
}
|
||||
},
|
||||
async loadedGraphNode (node, app) {
|
||||
if (node.type === 'RandomPrompt') {
|
||||
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
app.registerExtension({
|
||||
name: 'Mixlab.prompt.PromptSlide',
|
||||
async beforeRegisterNodeDef (nodeType, nodeData, app) {
|
||||
if (nodeType.comfyClass == 'PromptSlide') {
|
||||
const orig_nodeCreated = nodeType.prototype.onNodeCreated
|
||||
nodeType.prototype.onNodeCreated = async function () {
|
||||
orig_nodeCreated?.apply(this, arguments)
|
||||
|
||||
const prompt_keyword = this.widgets.filter(
|
||||
w => w.name == 'prompt_keyword'
|
||||
)[0]
|
||||
// console.log('PromptSlide nodeData', prompt_keyword)
|
||||
|
||||
const widget = {
|
||||
type: 'div',
|
||||
name: 'upload',
|
||||
draw (ctx, node, widget_width, y, widget_height) {
|
||||
Object.assign(
|
||||
this.div.style,
|
||||
get_position_style(ctx, widget_width, y, node.size[1])
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
widget.div = $el('div', {})
|
||||
|
||||
const btn = document.createElement('button')
|
||||
btn.innerText = 'Upload Keywords'
|
||||
|
||||
btn.style = `cursor: pointer;
|
||||
font-weight: 300;
|
||||
margin: 2px;
|
||||
color: var(--descrip-text);
|
||||
background-color: var(--comfy-input-bg);
|
||||
border-radius: 8px;
|
||||
border-color: var(--border-color);
|
||||
border-style: solid; height: 30px;min-width: 122px;
|
||||
`
|
||||
|
||||
const select = document.createElement('select')
|
||||
select.style = `display:none;cursor: pointer;
|
||||
font-weight: 300;
|
||||
margin: 2px;
|
||||
color: var(--descrip-text);
|
||||
background-color: var(--comfy-input-bg);
|
||||
border-radius: 8px;
|
||||
border-color: var(--border-color);
|
||||
border-style: solid; height: 30px;min-width: 100px;
|
||||
`
|
||||
widget.select = select
|
||||
|
||||
// const btn=document.createElement('button');
|
||||
// btn.innerText='Upload'
|
||||
btn.addEventListener('click', () => {
|
||||
let inp = document.createElement('input')
|
||||
inp.type = 'file'
|
||||
inp.accept = '.txt'
|
||||
inp.click()
|
||||
inp.addEventListener('change', event => {
|
||||
// 获取选择的文件
|
||||
const file = event.target.files[0]
|
||||
this.title = file.name.split('.')[0]
|
||||
|
||||
// console.log(file.name.split('.')[0])
|
||||
// 创建文件读取器
|
||||
const reader = new FileReader()
|
||||
|
||||
// 定义读取完成事件的回调函数
|
||||
reader.onload = event => {
|
||||
// 读取完成后的文本内容
|
||||
const fileContent = event.target.result.split('\n')
|
||||
const keywords = Array.from(fileContent, f => f.trim()).filter(
|
||||
f => f
|
||||
)
|
||||
// 打印文件内容
|
||||
// console.log(keywords)
|
||||
|
||||
widget.value = JSON.stringify(keywords)
|
||||
|
||||
// let ks = getLocalData(`_mixlab_PromptSlide`)
|
||||
// ks[this.id] = keywords
|
||||
// setLocalDataOfWin(`_mixlab_PromptSlide`, ks)
|
||||
|
||||
createSelect(select, keywords, prompt_keyword)
|
||||
|
||||
inp.remove()
|
||||
}
|
||||
|
||||
// 以文本方式读取文件
|
||||
reader.readAsText(file)
|
||||
})
|
||||
})
|
||||
|
||||
widget.div.appendChild(btn)
|
||||
widget.div.appendChild(select)
|
||||
document.body.appendChild(widget.div)
|
||||
this.addCustomWidget(widget)
|
||||
|
||||
const onRemoved = this.onRemoved
|
||||
this.onRemoved = () => {
|
||||
widget.div.remove()
|
||||
return onRemoved?.()
|
||||
}
|
||||
|
||||
if (this.onResize) {
|
||||
this.onResize(this.size)
|
||||
}
|
||||
|
||||
this.serialize_widgets = true //需要保存参数
|
||||
}
|
||||
}
|
||||
},
|
||||
async loadedGraphNode (node, app) {
|
||||
if (node.type === 'PromptSlide') {
|
||||
try {
|
||||
let prompt = node.widgets.filter(w => w.name === 'prompt_keyword')[0]
|
||||
// let ks = getLocalData(`_mixlab_PromptSlide`)
|
||||
let uploadWidget = node.widgets.filter(w => w.name == 'upload')[0]
|
||||
// console.log('##widget', uploadWidget.value)
|
||||
let keywords = JSON.parse(uploadWidget.value)
|
||||
// console.log('keywords',keywords)
|
||||
let widget = node.widgets.filter(w => w.select)[0]
|
||||
if (keywords && keywords[0]) {
|
||||
widget.select.style.display = 'block'
|
||||
createSelect(widget.select, keywords, prompt)
|
||||
}
|
||||
} catch (error) {}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const _createResult = async (node, widget, message) => {
|
||||
widget.div.innerHTML = ``
|
||||
|
||||
const width = node.size[0] * 0.5 - 12
|
||||
|
||||
let height_add = 0
|
||||
|
||||
for (let index = 0; index < message._images.length; index++) {
|
||||
const imgs = message._images[index]
|
||||
|
||||
for (const img of imgs) {
|
||||
let url = api.apiURL(
|
||||
`/view?filename=${encodeURIComponent(img.filename)}&type=${
|
||||
img.type
|
||||
}&subfolder=${
|
||||
img.subfolder
|
||||
}${app.getPreviewFormatParam()}${app.getRandParam()}`
|
||||
)
|
||||
|
||||
let image = await createImage(url)
|
||||
|
||||
// 创建card
|
||||
let div = document.createElement('div')
|
||||
div.className = 'card'
|
||||
div.draggable = true
|
||||
|
||||
div.ondragend = async event => {
|
||||
console.log('拖动停止')
|
||||
let url = div.querySelector('img').src
|
||||
|
||||
let blob = await fetchImage(url)
|
||||
|
||||
let imageNode = null
|
||||
// No image node selected: add a new one
|
||||
if (!imageNode) {
|
||||
const newNode = LiteGraph.createNode('LoadImage')
|
||||
newNode.pos = [...app.canvas.graph_mouse]
|
||||
imageNode = app.graph.add(newNode)
|
||||
app.graph.change()
|
||||
}
|
||||
|
||||
// const blob = item.getAsFile();
|
||||
imageNode.pasteFile(blob)
|
||||
}
|
||||
|
||||
div.setAttribute('data-scale', image.naturalHeight / image.naturalWidth)
|
||||
|
||||
let h = (image.naturalHeight * width) / image.naturalWidth
|
||||
if (index % 2 === 0) height_add += h
|
||||
div.style = `width: ${width}px;height:${h}px;position: relative;margin: 4px;`
|
||||
|
||||
div.innerHTML = `<a href="${url}"
|
||||
data-pswp-width="${image.naturalWidth}"
|
||||
data-pswp-height="${image.naturalHeight}"
|
||||
target="_blank">
|
||||
<img src="${url}" style='width: 100%' alt="${message.prompts[index]}"/>
|
||||
</a>
|
||||
<p style="position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
opacity: 0.6;
|
||||
background-color: var(--comfy-input-bg);
|
||||
color: var(--descrip-text);
|
||||
margin: 0;
|
||||
font-size: 12px;
|
||||
padding: 5px;
|
||||
text-align: left;">${message.prompts[index]}</p>`
|
||||
widget.div.appendChild(div)
|
||||
}
|
||||
}
|
||||
|
||||
node.size[1] = 98 + height_add
|
||||
}
|
||||
|
||||
app.registerExtension({
|
||||
name: 'Mixlab.prompt.PromptImage',
|
||||
|
||||
async beforeRegisterNodeDef (nodeType, nodeData, app) {
|
||||
if (nodeType.comfyClass == 'PromptImage') {
|
||||
const orig_nodeCreated = nodeType.prototype.onNodeCreated
|
||||
nodeType.prototype.onNodeCreated = function () {
|
||||
orig_nodeCreated?.apply(this, arguments)
|
||||
console.log('#orig_nodeCreated', this)
|
||||
const widget = {
|
||||
type: 'div',
|
||||
name: 'result',
|
||||
draw (ctx, node, widget_width, y, widget_height) {
|
||||
Object.assign(this.div.style, {
|
||||
...get_position_style(ctx, widget_width, y, node.size[1]),
|
||||
flexWrap: 'wrap',
|
||||
justifyContent: 'space-between',
|
||||
// outline: '1px solid red',
|
||||
paddingLeft: '0px',
|
||||
width: widget_width + 'px'
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
widget.div = $el('div', {})
|
||||
widget.div.className = 'prompt_image_output'
|
||||
|
||||
document.body.appendChild(widget.div)
|
||||
|
||||
this.addCustomWidget(widget)
|
||||
|
||||
initLightBox()
|
||||
|
||||
const onRemoved = this.onRemoved
|
||||
this.onRemoved = () => {
|
||||
widget.div.remove()
|
||||
return onRemoved?.()
|
||||
}
|
||||
|
||||
const onResize = this.onResize
|
||||
this.onResize = function () {
|
||||
// 缩放发生
|
||||
// console.log('##缩放发生', this.size)
|
||||
let w = this.size[0] * 0.5 - 12
|
||||
Array.from(widget.div.querySelectorAll('.card'), card => {
|
||||
card.style.width = `${w}px`
|
||||
card.style.height = `${
|
||||
w * parseFloat(card.getAttribute('data-scale'))
|
||||
}px`
|
||||
})
|
||||
return onResize?.apply(this, arguments)
|
||||
}
|
||||
|
||||
// this.serialize_widgets = true //需要保存参数
|
||||
}
|
||||
|
||||
const onExecuted = nodeType.prototype.onExecuted
|
||||
nodeType.prototype.onExecuted = async function (message) {
|
||||
onExecuted?.apply(this, arguments)
|
||||
console.log('#PromptImage', message.prompts, message._images)
|
||||
// window._mixlab_app_json = message.json
|
||||
try {
|
||||
let widget = this.widgets.filter(w => w.name === 'result')[0]
|
||||
widget.value = message
|
||||
_createResult(this, widget, { ...message })
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
}
|
||||
}
|
||||
|
||||
this.serialize_widgets = true //需要保存参数
|
||||
}
|
||||
},
|
||||
async loadedGraphNode (node, app) {
|
||||
if (node.type === 'PromptImage') {
|
||||
// await sleep(0)
|
||||
let widget = node.widgets.filter(w => w.name === 'result')[0]
|
||||
console.log('widget.value', widget.value)
|
||||
|
||||
initLightBox()
|
||||
|
||||
let cards = widget.div.querySelectorAll('.card')
|
||||
if (cards.length == 0) node.size = [280, 120]
|
||||
if(widget.value) _createResult(node, widget, widget.value)
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,302 @@
|
||||
const smart_connect_config_input = [
|
||||
{
|
||||
node_type: 'CLIPTextEncode',
|
||||
node_widget_name: 'text',
|
||||
inputNodeName: 'RandomPrompt',
|
||||
inputNode_output_name: 'STRING'
|
||||
},
|
||||
{
|
||||
node_type: 'CLIPTextEncode',
|
||||
node_widget_name: 'text',
|
||||
inputNodeName: 'EmbeddingPrompt',
|
||||
inputNode_output_name: 'STRING'
|
||||
},
|
||||
{
|
||||
node_type: 'CLIPTextEncode',
|
||||
node_widget_name: 'text',
|
||||
inputNodeName: 'ChinesePrompt_Mix',
|
||||
inputNode_output_name: 'prompt'
|
||||
},
|
||||
{
|
||||
node_type: 'CheckpointLoaderSimple',
|
||||
node_widget_name: 'ckpt_name',
|
||||
inputNodeName: 'CkptNames_',
|
||||
inputNode_output_name: 'ckpt_names'
|
||||
},
|
||||
{
|
||||
node_type: 'KSampler',
|
||||
node_widget_name: 'sampler_name',
|
||||
inputNodeName: 'SamplerNames_',
|
||||
inputNode_output_name: 'sampler_names'
|
||||
},
|
||||
{
|
||||
node_type: 'LoraLoaderModelOnly',
|
||||
node_widget_name: 'lora_name',
|
||||
inputNodeName: 'LoraNames_',
|
||||
inputNode_output_name: 'lora_names'
|
||||
},
|
||||
{
|
||||
node_type: 'LoadLoRA',
|
||||
node_widget_name: 'lora_name',
|
||||
inputNodeName: 'LoraNames_',
|
||||
inputNode_output_name: 'lora_names'
|
||||
},
|
||||
{
|
||||
node_type: 'Moondream',
|
||||
node_widget_name: 'image',
|
||||
inputNodeName: 'LoadImage',
|
||||
inputNode_output_name: 'IMAGE'
|
||||
}
|
||||
]
|
||||
|
||||
const smart_connect_config_output = [
|
||||
{
|
||||
node_type: 'LoadImage',
|
||||
node_output_name: 'IMAGE',
|
||||
outputNodeName: 'ClipInterrogator',
|
||||
outputNode_input_name: 'image'
|
||||
},
|
||||
{
|
||||
node_type: 'VAEDecode',
|
||||
node_output_name: 'IMAGE',
|
||||
outputNodeName: 'PromptImage',
|
||||
outputNode_input_name: 'images'
|
||||
},
|
||||
{
|
||||
node_type: 'VAEDecode',
|
||||
node_output_name: 'IMAGE',
|
||||
outputNodeName: 'PreviewImage',
|
||||
outputNode_input_name: 'images'
|
||||
},
|
||||
{
|
||||
node_type: 'VAEDecode',
|
||||
node_output_name: 'IMAGE',
|
||||
outputNodeName: 'SaveImage',
|
||||
outputNode_input_name: 'images'
|
||||
},
|
||||
{
|
||||
node_type: 'Moondream',
|
||||
node_output_name: 'STRING',
|
||||
outputNodeName: 'ShowTextForGPT',
|
||||
outputNode_input_name: 'text'
|
||||
}
|
||||
]
|
||||
|
||||
// import {
|
||||
// convertToInput,
|
||||
// getConfig,
|
||||
// isConvertableWidget
|
||||
// } from '../../../extensions/core/widgetInputs.js'
|
||||
|
||||
const CONVERTED_TYPE = 'converted-widget'
|
||||
const GET_CONFIG = Symbol()
|
||||
|
||||
function getConfig (widgetName) {
|
||||
const { nodeData } = this.constructor
|
||||
return (
|
||||
nodeData?.input?.required[widgetName] ??
|
||||
nodeData?.input?.optional?.[widgetName]
|
||||
)
|
||||
}
|
||||
|
||||
function hideWidget (node, widget, suffix = '') {
|
||||
widget.origType = widget.type
|
||||
widget.origComputeSize = widget.computeSize
|
||||
widget.origSerializeValue = widget.serializeValue
|
||||
widget.computeSize = () => [0, -4] // -4 is due to the gap litegraph adds between widgets automatically
|
||||
widget.type = CONVERTED_TYPE + suffix
|
||||
widget.serializeValue = () => {
|
||||
// Prevent serializing the widget if we have no input linked
|
||||
if (!node.inputs) {
|
||||
return undefined
|
||||
}
|
||||
let node_input = node.inputs.find(i => i.widget?.name === widget.name)
|
||||
|
||||
if (!node_input || !node_input.link) {
|
||||
return undefined
|
||||
}
|
||||
return widget.origSerializeValue
|
||||
? widget.origSerializeValue()
|
||||
: widget.value
|
||||
}
|
||||
|
||||
// Hide any linked widgets, e.g. seed+seedControl
|
||||
if (widget.linkedWidgets) {
|
||||
for (const w of widget.linkedWidgets) {
|
||||
hideWidget(node, w, ':' + widget.name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function convertToInput (node, widget, config) {
|
||||
hideWidget(node, widget)
|
||||
|
||||
const type = config[0]
|
||||
|
||||
// Add input and store widget config for creating on primitive node
|
||||
const sz = node.size
|
||||
node.addInput(widget.name, type, {
|
||||
widget: { name: widget.name, [GET_CONFIG]: () => config }
|
||||
})
|
||||
|
||||
for (const widget of node.widgets) {
|
||||
widget.last_y += LiteGraph.NODE_SLOT_HEIGHT
|
||||
}
|
||||
|
||||
// Restore original size but grow if needed
|
||||
node.setSize([Math.max(sz[0], node.size[0]), Math.max(sz[1], node.size[1])])
|
||||
}
|
||||
|
||||
export function smart_init () {
|
||||
LGraphCanvas.prototype._createNodeForInput = function (
|
||||
node,
|
||||
widget,
|
||||
inputNodeName,
|
||||
inputNode_slot
|
||||
) {
|
||||
// console.log(node.pos)
|
||||
|
||||
// var widget = node.widgets.filter(w => w.name === node_widget_name)[0]
|
||||
if (widget) {
|
||||
// 如果有存在的,没有连线输出的,自动连,不新建
|
||||
let input_node = null
|
||||
|
||||
Array.from(app.graph.findNodesByType(inputNodeName), n => {
|
||||
var links = n.outputs.filter(o => o.name === inputNode_slot)[0].links
|
||||
// console.log(links)
|
||||
if (!links || links?.length === 0) input_node = n
|
||||
})
|
||||
// 新建
|
||||
if (!input_node) {
|
||||
input_node = LiteGraph.createNode(inputNodeName)
|
||||
input_node.pos = [node.pos[0] - node.size[0] - 24, node.pos[1] - 48]
|
||||
app.canvas.graph.add(input_node, false)
|
||||
} else {
|
||||
input_node.pos = [node.pos[0] - node.size[0] - 24, node.pos[1] - 48]
|
||||
}
|
||||
|
||||
const config = getConfig.call(node, widget.name) ?? [
|
||||
widget.type,
|
||||
widget.options || {}
|
||||
]
|
||||
let node_slotType = config[0]
|
||||
// 如果input没有,则创建
|
||||
if (!node.inputs?.filter(inp => inp.name === widget.name)[0]||!node.inputs)
|
||||
convertToInput(node, widget, config)
|
||||
input_node.connectByType(inputNode_slot, node, node_slotType)
|
||||
}
|
||||
}
|
||||
|
||||
LGraphCanvas.prototype._createNodeForOutput = function (
|
||||
node,
|
||||
widget,
|
||||
outputNodeName,
|
||||
outputNode_slot
|
||||
) {
|
||||
if (widget) {
|
||||
let output_node
|
||||
Array.from(app.graph.findNodesByType(outputNodeName), n => {
|
||||
var links = n.inputs.filter(o => o.name === outputNode_slot)[0].links
|
||||
// console.log(links)
|
||||
if (!links || links?.length === 0) output_node = n
|
||||
})
|
||||
console.log('output_node', output_node, widget.name)
|
||||
|
||||
if (!output_node) {
|
||||
// 新建
|
||||
output_node = LiteGraph.createNode(outputNodeName)
|
||||
output_node.pos = [node.pos[0] + node.size[0] + 24, node.pos[1] - 48]
|
||||
app.canvas.graph.add(output_node, false)
|
||||
} else {
|
||||
output_node.pos = [node.pos[0] + node.size[0] + 24, node.pos[1] - 48]
|
||||
}
|
||||
|
||||
const config = getConfig.call(node, widget.name) ?? [
|
||||
widget.type,
|
||||
widget.options || {}
|
||||
]
|
||||
let node_slotType = config[0]
|
||||
console.log(node_slotType, output_node, outputNode_slot)
|
||||
let type = output_node.inputs.filter(
|
||||
inp => inp.name == outputNode_slot
|
||||
)[0].type
|
||||
node.connectByType(node_slotType, output_node, type)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function addSmartMenu (options, node) {
|
||||
let sopts = []
|
||||
|
||||
for (const sc of smart_connect_config_input) {
|
||||
// 有智能推荐,则出现
|
||||
if (node.type === sc.node_type) {
|
||||
// console.log('smart',node)
|
||||
// 则出现 randomPrompt
|
||||
// CLIPTextEncode 的widget ,name== 'text'
|
||||
let node_widget_name = sc.node_widget_name
|
||||
let widget = node.widgets.filter(w => w.name === node_widget_name)[0]
|
||||
if (!widget) {
|
||||
// 控件没有,则查找inputs
|
||||
widget = node.inputs.filter(w => w.name === node_widget_name)[0]
|
||||
}
|
||||
|
||||
let isLinkNull = true
|
||||
// 如果input里已经有,但是link为空
|
||||
if (node.inputs?.filter(inp => inp.name === node_widget_name)[0]) {
|
||||
isLinkNull =
|
||||
node.inputs.filter(inp => inp.name === node_widget_name)[0].link ===
|
||||
null
|
||||
}
|
||||
|
||||
if (widget && isLinkNull) {
|
||||
sopts.push({
|
||||
content: sc.inputNodeName.split('_')[0] + '➡️',
|
||||
callback: () => {
|
||||
LGraphCanvas.prototype._createNodeForInput(
|
||||
node, //当前node
|
||||
widget, //当前node里需要自动连线的widget
|
||||
sc.inputNodeName, //作为input的node type
|
||||
sc.inputNode_output_name // 作为input的node的outputs的name. the input slot type of the target node
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const sc of smart_connect_config_output) {
|
||||
if (node.type === sc.node_type) {
|
||||
let node_output_name = sc.node_output_name
|
||||
const widget = node.outputs.filter(w => w.name === node_output_name)[0]
|
||||
|
||||
let isLinkNull = true
|
||||
// 如果output里 link为空
|
||||
if (node.outputs?.filter(inp => inp.name === node_output_name)[0]) {
|
||||
isLinkNull =
|
||||
node.outputs.filter(inp => inp.name === node_output_name)[0].links
|
||||
?.length === 0
|
||||
if (!node.outputs.filter(inp => inp.name === node_output_name)[0].links)
|
||||
isLinkNull = true
|
||||
}
|
||||
|
||||
if (widget && isLinkNull) {
|
||||
sopts.push({
|
||||
content: '➡️' + sc.outputNodeName.split('_')[0],
|
||||
callback: () => {
|
||||
LGraphCanvas.prototype._createNodeForOutput(
|
||||
node, //当前node
|
||||
widget, //当前node里需要自动连线的widget
|
||||
sc.outputNodeName, //作为input的node type
|
||||
sc.outputNode_input_name // 作为input的node的outputs的name. the input slot type of the target node
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (sopts.length > 0) options = [...sopts, null, ...options]
|
||||
|
||||
return options
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export const closeIcon = '<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path d="m256-200-56-56 224-224-224-224 56-56 224 224 224-224 56 56-224 224 224 224-56 56-224-224-224 224Z"/></svg>'
|
||||
@@ -0,0 +1,361 @@
|
||||
import { app } from '../../../scripts/app.js'
|
||||
import { $el } from '../../../scripts/ui.js'
|
||||
|
||||
const getLocalData = key => {
|
||||
let data = {}
|
||||
try {
|
||||
data = JSON.parse(localStorage.getItem(key)) || {}
|
||||
} catch (error) {
|
||||
return {}
|
||||
}
|
||||
return data
|
||||
}
|
||||
function get_position_style (ctx, widget_width, y, node_height) {
|
||||
const MARGIN = 4 // the margin around the html element
|
||||
|
||||
/* Create a transform that deals with all the scrolling and zooming */
|
||||
const elRect = ctx.canvas.getBoundingClientRect()
|
||||
const transform = new DOMMatrix()
|
||||
.scaleSelf(
|
||||
elRect.width / ctx.canvas.width,
|
||||
elRect.height / ctx.canvas.height
|
||||
)
|
||||
.multiplySelf(ctx.getTransform())
|
||||
.translateSelf(MARGIN, MARGIN + y)
|
||||
|
||||
return {
|
||||
transformOrigin: '0 0',
|
||||
transform: transform,
|
||||
left: `0`,
|
||||
top: `0`,
|
||||
cursor: 'pointer',
|
||||
position: 'absolute',
|
||||
maxWidth: `${widget_width - MARGIN * 2}px`,
|
||||
// maxHeight: `${node_height - MARGIN * 2}px`, // we're assuming we have the whole height of the node
|
||||
width: `${widget_width - MARGIN * 2}px`,
|
||||
// height: `${node_height * 0.3 - MARGIN * 2}px`,
|
||||
// background: '#EEEEEE',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
// alignItems: 'center',
|
||||
justifyContent: 'space-around'
|
||||
}
|
||||
}
|
||||
|
||||
function hexToRGBA (hexColor) {
|
||||
var hex = hexColor.replace('#', '')
|
||||
var r = parseInt(hex.substring(0, 2), 16)
|
||||
var g = parseInt(hex.substring(2, 4), 16)
|
||||
var b = parseInt(hex.substring(4, 6), 16)
|
||||
|
||||
// 获取透明度的十六进制值
|
||||
var alphaHex = hex.substring(6)
|
||||
|
||||
// 将透明度的十六进制值转换为十进制值
|
||||
var alpha = parseInt(alphaHex, 16) / 255
|
||||
|
||||
return [r, g, b, alpha]
|
||||
}
|
||||
|
||||
app.registerExtension({
|
||||
name: 'Mixlab.utils.Color',
|
||||
init () {
|
||||
$el('link', {
|
||||
rel: 'stylesheet',
|
||||
href: '/extensions/comfyui-mixlab-nodes/lib/classic.min.css',
|
||||
parent: document.head
|
||||
})
|
||||
|
||||
$el('style', {
|
||||
textContent: `
|
||||
.pickr{
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
.pickr .pcr-button {
|
||||
width: 56px;
|
||||
height: 56px;
|
||||
outline: 1px solid white;
|
||||
}
|
||||
|
||||
`,
|
||||
parent: document.body
|
||||
})
|
||||
},
|
||||
async getCustomWidgets (app) {
|
||||
return {
|
||||
TCOLOR (node, inputName, inputData, app) {
|
||||
// console.log('##node', node)
|
||||
const widget = {
|
||||
type: inputData[0], // the type, CHEESE
|
||||
name: inputName, // the name, slice
|
||||
size: [128, 32], // a default size
|
||||
draw (ctx, node, width, y) {},
|
||||
computeSize (...args) {
|
||||
return [128, 32] // a method to compute the current size of the widget
|
||||
},
|
||||
async serializeValue (nodeId, widgetIndex) {
|
||||
// let data = getLocalData('_mixlab_utils_color')
|
||||
// let hex = data[node.id] || '#000000'
|
||||
let hex = widget.value || '#000000'
|
||||
let [r, g, b, a] = hexToRGBA(hex)
|
||||
return {
|
||||
hex,
|
||||
r,
|
||||
g,
|
||||
b,
|
||||
a
|
||||
}
|
||||
}
|
||||
}
|
||||
// widget.something = something; // maybe adds stuff to it
|
||||
node.addCustomWidget(widget) // adds it to the node
|
||||
return widget // and returns it.
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
async beforeRegisterNodeDef (nodeType, nodeData, app) {
|
||||
if (nodeType.comfyClass == 'Color') {
|
||||
const orig_nodeCreated = nodeType.prototype.onNodeCreated
|
||||
nodeType.prototype.onNodeCreated = function () {
|
||||
orig_nodeCreated?.apply(this, arguments)
|
||||
|
||||
// console.log('Color nodeData', this.widgets)
|
||||
|
||||
const widget = {
|
||||
type: 'div',
|
||||
name: 'input_color',
|
||||
draw (ctx, node, widget_width, y, widget_height) {
|
||||
Object.assign(
|
||||
this.div.style,
|
||||
get_position_style(ctx, widget_width, 44, node.size[1])
|
||||
)
|
||||
// console.log('draw',y,node.widgets[0].last_y)
|
||||
}
|
||||
}
|
||||
|
||||
widget.div = $el('div', {})
|
||||
|
||||
document.body.appendChild(widget.div)
|
||||
|
||||
const inputDiv = () => {
|
||||
let div = document.createElement('div')
|
||||
div.id = `color_picker_${this.id}`
|
||||
return div
|
||||
}
|
||||
|
||||
let inputColor = inputDiv('_mixlab_utils_color', 'Color', '#000000')
|
||||
|
||||
widget.div.appendChild(inputColor)
|
||||
|
||||
this.addCustomWidget(widget)
|
||||
|
||||
const pickr = Pickr.create({
|
||||
el: `#${inputColor.id}`,
|
||||
theme: 'classic', // or 'monolith', or 'nano'
|
||||
// closeOnScroll: true,
|
||||
default: '#000000',
|
||||
swatches: [
|
||||
'rgba(244, 67, 54, 1)',
|
||||
'rgba(233, 30, 99, 0.95)',
|
||||
'rgba(156, 39, 176, 0.9)',
|
||||
'rgba(103, 58, 183, 0.85)',
|
||||
'rgba(63, 81, 181, 0.8)',
|
||||
'rgba(33, 150, 243, 0.75)',
|
||||
'rgba(3, 169, 244, 0.7)',
|
||||
'rgba(0, 188, 212, 0.7)',
|
||||
'rgba(0, 150, 136, 0.75)',
|
||||
'rgba(76, 175, 80, 0.8)',
|
||||
'rgba(139, 195, 74, 0.85)',
|
||||
'rgba(205, 220, 57, 0.9)',
|
||||
'rgba(255, 235, 59, 0.95)',
|
||||
'rgba(255, 193, 7, 1)'
|
||||
],
|
||||
|
||||
components: {
|
||||
// Main components
|
||||
preview: true,
|
||||
opacity: true,
|
||||
hue: true,
|
||||
// Input / output Options
|
||||
interaction: {
|
||||
hex: true,
|
||||
rgba: true,
|
||||
hsla: true,
|
||||
hsva: true,
|
||||
cmyk: true,
|
||||
input: true,
|
||||
// clear: true,
|
||||
save: true,
|
||||
cancel: true
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
pickr
|
||||
.on('save', (color, instance) => {
|
||||
// console.log('Event: "save"', color.toHEXA().toString())
|
||||
// let data = getLocalData('_mixlab_utils_color')
|
||||
// data[this.id] = color.toHEXA().toString()
|
||||
// localStorage.setItem('_mixlab_utils_color', JSON.stringify(data))
|
||||
|
||||
try {
|
||||
let tc = this.widgets.filter(w => w.type == 'TCOLOR')[0]
|
||||
tc.value = color.toHEXA().toString()
|
||||
} catch (error) {}
|
||||
})
|
||||
.on('cancel', instance => {
|
||||
pickr && pickr.hide()
|
||||
})
|
||||
this.pickr = pickr
|
||||
const handleMouseWheel = () => {
|
||||
try {
|
||||
this.pickr && this.pickr.hide()
|
||||
} catch (error) {}
|
||||
}
|
||||
|
||||
document.addEventListener('wheel', handleMouseWheel)
|
||||
|
||||
const onRemoved = this.onRemoved
|
||||
this.onRemoved = () => {
|
||||
inputColor.remove()
|
||||
widget.div.remove()
|
||||
|
||||
try {
|
||||
this.pickr.destroyAndRemove()
|
||||
this.pickr = null
|
||||
document.removeEventListener('wheel', handleMouseWheel)
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
}
|
||||
|
||||
return onRemoved?.()
|
||||
}
|
||||
|
||||
this.serialize_widgets = true //需要保存参数
|
||||
}
|
||||
}
|
||||
},
|
||||
async loadedGraphNode (node, app) {
|
||||
// Fires every time a node is constructed
|
||||
// You can modify widgets/add handlers/etc here
|
||||
|
||||
if (node.type === 'Color') {
|
||||
try {
|
||||
let TCOLOR = node.widgets.filter(w => w.type == 'TCOLOR')[0]
|
||||
|
||||
setTimeout(() => node.pickr.setColor(TCOLOR.value || '#000000'), 1000)
|
||||
} catch (error) {}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
app.registerExtension({
|
||||
name: 'Mixlab.utils.TextToNumber',
|
||||
async beforeRegisterNodeDef (nodeType, nodeData, app) {
|
||||
if (nodeType.comfyClass == 'TextToNumber') {
|
||||
const onExecuted = nodeType.prototype.onExecuted
|
||||
nodeType.prototype.onExecuted = function (message) {
|
||||
onExecuted?.apply(this, arguments)
|
||||
const random_number = this.widgets.filter(
|
||||
w => w.name === 'random_number'
|
||||
)[0]
|
||||
if (random_number.value === 'enable') {
|
||||
const n = this.widgets.filter(w => w.name === 'number')[0]
|
||||
n.value = message.num[0]
|
||||
}
|
||||
console.log('TextToNumber', random_number.value)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const min_max = node => {
|
||||
if(node.widgets){
|
||||
const min_value = node.widgets.filter(w => w.name === 'min_value')[0]
|
||||
const max_value = node.widgets.filter(w => w.name === 'max_value')[0]
|
||||
|
||||
const number = node.widgets.filter(w => w.name === 'number')[0]
|
||||
if (number) {
|
||||
number.options.min = min_value.value
|
||||
number.options.max = max_value.value
|
||||
|
||||
number.value = Math.min(number.options.max, number.value)
|
||||
number.value = Math.max(number.options.min, number.value)
|
||||
}
|
||||
|
||||
if (min_value)
|
||||
min_value.callback = e => {
|
||||
number.options.min = e
|
||||
number.value = e
|
||||
}
|
||||
if (max_value)
|
||||
max_value.callback = e => {
|
||||
number.options.max = e
|
||||
number.value = e
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
app.registerExtension({
|
||||
name: 'Mixlab.utils.FloatSlider',
|
||||
async beforeRegisterNodeDef (nodeType, nodeData, app) {
|
||||
|
||||
if (nodeType.comfyClass == 'FloatSlider') {
|
||||
const orig_nodeCreated = nodeType.prototype.onNodeCreated;
|
||||
nodeType.prototype.onNodeCreated = function () {
|
||||
orig_nodeCreated?.apply(this, arguments)
|
||||
min_max(this)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
},
|
||||
async loadedGraphNode (node, app) {
|
||||
if (node.type === 'FloatSlider') {
|
||||
min_max(node)
|
||||
}
|
||||
}
|
||||
})
|
||||
app.registerExtension({
|
||||
name: 'Mixlab.utils.IntNumber',
|
||||
async beforeRegisterNodeDef (nodeType, nodeData, app) {
|
||||
|
||||
if (nodeType.comfyClass == 'IntNumber') {
|
||||
const orig_nodeCreated = nodeType.prototype.onNodeCreated
|
||||
nodeType.prototype.onNodeCreated = function () {
|
||||
orig_nodeCreated?.apply(this, arguments)
|
||||
min_max(this)
|
||||
}
|
||||
}
|
||||
|
||||
},
|
||||
async loadedGraphNode (node, app) {
|
||||
if (node.type === 'IntNumber') {
|
||||
min_max(node)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
app.registerExtension({
|
||||
name: 'Mixlab.utils.TESTNODE_',
|
||||
async beforeRegisterNodeDef (nodeType, nodeData, app) {
|
||||
|
||||
if (nodeType.comfyClass == 'TESTNODE_') {
|
||||
|
||||
const onExecuted = nodeType.prototype.onExecuted;
|
||||
nodeType.prototype.onExecuted = function (message) {
|
||||
onExecuted?.apply(this, arguments);
|
||||
console.log('##',message)
|
||||
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
|
||||
},
|
||||
})
|
||||
@@ -0,0 +1,515 @@
|
||||
import { app } from '../../../scripts/app.js'
|
||||
import { api } from '../../../scripts/api.js'
|
||||
import { ComfyWidgets } from '../../../scripts/widgets.js'
|
||||
|
||||
import { $el } from '../../../scripts/ui.js'
|
||||
|
||||
// The code is based on ComfyUI-VideoHelperSuite modification.
|
||||
|
||||
|
||||
|
||||
function injectCSS (css) {
|
||||
// 检查页面中是否已经存在具有相同内容的style标签
|
||||
const existingStyle = document.querySelector('style')
|
||||
if (existingStyle && existingStyle.textContent === css) {
|
||||
return // 如果已经存在相同的样式,则不进行注入
|
||||
}
|
||||
|
||||
// 创建一个新的style标签,并将CSS内容注入其中
|
||||
const style = document.createElement('style')
|
||||
style.textContent = css
|
||||
|
||||
// 将style标签插入到页面的head元素中
|
||||
const head = document.querySelector('head')
|
||||
head.appendChild(style)
|
||||
}
|
||||
|
||||
injectCSS(`
|
||||
.hidden{
|
||||
display:none !important
|
||||
}`)
|
||||
|
||||
function get_position_style (ctx, widget_width, y, node_height) {
|
||||
const MARGIN = 4 // the margin around the html element
|
||||
|
||||
/* Create a transform that deals with all the scrolling and zooming */
|
||||
const elRect = ctx.canvas.getBoundingClientRect()
|
||||
const transform = new DOMMatrix()
|
||||
.scaleSelf(
|
||||
elRect.width / ctx.canvas.width,
|
||||
elRect.height / ctx.canvas.height
|
||||
)
|
||||
.multiplySelf(ctx.getTransform())
|
||||
.translateSelf(MARGIN, MARGIN + y)
|
||||
|
||||
return {
|
||||
transformOrigin: '0 0',
|
||||
transform: transform,
|
||||
left: `0`,
|
||||
top: `0`,
|
||||
cursor: 'pointer',
|
||||
position: 'absolute',
|
||||
maxWidth: `${widget_width - MARGIN * 2}px`,
|
||||
// maxHeight: `${node_height - MARGIN * 2}px`, // we're assuming we have the whole height of the node
|
||||
width: `${widget_width - MARGIN * 2}px`,
|
||||
// height: `${node_height * 0.3 - MARGIN * 2}px`,
|
||||
// background: '#EEEEEE',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
// alignItems: 'center',
|
||||
justifyContent: 'space-around'
|
||||
}
|
||||
}
|
||||
|
||||
function videoUpload (node, inputName, inputData, app) {
|
||||
const imageWidget = node.widgets.find(w => w.name === 'video')
|
||||
let uploadWidget
|
||||
|
||||
const widget = {
|
||||
type: 'div',
|
||||
name: 'upload-preview',
|
||||
draw (ctx, node, widget_width, y, widget_height) {
|
||||
Object.assign(
|
||||
this.div.style,
|
||||
get_position_style(ctx, widget_width, 220, node.size[1]),
|
||||
{
|
||||
outline: '1px solid'
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
widget.div = $el('div', {})
|
||||
widget.div.style.width = `120px`
|
||||
document.body.appendChild(widget.div)
|
||||
node.addCustomWidget(widget)
|
||||
// console.log('#imageWidget', imageWidget)
|
||||
const displayDiv = document.createElement('video')
|
||||
displayDiv.controls = true
|
||||
// displayDiv.style=`width:200px;height:200px`
|
||||
imageWidget.callback = () => {
|
||||
displayDiv.src = `/view?filename=${
|
||||
imageWidget.value
|
||||
}&type=input&subfolder=${''}&rand=${Math.random()}`
|
||||
|
||||
// displayDiv.onloadedmetadata = function () {
|
||||
// var frameCount = displayDiv.duration * displayDiv.webkitDecodedFrameCount
|
||||
// console.log('视频帧数:' + frameCount)
|
||||
// node.widgets.filter(w => w.name == 'video_segment_frames')[0].value =
|
||||
// frameCount
|
||||
// }
|
||||
}
|
||||
|
||||
if (imageWidget.value) {
|
||||
// console.log(imageWidget.value)
|
||||
displayDiv.src = `/view?filename=${
|
||||
imageWidget.value
|
||||
}&type=input&subfolder=${''}&rand=${Math.random()}`
|
||||
}
|
||||
|
||||
widget.div.appendChild(displayDiv)
|
||||
|
||||
const onRemoved = node.onRemoved
|
||||
node.onRemoved = () => {
|
||||
widget.div.remove()
|
||||
return onRemoved?.()
|
||||
}
|
||||
|
||||
var default_value = imageWidget.value
|
||||
Object.defineProperty(imageWidget, 'value', {
|
||||
set: function (value) {
|
||||
this._real_value = value
|
||||
},
|
||||
|
||||
get: function () {
|
||||
let value = ''
|
||||
if (this._real_value) {
|
||||
value = this._real_value
|
||||
} else {
|
||||
return default_value
|
||||
}
|
||||
|
||||
if (value.filename) {
|
||||
let real_value = value
|
||||
value = ''
|
||||
if (real_value.subfolder) {
|
||||
value = real_value.subfolder + '/'
|
||||
}
|
||||
|
||||
value += real_value.filename
|
||||
|
||||
if (real_value.type && real_value.type !== 'input')
|
||||
value += ` [${real_value.type}]`
|
||||
}
|
||||
return value
|
||||
}
|
||||
})
|
||||
async function uploadFile (file, updateNode, pasted = false) {
|
||||
try {
|
||||
// Wrap file in formdata so it includes filename
|
||||
const body = new FormData()
|
||||
body.append('image', file)
|
||||
if (pasted) body.append('subfolder', 'pasted')
|
||||
const resp = await api.fetchApi('/upload/image', {
|
||||
method: 'POST',
|
||||
body
|
||||
})
|
||||
|
||||
if (resp.status === 200) {
|
||||
const data = await resp.json()
|
||||
// Add the file to the dropdown list and update the widget value
|
||||
let path = data.name
|
||||
if (data.subfolder) path = data.subfolder + '/' + path
|
||||
|
||||
if (!imageWidget.options.values.includes(path)) {
|
||||
imageWidget.options.values.push(path)
|
||||
}
|
||||
|
||||
if (updateNode) {
|
||||
imageWidget.value = path
|
||||
}
|
||||
|
||||
return `/view?filename=${path}&type=input&subfolder=${
|
||||
pasted ? 'pasted' : ''
|
||||
}&rand=${Math.random()}`
|
||||
} else {
|
||||
alert(resp.status + ' - ' + resp.statusText)
|
||||
}
|
||||
} catch (error) {
|
||||
alert(error)
|
||||
}
|
||||
}
|
||||
|
||||
const fileInput = document.createElement('input')
|
||||
Object.assign(fileInput, {
|
||||
type: 'file',
|
||||
accept: 'video/*,.mkv,video/webm,video/mp4,video/x-matroska,image/gif',
|
||||
style: 'display: none',
|
||||
onchange: async () => {
|
||||
if (fileInput.files.length) {
|
||||
let file = fileInput.files[0]
|
||||
|
||||
const url = await uploadFile(file, true)
|
||||
|
||||
// console.log('fileInput', file)
|
||||
var reader = new FileReader()
|
||||
reader.onload = function () {
|
||||
displayDiv.src = url
|
||||
displayDiv.onloadedmetadata = function () {
|
||||
// var frameCount =
|
||||
// displayDiv.duration * displayDiv.webkitDecodedFrameCount
|
||||
// console.log('视频帧数:' + frameCount)
|
||||
// node.widgets.filter(
|
||||
// w => w.name == 'video_segment_frames'
|
||||
// )[0].value = frameCount
|
||||
}
|
||||
}
|
||||
reader.readAsDataURL(file)
|
||||
}
|
||||
}
|
||||
})
|
||||
document.body.append(fileInput)
|
||||
|
||||
// Create the button widget for selecting the files
|
||||
uploadWidget = node.addWidget('button', 'upload file', 'video', () => {
|
||||
fileInput.click()
|
||||
})
|
||||
uploadWidget.serialize = false
|
||||
return { widget: uploadWidget }
|
||||
}
|
||||
ComfyWidgets.VIDEOUPLOAD_ = videoUpload
|
||||
|
||||
app.registerExtension({
|
||||
name: 'Mixlab.Video.LoadVideoAndSegment_',
|
||||
async beforeRegisterNodeDef (nodeType, nodeData, app) {
|
||||
if (nodeData?.name == 'LoadVideoAndSegment_') {
|
||||
nodeData.input.required.upload = ['VIDEOUPLOAD_']
|
||||
}
|
||||
},
|
||||
async loadedGraphNode (node, app) {
|
||||
if (node.type === 'LoadVideoAndSegment_') {
|
||||
const imageWidget = node.widgets.find(w => w.name === 'video')
|
||||
const uploadPreview = node.widgets.find(w => w.name === 'upload-preview')
|
||||
if (imageWidget.value) {
|
||||
// console.log(imageWidget.value)
|
||||
uploadPreview.div.querySelector('video').src = `/view?filename=${
|
||||
imageWidget.value
|
||||
}&type=input&subfolder=${''}&rand=${Math.random()}`
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
function offsetDOMWidget(
|
||||
widget,
|
||||
ctx,
|
||||
node,
|
||||
widgetWidth,
|
||||
widgetY,
|
||||
height
|
||||
) {
|
||||
const margin = 10
|
||||
const elRect = ctx.canvas.getBoundingClientRect()
|
||||
const transform = new DOMMatrix()
|
||||
.scaleSelf(
|
||||
elRect.width / ctx.canvas.width,
|
||||
elRect.height / ctx.canvas.height
|
||||
)
|
||||
.multiplySelf(ctx.getTransform())
|
||||
.translateSelf(0, widgetY + margin)
|
||||
|
||||
const scale = new DOMMatrix().scaleSelf(transform.a, transform.d)
|
||||
Object.assign(widget.inputEl.style, {
|
||||
transformOrigin: '0 0',
|
||||
transform: scale,
|
||||
left: `${transform.e}px`,
|
||||
top: `${transform.d + transform.f}px`,
|
||||
width: `${widgetWidth}px`,
|
||||
height: `${(height || widget.parent?.inputHeight || 32) - margin}px`,
|
||||
position: 'absolute',
|
||||
background: !node.color ? '' : node.color,
|
||||
color: !node.color ? '' : 'white',
|
||||
zIndex: 5, //app.graph._nodes.indexOf(node),
|
||||
})
|
||||
}
|
||||
|
||||
export const hasWidgets = (node) => {
|
||||
if (!node.widgets || !node.widgets?.[Symbol.iterator]) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
export const cleanupNode = (node) => {
|
||||
if (!hasWidgets(node)) {
|
||||
return
|
||||
}
|
||||
|
||||
for (const w of node.widgets) {
|
||||
if (w.canvas) {
|
||||
w.canvas.remove()
|
||||
}
|
||||
if (w.inputEl) {
|
||||
w.inputEl.remove()
|
||||
}
|
||||
// calls the widget remove callback
|
||||
w.onRemoved?.()
|
||||
}
|
||||
}
|
||||
|
||||
const CreatePreviewElement = (name, val, format) => {
|
||||
const [type] = format.split('/')
|
||||
const w = {
|
||||
name,
|
||||
type,
|
||||
value: val,
|
||||
draw: function (ctx, node, widgetWidth, widgetY, height) {
|
||||
const [cw, ch] = this.computeSize(widgetWidth)
|
||||
offsetDOMWidget(this, ctx, node, widgetWidth, widgetY, ch)
|
||||
},
|
||||
computeSize: function (_) {
|
||||
const ratio = this.inputRatio || 1
|
||||
const width = Math.max(220, this.parent.size[0])
|
||||
return [width, (width / ratio + 10)]
|
||||
},
|
||||
onRemoved: function () {
|
||||
if (this.inputEl) {
|
||||
this.inputEl.remove()
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
w.inputEl = document.createElement(type === 'video' ? 'video' : 'img')
|
||||
w.inputEl.src = w.value
|
||||
if (type === 'video') {
|
||||
w.inputEl.setAttribute('type', 'video/webm');
|
||||
w.inputEl.autoplay = true
|
||||
w.inputEl.loop = true
|
||||
w.inputEl.controls = false;
|
||||
}
|
||||
w.inputEl.onload = function () {
|
||||
w.inputRatio = w.inputEl.naturalWidth / w.inputEl.naturalHeight
|
||||
}
|
||||
document.body.appendChild(w.inputEl)
|
||||
return w
|
||||
}
|
||||
|
||||
|
||||
app.registerExtension({
|
||||
name: 'Mixlab.Video.ImageListReplace',
|
||||
async beforeRegisterNodeDef (nodeType, nodeData, app) {
|
||||
if (nodeData?.name == 'ImageListReplace_') {
|
||||
const orig_nodeCreated = nodeType.prototype.onNodeCreated
|
||||
nodeType.prototype.onNodeCreated = function () {
|
||||
orig_nodeCreated?.apply(this, arguments)
|
||||
const widget = {
|
||||
type: 'div',
|
||||
name: 'preview',
|
||||
draw (ctx, node, widget_width, y, widget_height) {
|
||||
Object.assign(
|
||||
this.div.style,
|
||||
get_position_style(ctx, widget_width, 188, node.size[1]),
|
||||
{
|
||||
outline: '1px solid',
|
||||
display: 'flex',
|
||||
flexWrap: 'wrap',
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'flex-start'
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
widget.div = $el('div', {})
|
||||
widget.div.style.width = `120px`
|
||||
widget.div.className = 'hidden'
|
||||
document.body.appendChild(widget.div)
|
||||
this.addCustomWidget(widget)
|
||||
// console.log('#ImageListReplace', widget)
|
||||
|
||||
const onRemoved = this.onRemoved
|
||||
this.onRemoved = () => {
|
||||
widget.div.remove()
|
||||
return onRemoved?.()
|
||||
}
|
||||
}
|
||||
|
||||
const onExecuted = nodeType.prototype.onExecuted
|
||||
nodeType.prototype.onExecuted = function (message) {
|
||||
onExecuted?.apply(this, arguments)
|
||||
|
||||
// let _image_replace = message._image_replace[0]
|
||||
// _image_replace = `/view?filename=${_image_replace.filename}&type=${
|
||||
// _image_replace.type
|
||||
// }&subfolder=${_image_replace.subfolder}&rand=${Math.random()}`
|
||||
|
||||
let preview = this.widgets.filter(w => w.name == 'preview')[0]
|
||||
|
||||
if (message._images.length > 0) {
|
||||
preview.div.className = ''
|
||||
// console.log('#ImageListReplace', preview.div)
|
||||
}
|
||||
|
||||
preview.div.innerHTML = ''
|
||||
for (const img_ of message._images) {
|
||||
let img = new Image()
|
||||
img.style = `width: 100px;
|
||||
margin: 4px;`
|
||||
img.src = `/view?filename=${img_.filename}&type=${
|
||||
img_.type
|
||||
}&subfolder=${img_.subfolder}&rand=${Math.random()}`
|
||||
preview.div.appendChild(img)
|
||||
}
|
||||
|
||||
let start_index = this.widgets.filter(w => w.name == 'start_index')[0]
|
||||
let end_index = this.widgets.filter(w => w.name == 'end_index')[0]
|
||||
let invert = this.widgets.filter(w => w.name == 'invert')[0]
|
||||
let _sc = start_index.callback.bind(start_index)
|
||||
let _ec = end_index.callback.bind(end_index)
|
||||
|
||||
const selectImages = () => {
|
||||
// console.log(v)
|
||||
let s = start_index.value,
|
||||
e = end_index.value
|
||||
let imgs = preview.div.querySelectorAll('img')
|
||||
for (let index = 0; index < imgs.length; index++) {
|
||||
if (invert.value) {
|
||||
imgs[index].style.outline =
|
||||
index >= s && index <= e ? 'none' : '4px solid #cbd3fe'
|
||||
} else {
|
||||
imgs[index].style.outline =
|
||||
index >= s && index <= e ? '4px solid #cbd3fe' : 'none'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
selectImages()
|
||||
|
||||
start_index.callback = v => {
|
||||
let s = v,
|
||||
e = end_index.value
|
||||
let imgs = preview.div.querySelectorAll('img')
|
||||
for (let index = 0; index < imgs.length; index++) {
|
||||
if (invert.value) {
|
||||
imgs[index].style.outline =
|
||||
index >= s && index <= e ? 'none' : '4px solid #cbd3fe'
|
||||
} else {
|
||||
imgs[index].style.outline =
|
||||
index >= s && index <= e ? '4px solid #cbd3fe' : 'none'
|
||||
}
|
||||
}
|
||||
|
||||
_sc(v)
|
||||
}
|
||||
|
||||
end_index.callback = v => {
|
||||
let s = start_index.value,
|
||||
e = v
|
||||
let imgs = preview.div.querySelectorAll('img')
|
||||
for (let index = 0; index < imgs.length; index++) {
|
||||
if (invert.value) {
|
||||
imgs[index].style.outline =
|
||||
index >= s && index <= e ? 'none' : '4px solid #cbd3fe'
|
||||
} else {
|
||||
imgs[index].style.outline =
|
||||
index >= s && index <= e ? '4px solid #cbd3fe' : 'none'
|
||||
}
|
||||
}
|
||||
|
||||
_ec(v)
|
||||
}
|
||||
|
||||
invert.callback = v => {
|
||||
selectImages()
|
||||
}
|
||||
|
||||
try {
|
||||
} catch (error) {}
|
||||
}
|
||||
}
|
||||
|
||||
if (nodeData?.name == 'VideoCombine_Adv') {
|
||||
const onExecuted = nodeType.prototype.onExecuted
|
||||
nodeType.prototype.onExecuted = function (message) {
|
||||
const prefix = 'vhs_gif_preview_'
|
||||
const r = onExecuted ? onExecuted.apply(this, message) : undefined
|
||||
|
||||
if (this.widgets) {
|
||||
const pos = this.widgets.findIndex(w => w.name === `${prefix}_0`)
|
||||
if (pos !== -1) {
|
||||
for (let i = pos; i < this.widgets.length; i++) {
|
||||
this.widgets[i].onRemoved?.()
|
||||
}
|
||||
this.widgets.length = pos
|
||||
}
|
||||
if (message?.gifs) {
|
||||
message.gifs.forEach((params, i) => {
|
||||
const previewUrl = api.apiURL(
|
||||
'/view?' + new URLSearchParams(params).toString()
|
||||
)
|
||||
const w = this.addCustomWidget(
|
||||
CreatePreviewElement(
|
||||
`${prefix}_${i}`,
|
||||
previewUrl,
|
||||
params.format || 'image/gif'
|
||||
)
|
||||
)
|
||||
w.parent = this
|
||||
})
|
||||
}
|
||||
const onRemoved = this.onRemoved
|
||||
this.onRemoved = () => {
|
||||
cleanupNode(this)
|
||||
return onRemoved?.()
|
||||
}
|
||||
}
|
||||
this.setSize([
|
||||
this.size[0],
|
||||
this.computeSize([this.size[0], this.size[1]])[1]
|
||||
])
|
||||
return r
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -60,30 +60,7 @@ app.registerExtension({
|
||||
type: inputData[0], // the type, CHEESE
|
||||
name: inputName, // the name, slice
|
||||
size: [128, 24], // a default size
|
||||
draw (ctx, node, width, y) {
|
||||
// // 绘制文件图标的函数
|
||||
// function drawFileIcon () {
|
||||
// // 清空画布
|
||||
// // ctx.clearRect(0, 0, canvas.width, canvas.height)
|
||||
|
||||
// // 绘制文件外框
|
||||
// ctx.fillStyle = '#000'
|
||||
// ctx.fillRect(5, 5, 40, 40)
|
||||
|
||||
// // 绘制文件夹图标
|
||||
// ctx.fillStyle = '#f00'
|
||||
// ctx.fillRect(10, 15, 30, 20)
|
||||
|
||||
// // 绘制监听符号
|
||||
// ctx.beginPath()
|
||||
// ctx.arc(30, 35, 5, 0, 2 * Math.PI)
|
||||
// ctx.fillStyle = '#00f'
|
||||
// ctx.fill()
|
||||
// }
|
||||
|
||||
// // 调用绘制函数
|
||||
// drawFileIcon()
|
||||
},
|
||||
draw (ctx, node, width, y) {},
|
||||
computeSize (...args) {
|
||||
return [128, 24] // a method to compute the current size of the widget
|
||||
},
|
||||
@@ -124,11 +101,19 @@ app.registerExtension({
|
||||
nodeType.prototype.onNodeCreated = function () {
|
||||
orig_nodeCreated?.apply(this, arguments)
|
||||
|
||||
console.log('watch widtget', this.widgets)
|
||||
// 虚拟的widget,用于更新节点,让其每次都运行
|
||||
const widget = {
|
||||
type: 'div',
|
||||
name: 'seed',
|
||||
draw (ctx, node, widget_width, y, widget_height) {}
|
||||
}
|
||||
|
||||
this.addCustomWidget(widget)
|
||||
|
||||
const watcher = this.widgets.filter(w => w.name == 'watcher')[0]
|
||||
|
||||
watcher.callback = () => {
|
||||
console.log('watcher', watcher.value)
|
||||
if (watcher.value === 'enable') {
|
||||
if (window._mixlab_watcher_t)
|
||||
clearInterval(window._mixlab_watcher_t)
|
||||
@@ -140,7 +125,7 @@ app.registerExtension({
|
||||
window._mixlab_file_path_watcher = json.event_type
|
||||
// widget.card.innerText = window._mixlab_file_path_watcher || ''
|
||||
//运行
|
||||
document.querySelector('#queue-button').click()
|
||||
// document.querySelector('#queue-button').click()
|
||||
}
|
||||
})
|
||||
}, 1000)
|
||||
@@ -162,15 +147,59 @@ app.registerExtension({
|
||||
window._mixlab_file_path_watcher = json.event_type
|
||||
})
|
||||
|
||||
/*
|
||||
Add the widget, make sure we clean up nicely, and we do not want to be serialized!
|
||||
*/
|
||||
// this.addCustomWidget(widget)
|
||||
this.onRemoved = function () {
|
||||
// widget.card.remove()
|
||||
}
|
||||
this.serialize_widgets = false
|
||||
this.serialize_widgets = true
|
||||
}
|
||||
|
||||
const onExecuted = nodeType.prototype.onExecuted
|
||||
nodeType.prototype.onExecuted = function (message) {
|
||||
onExecuted?.apply(this, arguments)
|
||||
console.log(message)
|
||||
try {
|
||||
let seed = this.widgets.filter(w => w.name === 'seed')[0]
|
||||
if (seed) {
|
||||
if (!seed.value) seed.value = 0
|
||||
seed.value += 1
|
||||
}
|
||||
} catch (error) {}
|
||||
}
|
||||
}
|
||||
},
|
||||
async loadedGraphNode (node, app) {
|
||||
if (node.type === 'LoadImagesFromPath') {
|
||||
const watcher = node.widgets.filter(w => w.name == 'watcher')[0]
|
||||
if (watcher) {
|
||||
if (watcher.value === 'enable') {
|
||||
if (window._mixlab_watcher_t) clearInterval(window._mixlab_watcher_t)
|
||||
window._mixlab_watcher_t = setInterval(() => {
|
||||
// 上次路径填充
|
||||
getConfig().then(json => {
|
||||
console.log(json.event_type)
|
||||
if (json.event_type != window._mixlab_file_path_watcher) {
|
||||
window._mixlab_file_path_watcher = json.event_type
|
||||
// widget.card.innerText = window._mixlab_file_path_watcher || ''
|
||||
//运行
|
||||
document.querySelector('#queue-button').click()
|
||||
}
|
||||
})
|
||||
}, 1000)
|
||||
} else {
|
||||
if (window._mixlab_watcher_t) {
|
||||
clearInterval(window._mixlab_watcher_t)
|
||||
}
|
||||
window._mixlab_watcher_t = null
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
let seed = node.widgets.filter(w => w.name === 'seed')[0]
|
||||
if (seed) {
|
||||
if (!seed.value) seed.value = 0
|
||||
seed.value += 1
|
||||
}
|
||||
} catch (error) {}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
/*! PhotoSwipe main CSS by Dmytro Semenov | photoswipe.com */.pswp{--pswp-bg:#000;--pswp-placeholder-bg:#222;--pswp-root-z-index:100000;--pswp-preloader-color:rgba(79, 79, 79, 0.4);--pswp-preloader-color-secondary:rgba(255, 255, 255, 0.9);--pswp-icon-color:#fff;--pswp-icon-color-secondary:#4f4f4f;--pswp-icon-stroke-color:#4f4f4f;--pswp-icon-stroke-width:2px;--pswp-error-text-color:var(--pswp-icon-color)}.pswp{position:fixed;top:0;left:0;width:100%;height:100%;z-index:var(--pswp-root-z-index);display:none;touch-action:none;outline:0;opacity:.003;contain:layout style size;-webkit-tap-highlight-color:transparent}.pswp:focus{outline:0}.pswp *{box-sizing:border-box}.pswp img{max-width:none}.pswp--open{display:block}.pswp,.pswp__bg{transform:translateZ(0);will-change:opacity}.pswp__bg{opacity:.005;background:var(--pswp-bg)}.pswp,.pswp__scroll-wrap{overflow:hidden}.pswp__bg,.pswp__container,.pswp__content,.pswp__img,.pswp__item,.pswp__scroll-wrap,.pswp__zoom-wrap{position:absolute;top:0;left:0;width:100%;height:100%}.pswp__img,.pswp__zoom-wrap{width:auto;height:auto}.pswp--click-to-zoom.pswp--zoom-allowed .pswp__img{cursor:-webkit-zoom-in;cursor:-moz-zoom-in;cursor:zoom-in}.pswp--click-to-zoom.pswp--zoomed-in .pswp__img{cursor:move;cursor:-webkit-grab;cursor:-moz-grab;cursor:grab}.pswp--click-to-zoom.pswp--zoomed-in .pswp__img:active{cursor:-webkit-grabbing;cursor:-moz-grabbing;cursor:grabbing}.pswp--no-mouse-drag.pswp--zoomed-in .pswp__img,.pswp--no-mouse-drag.pswp--zoomed-in .pswp__img:active,.pswp__img{cursor:-webkit-zoom-out;cursor:-moz-zoom-out;cursor:zoom-out}.pswp__button,.pswp__container,.pswp__counter,.pswp__img{-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.pswp__item{z-index:1;overflow:hidden}.pswp__hidden{display:none!important}.pswp__content{pointer-events:none}.pswp__content>*{pointer-events:auto}.pswp__error-msg-container{display:grid}.pswp__error-msg{margin:auto;font-size:1em;line-height:1;color:var(--pswp-error-text-color)}.pswp .pswp__hide-on-close{opacity:.005;will-change:opacity;transition:opacity var(--pswp-transition-duration) cubic-bezier(.4,0,.22,1);z-index:10;pointer-events:none}.pswp--ui-visible .pswp__hide-on-close{opacity:1;pointer-events:auto}.pswp__button{position:relative;display:block;width:50px;height:60px;padding:0;margin:0;overflow:hidden;cursor:pointer;background:0 0;border:0;box-shadow:none;opacity:.85;-webkit-appearance:none;-webkit-touch-callout:none}.pswp__button:active,.pswp__button:focus,.pswp__button:hover{transition:none;padding:0;background:0 0;border:0;box-shadow:none;opacity:1}.pswp__button:disabled{opacity:.3;cursor:auto}.pswp__icn{fill:var(--pswp-icon-color);color:var(--pswp-icon-color-secondary)}.pswp__icn{position:absolute;top:14px;left:9px;width:32px;height:32px;overflow:hidden;pointer-events:none}.pswp__icn-shadow{stroke:var(--pswp-icon-stroke-color);stroke-width:var(--pswp-icon-stroke-width);fill:none}.pswp__icn:focus{outline:0}.pswp__img--with-bg,div.pswp__img--placeholder{background:var(--pswp-placeholder-bg)}.pswp__top-bar{position:absolute;left:0;top:0;width:100%;height:60px;display:flex;flex-direction:row;justify-content:flex-end;z-index:10;pointer-events:none!important}.pswp__top-bar>*{pointer-events:auto;will-change:opacity}.pswp__button--close{margin-right:6px}.pswp__button--arrow{position:absolute;top:0;width:75px;height:100px;top:50%;margin-top:-50px}.pswp__button--arrow:disabled{display:none;cursor:default}.pswp__button--arrow .pswp__icn{top:50%;margin-top:-30px;width:60px;height:60px;background:0 0;border-radius:0}.pswp--one-slide .pswp__button--arrow{display:none}.pswp--touch .pswp__button--arrow{visibility:hidden}.pswp--has_mouse .pswp__button--arrow{visibility:visible}.pswp__button--arrow--prev{right:auto;left:0}.pswp__button--arrow--next{right:0}.pswp__button--arrow--next .pswp__icn{left:auto;right:14px;transform:scale(-1,1)}.pswp__button--zoom{display:none}.pswp--zoom-allowed .pswp__button--zoom{display:block}.pswp--zoomed-in .pswp__zoom-icn-bar-v{display:none}.pswp__preloader{position:relative;overflow:hidden;width:50px;height:60px;margin-right:auto}.pswp__preloader .pswp__icn{opacity:0;transition:opacity .2s linear;animation:pswp-clockwise .6s linear infinite}.pswp__preloader--active .pswp__icn{opacity:.85}@keyframes pswp-clockwise{0%{transform:rotate(0)}100%{transform:rotate(360deg)}}.pswp__counter{height:30px;margin-top:15px;margin-inline-start:20px;font-size:14px;line-height:30px;color:var(--pswp-icon-color);text-shadow:1px 1px 3px var(--pswp-icon-color-secondary);opacity:.85}.pswp--one-slide .pswp__counter{display:none}
|
||||
@@ -1,3 +0,0 @@
|
||||
::-webkit-scrollbar {
|
||||
width: 2px;
|
||||
}
|
||||
@@ -0,0 +1,736 @@
|
||||
{
|
||||
"last_node_id": 29,
|
||||
"last_link_id": 32,
|
||||
"nodes": [
|
||||
{
|
||||
"id": 7,
|
||||
"type": "CLIPTextEncode",
|
||||
"pos": [
|
||||
108,
|
||||
316
|
||||
],
|
||||
"size": {
|
||||
"0": 425.27801513671875,
|
||||
"1": 180.6060791015625
|
||||
},
|
||||
"flags": {},
|
||||
"order": 8,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "clip",
|
||||
"type": "CLIP",
|
||||
"link": 16
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "CONDITIONING",
|
||||
"type": "CONDITIONING",
|
||||
"links": [
|
||||
6
|
||||
],
|
||||
"slot_index": 0
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "CLIPTextEncode"
|
||||
},
|
||||
"widgets_values": [
|
||||
"text, watermark"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 18,
|
||||
"type": "ControlNetApply",
|
||||
"pos": [
|
||||
485,
|
||||
792
|
||||
],
|
||||
"size": {
|
||||
"0": 317.4000244140625,
|
||||
"1": 98
|
||||
},
|
||||
"flags": {},
|
||||
"order": 10,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "conditioning",
|
||||
"type": "CONDITIONING",
|
||||
"link": 22,
|
||||
"slot_index": 0
|
||||
},
|
||||
{
|
||||
"name": "control_net",
|
||||
"type": "CONTROL_NET",
|
||||
"link": 18,
|
||||
"slot_index": 1
|
||||
},
|
||||
{
|
||||
"name": "image",
|
||||
"type": "IMAGE",
|
||||
"link": 26
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "CONDITIONING",
|
||||
"type": "CONDITIONING",
|
||||
"links": [
|
||||
21
|
||||
],
|
||||
"shape": 3,
|
||||
"slot_index": 0
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "ControlNetApply"
|
||||
},
|
||||
"widgets_values": [
|
||||
1
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 6,
|
||||
"type": "CLIPTextEncode",
|
||||
"pos": [
|
||||
117,
|
||||
95
|
||||
],
|
||||
"size": {
|
||||
"0": 422.84503173828125,
|
||||
"1": 164.31304931640625
|
||||
},
|
||||
"flags": {},
|
||||
"order": 7,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "clip",
|
||||
"type": "CLIP",
|
||||
"link": 15
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "CONDITIONING",
|
||||
"type": "CONDITIONING",
|
||||
"links": [
|
||||
22
|
||||
],
|
||||
"slot_index": 0
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "CLIPTextEncode"
|
||||
},
|
||||
"widgets_values": [
|
||||
"a future city,buiding,future,magic,under water"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 15,
|
||||
"type": "LoraLoader",
|
||||
"pos": [
|
||||
116,
|
||||
-113
|
||||
],
|
||||
"size": {
|
||||
"0": 315,
|
||||
"1": 126
|
||||
},
|
||||
"flags": {},
|
||||
"order": 3,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "model",
|
||||
"type": "MODEL",
|
||||
"link": 13
|
||||
},
|
||||
{
|
||||
"name": "clip",
|
||||
"type": "CLIP",
|
||||
"link": 14
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "MODEL",
|
||||
"type": "MODEL",
|
||||
"links": [
|
||||
12,
|
||||
23
|
||||
],
|
||||
"shape": 3,
|
||||
"slot_index": 0
|
||||
},
|
||||
{
|
||||
"name": "CLIP",
|
||||
"type": "CLIP",
|
||||
"links": [
|
||||
15,
|
||||
16
|
||||
],
|
||||
"shape": 3,
|
||||
"slot_index": 1
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "LoraLoader"
|
||||
},
|
||||
"widgets_values": [
|
||||
"lcm-lora-sdv1-5.safetensors",
|
||||
1,
|
||||
1
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 4,
|
||||
"type": "CheckpointLoaderSimple",
|
||||
"pos": [
|
||||
-380,
|
||||
185
|
||||
],
|
||||
"size": {
|
||||
"0": 315,
|
||||
"1": 98
|
||||
},
|
||||
"flags": {},
|
||||
"order": 0,
|
||||
"mode": 0,
|
||||
"outputs": [
|
||||
{
|
||||
"name": "MODEL",
|
||||
"type": "MODEL",
|
||||
"links": [
|
||||
13
|
||||
],
|
||||
"slot_index": 0
|
||||
},
|
||||
{
|
||||
"name": "CLIP",
|
||||
"type": "CLIP",
|
||||
"links": [
|
||||
14
|
||||
],
|
||||
"slot_index": 1
|
||||
},
|
||||
{
|
||||
"name": "VAE",
|
||||
"type": "VAE",
|
||||
"links": [
|
||||
8
|
||||
],
|
||||
"slot_index": 2
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "CheckpointLoaderSimple"
|
||||
},
|
||||
"widgets_values": [
|
||||
"deliberate_v2.safetensors"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 19,
|
||||
"type": "DiffControlNetLoader",
|
||||
"pos": [
|
||||
40,
|
||||
792
|
||||
],
|
||||
"size": {
|
||||
"0": 367.8165283203125,
|
||||
"1": 58.083831787109375
|
||||
},
|
||||
"flags": {},
|
||||
"order": 6,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "model",
|
||||
"type": "MODEL",
|
||||
"link": 23,
|
||||
"slot_index": 0
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "CONTROL_NET",
|
||||
"type": "CONTROL_NET",
|
||||
"links": [
|
||||
18
|
||||
],
|
||||
"shape": 3
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "DiffControlNetLoader"
|
||||
},
|
||||
"widgets_values": [
|
||||
"control_v11f1p_sd15_depth.pth"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 5,
|
||||
"type": "EmptyLatentImage",
|
||||
"pos": [
|
||||
65,
|
||||
573
|
||||
],
|
||||
"size": {
|
||||
"0": 315,
|
||||
"1": 106
|
||||
},
|
||||
"flags": {},
|
||||
"order": 1,
|
||||
"mode": 0,
|
||||
"outputs": [
|
||||
{
|
||||
"name": "LATENT",
|
||||
"type": "LATENT",
|
||||
"links": [
|
||||
2
|
||||
],
|
||||
"slot_index": 0
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "EmptyLatentImage"
|
||||
},
|
||||
"widgets_values": [
|
||||
512,
|
||||
512,
|
||||
1
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 25,
|
||||
"type": "LeReS-DepthMapPreprocessor",
|
||||
"pos": [
|
||||
47,
|
||||
904
|
||||
],
|
||||
"size": {
|
||||
"0": 369.6000061035156,
|
||||
"1": 130
|
||||
},
|
||||
"flags": {},
|
||||
"order": 4,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "image",
|
||||
"type": "IMAGE",
|
||||
"link": 31
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "IMAGE",
|
||||
"type": "IMAGE",
|
||||
"links": [
|
||||
26,
|
||||
27
|
||||
],
|
||||
"shape": 3,
|
||||
"slot_index": 0
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "LeReS-DepthMapPreprocessor"
|
||||
},
|
||||
"widgets_values": [
|
||||
0.1,
|
||||
0,
|
||||
"disable",
|
||||
512
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"type": "KSampler",
|
||||
"pos": [
|
||||
1100,
|
||||
-97
|
||||
],
|
||||
"size": {
|
||||
"0": 315,
|
||||
"1": 262
|
||||
},
|
||||
"flags": {},
|
||||
"order": 11,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "model",
|
||||
"type": "MODEL",
|
||||
"link": 12,
|
||||
"slot_index": 0
|
||||
},
|
||||
{
|
||||
"name": "positive",
|
||||
"type": "CONDITIONING",
|
||||
"link": 21
|
||||
},
|
||||
{
|
||||
"name": "negative",
|
||||
"type": "CONDITIONING",
|
||||
"link": 6
|
||||
},
|
||||
{
|
||||
"name": "latent_image",
|
||||
"type": "LATENT",
|
||||
"link": 2
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "LATENT",
|
||||
"type": "LATENT",
|
||||
"links": [
|
||||
7
|
||||
],
|
||||
"slot_index": 0
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "KSampler"
|
||||
},
|
||||
"widgets_values": [
|
||||
170633013599955,
|
||||
"fixed",
|
||||
4,
|
||||
1.6,
|
||||
"lcm",
|
||||
"simple",
|
||||
1
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 8,
|
||||
"type": "VAEDecode",
|
||||
"pos": [
|
||||
1100,
|
||||
-241
|
||||
],
|
||||
"size": {
|
||||
"0": 210,
|
||||
"1": 46
|
||||
},
|
||||
"flags": {},
|
||||
"order": 12,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "samples",
|
||||
"type": "LATENT",
|
||||
"link": 7
|
||||
},
|
||||
{
|
||||
"name": "vae",
|
||||
"type": "VAE",
|
||||
"link": 8
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "IMAGE",
|
||||
"type": "IMAGE",
|
||||
"links": [
|
||||
28
|
||||
],
|
||||
"slot_index": 0
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "VAEDecode"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 20,
|
||||
"type": "PreviewImage",
|
||||
"pos": [
|
||||
492,
|
||||
955
|
||||
],
|
||||
"size": {
|
||||
"0": 526.9624633789062,
|
||||
"1": 367.3833923339844
|
||||
},
|
||||
"flags": {},
|
||||
"order": 9,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "images",
|
||||
"type": "IMAGE",
|
||||
"link": 27
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "PreviewImage"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 26,
|
||||
"type": "FloatingVideo",
|
||||
"pos": [
|
||||
1565,
|
||||
-181
|
||||
],
|
||||
"size": [
|
||||
315,
|
||||
58
|
||||
],
|
||||
"flags": {},
|
||||
"order": 13,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "images",
|
||||
"type": "IMAGE",
|
||||
"link": 28
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "FloatingVideo"
|
||||
},
|
||||
"widgets_values": [
|
||||
null
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 28,
|
||||
"type": "PreviewImage",
|
||||
"pos": [
|
||||
-241,
|
||||
953
|
||||
],
|
||||
"size": {
|
||||
"0": 210,
|
||||
"1": 246
|
||||
},
|
||||
"flags": {},
|
||||
"order": 5,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "images",
|
||||
"type": "IMAGE",
|
||||
"link": 32
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "PreviewImage"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 29,
|
||||
"type": "ScreenShare",
|
||||
"pos": [
|
||||
-639,
|
||||
366
|
||||
],
|
||||
"size": [
|
||||
315,
|
||||
644
|
||||
],
|
||||
"flags": {},
|
||||
"order": 2,
|
||||
"mode": 0,
|
||||
"outputs": [
|
||||
{
|
||||
"name": "IMAGE",
|
||||
"type": "IMAGE",
|
||||
"links": [
|
||||
31,
|
||||
32
|
||||
],
|
||||
"shape": 3,
|
||||
"slot_index": 0
|
||||
},
|
||||
{
|
||||
"name": "PROMPT",
|
||||
"type": "STRING",
|
||||
"links": null,
|
||||
"shape": 3
|
||||
},
|
||||
{
|
||||
"name": "FLOAT",
|
||||
"type": "FLOAT",
|
||||
"links": null,
|
||||
"shape": 3
|
||||
},
|
||||
{
|
||||
"name": "INT",
|
||||
"type": "INT",
|
||||
"links": null,
|
||||
"shape": 3
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "ScreenShare"
|
||||
},
|
||||
"widgets_values": [
|
||||
null,
|
||||
500,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null
|
||||
]
|
||||
}
|
||||
],
|
||||
"links": [
|
||||
[
|
||||
2,
|
||||
5,
|
||||
0,
|
||||
3,
|
||||
3,
|
||||
"LATENT"
|
||||
],
|
||||
[
|
||||
6,
|
||||
7,
|
||||
0,
|
||||
3,
|
||||
2,
|
||||
"CONDITIONING"
|
||||
],
|
||||
[
|
||||
7,
|
||||
3,
|
||||
0,
|
||||
8,
|
||||
0,
|
||||
"LATENT"
|
||||
],
|
||||
[
|
||||
8,
|
||||
4,
|
||||
2,
|
||||
8,
|
||||
1,
|
||||
"VAE"
|
||||
],
|
||||
[
|
||||
12,
|
||||
15,
|
||||
0,
|
||||
3,
|
||||
0,
|
||||
"MODEL"
|
||||
],
|
||||
[
|
||||
13,
|
||||
4,
|
||||
0,
|
||||
15,
|
||||
0,
|
||||
"MODEL"
|
||||
],
|
||||
[
|
||||
14,
|
||||
4,
|
||||
1,
|
||||
15,
|
||||
1,
|
||||
"CLIP"
|
||||
],
|
||||
[
|
||||
15,
|
||||
15,
|
||||
1,
|
||||
6,
|
||||
0,
|
||||
"CLIP"
|
||||
],
|
||||
[
|
||||
16,
|
||||
15,
|
||||
1,
|
||||
7,
|
||||
0,
|
||||
"CLIP"
|
||||
],
|
||||
[
|
||||
18,
|
||||
19,
|
||||
0,
|
||||
18,
|
||||
1,
|
||||
"CONTROL_NET"
|
||||
],
|
||||
[
|
||||
21,
|
||||
18,
|
||||
0,
|
||||
3,
|
||||
1,
|
||||
"CONDITIONING"
|
||||
],
|
||||
[
|
||||
22,
|
||||
6,
|
||||
0,
|
||||
18,
|
||||
0,
|
||||
"CONDITIONING"
|
||||
],
|
||||
[
|
||||
23,
|
||||
15,
|
||||
0,
|
||||
19,
|
||||
0,
|
||||
"MODEL"
|
||||
],
|
||||
[
|
||||
26,
|
||||
25,
|
||||
0,
|
||||
18,
|
||||
2,
|
||||
"IMAGE"
|
||||
],
|
||||
[
|
||||
27,
|
||||
25,
|
||||
0,
|
||||
20,
|
||||
0,
|
||||
"IMAGE"
|
||||
],
|
||||
[
|
||||
28,
|
||||
8,
|
||||
0,
|
||||
26,
|
||||
0,
|
||||
"IMAGE"
|
||||
],
|
||||
[
|
||||
31,
|
||||
29,
|
||||
0,
|
||||
25,
|
||||
0,
|
||||
"IMAGE"
|
||||
],
|
||||
[
|
||||
32,
|
||||
29,
|
||||
0,
|
||||
28,
|
||||
0,
|
||||
"IMAGE"
|
||||
]
|
||||
],
|
||||
"groups": [],
|
||||
"config": {},
|
||||
"extra": {},
|
||||
"version": 0.4
|
||||
}
|
||||
@@ -295,7 +295,7 @@
|
||||
"Node name for S&R": "KSampler"
|
||||
},
|
||||
"widgets_values": [
|
||||
1115769645491668,
|
||||
644769503212755,
|
||||
"randomize",
|
||||
4,
|
||||
1.6,
|
||||
@@ -329,6 +329,34 @@
|
||||
"Node name for S&R": "PreviewImage"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 20,
|
||||
"type": "FloatingVideo",
|
||||
"pos": [
|
||||
2041,
|
||||
277
|
||||
],
|
||||
"size": [
|
||||
315,
|
||||
58
|
||||
],
|
||||
"flags": {},
|
||||
"order": 12,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "images",
|
||||
"type": "IMAGE",
|
||||
"link": 41
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "FloatingVideo"
|
||||
},
|
||||
"widgets_values": [
|
||||
null
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 6,
|
||||
"type": "LoraLoader",
|
||||
@@ -476,13 +504,13 @@
|
||||
"id": 22,
|
||||
"type": "ScreenShare",
|
||||
"pos": [
|
||||
-63,
|
||||
483
|
||||
-111,
|
||||
427
|
||||
],
|
||||
"size": [
|
||||
315,
|
||||
644
|
||||
],
|
||||
"size": {
|
||||
"0": 315,
|
||||
"1": 98
|
||||
},
|
||||
"flags": {},
|
||||
"order": 3,
|
||||
"mode": 0,
|
||||
@@ -498,43 +526,38 @@
|
||||
"slot_index": 0
|
||||
},
|
||||
{
|
||||
"name": "STRING",
|
||||
"name": "PROMPT",
|
||||
"type": "STRING",
|
||||
"links": [
|
||||
48
|
||||
],
|
||||
"shape": 3,
|
||||
"slot_index": 1
|
||||
},
|
||||
{
|
||||
"name": "FLOAT",
|
||||
"type": "FLOAT",
|
||||
"links": null,
|
||||
"shape": 3
|
||||
},
|
||||
{
|
||||
"name": "INT",
|
||||
"type": "INT",
|
||||
"links": null,
|
||||
"shape": 3
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "ScreenShare"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 20,
|
||||
"type": "FloatingVideo",
|
||||
"pos": [
|
||||
1928,
|
||||
295
|
||||
],
|
||||
"size": [
|
||||
315,
|
||||
58
|
||||
],
|
||||
"flags": {},
|
||||
"order": 12,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "images",
|
||||
"type": "IMAGE",
|
||||
"link": 41
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "FloatingVideo"
|
||||
}
|
||||
},
|
||||
"widgets_values": [
|
||||
null,
|
||||
500,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null
|
||||
]
|
||||
}
|
||||
],
|
||||
"links": [
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"last_node_id": 34,
|
||||
"last_link_id": 35,
|
||||
"last_node_id": 47,
|
||||
"last_link_id": 46,
|
||||
"nodes": [
|
||||
{
|
||||
"id": 27,
|
||||
@@ -14,7 +14,7 @@
|
||||
"1": 164.31304931640625
|
||||
},
|
||||
"flags": {},
|
||||
"order": 7,
|
||||
"order": 5,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
@@ -25,7 +25,7 @@
|
||||
{
|
||||
"name": "text",
|
||||
"type": "STRING",
|
||||
"link": 35,
|
||||
"link": 46,
|
||||
"widget": {
|
||||
"name": "text"
|
||||
}
|
||||
@@ -136,7 +136,7 @@
|
||||
"Node name for S&R": "KSampler"
|
||||
},
|
||||
"widgets_values": [
|
||||
264070254513477,
|
||||
971428736321335,
|
||||
"randomize",
|
||||
15,
|
||||
8,
|
||||
@@ -294,26 +294,26 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 33,
|
||||
"type": "SessionHistory",
|
||||
"id": 44,
|
||||
"type": "ShowTextForGPT",
|
||||
"pos": [
|
||||
1300,
|
||||
430
|
||||
],
|
||||
"size": [
|
||||
580.1640810546876,
|
||||
223.1105875244142
|
||||
1171,
|
||||
425
|
||||
],
|
||||
"size": {
|
||||
"0": 432.46002197265625,
|
||||
"1": 264.40771484375
|
||||
},
|
||||
"flags": {},
|
||||
"order": 6,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "session_history",
|
||||
"name": "text",
|
||||
"type": "STRING",
|
||||
"link": 33,
|
||||
"link": 44,
|
||||
"widget": {
|
||||
"name": "session_history"
|
||||
"name": "text"
|
||||
}
|
||||
}
|
||||
],
|
||||
@@ -322,38 +322,37 @@
|
||||
"name": "STRING",
|
||||
"type": "STRING",
|
||||
"links": null,
|
||||
"shape": 6,
|
||||
"slot_index": 0
|
||||
"shape": 6
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "SessionHistory"
|
||||
"Node name for S&R": "ShowTextForGPT"
|
||||
},
|
||||
"widgets_values": [
|
||||
"[\n {\n \"role\": \"user\",\n \"content\": \"黑客与画家\"\n },\n {\n \"role\": \"assistant\",\n \"content\": \"\\\"Hacker and Painter\\\" is a popular Chinese comic series written and illustrated by Cheng先辈, which has gained a large following worldwide.\\n\\nThe series revolves around the lives of two main characters, a young hacker and an artist, who embark on a journey of self-discovery and personal growth. Set in a futuristic, dystopian world, the story combines elements of science fiction, technology, and action with a touch of humor and satire.\\n\\nThe art style in \\\"Hacker and Painter\\\" is a mix of sci-fi and traditional Chinese ink painting, with bold lines, vibrant colors, and intricate details. Cheng先辈 employs a unique painting technique, using watercolor washes and ink to create a dynamic, expressive, and visually striking visual style.\\n\\nThe artist's use of photography and various camera angles adds an extra layer of depth to the storytelling, allowing the viewer to experience the world from different perspectives and gain a deeper understanding of the characters' emotions and motivations.\\n\\nLighting plays a significant role in the series, with the use of dramatic, contrasting light sources adding to the intensity and emotion of various scenes. Cheng先辈 expertly utilizes lighting to enhance the mood and setting, drawing the audience into the world of \\\"Hacker and Painter\\\" and making them feel like they are part of the action.\\n\\nOverall, \\\"Hacker and Painter\\\" is a visually stunning and thought-provoking series that combines the best of science fiction, action, and art, making it a must-read for fans of all ages.\"\n }\n]"
|
||||
"[\n {\n \"role\": \"system\",\n \"content\": \"You are ChatGPT, a large language model trained by OpenAI. Answer as concisely as possible.\"\n },\n {\n \"role\": \"user\",\n \"content\": \"\"\n }\n]"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 34,
|
||||
"type": "SessionHistory",
|
||||
"id": 43,
|
||||
"type": "ShowTextForGPT",
|
||||
"pos": [
|
||||
1300,
|
||||
230
|
||||
],
|
||||
"size": [
|
||||
562.8610810546875,
|
||||
125.32558752441412
|
||||
1166,
|
||||
740
|
||||
],
|
||||
"size": {
|
||||
"0": 424.0079650878906,
|
||||
"1": 430.6391296386719
|
||||
},
|
||||
"flags": {},
|
||||
"order": 5,
|
||||
"order": 7,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "session_history",
|
||||
"name": "text",
|
||||
"type": "STRING",
|
||||
"link": 34,
|
||||
"link": 45,
|
||||
"widget": {
|
||||
"name": "session_history"
|
||||
"name": "text"
|
||||
}
|
||||
}
|
||||
],
|
||||
@@ -361,100 +360,114 @@
|
||||
{
|
||||
"name": "STRING",
|
||||
"type": "STRING",
|
||||
"links": [
|
||||
35
|
||||
],
|
||||
"shape": 6,
|
||||
"slot_index": 0
|
||||
"links": null,
|
||||
"shape": 6
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "SessionHistory"
|
||||
"Node name for S&R": "ShowTextForGPT"
|
||||
},
|
||||
"widgets_values": [
|
||||
"\"Hacker and Painter\" is a popular Chinese comic series written and illustrated by Cheng先辈, which has gained a large following worldwide.\n\nThe series revolves around the lives of two main characters, a young hacker and an artist, who embark on a journey of self-discovery and personal growth. Set in a futuristic, dystopian world, the story combines elements of science fiction, technology, and action with a touch of humor and satire.\n\nThe art style in \"Hacker and Painter\" is a mix of sci-fi and traditional Chinese ink painting, with bold lines, vibrant colors, and intricate details. Cheng先辈 employs a unique painting technique, using watercolor washes and ink to create a dynamic, expressive, and visually striking visual style.\n\nThe artist's use of photography and various camera angles adds an extra layer of depth to the storytelling, allowing the viewer to experience the world from different perspectives and gain a deeper understanding of the characters' emotions and motivations.\n\nLighting plays a significant role in the series, with the use of dramatic, contrasting light sources adding to the intensity and emotion of various scenes. Cheng先辈 expertly utilizes lighting to enhance the mood and setting, drawing the audience into the world of \"Hacker and Painter\" and making them feel like they are part of the action.\n\nOverall, \"Hacker and Painter\" is a visually stunning and thought-provoking series that combines the best of science fiction, action, and art, making it a must-read for fans of all ages."
|
||||
"[\n {\n \"role\": \"user\",\n \"content\": \"\"\n },\n {\n \"role\": \"assistant\",\n \"content\": \"I'm sorry, I'm ChatGLM3-6B, not ChatGPT. I am a language model jointly trained by Tsinghua University KEG Lab and Zhipu AI Company.\"\n }\n]"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 32,
|
||||
"type": "PrimitiveNode",
|
||||
"id": 45,
|
||||
"type": "ShowTextForGPT",
|
||||
"pos": [
|
||||
1314,
|
||||
1075
|
||||
1092,
|
||||
262
|
||||
],
|
||||
"size": {
|
||||
"0": 571.9803466796875,
|
||||
"1": 100.6700210571289
|
||||
},
|
||||
"flags": {},
|
||||
"order": 2,
|
||||
"mode": 0,
|
||||
"outputs": [
|
||||
{
|
||||
"name": "STRING",
|
||||
"type": "STRING",
|
||||
"links": [
|
||||
32
|
||||
],
|
||||
"widget": {
|
||||
"name": "prompt"
|
||||
}
|
||||
}
|
||||
],
|
||||
"title": "prompt",
|
||||
"properties": {},
|
||||
"widgets_values": [
|
||||
"黑客与画家"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 10,
|
||||
"type": "ChatGPT",
|
||||
"pos": [
|
||||
1300,
|
||||
720
|
||||
],
|
||||
"size": {
|
||||
"0": 586.7649536132812,
|
||||
"1": 301.5857849121094
|
||||
"0": 635.8358154296875,
|
||||
"1": 101.46092224121094
|
||||
},
|
||||
"flags": {},
|
||||
"order": 4,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "prompt",
|
||||
"name": "text",
|
||||
"type": "STRING",
|
||||
"link": 32,
|
||||
"link": 43,
|
||||
"widget": {
|
||||
"name": "prompt"
|
||||
},
|
||||
"slot_index": 0
|
||||
"name": "text"
|
||||
}
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "STRING",
|
||||
"type": "STRING",
|
||||
"links": null,
|
||||
"shape": 6
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "ShowTextForGPT"
|
||||
},
|
||||
"widgets_values": [
|
||||
"I'm sorry, I'm ChatGLM3-6B, not ChatGPT. I am a language model jointly trained by Tsinghua University KEG Lab and Zhipu AI Company."
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 47,
|
||||
"type": "ChatGPTOpenAI",
|
||||
"pos": [
|
||||
585,
|
||||
306
|
||||
],
|
||||
"size": {
|
||||
"0": 400,
|
||||
"1": 342
|
||||
},
|
||||
"flags": {},
|
||||
"order": 2,
|
||||
"mode": 0,
|
||||
"outputs": [
|
||||
{
|
||||
"name": "text",
|
||||
"type": "STRING",
|
||||
"links": [
|
||||
34
|
||||
43,
|
||||
46
|
||||
],
|
||||
"shape": 3,
|
||||
"slot_index": 0
|
||||
},
|
||||
{
|
||||
"name": "JSON",
|
||||
"name": "messages",
|
||||
"type": "STRING",
|
||||
"links": [
|
||||
33
|
||||
44
|
||||
],
|
||||
"shape": 3,
|
||||
"slot_index": 1
|
||||
},
|
||||
{
|
||||
"name": "session_history",
|
||||
"type": "STRING",
|
||||
"links": [
|
||||
45
|
||||
],
|
||||
"shape": 3,
|
||||
"slot_index": 2
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "ChatGPT"
|
||||
}
|
||||
"Node name for S&R": "ChatGPTOpenAI"
|
||||
},
|
||||
"widgets_values": [
|
||||
null,
|
||||
null,
|
||||
"",
|
||||
"You are ChatGPT, a large language model trained by OpenAI. Answer as concisely as possible.",
|
||||
"gpt-3.5-turbo-16k",
|
||||
2220,
|
||||
"randomize",
|
||||
1,
|
||||
null
|
||||
]
|
||||
}
|
||||
],
|
||||
"links": [
|
||||
@@ -579,32 +592,32 @@
|
||||
"IMAGE"
|
||||
],
|
||||
[
|
||||
32,
|
||||
32,
|
||||
43,
|
||||
47,
|
||||
0,
|
||||
10,
|
||||
45,
|
||||
0,
|
||||
"STRING"
|
||||
],
|
||||
[
|
||||
33,
|
||||
10,
|
||||
44,
|
||||
47,
|
||||
1,
|
||||
33,
|
||||
44,
|
||||
0,
|
||||
"STRING"
|
||||
],
|
||||
[
|
||||
34,
|
||||
10,
|
||||
0,
|
||||
34,
|
||||
45,
|
||||
47,
|
||||
2,
|
||||
43,
|
||||
0,
|
||||
"STRING"
|
||||
],
|
||||
[
|
||||
35,
|
||||
34,
|
||||
46,
|
||||
47,
|
||||
0,
|
||||
27,
|
||||
1,
|
||||
|
||||
@@ -0,0 +1,251 @@
|
||||
{
|
||||
"last_node_id": 5,
|
||||
"last_link_id": 4,
|
||||
"nodes": [
|
||||
{
|
||||
"id": 4,
|
||||
"type": "ChatGPTOpenAI",
|
||||
"pos": [
|
||||
-512,
|
||||
-236
|
||||
],
|
||||
"size": {
|
||||
"0": 400,
|
||||
"1": 342
|
||||
},
|
||||
"flags": {},
|
||||
"order": 0,
|
||||
"mode": 0,
|
||||
"outputs": [
|
||||
{
|
||||
"name": "text",
|
||||
"type": "STRING",
|
||||
"links": [
|
||||
2
|
||||
],
|
||||
"shape": 3,
|
||||
"slot_index": 0
|
||||
},
|
||||
{
|
||||
"name": "messages",
|
||||
"type": "STRING",
|
||||
"links": null,
|
||||
"shape": 3
|
||||
},
|
||||
{
|
||||
"name": "session_history",
|
||||
"type": "STRING",
|
||||
"links": null,
|
||||
"shape": 3
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "ChatGPTOpenAI"
|
||||
},
|
||||
"widgets_values": [
|
||||
null,
|
||||
null,
|
||||
"描述一个科幻的场景",
|
||||
"You are ChatGPT, a large language model trained by OpenAI. Answer as concisely as possible.",
|
||||
"gpt-35-turbo",
|
||||
6933,
|
||||
"randomize",
|
||||
1,
|
||||
null
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"type": "ChatGPTOpenAI",
|
||||
"pos": [
|
||||
-36,
|
||||
-234
|
||||
],
|
||||
"size": {
|
||||
"0": 400,
|
||||
"1": 342
|
||||
},
|
||||
"flags": {},
|
||||
"order": 1,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "prompt",
|
||||
"type": "STRING",
|
||||
"link": 2,
|
||||
"widget": {
|
||||
"name": "prompt"
|
||||
}
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "text",
|
||||
"type": "STRING",
|
||||
"links": [
|
||||
3
|
||||
],
|
||||
"shape": 3,
|
||||
"slot_index": 0
|
||||
},
|
||||
{
|
||||
"name": "messages",
|
||||
"type": "STRING",
|
||||
"links": null,
|
||||
"shape": 3
|
||||
},
|
||||
{
|
||||
"name": "session_history",
|
||||
"type": "STRING",
|
||||
"links": null,
|
||||
"shape": 3
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "ChatGPTOpenAI"
|
||||
},
|
||||
"widgets_values": [
|
||||
null,
|
||||
null,
|
||||
"",
|
||||
"增加丰富的细节和光影,摄影技巧,镜头语言,材质肌理",
|
||||
"gpt-3.5-turbo",
|
||||
2836,
|
||||
"randomize",
|
||||
1,
|
||||
null
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 5,
|
||||
"type": "ChatGPTOpenAI",
|
||||
"pos": [
|
||||
-30,
|
||||
183
|
||||
],
|
||||
"size": {
|
||||
"0": 400,
|
||||
"1": 342
|
||||
},
|
||||
"flags": {},
|
||||
"order": 2,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "prompt",
|
||||
"type": "STRING",
|
||||
"link": 3,
|
||||
"widget": {
|
||||
"name": "prompt"
|
||||
}
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "text",
|
||||
"type": "STRING",
|
||||
"links": [
|
||||
4
|
||||
],
|
||||
"shape": 3,
|
||||
"slot_index": 0
|
||||
},
|
||||
{
|
||||
"name": "messages",
|
||||
"type": "STRING",
|
||||
"links": null,
|
||||
"shape": 3
|
||||
},
|
||||
{
|
||||
"name": "session_history",
|
||||
"type": "STRING",
|
||||
"links": null,
|
||||
"shape": 3
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "ChatGPTOpenAI"
|
||||
},
|
||||
"widgets_values": [
|
||||
null,
|
||||
null,
|
||||
"",
|
||||
"翻译成英文,并按照格式输出: 画面、主题、细节、灯光、氛围、艺术家、其他",
|
||||
"gpt-3.5-turbo",
|
||||
1085,
|
||||
"randomize",
|
||||
1,
|
||||
null
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"type": "ShowTextForGPT",
|
||||
"pos": [
|
||||
447,
|
||||
-229
|
||||
],
|
||||
"size": [
|
||||
503.79851499517997,
|
||||
356.0560985581077
|
||||
],
|
||||
"flags": {},
|
||||
"order": 3,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "text",
|
||||
"type": "STRING",
|
||||
"link": 4,
|
||||
"widget": {
|
||||
"name": "text"
|
||||
}
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "STRING",
|
||||
"type": "STRING",
|
||||
"links": null,
|
||||
"shape": 6
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "ShowTextForGPT"
|
||||
},
|
||||
"widgets_values": [
|
||||
"Picture: The picture is composed of carefully chosen elements, capturing the subject matter in a visually striking way.\n\nTheme: The theme of the photograph could vary, from capturing nature's beauty to showcasing urban landscapes, human emotions, or abstract concepts.\n\nDetails: The photograph captures intricate details, bringing attention to the subject's textures, colors, shapes, and patterns.\n\nLighting: The photographer manipulates lighting, using techniques like natural light, dramatic shadows, or artificial lighting to enhance the mood and atmosphere of the photograph.\n\nAmbiance: The photograph evokes a specific ambiance or mood, whether it's serene, mysterious, joyful, melancholic, or any other emotional response.\n\nArtist: The photographer skillfully crafts the image, demonstrating their artistic vision, technical skills, and creative expression through the composition, framing, and post-processing choices.\n\nOthers: Apart from the elements mentioned above, the photograph may also incorporate other creative techniques like long exposure, multiple exposures, color grading, or unconventional perspectives to create a unique and captivating image.\n\nIn the future, as technology and imagination continue to advance, photography will likely continue to evolve and innovate, offering even more realistic and awe-inspiring visual experiences for humans."
|
||||
]
|
||||
}
|
||||
],
|
||||
"links": [
|
||||
[
|
||||
2,
|
||||
4,
|
||||
0,
|
||||
2,
|
||||
0,
|
||||
"STRING"
|
||||
],
|
||||
[
|
||||
3,
|
||||
2,
|
||||
0,
|
||||
5,
|
||||
0,
|
||||
"STRING"
|
||||
],
|
||||
[
|
||||
4,
|
||||
5,
|
||||
0,
|
||||
3,
|
||||
0,
|
||||
"STRING"
|
||||
]
|
||||
],
|
||||
"groups": [],
|
||||
"config": {},
|
||||
"extra": {},
|
||||
"version": 0.4
|
||||
}
|
||||
@@ -0,0 +1,731 @@
|
||||
{
|
||||
"last_node_id": 24,
|
||||
"last_link_id": 59,
|
||||
"nodes": [
|
||||
{
|
||||
"id": 7,
|
||||
"type": "CLIPTextEncode",
|
||||
"pos": [
|
||||
515,
|
||||
130
|
||||
],
|
||||
"size": {
|
||||
"0": 400,
|
||||
"1": 200
|
||||
},
|
||||
"flags": {},
|
||||
"order": 2,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "clip",
|
||||
"type": "CLIP",
|
||||
"link": 33
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "CONDITIONING",
|
||||
"type": "CONDITIONING",
|
||||
"links": [
|
||||
29
|
||||
],
|
||||
"shape": 3,
|
||||
"slot_index": 0
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "CLIPTextEncode"
|
||||
},
|
||||
"widgets_values": [
|
||||
""
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 8,
|
||||
"type": "CLIPTextEncode",
|
||||
"pos": [
|
||||
515,
|
||||
1268
|
||||
],
|
||||
"size": {
|
||||
"0": 400,
|
||||
"1": 200
|
||||
},
|
||||
"flags": {},
|
||||
"order": 6,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "clip",
|
||||
"type": "CLIP",
|
||||
"link": 32
|
||||
},
|
||||
{
|
||||
"name": "text",
|
||||
"type": "STRING",
|
||||
"link": 59,
|
||||
"widget": {
|
||||
"name": "text"
|
||||
}
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "CONDITIONING",
|
||||
"type": "CONDITIONING",
|
||||
"links": [
|
||||
51
|
||||
],
|
||||
"shape": 3,
|
||||
"slot_index": 0
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "CLIPTextEncode"
|
||||
},
|
||||
"widgets_values": [
|
||||
""
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 6,
|
||||
"type": "LoraLoader",
|
||||
"pos": [
|
||||
515,
|
||||
460
|
||||
],
|
||||
"size": {
|
||||
"0": 315,
|
||||
"1": 126
|
||||
},
|
||||
"flags": {},
|
||||
"order": 3,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "model",
|
||||
"type": "MODEL",
|
||||
"link": 31
|
||||
},
|
||||
{
|
||||
"name": "clip",
|
||||
"type": "CLIP",
|
||||
"link": 45
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "MODEL",
|
||||
"type": "MODEL",
|
||||
"links": [
|
||||
44
|
||||
],
|
||||
"shape": 3,
|
||||
"slot_index": 0
|
||||
},
|
||||
{
|
||||
"name": "CLIP",
|
||||
"type": "CLIP",
|
||||
"links": null,
|
||||
"shape": 3
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "LoraLoader"
|
||||
},
|
||||
"widgets_values": [
|
||||
"lcm-lora-sdv1-5.safetensors",
|
||||
1,
|
||||
1
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 5,
|
||||
"type": "KSampler",
|
||||
"pos": [
|
||||
1015,
|
||||
130
|
||||
],
|
||||
"size": {
|
||||
"0": 315,
|
||||
"1": 262
|
||||
},
|
||||
"flags": {},
|
||||
"order": 7,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "model",
|
||||
"type": "MODEL",
|
||||
"link": 44
|
||||
},
|
||||
{
|
||||
"name": "positive",
|
||||
"type": "CONDITIONING",
|
||||
"link": 51
|
||||
},
|
||||
{
|
||||
"name": "negative",
|
||||
"type": "CONDITIONING",
|
||||
"link": 29
|
||||
},
|
||||
{
|
||||
"name": "latent_image",
|
||||
"type": "LATENT",
|
||||
"link": 54
|
||||
},
|
||||
{
|
||||
"name": "denoise",
|
||||
"type": "FLOAT",
|
||||
"link": 56,
|
||||
"widget": {
|
||||
"name": "denoise"
|
||||
}
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "LATENT",
|
||||
"type": "LATENT",
|
||||
"links": [
|
||||
35
|
||||
],
|
||||
"shape": 3,
|
||||
"slot_index": 0
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "KSampler"
|
||||
},
|
||||
"widgets_values": [
|
||||
758428944049342,
|
||||
"fixed",
|
||||
4,
|
||||
1.6,
|
||||
"lcm",
|
||||
"karras",
|
||||
1
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 11,
|
||||
"type": "VAEDecode",
|
||||
"pos": [
|
||||
1430,
|
||||
130
|
||||
],
|
||||
"size": {
|
||||
"0": 210,
|
||||
"1": 46
|
||||
},
|
||||
"flags": {},
|
||||
"order": 8,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "samples",
|
||||
"type": "LATENT",
|
||||
"link": 35
|
||||
},
|
||||
{
|
||||
"name": "vae",
|
||||
"type": "VAE",
|
||||
"link": 34
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "IMAGE",
|
||||
"type": "IMAGE",
|
||||
"links": [
|
||||
41
|
||||
],
|
||||
"shape": 3,
|
||||
"slot_index": 0
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "VAEDecode"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 20,
|
||||
"type": "FloatingVideo",
|
||||
"pos": [
|
||||
1740,
|
||||
130
|
||||
],
|
||||
"size": {
|
||||
"0": 315,
|
||||
"1": 58
|
||||
},
|
||||
"flags": {},
|
||||
"order": 9,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "images",
|
||||
"type": "IMAGE",
|
||||
"link": 41
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "FloatingVideo"
|
||||
},
|
||||
"widgets_values": [
|
||||
null
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"type": "PreviewImage",
|
||||
"pos": [
|
||||
515,
|
||||
716
|
||||
],
|
||||
"size": {
|
||||
"0": 210,
|
||||
"1": 246
|
||||
},
|
||||
"flags": {},
|
||||
"order": 4,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "images",
|
||||
"type": "IMAGE",
|
||||
"link": 55
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "PreviewImage"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 23,
|
||||
"type": "VAEEncode",
|
||||
"pos": [
|
||||
515,
|
||||
1092
|
||||
],
|
||||
"size": {
|
||||
"0": 210,
|
||||
"1": 46
|
||||
},
|
||||
"flags": {},
|
||||
"order": 5,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "pixels",
|
||||
"type": "IMAGE",
|
||||
"link": 57
|
||||
},
|
||||
{
|
||||
"name": "vae",
|
||||
"type": "VAE",
|
||||
"link": 58
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "LATENT",
|
||||
"type": "LATENT",
|
||||
"links": [
|
||||
54
|
||||
],
|
||||
"shape": 3,
|
||||
"slot_index": 0
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "VAEEncode"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 10,
|
||||
"type": "CheckpointLoaderSimple",
|
||||
"pos": [
|
||||
100,
|
||||
130
|
||||
],
|
||||
"size": {
|
||||
"0": 315,
|
||||
"1": 98
|
||||
},
|
||||
"flags": {},
|
||||
"order": 0,
|
||||
"mode": 0,
|
||||
"outputs": [
|
||||
{
|
||||
"name": "MODEL",
|
||||
"type": "MODEL",
|
||||
"links": [
|
||||
31
|
||||
],
|
||||
"shape": 3,
|
||||
"slot_index": 0
|
||||
},
|
||||
{
|
||||
"name": "CLIP",
|
||||
"type": "CLIP",
|
||||
"links": [
|
||||
32,
|
||||
33,
|
||||
45
|
||||
],
|
||||
"shape": 3,
|
||||
"slot_index": 1
|
||||
},
|
||||
{
|
||||
"name": "VAE",
|
||||
"type": "VAE",
|
||||
"links": [
|
||||
34,
|
||||
58
|
||||
],
|
||||
"shape": 3,
|
||||
"slot_index": 2
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "CheckpointLoaderSimple"
|
||||
},
|
||||
"widgets_values": [
|
||||
"deliberate_v2.safetensors"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 24,
|
||||
"type": "ScreenShare",
|
||||
"pos": [
|
||||
100,
|
||||
358
|
||||
],
|
||||
"size": {
|
||||
"0": 315,
|
||||
"1": 170
|
||||
},
|
||||
"flags": {},
|
||||
"order": 1,
|
||||
"mode": 0,
|
||||
"outputs": [
|
||||
{
|
||||
"name": "IMAGE",
|
||||
"type": "IMAGE",
|
||||
"links": [
|
||||
55,
|
||||
57
|
||||
],
|
||||
"shape": 3,
|
||||
"slot_index": 0
|
||||
},
|
||||
{
|
||||
"name": "PROMPT",
|
||||
"type": "STRING",
|
||||
"links": [
|
||||
59
|
||||
],
|
||||
"shape": 3,
|
||||
"slot_index": 1
|
||||
},
|
||||
{
|
||||
"name": "FLOAT",
|
||||
"type": "FLOAT",
|
||||
"links": [
|
||||
56
|
||||
],
|
||||
"shape": 3,
|
||||
"slot_index": 2
|
||||
},
|
||||
{
|
||||
"name": "INT",
|
||||
"type": "INT",
|
||||
"links": null,
|
||||
"shape": 3
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "ScreenShare"
|
||||
},
|
||||
"widgets_values": [
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null
|
||||
]
|
||||
}
|
||||
],
|
||||
"links": [
|
||||
[
|
||||
1,
|
||||
1,
|
||||
0,
|
||||
2,
|
||||
0,
|
||||
"IMAGE"
|
||||
],
|
||||
[
|
||||
3,
|
||||
6,
|
||||
0,
|
||||
5,
|
||||
0,
|
||||
"MODEL"
|
||||
],
|
||||
[
|
||||
5,
|
||||
8,
|
||||
0,
|
||||
5,
|
||||
2,
|
||||
"CONDITIONING"
|
||||
],
|
||||
[
|
||||
6,
|
||||
9,
|
||||
0,
|
||||
5,
|
||||
3,
|
||||
"LATENT"
|
||||
],
|
||||
[
|
||||
7,
|
||||
10,
|
||||
0,
|
||||
6,
|
||||
0,
|
||||
"MODEL"
|
||||
],
|
||||
[
|
||||
8,
|
||||
10,
|
||||
1,
|
||||
6,
|
||||
1,
|
||||
"CLIP"
|
||||
],
|
||||
[
|
||||
9,
|
||||
5,
|
||||
0,
|
||||
11,
|
||||
0,
|
||||
"LATENT"
|
||||
],
|
||||
[
|
||||
10,
|
||||
10,
|
||||
2,
|
||||
11,
|
||||
1,
|
||||
"VAE"
|
||||
],
|
||||
[
|
||||
11,
|
||||
6,
|
||||
1,
|
||||
7,
|
||||
0,
|
||||
"CLIP"
|
||||
],
|
||||
[
|
||||
12,
|
||||
6,
|
||||
1,
|
||||
8,
|
||||
0,
|
||||
"CLIP"
|
||||
],
|
||||
[
|
||||
14,
|
||||
11,
|
||||
0,
|
||||
3,
|
||||
0,
|
||||
"IMAGE"
|
||||
],
|
||||
[
|
||||
17,
|
||||
1,
|
||||
2,
|
||||
7,
|
||||
1,
|
||||
"STRING"
|
||||
],
|
||||
[
|
||||
18,
|
||||
7,
|
||||
0,
|
||||
15,
|
||||
0,
|
||||
"CONDITIONING"
|
||||
],
|
||||
[
|
||||
19,
|
||||
15,
|
||||
0,
|
||||
5,
|
||||
1,
|
||||
"CONDITIONING"
|
||||
],
|
||||
[
|
||||
20,
|
||||
16,
|
||||
0,
|
||||
15,
|
||||
1,
|
||||
"CONTROL_NET"
|
||||
],
|
||||
[
|
||||
24,
|
||||
1,
|
||||
0,
|
||||
18,
|
||||
0,
|
||||
"IMAGE"
|
||||
],
|
||||
[
|
||||
25,
|
||||
18,
|
||||
0,
|
||||
15,
|
||||
2,
|
||||
"IMAGE"
|
||||
],
|
||||
[
|
||||
29,
|
||||
7,
|
||||
0,
|
||||
5,
|
||||
2,
|
||||
"CONDITIONING"
|
||||
],
|
||||
[
|
||||
31,
|
||||
10,
|
||||
0,
|
||||
6,
|
||||
0,
|
||||
"MODEL"
|
||||
],
|
||||
[
|
||||
32,
|
||||
10,
|
||||
1,
|
||||
8,
|
||||
0,
|
||||
"CLIP"
|
||||
],
|
||||
[
|
||||
33,
|
||||
10,
|
||||
1,
|
||||
7,
|
||||
0,
|
||||
"CLIP"
|
||||
],
|
||||
[
|
||||
34,
|
||||
10,
|
||||
2,
|
||||
11,
|
||||
1,
|
||||
"VAE"
|
||||
],
|
||||
[
|
||||
35,
|
||||
5,
|
||||
0,
|
||||
11,
|
||||
0,
|
||||
"LATENT"
|
||||
],
|
||||
[
|
||||
41,
|
||||
11,
|
||||
0,
|
||||
20,
|
||||
0,
|
||||
"IMAGE"
|
||||
],
|
||||
[
|
||||
44,
|
||||
6,
|
||||
0,
|
||||
5,
|
||||
0,
|
||||
"MODEL"
|
||||
],
|
||||
[
|
||||
45,
|
||||
10,
|
||||
1,
|
||||
6,
|
||||
1,
|
||||
"CLIP"
|
||||
],
|
||||
[
|
||||
51,
|
||||
8,
|
||||
0,
|
||||
5,
|
||||
1,
|
||||
"CONDITIONING"
|
||||
],
|
||||
[
|
||||
54,
|
||||
23,
|
||||
0,
|
||||
5,
|
||||
3,
|
||||
"LATENT"
|
||||
],
|
||||
[
|
||||
55,
|
||||
24,
|
||||
0,
|
||||
2,
|
||||
0,
|
||||
"IMAGE"
|
||||
],
|
||||
[
|
||||
56,
|
||||
24,
|
||||
2,
|
||||
5,
|
||||
4,
|
||||
"FLOAT"
|
||||
],
|
||||
[
|
||||
57,
|
||||
24,
|
||||
0,
|
||||
23,
|
||||
0,
|
||||
"IMAGE"
|
||||
],
|
||||
[
|
||||
58,
|
||||
10,
|
||||
2,
|
||||
23,
|
||||
1,
|
||||
"VAE"
|
||||
],
|
||||
[
|
||||
59,
|
||||
24,
|
||||
1,
|
||||
8,
|
||||
1,
|
||||
"STRING"
|
||||
]
|
||||
],
|
||||
"groups": [],
|
||||
"config": {},
|
||||
"extra": {},
|
||||
"version": 0.4
|
||||
}
|
||||
@@ -0,0 +1,722 @@
|
||||
{
|
||||
"last_node_id": 24,
|
||||
"last_link_id": 26,
|
||||
"nodes": [
|
||||
{
|
||||
"id": 9,
|
||||
"type": "CLIPTextEncode",
|
||||
"pos": [
|
||||
2070,
|
||||
830
|
||||
],
|
||||
"size": {
|
||||
"0": 425.27801513671875,
|
||||
"1": 180.6060791015625
|
||||
},
|
||||
"flags": {},
|
||||
"order": 5,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "clip",
|
||||
"type": "CLIP",
|
||||
"link": 7
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "CONDITIONING",
|
||||
"type": "CONDITIONING",
|
||||
"links": [
|
||||
4
|
||||
],
|
||||
"slot_index": 0
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "CLIPTextEncode"
|
||||
},
|
||||
"widgets_values": [
|
||||
"text, watermark"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 7,
|
||||
"type": "EmptyLatentImage",
|
||||
"pos": [
|
||||
2070,
|
||||
1060
|
||||
],
|
||||
"size": {
|
||||
"0": 315,
|
||||
"1": 106
|
||||
},
|
||||
"flags": {},
|
||||
"order": 0,
|
||||
"mode": 0,
|
||||
"outputs": [
|
||||
{
|
||||
"name": "LATENT",
|
||||
"type": "LATENT",
|
||||
"links": [
|
||||
5
|
||||
],
|
||||
"slot_index": 0
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "EmptyLatentImage"
|
||||
},
|
||||
"widgets_values": [
|
||||
512,
|
||||
512,
|
||||
1
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 6,
|
||||
"type": "CheckpointLoaderSimple",
|
||||
"pos": [
|
||||
1640,
|
||||
920
|
||||
],
|
||||
"size": {
|
||||
"0": 315,
|
||||
"1": 98
|
||||
},
|
||||
"flags": {},
|
||||
"order": 1,
|
||||
"mode": 0,
|
||||
"outputs": [
|
||||
{
|
||||
"name": "MODEL",
|
||||
"type": "MODEL",
|
||||
"links": [
|
||||
2
|
||||
],
|
||||
"slot_index": 0
|
||||
},
|
||||
{
|
||||
"name": "CLIP",
|
||||
"type": "CLIP",
|
||||
"links": [
|
||||
6,
|
||||
7
|
||||
],
|
||||
"slot_index": 1
|
||||
},
|
||||
{
|
||||
"name": "VAE",
|
||||
"type": "VAE",
|
||||
"links": [
|
||||
9
|
||||
],
|
||||
"slot_index": 2
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "CheckpointLoaderSimple"
|
||||
},
|
||||
"widgets_values": [
|
||||
"deliberate_v2.safetensors"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 8,
|
||||
"type": "CLIPTextEncode",
|
||||
"pos": [
|
||||
2080,
|
||||
630
|
||||
],
|
||||
"size": {
|
||||
"0": 422.84503173828125,
|
||||
"1": 164.31304931640625
|
||||
},
|
||||
"flags": {},
|
||||
"order": 8,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "clip",
|
||||
"type": "CLIP",
|
||||
"link": 6
|
||||
},
|
||||
{
|
||||
"name": "text",
|
||||
"type": "STRING",
|
||||
"link": 16,
|
||||
"widget": {
|
||||
"name": "text"
|
||||
}
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "CONDITIONING",
|
||||
"type": "CONDITIONING",
|
||||
"links": [
|
||||
3
|
||||
],
|
||||
"slot_index": 0
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "CLIPTextEncode"
|
||||
},
|
||||
"widgets_values": [
|
||||
"beautiful scenery nature glass bottle landscape, , purple galaxy bottle,"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 16,
|
||||
"type": "ShowTextForGPT",
|
||||
"pos": [
|
||||
3288,
|
||||
-323
|
||||
],
|
||||
"size": {
|
||||
"0": 449.1168212890625,
|
||||
"1": 76
|
||||
},
|
||||
"flags": {},
|
||||
"order": 9,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "text",
|
||||
"type": "STRING",
|
||||
"link": 20,
|
||||
"widget": {
|
||||
"name": "text"
|
||||
}
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "STRING",
|
||||
"type": "STRING",
|
||||
"links": null,
|
||||
"shape": 6
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "ShowTextForGPT"
|
||||
},
|
||||
"widgets_values": [
|
||||
"a girl face,super,(Pop Art:1.26),(Black and White:1.26)",
|
||||
"a girl face,super,(Pop Art:1.26),(Black and White:1.26)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 5,
|
||||
"type": "KSampler",
|
||||
"pos": [
|
||||
2520,
|
||||
630
|
||||
],
|
||||
"size": {
|
||||
"0": 315,
|
||||
"1": 262
|
||||
},
|
||||
"flags": {},
|
||||
"order": 10,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "model",
|
||||
"type": "MODEL",
|
||||
"link": 2
|
||||
},
|
||||
{
|
||||
"name": "positive",
|
||||
"type": "CONDITIONING",
|
||||
"link": 3
|
||||
},
|
||||
{
|
||||
"name": "negative",
|
||||
"type": "CONDITIONING",
|
||||
"link": 4
|
||||
},
|
||||
{
|
||||
"name": "latent_image",
|
||||
"type": "LATENT",
|
||||
"link": 5
|
||||
},
|
||||
{
|
||||
"name": "seed",
|
||||
"type": "INT",
|
||||
"link": 21,
|
||||
"widget": {
|
||||
"name": "seed"
|
||||
}
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "LATENT",
|
||||
"type": "LATENT",
|
||||
"links": [
|
||||
8
|
||||
],
|
||||
"slot_index": 0
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "KSampler"
|
||||
},
|
||||
"widgets_values": [
|
||||
1063141893699118,
|
||||
"fixed",
|
||||
20,
|
||||
8,
|
||||
"euler",
|
||||
"normal",
|
||||
1
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 18,
|
||||
"type": "IntNumber",
|
||||
"pos": [
|
||||
2852,
|
||||
275
|
||||
],
|
||||
"size": {
|
||||
"0": 315,
|
||||
"1": 130
|
||||
},
|
||||
"flags": {},
|
||||
"order": 2,
|
||||
"mode": 0,
|
||||
"outputs": [
|
||||
{
|
||||
"name": "INT",
|
||||
"type": "INT",
|
||||
"links": [
|
||||
21
|
||||
],
|
||||
"shape": 3,
|
||||
"slot_index": 0
|
||||
}
|
||||
],
|
||||
"title": "Seed",
|
||||
"properties": {
|
||||
"Node name for S&R": "IntNumber"
|
||||
},
|
||||
"widgets_values": [
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
1
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 22,
|
||||
"type": "PromptSlide",
|
||||
"pos": [
|
||||
2364,
|
||||
-294
|
||||
],
|
||||
"size": {
|
||||
"0": 315,
|
||||
"1": 106
|
||||
},
|
||||
"flags": {},
|
||||
"order": 3,
|
||||
"mode": 0,
|
||||
"outputs": [
|
||||
{
|
||||
"name": "prompt",
|
||||
"type": "STRING",
|
||||
"links": [
|
||||
23
|
||||
],
|
||||
"shape": 3,
|
||||
"slot_index": 0
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "PromptSlide"
|
||||
},
|
||||
"widgets_values": [
|
||||
"Pop Art",
|
||||
1.26,
|
||||
null
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 21,
|
||||
"type": "PromptSlide",
|
||||
"pos": [
|
||||
2360,
|
||||
-544
|
||||
],
|
||||
"size": {
|
||||
"0": 315,
|
||||
"1": 106
|
||||
},
|
||||
"flags": {},
|
||||
"order": 4,
|
||||
"mode": 0,
|
||||
"outputs": [
|
||||
{
|
||||
"name": "prompt",
|
||||
"type": "STRING",
|
||||
"links": [
|
||||
22
|
||||
],
|
||||
"shape": 3,
|
||||
"slot_index": 0
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "PromptSlide"
|
||||
},
|
||||
"widgets_values": [
|
||||
"Black and White",
|
||||
1.26,
|
||||
null
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 13,
|
||||
"type": "RandomPrompt",
|
||||
"pos": [
|
||||
2840,
|
||||
-330
|
||||
],
|
||||
"size": {
|
||||
"0": 400,
|
||||
"1": 224
|
||||
},
|
||||
"flags": {},
|
||||
"order": 6,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "mutable_prompt",
|
||||
"type": "STRING",
|
||||
"link": 22,
|
||||
"widget": {
|
||||
"name": "mutable_prompt"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "immutable_prompt",
|
||||
"type": "STRING",
|
||||
"link": 23,
|
||||
"widget": {
|
||||
"name": "immutable_prompt"
|
||||
}
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "STRING",
|
||||
"type": "STRING",
|
||||
"links": [
|
||||
19
|
||||
],
|
||||
"shape": 6,
|
||||
"slot_index": 0
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "RandomPrompt"
|
||||
},
|
||||
"widgets_values": [
|
||||
1,
|
||||
"",
|
||||
" ``",
|
||||
"disable",
|
||||
""
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 15,
|
||||
"type": "RandomPrompt",
|
||||
"pos": [
|
||||
2840,
|
||||
-30
|
||||
],
|
||||
"size": {
|
||||
"0": 400,
|
||||
"1": 224
|
||||
},
|
||||
"flags": {},
|
||||
"order": 7,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "mutable_prompt",
|
||||
"type": "STRING",
|
||||
"link": 19,
|
||||
"widget": {
|
||||
"name": "mutable_prompt"
|
||||
}
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "STRING",
|
||||
"type": "STRING",
|
||||
"links": [
|
||||
16,
|
||||
20
|
||||
],
|
||||
"shape": 6,
|
||||
"slot_index": 0
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "RandomPrompt"
|
||||
},
|
||||
"widgets_values": [
|
||||
1,
|
||||
"",
|
||||
" a girl face,super,``",
|
||||
"disable",
|
||||
""
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 11,
|
||||
"type": "PreviewImage",
|
||||
"pos": [
|
||||
3449,
|
||||
-102
|
||||
],
|
||||
"size": {
|
||||
"0": 435.8727111816406,
|
||||
"1": 511.8609619140625
|
||||
},
|
||||
"flags": {},
|
||||
"order": 12,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "images",
|
||||
"type": "IMAGE",
|
||||
"link": 24
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "PreviewImage"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 10,
|
||||
"type": "VAEDecode",
|
||||
"pos": [
|
||||
2870,
|
||||
630
|
||||
],
|
||||
"size": {
|
||||
"0": 210,
|
||||
"1": 46
|
||||
},
|
||||
"flags": {
|
||||
"collapsed": false
|
||||
},
|
||||
"order": 11,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "samples",
|
||||
"type": "LATENT",
|
||||
"link": 8
|
||||
},
|
||||
{
|
||||
"name": "vae",
|
||||
"type": "VAE",
|
||||
"link": 9
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "IMAGE",
|
||||
"type": "IMAGE",
|
||||
"links": [
|
||||
24,
|
||||
26
|
||||
],
|
||||
"slot_index": 0
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "VAEDecode"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 24,
|
||||
"type": "AppInfo",
|
||||
"pos": [
|
||||
3363.0014990624995,
|
||||
551.2261418945309
|
||||
],
|
||||
"size": {
|
||||
"0": 400,
|
||||
"1": 344
|
||||
},
|
||||
"flags": {},
|
||||
"order": 13,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "IMAGE",
|
||||
"type": "IMAGE",
|
||||
"link": 26
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "AppInfo"
|
||||
},
|
||||
"widgets_values": [
|
||||
"Prompt-weight-test",
|
||||
"21\n22\n18",
|
||||
"11\n16",
|
||||
"",
|
||||
1,
|
||||
"",
|
||||
"https://",
|
||||
"",
|
||||
"enable",
|
||||
2
|
||||
]
|
||||
}
|
||||
],
|
||||
"links": [
|
||||
[
|
||||
2,
|
||||
6,
|
||||
0,
|
||||
5,
|
||||
0,
|
||||
"MODEL"
|
||||
],
|
||||
[
|
||||
3,
|
||||
8,
|
||||
0,
|
||||
5,
|
||||
1,
|
||||
"CONDITIONING"
|
||||
],
|
||||
[
|
||||
4,
|
||||
9,
|
||||
0,
|
||||
5,
|
||||
2,
|
||||
"CONDITIONING"
|
||||
],
|
||||
[
|
||||
5,
|
||||
7,
|
||||
0,
|
||||
5,
|
||||
3,
|
||||
"LATENT"
|
||||
],
|
||||
[
|
||||
6,
|
||||
6,
|
||||
1,
|
||||
8,
|
||||
0,
|
||||
"CLIP"
|
||||
],
|
||||
[
|
||||
7,
|
||||
6,
|
||||
1,
|
||||
9,
|
||||
0,
|
||||
"CLIP"
|
||||
],
|
||||
[
|
||||
8,
|
||||
5,
|
||||
0,
|
||||
10,
|
||||
0,
|
||||
"LATENT"
|
||||
],
|
||||
[
|
||||
9,
|
||||
6,
|
||||
2,
|
||||
10,
|
||||
1,
|
||||
"VAE"
|
||||
],
|
||||
[
|
||||
16,
|
||||
15,
|
||||
0,
|
||||
8,
|
||||
1,
|
||||
"STRING"
|
||||
],
|
||||
[
|
||||
19,
|
||||
13,
|
||||
0,
|
||||
15,
|
||||
0,
|
||||
"STRING"
|
||||
],
|
||||
[
|
||||
20,
|
||||
15,
|
||||
0,
|
||||
16,
|
||||
0,
|
||||
"STRING"
|
||||
],
|
||||
[
|
||||
21,
|
||||
18,
|
||||
0,
|
||||
5,
|
||||
4,
|
||||
"INT"
|
||||
],
|
||||
[
|
||||
22,
|
||||
21,
|
||||
0,
|
||||
13,
|
||||
0,
|
||||
"STRING"
|
||||
],
|
||||
[
|
||||
23,
|
||||
22,
|
||||
0,
|
||||
13,
|
||||
1,
|
||||
"STRING"
|
||||
],
|
||||
[
|
||||
24,
|
||||
10,
|
||||
0,
|
||||
11,
|
||||
0,
|
||||
"IMAGE"
|
||||
],
|
||||
[
|
||||
26,
|
||||
10,
|
||||
0,
|
||||
24,
|
||||
0,
|
||||
"IMAGE"
|
||||
]
|
||||
],
|
||||
"groups": [],
|
||||
"config": {},
|
||||
"extra": {},
|
||||
"version": 0.4
|
||||
}
|
||||
@@ -0,0 +1,719 @@
|
||||
{
|
||||
"last_node_id": 26,
|
||||
"last_link_id": 34,
|
||||
"nodes": [
|
||||
{
|
||||
"id": 5,
|
||||
"type": "CLIPTextEncode",
|
||||
"pos": [
|
||||
1029,
|
||||
-2149
|
||||
],
|
||||
"size": {
|
||||
"0": 425.27801513671875,
|
||||
"1": 180.6060791015625
|
||||
},
|
||||
"flags": {},
|
||||
"order": 7,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "clip",
|
||||
"type": "CLIP",
|
||||
"link": 6
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "CONDITIONING",
|
||||
"type": "CONDITIONING",
|
||||
"links": [
|
||||
3
|
||||
],
|
||||
"slot_index": 0
|
||||
}
|
||||
],
|
||||
"title": "负向prompt",
|
||||
"properties": {
|
||||
"Node name for S&R": "CLIPTextEncode"
|
||||
},
|
||||
"widgets_values": [
|
||||
"text, watermark"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 15,
|
||||
"type": "EnhanceImage",
|
||||
"pos": [
|
||||
2591,
|
||||
-2531
|
||||
],
|
||||
"size": {
|
||||
"0": 315,
|
||||
"1": 58
|
||||
},
|
||||
"flags": {},
|
||||
"order": 12,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "image",
|
||||
"type": "IMAGE",
|
||||
"link": 34
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "IMAGE",
|
||||
"type": "IMAGE",
|
||||
"links": [
|
||||
18
|
||||
],
|
||||
"shape": 3,
|
||||
"slot_index": 0
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "EnhanceImage"
|
||||
},
|
||||
"widgets_values": [
|
||||
1.1
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 24,
|
||||
"type": "LimitNumber",
|
||||
"pos": [
|
||||
665,
|
||||
-2958
|
||||
],
|
||||
"size": {
|
||||
"0": 315,
|
||||
"1": 82
|
||||
},
|
||||
"flags": {},
|
||||
"order": 4,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "number",
|
||||
"type": "*",
|
||||
"link": 29
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "number",
|
||||
"type": "*",
|
||||
"links": [
|
||||
30
|
||||
],
|
||||
"shape": 3,
|
||||
"slot_index": 0
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "LimitNumber"
|
||||
},
|
||||
"widgets_values": [
|
||||
512,
|
||||
4089
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 25,
|
||||
"type": "LimitNumber",
|
||||
"pos": [
|
||||
648,
|
||||
-2659
|
||||
],
|
||||
"size": {
|
||||
"0": 315,
|
||||
"1": 82
|
||||
},
|
||||
"flags": {},
|
||||
"order": 5,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "number",
|
||||
"type": "*",
|
||||
"link": 31
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "number",
|
||||
"type": "*",
|
||||
"links": [
|
||||
32
|
||||
],
|
||||
"shape": 3,
|
||||
"slot_index": 0
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "LimitNumber"
|
||||
},
|
||||
"widgets_values": [
|
||||
512,
|
||||
4089
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 22,
|
||||
"type": "IntNumber",
|
||||
"pos": [
|
||||
302,
|
||||
-2758
|
||||
],
|
||||
"size": {
|
||||
"0": 315,
|
||||
"1": 130
|
||||
},
|
||||
"flags": {},
|
||||
"order": 0,
|
||||
"mode": 0,
|
||||
"outputs": [
|
||||
{
|
||||
"name": "INT",
|
||||
"type": "INT",
|
||||
"links": [
|
||||
29
|
||||
],
|
||||
"shape": 3,
|
||||
"slot_index": 0
|
||||
}
|
||||
],
|
||||
"title": "Width",
|
||||
"properties": {
|
||||
"Node name for S&R": "IntNumber"
|
||||
},
|
||||
"widgets_values": [
|
||||
1,
|
||||
0,
|
||||
1,
|
||||
1
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 23,
|
||||
"type": "IntNumber",
|
||||
"pos": [
|
||||
299,
|
||||
-2601
|
||||
],
|
||||
"size": {
|
||||
"0": 315,
|
||||
"1": 130
|
||||
},
|
||||
"flags": {},
|
||||
"order": 1,
|
||||
"mode": 0,
|
||||
"outputs": [
|
||||
{
|
||||
"name": "INT",
|
||||
"type": "INT",
|
||||
"links": [
|
||||
31
|
||||
],
|
||||
"shape": 3,
|
||||
"slot_index": 0
|
||||
}
|
||||
],
|
||||
"title": "Height",
|
||||
"properties": {
|
||||
"Node name for S&R": "IntNumber"
|
||||
},
|
||||
"widgets_values": [
|
||||
1,
|
||||
0,
|
||||
1,
|
||||
1
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"type": "CheckpointLoaderSimple",
|
||||
"pos": [
|
||||
567,
|
||||
-2422
|
||||
],
|
||||
"size": {
|
||||
"0": 315,
|
||||
"1": 98
|
||||
},
|
||||
"flags": {},
|
||||
"order": 2,
|
||||
"mode": 0,
|
||||
"outputs": [
|
||||
{
|
||||
"name": "MODEL",
|
||||
"type": "MODEL",
|
||||
"links": [
|
||||
1
|
||||
],
|
||||
"slot_index": 0
|
||||
},
|
||||
{
|
||||
"name": "CLIP",
|
||||
"type": "CLIP",
|
||||
"links": [
|
||||
5,
|
||||
6
|
||||
],
|
||||
"slot_index": 1
|
||||
},
|
||||
{
|
||||
"name": "VAE",
|
||||
"type": "VAE",
|
||||
"links": [
|
||||
8
|
||||
],
|
||||
"slot_index": 2
|
||||
}
|
||||
],
|
||||
"title": "Model",
|
||||
"properties": {
|
||||
"Node name for S&R": "CheckpointLoaderSimple"
|
||||
},
|
||||
"widgets_values": [
|
||||
"deliberate_v2.safetensors"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 1,
|
||||
"type": "KSampler",
|
||||
"pos": [
|
||||
1509,
|
||||
-2394
|
||||
],
|
||||
"size": {
|
||||
"0": 315,
|
||||
"1": 262
|
||||
},
|
||||
"flags": {},
|
||||
"order": 9,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "model",
|
||||
"type": "MODEL",
|
||||
"link": 1
|
||||
},
|
||||
{
|
||||
"name": "positive",
|
||||
"type": "CONDITIONING",
|
||||
"link": 2
|
||||
},
|
||||
{
|
||||
"name": "negative",
|
||||
"type": "CONDITIONING",
|
||||
"link": 3
|
||||
},
|
||||
{
|
||||
"name": "latent_image",
|
||||
"type": "LATENT",
|
||||
"link": 26,
|
||||
"slot_index": 3
|
||||
},
|
||||
{
|
||||
"name": "denoise",
|
||||
"type": "FLOAT",
|
||||
"link": 16,
|
||||
"widget": {
|
||||
"name": "denoise"
|
||||
},
|
||||
"slot_index": 4
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "LATENT",
|
||||
"type": "LATENT",
|
||||
"links": [
|
||||
7
|
||||
],
|
||||
"slot_index": 0
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "KSampler"
|
||||
},
|
||||
"widgets_values": [
|
||||
52336089190516,
|
||||
"randomize",
|
||||
15,
|
||||
6.9,
|
||||
"euler",
|
||||
"karras",
|
||||
0.59
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 16,
|
||||
"type": "PreviewImage",
|
||||
"pos": [
|
||||
2949,
|
||||
-2615
|
||||
],
|
||||
"size": {
|
||||
"0": 210,
|
||||
"1": 246
|
||||
},
|
||||
"flags": {},
|
||||
"order": 13,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "images",
|
||||
"type": "IMAGE",
|
||||
"link": 18
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "PreviewImage"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 4,
|
||||
"type": "CLIPTextEncode",
|
||||
"pos": [
|
||||
1022,
|
||||
-2375
|
||||
],
|
||||
"size": {
|
||||
"0": 422.84503173828125,
|
||||
"1": 164.31304931640625
|
||||
},
|
||||
"flags": {},
|
||||
"order": 6,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "clip",
|
||||
"type": "CLIP",
|
||||
"link": 5
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "CONDITIONING",
|
||||
"type": "CONDITIONING",
|
||||
"links": [
|
||||
2
|
||||
],
|
||||
"slot_index": 0
|
||||
}
|
||||
],
|
||||
"title": "prompt",
|
||||
"properties": {
|
||||
"Node name for S&R": "CLIPTextEncode"
|
||||
},
|
||||
"widgets_values": [
|
||||
"superman,fat cat"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 6,
|
||||
"type": "VAEDecode",
|
||||
"pos": [
|
||||
1867,
|
||||
-2378
|
||||
],
|
||||
"size": {
|
||||
"0": 210,
|
||||
"1": 46
|
||||
},
|
||||
"flags": {
|
||||
"collapsed": false
|
||||
},
|
||||
"order": 10,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "samples",
|
||||
"type": "LATENT",
|
||||
"link": 7
|
||||
},
|
||||
{
|
||||
"name": "vae",
|
||||
"type": "VAE",
|
||||
"link": 8
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "IMAGE",
|
||||
"type": "IMAGE",
|
||||
"links": [
|
||||
33,
|
||||
34
|
||||
],
|
||||
"slot_index": 0
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "VAEDecode"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 26,
|
||||
"type": "AppInfo",
|
||||
"pos": [
|
||||
2134,
|
||||
-2264
|
||||
],
|
||||
"size": {
|
||||
"0": 400,
|
||||
"1": 344.00006103515625
|
||||
},
|
||||
"flags": {},
|
||||
"order": 11,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "LOGO",
|
||||
"type": "IMAGE",
|
||||
"link": 33
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "AppInfo"
|
||||
},
|
||||
"widgets_values": [
|
||||
"Text-to-Image",
|
||||
"4\n22\n23\n2\n\n\n",
|
||||
"16",
|
||||
"演示基本的文生图流程",
|
||||
1,
|
||||
"#comfyui-mixlab-nodes# ",
|
||||
"https://",
|
||||
"",
|
||||
"enable",
|
||||
1
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 14,
|
||||
"type": "FloatSlider",
|
||||
"pos": [
|
||||
1007,
|
||||
-2860
|
||||
],
|
||||
"size": {
|
||||
"0": 315,
|
||||
"1": 130
|
||||
},
|
||||
"flags": {},
|
||||
"order": 3,
|
||||
"mode": 0,
|
||||
"outputs": [
|
||||
{
|
||||
"name": "FLOAT",
|
||||
"type": "FLOAT",
|
||||
"links": [
|
||||
16
|
||||
],
|
||||
"shape": 3,
|
||||
"slot_index": 0
|
||||
}
|
||||
],
|
||||
"title": "denoise",
|
||||
"properties": {
|
||||
"Node name for S&R": "FloatSlider"
|
||||
},
|
||||
"widgets_values": [
|
||||
1,
|
||||
0,
|
||||
1,
|
||||
0.001
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 21,
|
||||
"type": "EmptyLatentImage",
|
||||
"pos": [
|
||||
1004,
|
||||
-2633
|
||||
],
|
||||
"size": {
|
||||
"0": 315,
|
||||
"1": 106
|
||||
},
|
||||
"flags": {},
|
||||
"order": 8,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "width",
|
||||
"type": "INT",
|
||||
"link": 30,
|
||||
"widget": {
|
||||
"name": "width"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "height",
|
||||
"type": "INT",
|
||||
"link": 32,
|
||||
"widget": {
|
||||
"name": "height"
|
||||
}
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "LATENT",
|
||||
"type": "LATENT",
|
||||
"links": [
|
||||
26
|
||||
],
|
||||
"shape": 3
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "EmptyLatentImage"
|
||||
},
|
||||
"widgets_values": [
|
||||
512,
|
||||
512,
|
||||
1
|
||||
]
|
||||
}
|
||||
],
|
||||
"links": [
|
||||
[
|
||||
1,
|
||||
2,
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
"MODEL"
|
||||
],
|
||||
[
|
||||
2,
|
||||
4,
|
||||
0,
|
||||
1,
|
||||
1,
|
||||
"CONDITIONING"
|
||||
],
|
||||
[
|
||||
3,
|
||||
5,
|
||||
0,
|
||||
1,
|
||||
2,
|
||||
"CONDITIONING"
|
||||
],
|
||||
[
|
||||
5,
|
||||
2,
|
||||
1,
|
||||
4,
|
||||
0,
|
||||
"CLIP"
|
||||
],
|
||||
[
|
||||
6,
|
||||
2,
|
||||
1,
|
||||
5,
|
||||
0,
|
||||
"CLIP"
|
||||
],
|
||||
[
|
||||
7,
|
||||
1,
|
||||
0,
|
||||
6,
|
||||
0,
|
||||
"LATENT"
|
||||
],
|
||||
[
|
||||
8,
|
||||
2,
|
||||
2,
|
||||
6,
|
||||
1,
|
||||
"VAE"
|
||||
],
|
||||
[
|
||||
16,
|
||||
14,
|
||||
0,
|
||||
1,
|
||||
4,
|
||||
"FLOAT"
|
||||
],
|
||||
[
|
||||
18,
|
||||
15,
|
||||
0,
|
||||
16,
|
||||
0,
|
||||
"IMAGE"
|
||||
],
|
||||
[
|
||||
26,
|
||||
21,
|
||||
0,
|
||||
1,
|
||||
3,
|
||||
"LATENT"
|
||||
],
|
||||
[
|
||||
29,
|
||||
22,
|
||||
0,
|
||||
24,
|
||||
0,
|
||||
"*"
|
||||
],
|
||||
[
|
||||
30,
|
||||
24,
|
||||
0,
|
||||
21,
|
||||
0,
|
||||
"INT"
|
||||
],
|
||||
[
|
||||
31,
|
||||
23,
|
||||
0,
|
||||
25,
|
||||
0,
|
||||
"*"
|
||||
],
|
||||
[
|
||||
32,
|
||||
25,
|
||||
0,
|
||||
21,
|
||||
1,
|
||||
"INT"
|
||||
],
|
||||
[
|
||||
33,
|
||||
6,
|
||||
0,
|
||||
26,
|
||||
0,
|
||||
"IMAGE"
|
||||
],
|
||||
[
|
||||
34,
|
||||
6,
|
||||
0,
|
||||
15,
|
||||
0,
|
||||
"IMAGE"
|
||||
]
|
||||
],
|
||||
"groups": [],
|
||||
"config": {},
|
||||
"extra": {},
|
||||
"version": 0.4
|
||||
}
|
||||
|
After Width: | Height: | Size: 2.5 MiB |
@@ -0,0 +1,314 @@
|
||||
{
|
||||
"last_node_id": 10,
|
||||
"last_link_id": 16,
|
||||
"nodes": [
|
||||
{
|
||||
"id": 9,
|
||||
"type": "SpeechSynthesis",
|
||||
"pos": [
|
||||
40,
|
||||
356
|
||||
],
|
||||
"size": {
|
||||
"0": 352.8227844238281,
|
||||
"1": 95.3553237915039
|
||||
},
|
||||
"flags": {},
|
||||
"order": 1,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "text",
|
||||
"type": "STRING",
|
||||
"link": 12,
|
||||
"widget": {
|
||||
"name": "text"
|
||||
}
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "STRING",
|
||||
"type": "STRING",
|
||||
"links": [
|
||||
13,
|
||||
16
|
||||
],
|
||||
"shape": 6,
|
||||
"slot_index": 0
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "SpeechSynthesis"
|
||||
},
|
||||
"widgets_values": [
|
||||
"作为一个人工智能助手,我没有性别,也无法进行社交互动。我的主要任务是为用户提供帮助和信息。请问有什么问题我可以为您解答吗?"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 8,
|
||||
"type": "SpeechRecognition",
|
||||
"pos": [
|
||||
537,
|
||||
321
|
||||
],
|
||||
"size": {
|
||||
"0": 228.5580291748047,
|
||||
"1": 239.85951232910156
|
||||
},
|
||||
"flags": {},
|
||||
"order": 3,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "start_by",
|
||||
"type": "INT",
|
||||
"link": 14,
|
||||
"widget": {
|
||||
"name": "start_by"
|
||||
}
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "prompt",
|
||||
"type": "STRING",
|
||||
"links": [
|
||||
15
|
||||
],
|
||||
"shape": 3,
|
||||
"slot_index": 0
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "SpeechRecognition"
|
||||
},
|
||||
"widgets_values": [
|
||||
null,
|
||||
1,
|
||||
4,
|
||||
null
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 4,
|
||||
"type": "ChatGPTOpenAI",
|
||||
"pos": [
|
||||
-403,
|
||||
14
|
||||
],
|
||||
"size": {
|
||||
"0": 400,
|
||||
"1": 358
|
||||
},
|
||||
"flags": {},
|
||||
"order": 0,
|
||||
"mode": 0,
|
||||
"outputs": [
|
||||
{
|
||||
"name": "text",
|
||||
"type": "STRING",
|
||||
"links": [
|
||||
12
|
||||
],
|
||||
"shape": 3,
|
||||
"slot_index": 0
|
||||
},
|
||||
{
|
||||
"name": "messages",
|
||||
"type": "STRING",
|
||||
"links": null,
|
||||
"shape": 3
|
||||
},
|
||||
{
|
||||
"name": "session_history",
|
||||
"type": "STRING",
|
||||
"links": null,
|
||||
"shape": 3
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "ChatGPTOpenAI"
|
||||
},
|
||||
"widgets_values": [
|
||||
null,
|
||||
null,
|
||||
"跟你一样泡妞",
|
||||
"You are ChatGPT, a large language model trained by OpenAI. Answer as concisely as possible.",
|
||||
"gpt-3.5-turbo",
|
||||
354,
|
||||
"randomize",
|
||||
1,
|
||||
null
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 10,
|
||||
"type": "ShowTextForGPT",
|
||||
"pos": [
|
||||
867,
|
||||
349
|
||||
],
|
||||
"size": {
|
||||
"0": 315,
|
||||
"1": 76.00000762939453
|
||||
},
|
||||
"flags": {},
|
||||
"order": 4,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "text",
|
||||
"type": "STRING",
|
||||
"link": 15,
|
||||
"widget": {
|
||||
"name": "text"
|
||||
}
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "STRING",
|
||||
"type": "STRING",
|
||||
"links": null,
|
||||
"shape": 6
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "ShowTextForGPT"
|
||||
},
|
||||
"widgets_values": [
|
||||
"纽斯"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 5,
|
||||
"type": "DynamicDelayProcessor",
|
||||
"pos": [
|
||||
496,
|
||||
4
|
||||
],
|
||||
"size": {
|
||||
"0": 400,
|
||||
"1": 200
|
||||
},
|
||||
"flags": {},
|
||||
"order": 2,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "any_input",
|
||||
"type": "*",
|
||||
"link": 13
|
||||
},
|
||||
{
|
||||
"name": "delay_by_text",
|
||||
"type": "STRING",
|
||||
"link": 16,
|
||||
"widget": {
|
||||
"name": "delay_by_text"
|
||||
}
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "output",
|
||||
"type": "*",
|
||||
"links": [
|
||||
14
|
||||
],
|
||||
"shape": 3,
|
||||
"slot_index": 0
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "DynamicDelayProcessor"
|
||||
},
|
||||
"widgets_values": [
|
||||
1,
|
||||
"",
|
||||
2.5,
|
||||
"enable",
|
||||
1
|
||||
]
|
||||
}
|
||||
],
|
||||
"links": [
|
||||
[
|
||||
4,
|
||||
2,
|
||||
0,
|
||||
5,
|
||||
0,
|
||||
"*"
|
||||
],
|
||||
[
|
||||
7,
|
||||
4,
|
||||
0,
|
||||
6,
|
||||
0,
|
||||
"STRING"
|
||||
],
|
||||
[
|
||||
8,
|
||||
6,
|
||||
0,
|
||||
5,
|
||||
1,
|
||||
"STRING"
|
||||
],
|
||||
[
|
||||
9,
|
||||
5,
|
||||
0,
|
||||
7,
|
||||
0,
|
||||
"INT"
|
||||
],
|
||||
[
|
||||
12,
|
||||
4,
|
||||
0,
|
||||
9,
|
||||
0,
|
||||
"STRING"
|
||||
],
|
||||
[
|
||||
13,
|
||||
9,
|
||||
0,
|
||||
5,
|
||||
0,
|
||||
"*"
|
||||
],
|
||||
[
|
||||
14,
|
||||
5,
|
||||
0,
|
||||
8,
|
||||
0,
|
||||
"INT"
|
||||
],
|
||||
[
|
||||
15,
|
||||
8,
|
||||
0,
|
||||
10,
|
||||
0,
|
||||
"STRING"
|
||||
],
|
||||
[
|
||||
16,
|
||||
9,
|
||||
0,
|
||||
5,
|
||||
1,
|
||||
"STRING"
|
||||
]
|
||||
],
|
||||
"groups": [],
|
||||
"config": {},
|
||||
"extra": {},
|
||||
"version": 0.4
|
||||
}
|
||||
@@ -0,0 +1,558 @@
|
||||
{
|
||||
"last_node_id": 69,
|
||||
"last_link_id": 72,
|
||||
"nodes": [
|
||||
{
|
||||
"id": 37,
|
||||
"type": "CLIPTextEncode",
|
||||
"pos": [
|
||||
6705,
|
||||
-216
|
||||
],
|
||||
"size": {
|
||||
"0": 400,
|
||||
"1": 200
|
||||
},
|
||||
"flags": {},
|
||||
"order": 5,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "clip",
|
||||
"type": "CLIP",
|
||||
"link": 33
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "CONDITIONING",
|
||||
"type": "CONDITIONING",
|
||||
"links": [
|
||||
34
|
||||
],
|
||||
"shape": 3
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "CLIPTextEncode"
|
||||
},
|
||||
"widgets_values": [
|
||||
"beautiful scenery nature glass bottle landscape, , purple galaxy bottle,"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 5,
|
||||
"type": "CLIPTextEncode",
|
||||
"pos": [
|
||||
6693,
|
||||
61
|
||||
],
|
||||
"size": {
|
||||
"0": 425.27801513671875,
|
||||
"1": 180.6060791015625
|
||||
},
|
||||
"flags": {},
|
||||
"order": 4,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "clip",
|
||||
"type": "CLIP",
|
||||
"link": 6
|
||||
},
|
||||
{
|
||||
"name": "text",
|
||||
"type": "STRING",
|
||||
"link": 25,
|
||||
"widget": {
|
||||
"name": "text"
|
||||
}
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "CONDITIONING",
|
||||
"type": "CONDITIONING",
|
||||
"links": [
|
||||
3
|
||||
],
|
||||
"slot_index": 0
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "CLIPTextEncode"
|
||||
},
|
||||
"widgets_values": [
|
||||
"text, watermark"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"type": "EmptyLatentImage",
|
||||
"pos": [
|
||||
6689,
|
||||
306
|
||||
],
|
||||
"size": {
|
||||
"0": 315,
|
||||
"1": 106
|
||||
},
|
||||
"flags": {},
|
||||
"order": 0,
|
||||
"mode": 0,
|
||||
"outputs": [
|
||||
{
|
||||
"name": "LATENT",
|
||||
"type": "LATENT",
|
||||
"links": [
|
||||
4
|
||||
],
|
||||
"slot_index": 0
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "EmptyLatentImage"
|
||||
},
|
||||
"widgets_values": [
|
||||
512,
|
||||
512,
|
||||
1
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 27,
|
||||
"type": "EmbeddingPrompt",
|
||||
"pos": [
|
||||
6104,
|
||||
23
|
||||
],
|
||||
"size": {
|
||||
"0": 399.6408996582031,
|
||||
"1": 82
|
||||
},
|
||||
"flags": {},
|
||||
"order": 1,
|
||||
"mode": 0,
|
||||
"outputs": [
|
||||
{
|
||||
"name": "STRING",
|
||||
"type": "STRING",
|
||||
"links": [
|
||||
25
|
||||
],
|
||||
"shape": 3
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "EmbeddingPrompt"
|
||||
},
|
||||
"widgets_values": [
|
||||
"negative-embed-verybadimagenegative_v1.3",
|
||||
1
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 67,
|
||||
"type": "VAEDecode",
|
||||
"pos": [
|
||||
7551,
|
||||
-184
|
||||
],
|
||||
"size": {
|
||||
"0": 210,
|
||||
"1": 46
|
||||
},
|
||||
"flags": {},
|
||||
"order": 7,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "samples",
|
||||
"type": "LATENT",
|
||||
"link": 70
|
||||
},
|
||||
{
|
||||
"name": "vae",
|
||||
"type": "VAE",
|
||||
"link": 69
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "IMAGE",
|
||||
"type": "IMAGE",
|
||||
"links": [
|
||||
71
|
||||
],
|
||||
"shape": 3
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "VAEDecode"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 1,
|
||||
"type": "KSampler",
|
||||
"pos": [
|
||||
7187,
|
||||
-145
|
||||
],
|
||||
"size": {
|
||||
"0": 315,
|
||||
"1": 262
|
||||
},
|
||||
"flags": {},
|
||||
"order": 6,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "model",
|
||||
"type": "MODEL",
|
||||
"link": 1
|
||||
},
|
||||
{
|
||||
"name": "positive",
|
||||
"type": "CONDITIONING",
|
||||
"link": 34
|
||||
},
|
||||
{
|
||||
"name": "negative",
|
||||
"type": "CONDITIONING",
|
||||
"link": 3
|
||||
},
|
||||
{
|
||||
"name": "latent_image",
|
||||
"type": "LATENT",
|
||||
"link": 4
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "LATENT",
|
||||
"type": "LATENT",
|
||||
"links": [
|
||||
70
|
||||
],
|
||||
"slot_index": 0
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "KSampler"
|
||||
},
|
||||
"widgets_values": [
|
||||
408562451564429,
|
||||
"fixed",
|
||||
20,
|
||||
8,
|
||||
"euler",
|
||||
"normal",
|
||||
1
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 61,
|
||||
"type": "PromptImage",
|
||||
"pos": [
|
||||
7853,
|
||||
-238
|
||||
],
|
||||
"size": [
|
||||
465.6378949342379,
|
||||
760.4568424013569
|
||||
],
|
||||
"flags": {},
|
||||
"order": 8,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "images",
|
||||
"type": "IMAGE",
|
||||
"link": 71
|
||||
},
|
||||
{
|
||||
"name": "prompts",
|
||||
"type": "STRING",
|
||||
"link": 72,
|
||||
"widget": {
|
||||
"name": "prompts"
|
||||
}
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "PromptImage"
|
||||
},
|
||||
"widgets_values": [
|
||||
"",
|
||||
"disable",
|
||||
{
|
||||
"_images": [
|
||||
[
|
||||
{
|
||||
"filename": "mixlab_PromptImage_0_00027_.png",
|
||||
"subfolder": "",
|
||||
"type": "output"
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"filename": "mixlab_PromptImage_1_00028_.png",
|
||||
"subfolder": "",
|
||||
"type": "output"
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"filename": "mixlab_PromptImage_2_00029_.png",
|
||||
"subfolder": "",
|
||||
"type": "output"
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"filename": "mixlab_PromptImage_3_00030_.png",
|
||||
"subfolder": "",
|
||||
"type": "output"
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"filename": "mixlab_PromptImage_4_00031_.png",
|
||||
"subfolder": "",
|
||||
"type": "output"
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"filename": "mixlab_PromptImage_5_00032_.png",
|
||||
"subfolder": "",
|
||||
"type": "output"
|
||||
}
|
||||
]
|
||||
],
|
||||
"prompts": [
|
||||
"512-inpainting-ema.safetensors",
|
||||
"SSD-1B.safetensors",
|
||||
"awportrait_v12.safetensors",
|
||||
"cardosAnime_v20.safetensors",
|
||||
"deliberate_v2.safetensors",
|
||||
"gameIconInstitute_v40.safetensors"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"type": "CheckpointLoaderSimple",
|
||||
"pos": [
|
||||
6105,
|
||||
-139
|
||||
],
|
||||
"size": {
|
||||
"0": 315,
|
||||
"1": 98
|
||||
},
|
||||
"flags": {},
|
||||
"order": 3,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "ckpt_name",
|
||||
"type": [
|
||||
"512-inpainting-ema.safetensors",
|
||||
"SSD-1B.safetensors",
|
||||
"awportrait_v12.safetensors",
|
||||
"cardosAnime_v20.safetensors",
|
||||
"deliberate_v2.safetensors",
|
||||
"gameIconInstitute_v40.safetensors",
|
||||
"illuminatiDiffusionV1_v11-unclip-h-fp16.safetensors",
|
||||
"sd_xl_turbo_1.0_fp16.safetensors",
|
||||
"svd.safetensors"
|
||||
],
|
||||
"link": 64,
|
||||
"widget": {
|
||||
"name": "ckpt_name"
|
||||
}
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "MODEL",
|
||||
"type": "MODEL",
|
||||
"links": [
|
||||
1
|
||||
],
|
||||
"slot_index": 0
|
||||
},
|
||||
{
|
||||
"name": "CLIP",
|
||||
"type": "CLIP",
|
||||
"links": [
|
||||
6,
|
||||
33
|
||||
],
|
||||
"slot_index": 1
|
||||
},
|
||||
{
|
||||
"name": "VAE",
|
||||
"type": "VAE",
|
||||
"links": [
|
||||
69
|
||||
],
|
||||
"slot_index": 2
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "CheckpointLoaderSimple"
|
||||
},
|
||||
"widgets_values": [
|
||||
"deliberate_v2.safetensors"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 56,
|
||||
"type": "CkptNames_",
|
||||
"pos": [
|
||||
7860,
|
||||
-494
|
||||
],
|
||||
"size": {
|
||||
"0": 400,
|
||||
"1": 200
|
||||
},
|
||||
"flags": {},
|
||||
"order": 2,
|
||||
"mode": 0,
|
||||
"outputs": [
|
||||
{
|
||||
"name": "ckpt_names",
|
||||
"type": "*",
|
||||
"links": [
|
||||
64,
|
||||
72
|
||||
],
|
||||
"shape": 6,
|
||||
"slot_index": 0
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "CkptNames_"
|
||||
},
|
||||
"widgets_values": [
|
||||
"512-inpainting-ema.safetensors\nSSD-1B.safetensors\nawportrait_v12.safetensors\ncardosAnime_v20.safetensors\ndeliberate_v2.safetensors\ngameIconInstitute_v40.safetensors"
|
||||
]
|
||||
}
|
||||
],
|
||||
"links": [
|
||||
[
|
||||
1,
|
||||
2,
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
"MODEL"
|
||||
],
|
||||
[
|
||||
3,
|
||||
5,
|
||||
0,
|
||||
1,
|
||||
2,
|
||||
"CONDITIONING"
|
||||
],
|
||||
[
|
||||
4,
|
||||
3,
|
||||
0,
|
||||
1,
|
||||
3,
|
||||
"LATENT"
|
||||
],
|
||||
[
|
||||
6,
|
||||
2,
|
||||
1,
|
||||
5,
|
||||
0,
|
||||
"CLIP"
|
||||
],
|
||||
[
|
||||
25,
|
||||
27,
|
||||
0,
|
||||
5,
|
||||
1,
|
||||
"STRING"
|
||||
],
|
||||
[
|
||||
33,
|
||||
2,
|
||||
1,
|
||||
37,
|
||||
0,
|
||||
"CLIP"
|
||||
],
|
||||
[
|
||||
34,
|
||||
37,
|
||||
0,
|
||||
1,
|
||||
1,
|
||||
"CONDITIONING"
|
||||
],
|
||||
[
|
||||
64,
|
||||
56,
|
||||
0,
|
||||
2,
|
||||
0,
|
||||
[
|
||||
"512-inpainting-ema.safetensors",
|
||||
"SSD-1B.safetensors",
|
||||
"awportrait_v12.safetensors",
|
||||
"cardosAnime_v20.safetensors",
|
||||
"deliberate_v2.safetensors",
|
||||
"gameIconInstitute_v40.safetensors",
|
||||
"illuminatiDiffusionV1_v11-unclip-h-fp16.safetensors",
|
||||
"sd_xl_turbo_1.0_fp16.safetensors",
|
||||
"svd.safetensors"
|
||||
]
|
||||
],
|
||||
[
|
||||
69,
|
||||
2,
|
||||
2,
|
||||
67,
|
||||
1,
|
||||
"VAE"
|
||||
],
|
||||
[
|
||||
70,
|
||||
1,
|
||||
0,
|
||||
67,
|
||||
0,
|
||||
"LATENT"
|
||||
],
|
||||
[
|
||||
71,
|
||||
67,
|
||||
0,
|
||||
61,
|
||||
0,
|
||||
"IMAGE"
|
||||
],
|
||||
[
|
||||
72,
|
||||
56,
|
||||
0,
|
||||
61,
|
||||
1,
|
||||
"STRING"
|
||||
]
|
||||
],
|
||||
"groups": [],
|
||||
"config": {},
|
||||
"extra": {},
|
||||
"version": 0.4
|
||||
}
|
||||
@@ -0,0 +1,828 @@
|
||||
{
|
||||
"last_node_id": 23,
|
||||
"last_link_id": 0,
|
||||
"nodes": [
|
||||
{
|
||||
"id": 7,
|
||||
"type": "ResizeImageMixlab",
|
||||
"pos": [
|
||||
1096.229866976564,
|
||||
731.8207909156248
|
||||
],
|
||||
"size": {
|
||||
"0": 315,
|
||||
"1": 106
|
||||
},
|
||||
"flags": {},
|
||||
"order": 0,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "image",
|
||||
"type": "IMAGE",
|
||||
"link": null
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "IMAGE",
|
||||
"type": "IMAGE",
|
||||
"links": null,
|
||||
"shape": 3
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "ResizeImageMixlab"
|
||||
},
|
||||
"widgets_values": [
|
||||
512,
|
||||
512,
|
||||
"width"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 8,
|
||||
"type": "LoadImagesFromPath",
|
||||
"pos": [
|
||||
1101.229866976564,
|
||||
900.8207909156248
|
||||
],
|
||||
"size": {
|
||||
"0": 315,
|
||||
"1": 238
|
||||
},
|
||||
"flags": {},
|
||||
"order": 1,
|
||||
"mode": 0,
|
||||
"outputs": [
|
||||
{
|
||||
"name": "IMAGE",
|
||||
"type": "IMAGE",
|
||||
"links": null,
|
||||
"shape": 6
|
||||
},
|
||||
{
|
||||
"name": "MASK",
|
||||
"type": "MASK",
|
||||
"links": null,
|
||||
"shape": 6
|
||||
},
|
||||
{
|
||||
"name": "STRING",
|
||||
"type": "STRING",
|
||||
"links": null,
|
||||
"shape": 3
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "LoadImagesFromPath"
|
||||
},
|
||||
"widgets_values": [
|
||||
"C:\\Users\\38957\\Documents\\GitHub\\extract-anything\\outputs",
|
||||
"disable",
|
||||
"enable",
|
||||
0,
|
||||
"disable",
|
||||
null,
|
||||
null
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"type": "ShowTextForGPT",
|
||||
"pos": [
|
||||
1786,
|
||||
252
|
||||
],
|
||||
"size": {
|
||||
"0": 315,
|
||||
"1": 58
|
||||
},
|
||||
"flags": {},
|
||||
"order": 2,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "text",
|
||||
"type": "STRING",
|
||||
"link": null,
|
||||
"widget": {
|
||||
"name": "text"
|
||||
}
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "STRING",
|
||||
"type": "STRING",
|
||||
"links": null,
|
||||
"shape": 6
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "ShowTextForGPT"
|
||||
},
|
||||
"widgets_values": [
|
||||
""
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 9,
|
||||
"type": "TextImage",
|
||||
"pos": [
|
||||
1448.4608623625004,
|
||||
735.0514200906249
|
||||
],
|
||||
"size": {
|
||||
"0": 315,
|
||||
"1": 198
|
||||
},
|
||||
"flags": {},
|
||||
"order": 3,
|
||||
"mode": 0,
|
||||
"outputs": [
|
||||
{
|
||||
"name": "IMAGE",
|
||||
"type": "IMAGE",
|
||||
"links": null,
|
||||
"shape": 3
|
||||
},
|
||||
{
|
||||
"name": "MASK",
|
||||
"type": "MASK",
|
||||
"links": null,
|
||||
"shape": 3
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "TextImage"
|
||||
},
|
||||
"widgets_values": [
|
||||
"龍馬精神迎新歲",
|
||||
"C:\\Users\\38957\\Documents\\ai-lab\\ComfyUI_windows_portable\\ComfyUI\\custom_nodes\\comfyui-mixlab-nodes\\assets\\王汉宗颜楷体繁.ttf",
|
||||
100,
|
||||
12,
|
||||
"#000000",
|
||||
true
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"type": "ChatGPTOpenAI",
|
||||
"pos": [
|
||||
1086,
|
||||
235
|
||||
],
|
||||
"size": {
|
||||
"0": 400,
|
||||
"1": 358
|
||||
},
|
||||
"flags": {},
|
||||
"order": 4,
|
||||
"mode": 0,
|
||||
"outputs": [
|
||||
{
|
||||
"name": "text",
|
||||
"type": "STRING",
|
||||
"links": null,
|
||||
"shape": 3
|
||||
},
|
||||
{
|
||||
"name": "messages",
|
||||
"type": "STRING",
|
||||
"links": null,
|
||||
"shape": 3
|
||||
},
|
||||
{
|
||||
"name": "session_history",
|
||||
"type": "STRING",
|
||||
"links": null,
|
||||
"shape": 3
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "ChatGPTOpenAI"
|
||||
},
|
||||
"widgets_values": [
|
||||
null,
|
||||
null,
|
||||
"",
|
||||
"You are ChatGPT, a large language model trained by OpenAI. Answer as concisely as possible.",
|
||||
"gpt-3.5-turbo",
|
||||
0,
|
||||
"randomize",
|
||||
1,
|
||||
null
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 16,
|
||||
"type": "SpeechRecognition",
|
||||
"pos": [
|
||||
2892.502226325001,
|
||||
724.176680771093
|
||||
],
|
||||
"size": {
|
||||
"0": 322.593505859375,
|
||||
"1": 135.22850036621094
|
||||
},
|
||||
"flags": {},
|
||||
"order": 5,
|
||||
"mode": 0,
|
||||
"outputs": [
|
||||
{
|
||||
"name": "prompt",
|
||||
"type": "STRING",
|
||||
"links": null,
|
||||
"shape": 3
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "SpeechRecognition"
|
||||
},
|
||||
"widgets_values": [
|
||||
null,
|
||||
null
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 17,
|
||||
"type": "SpeechSynthesis",
|
||||
"pos": [
|
||||
2901.502226325001,
|
||||
924.1766807710948
|
||||
],
|
||||
"size": {
|
||||
"0": 315,
|
||||
"1": 58
|
||||
},
|
||||
"flags": {},
|
||||
"order": 6,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "text",
|
||||
"type": "STRING",
|
||||
"link": null,
|
||||
"widget": {
|
||||
"name": "text"
|
||||
}
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "STRING",
|
||||
"type": "STRING",
|
||||
"links": null,
|
||||
"shape": 6
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "SpeechSynthesis"
|
||||
},
|
||||
"widgets_values": [
|
||||
""
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 18,
|
||||
"type": "ShowLayer",
|
||||
"pos": [
|
||||
2157.913220668749,
|
||||
232.7887044208986
|
||||
],
|
||||
"size": {
|
||||
"0": 315,
|
||||
"1": 226
|
||||
},
|
||||
"flags": {},
|
||||
"order": 7,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "layers",
|
||||
"type": "LAYER",
|
||||
"link": null
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "ShowLayer"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 19,
|
||||
"type": "NewLayer",
|
||||
"pos": [
|
||||
2494.913220668749,
|
||||
232.7887044208986
|
||||
],
|
||||
"size": {
|
||||
"0": 315,
|
||||
"1": 218
|
||||
},
|
||||
"flags": {},
|
||||
"order": 8,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "image",
|
||||
"type": "IMAGE",
|
||||
"link": null
|
||||
},
|
||||
{
|
||||
"name": "mask",
|
||||
"type": "MASK",
|
||||
"link": null
|
||||
},
|
||||
{
|
||||
"name": "layers",
|
||||
"type": "LAYER",
|
||||
"link": null
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "layers",
|
||||
"type": "LAYER",
|
||||
"links": null,
|
||||
"shape": 6
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "NewLayer"
|
||||
},
|
||||
"widgets_values": [
|
||||
0,
|
||||
0,
|
||||
512,
|
||||
512,
|
||||
0,
|
||||
"width"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 20,
|
||||
"type": "MergeLayers",
|
||||
"pos": [
|
||||
2163.913220668749,
|
||||
515.7887044208991
|
||||
],
|
||||
"size": {
|
||||
"0": 315.47509765625,
|
||||
"1": 69.53152465820312
|
||||
},
|
||||
"flags": {},
|
||||
"order": 9,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "layers",
|
||||
"type": "LAYER",
|
||||
"link": null
|
||||
},
|
||||
{
|
||||
"name": "image",
|
||||
"type": "IMAGE",
|
||||
"link": null
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "IMAGE",
|
||||
"type": "IMAGE",
|
||||
"links": null,
|
||||
"shape": 3
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "MergeLayers"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 4,
|
||||
"type": "CharacterInText",
|
||||
"pos": [
|
||||
1778,
|
||||
386
|
||||
],
|
||||
"size": {
|
||||
"0": 319.45068359375,
|
||||
"1": 194.45606994628906
|
||||
},
|
||||
"flags": {},
|
||||
"order": 10,
|
||||
"mode": 0,
|
||||
"outputs": [
|
||||
{
|
||||
"name": "INT",
|
||||
"type": "INT",
|
||||
"links": null,
|
||||
"shape": 3
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "CharacterInText"
|
||||
},
|
||||
"widgets_values": [
|
||||
"",
|
||||
"",
|
||||
1
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 21,
|
||||
"type": "Note",
|
||||
"pos": [
|
||||
2507.333181606249,
|
||||
517.1286262958992
|
||||
],
|
||||
"size": {
|
||||
"0": 210,
|
||||
"1": 58
|
||||
},
|
||||
"flags": {},
|
||||
"order": 11,
|
||||
"mode": 0,
|
||||
"properties": {
|
||||
"text": ""
|
||||
},
|
||||
"widgets_values": [
|
||||
"TODO:可视化操作"
|
||||
],
|
||||
"color": "#323",
|
||||
"bgcolor": "#535"
|
||||
},
|
||||
{
|
||||
"id": 22,
|
||||
"type": "Note",
|
||||
"pos": [
|
||||
1533,
|
||||
347
|
||||
],
|
||||
"size": {
|
||||
"0": 210,
|
||||
"1": 58
|
||||
},
|
||||
"flags": {},
|
||||
"order": 12,
|
||||
"mode": 0,
|
||||
"properties": {
|
||||
"text": ""
|
||||
},
|
||||
"widgets_values": [
|
||||
"funtion call"
|
||||
],
|
||||
"color": "#323",
|
||||
"bgcolor": "#535"
|
||||
},
|
||||
{
|
||||
"id": 11,
|
||||
"type": "3DImage",
|
||||
"pos": [
|
||||
1794.6918193937495,
|
||||
724.2820719460937
|
||||
],
|
||||
"size": {
|
||||
"0": 309.2974548339844,
|
||||
"1": 433.12554931640625
|
||||
},
|
||||
"flags": {},
|
||||
"order": 13,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "material",
|
||||
"type": "IMAGE",
|
||||
"link": null
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "IMAGE",
|
||||
"type": "IMAGE",
|
||||
"links": null,
|
||||
"shape": 3
|
||||
},
|
||||
{
|
||||
"name": "MASK",
|
||||
"type": "MASK",
|
||||
"links": null,
|
||||
"shape": 3
|
||||
},
|
||||
{
|
||||
"name": "BG_IMAGE",
|
||||
"type": "IMAGE",
|
||||
"links": null,
|
||||
"shape": 3
|
||||
},
|
||||
{
|
||||
"name": "MATERIAL",
|
||||
"type": "IMAGE",
|
||||
"links": null,
|
||||
"shape": 3
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "3DImage"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 13,
|
||||
"type": "ScreenShare",
|
||||
"pos": [
|
||||
2138.691819393749,
|
||||
728.2820719460937
|
||||
],
|
||||
"size": {
|
||||
"0": 322.9458923339844,
|
||||
"1": 594.0604858398438
|
||||
},
|
||||
"flags": {},
|
||||
"order": 14,
|
||||
"mode": 0,
|
||||
"outputs": [
|
||||
{
|
||||
"name": "IMAGE",
|
||||
"type": "IMAGE",
|
||||
"links": null,
|
||||
"shape": 3
|
||||
},
|
||||
{
|
||||
"name": "PROMPT",
|
||||
"type": "STRING",
|
||||
"links": null,
|
||||
"shape": 3
|
||||
},
|
||||
{
|
||||
"name": "FLOAT",
|
||||
"type": "FLOAT",
|
||||
"links": null,
|
||||
"shape": 3
|
||||
},
|
||||
{
|
||||
"name": "INT",
|
||||
"type": "INT",
|
||||
"links": null,
|
||||
"shape": 3
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "ScreenShare"
|
||||
},
|
||||
"widgets_values": [
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 14,
|
||||
"type": "FloatingVideo",
|
||||
"pos": [
|
||||
2486.691819393749,
|
||||
726.2820719460937
|
||||
],
|
||||
"size": {
|
||||
"0": 302.3232421875,
|
||||
"1": 303.61279296875
|
||||
},
|
||||
"flags": {},
|
||||
"order": 15,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "images",
|
||||
"type": "IMAGE",
|
||||
"link": null
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "FloatingVideo"
|
||||
},
|
||||
"widgets_values": [
|
||||
null
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 6,
|
||||
"type": "TransparentImage",
|
||||
"pos": [
|
||||
2496.691819393749,
|
||||
1157.2820719460938
|
||||
],
|
||||
"size": {
|
||||
"0": 315,
|
||||
"1": 146
|
||||
},
|
||||
"flags": {},
|
||||
"order": 16,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "images",
|
||||
"type": "IMAGE",
|
||||
"link": null
|
||||
},
|
||||
{
|
||||
"name": "masks",
|
||||
"type": "MASK",
|
||||
"link": null
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "file_path",
|
||||
"type": "STRING",
|
||||
"links": null,
|
||||
"shape": 6
|
||||
},
|
||||
{
|
||||
"name": "IMAGE",
|
||||
"type": "IMAGE",
|
||||
"links": null,
|
||||
"shape": 6
|
||||
},
|
||||
{
|
||||
"name": "RGBA",
|
||||
"type": "RGBA",
|
||||
"links": null,
|
||||
"shape": 6
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "TransparentImage"
|
||||
},
|
||||
"widgets_values": [
|
||||
"yes",
|
||||
"yes",
|
||||
"Mixlab_save"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 12,
|
||||
"type": "ImageCropByAlpha",
|
||||
"pos": [
|
||||
1806.6918193937495,
|
||||
1240.2820719460938
|
||||
],
|
||||
"size": {
|
||||
"0": 289.27215576171875,
|
||||
"1": 46
|
||||
},
|
||||
"flags": {},
|
||||
"order": 17,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "image",
|
||||
"type": "IMAGE",
|
||||
"link": null
|
||||
},
|
||||
{
|
||||
"name": "RGBA",
|
||||
"type": "RGBA",
|
||||
"link": null
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "IMAGE",
|
||||
"type": "IMAGE",
|
||||
"links": null,
|
||||
"shape": 3
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "ImageCropByAlpha"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 5,
|
||||
"type": "EnhanceImage",
|
||||
"pos": [
|
||||
1098.6918193937504,
|
||||
1217.2820719460938
|
||||
],
|
||||
"size": {
|
||||
"0": 315,
|
||||
"1": 58
|
||||
},
|
||||
"flags": {},
|
||||
"order": 18,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "image",
|
||||
"type": "IMAGE",
|
||||
"link": null
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "IMAGE",
|
||||
"type": "IMAGE",
|
||||
"links": null,
|
||||
"shape": 3
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "EnhanceImage"
|
||||
},
|
||||
"widgets_values": [
|
||||
0.5
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 10,
|
||||
"type": "SvgImage",
|
||||
"pos": [
|
||||
1454.6918193937504,
|
||||
1074.2820719460938
|
||||
],
|
||||
"size": {
|
||||
"0": 315,
|
||||
"1": 170
|
||||
},
|
||||
"flags": {},
|
||||
"order": 19,
|
||||
"mode": 0,
|
||||
"outputs": [
|
||||
{
|
||||
"name": "IMAGE",
|
||||
"type": "IMAGE",
|
||||
"links": null,
|
||||
"shape": 3
|
||||
},
|
||||
{
|
||||
"name": "layers",
|
||||
"type": "LAYER",
|
||||
"links": null,
|
||||
"shape": 6
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "SvgImage"
|
||||
},
|
||||
"widgets_values": [
|
||||
null,
|
||||
null
|
||||
]
|
||||
}
|
||||
],
|
||||
"links": [],
|
||||
"groups": [
|
||||
{
|
||||
"title": "GPT",
|
||||
"bounding": [
|
||||
1083,
|
||||
157,
|
||||
1039,
|
||||
450
|
||||
],
|
||||
"color": "#b06634",
|
||||
"font_size": 24,
|
||||
"locked": false
|
||||
},
|
||||
{
|
||||
"title": "Image",
|
||||
"bounding": [
|
||||
1086,
|
||||
650,
|
||||
1735,
|
||||
682
|
||||
],
|
||||
"color": "#A88",
|
||||
"font_size": 24,
|
||||
"locked": false
|
||||
},
|
||||
{
|
||||
"title": "Audio",
|
||||
"bounding": [
|
||||
2882,
|
||||
650,
|
||||
344,
|
||||
342
|
||||
],
|
||||
"color": "#8A8",
|
||||
"font_size": 24,
|
||||
"locked": false
|
||||
},
|
||||
{
|
||||
"title": "Layer",
|
||||
"bounding": [
|
||||
2148,
|
||||
158,
|
||||
672,
|
||||
437
|
||||
],
|
||||
"color": "#a1309b",
|
||||
"font_size": 24,
|
||||
"locked": false
|
||||
}
|
||||
],
|
||||
"config": {},
|
||||
"extra": {},
|
||||
"version": 0.4
|
||||
}
|
||||