diff --git a/README.md b/README.md index c80f689..72d7fef 100644 --- a/README.md +++ b/README.md @@ -22,6 +22,8 @@ Once the image generation begins, the intermediate images will start saving in a Please be aware that _Image creation progress preview mode_ in the webui's settings affects how the intermediate images are created. You can also make a video out of the intermediate images: -
+
GIF:
+
+
MP4, with interpolation:
\ No newline at end of file
diff --git a/scripts/sd_save_intermediate_images.py b/scripts/sd_save_intermediate_images.py
index f8ae736..54514b5 100644
--- a/scripts/sd_save_intermediate_images.py
+++ b/scripts/sd_save_intermediate_images.py
@@ -16,6 +16,74 @@ import gradio as gr; gr.__version__
orig_callback_state = KDiffusionSampler.callback_state
+
+def make_video(p, ssii_is_active, ssii_intermediate_type, ssii_every_n, ssii_stop_at_n, ssii_video, ssii_video_format, ssii_video_fps, ssii_video_hires, ssii_smooth, ssii_seconds, ssii_debug):
+ if ssii_is_active and ssii_video and not state.skipped and not state.interrupted:
+ logger = logging.getLogger(__name__)
+ # ffmpeg requires sequential numbers in filenames (that is exactly +1)
+ p.intermed_files.sort(key=lambda x: x[0])
+ prev_batch = None
+ for real_i, (batch_no, name_org, _) in enumerate(p.intermed_files):
+ if prev_batch != batch_no:
+ i = 0
+ num_seq = '{:03}'.format(i)
+ name_seq = re.sub(r'^\d+-(\d{3})', f'{name_org.split("-")[0]}-{num_seq}', name_org)
+ p.intermed_files[real_i] = (batch_no, name_org, name_seq)
+ path_name_org = os.path.join(p.intermed_outpath, name_org)
+ path_name_seq = os.path.join(p.intermed_outpath, name_seq)
+ os.replace(path_name_org, path_name_seq)
+ logger.debug(f"replace {path_name_org} / {path_name_seq}")
+ i = i + 1
+ prev_batch = batch_no
+ frames_per_image = i
+
+ for intermed_pattern in p.intermed_pattern.values():
+ img_file = intermed_pattern.replace("%%%", "%03d") + ".png"
+ vid_file = intermed_pattern.replace("%%%-", "") + "." + ssii_video_format
+ if hasattr(p, "enable_hr"):
+ if p.enable_hr and ssii_video_hires == "1":
+ img_file = img_file.replace("-p2-", "-p1-")
+ vid_file = vid_file.replace("-p2-", "-p1-")
+ path_img_file = os.path.join(p.intermed_outpath, img_file)
+ path_vid_file = os.path.join(p.intermed_outpath, vid_file)
+ if ssii_smooth:
+ pts = (round(ssii_seconds / frames_per_image, 5))
+ logger.debug(f"pts: {pts}")
+ if pts < 1:
+ pts = "1"
+ else:
+ pts = str(pts)
+ if ssii_video_format == "gif":
+ ff = FFmpeg(
+ inputs={path_img_file: "-benchmark -framerate 1"},
+ outputs={path_vid_file: f'-filter_complex "split[v1][v2]; [v1]palettegen=stats_mode=full [palette]; [v2][palette]paletteuse=dither=sierra2_4a [v3]; [v3]setpts={pts}*PTS [v4]; [v4]minterpolate=fps={int(ssii_video_fps)}:mi_mode=mci:mc_mode=aobmc:me_mode=bidir:vsbmc=1"'}
+ )
+ else:
+ ff = FFmpeg(
+ inputs={path_img_file: "-benchmark -framerate 1"},
+ outputs={path_vid_file: f'-filter_complex "setpts={pts}*PTS [v4]; [v4]minterpolate=fps={int(ssii_video_fps)}:mi_mode=mci:mc_mode=aobmc:me_mode=bidir:vsbmc=1"'}
+ )
+ else:
+ if ssii_video_format == "gif":
+ ff = FFmpeg(
+ inputs={path_img_file: f"-benchmark -framerate {int(ssii_video_fps)}"},
+ outputs={path_vid_file: '-filter_complex "split[v1][v2]; [v1]palettegen=stats_mode=full [palette]; [v2][palette]paletteuse=dither=sierra2_4a"'}
+ )
+ else:
+ ff = FFmpeg(
+ inputs={path_img_file: f"-benchmark -framerate {int(ssii_video_fps)}"},
+ outputs={path_vid_file: None}
+ )
+ ff.run()
+
+ # Back to original numbering
+ for (batch_no, name_org, name_seq) in reversed(p.intermed_files):
+ path_name_org = os.path.join(p.intermed_outpath, name_org)
+ path_name_seq = os.path.join(p.intermed_outpath, name_seq)
+ os.replace(path_name_seq, path_name_org)
+ logger.debug(f"replace {path_name_seq} / {path_name_org}")
+ return
+
class Script(scripts.Script):
def title(self):
return "Save intermediate images during the sampling process"
@@ -90,7 +158,7 @@ class Script(scripts.Script):
return [ssii_is_active, ssii_intermediate_type, ssii_every_n, ssii_stop_at_n, ssii_video, ssii_video_format, ssii_video_fps, ssii_video_hires, ssii_smooth, ssii_seconds, ssii_debug]
def save_image_only_get_name(image, path, basename, seed=None, prompt=None, extension='png', info=None, short_filename=False, no_prompt=False, grid=False, pnginfo_section_name='parameters', p=None, existing_info=None, forced_filename=None, suffix="", save_to_dirs=None):
- #for description see modules.images.save_image, same code up saving of files
+ # for description see modules.images.save_image, same code up saving of files
namegen = FilenameGenerator(p, seed, prompt, image)
@@ -176,7 +244,20 @@ class Script(scripts.Script):
logger.debug(f"Step: {current_step}")
logger.debug(f"hr: {hr}")
- #Highres. fix requires 2 passes
+ if current_step == 0:
+ # Deal with batch_count > 1
+ if hasattr(p, 'intermed_batch_iter'):
+ if p.iteration > p.intermed_batch_iter:
+ p.intermed_batch_iter = p.iteration
+ # Reset per-batch_count-attributes
+ delattr(p, "intermed_final_pass")
+ delattr(p, "intermed_max_step")
+ # Make video for previous batch_count
+ make_video(p, ssii_is_active, ssii_intermediate_type, ssii_every_n, ssii_stop_at_n, ssii_video, ssii_video_format, ssii_video_fps, ssii_video_hires, ssii_smooth, ssii_seconds, ssii_debug)
+ else:
+ p.intermed_batch_iter = p.iteration
+
+ # Highres. fix requires 2 passes
if not hasattr(p, 'intermed_final_pass'):
if hr:
p.intermed_first_pass = True
@@ -185,7 +266,7 @@ class Script(scripts.Script):
p.intermed_first_pass = True
p.intermed_final_pass = True
- #Check if pass 1 has finished
+ # Check if pass 1 has finished
if hasattr(p, 'intermed_max_step'):
if current_step >= p.intermed_max_step:
p.intermed_max_step = current_step
@@ -196,7 +277,7 @@ class Script(scripts.Script):
else:
p.intermed_max_step = current_step
- #ssii_stop_at_n must be a multiple of ssii_every_n
+ # ssii_stop_at_n must be a multiple of ssii_every_n
if not hasattr(p, 'intermed_ssii_stop_at_n'):
if ssii_stop_at_n % ssii_every_n == 0:
p.intermed_ssii_stop_at_n = ssii_stop_at_n
@@ -205,6 +286,7 @@ class Script(scripts.Script):
if current_step % ssii_every_n == 0:
for index in range(0, p.batch_size):
+ # Live preview only works on first batch_pos
if ssii_intermediate_type == "According to Live preview subject setting" and index == 0:
image = state.current_image
elif ssii_intermediate_type == "Noisy":
@@ -214,6 +296,7 @@ class Script(scripts.Script):
logger.debug(f"ssii_intermediate_type, ssii_every_n, ssii_stop_at_n: {ssii_intermediate_type}, {ssii_every_n}, {ssii_stop_at_n}")
logger.debug(f"Step: {current_step}")
+ logger.debug(f"batch_count, iteration, batch_size, batch_pos: {p.n_iter}, {p.iteration}, {p.batch_size}, {index}")
# Inits per seed
if current_step == 0 and p.intermed_first_pass:
@@ -263,46 +346,52 @@ class Script(scripts.Script):
logger.debug(f"p.all_seeds: {p.all_seeds}")
logger.debug(f"p.cfg_scale: {p.cfg_scale}")
logger.debug(f"p.sampler_name: {p.sampler_name}")
- logger.debug(f"p.batch_size: {p.batch_size}")
-
- intermed_suffix = p.intermed_outpath_suffix.replace(str(int(p.seed)), str(int(p.all_seeds[index])), 1)
- intermed_pattern = p.intermed_outpath_number[index] + "-%%%-" + intermed_suffix
- if hr:
- if p.intermed_final_pass:
- intermed_pattern = intermed_pattern.replace("%%%", "%%%-p2")
- else:
- intermed_pattern = intermed_pattern.replace("%%%", "%%%-p1")
- p.intermed_pattern[int(p.all_seeds[index])] = intermed_pattern
- filename = intermed_pattern.replace("%%%", f"{current_step:03}")
-
- #don't save first step
- if current_step > 0:
- #generate png-info
- infotext = create_infotext(p, p.all_prompts, p.all_seeds, p.all_subseeds, comments=[], position_in_batch=index % p.batch_size, iteration=index // p.batch_size)
- infotext = f'{infotext}, intermediate: {current_step:03d}'
-
- if current_step == p.intermed_ssii_stop_at_n:
- if (hr and p.intermed_final_pass) or not hr:
- #early stop for this seed reached, prevent normal save, save as final image
- p.do_not_save_samples = True
- save_image(image, p.outpath_samples, "", p.all_seeds[index], p.prompt, opts.samples_format, info=infotext, p=p)
- if index == p.batch_size - 1:
- #early stop for final seed and final pass reached, interrupt further processing
- state.interrupt()
+
+ # Don't continue with no image (can happen with live preview subject setting)
+ if image is None:
+ logger.debug("image is None")
+ else:
+ intermed_seed_index = p.iteration * p.batch_size + index
+ intermed_seed = int(p.all_seeds[intermed_seed_index])
+ logger.debug(f"intermed_seed_index, intermed_seed: {intermed_seed_index}, {intermed_seed}")
+ intermed_suffix = p.intermed_outpath_suffix.replace(str(int(p.seed)), str(intermed_seed), 1)
+ intermed_pattern = p.intermed_outpath_number[index] + "-%%%-" + intermed_suffix
+ if hr:
+ if p.intermed_final_pass:
+ intermed_pattern = intermed_pattern.replace("%%%", "%%%-p2")
else:
- #save intermediate image
+ intermed_pattern = intermed_pattern.replace("%%%", "%%%-p1")
+ p.intermed_pattern[intermed_seed] = intermed_pattern
+ filename = intermed_pattern.replace("%%%", f"{current_step:03}")
+
+ # Don't save first step
+ if current_step > 0:
+ # generate png-info
+ infotext = create_infotext(p, p.all_prompts, p.all_seeds, p.all_subseeds, comments=[], position_in_batch=index % p.batch_size, iteration=index // p.batch_size)
+ infotext = f'{infotext}, intermediate: {current_step:03d}'
+
+ if current_step == p.intermed_ssii_stop_at_n:
+ if (hr and p.intermed_final_pass) or not hr:
+ # early stop for this seed reached, prevent normal save, save as final image
+ p.do_not_save_samples = True
+ save_image(image, p.outpath_samples, "", intermed_seed, p.prompt, opts.samples_format, info=infotext, p=p)
+ if index == p.batch_size - 1:
+ # early stop for final seed and final pass reached, interrupt further processing
+ state.interrupt()
+ else:
+ # save intermediate image
+ save_image(image, p.intermed_outpath, "", info=infotext, p=p, forced_filename=filename, save_to_dirs=False)
+ filename_clean = re.sub(r"[^\d-]", "%", filename)
+ logger.debug(f"filename: {filename_clean}")
+ if ssii_video and ((hr and p.intermed_first_pass and ssii_video_hires == "1") or (hr and p.intermed_final_pass and ssii_video_hires == "2") or not hr):
+ p.intermed_files.append((index, filename + ".png", None))
+ else:
+ # save intermediate image
save_image(image, p.intermed_outpath, "", info=infotext, p=p, forced_filename=filename, save_to_dirs=False)
filename_clean = re.sub(r"[^\d-]", "%", filename)
logger.debug(f"filename: {filename_clean}")
if ssii_video and ((hr and p.intermed_first_pass and ssii_video_hires == "1") or (hr and p.intermed_final_pass and ssii_video_hires == "2") or not hr):
p.intermed_files.append((index, filename + ".png", None))
- else:
- #save intermediate image
- save_image(image, p.intermed_outpath, "", info=infotext, p=p, forced_filename=filename, save_to_dirs=False)
- filename_clean = re.sub(r"[^\d-]", "%", filename)
- logger.debug(f"filename: {filename_clean}")
- if ssii_video and ((hr and p.intermed_first_pass and ssii_video_hires == "1") or (hr and p.intermed_final_pass and ssii_video_hires == "2") or not hr):
- p.intermed_files.append((index, filename + ".png", None))
return orig_callback_state(self, d)
setattr(KDiffusionSampler, "callback_state", callback_state)
@@ -310,68 +399,5 @@ class Script(scripts.Script):
def postprocess(self, p, processed, ssii_is_active, ssii_intermediate_type, ssii_every_n, ssii_stop_at_n, ssii_video, ssii_video_format, ssii_video_fps, ssii_video_hires, ssii_smooth, ssii_seconds, ssii_debug):
setattr(KDiffusionSampler, "callback_state", orig_callback_state)
- # Make a video file
- if ssii_is_active and ssii_video and not state.skipped and not state.interrupted:
- logger = logging.getLogger(__name__)
- # ffmpeg requires sequential numbers in filenames (that is exactly +1)
- p.intermed_files.sort(key=lambda x: x[0])
- prev_batch = None
- for real_i, (batch_no, name_org, _) in enumerate(p.intermed_files):
- if prev_batch != batch_no:
- i = 0
- num_seq = '{:03}'.format(i)
- name_seq = re.sub(r'^\d+-(\d{3})', f'{name_org.split("-")[0]}-{num_seq}', name_org)
- p.intermed_files[real_i] = (batch_no, name_org, name_seq)
- path_name_org = os.path.join(p.intermed_outpath, name_org)
- path_name_seq = os.path.join(p.intermed_outpath, name_seq)
- os.replace(path_name_org, path_name_seq)
- logger.debug(f"replace {path_name_org} / {path_name_seq}")
- i = i + 1
- prev_batch = batch_no
- frames_per_image = i
-
- for intermed_pattern in p.intermed_pattern.values():
- img_file = intermed_pattern.replace("%%%", "%03d") + ".png"
- vid_file = intermed_pattern.replace("%%%-", "") + "." + ssii_video_format
- if hasattr(p, "enable_hr"):
- if p.enable_hr and ssii_video_hires == "1":
- img_file = img_file.replace("-p2-", "-p1-")
- vid_file = vid_file.replace("-p2-", "-p1-")
- path_img_file = os.path.join(p.intermed_outpath, img_file)
- path_vid_file = os.path.join(p.intermed_outpath, vid_file)
- if ssii_smooth:
- pts = (round(ssii_seconds / frames_per_image, 5))
- logger.debug(f"pts: {pts}")
- if pts < 1:
- pts = "1"
- else:
- pts = str(pts)
- if ssii_video_format == "gif":
- ff = FFmpeg(
- inputs={path_img_file: "-benchmark -framerate 1"},
- outputs={path_vid_file: f'-filter_complex "split[v1][v2]; [v1]palettegen=stats_mode=full [palette]; [v2][palette]paletteuse=dither=sierra2_4a [v3]; [v3]setpts={pts}*PTS [v4]; [v4]minterpolate=fps={int(ssii_video_fps)}:mi_mode=mci:mc_mode=aobmc:me_mode=bidir:vsbmc=1"'}
- )
- else:
- ff = FFmpeg(
- inputs={path_img_file: "-benchmark -framerate 1"},
- outputs={path_vid_file: f'-filter_complex "setpts={pts}*PTS [v4]; [v4]minterpolate=fps={int(ssii_video_fps)}:mi_mode=mci:mc_mode=aobmc:me_mode=bidir:vsbmc=1"'}
- )
- else:
- if ssii_video_format == "gif":
- ff = FFmpeg(
- inputs={path_img_file: f"-benchmark -framerate {int(ssii_video_fps)}"},
- outputs={path_vid_file: '-filter_complex "split[v1][v2]; [v1]palettegen=stats_mode=full [palette]; [v2][palette]paletteuse=dither=sierra2_4a"'}
- )
- else:
- ff = FFmpeg(
- inputs={path_img_file: f"-benchmark -framerate {int(ssii_video_fps)}"},
- outputs={path_vid_file: None}
- )
- ff.run()
-
- # Back to original numbering
- for (batch_no, name_org, name_seq) in reversed(p.intermed_files):
- path_name_org = os.path.join(p.intermed_outpath, name_org)
- path_name_seq = os.path.join(p.intermed_outpath, name_seq)
- os.replace(path_name_seq, path_name_org)
- logger.debug(f"replace {path_name_seq} / {path_name_org}")
+ # Make video for last batch_count
+ make_video(p, ssii_is_active, ssii_intermediate_type, ssii_every_n, ssii_stop_at_n, ssii_video, ssii_video_format, ssii_video_fps, ssii_video_hires, ssii_smooth, ssii_seconds, ssii_debug)