From e1036b85c07c87fb430211eb6ca8da1d92d79f82 Mon Sep 17 00:00:00 2001 From: digital-garbage <133010426+digital-garbage@users.noreply.github.com> Date: Tue, 26 May 2026 01:07:24 +0300 Subject: [PATCH] Fix guide regression, blur edge artifact, and effect curve shape MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Revert anchor source back to blended cumulative output — prev_sampled gave scene N+2 a tail dominated by scene N-1 pinned frames, weakening the scene-N representation and making the i2v guide's reference pull visually dominant (apparent reset to reference image). Blended anchor frames are already hidden by the coverage fix so no yellow leaks. Fix blur edge distortion: avg_pool2d with zero-padding darkened image edges at high sigma. Now pre-pad with reflect mode and pool with padding=0, eliminating the dark halo at frame boundaries. Fix effect curve shape: previous linear V-fade left blended-latent content visible at ~89% brightness at the blend zone edges, giving a "generated transition frames" look. New shape holds brightness=0 / sigma=max flat across the entire blend zone, then ramps back to full over transition_duration//2 extra frames into clean scene content. The blend zone is now fully hidden regardless of transition_duration. Co-Authored-By: Claude Sonnet 4.6 --- samplers.py | 39 +++++++++++++++++++++++---------------- 1 file changed, 23 insertions(+), 16 deletions(-) diff --git a/samplers.py b/samplers.py index d63e19f1..5a5c5334 100644 --- a/samplers.py +++ b/samplers.py @@ -1267,17 +1267,27 @@ class FunPackLTXAVSceneChainSampler: if sigma <= 0: return frame k = max(3, int(sigma * 3) | 1) + pad = k // 2 x = frame.permute(2, 0, 1).unsqueeze(0).float() - x = torch.nn.functional.avg_pool2d(x, k, stride=1, padding=k // 2) + x = torch.nn.functional.pad(x, (pad, pad, pad, pad), mode="reflect") + x = torch.nn.functional.avg_pool2d(x, k, stride=1, padding=0) return x.squeeze(0).permute(1, 2, 0).to(dtype=frame.dtype) - def _apply_effect_on_pixels(self, frames, effect, center, half): - """Apply one visual transition effect in-place on a [N, H, W, C] float tensor.""" + def _apply_effect_on_pixels(self, frames, effect, center, half, blend_half=0): + """Apply one visual transition effect in-place on a [N, H, W, C] float tensor. + + blend_half: number of frames on each side of center that are blended-latent + content and must be fully processed (zero brightness / max blur) regardless + of transition_duration. extra frames beyond blend_half fade/ramp back to + full brightness over the remaining (half - blend_half) range. + """ n = frames.shape[0] center = max(half, min(n - half - 1, center)) + extra_half = max(1, half - blend_half) if effect == "fade_to_black": for i in range(max(0, center - half), min(n, center + half)): - brightness = min(1.0, abs(i - center) / max(1, half)) + dist = abs(i - center) + brightness = min(1.0, max(0.0, (dist - blend_half) / extra_half)) frames[i] = frames[i] * brightness elif effect == "crossfade": orig = frames.clone() @@ -1289,7 +1299,9 @@ class FunPackLTXAVSceneChainSampler: frames[post_i] = (1 - alpha) * orig[post_i] + alpha * orig[pre_i] elif effect == "blur_out_in": for i in range(max(0, center - half), min(n, center + half)): - sigma = 8.0 * (1.0 - abs(i - center) / max(1, half)) + dist = abs(i - center) + ramp = max(0.0, (dist - blend_half) / extra_half) + sigma = 8.0 * (1.0 - ramp) if sigma > 0: frames[i] = self._blur_pixel_frame(frames[i], sigma) @@ -1315,9 +1327,10 @@ class FunPackLTXAVSceneChainSampler: return decoded frames = decoded.clone().float() for entry in active: - blend_half = entry.get("blend_half_pixels", 1) - half = blend_half + max(0, transition_duration // 2) - self._apply_effect_on_pixels(frames, entry["effect"], int(entry["pixel_frame"]), half) + blend_half = entry.get("blend_half_pixels", 0) + extra = max(1, transition_duration // 2) + half = blend_half + extra + self._apply_effect_on_pixels(frames, entry["effect"], int(entry["pixel_frame"]), half, blend_half=blend_half) return frames.clamp(0.0, 1.0).to(dtype=decoded.dtype) def _apply_transitions_latent(self, output, boundary_entries, vae, time_scale, transition_duration): @@ -1432,7 +1445,6 @@ class FunPackLTXAVSceneChainSampler: video_overlap = self._overlap_frames(latent_template, frame_overlap, time_scale) output = None - prev_sampled = None report_lines = [] carried_guide_frames = 0 boundary_entries = [] @@ -1449,7 +1461,7 @@ class FunPackLTXAVSceneChainSampler: else: scene_seed = provided_seed if provided_seed is not None else int(seed) + scene_index carried = 0 - if prev_sampled is None: + if output is None: chunk = self._clone_latent(latent_template) else: # Record boundary (center of the overlap region) before blending @@ -1466,11 +1478,7 @@ class FunPackLTXAVSceneChainSampler: "blend_half_latent": blend_half_latent, "blend_half_pixels": blend_half_pixels, }) - # Use the previous scene's raw sampled output as the anchor, not the - # blended cumulative output. The blended tail contains interpolated - # latent frames (mix of two scenes) that decode with color artifacts - # and would corrupt the anchor context for this scene. - chunk = self._build_continuation_chunk(latent_template, prev_sampled, video_overlap) + chunk = self._build_continuation_chunk(latent_template, output, video_overlap) if carry_i2v_guides: chunk, scene_positive, scene_negative, carried = self._append_i2v_guides( chunk, latent_template, scene_positive, scene_negative, vae, @@ -1484,7 +1492,6 @@ class FunPackLTXAVSceneChainSampler: if output is not None: sampled = self._match_audio_amplitude(sampled, video_overlap, video_frames) output = sampled if output is None else self._blend_latents(output, sampled, video_overlap) - prev_sampled = sampled cumulative_latent_frames = self._tensor_frames(self._latent_tensors(output)[0]) report_lines.append(f"Scene {scene_index + 1}: seed={scene_seed}, text={self._scene_text(scene_cond, scene_index)}")