Store masks inside exported videos and convert the example set to ImageEncode/ImageDecode
Mask-in-video (the masks now travel with the video file): - New videometa module: write/read a JSON payload under the lanpaint-mask mp4 metadata tag via PyAV (stream copy, movflags=use_metadata_tags), so exporting produces a NEW file and the source is never modified. - Two server routes: GET /lanpaint/video_mask_meta (read the payload back) and POST /lanpaint/export_mask_video (remux into input/<base>_masked.mp4). - The mask editor exports the painted keyframes (base64 PNGs) + audio intervals into the video; loading a video with the tag auto-restores the masks (editor and node preview). A video without the tag clears the workflow's masks - the video is the source of truth. The export also offers a native save dialog (showSaveFilePicker) when available. - The video widget upload now uses video_upload so MP4s are selectable in the file chooser (was image_upload). - LanPaint_ImageEncode accepts 5D latents from video VAEs encoding a single image (e.g. the Hunyuan video VAE) - fixes the Hunyuan example. Example set conversion (one example per supported model, new files, old pairs removed after being superseded): - New *ImageEncode/*EncodeDecode example workflows for SDXL, Flux.1, Flux.2 Dev, Flux2 Klein, SD3.5, Hunyuan, Ideogram4, Krea2, Qwen Edit, Qwen Image, Z-Image, Wan 2.2 and MiniMax H3 (AV), all using LanPaint_ImageEncode/ImageDecode instead of VAEEncode + SetLatentNoiseMask + VAEDecode + MaskBlend; every example references its own example folder's masked input (hash-verified); preview jpgs generated from the test runs; the H3 example ships its output mp4 as a preview. - HiDream example removed on request; Example_29 added with the H3 masked input video. Fixes along the way: normalize node input.link/output.links fields against the links arrays in all converted workflows (the frontend renders null-link nodes as disconnected); stale Inpainting_mode widget removed from the Flux.2 sampler; Lambda defaults 5.0 (first-order scheme); torchaudio resample test assertion relaxed (edge ringing); the videometa tests updated to the final export signature.
@@ -92,3 +92,17 @@ except ModuleNotFoundError:
|
||||
from .src.LanPaint.nodes import NODE_DISPLAY_NAME_MAPPINGS
|
||||
|
||||
WEB_DIRECTORY = "./web"
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Server routes — registered only when running inside ComfyUI (the ``server``
|
||||
# module and ``folder_paths`` are ComfyUI internals not available in CI/tests).
|
||||
# ---------------------------------------------------------------------------
|
||||
try:
|
||||
from server import PromptServer # noqa: F811 (re-export for convenience)
|
||||
|
||||
from .src.LanPaint.videometa import register_routes
|
||||
|
||||
register_routes(PromptServer.instance)
|
||||
except Exception:
|
||||
pass # not running inside ComfyUI — routes are not needed
|
||||
|
||||
|
Before Width: | Height: | Size: 1.6 MiB |
|
After Width: | Height: | Size: 293 KiB |
|
After Width: | Height: | Size: 412 KiB |
|
Before Width: | Height: | Size: 409 KiB |
|
After Width: | Height: | Size: 280 KiB |
@@ -0,0 +1,671 @@
|
||||
{
|
||||
"id": "978d3a45-3d13-43c6-8ef9-89dc3e74d6ba",
|
||||
"revision": 0,
|
||||
"last_node_id": 84,
|
||||
"last_link_id": 229,
|
||||
"nodes": [
|
||||
{
|
||||
"id": 78,
|
||||
"type": "CLIPTextEncode",
|
||||
"pos": [
|
||||
314.8565368652344,
|
||||
255.63235473632812
|
||||
],
|
||||
"size": [
|
||||
422.84503173828125,
|
||||
164.31304931640625
|
||||
],
|
||||
"flags": {},
|
||||
"order": 2,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"label": "clip",
|
||||
"name": "clip",
|
||||
"type": "CLIP",
|
||||
"link": 194
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"label": "CONDITIONING",
|
||||
"name": "CONDITIONING",
|
||||
"type": "CONDITIONING",
|
||||
"slot_index": 0,
|
||||
"links": [
|
||||
195
|
||||
]
|
||||
}
|
||||
],
|
||||
"title": "CLIP Text Encode (Positive Prompt)",
|
||||
"properties": {
|
||||
"cnr_id": "comfy-core",
|
||||
"ver": "0.3.26",
|
||||
"Node name for S&R": "CLIPTextEncode"
|
||||
},
|
||||
"widgets_values": [
|
||||
"cute anime girl with massive fluffy fennec ears and a big fluffy tail blonde messy long hair blue eyes wearing a maid outfit with a long black gold leaf pattern dress and a white apron mouth open placing a fancy black forest cake with candles on top of a dinner table of an old dark Victorian mansion lit by candlelight with a bright window to the foggy forest and very expensive stuff everywhere there are paintings on the walls"
|
||||
],
|
||||
"color": "#232",
|
||||
"bgcolor": "#353"
|
||||
},
|
||||
{
|
||||
"id": 80,
|
||||
"type": "CLIPTextEncode",
|
||||
"pos": [
|
||||
362.7684020996094,
|
||||
481.0662536621094
|
||||
],
|
||||
"size": [
|
||||
422.84503173828125,
|
||||
164.31304931640625
|
||||
],
|
||||
"flags": {
|
||||
"collapsed": true
|
||||
},
|
||||
"order": 3,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"label": "clip",
|
||||
"name": "clip",
|
||||
"type": "CLIP",
|
||||
"link": 196
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"label": "CONDITIONING",
|
||||
"name": "CONDITIONING",
|
||||
"type": "CONDITIONING",
|
||||
"slot_index": 0,
|
||||
"links": [
|
||||
210
|
||||
]
|
||||
}
|
||||
],
|
||||
"title": "CLIP Text Encode (Negative Prompt)",
|
||||
"properties": {
|
||||
"cnr_id": "comfy-core",
|
||||
"ver": "0.3.26",
|
||||
"Node name for S&R": "CLIPTextEncode"
|
||||
},
|
||||
"widgets_values": [
|
||||
""
|
||||
],
|
||||
"color": "#322",
|
||||
"bgcolor": "#533"
|
||||
},
|
||||
{
|
||||
"id": 79,
|
||||
"type": "FluxGuidance",
|
||||
"pos": [
|
||||
529.1380615234375,
|
||||
156.20236206054688
|
||||
],
|
||||
"size": [
|
||||
211.60000610351562,
|
||||
58
|
||||
],
|
||||
"flags": {},
|
||||
"order": 5,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"label": "conditioning",
|
||||
"name": "conditioning",
|
||||
"type": "CONDITIONING",
|
||||
"link": 195
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"label": "CONDITIONING",
|
||||
"name": "CONDITIONING",
|
||||
"type": "CONDITIONING",
|
||||
"slot_index": 0,
|
||||
"links": [
|
||||
207
|
||||
]
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"cnr_id": "comfy-core",
|
||||
"ver": "0.3.26",
|
||||
"Node name for S&R": "FluxGuidance"
|
||||
},
|
||||
"widgets_values": [
|
||||
3.5
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 77,
|
||||
"type": "CheckpointLoaderSimple",
|
||||
"pos": [
|
||||
-66.25785064697266,
|
||||
484.081787109375
|
||||
],
|
||||
"size": [
|
||||
315,
|
||||
98
|
||||
],
|
||||
"flags": {},
|
||||
"order": 0,
|
||||
"mode": 0,
|
||||
"inputs": [],
|
||||
"outputs": [
|
||||
{
|
||||
"label": "MODEL",
|
||||
"name": "MODEL",
|
||||
"type": "MODEL",
|
||||
"slot_index": 0,
|
||||
"links": [
|
||||
197
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": "CLIP",
|
||||
"name": "CLIP",
|
||||
"type": "CLIP",
|
||||
"slot_index": 1,
|
||||
"links": [
|
||||
194,
|
||||
196
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": "VAE",
|
||||
"name": "VAE",
|
||||
"type": "VAE",
|
||||
"slot_index": 2,
|
||||
"links": [
|
||||
222,
|
||||
226
|
||||
]
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"cnr_id": "comfy-core",
|
||||
"ver": "0.3.26",
|
||||
"Node name for S&R": "CheckpointLoaderSimple"
|
||||
},
|
||||
"widgets_values": [
|
||||
"flux1-dev-fp8.safetensors"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 73,
|
||||
"type": "LanPaint_KSampler",
|
||||
"pos": [
|
||||
1005.0399780273438,
|
||||
296.9553527832031
|
||||
],
|
||||
"size": [
|
||||
413.6495666503906,
|
||||
596
|
||||
],
|
||||
"flags": {},
|
||||
"order": 7,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "model",
|
||||
"type": "MODEL",
|
||||
"link": 197
|
||||
},
|
||||
{
|
||||
"name": "positive",
|
||||
"type": "CONDITIONING",
|
||||
"link": 207
|
||||
},
|
||||
{
|
||||
"name": "negative",
|
||||
"type": "CONDITIONING",
|
||||
"link": 210
|
||||
},
|
||||
{
|
||||
"name": "latent_image",
|
||||
"type": "LATENT",
|
||||
"link": 224
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "LATENT",
|
||||
"type": "LATENT",
|
||||
"slot_index": 0,
|
||||
"links": [
|
||||
225
|
||||
]
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"cnr_id": "LanPaint",
|
||||
"ver": "56bd6c04e89124cd06682b304245d6ddf8b20522",
|
||||
"Node name for S&R": "LanPaint_KSampler"
|
||||
},
|
||||
"widgets_values": [
|
||||
0,
|
||||
"fixed",
|
||||
30,
|
||||
1,
|
||||
"euler",
|
||||
"simple",
|
||||
1,
|
||||
5,
|
||||
"Image First",
|
||||
"LanPaint KSampler. Recommend steps 50, LanPaint NumSteps 1-20 depending on the difficulty of task. LanPaint_EndSigma = 3.0 for anime style, 0.6 for realistic style. For more information, visit https://github.com/scraed/LanPaint",
|
||||
"🖼️ Image Inpainting"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 48,
|
||||
"type": "SaveImage",
|
||||
"pos": [
|
||||
1075.49169921875,
|
||||
1167.2703857421875
|
||||
],
|
||||
"size": [
|
||||
311.2532653808594,
|
||||
484.7096252441406
|
||||
],
|
||||
"flags": {},
|
||||
"order": 9,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "images",
|
||||
"type": "IMAGE",
|
||||
"link": 229
|
||||
}
|
||||
],
|
||||
"outputs": [],
|
||||
"properties": {
|
||||
"cnr_id": "comfy-core",
|
||||
"ver": "0.3.23"
|
||||
},
|
||||
"widgets_values": [
|
||||
"ComfyUI"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 75,
|
||||
"type": "LoadImage",
|
||||
"pos": [
|
||||
45.954593658447266,
|
||||
1150.45556640625
|
||||
],
|
||||
"size": [
|
||||
266.13720703125,
|
||||
487.1314697265625
|
||||
],
|
||||
"flags": {},
|
||||
"order": 1,
|
||||
"mode": 0,
|
||||
"inputs": [],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "IMAGE",
|
||||
"type": "IMAGE",
|
||||
"links": [
|
||||
221,
|
||||
227
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "MASK",
|
||||
"type": "MASK",
|
||||
"links": [
|
||||
221,
|
||||
227
|
||||
]
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"cnr_id": "comfy-core",
|
||||
"ver": "0.3.27",
|
||||
"Node name for S&R": "LoadImage"
|
||||
},
|
||||
"widgets_values": [
|
||||
"Example_7_Masked.png",
|
||||
"image"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 83,
|
||||
"type": "LanPaint_ImageEncode",
|
||||
"pos": [
|
||||
627,
|
||||
780
|
||||
],
|
||||
"size": [
|
||||
195.3150390625,
|
||||
66
|
||||
],
|
||||
"flags": {},
|
||||
"order": 4,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"localized_name": "image",
|
||||
"name": "image",
|
||||
"type": "IMAGE",
|
||||
"link": 221
|
||||
},
|
||||
{
|
||||
"localized_name": "vae",
|
||||
"name": "vae",
|
||||
"type": "VAE",
|
||||
"link": 222
|
||||
},
|
||||
{
|
||||
"localized_name": "mask",
|
||||
"name": "mask",
|
||||
"shape": 7,
|
||||
"type": "MASK",
|
||||
"link": 223
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"localized_name": "latent",
|
||||
"name": "latent",
|
||||
"type": "LATENT",
|
||||
"links": [
|
||||
224
|
||||
]
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "LanPaint_ImageEncode"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 84,
|
||||
"type": "LanPaint_ImageDecode",
|
||||
"pos": [
|
||||
1755,
|
||||
780
|
||||
],
|
||||
"size": [
|
||||
270,
|
||||
118
|
||||
],
|
||||
"flags": {},
|
||||
"order": 6,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"localized_name": "samples",
|
||||
"name": "samples",
|
||||
"type": "LATENT",
|
||||
"link": 225
|
||||
},
|
||||
{
|
||||
"localized_name": "vae",
|
||||
"name": "vae",
|
||||
"type": "VAE",
|
||||
"link": 226
|
||||
},
|
||||
{
|
||||
"localized_name": "image",
|
||||
"name": "image",
|
||||
"shape": 7,
|
||||
"type": "IMAGE",
|
||||
"link": 227
|
||||
},
|
||||
{
|
||||
"localized_name": "mask",
|
||||
"name": "mask",
|
||||
"shape": 7,
|
||||
"type": "MASK",
|
||||
"link": 228
|
||||
},
|
||||
{
|
||||
"localized_name": "blend_overlap",
|
||||
"name": "blend_overlap",
|
||||
"shape": 7,
|
||||
"type": "INT",
|
||||
"widget": {
|
||||
"name": "blend_overlap"
|
||||
},
|
||||
"link": null
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"localized_name": "image",
|
||||
"name": "image",
|
||||
"type": "IMAGE",
|
||||
"links": [
|
||||
229
|
||||
]
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "LanPaint_ImageDecode"
|
||||
},
|
||||
"widgets_values": [
|
||||
9
|
||||
]
|
||||
}
|
||||
],
|
||||
"links": [
|
||||
[
|
||||
194,
|
||||
77,
|
||||
1,
|
||||
78,
|
||||
0,
|
||||
"CLIP"
|
||||
],
|
||||
[
|
||||
195,
|
||||
78,
|
||||
0,
|
||||
79,
|
||||
0,
|
||||
"CONDITIONING"
|
||||
],
|
||||
[
|
||||
196,
|
||||
77,
|
||||
1,
|
||||
80,
|
||||
0,
|
||||
"CLIP"
|
||||
],
|
||||
[
|
||||
197,
|
||||
77,
|
||||
0,
|
||||
73,
|
||||
0,
|
||||
"MODEL"
|
||||
],
|
||||
[
|
||||
207,
|
||||
79,
|
||||
0,
|
||||
73,
|
||||
1,
|
||||
"CONDITIONING"
|
||||
],
|
||||
[
|
||||
210,
|
||||
80,
|
||||
0,
|
||||
73,
|
||||
2,
|
||||
"CONDITIONING"
|
||||
],
|
||||
[
|
||||
221,
|
||||
75,
|
||||
0,
|
||||
83,
|
||||
0,
|
||||
"IMAGE"
|
||||
],
|
||||
[
|
||||
222,
|
||||
77,
|
||||
2,
|
||||
83,
|
||||
1,
|
||||
"VAE"
|
||||
],
|
||||
[
|
||||
223,
|
||||
75,
|
||||
1,
|
||||
83,
|
||||
2,
|
||||
"MASK"
|
||||
],
|
||||
[
|
||||
224,
|
||||
83,
|
||||
0,
|
||||
73,
|
||||
3,
|
||||
"LATENT"
|
||||
],
|
||||
[
|
||||
225,
|
||||
73,
|
||||
0,
|
||||
84,
|
||||
0,
|
||||
"LATENT"
|
||||
],
|
||||
[
|
||||
226,
|
||||
77,
|
||||
2,
|
||||
84,
|
||||
1,
|
||||
"VAE"
|
||||
],
|
||||
[
|
||||
227,
|
||||
75,
|
||||
0,
|
||||
84,
|
||||
2,
|
||||
"IMAGE"
|
||||
],
|
||||
[
|
||||
228,
|
||||
75,
|
||||
1,
|
||||
84,
|
||||
3,
|
||||
"MASK"
|
||||
],
|
||||
[
|
||||
229,
|
||||
84,
|
||||
0,
|
||||
48,
|
||||
0,
|
||||
"IMAGE"
|
||||
]
|
||||
],
|
||||
"groups": [
|
||||
{
|
||||
"id": 1,
|
||||
"title": "Mask image for inpainting.",
|
||||
"bounding": [
|
||||
36.04227828979492,
|
||||
989.7313232421875,
|
||||
278.89093017578125,
|
||||
669.3414916992188
|
||||
],
|
||||
"color": "#3f789e",
|
||||
"font_size": 24,
|
||||
"flags": {}
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"title": "Convert Latents for LanPaint",
|
||||
"bounding": [
|
||||
286.0640563964844,
|
||||
714.343505859375,
|
||||
489.16796875,
|
||||
197.81044006347656
|
||||
],
|
||||
"color": "#3f789e",
|
||||
"font_size": 24,
|
||||
"flags": {}
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"title": "Load Model and Set Prompts",
|
||||
"bounding": [
|
||||
-78.9311294555664,
|
||||
176.08712768554688,
|
||||
1006.1721801757812,
|
||||
514.258544921875
|
||||
],
|
||||
"color": "#3f789e",
|
||||
"font_size": 24,
|
||||
"flags": {}
|
||||
},
|
||||
{
|
||||
"id": 4,
|
||||
"title": "Inpaint with the LanPaint KSampler",
|
||||
"bounding": [
|
||||
960.8922729492188,
|
||||
179.17588806152344,
|
||||
474.8909606933594,
|
||||
630.4742431640625
|
||||
],
|
||||
"color": "#3f789e",
|
||||
"font_size": 24,
|
||||
"flags": {}
|
||||
},
|
||||
{
|
||||
"id": 5,
|
||||
"title": "LanPaint OutPut",
|
||||
"bounding": [
|
||||
1085.6029052734375,
|
||||
994.0775756835938,
|
||||
345.4561767578125,
|
||||
669.4969482421875
|
||||
],
|
||||
"color": "#3f789e",
|
||||
"font_size": 24,
|
||||
"flags": {}
|
||||
},
|
||||
{
|
||||
"id": 11,
|
||||
"title": "LanPaint",
|
||||
"bounding": [
|
||||
-262.59381103515625,
|
||||
140.46656799316406,
|
||||
1737.328857421875,
|
||||
797.4443359375
|
||||
],
|
||||
"color": "#3f789e",
|
||||
"font_size": 24,
|
||||
"flags": {}
|
||||
}
|
||||
],
|
||||
"config": {},
|
||||
"extra": {
|
||||
"ds": {
|
||||
"scale": 0.35049389948139237,
|
||||
"offset": [
|
||||
348.866804381099,
|
||||
308.65057628971834
|
||||
]
|
||||
},
|
||||
"frontendVersion": "1.27.10",
|
||||
"node_versions": {
|
||||
"comfy-core": "0.3.18",
|
||||
"LanPaint": "0f509469ed2cd60c6032f739e282aad5dfc06166"
|
||||
}
|
||||
},
|
||||
"version": 0.4
|
||||
}
|
||||
|
Before Width: | Height: | Size: 1.3 MiB |
@@ -1,786 +0,0 @@
|
||||
{
|
||||
"id": "978d3a45-3d13-43c6-8ef9-89dc3e74d6ba",
|
||||
"revision": 0,
|
||||
"last_node_id": 82,
|
||||
"last_link_id": 220,
|
||||
"nodes": [
|
||||
{
|
||||
"id": 66,
|
||||
"type": "SetLatentNoiseMask",
|
||||
"pos": [
|
||||
514.1915893554688,
|
||||
781.9396362304688
|
||||
],
|
||||
"size": [
|
||||
264.5999755859375,
|
||||
46
|
||||
],
|
||||
"flags": {},
|
||||
"order": 6,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "samples",
|
||||
"type": "LATENT",
|
||||
"link": 216
|
||||
},
|
||||
{
|
||||
"name": "mask",
|
||||
"type": "MASK",
|
||||
"link": 189
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "LATENT",
|
||||
"type": "LATENT",
|
||||
"slot_index": 0,
|
||||
"links": [
|
||||
186
|
||||
]
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"cnr_id": "comfy-core",
|
||||
"ver": "0.3.23",
|
||||
"Node name for S&R": "SetLatentNoiseMask"
|
||||
},
|
||||
"widgets_values": []
|
||||
},
|
||||
{
|
||||
"id": 78,
|
||||
"type": "CLIPTextEncode",
|
||||
"pos": [
|
||||
314.8565368652344,
|
||||
255.63235473632812
|
||||
],
|
||||
"size": [
|
||||
422.84503173828125,
|
||||
164.31304931640625
|
||||
],
|
||||
"flags": {},
|
||||
"order": 2,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"label": "clip",
|
||||
"name": "clip",
|
||||
"type": "CLIP",
|
||||
"link": 194
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"label": "CONDITIONING",
|
||||
"name": "CONDITIONING",
|
||||
"type": "CONDITIONING",
|
||||
"slot_index": 0,
|
||||
"links": [
|
||||
195
|
||||
]
|
||||
}
|
||||
],
|
||||
"title": "CLIP Text Encode (Positive Prompt)",
|
||||
"properties": {
|
||||
"cnr_id": "comfy-core",
|
||||
"ver": "0.3.26",
|
||||
"Node name for S&R": "CLIPTextEncode"
|
||||
},
|
||||
"widgets_values": [
|
||||
"cute anime girl with massive fluffy fennec ears and a big fluffy tail blonde messy long hair blue eyes wearing a maid outfit with a long black gold leaf pattern dress and a white apron mouth open placing a fancy black forest cake with candles on top of a dinner table of an old dark Victorian mansion lit by candlelight with a bright window to the foggy forest and very expensive stuff everywhere there are paintings on the walls"
|
||||
],
|
||||
"color": "#232",
|
||||
"bgcolor": "#353"
|
||||
},
|
||||
{
|
||||
"id": 80,
|
||||
"type": "CLIPTextEncode",
|
||||
"pos": [
|
||||
362.7684020996094,
|
||||
481.0662536621094
|
||||
],
|
||||
"size": [
|
||||
422.84503173828125,
|
||||
164.31304931640625
|
||||
],
|
||||
"flags": {
|
||||
"collapsed": true
|
||||
},
|
||||
"order": 3,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"label": "clip",
|
||||
"name": "clip",
|
||||
"type": "CLIP",
|
||||
"link": 196
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"label": "CONDITIONING",
|
||||
"name": "CONDITIONING",
|
||||
"type": "CONDITIONING",
|
||||
"slot_index": 0,
|
||||
"links": [
|
||||
210
|
||||
]
|
||||
}
|
||||
],
|
||||
"title": "CLIP Text Encode (Negative Prompt)",
|
||||
"properties": {
|
||||
"cnr_id": "comfy-core",
|
||||
"ver": "0.3.26",
|
||||
"Node name for S&R": "CLIPTextEncode"
|
||||
},
|
||||
"widgets_values": [
|
||||
""
|
||||
],
|
||||
"color": "#322",
|
||||
"bgcolor": "#533"
|
||||
},
|
||||
{
|
||||
"id": 79,
|
||||
"type": "FluxGuidance",
|
||||
"pos": [
|
||||
529.1380615234375,
|
||||
156.20236206054688
|
||||
],
|
||||
"size": [
|
||||
211.60000610351562,
|
||||
58
|
||||
],
|
||||
"flags": {},
|
||||
"order": 5,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"label": "conditioning",
|
||||
"name": "conditioning",
|
||||
"type": "CONDITIONING",
|
||||
"link": 195
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"label": "CONDITIONING",
|
||||
"name": "CONDITIONING",
|
||||
"type": "CONDITIONING",
|
||||
"slot_index": 0,
|
||||
"links": [
|
||||
207
|
||||
]
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"cnr_id": "comfy-core",
|
||||
"ver": "0.3.26",
|
||||
"Node name for S&R": "FluxGuidance"
|
||||
},
|
||||
"widgets_values": [
|
||||
3.5
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 77,
|
||||
"type": "CheckpointLoaderSimple",
|
||||
"pos": [
|
||||
-66.25785064697266,
|
||||
484.081787109375
|
||||
],
|
||||
"size": [
|
||||
315,
|
||||
98
|
||||
],
|
||||
"flags": {},
|
||||
"order": 0,
|
||||
"mode": 0,
|
||||
"inputs": [],
|
||||
"outputs": [
|
||||
{
|
||||
"label": "MODEL",
|
||||
"name": "MODEL",
|
||||
"type": "MODEL",
|
||||
"slot_index": 0,
|
||||
"links": [
|
||||
197
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": "CLIP",
|
||||
"name": "CLIP",
|
||||
"type": "CLIP",
|
||||
"slot_index": 1,
|
||||
"links": [
|
||||
194,
|
||||
196
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": "VAE",
|
||||
"name": "VAE",
|
||||
"type": "VAE",
|
||||
"slot_index": 2,
|
||||
"links": [
|
||||
200,
|
||||
203
|
||||
]
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"cnr_id": "comfy-core",
|
||||
"ver": "0.3.26",
|
||||
"Node name for S&R": "CheckpointLoaderSimple"
|
||||
},
|
||||
"widgets_values": [
|
||||
"flux1-dev-fp8.safetensors"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 73,
|
||||
"type": "LanPaint_KSampler",
|
||||
"pos": [
|
||||
1005.0399780273438,
|
||||
296.9553527832031
|
||||
],
|
||||
"size": [
|
||||
413.6495666503906,
|
||||
596
|
||||
],
|
||||
"flags": {},
|
||||
"order": 7,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "model",
|
||||
"type": "MODEL",
|
||||
"link": 197
|
||||
},
|
||||
{
|
||||
"name": "positive",
|
||||
"type": "CONDITIONING",
|
||||
"link": 207
|
||||
},
|
||||
{
|
||||
"name": "negative",
|
||||
"type": "CONDITIONING",
|
||||
"link": 210
|
||||
},
|
||||
{
|
||||
"name": "latent_image",
|
||||
"type": "LATENT",
|
||||
"link": 186
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "LATENT",
|
||||
"type": "LATENT",
|
||||
"slot_index": 0,
|
||||
"links": [
|
||||
187
|
||||
]
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"cnr_id": "LanPaint",
|
||||
"ver": "56bd6c04e89124cd06682b304245d6ddf8b20522",
|
||||
"Node name for S&R": "LanPaint_KSampler"
|
||||
},
|
||||
"widgets_values": [
|
||||
0,
|
||||
"fixed",
|
||||
30,
|
||||
1,
|
||||
"euler",
|
||||
"simple",
|
||||
1,
|
||||
5,
|
||||
"Image First",
|
||||
"LanPaint KSampler. Recommend steps 50, LanPaint NumSteps 1-20 depending on the difficulty of task. LanPaint_EndSigma = 3.0 for anime style, 0.6 for realistic style. For more information, visit https://github.com/scraed/LanPaint",
|
||||
"🖼️ Image Inpainting"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 65,
|
||||
"type": "VAEEncode",
|
||||
"pos": [
|
||||
200.1034698486328,
|
||||
776.3561401367188
|
||||
],
|
||||
"size": [
|
||||
210,
|
||||
46
|
||||
],
|
||||
"flags": {},
|
||||
"order": 4,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "pixels",
|
||||
"type": "IMAGE",
|
||||
"link": 188
|
||||
},
|
||||
{
|
||||
"name": "vae",
|
||||
"type": "VAE",
|
||||
"link": 203
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "LATENT",
|
||||
"type": "LATENT",
|
||||
"slot_index": 0,
|
||||
"links": [
|
||||
216
|
||||
]
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"cnr_id": "comfy-core",
|
||||
"ver": "0.3.23",
|
||||
"Node name for S&R": "VAEEncode"
|
||||
},
|
||||
"widgets_values": []
|
||||
},
|
||||
{
|
||||
"id": 48,
|
||||
"type": "SaveImage",
|
||||
"pos": [
|
||||
1075.49169921875,
|
||||
1167.2703857421875
|
||||
],
|
||||
"size": [
|
||||
311.2532653808594,
|
||||
484.7096252441406
|
||||
],
|
||||
"flags": {},
|
||||
"order": 9,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "images",
|
||||
"type": "IMAGE",
|
||||
"link": 103
|
||||
}
|
||||
],
|
||||
"outputs": [],
|
||||
"properties": {
|
||||
"cnr_id": "comfy-core",
|
||||
"ver": "0.3.23"
|
||||
},
|
||||
"widgets_values": [
|
||||
"ComfyUI"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 81,
|
||||
"type": "LanPaint_MaskBlend",
|
||||
"pos": [
|
||||
1773.0189208984375,
|
||||
1222.9478759765625
|
||||
],
|
||||
"size": [
|
||||
210,
|
||||
98
|
||||
],
|
||||
"flags": {},
|
||||
"order": 10,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "image1",
|
||||
"type": "IMAGE",
|
||||
"link": 219
|
||||
},
|
||||
{
|
||||
"name": "image2",
|
||||
"type": "IMAGE",
|
||||
"link": 218
|
||||
},
|
||||
{
|
||||
"name": "mask",
|
||||
"type": "MASK",
|
||||
"link": 220
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "IMAGE",
|
||||
"type": "IMAGE",
|
||||
"links": [
|
||||
217
|
||||
]
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"cnr_id": "LanPaint",
|
||||
"ver": "4d3d5d17f0105b673df92da5b084cce567c9c712",
|
||||
"Node name for S&R": "LanPaint_MaskBlend"
|
||||
},
|
||||
"widgets_values": [
|
||||
9
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 82,
|
||||
"type": "SaveImage",
|
||||
"pos": [
|
||||
2106.45166015625,
|
||||
1090.682861328125
|
||||
],
|
||||
"size": [
|
||||
311.2532653808594,
|
||||
484.7096252441406
|
||||
],
|
||||
"flags": {},
|
||||
"order": 11,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "images",
|
||||
"type": "IMAGE",
|
||||
"link": 217
|
||||
}
|
||||
],
|
||||
"outputs": [],
|
||||
"properties": {
|
||||
"cnr_id": "comfy-core",
|
||||
"ver": "0.3.23"
|
||||
},
|
||||
"widgets_values": [
|
||||
"ComfyUI"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 8,
|
||||
"type": "VAEDecode",
|
||||
"pos": [
|
||||
1211.46484375,
|
||||
1065.318359375
|
||||
],
|
||||
"size": [
|
||||
210,
|
||||
46
|
||||
],
|
||||
"flags": {},
|
||||
"order": 8,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "samples",
|
||||
"type": "LATENT",
|
||||
"link": 187
|
||||
},
|
||||
{
|
||||
"name": "vae",
|
||||
"type": "VAE",
|
||||
"link": 200
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "IMAGE",
|
||||
"type": "IMAGE",
|
||||
"slot_index": 0,
|
||||
"links": [
|
||||
103,
|
||||
218
|
||||
]
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"cnr_id": "comfy-core",
|
||||
"ver": "0.3.23",
|
||||
"Node name for S&R": "VAEDecode"
|
||||
},
|
||||
"widgets_values": []
|
||||
},
|
||||
{
|
||||
"id": 75,
|
||||
"type": "LoadImage",
|
||||
"pos": [
|
||||
45.954593658447266,
|
||||
1150.45556640625
|
||||
],
|
||||
"size": [
|
||||
266.13720703125,
|
||||
487.1314697265625
|
||||
],
|
||||
"flags": {},
|
||||
"order": 1,
|
||||
"mode": 0,
|
||||
"inputs": [],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "IMAGE",
|
||||
"type": "IMAGE",
|
||||
"links": [
|
||||
188,
|
||||
219
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "MASK",
|
||||
"type": "MASK",
|
||||
"links": [
|
||||
189,
|
||||
220
|
||||
]
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"cnr_id": "comfy-core",
|
||||
"ver": "0.3.27",
|
||||
"Node name for S&R": "LoadImage"
|
||||
},
|
||||
"widgets_values": [
|
||||
"clipspace/clipspace-mask-8865503.5.png [input]",
|
||||
"image"
|
||||
]
|
||||
}
|
||||
],
|
||||
"links": [
|
||||
[
|
||||
103,
|
||||
8,
|
||||
0,
|
||||
48,
|
||||
0,
|
||||
"IMAGE"
|
||||
],
|
||||
[
|
||||
186,
|
||||
66,
|
||||
0,
|
||||
73,
|
||||
3,
|
||||
"LATENT"
|
||||
],
|
||||
[
|
||||
187,
|
||||
73,
|
||||
0,
|
||||
8,
|
||||
0,
|
||||
"LATENT"
|
||||
],
|
||||
[
|
||||
188,
|
||||
75,
|
||||
0,
|
||||
65,
|
||||
0,
|
||||
"IMAGE"
|
||||
],
|
||||
[
|
||||
189,
|
||||
75,
|
||||
1,
|
||||
66,
|
||||
1,
|
||||
"MASK"
|
||||
],
|
||||
[
|
||||
194,
|
||||
77,
|
||||
1,
|
||||
78,
|
||||
0,
|
||||
"CLIP"
|
||||
],
|
||||
[
|
||||
195,
|
||||
78,
|
||||
0,
|
||||
79,
|
||||
0,
|
||||
"CONDITIONING"
|
||||
],
|
||||
[
|
||||
196,
|
||||
77,
|
||||
1,
|
||||
80,
|
||||
0,
|
||||
"CLIP"
|
||||
],
|
||||
[
|
||||
197,
|
||||
77,
|
||||
0,
|
||||
73,
|
||||
0,
|
||||
"MODEL"
|
||||
],
|
||||
[
|
||||
200,
|
||||
77,
|
||||
2,
|
||||
8,
|
||||
1,
|
||||
"VAE"
|
||||
],
|
||||
[
|
||||
203,
|
||||
77,
|
||||
2,
|
||||
65,
|
||||
1,
|
||||
"VAE"
|
||||
],
|
||||
[
|
||||
207,
|
||||
79,
|
||||
0,
|
||||
73,
|
||||
1,
|
||||
"CONDITIONING"
|
||||
],
|
||||
[
|
||||
210,
|
||||
80,
|
||||
0,
|
||||
73,
|
||||
2,
|
||||
"CONDITIONING"
|
||||
],
|
||||
[
|
||||
216,
|
||||
65,
|
||||
0,
|
||||
66,
|
||||
0,
|
||||
"LATENT"
|
||||
],
|
||||
[
|
||||
217,
|
||||
81,
|
||||
0,
|
||||
82,
|
||||
0,
|
||||
"IMAGE"
|
||||
],
|
||||
[
|
||||
218,
|
||||
8,
|
||||
0,
|
||||
81,
|
||||
1,
|
||||
"IMAGE"
|
||||
],
|
||||
[
|
||||
219,
|
||||
75,
|
||||
0,
|
||||
81,
|
||||
0,
|
||||
"IMAGE"
|
||||
],
|
||||
[
|
||||
220,
|
||||
75,
|
||||
1,
|
||||
81,
|
||||
2,
|
||||
"MASK"
|
||||
]
|
||||
],
|
||||
"groups": [
|
||||
{
|
||||
"id": 1,
|
||||
"title": "Mask image for inpainting.",
|
||||
"bounding": [
|
||||
36.04227828979492,
|
||||
989.7313232421875,
|
||||
278.89093017578125,
|
||||
669.3414916992188
|
||||
],
|
||||
"color": "#3f789e",
|
||||
"font_size": 24,
|
||||
"flags": {}
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"title": "Convert Latents for LanPaint",
|
||||
"bounding": [
|
||||
286.0640563964844,
|
||||
714.343505859375,
|
||||
489.16796875,
|
||||
197.81044006347656
|
||||
],
|
||||
"color": "#3f789e",
|
||||
"font_size": 24,
|
||||
"flags": {}
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"title": "Load Model and Set Prompts",
|
||||
"bounding": [
|
||||
-78.9311294555664,
|
||||
176.08712768554688,
|
||||
1006.1721801757812,
|
||||
514.258544921875
|
||||
],
|
||||
"color": "#3f789e",
|
||||
"font_size": 24,
|
||||
"flags": {}
|
||||
},
|
||||
{
|
||||
"id": 4,
|
||||
"title": "Inpaint with the LanPaint KSampler",
|
||||
"bounding": [
|
||||
960.8922729492188,
|
||||
179.17588806152344,
|
||||
474.8909606933594,
|
||||
630.4742431640625
|
||||
],
|
||||
"color": "#3f789e",
|
||||
"font_size": 24,
|
||||
"flags": {}
|
||||
},
|
||||
{
|
||||
"id": 5,
|
||||
"title": "LanPaint OutPut",
|
||||
"bounding": [
|
||||
1085.6029052734375,
|
||||
994.0775756835938,
|
||||
345.4561767578125,
|
||||
669.4969482421875
|
||||
],
|
||||
"color": "#3f789e",
|
||||
"font_size": 24,
|
||||
"flags": {}
|
||||
},
|
||||
{
|
||||
"id": 11,
|
||||
"title": "LanPaint",
|
||||
"bounding": [
|
||||
-262.59381103515625,
|
||||
140.46656799316406,
|
||||
1737.328857421875,
|
||||
797.4443359375
|
||||
],
|
||||
"color": "#3f789e",
|
||||
"font_size": 24,
|
||||
"flags": {}
|
||||
}
|
||||
],
|
||||
"config": {},
|
||||
"extra": {
|
||||
"ds": {
|
||||
"scale": 0.35049389948139237,
|
||||
"offset": [
|
||||
348.866804381099,
|
||||
308.65057628971834
|
||||
]
|
||||
},
|
||||
"frontendVersion": "1.27.10",
|
||||
"node_versions": {
|
||||
"comfy-core": "0.3.18",
|
||||
"LanPaint": "0f509469ed2cd60c6032f739e282aad5dfc06166"
|
||||
}
|
||||
},
|
||||
"version": 0.4
|
||||
}
|
||||
|
After Width: | Height: | Size: 303 KiB |
@@ -0,0 +1,998 @@
|
||||
{
|
||||
"id": "054799b4-76bf-4b4e-b7b7-615d2914285b",
|
||||
"revision": 0,
|
||||
"last_node_id": 119,
|
||||
"last_link_id": 274,
|
||||
"nodes": [
|
||||
{
|
||||
"id": 88,
|
||||
"type": "Note",
|
||||
"pos": [
|
||||
180,
|
||||
440
|
||||
],
|
||||
"size": [
|
||||
350,
|
||||
110
|
||||
],
|
||||
"flags": {},
|
||||
"order": 0,
|
||||
"mode": 0,
|
||||
"inputs": [],
|
||||
"outputs": [],
|
||||
"properties": {
|
||||
"ue_properties": {
|
||||
"widget_ue_connectable": {},
|
||||
"version": "7.1",
|
||||
"input_ue_unconnectable": {}
|
||||
}
|
||||
},
|
||||
"widgets_values": [
|
||||
"Select a fp8 weight_dtype if you are running out of memory."
|
||||
],
|
||||
"color": "#432",
|
||||
"bgcolor": "#653"
|
||||
},
|
||||
{
|
||||
"id": 103,
|
||||
"type": "UNETLoader",
|
||||
"pos": [
|
||||
180,
|
||||
310
|
||||
],
|
||||
"size": [
|
||||
350,
|
||||
82
|
||||
],
|
||||
"flags": {},
|
||||
"order": 1,
|
||||
"mode": 0,
|
||||
"inputs": [],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "MODEL",
|
||||
"type": "MODEL",
|
||||
"slot_index": 0,
|
||||
"links": [
|
||||
250
|
||||
]
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"cnr_id": "comfy-core",
|
||||
"ver": "0.3.23",
|
||||
"Node name for S&R": "UNETLoader",
|
||||
"ue_properties": {
|
||||
"widget_ue_connectable": {},
|
||||
"version": "7.1",
|
||||
"input_ue_unconnectable": {}
|
||||
}
|
||||
},
|
||||
"widgets_values": [
|
||||
"hunyuan_video_t2v_720p_bf16.safetensors",
|
||||
"default"
|
||||
],
|
||||
"color": "#223",
|
||||
"bgcolor": "#335"
|
||||
},
|
||||
{
|
||||
"id": 94,
|
||||
"type": "DualCLIPLoader",
|
||||
"pos": [
|
||||
-160,
|
||||
-60
|
||||
],
|
||||
"size": [
|
||||
350,
|
||||
130
|
||||
],
|
||||
"flags": {},
|
||||
"order": 2,
|
||||
"mode": 0,
|
||||
"inputs": [],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "CLIP",
|
||||
"type": "CLIP",
|
||||
"slot_index": 0,
|
||||
"links": [
|
||||
252,
|
||||
255
|
||||
]
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"cnr_id": "comfy-core",
|
||||
"ver": "0.3.23",
|
||||
"Node name for S&R": "DualCLIPLoader",
|
||||
"ue_properties": {
|
||||
"widget_ue_connectable": {},
|
||||
"version": "7.1",
|
||||
"input_ue_unconnectable": {}
|
||||
}
|
||||
},
|
||||
"widgets_values": [
|
||||
"clip_l.safetensors",
|
||||
"llava_llama3_fp8_scaled.safetensors",
|
||||
"hunyuan_video",
|
||||
"default"
|
||||
],
|
||||
"color": "#223",
|
||||
"bgcolor": "#335"
|
||||
},
|
||||
{
|
||||
"id": 114,
|
||||
"type": "FluxGuidance",
|
||||
"pos": [
|
||||
653.5101318359375,
|
||||
-73.20354461669922
|
||||
],
|
||||
"size": [
|
||||
270,
|
||||
58
|
||||
],
|
||||
"flags": {},
|
||||
"order": 11,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "conditioning",
|
||||
"type": "CONDITIONING",
|
||||
"link": 258
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "CONDITIONING",
|
||||
"type": "CONDITIONING",
|
||||
"links": [
|
||||
259
|
||||
]
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"cnr_id": "comfy-core",
|
||||
"ver": "0.3.65",
|
||||
"Node name for S&R": "FluxGuidance"
|
||||
},
|
||||
"widgets_values": [
|
||||
6
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 105,
|
||||
"type": "LoadImage",
|
||||
"pos": [
|
||||
171.56072998046875,
|
||||
795.9495239257812
|
||||
],
|
||||
"size": [
|
||||
274.080078125,
|
||||
314
|
||||
],
|
||||
"flags": {},
|
||||
"order": 3,
|
||||
"mode": 0,
|
||||
"inputs": [],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "IMAGE",
|
||||
"type": "IMAGE",
|
||||
"links": [
|
||||
266,
|
||||
272
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "MASK",
|
||||
"type": "MASK",
|
||||
"links": [
|
||||
266,
|
||||
272
|
||||
]
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"cnr_id": "comfy-core",
|
||||
"ver": "0.3.59",
|
||||
"Node name for S&R": "LoadImage",
|
||||
"ue_properties": {
|
||||
"widget_ue_connectable": {},
|
||||
"version": "7.1",
|
||||
"input_ue_unconnectable": {}
|
||||
}
|
||||
},
|
||||
"widgets_values": [
|
||||
"Masked_Load_Me_in_Loader (8).png",
|
||||
"image"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 104,
|
||||
"type": "CLIPTextEncode",
|
||||
"pos": [
|
||||
226.1235809326172,
|
||||
-161.8229522705078
|
||||
],
|
||||
"size": [
|
||||
310,
|
||||
240
|
||||
],
|
||||
"flags": {},
|
||||
"order": 8,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "clip",
|
||||
"type": "CLIP",
|
||||
"link": 252
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "CONDITIONING",
|
||||
"type": "CONDITIONING",
|
||||
"slot_index": 0,
|
||||
"links": [
|
||||
258
|
||||
]
|
||||
}
|
||||
],
|
||||
"title": "CLIP Text Encode (Positive Prompt)",
|
||||
"properties": {
|
||||
"cnr_id": "comfy-core",
|
||||
"ver": "0.3.23",
|
||||
"Node name for S&R": "CLIPTextEncode",
|
||||
"ue_properties": {
|
||||
"widget_ue_connectable": {},
|
||||
"version": "7.1",
|
||||
"input_ue_unconnectable": {}
|
||||
}
|
||||
},
|
||||
"widgets_values": [
|
||||
"Close up Portrait of a futuristic cowgirl wearing jeans rides a mechanical horse across a vast cyberpunk desert. The dunes shimmer with holographic projections, glowing in blues and purples. cacti and two massive moons hang low on the horizon, illuminating the dusty air. Cyberpunk western fusion, cinematic, 8K, highly detailed, atmospheric perspective, Blade Runner style."
|
||||
],
|
||||
"color": "#232",
|
||||
"bgcolor": "#353"
|
||||
},
|
||||
{
|
||||
"id": 113,
|
||||
"type": "CLIPTextEncode",
|
||||
"pos": [
|
||||
233.61778259277344,
|
||||
122.75636291503906
|
||||
],
|
||||
"size": [
|
||||
319.00726318359375,
|
||||
88
|
||||
],
|
||||
"flags": {},
|
||||
"order": 9,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "clip",
|
||||
"type": "CLIP",
|
||||
"link": 255
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "CONDITIONING",
|
||||
"type": "CONDITIONING",
|
||||
"slot_index": 0,
|
||||
"links": [
|
||||
254
|
||||
]
|
||||
}
|
||||
],
|
||||
"title": "CLIP Text Encode (Positive Prompt)",
|
||||
"properties": {
|
||||
"cnr_id": "comfy-core",
|
||||
"ver": "0.3.23",
|
||||
"Node name for S&R": "CLIPTextEncode",
|
||||
"ue_properties": {
|
||||
"widget_ue_connectable": {},
|
||||
"version": "7.1",
|
||||
"input_ue_unconnectable": {}
|
||||
}
|
||||
},
|
||||
"widgets_values": [
|
||||
""
|
||||
],
|
||||
"color": "#232",
|
||||
"bgcolor": "#353"
|
||||
},
|
||||
{
|
||||
"id": 92,
|
||||
"type": "Note",
|
||||
"pos": [
|
||||
1340,
|
||||
250
|
||||
],
|
||||
"size": [
|
||||
210,
|
||||
170
|
||||
],
|
||||
"flags": {},
|
||||
"order": 4,
|
||||
"mode": 0,
|
||||
"inputs": [],
|
||||
"outputs": [],
|
||||
"properties": {
|
||||
"ue_properties": {
|
||||
"widget_ue_connectable": {},
|
||||
"version": "7.1",
|
||||
"input_ue_unconnectable": {}
|
||||
}
|
||||
},
|
||||
"widgets_values": [
|
||||
"Use the tiled decode node by default because most people will need it.\n\nLower the tile_size and overlap if you run out of memory."
|
||||
],
|
||||
"color": "#432",
|
||||
"bgcolor": "#653"
|
||||
},
|
||||
{
|
||||
"id": 116,
|
||||
"type": "Note",
|
||||
"pos": [
|
||||
1324.0262451171875,
|
||||
583.7811889648438
|
||||
],
|
||||
"size": [
|
||||
210,
|
||||
170
|
||||
],
|
||||
"flags": {},
|
||||
"order": 5,
|
||||
"mode": 0,
|
||||
"inputs": [],
|
||||
"outputs": [],
|
||||
"properties": {
|
||||
"ue_properties": {
|
||||
"widget_ue_connectable": {},
|
||||
"version": "7.1",
|
||||
"input_ue_unconnectable": {}
|
||||
}
|
||||
},
|
||||
"widgets_values": [
|
||||
"Decrease LanPaint_NumSteps to accelerate"
|
||||
],
|
||||
"color": "#432",
|
||||
"bgcolor": "#653"
|
||||
},
|
||||
{
|
||||
"id": 85,
|
||||
"type": "VAEDecodeTiled",
|
||||
"pos": [
|
||||
1341.9100341796875,
|
||||
49.04500961303711
|
||||
],
|
||||
"size": [
|
||||
210,
|
||||
150
|
||||
],
|
||||
"flags": {},
|
||||
"order": 14,
|
||||
"mode": 4,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "samples",
|
||||
"type": "LATENT",
|
||||
"link": 248
|
||||
},
|
||||
{
|
||||
"name": "vae",
|
||||
"type": "VAE",
|
||||
"link": 237
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "IMAGE",
|
||||
"type": "IMAGE",
|
||||
"slot_index": 0,
|
||||
"links": [
|
||||
251
|
||||
]
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"cnr_id": "comfy-core",
|
||||
"ver": "0.3.18",
|
||||
"Node name for S&R": "VAEDecodeTiled",
|
||||
"ue_properties": {
|
||||
"widget_ue_connectable": {},
|
||||
"version": "7.1",
|
||||
"input_ue_unconnectable": {}
|
||||
}
|
||||
},
|
||||
"widgets_values": [
|
||||
256,
|
||||
64,
|
||||
64,
|
||||
8
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 86,
|
||||
"type": "SaveAnimatedWEBP",
|
||||
"pos": [
|
||||
1661.5201416015625,
|
||||
68.0674819946289
|
||||
],
|
||||
"size": [
|
||||
380,
|
||||
366
|
||||
],
|
||||
"flags": {},
|
||||
"order": 16,
|
||||
"mode": 4,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "images",
|
||||
"type": "IMAGE",
|
||||
"link": 251
|
||||
}
|
||||
],
|
||||
"outputs": [],
|
||||
"properties": {
|
||||
"cnr_id": "comfy-core",
|
||||
"ver": "0.3.18",
|
||||
"Node name for S&R": "SaveAnimatedWEBP",
|
||||
"ue_properties": {
|
||||
"widget_ue_connectable": {},
|
||||
"version": "7.1",
|
||||
"input_ue_unconnectable": {}
|
||||
}
|
||||
},
|
||||
"widgets_values": [
|
||||
"ComfyUI",
|
||||
24,
|
||||
false,
|
||||
80,
|
||||
"default"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 110,
|
||||
"type": "LanPaint_KSampler",
|
||||
"pos": [
|
||||
829.425537109375,
|
||||
218.98995971679688
|
||||
],
|
||||
"size": [
|
||||
400,
|
||||
596
|
||||
],
|
||||
"flags": {},
|
||||
"order": 13,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "model",
|
||||
"type": "MODEL",
|
||||
"link": 239
|
||||
},
|
||||
{
|
||||
"name": "positive",
|
||||
"type": "CONDITIONING",
|
||||
"link": 259
|
||||
},
|
||||
{
|
||||
"name": "negative",
|
||||
"type": "CONDITIONING",
|
||||
"link": 254
|
||||
},
|
||||
{
|
||||
"name": "latent_image",
|
||||
"type": "LATENT",
|
||||
"link": 269
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "LATENT",
|
||||
"type": "LATENT",
|
||||
"links": [
|
||||
248,
|
||||
270
|
||||
]
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"cnr_id": "LanPaint",
|
||||
"ver": "6109df6591a4cf2bc9d3b113d03f7297fa9248e9",
|
||||
"Node name for S&R": "LanPaint_KSampler"
|
||||
},
|
||||
"widgets_values": [
|
||||
534861079790570,
|
||||
"randomize",
|
||||
20,
|
||||
1,
|
||||
"euler",
|
||||
"simple",
|
||||
1,
|
||||
5,
|
||||
"Image First",
|
||||
"LanPaint KSampler. For more info, visit https://github.com/scraed/LanPaint. If you find it useful, please give a star ⭐️!",
|
||||
"🖼️ Image Inpainting"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 78,
|
||||
"type": "VAELoader",
|
||||
"pos": [
|
||||
910,
|
||||
-230
|
||||
],
|
||||
"size": [
|
||||
350.65509033203125,
|
||||
58
|
||||
],
|
||||
"flags": {},
|
||||
"order": 6,
|
||||
"mode": 0,
|
||||
"inputs": [],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "VAE",
|
||||
"type": "VAE",
|
||||
"slot_index": 0,
|
||||
"links": [
|
||||
237,
|
||||
267,
|
||||
271
|
||||
]
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"cnr_id": "comfy-core",
|
||||
"ver": "0.3.18",
|
||||
"Node name for S&R": "VAELoader",
|
||||
"ue_properties": {
|
||||
"widget_ue_connectable": {},
|
||||
"version": "7.1",
|
||||
"input_ue_unconnectable": {}
|
||||
}
|
||||
},
|
||||
"widgets_values": [
|
||||
"hunyuan_video_vae_bf16.safetensors"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 81,
|
||||
"type": "ModelSamplingSD3",
|
||||
"pos": [
|
||||
613.9942626953125,
|
||||
59.9338493347168
|
||||
],
|
||||
"size": [
|
||||
210,
|
||||
58
|
||||
],
|
||||
"flags": {},
|
||||
"order": 7,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "model",
|
||||
"type": "MODEL",
|
||||
"link": 250
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "MODEL",
|
||||
"type": "MODEL",
|
||||
"slot_index": 0,
|
||||
"links": [
|
||||
239
|
||||
]
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"cnr_id": "comfy-core",
|
||||
"ver": "0.3.18",
|
||||
"Node name for S&R": "ModelSamplingSD3",
|
||||
"ue_properties": {
|
||||
"widget_ue_connectable": {},
|
||||
"version": "7.1",
|
||||
"input_ue_unconnectable": {}
|
||||
}
|
||||
},
|
||||
"widgets_values": [
|
||||
7
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 117,
|
||||
"type": "SaveImage",
|
||||
"pos": [
|
||||
1670.939697265625,
|
||||
-104.6227798461914
|
||||
],
|
||||
"size": [
|
||||
531.0060424804688,
|
||||
550.45361328125
|
||||
],
|
||||
"flags": {},
|
||||
"order": 17,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "images",
|
||||
"type": "IMAGE",
|
||||
"link": 274
|
||||
}
|
||||
],
|
||||
"outputs": [],
|
||||
"properties": {
|
||||
"cnr_id": "comfy-core",
|
||||
"ver": "0.3.65"
|
||||
},
|
||||
"widgets_values": [
|
||||
"ComfyUI"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 118,
|
||||
"type": "LanPaint_ImageEncode",
|
||||
"pos": [
|
||||
558,
|
||||
922
|
||||
],
|
||||
"size": [
|
||||
300,
|
||||
150
|
||||
],
|
||||
"flags": {},
|
||||
"order": 0,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "image",
|
||||
"type": "IMAGE",
|
||||
"link": 266
|
||||
},
|
||||
{
|
||||
"name": "vae",
|
||||
"type": "VAE",
|
||||
"link": 267
|
||||
},
|
||||
{
|
||||
"name": "mask",
|
||||
"type": "MASK",
|
||||
"link": 268
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "latent",
|
||||
"type": "LATENT",
|
||||
"links": [
|
||||
269
|
||||
]
|
||||
}
|
||||
],
|
||||
"widgets_values": [],
|
||||
"properties": {
|
||||
"Node name for S&R": "LanPaint_ImageEncode",
|
||||
"cnr_id": "LanPaint"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 119,
|
||||
"type": "LanPaint_ImageDecode",
|
||||
"pos": [
|
||||
1670,
|
||||
-110
|
||||
],
|
||||
"size": [
|
||||
300,
|
||||
200
|
||||
],
|
||||
"flags": {},
|
||||
"order": 0,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "samples",
|
||||
"type": "LATENT",
|
||||
"link": 270
|
||||
},
|
||||
{
|
||||
"name": "vae",
|
||||
"type": "VAE",
|
||||
"link": 271
|
||||
},
|
||||
{
|
||||
"name": "image",
|
||||
"type": "IMAGE",
|
||||
"link": 272
|
||||
},
|
||||
{
|
||||
"name": "mask",
|
||||
"type": "MASK",
|
||||
"link": 273
|
||||
},
|
||||
{
|
||||
"name": "blend_overlap",
|
||||
"type": "INT",
|
||||
"widget": {
|
||||
"name": "blend_overlap"
|
||||
}
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "image",
|
||||
"type": "IMAGE",
|
||||
"links": [
|
||||
274
|
||||
]
|
||||
}
|
||||
],
|
||||
"widgets_values": [
|
||||
9
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "LanPaint_ImageDecode",
|
||||
"cnr_id": "LanPaint"
|
||||
}
|
||||
}
|
||||
],
|
||||
"links": [
|
||||
[
|
||||
237,
|
||||
78,
|
||||
0,
|
||||
85,
|
||||
1,
|
||||
"VAE"
|
||||
],
|
||||
[
|
||||
239,
|
||||
81,
|
||||
0,
|
||||
110,
|
||||
0,
|
||||
"MODEL"
|
||||
],
|
||||
[
|
||||
248,
|
||||
110,
|
||||
0,
|
||||
85,
|
||||
0,
|
||||
"LATENT"
|
||||
],
|
||||
[
|
||||
250,
|
||||
103,
|
||||
0,
|
||||
81,
|
||||
0,
|
||||
"MODEL"
|
||||
],
|
||||
[
|
||||
251,
|
||||
85,
|
||||
0,
|
||||
86,
|
||||
0,
|
||||
"IMAGE"
|
||||
],
|
||||
[
|
||||
252,
|
||||
94,
|
||||
0,
|
||||
104,
|
||||
0,
|
||||
"CLIP"
|
||||
],
|
||||
[
|
||||
254,
|
||||
113,
|
||||
0,
|
||||
110,
|
||||
2,
|
||||
"CONDITIONING"
|
||||
],
|
||||
[
|
||||
255,
|
||||
94,
|
||||
0,
|
||||
113,
|
||||
0,
|
||||
"CLIP"
|
||||
],
|
||||
[
|
||||
258,
|
||||
104,
|
||||
0,
|
||||
114,
|
||||
0,
|
||||
"CONDITIONING"
|
||||
],
|
||||
[
|
||||
259,
|
||||
114,
|
||||
0,
|
||||
110,
|
||||
1,
|
||||
"CONDITIONING"
|
||||
],
|
||||
[
|
||||
266,
|
||||
105,
|
||||
0,
|
||||
118,
|
||||
0,
|
||||
"IMAGE"
|
||||
],
|
||||
[
|
||||
267,
|
||||
78,
|
||||
0,
|
||||
118,
|
||||
1,
|
||||
"VAE"
|
||||
],
|
||||
[
|
||||
268,
|
||||
105,
|
||||
1,
|
||||
118,
|
||||
2,
|
||||
"MASK"
|
||||
],
|
||||
[
|
||||
269,
|
||||
118,
|
||||
0,
|
||||
110,
|
||||
3,
|
||||
"LATENT"
|
||||
],
|
||||
[
|
||||
270,
|
||||
110,
|
||||
0,
|
||||
119,
|
||||
0,
|
||||
"LATENT"
|
||||
],
|
||||
[
|
||||
271,
|
||||
78,
|
||||
0,
|
||||
119,
|
||||
1,
|
||||
"VAE"
|
||||
],
|
||||
[
|
||||
272,
|
||||
105,
|
||||
0,
|
||||
119,
|
||||
2,
|
||||
"IMAGE"
|
||||
],
|
||||
[
|
||||
273,
|
||||
105,
|
||||
1,
|
||||
119,
|
||||
3,
|
||||
"MASK"
|
||||
],
|
||||
[
|
||||
274,
|
||||
119,
|
||||
0,
|
||||
117,
|
||||
0,
|
||||
"IMAGE"
|
||||
]
|
||||
],
|
||||
"groups": [
|
||||
{
|
||||
"id": 1,
|
||||
"title": "Text Encoding",
|
||||
"bounding": [
|
||||
-190,
|
||||
-140,
|
||||
730,
|
||||
340
|
||||
],
|
||||
"color": "#8A8",
|
||||
"font_size": 24,
|
||||
"flags": {}
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"title": "VAE Decoding & Output",
|
||||
"bounding": [
|
||||
1320,
|
||||
-140,
|
||||
1110,
|
||||
600
|
||||
],
|
||||
"color": "#3f789e",
|
||||
"font_size": 24,
|
||||
"flags": {}
|
||||
},
|
||||
{
|
||||
"id": 4,
|
||||
"title": "Sampling Process",
|
||||
"bounding": [
|
||||
570,
|
||||
-140,
|
||||
730,
|
||||
980
|
||||
],
|
||||
"color": "#3f789e",
|
||||
"font_size": 24,
|
||||
"flags": {}
|
||||
},
|
||||
{
|
||||
"id": 5,
|
||||
"title": "HunyuanVideo Model",
|
||||
"bounding": [
|
||||
170,
|
||||
230,
|
||||
370.5005798339844,
|
||||
330.7360534667969
|
||||
],
|
||||
"color": "#88A",
|
||||
"font_size": 24,
|
||||
"flags": {}
|
||||
},
|
||||
{
|
||||
"id": 6,
|
||||
"title": "Guidance",
|
||||
"bounding": [
|
||||
590,
|
||||
-90,
|
||||
680,
|
||||
260
|
||||
],
|
||||
"color": "#8AA",
|
||||
"font_size": 24,
|
||||
"flags": {}
|
||||
},
|
||||
{
|
||||
"id": 7,
|
||||
"title": "sampling",
|
||||
"bounding": [
|
||||
590,
|
||||
200,
|
||||
680,
|
||||
440
|
||||
],
|
||||
"color": "#b06634",
|
||||
"font_size": 24,
|
||||
"flags": {}
|
||||
}
|
||||
],
|
||||
"config": {},
|
||||
"extra": {
|
||||
"ds": {
|
||||
"scale": 0.4870776476432723,
|
||||
"offset": [
|
||||
85.03251058761712,
|
||||
779.6524460150363
|
||||
]
|
||||
},
|
||||
"frontendVersion": "1.27.10",
|
||||
"groupNodes": {},
|
||||
"ue_links": [],
|
||||
"links_added_by_ue": [],
|
||||
"VHS_latentpreview": false,
|
||||
"VHS_latentpreviewrate": 0,
|
||||
"VHS_MetadataImage": true,
|
||||
"VHS_KeepIntermediate": true
|
||||
},
|
||||
"version": 0.4
|
||||
}
|
||||
|
Before Width: | Height: | Size: 670 KiB |
|
After Width: | Height: | Size: 598 KiB |
|
Before Width: | Height: | Size: 603 KiB |
|
After Width: | Height: | Size: 275 KiB |
|
Before Width: | Height: | Size: 277 KiB |
|
After Width: | Height: | Size: 88 KiB |
@@ -0,0 +1,731 @@
|
||||
{
|
||||
"id": "11cce4ab-536b-4f42-a95c-0be437d04ace",
|
||||
"revision": 0,
|
||||
"last_node_id": 137,
|
||||
"last_link_id": 364,
|
||||
"nodes": [
|
||||
{
|
||||
"id": 74,
|
||||
"type": "LanPaint_KSampler",
|
||||
"pos": [
|
||||
276.219970703125,
|
||||
179.55892944335938
|
||||
],
|
||||
"size": [
|
||||
388.97625732421875,
|
||||
572
|
||||
],
|
||||
"flags": {},
|
||||
"order": 17,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "model",
|
||||
"type": "MODEL",
|
||||
"link": 325
|
||||
},
|
||||
{
|
||||
"name": "positive",
|
||||
"type": "CONDITIONING",
|
||||
"link": 323
|
||||
},
|
||||
{
|
||||
"name": "negative",
|
||||
"type": "CONDITIONING",
|
||||
"link": 324
|
||||
},
|
||||
{
|
||||
"name": "latent_image",
|
||||
"type": "LATENT",
|
||||
"link": 359
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "LATENT",
|
||||
"type": "LATENT",
|
||||
"slot_index": 0,
|
||||
"links": [
|
||||
360
|
||||
]
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "LanPaint_KSampler",
|
||||
"cnr_id": "LanPaint",
|
||||
"ver": "56bd6c04e89124cd06682b304245d6ddf8b20522"
|
||||
},
|
||||
"widgets_values": [
|
||||
0,
|
||||
"fixed",
|
||||
20,
|
||||
4,
|
||||
"euler",
|
||||
"simple",
|
||||
1,
|
||||
5,
|
||||
"Image First",
|
||||
"LanPaint KSampler. For more info, visit https://github.com/scraed/LanPaint. If you find it useful, please give a star ⭐️!",
|
||||
"🖼️ Image Inpainting",
|
||||
"lanpaint_star_button"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 113,
|
||||
"type": "SaveImage",
|
||||
"pos": [
|
||||
807.1268310546875,
|
||||
868.395263671875
|
||||
],
|
||||
"size": [
|
||||
311.2532653808594,
|
||||
484.7096252441406
|
||||
],
|
||||
"flags": {},
|
||||
"order": 20,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "images",
|
||||
"type": "IMAGE",
|
||||
"link": 364
|
||||
}
|
||||
],
|
||||
"outputs": [],
|
||||
"properties": {
|
||||
"cnr_id": "comfy-core",
|
||||
"ver": "0.3.23"
|
||||
},
|
||||
"widgets_values": [
|
||||
"ComfyUI"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 117,
|
||||
"type": "CLIPLoader",
|
||||
"pos": [
|
||||
-824.4296875,
|
||||
177.9814910888672
|
||||
],
|
||||
"size": [
|
||||
330,
|
||||
110
|
||||
],
|
||||
"flags": {},
|
||||
"order": 0,
|
||||
"mode": 0,
|
||||
"inputs": [],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "CLIP",
|
||||
"type": "CLIP",
|
||||
"slot_index": 0,
|
||||
"links": [
|
||||
318,
|
||||
319
|
||||
]
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "CLIPLoader",
|
||||
"cnr_id": "comfy-core",
|
||||
"ver": "0.3.48",
|
||||
"models": [
|
||||
{
|
||||
"name": "qwen_2.5_vl_7b_fp8_scaled.safetensors",
|
||||
"url": "https://huggingface.co/Comfy-Org/Qwen-Image_ComfyUI/resolve/main/split_files/text_encoders/qwen_2.5_vl_7b_fp8_scaled.safetensors",
|
||||
"directory": "text_encoders"
|
||||
}
|
||||
],
|
||||
"enableTabs": false,
|
||||
"tabWidth": 65,
|
||||
"tabXOffset": 10,
|
||||
"hasSecondTab": false,
|
||||
"secondTabText": "Send Back",
|
||||
"secondTabOffset": 80,
|
||||
"secondTabWidth": 65,
|
||||
"widget_ue_connectable": {}
|
||||
},
|
||||
"widgets_values": [
|
||||
"qwen_2.5_vl_7b_fp8_scaled.safetensors",
|
||||
"qwen_image",
|
||||
"default"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 122,
|
||||
"type": "ModelSamplingAuraFlow",
|
||||
"pos": [
|
||||
-34.14249038696289,
|
||||
-43.64523696899414
|
||||
],
|
||||
"size": [
|
||||
300,
|
||||
58
|
||||
],
|
||||
"flags": {},
|
||||
"order": 7,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "model",
|
||||
"type": "MODEL",
|
||||
"link": 320
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "MODEL",
|
||||
"type": "MODEL",
|
||||
"links": [
|
||||
325
|
||||
]
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "ModelSamplingAuraFlow",
|
||||
"cnr_id": "comfy-core",
|
||||
"ver": "0.3.48",
|
||||
"enableTabs": false,
|
||||
"tabWidth": 65,
|
||||
"tabXOffset": 10,
|
||||
"hasSecondTab": false,
|
||||
"secondTabText": "Send Back",
|
||||
"secondTabOffset": 80,
|
||||
"secondTabWidth": 65,
|
||||
"widget_ue_connectable": {}
|
||||
},
|
||||
"widgets_values": [
|
||||
3.5
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 119,
|
||||
"type": "UNETLoader",
|
||||
"pos": [
|
||||
-824.4296875,
|
||||
37.98154830932617
|
||||
],
|
||||
"size": [
|
||||
330,
|
||||
90
|
||||
],
|
||||
"flags": {},
|
||||
"order": 1,
|
||||
"mode": 0,
|
||||
"inputs": [],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "MODEL",
|
||||
"type": "MODEL",
|
||||
"slot_index": 0,
|
||||
"links": [
|
||||
320
|
||||
]
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "UNETLoader",
|
||||
"cnr_id": "comfy-core",
|
||||
"ver": "0.3.48",
|
||||
"models": [
|
||||
{
|
||||
"name": "qwen_image_fp8_e4m3fn.safetensors",
|
||||
"url": "https://huggingface.co/Comfy-Org/Qwen-Image_ComfyUI/resolve/main/split_files/diffusion_models/qwen_image_fp8_e4m3fn.safetensors",
|
||||
"directory": "diffusion_models"
|
||||
}
|
||||
],
|
||||
"enableTabs": false,
|
||||
"tabWidth": 65,
|
||||
"tabXOffset": 10,
|
||||
"hasSecondTab": false,
|
||||
"secondTabText": "Send Back",
|
||||
"secondTabOffset": 80,
|
||||
"secondTabWidth": 65,
|
||||
"widget_ue_connectable": {}
|
||||
},
|
||||
"widgets_values": [
|
||||
"qwen_image_fp8_e4m3fn.safetensors",
|
||||
"default"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 128,
|
||||
"type": "MarkdownNote",
|
||||
"pos": [
|
||||
715.929931640625,
|
||||
371.1071472167969
|
||||
],
|
||||
"size": [
|
||||
300,
|
||||
190
|
||||
],
|
||||
"flags": {},
|
||||
"order": 2,
|
||||
"mode": 0,
|
||||
"inputs": [],
|
||||
"outputs": [],
|
||||
"title": "KSampler settings",
|
||||
"properties": {},
|
||||
"widgets_values": [
|
||||
"Decrease **LanPaint_NumSteps** for faster generation. \n"
|
||||
],
|
||||
"color": "#432",
|
||||
"bgcolor": "#653"
|
||||
},
|
||||
{
|
||||
"id": 123,
|
||||
"type": "LoadImage",
|
||||
"pos": [
|
||||
-935.2606748101325,
|
||||
786.6170753867922
|
||||
],
|
||||
"size": [
|
||||
262.12347412109375,
|
||||
487.22296142578125
|
||||
],
|
||||
"flags": {},
|
||||
"order": 3,
|
||||
"mode": 0,
|
||||
"inputs": [],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "IMAGE",
|
||||
"type": "IMAGE",
|
||||
"slot_index": 0,
|
||||
"links": [
|
||||
356,
|
||||
362
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "MASK",
|
||||
"type": "MASK",
|
||||
"slot_index": 1,
|
||||
"links": [
|
||||
357,
|
||||
363
|
||||
]
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "LoadImage",
|
||||
"cnr_id": "comfy-core",
|
||||
"ver": "0.3.23"
|
||||
},
|
||||
"widgets_values": [
|
||||
"Example_12_Masked.png",
|
||||
"image"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 118,
|
||||
"type": "VAELoader",
|
||||
"pos": [
|
||||
-824.4296875,
|
||||
327.9817199707031
|
||||
],
|
||||
"size": [
|
||||
330,
|
||||
60
|
||||
],
|
||||
"flags": {},
|
||||
"order": 4,
|
||||
"mode": 0,
|
||||
"inputs": [],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "VAE",
|
||||
"type": "VAE",
|
||||
"slot_index": 0,
|
||||
"links": [
|
||||
358,
|
||||
361
|
||||
]
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "VAELoader",
|
||||
"cnr_id": "comfy-core",
|
||||
"ver": "0.3.48",
|
||||
"models": [
|
||||
{
|
||||
"name": "qwen_image_vae.safetensors",
|
||||
"url": "https://huggingface.co/Comfy-Org/Qwen-Image_ComfyUI/resolve/main/split_files/vae/qwen_image_vae.safetensors",
|
||||
"directory": "vae"
|
||||
}
|
||||
],
|
||||
"enableTabs": false,
|
||||
"tabWidth": 65,
|
||||
"tabXOffset": 10,
|
||||
"hasSecondTab": false,
|
||||
"secondTabText": "Send Back",
|
||||
"secondTabOffset": 80,
|
||||
"secondTabWidth": 65,
|
||||
"widget_ue_connectable": {}
|
||||
},
|
||||
"widgets_values": [
|
||||
"qwen_image_vae.safetensors"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 121,
|
||||
"type": "CLIPTextEncode",
|
||||
"pos": [
|
||||
-454.4298095703125,
|
||||
247.9816436767578
|
||||
],
|
||||
"size": [
|
||||
425.27801513671875,
|
||||
180.6060791015625
|
||||
],
|
||||
"flags": {},
|
||||
"order": 6,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "clip",
|
||||
"type": "CLIP",
|
||||
"link": 319
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "CONDITIONING",
|
||||
"type": "CONDITIONING",
|
||||
"slot_index": 0,
|
||||
"links": [
|
||||
324
|
||||
]
|
||||
}
|
||||
],
|
||||
"title": "CLIP Text Encode (Negative Prompt)",
|
||||
"properties": {
|
||||
"Node name for S&R": "CLIPTextEncode",
|
||||
"cnr_id": "comfy-core",
|
||||
"ver": "0.3.48",
|
||||
"enableTabs": false,
|
||||
"tabWidth": 65,
|
||||
"tabXOffset": 10,
|
||||
"hasSecondTab": false,
|
||||
"secondTabText": "Send Back",
|
||||
"secondTabOffset": 80,
|
||||
"secondTabWidth": 65,
|
||||
"widget_ue_connectable": {}
|
||||
},
|
||||
"widgets_values": [
|
||||
" low quality, bad anatomy, extra digits, missing digits, extra limbs, missing limbs"
|
||||
],
|
||||
"color": "#322",
|
||||
"bgcolor": "#533"
|
||||
},
|
||||
{
|
||||
"id": 120,
|
||||
"type": "CLIPTextEncode",
|
||||
"pos": [
|
||||
-454.86480712890625,
|
||||
41.89194869995117
|
||||
],
|
||||
"size": [
|
||||
422.84503173828125,
|
||||
164.31304931640625
|
||||
],
|
||||
"flags": {},
|
||||
"order": 5,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "clip",
|
||||
"type": "CLIP",
|
||||
"link": 318
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "CONDITIONING",
|
||||
"type": "CONDITIONING",
|
||||
"slot_index": 0,
|
||||
"links": [
|
||||
323
|
||||
]
|
||||
}
|
||||
],
|
||||
"title": "CLIP Text Encode (Positive Prompt)",
|
||||
"properties": {
|
||||
"Node name for S&R": "CLIPTextEncode",
|
||||
"cnr_id": "comfy-core",
|
||||
"ver": "0.3.48",
|
||||
"enableTabs": false,
|
||||
"tabWidth": 65,
|
||||
"tabXOffset": 10,
|
||||
"hasSecondTab": false,
|
||||
"secondTabText": "Send Back",
|
||||
"secondTabOffset": 80,
|
||||
"secondTabWidth": 65,
|
||||
"widget_ue_connectable": {}
|
||||
},
|
||||
"widgets_values": [
|
||||
"Cyberpunk-style Einstein portrait: He wears a sleek black coat with glowing cyan circuit patterns, silver-rimmed cybernetic glasses (lenses display faint data streams), and his hair has subtle neon blue highlights. His expression is calm, with a faint smile. Behind him: a dark, rain-washed cybercity backdrop—towering skyscrapers with flickering holographic ads, wet pavement reflecting neon pink/magenta lights. In front of him: giant, glowing white 3D text of \"LanPaint\", with electric blue energy pulses swirling around the equation. Cinematic lighting, hyper-detailed textures, rain droplets visible in the air."
|
||||
],
|
||||
"color": "#232",
|
||||
"bgcolor": "#353"
|
||||
},
|
||||
{
|
||||
"id": 136,
|
||||
"type": "LanPaint_ImageEncode",
|
||||
"pos": [
|
||||
4360,
|
||||
2000
|
||||
],
|
||||
"size": [
|
||||
270,
|
||||
110
|
||||
],
|
||||
"flags": {},
|
||||
"order": 50,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"localized_name": "image",
|
||||
"name": "image",
|
||||
"type": "IMAGE",
|
||||
"link": 356
|
||||
},
|
||||
{
|
||||
"localized_name": "vae",
|
||||
"name": "vae",
|
||||
"type": "VAE",
|
||||
"link": 358
|
||||
},
|
||||
{
|
||||
"localized_name": "mask",
|
||||
"name": "mask",
|
||||
"type": "MASK",
|
||||
"link": 357
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"localized_name": "latent",
|
||||
"name": "latent",
|
||||
"type": "LATENT",
|
||||
"links": [
|
||||
359
|
||||
]
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "LanPaint_ImageEncode",
|
||||
"cnr_id": "LanPaint"
|
||||
},
|
||||
"widgets_values": []
|
||||
},
|
||||
{
|
||||
"id": 137,
|
||||
"type": "LanPaint_ImageDecode",
|
||||
"pos": [
|
||||
4670,
|
||||
2000
|
||||
],
|
||||
"size": [
|
||||
270,
|
||||
150
|
||||
],
|
||||
"flags": {},
|
||||
"order": 51,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"localized_name": "samples",
|
||||
"name": "samples",
|
||||
"type": "LATENT",
|
||||
"link": 360
|
||||
},
|
||||
{
|
||||
"localized_name": "vae",
|
||||
"name": "vae",
|
||||
"type": "VAE",
|
||||
"link": 361
|
||||
},
|
||||
{
|
||||
"localized_name": "image",
|
||||
"name": "image",
|
||||
"type": "IMAGE",
|
||||
"link": 362
|
||||
},
|
||||
{
|
||||
"localized_name": "mask",
|
||||
"name": "mask",
|
||||
"type": "MASK",
|
||||
"link": 363
|
||||
},
|
||||
{
|
||||
"localized_name": "blend_overlap",
|
||||
"name": "blend_overlap",
|
||||
"type": "INT",
|
||||
"widget": {
|
||||
"name": "blend_overlap"
|
||||
},
|
||||
"link": null
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"localized_name": "image",
|
||||
"name": "image",
|
||||
"type": "IMAGE",
|
||||
"links": [
|
||||
364
|
||||
]
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "LanPaint_ImageDecode",
|
||||
"cnr_id": "LanPaint"
|
||||
},
|
||||
"widgets_values": [
|
||||
9
|
||||
]
|
||||
}
|
||||
],
|
||||
"links": [
|
||||
[
|
||||
318,
|
||||
117,
|
||||
0,
|
||||
120,
|
||||
0,
|
||||
"CLIP"
|
||||
],
|
||||
[
|
||||
319,
|
||||
117,
|
||||
0,
|
||||
121,
|
||||
0,
|
||||
"CLIP"
|
||||
],
|
||||
[
|
||||
320,
|
||||
119,
|
||||
0,
|
||||
122,
|
||||
0,
|
||||
"MODEL"
|
||||
],
|
||||
[
|
||||
323,
|
||||
120,
|
||||
0,
|
||||
74,
|
||||
1,
|
||||
"CONDITIONING"
|
||||
],
|
||||
[
|
||||
324,
|
||||
121,
|
||||
0,
|
||||
74,
|
||||
2,
|
||||
"CONDITIONING"
|
||||
],
|
||||
[
|
||||
325,
|
||||
122,
|
||||
0,
|
||||
74,
|
||||
0,
|
||||
"MODEL"
|
||||
],
|
||||
[
|
||||
356,
|
||||
123,
|
||||
0,
|
||||
136,
|
||||
0,
|
||||
"IMAGE"
|
||||
],
|
||||
[
|
||||
357,
|
||||
123,
|
||||
1,
|
||||
136,
|
||||
2,
|
||||
"MASK"
|
||||
],
|
||||
[
|
||||
358,
|
||||
118,
|
||||
0,
|
||||
136,
|
||||
1,
|
||||
"VAE"
|
||||
],
|
||||
[
|
||||
359,
|
||||
136,
|
||||
0,
|
||||
74,
|
||||
3,
|
||||
"LATENT"
|
||||
],
|
||||
[
|
||||
360,
|
||||
74,
|
||||
0,
|
||||
137,
|
||||
0,
|
||||
"LATENT"
|
||||
],
|
||||
[
|
||||
361,
|
||||
118,
|
||||
0,
|
||||
137,
|
||||
1,
|
||||
"VAE"
|
||||
],
|
||||
[
|
||||
362,
|
||||
123,
|
||||
0,
|
||||
137,
|
||||
2,
|
||||
"IMAGE"
|
||||
],
|
||||
[
|
||||
363,
|
||||
123,
|
||||
1,
|
||||
137,
|
||||
3,
|
||||
"MASK"
|
||||
],
|
||||
[
|
||||
364,
|
||||
137,
|
||||
0,
|
||||
113,
|
||||
0,
|
||||
"IMAGE"
|
||||
]
|
||||
],
|
||||
"groups": [],
|
||||
"config": {},
|
||||
"extra": {
|
||||
"ds": {
|
||||
"scale": 0.6722341729170042,
|
||||
"offset": [
|
||||
1421.2190992818914,
|
||||
-80.75631642004547
|
||||
]
|
||||
},
|
||||
"frontendVersion": "1.43.18",
|
||||
"node_versions": {
|
||||
"comfy-core": "0.3.18",
|
||||
"LanPaint": "0f509469ed2cd60c6032f739e282aad5dfc06166"
|
||||
},
|
||||
"groupNodes": {}
|
||||
},
|
||||
"version": 0.4
|
||||
}
|
||||
@@ -0,0 +1,759 @@
|
||||
{
|
||||
"id": "26fb90cb-eb4a-422e-97d0-8b84dd6c3302",
|
||||
"revision": 0,
|
||||
"last_node_id": 79,
|
||||
"last_link_id": 211,
|
||||
"nodes": [
|
||||
{
|
||||
"id": 6,
|
||||
"type": "CLIPTextEncode",
|
||||
"pos": [
|
||||
333.06903076171875,
|
||||
249.68698120117188
|
||||
],
|
||||
"size": [
|
||||
422.84503173828125,
|
||||
164.31304931640625
|
||||
],
|
||||
"flags": {},
|
||||
"order": 2,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"localized_name": "clip",
|
||||
"name": "clip",
|
||||
"type": "CLIP",
|
||||
"link": 81
|
||||
},
|
||||
{
|
||||
"localized_name": "text",
|
||||
"name": "text",
|
||||
"type": "STRING",
|
||||
"widget": {
|
||||
"name": "text"
|
||||
},
|
||||
"link": null
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"localized_name": "CONDITIONING",
|
||||
"name": "CONDITIONING",
|
||||
"type": "CONDITIONING",
|
||||
"slot_index": 0,
|
||||
"links": [
|
||||
199
|
||||
]
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "CLIPTextEncode",
|
||||
"cnr_id": "comfy-core",
|
||||
"ver": "0.3.23"
|
||||
},
|
||||
"widgets_values": [
|
||||
"1girl, blue shirt, masterpiece, high score, great score, absurdres"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 7,
|
||||
"type": "CLIPTextEncode",
|
||||
"pos": [
|
||||
335.06903076171875,
|
||||
462.68701171875
|
||||
],
|
||||
"size": [
|
||||
425.27801513671875,
|
||||
180.6060791015625
|
||||
],
|
||||
"flags": {},
|
||||
"order": 3,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"localized_name": "clip",
|
||||
"name": "clip",
|
||||
"type": "CLIP",
|
||||
"link": 82
|
||||
},
|
||||
{
|
||||
"localized_name": "text",
|
||||
"name": "text",
|
||||
"type": "STRING",
|
||||
"widget": {
|
||||
"name": "text"
|
||||
},
|
||||
"link": null
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"localized_name": "CONDITIONING",
|
||||
"name": "CONDITIONING",
|
||||
"type": "CONDITIONING",
|
||||
"slot_index": 0,
|
||||
"links": [
|
||||
200
|
||||
]
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "CLIPTextEncode",
|
||||
"cnr_id": "comfy-core",
|
||||
"ver": "0.3.23"
|
||||
},
|
||||
"widgets_values": [
|
||||
"lowres, bad anatomy, bad hands, text, error, missing finger, extra digits, fewer digits, cropped, worst quality, low quality, low score, bad score, average score, signature, watermark, username, blurry, nude, NSFW"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 78,
|
||||
"type": "LanPaint_ImageEncode",
|
||||
"pos": [
|
||||
627.3978942185906,
|
||||
984.308971592649
|
||||
],
|
||||
"size": [
|
||||
195.3150390625,
|
||||
66
|
||||
],
|
||||
"flags": {},
|
||||
"order": 4,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"localized_name": "image",
|
||||
"name": "image",
|
||||
"type": "IMAGE",
|
||||
"link": 204
|
||||
},
|
||||
{
|
||||
"localized_name": "vae",
|
||||
"name": "vae",
|
||||
"type": "VAE",
|
||||
"link": 203
|
||||
},
|
||||
{
|
||||
"localized_name": "mask",
|
||||
"name": "mask",
|
||||
"shape": 7,
|
||||
"type": "MASK",
|
||||
"link": 205
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"localized_name": "latent",
|
||||
"name": "latent",
|
||||
"type": "LATENT",
|
||||
"links": [
|
||||
206
|
||||
]
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "LanPaint_ImageEncode"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 73,
|
||||
"type": "LanPaint_KSampler",
|
||||
"pos": [
|
||||
992.965419921875,
|
||||
268.686944885254
|
||||
],
|
||||
"size": [
|
||||
413.6495666503906,
|
||||
572
|
||||
],
|
||||
"flags": {},
|
||||
"order": 5,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"localized_name": "model",
|
||||
"name": "model",
|
||||
"type": "MODEL",
|
||||
"link": 198
|
||||
},
|
||||
{
|
||||
"localized_name": "positive",
|
||||
"name": "positive",
|
||||
"type": "CONDITIONING",
|
||||
"link": 199
|
||||
},
|
||||
{
|
||||
"localized_name": "negative",
|
||||
"name": "negative",
|
||||
"type": "CONDITIONING",
|
||||
"link": 200
|
||||
},
|
||||
{
|
||||
"localized_name": "latent_image",
|
||||
"name": "latent_image",
|
||||
"type": "LATENT",
|
||||
"link": 206
|
||||
},
|
||||
{
|
||||
"localized_name": "seed",
|
||||
"name": "seed",
|
||||
"type": "INT",
|
||||
"widget": {
|
||||
"name": "seed"
|
||||
},
|
||||
"link": null
|
||||
},
|
||||
{
|
||||
"localized_name": "steps",
|
||||
"name": "steps",
|
||||
"type": "INT",
|
||||
"widget": {
|
||||
"name": "steps"
|
||||
},
|
||||
"link": null
|
||||
},
|
||||
{
|
||||
"localized_name": "cfg",
|
||||
"name": "cfg",
|
||||
"type": "FLOAT",
|
||||
"widget": {
|
||||
"name": "cfg"
|
||||
},
|
||||
"link": null
|
||||
},
|
||||
{
|
||||
"localized_name": "sampler_name",
|
||||
"name": "sampler_name",
|
||||
"type": "COMBO",
|
||||
"widget": {
|
||||
"name": "sampler_name"
|
||||
},
|
||||
"link": null
|
||||
},
|
||||
{
|
||||
"localized_name": "scheduler",
|
||||
"name": "scheduler",
|
||||
"type": "COMBO",
|
||||
"widget": {
|
||||
"name": "scheduler"
|
||||
},
|
||||
"link": null
|
||||
},
|
||||
{
|
||||
"localized_name": "denoise",
|
||||
"name": "denoise",
|
||||
"type": "FLOAT",
|
||||
"widget": {
|
||||
"name": "denoise"
|
||||
},
|
||||
"link": null
|
||||
},
|
||||
{
|
||||
"localized_name": "LanPaint_NumSteps",
|
||||
"name": "LanPaint_NumSteps",
|
||||
"type": "INT",
|
||||
"widget": {
|
||||
"name": "LanPaint_NumSteps"
|
||||
},
|
||||
"link": null
|
||||
},
|
||||
{
|
||||
"localized_name": "LanPaint_PromptMode",
|
||||
"name": "LanPaint_PromptMode",
|
||||
"type": "COMBO",
|
||||
"widget": {
|
||||
"name": "LanPaint_PromptMode"
|
||||
},
|
||||
"link": null
|
||||
},
|
||||
{
|
||||
"localized_name": "LanPaint_Info",
|
||||
"name": "LanPaint_Info",
|
||||
"type": "STRING",
|
||||
"widget": {
|
||||
"name": "LanPaint_Info"
|
||||
},
|
||||
"link": null
|
||||
},
|
||||
{
|
||||
"localized_name": "Inpainting_mode",
|
||||
"name": "Inpainting_mode",
|
||||
"type": "COMBO",
|
||||
"widget": {
|
||||
"name": "Inpainting_mode"
|
||||
},
|
||||
"link": null
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"localized_name": "LATENT",
|
||||
"name": "LATENT",
|
||||
"type": "LATENT",
|
||||
"slot_index": 0,
|
||||
"links": [
|
||||
207
|
||||
]
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "LanPaint_KSampler",
|
||||
"cnr_id": "LanPaint",
|
||||
"ver": "56bd6c04e89124cd06682b304245d6ddf8b20522"
|
||||
},
|
||||
"widgets_values": [
|
||||
0,
|
||||
"fixed",
|
||||
20,
|
||||
5,
|
||||
"euler",
|
||||
"karras",
|
||||
1,
|
||||
5,
|
||||
"Image First",
|
||||
"LanPaint KSampler. For more info, visit https://github.com/scraed/LanPaint. If you find it useful, please give a star ⭐️!",
|
||||
"🖼️ Image Inpainting",
|
||||
"lanpaint_star_button"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 29,
|
||||
"type": "CheckpointLoaderSimple",
|
||||
"pos": [
|
||||
-62.627220153808594,
|
||||
407.01416015625
|
||||
],
|
||||
"size": [
|
||||
315,
|
||||
98
|
||||
],
|
||||
"flags": {},
|
||||
"order": 0,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"localized_name": "ckpt_name",
|
||||
"name": "ckpt_name",
|
||||
"type": "COMBO",
|
||||
"widget": {
|
||||
"name": "ckpt_name"
|
||||
},
|
||||
"link": null
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"localized_name": "MODEL",
|
||||
"name": "MODEL",
|
||||
"type": "MODEL",
|
||||
"slot_index": 0,
|
||||
"links": [
|
||||
198
|
||||
]
|
||||
},
|
||||
{
|
||||
"localized_name": "CLIP",
|
||||
"name": "CLIP",
|
||||
"type": "CLIP",
|
||||
"slot_index": 1,
|
||||
"links": [
|
||||
81,
|
||||
82
|
||||
]
|
||||
},
|
||||
{
|
||||
"localized_name": "VAE",
|
||||
"name": "VAE",
|
||||
"type": "VAE",
|
||||
"slot_index": 2,
|
||||
"links": [
|
||||
203,
|
||||
208
|
||||
]
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "CheckpointLoaderSimple",
|
||||
"cnr_id": "comfy-core",
|
||||
"ver": "0.3.23"
|
||||
},
|
||||
"widgets_values": [
|
||||
"animagineXL40_v4Opt.safetensors"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 20,
|
||||
"type": "LoadImage",
|
||||
"pos": [
|
||||
45.70227813720703,
|
||||
1147.2928466796875
|
||||
],
|
||||
"size": [
|
||||
262.12347412109375,
|
||||
487.22296142578125
|
||||
],
|
||||
"flags": {},
|
||||
"order": 1,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"localized_name": "image",
|
||||
"name": "image",
|
||||
"type": "COMBO",
|
||||
"widget": {
|
||||
"name": "image"
|
||||
},
|
||||
"link": null
|
||||
},
|
||||
{
|
||||
"localized_name": "choose file to upload",
|
||||
"name": "upload",
|
||||
"type": "IMAGEUPLOAD",
|
||||
"widget": {
|
||||
"name": "upload"
|
||||
},
|
||||
"link": null
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"localized_name": "IMAGE",
|
||||
"name": "IMAGE",
|
||||
"type": "IMAGE",
|
||||
"slot_index": 0,
|
||||
"links": [
|
||||
204,
|
||||
209
|
||||
]
|
||||
},
|
||||
{
|
||||
"localized_name": "MASK",
|
||||
"name": "MASK",
|
||||
"type": "MASK",
|
||||
"slot_index": 1,
|
||||
"links": [
|
||||
205,
|
||||
210
|
||||
]
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "LoadImage",
|
||||
"cnr_id": "comfy-core",
|
||||
"ver": "0.3.23"
|
||||
},
|
||||
"widgets_values": [
|
||||
"Masked_Load_Me_in_Loader (19).png",
|
||||
"image"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 75,
|
||||
"type": "SaveImage",
|
||||
"pos": [
|
||||
2279.375686842185,
|
||||
747.0061447670435
|
||||
],
|
||||
"size": [
|
||||
311.2532653808594,
|
||||
484.7096252441406
|
||||
],
|
||||
"flags": {},
|
||||
"order": 7,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"localized_name": "images",
|
||||
"name": "images",
|
||||
"type": "IMAGE",
|
||||
"link": 211
|
||||
},
|
||||
{
|
||||
"localized_name": "filename_prefix",
|
||||
"name": "filename_prefix",
|
||||
"type": "STRING",
|
||||
"widget": {
|
||||
"name": "filename_prefix"
|
||||
},
|
||||
"link": null
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"localized_name": "images",
|
||||
"name": "images",
|
||||
"type": "IMAGE",
|
||||
"links": null
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"cnr_id": "comfy-core",
|
||||
"ver": "0.3.23"
|
||||
},
|
||||
"widgets_values": [
|
||||
"ComfyUI"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 79,
|
||||
"type": "LanPaint_ImageDecode",
|
||||
"pos": [
|
||||
1755.3697160664874,
|
||||
781.9181675937066
|
||||
],
|
||||
"size": [
|
||||
270,
|
||||
118
|
||||
],
|
||||
"flags": {},
|
||||
"order": 6,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"localized_name": "samples",
|
||||
"name": "samples",
|
||||
"type": "LATENT",
|
||||
"link": 207
|
||||
},
|
||||
{
|
||||
"localized_name": "vae",
|
||||
"name": "vae",
|
||||
"type": "VAE",
|
||||
"link": 208
|
||||
},
|
||||
{
|
||||
"localized_name": "image",
|
||||
"name": "image",
|
||||
"shape": 7,
|
||||
"type": "IMAGE",
|
||||
"link": 209
|
||||
},
|
||||
{
|
||||
"localized_name": "mask",
|
||||
"name": "mask",
|
||||
"shape": 7,
|
||||
"type": "MASK",
|
||||
"link": 210
|
||||
},
|
||||
{
|
||||
"localized_name": "blend_overlap",
|
||||
"name": "blend_overlap",
|
||||
"shape": 7,
|
||||
"type": "INT",
|
||||
"widget": {
|
||||
"name": "blend_overlap"
|
||||
},
|
||||
"link": null
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"localized_name": "image",
|
||||
"name": "image",
|
||||
"type": "IMAGE",
|
||||
"links": [
|
||||
211
|
||||
]
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "LanPaint_ImageDecode"
|
||||
},
|
||||
"widgets_values": [
|
||||
9
|
||||
]
|
||||
}
|
||||
],
|
||||
"links": [
|
||||
[
|
||||
81,
|
||||
29,
|
||||
1,
|
||||
6,
|
||||
0,
|
||||
"CLIP"
|
||||
],
|
||||
[
|
||||
82,
|
||||
29,
|
||||
1,
|
||||
7,
|
||||
0,
|
||||
"CLIP"
|
||||
],
|
||||
[
|
||||
198,
|
||||
29,
|
||||
0,
|
||||
73,
|
||||
0,
|
||||
"MODEL"
|
||||
],
|
||||
[
|
||||
199,
|
||||
6,
|
||||
0,
|
||||
73,
|
||||
1,
|
||||
"CONDITIONING"
|
||||
],
|
||||
[
|
||||
200,
|
||||
7,
|
||||
0,
|
||||
73,
|
||||
2,
|
||||
"CONDITIONING"
|
||||
],
|
||||
[
|
||||
203,
|
||||
29,
|
||||
2,
|
||||
78,
|
||||
1,
|
||||
"VAE"
|
||||
],
|
||||
[
|
||||
204,
|
||||
20,
|
||||
0,
|
||||
78,
|
||||
0,
|
||||
"IMAGE"
|
||||
],
|
||||
[
|
||||
205,
|
||||
20,
|
||||
1,
|
||||
78,
|
||||
2,
|
||||
"MASK"
|
||||
],
|
||||
[
|
||||
206,
|
||||
78,
|
||||
0,
|
||||
73,
|
||||
3,
|
||||
"LATENT"
|
||||
],
|
||||
[
|
||||
207,
|
||||
73,
|
||||
0,
|
||||
79,
|
||||
0,
|
||||
"LATENT"
|
||||
],
|
||||
[
|
||||
208,
|
||||
29,
|
||||
2,
|
||||
79,
|
||||
1,
|
||||
"VAE"
|
||||
],
|
||||
[
|
||||
209,
|
||||
20,
|
||||
0,
|
||||
79,
|
||||
2,
|
||||
"IMAGE"
|
||||
],
|
||||
[
|
||||
210,
|
||||
20,
|
||||
1,
|
||||
79,
|
||||
3,
|
||||
"MASK"
|
||||
],
|
||||
[
|
||||
211,
|
||||
79,
|
||||
0,
|
||||
75,
|
||||
0,
|
||||
"IMAGE"
|
||||
]
|
||||
],
|
||||
"groups": [
|
||||
{
|
||||
"id": 1,
|
||||
"title": "Mask image for inpainting.",
|
||||
"bounding": [
|
||||
36.04227828979492,
|
||||
989.7313232421875,
|
||||
278.89093017578125,
|
||||
669.3414916992188
|
||||
],
|
||||
"color": "#3f789e",
|
||||
"flags": {}
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"title": "Convert Latents for LanPaint",
|
||||
"bounding": [
|
||||
286.0640563964844,
|
||||
714.343505859375,
|
||||
489.16796875,
|
||||
197.81044006347656
|
||||
],
|
||||
"color": "#3f789e",
|
||||
"flags": {}
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"title": "Load Model and Set Prompts",
|
||||
"bounding": [
|
||||
-78.9311294555664,
|
||||
176.08712768554688,
|
||||
1006.1721801757812,
|
||||
514.258544921875
|
||||
],
|
||||
"color": "#3f789e",
|
||||
"flags": {}
|
||||
},
|
||||
{
|
||||
"id": 4,
|
||||
"title": "Inpaint with the LanPaint KSampler",
|
||||
"bounding": [
|
||||
960.8922729492188,
|
||||
179.17588806152344,
|
||||
474.8909606933594,
|
||||
630.4742431640625
|
||||
],
|
||||
"color": "#3f789e",
|
||||
"flags": {}
|
||||
},
|
||||
{
|
||||
"id": 11,
|
||||
"title": "LanPaint",
|
||||
"bounding": [
|
||||
-262.59381103515625,
|
||||
140.46656799316406,
|
||||
1737.328857421875,
|
||||
797.4443359375
|
||||
],
|
||||
"color": "#3f789e",
|
||||
"flags": {}
|
||||
}
|
||||
],
|
||||
"config": {},
|
||||
"extra": {
|
||||
"ds": {
|
||||
"scale": 0.8954302432552493,
|
||||
"offset": [
|
||||
-1381.79269847794,
|
||||
-521.6998945185028
|
||||
]
|
||||
},
|
||||
"frontendVersion": "1.25.10",
|
||||
"node_versions": {
|
||||
"comfy-core": "0.3.18",
|
||||
"LanPaint": "0f509469ed2cd60c6032f739e282aad5dfc06166"
|
||||
}
|
||||
},
|
||||
"version": 0.4
|
||||
}
|
||||
@@ -0,0 +1,925 @@
|
||||
{
|
||||
"id": "9ae6082b-c7f4-433c-9971-7a8f65a3ea65",
|
||||
"revision": 0,
|
||||
"last_node_id": 75,
|
||||
"last_link_id": 105,
|
||||
"nodes": [
|
||||
{
|
||||
"id": 39,
|
||||
"type": "CLIPLoader",
|
||||
"pos": [
|
||||
158.31605577680375,
|
||||
488.30506119064967
|
||||
],
|
||||
"size": [
|
||||
323.734375,
|
||||
179.390625
|
||||
],
|
||||
"flags": {},
|
||||
"order": 0,
|
||||
"mode": 0,
|
||||
"inputs": [],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "CLIP",
|
||||
"type": "CLIP",
|
||||
"links": [
|
||||
44
|
||||
]
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "CLIPLoader",
|
||||
"cnr_id": "comfy-core",
|
||||
"ver": "0.3.73",
|
||||
"models": [
|
||||
{
|
||||
"name": "qwen_3_4b.safetensors",
|
||||
"url": "https://huggingface.co/Comfy-Org/z_image_turbo/resolve/main/split_files/text_encoders/qwen_3_4b.safetensors",
|
||||
"directory": "text_encoders"
|
||||
}
|
||||
],
|
||||
"enableTabs": false,
|
||||
"tabWidth": 65,
|
||||
"tabXOffset": 10,
|
||||
"hasSecondTab": false,
|
||||
"secondTabText": "Send Back",
|
||||
"secondTabOffset": 80,
|
||||
"secondTabWidth": 65
|
||||
},
|
||||
"widgets_values": [
|
||||
"qwen_3_4b.safetensors",
|
||||
"lumina2",
|
||||
"default"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 40,
|
||||
"type": "VAELoader",
|
||||
"pos": [
|
||||
159.30157212272297,
|
||||
693.9279006020886
|
||||
],
|
||||
"size": [
|
||||
323.734375,
|
||||
111.390625
|
||||
],
|
||||
"flags": {},
|
||||
"order": 1,
|
||||
"mode": 0,
|
||||
"inputs": [],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "VAE",
|
||||
"type": "VAE",
|
||||
"links": [
|
||||
97,
|
||||
101
|
||||
]
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "VAELoader",
|
||||
"cnr_id": "comfy-core",
|
||||
"ver": "0.3.73",
|
||||
"models": [
|
||||
{
|
||||
"name": "ae.safetensors",
|
||||
"url": "https://huggingface.co/Comfy-Org/z_image_turbo/resolve/main/split_files/vae/ae.safetensors",
|
||||
"directory": "vae"
|
||||
}
|
||||
],
|
||||
"enableTabs": false,
|
||||
"tabWidth": 65,
|
||||
"tabXOffset": 10,
|
||||
"hasSecondTab": false,
|
||||
"secondTabText": "Send Back",
|
||||
"secondTabOffset": 80,
|
||||
"secondTabWidth": 65
|
||||
},
|
||||
"widgets_values": [
|
||||
"ae.safetensors"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 42,
|
||||
"type": "ConditioningZeroOut",
|
||||
"pos": [
|
||||
794.3164307166835,
|
||||
836.3052371197768
|
||||
],
|
||||
"size": [
|
||||
236.984375,
|
||||
77.421875
|
||||
],
|
||||
"flags": {},
|
||||
"order": 11,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "conditioning",
|
||||
"type": "CONDITIONING",
|
||||
"link": 36
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "CONDITIONING",
|
||||
"type": "CONDITIONING",
|
||||
"links": [
|
||||
64
|
||||
]
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "ConditioningZeroOut",
|
||||
"cnr_id": "comfy-core",
|
||||
"ver": "0.3.73",
|
||||
"enableTabs": false,
|
||||
"tabWidth": 65,
|
||||
"tabXOffset": 10,
|
||||
"hasSecondTab": false,
|
||||
"secondTabText": "Send Back",
|
||||
"secondTabOffset": 80,
|
||||
"secondTabWidth": 65
|
||||
},
|
||||
"widgets_values": []
|
||||
},
|
||||
{
|
||||
"id": 41,
|
||||
"type": "EmptySD3LatentImage",
|
||||
"pos": [
|
||||
158.31605577680375,
|
||||
848.3047505883683
|
||||
],
|
||||
"size": [
|
||||
311.734375,
|
||||
179.390625
|
||||
],
|
||||
"flags": {},
|
||||
"order": 2,
|
||||
"mode": 0,
|
||||
"inputs": [],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "LATENT",
|
||||
"type": "LATENT",
|
||||
"slot_index": 0,
|
||||
"links": []
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "EmptySD3LatentImage",
|
||||
"cnr_id": "comfy-core",
|
||||
"ver": "0.3.64",
|
||||
"enableTabs": false,
|
||||
"tabWidth": 65,
|
||||
"tabXOffset": 10,
|
||||
"hasSecondTab": false,
|
||||
"secondTabText": "Send Back",
|
||||
"secondTabOffset": 80,
|
||||
"secondTabWidth": 65
|
||||
},
|
||||
"widgets_values": [
|
||||
1024,
|
||||
1024,
|
||||
1
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 47,
|
||||
"type": "ModelSamplingAuraFlow",
|
||||
"pos": [
|
||||
1106.844558468847,
|
||||
300.1767719883135
|
||||
],
|
||||
"size": [
|
||||
371.875,
|
||||
111.390625
|
||||
],
|
||||
"flags": {},
|
||||
"order": 13,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "model",
|
||||
"type": "MODEL",
|
||||
"link": 60
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "MODEL",
|
||||
"type": "MODEL",
|
||||
"slot_index": 0,
|
||||
"links": [
|
||||
62
|
||||
]
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "ModelSamplingAuraFlow",
|
||||
"cnr_id": "comfy-core",
|
||||
"ver": "0.3.64",
|
||||
"enableTabs": false,
|
||||
"tabWidth": 65,
|
||||
"tabXOffset": 10,
|
||||
"hasSecondTab": false,
|
||||
"secondTabText": "Send Back",
|
||||
"secondTabOffset": 80,
|
||||
"secondTabWidth": 65
|
||||
},
|
||||
"widgets_values": [
|
||||
3
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 45,
|
||||
"type": "CLIPTextEncode",
|
||||
"pos": [
|
||||
542.3159768994137,
|
||||
332.3053621604172
|
||||
],
|
||||
"size": [
|
||||
491.734375,
|
||||
479.734375
|
||||
],
|
||||
"flags": {},
|
||||
"order": 7,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "clip",
|
||||
"type": "CLIP",
|
||||
"link": 44
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "CONDITIONING",
|
||||
"type": "CONDITIONING",
|
||||
"links": [
|
||||
36,
|
||||
63
|
||||
]
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "CLIPTextEncode",
|
||||
"cnr_id": "comfy-core",
|
||||
"ver": "0.3.73",
|
||||
"enableTabs": false,
|
||||
"tabWidth": 65,
|
||||
"tabXOffset": 10,
|
||||
"hasSecondTab": false,
|
||||
"secondTabText": "Send Back",
|
||||
"secondTabOffset": 80,
|
||||
"secondTabWidth": 65
|
||||
},
|
||||
"widgets_values": [
|
||||
"Latina female with thick wavy hair, white shirt, harbor boats and pastel houses behind. Breezy seaside light, warm tones, cinematic close-up."
|
||||
],
|
||||
"color": "#232",
|
||||
"bgcolor": "#353"
|
||||
},
|
||||
{
|
||||
"id": 48,
|
||||
"type": "LoraLoaderModelOnly",
|
||||
"pos": [
|
||||
553.9994917632011,
|
||||
133.99988695553816
|
||||
],
|
||||
"size": [
|
||||
443.75,
|
||||
145.390625
|
||||
],
|
||||
"flags": {},
|
||||
"order": 10,
|
||||
"mode": 4,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "model",
|
||||
"type": "MODEL",
|
||||
"link": 54
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "MODEL",
|
||||
"type": "MODEL",
|
||||
"links": [
|
||||
60
|
||||
]
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "LoraLoaderModelOnly",
|
||||
"cnr_id": "comfy-core",
|
||||
"ver": "0.3.75",
|
||||
"models": [
|
||||
{
|
||||
"name": "pixel_art_style_z_image_turbo.safetensors",
|
||||
"url": "https://huggingface.co/tarn59/pixel_art_style_lora_z_image_turbo/resolve/main/pixel_art_style_z_image_turbo.safetensors",
|
||||
"directory": "loras"
|
||||
}
|
||||
],
|
||||
"enableTabs": false,
|
||||
"tabWidth": 65,
|
||||
"tabXOffset": 10,
|
||||
"hasSecondTab": false,
|
||||
"secondTabText": "Send Back",
|
||||
"secondTabOffset": 80,
|
||||
"secondTabWidth": 65
|
||||
},
|
||||
"widgets_values": [
|
||||
"pixel_art_style_z_image_turbo.safetensors",
|
||||
1
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 35,
|
||||
"type": "MarkdownNote",
|
||||
"pos": [
|
||||
-466.00011584916956,
|
||||
289.9999302108306
|
||||
],
|
||||
"size": [
|
||||
587.734375,
|
||||
770.859375
|
||||
],
|
||||
"flags": {
|
||||
"collapsed": false
|
||||
},
|
||||
"order": 3,
|
||||
"mode": 0,
|
||||
"inputs": [],
|
||||
"outputs": [],
|
||||
"title": "Model link",
|
||||
"properties": {},
|
||||
"widgets_values": [
|
||||
"## Report workflow issue\n\nIf you found any issues when running this workflow, [report template issue here](https://github.com/Comfy-Org/workflow_templates/issues)\n\n\n## Model links\n\n**text_encoders**\n\n- [qwen_3_4b.safetensors](https://huggingface.co/Comfy-Org/z_image_turbo/resolve/main/split_files/text_encoders/qwen_3_4b.safetensors)\n\n**loras**\n\n- [pixel_art_style_z_image_turbo.safetensors](https://huggingface.co/tarn59/pixel_art_style_lora_z_image_turbo/resolve/main/pixel_art_style_z_image_turbo.safetensors)\n\n**diffusion_models**\n\n- [z_image_turbo_bf16.safetensors](https://huggingface.co/Comfy-Org/z_image_turbo/resolve/main/split_files/diffusion_models/z_image_turbo_bf16.safetensors)\n\n**vae**\n\n- [ae.safetensors](https://huggingface.co/Comfy-Org/z_image_turbo/resolve/main/split_files/vae/ae.safetensors)\n\n\nModel Storage Location\n\n```\n📂 ComfyUI/\n├── 📂 models/\n│ ├── 📂 text_encoders/\n│ │ └── qwen_3_4b.safetensors\n│ ├── 📂 loras/\n│ │ └── pixel_art_style_z_image_turbo.safetensors\n│ ├── 📂 diffusion_models/\n│ │ └── z_image_turbo_bf16.safetensors\n│ └── 📂 vae/\n│ └── ae.safetensors\n```\n"
|
||||
],
|
||||
"color": "#432",
|
||||
"bgcolor": "#653"
|
||||
},
|
||||
{
|
||||
"id": 56,
|
||||
"type": "LanPaint_KSampler",
|
||||
"pos": [
|
||||
1280.6434468860161,
|
||||
480.42471689895115
|
||||
],
|
||||
"size": [
|
||||
479.765625,
|
||||
887.59375
|
||||
],
|
||||
"flags": {},
|
||||
"order": 20,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "model",
|
||||
"type": "MODEL",
|
||||
"link": 62
|
||||
},
|
||||
{
|
||||
"name": "positive",
|
||||
"type": "CONDITIONING",
|
||||
"link": 63
|
||||
},
|
||||
{
|
||||
"name": "negative",
|
||||
"type": "CONDITIONING",
|
||||
"link": 64
|
||||
},
|
||||
{
|
||||
"name": "latent_image",
|
||||
"type": "LATENT",
|
||||
"link": 99
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "LATENT",
|
||||
"type": "LATENT",
|
||||
"links": [
|
||||
100
|
||||
]
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "LanPaint_KSampler",
|
||||
"cnr_id": "LanPaint",
|
||||
"ver": "6109df6591a4cf2bc9d3b113d03f7297fa9248e9"
|
||||
},
|
||||
"widgets_values": [
|
||||
880311146947153,
|
||||
"randomize",
|
||||
9,
|
||||
1,
|
||||
"euler",
|
||||
"simple",
|
||||
1,
|
||||
5,
|
||||
"Image First",
|
||||
"LanPaint KSampler. For more info, visit https://github.com/scraed/LanPaint. If you find it useful, please give a star ⭐️!",
|
||||
"🖼️ Image Inpainting"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 57,
|
||||
"type": "LoadImage",
|
||||
"pos": [
|
||||
9.292593918969033,
|
||||
1110.6054508670231
|
||||
],
|
||||
"size": [
|
||||
328.65625,
|
||||
686.171875
|
||||
],
|
||||
"flags": {},
|
||||
"order": 4,
|
||||
"mode": 0,
|
||||
"inputs": [],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "IMAGE",
|
||||
"type": "IMAGE",
|
||||
"links": [
|
||||
96,
|
||||
102
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "MASK",
|
||||
"type": "MASK",
|
||||
"links": [
|
||||
96,
|
||||
102
|
||||
]
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "LoadImage",
|
||||
"cnr_id": "comfy-core",
|
||||
"ver": "0.3.59",
|
||||
"ue_properties": {
|
||||
"widget_ue_connectable": {},
|
||||
"version": "7.1",
|
||||
"input_ue_unconnectable": {}
|
||||
},
|
||||
"image": "clipspace/clipspace-painted-masked-1764827669471.png [input]"
|
||||
},
|
||||
"widgets_values": [
|
||||
"Example_21_Masked.png",
|
||||
"image"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 61,
|
||||
"type": "SaveImage",
|
||||
"pos": [
|
||||
3313.9622863771715,
|
||||
1191.2159032348995
|
||||
],
|
||||
"size": [
|
||||
1304.078125,
|
||||
1434.015625
|
||||
],
|
||||
"flags": {},
|
||||
"order": 24,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "images",
|
||||
"type": "IMAGE",
|
||||
"link": 104
|
||||
}
|
||||
],
|
||||
"outputs": [],
|
||||
"properties": {
|
||||
"cnr_id": "comfy-core",
|
||||
"ver": "0.3.23"
|
||||
},
|
||||
"widgets_values": [
|
||||
"ComfyUI"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 73,
|
||||
"type": "PreviewImage",
|
||||
"pos": [
|
||||
2226.506787053337,
|
||||
107.57278250350078
|
||||
],
|
||||
"size": [
|
||||
225,
|
||||
246
|
||||
],
|
||||
"flags": {},
|
||||
"order": 23,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "images",
|
||||
"type": "IMAGE",
|
||||
"link": 105
|
||||
}
|
||||
],
|
||||
"outputs": [],
|
||||
"properties": {
|
||||
"Node name for S&R": "PreviewImage",
|
||||
"cnr_id": "comfy-core",
|
||||
"ver": "0.3.76"
|
||||
},
|
||||
"widgets_values": []
|
||||
},
|
||||
{
|
||||
"id": 46,
|
||||
"type": "UNETLoader",
|
||||
"pos": [
|
||||
159.05646022112083,
|
||||
288.62105108441466
|
||||
],
|
||||
"size": [
|
||||
323.734375,
|
||||
145.390625
|
||||
],
|
||||
"flags": {},
|
||||
"order": 5,
|
||||
"mode": 0,
|
||||
"inputs": [],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "MODEL",
|
||||
"type": "MODEL",
|
||||
"links": [
|
||||
54
|
||||
]
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "UNETLoader",
|
||||
"cnr_id": "comfy-core",
|
||||
"ver": "0.3.73",
|
||||
"models": [
|
||||
{
|
||||
"name": "z_image_turbo_bf16.safetensors",
|
||||
"url": "https://huggingface.co/Comfy-Org/z_image_turbo/resolve/main/split_files/diffusion_models/z_image_turbo_bf16.safetensors",
|
||||
"directory": "diffusion_models"
|
||||
}
|
||||
],
|
||||
"enableTabs": false,
|
||||
"tabWidth": 65,
|
||||
"tabXOffset": 10,
|
||||
"hasSecondTab": false,
|
||||
"secondTabText": "Send Back",
|
||||
"secondTabOffset": 80,
|
||||
"secondTabWidth": 65
|
||||
},
|
||||
"widgets_values": [
|
||||
"z_image_turbo_bf16.safetensors",
|
||||
"default"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 72,
|
||||
"type": "Note",
|
||||
"pos": [
|
||||
1288.7909707861209,
|
||||
1503.5796522859714
|
||||
],
|
||||
"size": [
|
||||
225,
|
||||
109.765625
|
||||
],
|
||||
"flags": {},
|
||||
"order": 6,
|
||||
"mode": 0,
|
||||
"inputs": [],
|
||||
"outputs": [],
|
||||
"properties": {},
|
||||
"widgets_values": [
|
||||
"Reshape image to suit VAE "
|
||||
],
|
||||
"color": "#432",
|
||||
"bgcolor": "#653"
|
||||
},
|
||||
{
|
||||
"id": 74,
|
||||
"type": "LanPaint_ImageEncode",
|
||||
"pos": [
|
||||
1700,
|
||||
1660
|
||||
],
|
||||
"size": [
|
||||
300,
|
||||
150
|
||||
],
|
||||
"flags": {},
|
||||
"order": 0,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "image",
|
||||
"type": "IMAGE",
|
||||
"link": 96
|
||||
},
|
||||
{
|
||||
"name": "vae",
|
||||
"type": "VAE",
|
||||
"link": 97
|
||||
},
|
||||
{
|
||||
"name": "mask",
|
||||
"type": "MASK",
|
||||
"link": 98
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "latent",
|
||||
"type": "LATENT",
|
||||
"links": [
|
||||
99
|
||||
]
|
||||
}
|
||||
],
|
||||
"widgets_values": [],
|
||||
"properties": {
|
||||
"Node name for S&R": "LanPaint_ImageEncode",
|
||||
"cnr_id": "LanPaint"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 75,
|
||||
"type": "LanPaint_ImageDecode",
|
||||
"pos": [
|
||||
2740,
|
||||
1190
|
||||
],
|
||||
"size": [
|
||||
300,
|
||||
200
|
||||
],
|
||||
"flags": {},
|
||||
"order": 0,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "samples",
|
||||
"type": "LATENT",
|
||||
"link": 100
|
||||
},
|
||||
{
|
||||
"name": "vae",
|
||||
"type": "VAE",
|
||||
"link": 101
|
||||
},
|
||||
{
|
||||
"name": "image",
|
||||
"type": "IMAGE",
|
||||
"link": 102
|
||||
},
|
||||
{
|
||||
"name": "mask",
|
||||
"type": "MASK",
|
||||
"link": 103
|
||||
},
|
||||
{
|
||||
"name": "blend_overlap",
|
||||
"type": "INT",
|
||||
"widget": {
|
||||
"name": "blend_overlap"
|
||||
}
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "image",
|
||||
"type": "IMAGE",
|
||||
"links": [
|
||||
104,
|
||||
105
|
||||
]
|
||||
}
|
||||
],
|
||||
"widgets_values": [
|
||||
9
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "LanPaint_ImageDecode",
|
||||
"cnr_id": "LanPaint"
|
||||
}
|
||||
}
|
||||
],
|
||||
"links": [
|
||||
[
|
||||
36,
|
||||
45,
|
||||
0,
|
||||
42,
|
||||
0,
|
||||
"CONDITIONING"
|
||||
],
|
||||
[
|
||||
44,
|
||||
39,
|
||||
0,
|
||||
45,
|
||||
0,
|
||||
"CLIP"
|
||||
],
|
||||
[
|
||||
54,
|
||||
46,
|
||||
0,
|
||||
48,
|
||||
0,
|
||||
"MODEL"
|
||||
],
|
||||
[
|
||||
60,
|
||||
48,
|
||||
0,
|
||||
47,
|
||||
0,
|
||||
"MODEL"
|
||||
],
|
||||
[
|
||||
62,
|
||||
47,
|
||||
0,
|
||||
56,
|
||||
0,
|
||||
"MODEL"
|
||||
],
|
||||
[
|
||||
63,
|
||||
45,
|
||||
0,
|
||||
56,
|
||||
1,
|
||||
"CONDITIONING"
|
||||
],
|
||||
[
|
||||
64,
|
||||
42,
|
||||
0,
|
||||
56,
|
||||
2,
|
||||
"CONDITIONING"
|
||||
],
|
||||
[
|
||||
96,
|
||||
57,
|
||||
0,
|
||||
74,
|
||||
0,
|
||||
"IMAGE"
|
||||
],
|
||||
[
|
||||
97,
|
||||
40,
|
||||
0,
|
||||
74,
|
||||
1,
|
||||
"VAE"
|
||||
],
|
||||
[
|
||||
98,
|
||||
57,
|
||||
1,
|
||||
74,
|
||||
2,
|
||||
"MASK"
|
||||
],
|
||||
[
|
||||
99,
|
||||
74,
|
||||
0,
|
||||
56,
|
||||
3,
|
||||
"LATENT"
|
||||
],
|
||||
[
|
||||
100,
|
||||
56,
|
||||
0,
|
||||
75,
|
||||
0,
|
||||
"LATENT"
|
||||
],
|
||||
[
|
||||
101,
|
||||
40,
|
||||
0,
|
||||
75,
|
||||
1,
|
||||
"VAE"
|
||||
],
|
||||
[
|
||||
102,
|
||||
57,
|
||||
0,
|
||||
75,
|
||||
2,
|
||||
"IMAGE"
|
||||
],
|
||||
[
|
||||
103,
|
||||
57,
|
||||
1,
|
||||
75,
|
||||
3,
|
||||
"MASK"
|
||||
],
|
||||
[
|
||||
104,
|
||||
75,
|
||||
0,
|
||||
61,
|
||||
0,
|
||||
"IMAGE"
|
||||
],
|
||||
[
|
||||
105,
|
||||
75,
|
||||
0,
|
||||
73,
|
||||
0,
|
||||
"IMAGE"
|
||||
]
|
||||
],
|
||||
"groups": [
|
||||
{
|
||||
"id": 2,
|
||||
"title": "Step2 - Image size",
|
||||
"bounding": [
|
||||
146,
|
||||
770,
|
||||
348,
|
||||
240
|
||||
],
|
||||
"color": "#3f789e",
|
||||
"font_size": 24,
|
||||
"flags": {}
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"title": "Step3 - Prompt",
|
||||
"bounding": [
|
||||
518,
|
||||
254,
|
||||
540,
|
||||
648
|
||||
],
|
||||
"color": "#3f789e",
|
||||
"font_size": 24,
|
||||
"flags": {}
|
||||
},
|
||||
{
|
||||
"id": 4,
|
||||
"title": "Step1 - Load models",
|
||||
"bounding": [
|
||||
146,
|
||||
254,
|
||||
348,
|
||||
496.32
|
||||
],
|
||||
"color": "#3f789e",
|
||||
"font_size": 24,
|
||||
"flags": {}
|
||||
},
|
||||
{
|
||||
"id": 5,
|
||||
"title": "Ctrl-B to enable LoRA input",
|
||||
"bounding": [
|
||||
518,
|
||||
50,
|
||||
528,
|
||||
192
|
||||
],
|
||||
"color": "#3f789e",
|
||||
"font_size": 24,
|
||||
"flags": {}
|
||||
}
|
||||
],
|
||||
"config": {},
|
||||
"extra": {
|
||||
"ds": {
|
||||
"scale": 0.3800835362432756,
|
||||
"offset": [
|
||||
-902.897494105397,
|
||||
-396.4805064253962
|
||||
]
|
||||
},
|
||||
"frontendVersion": "1.33.14",
|
||||
"VHS_latentpreview": false,
|
||||
"VHS_latentpreviewrate": 0,
|
||||
"VHS_MetadataImage": true,
|
||||
"VHS_KeepIntermediate": true,
|
||||
"workflowRendererVersion": "Vue"
|
||||
},
|
||||
"version": 0.4
|
||||
}
|
||||
@@ -865,7 +865,7 @@ class LanPaint_VideoMaskEditor:
|
||||
files = []
|
||||
return {
|
||||
"required": {
|
||||
"video": (files, {"image_upload": True, "tooltip": "Source video file. Returned as the video output and used by the mask editor preview."}),
|
||||
"video": (files, {"video_upload": True, "tooltip": "Source video file. Returned as the video output and used by the mask editor preview."}),
|
||||
"keyframes": ("STRING", {"default": "{}", "multiline": True, "tooltip": "Hidden: keyframe mask files {\"frame\": \"file.png\"}. Written by the mask editor."}),
|
||||
"audio_mask": ("STRING", {"default": "[]", "multiline": True, "tooltip": "Hidden: audio inpainting intervals [{\"start\": s, \"end\": e}] in seconds. Written by the mask editor."}),
|
||||
},
|
||||
@@ -1170,8 +1170,11 @@ class LanPaint_ImageEncode:
|
||||
Replaces the chain VAEEncode -> SetLatentNoiseMask (plus the manual
|
||||
ImageScale of the mask) with one node. The mask is snapped to the latent's
|
||||
actual spatial size (nearest-exact, the LanPaint mask convention), so
|
||||
whatever the VAE's padding does, the mask always matches. Without a mask
|
||||
this is a plain encode.
|
||||
whatever the VAE's padding does, the mask always matches. Works with
|
||||
image VAEs (4D latent) and video VAEs encoding a single image (5D latent
|
||||
[B, C, T, H, W], e.g. the Hunyuan video VAE): the mask is attached as
|
||||
[1, 1, T, H, W] and the sampler's prep handles it. Without a mask this is
|
||||
a plain encode.
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
@@ -1194,10 +1197,10 @@ class LanPaint_ImageEncode:
|
||||
|
||||
def encode(self, image, vae, mask=None):
|
||||
z = vae.encode(image)
|
||||
if len(z.shape) != 4:
|
||||
ndim = len(z.shape)
|
||||
if ndim not in (4, 5):
|
||||
raise ValueError(
|
||||
"LanPaint_ImageEncode expects an image VAE (latent [B, C, H, W]); "
|
||||
f"got a {len(z.shape)}D latent — use LanPaint_AVEncode for video."
|
||||
f"LanPaint_ImageEncode expects a 4D or 5D latent, got {ndim}D"
|
||||
)
|
||||
latent = {"samples": z}
|
||||
if mask is not None:
|
||||
@@ -1206,12 +1209,20 @@ class LanPaint_ImageEncode:
|
||||
m = m[0, 0]
|
||||
elif m.ndim == 3:
|
||||
m = m[0]
|
||||
target = z.shape[-2:]
|
||||
if tuple(m.shape) != target:
|
||||
m = torch.nn.functional.interpolate(
|
||||
m.unsqueeze(0).unsqueeze(0), size=target, mode="nearest-exact"
|
||||
)[0, 0]
|
||||
latent["noise_mask"] = m.unsqueeze(0).unsqueeze(0)
|
||||
if ndim == 4:
|
||||
target = z.shape[-2:]
|
||||
if tuple(m.shape) != target:
|
||||
m = torch.nn.functional.interpolate(
|
||||
m.unsqueeze(0).unsqueeze(0), size=target, mode="nearest-exact"
|
||||
)[0, 0]
|
||||
latent["noise_mask"] = m.unsqueeze(0).unsqueeze(0)
|
||||
else: # 5D video-style VAE: snap to (T, H, W), one mask frame per token
|
||||
t, h, w = z.shape[-3:]
|
||||
if tuple(m.shape) != (h, w):
|
||||
m = torch.nn.functional.interpolate(
|
||||
m.unsqueeze(0).unsqueeze(0), size=(h, w), mode="nearest-exact"
|
||||
)[0, 0]
|
||||
latent["noise_mask"] = m.unsqueeze(0).unsqueeze(0).unsqueeze(2).expand(1, 1, t, h, w)
|
||||
return (latent,)
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,291 @@
|
||||
"""Read and write LanPaint mask metadata in MP4 files via PyAV.
|
||||
|
||||
The metadata tag ``lanpaint-mask`` holds a UTF-8 JSON payload:
|
||||
{
|
||||
"version": 1,
|
||||
"video": "<source video filename>",
|
||||
"fps": <number>,
|
||||
"keyframes": {"<frame_idx>": "<base64 PNG (no data: prefix)>", ...},
|
||||
"audio_intervals": [{"start": <float>, "end": <float>}, ...]
|
||||
}
|
||||
|
||||
Keyframes are base64-encoded PNG masks (RGBA, alpha = mask).
|
||||
"No mask" = tag absent (None); "empty mask" = tag present with ``{}`` keyframes.
|
||||
|
||||
PyAV is import-guarded: the module-level ``av`` is ``None`` when PyAV is
|
||||
unavailable, and the read/write functions raise ``RuntimeError`` in that case.
|
||||
"""
|
||||
|
||||
import json
|
||||
import os
|
||||
from typing import Any, Dict, Optional
|
||||
|
||||
try:
|
||||
import av
|
||||
|
||||
_HAS_AV = True
|
||||
except ImportError:
|
||||
av = None # type: ignore[assignment]
|
||||
_HAS_AV = False
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Payload helpers
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
_METADATA_KEY = "lanpaint-mask"
|
||||
_PAYLOAD_VERSION = 1
|
||||
|
||||
|
||||
def encode_payload(payload: dict) -> str:
|
||||
"""Encode a payload dict as a compact JSON string (UTF-8)."""
|
||||
return json.dumps(payload, ensure_ascii=False, separators=(",", ":"))
|
||||
|
||||
|
||||
def decode_payload(raw: Optional[str]) -> Optional[dict]:
|
||||
"""Decode a metadata value to a payload dict, or None on any error.
|
||||
|
||||
``raw`` may be ``None`` (tag absent), a JSON string, or something
|
||||
unexpected. Malformed / missing input returns ``None`` gracefully.
|
||||
"""
|
||||
if raw is None:
|
||||
return None
|
||||
if not isinstance(raw, str):
|
||||
return None
|
||||
try:
|
||||
data = json.loads(raw)
|
||||
except (TypeError, ValueError):
|
||||
return None
|
||||
if not isinstance(data, dict):
|
||||
return None
|
||||
return data
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Core read / write
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
def read_mask_metadata(path: str) -> Optional[dict]:
|
||||
"""Read the ``lanpaint-mask`` metadata tag from an MP4 file.
|
||||
|
||||
Returns the decoded payload dict, or ``None`` when the tag is absent or
|
||||
PyAV is unavailable.
|
||||
"""
|
||||
if not _HAS_AV:
|
||||
raise RuntimeError(
|
||||
"PyAV (av) is required to read mask metadata but is not installed"
|
||||
)
|
||||
with av.open(path, "r") as container:
|
||||
raw = container.metadata.get(_METADATA_KEY, None)
|
||||
return decode_payload(raw)
|
||||
|
||||
|
||||
def write_mask_metadata(
|
||||
input_path: str,
|
||||
output_path: str,
|
||||
payload: dict,
|
||||
) -> None:
|
||||
"""Remux *input_path* to *output_path*, attaching a ``lanpaint-mask``
|
||||
metadata tag.
|
||||
|
||||
The video track is stream-copied (no re-encode) so the video content is
|
||||
preserved byte-for-byte. Audio and other tracks are also preserved.
|
||||
|
||||
The source file at *input_path* is **never** modified.
|
||||
|
||||
Raises ``RuntimeError`` when PyAV is unavailable.
|
||||
"""
|
||||
if not _HAS_AV:
|
||||
raise RuntimeError(
|
||||
"PyAV (av) is required to write mask metadata but is not installed"
|
||||
)
|
||||
|
||||
json_str = encode_payload(payload)
|
||||
|
||||
with av.open(input_path, "r") as in_container:
|
||||
# ``movflags=use_metadata_tags`` is MANDATORY — without it ffmpeg
|
||||
# silently drops custom metadata keys from the output.
|
||||
with av.open(
|
||||
output_path,
|
||||
"w",
|
||||
format="mp4",
|
||||
options={"movflags": "use_metadata_tags"},
|
||||
) as out_container:
|
||||
# Copy any existing metadata (except our own key) from the source.
|
||||
for key, value in in_container.metadata.items():
|
||||
if key != _METADATA_KEY:
|
||||
out_container.metadata[key] = value
|
||||
|
||||
# Write the LanPaint payload.
|
||||
out_container.metadata[_METADATA_KEY] = json_str
|
||||
|
||||
# Stream-copy every stream from the source.
|
||||
stream_map: Dict[int, Any] = {}
|
||||
for in_stream in in_container.streams:
|
||||
out_stream = out_container.add_stream_from_template(in_stream)
|
||||
stream_map[in_stream.index] = out_stream
|
||||
|
||||
# Demux → mux every packet.
|
||||
for packet in in_container.demux():
|
||||
if packet.dts is None:
|
||||
continue
|
||||
out_stream = stream_map[packet.stream.index]
|
||||
packet.stream = out_stream
|
||||
out_container.mux(packet)
|
||||
|
||||
# The caller is responsible for the output file on disk.
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Helpers for server routes
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
def _unique_output_path(input_dir: str, base_name: str) -> str:
|
||||
"""Return a non-clobbering output filename in *input_dir*.
|
||||
|
||||
Appends ``_masked``, then ``_masked_2``, ``_masked_3``, ... until a name
|
||||
that does not already exist is found.
|
||||
|
||||
Returns the bare filename (not the full path).
|
||||
"""
|
||||
stem, ext = os.path.splitext(base_name)
|
||||
candidate = f"{stem}_masked{ext}"
|
||||
if not os.path.exists(os.path.join(input_dir, candidate)):
|
||||
return candidate
|
||||
n = 2
|
||||
while True:
|
||||
candidate = f"{stem}_masked_{n}{ext}"
|
||||
if not os.path.exists(os.path.join(input_dir, candidate)):
|
||||
return candidate
|
||||
n += 1
|
||||
|
||||
|
||||
def export_mask_video_from_request(
|
||||
input_dir: str,
|
||||
filename: str,
|
||||
keyframes: dict,
|
||||
audio_intervals: list,
|
||||
fps: float,
|
||||
) -> str:
|
||||
"""Remux a source video with mask metadata, returning the new filename.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
input_dir:
|
||||
The ComfyUI input directory (``folder_paths.get_input_directory()``).
|
||||
filename:
|
||||
The source video filename (relative to *input_dir*).
|
||||
keyframes:
|
||||
Dict of ``{frame_idx: base64_png_string}``.
|
||||
audio_intervals:
|
||||
List of ``{"start": float, "end": float}`` dicts.
|
||||
fps:
|
||||
The video frame rate.
|
||||
|
||||
Returns
|
||||
-------
|
||||
The bare filename of the exported MP4 (in *input_dir*).
|
||||
"""
|
||||
payload: Dict[str, Any] = {
|
||||
"version": _PAYLOAD_VERSION,
|
||||
"video": filename,
|
||||
"fps": fps,
|
||||
"keyframes": keyframes,
|
||||
"audio_intervals": audio_intervals,
|
||||
}
|
||||
|
||||
src = os.path.join(input_dir, filename)
|
||||
if not os.path.isfile(src):
|
||||
raise FileNotFoundError(f"source video not found: {src}")
|
||||
|
||||
out_name = _unique_output_path(input_dir, filename)
|
||||
out_path = os.path.join(input_dir, out_name)
|
||||
write_mask_metadata(src, out_path, payload)
|
||||
return out_name
|
||||
|
||||
|
||||
def register_routes(server) -> None:
|
||||
"""Register the LanPaint video-mask metadata routes on a ComfyUI
|
||||
PromptServer instance.
|
||||
|
||||
Call this from ``__init__.py`` when running inside ComfyUI (the ``server``
|
||||
module is only importable in that environment). Safe to call multiple
|
||||
times — routes are registered once.
|
||||
"""
|
||||
import aiohttp
|
||||
import folder_paths
|
||||
|
||||
@server.routes.get("/lanpaint/video_mask_meta")
|
||||
async def video_mask_meta(request: aiohttp.web.Request) -> aiohttp.web.Response:
|
||||
filename = request.query.get("filename", "")
|
||||
if not filename:
|
||||
return aiohttp.web.json_response({"found": False})
|
||||
|
||||
input_dir = folder_paths.get_input_directory()
|
||||
path = os.path.join(input_dir, filename)
|
||||
if not os.path.isfile(path):
|
||||
return aiohttp.web.json_response({"found": False})
|
||||
|
||||
try:
|
||||
payload = read_mask_metadata(path)
|
||||
except Exception:
|
||||
payload = None
|
||||
|
||||
if payload is None:
|
||||
return aiohttp.web.json_response({"found": False})
|
||||
return aiohttp.web.json_response({"found": True, "payload": payload})
|
||||
|
||||
@server.routes.post("/lanpaint/export_mask_video")
|
||||
async def export_mask_video(request: aiohttp.web.Request) -> aiohttp.web.Response:
|
||||
try:
|
||||
body = await request.json()
|
||||
except Exception:
|
||||
return aiohttp.web.json_response(
|
||||
{"error": "invalid JSON body"}, status=400
|
||||
)
|
||||
|
||||
filename = body.get("filename", "")
|
||||
if not filename:
|
||||
return aiohttp.web.json_response(
|
||||
{"error": "missing filename"}, status=400
|
||||
)
|
||||
|
||||
keyframes = body.get("keyframes", {})
|
||||
if not isinstance(keyframes, dict):
|
||||
return aiohttp.web.json_response(
|
||||
{"error": "keyframes must be a dict"}, status=400
|
||||
)
|
||||
|
||||
audio_intervals = body.get("audio_intervals", [])
|
||||
if not isinstance(audio_intervals, list):
|
||||
return aiohttp.web.json_response(
|
||||
{"error": "audio_intervals must be a list"}, status=400
|
||||
)
|
||||
|
||||
fps = body.get("fps", 30.0)
|
||||
try:
|
||||
fps = float(fps)
|
||||
except (TypeError, ValueError):
|
||||
return aiohttp.web.json_response(
|
||||
{"error": "fps must be a number"}, status=400
|
||||
)
|
||||
|
||||
input_dir = folder_paths.get_input_directory()
|
||||
try:
|
||||
out_name = export_mask_video_from_request(
|
||||
input_dir, filename, keyframes, audio_intervals, fps
|
||||
)
|
||||
except FileNotFoundError as e:
|
||||
return aiohttp.web.json_response({"error": str(e)}, status=404)
|
||||
except RuntimeError as e:
|
||||
return aiohttp.web.json_response({"error": str(e)}, status=500)
|
||||
except Exception as e:
|
||||
return aiohttp.web.json_response(
|
||||
{"error": f"export failed: {e}"}, status=500
|
||||
)
|
||||
|
||||
return aiohttp.web.json_response(
|
||||
{"filename": out_name, "path": os.path.join(input_dir, out_name)}
|
||||
)
|
||||
@@ -928,7 +928,10 @@ def test_merge_audio_resamples_to_original_rate(monkeypatch, tmp_path) -> None:
|
||||
am = torch.ones(8)
|
||||
out = nodes.merge_audio_with_mask(orig, inp, am, 0.0, 32000, 24000)
|
||||
assert out.shape == (1, 2, 32000) # at the original sample rate
|
||||
assert (out == 1.0).all() # fully masked: everything from the inpainted track
|
||||
# fully masked: everything from the inpainted track. torchaudio's
|
||||
# resampler rings at the signal edges (a constant signal is a step at the
|
||||
# boundary), so assert the interior is exactly the inpainted track.
|
||||
assert torch.allclose(out[..., 512:-512], torch.ones_like(out[..., 512:-512]), atol=1e-3)
|
||||
|
||||
|
||||
def test_scipy_edt_matches_python_fallback(monkeypatch) -> None:
|
||||
@@ -1043,15 +1046,21 @@ def test_image_encode_accepts_3d_and_4d_masks(monkeypatch, tmp_path) -> None:
|
||||
assert torch.equal(l1["noise_mask"], l3["noise_mask"])
|
||||
|
||||
|
||||
def test_image_encode_rejects_video_latent(monkeypatch, tmp_path) -> None:
|
||||
def test_image_encode_accepts_video_vae_latent(monkeypatch, tmp_path) -> None:
|
||||
nodes = _import_nodes(monkeypatch, tmp_path)
|
||||
|
||||
class VideoVAE:
|
||||
def encode(self, x):
|
||||
return torch.zeros(1, 24, 2, 2, 3) # 5D: video VAE
|
||||
return torch.zeros(1, 24, 2, 2, 3) # 5D [B, C, T, H, W]: video VAE
|
||||
|
||||
with pytest.raises(ValueError, match="AVEncode"):
|
||||
nodes.LanPaint_ImageEncode().encode(torch.zeros(1, 16, 24, 3), VideoVAE())
|
||||
mask = torch.zeros(16, 24)
|
||||
mask[8:, 12:] = 1.0
|
||||
latent = nodes.LanPaint_ImageEncode().encode(
|
||||
torch.zeros(1, 16, 24, 3), VideoVAE(), mask)[0]
|
||||
# the mask rides as [1, 1, T, H, W], snapped to the latent spatial size
|
||||
assert latent["noise_mask"].shape == (1, 1, 2, 2, 3)
|
||||
assert latent["noise_mask"][0, 0, 1, 1, 1] == 1.0
|
||||
assert latent["noise_mask"][0, 0, 0, 0, 0] == 0.0
|
||||
|
||||
|
||||
def test_image_decode_resizes_and_merges(monkeypatch, tmp_path) -> None:
|
||||
|
||||
@@ -0,0 +1,349 @@
|
||||
"""Tests for the LanPaint video mask metadata read/write (videometa module).
|
||||
|
||||
Requires PyAV. All tests are skipped gracefully when PyAV is unavailable,
|
||||
which is the case in CI environments (e.g., the system Python at C:\\Python314
|
||||
does not have av installed, while the ComfyUI venv at E:\\ComfyUI\\.venv does).
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import tempfile
|
||||
|
||||
import pytest
|
||||
|
||||
# Ensure the project root is on sys.path (tests may be run from any CWD).
|
||||
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
|
||||
|
||||
try:
|
||||
import av
|
||||
import numpy as np
|
||||
|
||||
HAVE_AV = True
|
||||
except ImportError:
|
||||
av = None # type: ignore[assignment]
|
||||
np = None # type: ignore[assignment]
|
||||
HAVE_AV = False
|
||||
|
||||
pytestmark = pytest.mark.skipif(not HAVE_AV, reason="PyAV (av) not available")
|
||||
|
||||
if HAVE_AV:
|
||||
from src.LanPaint.videometa import (
|
||||
decode_payload,
|
||||
encode_payload,
|
||||
export_mask_video_from_request,
|
||||
read_mask_metadata,
|
||||
write_mask_metadata,
|
||||
)
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Helpers
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
if HAVE_AV:
|
||||
|
||||
def _make_test_mp4(path: str, duration_frames: int = 10, fps: int = 10) -> None:
|
||||
"""Create a tiny colour-bar MP4 via PyAV (no external tool required).
|
||||
|
||||
The video is 32x24 pixels, RGB frames encoded with libx264.
|
||||
"""
|
||||
container = av.open(path, "w", format="mp4")
|
||||
stream = container.add_stream("libx264", rate=fps)
|
||||
stream.width = 32
|
||||
stream.height = 24
|
||||
stream.pix_fmt = "yuv420p"
|
||||
for i in range(duration_frames):
|
||||
# simple colour bar: red channel varies per frame
|
||||
arr = np.zeros((24, 32, 3), dtype=np.uint8)
|
||||
r = (i * 25) % 256
|
||||
for py in range(24):
|
||||
for px in range(32):
|
||||
arr[py, px] = [r, (px * 8) % 256, (py * 10) % 256]
|
||||
frame = av.VideoFrame.from_ndarray(arr, format="rgb24")
|
||||
for packet in stream.encode(frame):
|
||||
container.mux(packet)
|
||||
for packet in stream.encode():
|
||||
container.mux(packet)
|
||||
container.close()
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Payload encode / decode
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
class TestPayloadCodec:
|
||||
def test_roundtrip_simple(self) -> None:
|
||||
payload = {
|
||||
"version": 1,
|
||||
"video": "test.mp4",
|
||||
"fps": 30.0,
|
||||
"keyframes": {"0": "iVBORw0KGgoAAAA="},
|
||||
"audio_intervals": [{"start": 1.0, "end": 2.5}],
|
||||
}
|
||||
encoded = encode_payload(payload)
|
||||
assert isinstance(encoded, str)
|
||||
decoded = decode_payload(encoded)
|
||||
assert decoded == payload
|
||||
|
||||
def test_roundtrip_unicode(self) -> None:
|
||||
payload = {
|
||||
"version": 1,
|
||||
"video": "テスト動画.mp4",
|
||||
"fps": 24.0,
|
||||
"keyframes": {
|
||||
"0": "iVBORw0KGgoAAAA=",
|
||||
"12": "iVBORw0KGgoAAAANSUhEUg==",
|
||||
},
|
||||
"audio_intervals": [
|
||||
{"start": 0.5, "end": 1.25},
|
||||
{"start": 3.0, "end": 4.75},
|
||||
],
|
||||
}
|
||||
encoded = encode_payload(payload)
|
||||
decoded = decode_payload(encoded)
|
||||
assert decoded == payload
|
||||
|
||||
def test_empty_keyframes(self) -> None:
|
||||
payload = {
|
||||
"version": 1,
|
||||
"video": "no_mask.mp4",
|
||||
"fps": 30.0,
|
||||
"keyframes": {},
|
||||
"audio_intervals": [],
|
||||
}
|
||||
encoded = encode_payload(payload)
|
||||
decoded = decode_payload(encoded)
|
||||
assert decoded == payload
|
||||
|
||||
def test_decode_none(self) -> None:
|
||||
assert decode_payload(None) is None
|
||||
|
||||
def test_decode_garbage(self) -> None:
|
||||
assert decode_payload("not json") is None
|
||||
assert decode_payload(42) is None # type: ignore[arg-type]
|
||||
assert decode_payload("[]") is None # valid JSON but not a dict
|
||||
|
||||
def test_decode_missing_keyframes_ok(self) -> None:
|
||||
# payloads missing optional fields still decode
|
||||
assert decode_payload('{"version":1,"video":"v.mp4"}') == {
|
||||
"version": 1,
|
||||
"video": "v.mp4",
|
||||
}
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Read / write round-trip
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
class TestWriteRead:
|
||||
def test_roundtrip_with_keyframes(self) -> None:
|
||||
payload = {
|
||||
"version": 1,
|
||||
"video": "src.mp4",
|
||||
"fps": 30.0,
|
||||
"keyframes": {
|
||||
"0": "iVBORw0KGgoAAAA=",
|
||||
"5": "iVBORw0KGgoAAAANSUhEUg==",
|
||||
},
|
||||
"audio_intervals": [{"start": 1.0, "end": 3.0}],
|
||||
}
|
||||
with tempfile.TemporaryDirectory() as td:
|
||||
src = os.path.join(td, "src.mp4")
|
||||
dst = os.path.join(td, "out.mp4")
|
||||
_make_test_mp4(src, duration_frames=10, fps=30)
|
||||
write_mask_metadata(src, dst, payload)
|
||||
assert os.path.isfile(dst)
|
||||
read_back = read_mask_metadata(dst)
|
||||
assert read_back == payload
|
||||
|
||||
def test_tag_absent_on_plain_mp4(self) -> None:
|
||||
with tempfile.TemporaryDirectory() as td:
|
||||
src = os.path.join(td, "plain.mp4")
|
||||
_make_test_mp4(src)
|
||||
result = read_mask_metadata(src)
|
||||
assert result is None
|
||||
|
||||
def test_source_is_never_modified(self) -> None:
|
||||
payload = {
|
||||
"version": 1,
|
||||
"video": "src.mp4",
|
||||
"fps": 25.0,
|
||||
"keyframes": {},
|
||||
"audio_intervals": [],
|
||||
}
|
||||
with tempfile.TemporaryDirectory() as td:
|
||||
src = os.path.join(td, "src.mp4")
|
||||
dst = os.path.join(td, "out.mp4")
|
||||
_make_test_mp4(src, duration_frames=5, fps=25)
|
||||
|
||||
# hash the source bytes before writing
|
||||
src_bytes_before = open(src, "rb").read()
|
||||
write_mask_metadata(src, dst, payload)
|
||||
src_bytes_after = open(src, "rb").read()
|
||||
assert src_bytes_before == src_bytes_after, (
|
||||
"write_mask_metadata modified the source file"
|
||||
)
|
||||
|
||||
def test_unicode_in_keyframe_data(self) -> None:
|
||||
# base64 strings can contain unicode context; metadata values are
|
||||
# UTF-8 ― ensure round-trip with non-ASCII frame indices works.
|
||||
payload = {
|
||||
"version": 1,
|
||||
"video": "видео.mp4",
|
||||
"fps": 24.0,
|
||||
"keyframes": {"0": "iVBORw0KGgoAAAA="},
|
||||
"audio_intervals": [],
|
||||
}
|
||||
with tempfile.TemporaryDirectory() as td:
|
||||
src = os.path.join(td, "src.mp4")
|
||||
dst = os.path.join(td, "out.mp4")
|
||||
_make_test_mp4(src, duration_frames=6, fps=24)
|
||||
write_mask_metadata(src, dst, payload)
|
||||
result = read_mask_metadata(dst)
|
||||
assert result is not None
|
||||
assert result["video"] == "видео.mp4"
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Filename suffixing (non-clobber)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
class TestExportMaskVideo:
|
||||
def test_basic_export(self) -> None:
|
||||
with tempfile.TemporaryDirectory() as td:
|
||||
src_name = "video.mp4"
|
||||
src = os.path.join(td, src_name)
|
||||
_make_test_mp4(src, duration_frames=10, fps=30)
|
||||
out_name = export_mask_video_from_request(
|
||||
input_dir=td,
|
||||
filename=src_name,
|
||||
keyframes={"0": "abc123"},
|
||||
audio_intervals=[{"start": 0.0, "end": 2.0}],
|
||||
fps=30.0,
|
||||
)
|
||||
assert out_name == "video_masked.mp4"
|
||||
assert os.path.isfile(os.path.join(td, out_name))
|
||||
|
||||
def test_suffix_when_target_exists(self) -> None:
|
||||
with tempfile.TemporaryDirectory() as td:
|
||||
src_name = "video.mp4"
|
||||
src = os.path.join(td, src_name)
|
||||
_make_test_mp4(src, duration_frames=5, fps=25)
|
||||
|
||||
# Create a fake collision file
|
||||
with open(os.path.join(td, "video_masked.mp4"), "wb") as f:
|
||||
f.write(b"not an mp4")
|
||||
|
||||
out_name = export_mask_video_from_request(
|
||||
input_dir=td,
|
||||
filename=src_name,
|
||||
keyframes={},
|
||||
audio_intervals=[],
|
||||
fps=25.0,
|
||||
)
|
||||
assert out_name == "video_masked_2.mp4"
|
||||
assert os.path.isfile(os.path.join(td, out_name))
|
||||
|
||||
def test_multiple_suffixes(self) -> None:
|
||||
with tempfile.TemporaryDirectory() as td:
|
||||
src_name = "video.mp4"
|
||||
src = os.path.join(td, src_name)
|
||||
_make_test_mp4(src, duration_frames=5, fps=25)
|
||||
# create several collisions
|
||||
for name in ("video_masked.mp4", "video_masked_2.mp4", "video_masked_3.mp4"):
|
||||
with open(os.path.join(td, name), "wb") as f:
|
||||
f.write(b"not an mp4")
|
||||
|
||||
out_name = export_mask_video_from_request(
|
||||
input_dir=td,
|
||||
filename=src_name,
|
||||
keyframes={},
|
||||
audio_intervals=[],
|
||||
fps=25.0,
|
||||
)
|
||||
assert out_name == "video_masked_4.mp4"
|
||||
|
||||
def test_source_not_found(self) -> None:
|
||||
with tempfile.TemporaryDirectory() as td:
|
||||
with pytest.raises(FileNotFoundError):
|
||||
export_mask_video_from_request(
|
||||
input_dir=td,
|
||||
filename="nonexistent.mp4",
|
||||
keyframes={},
|
||||
audio_intervals=[],
|
||||
fps=30.0,
|
||||
)
|
||||
|
||||
def test_payload_fidelity(self) -> None:
|
||||
"""The exported file must carry the exact payload that was given."""
|
||||
payload_kf = {"0": "iVBORw0KGgoAAAA=", "7": "/9j/4AAQSkZJRgABAQ=="}
|
||||
payload_ai = [
|
||||
{"start": 0.5, "end": 1.0},
|
||||
{"start": 3.25, "end": 5.75},
|
||||
]
|
||||
with tempfile.TemporaryDirectory() as td:
|
||||
src_name = "vid.mp4"
|
||||
_make_test_mp4(os.path.join(td, src_name), duration_frames=10, fps=30)
|
||||
out_name = export_mask_video_from_request(
|
||||
input_dir=td,
|
||||
filename=src_name,
|
||||
keyframes=payload_kf,
|
||||
audio_intervals=payload_ai,
|
||||
fps=30.0,
|
||||
)
|
||||
payload = read_mask_metadata(os.path.join(td, out_name))
|
||||
assert payload is not None
|
||||
assert payload["version"] == 1
|
||||
assert payload["video"] == src_name
|
||||
assert payload["fps"] == 30.0
|
||||
assert payload["keyframes"] == payload_kf
|
||||
assert payload["audio_intervals"] == payload_ai
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Metadata coexistence
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
class TestMetadataCoexistence:
|
||||
def test_preexisting_metadata_is_preserved(self) -> None:
|
||||
"""When the source MP4 already has metadata (e.g., title), it survives."""
|
||||
with tempfile.TemporaryDirectory() as td:
|
||||
src = os.path.join(td, "labelled.mp4")
|
||||
# Create with a metadata tag
|
||||
container = av.open(src, "w", format="mp4")
|
||||
container.metadata["title"] = "Original Title"
|
||||
container.metadata["comment"] = "Should survive"
|
||||
stream = container.add_stream("libx264", rate=10)
|
||||
stream.width = 32
|
||||
stream.height = 24
|
||||
stream.pix_fmt = "yuv420p"
|
||||
frame = av.VideoFrame.from_ndarray(
|
||||
np.zeros((24, 32, 3), dtype=np.uint8), format="rgb24"
|
||||
)
|
||||
for pkt in stream.encode(frame):
|
||||
container.mux(pkt)
|
||||
for pkt in stream.encode():
|
||||
container.mux(pkt)
|
||||
container.close()
|
||||
|
||||
dst = os.path.join(td, "labelled_masked.mp4")
|
||||
payload = {
|
||||
"version": 1,
|
||||
"video": "labelled.mp4",
|
||||
"fps": 10.0,
|
||||
"keyframes": {},
|
||||
"audio_intervals": [],
|
||||
}
|
||||
write_mask_metadata(src, dst, payload)
|
||||
|
||||
result = read_mask_metadata(dst)
|
||||
assert result == payload
|
||||
|
||||
# Verify original metadata survived
|
||||
container2 = av.open(dst, "r")
|
||||
assert container2.metadata.get("title") == "Original Title"
|
||||
assert container2.metadata.get("comment") == "Should survive"
|
||||
container2.close()
|
||||
@@ -345,6 +345,7 @@ class VideoMaskEditor {
|
||||
this.peaks = null; // decoded waveform peaks
|
||||
this.waveCanvas = null;
|
||||
this.waveDrag = null; // {start, end} while dragging a new interval
|
||||
this._maskVideo = null; // last video for which masks were auto-loaded
|
||||
}
|
||||
|
||||
/* ---------------- lifecycle ---------------- */
|
||||
@@ -369,7 +370,9 @@ class VideoMaskEditor {
|
||||
|
||||
try {
|
||||
await this._loadVideo(filename);
|
||||
await this._loadSavedKeyframes();
|
||||
if (!this._metaLoaded) {
|
||||
await this._loadSavedKeyframes();
|
||||
}
|
||||
this.peaks = await loadAudioPeaks(this.video.currentSrc);
|
||||
this._loadAudioIntervals();
|
||||
await this.showFrame(0);
|
||||
@@ -517,6 +520,114 @@ class VideoMaskEditor {
|
||||
video.pause();
|
||||
this.fps = await measureVideoFps(video);
|
||||
this.frameCount = Math.max(1, Math.round(video.duration * this.fps));
|
||||
this._metaLoaded = await this._autoLoadMaskMeta(filename);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch `/lanpaint/video_mask_meta` for the video and, if metadata is
|
||||
* present, upload the base64 keyframes as PNGs and populate the timeline
|
||||
* and widget. Returns true when metadata was found and loaded.
|
||||
*/
|
||||
async _autoLoadMaskMeta(filename) {
|
||||
try {
|
||||
const url =
|
||||
api.apiURL(
|
||||
"/lanpaint/video_mask_meta?filename=" + encodeURIComponent(filename)
|
||||
) + "&rand=" + Math.random();
|
||||
const resp = await fetch(url);
|
||||
if (!resp.ok) throw new Error("HTTP " + resp.status);
|
||||
const json = await resp.json();
|
||||
if (!json.found || !json.payload) {
|
||||
// the video carries no mask metadata: the workflow's masks are
|
||||
// meaningless for it, so clear them (the video is the source
|
||||
// of truth for masks)
|
||||
this.timeline.keyframes.clear();
|
||||
this.timeline.sdfCache.clear();
|
||||
this.undo.clear();
|
||||
this.audioIntervals = [];
|
||||
const kw = this.getKeyframesWidget();
|
||||
if (kw) kw.value = "{}";
|
||||
const aw = this.getAudioMaskWidget();
|
||||
if (aw) aw.value = "[]";
|
||||
return false;
|
||||
}
|
||||
const payload = json.payload;
|
||||
// clear existing keyframes (the metadata is the source of truth)
|
||||
this.timeline.keyframes.clear();
|
||||
this.timeline.sdfCache.clear();
|
||||
this.undo.clear();
|
||||
|
||||
const keyframeData = payload.keyframes || {};
|
||||
const filenames = {};
|
||||
const stamp = Date.now();
|
||||
for (const [idxStr, b64png] of Object.entries(keyframeData)) {
|
||||
const idx = parseInt(idxStr, 10);
|
||||
if (isNaN(idx) || idx < 0 || idx >= this.frameCount) continue;
|
||||
try {
|
||||
const img = await new Promise((resolve, reject) => {
|
||||
const im = new Image();
|
||||
im.onload = () => resolve(im);
|
||||
im.onerror = () => reject(new Error("decode failed"));
|
||||
im.src = "data:image/png;base64," + b64png;
|
||||
});
|
||||
const c = this.timeline.newMaskCanvas();
|
||||
c.getContext("2d").drawImage(img, 0, 0, c.width, c.height);
|
||||
this.timeline.setKeyframe(idx, c);
|
||||
|
||||
// upload so the widget stores a server filename
|
||||
const blob = await new Promise((r) => c.toBlob(r, "image/png"));
|
||||
if (!blob) continue;
|
||||
const name = "lanpaint_kf_" + stamp + "_" + idx + ".png";
|
||||
const fd = new FormData();
|
||||
fd.append("image", blob, name);
|
||||
fd.append("type", "input");
|
||||
const upResp = await api.fetchApi("/upload/image", {
|
||||
method: "POST",
|
||||
body: fd,
|
||||
});
|
||||
if (!upResp.ok) continue;
|
||||
const upData = await upResp.json();
|
||||
filenames[idx] = upData.name;
|
||||
} catch (e) {
|
||||
console.warn("[LanPaint] auto-load keyframe decode failed:", e);
|
||||
}
|
||||
}
|
||||
|
||||
const kw = this.getKeyframesWidget();
|
||||
if (kw) kw.value = JSON.stringify(filenames);
|
||||
const aw = this.getAudioMaskWidget();
|
||||
if (aw) aw.value = JSON.stringify(payload.audio_intervals || "[]");
|
||||
this._loadAudioIntervals();
|
||||
this._maskVideo = filename;
|
||||
|
||||
// keep widget_values in sync
|
||||
if (this.node.widgets_values && this.node.widgets) {
|
||||
for (const ww of [kw, aw]) {
|
||||
if (!ww) continue;
|
||||
const wi = this.node.widgets.indexOf(ww);
|
||||
if (wi >= 0) this.node.widgets_values[wi] = ww.value;
|
||||
}
|
||||
}
|
||||
|
||||
console.log(
|
||||
"[LanPaint VideoMaskEditor] auto-loaded",
|
||||
Object.keys(filenames).length,
|
||||
"keyframes from mp4 metadata"
|
||||
);
|
||||
return true;
|
||||
} catch (err) {
|
||||
console.warn("[LanPaint] video_mask_meta fetch failed:", err);
|
||||
// no metadata could be read: treat like not-found and clear
|
||||
this.timeline.keyframes.clear();
|
||||
this.timeline.sdfCache.clear();
|
||||
this.undo.clear();
|
||||
this.audioIntervals = [];
|
||||
const kw = this.getKeyframesWidget();
|
||||
if (kw) kw.value = "{}";
|
||||
const aw = this.getAudioMaskWidget();
|
||||
if (aw) aw.value = "[]";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/** Canvas for frame `index` (cached, LRU). */
|
||||
@@ -690,6 +801,93 @@ class VideoMaskEditor {
|
||||
}
|
||||
}
|
||||
|
||||
async _exportMaskVideo() {
|
||||
const filename = this.getVideoFilename();
|
||||
if (!filename) {
|
||||
alert("No video file selected.");
|
||||
return;
|
||||
}
|
||||
if (this.timeline.keyframes.size === 0 && this.audioIntervals.length === 0) {
|
||||
alert("No keyframes or audio intervals to export.");
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const keyframes = {};
|
||||
for (const [idx, canvas] of this.timeline.keyframes) {
|
||||
keyframes[idx] = canvas
|
||||
.toDataURL("image/png")
|
||||
.replace(/^data:image\/png;base64,/, "");
|
||||
}
|
||||
const body = {
|
||||
filename: filename,
|
||||
keyframes: keyframes,
|
||||
audio_intervals: this.audioIntervals,
|
||||
fps: this.fps,
|
||||
};
|
||||
const resp = await api.fetchApi("/lanpaint/export_mask_video", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(body),
|
||||
});
|
||||
if (!resp.ok) {
|
||||
const text = await resp.text().catch(() => "");
|
||||
throw new Error("Export failed (" + resp.status + "): " + text);
|
||||
}
|
||||
const result = await resp.json();
|
||||
this._maskVideo = filename;
|
||||
// offer a native save dialog (choose location + name) when available;
|
||||
// the input/ copy from the export stays as the ComfyUI-visible copy
|
||||
if (typeof window.showSaveFilePicker === "function") {
|
||||
try {
|
||||
const viewUrl =
|
||||
api.apiURL(
|
||||
"/view?filename=" + encodeURIComponent(result.filename) + "&type=input"
|
||||
) + app.getRandParam();
|
||||
const buf = await (await fetch(viewUrl)).arrayBuffer();
|
||||
const handle = await window.showSaveFilePicker({
|
||||
suggestedName: result.filename,
|
||||
types: [
|
||||
{
|
||||
description: "MP4 video",
|
||||
accept: { "video/mp4": [".mp4"] },
|
||||
},
|
||||
],
|
||||
});
|
||||
const writable = await handle.createWritable();
|
||||
await writable.write(new Uint8Array(buf));
|
||||
await writable.close();
|
||||
this._flash(
|
||||
"Saved to " + handle.name + " (also in input: " + result.filename + ")"
|
||||
);
|
||||
} catch (pickErr) {
|
||||
// user cancelled the picker or the API failed: the input/
|
||||
// copy is still there and usable
|
||||
this._flash("Exported to input: " + (result.path || result.filename));
|
||||
}
|
||||
} else {
|
||||
this._flash("Exported: " + (result.path || result.filename));
|
||||
}
|
||||
|
||||
// If the video widget points at the source file, refresh it to the
|
||||
// newly exported masked file.
|
||||
const vw = this.node.widgets?.find((x) => x.name === "video");
|
||||
if (vw && vw.value === filename && vw.callback) {
|
||||
vw.value = result.filename;
|
||||
if (this.node.widgets_values) {
|
||||
const vi = this.node.widgets.indexOf(vw);
|
||||
if (vi >= 0) this.node.widgets_values[vi] = result.filename;
|
||||
}
|
||||
try {
|
||||
vw.callback(result.filename);
|
||||
} catch (_) {
|
||||
/* ignore callback errors */
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
alert("Export failed: " + err.message);
|
||||
}
|
||||
}
|
||||
|
||||
async save() {
|
||||
const w = this.getKeyframesWidget();
|
||||
if (!w) return;
|
||||
@@ -745,6 +943,7 @@ class VideoMaskEditor {
|
||||
<span id="lpvme-frame-label">frame 0 / 0</span>
|
||||
<span style="flex:1"></span>
|
||||
<button id="lpvme-save" title="Upload keyframes and write them into the node">Save</button>
|
||||
<button id="lpvme-export" title="Remux the source video with mask metadata">导出 mask 视频</button>
|
||||
<button id="lpvme-close" title="Close (Escape)">Close</button>
|
||||
</div>
|
||||
<div id="lpvme-stage" style="flex:1;position:relative;overflow:hidden;background:#0f0f0f;">
|
||||
@@ -859,6 +1058,9 @@ class VideoMaskEditor {
|
||||
alert("Save failed: " + err.message);
|
||||
}
|
||||
});
|
||||
this.el.querySelector("#lpvme-export").addEventListener("click", () => {
|
||||
this._exportMaskVideo();
|
||||
});
|
||||
this.el.querySelector("#lpvme-close").addEventListener("click", () => this.close());
|
||||
this.el.querySelector("#lpvme-add-kf").addEventListener("click", () => {
|
||||
this.ensureKeyframe(this.current);
|
||||
@@ -1030,9 +1232,15 @@ class NodeMaskPreview {
|
||||
this.lastDrawnValue = null;
|
||||
this.lastPlayheadX = null;
|
||||
this._sizeWrapped = false;
|
||||
this._maskVideo = null; // last video for which masks were auto-loaded
|
||||
this._tick();
|
||||
}
|
||||
|
||||
_getVideoFilename() {
|
||||
const w = this.node.widgets?.find((x) => x.name === "video");
|
||||
return w && typeof w.value === "string" ? w.value : null;
|
||||
}
|
||||
|
||||
_keyframesWidget() {
|
||||
return this.node.widgets?.find((w) => w.name === "keyframes");
|
||||
}
|
||||
@@ -1188,10 +1396,114 @@ class NodeMaskPreview {
|
||||
this.timeline = new MaskTimeline();
|
||||
this.timeline.setSize(video.videoWidth, video.videoHeight);
|
||||
this.lastMaskIdx = null;
|
||||
await this._reloadKeyframes();
|
||||
const filename = this._getVideoFilename();
|
||||
if (filename) {
|
||||
const metaLoaded = await this._autoLoadMaskMeta(filename);
|
||||
if (!metaLoaded) {
|
||||
await this._reloadKeyframes();
|
||||
}
|
||||
} else {
|
||||
await this._reloadKeyframes();
|
||||
}
|
||||
this.lastKeyframesValue = this._keyframesWidget()?.value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch `/lanpaint/video_mask_meta` for the video and, if metadata is
|
||||
* present, upload the base64 keyframes as PNGs and populate the timeline
|
||||
* and widget. Returns true when metadata was found and loaded.
|
||||
*/
|
||||
async _autoLoadMaskMeta(filename) {
|
||||
try {
|
||||
const url =
|
||||
api.apiURL(
|
||||
"/lanpaint/video_mask_meta?filename=" + encodeURIComponent(filename)
|
||||
) + "&rand=" + Math.random();
|
||||
const resp = await fetch(url);
|
||||
if (!resp.ok) throw new Error("HTTP " + resp.status);
|
||||
const json = await resp.json();
|
||||
if (!json.found || !json.payload) {
|
||||
// the video carries no mask metadata: clear the workflow's masks
|
||||
this.timeline.keyframes.clear();
|
||||
this.timeline.sdfCache.clear();
|
||||
const kw = this._keyframesWidget();
|
||||
if (kw) kw.value = "{}";
|
||||
const aw = this.getAudioMaskWidget();
|
||||
if (aw) aw.value = "[]";
|
||||
return false;
|
||||
}
|
||||
const payload = json.payload;
|
||||
this.timeline.keyframes.clear();
|
||||
this.timeline.sdfCache.clear();
|
||||
|
||||
const keyframeData = payload.keyframes || {};
|
||||
const filenames = {};
|
||||
const stamp = Date.now();
|
||||
for (const [idxStr, b64png] of Object.entries(keyframeData)) {
|
||||
const idx = parseInt(idxStr, 10);
|
||||
if (isNaN(idx) || idx < 0 || idx >= this.frameCount) continue;
|
||||
try {
|
||||
const img = await new Promise((resolve, reject) => {
|
||||
const im = new Image();
|
||||
im.onload = () => resolve(im);
|
||||
im.onerror = () => reject(new Error("decode failed"));
|
||||
im.src = "data:image/png;base64," + b64png;
|
||||
});
|
||||
const c = this.timeline.newMaskCanvas();
|
||||
c.getContext("2d").drawImage(img, 0, 0, c.width, c.height);
|
||||
this.timeline.setKeyframe(idx, c);
|
||||
|
||||
const blob = await new Promise((r) => c.toBlob(r, "image/png"));
|
||||
if (!blob) continue;
|
||||
const name = "lanpaint_kf_" + stamp + "_" + idx + ".png";
|
||||
const fd = new FormData();
|
||||
fd.append("image", blob, name);
|
||||
fd.append("type", "input");
|
||||
const upResp = await api.fetchApi("/upload/image", {
|
||||
method: "POST",
|
||||
body: fd,
|
||||
});
|
||||
if (!upResp.ok) continue;
|
||||
const upData = await upResp.json();
|
||||
filenames[idx] = upData.name;
|
||||
} catch (e) {
|
||||
console.warn("[LanPaint] preview auto-load keyframe decode failed:", e);
|
||||
}
|
||||
}
|
||||
|
||||
const kw = this._keyframesWidget();
|
||||
if (kw) kw.value = JSON.stringify(filenames);
|
||||
const aw = this.getAudioMaskWidget();
|
||||
if (aw) aw.value = JSON.stringify(payload.audio_intervals || "[]");
|
||||
this._maskVideo = filename;
|
||||
|
||||
if (this.node.widgets_values && this.node.widgets) {
|
||||
for (const ww of [kw, aw]) {
|
||||
if (!ww) continue;
|
||||
const wi = this.node.widgets.indexOf(ww);
|
||||
if (wi >= 0) this.node.widgets_values[wi] = ww.value;
|
||||
}
|
||||
}
|
||||
|
||||
console.log(
|
||||
"[LanPaint NodeMaskPreview] auto-loaded",
|
||||
Object.keys(filenames).length,
|
||||
"keyframes from mp4 metadata"
|
||||
);
|
||||
return true;
|
||||
} catch (err) {
|
||||
console.warn("[LanPaint] preview video_mask_meta fetch failed:", err);
|
||||
// no metadata could be read: treat like not-found and clear
|
||||
this.timeline.keyframes.clear();
|
||||
this.timeline.sdfCache.clear();
|
||||
const kw = this._keyframesWidget();
|
||||
if (kw) kw.value = "{}";
|
||||
const aw = this.getAudioMaskWidget();
|
||||
if (aw) aw.value = "[]";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
async _reloadKeyframes() {
|
||||
const w = this._keyframesWidget();
|
||||
const timeline = this.timeline;
|
||||
|
||||