Remove Ghost Mask reference mode

This commit is contained in:
OpenClaw Agent
2026-07-17 07:45:53 +00:00
parent 7b4e8865d6
commit 5c4349d9b9
5 changed files with 35 additions and 89 deletions

View File

@@ -58,7 +58,7 @@ This node uses Ollama Locally : you will need to install it (very lightweight) a
- You can still enter your description manually if you don't want to install it but it works very well! - You can still enter your description manually if you don't want to install it but it works very well!
- Both references mode work (fixed), Licon MSR and Ghost Mask. - Reference mode included: Licon MSR.
- Use their Lora, that is an important step : https://huggingface.co/LiconStudio/LTX-2.3-Multiple-Subject-Reference/tree/main - Use their Lora, that is an important step : https://huggingface.co/LiconStudio/LTX-2.3-Multiple-Subject-Reference/tree/main

File diff suppressed because one or more lines are too long

View File

@@ -2534,24 +2534,28 @@ class TimelineEditor {
this.deleteBtn = deleteBtn; this.deleteBtn = deleteBtn;
// --- Ref Option dropdown (sits to the right of Delete) --- // --- Ref Option dropdown (sits to the right of Delete) ---
const refOptionSelect = document.createElement("select"); const refOptionSelect = document.createElement("select");
refOptionSelect.className = "prcs-ref-option-select"; refOptionSelect.className = "prcs-ref-option-select";
refOptionSelect.title = "Character reference mode"; refOptionSelect.title = "Character reference mode";
const REF_OPTIONS = [ const REF_OPTIONS = [
{ value: "Ghost Mask (End)", label: "Ghost Mask mode" }, { value: "Licon MSR (Prefix)", label: "Licon MSR" },
{ value: "Licon MSR (Prefix)", label: "Licon MSR" }, { value: "OFF", label: "OFF" },
{ value: "OFF", label: "OFF" }, ];
]; REF_OPTIONS.forEach(opt => {
REF_OPTIONS.forEach(opt => { const o = document.createElement("option");
const o = document.createElement("option"); o.value = opt.value;
o.value = opt.value; o.textContent = opt.label;
o.textContent = opt.label; refOptionSelect.appendChild(o);
refOptionSelect.appendChild(o); });
}); const refOptionValues = new Set(REF_OPTIONS.map(opt => opt.value));
refOptionSelect.value = this.timeline.reference_mode || "OFF"; const initialRefMode = refOptionValues.has(this.timeline.reference_mode)
refOptionSelect.addEventListener("change", (e) => { ? this.timeline.reference_mode
this.timeline.reference_mode = e.target.value; : "OFF";
this.commitChanges(); refOptionSelect.value = initialRefMode;
this.timeline.reference_mode = initialRefMode;
refOptionSelect.addEventListener("change", (e) => {
this.timeline.reference_mode = e.target.value;
this.commitChanges();
}); });
this.refOptionSelect = refOptionSelect; this.refOptionSelect = refOptionSelect;

View File

@@ -1698,7 +1698,6 @@ def _build_reference_mode_outputs(
clean_latent_frames: int, clean_latent_frames: int,
reference_strength: float, reference_strength: float,
optional_latent, optional_latent,
ref_images,
resize_method: str, resize_method: str,
divisible_by: int, divisible_by: int,
prepare_tensor_image, prepare_tensor_image,
@@ -1785,52 +1784,6 @@ def _build_reference_mode_outputs(
) )
return patched, conditioning, latent, out_guide_data return patched, conditioning, latent, out_guide_data
extra_refs = []
if reference_mode == "Ghost Mask (End)" and ref_images is not None:
try:
for bi in range(ref_images.shape[0]):
extra_refs.append(ref_images[bi:bi + 1])
except Exception as e:
log.warning("[LTXDirector] Could not process ref_images input: %s", e)
ghost_refs = char_images + extra_refs
is_ghost = reference_mode == "Ghost Mask (End)" and bool(ghost_refs)
n_refs = len(ghost_refs) if is_ghost else 0
if is_ghost:
for i, single_ref in enumerate(ghost_refs):
ref_tensor = prepare_tensor_image(single_ref, latent_w, latent_h)
guide_data["images"].append(ref_tensor)
guide_data["insert_frames"].append((clean_latent_frames + i) * 8)
guide_data["strengths"].append(float(reference_strength))
guide_data["ghost_pre_extend"] = int(n_refs)
total_latent_frames = clean_latent_frames + n_refs
tail_pixels = n_refs * 8
local_part = local_prompts.strip() if local_prompts.strip() else global_prompt
clean_lengths = segment_lengths.strip() if segment_lengths.strip() else str(duration_frames)
injected_local = f"{local_part} | {global_prompt}"
injected_lengths = f"{clean_lengths},{tail_pixels}"
dummy_full = {"samples": torch.zeros([1, 128, total_latent_frames, latent_grid_h, latent_grid_w], device=_dev)}
patched, conditioning = _encode_relay(
model, clip, dummy_full, global_prompt, injected_local, injected_lengths, epsilon,
)
if optional_latent is None:
latent = {
"samples": torch.zeros([1, 128, clean_latent_frames, latent_grid_h, latent_grid_w], device=_dev),
"noise_mask": torch.ones((1, 1, clean_latent_frames, latent_grid_h, latent_grid_w), dtype=torch.float32, device=_dev),
}
else:
latent = optional_latent
log.info(
"[LTXDirector] Ghost Mask: %d references; guide will hide them in a tail past "
"frame %d (clean_latent_frames=%d). Enable Clean Latent Slice (length=%d) downstream.",
n_refs, clean_latent_frames, clean_latent_frames, clean_latent_frames,
)
return patched, conditioning, latent, guide_data
if optional_latent is None: if optional_latent is None:
latent = {"samples": torch.zeros([1, 128, clean_latent_frames, latent_grid_h, latent_grid_w], device=_dev)} latent = {"samples": torch.zeros([1, 128, clean_latent_frames, latent_grid_h, latent_grid_w], device=_dev)}
log.info( log.info(
@@ -2054,13 +2007,9 @@ class LTXDirector(io.ComfyNode):
"vae", optional=True, "vae", optional=True,
tooltip="Optional. Connect the LTX Autoencoder/VAE here to natively encode the MSR visual reference slideshow into the latent prefix. Required for the 'Licon MSR' ref option.", tooltip="Optional. Connect the LTX Autoencoder/VAE here to natively encode the MSR visual reference slideshow into the latent prefix. Required for the 'Licon MSR' ref option.",
), ),
io.Float.Input( io.Float.Input(
"reference_strength", default=1.0, min=0.0, max=5.0, step=0.05, optional=True, "reference_strength", default=1.0, min=0.0, max=5.0, step=0.05, optional=True,
tooltip="Guide strength applied to the character reference images (Ghost Mask / Licon MSR ref options).", tooltip="Guide strength applied to the character reference images when using Licon MSR.",
),
io.Image.Input(
"ref_images", optional=True,
tooltip="Ghost Mask only. Extra reference image(s) (e.g. an object) — a single image or a batch. Appended as their own block of hidden reference frames AFTER the @char references, for any number of characters loaded (0-6). Ignored in MSR / OFF modes.",
), ),
MSRCharacterSetData.Input( MSRCharacterSetData.Input(
"character_set", "character_set",
@@ -2107,7 +2056,7 @@ class LTXDirector(io.ComfyNode):
custom_width=768, custom_height=512, resize_method="maintain aspect ratio", custom_width=768, custom_height=512, resize_method="maintain aspect ratio",
divisible_by=32, img_compression=0, audio_vae=None, optional_latent=None, divisible_by=32, img_compression=0, audio_vae=None, optional_latent=None,
use_custom_audio=False, inpaint_audio=True, use_custom_motion=True, override_audio=False, use_custom_audio=False, inpaint_audio=True, use_custom_motion=True, override_audio=False,
vae=None, reference_strength=1.0, ref_images=None, character_set=None, timeline=None, vae=None, reference_strength=1.0, character_set=None, timeline=None,
start=None, end=None, duration=None) -> io.NodeOutput: start=None, end=None, duration=None) -> io.NodeOutput:
input_dir = folder_paths.get_input_directory() input_dir = folder_paths.get_input_directory()
image_cache = {} image_cache = {}
@@ -2182,9 +2131,12 @@ class LTXDirector(io.ComfyNode):
log.info(f"[LTXDirector] execute RECEIVED global_prompt: {repr(global_prompt)}") log.info(f"[LTXDirector] execute RECEIVED global_prompt: {repr(global_prompt)}")
# --- Reference option (set by the toolbar "Ref Option" dropdown, stored in timeline JSON) --- # --- Reference option (set by the toolbar "Ref Option" dropdown, stored in timeline JSON) ---
# One of: "Ghost Mask (End)", "Licon MSR (Prefix)", "OFF". # One of: "Licon MSR (Prefix)", "OFF".
reference_mode = tdata.get("reference_mode", "OFF") reference_mode = tdata.get("reference_mode", "OFF")
if reference_mode not in {"Licon MSR (Prefix)", "OFF"}:
reference_mode = "OFF"
tdata["reference_mode"] = reference_mode
characters = _extract_runtime_character_entries( characters = _extract_runtime_character_entries(
tdata=tdata, tdata=tdata,
character_set=character_set, character_set=character_set,
@@ -2195,7 +2147,7 @@ class LTXDirector(io.ComfyNode):
char_images = [img for slot_images in char_slot_images for img in slot_images] char_images = [img for slot_images in char_slot_images for img in slot_images]
# --- @charN substitution --- # --- @charN substitution ---
# Ghost Mask / OFF: swap @char tags for their VLM descriptions in the prompt text. # OFF: swap @char tags for their VLM descriptions in the prompt text.
# Licon MSR: leave the tags raw — there the reference IMAGE drives identity, and the # Licon MSR: leave the tags raw — there the reference IMAGE drives identity, and the
# tags are used only to pick which character slots feed the slideshow. # tags are used only to pick which character slots feed the slideshow.
if reference_mode == "Licon MSR (Prefix)": if reference_mode == "Licon MSR (Prefix)":
@@ -2267,7 +2219,6 @@ class LTXDirector(io.ComfyNode):
clean_latent_frames=clean_latent_frames, clean_latent_frames=clean_latent_frames,
reference_strength=reference_strength, reference_strength=reference_strength,
optional_latent=optional_latent, optional_latent=optional_latent,
ref_images=ref_images,
resize_method=resize_method, resize_method=resize_method,
divisible_by=divisible_by, divisible_by=divisible_by,
prepare_tensor_image=prepare_tensor_image, prepare_tensor_image=prepare_tensor_image,

View File

@@ -808,15 +808,6 @@ class LTXDirectorGuide:
if auto_snap_ic_grid and model is not None and ic_lora_name != "None": if auto_snap_ic_grid and model is not None and ic_lora_name != "None":
latent_image, noise_mask = _snap_latent_to_downscale(latent_image, noise_mask, latent_downscale_factor, upscale_method) latent_image, noise_mask = _snap_latent_to_downscale(latent_image, noise_mask, latent_downscale_factor, upscale_method)
# Ghost Mask: the Director kept the latent clean (so it stays length-matched with the audio
# latent) and asked us to pre-extend it here by one hidden frame per reference. The tail
# sits past the clean region; the reference images (in guide_data["images"], anchored at
# (clean + i)) are written into it by the loop below, then dropped downstream by Clean
# Latent Slice (length = clean_latent_frames).
_ghost_pre_extend = int(guide_data.get("ghost_pre_extend", 0)) if guide_data else 0
if _ghost_pre_extend > 0:
latent_image, noise_mask = _append_latent_frames(latent_image, noise_mask, _ghost_pre_extend, mask_fill=1.0)
_, _, latent_length, latent_height, latent_width = latent_image.shape _, _, latent_length, latent_height, latent_width = latent_image.shape
initial_latent_length = int(latent_length) initial_latent_length = int(latent_length)