From 7e51b1ea411279e699dc9584d8424e87f2ffda62 Mon Sep 17 00:00:00 2001 From: Nochnoe <136372012+nochnoe@users.noreply.github.com> Date: Tue, 13 Jun 2023 21:24:20 +0700 Subject: [PATCH] feat: settings options, ui --- .env.example | 1 + LICENSE | 20 +++++++ README.md | 10 ++++ scripts/e621_prompt.py | 116 +++++++++++++++++++++++++++++++++++++++++ 4 files changed, 147 insertions(+) create mode 100644 .env.example create mode 100644 LICENSE create mode 100644 README.md create mode 100644 scripts/e621_prompt.py diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..035232a --- /dev/null +++ b/.env.example @@ -0,0 +1 @@ +PYTHONPATH=../../;../../venv/Lib/site-packages diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..1ad1132 --- /dev/null +++ b/LICENSE @@ -0,0 +1,20 @@ +Copyright (c) 2023 Nochnoe + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..c9a21d5 --- /dev/null +++ b/README.md @@ -0,0 +1,10 @@ +# sd-webui-e621-prompt + +Request tags of an image from e621, inspired by [`sd-webui-gelbooru-prompt`](https://github.com/antis0007/sd-webui-gelbooru-prompt). + +**Note**: I'm not a Python developer and haven't touched this language for a few years. This extension was hacked together using some other sd-webui extensions sources, stackoverflow answers and pure luck. Feel free to submit PR's fixing this unholy mess. + +## Development + +1. Clone into `extensions` folder +2. `cp .env.example .env` for setting `PYTHONPATH`. It is required for IDE autocompletions to work (tested with vscode, if you use PyCharm your mileage may vary). If you using unix-like OS, replace semicolon with a colon diff --git a/scripts/e621_prompt.py b/scripts/e621_prompt.py new file mode 100644 index 0000000..b16fd40 --- /dev/null +++ b/scripts/e621_prompt.py @@ -0,0 +1,116 @@ +import gradio as gr + +from modules import scripts, script_callbacks +from modules.ui_components import DropdownMulti +from modules.shared import opts, OptionInfo + +from urllib.request import urlopen +from hashlib import md5 + +NAME = "e621 Prompt" + +# List of tags categories. Shared between settings and UI +tags_categories_options = ["general", "species", "character", "copyright", "artist", "invalid", "lore", "meta", "rating"] + +class E621PromptScript(scripts.Script): + def title(self): + return NAME + + def show(self, _is_img2img): + return scripts.AlwaysVisible + + def clear_callback(self): + return opts.e621_prompt_included_tags_categories, None, None, None + + def generate_callback(self, source, file_source, categories): + if not source or file_source is None: + return "ERROR: No input provided" + + if not categories: + return "ERROR: No categories selected" + + return "result will be here" + + # Calculates hash of the uploaded image, and puts it inside of "source" field + def image_upload_callback(self, source, image): + if image is None: + return source + + with urlopen(image) as response: + return md5(response.read()).hexdigest() + + def ui(self, _is_img2img): + with gr.Group(): + with gr.Accordion(NAME, open=False): + source = gr.Textbox(label="Source", value="", placeholder="e621 post link / e621 post id / md5 hash of the image") + + file_source = gr.Image( + source="upload", + label="or upload image for hash calculation", + type="numpy" + ) + + file_source.upload(self.image_upload_callback, inputs=[source, file_source], outputs=[source], preprocess=False) + + categories = gr.Dropdown( + tags_categories_options, + multiselect=True, + value=opts.e621_prompt_included_tags_categories, + label="Categories" + ) + + result = gr.Textbox(value="", label="Result", lines=5, interactive=False).style(show_copy_button=True) + + with gr.Row(): + with gr.Column(): + clear_btn = gr.Button("Reset form") + clear_btn.click(fn=self.clear_callback, inputs=None, outputs=[categories, result, source, file_source]) + with gr.Column(): + generate_btn = gr.Button("Generate", variant="primary") + generate_btn.click(fn=self.generate_callback, inputs=[source, file_source, categories], outputs=[result]) + +def on_ui_settings(): + default_tags_categories = ["artist", "species", "character", "general"] + default_excluded_tags = ", ".join([ + "comic", "watermark", "text", "sign", "patreon_logo", "censored", "censored_genitalia", "censored_penis", "censored_pussy", + "censored_text", "censored_anus", "multiple_poses", "multiple_images", "dialogue", "speech_bubble", "english_text", "dialogue_box", + ]) + + section = ("e621-prompt", NAME) + + settings_options = [ + ("e621_prompt_username", "", "e621 Username. Not required, but highly preferred"), + ("e621_prompt_api_key", "", "e621 API Key. Not required, but highly preferred"), + ( + "e621_prompt_user_agent", + "sd-webui-e621-prompt (nochnoe)", + "User-Agent for API calls. DO NOT change this line if you don't know what you're doing" + ), + ("e621_prompt_use_proxy", False, "Use proxy when accessing e621"), + ("e621_prompt_proxy_url", "", "HTTPS proxy"), + ( + "e621_prompt_included_tags_categories", + default_tags_categories, + "Default tag categories to include", + DropdownMulti, + lambda: {"choices": tags_categories_options} + ), + ("e621_prompt_excluded_tags", default_excluded_tags, "Tags that always should be EXCLUDED from the final result. Comma-separated, with underscores"), + ("e621_prompt_appended_tags", "", "Tags that always should be APPENDED to the final result. Comma-separated, with underscores"), + ("e621_prompt_replace_underscores", True, "Replace underscores with spaces"), + ("e621_prompt_replace_underscores_in_appended", True, "Replace underscores with spaces in the appended tags"), + ("e621_prompt_artist_prefix", "", "Prefix for artists (for example, artist:)"), + ("e621_prompt_meta_prefix", "", "Prefix for meta tags (for example, meta:)"), + ("e621_prompt_species_prefix", "", "Prefix for species tags (for example, species:)"), + ("e621_prompt_character_prefix", "", "Prefix for characters tags (for example, character:)"), + ("e621_prompt_lore_prefix", "", "Prefix for lore tags (for example, lore:)"), + ("e621_prompt_general_prefix", "", "Prefix for general tags (for example, general:)"), + ("e621_prompt_copyright_prefix", "", "Prefix for copyright tags (for example, copyright:)"), + ("e621_prompt_rating_prefix", "rating:", "Prefix for rating (for example, rating:)"), + ("e621_prompt_rating_format", "short", "Rating format (short: e/s/q, full: explicit/safe/questionable)", gr.Dropdown, lambda: {"choices": ["short", "full"]}), + ] + + for setting_name, *data in settings_options: + opts.add_option(setting_name, OptionInfo(*data, section=section)) + +script_callbacks.on_ui_settings(on_ui_settings)