Remove i2i_scene_cut feature

LTX-2 is not designed for creative i2i — distilled schedule gives no
reliable middle ground between 1-step (unchanged) and full regeneration
(no character identity). Removing the feature entirely rather than shipping
something that doesn't work.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
digital-garbage
2026-05-29 00:01:30 +03:00
co-authored by Claude Sonnet 4.6
parent 1b352e82ac
commit 416bcb6c69
+7 -85
View File
@@ -792,15 +792,7 @@ class FunPackLTXAVSceneChainSampler:
}),
"carry_i2v_guides": ("BOOLEAN", {
"default": False,
"tooltip": "Carry the i2v anchor frame from latent_template into each continuation chunk as a style guide. Works best with frame_overlap > 0 or combined with i2i_scene_cut.",
}),
"i2i_scene_cut": ("BOOLEAN", {
"default": False,
"tooltip": "Hard cut mode: before each continuation scene, generate a new starting frame by denoising the reference image (latent_template frame 0) with the new scene's prompt. Ignores frame_overlap — always starts fresh. Replaces overlap continuation.",
}),
"i2i_strength": ("FLOAT", {
"default": 0.7, "min": 0.0, "max": 1.0, "step": 0.05,
"tooltip": "Reference image influence on the generated anchor frame. 1.0 = reference fully pinned (strongest character preservation). 0.0 = reference ignored (free generation from prompt only). Start around 0.6–0.8.",
"tooltip": "Carry protected frames from latent_template noise_mask into each continuation chunk as a style guide.",
}),
"transition_duration": ("INT", {
"default": 16, "min": 0, "max": 128, "step": 2,
@@ -828,8 +820,8 @@ class FunPackLTXAVSceneChainSampler:
},
}
RETURN_TYPES = ("LATENT", "IMAGE", "IMAGE", "STRING", "INT", "STRING", "STRING")
RETURN_NAMES = ("latent", "images", "reference_frames", "status", "scene_count", "scene_report", "scene_boundaries")
RETURN_TYPES = ("LATENT", "IMAGE", "STRING", "INT", "STRING", "STRING")
RETURN_NAMES = ("latent", "images", "status", "scene_count", "scene_report", "scene_boundaries")
FUNCTION = "sample"
CATEGORY = "FunPack/Sampling"
DESCRIPTION = (
@@ -1023,54 +1015,6 @@ class FunPackLTXAVSceneChainSampler:
return None
def _generate_i2i_anchor(self, model, sampler, sigmas, seed, cfg,
positive, negative, reference_latent, strength):
"""Generate a new scene starting frame using the reference as an i2v anchor.
Pins frame 0 (the character reference) at mask=strength and generates
frame 1 freely from the new scene prompt. Returns only frame 1.
strength=0.0 tightly enforces the reference; 1.0 ignores it.
"""
ref_tensors = self._latent_tensors(reference_latent)
if not ref_tensors:
return None
ref_frame = self._time_slice(ref_tensors[0], 0, 1)
blank = torch.zeros_like(ref_frame)
two_frame = torch.cat([ref_frame, blank], dim=2)
b = ref_frame.shape[0]
noise_mask = torch.ones((b, 1, 2, 1, 1), dtype=torch.float32, device=ref_frame.device)
noise_mask[:, :, 0] = 1.0 - float(strength)
anchor_latent = {"samples": two_frame, "noise_mask": noise_mask}
sampled = self._sample_chunk(model, sampler, sigmas, seed, cfg, positive, negative, anchor_latent)
sampled_tensors = self._latent_tensors(sampled)
if not sampled_tensors:
return None
result_frame = self._time_slice(sampled_tensors[0], 1, 2)
return {"samples": result_frame}
def _apply_i2v_anchor(self, chunk, anchor_latent, offset=0):
"""Pin the anchor's single frame at position offset in the chunk (mask=0, i2v style)."""
anchor_tensors = self._latent_tensors(anchor_latent)
if not anchor_tensors:
return chunk
result = self._clone_latent(chunk)
tensors = self._latent_tensors(result)
masks = self._latent_masks(result, len(tensors))
anchor_frame = anchor_tensors[0].to(device=tensors[0].device, dtype=tensors[0].dtype)
tensors[0][:, :, offset:offset + 1] = anchor_frame
if masks[0] is None:
masks[0] = torch.ones_like(tensors[0])
else:
masks[0] = masks[0].clone()
masks[0][:, :, offset:offset + 1] = 0.0
if self._is_nested(result.get("samples")):
result["samples"] = comfy.nested_tensor.NestedTensor(tensors)
result["noise_mask"] = comfy.nested_tensor.NestedTensor(masks)
else:
result["samples"] = tensors[0]
result["noise_mask"] = masks[0]
return result
def _prepend_soft_continuation(self, chunk, previous, mask_value=0.4, n_frames=4):
chunk_tensors = self._latent_tensors(chunk)
previous_tensors = self._latent_tensors(previous)
@@ -1361,8 +1305,7 @@ class FunPackLTXAVSceneChainSampler:
def sample(self, model, vae, positive, negative, sampler, sigmas, seed, latent_template,
num_frames_per_scene, frame_overlap, cfg, max_scenes, use_same_seed=False,
carry_i2v_guides=False, i2i_scene_cut=False, i2i_strength=0.7,
transition_duration=16, decode_tile_size=0,
carry_i2v_guides=False, transition_duration=16, decode_tile_size=0,
refinement_key_input="", clip=None, unique_id=None, prompt=None):
if not isinstance(positive, list) or not positive:
raise ValueError("positive conditioning must contain at least one scene entry.")
@@ -1381,7 +1324,6 @@ class FunPackLTXAVSceneChainSampler:
carried_guide_frames = 0
boundary_entries = []
cumulative_latent_frames = 0
reference_frames = []
first_scene_seed = self._scene_seed(scene_conditionings[0])
if first_scene_seed is None:
first_scene_seed = int(seed)
@@ -1409,20 +1351,7 @@ class FunPackLTXAVSceneChainSampler:
"effect": effect,
})
chunk = self._build_continuation_chunk(latent_template, output, video_overlap)
if i2i_scene_cut:
# Place i2i anchor right after the overlap region.
# Overlap frames (0..video_overlap-1) are identical to previous output
# so the latent blend remains a no-op; the anchor starts the new content.
anchor = self._generate_i2i_anchor(
model, sampler, sigmas, scene_seed, cfg,
scene_positive, scene_negative, latent_template, i2i_strength,
)
if anchor is not None:
chunk = self._apply_i2v_anchor(chunk, anchor, offset=video_overlap)
anchor_decoded = self._decode_last_frame(anchor, vae)
if anchor_decoded is not None:
reference_frames.append(anchor_decoded)
elif video_overlap == 0:
if video_overlap == 0:
chunk, soft_carried = self._prepend_soft_continuation(chunk, output)
if carry_i2v_guides:
chunk, scene_positive, scene_negative, carried = self._append_i2v_guides(
@@ -1446,7 +1375,7 @@ class FunPackLTXAVSceneChainSampler:
del scene_cond, scene_positive, scene_negative, scene_conditionings, chunk, sampled
# RETURN_TYPES slot indices: 0=latent, 1=images, 2=reference_frames, 3=status...
# RETURN_TYPES slot indices: 0=latent, 1=images, 2=status...
# Sampling is fully complete. Latent is untouched and returned as-is.
# IMAGES: decode the whole latent in one pass, then apply transition effects.
want_image = self._output_connected(prompt, unique_id, 1)
@@ -1477,8 +1406,6 @@ class FunPackLTXAVSceneChainSampler:
)
if carry_i2v_guides and carried_guide_frames > 0:
status += f", i2v guide tokens={carried_guide_frames} latent frame(s)"
if reference_frames:
status += f", i2i anchors={len(reference_frames)}"
boundaries_out = [{"pixel_frame": e["pixel_frame"], "effect": e["effect"]} for e in boundary_entries]
if refinement_key_input:
try:
@@ -1494,9 +1421,4 @@ class FunPackLTXAVSceneChainSampler:
except Exception as e:
print(f"[FunPackLTXAVSceneChainSampler] Failed to write sampler context: {e}")
if reference_frames:
refs_out = torch.cat(reference_frames, dim=0)
else:
refs_out = torch.zeros(1, 8, 8, 3)
return (output, images, refs_out, status, scene_count, "\n".join(report_lines), _json.dumps(boundaries_out))
return (output, images, status, scene_count, "\n".join(report_lines), _json.dumps(boundaries_out))