fix: migrate SeedVR and SwiftVR to LightX2V 0.6 API
This commit is contained in:
@@ -0,0 +1,53 @@
|
||||
"""Small adapters for LightX2V 0.6's startup/request runner API."""
|
||||
|
||||
from contextlib import contextmanager
|
||||
|
||||
|
||||
def build_runner(startup_data):
|
||||
"""Build a resident LightX2V runner from deployment-time settings."""
|
||||
from ..lightx2v.lightx2v.models.runners.runner_factory import build_runner as _build_runner
|
||||
from ..lightx2v.lightx2v.utils.set_config import build_startup_config
|
||||
|
||||
startup_config = build_startup_config(dict(startup_data))
|
||||
return _build_runner(startup_config)
|
||||
|
||||
|
||||
@contextmanager
|
||||
def temporary_runner_config(runner, values):
|
||||
"""Apply per-call implementation settings while a request is running.
|
||||
|
||||
LightX2V 0.6 keeps deployment settings in a locked ``LockableDict`` and
|
||||
exposes request data through ``prepare_request``. A few legacy ComfyUI
|
||||
controls (SeedVR segmenting/color correction and SwiftVR video encoding)
|
||||
are still implementation settings, so they need a narrow, serialized
|
||||
override instead of being passed as unsupported request fields.
|
||||
"""
|
||||
values = {key: value for key, value in values.items() if value is not None}
|
||||
if not values:
|
||||
yield
|
||||
return
|
||||
|
||||
config = runner.config
|
||||
missing = object()
|
||||
previous = {key: config.get(key, missing) for key in values}
|
||||
unlock = getattr(config, "temporarily_unlocked", None)
|
||||
if unlock is None:
|
||||
raise RuntimeError("LightX2V runner config does not support temporary request settings")
|
||||
|
||||
with unlock():
|
||||
config.update(values)
|
||||
try:
|
||||
yield
|
||||
finally:
|
||||
for key, value in previous.items():
|
||||
if value is missing:
|
||||
config.pop(key, None)
|
||||
else:
|
||||
config[key] = value
|
||||
|
||||
|
||||
def run_request(runner, request, *, runtime_config=None):
|
||||
"""Validate one modern request and execute it through the runner API."""
|
||||
with temporary_runner_config(runner, runtime_config or {}):
|
||||
input_info = runner.prepare_request(dict(request))
|
||||
return runner.run_request(input_info)
|
||||
+45
-85
@@ -13,7 +13,6 @@ IMAGE tensor (no temp file, no re-encode); the runner's segmenting logic still
|
||||
runs and slices our in-memory tensor.
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import gc
|
||||
import logging
|
||||
import math
|
||||
@@ -30,6 +29,7 @@ import folder_paths
|
||||
import torch
|
||||
from comfy.utils import ProgressBar
|
||||
|
||||
from ._lightx2v_runner import build_runner, run_request
|
||||
from .file_input import probe_video_file, resolve_input_video_path
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -209,7 +209,7 @@ def _install_tensor_input_shim(runner, frames_u8, fps):
|
||||
|
||||
def _probe_video(self, video_path): # noqa: ARG001
|
||||
total = self._tensor_input.shape[0]
|
||||
self._set_output_fps(self._tensor_input_fps)
|
||||
self.set_output_fps(self._tensor_input_fps)
|
||||
return total, self._tensor_input_fps, None
|
||||
|
||||
def _read_video_segment(self, video_path, start_idx, end_idx): # noqa: ARG001
|
||||
@@ -330,9 +330,6 @@ class LightX2VSeedVR2Loader:
|
||||
vae_causal_slice_size,
|
||||
vae_memory_limit_gb,
|
||||
):
|
||||
from ..lightx2v.lightx2v.infer import init_runner
|
||||
from ..lightx2v.lightx2v.utils.set_config import set_config
|
||||
|
||||
model_dir = _seedvr2_model_dir()
|
||||
if ckpt_name == "None":
|
||||
raise FileNotFoundError(f"No .safetensors checkpoints found in {model_dir}")
|
||||
@@ -376,8 +373,7 @@ class LightX2VSeedVR2Loader:
|
||||
else:
|
||||
config["dit_original_ckpt"] = str(ckpt_path)
|
||||
|
||||
formatted = set_config(argparse.Namespace(**config))
|
||||
runner = init_runner(formatted)
|
||||
runner = build_runner(config)
|
||||
logger.info(
|
||||
"[SeedVR2Loader] loaded %s (%s); cpu_offload=%s, tile_vae=%s, tile=%s, overlap=%s, slice=%s, mem_limit=%sGiB",
|
||||
ckpt_name,
|
||||
@@ -467,10 +463,11 @@ class LightX2VSeedVR2Sampler:
|
||||
filename_prefix,
|
||||
color_fix,
|
||||
):
|
||||
from ..lightx2v.lightx2v.utils.input_info import init_empty_input_info, update_input_info_from_dict
|
||||
|
||||
runner = model["runner"]
|
||||
|
||||
if int(infer_steps) != 1:
|
||||
raise ValueError("SeedVR2 requires infer_steps=1")
|
||||
|
||||
if images.dim() != 4 or images.shape[-1] not in (3, 4):
|
||||
raise ValueError(f"Expected IMAGE [T, H, W, C], got shape {tuple(images.shape)}")
|
||||
# ComfyUI IMAGE: [T, H, W, C] float[0,1] → [T, C, H, W] uint8 (read_video's contract)
|
||||
@@ -496,49 +493,31 @@ class LightX2VSeedVR2Sampler:
|
||||
full_path, output_file, output_subfolder = _prepare_output_video(filename_prefix, target_width, target_height)
|
||||
save_path = str(full_path)
|
||||
|
||||
# runner.config is a LockableDict (locked after init); set_config uses temporarily_unlocked.
|
||||
runner.set_config(
|
||||
{
|
||||
"sr_ratio": float(sr_ratio),
|
||||
"target_height": int(target_height),
|
||||
"target_width": int(target_width),
|
||||
"target_video_length": int(segment_length), # vestigial for SR; keep aligned with segment_length
|
||||
"sr_segment_length": int(segment_length),
|
||||
"sr_overlap": int(segment_overlap),
|
||||
"infer_steps": int(infer_steps),
|
||||
"seed": int(seed),
|
||||
"fps": float(source_fps),
|
||||
"video_path": "<tensor>", # truthy sentinel so segmenting logic runs; shim bypasses file I/O
|
||||
"image_path": "",
|
||||
"prompt": "",
|
||||
"negative_prompt": "",
|
||||
"save_result_path": save_path,
|
||||
"return_result_tensor": not bool(save_to_output_file),
|
||||
"color_fix": str(color_fix),
|
||||
}
|
||||
)
|
||||
|
||||
input_info = init_empty_input_info("sr")
|
||||
update_input_info_from_dict(
|
||||
input_info,
|
||||
{
|
||||
"video_path": "<tensor>",
|
||||
"image_path": "",
|
||||
"prompt": "",
|
||||
"negative_prompt": "",
|
||||
"seed": int(seed),
|
||||
"sr_ratio": float(sr_ratio),
|
||||
"save_result_path": save_path,
|
||||
"return_result_tensor": not bool(save_to_output_file),
|
||||
},
|
||||
)
|
||||
request = {
|
||||
"task": "sr",
|
||||
"video_path": "<tensor>",
|
||||
"image_path": "",
|
||||
"size": [int(target_height), int(target_width)],
|
||||
"seed": int(seed),
|
||||
"sr_ratio": float(sr_ratio),
|
||||
"save_result_path": save_path,
|
||||
"return_result_tensor": not bool(save_to_output_file),
|
||||
}
|
||||
runtime_config = {
|
||||
"sr_segment_length": int(segment_length),
|
||||
"sr_overlap": int(segment_overlap),
|
||||
"fps": float(source_fps),
|
||||
"color_fix": str(color_fix),
|
||||
"stream_save_video": bool(save_to_output_file),
|
||||
}
|
||||
|
||||
progress = ProgressBar(100)
|
||||
if hasattr(runner, "set_progress_callback"):
|
||||
runner.set_progress_callback(lambda cur, _tot: progress.update_absolute(cur))
|
||||
|
||||
try:
|
||||
result = runner.run_pipeline(input_info)
|
||||
with _SEEDVR_RUN_LOCK:
|
||||
result = run_request(runner, request, runtime_config=runtime_config)
|
||||
finally:
|
||||
_clear_tensor_input_shim(runner)
|
||||
torch.cuda.empty_cache()
|
||||
@@ -628,8 +607,6 @@ class LightX2VSeedVR2FileSampler:
|
||||
filename_prefix,
|
||||
color_fix,
|
||||
):
|
||||
from ..lightx2v.lightx2v.utils.input_info import init_empty_input_info, update_input_info_from_dict
|
||||
|
||||
input_path = resolve_input_video_path(video_path)
|
||||
source_width, source_height, source_fps = probe_video_file(input_path)
|
||||
effective_fps = source_fps if source_fps > 0 else 16.0
|
||||
@@ -647,20 +624,25 @@ class LightX2VSeedVR2FileSampler:
|
||||
|
||||
full_path, output_file, output_subfolder = _prepare_output_video(filename_prefix, target_width, target_height)
|
||||
save_path = str(full_path)
|
||||
input_info = init_empty_input_info("sr")
|
||||
update_input_info_from_dict(
|
||||
input_info,
|
||||
{
|
||||
"video_path": str(input_path),
|
||||
"image_path": "",
|
||||
"prompt": "",
|
||||
"negative_prompt": "",
|
||||
"seed": int(seed),
|
||||
"sr_ratio": float(sr_ratio),
|
||||
"save_result_path": save_path,
|
||||
"return_result_tensor": False,
|
||||
},
|
||||
)
|
||||
if int(infer_steps) != 1:
|
||||
raise ValueError("SeedVR2 requires infer_steps=1")
|
||||
request = {
|
||||
"task": "sr",
|
||||
"video_path": str(input_path),
|
||||
"image_path": "",
|
||||
"size": [int(target_height), int(target_width)],
|
||||
"seed": int(seed),
|
||||
"sr_ratio": float(sr_ratio),
|
||||
"save_result_path": save_path,
|
||||
"return_result_tensor": False,
|
||||
}
|
||||
runtime_config = {
|
||||
"sr_segment_length": int(segment_length),
|
||||
"sr_overlap": int(segment_overlap),
|
||||
"stream_save_video": True,
|
||||
"fps": float(effective_fps),
|
||||
"color_fix": str(color_fix),
|
||||
}
|
||||
|
||||
runner = model["runner"]
|
||||
progress = ProgressBar(100)
|
||||
@@ -677,31 +659,9 @@ class LightX2VSeedVR2FileSampler:
|
||||
)
|
||||
try:
|
||||
with _SEEDVR_RUN_LOCK:
|
||||
_clear_tensor_input_shim(runner)
|
||||
runner.set_config(
|
||||
{
|
||||
"sr_ratio": float(sr_ratio),
|
||||
"target_height": int(target_height),
|
||||
"target_width": int(target_width),
|
||||
"target_video_length": int(segment_length),
|
||||
"sr_segment_length": int(segment_length),
|
||||
"sr_overlap": int(segment_overlap),
|
||||
"stream_save_video": True,
|
||||
"infer_steps": int(infer_steps),
|
||||
"seed": int(seed),
|
||||
"fps": float(effective_fps),
|
||||
"video_path": str(input_path),
|
||||
"image_path": "",
|
||||
"prompt": "",
|
||||
"negative_prompt": "",
|
||||
"save_result_path": save_path,
|
||||
"return_result_tensor": False,
|
||||
"color_fix": str(color_fix),
|
||||
}
|
||||
)
|
||||
if hasattr(runner, "set_progress_callback"):
|
||||
runner.set_progress_callback(lambda current, _total: progress.update_absolute(current))
|
||||
runner.run_pipeline(input_info)
|
||||
run_request(runner, request, runtime_config=runtime_config)
|
||||
finally:
|
||||
torch.cuda.empty_cache()
|
||||
gc.collect()
|
||||
|
||||
+35
-64
@@ -1,6 +1,5 @@
|
||||
"""SwiftVR restoration nodes for ComfyUI."""
|
||||
|
||||
import argparse
|
||||
import gc
|
||||
import logging
|
||||
import tempfile
|
||||
@@ -12,6 +11,7 @@ import folder_paths
|
||||
import torch
|
||||
from comfy.utils import ProgressBar
|
||||
|
||||
from ._lightx2v_runner import build_runner, run_request
|
||||
from .file_input import probe_video_file, resolve_input_video_path
|
||||
from .seedvr import _prepare_output_video
|
||||
|
||||
@@ -189,9 +189,6 @@ class LightX2VSwiftVRLoader:
|
||||
CATEGORY = "LightX2V/SwiftVR"
|
||||
|
||||
def load(self, model_name, attention_backend, rope_type, clip_length, dit_overlap, reae_frame_batch_size, use_compile):
|
||||
from ..lightx2v.lightx2v.infer import init_runner
|
||||
from ..lightx2v.lightx2v.utils.set_config import set_config
|
||||
|
||||
model_path = _swiftvr_model_root() / model_name
|
||||
if model_name == "None" or not _is_swiftvr_model(model_path):
|
||||
missing = [str(model_path / relative_path) for relative_path in _REQUIRED_MODEL_FILES if not (model_path / relative_path).is_file()]
|
||||
@@ -218,7 +215,7 @@ class LightX2VSwiftVRLoader:
|
||||
"parallel": False,
|
||||
"use_compile": bool(use_compile),
|
||||
}
|
||||
runner = init_runner(set_config(argparse.Namespace(**config)))
|
||||
runner = build_runner(config)
|
||||
logger.info(
|
||||
"[SwiftVRLoader] loaded %s; attention=%s, rope=%s, clip=%s, overlap=%s, reae_batch=%s, compile=%s",
|
||||
model_name,
|
||||
@@ -288,8 +285,6 @@ class LightX2VSwiftVRSampler:
|
||||
):
|
||||
from lightx2v.models.runners.swiftvr import swiftvr_runner as swiftvr_module
|
||||
|
||||
from ..lightx2v.lightx2v.utils.input_info import init_empty_input_info, update_input_info_from_dict
|
||||
|
||||
if images.dim() != 4 or images.shape[-1] not in (3, 4):
|
||||
raise ValueError(f"Expected IMAGE [T, H, W, C], got shape {tuple(images.shape)}")
|
||||
if images.shape[0] < 1:
|
||||
@@ -308,7 +303,7 @@ class LightX2VSwiftVRSampler:
|
||||
target_short_edge,
|
||||
require_even=not is_image,
|
||||
)
|
||||
target_shape = [output_height, output_width]
|
||||
target_size = [output_height, output_width]
|
||||
logger.info(
|
||||
"[SwiftVRSampler] aligned input=%sx%s, target_short_edge=%s, output=%sx%s, scale=%.4f",
|
||||
source_width,
|
||||
@@ -340,18 +335,21 @@ class LightX2VSwiftVRSampler:
|
||||
video_path = "" if is_image else "<tensor>"
|
||||
return_result_tensor = is_image
|
||||
|
||||
input_info = init_empty_input_info("sr")
|
||||
update_input_info_from_dict(
|
||||
input_info,
|
||||
{
|
||||
"video_path": video_path,
|
||||
"image_path": image_path,
|
||||
"sr_ratio": float(sr_ratio),
|
||||
"target_shape": target_shape,
|
||||
"save_result_path": save_path,
|
||||
"return_result_tensor": return_result_tensor,
|
||||
},
|
||||
)
|
||||
request = {
|
||||
"task": "sr",
|
||||
"video_path": video_path,
|
||||
"image_path": image_path,
|
||||
"sr_ratio": float(sr_ratio),
|
||||
"size": target_size,
|
||||
"save_result_path": save_path,
|
||||
"return_result_tensor": return_result_tensor,
|
||||
}
|
||||
runtime_config = {
|
||||
"fps": float(source_fps),
|
||||
"video_codec": str(video_codec),
|
||||
"quality": int(quality),
|
||||
"ffmpeg_preset": str(ffmpeg_preset),
|
||||
}
|
||||
|
||||
progress = ProgressBar(100)
|
||||
restored_images = None
|
||||
@@ -364,19 +362,6 @@ class LightX2VSwiftVRSampler:
|
||||
had_instance_image_reader = "read_image_frame" in runner.__dict__
|
||||
original_instance_image_reader = runner.__dict__.get("read_image_frame")
|
||||
try:
|
||||
runner.set_config(
|
||||
{
|
||||
"fps": float(source_fps),
|
||||
"video_codec": str(video_codec),
|
||||
"quality": int(quality),
|
||||
"ffmpeg_preset": str(ffmpeg_preset),
|
||||
"video_path": video_path,
|
||||
"image_path": image_path,
|
||||
"sr_ratio": float(sr_ratio),
|
||||
"target_shape": target_shape,
|
||||
"return_result_tensor": return_result_tensor,
|
||||
}
|
||||
)
|
||||
if hasattr(runner, "set_progress_callback"):
|
||||
runner.set_progress_callback(lambda current, _total: progress.update_absolute(current))
|
||||
if is_image:
|
||||
@@ -390,7 +375,7 @@ class LightX2VSwiftVRSampler:
|
||||
swiftvr_module.mux_audio_from_video = lambda *_args, **_kwargs: None
|
||||
if not is_image and memory_writer is not None:
|
||||
runner.open_video_writer = types.MethodType(lambda _runner, _path, _fps: memory_writer, runner)
|
||||
result = runner.run_pipeline(input_info)
|
||||
result = run_request(runner, request, runtime_config=runtime_config)
|
||||
if is_image:
|
||||
restored_images = result.get("images") if isinstance(result, dict) else result
|
||||
finally:
|
||||
@@ -474,8 +459,6 @@ class LightX2VSwiftVRFileSampler:
|
||||
CATEGORY = "LightX2V/SwiftVR"
|
||||
|
||||
def sample(self, model, video_path, target_short_edge, filename_prefix, video_codec, quality, ffmpeg_preset):
|
||||
from ..lightx2v.lightx2v.utils.input_info import init_empty_input_info, update_input_info_from_dict
|
||||
|
||||
input_path = resolve_input_video_path(video_path)
|
||||
raw_width, raw_height, source_fps = probe_video_file(input_path)
|
||||
source_height, source_width = raw_height // 8 * 8, raw_width // 8 * 8
|
||||
@@ -490,20 +473,21 @@ class LightX2VSwiftVRFileSampler:
|
||||
)
|
||||
full_path, output_file, output_subfolder = _prepare_output_video(filename_prefix, output_width, output_height)
|
||||
save_path = str(full_path)
|
||||
target_shape = [output_height, output_width]
|
||||
|
||||
input_info = init_empty_input_info("sr")
|
||||
update_input_info_from_dict(
|
||||
input_info,
|
||||
{
|
||||
"video_path": str(input_path),
|
||||
"image_path": "",
|
||||
"sr_ratio": float(sr_ratio),
|
||||
"target_shape": target_shape,
|
||||
"save_result_path": save_path,
|
||||
"return_result_tensor": False,
|
||||
},
|
||||
)
|
||||
request = {
|
||||
"task": "sr",
|
||||
"video_path": str(input_path),
|
||||
"image_path": "",
|
||||
"sr_ratio": float(sr_ratio),
|
||||
"size": [output_height, output_width],
|
||||
"save_result_path": save_path,
|
||||
"return_result_tensor": False,
|
||||
}
|
||||
runtime_config = {
|
||||
"fps": 0.0,
|
||||
"video_codec": str(video_codec),
|
||||
"quality": int(quality),
|
||||
"ffmpeg_preset": str(ffmpeg_preset),
|
||||
}
|
||||
|
||||
runner = model["runner"]
|
||||
progress = ProgressBar(100)
|
||||
@@ -518,22 +502,9 @@ class LightX2VSwiftVRFileSampler:
|
||||
)
|
||||
try:
|
||||
with _SWIFTVR_RUN_LOCK:
|
||||
runner.set_config(
|
||||
{
|
||||
"fps": 0.0,
|
||||
"video_codec": str(video_codec),
|
||||
"quality": int(quality),
|
||||
"ffmpeg_preset": str(ffmpeg_preset),
|
||||
"video_path": str(input_path),
|
||||
"image_path": "",
|
||||
"sr_ratio": float(sr_ratio),
|
||||
"target_shape": target_shape,
|
||||
"return_result_tensor": False,
|
||||
}
|
||||
)
|
||||
if hasattr(runner, "set_progress_callback"):
|
||||
runner.set_progress_callback(lambda current, _total: progress.update_absolute(current))
|
||||
runner.run_pipeline(input_info)
|
||||
run_request(runner, request, runtime_config=runtime_config)
|
||||
finally:
|
||||
torch.cuda.empty_cache()
|
||||
gc.collect()
|
||||
|
||||
Reference in New Issue
Block a user