From cfb3b237cf64b512414a17f71e6d89c3355aa8ef Mon Sep 17 00:00:00 2001 From: melMass Date: Sat, 5 Aug 2023 13:08:18 +0200 Subject: [PATCH 01/20] =?UTF-8?q?revertible:=20=F0=9F=92=84=20use=20BOOLEA?= =?UTF-8?q?N=20instead=20of=20BOOL?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Since this commit: https://github.com/comfyanonymous/ComfyUI/commit/9534f0f8a5a026654492da378f84d2cdc589ed01 Input <-> widget is possible on booleans. Locally I edited it but forgot about it not being in comfy This commit is reversable since I'm not yet sure of all the impacts --- .../03-animation_builder-condition-lerp.json | 1249 +---------------- examples/04-animation_builder-deforum.json | 1190 +--------------- nodes/animation.py | 2 +- nodes/deep_bump.py | 2 +- nodes/faceenhance.py | 6 +- nodes/faceswap.py | 5 +- nodes/fun.py | 2 +- nodes/graph_utils.py | 2 +- nodes/image_interpolation.py | 4 +- nodes/image_processing.py | 6 +- nodes/io.py | 3 +- nodes/mask.py | 4 +- nodes/number.py | 2 +- web/mtb_widgets.js | 111 +- 14 files changed, 21 insertions(+), 2567 deletions(-) diff --git a/examples/03-animation_builder-condition-lerp.json b/examples/03-animation_builder-condition-lerp.json index 7262e17..531e312 100644 --- a/examples/03-animation_builder-condition-lerp.json +++ b/examples/03-animation_builder-condition-lerp.json @@ -1,1248 +1 @@ -{ - "last_node_id": 82, - "last_link_id": 171, - "nodes": [ - { - "id": 59, - "type": "Reroute", - "pos": [ - -150.35178124999982, - 644.4360633544919 - ], - "size": [ - 75, - 26 - ], - "flags": {}, - "order": 13, - "mode": 0, - "inputs": [ - { - "name": "", - "type": "*", - "link": 124 - } - ], - "outputs": [ - { - "name": "", - "type": "VAE", - "links": [ - 139 - ], - "slot_index": 0 - } - ], - "properties": { - "showOutputText": false, - "horizontal": false - } - }, - { - "id": 56, - "type": "Reroute", - "pos": [ - -1580.8297949218763, - 644.7740239257807 - ], - "size": [ - 75, - 26 - ], - "flags": {}, - "order": 7, - "mode": 0, - "inputs": [ - { - "name": "", - "type": "*", - "link": 117 - } - ], - "outputs": [ - { - "name": "", - "type": "VAE", - "links": [ - 124 - ] - } - ], - "properties": { - "showOutputText": false, - "horizontal": false - } - }, - { - "id": 57, - "type": "Reroute", - "pos": [ - -673.8297949218747, - -185.22597607421872 - ], - "size": [ - 75, - 26 - ], - "flags": {}, - "order": 10, - "mode": 0, - "inputs": [ - { - "name": "", - "type": "*", - "link": 135 - } - ], - "outputs": [ - { - "name": "", - "type": "MODEL", - "links": [ - 120 - ], - "slot_index": 0 - } - ], - "properties": { - "showOutputText": false, - "horizontal": false - } - }, - { - "id": 65, - "type": "Reroute", - "pos": [ - -1512.8297949218763, - -181.22597607421872 - ], - "size": [ - 75, - 26 - ], - "flags": {}, - "order": 5, - "mode": 0, - "inputs": [ - { - "name": "", - "type": "*", - "link": 134 - } - ], - "outputs": [ - { - "name": "", - "type": "MODEL", - "links": [ - 135 - ] - } - ], - "properties": { - "showOutputText": false, - "horizontal": false - } - }, - { - "id": 16, - "type": "CheckpointLoaderSimple", - "pos": [ - -2000.8297949218756, - 192.77402392578128 - ], - "size": [ - 315, - 98 - ], - "flags": {}, - "order": 0, - "mode": 0, - "outputs": [ - { - "name": "MODEL", - "type": "MODEL", - "links": [ - 134 - ], - "slot_index": 0 - }, - { - "name": "CLIP", - "type": "CLIP", - "links": [ - 116 - ], - "slot_index": 1 - }, - { - "name": "VAE", - "type": "VAE", - "links": [ - 117 - ], - "slot_index": 2 - } - ], - "properties": { - "Node name for S&R": "CheckpointLoaderSimple" - }, - "widgets_values": [ - "revAnimated_v122.safetensors" - ] - }, - { - "id": 19, - "type": "CLIPSetLastLayer", - "pos": [ - -1597, - 209 - ], - "size": [ - 315, - 58 - ], - "flags": {}, - "order": 6, - "mode": 0, - "inputs": [ - { - "name": "clip", - "type": "CLIP", - "link": 116 - } - ], - "outputs": [ - { - "name": "CLIP", - "type": "CLIP", - "links": [ - 28, - 29, - 149 - ], - "shape": 3, - "slot_index": 0 - } - ], - "properties": { - "Node name for S&R": "CLIPSetLastLayer" - }, - "widgets_values": [ - -2 - ] - }, - { - "id": 7, - "type": "CLIPTextEncode", - "pos": [ - -839, - 335 - ], - "size": [ - 210, - 54 - ], - "flags": {}, - "order": 11, - "mode": 0, - "inputs": [ - { - "name": "clip", - "type": "CLIP", - "link": 29 - }, - { - "name": "text", - "type": "STRING", - "link": 163, - "widget": { - "name": "text", - "config": [ - "STRING", - { - "multiline": true - } - ] - }, - "slot_index": 1 - } - ], - "outputs": [ - { - "name": "CONDITIONING", - "type": "CONDITIONING", - "links": [ - 6 - ], - "slot_index": 0 - } - ], - "properties": { - "Node name for S&R": "CLIPTextEncode" - }, - "widgets_values": [ - "worst quality, hands, embedding:EasyNegative," - ] - }, - { - "id": 5, - "type": "EmptyLatentImage", - "pos": [ - -969, - 484 - ], - "size": [ - 315, - 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": [ - 768, - 512, - 1 - ] - }, - { - "id": 80, - "type": "Text box", - "pos": [ - -1397, - 356 - ], - "size": [ - 294, - 93.11674499511719 - ], - "flags": {}, - "order": 2, - "mode": 0, - "outputs": [ - { - "name": "STRING", - "type": "STRING", - "links": [ - 163 - ], - "shape": 3, - "slot_index": 0 - } - ], - "title": "❌Mel Negatives (general) (Negative)", - "properties": { - "Node name for S&R": "Text box" - }, - "widgets_values": [ - "embedding:EasyNegative, embedding:EasyNegativeV2, watermark, text, deformed" - ] - }, - { - "id": 72, - "type": "ConditioningAverage ", - "pos": [ - -960, - 2 - ], - "size": [ - 380.4000244140625, - 78 - ], - "flags": {}, - "order": 16, - "mode": 0, - "inputs": [ - { - "name": "conditioning_to", - "type": "CONDITIONING", - "link": 151 - }, - { - "name": "conditioning_from", - "type": "CONDITIONING", - "link": 152, - "slot_index": 1 - }, - { - "name": "conditioning_to_strength", - "type": "FLOAT", - "link": 154, - "widget": { - "name": "conditioning_to_strength", - "config": [ - "FLOAT", - { - "default": 1, - "min": 0, - "max": 1, - "step": 0.01 - } - ] - }, - "slot_index": 2 - } - ], - "outputs": [ - { - "name": "CONDITIONING", - "type": "CONDITIONING", - "links": [ - 153 - ], - "shape": 3, - "slot_index": 0 - } - ], - "properties": { - "Node name for S&R": "ConditioningAverage " - }, - "widgets_values": [ - 1 - ] - }, - { - "id": 6, - "type": "CLIPTextEncode", - "pos": [ - -1261, - -46 - ], - "size": [ - 210, - 54 - ], - "flags": {}, - "order": 14, - "mode": 0, - "inputs": [ - { - "name": "clip", - "type": "CLIP", - "link": 28 - }, - { - "name": "text", - "type": "STRING", - "link": 145, - "widget": { - "name": "text", - "config": [ - "STRING", - { - "multiline": true - } - ] - }, - "slot_index": 1 - } - ], - "outputs": [ - { - "name": "CONDITIONING", - "type": "CONDITIONING", - "links": [ - 151 - ], - "slot_index": 0 - } - ], - "properties": { - "Node name for S&R": "CLIPTextEncode" - }, - "widgets_values": [ - "A majestic Lion, fur in the wind, (smirk smile)" - ] - }, - { - "id": 70, - "type": "CLIPTextEncode", - "pos": [ - -1245, - 87 - ], - "size": [ - 210, - 54 - ], - "flags": {}, - "order": 12, - "mode": 0, - "inputs": [ - { - "name": "clip", - "type": "CLIP", - "link": 149, - "slot_index": 0 - }, - { - "name": "text", - "type": "STRING", - "link": 150, - "widget": { - "name": "text", - "config": [ - "STRING", - { - "multiline": true - } - ] - }, - "slot_index": 1 - } - ], - "outputs": [ - { - "name": "CONDITIONING", - "type": "CONDITIONING", - "links": [ - 152 - ], - "slot_index": 0 - } - ], - "properties": { - "Node name for S&R": "CLIPTextEncode" - }, - "widgets_values": [ - "A majestic Lion, fur in the wind, (smirk smile)" - ] - }, - { - "id": 69, - "type": "String Replace (mtb)", - "pos": [ - -1560, - -70 - ], - "size": [ - 210, - 82 - ], - "flags": {}, - "order": 8, - "mode": 0, - "inputs": [ - { - "name": "string", - "type": "STRING", - "link": 141 - } - ], - "outputs": [ - { - "name": "STRING", - "type": "STRING", - "links": [ - 145 - ], - "shape": 3, - "slot_index": 0 - } - ], - "properties": { - "Node name for S&R": "String Replace (mtb)" - }, - "widgets_values": [ - "blue", - "yellow" - ], - "color": "#432", - "bgcolor": "#653" - }, - { - "id": 68, - "type": "Text box", - "pos": [ - -1950, - 10 - ], - "size": [ - 217.2119140625, - 122.18632507324219 - ], - "flags": {}, - "order": 3, - "mode": 0, - "outputs": [ - { - "name": "STRING", - "type": "STRING", - "links": [ - 141, - 150 - ], - "shape": 3, - "slot_index": 0 - } - ], - "properties": { - "Node name for S&R": "Text box" - }, - "widgets_values": [ - "A cinematic shot of a blue car, high speed, smoke whisps in the air, (at night), blue neon lighting" - ], - "color": "#432", - "bgcolor": "#653" - }, - { - "id": 3, - "type": "KSampler", - "pos": [ - -483, - -21 - ], - "size": [ - 315, - 474 - ], - "flags": {}, - "order": 19, - "mode": 0, - "inputs": [ - { - "name": "model", - "type": "MODEL", - "link": 120 - }, - { - "name": "positive", - "type": "CONDITIONING", - "link": 153 - }, - { - "name": "negative", - "type": "CONDITIONING", - "link": 6 - }, - { - "name": "latent_image", - "type": "LATENT", - "link": 2 - } - ], - "outputs": [ - { - "name": "LATENT", - "type": "LATENT", - "links": [ - 138 - ], - "slot_index": 0 - } - ], - "properties": { - "Node name for S&R": "KSampler" - }, - "widgets_values": [ - 5428233522, - "fixed", - 45, - 8, - "euler_ancestral", - "simple", - 1 - ] - }, - { - "id": 66, - "type": "VAEDecodeTiled", - "pos": [ - 197, - -20 - ], - "size": [ - 210, - 46 - ], - "flags": { - "collapsed": false - }, - "order": 20, - "mode": 0, - "inputs": [ - { - "name": "samples", - "type": "LATENT", - "link": 138 - }, - { - "name": "vae", - "type": "VAE", - "link": 139, - "slot_index": 1 - } - ], - "outputs": [ - { - "name": "IMAGE", - "type": "IMAGE", - "links": [ - 161 - ], - "shape": 3, - "slot_index": 0 - } - ], - "properties": { - "Node name for S&R": "VAEDecodeTiled" - } - }, - { - "id": 78, - "type": "SaveImage", - "pos": [ - 565, - -18 - ], - "size": [ - 315, - 270 - ], - "flags": {}, - "order": 21, - "mode": 0, - "inputs": [ - { - "name": "images", - "type": "IMAGE", - "link": 161 - } - ], - "properties": {}, - "widgets_values": [ - "mtb_demo-conditional_blend" - ] - }, - { - "id": 81, - "type": "Get Batch From History (mtb)", - "pos": [ - -625, - 848 - ], - "size": [ - 235.1999969482422, - 118 - ], - "flags": {}, - "order": 15, - "mode": 0, - "inputs": [ - { - "name": "passthrough_image", - "type": "IMAGE", - "link": null - }, - { - "name": "count", - "type": "INT", - "link": 171, - "widget": { - "name": "count", - "config": [ - "INT", - { - "default": 1, - "min": 0 - } - ] - }, - "slot_index": 2 - }, - { - "name": "enable", - "type": "BOOL", - "link": 168, - "widget": { - "name": "enable", - "config": [ - "BOOL", - { - "default": true - } - ] - }, - "slot_index": 2 - } - ], - "outputs": [ - { - "name": "i", - "type": "IMAGE", - "links": [ - 166, - 167 - ], - "shape": 3, - "slot_index": 0 - } - ], - "properties": { - "Node name for S&R": "Get Batch From History (mtb)" - }, - "widgets_values": [ - true, - 24, - 0 - ] - }, - { - "id": 77, - "type": "Export To Prores (mtb)", - "pos": [ - -111, - 849 - ], - "size": [ - 315, - 82 - ], - "flags": {}, - "order": 17, - "mode": 0, - "inputs": [ - { - "name": "images", - "type": "IMAGE", - "link": 166, - "slot_index": 0 - } - ], - "outputs": [ - { - "name": "VIDEO", - "type": "VIDEO", - "links": null, - "shape": 3 - } - ], - "properties": { - "Node name for S&R": "Export To Prores (mtb)" - }, - "widgets_values": [ - 12, - "export" - ] - }, - { - "id": 79, - "type": "Save Gif (mtb)", - "pos": [ - -104, - 1000 - ], - "size": [ - 210, - 356 - ], - "flags": {}, - "order": 18, - "mode": 0, - "inputs": [ - { - "name": "image", - "type": "IMAGE", - "link": 167 - }, - { - "name": "pingpong", - "type": "BOOL", - "link": null - } - ], - "properties": { - "Node name for S&R": "Save Gif (mtb)" - }, - "widgets_values": [ - 12, - 1, - true, - "/view?filename=03031ebda0.gif&subfolder=&type=output" - ] - }, - { - "id": 73, - "type": "Animation Builder (mtb)", - "pos": [ - -1285, - 831 - ], - "size": [ - 211.60000610351562, - 294 - ], - "flags": {}, - "order": 9, - "mode": 0, - "inputs": [ - { - "name": "total_frames", - "type": "INT", - "link": 170, - "widget": { - "name": "total_frames", - "config": [ - "INT", - { - "default": 100, - "min": 0 - } - ] - }, - "slot_index": 0 - } - ], - "outputs": [ - { - "name": "frame", - "type": "INT", - "links": null, - "shape": 3, - "slot_index": 0 - }, - { - "name": "0-1 (scaled)", - "type": "FLOAT", - "links": [ - 154 - ], - "shape": 3, - "slot_index": 1 - }, - { - "name": "count", - "type": "INT", - "links": null, - "shape": 3 - }, - { - "name": "loop_ended", - "type": "BOOL", - "links": [ - 168 - ], - "shape": 3, - "slot_index": 3 - } - ], - "properties": { - "Node name for S&R": "Animation Builder (mtb)" - }, - "widgets_values": [ - 24, - 1, - 1, - 0, - 0, - "Idle", - "Iteration: Idle", - "reset", - "queue" - ], - "color": "#232", - "bgcolor": "#353", - "shape": 1 - }, - { - "id": 82, - "type": "PrimitiveNode", - "pos": [ - -1663, - 1205 - ], - "size": [ - 210, - 82 - ], - "flags": {}, - "order": 4, - "mode": 0, - "outputs": [ - { - "name": "INT", - "type": "INT", - "links": [ - 170, - 171 - ], - "widget": { - "name": "total_frames", - "config": [ - "INT", - { - "default": 100, - "min": 0 - } - ] - }, - "slot_index": 0 - } - ], - "title": "total_frames", - "properties": {}, - "widgets_values": [ - 24, - "fixed" - ] - } - ], - "links": [ - [ - 2, - 5, - 0, - 3, - 3, - "LATENT" - ], - [ - 6, - 7, - 0, - 3, - 2, - "CONDITIONING" - ], - [ - 28, - 19, - 0, - 6, - 0, - "CLIP" - ], - [ - 29, - 19, - 0, - 7, - 0, - "CLIP" - ], - [ - 116, - 16, - 1, - 19, - 0, - "CLIP" - ], - [ - 117, - 16, - 2, - 56, - 0, - "*" - ], - [ - 120, - 57, - 0, - 3, - 0, - "MODEL" - ], - [ - 124, - 56, - 0, - 59, - 0, - "*" - ], - [ - 134, - 16, - 0, - 65, - 0, - "*" - ], - [ - 135, - 65, - 0, - 57, - 0, - "*" - ], - [ - 138, - 3, - 0, - 66, - 0, - "LATENT" - ], - [ - 139, - 59, - 0, - 66, - 1, - "VAE" - ], - [ - 141, - 68, - 0, - 69, - 0, - "STRING" - ], - [ - 145, - 69, - 0, - 6, - 1, - "STRING" - ], - [ - 149, - 19, - 0, - 70, - 0, - "CLIP" - ], - [ - 150, - 68, - 0, - 70, - 1, - "STRING" - ], - [ - 151, - 6, - 0, - 72, - 0, - "CONDITIONING" - ], - [ - 152, - 70, - 0, - 72, - 1, - "CONDITIONING" - ], - [ - 153, - 72, - 0, - 3, - 1, - "CONDITIONING" - ], - [ - 154, - 73, - 1, - 72, - 2, - "FLOAT" - ], - [ - 161, - 66, - 0, - 78, - 0, - "IMAGE" - ], - [ - 163, - 80, - 0, - 7, - 1, - "STRING" - ], - [ - 166, - 81, - 0, - 77, - 0, - "IMAGE" - ], - [ - 167, - 81, - 0, - 79, - 0, - "IMAGE" - ], - [ - 168, - 73, - 3, - 81, - 2, - "BOOL" - ], - [ - 170, - 82, - 0, - 73, - 0, - "INT" - ], - [ - 171, - 82, - 0, - 81, - 1, - "INT" - ] - ], - "groups": [ - { - "title": "Txt2Img", - "bounding": [ - -2061, - -234, - 1932, - 973 - ], - "color": "#a1309b", - "locked": false - }, - { - "title": "Save Intermediate Image", - "bounding": [ - 150, - -116, - 303, - 213 - ], - "color": "#3f789e", - "locked": false - } - ], - "config": {}, - "extra": {}, - "version": 0.4 -} \ No newline at end of file +{"last_node_id":82,"last_link_id":171,"nodes":[{"id":59,"type":"Reroute","pos":[-150.35178124999982,644.4360633544919],"size":[75,26],"flags":{},"order":13,"mode":0,"inputs":[{"name":"","type":"*","link":124}],"outputs":[{"name":"","type":"VAE","links":[139],"slot_index":0}],"properties":{"showOutputText":false,"horizontal":false}},{"id":56,"type":"Reroute","pos":[-1580.8297949218763,644.7740239257807],"size":[75,26],"flags":{},"order":7,"mode":0,"inputs":[{"name":"","type":"*","link":117}],"outputs":[{"name":"","type":"VAE","links":[124]}],"properties":{"showOutputText":false,"horizontal":false}},{"id":57,"type":"Reroute","pos":[-673.8297949218747,-185.22597607421872],"size":[75,26],"flags":{},"order":10,"mode":0,"inputs":[{"name":"","type":"*","link":135}],"outputs":[{"name":"","type":"MODEL","links":[120],"slot_index":0}],"properties":{"showOutputText":false,"horizontal":false}},{"id":65,"type":"Reroute","pos":[-1512.8297949218763,-181.22597607421872],"size":[75,26],"flags":{},"order":5,"mode":0,"inputs":[{"name":"","type":"*","link":134}],"outputs":[{"name":"","type":"MODEL","links":[135]}],"properties":{"showOutputText":false,"horizontal":false}},{"id":16,"type":"CheckpointLoaderSimple","pos":[-2000.8297949218756,192.77402392578128],"size":[315,98],"flags":{},"order":0,"mode":0,"outputs":[{"name":"MODEL","type":"MODEL","links":[134],"slot_index":0},{"name":"CLIP","type":"CLIP","links":[116],"slot_index":1},{"name":"VAE","type":"VAE","links":[117],"slot_index":2}],"properties":{"Node name for S&R":"CheckpointLoaderSimple"},"widgets_values":["revAnimated_v122.safetensors"]},{"id":19,"type":"CLIPSetLastLayer","pos":[-1597,209],"size":[315,58],"flags":{},"order":6,"mode":0,"inputs":[{"name":"clip","type":"CLIP","link":116}],"outputs":[{"name":"CLIP","type":"CLIP","links":[28,29,149],"shape":3,"slot_index":0}],"properties":{"Node name for S&R":"CLIPSetLastLayer"},"widgets_values":[-2]},{"id":7,"type":"CLIPTextEncode","pos":[-839,335],"size":[210,54],"flags":{},"order":11,"mode":0,"inputs":[{"name":"clip","type":"CLIP","link":29},{"name":"text","type":"STRING","link":163,"widget":{"name":"text","config":["STRING",{"multiline":true}]},"slot_index":1}],"outputs":[{"name":"CONDITIONING","type":"CONDITIONING","links":[6],"slot_index":0}],"properties":{"Node name for S&R":"CLIPTextEncode"},"widgets_values":["worst quality, hands, embedding:EasyNegative,"]},{"id":5,"type":"EmptyLatentImage","pos":[-969,484],"size":[315,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":[768,512,1]},{"id":80,"type":"Text box","pos":[-1397,356],"size":[294,93.11674499511719],"flags":{},"order":2,"mode":0,"outputs":[{"name":"STRING","type":"STRING","links":[163],"shape":3,"slot_index":0}],"title":"❌Mel Negatives (general) (Negative)","properties":{"Node name for S&R":"Text box"},"widgets_values":["embedding:EasyNegative, embedding:EasyNegativeV2, watermark, text, deformed"]},{"id":72,"type":"ConditioningAverage ","pos":[-960,2],"size":[380.4000244140625,78],"flags":{},"order":16,"mode":0,"inputs":[{"name":"conditioning_to","type":"CONDITIONING","link":151},{"name":"conditioning_from","type":"CONDITIONING","link":152,"slot_index":1},{"name":"conditioning_to_strength","type":"FLOAT","link":154,"widget":{"name":"conditioning_to_strength","config":["FLOAT",{"default":1,"min":0,"max":1,"step":0.01}]},"slot_index":2}],"outputs":[{"name":"CONDITIONING","type":"CONDITIONING","links":[153],"shape":3,"slot_index":0}],"properties":{"Node name for S&R":"ConditioningAverage "},"widgets_values":[1]},{"id":6,"type":"CLIPTextEncode","pos":[-1261,-46],"size":[210,54],"flags":{},"order":14,"mode":0,"inputs":[{"name":"clip","type":"CLIP","link":28},{"name":"text","type":"STRING","link":145,"widget":{"name":"text","config":["STRING",{"multiline":true}]},"slot_index":1}],"outputs":[{"name":"CONDITIONING","type":"CONDITIONING","links":[151],"slot_index":0}],"properties":{"Node name for S&R":"CLIPTextEncode"},"widgets_values":["A majestic Lion, fur in the wind, (smirk smile)"]},{"id":70,"type":"CLIPTextEncode","pos":[-1245,87],"size":[210,54],"flags":{},"order":12,"mode":0,"inputs":[{"name":"clip","type":"CLIP","link":149,"slot_index":0},{"name":"text","type":"STRING","link":150,"widget":{"name":"text","config":["STRING",{"multiline":true}]},"slot_index":1}],"outputs":[{"name":"CONDITIONING","type":"CONDITIONING","links":[152],"slot_index":0}],"properties":{"Node name for S&R":"CLIPTextEncode"},"widgets_values":["A majestic Lion, fur in the wind, (smirk smile)"]},{"id":69,"type":"String Replace (mtb)","pos":[-1560,-70],"size":[210,82],"flags":{},"order":8,"mode":0,"inputs":[{"name":"string","type":"STRING","link":141}],"outputs":[{"name":"STRING","type":"STRING","links":[145],"shape":3,"slot_index":0}],"properties":{"Node name for S&R":"String Replace (mtb)"},"widgets_values":["blue","yellow"],"color":"#432","bgcolor":"#653"},{"id":68,"type":"Text box","pos":[-1950,10],"size":[217.2119140625,122.18632507324219],"flags":{},"order":3,"mode":0,"outputs":[{"name":"STRING","type":"STRING","links":[141,150],"shape":3,"slot_index":0}],"properties":{"Node name for S&R":"Text box"},"widgets_values":["A cinematic shot of a blue car, high speed, smoke whisps in the air, (at night), blue neon lighting"],"color":"#432","bgcolor":"#653"},{"id":3,"type":"KSampler","pos":[-483,-21],"size":[315,474],"flags":{},"order":19,"mode":0,"inputs":[{"name":"model","type":"MODEL","link":120},{"name":"positive","type":"CONDITIONING","link":153},{"name":"negative","type":"CONDITIONING","link":6},{"name":"latent_image","type":"LATENT","link":2}],"outputs":[{"name":"LATENT","type":"LATENT","links":[138],"slot_index":0}],"properties":{"Node name for S&R":"KSampler"},"widgets_values":[5428233522,"fixed",45,8,"euler_ancestral","simple",1]},{"id":66,"type":"VAEDecodeTiled","pos":[197,-20],"size":[210,46],"flags":{"collapsed":false},"order":20,"mode":0,"inputs":[{"name":"samples","type":"LATENT","link":138},{"name":"vae","type":"VAE","link":139,"slot_index":1}],"outputs":[{"name":"IMAGE","type":"IMAGE","links":[161],"shape":3,"slot_index":0}],"properties":{"Node name for S&R":"VAEDecodeTiled"}},{"id":78,"type":"SaveImage","pos":[565,-18],"size":[315,270],"flags":{},"order":21,"mode":0,"inputs":[{"name":"images","type":"IMAGE","link":161}],"properties":{},"widgets_values":["mtb_demo-conditional_blend"]},{"id":81,"type":"Get Batch From History (mtb)","pos":[-625,848],"size":[235.1999969482422,118],"flags":{},"order":15,"mode":0,"inputs":[{"name":"passthrough_image","type":"IMAGE","link":null},{"name":"count","type":"INT","link":171,"widget":{"name":"count","config":["INT",{"default":1,"min":0}]},"slot_index":2},{"name":"enable","type":"BOOLEAN","link":168,"widget":{"name":"enable","config":["BOOLEAN",{"default":true}]},"slot_index":2}],"outputs":[{"name":"i","type":"IMAGE","links":[166,167],"shape":3,"slot_index":0}],"properties":{"Node name for S&R":"Get Batch From History (mtb)"},"widgets_values":[true,24,0]},{"id":77,"type":"Export To Prores (mtb)","pos":[-111,849],"size":[315,82],"flags":{},"order":17,"mode":0,"inputs":[{"name":"images","type":"IMAGE","link":166,"slot_index":0}],"outputs":[{"name":"VIDEO","type":"VIDEO","links":null,"shape":3}],"properties":{"Node name for S&R":"Export To Prores (mtb)"},"widgets_values":[12,"export"]},{"id":79,"type":"Save Gif (mtb)","pos":[-104,1000],"size":[210,356],"flags":{},"order":18,"mode":0,"inputs":[{"name":"image","type":"IMAGE","link":167},{"name":"pingpong","type":"BOOLEAN","link":null}],"properties":{"Node name for S&R":"Save Gif (mtb)"},"widgets_values":[12,1,true,"/view?filename=03031ebda0.gif&subfolder=&type=output"]},{"id":73,"type":"Animation Builder (mtb)","pos":[-1285,831],"size":[211.60000610351562,294],"flags":{},"order":9,"mode":0,"inputs":[{"name":"total_frames","type":"INT","link":170,"widget":{"name":"total_frames","config":["INT",{"default":100,"min":0}]},"slot_index":0}],"outputs":[{"name":"frame","type":"INT","links":null,"shape":3,"slot_index":0},{"name":"0-1 (scaled)","type":"FLOAT","links":[154],"shape":3,"slot_index":1},{"name":"count","type":"INT","links":null,"shape":3},{"name":"loop_ended","type":"BOOLEAN","links":[168],"shape":3,"slot_index":3}],"properties":{"Node name for S&R":"Animation Builder (mtb)"},"widgets_values":[24,1,1,0,0,"Idle","Iteration: Idle","reset","queue"],"color":"#232","bgcolor":"#353","shape":1},{"id":82,"type":"PrimitiveNode","pos":[-1663,1205],"size":[210,82],"flags":{},"order":4,"mode":0,"outputs":[{"name":"INT","type":"INT","links":[170,171],"widget":{"name":"total_frames","config":["INT",{"default":100,"min":0}]},"slot_index":0}],"title":"total_frames","properties":{},"widgets_values":[24,"fixed"]}],"links":[[2,5,0,3,3,"LATENT"],[6,7,0,3,2,"CONDITIONING"],[28,19,0,6,0,"CLIP"],[29,19,0,7,0,"CLIP"],[116,16,1,19,0,"CLIP"],[117,16,2,56,0,"*"],[120,57,0,3,0,"MODEL"],[124,56,0,59,0,"*"],[134,16,0,65,0,"*"],[135,65,0,57,0,"*"],[138,3,0,66,0,"LATENT"],[139,59,0,66,1,"VAE"],[141,68,0,69,0,"STRING"],[145,69,0,6,1,"STRING"],[149,19,0,70,0,"CLIP"],[150,68,0,70,1,"STRING"],[151,6,0,72,0,"CONDITIONING"],[152,70,0,72,1,"CONDITIONING"],[153,72,0,3,1,"CONDITIONING"],[154,73,1,72,2,"FLOAT"],[161,66,0,78,0,"IMAGE"],[163,80,0,7,1,"STRING"],[166,81,0,77,0,"IMAGE"],[167,81,0,79,0,"IMAGE"],[168,73,3,81,2,"BOOLEAN"],[170,82,0,73,0,"INT"],[171,82,0,81,1,"INT"]],"groups":[{"title":"Txt2Img","bounding":[-2061,-234,1932,973],"color":"#a1309b","locked":false},{"title":"Save Intermediate Image","bounding":[150,-116,303,213],"color":"#3f789e","locked":false}],"config":{},"extra":{},"version":0.4} \ No newline at end of file diff --git a/examples/04-animation_builder-deforum.json b/examples/04-animation_builder-deforum.json index 99d3d0b..bd1ba9b 100644 --- a/examples/04-animation_builder-deforum.json +++ b/examples/04-animation_builder-deforum.json @@ -1,1189 +1 @@ -{ - "last_node_id": 42, - "last_link_id": 69, - "nodes": [ - { - "id": 11, - "type": "Get Batch From History (mtb)", - "pos": [ - -828, - 522 - ], - "size": [ - 235.1999969482422, - 118 - ], - "flags": {}, - "order": 12, - "mode": 0, - "inputs": [ - { - "name": "passthrough_image", - "type": "IMAGE", - "link": 10 - }, - { - "name": "enable", - "type": "BOOL", - "link": 9, - "widget": { - "name": "enable", - "config": [ - "BOOL", - { - "default": true - } - ] - }, - "slot_index": 1 - } - ], - "outputs": [ - { - "name": "i", - "type": "IMAGE", - "links": [ - 26 - ], - "shape": 3, - "slot_index": 0 - } - ], - "properties": { - "Node name for S&R": "Get Batch From History (mtb)" - }, - "widgets_values": [ - false, - 1, - 0 - ], - "color": "#223", - "bgcolor": "#335" - }, - { - "id": 24, - "type": "Note", - "pos": [ - -827, - 406 - ], - "size": [ - 233.25148010253906, - 82.53218841552734 - ], - "flags": {}, - "order": 0, - "mode": 0, - "properties": { - "text": "" - }, - "widgets_values": [ - "On first frame we get the init image, on all subsequent ones the feedback from the previous queue item" - ], - "color": "#223", - "bgcolor": "#335", - "shape": 1 - }, - { - "id": 12, - "type": "Int To Bool (mtb)", - "pos": [ - -1057, - 583 - ], - "size": [ - 210, - 36.366058349609375 - ], - "flags": {}, - "order": 8, - "mode": 0, - "inputs": [ - { - "name": "int", - "type": "INT", - "link": 34, - "widget": { - "name": "int", - "config": [ - "INT", - { - "default": 0 - } - ] - }, - "slot_index": 0 - } - ], - "outputs": [ - { - "name": "BOOL", - "type": "BOOL", - "links": [ - 9 - ], - "shape": 3, - "slot_index": 0 - } - ], - "properties": { - "Node name for S&R": "Int To Bool (mtb)" - }, - "widgets_values": [ - 29 - ], - "color": "#222", - "bgcolor": "#000" - }, - { - "id": 10, - "type": "LoadImage", - "pos": [ - -1409, - 524 - ], - "size": [ - 315, - 314 - ], - "flags": {}, - "order": 1, - "mode": 0, - "outputs": [ - { - "name": "IMAGE", - "type": "IMAGE", - "links": [ - 10 - ], - "shape": 3, - "slot_index": 0 - }, - { - "name": "MASK", - "type": "MASK", - "links": null, - "shape": 3 - } - ], - "properties": { - "Node name for S&R": "LoadImage" - }, - "widgets_values": [ - "example.png", - "image" - ], - "color": "#432", - "bgcolor": "#653", - "shape": 1 - }, - { - "id": 18, - "type": "Get Batch From History (mtb)", - "pos": [ - -960, - 1257 - ], - "size": [ - 235.1999969482422, - 118 - ], - "flags": {}, - "order": 11, - "mode": 0, - "inputs": [ - { - "name": "passthrough_image", - "type": "IMAGE", - "link": null - }, - { - "name": "enable", - "type": "BOOL", - "link": 31, - "widget": { - "name": "enable", - "config": [ - "BOOL", - { - "default": true - } - ] - }, - "slot_index": 1 - }, - { - "name": "count", - "type": "INT", - "link": 44, - "widget": { - "name": "count", - "config": [ - "INT", - { - "default": 1, - "min": 0 - } - ] - } - } - ], - "outputs": [ - { - "name": "i", - "type": "IMAGE", - "links": [ - 17, - 18 - ], - "shape": 3, - "slot_index": 0 - } - ], - "properties": { - "Node name for S&R": "Get Batch From History (mtb)" - }, - "widgets_values": [ - true, - 30, - 0 - ] - }, - { - "id": 20, - "type": "Export To Prores (mtb)", - "pos": [ - -612, - 1818 - ], - "size": [ - 292.4239807128906, - 93.884033203125 - ], - "flags": {}, - "order": 13, - "mode": 0, - "inputs": [ - { - "name": "images", - "type": "IMAGE", - "link": 17 - } - ], - "outputs": [ - { - "name": "VIDEO", - "type": "VIDEO", - "links": null, - "shape": 3 - } - ], - "properties": { - "Node name for S&R": "Export To Prores (mtb)" - }, - "widgets_values": [ - 12, - "export" - ] - }, - { - "id": 35, - "type": "CLIPTextEncode", - "pos": [ - -118, - 331 - ], - "size": [ - 210, - 54 - ], - "flags": {}, - "order": 6, - "mode": 0, - "inputs": [ - { - "name": "clip", - "type": "CLIP", - "link": 60 - }, - { - "name": "text", - "type": "STRING", - "link": 66, - "widget": { - "name": "text", - "config": [ - "STRING", - { - "multiline": true - } - ] - } - } - ], - "outputs": [ - { - "name": "CONDITIONING", - "type": "CONDITIONING", - "links": [ - 54 - ], - "shape": 3, - "slot_index": 0 - } - ], - "properties": { - "Node name for S&R": "CLIPTextEncode" - }, - "widgets_values": [ - "" - ] - }, - { - "id": 9, - "type": "CheckpointLoaderSimple", - "pos": [ - -558, - 114 - ], - "size": [ - 301.2330322265625, - 98 - ], - "flags": {}, - "order": 2, - "mode": 0, - "outputs": [ - { - "name": "MODEL", - "type": "MODEL", - "links": [ - 62 - ], - "shape": 3, - "slot_index": 0 - }, - { - "name": "CLIP", - "type": "CLIP", - "links": [ - 59, - 60 - ], - "shape": 3, - "slot_index": 1 - }, - { - "name": "VAE", - "type": "VAE", - "links": [ - 58, - 69 - ], - "shape": 3, - "slot_index": 2 - } - ], - "properties": { - "Node name for S&R": "CheckpointLoaderSimple" - }, - "widgets_values": [ - "revAnimated_v122.safetensors" - ] - }, - { - "id": 37, - "type": "VAEEncode", - "pos": [ - -125, - 236 - ], - "size": [ - 210, - 46 - ], - "flags": {}, - "order": 16, - "mode": 0, - "inputs": [ - { - "name": "pixels", - "type": "IMAGE", - "link": 57 - }, - { - "name": "vae", - "type": "VAE", - "link": 58, - "slot_index": 1 - } - ], - "outputs": [ - { - "name": "LATENT", - "type": "LATENT", - "links": [ - 55 - ], - "shape": 3, - "slot_index": 0 - } - ], - "properties": { - "Node name for S&R": "VAEEncode" - } - }, - { - "id": 34, - "type": "CLIPTextEncode", - "pos": [ - -124, - 84 - ], - "size": [ - 210, - 96 - ], - "flags": {}, - "order": 5, - "mode": 0, - "inputs": [ - { - "name": "clip", - "type": "CLIP", - "link": 59 - } - ], - "outputs": [ - { - "name": "CONDITIONING", - "type": "CONDITIONING", - "links": [ - 53 - ], - "shape": 3, - "slot_index": 0 - } - ], - "properties": { - "Node name for S&R": "CLIPTextEncode" - }, - "widgets_values": [ - "A plastic Barbie doll with a pink dress sleeping, draping, wrinkles, shiny" - ] - }, - { - "id": 33, - "type": "Text box", - "pos": [ - -497, - 349 - ], - "size": [ - 294, - 95.1284408569336 - ], - "flags": {}, - "order": 3, - "mode": 0, - "outputs": [ - { - "name": "STRING", - "type": "STRING", - "links": [ - 66 - ], - "shape": 3, - "slot_index": 0 - } - ], - "title": "❌Mel Negatives (general) (Negative)", - "properties": { - "Node name for S&R": "Text box" - }, - "widgets_values": [ - "embedding:EasyNegative, embedding:EasyNegativeV2, watermark, text, deformed, NSFW, Cleavage, Pubic Hair, Nudity, Naked, censored" - ] - }, - { - "id": 39, - "type": "Reroute", - "pos": [ - -156, - 908 - ], - "size": [ - 75, - 26 - ], - "flags": {}, - "order": 10, - "mode": 0, - "inputs": [ - { - "name": "", - "type": "*", - "link": 67 - } - ], - "outputs": [ - { - "name": "", - "type": "INT", - "links": [ - 68 - ] - } - ], - "properties": { - "showOutputText": false, - "horizontal": false - } - }, - { - "id": 14, - "type": "Transform Image (mtb)", - "pos": [ - -527, - 520 - ], - "size": [ - 315, - 178 - ], - "flags": {}, - "order": 15, - "mode": 0, - "inputs": [ - { - "name": "image", - "type": "IMAGE", - "link": 26 - } - ], - "outputs": [ - { - "name": "IMAGE", - "type": "IMAGE", - "links": [ - 57 - ], - "shape": 3, - "slot_index": 0 - } - ], - "properties": { - "Node name for S&R": "Transform Image (mtb)" - }, - "widgets_values": [ - 0, - 5, - 1.03, - 1, - 0, - "reflect" - ], - "color": "#223", - "bgcolor": "#335" - }, - { - "id": 36, - "type": "KSampler", - "pos": [ - 223, - 262 - ], - "size": [ - 315, - 442 - ], - "flags": {}, - "order": 17, - "mode": 0, - "inputs": [ - { - "name": "model", - "type": "MODEL", - "link": 62, - "slot_index": 0 - }, - { - "name": "positive", - "type": "CONDITIONING", - "link": 53 - }, - { - "name": "negative", - "type": "CONDITIONING", - "link": 54 - }, - { - "name": "latent_image", - "type": "LATENT", - "link": 55 - }, - { - "name": "denoise", - "type": "FLOAT", - "link": 63, - "widget": { - "name": "denoise", - "config": [ - "FLOAT", - { - "default": 1, - "min": 0, - "max": 1, - "step": 0.01 - } - ] - }, - "slot_index": 4 - }, - { - "name": "seed", - "type": "INT", - "link": 68, - "widget": { - "name": "seed", - "config": [ - "INT", - { - "default": 0, - "min": 0, - "max": 18446744073709552000 - } - ] - } - } - ], - "outputs": [ - { - "name": "LATENT", - "type": "LATENT", - "links": [ - 56 - ], - "shape": 3, - "slot_index": 0, - "color": "#FF9CF9" - } - ], - "properties": { - "Node name for S&R": "KSampler" - }, - "widgets_values": [ - 61876307854624, - "randomize", - 15, - 8, - "euler_ancestral", - "normal", - 0.6 - ] - }, - { - "id": 15, - "type": "SaveImage", - "pos": [ - 782, - 259 - ], - "size": [ - 330.1112365722656, - 378.1239929199219 - ], - "flags": {}, - "order": 19, - "mode": 0, - "inputs": [ - { - "name": "images", - "type": "IMAGE", - "link": 65 - } - ], - "properties": {}, - "widgets_values": [ - "ComfyUI" - ] - }, - { - "id": 19, - "type": "Save Gif (mtb)", - "pos": [ - -613, - 1256 - ], - "size": [ - 356.1775817871094, - 471.8785705566406 - ], - "flags": {}, - "order": 14, - "mode": 0, - "inputs": [ - { - "name": "image", - "type": "IMAGE", - "link": 18 - } - ], - "properties": { - "Node name for S&R": "Save Gif (mtb)" - }, - "widgets_values": [ - 12, - 1, - true - ] - }, - { - "id": 31, - "type": "PrimitiveNode", - "pos": [ - -2060, - 1291 - ], - "size": [ - 210, - 82 - ], - "flags": {}, - "order": 4, - "mode": 0, - "outputs": [ - { - "name": "INT", - "type": "INT", - "links": [ - 43, - 44 - ], - "widget": { - "name": "total_frames", - "config": [ - "INT", - { - "default": 100, - "min": 0 - } - ] - }, - "slot_index": 0 - } - ], - "title": "total_frames", - "properties": {}, - "widgets_values": [ - 30, - "fixed" - ], - "color": "#432", - "bgcolor": "#653" - }, - { - "id": 38, - "type": "VAEDecode", - "pos": [ - 556, - 260 - ], - "size": [ - 210, - 46 - ], - "flags": {}, - "order": 18, - "mode": 0, - "inputs": [ - { - "name": "samples", - "type": "LATENT", - "link": 56 - }, - { - "name": "vae", - "type": "VAE", - "link": 69, - "slot_index": 1 - } - ], - "outputs": [ - { - "name": "IMAGE", - "type": "IMAGE", - "links": [ - 65 - ], - "shape": 3, - "slot_index": 0 - } - ], - "properties": { - "Node name for S&R": "VAEDecode" - } - }, - { - "id": 22, - "type": "Fit Number (mtb)", - "pos": [ - -647, - 882 - ], - "size": [ - 232.28509521484375, - 166 - ], - "flags": {}, - "order": 9, - "mode": 0, - "inputs": [ - { - "name": "value", - "type": "FLOAT", - "link": 27 - } - ], - "outputs": [ - { - "name": "FLOAT", - "type": "FLOAT", - "links": [ - 63 - ], - "shape": 3, - "slot_index": 0 - } - ], - "title": "Fit Number (mtb) - Denoise", - "properties": { - "Node name for S&R": "Fit Number (mtb)" - }, - "widgets_values": [ - true, - 0, - 1, - 0.3, - 0.6 - ] - }, - { - "id": 17, - "type": "Animation Builder (mtb)", - "pos": [ - -1310, - 883 - ], - "size": [ - 211.60000610351562, - 318 - ], - "flags": {}, - "order": 7, - "mode": 0, - "inputs": [ - { - "name": "total_frames", - "type": "INT", - "link": 43, - "widget": { - "name": "total_frames", - "config": [ - "INT", - { - "default": 100, - "min": 0 - } - ] - }, - "slot_index": 0 - } - ], - "outputs": [ - { - "name": "frame", - "type": "INT", - "links": [ - 34 - ], - "shape": 3, - "slot_index": 0 - }, - { - "name": "0-1 (scaled)", - "type": "FLOAT", - "links": [ - 27 - ], - "shape": 3, - "slot_index": 1 - }, - { - "name": "count", - "type": "INT", - "links": [ - 67 - ], - "shape": 3, - "slot_index": 2 - }, - { - "name": "loop_ended", - "type": "BOOL", - "links": [ - 31 - ], - "shape": 3, - "slot_index": 3 - } - ], - "properties": { - "Node name for S&R": "Animation Builder (mtb)" - }, - "widgets_values": [ - 30, - 1, - 1, - 0, - 0, - "Idle", - "Iteration: Idle", - "reset", - "queue" - ], - "color": "#232", - "bgcolor": "#353", - "shape": 1 - } - ], - "links": [ - [ - 9, - 12, - 0, - 11, - 1, - "BOOL" - ], - [ - 10, - 10, - 0, - 11, - 0, - "IMAGE" - ], - [ - 17, - 18, - 0, - 20, - 0, - "IMAGE" - ], - [ - 18, - 18, - 0, - 19, - 0, - "IMAGE" - ], - [ - 26, - 11, - 0, - 14, - 0, - "IMAGE" - ], - [ - 27, - 17, - 1, - 22, - 0, - "FLOAT" - ], - [ - 31, - 17, - 3, - 18, - 1, - "BOOL" - ], - [ - 34, - 17, - 0, - 12, - 0, - "INT" - ], - [ - 43, - 31, - 0, - 17, - 0, - "INT" - ], - [ - 44, - 31, - 0, - 18, - 2, - "INT" - ], - [ - 53, - 34, - 0, - 36, - 1, - "CONDITIONING" - ], - [ - 54, - 35, - 0, - 36, - 2, - "CONDITIONING" - ], - [ - 55, - 37, - 0, - 36, - 3, - "LATENT" - ], - [ - 56, - 36, - 0, - 38, - 0, - "LATENT" - ], - [ - 57, - 14, - 0, - 37, - 0, - "IMAGE" - ], - [ - 58, - 9, - 2, - 37, - 1, - "VAE" - ], - [ - 59, - 9, - 1, - 34, - 0, - "CLIP" - ], - [ - 60, - 9, - 1, - 35, - 0, - "CLIP" - ], - [ - 62, - 9, - 0, - 36, - 0, - "MODEL" - ], - [ - 63, - 22, - 0, - 36, - 4, - "FLOAT" - ], - [ - 65, - 38, - 0, - 15, - 0, - "IMAGE" - ], - [ - 66, - 33, - 0, - 35, - 1, - "STRING" - ], - [ - 67, - 17, - 2, - 39, - 0, - "*" - ], - [ - 68, - 39, - 0, - 36, - 5, - "INT" - ], - [ - 69, - 9, - 2, - 38, - 1, - "VAE" - ] - ], - "groups": [ - { - "title": "Video Output", - "bounding": [ - -702, - 1161, - 516, - 773 - ], - "color": "#3f789e", - "locked": false - }, - { - "title": "START THE QUEUE BY CLICKLING HERE 👆", - "bounding": [ - -1612, - 1219, - 521, - 80 - ], - "color": "#8A8", - "locked": false - } - ], - "config": {}, - "extra": {}, - "version": 0.4 -} \ No newline at end of file +{"last_node_id":42,"last_link_id":69,"nodes":[{"id":11,"type":"Get Batch From History (mtb)","pos":[-828,522],"size":[235.1999969482422,118],"flags":{},"order":12,"mode":0,"inputs":[{"name":"passthrough_image","type":"IMAGE","link":10},{"name":"enable","type":"BOOLEAN","link":9,"widget":{"name":"enable","config":["BOOLEAN",{"default":true}]},"slot_index":1}],"outputs":[{"name":"i","type":"IMAGE","links":[26],"shape":3,"slot_index":0}],"properties":{"Node name for S&R":"Get Batch From History (mtb)"},"widgets_values":[false,1,0],"color":"#223","bgcolor":"#335"},{"id":24,"type":"Note","pos":[-827,406],"size":[233.25148010253906,82.53218841552734],"flags":{},"order":0,"mode":0,"properties":{"text":""},"widgets_values":["On first frame we get the init image, on all subsequent ones the feedback from the previous queue item"],"color":"#223","bgcolor":"#335","shape":1},{"id":12,"type":"Int To Bool (mtb)","pos":[-1057,583],"size":[210,36.366058349609375],"flags":{},"order":8,"mode":0,"inputs":[{"name":"int","type":"INT","link":34,"widget":{"name":"int","config":["INT",{"default":0}]},"slot_index":0}],"outputs":[{"name":"BOOLEAN","type":"BOOLEAN","links":[9],"shape":3,"slot_index":0}],"properties":{"Node name for S&R":"Int To Bool (mtb)"},"widgets_values":[29],"color":"#222","bgcolor":"#000"},{"id":10,"type":"LoadImage","pos":[-1409,524],"size":[315,314],"flags":{},"order":1,"mode":0,"outputs":[{"name":"IMAGE","type":"IMAGE","links":[10],"shape":3,"slot_index":0},{"name":"MASK","type":"MASK","links":null,"shape":3}],"properties":{"Node name for S&R":"LoadImage"},"widgets_values":["example.png","image"],"color":"#432","bgcolor":"#653","shape":1},{"id":18,"type":"Get Batch From History (mtb)","pos":[-960,1257],"size":[235.1999969482422,118],"flags":{},"order":11,"mode":0,"inputs":[{"name":"passthrough_image","type":"IMAGE","link":null},{"name":"enable","type":"BOOLEAN","link":31,"widget":{"name":"enable","config":["BOOLEAN",{"default":true}]},"slot_index":1},{"name":"count","type":"INT","link":44,"widget":{"name":"count","config":["INT",{"default":1,"min":0}]}}],"outputs":[{"name":"i","type":"IMAGE","links":[17,18],"shape":3,"slot_index":0}],"properties":{"Node name for S&R":"Get Batch From History (mtb)"},"widgets_values":[true,30,0]},{"id":20,"type":"Export To Prores (mtb)","pos":[-612,1818],"size":[292.4239807128906,93.884033203125],"flags":{},"order":13,"mode":0,"inputs":[{"name":"images","type":"IMAGE","link":17}],"outputs":[{"name":"VIDEO","type":"VIDEO","links":null,"shape":3}],"properties":{"Node name for S&R":"Export To Prores (mtb)"},"widgets_values":[12,"export"]},{"id":35,"type":"CLIPTextEncode","pos":[-118,331],"size":[210,54],"flags":{},"order":6,"mode":0,"inputs":[{"name":"clip","type":"CLIP","link":60},{"name":"text","type":"STRING","link":66,"widget":{"name":"text","config":["STRING",{"multiline":true}]}}],"outputs":[{"name":"CONDITIONING","type":"CONDITIONING","links":[54],"shape":3,"slot_index":0}],"properties":{"Node name for S&R":"CLIPTextEncode"},"widgets_values":[""]},{"id":9,"type":"CheckpointLoaderSimple","pos":[-558,114],"size":[301.2330322265625,98],"flags":{},"order":2,"mode":0,"outputs":[{"name":"MODEL","type":"MODEL","links":[62],"shape":3,"slot_index":0},{"name":"CLIP","type":"CLIP","links":[59,60],"shape":3,"slot_index":1},{"name":"VAE","type":"VAE","links":[58,69],"shape":3,"slot_index":2}],"properties":{"Node name for S&R":"CheckpointLoaderSimple"},"widgets_values":["revAnimated_v122.safetensors"]},{"id":37,"type":"VAEEncode","pos":[-125,236],"size":[210,46],"flags":{},"order":16,"mode":0,"inputs":[{"name":"pixels","type":"IMAGE","link":57},{"name":"vae","type":"VAE","link":58,"slot_index":1}],"outputs":[{"name":"LATENT","type":"LATENT","links":[55],"shape":3,"slot_index":0}],"properties":{"Node name for S&R":"VAEEncode"}},{"id":34,"type":"CLIPTextEncode","pos":[-124,84],"size":[210,96],"flags":{},"order":5,"mode":0,"inputs":[{"name":"clip","type":"CLIP","link":59}],"outputs":[{"name":"CONDITIONING","type":"CONDITIONING","links":[53],"shape":3,"slot_index":0}],"properties":{"Node name for S&R":"CLIPTextEncode"},"widgets_values":["A plastic Barbie doll with a pink dress sleeping, draping, wrinkles, shiny"]},{"id":33,"type":"Text box","pos":[-497,349],"size":[294,95.1284408569336],"flags":{},"order":3,"mode":0,"outputs":[{"name":"STRING","type":"STRING","links":[66],"shape":3,"slot_index":0}],"title":"❌Mel Negatives (general) (Negative)","properties":{"Node name for S&R":"Text box"},"widgets_values":["embedding:EasyNegative, embedding:EasyNegativeV2, watermark, text, deformed, NSFW, Cleavage, Pubic Hair, Nudity, Naked, censored"]},{"id":39,"type":"Reroute","pos":[-156,908],"size":[75,26],"flags":{},"order":10,"mode":0,"inputs":[{"name":"","type":"*","link":67}],"outputs":[{"name":"","type":"INT","links":[68]}],"properties":{"showOutputText":false,"horizontal":false}},{"id":14,"type":"Transform Image (mtb)","pos":[-527,520],"size":[315,178],"flags":{},"order":15,"mode":0,"inputs":[{"name":"image","type":"IMAGE","link":26}],"outputs":[{"name":"IMAGE","type":"IMAGE","links":[57],"shape":3,"slot_index":0}],"properties":{"Node name for S&R":"Transform Image (mtb)"},"widgets_values":[0,5,1.03,1,0,"reflect"],"color":"#223","bgcolor":"#335"},{"id":36,"type":"KSampler","pos":[223,262],"size":[315,442],"flags":{},"order":17,"mode":0,"inputs":[{"name":"model","type":"MODEL","link":62,"slot_index":0},{"name":"positive","type":"CONDITIONING","link":53},{"name":"negative","type":"CONDITIONING","link":54},{"name":"latent_image","type":"LATENT","link":55},{"name":"denoise","type":"FLOAT","link":63,"widget":{"name":"denoise","config":["FLOAT",{"default":1,"min":0,"max":1,"step":0.01}]},"slot_index":4},{"name":"seed","type":"INT","link":68,"widget":{"name":"seed","config":["INT",{"default":0,"min":0,"max":18446744073709552000}]}}],"outputs":[{"name":"LATENT","type":"LATENT","links":[56],"shape":3,"slot_index":0,"color":"#FF9CF9"}],"properties":{"Node name for S&R":"KSampler"},"widgets_values":[61876307854624,"randomize",15,8,"euler_ancestral","normal",0.6]},{"id":15,"type":"SaveImage","pos":[782,259],"size":[330.1112365722656,378.1239929199219],"flags":{},"order":19,"mode":0,"inputs":[{"name":"images","type":"IMAGE","link":65}],"properties":{},"widgets_values":["ComfyUI"]},{"id":19,"type":"Save Gif (mtb)","pos":[-613,1256],"size":[356.1775817871094,471.8785705566406],"flags":{},"order":14,"mode":0,"inputs":[{"name":"image","type":"IMAGE","link":18}],"properties":{"Node name for S&R":"Save Gif (mtb)"},"widgets_values":[12,1,true]},{"id":31,"type":"PrimitiveNode","pos":[-2060,1291],"size":[210,82],"flags":{},"order":4,"mode":0,"outputs":[{"name":"INT","type":"INT","links":[43,44],"widget":{"name":"total_frames","config":["INT",{"default":100,"min":0}]},"slot_index":0}],"title":"total_frames","properties":{},"widgets_values":[30,"fixed"],"color":"#432","bgcolor":"#653"},{"id":38,"type":"VAEDecode","pos":[556,260],"size":[210,46],"flags":{},"order":18,"mode":0,"inputs":[{"name":"samples","type":"LATENT","link":56},{"name":"vae","type":"VAE","link":69,"slot_index":1}],"outputs":[{"name":"IMAGE","type":"IMAGE","links":[65],"shape":3,"slot_index":0}],"properties":{"Node name for S&R":"VAEDecode"}},{"id":22,"type":"Fit Number (mtb)","pos":[-647,882],"size":[232.28509521484375,166],"flags":{},"order":9,"mode":0,"inputs":[{"name":"value","type":"FLOAT","link":27}],"outputs":[{"name":"FLOAT","type":"FLOAT","links":[63],"shape":3,"slot_index":0}],"title":"Fit Number (mtb) - Denoise","properties":{"Node name for S&R":"Fit Number (mtb)"},"widgets_values":[true,0,1,0.3,0.6]},{"id":17,"type":"Animation Builder (mtb)","pos":[-1310,883],"size":[211.60000610351562,318],"flags":{},"order":7,"mode":0,"inputs":[{"name":"total_frames","type":"INT","link":43,"widget":{"name":"total_frames","config":["INT",{"default":100,"min":0}]},"slot_index":0}],"outputs":[{"name":"frame","type":"INT","links":[34],"shape":3,"slot_index":0},{"name":"0-1 (scaled)","type":"FLOAT","links":[27],"shape":3,"slot_index":1},{"name":"count","type":"INT","links":[67],"shape":3,"slot_index":2},{"name":"loop_ended","type":"BOOLEAN","links":[31],"shape":3,"slot_index":3}],"properties":{"Node name for S&R":"Animation Builder (mtb)"},"widgets_values":[30,1,1,0,0,"Idle","Iteration: Idle","reset","queue"],"color":"#232","bgcolor":"#353","shape":1}],"links":[[9,12,0,11,1,"BOOLEAN"],[10,10,0,11,0,"IMAGE"],[17,18,0,20,0,"IMAGE"],[18,18,0,19,0,"IMAGE"],[26,11,0,14,0,"IMAGE"],[27,17,1,22,0,"FLOAT"],[31,17,3,18,1,"BOOLEAN"],[34,17,0,12,0,"INT"],[43,31,0,17,0,"INT"],[44,31,0,18,2,"INT"],[53,34,0,36,1,"CONDITIONING"],[54,35,0,36,2,"CONDITIONING"],[55,37,0,36,3,"LATENT"],[56,36,0,38,0,"LATENT"],[57,14,0,37,0,"IMAGE"],[58,9,2,37,1,"VAE"],[59,9,1,34,0,"CLIP"],[60,9,1,35,0,"CLIP"],[62,9,0,36,0,"MODEL"],[63,22,0,36,4,"FLOAT"],[65,38,0,15,0,"IMAGE"],[66,33,0,35,1,"STRING"],[67,17,2,39,0,"*"],[68,39,0,36,5,"INT"],[69,9,2,38,1,"VAE"]],"groups":[{"title":"Video Output","bounding":[-702,1161,516,773],"color":"#3f789e","locked":false},{"title":"START THE QUEUE BY CLICKLING HERE 👆","bounding":[-1612,1219,521,80],"color":"#8A8","locked":false}],"config":{},"extra":{},"version":0.4} \ No newline at end of file diff --git a/nodes/animation.py b/nodes/animation.py index da22df1..9822685 100644 --- a/nodes/animation.py +++ b/nodes/animation.py @@ -17,7 +17,7 @@ def INPUT_TYPES(cls): }, } - RETURN_TYPES = ("INT", "FLOAT", "INT", "BOOL") + RETURN_TYPES = ("INT", "FLOAT", "INT", "BOOLEAN") RETURN_NAMES = ("frame", "0-1 (scaled)", "count", "loop_ended") CATEGORY = "mtb/animation" FUNCTION = "build_animation" diff --git a/nodes/deep_bump.py b/nodes/deep_bump.py index 1444d35..47a4d26 100644 --- a/nodes/deep_bump.py +++ b/nodes/deep_bump.py @@ -261,7 +261,7 @@ def INPUT_TYPES(cls): "LARGEST", ], ), - "normals_to_height_seamless": ("BOOL", {"default": False}), + "normals_to_height_seamless": ("BOOLEAN", {"default": False}), }, } diff --git a/nodes/faceenhance.py b/nodes/faceenhance.py index 4dafd39..0155514 100644 --- a/nodes/faceenhance.py +++ b/nodes/faceenhance.py @@ -143,12 +143,12 @@ def INPUT_TYPES(cls): "image": ("IMAGE",), "model": ("FACEENHANCE_MODEL",), # Input are aligned faces - "aligned": ("BOOL", {"default": False}), + "aligned": ("BOOLEAN", {"default": False}), # Only restore the center face - "only_center_face": ("BOOL", {"default": False}), + "only_center_face": ("BOOLEAN", {"default": False}), # Adjustable weights "weight": ("FLOAT", {"default": 0.5}), - "save_tmp_steps": ("BOOL", {"default": True}), + "save_tmp_steps": ("BOOLEAN", {"default": True}), } } diff --git a/nodes/faceswap.py b/nodes/faceswap.py index 4f0eba9..83074d7 100644 --- a/nodes/faceswap.py +++ b/nodes/faceswap.py @@ -2,14 +2,13 @@ import onnxruntime from pathlib import Path from PIL import Image -from typing import List, Set, Tuple, Union, Optional +from typing import List, Set, Union, Optional import cv2 import folder_paths import glob import insightface import numpy as np import os -import tempfile import torch from insightface.model_zoo.inswapper import INSwapper from ..utils import pil2tensor, tensor2pil @@ -120,7 +119,6 @@ def INPUT_TYPES(cls): "faces_index": ("STRING", {"default": "0"}), "faceanalysis_model": ("FACE_ANALYSIS_MODEL", {"default": "None"}), "faceswap_model": ("FACESWAP_MODEL", {"default": "None"}), - "debug": ("BOOL", {"default": False}), }, "optional": {}, } @@ -136,7 +134,6 @@ def swap( faces_index: str, faceanalysis_model, faceswap_model, - debug=False, ): def do_swap(img): model_management.throw_exception_if_processing_interrupted() diff --git a/nodes/fun.py b/nodes/fun.py index d12af8f..332e11a 100644 --- a/nodes/fun.py +++ b/nodes/fun.py @@ -121,7 +121,7 @@ def INPUT_TYPES(cls): "error_correct": (("L", "M", "Q", "H"), {"default": "L"}), "box_size": ("INT", {"default": 10, "max": 8096, "min": 0, "step": 1}), "border": ("INT", {"default": 4, "max": 8096, "min": 0, "step": 1}), - "invert": (("BOOL",), {"default": False}), + "invert": (("BOOLEAN",), {"default": False}), } } diff --git a/nodes/graph_utils.py b/nodes/graph_utils.py index e191fbc..0288a99 100644 --- a/nodes/graph_utils.py +++ b/nodes/graph_utils.py @@ -38,7 +38,7 @@ def INPUT_TYPES(cls): return { "required": { "value": ("FLOAT", {"default": 0, "forceInput": True}), - "clamp": ("BOOL", {"default": False}), + "clamp": ("BOOLEAN", {"default": False}), "source_min": ("FLOAT", {"default": 0.0}), "source_max": ("FLOAT", {"default": 1.0}), "target_min": ("FLOAT", {"default": 0.0}), diff --git a/nodes/image_interpolation.py b/nodes/image_interpolation.py index 2fcb3f7..b8e71ef 100644 --- a/nodes/image_interpolation.py +++ b/nodes/image_interpolation.py @@ -6,9 +6,9 @@ from ..log import log import torch from frame_interpolation.eval import util, interpolator -from ..utils import tensor2np import numpy as np import comfy +import comfy.utils from PIL import Image import urllib.request import urllib.parse @@ -39,7 +39,7 @@ class GetBatchFromHistory: def INPUT_TYPES(cls): return { "required": { - "enable": ("BOOL", {"default": True}), + "enable": ("BOOLEAN", {"default": True}), "count": ("INT", {"default": 1, "min": 0}), "offset": ("INT", {"default": 0, "min": -1e9, "max": 1e9}), }, diff --git a/nodes/image_processing.py b/nodes/image_processing.py index 04f04e1..de43c9a 100644 --- a/nodes/image_processing.py +++ b/nodes/image_processing.py @@ -377,7 +377,7 @@ def INPUT_TYPES(cls): "required": { "image": ("IMAGE",), "mask": ("MASK",), - "invert": ("BOOL", {"default": False}), + "invert": ("BOOLEAN", {"default": False}), } } @@ -425,7 +425,7 @@ def INPUT_TYPES(cls): "FLOAT", {"default": 2, "min": 0.01, "max": 16.0, "step": 0.01}, ), - "supersample": ("BOOL", {"default": True}), + "supersample": ("BOOLEAN", {"default": True}), "resampling": ( ["lanczos", "nearest", "bilinear", "bicubic"], {"default": "lanczos"}, @@ -546,7 +546,7 @@ def INPUT_TYPES(cls): "required": { "images": ("IMAGE",), "filename_prefix": ("STRING", {"default": "ComfyUI"}), - "save_intermediate": ("BOOL", {"default": False}), + "save_intermediate": ("BOOLEAN", {"default": False}), }, "hidden": {"prompt": "PROMPT", "extra_pnginfo": "EXTRA_PNGINFO"}, } diff --git a/nodes/io.py b/nodes/io.py index d31d27e..e8a07bd 100644 --- a/nodes/io.py +++ b/nodes/io.py @@ -101,7 +101,8 @@ def INPUT_TYPES(cls): "image": ("IMAGE",), "fps": ("INT", {"default": 12, "min": 1, "max": 120}), "resize_by": ("FLOAT", {"default": 1.0, "min": 0.1}), - "pingpong": ("BOOL", {"default": False}), + "optimize": ("BOOLEAN", {"default": False}), + "pingpong": ("BOOLEAN", {"default": False}), } } diff --git a/nodes/mask.py b/nodes/mask.py index 64e5119..fe80fc4 100644 --- a/nodes/mask.py +++ b/nodes/mask.py @@ -13,7 +13,7 @@ def INPUT_TYPES(cls): "required": { "image": ("IMAGE",), "alpha_matting": ( - "BOOL", + "BOOLEAN", {"default": False}, ), "alpha_matting_foreground_threshold": ( @@ -29,7 +29,7 @@ def INPUT_TYPES(cls): {"default": 10, "min": 0, "max": 255}, ), "post_process_mask": ( - "BOOL", + "BOOLEAN", {"default": False}, ), "bgcolor": ( diff --git a/nodes/number.py b/nodes/number.py index dc1be4b..6881194 100644 --- a/nodes/number.py +++ b/nodes/number.py @@ -14,7 +14,7 @@ def INPUT_TYPES(cls): } } - RETURN_TYPES = ("BOOL",) + RETURN_TYPES = ("BOOLEAN",) FUNCTION = "int_to_bool" CATEGORY = "mtb/number" diff --git a/web/mtb_widgets.js b/web/mtb_widgets.js index c5f36b4..30283b5 100644 --- a/web/mtb_widgets.js +++ b/web/mtb_widgets.js @@ -13,7 +13,7 @@ import * as shared from '/extensions/mtb/comfy_shared.js' import { log } from '/extensions/mtb/comfy_shared.js' import { api } from '/scripts/api.js' -const newTypes = ['BOOL', 'COLOR', 'BBOX'] +const newTypes = [, /*'BOOL'*/ 'COLOR', 'BBOX'] export const MtbWidgets = { BBOX: (key, val) => { @@ -204,116 +204,7 @@ export const MtbWidgets = { widget.desc = 'Represents a Bounding Box with x, y, width, and height.' return widget }, - BOOL: (key, val, compute = false) => { - /** @type {import("/types/litegraph").IWidget} */ - const widget = { - name: key, - type: 'BOOL', - options: { default: false }, - y: 0, - - draw: function (ctx, node, widget_width, widgetY, height) { - const hide = this.type !== 'BOOL' && app.canvas.ds.scale > 0.5 - if (hide) { - return - } - const outline_color = LiteGraph.WIDGET_OUTLINE_COLOR - const background_color = LiteGraph.WIDGET_BGCOLOR - const text_color = LiteGraph.WIDGET_TEXT_COLOR - const H = LiteGraph.NODE_WIDGET_HEIGHT - // const arrowSize = 8 - - let margin = 15 - if (hide) return - - let currentY = widgetY - - ctx.textAlign = 'left' - ctx.strokeStyle = outline_color - ctx.fillStyle = background_color - ctx.beginPath() - // ctx.roundRect(margin, currentY, widget_width - margin * 2, H, [H * 0.5]); - ctx.rect(margin, currentY, H, H) // Draw checkbox square - - ctx.fill() - ctx.stroke() - - ctx.fillStyle = text_color - // ctx.fillText(this.label || this.name, margin * 2 + 5, currentY + H * 0.7); - ctx.fillText( - this.label || this.name, - H + margin * 2, - currentY + H * 0.7 - ) - - // Draw arrow if the value is true - // Draw checkmark if the value is true - if (this.value) { - ctx.fillStyle = text_color - ctx.beginPath() - ctx.moveTo(margin + H * 0.15, currentY + H * 0.5) - ctx.lineTo(margin + H * 0.4, currentY + H * 0.8) - ctx.lineTo(margin + H * 0.85, currentY + H * 0.2) - ctx.stroke() - } - }, - get value() { - return this.inputEl.value === 'true' - }, - set value(x) { - this.inputEl.value = x - }, - computeSize: function (width) { - return [width, 32] - }, - mouse: function (event, pos, node) { - // let x = pos[0] - node.pos[0]; - // let y = pos[1] - node.pos[1]; - // let width = node.size[0]; - // let H = LiteGraph.NODE_WIDGET_HEIGHT; - // let margin = 15; - - // if (event.type == LiteGraph.pointerevents_method + "down") { - // if (x > margin && x < widget_width - margin && y > widgetY && y < widgetY + H) { - // this.value = !this.value; // Toggle checkbox value - // shared.inner_value_change(this, this.value, event); - // app.canvas.setDirty(true); - // } - // } - if (event.type === 'pointerdown') { - // get widgets of type type : "COLOR" - const widgets = node.widgets.filter((w) => w.type === 'BOOL') - - for (const w of widgets) { - // color picker - const rect = [w.last_y, w.last_y + 32] - if (pos[1] > rect[0] && pos[1] < rect[1]) { - // picker.style.position = "absolute"; - // picker.style.left = ( pos[0]) + "px"; - // picker.style.top = ( pos[1]) + "px"; - - // place at screen center - // picker.style.position = "absolute"; - // picker.style.left = (window.innerWidth / 2) + "px"; - // picker.style.top = (window.innerHeight / 2) + "px"; - // picker.style.transform = "translate(-50%, -50%)"; - // picker.style.zIndex = 1000; - - this.value = this.value ? false : true - } - } - } - }, - } - - // create a checkbox - widget.inputEl = document.createElement('input') - widget.inputEl.type = 'checkbox' - widget.value = val || false - document.body.appendChild(widget.inputEl) - return widget - }, COLOR: (key, val, compute = false) => { /** @type {import("/types/litegraph").IWidget} */ const widget = {} From 0fb2d4da90a7e65f82b3f9c8942a68e360456cf7 Mon Sep 17 00:00:00 2001 From: melMass Date: Sat, 5 Aug 2023 13:28:01 +0200 Subject: [PATCH 02/20] =?UTF-8?q?fix:=20=F0=9F=90=9B=20image=20feed=20zord?= =?UTF-8?q?er?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- web/imageFeed.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/web/imageFeed.js b/web/imageFeed.js index 4a2cf4b..45c4973 100644 --- a/web/imageFeed.js +++ b/web/imageFeed.js @@ -31,7 +31,7 @@ const styles = { background: 'none', border: 'none', color: '#fff', - zIndex: 9999999, + zIndex: 1000, fontSize: '30px', cursor: 'pointer', pointerEvents: 'auto', @@ -43,7 +43,7 @@ const styles = { width: '100vw', position: 'absolute', bottom: 0, - zIndex: 9999999, + zIndex: 10, background: '#333', overflow: 'auto', }, From 2bc7ae88bf4cdfa575d11233c0e6f7b07f9dfd23 Mon Sep 17 00:00:00 2001 From: melMass Date: Sat, 5 Aug 2023 13:31:30 +0200 Subject: [PATCH 03/20] =?UTF-8?q?feat:=20=E2=9C=A8=20use=20PIL=20for=20gif?= =?UTF-8?q?=20saving?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- nodes/io.py | 115 +++++++++++++++++++++++++++++++--------------------- utils.py | 31 ++++++++------ 2 files changed, 86 insertions(+), 60 deletions(-) diff --git a/nodes/io.py b/nodes/io.py index e8a07bd..5bca95b 100644 --- a/nodes/io.py +++ b/nodes/io.py @@ -1,4 +1,4 @@ -from ..utils import tensor2np +from ..utils import tensor2np, PIL_FILTER_MAP import uuid import folder_paths from ..log import log @@ -7,6 +7,8 @@ import torch from pathlib import Path import numpy as np +from PIL import Image +from typing import Optional, List class ExportToProres: @@ -37,9 +39,9 @@ def export_prores( if images.size(0) == 0: return ("",) output_dir = Path(folder_paths.get_output_directory()) - id = f"{prefix}_{uuid.uuid4()}.mov" + file_id = f"{prefix}_{uuid.uuid4()}.mov" - log.debug(f"Exporting to {output_dir / id}") + log.debug(f"Exporting to {output_dir / file_id}") frames = tensor2np(images) log.debug(f"Frames type {type(frames[0])}") @@ -49,7 +51,7 @@ def export_prores( height, width, _ = frames[0].shape - out_path = (output_dir / id).as_posix() + out_path = (output_dir / file_id).as_posix() # Prepare the FFmpeg command command = [ @@ -91,6 +93,37 @@ def export_prores( return (out_path,) +def prepare_animated_batch( + batch: torch.Tensor, + pingpong=False, + resize_by=1.0, + resample_filter: Optional[Image.Resampling] = None, + image_type=np.uint8, +) -> List[Image.Image]: + images = tensor2np(batch) + images = [frame.astype(image_type) for frame in images] + + height, width, _ = batch[0].shape + + if pingpong: + reversed_frames = images[::-1] + images.extend(reversed_frames) + pil_images = [Image.fromarray(frame) for frame in images] + + # Resize frames if necessary + if abs(resize_by - 1.0) > 1e-6: + new_width = int(width * resize_by) + new_height = int(height * resize_by) + pil_images_resized = [ + frame.resize((new_width, new_height), resample=resample_filter) + for frame in pil_images + ] + pil_images = pil_images_resized + + return pil_images + + +# todo: deprecate for apng class SaveGif: """Save the images from the batch as a GIF""" @@ -103,7 +136,10 @@ def INPUT_TYPES(cls): "resize_by": ("FLOAT", {"default": 1.0, "min": 0.1}), "optimize": ("BOOLEAN", {"default": False}), "pingpong": ("BOOLEAN", {"default": False}), - } + }, + "optional": { + "resample_filter": (list(PIL_FILTER_MAP.keys()),), + }, } RETURN_TYPES = () @@ -111,58 +147,43 @@ def INPUT_TYPES(cls): CATEGORY = "mtb/IO" FUNCTION = "save_gif" - def save_gif(self, image, fps=12, resize_by=1.0, pingpong=False): + def save_gif( + self, + image, + fps=12, + resize_by=1.0, + optimize=False, + pingpong=False, + resample_filter=None, + ): if image.size(0) == 0: return ("",) - images = tensor2np(image) - images = [frame.astype(np.uint8) for frame in images] - if pingpong: - reversed_frames = images[::-1] - images.extend(reversed_frames) + if resample_filter is not None: + resample_filter = PIL_FILTER_MAP.get(resample_filter) - height, width, _ = image[0].shape + pil_images = prepare_animated_batch( + image, + pingpong, + resize_by, + resample_filter, + ) ruuid = uuid.uuid4() - ruuid = ruuid.hex[:10] - out_path = f"{folder_paths.output_directory}/{ruuid}.gif" - log.debug(f"Saving a gif file {width}x{height} as {ruuid}.gif") - - # Prepare the FFmpeg command - command = [ - "ffmpeg", - "-y", - "-f", - "rawvideo", - "-vcodec", - "rawvideo", - "-s", - f"{width}x{height}", - "-pix_fmt", - "rgb24", # GIF only supports rgb24 - "-r", - str(fps), - "-i", - "-", - "-vf", - f"fps={fps},scale={width * resize_by}:-1", # Set frame rate and resize if necessary - "-y", + # Create the GIF from PIL images + pil_images[0].save( out_path, - ] - - process = subprocess.Popen(command, stdin=subprocess.PIPE) - - for frame in images: - model_management.throw_exception_if_processing_interrupted() - process.stdin.write(frame.tobytes()) - - process.stdin.close() - process.wait() - results = [] - results.append({"filename": f"{ruuid}.gif", "subfolder": "", "type": "output"}) + save_all=True, + append_images=pil_images[1:], + optimize=optimize, + duration=int(1000 / fps), + loop=0, + ) + + results = [{"filename": f"{ruuid}.gif", "subfolder": "", "type": "output"}] return {"ui": {"gif": results}} diff --git a/utils.py b/utils.py index 1152504..ccfdfea 100644 --- a/utils.py +++ b/utils.py @@ -50,35 +50,42 @@ def import_install(package_name): # endregion # region GLOBAL VARIABLES -# Get the absolute path of the parent directory of the current script +# - Get the absolute path of the parent directory of the current script here = Path(__file__).parent.resolve() -# Construct the absolute path to the ComfyUI directory +# - Construct the absolute path to the ComfyUI directory comfy_dir = here.parent.parent -# Construct the path to the font file +# - Construct the path to the font file font_path = here / "font.ttf" -# Add extern folder to path +# - Add extern folder to path extern_root = here / "extern" add_path(extern_root) for pth in extern_root.iterdir(): if pth.is_dir(): add_path(pth) - -# Add the ComfyUI directory and custom nodes path to the sys.path list +# - Add the ComfyUI directory and custom nodes path to the sys.path list add_path(comfy_dir) add_path((comfy_dir / "custom_nodes")) + +PIL_FILTER_MAP = { + "nearest": Image.Resampling.NEAREST, + "box": Image.Resampling.BOX, + "bilinear": Image.Resampling.BILINEAR, + "hamming": Image.Resampling.HAMMING, + "bicubic": Image.Resampling.BICUBIC, + "lanczos": Image.Resampling.LANCZOS, +} + + # endregion # region TENSOR UTILITIES def tensor2pil(image: torch.Tensor) -> List[Image.Image]: - batch_count = 1 - if len(image.shape) > 3: - batch_count = image.size(0) - + batch_count = image.size(0) if len(image.shape) > 3 else 1 if batch_count > 1: out = [] for i in range(batch_count): @@ -107,9 +114,7 @@ def np2tensor(img_np: np.ndarray | List[np.ndarray]) -> torch.Tensor: def tensor2np(tensor: torch.Tensor) -> List[np.ndarray]: - batch_count = 1 - if len(tensor.shape) > 3: - batch_count = tensor.size(0) + batch_count = tensor.size(0) if len(tensor.shape) > 3 else 1 if batch_count > 1: out = [] for i in range(batch_count): From 13d255a730b08c4903647875350b9b3dcd61b4a6 Mon Sep 17 00:00:00 2001 From: melMass Date: Sat, 5 Aug 2023 13:31:54 +0200 Subject: [PATCH 04/20] =?UTF-8?q?refactor:=20=E2=99=BB=EF=B8=8F=20get=20ba?= =?UTF-8?q?tch=20from=20history?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- nodes/image_interpolation.py | 87 ++++++++++++++++++++---------------- 1 file changed, 49 insertions(+), 38 deletions(-) diff --git a/nodes/image_interpolation.py b/nodes/image_interpolation.py index b8e71ef..8b1e013 100644 --- a/nodes/image_interpolation.py +++ b/nodes/image_interpolation.py @@ -25,7 +25,7 @@ def get_image(filename, subfolder, folder_type): data = {"filename": filename, "subfolder": subfolder, "type": folder_type} url_values = urllib.parse.urlencode(data) with urllib.request.urlopen( - "http://{}:{}/view?{}".format(args.listen, args.port, url_values) + f"http://{args.listen}:{args.port}/view?{url_values}" ) as response: return io.BytesIO(response.read()) @@ -33,7 +33,7 @@ def get_image(filename, subfolder, folder_type): class GetBatchFromHistory: """Very experimental node to load images from the history of the server. - Queue items without output are ignore in the count.""" + Queue items without output are ignored in the count.""" @classmethod def INPUT_TYPES(cls): @@ -42,8 +42,11 @@ def INPUT_TYPES(cls): "enable": ("BOOLEAN", {"default": True}), "count": ("INT", {"default": 1, "min": 0}), "offset": ("INT", {"default": 0, "min": -1e9, "max": 1e9}), + "internal_count": ("INT", {"default": 0}), + }, + "optional": { + "passthrough_image": ("IMAGE",), }, - "optional": {"passthrough_image": ("IMAGE",)}, } RETURN_TYPES = ("IMAGE",) @@ -56,55 +59,63 @@ def load_from_history( enable=True, count=0, offset=0, + internal_count=0, # hacky way to invalidate the node passthrough_image=None, ): if not enable or count == 0: if passthrough_image is not None: + log.debug("Using passthrough image") return (passthrough_image,) log.debug("Load from history is disabled for this iteration") return (torch.zeros(0),) frames = [] with urllib.request.urlopen( - "http://{}:{}/history".format(args.listen, args.port) + f"http://{args.listen}:{args.port}/history" ) as response: - history = json.loads(response.read()) - - output_images = [] - for k, run in history.items(): - for o in run["outputs"]: - for node_id in run["outputs"]: - node_output = run["outputs"][node_id] - if "images" in node_output: - images_output = [] - for image in node_output["images"]: - image_data = get_image( - image["filename"], image["subfolder"], image["type"] - ) - images_output.append(image_data) - output_images.extend(images_output) - if len(output_images) == 0: - return (torch.zeros(0),) - for i, image in enumerate(list(reversed(output_images))): - if i < offset: - continue - if i >= offset + count: - break - # Decode image as tensor - img = Image.open(image) - log.debug(f"Image from history {i} of shape {img.size}") - frames.append(img) - - # Display the shape of the tensor - # print("Tensor shape:", image_tensor.shape) + return self.load_batch_frames(response, offset, count, frames) + + def load_batch_frames(self, response, offset, count, frames): + history = json.loads(response.read()) + + output_images = [] + for k, run in history.items(): + for o in run["outputs"]: + for node_id in run["outputs"]: + node_output = run["outputs"][node_id] + if "images" in node_output: + images_output = [] + for image in node_output["images"]: + image_data = get_image( + image["filename"], image["subfolder"], image["type"] + ) + images_output.append(image_data) + output_images.extend(images_output) + if not output_images: + return (torch.zeros(0),) + for i, image in enumerate(list(reversed(output_images))): + if i < offset: + continue + if i >= offset + count: + break + # Decode image as tensor + img = Image.open(image) + log.debug(f"Image from history {i} of shape {img.size}") + frames.append(img) + + # Display the shape of the tensor + # print("Tensor shape:", image_tensor.shape) # return (output_images,) + if not frames: + return (torch.zeros(0),) + elif len(frames) != count: + log.warning(f"Expected {count} images, got {len(frames)} instead") + output = pil2tensor( + list(reversed(frames)), + ) - output = pil2tensor( - list(reversed(frames)), - ) - - return (output,) + return (output,) class LoadFilmModel: From fb644847ca434123e8e8e4991d33949fd31e3cbe Mon Sep 17 00:00:00 2001 From: melMass Date: Mon, 7 Aug 2023 20:49:47 +0200 Subject: [PATCH 05/20] =?UTF-8?q?feat:=20=E2=9C=A8=20add=20border=20extens?= =?UTF-8?q?ion?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The maths are still not correct I need to debug it in isolation --- nodes/transform.py | 36 +++++++++++++++++++++++++++++++----- web/comfy_shared.js | 2 +- 2 files changed, 32 insertions(+), 6 deletions(-) diff --git a/nodes/transform.py b/nodes/transform.py index 71d6580..c0b9be9 100644 --- a/nodes/transform.py +++ b/nodes/transform.py @@ -19,6 +19,10 @@ def INPUT_TYPES(cls): "zoom": ("FLOAT", {"default": 1.0, "min": 0.001}), "angle": ("FLOAT", {"default": 0}), "shear": ("FLOAT", {"default": 0}), + "border_handling": ( + ["edge", "constant", "reflect", "symmetric"], + {"default": "edge"}, + ), }, } @@ -34,19 +38,41 @@ def transform( zoom: float, angle: int, shear, + border_handling="edge", ): if image.size(0) == 0: return (torch.zeros(0),) transformed_images = [] + frames_count, frame_height, frame_width, frame_channel_count = image.size() + + new_height, new_width = int(frame_height * zoom), int(frame_width * zoom) + + pw = int(frame_width - new_width) + ph = int(frame_height - new_height) + padding = [max(0, pw + x), max(0, ph + y), max(0, pw - x), max(0, ph - y)] + for img in image: - img = img.transpose(0, 2) + img = img.permute(2, 0, 1) + new_height, new_width = int(frame_height * zoom), int(frame_width * zoom) + pw = int(frame_width - new_width) + ph = int(frame_height - new_height) + + padding = [int(i) for i in padding] - transformed_image = F.affine( - img, angle=angle, scale=zoom, translate=[int(y), int(x)], shear=shear + img = F.pad( + img, # transformed_frame, + padding=padding, + padding_mode=border_handling, ) - transformed_image = transformed_image.transpose(2, 0) - transformed_images.append(transformed_image.unsqueeze(0)) + img = F.affine(img, angle=angle, scale=zoom, translate=[x, y], shear=shear) + + crop = [ph + y, -(ph - y), x + pw, -(pw - x)] + + img = img[:, crop[0] : crop[1], crop[2] : crop[3]] + + img = img.permute(1, 2, 0) + transformed_images.append(img.unsqueeze(0)) return (torch.cat(transformed_images, dim=0),) diff --git a/web/comfy_shared.js b/web/comfy_shared.js index 57850ac..3f34b54 100644 --- a/web/comfy_shared.js +++ b/web/comfy_shared.js @@ -49,7 +49,7 @@ export function offsetDOMWidget( position: 'absolute', background: !node.color ? '' : node.color, color: !node.color ? '' : 'white', - zIndex: app.graph._nodes.indexOf(node), + zIndex: 5, //app.graph._nodes.indexOf(node), }) } From 3f14b1676d28f5ffa1f47fda00b9bc244951045c Mon Sep 17 00:00:00 2001 From: melMass Date: Tue, 8 Aug 2023 21:29:28 +0200 Subject: [PATCH 06/20] =?UTF-8?q?feat:=20=E2=9C=A8=20add=20portable=20reqs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- reqs_portable.txt | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 reqs_portable.txt diff --git a/reqs_portable.txt b/reqs_portable.txt new file mode 100644 index 0000000..a29f000 --- /dev/null +++ b/reqs_portable.txt @@ -0,0 +1,14 @@ +https://github.com/melMass/comfy_mtb/releases/download/v0.1.3/pycocotools-2.0.6-cp310-cp310-win_amd64.whl +https://github.com/melMass/comfy_mtb/releases/download/v0.1.3/future-0.18.3-py3-none-any.whl +https://github.com/melMass/comfy_mtb/releases/download/v0.1.3/filterpy-1.4.5-py3-none-any.whl +https://github.com/melMass/comfy_mtb/releases/download/v0.1.3/easydict-1.10-py3-none-any.whl +https://github.com/melMass/comfy_mtb/releases/download/v0.1.3/gdown-4.7.1-py3-none-any.whl +https://github.com/melMass/comfy_mtb/releases/download/v0.1.3/basicsr-1.4.2-py3-none-any.whl +https://github.com/melMass/comfy_mtb/releases/download/v0.1.3/mmcv-2.0.0-py2.py3-none-any.whl +https://github.com/melMass/comfy_mtb/releases/download/v0.1.3/insightface-0.7.3-cp310-cp310-win_amd64.whl + +onnxruntime-gpu==1.15.1 +qrcode[pil] +rembg==2.0.50 +tensorflow==2.10.1; platform_system == "Windows" +facexlib==0.3.0 From 4f30829e06c41b3685644bfe7bece07e0bcfb70e Mon Sep 17 00:00:00 2001 From: melMass Date: Tue, 8 Aug 2023 23:16:29 +0200 Subject: [PATCH 07/20] =?UTF-8?q?refactor:=20=F0=9F=9A=A7=20tidy?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- reqs.txt | 1 + reqs_portable.txt | 1 + .../interpolate_frames.py | 0 utils.py | 89 +++++++++++++++++++ 4 files changed, 91 insertions(+) rename interpolate_frames.py => scripts/interpolate_frames.py (100%) diff --git a/reqs.txt b/reqs.txt index b0c986f..9049834 100644 --- a/reqs.txt +++ b/reqs.txt @@ -9,3 +9,4 @@ tensorflow; platform_system != "Windows" facexlib==0.3.0 insightface==0.7.3 basicsr==1.4.2 +protobuf==3.19.6 \ No newline at end of file diff --git a/reqs_portable.txt b/reqs_portable.txt index a29f000..ddec4c0 100644 --- a/reqs_portable.txt +++ b/reqs_portable.txt @@ -12,3 +12,4 @@ qrcode[pil] rembg==2.0.50 tensorflow==2.10.1; platform_system == "Windows" facexlib==0.3.0 +protobuf==3.19.6 \ No newline at end of file diff --git a/interpolate_frames.py b/scripts/interpolate_frames.py similarity index 100% rename from interpolate_frames.py rename to scripts/interpolate_frames.py diff --git a/utils.py b/utils.py index e8aa4d3..d6ab36f 100644 --- a/utils.py +++ b/utils.py @@ -5,6 +5,21 @@ import sys from typing import List from .log import log +import signal +from contextlib import suppress +from queue import Queue, Empty +import subprocess +import threading +import os + +# - detect mode +comfy_mode = None +if os.environ.get("COLAB_GPU"): + comfy_mode = "colab" +elif "python_embeded" in sys.executable: + comfy_mode = "embeded" +elif ".venv" in sys.executable: + comfy_mode = "venv" # region MISC Utilities @@ -24,6 +39,79 @@ def add_path(path, prepend=False): sys.path.append(path) +def enqueue_output(out, queue): + for line in iter(out.readline, b""): + queue.put(line) + out.close() + + +def run_command(cmd): + if isinstance(cmd, str): + shell_cmd = cmd + elif isinstance(cmd, list): + shell_cmd = "" + for arg in cmd: + if isinstance(arg, Path): + arg = arg.as_posix() + shell_cmd += f"{arg} " + else: + raise ValueError( + "Invalid 'cmd' argument. It must be a string or a list of arguments." + ) + + process = subprocess.Popen( + shell_cmd, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + universal_newlines=True, + shell=True, + ) + + # Create separate threads to read standard output and standard error streams + stdout_queue = Queue() + stderr_queue = Queue() + stdout_thread = threading.Thread( + target=enqueue_output, args=(process.stdout, stdout_queue) + ) + stderr_thread = threading.Thread( + target=enqueue_output, args=(process.stderr, stderr_queue) + ) + stdout_thread.daemon = True + stderr_thread.daemon = True + stdout_thread.start() + stderr_thread.start() + + interrupted = False + + def signal_handler(signum, frame): + nonlocal interrupted + interrupted = True + print("Command execution interrupted.") + + # Register the signal handler for keyboard interrupts (SIGINT) + signal.signal(signal.SIGINT, signal_handler) + + # Process output from both streams until the process completes or interrupted + while not interrupted and ( + process.poll() is None or not stdout_queue.empty() or not stderr_queue.empty() + ): + with suppress(Empty): + stdout_line = stdout_queue.get_nowait() + if stdout_line.strip() != "": + print(stdout_line.strip()) + with suppress(Empty): + stderr_line = stderr_queue.get_nowait() + if stderr_line.strip() != "": + print(stderr_line.strip()) + return_code = process.returncode + + if return_code == 0 and not interrupted: + print("Command executed successfully!") + else: + if not interrupted: + print(f"Command failed with return code: {return_code}") + + # todo use the requirements library reqs_map = { "onnxruntime": "onnxruntime-gpu==1.15.1", @@ -127,6 +215,7 @@ def tensor2np(tensor: torch.Tensor) -> List[np.ndarray]: # endregion + # region MODEL Utilities def download_antelopev2(): antelopev2_url = "https://drive.google.com/uc?id=18wEUfMNohBJ4K3Ly5wpTejPfDzp-8fI8" From 630b492347f75d7308b31a000061b41d7dfa4a10 Mon Sep 17 00:00:00 2001 From: melMass Date: Wed, 9 Aug 2023 14:33:58 +0200 Subject: [PATCH 08/20] =?UTF-8?q?fix:=20=F0=9F=9A=A7=20wip=20dependency=20?= =?UTF-8?q?installer=20UI?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Will allow to install missing deps/models from the endpoint: /mtb/status --- __init__.py | 16 +++++++++- endpoint.py | 90 +++++++++++++++++++++++++++++++++++++++++++++-------- 2 files changed, 92 insertions(+), 14 deletions(-) diff --git a/__init__.py b/__init__.py index 0439ac0..90e764d 100644 --- a/__init__.py +++ b/__init__.py @@ -187,6 +187,14 @@ def load_nodes(): from .endpoint import endlog if hasattr(PromptServer, "instance"): + restore_deps = ["basicsr"] + swap_deps = ["insightface", "onnxruntime"] + + node_dependency_mapping = { + "FaceSwap": swap_deps, + "LoadFaceSwapModel": swap_deps, + "LoadFaceAnalysisModel": restore_deps, + } @PromptServer.instance.routes.get("/mtb/status") async def get_full_library(request): @@ -202,7 +210,13 @@ async def get_full_library(request): NODE_CLASS_MAPPINGS_DEBUG, title="Registered" ) html_response += endpoint.render_table( - {k: "-" for k in failed}, title="Failed to load" + { + k: {"dependencies": node_dependency_mapping.get(k)} + if node_dependency_mapping.get(k) + else "-" + for k in failed + }, + title="Failed to load", ) return web.Response( diff --git a/endpoint.py b/endpoint.py index d2030e0..9d137f7 100644 --- a/endpoint.py +++ b/endpoint.py @@ -1,11 +1,35 @@ -from .utils import here +from .utils import here, run_command, comfy_mode from aiohttp import web from .log import mklog -import os +import sys endlog = mklog("mtb endpoint") -#- ACTIONS +# - ACTIONS +import requirements + + + +def ACTIONS_installDependency(dependency_names=None): + if dependency_names is None: + return {"error": "No dependency name provided"} + endlog.debug(f"Received Install Dependency request for {dependency_names}") + reqs = [] + if comfy_mode == "embeded": + reqs = list(requirements.parse((here / "reqs_portable.txt").read_text())) + else: + reqs = list(requirements.parse((here / "reqs.txt").read_text())) + print([x.specs for x in reqs]) + print( + "\n".join([f"{x.line} {''.join(x.specs[0] if x.specs else '')}" for x in reqs]) + ) + for dependency_name in dependency_names: + for req in reqs: + if req.name == dependency_name: + endlog.debug(f"Dependency {dependency_name} installed") + break + return {"success": True} + def ACTIONS_getStyles(style_name=None): from .nodes.conditions import StylesLoader @@ -19,10 +43,7 @@ def ACTIONS_getStyles(style_name=None): if not key.startswith("__") and key not in match_list } if style_name: - if style_name in filtered_styles: - return filtered_styles[style_name] - else: - return {"error": "Style not found"} + return filtered_styles.get(style_name, {"error": "Style not found"}) return filtered_styles return {"error": "No styles found"} @@ -35,7 +56,7 @@ async def do_action(request) -> web.Response: endlog.debug(f"Received action request: {name} {args}") - method_name = "ACTIONS_" + name + method_name = f"ACTIONS_{name}" method = globals().get(method_name) if callable(method): @@ -53,16 +74,36 @@ async def do_action(request) -> web.Response: # - HTML UTILS + + +def dependencies_button(name, dependencies): + deps = ",".join([f"'{x}'" for x in dependencies]) + return f""" + + """ + + def render_table(table_dict, sort=True, title=None): - table_rows = "" table_dict = sorted( table_dict.items(), key=lambda item: item[0] ) # Sort the dictionary by keys - for name, description in table_dict: - table_rows += f"{name}{description}" + table_rows = "" + for name, item in table_dict: + if isinstance(item, dict): + if "dependencies" in item: + table_rows += f"{name}" + table_rows += f"{dependencies_button(name,item['dependencies'])}" + + table_rows += "" + else: + table_rows += f"{name}{render_table(item)}" + # elif isinstance(item, str): + # table_rows += f"{name}{item}" + else: + table_rows += f"{name}{item}" - html_response = f""" + return f"""
{"" if title is None else f"

{title}

"} @@ -78,7 +119,6 @@ def render_table(table_dict, sort=True, title=None):
""" - return html_response def render_base_template(title, content): @@ -98,6 +138,29 @@ def render_base_template(title, content): {css_content} +
Back to Comfy @@ -117,5 +180,6 @@ def render_base_template(title, content): + """ From 5ec551143302b2a94ca82e477f684ecee23f1459 Mon Sep 17 00:00:00 2001 From: melMass Date: Wed, 9 Aug 2023 21:59:46 +0200 Subject: [PATCH 09/20] =?UTF-8?q?feat:=20=E2=9C=A8=20add=20UI=20for=20inte?= =?UTF-8?q?rpolate=20clip=20sequential?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- web/mtb_widgets.js | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/web/mtb_widgets.js b/web/mtb_widgets.js index 30283b5..803d1a4 100644 --- a/web/mtb_widgets.js +++ b/web/mtb_widgets.js @@ -781,6 +781,40 @@ const mtb_widgets = { } break } + case 'Interpolate Clip Sequential (mtb)': { + const onNodeCreated = nodeType.prototype.onNodeCreated + nodeType.prototype.onNodeCreated = function () { + const r = onNodeCreated + ? onNodeCreated.apply(this, arguments) + : undefined + const addReplacement = () => { + const input = this.addInput( + `replacement_${this.widgets.length}`, + 'STRING', + '' + ) + console.log(input) + this.addWidget('STRING', `replacement_${this.widgets.length}`, '') + } + //- add + this.addWidget('button', '+', 'add', function (value, widget, node) { + console.log('Button clicked', value, widget, node) + addReplacement() + }) + //- remove + this.addWidget( + 'button', + '-', + 'remove', + function (value, widget, node) { + console.log(`Button clicked: ${value}`, widget, node) + } + ) + + return r + } + break + } case 'Styles Loader (mtb)': { const origGetExtraMenuOptions = nodeType.prototype.getExtraMenuOptions nodeType.prototype.getExtraMenuOptions = function (_, options) { From 2eccba4e33b21d1d080cb2f415f76a93488120f0 Mon Sep 17 00:00:00 2001 From: melMass Date: Thu, 10 Aug 2023 16:34:36 +0200 Subject: [PATCH 10/20] =?UTF-8?q?fix:=20=E2=9A=A1=EF=B8=8F=20move=20getbat?= =?UTF-8?q?chfromhistory=20to=20graphutils?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes #59 --- nodes/graph_utils.py | 107 ++++++++++++++++++++++++++++++++- nodes/image_interpolation.py | 112 +---------------------------------- 2 files changed, 107 insertions(+), 112 deletions(-) diff --git a/nodes/graph_utils.py b/nodes/graph_utils.py index 0288a99..3704a2d 100644 --- a/nodes/graph_utils.py +++ b/nodes/graph_utils.py @@ -1,4 +1,109 @@ from ..log import log +from PIL import Image +import urllib.request +import urllib.parse +import torch +import json +from comfy.cli_args import args +from ..utils import pil2tensor +import io + + +def get_image(filename, subfolder, folder_type): + data = {"filename": filename, "subfolder": subfolder, "type": folder_type} + url_values = urllib.parse.urlencode(data) + with urllib.request.urlopen( + f"http://{args.listen}:{args.port}/view?{url_values}" + ) as response: + return io.BytesIO(response.read()) + + +class GetBatchFromHistory: + """Very experimental node to load images from the history of the server. + + Queue items without output are ignored in the count.""" + + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "enable": ("BOOLEAN", {"default": True}), + "count": ("INT", {"default": 1, "min": 0}), + "offset": ("INT", {"default": 0, "min": -1e9, "max": 1e9}), + "internal_count": ("INT", {"default": 0}), + }, + "optional": { + "passthrough_image": ("IMAGE",), + }, + } + + RETURN_TYPES = ("IMAGE",) + RETURN_NAMES = "images" + CATEGORY = "mtb/animation" + FUNCTION = "load_from_history" + + def load_from_history( + self, + enable=True, + count=0, + offset=0, + internal_count=0, # hacky way to invalidate the node + passthrough_image=None, + ): + if not enable or count == 0: + if passthrough_image is not None: + log.debug("Using passthrough image") + return (passthrough_image,) + log.debug("Load from history is disabled for this iteration") + return (torch.zeros(0),) + frames = [] + + with urllib.request.urlopen( + f"http://{args.listen}:{args.port}/history" + ) as response: + return self.load_batch_frames(response, offset, count, frames) + + def load_batch_frames(self, response, offset, count, frames): + history = json.loads(response.read()) + + output_images = [] + for k, run in history.items(): + for o in run["outputs"]: + for node_id in run["outputs"]: + node_output = run["outputs"][node_id] + if "images" in node_output: + images_output = [] + for image in node_output["images"]: + image_data = get_image( + image["filename"], image["subfolder"], image["type"] + ) + images_output.append(image_data) + output_images.extend(images_output) + if not output_images: + return (torch.zeros(0),) + for i, image in enumerate(list(reversed(output_images))): + if i < offset: + continue + if i >= offset + count: + break + # Decode image as tensor + img = Image.open(image) + log.debug(f"Image from history {i} of shape {img.size}") + frames.append(img) + + # Display the shape of the tensor + # print("Tensor shape:", image_tensor.shape) + + # return (output_images,) + if not frames: + return (torch.zeros(0),) + elif len(frames) != count: + log.warning(f"Expected {count} images, got {len(frames)} instead") + output = pil2tensor( + list(reversed(frames)), + ) + + return (output,) class StringReplace: @@ -72,4 +177,4 @@ def set_range( return (res,) -__nodes__ = [StringReplace, FitNumber] +__nodes__ = [StringReplace, FitNumber, GetBatchFromHistory] diff --git a/nodes/image_interpolation.py b/nodes/image_interpolation.py index 8b1e013..6bd39ae 100644 --- a/nodes/image_interpolation.py +++ b/nodes/image_interpolation.py @@ -9,113 +9,8 @@ import numpy as np import comfy import comfy.utils -from PIL import Image -import urllib.request -import urllib.parse -import json import tensorflow as tf import comfy.model_management as model_management -import io - -from comfy.cli_args import args -from ..utils import pil2tensor - - -def get_image(filename, subfolder, folder_type): - data = {"filename": filename, "subfolder": subfolder, "type": folder_type} - url_values = urllib.parse.urlencode(data) - with urllib.request.urlopen( - f"http://{args.listen}:{args.port}/view?{url_values}" - ) as response: - return io.BytesIO(response.read()) - - -class GetBatchFromHistory: - """Very experimental node to load images from the history of the server. - - Queue items without output are ignored in the count.""" - - @classmethod - def INPUT_TYPES(cls): - return { - "required": { - "enable": ("BOOLEAN", {"default": True}), - "count": ("INT", {"default": 1, "min": 0}), - "offset": ("INT", {"default": 0, "min": -1e9, "max": 1e9}), - "internal_count": ("INT", {"default": 0}), - }, - "optional": { - "passthrough_image": ("IMAGE",), - }, - } - - RETURN_TYPES = ("IMAGE",) - RETURN_NAMES = "images" - CATEGORY = "mtb/animation" - FUNCTION = "load_from_history" - - def load_from_history( - self, - enable=True, - count=0, - offset=0, - internal_count=0, # hacky way to invalidate the node - passthrough_image=None, - ): - if not enable or count == 0: - if passthrough_image is not None: - log.debug("Using passthrough image") - return (passthrough_image,) - log.debug("Load from history is disabled for this iteration") - return (torch.zeros(0),) - frames = [] - - with urllib.request.urlopen( - f"http://{args.listen}:{args.port}/history" - ) as response: - return self.load_batch_frames(response, offset, count, frames) - - def load_batch_frames(self, response, offset, count, frames): - history = json.loads(response.read()) - - output_images = [] - for k, run in history.items(): - for o in run["outputs"]: - for node_id in run["outputs"]: - node_output = run["outputs"][node_id] - if "images" in node_output: - images_output = [] - for image in node_output["images"]: - image_data = get_image( - image["filename"], image["subfolder"], image["type"] - ) - images_output.append(image_data) - output_images.extend(images_output) - if not output_images: - return (torch.zeros(0),) - for i, image in enumerate(list(reversed(output_images))): - if i < offset: - continue - if i >= offset + count: - break - # Decode image as tensor - img = Image.open(image) - log.debug(f"Image from history {i} of shape {img.size}") - frames.append(img) - - # Display the shape of the tensor - # print("Tensor shape:", image_tensor.shape) - - # return (output_images,) - if not frames: - return (torch.zeros(0),) - elif len(frames) != count: - log.warning(f"Expected {count} images, got {len(frames)} instead") - output = pil2tensor( - list(reversed(frames)), - ) - - return (output,) class LoadFilmModel: @@ -256,9 +151,4 @@ def concat_images(self, imageA: torch.Tensor, imageB: torch.Tensor): return (self.concatenate_tensors(imageA, imageB),) -__nodes__ = [ - LoadFilmModel, - FilmInterpolation, - ConcatImages, - GetBatchFromHistory, -] +__nodes__ = [LoadFilmModel, FilmInterpolation, ConcatImages] From 11444662b9198861b62aff06a08b9c9ea01dd8bd Mon Sep 17 00:00:00 2001 From: melMass Date: Thu, 10 Aug 2023 22:54:19 +0200 Subject: [PATCH 11/20] =?UTF-8?q?fix:=20=E2=9C=A8=20refactor=20existing?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- nodes/graph_utils.py | 1 + nodes/transform.py | 70 +++++++++++++++++++++++++++++++------------- utils.py | 5 ++++ 3 files changed, 56 insertions(+), 20 deletions(-) diff --git a/nodes/graph_utils.py b/nodes/graph_utils.py index 3704a2d..70c711b 100644 --- a/nodes/graph_utils.py +++ b/nodes/graph_utils.py @@ -10,6 +10,7 @@ def get_image(filename, subfolder, folder_type): + log.debug(f"Getting image {filename} from {subfolder} of {folder_type}") data = {"filename": filename, "subfolder": subfolder, "type": folder_type} url_values = urllib.parse.urlencode(data) with urllib.request.urlopen( diff --git a/nodes/transform.py b/nodes/transform.py index c0b9be9..858a489 100644 --- a/nodes/transform.py +++ b/nodes/transform.py @@ -1,5 +1,9 @@ import torch import torchvision.transforms.functional as F +from ..utils import log, hex_to_rgb, tensor2pil, pil2tensor +from math import sqrt, ceil +from typing import cast +from PIL import Image class TransformImage: @@ -14,15 +18,19 @@ def INPUT_TYPES(cls): return { "required": { "image": ("IMAGE",), - "x": ("FLOAT", {"default": 0}), - "y": ("FLOAT", {"default": 0}), - "zoom": ("FLOAT", {"default": 1.0, "min": 0.001}), - "angle": ("FLOAT", {"default": 0}), - "shear": ("FLOAT", {"default": 0}), + "x": ("FLOAT", {"default": 0, "step": 1, "min": -4096, "max": 4096}), + "y": ("FLOAT", {"default": 0, "step": 1, "min": -4096, "max": 4096}), + "zoom": ("FLOAT", {"default": 1.0, "min": 0.001, "step": 0.01}), + "angle": ("FLOAT", {"default": 0, "step": 1, "min": -360, "max": 360}), + "shear": ( + "FLOAT", + {"default": 0, "step": 1, "min": -4096, "max": 4096}, + ), "border_handling": ( ["edge", "constant", "reflect", "symmetric"], {"default": "edge"}, ), + "constant_color": ("COLOR", {"default": "black"}), }, } @@ -36,10 +44,17 @@ def transform( x: float, y: float, zoom: float, - angle: int, - shear, + angle: float, + shear: float, border_handling="edge", + constant_color=None, ): + x = int(x) + y = int(y) + angle = int(angle) + + log.debug(f"Zoom: {zoom} | x: {x}, y: {y}, angle: {angle}, shear: {shear}") + if image.size(0) == 0: return (torch.zeros(0),) transformed_images = [] @@ -47,34 +62,49 @@ def transform( new_height, new_width = int(frame_height * zoom), int(frame_width * zoom) + log.debug(f"New height: {new_height}, New width: {new_width}") + + # - Calculate diagonal of the original image + diagonal = sqrt(frame_width**2 + frame_height**2) + max_padding = ceil(diagonal * zoom - min(frame_width, frame_height)) + # Calculate padding for zoom pw = int(frame_width - new_width) ph = int(frame_height - new_height) - padding = [max(0, pw + x), max(0, ph + y), max(0, pw - x), max(0, ph - y)] - for img in image: - img = img.permute(2, 0, 1) - new_height, new_width = int(frame_height * zoom), int(frame_width * zoom) - pw = int(frame_width - new_width) - ph = int(frame_height - new_height) + pw += max_padding + ph += max_padding + + padding = [max(0, pw + x), max(0, ph + y), max(0, pw - x), max(0, ph - y)] - padding = [int(i) for i in padding] + constant_color = hex_to_rgb(constant_color) + log.debug(f"Fill Tuple: {constant_color}") + for img in tensor2pil(image): img = F.pad( img, # transformed_frame, padding=padding, padding_mode=border_handling, + fill=constant_color or 0, ) - img = F.affine(img, angle=angle, scale=zoom, translate=[x, y], shear=shear) + img = cast( + Image.Image, + F.affine(img, angle=angle, scale=zoom, translate=[x, y], shear=shear), + ) - crop = [ph + y, -(ph - y), x + pw, -(pw - x)] + left = abs(padding[0]) + upper = abs(padding[1]) + right = img.width - abs(padding[2]) + bottom = img.height - abs(padding[3]) - img = img[:, crop[0] : crop[1], crop[2] : crop[3]] + # log.debug("crop is [:,top:bottom, left:right] for tensors") + log.debug("crop is [left, top, right, bottom] for PIL") + log.debug(f"crop is {left}, {upper}, {right}, {bottom}") + img = img.crop((left, upper, right, bottom)) - img = img.permute(1, 2, 0) - transformed_images.append(img.unsqueeze(0)) + transformed_images.append(img) - return (torch.cat(transformed_images, dim=0),) + return (pil2tensor(transformed_images),) __nodes__ = [TransformImage] diff --git a/utils.py b/utils.py index d6ab36f..2005f18 100644 --- a/utils.py +++ b/utils.py @@ -22,6 +22,11 @@ comfy_mode = "venv" +def hex_to_rgb(hex_color): + hex_color = hex_color.lstrip("#") + return tuple(int(hex_color[i : i + 2], 16) for i in (0, 2, 4)) + + # region MISC Utilities def add_path(path, prepend=False): if isinstance(path, list): From e7f72f9825da58254e3084b4ba91f76e6cf2cf5f Mon Sep 17 00:00:00 2001 From: melMass Date: Thu, 10 Aug 2023 22:58:12 +0200 Subject: [PATCH 12/20] =?UTF-8?q?fix:=20=F0=9F=8E=A8=20rename=20fun=20to?= =?UTF-8?q?=20generate?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- nodes/{fun.py => generate.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename nodes/{fun.py => generate.py} (100%) diff --git a/nodes/fun.py b/nodes/generate.py similarity index 100% rename from nodes/fun.py rename to nodes/generate.py From 40560f8154d3ddeabf708be4d111370648d466ac Mon Sep 17 00:00:00 2001 From: melMass Date: Thu, 10 Aug 2023 23:22:05 +0200 Subject: [PATCH 13/20] =?UTF-8?q?fix:=20=F0=9F=90=9B=20debug=20rgba?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- nodes/debug.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nodes/debug.py b/nodes/debug.py index 2ca0ff9..1aaf5f0 100644 --- a/nodes/debug.py +++ b/nodes/debug.py @@ -38,9 +38,9 @@ def do_debug(self, **kwargs): b64_imgs = [] for im in image: buffered = io.BytesIO() - im.save(buffered, format="JPEG") + im.save(buffered, format="PNG") b64_imgs.append( - "data:image/jpeg;base64," + "data:image/png;base64," + base64.b64encode(buffered.getvalue()).decode("utf-8") ) From dbdb872b74e18c16feb44bd037abc3aafbb4700f Mon Sep 17 00:00:00 2001 From: melMass Date: Thu, 10 Aug 2023 23:31:46 +0200 Subject: [PATCH 14/20] =?UTF-8?q?feat:=20=F0=9F=94=A5=20add=20any=20to=20s?= =?UTF-8?q?tring=20&=20refactor?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- nodes/conditions.py | 111 +-------------------------------------- nodes/generate.py | 122 ++++++++++++++++++++++++++++++++++++++++++- nodes/graph_utils.py | 34 +++++++++++- web/comfy_shared.js | 2 +- 4 files changed, 157 insertions(+), 112 deletions(-) diff --git a/nodes/conditions.py b/nodes/conditions.py index 71eeef0..33d50e9 100644 --- a/nodes/conditions.py +++ b/nodes/conditions.py @@ -1,5 +1,4 @@ -from ..utils import pil2tensor -from ..utils import here, comfy_dir +from ..utils import here from ..log import log import folder_paths from pathlib import Path @@ -97,110 +96,4 @@ def load_style(self, style_name): return (self.options[style_name][0], self.options[style_name][1]) -class TextToImage: - """Utils to convert text to image using a font - - - The tool looks for any .ttf file in the Comfy folder hierarchy. - """ - - fonts = {} - - def __init__(self): - # - This is executed when the graph is executed, we could conditionaly reload fonts there - pass - - @classmethod - def CACHE_FONTS(cls): - font_extensions = ["*.ttf", "*.otf", "*.woff", "*.woff2", "*.eot"] - fonts = [] - - for extension in font_extensions: - fonts.extend(comfy_dir.glob(f"**/{extension}")) - - if not fonts: - log.warn( - "> No fonts found in the comfy folder, place at least one font file somewhere in ComfyUI's hierarchy" - ) - else: - log.debug(f"> Found {len(fonts)} fonts") - - for font in fonts: - log.debug(f"Adding font {font}") - cls.fonts[font.stem] = font.as_posix() - - @classmethod - def INPUT_TYPES(cls): - if not cls.fonts: - cls.CACHE_FONTS() - else: - log.debug(f"Using cached fonts (count: {len(cls.fonts)})") - return { - "required": { - "text": ( - "STRING", - {"default": "Hello world!"}, - ), - "font": ((sorted(cls.fonts.keys())),), - "wrap": ( - "INT", - {"default": 120, "min": 0, "max": 8096, "step": 1}, - ), - "font_size": ( - "INT", - {"default": 12, "min": 1, "max": 2500, "step": 1}, - ), - "width": ( - "INT", - {"default": 512, "min": 1, "max": 8096, "step": 1}, - ), - "height": ( - "INT", - {"default": 512, "min": 1, "max": 8096, "step": 1}, - ), - # "position": (["INT"], {"default": 0, "min": 0, "max": 100, "step": 1}), - "color": ( - "COLOR", - {"default": "black"}, - ), - "background": ( - "COLOR", - {"default": "white"}, - ), - } - } - - RETURN_TYPES = ("IMAGE",) - RETURN_NAMES = ("image",) - FUNCTION = "text_to_image" - CATEGORY = "mtb/generate" - - def text_to_image( - self, text, font, wrap, font_size, width, height, color, background - ): - from PIL import Image, ImageDraw, ImageFont - import textwrap - - font = self.fonts[font] - font = ImageFont.truetype(font, font_size) - if wrap == 0: - wrap = width / font_size - lines = textwrap.wrap(text, width=wrap) - log.debug(f"Lines: {lines}") - line_height = font.getsize("hg")[1] - img_height = height # line_height * len(lines) - img_width = width # max(font.getsize(line)[0] for line in lines) - - img = Image.new("RGBA", (img_width, img_height), background) - draw = ImageDraw.Draw(img) - y_text = 0 - for line in lines: - width, height = font.getsize(line) - draw.text((0, y_text), line, color, font=font) - y_text += height - - # img.save(os.path.join(folder_paths.base_path, f'{str(uuid.uuid4())}.png')) - return (pil2tensor(img),) - - -__nodes__ = [SmartStep, TextToImage, StylesLoader] +__nodes__ = [SmartStep, StylesLoader] diff --git a/nodes/generate.py b/nodes/generate.py index 332e11a..8fa55ee 100644 --- a/nodes/generate.py +++ b/nodes/generate.py @@ -1,5 +1,7 @@ import qrcode from ..utils import pil2tensor +from ..utils import comfy_dir +from typing import cast from PIL import Image from ..log import log @@ -130,6 +132,9 @@ def INPUT_TYPES(cls): CATEGORY = "mtb/generate" def do_qr(self, url, width, height, error_correct, box_size, border, invert): + log.warning( + "This node will soon be deprecated, there are much better alternatives like https://github.com/coreyryanhanson/comfy-qr" + ) if error_correct == "L" or error_correct not in ["M", "Q", "H"]: error_correct = qrcode.constants.ERROR_CORRECT_L elif error_correct == "M": @@ -159,8 +164,123 @@ def do_qr(self, url, width, height, error_correct, box_size, border, invert): return (pil2tensor(code),) +def bbox_dim(bbox): + left, upper, right, lower = bbox + width = right - left + height = lower - upper + return width, height + + +class TextToImage: + """Utils to convert text to image using a font + + + The tool looks for any .ttf file in the Comfy folder hierarchy. + """ + + fonts = {} + + def __init__(self): + # - This is executed when the graph is executed, we could conditionaly reload fonts there + pass + + @classmethod + def CACHE_FONTS(cls): + font_extensions = ["*.ttf", "*.otf", "*.woff", "*.woff2", "*.eot"] + fonts = [] + + for extension in font_extensions: + fonts.extend(comfy_dir.glob(f"**/{extension}")) + + if not fonts: + log.warn( + "> No fonts found in the comfy folder, place at least one font file somewhere in ComfyUI's hierarchy" + ) + else: + log.debug(f"> Found {len(fonts)} fonts") + + for font in fonts: + log.debug(f"Adding font {font}") + cls.fonts[font.stem] = font.as_posix() + + @classmethod + def INPUT_TYPES(cls): + if not cls.fonts: + cls.CACHE_FONTS() + else: + log.debug(f"Using cached fonts (count: {len(cls.fonts)})") + return { + "required": { + "text": ( + "STRING", + {"default": "Hello world!"}, + ), + "font": ((sorted(cls.fonts.keys())),), + "wrap": ( + "INT", + {"default": 120, "min": 0, "max": 8096, "step": 1}, + ), + "font_size": ( + "INT", + {"default": 12, "min": 1, "max": 2500, "step": 1}, + ), + "width": ( + "INT", + {"default": 512, "min": 1, "max": 8096, "step": 1}, + ), + "height": ( + "INT", + {"default": 512, "min": 1, "max": 8096, "step": 1}, + ), + # "position": (["INT"], {"default": 0, "min": 0, "max": 100, "step": 1}), + "color": ( + "COLOR", + {"default": "black"}, + ), + "background": ( + "COLOR", + {"default": "white"}, + ), + } + } + + RETURN_TYPES = ("IMAGE",) + RETURN_NAMES = ("image",) + FUNCTION = "text_to_image" + CATEGORY = "mtb/generate" + + def text_to_image( + self, text, font, wrap, font_size, width, height, color, background + ): + from PIL import Image, ImageDraw, ImageFont + import textwrap + + font = self.fonts[font] + font = cast(ImageFont.FreeTypeFont, ImageFont.truetype(font, font_size)) + if wrap == 0: + wrap = width / font_size + lines = textwrap.wrap(text, width=wrap) + log.debug(f"Lines: {lines}") + line_height = bbox_dim(font.getbbox("hg"))[1] + img_height = height # line_height * len(lines) + img_width = width # max(font.getsize(line)[0] for line in lines) + + img = Image.new("RGBA", (img_width, img_height), background) + draw = ImageDraw.Draw(img) + y_text = 0 + # - bbox is [left, upper, right, lower] + for line in lines: + width, height = bbox_dim(font.getbbox(line)) + draw.text((0, y_text), line, color, font=font) + y_text += height + + # img.save(os.path.join(folder_paths.base_path, f'{str(uuid.uuid4())}.png')) + return (pil2tensor(img),) + + __nodes__ = [ QrCode, - UnsplashImage + UnsplashImage, + TextToImage # MtbExamples, ] diff --git a/nodes/graph_utils.py b/nodes/graph_utils.py index 70c711b..a9ccc35 100644 --- a/nodes/graph_utils.py +++ b/nodes/graph_utils.py @@ -7,6 +7,7 @@ from comfy.cli_args import args from ..utils import pil2tensor import io +import numpy as np def get_image(filename, subfolder, folder_type): @@ -107,6 +108,37 @@ def load_batch_frames(self, response, offset, count, frames): return (output,) +class AnyToString: + """Tries to take any input and convert it to a string""" + + @classmethod + def INPUT_TYPES(cls): + return { + "required": {"input": ("*")}, + } + + RETURN_TYPES = ("STRING",) + FUNCTION = "do_str" + CATEGORY = "mtb/converters" + + def do_str(self, input): + if isinstance(input, str): + return (input,) + elif isinstance(input, torch.Tensor): + return (f"Tensor of shape {input.shape} and dtype {input.dtype}",) + elif isinstance(input, Image.Image): + return (f"PIL Image of size {input.size} and mode {input.mode}",) + elif isinstance(input, np.ndarray): + return (f"Numpy array of shape {input.shape} and dtype {input.dtype}",) + + elif isinstance(input, dict): + return (f"Dictionary of {len(input)} items, with keys {input.keys()}",) + + else: + log.debug(f"Falling back to string conversion of {input}") + return (str(input),) + + class StringReplace: """Basic string replacement""" @@ -178,4 +210,4 @@ def set_range( return (res,) -__nodes__ = [StringReplace, FitNumber, GetBatchFromHistory] +__nodes__ = [StringReplace, FitNumber, GetBatchFromHistory, AnyToString] diff --git a/web/comfy_shared.js b/web/comfy_shared.js index 3f34b54..f570fac 100644 --- a/web/comfy_shared.js +++ b/web/comfy_shared.js @@ -60,7 +60,7 @@ export function offsetDOMWidget( */ export function getWidgetType(config) { // Special handling for COMBO so we restrict links based on the entries - let type = config[0] + let type = config?.[0] let linkType = type if (type instanceof Array) { type = 'COMBO' From 8523392df74c586dc940841ddbb5069943b16f7d Mon Sep 17 00:00:00 2001 From: melMass Date: Fri, 11 Aug 2023 22:22:07 +0200 Subject: [PATCH 15/20] =?UTF-8?q?fix:=20=E2=9C=A8=20refactor?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- __init__.py | 33 +++---- log.py | 1 - nodes/graph_utils.py | 90 ++++++++++------- utils.py | 227 ++++++++++++++++++++++++++++++++++++++++--- 4 files changed, 284 insertions(+), 67 deletions(-) diff --git a/__init__.py b/__init__.py index 90e764d..4440b9f 100644 --- a/__init__.py +++ b/__init__.py @@ -45,19 +45,15 @@ def extract_nodes_from_source(filename): if isinstance(target, ast.Name) and target.id == "__nodes__": value = ast.get_source_segment(source_code, node.value) node_value = ast.parse(value).body[0].value - if isinstance(node_value, ast.List) or isinstance( - node_value, ast.Tuple - ): - for element in node_value.elts: - if isinstance(element, ast.Name): - print(element.id) - nodes.append(element.id) - + if isinstance(node_value, (ast.List, ast.Tuple)): + nodes.extend( + element.id + for element in node_value.elts + if isinstance(element, ast.Name) + ) break except SyntaxError: log.error("Failed to parse") - pass # File couldn't be parsed - return nodes @@ -240,11 +236,10 @@ async def set_debug(request): log.setLevel(logging.DEBUG) log.debug("Debug mode set from API (/mtb/debug POST route)") - else: - if "MTB_DEBUG" in os.environ: - # del os.environ["MTB_DEBUG"] - os.environ.pop("MTB_DEBUG") - log.setLevel(logging.INFO) + elif "MTB_DEBUG" in os.environ: + # del os.environ["MTB_DEBUG"] + os.environ.pop("MTB_DEBUG") + log.setLevel(logging.INFO) return web.json_response( {"message": f"Debug mode {'set' if enabled else 'unset'}"} @@ -258,7 +253,7 @@ async def get_home(request): # Check if the request prefers HTML content if "text/html" in request.headers.get("Accept", ""): # # Return an HTML page - html_response = f""" + html_response = """