Sign In

A useful set of scripts: Randomize and more!

4
A useful set of scripts: Randomize and more!

Since I've gotten started I've had to learn a few things even though some of it may eventually become useless or need to be repurposed and integrated once I start picking up comfyui, but for my Forge/A1111 WebUi brothers I have a set of scripts that may help you.

Prompt Generation: Randomize, Fluctuate, and Control Weight and Prompt Tags.

So this first bit is all about Large Batch prompt tasks. When I have nothing queued up to be produced, it means I can experiment with prompts, Lora's, checkpoints, and break stuff with pure throughput generation looking for interesting image subsets or tag/weights to use for more specific higher level generation.

This is where my Batch Prompt Generator comes in handy. It takes a simple text file with custom keyword arguments and puts them through a python script to give large batches of prompt outputs.

As an example:

$Random_tag() This sets up a set of tags or groups of tags to be chosen at random every time it generates a prompt.
Further we can augment it's behavior like so

$random_tag((Hat, Hood),(&(Scarf, Sunglasses, Headband)&), (Facemask))$

This should output one of the following:

Hat, Hood

(Scarf, Sunglasses, Headband)

Facemask

You can also do things more advanced than this, like swapping or randomly adding different Lora tags etc.

Random Numbers and Controlled Numbers

The next are far more simple and just mathematic functions I initially used to try and manipulate slider Lora for video frame generation.

&sine_wav, 2, -2, 60, 2& -- This script generates numbers in a wave pattern between 2 and -2 over the period of 60 prompt generations with a percision of 2 decimal places: 0.00. The idea being you can shift between the extremes of weights over a controlled period of generations.

&rand, 2, -2, 2& -- Much the same, just generates a random number between 2 and -2 up to .00 decimal places.

Process Control and Large Batch Control Tags

Another thing I decided I needed, and someone might find useful, is being able to stop adding or start adding certain tags into the prompts they are generating after certain number of prompts generated. The idea being that say if you have a Lora or Prompt meant to start walking, you could drop the walking tag and start a running one at the same time in a large batch of prompts.

$start 5, $top 10 -- This will start a prompt it is contained in after 5 generated prompts, and stop adding it to further prompts after 10 generations.

The last bit of this script pulls all of these commands and supportive syntax out of the batch generations so you can just plug and go.

How to use

First you need two text files to make a .bat and a .py

process_prompts.bat

@echo off
echo Enter the path to the input file containing prompts:
set /p input_file=
echo Enter the name for the new output text file (without extension):
set /p output_file_name=
echo Enter the total number of generations to process:
set /p total_generations=
python process_prompts.py "%input_file%" "%output_file_name%.txt" "%total_generations%"
echo Processing complete.
pause

process_prompts.py

import sys
import re
import math
import random

def sine_wave(index, total, min_weight, max_weight, angle, precision):
    amplitude = (max_weight - min_weight) / 2
    midline = (max_weight + min_weight) / 2
    angle_rad = math.radians(angle)
    weight = midline + amplitude * math.sin(angle_rad * (index / total) * 2 * math.pi)
    return round(weight, precision)

def random_weight(min_weight, max_weight, precision):
    weight = random.uniform(min_weight, max_weight)
    return round(weight, precision)

def random_tag(tags):
    processed_tags = []
    buffer = ""
    depth = 0  # Depth of nested parentheses
    special_format = False  # Whether we are in a special format group

    i = 0
    while i < len(tags):
        char = tags[i]

        # Check for special format toggle
        if char == '&' and (i == 0 or tags[i-1] != '\\') and (i+1 < len(tags) and tags[i+1] == '('):
            special_format = not special_format
            i += 1  # Skip '&' and '('
            continue
        elif char == '&' and (i+1 < len(tags) and tags[i+1] == ')'):
            i += 1  # Skip '&' and ')'
            special_format = not special_format
            continue

        if char == '(':
            depth += 1
        elif char == ')':
            depth -= 1
            if depth == 0 and special_format:
                # Special format: Keep parentheses in output
                processed_tags.append(buffer + char)
                buffer = ""
                continue

        if char == ',' and depth == 0:
            if buffer.strip():
                processed_tags.append(buffer.strip())
            buffer = ""
        else:
            buffer += char

        i += 1

    if buffer.strip():
        processed_tags.append(buffer.strip())

    chosen_tag = random.choice(processed_tags).strip()
    # Post-process to remove outer parentheses if not in special format
    if chosen_tag.startswith('(') and chosen_tag.endswith(')') and not special_format:
        return chosen_tag[1:-1]
    return chosen_tag


def process_prompt(prompt, index, total_generations):
    sine_wave_pattern = re.compile(r"&sine_wav, (-?\d+\.?\d*), (-?\d+\.?\d*), (-?\d+\.?\d*), (\d+)&")
    random_weight_pattern = re.compile(r"&Rand, (-?\d+\.?\d*), (-?\d+\.?\d*)&")
    random_tag_pattern = re.compile(r"\$random_tag\((.*?)\)\$")
    directive_pattern = re.compile(r"(<[^<>]*\$(top|tart) \d+[^<>]*>)")

    def check_visibility(match):
        content = match.group(0)
        directive, number = re.search(r"\$(top|tart) (\d+)", content).groups()
        threshold = int(number)
        if (directive == "top" and index >= threshold) or (directive == "tart" and index < threshold):
            return ""
        return content

    prompt = re.sub(directive_pattern, check_visibility, prompt)
    prompt = re.sub(sine_wave_pattern, lambda m: str(sine_wave(index, total_generations, float(m.group(2)), float(m.group(1)), float(m.group(3)), int(m.group(4)))), prompt)
    prompt = re.sub(random_weight_pattern, lambda m: str(random_weight(float(m.group(1)), float(m.group(2)), 2)), prompt)
    prompt = re.sub(random_tag_pattern, lambda m: random_tag(m.group(1)), prompt)
    prompt = re.sub(r"\$top \d+|\$tart \d+|\$[^$]*\$", "", prompt)

    return prompt

def main(input_file, output_file, total_generations):
    with open(input_file, 'r') as file:
        prompts = file.readlines()

    processed_prompts = []

    for index in range(total_generations):
        for prompt in prompts:
            processed_prompt = process_prompt(prompt.strip(), index, total_generations)
            processed_prompts.append(processed_prompt)

    with open(output_file, 'w') as file:
        file.write("\n".join(processed_prompts))

if __name__ == "__main__":
    input_file = sys.argv[1]
    output_file = sys.argv[2]
    total_generations = int(sys.argv[3])
    main(input_file, output_file, total_generations)

Next just make a text file with your guide prompt that you want iterated, copy its path, run the .bat, use the path, name the new text file output in the command window, and how many generations you want it to make and your good to go.
You can then drag and drop the new text file (assuming you didn't make a syntax error) and you now have hours and hours of generation time when you're otherwise not wanting to either manually or handling more sensitive generation tweaking.

4

Comments