Add i2i hard-cut anchor generation and reference_frames output to chain sampler
When frame_overlap=0 and i2i_scene_cut=True, generates a new starting frame for each continuation scene by denoising the previous scene's last frame with the new scene prompt (i2i strength-controlled). The result is pinned as frame 0 of the next chunk (mask=0, i2v style), replacing soft continuation. reference_frames output collects all generated anchor images as an IMAGE batch. New inputs: i2i_scene_cut (BOOLEAN), i2i_strength (FLOAT 0.0–1.0). New output: reference_frames (IMAGE, slot 2 — shifts status/scene_count/etc up by 1). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 4.6
parent
a0419e866d
commit
84b88de35a
+75
-7
@@ -792,7 +792,15 @@ class FunPackLTXAVSceneChainSampler:
|
||||
}),
|
||||
"carry_i2v_guides": ("BOOLEAN", {
|
||||
"default": False,
|
||||
"tooltip": "Experimental: carry protected frames from latent_template noise_mask into each continuation chunk after the overlap. WARNING: using this with frame_overlap=0 is confirmed to produce bad results — enable only for testing.",
|
||||
"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 previous scene's last frame with the new scene's prompt. Only active when frame_overlap=0. Replaces soft continuation.",
|
||||
}),
|
||||
"i2i_strength": ("FLOAT", {
|
||||
"default": 0.75, "min": 0.0, "max": 1.0, "step": 0.05,
|
||||
"tooltip": "How much to transform the reference frame during i2i anchor generation. 0.0 = no change, 1.0 = ignore reference. 0.6–0.85 is typical for a new scene while preserving character.",
|
||||
}),
|
||||
"transition_duration": ("INT", {
|
||||
"default": 16, "min": 0, "max": 128, "step": 2,
|
||||
@@ -820,8 +828,8 @@ class FunPackLTXAVSceneChainSampler:
|
||||
},
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("LATENT", "IMAGE", "STRING", "INT", "STRING", "STRING")
|
||||
RETURN_NAMES = ("latent", "images", "status", "scene_count", "scene_report", "scene_boundaries")
|
||||
RETURN_TYPES = ("LATENT", "IMAGE", "IMAGE", "STRING", "INT", "STRING", "STRING")
|
||||
RETURN_NAMES = ("latent", "images", "reference_frames", "status", "scene_count", "scene_report", "scene_boundaries")
|
||||
FUNCTION = "sample"
|
||||
CATEGORY = "FunPack/Sampling"
|
||||
DESCRIPTION = (
|
||||
@@ -1015,6 +1023,45 @@ class FunPackLTXAVSceneChainSampler:
|
||||
return None
|
||||
|
||||
|
||||
def _generate_i2i_anchor(self, model, sampler, sigmas, seed, cfg,
|
||||
positive, negative, previous_output, strength):
|
||||
"""Denoise the last frame of the previous scene into a new starting frame."""
|
||||
prev_tensors = self._latent_tensors(previous_output)
|
||||
if not prev_tensors:
|
||||
return None
|
||||
last_frame = self._tail(prev_tensors[0], 1)
|
||||
noise_mask = torch.full(
|
||||
(last_frame.shape[0], 1, 1, 1, 1),
|
||||
float(strength),
|
||||
dtype=torch.float32,
|
||||
device=last_frame.device,
|
||||
)
|
||||
anchor_latent = {"samples": last_frame, "noise_mask": noise_mask}
|
||||
return self._sample_chunk(model, sampler, sigmas, seed, cfg, positive, negative, anchor_latent)
|
||||
|
||||
def _apply_i2v_anchor(self, chunk, anchor_latent):
|
||||
"""Pin the anchor's single frame as frame 0 of 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][:, :, :1] = anchor_frame
|
||||
if masks[0] is None:
|
||||
masks[0] = torch.ones_like(tensors[0])
|
||||
else:
|
||||
masks[0] = masks[0].clone()
|
||||
masks[0][:, :, :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)
|
||||
@@ -1305,7 +1352,8 @@ 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, transition_duration=16, decode_tile_size=0,
|
||||
carry_i2v_guides=False, i2i_scene_cut=False, i2i_strength=0.75,
|
||||
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.")
|
||||
@@ -1324,6 +1372,7 @@ 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)
|
||||
@@ -1355,7 +1404,18 @@ class FunPackLTXAVSceneChainSampler:
|
||||
})
|
||||
chunk = self._build_continuation_chunk(latent_template, output, video_overlap)
|
||||
if video_overlap == 0:
|
||||
chunk, soft_carried = self._prepend_soft_continuation(chunk, output)
|
||||
if i2i_scene_cut:
|
||||
anchor = self._generate_i2i_anchor(
|
||||
model, sampler, sigmas, scene_seed, cfg,
|
||||
scene_positive, scene_negative, output, i2i_strength,
|
||||
)
|
||||
if anchor is not None:
|
||||
chunk = self._apply_i2v_anchor(chunk, anchor)
|
||||
anchor_decoded = self._decode_last_frame(anchor, vae)
|
||||
if anchor_decoded is not None:
|
||||
reference_frames.append(anchor_decoded)
|
||||
else:
|
||||
chunk, soft_carried = self._prepend_soft_continuation(chunk, output)
|
||||
if carry_i2v_guides:
|
||||
chunk, scene_positive, scene_negative, carried = self._append_i2v_guides(
|
||||
chunk, latent_template, scene_positive, scene_negative,
|
||||
@@ -1378,7 +1438,7 @@ class FunPackLTXAVSceneChainSampler:
|
||||
|
||||
del scene_cond, scene_positive, scene_negative, scene_conditionings, chunk, sampled
|
||||
|
||||
# RETURN_TYPES slot indices: 0=latent, 1=images
|
||||
# RETURN_TYPES slot indices: 0=latent, 1=images, 2=reference_frames, 3=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)
|
||||
@@ -1409,6 +1469,8 @@ 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:
|
||||
@@ -1423,4 +1485,10 @@ class FunPackLTXAVSceneChainSampler:
|
||||
})
|
||||
except Exception as e:
|
||||
print(f"[FunPackLTXAVSceneChainSampler] Failed to write sampler context: {e}")
|
||||
return (output, images, status, scene_count, "\n".join(report_lines), _json.dumps(boundaries_out))
|
||||
|
||||
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))
|
||||
|
||||
Reference in New Issue
Block a user