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)