diff --git a/README.md b/README.md index 97eaa9c..1e03da4 100644 --- a/README.md +++ b/README.md @@ -5,12 +5,19 @@ #### This extension allows you to output edited videos using ebsynth.(AE is not required) ## Example +- The following sample is raw output of this extension. #### sample 1
#### sample 2 +#### sample 3 blend background +- person : masterpiece, best quality, masterpiece, 1girl, masterpiece, best quality,anime screencap, anime style +- background : cyberpunk, factory, room ,anime screencap, anime style +It is also possible to blend with your favorite videos. + + ## Installation - Install [ffmpeg](https://ffmpeg.org/) for your operating system (https://www.geeksforgeeks.org/how-to-install-ffmpeg-on-windows/) @@ -37,7 +44,9 @@ All frames of the video and mask images for all frames are generated. #### Stage 2 In the implementation of this extension, the keyframe interval is chosen to be shorter where there is a lot of motion and longer where there is little motion. +If the animation breaks up, increase the keyframe, if it flickers, decrease the keyframe. First, generate one time with the default settings and go straight ahead without worrying about the result. + #### Stage 3 Select one of the keyframes, throw it to img2img, and run [Interrogate DeepBooru]. @@ -45,19 +54,21 @@ Delete unwanted words such as blur from the displayed prompt. Fill in the rest of the settings as you would normally do for image generation. Here is the settings I used. -- Sampling method : DDIM +- Sampling method : Euler a - Sampling Steps : 50 - Width : 960 - Height : 512 - CFG Scale : 20 -- Denoising strength : 0.35 +- Denoising strength : 0.2 Here is the settings for extension. -- Img2Img Repeat Count : 1 ( or 3 with Euler a / lower Denoising strength) +- Mask Mode(Override img2img Mask mode) : Normal +- Img2Img Repeat Count : 5 +- Add N to seed when repeating : 1 - use Face Crop img2img : True - Face Detection Method : YuNet - Max Crop Size : 1024 -- Face Denoising Strength : 0.35 +- Face Denoising Strength : 0.25 - Face Area Magnification : 1.5 (The larger the number, the closer to the model's painting style, but the more likely it is to shift when merged with the body.) - Enable Face Prompt : False diff --git a/ebsynth_utility.py b/ebsynth_utility.py index 66d2da5..fb15977 100644 --- a/ebsynth_utility.py +++ b/ebsynth_utility.py @@ -5,10 +5,12 @@ from modules.ui import plaintext_to_html import cv2 import glob -from extensions.ebsynth_utility.stage1 import ebsynth_utility_stage1 +from extensions.ebsynth_utility.stage1 import ebsynth_utility_stage1,ebsynth_utility_stage1_invert from extensions.ebsynth_utility.stage2 import ebsynth_utility_stage2 from extensions.ebsynth_utility.stage5 import ebsynth_utility_stage5 from extensions.ebsynth_utility.stage7 import ebsynth_utility_stage7 +from extensions.ebsynth_utility.stage8 import ebsynth_utility_stage8 + def x_ceiling(value, step): return -(-value // step) * step @@ -21,11 +23,12 @@ def dump_dict(string, d:dict): class debug_string: txt = "" def print(self, comment): + print(comment) self.txt += comment + '\n' def to_string(self): return self.txt -def ebsynth_utility_process(stage_index: int, project_dir:str, original_movie_path:str, key_min_gap:int, key_max_gap:int, key_th:float, key_add_last_frame:bool, blend_rate:float, export_type:str, no_mask_mode:bool): +def ebsynth_utility_process(stage_index: int, project_dir:str, original_movie_path:str, tb_use_fast_mode:bool, tb_use_jit:bool, key_min_gap:int, key_max_gap:int, key_th:float, key_add_last_frame:bool, blend_rate:float, export_type:str, bg_src:str, bg_type:str, mask_blur_size:int, mask_mode:str): args = locals() info = "" info = dump_dict(info, args) @@ -43,22 +46,40 @@ def ebsynth_utility_process(stage_index: int, project_dir:str, original_movie_pa dbg.print("original_movie_path not found") return process_end( dbg, info ) + is_invert_mask = False + if mask_mode == "Invert": + is_invert_mask = True + frame_path = os.path.join(project_dir , "video_frame") frame_mask_path = os.path.join(project_dir, "video_mask") - org_key_path = os.path.join(project_dir, "video_key") - img2img_key_path = os.path.join(project_dir, "img2img_key") - img2img_upscale_key_path = os.path.join(project_dir, "img2img_upscale_key") - if no_mask_mode: + if is_invert_mask: + inv_path = os.path.join(project_dir, "inv") + os.makedirs(inv_path, exist_ok=True) + + org_key_path = os.path.join(inv_path, "video_key") + img2img_key_path = os.path.join(inv_path, "img2img_key") + img2img_upscale_key_path = os.path.join(inv_path, "img2img_upscale_key") + else: + org_key_path = os.path.join(project_dir, "video_key") + img2img_key_path = os.path.join(project_dir, "img2img_key") + img2img_upscale_key_path = os.path.join(project_dir, "img2img_upscale_key") + + if mask_mode == "None": frame_mask_path = "" + project_args = [project_dir, original_movie_path, frame_path, frame_mask_path, org_key_path, img2img_key_path, img2img_upscale_key_path] if stage_index == 0: - ebsynth_utility_stage1(dbg, project_args) + ebsynth_utility_stage1(dbg, project_args, tb_use_fast_mode, tb_use_jit, is_invert_mask) + if is_invert_mask: + inv_mask_path = os.path.join(inv_path, "inv_video_mask") + ebsynth_utility_stage1_invert(dbg, frame_mask_path, inv_mask_path) + elif stage_index == 1: - ebsynth_utility_stage2(dbg, project_args, key_min_gap, key_max_gap, key_th, key_add_last_frame) + ebsynth_utility_stage2(dbg, project_args, key_min_gap, key_max_gap, key_th, key_add_last_frame, is_invert_mask) elif stage_index == 2: sample_image = glob.glob( os.path.join(frame_path , "*.png" ) )[0] @@ -78,18 +99,24 @@ def ebsynth_utility_process(stage_index: int, project_dir:str, original_movie_pa dbg.print("1. Go to img2img tab") dbg.print("2. Select [ebsynth utility] in the script combo box") dbg.print("3. Fill in the \"Project directory\" field with [" + project_dir + "]" ) - dbg.print("4. I recommend to fill in the \"Width\" field with [" + str(img_width) + "]" ) - dbg.print("5. I recommend to fill in the \"Height\" field with [" + str(img_height) + "]" ) - dbg.print("6. I recommend to fill in the \"Denoising strength\" field with lower than 0.35" ) + dbg.print("4. Select in the \"Mask Mode(Override img2img Mask mode)\" field with [" + ("Invert" if is_invert_mask else "Normal") + "]" ) + dbg.print("5. I recommend to fill in the \"Width\" field with [" + str(img_width) + "]" ) + dbg.print("6. I recommend to fill in the \"Height\" field with [" + str(img_height) + "]" ) + dbg.print("7. I recommend to fill in the \"Denoising strength\" field with lower than 0.35" ) dbg.print(" (It's okay to put a Large value in \"Face Denoising Strength\")") - dbg.print("7. Fill in the remaining configuration fields of img2img. No image and mask settings are required.") - dbg.print("8. Generate") + dbg.print("8. Fill in the remaining configuration fields of img2img. No image and mask settings are required.") + dbg.print("9. Drop any image onto the img2img main screen. This is necessary to avoid errors, but does not affect the results of img2img.") + dbg.print("10. Generate") dbg.print("(Images are output to [" + img2img_key_path + "])") dbg.print("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!") return process_end( dbg, "" ) elif stage_index == 3: sample_image = glob.glob( os.path.join(frame_path , "*.png" ) )[0] img_height, img_width, _ = cv2.imread(sample_image).shape + + if is_invert_mask: + project_dir = inv_path + dbg.print("stage 4") dbg.print("") dbg.print("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!") @@ -98,32 +125,44 @@ def ebsynth_utility_process(stage_index: int, project_dir:str, original_movie_pa dbg.print(" Saving images/grids ->") dbg.print(" Use original name for output filename during batch process in extras tab") dbg.print("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!") - dbg.print("1. Go to Extras tab") - dbg.print("2. Go to Batch from Directory tab") - dbg.print("3. Fill in the \"Input directory\" field with [" + img2img_key_path + "]" ) - dbg.print("4. Fill in the \"Output directory\" field with [" + img2img_upscale_key_path + "]" ) - dbg.print("5. Go to Scale to tab") - dbg.print("6. Fill in the \"Width\" field with [" + str(img_width) + "]" ) - dbg.print("7. Fill in the \"Height\" field with [" + str(img_height) + "]" ) - dbg.print("8. Fill in the remaining configuration fields of Upscaler.") - dbg.print("9. Generate") + dbg.print("1. If \"img2img_upscale_key\" directory already exists in the %s, delete it manually before executing."%(project_dir)) + dbg.print("2. Go to Extras tab") + dbg.print("3. Go to Batch from Directory tab") + dbg.print("4. Fill in the \"Input directory\" field with [" + img2img_key_path + "]" ) + dbg.print("5. Fill in the \"Output directory\" field with [" + img2img_upscale_key_path + "]" ) + dbg.print("6. Go to Scale to tab") + dbg.print("7. Fill in the \"Width\" field with [" + str(img_width) + "]" ) + dbg.print("8. Fill in the \"Height\" field with [" + str(img_height) + "]" ) + dbg.print("9. Fill in the remaining configuration fields of Upscaler.") + dbg.print("10. Generate") dbg.print("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!") return process_end( dbg, "" ) elif stage_index == 4: - ebsynth_utility_stage5(dbg, project_args) + ebsynth_utility_stage5(dbg, project_args, is_invert_mask) elif stage_index == 5: + + if is_invert_mask: + project_dir = inv_path + dbg.print("stage 6") dbg.print("") dbg.print("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!") dbg.print("Running ebsynth.(on your self)") - dbg.print("Open the generated .ebs under project directory and press [Run All] button.") - dbg.print("If ""out-*"" directory already exists in the Project directory, delete it manually before executing.") + dbg.print("Open the generated .ebs under %s and press [Run All] button."%(project_dir)) + dbg.print("If ""out-*"" directory already exists in the %s, delete it manually before executing."%(project_dir)) dbg.print("If multiple .ebs files are generated, run them all.") dbg.print("(I recommend associating the .ebs file with EbSynth.exe.)") dbg.print("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!") return process_end( dbg, "" ) elif stage_index == 6: - ebsynth_utility_stage7(dbg, project_args, blend_rate, export_type) + ebsynth_utility_stage7(dbg, project_args, blend_rate, export_type, is_invert_mask) + elif stage_index == 7: + if mask_mode != "Normal": + dbg.print("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!") + dbg.print("Please reset [configuration]->[etc]->[Mask Mode] to Normal.") + dbg.print("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!") + return process_end( dbg, "" ) + ebsynth_utility_stage8(dbg, project_args, bg_src, bg_type, mask_blur_size, export_type) else: pass diff --git a/imgs/sample6.mp4 b/imgs/sample6.mp4 new file mode 100644 index 0000000..02e0436 Binary files /dev/null and b/imgs/sample6.mp4 differ diff --git a/scripts/custom_script.py b/scripts/custom_script.py index 637637e..c3d6770 100644 --- a/scripts/custom_script.py +++ b/scripts/custom_script.py @@ -4,7 +4,7 @@ import os import torch import random -from modules.processing import process_images +from modules.processing import process_images,Processed from modules.paths import models_path from modules.textual_inversion import autocrop import cv2 @@ -80,9 +80,10 @@ class Script(scripts.Script): def ui(self, is_img2img): project_dir = gr.Textbox(label='Project directory', lines=1) + mask_mode = gr.Dropdown(choices=["Normal","Invert","None","Don't Override"], value="Normal" ,label="Mask Mode(Override img2img Mask mode)") - img2img_repeat_count = gr.Slider(minimum=1, maximum=10, step=1, value=1, label="Img2Img Repeat Count") - inc_seed = gr.Slider(minimum=0, maximum=9999999, step=1, value=0, label="Add N to seed when repeating") + img2img_repeat_count = gr.Slider(minimum=1, maximum=10, step=1, value=1, label="Img2Img Repeat Count(Loop Back)") + inc_seed = gr.Slider(minimum=0, maximum=9999999, step=1, value=1, label="Add N to seed when repeating ") with gr.Group(): is_facecrop = gr.Checkbox(False, label="use Face Crop img2img") @@ -93,7 +94,7 @@ class Script(scripts.Script): ") max_crop_size = gr.Slider(minimum=0, maximum=2048, step=1, value=1024, label="Max Crop Size") face_denoising_strength = gr.Slider(minimum=0.00, maximum=1.00, step=0.01, value=0.5, label="Face Denoising Strength") - face_area_magnification = gr.Slider(minimum=1.00, maximum=10.00, step=0.01, value=1.5, label="Face Area Magnification") + face_area_magnification = gr.Slider(minimum=1.00, maximum=10.00, step=0.01, value=1.5, label="Face Area Magnification ") with gr.Column(): enable_face_prompt = gr.Checkbox(False, label="Enable Face Prompt") @@ -102,7 +103,7 @@ class Script(scripts.Script): value = "face close up," ) - return [project_dir, img2img_repeat_count, inc_seed, is_facecrop, face_detection_method, max_crop_size, face_denoising_strength, face_area_magnification, enable_face_prompt, face_prompt] + return [project_dir, mask_mode, img2img_repeat_count, inc_seed, is_facecrop, face_detection_method, max_crop_size, face_denoising_strength, face_area_magnification, enable_face_prompt, face_prompt] def detect_face(self, img_array): @@ -282,7 +283,8 @@ class Script(scripts.Script): # to be used in processing. The return value should be a Processed object, which is # what is returned by the process_images method. - def run(self, p, project_dir, img2img_repeat_count, inc_seed, is_facecrop, face_detection_method, max_crop_size, face_denoising_strength, face_area_magnification, enable_face_prompt, face_prompt): + def run(self, p, project_dir, mask_mode, img2img_repeat_count, inc_seed, is_facecrop, face_detection_method, max_crop_size, face_denoising_strength, face_area_magnification, enable_face_prompt, face_prompt): + args = locals() def detect_face(img, mask, face_detection_method, max_crop_size): img_array = np.array(img) @@ -325,15 +327,39 @@ class Script(scripts.Script): if not os.path.isdir(project_dir): print("project_dir not found") - return process_images(p) - + return Processed() + if p.seed == -1: p.seed = int(random.randrange(4294967294)) - - frame_mask_path = os.path.join(project_dir, "video_mask") - org_key_path = os.path.join(project_dir, "video_key") - img2img_key_path = os.path.join(project_dir, "img2img_key") + if mask_mode == "Normal": + p.inpainting_mask_invert = 0 + elif mask_mode == "Invert": + p.inpainting_mask_invert = 1 + + is_invert_mask = False + if mask_mode == "Invert": + is_invert_mask = True + + inv_path = os.path.join(project_dir, "inv") + if not os.path.isdir(inv_path): + print("project_dir/inv not found") + return Processed() + + org_key_path = os.path.join(inv_path, "video_key") + img2img_key_path = os.path.join(inv_path, "img2img_key") + else: + org_key_path = os.path.join(project_dir, "video_key") + img2img_key_path = os.path.join(project_dir, "img2img_key") + + frame_mask_path = os.path.join(project_dir, "video_mask") + + if not os.path.isdir(org_key_path): + print(org_key_path + " not found") + print("Generate key frames first." if is_invert_mask == False else \ + "Generate key frames first.(with [Ebsynth Utility] Tab -> [configuration] -> [etc]-> [Mask Mode] = Invert setting)") + return Processed() + remove_pngs_in_dir(img2img_key_path) os.makedirs(img2img_key_path, exist_ok=True) @@ -343,11 +369,13 @@ class Script(scripts.Script): image = Image.open(img) img_basename = os.path.basename(img) - mask_path = os.path.join( frame_mask_path , img_basename ) mask = None - if os.path.isfile( mask_path ): - mask = Image.open(mask_path) + + if mask_mode != "None": + mask_path = os.path.join( frame_mask_path , img_basename ) + if os.path.isfile( mask_path ): + mask = Image.open(mask_path) _p = copy.copy(p) @@ -379,7 +407,6 @@ class Script(scripts.Script): else: proc = process_images(_p) print(proc.seed) - repeat_count -= 1 @@ -394,4 +421,9 @@ class Script(scripts.Script): proc.images[0].save( os.path.join( img2img_key_path , img_basename ) ) + with open( os.path.join( project_dir if is_invert_mask == False else inv_path,"param.txt" ), "w") as f: + f.write(proc.info) + with open( os.path.join( project_dir if is_invert_mask == False else inv_path ,"args.txt" ), "w") as f: + f.write(str(args)) + return proc diff --git a/scripts/ui.py b/scripts/ui.py index 335ba9a..278292e 100644 --- a/scripts/ui.py +++ b/scripts/ui.py @@ -17,18 +17,32 @@ def on_ui_tabs(): project_dir = gr.Textbox(label='Project directory', lines=1) original_movie_path = gr.Textbox(label='Original Movie Path', lines=1) with gr.TabItem('configuration', id='ebs_configuration'): + with gr.Accordion(label="stage 1"): + # https://pypi.org/project/transparent-background/ + gr.HTML(value="\ + configuration for \ + [transparent-background]\ +
") + tb_use_fast_mode = gr.Checkbox(label="Use Fast Mode(It will be faster, but the quality of the mask will be lower.)", value=False) + tb_use_jit = gr.Checkbox(label="Use Jit", value=False) + with gr.Accordion(label="stage 2"): key_min_gap = gr.Slider(minimum=0, maximum=500, step=1, label='Minimum keyframe gap', value=10) key_max_gap = gr.Slider(minimum=0, maximum=1000, step=1, label='Maximum keyframe gap', value=300) - key_th = gr.Slider(minimum=5.0, maximum=100.0, step=0.1, label='Threshold of delta frame edge', value=27.0) + key_th = gr.Slider(minimum=0.0, maximum=100.0, step=0.1, label='Threshold of delta frame edge', value=8.5) key_add_last_frame = gr.Checkbox(label="Add last frame to keyframes", value=True) with gr.Accordion(label="stage 7"): blend_rate = gr.Slider(minimum=0.0, maximum=1.0, step=0.01, label='Crossfade blend rate', value=1.0) export_type = gr.Dropdown(choices=["mp4","webm","gif","rawvideo"], value="mp4" ,label="Export type") + with gr.Accordion(label="stage 8"): + bg_src = gr.Textbox(label='Background source(mp4 or directory containing images)', lines=1) + bg_type = gr.Dropdown(choices=["Fit video length","Loop"], value="Fit video length" ,label="Background type") + mask_blur_size = gr.Slider(minimum=0, maximum=150, step=1, label='Mask Blur Kernel Size', value=5) + with gr.Accordion(label="etc"): - no_mask_mode = gr.Checkbox(label="No Mask Mode", value=False) + mask_mode = gr.Dropdown(choices=["Normal","Invert","None"], value="Normal" ,label="Mask Mode") with gr.Column(variant='panel'): with gr.Column(scale=1): @@ -36,7 +50,7 @@ def on_ui_tabs(): debug_info = gr.HTML(elem_id="ebs_info_area", value=".") with gr.Column(scale=2): - stage_index = gr.Radio(label='Process Stage', choices=["stage 1","stage 2","stage 3","stage 4","stage 5","stage 6","stage 7"], value="stage 1", type="index") + stage_index = gr.Radio(label='Process Stage', choices=["stage 1","stage 2","stage 3","stage 4","stage 5","stage 6","stage 7","stage 8"], value="stage 1", type="index") gr.HTML(value="\
The process of creating a video can be divided into the following stages.
\
(Stage 3, 4, and 6 only show a guide and do nothing actual processing.)
\
@@ -59,7 +73,13 @@ def on_ui_tabs():
If multiple .ebs files are generated, run them all.
\
stage 7
\
Concatenate each frame while crossfading.
\
- Composite audio files extracted from the original video onto the concatenated video.
\
+ Composite audio files extracted from the original video onto the concatenated video.
\
+ stage 8
\
+ This is an extra stage.
\
+ You can put any image or images or video you like in the background.
\
+ You can specify in this field -> [Ebsynth Utility]->[configuration]->[stage 8]->[Background source]
\
+ If you have already created a background video in Invert Mask Mode([Ebsynth Utility]->[configuration]->[etc]->[Mask Mode]),
\
+ You can specify \"path_to_project_dir/inv/crossfade_tmp\".
\