Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 1 addition & 12 deletions models/wan/any2video.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,11 @@
from .modules.posemb_layers import get_rotary_pos_embed, get_nd_rotary_pos_embed
from shared.utils.vace_preprocessor import VaceVideoProcessor
from shared.utils.basic_flowmatch import FlowMatchScheduler
from shared.utils.lcm_scheduler import LCMScheduler
from shared.utils.utils import get_outpainting_frame_location, resize_lanczos, calculate_new_dimensions, convert_image_to_tensor, fit_image_into_canvas
from .multitalk.multitalk_utils import MomentumBuffer, adaptive_projected_guidance, match_and_blend_colors, match_and_blend_colors_with_mask
from shared.utils.audio_video import save_video
from mmgp import safetensors2
from shared.utils.audio_video import save_video

def optimized_scale(positive_flat, negative_flat):

Expand Down Expand Up @@ -413,17 +413,6 @@ def generate(self,
sample_scheduler,
device=self.device,
sigmas=sampling_sigmas)
elif sample_solver == 'lcm':
# LCM + LTX scheduler: Latent Consistency Model with RectifiedFlow
# Optimized for Lightning LoRAs with ultra-fast 2-8 step inference
effective_steps = min(sampling_steps, 8) # LCM works best with few steps
sample_scheduler = LCMScheduler(
num_train_timesteps=self.num_train_timesteps,
num_inference_steps=effective_steps,
shift=shift
)
sample_scheduler.set_timesteps(effective_steps, device=self.device, shift=shift)
timesteps = sample_scheduler.timesteps
else:
raise NotImplementedError(f"Unsupported Scheduler {sample_solver}")
original_timesteps = timesteps
Expand Down
3 changes: 1 addition & 2 deletions models/wan/wan_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -122,8 +122,7 @@ def query_model_def(base_model_type, model_def):
("unipc", "unipc"),
("euler", "euler"),
("dpm++", "dpm++"),
("flowmatch causvid", "causvid"),
("lcm + ltx", "lcm"), ]
("flowmatch causvid", "causvid"), ]
})


Expand Down
99 changes: 0 additions & 99 deletions shared/utils/lcm_scheduler.py

This file was deleted.

77 changes: 77 additions & 0 deletions shared/utils/sliding_window_cleanup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
"""
Sliding Window Video Cleanup Utility

This module provides functionality to automatically delete shorter intermediate videos
during sliding window generation to save disk space.
"""

import os


def cleanup_previous_video(previous_video_path, file_list, file_settings_list, lock,
sliding_window=True, sliding_window_keep_only_longest=True):
"""
Delete a previous video file and remove it from UI file lists.

Args:
previous_video_path: Path to the video file to delete
file_list: List of file paths in UI preview
file_settings_list: List of file settings in UI preview
lock: Threading lock for safe list manipulation
sliding_window: Whether sliding window is enabled (default: True)
sliding_window_keep_only_longest: Whether cleanup is enabled (default: True)

Returns:
bool: True if cleanup was successful, False otherwise
"""
# Check if cleanup should be performed
if not sliding_window or not sliding_window_keep_only_longest or previous_video_path is None:
return False

try:
if os.path.isfile(previous_video_path):
os.remove(previous_video_path)

# Also remove from UI file list to prevent dead links in preview
with lock:
if previous_video_path in file_list:
index = file_list.index(previous_video_path)
file_list.pop(index)
if index < len(file_settings_list):
file_settings_list.pop(index)

return True
except Exception as e:
# Silently handle cleanup errors to not interrupt generation
pass

return False


def should_cleanup_video(sliding_window, sliding_window_keep_only_longest, is_image=False):
"""
Determine if video cleanup should be performed.

Args:
sliding_window: Whether sliding window is enabled
sliding_window_keep_only_longest: Whether cleanup is enabled
is_image: Whether the output is an image (cleanup not needed)

Returns:
bool: True if cleanup should be performed
"""
return sliding_window and sliding_window_keep_only_longest and not is_image


def get_cleanup_status_text(server_config):
"""
Get the cleanup status text for info messages.

Args:
server_config: Server configuration dictionary

Returns:
str: "Enabled" or "Disabled"
"""
return "Enabled" if server_config.get("sliding_window_keep_only_longest", False) else "Disabled"

46 changes: 40 additions & 6 deletions wgp.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
import asyncio
import inspect
from shared.utils import prompt_parser
from shared.utils.sliding_window_cleanup import cleanup_previous_video, should_cleanup_video, get_cleanup_status_text
import base64
import io
from PIL import Image
Expand Down Expand Up @@ -563,7 +564,8 @@ def ret():
full_video_length = video_length if video_source is None else video_length + sliding_window_overlap -1
extra = "" if full_video_length == video_length else f" including {sliding_window_overlap} added for Video Continuation"
no_windows = compute_sliding_window_no(full_video_length, sliding_window_size, sliding_window_discard_last_frames, sliding_window_overlap)
gr.Info(f"The Number of Frames to generate ({video_length}{extra}) is greater than the Sliding Window Size ({sliding_window_size}), {no_windows} Windows will be generated")
cleanup_status = get_cleanup_status_text(server_config)
gr.Info(f"The Number of Frames to generate ({video_length}{extra}) is greater than the Sliding Window Size ({sliding_window_size}), {no_windows} Windows will be generated. Video Cleanup: {cleanup_status}")
if "recam" in model_filename:
if video_guide == None:
gr.Info("You must provide a Control Video")
Expand Down Expand Up @@ -1817,7 +1819,8 @@ def get_lora_dir(model_type):
"vae_config": 0,
"profile" : profile_type.LowRAM_LowVRAM,
"preload_model_policy": [],
"UI_theme": "default"
"UI_theme": "default",
"sliding_window_keep_only_longest": False
}

with open(server_config_filename, "w", encoding="utf-8") as writer:
Expand Down Expand Up @@ -2991,6 +2994,7 @@ def apply_changes( state,
image_output_codec_choice = None,
audio_output_codec_choice = None,
last_resolution_choice = None,
sliding_window_keep_only_longest_choice = False,
):
if args.lock_config:
return "<DIV ALIGN=CENTER>Config Locked</DIV>",*[gr.update()]*4
Expand Down Expand Up @@ -3028,6 +3032,7 @@ def apply_changes( state,
"video_output_codec" : video_output_codec_choice,
"image_output_codec" : image_output_codec_choice,
"audio_output_codec" : audio_output_codec_choice,
"sliding_window_keep_only_longest" : sliding_window_keep_only_longest_choice,
"last_model_type" : state["model_type"],
"last_model_per_family": state["last_model_per_family"],
"last_advanced_choice": state["advanced"],
Expand Down Expand Up @@ -4582,6 +4587,7 @@ def remove_temp_filenames(temp_filenames_list):
processes_names = { "pose": "Open Pose", "depth": "Depth Mask", "scribble" : "Shapes", "flow" : "Flow Map", "gray" : "Gray Levels", "inpaint" : "Inpaint Mask", "identity": "Identity Mask", "raw" : "Raw Format", "canny" : "Canny Edges"}

global wan_model, offloadobj, reload_needed
sliding_window_keep_only_longest = server_config.get("sliding_window_keep_only_longest", False)
gen = get_gen_info(state)
torch.set_grad_enabled(False)
if mode.startswith("edit_"):
Expand Down Expand Up @@ -4900,6 +4906,7 @@ def remove_temp_filenames(temp_filenames_list):
context_scale = None
window_no = 0
extra_windows = 0
previous_video_path = None # Track previous video for cleanup when sliding_window_keep_only_longest is enabled
guide_start_frame = 0 # pos of of first control video frame of current window (reuse_frames later than the first processed frame)
keep_frames_parsed = [] # aligned to the first control frame of current window (therefore ignore previous reuse_frames)
pre_video_guide = None # reuse_frames of previous window
Expand Down Expand Up @@ -5413,6 +5420,11 @@ def set_header_text(txt):
video_path= new_image_path
elif len(control_audio_tracks) > 0 or len(source_audio_tracks) > 0 or output_new_audio_filepath is not None or any_mmaudio or output_new_audio_data is not None or audio_source is not None:
video_path = os.path.join(save_path, file_name)

# Delete previous video if sliding_window_keep_only_longest is enabled
cleanup_previous_video(previous_video_path, file_list, file_settings_list, lock,
sliding_window, sliding_window_keep_only_longest)

save_path_tmp = video_path[:-4] + "_tmp.mp4"
save_video( tensor=sample[None], save_file=save_path_tmp, fps=output_fps, nrow=1, normalize=True, value_range=(-1, 1), codec_type = server_config.get("video_output_codec", None))
output_new_audio_temp_filepath = None
Expand Down Expand Up @@ -5441,6 +5453,10 @@ def set_header_text(txt):
if output_new_audio_temp_filepath is not None: os.remove(output_new_audio_temp_filepath)

else:
# Delete previous video if sliding_window_keep_only_longest is enabled
cleanup_previous_video(previous_video_path, file_list, file_settings_list, lock,
sliding_window, sliding_window_keep_only_longest)

save_video( tensor=sample[None], save_file=video_path, fps=output_fps, nrow=1, normalize=True, value_range=(-1, 1), codec_type= server_config.get("video_output_codec", None))

end_time = time.time()
Expand Down Expand Up @@ -5491,6 +5507,14 @@ def set_header_text(txt):
with lock:
file_list.append(path)
file_settings_list.append(configs if no > 0 else configs.copy())

# Update previous video path for cleanup in next iteration (only for videos, not images)
if should_cleanup_video(sliding_window, sliding_window_keep_only_longest, is_image):
if isinstance(video_path, list):
# Handle case where video_path might be a list (for images)
previous_video_path = video_path[0] if len(video_path) > 0 else None
else:
previous_video_path = video_path

# Play notification sound for single video
try:
Expand Down Expand Up @@ -6629,12 +6653,12 @@ def load_settings_from_file(state, file_path):
gen = get_gen_info(state)

if file_path==None:
return gr.update(), gr.update(), None
return gr.update(), gr.update(), None, gr.update()

configs, any_video_or_image_file = get_settings_from_file(state, file_path, True, True, True)
if configs == None:
gr.Info("File not supported")
return gr.update(), gr.update(), None
return gr.update(), gr.update(), None, gr.update()

current_model_type = state["model_type"]
model_type = configs["model_type"]
Expand All @@ -6648,10 +6672,10 @@ def load_settings_from_file(state, file_path):

if model_type == current_model_type:
set_model_settings(state, current_model_type, configs)
return gr.update(), gr.update(), str(time.time()), None
return gr.update(), gr.update(), str(time.time()), gr.update()
else:
set_model_settings(state, model_type, configs)
return *generate_dropdown_model_list(model_type), gr.update(), None
return *generate_dropdown_model_list(model_type), gr.update(), gr.update()

def save_inputs(
target,
Expand Down Expand Up @@ -8876,6 +8900,15 @@ def check(mode):
label="User Interface Theme. You will need to restart the App the see new Theme."
)

sliding_window_keep_only_longest_choice = gr.Dropdown(
choices=[
("Disabled", False),
("Enabled", True),
],
value=server_config.get("sliding_window_keep_only_longest", False),
label="Sliding Window Video Cleanup Policy (automatically deletes shorter intermediate videos during generation)"
)


with gr.Tab("Performance"):

Expand Down Expand Up @@ -9112,6 +9145,7 @@ def check(mode):
image_output_codec_choice,
audio_output_codec_choice,
resolution,
sliding_window_keep_only_longest_choice,
],
outputs= [msg , header, model_family, model_choice, refresh_form_trigger]
)
Expand Down