* feat(ltx-director): Ghost Mask reference image (REF / character sheet) Add a no-LoRA reference-image identity anchor to LTXDirector__koolook, the way a WAN VACE reference works: attach reference image(s) and the model keeps the character's face / mouth consistent even when keyframes show a closed (or different) mouth than the audio-driven motion needs. Targets the mouth / identity drift seen in the talking-goat lip-sync workflow. Mechanism (Ghost Mask): the reference image(s) are appended as masked guide frames *after* the clean video region, so the transformer attends to them for identity but they never appear in the output; the trailing reference frames are then sliced off the result. - LTXDirector__koolook: new optional `reference_images` (IMAGE) + `reference_strength` inputs, and appended `clean_latent_frames` / `clean_pixel_frames` outputs. All additive/optional -> saved workflows unaffected. Refs are appended AFTER snap/ease so they are never grid-snapped or ease-expanded; the auto latent is grown to hold them. ltx_director.py is under ruff's forks/ exclude. - reference_ghost.py (new, Koolook-original, pure stdlib): trailing-reference geometry (insert frames, grown latent length, slice bounds). Unit-tested. - k_clean_latent_slice.py (new root node `CleanLatentSlice`): torch.narrow slice to drop the trailing reference frames (start=0, length=clean_latent_frames). torch imported lazily; clamp math unit-tested, slice path tested when real torch is present (skips under the CI stub). - Registered the node; added to sync_to_dev RUNTIME_PATHS. - Attribution: the Ghost Mask APPROACH is adapted (idea only, reimplemented) from CGlide/WhatDreamsCost-CSGlide (main_cs @ 21dd076, GPL-3.0, license verified 2026-06-24, GPL->GPL OK). THIRD_PARTY.md, forks_manifest.yaml, UPSTREAM_PIN.yaml, CHANGELOG updated. Phase 1: Ghost Mask only. The LoRA-backed Licon MSR prefix mode and the editor's character-slot UI are intentionally NOT ported. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * feat(sidebar): action-led star footer ("Give a ★ on GitHub") Reword the GitHub discovery footer from the passive "Help creators find us on GitHub" to an action-led "Help creators find us! Give a ★ on GitHub", with the star as the explicit ask. Render the sentence as one inline text flow (not separate flex children) so the footer's `gap` doesn't space out the words; the ★ glyph keeps its gold styling inline and now carries role="img"/aria-label="star" so it is announced. Verified rendering in an isolated harness. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(ltx-director): Ghost Mask broke motion in the append-and-crop pipeline The upstream guide pipeline (LTXDirectorGuide + LTXDirectorCropGuides) appends every guide_data image as an extra latent frame and crops them back off via `exact_crop_frames = latent.shape[2] - initial_latent_length`. The Ghost Mask code (modeled on CGlide's CleanLatentSlice flow) ALSO pre-grew the video latent by n_refs, so initial_latent_length became clean+n_refs: CropGuides then left n_refs frames uncropped AND the audio latent stayed at the clean length, desyncing the A/V joint transformer and breaking motion. Fix: don't pre-grow the latent. References are added to guide_data exactly like timeline keyframes and ride the same append-and-crop path — the guide node appends them and CropGuides removes them, audio untouched. Auto latent is sized to the clean length again. CleanLatentSlice/clean_latent_frames remain available for bare pipelines that don't use LTXDirectorCropGuides. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * feat(ltx): LTX Guide Reference Strength — per-stage reference boost Adds a small Koolook node that rewrites the strength of ONLY the Ghost Mask reference frames in guide_data (the trailing entries the Director now tags with `reference_count`), leaving the keyframe pins untouched. Insert it before a stage-2 refinement guide and set ~0.9 to lock identity during the low-denoise pass without disturbing the motion set in stage 1 — the deterministic form of "more reference in stage 2". Chosen over vendoring the 600-line upstream LTXDirectorGuide (which would force a node-ID rename + graph rewire): this is one small node inserted on the stage-2 guide_data wire. The sigma-ramp alternative (ramp reference up at low sigma) is deferred — the reference hook is noise_mask + a core-model attention term, so it needs a live-model spike, not a clean static patch. - Director tags guide_data["reference_count"] = n_refs. - k_ltx_guide_reference_strength.py: pure strength rewrite (absolute / multiply, clamped), unit-tested; registered; added to sync paths. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * feat(ltx): LTX Reference Bind Schedule — sigma-ramp the reference attention Confirmed against core (comfy/ldm/lightricks/model.py): the reference's guide attention strength is turned into a log-space additive self-attention bias in `_build_guide_self_attention_mask`, which is rebuilt EVERY denoise step and explicitly supports strength > 1.0 to amplify. That's a clean per-step hook — so the "inverse sigma curve" idea is buildable, not just a stage knob. New node mirrors LTXAVBindSchedule: a model_function_wrapper captures the per-step sigma and stashes a gain in transformer_options; an object-patch on diffusion_model._build_guide_self_attention_mask multiplies the strength of ONLY the trailing `num_references` guide entries (references are appended after keyframes) by a smoothstep ramp — neutral early (motion forms), rising to peak_strength late (identity locks during refinement). Keyframe pins and noise-mask freezing untouched; orthogonal to reference_strength. One pass, no re-noise, no LoRA. - ref_gain ramp + _boosted_entries unit-tested (11 tests); model wiring validated by live render. - Registered; added to sync paths. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(ltx): mark Reference Bind Schedule inert without an IC-LoRA Live diagnostic on the AV model showed the builder is reached and the sigma gain flows in, but resolved_guide_entries=None despite 3276 guide tokens. Root cause: LTXDirectorGuide only records guide_attention_entries when an IC-LoRA is active (`if is_lora_active:` gates _append_guide_attention_entry). With no LoRA there is no per-guide attention bias to scale, so the attention-ramp lever does not exist in the Ghost Mask path — it can only attenuate/amplify entries that an IC-LoRA created. Rather than silently no-op, the node now logs a clear INERT warning when the entry list is empty, and the docstring/DESCRIPTION/CHANGELOG state the IC-LoRA requirement. The node remains correct for the IC-LoRA path (its partner). No-LoRA reference strengthening stays on the noise-mask lever (reference_strength / LTX Guide Reference Strength). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * feat(ltx): Reference Bind Schedule works without an IC-LoRA (fabricates entries) The attention-bias identity channel (_build_guide_self_attention_mask) is base-model functionality, but LTXDirectorGuide only populates the per-guide entries when an IC-LoRA is active, so the node was inert in the no-LoRA Ghost Mask path. Now, when entries are missing, the node FABRICATES them for the trailing reference tokens: it splits num_guide_tokens across (num_keyframes + num_references) guide frames and assigns the sigma-ramped strength to the reference share, then hands off to the core builder. This gives the reference the same amplified-attention channel an IC-LoRA would use — inside the Director, no guide-node vendor, no graph rewire (the node is already in the user's setup). When an IC-LoRA IS active it scales the real entries as before. - New `fabricate_reference_entries(num_guide_tokens, num_references, num_keyframes, strength)` (pure, unit-tested) + `num_keyframes` input. - Wrapper now does scale-or-fabricate; clearer one-shot diagnostics. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(ltx): keep num_keyframes last so pre-existing RefBind widgets stay aligned num_keyframes was inserted mid widget-order, which shifted ramp_start/ramp_end for workflows saved before it existed. Move it to the end of the optional inputs so saved [num_references, peak_strength, ramp_start, ramp_end] values keep mapping correctly; num_keyframes just defaults to 0 on reload. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(test): drop unused pytest import (ruff F401) The guide-reference-strength test uses plain asserts; the stray `import pytest` tripped ruff F401 and reddened the Lint & Smoke CI job. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(ltx-director): default keyframe guidance 0.8 (smooth), not 1.0 The timeline editor auto-built the guide_strength string from each clip's per-segment guideStrength, filling 1.0 for any clip left unset (and the Python fallback matched). 1.0 is a hard pin, so a clip you forgot to lower snapped instead of transitioning smoothly. Unset clips now default to 0.8 in the editor (display + serialization) and the node; explicit per-clip values still override. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * feat(ltx): Keyframe Soften Schedule — smooth motion early, sharp poses late New model-patcher node LTXKeyframeSoftenSchedule: sigma-schedules the LTX keyframe denoise-mask pins so the model forms smooth motion between poses early (pins loosened at high sigma) and resolves sharp poses late (pins restored by a crossover). Uses the sampler's per-step denoise_mask_function hook — no IC-LoRA required — and adds no guide frames (so it can't fabricate extra poses). Audio-safe: only the video half of the NestedTensor A/V mask is softened; the audio mask is untouched, so lip-sync is unaffected. Two normalized 0-1 widgets map onto the sensitive useful range: max_soften (ease, ->0-0.20) and crossover (invention window, ->0-0.05). Pure soften_gain ramp unit-tested (11 tests); model wiring render-validated. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(review): chain denoise hook, optional_latent clean-length, audio test, CGlide manifest Addresses PR #264 review findings: - MEDIUM (Soften node): chain a pre-existing denoise_mask_function instead of clobbering it (mirrors the sibling LTX patchers' wrapper-chaining), so the node composes with anything else that sets that hook. - HIGH #2 (Director): clean_pixel_frames/clean_latent_frames now derive from the ACTUAL optional_latent shape when one is connected, so Ghost-Mask reference geometry and the reported counts follow the real latent instead of the timeline duration (with a safe fallback + warning). - LOW (Soften node): reconcile docstring (crossover is sigma-progress, maps to internal 0-0.05), one-time warn on the sigma-read fallback, and extract a testable soften_denoise_mask() helper. - LOW: lock the audio-safety guarantee with NestedTensor-stub tests — video mask softened, audio mask untouched, part-ordering asserted. - #6: add a formal forks_manifest.yaml entry for the CSGlide Ghost Mask idea-only adaptation (was prose-only in another entry's notes; THIRD_PARTY.md already carries the full attribution). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: AI Machine <ai.machine@x.baconcph.com> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
287 lines
14 KiB
Python
287 lines
14 KiB
Python
# SPDX-License-Identifier: GPL-3.0-or-later
|
|
#
|
|
# ComfyUI-Koolook — LTX Reference Bind Schedule node.
|
|
# Copyright (C) 2026 ComfyUI-Koolook contributors (kforgelabs).
|
|
"""LTX Reference Bind Schedule (Koolook) — ramp reference identity up at low sigma.
|
|
|
|
Companion to the LTX Director Ghost Mask reference. The reference image is added
|
|
as a guide frame; the core LTX transformer attends to it with a per-guide
|
|
**attention strength** (`guide_attention_entries[].strength`) which it turns into
|
|
a log-space additive self-attention bias, rebuilt **every denoise step** in
|
|
`comfy/ldm/lightricks/model.py::_build_guide_self_attention_mask`. That builder
|
|
explicitly supports `strength > 1.0` to *amplify* a guide's attention.
|
|
|
|
This node schedules that amplification over the denoise so identity locks late
|
|
without disturbing motion that forms early:
|
|
|
|
* **Early steps (high sigma)** — gain ~1.0: the reference attention is neutral,
|
|
big motion resolves freely.
|
|
* **Late steps (low sigma)** — gain ramps up to `peak_strength`: the refinement
|
|
conforms harder to the reference's identity (face / mouth).
|
|
|
|
It's the inverse of the motion curve, and the mirror of `LTXAVBindSchedule`
|
|
(which ramps audio->video up at low sigma for lip-sync). It touches ONLY the
|
|
attention bias of the trailing `num_references` guide entries (references are
|
|
appended after the keyframes) — the keyframe pins and the noise-mask freezing
|
|
are untouched.
|
|
|
|
WORKS WITHOUT AN IC-LoRA. The attention-bias machinery is BASE-model
|
|
functionality — `_build_guide_self_attention_mask` builds the mask from
|
|
`resolved_guide_entries` regardless of any LoRA. The catch is that
|
|
LTXDirectorGuide only *populates* those entries `if is_lora_active`, so in the
|
|
no-LoRA Ghost Mask path the list is empty. This node closes that gap: when the
|
|
entries are missing it **fabricates them itself** for the trailing reference
|
|
tokens (splitting `num_guide_tokens` across the `num_keyframes + num_references`
|
|
guide frames and assigning the ramped strength to the reference share). So the
|
|
reference gets the same amplified-attention channel an IC-LoRA would use, inside
|
|
the Director, with no LoRA, no guide-node vendor, and no graph change. When an
|
|
IC-LoRA *is* active and real entries exist, it scales those instead.
|
|
|
|
Mechanics (same as the A/V node): a `model_function_wrapper` captures the
|
|
current sigma each step and stashes the gain in `transformer_options`; an
|
|
object-patch on `_build_guide_self_attention_mask` reads it and fabricates (or
|
|
scales) the reference entries' `strength` before the mask is built.
|
|
|
|
NOTE: the `ref_gain` ramp is unit-tested; the model wiring is validated by
|
|
rendering against a live LTX 2.3 model (not in CI).
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import types
|
|
import logging
|
|
|
|
log = logging.getLogger(__name__)
|
|
|
|
_REF_GAIN_KEY = "nghtdrp_ref_gain" # per-step reference-attention multiplier
|
|
|
|
|
|
def ref_gain(sigma, sigma_max, ramp_start=0.5, ramp_end=0.9, peak=2.0):
|
|
"""Reference-attention multiplier for the current ``sigma``.
|
|
|
|
Progress runs 0 at the start of denoise (``sigma == sigma_max``) to 1 at the
|
|
end (``sigma -> 0``). Holds at 1.0 (neutral) until ``ramp_start``, then
|
|
smoothsteps up to ``peak`` by ``ramp_end`` (progress fractions in [0, 1]).
|
|
Returning 1.0 means "no change" — the mask builder then skips entirely.
|
|
"""
|
|
if sigma_max <= 0.0:
|
|
return float(peak)
|
|
progress = 1.0 - (float(sigma) / float(sigma_max))
|
|
progress = 0.0 if progress < 0.0 else (1.0 if progress > 1.0 else progress)
|
|
if ramp_end <= ramp_start:
|
|
return float(peak) if progress >= ramp_end else 1.0
|
|
if progress <= ramp_start:
|
|
return 1.0
|
|
if progress >= ramp_end:
|
|
return float(peak)
|
|
t = (progress - ramp_start) / (ramp_end - ramp_start)
|
|
s = t * t * (3.0 - 2.0 * t) # smoothstep
|
|
return 1.0 + (float(peak) - 1.0) * s
|
|
|
|
|
|
def _boosted_entries(resolved_entries, ref_count, gain):
|
|
"""Return a copy of ``resolved_entries`` with the LAST ``ref_count`` entries'
|
|
``strength`` multiplied by ``gain`` (references are appended after keyframes).
|
|
Returns the original list unchanged when there's nothing to do."""
|
|
if not resolved_entries or gain == 1.0 or ref_count <= 0:
|
|
return resolved_entries
|
|
n = len(resolved_entries)
|
|
k = min(int(ref_count), n)
|
|
if k <= 0:
|
|
return resolved_entries
|
|
out = list(resolved_entries)
|
|
for i in range(n - k, n):
|
|
e = dict(out[i])
|
|
e["strength"] = float(e.get("strength", 1.0)) * float(gain)
|
|
out[i] = e
|
|
return out
|
|
|
|
|
|
def fabricate_reference_entries(num_guide_tokens, num_references, num_keyframes, strength):
|
|
"""Build ``resolved_guide_entries`` for the trailing reference tokens when the
|
|
guide node created none (the no-IC-LoRA path).
|
|
|
|
Splits ``num_guide_tokens`` evenly across the ``num_keyframes + num_references``
|
|
guide frames and gives the trailing reference share ``strength`` while the
|
|
keyframe share stays 1.0 (neutral). The entries match the shape the core
|
|
builder consumes (``strength`` + ``surviving_count`` + optional pixel_mask /
|
|
latent_shape). When ``num_keyframes`` is 0 the whole guide region is treated
|
|
as reference. Returns ``[]`` when there's nothing to amplify.
|
|
"""
|
|
n_guide = int(num_guide_tokens)
|
|
n_ref = int(num_references)
|
|
n_kf = max(0, int(num_keyframes))
|
|
if n_guide <= 0 or n_ref <= 0:
|
|
return []
|
|
total_frames = n_ref + n_kf
|
|
ref_tokens = round(n_guide * n_ref / total_frames) if total_frames > 0 else n_guide
|
|
ref_tokens = max(1, min(int(ref_tokens), n_guide))
|
|
kf_tokens = n_guide - ref_tokens
|
|
entries = []
|
|
if kf_tokens > 0:
|
|
entries.append({"strength": 1.0, "surviving_count": kf_tokens, "pixel_mask": None, "latent_shape": None})
|
|
entries.append({"strength": float(strength), "surviving_count": ref_tokens, "pixel_mask": None, "latent_shape": None})
|
|
return entries
|
|
|
|
|
|
def _make_guide_mask_wrapper(underlying, ref_count, kf_count, peak, diag):
|
|
"""Bound-method wrapper around ``_build_guide_self_attention_mask``.
|
|
|
|
If the guide node already populated entries (IC-LoRA path) scale the trailing
|
|
``ref_count`` of them by the per-step gain. If not (no-LoRA Ghost Mask path)
|
|
fabricate entries for the trailing reference tokens at the gain strength. Then
|
|
hand off to the original builder, which turns it into the additive bias.
|
|
"""
|
|
|
|
def wrapped(_self_module, x, transformer_options, merged_args, *args, **kwargs):
|
|
to = transformer_options or {}
|
|
gain = float(to.get(_REF_GAIN_KEY, 1.0))
|
|
ma = merged_args if isinstance(merged_args, dict) else {}
|
|
entries = ma.get("resolved_guide_entries")
|
|
n_guide = int(ma.get("num_guide_tokens") or 0)
|
|
|
|
mode = "passthrough"
|
|
if entries: # IC-LoRA path — scale real entries
|
|
if gain != 1.0 and ref_count > 0:
|
|
boosted = _boosted_entries(entries, ref_count, gain)
|
|
if boosted is not entries:
|
|
merged_args = {**ma, "resolved_guide_entries": boosted}
|
|
mode = "scaled"
|
|
elif n_guide > 0 and ref_count > 0 and gain != 1.0: # no-LoRA path — fabricate
|
|
fab = fabricate_reference_entries(n_guide, ref_count, kf_count, gain)
|
|
if fab:
|
|
merged_args = {**ma, "resolved_guide_entries": fab}
|
|
mode = "fabricated"
|
|
|
|
if not diag.get("first"):
|
|
diag["first"] = True
|
|
log.info(
|
|
"[LTXReferenceBindSchedule] active: num_guide_tokens=%s entries=%s "
|
|
"ref_count=%d num_keyframes=%d peak=%.2f (amplifies the trailing "
|
|
"reference tokens at low sigma).",
|
|
n_guide, (len(entries) if entries else 0), int(ref_count),
|
|
int(kf_count), float(peak),
|
|
)
|
|
if mode in ("scaled", "fabricated") and not diag.get(mode):
|
|
diag[mode] = True
|
|
log.info("[LTXReferenceBindSchedule] %s reference attention (gain x%.2f).", mode, gain)
|
|
return underlying(x, transformer_options, merged_args, *args, **kwargs)
|
|
|
|
return wrapped
|
|
|
|
|
|
class LTXReferenceBindSchedule:
|
|
"""Ramp the LTX Ghost Mask reference's attention up as sigma falls."""
|
|
|
|
@classmethod
|
|
def INPUT_TYPES(cls):
|
|
return {
|
|
"required": {
|
|
"model": ("MODEL", {"tooltip": "LTX 2.3 model (the same one feeding the guide pipeline)."}),
|
|
"num_references": ("INT", {
|
|
"default": 1, "min": 0, "max": 64, "step": 1,
|
|
"tooltip": "How many trailing guide frames are references (= how many images you fed LTX Director's reference_images). They're appended after the keyframes, so the LAST N guide frames are the references.",
|
|
}),
|
|
"peak_strength": ("FLOAT", {
|
|
"default": 2.0, "min": 1.0, "max": 8.0, "step": 0.05,
|
|
"tooltip": "Reference attention multiplier at the end of denoise (low sigma). 1.0 = no boost; 2.0 = double the identity pull during refinement. Try 2-4.",
|
|
}),
|
|
},
|
|
"optional": {
|
|
# NOTE: ramp_start/ramp_end stay first to preserve widget order
|
|
# for workflows saved before num_keyframes existed; num_keyframes
|
|
# is appended last so old [num_references, peak_strength, ramp_start,
|
|
# ramp_end] values keep aligning.
|
|
"ramp_start": ("FLOAT", {
|
|
"default": 0.5, "min": 0.0, "max": 1.0, "step": 0.01,
|
|
"tooltip": "Denoise progress (0=start, 1=end) where the reference boost begins. Keep above where big motion finalizes (~0.5-0.6) so motion forms first.",
|
|
}),
|
|
"ramp_end": ("FLOAT", {
|
|
"default": 0.9, "min": 0.0, "max": 1.0, "step": 0.01,
|
|
"tooltip": "Denoise progress where the boost reaches peak_strength.",
|
|
}),
|
|
"num_keyframes": ("INT", {
|
|
"default": 0, "min": 0, "max": 256, "step": 1,
|
|
"tooltip": "No-LoRA path only: how many timeline image keyframes precede the references, so the node knows the keyframe/reference token split. Set this = your number of timeline image segments (e.g. 4). Leave 0 and the WHOLE guide region is treated as reference (also boosts keyframes). Ignored when an IC-LoRA populated real entries.",
|
|
}),
|
|
},
|
|
}
|
|
|
|
RETURN_TYPES = ("MODEL",)
|
|
RETURN_NAMES = ("model",)
|
|
FUNCTION = "patch"
|
|
CATEGORY = "Koolook/LTX"
|
|
DESCRIPTION = (
|
|
"Ramp the reference's guide-attention UP at low sigma so identity locks "
|
|
"during refinement without disturbing early motion. Works WITHOUT an "
|
|
"IC-LoRA: it fabricates the per-guide attention entries for the trailing "
|
|
"reference tokens itself (set num_keyframes so it knows the split), so the "
|
|
"reference gets the same amplified-attention channel an IC-LoRA would use "
|
|
"— inside the Director, no guide-node changes. Mirror of LTX A/V Bind "
|
|
"Schedule. One pass, no re-noise."
|
|
)
|
|
|
|
def patch(self, model, num_references, peak_strength, num_keyframes=0, ramp_start=0.5, ramp_end=0.9):
|
|
m = model.clone()
|
|
|
|
if int(num_references) <= 0:
|
|
log.info("[LTXReferenceBindSchedule] num_references=0 — model passed through.")
|
|
return (m,)
|
|
|
|
try:
|
|
sigma_max = float(m.get_model_object("model_sampling").sigma_max)
|
|
except Exception as e: # pragma: no cover - live model
|
|
log.warning("[LTXReferenceBindSchedule] no model_sampling.sigma_max: %s", e)
|
|
sigma_max = 1.0
|
|
|
|
key = "diffusion_model._build_guide_self_attention_mask"
|
|
try:
|
|
underlying = m.get_model_object(key)
|
|
dm = m.get_model_object("diffusion_model")
|
|
except Exception as e: # pragma: no cover - live model
|
|
log.warning(
|
|
"[LTXReferenceBindSchedule] no %s on this model (not an LTX guide "
|
|
"model?): %s — passed through unchanged.", key, e,
|
|
)
|
|
return (m,)
|
|
|
|
diag = {}
|
|
wrapper = _make_guide_mask_wrapper(
|
|
underlying, int(num_references), int(num_keyframes), float(peak_strength), diag,
|
|
)
|
|
try:
|
|
m.add_object_patch(key, types.MethodType(wrapper, dm))
|
|
except Exception as e: # pragma: no cover - live model
|
|
log.warning("[LTXReferenceBindSchedule] guide-mask patch failed: %s", e)
|
|
return (m,)
|
|
|
|
prev = m.model_options.get("model_function_wrapper")
|
|
|
|
def unet_wrapper(model_function, params):
|
|
try:
|
|
sigma = float(params["timestep"].max())
|
|
except Exception:
|
|
sigma = sigma_max
|
|
gain = ref_gain(sigma, sigma_max, ramp_start, ramp_end, peak_strength)
|
|
c = params["c"]
|
|
to = dict(c.get("transformer_options", {}))
|
|
to[_REF_GAIN_KEY] = gain
|
|
c = {**c, "transformer_options": to}
|
|
params = {**params, "c": c}
|
|
if prev is not None:
|
|
return prev(model_function, params)
|
|
return model_function(params["input"], params["timestep"], **params["c"])
|
|
|
|
m.set_model_unet_function_wrapper(unet_wrapper)
|
|
log.info(
|
|
"[LTXReferenceBindSchedule] scheduling %d reference(s): peak x%.2f, "
|
|
"ramp %.2f..%.2f of denoise.", int(num_references), float(peak_strength),
|
|
float(ramp_start), float(ramp_end),
|
|
)
|
|
return (m,)
|
|
|
|
|
|
NODE_CLASS_MAPPINGS = {"LTXReferenceBindSchedule": LTXReferenceBindSchedule}
|
|
NODE_DISPLAY_NAME_MAPPINGS = {"LTXReferenceBindSchedule": "LTX Reference Bind Schedule (Koolook)"}
|
|
|
|
__all__ = ["NODE_CLASS_MAPPINGS", "NODE_DISPLAY_NAME_MAPPINGS"]
|