diff --git a/TODO.md b/TODO.md index 6acb369c5..0575e97c7 100644 --- a/TODO.md +++ b/TODO.md @@ -4,28 +4,23 @@ Main ToDo list can be found at [GitHub projects](https://github.com/users/vladma ## Current -- - ## Future Candidates - Redesign postprocessing - Flux NF4 loader: - IPAdapter negative: - Control API enhance scripts compatibility -- CogView4 ## Code TODO -- flux: loader for civitai nf4 models (fixme) -- hypertile: vae breaks when using non-standard sizes (fixme) -- install: enable ROCm for windows when available (fixme) -- lora make support quantized flux (fixme) -- lora: add other quantization types (fixme) -- model load: force-reloading entire model as loading transformers only leads to massive memory usage (fixme) -- model loader: implement model in-memory caching (fixme) -- modernui: monkey-patch for missing tabs.select event (fixme) -- processing: remove duplicate mask params (fixme) -- resize image: enable full VAE mode for resize-latent (fixme) -- sana: fails when quantized (fixme) -- support scripts via api (fixme) -- transformer from-single-file with quant (fixme) +- enable ROCm for windows when available +- resize image: enable full VAE mode for resize-latent +- infotext: handle using regex instead +- processing: remove duplicate mask params +- model loader: implement model in-memory caching +- hypertile: vae breaks when using non-standard sizes +- force-reloading entire model as loading transformers only leads to massive memory usage +- add other quantization types +- lora make support quantized flux +- control: support scripts via api +- modernui: monkey-patch for missing tabs.select event diff --git a/javascript/amethyst-nightfall.css b/javascript/amethyst-nightfall.css index ef582848d..929bd9e77 100644 --- a/javascript/amethyst-nightfall.css +++ b/javascript/amethyst-nightfall.css @@ -88,8 +88,6 @@ svg.feather.feather-image, .feather .feather-image { display: none } /* gradio elements overrides */ #div.gradio-container { overflow-x: hidden; } #img2img_label_copy_to_img2img { font-weight: normal; } -#txt2img_prompt, #txt2img_neg_prompt, #img2img_prompt, #img2img_neg_prompt { background-color: var(--background-color); box-shadow: 4px 4px 4px 0px #333333 !important; } -#txt2img_prompt > label > textarea, #txt2img_neg_prompt > label > textarea, #img2img_prompt > label > textarea, #img2img_neg_prompt > label > textarea { font-size: 1.1rem; } #img2img_settings { min-width: calc(2 * var(--left-column)); max-width: calc(2 * var(--left-column)); background-color: #111111; padding-top: 16px; } #interrogate, #deepbooru { margin: 0 0px 10px 0px; max-width: 80px; max-height: 80px; font-weight: normal; font-size: 0.95em; } #quicksettings .gr-button-tool { font-size: 1.6rem; box-shadow: none; margin-left: -20px; margin-top: -2px; height: 2.4em; } diff --git a/javascript/black-gray.css b/javascript/black-gray.css index c262a3bf4..784626d60 100644 --- a/javascript/black-gray.css +++ b/javascript/black-gray.css @@ -103,11 +103,6 @@ svg.feather.feather-image, .feather .feather-image { display: none } /* gradio elements overrides */ #div.gradio-container { overflow-x: hidden; } #img2img_label_copy_to_img2img { font-weight: normal; } - -#txt2img_prompt, #txt2img_neg_prompt, #img2img_prompt, #img2img_neg_prompt, #control_prompt, #control_neg_prompt { background-color: var(--background-color); box-shadow: none !important; } -#txt2img_prompt > label > textarea, #txt2img_neg_prompt > label > textarea, #img2img_prompt > label > textarea, #img2img_neg_prompt > label > textarea, #control_prompt > label > textarea, #control_neg_prompt > label > textarea { font-size: 1.0em; line-height: 1.4em; } -#txt2img_styles, #img2img_styles, #control_styles { padding: 0; } -#txt2img_styles_refresh, #img2img_styles_refresh, #control_styles_refresh { padding: 0; margin-top: 1em; } #img2img_settings { min-width: calc(2 * var(--left-column)); max-width: calc(2 * var(--left-column)); background-color: var(--primary-950); padding-top: 16px; } #interrogate, #deepbooru { margin: 0 0px 10px 0px; max-width: 80px; max-height: 80px; font-weight: normal; font-size: 0.95em; } #quicksettings .gr-button-tool { font-size: 1.6rem; box-shadow: none; margin-left: -20px; margin-top: -2px; height: 2.4em; } diff --git a/javascript/black-orange.css b/javascript/black-orange.css index 54b98b1df..467c53d1d 100644 --- a/javascript/black-orange.css +++ b/javascript/black-orange.css @@ -105,8 +105,6 @@ svg.feather.feather-image, .feather .feather-image { display: none } /* gradio elements overrides */ #div.gradio-container { overflow-x: hidden; } #img2img_label_copy_to_img2img { font-weight: normal; } -#txt2img_prompt, #txt2img_neg_prompt, #img2img_prompt, #img2img_neg_prompt { background-color: var(--background-color); box-shadow: 4px 4px 4px 0px #333333 !important; } -#txt2img_prompt > label > textarea, #txt2img_neg_prompt > label > textarea, #img2img_prompt > label > textarea, #img2img_neg_prompt > label > textarea { font-size: 1.1rem; } #img2img_settings { min-width: calc(2 * var(--left-column)); max-width: calc(2 * var(--left-column)); background-color: #111111; padding-top: 16px; } #interrogate, #deepbooru { margin: 0 0px 10px 0px; max-width: 80px; max-height: 80px; font-weight: normal; font-size: 0.95em; } #quicksettings .gr-button-tool { font-size: 1.6rem; box-shadow: none; margin-left: -20px; margin-top: -2px; height: 2.4em; } diff --git a/javascript/black-teal.css b/javascript/black-teal.css index 0a6db4fa2..851c03953 100644 --- a/javascript/black-teal.css +++ b/javascript/black-teal.css @@ -142,11 +142,6 @@ svg.feather.feather-image, .feather .feather-image { display: none } /* gradio elements overrides */ #div.gradio-container { overflow-x: hidden; } #img2img_label_copy_to_img2img { font-weight: normal; } - -#txt2img_prompt, #txt2img_neg_prompt, #img2img_prompt, #img2img_neg_prompt, #control_prompt, #control_neg_prompt { background-color: var(--background-color); box-shadow: none !important; } -#txt2img_prompt > label > textarea, #txt2img_neg_prompt > label > textarea, #img2img_prompt > label > textarea, #img2img_neg_prompt > label > textarea, #control_prompt > label > textarea, #control_neg_prompt > label > textarea { font-size: 1.0em; line-height: 1.4em; } -#txt2img_styles, #img2img_styles, #control_styles { padding: 0; margin-top: 2px; } -#txt2img_styles_refresh, #img2img_styles_refresh, #control_styles_refresh { padding: 0; margin-top: 1em; } #img2img_settings { min-width: calc(2 * var(--left-column)); max-width: calc(2 * var(--left-column)); background-color: var(--neutral-950); padding-top: 16px; } #interrogate, #deepbooru { margin: 0 0px 10px 0px; max-width: 80px; max-height: 80px; font-weight: normal; font-size: 0.95em; } #quicksettings .gr-button-tool { font-size: 1.6rem; box-shadow: none; margin-left: -20px; margin-top: -2px; height: 2.4em; } diff --git a/javascript/emerald-paradise.css b/javascript/emerald-paradise.css index f951356cc..411b37774 100644 --- a/javascript/emerald-paradise.css +++ b/javascript/emerald-paradise.css @@ -101,8 +101,6 @@ button.selected {background: var(--button-primary-background-fill);} /* gradio elements overrides */ #div.gradio-container { overflow-x: hidden; } #img2img_label_copy_to_img2img { font-weight: normal; } -#txt2img_prompt, #txt2img_neg_prompt, #img2img_prompt, #img2img_neg_prompt { background-color: var(--background-color); box-shadow: 4px 4px 4px 0px #333333 !important; } -#txt2img_prompt > label > textarea, #txt2img_neg_prompt > label > textarea, #img2img_prompt > label > textarea, #img2img_neg_prompt > label > textarea { font-size: 1.1rem; } #img2img_settings { min-width: calc(2 * var(--left-column)); max-width: calc(2 * var(--left-column)); background-color: #111111; padding-top: 16px; } #interrogate, #deepbooru { margin: 0 0px 10px 0px; max-width: 80px; max-height: 80px; font-weight: normal; font-size: 0.95em; } #quicksettings .gr-button-tool { font-size: 1.6rem; box-shadow: none; margin-top: -2px; height: 2.4em; } diff --git a/javascript/invoked.css b/javascript/invoked.css index 72e78d31a..a5954adbf 100644 --- a/javascript/invoked.css +++ b/javascript/invoked.css @@ -101,8 +101,6 @@ button.selected {background: var(--button-primary-background-fill);} /* gradio elements overrides */ #div.gradio-container { overflow-x: hidden; } #img2img_label_copy_to_img2img { font-weight: normal; } -#txt2img_prompt, #txt2img_neg_prompt, #img2img_prompt, #img2img_neg_prompt { background-color: var(--background-color); box-shadow: 4px 4px 4px 0px #333333 !important; } -#txt2img_prompt > label > textarea, #txt2img_neg_prompt > label > textarea, #img2img_prompt > label > textarea, #img2img_neg_prompt > label > textarea { font-size: 1.1rem; } #img2img_settings { min-width: calc(2 * var(--left-column)); max-width: calc(2 * var(--left-column)); background-color: #111111; padding-top: 16px; } #interrogate, #deepbooru { margin: 0 0px 10px 0px; max-width: 80px; max-height: 80px; font-weight: normal; font-size: 0.95em; } #quicksettings .gr-button-tool { font-size: 1.6rem; box-shadow: none; margin-top: -2px; height: 2.4em; } diff --git a/javascript/light-teal.css b/javascript/light-teal.css index 7dc9e5950..df8a3ab51 100644 --- a/javascript/light-teal.css +++ b/javascript/light-teal.css @@ -101,10 +101,6 @@ svg.feather.feather-image, .feather .feather-image { display: none } /* gradio elements overrides */ #div.gradio-container { overflow-x: hidden; } #img2img_label_copy_to_img2img { font-weight: normal; } -#txt2img_prompt, #txt2img_neg_prompt, #img2img_prompt, #img2img_neg_prompt, #control_prompt, #control_neg_prompt { background-color: var(--background-color); box-shadow: 4px 4px 4px 0px #333333 !important; } -#txt2img_prompt > label > textarea, #txt2img_neg_prompt > label > textarea, #img2img_prompt > label > textarea, #img2img_neg_prompt > label > textarea, #control_prompt > label > textarea, #control_neg_prompt > label > textarea { font-size: 1.0em; line-height: 1.4em; } -#txt2img_styles, #img2img_styles, #control_styles { padding: 0; } -#txt2img_styles_refresh, #img2img_styles_refresh, #control_styles_refresh { padding: 0; margin-top: 1em; } #img2img_settings { min-width: calc(2 * var(--left-column)); max-width: calc(2 * var(--left-column)); background-color: #111111; padding-top: 16px; } #interrogate, #deepbooru { margin: 0 0px 10px 0px; max-width: 80px; max-height: 80px; font-weight: normal; font-size: 0.95em; } #quicksettings .gr-button-tool { font-size: 1.6rem; box-shadow: none; margin-left: -20px; margin-top: -2px; height: 2.4em; } diff --git a/javascript/midnight-barbie.css b/javascript/midnight-barbie.css index ea78e5cab..9facd698d 100644 --- a/javascript/midnight-barbie.css +++ b/javascript/midnight-barbie.css @@ -94,8 +94,6 @@ svg.feather.feather-image, .feather .feather-image { display: none } /* gradio elements overrides */ #div.gradio-container { overflow-x: hidden; } #img2img_label_copy_to_img2img { font-weight: normal; } -#txt2img_prompt, #txt2img_neg_prompt, #img2img_prompt, #img2img_neg_prompt { background-color: var(--background-color); box-shadow: 4px 4px 4px 0px #333333 !important; } -#txt2img_prompt > label > textarea, #txt2img_neg_prompt > label > textarea, #img2img_prompt > label > textarea, #img2img_neg_prompt > label > textarea { font-size: 1.1rem; } #img2img_settings { min-width: calc(2 * var(--left-column)); max-width: calc(2 * var(--left-column)); background-color: #111111; padding-top: 16px; } #interrogate, #deepbooru { margin: 0 0px 10px 0px; max-width: 80px; max-height: 80px; font-weight: normal; font-size: 0.95em; } #quicksettings .gr-button-tool { font-size: 1.6rem; box-shadow: none; margin-left: -20px; margin-top: -2px; height: 2.4em; } diff --git a/javascript/orchid-dreams.css b/javascript/orchid-dreams.css index 4b121c761..915823bb3 100644 --- a/javascript/orchid-dreams.css +++ b/javascript/orchid-dreams.css @@ -101,8 +101,6 @@ button.selected {background: var(--button-primary-background-fill);} /* gradio elements overrides */ #div.gradio-container { overflow-x: hidden; } #img2img_label_copy_to_img2img { font-weight: normal; } -#txt2img_prompt, #txt2img_neg_prompt, #img2img_prompt, #img2img_neg_prompt { background-color: var(--background-color); box-shadow: 4px 4px 4px 0px #333333 !important; } -#txt2img_prompt > label > textarea, #txt2img_neg_prompt > label > textarea, #img2img_prompt > label > textarea, #img2img_neg_prompt > label > textarea { font-size: 1.1rem; } #img2img_settings { min-width: calc(2 * var(--left-column)); max-width: calc(2 * var(--left-column)); background-color: #111111; padding-top: 16px; } #interrogate, #deepbooru { margin: 0 0px 10px 0px; max-width: 80px; max-height: 80px; font-weight: normal; font-size: 0.95em; } #quicksettings .gr-button-tool { font-size: 1.6rem; box-shadow: none; margin-top: -2px; height: 2.4em; } diff --git a/javascript/sdnext.css b/javascript/sdnext.css index 3a564679e..d9aa365e7 100644 --- a/javascript/sdnext.css +++ b/javascript/sdnext.css @@ -99,20 +99,20 @@ button.custom-button { border-radius: var(--button-large-radius); padding: var(- #txt2img_gallery, #img2img_gallery { height: 50vh; } #control-result { background: var(--button-secondary-background-fill); padding: 0.2em; } #control-inputs { margin-top: 1em; } -#txt2img_prompt_container, #img2img_prompt_container, #control_prompt_container { margin-right: var(--layout-gap) } +#txt2img_prompt_container, #img2img_prompt_container, #control_prompt_container, #video_prompt_container { margin-right: var(--layout-gap) } #txt2img_footer, #img2img_footer, #control_footer { height: fit-content; display: none; } #txt2img_generate_box, #img2img_generate_box { gap: 0.5em; flex-wrap: unset; min-width: unset; width: 66.6%; } #control_generate_box { gap: 0.5em; flex-wrap: unset; min-width: unset; width: 100%; } #control_generate_box button:nth-child(1) { flex-grow: 2; } #control_generate_box button:nth-child(2) { flex-grow: 1; } -#txt2img_actions_column, #img2img_actions_column, #control_actions_column { gap: 0.3em; height: fit-content; } -#txt2img_generate_box>button, #img2img_generate_box>button, #control_generate_box>button, #txt2img_enqueue, #img2img_enqueue, #txt2img_enqueue>button, #img2img_enqueue>button { min-height: 44px !important; max-height: 44px !important; line-height: 1em; white-space: break-spaces; min-width: unset; } +#txt2img_actions_column, #img2img_actions_column, #control_actions_column, #video_actions_column { gap: 0.3em; height: fit-content; } +#txt2img_generate_box>button, #img2img_generate_box>button, #control_generate_box>button, #video_generate_box>button, #txt2img_enqueue, #img2img_enqueue, #txt2img_enqueue>button, #img2img_enqueue>button { min-height: 44px !important; max-height: 44px !important; line-height: 1em; white-space: break-spaces; min-width: unset; } #txt2img_enqueue_wrapper, #img2img_enqueue_wrapper, #control_enqueue_wrapper { min-width: unset !important; width: 31%; } #txt2img_generate_line2, #img2img_generate_line2, #txt2img_tools, #img2img_tools, #control_generate_line2, #control_tools { display: flex; } #txt2img_generate_line2>button, #img2img_generate_line2>button, #extras_generate_box>button, #control_generate_line2>button, #txt2img_tools>button, #img2img_tools>button, #control_tools>button { height: 2em; line-height: 0; font-size: var(--text-md); min-width: unset; display: block !important; } -#txt2img_prompt, #txt2img_neg_prompt, #img2img_prompt, #img2img_neg_prompt, #control_prompt, #control_neg_prompt { display: contents; } -#txt2img_actions_column, #img2img_actions_column, #control_actions { flex-flow: wrap; justify-content: space-between; } +#txt2img_prompt, #txt2img_neg_prompt, #img2img_prompt, #img2img_neg_prompt, #control_prompt, #control_neg_prompt, #video_prompt, #video_neg_prompt { display: contents; } +#txt2img_actions_column, #img2img_actions_column, #control_actions, #video_actions { flex-flow: wrap; justify-content: space-between; } .interrogate { position: absolute; right: 2.8em; top: 0.2em; max-width: fit-content; background: none !important; z-index: 50; font-size: 1.5em !important; } .interrogate:hover { background: var(--button-primary-background-fill-hover) !important; } @@ -140,6 +140,11 @@ div#extras_scale_to_tab div.form { flex-direction: row; } #txt2img_advanced_options, #img2img_advanced_options, #control_advanced_options { min-width: 100%; } #txt2img_advanced_options .gradio-checkbox, #img2img_advanced_options .gradio-checkbox, #control_advanced_options .gradio-checkbox { min-width: unset !important; max-width: fit-content; } +#txt2img_prompt, #txt2img_neg_prompt, #img2img_prompt, #img2img_neg_prompt, #control_prompt, #control_neg_prompt, #video_prompt, #video_neg_prompt { background-color: var(--background-color); box-shadow: none !important; } +#txt2img_prompt > label > textarea, #txt2img_neg_prompt > label > textarea, #img2img_prompt > label > textarea, #img2img_neg_prompt > label > textarea, #control_prompt > label > textarea, #control_neg_prompt > label > textarea, #video_prompt > label > textarea, #video_neg_prompt > label > textarea { font-size: 1.0em; line-height: 1.4em; } +#txt2img_styles, #img2img_styles, #control_styles, #video_styles { padding: 0; margin-top: 2px; } +#txt2img_styles_refresh, #img2img_styles_refresh, #control_styles_refresh, #video_styles_refresh { padding: 0; margin-top: 1em; } + /* settings */ #si-sparkline-memo, #si-sparkline-load { background-color: #111; } #quicksettings { width: fit-content; } @@ -388,7 +393,7 @@ div:has(>#tab-gallery-folders) { flex-grow: 0 !important; background-color: var( #txt2img_results, #extras_results, #txt2im g_footer p { text-wrap: wrap; max-width: 100% !important; } /* maintain side by side split on larger mobile displays for from text */ } #scripts_alwayson_txt2img div, #scripts_alwayson_img2img div { max-width: 100%; } - #txt2img_prompt_container, #img2img_prompt_container, #control_prompt_container { resize: vertical !important; } + #txt2img_prompt_container, #img2img_prompt_container, #control_prompt_container, #video_prompt_container { resize: vertical !important; } #txt2img_generate_box, #txt2img_enqueue_wrapper { min-width: 100% !important;} /* make generate and enqueue buttons take up the entire width of their rows. */ #img2img_toprow>div.gradio-column { flex-grow: 1 !important;} /*make interrogate buttons take up appropriate space. */ #img2img_actions_column { display: flex; min-width: fit-content !important; flex-direction: row;justify-content: space-evenly; align-items: center;} diff --git a/javascript/timeless-beige.css b/javascript/timeless-beige.css index 4b0f7d9e4..a8a9c1536 100644 --- a/javascript/timeless-beige.css +++ b/javascript/timeless-beige.css @@ -101,8 +101,6 @@ button.selected {background: var(--button-primary-background-fill);} /* gradio elements overrides */ #div.gradio-container { overflow-x: hidden; } #img2img_label_copy_to_img2img { font-weight: normal; } -#txt2img_prompt, #txt2img_neg_prompt, #img2img_prompt, #img2img_neg_prompt { background-color: var(--background-color); box-shadow: 4px 4px 4px 0px #333333 !important; } -#txt2img_prompt > label > textarea, #txt2img_neg_prompt > label > textarea, #img2img_prompt > label > textarea, #img2img_neg_prompt > label > textarea { font-size: 1.1rem; } #img2img_settings { min-width: calc(2 * var(--left-column)); max-width: calc(2 * var(--left-column)); background-color: #111111; padding-top: 16px; } #interrogate, #deepbooru { margin: 0 0px 10px 0px; max-width: 80px; max-height: 80px; font-weight: normal; font-size: 0.95em; } #quicksettings .gr-button-tool { font-size: 1.6rem; box-shadow: none; margin-top: -2px; height: 2.4em; } diff --git a/javascript/ui.js b/javascript/ui.js index 9df1a26e4..badec0f01 100644 --- a/javascript/ui.js +++ b/javascript/ui.js @@ -240,6 +240,18 @@ function submit_control(...args) { return res; } +function submit_video(...args) { + log('submitVideo'); + clearGallery('video'); + const id = randomId(); + requestProgress(id, null, gradioApp().getElementById('video_gallery')); + const res = create_submit_args(args); + res[0] = id; + res[1] = window.submit_state; + window.submit_state = ''; + return res; +} + function submit_postprocessing(...args) { log('SubmitExtras'); clearGallery('extras'); diff --git a/modules/ui.py b/modules/ui.py index 6d52187c8..371a26945 100644 --- a/modules/ui.py +++ b/modules/ui.py @@ -146,6 +146,12 @@ def create_ui(startup_timer = None): ui_control.create_ui() timer.startup.record("ui-control") + with gr.Blocks(analytics_enabled=False) as video_interface: + if shared.native and shared.cmd_opts.experimental: + from modules import ui_video + ui_video.create_ui() + timer.startup.record("ui-video") + with gr.Blocks(analytics_enabled=False) as extras_interface: from modules import ui_postprocessing ui_postprocessing.create_ui() @@ -398,7 +404,10 @@ def create_ui(startup_timer = None): interfaces = [] interfaces += [(txt2img_interface, "Text", "txt2img")] interfaces += [(img2img_interface, "Image", "img2img")] - interfaces += [(control_interface, "Control", "control")] if control_interface is not None else [] + if control_interface is not None: + interfaces += [(control_interface, "Control", "control")] + if video_interface is not None: + interfaces += [(video_interface, "Video", "video")] interfaces += [(extras_interface, "Process", "process")] interfaces += [(caption_interface, "Caption", "caption")] interfaces += [(gallery_interface, "Gallery", "gallery")] diff --git a/modules/ui_common.py b/modules/ui_common.py index a8167e457..f5260d10c 100644 --- a/modules/ui_common.py +++ b/modules/ui_common.py @@ -237,8 +237,8 @@ def interrogate_booru(image): # legacy function return gr.update() if prompt is None else prompt -def create_output_panel(tabname, preview=True, prompt=None, height=None): - with gr.Column(variant='panel', elem_id=f"{tabname}_results"): +def create_output_panel(tabname, preview=True, prompt=None, height=None, transfer=True, scale=1): + with gr.Column(variant='panel', elem_id=f"{tabname}_results", scale=scale): with gr.Group(elem_id=f"{tabname}_gallery_container"): if tabname == "txt2img": gr.HTML(value="", elem_id="main_info", visible=False, elem_classes=["main-info"]) @@ -270,10 +270,13 @@ def create_output_panel(tabname, preview=True, prompt=None, height=None): clip_files.click(fn=None, _js='clip_gallery_urls', inputs=[result_gallery], outputs=[]) save = gr.Button('Save', elem_id=f'save_{tabname}') delete = gr.Button('Delete', elem_id=f'delete_{tabname}') - if not shared.native: - buttons = generation_parameters_copypaste.create_buttons(["img2img", "inpaint", "extras"]) + if transfer: + if not shared.native: + buttons = generation_parameters_copypaste.create_buttons(["img2img", "inpaint", "extras"]) + else: + buttons = generation_parameters_copypaste.create_buttons(["txt2img", "img2img", "control", "extras", "caption"]) else: - buttons = generation_parameters_copypaste.create_buttons(["txt2img", "img2img", "control", "extras", "caption"]) + buttons = None download_files = gr.File(None, file_count="multiple", interactive=False, show_label=False, visible=False, elem_id=f'download_files_{tabname}') with gr.Group(): @@ -309,17 +312,18 @@ def create_output_panel(tabname, preview=True, prompt=None, height=None): else: paste_field_names = [] debug(f'Paste field: tab={tabname} fields={paste_field_names}') - for paste_tabname, paste_button in buttons.items(): - debug(f'Create output panel: source={tabname} target={paste_tabname} button={paste_button}') - bindings = generation_parameters_copypaste.ParamBinding( - paste_button=paste_button, - tabname=paste_tabname, - source_tabname=tabname, - source_image_component=result_gallery, - paste_field_names=paste_field_names, - source_text_component=prompt or generation_info - ) - generation_parameters_copypaste.register_paste_params_button(bindings) + if buttons is not None: + for paste_tabname, paste_button in buttons.items(): + debug(f'Create output panel: source={tabname} target={paste_tabname} button={paste_button}') + bindings = generation_parameters_copypaste.ParamBinding( + paste_button=paste_button, + tabname=paste_tabname, + source_tabname=tabname, + source_image_component=result_gallery, + paste_field_names=paste_field_names, + source_text_component=prompt or generation_info + ) + generation_parameters_copypaste.register_paste_params_button(bindings) return result_gallery, generation_info, html_info, html_info_formatted, html_log diff --git a/modules/ui_sections.py b/modules/ui_sections.py index 0d0e0eb8d..20b1cfc50 100644 --- a/modules/ui_sections.py +++ b/modules/ui_sections.py @@ -4,7 +4,7 @@ from modules.ui_components import ToolButton from modules.interrogate import interrogate -def create_toprow(is_img2img: bool = False, id_part: str = None): +def create_toprow(is_img2img: bool = False, id_part: str = None, negative_visible: bool = True, reprocess_visible: bool = True): def apply_styles(prompt, prompt_neg, styles): prompt = shared.prompt_styles.apply_styles_to_prompt(prompt, styles, wildcards=not shared.opts.extra_networks_apply_unparsed) prompt_neg = shared.prompt_styles.apply_negative_styles_to_prompt(prompt_neg, styles, wildcards=not shared.opts.extra_networks_apply_unparsed) @@ -21,19 +21,20 @@ def create_toprow(is_img2img: bool = False, id_part: str = None): with gr.Row(): with gr.Column(scale=80): with gr.Row(elem_id=f"{id_part}_prompt_row"): - prompt = gr.Textbox(elem_id=f"{id_part}_prompt", label="Prompt", show_label=False, lines=3, placeholder="Prompt", elem_classes=["prompt"]) + prompt = gr.Textbox(elem_id=f"{id_part}_prompt", label="Prompt", show_label=False, lines=3 if negative_visible else 5, placeholder="Prompt", elem_classes=["prompt"]) with gr.Row(): with gr.Column(scale=80): with gr.Row(elem_id=f"{id_part}_negative_row"): - negative_prompt = gr.Textbox(elem_id=f"{id_part}_neg_prompt", label="Negative prompt", show_label=False, lines=3, placeholder="Negative prompt", elem_classes=["prompt"]) + negative_prompt = gr.Textbox(elem_id=f"{id_part}_neg_prompt", label="Negative prompt", show_label=False, lines=3, placeholder="Negative prompt", elem_classes=["prompt"], visible=negative_visible) with gr.Column(scale=1, elem_id=f"{id_part}_actions_column"): with gr.Row(elem_id=f"{id_part}_generate_box"): reprocess = [] submit = gr.Button('Generate', elem_id=f"{id_part}_generate", variant='primary') - reprocess.append(gr.Button('Reprocess', elem_id=f"{id_part}_reprocess", variant='primary', visible=True)) - reprocess.append(gr.Button('Reprocess decode', elem_id=f"{id_part}_reprocess_decode", variant='primary', visible=False)) - reprocess.append(gr.Button('Reprocess refine', elem_id=f"{id_part}_reprocess_refine", variant='primary', visible=False)) - reprocess.append(gr.Button('Reprocess face', elem_id=f"{id_part}_reprocess_detail", variant='primary', visible=False)) + if reprocess_visible: + reprocess.append(gr.Button('Reprocess', elem_id=f"{id_part}_reprocess", variant='primary', visible=True)) + reprocess.append(gr.Button('Reprocess decode', elem_id=f"{id_part}_reprocess_decode", variant='primary', visible=False)) + reprocess.append(gr.Button('Reprocess refine', elem_id=f"{id_part}_reprocess_refine", variant='primary', visible=False)) + reprocess.append(gr.Button('Reprocess face', elem_id=f"{id_part}_reprocess_detail", variant='primary', visible=False)) with gr.Row(elem_id=f"{id_part}_generate_line2"): interrupt = gr.Button('Stop', elem_id=f"{id_part}_interrupt") interrupt.click(fn=lambda: shared.state.interrupt(), _js="requestInterrupt", inputs=[], outputs=[]) @@ -79,9 +80,9 @@ def ar_change(ar, width, height): return gr.update(), gr.update() -def create_resolution_inputs(tab): - width = gr.Slider(minimum=64, maximum=4096, step=8, label="Width", value=1024, elem_id=f"{tab}_width") - height = gr.Slider(minimum=64, maximum=4096, step=8, label="Height", value=1024, elem_id=f"{tab}_height") +def create_resolution_inputs(tab, default_width=1024, default_height=1024): + width = gr.Slider(minimum=64, maximum=4096, step=8, label="Width", value=default_width, elem_id=f"{tab}_width") + height = gr.Slider(minimum=64, maximum=4096, step=8, label="Height", value=default_height, elem_id=f"{tab}_height") ar_list = ['AR'] + [x.strip() for x in shared.opts.aspect_ratios.split(',') if x.strip() != ''] ar_dropdown = gr.Dropdown(show_label=False, interactive=True, choices=ar_list, value=ar_list[0], elem_id=f"{tab}_ar", elem_classes=["ar-dropdown"]) for c in [ar_dropdown, width, height]: diff --git a/modules/ui_txt2img.py b/modules/ui_txt2img.py index 30f9ab5ad..42f57fc26 100644 --- a/modules/ui_txt2img.py +++ b/modules/ui_txt2img.py @@ -1,6 +1,6 @@ import gradio as gr from modules.call_queue import wrap_gradio_gpu_call, wrap_queued_call -from modules import timer, shared, ui_common, ui_sections, generation_parameters_copypaste, processing, processing_vae, devices +from modules import timer, shared, ui_common, ui_sections, generation_parameters_copypaste, processing, processing_vae, devices, images from modules.ui_components import ToolButton # pylint: disable=unused-import @@ -23,7 +23,7 @@ def create_ui(): txt2img_prompt, txt2img_prompt_styles, txt2img_negative_prompt, txt2img_submit, txt2img_reprocess, txt2img_paste, txt2img_extra_networks_button, txt2img_token_counter, txt2img_token_button, txt2img_negative_token_counter, txt2img_negative_token_button = ui_sections.create_toprow(is_img2img=False, id_part="txt2img") txt_prompt_img = gr.File(label="", elem_id="txt2img_prompt_image", file_count="single", type="binary", visible=False) - txt_prompt_img.change(fn=modules.images.image_data, inputs=[txt_prompt_img], outputs=[txt2img_prompt, txt_prompt_img]) + txt_prompt_img.change(fn=images.image_data, inputs=[txt_prompt_img], outputs=[txt2img_prompt, txt_prompt_img]) with gr.Row(variant='compact', elem_id="txt2img_extra_networks", elem_classes=["extra_networks_root"], visible=False) as extra_networks_ui: from modules import ui_extra_networks diff --git a/modules/ui_video.py b/modules/ui_video.py new file mode 100644 index 000000000..130aebd0d --- /dev/null +++ b/modules/ui_video.py @@ -0,0 +1,130 @@ +# TODO hunyuanvideo: seed, scheduler, scheduler_shift, guidance_scale=1.0, true_cfg_scale=6.0, num_inference_steps=30, prompt_template, vae, offloading +# TODO modernui video tab + +from dataclasses import dataclass +import gradio as gr +from modules import shared, images, ui_common, ui_sections, sd_models, call_queue, generation_parameters_copypaste +from modules.video_models import hunyuan + + +@dataclass +class Model(): + name: str + repo: str + dit: str + + +MODELS = { + 'None': [], + 'Hunyuan Video': [ + Model('None', None, None), + Model('Hunyuan Video T2V', 'hunyuanvideo-community/HunyuanVideo', None), + Model('Hunyuan Video I2V', 'hunyuanvideo-community/HunyuanVideo', 'hunyuanvideo-community/HunyuanVideo-I2V'), # https://github.com/huggingface/diffusers/pull/10983 + Model('SkyReels Hunyuan T2V', 'hunyuanvideo-community/HunyuanVideo', 'Skywork/SkyReels-V1-Hunyuan-T2V'), # https://github.com/huggingface/diffusers/pull/10837 + Model('SkyReels Hunyuan I2V', 'hunyuanvideo-community/HunyuanVideo', 'Skywork/SkyReels-V1-Hunyuan-I2V'), + Model('Fast Hunyuan T2V', 'hunyuanvideo-community/HunyuanVideo', 'hunyuan-video-t2v-720p/transformers/mp_rank_00_model_states.pt'), # https://github.com/hao-ai-lab/FastVideo/blob/8a77cf22c9b9e7f931f42bc4b35d21fd91d24e45/fastvideo/models/hunyuan/inference.py#L213 + ] +} + + +def engine_change(engine): + models = [model.name for model in MODELS.get(engine, [])] + return gr.update(choices=models, value=models[0] if len(models) > 0 else None) + + +def model_change(engine, model): + models = [model.name for model in MODELS.get(engine, [])] + selected = [m for m in MODELS[engine] if m.name == model][0] if len(models) > 0 else None + if selected: + if 'None' in selected.name: + sd_models.unload_model_weights() + msg = 'Video model unloaded' + elif 'Hunyuan' in selected.name: + msg = hunyuan.load(selected) + elif model != 'None': + msg = f'Video model not found: engine={engine} model={model}' + shared.log.error(msg) + else: + sd_models.unload_model_weights() + msg = 'Video model unloaded' + return [msg, gr.update(visible='I2V' in selected.name) if selected else gr.update(visible=False)] + + +def run_video(*args): + engine, model = args[2], args[3] + models = [model.name for model in MODELS.get(engine, [])] + selected = [m for m in MODELS[engine] if m.name == model][0] if len(models) > 0 else None + if selected and 'Hunyuan' in selected.name: + return hunyuan.generate(*args) + shared.log.error(f'Video model not found: args={args}') + return [], '', '', f'Video model not found: engine={engine} model={model}' + + +def create_ui(): + shared.log.debug('UI initialize: txt2img') + with gr.Blocks(analytics_enabled=False) as _video_interface: + prompt, styles, _negative, generate, _reprocess, paste, _networks, _token_counter, _token_button, _token_counter_negative, _token_button_negative = ui_sections.create_toprow(is_img2img=False, id_part="video", negative_visible=False, reprocess_visible=False) + prompt_image = gr.File(label="", elem_id="video_prompt_image", file_count="single", type="binary", visible=False) + prompt_image.change(fn=images.image_data, inputs=[prompt_image], outputs=[prompt, prompt_image]) + + with gr.Row(elem_id="video_interface", equal_height=False): + with gr.Column(variant='compact', elem_id="video_settings", scale=1): + + with gr.Row(): + engine = gr.Dropdown(label='Engine', choices=list(MODELS), value='None') + model = gr.Dropdown(label='Model', choices=[''], value=None) + with gr.Row(): + width, height = ui_sections.create_resolution_inputs('video', default_width=720, default_height=480) + with gr.Row(): + frames = gr.Slider(label='Frames', minimum=1, maximum=1024, step=1, value=15) + with gr.Row(): + with gr.Group(visible=False) as image_group: + gr.HTML("
  Init image") + image = gr.Image(elem_id="video_image", show_label=False, source="upload", interactive=True, type="pil", tool="select", image_mode="RGB", height=512) + with gr.Row(): + save_frames = gr.Checkbox(label='Save image frames', value=False) + with gr.Row(): + cc, duration, loop, pad, interpolate = ui_sections.create_video_inputs(tab='video') + override_settings = ui_common.create_override_inputs('video') + + # output panel with gallery + gallery, gen_info, html_info, _html_info_formatted, html_log = ui_common.create_output_panel("video", prompt=prompt, preview=False, transfer=False, scale=2) + + # handle engine and model change + engine.change(fn=engine_change, inputs=[engine], outputs=[model]) + model.change(fn=model_change, inputs=[engine, model], outputs=[html_log, image_group]) + # handle restore fields + paste_fields = [ + (prompt, "Prompt"), + # main + (width, "Size-1"), + (height, "Size-2"), + (frames, "Frames"), + ] + generation_parameters_copypaste.add_paste_fields("txt2img", None, paste_fields, override_settings) + bindings = generation_parameters_copypaste.ParamBinding(paste_button=paste, tabname="video", source_text_component=prompt, source_image_component=None) + generation_parameters_copypaste.register_paste_params_button(bindings) + # hidden fields + task_id = gr.Textbox(visible=False, value='') + ui_state = gr.Textbox(visible=False, value='') + # generate args + video_args = [ + task_id, ui_state, + engine, model, + prompt, styles, + width, height, + frames, + image, + save_frames, + cc, duration, loop, pad, interpolate, + ] + # generate function + video_dict = dict( + fn=call_queue.wrap_gradio_gpu_call(run_video, extra_outputs=[None, '', ''], name='Video'), + _js="submit_video", + inputs=video_args, + outputs=[gallery, gen_info, html_info, html_log], + show_progress=False, + ) + prompt.submit(**video_dict) + generate.click(**video_dict) diff --git a/modules/video_models/hunyuan.py b/modules/video_models/hunyuan.py new file mode 100644 index 000000000..6fed27cb3 --- /dev/null +++ b/modules/video_models/hunyuan.py @@ -0,0 +1,13 @@ +from modules import shared + + +def load(selected): + msg = f'Video load: model="{selected.name}" repo="{selected.repo}" dit="{selected.dit}"' + shared.log.info(msg) + return msg + + +def generate(*args, **kwargs): + # TODO hunyuanvideo: check if loaded + shared.log.debug(f'Video generate: args={args} kwargs={kwargs}') + return [], '', '', 'TBD'