Prepare DiffusionGemma Director 0.2.0

This commit is contained in:
danrh
2026-08-07 15:11:28 -05:00
parent 119634ca54
commit dfcd642a21
27 changed files with 29785 additions and 108 deletions
@@ -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()
+3 -3
View File
@@ -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"
File diff suppressed because it is too large Load Diff