add process video

Signed-off-by: Vladimir Mandic <mandic00@live.com>
This commit is contained in:
Vladimir Mandic
2026-07-20 21:44:43 +02:00
parent 24b4ffcf60
commit 1281cf8132
17 changed files with 320 additions and 140 deletions
+5 -2
View File
@@ -1,6 +1,6 @@
# Change Log for SD.Next
## Update for 2026-07-30
## Update for 2026-07-20
- **Compute**
- torch: update to `2.13.0` for CUDA, ROCm, IPEX
@@ -11,8 +11,11 @@
- sdnq attention optimizations
- sdnq separate dit/te settings
- **Features**
- process: read video properties and metadata
- process: allow processing of video files
*note*: currently only seedvr postprocessing is supported
other workflows will be added in future releases
- seedvr: enhanced upscaler support
- process: read video properties metadata
- logs: propagate server tracebacks to client
- networks: improve search and filtering to allow multi-words
- **Fixes**
+2
View File
@@ -307,6 +307,8 @@ class Detailer():
via detailer_opt(). The seed is resolved here so restore()'s inpaint passes are reproducible and the
effective value can be reported back.
"""
if image is None:
return None
from modules.processing_helpers import get_fixed_seed
from modules.paths import resolve_output_path
seed = int(get_fixed_seed(seed))
+138 -37
View File
@@ -4,10 +4,11 @@ import numpy as np
import torch
from PIL import Image
from modules import devices
from modules.shared import opts, log
from modules.shared import opts
from modules.upscaler import Upscaler, UpscalerData
from modules.image import convert
from modules.model_quant import do_post_load_quant
from modules.logger import log, console
MODELS_MAP = {
@@ -31,6 +32,12 @@ class UpscalerSeedVR(Upscaler):
self.tile_size = 1024
self.tile_overlap = 0.25
self.device = devices.device
self.step = 1
self.frames = 0
self.offload = True
self.pbar = None
self.task = None
self.fps = 24
def load_model(self, path: str):
model_name = MODELS_MAP.get(path, None)
@@ -71,16 +78,15 @@ class UpscalerSeedVR(Upscaler):
log.info(f'Upscaler loaded: name="{self.name}" model="{model_name}" time={t1 - t0:.2f}')
def vae_encode(self, samples):
log.debug(f'Upscaler encode: samples={samples[0].shape if len(samples) > 0 else None} tile={self.model.vae.tile_sample_min_size} overlap={self.model.vae.tile_overlap_factor}')
latents = []
if len(samples) == 0:
return latents
self.model.dit = self.model.dit.to(device="cpu")
self.model.vae = self.model.vae.to(device=self.device)
devices.torch_gc()
self.pbar.update(self.task, description=f'encode: samples={samples[0].shape if len(samples) > 0 else None} tile={self.model.vae.tile_sample_min_size} overlap={self.model.vae.tile_overlap_factor}')
if self.offload:
self.model.dit = self.model.dit.to(device="cpu")
self.model.vae = self.model.vae.to(device=self.device)
devices.torch_gc()
from einops import rearrange
from modules.seedvr.src.optimization import memory_manager
memory_manager.clear_rope_cache(self.model)
scale = self.model.config.vae.scaling_factor
shift = self.model.config.vae.get("shifting_factor", 0.0)
batches = [sample.unsqueeze(0) for sample in samples]
@@ -93,21 +99,21 @@ class UpscalerSeedVR(Upscaler):
latent = (latent - shift) * scale
latents.append(latent)
latents = [latent.squeeze(0) for latent in latents]
self.model.vae = self.model.vae.to(device="cpu")
devices.torch_gc()
if self.offload:
self.model.vae = self.model.vae.to(device="cpu")
devices.torch_gc()
return latents
def vae_decode(self, latents, target_dtype: torch.dtype = None):
log.debug(f'Upscaler decode: latents={latents[0].shape if len(latents) > 0 else None} tile={self.model.vae.tile_latent_min_size} overlap={self.model.vae.tile_overlap_factor}')
self.pbar.update(self.task, description=f'decode: latents={latents[0].shape if len(latents) > 0 else None} tile={self.model.vae.tile_latent_min_size} overlap={self.model.vae.tile_overlap_factor}')
samples = []
if len(latents) == 0:
return samples
from einops import rearrange
from modules.seedvr.src.optimization import memory_manager
memory_manager.clear_rope_cache(self.model)
self.model.dit = self.model.dit.to(device="cpu")
self.model.vae = self.model.vae.to(device=self.device)
devices.torch_gc()
if self.offload:
self.model.dit = self.model.dit.to(device="cpu")
self.model.vae = self.model.vae.to(device=self.device)
devices.torch_gc()
scale = self.model.config.vae.scaling_factor
shift = self.model.config.vae.get("shifting_factor", 0.0)
latents = [latent.unsqueeze(0) for latent in latents]
@@ -121,28 +127,89 @@ class UpscalerSeedVR(Upscaler):
sample = self.model.vae.postprocess(sample)
samples.append(sample)
samples = [sample.squeeze(0) for sample in samples]
self.model.vae = self.model.vae.to(device="cpu")
devices.torch_gc()
if self.offload:
self.model.vae = self.model.vae.to(device="cpu")
devices.torch_gc()
return samples
def model_step(self, *args, **kwargs):
from modules.seedvr.src.core import generation
from modules.seedvr.src.optimization import memory_manager
self.model.vae = self.model.vae.to(device="cpu")
self.model.dit = self.model.dit.to(device=self.device)
devices.torch_gc()
log.debug(f'Upscaler inference: args={len(args)} kwargs={list(kwargs.keys())}')
memory_manager.preinitialize_rope_cache(self.model)
if self.offload:
self.model.vae = self.model.vae.to(device="cpu")
self.model.dit = self.model.dit.to(device=self.device)
devices.torch_gc()
with devices.inference_context():
self.pbar.update(self.task, description=f'inference: step={self.step}')
result = generation.generation_step_original(*args, **kwargs)
self.model.dit = self.model.dit.to(device="cpu")
devices.torch_gc()
self.pbar.update(self.task, advance=self.step)
if self.offload:
self.model.dit = self.model.dit.to(device="cpu")
devices.torch_gc()
return result
def do_upscale(self, img: Image.Image, selected_file, cfg_scale: float = 1.5, cfg_rescale: float = 0.0, steps: int = 1, seed: int = -1, scale: float | None = None, tile_size: int = 1024, tile_overlap: float = 0.25):
def read_image(self, image: str | Image.Image):
try:
if isinstance(image, str):
image = Image.open(image)
image = image.convert("RGB")
width = image.width
tensor = np.array(image)
tensor = torch.from_numpy(tensor).to(device=devices.device, dtype=devices.dtype).unsqueeze(0) / 255.0
self.frames = 1
return tensor, width
except Exception as e:
log.error(f'Upscaler: name="SeedVR2" image="{image}" {e}')
return None, None
def read_video(self, video_path: str):
try:
import cv2
cap = cv2.VideoCapture(video_path)
if not cap.isOpened():
log.error(f'Upscaler: name="SeedVR2" video="{video_path}" failed to open')
return None, None
frames = []
width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
while True:
ret, frame = cap.read()
if not ret:
break
frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
frames.append(frame)
cap.release()
if len(frames) == 0:
log.error(f'Upscaler: name="SeedVR2" video="{video_path}" no frames read')
return None, None
tensor = torch.from_numpy(np.array(frames)).to(device=devices.device, dtype=devices.dtype) / 255.0
self.frames = tensor.shape[0]
self.fps = int(cap.get(cv2.CAP_PROP_FPS))
return tensor, width
except Exception as e:
log.error(f'Upscaler: name="SeedVR2" video="{video_path}" {e}')
return None, None
def do_upscale(self,
img: Image.Image | str,
selected_file,
cfg_scale: float = 1.5,
cfg_rescale: float = 0.0,
steps: int = 1,
seed: int = -1,
scale: float | None = None,
tile_size: int = 1024,
tile_overlap: float = 0.25,
batch_size: int = 1,
batch_overlap: int = 0,
offload: bool = True
):
self.offload = offload
self.load_model(selected_file)
if self.model is None:
return img
if not self.offload:
self.model.dit = self.model.dit.to(device=devices.device)
self.model.vae = self.model.vae.to(device=devices.device)
devices.torch_gc()
from modules.seedvr.src.core import generation
@@ -152,32 +219,55 @@ class UpscalerSeedVR(Upscaler):
self.model.vae.tile_sample_min_size = self.tile_size
self.model.vae.tile_latent_min_size = self.tile_size // 8
self.model.vae.tile_overlap_factor = self.tile_overlap
width = int(self.scale * img.width) // 8 * 8
image_tensor = np.array(img)
image_tensor = torch.from_numpy(image_tensor).to(device=devices.device, dtype=devices.dtype).unsqueeze(0) / 255.0
if isinstance(img, Image.Image):
tensor, width = self.read_image(img)
elif isinstance(img, str):
tensor, width = self.read_video(img)
else:
log.error(f'Upscaler: name="SeedVR2" image="{img}" unsupported type {type(img)}')
return img
if tensor is None or width is None:
log.error(f'Upscaler: name="SeedVR2" image="{img}" failed to read')
return img
width = int(self.scale * width) // 8 * 8
random.seed()
seed = int(random.randrange(4294967294)) if seed == -1 else int(seed)
self.step = 1 if self.frames == 1 else batch_size - batch_overlap
t0 = time.time()
with devices.inference_context():
log.info(f'Upscaler: type="{self.name}" model="{selected_file}" scale={self.scale} cfg={cfg_scale}:{cfg_rescale} seed={seed} steps={steps} frames={self.frames} mode={"image" if self.frames == 1 else "video"} tile={self.tile_size}:{self.tile_overlap} batch={batch_size}:{batch_overlap} offload={self.offload}')
import rich.progress as rp
self.pbar = rp.Progress(rp.TextColumn('[cyan]SeedVR:'), rp.BarColumn(), rp.MofNCompleteColumn(), rp.TaskProgressColumn(), rp.TimeRemainingColumn(), rp.TimeElapsedColumn(), rp.TextColumn('[cyan]{task.description}'), console=console)
self.task = self.pbar.add_task(total=self.frames, description='starting...')
with devices.inference_context(), self.pbar:
self.pbar.update(self.task, description='initialize rope')
from modules.seedvr.src.optimization import memory_manager
memory_manager.clear_rope_cache(self.model)
memory_manager.preinitialize_rope_cache(self.model)
result_tensor = generation.generation_loop(
runner=self.model,
images=image_tensor,
images=tensor,
cfg_scale=cfg_scale,
cfg_rescale=cfg_rescale,
steps=steps,
steps=steps, # TODO SeedVR steps
batch_size=batch_size, # TODO SeedVR batch size
temporal_overlap=batch_overlap, # TODO SeedVR temporal overlap
seed=seed,
res_w=width,
batch_size=1,
temporal_overlap=0,
device=devices.device,
)
memory_manager.clear_rope_cache(self.model)
self.pbar.update(self.task, completed=self.frames)
t1 = time.time()
tiles = getattr(self.model.vae, "tiles", None)
log.info(f'Upscaler: type="{self.name}" model="{selected_file}" scale={self.scale} cfg={cfg_scale} seed={seed} tiles={tiles} time={t1 - t0:.2f}')
img = convert.to_pil(result_tensor.squeeze())
self.frames = result_tensor.shape[0] if result_tensor is not None else 0
log.info(f'Upscaler: type="{self.name}" model="{selected_file}" scale={self.scale} cfg={cfg_scale} seed={seed} tiles={tiles} frames={self.frames} time={t1 - t0:.2f}')
if self.offload:
self.model.dit = self.model.dit.to(device="cpu")
self.model.vae = self.model.vae.to(device="cpu")
if opts.upscaler_unload:
self.model.dit = None
self.model.vae = None
@@ -185,4 +275,15 @@ class UpscalerSeedVR(Upscaler):
self.model = None
log.debug(f'Upscaler unload: type="{self.name}" model="{selected_file}"')
devices.torch_gc(force=True)
return img
if self.frames == 1:
img = convert.to_pil(result_tensor.squeeze())
return img
elif self.frames > 1:
from modules.video_models.video_save import save_video
pixels = result_tensor.permute(3, 0, 1, 2).unsqueeze(0) # from (t, h, w, c) to (n, c, t, h, w)
_frames, filename, _thumb = save_video(p=None, pixels=pixels, mp4_fps=self.fps, mp4_thumb=False, mp4_frames=False, reclamp=False)
return filename
else:
log.error(f'Upscaler: name="SeedVR2" model="{selected_file}" no frames generated')
return img
+107 -79
View File
@@ -14,104 +14,132 @@ def run_postprocessing(extras_mode,
image_folder: list[tempfile.NamedTemporaryFile],
input_dir,
output_dir,
extras_video,
video,
show_extras_results,
*args,
save_output: bool = True):
devices.torch_gc()
shared.state.begin('Extras')
image_data = []
image_names = []
image_ext = []
outputs = []
params = {}
info = ''
if extras_mode == 1:
for img in image_folder:
if isinstance(img, Image.Image):
image = img
fn = ''
ext = None
else:
shared.state.begin('Process')
def prepare_inputs(image):
image_data = []
image_names = []
image_ext = []
if extras_mode == 1: # process batch
for img in image_folder:
if isinstance(img, Image.Image):
image = img
fn = ''
ext = None
else:
try:
image = Image.open(os.path.abspath(img.name))
except Exception as e:
log.error(f'Failed to open image: file="{img.name}" {e}')
continue
fn, ext = os.path.splitext(img.orig_name)
image_data.append(image)
image_names.append(fn)
image_ext.append(ext)
log.debug(f'Process: mode=batch inputs={len(image_folder)} images={len(image_data)}')
elif extras_mode == 2: # process folder
assert input_dir, 'input directory not selected'
image_list = os.listdir(input_dir)
for filename in image_list:
fn = os.path.join(input_dir, filename)
try:
image = Image.open(os.path.abspath(img.name))
image = Image.open(fn)
except Exception as e:
log.error(f'Failed to open image: file="{img.name}" {e}')
log.error(f'Failed to open image: file="{fn}" {e}')
continue
fn, ext = os.path.splitext(img.orig_name)
image_data.append(image)
image_names.append(fn)
image_ext.append(None)
log.debug(f'Process: mode=folder inputs={input_dir} files={len(image_list)} images={len(image_data)}')
elif extras_mode == 3: # process video
pass
else: # process image
image_data.append(image)
image_names.append(fn)
image_ext.append(ext)
log.debug(f'Process: mode=batch inputs={len(image_folder)} images={len(image_data)}')
elif extras_mode == 2:
assert input_dir, 'input directory not selected'
image_list = os.listdir(input_dir)
for filename in image_list:
fn = os.path.join(input_dir, filename)
try:
image = Image.open(fn)
except Exception as e:
log.error(f'Failed to open image: file="{fn}" {e}')
continue
image_data.append(image)
image_names.append(fn)
image_names.append(None)
image_ext.append(None)
log.debug(f'Process: mode=folder inputs={input_dir} files={len(image_list)} images={len(image_data)}')
elif extras_mode == 3:
log.error(f'Process: mode=video file="{extras_video}" not implemented yet')
else:
image_data.append(image)
image_names.append(None)
image_ext.append(None)
return image_data, image_names, image_ext
image_data, image_names, image_ext = prepare_inputs(image)
if extras_mode == 2 and output_dir != '':
outpath = output_dir
else:
outpath = resolve_output_path(opts.outdir_samples, opts.outdir_extras_samples)
processed_images = []
for image, name, ext in zip(image_data, image_names, image_ext, strict=False): # pylint: disable=redefined-argument-from-local
log.debug(f'Process: image={image} {args}')
def process_images():
outputs = []
params = {}
info = ''
if shared.state.interrupted:
log.debug('Postprocess interrupted')
break
if isinstance(image, str):
try:
image = Image.open(image)
except Exception as e:
log.error(f'Failed to open image: file="{image}" {e}')
processed_images = []
for image, name, ext in zip(image_data, image_names, image_ext, strict=False): # pylint: disable=redefined-argument-from-local
log.debug(f'Process: image={image} {args}')
info = ''
if shared.state.interrupted:
log.debug('Postprocess interrupted')
break
if isinstance(image, str):
try:
image = Image.open(image)
except Exception as e:
log.error(f'Failed to open image: file="{image}" {e}')
continue
if image is None:
continue
if image is None:
continue
shared.state.textinfo = name
pp = scripts_postprocessing.PostprocessedImage(image.convert("RGB"))
shared.state.textinfo = name
pp = scripts_postprocessing.PostprocessedImage(image.convert("RGB"))
scripts_manager.scripts_postproc.run(pp, args)
geninfo, items = images.read_info_from_image(image)
params = infotext.parse(geninfo)
for k, v in items.items():
pp.image.info[k] = v
if 'parameters' in items:
info = items['parameters'] + ', '
if (params.get('size-1', 0) != pp.image.width) or (params.get('size-2', 0) != pp.image.height):
params['size-1'] = pp.image.width
params['size-2'] = pp.image.height
info += f"Size: {pp.image.width}x{pp.image.height}, "
info = info + ", ".join([k if k == v else f'{k}: {infotext.quote(v)}' for k, v in pp.info.items() if v is not None])
pp.image.info["postprocessing"] = info
processed_images.append(pp.image)
if save_output:
if opts.use_original_name_batch and name is not None:
forced_filename = os.path.splitext(os.path.basename(name))[0]
images.save_image(pp.image, path=outpath, extension=ext or opts.samples_format, info=info, grid=False, pnginfo_section_name="extras", existing_info=pp.image.info, forced_filename=forced_filename)
else:
images.save_image(pp.image, path=outpath, extension=ext or opts.samples_format, info=info, grid=False, pnginfo_section_name="extras", existing_info=pp.image.info)
if extras_mode != 2 or show_extras_results:
outputs.append(pp.image)
image.close()
scripts_manager.scripts_postproc.postprocess(processed_images, args)
return outputs, info, params
def process_video():
outputs = []
params = {}
info = '' # TODO process: video add infotext
if not video or not isinstance(video, str) or not os.path.isfile(video):
log.error(f'Process: mode=video file="{video}" not found')
return outputs, video, info, params
log.debug(f'Process: video={video} {args}')
shared.state.textinfo = video
pp = scripts_postprocessing.PostprocessedImage(video=video)
scripts_manager.scripts_postproc.run(pp, args)
geninfo, items = images.read_info_from_image(image)
params = infotext.parse(geninfo)
for k, v in items.items():
pp.image.info[k] = v
if 'parameters' in items:
info = items['parameters'] + ', '
if (params.get('size-1', 0) != pp.image.width) or (params.get('size-2', 0) != pp.image.height):
params['size-1'] = pp.image.width
params['size-2'] = pp.image.height
info += f"Size: {pp.image.width}x{pp.image.height}, "
info = info + ", ".join([k if k == v else f'{k}: {infotext.quote(v)}' for k, v in pp.info.items() if v is not None])
pp.image.info["postprocessing"] = info
processed_images.append(pp.image)
if save_output:
if opts.use_original_name_batch and name is not None:
forced_filename = os.path.splitext(os.path.basename(name))[0]
images.save_image(pp.image, path=outpath, extension=ext or opts.samples_format, info=info, grid=False, pnginfo_section_name="extras", existing_info=pp.image.info, forced_filename=forced_filename)
else:
images.save_image(pp.image, path=outpath, extension=ext or opts.samples_format, info=info, grid=False, pnginfo_section_name="extras", existing_info=pp.image.info)
if extras_mode != 2 or show_extras_results:
outputs.append(pp.image)
image.close()
scripts_manager.scripts_postproc.postprocess(processed_images, args)
return pp.video, info, params
if extras_mode == 3:
video, info, params = process_video()
outputs = []
else:
outputs, info, params = process_images()
video = None
devices.torch_gc()
return outputs, info, params
return outputs, video, info, params
def run_extras(extras_mode, resize_mode, image, image_folder, input_dir, output_dir, video, show_extras_results, upscaling_resize, upscaling_resize_w, upscaling_resize_h, upscaling_crop, extras_upscaler_1, extras_upscaler_2, extras_upscaler_2_visibility, save_output: bool = True, script_args: dict | None = None):
+4 -3
View File
@@ -5,14 +5,15 @@ from modules.logger import log
class PostprocessedImage:
def __init__(self, image, info = None):
def __init__(self, image = None, video = None, info = None):
if info is None:
info = {}
self.image = image
self.video = video
self.info = info
def __str__(self):
return f'PostprocessedImage(image={self.image} info={self.info})'
return f'PostprocessedImage(image={self.image} video={self.video} info={self.info})'
class ScriptPostprocessing:
@@ -160,6 +161,6 @@ class ScriptPostprocessingRunner:
else:
for (name, _component), value in zip(script.controls.items(), script_args, strict=False):
process_kwargs[name] = value
log.debug(f'Postprocess: script={script.name} args={process_args} kwargs={process_kwargs}')
log.debug(f'Postprocess: script="{script.name}" args={process_args} kwargs={process_kwargs}')
script.postprocess(filenames, *process_args, **process_kwargs)
shared.state.end(jobid)
+15 -5
View File
@@ -32,8 +32,10 @@ def submit_video(video):
def submit_process(tab_index, extras_image, image_batch, extras_batch_input_dir, extras_batch_output_dir, extras_video, show_extras_results, save_output, *script_inputs):
from modules.ui_common import infotext_to_html
result_images, geninfo, _js_info = postprocessing.run_postprocessing(tab_index, extras_image, image_batch, extras_batch_input_dir, extras_batch_output_dir, extras_video, show_extras_results, *script_inputs, save_output=save_output)
return result_images, geninfo, infotext_to_html(geninfo)
result_images, result_video, geninfo, _js_info = postprocessing.run_postprocessing(tab_index, extras_image, image_batch, extras_batch_input_dir, extras_batch_output_dir, extras_video, show_extras_results, *script_inputs, save_output=save_output)
gr_result_image = gr.update(value=result_images, visible=tab_index != 3)
gr_result_video = gr.update(value=result_video, visible=tab_index == 3)
return gr_result_image, gr_result_video, geninfo, infotext_to_html(geninfo)
def create_ui():
@@ -51,12 +53,12 @@ def create_ui():
extras_batch_output_dir = gr.Textbox(label="Output directory", **shared.hide_dirs, placeholder="Leave blank to save images to the default path.", elem_id="extras_batch_output_dir")
show_extras_results = gr.Checkbox(label='Show result images', value=True, elem_id="extras_show_extras_results")
with gr.Tab('Process Video', id="process_video", elem_id="extras_process_video_tab") as tab_process_video:
extras_video = gr.Video(label="Input Video", show_label=False, interactive=True, elem_id="extras_video")
extras_video = gr.Video(label="Input Video", show_label=False, height=512, interactive=True, elem_id="extras_video")
with gr.Row():
save_output = gr.Checkbox(label='Save output', value=True, elem_id="extras_save_output")
script_inputs = scripts_manager.scripts_postproc.setup_ui()
with gr.Column():
with gr.Column(elem_id="extras_output_column"):
id_part = 'extras'
with gr.Row(elem_id=f"{id_part}_generate_box", elem_classes="generate-box"):
submit = gr.Button('Generate', elem_id=f"{id_part}_generate", variant='primary')
@@ -66,9 +68,16 @@ def create_ui():
skip.click(fn=shared.state.skip, inputs=[], outputs=[])
pause = gr.Button('Pause', elem_id=f"{id_part}_pause")
pause.click(fn=shared.state.pause, _js='checkPaused', inputs=[], outputs=[])
result_images, generation_info, _html_info, html_info_formatted, _html_log = ui_common.create_output_panel("extras")
with gr.Tabs(elem_id="extras_output_tabs"):
with gr.Tab('Image', id="process_output_image", elem_id="extras_output_image_tab"):
result_images, generation_info, _html_info, html_info_formatted, _html_log = ui_common.create_output_panel("extras")
with gr.Tab('Video', id="process_output_video", elem_id="extras_output_video_tab"):
result_video = gr.Video(label="Video", show_label=False, interactive=False, elem_id="extras_output_video", visible=False)
gr.HTML('File metadata')
exif_info = gr.HTML(elem_id="pnginfo_html_info")
with gr.Row(elem_id='copy_buttons_process'):
copy_process_buttons = generation_parameters_copypaste.create_buttons(["txt2img", "img2img", "control", "caption"])
@@ -100,6 +109,7 @@ def create_ui():
],
outputs=[
result_images,
result_video,
generation_info,
html_info_formatted,
]
+7 -2
View File
@@ -256,6 +256,7 @@ def save_video(
stream=None, # async progress reporting stream
metadata: dict | None = None, # metadata for video
pbar=None, # progress bar for video
reclamp: bool = True, # reclamp pixels to [-1, 1] range
):
if metadata is None:
metadata = {}
@@ -288,6 +289,8 @@ def save_video(
log.error(f'Video: type={type(pixels)} not a tensor')
return 0, output_video, None
t_save = time.time()
if pixels.ndim == 4:
pixels = pixels.unsqueeze(0)
n, _c, t, h, w = pixels.shape
size = pixels.element_size() * pixels.numel()
log.debug(f'Video: video={mp4_video} export={mp4_frames} safetensors={mp4_sf} interpolate={mp4_interpolate}')
@@ -304,8 +307,10 @@ def save_video(
pixels = pixels.permute(1, 2, 0, 3, 4)
pixels = pixels * 2.0 - 1.0
n, _c, t, h, w = pixels.shape
x = torch.clamp(pixels.float(), -1., 1.) * 127.5 + 127.5
if reclamp:
x = torch.clamp(pixels.float(), -1., 1.) * 127.5 + 127.5
else:
x = pixels.float() * 255.0
x = x.detach().cpu().to(torch.uint8)
x = einops.rearrange(x, '(m n) c t h w -> t (m h) (n w) c', n=n)
x = x.contiguous()
+2
View File
@@ -16,6 +16,8 @@ class ScriptPostprocessingColorGrading(scripts_postprocessing.ScriptPostprocessi
grading_params = processing_grading.GradingParams(*args, **kwargs)
if not processing_grading.is_active(grading_params):
return
if pp.image is None:
return
pp.image = processing_grading.grade_image(pp.image, grading_params)
defaults = processing_grading.GradingParams()
for f in fields(grading_params):
+4
View File
@@ -28,6 +28,10 @@ class ScriptPostprocessingDetailer(scripts_postprocessing.ScriptPostprocessing):
sampler='Default', prediction='default', shift=3.0, cfg_scale=6.0, options=None, seed=-1):
if not enabled:
return pp
if not shared.sd_loaded:
log.warning('Detailer postprocess: SD model not loaded')
pp.info["Detailer"] = "skipped (SD model not loaded)"
return pp
if shared.sd_model is None or not hasattr(shared.sd_model, 'sd_checkpoint_info'):
log.warning('Detailer postprocess: no base model selected')
pp.info["Detailer"] = "skipped (no base model selected)"
+2
View File
@@ -31,6 +31,8 @@ class ScriptPixelArt(scripts_postprocessing.ScriptPostprocessing):
return
from modules.postprocess.pixelart import img_to_pixelart, edge_detect_for_pixelart
pixel_image = pp.image
if pixel_image is None:
return
if pixelart_use_edge_detection:
pixel_image = edge_detect_for_pixelart(pixel_image, image_weight=pixelart_image_weight, block_size=pixelart_edge_block_size, device=devices.device)
+2 -1
View File
@@ -69,7 +69,8 @@ class ScriptPostprocessingRembg(scripts_postprocessing.ScriptPostprocessing):
else:
image = pp.image
info = pp.info
if image is None:
return pp
log.info(f'RemoveBackground: model={model} merge_alpha={merge_alpha} refine={refine} mask_only={mask_only} postprocess_mask={postprocess_mask} alpha_matting={alpha_matting} alpha_matting_foreground_threshold={alpha_matting_foreground_threshold} alpha_matting_background_threshold={alpha_matting_background_threshold} alpha_matting_erode_size={alpha_matting_erode_size}')
if model == 'ben2':
try:
+25 -6
View File
@@ -13,18 +13,23 @@ class ScriptSeedVR(scripts_postprocessing.ScriptPostprocessing):
with gr.Accordion(self.name, open = False, elem_id="postprocess_seedvr_accordion"):
with gr.Row():
seedvr_enabled = gr.Checkbox(label="Enable SeedVR", value=False, elem_id="extras_seedvr_enabled")
seedvr_offload = gr.Checkbox(label="Offload model", value=True, elem_id="extras_seedvr_offload")
with gr.Row():
seedvr_selected = gr.Dropdown(label="SeedVR model", choices=list(MODELS_MAP.keys()), value=list(MODELS_MAP.keys())[0], elem_id="extras_seedvr_model")
with gr.Row():
seedvr_scale = gr.Slider(minimum=1, maximum=16, step=0.1, value=2, label="SeedVR scale", elem_id="extras_seedvr_scale")
seedvr_steps = gr.Slider(step=1, value=1, minimum=1, maximum=99, label="SeedVR steps", elem_id="extras_seedvr_steps")
with gr.Row():
seedvr_seed = gr.Number(step=1, value=-1, label="SeedVR seed", elem_id="extras_seedvr_seed")
seedvr_steps = gr.Number(step=1, value=1, minimum=1, maximum=99, label="SeedVR steps", elem_id="extras_seedvr_steps", visible=False)
with gr.Row():
seedvr_cfg_scale = gr.Slider(minimum=0.0, maximum=15.0, step=0.01, value=1.5, label="SeedVR guidance scale", elem_id="extras_seedvr_cfg_scale")
seedvr_cfg_rescale = gr.Slider(minimum=0.0, maximum=15.0, step=0.01, value=0.0, label="SeedVR guidance rescale", elem_id="extras_seedvr_cfg_rescale")
with gr.Row():
seedvr_tile_size = gr.Slider(minimum=64, maximum=4096, step=8, value=1024, label="SeedVR tile size", elem_id="extras_seedvr_tile_size")
seedvr_tile_overlap = gr.Slider(minimum=0, maximum=1.0, step=0.01, value=0.25, label="SeedVR tile overlap", elem_id="extras_seedvr_tile_overlap")
with gr.Row():
seedvr_batch_size = gr.Slider(minimum=1, maximum=64, step=1, value=1, label="SeedVR batch size", elem_id="extras_seedvr_batch_size")
seedvr_batch_overlap = gr.Slider(minimum=0, maximum=16, step=1, value=0, label="SeedVR batch overlap", elem_id="extras_seedvr_batch_overlap")
return {
"seedvr_enabled": seedvr_enabled,
"seedvr_selected": seedvr_selected,
@@ -35,6 +40,9 @@ class ScriptSeedVR(scripts_postprocessing.ScriptPostprocessing):
"seedvr_cfg_rescale": seedvr_cfg_rescale,
"seedvr_tile_size": seedvr_tile_size,
"seedvr_tile_overlap": seedvr_tile_overlap,
"seedvr_batch_size": seedvr_batch_size,
"seedvr_batch_overlap": seedvr_batch_overlap,
"seedvr_offload": seedvr_offload,
}
def process(self,
@@ -47,22 +55,27 @@ class ScriptSeedVR(scripts_postprocessing.ScriptPostprocessing):
seedvr_cfg_scale: float,
seedvr_cfg_rescale: float,
seedvr_tile_size: int,
seedvr_tile_overlap: float
seedvr_tile_overlap: float,
seedvr_batch_size: int,
seedvr_batch_overlap: int,
seedvr_offload: bool
): # pylint: disable=arguments-differ
if not seedvr_enabled:
return
from modules import shared, upscaler
from modules.logger import log
image = pp.image
_input = pp.image or pp.video
if _input is None:
return
instance: upscaler.UpscalerData = next(iter([x for x in shared.sd_upscalers if x.name == seedvr_selected]), None)
scaler: UpscalerSeedVR = instance.scaler
log.info(f'Upscaler: type="SeedVR" model="{seedvr_selected}" scale={seedvr_scale} seed={seedvr_seed} steps={seedvr_steps} cfg_scale={seedvr_cfg_scale} cfg_rescale={seedvr_cfg_rescale} tile_size={seedvr_tile_size} tile_overlap={seedvr_tile_overlap}')
log.info(f'Upscaler: type="SeedVR" model="{seedvr_selected}" scale={seedvr_scale} seed={seedvr_seed} steps={seedvr_steps} cfg_scale={seedvr_cfg_scale} cfg_rescale={seedvr_cfg_rescale} tile_size={seedvr_tile_size} tile_overlap={seedvr_tile_overlap} batch_size={seedvr_batch_size} batch_overlap={seedvr_batch_overlap}')
jobid = shared.state.begin('Upscale')
scaler.scale = float(seedvr_scale)
upscaled = scaler.do_upscale(image,
upscaled = scaler.do_upscale(_input,
seedvr_selected,
cfg_scale=seedvr_cfg_scale,
cfg_rescale=seedvr_cfg_rescale,
@@ -70,8 +83,14 @@ class ScriptSeedVR(scripts_postprocessing.ScriptPostprocessing):
seed=seedvr_seed,
tile_size=seedvr_tile_size,
tile_overlap=seedvr_tile_overlap,
batch_size=seedvr_batch_size,
batch_overlap=seedvr_batch_overlap,
offload=seedvr_offload
)
shared.state.end(jobid)
pp.image = upscaled
if isinstance(upscaled, str):
pp.video = upscaled
else:
pp.image = upscaled
pp.info["SeedVR"] = f"Scale={seedvr_scale} Seed={seedvr_seed} CFG Scale={seedvr_cfg_scale} CFG Rescale={seedvr_cfg_rescale}"
+2
View File
@@ -52,6 +52,8 @@ class ScriptPostprocessingUpscale(scripts_postprocessing.ScriptPostprocessing):
}
def upscale(self, image, info, upscaler, upscale_mode, upscale_by, upscale_to_width, upscale_to_height, upscale_crop):
if image is None:
return None
if upscale_mode == 1:
upscale_by = max(upscale_to_width / image.width, upscale_to_height / image.height)
info["Postprocess upscale to"] = f"{upscale_to_width}x{upscale_to_height}"
+1 -1
View File
@@ -11713,7 +11713,7 @@ function updateImg2imgResizeToTextAfterChangingImage() {
}
async function toggleCompact(val, old) {
if (val === old) return;
log("toggleCompact", val, old);
log("toggleCompact", val);
if (val) {
gradioApp().style.setProperty("--layout-gap", "var(--spacing-md)");
gradioApp().querySelectorAll("input[type=range]").forEach((el2) => el2.classList.add("hidden"));
+2 -2
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -729,7 +729,7 @@ function createThemeElement(): HTMLImageElement {
export async function toggleCompact(val, old) {
if (val === old) return;
log('toggleCompact', val, old);
log('toggleCompact', val);
if (val) {
gradioApp().style.setProperty('--layout-gap', 'var(--spacing-md)');
gradioApp().querySelectorAll('input[type=range]').forEach((el) => el.classList.add('hidden'));