Prepare DiffusionGemma Director 0.2.0
This commit is contained in:
@@ -0,0 +1,301 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import shutil
|
||||
import uuid
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
SOURCE = (
|
||||
ROOT.parent
|
||||
/ "ComfyUI-LTXVideo"
|
||||
/ "example_workflows"
|
||||
/ "2.3"
|
||||
/ "LTX-2.3_ICLoRA_Outpaint_Two_Stage_Distilled.json"
|
||||
)
|
||||
OUTPUT = ROOT / "examples" / "08_ltx23_reframe_iclora_outpaint_two_stage.json"
|
||||
INSTALLED_OUTPUT = (
|
||||
ROOT.parents[1]
|
||||
/ "user"
|
||||
/ "default"
|
||||
/ "workflows"
|
||||
/ "LTX 2.3 Reframe IC-LoRA Outpaint - Verified.json"
|
||||
)
|
||||
|
||||
REMOVED_NODE_IDS = {5168, 5363, 5371, 5393}
|
||||
REMOVED_LINK_IDS = {14383, 14395, 14445, 14446}
|
||||
|
||||
|
||||
def node_by_id(workflow: dict[str, Any], node_id: int) -> dict[str, Any]:
|
||||
return next(node for node in workflow["nodes"] if node["id"] == node_id)
|
||||
|
||||
|
||||
def build_reframe_node() -> dict[str, Any]:
|
||||
return {
|
||||
"id": 5368,
|
||||
"type": "LTXReframeLayout",
|
||||
"pos": [-4225.854240441995, 3547.938320763212],
|
||||
"size": [600, 760],
|
||||
"flags": {},
|
||||
"order": 12,
|
||||
"mode": 0,
|
||||
"inputs": [],
|
||||
"outputs": [
|
||||
{"name": "padded_frames", "type": "IMAGE", "links": [14364]},
|
||||
{"name": "outpaint_mask", "type": "MASK", "links": [14369]},
|
||||
{"name": "source_mask", "type": "MASK", "links": []},
|
||||
{"name": "source_frames", "type": "IMAGE", "links": []},
|
||||
{"name": "audio", "type": "AUDIO", "links": [14422]},
|
||||
{"name": "fps", "type": "FLOAT", "links": [13889, 13968]},
|
||||
{"name": "source_video", "type": "VIDEO", "links": []},
|
||||
{"name": "target_width", "type": "INT", "links": []},
|
||||
{"name": "target_height", "type": "INT", "links": []},
|
||||
{"name": "placement_json", "type": "STRING", "links": []},
|
||||
],
|
||||
"title": "1. LTX Reframe Layout · Source & Framing",
|
||||
"properties": {"Node name for S&R": "LTXReframeLayout"},
|
||||
"widgets_values": [
|
||||
"LTX-2_00180_.mp4",
|
||||
"9:16",
|
||||
576,
|
||||
1024,
|
||||
0.5,
|
||||
0.5,
|
||||
0.7,
|
||||
32,
|
||||
],
|
||||
"color": "#1f4d52",
|
||||
"bgcolor": "#16383c",
|
||||
}
|
||||
|
||||
|
||||
def build_mask_batch_node(
|
||||
node_id: int,
|
||||
*,
|
||||
pos: list[float],
|
||||
mask_link: int,
|
||||
size_link: int,
|
||||
output_link: int,
|
||||
title: str,
|
||||
) -> dict[str, Any]:
|
||||
return {
|
||||
"id": node_id,
|
||||
"type": "MaskExpandBatch+",
|
||||
"pos": pos,
|
||||
"size": [270, 106],
|
||||
"flags": {},
|
||||
"order": 63 if node_id == 5400 else 64,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{"name": "mask", "type": "MASK", "link": mask_link},
|
||||
{
|
||||
"name": "size",
|
||||
"type": "INT",
|
||||
"widget": {"name": "size"},
|
||||
"link": size_link,
|
||||
},
|
||||
],
|
||||
"outputs": [{"name": "MASK", "type": "MASK", "links": [output_link]}],
|
||||
"title": title,
|
||||
"properties": {
|
||||
"cnr_id": "comfyui_essentials",
|
||||
"Node name for S&R": "MaskExpandBatch+",
|
||||
},
|
||||
"widgets_values": [121, "expand"],
|
||||
"color": "#5b4636",
|
||||
"bgcolor": "#3c3027",
|
||||
}
|
||||
|
||||
|
||||
def clean_graph_references(workflow: dict[str, Any]) -> None:
|
||||
valid_node_ids = {node["id"] for node in workflow["nodes"]}
|
||||
workflow["links"] = [
|
||||
link
|
||||
for link in workflow["links"]
|
||||
if link[0] not in REMOVED_LINK_IDS
|
||||
and link[1] in valid_node_ids
|
||||
and link[3] in valid_node_ids
|
||||
]
|
||||
valid_link_ids = {link[0] for link in workflow["links"]}
|
||||
|
||||
for node in workflow["nodes"]:
|
||||
for item in node.get("inputs", []):
|
||||
if item.get("link") not in valid_link_ids:
|
||||
item["link"] = None
|
||||
for item in node.get("outputs", []):
|
||||
item["links"] = [
|
||||
link_id for link_id in item.get("links") or [] if link_id in valid_link_ids
|
||||
]
|
||||
|
||||
extra = workflow.get("extra", {})
|
||||
reroutes = extra.get("reroutes", [])
|
||||
for reroute in reroutes:
|
||||
reroute["linkIds"] = [
|
||||
link_id for link_id in reroute.get("linkIds", []) if link_id in valid_link_ids
|
||||
]
|
||||
extra["reroutes"] = [reroute for reroute in reroutes if reroute.get("linkIds")]
|
||||
extra["linkExtensions"] = [
|
||||
extension
|
||||
for extension in extra.get("linkExtensions", [])
|
||||
if extension.get("id") in valid_link_ids
|
||||
]
|
||||
|
||||
for group in workflow.get("groups", []):
|
||||
group["nodes"] = [node_id for node_id in group.get("nodes", []) if node_id in valid_node_ids]
|
||||
|
||||
workflow["last_node_id"] = max(valid_node_ids)
|
||||
workflow["last_link_id"] = max(valid_link_ids)
|
||||
|
||||
|
||||
def validate_graph(workflow: dict[str, Any]) -> None:
|
||||
nodes = {node["id"]: node for node in workflow["nodes"]}
|
||||
links = {link[0]: link for link in workflow["links"]}
|
||||
if len(nodes) != len(workflow["nodes"]):
|
||||
raise ValueError("Duplicate node IDs remain in the generated workflow.")
|
||||
if len(links) != len(workflow["links"]):
|
||||
raise ValueError("Duplicate link IDs remain in the generated workflow.")
|
||||
|
||||
for link_id, link in links.items():
|
||||
if link[1] not in nodes or link[3] not in nodes:
|
||||
raise ValueError(f"Link {link_id} has a missing endpoint: {link}")
|
||||
source_output = nodes[link[1]]["outputs"][link[2]]
|
||||
destination_input = nodes[link[3]]["inputs"][link[4]]
|
||||
if link_id not in (source_output.get("links") or []):
|
||||
raise ValueError(f"Link {link_id} is absent from its source output socket.")
|
||||
if destination_input.get("link") != link_id:
|
||||
raise ValueError(f"Link {link_id} is absent from its destination input socket.")
|
||||
|
||||
reframe = nodes.get(5368)
|
||||
if reframe is None or reframe["type"] != "LTXReframeLayout":
|
||||
raise ValueError("The generated workflow is missing LTXReframeLayout node 5368.")
|
||||
|
||||
expected = {
|
||||
14364: (5368, 0, 5360, 0, "IMAGE"),
|
||||
14369: (5368, 1, 5365, 0, "MASK"),
|
||||
13889: (5368, 5, 1241, 2, "FLOAT"),
|
||||
13968: (5368, 5, 5227, 2, "FLOAT"),
|
||||
14422: (5368, 4, 5382, 0, "AUDIO"),
|
||||
14376: (5365, 0, 5400, 0, "MASK"),
|
||||
14377: (5364, 0, 5401, 0, "MASK"),
|
||||
14447: (5054, 2, 5400, 1, "INT"),
|
||||
14448: (5054, 2, 5401, 1, "INT"),
|
||||
14449: (5400, 0, 5226, 2, "MASK"),
|
||||
14450: (5401, 0, 5266, 2, "MASK"),
|
||||
}
|
||||
for link_id, contract in expected.items():
|
||||
actual = tuple(links[link_id][1:])
|
||||
if actual != contract:
|
||||
raise ValueError(f"Link {link_id} is {actual}, expected {contract}.")
|
||||
|
||||
|
||||
def build() -> dict[str, Any]:
|
||||
workflow = json.loads(SOURCE.read_text(encoding="utf-8"))
|
||||
workflow["id"] = str(
|
||||
uuid.uuid5(uuid.NAMESPACE_URL, "ltx-2.3-reframe-iclora-outpaint-two-stage")
|
||||
)
|
||||
workflow["nodes"] = [
|
||||
node for node in workflow["nodes"] if node["id"] not in REMOVED_NODE_IDS | {5368}
|
||||
]
|
||||
workflow["nodes"].append(build_reframe_node())
|
||||
workflow["nodes"].extend(
|
||||
[
|
||||
build_mask_batch_node(
|
||||
5400,
|
||||
pos=[1050.0, 3261.0],
|
||||
mask_link=14376,
|
||||
size_link=14447,
|
||||
output_link=14449,
|
||||
title="Full-Res Mask · Match Video Frame Count",
|
||||
),
|
||||
build_mask_batch_node(
|
||||
5401,
|
||||
pos=[-660.0, 3432.0],
|
||||
mask_link=14377,
|
||||
size_link=14448,
|
||||
output_link=14450,
|
||||
title="Half-Res Mask · Match Video Frame Count",
|
||||
),
|
||||
]
|
||||
)
|
||||
|
||||
links = {link[0]: link for link in workflow["links"]}
|
||||
links[14364] = [14364, 5368, 0, 5360, 0, "IMAGE"]
|
||||
links[14369] = [14369, 5368, 1, 5365, 0, "MASK"]
|
||||
links[13889] = [13889, 5368, 5, 1241, 2, "FLOAT"]
|
||||
links[13968] = [13968, 5368, 5, 5227, 2, "FLOAT"]
|
||||
links[14422] = [14422, 5368, 4, 5382, 0, "AUDIO"]
|
||||
links[14376] = [14376, 5365, 0, 5400, 0, "MASK"]
|
||||
links[14377] = [14377, 5364, 0, 5401, 0, "MASK"]
|
||||
links[14447] = [14447, 5054, 2, 5400, 1, "INT"]
|
||||
links[14448] = [14448, 5054, 2, 5401, 1, "INT"]
|
||||
links[14449] = [14449, 5400, 0, 5226, 2, "MASK"]
|
||||
links[14450] = [14450, 5401, 0, 5266, 2, "MASK"]
|
||||
workflow["links"] = list(links.values())
|
||||
|
||||
node_by_id(workflow, 5226)["inputs"][2]["link"] = 14449
|
||||
node_by_id(workflow, 5266)["inputs"][2]["link"] = 14450
|
||||
node_by_id(workflow, 5054)["outputs"][2]["links"].extend([14447, 14448])
|
||||
|
||||
node_by_id(workflow, 5023)["widgets_values"][0] = (
|
||||
"gemma_3_12B_it_fp4_mixed.safetensors"
|
||||
)
|
||||
node_by_id(workflow, 4922)["widgets_values"][0] = (
|
||||
"ltx-2.3-22b-distilled-lora-384-1.1.safetensors"
|
||||
)
|
||||
node_by_id(workflow, 5011)["widgets_values"][0] = (
|
||||
"ltx-2.3-22b-ic-lora-in-outpainting-0.9.safetensors"
|
||||
)
|
||||
node_by_id(workflow, 2483)["widgets_values"][0] = (
|
||||
"A cinematic live-action scene continuing naturally beyond the source frame, "
|
||||
"with a coherent environment, realistic lighting, consistent subjects and camera "
|
||||
"motion, seamless edges, and stable fine detail."
|
||||
)
|
||||
node_by_id(workflow, 5228)["widgets_values"][0] = "video/LTX_Reframe_Outpaint"
|
||||
node_by_id(workflow, 5356)["widgets_values"][0] = (
|
||||
"### Reframe Setup\n\nUse **LTX Reframe Layout** to choose the final canvas and place "
|
||||
"the source. `source_scale < 1` zooms out for outpainting; `source_scale > 1` "
|
||||
"zooms in and crops. The example starts at **576 × 1024**, 121 frames, with "
|
||||
"audio, so it is suitable for a first end-to-end test."
|
||||
)
|
||||
|
||||
for group in workflow.get("groups", []):
|
||||
if group["id"] == 164:
|
||||
group["title"] = "1. Reframe Source Video"
|
||||
group["bounding"] = [-4235.854240441995, 3507.938320763212, 620, 800]
|
||||
elif group["id"] == 168:
|
||||
group["title"] = "2. Prepare Two-Stage Outpaint Conditions"
|
||||
elif group["id"] == 178:
|
||||
group["title"] = "Stage 2 Full-Resolution Mask"
|
||||
elif group["id"] == 179:
|
||||
group["title"] = "Stage 1 Half-Resolution Mask"
|
||||
|
||||
workflow.setdefault("extra", {})["ltx_reframe_workflow"] = {
|
||||
"schema": "ltx_reframe_iclora_outpaint/v1",
|
||||
"based_on": SOURCE.name,
|
||||
"source_node": "LTXReframeLayout",
|
||||
"default_source": "LTX-2_00180_.mp4",
|
||||
"default_target": [576, 1024],
|
||||
"required_lora": "ltx-2.3-22b-ic-lora-in-outpainting-0.9.safetensors",
|
||||
"mask_batch_safety": "MaskExpandBatch+ before both Laplacian blends",
|
||||
}
|
||||
|
||||
clean_graph_references(workflow)
|
||||
validate_graph(workflow)
|
||||
return workflow
|
||||
|
||||
|
||||
def main() -> None:
|
||||
workflow = build()
|
||||
OUTPUT.parent.mkdir(parents=True, exist_ok=True)
|
||||
OUTPUT.write_text(json.dumps(workflow, indent=2) + "\n", encoding="utf-8")
|
||||
INSTALLED_OUTPUT.parent.mkdir(parents=True, exist_ok=True)
|
||||
shutil.copy2(OUTPUT, INSTALLED_OUTPUT)
|
||||
print(f"Wrote {OUTPUT}")
|
||||
print(f"Installed {INSTALLED_OUTPUT}")
|
||||
print(f"Nodes: {len(workflow['nodes'])}; links: {len(workflow['links'])}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -9,9 +9,9 @@ from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
|
||||
DEFAULT_WORKFLOW = Path(r"C:\Users\danrh\Desktop\diffusion_gemma_workflows\LTX-2.3-INPAINTING-EDIT-ANY-VIDEO.json")
|
||||
DEFAULT_OUTPUT_WORKFLOW = Path(r"C:\Users\danrh\Desktop\diffusion_gemma_workflows\LTX-2.3-INPAINTING-EDIT-ANY-VIDEO_DG_PROMPT_INJECTED_v0.json")
|
||||
DEFAULT_MANIFEST = Path(r"C:\Users\danrh\Desktop\diffusion_gemma_workflows\LTX-2.3-INPAINTING-EDIT-ANY-VIDEO_DG_PROMPT_INJECTED_v0.manifest.json")
|
||||
DEFAULT_WORKFLOW = Path("LTX-2.3-INPAINTING-EDIT-ANY-VIDEO.json")
|
||||
DEFAULT_OUTPUT_WORKFLOW = Path("LTX-2.3-INPAINTING-EDIT-ANY-VIDEO_DG_PROMPT_INJECTED_v0.json")
|
||||
DEFAULT_MANIFEST = Path("LTX-2.3-INPAINTING-EDIT-ANY-VIDEO_DG_PROMPT_INJECTED_v0.manifest.json")
|
||||
REPAIR_SCOPE = "localized_masked_inpaint"
|
||||
|
||||
|
||||
|
||||
+1392
-2
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user