diff --git a/iz_helpers/InfZoomConfig.py b/iz_helpers/InfZoomConfig.py index 44d9783..46167c1 100644 --- a/iz_helpers/InfZoomConfig.py +++ b/iz_helpers/InfZoomConfig.py @@ -30,11 +30,11 @@ class InfZoomConfig(): overmask:int outpaintStrategy: str blend_image:Image - blend_mode:str + blend_mode:int #0: None, 1: Blend, 2: AlphaComposite, 3: LumaWipe blend_gradient_size:int blend_invert_do:bool blend_color:str - audio_filename=None, + audio_filename:str=None inpainting_denoising_strength:float=1 inpainting_full_res:int =0 inpainting_padding:int=0 diff --git a/iz_helpers/run.py b/iz_helpers/run.py index a70c9a9..3dd64f7 100644 --- a/iz_helpers/run.py +++ b/iz_helpers/run.py @@ -98,6 +98,625 @@ class InfZoomer: fnOutpaintMainFrames: Callable fnInterpolateFrames: Callable + def create_zoom(self): + for i in range(self.C.batchcount): + print(f"Batch {i+1}/{self.C.batchcount}") + result = self.create_zoom_single() + return result + + def create_zoom_single(self): + + self.main_frames.append(self.prepareInitImage()) + + load_model_from_setting("infzoom_inpainting_model", self.C.progress, "Loading Model for inpainting/img2img: ") + + processed = self.fnOutpaintMainFrames() + + if (self.C.upscale_do): + self.doUpscaling() + + if self.C.video_zoom_mode: + self.main_frames = self.main_frames[::-1] + + if not self.outerZoom: + self.contVW = ContinuousVideoWriter( + self.out_config["video_filename"], + self.main_frames[0], + self.C.video_frame_rate, + int(self.C.video_start_frame_dupe_amount), + self.C.video_ffmpeg_opts + ) + + self.fnInterpolateFrames() # changes main_frame and writes to video + + if self.C.audio_filename is not None: + self.out_config["video_filename"] = add_audio_to_video(self.out_config["video_filename"], self.C.audio_filename, str.replace(self.out_config["video_filename"], ".mp4", "_audio.mp4"), find_ffmpeg_binary()) + + print("Video saved in: " + os.path.join(script_path, self.out_config["video_filename"])) + + return ( + self.out_config["video_filename"], + self.main_frames, + processed.js(), + plaintext_to_html(processed.info), + plaintext_to_html(""), + ) + + def doUpscaling(self): + for idx,mf in enumerate(self.main_frames): + print (f"\033[KInfZoom: Upscaling mainframe: {idx} \r",end="") + self.main_frames[idx]=do_upscaleImg(mf, self.C.upscale_do, self.C.upscaler_name, self.C.upscale_by) + + self.mask_width = math.trunc(self.mask_width*self.C.upscale_by) + self.mask_height = math.trunc(self.mask_height *self.C.upscale_by) + + if self.C.outpaintStrategy == "Corners": + self.width = self.main_frames[0].width-2*self.mask_width + self.height = self.main_frames[0].height-2*self.mask_height + else: + self.width = self.main_frames[0].width + self.height = self.main_frames[0].height + + def prepareInitImage(self) -> Image: + if self.C.custom_init_image: + current_image = Image.new(mode="RGBA", size=(self.width, self.height)) + current_image = current_image.convert("RGB") + current_image = cv2_to_pil(cv2.resize( + pil_to_cv2(self.C.custom_init_image), + (self.width, self.height), + interpolation=cv2.INTER_AREA + ) + ) + self.save2Collect(current_image, f"init_custom.png") + else: + if self.prompt_images[min(k for k in self.prompt_images.keys() if k >= 0)] == "": + load_model_from_setting("infzoom_txt2img_model", self.C.progress, "Loading Model for txt2img: ") + processed, self.current_seed = self.renderFirstFrame() + if len(processed.images) > 0: + current_image = processed.images[0] + self.save2Collect(current_image, f"init_txt2img.png") + else: + print("using image 0 as Initial keyframe") + current_image = open_image(self.prompt_images[min(k for k in self.prompt_images.keys() if k >= 0)]) + current_image = cv2_to_pil(cv2.resize( + pil_to_cv2(current_image), + (self.width, self.height), + interpolation=cv2.INTER_AREA + ) + ) + self.save2Collect(current_image, f"init_custom.png") + + return current_image + + def renderFirstFrame(self): + pr = self.getInitialPrompt() + + return renderTxt2Img( + f"{self.C.common_prompt_pre}\n{pr}\n{self.C.common_prompt_suf}".strip(), + self.C.negative_prompt, + self.C.sampler, + self.C.num_inference_steps, + self.C.guidance_scale, + self.current_seed, + self.width, + self.height + ) + + def getInitialPrompt(self): + return self.prompts[min(k for k in self.prompts.keys() if k >= 0)] + + + def outpaint_steps_cornerStrategy(self): + current_image = self.main_frames[-1] + + # just 30 radius to get inpaint connected between outer and innter motive + masked_image = create_mask_with_circles( + current_image, + self.mask_width, self.mask_height, + overmask=self.C.overmask, + radius=min(self.mask_height,self.mask_height)*0.2 + ) + + new_width= masked_image.width + new_height=masked_image.height + + outpaint_steps=self.C.num_outpainting_steps + for i in range(outpaint_steps): + print (f"Outpaint step: {str(i + 1)}/{str(outpaint_steps)} Seed: {str(self.current_seed)}") + current_image = self.main_frames[-1] + + #keyframes are not outpainted + paste_previous_image = not self.prompt_image_is_keyframe[(i + 1)] + print(f"paste_prev_image: {paste_previous_image} {i} {i + 1}") + + if self.C.custom_exit_image and ((i + 1) == outpaint_steps): + current_image = cv2_to_pil(cv2.resize( + pil_to_cv2(self.C.custom_exit_image), + (self.C.width, self.C.height), + interpolation=cv2.INTER_AREA + ) + ) + + if 0 == self.outerZoom: + exit_img = current_image.convert("RGB") + + self.save2Collect(current_image, self.out_config, f"exit_img.png") + paste_previous_image = False + else: + if self.prompt_images[max(k for k in self.prompt_images.keys() if k <= (i + 1))] == "": + expanded_image = cv2_to_pil( + cv2.resize(pil_to_cv2(current_image), + (new_width,new_height), + interpolation=cv2.INTER_AREA + ) + ) + + #expanded_image = Image.new("RGB",(new_width,new_height),"black") + expanded_image.paste(current_image, (self.mask_width,self.mask_height)) + pr = self.prompts[max(k for k in self.prompts.keys() if k <= i)] + + processed, newseed = renderImg2Img( + f"{self.C.common_prompt_pre}\n{pr}\n{self.C.common_prompt_suf}".strip(), + self.C.negative_prompt, + self.C.sampler, + self.C.num_inference_steps, + self.C.guidance_scale, + -1, # try to avoid massive repeatings: self.current_seed, + new_width, #outpaintsizeW + new_height, #outpaintsizeH + expanded_image, + masked_image, + self.C.inpainting_denoising_strength, + self.C.inpainting_mask_blur, + self.C.inpainting_fill_mode, + False, # self.C.inpainting_full_res, + 0 #self.C.inpainting_padding, + ) + + if len(processed.images) > 0: + expanded_image = processed.images[0] + zoomed_img = cv2_to_pil(cv2.resize( + pil_to_cv2(expanded_image), + (self.width,self.height), + interpolation=cv2.INTER_AREA + ) + ) + # + else: + # use prerendered image, known as keyframe. Resize to target size + print(f"image {i + 1} is a keyframe: {not paste_previous_image}") + current_image = open_image(self.prompt_images[(i + 1)]) + current_image = resize_and_crop_image(current_image, self.width, self.height).convert("RGBA") + + # if keyframe is last frame, use it as exit image + if (not paste_previous_image) and ((i + 1) == outpaint_steps): + exit_img = current_image + print("using keyframe as exit image") + else: + # apply predefined or generated alpha mask to current image: + if self.prompt_alpha_mask_images[max(k for k in self.prompt_alpha_mask_images.keys() if k <= (i + 1))] != "": + current_image_amask = open_image(self.prompt_alpha_mask_images[max(k for k in self.prompt_alpha_mask_images.keys() if k <= (i + 1))]) + else: + current_image_gradient_ratio = (self.C.blend_gradient_size / 100) + current_image_amask = draw_gradient_ellipse(current_image.width, current_image.height, current_image_gradient_ratio, 0.0, 2.5) + current_image = apply_alpha_mask(current_image, current_image_amask) + self.main_frames.append(current_image) + self.save2Collect(current_image, f"key_frame_{i + 1}.png") + + if paste_previous_image and i > 0: + if self.prompt_alpha_mask_images[max(k for k in self.prompt_alpha_mask_images.keys() if k <= (i + 1))] != "": + current_image_amask = open_image(self.prompt_alpha_mask_images[max(k for k in self.prompt_alpha_mask_images.keys() if k <= (i + 1))]) + else: + current_image_gradient_ratio = (self.C.blend_gradient_size / 100) + current_image_amask = draw_gradient_ellipse(self.main_frames[-1].width, self.main_frames[-1].height, current_image_gradient_ratio, 0.0, 2.5) + current_image = apply_alpha_mask(self.main_frames[-1], current_image_amask) + expanded_image.paste(current_image, (self.mask_width,self.mask_height)) + zoomed_img = cv2_to_pil(cv2.resize( + pil_to_cv2(expanded_image), + (self.width,self.height), + interpolation=cv2.INTER_AREA + ) + ) + + if self.outerZoom: + self.main_frames[-1] = expanded_image # replace small image + self.save2Collect(processed.images[0], f"outpaint_step_{i}.png") + + if (i < outpaint_steps-1): + self.main_frames.append(zoomed_img) # prepare next frame with former content + + else: + zoomed_img = cv2_to_pil(cv2.resize( + expanded_image, + (self.width,self.height), + interpolation=cv2.INTER_AREA + ) + ) + self.main_frames.append(zoomed_img) + processed.images[0]=self.main_frames[-1] + self.save2Collect(processed.images[0], f"outpaint_step_{i}.png") + + if exit_img is not None: + self.main_frames.append(exit_img) + + return processed + + + def outpaint_steps_v8hid(self): + + self.main_frames = [self.C.init_img.convert("RGBA")] + prev_image = self.C.init_img.convert("RGBA") + exit_img = self.C.custom_exit_image.convert("RGBA") if self.C.custom_exit_image else None + + for i in range(self.C.num_outpainting_steps): + print (f"Outpaint step: {str(i + 1)} / {str(self.C.num_outpainting_steps)} Seed: {str(self.current_seed)}") + + current_image = self.main_frames[-1] + current_image = shrink_and_paste_on_blank( + current_image, self.mask_width, self.mask_height + ) + + mask_image = np.array(current_image)[:, :, 3] + mask_image = Image.fromarray(255 - mask_image).convert("RGB") + + #keyframes are not inpainted + paste_previous_image = not self.prompt_image_is_keyframe[(i + 1)] + print(f"paste_prev_image: {paste_previous_image} {i} {i + 1}") + + if self.C.custom_exit_image and ((i + 1) == self.C.num_outpainting_steps): + current_image = cv2_to_pil( + cv2.resize( pil_to_cv2( + self.C.custom_exit_image), + (self.width, self.height), + interpolation=cv2.INTER_AREA) + ) + exit_img = current_image.convert("RGB") + # print("using Custom Exit Image") + self.save2Collect(current_image, f"exit_img.png") + + paste_previous_image = False + else: + if self.prompt_images[max(k for k in self.prompt_images.keys() if k <= (i + 1))] == "": + pr = self.prompts[max(k for k in self.prompts.keys() if k <= i)] + processed, seed = renderImg2Img( + f"{self.C.common_prompt_pre}\n{pr}\n{self.C.common_prompt_suf}".strip(), + self.C.negative_prompt, + self.C.sampler, + self.C.num_inference_steps, + self.C.guidance_scale, + self.current_seed, + self.width, + self.height, + current_image, + mask_image, + self.C.inpainting_denoising_strength, + self.C.inpainting_mask_blur, + self.C.inpainting_fill_mode, + self.C.inpainting_full_res, + self.C.inpainting_padding, + ) + + if len(processed.images) > 0: + self.main_frames.append(processed.images[0].convert("RGB")) + self.save2Collect(processed.images[0], f"outpain_step_{i}.png") + else: + # use prerendered image, known as keyframe. Resize to target size + print(f"image {i + 1} is a keyframe: {not paste_previous_image}") + current_image = open_image(self.prompt_images[(i + 1)]) + current_image = resize_and_crop_image(current_image, self.width, self.height).convert("RGBA") + + # if keyframe is last frame, use it as exit image + if (not paste_previous_image) and ((i + 1) == self.C.outpaint_steps): + exit_img = current_image + print("using keyframe as exit image") + else: + # apply predefined or generated alpha mask to current image: + if self.prompt_alpha_mask_images[max(k for k in self.prompt_alpha_mask_images.keys() if k <= (i + 1))] != "": + current_image_amask = open_image(self.prompt_alpha_mask_images[max(k for k in self.prompt_alpha_mask_images.keys() if k <= (i + 1))]) + else: + current_image_gradient_ratio = (self.C.blend_gradient_size / 100) + current_image_amask = draw_gradient_ellipse(current_image.width, current_image.height, current_image_gradient_ratio, 0.0, 2.5) + current_image = apply_alpha_mask(current_image, current_image_amask) + self.main_frames.append(current_image) + self.save2Collect(current_image, f"key_frame_{i + 1}.png") + + # TODO: seed behavior + + # paste current image with alpha layer on previous image to merge : paste on i + if paste_previous_image and i > 0: + # apply predefined or generated alpha mask to current image: + # current image must be redefined as most current image in frame stack + # use previous image alpha mask if available + if self.prompt_alpha_mask_images[max(k for k in self.prompt_alpha_mask_images.keys() if k <= (i + 1))] != "": + current_image_amask = open_image(self.prompt_alpha_mask_images[max(k for k in self.prompt_alpha_mask_images.keys() if k <= (i + 1))]) + else: + current_image_gradient_ratio = (self.C.blend_gradient_size / 100) + current_image_amask = draw_gradient_ellipse(self.main_frames[i + 1].width, self.main_frames[i + 1].height, current_image_gradient_ratio, 0.0, 2.5) + current_image = apply_alpha_mask(self.main_frames[i + 1], current_image_amask) + + #handle previous image alpha layer + #prev_image = (main_frames[i] if main_frames[i] else main_frames[0]) + ## apply available alpha mask of previous image (inverted) + if self.prompt_alpha_mask_images[max(k for k in self.prompt_alpha_mask_images.keys() if k <= (i))] != "": + prev_image_amask = open_image(self.prompt_alpha_mask_images[max(k for k in self.prompt_alpha_mask_images.keys() if k <= (i))]) + else: + prev_image_gradient_ratio = (self.C.blend_gradient_size / 100) + prev_image_amask = draw_gradient_ellipse(prev_image.width, prev_image.height, prev_image_gradient_ratio, 0.0, 2.5) + #prev_image = apply_alpha_mask(prev_image, prev_image_amask, invert = True) + + # merge previous image with current image + corrected_frame = crop_inner_image( + current_image, self.mask_width, self.mask_height + ) + prev = Image.new(prev_image.mode, (self.width, self.height), (255,255,255,255)) + prev.paste(apply_alpha_mask(self.main_frames[i], prev_image_amask)) + corrected_frame.paste(prev, mask=prev) + + self.main_frames[i] = corrected_frame + self.save2Collect(corrected_frame, f"main_frame_gradient_{i + 0}") + + if exit_img is not None: + self.main_frames.append(exit_img) + return processed + + def calculate_interpolation_steps_linear(self, original_size, target_size, steps): + width, height = original_size + target_width, target_height = target_size + + if width <= 0 or height <= 0 or target_width <= 0 or target_height <= 0 or steps <= 0: + return [] + + width_step = (width - target_width) / (steps+1) #+1 enforce steps BETWEEN keyframe, dont reach the target size. interval like [] + height_step = (height - target_height) / (steps+1) + + scaling_steps = [(round(width - i * width_step), round(height - i * height_step)) for i in range(1,steps+1)] + #scaling_steps.insert(0,original_size) # initial size is in the list + return scaling_steps + + + def interpolateFramesOuterZoom(self): + + if 0 == self.C.video_zoom_mode: + current_image = self.main_frames[0] + next_image = self.main_frames[1] + elif 1 == self.C.video_zoom_mode: + current_image = self.main_frames[-1] + next_image = self.main_frames[-2] + else: + raise ValueError("unsupported Zoom mode in INfZoom") + + outzoomSize = (self.width+self.mask_width*2, self.height+self.mask_height*2) + target_size = (self.width, self.height) # mask border, hide blipping + + scaling_steps = self.calculate_interpolation_steps_linear(outzoomSize, target_size, self.num_interpol_frames) + print(f"Before: {scaling_steps}, length: {len(scaling_steps)}") + + # all sizes EVEN + for i,s in enumerate(scaling_steps): + scaling_steps[i] = (s[0]+s[0]%2, s[1]+s[1]%2) + + print(f"After EVEN: {scaling_steps}, length: {len(scaling_steps)}") + for s in scaling_steps: + print(f"Ratios: {str(s[0]/s[1])}",end=";") + + self.contVW = ContinuousVideoWriter(self.out_config["video_filename"], + self.cropCenterTo(current_image,(target_size)), + self.cropCenterTo(next_image,(target_size)), + self.C.video_frame_rate,int(self.C.video_start_frame_dupe_amount-1), + self.C.video_ffmpeg_opts, + self.C.blend_invert_do, + self.C.blend_image, + self.C.blend_mode, + self.C.blend_gradient_size, + self.C.blend_color) + + for i in range(len(self.main_frames)): + if 0 == self.C.video_zoom_mode: + current_image = self.main_frames[0+i] + previous_image = self.main_frames[i-1] + else: + current_image = self.main_frames[-1-i] + previous_image = self.main_frames[0-i] + + lastFrame = self.cropCenterTo(current_image,target_size) + nextToLastFrame = self.cropCenterTo(previous_image,target_size) + + if self.C.blend_mode == 0: + self.contVW.append([lastFrame]) + + cv2_image = pil_to_cv2(current_image) + + # Resize and crop using OpenCV2 + for j in range(self.num_interpol_frames): + print(f"\033[KInfZoom: Interpolate frame(CV2): main/inter: {i}/{j} \r", end="") + resized_image = cv2.resize( + cv2_image, + (scaling_steps[j][0], scaling_steps[j][1]), + interpolation=cv2.INTER_AREA + ) + cropped_image_cv2 = cv2_crop_center(resized_image, target_size) + cropped_image_pil = cv2_to_pil(cropped_image_cv2) + + self.contVW.append([cropped_image_pil]) + lastFrame = cropped_image_pil + + self.contVW.finish(lastFrame, + nextToLastFrame, + int(self.C.video_last_frame_dupe_amount), + self.C.blend_invert_do, + self.C.blend_image, + self.C.blend_mode, + self.C.blend_gradient_size, + self.C.blend_color) + + """ USING PIL: + for i in range(len(self.main_frames)): + if 0 == self.C.video_zoom_mode: + current_image = self.main_frames[0+i] + else: + current_image = self.main_frames[-1-i] + + self.contVW.append([ + self.cropCenterTo(current_image,(self.width, self.height)) + ]) + + # interpolation steps between 2 inpainted images (=sequential zoom and crop) + for j in range(self.num_interpol_frames - 1): + print (f"\033[KInfZoom: Interpolate frame: main/inter: {i}/{j} \r",end="") + #todo: howto zoomIn when writing each frame; self.main_frames are inverted, howto interpolate? + scaled_image = current_image.resize(scaling_steps[j], Image.LANCZOS) + cropped_image = self.cropCenterTo(scaled_image,(self.width, self.height)) + + self.contVW.append([cropped_image]) + """ + + def interpolateFramesSmallCenter(self): + + if self.C.video_zoom_mode: + firstImage = self.main_frames[0] + nextImage = self.main_frames[1] + else: + firstImage = self.main_frames[-1] + nextImage = self.main_frames[-2] + + self.contVW = ContinuousVideoWriter(self.out_config["video_filename"], + (firstImage,(self.width,self.height)), + (nextImage,(self.width,self.height)), + self.C.video_frame_rate,int(self.C.video_start_frame_dupe_amount), + self.C.video_ffmpeg_opts, + self.C.blend_invert_do, + self.C.blend_image, + self.C.blend_mode, + self.C.blend_gradient_size, + self.C.blend_color) + + for i in range(len(self.main_frames) - 1): + # interpolation steps between 2 inpainted images (=sequential zoom and crop) + for j in range(self.num_interpol_frames - 1): + + print (f"\033[KInfZoom: Interpolate frame: main/inter: {i}/{j} \r",end="") + #todo: howto zoomIn when writing each frame; self.main_frames are inverted, howto interpolate? + if self.C.video_zoom_mode: + current_image = self.main_frames[i + 1] + else: + current_image = self.main_frames[i + 1] + + + interpol_image = current_image + self.save2Collect(interpol_image, f"interpol_img_{i}_{j}].png") + + interpol_width = math.ceil( + ( 1 - (1 - 2 * self.mask_width / self.width) **(1 - (j + 1) / self.num_interpol_frames) ) + * self.width / 2 + ) + + interpol_height = math.ceil( + ( 1 - (1 - 2 * self.mask_height / self.height) ** (1 - (j + 1) / self.num_interpol_frames) ) + * self.height/2 + ) + + interpol_image = interpol_image.crop( + ( + interpol_width, + interpol_height, + self.width - interpol_width, + self.height - interpol_height, + ) + ) + + interpol_image = interpol_image.resize((self.width, self.height)) + self.save2Collect(interpol_image, f"interpol_resize_{i}_{j}.png") + + # paste the higher resolution previous image in the middle to avoid drop in quality caused by zooming + interpol_width2 = math.ceil( + (1 - (self.width - 2 * self.mask_width) / (self.width - 2 * interpol_width)) + / 2 * self.width + ) + + interpol_height2 = math.ceil( + (1 - (self.height - 2 * self.mask_height) / (self.height - 2 * interpol_height)) + / 2 * self.height + ) + + prev_image_fix_crop = shrink_and_paste_on_blank( + self.main_frames[i], interpol_width2, interpol_height2 + ) + + interpol_image.paste(prev_image_fix_crop, mask=prev_image_fix_crop) + self.save2Collect(interpol_image, f"interpol_prevcrop_{i}_{j}.png") + + self.contVW.append([interpol_image]) + + self.contVW.append([current_image]) + + + def prepare_output_path(self): + isCollect = shared.opts.data.get("infzoom_collectAllResources", False) + output_path = shared.opts.data.get("infzoom_outpath", "outputs") + + save_path = os.path.join( + output_path, shared.opts.data.get("infzoom_outSUBpath", "infinite-zooms") + ) + + if isCollect: + save_path = os.path.join(save_path, "iz_collect" + str(int(time.time()))) + + if not os.path.exists(save_path): + os.makedirs(save_path) + + video_filename = os.path.join( + save_path, "infinite_zoom_" + str(int(time.time())) + ".mp4" + ) + + return { + "isCollect": isCollect, + "save_path": save_path, + "video_filename": video_filename, + } + + + def save2Collect(self, img, name): + if self.out_config["isCollect"]: + img.save(f'{self.out_config["save_path"]}/{name}.png') + + + def frame2Collect(self,all_frames): + self.save2Collect(all_frames[-1], self.out_config, f"frame_{len(all_frames)}") + + + def frames2Collect(self, all_frames): + for i, f in enumerate(all_frames): + self.save2Collect(f, self.out_config, f"frame_{i}") + + + def crop_inner_image(self, outpainted_img, width_offset, height_offset): + width, height = outpainted_img.size + + center_x, center_y = int(width / 2), int(height / 2) + + # Crop the image to the center + cropped_img = outpainted_img.crop( + ( + center_x - width_offset, + center_y - height_offset, + center_x + width_offset, + center_y + height_offset, + ) + ) + prev_step_img = cropped_img.resize((width, height), resample=Image.LANCZOS) + # resized_img = resized_img.filter(ImageFilter.SHARPEN) + + return prev_step_img + + def cropCenterTo(self, im: Image, toSize: tuple[int,int]): + width, height = im.size + left = (width - toSize[0])//2 + top = (height - toSize[1])//2 + right = (width + toSize[0])//2 + bottom = (height + toSize[1])//2 + return im.crop((left, top, right, bottom)) + +########################################################################################################################## def outpaint_steps( width, height, @@ -317,6 +936,9 @@ def create_zoom( upscale_do, upscaler_name, upscale_by, + overmask, + outpaintStrategy, + outpaint_amount_px, blend_image, blend_mode, blend_gradient_size, @@ -394,6 +1016,9 @@ def create_zoom_single( upscale_do, upscaler_name, upscale_by, + overmask, + outpaintStrategy, + outpaint_amount_px, blend_image, blend_mode, blend_gradient_size, @@ -641,7 +1266,7 @@ def create_zoom_single( plaintext_to_html(processed.info), plaintext_to_html(""), ) - +################################################################################################################# def create_mask_with_circles(original_image, border_width, border_height, overmask: int, radius=4): # Create a new image with border and draw a mask new_width = original_image.width + 2 * border_width diff --git a/iz_helpers/run_interface.py b/iz_helpers/run_interface.py index 9403128..f796fab 100644 --- a/iz_helpers/run_interface.py +++ b/iz_helpers/run_interface.py @@ -32,6 +32,12 @@ def createZoom( overmask:int, outpaintStrategy:str, outpaint_amount_px: int, + blend_image:Image, + blend_mode:int, + blend_gradient_size:int, + blend_invert_do:bool, + blend_color:str, + audio_filename:str, inpainting_denoising_strength:float=1, inpainting_full_res:int =0, inpainting_padding:int=0, @@ -42,7 +48,7 @@ def createZoom( prompts_array, common_prompt_suf, negative_prompt, - num_outpainting_steps, + num_outpainting_steps if custom_exit_image == None else (num_outpainting_steps + 1), guidance_scale, num_inference_steps, custom_init_image, @@ -65,6 +71,12 @@ def createZoom( upscale_by, overmask, outpaintStrategy, + blend_image, + blend_mode, + blend_gradient_size, + blend_invert_do, + blend_color, + audio_filename, inpainting_denoising_strength, inpainting_full_res, inpainting_padding, diff --git a/iz_helpers/ui.py b/iz_helpers/ui.py index 2a7a32d..10eb1ff 100644 --- a/iz_helpers/ui.py +++ b/iz_helpers/ui.py @@ -386,7 +386,7 @@ Our best experience and trade-off is the R-ERSGAn4x upscaler. ) generate_btn.click( - fn=wrap_gradio_gpu_call(create_zoom, extra_outputs=[None, "", ""]), + fn=wrap_gradio_gpu_call(createZoom, extra_outputs=[None, "", ""]), inputs=[ main_common_prompt_pre, main_prompts, diff --git a/iz_helpers/video.py b/iz_helpers/video.py index 21283cc..04a469a 100644 --- a/iz_helpers/video.py +++ b/iz_helpers/video.py @@ -74,7 +74,7 @@ class ContinuousVideoWriter: _writer = None - def __init__(self, file_path, initframe, fps, start_frame_dupe_amount=15, video_ffmpeg_opts="" ): + def __init__(self, file_path, initframe, nextframe, fps, start_frame_dupe_amount=15, video_ffmpeg_opts="", blend_invert: bool = False, blend_image= None, blend_type:int = 0, blend_gradient_size: int = 63, blend_color = "#ffff00" ): """ Writes initial frame to a new mp4 video file :param file_path: Path to output video, must end with .mp4 @@ -87,7 +87,19 @@ class ContinuousVideoWriter: ffopts= video_ffmpeg_opts.split(" ") writer = imageio.get_writer(file_path, fps=fps, macro_block_size=None, ffmpeg_params=ffopts) - start_frames = [initframe] * start_frame_dupe_amount + # Duplicate the start frames + if blend_type != 0: + if blend_image is None: + blend_image = draw_gradient_ellipse(*initframe.size, blend_gradient_size) + + if blend_type == 1: + start_frames = blend_images(initframe, nextframe, math.ceil(start_frame_dupe_amount), blend_invert) + elif blend_type == 2: + start_frames = alpha_composite_images(initframe, nextframe, blend_image, math.ceil(start_frame_dupe_amount), blend_invert) + elif blend_type == 3: + start_frames = PSLumaWipe_images2(initframe, nextframe, blend_image, math.ceil(start_frame_dupe_amount), blend_invert,blend_color) + else: + start_frames = [initframe] * start_frame_dupe_amount for f in start_frames: writer.append_data(np.array(f)) self._writer = writer @@ -100,13 +112,22 @@ class ContinuousVideoWriter: for i,f in enumerate(frames): self._writer.append_data(np.array(f)) - def finish(self, frame, last_frame_dupe_amount=30 ): + def finish(self, exitframe, next_to_last_frame, last_frame_dupe_amount=30, blend_invert: bool = False, blend_image= None, blend_type:int = 0, blend_gradient_size: int = 63, blend_color = "#ffff00" ): """ Closes the file writer. """ - for i in range(last_frame_dupe_amount): - self._writer.append_data(np.array(frame)) - + # Duplicate the exit frames + if blend_type != 0: + if blend_type == 1: + end_frames = blend_images(next_to_last_frame, exitframe, math.ceil(last_frame_dupe_amount), blend_invert) + elif blend_type == 2: + end_frames = alpha_composite_images(next_to_last_frame, exitframe, blend_image, math.ceil(last_frame_dupe_amount), blend_invert) + elif blend_type == 3: + end_frames = PSLumaWipe_images2(next_to_last_frame, exitframe, blend_image, math.ceil(last_frame_dupe_amount), blend_invert, blend_color) + else: + end_frames = [exitframe] * last_frame_dupe_amount + for f in end_frames: + self._writer.append_data(np.array(f)) self._writer.close() def add_audio_to_video(video_path, audio_path, output_path, ffmpeg_location = 'ffmpeg'):