Fix transition flat zone and memory leak scene_cond ref

Transition "bad" appearance: blend_half_pixels=64 caused 2+ seconds of
forced full-black at every seam — the flat zone existed to hide latent
blend artifacts, but the blend is a no-op (overlap frames are identical
on both sides, mask=0). Removed blend_half entirely. Transitions are now
a simple V-ramp: full effect at the seam center, linear ramp back to
normal over transition_duration//2 pixel frames on each side.

Memory leak: scene_cond (Python loop variable) stays alive after
del scene_conditionings — still holds the conditioning tuple whose
embedding tensor keeps LTXAVTEModel referenced during VAE decode.
Added scene_cond to the del statement.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
digital-garbage
2026-05-26 03:44:09 +03:00
co-authored by Claude Sonnet 4.6
parent 5580b15031
commit d9bf41b6dd
+10 -21
View File
@@ -1166,21 +1166,17 @@ class FunPackLTXAVSceneChainSampler:
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, blend_half=0):
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.
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.
Simple V-ramp centered at the seam: full effect at center, linear ramp
back to no-effect at ±half frames. transition_duration controls half.
"""
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)):
dist = abs(i - center)
brightness = min(1.0, max(0.0, (dist - blend_half) / extra_half))
brightness = abs(i - center) / max(1, half)
frames[i] = frames[i] * brightness
elif effect == "crossfade":
orig = frames.clone()
@@ -1192,8 +1188,7 @@ 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)):
dist = abs(i - center)
ramp = max(0.0, (dist - blend_half) / extra_half)
ramp = abs(i - center) / max(1, half)
sigma = 8.0 * (1.0 - ramp)
if sigma > 0:
frames[i] = self._blur_pixel_frame(frames[i], sigma)
@@ -1243,11 +1238,9 @@ class FunPackLTXAVSceneChainSampler:
if not active:
return decoded
frames = decoded.clone().float()
half = max(1, transition_duration // 2)
for entry in active:
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)
self._apply_effect_on_pixels(frames, entry["effect"], int(entry["pixel_frame"]), half)
return frames.clamp(0.0, 1.0).to(dtype=decoded.dtype)
def _scene_text(self, scene_conditioning, index):
@@ -1323,18 +1316,14 @@ class FunPackLTXAVSceneChainSampler:
effect = self._scene_transition_effect(scene_cond)
if effect and transition_duration > 0:
# Seam is at cumulative_latent_frames: overlap frames are pinned
# (mask=0) so the latent blend is a no-op — the actual content
# boundary is exactly here, not offset by overlap//2.
# (mask=0) so the latent blend is a no-op — actual content
# boundary is exactly here.
boundary_latent = cumulative_latent_frames
boundary_pixel = int((boundary_latent - 1) * time_scale + 1) if time_scale > 1 else boundary_latent
blend_half_latent = max(1, video_overlap // 2)
blend_half_pixels = blend_half_latent * time_scale
boundary_entries.append({
"boundary_latent": boundary_latent,
"pixel_frame": max(0, boundary_pixel),
"effect": effect,
"blend_half_latent": blend_half_latent,
"blend_half_pixels": blend_half_pixels,
})
chunk = self._build_continuation_chunk(latent_template, output, video_overlap)
if carry_i2v_guides:
@@ -1358,7 +1347,7 @@ class FunPackLTXAVSceneChainSampler:
# model and text encoder before VAE decode runs. scene_conditionings is a
# slice of positive; scene_positive/negative are loop-scoped conditioning
# copies — all keep the LTXAVTEModel alive through their embedding tensors.
del model, positive, negative, scene_conditionings, scene_positive, scene_negative, chunk, sampled
del model, positive, negative, scene_conditionings, scene_cond, scene_positive, scene_negative, chunk, sampled
# Scene boundaries for per-scene decode: each entry's boundary_latent is
# where the next scene's new content starts in the concatenated latent.