from io import BytesIO import random import requests import modules.scripts as scripts import gradio as gr import os from PIL import Image import numpy as np import importlib import requests_cache from modules.processing import process_images, StableDiffusionProcessingImg2Img from modules import shared from modules.sd_hijack import model_hijack from modules import deepbooru from modules.ui_components import InputAccordion extension_root = scripts.basedir() user_data_dir = os.path.join(extension_root, 'user') user_search_dir = os.path.join(user_data_dir, 'search') user_remove_dir = os.path.join(user_data_dir, 'remove') os.makedirs(user_search_dir, exist_ok=True) os.makedirs(user_remove_dir, exist_ok=True) if not os.path.isfile(os.path.join(user_search_dir, 'tags_search.txt')): with open(os.path.join(user_search_dir, 'tags_search.txt'), 'w'): pass if not os.path.isfile(os.path.join(user_remove_dir, 'tags_remove.txt')): with open(os.path.join(user_remove_dir, 'tags_remove.txt'), 'w'): pass COLORED_BG = ['black_background', 'aqua_background', 'white_background', 'colored_background', 'gray_background', 'blue_background', 'green_background', 'red_background', 'brown_background', 'purple_background', 'yellow_background', 'orange_background', 'pink_background', 'plain', 'transparent_background', 'simple_background', 'two-tone_background', 'grey_background'] ADD_BG = ['outdoors', 'indoors'] BW_BG = ['monochrome', 'greyscale', 'grayscale'] POST_AMOUNT = 100 COUNT = 100 #Number of images the search returned. Booru classes below were modified to update this value with the latest search result count. DEBUG = False RATING_TYPES = { "none": { "All": "All" }, "full": { "All": "All", "Safe": "safe", "Questionable": "questionable", "Explicit": "explicit" }, "single": { "All": "All", "Safe": "g", "Sensitive": "s", "Questionable": "q", "Explicit": "e" } } RATINGS = { "e621": RATING_TYPES['full'], "danbooru": RATING_TYPES['single'], "aibooru": RATING_TYPES['full'], "yande.re": RATING_TYPES['full'], "konachan": RATING_TYPES['full'], "safebooru": RATING_TYPES['none'], "rule34": RATING_TYPES['full'], "xbooru": RATING_TYPES['full'], "gelbooru": RATING_TYPES['single'] } def get_available_ratings(booru): mature_ratings = gr.Radio.update(choices=RATINGS[booru].keys(), value="All") return mature_ratings def show_fringe_benefits(booru): if booru == 'gelbooru': return gr.Checkbox.update(visible=True) else: return gr.Checkbox.update(visible=False) def check_exception(booru, parameters): post_id = parameters.get('post_id') tags = parameters.get('tags') if booru == 'konachan' and post_id: raise Exception("Konachan does not support post IDs") if booru == 'yande.re' and post_id: raise Exception("Yande.re does not support post IDs") if booru == 'e621' and post_id: raise Exception("e621 does not support post IDs") if booru == 'danbooru' and len(tags.split(',')) > 1: raise Exception("Danbooru does not support multiple tags. You can have only one tag.") class Booru(): def __init__(self, booru, booru_url): self.booru = booru self.booru_url = booru_url self.headers = {'user-agent': 'my-app/0.0.1'} def get_data(self, add_tags, max_pages=10, id=''): pass def get_post(self, add_tags, max_pages=10, id=''): pass class Gelbooru(Booru): def __init__(self, fringe_benefits): super().__init__('gelbooru', f'https://gelbooru.com/index.php?page=dapi&s=post&q=index&json=1&limit={POST_AMOUNT}') self.fringeBenefits = fringe_benefits def get_data(self, add_tags, max_pages=10, id=''): global COUNT loop_msg = True # avoid showing same msg twice for loop in range(2): # run loop at most twice if id: add_tags = '' self.booru_url = f"{self.booru_url}&pid={random.randint(0, max_pages-1)}{id}{add_tags}" # The randint function is an alias to randrange(a, b+1), so 'max_pages' should be passed as 'max_pages-1' if self.fringeBenefits: res = requests.get(self.booru_url, cookies={'fringeBenefits': 'yup'}) else: res = requests.get(self.booru_url) data = res.json() COUNT = data['@attributes']['count'] if COUNT <= max_pages*POST_AMOUNT: max_pages = COUNT // POST_AMOUNT+1 # If max_pages is bigger than available pages, loop the function with updated max_pages based on the value of COUNT while loop_msg: print(f" Processing {COUNT} results.") loop_msg = False # avoid showing same msg twice continue else: print(f" Processing {max_pages*POST_AMOUNT} out of {COUNT} results.") break return data def get_post(self, add_tags, max_pages=10, id=''): return self.get_data(add_tags, max_pages, "&id=" + id) class XBooru(Booru): def __init__(self): super().__init__('xbooru', f'https://xbooru.com/index.php?page=dapi&s=post&q=index&json=1&limit={POST_AMOUNT}') def get_data(self, add_tags, max_pages=10, id=''): global COUNT loop_msg = True # avoid showing same msg twice for loop in range(2): # run loop at most twice if id: add_tags = '' self.booru_url = f"{self.booru_url}&pid={random.randint(0, max_pages-1)}{id}{add_tags}" print(self.booru_url) res = requests.get(self.booru_url) data = res.json() COUNT = 0 for post in data: post['file_url'] = f"https://xbooru.com/images/{post['directory']}/{post['image']}" COUNT += 1 if COUNT <= max_pages*POST_AMOUNT: max_pages = COUNT // POST_AMOUNT+1 # If max_pages is bigger than available pages, loop the function with updated max_pages based on the value of COUNT while loop_msg: print(f" Processing {COUNT} results.") loop_msg = False # avoid showing same msg twice continue else: print(f" Processing {max_pages*POST_AMOUNT} out of {COUNT} results.") break return {'post': data} def get_post(self, add_tags, max_pages=10, id=''): return self.get_data(add_tags, max_pages, "&id=" + id) class Rule34(Booru): def __init__(self): super().__init__('rule34', f'https://api.rule34.xxx/index.php?page=dapi&s=post&q=index&json=1&limit={POST_AMOUNT}') def get_data(self, add_tags, max_pages=10, id=''): global COUNT loop_msg = True # avoid showing same msg twice for loop in range(2): # run loop at most twice if id: add_tags = '' self.booru_url = f"{self.booru_url}&pid={random.randint(0, max_pages-1)}{id}{add_tags}" res = requests.get(self.booru_url) data = res.json() COUNT = len(data) if COUNT == 0: max_pages = 2 # Rule34 does not have a way to know the amount of results available in the search, so we need to run the function again with a fixed amount of pages while loop_msg: print(f" Processing {COUNT} results.") loop_msg = False # avoid showing same msg twice continue else: print(f"Found enough results") break return {'post': data} def get_post(self, add_tags, max_pages=10, id=''): return self.get_data(add_tags, max_pages, "&id=" + id) class Safebooru(Booru): def __init__(self): super().__init__('safebooru', f'https://safebooru.org/index.php?page=dapi&s=post&q=index&json=1&limit={POST_AMOUNT}') def get_data(self, add_tags, max_pages=10, id=''): global COUNT loop_msg = True # avoid showing same msg twice for loop in range(2): # run loop at most twice if id: add_tags = '' self.booru_url = f"{self.booru_url}&pid={random.randint(0, max_pages-1)}{id}{add_tags}" res = requests.get(self.booru_url) data = res.json() COUNT = 0 for post in data: post['file_url'] = f"https://safebooru.org/images/{post['directory']}/{post['image']}" COUNT += 1 if COUNT <= max_pages*POST_AMOUNT: max_pages = COUNT // POST_AMOUNT+1 # If max_pages is bigger than available pages, loop the function with updated max_pages based on the value of COUNT while loop_msg: print(f" Processing {COUNT} results.") loop_msg = False # avoid showing same msg twice continue else: print(f" Processing {max_pages*POST_AMOUNT} out of {COUNT} results.") break return {'post': data} def get_post(self, add_tags, max_pages=10, id=''): return self.get_data(add_tags, max_pages, "&id=" + id) class Konachan(Booru): def __init__(self): super().__init__('konachan', f'https://konachan.com/post.json?limit={POST_AMOUNT}') def get_data(self, add_tags, max_pages=10, id=''): global COUNT loop_msg = True # avoid showing same msg twice for loop in range(2): # run loop at most twice if id: add_tags = '' self.booru_url = f"{self.booru_url}&page={random.randint(0, max_pages-1)}{id}{add_tags}" res = requests.get(self.booru_url) data = res.json() COUNT = len(data) if COUNT == 0: max_pages = 2 # Konachan does not have a way to know the amount of results available in the search, so we need to run the function again with a fixed amount of pages while loop_msg: print(f" Processing {COUNT} results.") loop_msg = False # avoid showing same msg twice continue else: print(f"Found enough results") break return {'post': data} def get_post(self, add_tags, max_pages=10, id=''): raise Exception("Konachan does not support post IDs") class Yandere(Booru): def __init__(self): super().__init__('yande.re', f'https://yande.re/post.json?limit={POST_AMOUNT}') def get_data(self, add_tags, max_pages=10, id=''): global COUNT loop_msg = True # avoid showing same msg twice for loop in range(2): # run loop at most twice if id: add_tags = '' self.booru_url = f"{self.booru_url}&page={random.randint(0, max_pages-1)}{id}{add_tags}" res = requests.get(self.booru_url) data = res.json() COUNT = len(data) COUNT = len(data) if COUNT == 0: max_pages = 2 # Yandere does not have a way to know the amount of results available in the search, so we need to run the function again with a fixed amount of pages while loop_msg: print(f" Processing {COUNT} results.") loop_msg = False # avoid showing same msg twice continue else: print(f"Found enough results") break return {'post': data} def get_post(self, add_tags, max_pages=10, id=''): raise Exception("Yande.re does not support post IDs") class AIBooru(Booru): def __init__(self): super().__init__('AIBooru', f'https://aibooru.online/posts.json?limit={POST_AMOUNT}') def get_data(self, add_tags, max_pages=10, id=''): global COUNT loop_msg = True # avoid showing same msg twice for loop in range(2): # run loop at most twice if id: add_tags = '' self.booru_url = f"{self.booru_url}&page={random.randint(0, max_pages-1)}{id}{add_tags}" res = requests.get(self.booru_url) data = res.json() for post in data: post['tags'] = post['tag_string'] COUNT = len(data) if COUNT == 0: max_pages = 2 # AIBooru does not have a way to know the amount of results available in the search, so we need to run the function again with a fixed amount of pages while loop_msg: print(f" Processing {COUNT} results.") loop_msg = False # avoid showing same msg twice continue else: print(f"Found enough results") break return {'post': data} def get_post(self, add_tags, max_pages=10, id=''): raise Exception("AIBooru does not support post IDs") class Danbooru(Booru): def __init__(self): super().__init__('danbooru', f'https://danbooru.donmai.us/posts.json?limit={POST_AMOUNT}') def get_data(self, add_tags, max_pages=10, id=''): global COUNT loop_msg = True # avoid showing same msg twice for loop in range(2): # run loop at most twice if id: add_tags = '' self.booru_url = f"{self.booru_url}&page={random.randint(0, max_pages-1)}{id}{add_tags}" res = requests.get(self.booru_url, headers=self.headers) data = res.json() for post in data: post['tags'] = post['tag_string'] COUNT = len(data) if COUNT == 0: max_pages = 2 # Danbooru does not have a way to know the amount of results available in the search, so we need to run the function again with a fixed amount of pages while loop_msg: print(f" Processing {COUNT} results.") loop_msg = False # avoid showing same msg twice continue else: print(f"Found enough results") break return {'post': data} def get_post(self, add_tags, max_pages=10, id=''): self.booru_url = f"https://danbooru.donmai.us/posts/{id}.json" res = requests.get(self.booru_url, headers=self.headers) data = res.json() data['tags'] = data['tag_string'] data = {'post': [data]} return data class e621(Booru): def __init__(self): super().__init__('danbooru', f'https://e621.net/posts.json?limit={POST_AMOUNT}') def get_data(self, add_tags, max_pages=10, id=''): global COUNT loop_msg = True # avoid showing same msg twice for loop in range(2): # run loop at most twice if id: add_tags = '' self.booru_url = f"{self.booru_url}&page={random.randint(0, max_pages-1)}{id}{add_tags}" res = requests.get(self.booru_url, headers=self.headers) data = res.json()['posts'] COUNT = len(data) for post in data: temp_tags = [] sublevels = ['general', 'artist', 'copyright', 'character', 'species'] for sublevel in sublevels: temp_tags.extend(post['tags'][sublevel]) post['tags'] = ' '.join(temp_tags) post['score'] = post['score']['total'] if COUNT <= max_pages*POST_AMOUNT: max_pages = COUNT // POST_AMOUNT+1 # If max_pages is bigger than available pages, loop the function with updated max_pages based on the value of COUNT while loop_msg: print(f" Processing {COUNT} results.") loop_msg = False # avoid showing same msg twice continue else: print(f" Processing {max_pages*POST_AMOUNT} out of {COUNT} results.") break return {'post': data} def get_post(self, add_tags, max_pages=10, id=''): self.get_data(add_tags, max_pages, "&id=" + id) def generate_chaos(pos_tags, neg_tags, chaos_amount): """Generates chaos in the prompt by adding random tags from the prompt to the positive and negative prompts Args: pos_tags (str): the positive prompt neg_tags (str): the negative prompt chaos_amount (float): the percentage of tags to put in the positive prompt Returns: str: the positive prompt str: the negative prompt """ # create a list with the tags in the prompt and in the negative prompt chaos_list = [tag for tag in pos_tags.split(',') + neg_tags.split(',') if tag.strip() != ''] # distinct the list chaos_list = list(set(chaos_list)) random.shuffle(chaos_list) # put 50% of the tags in the prompt and the remaining 50% in the negative prompt len_list = round(len(chaos_list) * chaos_amount) pos_list = chaos_list[len_list:] pos_prompt = ','.join(pos_list) neg_list = chaos_list[:len_list] random.shuffle(neg_list) neg_prompt = ','.join(neg_list) return pos_prompt, neg_prompt def resize_image(img, width, height, cropping=True): """Resize image to specified width and height Args: img (PIL.Image): the image width (int): the width in pixels height (int): the height in pixels cropping (bool): whether to crop the image or not Returns: PIL.Image: the resized image """ if cropping: # resize the picture and center crop it # example: you have a 100x200 picture and width=300 and height=300 # resize to 300x600 and crop to 300x300 from the center x, y = img.size if x < y: # scale to width keeping aspect ratio wpercent = (width / float(img.size[0])) hsize = int((float(img.size[1]) * float(wpercent))) img_new = img.resize((width, hsize)) if img_new.size[1] < height: # scale to height keeping aspect ratio hpercent = (height / float(img.size[1])) wsize = int((float(img.size[0]) * float(hpercent))) img_new = img.resize((wsize, height)) else: ypercent = (height / float(img.size[1])) wsize = int((float(img.size[0]) * float(ypercent))) img_new = img.resize((wsize, height)) if img_new.size[0] < width: xpercent = (width / float(img.size[0])) hsize = int((float(img.size[1]) * float(xpercent))) img_new = img.resize((width, hsize)) # crop center x, y = img_new.size left = (x - width) / 2 top = (y - height) / 2 right = (x + width) / 2 bottom = (y + height) / 2 img = img_new.crop((left, top, right, bottom)) else: img = img.resize((width, height)) return img def modify_prompt(prompt, tagged_prompt, type_deepbooru): """Modifies the prompt based on the type_deepbooru selected Args: prompt (str): the prompt tagged_prompt (str): the prompt tagged by deepbooru type_deepbooru (str): the type of modification Returns: str: the modified prompt """ if type_deepbooru == 'Add Before': return tagged_prompt + ',' + prompt elif type_deepbooru == 'Add After': return prompt + ',' + tagged_prompt elif type_deepbooru == 'Replace': return tagged_prompt return prompt def remove_repeated_tags(prompt): """Removes the repeated tags keeping the same order Args: prompt (str): the prompt Returns: str: the prompt without repeated tags """ prompt = prompt.split(',') new_prompt = [] for tag in prompt: if tag not in new_prompt: new_prompt.append(tag) return ','.join(new_prompt) def limit_prompt_tags(prompt, limit_tags, mode): """Limits the amount of tags in the prompt. It can be done by percentage or by a fixed amount. Args: prompt (str): the prompt limit_tags (float): the percentage of tags to keep mode (str): 'Limit' or 'Max' Returns: str: the prompt with the limited amount of tags """ clean_prompt = prompt.split(',') if mode == 'Limit': clean_prompt = clean_prompt[:int(len(clean_prompt) * limit_tags)] elif mode == 'Max': clean_prompt = clean_prompt[:limit_tags] return ','.join(clean_prompt) class Script(scripts.Script): previous_loras = '' last_img = [] real_steps = 0 version = "1.2" original_prompt = '' def get_files(self, path): files = [] for file in os.listdir(path): if file.endswith('.txt'): files.append(file) return files def hide_object(self, obj, booru): print(f'hide_object: {obj}, {booru.value}') if booru.value == 'konachan' or booru.value == 'yande.re': obj.interactive = False else: obj.interactive = True def title(self): return "Ranbooru" def show(self, is_img2img): return scripts.AlwaysVisible def refresh_ser(self): return gr.update(choices=self.get_files(user_search_dir)) def refresh_rem(self): return gr.update(choices=self.get_files(user_remove_dir)) def ui(self, is_img2img): with gr.Group(): with InputAccordion(False, label="Ranbooru", elem_id=self.elem_id("ra_enable")) as enabled: booru = gr.Dropdown( ["gelbooru", "rule34", "safebooru", "danbooru", "konachan", 'yande.re', 'aibooru', 'xbooru', 'e621'], label="Booru", value="gelbooru") max_pages = gr.Slider(label="Max Pages", minimum=1, maximum=100, value=100, step=1) gr.Markdown("""## Post""") post_id = gr.Textbox(lines=1, label="Post ID") gr.Markdown("""## Tags""") tags = gr.Textbox(lines=1, label="Tags to Search (Pre)") remove_tags = gr.Textbox(lines=1, label="Tags to Remove (Post)") mature_rating = gr.Radio(list(RATINGS['gelbooru']), label="Mature Rating", value="All") remove_bad_tags = gr.Checkbox(label="Remove bad tags", value=True) shuffle_tags = gr.Checkbox(label="Shuffle tags", value=True) change_dash = gr.Checkbox(label='Convert "_" to spaces', value=False) same_prompt = gr.Checkbox(label="Use same prompt for all images", value=False) fringe_benefits = gr.Checkbox(label="Fringe Benefits", value=True) limit_tags = gr.Slider(value=1.0, label="Limit tags", minimum=0.05, maximum=1.0, step=0.05) max_tags = gr.Slider(value=100, label="Max tags", minimum=1, maximum=100, step=1) change_background = gr.Radio(["Don't Change", "Add Background", "Remove Background", "Remove All"], label="Change Background", value="Don't Change") change_color = gr.Radio(["Don't Change", "Colored", "Limited Palette", "Monochrome"], label="Change Color", value="Don't Change") sorting_order = gr.Radio(["Random", "High Score", "Low Score"], label="Sorting Order", value="Random") booru.change(get_available_ratings, booru, mature_rating) # update available ratings booru.change(show_fringe_benefits, booru, fringe_benefits) # display fringe benefits checkbox if gelbooru is selected gr.Markdown("""\n---\n""") with gr.Group(): with gr.Accordion("Img2Img", open=False): use_img2img = gr.Checkbox(label="Use img2img", value=False) use_ip = gr.Checkbox(label="Send to Controlnet", value=False) denoising = gr.Slider(value=0.75, label="Denoising", minimum=0.05, maximum=1.0, step=0.05) use_last_img = gr.Checkbox(label="Use last image as img2img", value=False) crop_center = gr.Checkbox(label="Crop Center", value=False) use_deepbooru = gr.Checkbox(label="Use Deepbooru", value=False) type_deepbooru = gr.Radio(["Add Before", "Add After", "Replace"], label="Deepbooru Tags Position", value="Add Before") with gr.Group(): with gr.Accordion("File", open=False): use_search_txt = gr.Checkbox(label="Use tags_search.txt", value=False) choose_search_txt = gr.Dropdown(self.get_files(user_search_dir), label="Choose tags_search.txt", value="") search_refresh_btn = gr.Button("Refresh") use_remove_txt = gr.Checkbox(label="Use tags_remove.txt", value=False) choose_remove_txt = gr.Dropdown(self.get_files(user_remove_dir), label="Choose tags_remove.txt", value="") remove_refresh_btn = gr.Button("Refresh") with gr.Group(): with gr.Accordion("Extra", open=False): with gr.Box(): mix_prompt = gr.Checkbox(label="Mix prompts", value=False) mix_amount = gr.Slider(value=2, label="Mix amount", minimum=2, maximum=10, step=1) with gr.Box(): chaos_mode = gr.Radio(["None", "Chaos", "Less Chaos"], label="Chaos Mode", value="None") chaos_amount = gr.Slider(value=0.5, label="Chaos Amount %", minimum=0.1, maximum=1, step=0.05) with gr.Box(): negative_mode = gr.Radio(["None", "Negative"], label="Negative Mode", value="None") use_same_seed = gr.Checkbox(label="Use same seed for all pictures", value=False) with gr.Box(): use_cache = gr.Checkbox(label="Use cache", value=True) with gr.Group(): with InputAccordion(False, label="LoRAnado", elem_id=self.elem_id("lo_enable")) as lora_enabled: with gr.Box(): lora_lock_prev = gr.Checkbox(label="Lock previous LoRAs", value=False) lora_folder = gr.Textbox(lines=1, label="LoRAs Subfolder") lora_amount = gr.Slider(default=1, label="LoRAs Amount", minimum=1, maximum=10, step=1) with gr.Box(): lora_min = gr.Slider(value=-1.0, label="Min LoRAs Weight", minimum=-1.0, maximum=1, step=0.1) lora_max = gr.Slider(value=1.0, label="Max LoRAs Weight", minimum=-1.0, maximum=1.0, step=0.1) lora_custom_weights = gr.Textbox(lines=1, label="LoRAs Custom Weights") search_refresh_btn.click( fn=self.refresh_ser, inputs=[], outputs=[choose_search_txt] ) remove_refresh_btn.click( fn=self.refresh_rem, inputs=[], outputs=[choose_remove_txt] ) return [enabled, tags, booru, remove_bad_tags, max_pages, change_dash, same_prompt, fringe_benefits, remove_tags, use_img2img, denoising, use_last_img, change_background, change_color, shuffle_tags, post_id, mix_prompt, mix_amount, chaos_mode, negative_mode, chaos_amount, limit_tags, max_tags, sorting_order, mature_rating, lora_folder, lora_amount, lora_min, lora_max, lora_enabled, lora_custom_weights, lora_lock_prev, use_ip, use_search_txt, use_remove_txt, choose_search_txt, choose_remove_txt, search_refresh_btn, remove_refresh_btn, crop_center, use_deepbooru, type_deepbooru, use_same_seed, use_cache] def check_orientation(self, img): """Check if image is portrait, landscape or square""" x, y = img.size if x / y > 1.2: return [768, 512] elif y / x > 1.2: return [512, 768] else: return [768, 768] def loranado(self, lora_enabled, lora_folder, lora_amount, lora_min, lora_max, lora_custom_weights, p, lora_lock_prev): lora_prompt = '' if lora_enabled: if lora_lock_prev: lora_prompt = self.previous_loras else: loras = [] loras = os.listdir(f'{lora_folder}') # get only .safetensors files loras = [lora.replace('.safetensors', '') for lora in loras if lora.endswith('.safetensors')] for l in range(0, lora_amount): lora_weight = 0 if lora_custom_weights != '': lora_weight = float(lora_custom_weights.split(',')[l]) while lora_weight == 0: lora_weight = round(random.uniform(lora_min, lora_max), 1) lora_prompt += f'' self.previous_loras = lora_prompt if lora_prompt: if isinstance(p.prompt, list): for num, pr in enumerate(p.prompt): p.prompt[num] = f'{lora_prompt} {pr}' else: p.prompt = f'{lora_prompt} {p.prompt}' return p def before_process(self, p, enabled, tags, booru, remove_bad_tags, max_pages, change_dash, same_prompt, fringe_benefits, remove_tags, use_img2img, denoising, use_last_img, change_background, change_color, shuffle_tags, post_id, mix_prompt, mix_amount, chaos_mode, negative_mode, chaos_amount, limit_tags, max_tags, sorting_order, mature_rating, lora_folder, lora_amount, lora_min, lora_max, lora_enabled, lora_custom_weights, lora_lock_prev, use_ip, use_search_txt, use_remove_txt, choose_search_txt, choose_remove_txt, search_refresh_btn, remove_refresh_btn, crop_center, use_deepbooru, type_deepbooru, use_same_seed, use_cache): # Manage Cache if use_cache and not requests_cache.patcher.is_installed(): requests_cache.install_cache('ranbooru_cache', backend='sqlite', expire_after=3600) elif not use_cache and requests_cache.patcher.is_installed(): requests_cache.uninstall_cache() if enabled: # Initialize APIs booru_apis = { 'gelbooru': Gelbooru(fringe_benefits), 'rule34': Rule34(), 'safebooru': Safebooru(), 'danbooru': Danbooru(), 'konachan': Konachan(), 'yande.re': Yandere(), 'aibooru': AIBooru(), 'xbooru': XBooru(), 'e621': e621(), } self.original_prompt = p.prompt # Check if compatible check_exception(booru, {'tags': tags, 'post_id': post_id}) # Manage Bad Tags bad_tags = [] if remove_bad_tags: bad_tags = ['mixed-language_text', 'watermark', 'text', 'english_text', 'speech_bubble', 'signature', 'artist_name', 'censored', 'bar_censor', 'translation', 'twitter_username', "twitter_logo", 'patreon_username', 'commentary_request', 'tagme', 'commentary', 'character_name', 'mosaic_censoring', 'instagram_username', 'text_focus', 'english_commentary', 'comic', 'translation_request', 'fake_text', 'translated', 'paid_reward_available', 'thought_bubble', 'multiple_views', 'silent_comic', 'out-of-frame_censoring', 'symbol-only_commentary', '3koma', '2koma', 'character_watermark', 'spoken_question_mark', 'japanese_text', 'spanish_text', 'language_text', 'fanbox_username', 'commission', 'original', 'ai_generated', 'stable_diffusion', 'tagme_(artist)', 'text_bubble', 'qr_code', 'chinese_commentary', 'korean_text', 'partial_commentary', 'chinese_text', 'copyright_request', 'heart_censor', 'censored_nipples', 'page_number', 'scan', 'fake_magazine_cover', 'korean_commentary'] if ',' in remove_tags: bad_tags.extend(remove_tags.split(',')) else: bad_tags.append(remove_tags) if use_remove_txt: bad_tags.extend(open(os.path.join(user_remove_dir, choose_remove_txt), 'r').read().split(',')) # Manage Backgrounds background_options = { 'Add Background': ('detailed_background,' + random.choice(["outdoors", "indoors"]), COLORED_BG), 'Remove Background': ('plain_background,simple_background,' + random.choice(COLORED_BG), ADD_BG), 'Remove All': ('', COLORED_BG + ADD_BG) } if change_background in background_options: prompt_addition, tags_to_remove = background_options[change_background] bad_tags.extend(tags_to_remove) p.prompt = f'{p.prompt},{prompt_addition}' if p.prompt else prompt_addition # Manage Colors color_options = { 'Colored': BW_BG, 'Limited Palette': '(limited_palette:1.3)', 'Monochrome': ','.join(BW_BG) } if change_color in color_options: color_option = color_options[change_color] if isinstance(color_option, list): bad_tags.extend(color_option) else: p.prompt = f'{p.prompt},{color_option}' if p.prompt else color_option if use_search_txt: search_tags = open(os.path.join(user_search_dir, choose_search_txt), 'r').read() search_tags_r = search_tags.replace(" ", "") split_tags = search_tags_r.splitlines() filtered_tags = [line for line in split_tags if line.strip()] rand_selected = random.randint(0, len(filtered_tags) - 1) selected_tags = filtered_tags[rand_selected] tags = f'{tags},{selected_tags}' if tags else selected_tags add_tags = '&tags=-animated' if tags: add_tags += f'+{tags.replace(",", "+")}' if mature_rating != 'All': add_tags += f'+rating:{RATINGS[booru][mature_rating]}' # Getting Data random_post = {'preview_url': ''} prompts = [] last_img = [] preview_urls = [] api_url = booru_apis.get(booru, Gelbooru(fringe_benefits)) print(f'Using {booru}') # Manage Post ID if post_id: data = api_url.get_post(add_tags, max_pages, post_id) else: data = api_url.get_data(add_tags, max_pages) print(api_url.booru_url) # Replace null scores with 0s for post in data['post']: post['score'] = post.get('score', 0) # Sort based on sorting_order if sorting_order == 'High Score': data['post'] = sorted(data['post'], key=lambda k: k.get('score', 0), reverse=True) elif sorting_order == 'Low Score': data['post'] = sorted(data['post'], key=lambda k: k.get('score', 0)) if post_id: print(f'Using post ID: {post_id}') random_numbers = [0 for _ in range(0, p.batch_size * p.n_iter)] else: random_numbers = self.random_number(sorting_order, p.batch_size * p.n_iter) for random_number in random_numbers: if same_prompt: random_post = data['post'][random_numbers[0]] else: if mix_prompt: temp_tags = [] max_tags = 0 for _ in range(0, mix_amount): if not post_id: random_mix_number = self.random_number(sorting_order, 1)[0] temp_tags.extend(data['post'][random_mix_number]['tags'].split(' ')) max_tags = max(max_tags, len(data['post'][random_mix_number]['tags'].split(' '))) # distinct temp_tags temp_tags = list(set(temp_tags)) random_post = data['post'][random_number] max_tags = min(max(len(temp_tags), 20), max_tags) random_post['tags'] = ' '.join(random.sample(temp_tags, max_tags)) else: try: random_post = data['post'][random_number] except IndexError: raise Exception( "No posts found with those tags. Try lowering the pages or changing the tags.") clean_tags = random_post['tags'].replace('(', r'\(').replace(')', r'\)') temp_tags = random.sample(clean_tags.split(' '), len(clean_tags.split(' '))) if shuffle_tags else clean_tags.split(' ') prompts.append(','.join(temp_tags)) preview_urls.append(random_post.get('file_url', 'https://pic.re/image')) # Debug picture if DEBUG: print(random_post) # Get Images if use_img2img or use_deepbooru: image_urls = [random_post['file_url']] if use_last_img else preview_urls for img in image_urls: response = requests.get(img, headers=api_url.headers) last_img.append(Image.open(BytesIO(response.content))) new_prompts = [] # Cleaning Tags for prompt in prompts: prompt_tags = [tag for tag in prompt.split(',') if tag.strip() not in bad_tags] for bad_tag in bad_tags: if '*' in bad_tag: prompt_tags = [tag for tag in prompt_tags if bad_tag.replace('*', '') not in tag] new_prompt = ','.join(prompt_tags) if change_dash: new_prompt = new_prompt.replace("_", " ") new_prompts.append(new_prompt) prompts = new_prompts if len(prompts) == 1: print('Processing Single Prompt') p.prompt = f"{p.prompt},{prompts[-1]}" if p.prompt else prompts[-1] if chaos_mode in ['Chaos', 'Less Chaos']: negative_prompt = '' if chaos_mode == 'Less Chaos' else p.negative_prompt p.prompt, negative_prompt = generate_chaos(p.prompt, negative_prompt, chaos_amount) p.negative_prompt = f"{p.negative_prompt},{negative_prompt}" if p.negative_prompt else negative_prompt else: print('Processing Multiple Prompts') negative_prompts = [] new_prompts = [] if chaos_mode == 'Chaos': for prompt in prompts: tmp_prompt, negative_prompt = generate_chaos(prompt, p.negative_prompt, chaos_amount) new_prompts.append(tmp_prompt) negative_prompts.append(negative_prompt) prompts = new_prompts p.negative_prompt = negative_prompts elif chaos_mode == 'Less Chaos': for prompt in prompts: tmp_prompt, negative_prompt = generate_chaos(prompt, '', chaos_amount) new_prompts.append(tmp_prompt) negative_prompts.append(negative_prompt) prompts = new_prompts p.negative_prompt = [p.negative_prompt + ',' + negative_prompt for negative_prompt in negative_prompts] else: p.negative_prompt = [p.negative_prompt for _ in range(0, p.batch_size * p.n_iter)] p.prompt = prompts if not p.prompt else [f"{p.prompt},{prompt}" for prompt in prompts] if use_img2img: if len(last_img) < p.batch_size * p.n_iter: last_img = [last_img[0] for _ in range(0, p.batch_size * p.n_iter)] if negative_mode == 'Negative': # remove tags from p.prompt using tags from the original prompt orig_list = self.original_prompt.split(',') if isinstance(p.prompt, list): new_positive_prompts = [] new_negative_prompts = [] for pr, npp in zip(p.prompt, p.negative_prompt): clean_prompt = pr.split(',') clean_prompt = [tag for tag in clean_prompt if tag not in orig_list] clean_prompt = ','.join(clean_prompt) new_positive_prompts.append(self.original_prompt) new_negative_prompts.append(f'{npp},{clean_prompt}') p.prompt = new_positive_prompts p.negative_prompt = new_negative_prompts else: clean_prompt = p.prompt.split(',') clean_prompt = [tag for tag in clean_prompt if tag not in orig_list] clean_prompt = ','.join(clean_prompt) p.negative_prompt = f'{p.negative_prompt},{clean_prompt}' p.prompt = self.original_prompt if negative_mode == 'Negative' or chaos_mode in ['Chaos', 'Less Chaos']: # NEGATIVE PROMPT FIX neg_prompt_tokens = [] for pr in p.negative_prompt: neg_prompt_tokens.append(model_hijack.get_prompt_lengths(pr)[1]) if len(set(neg_prompt_tokens)) != 1: print('Padding negative prompts') max_tokens = max(neg_prompt_tokens) for num, neg in enumerate(neg_prompt_tokens): while neg < max_tokens: p.negative_prompt[num] += random.choice(p.negative_prompt[num].split(',')) # p.negative_prompt[num] += '_' neg = model_hijack.get_prompt_lengths(p.negative_prompt[num])[1] if limit_tags < 1: if isinstance(p.prompt, list): p.prompt = [limit_prompt_tags(pr, limit_tags, 'Limit') for pr in p.prompt] else: p.prompt = limit_prompt_tags(p.prompt, limit_tags, 'Limit') if max_tags > 0: if isinstance(p.prompt, list): p.prompt = [limit_prompt_tags(pr, max_tags, 'Max') for pr in p.prompt] else: p.prompt = limit_prompt_tags(p.prompt, max_tags, 'Max') if use_same_seed: p.seed = random.randint(0, 2 ** 32 - 1) if p.seed == -1 else p.seed p.seed = [p.seed] * p.batch_size # LORANADO p = self.loranado(lora_enabled, lora_folder, lora_amount, lora_min, lora_max, lora_custom_weights, p, lora_lock_prev) if use_deepbooru and not use_img2img: self.last_img = last_img tagged_prompts = self.use_autotagger('deepbooru') if isinstance(p.prompt, list): p.prompt = [modify_prompt(pr, tagged_prompts[num], type_deepbooru) for num, pr in enumerate(p.prompt)] p.prompt = [remove_repeated_tags(pr) for pr in p.prompt] else: p.prompt = modify_prompt(p.prompt, tagged_prompts, type_deepbooru) p.prompt = remove_repeated_tags(p.prompt[0]) if use_img2img: if not use_ip: self.real_steps = p.steps p.steps = 1 self.last_img = last_img if use_ip: controlNetModule = importlib.import_module('extensions.sd-webui-controlnet.scripts.external_code', 'external_code') controlNetList = controlNetModule.get_all_units_in_processing(p) copied_network = controlNetList[0].__dict__.copy() copied_network['enabled'] = True copied_network['weight'] = denoising array_img = np.array(last_img[0]) copied_network['image']['image'] = array_img copied_networks = [copied_network] + controlNetList[1:] controlNetModule.update_cn_script_in_processing(p, copied_networks) elif lora_enabled: p = self.loranado(lora_enabled, lora_folder, lora_amount, lora_min, lora_max, lora_custom_weights, p, lora_lock_prev) def postprocess(self, p, processed, enabled, tags, booru, remove_bad_tags, max_pages, change_dash, same_prompt, fringe_benefits, remove_tags, use_img2img, denoising, use_last_img, change_background, change_color, shuffle_tags, post_id, mix_prompt, mix_amount, chaos_mode, negative_mode, chaos_amount, limit_tags, max_tags, sorting_order, mature_rating, lora_folder, lora_amount, lora_min, lora_max, lora_enabled, lora_custom_weights, lora_lock_prev, use_ip, use_search_txt, use_remove_txt, choose_search_txt, choose_remove_txt, search_refresh_btn, remove_refresh_btn, crop_center, use_deepbooru, type_deepbooru, use_same_seed, use_cache): if use_img2img and not use_ip and enabled: print('Using pictures') if crop_center: width, height = p.width, p.height self.last_img = [resize_image(img, width, height, cropping=True) for img in self.last_img] else: width, height = self.check_orientation(self.last_img[0]) final_prompts = p.prompt if use_deepbooru: tagged_prompts = self.use_autotagger('deepbooru') if isinstance(p.prompt, list): final_prompts = [modify_prompt(pr, tagged_prompts[num], type_deepbooru) for num, pr in enumerate(p.prompt)] final_prompts = [remove_repeated_tags(pr) for pr in final_prompts] else: final_prompts = modify_prompt(p.prompt, tagged_prompts, type_deepbooru) final_prompts = remove_repeated_tags(final_prompts) p = StableDiffusionProcessingImg2Img( sd_model=shared.sd_model, outpath_samples=shared.opts.outdir_samples or shared.opts.outdir_img2img_samples, outpath_grids=shared.opts.outdir_grids or shared.opts.outdir_img2img_grids, prompt=final_prompts, negative_prompt=p.negative_prompt, seed=p.seed, sampler_name=p.sampler_name, scheduler=p.scheduler, batch_size=p.batch_size, n_iter=p.n_iter, steps=self.real_steps, cfg_scale=p.cfg_scale, width=width, height=height, init_images=self.last_img, denoising_strength=denoising, ) proc = process_images(p) processed.images = proc.images processed.infotexts = proc.infotexts if use_last_img: processed.images.append(self.last_img[0]) else: for num, img in enumerate(self.last_img): processed.images.append(img) processed.infotexts.append(proc.infotexts[num + 1]) def random_number(self, sorting_order, size): """Generates random numbers based on the sorting_order Args: sorting_order (str): the sorting order. It can be 'Random', 'High Score' or 'Low Score' size (int): the amount of random numbers to generate Returns: list: the random numbers """ global COUNT if COUNT > POST_AMOUNT: # Modified to use COUNT instead of POST_AMOUNT COUNT = POST_AMOUNT # If there are more than 100 images, use POST_AMOUNT weights = np.arange(COUNT, 0, -1) weights = weights / weights.sum() if sorting_order in ('High Score', 'Low Score'): random_numbers = np.random.choice(np.arange(COUNT), size=size, p=weights, replace=False) else: random_numbers = random.sample(range(COUNT), size) return random_numbers def use_autotagger(self, model): """Use the autotagger to tag the images Args: model (str): the model to use. Right now only 'deepbooru' is supported Returns: list: the tagged prompts """ if model == 'deepbooru': if isinstance(self.original_prompt, str): orig_prompt = [self.original_prompt] else: orig_prompt = self.original_prompt deepbooru.model.start() for img, prompt in zip(self.last_img, orig_prompt): final_prompts = [prompt + ',' + deepbooru.model.tag_multi(img) for img in self.last_img] deepbooru.model.stop() return final_prompts