Skip to content
tomhallmainPublic

About

Custom runner for image generation prompts

Resources

Stars

0 stars

Watchers

1 watching

Forks

Repository files navigation

sd-runner - Image Generation Runner

This code is primarily a prompt engineering application that triggers ComfyUI workflows or stable-diffusion-webui functions using advanced prompt randomization, concept management, and intelligent filtering systems.

Warnings

⚠️ Warnings
  • This was originally developed during early 2024 and some prompts may be out of date with current versions of the respective image generation projects.
  • Though any prompt applied to any model can result in undesirable images despite properly set negative prompts, prompt randomization increases the chance that undesirable images may be generated even if only innocuous terms are included in the prompt, because the randomness allows for wider traversal of the model's latent space. For this reason it is wise to use a local prevalidation and content filtering tool. I recommend using [Weidr](https://github.com/tomhallmain/Weidr) which has many other features in addition to customizable prevalidations based on CLIP as well as HDF5 and PyTorch image classifier models.
  • An English or German dictionary, depending on the selected concepts folder, is used for generating random words, some of which may be found objectionable. A word with a high degree of relation to strong feelings like disgust also tends to carry a lot of prompt weight, even if it is buried in a much larger prompt with no other similar words. Luckily this will often result in an "incoherent" result with earlier models, or a slightly objectionable result with later models, but there is still a chance of problematic images being generated. As a result you may choose not to use the `random_word` prompt variable, or implement a blacklist using the provided blacklist window which blocks prompts with undesirable strings or otherwise drops them from prompts. There is a default blacklist used if none is provided, which requires extra security to clear and even to reveal its concepts. It covers English and 24 other languages at varying depth, and coverage outside English and German is much thinner.
  • In the NSFW and NSFL prompt modes, random words are also drawn by default from a much larger slang corpus of roughly two million entries derived from Urban Dictionary, shipped encrypted in sd_runner/data/urban_dictionary.enc. Great pains were taken to filter it: the default blacklist was applied first, then non-lexical noise was removed, then entries with sexual, violent or gory, or disgusting readings were culled through successive pattern passes, manual review of hundreds of thousands of borderline entries, and an embedding classifier trained on the rejected examples, together removing around half a million further entries and erring on the side of omission throughout. Slang invents euphemisms and double meanings faster than any filter can anticipate, however, so objectionable terms can still appear, and as noted above a single strongly charged word can carry a lot of prompt weight. Keep the blacklist and a prevalidation tool in place when using these modes, or turn the corpus off: uncheck Use Urban Dictionary Corpus in NSFW Modes in the Config window, or set "nsfw_urban_dictionary": false in config.json.
  • If sharing your computer with multiple users, consider setting a password to lock the blacklist and other features.
  • Continuously viewing random images may cause small lapses in sanity. Employ total randomness with caution.
  • The application can be addictive due to its infinite generation capabilities and random prompt variations. Consider enabling a timed shutdown schedule to automatically stop generation at a reasonable hour and prevent excessive usage.

Running the Application

  • Entry point: python app_gui.py or start.bat
  • Without a window: python app_headless.py — serves the request front ends only; see Running without the UI

The application uses PySide6 with custom theming, optional frameless windows, and configurable UI scaling (ui_scale_factor in config.json).

Where your data lives

Everything the application writes is kept in its data folder, %APPDATA%\sd_runner on Windows and ~/.local/share/sd_runner elsewhere: configs/config.json, the encrypted app_info_cache.enc with its backups, the logs, and caches. Files that earlier versions kept in the repo (app_info_cache.enc*, configs/config.json, sd_runner/data/pending_translation.json) are moved there the first time the app is started from the repo. A file in the data folder is replaced by the one from the repo unless the repo's is newer; then both are kept and the log says so. Relative paths in config.json (the default "." save paths included) are taken relative to the data folder.

Building an executable

python build_exe.py --with-oqs builds a single-file executable with Nuitka, dist/SDRunner.exe (no .exe suffix on macOS/Linux). --with-headless also builds dist/SDRunnerHeadless.exe, which serves the request front ends without a window; it about doubles the build time. Run it on the OS you are building for; Nuitka does not cross-compile.

  • Requirements: the Python used for development (3.11), and a C compiler: Visual Studio Build Tools on Windows (or let Nuitka download MinGW), the Xcode Command Line Tools on macOS. The build includes torch and transformers and takes a long time and several GB of disk space.
  • Memory: the last step of each compile compresses the payload, with one compression thread per Nuitka job. Before it starts, the build estimates how much memory that needs and, if less is free, waits until it is (close other programs, or press Enter to go ahead). The wait is a Nuitka plugin shipped by app-release (below): with app-release installed, or the build run through it, the build finds the plugin; without it the build warns and does not wait. --min-free-memory=<GiB> sets the amount, and 0 turns the wait off. --jobs=N uses fewer threads, which needs less memory but also makes the C compile slower.
  • Build environment: packages are installed into .venv-build from requirements-build.txt. --current-env builds with the running interpreter instead; for CUDA torch, install it into .venv-build from the PyTorch index first. Each build records the environment in build/nuitka/build-requirements.lock.txt; --lock=<file> installs from such a file to repeat a build (delete .venv-build first for an exact copy). build/nuitka/build_info.json (version, build id, whether OQS is included, package versions, and this checkout's commit and uncommitted changes) is bundled; the executable logs it at startup. Each step's duration is logged to the console and to build/logs/build_exe-<build id>.log.
  • Version: taken from the VERSION file at the repo root, which is also bundled.
  • OQS: --with-oqs bundles the liboqs library for quantum-safe key encapsulation. Without it the executables cannot read a cache encrypted with OQS keys: they start without it, leave it untouched, and refuse password-protected actions.
  • Smoke test: after building, the script runs each executable with --smoke-test against a scratch data folder; the build fails if it does.
  • First start: an executable unpacks itself to unpacked/ in the data folder on its first start, so later starts are fast; folders of older builds are removed at startup.
  • Same data: an executable uses the same data folder as the app started from the repo. Start the app from the repo once before the first executable start, so that a cache still in the repo is moved there.
  • Concepts: concept sets named relatively in config.json (concepts, Konzepte) are copied from the executable into the data folder on first use, and the Concept Editor edits that copy. To keep editing the repo's lists, name their folders with absolute paths in concepts_dirs.
  • Releases: app-release, a separate tool installed with pipx, is run as app-release <checkout>. It rebuilds the executables in dist/ that are behind the checkout's HEAD through build_exe.py, stages them in dist/release/v<version>/ with the lock file, a SHA256SUMS file and release notes, then tags and drafts a GitHub release; --no-upload stops after staging. See its README for the options. The executables are unsigned, and build_info.json carries the path of the checkout they were built from.
  • External: generation backends (ComfyUI and the others), model weights (Hugging Face cache, models_dir) and sd_prompt_reader_loc are not part of the build.

Configuration Options

Set Total (sidebar, default 2): how many times to run the workflow — with prompt randomization the same workflow produces a different result each time. Set to -1 to run infinitely.

gen_order: A single run can include multiple resolutions, models, vaes, loras, IP adapters, and control nets. Modify the gen_order list to set the order in which these combinations are run.

redo_parameters: Redo an image using the stored workflow found in the image, but with desired different parameters.

model_presets: For specific models set clip_req and prompt_tags specific to prompt modes or generally.

prompt_presets: For specific prompt modes add a chance to prepend or append tokens to the prompt.

default_negative_prompt: Any of the defaults are useful, but the negative default can be especially useful as it is often less likely to change.

purge_blacklisted_prompt_history (default true): When enabled, the encrypted cache will automatically purge any saved run history entries whose prompts contain blacklisted items. NOTE: This setting does not prevent running blacklisted prompts, by default they are disallowed but to update that behavior please modify the settings in the blacklist window.

comfyui_output_dir (default null): Where ComfyUI writes its images. Leave unset and it is taken to be <comfyui_loc>/output; set it for an install that writes somewhere else, such as one started with --output-directory. Besides naming saved images, this is the directory searched when ComfyUI finishes a generation but its history request fails — if exactly one new image has appeared there, it is taken as the output rather than losing the generation. Two or more and nothing is assumed, since generations run concurrently into the same folder.

encrypt_log_file (default true): Whether the log file is encrypted. Set to false (or uncheck it in the Config window) to write a plaintext .log file instead; encrypted logs can be read with scripts/decrypt_logs.py.

Prompt Syntax

Preset variables can be defined in the config to expand into full prompt text. To access these in the prompt UI, prepend $ or surround them with curly braces, and upon running the prompt the expansion will occur in the UI, overwriting the original prompt.

"this is my $promptvar" -> "this is my expanded prompt text from promptvar"

To use a variable without expanding it in the UI prompt, prepend two dollar signs instead of one.

"this is my $$promptvar" -> "this is my $$promptvar" (UI after starting a run) -> this is my expanded prompt text from promptvar (final prompt)

To define a set of words to randomly choose from, surround a comma-separated list with double square brackets, and one of the options will be chosen. All options are given equal choice weight of 1 unless one of the options has an appended colon at the end specifying the desired chance for the choice. The choice set will not be overwritten in the UI prompt.

"A [[red,blue,yellow:2]] car" -> "A yellow car"

To pick a value from a numeric or character range instead of a fixed list, use -- as a separator. An optional third value sets the step (default 1). Ranges are always inclusive of the end value. Floats are supported; decimal precision is inferred from the values given.

"[[1--10]] steps" -> "7 steps" (one possible result)

"[[0.5--1.0--0.1]] strength" -> "0.8 strength" (one possible result)

"[[a--z]] style" -> "m style" (one possible result)

Nested choice sets are also available:

"In over [[[my,your,his,her] head,[our,their] heads]]" -> "In over my head" OR "In over our heads"

Note that choice sets can be stored in preset prompt variables to cut down on visible prompt lengths.

A @@filepath token can appear anywhere in a prompt (or as an inline variable value) to draw one random line from a text file as if it were a choice set. The path must contain a dot (e.g. @@animals.txt, @@/abs/path/file.txt). Bare filenames are looked up in the concepts directory with .txt appended automatically.

"A @@animals.txt in a forest" -> "A lion in a forest" (one possible result)

Variables can also be defined inline at the top of the prompt itself, without going through the Expansions window. Two syntaxes are supported — either prefix each definition line with ||| and separate the name from the value with ->, or prefix with :: and use =. Access the variable with the $$ accessor. The definition block is stripped from the final delivered prompt along with any trailing whitespace, so the prompt body receives only the resolved text.

|||MyVar1->[[a,b,c]]
|||MyVar2->[[d,e,f]]
|||MyVar3->my_current_value
A $$MyVar1 image in $$MyVar2 tones, quality: $$MyVar3

-> "A b image in e tones, quality: my_current_value" (one possible result)

The :: syntax is an alternative form that may feel more natural:

::Animal = [[cat,dog,bird]]
::Style = watercolor
A $$Animal in $$Style style

-> "A dog in watercolor style" (one possible result)

Inline variable values support the full choice-set syntax ([[...]]), so $$MyVar1 above would randomly resolve to a, b, or c each run. Inline variables take priority over config wildcards and stored expansions when names collide. The definition lines must appear at the very start of the prompt with no blank lines between them.

Supported Systems, Architectures, and Workflows

Systems

Local backends

  • ComfyUI — Primary target; full node-graph workflow support across all architectures
  • SDWebUI (Automatic1111) — Standard A1111 API; txt2img, img2img, ControlNet, IP-Adapter, LoRA, upscale
  • Forge — Drop-in SDWebUI replacement at a different port; identical API and workflow coverage
  • SD.Next — SDWebUI-compatible API with extended sampler support
  • SwarmUI — Session-based REST API; LoRA injected via prompt embedding; txt2img, ControlNet, img2img, IP-Adapter
  • InvokeAI — Node-graph queue API (v3.x); model keys resolved by name lookup; txt2img, LoRA, ControlNet, img2img, IP-Adapter
  • Fooocus — Synchronous REST API (--listen --api); SDXL-only; performance presets map to sampler field; txt2img, LoRA, ControlNet, img2img, IP-Adapter, upscale

Cloud / API backends

  • Stability AI — Multipart POST to /v2beta/stable-image/generate; aspect-ratio sizing
  • BFL Flux — api.bfl.ai; async poll; Flux models only
  • Fal.ai — fal.run; sync POST; native batch support
  • HuggingFace — Inference API; binary PNG response; 503 retry loop
  • Replicate — /v1/predictions with Prefer: wait; polling fallback; URL output list
  • OpenAI (DALL·E / GPT-Image) — dall-e-2, dall-e-3, gpt-image-1; per-model size constraints; n=1 loop for dall-e-3
  • Grok (xAI) — OpenAI-compatible endpoint at api.x.ai
  • Google Imagen — Vertex-style predict endpoint; aspect-ratio sizing; base64 response
  • Ideogram — api.ideogram.ai/generate; named aspect-ratio mapping; V2/V2 Turbo/V3

Model Architectures

The following model architectures are supported. Resolution tags automatically scale to match each architecture's expected output dimensions:

  • SD 1.5
  • SDXL
  • Illustrious
  • Flux
  • Flux.2 Klein (4B and 9B)
  • Flux.3
  • Krea 2
  • MiniMax H3
  • Chroma
  • ZImageTurbo
  • Qwen

Architecture is taken from the filename first, then safetensors metadata and tensor keys, then the folder the file lives in. Checkpoints are resolved under both WebUI (Stable-diffusion) and ComfyUI (diffusion_models, unet, checkpoints) roots; LoRAs are resolved under Lora, LoRA, and loras. Model files may be organized in scoped subfolders under those roots (for example Stable-diffusion/SD1.5, diffusion_models/FluxKlein, Lora/SD1.5).

Workflows

The table below covers ComfyUI (C) and SDWebUI (S) — the two backends with full architecture-specific tracking. Flux, Chroma, ZImageTurbo, and Qwen have not been implemented/tested in SDWebUI, so those columns are ComfyUI-only.

Flux.2 Klein, Flux.3, Krea 2, and MiniMax H3 are ComfyUI-only as well, and are omitted from the table because each one only has simple image gen. Flux.2 Klein and Flux.3 simple generation both use the Flux.3 Klein graph, and that graph accepts Flux.2 Klein weights. Flux.2 Klein also has simple image gen with LoRA and image edit. MiniMax H3 simple image gen writes video. Resolution tags land on 1024 for Flux.2 Klein and Flux.3, 1328 for Krea 2 (inside its 1K–2K range), and 768 for MiniMax H3.

Additional local backends: Forge and SD.Next share SDWebUI's full coverage (treat as S). SwarmUI, InvokeAI, and Fooocus support txt2img, LoRA, ControlNet, img2img, and IP-Adapter for SD 1.5/SDXL architectures; Fooocus is SDXL-only.

Cloud backends: All support txt2img regardless of architecture. LoRA support varies by provider; img2img and IP-Adapter are available on select backends (see itemized list above).

Workflow Description SD1.5 SDXL Illust. Flux Chroma ZIT Qwen
Simple Image Gen Basic text-to-image generation C/S C/S C/S C C C C
Simple Image Gen Lora Text-to-image with LoRA support C/S C/S C/S C C C C
Simple Image Gen Tiled Upscale Tiled upscaling workflow C C
Instant LoRA Instant LoRA training workflow C
IP Adapter IP-Adapter style transfer C/S C/S C/S ? ? ? ?
Image to Image Image-to-image transformation C/S C/S C/S ? ? ? ?
ControlNet ControlNet conditioning C/S C/S C/S
Inpaint ClipSeg Inpainting with ClipSeg C C
Animate Diff Animation generation C C
Renoiser Image denoising workflow C C C
Upscale Simple Simple upscaling C/S C/S C/S
Upscale Better Advanced upscaling C C C

Legend: C = ComfyUI, S = SDWebUI (also Forge/SD.Next), C/S = Both

Second Derivative

A checkbox on the sidebar. When it is on, every image a run generates is fed straight back through the same workflow with the same settings — same model, prompt, LoRA, sampler, adapter strengths, everything — with only the input image changed to the image that was just made. Ask for one image and you get two: the image, then the image made from it. Ask for two and you get that twice.

It applies only to workflows that take an input image (IP Adapter, Image to Image, Image Edit, ControlNet, Renoiser) and is silently inactive for the others, so it is safe to leave on while switching workflows. Depth is two — a derivative is not itself derived from.

ComfyUI only for now: it is the backend that reports where it wrote each image. On other backends the derivative is skipped and logged as an error.

Image Resolutions

Any of the following resolution options can be used: square, portrait1, portrait2, portrait3, landscape1, landscape2, landscape3. These resolution tags work with all supported model architectures and automatically scale dimensions to match each architecture's expected output size.

To randomly skip a resolution during generation, attach a "*" to the resolution tag in the resolutions box. This is one way to help inject more randomness of output.

Note the default behavior of Control Net and IP Adapter workflows is to inherit from the source image a resolution with a matching aspect ratio. The Override resolutions checkbox allows you to instead use the predefined resolution tags for these workflows.

Prompt Presets

The presets window allows you to manage presets for specific prompt and prompt mode combinations. Use this window to add presets, delete presets and to apply them to the UI.

If a presets schedule is defined in the config, the checkbox Run Preset Schedule will be enabled and presets will be run at the specified schedule if this boolean is set to True.

Stashed Configs

The second tab of the presets window stores named run configurations. A stash holds everything a preset does not, so applying one leaves the prompt mode, prompt tags and edit suffix untouched and the two can be used together. Unlike stepping back through run history, a stash is named rather than positional and does not age out. Prompt text is not stored in a stash.

Preset Schedules

After having defined presets using the presets window, you can create and modify batch schedules to run presets using the preset schedules window. Giving the preset schedules a unique name is helpful for quickly switching between them.

A preset schedule should specify the number of times each preset should run, as well as the order. If the run count for a preset is set to -1, it should inherit the run count from the value set before starting the run schedule.

Note that similar to normal image generation runs, run schedules can be queued while one is already running.

The run queue can be paused at any time using the Pause Queue button (sidebar or Runs window). The current job completes normally; subsequent queued jobs are held until Resume Queue is clicked. Pending jobs are also saved to cache on exit and restored as a paused queue on next launch.

Concepts Folder

The prompts are generated using text concepts files. Each line in each file represents a concept that can be added randomly to the prompt, based on the prompter configuration.

Additional concepts folders can be defined by adding paths to the concepts_dirs array in config (see sd_runner/data/config_example.json). Restart the UI and select the desired folder from the concepts dropdown in the sidebar. The repo includes English concepts/ and German Konzepte/; both are listed in the example config.

A folder's optional concept_set.json states its language and, for a translation, the set it came from. A line in a translated set is then blocked if the line it translates is blocked.

Concept Editor Window

A UI window for managing your concept files. Search, add, edit, or delete concepts across different categories. Access it via the "Edit Concepts" button in the main interface.

Blacklist Window

A UI window for managing terms to filter from generated prompts. Add, remove, or toggle terms, and import/export your blacklist in CSV, JSON, or TXT formats.

Exception Patterns: Each blacklist item can optionally include an exception pattern (regex) that will un-filter tags that would otherwise be filtered by the primary pattern. This allows for fine-grained control - for example, you can blacklist "cat" but use an exception pattern like "cathedral" to allow that specific term through.

Applies To: An item can check everything, generated content only, or user prompts only. A user-prompts-only item does not filter concept lines or dictionary words, so the term can still reach a prompt.

Languages: An item can have a language code. Concept lists are checked against every language, while typed prompts are checked against the languages detected in their text, along with all items that have no language.

Enhanced Security: The blacklist features multi-level password protection with separate permissions for revealing and editing concepts. A default encrypted blacklist is provided for first-time users, and blacklist operations require security to be configured with user confirmation to prevent accidental data loss and exposure of problematic concepts.

Similarity Check

The Similarity Check tab in the Blacklist window adds a complementary cosine-similarity filter that catches evasion the regex blacklist may miss (e.g. deliberate misspellings, Unicode substitutions, or semantically equivalent phrasing with a CLIP backend).

Configure it in Blacklist → Similarity Check:

  • Blocked concepts — one phrase per line; the engine compares the full prompt against each phrase and blocks it when the cosine similarity score meets or exceeds the threshold.
  • Threshold — 0.50–1.00 (default 0.85). Lower values are more sensitive.
  • Enable checkbox — off by default; enable when phrases are configured.

The similarity engine is selected automatically in priority order:

Backend Requirement Capability
ONNX CLIP onnxruntime + a .onnx text-encoder model + clip or transformers for tokenization Full semantic similarity
Torch CLIP clip package + torch Full semantic similarity
N-gram (fallback) numpy only — always available Character-level similarity; catches misspellings and substitution tricks but not unrelated synonyms

Setup for CLIP backends: Set clip_model_path in Config → Similarity Check to the path of your .onnx CLIP text encoder file (for ONNX) or a named Torch CLIP model (e.g. ViT-B/32). With no path set the n-gram backend is used automatically with no additional dependencies. The optional onnxruntime package can be uncommented in requirements.txt.

A similarity violation always prevents execution regardless of the blacklist mode setting, and respects the Silent Removal toggle for alert visibility.

Password Protection

The application includes a password protection system that can be configured to require authentication for sensitive actions. Password settings are stored securely in the encrypted application cache. Access the Password Administration window using <Control-P> to configure which actions require password verification.

Protected actions include:

  • Access Password Administration (defaults to protected)
  • NSFW/NSFL Prompt Modes (defaults to protected)
  • Reveal Blacklist Items (defaults to protected)
  • Edit Blacklist (defaults to protected)
  • Edit Schedules
  • Edit Expansions
  • Edit Presets
  • Edit Concepts

The keys encrypting the cache are backed up automatically to an external drive (set SD_RUNNER_KEY_BACKUP_DIR to choose where). Run python scripts/key_material.py to check that backup's state, backup to take one by hand, or restore to put the keys back from a backup if the cache can no longer be opened.

Server

Set configuration options for a server port to make use of the server while the UI is running. Calls to the server made with Python's multiprocessing client run alongside whatever you are doing in the UI. This can be helpful to use in conjunction with other applications that involve images. For an example, see this class.

A request that carries its own parameters (renoiser, control_net, ip_adapter, image_edit, img2img, redo_prompt, take_prompt) runs without disturbing the sidebar: anything it does not specify — the model, resolutions, counts and the rest — comes from your current settings, and the request's own values are not saved over them. Such runs are marked in the progress label with the name of the client that asked for them, and shown under an Origin column in the runs window; they queue behind any run already in progress, and once that queue is full they are staged instead — saved with the rest of the cache, so a restart resumes them rather than dropping them. The last_settings request is the exception — reusing what is currently set in the UI is the point of that command — and cancel and revert_to_simple_gen act on the UI directly.

A request is refused, rather than run, when it would produce nothing useful: no models found when your model tags match no installed model, and run too large when the estimate exceeds server_run_max_seconds (unset means no ceiling). Both name the reason in the reply, since there is no user at that end to ask.

Every accepted request replies with a run_id — run_batch replies with a run_ids list, one per accepted item in order — which identifies that run from acceptance onward, including across a restart while it sits staged. It is a handle, not a subscription: nothing is pushed when the run finishes, and nothing retains the id afterwards.

A client names itself by putting a client_id on any message it sends; it is optional, sticky for the connection, and only needs sending once. A client that sends none is shown as server, since two clients on the same machine cannot be told apart from the connection alone.

A client can also ask whether a backend is working: {"command": "health_check", "level": 1}. Level 1 confirms the backend answers its API; level 2 additionally confirms it has a model loaded and is not stuck. Add "software": "ComfyUI" to ask about a specific backend rather than the selected one. A backend busy with your own run answers ok with a generation_in_progress note — activity is evidence of health.

Launching backends automatically

Set backend_launch_commands to have SD Runner start a backend for you, mapping the backend name to the command you would type yourself. A configured backend is not started when SD Runner opens — it starts the first time a run actually needs it, so an install with several configured backends does not pay to warm up ones that go unused this session. That first run waits for the backend to answer before it begins generating, which on a cold start can take minutes; later runs find it already up and start immediately.

"backend_launch_commands": {
    "ComfyUI": "C:\\miniconda3\\envs\\comfy\\python.exe main.py --port 8188",
    "SDWebUI": "C:\\miniconda3\\envs\\webui\\python.exe launch.py"
}

While a launch is in progress a small notice appears in the corner of the window — it changes the moment the backend answers its first health check, and closes itself a few seconds later. It only appears for a launch that actually waits: a backend already running is adopted without one.

Each backend runs as its own OS process, so backends needing different Python or conda environments coexist. The command goes through a shell (cmd.exe /s /c on Windows, /bin/sh -c elsewhere), so anything you would type at a prompt works — including the launcher scripts these backends ship and recommend:

"backend_launch_commands": {
    "ComfyUI": "run_nvidia_gpu.bat",
    "SwarmUI": "launch-windows.bat"
}

Because the shell is what SD Runner holds, stopping a backend kills the whole process tree rather than just the script — otherwise the server the script started would be orphaned, still holding its port and the GPU.

It runs from that backend's *_loc directory. There are no built-in commands — install layouts vary too much for a guess to be reliable, and a wrong one fails only after a long startup wait. A backend already running is adopted rather than started twice, and is left running on exit; only processes SD Runner started are stopped.

log_backend_output (default false) repeats the backend's own output in the SD Runner log. Off, it is logged at debug level instead, so a failed launch is still diagnosable without a serving backend's chatter interleaved with everything else.

backend_startup_timeout (default 600s) is how long SD Runner waits for a backend to answer. Running out does not kill it — these are slow to start for real reasons, and a backend that has not exited is still working, so it is left to carry on and reported as still starting. A health check arriving during that window answers starting rather than error. If a backend really is stuck, stop it yourself.

Model Context Protocol

SD Runner can also present itself to an MCP client — Claude Desktop, an editor, an agent harness — as a set of tools. This is a second front end over the same actions the server above exposes; both can run at once, and neither knows about the other.

It needs the optional mcp package (pip install -r requirements-optional.txt) and a port:

"mcp_server_port": 8765

Without a port it does not start, and without the package it logs once and declines — the app runs normally either way.

The tools are generate (naming one of the same commands the server takes, with that command's arguments), generate_batch, cancel, revert_to_simple_gen, health_check, and run_status.

generate returns when the run is queued, not when it has finished — the images come later and are not part of the result. It replies with a run_id, and generate_batch replies with run_ids, one per accepted item in order. Poll run_status to find out when work is done: with no arguments it answers for the client as a whole, and given a run_id it reports that run as running, queued, staged, or unknown once it is no longer outstanding. Nothing keeps a record of a finished run, so unknown covers both "done" and "never issued".

Alongside the tools it exposes three read-only resources, so a client can find out what it is driving before asking for anything:

Resource URI Holds
current_workflow sdrunner://workflow/current the workflow, model tags, resolutions and counts a run would use right now
preset_names sdrunner://presets/names the saved preset names, which a generate request can then ask for by edit_suffix
run_history sdrunner://runs/history the most recent runs, newest first — capped, since the entries are near-duplicates

These read stored settings rather than the window, so they answer the same way with no UI running.

It binds to localhost only. The other server authenticates with a key belonging to its transport, which does not carry over to HTTP, and the MCP SDK's own answer is a full OAuth resource server rather than a shared secret. Until that exists, remote binds are refused. mcp_server_token is reserved for it and currently does nothing — if you set one, the server refuses to start rather than run while leaving it unenforced.

Running without the UI

python app_headless.py serves both front ends with no window — on a machine with no display and no PySide6 installed. Same config.json, same saved settings, same backend management, and the MCP server starts when a port is configured. It takes the same single-instance lock as the windowed app, so the two do not run at once over one cache.

Requests carrying their own parameters are served as usual, from whatever the last windowed session saved. The two that mean the screen are refused with no user interface: last_settings and revert_to_simple_gen. Anything the UI would ask about is declined rather than assumed, including the confirmation before a long run — so set server_run_max_seconds if you want large requests refused with a reason instead.

Image to Prompt

The PySide6 UI includes an Image to Prompt window that can generate prompt text from an image without starting image generation. Current backends include captioner and fast-tagger support (with automatic model download), with VLM left as a scaffolded backend for future work.

Notes

For stable-diffusion-webui, the img2img workflow is set up as the IP Adapter workflow. In ComfyUI, the Image to Image workflow provides a dedicated image-to-image transformation with the same inverse strength behavior, while the IP Adapter workflow uses IP-Adapter models for style transfer. In all img2img cases, modifying the IP adapter strength in the UI will inversely modify the denoising strength to produce a similar effect as IP adapter strength would for that workflow.

The following locales are supported in the UI: en (English), de (Deutsch), es (Español), fr (Français), ja (日本語), ko (한국어), pt (Português), ru (Русский), zh (中文). Prompt text language follows the selected concepts directory (English in concepts/, German in Konzepte/); see Concepts Folder above. Other written languages with Unicode support can use the same pattern by adding another concepts path to concepts_dirs.

Excepting the concepts files, application data is encrypted for security, including the log file unless encrypt_log_file is turned off.

Optional (platform-specific): pip install -r requirements-optional.txt installs extras for your OS only (e.g. on macOS, Foundation/Cocoa for keychain integration; on Linux, D-Bus for Secret Service lock).

Third-party components and their licences are listed in THIRD_PARTY_NOTICES.md.

Keyboard Shortcuts

  • <Control-Return>: Run workflows
  • <Shift-R>: Run workflows (alternative)
  • <Shift-N>: Next preset
  • <Control-P>: Open Password Administration window
  • <Control-Q>: Quit application
  • <Prior>/<Next>: Navigate configuration history
  • <Home>/<End>: Go to first/last configuration

About

Custom runner for image generation prompts

Resources

Stars

0 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages