import importlib import os import platform import shutil import subprocess as sp import sys import gradio as gr import modules import modules.scripts as scripts from modules import ( script_callbacks, shared, call_queue, sd_samplers, ui_prompt_styles, sd_models, ) from modules.call_queue import wrap_gradio_gpu_call from modules.images import image_data from modules.shared import opts from modules.ui import ( ordered_ui_categories, create_sampler_and_steps_selection, switch_values_symbol, create_override_settings_dropdown, detect_image_size_symbol, plaintext_to_html, paste_symbol, clear_prompt_symbol, restore_progress_symbol, ) from modules.ui_common import ( folder_symbol, update_generation_info, create_refresh_button, ) from modules.ui_components import ( ResizeHandleRow, FormRow, ToolButton, FormGroup, InputAccordion, ) from scripts import m2m_hook as patches from scripts import m2m_util from scripts import mov2mov from scripts.mov2mov import scripts_mov2mov from scripts.m2m_config import mov2mov_outpath_samples, mov2mov_output_dir from scripts.movie_editor import MovieEditor id_part = "mov2mov" def save_video(video): path = "logs/movies" if not os.path.exists(path): os.makedirs(path, exist_ok=True) index = len([path for path in os.listdir(path) if path.endswith(".mp4")]) + 1 video_path = os.path.join(path, str(index).zfill(5) + ".mp4") shutil.copyfile(video, video_path) filename = os.path.relpath(video_path, path) return gr.File.update(value=video_path, visible=True), plaintext_to_html( f"Saved: {filename}" ) class Toprow: """Creates a top row UI with prompts, generate button, styles, extra little buttons for things, and enables some functionality related to their operation""" def __init__(self, is_img2img, id_part=None): if not id_part: id_part = "img2img" if is_img2img else "txt2img" self.id_part = id_part with gr.Row(elem_id=f"{id_part}_toprow", variant="compact"): with gr.Column(elem_id=f"{id_part}_prompt_container", scale=6): with gr.Row(): with gr.Column(scale=80): with gr.Row(): self.prompt = gr.Textbox( label="Prompt", elem_id=f"{id_part}_prompt", show_label=False, lines=3, placeholder="Prompt (press Ctrl+Enter or Alt+Enter to generate)", elem_classes=["prompt"], ) self.prompt_img = gr.File( label="", elem_id=f"{id_part}_prompt_image", file_count="single", type="binary", visible=False, ) with gr.Row(): with gr.Column(scale=80): with gr.Row(): self.negative_prompt = gr.Textbox( label="Negative prompt", elem_id=f"{id_part}_neg_prompt", show_label=False, lines=3, placeholder="Negative prompt (press Ctrl+Enter or Alt+Enter to generate)", elem_classes=["prompt"], ) self.button_interrogate = None self.button_deepbooru = None if is_img2img: with gr.Column(scale=1, elem_classes="interrogate-col"): self.button_interrogate = gr.Button( "Interrogate\nCLIP", elem_id="interrogate" ) self.button_deepbooru = gr.Button( "Interrogate\nDeepBooru", elem_id="deepbooru" ) with gr.Column(scale=1, elem_id=f"{id_part}_actions_column"): with gr.Row( elem_id=f"{id_part}_generate_box", elem_classes="generate-box" ): self.interrupt = gr.Button( "Interrupt", elem_id=f"{id_part}_interrupt", elem_classes="generate-box-interrupt", ) self.skip = gr.Button( "Skip", elem_id=f"{id_part}_skip", elem_classes="generate-box-skip", ) self.submit = gr.Button( "Generate", elem_id=f"{id_part}_generate", variant="primary" ) self.skip.click( fn=lambda: shared.state.skip(), inputs=[], outputs=[], ) self.interrupt.click( fn=lambda: shared.state.interrupt(), inputs=[], outputs=[], ) with gr.Row(elem_id=f"{id_part}_tools"): self.paste = ToolButton(value=paste_symbol, elem_id="paste") self.clear_prompt_button = ToolButton( value=clear_prompt_symbol, elem_id=f"{id_part}_clear_prompt" ) self.restore_progress_button = ToolButton( value=restore_progress_symbol, elem_id=f"{id_part}_restore_progress", visible=False, ) self.token_counter = gr.HTML( value="0/75", elem_id=f"{id_part}_token_counter", elem_classes=["token-counter"], ) self.token_button = gr.Button( visible=False, elem_id=f"{id_part}_token_button" ) self.negative_token_counter = gr.HTML( value="0/75", elem_id=f"{id_part}_negative_token_counter", elem_classes=["token-counter"], ) self.negative_token_button = gr.Button( visible=False, elem_id=f"{id_part}_negative_token_button" ) self.clear_prompt_button.click( fn=lambda *x: x, _js="confirm_clear_prompt", inputs=[self.prompt, self.negative_prompt], outputs=[self.prompt, self.negative_prompt], ) self.ui_styles = ui_prompt_styles.UiPromptStyles( id_part, self.prompt, self.negative_prompt ) self.prompt_img.change( fn=modules.images.image_data, inputs=[self.prompt_img], outputs=[self.prompt, self.prompt_img], show_progress=False, ) def create_output_panel(tabname, outdir): def open_folder(f): if not os.path.exists(f): print( f'Folder "{f}" does not exist. After you create an image, the folder will be created.' ) return elif not os.path.isdir(f): print( f""" WARNING An open_folder request was made with an argument that is not a folder. This could be an error or a malicious attempt to run code on your computer. Requested path was: {f} """, file=sys.stderr, ) return if not shared.cmd_opts.hide_ui_dir_config: path = os.path.normpath(f) if platform.system() == "Windows": os.startfile(path) elif platform.system() == "Darwin": sp.Popen(["open", path]) elif "microsoft-standard-WSL2" in platform.uname().release: sp.Popen(["wsl-open", path]) else: sp.Popen(["xdg-open", path]) with gr.Column(variant="panel", elem_id=f"{tabname}_results"): with gr.Group(elem_id=f"{tabname}_gallery_container"): result_gallery = gr.Gallery( label="Output", show_label=False, elem_id=f"{tabname}_gallery", columns=4, preview=True, height=shared.opts.gallery_height or None, ) result_video = gr.PlayableVideo( label="Output Video", show_label=False, elem_id=f"{tabname}_video" ) generation_info = None with gr.Column(): with gr.Row( elem_id=f"image_buttons_{tabname}", elem_classes="image-buttons" ): open_folder_button = ToolButton( folder_symbol, elem_id=f"{tabname}_open_folder", visible=not shared.cmd_opts.hide_ui_dir_config, tooltip="Open images output directory.", ) if tabname != "extras": save = ToolButton( "💾", elem_id=f"save_{tabname}", tooltip=f"Save the image to a dedicated directory ({shared.opts.outdir_save}).", ) open_folder_button.click( fn=lambda: open_folder(shared.opts.outdir_samples or outdir), inputs=[], outputs=[], ) download_files = gr.File( None, file_count="multiple", interactive=False, show_label=False, visible=False, elem_id=f"download_files_{tabname}", ) with gr.Group(): html_info = gr.HTML( elem_id=f"html_info_{tabname}", elem_classes="infotext" ) html_log = gr.HTML( elem_id=f"html_log_{tabname}", elem_classes="html-log" ) generation_info = gr.Textbox( visible=False, elem_id=f"generation_info_{tabname}" ) if tabname == "txt2img" or tabname == "img2img" or tabname == "mov2mov": generation_info_button = gr.Button( visible=False, elem_id=f"{tabname}_generation_info_button" ) generation_info_button.click( fn=update_generation_info, _js="function(x, y, z){ return [x, y, selected_gallery_index()] }", inputs=[generation_info, html_info, html_info], outputs=[html_info, html_info], show_progress=False, ) save.click( fn=call_queue.wrap_gradio_call(save_video), inputs=[result_video], outputs=[ download_files, html_log, ], show_progress=False, ) return result_gallery, result_video, generation_info, html_info, html_log def create_refiner(): with InputAccordion( False, label="Refiner", elem_id=f"{id_part}_enable" ) as enable_refiner: with gr.Row(): refiner_checkpoint = gr.Dropdown( label="Checkpoint", elem_id=f"{id_part}_checkpoint", choices=sd_models.checkpoint_tiles(), value="", tooltip="switch to another model in the middle of generation", ) create_refresh_button( refiner_checkpoint, sd_models.list_models, lambda: {"choices": sd_models.checkpoint_tiles()}, f"{id_part}_checkpoint_refresh", ) refiner_switch_at = gr.Slider( value=0.8, label="Switch at", minimum=0.01, maximum=1.0, step=0.01, elem_id=f"{id_part}_switch_at", tooltip="fraction of sampling steps when the switch to refiner model should happen; 1=never, 0.5=switch in the middle of generation", ) return enable_refiner, refiner_checkpoint, refiner_switch_at def on_ui_tabs(): scripts_mov2mov.initialize_scripts(is_img2img=True) # with gr.Blocks(analytics_enabled=False) as mov2mov_interface: with gr.TabItem( "mov2mov", id=f"tab_{id_part}", elem_id=f"tab_{id_part}" ) as mov2mov_interface: toprow = Toprow(is_img2img=False, id_part=id_part) dummy_component = gr.Label(visible=False) with gr.Tab( "Generation", id=f"{id_part}_generation" ) as mov2mov_generation_tab, ResizeHandleRow(equal_height=False): with gr.Column(variant="compact", elem_id="mov2mov_settings"): with gr.Tabs(elem_id=f"mode_{id_part}"): init_mov = gr.Video( label="Video for mov2mov", elem_id=f"{id_part}_mov", show_label=False, source="upload", ) with FormRow(): resize_mode = gr.Radio( label="Resize mode", elem_id=f"{id_part}_resize_mode", choices=[ "Just resize", "Crop and resize", "Resize and fill", "Just resize (latent upscale)", ], type="index", value="Just resize", ) scripts_mov2mov.prepare_ui() for category in ordered_ui_categories(): if category == "sampler": steps, sampler_name = create_sampler_and_steps_selection( sd_samplers.visible_sampler_names(), id_part ) elif category == "dimensions": with FormRow(): with gr.Column(elem_id=f"{id_part}_column_size", scale=4): with gr.Tabs(): with gr.Tab( label="Resize to", elem_id=f"{id_part}_tab_resize_to", ) as tab_scale_to: with FormRow(): with gr.Column( elem_id=f"{id_part}_column_size", scale=4, ): width = gr.Slider( minimum=64, maximum=2048, step=8, label="Width", value=512, elem_id=f"{id_part}_width", ) height = gr.Slider( minimum=64, maximum=2048, step=8, label="Height", value=512, elem_id=f"{id_part}_height", ) with gr.Column( elem_id=f"{id_part}_dimensions_row", scale=1, elem_classes="dimensions-tools", ): res_switch_btn = ToolButton( value=switch_values_symbol, elem_id=f"{id_part}_res_switch_btn", ) detect_image_size_btn = ToolButton( value=detect_image_size_symbol, elem_id=f"{id_part}_detect_image_size_btn", ) elif category == "denoising": denoising_strength = gr.Slider( minimum=0.0, maximum=1.0, step=0.01, label="Denoising strength", value=0.75, elem_id=f"{id_part}_denoising_strength", ) noise_multiplier = gr.Slider( minimum=0, maximum=1.5, step=0.01, label="Noise multiplier", elem_id=f"{id_part}_noise_multiplier", value=1, ) with gr.Row(elem_id=f"{id_part}_frames_setting"): movie_frames = gr.Slider( minimum=10, maximum=60, step=1, label="Movie FPS", elem_id=f"{id_part}_movie_frames", value=30, ) max_frames = gr.Number( label="Max FPS", value=-1, elem_id=f"{id_part}_max_frames", ) elif category == "cfg": with gr.Row(): cfg_scale = gr.Slider( minimum=1.0, maximum=30.0, step=0.5, label="CFG Scale", value=7.0, elem_id=f"{id_part}_cfg_scale", ) image_cfg_scale = gr.Slider( minimum=0, maximum=3.0, step=0.05, label="Image CFG Scale", value=1.5, elem_id=f"{id_part}_image_cfg_scale", visible=False, ) elif category == "checkboxes": with FormRow(elem_classes="checkboxes-row", variant="compact"): pass elif category == "accordions": with gr.Row( elem_id=f"{id_part}_accordions", elem_classes="accordions" ): scripts_mov2mov.setup_ui_for_section(category) elif category == "override_settings": with FormRow(elem_id=f"{id_part}_override_settings_row") as row: override_settings = create_override_settings_dropdown( "mov2mov", row ) elif category == "scripts": editor = MovieEditor(id_part, init_mov, movie_frames) editor.render() with FormGroup(elem_id=f"{id_part}_script_container"): custom_inputs = scripts_mov2mov.setup_ui() if category not in {"accordions"}: scripts_mov2mov.setup_ui_for_section(category) ( mov2mov_gallery, result_video, generation_info, html_info, html_log, ) = create_output_panel(id_part, opts.mov2mov_output_dir) res_switch_btn.click( fn=None, _js="function(){switchWidthHeight('mov2mov')}", inputs=None, outputs=None, show_progress=False, ) # calc video size detect_image_size_btn.click( fn=calc_video_w_h, inputs=[init_mov, width, height], outputs=[width, height], ) mov2mov_args = dict( fn=wrap_gradio_gpu_call(mov2mov.mov2mov, extra_outputs=[None, "", ""]), _js="submit_mov2mov", inputs=[ dummy_component, toprow.prompt, toprow.negative_prompt, toprow.ui_styles.dropdown, init_mov, steps, sampler_name, cfg_scale, image_cfg_scale, denoising_strength, height, width, resize_mode, override_settings, # refiner # enable_refiner, refiner_checkpoint, refiner_switch_at, # mov2mov params noise_multiplier, movie_frames, max_frames, # editor editor.gr_enable_movie_editor, editor.gr_df, editor.gr_eb_weight, ] + custom_inputs, outputs=[ result_video, generation_info, html_info, html_log, ], show_progress=False, ) toprow.submit.click(**mov2mov_args) return [(mov2mov_interface, "mov2mov", f"{id_part}_tabs")] def calc_video_w_h(video, width, height): if not video: return width, height return m2m_util.calc_video_w_h(video) def on_ui_settings(): section = ("mov2mov", "Mov2Mov") shared.opts.add_option( "mov2mov_outpath_samples", shared.OptionInfo( mov2mov_outpath_samples, "Mov2Mov output path for image", section=section ), ) shared.opts.add_option( "mov2mov_output_dir", shared.OptionInfo( mov2mov_output_dir, "Mov2Mov output path for video", section=section ), ) img2img_toprow: gr.Row = None def block_context_init(self, *args, **kwargs): origin_block_context_init(self, *args, **kwargs) if self.elem_id == "tab_img2img": self.parent.__enter__() on_ui_tabs() self.parent.__exit__() def on_app_reload(): global origin_block_context_init if origin_block_context_init: patches.undo(__name__, obj=gr.blocks.BlockContext, field="__init__") origin_block_context_init = None origin_block_context_init = patches.patch( __name__, obj=gr.blocks.BlockContext, field="__init__", replacement=block_context_init, ) script_callbacks.on_before_reload(on_app_reload) script_callbacks.on_ui_settings(on_ui_settings) # script_callbacks.on_ui_tabs(on_ui_tabs)